@reliverse/rempts-core 1.6.1
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/LICENSE +21 -0
- package/README.md +102 -0
- package/bin/core-impl/anykey/anykey-mod.d.ts +12 -0
- package/bin/core-impl/anykey/anykey-mod.js +125 -0
- package/bin/core-impl/date/date.d.ts +2 -0
- package/bin/core-impl/date/date.js +236 -0
- package/bin/core-impl/editor/editor-mod.d.ts +25 -0
- package/bin/core-impl/editor/editor-mod.js +896 -0
- package/bin/core-impl/figures/figures-mod.d.ts +233 -0
- package/bin/core-impl/figures/figures-mod.js +286 -0
- package/bin/core-impl/figures/figures.test.d.ts +1 -0
- package/bin/core-impl/figures/figures.test.js +474 -0
- package/bin/core-impl/input/confirm-prompt.d.ts +5 -0
- package/bin/core-impl/input/confirm-prompt.js +173 -0
- package/bin/core-impl/input/input-prompt.d.ts +16 -0
- package/bin/core-impl/input/input-prompt.js +370 -0
- package/bin/core-impl/launcher/_parser.d.ts +2 -0
- package/bin/core-impl/launcher/_parser.js +122 -0
- package/bin/core-impl/launcher/_utils.d.ts +8 -0
- package/bin/core-impl/launcher/_utils.js +29 -0
- package/bin/core-impl/launcher/args.d.ts +3 -0
- package/bin/core-impl/launcher/args.js +89 -0
- package/bin/core-impl/launcher/command.d.ts +8 -0
- package/bin/core-impl/launcher/command.js +68 -0
- package/bin/core-impl/launcher/launcher-mod.d.ts +8 -0
- package/bin/core-impl/launcher/launcher-mod.js +34 -0
- package/bin/core-impl/launcher/usage.d.ts +3 -0
- package/bin/core-impl/launcher/usage.js +104 -0
- package/bin/core-impl/msg-fmt/colors.d.ts +30 -0
- package/bin/core-impl/msg-fmt/colors.js +42 -0
- package/bin/core-impl/msg-fmt/logger.d.ts +17 -0
- package/bin/core-impl/msg-fmt/logger.js +106 -0
- package/bin/core-impl/msg-fmt/mapping.d.ts +3 -0
- package/bin/core-impl/msg-fmt/mapping.js +49 -0
- package/bin/core-impl/msg-fmt/messages.d.ts +35 -0
- package/bin/core-impl/msg-fmt/messages.js +314 -0
- package/bin/core-impl/msg-fmt/terminal.d.ts +15 -0
- package/bin/core-impl/msg-fmt/terminal.js +59 -0
- package/bin/core-impl/msg-fmt/variants.d.ts +11 -0
- package/bin/core-impl/msg-fmt/variants.js +52 -0
- package/bin/core-impl/next-steps/next-steps.d.ts +14 -0
- package/bin/core-impl/next-steps/next-steps.js +24 -0
- package/bin/core-impl/number/number-mod.d.ts +28 -0
- package/bin/core-impl/number/number-mod.js +197 -0
- package/bin/core-impl/results/results.d.ts +7 -0
- package/bin/core-impl/results/results.js +27 -0
- package/bin/core-impl/select/multiselect-prompt.d.ts +2 -0
- package/bin/core-impl/select/multiselect-prompt.js +341 -0
- package/bin/core-impl/select/nummultiselect-prompt.d.ts +6 -0
- package/bin/core-impl/select/nummultiselect-prompt.js +105 -0
- package/bin/core-impl/select/numselect-prompt.d.ts +7 -0
- package/bin/core-impl/select/numselect-prompt.js +115 -0
- package/bin/core-impl/select/select-prompt.d.ts +33 -0
- package/bin/core-impl/select/select-prompt.js +302 -0
- package/bin/core-impl/select/toggle-prompt.d.ts +5 -0
- package/bin/core-impl/select/toggle-prompt.js +208 -0
- package/bin/core-impl/st-end/end.d.ts +2 -0
- package/bin/core-impl/st-end/end.js +42 -0
- package/bin/core-impl/st-end/start.d.ts +17 -0
- package/bin/core-impl/st-end/start.js +66 -0
- package/bin/core-impl/task/progress.d.ts +2 -0
- package/bin/core-impl/task/progress.js +57 -0
- package/bin/core-impl/task/spinner.d.ts +15 -0
- package/bin/core-impl/task/spinner.js +110 -0
- package/bin/core-impl/utils/colorize.d.ts +2 -0
- package/bin/core-impl/utils/colorize.js +134 -0
- package/bin/core-impl/utils/errors.d.ts +1 -0
- package/bin/core-impl/utils/errors.js +15 -0
- package/bin/core-impl/utils/prevent.d.ts +10 -0
- package/bin/core-impl/utils/prevent.js +69 -0
- package/bin/core-impl/utils/prompt-end.d.ts +8 -0
- package/bin/core-impl/utils/prompt-end.js +33 -0
- package/bin/core-impl/utils/stream-text.d.ts +18 -0
- package/bin/core-impl/utils/stream-text.js +136 -0
- package/bin/core-impl/utils/system.d.ts +6 -0
- package/bin/core-impl/utils/system.js +7 -0
- package/bin/core-impl/utils/validate.d.ts +22 -0
- package/bin/core-impl/utils/validate.js +17 -0
- package/bin/core-impl/visual/animate/animate.d.ts +14 -0
- package/bin/core-impl/visual/animate/animate.js +64 -0
- package/bin/core-impl/visual/ascii-art/ascii-art.d.ts +6 -0
- package/bin/core-impl/visual/ascii-art/ascii-art.js +12 -0
- package/bin/core-types.d.ts +434 -0
- package/bin/core-types.js +0 -0
- package/bin/main.d.ts +41 -0
- package/bin/main.js +96 -0
- package/package.json +58 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
2
|
+
import readline from "node:readline/promises";
|
|
3
|
+
import { bar, msg } from "../msg-fmt/messages.js";
|
|
4
|
+
import {
|
|
5
|
+
deleteLastLine,
|
|
6
|
+
deleteLastLines
|
|
7
|
+
} from "../msg-fmt/terminal.js";
|
|
8
|
+
function renderPromptUI(params) {
|
|
9
|
+
const {
|
|
10
|
+
title,
|
|
11
|
+
hint,
|
|
12
|
+
content,
|
|
13
|
+
contentColor = "dim",
|
|
14
|
+
contentTypography = "italic",
|
|
15
|
+
contentVariant,
|
|
16
|
+
titleColor = "cyan",
|
|
17
|
+
titleTypography = "none",
|
|
18
|
+
titleVariant,
|
|
19
|
+
borderColor = "dim",
|
|
20
|
+
userInput,
|
|
21
|
+
errorMessage
|
|
22
|
+
} = params;
|
|
23
|
+
let lineCount = 0;
|
|
24
|
+
const type = errorMessage !== "" ? "M_ERROR" : "M_GENERAL";
|
|
25
|
+
const msgParams = {
|
|
26
|
+
type,
|
|
27
|
+
title,
|
|
28
|
+
titleColor,
|
|
29
|
+
titleTypography,
|
|
30
|
+
titleVariant: titleVariant ?? "none",
|
|
31
|
+
content: content ?? "",
|
|
32
|
+
contentColor,
|
|
33
|
+
contentTypography,
|
|
34
|
+
contentVariant: contentVariant ?? "none",
|
|
35
|
+
borderColor,
|
|
36
|
+
hint: hint ?? "",
|
|
37
|
+
errorMessage
|
|
38
|
+
};
|
|
39
|
+
msg(msgParams);
|
|
40
|
+
lineCount++;
|
|
41
|
+
if (userInput !== "") {
|
|
42
|
+
msg({ type: "M_MIDDLE", title: ` ${userInput}` });
|
|
43
|
+
lineCount++;
|
|
44
|
+
}
|
|
45
|
+
return lineCount;
|
|
46
|
+
}
|
|
47
|
+
export async function numberPrompt(opts) {
|
|
48
|
+
const {
|
|
49
|
+
title = "",
|
|
50
|
+
hint,
|
|
51
|
+
hintPlaceholderColor = "blue",
|
|
52
|
+
validate,
|
|
53
|
+
defaultValue,
|
|
54
|
+
titleColor = "cyan",
|
|
55
|
+
titleTypography = "none",
|
|
56
|
+
titleVariant,
|
|
57
|
+
content,
|
|
58
|
+
contentColor = "dim",
|
|
59
|
+
contentTypography = "italic",
|
|
60
|
+
contentVariant,
|
|
61
|
+
borderColor = "dim",
|
|
62
|
+
hardcoded,
|
|
63
|
+
endTitle = "",
|
|
64
|
+
endTitleColor = "dim",
|
|
65
|
+
border = true
|
|
66
|
+
} = opts;
|
|
67
|
+
const rl = readline.createInterface({ input, output });
|
|
68
|
+
rl.on("SIGINT", () => {
|
|
69
|
+
if (endTitle !== "") {
|
|
70
|
+
msg({
|
|
71
|
+
type: "M_END",
|
|
72
|
+
title: endTitle,
|
|
73
|
+
titleColor: endTitleColor,
|
|
74
|
+
titleTypography,
|
|
75
|
+
border,
|
|
76
|
+
borderColor
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
rl.close();
|
|
80
|
+
process.exit(0);
|
|
81
|
+
});
|
|
82
|
+
let currentInput = hardcoded?.userInput || "";
|
|
83
|
+
let errorMessage = hardcoded?.errorMessage || "";
|
|
84
|
+
let isRerender = false;
|
|
85
|
+
let lastLineCount = 0;
|
|
86
|
+
const effectiveDefault = typeof defaultValue === "string" ? Number(defaultValue) : defaultValue;
|
|
87
|
+
if (hardcoded?.userInput !== void 0) {
|
|
88
|
+
renderPromptUI({
|
|
89
|
+
title,
|
|
90
|
+
hint,
|
|
91
|
+
hintPlaceholderColor,
|
|
92
|
+
content,
|
|
93
|
+
contentColor,
|
|
94
|
+
contentTypography,
|
|
95
|
+
contentVariant,
|
|
96
|
+
titleColor,
|
|
97
|
+
titleTypography,
|
|
98
|
+
titleVariant,
|
|
99
|
+
borderColor,
|
|
100
|
+
userInput: currentInput,
|
|
101
|
+
errorMessage,
|
|
102
|
+
border,
|
|
103
|
+
isRerender
|
|
104
|
+
});
|
|
105
|
+
const num = Number(currentInput);
|
|
106
|
+
if (Number.isNaN(num)) {
|
|
107
|
+
throw new Error("Please enter a valid number.");
|
|
108
|
+
}
|
|
109
|
+
const validated = await validateInput(num, validate);
|
|
110
|
+
if (validated.isValid) {
|
|
111
|
+
msg({ type: "M_MIDDLE", title: ` ${num}` });
|
|
112
|
+
msg({ type: "M_BAR", borderColor });
|
|
113
|
+
rl.close();
|
|
114
|
+
return num;
|
|
115
|
+
}
|
|
116
|
+
rl.close();
|
|
117
|
+
throw new Error(validated.errorMessage || "Invalid input.");
|
|
118
|
+
}
|
|
119
|
+
while (true) {
|
|
120
|
+
if (isRerender) {
|
|
121
|
+
deleteLastLines(lastLineCount + 1);
|
|
122
|
+
}
|
|
123
|
+
lastLineCount = renderPromptUI({
|
|
124
|
+
title,
|
|
125
|
+
hint,
|
|
126
|
+
hintPlaceholderColor,
|
|
127
|
+
content,
|
|
128
|
+
contentColor,
|
|
129
|
+
contentTypography,
|
|
130
|
+
contentVariant,
|
|
131
|
+
titleColor,
|
|
132
|
+
titleTypography,
|
|
133
|
+
titleVariant,
|
|
134
|
+
borderColor,
|
|
135
|
+
userInput: currentInput,
|
|
136
|
+
errorMessage,
|
|
137
|
+
border,
|
|
138
|
+
isRerender
|
|
139
|
+
});
|
|
140
|
+
const formattedBar = bar({ borderColor });
|
|
141
|
+
const answerInput = await rl.question(`${formattedBar} `);
|
|
142
|
+
isRerender = true;
|
|
143
|
+
if (answerInput === null) {
|
|
144
|
+
if (endTitle !== "") {
|
|
145
|
+
msg({
|
|
146
|
+
type: "M_END",
|
|
147
|
+
title: endTitle,
|
|
148
|
+
titleColor: endTitleColor,
|
|
149
|
+
titleTypography,
|
|
150
|
+
border,
|
|
151
|
+
borderColor
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
rl.close();
|
|
155
|
+
process.exit(0);
|
|
156
|
+
}
|
|
157
|
+
currentInput = answerInput.trim();
|
|
158
|
+
if (!currentInput && effectiveDefault !== void 0) {
|
|
159
|
+
deleteLastLine();
|
|
160
|
+
msg({ type: "M_MIDDLE", title: ` ${effectiveDefault}` });
|
|
161
|
+
msg({ type: "M_BAR", borderColor });
|
|
162
|
+
rl.close();
|
|
163
|
+
return effectiveDefault;
|
|
164
|
+
}
|
|
165
|
+
const num = Number(currentInput);
|
|
166
|
+
if (Number.isNaN(num)) {
|
|
167
|
+
errorMessage = "Please enter a valid number.";
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const validated = await validateInput(num, validate);
|
|
171
|
+
if (validated.isValid) {
|
|
172
|
+
deleteLastLine();
|
|
173
|
+
msg({ type: "M_MIDDLE", title: ` ${num}` });
|
|
174
|
+
msg({ type: "M_BAR", borderColor });
|
|
175
|
+
rl.close();
|
|
176
|
+
return num;
|
|
177
|
+
}
|
|
178
|
+
errorMessage = validated.errorMessage;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function validateInput(input2, validate) {
|
|
182
|
+
let isValid = true;
|
|
183
|
+
let errorMessage = "";
|
|
184
|
+
if (validate && isValid) {
|
|
185
|
+
const validationResult = await validate(input2);
|
|
186
|
+
if (validationResult === true || validationResult === void 0) {
|
|
187
|
+
isValid = true;
|
|
188
|
+
} else if (typeof validationResult === "string") {
|
|
189
|
+
isValid = false;
|
|
190
|
+
errorMessage = validationResult;
|
|
191
|
+
} else {
|
|
192
|
+
isValid = false;
|
|
193
|
+
errorMessage = "Invalid input.";
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return { isValid, errorMessage };
|
|
197
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { msg } from "../msg-fmt/messages.js";
|
|
2
|
+
export async function resultPrompt({
|
|
3
|
+
results,
|
|
4
|
+
inline = true
|
|
5
|
+
}) {
|
|
6
|
+
const title = "Your input results:";
|
|
7
|
+
if (inline) {
|
|
8
|
+
const formattedResults = Object.entries(results).map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
9
|
+
msg({
|
|
10
|
+
type: "M_INFO",
|
|
11
|
+
title,
|
|
12
|
+
content: formattedResults,
|
|
13
|
+
titleColor: "cyan",
|
|
14
|
+
contentColor: "dim",
|
|
15
|
+
wrapContent: true
|
|
16
|
+
});
|
|
17
|
+
} else {
|
|
18
|
+
const formattedResults = JSON.stringify(results, null, 2);
|
|
19
|
+
msg({
|
|
20
|
+
type: "M_INFO",
|
|
21
|
+
title,
|
|
22
|
+
content: formattedResults,
|
|
23
|
+
titleColor: "cyan",
|
|
24
|
+
contentColor: "dim"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { re } from "@reliverse/relico";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import readline from "node:readline";
|
|
4
|
+
import { msg, symbols } from "../msg-fmt/messages.js";
|
|
5
|
+
import { deleteLastLine } from "../msg-fmt/terminal.js";
|
|
6
|
+
import { completePrompt } from "../utils/prompt-end.js";
|
|
7
|
+
function isSelectOption(option) {
|
|
8
|
+
return !("separator" in option);
|
|
9
|
+
}
|
|
10
|
+
function renderPromptUI(params) {
|
|
11
|
+
const {
|
|
12
|
+
title,
|
|
13
|
+
content,
|
|
14
|
+
options,
|
|
15
|
+
pointer,
|
|
16
|
+
selectedOptions,
|
|
17
|
+
errorMessage,
|
|
18
|
+
displayInstructions,
|
|
19
|
+
instructions,
|
|
20
|
+
allDisabled,
|
|
21
|
+
titleColor,
|
|
22
|
+
contentColor,
|
|
23
|
+
contentTypography,
|
|
24
|
+
debug,
|
|
25
|
+
titleVariant,
|
|
26
|
+
titleTypography,
|
|
27
|
+
isRerender = false
|
|
28
|
+
} = params;
|
|
29
|
+
if (!isRerender) {
|
|
30
|
+
msg({
|
|
31
|
+
type: "M_NULL",
|
|
32
|
+
title,
|
|
33
|
+
titleColor,
|
|
34
|
+
...titleTypography ? { titleTypography } : {},
|
|
35
|
+
...titleVariant ? { titleVariant } : {}
|
|
36
|
+
});
|
|
37
|
+
if (content) {
|
|
38
|
+
msg({
|
|
39
|
+
type: "M_NULL",
|
|
40
|
+
content,
|
|
41
|
+
contentColor,
|
|
42
|
+
contentTypography
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
let uiLineCount = 0;
|
|
47
|
+
if (errorMessage) {
|
|
48
|
+
msg({
|
|
49
|
+
type: "M_NULL",
|
|
50
|
+
title: `${re.redBright(symbols.step_error)} ${re.redBright(errorMessage)}`
|
|
51
|
+
});
|
|
52
|
+
uiLineCount++;
|
|
53
|
+
} else if (allDisabled) {
|
|
54
|
+
msg({ type: "M_NULL", title: re.redBright("All options are disabled.") });
|
|
55
|
+
uiLineCount++;
|
|
56
|
+
} else if (displayInstructions && !isRerender) {
|
|
57
|
+
msg({ type: "M_NULL", title: re.blue(instructions) });
|
|
58
|
+
uiLineCount++;
|
|
59
|
+
}
|
|
60
|
+
for (let index = 0; index < options.length; index++) {
|
|
61
|
+
const option = options[index];
|
|
62
|
+
if (!option) continue;
|
|
63
|
+
if (!isSelectOption(option)) {
|
|
64
|
+
const width = option.width ?? 20;
|
|
65
|
+
const symbolKey = option.symbol ?? "line";
|
|
66
|
+
const lineSymbol = symbolKey in symbols ? symbols[symbolKey] : "\u2500";
|
|
67
|
+
msg({ type: "M_NULL", title: re.dim(lineSymbol.repeat(width)) });
|
|
68
|
+
uiLineCount++;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const isSelected = selectedOptions.has(index);
|
|
72
|
+
const isHighlighted = index === pointer;
|
|
73
|
+
const isDisabled = option.disabled;
|
|
74
|
+
const checkbox = isSelected ? "[x]" : "[ ]";
|
|
75
|
+
const prefix = isHighlighted ? re.yellow(re.reset("> ")) : " ";
|
|
76
|
+
const labelColor = isDisabled ? re.dim(re.reset(option.label)) : isHighlighted ? re.reset(re.yellow(option.label)) : re.reset(option.label);
|
|
77
|
+
const hint = option.hint ? re.reset(
|
|
78
|
+
` (${isDisabled ? re.dim(option.hint) : re.gray(option.hint)})`
|
|
79
|
+
) : "";
|
|
80
|
+
const formattedCheckbox = isHighlighted ? re.yellow(checkbox) : checkbox;
|
|
81
|
+
msg({
|
|
82
|
+
type: "M_NULL",
|
|
83
|
+
title: `${prefix}${formattedCheckbox} ${labelColor}${hint}`
|
|
84
|
+
});
|
|
85
|
+
uiLineCount++;
|
|
86
|
+
}
|
|
87
|
+
if (debug) {
|
|
88
|
+
console.log({ optionsCount: options.length });
|
|
89
|
+
}
|
|
90
|
+
return uiLineCount;
|
|
91
|
+
}
|
|
92
|
+
export async function multiselectPrompt(params) {
|
|
93
|
+
const {
|
|
94
|
+
title = "",
|
|
95
|
+
content = "",
|
|
96
|
+
options,
|
|
97
|
+
defaultValue = [],
|
|
98
|
+
borderColor = "dim",
|
|
99
|
+
titleColor = "cyan",
|
|
100
|
+
titleTypography = "none",
|
|
101
|
+
titleVariant,
|
|
102
|
+
contentColor = "dim",
|
|
103
|
+
contentTypography = "italic",
|
|
104
|
+
border = true,
|
|
105
|
+
endTitle = "",
|
|
106
|
+
endTitleColor = "dim",
|
|
107
|
+
debug = false,
|
|
108
|
+
terminalWidth: customTerminalWidth = 90,
|
|
109
|
+
displayInstructions = false,
|
|
110
|
+
minSelect = 0,
|
|
111
|
+
maxSelect,
|
|
112
|
+
selectAll = false
|
|
113
|
+
} = params;
|
|
114
|
+
let pointer = defaultValue.length > 0 ? options.findIndex(
|
|
115
|
+
(opt) => opt && isSelectOption(opt) && defaultValue.includes(opt.value) && !opt.disabled
|
|
116
|
+
) : 0;
|
|
117
|
+
if (pointer === -1) {
|
|
118
|
+
pointer = options.findIndex(
|
|
119
|
+
(opt) => opt && isSelectOption(opt) && !opt.disabled
|
|
120
|
+
);
|
|
121
|
+
if (pointer === -1) {
|
|
122
|
+
pointer = 0;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const selectedOptions = new Set(
|
|
126
|
+
selectAll ? options.map(
|
|
127
|
+
(opt, index) => opt && isSelectOption(opt) && !opt.disabled ? index : -1
|
|
128
|
+
).filter((i) => i !== -1) : defaultValue.map(
|
|
129
|
+
(val) => options.findIndex((o) => o && isSelectOption(o) && o.value === val)
|
|
130
|
+
).filter(
|
|
131
|
+
(i) => i >= 0 && options[i] && isSelectOption(options[i]) && !options[i].disabled
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
const rl = readline.createInterface({ input, output });
|
|
135
|
+
readline.emitKeypressEvents(input, rl);
|
|
136
|
+
if (typeof input.setRawMode === "function") {
|
|
137
|
+
input.setRawMode(true);
|
|
138
|
+
}
|
|
139
|
+
let errorMessage = "";
|
|
140
|
+
const allDisabled = options.filter(isSelectOption).every((option) => option.disabled);
|
|
141
|
+
const instructions = "Use <\u2191/\u2193> to navigate, <Space> to toggle, <A> to toggle all";
|
|
142
|
+
function toggleSelectAll() {
|
|
143
|
+
const selectableIndexes = options.map((opt, i) => isSelectOption(opt) && !opt.disabled ? i : -1).filter((i) => i !== -1);
|
|
144
|
+
const allSelected = selectableIndexes.every((i) => selectedOptions.has(i));
|
|
145
|
+
if (allSelected) {
|
|
146
|
+
for (const i of selectableIndexes) {
|
|
147
|
+
selectedOptions.delete(i);
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
for (const i of selectableIndexes) {
|
|
151
|
+
selectedOptions.add(i);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function movePointerUp() {
|
|
156
|
+
const originalPointer = pointer;
|
|
157
|
+
do {
|
|
158
|
+
pointer = (pointer - 1 + options.length) % options.length;
|
|
159
|
+
const currentOption = options[pointer];
|
|
160
|
+
if (currentOption && isSelectOption(currentOption) && !currentOption.disabled) {
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
} while (pointer !== originalPointer);
|
|
164
|
+
errorMessage = "";
|
|
165
|
+
}
|
|
166
|
+
function movePointerDown() {
|
|
167
|
+
const originalPointer = pointer;
|
|
168
|
+
do {
|
|
169
|
+
pointer = (pointer + 1) % options.length;
|
|
170
|
+
const currentOption = options[pointer];
|
|
171
|
+
if (currentOption && isSelectOption(currentOption) && !currentOption.disabled) {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
} while (pointer !== originalPointer);
|
|
175
|
+
errorMessage = "";
|
|
176
|
+
}
|
|
177
|
+
let lastUILineCount = 0;
|
|
178
|
+
function renderOptions() {
|
|
179
|
+
if (lastUILineCount > 0) {
|
|
180
|
+
for (let i = 0; i < lastUILineCount; i++) {
|
|
181
|
+
process.stdout.write("\x1B[1A\x1B[2K");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
lastUILineCount = renderPromptUI({
|
|
185
|
+
title,
|
|
186
|
+
content,
|
|
187
|
+
options,
|
|
188
|
+
pointer,
|
|
189
|
+
selectedOptions,
|
|
190
|
+
errorMessage,
|
|
191
|
+
displayInstructions,
|
|
192
|
+
instructions,
|
|
193
|
+
allDisabled,
|
|
194
|
+
titleColor,
|
|
195
|
+
contentColor,
|
|
196
|
+
contentTypography,
|
|
197
|
+
debug,
|
|
198
|
+
borderColor,
|
|
199
|
+
titleVariant,
|
|
200
|
+
titleTypography,
|
|
201
|
+
terminalWidth: customTerminalWidth,
|
|
202
|
+
resized: false,
|
|
203
|
+
isRerender: true
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
lastUILineCount = renderPromptUI({
|
|
207
|
+
title,
|
|
208
|
+
content,
|
|
209
|
+
options,
|
|
210
|
+
pointer,
|
|
211
|
+
selectedOptions,
|
|
212
|
+
errorMessage,
|
|
213
|
+
displayInstructions,
|
|
214
|
+
instructions,
|
|
215
|
+
allDisabled,
|
|
216
|
+
titleColor,
|
|
217
|
+
contentColor,
|
|
218
|
+
contentTypography,
|
|
219
|
+
debug,
|
|
220
|
+
borderColor,
|
|
221
|
+
titleVariant,
|
|
222
|
+
titleTypography,
|
|
223
|
+
terminalWidth: customTerminalWidth,
|
|
224
|
+
resized: false,
|
|
225
|
+
isRerender: false
|
|
226
|
+
});
|
|
227
|
+
return new Promise((resolve) => {
|
|
228
|
+
function cleanup(isCtrlC = false) {
|
|
229
|
+
if (typeof input.setRawMode === "function") {
|
|
230
|
+
input.setRawMode(false);
|
|
231
|
+
}
|
|
232
|
+
rl.close();
|
|
233
|
+
input.removeListener("keypress", handleKeypress);
|
|
234
|
+
if (isCtrlC) {
|
|
235
|
+
process.exit(0);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
async function endPrompt(isCtrlC = false) {
|
|
239
|
+
await completePrompt(
|
|
240
|
+
"multiselect",
|
|
241
|
+
isCtrlC,
|
|
242
|
+
endTitle,
|
|
243
|
+
endTitleColor,
|
|
244
|
+
titleTypography,
|
|
245
|
+
titleVariant ? titleVariant : void 0,
|
|
246
|
+
border,
|
|
247
|
+
borderColor,
|
|
248
|
+
void 0,
|
|
249
|
+
false
|
|
250
|
+
);
|
|
251
|
+
cleanup(isCtrlC);
|
|
252
|
+
}
|
|
253
|
+
async function confirmSelection() {
|
|
254
|
+
const selectedCount = selectedOptions.size;
|
|
255
|
+
if (selectedCount < minSelect) {
|
|
256
|
+
deleteLastLine();
|
|
257
|
+
errorMessage = `You must select at least ${minSelect} option${minSelect !== 1 ? "s" : ""}.`;
|
|
258
|
+
renderOptions();
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (maxSelect !== void 0 && selectedCount > maxSelect) {
|
|
262
|
+
deleteLastLine();
|
|
263
|
+
errorMessage = `You can select at most ${maxSelect} option${maxSelect !== 1 ? "s" : ""}.`;
|
|
264
|
+
renderOptions();
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const selectedValues = Array.from(selectedOptions).filter((idx) => {
|
|
268
|
+
const opt = options[idx];
|
|
269
|
+
return opt && isSelectOption(opt) && !opt.disabled;
|
|
270
|
+
}).map((idx) => {
|
|
271
|
+
const opt = options[idx];
|
|
272
|
+
return opt && isSelectOption(opt) ? opt.value : null;
|
|
273
|
+
}).filter((val) => val !== null && val !== void 0);
|
|
274
|
+
cleanup();
|
|
275
|
+
resolve(selectedValues);
|
|
276
|
+
deleteLastLine();
|
|
277
|
+
await completePrompt(
|
|
278
|
+
"multiselect",
|
|
279
|
+
false,
|
|
280
|
+
endTitle,
|
|
281
|
+
endTitleColor,
|
|
282
|
+
titleTypography,
|
|
283
|
+
titleVariant ? titleVariant : void 0,
|
|
284
|
+
border,
|
|
285
|
+
borderColor,
|
|
286
|
+
void 0,
|
|
287
|
+
true
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
function handleKeypress(_str, key) {
|
|
291
|
+
if (allDisabled) {
|
|
292
|
+
if (key.name === "c" && key.ctrl) {
|
|
293
|
+
void endPrompt(true);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
switch (key.name) {
|
|
299
|
+
case "up":
|
|
300
|
+
case "k":
|
|
301
|
+
movePointerUp();
|
|
302
|
+
renderOptions();
|
|
303
|
+
break;
|
|
304
|
+
case "down":
|
|
305
|
+
case "j":
|
|
306
|
+
movePointerDown();
|
|
307
|
+
renderOptions();
|
|
308
|
+
break;
|
|
309
|
+
case "space": {
|
|
310
|
+
const currentOption = options[pointer];
|
|
311
|
+
if (!currentOption || !isSelectOption(currentOption) || currentOption.disabled) {
|
|
312
|
+
errorMessage = "This option is disabled";
|
|
313
|
+
} else {
|
|
314
|
+
if (selectedOptions.has(pointer)) {
|
|
315
|
+
selectedOptions.delete(pointer);
|
|
316
|
+
} else {
|
|
317
|
+
selectedOptions.add(pointer);
|
|
318
|
+
}
|
|
319
|
+
errorMessage = "";
|
|
320
|
+
}
|
|
321
|
+
renderOptions();
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
case "return":
|
|
325
|
+
void confirmSelection();
|
|
326
|
+
break;
|
|
327
|
+
case "c":
|
|
328
|
+
if (key.ctrl) {
|
|
329
|
+
void endPrompt(true);
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
case "a":
|
|
333
|
+
toggleSelectAll();
|
|
334
|
+
errorMessage = "";
|
|
335
|
+
renderOptions();
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
input.on("keypress", handleKeypress);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { re } from "@reliverse/relico";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import readline from "node:readline/promises";
|
|
4
|
+
import { bar, fmt, msg } from "../msg-fmt/messages.js";
|
|
5
|
+
import {
|
|
6
|
+
countLines,
|
|
7
|
+
deleteLastLine,
|
|
8
|
+
deleteLastLines
|
|
9
|
+
} from "../msg-fmt/terminal.js";
|
|
10
|
+
export async function numMultiSelectPrompt(opts) {
|
|
11
|
+
const {
|
|
12
|
+
title = "",
|
|
13
|
+
choices,
|
|
14
|
+
defaultValue,
|
|
15
|
+
titleColor = "cyan",
|
|
16
|
+
titleTypography = "none",
|
|
17
|
+
titleVariant,
|
|
18
|
+
hint,
|
|
19
|
+
hintPlaceholderColor = "blue",
|
|
20
|
+
content,
|
|
21
|
+
contentColor = "dim",
|
|
22
|
+
contentTypography = "italic",
|
|
23
|
+
contentVariant,
|
|
24
|
+
borderColor = "dim",
|
|
25
|
+
variantOptions
|
|
26
|
+
} = opts;
|
|
27
|
+
if (!choices || choices.length === 0) {
|
|
28
|
+
throw new Error("Choices are required for multiselect prompt.");
|
|
29
|
+
}
|
|
30
|
+
const rl = readline.createInterface({ input, output });
|
|
31
|
+
const formattedBar = bar({ borderColor });
|
|
32
|
+
let linesToDelete = 0;
|
|
33
|
+
let errorMessage = "";
|
|
34
|
+
try {
|
|
35
|
+
while (true) {
|
|
36
|
+
if (linesToDelete > 0) {
|
|
37
|
+
deleteLastLines(linesToDelete);
|
|
38
|
+
}
|
|
39
|
+
const { text: question } = fmt({
|
|
40
|
+
hintPlaceholderColor,
|
|
41
|
+
type: errorMessage !== "" ? "M_ERROR" : "M_GENERAL",
|
|
42
|
+
title: `${title}${defaultValue ? ` [Default: ${Array.isArray(defaultValue) ? defaultValue.join(", ") : defaultValue}]` : ""}`,
|
|
43
|
+
titleColor,
|
|
44
|
+
titleTypography,
|
|
45
|
+
titleVariant: titleVariant ?? "none",
|
|
46
|
+
content: content ?? "",
|
|
47
|
+
contentColor: contentColor ?? "dim",
|
|
48
|
+
contentTypography: contentTypography ?? "none",
|
|
49
|
+
contentVariant: contentVariant ?? "none",
|
|
50
|
+
borderColor,
|
|
51
|
+
hint: hint ?? "",
|
|
52
|
+
variantOptions: variantOptions ?? {},
|
|
53
|
+
errorMessage
|
|
54
|
+
});
|
|
55
|
+
const choicesText = choices.map(
|
|
56
|
+
(choice, index) => re.dim(
|
|
57
|
+
`${formattedBar} ${index + 1}) ${choice.title}${choice.description ? ` - ${choice.description}` : ""}`
|
|
58
|
+
)
|
|
59
|
+
).join("\n");
|
|
60
|
+
const fullPrompt = `${question}
|
|
61
|
+
${choicesText}
|
|
62
|
+
${formattedBar} ${re.bold(re.blue(`Enter your choices (comma-separated numbers between 1-${choices.length})`))}:
|
|
63
|
+
${formattedBar} `;
|
|
64
|
+
const { text: formattedPrompt } = fmt({
|
|
65
|
+
hintPlaceholderColor,
|
|
66
|
+
type: "M_NULL",
|
|
67
|
+
title: fullPrompt
|
|
68
|
+
});
|
|
69
|
+
const questionLines = countLines(formattedPrompt);
|
|
70
|
+
linesToDelete = questionLines + 1;
|
|
71
|
+
const answer = (await rl.question(`${formattedPrompt} `)).trim();
|
|
72
|
+
if (!answer && defaultValue !== void 0) {
|
|
73
|
+
deleteLastLine();
|
|
74
|
+
msg({
|
|
75
|
+
type: "M_MIDDLE",
|
|
76
|
+
title: ` ${Array.isArray(defaultValue) ? defaultValue.join(", ") : defaultValue}`,
|
|
77
|
+
titleColor: "none"
|
|
78
|
+
});
|
|
79
|
+
msg({ type: "M_BAR", borderColor });
|
|
80
|
+
return defaultValue;
|
|
81
|
+
}
|
|
82
|
+
const selections = answer.split(",").map((s) => s.trim());
|
|
83
|
+
const invalidSelections = selections.filter((s) => {
|
|
84
|
+
const num = Number(s);
|
|
85
|
+
return Number.isNaN(num) || num < 1 || num > choices.length;
|
|
86
|
+
});
|
|
87
|
+
if (invalidSelections.length > 0) {
|
|
88
|
+
errorMessage = `Invalid selections: ${invalidSelections.join(
|
|
89
|
+
", "
|
|
90
|
+
)}. Please enter numbers between 1 and ${choices.length}.`;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const selectedValues = selections.map((s) => choices[Number(s) - 1]?.id);
|
|
94
|
+
const isValid = true;
|
|
95
|
+
errorMessage = "";
|
|
96
|
+
if (isValid) {
|
|
97
|
+
rl.close();
|
|
98
|
+
msg({ type: "M_BAR", borderColor });
|
|
99
|
+
return selectedValues;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} finally {
|
|
103
|
+
rl.close();
|
|
104
|
+
}
|
|
105
|
+
}
|