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,82 @@
1
+ "use strict";
2
+ /**
3
+ * Progress - Progress indicator components
4
+ * Displays step flow and stage information
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.renderProgressIndicator = renderProgressIndicator;
8
+ exports.renderStageHeader = renderStageHeader;
9
+ exports.renderStageSeparator = renderStageSeparator;
10
+ exports.createProgressIndicator = createProgressIndicator;
11
+ exports.createStageHeader = createStageHeader;
12
+ exports.createStageSeparator = createStageSeparator;
13
+ const terminal_js_1 = require("../../core/terminal.js");
14
+ const colors_js_1 = require("../../core/colors.js");
15
+ const terminal_js_2 = require("../../core/terminal.js");
16
+ /**
17
+ * Render a progress indicator showing steps
18
+ * @param config - Progress configuration
19
+ */
20
+ function renderProgressIndicator(config) {
21
+ const { steps, currentStep, separator = '→' } = config;
22
+ const parts = [];
23
+ steps.forEach((step, index) => {
24
+ if (index === currentStep) {
25
+ // Current step - highlighted
26
+ parts.push(`${colors_js_1.colors.cyan}${step}${colors_js_1.colors.reset}`);
27
+ }
28
+ else if (index < currentStep) {
29
+ // Completed step - normal
30
+ parts.push(step);
31
+ }
32
+ else {
33
+ // Future step - dimmed
34
+ parts.push(`${colors_js_1.colors.dim}${step}${colors_js_1.colors.reset}`);
35
+ }
36
+ });
37
+ const progressLine = ` ${parts.join(` ${colors_js_1.colors.dim}${separator}${colors_js_1.colors.reset} `)}`;
38
+ (0, terminal_js_1.writeLine)(progressLine);
39
+ }
40
+ /**
41
+ * Render a stage header with step number
42
+ * @param stageName - Name of the stage
43
+ * @param stepNumber - Step number (1-based)
44
+ */
45
+ function renderStageHeader(stageName, stepNumber) {
46
+ (0, terminal_js_1.writeLine)(` ${colors_js_1.colors.cyan}步骤${stepNumber}: ${stageName}${colors_js_1.colors.reset}`);
47
+ }
48
+ /**
49
+ * Render a stage separator
50
+ * @param char - Character to use for separator
51
+ * @param width - Width of separator (default: terminal width)
52
+ */
53
+ function renderStageSeparator(char = '─', width) {
54
+ const termWidth = (0, terminal_js_2.getTerminalWidth)();
55
+ const sepWidth = width || termWidth;
56
+ (0, terminal_js_1.writeLine)(colors_js_1.colors.dim + char.repeat(sepWidth) + colors_js_1.colors.reset);
57
+ }
58
+ /**
59
+ * Create a progress indicator
60
+ * @param steps - Array of step names
61
+ * @param currentStep - Index of current step (0-based)
62
+ * @param separator - Optional separator character
63
+ */
64
+ function createProgressIndicator(steps, currentStep, separator) {
65
+ renderProgressIndicator({ steps, currentStep, separator });
66
+ }
67
+ /**
68
+ * Create a stage header
69
+ * @param stageName - Name of the stage
70
+ * @param stepNumber - Step number (1-based)
71
+ */
72
+ function createStageHeader(stageName, stepNumber) {
73
+ renderStageHeader(stageName, stepNumber);
74
+ }
75
+ /**
76
+ * Create a stage separator
77
+ * @param char - Optional separator character
78
+ * @param width - Optional width
79
+ */
80
+ function createStageSeparator(char, width) {
81
+ renderStageSeparator(char, width);
82
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Summary - Summary table component
3
+ * Displays statistics and information in a bordered box
4
+ */
5
+ import { SummaryTableConfig } from '../../types/display.types.js';
6
+ /**
7
+ * Render a summary table
8
+ * @param config - Summary table configuration
9
+ */
10
+ export declare function renderSummaryTable(config: SummaryTableConfig): void;
11
+ /**
12
+ * Create a summary table
13
+ * @param title - Optional title
14
+ * @param sections - Array of sections with items
15
+ * @param width - Optional width
16
+ */
17
+ export declare function createSummaryTable(title: string | undefined, sections: Array<{
18
+ header?: string;
19
+ items: Array<{
20
+ key: string;
21
+ value: string;
22
+ }>;
23
+ }>, width?: number): void;
24
+ /**
25
+ * Create a simple summary table with one section
26
+ * @param title - Optional title
27
+ * @param items - Key-value pairs
28
+ * @param width - Optional width
29
+ */
30
+ export declare function createSimpleSummary(title: string | undefined, items: Array<{
31
+ key: string;
32
+ value: string;
33
+ }>, width?: number): void;
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ /**
3
+ * Summary - Summary table component
4
+ * Displays statistics and information in a bordered box
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.renderSummaryTable = renderSummaryTable;
8
+ exports.createSummaryTable = createSummaryTable;
9
+ exports.createSimpleSummary = createSimpleSummary;
10
+ const terminal_js_1 = require("../../core/terminal.js");
11
+ const colors_js_1 = require("../../core/colors.js");
12
+ const terminal_js_2 = require("../../core/terminal.js");
13
+ /**
14
+ * Render a summary table
15
+ * @param config - Summary table configuration
16
+ */
17
+ function renderSummaryTable(config) {
18
+ const { title, sections, width } = config;
19
+ const termWidth = (0, terminal_js_2.getTerminalWidth)();
20
+ const boxWidth = width || Math.min(termWidth - 4, 60);
21
+ // Calculate content width (excluding borders and padding)
22
+ const contentWidth = boxWidth - 4;
23
+ // Top border
24
+ (0, terminal_js_1.writeLine)(`╭${'─'.repeat(boxWidth - 2)}╮`);
25
+ // Empty line
26
+ (0, terminal_js_1.writeLine)(`│${' '.repeat(boxWidth - 2)}│`);
27
+ // Title if provided
28
+ if (title) {
29
+ const titlePadding = Math.floor((contentWidth - title.length) / 2);
30
+ const titleLine = ' '.repeat(titlePadding + 2) + colors_js_1.colors.cyan + title + colors_js_1.colors.reset;
31
+ const remainingSpace = boxWidth - titlePadding - title.length - 4;
32
+ (0, terminal_js_1.writeLine)(`│${titleLine}${' '.repeat(remainingSpace)}│`);
33
+ (0, terminal_js_1.writeLine)(`│${' '.repeat(boxWidth - 2)}│`);
34
+ }
35
+ // Sections
36
+ sections.forEach((section, sectionIndex) => {
37
+ // Section header if provided
38
+ if (section.header) {
39
+ const headerLine = ` ${colors_js_1.colors.brightCyan}${section.header}${colors_js_1.colors.reset}`;
40
+ const remainingSpace = boxWidth - section.header.length - 4;
41
+ (0, terminal_js_1.writeLine)(`│${headerLine}${' '.repeat(remainingSpace)}│`);
42
+ }
43
+ // Section items
44
+ section.items.forEach(item => {
45
+ const itemLine = ` ${item.key}:${' '.repeat(Math.max(1, 15 - item.key.length))}${item.value}`;
46
+ // Remove ANSI codes for length calculation
47
+ const plainItemLine = itemLine.replace(/\x1b\[[0-9;]*m/g, '');
48
+ const remainingSpace = boxWidth - plainItemLine.length - 2;
49
+ (0, terminal_js_1.writeLine)(`│${itemLine}${' '.repeat(Math.max(0, remainingSpace))}│`);
50
+ });
51
+ // Add spacing between sections (except after last section)
52
+ if (sectionIndex < sections.length - 1) {
53
+ (0, terminal_js_1.writeLine)(`│${' '.repeat(boxWidth - 2)}│`);
54
+ }
55
+ });
56
+ // Empty line
57
+ (0, terminal_js_1.writeLine)(`│${' '.repeat(boxWidth - 2)}│`);
58
+ // Bottom border
59
+ (0, terminal_js_1.writeLine)(`╰${'─'.repeat(boxWidth - 2)}╯`);
60
+ }
61
+ /**
62
+ * Create a summary table
63
+ * @param title - Optional title
64
+ * @param sections - Array of sections with items
65
+ * @param width - Optional width
66
+ */
67
+ function createSummaryTable(title, sections, width) {
68
+ renderSummaryTable({ title, sections, width });
69
+ }
70
+ /**
71
+ * Create a simple summary table with one section
72
+ * @param title - Optional title
73
+ * @param items - Key-value pairs
74
+ * @param width - Optional width
75
+ */
76
+ function createSimpleSummary(title, items, width) {
77
+ renderSummaryTable({
78
+ title,
79
+ sections: [{ items }],
80
+ width
81
+ });
82
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Input components index
3
+ * Exports all input component functions
4
+ */
5
+ export { showTextInput } from './text-input.js';
6
+ export { showNumberInput } from './number-input.js';
7
+ export { showLanguageSelector } from './language-input.js';
8
+ export { showModifyField } from './modify-field.js';
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ /**
3
+ * Input components index
4
+ * Exports all input component functions
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.showModifyField = exports.showLanguageSelector = exports.showNumberInput = exports.showTextInput = void 0;
8
+ var text_input_js_1 = require("./text-input.js");
9
+ Object.defineProperty(exports, "showTextInput", { enumerable: true, get: function () { return text_input_js_1.showTextInput; } });
10
+ var number_input_js_1 = require("./number-input.js");
11
+ Object.defineProperty(exports, "showNumberInput", { enumerable: true, get: function () { return number_input_js_1.showNumberInput; } });
12
+ var language_input_js_1 = require("./language-input.js");
13
+ Object.defineProperty(exports, "showLanguageSelector", { enumerable: true, get: function () { return language_input_js_1.showLanguageSelector; } });
14
+ var modify_field_js_1 = require("./modify-field.js");
15
+ Object.defineProperty(exports, "showModifyField", { enumerable: true, get: function () { return modify_field_js_1.showModifyField; } });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * LanguageSelector - Language selection component
3
+ * Specialized selector for choosing a language
4
+ */
5
+ import { LanguageSelectorConfig, LanguageSelectorResult } from '../../types/input.types.js';
6
+ /**
7
+ * Show a language selector
8
+ * @param config - Selector configuration
9
+ * @returns Promise resolving to selected language code
10
+ */
11
+ export declare function showLanguageSelector(config: LanguageSelectorConfig): Promise<LanguageSelectorResult>;
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ /**
3
+ * LanguageSelector - Language selection component
4
+ * Specialized selector for choosing a language
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.showLanguageSelector = showLanguageSelector;
8
+ const terminal_js_1 = require("../../core/terminal.js");
9
+ const keyboard_js_1 = require("../../core/keyboard.js");
10
+ const renderer_js_1 = require("../../core/renderer.js");
11
+ const colors_js_1 = require("../../core/colors.js");
12
+ /**
13
+ * Show a language selector
14
+ * @param config - Selector configuration
15
+ * @returns Promise resolving to selected language code
16
+ */
17
+ async function showLanguageSelector(config) {
18
+ const { languages, defaultLanguage, prompt = '选择语言 / Select Language', onExit } = config;
19
+ // Validate languages
20
+ if (!languages || languages.length === 0) {
21
+ throw new Error('LanguageSelector requires at least one language');
22
+ }
23
+ // Find default index
24
+ let selectedIndex = 0;
25
+ if (defaultLanguage) {
26
+ const index = languages.findIndex(lang => lang.code === defaultLanguage);
27
+ if (index !== -1) {
28
+ selectedIndex = index;
29
+ }
30
+ }
31
+ const state = (0, terminal_js_1.initTerminal)();
32
+ const render = () => {
33
+ (0, terminal_js_1.clearMenu)(state);
34
+ let lineCount = 0;
35
+ // Render header
36
+ (0, renderer_js_1.renderHeader)(` ${prompt}`, colors_js_1.colors.cyan);
37
+ lineCount++;
38
+ (0, renderer_js_1.renderBlankLines)(1);
39
+ lineCount++;
40
+ // Render language options
41
+ languages.forEach((lang, index) => {
42
+ const displayText = lang.nativeName
43
+ ? `${lang.name} (${lang.nativeName})`
44
+ : lang.name;
45
+ (0, renderer_js_1.renderOption)(displayText, false, index === selectedIndex, `${index + 1}. `);
46
+ lineCount++;
47
+ });
48
+ // Render hints
49
+ (0, renderer_js_1.renderBlankLines)(1);
50
+ lineCount++;
51
+ (0, renderer_js_1.renderHints)(['↑↓ 方向键', '1-9 输入序号', '⏎ 确认']);
52
+ lineCount++;
53
+ state.renderedLines = lineCount;
54
+ };
55
+ // Initial render
56
+ render();
57
+ // Handle keyboard input
58
+ return new Promise((resolve) => {
59
+ const onData = (key) => {
60
+ // Handle Ctrl+C
61
+ if ((0, keyboard_js_1.isCtrlC)(key)) {
62
+ state.stdin.removeListener('data', onData);
63
+ (0, terminal_js_1.clearMenu)(state);
64
+ (0, terminal_js_1.restoreTerminal)(state);
65
+ if (onExit) {
66
+ onExit();
67
+ }
68
+ else {
69
+ console.log('\n👋 再见!');
70
+ }
71
+ process.exit(0);
72
+ }
73
+ // Handle Enter
74
+ if ((0, keyboard_js_1.isEnter)(key)) {
75
+ state.stdin.removeListener('data', onData);
76
+ (0, terminal_js_1.clearMenu)(state);
77
+ (0, terminal_js_1.restoreTerminal)(state);
78
+ resolve(languages[selectedIndex].code);
79
+ return;
80
+ }
81
+ // Handle arrow keys
82
+ if (key === keyboard_js_1.KEY_CODES.UP) {
83
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : languages.length - 1;
84
+ render();
85
+ return;
86
+ }
87
+ if (key === keyboard_js_1.KEY_CODES.DOWN) {
88
+ selectedIndex = selectedIndex < languages.length - 1 ? selectedIndex + 1 : 0;
89
+ render();
90
+ return;
91
+ }
92
+ // Handle number keys
93
+ if ((0, keyboard_js_1.isNumberKey)(key)) {
94
+ const num = parseInt(key, 10);
95
+ if (num > 0 && num <= languages.length) {
96
+ selectedIndex = num - 1;
97
+ render();
98
+ }
99
+ return;
100
+ }
101
+ };
102
+ state.stdin.on('data', onData);
103
+ });
104
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * ModifyField - Field modification composite component
3
+ * Shows current value and asks if user wants to modify it
4
+ */
5
+ import { ModifyFieldConfig, ModifyFieldResult } from '../../types/input.types.js';
6
+ /**
7
+ * Show a modify field prompt
8
+ * @param config - Field configuration
9
+ * @returns Promise resolving to modification result
10
+ */
11
+ export declare function showModifyField(config: ModifyFieldConfig): Promise<ModifyFieldResult>;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ /**
3
+ * ModifyField - Field modification composite component
4
+ * Shows current value and asks if user wants to modify it
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.showModifyField = showModifyField;
8
+ const boolean_menu_js_1 = require("../menus/boolean-menu.js");
9
+ const text_input_js_1 = require("./text-input.js");
10
+ /**
11
+ * Show a modify field prompt
12
+ * @param config - Field configuration
13
+ * @returns Promise resolving to modification result
14
+ */
15
+ async function showModifyField(config) {
16
+ const { fieldName, currentValue, modifyPrompt = `是否修改 ${fieldName}? (当前: ${currentValue})`, newValuePrompt = `请输入新的 ${fieldName}`, validate, onExit } = config;
17
+ // Ask if user wants to modify
18
+ const shouldModify = await (0, boolean_menu_js_1.showBooleanMenu)({
19
+ question: modifyPrompt,
20
+ orientation: 'horizontal',
21
+ defaultValue: false,
22
+ onExit
23
+ });
24
+ // If user doesn't want to modify, return current value
25
+ if (!shouldModify) {
26
+ return {
27
+ modified: false,
28
+ value: currentValue
29
+ };
30
+ }
31
+ // Get new value
32
+ const newValue = await (0, text_input_js_1.showTextInput)({
33
+ prompt: newValuePrompt,
34
+ defaultValue: currentValue,
35
+ validate,
36
+ onExit
37
+ });
38
+ return {
39
+ modified: true,
40
+ value: newValue
41
+ };
42
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * NumberInput - Numeric input component
3
+ * Allows user to enter numbers with validation
4
+ */
5
+ import { NumberInputConfig, NumberInputResult } from '../../types/input.types.js';
6
+ /**
7
+ * Show a number input prompt
8
+ * @param config - Input configuration
9
+ * @returns Promise resolving to entered number
10
+ */
11
+ export declare function showNumberInput(config: NumberInputConfig): Promise<NumberInputResult>;
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ /**
3
+ * NumberInput - Numeric input component
4
+ * Allows user to enter numbers with validation
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.showNumberInput = showNumberInput;
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 number input prompt
13
+ * @param config - Input configuration
14
+ * @returns Promise resolving to entered number
15
+ */
16
+ async function showNumberInput(config) {
17
+ const { prompt, defaultValue, min, max, allowDecimals = false, allowNegative = false, validate, errorMessage, onExit } = config;
18
+ const state = (0, terminal_js_1.initTerminal)();
19
+ let inputValue = '';
20
+ let errorMsg = '';
21
+ const render = () => {
22
+ (0, terminal_js_1.clearMenu)(state);
23
+ let lineCount = 0;
24
+ // Render prompt with constraints
25
+ let promptLine = ` ${prompt}`;
26
+ if (min !== undefined || max !== undefined) {
27
+ const constraints = [];
28
+ if (min !== undefined)
29
+ constraints.push(`最小: ${min}`);
30
+ if (max !== undefined)
31
+ constraints.push(`最大: ${max}`);
32
+ promptLine += ` ${colors_js_1.colors.dim}(${constraints.join(', ')})${colors_js_1.colors.reset}`;
33
+ }
34
+ if (defaultValue !== undefined && !inputValue) {
35
+ promptLine += ` ${colors_js_1.colors.dim}(默认: ${defaultValue})${colors_js_1.colors.reset}`;
36
+ }
37
+ promptLine += `: ${colors_js_1.colors.cyan}${inputValue}_${colors_js_1.colors.reset}`;
38
+ (0, terminal_js_1.writeLine)(promptLine);
39
+ lineCount++;
40
+ // Render error message if any
41
+ if (errorMsg) {
42
+ (0, terminal_js_1.writeLine)(` ${colors_js_1.colors.red}✗ ${errorMsg}${colors_js_1.colors.reset}`);
43
+ lineCount++;
44
+ }
45
+ state.renderedLines = lineCount;
46
+ };
47
+ // Initial render
48
+ render();
49
+ // Handle keyboard input
50
+ return new Promise((resolve) => {
51
+ const onData = (key) => {
52
+ // Handle Ctrl+C
53
+ if ((0, keyboard_js_1.isCtrlC)(key)) {
54
+ state.stdin.removeListener('data', onData);
55
+ (0, terminal_js_1.clearMenu)(state);
56
+ (0, terminal_js_1.restoreTerminal)(state);
57
+ if (onExit) {
58
+ onExit();
59
+ }
60
+ else {
61
+ console.log('\n👋 再见!');
62
+ }
63
+ process.exit(0);
64
+ }
65
+ // Handle Enter
66
+ if ((0, keyboard_js_1.isEnter)(key)) {
67
+ const finalValue = inputValue || (defaultValue !== undefined ? String(defaultValue) : '');
68
+ // Check if empty
69
+ if (!finalValue) {
70
+ errorMsg = errorMessage || '请输入数字';
71
+ render();
72
+ return;
73
+ }
74
+ // Parse number
75
+ const num = allowDecimals ? parseFloat(finalValue) : parseInt(finalValue, 10);
76
+ // Check if valid number
77
+ if (isNaN(num)) {
78
+ errorMsg = errorMessage || '输入必须是有效的数字';
79
+ render();
80
+ return;
81
+ }
82
+ // Check min
83
+ if (min !== undefined && num < min) {
84
+ errorMsg = errorMessage || `数字不能小于 ${min}`;
85
+ render();
86
+ return;
87
+ }
88
+ // Check max
89
+ if (max !== undefined && num > max) {
90
+ errorMsg = errorMessage || `数字不能大于 ${max}`;
91
+ render();
92
+ return;
93
+ }
94
+ // Custom validation
95
+ if (validate) {
96
+ const validationResult = validate(String(num));
97
+ if (validationResult !== true) {
98
+ errorMsg = typeof validationResult === 'string' ? validationResult : (errorMessage || '输入无效');
99
+ render();
100
+ return;
101
+ }
102
+ }
103
+ // Success
104
+ state.stdin.removeListener('data', onData);
105
+ (0, terminal_js_1.clearMenu)(state);
106
+ (0, terminal_js_1.restoreTerminal)(state);
107
+ resolve(num);
108
+ return;
109
+ }
110
+ // Handle Backspace
111
+ if ((0, keyboard_js_1.isBackspace)(key)) {
112
+ if (inputValue.length > 0) {
113
+ inputValue = inputValue.slice(0, -1);
114
+ errorMsg = ''; // Clear error on edit
115
+ render();
116
+ }
117
+ return;
118
+ }
119
+ // Handle number keys
120
+ if ((0, keyboard_js_1.isNumberKey)(key)) {
121
+ inputValue += key;
122
+ errorMsg = ''; // Clear error on edit
123
+ render();
124
+ return;
125
+ }
126
+ // Handle decimal point
127
+ if (allowDecimals && key === '.' && !inputValue.includes('.')) {
128
+ inputValue += key;
129
+ errorMsg = ''; // Clear error on edit
130
+ render();
131
+ return;
132
+ }
133
+ // Handle negative sign
134
+ if (allowNegative && key === '-' && inputValue.length === 0) {
135
+ inputValue += key;
136
+ errorMsg = ''; // Clear error on edit
137
+ render();
138
+ return;
139
+ }
140
+ };
141
+ state.stdin.on('data', onData);
142
+ });
143
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * TextInput - Single-line text input component
3
+ * Allows user to enter text with validation
4
+ */
5
+ import { TextInputConfig, TextInputResult } from '../../types/input.types.js';
6
+ /**
7
+ * Show a text input prompt
8
+ * @param config - Input configuration
9
+ * @returns Promise resolving to entered text
10
+ */
11
+ export declare function showTextInput(config: TextInputConfig): Promise<TextInputResult>;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ /**
3
+ * TextInput - Single-line text input component
4
+ * Allows user to enter text with validation
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.showTextInput = showTextInput;
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 text input prompt
13
+ * @param config - Input configuration
14
+ * @returns Promise resolving to entered text
15
+ */
16
+ async function showTextInput(config) {
17
+ const { prompt, defaultValue = '', placeholder, maxLength, minLength = 0, allowEmpty = false, validate, errorMessage, onExit } = config;
18
+ const state = (0, terminal_js_1.initTerminal)();
19
+ let inputValue = '';
20
+ let errorMsg = '';
21
+ const render = () => {
22
+ (0, terminal_js_1.clearMenu)(state);
23
+ let lineCount = 0;
24
+ // Render prompt with default value hint
25
+ let promptLine = ` ${prompt}`;
26
+ if (defaultValue && !inputValue) {
27
+ promptLine += ` ${colors_js_1.colors.dim}(默认: ${defaultValue})${colors_js_1.colors.reset}`;
28
+ }
29
+ if (placeholder && !inputValue) {
30
+ promptLine += ` ${colors_js_1.colors.dim}${placeholder}${colors_js_1.colors.reset}`;
31
+ }
32
+ promptLine += `: ${colors_js_1.colors.cyan}${inputValue}_${colors_js_1.colors.reset}`;
33
+ (0, terminal_js_1.writeLine)(promptLine);
34
+ lineCount++;
35
+ // Render error message if any
36
+ if (errorMsg) {
37
+ (0, terminal_js_1.writeLine)(` ${colors_js_1.colors.red}✗ ${errorMsg}${colors_js_1.colors.reset}`);
38
+ lineCount++;
39
+ }
40
+ state.renderedLines = lineCount;
41
+ };
42
+ // Initial render
43
+ render();
44
+ // Handle keyboard input
45
+ return new Promise((resolve) => {
46
+ const onData = (key) => {
47
+ // Handle Ctrl+C
48
+ if ((0, keyboard_js_1.isCtrlC)(key)) {
49
+ state.stdin.removeListener('data', onData);
50
+ (0, terminal_js_1.clearMenu)(state);
51
+ (0, terminal_js_1.restoreTerminal)(state);
52
+ if (onExit) {
53
+ onExit();
54
+ }
55
+ else {
56
+ console.log('\n👋 再见!');
57
+ }
58
+ process.exit(0);
59
+ }
60
+ // Handle Enter
61
+ if ((0, keyboard_js_1.isEnter)(key)) {
62
+ const finalValue = inputValue || defaultValue;
63
+ // Check if empty
64
+ if (!allowEmpty && !finalValue) {
65
+ errorMsg = errorMessage || '输入不能为空';
66
+ render();
67
+ return;
68
+ }
69
+ // Check min length
70
+ if (minLength && finalValue.length < minLength) {
71
+ errorMsg = errorMessage || `输入长度不能少于 ${minLength} 个字符`;
72
+ render();
73
+ return;
74
+ }
75
+ // Custom validation
76
+ if (validate) {
77
+ const validationResult = validate(finalValue);
78
+ if (validationResult !== true) {
79
+ errorMsg = typeof validationResult === 'string' ? validationResult : (errorMessage || '输入无效');
80
+ render();
81
+ return;
82
+ }
83
+ }
84
+ // Success
85
+ state.stdin.removeListener('data', onData);
86
+ (0, terminal_js_1.clearMenu)(state);
87
+ (0, terminal_js_1.restoreTerminal)(state);
88
+ resolve(finalValue);
89
+ return;
90
+ }
91
+ // Handle Backspace
92
+ if ((0, keyboard_js_1.isBackspace)(key)) {
93
+ if (inputValue.length > 0) {
94
+ inputValue = inputValue.slice(0, -1);
95
+ errorMsg = ''; // Clear error on edit
96
+ render();
97
+ }
98
+ return;
99
+ }
100
+ // Handle printable characters
101
+ if ((0, keyboard_js_1.isPrintable)(key)) {
102
+ // Check max length
103
+ if (maxLength && inputValue.length >= maxLength) {
104
+ return;
105
+ }
106
+ inputValue += key;
107
+ errorMsg = ''; // Clear error on edit
108
+ render();
109
+ return;
110
+ }
111
+ };
112
+ state.stdin.on('data', onData);
113
+ });
114
+ }