mattermost-mobile/app/components/post_draft/custom_emoji_picker/index.tsx
Rajat Dabade 462d6d5052
Fix Emoji insert at end of post instead of caret position (#9473)
* Fix Emoji insert at end of post instead of caret position

* copilot code suggestions

* Added test
2026-02-02 19:32:11 +05:30

99 lines
3.7 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {type SharedValue} from 'react-native-reanimated';
import {useKeyboardAnimationContext} from '@context/keyboard_animation';
import {EmojiIndicesByAlias, Emojis} from '@utils/emoji';
import EmojiPicker from './emoji_picker';
type Props = {
height: SharedValue<number>;
onEmojiPress?: (emoji: string) => void;
setIsEmojiSearchFocused: React.Dispatch<React.SetStateAction<boolean>>;
isEmojiSearchFocused: boolean;
}
const CustomEmojiPicker: React.FC<Props> = ({
height,
isEmojiSearchFocused,
onEmojiPress,
setIsEmojiSearchFocused,
}) => {
const {cursorPositionRef, updateValue, updateCursorPosition, clearCursorPositionPreservation} = useKeyboardAnimationContext();
const handleEmojiPress = useCallback((emojiName: string) => {
// If onEmojiPress prop is provided, use it (for other use cases)
if (onEmojiPress) {
onEmojiPress(emojiName);
return;
}
// Otherwise, use context-based insertion (for input accessory view)
if (!updateValue || !updateCursorPosition || !cursorPositionRef) {
return;
}
const name = emojiName.trim();
// Calculate the inserted text first to determine its length
let insertedText = '';
if (EmojiIndicesByAlias.get(name)) {
const emoji = Emojis[EmojiIndicesByAlias.get(name)!];
if (emoji.category === 'custom') {
insertedText = ` :${emojiName}: `;
} else {
const unicode = emoji.image;
if (unicode) {
const codeArray = unicode.split('-');
const convertToUnicode = (acc: string, c: string) => {
return acc + String.fromCodePoint(parseInt(c, 16));
};
insertedText = codeArray.reduce(convertToUnicode, '');
} else {
insertedText = ` :${emojiName}: `;
}
}
} else {
insertedText = ` :${emojiName}: `;
}
// Read cursor position and calculate new position immediately
// This ensures rapid clicks use the correct position
const currentCursorPosition = cursorPositionRef.current;
const insertedTextLength = insertedText.length;
const newCursorPosition = currentCursorPosition + insertedTextLength;
// Update cursorPositionRef IMMEDIATELY (before React processes the update)
// This ensures the next rapid click uses the updated position
cursorPositionRef.current = newCursorPosition;
// Update cursor position state (for Android and to keep state in sync)
updateCursorPosition(newCursorPosition);
// Clear preservation flags after emoji is inserted
// This allows normal cursor updates to proceed
clearCursorPositionPreservation?.();
const insertEmoji = (v: string): string => {
// Use the captured cursor position from when the function was created
return v.slice(0, currentCursorPosition) + insertedText + v.slice(currentCursorPosition);
};
updateValue(insertEmoji);
}, [onEmojiPress, updateValue, updateCursorPosition, cursorPositionRef, clearCursorPositionPreservation]);
return (
<EmojiPicker
onEmojiPress={handleEmojiPress}
testID='custom_emoji_picker'
setIsEmojiSearchFocused={setIsEmojiSearchFocused}
isEmojiSearchFocused={isEmojiSearchFocused}
emojiPickerHeight={height}
/>
);
};
export default CustomEmojiPicker;