cli-menu-kit 0.1.23 → 0.2.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/dist/api.d.ts +23 -5
- package/dist/api.js +16 -4
- package/dist/component-factories.d.ts +59 -0
- package/dist/component-factories.js +141 -0
- package/dist/components/display/header-v2.d.ts +13 -0
- package/dist/components/display/header-v2.js +43 -0
- package/dist/components/display/hints-v2.d.ts +10 -0
- package/dist/components/display/hints-v2.js +34 -0
- package/dist/components/display/hints.d.ts +56 -0
- package/dist/components/display/hints.js +81 -0
- package/dist/components/display/index.d.ts +3 -0
- package/dist/components/display/index.js +15 -1
- package/dist/components/display/input-prompt.d.ts +35 -0
- package/dist/components/display/input-prompt.js +36 -0
- package/dist/components/display/list.d.ts +49 -0
- package/dist/components/display/list.js +86 -0
- package/dist/components/display/summary.js +119 -15
- package/dist/components/display/table.d.ts +42 -0
- package/dist/components/display/table.js +107 -0
- package/dist/components/menus/boolean-menu.js +2 -1
- package/dist/components/menus/checkbox-menu.d.ts +2 -1
- package/dist/components/menus/checkbox-menu.js +30 -59
- package/dist/components/menus/checkbox-table-menu.d.ts +12 -0
- package/dist/components/menus/checkbox-table-menu.js +395 -0
- package/dist/components/menus/index.d.ts +1 -0
- package/dist/components/menus/index.js +3 -1
- package/dist/components/menus/radio-menu-split.d.ts +33 -0
- package/dist/components/menus/radio-menu-split.js +248 -0
- package/dist/components/menus/radio-menu-v2.d.ts +11 -0
- package/dist/components/menus/radio-menu-v2.js +150 -0
- package/dist/components/menus/radio-menu.d.ts +2 -1
- package/dist/components/menus/radio-menu.js +60 -123
- package/dist/core/hint-manager.d.ts +29 -0
- package/dist/core/hint-manager.js +65 -0
- package/dist/core/renderer.d.ts +2 -1
- package/dist/core/renderer.js +22 -6
- package/dist/core/screen-manager.d.ts +54 -0
- package/dist/core/screen-manager.js +119 -0
- package/dist/core/state-manager.d.ts +27 -0
- package/dist/core/state-manager.js +56 -0
- package/dist/core/terminal.d.ts +4 -1
- package/dist/core/terminal.js +37 -4
- package/dist/core/virtual-scroll.d.ts +65 -0
- package/dist/core/virtual-scroll.js +120 -0
- package/dist/i18n/languages/en.js +4 -1
- package/dist/i18n/languages/zh.js +4 -1
- package/dist/i18n/registry.d.ts +4 -3
- package/dist/i18n/registry.js +12 -4
- package/dist/i18n/types.d.ts +3 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.js +30 -4
- package/dist/layout.d.ts +68 -0
- package/dist/layout.js +134 -0
- package/dist/page-layout.d.ts +92 -0
- package/dist/page-layout.js +156 -0
- package/dist/types/display.types.d.ts +6 -0
- package/dist/types/menu.types.d.ts +57 -5
- package/package.json +1 -1
- package/dist/types/layout.types.d.ts +0 -56
- package/dist/types/layout.types.js +0 -36
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List Component
|
|
3
|
+
* Displays items in a list format with optional bullets/numbers
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* List item configuration
|
|
7
|
+
*/
|
|
8
|
+
export interface ListItem {
|
|
9
|
+
/** Item text */
|
|
10
|
+
text: string;
|
|
11
|
+
/** Item indent level (0 = no indent) */
|
|
12
|
+
indent?: number;
|
|
13
|
+
/** Custom bullet/prefix */
|
|
14
|
+
bullet?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* List configuration
|
|
18
|
+
*/
|
|
19
|
+
export interface ListConfig {
|
|
20
|
+
/** List items */
|
|
21
|
+
items: (string | ListItem)[];
|
|
22
|
+
/** List style */
|
|
23
|
+
style?: 'bullet' | 'number' | 'none';
|
|
24
|
+
/** Custom bullet character (for bullet style) */
|
|
25
|
+
bulletChar?: string;
|
|
26
|
+
/** Indent size in spaces */
|
|
27
|
+
indentSize?: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Render list component
|
|
31
|
+
* @param config - List configuration
|
|
32
|
+
*/
|
|
33
|
+
export declare function renderList(config: ListConfig): void;
|
|
34
|
+
/**
|
|
35
|
+
* Create list configuration (factory function)
|
|
36
|
+
*/
|
|
37
|
+
export declare function createList(items: (string | ListItem)[], options?: {
|
|
38
|
+
style?: 'bullet' | 'number' | 'none';
|
|
39
|
+
bulletChar?: string;
|
|
40
|
+
indentSize?: number;
|
|
41
|
+
}): ListConfig;
|
|
42
|
+
/**
|
|
43
|
+
* Create a simple bullet list
|
|
44
|
+
*/
|
|
45
|
+
export declare function createBulletList(items: string[]): ListConfig;
|
|
46
|
+
/**
|
|
47
|
+
* Create a numbered list
|
|
48
|
+
*/
|
|
49
|
+
export declare function createNumberedList(items: string[]): ListConfig;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* List Component
|
|
4
|
+
* Displays items in a list format with optional bullets/numbers
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.renderList = renderList;
|
|
8
|
+
exports.createList = createList;
|
|
9
|
+
exports.createBulletList = createBulletList;
|
|
10
|
+
exports.createNumberedList = createNumberedList;
|
|
11
|
+
const terminal_js_1 = require("../../core/terminal.js");
|
|
12
|
+
/**
|
|
13
|
+
* Render list component
|
|
14
|
+
* @param config - List configuration
|
|
15
|
+
*/
|
|
16
|
+
function renderList(config) {
|
|
17
|
+
const { items, style = 'bullet', bulletChar = '•', indentSize = 2 } = config;
|
|
18
|
+
if (items.length === 0) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
items.forEach((item, index) => {
|
|
22
|
+
let text;
|
|
23
|
+
let indent;
|
|
24
|
+
let bullet;
|
|
25
|
+
// Parse item
|
|
26
|
+
if (typeof item === 'string') {
|
|
27
|
+
text = item;
|
|
28
|
+
indent = 0;
|
|
29
|
+
bullet = undefined;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
text = item.text;
|
|
33
|
+
indent = item.indent || 0;
|
|
34
|
+
bullet = item.bullet;
|
|
35
|
+
}
|
|
36
|
+
// Generate prefix based on style
|
|
37
|
+
let prefix;
|
|
38
|
+
if (bullet) {
|
|
39
|
+
// Custom bullet
|
|
40
|
+
prefix = bullet;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
switch (style) {
|
|
44
|
+
case 'number':
|
|
45
|
+
prefix = `${index + 1}.`;
|
|
46
|
+
break;
|
|
47
|
+
case 'bullet':
|
|
48
|
+
prefix = bulletChar;
|
|
49
|
+
break;
|
|
50
|
+
case 'none':
|
|
51
|
+
default:
|
|
52
|
+
prefix = '';
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Build line with indent and prefix
|
|
57
|
+
const indentStr = ' '.repeat(indent * indentSize);
|
|
58
|
+
const line = prefix
|
|
59
|
+
? `${indentStr}${prefix} ${text}`
|
|
60
|
+
: `${indentStr}${text}`;
|
|
61
|
+
(0, terminal_js_1.writeLine)(line);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Create list configuration (factory function)
|
|
66
|
+
*/
|
|
67
|
+
function createList(items, options) {
|
|
68
|
+
return {
|
|
69
|
+
items,
|
|
70
|
+
style: options?.style ?? 'bullet',
|
|
71
|
+
bulletChar: options?.bulletChar ?? '•',
|
|
72
|
+
indentSize: options?.indentSize ?? 2
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Create a simple bullet list
|
|
77
|
+
*/
|
|
78
|
+
function createBulletList(items) {
|
|
79
|
+
return createList(items, { style: 'bullet' });
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Create a numbered list
|
|
83
|
+
*/
|
|
84
|
+
function createNumberedList(items) {
|
|
85
|
+
return createList(items, { style: 'number' });
|
|
86
|
+
}
|
|
@@ -11,7 +11,83 @@ const terminal_js_1 = require("../../core/terminal.js");
|
|
|
11
11
|
const colors_js_1 = require("../../core/colors.js");
|
|
12
12
|
const terminal_js_2 = require("../../core/terminal.js");
|
|
13
13
|
/**
|
|
14
|
-
* Wrap text to fit within a specific width
|
|
14
|
+
* Wrap text to fit within a specific width, preserving ANSI color codes
|
|
15
|
+
*/
|
|
16
|
+
function wrapTextWithColors(text, maxWidth) {
|
|
17
|
+
// Extract ANSI codes and plain text
|
|
18
|
+
const ansiRegex = /\x1b\[[0-9;]*m/g;
|
|
19
|
+
const plainText = text.replace(ansiRegex, '');
|
|
20
|
+
// If plain text fits, return as-is
|
|
21
|
+
if (plainText.length <= maxWidth) {
|
|
22
|
+
return [text];
|
|
23
|
+
}
|
|
24
|
+
// Find all ANSI codes and their positions in the original text
|
|
25
|
+
const codes = [];
|
|
26
|
+
let match;
|
|
27
|
+
let offset = 0;
|
|
28
|
+
const textCopy = text;
|
|
29
|
+
while ((match = ansiRegex.exec(textCopy)) !== null) {
|
|
30
|
+
// Calculate position in plain text
|
|
31
|
+
const plainPos = match.index - offset;
|
|
32
|
+
codes.push({ pos: plainPos, code: match[0] });
|
|
33
|
+
offset += match[0].length;
|
|
34
|
+
}
|
|
35
|
+
// Wrap plain text by words
|
|
36
|
+
const words = plainText.split(' ');
|
|
37
|
+
const lines = [];
|
|
38
|
+
let currentLine = '';
|
|
39
|
+
let currentPos = 0;
|
|
40
|
+
for (const word of words) {
|
|
41
|
+
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
42
|
+
if (testLine.length <= maxWidth) {
|
|
43
|
+
currentLine = testLine;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
if (currentLine) {
|
|
47
|
+
lines.push(currentLine);
|
|
48
|
+
currentPos += currentLine.length + 1; // +1 for space
|
|
49
|
+
}
|
|
50
|
+
currentLine = word;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (currentLine) {
|
|
54
|
+
lines.push(currentLine);
|
|
55
|
+
}
|
|
56
|
+
// Re-insert ANSI codes into wrapped lines
|
|
57
|
+
const result = [];
|
|
58
|
+
let lineStart = 0;
|
|
59
|
+
let activeColor = '';
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
const lineEnd = lineStart + line.length;
|
|
62
|
+
let coloredLine = activeColor; // Start with active color from previous line
|
|
63
|
+
// Find codes that apply to this line
|
|
64
|
+
let lastPos = 0;
|
|
65
|
+
for (const { pos, code } of codes) {
|
|
66
|
+
if (pos >= lineStart && pos < lineEnd) {
|
|
67
|
+
const relPos = pos - lineStart;
|
|
68
|
+
coloredLine += line.substring(lastPos, relPos) + code;
|
|
69
|
+
lastPos = relPos;
|
|
70
|
+
// Track active color (reset or new color)
|
|
71
|
+
if (code === '\x1b[0m') {
|
|
72
|
+
activeColor = '';
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
activeColor = code;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
coloredLine += line.substring(lastPos);
|
|
80
|
+
// Add reset at end if there's an active color
|
|
81
|
+
if (activeColor && activeColor !== '\x1b[0m') {
|
|
82
|
+
coloredLine += '\x1b[0m';
|
|
83
|
+
}
|
|
84
|
+
result.push(coloredLine);
|
|
85
|
+
lineStart = lineEnd + 1; // +1 for space between words
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Wrap text to fit within a specific width (plain text only)
|
|
15
91
|
*/
|
|
16
92
|
function wrapText(text, maxWidth) {
|
|
17
93
|
const words = text.split(' ');
|
|
@@ -39,7 +115,16 @@ function wrapText(text, maxWidth) {
|
|
|
39
115
|
* @param config - Summary table configuration
|
|
40
116
|
*/
|
|
41
117
|
function renderSummaryTable(config) {
|
|
42
|
-
const { title, titleAlign = 'center', sections, width } = config;
|
|
118
|
+
const { title, titleAlign = 'center', sections, width, colors: colorConfig } = config;
|
|
119
|
+
// Default colors
|
|
120
|
+
const defaultColors = {
|
|
121
|
+
title: 'cyan+bold',
|
|
122
|
+
sectionHeader: '', // No color (default/black)
|
|
123
|
+
key: 'cyan',
|
|
124
|
+
value: '' // No color (default/black)
|
|
125
|
+
};
|
|
126
|
+
// Merge with provided colors
|
|
127
|
+
const finalColors = { ...defaultColors, ...colorConfig };
|
|
43
128
|
const termWidth = (0, terminal_js_2.getTerminalWidth)();
|
|
44
129
|
// Use full terminal width minus padding, or specified width
|
|
45
130
|
const boxWidth = width || Math.max(60, termWidth - 4);
|
|
@@ -51,55 +136,68 @@ function renderSummaryTable(config) {
|
|
|
51
136
|
(0, terminal_js_1.writeLine)(`│${' '.repeat(boxWidth - 2)}│`);
|
|
52
137
|
// Title if provided
|
|
53
138
|
if (title) {
|
|
139
|
+
// Parse color configuration (supports "color" or "color+style" format)
|
|
140
|
+
let titleColor = '';
|
|
141
|
+
if (finalColors.title) {
|
|
142
|
+
const parts = finalColors.title.split('+');
|
|
143
|
+
titleColor = parts.map(part => colors_js_1.colors[part.trim()] || '').join('');
|
|
144
|
+
}
|
|
145
|
+
const resetColor = titleColor ? colors_js_1.colors.reset : '';
|
|
54
146
|
let titleLine;
|
|
55
147
|
let remainingSpace;
|
|
56
148
|
if (titleAlign === 'left') {
|
|
57
|
-
titleLine = ` ${
|
|
149
|
+
titleLine = ` ${titleColor}${title}${resetColor}`;
|
|
58
150
|
const plainTitle = title;
|
|
59
151
|
remainingSpace = boxWidth - plainTitle.length - 4;
|
|
60
152
|
}
|
|
61
153
|
else if (titleAlign === 'right') {
|
|
62
154
|
const plainTitle = title;
|
|
63
155
|
const rightPadding = contentWidth - plainTitle.length;
|
|
64
|
-
titleLine = ' '.repeat(rightPadding + 2) +
|
|
156
|
+
titleLine = ' '.repeat(rightPadding + 2) + titleColor + title + resetColor;
|
|
65
157
|
remainingSpace = 2;
|
|
66
158
|
}
|
|
67
159
|
else {
|
|
68
160
|
// center (default)
|
|
69
161
|
const titlePadding = Math.floor((contentWidth - title.length) / 2);
|
|
70
|
-
titleLine = ' '.repeat(titlePadding + 2) +
|
|
162
|
+
titleLine = ' '.repeat(titlePadding + 2) + titleColor + title + resetColor;
|
|
71
163
|
remainingSpace = boxWidth - titlePadding - title.length - 4;
|
|
72
164
|
}
|
|
73
165
|
(0, terminal_js_1.writeLine)(`│${titleLine}${' '.repeat(remainingSpace)}│`);
|
|
74
166
|
(0, terminal_js_1.writeLine)(`│${' '.repeat(boxWidth - 2)}│`);
|
|
75
167
|
}
|
|
168
|
+
// Calculate global keyPadding based on longest key across ALL sections
|
|
169
|
+
const allKeys = sections.flatMap(section => section.items.map(item => item.key));
|
|
170
|
+
const maxKeyLength = Math.max(...allKeys.map(key => key.length));
|
|
171
|
+
const keyPadding = maxKeyLength + 3; // +3 for colon and spacing
|
|
76
172
|
// Sections
|
|
77
173
|
sections.forEach((section, sectionIndex) => {
|
|
78
174
|
// Section header if provided
|
|
79
175
|
if (section.header) {
|
|
80
|
-
const
|
|
176
|
+
const headerColor = finalColors.sectionHeader ? colors_js_1.colors[finalColors.sectionHeader] || '' : '';
|
|
177
|
+
const resetColor = headerColor ? colors_js_1.colors.reset : '';
|
|
178
|
+
const headerLine = ` ${headerColor}${section.header}${resetColor}`;
|
|
81
179
|
const remainingSpace = boxWidth - section.header.length - 4;
|
|
82
180
|
(0, terminal_js_1.writeLine)(`│${headerLine}${' '.repeat(remainingSpace)}│`);
|
|
83
181
|
}
|
|
84
|
-
//
|
|
85
|
-
const maxKeyLength = Math.max(...section.items.map(item => item.key.length));
|
|
86
|
-
const keyPadding = maxKeyLength + 3; // +3 for colon and spacing
|
|
87
|
-
// Section items
|
|
182
|
+
// Section items (using global keyPadding)
|
|
88
183
|
section.items.forEach(item => {
|
|
89
184
|
const valueMaxWidth = contentWidth - keyPadding - 2; // 2 for leading spaces
|
|
90
185
|
// Check if value needs wrapping
|
|
91
186
|
const plainValue = item.value.replace(/\x1b\[[0-9;]*m/g, '');
|
|
92
187
|
if (plainValue.length > valueMaxWidth) {
|
|
93
|
-
// Wrap the value
|
|
94
|
-
const wrappedLines =
|
|
188
|
+
// Wrap the value (preserving colors if present)
|
|
189
|
+
const wrappedLines = wrapTextWithColors(item.value, valueMaxWidth);
|
|
95
190
|
// First line with key
|
|
96
|
-
const
|
|
191
|
+
const keyColor = finalColors.key ? colors_js_1.colors[finalColors.key] || '' : '';
|
|
192
|
+
const keyResetColor = keyColor ? colors_js_1.colors.reset : '';
|
|
193
|
+
// wrappedLines already contain colors, don't add valueColor
|
|
194
|
+
const firstLine = ` ${keyColor}${item.key}:${keyResetColor}${' '.repeat(Math.max(1, keyPadding - item.key.length))}${wrappedLines[0]}`;
|
|
97
195
|
const plainFirstLine = firstLine.replace(/\x1b\[[0-9;]*m/g, '');
|
|
98
196
|
const remainingSpace = boxWidth - plainFirstLine.length - 2;
|
|
99
197
|
(0, terminal_js_1.writeLine)(`│${firstLine}${' '.repeat(Math.max(0, remainingSpace))}│`);
|
|
100
198
|
// Subsequent lines with indentation
|
|
101
199
|
for (let i = 1; i < wrappedLines.length; i++) {
|
|
102
|
-
const continuationLine = ` ${' '.repeat(keyPadding)}${wrappedLines[i]}`;
|
|
200
|
+
const continuationLine = ` ${' '.repeat(keyPadding + 1)}${wrappedLines[i]}`;
|
|
103
201
|
const plainContinuationLine = continuationLine.replace(/\x1b\[[0-9;]*m/g, '');
|
|
104
202
|
const contRemainingSpace = boxWidth - plainContinuationLine.length - 2;
|
|
105
203
|
(0, terminal_js_1.writeLine)(`│${continuationLine}${' '.repeat(Math.max(0, contRemainingSpace))}│`);
|
|
@@ -107,7 +205,13 @@ function renderSummaryTable(config) {
|
|
|
107
205
|
}
|
|
108
206
|
else {
|
|
109
207
|
// No wrapping needed
|
|
110
|
-
const
|
|
208
|
+
const keyColor = finalColors.key ? colors_js_1.colors[finalColors.key] || '' : '';
|
|
209
|
+
const keyResetColor = keyColor ? colors_js_1.colors.reset : '';
|
|
210
|
+
const valueColor = finalColors.value ? colors_js_1.colors[finalColors.value] || '' : '';
|
|
211
|
+
const valueResetColor = valueColor ? colors_js_1.colors.reset : '';
|
|
212
|
+
// Only wrap value with color if valueColor is set, otherwise preserve original colors in item.value
|
|
213
|
+
const coloredValue = valueColor ? `${valueColor}${item.value}${valueResetColor}` : item.value;
|
|
214
|
+
const itemLine = ` ${keyColor}${item.key}:${keyResetColor}${' '.repeat(Math.max(1, keyPadding - item.key.length))}${coloredValue}`;
|
|
111
215
|
const plainItemLine = itemLine.replace(/\x1b\[[0-9;]*m/g, '');
|
|
112
216
|
const remainingSpace = boxWidth - plainItemLine.length - 2;
|
|
113
217
|
(0, terminal_js_1.writeLine)(`│${itemLine}${' '.repeat(Math.max(0, remainingSpace))}│`);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Table Component
|
|
3
|
+
* Displays data in a table format with headers and rows
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Table column configuration
|
|
7
|
+
*/
|
|
8
|
+
export interface TableColumn {
|
|
9
|
+
/** Column header */
|
|
10
|
+
header: string;
|
|
11
|
+
/** Column key in data object */
|
|
12
|
+
key: string;
|
|
13
|
+
/** Column width (optional, auto-calculated if not provided) */
|
|
14
|
+
width?: number;
|
|
15
|
+
/** Text alignment */
|
|
16
|
+
align?: 'left' | 'center' | 'right';
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Table configuration
|
|
20
|
+
*/
|
|
21
|
+
export interface TableConfig {
|
|
22
|
+
/** Table columns */
|
|
23
|
+
columns: TableColumn[];
|
|
24
|
+
/** Table data rows */
|
|
25
|
+
data: Record<string, any>[];
|
|
26
|
+
/** Show table borders */
|
|
27
|
+
showBorders?: boolean;
|
|
28
|
+
/** Show header separator */
|
|
29
|
+
showHeaderSeparator?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Render table component
|
|
33
|
+
* @param config - Table configuration
|
|
34
|
+
*/
|
|
35
|
+
export declare function renderTable(config: TableConfig): void;
|
|
36
|
+
/**
|
|
37
|
+
* Create table configuration (factory function)
|
|
38
|
+
*/
|
|
39
|
+
export declare function createTable(columns: TableColumn[], data: Record<string, any>[], options?: {
|
|
40
|
+
showBorders?: boolean;
|
|
41
|
+
showHeaderSeparator?: boolean;
|
|
42
|
+
}): TableConfig;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Table Component
|
|
4
|
+
* Displays data in a table format with headers and rows
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.renderTable = renderTable;
|
|
8
|
+
exports.createTable = createTable;
|
|
9
|
+
const terminal_js_1 = require("../../core/terminal.js");
|
|
10
|
+
/**
|
|
11
|
+
* Render table component
|
|
12
|
+
* @param config - Table configuration
|
|
13
|
+
*/
|
|
14
|
+
function renderTable(config) {
|
|
15
|
+
const { columns, data, showBorders = true, showHeaderSeparator = true } = config;
|
|
16
|
+
if (columns.length === 0 || data.length === 0) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
// Calculate column widths
|
|
20
|
+
const columnWidths = columns.map((col, index) => {
|
|
21
|
+
if (col.width)
|
|
22
|
+
return col.width;
|
|
23
|
+
// Auto-calculate width based on header and data
|
|
24
|
+
const headerWidth = col.header.length;
|
|
25
|
+
const dataWidth = Math.max(...data.map(row => String(row[col.key] || '').length));
|
|
26
|
+
return Math.max(headerWidth, dataWidth) + 2; // +2 for padding
|
|
27
|
+
});
|
|
28
|
+
// Render top border
|
|
29
|
+
if (showBorders) {
|
|
30
|
+
const border = columnWidths.map(w => '─'.repeat(w)).join('┬');
|
|
31
|
+
(0, terminal_js_1.writeLine)(`┌${border}┐`);
|
|
32
|
+
}
|
|
33
|
+
// Render header
|
|
34
|
+
const headerCells = columns.map((col, index) => {
|
|
35
|
+
const width = columnWidths[index];
|
|
36
|
+
const align = col.align || 'left';
|
|
37
|
+
return alignText(col.header, width, align);
|
|
38
|
+
});
|
|
39
|
+
if (showBorders) {
|
|
40
|
+
(0, terminal_js_1.writeLine)(`│${headerCells.join('│')}│`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
(0, terminal_js_1.writeLine)(headerCells.join(' '));
|
|
44
|
+
}
|
|
45
|
+
// Render header separator
|
|
46
|
+
if (showHeaderSeparator) {
|
|
47
|
+
if (showBorders) {
|
|
48
|
+
const separator = columnWidths.map(w => '─'.repeat(w)).join('┼');
|
|
49
|
+
(0, terminal_js_1.writeLine)(`├${separator}┤`);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const separator = columnWidths.map(w => '─'.repeat(w)).join(' ');
|
|
53
|
+
(0, terminal_js_1.writeLine)(separator);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// Render data rows
|
|
57
|
+
data.forEach((row, rowIndex) => {
|
|
58
|
+
const cells = columns.map((col, colIndex) => {
|
|
59
|
+
const width = columnWidths[colIndex];
|
|
60
|
+
const align = col.align || 'left';
|
|
61
|
+
const value = String(row[col.key] || '');
|
|
62
|
+
return alignText(value, width, align);
|
|
63
|
+
});
|
|
64
|
+
if (showBorders) {
|
|
65
|
+
(0, terminal_js_1.writeLine)(`│${cells.join('│')}│`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
(0, terminal_js_1.writeLine)(cells.join(' '));
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// Render bottom border
|
|
72
|
+
if (showBorders) {
|
|
73
|
+
const border = columnWidths.map(w => '─'.repeat(w)).join('┴');
|
|
74
|
+
(0, terminal_js_1.writeLine)(`└${border}┘`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Align text within a given width
|
|
79
|
+
*/
|
|
80
|
+
function alignText(text, width, align) {
|
|
81
|
+
const padding = width - text.length;
|
|
82
|
+
if (padding <= 0) {
|
|
83
|
+
return text.substring(0, width);
|
|
84
|
+
}
|
|
85
|
+
switch (align) {
|
|
86
|
+
case 'center':
|
|
87
|
+
const leftPad = Math.floor(padding / 2);
|
|
88
|
+
const rightPad = padding - leftPad;
|
|
89
|
+
return ' '.repeat(leftPad) + text + ' '.repeat(rightPad);
|
|
90
|
+
case 'right':
|
|
91
|
+
return ' '.repeat(padding) + text;
|
|
92
|
+
case 'left':
|
|
93
|
+
default:
|
|
94
|
+
return text + ' '.repeat(padding);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Create table configuration (factory function)
|
|
99
|
+
*/
|
|
100
|
+
function createTable(columns, data, options) {
|
|
101
|
+
return {
|
|
102
|
+
columns,
|
|
103
|
+
data,
|
|
104
|
+
showBorders: options?.showBorders ?? true,
|
|
105
|
+
showHeaderSeparator: options?.showHeaderSeparator ?? true
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -8,13 +8,14 @@ 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 = '
|
|
18
|
+
const { question, defaultValue = true, yesText = (0, registry_js_1.t)('menus.yes'), noText = (0, registry_js_1.t)('menus.no'), orientation = 'horizontal', onExit, preserveOnSelect = false } = config;
|
|
18
19
|
if (orientation === 'horizontal') {
|
|
19
20
|
return showBooleanMenuHorizontal(question, defaultValue, yesText, noText, onExit, preserveOnSelect);
|
|
20
21
|
}
|
|
@@ -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,20 @@
|
|
|
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,
|
|
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;
|
|
34
20
|
// Use i18n for default prompt if not provided
|
|
35
21
|
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
22
|
// Validate options
|
|
39
23
|
if (!options || options.length === 0) {
|
|
40
24
|
throw new Error('CheckboxMenu requires at least one option');
|
|
@@ -89,49 +73,36 @@ async function showCheckboxMenu(config) {
|
|
|
89
73
|
const render = () => {
|
|
90
74
|
(0, terminal_js_1.clearMenu)(state);
|
|
91
75
|
let lineCount = 0;
|
|
92
|
-
// Render
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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;
|
|
76
|
+
// Render title if provided
|
|
77
|
+
if (title) {
|
|
78
|
+
(0, renderer_js_1.renderBlankLines)(1);
|
|
79
|
+
lineCount++;
|
|
80
|
+
}
|
|
81
|
+
// Render input prompt (show selected count)
|
|
82
|
+
const selectedCount = selected.size;
|
|
83
|
+
const displayValue = `${selectedCount} ${(0, registry_js_1.t)('menus.selectedCount')}`;
|
|
84
|
+
(0, renderer_js_1.renderInputPrompt)(displayPrompt, displayValue);
|
|
85
|
+
lineCount++;
|
|
86
|
+
(0, renderer_js_1.renderBlankLines)(1);
|
|
87
|
+
lineCount++;
|
|
88
|
+
// Render options
|
|
89
|
+
optionData.forEach((item, index) => {
|
|
90
|
+
if (item.isSeparator) {
|
|
91
|
+
// Render section label
|
|
92
|
+
(0, renderer_js_1.renderSectionLabel)(item.label, separatorWidth);
|
|
127
93
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (layout.spacing?.[afterSpacingKey]) {
|
|
131
|
-
(0, renderer_js_1.renderBlankLines)(layout.spacing[afterSpacingKey]);
|
|
132
|
-
lineCount += layout.spacing[afterSpacingKey];
|
|
94
|
+
else {
|
|
95
|
+
(0, renderer_js_1.renderOption)(item.value, selected.has(index), index === cursorIndex);
|
|
133
96
|
}
|
|
97
|
+
lineCount++;
|
|
134
98
|
});
|
|
99
|
+
// Render hints if provided
|
|
100
|
+
if (hints && hints.length > 0) {
|
|
101
|
+
(0, renderer_js_1.renderBlankLines)(1);
|
|
102
|
+
lineCount++;
|
|
103
|
+
(0, renderer_js_1.renderHints)(hints);
|
|
104
|
+
lineCount += 1;
|
|
105
|
+
}
|
|
135
106
|
state.renderedLines = lineCount;
|
|
136
107
|
};
|
|
137
108
|
// Initial render
|
|
@@ -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>;
|