From 4b2280ac33bd97a544398addc831d2028fc222cb Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Thu, 10 Jul 2025 20:56:08 +0200 Subject: [PATCH 1/4] feat: allow selecting all and selecting inverse --- packages/core/src/prompts/autocomplete.ts | 21 ++ packages/prompts/src/autocomplete.ts | 2 + .../__snapshots__/autocomplete.test.ts.snap | 238 +++++++++++++++++- packages/prompts/test/autocomplete.test.ts | 82 ++++++ 4 files changed, 339 insertions(+), 4 deletions(-) diff --git a/packages/core/src/prompts/autocomplete.ts b/packages/core/src/prompts/autocomplete.ts index 6e308ff5..24ef7127 100644 --- a/packages/core/src/prompts/autocomplete.ts +++ b/packages/core/src/prompts/autocomplete.ts @@ -140,6 +140,8 @@ export default class AutocompletePrompt extends Prompt< const isUpKey = key.name === 'up'; const isDownKey = key.name === 'down'; const isReturnKey = key.name === 'return'; + const isLeft = key.name === 'left'; + const isRight = key.name === 'right'; // Start navigation mode with up/down arrows if (isUpKey || isDownKey) { @@ -161,6 +163,15 @@ export default class AutocompletePrompt extends Prompt< (key.name === 'tab' || (this.isNavigating && key.name === 'space')) ) { this.toggleSelected(this.focusedValue); + } else if (isLeft) { + // set to none if all are selected + if (this.selectedValues.length === this.filteredOptions.length) { + this.deselectAll(); + } else { + this.selectAll(); + } + } else if (isRight) { + this.invertSelected(); } else { this.isNavigating = false; } @@ -173,10 +184,20 @@ export default class AutocompletePrompt extends Prompt< } } + selectAll() { + this.selectedValues = this.filteredOptions.map((opt) => opt.value); + } + deselectAll() { this.selectedValues = []; } + invertSelected() { + this.selectedValues = this.filteredOptions + .filter((opt) => !this.selectedValues.includes(opt.value)) + .map((opt) => opt.value); + } + toggleSelected(value: T['value']) { if (this.filteredOptions.length === 0) { return; diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index a2041b2d..35a5b650 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -96,6 +96,7 @@ export const autocomplete = (opts: AutocompleteOptions) => { const placeholder = opts.placeholder; const showPlaceholder = valueAsString === '' && placeholder !== undefined; + console.log(this.state); // Handle different states switch (this.state) { case 'submit': { @@ -275,6 +276,7 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // Instructions const instructions = [ `${color.dim('↑/↓')} to navigate`, + `${color.dim('←/→')} select all/inverse`, `${color.dim('Space:')} select`, `${color.dim('Enter:')} confirm`, `${color.dim('Type:')} to search`, diff --git a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap index 1e0daf65..5d001424 100644 --- a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap +++ b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap @@ -286,6 +286,49 @@ exports[`autocomplete > supports initialValue 1`] = ` ] `; +exports[`autocompleteMultiselect > all selection only applies to filtered options 1`] = ` +[ + "", + "│ +◆ Select a fruit + +│ Search: _ +│ ◻ Apple +│ ◻ Banana +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: r█ (3 matches) +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: r (3 matches) +│ ◼ Cherry +│ ◼ Grape +│ ◼ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +│ 3 items selected", + " +", + "", +] +`; + exports[`autocompleteMultiselect > can be aborted by a signal 1`] = ` [ "", @@ -298,8 +341,195 @@ exports[`autocompleteMultiselect > can be aborted by a signal 1`] = ` │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + " +", + "", +] +`; + +exports[`autocompleteMultiselect > everything can be selected with left arrow 1`] = ` +[ + "", + "│ +◆ Select a fruit + +│ Search: _ +│ ◻ Apple +│ ◻ Banana +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ ◼ Apple +│ ◼ Banana +│ ◼ Cherry +│ ◼ Grape +│ ◼ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +│ 5 items selected", + " +", + "", +] +`; + +exports[`autocompleteMultiselect > everything is deselected if left is pressed again 1`] = ` +[ + "", + "│ +◆ Select a fruit + +│ Search: _ +│ ◻ Apple +│ ◻ Banana +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ ◼ Apple +│ ◼ Banana +│ ◼ Cherry +│ ◼ Grape +│ ◼ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ ◻ Apple +│ ◻ Banana +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +│ 0 items selected", + " +", + "", +] +`; + +exports[`autocompleteMultiselect > inverse can be selected with right arrow 1`] = ` +[ + "", + "│ +◆ Select a fruit + +│ Search: _ +│ ◻ Apple +│ ◻ Banana +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search:  +│ ◻ Apple +│ ◻ Banana +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ ◼ Banana", + "", + "", + "", + "", + "│ ◼ Apple +│ ◻ Banana +│ ◼ Cherry +│ ◼ Grape +│ ◼ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search └", + "", + "", + "", + "◇ Select a fruit +│ 4 items selected", + " +", + "", +] +`; + +exports[`autocompleteMultiselect > inversion only applies to filtered options 1`] = ` +[ + "", + "│ +◆ Select a fruit + +│ Search: _ +│ ◻ Apple +│ ◻ Banana +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: r█ (3 matches) +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ Search: r (3 matches) +│ ◻ Cherry +│ ◻ Grape +│ ◻ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "│ ◼ Grape", + "", + "", + "", + "", + "│ ◼ Cherry +│ ◻ Grape +│ ◼ Orange +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +└", + "", + "", + "", + "◇ Select a fruit +│ 2 items selected", " ", "", @@ -318,7 +548,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -332,7 +562,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -345,7 +575,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", diff --git a/packages/prompts/test/autocomplete.test.ts b/packages/prompts/test/autocomplete.test.ts index 2e6f7b6c..3bba0232 100644 --- a/packages/prompts/test/autocomplete.test.ts +++ b/packages/prompts/test/autocomplete.test.ts @@ -221,4 +221,86 @@ describe('autocompleteMultiselect', () => { expect(isCancel(value)).toBe(true); expect(output.buffer).toMatchSnapshot(); }); + + test('everything can be selected with left arrow', async () => { + const result = autocompleteMultiselect({ + message: 'Select a fruit', + options: testOptions, + input, + output, + }); + + input.emit('keypress', '', { name: 'left' }); + input.emit('keypress', '', { name: 'return' }); + await result; + expect(output.buffer).toMatchSnapshot(); + expect(output.buffer.toString()).toMatch('5 items selected'); + }); + + test('everything is deselected if left is pressed again', async () => { + const result = autocompleteMultiselect({ + message: 'Select a fruit', + options: testOptions, + input, + output, + }); + + input.emit('keypress', '', { name: 'left' }); + input.emit('keypress', '', { name: 'left' }); + input.emit('keypress', '', { name: 'return' }); + await result; + expect(output.buffer).toMatchSnapshot(); + expect(output.buffer.toString()).toMatch('0 items selected'); + }); + + test('inverse can be selected with right arrow', async () => { + const result = autocompleteMultiselect({ + message: 'Select a fruit', + options: testOptions, + input, + output, + }); + + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + input.emit('keypress', '', { name: 'right' }); + input.emit('keypress', '', { name: 'return' }); + await result; + expect(output.buffer).toMatchSnapshot(); + expect(output.buffer.toString()).toMatch('4 items selected'); + }); + + test('all selection only applies to filtered options', async () => { + const result = autocompleteMultiselect({ + message: 'Select a fruit', + options: testOptions, + input, + output, + }); + + input.emit('keypress', 'r', { name: 'r' }); + input.emit('keypress', '', { name: 'left' }); + input.emit('keypress', '', { name: 'return' }); + await result; + expect(output.buffer).toMatchSnapshot(); + expect(output.buffer.toString()).toMatch('3 items selected'); + }); + + test('inversion only applies to filtered options', async () => { + const result = autocompleteMultiselect({ + message: 'Select a fruit', + options: testOptions, + input, + output, + }); + + input.emit('keypress', 'r', { name: 'r' }); + input.emit('keypress', '', { name: 'down' }); + input.emit('keypress', '', { name: 'space' }); + input.emit('keypress', '', { name: 'right' }); + input.emit('keypress', '', { name: 'return' }); + await result; + expect(output.buffer).toMatchSnapshot(); + expect(output.buffer.toString()).toMatch('2 items selected'); + }); }); From 2f720dc3398a6e4050ac138b11259e99f9594729 Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Thu, 10 Jul 2025 20:58:31 +0200 Subject: [PATCH 2/4] chore: add changeset --- .changeset/happy-spoons-push.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/happy-spoons-push.md diff --git a/.changeset/happy-spoons-push.md b/.changeset/happy-spoons-push.md new file mode 100644 index 00000000..e524174f --- /dev/null +++ b/.changeset/happy-spoons-push.md @@ -0,0 +1,6 @@ +--- +"@clack/prompts": minor +"@clack/core": minor +--- + +add inversion and selecting all options for autocomplete multiselect From 8ed6d4147e66a6d582bcaa2c0edf329cc4251e88 Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Thu, 10 Jul 2025 21:59:11 +0200 Subject: [PATCH 3/4] fix: use shift u/d instead of l/r --- packages/core/src/prompts/autocomplete.ts | 48 +++++++++++++------ packages/prompts/src/autocomplete.ts | 3 +- .../__snapshots__/autocomplete.test.ts.snap | 43 ++++++++--------- packages/prompts/test/autocomplete.test.ts | 12 ++--- 4 files changed, 61 insertions(+), 45 deletions(-) diff --git a/packages/core/src/prompts/autocomplete.ts b/packages/core/src/prompts/autocomplete.ts index 24ef7127..4b3e912d 100644 --- a/packages/core/src/prompts/autocomplete.ts +++ b/packages/core/src/prompts/autocomplete.ts @@ -140,11 +140,15 @@ export default class AutocompletePrompt extends Prompt< const isUpKey = key.name === 'up'; const isDownKey = key.name === 'down'; const isReturnKey = key.name === 'return'; - const isLeft = key.name === 'left'; - const isRight = key.name === 'right'; // Start navigation mode with up/down arrows if (isUpKey || isDownKey) { + // shift up/down behavior + if (key.shift) { + this.#handleShiftNavigation(isUpKey); + return; + } + this.#cursor = Math.max( 0, Math.min(this.#cursor + (isUpKey ? -1 : 1), this.filteredOptions.length - 1) @@ -163,15 +167,6 @@ export default class AutocompletePrompt extends Prompt< (key.name === 'tab' || (this.isNavigating && key.name === 'space')) ) { this.toggleSelected(this.focusedValue); - } else if (isLeft) { - // set to none if all are selected - if (this.selectedValues.length === this.filteredOptions.length) { - this.deselectAll(); - } else { - this.selectAll(); - } - } else if (isRight) { - this.invertSelected(); } else { this.isNavigating = false; } @@ -184,20 +179,43 @@ export default class AutocompletePrompt extends Prompt< } } - selectAll() { + #handleShiftNavigation(isUpKey: boolean) { + // invert if Shift + Down + if (!isUpKey) { + this.invertSelectedFiltered(); + return; + } + + // set to none if all are selected + if (this.selectedValues.length === this.filteredOptions.length) { + this.deselectAllFiltered(); + return; + } + + this.selectAllFiltered(); + return; + } + + selectAllFiltered() { this.selectedValues = this.filteredOptions.map((opt) => opt.value); } - deselectAll() { - this.selectedValues = []; + deselectAllFiltered() { + this.selectedValues = this.filteredOptions + .filter((opt) => !this.selectedValues.includes(opt.value)) + .map((opt) => opt.value); } - invertSelected() { + invertSelectedFiltered() { this.selectedValues = this.filteredOptions .filter((opt) => !this.selectedValues.includes(opt.value)) .map((opt) => opt.value); } + deselectAll() { + this.selectedValues = []; + } + toggleSelected(value: T['value']) { if (this.filteredOptions.length === 0) { return; diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index 35a5b650..9fe88464 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -96,7 +96,6 @@ export const autocomplete = (opts: AutocompleteOptions) => { const placeholder = opts.placeholder; const showPlaceholder = valueAsString === '' && placeholder !== undefined; - console.log(this.state); // Handle different states switch (this.state) { case 'submit': { @@ -276,7 +275,7 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // Instructions const instructions = [ `${color.dim('↑/↓')} to navigate`, - `${color.dim('←/→')} select all/inverse`, + `${color.dim('Ctrl+↑/↓')} select all/inverse`, `${color.dim('Space:')} select`, `${color.dim('Enter:')} confirm`, `${color.dim('Type:')} to search`, diff --git a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap index 5d001424..9232132f 100644 --- a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap +++ b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap @@ -298,7 +298,7 @@ exports[`autocompleteMultiselect > all selection only applies to filtered option │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -307,16 +307,15 @@ exports[`autocompleteMultiselect > all selection only applies to filtered option │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", - "", + "", "", - "│ Search: r (3 matches) -│ ◼ Cherry + "│ ◼ Cherry │ ◼ Grape │ ◼ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -341,7 +340,7 @@ exports[`autocompleteMultiselect > can be aborted by a signal 1`] = ` │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", " ", @@ -361,7 +360,7 @@ exports[`autocompleteMultiselect > everything can be selected with left arrow 1` │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -371,7 +370,7 @@ exports[`autocompleteMultiselect > everything can be selected with left arrow 1` │ ◼ Cherry │ ◼ Grape │ ◼ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -396,7 +395,7 @@ exports[`autocompleteMultiselect > everything is deselected if left is pressed a │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -406,7 +405,7 @@ exports[`autocompleteMultiselect > everything is deselected if left is pressed a │ ◼ Cherry │ ◼ Grape │ ◼ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -416,7 +415,7 @@ exports[`autocompleteMultiselect > everything is deselected if left is pressed a │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -441,7 +440,7 @@ exports[`autocompleteMultiselect > inverse can be selected with right arrow 1`] │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -452,7 +451,7 @@ exports[`autocompleteMultiselect > inverse can be selected with right arrow 1`] │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -467,7 +466,7 @@ exports[`autocompleteMultiselect > inverse can be selected with right arrow 1`] │ ◼ Cherry │ ◼ Grape │ ◼ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -492,7 +491,7 @@ exports[`autocompleteMultiselect > inversion only applies to filtered options 1` │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -501,7 +500,7 @@ exports[`autocompleteMultiselect > inversion only applies to filtered options 1` │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -510,7 +509,7 @@ exports[`autocompleteMultiselect > inversion only applies to filtered options 1` │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -523,7 +522,7 @@ exports[`autocompleteMultiselect > inversion only applies to filtered options 1` "│ ◼ Cherry │ ◻ Grape │ ◼ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -548,7 +547,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -562,7 +561,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -575,7 +574,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", diff --git a/packages/prompts/test/autocomplete.test.ts b/packages/prompts/test/autocomplete.test.ts index 3bba0232..9762c325 100644 --- a/packages/prompts/test/autocomplete.test.ts +++ b/packages/prompts/test/autocomplete.test.ts @@ -230,7 +230,7 @@ describe('autocompleteMultiselect', () => { output, }); - input.emit('keypress', '', { name: 'left' }); + input.emit('keypress', '', { name: 'up', shift: true }); input.emit('keypress', '', { name: 'return' }); await result; expect(output.buffer).toMatchSnapshot(); @@ -245,8 +245,8 @@ describe('autocompleteMultiselect', () => { output, }); - input.emit('keypress', '', { name: 'left' }); - input.emit('keypress', '', { name: 'left' }); + input.emit('keypress', '', { name: 'up', shift: true }); + input.emit('keypress', '', { name: 'up', shift: true }); input.emit('keypress', '', { name: 'return' }); await result; expect(output.buffer).toMatchSnapshot(); @@ -263,7 +263,7 @@ describe('autocompleteMultiselect', () => { input.emit('keypress', '', { name: 'down' }); input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'right' }); + input.emit('keypress', '', { name: 'down', shift: true }); input.emit('keypress', '', { name: 'return' }); await result; expect(output.buffer).toMatchSnapshot(); @@ -279,7 +279,7 @@ describe('autocompleteMultiselect', () => { }); input.emit('keypress', 'r', { name: 'r' }); - input.emit('keypress', '', { name: 'left' }); + input.emit('keypress', '', { name: 'up', shift: true }); input.emit('keypress', '', { name: 'return' }); await result; expect(output.buffer).toMatchSnapshot(); @@ -297,7 +297,7 @@ describe('autocompleteMultiselect', () => { input.emit('keypress', 'r', { name: 'r' }); input.emit('keypress', '', { name: 'down' }); input.emit('keypress', '', { name: 'space' }); - input.emit('keypress', '', { name: 'right' }); + input.emit('keypress', '', { name: 'down', shift: true }); input.emit('keypress', '', { name: 'return' }); await result; expect(output.buffer).toMatchSnapshot(); From 4ae70560b5313573e763d36ac7a66ec00102d3e5 Mon Sep 17 00:00:00 2001 From: "Thomas F. K. Jorna" Date: Thu, 10 Jul 2025 23:40:09 +0200 Subject: [PATCH 4/4] fix: show shift instead of ctrl --- packages/prompts/src/autocomplete.ts | 2 +- .../__snapshots__/autocomplete.test.ts.snap | 38 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/packages/prompts/src/autocomplete.ts b/packages/prompts/src/autocomplete.ts index 9fe88464..3c09df7e 100644 --- a/packages/prompts/src/autocomplete.ts +++ b/packages/prompts/src/autocomplete.ts @@ -275,7 +275,7 @@ export const autocompleteMultiselect = (opts: AutocompleteMultiSelectOpti // Instructions const instructions = [ `${color.dim('↑/↓')} to navigate`, - `${color.dim('Ctrl+↑/↓')} select all/inverse`, + `${color.dim('Shift+↑/↓')} select all/inverse`, `${color.dim('Space:')} select`, `${color.dim('Enter:')} confirm`, `${color.dim('Type:')} to search`, diff --git a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap index 9232132f..39304e29 100644 --- a/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap +++ b/packages/prompts/test/__snapshots__/autocomplete.test.ts.snap @@ -298,7 +298,7 @@ exports[`autocompleteMultiselect > all selection only applies to filtered option │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -307,7 +307,7 @@ exports[`autocompleteMultiselect > all selection only applies to filtered option │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -315,7 +315,7 @@ exports[`autocompleteMultiselect > all selection only applies to filtered option "│ ◼ Cherry │ ◼ Grape │ ◼ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -340,7 +340,7 @@ exports[`autocompleteMultiselect > can be aborted by a signal 1`] = ` │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", " ", @@ -360,7 +360,7 @@ exports[`autocompleteMultiselect > everything can be selected with left arrow 1` │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -370,7 +370,7 @@ exports[`autocompleteMultiselect > everything can be selected with left arrow 1` │ ◼ Cherry │ ◼ Grape │ ◼ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -395,7 +395,7 @@ exports[`autocompleteMultiselect > everything is deselected if left is pressed a │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -405,7 +405,7 @@ exports[`autocompleteMultiselect > everything is deselected if left is pressed a │ ◼ Cherry │ ◼ Grape │ ◼ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -415,7 +415,7 @@ exports[`autocompleteMultiselect > everything is deselected if left is pressed a │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -440,7 +440,7 @@ exports[`autocompleteMultiselect > inverse can be selected with right arrow 1`] │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -451,7 +451,7 @@ exports[`autocompleteMultiselect > inverse can be selected with right arrow 1`] │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -466,7 +466,7 @@ exports[`autocompleteMultiselect > inverse can be selected with right arrow 1`] │ ◼ Cherry │ ◼ Grape │ ◼ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -491,7 +491,7 @@ exports[`autocompleteMultiselect > inversion only applies to filtered options 1` │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -500,7 +500,7 @@ exports[`autocompleteMultiselect > inversion only applies to filtered options 1` │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -509,7 +509,7 @@ exports[`autocompleteMultiselect > inversion only applies to filtered options 1` │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -522,7 +522,7 @@ exports[`autocompleteMultiselect > inversion only applies to filtered options 1` "│ ◼ Cherry │ ◻ Grape │ ◼ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -547,7 +547,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -561,7 +561,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "", @@ -574,7 +574,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required │ ◻ Cherry │ ◻ Grape │ ◻ Orange -│ ↑/↓ to navigate • Ctrl+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search +│ ↑/↓ to navigate • Shift+↑/↓ select all/inverse • Space: select • Enter: confirm • Type: to search └", "", "",