Skip to content

Commit 8d4290c

Browse files
committed
Possible fix for multi-chinese-character input
1 parent d7a012c commit 8d4290c

File tree

1 file changed

+38
-8
lines changed

1 file changed

+38
-8
lines changed

cli/src/components/multiline-input.tsx

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,17 @@ export const MultilineInput = forwardRef<
198198

199199
const stickyColumnRef = useRef<number | null>(null)
200200

201+
// Refs to track latest value and cursor position synchronously for IME input handling.
202+
// When IME sends multiple character events rapidly (e.g., Chinese input), React batches
203+
// state updates, causing subsequent events to see stale closure values. These refs are
204+
// updated synchronously to ensure each keystroke builds on the previous one.
205+
const valueRef = useRef(value)
206+
const cursorPositionRef = useRef(cursorPosition)
207+
208+
// Keep refs current on every render (synchronous assignment avoids useEffect timing issues)
209+
valueRef.current = value
210+
cursorPositionRef.current = cursorPosition
211+
201212
// Helper to get or set the sticky column for vertical navigation.
202213
// When stickyColumnRef.current is set, we return it (preserving column across
203214
// multiple up/down presses). When null, we calculate from current cursor position.
@@ -337,31 +348,50 @@ export const MultilineInput = forwardRef<
337348
const selection = getSelectionRange()
338349
if (selection) {
339350
// Replace selected text with the new text
351+
clearSelection()
352+
// Read from refs which have the latest values (updated synchronously below)
353+
const currentValue = valueRef.current
340354
const newValue =
341-
value.slice(0, selection.start) +
355+
currentValue.slice(0, selection.start) +
342356
textToInsert +
343-
value.slice(selection.end)
344-
clearSelection()
357+
currentValue.slice(selection.end)
358+
const newCursor = selection.start + textToInsert.length
359+
360+
// Update refs synchronously BEFORE calling onChange - critical for IME input
361+
// where multiple characters may arrive before React processes state updates
362+
valueRef.current = newValue
363+
cursorPositionRef.current = newCursor
364+
345365
onChange({
346366
text: newValue,
347-
cursorPosition: selection.start + textToInsert.length,
367+
cursorPosition: newCursor,
348368
lastEditDueToNav: false,
349369
})
350370
return
351371
}
352372

353373
// No selection, insert at cursor
374+
// Read from refs to get latest state (handles rapid IME input)
375+
const currentValue = valueRef.current
376+
const currentCursor = cursorPositionRef.current
354377
const newValue =
355-
value.slice(0, cursorPosition) +
378+
currentValue.slice(0, currentCursor) +
356379
textToInsert +
357-
value.slice(cursorPosition)
380+
currentValue.slice(currentCursor)
381+
const newCursor = currentCursor + textToInsert.length
382+
383+
// Update refs synchronously BEFORE calling onChange - critical for IME input
384+
// where multiple characters may arrive before React processes state updates
385+
valueRef.current = newValue
386+
cursorPositionRef.current = newCursor
387+
358388
onChange({
359389
text: newValue,
360-
cursorPosition: cursorPosition + textToInsert.length,
390+
cursorPosition: newCursor,
361391
lastEditDueToNav: false,
362392
})
363393
},
364-
[cursorPosition, onChange, value, getSelectionRange, clearSelection],
394+
[onChange, getSelectionRange, clearSelection],
365395
)
366396

367397
const moveCursor = useCallback(

0 commit comments

Comments
 (0)