@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 +12 -1
- package/dist/index.cjs +97 -33
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +97 -33
- package/package.json +4 -4
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
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
473
|
+
for (const choice of choices) {
|
|
432
474
|
if (typeof choice === "string") {
|
|
433
|
-
|
|
475
|
+
continue;
|
|
434
476
|
}
|
|
435
|
-
const
|
|
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
|
-
|
|
443
|
-
}));
|
|
483
|
+
}
|
|
444
484
|
}
|
|
445
485
|
#getFormattedChoice(choiceIndex) {
|
|
446
|
-
const choice = this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
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
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
524
|
-
this.#showAnsweredQuestion(formatedAnser);
|
|
576
|
+
this.#showAnsweredQuestion(answer);
|
|
525
577
|
this.destroy();
|
|
526
|
-
return
|
|
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
|
|
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
|
|
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("<
|
|
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 +=
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
433
|
+
for (const choice of choices) {
|
|
392
434
|
if (typeof choice === "string") {
|
|
393
|
-
|
|
435
|
+
continue;
|
|
394
436
|
}
|
|
395
|
-
const
|
|
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
|
-
|
|
403
|
-
}));
|
|
443
|
+
}
|
|
404
444
|
}
|
|
405
445
|
#getFormattedChoice(choiceIndex) {
|
|
406
|
-
const choice = this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 =
|
|
484
|
+
const str = `${prefix}${color(`${formattedLabel}${formattedDescription}`)}${EOL4}`;
|
|
442
485
|
this.write(str);
|
|
443
486
|
}
|
|
444
487
|
}
|
|
445
488
|
#showAnsweredQuestion(choice) {
|
|
446
|
-
const
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
484
|
-
this.#showAnsweredQuestion(formatedAnser);
|
|
536
|
+
this.#showAnsweredQuestion(answer);
|
|
485
537
|
this.destroy();
|
|
486
|
-
return
|
|
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
|
|
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
|
|
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("<
|
|
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 +=
|
|
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.
|
|
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.
|
|
36
|
-
"c8": "^
|
|
35
|
+
"@types/node": "^20.11.6",
|
|
36
|
+
"c8": "^9.1.0",
|
|
37
37
|
"eslint": "^8.56.0",
|
|
38
|
-
"esmock": "^2.6.
|
|
38
|
+
"esmock": "^2.6.3",
|
|
39
39
|
"glob": "^10.3.10",
|
|
40
40
|
"tsup": "^8.0.1",
|
|
41
41
|
"tsx": "^4.7.0",
|