@topcli/prompts 1.5.1 → 1.7.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 +11 -4
- package/index.d.ts +2 -0
- package/index.js +5 -8
- package/package.json +2 -5
- package/src/constants.js +4 -3
- package/src/{abstract-prompt.js → prompts/abstract.js} +2 -4
- package/src/{confirm-prompt.js → prompts/confirm.js} +3 -3
- package/src/prompts/index.js +12 -0
- package/src/{multiselect-prompt.js → prompts/multiselect.js} +100 -36
- package/src/{question-prompt.js → prompts/question.js} +3 -3
- package/src/{select-prompt.js → prompts/select.js} +3 -3
- package/src/utils.js +43 -0
package/README.md
CHANGED
|
@@ -12,7 +12,8 @@ Node.js user prompt library for command-line interfaces.
|
|
|
12
12
|
|
|
13
13
|
## Getting Started
|
|
14
14
|
|
|
15
|
-
>
|
|
15
|
+
> [!CAUTION]
|
|
16
|
+
> This package is ESM only.
|
|
16
17
|
|
|
17
18
|
This package is available in the Node Package Repository and can be easily installed with [npm](https://docs.npmjs.com/getting-started/what-is-npm) or [yarn](https://yarnpkg.com).
|
|
18
19
|
|
|
@@ -94,7 +95,7 @@ Use `ignoreValues` to skip result render & clear lines after a selected one.
|
|
|
94
95
|
multiselect(message: string, options: MultiselectOptions): Promise<[string]>
|
|
95
96
|
```
|
|
96
97
|
|
|
97
|
-
Scrollable multiselect depending `maxVisible` (default `8`)
|
|
98
|
+
Scrollable multiselect depending `maxVisible` (default `8`).<br>
|
|
98
99
|
Use `preSelectedChoices` to pre-select choices.
|
|
99
100
|
|
|
100
101
|
Use `validators` to handle user input.
|
|
@@ -108,6 +109,10 @@ const os = await multiselect('Choose OS', {
|
|
|
108
109
|
});
|
|
109
110
|
```
|
|
110
111
|
|
|
112
|
+
Use `autocomplete` to allow filtered choices. This can be usefull for a large list of choices.
|
|
113
|
+
|
|
114
|
+
Use `caseSensitive` to make autocomplete filters case sensitive. Default `false`
|
|
115
|
+
|
|
111
116
|
### `confirm()`
|
|
112
117
|
|
|
113
118
|
```ts
|
|
@@ -134,7 +139,7 @@ assert.equal(input, "John");
|
|
|
134
139
|
> - When using `question()`, `validators` functions will not be executed.
|
|
135
140
|
> - When using `select()`, the answer can be different from the available choices.
|
|
136
141
|
> - When using `confirm()`, the answer can be any type other than boolean.
|
|
137
|
-
> - etc
|
|
142
|
+
> - etc<br>
|
|
138
143
|
> **Use with caution**
|
|
139
144
|
|
|
140
145
|
## Interfaces
|
|
@@ -177,6 +182,8 @@ export interface MultiselectOptions extends SharedOptions {
|
|
|
177
182
|
maxVisible?: number;
|
|
178
183
|
preSelectedChoices?: (Choice | string)[];
|
|
179
184
|
validators?: Validator[];
|
|
185
|
+
autocomplete?: boolean;
|
|
186
|
+
caseSensitive?: boolean;
|
|
180
187
|
}
|
|
181
188
|
|
|
182
189
|
export interface ConfirmOptions extends SharedOptions {
|
|
@@ -193,7 +200,7 @@ export interface ConfirmOptions extends SharedOptions {
|
|
|
193
200
|
<tbody>
|
|
194
201
|
<tr>
|
|
195
202
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/PierreDemailly"><img src="https://avatars.githubusercontent.com/u/39910767?v=4?s=100" width="100px;" alt="PierreDemailly"/><br /><sub><b>PierreDemailly</b></sub></a><br /><a href="https://github.com/TopCli/prompts/commits?author=PierreDemailly" title="Code">💻</a> <a href="https://github.com/TopCli/prompts/commits?author=PierreDemailly" title="Tests">⚠️</a></td>
|
|
196
|
-
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/thomas-gentilhomme/"><img src="https://avatars.githubusercontent.com/u/4438263?v=4?s=100" width="100px;" alt="Gentilhomme"/><br /><sub><b>Gentilhomme</b></sub></a><br /><a href="https://github.com/TopCli/prompts/pulls?q=is%3Apr+reviewed-by%3Afraxken" title="Reviewed Pull Requests">👀</a></td>
|
|
203
|
+
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/thomas-gentilhomme/"><img src="https://avatars.githubusercontent.com/u/4438263?v=4?s=100" width="100px;" alt="Gentilhomme"/><br /><sub><b>Gentilhomme</b></sub></a><br /><a href="https://github.com/TopCli/prompts/pulls?q=is%3Apr+reviewed-by%3Afraxken" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/TopCli/prompts/commits?author=fraxken" title="Code">💻</a> <a href="https://github.com/TopCli/prompts/commits?author=fraxken" title="Documentation">📖</a></td>
|
|
197
204
|
<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>
|
|
198
205
|
<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>
|
|
199
206
|
<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>
|
package/index.d.ts
CHANGED
|
@@ -35,6 +35,8 @@ export interface MultiselectOptions extends SharedOptions {
|
|
|
35
35
|
maxVisible?: number;
|
|
36
36
|
preSelectedChoices?: (Choice | string)[];
|
|
37
37
|
validators?: Validator[];
|
|
38
|
+
autocomplete?: boolean;
|
|
39
|
+
caseSensitive?: boolean;
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
export interface ConfirmOptions extends SharedOptions {
|
package/index.js
CHANGED
|
@@ -1,31 +1,28 @@
|
|
|
1
1
|
// Import Internal Dependencies
|
|
2
|
-
import
|
|
3
|
-
import { SelectPrompt } from "./src/select-prompt.js";
|
|
4
|
-
import { QuestionPrompt } from "./src/question-prompt.js";
|
|
2
|
+
import * as prompts from "./src/prompts/index.js";
|
|
5
3
|
import { required } from "./src/validators.js";
|
|
6
4
|
import { PromptAgent } from "./src/prompt-agent.js";
|
|
7
|
-
import { MultiselectPrompt } from "./src/multiselect-prompt.js";
|
|
8
5
|
|
|
9
6
|
export async function question(message, options = {}) {
|
|
10
|
-
const questionPrompt = new QuestionPrompt(message, options);
|
|
7
|
+
const questionPrompt = new prompts.QuestionPrompt(message, options);
|
|
11
8
|
|
|
12
9
|
return questionPrompt.question();
|
|
13
10
|
}
|
|
14
11
|
|
|
15
12
|
export async function select(message, options) {
|
|
16
|
-
const selectPrompt = new SelectPrompt(message, options);
|
|
13
|
+
const selectPrompt = new prompts.SelectPrompt(message, options);
|
|
17
14
|
|
|
18
15
|
return selectPrompt.select();
|
|
19
16
|
}
|
|
20
17
|
|
|
21
18
|
export async function confirm(message, options) {
|
|
22
|
-
const confirmPrompt = new ConfirmPrompt(message, options);
|
|
19
|
+
const confirmPrompt = new prompts.ConfirmPrompt(message, options);
|
|
23
20
|
|
|
24
21
|
return confirmPrompt.confirm();
|
|
25
22
|
}
|
|
26
23
|
|
|
27
24
|
export async function multiselect(message, options) {
|
|
28
|
-
const multiselectPrompt = new MultiselectPrompt(message, options);
|
|
25
|
+
const multiselectPrompt = new prompts.MultiselectPrompt(message, options);
|
|
29
26
|
|
|
30
27
|
return multiselectPrompt.multiselect();
|
|
31
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topcli/prompts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Node.js user input library for command-line interfaces.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --no-warnings=ExperimentalWarning --loader=esmock --test test/",
|
|
@@ -31,10 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@topcli/wcwidth": "^1.0.1",
|
|
34
|
-
"
|
|
35
|
-
"is-unicode-supported": "^2.0.0",
|
|
36
|
-
"kleur": "^4.1.5",
|
|
37
|
-
"strip-ansi": "^7.1.0"
|
|
34
|
+
"kleur": "^4.1.5"
|
|
38
35
|
},
|
|
39
36
|
"engines": {
|
|
40
37
|
"node": ">=14"
|
package/src/constants.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Import Third-party Dependencies
|
|
2
2
|
import kleur from "kleur";
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
|
|
4
|
+
// Import Internal Dependencies
|
|
5
|
+
import { isUnicodeSupported } from "./utils.js";
|
|
5
6
|
|
|
6
7
|
const kMainSymbols = {
|
|
7
8
|
tick: "✔",
|
|
@@ -21,7 +22,7 @@ const kFallbackSymbols = {
|
|
|
21
22
|
active: "(+)",
|
|
22
23
|
inactive: "(-)"
|
|
23
24
|
};
|
|
24
|
-
const kSymbols = isUnicodeSupported() ||
|
|
25
|
+
const kSymbols = isUnicodeSupported() || process.env.CI ? kMainSymbols : kFallbackSymbols;
|
|
25
26
|
const kPointer = kleur.gray(kSymbols.pointer);
|
|
26
27
|
|
|
27
28
|
export const SYMBOLS = {
|
|
@@ -3,11 +3,9 @@ import { EOL } from "node:os";
|
|
|
3
3
|
import { createInterface } from "node:readline";
|
|
4
4
|
import { Writable } from "node:stream";
|
|
5
5
|
|
|
6
|
-
// Import Third-party Dependencies
|
|
7
|
-
import stripAnsi from "strip-ansi";
|
|
8
|
-
|
|
9
6
|
// Import Internal Dependencies
|
|
10
|
-
import {
|
|
7
|
+
import { stripAnsi } from "../utils.js";
|
|
8
|
+
import { PromptAgent } from "../prompt-agent.js";
|
|
11
9
|
|
|
12
10
|
export class AbstractPrompt {
|
|
13
11
|
constructor(message, input = process.stdin, output = process.stdout) {
|
|
@@ -3,12 +3,12 @@ import { EOL } from "node:os";
|
|
|
3
3
|
|
|
4
4
|
// Import Third-party Dependencies
|
|
5
5
|
import kleur from "kleur";
|
|
6
|
-
import stripAnsi from "strip-ansi";
|
|
7
6
|
import wcwidth from "@topcli/wcwidth";
|
|
8
7
|
|
|
9
8
|
// Import Internal Dependencies
|
|
10
|
-
import { AbstractPrompt } from "./abstract
|
|
11
|
-
import {
|
|
9
|
+
import { AbstractPrompt } from "./abstract.js";
|
|
10
|
+
import { stripAnsi } from "../utils.js";
|
|
11
|
+
import { SYMBOLS } from "../constants.js";
|
|
12
12
|
|
|
13
13
|
// CONSTANTS
|
|
14
14
|
const kToggleKeys = new Set([
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Import Internal Dependencies
|
|
2
|
+
import { QuestionPrompt } from "./question.js";
|
|
3
|
+
import { ConfirmPrompt } from "./confirm.js";
|
|
4
|
+
import { SelectPrompt } from "./select.js";
|
|
5
|
+
import { MultiselectPrompt } from "./multiselect.js";
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
QuestionPrompt,
|
|
9
|
+
ConfirmPrompt,
|
|
10
|
+
SelectPrompt,
|
|
11
|
+
MultiselectPrompt
|
|
12
|
+
};
|
|
@@ -3,12 +3,15 @@ import { EOL } from "node:os";
|
|
|
3
3
|
|
|
4
4
|
// Import Third-party Dependencies
|
|
5
5
|
import kleur from "kleur";
|
|
6
|
-
import stripAnsi from "strip-ansi";
|
|
7
6
|
import wcwidth from "@topcli/wcwidth";
|
|
8
7
|
|
|
9
8
|
// Import Internal Dependencies
|
|
10
|
-
import { AbstractPrompt } from "./abstract
|
|
11
|
-
import {
|
|
9
|
+
import { AbstractPrompt } from "./abstract.js";
|
|
10
|
+
import { stripAnsi } from "../utils.js";
|
|
11
|
+
import { SYMBOLS } from "../constants.js";
|
|
12
|
+
|
|
13
|
+
// CONSTANTS
|
|
14
|
+
const kRequiredChoiceProperties = ["label", "value"];
|
|
12
15
|
|
|
13
16
|
export class MultiselectPrompt extends AbstractPrompt {
|
|
14
17
|
#boundExitEvent = () => void 0;
|
|
@@ -18,11 +21,54 @@ export class MultiselectPrompt extends AbstractPrompt {
|
|
|
18
21
|
activeIndex = 0;
|
|
19
22
|
selectedIndexes = [];
|
|
20
23
|
questionMessage;
|
|
24
|
+
autocompleteValue = "";
|
|
21
25
|
|
|
22
26
|
get choices() {
|
|
23
27
|
return this.options.choices;
|
|
24
28
|
}
|
|
25
29
|
|
|
30
|
+
get filteredChoices() {
|
|
31
|
+
if (!(this.options.autocomplete && this.autocompleteValue.length > 0)) {
|
|
32
|
+
return this.choices;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const isCaseSensitive = this.options.caseSensitive;
|
|
36
|
+
const autocompleteValue = isCaseSensitive ? this.autocompleteValue : this.autocompleteValue.toLowerCase();
|
|
37
|
+
|
|
38
|
+
return this.choices.filter((choice) => this.#filterChoice(choice, autocompleteValue, isCaseSensitive));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#filterChoice(choice, autocompleteValue, isCaseSensitive) {
|
|
42
|
+
// eslint-disable-next-line no-nested-ternary
|
|
43
|
+
const choiceValue = typeof choice === "string" ?
|
|
44
|
+
(isCaseSensitive ? choice : choice.toLowerCase()) :
|
|
45
|
+
(isCaseSensitive ? choice.label : choice.label.toLowerCase());
|
|
46
|
+
|
|
47
|
+
if (autocompleteValue.includes(" ")) {
|
|
48
|
+
return this.#filterMultipleWords(choiceValue, autocompleteValue, isCaseSensitive);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return choiceValue.includes(autocompleteValue);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#filterMultipleWords(choiceValue, autocompleteValue, isCaseSensitive) {
|
|
55
|
+
return autocompleteValue.split(" ").every((word) => {
|
|
56
|
+
const wordValue = isCaseSensitive ? word : word.toLowerCase();
|
|
57
|
+
|
|
58
|
+
return choiceValue.includes(wordValue) || choiceValue.includes(autocompleteValue);
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get longestChoice() {
|
|
63
|
+
return Math.max(...this.filteredChoices.map((choice) => {
|
|
64
|
+
if (typeof choice === "string") {
|
|
65
|
+
return choice.length;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return choice.label.length;
|
|
69
|
+
}));
|
|
70
|
+
}
|
|
71
|
+
|
|
26
72
|
constructor(message, options) {
|
|
27
73
|
const {
|
|
28
74
|
stdin = process.stdin,
|
|
@@ -46,31 +92,27 @@ export class MultiselectPrompt extends AbstractPrompt {
|
|
|
46
92
|
throw new TypeError("Missing required param: choices");
|
|
47
93
|
}
|
|
48
94
|
|
|
49
|
-
this
|
|
95
|
+
this.#validators = validators;
|
|
96
|
+
|
|
97
|
+
for (const choice of choices) {
|
|
50
98
|
if (typeof choice === "string") {
|
|
51
|
-
|
|
99
|
+
continue;
|
|
52
100
|
}
|
|
53
101
|
|
|
54
|
-
const kRequiredChoiceProperties = ["label", "value"];
|
|
55
|
-
|
|
56
102
|
for (const prop of kRequiredChoiceProperties) {
|
|
57
103
|
if (!choice[prop]) {
|
|
58
104
|
this.destroy();
|
|
59
105
|
throw new TypeError(`Missing ${prop} for choice ${JSON.stringify(choice)}`);
|
|
60
106
|
}
|
|
61
107
|
}
|
|
62
|
-
|
|
63
|
-
return choice.label.length;
|
|
64
|
-
}));
|
|
65
|
-
|
|
66
|
-
this.#validators = validators;
|
|
108
|
+
}
|
|
67
109
|
|
|
68
110
|
if (!preSelectedChoices) {
|
|
69
111
|
return;
|
|
70
112
|
}
|
|
71
113
|
|
|
72
114
|
for (const choice of preSelectedChoices) {
|
|
73
|
-
const choiceIndex = this.
|
|
115
|
+
const choiceIndex = this.filteredChoices.findIndex((item) => {
|
|
74
116
|
if (typeof item === "string") {
|
|
75
117
|
return item === choice;
|
|
76
118
|
}
|
|
@@ -79,6 +121,7 @@ export class MultiselectPrompt extends AbstractPrompt {
|
|
|
79
121
|
});
|
|
80
122
|
|
|
81
123
|
if (choiceIndex === -1) {
|
|
124
|
+
this.destroy();
|
|
82
125
|
throw new Error(`Invalid pre-selected choice: ${choice.value ?? choice}`);
|
|
83
126
|
}
|
|
84
127
|
|
|
@@ -87,7 +130,7 @@ export class MultiselectPrompt extends AbstractPrompt {
|
|
|
87
130
|
}
|
|
88
131
|
|
|
89
132
|
#getFormattedChoice(choiceIndex) {
|
|
90
|
-
const choice = this.
|
|
133
|
+
const choice = this.filteredChoices[choiceIndex];
|
|
91
134
|
|
|
92
135
|
if (typeof choice === "string") {
|
|
93
136
|
return { value: choice, label: choice };
|
|
@@ -98,12 +141,12 @@ export class MultiselectPrompt extends AbstractPrompt {
|
|
|
98
141
|
|
|
99
142
|
#getVisibleChoices() {
|
|
100
143
|
const maxVisible = this.options.maxVisible || 8;
|
|
101
|
-
let startIndex = Math.min(this.
|
|
144
|
+
let startIndex = Math.min(this.filteredChoices.length - maxVisible, this.activeIndex - Math.floor(maxVisible / 2));
|
|
102
145
|
if (startIndex < 0) {
|
|
103
146
|
startIndex = 0;
|
|
104
147
|
}
|
|
105
148
|
|
|
106
|
-
const endIndex = Math.min(startIndex + maxVisible, this.
|
|
149
|
+
const endIndex = Math.min(startIndex + maxVisible, this.filteredChoices.length);
|
|
107
150
|
|
|
108
151
|
return { startIndex, endIndex };
|
|
109
152
|
}
|
|
@@ -112,12 +155,15 @@ export class MultiselectPrompt extends AbstractPrompt {
|
|
|
112
155
|
const { startIndex, endIndex } = this.#getVisibleChoices();
|
|
113
156
|
this.lastRender = { startIndex, endIndex };
|
|
114
157
|
|
|
158
|
+
if (this.options.autocomplete) {
|
|
159
|
+
this.write(`${SYMBOLS.Pointer} ${this.autocompleteValue}${EOL}`);
|
|
160
|
+
}
|
|
115
161
|
for (let choiceIndex = startIndex; choiceIndex < endIndex; choiceIndex++) {
|
|
116
162
|
const choice = this.#getFormattedChoice(choiceIndex);
|
|
117
163
|
const isChoiceActive = choiceIndex === this.activeIndex;
|
|
118
164
|
const isChoiceSelected = this.selectedIndexes.includes(choiceIndex);
|
|
119
165
|
const showPreviousChoicesArrow = startIndex > 0 && choiceIndex === startIndex;
|
|
120
|
-
const showNextChoicesArrow = endIndex < this.
|
|
166
|
+
const showNextChoicesArrow = endIndex < this.filteredChoices.length && choiceIndex === endIndex - 1;
|
|
121
167
|
|
|
122
168
|
let prefixArrow = " ";
|
|
123
169
|
if (showPreviousChoicesArrow) {
|
|
@@ -156,34 +202,30 @@ export class MultiselectPrompt extends AbstractPrompt {
|
|
|
156
202
|
|
|
157
203
|
#onKeypress(...args) {
|
|
158
204
|
const [resolve, render, _, key] = args;
|
|
159
|
-
|
|
160
205
|
if (key.name === "up") {
|
|
161
|
-
this.activeIndex = this.activeIndex === 0 ? this.
|
|
206
|
+
this.activeIndex = this.activeIndex === 0 ? this.filteredChoices.length - 1 : this.activeIndex - 1;
|
|
162
207
|
render();
|
|
163
208
|
}
|
|
164
209
|
else if (key.name === "down") {
|
|
165
|
-
this.activeIndex = this.activeIndex === this.
|
|
210
|
+
this.activeIndex = this.activeIndex === this.filteredChoices.length - 1 ? 0 : this.activeIndex + 1;
|
|
166
211
|
render();
|
|
167
212
|
}
|
|
168
|
-
else if (key.name === "a") {
|
|
169
|
-
|
|
213
|
+
else if (key.ctrl && key.name === "a") {
|
|
214
|
+
// eslint-disable-next-line max-len
|
|
215
|
+
this.selectedIndexes = this.selectedIndexes.length === this.filteredChoices.length ? [] : this.filteredChoices.map((_, index) => index);
|
|
170
216
|
render();
|
|
171
217
|
}
|
|
172
|
-
else if (key.name === "
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
else {
|
|
179
|
-
this.selectedIndexes.push(this.activeIndex);
|
|
180
|
-
}
|
|
181
|
-
|
|
218
|
+
else if (key.name === "right") {
|
|
219
|
+
this.selectedIndexes.push(this.activeIndex);
|
|
220
|
+
render();
|
|
221
|
+
}
|
|
222
|
+
else if (key.name === "left") {
|
|
223
|
+
this.selectedIndexes = this.selectedIndexes.filter((index) => index !== this.activeIndex);
|
|
182
224
|
render();
|
|
183
225
|
}
|
|
184
226
|
else if (key.name === "return") {
|
|
185
|
-
const labels = this.selectedIndexes.map((index) => this.
|
|
186
|
-
const values = this.selectedIndexes.map((index) => this.
|
|
227
|
+
const labels = this.selectedIndexes.map((index) => this.filteredChoices[index].label ?? this.filteredChoices[index]);
|
|
228
|
+
const values = this.selectedIndexes.map((index) => this.filteredChoices[index].value ?? this.filteredChoices[index]);
|
|
187
229
|
|
|
188
230
|
for (const validator of this.#validators) {
|
|
189
231
|
if (!validator.validate(values)) {
|
|
@@ -207,6 +249,17 @@ export class MultiselectPrompt extends AbstractPrompt {
|
|
|
207
249
|
resolve(values);
|
|
208
250
|
}
|
|
209
251
|
else {
|
|
252
|
+
if (!key.ctrl && this.options.autocomplete) {
|
|
253
|
+
// reset selected choices when user type
|
|
254
|
+
this.selectedIndexes = [];
|
|
255
|
+
this.activeIndex = 0;
|
|
256
|
+
if (key.name === "backspace" && this.autocompleteValue.length > 0) {
|
|
257
|
+
this.autocompleteValue = this.autocompleteValue.slice(0, -1);
|
|
258
|
+
}
|
|
259
|
+
else if (key.name !== "backspace") {
|
|
260
|
+
this.autocompleteValue += key.sequence;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
210
263
|
render();
|
|
211
264
|
}
|
|
212
265
|
}
|
|
@@ -236,6 +289,15 @@ export class MultiselectPrompt extends AbstractPrompt {
|
|
|
236
289
|
this.clearLastLine();
|
|
237
290
|
linesToClear--;
|
|
238
291
|
}
|
|
292
|
+
if (this.options.autocomplete) {
|
|
293
|
+
let linesToClear = Math.ceil(
|
|
294
|
+
wcwidth(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
|
|
295
|
+
);
|
|
296
|
+
while (linesToClear > 0) {
|
|
297
|
+
this.clearLastLine();
|
|
298
|
+
linesToClear--;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
239
301
|
}
|
|
240
302
|
|
|
241
303
|
if (clearRender) {
|
|
@@ -249,7 +311,8 @@ export class MultiselectPrompt extends AbstractPrompt {
|
|
|
249
311
|
}
|
|
250
312
|
|
|
251
313
|
if (error) {
|
|
252
|
-
this.stdout.
|
|
314
|
+
const linesToClear = Math.ceil(wcwidth(this.questionMessage) / this.stdout.columns) + 1;
|
|
315
|
+
this.stdout.moveCursor(0, -linesToClear);
|
|
253
316
|
this.stdout.clearScreenDown();
|
|
254
317
|
this.#showQuestion(error);
|
|
255
318
|
}
|
|
@@ -270,7 +333,8 @@ export class MultiselectPrompt extends AbstractPrompt {
|
|
|
270
333
|
|
|
271
334
|
#showQuestion(error = null) {
|
|
272
335
|
let hint = kleur.gray(
|
|
273
|
-
|
|
336
|
+
// eslint-disable-next-line max-len
|
|
337
|
+
`(Press ${kleur.bold("<Ctrl+A>")} to toggle all, ${kleur.bold("<Ctrl+Space>")} to select, ${kleur.bold("<Left/Right>")} to toggle, ${kleur.bold("<Return>")} to submit)`
|
|
274
338
|
);
|
|
275
339
|
if (error) {
|
|
276
340
|
hint += ` ${kleur.red().bold(`[${error}]`)}`;
|
|
@@ -3,12 +3,12 @@ import { EOL } from "node:os";
|
|
|
3
3
|
|
|
4
4
|
// Import Third-party Dependencies
|
|
5
5
|
import kleur from "kleur";
|
|
6
|
-
import stripAnsi from "strip-ansi";
|
|
7
6
|
import wcwidth from "@topcli/wcwidth";
|
|
8
7
|
|
|
9
8
|
// Import Internal Dependencies
|
|
10
|
-
import { AbstractPrompt } from "./abstract
|
|
11
|
-
import {
|
|
9
|
+
import { AbstractPrompt } from "./abstract.js";
|
|
10
|
+
import { stripAnsi } from "../utils.js";
|
|
11
|
+
import { SYMBOLS } from "../constants.js";
|
|
12
12
|
|
|
13
13
|
export class QuestionPrompt extends AbstractPrompt {
|
|
14
14
|
#validators;
|
|
@@ -3,12 +3,12 @@ import { EOL } from "node:os";
|
|
|
3
3
|
|
|
4
4
|
// Import Third-party Dependencies
|
|
5
5
|
import kleur from "kleur";
|
|
6
|
-
import stripAnsi from "strip-ansi";
|
|
7
6
|
import wcwidth from "@topcli/wcwidth";
|
|
8
7
|
|
|
9
8
|
// Import Internal Dependencies
|
|
10
|
-
import { AbstractPrompt } from "./abstract
|
|
11
|
-
import {
|
|
9
|
+
import { AbstractPrompt } from "./abstract.js";
|
|
10
|
+
import { stripAnsi } from "../utils.js";
|
|
11
|
+
import { SYMBOLS } from "../constants.js";
|
|
12
12
|
|
|
13
13
|
export class SelectPrompt extends AbstractPrompt {
|
|
14
14
|
#boundExitEvent = () => void 0;
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
// CONSTANTS
|
|
4
|
+
const kAnsiRegex = ansiRegex();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @see https://github.com/chalk/ansi-regex
|
|
8
|
+
*/
|
|
9
|
+
export function ansiRegex({onlyFirst = false} = {}) {
|
|
10
|
+
const pattern = [
|
|
11
|
+
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)',
|
|
12
|
+
'(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))'
|
|
13
|
+
].join('|');
|
|
14
|
+
|
|
15
|
+
return new RegExp(pattern, onlyFirst ? undefined : 'g');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {!string} string
|
|
20
|
+
* @returns {string}
|
|
21
|
+
*/
|
|
22
|
+
export function stripAnsi(string) {
|
|
23
|
+
return string.replace(kAnsiRegex, "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @see https://github.com/sindresorhus/is-unicode-supported
|
|
28
|
+
* @returns {boolean}
|
|
29
|
+
*/
|
|
30
|
+
export function isUnicodeSupported() {
|
|
31
|
+
if (process.platform !== 'win32') {
|
|
32
|
+
return process.env.TERM !== 'linux'; // Linux console (kernel)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return Boolean(process.env.WT_SESSION) // Windows Terminal
|
|
36
|
+
|| Boolean(process.env.TERMINUS_SUBLIME) // Terminus (<0.2.27)
|
|
37
|
+
|| process.env.ConEmuTask === '{cmd::Cmder}' // ConEmu and cmder
|
|
38
|
+
|| process.env.TERM_PROGRAM === 'Terminus-Sublime'
|
|
39
|
+
|| process.env.TERM_PROGRAM === 'vscode'
|
|
40
|
+
|| process.env.TERM === 'xterm-256color'
|
|
41
|
+
|| process.env.TERM === 'alacritty'
|
|
42
|
+
|| process.env.TERMINAL_EMULATOR === 'JetBrains-JediTerm';
|
|
43
|
+
}
|