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
package/README.md
ADDED
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
# CLI Menu Kit
|
|
2
|
+
|
|
3
|
+
A comprehensive, modular CLI menu system for Node.js with full TypeScript support. Zero dependencies, pure Node.js implementation with advanced features including i18n, wizards, and command handling.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### Menu Components
|
|
8
|
+
- ✅ **RadioMenu** - Single-select vertical menu with arrow/number/letter navigation
|
|
9
|
+
- ✅ **CheckboxMenu** - Multi-select with checkboxes, select all, and invert
|
|
10
|
+
- ✅ **BooleanMenu** - Yes/No selection (horizontal and vertical)
|
|
11
|
+
|
|
12
|
+
### Input Components
|
|
13
|
+
- ✅ **TextInput** - Single-line text with validation and constraints
|
|
14
|
+
- ✅ **NumberInput** - Numeric input with min/max validation
|
|
15
|
+
- ✅ **LanguageSelector** - Specialized language picker
|
|
16
|
+
- ✅ **ModifyField** - Composite field modification prompt
|
|
17
|
+
|
|
18
|
+
### Display Components
|
|
19
|
+
- ✅ **Headers** - Simple and ASCII art headers with borders
|
|
20
|
+
- ✅ **Progress** - Step indicators, stage headers, separators
|
|
21
|
+
- ✅ **Messages** - Success/Error/Warning/Info/Question with icons
|
|
22
|
+
- ✅ **Summary** - Bordered tables with sections and key-value pairs
|
|
23
|
+
|
|
24
|
+
### Advanced Features
|
|
25
|
+
- ✅ **Wizard System** - Multi-step configuration flows with progress tracking
|
|
26
|
+
- ✅ **i18n Support** - Chinese and English translations (extensible)
|
|
27
|
+
- ✅ **Command Handling** - Built-in commands (/quit, /help, /clear, /back)
|
|
28
|
+
- ✅ **Layout System** - Flexible component composition
|
|
29
|
+
- ✅ **Color System** - Single colors and two-color gradients
|
|
30
|
+
- ✅ **Unified API** - Simple, consistent interface for all components
|
|
31
|
+
|
|
32
|
+
### Core Principles
|
|
33
|
+
- ✅ **Zero dependencies** - Pure Node.js
|
|
34
|
+
- ✅ **Fully typed** - Complete TypeScript support
|
|
35
|
+
- ✅ **Modular architecture** - All files under 300 lines
|
|
36
|
+
- ✅ **Component-based** - Reusable, composable components
|
|
37
|
+
- ✅ **Type-safe** - Strict TypeScript with full type definitions
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install cli-menu-kit
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Quick Start
|
|
46
|
+
|
|
47
|
+
### Unified API (Recommended)
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
import { menu, input, wizard } from 'cli-menu-kit';
|
|
51
|
+
|
|
52
|
+
// Radio menu (single-select)
|
|
53
|
+
const result = await menu.radio({
|
|
54
|
+
title: 'Select Framework',
|
|
55
|
+
options: ['React', 'Vue', 'Angular', 'Svelte']
|
|
56
|
+
});
|
|
57
|
+
console.log(`Selected: ${result.value}`);
|
|
58
|
+
|
|
59
|
+
// Checkbox menu (multi-select)
|
|
60
|
+
const features = await menu.checkbox({
|
|
61
|
+
options: ['TypeScript', 'ESLint', 'Prettier', 'Testing'],
|
|
62
|
+
minSelections: 1
|
|
63
|
+
});
|
|
64
|
+
console.log(`Selected: ${features.values.join(', ')}`);
|
|
65
|
+
|
|
66
|
+
// Boolean menu (yes/no)
|
|
67
|
+
const confirmed = await menu.booleanH('Continue?', true);
|
|
68
|
+
console.log(`Confirmed: ${confirmed}`);
|
|
69
|
+
|
|
70
|
+
// Text input
|
|
71
|
+
const name = await input.text({
|
|
72
|
+
prompt: 'Enter your name',
|
|
73
|
+
defaultValue: 'User',
|
|
74
|
+
minLength: 2
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Number input
|
|
78
|
+
const age = await input.number({
|
|
79
|
+
prompt: 'Enter your age',
|
|
80
|
+
min: 1,
|
|
81
|
+
max: 120
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Language selector
|
|
85
|
+
const lang = await input.language({
|
|
86
|
+
languages: [
|
|
87
|
+
{ code: 'zh', name: 'Chinese', nativeName: '简体中文' },
|
|
88
|
+
{ code: 'en', name: 'English' }
|
|
89
|
+
]
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Wizard (multi-step flow)
|
|
93
|
+
const result = await wizard.run({
|
|
94
|
+
steps: [
|
|
95
|
+
{
|
|
96
|
+
name: 'language',
|
|
97
|
+
title: 'Select Language',
|
|
98
|
+
component: 'language-selector',
|
|
99
|
+
config: { /* ... */ }
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'projectName',
|
|
103
|
+
title: 'Project Name',
|
|
104
|
+
component: 'text-input',
|
|
105
|
+
config: { prompt: 'Enter project name' }
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Display Components
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
import {
|
|
115
|
+
createSimpleHeader,
|
|
116
|
+
createAsciiHeader,
|
|
117
|
+
createProgressIndicator,
|
|
118
|
+
showSuccess,
|
|
119
|
+
showError,
|
|
120
|
+
showWarning,
|
|
121
|
+
showInfo,
|
|
122
|
+
createSummaryTable
|
|
123
|
+
} from 'cli-menu-kit';
|
|
124
|
+
|
|
125
|
+
// Simple header
|
|
126
|
+
createSimpleHeader('My Application', '\x1b[36m');
|
|
127
|
+
|
|
128
|
+
// ASCII header
|
|
129
|
+
createAsciiHeader(asciiArt, {
|
|
130
|
+
subtitle: 'Version 1.0.0',
|
|
131
|
+
url: 'https://github.com/user/repo'
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Progress indicator
|
|
135
|
+
createProgressIndicator(['Step 1', 'Step 2', 'Step 3'], 1);
|
|
136
|
+
|
|
137
|
+
// Messages
|
|
138
|
+
showSuccess('Operation completed!');
|
|
139
|
+
showError('Something went wrong');
|
|
140
|
+
showWarning('Please check your input');
|
|
141
|
+
showInfo('Press Ctrl+C to exit');
|
|
142
|
+
|
|
143
|
+
// Summary table
|
|
144
|
+
createSummaryTable('Session Summary', [
|
|
145
|
+
{
|
|
146
|
+
header: 'Statistics',
|
|
147
|
+
items: [
|
|
148
|
+
{ key: 'Total', value: '100' },
|
|
149
|
+
{ key: 'Success', value: '95' }
|
|
150
|
+
]
|
|
151
|
+
}
|
|
152
|
+
]);
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### i18n Support
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
import { setLanguage, t } from 'cli-menu-kit';
|
|
159
|
+
|
|
160
|
+
// Set language
|
|
161
|
+
setLanguage('en'); // or 'zh'
|
|
162
|
+
|
|
163
|
+
// Get translations
|
|
164
|
+
const prompt = t('menus.selectPrompt');
|
|
165
|
+
const goodbye = t('messages.goodbye');
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Command Handling
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
import { registerCommand, handleCommand } from 'cli-menu-kit';
|
|
172
|
+
|
|
173
|
+
// Register custom command
|
|
174
|
+
registerCommand('test', () => {
|
|
175
|
+
console.log('Test command executed!');
|
|
176
|
+
return false; // Continue (don't exit)
|
|
177
|
+
}, 'Run test command');
|
|
178
|
+
|
|
179
|
+
// Handle command input
|
|
180
|
+
const result = handleCommand('/test');
|
|
181
|
+
// Built-in commands: /quit, /help, /clear, /back
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## API Reference
|
|
185
|
+
|
|
186
|
+
### Menu API
|
|
187
|
+
|
|
188
|
+
#### menu.radio(config)
|
|
189
|
+
Single-select vertical menu.
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
interface RadioMenuConfig {
|
|
193
|
+
title?: string;
|
|
194
|
+
options: MenuOption[];
|
|
195
|
+
prompt?: string;
|
|
196
|
+
hints?: string[];
|
|
197
|
+
layout?: MenuLayout;
|
|
198
|
+
defaultIndex?: number;
|
|
199
|
+
allowNumberKeys?: boolean;
|
|
200
|
+
allowLetterKeys?: boolean;
|
|
201
|
+
onExit?: () => void;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Returns: { index: number, value: string }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### menu.checkbox(config)
|
|
208
|
+
Multi-select vertical menu.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
interface CheckboxMenuConfig {
|
|
212
|
+
title?: string;
|
|
213
|
+
options: MenuOption[];
|
|
214
|
+
prompt?: string;
|
|
215
|
+
hints?: string[];
|
|
216
|
+
layout?: MenuLayout;
|
|
217
|
+
defaultSelected?: number[];
|
|
218
|
+
minSelections?: number;
|
|
219
|
+
maxSelections?: number;
|
|
220
|
+
allowSelectAll?: boolean;
|
|
221
|
+
allowInvert?: boolean;
|
|
222
|
+
onExit?: () => void;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Returns: { indices: number[], values: string[] }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
#### menu.boolean(config)
|
|
229
|
+
Yes/No selection menu.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
interface BooleanMenuConfig {
|
|
233
|
+
question: string;
|
|
234
|
+
defaultValue?: boolean;
|
|
235
|
+
yesText?: string;
|
|
236
|
+
noText?: string;
|
|
237
|
+
orientation?: 'horizontal' | 'vertical';
|
|
238
|
+
onExit?: () => void;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Returns: boolean
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Input API
|
|
245
|
+
|
|
246
|
+
#### input.text(config)
|
|
247
|
+
Text input with validation.
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
interface TextInputConfig {
|
|
251
|
+
prompt: string;
|
|
252
|
+
defaultValue?: string;
|
|
253
|
+
placeholder?: string;
|
|
254
|
+
maxLength?: number;
|
|
255
|
+
minLength?: number;
|
|
256
|
+
allowEmpty?: boolean;
|
|
257
|
+
validate?: (value: string) => boolean | string;
|
|
258
|
+
errorMessage?: string;
|
|
259
|
+
onExit?: () => void;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Returns: string
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### input.number(config)
|
|
266
|
+
Number input with constraints.
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
interface NumberInputConfig {
|
|
270
|
+
prompt: string;
|
|
271
|
+
defaultValue?: number;
|
|
272
|
+
min?: number;
|
|
273
|
+
max?: number;
|
|
274
|
+
allowDecimals?: boolean;
|
|
275
|
+
allowNegative?: boolean;
|
|
276
|
+
validate?: (value: string) => boolean | string;
|
|
277
|
+
errorMessage?: string;
|
|
278
|
+
onExit?: () => void;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Returns: number
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
#### input.language(config)
|
|
285
|
+
Language selector.
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
interface LanguageSelectorConfig {
|
|
289
|
+
languages: Array<{
|
|
290
|
+
code: string;
|
|
291
|
+
name: string;
|
|
292
|
+
nativeName?: string;
|
|
293
|
+
}>;
|
|
294
|
+
defaultLanguage?: string;
|
|
295
|
+
prompt?: string;
|
|
296
|
+
onExit?: () => void;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Returns: string (language code)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Wizard API
|
|
303
|
+
|
|
304
|
+
#### wizard.run(config)
|
|
305
|
+
Run a multi-step wizard.
|
|
306
|
+
|
|
307
|
+
```typescript
|
|
308
|
+
interface WizardConfig {
|
|
309
|
+
title?: string;
|
|
310
|
+
steps: WizardStep[];
|
|
311
|
+
showProgress?: boolean;
|
|
312
|
+
onComplete?: (results: Record<string, any>) => void;
|
|
313
|
+
onCancel?: () => void;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
interface WizardStep {
|
|
317
|
+
name: string;
|
|
318
|
+
title: string;
|
|
319
|
+
component: 'radio-menu' | 'checkbox-menu' | 'boolean-menu' |
|
|
320
|
+
'text-input' | 'number-input' | 'language-selector';
|
|
321
|
+
config: any;
|
|
322
|
+
required?: boolean;
|
|
323
|
+
validate?: (value: any) => boolean | string;
|
|
324
|
+
skip?: (results: Record<string, any>) => boolean;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Returns: { completed: boolean, results: Record<string, any> }
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## Architecture
|
|
331
|
+
|
|
332
|
+
The library is organized into a modular architecture:
|
|
333
|
+
|
|
334
|
+
```
|
|
335
|
+
src/
|
|
336
|
+
├── types/ # Type definitions
|
|
337
|
+
│ ├── layout.types.ts
|
|
338
|
+
│ ├── menu.types.ts
|
|
339
|
+
│ ├── input.types.ts
|
|
340
|
+
│ └── display.types.ts
|
|
341
|
+
├── core/ # Core utilities
|
|
342
|
+
│ ├── terminal.ts
|
|
343
|
+
│ ├── keyboard.ts
|
|
344
|
+
│ ├── renderer.ts
|
|
345
|
+
│ └── colors.ts
|
|
346
|
+
├── components/ # UI components
|
|
347
|
+
│ ├── menus/
|
|
348
|
+
│ ├── inputs/
|
|
349
|
+
│ └── display/
|
|
350
|
+
├── features/ # Advanced features
|
|
351
|
+
│ ├── wizard.ts
|
|
352
|
+
│ └── commands.ts
|
|
353
|
+
├── i18n/ # Internationalization
|
|
354
|
+
│ ├── types.ts
|
|
355
|
+
│ ├── registry.ts
|
|
356
|
+
│ └── languages/
|
|
357
|
+
├── api.ts # Unified API
|
|
358
|
+
└── index.ts # Main entry point
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed documentation.
|
|
362
|
+
|
|
363
|
+
## Development
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
# Install dependencies
|
|
367
|
+
npm install
|
|
368
|
+
|
|
369
|
+
# Build TypeScript
|
|
370
|
+
npm run build
|
|
371
|
+
|
|
372
|
+
# Run tests
|
|
373
|
+
node test/phase2-test.js # Menu components
|
|
374
|
+
node test/phase3-test.js # Input components
|
|
375
|
+
node test/phase4-test.js # Display components
|
|
376
|
+
node test/phase5-test.js # Advanced features
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Design Principles
|
|
380
|
+
|
|
381
|
+
1. **Component-Based**: Each UI element is a separate, reusable component
|
|
382
|
+
2. **Layout System**: Components can be composed in different orders
|
|
383
|
+
3. **Type Safety**: Full TypeScript support with strict typing
|
|
384
|
+
4. **Zero Dependencies**: Pure Node.js implementation
|
|
385
|
+
5. **i18n Support**: Multi-language support with mapping system
|
|
386
|
+
6. **Maintainability**: All files kept under 300 lines
|
|
387
|
+
|
|
388
|
+
## License
|
|
389
|
+
|
|
390
|
+
MIT
|
|
391
|
+
|
|
392
|
+
## Contributing
|
|
393
|
+
|
|
394
|
+
Contributions are welcome! Please ensure:
|
|
395
|
+
- Files stay under 300 lines
|
|
396
|
+
- TypeScript types are properly defined
|
|
397
|
+
- Code follows existing patterns
|
|
398
|
+
- Tests are included for new features
|
|
399
|
+
- All comments in English
|
|
400
|
+
|
|
401
|
+
## Installation
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
npm install cli-menu-kit
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
## Quick Start
|
|
408
|
+
|
|
409
|
+
### Unified API (Recommended)
|
|
410
|
+
|
|
411
|
+
```javascript
|
|
412
|
+
const { menu } = require('cli-menu-kit');
|
|
413
|
+
|
|
414
|
+
// Single select
|
|
415
|
+
const choice = await menu.select(
|
|
416
|
+
['Option 1', 'Option 2', 'Option 3'],
|
|
417
|
+
{ title: 'Choose one', lang: 'en' }
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
// Multi select
|
|
421
|
+
const choices = await menu.multiSelect(
|
|
422
|
+
['Feature A', 'Feature B', 'Feature C'],
|
|
423
|
+
{ lang: 'en' }
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
// Yes/No confirmation
|
|
427
|
+
const confirmed = await menu.confirm('Continue?', { lang: 'en' });
|
|
428
|
+
|
|
429
|
+
// Text input
|
|
430
|
+
const name = await menu.input('Enter your name', {
|
|
431
|
+
defaultValue: 'User',
|
|
432
|
+
validator: (input) => input.length > 0 || 'Name cannot be empty'
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
// Number input
|
|
436
|
+
const age = await menu.number('Enter your age', {
|
|
437
|
+
min: 1,
|
|
438
|
+
max: 120
|
|
439
|
+
});
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
### Direct Function Calls
|
|
443
|
+
|
|
444
|
+
```javascript
|
|
445
|
+
const {
|
|
446
|
+
selectMenu,
|
|
447
|
+
selectMultiMenu,
|
|
448
|
+
askYesNo,
|
|
449
|
+
askInput,
|
|
450
|
+
askNumber
|
|
451
|
+
} = require('cli-menu-kit');
|
|
452
|
+
|
|
453
|
+
const choice = await selectMenu(['A', 'B', 'C'], { lang: 'zh' });
|
|
454
|
+
const choices = await selectMultiMenu(['1', '2', '3'], { lang: 'zh' });
|
|
455
|
+
const confirmed = await askYesNo('确认吗?', { lang: 'zh' });
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## API Reference
|
|
459
|
+
|
|
460
|
+
### menu.select(options, config)
|
|
461
|
+
|
|
462
|
+
Single-select menu with vertical navigation.
|
|
463
|
+
|
|
464
|
+
**Parameters:**
|
|
465
|
+
- `options`: Array of strings or `MenuOption` objects
|
|
466
|
+
- `config`: Configuration object
|
|
467
|
+
- `lang`: 'zh' | 'en' (default: 'zh')
|
|
468
|
+
- `type`: 'main' | 'sub' | 'firstRun' (default: 'main')
|
|
469
|
+
- `title`: Optional header title
|
|
470
|
+
- `showPrompt`: Show input prompt (default: true for main)
|
|
471
|
+
- `showHints`: Show operation hints (default: true)
|
|
472
|
+
|
|
473
|
+
**Returns:** Selected index (0-based)
|
|
474
|
+
|
|
475
|
+
**Keyboard shortcuts:**
|
|
476
|
+
- ↑/↓: Navigate
|
|
477
|
+
- 1-9: Quick select by number
|
|
478
|
+
- A-Z: Quick select by letter (for labeled options)
|
|
479
|
+
- Enter: Confirm
|
|
480
|
+
- Ctrl+C: Exit
|
|
481
|
+
|
|
482
|
+
### menu.multiSelect(options, config)
|
|
483
|
+
|
|
484
|
+
Multi-select menu with checkboxes.
|
|
485
|
+
|
|
486
|
+
**Parameters:**
|
|
487
|
+
- `options`: Array of strings
|
|
488
|
+
- `config`: Configuration object
|
|
489
|
+
- `lang`: 'zh' | 'en' (default: 'zh')
|
|
490
|
+
- `defaultSelected`: Array of pre-selected indices
|
|
491
|
+
|
|
492
|
+
**Returns:** Array of selected indices
|
|
493
|
+
|
|
494
|
+
**Keyboard shortcuts:**
|
|
495
|
+
- ↑/↓: Navigate
|
|
496
|
+
- Space: Toggle selection
|
|
497
|
+
- A: Select all
|
|
498
|
+
- I: Invert selection
|
|
499
|
+
- Enter: Confirm
|
|
500
|
+
- Ctrl+C: Exit
|
|
501
|
+
|
|
502
|
+
### menu.confirm(prompt, options)
|
|
503
|
+
|
|
504
|
+
Yes/No confirmation with horizontal selection.
|
|
505
|
+
|
|
506
|
+
**Parameters:**
|
|
507
|
+
- `prompt`: Question to ask
|
|
508
|
+
- `options`: Configuration object
|
|
509
|
+
- `lang`: 'zh' | 'en' (default: 'zh')
|
|
510
|
+
- `defaultYes`: Default to Yes (default: true)
|
|
511
|
+
|
|
512
|
+
**Returns:** Boolean (true for Yes, false for No)
|
|
513
|
+
|
|
514
|
+
**Keyboard shortcuts:**
|
|
515
|
+
- ←/→: Navigate
|
|
516
|
+
- Y/N: Quick select
|
|
517
|
+
- Enter: Confirm
|
|
518
|
+
- Ctrl+C: Exit
|
|
519
|
+
|
|
520
|
+
### menu.input(prompt, options)
|
|
521
|
+
|
|
522
|
+
Text input with validation.
|
|
523
|
+
|
|
524
|
+
**Parameters:**
|
|
525
|
+
- `prompt`: Input prompt text
|
|
526
|
+
- `options`: Configuration object
|
|
527
|
+
- `lang`: 'zh' | 'en' (default: 'zh')
|
|
528
|
+
- `defaultValue`: Default value
|
|
529
|
+
- `validator`: Validation function `(input: string) => boolean | string`
|
|
530
|
+
|
|
531
|
+
**Returns:** User input string
|
|
532
|
+
|
|
533
|
+
### menu.number(prompt, options)
|
|
534
|
+
|
|
535
|
+
Number input with constraints.
|
|
536
|
+
|
|
537
|
+
**Parameters:**
|
|
538
|
+
- `prompt`: Input prompt text
|
|
539
|
+
- `options`: Configuration object
|
|
540
|
+
- `lang`: 'zh' | 'en' (default: 'zh')
|
|
541
|
+
- `min`: Minimum value
|
|
542
|
+
- `max`: Maximum value
|
|
543
|
+
- `defaultValue`: Default value
|
|
544
|
+
|
|
545
|
+
**Returns:** User input number
|
|
546
|
+
|
|
547
|
+
### selectWithChildren(parentOptions, getChildOptions, config)
|
|
548
|
+
|
|
549
|
+
Parent-child menu relationship.
|
|
550
|
+
|
|
551
|
+
**Parameters:**
|
|
552
|
+
- `parentOptions`: Parent menu options
|
|
553
|
+
- `getChildOptions`: Function `(parentIndex: number) => string[]`
|
|
554
|
+
- `config`: Configuration object
|
|
555
|
+
- `parentConfig`: Parent menu configuration
|
|
556
|
+
- `childConfig`: Child menu configuration
|
|
557
|
+
|
|
558
|
+
**Returns:** `{ parentIndex: number, childIndices: number[] }`
|
|
559
|
+
|
|
560
|
+
## Examples
|
|
561
|
+
|
|
562
|
+
### Basic Single Select
|
|
563
|
+
|
|
564
|
+
```javascript
|
|
565
|
+
const { menu } = require('cli-menu-kit');
|
|
566
|
+
|
|
567
|
+
const options = [
|
|
568
|
+
'1. Create new project - Start a new project',
|
|
569
|
+
'2. Open existing - Open an existing project',
|
|
570
|
+
'3. Settings - Configure settings',
|
|
571
|
+
'4. Exit - Exit the application'
|
|
572
|
+
];
|
|
573
|
+
|
|
574
|
+
const choice = await menu.select(options, {
|
|
575
|
+
title: 'Main Menu',
|
|
576
|
+
lang: 'en'
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
console.log(`You selected: ${choice}`);
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### Multi-Select with Default Selection
|
|
583
|
+
|
|
584
|
+
```javascript
|
|
585
|
+
const { menu } = require('cli-menu-kit');
|
|
586
|
+
|
|
587
|
+
const features = ['Dark Mode', 'Auto Save', 'Notifications', 'Analytics'];
|
|
588
|
+
|
|
589
|
+
const selected = await menu.multiSelect(features, {
|
|
590
|
+
lang: 'en',
|
|
591
|
+
defaultSelected: [0, 1] // Pre-select first two options
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
console.log(`Selected features: ${selected.map(i => features[i]).join(', ')}`);
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
### Input with Validation
|
|
598
|
+
|
|
599
|
+
```javascript
|
|
600
|
+
const { menu } = require('cli-menu-kit');
|
|
601
|
+
|
|
602
|
+
const email = await menu.input('Enter your email', {
|
|
603
|
+
lang: 'en',
|
|
604
|
+
validator: (input) => {
|
|
605
|
+
if (!input.includes('@')) {
|
|
606
|
+
return 'Invalid email format';
|
|
607
|
+
}
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
console.log(`Email: ${email}`);
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### Parent-Child Menu
|
|
616
|
+
|
|
617
|
+
```javascript
|
|
618
|
+
const { selectWithChildren } = require('cli-menu-kit');
|
|
619
|
+
|
|
620
|
+
const categories = ['Electronics', 'Clothing', 'Books'];
|
|
621
|
+
|
|
622
|
+
const result = await selectWithChildren(
|
|
623
|
+
categories,
|
|
624
|
+
(parentIndex) => {
|
|
625
|
+
if (parentIndex === 0) return ['Phones', 'Laptops', 'Tablets'];
|
|
626
|
+
if (parentIndex === 1) return ['Shirts', 'Pants', 'Shoes'];
|
|
627
|
+
return ['Fiction', 'Non-Fiction', 'Comics'];
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
parentConfig: { title: 'Select Category', lang: 'en' },
|
|
631
|
+
childConfig: { lang: 'en' }
|
|
632
|
+
}
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
console.log(`Category: ${categories[result.parentIndex]}`);
|
|
636
|
+
console.log(`Items: ${result.childIndices.join(', ')}`);
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### Display Components
|
|
640
|
+
|
|
641
|
+
```javascript
|
|
642
|
+
const {
|
|
643
|
+
showInfo,
|
|
644
|
+
showSuccess,
|
|
645
|
+
showError,
|
|
646
|
+
showWarning,
|
|
647
|
+
printHeader
|
|
648
|
+
} = require('cli-menu-kit');
|
|
649
|
+
|
|
650
|
+
showInfo('Processing...', 'zh');
|
|
651
|
+
showSuccess('Operation completed!', 'en');
|
|
652
|
+
showError('Something went wrong', 'en');
|
|
653
|
+
showWarning('Please check your input', 'zh');
|
|
654
|
+
|
|
655
|
+
printHeader({
|
|
656
|
+
asciiArt: [' ███╗ ███╗', ' ████╗ ████║', ' ██╔████╔██║'],
|
|
657
|
+
title: 'My App',
|
|
658
|
+
subtitle: 'v1.0.0'
|
|
659
|
+
});
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
## Architecture
|
|
663
|
+
|
|
664
|
+
See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed architecture documentation.
|
|
665
|
+
|
|
666
|
+
## File Structure
|
|
667
|
+
|
|
668
|
+
```
|
|
669
|
+
src/
|
|
670
|
+
├── types.ts # Type definitions (58 lines)
|
|
671
|
+
├── components.ts # Colors, themes, symbols (187 lines)
|
|
672
|
+
├── menu-core.ts # Shared utilities (213 lines)
|
|
673
|
+
├── menu-single.ts # Single-select menu (163 lines)
|
|
674
|
+
├── menu-multi.ts # Multi-select menu (151 lines)
|
|
675
|
+
├── input.ts # Input components (246 lines)
|
|
676
|
+
├── menu.ts # Unified API wrapper (90 lines)
|
|
677
|
+
└── index.ts # Main entry point (12 lines)
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
All files are kept under 300 lines for maintainability.
|
|
681
|
+
|
|
682
|
+
## Development
|
|
683
|
+
|
|
684
|
+
```bash
|
|
685
|
+
# Install dependencies
|
|
686
|
+
npm install
|
|
687
|
+
|
|
688
|
+
# Build TypeScript
|
|
689
|
+
npm run build
|
|
690
|
+
|
|
691
|
+
# Run tests
|
|
692
|
+
node test/simple-test.js
|
|
693
|
+
node test/input-test.js
|
|
694
|
+
node test/unified-api-test.js
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
## Design Principles
|
|
698
|
+
|
|
699
|
+
1. **Modularity**: Each menu type is in its own file
|
|
700
|
+
2. **Decoupling**: Common utilities extracted to menu-core.ts
|
|
701
|
+
3. **File Size**: Each file kept under 200-300 lines
|
|
702
|
+
4. **Zero Dependencies**: Pure Node.js implementation
|
|
703
|
+
5. **Type Safety**: Full TypeScript support
|
|
704
|
+
6. **Flexibility**: Configurable layouts, prompts, hints
|
|
705
|
+
|
|
706
|
+
## License
|
|
707
|
+
|
|
708
|
+
MIT
|
|
709
|
+
|
|
710
|
+
## Contributing
|
|
711
|
+
|
|
712
|
+
Contributions are welcome! Please ensure:
|
|
713
|
+
- Files stay under 300 lines
|
|
714
|
+
- TypeScript types are properly defined
|
|
715
|
+
- Code follows existing patterns
|
|
716
|
+
- Tests are included for new features
|