From ffac7a5542d6595d29c92997b6d928462d8252de Mon Sep 17 00:00:00 2001 From: Andrew Dibble Date: Tue, 19 Sep 2023 13:38:20 +0200 Subject: [PATCH 1/4] fix: only focus active elements --- src/components/App.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index f6518160..894e4e7a 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -250,7 +250,9 @@ export default class App extends PureComponent { focusNext = (): void => { this.setState(previousState => { - const firstFocusableId = previousState.focusables[0]?.id; + const firstFocusableId = previousState.focusables.find( + focusable => focusable.isActive + )?.id; const nextFocusableId = this.findNextFocusable(previousState); return { @@ -261,8 +263,9 @@ export default class App extends PureComponent { focusPrevious = (): void => { this.setState(previousState => { - const lastFocusableId = previousState.focusables.at(-1)?.id; - + const lastFocusableId = previousState.focusables.findLast( + (focusable) => focusable.isActive + )?.id; const previousFocusableId = this.findPreviousFocusable(previousState); return { From df4b77e929e843c58be0aeb9e72d13f934c0a9a8 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Mon, 6 Nov 2023 23:01:14 +0700 Subject: [PATCH 2/4] Update App.tsx --- src/components/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 894e4e7a..21c22f46 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -264,7 +264,7 @@ export default class App extends PureComponent { focusPrevious = (): void => { this.setState(previousState => { const lastFocusableId = previousState.focusables.findLast( - (focusable) => focusable.isActive + focusable => focusable.isActive )?.id; const previousFocusableId = this.findPreviousFocusable(previousState); From 5d6a72a8b08d53ed06bbf3df2aa0a788273d91a1 Mon Sep 17 00:00:00 2001 From: Andrew Dibble Date: Sun, 12 Nov 2023 12:48:31 +0100 Subject: [PATCH 3/4] add tests --- readme.md | 2 +- test/focus.tsx | 67 ++++++++++++++++++++++++++++++++++++++++++++++++-- tsconfig.json | 3 ++- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 7ea43cee..1247a19e 100644 --- a/readme.md +++ b/readme.md @@ -1858,7 +1858,7 @@ const Example = () => { Switch focus to the next focusable component. If there's no active component right now, focus will be given to the first focusable component. -If active component is the last in the list of focusable components, focus will be switched to the first component. +If active component is the last in the list of focusable components, focus will be switched to the first focusable component. **Note:** Ink calls this method when user presses Tab. diff --git a/test/focus.tsx b/test/focus.tsx index f884c896..5d01538b 100644 --- a/test/focus.tsx +++ b/test/focus.tsx @@ -28,7 +28,9 @@ const emitReadable = (stdin: NodeJS.WriteStream, chunk: string) => { type TestProps = { readonly showFirst?: boolean; + readonly disableFirst?: boolean; readonly disableSecond?: boolean; + readonly disableThird?: boolean; readonly autoFocus?: boolean; readonly disabled?: boolean; readonly focusNext?: boolean; @@ -38,7 +40,9 @@ type TestProps = { function Test({ showFirst = true, + disableFirst = false, disableSecond = false, + disableThird = false, autoFocus = false, disabled = false, focusNext = false, @@ -73,9 +77,11 @@ function Test({ return ( - {showFirst && } + {showFirst && ( + + )} - + ); } @@ -442,3 +448,60 @@ test('doesnt crash when focusing previous on unmounted children', async t => { t.is((stdout.write as any).lastCall.args[0], ''); }); + +test('focuses first non-disabled component', async t => { + const stdout = createStdout(); + const stdin = createStdin(); + render(, { + stdout, + stdin, + debug: true + }); + + await delay(100); + + t.is( + (stdout.write as any).lastCall.args[0], + ['First', 'Second', 'Third ✔'].join('\n') + ); +}); + +test('skips disabled elements when wrapping around', async t => { + const stdout = createStdout(); + const stdin = createStdin(); + render(, { + stdout, + stdin, + debug: true + }); + + await delay(100); + emitReadable(stdin, '\t'); + await delay(100); + emitReadable(stdin, '\t'); + await delay(100); + + t.is( + (stdout.write as any).lastCall.args[0], + ['First', 'Second ✔', 'Third'].join('\n') + ); +}); + +test('skips disabled elements when wrapping around from the front', async t => { + const stdout = createStdout(); + const stdin = createStdin(); + render(, { + stdout, + stdin, + debug: true + }); + + await delay(100); + emitReadable(stdin, '\u001B[Z'); + await delay(100); + + t.is( + (stdout.write as any).lastCall.args[0], + ['First', 'Second ✔', 'Third'].join('\n') + ); +}); diff --git a/tsconfig.json b/tsconfig.json index b4025814..25f1be81 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,8 @@ "outDir": "build", "sourceMap": true, "jsx": "react", - "isolatedModules": true + "isolatedModules": true, + "lib": ["ES2023"] }, "include": ["src"], "ts-node": { From a709d9ecf7b3ed4e89ae419d0c5c8c5d9304f1a2 Mon Sep 17 00:00:00 2001 From: Andrew Dibble Date: Sun, 12 Nov 2023 22:35:06 +0100 Subject: [PATCH 4/4] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 1247a19e..40e67915 100644 --- a/readme.md +++ b/readme.md @@ -1858,7 +1858,7 @@ const Example = () => { Switch focus to the next focusable component. If there's no active component right now, focus will be given to the first focusable component. -If active component is the last in the list of focusable components, focus will be switched to the first focusable component. +If active component is the last in the list of focusable components, focus will be switched to the first active component. **Note:** Ink calls this method when user presses Tab.