@zakmandhro/bunti 0.1.5 → 0.2.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/AGENTS.md +65 -0
- package/README.md +86 -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 +15 -2
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Button.js +45 -25
- 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 +176 -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 +38 -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 +88 -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 +658 -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 +31 -0
- package/dist/dsl/interaction.d.ts.map +1 -0
- package/dist/dsl/interaction.js +150 -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 +555 -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 +95 -23
- 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 +153 -7
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +221 -34
- 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 +156 -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
|
@@ -0,0 +1,658 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunti DSL context factories: the common per-closure context, the direct
|
|
3
|
+
* (screen-buffer) box renderer, and the top-level screen context.
|
|
4
|
+
*/
|
|
5
|
+
import { bg, contrastText, createGradient, darken, fade, fg, lighten, rgb, } from '../colors';
|
|
6
|
+
import { identifyTerminal } from '../detect';
|
|
7
|
+
import { beginHookFrame, hintInputReadWithoutKeyboard, hintOverlappingBoxes, } from '../diagnostics';
|
|
8
|
+
import { resolveRect as resolveGeometryRect, resolvePlacedRect, splitRect as splitGeometryRect, } from '../geometry';
|
|
9
|
+
import { icon, replaceEmojis } from '../icons';
|
|
10
|
+
import { dimRect, getWindow, joinHorizontal, joinVertical, blit as layoutBlit, box as layoutBox, gradient as layoutGradient, list as layoutList, table as layoutTable, viewport as layoutViewport, wallpaper as layoutWallpaper, measureBox, rect, resolveSize, } from '../layout';
|
|
11
|
+
import { darkTheme, resolveTheme, } from '../theme';
|
|
12
|
+
import { visibleWidth } from '../utils';
|
|
13
|
+
import { colors } from '../vendor/colors';
|
|
14
|
+
import { createHooks } from './hooks';
|
|
15
|
+
import { createInteraction } from './interaction';
|
|
16
|
+
import { compositeLayer, createLayerScreenState, layerContentBounds, } from './layers';
|
|
17
|
+
import { createMotion } from './motion';
|
|
18
|
+
import { KEYS, } from './types';
|
|
19
|
+
/** Luminance scale applied by layer drop shadows at composite time. */
|
|
20
|
+
const LAYER_SHADOW_DIM = 0.45;
|
|
21
|
+
function pushHitboxFrame(dslState) {
|
|
22
|
+
const frame = { entries: [] };
|
|
23
|
+
(dslState.hitboxFrames ??= []).push(frame);
|
|
24
|
+
return frame;
|
|
25
|
+
}
|
|
26
|
+
function popHitboxFrame(dslState) {
|
|
27
|
+
dslState.hitboxFrames?.pop();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Re-places hitboxes registered inside a direct box once its painted rect
|
|
31
|
+
* is known. Flow-anchored entries land exactly where their content line
|
|
32
|
+
* painted (per-line alignment included, via the SAME measure the painter
|
|
33
|
+
* used); area-anchored entries shift by the delta between the assumed and
|
|
34
|
+
* painted inner origins. Pass `measure` as undefined when the box wraps —
|
|
35
|
+
* wrapping reflows line indices, so flow entries fall back to the delta.
|
|
36
|
+
*/
|
|
37
|
+
function fixupHitboxFrame(frame, measure, boxX, boxY, dx, dy) {
|
|
38
|
+
for (const entry of frame.entries) {
|
|
39
|
+
const flow = entry.flow;
|
|
40
|
+
if (flow && measure && flow.line >= 0 && flow.line < measure.lines.length) {
|
|
41
|
+
entry.box.x = boxX + measure.contentLeft(flow.line) + flow.col;
|
|
42
|
+
entry.box.y = boxY + measure.contentTop(flow.line);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
entry.box.x += dx;
|
|
46
|
+
entry.box.y += dy;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Converts flow-anchored entries captured during a nested closure back to
|
|
52
|
+
* area anchoring. Nested flow boxes and spans re-position their content
|
|
53
|
+
* within the parent flow in ways the direct-box fixup does not model, so
|
|
54
|
+
* entries registered inside them keep their eagerly resolved rects.
|
|
55
|
+
*/
|
|
56
|
+
function dropFlowAnchors(dslState, mark) {
|
|
57
|
+
const frames = dslState.hitboxFrames;
|
|
58
|
+
const frame = frames?.[frames.length - 1];
|
|
59
|
+
if (!frame)
|
|
60
|
+
return;
|
|
61
|
+
for (let i = mark; i < frame.entries.length; i++) {
|
|
62
|
+
frame.entries[i].flow = undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Entry count of the innermost hitbox frame (for dropFlowAnchors marks). */
|
|
66
|
+
function hitboxFrameMark(dslState) {
|
|
67
|
+
const frames = dslState.hitboxFrames;
|
|
68
|
+
return frames?.[frames.length - 1]?.entries.length ?? 0;
|
|
69
|
+
}
|
|
70
|
+
function boxBorderInset(options) {
|
|
71
|
+
return options.border === 'none' || !options.border ? 0 : 1;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolves the effective foreground for a box's `color` option. 'blank'
|
|
75
|
+
* auto-picks pure black or pure white text by WCAG luminance of the box
|
|
76
|
+
* background (gradients sample their middle stop).
|
|
77
|
+
*/
|
|
78
|
+
function resolveBoxFg(options) {
|
|
79
|
+
if (options.color !== 'blank')
|
|
80
|
+
return options.color;
|
|
81
|
+
const bgColor = options.bgColor;
|
|
82
|
+
if (bgColor === undefined)
|
|
83
|
+
return undefined;
|
|
84
|
+
if (typeof bgColor === 'object' && 'colors' in bgColor) {
|
|
85
|
+
const stops = bgColor.colors;
|
|
86
|
+
if (stops.length === 0)
|
|
87
|
+
return undefined;
|
|
88
|
+
return contrastText(stops[Math.floor(stops.length / 2)]);
|
|
89
|
+
}
|
|
90
|
+
return contrastText(bgColor);
|
|
91
|
+
}
|
|
92
|
+
function activeTheme(dslState, state) {
|
|
93
|
+
// options is shared by reference with layer screen-state copies, so
|
|
94
|
+
// options.theme always reflects the latest setTheme() call.
|
|
95
|
+
return (dslState.themeStack[dslState.themeStack.length - 1] ??
|
|
96
|
+
state.options.theme ??
|
|
97
|
+
state.theme ??
|
|
98
|
+
darkTheme);
|
|
99
|
+
}
|
|
100
|
+
function resolveBoxArea(parent, options, contentWidth, contentHeight) {
|
|
101
|
+
return resolvePlacedRect(parent, {
|
|
102
|
+
x: options.x,
|
|
103
|
+
y: options.y,
|
|
104
|
+
width: options.anchor === 'top' || options.anchor === 'bottom'
|
|
105
|
+
? parent.width
|
|
106
|
+
: options.width,
|
|
107
|
+
height: options.height,
|
|
108
|
+
anchor: options.anchor,
|
|
109
|
+
contentWidth,
|
|
110
|
+
contentHeight,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function resolveBoxInnerArea(area, options) {
|
|
114
|
+
const borderInset = boxBorderInset(options);
|
|
115
|
+
const px = options.padding?.[1] ?? 0;
|
|
116
|
+
const py = options.padding?.[0] ?? 0;
|
|
117
|
+
return {
|
|
118
|
+
x: area.x + borderInset + px,
|
|
119
|
+
y: area.y + borderInset + py,
|
|
120
|
+
width: Math.max(0, area.width - borderInset * 2 - px * 2),
|
|
121
|
+
height: Math.max(0, area.height - borderInset * 2 - py * 2),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Redefines ctx.lastKey / ctx.keys as live getters that also feed the
|
|
126
|
+
* "input read without { keyboard: true }" dev hint. Non-enumerable so the
|
|
127
|
+
* screen-context spread of the base context can never trigger a spurious
|
|
128
|
+
* read — only real user reads do.
|
|
129
|
+
*/
|
|
130
|
+
function defineInputAccessors(ctx, state) {
|
|
131
|
+
Object.defineProperty(ctx, 'lastKey', {
|
|
132
|
+
configurable: true,
|
|
133
|
+
enumerable: false,
|
|
134
|
+
get: () => {
|
|
135
|
+
hintInputReadWithoutKeyboard(state.options);
|
|
136
|
+
return state.lastKey;
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
Object.defineProperty(ctx, 'keys', {
|
|
140
|
+
configurable: true,
|
|
141
|
+
enumerable: false,
|
|
142
|
+
get: () => {
|
|
143
|
+
hintInputReadWithoutKeyboard(state.options);
|
|
144
|
+
return state.keys ?? [];
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const buntiColor = {
|
|
149
|
+
...colors,
|
|
150
|
+
darken,
|
|
151
|
+
lighten,
|
|
152
|
+
rgb,
|
|
153
|
+
fg,
|
|
154
|
+
bg,
|
|
155
|
+
dim: (text) => fg({ r: 96, g: 102, b: 118 }, text),
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Common Context Factory: Provided to every closure.
|
|
159
|
+
*/
|
|
160
|
+
function createDSLContext(state, dslState, availableW, availableH, offsetX = 0, offsetY = 0, isRoot = false) {
|
|
161
|
+
const hooks = createHooks(state);
|
|
162
|
+
const motion = createMotion(state, hooks.useState);
|
|
163
|
+
const interaction = createInteraction(state, (bounds) => ctx.resolveRect(bounds),
|
|
164
|
+
// Hitboxes registered while a direct box renders are captured so the
|
|
165
|
+
// renderer can re-place them once the painted rect is known.
|
|
166
|
+
(box, flow) => {
|
|
167
|
+
const frames = dslState.hitboxFrames;
|
|
168
|
+
frames?.[frames.length - 1]?.entries.push({ box, flow });
|
|
169
|
+
});
|
|
170
|
+
const ctx = {
|
|
171
|
+
color: buntiColor,
|
|
172
|
+
state,
|
|
173
|
+
width: availableW,
|
|
174
|
+
height: availableH,
|
|
175
|
+
area: { x: offsetX, y: offsetY, width: availableW, height: availableH },
|
|
176
|
+
isRoot,
|
|
177
|
+
offsetX,
|
|
178
|
+
offsetY,
|
|
179
|
+
get cursorX() {
|
|
180
|
+
const currentFlow = dslState.activeContents.join('');
|
|
181
|
+
const lines = currentFlow.split('\n');
|
|
182
|
+
return visibleWidth(lines[lines.length - 1]);
|
|
183
|
+
},
|
|
184
|
+
get cursorY() {
|
|
185
|
+
const currentFlow = dslState.activeContents.join('');
|
|
186
|
+
return Math.max(0, currentFlow.split('\n').length - 1);
|
|
187
|
+
},
|
|
188
|
+
mouseX: state.mouseX,
|
|
189
|
+
mouseY: state.mouseY,
|
|
190
|
+
mouseButton: state.mouseButton,
|
|
191
|
+
isMouseDown: state.isMouseDown,
|
|
192
|
+
lastKey: state.lastKey,
|
|
193
|
+
keys: state.keys ?? [],
|
|
194
|
+
focusedId: state.focusedId,
|
|
195
|
+
elapsedTime: Date.now() - state.startTime,
|
|
196
|
+
terminal: (state.terminal ??= identifyTerminal()),
|
|
197
|
+
get theme() {
|
|
198
|
+
return activeTheme(dslState, state);
|
|
199
|
+
},
|
|
200
|
+
setTheme(theme) {
|
|
201
|
+
const resolved = resolveTheme(theme);
|
|
202
|
+
state.theme = resolved;
|
|
203
|
+
state.options.theme = resolved;
|
|
204
|
+
state.requestTick?.();
|
|
205
|
+
},
|
|
206
|
+
themed(theme, callback) {
|
|
207
|
+
const next = resolveTheme(theme, activeTheme(dslState, state));
|
|
208
|
+
dslState.themeStack.push(next);
|
|
209
|
+
try {
|
|
210
|
+
callback(ctx);
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
dslState.themeStack.pop();
|
|
214
|
+
}
|
|
215
|
+
return ctx;
|
|
216
|
+
},
|
|
217
|
+
keyPressed(name) {
|
|
218
|
+
hintInputReadWithoutKeyboard(state.options);
|
|
219
|
+
return (state.keys?.some((e) => e.key === name && e.kind !== 'release') ?? false);
|
|
220
|
+
},
|
|
221
|
+
isKeyHeld(name) {
|
|
222
|
+
hintInputReadWithoutKeyboard(state.options);
|
|
223
|
+
return state.heldKeys?.isHeld(name) ?? false;
|
|
224
|
+
},
|
|
225
|
+
text(str) {
|
|
226
|
+
dslState.activeContents.push(replaceEmojis(String(str)));
|
|
227
|
+
return ctx;
|
|
228
|
+
},
|
|
229
|
+
...motion,
|
|
230
|
+
...hooks,
|
|
231
|
+
...interaction,
|
|
232
|
+
resolveRect(bounds) {
|
|
233
|
+
return resolveGeometryRect({ x: offsetX, y: offsetY, width: availableW, height: availableH }, {
|
|
234
|
+
...bounds,
|
|
235
|
+
y: bounds.y ?? ctx.cursorY,
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
resolveLocalRect(bounds, options) {
|
|
239
|
+
return resolvePlacedRect({ x: 0, y: 0, width: availableW, height: availableH }, bounds, options);
|
|
240
|
+
},
|
|
241
|
+
split(options) {
|
|
242
|
+
return splitGeometryRect({ x: 0, y: 0, width: availableW, height: availableH }, options);
|
|
243
|
+
},
|
|
244
|
+
list(id, items, options = {}) {
|
|
245
|
+
const [selectedIndex, setSelectedIndex] = ctx.useState(`${id}_index`, 0);
|
|
246
|
+
const isFocused = ctx.focusable(id);
|
|
247
|
+
if (isFocused && options.interactive !== false) {
|
|
248
|
+
if (state.lastKey === KEYS.UP)
|
|
249
|
+
setSelectedIndex(Math.max(0, selectedIndex - 1));
|
|
250
|
+
if (state.lastKey === KEYS.DOWN)
|
|
251
|
+
setSelectedIndex(Math.min(items.length - 1, selectedIndex + 1));
|
|
252
|
+
}
|
|
253
|
+
// Mouse: each visible row registers a `${id}:row:${index}` hitbox.
|
|
254
|
+
// Clicking a row selects it (and focuses the list so the selection is
|
|
255
|
+
// visible); the hovered row gets a soft selection wash. Keyboard
|
|
256
|
+
// behavior is unchanged.
|
|
257
|
+
let hoveredIndex;
|
|
258
|
+
if (state.options.mouse && options.interactive !== false) {
|
|
259
|
+
const startY = ctx.cursorY;
|
|
260
|
+
let visibleItems = items;
|
|
261
|
+
let offset = 0;
|
|
262
|
+
let hasMoreAbove = false;
|
|
263
|
+
if (options.maxVisible && items.length > options.maxVisible) {
|
|
264
|
+
const win = getWindow(items, selectedIndex, options.maxVisible);
|
|
265
|
+
visibleItems = win.visible;
|
|
266
|
+
offset = win.start;
|
|
267
|
+
hasMoreAbove = win.hasMoreAbove;
|
|
268
|
+
}
|
|
269
|
+
const resolvedW = resolveSize(options.width, availableW, 0);
|
|
270
|
+
const rowW = resolvedW > 0 ? resolvedW : availableW;
|
|
271
|
+
for (let i = 0; i < visibleItems.length; i++) {
|
|
272
|
+
const actualIdx = offset + i;
|
|
273
|
+
const row = ctx.hitbox(`${id}:row:${actualIdx}`, {
|
|
274
|
+
x: 0,
|
|
275
|
+
y: startY + (hasMoreAbove ? 1 : 0) + i,
|
|
276
|
+
width: rowW,
|
|
277
|
+
height: 1,
|
|
278
|
+
});
|
|
279
|
+
if (row.hovered)
|
|
280
|
+
hoveredIndex = actualIdx;
|
|
281
|
+
if (row.clicked) {
|
|
282
|
+
setSelectedIndex(actualIdx);
|
|
283
|
+
ctx.focus(id);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Theme-token fallbacks: focused rows highlight with theme.selection /
|
|
288
|
+
// theme.focus, unfocused lists mute their selected row via theme.muted.
|
|
289
|
+
// Explicit options always win.
|
|
290
|
+
const theme = activeTheme(dslState, state);
|
|
291
|
+
const content = layoutList(items, {
|
|
292
|
+
...options,
|
|
293
|
+
focusedIndex: selectedIndex,
|
|
294
|
+
focusStyle: isFocused
|
|
295
|
+
? (options.focusStyle ?? ((s) => theme.focus(s)))
|
|
296
|
+
: (s) => theme.muted(s),
|
|
297
|
+
selectedBg: isFocused
|
|
298
|
+
? (options.selectedBg ?? theme.selection)
|
|
299
|
+
: undefined,
|
|
300
|
+
hoveredIndex: options.hoveredIndex ?? hoveredIndex,
|
|
301
|
+
hoverBg: options.hoverBg ??
|
|
302
|
+
fade(theme.selection.rgb, theme.background.rgb, 0.55),
|
|
303
|
+
}, availableW);
|
|
304
|
+
dslState.activeContents.push(content);
|
|
305
|
+
return ctx;
|
|
306
|
+
},
|
|
307
|
+
table(rows, options = {}) {
|
|
308
|
+
const content = layoutTable(rows, options, availableW);
|
|
309
|
+
dslState.activeContents.push(content);
|
|
310
|
+
return ctx;
|
|
311
|
+
},
|
|
312
|
+
icon(name) {
|
|
313
|
+
return icon(name);
|
|
314
|
+
},
|
|
315
|
+
blit(x, y, content, style = {}) {
|
|
316
|
+
layoutBlit(state, x, y, content, style);
|
|
317
|
+
return ctx;
|
|
318
|
+
},
|
|
319
|
+
rect(x, y, w, h, style) {
|
|
320
|
+
rect(state, x, y, w, h, style);
|
|
321
|
+
return ctx;
|
|
322
|
+
},
|
|
323
|
+
viewport(content, width, height, scrollY = 0) {
|
|
324
|
+
return layoutViewport(content, width, height, scrollY);
|
|
325
|
+
},
|
|
326
|
+
span(options, callback) {
|
|
327
|
+
const subContents = [];
|
|
328
|
+
dslState.stack.push(dslState.activeContents);
|
|
329
|
+
dslState.activeContents = subContents;
|
|
330
|
+
const mark = hitboxFrameMark(dslState);
|
|
331
|
+
callback(ctx);
|
|
332
|
+
dropFlowAnchors(dslState, mark);
|
|
333
|
+
dslState.activeContents = dslState.stack.pop();
|
|
334
|
+
const combined = subContents.join('');
|
|
335
|
+
let styled = combined;
|
|
336
|
+
if (typeof options.color === 'function') {
|
|
337
|
+
styled = options.color(combined);
|
|
338
|
+
}
|
|
339
|
+
else if (options.color !== undefined) {
|
|
340
|
+
styled = fg(options.color, combined);
|
|
341
|
+
}
|
|
342
|
+
dslState.activeContents.push(styled);
|
|
343
|
+
return styled;
|
|
344
|
+
},
|
|
345
|
+
box(options, callback) {
|
|
346
|
+
if (options.zIndex !== undefined) {
|
|
347
|
+
const { zIndex, ...layeredOptions } = options;
|
|
348
|
+
ctx.layer(zIndex, (layerCtx) => {
|
|
349
|
+
layerCtx.box(layeredOptions, callback);
|
|
350
|
+
});
|
|
351
|
+
return '';
|
|
352
|
+
}
|
|
353
|
+
const preContentW = options.width === undefined ? availableW : 0;
|
|
354
|
+
const preContentH = options.height === undefined ? availableH : 0;
|
|
355
|
+
const preArea = resolveBoxArea(ctx.area, options, preContentW, preContentH);
|
|
356
|
+
const innerArea = resolveBoxInnerArea(preArea, options);
|
|
357
|
+
const subContents = [];
|
|
358
|
+
dslState.stack.push(dslState.activeContents);
|
|
359
|
+
dslState.activeContents = subContents;
|
|
360
|
+
const mark = hitboxFrameMark(dslState);
|
|
361
|
+
const subCtx = createDSLContext(state, dslState, innerArea.width, innerArea.height, innerArea.x, innerArea.y);
|
|
362
|
+
callback(subCtx);
|
|
363
|
+
dropFlowAnchors(dslState, mark);
|
|
364
|
+
dslState.activeContents = dslState.stack.pop();
|
|
365
|
+
const innerContent = subContents.join('');
|
|
366
|
+
const styledBox = layoutBox(innerContent, options, availableW, availableH);
|
|
367
|
+
if (!options.detach) {
|
|
368
|
+
dslState.activeContents.push(styledBox);
|
|
369
|
+
}
|
|
370
|
+
return styledBox;
|
|
371
|
+
},
|
|
372
|
+
layer(zIndexOrOptions, maybeCallback) {
|
|
373
|
+
const callback = typeof zIndexOrOptions === 'function' ? zIndexOrOptions : maybeCallback;
|
|
374
|
+
if (!callback)
|
|
375
|
+
return ctx;
|
|
376
|
+
const layerOptions = typeof zIndexOrOptions === 'number'
|
|
377
|
+
? { zIndex: zIndexOrOptions }
|
|
378
|
+
: typeof zIndexOrOptions === 'object'
|
|
379
|
+
? zIndexOrOptions
|
|
380
|
+
: {};
|
|
381
|
+
const zIndex = layerOptions.zIndex ?? 0;
|
|
382
|
+
const layerState = createLayerScreenState(state);
|
|
383
|
+
const layerDslState = {
|
|
384
|
+
activeContents: [],
|
|
385
|
+
stack: [],
|
|
386
|
+
layers: dslState.layers,
|
|
387
|
+
layerOrder: dslState.layerOrder,
|
|
388
|
+
themeStack: dslState.themeStack,
|
|
389
|
+
};
|
|
390
|
+
const layerCtx = createDSLContext(layerState, layerDslState, availableW, availableH, offsetX, offsetY, isRoot);
|
|
391
|
+
layerCtx.box = createDirectBoxRenderer(layerState, layerDslState, layerCtx);
|
|
392
|
+
callback(layerCtx);
|
|
393
|
+
const flow = layerDslState.activeContents.join('');
|
|
394
|
+
if (flow)
|
|
395
|
+
layoutBlit(layerState, offsetX, offsetY, flow);
|
|
396
|
+
dslState.layerOrder = layerDslState.layerOrder + 1;
|
|
397
|
+
dslState.layers.push({
|
|
398
|
+
zIndex,
|
|
399
|
+
order: layerDslState.layerOrder,
|
|
400
|
+
buffer: layerState.backBuffer,
|
|
401
|
+
shadow: layerOptions.shadow,
|
|
402
|
+
backdrop: layerOptions.backdrop,
|
|
403
|
+
bounds: layerContentBounds(layerState.backBuffer, state.width),
|
|
404
|
+
});
|
|
405
|
+
return ctx;
|
|
406
|
+
},
|
|
407
|
+
joinHorizontal(...blocks) {
|
|
408
|
+
return joinHorizontal(...blocks);
|
|
409
|
+
},
|
|
410
|
+
joinVertical(...blocks) {
|
|
411
|
+
return joinVertical(...blocks);
|
|
412
|
+
},
|
|
413
|
+
wallpaper(input) {
|
|
414
|
+
if (typeof input === 'object' && 'colors' in input) {
|
|
415
|
+
layoutGradient(state, input.colors, { direction: input.direction });
|
|
416
|
+
}
|
|
417
|
+
else if (Array.isArray(input)) {
|
|
418
|
+
layoutGradient(state, input);
|
|
419
|
+
}
|
|
420
|
+
else if (typeof input === 'object' && 'color' in input) {
|
|
421
|
+
ctx.wallpaper(input.color);
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
layoutWallpaper(state, { bg: input });
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
gradient: (opts) => ({
|
|
428
|
+
colors: createGradient(opts.colors, opts.steps || 10),
|
|
429
|
+
direction: opts.direction || 'vertical',
|
|
430
|
+
steps: opts.steps || 10,
|
|
431
|
+
}),
|
|
432
|
+
rgb,
|
|
433
|
+
requestStop() {
|
|
434
|
+
state.requestStop?.();
|
|
435
|
+
},
|
|
436
|
+
flushFlow() { },
|
|
437
|
+
};
|
|
438
|
+
// Live input accessors (also feed the keyboard-not-enabled dev hint).
|
|
439
|
+
defineInputAccessors(ctx, state);
|
|
440
|
+
return ctx;
|
|
441
|
+
}
|
|
442
|
+
function createDirectBoxRenderer(state, dslState, base) {
|
|
443
|
+
// Dev diagnostics: remember where direct boxes painted this frame
|
|
444
|
+
// (root screen context only — layer states never set boxRects).
|
|
445
|
+
// Anchored boxes are exempt: top/bottom bars are deliberate full-width
|
|
446
|
+
// chrome, and their "overlaps" are almost always small-screen clipping
|
|
447
|
+
// rather than missing-layer bugs.
|
|
448
|
+
const trackBoxRect = (area, options) => {
|
|
449
|
+
if (options.anchor !== undefined)
|
|
450
|
+
return;
|
|
451
|
+
const rects = dslState.boxRects;
|
|
452
|
+
if (rects && rects.length < 50)
|
|
453
|
+
rects.push(area);
|
|
454
|
+
};
|
|
455
|
+
return (options, callback) => {
|
|
456
|
+
if (options.zIndex !== undefined) {
|
|
457
|
+
const { zIndex, ...layeredOptions } = options;
|
|
458
|
+
base.layer(zIndex, (layerCtx) => {
|
|
459
|
+
layerCtx.box(layeredOptions, callback);
|
|
460
|
+
});
|
|
461
|
+
return '';
|
|
462
|
+
}
|
|
463
|
+
const boxOptions = options.anchor === 'top' || options.anchor === 'bottom'
|
|
464
|
+
? { ...options, width: base.width }
|
|
465
|
+
: options;
|
|
466
|
+
const hasFixedSize = boxOptions.width !== undefined &&
|
|
467
|
+
boxOptions.width !== 'auto' &&
|
|
468
|
+
boxOptions.height !== undefined &&
|
|
469
|
+
boxOptions.height !== 'auto';
|
|
470
|
+
// 'blank' resolves to WCAG auto-contrast black/white vs the box bg.
|
|
471
|
+
const boxFg = resolveBoxFg(boxOptions);
|
|
472
|
+
const fgStyle = boxFg !== undefined ? { fg: boxFg } : {};
|
|
473
|
+
// Visual-only slide (entrance animations): paints shift, geometry
|
|
474
|
+
// (sub-context, hitboxes) stays at the settled rect.
|
|
475
|
+
const paintDy = boxOptions.paintOffsetY ?? 0;
|
|
476
|
+
if (hasFixedSize) {
|
|
477
|
+
const preArea = resolveBoxArea(base.area, boxOptions, 0, 0);
|
|
478
|
+
const innerArea = resolveBoxInnerArea(preArea, boxOptions);
|
|
479
|
+
trackBoxRect(preArea, boxOptions);
|
|
480
|
+
if (boxOptions.bgColor || boxOptions.color) {
|
|
481
|
+
rect(state, preArea.x, preArea.y + paintDy, preArea.width, preArea.height, {
|
|
482
|
+
char: ' ',
|
|
483
|
+
bg: boxOptions.bgColor,
|
|
484
|
+
fg: boxFg,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const styledBox = layoutBox('', boxOptions, base.width, base.height);
|
|
488
|
+
layoutBlit(state, preArea.x, preArea.y + paintDy, styledBox, fgStyle);
|
|
489
|
+
const frame = pushHitboxFrame(dslState);
|
|
490
|
+
const subContents = [];
|
|
491
|
+
dslState.stack.push(dslState.activeContents);
|
|
492
|
+
dslState.activeContents = subContents;
|
|
493
|
+
const subCtx = createDSLContext(state, dslState, innerArea.width, innerArea.height, innerArea.x, innerArea.y);
|
|
494
|
+
callback(subCtx);
|
|
495
|
+
dslState.activeContents = dslState.stack.pop();
|
|
496
|
+
popHitboxFrame(dslState);
|
|
497
|
+
const contentStr = subContents.join('');
|
|
498
|
+
if (contentStr) {
|
|
499
|
+
const innerOptions = {
|
|
500
|
+
width: innerArea.width,
|
|
501
|
+
height: innerArea.height,
|
|
502
|
+
align: boxOptions.align,
|
|
503
|
+
valign: boxOptions.valign,
|
|
504
|
+
wrap: boxOptions.wrap,
|
|
505
|
+
border: 'none',
|
|
506
|
+
padding: [0, 0],
|
|
507
|
+
};
|
|
508
|
+
const measure = measureBox(contentStr, innerOptions, innerArea.width, innerArea.height);
|
|
509
|
+
const styledContent = layoutBox(contentStr, innerOptions, innerArea.width, innerArea.height, measure);
|
|
510
|
+
layoutBlit(state, innerArea.x, innerArea.y + paintDy, styledContent, fgStyle);
|
|
511
|
+
// Flow-anchored hitboxes land where their content line painted
|
|
512
|
+
// (alignment included); area-anchored rects were exact already.
|
|
513
|
+
fixupHitboxFrame(frame, boxOptions.wrap ? undefined : measure, innerArea.x, innerArea.y, 0, 0);
|
|
514
|
+
}
|
|
515
|
+
return styledBox;
|
|
516
|
+
}
|
|
517
|
+
// Auto-sized path: content must render before the painted rect is
|
|
518
|
+
// known. Seed the sub-context with last frame's measured rect for this
|
|
519
|
+
// declaration slot (exact from frame 2 on), fall back to the
|
|
520
|
+
// parent-sized guess on the first frame, then re-place the sub-context
|
|
521
|
+
// and every captured hitbox at the measured rect below — so hitboxes
|
|
522
|
+
// and rendered output share the same resolved rect within one frame.
|
|
523
|
+
const seqRef = state.autoBoxSeq;
|
|
524
|
+
const seq = seqRef ? seqRef.n++ : -1;
|
|
525
|
+
const seeded = seq >= 0 ? state.autoBoxAreas?.get(seq) : undefined;
|
|
526
|
+
const frame = pushHitboxFrame(dslState);
|
|
527
|
+
const subContents = [];
|
|
528
|
+
dslState.stack.push(dslState.activeContents);
|
|
529
|
+
dslState.activeContents = subContents;
|
|
530
|
+
const preAreaTemp = seeded ?? resolveBoxArea(base.area, boxOptions, base.width, base.height);
|
|
531
|
+
const innerAreaTemp = resolveBoxInnerArea(preAreaTemp, boxOptions);
|
|
532
|
+
const subCtx = createDSLContext(state, dslState, innerAreaTemp.width, innerAreaTemp.height, innerAreaTemp.x, innerAreaTemp.y);
|
|
533
|
+
callback(subCtx);
|
|
534
|
+
dslState.activeContents = dslState.stack.pop();
|
|
535
|
+
popHitboxFrame(dslState);
|
|
536
|
+
const contentStr = subContents.join('');
|
|
537
|
+
const measure = measureBox(contentStr, boxOptions, base.width, base.height);
|
|
538
|
+
const styledBox = layoutBox(contentStr, boxOptions, base.width, base.height, measure);
|
|
539
|
+
const boxArea = resolveBoxArea(base.area, boxOptions, measure.width, measure.height);
|
|
540
|
+
trackBoxRect(boxArea, boxOptions);
|
|
541
|
+
if (seq >= 0)
|
|
542
|
+
state.autoBoxAreas?.set(seq, boxArea);
|
|
543
|
+
// Retarget the sub-context to the painted rect so post-render reads
|
|
544
|
+
// (offsetX/offsetY/width/height/area) match the pixels on screen.
|
|
545
|
+
const innerArea = resolveBoxInnerArea(boxArea, boxOptions);
|
|
546
|
+
subCtx.offsetX = innerArea.x;
|
|
547
|
+
subCtx.offsetY = innerArea.y;
|
|
548
|
+
subCtx.width = innerArea.width;
|
|
549
|
+
subCtx.height = innerArea.height;
|
|
550
|
+
subCtx.area = innerArea;
|
|
551
|
+
fixupHitboxFrame(frame, boxOptions.wrap ? undefined : measure, boxArea.x, boxArea.y, innerArea.x - innerAreaTemp.x, innerArea.y - innerAreaTemp.y);
|
|
552
|
+
if (boxOptions.bgColor || boxOptions.color) {
|
|
553
|
+
rect(state, boxArea.x, boxArea.y + paintDy, boxArea.width, boxArea.height, {
|
|
554
|
+
char: ' ',
|
|
555
|
+
bg: boxOptions.bgColor,
|
|
556
|
+
fg: boxFg,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
layoutBlit(state, boxArea.x, boxArea.y + paintDy, styledBox, fgStyle);
|
|
560
|
+
return styledBox;
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Top-level Screen Context
|
|
565
|
+
*/
|
|
566
|
+
export function createScreenContext(state) {
|
|
567
|
+
state.hookCounter = 0;
|
|
568
|
+
// Dev diagnostics: frame boundary for keyless hook-drift tracking.
|
|
569
|
+
beginHookFrame(state);
|
|
570
|
+
const previousFocusableIds = state.focusableIds;
|
|
571
|
+
if (state.lastKey === KEYS.TAB && previousFocusableIds.length > 0) {
|
|
572
|
+
const idx = previousFocusableIds.indexOf(state.focusedId || '');
|
|
573
|
+
const nextIdx = (idx + 1) % previousFocusableIds.length;
|
|
574
|
+
state.focusedId = previousFocusableIds[nextIdx];
|
|
575
|
+
}
|
|
576
|
+
state.focusableIds = []; // Rebuild from this frame's rendered focusables.
|
|
577
|
+
// Last frame's settled hitboxes drive hover/click evaluation while this
|
|
578
|
+
// frame's registrations are still being placed (see interaction.ts).
|
|
579
|
+
state.prevHitboxes = state.hitboxes;
|
|
580
|
+
state.hitboxes = new Map(); // Rebuild from this frame's interactive geometry.
|
|
581
|
+
state.autoBoxSeq = { n: 0 }; // Re-key auto-box area seeding per frame.
|
|
582
|
+
state.autoBoxAreas ??= new Map();
|
|
583
|
+
// Hover enter/leave events last one frame; hitbox() refills them as this
|
|
584
|
+
// frame's hitboxes register (mutated, not reassigned: layers share them).
|
|
585
|
+
state.hoverEntered?.clear();
|
|
586
|
+
state.hoverLeft?.clear();
|
|
587
|
+
const dslState = {
|
|
588
|
+
activeContents: [],
|
|
589
|
+
stack: [],
|
|
590
|
+
layers: [],
|
|
591
|
+
layerOrder: 0,
|
|
592
|
+
themeStack: [],
|
|
593
|
+
boxRects: [],
|
|
594
|
+
};
|
|
595
|
+
const base = createDSLContext(state, dslState, state.width, state.height, 0, 0, true);
|
|
596
|
+
const flushFlow = () => {
|
|
597
|
+
const flow = dslState.activeContents.join('');
|
|
598
|
+
if (flow)
|
|
599
|
+
layoutBlit(state, 0, 0, flow);
|
|
600
|
+
// Dev diagnostics: direct boxes that painted over each other this
|
|
601
|
+
// frame with nothing controlling the stacking (hints once).
|
|
602
|
+
hintOverlappingBoxes(dslState.boxRects);
|
|
603
|
+
// Layers composite in ascending z-order. Right before each layer lands,
|
|
604
|
+
// its backdrop dims the whole screen and its shadow dims an offset rect
|
|
605
|
+
// (+2 cols, +1 row) under its painted bounds — both darken everything
|
|
606
|
+
// already composited, lower layers included.
|
|
607
|
+
const screenRect = { x: 0, y: 0, width: state.width, height: state.height };
|
|
608
|
+
dslState.layers
|
|
609
|
+
.sort((a, b) => a.zIndex - b.zIndex || a.order - b.order)
|
|
610
|
+
.forEach((layer) => {
|
|
611
|
+
if (layer.backdrop)
|
|
612
|
+
dimRect(state, screenRect, layer.backdrop);
|
|
613
|
+
if (layer.shadow && layer.bounds) {
|
|
614
|
+
dimRect(state, {
|
|
615
|
+
x: layer.bounds.x + 2,
|
|
616
|
+
y: layer.bounds.y + 1,
|
|
617
|
+
width: layer.bounds.width,
|
|
618
|
+
height: layer.bounds.height,
|
|
619
|
+
}, LAYER_SHADOW_DIM);
|
|
620
|
+
}
|
|
621
|
+
compositeLayer(state, layer.buffer);
|
|
622
|
+
});
|
|
623
|
+
};
|
|
624
|
+
const boxOverride = createDirectBoxRenderer(state, dslState, base);
|
|
625
|
+
const screenCtx = {
|
|
626
|
+
...base,
|
|
627
|
+
box: boxOverride,
|
|
628
|
+
flushFlow,
|
|
629
|
+
requestStop: () => {
|
|
630
|
+
state.requestStop?.();
|
|
631
|
+
},
|
|
632
|
+
// Rebound so callbacks receive the screen context (direct box renderer),
|
|
633
|
+
// not the flow-mode base it was originally defined against.
|
|
634
|
+
themed(theme, callback) {
|
|
635
|
+
const next = resolveTheme(theme, activeTheme(dslState, state));
|
|
636
|
+
dslState.themeStack.push(next);
|
|
637
|
+
try {
|
|
638
|
+
callback(screenCtx);
|
|
639
|
+
}
|
|
640
|
+
finally {
|
|
641
|
+
dslState.themeStack.pop();
|
|
642
|
+
}
|
|
643
|
+
return screenCtx;
|
|
644
|
+
},
|
|
645
|
+
};
|
|
646
|
+
// Spreading `base` snapshots getter values; re-declare theme as a live
|
|
647
|
+
// getter so themed() overrides are visible on the screen context too.
|
|
648
|
+
Object.defineProperty(screenCtx, 'theme', {
|
|
649
|
+
configurable: true,
|
|
650
|
+
enumerable: true,
|
|
651
|
+
get: () => activeTheme(dslState, state),
|
|
652
|
+
});
|
|
653
|
+
// lastKey/keys are non-enumerable on `base` (so the spread above cannot
|
|
654
|
+
// fake an input read); re-declare them on the screen context.
|
|
655
|
+
defineInputAccessors(screenCtx, state);
|
|
656
|
+
return screenCtx;
|
|
657
|
+
}
|
|
658
|
+
//# sourceMappingURL=context.js.map
|