@@ -14,6 +14,7 @@ import { useOpentuiPaste } from '../hooks/use-opentui-paste'
1414import { useTheme } from '../hooks/use-theme'
1515import { computeInputLayoutMetrics } from '../utils/text-layout'
1616
17+ import type { InputValue } from '../state/chat-store'
1718import type { PasteEvent , ScrollBoxRenderable } from '@opentui/core'
1819
1920// Helper functions for text manipulation
@@ -80,7 +81,7 @@ function preventKeyDefault(key: KeyWithPreventDefault) {
8081
8182interface MultilineInputProps {
8283 value : string
83- onChange : ( value : string ) => void
84+ onChange : ( value : InputValue | ( ( prev : InputValue ) => InputValue ) ) => void
8485 onSubmit : ( ) => void
8586 onKeyIntercept ?: (
8687 key : any ,
@@ -153,6 +154,9 @@ export const MultilineInput = forwardRef<
153154 if ( cursorPosition > value . length ) {
154155 setCursorPosition ( value . length )
155156 }
157+ if ( cursorPosition < 0 ) {
158+ setCursorPosition ( 0 )
159+ }
156160 } , [ value . length , cursorPosition ] )
157161
158162 useOpentuiPaste (
@@ -165,8 +169,11 @@ export const MultilineInput = forwardRef<
165169
166170 const newValue =
167171 value . slice ( 0 , cursorPosition ) + text + value . slice ( cursorPosition )
168- onChange ( newValue )
169- setCursorPosition ( cursorPosition + text . length )
172+ onChange ( ( prev ) => ( {
173+ text : newValue ,
174+ cursorPosition : prev . cursorPosition + text . length ,
175+ lastEditDueToNav : false ,
176+ } ) )
170177 } ,
171178 [ focused , value , cursorPosition , onChange ] ,
172179 ) ,
@@ -183,7 +190,6 @@ export const MultilineInput = forwardRef<
183190 scrollBox . verticalScrollBar . scrollPosition = maxScroll
184191 }
185192 } , [ value , cursorPosition , focused ] )
186-
187193 // Measure actual viewport width from the scrollbox to avoid
188194 // wrap miscalculations from heuristic padding/border math.
189195 useEffect ( ( ) => {
@@ -206,7 +212,11 @@ export const MultilineInput = forwardRef<
206212 value,
207213 cursorPosition,
208214 setValue : ( newValue : string ) => {
209- onChange ( newValue )
215+ onChange ( {
216+ text : newValue ,
217+ cursorPosition,
218+ lastEditDueToNav : false ,
219+ } )
210220 return newValue . length
211221 } ,
212222 setCursorPosition : ( position : number ) =>
@@ -270,16 +280,22 @@ export const MultilineInput = forwardRef<
270280 value . slice ( 0 , cursorPosition - 1 ) +
271281 '\n' +
272282 value . slice ( cursorPosition )
273- onChange ( newValue )
274- setCursorPosition ( cursorPosition )
283+ onChange ( {
284+ text : newValue ,
285+ cursorPosition,
286+ lastEditDueToNav : false ,
287+ } )
275288 return
276289 }
277290
278291 // For other newline shortcuts, just insert newline
279292 const newValue =
280293 value . slice ( 0 , cursorPosition ) + '\n' + value . slice ( cursorPosition )
281- onChange ( newValue )
282- setCursorPosition ( cursorPosition + 1 )
294+ onChange ( ( prev ) => ( {
295+ text : newValue ,
296+ cursorPosition : prev . cursorPosition + 1 ,
297+ lastEditDueToNav : false ,
298+ } ) )
283299 return
284300 }
285301
@@ -326,8 +342,11 @@ export const MultilineInput = forwardRef<
326342 return
327343 }
328344
329- onChange ( newValue )
330- setCursorPosition ( Math . max ( 0 , nextCursor ) )
345+ onChange ( {
346+ text : newValue ,
347+ cursorPosition : nextCursor ,
348+ lastEditDueToNav : false ,
349+ } )
331350 return
332351 }
333352
@@ -339,8 +358,11 @@ export const MultilineInput = forwardRef<
339358 preventKeyDefault ( key )
340359 const newValue =
341360 value . slice ( 0 , wordStart ) + value . slice ( cursorPosition )
342- onChange ( newValue )
343- setCursorPosition ( wordStart )
361+ onChange ( {
362+ text : newValue ,
363+ cursorPosition : wordStart ,
364+ lastEditDueToNav : false ,
365+ } )
344366 return
345367 } // Cmd+Delete: Delete to line start; fallback to single delete if nothing changes
346368 if ( key . name === 'delete' && key . meta && ! isAltLikeModifier ) {
@@ -374,22 +396,29 @@ export const MultilineInput = forwardRef<
374396 return
375397 }
376398
377- onChange ( newValue )
378- setCursorPosition ( Math . max ( 0 , nextCursor ) )
399+ onChange ( {
400+ text : newValue ,
401+ cursorPosition : nextCursor ,
402+ lastEditDueToNav : false ,
403+ } )
379404 return
380405 } // Alt+Delete: Delete word forward
381406 if ( key . name === 'delete' && isAltLikeModifier ) {
382407 preventKeyDefault ( key )
383408 const newValue = value . slice ( 0 , cursorPosition ) + value . slice ( wordEnd )
384- onChange ( newValue )
409+ onChange ( {
410+ text : newValue ,
411+ cursorPosition,
412+ lastEditDueToNav : false ,
413+ } )
385414 return
386415 }
387416
388417 // Ctrl+K: Delete to line end
389418 if ( key . ctrl && lowerKeyName === 'k' && ! key . meta && ! key . option ) {
390419 preventKeyDefault ( key )
391420 const newValue = value . slice ( 0 , cursorPosition ) + value . slice ( lineEnd )
392- onChange ( newValue )
421+ onChange ( { text : newValue , cursorPosition , lastEditDueToNav : true } )
393422 return
394423 }
395424
@@ -399,8 +428,11 @@ export const MultilineInput = forwardRef<
399428 if ( cursorPosition > 0 ) {
400429 const newValue =
401430 value . slice ( 0 , cursorPosition - 1 ) + value . slice ( cursorPosition )
402- onChange ( newValue )
403- setCursorPosition ( cursorPosition - 1 )
431+ onChange ( {
432+ text : newValue ,
433+ cursorPosition : cursorPosition - 1 ,
434+ lastEditDueToNav : false ,
435+ } )
404436 }
405437 return
406438 }
@@ -411,7 +443,11 @@ export const MultilineInput = forwardRef<
411443 if ( cursorPosition < value . length ) {
412444 const newValue =
413445 value . slice ( 0 , cursorPosition ) + value . slice ( cursorPosition + 1 )
414- onChange ( newValue )
446+ onChange ( {
447+ text : newValue ,
448+ cursorPosition,
449+ lastEditDueToNav : false ,
450+ } )
415451 }
416452 return
417453 }
@@ -422,8 +458,11 @@ export const MultilineInput = forwardRef<
422458 if ( cursorPosition > 0 ) {
423459 const newValue =
424460 value . slice ( 0 , cursorPosition - 1 ) + value . slice ( cursorPosition )
425- onChange ( newValue )
426- setCursorPosition ( cursorPosition - 1 )
461+ onChange ( {
462+ text : newValue ,
463+ cursorPosition : cursorPosition - 1 ,
464+ lastEditDueToNav : false ,
465+ } )
427466 }
428467 return
429468 }
@@ -434,7 +473,11 @@ export const MultilineInput = forwardRef<
434473 if ( cursorPosition < value . length ) {
435474 const newValue =
436475 value . slice ( 0 , cursorPosition ) + value . slice ( cursorPosition + 1 )
437- onChange ( newValue )
476+ onChange ( {
477+ text : newValue ,
478+ cursorPosition,
479+ lastEditDueToNav : false ,
480+ } )
438481 }
439482 return
440483 }
@@ -544,8 +587,11 @@ export const MultilineInput = forwardRef<
544587 value . slice ( 0 , cursorPosition ) +
545588 key . sequence +
546589 value . slice ( cursorPosition )
547- onChange ( newValue )
548- setCursorPosition ( cursorPosition + 1 )
590+ onChange ( {
591+ text : newValue ,
592+ cursorPosition : cursorPosition + 1 ,
593+ lastEditDueToNav : false ,
594+ } )
549595 return
550596 }
551597 } ,
0 commit comments