voonex 0.1.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +223 -0
  3. package/dist/components/Box.d.ts +33 -0
  4. package/dist/components/Box.js +242 -0
  5. package/dist/components/Button.d.ts +23 -0
  6. package/dist/components/Button.js +76 -0
  7. package/dist/components/Checkbox.d.ts +20 -0
  8. package/dist/components/Checkbox.js +40 -0
  9. package/dist/components/Input.d.ts +23 -0
  10. package/dist/components/Input.js +79 -0
  11. package/dist/components/Menu.d.ts +27 -0
  12. package/dist/components/Menu.js +93 -0
  13. package/dist/components/Popup.d.ts +7 -0
  14. package/dist/components/Popup.js +50 -0
  15. package/dist/components/ProgressBar.d.ts +28 -0
  16. package/dist/components/ProgressBar.js +47 -0
  17. package/dist/components/Radio.d.ts +22 -0
  18. package/dist/components/Radio.js +64 -0
  19. package/dist/components/Select.d.ts +24 -0
  20. package/dist/components/Select.js +126 -0
  21. package/dist/components/Tab.d.ts +24 -0
  22. package/dist/components/Tab.js +72 -0
  23. package/dist/components/Table.d.ts +25 -0
  24. package/dist/components/Table.js +116 -0
  25. package/dist/components/Textarea.d.ts +26 -0
  26. package/dist/components/Textarea.js +176 -0
  27. package/dist/components/Tree.d.ts +33 -0
  28. package/dist/components/Tree.js +166 -0
  29. package/dist/core/Component.d.ts +6 -0
  30. package/dist/core/Component.js +5 -0
  31. package/dist/core/Cursor.d.ts +7 -0
  32. package/dist/core/Cursor.js +59 -0
  33. package/dist/core/Events.d.ts +11 -0
  34. package/dist/core/Events.js +37 -0
  35. package/dist/core/Focus.d.ts +16 -0
  36. package/dist/core/Focus.js +69 -0
  37. package/dist/core/Input.d.ts +13 -0
  38. package/dist/core/Input.js +88 -0
  39. package/dist/core/Layout.d.ts +18 -0
  40. package/dist/core/Layout.js +65 -0
  41. package/dist/core/Screen.d.ts +78 -0
  42. package/dist/core/Screen.js +373 -0
  43. package/dist/core/Styler.d.ts +71 -0
  44. package/dist/core/Styler.js +157 -0
  45. package/dist/index.d.ts +25 -0
  46. package/dist/index.js +41 -0
  47. package/package.json +29 -0
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TabManager = void 0;
4
+ const Screen_1 = require("../core/Screen");
5
+ const Styler_1 = require("../core/Styler");
6
+ class TabManager {
7
+ constructor(options) {
8
+ this.activeIndex = 0;
9
+ this.isFocused = false;
10
+ this.id = options.id;
11
+ this.options = options;
12
+ this.activeIndex = options.initialTab || 0;
13
+ }
14
+ focus() {
15
+ this.isFocused = true;
16
+ this.render();
17
+ }
18
+ blur() {
19
+ this.isFocused = false;
20
+ this.render();
21
+ }
22
+ handleKey(key) {
23
+ if (!this.isFocused)
24
+ return false;
25
+ // Left/Right arrows to switch tabs
26
+ if (key.name === 'left') {
27
+ this.activeIndex = (this.activeIndex - 1 + this.options.titles.length) % this.options.titles.length;
28
+ this.triggerChange();
29
+ this.render();
30
+ return true;
31
+ }
32
+ else if (key.name === 'right') {
33
+ this.activeIndex = (this.activeIndex + 1) % this.options.titles.length;
34
+ this.triggerChange();
35
+ this.render();
36
+ return true;
37
+ }
38
+ return false;
39
+ }
40
+ triggerChange() {
41
+ if (this.options.onTabChange) {
42
+ this.options.onTabChange(this.activeIndex);
43
+ }
44
+ }
45
+ render() {
46
+ const { x, y, width, titles } = this.options;
47
+ // Draw Tabs Row
48
+ let currentX = x;
49
+ titles.forEach((title, index) => {
50
+ const isActive = index === this.activeIndex;
51
+ const style = isActive ? (this.isFocused ? 'brightCyan' : 'white') : 'dim';
52
+ const decoration = isActive ? 'bold' : 'dim';
53
+ const displayTitle = ` ${title} `;
54
+ // Draw top border for active tab or something distinct?
55
+ // Simple style: [ Active ] Inactive
56
+ let renderStr = displayTitle;
57
+ if (isActive) {
58
+ renderStr = Styler_1.Styler.style(displayTitle, 'bgBlue', 'white', 'bold');
59
+ }
60
+ else {
61
+ renderStr = Styler_1.Styler.style(displayTitle, 'dim', 'bgBlack');
62
+ }
63
+ Screen_1.Screen.write(currentX, y, renderStr);
64
+ currentX += Styler_1.Styler.len(displayTitle) + 1;
65
+ });
66
+ // Draw separator line below tabs
67
+ Screen_1.Screen.write(x, y + 1, Styler_1.Styler.style('─'.repeat(width), 'dim'));
68
+ // Ideally, we might want to clear the content area below if the user handles it manually?
69
+ // Or we assume the user's `onTabChange` will re-render the content area.
70
+ }
71
+ }
72
+ exports.TabManager = TabManager;
@@ -0,0 +1,25 @@
1
+ import { Focusable } from '../core/Focus';
2
+ import * as readline from 'readline';
3
+ interface TableOptions {
4
+ id?: string;
5
+ x?: number;
6
+ y?: number;
7
+ height?: number;
8
+ width?: number;
9
+ scrollable?: boolean;
10
+ }
11
+ export declare class Table implements Focusable {
12
+ id: string;
13
+ private headers;
14
+ private rows;
15
+ private options;
16
+ private scrollTop;
17
+ private isFocused;
18
+ constructor(headers: string[], options?: TableOptions);
19
+ addRow(row: string[]): void;
20
+ focus(): void;
21
+ blur(): void;
22
+ handleKey(key: readline.Key): boolean;
23
+ render(): void;
24
+ }
25
+ export {};
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Table = void 0;
4
+ const Styler_1 = require("../core/Styler");
5
+ const Screen_1 = require("../core/Screen");
6
+ class Table {
7
+ constructor(headers, options = {}) {
8
+ this.rows = [];
9
+ this.scrollTop = 0;
10
+ this.isFocused = false;
11
+ this.headers = headers;
12
+ this.id = options.id || `table-${Math.random().toString(36).substr(2, 9)}`;
13
+ this.options = { x: 1, y: 1, scrollable: true, ...options };
14
+ }
15
+ addRow(row) {
16
+ this.rows.push(row);
17
+ }
18
+ focus() {
19
+ this.isFocused = true;
20
+ this.render();
21
+ }
22
+ blur() {
23
+ this.isFocused = false;
24
+ this.render();
25
+ }
26
+ handleKey(key) {
27
+ if (!this.options.scrollable)
28
+ return false;
29
+ const maxScroll = Math.max(0, this.rows.length - (this.options.height ? this.options.height - 4 : this.rows.length));
30
+ // Height - header (1) - separator (1) - border/margin?
31
+ // Let's refine calculation in render, but basically:
32
+ // Visible rows = height - 2 (header + sep)
33
+ const visibleRows = (this.options.height || this.rows.length + 2) - 2;
34
+ if (key.name === 'up') {
35
+ if (this.scrollTop > 0) {
36
+ this.scrollTop--;
37
+ this.render();
38
+ return true;
39
+ }
40
+ }
41
+ else if (key.name === 'down') {
42
+ if (this.scrollTop < maxScroll) {
43
+ this.scrollTop++;
44
+ this.render();
45
+ return true;
46
+ }
47
+ }
48
+ else if (key.name === 'pageup') {
49
+ this.scrollTop = Math.max(0, this.scrollTop - visibleRows);
50
+ this.render();
51
+ return true;
52
+ }
53
+ else if (key.name === 'pagedown') {
54
+ this.scrollTop = Math.min(maxScroll, this.scrollTop + visibleRows);
55
+ this.render();
56
+ return true;
57
+ }
58
+ return false;
59
+ }
60
+ render() {
61
+ const startX = this.options.x || 1;
62
+ let currentY = this.options.y || 1;
63
+ const height = this.options.height;
64
+ // Calculate column widths
65
+ const colWidths = this.headers.map((h, i) => {
66
+ const maxRow = Math.max(...this.rows.map(r => Styler_1.Styler.len(r[i] || '')));
67
+ return Math.max(Styler_1.Styler.len(h), maxRow) + 2;
68
+ });
69
+ const buildRow = (items, isHeader = false) => {
70
+ return items.map((item, i) => {
71
+ const len = Styler_1.Styler.len(item);
72
+ const pad = ' '.repeat(colWidths[i] - len - 1);
73
+ const cell = ` ${item}${pad}`;
74
+ const style = isHeader ? 'blue' : (this.isFocused ? 'white' : 'gray');
75
+ return isHeader ? Styler_1.Styler.style(cell, 'bold', 'blue') : Styler_1.Styler.style(cell, style);
76
+ }).join('│');
77
+ };
78
+ const totalWidth = colWidths.reduce((a, b) => a + b, 0) + (colWidths.length - 1);
79
+ const separator = colWidths.map(w => '─'.repeat(w)).join('┼');
80
+ // Draw Header
81
+ Screen_1.Screen.write(startX, currentY++, buildRow(this.headers, true));
82
+ Screen_1.Screen.write(startX, currentY++, Styler_1.Styler.style(separator, 'dim'));
83
+ // Determine visible rows
84
+ let visibleRowsCount = this.rows.length;
85
+ if (height) {
86
+ visibleRowsCount = height - 2; // Subtract header and separator
87
+ }
88
+ for (let i = 0; i < visibleRowsCount; i++) {
89
+ const rowIndex = i + this.scrollTop;
90
+ if (rowIndex < this.rows.length) {
91
+ let rowStr = buildRow(this.rows[rowIndex]);
92
+ // Add Scrollbar indicator if needed
93
+ if (this.options.scrollable && height && this.rows.length > visibleRowsCount) {
94
+ const scrollbarHeight = Math.max(1, Math.floor((visibleRowsCount / this.rows.length) * visibleRowsCount));
95
+ const maxScroll = this.rows.length - visibleRowsCount;
96
+ const scrollPercent = this.scrollTop / maxScroll;
97
+ const scrollPos = Math.floor(scrollPercent * (visibleRowsCount - scrollbarHeight));
98
+ if (i >= scrollPos && i < scrollPos + scrollbarHeight) {
99
+ rowStr += Styler_1.Styler.style('│', 'brightWhite');
100
+ }
101
+ else {
102
+ rowStr += Styler_1.Styler.style('│', 'dim');
103
+ }
104
+ }
105
+ Screen_1.Screen.write(startX, currentY++, rowStr);
106
+ }
107
+ else {
108
+ // Clear empty lines if height is fixed
109
+ if (height) {
110
+ Screen_1.Screen.write(startX, currentY++, ' '.repeat(totalWidth + 1));
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+ exports.Table = Table;
@@ -0,0 +1,26 @@
1
+ import { Focusable } from '../core/Focus';
2
+ import * as readline from 'readline';
3
+ export interface TextareaOptions {
4
+ id: string;
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ height: number;
9
+ label?: string;
10
+ value?: string;
11
+ }
12
+ export declare class Textarea implements Focusable {
13
+ id: string;
14
+ value: string;
15
+ private options;
16
+ private isFocused;
17
+ private cursorIndex;
18
+ private scrollTop;
19
+ constructor(options: TextareaOptions);
20
+ focus(): void;
21
+ blur(): void;
22
+ handleKey(key: readline.Key): boolean;
23
+ private adjustScroll;
24
+ render(): void;
25
+ private getLineStart;
26
+ }
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Textarea = void 0;
4
+ const Styler_1 = require("../core/Styler");
5
+ const Screen_1 = require("../core/Screen");
6
+ class Textarea {
7
+ constructor(options) {
8
+ this.isFocused = false;
9
+ this.cursorIndex = 0; // Absolute index in value
10
+ this.scrollTop = 0; // Line offset
11
+ this.id = options.id;
12
+ this.options = options;
13
+ this.value = options.value || "";
14
+ this.cursorIndex = this.value.length;
15
+ }
16
+ focus() {
17
+ this.isFocused = true;
18
+ this.render();
19
+ }
20
+ blur() {
21
+ this.isFocused = false;
22
+ this.render();
23
+ }
24
+ handleKey(key) {
25
+ if (!this.isFocused)
26
+ return false;
27
+ let consumed = false;
28
+ if (key.name === 'backspace') {
29
+ if (this.cursorIndex > 0) {
30
+ this.value = this.value.slice(0, this.cursorIndex - 1) + this.value.slice(this.cursorIndex);
31
+ this.cursorIndex--;
32
+ consumed = true;
33
+ }
34
+ }
35
+ else if (key.name === 'return' || key.name === 'enter') {
36
+ this.value = this.value.slice(0, this.cursorIndex) + '\n' + this.value.slice(this.cursorIndex);
37
+ this.cursorIndex++;
38
+ consumed = true;
39
+ }
40
+ else if (key.name === 'left') {
41
+ if (this.cursorIndex > 0) {
42
+ this.cursorIndex--;
43
+ consumed = true;
44
+ }
45
+ }
46
+ else if (key.name === 'right') {
47
+ if (this.cursorIndex < this.value.length) {
48
+ this.cursorIndex++;
49
+ consumed = true;
50
+ }
51
+ }
52
+ else if (key.name === 'up') {
53
+ // Move cursor up a line
54
+ const currentLineStart = this.value.lastIndexOf('\n', this.cursorIndex - 1) + 1;
55
+ const col = this.cursorIndex - currentLineStart;
56
+ if (currentLineStart > 0) {
57
+ const prevLineEnd = currentLineStart - 1;
58
+ const prevLineStart = this.value.lastIndexOf('\n', prevLineEnd - 1) + 1;
59
+ const prevLineLength = prevLineEnd - prevLineStart;
60
+ this.cursorIndex = prevLineStart + Math.min(col, prevLineLength);
61
+ consumed = true;
62
+ }
63
+ }
64
+ else if (key.name === 'down') {
65
+ // Move cursor down a line
66
+ const nextLineStart = this.value.indexOf('\n', this.cursorIndex);
67
+ if (nextLineStart !== -1) {
68
+ const currentLineStart = this.value.lastIndexOf('\n', this.cursorIndex - 1) + 1;
69
+ const col = this.cursorIndex - currentLineStart;
70
+ const nextLineEnd = this.value.indexOf('\n', nextLineStart + 1);
71
+ const actualNextLineEnd = nextLineEnd === -1 ? this.value.length : nextLineEnd;
72
+ const nextLineLength = actualNextLineEnd - (nextLineStart + 1);
73
+ this.cursorIndex = (nextLineStart + 1) + Math.min(col, nextLineLength);
74
+ consumed = true;
75
+ }
76
+ }
77
+ else if (key.sequence && key.sequence.length === 1 && !key.ctrl && !key.meta) {
78
+ if (/^[\x20-\x7E]$/.test(key.sequence)) {
79
+ this.value = this.value.slice(0, this.cursorIndex) + key.sequence + this.value.slice(this.cursorIndex);
80
+ this.cursorIndex++;
81
+ consumed = true;
82
+ }
83
+ }
84
+ if (consumed) {
85
+ this.adjustScroll();
86
+ this.render();
87
+ return true;
88
+ }
89
+ return false;
90
+ }
91
+ adjustScroll() {
92
+ // Calculate which line the cursor is on
93
+ const lines = this.value.split('\n');
94
+ let charCount = 0;
95
+ let cursorLine = 0;
96
+ for (let i = 0; i < lines.length; i++) {
97
+ const lineLen = lines[i].length + 1; // +1 for newline
98
+ if (this.cursorIndex < charCount + lineLen) {
99
+ cursorLine = i;
100
+ break;
101
+ }
102
+ charCount += lineLen;
103
+ }
104
+ // Special case: cursor at end of string which is a newline
105
+ if (this.cursorIndex === this.value.length && this.value.endsWith('\n')) {
106
+ cursorLine = lines.length; // Actually lines array might have empty string at end
107
+ // If split('\n') on "a\n" gives ["a", ""]. Cursor at end is on line 1 (index 1).
108
+ // Correct.
109
+ }
110
+ const visibleLines = this.options.height - 2; // Border
111
+ if (cursorLine < this.scrollTop) {
112
+ this.scrollTop = cursorLine;
113
+ }
114
+ else if (cursorLine >= this.scrollTop + visibleLines) {
115
+ this.scrollTop = cursorLine - visibleLines + 1;
116
+ }
117
+ }
118
+ render() {
119
+ const { x, y, width, height, label } = this.options;
120
+ const borderColor = this.isFocused ? 'brightCyan' : 'white';
121
+ const innerWidth = width - 2;
122
+ const innerHeight = height - 2;
123
+ // Draw Label if provided
124
+ if (label) {
125
+ Screen_1.Screen.write(x, y - 1, Styler_1.Styler.style(label, 'bold'));
126
+ }
127
+ // Draw Box Border
128
+ const top = '┌' + '─'.repeat(innerWidth) + '┐';
129
+ const bottom = '└' + '─'.repeat(innerWidth) + '┘';
130
+ Screen_1.Screen.write(x, y, Styler_1.Styler.style(top, borderColor));
131
+ for (let i = 0; i < innerHeight; i++) {
132
+ Screen_1.Screen.write(x, y + 1 + i, Styler_1.Styler.style('│' + ' '.repeat(innerWidth) + '│', borderColor));
133
+ }
134
+ Screen_1.Screen.write(x, y + height - 1, Styler_1.Styler.style(bottom, borderColor));
135
+ // Draw Content
136
+ const lines = this.value.split('\n');
137
+ // We need to map cursorIndex to screen coordinates to draw the cursor character
138
+ let charCounter = 0;
139
+ for (let i = 0; i < innerHeight; i++) {
140
+ const lineIndex = i + this.scrollTop;
141
+ if (lineIndex < lines.length) {
142
+ let lineStr = lines[lineIndex];
143
+ // Truncate if too long (TODO: Horizontal scroll?)
144
+ if (lineStr.length > innerWidth) {
145
+ lineStr = lineStr.substring(0, innerWidth);
146
+ }
147
+ // Check if cursor is on this line
148
+ const lineStart = this.getLineStart(lines, lineIndex);
149
+ const lineEnd = lineStart + lines[lineIndex].length;
150
+ let displayStr = lineStr;
151
+ if (this.isFocused && this.cursorIndex >= lineStart && this.cursorIndex <= lineEnd) {
152
+ // Cursor is on this line
153
+ const col = this.cursorIndex - lineStart;
154
+ if (col < innerWidth) {
155
+ const charAtCursor = lineStr[col] || ' ';
156
+ const before = lineStr.substring(0, col);
157
+ const after = lineStr.substring(col + 1);
158
+ displayStr = before + Styler_1.Styler.style(charAtCursor, 'bgGreen', 'black') + after;
159
+ }
160
+ }
161
+ // Pad to clear
162
+ const remaining = innerWidth - Styler_1.Styler.len(displayStr);
163
+ const finalStr = displayStr + ' '.repeat(Math.max(0, remaining));
164
+ Screen_1.Screen.write(x + 1, y + 1 + i, finalStr);
165
+ }
166
+ }
167
+ }
168
+ getLineStart(lines, lineIndex) {
169
+ let start = 0;
170
+ for (let i = 0; i < lineIndex; i++) {
171
+ start += lines[i].length + 1;
172
+ }
173
+ return start;
174
+ }
175
+ }
176
+ exports.Textarea = Textarea;
@@ -0,0 +1,33 @@
1
+ import { Focusable } from '../core/Focus';
2
+ import * as readline from 'readline';
3
+ export interface TreeNode {
4
+ name: string;
5
+ children?: TreeNode[];
6
+ info?: string;
7
+ expanded?: boolean;
8
+ }
9
+ export interface TreeOptions {
10
+ id: string;
11
+ root: TreeNode;
12
+ x: number;
13
+ y: number;
14
+ height?: number;
15
+ width?: number;
16
+ }
17
+ export declare class Tree implements Focusable {
18
+ id: string;
19
+ private root;
20
+ private options;
21
+ private isFocused;
22
+ private flatList;
23
+ private selectedIndex;
24
+ private scrollTop;
25
+ constructor(options: TreeOptions);
26
+ private recalculateFlatList;
27
+ focus(): void;
28
+ blur(): void;
29
+ handleKey(key: readline.Key): boolean;
30
+ private ensureVisible;
31
+ render(): void;
32
+ static render(node: TreeNode, prefix?: string, isLast?: boolean): void;
33
+ }
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Tree = void 0;
4
+ const Styler_1 = require("../core/Styler");
5
+ const Screen_1 = require("../core/Screen");
6
+ class Tree {
7
+ constructor(options) {
8
+ this.isFocused = false;
9
+ this.flatList = [];
10
+ this.selectedIndex = 0;
11
+ this.scrollTop = 0;
12
+ this.id = options.id;
13
+ this.options = { width: 50, ...options }; // Default width to 50
14
+ this.root = options.root;
15
+ // Ensure root is expanded by default?
16
+ this.root.expanded = true;
17
+ this.recalculateFlatList();
18
+ }
19
+ recalculateFlatList() {
20
+ this.flatList = [];
21
+ const traverse = (node, depth, parent) => {
22
+ this.flatList.push({ node, depth, parent });
23
+ if (node.children && node.expanded) {
24
+ node.children.forEach(child => traverse(child, depth + 1, node));
25
+ }
26
+ };
27
+ traverse(this.root, 0);
28
+ }
29
+ focus() {
30
+ this.isFocused = true;
31
+ this.render();
32
+ }
33
+ blur() {
34
+ this.isFocused = false;
35
+ this.render();
36
+ }
37
+ handleKey(key) {
38
+ if (!this.isFocused)
39
+ return false;
40
+ let consumed = false;
41
+ if (key.name === 'up') {
42
+ if (this.selectedIndex > 0) {
43
+ this.selectedIndex--;
44
+ this.ensureVisible();
45
+ consumed = true;
46
+ }
47
+ }
48
+ else if (key.name === 'down') {
49
+ if (this.selectedIndex < this.flatList.length - 1) {
50
+ this.selectedIndex++;
51
+ this.ensureVisible();
52
+ consumed = true;
53
+ }
54
+ }
55
+ else if (key.name === 'right') {
56
+ const current = this.flatList[this.selectedIndex];
57
+ if (current.node.children) {
58
+ if (!current.node.expanded) {
59
+ current.node.expanded = true;
60
+ this.recalculateFlatList();
61
+ consumed = true;
62
+ }
63
+ }
64
+ }
65
+ else if (key.name === 'left') {
66
+ const current = this.flatList[this.selectedIndex];
67
+ if (current.node.children && current.node.expanded) {
68
+ current.node.expanded = false;
69
+ this.recalculateFlatList();
70
+ consumed = true;
71
+ }
72
+ else if (current.parent) {
73
+ // Move selection to parent
74
+ const parentIdx = this.flatList.findIndex(x => x.node === current.parent);
75
+ if (parentIdx !== -1) {
76
+ this.selectedIndex = parentIdx;
77
+ this.ensureVisible();
78
+ consumed = true;
79
+ }
80
+ }
81
+ }
82
+ else if (key.name === 'enter') {
83
+ // Toggle expansion
84
+ const current = this.flatList[this.selectedIndex];
85
+ if (current.node.children) {
86
+ current.node.expanded = !current.node.expanded;
87
+ this.recalculateFlatList();
88
+ consumed = true;
89
+ }
90
+ }
91
+ if (consumed)
92
+ this.render();
93
+ return consumed;
94
+ }
95
+ ensureVisible() {
96
+ if (!this.options.height)
97
+ return;
98
+ const visibleHeight = this.options.height;
99
+ if (this.selectedIndex < this.scrollTop) {
100
+ this.scrollTop = this.selectedIndex;
101
+ }
102
+ else if (this.selectedIndex >= this.scrollTop + visibleHeight) {
103
+ this.scrollTop = this.selectedIndex - visibleHeight + 1;
104
+ }
105
+ }
106
+ render() {
107
+ const { x, y, height, width } = this.options;
108
+ const visibleHeight = height || this.flatList.length;
109
+ const renderWidth = width || 50;
110
+ // Clear area if possible? Or rely on overwrite.
111
+ // Assuming box drawing or clearing happens before or we write spaces.
112
+ for (let i = 0; i < visibleHeight; i++) {
113
+ const idx = i + this.scrollTop;
114
+ if (idx < this.flatList.length) {
115
+ const { node, depth } = this.flatList[idx];
116
+ const isSelected = idx === this.selectedIndex;
117
+ const indent = ' '.repeat(depth);
118
+ const icon = node.children ? (node.expanded ? '📂' : '📁') : '📄';
119
+ const prefix = isSelected ? (this.isFocused ? '❯ ' : '> ') : ' ';
120
+ let name = node.name;
121
+ if (isSelected && this.isFocused) {
122
+ name = Styler_1.Styler.style(name, 'cyan', 'bold');
123
+ }
124
+ else if (isSelected) {
125
+ name = Styler_1.Styler.style(name, 'white', 'bold');
126
+ }
127
+ else {
128
+ name = Styler_1.Styler.style(name, 'dim');
129
+ }
130
+ // Note: Styler.style adds ansi codes. padEnd counts string length.
131
+ // We should pad AFTER stripping, or be careful.
132
+ // However, padEnd works on the full string including codes, so visual length will be shorter if we pad the colored string.
133
+ // We need to calculate how much padding to add based on visual length.
134
+ const content = `${prefix}${indent}${icon} ${name}`;
135
+ // This is still tricky because 'name' has codes.
136
+ // Simple hack: Write content, then clear rest of line manually.
137
+ // Or construct string without colors to measure length, then add padding.
138
+ const cleanName = node.name;
139
+ const cleanContent = `${prefix}${indent}${icon} ${cleanName}`; // Approximation
140
+ const visualLen = cleanContent.length; // Approximate
141
+ const padding = ' '.repeat(Math.max(0, renderWidth - visualLen));
142
+ Screen_1.Screen.write(x, y + i, content + padding);
143
+ }
144
+ else {
145
+ Screen_1.Screen.write(x, y + i, ' '.repeat(renderWidth));
146
+ }
147
+ }
148
+ }
149
+ // Compatibility Static Render (prints to console directly, not Screen.write at xy)
150
+ static render(node, prefix = '', isLast = true) {
151
+ // This is for CLI usage (console.log)
152
+ const connector = isLast ? '└── ' : '├── ';
153
+ const childPrefix = prefix + (isLast ? ' ' : '│ ');
154
+ const icon = node.children ? '📁' : '📄';
155
+ const info = node.info ? Styler_1.Styler.style(` (${node.info})`, 'dim') : '';
156
+ const name = node.children ? Styler_1.Styler.style(node.name, 'brightCyan', 'bold') : node.name;
157
+ console.log(`${Styler_1.Styler.style(prefix + connector, 'dim')}${icon} ${name}${info}`);
158
+ if (node.children) {
159
+ const children = node.children;
160
+ children.forEach((child, index) => {
161
+ Tree.render(child, childPrefix, index === children.length - 1);
162
+ });
163
+ }
164
+ }
165
+ }
166
+ exports.Tree = Tree;
@@ -0,0 +1,6 @@
1
+ export interface ComponentLifecycle {
2
+ init?(): void;
3
+ destroy?(): void;
4
+ onMount?(): void;
5
+ onUnmount?(): void;
6
+ }
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ // ==========================================
3
+ // CORE: COMPONENT LIFECYCLE
4
+ // ==========================================
5
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ export declare class Cursor {
2
+ static hide(): void;
3
+ static show(): void;
4
+ static moveTo(x: number, y: number): void;
5
+ static clearLine(): void;
6
+ static clearScreen(): void;
7
+ }