Skip to content

Commit 4b2280a

Browse files
committed
feat: allow selecting all and selecting inverse
1 parent 9a1412d commit 4b2280a

4 files changed

Lines changed: 339 additions & 4 deletions

File tree

packages/core/src/prompts/autocomplete.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
140140
const isUpKey = key.name === 'up';
141141
const isDownKey = key.name === 'down';
142142
const isReturnKey = key.name === 'return';
143+
const isLeft = key.name === 'left';
144+
const isRight = key.name === 'right';
143145

144146
// Start navigation mode with up/down arrows
145147
if (isUpKey || isDownKey) {
@@ -161,6 +163,15 @@ export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
161163
(key.name === 'tab' || (this.isNavigating && key.name === 'space'))
162164
) {
163165
this.toggleSelected(this.focusedValue);
166+
} else if (isLeft) {
167+
// set to none if all are selected
168+
if (this.selectedValues.length === this.filteredOptions.length) {
169+
this.deselectAll();
170+
} else {
171+
this.selectAll();
172+
}
173+
} else if (isRight) {
174+
this.invertSelected();
164175
} else {
165176
this.isNavigating = false;
166177
}
@@ -173,10 +184,20 @@ export default class AutocompletePrompt<T extends OptionLike> extends Prompt<
173184
}
174185
}
175186

187+
selectAll() {
188+
this.selectedValues = this.filteredOptions.map((opt) => opt.value);
189+
}
190+
176191
deselectAll() {
177192
this.selectedValues = [];
178193
}
179194

195+
invertSelected() {
196+
this.selectedValues = this.filteredOptions
197+
.filter((opt) => !this.selectedValues.includes(opt.value))
198+
.map((opt) => opt.value);
199+
}
200+
180201
toggleSelected(value: T['value']) {
181202
if (this.filteredOptions.length === 0) {
182203
return;

packages/prompts/src/autocomplete.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export const autocomplete = <Value>(opts: AutocompleteOptions<Value>) => {
9696
const placeholder = opts.placeholder;
9797
const showPlaceholder = valueAsString === '' && placeholder !== undefined;
9898

99+
console.log(this.state);
99100
// Handle different states
100101
switch (this.state) {
101102
case 'submit': {
@@ -275,6 +276,7 @@ export const autocompleteMultiselect = <Value>(opts: AutocompleteMultiSelectOpti
275276
// Instructions
276277
const instructions = [
277278
`${color.dim('↑/↓')} to navigate`,
279+
`${color.dim('←/→')} select all/inverse`,
278280
`${color.dim('Space:')} select`,
279281
`${color.dim('Enter:')} confirm`,
280282
`${color.dim('Type:')} to search`,

packages/prompts/test/__snapshots__/autocomplete.test.ts.snap

Lines changed: 234 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,49 @@ exports[`autocomplete > supports initialValue 1`] = `
286286
]
287287
`;
288288
289+
exports[`autocompleteMultiselect > all selection only applies to filtered options 1`] = `
290+
[
291+
"<cursor.hide>",
292+
"│
293+
◆ Select a fruit
294+
295+
│ Search: _
296+
│ ◻ Apple
297+
│ ◻ Banana
298+
│ ◻ Cherry
299+
│ ◻ Grape
300+
│ ◻ Orange
301+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
302+
└",
303+
"<cursor.backward count=999><cursor.up count=10>",
304+
"<cursor.down count=3>",
305+
"<erase.down>",
306+
"│ Search: r█ (3 matches)
307+
│ ◻ Cherry
308+
│ ◻ Grape
309+
│ ◻ Orange
310+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
311+
└",
312+
"<cursor.backward count=999><cursor.up count=8>",
313+
"<cursor.down count=3>",
314+
"<erase.down>",
315+
"│ Search: r (3 matches)
316+
│ ◼ Cherry
317+
│ ◼ Grape
318+
│ ◼ Orange
319+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
320+
└",
321+
"<cursor.backward count=999><cursor.up count=8>",
322+
"<cursor.down count=1>",
323+
"<erase.down>",
324+
"◇ Select a fruit
325+
│ 3 items selected",
326+
"
327+
",
328+
"<cursor.show>",
329+
]
330+
`;
331+
289332
exports[`autocompleteMultiselect > can be aborted by a signal 1`] = `
290333
[
291334
"<cursor.hide>",
@@ -298,8 +341,195 @@ exports[`autocompleteMultiselect > can be aborted by a signal 1`] = `
298341
│ ◻ Cherry
299342
│ ◻ Grape
300343
│ ◻ Orange
301-
│ ↑/↓ to navigate • Space: select • Enter: confirm • Type: to search
344+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
345+
└",
346+
"
347+
",
348+
"<cursor.show>",
349+
]
350+
`;
351+
352+
exports[`autocompleteMultiselect > everything can be selected with left arrow 1`] = `
353+
[
354+
"<cursor.hide>",
355+
"│
356+
◆ Select a fruit
357+
358+
│ Search: _
359+
│ ◻ Apple
360+
│ ◻ Banana
361+
│ ◻ Cherry
362+
│ ◻ Grape
363+
│ ◻ Orange
364+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
365+
└",
366+
"<cursor.backward count=999><cursor.up count=10>",
367+
"<cursor.down count=4>",
368+
"<erase.down>",
369+
"│ ◼ Apple
370+
│ ◼ Banana
371+
│ ◼ Cherry
372+
│ ◼ Grape
373+
│ ◼ Orange
374+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
375+
└",
376+
"<cursor.backward count=999><cursor.up count=10>",
377+
"<cursor.down count=1>",
378+
"<erase.down>",
379+
"◇ Select a fruit
380+
│ 5 items selected",
381+
"
382+
",
383+
"<cursor.show>",
384+
]
385+
`;
386+
387+
exports[`autocompleteMultiselect > everything is deselected if left is pressed again 1`] = `
388+
[
389+
"<cursor.hide>",
390+
"│
391+
◆ Select a fruit
392+
393+
│ Search: _
394+
│ ◻ Apple
395+
│ ◻ Banana
396+
│ ◻ Cherry
397+
│ ◻ Grape
398+
│ ◻ Orange
399+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
400+
└",
401+
"<cursor.backward count=999><cursor.up count=10>",
402+
"<cursor.down count=4>",
403+
"<erase.down>",
404+
"│ ◼ Apple
405+
│ ◼ Banana
406+
│ ◼ Cherry
407+
│ ◼ Grape
408+
│ ◼ Orange
409+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
410+
└",
411+
"<cursor.backward count=999><cursor.up count=10>",
412+
"<cursor.down count=4>",
413+
"<erase.down>",
414+
"│ ◻ Apple
415+
│ ◻ Banana
416+
│ ◻ Cherry
417+
│ ◻ Grape
418+
│ ◻ Orange
419+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
420+
└",
421+
"<cursor.backward count=999><cursor.up count=10>",
422+
"<cursor.down count=1>",
423+
"<erase.down>",
424+
"◇ Select a fruit
425+
│ 0 items selected",
426+
"
427+
",
428+
"<cursor.show>",
429+
]
430+
`;
431+
432+
exports[`autocompleteMultiselect > inverse can be selected with right arrow 1`] = `
433+
[
434+
"<cursor.hide>",
435+
"│
436+
◆ Select a fruit
437+
438+
│ Search: _
439+
│ ◻ Apple
440+
│ ◻ Banana
441+
│ ◻ Cherry
442+
│ ◻ Grape
443+
│ ◻ Orange
444+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
445+
└",
446+
"<cursor.backward count=999><cursor.up count=10>",
447+
"<cursor.down count=3>",
448+
"<erase.down>",
449+
"│ Search: 
450+
│ ◻ Apple
451+
│ ◻ Banana
452+
│ ◻ Cherry
453+
│ ◻ Grape
454+
│ ◻ Orange
455+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
456+
└",
457+
"<cursor.backward count=999><cursor.up count=10>",
458+
"<cursor.down count=5>",
459+
"<erase.line><cursor.left count=1>",
460+
"│ ◼ Banana",
461+
"<cursor.down count=5>",
462+
"<cursor.backward count=999><cursor.up count=10>",
463+
"<cursor.down count=4>",
464+
"<erase.down>",
465+
"│ ◼ Apple
466+
│ ◻ Banana
467+
│ ◼ Cherry
468+
│ ◼ Grape
469+
│ ◼ Orange
470+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
302471
└",
472+
"<cursor.backward count=999><cursor.up count=10>",
473+
"<cursor.down count=1>",
474+
"<erase.down>",
475+
"◇ Select a fruit
476+
│ 4 items selected",
477+
"
478+
",
479+
"<cursor.show>",
480+
]
481+
`;
482+
483+
exports[`autocompleteMultiselect > inversion only applies to filtered options 1`] = `
484+
[
485+
"<cursor.hide>",
486+
"│
487+
◆ Select a fruit
488+
489+
│ Search: _
490+
│ ◻ Apple
491+
│ ◻ Banana
492+
│ ◻ Cherry
493+
│ ◻ Grape
494+
│ ◻ Orange
495+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
496+
└",
497+
"<cursor.backward count=999><cursor.up count=10>",
498+
"<cursor.down count=3>",
499+
"<erase.down>",
500+
"│ Search: r█ (3 matches)
501+
│ ◻ Cherry
502+
│ ◻ Grape
503+
│ ◻ Orange
504+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
505+
└",
506+
"<cursor.backward count=999><cursor.up count=8>",
507+
"<cursor.down count=3>",
508+
"<erase.down>",
509+
"│ Search: r (3 matches)
510+
│ ◻ Cherry
511+
│ ◻ Grape
512+
│ ◻ Orange
513+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
514+
└",
515+
"<cursor.backward count=999><cursor.up count=8>",
516+
"<cursor.down count=5>",
517+
"<erase.line><cursor.left count=1>",
518+
"│ ◼ Grape",
519+
"<cursor.down count=3>",
520+
"<cursor.backward count=999><cursor.up count=8>",
521+
"<cursor.down count=4>",
522+
"<erase.down>",
523+
"│ ◼ Cherry
524+
│ ◻ Grape
525+
│ ◼ Orange
526+
│ ↑/↓ to navigate • ←/→ select all/inverse • Space: select • Enter: confirm • Type: to search
527+
└",
528+
"<cursor.backward count=999><cursor.up count=8>",
529+
"<cursor.down count=1>",
530+
"<erase.down>",
531+
"◇ Select a fruit
532+
│ 2 items selected",
303533
"
304534
",
305535
"<cursor.show>",
@@ -318,7 +548,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required
318548
│ ◻ Cherry
319549
│ ◻ Grape
320550
│ ◻ Orange
321-
[36m│[39m [2m[2m↑/↓[22m[2m to navigate • [2mSpace:[22m[2m select • [2mEnter:[22m[2m confirm • [2mType:[22m[2m to search[22m
551+
[36m│[39m [2m[2m↑/↓[22m[2m to navigate • [2m←/→[22m[2m select all/inverse • [2mSpace:[22m[2m select • [2mEnter:[22m[2m confirm • [2mType:[22m[2m to search[22m
322552
└",
323553
"<cursor.backward count=999><cursor.up count=10>",
324554
"<cursor.down count=1>",
@@ -332,7 +562,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required
332562
│ ◻ Cherry
333563
│ ◻ Grape
334564
│ ◻ Orange
335-
[36m│[39m [2m[2m↑/↓[22m[2m to navigate • [2mSpace:[22m[2m select • [2mEnter:[22m[2m confirm • [2mType:[22m[2m to search[22m
565+
[36m│[39m [2m[2m↑/↓[22m[2m to navigate • [2m←/→[22m[2m select all/inverse • [2mSpace:[22m[2m select • [2mEnter:[22m[2m confirm • [2mType:[22m[2m to search[22m
336566
└",
337567
"<cursor.backward count=999><cursor.up count=11>",
338568
"<cursor.down count=1>",
@@ -345,7 +575,7 @@ exports[`autocompleteMultiselect > renders error when empty selection & required
345575
│ ◻ Cherry
346576
│ ◻ Grape
347577
│ ◻ Orange
348-
[36m│[39m [2m[2m↑/↓[22m[2m to navigate • [2mSpace:[22m[2m select • [2mEnter:[22m[2m confirm • [2mType:[22m[2m to search[22m
578+
[36m│[39m [2m[2m↑/↓[22m[2m to navigate • [2m←/→[22m[2m select all/inverse • [2mSpace:[22m[2m select • [2mEnter:[22m[2m confirm • [2mType:[22m[2m to search[22m
349579
└",
350580
"<cursor.backward count=999><cursor.up count=10>",
351581
"<cursor.down count=1>",

0 commit comments

Comments
 (0)