@@ -32,21 +32,25 @@ vi.mock('@inkjs/ui', () => ({
3232 } ) => {
3333 const [ value , setValue ] = useState ( defaultValue ?? '' ) ;
3434 const valueRef = useRef ( defaultValue ?? '' ) ;
35+ const onChangeRef = useRef ( onChange ) ;
36+ const onSubmitRef = useRef ( onSubmit ) ;
37+ onChangeRef . current = onChange ;
38+ onSubmitRef . current = onSubmit ;
3539
3640 useInput ( ( input , key ) => {
3741 if ( isDisabled ) {
3842 return ;
3943 }
4044
4145 if ( key . return ) {
42- onSubmit ?.( value ) ;
46+ onSubmitRef . current ?.( valueRef . current ) ;
4347 return ;
4448 }
4549
4650 if ( key . backspace || key . delete ) {
4751 const nextValue = valueRef . current . slice ( 0 , - 1 ) ;
4852 valueRef . current = nextValue ;
49- onChange ?.( nextValue ) ;
53+ onChangeRef . current ?.( nextValue ) ;
5054 setValue ( nextValue ) ;
5155 return ;
5256 }
@@ -69,7 +73,7 @@ vi.mock('@inkjs/ui', () => ({
6973
7074 const nextValue = valueRef . current + input ;
7175 valueRef . current = nextValue ;
72- onChange ?.( nextValue ) ;
76+ onChangeRef . current ?.( nextValue ) ;
7377 setValue ( nextValue ) ;
7478 } ) ;
7579
@@ -134,10 +138,12 @@ vi.mock('./FileSuggestions', () => ({
134138 FileSuggestions : ( {
135139 input,
136140 isDisabled,
141+ onChange,
137142 onSelect,
138143 } : {
139144 input : string ;
140145 isDisabled ?: boolean ;
146+ onChange ?: ( value : string | null ) => void ;
141147 onSelect : ( value : string ) => void ;
142148 } ) => {
143149 const match = / ( ^ | \s ) @ ( \S + ) $ / . exec ( input ) ;
@@ -151,6 +157,11 @@ vi.mock('./FileSuggestions', () => ({
151157 ] . filter ( ( value ) => value . toLowerCase ( ) . includes ( match [ 2 ] . toLowerCase ( ) ) ) ;
152158
153159 const [ focusedIndex , setFocusedIndex ] = useState ( 0 ) ;
160+ const prefix = input . slice ( 0 , match . index + match [ 1 ] . length ) ;
161+ const activeSuggestion = options [ focusedIndex ]
162+ ? `${ prefix } ${ options [ focusedIndex ] } `
163+ : null ;
164+ onChange ?.( activeSuggestion ) ;
154165
155166 useInput ( ( _ , key ) => {
156167 if ( isDisabled || ! options . length ) {
@@ -168,7 +179,6 @@ vi.mock('./FileSuggestions', () => ({
168179 }
169180
170181 if ( key . tab ) {
171- const prefix = input . slice ( 0 , match . index + match [ 1 ] . length ) ;
172182 onSelect ( `${ prefix } ${ options [ focusedIndex ] } ` ) ;
173183 }
174184 } ) ;
@@ -271,14 +281,15 @@ describe('Input', () => {
271281 expect ( onSubmit ) . toHaveBeenCalledWith ( 'hi' ) ;
272282 } ) ;
273283
274- it ( 'submits typed text on Enter while file suggestions are visible' , async ( ) => {
275- const onSubmit = vi . fn ( ) ;
276- const { stdin } = render ( < Input onSubmit = { onSubmit } /> ) ;
277- stdin . write ( 'read @s' ) ;
284+ it ( 'inserts the focused file suggestion on Enter with a trailing space' , async ( ) => {
285+ const { lastFrame, stdin } = render ( < Input onSubmit = { vi . fn ( ) } /> ) ;
286+ stdin . write ( '@' ) ;
278287 await time . tick ( ) ;
288+ stdin . write ( 's' ) ;
289+ await time . tick ( 10 ) ;
279290 stdin . write ( KEY . ENTER ) ;
280- await time . tick ( ) ;
281- expect ( onSubmit ) . toHaveBeenCalledWith ( 'read @s ') ;
291+ await time . tick ( 10 ) ;
292+ expect ( lastFrame ( ) ) . toContain ( '[value:src/components/Chat/Input.tsx ] ') ;
282293 } ) ;
283294
284295 it ( 'submits first matching slash command on Enter when list is visible' , async ( ) => {
@@ -357,6 +368,33 @@ describe('Input', () => {
357368 expect ( lastFrame ( ) ) . toContain ( 'src/utils/tools.ts x' ) ;
358369 } ) ;
359370
371+ it ( 'inserts the focused file suggestion on Enter after arrow navigation' , async ( ) => {
372+ const { lastFrame, stdin } = render ( < Input onSubmit = { vi . fn ( ) } /> ) ;
373+ stdin . write ( '@' ) ;
374+ await time . tick ( ) ;
375+ stdin . write ( 's' ) ;
376+ await time . tick ( 10 ) ;
377+ stdin . write ( KEY . DOWN ) ;
378+ await time . tick ( ) ;
379+ stdin . write ( KEY . ENTER ) ;
380+ await time . tick ( 10 ) ;
381+ expect ( lastFrame ( ) ) . toContain ( '[value:src/utils/tools.ts ]' ) ;
382+ } ) ;
383+
384+ it ( 'does not submit or change input on Enter when no file suggestion matches' , async ( ) => {
385+ const onSubmit = vi . fn ( ) ;
386+ const { lastFrame, stdin } = render ( < Input onSubmit = { onSubmit } /> ) ;
387+ stdin . write ( '@' ) ;
388+ await time . tick ( ) ;
389+ stdin . write ( 'z' ) ;
390+ await time . tick ( 10 ) ;
391+ stdin . write ( KEY . ENTER ) ;
392+ await time . tick ( 10 ) ;
393+
394+ expect ( onSubmit ) . not . toHaveBeenCalled ( ) ;
395+ expect ( lastFrame ( ) ) . toContain ( '[value:@z]' ) ;
396+ } ) ;
397+
360398 it ( 'does not submit blank input' , async ( ) => {
361399 const onSubmit = vi . fn ( ) ;
362400 const { stdin } = render ( < Input onSubmit = { onSubmit } /> ) ;
0 commit comments