diff --git a/.changeset/proud-donuts-listen.md b/.changeset/proud-donuts-listen.md new file mode 100644 index 00000000..02205e97 --- /dev/null +++ b/.changeset/proud-donuts-listen.md @@ -0,0 +1,6 @@ +--- +"@clack/core": patch +"@clack/prompts": patch +--- + +Prevent `selectKey` from selecting disabled options. diff --git a/packages/core/src/prompts/select-key.ts b/packages/core/src/prompts/select-key.ts index 04a49a52..2bf2d319 100644 --- a/packages/core/src/prompts/select-key.ts +++ b/packages/core/src/prompts/select-key.ts @@ -1,11 +1,13 @@ import Prompt, { type PromptOptions } from './prompt.js'; -export interface SelectKeyOptions +export interface SelectKeyOptions extends PromptOptions> { options: T[]; caseSensitive?: boolean; } -export default class SelectKeyPrompt extends Prompt { +export default class SelectKeyPrompt< + T extends { value: string; disabled?: boolean }, +> extends Prompt { options: T[]; cursor = 0; @@ -28,7 +30,10 @@ export default class SelectKeyPrompt extends Prompt return; } - const value = this.options.find(({ value: [initial] }) => { + const value = this.options.find(({ value: [initial], disabled }) => { + if (disabled) { + return false; + } return caseSensitive ? initial === casedKey : initial?.toLowerCase() === casedKey; }); if (value) { diff --git a/packages/prompts/test/select-key.test.ts b/packages/prompts/test/select-key.test.ts index 856afde8..6838bb5f 100644 --- a/packages/prompts/test/select-key.test.ts +++ b/packages/prompts/test/select-key.test.ts @@ -65,6 +65,39 @@ describe.each(['true', 'false'])('text (isCI = %s)', (isCI) => { expect(value).toBe('b'); }); + test('does not select disabled options', async () => { + const result = prompts.selectKey({ + message: 'foo', + options: [ + { label: 'Disabled', value: 'd', disabled: true }, + { label: 'Enabled', value: 'e' }, + ], + input, + output, + }); + + input.emit('keypress', 'd', { name: 'd' }); + input.emit('keypress', 'e', { name: 'e' }); + + expect(await result).toBe('e'); + }); + + test('skips disabled options sharing the same initial', async () => { + const result = prompts.selectKey({ + message: 'foo', + options: [ + { label: 'delete', value: 'delete', disabled: true }, + { label: 'deploy', value: 'deploy' }, + ], + input, + output, + }); + + input.emit('keypress', 'd', { name: 'd' }); + + expect(await result).toBe('deploy'); + }); + test('can cancel by pressing escape', async () => { const result = prompts.selectKey({ message: 'foo',