cli-menu-kit 0.2.0 → 0.2.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/dist/components/display/header.d.ts +40 -0
- package/dist/components/display/header.js +331 -18
- package/dist/components/display/headers.d.ts +1 -0
- package/dist/components/display/headers.js +15 -5
- package/dist/components/display/index.d.ts +1 -1
- package/dist/components/display/index.js +3 -1
- package/dist/components/display/messages.js +5 -5
- package/dist/components/display/progress.d.ts +17 -0
- package/dist/components/display/progress.js +18 -0
- package/dist/components/display/summary.js +72 -10
- package/dist/components/display/table.d.ts +2 -0
- package/dist/components/display/table.js +7 -6
- package/dist/components/inputs/language-input.js +8 -5
- package/dist/components/inputs/number-input.js +19 -14
- package/dist/components/inputs/text-input.js +50 -13
- package/dist/components/menus/boolean-menu.js +33 -20
- package/dist/components/menus/checkbox-menu.js +5 -2
- package/dist/components/menus/checkbox-table-menu.js +12 -9
- package/dist/components/menus/radio-menu-split.d.ts +1 -0
- package/dist/components/menus/radio-menu-split.js +26 -16
- package/dist/components/menus/radio-menu.js +67 -38
- package/dist/components.js +3 -3
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.js +21 -0
- package/dist/config/language-config.d.ts +73 -0
- package/dist/config/language-config.js +157 -0
- package/dist/config/user-config.d.ts +83 -0
- package/dist/config/user-config.js +185 -0
- package/dist/core/colors.d.ts +24 -18
- package/dist/core/colors.js +74 -7
- package/dist/core/renderer.js +26 -18
- package/dist/core/terminal.d.ts +13 -0
- package/dist/core/terminal.js +87 -0
- package/dist/features/commands.js +23 -22
- package/dist/index.d.ts +3 -1
- package/dist/index.js +21 -2
- package/dist/layout.d.ts +50 -51
- package/dist/layout.js +69 -117
- package/dist/page-layout.d.ts +31 -0
- package/dist/page-layout.js +46 -7
- package/dist/types/input.types.d.ts +8 -0
- package/dist/types/layout.types.d.ts +56 -0
- package/dist/types/layout.types.js +36 -0
- package/dist/types/menu.types.d.ts +4 -0
- package/package.json +4 -1
|
@@ -10,6 +10,58 @@ exports.createSimpleSummary = createSimpleSummary;
|
|
|
10
10
|
const terminal_js_1 = require("../../core/terminal.js");
|
|
11
11
|
const colors_js_1 = require("../../core/colors.js");
|
|
12
12
|
const terminal_js_2 = require("../../core/terminal.js");
|
|
13
|
+
function resolveColorSpec(spec) {
|
|
14
|
+
if (!spec) {
|
|
15
|
+
return '';
|
|
16
|
+
}
|
|
17
|
+
return spec
|
|
18
|
+
.split('+')
|
|
19
|
+
.map(part => {
|
|
20
|
+
const token = part.trim();
|
|
21
|
+
if (!token) {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
const named = colors_js_1.colors[token];
|
|
25
|
+
if (named) {
|
|
26
|
+
return named;
|
|
27
|
+
}
|
|
28
|
+
// Accept raw ANSI sequences (e.g. values from getUIColors()).
|
|
29
|
+
if (/^\x1b\[[0-9;]*m$/.test(token)) {
|
|
30
|
+
return token;
|
|
31
|
+
}
|
|
32
|
+
return '';
|
|
33
|
+
})
|
|
34
|
+
.join('');
|
|
35
|
+
}
|
|
36
|
+
function splitLongToken(token, maxWidth) {
|
|
37
|
+
if (token.length <= maxWidth) {
|
|
38
|
+
return [token];
|
|
39
|
+
}
|
|
40
|
+
const chunks = [];
|
|
41
|
+
let rest = token;
|
|
42
|
+
const breakChars = ['/', '\\', '-', '_', '.'];
|
|
43
|
+
while (rest.length > maxWidth) {
|
|
44
|
+
let breakPos = -1;
|
|
45
|
+
for (const ch of breakChars) {
|
|
46
|
+
const idx = rest.lastIndexOf(ch, maxWidth - 1);
|
|
47
|
+
if (idx > breakPos) {
|
|
48
|
+
breakPos = idx;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (breakPos <= 0) {
|
|
52
|
+
breakPos = maxWidth;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
breakPos += 1;
|
|
56
|
+
}
|
|
57
|
+
chunks.push(rest.slice(0, breakPos));
|
|
58
|
+
rest = rest.slice(breakPos);
|
|
59
|
+
}
|
|
60
|
+
if (rest.length > 0) {
|
|
61
|
+
chunks.push(rest);
|
|
62
|
+
}
|
|
63
|
+
return chunks;
|
|
64
|
+
}
|
|
13
65
|
/**
|
|
14
66
|
* Wrap text to fit within a specific width, preserving ANSI color codes
|
|
15
67
|
*/
|
|
@@ -17,10 +69,15 @@ function wrapTextWithColors(text, maxWidth) {
|
|
|
17
69
|
// Extract ANSI codes and plain text
|
|
18
70
|
const ansiRegex = /\x1b\[[0-9;]*m/g;
|
|
19
71
|
const plainText = text.replace(ansiRegex, '');
|
|
72
|
+
const hasAnsi = /\x1b\[[0-9;]*m/.test(text);
|
|
20
73
|
// If plain text fits, return as-is
|
|
21
74
|
if (plainText.length <= maxWidth) {
|
|
22
75
|
return [text];
|
|
23
76
|
}
|
|
77
|
+
// Fast path for plain text (no ANSI): support long token wrapping (e.g. file paths).
|
|
78
|
+
if (!hasAnsi) {
|
|
79
|
+
return wrapText(text, maxWidth);
|
|
80
|
+
}
|
|
24
81
|
// Find all ANSI codes and their positions in the original text
|
|
25
82
|
const codes = [];
|
|
26
83
|
let match;
|
|
@@ -94,6 +151,16 @@ function wrapText(text, maxWidth) {
|
|
|
94
151
|
const lines = [];
|
|
95
152
|
let currentLine = '';
|
|
96
153
|
for (const word of words) {
|
|
154
|
+
if (word.length > maxWidth) {
|
|
155
|
+
if (currentLine) {
|
|
156
|
+
lines.push(currentLine);
|
|
157
|
+
currentLine = '';
|
|
158
|
+
}
|
|
159
|
+
const chunks = splitLongToken(word, maxWidth);
|
|
160
|
+
lines.push(...chunks.slice(0, -1));
|
|
161
|
+
currentLine = chunks[chunks.length - 1] || '';
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
97
164
|
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
98
165
|
if (testLine.length <= maxWidth) {
|
|
99
166
|
currentLine = testLine;
|
|
@@ -136,12 +203,7 @@ function renderSummaryTable(config) {
|
|
|
136
203
|
(0, terminal_js_1.writeLine)(`│${' '.repeat(boxWidth - 2)}│`);
|
|
137
204
|
// Title if provided
|
|
138
205
|
if (title) {
|
|
139
|
-
|
|
140
|
-
let titleColor = '';
|
|
141
|
-
if (finalColors.title) {
|
|
142
|
-
const parts = finalColors.title.split('+');
|
|
143
|
-
titleColor = parts.map(part => colors_js_1.colors[part.trim()] || '').join('');
|
|
144
|
-
}
|
|
206
|
+
const titleColor = resolveColorSpec(finalColors.title);
|
|
145
207
|
const resetColor = titleColor ? colors_js_1.colors.reset : '';
|
|
146
208
|
let titleLine;
|
|
147
209
|
let remainingSpace;
|
|
@@ -173,7 +235,7 @@ function renderSummaryTable(config) {
|
|
|
173
235
|
sections.forEach((section, sectionIndex) => {
|
|
174
236
|
// Section header if provided
|
|
175
237
|
if (section.header) {
|
|
176
|
-
const headerColor = finalColors.sectionHeader
|
|
238
|
+
const headerColor = resolveColorSpec(finalColors.sectionHeader);
|
|
177
239
|
const resetColor = headerColor ? colors_js_1.colors.reset : '';
|
|
178
240
|
const headerLine = ` ${headerColor}${section.header}${resetColor}`;
|
|
179
241
|
const remainingSpace = boxWidth - section.header.length - 4;
|
|
@@ -188,7 +250,7 @@ function renderSummaryTable(config) {
|
|
|
188
250
|
// Wrap the value (preserving colors if present)
|
|
189
251
|
const wrappedLines = wrapTextWithColors(item.value, valueMaxWidth);
|
|
190
252
|
// First line with key
|
|
191
|
-
const keyColor = finalColors.key
|
|
253
|
+
const keyColor = resolveColorSpec(finalColors.key);
|
|
192
254
|
const keyResetColor = keyColor ? colors_js_1.colors.reset : '';
|
|
193
255
|
// wrappedLines already contain colors, don't add valueColor
|
|
194
256
|
const firstLine = ` ${keyColor}${item.key}:${keyResetColor}${' '.repeat(Math.max(1, keyPadding - item.key.length))}${wrappedLines[0]}`;
|
|
@@ -205,9 +267,9 @@ function renderSummaryTable(config) {
|
|
|
205
267
|
}
|
|
206
268
|
else {
|
|
207
269
|
// No wrapping needed
|
|
208
|
-
const keyColor = finalColors.key
|
|
270
|
+
const keyColor = resolveColorSpec(finalColors.key);
|
|
209
271
|
const keyResetColor = keyColor ? colors_js_1.colors.reset : '';
|
|
210
|
-
const valueColor = finalColors.value
|
|
272
|
+
const valueColor = resolveColorSpec(finalColors.value);
|
|
211
273
|
const valueResetColor = valueColor ? colors_js_1.colors.reset : '';
|
|
212
274
|
// Only wrap value with color if valueColor is set, otherwise preserve original colors in item.value
|
|
213
275
|
const coloredValue = valueColor ? `${valueColor}${item.value}${valueResetColor}` : item.value;
|
|
@@ -12,10 +12,11 @@ const terminal_js_1 = require("../../core/terminal.js");
|
|
|
12
12
|
* @param config - Table configuration
|
|
13
13
|
*/
|
|
14
14
|
function renderTable(config) {
|
|
15
|
-
const { columns, data, showBorders = true, showHeaderSeparator = true } = config;
|
|
15
|
+
const { columns, data, showBorders = true, showHeaderSeparator = true, borderColor = '' } = config;
|
|
16
16
|
if (columns.length === 0 || data.length === 0) {
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
|
+
const resetColor = borderColor ? '\x1b[0m' : '';
|
|
19
20
|
// Calculate column widths
|
|
20
21
|
const columnWidths = columns.map((col, index) => {
|
|
21
22
|
if (col.width)
|
|
@@ -28,7 +29,7 @@ function renderTable(config) {
|
|
|
28
29
|
// Render top border
|
|
29
30
|
if (showBorders) {
|
|
30
31
|
const border = columnWidths.map(w => '─'.repeat(w)).join('┬');
|
|
31
|
-
(0, terminal_js_1.writeLine)(
|
|
32
|
+
(0, terminal_js_1.writeLine)(`${borderColor}┌${border}┐${resetColor}`);
|
|
32
33
|
}
|
|
33
34
|
// Render header
|
|
34
35
|
const headerCells = columns.map((col, index) => {
|
|
@@ -37,7 +38,7 @@ function renderTable(config) {
|
|
|
37
38
|
return alignText(col.header, width, align);
|
|
38
39
|
});
|
|
39
40
|
if (showBorders) {
|
|
40
|
-
(0, terminal_js_1.writeLine)(
|
|
41
|
+
(0, terminal_js_1.writeLine)(`${borderColor}│${resetColor}${headerCells.join(`${borderColor}│${resetColor}`)}${borderColor}│${resetColor}`);
|
|
41
42
|
}
|
|
42
43
|
else {
|
|
43
44
|
(0, terminal_js_1.writeLine)(headerCells.join(' '));
|
|
@@ -46,7 +47,7 @@ function renderTable(config) {
|
|
|
46
47
|
if (showHeaderSeparator) {
|
|
47
48
|
if (showBorders) {
|
|
48
49
|
const separator = columnWidths.map(w => '─'.repeat(w)).join('┼');
|
|
49
|
-
(0, terminal_js_1.writeLine)(
|
|
50
|
+
(0, terminal_js_1.writeLine)(`${borderColor}├${separator}┤${resetColor}`);
|
|
50
51
|
}
|
|
51
52
|
else {
|
|
52
53
|
const separator = columnWidths.map(w => '─'.repeat(w)).join(' ');
|
|
@@ -62,7 +63,7 @@ function renderTable(config) {
|
|
|
62
63
|
return alignText(value, width, align);
|
|
63
64
|
});
|
|
64
65
|
if (showBorders) {
|
|
65
|
-
(0, terminal_js_1.writeLine)(
|
|
66
|
+
(0, terminal_js_1.writeLine)(`${borderColor}│${resetColor}${cells.join(`${borderColor}│${resetColor}`)}${borderColor}│${resetColor}`);
|
|
66
67
|
}
|
|
67
68
|
else {
|
|
68
69
|
(0, terminal_js_1.writeLine)(cells.join(' '));
|
|
@@ -71,7 +72,7 @@ function renderTable(config) {
|
|
|
71
72
|
// Render bottom border
|
|
72
73
|
if (showBorders) {
|
|
73
74
|
const border = columnWidths.map(w => '─'.repeat(w)).join('┴');
|
|
74
|
-
(0, terminal_js_1.writeLine)(
|
|
75
|
+
(0, terminal_js_1.writeLine)(`${borderColor}└${border}┘${resetColor}`);
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
/**
|
|
@@ -9,13 +9,14 @@ const terminal_js_1 = require("../../core/terminal.js");
|
|
|
9
9
|
const keyboard_js_1 = require("../../core/keyboard.js");
|
|
10
10
|
const renderer_js_1 = require("../../core/renderer.js");
|
|
11
11
|
const colors_js_1 = require("../../core/colors.js");
|
|
12
|
+
const registry_js_1 = require("../../i18n/registry.js");
|
|
12
13
|
/**
|
|
13
14
|
* Show a language selector
|
|
14
15
|
* @param config - Selector configuration
|
|
15
16
|
* @returns Promise resolving to selected language code
|
|
16
17
|
*/
|
|
17
18
|
async function showLanguageSelector(config) {
|
|
18
|
-
const { languages, defaultLanguage, prompt = '
|
|
19
|
+
const { languages, defaultLanguage, prompt = 'Select Language', onExit, preserveOnExit = false } = config;
|
|
19
20
|
// Validate languages
|
|
20
21
|
if (!languages || languages.length === 0) {
|
|
21
22
|
throw new Error('LanguageSelector requires at least one language');
|
|
@@ -33,7 +34,7 @@ async function showLanguageSelector(config) {
|
|
|
33
34
|
(0, terminal_js_1.clearMenu)(state);
|
|
34
35
|
let lineCount = 0;
|
|
35
36
|
// Render header
|
|
36
|
-
(0, renderer_js_1.renderHeader)(` ${prompt}`, colors_js_1.
|
|
37
|
+
(0, renderer_js_1.renderHeader)(` ${prompt}`, colors_js_1.uiColors.primary);
|
|
37
38
|
lineCount++;
|
|
38
39
|
(0, renderer_js_1.renderBlankLines)(1);
|
|
39
40
|
lineCount++;
|
|
@@ -48,7 +49,7 @@ async function showLanguageSelector(config) {
|
|
|
48
49
|
// Render hints
|
|
49
50
|
(0, renderer_js_1.renderBlankLines)(1);
|
|
50
51
|
lineCount++;
|
|
51
|
-
(0, renderer_js_1.renderHints)([
|
|
52
|
+
(0, renderer_js_1.renderHints)([(0, registry_js_1.t)('hints.arrows'), (0, registry_js_1.t)('hints.numbers'), (0, registry_js_1.t)('hints.enter')]);
|
|
52
53
|
lineCount++;
|
|
53
54
|
state.renderedLines = lineCount;
|
|
54
55
|
};
|
|
@@ -60,13 +61,15 @@ async function showLanguageSelector(config) {
|
|
|
60
61
|
// Handle Ctrl+C
|
|
61
62
|
if ((0, keyboard_js_1.isCtrlC)(key)) {
|
|
62
63
|
state.stdin.removeListener('data', onData);
|
|
63
|
-
|
|
64
|
+
if (!preserveOnExit) {
|
|
65
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
66
|
+
}
|
|
64
67
|
(0, terminal_js_1.restoreTerminal)(state);
|
|
65
68
|
if (onExit) {
|
|
66
69
|
onExit();
|
|
67
70
|
}
|
|
68
71
|
else {
|
|
69
|
-
console.log(
|
|
72
|
+
console.log(`\n${(0, registry_js_1.t)('messages.goodbye')}`);
|
|
70
73
|
}
|
|
71
74
|
process.exit(0);
|
|
72
75
|
}
|
|
@@ -8,13 +8,15 @@ exports.showNumberInput = showNumberInput;
|
|
|
8
8
|
const terminal_js_1 = require("../../core/terminal.js");
|
|
9
9
|
const keyboard_js_1 = require("../../core/keyboard.js");
|
|
10
10
|
const colors_js_1 = require("../../core/colors.js");
|
|
11
|
+
const registry_js_1 = require("../../i18n/registry.js");
|
|
11
12
|
/**
|
|
12
13
|
* Show a number input prompt
|
|
13
14
|
* @param config - Input configuration
|
|
14
15
|
* @returns Promise resolving to entered number
|
|
15
16
|
*/
|
|
16
17
|
async function showNumberInput(config) {
|
|
17
|
-
const { prompt, defaultValue, min, max, allowDecimals = false, allowNegative = false, validate, errorMessage, onExit } = config;
|
|
18
|
+
const { lang: langInput, prompt, defaultValue, min, max, allowDecimals = false, allowNegative = false, validate, errorMessage, onExit, preserveOnExit = false } = config;
|
|
19
|
+
const lang = langInput ?? (0, registry_js_1.getCurrentLanguage)();
|
|
18
20
|
const state = (0, terminal_js_1.initTerminal)();
|
|
19
21
|
let inputValue = '';
|
|
20
22
|
let errorMsg = '';
|
|
@@ -26,20 +28,21 @@ async function showNumberInput(config) {
|
|
|
26
28
|
if (min !== undefined || max !== undefined) {
|
|
27
29
|
const constraints = [];
|
|
28
30
|
if (min !== undefined)
|
|
29
|
-
constraints.push(
|
|
31
|
+
constraints.push(`${(0, registry_js_1.t)('inputs.minValue')}: ${min}`);
|
|
30
32
|
if (max !== undefined)
|
|
31
|
-
constraints.push(
|
|
32
|
-
promptLine += ` ${colors_js_1.
|
|
33
|
+
constraints.push(`${(0, registry_js_1.t)('inputs.maxValue')}: ${max}`);
|
|
34
|
+
promptLine += ` ${colors_js_1.uiColors.textSecondary}(${constraints.join(', ')})${colors_js_1.colors.reset}`;
|
|
33
35
|
}
|
|
34
36
|
if (defaultValue !== undefined && !inputValue) {
|
|
35
|
-
|
|
37
|
+
const defaultLabel = lang === 'en' ? 'default' : (0, registry_js_1.t)('inputs.defaultValue');
|
|
38
|
+
promptLine += ` ${colors_js_1.uiColors.textSecondary}(${defaultLabel}: ${defaultValue})${colors_js_1.colors.reset}`;
|
|
36
39
|
}
|
|
37
|
-
promptLine += `: ${colors_js_1.
|
|
40
|
+
promptLine += `: ${colors_js_1.uiColors.primary}${inputValue}_${colors_js_1.colors.reset}`;
|
|
38
41
|
(0, terminal_js_1.writeLine)(promptLine);
|
|
39
42
|
lineCount++;
|
|
40
43
|
// Render error message if any
|
|
41
44
|
if (errorMsg) {
|
|
42
|
-
(0, terminal_js_1.writeLine)(` ${colors_js_1.
|
|
45
|
+
(0, terminal_js_1.writeLine)(` ${colors_js_1.uiColors.error}✗ ${errorMsg}${colors_js_1.colors.reset}`);
|
|
43
46
|
lineCount++;
|
|
44
47
|
}
|
|
45
48
|
state.renderedLines = lineCount;
|
|
@@ -52,13 +55,15 @@ async function showNumberInput(config) {
|
|
|
52
55
|
// Handle Ctrl+C
|
|
53
56
|
if ((0, keyboard_js_1.isCtrlC)(key)) {
|
|
54
57
|
state.stdin.removeListener('data', onData);
|
|
55
|
-
|
|
58
|
+
if (!preserveOnExit) {
|
|
59
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
60
|
+
}
|
|
56
61
|
(0, terminal_js_1.restoreTerminal)(state);
|
|
57
62
|
if (onExit) {
|
|
58
63
|
onExit();
|
|
59
64
|
}
|
|
60
65
|
else {
|
|
61
|
-
console.log(
|
|
66
|
+
console.log(`\n${(0, registry_js_1.t)('messages.goodbye')}`);
|
|
62
67
|
}
|
|
63
68
|
process.exit(0);
|
|
64
69
|
}
|
|
@@ -67,7 +72,7 @@ async function showNumberInput(config) {
|
|
|
67
72
|
const finalValue = inputValue || (defaultValue !== undefined ? String(defaultValue) : '');
|
|
68
73
|
// Check if empty
|
|
69
74
|
if (!finalValue) {
|
|
70
|
-
errorMsg = errorMessage || '
|
|
75
|
+
errorMsg = errorMessage || (0, registry_js_1.t)('inputs.enterNumber');
|
|
71
76
|
render();
|
|
72
77
|
return;
|
|
73
78
|
}
|
|
@@ -75,19 +80,19 @@ async function showNumberInput(config) {
|
|
|
75
80
|
const num = allowDecimals ? parseFloat(finalValue) : parseInt(finalValue, 10);
|
|
76
81
|
// Check if valid number
|
|
77
82
|
if (isNaN(num)) {
|
|
78
|
-
errorMsg = errorMessage || '
|
|
83
|
+
errorMsg = errorMessage || (0, registry_js_1.t)('inputs.invalidInput');
|
|
79
84
|
render();
|
|
80
85
|
return;
|
|
81
86
|
}
|
|
82
87
|
// Check min
|
|
83
88
|
if (min !== undefined && num < min) {
|
|
84
|
-
errorMsg = errorMessage ||
|
|
89
|
+
errorMsg = errorMessage || `${(0, registry_js_1.t)('inputs.minValue')}: ${min}`;
|
|
85
90
|
render();
|
|
86
91
|
return;
|
|
87
92
|
}
|
|
88
93
|
// Check max
|
|
89
94
|
if (max !== undefined && num > max) {
|
|
90
|
-
errorMsg = errorMessage ||
|
|
95
|
+
errorMsg = errorMessage || `${(0, registry_js_1.t)('inputs.maxValue')}: ${max}`;
|
|
91
96
|
render();
|
|
92
97
|
return;
|
|
93
98
|
}
|
|
@@ -95,7 +100,7 @@ async function showNumberInput(config) {
|
|
|
95
100
|
if (validate) {
|
|
96
101
|
const validationResult = validate(String(num));
|
|
97
102
|
if (validationResult !== true) {
|
|
98
|
-
errorMsg = typeof validationResult === 'string' ? validationResult : (errorMessage || '
|
|
103
|
+
errorMsg = typeof validationResult === 'string' ? validationResult : (errorMessage || (0, registry_js_1.t)('inputs.invalidInput'));
|
|
99
104
|
render();
|
|
100
105
|
return;
|
|
101
106
|
}
|
|
@@ -8,13 +8,36 @@ exports.showTextInput = showTextInput;
|
|
|
8
8
|
const terminal_js_1 = require("../../core/terminal.js");
|
|
9
9
|
const keyboard_js_1 = require("../../core/keyboard.js");
|
|
10
10
|
const colors_js_1 = require("../../core/colors.js");
|
|
11
|
+
const registry_js_1 = require("../../i18n/registry.js");
|
|
12
|
+
/**
|
|
13
|
+
* Normalize a terminal data chunk to printable text.
|
|
14
|
+
* Supports bracketed paste and ignores control/escape sequences.
|
|
15
|
+
*/
|
|
16
|
+
function normalizeTextChunk(raw) {
|
|
17
|
+
if (!raw) {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
let chunk = raw;
|
|
21
|
+
// Bracketed paste wrappers used by many terminals.
|
|
22
|
+
chunk = chunk.replace(/\x1b\[200~/g, '');
|
|
23
|
+
chunk = chunk.replace(/\x1b\[201~/g, '');
|
|
24
|
+
// Remove CSI escape sequences (arrows, function keys, etc.).
|
|
25
|
+
chunk = chunk.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, '');
|
|
26
|
+
// Remove remaining ESC-prefixed control bytes.
|
|
27
|
+
chunk = chunk.replace(/\x1b./g, '');
|
|
28
|
+
// Single-line input: strip CR/LF and other control chars.
|
|
29
|
+
chunk = chunk.replace(/[\r\n]/g, '');
|
|
30
|
+
chunk = chunk.replace(/[\x00-\x1F\x7F]/g, '');
|
|
31
|
+
return chunk;
|
|
32
|
+
}
|
|
11
33
|
/**
|
|
12
34
|
* Show a text input prompt
|
|
13
35
|
* @param config - Input configuration
|
|
14
36
|
* @returns Promise resolving to entered text
|
|
15
37
|
*/
|
|
16
38
|
async function showTextInput(config) {
|
|
17
|
-
const { prompt, defaultValue = '', placeholder, maxLength, minLength = 0, allowEmpty = false, validate, errorMessage, onExit } = config;
|
|
39
|
+
const { lang: langInput, prompt, defaultValue = '', placeholder, maxLength, minLength = 0, allowEmpty = false, validate, errorMessage, onExit, preserveOnExit = false } = config;
|
|
40
|
+
const lang = langInput ?? (0, registry_js_1.getCurrentLanguage)();
|
|
18
41
|
const state = (0, terminal_js_1.initTerminal)();
|
|
19
42
|
let inputValue = '';
|
|
20
43
|
let errorMsg = '';
|
|
@@ -24,17 +47,18 @@ async function showTextInput(config) {
|
|
|
24
47
|
// Render prompt with default value hint
|
|
25
48
|
let promptLine = ` ${prompt}`;
|
|
26
49
|
if (defaultValue && !inputValue) {
|
|
27
|
-
|
|
50
|
+
const defaultLabel = lang === 'en' ? 'default' : (0, registry_js_1.t)('inputs.defaultValue');
|
|
51
|
+
promptLine += ` ${colors_js_1.uiColors.textSecondary}(${defaultLabel}: ${defaultValue})${colors_js_1.colors.reset}`;
|
|
28
52
|
}
|
|
29
53
|
if (placeholder && !inputValue) {
|
|
30
|
-
promptLine += ` ${colors_js_1.
|
|
54
|
+
promptLine += ` ${colors_js_1.uiColors.textSecondary}${placeholder}${colors_js_1.colors.reset}`;
|
|
31
55
|
}
|
|
32
|
-
promptLine += `: ${colors_js_1.
|
|
56
|
+
promptLine += `: ${colors_js_1.uiColors.primary}${inputValue}_${colors_js_1.colors.reset}`;
|
|
33
57
|
(0, terminal_js_1.writeLine)(promptLine);
|
|
34
58
|
lineCount++;
|
|
35
59
|
// Render error message if any
|
|
36
60
|
if (errorMsg) {
|
|
37
|
-
(0, terminal_js_1.writeLine)(` ${colors_js_1.
|
|
61
|
+
(0, terminal_js_1.writeLine)(` ${colors_js_1.uiColors.error}✗ ${errorMsg}${colors_js_1.colors.reset}`);
|
|
38
62
|
lineCount++;
|
|
39
63
|
}
|
|
40
64
|
state.renderedLines = lineCount;
|
|
@@ -47,13 +71,15 @@ async function showTextInput(config) {
|
|
|
47
71
|
// Handle Ctrl+C
|
|
48
72
|
if ((0, keyboard_js_1.isCtrlC)(key)) {
|
|
49
73
|
state.stdin.removeListener('data', onData);
|
|
50
|
-
|
|
74
|
+
if (!preserveOnExit) {
|
|
75
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
76
|
+
}
|
|
51
77
|
(0, terminal_js_1.restoreTerminal)(state);
|
|
52
78
|
if (onExit) {
|
|
53
79
|
onExit();
|
|
54
80
|
}
|
|
55
81
|
else {
|
|
56
|
-
console.log(
|
|
82
|
+
console.log(`\n${(0, registry_js_1.t)('messages.goodbye')}`);
|
|
57
83
|
}
|
|
58
84
|
process.exit(0);
|
|
59
85
|
}
|
|
@@ -62,13 +88,13 @@ async function showTextInput(config) {
|
|
|
62
88
|
const finalValue = inputValue || defaultValue;
|
|
63
89
|
// Check if empty
|
|
64
90
|
if (!allowEmpty && !finalValue) {
|
|
65
|
-
errorMsg = errorMessage || '
|
|
91
|
+
errorMsg = errorMessage || (0, registry_js_1.t)('inputs.cannotBeEmpty');
|
|
66
92
|
render();
|
|
67
93
|
return;
|
|
68
94
|
}
|
|
69
95
|
// Check min length
|
|
70
96
|
if (minLength && finalValue.length < minLength) {
|
|
71
|
-
errorMsg = errorMessage ||
|
|
97
|
+
errorMsg = errorMessage || `${(0, registry_js_1.t)('inputs.minLength')}: ${minLength}`;
|
|
72
98
|
render();
|
|
73
99
|
return;
|
|
74
100
|
}
|
|
@@ -76,7 +102,7 @@ async function showTextInput(config) {
|
|
|
76
102
|
if (validate) {
|
|
77
103
|
const validationResult = validate(finalValue);
|
|
78
104
|
if (validationResult !== true) {
|
|
79
|
-
errorMsg = typeof validationResult === 'string' ? validationResult : (errorMessage || '
|
|
105
|
+
errorMsg = typeof validationResult === 'string' ? validationResult : (errorMessage || (0, registry_js_1.t)('inputs.invalidInput'));
|
|
80
106
|
render();
|
|
81
107
|
return;
|
|
82
108
|
}
|
|
@@ -97,13 +123,24 @@ async function showTextInput(config) {
|
|
|
97
123
|
}
|
|
98
124
|
return;
|
|
99
125
|
}
|
|
100
|
-
// Handle
|
|
101
|
-
|
|
126
|
+
// Handle single-key typing and multi-char paste chunks.
|
|
127
|
+
const chunk = (0, keyboard_js_1.isPrintable)(key) ? key : normalizeTextChunk(key);
|
|
128
|
+
if (chunk.length > 0) {
|
|
102
129
|
// Check max length
|
|
103
130
|
if (maxLength && inputValue.length >= maxLength) {
|
|
104
131
|
return;
|
|
105
132
|
}
|
|
106
|
-
|
|
133
|
+
let next = chunk;
|
|
134
|
+
if (maxLength) {
|
|
135
|
+
const remaining = maxLength - inputValue.length;
|
|
136
|
+
if (remaining <= 0) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (next.length > remaining) {
|
|
140
|
+
next = next.slice(0, remaining);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
inputValue += next;
|
|
107
144
|
errorMsg = ''; // Clear error on edit
|
|
108
145
|
render();
|
|
109
146
|
return;
|
|
@@ -15,30 +15,36 @@ const registry_js_1 = require("../../i18n/registry.js");
|
|
|
15
15
|
* @returns Promise resolving to boolean result
|
|
16
16
|
*/
|
|
17
17
|
async function showBooleanMenu(config) {
|
|
18
|
-
const { question, defaultValue = true, yesText = (0, registry_js_1.t)('menus.yes'), noText = (0, registry_js_1.t)('menus.no'), orientation = 'horizontal', onExit, preserveOnSelect = false } = config;
|
|
18
|
+
const { question, helperText, defaultValue = true, yesText = (0, registry_js_1.t)('menus.yes'), noText = (0, registry_js_1.t)('menus.no'), orientation = 'horizontal', onExit, preserveOnSelect = false } = config;
|
|
19
|
+
const preserveOnExit = config.preserveOnExit ?? preserveOnSelect;
|
|
19
20
|
if (orientation === 'horizontal') {
|
|
20
|
-
return showBooleanMenuHorizontal(question, defaultValue, yesText, noText, onExit, preserveOnSelect);
|
|
21
|
+
return showBooleanMenuHorizontal(question, helperText, defaultValue, yesText, noText, onExit, preserveOnSelect, preserveOnExit);
|
|
21
22
|
}
|
|
22
23
|
else {
|
|
23
|
-
return showBooleanMenuVertical(question, defaultValue, yesText, noText, onExit, preserveOnSelect);
|
|
24
|
+
return showBooleanMenuVertical(question, helperText, defaultValue, yesText, noText, onExit, preserveOnSelect, preserveOnExit);
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
/**
|
|
27
28
|
* Show horizontal boolean menu (side by side)
|
|
28
29
|
*/
|
|
29
|
-
async function showBooleanMenuHorizontal(question, defaultValue, yesText, noText, onExit, preserveOnSelect = false) {
|
|
30
|
+
async function showBooleanMenuHorizontal(question, helperText, defaultValue, yesText, noText, onExit, preserveOnSelect = false, preserveOnExit = false) {
|
|
30
31
|
const state = (0, terminal_js_1.initTerminal)();
|
|
31
32
|
let selected = defaultValue;
|
|
32
33
|
const render = () => {
|
|
33
34
|
(0, terminal_js_1.clearMenu)(state);
|
|
34
35
|
// Render question with options on same line
|
|
35
36
|
const yesOption = selected
|
|
36
|
-
? `${colors_js_1.
|
|
37
|
-
: `${colors_js_1.
|
|
37
|
+
? `${colors_js_1.uiColors.primary}${yesText}${colors_js_1.colors.reset}`
|
|
38
|
+
: `${colors_js_1.uiColors.textSecondary}${yesText}${colors_js_1.colors.reset}`;
|
|
38
39
|
const noOption = !selected
|
|
39
|
-
? `${colors_js_1.
|
|
40
|
-
: `${colors_js_1.
|
|
41
|
-
(0, terminal_js_1.writeLine)(`${colors_js_1.
|
|
40
|
+
? `${colors_js_1.uiColors.primary}${noText}${colors_js_1.colors.reset}`
|
|
41
|
+
: `${colors_js_1.uiColors.textSecondary}${noText}${colors_js_1.colors.reset}`;
|
|
42
|
+
(0, terminal_js_1.writeLine)(`${colors_js_1.uiColors.warning}?${colors_js_1.colors.reset} ${question} ${yesOption} | ${noOption}`);
|
|
43
|
+
if (helperText && helperText.trim().length > 0) {
|
|
44
|
+
(0, terminal_js_1.writeLine)(` ${colors_js_1.uiColors.textSecondary}${helperText}${colors_js_1.colors.reset}`);
|
|
45
|
+
state.renderedLines = 2;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
42
48
|
state.renderedLines = 1;
|
|
43
49
|
};
|
|
44
50
|
// Initial render
|
|
@@ -49,13 +55,15 @@ async function showBooleanMenuHorizontal(question, defaultValue, yesText, noText
|
|
|
49
55
|
// Handle Ctrl+C
|
|
50
56
|
if ((0, keyboard_js_1.isCtrlC)(key)) {
|
|
51
57
|
state.stdin.removeListener('data', onData);
|
|
52
|
-
|
|
58
|
+
if (!preserveOnExit) {
|
|
59
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
60
|
+
}
|
|
53
61
|
(0, terminal_js_1.restoreTerminal)(state);
|
|
54
62
|
if (onExit) {
|
|
55
63
|
onExit();
|
|
56
64
|
}
|
|
57
65
|
else {
|
|
58
|
-
console.log(
|
|
66
|
+
console.log(`\n${(0, registry_js_1.t)('messages.goodbye')}`);
|
|
59
67
|
}
|
|
60
68
|
process.exit(0);
|
|
61
69
|
}
|
|
@@ -99,23 +107,26 @@ async function showBooleanMenuHorizontal(question, defaultValue, yesText, noText
|
|
|
99
107
|
/**
|
|
100
108
|
* Show vertical boolean menu (stacked)
|
|
101
109
|
*/
|
|
102
|
-
async function showBooleanMenuVertical(question, defaultValue, yesText, noText, onExit, preserveOnSelect = false) {
|
|
110
|
+
async function showBooleanMenuVertical(question, helperText, defaultValue, yesText, noText, onExit, preserveOnSelect = false, preserveOnExit = false) {
|
|
103
111
|
const state = (0, terminal_js_1.initTerminal)();
|
|
104
112
|
let selected = defaultValue;
|
|
105
113
|
const render = () => {
|
|
106
114
|
(0, terminal_js_1.clearMenu)(state);
|
|
107
115
|
// Render question
|
|
108
|
-
(0, terminal_js_1.writeLine)(`${colors_js_1.
|
|
116
|
+
(0, terminal_js_1.writeLine)(`${colors_js_1.uiColors.warning}?${colors_js_1.colors.reset} ${question}`);
|
|
117
|
+
if (helperText && helperText.trim().length > 0) {
|
|
118
|
+
(0, terminal_js_1.writeLine)(` ${colors_js_1.uiColors.textSecondary}${helperText}${colors_js_1.colors.reset}`);
|
|
119
|
+
}
|
|
109
120
|
(0, terminal_js_1.writeLine)('');
|
|
110
121
|
// Render yes option
|
|
111
|
-
const yesCursor = selected ? `${colors_js_1.
|
|
112
|
-
const yesColor = selected ? colors_js_1.
|
|
122
|
+
const yesCursor = selected ? `${colors_js_1.uiColors.cursor}❯ ${colors_js_1.colors.reset}` : ' ';
|
|
123
|
+
const yesColor = selected ? colors_js_1.uiColors.primary : colors_js_1.uiColors.textPrimary;
|
|
113
124
|
(0, terminal_js_1.writeLine)(`${yesCursor}${yesColor}${yesText}${colors_js_1.colors.reset}`);
|
|
114
125
|
// Render no option
|
|
115
|
-
const noCursor = !selected ? `${colors_js_1.
|
|
116
|
-
const noColor = !selected ? colors_js_1.
|
|
126
|
+
const noCursor = !selected ? `${colors_js_1.uiColors.cursor}❯ ${colors_js_1.colors.reset}` : ' ';
|
|
127
|
+
const noColor = !selected ? colors_js_1.uiColors.primary : colors_js_1.uiColors.textPrimary;
|
|
117
128
|
(0, terminal_js_1.writeLine)(`${noCursor}${noColor}${noText}${colors_js_1.colors.reset}`);
|
|
118
|
-
state.renderedLines = 4;
|
|
129
|
+
state.renderedLines = helperText && helperText.trim().length > 0 ? 5 : 4;
|
|
119
130
|
};
|
|
120
131
|
// Initial render
|
|
121
132
|
render();
|
|
@@ -125,13 +136,15 @@ async function showBooleanMenuVertical(question, defaultValue, yesText, noText,
|
|
|
125
136
|
// Handle Ctrl+C
|
|
126
137
|
if ((0, keyboard_js_1.isCtrlC)(key)) {
|
|
127
138
|
state.stdin.removeListener('data', onData);
|
|
128
|
-
|
|
139
|
+
if (!preserveOnExit) {
|
|
140
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
141
|
+
}
|
|
129
142
|
(0, terminal_js_1.restoreTerminal)(state);
|
|
130
143
|
if (onExit) {
|
|
131
144
|
onExit();
|
|
132
145
|
}
|
|
133
146
|
else {
|
|
134
|
-
console.log(
|
|
147
|
+
console.log(`\n${(0, registry_js_1.t)('messages.goodbye')}`);
|
|
135
148
|
}
|
|
136
149
|
process.exit(0);
|
|
137
150
|
}
|
|
@@ -17,6 +17,7 @@ const registry_js_1 = require("../../i18n/registry.js");
|
|
|
17
17
|
*/
|
|
18
18
|
async function showCheckboxMenu(config, hints) {
|
|
19
19
|
const { options, title, prompt, defaultSelected = [], minSelections = 0, maxSelections, allowSelectAll = true, allowInvert = true, separatorWidth = 30, onExit, preserveOnSelect = false } = config;
|
|
20
|
+
const preserveOnExit = config.preserveOnExit ?? preserveOnSelect;
|
|
20
21
|
// Use i18n for default prompt if not provided
|
|
21
22
|
const displayPrompt = prompt || (0, registry_js_1.t)('menus.multiSelectPrompt');
|
|
22
23
|
// Validate options
|
|
@@ -113,13 +114,15 @@ async function showCheckboxMenu(config, hints) {
|
|
|
113
114
|
// Handle Ctrl+C
|
|
114
115
|
if ((0, keyboard_js_1.isCtrlC)(key)) {
|
|
115
116
|
state.stdin.removeListener('data', onData);
|
|
116
|
-
|
|
117
|
+
if (!preserveOnExit) {
|
|
118
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
119
|
+
}
|
|
117
120
|
(0, terminal_js_1.restoreTerminal)(state);
|
|
118
121
|
if (onExit) {
|
|
119
122
|
onExit();
|
|
120
123
|
}
|
|
121
124
|
else {
|
|
122
|
-
console.log(
|
|
125
|
+
console.log(`\n${(0, registry_js_1.t)('messages.goodbye')}`);
|
|
123
126
|
}
|
|
124
127
|
process.exit(0);
|
|
125
128
|
}
|