From e8a054ae0e486142d04760027c74d15a1ff32cf1 Mon Sep 17 00:00:00 2001 From: Roksana Zawilowska Date: Fri, 3 Nov 2023 12:32:59 +0100 Subject: [PATCH 1/7] Fix 29405 cursor displayed before emoji on ios native --- src/components/Composer/index.ios.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js index e5dab3756594..3324cb8f3854 100644 --- a/src/components/Composer/index.ios.js +++ b/src/components/Composer/index.ios.js @@ -109,8 +109,6 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC // On native layers we like to have the Text Input not focused so the // user can read new chats without the keyboard in the way of the view. - // On Android the selection prop is required on the TextInput but this prop has issues on IOS - const propsToPass = _.omit(props, 'selection'); return ( ); From b107b9caa20bd2de3545da82190c4bf7671d5820 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Tue, 5 Dec 2023 21:53:36 +0100 Subject: [PATCH 2/7] Fix Composer not positioning caret properly on iOS after substituting emoji for marker --- src/components/Composer/index.ios.js | 4 ++- .../ComposerWithSuggestions.js | 30 +++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js index b2d7a6e89230..a1b8c1a4ffe6 100644 --- a/src/components/Composer/index.ios.js +++ b/src/components/Composer/index.ios.js @@ -110,6 +110,8 @@ function Composer({shouldClear, onClear, isDisabled, maxLines, forwardedRef, isC // On native layers we like to have the Text Input not focused so the // user can read new chats without the keyboard in the way of the view. + // On Android the selection prop is required on the TextInput but this prop has issues on IOS + const propsToPass = _.omit(props, 'selection'); return ( ); diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 663db82a6067..c363fb4e0f72 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -1,7 +1,7 @@ import {useIsFocused, useNavigation} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; -import {findNodeHandle, NativeModules, View} from 'react-native'; +import {findNodeHandle, InteractionManager, NativeModules, Platform, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import Composer from '@components/Composer'; @@ -44,6 +44,7 @@ const {RNTextInputReset} = NativeModules; // and subsequent programmatic focus shifts (e.g., modal focus trap) to show the blue frame (:focus-visible style), // so we need to ensure that it is only updated after focus. const isMobileSafari = Browser.isMobileSafari(); +const isIOSNative = Platform.OS === 'ios'; /** * Broadcast that the user is typing. Debounced to limit how often we publish client events. @@ -142,6 +143,8 @@ function ComposerWithSuggestions({ const textInputRef = useRef(null); const insertedEmojisRef = useRef([]); + const syncSelectionWithOnChangeTextRef = useRef(null); + // A flag to indicate whether the onScroll callback is likely triggered by a layout change (caused by text change) or not const isScrollLikelyLayoutTriggered = useRef(false); const suggestions = lodashGet(suggestionsRef, 'current.getSuggestions', () => [])(); @@ -241,6 +244,11 @@ function ComposerWithSuggestions({ setValue(newCommentConverted); if (commentValue !== newComment) { const position = Math.max(selection.end + (newComment.length - commentRef.current.length), cursorPosition || 0); + + if (isIOSNative) { + syncSelectionWithOnChangeTextRef.current = {position, value: newComment}; + } + setSelection({ start: position, end: position, @@ -543,7 +551,25 @@ function ComposerWithSuggestions({ ref={setTextInputRef} placeholder={inputPlaceholder} placeholderTextColor={theme.placeholderText} - onChangeText={(commentValue) => updateComment(commentValue, true)} + onChangeText={(commentValue) => { + if (syncSelectionWithOnChangeTextRef.current !== null) { + setSelection({ + start: syncSelectionWithOnChangeTextRef.current.position, + end: syncSelectionWithOnChangeTextRef.current.position, + }); + } + + updateComment(commentValue, true); + + if (syncSelectionWithOnChangeTextRef.current !== null) { + let positionSnaphsot = syncSelectionWithOnChangeTextRef.current.position; + syncSelectionWithOnChangeTextRef.current = null; + + InteractionManager.runAfterInteractions(() => { + textInputRef.current.setSelection(positionSnaphsot, positionSnaphsot); + }); + } + }} onKeyPress={triggerHotkeyActions} textAlignVertical="top" style={[styles.textInputCompose, isComposerFullSize ? styles.textInputFullCompose : styles.flex4]} From e2480e5c4161e7b2b202671622bf1929eb89153e Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 6 Dec 2023 11:42:15 +0100 Subject: [PATCH 3/7] Added comments, additional conditions, refactored fix in ComposerWithSuggestions --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 56184bce73da..471e58dea40c 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -554,11 +554,13 @@ function ComposerWithSuggestions({ updateComment(commentValue, true); - if (syncSelectionWithOnChangeTextRef.current !== null) { - let positionSnaphsot = syncSelectionWithOnChangeTextRef.current.position; + if (isIOSNative && syncSelectionWithOnChangeTextRef.current !== null) { + const positionSnaphsot = syncSelectionWithOnChangeTextRef.current.position; syncSelectionWithOnChangeTextRef.current = null; InteractionManager.runAfterInteractions(() => { + // note: this implementation is only available on non-web RN, thus the wrapping + // 'if' block contains a redundant (since the ref is only used on iOS) platform check textInputRef.current.setSelection(positionSnaphsot, positionSnaphsot); }); } From a0b31b568da56f87f7001f46444fca0e6e3c2254 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 6 Dec 2023 11:47:37 +0100 Subject: [PATCH 4/7] Fixed typo in ComposerWithSuggestions fix --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 471e58dea40c..490771620e29 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -555,13 +555,13 @@ function ComposerWithSuggestions({ updateComment(commentValue, true); if (isIOSNative && syncSelectionWithOnChangeTextRef.current !== null) { - const positionSnaphsot = syncSelectionWithOnChangeTextRef.current.position; + const positionSnapshot = syncSelectionWithOnChangeTextRef.current.position; syncSelectionWithOnChangeTextRef.current = null; InteractionManager.runAfterInteractions(() => { // note: this implementation is only available on non-web RN, thus the wrapping // 'if' block contains a redundant (since the ref is only used on iOS) platform check - textInputRef.current.setSelection(positionSnaphsot, positionSnaphsot); + textInputRef.current.setSelection(positionSnapshot, positionSnapshot); }); } }} From 062265efd0de45f311da38c81f0a91333ac8ec80 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Thu, 7 Dec 2023 16:30:03 +0100 Subject: [PATCH 5/7] Refactored ComposerWithSuggestions --- .../ComposerWithSuggestions.js | 52 +++++++++++-------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 574ab020d01a..25aab5747f1e 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -1,7 +1,7 @@ import {useIsFocused, useNavigation} from '@react-navigation/native'; import lodashGet from 'lodash/get'; import React, {useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; -import {findNodeHandle, InteractionManager, NativeModules, Platform, View} from 'react-native'; +import {findNodeHandle, InteractionManager, NativeModules, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import Composer from '@components/Composer'; @@ -17,6 +17,7 @@ import getDraftComment from '@libs/ComposerUtils/getDraftComment'; import convertToLTRForComposer from '@libs/convertToLTRForComposer'; import * as EmojiUtils from '@libs/EmojiUtils'; import focusComposerWithDelay from '@libs/focusComposerWithDelay'; +import getPlatform from '@libs/getPlatform'; import * as KeyDownListener from '@libs/KeyboardShortcut/KeyDownPressListener'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; @@ -39,7 +40,7 @@ import {defaultProps, propTypes} from './composerWithSuggestionsProps'; const {RNTextInputReset} = NativeModules; -const isIOSNative = Platform.OS === 'ios'; +const isIOSNative = getPlatform() === CONST.PLATFORM.IOS; /** * Broadcast that the user is typing. Debounced to limit how often we publish client events. @@ -373,6 +374,31 @@ function ComposerWithSuggestions({ [isKeyboardShown, isSmallScreenWidth, parentReportActions, report, reportActions, reportID, handleSendMessage, suggestionsRef, valueRef], ); + const onChangeText = useCallback( + (commentValue) => { + if (syncSelectionWithOnChangeTextRef.current) { + setSelection({ + start: syncSelectionWithOnChangeTextRef.current.position, + end: syncSelectionWithOnChangeTextRef.current.position, + }); + } + + updateComment(commentValue, true); + + if (isIOSNative && syncSelectionWithOnChangeTextRef.current) { + const positionSnapshot = syncSelectionWithOnChangeTextRef.current.position; + syncSelectionWithOnChangeTextRef.current = null; + + InteractionManager.runAfterInteractions(() => { + // note: this implementation is only available on non-web RN, thus the wrapping + // 'if' block contains a redundant (since the ref is only used on iOS) platform check + textInputRef.current.setSelection(positionSnapshot, positionSnapshot); + }); + } + }, + [updateComment], + ); + const onSelectionChange = useCallback( (e) => { if (textInputRef.current && textInputRef.current.isFocused() && suggestionsRef.current.onSelectionChange(e)) { @@ -533,27 +559,7 @@ function ComposerWithSuggestions({ ref={setTextInputRef} placeholder={inputPlaceholder} placeholderTextColor={theme.placeholderText} - onChangeText={(commentValue) => { - if (syncSelectionWithOnChangeTextRef.current !== null) { - setSelection({ - start: syncSelectionWithOnChangeTextRef.current.position, - end: syncSelectionWithOnChangeTextRef.current.position, - }); - } - - updateComment(commentValue, true); - - if (isIOSNative && syncSelectionWithOnChangeTextRef.current !== null) { - const positionSnapshot = syncSelectionWithOnChangeTextRef.current.position; - syncSelectionWithOnChangeTextRef.current = null; - - InteractionManager.runAfterInteractions(() => { - // note: this implementation is only available on non-web RN, thus the wrapping - // 'if' block contains a redundant (since the ref is only used on iOS) platform check - textInputRef.current.setSelection(positionSnapshot, positionSnapshot); - }); - } - }} + onChangeText={onChangeText} onKeyPress={triggerHotkeyActions} textAlignVertical="top" style={[styles.textInputCompose, isComposerFullSize ? styles.textInputFullCompose : styles.textInputCollapseCompose]} From b2ff88cd3d44870fdf7677ebb5302df9568d2951 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 13 Dec 2023 12:54:40 +0100 Subject: [PATCH 6/7] Stripped obsolete code from ComposerWithSuggestions.js --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 25aab5747f1e..097a2fff8b28 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -376,13 +376,6 @@ function ComposerWithSuggestions({ const onChangeText = useCallback( (commentValue) => { - if (syncSelectionWithOnChangeTextRef.current) { - setSelection({ - start: syncSelectionWithOnChangeTextRef.current.position, - end: syncSelectionWithOnChangeTextRef.current.position, - }); - } - updateComment(commentValue, true); if (isIOSNative && syncSelectionWithOnChangeTextRef.current) { From 98436b74a0db72abc683e0f35799acbbba9874ab Mon Sep 17 00:00:00 2001 From: artus9033 Date: Thu, 21 Dec 2023 00:28:10 +0100 Subject: [PATCH 7/7] Added explanatory commit to ComposerWithSuggestions.js --- .../ComposerWithSuggestions/ComposerWithSuggestions.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js index 097a2fff8b28..24c4948598af 100644 --- a/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js +++ b/src/pages/home/report/ReportActionCompose/ComposerWithSuggestions/ComposerWithSuggestions.js @@ -382,6 +382,7 @@ function ComposerWithSuggestions({ const positionSnapshot = syncSelectionWithOnChangeTextRef.current.position; syncSelectionWithOnChangeTextRef.current = null; + // ensure that selection is set imperatively after all state changes are effective InteractionManager.runAfterInteractions(() => { // note: this implementation is only available on non-web RN, thus the wrapping // 'if' block contains a redundant (since the ref is only used on iOS) platform check