@@ -117,6 +117,7 @@ function ComposerWithSuggestions({
117
117
return draft ;
118
118
} ) ;
119
119
const commentRef = useRef ( value ) ;
120
+ const lastTextRef = useRef ( value ) ;
120
121
121
122
const { isSmallScreenWidth} = useWindowDimensions ( ) ;
122
123
const maxComposerLines = isSmallScreenWidth ? CONST . COMPOSER . MAX_LINES_SMALL_SCREEN : CONST . COMPOSER . MAX_LINES ;
@@ -196,6 +197,50 @@ function ComposerWithSuggestions({
196
197
RNTextInputReset . resetKeyboardInput ( findNodeHandle ( textInputRef . current ) ) ;
197
198
} , [ textInputRef ] ) ;
198
199
200
+ /**
201
+ * Find the newly added characters between the previous text and the new text based on the selection.
202
+ *
203
+ * @param {string } prevText - The previous text.
204
+ * @param {string } newText - The new text.
205
+ * @returns {object } An object containing information about the newly added characters.
206
+ * @property {number } startIndex - The start index of the newly added characters in the new text.
207
+ * @property {number } endIndex - The end index of the newly added characters in the new text.
208
+ * @property {string } diff - The newly added characters.
209
+ */
210
+ const findNewlyAddedChars = useCallback (
211
+ ( prevText , newText ) => {
212
+ let startIndex = - 1 ;
213
+ let endIndex = - 1 ;
214
+ let currentIndex = 0 ;
215
+
216
+ // Find the first character mismatch with newText
217
+ while ( currentIndex < newText . length && prevText . charAt ( currentIndex ) === newText . charAt ( currentIndex ) && selection . start > currentIndex ) {
218
+ currentIndex ++ ;
219
+ }
220
+
221
+ if ( currentIndex < newText . length ) {
222
+ startIndex = currentIndex ;
223
+
224
+ // if text is getting pasted over find length of common suffix and subtract it from new text length
225
+ if ( selection . end - selection . start > 0 ) {
226
+ const commonSuffixLength = ComposerUtils . getCommonSuffixLength ( prevText , newText ) ;
227
+ endIndex = newText . length - commonSuffixLength ;
228
+ } else {
229
+ endIndex = currentIndex + ( newText . length - prevText . length ) ;
230
+ }
231
+ }
232
+
233
+ return {
234
+ startIndex,
235
+ endIndex,
236
+ diff : newText . substring ( startIndex , endIndex ) ,
237
+ } ;
238
+ } ,
239
+ [ selection . end , selection . start ] ,
240
+ ) ;
241
+
242
+ const insertWhiteSpace = ( text , index ) => `${ text . slice ( 0 , index ) } ${ text . slice ( index ) } ` ;
243
+
199
244
const debouncedSaveReportComment = useMemo (
200
245
( ) =>
201
246
_ . debounce ( ( selectedReportID , newComment ) => {
@@ -213,7 +258,14 @@ function ComposerWithSuggestions({
213
258
const updateComment = useCallback (
214
259
( commentValue , shouldDebounceSaveComment ) => {
215
260
raiseIsScrollLikelyLayoutTriggered ( ) ;
216
- const { text : newComment , emojis} = EmojiUtils . replaceAndExtractEmojis ( commentValue , preferredSkinTone , preferredLocale ) ;
261
+ const { startIndex, endIndex, diff} = findNewlyAddedChars ( lastTextRef . current , commentValue ) ;
262
+ const isEmojiInserted = diff . length && endIndex > startIndex && EmojiUtils . containsOnlyEmojis ( diff ) ;
263
+ const { text : newComment , emojis} = EmojiUtils . replaceAndExtractEmojis (
264
+ isEmojiInserted ? insertWhiteSpace ( commentValue , endIndex ) : commentValue ,
265
+ preferredSkinTone ,
266
+ preferredLocale ,
267
+ ) ;
268
+
217
269
if ( ! _ . isEmpty ( emojis ) ) {
218
270
const newEmojis = EmojiUtils . getAddedEmojis ( emojis , emojisPresentBefore . current ) ;
219
271
if ( ! _ . isEmpty ( newEmojis ) ) {
@@ -264,13 +316,14 @@ function ComposerWithSuggestions({
264
316
}
265
317
} ,
266
318
[
267
- debouncedUpdateFrequentlyUsedEmojis ,
268
- preferredLocale ,
319
+ raiseIsScrollLikelyLayoutTriggered ,
320
+ findNewlyAddedChars ,
269
321
preferredSkinTone ,
270
- reportID ,
322
+ preferredLocale ,
271
323
setIsCommentEmpty ,
324
+ debouncedUpdateFrequentlyUsedEmojis ,
272
325
suggestionsRef ,
273
- raiseIsScrollLikelyLayoutTriggered ,
326
+ reportID ,
274
327
debouncedSaveReportComment ,
275
328
] ,
276
329
) ;
@@ -321,14 +374,8 @@ function ComposerWithSuggestions({
321
374
* @param {Boolean } shouldAddTrailSpace
322
375
*/
323
376
const replaceSelectionWithText = useCallback (
324
- ( text , shouldAddTrailSpace = true ) => {
325
- const updatedText = shouldAddTrailSpace ? `${ text } ` : text ;
326
- const selectionSpaceLength = shouldAddTrailSpace ? CONST . SPACE_LENGTH : 0 ;
327
- updateComment ( ComposerUtils . insertText ( commentRef . current , selection , updatedText ) ) ;
328
- setSelection ( ( prevSelection ) => ( {
329
- start : prevSelection . start + text . length + selectionSpaceLength ,
330
- end : prevSelection . start + text . length + selectionSpaceLength ,
331
- } ) ) ;
377
+ ( text ) => {
378
+ updateComment ( ComposerUtils . insertText ( commentRef . current , selection , text ) ) ;
332
379
} ,
333
380
[ selection , updateComment ] ,
334
381
) ;
@@ -452,7 +499,12 @@ function ComposerWithSuggestions({
452
499
}
453
500
454
501
focus ( ) ;
455
- replaceSelectionWithText ( e . key , false ) ;
502
+ // Reset cursor to last known location
503
+ setSelection ( ( prevSelection ) => ( {
504
+ start : prevSelection . start + 1 ,
505
+ end : prevSelection . end + 1 ,
506
+ } ) ) ;
507
+ replaceSelectionWithText ( e . key ) ;
456
508
} ,
457
509
[ checkComposerVisibility , focus , replaceSelectionWithText ] ,
458
510
) ;
@@ -514,6 +566,10 @@ function ComposerWithSuggestions({
514
566
// eslint-disable-next-line react-hooks/exhaustive-deps
515
567
} , [ ] ) ;
516
568
569
+ useEffect ( ( ) => {
570
+ lastTextRef . current = value ;
571
+ } , [ value ] ) ;
572
+
517
573
useImperativeHandle (
518
574
forwardedRef ,
519
575
( ) => ( {
0 commit comments