@topcli/prompts 1.5.0 → 1.6.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 +3 -0
- package/index.d.ts +1 -0
- package/index.js +5 -8
- package/package.json +3 -4
- package/src/constants.js +4 -2
- package/src/{abstract-prompt.js → prompts/abstract.js} +2 -4
- package/src/{confirm-prompt.js → prompts/confirm.js} +13 -4
- package/src/prompts/index.js +12 -0
- package/src/prompts/multiselect.js +333 -0
- package/src/{question-prompt.js → prompts/question.js} +10 -3
- package/src/{select-prompt.js → prompts/select.js} +57 -32
- package/src/utils.js +43 -0
- package/src/multiselect-prompt.js +0 -257
package/README.md
CHANGED
|
@@ -108,6 +108,8 @@ const os = await multiselect('Choose OS', {
|
|
|
108
108
|
});
|
|
109
109
|
```
|
|
110
110
|
|
|
111
|
+
Use `autocomplete` to allow filtered choices. This can be usefull for a large list of choices.
|
|
112
|
+
|
|
111
113
|
### `confirm()`
|
|
112
114
|
|
|
113
115
|
```ts
|
|
@@ -177,6 +179,7 @@ export interface MultiselectOptions extends SharedOptions {
|
|
|
177
179
|
maxVisible?: number;
|
|
178
180
|
preSelectedChoices?: (Choice | string)[];
|
|
179
181
|
validators?: Validator[];
|
|
182
|
+
autocomplete?: boolean;
|
|
180
183
|
}
|
|
181
184
|
|
|
182
185
|
export interface ConfirmOptions extends SharedOptions {
|
package/index.d.ts
CHANGED
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.6.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/",
|
|
@@ -30,9 +30,8 @@
|
|
|
30
30
|
"esmock": "^2.5.2"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"
|
|
34
|
-
"kleur": "^4.1.5"
|
|
35
|
-
"strip-ansi": "^7.1.0"
|
|
33
|
+
"@topcli/wcwidth": "^1.0.1",
|
|
34
|
+
"kleur": "^4.1.5"
|
|
36
35
|
},
|
|
37
36
|
"engines": {
|
|
38
37
|
"node": ">=14"
|
package/src/constants.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Import Third-party Dependencies
|
|
2
2
|
import kleur from "kleur";
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
// Import Internal Dependencies
|
|
5
|
+
import { isUnicodeSupported } from "./utils.js";
|
|
4
6
|
|
|
5
7
|
const kMainSymbols = {
|
|
6
8
|
tick: "✔",
|
|
@@ -20,7 +22,7 @@ const kFallbackSymbols = {
|
|
|
20
22
|
active: "(+)",
|
|
21
23
|
inactive: "(-)"
|
|
22
24
|
};
|
|
23
|
-
const kSymbols = isUnicodeSupported() ? kMainSymbols : kFallbackSymbols;
|
|
25
|
+
const kSymbols = isUnicodeSupported() || process.env.CI ? kMainSymbols : kFallbackSymbols;
|
|
24
26
|
const kPointer = kleur.gray(kSymbols.pointer);
|
|
25
27
|
|
|
26
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,10 +3,12 @@ import { EOL } from "node:os";
|
|
|
3
3
|
|
|
4
4
|
// Import Third-party Dependencies
|
|
5
5
|
import kleur from "kleur";
|
|
6
|
+
import wcwidth from "@topcli/wcwidth";
|
|
6
7
|
|
|
7
8
|
// Import Internal Dependencies
|
|
8
|
-
import { AbstractPrompt } from "./abstract
|
|
9
|
-
import {
|
|
9
|
+
import { AbstractPrompt } from "./abstract.js";
|
|
10
|
+
import { stripAnsi } from "../utils.js";
|
|
11
|
+
import { SYMBOLS } from "../constants.js";
|
|
10
12
|
|
|
11
13
|
// CONSTANTS
|
|
12
14
|
const kToggleKeys = new Set([
|
|
@@ -65,7 +67,10 @@ export class ConfirmPrompt extends AbstractPrompt {
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
#onKeypress(resolve, value, key) {
|
|
68
|
-
this.stdout.moveCursor(
|
|
70
|
+
this.stdout.moveCursor(
|
|
71
|
+
-this.stdout.columns,
|
|
72
|
+
-Math.floor(wcwidth(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns)
|
|
73
|
+
);
|
|
69
74
|
this.stdout.clearScreenDown();
|
|
70
75
|
|
|
71
76
|
if (key.name === "return") {
|
|
@@ -92,7 +97,11 @@ export class ConfirmPrompt extends AbstractPrompt {
|
|
|
92
97
|
}
|
|
93
98
|
|
|
94
99
|
#onQuestionAnswer() {
|
|
95
|
-
this.
|
|
100
|
+
this.stdout.moveCursor(
|
|
101
|
+
-this.stdout.columns,
|
|
102
|
+
-(Math.floor(wcwidth(stripAnsi(this.#getQuestionQuery())) / this.stdout.columns) || 1)
|
|
103
|
+
);
|
|
104
|
+
this.stdout.clearScreenDown();
|
|
96
105
|
this.write(`${this.selectedValue ? SYMBOLS.Tick : SYMBOLS.Cross} ${kleur.bold(this.message)}${EOL}`);
|
|
97
106
|
}
|
|
98
107
|
|
|
@@ -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
|
+
};
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// Import Node.js Dependencies
|
|
2
|
+
import { EOL } from "node:os";
|
|
3
|
+
|
|
4
|
+
// Import Third-party Dependencies
|
|
5
|
+
import kleur from "kleur";
|
|
6
|
+
import wcwidth from "@topcli/wcwidth";
|
|
7
|
+
|
|
8
|
+
// Import Internal Dependencies
|
|
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"];
|
|
15
|
+
|
|
16
|
+
export class MultiselectPrompt extends AbstractPrompt {
|
|
17
|
+
#boundExitEvent = () => void 0;
|
|
18
|
+
#boundKeyPressEvent = () => void 0;
|
|
19
|
+
#validators;
|
|
20
|
+
|
|
21
|
+
activeIndex = 0;
|
|
22
|
+
selectedIndexes = [];
|
|
23
|
+
questionMessage;
|
|
24
|
+
autocompleteValue = "";
|
|
25
|
+
|
|
26
|
+
get choices() {
|
|
27
|
+
return this.options.choices;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
get filteredChoices() {
|
|
31
|
+
return this.options.autocomplete && this.autocompleteValue.length > 0 ? this.choices.filter((choice) => {
|
|
32
|
+
if (typeof choice === "string") {
|
|
33
|
+
if (this.autocompleteValue.includes(" ")) {
|
|
34
|
+
return this.autocompleteValue.split(" ").every((word) => choice.includes(word)) ||
|
|
35
|
+
choice.includes(this.autocompleteValue);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return choice.includes(this.autocompleteValue);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (this.autocompleteValue.includes(" ")) {
|
|
42
|
+
return this.autocompleteValue.split(" ").every((word) => choice.label.includes(word)) ||
|
|
43
|
+
choice.label.includes(this.autocompleteValue);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return choice.label.includes(this.autocompleteValue);
|
|
47
|
+
}) : this.choices;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get longestChoice() {
|
|
51
|
+
return Math.max(...this.filteredChoices.map((choice) => {
|
|
52
|
+
if (typeof choice === "string") {
|
|
53
|
+
return choice.length;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return choice.label.length;
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
constructor(message, options) {
|
|
61
|
+
const {
|
|
62
|
+
stdin = process.stdin,
|
|
63
|
+
stdout = process.stdout,
|
|
64
|
+
choices,
|
|
65
|
+
preSelectedChoices,
|
|
66
|
+
validators = []
|
|
67
|
+
} = options ?? {};
|
|
68
|
+
|
|
69
|
+
super(message, stdin, stdout);
|
|
70
|
+
|
|
71
|
+
if (!options) {
|
|
72
|
+
this.destroy();
|
|
73
|
+
throw new TypeError("Missing required options");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.options = options;
|
|
77
|
+
|
|
78
|
+
if (!choices?.length) {
|
|
79
|
+
this.destroy();
|
|
80
|
+
throw new TypeError("Missing required param: choices");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
this.#validators = validators;
|
|
84
|
+
|
|
85
|
+
for (const choice of choices) {
|
|
86
|
+
if (typeof choice === "string") {
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const prop of kRequiredChoiceProperties) {
|
|
91
|
+
if (!choice[prop]) {
|
|
92
|
+
this.destroy();
|
|
93
|
+
throw new TypeError(`Missing ${prop} for choice ${JSON.stringify(choice)}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!preSelectedChoices) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
for (const choice of preSelectedChoices) {
|
|
103
|
+
const choiceIndex = this.filteredChoices.findIndex((item) => {
|
|
104
|
+
if (typeof item === "string") {
|
|
105
|
+
return item === choice;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return item.value === choice;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (choiceIndex === -1) {
|
|
112
|
+
throw new Error(`Invalid pre-selected choice: ${choice.value ?? choice}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
this.selectedIndexes.push(choiceIndex);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#getFormattedChoice(choiceIndex) {
|
|
120
|
+
const choice = this.filteredChoices[choiceIndex];
|
|
121
|
+
|
|
122
|
+
if (typeof choice === "string") {
|
|
123
|
+
return { value: choice, label: choice };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return choice;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#getVisibleChoices() {
|
|
130
|
+
const maxVisible = this.options.maxVisible || 8;
|
|
131
|
+
let startIndex = Math.min(this.filteredChoices.length - maxVisible, this.activeIndex - Math.floor(maxVisible / 2));
|
|
132
|
+
if (startIndex < 0) {
|
|
133
|
+
startIndex = 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const endIndex = Math.min(startIndex + maxVisible, this.filteredChoices.length);
|
|
137
|
+
|
|
138
|
+
return { startIndex, endIndex };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
#showChoices() {
|
|
142
|
+
const { startIndex, endIndex } = this.#getVisibleChoices();
|
|
143
|
+
this.lastRender = { startIndex, endIndex };
|
|
144
|
+
|
|
145
|
+
if (this.options.autocomplete) {
|
|
146
|
+
this.write(`${SYMBOLS.Pointer} ${this.autocompleteValue}${EOL}`);
|
|
147
|
+
}
|
|
148
|
+
for (let choiceIndex = startIndex; choiceIndex < endIndex; choiceIndex++) {
|
|
149
|
+
const choice = this.#getFormattedChoice(choiceIndex);
|
|
150
|
+
const isChoiceActive = choiceIndex === this.activeIndex;
|
|
151
|
+
const isChoiceSelected = this.selectedIndexes.includes(choiceIndex);
|
|
152
|
+
const showPreviousChoicesArrow = startIndex > 0 && choiceIndex === startIndex;
|
|
153
|
+
const showNextChoicesArrow = endIndex < this.filteredChoices.length && choiceIndex === endIndex - 1;
|
|
154
|
+
|
|
155
|
+
let prefixArrow = " ";
|
|
156
|
+
if (showPreviousChoicesArrow) {
|
|
157
|
+
prefixArrow = SYMBOLS.Previous + " ";
|
|
158
|
+
}
|
|
159
|
+
else if (showNextChoicesArrow) {
|
|
160
|
+
prefixArrow = SYMBOLS.Next + " ";
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const prefix = `${prefixArrow}${isChoiceSelected ? SYMBOLS.Active : SYMBOLS.Inactive}`;
|
|
164
|
+
const formattedLabel = choice.label.padEnd(
|
|
165
|
+
this.longestChoice < 10 ? this.longestChoice : 0
|
|
166
|
+
);
|
|
167
|
+
const formattedDescription = choice.description ? ` - ${choice.description}` : "";
|
|
168
|
+
const color = isChoiceActive ? kleur.white().bold : kleur.gray;
|
|
169
|
+
const str = `${prefix} ${color(`${formattedLabel}${formattedDescription}`)}${EOL}`;
|
|
170
|
+
|
|
171
|
+
this.write(str);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#showAnsweredQuestion(choices, isAgentAnswer = false) {
|
|
176
|
+
const prefixSymbol = this.selectedIndexes.length === 0 && !isAgentAnswer ? SYMBOLS.Cross : SYMBOLS.Tick;
|
|
177
|
+
const prefix = `${prefixSymbol} ${kleur.bold(this.message)} ${SYMBOLS.Pointer}`;
|
|
178
|
+
const formattedChoice = kleur.yellow(choices);
|
|
179
|
+
|
|
180
|
+
this.write(`${prefix}${choices ? ` ${formattedChoice}` : ""}${EOL}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#onProcessExit() {
|
|
184
|
+
this.stdin.off("keypress", this.#boundKeyPressEvent);
|
|
185
|
+
this.stdout.moveCursor(-this.stdout.columns, 0);
|
|
186
|
+
this.stdout.clearScreenDown();
|
|
187
|
+
this.write(SYMBOLS.ShowCursor);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
#onKeypress(...args) {
|
|
191
|
+
const [resolve, render, _, key] = args;
|
|
192
|
+
if (key.name === "up") {
|
|
193
|
+
this.activeIndex = this.activeIndex === 0 ? this.filteredChoices.length - 1 : this.activeIndex - 1;
|
|
194
|
+
render();
|
|
195
|
+
}
|
|
196
|
+
else if (key.name === "down") {
|
|
197
|
+
this.activeIndex = this.activeIndex === this.filteredChoices.length - 1 ? 0 : this.activeIndex + 1;
|
|
198
|
+
render();
|
|
199
|
+
}
|
|
200
|
+
else if (key.ctrl && key.name === "a") {
|
|
201
|
+
// eslint-disable-next-line max-len
|
|
202
|
+
this.selectedIndexes = this.selectedIndexes.length === this.filteredChoices.length ? [] : this.filteredChoices.map((_, index) => index);
|
|
203
|
+
render();
|
|
204
|
+
}
|
|
205
|
+
else if (key.name === "right") {
|
|
206
|
+
this.selectedIndexes.push(this.activeIndex);
|
|
207
|
+
render();
|
|
208
|
+
}
|
|
209
|
+
else if (key.name === "left") {
|
|
210
|
+
this.selectedIndexes = this.selectedIndexes.filter((index) => index !== this.activeIndex);
|
|
211
|
+
render();
|
|
212
|
+
}
|
|
213
|
+
else if (key.name === "return") {
|
|
214
|
+
const labels = this.selectedIndexes.map((index) => this.filteredChoices[index].label ?? this.filteredChoices[index]);
|
|
215
|
+
const values = this.selectedIndexes.map((index) => this.filteredChoices[index].value ?? this.filteredChoices[index]);
|
|
216
|
+
|
|
217
|
+
for (const validator of this.#validators) {
|
|
218
|
+
if (!validator.validate(values)) {
|
|
219
|
+
const error = validator.error(values);
|
|
220
|
+
render({ error });
|
|
221
|
+
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
render({ clearRender: true });
|
|
227
|
+
|
|
228
|
+
this.#showAnsweredQuestion(labels.join(", "));
|
|
229
|
+
|
|
230
|
+
this.write(SYMBOLS.ShowCursor);
|
|
231
|
+
this.destroy();
|
|
232
|
+
|
|
233
|
+
this.#onProcessExit();
|
|
234
|
+
process.off("exit", this.#boundExitEvent);
|
|
235
|
+
|
|
236
|
+
resolve(values);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
if (!key.ctrl && this.options.autocomplete) {
|
|
240
|
+
// reset selected choices when user type
|
|
241
|
+
this.selectedIndexes = [];
|
|
242
|
+
this.activeIndex = 0;
|
|
243
|
+
if (key.name === "backspace" && this.autocompleteValue.length > 0) {
|
|
244
|
+
this.autocompleteValue = this.autocompleteValue.slice(0, -1);
|
|
245
|
+
}
|
|
246
|
+
else if (key.name !== "backspace") {
|
|
247
|
+
this.autocompleteValue += key.sequence;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
render();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async multiselect() {
|
|
255
|
+
if (this.agent.nextAnswers.length > 0) {
|
|
256
|
+
const answer = this.agent.nextAnswers.shift();
|
|
257
|
+
this.#showAnsweredQuestion(answer, true);
|
|
258
|
+
this.destroy();
|
|
259
|
+
|
|
260
|
+
return answer;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
this.write(SYMBOLS.HideCursor);
|
|
264
|
+
this.#showQuestion();
|
|
265
|
+
|
|
266
|
+
const render = (options = {}) => {
|
|
267
|
+
const {
|
|
268
|
+
initialRender = false,
|
|
269
|
+
clearRender = false,
|
|
270
|
+
error = null
|
|
271
|
+
} = options;
|
|
272
|
+
|
|
273
|
+
if (!initialRender) {
|
|
274
|
+
let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
|
|
275
|
+
while (linesToClear > 0) {
|
|
276
|
+
this.clearLastLine();
|
|
277
|
+
linesToClear--;
|
|
278
|
+
}
|
|
279
|
+
if (this.options.autocomplete) {
|
|
280
|
+
let linesToClear = Math.ceil(
|
|
281
|
+
wcwidth(`${SYMBOLS.Pointer} ${this.autocompleteValue}`) / this.stdout.columns
|
|
282
|
+
);
|
|
283
|
+
while (linesToClear > 0) {
|
|
284
|
+
this.clearLastLine();
|
|
285
|
+
linesToClear--;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (clearRender) {
|
|
291
|
+
const questionLineCount = Math.ceil(
|
|
292
|
+
wcwidth(stripAnsi(this.questionMessage)) / this.stdout.columns
|
|
293
|
+
);
|
|
294
|
+
this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
|
|
295
|
+
this.stdout.clearScreenDown();
|
|
296
|
+
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (error) {
|
|
301
|
+
const linesToClear = Math.ceil(wcwidth(this.questionMessage) / this.stdout.columns) + 1;
|
|
302
|
+
this.stdout.moveCursor(0, -linesToClear);
|
|
303
|
+
this.stdout.clearScreenDown();
|
|
304
|
+
this.#showQuestion(error);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
this.#showChoices();
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
render({ initialRender: true });
|
|
311
|
+
|
|
312
|
+
return new Promise((resolve) => {
|
|
313
|
+
this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
|
|
314
|
+
this.stdin.on("keypress", this.#boundKeyPressEvent);
|
|
315
|
+
|
|
316
|
+
this.#boundExitEvent = this.#onProcessExit.bind(this);
|
|
317
|
+
process.once("exit", this.#boundExitEvent);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
#showQuestion(error = null) {
|
|
322
|
+
let hint = kleur.gray(
|
|
323
|
+
// eslint-disable-next-line max-len
|
|
324
|
+
`(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)`
|
|
325
|
+
);
|
|
326
|
+
if (error) {
|
|
327
|
+
hint += ` ${kleur.red().bold(`[${error}]`)}`;
|
|
328
|
+
}
|
|
329
|
+
this.questionMessage = `${SYMBOLS.QuestionMark} ${kleur.bold(this.message)} ${hint}`;
|
|
330
|
+
|
|
331
|
+
this.write(`${this.questionMessage}${EOL}`);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
@@ -3,10 +3,12 @@ import { EOL } from "node:os";
|
|
|
3
3
|
|
|
4
4
|
// Import Third-party Dependencies
|
|
5
5
|
import kleur from "kleur";
|
|
6
|
+
import wcwidth from "@topcli/wcwidth";
|
|
6
7
|
|
|
7
8
|
// Import Internal Dependencies
|
|
8
|
-
import { AbstractPrompt } from "./abstract
|
|
9
|
-
import {
|
|
9
|
+
import { AbstractPrompt } from "./abstract.js";
|
|
10
|
+
import { stripAnsi } from "../utils.js";
|
|
11
|
+
import { SYMBOLS } from "../constants.js";
|
|
10
12
|
|
|
11
13
|
export class QuestionPrompt extends AbstractPrompt {
|
|
12
14
|
#validators;
|
|
@@ -64,7 +66,12 @@ export class QuestionPrompt extends AbstractPrompt {
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
#onQuestionAnswer() {
|
|
67
|
-
|
|
69
|
+
const questionLineCount = Math.ceil(
|
|
70
|
+
wcwidth(stripAnsi(this.#getQuestionQuery() + this.answer)) / this.stdout.columns
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
this.stdout.moveCursor(-this.stdout.columns, -questionLineCount);
|
|
74
|
+
this.stdout.clearScreenDown();
|
|
68
75
|
|
|
69
76
|
for (const validator of this.#validators) {
|
|
70
77
|
if (!validator.validate(this.answer)) {
|
|
@@ -3,12 +3,16 @@ import { EOL } from "node:os";
|
|
|
3
3
|
|
|
4
4
|
// Import Third-party Dependencies
|
|
5
5
|
import kleur from "kleur";
|
|
6
|
+
import wcwidth from "@topcli/wcwidth";
|
|
6
7
|
|
|
7
8
|
// Import Internal Dependencies
|
|
8
|
-
import { AbstractPrompt } from "./abstract
|
|
9
|
-
import {
|
|
9
|
+
import { AbstractPrompt } from "./abstract.js";
|
|
10
|
+
import { stripAnsi } from "../utils.js";
|
|
11
|
+
import { SYMBOLS } from "../constants.js";
|
|
10
12
|
|
|
11
13
|
export class SelectPrompt extends AbstractPrompt {
|
|
14
|
+
#boundExitEvent = () => void 0;
|
|
15
|
+
#boundKeyPressEvent = () => void 0;
|
|
12
16
|
activeIndex = 0;
|
|
13
17
|
|
|
14
18
|
get choices() {
|
|
@@ -113,6 +117,46 @@ export class SelectPrompt extends AbstractPrompt {
|
|
|
113
117
|
this.write(`${prefix} ${formattedChoice}${EOL}`);
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
#onProcessExit() {
|
|
121
|
+
this.stdin.off("keypress", this.#boundKeyPressEvent);
|
|
122
|
+
this.stdout.moveCursor(-this.stdout.columns, 0);
|
|
123
|
+
this.stdout.clearScreenDown();
|
|
124
|
+
this.write(SYMBOLS.ShowCursor);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#onKeypress(...args) {
|
|
128
|
+
const [resolve, render, _, key] = args;
|
|
129
|
+
|
|
130
|
+
if (key.name === "up") {
|
|
131
|
+
this.activeIndex = this.activeIndex === 0 ? this.choices.length - 1 : this.activeIndex - 1;
|
|
132
|
+
render();
|
|
133
|
+
}
|
|
134
|
+
else if (key.name === "down") {
|
|
135
|
+
this.activeIndex = this.activeIndex === this.choices.length - 1 ? 0 : this.activeIndex + 1;
|
|
136
|
+
render();
|
|
137
|
+
}
|
|
138
|
+
else if (key.name === "return") {
|
|
139
|
+
render({ clearRender: true });
|
|
140
|
+
|
|
141
|
+
const currentChoice = this.choices[this.activeIndex];
|
|
142
|
+
const value = currentChoice.value ?? currentChoice;
|
|
143
|
+
|
|
144
|
+
if (!this.options.ignoreValues?.includes(value)) {
|
|
145
|
+
this.#showAnsweredQuestion(currentChoice);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this.write(SYMBOLS.ShowCursor);
|
|
149
|
+
this.destroy();
|
|
150
|
+
|
|
151
|
+
this.#onProcessExit();
|
|
152
|
+
process.off("exit", this.#boundExitEvent);
|
|
153
|
+
resolve(value);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
render();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
116
160
|
async select() {
|
|
117
161
|
if (this.agent.nextAnswers.length > 0) {
|
|
118
162
|
const answer = this.agent.nextAnswers.shift();
|
|
@@ -140,7 +184,11 @@ export class SelectPrompt extends AbstractPrompt {
|
|
|
140
184
|
}
|
|
141
185
|
|
|
142
186
|
if (clearRender) {
|
|
143
|
-
|
|
187
|
+
const questionLineCount = Math.ceil(
|
|
188
|
+
wcwidth(stripAnsi(this.questionMessage)) / this.stdout.columns
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
this.stdout.moveCursor(-this.stdout.columns, -(1 + questionLineCount));
|
|
144
192
|
this.stdout.clearScreenDown();
|
|
145
193
|
|
|
146
194
|
return;
|
|
@@ -152,39 +200,16 @@ export class SelectPrompt extends AbstractPrompt {
|
|
|
152
200
|
render({ initialRender: true });
|
|
153
201
|
|
|
154
202
|
return new Promise((resolve) => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
this.activeIndex = this.activeIndex === 0 ? this.choices.length - 1 : this.activeIndex - 1;
|
|
158
|
-
render();
|
|
159
|
-
}
|
|
160
|
-
else if (key.name === "down") {
|
|
161
|
-
this.activeIndex = this.activeIndex === this.choices.length - 1 ? 0 : this.activeIndex + 1;
|
|
162
|
-
render();
|
|
163
|
-
}
|
|
164
|
-
else if (key.name === "return") {
|
|
165
|
-
this.stdin.off("keypress", onKeypress);
|
|
166
|
-
|
|
167
|
-
render({ clearRender: true });
|
|
168
|
-
|
|
169
|
-
const currentChoice = this.choices[this.activeIndex];
|
|
170
|
-
const value = currentChoice.value ?? currentChoice;
|
|
171
|
-
|
|
172
|
-
if (!this.options.ignoreValues?.includes(value)) {
|
|
173
|
-
this.#showAnsweredQuestion(currentChoice);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
this.write(SYMBOLS.ShowCursor);
|
|
177
|
-
this.destroy();
|
|
178
|
-
|
|
179
|
-
resolve(value);
|
|
180
|
-
}
|
|
181
|
-
};
|
|
203
|
+
this.#boundKeyPressEvent = this.#onKeypress.bind(this, resolve, render);
|
|
204
|
+
this.stdin.on("keypress", this.#boundKeyPressEvent);
|
|
182
205
|
|
|
183
|
-
this.
|
|
206
|
+
this.#boundExitEvent = this.#onProcessExit.bind(this);
|
|
207
|
+
process.once("exit", this.#boundExitEvent);
|
|
184
208
|
});
|
|
185
209
|
}
|
|
186
210
|
|
|
187
211
|
#showQuestion() {
|
|
188
|
-
this.
|
|
212
|
+
this.questionMessage = `${SYMBOLS.QuestionMark} ${kleur.bold(this.message)}`;
|
|
213
|
+
this.write(`${this.questionMessage}${EOL}`);
|
|
189
214
|
}
|
|
190
215
|
}
|
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
|
+
}
|
|
@@ -1,257 +0,0 @@
|
|
|
1
|
-
// Import Node.js Dependencies
|
|
2
|
-
import { EOL } from "node:os";
|
|
3
|
-
|
|
4
|
-
// Import Third-party Dependencies
|
|
5
|
-
import kleur from "kleur";
|
|
6
|
-
|
|
7
|
-
// Import Internal Dependencies
|
|
8
|
-
import { AbstractPrompt } from "./abstract-prompt.js";
|
|
9
|
-
import { SYMBOLS } from "./constants.js";
|
|
10
|
-
|
|
11
|
-
export class MultiselectPrompt extends AbstractPrompt {
|
|
12
|
-
#validators;
|
|
13
|
-
|
|
14
|
-
activeIndex = 0;
|
|
15
|
-
selectedIndexes = [];
|
|
16
|
-
|
|
17
|
-
get choices() {
|
|
18
|
-
return this.options.choices;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
constructor(message, options) {
|
|
22
|
-
const {
|
|
23
|
-
stdin = process.stdin,
|
|
24
|
-
stdout = process.stdout,
|
|
25
|
-
choices,
|
|
26
|
-
preSelectedChoices,
|
|
27
|
-
validators = []
|
|
28
|
-
} = options ?? {};
|
|
29
|
-
|
|
30
|
-
super(message, stdin, stdout);
|
|
31
|
-
|
|
32
|
-
if (!options) {
|
|
33
|
-
this.destroy();
|
|
34
|
-
throw new TypeError("Missing required options");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
this.options = options;
|
|
38
|
-
|
|
39
|
-
if (!choices?.length) {
|
|
40
|
-
this.destroy();
|
|
41
|
-
throw new TypeError("Missing required param: choices");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
this.longestChoice = Math.max(...choices.map((choice) => {
|
|
45
|
-
if (typeof choice === "string") {
|
|
46
|
-
return choice.length;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const kRequiredChoiceProperties = ["label", "value"];
|
|
50
|
-
|
|
51
|
-
for (const prop of kRequiredChoiceProperties) {
|
|
52
|
-
if (!choice[prop]) {
|
|
53
|
-
this.destroy();
|
|
54
|
-
throw new TypeError(`Missing ${prop} for choice ${JSON.stringify(choice)}`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return choice.label.length;
|
|
59
|
-
}));
|
|
60
|
-
|
|
61
|
-
this.#validators = validators;
|
|
62
|
-
|
|
63
|
-
if (!preSelectedChoices) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
for (const choice of preSelectedChoices) {
|
|
68
|
-
const choiceIndex = this.choices.findIndex((item) => {
|
|
69
|
-
if (typeof item === "string") {
|
|
70
|
-
return item === choice;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return item.value === choice;
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
if (choiceIndex === -1) {
|
|
77
|
-
throw new Error(`Invalid pre-selected choice: ${choice.value ?? choice}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
this.selectedIndexes.push(choiceIndex);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
#getFormattedChoice(choiceIndex) {
|
|
85
|
-
const choice = this.choices[choiceIndex];
|
|
86
|
-
|
|
87
|
-
if (typeof choice === "string") {
|
|
88
|
-
return { value: choice, label: choice };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return choice;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
#getVisibleChoices() {
|
|
95
|
-
const maxVisible = this.options.maxVisible || 8;
|
|
96
|
-
let startIndex = Math.min(this.choices.length - maxVisible, this.activeIndex - Math.floor(maxVisible / 2));
|
|
97
|
-
if (startIndex < 0) {
|
|
98
|
-
startIndex = 0;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const endIndex = Math.min(startIndex + maxVisible, this.choices.length);
|
|
102
|
-
|
|
103
|
-
return { startIndex, endIndex };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
#showChoices() {
|
|
107
|
-
const { startIndex, endIndex } = this.#getVisibleChoices();
|
|
108
|
-
this.lastRender = { startIndex, endIndex };
|
|
109
|
-
|
|
110
|
-
for (let choiceIndex = startIndex; choiceIndex < endIndex; choiceIndex++) {
|
|
111
|
-
const choice = this.#getFormattedChoice(choiceIndex);
|
|
112
|
-
const isChoiceActive = choiceIndex === this.activeIndex;
|
|
113
|
-
const isChoiceSelected = this.selectedIndexes.includes(choiceIndex);
|
|
114
|
-
const showPreviousChoicesArrow = startIndex > 0 && choiceIndex === startIndex;
|
|
115
|
-
const showNextChoicesArrow = endIndex < this.choices.length && choiceIndex === endIndex - 1;
|
|
116
|
-
|
|
117
|
-
let prefixArrow = " ";
|
|
118
|
-
if (showPreviousChoicesArrow) {
|
|
119
|
-
prefixArrow = SYMBOLS.Previous + " ";
|
|
120
|
-
}
|
|
121
|
-
else if (showNextChoicesArrow) {
|
|
122
|
-
prefixArrow = SYMBOLS.Next + " ";
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const prefix = `${prefixArrow}${isChoiceSelected ? SYMBOLS.Active : SYMBOLS.Inactive}`;
|
|
126
|
-
const formattedLabel = choice.label.padEnd(
|
|
127
|
-
this.longestChoice < 10 ? this.longestChoice : 0
|
|
128
|
-
);
|
|
129
|
-
const formattedDescription = choice.description ? ` - ${choice.description}` : "";
|
|
130
|
-
const color = isChoiceActive ? kleur.white().bold : kleur.gray;
|
|
131
|
-
const str = `${prefix} ${color(`${formattedLabel}${formattedDescription}`)}${EOL}`;
|
|
132
|
-
|
|
133
|
-
this.write(str);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
#showAnsweredQuestion(choices, isAgentAnswer = false) {
|
|
138
|
-
const prefixSymbol = this.selectedIndexes.length === 0 && !isAgentAnswer ? SYMBOLS.Cross : SYMBOLS.Tick;
|
|
139
|
-
const prefix = `${prefixSymbol} ${kleur.bold(this.message)} ${SYMBOLS.Pointer}`;
|
|
140
|
-
const formattedChoice = kleur.yellow(choices);
|
|
141
|
-
|
|
142
|
-
this.write(`${prefix}${choices ? ` ${formattedChoice}` : ""}${EOL}`);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
async multiselect() {
|
|
146
|
-
if (this.agent.nextAnswers.length > 0) {
|
|
147
|
-
const answer = this.agent.nextAnswers.shift();
|
|
148
|
-
this.#showAnsweredQuestion(answer, true);
|
|
149
|
-
this.destroy();
|
|
150
|
-
|
|
151
|
-
return answer;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
this.write(SYMBOLS.HideCursor);
|
|
155
|
-
this.#showQuestion();
|
|
156
|
-
|
|
157
|
-
const render = (options = {}) => {
|
|
158
|
-
const {
|
|
159
|
-
initialRender = false,
|
|
160
|
-
clearRender = false,
|
|
161
|
-
error = null
|
|
162
|
-
} = options;
|
|
163
|
-
|
|
164
|
-
if (!initialRender) {
|
|
165
|
-
let linesToClear = this.lastRender.endIndex - this.lastRender.startIndex;
|
|
166
|
-
while (linesToClear > 0) {
|
|
167
|
-
this.clearLastLine();
|
|
168
|
-
linesToClear--;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (clearRender) {
|
|
173
|
-
this.stdout.moveCursor(0, -2);
|
|
174
|
-
this.stdout.clearScreenDown();
|
|
175
|
-
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (error) {
|
|
180
|
-
this.stdout.moveCursor(0, -2);
|
|
181
|
-
this.stdout.clearScreenDown();
|
|
182
|
-
this.#showQuestion(error);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
this.#showChoices();
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
render({ initialRender: true });
|
|
189
|
-
|
|
190
|
-
return new Promise((resolve) => {
|
|
191
|
-
const onKeypress = (value, key) => {
|
|
192
|
-
if (key.name === "up") {
|
|
193
|
-
this.activeIndex = this.activeIndex === 0 ? this.choices.length - 1 : this.activeIndex - 1;
|
|
194
|
-
render();
|
|
195
|
-
}
|
|
196
|
-
else if (key.name === "down") {
|
|
197
|
-
this.activeIndex = this.activeIndex === this.choices.length - 1 ? 0 : this.activeIndex + 1;
|
|
198
|
-
render();
|
|
199
|
-
}
|
|
200
|
-
else if (key.name === "a") {
|
|
201
|
-
this.selectedIndexes = this.selectedIndexes.length === this.choices.length ? [] : this.choices.map((_, index) => index);
|
|
202
|
-
render();
|
|
203
|
-
}
|
|
204
|
-
else if (key.name === "space") {
|
|
205
|
-
const isChoiceSelected = this.selectedIndexes.includes(this.activeIndex);
|
|
206
|
-
|
|
207
|
-
if (isChoiceSelected) {
|
|
208
|
-
this.selectedIndexes = this.selectedIndexes.filter((index) => index !== this.activeIndex);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
this.selectedIndexes.push(this.activeIndex);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
render();
|
|
215
|
-
}
|
|
216
|
-
else if (key.name === "return") {
|
|
217
|
-
const labels = this.selectedIndexes.map((index) => this.choices[index].label ?? this.choices[index]);
|
|
218
|
-
const values = this.selectedIndexes.map((index) => this.choices[index].value ?? this.choices[index]);
|
|
219
|
-
|
|
220
|
-
for (const validator of this.#validators) {
|
|
221
|
-
if (!validator.validate(values)) {
|
|
222
|
-
const error = validator.error(values);
|
|
223
|
-
render({ error });
|
|
224
|
-
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
this.stdin.off("keypress", onKeypress);
|
|
230
|
-
|
|
231
|
-
render({ clearRender: true });
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
this.#showAnsweredQuestion(labels.join(", "));
|
|
235
|
-
|
|
236
|
-
this.write(SYMBOLS.ShowCursor);
|
|
237
|
-
this.destroy();
|
|
238
|
-
|
|
239
|
-
resolve(values);
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
this.stdin.on("keypress", onKeypress);
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
#showQuestion(error = null) {
|
|
248
|
-
let hint = kleur.gray(
|
|
249
|
-
`(Press ${kleur.bold("<a>")} to toggle all, ${kleur.bold("<space>")} to select, ${kleur.bold("<return>")} to submit)`
|
|
250
|
-
);
|
|
251
|
-
if (error) {
|
|
252
|
-
hint += ` ${kleur.red().bold(`[${error}]`)}`;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
this.write(`${SYMBOLS.QuestionMark} ${kleur.bold(this.message)} ${hint}${EOL}`);
|
|
256
|
-
}
|
|
257
|
-
}
|