From ce9d3895206bb74f6ebf8f750fc580f8820c19c1 Mon Sep 17 00:00:00 2001 From: dominictb Date: Mon, 27 May 2024 09:42:19 +0700 Subject: [PATCH 1/9] fix: keep the Android keyboard visible when pasting in Composer component env: Android Chrome Signed-off-by: dominictb --- src/components/Composer/index.tsx | 2 +- src/hooks/useHtmlPaste/index.ts | 11 +++++++---- src/hooks/useHtmlPaste/types.ts | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index f7bf277050a2..a7635db58472 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -250,7 +250,7 @@ function Composer( // eslint-disable-next-line react-hooks/exhaustive-deps }, [isComposerFullSize]); - useHtmlPaste(textInput, handlePaste, true); + useHtmlPaste(textInput, handlePaste, true, false); useEffect(() => { if (typeof ref === 'function') { diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 925a3db518ae..b6ee5ab122fc 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -18,8 +18,7 @@ const insertAtCaret = (target: HTMLElement, text: string) => { // Move caret to the end of the newly inserted text node. range.setStart(node, node.length); range.setEnd(node, node.length); - selection.removeAllRanges(); - selection.addRange(range); + selection.setBaseAndExtent(range.startContainer, range.startOffset, range.endContainer, range.endOffset); // Dispatch paste event to simulate real browser behavior target.dispatchEvent(new Event('paste', {bubbles: true})); @@ -30,7 +29,7 @@ const insertAtCaret = (target: HTMLElement, text: string) => { } }; -const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false) => { +const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false, shouldRefocusAfterPaste = true) => { const navigation = useNavigation(); /** @@ -47,7 +46,11 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi } // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. - textInputRef.current?.blur(); + // If shouldRefocusAfterPaste = false, we want to call `focus()` only as it won't change the focus state of the input since it is already focused + if (shouldRefocusAfterPaste) { + textInputRef.current?.blur(); + } + textInputRef.current?.focus(); // eslint-disable-next-line no-empty } catch (e) {} diff --git a/src/hooks/useHtmlPaste/types.ts b/src/hooks/useHtmlPaste/types.ts index 305ebe5fbd0f..f2dd41eb2488 100644 --- a/src/hooks/useHtmlPaste/types.ts +++ b/src/hooks/useHtmlPaste/types.ts @@ -5,6 +5,7 @@ type UseHtmlPaste = ( textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput) | TextInput | null>, preHtmlPasteCallback?: (event: ClipboardEvent) => boolean, removeListenerOnScreenBlur?: boolean, + shouldRefocusAfterPaste?: boolean, ) => void; export default UseHtmlPaste; From d5176fdd4b2113d983b6d2223eea489472b00557 Mon Sep 17 00:00:00 2001 From: dominictb Date: Mon, 10 Jun 2024 03:14:45 +0700 Subject: [PATCH 2/9] fix: another solution for cursor position after paste Signed-off-by: dominictb --- src/components/Composer/index.tsx | 3 ++- src/hooks/useHtmlPaste/index.ts | 11 +++++++++-- src/hooks/useHtmlPaste/types.ts | 6 +++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 69e2fd909010..c4728cd6635e 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -9,6 +9,7 @@ import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; import Text from '@components/Text'; import useHtmlPaste from '@hooks/useHtmlPaste'; +import UseHtmlPaste from '@hooks/useHtmlPaste/types'; import useIsScrollBarVisible from '@hooks/useIsScrollBarVisible'; import useMarkdownStyle from '@hooks/useMarkdownStyle'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -251,7 +252,7 @@ function Composer( // eslint-disable-next-line react-hooks/exhaustive-deps }, [isComposerFullSize]); - useHtmlPaste(textInput, handlePaste, true, false); + useHtmlPaste(textInput as Parameters[0], handlePaste, true, false); useEffect(() => { if (typeof ref === 'function') { diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 11b70a6c4a11..645d6be076e5 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -45,13 +45,20 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi insertByCommand(text); } + if (!textInputRef.current?.isFocused()) { + textInputRef.current?.focus(); + return; + } + // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. // If shouldRefocusAfterPaste = false, we want to call `focus()` only as it won't change the focus state of the input since it is already focused if (shouldRefocusAfterPaste) { textInputRef.current?.blur(); + textInputRef.current?.focus(); + return; } - - textInputRef.current?.focus(); + // just restore the selection position if the input is already focused + textInputRef.current?.restoreSelectionPosition?.(); // eslint-disable-next-line no-empty } catch (e) {} // We only need to set the callback once. diff --git a/src/hooks/useHtmlPaste/types.ts b/src/hooks/useHtmlPaste/types.ts index f2dd41eb2488..377b0d6a7703 100644 --- a/src/hooks/useHtmlPaste/types.ts +++ b/src/hooks/useHtmlPaste/types.ts @@ -1,8 +1,12 @@ import type {MutableRefObject} from 'react'; import type {TextInput} from 'react-native'; +export type WithSelectionRestoreAbility = { + restoreSelectionPosition?: () => void; +}; + type UseHtmlPaste = ( - textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput) | TextInput | null>, + textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput & WithSelectionRestoreAbility) | (TextInput & WithSelectionRestoreAbility) | null>, preHtmlPasteCallback?: (event: ClipboardEvent) => boolean, removeListenerOnScreenBlur?: boolean, shouldRefocusAfterPaste?: boolean, From a47bee0ed4ef10e00b81dc37c83c6a032ad0387f Mon Sep 17 00:00:00 2001 From: dominictb Date: Mon, 10 Jun 2024 03:30:04 +0700 Subject: [PATCH 3/9] fix: lint issue Signed-off-by: dominictb --- src/components/Composer/index.tsx | 3 +-- src/hooks/useHtmlPaste/types.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index c4728cd6635e..69e2fd909010 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -9,7 +9,6 @@ import type {AnimatedMarkdownTextInputRef} from '@components/RNMarkdownTextInput import RNMarkdownTextInput from '@components/RNMarkdownTextInput'; import Text from '@components/Text'; import useHtmlPaste from '@hooks/useHtmlPaste'; -import UseHtmlPaste from '@hooks/useHtmlPaste/types'; import useIsScrollBarVisible from '@hooks/useIsScrollBarVisible'; import useMarkdownStyle from '@hooks/useMarkdownStyle'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -252,7 +251,7 @@ function Composer( // eslint-disable-next-line react-hooks/exhaustive-deps }, [isComposerFullSize]); - useHtmlPaste(textInput as Parameters[0], handlePaste, true, false); + useHtmlPaste(textInput, handlePaste, true, false); useEffect(() => { if (typeof ref === 'function') { diff --git a/src/hooks/useHtmlPaste/types.ts b/src/hooks/useHtmlPaste/types.ts index 377b0d6a7703..10ad40d5e4c0 100644 --- a/src/hooks/useHtmlPaste/types.ts +++ b/src/hooks/useHtmlPaste/types.ts @@ -1,7 +1,7 @@ import type {MutableRefObject} from 'react'; import type {TextInput} from 'react-native'; -export type WithSelectionRestoreAbility = { +type WithSelectionRestoreAbility = { restoreSelectionPosition?: () => void; }; From 89e850bc4ce9a5a9b3ce66fcbc7274ab5095496e Mon Sep 17 00:00:00 2001 From: dominictb Date: Tue, 11 Jun 2024 00:07:41 +0700 Subject: [PATCH 4/9] fix: fallback focus mechanism Signed-off-by: dominictb --- src/hooks/useHtmlPaste/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 645d6be076e5..cc8c47cbdcda 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -52,7 +52,7 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. // If shouldRefocusAfterPaste = false, we want to call `focus()` only as it won't change the focus state of the input since it is already focused - if (shouldRefocusAfterPaste) { + if (shouldRefocusAfterPaste || !textInputRef.current?.restoreSelectionPosition) { textInputRef.current?.blur(); textInputRef.current?.focus(); return; From ee2b19722c60cdfdf3cbe14287bc63a307a716d7 Mon Sep 17 00:00:00 2001 From: dominictb Date: Tue, 11 Jun 2024 10:02:49 +0700 Subject: [PATCH 5/9] fix: using dispatchEvent to avoid keyboard issue Signed-off-by: dominictb --- src/components/Composer/index.tsx | 2 +- src/hooks/useHtmlPaste/index.ts | 24 +++++++++++++++--------- src/hooks/useHtmlPaste/types.ts | 9 ++------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 69e2fd909010..14762b2d4bc1 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -251,7 +251,7 @@ function Composer( // eslint-disable-next-line react-hooks/exhaustive-deps }, [isComposerFullSize]); - useHtmlPaste(textInput, handlePaste, true, false); + useHtmlPaste(textInput, handlePaste, true); useEffect(() => { if (typeof ref === 'function') { diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index cc8c47cbdcda..8c70853543a1 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -29,7 +29,7 @@ const insertAtCaret = (target: HTMLElement, text: string) => { } }; -const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false, shouldRefocusAfterPaste = true) => { +const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false) => { const navigation = useNavigation(); /** @@ -51,14 +51,20 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi } // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. - // If shouldRefocusAfterPaste = false, we want to call `focus()` only as it won't change the focus state of the input since it is already focused - if (shouldRefocusAfterPaste || !textInputRef.current?.restoreSelectionPosition) { - textInputRef.current?.blur(); - textInputRef.current?.focus(); - return; - } - // just restore the selection position if the input is already focused - textInputRef.current?.restoreSelectionPosition?.(); + // to avoid the keyboard in mobile web if using blur() and focus() function, we just need to dispatch the event to trigger the onFocus handler + + textInputHTMLElement.dispatchEvent(new FocusEvent('focus', { + bubbles: true, + cancelable: true, + view: window + })) + + // need to trigger the focusin event to make sure the onFocus handler is triggered + textInputHTMLElement.dispatchEvent(new FocusEvent('focusin', { + bubbles: true, + cancelable: true, + view: window + })) // eslint-disable-next-line no-empty } catch (e) {} // We only need to set the callback once. diff --git a/src/hooks/useHtmlPaste/types.ts b/src/hooks/useHtmlPaste/types.ts index 10ad40d5e4c0..cdecf98135a0 100644 --- a/src/hooks/useHtmlPaste/types.ts +++ b/src/hooks/useHtmlPaste/types.ts @@ -1,15 +1,10 @@ import type {MutableRefObject} from 'react'; import type {TextInput} from 'react-native'; -type WithSelectionRestoreAbility = { - restoreSelectionPosition?: () => void; -}; - type UseHtmlPaste = ( - textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput & WithSelectionRestoreAbility) | (TextInput & WithSelectionRestoreAbility) | null>, + textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput) | TextInput | null>, preHtmlPasteCallback?: (event: ClipboardEvent) => boolean, - removeListenerOnScreenBlur?: boolean, - shouldRefocusAfterPaste?: boolean, + removeListenerOnScreenBlur?: boolean ) => void; export default UseHtmlPaste; From c1be42d49c9dc679e431511523b4b71b3ccaeacd Mon Sep 17 00:00:00 2001 From: dominictb Date: Tue, 11 Jun 2024 10:14:08 +0700 Subject: [PATCH 6/9] fix: lint Signed-off-by: dominictb --- src/hooks/useHtmlPaste/index.ts | 24 ++++++++++++++---------- src/hooks/useHtmlPaste/types.ts | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 8c70853543a1..87f684e58502 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -53,18 +53,22 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. // to avoid the keyboard in mobile web if using blur() and focus() function, we just need to dispatch the event to trigger the onFocus handler - textInputHTMLElement.dispatchEvent(new FocusEvent('focus', { - bubbles: true, - cancelable: true, - view: window - })) + textInputHTMLElement.dispatchEvent( + new FocusEvent('focus', { + bubbles: true, + cancelable: true, + view: window, + }), + ); // need to trigger the focusin event to make sure the onFocus handler is triggered - textInputHTMLElement.dispatchEvent(new FocusEvent('focusin', { - bubbles: true, - cancelable: true, - view: window - })) + textInputHTMLElement.dispatchEvent( + new FocusEvent('focusin', { + bubbles: true, + cancelable: true, + view: window, + }), + ); // eslint-disable-next-line no-empty } catch (e) {} // We only need to set the callback once. diff --git a/src/hooks/useHtmlPaste/types.ts b/src/hooks/useHtmlPaste/types.ts index cdecf98135a0..305ebe5fbd0f 100644 --- a/src/hooks/useHtmlPaste/types.ts +++ b/src/hooks/useHtmlPaste/types.ts @@ -4,7 +4,7 @@ import type {TextInput} from 'react-native'; type UseHtmlPaste = ( textInputRef: MutableRefObject<(HTMLTextAreaElement & TextInput) | TextInput | null>, preHtmlPasteCallback?: (event: ClipboardEvent) => boolean, - removeListenerOnScreenBlur?: boolean + removeListenerOnScreenBlur?: boolean, ) => void; export default UseHtmlPaste; From 0dee60889fedb8015e56122d3393521feb3e4706 Mon Sep 17 00:00:00 2001 From: dominictb Date: Tue, 11 Jun 2024 17:23:30 +0700 Subject: [PATCH 7/9] chore: upgrade rn-live-markdown 0.1.84 --- ios/Podfile.lock | 10 +++++----- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index aca46d6b18ed..b877c56a5581 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1852,7 +1852,7 @@ PODS: - RNGoogleSignin (10.0.1): - GoogleSignIn (~> 7.0) - React-Core - - RNLiveMarkdown (0.1.83): + - RNLiveMarkdown (0.1.84): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -1870,9 +1870,9 @@ PODS: - React-utils - ReactCommon/turbomodule/bridging - ReactCommon/turbomodule/core - - RNLiveMarkdown/common (= 0.1.83) + - RNLiveMarkdown/common (= 0.1.84) - Yoga - - RNLiveMarkdown/common (0.1.83): + - RNLiveMarkdown/common (0.1.84): - glog - hermes-engine - RCT-Folly (= 2022.05.16.00) @@ -2589,7 +2589,7 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 74b7b3d06d667ba0bbf41da7718f2607ae0dfe8f RNGoogleSignin: ccaa4a81582cf713eea562c5dd9dc1961a715fd0 - RNLiveMarkdown: 88030b7d9a31f5f6e67743df48ad952d64513b4a + RNLiveMarkdown: bf516c02a4549a059829a3fbb8f51c2e0a3110e7 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 rnmapbox-maps: df8fe93dbd251f25022f4023d31bc04160d4d65c RNPermissions: 0b61d30d21acbeafe25baaa47d9bae40a0c65216 @@ -2606,7 +2606,7 @@ SPEC CHECKSUMS: SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Turf: 13d1a92d969ca0311bbc26e8356cca178ce95da2 VisionCamera: 1394a316c7add37e619c48d7aa40b38b954bf055 - Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70 + Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312 PODFILE CHECKSUM: 66a5c97ae1059e4da1993a4ad95abe5d819f555b diff --git a/package-lock.json b/package-lock.json index 66015a4955a1..7f00798e6d29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.83", + "@expensify/react-native-live-markdown": "0.1.84", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", @@ -3558,9 +3558,9 @@ } }, "node_modules/@expensify/react-native-live-markdown": { - "version": "0.1.83", - "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.83.tgz", - "integrity": "sha512-xGn1P9FbFVueEF8BNKJJ4dQb0wPtsAvrrxND9pwVQT35ZL5cu1KZ4o6nzCqtesISPRB8Dw9Zx0ftIZy2uCQyzA==", + "version": "0.1.84", + "resolved": "https://registry.npmjs.org/@expensify/react-native-live-markdown/-/react-native-live-markdown-0.1.84.tgz", + "integrity": "sha512-gyRjmOozNlCCBKoQtEvohV+P4iR6VL4Z5QpuE3SXSE7J77WlCiaCMg5LjWFL8Q3Vn3ZApsQWhReLIXj3kfN9WA==", "workspaces": [ "parser", "example", diff --git a/package.json b/package.json index 697b9168dcd6..52b6b72f43ab 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@babel/plugin-proposal-private-methods": "^7.18.6", "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@dotlottie/react-player": "^1.6.3", - "@expensify/react-native-live-markdown": "0.1.83", + "@expensify/react-native-live-markdown": "0.1.84", "@expo/metro-runtime": "~3.1.1", "@formatjs/intl-datetimeformat": "^6.10.0", "@formatjs/intl-listformat": "^7.2.2", From 6728e0cdb4a9d3778460e458edd74b0f432be100 Mon Sep 17 00:00:00 2001 From: dominictb Date: Wed, 12 Jun 2024 13:17:31 +0700 Subject: [PATCH 8/9] chore: refine the target event Signed-off-by: dominictb --- src/hooks/useHtmlPaste/index.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 87f684e58502..53ea322610a6 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -53,20 +53,10 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. // to avoid the keyboard in mobile web if using blur() and focus() function, we just need to dispatch the event to trigger the onFocus handler - textInputHTMLElement.dispatchEvent( - new FocusEvent('focus', { - bubbles: true, - cancelable: true, - view: window, - }), - ); - - // need to trigger the focusin event to make sure the onFocus handler is triggered + // need to trigger the bubbled "focusin" event to make sure the onFocus handler is triggered textInputHTMLElement.dispatchEvent( new FocusEvent('focusin', { bubbles: true, - cancelable: true, - view: window, }), ); // eslint-disable-next-line no-empty From 6f58097cc311d7cf56432700c2bf255ec294cff1 Mon Sep 17 00:00:00 2001 From: dominictb Date: Mon, 17 Jun 2024 08:35:39 +0700 Subject: [PATCH 9/9] chore: update code comment Signed-off-by: dominictb --- src/hooks/useHtmlPaste/index.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index 9290550670df..422952c68435 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -51,9 +51,8 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi } // Pointer will go out of sight when a large paragraph is pasted on the web. Refocusing the input keeps the cursor in view. - // to avoid the keyboard in mobile web if using blur() and focus() function, we just need to dispatch the event to trigger the onFocus handler - - // need to trigger the bubbled "focusin" event to make sure the onFocus handler is triggered + // To avoid the keyboard toggle issue in mWeb if using blur() and focus() functions, we just need to dispatch the event to trigger the onFocus handler + // We need to trigger the bubbled "focusin" event to make sure the onFocus handler is triggered textInputHTMLElement.dispatchEvent( new FocusEvent('focusin', { bubbles: true,