@zakmandhro/bunti 0.1.5 → 0.2.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.
- package/AGENTS.md +65 -0
- package/README.md +81 -11
- package/dist/cli.d.ts +44 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +147 -0
- package/dist/cli.js.map +1 -0
- package/dist/colors.d.ts +77 -7
- package/dist/colors.d.ts.map +1 -1
- package/dist/colors.js +238 -39
- package/dist/colors.js.map +1 -1
- package/dist/components/Box.d.ts +6 -0
- package/dist/components/Box.d.ts.map +1 -1
- package/dist/components/Box.js +5 -0
- package/dist/components/Box.js.map +1 -1
- package/dist/components/Button.d.ts +12 -2
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Button.js +35 -24
- package/dist/components/Button.js.map +1 -1
- package/dist/components/Card.d.ts +24 -2
- package/dist/components/Card.d.ts.map +1 -1
- package/dist/components/Card.js +56 -14
- package/dist/components/Card.js.map +1 -1
- package/dist/components/Header.d.ts +11 -1
- package/dist/components/Header.d.ts.map +1 -1
- package/dist/components/Header.js +12 -8
- package/dist/components/Header.js.map +1 -1
- package/dist/components/Input.d.ts +10 -1
- package/dist/components/Input.d.ts.map +1 -1
- package/dist/components/Input.js +172 -41
- package/dist/components/Input.js.map +1 -1
- package/dist/components/Link.d.ts +21 -0
- package/dist/components/Link.d.ts.map +1 -0
- package/dist/components/Link.js +29 -0
- package/dist/components/Link.js.map +1 -0
- package/dist/components/Modal.d.ts +25 -0
- package/dist/components/Modal.d.ts.map +1 -1
- package/dist/components/Modal.js +83 -24
- package/dist/components/Modal.js.map +1 -1
- package/dist/components/Progress.d.ts +27 -0
- package/dist/components/Progress.d.ts.map +1 -0
- package/dist/components/Progress.js +39 -0
- package/dist/components/Progress.js.map +1 -0
- package/dist/components/Spinner.d.ts +19 -0
- package/dist/components/Spinner.d.ts.map +1 -0
- package/dist/components/Spinner.js +27 -0
- package/dist/components/Spinner.js.map +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +3 -0
- package/dist/components/index.js.map +1 -1
- package/dist/data/nf-glyphs.d.ts +11 -0
- package/dist/data/nf-glyphs.d.ts.map +1 -0
- package/dist/data/nf-glyphs.js +535 -0
- package/dist/data/nf-glyphs.js.map +1 -0
- package/dist/data/nf-names.d.ts +8 -0
- package/dist/data/nf-names.d.ts.map +1 -0
- package/dist/data/nf-names.js +2 -0
- package/dist/data/nf-names.js.map +1 -0
- package/dist/demo-registry.d.ts +27 -0
- package/dist/demo-registry.d.ts.map +1 -0
- package/dist/demo-registry.js +57 -0
- package/dist/demo-registry.js.map +1 -0
- package/dist/demos/2048.ts +743 -0
- package/dist/demos/animation.ts +246 -0
- package/dist/demos/demo-layout.ts +124 -0
- package/dist/demos/engine.ts +374 -0
- package/dist/demos/interaction.ts +91 -0
- package/dist/demos/login.ts +203 -0
- package/dist/demos/mission-control.ts +558 -0
- package/dist/demos/showcase.ts +224 -0
- package/dist/detect.d.ts +92 -3
- package/dist/detect.d.ts.map +1 -1
- package/dist/detect.js +218 -40
- package/dist/detect.js.map +1 -1
- package/dist/diagnostics.d.ts +73 -0
- package/dist/diagnostics.d.ts.map +1 -0
- package/dist/diagnostics.js +225 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/dsl/context.d.ts +11 -0
- package/dist/dsl/context.d.ts.map +1 -0
- package/dist/dsl/context.js +563 -0
- package/dist/dsl/context.js.map +1 -0
- package/dist/dsl/hooks.d.ts +12 -0
- package/dist/dsl/hooks.d.ts.map +1 -0
- package/dist/dsl/hooks.js +148 -0
- package/dist/dsl/hooks.js.map +1 -0
- package/dist/dsl/interaction.d.ts +16 -0
- package/dist/dsl/interaction.d.ts.map +1 -0
- package/dist/dsl/interaction.js +101 -0
- package/dist/dsl/interaction.js.map +1 -0
- package/dist/dsl/layers.d.ts +14 -0
- package/dist/dsl/layers.d.ts.map +1 -0
- package/dist/dsl/layers.js +87 -0
- package/dist/dsl/layers.js.map +1 -0
- package/dist/dsl/motion.d.ts +10 -0
- package/dist/dsl/motion.d.ts.map +1 -0
- package/dist/dsl/motion.js +150 -0
- package/dist/dsl/motion.js.map +1 -0
- package/dist/dsl/render.d.ts +22 -0
- package/dist/dsl/render.d.ts.map +1 -0
- package/dist/dsl/render.js +69 -0
- package/dist/dsl/render.js.map +1 -0
- package/dist/dsl/types.d.ts +510 -0
- package/dist/dsl/types.d.ts.map +1 -0
- package/dist/dsl/types.js +33 -0
- package/dist/dsl/types.js.map +1 -0
- package/dist/dsl.d.ts +13 -154
- package/dist/dsl.d.ts.map +1 -1
- package/dist/dsl.js +12 -658
- package/dist/dsl.js.map +1 -1
- package/dist/easing.d.ts +41 -1
- package/dist/easing.d.ts.map +1 -1
- package/dist/easing.js +105 -2
- package/dist/easing.js.map +1 -1
- package/dist/geometry.d.ts +39 -0
- package/dist/geometry.d.ts.map +1 -1
- package/dist/geometry.js +20 -0
- package/dist/geometry.js.map +1 -1
- package/dist/icons-full.d.ts +5 -0
- package/dist/icons-full.d.ts.map +1 -0
- package/dist/icons-full.js +31 -0
- package/dist/icons-full.js.map +1 -0
- package/dist/icons.d.ts +426 -5
- package/dist/icons.d.ts.map +1 -1
- package/dist/icons.js +144 -15
- package/dist/icons.js.map +1 -1
- package/dist/index.d.ts +93 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/dist/input.d.ts +177 -0
- package/dist/input.d.ts.map +1 -0
- package/dist/input.js +533 -0
- package/dist/input.js.map +1 -0
- package/dist/layout.d.ts +111 -6
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +163 -23
- package/dist/layout.js.map +1 -1
- package/dist/render.d.ts +33 -1
- package/dist/render.d.ts.map +1 -1
- package/dist/render.js +431 -116
- package/dist/render.js.map +1 -1
- package/dist/state.d.ts +137 -3
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +25 -0
- package/dist/state.js.map +1 -1
- package/dist/theme.d.ts +129 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/theme.js +167 -0
- package/dist/theme.js.map +1 -0
- package/dist/themes/catppuccin-mocha.d.ts +19 -0
- package/dist/themes/catppuccin-mocha.d.ts.map +1 -0
- package/dist/themes/catppuccin-mocha.js +36 -0
- package/dist/themes/catppuccin-mocha.js.map +1 -0
- package/dist/themes/dracula.d.ts +21 -0
- package/dist/themes/dracula.d.ts.map +1 -0
- package/dist/themes/dracula.js +37 -0
- package/dist/themes/dracula.js.map +1 -0
- package/dist/themes/github-light.d.ts +21 -0
- package/dist/themes/github-light.d.ts.map +1 -0
- package/dist/themes/github-light.js +36 -0
- package/dist/themes/github-light.js.map +1 -0
- package/dist/themes/index.d.ts +19 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +17 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/themes/nord.d.ts +23 -0
- package/dist/themes/nord.d.ts.map +1 -0
- package/dist/themes/nord.js +38 -0
- package/dist/themes/nord.js.map +1 -0
- package/dist/themes/one-dark-pro.d.ts +21 -0
- package/dist/themes/one-dark-pro.d.ts.map +1 -0
- package/dist/themes/one-dark-pro.js +36 -0
- package/dist/themes/one-dark-pro.js.map +1 -0
- package/dist/themes/tokyo-night.d.ts +20 -0
- package/dist/themes/tokyo-night.d.ts.map +1 -0
- package/dist/themes/tokyo-night.js +37 -0
- package/dist/themes/tokyo-night.js.map +1 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -0
- package/dist/utils.js.map +1 -1
- package/dist/vendor/colors.d.ts +45 -0
- package/dist/vendor/colors.d.ts.map +1 -0
- package/dist/vendor/colors.js +72 -0
- package/dist/vendor/colors.js.map +1 -0
- package/llms.txt +79 -0
- package/package.json +23 -7
- package/dist/data/glyphs.d.ts +0 -5
- package/dist/data/glyphs.d.ts.map +0 -1
- package/dist/data/glyphs.js +0 -29
- package/dist/data/glyphs.js.map +0 -1
package/dist/dsl.js
CHANGED
|
@@ -1,663 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bunti High-Level Contextual DSL
|
|
3
3
|
* Scoped closure API with contextual capabilities via trait-based composition.
|
|
4
|
+
*
|
|
5
|
+
* Implementation lives in src/dsl/ — this barrel preserves the public surface:
|
|
6
|
+
* - context: createDSLContext + box rendering + createScreenContext
|
|
7
|
+
* - render: the render() entry point
|
|
8
|
+
* - hooks: useState / useAsync / usePersistentState
|
|
9
|
+
* - interaction: hitbox / hover / press / click + focus management
|
|
10
|
+
* - layers: transparent buffers + compositing
|
|
11
|
+
* - motion: animate / typewriter / flicker / fade
|
|
12
|
+
* - types: shared interfaces + KEYS
|
|
4
13
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import { bg, createGradient, darken, fade, fg, lighten, rgb, } from './colors';
|
|
9
|
-
import { resolveRect as resolveGeometryRect, resolvePlacedRect, splitRect as splitGeometryRect, } from './geometry';
|
|
10
|
-
import { icon, init, replaceEmojis } from './icons';
|
|
11
|
-
import { joinHorizontal, joinVertical, blit as layoutBlit, box as layoutBox, gradient as layoutGradient, list as layoutList, table as layoutTable, viewport as layoutViewport, wallpaper as layoutWallpaper, rect, } from './layout';
|
|
12
|
-
import { flush, loop } from './render';
|
|
13
|
-
import { clearBackBuffer, createScreenState, } from './state';
|
|
14
|
-
import { visibleWidth } from './utils';
|
|
15
|
-
// --- Traits (Contextual Capabilities) ---
|
|
16
|
-
export const KEYS = {
|
|
17
|
-
UP: 'up',
|
|
18
|
-
DOWN: 'down',
|
|
19
|
-
RIGHT: 'right',
|
|
20
|
-
LEFT: 'left',
|
|
21
|
-
ENTER: 'enter',
|
|
22
|
-
ESCAPE: 'escape',
|
|
23
|
-
TAB: 'tab',
|
|
24
|
-
BACKSPACE: 'backspace',
|
|
25
|
-
SPACE: ' ',
|
|
26
|
-
};
|
|
27
|
-
function boxBorderInset(options) {
|
|
28
|
-
return options.border === 'none' || !options.border ? 0 : 1;
|
|
29
|
-
}
|
|
30
|
-
function resolveBoxArea(parent, options, contentWidth, contentHeight) {
|
|
31
|
-
return resolvePlacedRect(parent, {
|
|
32
|
-
x: options.x,
|
|
33
|
-
y: options.y,
|
|
34
|
-
width: options.anchor === 'top' || options.anchor === 'bottom'
|
|
35
|
-
? parent.width
|
|
36
|
-
: options.width,
|
|
37
|
-
height: options.height,
|
|
38
|
-
anchor: options.anchor,
|
|
39
|
-
contentWidth,
|
|
40
|
-
contentHeight,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
function resolveBoxInnerArea(area, options) {
|
|
44
|
-
const borderInset = boxBorderInset(options);
|
|
45
|
-
const px = options.padding?.[1] ?? 0;
|
|
46
|
-
const py = options.padding?.[0] ?? 0;
|
|
47
|
-
return {
|
|
48
|
-
x: area.x + borderInset + px,
|
|
49
|
-
y: area.y + borderInset + py,
|
|
50
|
-
width: Math.max(0, area.width - borderInset * 2 - px * 2),
|
|
51
|
-
height: Math.max(0, area.height - borderInset * 2 - py * 2),
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
function createTransparentBuffer(size) {
|
|
55
|
-
return Array.from({ length: size }, () => ({
|
|
56
|
-
char: ' ',
|
|
57
|
-
fg: undefined,
|
|
58
|
-
bg: undefined,
|
|
59
|
-
fgCode: undefined,
|
|
60
|
-
bgCode: undefined,
|
|
61
|
-
bold: false,
|
|
62
|
-
skip: true,
|
|
63
|
-
}));
|
|
64
|
-
}
|
|
65
|
-
function createLayerScreenState(state) {
|
|
66
|
-
const size = state.width * state.height;
|
|
67
|
-
return {
|
|
68
|
-
...state,
|
|
69
|
-
frontBuffer: createTransparentBuffer(size),
|
|
70
|
-
backBuffer: createTransparentBuffer(size),
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
function compositeLayer(state, buffer) {
|
|
74
|
-
for (let i = 0; i < buffer.length; i++) {
|
|
75
|
-
const source = buffer[i];
|
|
76
|
-
if (source.skip)
|
|
77
|
-
continue;
|
|
78
|
-
const target = state.backBuffer[i];
|
|
79
|
-
target.char = source.char;
|
|
80
|
-
target.fg = source.fg;
|
|
81
|
-
target.bg = source.bg;
|
|
82
|
-
target.bold = source.bold;
|
|
83
|
-
target.fgCode = source.fgCode;
|
|
84
|
-
target.bgCode = source.bgCode;
|
|
85
|
-
target.skip = source.skip;
|
|
86
|
-
target.raw = source.raw;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
function graphemes(text) {
|
|
90
|
-
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
|
|
91
|
-
return Array.from(segmenter.segment(text), ({ segment }) => segment);
|
|
92
|
-
}
|
|
93
|
-
const buntiColor = {
|
|
94
|
-
...pc,
|
|
95
|
-
darken,
|
|
96
|
-
lighten,
|
|
97
|
-
rgb,
|
|
98
|
-
fg,
|
|
99
|
-
bg,
|
|
100
|
-
dim: (text) => fg({ r: 96, g: 102, b: 118 }, text),
|
|
101
|
-
};
|
|
102
|
-
/**
|
|
103
|
-
* Common Context Factory: Provided to every closure.
|
|
104
|
-
*/
|
|
105
|
-
function createDSLContext(state, dslState, availableW, availableH, offsetX = 0, offsetY = 0, isRoot = false) {
|
|
106
|
-
const ctx = {
|
|
107
|
-
color: buntiColor,
|
|
108
|
-
state,
|
|
109
|
-
width: availableW,
|
|
110
|
-
height: availableH,
|
|
111
|
-
area: { x: offsetX, y: offsetY, width: availableW, height: availableH },
|
|
112
|
-
isRoot,
|
|
113
|
-
offsetX,
|
|
114
|
-
offsetY,
|
|
115
|
-
get cursorX() {
|
|
116
|
-
const currentFlow = dslState.activeContents.join('');
|
|
117
|
-
const lines = currentFlow.split('\n');
|
|
118
|
-
return visibleWidth(lines[lines.length - 1]);
|
|
119
|
-
},
|
|
120
|
-
get cursorY() {
|
|
121
|
-
const currentFlow = dslState.activeContents.join('');
|
|
122
|
-
return Math.max(0, currentFlow.split('\n').length - 1);
|
|
123
|
-
},
|
|
124
|
-
mouseX: state.mouseX,
|
|
125
|
-
mouseY: state.mouseY,
|
|
126
|
-
mouseButton: state.mouseButton,
|
|
127
|
-
isMouseDown: state.isMouseDown,
|
|
128
|
-
lastKey: state.lastKey,
|
|
129
|
-
focusedId: state.focusedId,
|
|
130
|
-
elapsedTime: Date.now() - state.startTime,
|
|
131
|
-
text(str) {
|
|
132
|
-
dslState.activeContents.push(replaceEmojis(String(str)));
|
|
133
|
-
return ctx;
|
|
134
|
-
},
|
|
135
|
-
animate(duration, options = {}) {
|
|
136
|
-
const now = Date.now();
|
|
137
|
-
const start = options.id
|
|
138
|
-
? ctx.useState(`${options.id}_start`, now)[0]
|
|
139
|
-
: state.startTime;
|
|
140
|
-
const elapsed = now - start - (options.delay || 0);
|
|
141
|
-
if (elapsed < 0)
|
|
142
|
-
return 0;
|
|
143
|
-
if (options.loop)
|
|
144
|
-
return (elapsed % duration) / duration;
|
|
145
|
-
return Math.min(1, elapsed / duration);
|
|
146
|
-
},
|
|
147
|
-
fade,
|
|
148
|
-
typewriter(text, options = {}) {
|
|
149
|
-
const now = Date.now();
|
|
150
|
-
const start = options.id
|
|
151
|
-
? ctx.useState(`${options.id}_start`, now)[0]
|
|
152
|
-
: state.startTime;
|
|
153
|
-
const delay = options.delay ?? 0;
|
|
154
|
-
const elapsed = Math.max(0, now - start - delay);
|
|
155
|
-
const cps = options.cps ?? 24;
|
|
156
|
-
const chars = graphemes(text);
|
|
157
|
-
const rawIndex = Math.floor((elapsed / 1000) * cps);
|
|
158
|
-
const index = options.loop && chars.length > 0
|
|
159
|
-
? rawIndex % (chars.length + 1)
|
|
160
|
-
: Math.min(chars.length, rawIndex);
|
|
161
|
-
const blinkRate = options.blinkRate ?? 450;
|
|
162
|
-
const showCursor = options.blink === false ||
|
|
163
|
-
Math.floor(elapsed / blinkRate) % 2 === 0 ||
|
|
164
|
-
index < chars.length;
|
|
165
|
-
return {
|
|
166
|
-
text: chars.slice(0, index).join(''),
|
|
167
|
-
cursor: showCursor ? (options.cursor ?? '█') : ' ',
|
|
168
|
-
done: index >= chars.length,
|
|
169
|
-
index,
|
|
170
|
-
progress: chars.length === 0 ? 1 : index / chars.length,
|
|
171
|
-
};
|
|
172
|
-
},
|
|
173
|
-
flicker(intensity = 0.5) {
|
|
174
|
-
return Math.random() > 1 - intensity;
|
|
175
|
-
},
|
|
176
|
-
useAsync(keyOrFetcher, fetcherOrOptions, maybeOptions) {
|
|
177
|
-
let key;
|
|
178
|
-
let fetcher;
|
|
179
|
-
let options;
|
|
180
|
-
if (typeof keyOrFetcher === 'string') {
|
|
181
|
-
key = keyOrFetcher;
|
|
182
|
-
fetcher = fetcherOrOptions;
|
|
183
|
-
options = maybeOptions;
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
if (state.hookCounter === undefined) {
|
|
187
|
-
state.hookCounter = 0;
|
|
188
|
-
}
|
|
189
|
-
const index = state.hookCounter++;
|
|
190
|
-
key = `_async_hook_${index}`;
|
|
191
|
-
fetcher = keyOrFetcher;
|
|
192
|
-
options = fetcherOrOptions;
|
|
193
|
-
}
|
|
194
|
-
const interval = options?.interval ?? 0;
|
|
195
|
-
const dataKey = `${key}_data`;
|
|
196
|
-
const loadingKey = `${key}_loading`;
|
|
197
|
-
const errorKey = `${key}_error`;
|
|
198
|
-
const lastFetchKey = `${key}_lastFetch`;
|
|
199
|
-
const fetchingKey = `${key}_fetching`;
|
|
200
|
-
if (!state.componentState.has(loadingKey)) {
|
|
201
|
-
state.componentState.set(loadingKey, true);
|
|
202
|
-
}
|
|
203
|
-
const lastFetch = state.componentState.get(lastFetchKey);
|
|
204
|
-
const isFetching = state.componentState.get(fetchingKey);
|
|
205
|
-
const now = Date.now();
|
|
206
|
-
const shouldFetch = !isFetching &&
|
|
207
|
-
(lastFetch === undefined ||
|
|
208
|
-
(interval > 0 && now - lastFetch >= interval));
|
|
209
|
-
if (shouldFetch) {
|
|
210
|
-
state.componentState.set(fetchingKey, true);
|
|
211
|
-
state.componentState.set(lastFetchKey, now);
|
|
212
|
-
fetcher()
|
|
213
|
-
.then((result) => {
|
|
214
|
-
state.componentState.set(dataKey, result);
|
|
215
|
-
state.componentState.set(loadingKey, false);
|
|
216
|
-
state.componentState.set(errorKey, undefined);
|
|
217
|
-
state.requestTick?.();
|
|
218
|
-
})
|
|
219
|
-
.catch((err) => {
|
|
220
|
-
state.componentState.set(errorKey, err);
|
|
221
|
-
state.componentState.set(loadingKey, false);
|
|
222
|
-
state.requestTick?.();
|
|
223
|
-
})
|
|
224
|
-
.finally(() => {
|
|
225
|
-
state.componentState.set(fetchingKey, false);
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
return {
|
|
229
|
-
data: state.componentState.get(dataKey),
|
|
230
|
-
loading: state.componentState.get(loadingKey),
|
|
231
|
-
error: state.componentState.get(errorKey),
|
|
232
|
-
};
|
|
233
|
-
},
|
|
234
|
-
useState(keyOrInitial, maybeInitial) {
|
|
235
|
-
let key;
|
|
236
|
-
let initial;
|
|
237
|
-
if (maybeInitial === undefined) {
|
|
238
|
-
if (state.hookCounter === undefined) {
|
|
239
|
-
state.hookCounter = 0;
|
|
240
|
-
}
|
|
241
|
-
const index = state.hookCounter++;
|
|
242
|
-
key = `_state_hook_${index}`;
|
|
243
|
-
initial = keyOrInitial;
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
key = keyOrInitial;
|
|
247
|
-
initial = maybeInitial;
|
|
248
|
-
}
|
|
249
|
-
if (!state.componentState.has(key)) {
|
|
250
|
-
state.componentState.set(key, initial);
|
|
251
|
-
}
|
|
252
|
-
return [
|
|
253
|
-
state.componentState.get(key),
|
|
254
|
-
(val) => {
|
|
255
|
-
state.componentState.set(key, val);
|
|
256
|
-
state.requestTick?.();
|
|
257
|
-
},
|
|
258
|
-
];
|
|
259
|
-
},
|
|
260
|
-
usePersistentState(key, initial) {
|
|
261
|
-
if (!state.componentState.has(key)) {
|
|
262
|
-
const storeDir = join(process.cwd(), '.tmp');
|
|
263
|
-
const storeFile = join(storeDir, 'bunti_store.json');
|
|
264
|
-
let fileVal;
|
|
265
|
-
try {
|
|
266
|
-
if (existsSync(storeFile)) {
|
|
267
|
-
const data = readFileSync(storeFile, 'utf-8');
|
|
268
|
-
const parsed = JSON.parse(data);
|
|
269
|
-
fileVal = parsed[key];
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
catch {
|
|
273
|
-
// Ignore
|
|
274
|
-
}
|
|
275
|
-
const finalVal = fileVal !== undefined ? fileVal : initial;
|
|
276
|
-
state.componentState.set(key, finalVal);
|
|
277
|
-
}
|
|
278
|
-
return [
|
|
279
|
-
state.componentState.get(key),
|
|
280
|
-
(val) => {
|
|
281
|
-
state.componentState.set(key, val);
|
|
282
|
-
const storeDir = join(process.cwd(), '.tmp');
|
|
283
|
-
const storeFile = join(storeDir, 'bunti_store.json');
|
|
284
|
-
try {
|
|
285
|
-
if (!existsSync(storeDir)) {
|
|
286
|
-
mkdirSync(storeDir, { recursive: true });
|
|
287
|
-
}
|
|
288
|
-
let currentStore = {};
|
|
289
|
-
if (existsSync(storeFile)) {
|
|
290
|
-
try {
|
|
291
|
-
currentStore = JSON.parse(readFileSync(storeFile, 'utf-8'));
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
currentStore = {};
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
currentStore[key] = val;
|
|
298
|
-
writeFileSync(storeFile, JSON.stringify(currentStore, null, 2), 'utf-8');
|
|
299
|
-
}
|
|
300
|
-
catch {
|
|
301
|
-
// Ignore
|
|
302
|
-
}
|
|
303
|
-
state.requestTick?.();
|
|
304
|
-
},
|
|
305
|
-
];
|
|
306
|
-
},
|
|
307
|
-
focusable(id) {
|
|
308
|
-
if (!state.focusableIds.includes(id)) {
|
|
309
|
-
state.focusableIds.push(id);
|
|
310
|
-
}
|
|
311
|
-
if (!state.focusedId)
|
|
312
|
-
state.focusedId = id;
|
|
313
|
-
return state.focusedId === id;
|
|
314
|
-
},
|
|
315
|
-
isFocused(id) {
|
|
316
|
-
return state.focusedId === id;
|
|
317
|
-
},
|
|
318
|
-
focus(id) {
|
|
319
|
-
state.focusedId = id;
|
|
320
|
-
},
|
|
321
|
-
focusNext() {
|
|
322
|
-
if (state.focusableIds.length === 0)
|
|
323
|
-
return;
|
|
324
|
-
const idx = state.focusableIds.indexOf(state.focusedId || '');
|
|
325
|
-
const nextIdx = (idx + 1) % state.focusableIds.length;
|
|
326
|
-
state.focusedId = state.focusableIds[nextIdx];
|
|
327
|
-
},
|
|
328
|
-
hitbox(id, bounds) {
|
|
329
|
-
const rect = ctx.resolveRect(bounds);
|
|
330
|
-
const box = { id, ...rect };
|
|
331
|
-
state.hitboxes.set(id, box);
|
|
332
|
-
const hovered = state.mouseX >= box.x &&
|
|
333
|
-
state.mouseX < box.x + box.width &&
|
|
334
|
-
state.mouseY >= box.y &&
|
|
335
|
-
state.mouseY < box.y + box.height;
|
|
336
|
-
const pressed = hovered && state.isMouseDown && state.mouseButton === 0;
|
|
337
|
-
const clicked = hovered && state.lastKey === 'click';
|
|
338
|
-
return { box, hovered, pressed, clicked };
|
|
339
|
-
},
|
|
340
|
-
resolveRect(bounds) {
|
|
341
|
-
return resolveGeometryRect({ x: offsetX, y: offsetY, width: availableW, height: availableH }, {
|
|
342
|
-
...bounds,
|
|
343
|
-
y: bounds.y ?? ctx.cursorY,
|
|
344
|
-
});
|
|
345
|
-
},
|
|
346
|
-
resolveLocalRect(bounds, options) {
|
|
347
|
-
return resolvePlacedRect({ x: 0, y: 0, width: availableW, height: availableH }, bounds, options);
|
|
348
|
-
},
|
|
349
|
-
split(options) {
|
|
350
|
-
return splitGeometryRect({ x: 0, y: 0, width: availableW, height: availableH }, options);
|
|
351
|
-
},
|
|
352
|
-
isHovered(id) {
|
|
353
|
-
const box = state.hitboxes.get(id);
|
|
354
|
-
if (!box)
|
|
355
|
-
return false;
|
|
356
|
-
return (state.mouseX >= box.x &&
|
|
357
|
-
state.mouseX < box.x + box.width &&
|
|
358
|
-
state.mouseY >= box.y &&
|
|
359
|
-
state.mouseY < box.y + box.height);
|
|
360
|
-
},
|
|
361
|
-
isPressed(id) {
|
|
362
|
-
return ctx.isHovered(id) && state.isMouseDown && state.mouseButton === 0;
|
|
363
|
-
},
|
|
364
|
-
isClicked(id) {
|
|
365
|
-
return ctx.isHovered(id) && state.lastKey === 'click';
|
|
366
|
-
},
|
|
367
|
-
list(id, items, options = {}) {
|
|
368
|
-
const [selectedIndex, setSelectedIndex] = ctx.useState(`${id}_index`, 0);
|
|
369
|
-
const isFocused = ctx.focusable(id);
|
|
370
|
-
if (isFocused && options.interactive !== false) {
|
|
371
|
-
if (state.lastKey === KEYS.UP)
|
|
372
|
-
setSelectedIndex(Math.max(0, selectedIndex - 1));
|
|
373
|
-
if (state.lastKey === KEYS.DOWN)
|
|
374
|
-
setSelectedIndex(Math.min(items.length - 1, selectedIndex + 1));
|
|
375
|
-
}
|
|
376
|
-
const content = layoutList(items, {
|
|
377
|
-
...options,
|
|
378
|
-
focusedIndex: selectedIndex,
|
|
379
|
-
focusStyle: isFocused ? options.focusStyle : (s) => pc.dim(s),
|
|
380
|
-
selectedBg: isFocused ? options.selectedBg : undefined,
|
|
381
|
-
}, availableW);
|
|
382
|
-
dslState.activeContents.push(content);
|
|
383
|
-
return ctx;
|
|
384
|
-
},
|
|
385
|
-
table(rows, options = {}) {
|
|
386
|
-
const content = layoutTable(rows, options, availableW);
|
|
387
|
-
dslState.activeContents.push(content);
|
|
388
|
-
return ctx;
|
|
389
|
-
},
|
|
390
|
-
icon(name) {
|
|
391
|
-
return icon(name);
|
|
392
|
-
},
|
|
393
|
-
blit(x, y, content, style = {}) {
|
|
394
|
-
layoutBlit(state, x, y, content, style);
|
|
395
|
-
return ctx;
|
|
396
|
-
},
|
|
397
|
-
rect(x, y, w, h, style) {
|
|
398
|
-
rect(state, x, y, w, h, style);
|
|
399
|
-
return ctx;
|
|
400
|
-
},
|
|
401
|
-
viewport(content, width, height, scrollY = 0) {
|
|
402
|
-
return layoutViewport(content, width, height, scrollY);
|
|
403
|
-
},
|
|
404
|
-
span(options, callback) {
|
|
405
|
-
const subContents = [];
|
|
406
|
-
dslState.stack.push(dslState.activeContents);
|
|
407
|
-
dslState.activeContents = subContents;
|
|
408
|
-
callback(ctx);
|
|
409
|
-
dslState.activeContents = dslState.stack.pop();
|
|
410
|
-
const combined = subContents.join('');
|
|
411
|
-
let styled = combined;
|
|
412
|
-
if (typeof options.color === 'function') {
|
|
413
|
-
styled = options.color(combined);
|
|
414
|
-
}
|
|
415
|
-
else if (options.color !== undefined) {
|
|
416
|
-
styled = fg(options.color, combined);
|
|
417
|
-
}
|
|
418
|
-
dslState.activeContents.push(styled);
|
|
419
|
-
return styled;
|
|
420
|
-
},
|
|
421
|
-
box(options, callback) {
|
|
422
|
-
if (options.zIndex !== undefined) {
|
|
423
|
-
const { zIndex, ...layeredOptions } = options;
|
|
424
|
-
ctx.layer(zIndex, (layerCtx) => {
|
|
425
|
-
layerCtx.box(layeredOptions, callback);
|
|
426
|
-
});
|
|
427
|
-
return '';
|
|
428
|
-
}
|
|
429
|
-
const preContentW = options.width === undefined ? availableW : 0;
|
|
430
|
-
const preContentH = options.height === undefined ? availableH : 0;
|
|
431
|
-
const preArea = resolveBoxArea(ctx.area, options, preContentW, preContentH);
|
|
432
|
-
const innerArea = resolveBoxInnerArea(preArea, options);
|
|
433
|
-
const subContents = [];
|
|
434
|
-
dslState.stack.push(dslState.activeContents);
|
|
435
|
-
dslState.activeContents = subContents;
|
|
436
|
-
const subCtx = createDSLContext(state, dslState, innerArea.width, innerArea.height, innerArea.x, innerArea.y);
|
|
437
|
-
callback(subCtx);
|
|
438
|
-
dslState.activeContents = dslState.stack.pop();
|
|
439
|
-
const innerContent = subContents.join('');
|
|
440
|
-
const styledBox = layoutBox(innerContent, options, availableW, availableH);
|
|
441
|
-
if (!options.detach) {
|
|
442
|
-
dslState.activeContents.push(styledBox);
|
|
443
|
-
}
|
|
444
|
-
return styledBox;
|
|
445
|
-
},
|
|
446
|
-
layer(zIndexOrOptions, maybeCallback) {
|
|
447
|
-
const callback = typeof zIndexOrOptions === 'function' ? zIndexOrOptions : maybeCallback;
|
|
448
|
-
if (!callback)
|
|
449
|
-
return ctx;
|
|
450
|
-
const zIndex = typeof zIndexOrOptions === 'number'
|
|
451
|
-
? zIndexOrOptions
|
|
452
|
-
: typeof zIndexOrOptions === 'object'
|
|
453
|
-
? (zIndexOrOptions.zIndex ?? 0)
|
|
454
|
-
: 0;
|
|
455
|
-
const layerState = createLayerScreenState(state);
|
|
456
|
-
const layerDslState = {
|
|
457
|
-
activeContents: [],
|
|
458
|
-
stack: [],
|
|
459
|
-
layers: dslState.layers,
|
|
460
|
-
layerOrder: dslState.layerOrder,
|
|
461
|
-
};
|
|
462
|
-
const layerCtx = createDSLContext(layerState, layerDslState, availableW, availableH, offsetX, offsetY, isRoot);
|
|
463
|
-
layerCtx.box = createDirectBoxRenderer(layerState, layerDslState, layerCtx);
|
|
464
|
-
callback(layerCtx);
|
|
465
|
-
const flow = layerDslState.activeContents.join('');
|
|
466
|
-
if (flow)
|
|
467
|
-
layoutBlit(layerState, offsetX, offsetY, flow);
|
|
468
|
-
dslState.layerOrder = layerDslState.layerOrder + 1;
|
|
469
|
-
dslState.layers.push({
|
|
470
|
-
zIndex,
|
|
471
|
-
order: layerDslState.layerOrder,
|
|
472
|
-
buffer: layerState.backBuffer,
|
|
473
|
-
});
|
|
474
|
-
return ctx;
|
|
475
|
-
},
|
|
476
|
-
joinHorizontal(...blocks) {
|
|
477
|
-
return joinHorizontal(...blocks);
|
|
478
|
-
},
|
|
479
|
-
joinVertical(...blocks) {
|
|
480
|
-
return joinVertical(...blocks);
|
|
481
|
-
},
|
|
482
|
-
wallpaper(input) {
|
|
483
|
-
if (typeof input === 'object' && 'colors' in input) {
|
|
484
|
-
layoutGradient(state, input.colors, { direction: input.direction });
|
|
485
|
-
}
|
|
486
|
-
else if (Array.isArray(input)) {
|
|
487
|
-
layoutGradient(state, input);
|
|
488
|
-
}
|
|
489
|
-
else if (typeof input === 'object' && 'color' in input) {
|
|
490
|
-
ctx.wallpaper(input.color);
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
layoutWallpaper(state, { bg: input });
|
|
494
|
-
}
|
|
495
|
-
},
|
|
496
|
-
gradient: (opts) => ({
|
|
497
|
-
colors: createGradient(opts.colors, opts.steps || 10),
|
|
498
|
-
direction: opts.direction || 'vertical',
|
|
499
|
-
steps: opts.steps || 10,
|
|
500
|
-
}),
|
|
501
|
-
rgb,
|
|
502
|
-
requestStop() {
|
|
503
|
-
state.requestStop?.();
|
|
504
|
-
},
|
|
505
|
-
flushFlow() { },
|
|
506
|
-
};
|
|
507
|
-
return ctx;
|
|
508
|
-
}
|
|
509
|
-
function createDirectBoxRenderer(state, dslState, base) {
|
|
510
|
-
return (options, callback) => {
|
|
511
|
-
if (options.zIndex !== undefined) {
|
|
512
|
-
const { zIndex, ...layeredOptions } = options;
|
|
513
|
-
base.layer(zIndex, (layerCtx) => {
|
|
514
|
-
layerCtx.box(layeredOptions, callback);
|
|
515
|
-
});
|
|
516
|
-
return '';
|
|
517
|
-
}
|
|
518
|
-
const boxOptions = options.anchor === 'top' || options.anchor === 'bottom'
|
|
519
|
-
? { ...options, width: base.width }
|
|
520
|
-
: options;
|
|
521
|
-
const hasFixedSize = boxOptions.width !== undefined &&
|
|
522
|
-
boxOptions.width !== 'auto' &&
|
|
523
|
-
boxOptions.height !== undefined &&
|
|
524
|
-
boxOptions.height !== 'auto';
|
|
525
|
-
if (hasFixedSize) {
|
|
526
|
-
const preArea = resolveBoxArea(base.area, boxOptions, 0, 0);
|
|
527
|
-
const innerArea = resolveBoxInnerArea(preArea, boxOptions);
|
|
528
|
-
if (boxOptions.bgColor || boxOptions.color) {
|
|
529
|
-
rect(state, preArea.x, preArea.y, preArea.width, preArea.height, {
|
|
530
|
-
char: ' ',
|
|
531
|
-
bg: boxOptions.bgColor,
|
|
532
|
-
fg: boxOptions.color === 'blank' ? undefined : boxOptions.color,
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
const styledBox = layoutBox('', boxOptions, base.width, base.height);
|
|
536
|
-
layoutBlit(state, preArea.x, preArea.y, styledBox);
|
|
537
|
-
const subContents = [];
|
|
538
|
-
dslState.stack.push(dslState.activeContents);
|
|
539
|
-
dslState.activeContents = subContents;
|
|
540
|
-
const subCtx = createDSLContext(state, dslState, innerArea.width, innerArea.height, innerArea.x, innerArea.y);
|
|
541
|
-
callback(subCtx);
|
|
542
|
-
dslState.activeContents = dslState.stack.pop();
|
|
543
|
-
const contentStr = subContents.join('');
|
|
544
|
-
if (contentStr) {
|
|
545
|
-
const styledContent = layoutBox(contentStr, {
|
|
546
|
-
width: innerArea.width,
|
|
547
|
-
height: innerArea.height,
|
|
548
|
-
align: boxOptions.align,
|
|
549
|
-
valign: boxOptions.valign,
|
|
550
|
-
wrap: boxOptions.wrap,
|
|
551
|
-
border: 'none',
|
|
552
|
-
padding: [0, 0],
|
|
553
|
-
}, innerArea.width, innerArea.height);
|
|
554
|
-
layoutBlit(state, innerArea.x, innerArea.y, styledContent);
|
|
555
|
-
}
|
|
556
|
-
return styledBox;
|
|
557
|
-
}
|
|
558
|
-
const subContents = [];
|
|
559
|
-
dslState.stack.push(dslState.activeContents);
|
|
560
|
-
dslState.activeContents = subContents;
|
|
561
|
-
const preAreaTemp = resolveBoxArea(base.area, boxOptions, base.width, base.height);
|
|
562
|
-
const innerAreaTemp = resolveBoxInnerArea(preAreaTemp, boxOptions);
|
|
563
|
-
const subCtx = createDSLContext(state, dslState, innerAreaTemp.width, innerAreaTemp.height, innerAreaTemp.x, innerAreaTemp.y);
|
|
564
|
-
callback(subCtx);
|
|
565
|
-
dslState.activeContents = dslState.stack.pop();
|
|
566
|
-
const contentStr = subContents.join('');
|
|
567
|
-
const styledBox = layoutBox(contentStr, boxOptions, base.width, base.height);
|
|
568
|
-
const lines = styledBox.split('\n');
|
|
569
|
-
const lineWidths = lines.map(visibleWidth);
|
|
570
|
-
const boxArea = resolveBoxArea(base.area, boxOptions, lineWidths.length > 0 ? Math.max(...lineWidths) : 0, lines.length);
|
|
571
|
-
if (boxOptions.bgColor || boxOptions.color) {
|
|
572
|
-
rect(state, boxArea.x, boxArea.y, boxArea.width, boxArea.height, {
|
|
573
|
-
char: ' ',
|
|
574
|
-
bg: boxOptions.bgColor,
|
|
575
|
-
fg: boxOptions.color === 'blank' ? undefined : boxOptions.color,
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
layoutBlit(state, boxArea.x, boxArea.y, styledBox);
|
|
579
|
-
return styledBox;
|
|
580
|
-
};
|
|
581
|
-
}
|
|
582
|
-
/**
|
|
583
|
-
* Top-level Screen Context
|
|
584
|
-
*/
|
|
585
|
-
export function createScreenContext(state) {
|
|
586
|
-
state.hookCounter = 0;
|
|
587
|
-
const previousFocusableIds = state.focusableIds;
|
|
588
|
-
if (state.lastKey === KEYS.TAB && previousFocusableIds.length > 0) {
|
|
589
|
-
const idx = previousFocusableIds.indexOf(state.focusedId || '');
|
|
590
|
-
const nextIdx = (idx + 1) % previousFocusableIds.length;
|
|
591
|
-
state.focusedId = previousFocusableIds[nextIdx];
|
|
592
|
-
}
|
|
593
|
-
state.focusableIds = []; // Rebuild from this frame's rendered focusables.
|
|
594
|
-
state.hitboxes = new Map(); // Rebuild from this frame's interactive geometry.
|
|
595
|
-
const dslState = {
|
|
596
|
-
activeContents: [],
|
|
597
|
-
stack: [],
|
|
598
|
-
layers: [],
|
|
599
|
-
layerOrder: 0,
|
|
600
|
-
};
|
|
601
|
-
const base = createDSLContext(state, dslState, state.width, state.height, 0, 0, true);
|
|
602
|
-
const flushFlow = () => {
|
|
603
|
-
const flow = dslState.activeContents.join('');
|
|
604
|
-
if (flow)
|
|
605
|
-
layoutBlit(state, 0, 0, flow);
|
|
606
|
-
dslState.layers
|
|
607
|
-
.sort((a, b) => a.zIndex - b.zIndex || a.order - b.order)
|
|
608
|
-
.forEach((layer) => {
|
|
609
|
-
compositeLayer(state, layer.buffer);
|
|
610
|
-
});
|
|
611
|
-
};
|
|
612
|
-
const boxOverride = createDirectBoxRenderer(state, dslState, base);
|
|
613
|
-
return {
|
|
614
|
-
...base,
|
|
615
|
-
box: boxOverride,
|
|
616
|
-
flushFlow,
|
|
617
|
-
requestStop: () => {
|
|
618
|
-
state.requestStop?.();
|
|
619
|
-
},
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
/**
|
|
623
|
-
* Primary Entry Point
|
|
624
|
-
*/
|
|
625
|
-
export async function render(callback, options = {}) {
|
|
626
|
-
// Sync apply forced options first
|
|
627
|
-
if (options.nerdFont !== undefined) {
|
|
628
|
-
await init({ nerdFont: options.nerdFont });
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
// Start detection in background, don't await!
|
|
632
|
-
init();
|
|
633
|
-
}
|
|
634
|
-
const state = createScreenState(options);
|
|
635
|
-
const tick = () => {
|
|
636
|
-
clearBackBuffer(state);
|
|
637
|
-
const b = createScreenContext(state);
|
|
638
|
-
if (typeof callback === 'string') {
|
|
639
|
-
b.blit(0, 0, callback);
|
|
640
|
-
}
|
|
641
|
-
else {
|
|
642
|
-
callback(b);
|
|
643
|
-
}
|
|
644
|
-
if (state.isStopped)
|
|
645
|
-
return;
|
|
646
|
-
b.flushFlow();
|
|
647
|
-
if (state.isStopped)
|
|
648
|
-
return;
|
|
649
|
-
flush(state);
|
|
650
|
-
};
|
|
651
|
-
if (options.once) {
|
|
652
|
-
tick();
|
|
653
|
-
await new Promise((resolve) => {
|
|
654
|
-
setTimeout(() => {
|
|
655
|
-
resolve();
|
|
656
|
-
process.exit(0);
|
|
657
|
-
}, 50);
|
|
658
|
-
});
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
await loop(state, (_s) => tick());
|
|
662
|
-
}
|
|
14
|
+
export { createScreenContext } from './dsl/context';
|
|
15
|
+
export { render } from './dsl/render';
|
|
16
|
+
export { KEYS } from './dsl/types';
|
|
663
17
|
//# sourceMappingURL=dsl.js.map
|