Skip to content
Merged
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import lodashDebounce from 'lodash/debounce';
import type {ForwardedRef, RefObject} from 'react';
import React, {memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import type {BlurEvent, LayoutChangeEvent, MeasureInWindowOnSuccessCallback, TextInput, TextInputContentSizeChangeEvent, TextInputKeyPressEvent, TextInputScrollEvent} from 'react-native';
import {DeviceEventEmitter, NativeModules, StyleSheet, View} from 'react-native';
import {DeviceEventEmitter, InteractionManager, NativeModules, StyleSheet, View} from 'react-native';
import {useFocusedInputHandler} from 'react-native-keyboard-controller';
import type {OnyxEntry} from 'react-native-onyx';
import {useAnimatedRef, useSharedValue} from 'react-native-reanimated';
Expand All @@ -27,6 +27,7 @@ import {canSkipTriggerHotkeys, findCommonSuffixLength, insertText, insertWhiteSp
import convertToLTRForComposer from '@libs/convertToLTRForComposer';
import {containsOnlyEmojis, extractEmojis, getAddedEmojis, getPreferredSkinToneIndex, replaceAndExtractEmojis} from '@libs/EmojiUtils';
import focusComposerWithDelay from '@libs/focusComposerWithDelay';
import getPlatform from '@libs/getPlatform';
import {addKeyDownPressListener, removeKeyDownPressListener} from '@libs/KeyboardShortcut/KeyDownPressListener';
import {detectAndRewritePaste} from '@libs/MarkdownLinkHelpers';
import Parser from '@libs/Parser';
Expand All @@ -52,6 +53,11 @@ import type ChildrenProps from '@src/types/utils/ChildrenProps';
// eslint-disable-next-line no-restricted-imports
import findNodeHandle from '@src/utils/findNodeHandle';

type SyncSelection = {
position: number;
value: string;
};

type NewlyAddedChars = {startIndex: number; endIndex: number; diff: string};

type ComposerWithSuggestionsProps = Partial<ChildrenProps> & {
Expand Down Expand Up @@ -154,6 +160,8 @@ type ComposerRef = {

const {RNTextInputReset} = NativeModules;

const isIOSNative = getPlatform() === CONST.PLATFORM.IOS;

/**
* Broadcast that the user is typing. Debounced to limit how often we publish client events.
*/
Expand Down Expand Up @@ -261,10 +269,10 @@ function ComposerWithSuggestions({

const [composerHeight, setComposerHeight] = useState(0);

const [suggestionPosition, setSuggestionPosition] = useState<number | null>(null);

const textInputRef = useRef<TextInput | null>(null);

const syncSelectionWithOnChangeTextRef = useRef<SyncSelection | null>(null);

// The ref to check whether the comment saving is in progress
const isCommentPendingSaved = useRef(false);

Expand Down Expand Up @@ -407,6 +415,10 @@ function ComposerWithSuggestions({
if (commentValue !== newComment) {
const position = Math.max((selection.end ?? 0) + (newComment.length - commentRef.current.length), cursorPosition ?? 0);

if (commentWithSpaceInserted !== newComment && isIOSNative) {
syncSelectionWithOnChangeTextRef.current = {position, value: newComment};
}

setSelection((prevSelection) => ({
start: position,
end: position,
Expand Down Expand Up @@ -512,18 +524,25 @@ function ComposerWithSuggestions({
[shouldUseNarrowLayout, isKeyboardShown, suggestionsRef, selection.start, includeChronos, handleSendMessage, lastReportAction, reportID, updateComment, selection.end],
);

useEffect(() => {
if (suggestionPosition === null) {
return;
}
const onChangeText = useCallback(
(commentValue: string) => {
updateComment(commentValue, true);

textInputRef.current?.setSelection?.(suggestionPosition, suggestionPosition);
}, [suggestionPosition]);
if (isIOSNative && syncSelectionWithOnChangeTextRef.current) {
const positionSnapshot = syncSelectionWithOnChangeTextRef.current.position;
syncSelectionWithOnChangeTextRef.current = null;

const onSuggestionSelected = useCallback((suggestion: TextSelection) => {
setSelection(suggestion);
setSuggestionPosition(suggestion.end ?? 0);
}, []);
// ensure that selection is set imperatively after all state changes are effective
// eslint-disable-next-line @typescript-eslint/no-deprecated
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: CustomSelectionChangeEvent) => {
Expand Down Expand Up @@ -730,7 +749,7 @@ function ComposerWithSuggestions({
mobileInputScrollPosition.current = 0;
// Note: use the value when the clear happened, not the current value which might have changed already
onCleared(text);
updateComment('');
updateComment('', true);
},
[onCleared, updateComment],
);
Expand Down Expand Up @@ -808,7 +827,7 @@ function ComposerWithSuggestions({
ref={setTextInputRef}
placeholder={inputPlaceholder}
placeholderTextColor={theme.placeholderText}
onChangeText={updateComment}
onChangeText={onChangeText}
onKeyPress={handleKeyPress}
textAlignVertical="top"
style={[styles.textInputCompose, isComposerFullSize ? styles.textInputFullCompose : styles.textInputCollapseCompose]}
Expand Down Expand Up @@ -846,7 +865,7 @@ function ComposerWithSuggestions({
// Input
value={value}
selection={selection}
setSelection={onSuggestionSelected}
setSelection={setSelection}
resetKeyboardInput={resetKeyboardInput}
/>

Expand Down
Loading