@topcli/prompts 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -86,6 +86,10 @@ select(message: string, options: SelectOptions): Promise<string>
86
86
  Scrollable select depending `maxVisible` (default `8`).
87
87
  Use `ignoreValues` to skip result render & clear lines after a selected one.
88
88
 
89
+ Use `autocomplete` to allow filtered choices. This can be useful for a large list of choices.
90
+
91
+ Use `caseSensitive` to make autocomplete filters case sensitive. Default `false`
92
+
89
93
  ### `multiselect()`
90
94
 
91
95
  ```ts
@@ -97,6 +101,8 @@ Use `preSelectedChoices` to pre-select choices.
97
101
 
98
102
  Use `validators` to handle user input.
99
103
 
104
+ Use `showHint: false` to disable hint (this option is truthy by default).
105
+
100
106
  **Example**
101
107
 
102
108
  ```js
@@ -106,7 +112,7 @@ const os = await multiselect('Choose OS', {
106
112
  });
107
113
  ```
108
114
 
109
- Use `autocomplete` to allow filtered choices. This can be usefull for a large list of choices.
115
+ Use `autocomplete` to allow filtered choices. This can be useful for a large list of choices.
110
116
 
111
117
  Use `caseSensitive` to make autocomplete filters case sensitive. Default `false`
112
118
 
@@ -172,6 +178,8 @@ export interface SelectOptions extends SharedOptions {
172
178
  choices: (Choice | string)[];
173
179
  maxVisible?: number;
174
180
  ignoreValues?: (string | number | boolean)[];
181
+ autocomplete?: boolean;
182
+ caseSensitive?: boolean;
175
183
  }
176
184
 
177
185
  export interface MultiselectOptions extends SharedOptions {
@@ -181,6 +189,7 @@ export interface MultiselectOptions extends SharedOptions {
181
189
  validators?: Validator[];
182
190
  autocomplete?: boolean;
183
191
  caseSensitive?: boolean;
192
+ showHint?: boolean;
184
193
  }
185
194
 
186
195
  export interface ConfirmOptions extends SharedOptions {
@@ -201,6 +210,8 @@ export interface ConfirmOptions extends SharedOptions {
201
210
  <td align="center" valign="top" width="14.28%"><a href="http://tonygo.dev"><img src="https://avatars0.githubusercontent.com/u/22824417?v=4?s=100" width="100px;" alt="Tony Gorez"/><br /><sub><b>Tony Gorez</b></sub></a><br /><a href="https://github.com/TopCli/prompts/pulls?q=is%3Apr+reviewed-by%3Atony-go" title="Reviewed Pull Requests">👀</a></td>
202
211
  <td align="center" valign="top" width="14.28%"><a href="http://sofiand.github.io/portfolio-client/"><img src="https://avatars.githubusercontent.com/u/39944043?v=4?s=100" width="100px;" alt="Yefis"/><br /><sub><b>Yefis</b></sub></a><br /><a href="https://github.com/TopCli/prompts/commits?author=SofianD" title="Code">💻</a> <a href="https://github.com/TopCli/prompts/commits?author=SofianD" title="Documentation">📖</a></td>
203
212
  <td align="center" valign="top" width="14.28%"><a href="http://justie.dev"><img src="https://avatars.githubusercontent.com/u/7118300?v=4?s=100" width="100px;" alt="Ben"/><br /><sub><b>Ben</b></sub></a><br /><a href="https://github.com/TopCli/prompts/commits?author=JUSTIVE" title="Documentation">📖</a> <a href="#maintenance-JUSTIVE" title="Maintenance">🚧</a></td>
213
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/ncukondo"><img src="https://avatars.githubusercontent.com/u/17022138?v=4?s=100" width="100px;" alt="Takeshi Kondo"/><br /><sub><b>Takeshi Kondo</b></sub></a><br /><a href="#maintenance-ncukondo" title="Maintenance">🚧</a></td>
214
+ <td align="center" valign="top" width="14.28%"><a href="https://github.com/FredGuiou"><img src="https://avatars.githubusercontent.com/u/99122562?v=4?s=100" width="100px;" alt="FredGuiou"/><br /><sub><b>FredGuiou</b></sub></a><br /><a href="https://github.com/TopCli/prompts/commits?author=FredGuiou" title="Code">💻</a> <a href="https://github.com/TopCli/prompts/commits?author=FredGuiou" title="Tests">⚠️</a> <a href="https://github.com/TopCli/prompts/commits?author=FredGuiou" title="Documentation">📖</a></td>
204
215
  </tr>
205
216
  </tbody>
206
217
  </table>
package/dist/index.cjs CHANGED
@@ -314,6 +314,7 @@ var kToggleKeys = /* @__PURE__ */ new Set([
314
314
  var ConfirmPrompt = class extends AbstractPrompt {
315
315
  initial;
316
316
  selectedValue;
317
+ fastAnswer;
317
318
  #boundKeyPressEvent;
318
319
  #boundExitEvent;
319
320
  constructor(message, options = {}) {
@@ -357,7 +358,18 @@ var ConfirmPrompt = class extends AbstractPrompt {
357
358
  if (kToggleKeys.has(key.name ?? "")) {
358
359
  this.selectedValue = !this.selectedValue;
359
360
  }
360
- this.#render();
361
+ if (key.name === "y") {
362
+ this.selectedValue = true;
363
+ resolve(true);
364
+ this.fastAnswer = true;
365
+ } else if (key.name === "n") {
366
+ this.selectedValue = false;
367
+ resolve(false);
368
+ this.fastAnswer = true;
369
+ }
370
+ if (!this.fastAnswer) {
371
+ this.#render();
372
+ }
361
373
  }
362
374
  #onProcessExit() {
363
375
  this.stdin.off("keypress", this.#boundKeyPressEvent);
@@ -367,9 +379,10 @@ var ConfirmPrompt = class extends AbstractPrompt {
367
379
  return `${query} ${this.#getHint()}`;
368
380
  }
369
381
  #onQuestionAnswer() {
382
+ this.clearLastLine();
370
383
  this.stdout.moveCursor(
371
384
  -this.stdout.columns,
372
- -(Math.floor((0, import_wcwidth2.default)(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns) || 1)
385
+ -Math.floor((0, import_wcwidth2.default)(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns)
373
386
  );
374
387
  this.stdout.clearScreenDown();
375
388
  this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${import_kleur3.default.bold(this.message)}${import_node_os3.EOL}`);
@@ -400,18 +413,47 @@ var ConfirmPrompt = class extends AbstractPrompt {
400
413
  var import_node_os4 = require("os");
401
414
  var import_kleur4 = __toESM(require("kleur"), 1);
402
415
  var import_wcwidth3 = __toESM(require("@topcli/wcwidth"), 1);
416
+ var kRequiredChoiceProperties = ["label", "value"];
403
417
  var SelectPrompt = class extends AbstractPrompt {
404
418
  #boundExitEvent = () => void 0;
405
419
  #boundKeyPressEvent = () => void 0;
406
420
  activeIndex = 0;
407
- selectedIndexes = [];
408
421
  questionMessage;
409
- longestChoicelength;
422
+ autocompleteValue = "";
410
423
  options;
411
424
  lastRender;
412
425
  get choices() {
413
426
  return this.options.choices;
414
427
  }
428
+ get filteredChoices() {
429
+ if (!(this.options.autocomplete && this.autocompleteValue.length > 0)) {
430
+ return this.choices;
431
+ }
432
+ const isCaseSensitive = this.options.caseSensitive;
433
+ const autocompleteValue = isCaseSensitive ? this.autocompleteValue : this.autocompleteValue.toLowerCase();
434
+ return this.choices.filter((choice) => this.#filterChoice(choice, autocompleteValue, isCaseSensitive));
435
+ }
436
+ #filterChoice(choice, autocompleteValue, isCaseSensitive = false) {
437
+ const choiceValue = typeof choice === "string" ? isCaseSensitive ? choice : choice.toLowerCase() : isCaseSensitive ? choice.label : choice.label.toLowerCase();
438
+ if (autocompleteValue.includes(" ")) {
439
+ return this.#filterMultipleWords(choiceValue, autocompleteValue, isCaseSensitive);
440
+ }
441
+ return choiceValue.includes(autocompleteValue);
442
+ }
443
+ #filterMultipleWords(choiceValue, autocompleteValue, isCaseSensitive) {
444
+ return autocompleteValue.split(" ").every((word) => {
445
+ const wordValue = isCaseSensitive ? word : word.toLowerCase();
446
+ return choiceValue.includes(wordValue) || choiceValue.includes(autocompleteValue);
447
+ });
448
+ }
449
+ get longestChoice() {
450
+ return Math.max(...this.filteredChoices.map((choice) => {
451
+ if (typeof choice === "string") {
452
+ return choice.length;
453
+ }
454
+ return choice.label.length;
455
+ }));
456
+ }
415
457
  constructor(message, options) {
416
458
  const {
417
459
  stdin = process.stdin,
@@ -428,22 +470,20 @@ var SelectPrompt = class extends AbstractPrompt {
428
470
  this.destroy();
429
471
  throw new TypeError("Missing required param: choices");
430
472
  }
431
- this.longestChoicelength = Math.max(...choices.map((choice) => {
473
+ for (const choice of choices) {
432
474
  if (typeof choice === "string") {
433
- return choice.length;
475
+ continue;
434
476
  }
435
- const kRequiredChoiceProperties2 = ["label", "value"];
436
- for (const prop of kRequiredChoiceProperties2) {
477
+ for (const prop of kRequiredChoiceProperties) {
437
478
  if (!choice[prop]) {
438
479
  this.destroy();
439
480
  throw new TypeError(`Missing ${prop} for choice ${JSON.stringify(choice)}`);
440
481
  }
441
482
  }
442
- return choice.label.length;
443
- }));
483
+ }
444
484
  }
445
485
  #getFormattedChoice(choiceIndex) {
446
- const choice = this.choices[choiceIndex];
486
+ const choice = this.filteredChoices[choiceIndex];
447
487
  if (typeof choice === "string") {
448
488
  return { value: choice, label: choice };
449
489
  }
@@ -451,21 +491,24 @@ var SelectPrompt = class extends AbstractPrompt {
451
491
  }
452
492
  #getVisibleChoices() {
453
493
  const maxVisible = this.options.maxVisible || 8;
454
- let startIndex = Math.min(this.choices.length - maxVisible, this.activeIndex - Math.floor(maxVisible / 2));
494
+ let startIndex = Math.min(this.filteredChoices.length - maxVisible, this.activeIndex - Math.floor(maxVisible / 2));
455
495
  if (startIndex < 0) {
456
496
  startIndex = 0;
457
497
  }
458
- const endIndex = Math.min(startIndex + maxVisible, this.choices.length);
498
+ const endIndex = Math.min(startIndex + maxVisible, this.filteredChoices.length);
459
499
  return { startIndex, endIndex };
460
500
  }
461
501
  #showChoices() {
462
502
  const { startIndex, endIndex } = this.#getVisibleChoices();
463
503
  this.lastRender = { startIndex, endIndex };
504
+ if (this.options.autocomplete) {
505
+ this.write(`${SYMBOLS.Pointer} ${this.autocompleteValue}${import_node_os4.EOL}`);
506
+ }
464
507
  for (let choiceIndex = startIndex; choiceIndex < endIndex; choiceIndex++) {
465
508
  const choice = this.#getFormattedChoice(choiceIndex);
466
509
  const isChoiceSelected = choiceIndex === this.activeIndex;
467
510
  const showPreviousChoicesArrow = startIndex > 0 && choiceIndex === startIndex;
468
- const showNextChoicesArrow = endIndex < this.choices.length && choiceIndex === endIndex - 1;
511
+ const showNextChoicesArrow = endIndex < this.filteredChoices.length && choiceIndex === endIndex - 1;
469
512
  let prefixArrow = " ";
470
513
  if (showPreviousChoicesArrow) {
471
514
  prefixArrow = SYMBOLS.Previous;
@@ -474,16 +517,17 @@ var SelectPrompt = class extends AbstractPrompt {
474
517
  }
475
518
  const prefix = `${prefixArrow}${isChoiceSelected ? `${SYMBOLS.Pointer} ` : " "}`;
476
519
  const formattedLabel = choice.label.padEnd(
477
- this.longestChoicelength < 10 ? this.longestChoicelength : 0
520
+ this.longestChoice < 10 ? this.longestChoice : 0
478
521
  );
479
522
  const formattedDescription = choice.description ? ` - ${choice.description}` : "";
480
523
  const color = isChoiceSelected ? import_kleur4.default.white().bold : import_kleur4.default.gray;
481
- const str = color(`${prefix}${formattedLabel}${formattedDescription}${import_node_os4.EOL}`);
524
+ const str = `${prefix}${color(`${formattedLabel}${formattedDescription}`)}${import_node_os4.EOL}`;
482
525
  this.write(str);
483
526
  }
484
527
  }
485
528
  #showAnsweredQuestion(choice) {
486
- const prefix = `${SYMBOLS.Tick} ${import_kleur4.default.bold(this.message)} ${SYMBOLS.Pointer}`;
529
+ const symbolPrefix = choice === "" ? SYMBOLS.Cross : SYMBOLS.Tick;
530
+ const prefix = `${symbolPrefix} ${import_kleur4.default.bold(this.message)} ${SYMBOLS.Pointer}`;
487
531
  const formattedChoice = import_kleur4.default.yellow(typeof choice === "string" ? choice : choice.label);
488
532
  this.write(`${prefix} ${formattedChoice}${import_node_os4.EOL}`);
489
533
  }
@@ -496,17 +540,18 @@ var SelectPrompt = class extends AbstractPrompt {
496
540
  #onKeypress(...args) {
497
541
  const [resolve, render, , key] = args;
498
542
  if (key.name === "up") {
499
- this.activeIndex = this.activeIndex === 0 ? this.choices.length - 1 : this.activeIndex - 1;
543
+ this.activeIndex = this.activeIndex === 0 ? this.filteredChoices.length - 1 : this.activeIndex - 1;
500
544
  render();
501
545
  } else if (key.name === "down") {
502
- this.activeIndex = this.activeIndex === this.choices.length - 1 ? 0 : this.activeIndex + 1;
546
+ this.activeIndex = this.activeIndex === this.filteredChoices.length - 1 ? 0 : this.activeIndex + 1;
503
547
  render();
504
548
  } else if (key.name === "return") {
549
+ const choice = this.filteredChoices[this.activeIndex] || "";
550
+ const label = typeof choice === "string" ? choice : choice.label;
551
+ const value = typeof choice === "string" ? choice : choice.value;
505
552
  render({ clearRender: true });
506
- const currentChoice = this.choices[this.activeIndex];
507
- const value = typeof currentChoice === "string" ? currentChoice : currentChoice.value;
508
553
  if (!this.options.ignoreValues?.includes(value)) {
509
- this.#showAnsweredQuestion(currentChoice);
554
+ this.#showAnsweredQuestion(label);
510
555
  }
511
556
  this.write(SYMBOLS.ShowCursor);
512
557
  this.destroy();
@@ -514,16 +559,23 @@ var SelectPrompt = class extends AbstractPrompt {
514
559
  process.off("exit", this.#boundExitEvent);
515
560
  resolve(value);
516
561
  } else {
562
+ if (!key.ctrl && this.options.autocomplete) {
563
+ this.activeIndex = 0;
564
+ if (key.name === "backspace" && this.autocompleteValue.length > 0) {
565
+ this.autocompleteValue = this.autocompleteValue.slice(0, -1);
566
+ } else if (key.name !== "backspace") {
567
+ this.autocompleteValue += key.sequence;
568
+ }
569
+ }
517
570
  render();
518
571
  }
519
572
  }
520
573
  async select() {
521
574
  const answer = this.agent.nextAnswers.shift();
522
575
  if (answer !== void 0) {
523
- const formatedAnser = Array.isArray(answer) ? answer.join(", ") : answer;
524
- this.#showAnsweredQuestion(formatedAnser);
576
+ this.#showAnsweredQuestion(answer);
525
577
  this.destroy();
526
- return Array.isArray(answer) ? answer : [answer];
578
+ return answer;
527
579
  }
528
580
  this.write(SYMBOLS.HideCursor);
529
581
  this.#showQuestion();
@@ -538,6 +590,15 @@ var SelectPrompt = class extends AbstractPrompt {
538
590
  this.clearLastLine();
539
591
  linesToClear--;
540
592
  }
593
+ if (this.options.autocomplete) {
594
+ let linesToClear2 = Math.ceil(
595
+ (0, import_wcwidth3.default)(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
596
+ );
597
+ while (linesToClear2 > 0) {
598
+ this.clearLastLine();
599
+ linesToClear2--;
600
+ }
601
+ }
541
602
  }
542
603
  if (clearRender) {
543
604
  const questionLineCount = Math.ceil(
@@ -567,11 +628,12 @@ var SelectPrompt = class extends AbstractPrompt {
567
628
  var import_node_os5 = require("os");
568
629
  var import_kleur5 = __toESM(require("kleur"), 1);
569
630
  var import_wcwidth4 = __toESM(require("@topcli/wcwidth"), 1);
570
- var kRequiredChoiceProperties = ["label", "value"];
631
+ var kRequiredChoiceProperties2 = ["label", "value"];
571
632
  var MultiselectPrompt = class extends AbstractPrompt {
572
633
  #boundExitEvent = () => void 0;
573
634
  #boundKeyPressEvent = () => void 0;
574
635
  #validators;
636
+ #showHint;
575
637
  activeIndex = 0;
576
638
  selectedIndexes = [];
577
639
  questionMessage;
@@ -616,7 +678,8 @@ var MultiselectPrompt = class extends AbstractPrompt {
616
678
  stdout = process.stdout,
617
679
  choices,
618
680
  preSelectedChoices,
619
- validators = []
681
+ validators = [],
682
+ showHint = true
620
683
  } = options ?? {};
621
684
  super(message, stdin, stdout);
622
685
  if (!options) {
@@ -629,11 +692,12 @@ var MultiselectPrompt = class extends AbstractPrompt {
629
692
  throw new TypeError("Missing required param: choices");
630
693
  }
631
694
  this.#validators = validators;
695
+ this.#showHint = showHint;
632
696
  for (const choice of choices) {
633
697
  if (typeof choice === "string") {
634
698
  continue;
635
699
  }
636
- for (const prop of kRequiredChoiceProperties) {
700
+ for (const prop of kRequiredChoiceProperties2) {
637
701
  if (!choice[prop]) {
638
702
  this.destroy();
639
703
  throw new TypeError(`Missing ${prop} for choice ${JSON.stringify(choice)}`);
@@ -823,14 +887,14 @@ var MultiselectPrompt = class extends AbstractPrompt {
823
887
  });
824
888
  }
825
889
  #showQuestion(error = null) {
826
- let hint = import_kleur5.default.gray(
890
+ let hint = this.#showHint ? import_kleur5.default.gray(
827
891
  // eslint-disable-next-line max-len
828
- `(Press ${import_kleur5.default.bold("<Ctrl+A>")} to toggle all, ${import_kleur5.default.bold("<Ctrl+Space>")} to select, ${import_kleur5.default.bold("<Left/Right>")} to toggle, ${import_kleur5.default.bold("<Return>")} to submit)`
829
- );
892
+ `(Press ${import_kleur5.default.bold("<Ctrl+A>")} to toggle all, ${import_kleur5.default.bold("<Left/Right>")} to toggle, ${import_kleur5.default.bold("<Return>")} to submit)`
893
+ ) : "";
830
894
  if (error) {
831
- hint += ` ${import_kleur5.default.red().bold(`[${error}]`)}`;
895
+ hint += `${hint.length > 0 ? " " : ""}${import_kleur5.default.red().bold(`[${error}]`)}`;
832
896
  }
833
- this.questionMessage = `${SYMBOLS.QuestionMark} ${import_kleur5.default.bold(this.message)} ${hint}`;
897
+ this.questionMessage = `${SYMBOLS.QuestionMark} ${import_kleur5.default.bold(this.message)}${hint.length > 0 ? ` ${hint}` : ""}`;
834
898
  this.write(`${this.questionMessage}${import_node_os5.EOL}`);
835
899
  }
836
900
  };
package/dist/index.d.cts CHANGED
@@ -61,16 +61,19 @@ interface MultiselectOptions extends SharedOptions {
61
61
  validators?: PromptValidator[];
62
62
  autocomplete?: boolean;
63
63
  caseSensitive?: boolean;
64
+ showHint?: boolean;
64
65
  }
65
66
 
66
67
  interface SelectOptions extends SharedOptions {
67
68
  choices: (Choice | string)[];
68
69
  maxVisible?: number;
69
70
  ignoreValues?: (string | number | boolean)[];
71
+ autocomplete?: boolean;
72
+ caseSensitive?: boolean;
70
73
  }
71
74
 
72
- declare function question(message: any, options?: {}): Promise<string>;
73
- declare function select(message: string, options: SelectOptions): Promise<string[]>;
75
+ declare function question(message: string, options?: QuestionOptions): Promise<string>;
76
+ declare function select(message: string, options: SelectOptions): Promise<string>;
74
77
  declare function confirm(message: string, options: ConfirmOptions): Promise<boolean>;
75
78
  declare function multiselect(message: string, options: MultiselectOptions): Promise<string[]>;
76
79
 
package/dist/index.d.ts CHANGED
@@ -61,16 +61,19 @@ interface MultiselectOptions extends SharedOptions {
61
61
  validators?: PromptValidator[];
62
62
  autocomplete?: boolean;
63
63
  caseSensitive?: boolean;
64
+ showHint?: boolean;
64
65
  }
65
66
 
66
67
  interface SelectOptions extends SharedOptions {
67
68
  choices: (Choice | string)[];
68
69
  maxVisible?: number;
69
70
  ignoreValues?: (string | number | boolean)[];
71
+ autocomplete?: boolean;
72
+ caseSensitive?: boolean;
70
73
  }
71
74
 
72
- declare function question(message: any, options?: {}): Promise<string>;
73
- declare function select(message: string, options: SelectOptions): Promise<string[]>;
75
+ declare function question(message: string, options?: QuestionOptions): Promise<string>;
76
+ declare function select(message: string, options: SelectOptions): Promise<string>;
74
77
  declare function confirm(message: string, options: ConfirmOptions): Promise<boolean>;
75
78
  declare function multiselect(message: string, options: MultiselectOptions): Promise<string[]>;
76
79
 
package/dist/index.js CHANGED
@@ -274,6 +274,7 @@ var kToggleKeys = /* @__PURE__ */ new Set([
274
274
  var ConfirmPrompt = class extends AbstractPrompt {
275
275
  initial;
276
276
  selectedValue;
277
+ fastAnswer;
277
278
  #boundKeyPressEvent;
278
279
  #boundExitEvent;
279
280
  constructor(message, options = {}) {
@@ -317,7 +318,18 @@ var ConfirmPrompt = class extends AbstractPrompt {
317
318
  if (kToggleKeys.has(key.name ?? "")) {
318
319
  this.selectedValue = !this.selectedValue;
319
320
  }
320
- this.#render();
321
+ if (key.name === "y") {
322
+ this.selectedValue = true;
323
+ resolve(true);
324
+ this.fastAnswer = true;
325
+ } else if (key.name === "n") {
326
+ this.selectedValue = false;
327
+ resolve(false);
328
+ this.fastAnswer = true;
329
+ }
330
+ if (!this.fastAnswer) {
331
+ this.#render();
332
+ }
321
333
  }
322
334
  #onProcessExit() {
323
335
  this.stdin.off("keypress", this.#boundKeyPressEvent);
@@ -327,9 +339,10 @@ var ConfirmPrompt = class extends AbstractPrompt {
327
339
  return `${query} ${this.#getHint()}`;
328
340
  }
329
341
  #onQuestionAnswer() {
342
+ this.clearLastLine();
330
343
  this.stdout.moveCursor(
331
344
  -this.stdout.columns,
332
- -(Math.floor(wcwidth2(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns) || 1)
345
+ -Math.floor(wcwidth2(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns)
333
346
  );
334
347
  this.stdout.clearScreenDown();
335
348
  this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${kleur3.bold(this.message)}${EOL3}`);
@@ -360,18 +373,47 @@ var ConfirmPrompt = class extends AbstractPrompt {
360
373
  import { EOL as EOL4 } from "os";
361
374
  import kleur4 from "kleur";
362
375
  import wcwidth3 from "@topcli/wcwidth";
376
+ var kRequiredChoiceProperties = ["label", "value"];
363
377
  var SelectPrompt = class extends AbstractPrompt {
364
378
  #boundExitEvent = () => void 0;
365
379
  #boundKeyPressEvent = () => void 0;
366
380
  activeIndex = 0;
367
- selectedIndexes = [];
368
381
  questionMessage;
369
- longestChoicelength;
382
+ autocompleteValue = "";
370
383
  options;
371
384
  lastRender;
372
385
  get choices() {
373
386
  return this.options.choices;
374
387
  }
388
+ get filteredChoices() {
389
+ if (!(this.options.autocomplete && this.autocompleteValue.length > 0)) {
390
+ return this.choices;
391
+ }
392
+ const isCaseSensitive = this.options.caseSensitive;
393
+ const autocompleteValue = isCaseSensitive ? this.autocompleteValue : this.autocompleteValue.toLowerCase();
394
+ return this.choices.filter((choice) => this.#filterChoice(choice, autocompleteValue, isCaseSensitive));
395
+ }
396
+ #filterChoice(choice, autocompleteValue, isCaseSensitive = false) {
397
+ const choiceValue = typeof choice === "string" ? isCaseSensitive ? choice : choice.toLowerCase() : isCaseSensitive ? choice.label : choice.label.toLowerCase();
398
+ if (autocompleteValue.includes(" ")) {
399
+ return this.#filterMultipleWords(choiceValue, autocompleteValue, isCaseSensitive);
400
+ }
401
+ return choiceValue.includes(autocompleteValue);
402
+ }
403
+ #filterMultipleWords(choiceValue, autocompleteValue, isCaseSensitive) {
404
+ return autocompleteValue.split(" ").every((word) => {
405
+ const wordValue = isCaseSensitive ? word : word.toLowerCase();
406
+ return choiceValue.includes(wordValue) || choiceValue.includes(autocompleteValue);
407
+ });
408
+ }
409
+ get longestChoice() {
410
+ return Math.max(...this.filteredChoices.map((choice) => {
411
+ if (typeof choice === "string") {
412
+ return choice.length;
413
+ }
414
+ return choice.label.length;
415
+ }));
416
+ }
375
417
  constructor(message, options) {
376
418
  const {
377
419
  stdin = process.stdin,
@@ -388,22 +430,20 @@ var SelectPrompt = class extends AbstractPrompt {
388
430
  this.destroy();
389
431
  throw new TypeError("Missing required param: choices");
390
432
  }
391
- this.longestChoicelength = Math.max(...choices.map((choice) => {
433
+ for (const choice of choices) {
392
434
  if (typeof choice === "string") {
393
- return choice.length;
435
+ continue;
394
436
  }
395
- const kRequiredChoiceProperties2 = ["label", "value"];
396
- for (const prop of kRequiredChoiceProperties2) {
437
+ for (const prop of kRequiredChoiceProperties) {
397
438
  if (!choice[prop]) {
398
439
  this.destroy();
399
440
  throw new TypeError(`Missing ${prop} for choice ${JSON.stringify(choice)}`);
400
441
  }
401
442
  }
402
- return choice.label.length;
403
- }));
443
+ }
404
444
  }
405
445
  #getFormattedChoice(choiceIndex) {
406
- const choice = this.choices[choiceIndex];
446
+ const choice = this.filteredChoices[choiceIndex];
407
447
  if (typeof choice === "string") {
408
448
  return { value: choice, label: choice };
409
449
  }
@@ -411,21 +451,24 @@ var SelectPrompt = class extends AbstractPrompt {
411
451
  }
412
452
  #getVisibleChoices() {
413
453
  const maxVisible = this.options.maxVisible || 8;
414
- let startIndex = Math.min(this.choices.length - maxVisible, this.activeIndex - Math.floor(maxVisible / 2));
454
+ let startIndex = Math.min(this.filteredChoices.length - maxVisible, this.activeIndex - Math.floor(maxVisible / 2));
415
455
  if (startIndex < 0) {
416
456
  startIndex = 0;
417
457
  }
418
- const endIndex = Math.min(startIndex + maxVisible, this.choices.length);
458
+ const endIndex = Math.min(startIndex + maxVisible, this.filteredChoices.length);
419
459
  return { startIndex, endIndex };
420
460
  }
421
461
  #showChoices() {
422
462
  const { startIndex, endIndex } = this.#getVisibleChoices();
423
463
  this.lastRender = { startIndex, endIndex };
464
+ if (this.options.autocomplete) {
465
+ this.write(`${SYMBOLS.Pointer} ${this.autocompleteValue}${EOL4}`);
466
+ }
424
467
  for (let choiceIndex = startIndex; choiceIndex < endIndex; choiceIndex++) {
425
468
  const choice = this.#getFormattedChoice(choiceIndex);
426
469
  const isChoiceSelected = choiceIndex === this.activeIndex;
427
470
  const showPreviousChoicesArrow = startIndex > 0 && choiceIndex === startIndex;
428
- const showNextChoicesArrow = endIndex < this.choices.length && choiceIndex === endIndex - 1;
471
+ const showNextChoicesArrow = endIndex < this.filteredChoices.length && choiceIndex === endIndex - 1;
429
472
  let prefixArrow = " ";
430
473
  if (showPreviousChoicesArrow) {
431
474
  prefixArrow = SYMBOLS.Previous;
@@ -434,16 +477,17 @@ var SelectPrompt = class extends AbstractPrompt {
434
477
  }
435
478
  const prefix = `${prefixArrow}${isChoiceSelected ? `${SYMBOLS.Pointer} ` : " "}`;
436
479
  const formattedLabel = choice.label.padEnd(
437
- this.longestChoicelength < 10 ? this.longestChoicelength : 0
480
+ this.longestChoice < 10 ? this.longestChoice : 0
438
481
  );
439
482
  const formattedDescription = choice.description ? ` - ${choice.description}` : "";
440
483
  const color = isChoiceSelected ? kleur4.white().bold : kleur4.gray;
441
- const str = color(`${prefix}${formattedLabel}${formattedDescription}${EOL4}`);
484
+ const str = `${prefix}${color(`${formattedLabel}${formattedDescription}`)}${EOL4}`;
442
485
  this.write(str);
443
486
  }
444
487
  }
445
488
  #showAnsweredQuestion(choice) {
446
- const prefix = `${SYMBOLS.Tick} ${kleur4.bold(this.message)} ${SYMBOLS.Pointer}`;
489
+ const symbolPrefix = choice === "" ? SYMBOLS.Cross : SYMBOLS.Tick;
490
+ const prefix = `${symbolPrefix} ${kleur4.bold(this.message)} ${SYMBOLS.Pointer}`;
447
491
  const formattedChoice = kleur4.yellow(typeof choice === "string" ? choice : choice.label);
448
492
  this.write(`${prefix} ${formattedChoice}${EOL4}`);
449
493
  }
@@ -456,17 +500,18 @@ var SelectPrompt = class extends AbstractPrompt {
456
500
  #onKeypress(...args) {
457
501
  const [resolve, render, , key] = args;
458
502
  if (key.name === "up") {
459
- this.activeIndex = this.activeIndex === 0 ? this.choices.length - 1 : this.activeIndex - 1;
503
+ this.activeIndex = this.activeIndex === 0 ? this.filteredChoices.length - 1 : this.activeIndex - 1;
460
504
  render();
461
505
  } else if (key.name === "down") {
462
- this.activeIndex = this.activeIndex === this.choices.length - 1 ? 0 : this.activeIndex + 1;
506
+ this.activeIndex = this.activeIndex === this.filteredChoices.length - 1 ? 0 : this.activeIndex + 1;
463
507
  render();
464
508
  } else if (key.name === "return") {
509
+ const choice = this.filteredChoices[this.activeIndex] || "";
510
+ const label = typeof choice === "string" ? choice : choice.label;
511
+ const value = typeof choice === "string" ? choice : choice.value;
465
512
  render({ clearRender: true });
466
- const currentChoice = this.choices[this.activeIndex];
467
- const value = typeof currentChoice === "string" ? currentChoice : currentChoice.value;
468
513
  if (!this.options.ignoreValues?.includes(value)) {
469
- this.#showAnsweredQuestion(currentChoice);
514
+ this.#showAnsweredQuestion(label);
470
515
  }
471
516
  this.write(SYMBOLS.ShowCursor);
472
517
  this.destroy();
@@ -474,16 +519,23 @@ var SelectPrompt = class extends AbstractPrompt {
474
519
  process.off("exit", this.#boundExitEvent);
475
520
  resolve(value);
476
521
  } else {
522
+ if (!key.ctrl && this.options.autocomplete) {
523
+ this.activeIndex = 0;
524
+ if (key.name === "backspace" && this.autocompleteValue.length > 0) {
525
+ this.autocompleteValue = this.autocompleteValue.slice(0, -1);
526
+ } else if (key.name !== "backspace") {
527
+ this.autocompleteValue += key.sequence;
528
+ }
529
+ }
477
530
  render();
478
531
  }
479
532
  }
480
533
  async select() {
481
534
  const answer = this.agent.nextAnswers.shift();
482
535
  if (answer !== void 0) {
483
- const formatedAnser = Array.isArray(answer) ? answer.join(", ") : answer;
484
- this.#showAnsweredQuestion(formatedAnser);
536
+ this.#showAnsweredQuestion(answer);
485
537
  this.destroy();
486
- return Array.isArray(answer) ? answer : [answer];
538
+ return answer;
487
539
  }
488
540
  this.write(SYMBOLS.HideCursor);
489
541
  this.#showQuestion();
@@ -498,6 +550,15 @@ var SelectPrompt = class extends AbstractPrompt {
498
550
  this.clearLastLine();
499
551
  linesToClear--;
500
552
  }
553
+ if (this.options.autocomplete) {
554
+ let linesToClear2 = Math.ceil(
555
+ wcwidth3(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
556
+ );
557
+ while (linesToClear2 > 0) {
558
+ this.clearLastLine();
559
+ linesToClear2--;
560
+ }
561
+ }
501
562
  }
502
563
  if (clearRender) {
503
564
  const questionLineCount = Math.ceil(
@@ -527,11 +588,12 @@ var SelectPrompt = class extends AbstractPrompt {
527
588
  import { EOL as EOL5 } from "os";
528
589
  import kleur5 from "kleur";
529
590
  import wcwidth4 from "@topcli/wcwidth";
530
- var kRequiredChoiceProperties = ["label", "value"];
591
+ var kRequiredChoiceProperties2 = ["label", "value"];
531
592
  var MultiselectPrompt = class extends AbstractPrompt {
532
593
  #boundExitEvent = () => void 0;
533
594
  #boundKeyPressEvent = () => void 0;
534
595
  #validators;
596
+ #showHint;
535
597
  activeIndex = 0;
536
598
  selectedIndexes = [];
537
599
  questionMessage;
@@ -576,7 +638,8 @@ var MultiselectPrompt = class extends AbstractPrompt {
576
638
  stdout = process.stdout,
577
639
  choices,
578
640
  preSelectedChoices,
579
- validators = []
641
+ validators = [],
642
+ showHint = true
580
643
  } = options ?? {};
581
644
  super(message, stdin, stdout);
582
645
  if (!options) {
@@ -589,11 +652,12 @@ var MultiselectPrompt = class extends AbstractPrompt {
589
652
  throw new TypeError("Missing required param: choices");
590
653
  }
591
654
  this.#validators = validators;
655
+ this.#showHint = showHint;
592
656
  for (const choice of choices) {
593
657
  if (typeof choice === "string") {
594
658
  continue;
595
659
  }
596
- for (const prop of kRequiredChoiceProperties) {
660
+ for (const prop of kRequiredChoiceProperties2) {
597
661
  if (!choice[prop]) {
598
662
  this.destroy();
599
663
  throw new TypeError(`Missing ${prop} for choice ${JSON.stringify(choice)}`);
@@ -783,14 +847,14 @@ var MultiselectPrompt = class extends AbstractPrompt {
783
847
  });
784
848
  }
785
849
  #showQuestion(error = null) {
786
- let hint = kleur5.gray(
850
+ let hint = this.#showHint ? kleur5.gray(
787
851
  // eslint-disable-next-line max-len
788
- `(Press ${kleur5.bold("<Ctrl+A>")} to toggle all, ${kleur5.bold("<Ctrl+Space>")} to select, ${kleur5.bold("<Left/Right>")} to toggle, ${kleur5.bold("<Return>")} to submit)`
789
- );
852
+ `(Press ${kleur5.bold("<Ctrl+A>")} to toggle all, ${kleur5.bold("<Left/Right>")} to toggle, ${kleur5.bold("<Return>")} to submit)`
853
+ ) : "";
790
854
  if (error) {
791
- hint += ` ${kleur5.red().bold(`[${error}]`)}`;
855
+ hint += `${hint.length > 0 ? " " : ""}${kleur5.red().bold(`[${error}]`)}`;
792
856
  }
793
- this.questionMessage = `${SYMBOLS.QuestionMark} ${kleur5.bold(this.message)} ${hint}`;
857
+ this.questionMessage = `${SYMBOLS.QuestionMark} ${kleur5.bold(this.message)}${hint.length > 0 ? ` ${hint}` : ""}`;
794
858
  this.write(`${this.questionMessage}${EOL5}`);
795
859
  }
796
860
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topcli/prompts",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Node.js user input library for command-line interfaces.",
5
5
  "scripts": {
6
6
  "build": "tsup index.ts --format cjs,esm --dts --clean",
@@ -32,10 +32,10 @@
32
32
  "type": "module",
33
33
  "devDependencies": {
34
34
  "@nodesecure/eslint-config": "^1.9.0",
35
- "@types/node": "^20.10.6",
36
- "c8": "^8.0.1",
35
+ "@types/node": "^20.11.6",
36
+ "c8": "^9.1.0",
37
37
  "eslint": "^8.56.0",
38
- "esmock": "^2.6.0",
38
+ "esmock": "^2.6.3",
39
39
  "glob": "^10.3.10",
40
40
  "tsup": "^8.0.1",
41
41
  "tsx": "^4.7.0",