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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +716 -0
  3. package/dist/api.d.ts +158 -0
  4. package/dist/api.js +128 -0
  5. package/dist/components/display/headers.d.ts +32 -0
  6. package/dist/components/display/headers.js +84 -0
  7. package/dist/components/display/index.d.ts +8 -0
  8. package/dist/components/display/index.js +31 -0
  9. package/dist/components/display/messages.d.ts +41 -0
  10. package/dist/components/display/messages.js +90 -0
  11. package/dist/components/display/progress.d.ts +41 -0
  12. package/dist/components/display/progress.js +82 -0
  13. package/dist/components/display/summary.d.ts +33 -0
  14. package/dist/components/display/summary.js +82 -0
  15. package/dist/components/inputs/index.d.ts +8 -0
  16. package/dist/components/inputs/index.js +15 -0
  17. package/dist/components/inputs/language-input.d.ts +11 -0
  18. package/dist/components/inputs/language-input.js +104 -0
  19. package/dist/components/inputs/modify-field.d.ts +11 -0
  20. package/dist/components/inputs/modify-field.js +42 -0
  21. package/dist/components/inputs/number-input.d.ts +11 -0
  22. package/dist/components/inputs/number-input.js +143 -0
  23. package/dist/components/inputs/text-input.d.ts +11 -0
  24. package/dist/components/inputs/text-input.js +114 -0
  25. package/dist/components/menus/boolean-menu.d.ts +11 -0
  26. package/dist/components/menus/boolean-menu.js +169 -0
  27. package/dist/components/menus/checkbox-menu.d.ts +11 -0
  28. package/dist/components/menus/checkbox-menu.js +161 -0
  29. package/dist/components/menus/index.d.ts +7 -0
  30. package/dist/components/menus/index.js +13 -0
  31. package/dist/components/menus/radio-menu.d.ts +11 -0
  32. package/dist/components/menus/radio-menu.js +158 -0
  33. package/dist/components.d.ts +55 -0
  34. package/dist/components.js +166 -0
  35. package/dist/core/colors.d.ts +64 -0
  36. package/dist/core/colors.js +139 -0
  37. package/dist/core/keyboard.d.ts +124 -0
  38. package/dist/core/keyboard.js +185 -0
  39. package/dist/core/renderer.d.ts +74 -0
  40. package/dist/core/renderer.js +217 -0
  41. package/dist/core/terminal.d.ts +89 -0
  42. package/dist/core/terminal.js +170 -0
  43. package/dist/features/commands.d.ts +57 -0
  44. package/dist/features/commands.js +161 -0
  45. package/dist/features/wizard.d.ts +62 -0
  46. package/dist/features/wizard.js +112 -0
  47. package/dist/i18n/languages/en.d.ts +5 -0
  48. package/dist/i18n/languages/en.js +54 -0
  49. package/dist/i18n/languages/zh.d.ts +5 -0
  50. package/dist/i18n/languages/zh.js +54 -0
  51. package/dist/i18n/registry.d.ts +36 -0
  52. package/dist/i18n/registry.js +82 -0
  53. package/dist/i18n/types.d.ts +65 -0
  54. package/dist/i18n/types.js +5 -0
  55. package/dist/index.d.ts +27 -0
  56. package/dist/index.js +104 -0
  57. package/dist/input.d.ts +40 -0
  58. package/dist/input.js +211 -0
  59. package/dist/menu-core.d.ts +52 -0
  60. package/dist/menu-core.js +201 -0
  61. package/dist/menu-multi.d.ts +21 -0
  62. package/dist/menu-multi.js +119 -0
  63. package/dist/menu-single.d.ts +18 -0
  64. package/dist/menu-single.js +138 -0
  65. package/dist/menu.d.ts +66 -0
  66. package/dist/menu.js +78 -0
  67. package/dist/types/display.types.d.ts +57 -0
  68. package/dist/types/display.types.js +5 -0
  69. package/dist/types/input.types.d.ts +88 -0
  70. package/dist/types/input.types.js +5 -0
  71. package/dist/types/layout.types.d.ts +56 -0
  72. package/dist/types/layout.types.js +36 -0
  73. package/dist/types/menu.types.d.ts +85 -0
  74. package/dist/types/menu.types.js +5 -0
  75. package/dist/types.d.ts +49 -0
  76. package/dist/types.js +5 -0
  77. 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,7 @@
1
+ /**
2
+ * Menu components index
3
+ * Exports all menu component functions
4
+ */
5
+ export { showRadioMenu } from './radio-menu.js';
6
+ export { showCheckboxMenu } from './checkbox-menu.js';
7
+ export { showBooleanMenu } from './boolean-menu.js';
@@ -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;