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.
Files changed (75) hide show
  1. package/README.md +173 -6
  2. package/dist/ansi.d.ts +1 -0
  3. package/dist/ansi.js +1 -0
  4. package/dist/base.d.ts +1 -1
  5. package/dist/base.js +1 -10
  6. package/dist/core.d.ts +23 -1
  7. package/dist/core.js +60 -0
  8. package/dist/highlight.d.ts +1 -0
  9. package/dist/highlight.js +40 -0
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.js +1 -1
  12. package/dist/input.js +26 -14
  13. package/dist/prompts/autocomplete.d.ts +1 -1
  14. package/dist/prompts/autocomplete.js +2 -7
  15. package/dist/prompts/calendar.d.ts +20 -0
  16. package/dist/prompts/calendar.js +329 -0
  17. package/dist/prompts/checkbox.d.ts +1 -1
  18. package/dist/prompts/checkbox.js +38 -8
  19. package/dist/prompts/code.d.ts +17 -0
  20. package/dist/prompts/code.js +210 -0
  21. package/dist/prompts/color.d.ts +14 -0
  22. package/dist/prompts/color.js +147 -0
  23. package/dist/prompts/confirm.d.ts +1 -1
  24. package/dist/prompts/confirm.js +1 -1
  25. package/dist/prompts/cron.d.ts +13 -0
  26. package/dist/prompts/cron.js +176 -0
  27. package/dist/prompts/date.d.ts +1 -1
  28. package/dist/prompts/date.js +15 -5
  29. package/dist/prompts/editor.js +2 -2
  30. package/dist/prompts/file.d.ts +7 -0
  31. package/dist/prompts/file.js +56 -60
  32. package/dist/prompts/form.d.ts +17 -0
  33. package/dist/prompts/form.js +225 -0
  34. package/dist/prompts/grid.d.ts +14 -0
  35. package/dist/prompts/grid.js +178 -0
  36. package/dist/prompts/keypress.d.ts +2 -2
  37. package/dist/prompts/keypress.js +2 -2
  38. package/dist/prompts/list.d.ts +1 -1
  39. package/dist/prompts/list.js +42 -22
  40. package/dist/prompts/multi-select.d.ts +1 -1
  41. package/dist/prompts/multi-select.js +39 -4
  42. package/dist/prompts/number.d.ts +1 -1
  43. package/dist/prompts/number.js +2 -2
  44. package/dist/prompts/range.d.ts +9 -0
  45. package/dist/prompts/range.js +140 -0
  46. package/dist/prompts/rating.d.ts +1 -1
  47. package/dist/prompts/rating.js +1 -1
  48. package/dist/prompts/select.d.ts +1 -1
  49. package/dist/prompts/select.js +1 -1
  50. package/dist/prompts/slider.d.ts +1 -1
  51. package/dist/prompts/slider.js +1 -1
  52. package/dist/prompts/snippet.d.ts +18 -0
  53. package/dist/prompts/snippet.js +203 -0
  54. package/dist/prompts/sort.d.ts +1 -1
  55. package/dist/prompts/sort.js +1 -4
  56. package/dist/prompts/spam.d.ts +17 -0
  57. package/dist/prompts/spam.js +62 -0
  58. package/dist/prompts/table.d.ts +1 -1
  59. package/dist/prompts/table.js +1 -1
  60. package/dist/prompts/text.d.ts +1 -0
  61. package/dist/prompts/text.js +13 -31
  62. package/dist/prompts/toggle.d.ts +1 -1
  63. package/dist/prompts/toggle.js +1 -1
  64. package/dist/prompts/transfer.d.ts +18 -0
  65. package/dist/prompts/transfer.js +203 -0
  66. package/dist/prompts/tree-select.d.ts +32 -0
  67. package/dist/prompts/tree-select.js +277 -0
  68. package/dist/prompts/tree.d.ts +3 -3
  69. package/dist/prompts/tree.js +27 -19
  70. package/dist/prompts/wait.d.ts +18 -0
  71. package/dist/prompts/wait.js +62 -0
  72. package/dist/types.d.ts +84 -0
  73. package/dist/utils.js +1 -1
  74. package/example.ts +150 -15
  75. 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(firstRender: boolean): void;
6
- protected handleInput(char: string, key: Buffer): void;
5
+ protected render(_firstRender: boolean): void;
6
+ protected handleInput(char: string, _key: Buffer): void;
7
7
  }
@@ -8,7 +8,7 @@ class KeypressPrompt extends base_1.Prompt {
8
8
  constructor(options) {
9
9
  super(options);
10
10
  }
11
- render(firstRender) {
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, key) {
30
+ handleInput(char, _key) {
31
31
  let keyName = char;
32
32
  if (char === '\r' || char === '\n')
33
33
  keyName = 'enter';
@@ -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(firstRender: boolean): void;
7
+ protected render(_firstRender: boolean): void;
8
8
  protected handleInput(char: string): void;
9
9
  }
@@ -13,38 +13,57 @@ class ListPrompt extends base_1.Prompt {
13
13
  this.errorMsg = '';
14
14
  this.value = options.initial || [];
15
15
  }
16
- render(firstRender) {
17
- // Prepare content
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
- let mainLine = `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} `;
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
- mainLine += `${theme_1.theme.main}[${tag}]${ansi_1.ANSI.RESET} `;
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
- mainLine += `${this.currentInput}`;
28
- let output = mainLine;
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
- output += `\n${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`;
50
+ lines.push(`${theme_1.theme.error}>> ${this.errorMsg}${ansi_1.ANSI.RESET}`);
31
51
  }
32
- // Use Double Buffering
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
- // If we printed an error, the cursor is at the end of the error line.
36
- // We need to move it back to the end of the input line.
37
- if (this.errorMsg) {
38
- // Move up one line (since error is always on the next line in this simple implementation)
39
- this.print(ansi_1.ANSI.UP);
40
- // Move to the correct column.
41
- // We need to calculate visual length of mainLine to place cursor correctly.
42
- // stripAnsi is available in base class now.
43
- const visualLength = this.stripAnsi(mainLine).length;
44
- this.print(ansi_1.ANSI.CURSOR_LEFT); // Go to start
45
- if (visualLength > 0) {
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(firstRender: boolean): void;
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(firstRender) {
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}`; // No newline at end
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)) { // Up
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)) { // Down
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;
@@ -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(firstRender: boolean): void;
8
+ protected render(_firstRender: boolean): void;
9
9
  protected handleInput(char: string): void;
10
10
  protected handleMouse(event: MouseEvent): void;
11
11
  }