mepcli 0.5.5 → 0.6.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/README.md +173 -6
- package/dist/ansi.d.ts +1 -0
- package/dist/ansi.js +1 -0
- package/dist/base.d.ts +1 -1
- package/dist/base.js +1 -10
- package/dist/core.d.ts +23 -1
- package/dist/core.js +60 -0
- package/dist/highlight.d.ts +1 -0
- package/dist/highlight.js +40 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/input.js +26 -14
- package/dist/prompts/autocomplete.d.ts +1 -1
- package/dist/prompts/autocomplete.js +2 -7
- package/dist/prompts/calendar.d.ts +20 -0
- package/dist/prompts/calendar.js +329 -0
- package/dist/prompts/checkbox.d.ts +1 -1
- package/dist/prompts/checkbox.js +38 -8
- package/dist/prompts/code.d.ts +17 -0
- package/dist/prompts/code.js +210 -0
- package/dist/prompts/color.d.ts +14 -0
- package/dist/prompts/color.js +147 -0
- package/dist/prompts/confirm.d.ts +1 -1
- package/dist/prompts/confirm.js +1 -1
- package/dist/prompts/cron.d.ts +13 -0
- package/dist/prompts/cron.js +176 -0
- package/dist/prompts/date.d.ts +1 -1
- package/dist/prompts/date.js +15 -5
- package/dist/prompts/editor.js +2 -2
- package/dist/prompts/file.d.ts +7 -0
- package/dist/prompts/file.js +56 -60
- package/dist/prompts/form.d.ts +17 -0
- package/dist/prompts/form.js +225 -0
- package/dist/prompts/grid.d.ts +14 -0
- package/dist/prompts/grid.js +178 -0
- package/dist/prompts/keypress.d.ts +2 -2
- package/dist/prompts/keypress.js +2 -2
- package/dist/prompts/list.d.ts +1 -1
- package/dist/prompts/list.js +42 -22
- package/dist/prompts/multi-select.d.ts +1 -1
- package/dist/prompts/multi-select.js +39 -4
- package/dist/prompts/number.d.ts +1 -1
- package/dist/prompts/number.js +2 -2
- package/dist/prompts/range.d.ts +9 -0
- package/dist/prompts/range.js +140 -0
- package/dist/prompts/rating.d.ts +1 -1
- package/dist/prompts/rating.js +1 -1
- package/dist/prompts/select.d.ts +1 -1
- package/dist/prompts/select.js +1 -1
- package/dist/prompts/slider.d.ts +1 -1
- package/dist/prompts/slider.js +1 -1
- package/dist/prompts/snippet.d.ts +18 -0
- package/dist/prompts/snippet.js +203 -0
- package/dist/prompts/sort.d.ts +1 -1
- package/dist/prompts/sort.js +1 -4
- package/dist/prompts/spam.d.ts +17 -0
- package/dist/prompts/spam.js +62 -0
- package/dist/prompts/table.d.ts +1 -1
- package/dist/prompts/table.js +1 -1
- package/dist/prompts/text.d.ts +1 -0
- package/dist/prompts/text.js +13 -31
- package/dist/prompts/toggle.d.ts +1 -1
- package/dist/prompts/toggle.js +1 -1
- package/dist/prompts/transfer.d.ts +18 -0
- package/dist/prompts/transfer.js +203 -0
- package/dist/prompts/tree-select.d.ts +32 -0
- package/dist/prompts/tree-select.js +277 -0
- package/dist/prompts/tree.d.ts +3 -3
- package/dist/prompts/tree.js +27 -19
- package/dist/prompts/wait.d.ts +18 -0
- package/dist/prompts/wait.js +62 -0
- package/dist/types.d.ts +84 -0
- package/dist/utils.js +1 -1
- package/example.ts +150 -15
- package/package.json +2 -2
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FormPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
const symbols_1 = require("../symbols");
|
|
8
|
+
const utils_1 = require("../utils");
|
|
9
|
+
class FormPrompt extends base_1.Prompt {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
super(options);
|
|
12
|
+
this.values = {};
|
|
13
|
+
this.activeIndex = 0;
|
|
14
|
+
this.fieldErrors = {};
|
|
15
|
+
this.globalError = '';
|
|
16
|
+
this.cursor = 0; // Cursor position for the ACTIVE field.
|
|
17
|
+
this.lastLinesUp = 0; // To track cursor position after render
|
|
18
|
+
this.options.fields.forEach(field => {
|
|
19
|
+
this.values[field.name] = field.initial || '';
|
|
20
|
+
});
|
|
21
|
+
this.cursor = this.values[this.options.fields[0].name].length;
|
|
22
|
+
}
|
|
23
|
+
render(firstRender) {
|
|
24
|
+
if (!firstRender && this.lastLinesUp > 0) {
|
|
25
|
+
this.print(`\x1b[${this.lastLinesUp}B`);
|
|
26
|
+
}
|
|
27
|
+
this.lastLinesUp = 0;
|
|
28
|
+
const outputLines = [];
|
|
29
|
+
// Title
|
|
30
|
+
outputLines.push(`${theme_1.theme.success}? ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}`);
|
|
31
|
+
if (this.globalError) {
|
|
32
|
+
outputLines.push(`${theme_1.theme.error}>> ${this.globalError}${ansi_1.ANSI.RESET}`);
|
|
33
|
+
}
|
|
34
|
+
let cursorLineIndex = -1;
|
|
35
|
+
let cursorColIndex = 0;
|
|
36
|
+
this.options.fields.forEach((field, index) => {
|
|
37
|
+
const isActive = index === this.activeIndex;
|
|
38
|
+
const value = this.values[field.name];
|
|
39
|
+
const error = this.fieldErrors[field.name];
|
|
40
|
+
// Icon
|
|
41
|
+
let icon = '';
|
|
42
|
+
if (isActive) {
|
|
43
|
+
icon = `${theme_1.theme.main}${symbols_1.symbols.pointer}`; // >
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
icon = ' '; // indent
|
|
47
|
+
}
|
|
48
|
+
// Label
|
|
49
|
+
const labelStyle = isActive ? `${theme_1.theme.main}${ansi_1.ANSI.BOLD}` : theme_1.theme.muted;
|
|
50
|
+
const label = `${labelStyle}${field.message}:${ansi_1.ANSI.RESET}`;
|
|
51
|
+
// Value
|
|
52
|
+
// Note: Use Secret/Password prompt for case sensitive input, Form prompt is for general text input
|
|
53
|
+
const displayValue = isActive ? value : `${theme_1.theme.muted}${value}${ansi_1.ANSI.RESET}`;
|
|
54
|
+
// Construct Line
|
|
55
|
+
const line = `${icon} ${label} ${displayValue}`;
|
|
56
|
+
outputLines.push(line);
|
|
57
|
+
if (isActive) {
|
|
58
|
+
// Determine cursor position
|
|
59
|
+
// We need visual length of "icon + label + value_up_to_cursor"
|
|
60
|
+
const prefix = `${icon} ${label} `;
|
|
61
|
+
const valuePrefix = value.substring(0, this.cursor);
|
|
62
|
+
cursorLineIndex = outputLines.length - 1;
|
|
63
|
+
cursorColIndex = (0, utils_1.stringWidth)(this.stripAnsi(prefix)) + (0, utils_1.stringWidth)(this.stripAnsi(valuePrefix));
|
|
64
|
+
}
|
|
65
|
+
// Error for this field
|
|
66
|
+
if (error) {
|
|
67
|
+
outputLines.push(` ${theme_1.theme.error}>> ${error}${ansi_1.ANSI.RESET}`);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// Instructions
|
|
71
|
+
outputLines.push('');
|
|
72
|
+
outputLines.push(`${ansi_1.ANSI.RESET}${theme_1.theme.muted}(Use Up/Down/Tab to navigate, Enter to submit)${ansi_1.ANSI.RESET}`);
|
|
73
|
+
const output = outputLines.join('\n');
|
|
74
|
+
this.renderFrame(output);
|
|
75
|
+
// Position Cursor
|
|
76
|
+
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
77
|
+
if (cursorLineIndex !== -1) {
|
|
78
|
+
// Calculate lines up from bottom
|
|
79
|
+
const totalLines = outputLines.length;
|
|
80
|
+
const linesUp = (totalLines - 1) - cursorLineIndex;
|
|
81
|
+
if (linesUp > 0) {
|
|
82
|
+
this.print(`\x1b[${linesUp}A`);
|
|
83
|
+
this.lastLinesUp = linesUp;
|
|
84
|
+
}
|
|
85
|
+
this.print(ansi_1.ANSI.CURSOR_LEFT);
|
|
86
|
+
if (cursorColIndex > 0) {
|
|
87
|
+
this.print(`\x1b[${cursorColIndex}C`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
handleInput(char, key) {
|
|
92
|
+
// Navigation: Up / Shift+Tab
|
|
93
|
+
if (this.isUp(char) || (char === '\t' && key /* how to detect Shift+Tab? usually \u001b[Z */) || char === '\u001b[Z') {
|
|
94
|
+
this.validateCurrentField();
|
|
95
|
+
this.moveFocus(-1);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Navigation: Down / Tab
|
|
99
|
+
if (this.isDown(char) || char === '\t') {
|
|
100
|
+
this.validateCurrentField();
|
|
101
|
+
this.moveFocus(1);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// Enter
|
|
105
|
+
if (char === '\r' || char === '\n') {
|
|
106
|
+
this.validateCurrentField().then(isValid => {
|
|
107
|
+
if (isValid) {
|
|
108
|
+
if (this.activeIndex < this.options.fields.length - 1) {
|
|
109
|
+
this.moveFocus(1);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// Submit
|
|
113
|
+
this.submitForm();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Editing Active Field
|
|
120
|
+
const activeField = this.options.fields[this.activeIndex];
|
|
121
|
+
const val = this.values[activeField.name];
|
|
122
|
+
if (char === '\u0008' || char === '\x7f') { // Backspace
|
|
123
|
+
if (this.cursor > 0) {
|
|
124
|
+
const pre = val.slice(0, this.cursor - 1);
|
|
125
|
+
const post = val.slice(this.cursor);
|
|
126
|
+
this.values[activeField.name] = pre + post;
|
|
127
|
+
this.cursor--;
|
|
128
|
+
this.render(false);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Left/Right arrow to move cursor within field
|
|
133
|
+
if (this.isLeft(char)) {
|
|
134
|
+
if (this.cursor > 0)
|
|
135
|
+
this.cursor--;
|
|
136
|
+
this.render(false);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (this.isRight(char)) {
|
|
140
|
+
if (this.cursor < val.length)
|
|
141
|
+
this.cursor++;
|
|
142
|
+
this.render(false);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Typing
|
|
146
|
+
if (!/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
|
|
147
|
+
const pre = val.slice(0, this.cursor);
|
|
148
|
+
const post = val.slice(this.cursor);
|
|
149
|
+
this.values[activeField.name] = pre + char + post;
|
|
150
|
+
this.cursor += char.length;
|
|
151
|
+
this.fieldErrors[activeField.name] = ''; // Clear error on type
|
|
152
|
+
this.render(false);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
handleMouse(event) {
|
|
156
|
+
if (event.action === 'scroll') {
|
|
157
|
+
if (event.scroll === 'up') {
|
|
158
|
+
this.validateCurrentField();
|
|
159
|
+
this.moveFocus(-1);
|
|
160
|
+
}
|
|
161
|
+
else if (event.scroll === 'down') {
|
|
162
|
+
this.validateCurrentField();
|
|
163
|
+
this.moveFocus(1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
moveFocus(direction) {
|
|
168
|
+
const nextIndex = this.activeIndex + direction;
|
|
169
|
+
if (nextIndex >= 0 && nextIndex < this.options.fields.length) {
|
|
170
|
+
this.activeIndex = nextIndex;
|
|
171
|
+
// "cursor must jump to end of data"
|
|
172
|
+
this.cursor = this.values[this.options.fields[this.activeIndex].name].length;
|
|
173
|
+
this.render(false);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
async validateCurrentField() {
|
|
177
|
+
const field = this.options.fields[this.activeIndex];
|
|
178
|
+
const val = this.values[field.name];
|
|
179
|
+
if (field.validate) {
|
|
180
|
+
try {
|
|
181
|
+
const res = await field.validate(val);
|
|
182
|
+
if (res !== true) {
|
|
183
|
+
this.fieldErrors[field.name] = typeof res === 'string' ? res : 'Invalid value';
|
|
184
|
+
this.render(false);
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
this.fieldErrors[field.name] = err.message || 'Validation failed';
|
|
190
|
+
this.render(false);
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Clear error if valid
|
|
195
|
+
delete this.fieldErrors[field.name];
|
|
196
|
+
this.render(false);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
async submitForm() {
|
|
200
|
+
this.globalError = '';
|
|
201
|
+
// Validate all fields
|
|
202
|
+
let allValid = true;
|
|
203
|
+
for (const field of this.options.fields) {
|
|
204
|
+
const val = this.values[field.name];
|
|
205
|
+
if (field.validate) {
|
|
206
|
+
const res = await field.validate(val);
|
|
207
|
+
if (res !== true) {
|
|
208
|
+
this.fieldErrors[field.name] = typeof res === 'string' ? res : 'Invalid value';
|
|
209
|
+
allValid = false;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
delete this.fieldErrors[field.name];
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (allValid) {
|
|
217
|
+
this.submit(this.values);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
this.globalError = 'Please fix errors before submitting.';
|
|
221
|
+
this.render(false);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
exports.FormPrompt = FormPrompt;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Prompt } from '../base';
|
|
2
|
+
import { GridOptions, MouseEvent } from '../types';
|
|
3
|
+
export declare class GridPrompt extends Prompt<boolean[][], GridOptions> {
|
|
4
|
+
private cursorRow;
|
|
5
|
+
private cursorCol;
|
|
6
|
+
private selected;
|
|
7
|
+
private columnWidths;
|
|
8
|
+
private rowLabelWidth;
|
|
9
|
+
constructor(options: GridOptions);
|
|
10
|
+
private calculateLayout;
|
|
11
|
+
protected render(_firstRender: boolean): void;
|
|
12
|
+
protected handleInput(char: string, _key: Buffer): void;
|
|
13
|
+
protected handleMouse(event: MouseEvent): void;
|
|
14
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GridPrompt = void 0;
|
|
4
|
+
const ansi_1 = require("../ansi");
|
|
5
|
+
const base_1 = require("../base");
|
|
6
|
+
const theme_1 = require("../theme");
|
|
7
|
+
const utils_1 = require("../utils");
|
|
8
|
+
class GridPrompt extends base_1.Prompt {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
super(options);
|
|
11
|
+
this.cursorRow = 0;
|
|
12
|
+
this.cursorCol = 0;
|
|
13
|
+
this.columnWidths = [];
|
|
14
|
+
this.rowLabelWidth = 0;
|
|
15
|
+
// Initialize selection matrix
|
|
16
|
+
if (options.initial) {
|
|
17
|
+
this.selected = options.initial.map(row => [...row]);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
this.selected = options.rows.map(() => new Array(options.columns.length).fill(false));
|
|
21
|
+
}
|
|
22
|
+
this.calculateLayout();
|
|
23
|
+
}
|
|
24
|
+
calculateLayout() {
|
|
25
|
+
// Calculate max width for row labels
|
|
26
|
+
this.rowLabelWidth = 0;
|
|
27
|
+
for (const row of this.options.rows) {
|
|
28
|
+
this.rowLabelWidth = Math.max(this.rowLabelWidth, (0, utils_1.stringWidth)(row));
|
|
29
|
+
}
|
|
30
|
+
// Calculate width for each column
|
|
31
|
+
this.columnWidths = this.options.columns.map(col => {
|
|
32
|
+
// Header width vs Cell width (3 chars for "[ ]")
|
|
33
|
+
return Math.max((0, utils_1.stringWidth)(col), 3);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
render(_firstRender) {
|
|
37
|
+
const { rows, columns } = this.options;
|
|
38
|
+
let output = `${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
|
|
39
|
+
// Render Header
|
|
40
|
+
// Padding for the row label column
|
|
41
|
+
output += ' '.repeat(this.rowLabelWidth + 2); // +2 for spacing/border
|
|
42
|
+
output += columns.map((col, i) => {
|
|
43
|
+
const width = this.columnWidths[i];
|
|
44
|
+
const padding = width - (0, utils_1.stringWidth)(col);
|
|
45
|
+
const leftPad = Math.floor(padding / 2);
|
|
46
|
+
const rightPad = padding - leftPad;
|
|
47
|
+
return `${theme_1.theme.muted}${' '.repeat(leftPad)}${col}${' '.repeat(rightPad)}${ansi_1.ANSI.RESET}`;
|
|
48
|
+
}).join(' ') + '\n';
|
|
49
|
+
// Render Rows
|
|
50
|
+
for (let r = 0; r < rows.length; r++) {
|
|
51
|
+
const rowLabel = rows[r];
|
|
52
|
+
const labelPadding = this.rowLabelWidth - (0, utils_1.stringWidth)(rowLabel);
|
|
53
|
+
// Row Label
|
|
54
|
+
output += `${theme_1.theme.muted}${rowLabel}${' '.repeat(labelPadding)}${ansi_1.ANSI.RESET} `;
|
|
55
|
+
// Cells
|
|
56
|
+
const cells = this.selected[r].map((isChecked, c) => {
|
|
57
|
+
const isFocused = (r === this.cursorRow && c === this.cursorCol);
|
|
58
|
+
const width = this.columnWidths[c];
|
|
59
|
+
const content = isChecked ? `[${theme_1.theme.success}x${ansi_1.ANSI.RESET}]` : '[ ]';
|
|
60
|
+
// content string width is 3 (ignoring ansi)
|
|
61
|
+
const padding = width - 3;
|
|
62
|
+
const leftPad = Math.floor(padding / 2);
|
|
63
|
+
const rightPad = padding - leftPad;
|
|
64
|
+
let cellStr = `${' '.repeat(leftPad)}${content}${' '.repeat(rightPad)}`;
|
|
65
|
+
if (isFocused) {
|
|
66
|
+
// Highlight the focused cell
|
|
67
|
+
// If we use ANSI.REVERSE on the whole cell including padding:
|
|
68
|
+
cellStr = `${ansi_1.ANSI.REVERSE}${cellStr}${ansi_1.ANSI.RESET}`;
|
|
69
|
+
}
|
|
70
|
+
return cellStr;
|
|
71
|
+
});
|
|
72
|
+
output += cells.join(' ') + '\n';
|
|
73
|
+
}
|
|
74
|
+
output += `${theme_1.theme.muted}Arrows to move, Space to toggle, Enter to submit${ansi_1.ANSI.RESET}`;
|
|
75
|
+
this.renderFrame(output);
|
|
76
|
+
}
|
|
77
|
+
handleInput(char, _key) {
|
|
78
|
+
const isUp = this.isUp(char);
|
|
79
|
+
const isDown = this.isDown(char);
|
|
80
|
+
const isLeft = this.isLeft(char);
|
|
81
|
+
const isRight = this.isRight(char);
|
|
82
|
+
if (isUp || isDown || isLeft || isRight) {
|
|
83
|
+
if (isUp)
|
|
84
|
+
this.cursorRow = Math.max(0, this.cursorRow - 1);
|
|
85
|
+
if (isDown)
|
|
86
|
+
this.cursorRow = Math.min(this.options.rows.length - 1, this.cursorRow + 1);
|
|
87
|
+
if (isLeft)
|
|
88
|
+
this.cursorCol = Math.max(0, this.cursorCol - 1);
|
|
89
|
+
if (isRight)
|
|
90
|
+
this.cursorCol = Math.min(this.options.columns.length - 1, this.cursorCol + 1);
|
|
91
|
+
this.render(false);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (char === ' ') {
|
|
95
|
+
this.selected[this.cursorRow][this.cursorCol] = !this.selected[this.cursorRow][this.cursorCol];
|
|
96
|
+
this.render(false);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (char === '\r' || char === '\n') {
|
|
100
|
+
this.submit(this.selected);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// PageUp (First Row)
|
|
104
|
+
if (char === '\x1b[5~') {
|
|
105
|
+
this.cursorRow = 0;
|
|
106
|
+
this.render(false);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// PageDown (Last Row)
|
|
110
|
+
if (char === '\x1b[6~') {
|
|
111
|
+
this.cursorRow = this.options.rows.length - 1;
|
|
112
|
+
this.render(false);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Home (First Column)
|
|
116
|
+
if (char === '\x1b[H' || char === '\x1b[1~') {
|
|
117
|
+
this.cursorCol = 0;
|
|
118
|
+
this.render(false);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
// End (Last Column)
|
|
122
|
+
if (char === '\x1b[F' || char === '\x1b[4~') {
|
|
123
|
+
this.cursorCol = this.options.columns.length - 1;
|
|
124
|
+
this.render(false);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// r: Toggle Row
|
|
128
|
+
if (char === 'r') {
|
|
129
|
+
const row = this.selected[this.cursorRow];
|
|
130
|
+
const allSelected = row.every(c => c);
|
|
131
|
+
this.selected[this.cursorRow] = row.map(() => !allSelected);
|
|
132
|
+
this.render(false);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// c: Toggle Column
|
|
136
|
+
if (char === 'c') {
|
|
137
|
+
const colIdx = this.cursorCol;
|
|
138
|
+
const colValues = this.selected.map(row => row[colIdx]);
|
|
139
|
+
const allSelected = colValues.every(c => c);
|
|
140
|
+
this.selected.forEach(row => row[colIdx] = !allSelected);
|
|
141
|
+
this.render(false);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// a: Select All
|
|
145
|
+
if (char === 'a') {
|
|
146
|
+
this.selected = this.selected.map(row => row.map(() => true));
|
|
147
|
+
this.render(false);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// x: Deselect All
|
|
151
|
+
if (char === 'x') {
|
|
152
|
+
this.selected = this.selected.map(row => row.map(() => false));
|
|
153
|
+
this.render(false);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
// i: Invert
|
|
157
|
+
if (char === 'i') {
|
|
158
|
+
this.selected = this.selected.map(row => row.map(v => !v));
|
|
159
|
+
this.render(false);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
handleMouse(event) {
|
|
164
|
+
if (event.action === 'scroll') {
|
|
165
|
+
const direction = event.scroll === 'up' ? -1 : 1;
|
|
166
|
+
if (event.shift) {
|
|
167
|
+
// Horizontal (Column)
|
|
168
|
+
this.cursorCol = Math.max(0, Math.min(this.options.columns.length - 1, this.cursorCol + direction));
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Vertical (Row)
|
|
172
|
+
this.cursorRow = Math.max(0, Math.min(this.options.rows.length - 1, this.cursorRow + direction));
|
|
173
|
+
}
|
|
174
|
+
this.render(false);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
exports.GridPrompt = GridPrompt;
|
|
@@ -2,6 +2,6 @@ import { Prompt } from '../base';
|
|
|
2
2
|
import { KeypressOptions } from '../types';
|
|
3
3
|
export declare class KeypressPrompt extends Prompt<string, KeypressOptions> {
|
|
4
4
|
constructor(options: KeypressOptions);
|
|
5
|
-
protected render(
|
|
6
|
-
protected handleInput(char: string,
|
|
5
|
+
protected render(_firstRender: boolean): void;
|
|
6
|
+
protected handleInput(char: string, _key: Buffer): void;
|
|
7
7
|
}
|
package/dist/prompts/keypress.js
CHANGED
|
@@ -8,7 +8,7 @@ class KeypressPrompt extends base_1.Prompt {
|
|
|
8
8
|
constructor(options) {
|
|
9
9
|
super(options);
|
|
10
10
|
}
|
|
11
|
-
render(
|
|
11
|
+
render(_firstRender) {
|
|
12
12
|
let output = `${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}`;
|
|
13
13
|
if (this.options.keys) {
|
|
14
14
|
const hint = this.options.keys.map(k => {
|
|
@@ -27,7 +27,7 @@ class KeypressPrompt extends base_1.Prompt {
|
|
|
27
27
|
}
|
|
28
28
|
this.renderFrame(output);
|
|
29
29
|
}
|
|
30
|
-
handleInput(char,
|
|
30
|
+
handleInput(char, _key) {
|
|
31
31
|
let keyName = char;
|
|
32
32
|
if (char === '\r' || char === '\n')
|
|
33
33
|
keyName = 'enter';
|
package/dist/prompts/list.d.ts
CHANGED
|
@@ -4,6 +4,6 @@ export declare class ListPrompt extends Prompt<string[], ListOptions> {
|
|
|
4
4
|
private currentInput;
|
|
5
5
|
private errorMsg;
|
|
6
6
|
constructor(options: ListOptions);
|
|
7
|
-
protected render(
|
|
7
|
+
protected render(_firstRender: boolean): void;
|
|
8
8
|
protected handleInput(char: string): void;
|
|
9
9
|
}
|
package/dist/prompts/list.js
CHANGED
|
@@ -13,38 +13,57 @@ class ListPrompt extends base_1.Prompt {
|
|
|
13
13
|
this.errorMsg = '';
|
|
14
14
|
this.value = options.initial || [];
|
|
15
15
|
}
|
|
16
|
-
render(
|
|
17
|
-
|
|
16
|
+
render(_firstRender) {
|
|
17
|
+
const cols = process.stdout.columns || 80;
|
|
18
|
+
// 1. Prepare Prefix
|
|
18
19
|
const icon = this.errorMsg ? `${theme_1.theme.error}${symbols_1.symbols.cross}` : `${theme_1.theme.success}?`;
|
|
19
|
-
|
|
20
|
+
const prefix = `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `;
|
|
21
|
+
// 2. Build Lines with Wrapping
|
|
22
|
+
const lines = [];
|
|
23
|
+
let currentLine = prefix;
|
|
24
|
+
// Helper to check width
|
|
25
|
+
const addText = (text) => {
|
|
26
|
+
const visualLen = this.stripAnsi(currentLine).length + this.stripAnsi(text).length;
|
|
27
|
+
if (visualLen > cols) {
|
|
28
|
+
lines.push(currentLine);
|
|
29
|
+
currentLine = text.trimStart(); // Start new line
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
currentLine += text;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
20
35
|
// Render Tags
|
|
21
36
|
if (this.value.length > 0) {
|
|
22
37
|
this.value.forEach((tag) => {
|
|
23
|
-
|
|
38
|
+
const tagStr = `${theme_1.theme.main}[${tag}]${ansi_1.ANSI.RESET} `;
|
|
39
|
+
addText(tagStr);
|
|
24
40
|
});
|
|
25
41
|
}
|
|
26
42
|
// Render Current Input
|
|
27
|
-
|
|
28
|
-
|
|
43
|
+
addText(this.currentInput);
|
|
44
|
+
lines.push(currentLine); // Push the last line
|
|
45
|
+
// Track where the input ends (for cursor positioning)
|
|
46
|
+
const inputLineIndex = lines.length - 1;
|
|
47
|
+
const inputVisualCol = this.stripAnsi(currentLine).length;
|
|
48
|
+
// 3. Append Error if any
|
|
29
49
|
if (this.errorMsg) {
|
|
30
|
-
|
|
50
|
+
lines.push(`${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
|
|
31
51
|
}
|
|
32
|
-
|
|
52
|
+
const output = lines.join('\n');
|
|
53
|
+
// 4. Render Frame
|
|
33
54
|
this.renderFrame(output);
|
|
34
55
|
this.print(ansi_1.ANSI.SHOW_CURSOR);
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.print(`\x1b[${visualLength}C`);
|
|
47
|
-
}
|
|
56
|
+
// 5. Position Cursor
|
|
57
|
+
// If we printed lines after the input line (e.g. error msg), move up.
|
|
58
|
+
const totalRows = lines.length;
|
|
59
|
+
const linesUp = (totalRows - 1) - inputLineIndex;
|
|
60
|
+
if (linesUp > 0) {
|
|
61
|
+
this.print(`\x1b[${linesUp}A`);
|
|
62
|
+
}
|
|
63
|
+
// Move to correct column
|
|
64
|
+
this.print(ansi_1.ANSI.CURSOR_LEFT);
|
|
65
|
+
if (inputVisualCol > 0) {
|
|
66
|
+
this.print(`\x1b[${inputVisualCol}C`);
|
|
48
67
|
}
|
|
49
68
|
}
|
|
50
69
|
handleInput(char) {
|
|
@@ -75,7 +94,8 @@ class ListPrompt extends base_1.Prompt {
|
|
|
75
94
|
this.render(false);
|
|
76
95
|
}
|
|
77
96
|
else if (this.value.length > 0) {
|
|
78
|
-
this.value.pop();
|
|
97
|
+
const last = this.value.pop();
|
|
98
|
+
this.currentInput = last || '';
|
|
79
99
|
this.render(false);
|
|
80
100
|
}
|
|
81
101
|
return;
|
|
@@ -9,7 +9,7 @@ export declare class MultiSelectPrompt<V> extends Prompt<any[], MultiSelectOptio
|
|
|
9
9
|
private errorMsg;
|
|
10
10
|
constructor(options: MultiSelectOptions<V>);
|
|
11
11
|
private getFilteredChoices;
|
|
12
|
-
protected render(
|
|
12
|
+
protected render(_firstRender: boolean): void;
|
|
13
13
|
protected handleInput(char: string): void;
|
|
14
14
|
protected handleMouse(event: MouseEvent): void;
|
|
15
15
|
}
|
|
@@ -24,7 +24,7 @@ class MultiSelectPrompt extends base_1.Prompt {
|
|
|
24
24
|
.map((c, i) => ({ ...c, originalIndex: i }))
|
|
25
25
|
.filter(c => c.title.toLowerCase().includes(this.searchBuffer.toLowerCase()));
|
|
26
26
|
}
|
|
27
|
-
render(
|
|
27
|
+
render(_firstRender) {
|
|
28
28
|
let output = '';
|
|
29
29
|
const choices = this.getFilteredChoices();
|
|
30
30
|
// Adjust Scroll
|
|
@@ -41,7 +41,7 @@ class MultiSelectPrompt extends base_1.Prompt {
|
|
|
41
41
|
const searchStr = this.searchBuffer ? ` ${theme_1.theme.muted}(Filter: ${this.searchBuffer})${ansi_1.ANSI.RESET}` : '';
|
|
42
42
|
output += `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}${searchStr}\n`;
|
|
43
43
|
if (choices.length === 0) {
|
|
44
|
-
output += ` ${theme_1.theme.muted}No results found${ansi_1.ANSI.RESET}`;
|
|
44
|
+
output += ` ${theme_1.theme.muted}No results found${ansi_1.ANSI.RESET}`;
|
|
45
45
|
}
|
|
46
46
|
else {
|
|
47
47
|
const visible = choices.slice(this.scrollTop, this.scrollTop + this.pageSize);
|
|
@@ -57,6 +57,10 @@ class MultiSelectPrompt extends base_1.Prompt {
|
|
|
57
57
|
output += `${cursor} ${checkbox} ${choice.title}`;
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
|
+
// Hints
|
|
61
|
+
if (!this.searchBuffer && !this.errorMsg) {
|
|
62
|
+
output += `\n${ansi_1.ANSI.DIM}(Ctrl+A: All, Ctrl+X: None)${ansi_1.ANSI.RESET}`;
|
|
63
|
+
}
|
|
60
64
|
if (this.errorMsg) {
|
|
61
65
|
output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
|
|
62
66
|
}
|
|
@@ -64,6 +68,7 @@ class MultiSelectPrompt extends base_1.Prompt {
|
|
|
64
68
|
}
|
|
65
69
|
handleInput(char) {
|
|
66
70
|
const choices = this.getFilteredChoices();
|
|
71
|
+
// Enter
|
|
67
72
|
if (char === '\r' || char === '\n') {
|
|
68
73
|
const selectedCount = this.checkedState.filter(Boolean).length;
|
|
69
74
|
const { min = 0, max } = this.options;
|
|
@@ -83,6 +88,35 @@ class MultiSelectPrompt extends base_1.Prompt {
|
|
|
83
88
|
this.submit(results);
|
|
84
89
|
return;
|
|
85
90
|
}
|
|
91
|
+
// --- Batch Shortcuts (Visible Items Only) ---
|
|
92
|
+
// Ctrl+A (\x01): Select All Visible
|
|
93
|
+
if (char === '\x01') {
|
|
94
|
+
const newCheckedState = [...this.checkedState];
|
|
95
|
+
let potentialAdd = 0;
|
|
96
|
+
// Calculate potential selections
|
|
97
|
+
choices.forEach(c => {
|
|
98
|
+
if (!newCheckedState[c.originalIndex])
|
|
99
|
+
potentialAdd++;
|
|
100
|
+
});
|
|
101
|
+
const currentSelected = newCheckedState.filter(Boolean).length;
|
|
102
|
+
if (this.options.max && (currentSelected + potentialAdd) > this.options.max) {
|
|
103
|
+
this.errorMsg = `Max limit ${this.options.max} reached`;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
choices.forEach(c => newCheckedState[c.originalIndex] = true);
|
|
107
|
+
this.checkedState = newCheckedState;
|
|
108
|
+
this.errorMsg = '';
|
|
109
|
+
}
|
|
110
|
+
this.render(false);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Ctrl+X (\x18): Deselect All Visible
|
|
114
|
+
if (char === '\x18') {
|
|
115
|
+
choices.forEach(c => this.checkedState[c.originalIndex] = false);
|
|
116
|
+
this.errorMsg = '';
|
|
117
|
+
this.render(false);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
86
120
|
if (char === ' ') {
|
|
87
121
|
if (choices.length > 0) {
|
|
88
122
|
const choice = choices[this.selectedIndex];
|
|
@@ -92,14 +126,14 @@ class MultiSelectPrompt extends base_1.Prompt {
|
|
|
92
126
|
}
|
|
93
127
|
return;
|
|
94
128
|
}
|
|
95
|
-
if (this.isUp(char)) {
|
|
129
|
+
if (this.isUp(char)) {
|
|
96
130
|
if (choices.length > 0) {
|
|
97
131
|
this.selectedIndex = (this.selectedIndex - 1 + choices.length) % choices.length;
|
|
98
132
|
this.render(false);
|
|
99
133
|
}
|
|
100
134
|
return;
|
|
101
135
|
}
|
|
102
|
-
if (this.isDown(char)) {
|
|
136
|
+
if (this.isDown(char)) {
|
|
103
137
|
if (choices.length > 0) {
|
|
104
138
|
this.selectedIndex = (this.selectedIndex + 1) % choices.length;
|
|
105
139
|
this.render(false);
|
|
@@ -114,6 +148,7 @@ class MultiSelectPrompt extends base_1.Prompt {
|
|
|
114
148
|
}
|
|
115
149
|
return;
|
|
116
150
|
}
|
|
151
|
+
// Typing search
|
|
117
152
|
if (!/^[\x00-\x1F]/.test(char) && !char.startsWith('\x1b')) {
|
|
118
153
|
this.searchBuffer += char;
|
|
119
154
|
this.selectedIndex = 0;
|
package/dist/prompts/number.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare class NumberPrompt extends Prompt<number, NumberOptions> {
|
|
|
5
5
|
private cursor;
|
|
6
6
|
private errorMsg;
|
|
7
7
|
constructor(options: NumberOptions);
|
|
8
|
-
protected render(
|
|
8
|
+
protected render(_firstRender: boolean): void;
|
|
9
9
|
protected handleInput(char: string): void;
|
|
10
10
|
protected handleMouse(event: MouseEvent): void;
|
|
11
11
|
}
|