Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fluent: integrate webchat focus management #5150

Merged
merged 9 commits into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion __tests__/hooks/useFocusSendBox.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ test('calling emitTypingIndicator should send a typing activity', async () => {

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

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

await driver.wait(sendBoxTextBoxFocused(), timeouts.ui);
});
67 changes: 67 additions & 0 deletions __tests__/html/fluentTheme/focusManagement.backToSendBox.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!doctype html>
<html lang="en-US">
<head>
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
<script crossorigin="anonymous" src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react@16.8.6/umd/react.production.min.js"></script>
<script crossorigin="anonymous" src="https://unpkg.com/react-dom@16.8.6/umd/react-dom.production.min.js"></script>
<script crossorigin="anonymous" src="/test-harness.js"></script>
<script crossorigin="anonymous" src="/test-page-object.js"></script>
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
<script crossorigin="anonymous" src="/__dist__/botframework-webchat-fluent-theme.production.min.js"></script>
</head>
<body>
<main id="webchat"></main>
<script type="text/babel">
run(async function () {
const {
React,
ReactDOM: { render },
WebChat: { FluentThemeProvider, ReactWebChat }
} = window; // Imports in UMD fashion.

const { directLine, store } = testHelpers.createDirectLineEmulator();

const App = () => (
<ReactWebChat directLine={directLine} store={store} styleOptions={{ hideTelephoneKeypadButton: false }} />
);

render(
<FluentThemeProvider>
<App />
</FluentThemeProvider>,
document.getElementById('webchat')
);

await pageConditions.uiConnected();

await directLine.emulateIncomingActivity(
'Eiusmod anim adipisicing cupidatat adipisicing officia sint qui consequat veniam id aute.'
);

await pageConditions.numActivitiesShown(1);

document.querySelector(`[data-testid="${WebChat.testIds.sendBoxTextBox}"]`).focus();

// WHEN: SHIFT-TAB key is pressed.
await host.sendShiftTab();

// THEN: Should focus on the chat history.
await host.snapshot();

// WHEN: A key is pressed.
await host.sendKeys('The quick brown fox jumps over the lazy dog');

// THEN: Should focus on the SendBox
await host.snapshot();

await (await directLine.actPostActivity(() => host.sendKeys('\n'))).resolveAll();

// THEN: Should send the activity.
await pageConditions.numActivitiesShown(2);
await pageConditions.allOutgoingActivitiesSent();
await host.snapshot();
});
</script>
</body>
</html>
5 changes: 5 additions & 0 deletions __tests__/html/fluentTheme/focusManagement.backToSendBox.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */

describe('Fluent theme applied', () => {
test('focus moves back to sendbox when letter pressed', () => runHTML('fluentTheme/focusManagement.backToSendBox'));
});
2 changes: 1 addition & 1 deletion __tests__/html/useFocus.main.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
await pageConditions.uiConnected();

// WHEN: The callback of "useFocus()" is called.
await renderWebChatWithHook(() => window.WebChat.hooks.useFocus()());
await renderWebChatWithHook(() => Promise.resolve(window.WebChat.hooks.useFocus()).then(focus => focus()));
compulim marked this conversation as resolved.
Show resolved Hide resolved

// THEN: It should focus on the (blank) transcript.
await host.snapshot();
Expand Down
2 changes: 1 addition & 1 deletion __tests__/html/useFocus.sendBox.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
);

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

await host.snapshot();
});
Expand Down
2 changes: 1 addition & 1 deletion __tests__/html/useFocus.sendBoxWithoutKeyboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

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

await host.snapshot();
});
Expand Down
41 changes: 0 additions & 41 deletions __tests__/html/useFocusSendBox.html

This file was deleted.

3 changes: 0 additions & 3 deletions __tests__/html/useFocusSendBox.js

This file was deleted.

2 changes: 2 additions & 0 deletions docs/HOOKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,8 @@ useFocusSendBox(): () => void

> This function is deprecated. Developers should migrate to [`useFocus`](#usefocus).

> This function was removed in `botframework-webchat@4.17.0`.

When called, this function will send focus to the send box.

## `useGetSendTimeoutForActivity`
Expand Down
78 changes: 78 additions & 0 deletions packages/component/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/component/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@
"react-scroll-to-bottom": "4.2.0",
"redux": "5.0.0",
"simple-update-in": "2.2.0",
"use-propagate": "^0.1.0",
"use-ref-from": "^0.1.0",
"valibot": "^0.30.0"
},
Expand Down
4 changes: 0 additions & 4 deletions packages/component/src/Composer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ import createDefaultSendBoxMiddleware from './SendBox/createMiddleware';
import createDefaultSendBoxToolbarMiddleware from './SendBoxToolbar/createMiddleware';
import createStyleSet from './Styles/createStyleSet';
import { type ContextOf } from './types/ContextOf';
import { type FocusSendBoxInit } from './types/internal/FocusSendBoxInit';
import { type FocusTranscriptInit } from './types/internal/FocusTranscriptInit';
import addTargetBlankToHyperlinksMarkdown from './Utils/addTargetBlankToHyperlinksMarkdown';
import createCSSKey from './Utils/createCSSKey';
Expand Down Expand Up @@ -111,7 +110,6 @@ const ComposerCore = ({
const [dictateAbortable, setDictateAbortable] = useState();
const [referenceGrammarID] = useReferenceGrammarID();
const [styleOptions] = useStyleOptions();
const focusSendBoxCallbacksRef = useRef<((init: FocusSendBoxInit) => Promise<void>)[]>([]);
const focusTranscriptCallbacksRef = useRef<((init: FocusTranscriptInit) => Promise<void>)[]>([]);
const internalMarkdownIt = useMemo(() => new MarkdownIt(), []);
const scrollToCallbacksRef = useRef([]);
Expand Down Expand Up @@ -221,7 +219,6 @@ const ComposerCore = ({
dictateAbortable,
dispatchScrollPosition,
dispatchTranscriptFocusByActivityKey,
focusSendBoxCallbacksRef,
focusTranscriptCallbacksRef,
internalMarkdownItState: [internalMarkdownIt],
internalRenderMarkdownInline,
Expand All @@ -243,7 +240,6 @@ const ComposerCore = ({
dictateAbortable,
dispatchScrollPosition,
dispatchTranscriptFocusByActivityKey,
focusSendBoxCallbacksRef,
focusTranscriptCallbacksRef,
internalMarkdownIt,
internalRenderMarkdownInline,
Expand Down
6 changes: 3 additions & 3 deletions packages/component/src/SendBox/TextBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import React, { useCallback, useMemo, useRef } from 'react';
import AccessibleInputText from '../Utils/AccessibleInputText';
import navigableEvent from '../Utils/TypeFocusSink/navigableEvent';
import { ie11 } from '../Utils/detectBrowser';
import useRegisterFocusSendBox from '../hooks/internal/useRegisterFocusSendBox';
import { useRegisterFocusSendBox, type SendBoxFocusOptions } from '../hooks/sendBoxFocus';
import useStyleToEmotionObject from '../hooks/internal/useStyleToEmotionObject';
import useScrollDown from '../hooks/useScrollDown';
import useScrollUp from '../hooks/useScrollUp';
Expand Down Expand Up @@ -162,8 +162,8 @@ const TextBox = ({ className = '' }: Readonly<{ className?: string | undefined }
[scrollDown, scrollUp]
);

const focusCallback = useCallback<Parameters<typeof useRegisterFocusSendBox>[0]>(
options => {
const focusCallback = useCallback(
(options: SendBoxFocusOptions) => {
const { noKeyboard } = options;
const { current } = inputElementRef;

Expand Down
7 changes: 4 additions & 3 deletions packages/component/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import useDictateAbortable from './useDictateAbortable';
import useFocus from './useFocus';
import useFocusSendBox from './useFocusSendBox';
import useMakeThumbnail from './useMakeThumbnail';
import useObserveScrollPosition from './useObserveScrollPosition';
import useObserveTranscriptFocus from './useObserveTranscriptFocus';
Expand All @@ -18,17 +17,19 @@ import { useTypingIndicatorVisible } from '../BasicTypingIndicator';
import { useSendBoxSpeechInterimsVisible } from '../SendBox/BasicSendBox';
import { useMicrophoneButtonClick, useMicrophoneButtonDisabled } from '../SendBox/MicrophoneButton';
import { useTextBoxSubmit, useTextBoxValue } from '../SendBox/TextBox';
import { useRegisterFocusSendBox, type SendBoxFocusOptions } from './sendBoxFocus';

export { type SendBoxFocusOptions };

export {
useDictateAbortable,
useFocus,
/** @deprecated Please use `useFocus('sendBox')` instead. */
useFocusSendBox,
useMakeThumbnail,
useMicrophoneButtonClick,
useMicrophoneButtonDisabled,
useObserveScrollPosition,
useObserveTranscriptFocus,
useRegisterFocusSendBox,
OEvgeny marked this conversation as resolved.
Show resolved Hide resolved
useRenderMarkdownAsHTML,
useScrollDown,
useScrollTo,
Expand Down
2 changes: 0 additions & 2 deletions packages/component/src/hooks/internal/WebChatUIContext.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { createContext, type MutableRefObject } from 'react';

import { type FocusSendBoxInit } from '../../types/internal/FocusSendBoxInit';
import { type FocusTranscriptInit } from '../../types/internal/FocusTranscriptInit';

export type ContextType = {
focusSendBoxCallbacksRef: MutableRefObject<((init: FocusSendBoxInit) => Promise<void>)[]>;
focusTranscriptCallbacksRef: MutableRefObject<((init: FocusTranscriptInit) => Promise<void>)[]>;
};

Expand Down

This file was deleted.

8 changes: 8 additions & 0 deletions packages/component/src/hooks/sendBoxFocus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { WaitUntilable } from './internal/createWaitUntilable';
import { createPropagation } from 'use-propagate';

export type SendBoxFocusOptions = WaitUntilable<{ noKeyboard: boolean }>;

const { useListen: useRegisterFocusSendBox, usePropagate: useFocusSendBox } = createPropagation<SendBoxFocusOptions>();

export { useRegisterFocusSendBox, useFocusSendBox };
Loading
Loading