diff --git a/src/hooks/useHtmlPaste/index.ts b/src/hooks/useHtmlPaste/index.ts index bdd13c2ceff8..925a3db518ae 100644 --- a/src/hooks/useHtmlPaste/index.ts +++ b/src/hooks/useHtmlPaste/index.ts @@ -3,6 +3,33 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import {useCallback, useEffect} from 'react'; import type UseHtmlPaste from './types'; +const insertByCommand = (text: string) => { + document.execCommand('insertText', false, text); +}; + +const insertAtCaret = (target: HTMLElement, text: string) => { + const selection = window.getSelection(); + if (selection?.rangeCount) { + const range = selection.getRangeAt(0); + range.deleteContents(); + const node = document.createTextNode(text); + range.insertNode(node); + + // 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); + + // Dispatch paste event to simulate real browser behavior + target.dispatchEvent(new Event('paste', {bubbles: true})); + // Dispatch input event to trigger Markdown Input to parse the new text + target.dispatchEvent(new Event('input', {bubbles: true})); + } else { + insertByCommand(text); + } +}; + const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeListenerOnScreenBlur = false) => { const navigation = useNavigation(); @@ -12,7 +39,12 @@ const useHtmlPaste: UseHtmlPaste = (textInputRef, preHtmlPasteCallback, removeLi */ const paste = useCallback((text: string) => { try { - document.execCommand('insertText', false, text); + const textInputHTMLElement = textInputRef.current as HTMLElement; + if (textInputHTMLElement?.hasAttribute('contenteditable')) { + insertAtCaret(textInputHTMLElement, text); + } else { + insertByCommand(text); + } // 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();