({
onPaste,
onInput,
onImagePaste,
}: PasteHandlerProps)
| 28 | } |
| 29 | |
| 30 | export function usePasteHandler({ |
| 31 | onPaste, |
| 32 | onInput, |
| 33 | onImagePaste, |
| 34 | }: PasteHandlerProps): { |
| 35 | wrappedOnInput: (input: string, key: Key, event: InputEvent) => void |
| 36 | pasteState: { |
| 37 | chunks: string[] |
| 38 | timeoutId: ReturnType<typeof setTimeout> | null |
| 39 | } |
| 40 | isPasting: boolean |
| 41 | } { |
| 42 | const [pasteState, setPasteState] = React.useState<{ |
| 43 | chunks: string[] |
| 44 | timeoutId: ReturnType<typeof setTimeout> | null |
| 45 | }>({ chunks: [], timeoutId: null }) |
| 46 | const [isPasting, setIsPasting] = React.useState(false) |
| 47 | const isMountedRef = React.useRef(true) |
| 48 | // Mirrors pasteState.timeoutId but updated synchronously. When paste + a |
| 49 | // keystroke arrive in the same stdin chunk, both wrappedOnInput calls run |
| 50 | // in the same discreteUpdates batch before React commits — the second call |
| 51 | // reads stale pasteState.timeoutId (null) and takes the onInput path. If |
| 52 | // that key is Enter, it submits the old input and the paste is lost. |
| 53 | const pastePendingRef = React.useRef(false) |
| 54 | |
| 55 | const isMacOS = React.useMemo(() => getPlatform() === 'macos', []) |
| 56 | |
| 57 | React.useEffect(() => { |
| 58 | return () => { |
| 59 | isMountedRef.current = false |
| 60 | } |
| 61 | }, []) |
| 62 | |
| 63 | const checkClipboardForImageImpl = React.useCallback(() => { |
| 64 | if (!onImagePaste || !isMountedRef.current) return |
| 65 | |
| 66 | void getImageFromClipboard() |
| 67 | .then(imageData => { |
| 68 | if (imageData && isMountedRef.current) { |
| 69 | onImagePaste( |
| 70 | imageData.base64, |
| 71 | imageData.mediaType, |
| 72 | undefined, // no filename for clipboard images |
| 73 | imageData.dimensions, |
| 74 | ) |
| 75 | } |
| 76 | }) |
| 77 | .catch(error => { |
| 78 | if (isMountedRef.current) { |
| 79 | logError(error as Error) |
| 80 | } |
| 81 | }) |
| 82 | .finally(() => { |
| 83 | if (isMountedRef.current) { |
| 84 | setIsPasting(false) |
| 85 | } |
| 86 | }) |
| 87 | }, [onImagePaste]) |
no test coverage detected