cli-menu-kit 0.1.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/LICENSE +21 -0
- package/README.md +716 -0
- package/dist/api.d.ts +158 -0
- package/dist/api.js +128 -0
- package/dist/components/display/headers.d.ts +32 -0
- package/dist/components/display/headers.js +84 -0
- package/dist/components/display/index.d.ts +8 -0
- package/dist/components/display/index.js +31 -0
- package/dist/components/display/messages.d.ts +41 -0
- package/dist/components/display/messages.js +90 -0
- package/dist/components/display/progress.d.ts +41 -0
- package/dist/components/display/progress.js +82 -0
- package/dist/components/display/summary.d.ts +33 -0
- package/dist/components/display/summary.js +82 -0
- package/dist/components/inputs/index.d.ts +8 -0
- package/dist/components/inputs/index.js +15 -0
- package/dist/components/inputs/language-input.d.ts +11 -0
- package/dist/components/inputs/language-input.js +104 -0
- package/dist/components/inputs/modify-field.d.ts +11 -0
- package/dist/components/inputs/modify-field.js +42 -0
- package/dist/components/inputs/number-input.d.ts +11 -0
- package/dist/components/inputs/number-input.js +143 -0
- package/dist/components/inputs/text-input.d.ts +11 -0
- package/dist/components/inputs/text-input.js +114 -0
- package/dist/components/menus/boolean-menu.d.ts +11 -0
- package/dist/components/menus/boolean-menu.js +169 -0
- package/dist/components/menus/checkbox-menu.d.ts +11 -0
- package/dist/components/menus/checkbox-menu.js +161 -0
- package/dist/components/menus/index.d.ts +7 -0
- package/dist/components/menus/index.js +13 -0
- package/dist/components/menus/radio-menu.d.ts +11 -0
- package/dist/components/menus/radio-menu.js +158 -0
- package/dist/components.d.ts +55 -0
- package/dist/components.js +166 -0
- package/dist/core/colors.d.ts +64 -0
- package/dist/core/colors.js +139 -0
- package/dist/core/keyboard.d.ts +124 -0
- package/dist/core/keyboard.js +185 -0
- package/dist/core/renderer.d.ts +74 -0
- package/dist/core/renderer.js +217 -0
- package/dist/core/terminal.d.ts +89 -0
- package/dist/core/terminal.js +170 -0
- package/dist/features/commands.d.ts +57 -0
- package/dist/features/commands.js +161 -0
- package/dist/features/wizard.d.ts +62 -0
- package/dist/features/wizard.js +112 -0
- package/dist/i18n/languages/en.d.ts +5 -0
- package/dist/i18n/languages/en.js +54 -0
- package/dist/i18n/languages/zh.d.ts +5 -0
- package/dist/i18n/languages/zh.js +54 -0
- package/dist/i18n/registry.d.ts +36 -0
- package/dist/i18n/registry.js +82 -0
- package/dist/i18n/types.d.ts +65 -0
- package/dist/i18n/types.js +5 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +104 -0
- package/dist/input.d.ts +40 -0
- package/dist/input.js +211 -0
- package/dist/menu-core.d.ts +52 -0
- package/dist/menu-core.js +201 -0
- package/dist/menu-multi.d.ts +21 -0
- package/dist/menu-multi.js +119 -0
- package/dist/menu-single.d.ts +18 -0
- package/dist/menu-single.js +138 -0
- package/dist/menu.d.ts +66 -0
- package/dist/menu.js +78 -0
- package/dist/types/display.types.d.ts +57 -0
- package/dist/types/display.types.js +5 -0
- package/dist/types/input.types.d.ts +88 -0
- package/dist/types/input.types.js +5 -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 +85 -0
- package/dist/types/menu.types.js +5 -0
- package/dist/types.d.ts +49 -0
- package/dist/types.js +5 -0
- package/package.json +35 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BooleanMenu - Yes/No selection component
|
|
3
|
+
* Supports both horizontal and vertical orientations
|
|
4
|
+
*/
|
|
5
|
+
import { BooleanMenuConfig, BooleanMenuResult } from '../../types/menu.types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Show a boolean menu (yes/no selection)
|
|
8
|
+
* @param config - Menu configuration
|
|
9
|
+
* @returns Promise resolving to boolean result
|
|
10
|
+
*/
|
|
11
|
+
export declare function showBooleanMenu(config: BooleanMenuConfig): Promise<BooleanMenuResult>;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* BooleanMenu - Yes/No selection component
|
|
4
|
+
* Supports both horizontal and vertical orientations
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.showBooleanMenu = showBooleanMenu;
|
|
8
|
+
const terminal_js_1 = require("../../core/terminal.js");
|
|
9
|
+
const keyboard_js_1 = require("../../core/keyboard.js");
|
|
10
|
+
const colors_js_1 = require("../../core/colors.js");
|
|
11
|
+
/**
|
|
12
|
+
* Show a boolean menu (yes/no selection)
|
|
13
|
+
* @param config - Menu configuration
|
|
14
|
+
* @returns Promise resolving to boolean result
|
|
15
|
+
*/
|
|
16
|
+
async function showBooleanMenu(config) {
|
|
17
|
+
const { question, defaultValue = true, yesText = 'ęÆ', noText = 'å¦', orientation = 'horizontal', onExit } = config;
|
|
18
|
+
if (orientation === 'horizontal') {
|
|
19
|
+
return showBooleanMenuHorizontal(question, defaultValue, yesText, noText, onExit);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
return showBooleanMenuVertical(question, defaultValue, yesText, noText, onExit);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Show horizontal boolean menu (side by side)
|
|
27
|
+
*/
|
|
28
|
+
async function showBooleanMenuHorizontal(question, defaultValue, yesText, noText, onExit) {
|
|
29
|
+
const state = (0, terminal_js_1.initTerminal)();
|
|
30
|
+
let selected = defaultValue;
|
|
31
|
+
const render = () => {
|
|
32
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
33
|
+
// Render question with options on same line
|
|
34
|
+
const yesOption = selected
|
|
35
|
+
? `${colors_js_1.colors.cyan}${yesText}${colors_js_1.colors.reset}`
|
|
36
|
+
: `${colors_js_1.colors.dim}${yesText}${colors_js_1.colors.reset}`;
|
|
37
|
+
const noOption = !selected
|
|
38
|
+
? `${colors_js_1.colors.cyan}${noText}${colors_js_1.colors.reset}`
|
|
39
|
+
: `${colors_js_1.colors.dim}${noText}${colors_js_1.colors.reset}`;
|
|
40
|
+
(0, terminal_js_1.writeLine)(`${colors_js_1.colors.yellow}?${colors_js_1.colors.reset} ${question} ${yesOption} | ${noOption}`);
|
|
41
|
+
state.renderedLines = 1;
|
|
42
|
+
};
|
|
43
|
+
// Initial render
|
|
44
|
+
render();
|
|
45
|
+
// Handle keyboard input
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
const onData = (key) => {
|
|
48
|
+
// Handle Ctrl+C
|
|
49
|
+
if ((0, keyboard_js_1.isCtrlC)(key)) {
|
|
50
|
+
state.stdin.removeListener('data', onData);
|
|
51
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
52
|
+
(0, terminal_js_1.restoreTerminal)(state);
|
|
53
|
+
if (onExit) {
|
|
54
|
+
onExit();
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log('\nš åč§!');
|
|
58
|
+
}
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
// Handle Enter
|
|
62
|
+
if ((0, keyboard_js_1.isEnter)(key)) {
|
|
63
|
+
state.stdin.removeListener('data', onData);
|
|
64
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
65
|
+
(0, terminal_js_1.restoreTerminal)(state);
|
|
66
|
+
resolve(selected);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Handle left/right arrows
|
|
70
|
+
if (key === keyboard_js_1.KEY_CODES.LEFT) {
|
|
71
|
+
selected = true;
|
|
72
|
+
render();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (key === keyboard_js_1.KEY_CODES.RIGHT) {
|
|
76
|
+
selected = false;
|
|
77
|
+
render();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Handle Y/N keys
|
|
81
|
+
const letter = (0, keyboard_js_1.normalizeLetter)(key);
|
|
82
|
+
if (letter === 'y') {
|
|
83
|
+
selected = true;
|
|
84
|
+
render();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (letter === 'n') {
|
|
88
|
+
selected = false;
|
|
89
|
+
render();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
state.stdin.on('data', onData);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Show vertical boolean menu (stacked)
|
|
98
|
+
*/
|
|
99
|
+
async function showBooleanMenuVertical(question, defaultValue, yesText, noText, onExit) {
|
|
100
|
+
const state = (0, terminal_js_1.initTerminal)();
|
|
101
|
+
let selected = defaultValue;
|
|
102
|
+
const render = () => {
|
|
103
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
104
|
+
// Render question
|
|
105
|
+
(0, terminal_js_1.writeLine)(`${colors_js_1.colors.yellow}?${colors_js_1.colors.reset} ${question}`);
|
|
106
|
+
(0, terminal_js_1.writeLine)('');
|
|
107
|
+
// Render yes option
|
|
108
|
+
const yesCursor = selected ? `${colors_js_1.colors.cyan}⯠${colors_js_1.colors.reset}` : ' ';
|
|
109
|
+
const yesColor = selected ? colors_js_1.colors.cyan : colors_js_1.colors.reset;
|
|
110
|
+
(0, terminal_js_1.writeLine)(`${yesCursor}${yesColor}${yesText}${colors_js_1.colors.reset}`);
|
|
111
|
+
// Render no option
|
|
112
|
+
const noCursor = !selected ? `${colors_js_1.colors.cyan}⯠${colors_js_1.colors.reset}` : ' ';
|
|
113
|
+
const noColor = !selected ? colors_js_1.colors.cyan : colors_js_1.colors.reset;
|
|
114
|
+
(0, terminal_js_1.writeLine)(`${noCursor}${noColor}${noText}${colors_js_1.colors.reset}`);
|
|
115
|
+
state.renderedLines = 4;
|
|
116
|
+
};
|
|
117
|
+
// Initial render
|
|
118
|
+
render();
|
|
119
|
+
// Handle keyboard input
|
|
120
|
+
return new Promise((resolve) => {
|
|
121
|
+
const onData = (key) => {
|
|
122
|
+
// Handle Ctrl+C
|
|
123
|
+
if ((0, keyboard_js_1.isCtrlC)(key)) {
|
|
124
|
+
state.stdin.removeListener('data', onData);
|
|
125
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
126
|
+
(0, terminal_js_1.restoreTerminal)(state);
|
|
127
|
+
if (onExit) {
|
|
128
|
+
onExit();
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
console.log('\nš åč§!');
|
|
132
|
+
}
|
|
133
|
+
process.exit(0);
|
|
134
|
+
}
|
|
135
|
+
// Handle Enter
|
|
136
|
+
if ((0, keyboard_js_1.isEnter)(key)) {
|
|
137
|
+
state.stdin.removeListener('data', onData);
|
|
138
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
139
|
+
(0, terminal_js_1.restoreTerminal)(state);
|
|
140
|
+
resolve(selected);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Handle up/down arrows
|
|
144
|
+
if (key === keyboard_js_1.KEY_CODES.UP) {
|
|
145
|
+
selected = true;
|
|
146
|
+
render();
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (key === keyboard_js_1.KEY_CODES.DOWN) {
|
|
150
|
+
selected = false;
|
|
151
|
+
render();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// Handle Y/N keys
|
|
155
|
+
const letter = (0, keyboard_js_1.normalizeLetter)(key);
|
|
156
|
+
if (letter === 'y') {
|
|
157
|
+
selected = true;
|
|
158
|
+
render();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (letter === 'n') {
|
|
162
|
+
selected = false;
|
|
163
|
+
render();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
state.stdin.on('data', onData);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CheckboxMenu - Multi-select vertical menu component
|
|
3
|
+
* Allows user to select multiple options from a list
|
|
4
|
+
*/
|
|
5
|
+
import { CheckboxMenuConfig, CheckboxMenuResult } from '../../types/menu.types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Show a checkbox menu (multi-select)
|
|
8
|
+
* @param config - Menu configuration
|
|
9
|
+
* @returns Promise resolving to selected options
|
|
10
|
+
*/
|
|
11
|
+
export declare function showCheckboxMenu(config: CheckboxMenuConfig): Promise<CheckboxMenuResult>;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CheckboxMenu - Multi-select vertical menu component
|
|
4
|
+
* Allows user to select multiple options from a list
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.showCheckboxMenu = showCheckboxMenu;
|
|
8
|
+
const layout_types_js_1 = require("../../types/layout.types.js");
|
|
9
|
+
const terminal_js_1 = require("../../core/terminal.js");
|
|
10
|
+
const keyboard_js_1 = require("../../core/keyboard.js");
|
|
11
|
+
const renderer_js_1 = require("../../core/renderer.js");
|
|
12
|
+
/**
|
|
13
|
+
* Show a checkbox menu (multi-select)
|
|
14
|
+
* @param config - Menu configuration
|
|
15
|
+
* @returns Promise resolving to selected options
|
|
16
|
+
*/
|
|
17
|
+
async function showCheckboxMenu(config) {
|
|
18
|
+
const { options, title, prompt = 'ē©ŗę ¼éäø/åę¶,å车甮认', hints = ['āā ę¹åé®', 'ē©ŗę ¼ éäø/åę¶', 'A å
Øé', 'I åé', 'ā 甮认'], layout = { ...layout_types_js_1.LAYOUT_PRESETS.SUB_MENU, order: ['input', 'options', 'hints'] }, defaultSelected = [], minSelections = 0, maxSelections, allowSelectAll = true, allowInvert = true, onExit } = config;
|
|
19
|
+
// Validate options
|
|
20
|
+
if (!options || options.length === 0) {
|
|
21
|
+
throw new Error('CheckboxMenu requires at least one option');
|
|
22
|
+
}
|
|
23
|
+
// Initialize state
|
|
24
|
+
let cursorIndex = 0;
|
|
25
|
+
const selected = new Set(defaultSelected);
|
|
26
|
+
const state = (0, terminal_js_1.initTerminal)();
|
|
27
|
+
// Extract option values
|
|
28
|
+
const optionValues = options.map(opt => typeof opt === 'string' ? opt : opt.label);
|
|
29
|
+
// Render function
|
|
30
|
+
const render = () => {
|
|
31
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
32
|
+
let lineCount = 0;
|
|
33
|
+
// Render based on layout order
|
|
34
|
+
layout.order.forEach(element => {
|
|
35
|
+
// Add spacing before element
|
|
36
|
+
const spacingKey = `before${element.charAt(0).toUpperCase() + element.slice(1)}`;
|
|
37
|
+
if (layout.spacing?.[spacingKey]) {
|
|
38
|
+
(0, renderer_js_1.renderBlankLines)(layout.spacing[spacingKey]);
|
|
39
|
+
lineCount += layout.spacing[spacingKey];
|
|
40
|
+
}
|
|
41
|
+
switch (element) {
|
|
42
|
+
case 'input':
|
|
43
|
+
if (layout.visible.input) {
|
|
44
|
+
const selectedCount = selected.size;
|
|
45
|
+
const displayValue = `${selectedCount} 锹已é`;
|
|
46
|
+
(0, renderer_js_1.renderInputPrompt)(prompt, displayValue);
|
|
47
|
+
lineCount++;
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
case 'options':
|
|
51
|
+
optionValues.forEach((option, index) => {
|
|
52
|
+
(0, renderer_js_1.renderOption)(option, selected.has(index), index === cursorIndex);
|
|
53
|
+
lineCount++;
|
|
54
|
+
});
|
|
55
|
+
break;
|
|
56
|
+
case 'hints':
|
|
57
|
+
if (layout.visible.hints && hints.length > 0) {
|
|
58
|
+
(0, renderer_js_1.renderHints)(hints);
|
|
59
|
+
lineCount++;
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
// Add spacing after element
|
|
64
|
+
const afterSpacingKey = `after${element.charAt(0).toUpperCase() + element.slice(1)}`;
|
|
65
|
+
if (layout.spacing?.[afterSpacingKey]) {
|
|
66
|
+
(0, renderer_js_1.renderBlankLines)(layout.spacing[afterSpacingKey]);
|
|
67
|
+
lineCount += layout.spacing[afterSpacingKey];
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
state.renderedLines = lineCount;
|
|
71
|
+
};
|
|
72
|
+
// Initial render
|
|
73
|
+
render();
|
|
74
|
+
// Handle keyboard input
|
|
75
|
+
return new Promise((resolve) => {
|
|
76
|
+
const onData = (key) => {
|
|
77
|
+
// Handle Ctrl+C
|
|
78
|
+
if ((0, keyboard_js_1.isCtrlC)(key)) {
|
|
79
|
+
state.stdin.removeListener('data', onData);
|
|
80
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
81
|
+
(0, terminal_js_1.restoreTerminal)(state);
|
|
82
|
+
if (onExit) {
|
|
83
|
+
onExit();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log('\nš åč§!');
|
|
87
|
+
}
|
|
88
|
+
process.exit(0);
|
|
89
|
+
}
|
|
90
|
+
// Handle Enter
|
|
91
|
+
if ((0, keyboard_js_1.isEnter)(key)) {
|
|
92
|
+
// Validate minimum selections
|
|
93
|
+
if (selected.size < minSelections) {
|
|
94
|
+
// TODO: Show error message
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
state.stdin.removeListener('data', onData);
|
|
98
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
99
|
+
(0, terminal_js_1.restoreTerminal)(state);
|
|
100
|
+
const indices = Array.from(selected).sort((a, b) => a - b);
|
|
101
|
+
const values = indices.map(i => {
|
|
102
|
+
const option = options[i];
|
|
103
|
+
return typeof option === 'string' ? option : option.value || option.label;
|
|
104
|
+
});
|
|
105
|
+
resolve({ indices, values });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Handle Space (toggle selection)
|
|
109
|
+
if ((0, keyboard_js_1.isSpace)(key)) {
|
|
110
|
+
if (selected.has(cursorIndex)) {
|
|
111
|
+
selected.delete(cursorIndex);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Check max selections
|
|
115
|
+
if (!maxSelections || selected.size < maxSelections) {
|
|
116
|
+
selected.add(cursorIndex);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
render();
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Handle arrow keys
|
|
123
|
+
if (key === keyboard_js_1.KEY_CODES.UP) {
|
|
124
|
+
cursorIndex = cursorIndex > 0 ? cursorIndex - 1 : options.length - 1;
|
|
125
|
+
render();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (key === keyboard_js_1.KEY_CODES.DOWN) {
|
|
129
|
+
cursorIndex = cursorIndex < options.length - 1 ? cursorIndex + 1 : 0;
|
|
130
|
+
render();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Handle 'A' (select all)
|
|
134
|
+
if (allowSelectAll && (0, keyboard_js_1.normalizeLetter)(key) === 'a') {
|
|
135
|
+
if (!maxSelections || maxSelections >= options.length) {
|
|
136
|
+
for (let i = 0; i < options.length; i++) {
|
|
137
|
+
selected.add(i);
|
|
138
|
+
}
|
|
139
|
+
render();
|
|
140
|
+
}
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
// Handle 'I' (invert selection)
|
|
144
|
+
if (allowInvert && (0, keyboard_js_1.normalizeLetter)(key) === 'i') {
|
|
145
|
+
const newSelected = new Set();
|
|
146
|
+
for (let i = 0; i < options.length; i++) {
|
|
147
|
+
if (!selected.has(i)) {
|
|
148
|
+
if (!maxSelections || newSelected.size < maxSelections) {
|
|
149
|
+
newSelected.add(i);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
selected.clear();
|
|
154
|
+
newSelected.forEach(i => selected.add(i));
|
|
155
|
+
render();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
state.stdin.on('data', onData);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Menu components index
|
|
4
|
+
* Exports all menu component functions
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.showBooleanMenu = exports.showCheckboxMenu = exports.showRadioMenu = void 0;
|
|
8
|
+
var radio_menu_js_1 = require("./radio-menu.js");
|
|
9
|
+
Object.defineProperty(exports, "showRadioMenu", { enumerable: true, get: function () { return radio_menu_js_1.showRadioMenu; } });
|
|
10
|
+
var checkbox_menu_js_1 = require("./checkbox-menu.js");
|
|
11
|
+
Object.defineProperty(exports, "showCheckboxMenu", { enumerable: true, get: function () { return checkbox_menu_js_1.showCheckboxMenu; } });
|
|
12
|
+
var boolean_menu_js_1 = require("./boolean-menu.js");
|
|
13
|
+
Object.defineProperty(exports, "showBooleanMenu", { enumerable: true, get: function () { return boolean_menu_js_1.showBooleanMenu; } });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RadioMenu - Single-select vertical menu component
|
|
3
|
+
* Allows user to select one option from a list
|
|
4
|
+
*/
|
|
5
|
+
import { RadioMenuConfig, RadioMenuResult } from '../../types/menu.types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Show a radio menu (single-select)
|
|
8
|
+
* @param config - Menu configuration
|
|
9
|
+
* @returns Promise resolving to selected option
|
|
10
|
+
*/
|
|
11
|
+
export declare function showRadioMenu(config: RadioMenuConfig): Promise<RadioMenuResult>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* RadioMenu - Single-select vertical menu component
|
|
4
|
+
* Allows user to select one option from a list
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.showRadioMenu = showRadioMenu;
|
|
8
|
+
const layout_types_js_1 = require("../../types/layout.types.js");
|
|
9
|
+
const terminal_js_1 = require("../../core/terminal.js");
|
|
10
|
+
const keyboard_js_1 = require("../../core/keyboard.js");
|
|
11
|
+
const renderer_js_1 = require("../../core/renderer.js");
|
|
12
|
+
const colors_js_1 = require("../../core/colors.js");
|
|
13
|
+
/**
|
|
14
|
+
* Show a radio menu (single-select)
|
|
15
|
+
* @param config - Menu configuration
|
|
16
|
+
* @returns Promise resolving to selected option
|
|
17
|
+
*/
|
|
18
|
+
async function showRadioMenu(config) {
|
|
19
|
+
const { options, title, prompt = 'č¾å
„é锹ęēØāāéę©,å车甮认', hints = ['āā ę¹åé®', '0-9 č¾å
„åŗå·', 'ā 甮认'], layout = layout_types_js_1.LAYOUT_PRESETS.MAIN_MENU, defaultIndex = 0, allowNumberKeys = true, allowLetterKeys = false, onExit } = config;
|
|
20
|
+
// Validate options
|
|
21
|
+
if (!options || options.length === 0) {
|
|
22
|
+
throw new Error('RadioMenu requires at least one option');
|
|
23
|
+
}
|
|
24
|
+
// Initialize state
|
|
25
|
+
let selectedIndex = Math.max(0, Math.min(defaultIndex, options.length - 1));
|
|
26
|
+
const state = (0, terminal_js_1.initTerminal)();
|
|
27
|
+
// Extract option values
|
|
28
|
+
const optionValues = options.map(opt => typeof opt === 'string' ? opt : opt.label);
|
|
29
|
+
// Render function
|
|
30
|
+
const render = () => {
|
|
31
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
32
|
+
let lineCount = 0;
|
|
33
|
+
// Render based on layout order
|
|
34
|
+
layout.order.forEach(element => {
|
|
35
|
+
// Add spacing before element
|
|
36
|
+
const spacingKey = `before${element.charAt(0).toUpperCase() + element.slice(1)}`;
|
|
37
|
+
if (layout.spacing?.[spacingKey]) {
|
|
38
|
+
(0, renderer_js_1.renderBlankLines)(layout.spacing[spacingKey]);
|
|
39
|
+
lineCount += layout.spacing[spacingKey];
|
|
40
|
+
}
|
|
41
|
+
switch (element) {
|
|
42
|
+
case 'header':
|
|
43
|
+
if (layout.visible.header && title) {
|
|
44
|
+
(0, renderer_js_1.renderHeader)(` ${title}`, colors_js_1.colors.cyan);
|
|
45
|
+
lineCount++;
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
case 'options':
|
|
49
|
+
optionValues.forEach((option, index) => {
|
|
50
|
+
// Extract number prefix if exists
|
|
51
|
+
const match = option.match(/^(\d+)\.\s*/);
|
|
52
|
+
const prefix = match ? '' : `${index + 1}. `;
|
|
53
|
+
(0, renderer_js_1.renderOption)(option, false, index === selectedIndex, prefix);
|
|
54
|
+
lineCount++;
|
|
55
|
+
});
|
|
56
|
+
break;
|
|
57
|
+
case 'input':
|
|
58
|
+
if (layout.visible.input) {
|
|
59
|
+
// Calculate display value (current selection number)
|
|
60
|
+
let displayValue = '';
|
|
61
|
+
const currentOption = optionValues[selectedIndex];
|
|
62
|
+
const match = currentOption?.match(/^([^.]+)\./);
|
|
63
|
+
if (match) {
|
|
64
|
+
displayValue = match[1];
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
displayValue = String(selectedIndex + 1);
|
|
68
|
+
}
|
|
69
|
+
(0, renderer_js_1.renderInputPrompt)(prompt, displayValue);
|
|
70
|
+
lineCount++;
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
case 'hints':
|
|
74
|
+
if (layout.visible.hints && hints.length > 0) {
|
|
75
|
+
(0, renderer_js_1.renderHints)(hints);
|
|
76
|
+
lineCount++;
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
// Add spacing after element
|
|
81
|
+
const afterSpacingKey = `after${element.charAt(0).toUpperCase() + element.slice(1)}`;
|
|
82
|
+
if (layout.spacing?.[afterSpacingKey]) {
|
|
83
|
+
(0, renderer_js_1.renderBlankLines)(layout.spacing[afterSpacingKey]);
|
|
84
|
+
lineCount += layout.spacing[afterSpacingKey];
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
state.renderedLines = lineCount;
|
|
88
|
+
};
|
|
89
|
+
// Initial render
|
|
90
|
+
render();
|
|
91
|
+
// Handle keyboard input
|
|
92
|
+
return new Promise((resolve) => {
|
|
93
|
+
const onData = (key) => {
|
|
94
|
+
// Handle Ctrl+C
|
|
95
|
+
if ((0, keyboard_js_1.isCtrlC)(key)) {
|
|
96
|
+
state.stdin.removeListener('data', onData);
|
|
97
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
98
|
+
(0, terminal_js_1.restoreTerminal)(state);
|
|
99
|
+
if (onExit) {
|
|
100
|
+
onExit();
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.log('\nš åč§!');
|
|
104
|
+
}
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|
|
107
|
+
// Handle Enter
|
|
108
|
+
if ((0, keyboard_js_1.isEnter)(key)) {
|
|
109
|
+
state.stdin.removeListener('data', onData);
|
|
110
|
+
(0, terminal_js_1.clearMenu)(state);
|
|
111
|
+
(0, terminal_js_1.restoreTerminal)(state);
|
|
112
|
+
const selectedOption = options[selectedIndex];
|
|
113
|
+
const value = typeof selectedOption === 'string'
|
|
114
|
+
? selectedOption
|
|
115
|
+
: selectedOption.value || selectedOption.label;
|
|
116
|
+
resolve({
|
|
117
|
+
index: selectedIndex,
|
|
118
|
+
value
|
|
119
|
+
});
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// Handle arrow keys
|
|
123
|
+
if (key === keyboard_js_1.KEY_CODES.UP) {
|
|
124
|
+
selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : options.length - 1;
|
|
125
|
+
render();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (key === keyboard_js_1.KEY_CODES.DOWN) {
|
|
129
|
+
selectedIndex = selectedIndex < options.length - 1 ? selectedIndex + 1 : 0;
|
|
130
|
+
render();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
// Handle number keys
|
|
134
|
+
if (allowNumberKeys && (0, keyboard_js_1.isNumberKey)(key)) {
|
|
135
|
+
const num = parseInt(key, 10);
|
|
136
|
+
if (num > 0 && num <= options.length) {
|
|
137
|
+
selectedIndex = num - 1;
|
|
138
|
+
render();
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// Handle letter keys
|
|
143
|
+
if (allowLetterKeys && (0, keyboard_js_1.isLetterKey)(key)) {
|
|
144
|
+
const letter = (0, keyboard_js_1.normalizeLetter)(key);
|
|
145
|
+
const index = optionValues.findIndex(opt => {
|
|
146
|
+
const match = opt.match(/^([a-zA-Z])\./i);
|
|
147
|
+
return match && match[1].toLowerCase() === letter;
|
|
148
|
+
});
|
|
149
|
+
if (index !== -1) {
|
|
150
|
+
selectedIndex = index;
|
|
151
|
+
render();
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
state.stdin.on('data', onData);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Menu Kit - UI Components
|
|
3
|
+
* Handles all display-related UI components
|
|
4
|
+
*/
|
|
5
|
+
import { Colors, Theme, Symbols, HintKey } from './types';
|
|
6
|
+
export declare const colors: Colors;
|
|
7
|
+
export declare const theme: Theme;
|
|
8
|
+
export declare const symbols: Symbols;
|
|
9
|
+
/**
|
|
10
|
+
* Format section title with separator
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatSection(text: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Build hint text for interactive menus
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildHint(keys: HintKey[], lang?: 'zh' | 'en'): string;
|
|
17
|
+
/**
|
|
18
|
+
* Show submenu title with section format
|
|
19
|
+
*/
|
|
20
|
+
export declare function showSubMenuTitle(title: string, lang: 'zh' | 'en', indent?: string): void;
|
|
21
|
+
/**
|
|
22
|
+
* Show progress indicator
|
|
23
|
+
*/
|
|
24
|
+
export declare function showProgress(steps: string[], currentStep: number, lang: 'zh' | 'en', indent?: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Show info message
|
|
27
|
+
*/
|
|
28
|
+
export declare function showInfo(message: string, indent?: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Show success message
|
|
31
|
+
*/
|
|
32
|
+
export declare function showSuccess(message: string, indent?: string): void;
|
|
33
|
+
/**
|
|
34
|
+
* Show error message
|
|
35
|
+
*/
|
|
36
|
+
export declare function showError(message: string, indent?: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Show warning message
|
|
39
|
+
*/
|
|
40
|
+
export declare function showWarning(message: string, indent?: string): void;
|
|
41
|
+
/**
|
|
42
|
+
* Show goodbye message
|
|
43
|
+
*/
|
|
44
|
+
export declare function showGoodbye(lang?: 'zh' | 'en'): void;
|
|
45
|
+
/**
|
|
46
|
+
* Print header with ASCII art
|
|
47
|
+
*/
|
|
48
|
+
export interface HeaderOptions {
|
|
49
|
+
asciiArt: string[];
|
|
50
|
+
title: string;
|
|
51
|
+
subtitle?: string;
|
|
52
|
+
version?: string;
|
|
53
|
+
github?: string;
|
|
54
|
+
}
|
|
55
|
+
export declare function printHeader(options: HeaderOptions): void;
|