cli-menu-kit 0.1.26 → 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.
Files changed (78) hide show
  1. package/dist/api.d.ts +23 -5
  2. package/dist/api.js +16 -4
  3. package/dist/component-factories.d.ts +59 -0
  4. package/dist/component-factories.js +141 -0
  5. package/dist/components/display/header-v2.d.ts +13 -0
  6. package/dist/components/display/header-v2.js +43 -0
  7. package/dist/components/display/header.d.ts +40 -0
  8. package/dist/components/display/header.js +331 -18
  9. package/dist/components/display/headers.d.ts +1 -0
  10. package/dist/components/display/headers.js +15 -5
  11. package/dist/components/display/hints-v2.d.ts +10 -0
  12. package/dist/components/display/hints-v2.js +34 -0
  13. package/dist/components/display/hints.d.ts +56 -0
  14. package/dist/components/display/hints.js +81 -0
  15. package/dist/components/display/index.d.ts +4 -1
  16. package/dist/components/display/index.js +17 -1
  17. package/dist/components/display/input-prompt.d.ts +35 -0
  18. package/dist/components/display/input-prompt.js +36 -0
  19. package/dist/components/display/list.d.ts +49 -0
  20. package/dist/components/display/list.js +86 -0
  21. package/dist/components/display/messages.js +5 -5
  22. package/dist/components/display/progress.d.ts +17 -0
  23. package/dist/components/display/progress.js +18 -0
  24. package/dist/components/display/summary.js +72 -10
  25. package/dist/components/display/table.d.ts +44 -0
  26. package/dist/components/display/table.js +108 -0
  27. package/dist/components/inputs/language-input.js +8 -5
  28. package/dist/components/inputs/number-input.js +19 -14
  29. package/dist/components/inputs/text-input.js +50 -13
  30. package/dist/components/menus/boolean-menu.js +34 -20
  31. package/dist/components/menus/checkbox-menu.d.ts +2 -1
  32. package/dist/components/menus/checkbox-menu.js +35 -61
  33. package/dist/components/menus/checkbox-table-menu.d.ts +12 -0
  34. package/dist/components/menus/checkbox-table-menu.js +398 -0
  35. package/dist/components/menus/index.d.ts +1 -0
  36. package/dist/components/menus/index.js +3 -1
  37. package/dist/components/menus/radio-menu-split.d.ts +34 -0
  38. package/dist/components/menus/radio-menu-split.js +258 -0
  39. package/dist/components/menus/radio-menu-v2.d.ts +11 -0
  40. package/dist/components/menus/radio-menu-v2.js +150 -0
  41. package/dist/components/menus/radio-menu.d.ts +2 -1
  42. package/dist/components/menus/radio-menu.js +100 -134
  43. package/dist/components.js +3 -3
  44. package/dist/config/index.d.ts +5 -0
  45. package/dist/config/index.js +21 -0
  46. package/dist/config/language-config.d.ts +73 -0
  47. package/dist/config/language-config.js +157 -0
  48. package/dist/config/user-config.d.ts +83 -0
  49. package/dist/config/user-config.js +185 -0
  50. package/dist/core/colors.d.ts +24 -18
  51. package/dist/core/colors.js +74 -7
  52. package/dist/core/hint-manager.d.ts +29 -0
  53. package/dist/core/hint-manager.js +65 -0
  54. package/dist/core/renderer.d.ts +2 -1
  55. package/dist/core/renderer.js +46 -22
  56. package/dist/core/screen-manager.d.ts +54 -0
  57. package/dist/core/screen-manager.js +119 -0
  58. package/dist/core/state-manager.d.ts +27 -0
  59. package/dist/core/state-manager.js +56 -0
  60. package/dist/core/terminal.d.ts +17 -1
  61. package/dist/core/terminal.js +124 -4
  62. package/dist/core/virtual-scroll.d.ts +65 -0
  63. package/dist/core/virtual-scroll.js +120 -0
  64. package/dist/features/commands.js +23 -22
  65. package/dist/i18n/languages/en.js +4 -1
  66. package/dist/i18n/languages/zh.js +4 -1
  67. package/dist/i18n/registry.d.ts +4 -3
  68. package/dist/i18n/registry.js +12 -4
  69. package/dist/i18n/types.d.ts +3 -0
  70. package/dist/index.d.ts +7 -4
  71. package/dist/index.js +49 -4
  72. package/dist/layout.d.ts +67 -0
  73. package/dist/layout.js +86 -0
  74. package/dist/page-layout.d.ts +123 -0
  75. package/dist/page-layout.js +195 -0
  76. package/dist/types/input.types.d.ts +8 -0
  77. package/dist/types/menu.types.d.ts +61 -5
  78. package/package.json +4 -1
@@ -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(`最小: ${min}`);
31
+ constraints.push(`${(0, registry_js_1.t)('inputs.minValue')}: ${min}`);
30
32
  if (max !== undefined)
31
- constraints.push(`最大: ${max}`);
32
- promptLine += ` ${colors_js_1.colors.dim}(${constraints.join(', ')})${colors_js_1.colors.reset}`;
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
- promptLine += ` ${colors_js_1.colors.dim}(默认: ${defaultValue})${colors_js_1.colors.reset}`;
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.colors.cyan}${inputValue}_${colors_js_1.colors.reset}`;
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.colors.red}✗ ${errorMsg}${colors_js_1.colors.reset}`);
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
- (0, terminal_js_1.clearMenu)(state);
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('\n👋 再见!');
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 || `数字不能小于 ${min}`;
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 || `数字不能大于 ${max}`;
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
- promptLine += ` ${colors_js_1.colors.dim}(默认: ${defaultValue})${colors_js_1.colors.reset}`;
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.colors.dim}${placeholder}${colors_js_1.colors.reset}`;
54
+ promptLine += ` ${colors_js_1.uiColors.textSecondary}${placeholder}${colors_js_1.colors.reset}`;
31
55
  }
32
- promptLine += `: ${colors_js_1.colors.cyan}${inputValue}_${colors_js_1.colors.reset}`;
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.colors.red}✗ ${errorMsg}${colors_js_1.colors.reset}`);
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
- (0, terminal_js_1.clearMenu)(state);
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('\n👋 再见!');
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 || `输入长度不能少于 ${minLength} 个字符`;
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 printable characters
101
- if ((0, keyboard_js_1.isPrintable)(key)) {
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
- inputValue += key;
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;
@@ -8,36 +8,43 @@ exports.showBooleanMenu = showBooleanMenu;
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 boolean menu (yes/no selection)
13
14
  * @param config - Menu configuration
14
15
  * @returns Promise resolving to boolean result
15
16
  */
16
17
  async function showBooleanMenu(config) {
17
- const { question, defaultValue = true, yesText = '', noText = '', 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;
18
20
  if (orientation === 'horizontal') {
19
- return showBooleanMenuHorizontal(question, defaultValue, yesText, noText, onExit, preserveOnSelect);
21
+ return showBooleanMenuHorizontal(question, helperText, defaultValue, yesText, noText, onExit, preserveOnSelect, preserveOnExit);
20
22
  }
21
23
  else {
22
- return showBooleanMenuVertical(question, defaultValue, yesText, noText, onExit, preserveOnSelect);
24
+ return showBooleanMenuVertical(question, helperText, defaultValue, yesText, noText, onExit, preserveOnSelect, preserveOnExit);
23
25
  }
24
26
  }
25
27
  /**
26
28
  * Show horizontal boolean menu (side by side)
27
29
  */
28
- async function showBooleanMenuHorizontal(question, defaultValue, yesText, noText, onExit, preserveOnSelect = false) {
30
+ async function showBooleanMenuHorizontal(question, helperText, defaultValue, yesText, noText, onExit, preserveOnSelect = false, preserveOnExit = false) {
29
31
  const state = (0, terminal_js_1.initTerminal)();
30
32
  let selected = defaultValue;
31
33
  const render = () => {
32
34
  (0, terminal_js_1.clearMenu)(state);
33
35
  // Render question with options on same line
34
36
  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
+ ? `${colors_js_1.uiColors.primary}${yesText}${colors_js_1.colors.reset}`
38
+ : `${colors_js_1.uiColors.textSecondary}${yesText}${colors_js_1.colors.reset}`;
37
39
  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}`);
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
+ }
41
48
  state.renderedLines = 1;
42
49
  };
43
50
  // Initial render
@@ -48,13 +55,15 @@ async function showBooleanMenuHorizontal(question, defaultValue, yesText, noText
48
55
  // Handle Ctrl+C
49
56
  if ((0, keyboard_js_1.isCtrlC)(key)) {
50
57
  state.stdin.removeListener('data', onData);
51
- (0, terminal_js_1.clearMenu)(state);
58
+ if (!preserveOnExit) {
59
+ (0, terminal_js_1.clearMenu)(state);
60
+ }
52
61
  (0, terminal_js_1.restoreTerminal)(state);
53
62
  if (onExit) {
54
63
  onExit();
55
64
  }
56
65
  else {
57
- console.log('\n👋 再见!');
66
+ console.log(`\n${(0, registry_js_1.t)('messages.goodbye')}`);
58
67
  }
59
68
  process.exit(0);
60
69
  }
@@ -98,23 +107,26 @@ async function showBooleanMenuHorizontal(question, defaultValue, yesText, noText
98
107
  /**
99
108
  * Show vertical boolean menu (stacked)
100
109
  */
101
- async function showBooleanMenuVertical(question, defaultValue, yesText, noText, onExit, preserveOnSelect = false) {
110
+ async function showBooleanMenuVertical(question, helperText, defaultValue, yesText, noText, onExit, preserveOnSelect = false, preserveOnExit = false) {
102
111
  const state = (0, terminal_js_1.initTerminal)();
103
112
  let selected = defaultValue;
104
113
  const render = () => {
105
114
  (0, terminal_js_1.clearMenu)(state);
106
115
  // Render question
107
- (0, terminal_js_1.writeLine)(`${colors_js_1.colors.yellow}?${colors_js_1.colors.reset} ${question}`);
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
+ }
108
120
  (0, terminal_js_1.writeLine)('');
109
121
  // Render yes option
110
- const yesCursor = selected ? `${colors_js_1.colors.cyan}❯ ${colors_js_1.colors.reset}` : ' ';
111
- const yesColor = selected ? colors_js_1.colors.cyan : colors_js_1.colors.reset;
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;
112
124
  (0, terminal_js_1.writeLine)(`${yesCursor}${yesColor}${yesText}${colors_js_1.colors.reset}`);
113
125
  // Render no option
114
- const noCursor = !selected ? `${colors_js_1.colors.cyan}❯ ${colors_js_1.colors.reset}` : ' ';
115
- const noColor = !selected ? colors_js_1.colors.cyan : colors_js_1.colors.reset;
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;
116
128
  (0, terminal_js_1.writeLine)(`${noCursor}${noColor}${noText}${colors_js_1.colors.reset}`);
117
- state.renderedLines = 4;
129
+ state.renderedLines = helperText && helperText.trim().length > 0 ? 5 : 4;
118
130
  };
119
131
  // Initial render
120
132
  render();
@@ -124,13 +136,15 @@ async function showBooleanMenuVertical(question, defaultValue, yesText, noText,
124
136
  // Handle Ctrl+C
125
137
  if ((0, keyboard_js_1.isCtrlC)(key)) {
126
138
  state.stdin.removeListener('data', onData);
127
- (0, terminal_js_1.clearMenu)(state);
139
+ if (!preserveOnExit) {
140
+ (0, terminal_js_1.clearMenu)(state);
141
+ }
128
142
  (0, terminal_js_1.restoreTerminal)(state);
129
143
  if (onExit) {
130
144
  onExit();
131
145
  }
132
146
  else {
133
- console.log('\n👋 再见!');
147
+ console.log(`\n${(0, registry_js_1.t)('messages.goodbye')}`);
134
148
  }
135
149
  process.exit(0);
136
150
  }
@@ -6,6 +6,7 @@ import { CheckboxMenuConfig, CheckboxMenuResult } from '../../types/menu.types.j
6
6
  /**
7
7
  * Show a checkbox menu (multi-select)
8
8
  * @param config - Menu configuration
9
+ * @param hints - Optional hints to display at the bottom (for Page Layout use)
9
10
  * @returns Promise resolving to selected options
10
11
  */
11
- export declare function showCheckboxMenu(config: CheckboxMenuConfig): Promise<CheckboxMenuResult>;
12
+ export declare function showCheckboxMenu(config: CheckboxMenuConfig, hints?: string[]): Promise<CheckboxMenuResult>;
@@ -5,36 +5,21 @@
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.showCheckboxMenu = showCheckboxMenu;
8
- const layout_types_js_1 = require("../../types/layout.types.js");
9
8
  const terminal_js_1 = require("../../core/terminal.js");
10
9
  const keyboard_js_1 = require("../../core/keyboard.js");
11
10
  const renderer_js_1 = require("../../core/renderer.js");
12
11
  const registry_js_1 = require("../../i18n/registry.js");
13
- /**
14
- * Generate hints based on menu configuration
15
- */
16
- function generateHints(allowSelectAll, allowInvert) {
17
- const hints = [(0, registry_js_1.t)('hints.arrows'), (0, registry_js_1.t)('hints.space')];
18
- if (allowSelectAll) {
19
- hints.push((0, registry_js_1.t)('hints.selectAll'));
20
- }
21
- if (allowInvert) {
22
- hints.push((0, registry_js_1.t)('hints.invert'));
23
- }
24
- hints.push((0, registry_js_1.t)('hints.enter'));
25
- return hints;
26
- }
27
12
  /**
28
13
  * Show a checkbox menu (multi-select)
29
14
  * @param config - Menu configuration
15
+ * @param hints - Optional hints to display at the bottom (for Page Layout use)
30
16
  * @returns Promise resolving to selected options
31
17
  */
32
- async function showCheckboxMenu(config) {
33
- const { options, title, prompt, hints, layout = { ...layout_types_js_1.LAYOUT_PRESETS.SUB_MENU, order: ['input', 'options', 'hints'] }, defaultSelected = [], minSelections = 0, maxSelections, allowSelectAll = true, allowInvert = true, separatorWidth = 30, onExit, preserveOnSelect = false } = config;
18
+ async function showCheckboxMenu(config, hints) {
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;
34
21
  // Use i18n for default prompt if not provided
35
22
  const displayPrompt = prompt || (0, registry_js_1.t)('menus.multiSelectPrompt');
36
- // Generate hints dynamically if not provided
37
- const displayHints = hints || generateHints(allowSelectAll, allowInvert);
38
23
  // Validate options
39
24
  if (!options || options.length === 0) {
40
25
  throw new Error('CheckboxMenu requires at least one option');
@@ -89,49 +74,36 @@ async function showCheckboxMenu(config) {
89
74
  const render = () => {
90
75
  (0, terminal_js_1.clearMenu)(state);
91
76
  let lineCount = 0;
92
- // Render based on layout order
93
- layout.order.forEach(element => {
94
- // Add spacing before element
95
- const spacingKey = `before${element.charAt(0).toUpperCase() + element.slice(1)}`;
96
- if (layout.spacing?.[spacingKey]) {
97
- (0, renderer_js_1.renderBlankLines)(layout.spacing[spacingKey]);
98
- lineCount += layout.spacing[spacingKey];
99
- }
100
- switch (element) {
101
- case 'input':
102
- if (layout.visible.input) {
103
- const selectedCount = selected.size;
104
- const displayValue = `${selectedCount} ${(0, registry_js_1.t)('menus.selectedCount')}`;
105
- (0, renderer_js_1.renderInputPrompt)(displayPrompt, displayValue);
106
- lineCount++;
107
- }
108
- break;
109
- case 'options':
110
- optionData.forEach((item, index) => {
111
- if (item.isSeparator) {
112
- // Render section label
113
- (0, renderer_js_1.renderSectionLabel)(item.label, separatorWidth);
114
- }
115
- else {
116
- (0, renderer_js_1.renderOption)(item.value, selected.has(index), index === cursorIndex);
117
- }
118
- lineCount++;
119
- });
120
- break;
121
- case 'hints':
122
- if (layout.visible.hints && displayHints.length > 0) {
123
- (0, renderer_js_1.renderHints)(displayHints);
124
- lineCount++;
125
- }
126
- break;
77
+ // Render title if provided
78
+ if (title) {
79
+ (0, renderer_js_1.renderBlankLines)(1);
80
+ lineCount++;
81
+ }
82
+ // Render input prompt (show selected count)
83
+ const selectedCount = selected.size;
84
+ const displayValue = `${selectedCount} ${(0, registry_js_1.t)('menus.selectedCount')}`;
85
+ (0, renderer_js_1.renderInputPrompt)(displayPrompt, displayValue);
86
+ lineCount++;
87
+ (0, renderer_js_1.renderBlankLines)(1);
88
+ lineCount++;
89
+ // Render options
90
+ optionData.forEach((item, index) => {
91
+ if (item.isSeparator) {
92
+ // Render section label
93
+ (0, renderer_js_1.renderSectionLabel)(item.label, separatorWidth);
127
94
  }
128
- // Add spacing after element
129
- const afterSpacingKey = `after${element.charAt(0).toUpperCase() + element.slice(1)}`;
130
- if (layout.spacing?.[afterSpacingKey]) {
131
- (0, renderer_js_1.renderBlankLines)(layout.spacing[afterSpacingKey]);
132
- lineCount += layout.spacing[afterSpacingKey];
95
+ else {
96
+ (0, renderer_js_1.renderOption)(item.value, selected.has(index), index === cursorIndex);
133
97
  }
98
+ lineCount++;
134
99
  });
100
+ // Render hints if provided
101
+ if (hints && hints.length > 0) {
102
+ (0, renderer_js_1.renderBlankLines)(1);
103
+ lineCount++;
104
+ (0, renderer_js_1.renderHints)(hints);
105
+ lineCount += 1;
106
+ }
135
107
  state.renderedLines = lineCount;
136
108
  };
137
109
  // Initial render
@@ -142,13 +114,15 @@ async function showCheckboxMenu(config) {
142
114
  // Handle Ctrl+C
143
115
  if ((0, keyboard_js_1.isCtrlC)(key)) {
144
116
  state.stdin.removeListener('data', onData);
145
- (0, terminal_js_1.clearMenu)(state);
117
+ if (!preserveOnExit) {
118
+ (0, terminal_js_1.clearMenu)(state);
119
+ }
146
120
  (0, terminal_js_1.restoreTerminal)(state);
147
121
  if (onExit) {
148
122
  onExit();
149
123
  }
150
124
  else {
151
- console.log('\n👋 再见!');
125
+ console.log(`\n${(0, registry_js_1.t)('messages.goodbye')}`);
152
126
  }
153
127
  process.exit(0);
154
128
  }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * CheckboxTableMenu - Multi-select menu with table display
3
+ * Combines checkbox selection with formatted table layout
4
+ */
5
+ import { CheckboxTableMenuConfig, CheckboxTableMenuResult } from '../../types/menu.types.js';
6
+ /**
7
+ * Show a checkbox table menu (multi-select with table display)
8
+ * @param config - Menu configuration
9
+ * @param hints - Optional hints to display at the bottom (for Page Layout use)
10
+ * @returns Promise resolving to selected rows
11
+ */
12
+ export declare function showCheckboxTableMenu(config: CheckboxTableMenuConfig, hints?: string[]): Promise<CheckboxTableMenuResult>;