diff --git a/CHANGELOG.md b/CHANGELOG.md
index 730d0ee811..021ea178c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -386,6 +386,7 @@ Breaking changes in this release:
- Fixed Content Security Policy documentation and sample in PR, by [@compulim](https://github.com/compulim) in PR [#5648](https://github.com/microsoft/BotFramework-WebChat/pull/5648)
- Added `img-src data:`, required for icons
- Downgraded graph upsert conflict checks, by [@compulim](https://github.com/compulim) in PR [#5674](https://github.com/microsoft/BotFramework-WebChat/pull/5674)
+- Fixed virtual keyboard should show up on tap after being suppressed, in iOS 26.2, by [@compulim](https://github.com/compulim) in PR [#5678](https://github.com/microsoft/BotFramework-WebChat/pull/5678)
## [4.18.0] - 2024-07-10
diff --git a/__tests__/html2/fluentTheme/uiState.html.snap-3.png b/__tests__/html2/fluentTheme/uiState.html.snap-3.png
index ff7fa334e1..7c929272ae 100644
Binary files a/__tests__/html2/fluentTheme/uiState.html.snap-3.png and b/__tests__/html2/fluentTheme/uiState.html.snap-3.png differ
diff --git a/__tests__/html2/fluentTheme/uiState.html.snap-4.png b/__tests__/html2/fluentTheme/uiState.html.snap-4.png
index f9a5cc1bfb..4bd0b6dfa6 100644
Binary files a/__tests__/html2/fluentTheme/uiState.html.snap-4.png and b/__tests__/html2/fluentTheme/uiState.html.snap-4.png differ
diff --git a/__tests__/html2/sendBox/hideKeyboard.html b/__tests__/html2/sendBox/hideKeyboard.html
new file mode 100644
index 0000000000..37d193ce3e
--- /dev/null
+++ b/__tests__/html2/sendBox/hideKeyboard.html
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/component/src/SendBox/AutoResizeTextArea.tsx b/packages/component/src/SendBox/AutoResizeTextArea.tsx
index cf59b83d2c..7ed5957703 100644
--- a/packages/component/src/SendBox/AutoResizeTextArea.tsx
+++ b/packages/component/src/SendBox/AutoResizeTextArea.tsx
@@ -4,7 +4,8 @@ import React, {
FocusEventHandler,
forwardRef,
KeyboardEventHandler,
- ReactEventHandler
+ ReactEventHandler,
+ type MouseEventHandler
} from 'react';
import AccessibleTextArea from '../Utils/AccessibleTextArea';
@@ -21,6 +22,7 @@ type AutoResizeTextAreaProps = Readonly<{
enterKeyHint?: string | undefined;
inputMode?: 'text' | 'none' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search' | undefined;
onChange?: ChangeEventHandler | undefined;
+ onClick?: MouseEventHandler | undefined;
onFocus?: FocusEventHandler | undefined;
onKeyDown?: KeyboardEventHandler | undefined;
onKeyDownCapture?: KeyboardEventHandler | undefined;
@@ -45,6 +47,7 @@ const AutoResizeTextArea = forwardRef = useRef();
const localize = useLocalizer();
@@ -165,44 +165,25 @@ const TextBox = ({ className = '' }: Readonly<{ className?: string | undefined }
);
const focusCallback = useCallback(
- (options: SendBoxFocusOptions) => {
- const { noKeyboard } = options;
+ ({ noKeyboard }: SendBoxFocusOptions) => {
const { current } = inputElementRef;
- if (current) {
- // The "disable soft keyboard on mobile devices" logic will not work on IE11. It will cause the to become read-only until next focus.
- // Thus, no mobile devices carry IE11 so we don't need to explicitly disable soft keyboard on IE11.
- // See #3757 for repro and details.
- if (noKeyboard && !ie11) {
- // To not activate the virtual keyboard while changing focus to an input, we will temporarily set it as read-only and flip it back.
- // https://stackoverflow.com/questions/7610758/prevent-iphone-default-keyboard-when-focusing-an-input/7610923
- const readOnly = current.getAttribute('readonly');
-
- current.setAttribute('readonly', 'readonly');
-
- 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();
- }
- }
+ // Setting `inputMode` to `none` temporarily to suppress soft keyboard in iOS.
+ // We will revert the change once the end-user tap on the send box.
+ // This code path is only triggered when the user press "send" button to send the message, instead of pressing ENTER key.
+ noKeyboard && current?.setAttribute('inputmode', 'none');
+ current?.focus();
},
- [inputElementRef, setTimeout]
+ [inputElementRef]
);
useRegisterFocusSendBox(focusCallback);
+ const handleClick = useCallback(
+ ({ currentTarget }) => currentTarget.setAttribute('inputmode', DEFAULT_INPUT_MODE),
+ []
+ );
+
const emojiMap = useMemo(() => new Map(Object.entries(emojiSet)), [emojiSet]);
return (
@@ -225,8 +206,9 @@ const TextBox = ({ className = '' }: Readonly<{ className?: string | undefined }
disabled={disabled}
emojiMap={emojiMap}
enterKeyHint="send"
- inputMode="text"
+ inputMode={DEFAULT_INPUT_MODE}
onChange={setValue}
+ onClick={handleClick}
onKeyDownCapture={disabled ? undefined : handleKeyDownCapture}
onKeyPress={disabled ? undefined : handleKeyPress}
placeholder={typeYourMessageString}
@@ -244,8 +226,9 @@ const TextBox = ({ className = '' }: Readonly<{ className?: string | undefined }
disabled={disabled}
emojiMap={emojiMap}
enterKeyHint="send"
- inputMode="text"
+ inputMode={DEFAULT_INPUT_MODE}
onChange={setValue}
+ onClick={handleClick}
onKeyDownCapture={disabled ? undefined : handleKeyDownCapture}
onKeyPress={disabled ? undefined : handleKeyPress}
placeholder={typeYourMessageString}
diff --git a/packages/component/src/TextArea/TextArea.tsx b/packages/component/src/TextArea/TextArea.tsx
index fd870ef711..15ab98a91c 100644
--- a/packages/component/src/TextArea/TextArea.tsx
+++ b/packages/component/src/TextArea/TextArea.tsx
@@ -8,6 +8,7 @@ import React, {
useRef,
type FormEventHandler,
type KeyboardEventHandler,
+ type MouseEventHandler,
type ReactNode
} from 'react';
@@ -33,6 +34,7 @@ const TextArea = forwardRef<
* This ensures the flow of focus did not sent to document body
*/
hidden?: boolean | undefined;
+ onClick?: MouseEventHandler | undefined;
onInput?: FormEventHandler | undefined;
placeholder?: string | undefined;
startRows?: number | undefined;
@@ -90,6 +92,7 @@ const TextArea = forwardRef<
aria-placeholder={props.placeholder}
className={cx(classNames['text-area-input'], classNames['text-area-shared'])}
data-testid={props['data-testid']}
+ onClick={props.onClick}
onCompositionEnd={handleCompositionEnd}
onCompositionStart={handleCompositionStart}
onInput={props.onInput}
diff --git a/packages/component/src/Utils/AccessibleInputText.tsx b/packages/component/src/Utils/AccessibleInputText.tsx
index 3e19f3c57d..da9221baae 100644
--- a/packages/component/src/Utils/AccessibleInputText.tsx
+++ b/packages/component/src/Utils/AccessibleInputText.tsx
@@ -1,11 +1,12 @@
/* eslint no-magic-numbers: ["error", { "ignore": [-1] }] */
import React, {
- ChangeEventHandler,
- FocusEventHandler,
+ type ChangeEventHandler,
+ type FocusEventHandler,
forwardRef,
- KeyboardEventHandler,
- ReactEventHandler,
+ type KeyboardEventHandler,
+ type MouseEventHandler,
+ type ReactEventHandler,
useRef
} from 'react';
@@ -38,6 +39,7 @@ type AccessibleInputTextProps = Readonly<{
enterKeyHint?: string | undefined;
inputMode?: 'text' | 'none' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search' | undefined;
onChange?: ChangeEventHandler | undefined;
+ onClick?: MouseEventHandler | undefined;
onFocus?: FocusEventHandler | undefined;
onKeyDown?: KeyboardEventHandler | undefined;
onKeyDownCapture?: KeyboardEventHandler | undefined;
@@ -60,6 +62,7 @@ const AccessibleInputText = forwardRef, the developer need to filter out some `onSubmit` event caused by this widget
-type AccessibleTextAreaProps = {
+type AccessibleTextAreaProps = Readonly<{
className?: string;
disabled?: boolean;
inputMode?: 'text' | 'none' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search';
onChange?: ChangeEventHandler;
+ onClick?: MouseEventHandler | undefined;
onFocus?: FocusEventHandler;
onKeyDown?: KeyboardEventHandler;
onKeyDownCapture?: KeyboardEventHandler;
@@ -43,7 +44,7 @@ type AccessibleTextAreaProps = {
rows?: number;
tabIndex?: number;
value?: string;
-};
+}>;
const AccessibleTextArea = forwardRef(
(
@@ -52,6 +53,7 @@ const AccessibleTextArea = forwardRef {
- if (!inputRef.current) {
- return;
- }
- if (noKeyboard) {
- waitUntil(
- (async () => {
- const previousReadOnly = inputRef.current?.getAttribute('readonly');
- inputRef.current?.setAttribute('readonly', 'true');
- // 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`.
- // eslint-disable-next-line no-restricted-globals
- await new Promise(resolve => setTimeout(resolve, 0));
- inputRef.current?.focus();
- if (typeof previousReadOnly !== 'string') {
- inputRef.current?.removeAttribute('readonly');
- } else {
- inputRef.current?.setAttribute('readonly', previousReadOnly);
- }
- })()
- );
- } else {
- inputRef.current?.focus();
- }
+ ({ noKeyboard }) => {
+ const { current } = inputRef;
+
+ // Setting `inputMode` to `none` temporarily to suppress soft keyboard in iOS.
+ // We will revert the change once the end-user tap on the send box.
+ // This code path is only triggered when the user press "send" button to send the message, instead of pressing ENTER key.
+ noKeyboard && current?.setAttribute('inputmode', 'none');
+ current?.focus();
},
[inputRef]
)
@@ -144,6 +127,8 @@ function SendBox(props: Props) {
[attachmentsRef, makeThumbnail, setAttachments]
);
+ const handleClick = useCallback(({ currentTarget }) => currentTarget.removeAttribute('inputmode'), []);
+
const handleFormSubmit: FormEventHandler = useCallback(
event => {
event.preventDefault();
@@ -206,6 +191,7 @@ function SendBox(props: Props) {
completion={props.completion}
data-testid={testIds.sendBoxTextBox}
hidden={shouldShowTelephoneKeypad}
+ onClick={handleClick}
onInput={handleMessageChange}
placeholder={props.placeholder ?? localize('TEXT_INPUT_PLACEHOLDER')}
ref={inputRef}