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
@@ -15,14 +15,18 @@ class TextPrompt extends base_1.Prompt {
15
15
  this.cursor = 0;
16
16
  this.hasTyped = false;
17
17
  this.segments = [];
18
+ this.lastLinesUp = 0;
18
19
  this.value = options.initial || '';
19
20
  // Initialize segments from value
20
21
  this.segments = (0, utils_1.safeSplit)(this.value);
21
22
  this.cursor = this.segments.length;
22
23
  }
23
24
  render(firstRender) {
25
+ if (!firstRender && this.lastLinesUp > 0) {
26
+ this.print(`\x1b[${this.lastLinesUp}B`);
27
+ }
28
+ this.lastLinesUp = 0;
24
29
  // Calculate available width
25
- const cols = process.stdout.columns || 80;
26
30
  // 1. Prepare Prompt Label
27
31
  const icon = this.errorMsg ? `${theme_1.theme.error}${symbols_1.symbols.cross}` : `${theme_1.theme.success}?`;
28
32
  const hint = this.options.multiline ? ` ${theme_1.theme.muted}(Press Ctrl+D to submit)${ansi_1.ANSI.RESET}` : '';
@@ -43,31 +47,16 @@ class TextPrompt extends base_1.Prompt {
43
47
  cursorRelativeCol = 0;
44
48
  }
45
49
  else {
46
- const rawValue = this.options.isPassword ? '*'.repeat(this.segments.length) : this.value;
50
+ const maskChar = this.options.mask ?? (this.options.isPassword ? '*' : undefined);
47
51
  // Note: password masking replaces each grapheme with '*'
48
52
  // Split by lines (for multiline support)
49
- const lines = rawValue.split('\n');
50
53
  // Determine which line the cursor is on
51
54
  // We need to map 'cursor' (segments index) to line/col.
52
55
  // This is tricky because segments might contain '\n'.
53
56
  // safeSplit treats '\n' as a segment.
54
57
  let cursorLineIndex = 0;
55
- const cursorSegmentIndexOnLine = 0;
56
- const currentSegmentIndex = 0;
57
- for (let i = 0; i < lines.length; i++) {
58
- // How many segments in this line?
59
- // We can't just use lines[i].length because that's chars.
60
- // We need to split the line again or iterate segments.
61
- // Iterating segments is safer.
62
- // Let's assume we iterate global segments until we hit a newline segment
63
- const lineSegmentsCount = 0;
64
- // Since rawValue.split('\n') consumes the newlines, we need to account for them.
65
- // Alternative: iterate this.segments
66
- // Find where the cursor falls.
67
- }
68
58
  // Let's iterate segments to find cursor position (row, col)
69
59
  cursorLineIndex = 0;
70
- const colIndex = 0; // Visual column or char index?
71
60
  // If we want visual cursor position, we need visual width of segments.
72
61
  let visualColIndex = 0;
73
62
  for (let i = 0; i < this.cursor; i++) {
@@ -77,11 +66,11 @@ class TextPrompt extends base_1.Prompt {
77
66
  visualColIndex = 0;
78
67
  }
79
68
  else {
80
- if (this.options.isPassword) {
81
- visualColIndex += 1;
69
+ if (maskChar !== undefined) {
70
+ visualColIndex += maskChar.length;
82
71
  }
83
72
  else {
84
- visualColIndex += this.options.isPassword ? 1 : this.getSegmentWidth(seg);
73
+ visualColIndex += this.getSegmentWidth(seg);
85
74
  }
86
75
  }
87
76
  }
@@ -101,24 +90,16 @@ class TextPrompt extends base_1.Prompt {
101
90
  }
102
91
  }
103
92
  processedLines.push(currentLineSegments); // Last line
104
- processedLines.forEach((lineSegs, idx) => {
105
- const isCursorLine = idx === cursorLineIndex;
106
- const linePrefixLen = (idx === 0) ? prefixVisualLen : 0;
107
- const maxContentLen = Math.max(10, cols - linePrefixLen - 1);
93
+ processedLines.forEach((lineSegs) => {
108
94
  // Reconstruct line string for display calculation
109
95
  // If password, join with *?
110
96
  let visibleLine = '';
111
- if (this.options.isPassword) {
112
- visibleLine = '*'.repeat(lineSegs.length);
97
+ if (maskChar !== undefined) {
98
+ visibleLine = maskChar.repeat(lineSegs.length);
113
99
  }
114
100
  else {
115
101
  visibleLine = lineSegs.join('');
116
102
  }
117
- // If this is cursor line, we need to handle horizontal scroll based on cursorRelativeCol.
118
- // But cursorRelativeCol is global? No, we reset it on newline.
119
- // So cursorRelativeCol above was correct for the current line.
120
- if (isCursorLine) {
121
- }
122
103
  displayValueLines.push(theme_1.theme.main + visibleLine + ansi_1.ANSI.RESET);
123
104
  });
124
105
  }
@@ -145,6 +126,7 @@ class TextPrompt extends base_1.Prompt {
145
126
  if (linesUp > 0) {
146
127
  this.print(`\x1b[${linesUp}A`);
147
128
  }
129
+ this.lastLinesUp = linesUp;
148
130
  let targetCol = 0;
149
131
  if (cursorRelativeRow === 0) {
150
132
  targetCol = prefixVisualLen + cursorRelativeCol;
@@ -2,7 +2,7 @@ import { Prompt } from '../base';
2
2
  import { ToggleOptions, MouseEvent } from '../types';
3
3
  export declare class TogglePrompt extends Prompt<boolean, ToggleOptions> {
4
4
  constructor(options: ToggleOptions);
5
- protected render(firstRender: boolean): void;
5
+ protected render(_firstRender: boolean): void;
6
6
  protected handleInput(char: string): void;
7
7
  protected handleMouse(event: MouseEvent): void;
8
8
  }
@@ -10,7 +10,7 @@ class TogglePrompt extends base_1.Prompt {
10
10
  super(options);
11
11
  this.value = options.initial ?? false;
12
12
  }
13
- render(firstRender) {
13
+ render(_firstRender) {
14
14
  const activeText = this.options.activeText || 'ON';
15
15
  const inactiveText = this.options.inactiveText || 'OFF';
16
16
  let toggleDisplay = '';
@@ -0,0 +1,18 @@
1
+ import { Prompt } from '../base';
2
+ import { TransferOptions, MouseEvent } from '../types';
3
+ export declare class TransferPrompt<V> extends Prompt<[V[], V[]], TransferOptions<V>> {
4
+ private leftList;
5
+ private rightList;
6
+ private cursorLeft;
7
+ private cursorRight;
8
+ private scrollTopLeft;
9
+ private scrollTopRight;
10
+ private activeSide;
11
+ private readonly pageSize;
12
+ constructor(options: TransferOptions<V>);
13
+ private normalize;
14
+ protected truncate(str: string, width: number): string;
15
+ protected render(_firstRender: boolean): void;
16
+ protected handleInput(char: string): void;
17
+ protected handleMouse(event: MouseEvent): void;
18
+ }
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransferPrompt = 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 TransferPrompt extends base_1.Prompt {
10
+ constructor(options) {
11
+ super(options);
12
+ this.leftList = [];
13
+ this.rightList = [];
14
+ this.cursorLeft = 0;
15
+ this.cursorRight = 0;
16
+ this.scrollTopLeft = 0;
17
+ this.scrollTopRight = 0;
18
+ this.activeSide = 'left';
19
+ this.pageSize = 10;
20
+ this.leftList = this.normalize(options.source);
21
+ this.rightList = this.normalize(options.target || []);
22
+ }
23
+ normalize(items) {
24
+ return items.map(item => {
25
+ if (typeof item === 'string') {
26
+ return { title: item, value: item };
27
+ }
28
+ return item;
29
+ });
30
+ }
31
+ truncate(str, width) {
32
+ if ((0, utils_1.stringWidth)(str) <= width)
33
+ return str;
34
+ let res = str;
35
+ while ((0, utils_1.stringWidth)(res + '...') > width && res.length > 0) {
36
+ res = res.slice(0, -1);
37
+ }
38
+ return res + '...';
39
+ }
40
+ render(_firstRender) {
41
+ const termWidth = process.stdout.columns || 80;
42
+ const colWidth = Math.floor((termWidth - 6) / 2);
43
+ // Adjust Scroll Top
44
+ if (this.activeSide === 'left') {
45
+ if (this.cursorLeft < this.scrollTopLeft)
46
+ this.scrollTopLeft = this.cursorLeft;
47
+ if (this.cursorLeft >= this.scrollTopLeft + this.pageSize)
48
+ this.scrollTopLeft = this.cursorLeft - this.pageSize + 1;
49
+ }
50
+ else {
51
+ if (this.cursorRight < this.scrollTopRight)
52
+ this.scrollTopRight = this.cursorRight;
53
+ if (this.cursorRight >= this.scrollTopRight + this.pageSize)
54
+ this.scrollTopRight = this.cursorRight - this.pageSize + 1;
55
+ }
56
+ let output = `${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
57
+ // Headers
58
+ const leftTitle = this.activeSide === 'left' ? `${theme_1.theme.main}Source${ansi_1.ANSI.RESET}` : 'Source';
59
+ const rightTitle = this.activeSide === 'right' ? `${theme_1.theme.main}Target${ansi_1.ANSI.RESET}` : 'Target';
60
+ output += ` ${leftTitle}`.padEnd(colWidth + 2) + ' ' + ` ${rightTitle}\n`;
61
+ output += ` ${ansi_1.ANSI.DIM}${symbols_1.symbols.line.repeat(colWidth)}${ansi_1.ANSI.RESET} ${ansi_1.ANSI.DIM}${symbols_1.symbols.line.repeat(colWidth)}${ansi_1.ANSI.RESET}\n`;
62
+ for (let i = 0; i < this.pageSize; i++) {
63
+ const idxLeft = this.scrollTopLeft + i;
64
+ const idxRight = this.scrollTopRight + i;
65
+ const itemLeft = this.leftList[idxLeft];
66
+ const itemRight = this.rightList[idxRight];
67
+ // Left Column
68
+ let leftStr = '';
69
+ if (itemLeft) {
70
+ const isSelected = this.activeSide === 'left' && idxLeft === this.cursorLeft;
71
+ const title = this.truncate(itemLeft.title, colWidth - 2);
72
+ if (isSelected) {
73
+ leftStr = `${theme_1.theme.main}${symbols_1.symbols.pointer} ${title}${ansi_1.ANSI.RESET}`;
74
+ }
75
+ else {
76
+ leftStr = ` ${title}`;
77
+ }
78
+ }
79
+ else {
80
+ leftStr = ''; // Empty
81
+ }
82
+ // Right Column
83
+ let rightStr = '';
84
+ if (itemRight) {
85
+ const isSelected = this.activeSide === 'right' && idxRight === this.cursorRight;
86
+ const title = this.truncate(itemRight.title, colWidth - 2);
87
+ if (isSelected) {
88
+ rightStr = `${theme_1.theme.main}${symbols_1.symbols.pointer} ${title}${ansi_1.ANSI.RESET}`;
89
+ }
90
+ else {
91
+ rightStr = ` ${title}`;
92
+ }
93
+ }
94
+ const leftVisualWidth = itemLeft ? ((0, utils_1.stringWidth)(this.truncate(itemLeft.title, colWidth - 2)) + 2) : 0;
95
+ // Pad visually
96
+ const padding = ' '.repeat(Math.max(0, colWidth - leftVisualWidth));
97
+ output += leftStr + padding + ' | ' + rightStr + '\n';
98
+ }
99
+ output += `\n${ansi_1.ANSI.DIM}(Tab: Switch | a: Move All Right | r: Reset/All Left)${ansi_1.ANSI.RESET}`;
100
+ this.renderFrame(output);
101
+ }
102
+ handleInput(char) {
103
+ if (char === '\r' || char === '\n') {
104
+ const leftValues = this.leftList.map(i => i.value);
105
+ const rightValues = this.rightList.map(i => i.value);
106
+ this.submit([leftValues, rightValues]);
107
+ return;
108
+ }
109
+ if (char === '\t' || this.isLeft(char) || this.isRight(char)) {
110
+ // Toggle side
111
+ this.activeSide = this.activeSide === 'left' ? 'right' : 'left';
112
+ this.render(false);
113
+ return;
114
+ }
115
+ // --- Batch Transfer Shortcuts ---
116
+ // Move All to Right ('a' or '>')
117
+ if (char === 'a' || char === '>') {
118
+ if (this.leftList.length > 0) {
119
+ this.rightList.push(...this.leftList);
120
+ this.leftList = [];
121
+ this.cursorLeft = 0;
122
+ this.activeSide = 'right';
123
+ this.render(false);
124
+ }
125
+ return;
126
+ }
127
+ // Move All to Left ('r' or '<')
128
+ if (char === 'r' || char === '<') {
129
+ if (this.rightList.length > 0) {
130
+ this.leftList.push(...this.rightList);
131
+ this.rightList = [];
132
+ this.cursorRight = 0;
133
+ this.activeSide = 'left';
134
+ this.render(false);
135
+ }
136
+ return;
137
+ }
138
+ if (this.isUp(char)) {
139
+ if (this.activeSide === 'left') {
140
+ this.cursorLeft = Math.max(0, this.cursorLeft - 1);
141
+ }
142
+ else {
143
+ this.cursorRight = Math.max(0, this.cursorRight - 1);
144
+ }
145
+ this.render(false);
146
+ return;
147
+ }
148
+ if (this.isDown(char)) {
149
+ if (this.activeSide === 'left') {
150
+ this.cursorLeft = Math.min(this.leftList.length - 1, this.cursorLeft + 1);
151
+ }
152
+ else {
153
+ this.cursorRight = Math.min(this.rightList.length - 1, this.cursorRight + 1);
154
+ }
155
+ this.render(false);
156
+ return;
157
+ }
158
+ if (char === ' ') {
159
+ // Move item
160
+ if (this.activeSide === 'left') {
161
+ if (this.leftList.length > 0) {
162
+ const [item] = this.leftList.splice(this.cursorLeft, 1);
163
+ this.rightList.push(item);
164
+ if (this.cursorLeft >= this.leftList.length) {
165
+ this.cursorLeft = Math.max(0, this.leftList.length - 1);
166
+ }
167
+ }
168
+ }
169
+ else {
170
+ if (this.rightList.length > 0) {
171
+ const [item] = this.rightList.splice(this.cursorRight, 1);
172
+ this.leftList.push(item);
173
+ if (this.cursorRight >= this.rightList.length) {
174
+ this.cursorRight = Math.max(0, this.rightList.length - 1);
175
+ }
176
+ }
177
+ }
178
+ this.render(false);
179
+ }
180
+ }
181
+ handleMouse(event) {
182
+ if (event.action === 'scroll') {
183
+ if (event.scroll === 'up') {
184
+ if (this.activeSide === 'left') {
185
+ this.cursorLeft = Math.max(0, this.cursorLeft - 1);
186
+ }
187
+ else {
188
+ this.cursorRight = Math.max(0, this.cursorRight - 1);
189
+ }
190
+ }
191
+ else {
192
+ if (this.activeSide === 'left') {
193
+ this.cursorLeft = Math.min(this.leftList.length - 1, this.cursorLeft + 1);
194
+ }
195
+ else {
196
+ this.cursorRight = Math.min(this.rightList.length - 1, this.cursorRight + 1);
197
+ }
198
+ }
199
+ this.render(false);
200
+ }
201
+ }
202
+ }
203
+ exports.TransferPrompt = TransferPrompt;
@@ -0,0 +1,32 @@
1
+ import { Prompt } from '../base';
2
+ import { TreeSelectOptions, MouseEvent } from '../types';
3
+ /**
4
+ * FIXED-ISH: This prompt may hang on Windows TTY after multiple prompt cycles.
5
+ * Current workaround: User must press 'Enter' to flush the stdin buffer.
6
+ * Potential root cause: Synchronous recursion during initialization blocking setRawMode.
7
+ */
8
+ export declare class TreeSelectPrompt<V> extends Prompt<V[], TreeSelectOptions<V>> {
9
+ private cursor;
10
+ private expandedNodes;
11
+ private flatList;
12
+ private scrollTop;
13
+ private readonly pageSize;
14
+ private parentMap;
15
+ private static hasWarnedWindows;
16
+ private readonly ICON_CLOSED;
17
+ private readonly ICON_OPEN;
18
+ constructor(options: TreeSelectOptions<V>);
19
+ private buildParentMap;
20
+ private initializeExpanded;
21
+ private initializeSelection;
22
+ private recalculateAllParents;
23
+ private updateNodeStateFromChildren;
24
+ private setNodeState;
25
+ private recalculateFlatList;
26
+ private traverse;
27
+ private toggleRecursive;
28
+ protected render(_firstRender: boolean): void;
29
+ protected handleInput(char: string, _key: Buffer): void;
30
+ private collectSelected;
31
+ protected handleMouse(event: MouseEvent): void;
32
+ }
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TreeSelectPrompt = 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
+ /**
9
+ * FIXED-ISH: This prompt may hang on Windows TTY after multiple prompt cycles.
10
+ * Current workaround: User must press 'Enter' to flush the stdin buffer.
11
+ * Potential root cause: Synchronous recursion during initialization blocking setRawMode.
12
+ */
13
+ class TreeSelectPrompt extends base_1.Prompt {
14
+ constructor(options) {
15
+ super(options);
16
+ this.cursor = 0;
17
+ this.expandedNodes = new Set();
18
+ this.flatList = [];
19
+ this.scrollTop = 0;
20
+ this.pageSize = 15;
21
+ this.parentMap = new Map();
22
+ this.ICON_CLOSED = symbols_1.symbols.pointer === '>' ? '+' : '▸';
23
+ this.ICON_OPEN = symbols_1.symbols.pointer === '>' ? '-' : '▾';
24
+ this.buildParentMap(this.options.data, null);
25
+ this.initializeExpanded(this.options.data);
26
+ if (this.options.initial) {
27
+ const initialSet = new Set(this.options.initial);
28
+ this.initializeSelection(this.options.data, initialSet);
29
+ this.recalculateAllParents(this.options.data);
30
+ }
31
+ if (process.platform === 'win32' && !TreeSelectPrompt.hasWarnedWindows) {
32
+ console.warn(`${ansi_1.ANSI.FG_YELLOW}Warning:${ansi_1.ANSI.RESET} TreeSelectPrompt may hang on Windows TTY after multiple prompt cycles. Please press 'Enter' if the prompt becomes unresponsive.${ansi_1.ANSI.RESET}`);
33
+ TreeSelectPrompt.hasWarnedWindows = true;
34
+ }
35
+ this.recalculateFlatList();
36
+ }
37
+ buildParentMap(nodes, parent) {
38
+ for (const node of nodes) {
39
+ if (parent)
40
+ this.parentMap.set(node, parent);
41
+ if (node.children) {
42
+ this.buildParentMap(node.children, node);
43
+ }
44
+ }
45
+ }
46
+ initializeExpanded(nodes) {
47
+ for (const node of nodes) {
48
+ if (node.expanded) {
49
+ this.expandedNodes.add(node);
50
+ }
51
+ if (node.children) {
52
+ this.initializeExpanded(node.children);
53
+ }
54
+ }
55
+ }
56
+ initializeSelection(nodes, initialValues) {
57
+ for (const node of nodes) {
58
+ if (initialValues.has(node.value)) {
59
+ this.setNodeState(node, true, false);
60
+ }
61
+ if (node.children) {
62
+ this.initializeSelection(node.children, initialValues);
63
+ }
64
+ }
65
+ }
66
+ recalculateAllParents(nodes) {
67
+ for (const node of nodes) {
68
+ if (node.children) {
69
+ this.recalculateAllParents(node.children);
70
+ this.updateNodeStateFromChildren(node);
71
+ }
72
+ }
73
+ }
74
+ updateNodeStateFromChildren(node) {
75
+ if (!node.children || node.children.length === 0)
76
+ return;
77
+ const allChecked = node.children.every(c => c.selected === true);
78
+ const allUnchecked = node.children.every(c => !c.selected);
79
+ if (allChecked) {
80
+ node.selected = true;
81
+ }
82
+ else if (allUnchecked) {
83
+ node.selected = false;
84
+ }
85
+ else {
86
+ node.selected = 'indeterminate';
87
+ }
88
+ }
89
+ setNodeState(node, state, updateParents = true) {
90
+ node.selected = state;
91
+ if (node.children) {
92
+ node.children.forEach(c => this.setNodeState(c, state, false));
93
+ }
94
+ if (updateParents) {
95
+ let curr = node;
96
+ while (this.parentMap.has(curr)) {
97
+ const parent = this.parentMap.get(curr);
98
+ this.updateNodeStateFromChildren(parent);
99
+ curr = parent;
100
+ }
101
+ }
102
+ }
103
+ recalculateFlatList() {
104
+ this.flatList = [];
105
+ this.traverse(this.options.data, 0, null);
106
+ if (this.cursor >= this.flatList.length) {
107
+ this.cursor = Math.max(0, this.flatList.length - 1);
108
+ }
109
+ }
110
+ traverse(nodes, depth, parent) {
111
+ for (const node of nodes) {
112
+ this.flatList.push({ node, depth, parent });
113
+ if (node.children && node.children.length > 0 && this.expandedNodes.has(node)) {
114
+ this.traverse(node.children, depth + 1, node);
115
+ }
116
+ }
117
+ }
118
+ toggleRecursive(node, expand) {
119
+ if (expand)
120
+ this.expandedNodes.add(node);
121
+ else
122
+ this.expandedNodes.delete(node);
123
+ if (node.children) {
124
+ node.children.forEach(child => this.toggleRecursive(child, expand));
125
+ }
126
+ }
127
+ render(_firstRender) {
128
+ let output = `${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}\n`;
129
+ if (this.flatList.length === 0) {
130
+ output += ` ${theme_1.theme.muted}No data${ansi_1.ANSI.RESET}`;
131
+ this.renderFrame(output);
132
+ return;
133
+ }
134
+ if (this.cursor < this.scrollTop) {
135
+ this.scrollTop = this.cursor;
136
+ }
137
+ else if (this.cursor >= this.scrollTop + this.pageSize) {
138
+ this.scrollTop = this.cursor - this.pageSize + 1;
139
+ }
140
+ const visible = this.flatList.slice(this.scrollTop, this.scrollTop + this.pageSize);
141
+ visible.forEach((item, index) => {
142
+ const actualIndex = this.scrollTop + index;
143
+ const isSelected = actualIndex === this.cursor;
144
+ const indentSize = this.options.indent || 2;
145
+ const indentation = ' '.repeat(item.depth * indentSize);
146
+ const linePrefix = isSelected ? `${theme_1.theme.main}${symbols_1.symbols.pointer} ` : ' ';
147
+ let folderIcon = ' ';
148
+ const hasChildren = item.node.children && item.node.children.length > 0;
149
+ if (hasChildren) {
150
+ folderIcon = this.expandedNodes.has(item.node) ? `${this.ICON_OPEN} ` : `${this.ICON_CLOSED} `;
151
+ }
152
+ // Checkbox
153
+ let checkIcon = `[ ]`;
154
+ if (item.node.selected === true) {
155
+ checkIcon = `${theme_1.theme.success}[x]${ansi_1.ANSI.RESET}`;
156
+ }
157
+ else if (item.node.selected === 'indeterminate') {
158
+ checkIcon = `${ansi_1.ANSI.FG_YELLOW}[-]${ansi_1.ANSI.RESET}`;
159
+ }
160
+ else {
161
+ checkIcon = `${theme_1.theme.muted}[ ]${ansi_1.ANSI.RESET}`;
162
+ }
163
+ let title = item.node.title;
164
+ if (item.node.disabled) {
165
+ title = `${theme_1.theme.muted}${title} (disabled)${ansi_1.ANSI.RESET}`;
166
+ }
167
+ let line = `${indentation}${folderIcon}${checkIcon} ${title}`;
168
+ if (isSelected) {
169
+ line = `${theme_1.theme.main}${line}${ansi_1.ANSI.RESET}`;
170
+ }
171
+ output += linePrefix + line;
172
+ if (index < visible.length - 1)
173
+ output += '\n';
174
+ });
175
+ output += `\n${theme_1.theme.muted}(e: Expand All, c: Collapse All, Space: Toggle)${ansi_1.ANSI.RESET}`;
176
+ this.renderFrame(output);
177
+ }
178
+ handleInput(char, _key) {
179
+ if (this.flatList.length === 0)
180
+ return;
181
+ // 1. Priority: Navigation keys (ANSI sequences)
182
+ if (this.isUp(char)) {
183
+ this.cursor = (this.cursor - 1 + this.flatList.length) % this.flatList.length;
184
+ this.render(false);
185
+ return;
186
+ }
187
+ if (this.isDown(char)) {
188
+ this.cursor = (this.cursor + 1) % this.flatList.length;
189
+ this.render(false);
190
+ return;
191
+ }
192
+ const currentItem = this.flatList[this.cursor];
193
+ if (!currentItem)
194
+ return;
195
+ const node = currentItem.node;
196
+ const hasChildren = node.children && node.children.length > 0;
197
+ // 2. Right Arrow: Expand folder or move to first child
198
+ if (this.isRight(char)) {
199
+ if (hasChildren && !this.expandedNodes.has(node)) {
200
+ this.expandedNodes.add(node);
201
+ this.recalculateFlatList();
202
+ }
203
+ else if (this.cursor + 1 < this.flatList.length) {
204
+ this.cursor++;
205
+ }
206
+ this.render(false);
207
+ return;
208
+ }
209
+ // 3. Left Arrow: Collapse folder or move to parent
210
+ if (this.isLeft(char)) {
211
+ if (hasChildren && this.expandedNodes.has(node)) {
212
+ this.expandedNodes.delete(node);
213
+ this.recalculateFlatList();
214
+ }
215
+ else if (currentItem.parent) {
216
+ const parentIndex = this.flatList.findIndex(x => x.node === currentItem.parent);
217
+ if (parentIndex !== -1) {
218
+ this.cursor = parentIndex;
219
+ }
220
+ }
221
+ this.render(false);
222
+ return;
223
+ }
224
+ // 4. Space: Toggle selection
225
+ if (char === ' ') {
226
+ if (!node.disabled) {
227
+ const newState = node.selected === true ? false : true;
228
+ this.setNodeState(node, newState);
229
+ this.render(false);
230
+ }
231
+ return;
232
+ }
233
+ // 5. Functional keys (Keep it simple to avoid blocking)
234
+ if (char === 'e' && hasChildren) {
235
+ this.toggleRecursive(node, true);
236
+ this.recalculateFlatList();
237
+ this.render(false);
238
+ return;
239
+ }
240
+ if (char === 'c' && hasChildren) {
241
+ this.toggleRecursive(node, false);
242
+ this.recalculateFlatList();
243
+ this.render(false);
244
+ return;
245
+ }
246
+ // 6. Submit: Enter
247
+ if (char === '\r' || char === '\n') {
248
+ const selectedValues = [];
249
+ this.collectSelected(this.options.data, selectedValues);
250
+ this.submit(selectedValues);
251
+ }
252
+ }
253
+ collectSelected(nodes, result) {
254
+ for (const node of nodes) {
255
+ if (node.selected === true) {
256
+ result.push(node.value);
257
+ }
258
+ if (node.children) {
259
+ this.collectSelected(node.children, result);
260
+ }
261
+ }
262
+ }
263
+ handleMouse(event) {
264
+ if (event.action === 'scroll') {
265
+ if (event.scroll === 'up') {
266
+ this.cursor = (this.cursor - 1 + this.flatList.length) % this.flatList.length;
267
+ this.render(false);
268
+ }
269
+ else if (event.scroll === 'down') {
270
+ this.cursor = (this.cursor + 1) % this.flatList.length;
271
+ this.render(false);
272
+ }
273
+ }
274
+ }
275
+ }
276
+ exports.TreeSelectPrompt = TreeSelectPrompt;
277
+ TreeSelectPrompt.hasWarnedWindows = false; // Static flag to warn only once
@@ -8,13 +8,13 @@ export declare class TreePrompt<V> extends Prompt<V, TreeOptions<V>> {
8
8
  private readonly pageSize;
9
9
  private readonly ICON_CLOSED;
10
10
  private readonly ICON_OPEN;
11
- private readonly ICON_LEAF;
12
11
  constructor(options: TreeOptions<V>);
13
12
  private expandPathTo;
14
13
  private initializeExpanded;
15
14
  private recalculateFlatList;
16
15
  private traverse;
17
- protected render(firstRender: boolean): void;
18
- protected handleInput(char: string, key: Buffer): void;
16
+ private toggleRecursive;
17
+ protected render(_firstRender: boolean): void;
18
+ protected handleInput(char: string, _key: Buffer): void;
19
19
  protected handleMouse(event: MouseEvent): void;
20
20
  }