mepcli 0.5.0 → 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 (76) hide show
  1. package/README.md +182 -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 +26 -1
  7. package/dist/core.js +72 -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.d.ts +14 -0
  30. package/dist/prompts/editor.js +207 -0
  31. package/dist/prompts/file.d.ts +7 -0
  32. package/dist/prompts/file.js +56 -60
  33. package/dist/prompts/form.d.ts +17 -0
  34. package/dist/prompts/form.js +225 -0
  35. package/dist/prompts/grid.d.ts +14 -0
  36. package/dist/prompts/grid.js +178 -0
  37. package/dist/prompts/keypress.d.ts +7 -0
  38. package/dist/prompts/keypress.js +57 -0
  39. package/dist/prompts/list.d.ts +1 -1
  40. package/dist/prompts/list.js +42 -22
  41. package/dist/prompts/multi-select.d.ts +1 -1
  42. package/dist/prompts/multi-select.js +39 -4
  43. package/dist/prompts/number.d.ts +1 -1
  44. package/dist/prompts/number.js +2 -2
  45. package/dist/prompts/range.d.ts +9 -0
  46. package/dist/prompts/range.js +140 -0
  47. package/dist/prompts/rating.d.ts +1 -1
  48. package/dist/prompts/rating.js +1 -1
  49. package/dist/prompts/select.d.ts +1 -1
  50. package/dist/prompts/select.js +1 -1
  51. package/dist/prompts/slider.d.ts +1 -1
  52. package/dist/prompts/slider.js +1 -1
  53. package/dist/prompts/snippet.d.ts +18 -0
  54. package/dist/prompts/snippet.js +203 -0
  55. package/dist/prompts/sort.d.ts +1 -1
  56. package/dist/prompts/sort.js +1 -4
  57. package/dist/prompts/spam.d.ts +17 -0
  58. package/dist/prompts/spam.js +62 -0
  59. package/dist/prompts/table.d.ts +1 -1
  60. package/dist/prompts/table.js +1 -1
  61. package/dist/prompts/text.d.ts +1 -0
  62. package/dist/prompts/text.js +14 -32
  63. package/dist/prompts/toggle.d.ts +1 -1
  64. package/dist/prompts/toggle.js +1 -1
  65. package/dist/prompts/transfer.d.ts +18 -0
  66. package/dist/prompts/transfer.js +203 -0
  67. package/dist/prompts/tree-select.d.ts +32 -0
  68. package/dist/prompts/tree-select.js +277 -0
  69. package/dist/prompts/tree.d.ts +20 -0
  70. package/dist/prompts/tree.js +231 -0
  71. package/dist/prompts/wait.d.ts +18 -0
  72. package/dist/prompts/wait.js +62 -0
  73. package/dist/types.d.ts +105 -0
  74. package/dist/utils.js +6 -2
  75. package/example.ts +213 -27
  76. package/package.json +14 -4
@@ -40,7 +40,9 @@ const theme_1 = require("../theme");
40
40
  const symbols_1 = require("../symbols");
41
41
  const fs = __importStar(require("fs"));
42
42
  const path = __importStar(require("path"));
43
- // --- Implementation: File Prompt ---
43
+ /**
44
+ * Implementation of FilePrompt with autocomplete.
45
+ */
44
46
  class FilePrompt extends base_1.Prompt {
45
47
  constructor(options) {
46
48
  super(options);
@@ -49,22 +51,29 @@ class FilePrompt extends base_1.Prompt {
49
51
  this.suggestions = [];
50
52
  this.selectedSuggestion = -1;
51
53
  this.errorMsg = '';
54
+ this.lastLinesUp = 0;
52
55
  this.input = options.basePath || '';
53
56
  this.cursor = this.input.length;
57
+ this.updateSuggestions();
54
58
  }
59
+ /**
60
+ * Updates the suggestions list based on the current input path.
61
+ */
55
62
  updateSuggestions() {
56
63
  try {
57
- const dir = path.dirname(this.input) || '.';
58
- const partial = path.basename(this.input);
64
+ // Determine the directory to scan and the partial file name
65
+ const isDirQuery = this.input.endsWith('/') || this.input.endsWith('\\');
66
+ const dir = isDirQuery ? this.input : (path.dirname(this.input) || '.');
67
+ const partial = isDirQuery ? '' : path.basename(this.input);
59
68
  if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
60
69
  const files = fs.readdirSync(dir);
61
70
  this.suggestions = files
62
- .filter(f => f.startsWith(partial))
71
+ .filter(f => f.toLowerCase().startsWith(partial.toLowerCase()))
63
72
  .filter(f => {
64
73
  const fullPath = path.join(dir, f);
65
- // Handle errors if file doesn't exist or permission denied
66
74
  try {
67
- const isDir = fs.statSync(fullPath).isDirectory();
75
+ const stats = fs.statSync(fullPath);
76
+ const isDir = stats.isDirectory();
68
77
  if (this.options.onlyDirectories && !isDir)
69
78
  return false;
70
79
  if (this.options.extensions && !isDir) {
@@ -72,17 +81,18 @@ class FilePrompt extends base_1.Prompt {
72
81
  }
73
82
  return true;
74
83
  }
75
- catch (e) {
84
+ catch (_e) {
76
85
  return false;
77
86
  }
78
87
  })
79
88
  .map(f => {
80
89
  const fullPath = path.join(dir, f);
81
90
  try {
91
+ // Append separator if the file is a directory
82
92
  if (fs.statSync(fullPath).isDirectory())
83
- return f + '/';
93
+ return f + path.sep;
84
94
  }
85
- catch (e) { /* ignore */ }
95
+ catch (_e) { /* ignore */ }
86
96
  return f;
87
97
  });
88
98
  }
@@ -90,18 +100,21 @@ class FilePrompt extends base_1.Prompt {
90
100
  this.suggestions = [];
91
101
  }
92
102
  }
93
- catch (e) {
103
+ catch (_e) {
94
104
  this.suggestions = [];
95
105
  }
96
106
  this.selectedSuggestion = -1;
97
107
  }
98
108
  render(firstRender) {
99
- // Construct string
109
+ // Restore cursor position to the bottom before renderFrame clears the area
110
+ if (!firstRender && this.lastLinesUp > 0) {
111
+ this.print(`\x1b[${this.lastLinesUp}B`);
112
+ this.lastLinesUp = 0;
113
+ }
100
114
  const icon = this.errorMsg ? `${theme_1.theme.error}${symbols_1.symbols.cross}` : `${theme_1.theme.success}?`;
101
115
  let output = `${icon} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${this.input}`;
102
- // Suggestions
103
116
  if (this.suggestions.length > 0) {
104
- output += '\n'; // Separate input from suggestions
117
+ output += '\n';
105
118
  const maxShow = 5;
106
119
  const displayed = this.suggestions.slice(0, maxShow);
107
120
  displayed.forEach((s, i) => {
@@ -115,86 +128,69 @@ class FilePrompt extends base_1.Prompt {
115
128
  }
116
129
  });
117
130
  if (this.suggestions.length > maxShow) {
118
- output += `\n ...and ${this.suggestions.length - maxShow} more`;
131
+ output += `\n ${theme_1.theme.muted}...and ${this.suggestions.length - maxShow} more${ansi_1.ANSI.RESET}`;
119
132
  }
120
133
  }
121
134
  this.renderFrame(output);
122
- this.print(ansi_1.ANSI.SHOW_CURSOR);
123
- // Restore Cursor Logic
124
- // We need to move up to the input line if we printed suggestions.
125
- // The input line is always the first line (index 0).
126
- // So we move up by (totalLines - 1).
127
- const totalLines = this.lastRenderHeight; // renderFrame sets this
135
+ // Move cursor back up to the input line
136
+ const totalLines = this.lastRenderHeight;
128
137
  if (totalLines > 1) {
129
- this.print(`\x1b[${totalLines - 1}A`);
138
+ this.lastLinesUp = totalLines - 1;
139
+ this.print(`\x1b[${this.lastLinesUp}A`);
130
140
  }
131
- // Move right
141
+ // Calculate horizontal cursor position on the input line
132
142
  const prefix = `${icon} ${theme_1.theme.title}${this.options.message} `;
133
143
  const prefixLen = this.stripAnsi(prefix).length;
134
144
  const targetCol = prefixLen + this.input.length;
135
145
  this.print(ansi_1.ANSI.CURSOR_LEFT);
136
146
  if (targetCol > 0)
137
147
  this.print(`\x1b[${targetCol}C`);
148
+ this.print(ansi_1.ANSI.SHOW_CURSOR);
138
149
  }
139
150
  handleInput(char) {
140
- if (char === '\t') { // Tab
141
- if (this.suggestions.length === 1) {
142
- const dir = path.dirname(this.input);
143
- this.input = path.join(dir === '.' ? '' : dir, this.suggestions[0]);
151
+ if (char === '\t') {
152
+ if (this.suggestions.length > 0) {
153
+ // Use the selected suggestion or the first one available
154
+ const idx = this.selectedSuggestion === -1 ? 0 : this.selectedSuggestion;
155
+ const suggestion = this.suggestions[idx];
156
+ const isDir = this.input.endsWith('/') || this.input.endsWith('\\');
157
+ const dir = isDir ? this.input : path.dirname(this.input);
158
+ // Construct the new path accurately
159
+ const baseDir = (dir === '.' && !this.input.startsWith('.')) ? '' : dir;
160
+ this.input = path.join(baseDir, suggestion);
144
161
  this.cursor = this.input.length;
145
- this.suggestions = [];
146
- this.render(false);
147
- }
148
- else if (this.suggestions.length > 1) {
149
- // Cycle or show? For now cycle if selected
150
- if (this.selectedSuggestion !== -1) {
151
- const dir = path.dirname(this.input);
152
- this.input = path.join(dir === '.' ? '' : dir, this.suggestions[this.selectedSuggestion]);
153
- this.cursor = this.input.length;
154
- this.suggestions = [];
155
- this.render(false);
156
- }
157
- else {
158
- // Just show suggestions (already done in render loop usually, but update logic ensures it)
159
- this.updateSuggestions();
160
- this.render(false);
161
- }
162
- }
163
- else {
162
+ // Immediately refresh suggestions for the new path
164
163
  this.updateSuggestions();
165
164
  this.render(false);
166
165
  }
167
166
  return;
168
167
  }
169
168
  if (char === '\r' || char === '\n') {
170
- if (this.selectedSuggestion !== -1) {
171
- const dir = path.dirname(this.input);
172
- this.input = path.join(dir === '.' ? '' : dir, this.suggestions[this.selectedSuggestion]);
173
- this.cursor = this.input.length;
174
- this.suggestions = [];
175
- this.selectedSuggestion = -1;
176
- this.render(false);
177
- }
178
- else {
179
- this.submit(this.input);
169
+ // Move cursor to the bottom to ensure clean exit UI
170
+ if (this.lastLinesUp > 0) {
171
+ this.print(`\x1b[${this.lastLinesUp}B`);
172
+ this.lastLinesUp = 0;
180
173
  }
174
+ this.submit(this.input);
181
175
  return;
182
176
  }
183
- if (this.isDown(char)) { // Down
177
+ if (this.isDown(char)) {
184
178
  if (this.suggestions.length > 0) {
185
- this.selectedSuggestion = (this.selectedSuggestion + 1) % Math.min(this.suggestions.length, 5);
179
+ const count = Math.min(this.suggestions.length, 5);
180
+ this.selectedSuggestion = (this.selectedSuggestion + 1) % count;
186
181
  this.render(false);
187
182
  }
188
183
  return;
189
184
  }
190
- if (this.isUp(char)) { // Up
185
+ if (this.isUp(char)) {
191
186
  if (this.suggestions.length > 0) {
192
- this.selectedSuggestion = (this.selectedSuggestion - 1 + Math.min(this.suggestions.length, 5)) % Math.min(this.suggestions.length, 5);
187
+ const count = Math.min(this.suggestions.length, 5);
188
+ this.selectedSuggestion = (this.selectedSuggestion - 1 + count) % count;
193
189
  this.render(false);
194
190
  }
195
191
  return;
196
192
  }
197
- if (char === '\u0008' || char === '\x7f') { // Backspace
193
+ if (char === '\u0008' || char === '\x7f') {
198
194
  if (this.input.length > 0) {
199
195
  this.input = this.input.slice(0, -1);
200
196
  this.updateSuggestions();
@@ -0,0 +1,17 @@
1
+ import { Prompt } from '../base';
2
+ import { FormOptions, MouseEvent } from '../types';
3
+ export declare class FormPrompt extends Prompt<Record<string, string>, FormOptions> {
4
+ private values;
5
+ private activeIndex;
6
+ private fieldErrors;
7
+ private globalError;
8
+ private cursor;
9
+ private lastLinesUp;
10
+ constructor(options: FormOptions);
11
+ protected render(firstRender: boolean): void;
12
+ protected handleInput(char: string, key: Buffer): void;
13
+ protected handleMouse(event: MouseEvent): void;
14
+ private moveFocus;
15
+ private validateCurrentField;
16
+ private submitForm;
17
+ }
@@ -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;
@@ -0,0 +1,7 @@
1
+ import { Prompt } from '../base';
2
+ import { KeypressOptions } from '../types';
3
+ export declare class KeypressPrompt extends Prompt<string, KeypressOptions> {
4
+ constructor(options: KeypressOptions);
5
+ protected render(_firstRender: boolean): void;
6
+ protected handleInput(char: string, _key: Buffer): void;
7
+ }