mepcli 0.4.0 → 0.5.5

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.
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KeypressPrompt = void 0;
4
+ const ansi_1 = require("../ansi");
5
+ const base_1 = require("../base");
6
+ const theme_1 = require("../theme");
7
+ class KeypressPrompt extends base_1.Prompt {
8
+ constructor(options) {
9
+ super(options);
10
+ }
11
+ render(firstRender) {
12
+ let output = `${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET}`;
13
+ if (this.options.keys) {
14
+ const hint = this.options.keys.map(k => {
15
+ if (k === '\r' || k === '\n' || k === 'enter')
16
+ return 'enter';
17
+ if (k === ' ' || k === 'space')
18
+ return 'space';
19
+ return k;
20
+ }).join('/');
21
+ // Only show hint if it's short enough to be helpful, or always?
22
+ // Let's always show it if provided, or maybe just dimmed.
23
+ output += ` ${theme_1.theme.muted}(${hint})${ansi_1.ANSI.RESET}`;
24
+ }
25
+ else {
26
+ output += ` ${theme_1.theme.muted}(Press any key)${ansi_1.ANSI.RESET}`;
27
+ }
28
+ this.renderFrame(output);
29
+ }
30
+ handleInput(char, key) {
31
+ let keyName = char;
32
+ if (char === '\r' || char === '\n')
33
+ keyName = 'enter';
34
+ else if (char === ' ')
35
+ keyName = 'space';
36
+ else if (char === '\u001b')
37
+ keyName = 'escape';
38
+ else if (char === '\t')
39
+ keyName = 'tab';
40
+ // Handle backspace
41
+ else if (char === '\x7f' || char === '\b')
42
+ keyName = 'backspace';
43
+ // Check against whitelist
44
+ if (this.options.keys) {
45
+ const allowed = this.options.keys.map(k => k.toLowerCase());
46
+ // Check normalized name or exact char
47
+ if (!allowed.includes(keyName) && !allowed.includes(char)) {
48
+ return;
49
+ }
50
+ }
51
+ if (this.options.showInvisible) {
52
+ this.print(` ${theme_1.theme.success}${keyName}${ansi_1.ANSI.RESET}`);
53
+ }
54
+ this.submit(keyName);
55
+ }
56
+ }
57
+ exports.KeypressPrompt = KeypressPrompt;
@@ -0,0 +1,14 @@
1
+ import { Prompt } from '../base';
2
+ import { SortOptions, MouseEvent } from '../types';
3
+ export declare class SortPrompt extends Prompt<string[], SortOptions> {
4
+ private items;
5
+ private selectedIndex;
6
+ private grabbedIndex;
7
+ private scrollTop;
8
+ private readonly pageSize;
9
+ constructor(options: SortOptions);
10
+ protected render(firstRender: boolean): void;
11
+ protected handleInput(char: string): void;
12
+ private swap;
13
+ protected handleMouse(event: MouseEvent): void;
14
+ }
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SortPrompt = 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
+ class SortPrompt extends base_1.Prompt {
9
+ constructor(options) {
10
+ super(options);
11
+ this.selectedIndex = 0;
12
+ this.grabbedIndex = null;
13
+ this.scrollTop = 0;
14
+ this.pageSize = 10;
15
+ this.items = [...options.items];
16
+ }
17
+ render(firstRender) {
18
+ // Adjust Scroll Top
19
+ if (this.selectedIndex < this.scrollTop) {
20
+ this.scrollTop = this.selectedIndex;
21
+ }
22
+ else if (this.selectedIndex >= this.scrollTop + this.pageSize) {
23
+ this.scrollTop = this.selectedIndex - this.pageSize + 1;
24
+ }
25
+ // Ensure valid scroll (handle list shrinking?) - list doesn't shrink here but good practice
26
+ this.scrollTop = Math.max(0, Math.min(this.scrollTop, this.items.length - this.pageSize));
27
+ if (this.scrollTop < 0)
28
+ this.scrollTop = 0;
29
+ let output = '';
30
+ // Header
31
+ output += `${theme_1.theme.success}?${ansi_1.ANSI.RESET} ${ansi_1.ANSI.BOLD}${theme_1.theme.title}${this.options.message}${ansi_1.ANSI.RESET} ${theme_1.theme.muted}(Press <space> to grab, arrows to move, <enter> to confirm)${ansi_1.ANSI.RESET}\n`;
32
+ const visibleItems = this.items.slice(this.scrollTop, this.scrollTop + this.pageSize);
33
+ visibleItems.forEach((item, index) => {
34
+ const actualIndex = this.scrollTop + index;
35
+ if (index > 0)
36
+ output += '\n';
37
+ const isSelected = actualIndex === this.selectedIndex;
38
+ const isGrabbed = actualIndex === this.grabbedIndex;
39
+ // Pointer
40
+ let prefix = ' ';
41
+ if (isSelected) {
42
+ if (isGrabbed) {
43
+ prefix = `${theme_1.theme.main}${symbols_1.symbols.pointer}${symbols_1.symbols.pointer} `; // Double pointer for grabbed?
44
+ }
45
+ else {
46
+ prefix = `${theme_1.theme.main}${symbols_1.symbols.pointer} `;
47
+ }
48
+ }
49
+ else if (isGrabbed) {
50
+ // Should not happen if we move grabbed item with cursor
51
+ // But if logic differs, maybe.
52
+ }
53
+ // Item Content
54
+ let content = item;
55
+ if (isGrabbed) {
56
+ content = `${ansi_1.ANSI.BOLD}${theme_1.theme.main}${content}${ansi_1.ANSI.RESET}`;
57
+ }
58
+ else if (isSelected) {
59
+ content = `${theme_1.theme.main}${content}${ansi_1.ANSI.RESET}`;
60
+ }
61
+ // Index indicator? Maybe not needed, minimalist.
62
+ output += `${prefix}${content}`;
63
+ });
64
+ this.renderFrame(output);
65
+ }
66
+ handleInput(char) {
67
+ // Enter
68
+ if (char === '\r' || char === '\n') {
69
+ this.submit(this.items);
70
+ return;
71
+ }
72
+ // Space (Grab/Drop)
73
+ if (char === ' ') {
74
+ if (this.grabbedIndex === null) {
75
+ this.grabbedIndex = this.selectedIndex;
76
+ }
77
+ else {
78
+ this.grabbedIndex = null;
79
+ }
80
+ this.render(false);
81
+ return;
82
+ }
83
+ // Up
84
+ if (this.isUp(char)) {
85
+ if (this.grabbedIndex !== null) {
86
+ // Move item up
87
+ if (this.selectedIndex > 0) {
88
+ const newIndex = this.selectedIndex - 1;
89
+ this.swap(this.selectedIndex, newIndex);
90
+ this.selectedIndex = newIndex;
91
+ this.grabbedIndex = newIndex;
92
+ }
93
+ }
94
+ else {
95
+ // Move cursor up
96
+ this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.items.length - 1;
97
+ }
98
+ this.render(false);
99
+ return;
100
+ }
101
+ // Down
102
+ if (this.isDown(char)) {
103
+ if (this.grabbedIndex !== null) {
104
+ // Move item down
105
+ if (this.selectedIndex < this.items.length - 1) {
106
+ const newIndex = this.selectedIndex + 1;
107
+ this.swap(this.selectedIndex, newIndex);
108
+ this.selectedIndex = newIndex;
109
+ this.grabbedIndex = newIndex;
110
+ }
111
+ }
112
+ else {
113
+ // Move cursor down
114
+ this.selectedIndex = this.selectedIndex < this.items.length - 1 ? this.selectedIndex + 1 : 0;
115
+ }
116
+ this.render(false);
117
+ return;
118
+ }
119
+ }
120
+ swap(i, j) {
121
+ [this.items[i], this.items[j]] = [this.items[j], this.items[i]];
122
+ }
123
+ // Mouse support?
124
+ // Drag and drop is hard with just clicks/scroll.
125
+ // Maybe click to grab, scroll to move?
126
+ handleMouse(event) {
127
+ // Simple scroll support for navigation
128
+ if (event.action === 'scroll') {
129
+ if (event.scroll === 'up') {
130
+ if (this.grabbedIndex !== null) {
131
+ if (this.selectedIndex > 0) {
132
+ const newIndex = this.selectedIndex - 1;
133
+ this.swap(this.selectedIndex, newIndex);
134
+ this.selectedIndex = newIndex;
135
+ this.grabbedIndex = newIndex;
136
+ }
137
+ }
138
+ else {
139
+ this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.items.length - 1;
140
+ }
141
+ this.render(false);
142
+ }
143
+ else if (event.scroll === 'down') {
144
+ if (this.grabbedIndex !== null) {
145
+ if (this.selectedIndex < this.items.length - 1) {
146
+ const newIndex = this.selectedIndex + 1;
147
+ this.swap(this.selectedIndex, newIndex);
148
+ this.selectedIndex = newIndex;
149
+ this.grabbedIndex = newIndex;
150
+ }
151
+ }
152
+ else {
153
+ this.selectedIndex = this.selectedIndex < this.items.length - 1 ? this.selectedIndex + 1 : 0;
154
+ }
155
+ this.render(false);
156
+ }
157
+ }
158
+ }
159
+ }
160
+ exports.SortPrompt = SortPrompt;
@@ -0,0 +1,14 @@
1
+ import { Prompt } from '../base';
2
+ import { TableOptions, MouseEvent } from '../types';
3
+ export declare class TablePrompt<V> extends Prompt<V, TableOptions<V>> {
4
+ private selectedIndex;
5
+ private scrollTop;
6
+ private readonly pageSize;
7
+ private colWidths;
8
+ constructor(options: TableOptions<V>);
9
+ private calculateColWidths;
10
+ protected render(firstRender: boolean): void;
11
+ private pad;
12
+ protected handleInput(char: string): void;
13
+ protected handleMouse(event: MouseEvent): void;
14
+ }
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TablePrompt = 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 TablePrompt extends base_1.Prompt {
10
+ constructor(options) {
11
+ super(options);
12
+ this.selectedIndex = 0;
13
+ this.scrollTop = 0;
14
+ this.colWidths = [];
15
+ this.pageSize = options.rows || 7;
16
+ this.calculateColWidths();
17
+ }
18
+ calculateColWidths() {
19
+ const { columns, data } = this.options;
20
+ this.colWidths = columns.map(c => (0, utils_1.stringWidth)(c));
21
+ data.forEach(row => {
22
+ row.row.forEach((cell, idx) => {
23
+ if (idx < this.colWidths.length) {
24
+ this.colWidths[idx] = Math.max(this.colWidths[idx], (0, utils_1.stringWidth)(cell));
25
+ }
26
+ });
27
+ });
28
+ // Add padding
29
+ this.colWidths = this.colWidths.map(w => w + 2);
30
+ }
31
+ render(firstRender) {
32
+ // Scroll Logic
33
+ if (this.selectedIndex < this.scrollTop) {
34
+ this.scrollTop = this.selectedIndex;
35
+ }
36
+ else if (this.selectedIndex >= this.scrollTop + this.pageSize) {
37
+ this.scrollTop = this.selectedIndex - this.pageSize + 1;
38
+ }
39
+ const maxScroll = Math.max(0, this.options.data.length - this.pageSize);
40
+ this.scrollTop = Math.min(this.scrollTop, maxScroll);
41
+ let output = '';
42
+ // Title
43
+ 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`;
44
+ // Table Header
45
+ let headerStr = ' '; // Indent for pointer space
46
+ this.options.columns.forEach((col, i) => {
47
+ headerStr += this.pad(col, this.colWidths[i]);
48
+ });
49
+ output += `${ansi_1.ANSI.BOLD}${headerStr}${ansi_1.ANSI.RESET}\n`;
50
+ // Table Body
51
+ const visibleRows = this.options.data.slice(this.scrollTop, this.scrollTop + this.pageSize);
52
+ visibleRows.forEach((item, index) => {
53
+ const actualIndex = this.scrollTop + index;
54
+ if (index > 0)
55
+ output += '\n';
56
+ const isSelected = actualIndex === this.selectedIndex;
57
+ const pointer = isSelected ? `${theme_1.theme.main}${symbols_1.symbols.pointer}${ansi_1.ANSI.RESET} ` : ' ';
58
+ let rowStr = '';
59
+ item.row.forEach((cell, colIdx) => {
60
+ const width = this.colWidths[colIdx];
61
+ let cellStr = this.pad(cell, width);
62
+ if (isSelected) {
63
+ cellStr = `${theme_1.theme.main}${cellStr}${ansi_1.ANSI.RESET}`;
64
+ }
65
+ rowStr += cellStr;
66
+ });
67
+ output += `${pointer}${rowStr}`;
68
+ });
69
+ this.renderFrame(output);
70
+ }
71
+ pad(str, width) {
72
+ const len = (0, utils_1.stringWidth)(str);
73
+ if (len >= width)
74
+ return str;
75
+ return str + ' '.repeat(width - len);
76
+ }
77
+ handleInput(char) {
78
+ if (char === '\r' || char === '\n') {
79
+ this.submit(this.options.data[this.selectedIndex].value);
80
+ return;
81
+ }
82
+ if (this.isUp(char)) {
83
+ this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.options.data.length - 1;
84
+ this.render(false);
85
+ return;
86
+ }
87
+ if (this.isDown(char)) {
88
+ this.selectedIndex = this.selectedIndex < this.options.data.length - 1 ? this.selectedIndex + 1 : 0;
89
+ this.render(false);
90
+ return;
91
+ }
92
+ }
93
+ handleMouse(event) {
94
+ if (event.action === 'scroll') {
95
+ if (event.scroll === 'up') {
96
+ this.selectedIndex = this.selectedIndex > 0 ? this.selectedIndex - 1 : this.options.data.length - 1;
97
+ this.render(false);
98
+ }
99
+ else if (event.scroll === 'down') {
100
+ this.selectedIndex = this.selectedIndex < this.options.data.length - 1 ? this.selectedIndex + 1 : 0;
101
+ this.render(false);
102
+ }
103
+ }
104
+ }
105
+ }
106
+ exports.TablePrompt = TablePrompt;
@@ -52,22 +52,22 @@ class TextPrompt extends base_1.Prompt {
52
52
  // This is tricky because segments might contain '\n'.
53
53
  // safeSplit treats '\n' as a segment.
54
54
  let cursorLineIndex = 0;
55
- let cursorSegmentIndexOnLine = 0;
56
- let currentSegmentIndex = 0;
55
+ const cursorSegmentIndexOnLine = 0;
56
+ const currentSegmentIndex = 0;
57
57
  for (let i = 0; i < lines.length; i++) {
58
58
  // How many segments in this line?
59
59
  // We can't just use lines[i].length because that's chars.
60
60
  // We need to split the line again or iterate segments.
61
61
  // Iterating segments is safer.
62
62
  // Let's assume we iterate global segments until we hit a newline segment
63
- let lineSegmentsCount = 0;
63
+ const lineSegmentsCount = 0;
64
64
  // Since rawValue.split('\n') consumes the newlines, we need to account for them.
65
65
  // Alternative: iterate this.segments
66
66
  // Find where the cursor falls.
67
67
  }
68
68
  // Let's iterate segments to find cursor position (row, col)
69
69
  cursorLineIndex = 0;
70
- let colIndex = 0; // Visual column or char index?
70
+ const colIndex = 0; // Visual column or char index?
71
71
  // If we want visual cursor position, we need visual width of segments.
72
72
  let visualColIndex = 0;
73
73
  for (let i = 0; i < this.cursor; i++) {
@@ -90,7 +90,7 @@ class TextPrompt extends base_1.Prompt {
90
90
  // Now prepare lines for display (scrolling/truncation)
91
91
  // We need to reconstruct lines from segments to apply styling/truncation logic per line.
92
92
  let currentLineSegments = [];
93
- let processedLines = []; // Array of segment arrays
93
+ const processedLines = []; // Array of segment arrays
94
94
  for (const seg of this.segments) {
95
95
  if (seg === '\n') {
96
96
  processedLines.push(currentLineSegments);
@@ -118,25 +118,6 @@ class TextPrompt extends base_1.Prompt {
118
118
  // But cursorRelativeCol is global? No, we reset it on newline.
119
119
  // So cursorRelativeCol above was correct for the current line.
120
120
  if (isCursorLine) {
121
- // Check if we need to scroll
122
- // We need visual width of the line up to cursor.
123
- // cursorRelativeCol holds that.
124
- // If visual position > maxContentLen, we scroll.
125
- // This logic is similar to before but needs to use widths.
126
- // For simplicity, let's stick to the previous slice logic but apply it to SEGMENTS if possible.
127
- // But slicing segments for display is safer.
128
- // Let's implement simple tail truncation for now to keep it robust.
129
- // Ideally we scroll, but scrolling with variable width chars is complex.
130
- // "Good Enough": if it fits, show it. If not, truncate end.
131
- // If cursor is beyond end, scroll (slice from left).
132
- // Simplified: just show visibleLine truncated by base class renderFrame?
133
- // But renderFrame truncates blindly. We want the cursor visible.
134
- // Let's leave scrolling out for this specific "Backspace" fix task unless it's critical.
135
- // The user asked for "Backspace Emoji fix".
136
- // The scrolling logic is secondary but important.
137
- // I will preserve the existing simple scrolling logic but using segments?
138
- // No, let's just use the string for display and let renderFrame truncate.
139
- // Fix: Ensure we don't crash or show garbage.
140
121
  }
141
122
  displayValueLines.push(theme_1.theme.main + visibleLine + ansi_1.ANSI.RESET);
142
123
  });
@@ -0,0 +1,20 @@
1
+ import { Prompt } from '../base';
2
+ import { TreeOptions, MouseEvent } from '../types';
3
+ export declare class TreePrompt<V> extends Prompt<V, TreeOptions<V>> {
4
+ private cursor;
5
+ private expandedNodes;
6
+ private flatList;
7
+ private scrollTop;
8
+ private readonly pageSize;
9
+ private readonly ICON_CLOSED;
10
+ private readonly ICON_OPEN;
11
+ private readonly ICON_LEAF;
12
+ constructor(options: TreeOptions<V>);
13
+ private expandPathTo;
14
+ private initializeExpanded;
15
+ private recalculateFlatList;
16
+ private traverse;
17
+ protected render(firstRender: boolean): void;
18
+ protected handleInput(char: string, key: Buffer): void;
19
+ protected handleMouse(event: MouseEvent): void;
20
+ }
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TreePrompt = 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
+ class TreePrompt extends base_1.Prompt {
9
+ constructor(options) {
10
+ super(options);
11
+ this.cursor = 0;
12
+ this.expandedNodes = new Set();
13
+ this.flatList = [];
14
+ this.scrollTop = 0;
15
+ this.pageSize = 15;
16
+ // Icons
17
+ this.ICON_CLOSED = symbols_1.symbols.pointer === '>' ? '+' : '▸';
18
+ this.ICON_OPEN = symbols_1.symbols.pointer === '>' ? '-' : '▾';
19
+ this.ICON_LEAF = symbols_1.symbols.pointer === '>' ? ' ' : ' '; // No specific icon for leaf, just indentation
20
+ this.initializeExpanded(this.options.data);
21
+ // Handle initial value
22
+ if (this.options.initial !== undefined) {
23
+ this.expandPathTo(this.options.initial);
24
+ }
25
+ this.recalculateFlatList();
26
+ if (this.options.initial !== undefined) {
27
+ const index = this.flatList.findIndex(item => item.node.value === this.options.initial);
28
+ if (index !== -1) {
29
+ this.cursor = index;
30
+ }
31
+ }
32
+ }
33
+ expandPathTo(value) {
34
+ const find = (nodes) => {
35
+ for (const node of nodes) {
36
+ if (node.value === value)
37
+ return true;
38
+ if (node.children) {
39
+ if (find(node.children)) {
40
+ this.expandedNodes.add(node);
41
+ return true;
42
+ }
43
+ }
44
+ }
45
+ return false;
46
+ };
47
+ find(this.options.data);
48
+ }
49
+ initializeExpanded(nodes) {
50
+ for (const node of nodes) {
51
+ if (node.expanded) {
52
+ this.expandedNodes.add(node);
53
+ }
54
+ if (node.children) {
55
+ this.initializeExpanded(node.children);
56
+ }
57
+ }
58
+ }
59
+ recalculateFlatList() {
60
+ this.flatList = [];
61
+ this.traverse(this.options.data, 0, null);
62
+ // Adjust cursor if it went out of bounds (e.g. collapsing a folder above cursor)
63
+ if (this.cursor >= this.flatList.length) {
64
+ this.cursor = Math.max(0, this.flatList.length - 1);
65
+ }
66
+ }
67
+ traverse(nodes, depth, parent) {
68
+ for (const node of nodes) {
69
+ this.flatList.push({
70
+ node,
71
+ depth,
72
+ parent
73
+ });
74
+ if (node.children && node.children.length > 0 && this.expandedNodes.has(node)) {
75
+ this.traverse(node.children, depth + 1, node);
76
+ }
77
+ }
78
+ }
79
+ render(firstRender) {
80
+ 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`;
81
+ if (this.flatList.length === 0) {
82
+ output += ` ${theme_1.theme.muted}No data${ansi_1.ANSI.RESET}`;
83
+ this.renderFrame(output);
84
+ return;
85
+ }
86
+ // Adjust Scroll
87
+ if (this.cursor < this.scrollTop) {
88
+ this.scrollTop = this.cursor;
89
+ }
90
+ else if (this.cursor >= this.scrollTop + this.pageSize) {
91
+ this.scrollTop = this.cursor - this.pageSize + 1;
92
+ }
93
+ const visible = this.flatList.slice(this.scrollTop, this.scrollTop + this.pageSize);
94
+ visible.forEach((item, index) => {
95
+ const actualIndex = this.scrollTop + index;
96
+ const isSelected = actualIndex === this.cursor;
97
+ // Indentation
98
+ const indentSize = this.options.indent || 2;
99
+ const indentation = ' '.repeat(item.depth * indentSize);
100
+ // Pointer
101
+ let linePrefix = '';
102
+ if (isSelected) {
103
+ linePrefix = `${theme_1.theme.main}${symbols_1.symbols.pointer} `;
104
+ }
105
+ else {
106
+ linePrefix = ' ';
107
+ }
108
+ // Folder Icon
109
+ let icon = ' '; // Default 2 spaces for alignment
110
+ const hasChildren = item.node.children && item.node.children.length > 0;
111
+ if (hasChildren) {
112
+ if (this.expandedNodes.has(item.node)) {
113
+ icon = `${this.ICON_OPEN} `;
114
+ }
115
+ else {
116
+ icon = `${this.ICON_CLOSED} `;
117
+ }
118
+ }
119
+ // Title
120
+ let title = item.node.title;
121
+ if (item.node.disabled) {
122
+ title = `${theme_1.theme.muted}${title} (disabled)${ansi_1.ANSI.RESET}`;
123
+ }
124
+ // Compose line
125
+ let line = `${indentation}${icon}${title}`;
126
+ if (isSelected) {
127
+ line = `${theme_1.theme.main}${line}${ansi_1.ANSI.RESET}`;
128
+ }
129
+ output += linePrefix + line;
130
+ if (index < visible.length - 1)
131
+ output += '\n';
132
+ });
133
+ this.renderFrame(output);
134
+ }
135
+ handleInput(char, key) {
136
+ if (this.flatList.length === 0)
137
+ return;
138
+ // Navigation
139
+ if (this.isUp(char)) {
140
+ this.cursor = (this.cursor - 1 + this.flatList.length) % this.flatList.length;
141
+ this.render(false);
142
+ return;
143
+ }
144
+ if (this.isDown(char)) {
145
+ this.cursor = (this.cursor + 1) % this.flatList.length;
146
+ this.render(false);
147
+ return;
148
+ }
149
+ const currentItem = this.flatList[this.cursor];
150
+ const node = currentItem.node;
151
+ const hasChildren = node.children && node.children.length > 0;
152
+ // Right: Expand or Go Down
153
+ if (this.isRight(char)) {
154
+ if (hasChildren) {
155
+ if (!this.expandedNodes.has(node)) {
156
+ this.expandedNodes.add(node);
157
+ this.recalculateFlatList();
158
+ }
159
+ else {
160
+ // Go to first child (next item in flat list)
161
+ if (this.cursor + 1 < this.flatList.length) {
162
+ this.cursor++;
163
+ }
164
+ }
165
+ this.render(false);
166
+ return;
167
+ }
168
+ }
169
+ // Left: Collapse or Go Up (Parent)
170
+ if (this.isLeft(char)) {
171
+ if (hasChildren && this.expandedNodes.has(node)) {
172
+ this.expandedNodes.delete(node);
173
+ this.recalculateFlatList();
174
+ this.render(false);
175
+ return;
176
+ }
177
+ else {
178
+ // Go to parent
179
+ if (currentItem.parent) {
180
+ const parentIndex = this.flatList.findIndex(x => x.node === currentItem.parent);
181
+ if (parentIndex !== -1) {
182
+ this.cursor = parentIndex;
183
+ this.render(false);
184
+ return;
185
+ }
186
+ }
187
+ }
188
+ }
189
+ // Toggle (Space)
190
+ if (char === ' ') {
191
+ if (hasChildren) {
192
+ if (this.expandedNodes.has(node)) {
193
+ this.expandedNodes.delete(node);
194
+ }
195
+ else {
196
+ this.expandedNodes.add(node);
197
+ }
198
+ this.recalculateFlatList();
199
+ this.render(false);
200
+ }
201
+ return;
202
+ }
203
+ // Submit (Enter)
204
+ if (char === '\r' || char === '\n') {
205
+ if (!node.disabled) {
206
+ this.submit(node.value);
207
+ }
208
+ }
209
+ }
210
+ handleMouse(event) {
211
+ if (event.action === 'scroll') {
212
+ if (event.scroll === 'up') {
213
+ this.cursor = (this.cursor - 1 + this.flatList.length) % this.flatList.length;
214
+ this.render(false);
215
+ }
216
+ else if (event.scroll === 'down') {
217
+ this.cursor = (this.cursor + 1) % this.flatList.length;
218
+ this.render(false);
219
+ }
220
+ }
221
+ }
222
+ }
223
+ exports.TreePrompt = TreePrompt;