Skip to content

Commit 533c151

Browse files
OEvgenycompulim
andauthored
Fluent: integrate webchat focus management (#5150)
* Fluent: focus management integration * Self review * Update use-propagate * Remove deprecated functionality * Update test to use useFocus() hook * Fix: itemEffector should pass cleanup callbacks * Update changelog * Fix changelog * Update wordings --------- Co-authored-by: William Wong <[email protected]>
1 parent 73ee44a commit 533c151

27 files changed

+230
-114
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
2929
- `useSuggestedActions` type is updated to align with its actual implementation, by [@OEvgeny](https://github.com/OEvgeny), in PR [#5122](https://github.com/microsoft/BotFramework-WebChat/pull/5122)
3030
- Removed deprecated code: `connect*`, `useRenderActivity`, `useRenderActivityStatus`, `useRenderAvatar`, in PR [#5148](https://github.com/microsoft/BotFramework-WebChat/pull/5148), by [@compulim](https://github.com/compulim)
3131
- Added named exports in both CommonJS and ES Modules module format, in PR [#5148](https://github.com/microsoft/BotFramework-WebChat/pull/5148), by [@compulim](https://github.com/compulim)
32+
- Removed deprecated `useFocusSendBox()` hook, please use `useFocus('sendBox')` instead, in PR [#5150](https://github.com/microsoft/BotFramework-WebChat/pull/5150), by [@OEvgeny](https://github.com/OEvgeny)
3233

3334
### Added
3435

@@ -53,6 +54,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
5354
- Added dark theme support, in PR [#5138](https://github.com/microsoft/BotFramework-WebChat/pull/5138)
5455
- Added an information message to the telephone keypad, in PR [#5140](https://github.com/microsoft/BotFramework-WebChat/pull/5140)
5556
- Added animation to focus indicator and pixel-perfected, in PR [#5143](https://github.com/microsoft/BotFramework-WebChat/pull/5143)
57+
- Integrated focus management for send box, in PR [#5150](https://github.com/microsoft/BotFramework-WebChat/pull/5150), by [@OEvgeny](https://github.com/OEvgeny)
5658
- (Experimental) Added `<LocalizeString />` component which can be used to localize strings, by [@OEvgeny](https://github.com/OEvgeny) in PR [#5140](https://github.com/microsoft/BotFramework-WebChat/pull/5140)
5759
- Added `<ThemeProvider>` component to apply theme pack to Web Chat, by [@compulim](https://github.com/compulim), in PR [#5120](https://github.com/microsoft/BotFramework-WebChat/pull/5120)
5860
- Added `useMakeThumbnail` hook option to create a thumbnail from the file given, by [@compulim](https://github.com/compulim), in PR [#5123](https://github.com/microsoft/BotFramework-WebChat/pull/5123) and [#5122](https://github.com/microsoft/BotFramework-WebChat/pull/5122)
@@ -66,6 +68,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6668
- Fixes [#5146](https://github.com/microsoft/BotFramework-WebChat/issues/5146). Fixed chat history focus indicator should not show up on tap, by [@OEvgeny](https://github.com/OEvgeny), in PR [#5145](https://github.com/microsoft/BotFramework-WebChat/pull/5145)
6769
- Fixes type portability issues by exporting types from all exported code, in PR [#5148](https://github.com/microsoft/BotFramework-WebChat/pull/5148), by [@compulim](https://github.com/compulim)
6870
- Fixes missing exports of `useNotifications`, in PR [#5148](https://github.com/microsoft/BotFramework-WebChat/pull/5148), by [@compulim](https://github.com/compulim)
71+
- Fixes suggested actions keyboard navigation skips actions after suggested actions got updated, in PR [#5150](https://github.com/microsoft/BotFramework-WebChat/pull/5150), by [@OEvgeny](https://github.com/OEvgeny)
6972

7073
### Changed
7174

__tests__/hooks/useFocusSendBox.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ test('calling emitTypingIndicator should send a typing activity', async () => {
1313

1414
await driver.wait(uiConnected(), timeouts.directLine);
1515

16-
await pageObjects.runHook('useFocusSendBox', [], fn => fn());
16+
await pageObjects.runHook('useFocus', [], fn => fn('sendBox'));
1717

1818
await driver.wait(sendBoxTextBoxFocused(), timeouts.ui);
1919
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
<head>
4+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
5+
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
6+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
7+
<script crossorigin="anonymous" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
8+
<script crossorigin="anonymous" src="/test-harness.js"></script>
9+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
10+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
11+
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
12+
</head>
13+
<body>
14+
<main id="webchat"></main>
15+
<script type="text/babel">
16+
run(async function () {
17+
const {
18+
React,
19+
ReactDOM: { render },
20+
WebChat: { FluentThemeProvider, ReactWebChat }
21+
} = window; // Imports in UMD fashion.
22+
23+
const { directLine, store } = testHelpers.createDirectLineEmulator();
24+
25+
const App = () => (
26+
<ReactWebChat directLine={directLine} store={store} styleOptions={{ hideTelephoneKeypadButton: false }} />
27+
);
28+
29+
render(
30+
<FluentThemeProvider>
31+
<App />
32+
</FluentThemeProvider>,
33+
document.getElementById('webchat')
34+
);
35+
36+
await pageConditions.uiConnected();
37+
38+
await directLine.emulateIncomingActivity(
39+
'Eiusmod anim adipisicing cupidatat adipisicing officia sint qui consequat veniam id aute.'
40+
);
41+
42+
await pageConditions.numActivitiesShown(1);
43+
44+
document.querySelector(`[data-testid="${WebChat.testIds.sendBoxTextBox}"]`).focus();
45+
46+
// WHEN: SHIFT-TAB key is pressed.
47+
await host.sendShiftTab();
48+
49+
// THEN: Should focus on the chat history.
50+
await host.snapshot();
51+
52+
// WHEN: A key is pressed.
53+
await host.sendKeys('The quick brown fox jumps over the lazy dog');
54+
55+
// THEN: Should focus on the SendBox
56+
await host.snapshot();
57+
58+
await (await directLine.actPostActivity(() => host.sendKeys('\n'))).resolveAll();
59+
60+
// THEN: Should send the activity.
61+
await pageConditions.numActivitiesShown(2);
62+
await pageConditions.allOutgoingActivitiesSent();
63+
await host.snapshot();
64+
});
65+
</script>
66+
</body>
67+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
2+
3+
describe('Fluent theme applied', () => {
4+
test('focus moves back to sendbox when letter pressed', () => runHTML('fluentTheme/focusManagement.backToSendBox'));
5+
});

__tests__/html/useFocus.main.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
await pageConditions.uiConnected();
3232

3333
// WHEN: The callback of "useFocus()" is called.
34-
await renderWebChatWithHook(() => window.WebChat.hooks.useFocus()());
34+
await renderWebChatWithHook(() => Promise.resolve(window.WebChat.hooks.useFocus()).then(focus => focus()));
3535

3636
// THEN: It should focus on the (blank) transcript.
3737
await host.snapshot();

__tests__/html/useFocus.sendBox.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
);
3030

3131
await pageConditions.uiConnected();
32-
await pageObjects.runHook(({ useFocus }) => useFocus()('sendBox'));
32+
await pageObjects.runHook(({ useFocus }) => Promise.resolve(useFocus()).then(focus => focus('sendBox')));
3333

3434
await host.snapshot();
3535
});

__tests__/html/useFocus.sendBoxWithoutKeyboard.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
// Since the keyboard is controlled by the browser and OS, we cannot test if the keyboard activated or not.
3434
// This is our best-effort to check if focus is being set correctly or not.
35-
await pageObjects.runHook(({ useFocus }) => useFocus()('sendBoxWithoutKeyboard'));
35+
await pageObjects.runHook(({ useFocus }) => Promise.resolve(useFocus()).then(focus => focus('sendBoxWithoutKeyboard')));
3636

3737
await host.snapshot();
3838
});

__tests__/html/useFocusSendBox.html

-41
This file was deleted.

__tests__/html/useFocusSendBox.js

-3
This file was deleted.

docs/HOOKS.md

+2
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,8 @@ useFocusSendBox(): () => void
507507

508508
> This function is deprecated. Developers should migrate to [`useFocus`](#usefocus).
509509
510+
> This function was removed in `[email protected]`.
511+
510512
When called, this function will send focus to the send box.
511513

512514
## `useGetSendTimeoutForActivity`

packages/component/package-lock.json

+78
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/component/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@
135135
"react-scroll-to-bottom": "4.2.0",
136136
"redux": "5.0.0",
137137
"simple-update-in": "2.2.0",
138+
"use-propagate": "^0.1.0",
138139
"use-ref-from": "^0.1.0",
139140
"valibot": "^0.30.0"
140141
},

packages/component/src/Composer.tsx

-4
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ import createDefaultSendBoxMiddleware from './SendBox/createMiddleware';
4444
import createDefaultSendBoxToolbarMiddleware from './SendBoxToolbar/createMiddleware';
4545
import createStyleSet from './Styles/createStyleSet';
4646
import { type ContextOf } from './types/ContextOf';
47-
import { type FocusSendBoxInit } from './types/internal/FocusSendBoxInit';
4847
import { type FocusTranscriptInit } from './types/internal/FocusTranscriptInit';
4948
import addTargetBlankToHyperlinksMarkdown from './Utils/addTargetBlankToHyperlinksMarkdown';
5049
import createCSSKey from './Utils/createCSSKey';
@@ -111,7 +110,6 @@ const ComposerCore = ({
111110
const [dictateAbortable, setDictateAbortable] = useState();
112111
const [referenceGrammarID] = useReferenceGrammarID();
113112
const [styleOptions] = useStyleOptions();
114-
const focusSendBoxCallbacksRef = useRef<((init: FocusSendBoxInit) => Promise<void>)[]>([]);
115113
const focusTranscriptCallbacksRef = useRef<((init: FocusTranscriptInit) => Promise<void>)[]>([]);
116114
const internalMarkdownIt = useMemo(() => new MarkdownIt(), []);
117115
const scrollToCallbacksRef = useRef([]);
@@ -221,7 +219,6 @@ const ComposerCore = ({
221219
dictateAbortable,
222220
dispatchScrollPosition,
223221
dispatchTranscriptFocusByActivityKey,
224-
focusSendBoxCallbacksRef,
225222
focusTranscriptCallbacksRef,
226223
internalMarkdownItState: [internalMarkdownIt],
227224
internalRenderMarkdownInline,
@@ -243,7 +240,6 @@ const ComposerCore = ({
243240
dictateAbortable,
244241
dispatchScrollPosition,
245242
dispatchTranscriptFocusByActivityKey,
246-
focusSendBoxCallbacksRef,
247243
focusTranscriptCallbacksRef,
248244
internalMarkdownIt,
249245
internalRenderMarkdownInline,

packages/component/src/SendBox/TextBox.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import React, { useCallback, useMemo, useRef } from 'react';
55
import AccessibleInputText from '../Utils/AccessibleInputText';
66
import navigableEvent from '../Utils/TypeFocusSink/navigableEvent';
77
import { ie11 } from '../Utils/detectBrowser';
8-
import useRegisterFocusSendBox from '../hooks/internal/useRegisterFocusSendBox';
8+
import { useRegisterFocusSendBox, type SendBoxFocusOptions } from '../hooks/sendBoxFocus';
99
import useStyleToEmotionObject from '../hooks/internal/useStyleToEmotionObject';
1010
import useScrollDown from '../hooks/useScrollDown';
1111
import useScrollUp from '../hooks/useScrollUp';
@@ -162,8 +162,8 @@ const TextBox = ({ className = '' }: Readonly<{ className?: string | undefined }
162162
[scrollDown, scrollUp]
163163
);
164164

165-
const focusCallback = useCallback<Parameters<typeof useRegisterFocusSendBox>[0]>(
166-
options => {
165+
const focusCallback = useCallback(
166+
(options: SendBoxFocusOptions) => {
167167
const { noKeyboard } = options;
168168
const { current } = inputElementRef;
169169

packages/component/src/hooks/index.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import useDictateAbortable from './useDictateAbortable';
22
import useFocus from './useFocus';
3-
import useFocusSendBox from './useFocusSendBox';
43
import useMakeThumbnail from './useMakeThumbnail';
54
import useObserveScrollPosition from './useObserveScrollPosition';
65
import useObserveTranscriptFocus from './useObserveTranscriptFocus';
@@ -18,17 +17,19 @@ import { useTypingIndicatorVisible } from '../BasicTypingIndicator';
1817
import { useSendBoxSpeechInterimsVisible } from '../SendBox/BasicSendBox';
1918
import { useMicrophoneButtonClick, useMicrophoneButtonDisabled } from '../SendBox/MicrophoneButton';
2019
import { useTextBoxSubmit, useTextBoxValue } from '../SendBox/TextBox';
20+
import { useRegisterFocusSendBox, type SendBoxFocusOptions } from './sendBoxFocus';
21+
22+
export { type SendBoxFocusOptions };
2123

2224
export {
2325
useDictateAbortable,
2426
useFocus,
25-
/** @deprecated Please use `useFocus('sendBox')` instead. */
26-
useFocusSendBox,
2727
useMakeThumbnail,
2828
useMicrophoneButtonClick,
2929
useMicrophoneButtonDisabled,
3030
useObserveScrollPosition,
3131
useObserveTranscriptFocus,
32+
useRegisterFocusSendBox,
3233
useRenderMarkdownAsHTML,
3334
useScrollDown,
3435
useScrollTo,

packages/component/src/hooks/internal/WebChatUIContext.ts

-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { createContext, type MutableRefObject } from 'react';
22

3-
import { type FocusSendBoxInit } from '../../types/internal/FocusSendBoxInit';
43
import { type FocusTranscriptInit } from '../../types/internal/FocusTranscriptInit';
54

65
export type ContextType = {
7-
focusSendBoxCallbacksRef: MutableRefObject<((init: FocusSendBoxInit) => Promise<void>)[]>;
86
focusTranscriptCallbacksRef: MutableRefObject<((init: FocusTranscriptInit) => Promise<void>)[]>;
97
};
108

0 commit comments

Comments
 (0)