voonex 0.2.0 → 0.3.1
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.
- package/README.md +79 -49
- package/dist/components/Button.d.ts +9 -3
- package/dist/components/Button.js +17 -8
- package/dist/components/Input.js +0 -4
- package/dist/components/Menu.js +1 -1
- package/dist/components/Select.js +0 -1
- package/dist/components/Tab.js +0 -2
- package/dist/components/Textarea.js +0 -1
- package/dist/core/Component.d.ts +34 -0
- package/dist/core/Component.js +31 -0
- package/dist/core/Layout.js +1 -1
- package/dist/core/Signal.d.ts +22 -0
- package/dist/core/Signal.js +42 -0
- package/dist/core/Styler.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +13 -5
package/README.md
CHANGED
|
@@ -6,10 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
- **Zero Dependencies**: Lightweight and easy to audit.
|
|
8
8
|
- **Double Buffering & Diffing**: Efficient rendering that eliminates flickering and minimizes I/O.
|
|
9
|
+
- **Reactive Signals**: Modern state management inspired by SolidJS. State changes automatically trigger updates.
|
|
10
|
+
- **Component Lifecycle**: Class-based components with `mount`, `unmount`, and lifecycle hooks.
|
|
9
11
|
- **Auto Layout**: Flexbox-like layout engine for responsive designs.
|
|
10
12
|
- **Layer Management**: Z-index support for Modals, Tooltips, and Popups.
|
|
11
13
|
- **Component System**: Built-in widgets like `Box`, `Menu`, `ProgressBar`, `Input`, `Table`, and more.
|
|
12
|
-
- **Reactive Rendering**: Automated screen updates via `Screen.mount()` and `Screen.scheduleRender()`.
|
|
13
14
|
- **Focus Management**: Built-in keyboard navigation and focus delegation.
|
|
14
15
|
- **Styling Engine**: Simple yet powerful API for ANSI colors and text modifiers.
|
|
15
16
|
- **TypeScript Support**: Written in TypeScript with full type definitions included.
|
|
@@ -24,41 +25,48 @@ npm install voonex
|
|
|
24
25
|
|
|
25
26
|
## Quick Start
|
|
26
27
|
|
|
27
|
-
Here is a minimal example showing how to
|
|
28
|
+
Here is a minimal example showing how to create a reactive counter app.
|
|
28
29
|
|
|
29
30
|
```typescript
|
|
30
|
-
import { Screen,
|
|
31
|
-
|
|
32
|
-
// 1.
|
|
33
|
-
|
|
31
|
+
import { Screen, Component, createSignal, Input } from 'voonex';
|
|
32
|
+
|
|
33
|
+
// 1. Create a Component
|
|
34
|
+
class CounterApp extends Component {
|
|
35
|
+
// Define reactive state
|
|
36
|
+
private count = createSignal(0);
|
|
37
|
+
|
|
38
|
+
// Getters/Setters for convenience
|
|
39
|
+
get value() { return this.count[0](); }
|
|
40
|
+
set value(v) { this.count[1](v); }
|
|
41
|
+
|
|
42
|
+
constructor() {
|
|
43
|
+
super();
|
|
44
|
+
|
|
45
|
+
// Handle input to increment
|
|
46
|
+
Input.onKey(key => {
|
|
47
|
+
if (key.name === 'up') this.value = this.value + 1;
|
|
48
|
+
if (key.name === 'down') this.value = this.value - 1;
|
|
49
|
+
if (key.name === 'q') {
|
|
50
|
+
Screen.leave();
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
34
55
|
|
|
35
|
-
// 2.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
], {
|
|
42
|
-
title: "Hello World",
|
|
43
|
-
x: 5,
|
|
44
|
-
y: 5,
|
|
45
|
-
padding: 1,
|
|
46
|
-
style: 'double',
|
|
47
|
-
borderColor: 'cyan'
|
|
48
|
-
});
|
|
56
|
+
// 2. Implement render()
|
|
57
|
+
// It runs automatically whenever 'this.value' changes!
|
|
58
|
+
render() {
|
|
59
|
+
Screen.write(5, 5, `Count: ${this.value} `);
|
|
60
|
+
Screen.write(5, 7, "Press Up/Down to change, Q to quit.");
|
|
61
|
+
}
|
|
49
62
|
}
|
|
50
63
|
|
|
51
|
-
// 3.
|
|
52
|
-
Screen.
|
|
64
|
+
// 3. Setup Screen
|
|
65
|
+
Screen.enter();
|
|
53
66
|
|
|
54
|
-
// 4.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
// Leave the screen buffer properly before exiting
|
|
58
|
-
Screen.leave();
|
|
59
|
-
process.exit(0);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
67
|
+
// 4. Mount the App
|
|
68
|
+
const app = new CounterApp();
|
|
69
|
+
app.mount();
|
|
62
70
|
```
|
|
63
71
|
|
|
64
72
|
Run it with:
|
|
@@ -68,32 +76,48 @@ npx ts-node my-app.ts
|
|
|
68
76
|
|
|
69
77
|
## Core Concepts
|
|
70
78
|
|
|
79
|
+
### Reactive Signals
|
|
80
|
+
Voonex uses a fine-grained reactivity system. When you update a signal, Voonex automatically schedules a render for the next tick. No manual `render()` calls required.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { createSignal } from 'voonex';
|
|
84
|
+
|
|
85
|
+
const [count, setCount] = createSignal(0);
|
|
86
|
+
|
|
87
|
+
// Reading the value
|
|
88
|
+
console.log(count());
|
|
89
|
+
|
|
90
|
+
// Updating the value (triggers UI update)
|
|
91
|
+
setCount(5);
|
|
92
|
+
setCount(prev => prev + 1);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Components & Lifecycle
|
|
96
|
+
Components extend the `Component` abstract class.
|
|
97
|
+
|
|
98
|
+
- `mount(zIndex?)`: Registers the component to the screen loop.
|
|
99
|
+
- `unmount()`: Removes the component.
|
|
100
|
+
- `render()`: The drawing logic.
|
|
101
|
+
|
|
102
|
+
**Lifecycle Hooks:**
|
|
103
|
+
- `init()`: Called on instantiation.
|
|
104
|
+
- `onMount()`: Called after mounting.
|
|
105
|
+
- `onUnmount()`: Called after unmounting.
|
|
106
|
+
- `destroy()`: Cleanup hook.
|
|
107
|
+
|
|
71
108
|
### The Screen
|
|
72
109
|
The `Screen` class is the heart of Voonex. It manages the terminal buffer, handles resizing, and optimizes rendering using a diffing algorithm.
|
|
73
110
|
|
|
74
111
|
- `Screen.enter()`: Switches to the alternate buffer (like `vim` or `nano`).
|
|
75
112
|
- `Screen.leave()`: Restores the original terminal state. **Always call this before exiting.**
|
|
76
|
-
- `Screen.mount(renderFn, layer?)`: Registers a function to be called during the render cycle.
|
|
77
|
-
- `Screen.scheduleRender()`: Triggers a screen update.
|
|
78
113
|
|
|
79
114
|
#### Layer Management
|
|
80
115
|
Voonex uses a "Painter's Algorithm" with Z-index layers.
|
|
81
116
|
```typescript
|
|
82
|
-
import {
|
|
117
|
+
import { Layer } from 'voonex';
|
|
83
118
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
Screen.mount(drawPopup, Layer.MODAL); // 100
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### Input Handling
|
|
90
|
-
Voonex provides global input listeners.
|
|
91
|
-
|
|
92
|
-
**Keyboard:**
|
|
93
|
-
```typescript
|
|
94
|
-
Input.onKey((key) => {
|
|
95
|
-
console.log(key.name);
|
|
96
|
-
});
|
|
119
|
+
// Components handle this automatically via mount()
|
|
120
|
+
myComponent.mount(Layer.MODAL);
|
|
97
121
|
```
|
|
98
122
|
|
|
99
123
|
### Layout Engine
|
|
@@ -114,7 +138,7 @@ const sidebarRect = rects[0];
|
|
|
114
138
|
const contentRect = rects[1];
|
|
115
139
|
```
|
|
116
140
|
|
|
117
|
-
## Components
|
|
141
|
+
## Built-in Components
|
|
118
142
|
|
|
119
143
|
### Box
|
|
120
144
|
A container for text with optional borders, padding, and titles.
|
|
@@ -131,8 +155,8 @@ Box.render([
|
|
|
131
155
|
});
|
|
132
156
|
```
|
|
133
157
|
|
|
134
|
-
### Button
|
|
135
|
-
Interactive button that supports
|
|
158
|
+
### Button (Reactive)
|
|
159
|
+
Interactive button that supports focus and press states.
|
|
136
160
|
|
|
137
161
|
```typescript
|
|
138
162
|
const btn = new Button({
|
|
@@ -141,6 +165,8 @@ const btn = new Button({
|
|
|
141
165
|
x: 10, y: 10,
|
|
142
166
|
onPress: () => submitForm()
|
|
143
167
|
});
|
|
168
|
+
|
|
169
|
+
btn.mount(); // Don't forget to mount!
|
|
144
170
|
```
|
|
145
171
|
|
|
146
172
|
### Input Field
|
|
@@ -170,6 +196,10 @@ A modal dialog that overlays other content (uses `Layer.MODAL`).
|
|
|
170
196
|
await Popup.alert("This is an important message!", { title: "Alert" });
|
|
171
197
|
```
|
|
172
198
|
|
|
199
|
+
|
|
200
|
+
> Voonex is currently in Beta stage.
|
|
201
|
+
|
|
202
|
+
|
|
173
203
|
## License
|
|
174
204
|
|
|
175
205
|
This project is under the **MIT License**.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Component } from '../core/Component';
|
|
1
2
|
import { Focusable } from '../core/Focus';
|
|
2
3
|
import * as readline from 'readline';
|
|
3
4
|
interface ButtonOptions {
|
|
@@ -9,11 +10,16 @@ interface ButtonOptions {
|
|
|
9
10
|
style?: 'simple' | 'brackets';
|
|
10
11
|
onPress: () => void;
|
|
11
12
|
}
|
|
12
|
-
export declare class Button implements Focusable {
|
|
13
|
+
export declare class Button extends Component implements Focusable {
|
|
13
14
|
id: string;
|
|
15
|
+
parent?: Focusable;
|
|
14
16
|
private options;
|
|
15
|
-
private
|
|
16
|
-
private
|
|
17
|
+
private isFocusedSignal;
|
|
18
|
+
private isPressedSignal;
|
|
19
|
+
private get isFocused();
|
|
20
|
+
private set isFocused(value);
|
|
21
|
+
private get isPressed();
|
|
22
|
+
private set isPressed(value);
|
|
17
23
|
constructor(options: ButtonOptions);
|
|
18
24
|
focus(): void;
|
|
19
25
|
blur(): void;
|
|
@@ -3,21 +3,30 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Button = void 0;
|
|
4
4
|
const Styler_1 = require("../core/Styler");
|
|
5
5
|
const Screen_1 = require("../core/Screen");
|
|
6
|
-
|
|
6
|
+
const Component_1 = require("../core/Component");
|
|
7
|
+
const Signal_1 = require("../core/Signal");
|
|
8
|
+
class Button extends Component_1.Component {
|
|
9
|
+
// Getters/Setters for convenience
|
|
10
|
+
get isFocused() { return this.isFocusedSignal[0](); }
|
|
11
|
+
set isFocused(v) { this.isFocusedSignal[1](v); }
|
|
12
|
+
get isPressed() { return this.isPressedSignal[0](); }
|
|
13
|
+
set isPressed(v) { this.isPressedSignal[1](v); }
|
|
7
14
|
constructor(options) {
|
|
8
|
-
|
|
9
|
-
|
|
15
|
+
super();
|
|
16
|
+
// State managed by signals (implicit render on change)
|
|
17
|
+
this.isFocusedSignal = (0, Signal_1.createSignal)(false);
|
|
18
|
+
this.isPressedSignal = (0, Signal_1.createSignal)(false);
|
|
10
19
|
this.id = options.id;
|
|
11
20
|
this.options = { style: 'brackets', ...options };
|
|
12
21
|
}
|
|
13
22
|
focus() {
|
|
14
23
|
this.isFocused = true;
|
|
15
|
-
|
|
24
|
+
// No need to call render(), signal handles it
|
|
16
25
|
}
|
|
17
26
|
blur() {
|
|
18
27
|
this.isFocused = false;
|
|
19
28
|
this.isPressed = false;
|
|
20
|
-
|
|
29
|
+
// No need to call render(), signal handles it
|
|
21
30
|
}
|
|
22
31
|
handleKey(key) {
|
|
23
32
|
if (!this.isFocused)
|
|
@@ -30,16 +39,17 @@ class Button {
|
|
|
30
39
|
}
|
|
31
40
|
triggerPress() {
|
|
32
41
|
this.isPressed = true;
|
|
33
|
-
|
|
42
|
+
// render triggered by signal
|
|
34
43
|
// Trigger action slightly delayed to show visual feedback
|
|
35
44
|
setTimeout(() => {
|
|
36
45
|
this.isPressed = false;
|
|
37
|
-
this.render();
|
|
38
46
|
this.options.onPress();
|
|
39
47
|
}, 150);
|
|
40
48
|
}
|
|
41
49
|
render() {
|
|
42
50
|
const { x, y, text, width } = this.options;
|
|
51
|
+
// Reading signals here (isFocused, isPressed) creates the dependency
|
|
52
|
+
// although in our simple system, any signal write triggers global render.
|
|
43
53
|
let label = text;
|
|
44
54
|
// Simple visual centering if width is provided
|
|
45
55
|
if (width) {
|
|
@@ -61,7 +71,6 @@ class Button {
|
|
|
61
71
|
renderedText = ` ${label} `;
|
|
62
72
|
}
|
|
63
73
|
// Apply styles
|
|
64
|
-
// Note: Styler.style takes varargs, we need to handle types carefully or cast
|
|
65
74
|
Screen_1.Screen.write(x, y, Styler_1.Styler.style(renderedText, color, 'bold', bgStyle));
|
|
66
75
|
}
|
|
67
76
|
else {
|
package/dist/components/Input.js
CHANGED
|
@@ -104,10 +104,6 @@ class InputField {
|
|
|
104
104
|
// Clamp scrollOffset
|
|
105
105
|
if (this.scrollOffset < 0)
|
|
106
106
|
this.scrollOffset = 0;
|
|
107
|
-
let displayValue = this.value.substring(this.scrollOffset, this.scrollOffset + maxWidth);
|
|
108
|
-
if (type === 'password') {
|
|
109
|
-
displayValue = '*'.repeat(displayValue.length);
|
|
110
|
-
}
|
|
111
107
|
// Prepare content with cursor
|
|
112
108
|
let renderedContent = "";
|
|
113
109
|
const cursorRelPos = this.cursorIndex - this.scrollOffset;
|
package/dist/components/Menu.js
CHANGED
|
@@ -93,7 +93,6 @@ class Select {
|
|
|
93
93
|
const labelText = label ? `${label}: ` : '';
|
|
94
94
|
const labelLen = labelText.length;
|
|
95
95
|
if (label) {
|
|
96
|
-
const labelStyle = this.isFocused ? 'brightCyan' : 'white';
|
|
97
96
|
// If not focused, make it dim using modifier
|
|
98
97
|
if (this.isFocused) {
|
|
99
98
|
Screen_1.Screen.write(x, y, Styler_1.Styler.style(labelText, 'brightCyan', 'bold'));
|
package/dist/components/Tab.js
CHANGED
|
@@ -48,8 +48,6 @@ class TabManager {
|
|
|
48
48
|
let currentX = x;
|
|
49
49
|
titles.forEach((title, index) => {
|
|
50
50
|
const isActive = index === this.activeIndex;
|
|
51
|
-
const style = isActive ? (this.isFocused ? 'brightCyan' : 'white') : 'dim';
|
|
52
|
-
const decoration = isActive ? 'bold' : 'dim';
|
|
53
51
|
const displayTitle = ` ${title} `;
|
|
54
52
|
// Draw top border for active tab or something distinct?
|
|
55
53
|
// Simple style: [ Active ] Inactive
|
|
@@ -135,7 +135,6 @@ class Textarea {
|
|
|
135
135
|
// Draw Content
|
|
136
136
|
const lines = this.value.split('\n');
|
|
137
137
|
// We need to map cursorIndex to screen coordinates to draw the cursor character
|
|
138
|
-
let charCounter = 0;
|
|
139
138
|
for (let i = 0; i < innerHeight; i++) {
|
|
140
139
|
const lineIndex = i + this.scrollTop;
|
|
141
140
|
if (lineIndex < lines.length) {
|
package/dist/core/Component.d.ts
CHANGED
|
@@ -4,3 +4,37 @@ export interface ComponentLifecycle {
|
|
|
4
4
|
onMount?(): void;
|
|
5
5
|
onUnmount?(): void;
|
|
6
6
|
}
|
|
7
|
+
export declare abstract class Component implements ComponentLifecycle {
|
|
8
|
+
private mounted;
|
|
9
|
+
private boundRender;
|
|
10
|
+
constructor();
|
|
11
|
+
/**
|
|
12
|
+
* Called when the component is instantiated.
|
|
13
|
+
*/
|
|
14
|
+
init?(): void;
|
|
15
|
+
/**
|
|
16
|
+
* Called before the component is destroyed.
|
|
17
|
+
*/
|
|
18
|
+
destroy?(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Called when the component is mounted to the screen.
|
|
21
|
+
*/
|
|
22
|
+
onMount?(): void;
|
|
23
|
+
/**
|
|
24
|
+
* Called when the component is unmounted from the screen.
|
|
25
|
+
*/
|
|
26
|
+
onUnmount?(): void;
|
|
27
|
+
/**
|
|
28
|
+
* The render function that draws the component to the screen.
|
|
29
|
+
*/
|
|
30
|
+
abstract render(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Mounts the component to the Screen rendering loop.
|
|
33
|
+
* @param zIndex Layer priority (default: Layer.CONTENT)
|
|
34
|
+
*/
|
|
35
|
+
mount(zIndex?: number): void;
|
|
36
|
+
/**
|
|
37
|
+
* Unmounts the component from the Screen rendering loop.
|
|
38
|
+
*/
|
|
39
|
+
unmount(): void;
|
|
40
|
+
}
|
package/dist/core/Component.js
CHANGED
|
@@ -3,3 +3,34 @@
|
|
|
3
3
|
// CORE: COMPONENT LIFECYCLE
|
|
4
4
|
// ==========================================
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Component = void 0;
|
|
7
|
+
const Screen_1 = require("./Screen");
|
|
8
|
+
class Component {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.mounted = false;
|
|
11
|
+
this.boundRender = this.render.bind(this);
|
|
12
|
+
this.init?.();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Mounts the component to the Screen rendering loop.
|
|
16
|
+
* @param zIndex Layer priority (default: Layer.CONTENT)
|
|
17
|
+
*/
|
|
18
|
+
mount(zIndex = Screen_1.Layer.CONTENT) {
|
|
19
|
+
if (this.mounted)
|
|
20
|
+
return;
|
|
21
|
+
this.mounted = true;
|
|
22
|
+
Screen_1.Screen.mount(this.boundRender, zIndex);
|
|
23
|
+
this.onMount?.();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Unmounts the component from the Screen rendering loop.
|
|
27
|
+
*/
|
|
28
|
+
unmount() {
|
|
29
|
+
if (!this.mounted)
|
|
30
|
+
return;
|
|
31
|
+
this.mounted = false;
|
|
32
|
+
Screen_1.Screen.unmount(this.boundRender);
|
|
33
|
+
this.onUnmount?.();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.Component = Component;
|
package/dist/core/Layout.js
CHANGED
|
@@ -88,7 +88,7 @@ class Layout {
|
|
|
88
88
|
const rects = [];
|
|
89
89
|
const map = new Map();
|
|
90
90
|
let currentPos = direction === 'row' ? parent.x : parent.y;
|
|
91
|
-
children.forEach((child,
|
|
91
|
+
children.forEach((child, _) => {
|
|
92
92
|
let size = 0;
|
|
93
93
|
if (child.fixed !== undefined) {
|
|
94
94
|
size = child.fixed;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type Accessor<T> = () => T;
|
|
2
|
+
export type Setter<T> = (value: T | ((prev: T) => T)) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Creates a reactive signal.
|
|
5
|
+
* When the value updates, it automatically schedules a screen render.
|
|
6
|
+
* @param initialValue The initial value of the signal.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createSignal<T>(initialValue: T): [Accessor<T>, Setter<T>];
|
|
9
|
+
/**
|
|
10
|
+
* Creates a memoized value that caches its result and only re-calculates
|
|
11
|
+
* when the result of the function changes (polled during access).
|
|
12
|
+
*
|
|
13
|
+
* Note: A true dependency graph is out of scope. This implementation
|
|
14
|
+
* re-runs the function on every access but could store the last result
|
|
15
|
+
* if we had a way to know if dependencies changed.
|
|
16
|
+
*
|
|
17
|
+
* Since we don't track dependencies, we can't safely cache unless we know inputs didn't change.
|
|
18
|
+
* BUT, often `createMemo` is used to avoid expensive calcs if called multiple times in one render pass.
|
|
19
|
+
*
|
|
20
|
+
* For now, this is a simple pass-through.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createMemo<T>(fn: () => T): Accessor<T>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createSignal = createSignal;
|
|
4
|
+
exports.createMemo = createMemo;
|
|
5
|
+
const Screen_1 = require("./Screen");
|
|
6
|
+
/**
|
|
7
|
+
* Creates a reactive signal.
|
|
8
|
+
* When the value updates, it automatically schedules a screen render.
|
|
9
|
+
* @param initialValue The initial value of the signal.
|
|
10
|
+
*/
|
|
11
|
+
function createSignal(initialValue) {
|
|
12
|
+
let value = initialValue;
|
|
13
|
+
const read = () => value;
|
|
14
|
+
const write = (newValue) => {
|
|
15
|
+
const nextValue = newValue instanceof Function ? newValue(value) : newValue;
|
|
16
|
+
if (value !== nextValue) {
|
|
17
|
+
value = nextValue;
|
|
18
|
+
Screen_1.Screen.scheduleRender();
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
return [read, write];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Creates a memoized value that caches its result and only re-calculates
|
|
25
|
+
* when the result of the function changes (polled during access).
|
|
26
|
+
*
|
|
27
|
+
* Note: A true dependency graph is out of scope. This implementation
|
|
28
|
+
* re-runs the function on every access but could store the last result
|
|
29
|
+
* if we had a way to know if dependencies changed.
|
|
30
|
+
*
|
|
31
|
+
* Since we don't track dependencies, we can't safely cache unless we know inputs didn't change.
|
|
32
|
+
* BUT, often `createMemo` is used to avoid expensive calcs if called multiple times in one render pass.
|
|
33
|
+
*
|
|
34
|
+
* For now, this is a simple pass-through.
|
|
35
|
+
*/
|
|
36
|
+
function createMemo(fn) {
|
|
37
|
+
// Without a dependency graph (tracking which signals are read inside fn),
|
|
38
|
+
// we cannot safely memoize across time because we don't know when to invalidate.
|
|
39
|
+
// We would need a global "context stack" like SolidJS/React.
|
|
40
|
+
// For this architectural step, we provide the API surface.
|
|
41
|
+
return () => fn();
|
|
42
|
+
}
|
package/dist/core/Styler.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/* eslint-disable no-control-regex */
|
|
2
3
|
// ==========================================
|
|
3
4
|
// CORE: ANSI & STYLING
|
|
4
5
|
// ==========================================
|
|
@@ -54,6 +55,7 @@ class Styler {
|
|
|
54
55
|
(code >= 0xff00 && code <= 0xff60) || // Fullwidth Forms
|
|
55
56
|
(code >= 0xffe0 && code <= 0xffe6) ||
|
|
56
57
|
(code >= 0x1f300 && code <= 0x1f64f) || // Misc Symbols and Pictographs
|
|
58
|
+
(code >= 0x1f680 && code <= 0x1f6ff) || // Transport and Map Symbols
|
|
57
59
|
(code >= 0x1f900 && code <= 0x1f9ff) // Supplemental Symbols and Pictographs
|
|
58
60
|
));
|
|
59
61
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from './core/Layout';
|
|
|
10
10
|
export * from './core/Focus';
|
|
11
11
|
export * from './core/Events';
|
|
12
12
|
export * from './core/Component';
|
|
13
|
+
export * from './core/Signal';
|
|
13
14
|
export * from './components/Box';
|
|
14
15
|
export * from './components/Menu';
|
|
15
16
|
export * from './components/ProgressBar';
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ __exportStar(require("./core/Layout"), exports);
|
|
|
26
26
|
__exportStar(require("./core/Focus"), exports);
|
|
27
27
|
__exportStar(require("./core/Events"), exports);
|
|
28
28
|
__exportStar(require("./core/Component"), exports);
|
|
29
|
+
__exportStar(require("./core/Signal"), exports);
|
|
29
30
|
__exportStar(require("./components/Box"), exports);
|
|
30
31
|
__exportStar(require("./components/Menu"), exports);
|
|
31
32
|
__exportStar(require("./components/ProgressBar"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "voonex",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "A zero-dependency Terminal UI Library for Node.js.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "CodeTease",
|
|
@@ -13,13 +13,21 @@
|
|
|
13
13
|
"types": "dist/index.d.ts",
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc",
|
|
16
|
-
"
|
|
16
|
+
"lint": "eslint src/**/*.ts",
|
|
17
|
+
"test": "jest",
|
|
17
18
|
"prepublishOnly": "npm run build"
|
|
18
19
|
},
|
|
19
20
|
"devDependencies": {
|
|
20
|
-
"@
|
|
21
|
-
"
|
|
22
|
-
"
|
|
21
|
+
"@eslint/js": "^9",
|
|
22
|
+
"@types/jest": "^30",
|
|
23
|
+
"@types/node": "^22",
|
|
24
|
+
"eslint": "^9",
|
|
25
|
+
"globals": "^17",
|
|
26
|
+
"jest": "^30",
|
|
27
|
+
"ts-jest": "^29",
|
|
28
|
+
"ts-node": "^10",
|
|
29
|
+
"typescript": "^5",
|
|
30
|
+
"typescript-eslint": "^8.53.1"
|
|
23
31
|
},
|
|
24
32
|
"files": [
|
|
25
33
|
"dist",
|