From ae89087da9ce59f614849965d14542f3d6ee8f19 Mon Sep 17 00:00:00 2001 From: William Wong Date: Mon, 25 Mar 2024 10:12:24 +0000 Subject: [PATCH 1/2] Fix focus should not blur briefly after tapping on a suggested action --- CHANGELOG.md | 6 +- ...suggestedActions.sendFocusImmediately.html | 65 ++++++++++++ ...y.suggestedActions.sendFocusImmediately.js | 7 ++ packages/component/package-lock.json | 19 ++++ packages/component/package.json | 1 + packages/component/src/Composer.tsx | 37 +++---- .../component/src/SendBox/SuggestedAction.tsx | 34 ++++--- packages/component/src/SendBox/TextBox.tsx | 35 ++++--- .../src/hooks/internal/WebChatUIContext.js | 5 - .../src/hooks/internal/WebChatUIContext.ts | 13 +++ .../internal/createWaitUntilable.spec.ts | 98 +++++++++++++++++++ .../src/hooks/internal/createWaitUntilable.ts | 35 +++++++ packages/component/src/hooks/useFocus.ts | 21 ++-- .../src/types/internal/FocusSendBoxInit.ts | 3 + .../src/types/internal/FocusTranscriptInit.ts | 4 + 15 files changed, 323 insertions(+), 60 deletions(-) create mode 100644 __tests__/html/accessibility.suggestedActions.sendFocusImmediately.html create mode 100644 __tests__/html/accessibility.suggestedActions.sendFocusImmediately.js delete mode 100644 packages/component/src/hooks/internal/WebChatUIContext.js create mode 100644 packages/component/src/hooks/internal/WebChatUIContext.ts create mode 100644 packages/component/src/hooks/internal/createWaitUntilable.spec.ts create mode 100644 packages/component/src/hooks/internal/createWaitUntilable.ts create mode 100644 packages/component/src/types/internal/FocusSendBoxInit.ts create mode 100644 packages/component/src/types/internal/FocusTranscriptInit.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index e2910656b7..ce14a38d24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added -- Resolves [#5081](https://github.com/microsoft/BotFramework-WebChat/issues/5081). Added `uploadAccept` and `uploadMultiple` style options, by [@ms-jb](https://github.com/ms-jb) +- Resolves [#5081](https://github.com/microsoft/BotFramework-WebChat/issues/5081). Added `uploadAccept` and `uploadMultiple` style options, by [@ms-jb](https://github.com/ms-jb) + +### Fixed + +- Fixes [#5050](https://github.com/microsoft/BotFramework-WebChat/issues/5050). Fixed focus should not blur briefly after tapping on a suggested action, by [@compulim](https://github.com/compulim), in PR [#XXX](https://github.com/microsoft/BotFramework-WebChat/issues/pull/XXX) ### Changed diff --git a/__tests__/html/accessibility.suggestedActions.sendFocusImmediately.html b/__tests__/html/accessibility.suggestedActions.sendFocusImmediately.html new file mode 100644 index 0000000000..fcb57eb575 --- /dev/null +++ b/__tests__/html/accessibility.suggestedActions.sendFocusImmediately.html @@ -0,0 +1,65 @@ + + + + + + + + + +
+ + + diff --git a/__tests__/html/accessibility.suggestedActions.sendFocusImmediately.js b/__tests__/html/accessibility.suggestedActions.sendFocusImmediately.js new file mode 100644 index 0000000000..e611366ff0 --- /dev/null +++ b/__tests__/html/accessibility.suggestedActions.sendFocusImmediately.js @@ -0,0 +1,7 @@ +/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */ + +describe('accessibility requirement', () => { + describe('after clicking on suggested action', () => { + test('should send the focus to send box immediately', () => runHTML('accessibility.suggestedActions.sendFocusImmediately.html')); + }); +}); diff --git a/packages/component/package-lock.json b/packages/component/package-lock.json index e041cd656a..a21ebbfb44 100644 --- a/packages/component/package-lock.json +++ b/packages/component/package-lock.json @@ -43,6 +43,7 @@ "concurrently": "^8.2.2", "core-js": "^3.34.0", "node-dev": "^8.0.0", + "type-fest": "^4.14.0", "typescript": "^5.3.2" }, "peerDependencies": { @@ -4641,6 +4642,18 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "node_modules/type-fest": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.14.0.tgz", + "integrity": "sha512-on5/Cw89wwqGZQu+yWO0gGMGu8VNxsaW9SB2HE8yJjllEk7IDTwnSN1dUVldYILhYPN5HzD7WAaw2cc/jBfn0Q==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", @@ -8201,6 +8214,12 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", "dev": true }, + "type-fest": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.14.0.tgz", + "integrity": "sha512-on5/Cw89wwqGZQu+yWO0gGMGu8VNxsaW9SB2HE8yJjllEk7IDTwnSN1dUVldYILhYPN5HzD7WAaw2cc/jBfn0Q==", + "dev": true + }, "typescript": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz", diff --git a/packages/component/package.json b/packages/component/package.json index 115a31ca33..3db8cb0958 100644 --- a/packages/component/package.json +++ b/packages/component/package.json @@ -90,6 +90,7 @@ "concurrently": "^8.2.2", "core-js": "^3.34.0", "node-dev": "^8.0.0", + "type-fest": "^4.14.0", "typescript": "^5.3.2" }, "dependencies": { diff --git a/packages/component/src/Composer.tsx b/packages/component/src/Composer.tsx index e4a4508d51..3316e2d60e 100644 --- a/packages/component/src/Composer.tsx +++ b/packages/component/src/Composer.tsx @@ -1,41 +1,44 @@ +import createEmotion from '@emotion/css/create-instance'; import { Composer as APIComposer, hooks, WebSpeechPonyfillFactory } from 'botframework-webchat-api'; -import { Composer as SayComposer } from 'react-say'; import { singleToArray } from 'botframework-webchat-core'; import classNames from 'classnames'; -import createEmotion from '@emotion/css/create-instance'; -import createStyleSet from './Styles/createStyleSet'; import MarkdownIt from 'markdown-it'; import PropTypes from 'prop-types'; import React, { memo, useCallback, useMemo, useRef, useState } from 'react'; +import { Composer as SayComposer } from 'react-say'; +import createStyleSet from './Styles/createStyleSet'; +import createDefaultAttachmentMiddleware from './Attachment/createMiddleware'; +import Dictation from './Dictation'; +import ErrorBox from './ErrorBox'; import { speechSynthesis as bypassSpeechSynthesis, SpeechSynthesisUtterance as BypassSpeechSynthesisUtterance } from './hooks/internal/BypassSpeechSynthesisPonyfill'; -import ActivityTreeComposer from './providers/ActivityTree/ActivityTreeComposer'; -import addTargetBlankToHyperlinksMarkdown from './Utils/addTargetBlankToHyperlinksMarkdown'; -import createCSSKey from './Utils/createCSSKey'; +import UITracker from './hooks/internal/UITracker'; +import WebChatUIContext from './hooks/internal/WebChatUIContext'; +import useStyleSet from './hooks/useStyleSet'; import createDefaultActivityMiddleware from './Middleware/Activity/createCoreMiddleware'; import createDefaultActivityStatusMiddleware from './Middleware/ActivityStatus/createCoreMiddleware'; import createDefaultAttachmentForScreenReaderMiddleware from './Middleware/AttachmentForScreenReader/createCoreMiddleware'; -import createDefaultAttachmentMiddleware from './Attachment/createMiddleware'; import createDefaultAvatarMiddleware from './Middleware/Avatar/createCoreMiddleware'; import createDefaultCardActionMiddleware from './Middleware/CardAction/createCoreMiddleware'; import createDefaultScrollToEndButtonMiddleware from './Middleware/ScrollToEndButton/createScrollToEndButtonMiddleware'; import createDefaultToastMiddleware from './Middleware/Toast/createCoreMiddleware'; import createDefaultTypingIndicatorMiddleware from './Middleware/TypingIndicator/createCoreMiddleware'; -import Dictation from './Dictation'; +import ActivityTreeComposer from './providers/ActivityTree/ActivityTreeComposer'; +import SendBoxComposer from './providers/internal/SendBox/SendBoxComposer'; +import ModalDialogComposer from './providers/ModalDialog/ModalDialogComposer'; +import addTargetBlankToHyperlinksMarkdown from './Utils/addTargetBlankToHyperlinksMarkdown'; +import createCSSKey from './Utils/createCSSKey'; import downscaleImageToDataURL from './Utils/downscaleImageToDataURL'; -import ErrorBox from './ErrorBox'; import mapMap from './Utils/mapMap'; -import ModalDialogComposer from './providers/ModalDialog/ModalDialogComposer'; -import SendBoxComposer from './providers/internal/SendBox/SendBoxComposer'; -import UITracker from './hooks/internal/UITracker'; -import useStyleSet from './hooks/useStyleSet'; -import WebChatUIContext from './hooks/internal/WebChatUIContext'; import type { ComposerProps as APIComposerProps } from 'botframework-webchat-api'; import type { FC, ReactNode } from 'react'; +import type { ContextOf } from './types/ContextOf'; +import { type FocusSendBoxInit } from './types/internal/FocusSendBoxInit'; +import { type FocusTranscriptInit } from './types/internal/FocusTranscriptInit'; const { useGetActivityByKey, useReferenceGrammarID, useStyleOptions } = hooks; @@ -97,8 +100,8 @@ const ComposerCore: FC = ({ const [dictateAbortable, setDictateAbortable] = useState(); const [referenceGrammarID] = useReferenceGrammarID(); const [styleOptions] = useStyleOptions(); - const focusSendBoxCallbacksRef = useRef([]); - const focusTranscriptCallbacksRef = useRef([]); + const focusSendBoxCallbacksRef = useRef<((init: FocusSendBoxInit) => Promise)[]>([]); + const focusTranscriptCallbacksRef = useRef<((init: FocusTranscriptInit) => Promise)[]>([]); const internalMarkdownIt = useMemo(() => new MarkdownIt(), []); const scrollToCallbacksRef = useRef([]); const scrollToEndCallbacksRef = useRef([]); @@ -202,7 +205,7 @@ const ComposerCore: FC = ({ [transcriptFocusObserversRef, setNumTranscriptFocusObservers] ); - const context = useMemo( + const context = useMemo>( () => ({ dictateAbortable, dispatchScrollPosition, diff --git a/packages/component/src/SendBox/SuggestedAction.tsx b/packages/component/src/SendBox/SuggestedAction.tsx index 189b6a28e3..021fdd7cc1 100644 --- a/packages/component/src/SendBox/SuggestedAction.tsx +++ b/packages/component/src/SendBox/SuggestedAction.tsx @@ -1,20 +1,20 @@ import { hooks } from 'botframework-webchat-api'; +import type { DirectLineCardAction } from 'botframework-webchat-core'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import React, { MouseEventHandler, useCallback, VFC } from 'react'; -import type { DirectLineCardAction } from 'botframework-webchat-core'; -import AccessibleButton from '../Utils/AccessibleButton'; import connectToWebChat from '../connectToWebChat'; -import useFocus from '../hooks/useFocus'; -import useFocusAccessKeyEffect from '../Utils/AccessKeySink/useFocusAccessKeyEffect'; import useFocusVisible from '../hooks/internal/useFocusVisible'; -import useItemRef from '../providers/RovingTabIndex/useItemRef'; import useLocalizeAccessKey from '../hooks/internal/useLocalizeAccessKey'; -import useScrollToEnd from '../hooks/useScrollToEnd'; -import useStyleSet from '../hooks/useStyleSet'; import useStyleToEmotionObject from '../hooks/internal/useStyleToEmotionObject'; import useSuggestedActionsAccessKey from '../hooks/internal/useSuggestedActionsAccessKey'; +import useFocus from '../hooks/useFocus'; +import useScrollToEnd from '../hooks/useScrollToEnd'; +import useStyleSet from '../hooks/useStyleSet'; +import useItemRef from '../providers/RovingTabIndex/useItemRef'; +import AccessibleButton from '../Utils/AccessibleButton'; +import useFocusAccessKeyEffect from '../Utils/AccessKeySink/useFocusAccessKeyEffect'; const { useDirection, useDisabled, usePerformCardAction, useStyleOptions, useSuggestedActions } = hooks; @@ -90,15 +90,21 @@ const SuggestedAction: VFC = ({ const handleClick = useCallback>( ({ target }) => { - // TODO: [P3] #XXX We should not destruct DirectLineCardAction into React props and pass them in. It makes typings difficult. - // Instead, we should pass a "cardAction" props. - performCardAction({ displayText, text, type, value } as DirectLineCardAction, { target }); + (async function () { + // We need to focus to the send box before we are performing this card action. + // The will make sure the focus is always on Web Chat. + // Otherwise, the focus may momentarily send to `document.body` and screen reader will be confused. + await focus('sendBoxWithoutKeyboard'); + + // TODO: [P3] #XXX We should not destruct DirectLineCardAction into React props and pass them in. It makes typings difficult. + // Instead, we should pass a "cardAction" props. + performCardAction({ displayText, text, type, value } as DirectLineCardAction, { target }); - // Since "openUrl" action do not submit, the suggested action buttons do not hide after click. - type === 'openUrl' && setSuggestedActions([]); + // Since "openUrl" action do not submit, the suggested action buttons do not hide after click. + type === 'openUrl' && setSuggestedActions([]); - focus('sendBoxWithoutKeyboard'); - scrollToEnd(); + scrollToEnd(); + })(); }, [displayText, focus, performCardAction, scrollToEnd, setSuggestedActions, text, type, value] ); diff --git a/packages/component/src/SendBox/TextBox.tsx b/packages/component/src/SendBox/TextBox.tsx index e59e02c28d..3f464df986 100644 --- a/packages/component/src/SendBox/TextBox.tsx +++ b/packages/component/src/SendBox/TextBox.tsx @@ -3,17 +3,17 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; import React, { useCallback, useMemo, useRef } from 'react'; -import { ie11 } from '../Utils/detectBrowser'; import AccessibleInputText from '../Utils/AccessibleInputText'; -import AutoResizeTextArea from './AutoResizeTextArea'; import navigableEvent from '../Utils/TypeFocusSink/navigableEvent'; +import { ie11 } from '../Utils/detectBrowser'; import useRegisterFocusSendBox from '../hooks/internal/useRegisterFocusSendBox'; +import useStyleToEmotionObject from '../hooks/internal/useStyleToEmotionObject'; import useScrollDown from '../hooks/useScrollDown'; import useScrollUp from '../hooks/useScrollUp'; import useStyleSet from '../hooks/useStyleSet'; -import useStyleToEmotionObject from '../hooks/internal/useStyleToEmotionObject'; import useSubmit from '../providers/internal/SendBox/useSubmit'; import withEmoji from '../withEmoji/withEmoji'; +import AutoResizeTextArea from './AutoResizeTextArea'; import type { MutableRefObject } from 'react'; @@ -163,8 +163,9 @@ const TextBox = ({ className }) => { [scrollDown, scrollUp] ); - const focusCallback = useCallback<(options?: { noKeyboard?: boolean }) => void>( - ({ noKeyboard } = {}) => { + const focusCallback = useCallback[0]>( + options => { + const { noKeyboard } = options; const { current } = inputElementRef; if (current) { @@ -178,17 +179,19 @@ const TextBox = ({ className }) => { current.setAttribute('readonly', 'readonly'); - // TODO: [P2] We should update this logic to handle quickly-successive `focusCallback`. - // If a succeeding `focusCallback` is being called, the `setTimeout` should run immediately. - // Or the second `focusCallback` should not set `readonly` to `true`. - setTimeout(() => { - const { current } = inputElementRef; - - if (current) { - current.focus(); - readOnly ? current.setAttribute('readonly', readOnly) : current.removeAttribute('readonly'); - } - }, 0); + options.waitUntil( + (async function () { + // TODO: [P2] We should update this logic to handle quickly-successive `focusCallback`. + // If a succeeding `focusCallback` is being called, the `setTimeout` should run immediately. + // Or the second `focusCallback` should not set `readonly` to `true`. + await new Promise(resolve => setTimeout(resolve, 0)); + + if (current) { + current.focus(); + readOnly ? current.setAttribute('readonly', readOnly) : current.removeAttribute('readonly'); + } + })() + ); } else { current.focus(); } diff --git a/packages/component/src/hooks/internal/WebChatUIContext.js b/packages/component/src/hooks/internal/WebChatUIContext.js deleted file mode 100644 index bcfb4e6924..0000000000 --- a/packages/component/src/hooks/internal/WebChatUIContext.js +++ /dev/null @@ -1,5 +0,0 @@ -import { createContext } from 'react'; - -const context = createContext(); - -export default context; diff --git a/packages/component/src/hooks/internal/WebChatUIContext.ts b/packages/component/src/hooks/internal/WebChatUIContext.ts new file mode 100644 index 0000000000..805b5b29af --- /dev/null +++ b/packages/component/src/hooks/internal/WebChatUIContext.ts @@ -0,0 +1,13 @@ +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)[]>; + focusTranscriptCallbacksRef: MutableRefObject<((init: FocusTranscriptInit) => Promise)[]>; +}; + +const context = createContext(undefined as ContextType); + +export default context; diff --git a/packages/component/src/hooks/internal/createWaitUntilable.spec.ts b/packages/component/src/hooks/internal/createWaitUntilable.spec.ts new file mode 100644 index 0000000000..cd3dee4a6e --- /dev/null +++ b/packages/component/src/hooks/internal/createWaitUntilable.spec.ts @@ -0,0 +1,98 @@ +import createWaitUntilable, { type WaitUntilable } from './createWaitUntilable'; + +type Deferred = { + promise: Promise; + reject: (error: unknown) => void; + resolve: (value: T) => void; +}; + +function createDeferred(): Deferred { + const deferred: Partial> = {}; + + deferred.promise = new Promise((resolve, reject) => { + deferred.resolve = resolve; + deferred.reject = reject; + }); + + return deferred as Deferred; +} + +function hasResolve(promise): Promise { + // eslint-disable-next-line no-restricted-globals + return Promise.race([promise.then(() => true), new Promise(resolve => setTimeout(() => resolve(false), 0))]); +} + +describe('with no waitUntil()', () => { + let getPromise: () => Promise; + let promise: Promise; + + beforeEach(() => { + [, getPromise] = createWaitUntilable({}); + + promise = getPromise(); + }); + + test('should resolve initially', () => expect(hasResolve(promise)).resolves.toBe(true)); +}); + +describe('With a single waitUntil()', () => { + let event: WaitUntilable<{}>; + let getPromise: () => Promise; + let promise: Promise; + let deferred: Deferred; + + beforeEach(() => { + [event, getPromise] = createWaitUntilable({}); + + deferred = createDeferred(); + event.waitUntil(deferred.promise); + promise = getPromise(); + }); + + test('should not resolve initially', () => expect(hasResolve(promise)).resolves.toBe(false)); + + describe('after waitUntil() has resolved', () => { + beforeEach(() => deferred.resolve()); + + test('should resolve', () => expect(hasResolve(promise)).resolves.toBe(true)); + }); +}); + +describe('With nested waitUntil()', () => { + let event: WaitUntilable<{}>; + let getPromise: () => Promise; + let promise: Promise; + let deferred1: Deferred; + let deferred2: Deferred; + + beforeEach(() => { + [event, getPromise] = createWaitUntilable({}); + + deferred1 = createDeferred(); + deferred2 = createDeferred(); + + event.waitUntil( + (async () => { + await deferred1.promise; + + event.waitUntil(deferred2.promise); + })() + ); + + promise = getPromise(); + }); + + test('should not resolve initially', () => expect(hasResolve(promise)).resolves.toBe(false)); + + describe('after first waitUntil() is resolved', () => { + beforeEach(() => deferred1.resolve()); + + test('should not resolve', () => expect(hasResolve(promise)).resolves.toBe(false)); + + describe('after second waitUntil() is resolved', () => { + beforeEach(() => deferred2.resolve()); + + test('should resolve', () => expect(hasResolve(promise)).resolves.toBe(true)); + }); + }); +}); diff --git a/packages/component/src/hooks/internal/createWaitUntilable.ts b/packages/component/src/hooks/internal/createWaitUntilable.ts new file mode 100644 index 0000000000..162ee6fa2a --- /dev/null +++ b/packages/component/src/hooks/internal/createWaitUntilable.ts @@ -0,0 +1,35 @@ +export type WaitUntilable = T & { waitUntil: (promise: Promise) => void }; + +export default function createWaitUntilable( + object: T +): readonly [WaitUntilable, () => Promise] { + const allPromises: Promise[] = []; + + return Object.freeze([ + { + ...object, + waitUntil(promise: Promise) { + allPromises.push(promise); + } + }, + async () => { + // Implements the logic of ExtendableEvent.waitUntil. + // When calling waitUntil(), new promises can be added by calling waitUntil() again. + // It should wait until no new promises are added. + + // From excerpt of https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil: + // "The waitUntil() method must be initially called within the event callback, but after that it can be called multiple times, until all the promises passed to it settle." + for (let numPromise = 0; ; numPromise = allPromises.length) { + // eslint-disable-next-line no-await-in-loop + await Promise.all(allPromises).then(() => { + // Intentionally left blank. + }); + + // Wait until all promise changes are settled. + if (numPromise === allPromises.length) { + break; + } + } + } + ]); +} diff --git a/packages/component/src/hooks/useFocus.ts b/packages/component/src/hooks/useFocus.ts index d6d97872bf..2869614549 100644 --- a/packages/component/src/hooks/useFocus.ts +++ b/packages/component/src/hooks/useFocus.ts @@ -1,19 +1,26 @@ import { useCallback } from 'react'; +import createWaitUntilable from './internal/createWaitUntilable'; import useWebChatUIContext from './internal/useWebChatUIContext'; -export default function useFocus(): (where?: 'main' | 'sendBox' | 'sendBoxWithoutKeyboard') => void { +export default function useFocus(): (where?: 'main' | 'sendBox' | 'sendBoxWithoutKeyboard') => Promise { const { focusSendBoxCallbacksRef, focusTranscriptCallbacksRef } = useWebChatUIContext(); return useCallback( - where => { - if (where === 'sendBoxWithoutKeyboard') { - return focusSendBoxCallbacksRef.current.forEach(callback => callback({ noKeyboard: true })); - } + async where => { + if (where === 'sendBox' || where === 'sendBoxWithoutKeyboard') { + const [options, getPromise] = createWaitUntilable({ noKeyboard: where === 'sendBoxWithoutKeyboard' }); + + focusSendBoxCallbacksRef.current.forEach(callback => callback(options)); - const { current } = where === 'sendBox' ? focusSendBoxCallbacksRef : focusTranscriptCallbacksRef; + await getPromise(); + } else { + const [, getPromise] = createWaitUntilable({}); - current.forEach(callback => callback()); + focusTranscriptCallbacksRef.current.forEach(callback => callback()); + + await getPromise(); + } }, [focusSendBoxCallbacksRef, focusTranscriptCallbacksRef] ); diff --git a/packages/component/src/types/internal/FocusSendBoxInit.ts b/packages/component/src/types/internal/FocusSendBoxInit.ts new file mode 100644 index 0000000000..f6de60ed01 --- /dev/null +++ b/packages/component/src/types/internal/FocusSendBoxInit.ts @@ -0,0 +1,3 @@ +import { type WaitUntilable } from '../../hooks/internal/createWaitUntilable'; + +export type FocusSendBoxInit = WaitUntilable<{ noKeyboard: boolean }>; diff --git a/packages/component/src/types/internal/FocusTranscriptInit.ts b/packages/component/src/types/internal/FocusTranscriptInit.ts new file mode 100644 index 0000000000..bceafccfc7 --- /dev/null +++ b/packages/component/src/types/internal/FocusTranscriptInit.ts @@ -0,0 +1,4 @@ +import { type EmptyObject } from 'type-fest'; +import { type WaitUntilable } from '../../hooks/internal/createWaitUntilable'; + +export type FocusTranscriptInit = WaitUntilable; From 2014516532d6f944c9f2e86c846e7c8634ff8a35 Mon Sep 17 00:00:00 2001 From: William Wong Date: Mon, 25 Mar 2024 10:13:50 +0000 Subject: [PATCH 2/2] Update PR number --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce14a38d24..2a3ccd5028 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Fixed -- Fixes [#5050](https://github.com/microsoft/BotFramework-WebChat/issues/5050). Fixed focus should not blur briefly after tapping on a suggested action, by [@compulim](https://github.com/compulim), in PR [#XXX](https://github.com/microsoft/BotFramework-WebChat/issues/pull/XXX) +- Fixes [#5050](https://github.com/microsoft/BotFramework-WebChat/issues/5050). Fixed focus should not blur briefly after tapping on a suggested action, by [@compulim](https://github.com/compulim), in PR [#5097](https://github.com/microsoft/BotFramework-WebChat/issues/pull/5097) ### Changed