@weasel-js/headless 1.15.0 → 1.15.2
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 +42 -69
- package/dist/chunk-36DNDFOQ.js +15 -0
- package/dist/chunk-36DNDFOQ.js.map +1 -0
- package/dist/index.d.ts +70 -319
- package/dist/index.js +63 -312
- package/dist/index.js.map +1 -1
- package/dist/page.d.ts +2 -0
- package/dist/page.global.js +456 -0
- package/dist/page.js +32 -0
- package/dist/page.js.map +1 -0
- package/package.json +14 -6
package/dist/index.js
CHANGED
|
@@ -1,326 +1,77 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import { clamp } from '@weasel-js/geom';
|
|
1
|
+
import { sceneToWire, PAGE_GLOBAL } from './chunk-36DNDFOQ.js';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
if (key === opens) {
|
|
12
|
-
if (!shape.isBranch(item)) return { handled: false };
|
|
13
|
-
if (!shape.isOpen(item)) return canToggle ? { handled: true, toggle: at } : { handled: true, at: null };
|
|
14
|
-
const next = items[at + 1];
|
|
15
|
-
return { handled: true, at: next !== void 0 && shape.isChildOf(next, item) ? at + 1 : null };
|
|
4
|
+
function defaultPageScript() {
|
|
5
|
+
const url = new URL("./page.global.js", import.meta.url);
|
|
6
|
+
try {
|
|
7
|
+
return readFileSync(url, "utf8");
|
|
8
|
+
} catch (err) {
|
|
9
|
+
throw new Error(`node raster host: no page script at ${url.pathname} \u2014 pass \`pageScript\``, { cause: err });
|
|
16
10
|
}
|
|
17
|
-
if (key !== closes) return { handled: false };
|
|
18
|
-
if (shape.isOpen(item) && canToggle) return { handled: true, toggle: at };
|
|
19
|
-
const parent = items.findIndex((p) => shape.isChildOf(item, p));
|
|
20
|
-
return parent < 0 ? { handled: false } : { handled: true, at: parent };
|
|
21
11
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
i += dir;
|
|
36
|
-
if (i < 0 || i >= n) {
|
|
37
|
-
if (!wrap) break;
|
|
38
|
-
i = (i + n) % n;
|
|
39
|
-
}
|
|
40
|
-
if (canStop(items[i])) return i;
|
|
41
|
-
}
|
|
42
|
-
return !wrap && from !== null && canStop(items[from]) ? from : null;
|
|
43
|
-
}
|
|
44
|
-
function arrowDir(key, shape) {
|
|
45
|
-
const orientation = shape.orientation ?? "vertical";
|
|
46
|
-
if (orientation !== "horizontal") {
|
|
47
|
-
if (key === "ArrowDown") return 1;
|
|
48
|
-
if (key === "ArrowUp") return -1;
|
|
49
|
-
}
|
|
50
|
-
if (orientation !== "vertical") {
|
|
51
|
-
const forward = shape.dir === "rtl" ? -1 : 1;
|
|
52
|
-
if (key === "ArrowRight") return forward;
|
|
53
|
-
if (key === "ArrowLeft") return -forward;
|
|
54
|
-
}
|
|
55
|
-
return 0;
|
|
56
|
-
}
|
|
57
|
-
function rovingKey(items, at, key, shape = {}) {
|
|
58
|
-
if (key === "Home") return { handled: true, at: stepStop(items, null, 1, shape) };
|
|
59
|
-
if (key === "End") return { handled: true, at: stepStop(items, null, -1, shape) };
|
|
60
|
-
const dir = arrowDir(key, shape);
|
|
61
|
-
return dir === 0 ? { handled: false } : { handled: true, at: stepStop(items, at, dir, shape) };
|
|
62
|
-
}
|
|
63
|
-
function rowKey(items, at, press, shape = {}, searching = false) {
|
|
64
|
-
const { key } = press;
|
|
65
|
-
const chord = !!(press.ctrlKey || press.metaKey);
|
|
66
|
-
if (shape.selectAll && key.toLowerCase() === "a" && chord && !press.altKey && !press.shiftKey) return { kind: "selectAll" };
|
|
67
|
-
const step = rovingKey(items, at, key, shape);
|
|
68
|
-
if (step.handled) return { kind: "move", at: step.at, extend: !!press.shiftKey };
|
|
69
|
-
if (shape.tree && (key === "ArrowRight" || key === "ArrowLeft")) {
|
|
70
|
-
const t = treeKey(items, at, key, shape.dir && !shape.tree.dir ? { ...shape.tree, dir: shape.dir } : shape.tree);
|
|
71
|
-
if (!t.handled) return null;
|
|
72
|
-
return "toggle" in t ? { kind: "toggle", at: t.toggle } : { kind: "move", at: t.at, extend: false };
|
|
73
|
-
}
|
|
74
|
-
if (key === " " && shape.typeahead && searching) return { kind: "type", held: true };
|
|
75
|
-
if (key === "Enter" || key === " ") return shape.activate ? { kind: "activate" } : null;
|
|
76
|
-
if (shape.typeahead && key.length === 1 && !chord && !press.altKey) return { kind: "type", held: false };
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// src/typeahead.ts
|
|
81
|
-
var TYPEAHEAD_RESET_MS = 500;
|
|
82
|
-
var EMPTY_TYPEAHEAD = { text: "", at: -Infinity };
|
|
83
|
-
function typeaheadMatch(items, from, query, shape) {
|
|
84
|
-
const q = query.toLowerCase();
|
|
85
|
-
const n = items.length;
|
|
86
|
-
const start = from == null ? 0 : q.length === 1 ? from + 1 : from;
|
|
87
|
-
for (let k = 0; k < n; k++) {
|
|
88
|
-
const i = (start + k) % n;
|
|
89
|
-
const item = items[i];
|
|
90
|
-
if ((shape.canStop?.(item) ?? true) && shape.label(item).toLowerCase().startsWith(q)) return i;
|
|
91
|
-
}
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
function typeaheadStep(items, from, buffer, key, now, shape, resetMs = TYPEAHEAD_RESET_MS) {
|
|
95
|
-
const prev = now - buffer.at < resetMs ? buffer.text : "";
|
|
96
|
-
const k = key.toLowerCase();
|
|
97
|
-
const cycling = prev !== "" && [...prev].every((c) => c.toLowerCase() === k);
|
|
98
|
-
const text = cycling ? key : prev + key;
|
|
99
|
-
return { buffer: { text, at: now }, hit: typeaheadMatch(items, from, text, shape) };
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// src/focus.ts
|
|
103
|
-
function tabSort(targets, declared) {
|
|
104
|
-
const key = (t) => declared(t) ?? Number.POSITIVE_INFINITY;
|
|
105
|
-
return [...targets].sort((a, b) => key(a) - key(b));
|
|
106
|
-
}
|
|
107
|
-
function tabStep(order, from, direction, isBefore) {
|
|
108
|
-
const forward = direction === "next";
|
|
109
|
-
const here = from === null ? -1 : order.indexOf(from);
|
|
110
|
-
let at;
|
|
111
|
-
if (here !== -1) at = here + (forward ? 1 : -1);
|
|
112
|
-
else if (from === null || !isBefore) at = forward ? 0 : order.length - 1;
|
|
113
|
-
else {
|
|
114
|
-
const after = order.findIndex((t) => isBefore(from, t));
|
|
115
|
-
const end = after === -1 ? order.length : after;
|
|
116
|
-
at = forward ? end : end - 1;
|
|
117
|
-
}
|
|
118
|
-
return order[at] ?? null;
|
|
119
|
-
}
|
|
120
|
-
function arrowSteps(key, shift, dir = "ltr") {
|
|
121
|
-
const steps = shift ? COARSE_STEPS : 1;
|
|
122
|
-
if (key === "ArrowUp") return steps;
|
|
123
|
-
if (key === "ArrowDown") return -steps;
|
|
124
|
-
const d = arrowDir(key, { orientation: "horizontal", dir });
|
|
125
|
-
return d === 0 ? null : d * steps;
|
|
126
|
-
}
|
|
127
|
-
function arrowMove(key, shift) {
|
|
128
|
-
const steps = arrowSteps(key, shift);
|
|
129
|
-
if (steps === null) return null;
|
|
130
|
-
return key === "ArrowRight" || key === "ArrowLeft" ? { dx: steps, dy: 0 } : { dx: 0, dy: -steps };
|
|
131
|
-
}
|
|
132
|
-
function rangeKey(key, shift, dir = "ltr") {
|
|
133
|
-
switch (key) {
|
|
134
|
-
case "PageUp":
|
|
135
|
-
return COARSE_STEPS;
|
|
136
|
-
case "PageDown":
|
|
137
|
-
return -COARSE_STEPS;
|
|
138
|
-
case "Home":
|
|
139
|
-
return "home";
|
|
140
|
-
case "End":
|
|
141
|
-
return "end";
|
|
142
|
-
default:
|
|
143
|
-
return arrowSteps(key, shift, dir);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
function usableStops(stops, min, max) {
|
|
147
|
-
if (!stops || stops.length === 0) return [];
|
|
148
|
-
const byValue = /* @__PURE__ */ new Map();
|
|
149
|
-
for (const stop of stops) {
|
|
150
|
-
const s = typeof stop === "number" ? { value: stop } : stop;
|
|
151
|
-
if (s.value < min || s.value > max) continue;
|
|
152
|
-
const seen = byValue.get(s.value);
|
|
153
|
-
if (!seen || seen.label === void 0 && s.label !== void 0) byValue.set(s.value, s);
|
|
154
|
-
}
|
|
155
|
-
return [...byValue.values()].sort((a, b) => a.value - b.value);
|
|
156
|
-
}
|
|
157
|
-
function attract(v, stops, tolerance, toFraction) {
|
|
158
|
-
const f = toFraction(v);
|
|
159
|
-
let best = v;
|
|
160
|
-
let bestGap = tolerance;
|
|
161
|
-
for (const stop of stops) {
|
|
162
|
-
const gap = Math.abs(toFraction(stop) - f);
|
|
163
|
-
if (gap <= bestGap) {
|
|
164
|
-
best = stop;
|
|
165
|
-
bestGap = gap;
|
|
166
|
-
}
|
|
12
|
+
async function open(options) {
|
|
13
|
+
const script = options.pageScript ?? defaultPageScript();
|
|
14
|
+
const browser = options.browser ?? await (await import('playwright-core')).chromium.launch({ headless: true, ...options.launch });
|
|
15
|
+
try {
|
|
16
|
+
const context = await browser.newContext({ deviceScaleFactor: 1 });
|
|
17
|
+
const page = await context.newPage();
|
|
18
|
+
await page.addScriptTag({ content: script });
|
|
19
|
+
const ready = await page.evaluate((key) => key in globalThis, PAGE_GLOBAL);
|
|
20
|
+
if (!ready) throw new Error("node raster host: the page script ran but set no render entry");
|
|
21
|
+
return { browser, context, page };
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (!options.browser) await browser.close();
|
|
24
|
+
throw err;
|
|
167
25
|
}
|
|
168
|
-
return best;
|
|
169
26
|
}
|
|
170
|
-
function
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
27
|
+
function createNodeRasterHost(options = {}) {
|
|
28
|
+
let opening = null;
|
|
29
|
+
let disposed = false;
|
|
30
|
+
async function call(args, format) {
|
|
31
|
+
if (disposed) throw new Error("node raster host: render on a disposed host");
|
|
32
|
+
opening ??= open(options).catch((err) => {
|
|
33
|
+
opening = null;
|
|
34
|
+
throw err;
|
|
35
|
+
});
|
|
36
|
+
const { page } = await opening;
|
|
37
|
+
const { scene, screen, ...rest } = args;
|
|
38
|
+
const req = {
|
|
39
|
+
...rest,
|
|
40
|
+
scene: sceneToWire(scene),
|
|
41
|
+
format,
|
|
42
|
+
...screen ? { screen: Array.from(screen) } : {}
|
|
43
|
+
};
|
|
44
|
+
return page.evaluate(
|
|
45
|
+
([key, r]) => globalThis[key].render(r),
|
|
46
|
+
[PAGE_GLOBAL, req]
|
|
47
|
+
);
|
|
175
48
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
49
|
+
async function dispose() {
|
|
50
|
+
if (disposed) return;
|
|
51
|
+
disposed = true;
|
|
52
|
+
const pending = opening;
|
|
53
|
+
opening = null;
|
|
54
|
+
if (!pending) return;
|
|
55
|
+
const opened = await pending.catch(() => null);
|
|
56
|
+
if (!opened) return;
|
|
57
|
+
if (options.browser) await opened.context.close();
|
|
58
|
+
else await opened.browser.close();
|
|
182
59
|
}
|
|
183
|
-
if (first === -1) return stops[0];
|
|
184
|
-
return stops[Math.max(0, first - (count - 1))];
|
|
185
|
-
}
|
|
186
|
-
function defaultStep(step, min, max) {
|
|
187
|
-
if (step !== void 0 && step > 0) return step;
|
|
188
|
-
return (max - min) / 100;
|
|
189
|
-
}
|
|
190
|
-
function clampOrdered(v, thumbs, index, min, max, step) {
|
|
191
|
-
const gap = step !== void 0 && step > 0 ? step : (max - min) / 1e3;
|
|
192
|
-
const lower = index > 0 ? thumbs[index - 1].value + gap : min;
|
|
193
|
-
const upper = index < thumbs.length - 1 ? thumbs[index + 1].value - gap : max;
|
|
194
|
-
return clamp(v, lower, upper);
|
|
195
|
-
}
|
|
196
|
-
function crossEnd(from, delta, stepSize, range, track) {
|
|
197
|
-
const [lo, hi] = range;
|
|
198
|
-
if (from > hi) return delta < 0 ? hi + delta + stepSize : from;
|
|
199
|
-
if (from < lo) return delta > 0 ? lo + delta - stepSize : from;
|
|
200
|
-
if (from + delta > hi) return from < hi ? hi : track[1];
|
|
201
|
-
if (from + delta < lo) return from > lo ? lo : track[0];
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
function sliderKey(key, shift, shape) {
|
|
205
|
-
const move = rangeKey(key, shift, shape.dir);
|
|
206
|
-
if (move === null) return null;
|
|
207
|
-
const { thumbs, index, min, max, step } = shape;
|
|
208
|
-
const from = thumbs[index].value;
|
|
209
|
-
const [bLo, bHi] = shape.bounds?.() ?? [min, max];
|
|
210
|
-
const lo = Math.max(min, bLo);
|
|
211
|
-
const hi = Math.min(max, bHi);
|
|
212
|
-
const stops = shape.stops ?? [];
|
|
213
|
-
let v;
|
|
214
|
-
if (move === "home") v = lo;
|
|
215
|
-
else if (move === "end") v = hi;
|
|
216
|
-
else if (shape.keyStep) v = shape.keyStep(move);
|
|
217
|
-
else if (stops.length > 0) v = stepStops(stops, from, move > 0 ? 1 : -1, Math.abs(move));
|
|
218
|
-
else {
|
|
219
|
-
const range = shape.range ?? [min, max];
|
|
220
|
-
const stepSize = defaultStep(step, range[0], range[1]);
|
|
221
|
-
const delta = move * stepSize;
|
|
222
|
-
v = crossEnd(from, delta, stepSize, range, [min, max]) ?? snapToStep(from + delta, step, min);
|
|
223
|
-
}
|
|
224
|
-
v = clamp(v, lo, hi);
|
|
225
|
-
return shape.ordered ? clampOrdered(v, thumbs, index, lo, hi, step) : v;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// src/textCommit.ts
|
|
229
|
-
function editEnd(cause, shape = {}) {
|
|
230
|
-
if (cause === "blur") return "commit";
|
|
231
|
-
if (cause.key === "Enter") return shape.multiline && cause.shift ? null : "commit";
|
|
232
|
-
if (cause.key === "Escape") return shape.escape ?? "cancel";
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
function imeKey(e) {
|
|
236
|
-
return e.isComposing === true || e.keyCode === 229 || e.key === "Process" || e.key === "Dead";
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// src/reorder.ts
|
|
240
|
-
var sortedSet = (at) => [...new Set(at)].sort((a, b) => a - b);
|
|
241
|
-
function moveItems(items, at, to) {
|
|
242
|
-
const taken = new Set(at);
|
|
243
|
-
const moved = sortedSet(at).flatMap((i) => i >= 0 && i < items.length ? [items[i]] : []);
|
|
244
|
-
const out = items.filter((_, i) => !taken.has(i));
|
|
245
|
-
out.splice(movedIndex(at, to), 0, ...moved);
|
|
246
|
-
return out;
|
|
247
|
-
}
|
|
248
|
-
function movedIndex(at, to) {
|
|
249
|
-
return to - sortedSet(at).filter((i) => i < to).length;
|
|
250
|
-
}
|
|
251
|
-
function isNoopDrop(at, to) {
|
|
252
|
-
const s = sortedSet(at);
|
|
253
|
-
if (s.length === 0) return false;
|
|
254
|
-
const first = s[0];
|
|
255
|
-
const last = s[s.length - 1];
|
|
256
|
-
return last - first + 1 === s.length && to >= first && to <= last + 1;
|
|
257
|
-
}
|
|
258
|
-
function nudgeGap(at, by, length) {
|
|
259
|
-
const s = sortedSet(at);
|
|
260
|
-
if (s.length === 0) return null;
|
|
261
|
-
const to = by < 0 ? s[0] - 1 : s[s.length - 1] + 2;
|
|
262
|
-
return to < 0 || to > length ? null : to;
|
|
263
|
-
}
|
|
264
|
-
function gapByMidpoint(mids, p) {
|
|
265
|
-
const backward = mids.length > 1 && mids[mids.length - 1] < mids[0];
|
|
266
|
-
return mids.filter((m) => backward ? m >= p : m <= p).length;
|
|
267
|
-
}
|
|
268
|
-
function nearestGap(gaps, p) {
|
|
269
|
-
let best = -1;
|
|
270
|
-
for (let k = 0; k < gaps.length; k++) if (best < 0 || Math.abs(gaps[k] - p) < Math.abs(gaps[best] - p)) best = k;
|
|
271
|
-
return best;
|
|
272
|
-
}
|
|
273
|
-
function isNudgeChord(e) {
|
|
274
|
-
return e.altKey && !e.ctrlKey && !e.metaKey;
|
|
275
|
-
}
|
|
276
|
-
function nudgeOf(e, back = "ArrowUp", on = "ArrowDown") {
|
|
277
|
-
if (!isNudgeChord(e)) return null;
|
|
278
|
-
if (e.key === back) return -1;
|
|
279
|
-
return e.key === on ? 1 : null;
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// src/scheduler.ts
|
|
283
|
-
var timerScheduler = {
|
|
284
|
-
now: () => Date.now(),
|
|
285
|
-
set: (fn, ms) => {
|
|
286
|
-
const t = setTimeout(fn, ms);
|
|
287
|
-
return () => clearTimeout(t);
|
|
288
|
-
}
|
|
289
|
-
};
|
|
290
|
-
|
|
291
|
-
// src/stepRepeat.ts
|
|
292
|
-
var REPEAT_DELAY_MS = 400;
|
|
293
|
-
var REPEAT_EVERY_MS = 60;
|
|
294
|
-
function createStepRepeat(step, spent, opts = {}) {
|
|
295
|
-
let cancel = null;
|
|
296
|
-
let key = null;
|
|
297
|
-
let hold = 0;
|
|
298
|
-
const stop = () => {
|
|
299
|
-
cancel?.();
|
|
300
|
-
cancel = null;
|
|
301
|
-
key = null;
|
|
302
|
-
hold++;
|
|
303
|
-
};
|
|
304
|
-
const tick = (k, ms) => {
|
|
305
|
-
cancel = (opts.scheduler ?? timerScheduler).set(() => {
|
|
306
|
-
if (spent(k)) return stop();
|
|
307
|
-
step(k);
|
|
308
|
-
tick(k, opts.everyMs ?? REPEAT_EVERY_MS);
|
|
309
|
-
}, ms);
|
|
310
|
-
};
|
|
311
60
|
return {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
61
|
+
async render(args) {
|
|
62
|
+
const { width, height, base64 } = await call(args, "rgba");
|
|
63
|
+
const bytes = Buffer.from(base64, "base64");
|
|
64
|
+
return { width, height, data: new Uint8ClampedArray(bytes.buffer, bytes.byteOffset, bytes.byteLength) };
|
|
65
|
+
},
|
|
66
|
+
async renderPng(args) {
|
|
67
|
+
const { width, height, base64 } = await call(args, "png");
|
|
68
|
+
return { width, height, png: new Uint8Array(Buffer.from(base64, "base64")) };
|
|
318
69
|
},
|
|
319
|
-
|
|
320
|
-
|
|
70
|
+
dispose,
|
|
71
|
+
[Symbol.asyncDispose]: dispose
|
|
321
72
|
};
|
|
322
73
|
}
|
|
323
74
|
|
|
324
|
-
export {
|
|
75
|
+
export { createNodeRasterHost };
|
|
325
76
|
//# sourceMappingURL=index.js.map
|
|
326
77
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tree.ts","../src/list.ts","../src/typeahead.ts","../src/focus.ts","../src/keySteps.ts","../src/slider.ts","../src/textCommit.ts","../src/reorder.ts","../src/scheduler.ts","../src/stepRepeat.ts"],"names":["first"],"mappings":"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","file":"index.js","sourcesContent":["/** How a list that folds is read: which items are branches, which are open, and which item is whose child. */\nexport interface TreeShape<T> {\n /** Whether `item` can open, its children shown or not. */\n isBranch(item: T): boolean;\n isOpen(item: T): boolean;\n /** Whether `child` is one of `parent`'s own children. */\n isChildOf(child: T, parent: T): boolean;\n /** Whether a branch can open or close now. A disabled one can't. Default: every branch. */\n canToggle?(item: T): boolean;\n /** The inline direction the rows read in. Under `'rtl'` Left opens and Right closes. Default `'ltr'`. */\n dir?: 'ltr' | 'rtl';\n}\n\n/** What Right or Left does on one item of a tree: open or close it, move the highlight, or nothing (`handled` false).\n * `at` null is a key the tree takes without moving. */\nexport type TreeKeyResult =\n | { handled: false }\n | { handled: true; toggle: number }\n | { handled: true; at: number | null };\n\n/**\n * Right and Left on the item at `at`, the WAI-ARIA tree way: Right opens a closed branch, else steps to its first\n * child; Left closes an open branch, else steps to the parent. Mirrored under `dir: 'rtl'`. `items` are the rows\n * shown, in drawn order.\n */\nexport function treeKey<T>(items: readonly T[], at: number, key: string, shape: TreeShape<T>): TreeKeyResult {\n const item = items[at];\n if (item === undefined) return { handled: false };\n const canToggle = shape.canToggle?.(item) ?? true;\n const [opens, closes] = shape.dir === 'rtl' ? ['ArrowLeft', 'ArrowRight'] : ['ArrowRight', 'ArrowLeft'];\n if (key === opens) {\n if (!shape.isBranch(item)) return { handled: false };\n if (!shape.isOpen(item)) return canToggle ? { handled: true, toggle: at } : { handled: true, at: null };\n const next = items[at + 1];\n return { handled: true, at: next !== undefined && shape.isChildOf(next, item) ? at + 1 : null };\n }\n if (key !== closes) return { handled: false };\n if (shape.isOpen(item) && canToggle) return { handled: true, toggle: at };\n const parent = items.findIndex((p) => shape.isChildOf(item, p));\n return parent < 0 ? { handled: false } : { handled: true, at: parent };\n}\n","import { treeKey, type TreeShape } from './tree';\n\n/** How a list's items are read: which ones the highlight can land on, and which way its arrows run. */\nexport interface ListShape<T> {\n /** Whether the highlight can land on `item`. A separator or a heading can't. Default: every item. */\n canStop?(item: T): boolean;\n /** Which arrow keys move the highlight. `'both'` answers to all four, the way a radio group does. Default `'vertical'`. */\n orientation?: 'vertical' | 'horizontal' | 'both';\n /** The inline direction the items are laid out in. Under `'rtl'` Left steps forward and Right back. Default `'ltr'`. */\n dir?: 'ltr' | 'rtl';\n /** An arrow past either end comes round to the other. Default true. */\n wrap?: boolean;\n}\n\nconst always = () => true;\n\n/** Index of the first item the highlight can land on, or null for a list with none. */\nexport function firstStop<T>(items: readonly T[], shape: ListShape<T> = {}): number | null {\n const i = items.findIndex(shape.canStop ?? always);\n return i < 0 ? null : i;\n}\n\n/** The next item the highlight can land on from `from` in direction `dir`; from `null`, the first or last.\n * Without wrap, an end stays where it is. Null for a list with no such item. */\nexport function stepStop<T>(\n items: readonly T[], from: number | null, dir: 1 | -1, shape: ListShape<T> = {},\n): number | null {\n const canStop = shape.canStop ?? always;\n const wrap = shape.wrap ?? true;\n const n = items.length;\n let i = from ?? (dir === 1 ? -1 : n);\n for (let k = 0; k < n; k++) {\n i += dir;\n if (i < 0 || i >= n) {\n if (!wrap) break;\n i = (i + n) % n;\n }\n if (canStop(items[i]!)) return i;\n }\n return !wrap && from !== null && canStop(items[from]!) ? from : null;\n}\n\n/** What one key does to a list's highlight. `handled` false for a key that is not the list's to move. */\nexport type RovingResult = { handled: false } | { handled: true; at: number | null };\n\n/** Which way an arrow key steps a list of `shape`: 1, -1, or 0 for a key that is not one of its arrows. */\nexport function arrowDir(key: string, shape: ListShape<unknown>): 1 | -1 | 0 {\n const orientation = shape.orientation ?? 'vertical';\n if (orientation !== 'horizontal') {\n if (key === 'ArrowDown') return 1;\n if (key === 'ArrowUp') return -1;\n }\n if (orientation !== 'vertical') {\n const forward = shape.dir === 'rtl' ? -1 : 1;\n if (key === 'ArrowRight') return forward;\n if (key === 'ArrowLeft') return -forward as 1 | -1;\n }\n return 0;\n}\n\n/** Move the highlight `at` by one key: the arrows along the list's orientation, Home and End. */\nexport function rovingKey<T>(items: readonly T[], at: number | null, key: string, shape: ListShape<T> = {}): RovingResult {\n if (key === 'Home') return { handled: true, at: stepStop(items, null, 1, shape) };\n if (key === 'End') return { handled: true, at: stepStop(items, null, -1, shape) };\n const dir = arrowDir(key, shape as ListShape<unknown>);\n return dir === 0 ? { handled: false } : { handled: true, at: stepStop(items, at, dir, shape) };\n}\n\n/** A key as {@link rowKey} reads it: the key and the modifiers held. */\nexport interface KeyPress {\n key: string;\n shiftKey?: boolean;\n ctrlKey?: boolean;\n metaKey?: boolean;\n altKey?: boolean;\n}\n\n/** A list of rows, and which of the keys past the arrows its caller answers. */\nexport interface RowKeysShape<T> extends ListShape<T> {\n /** Set for a list that folds: Right and Left open, close, and walk it ({@link treeKey}). */\n tree?: TreeShape<T>;\n /** Cmd/Ctrl+A selects every row. */\n selectAll?: boolean;\n /** Enter and Space activate a row. */\n activate?: boolean;\n /** Typing a character searches the rows' text. */\n typeahead?: boolean;\n}\n\n/** What one key on a row asks for, for the caller to carry out. */\nexport type RowKeyAction =\n /** Select every row. */\n | { kind: 'selectAll' }\n /** Move to row `at`, extending a range from the anchor when `extend`. `at` null takes the key without moving. */\n | { kind: 'move'; at: number | null; extend: boolean }\n /** Open or close the branch at `at`. */\n | { kind: 'toggle'; at: number }\n | { kind: 'activate' }\n /** Hand the key to typeahead. `held` when the key is the list's whether or not the search finds a row. */\n | { kind: 'type'; held: boolean };\n\n/**\n * What a key on row `at` of a list of rows does, in the order the keys are tried: Cmd/Ctrl+A, the arrows with Home\n * and End (Shift to extend), a tree's Right and Left, a space typed mid-search, Enter or Space to activate, then any\n * other character to search. `searching` says a typeahead search is under way. Null for a key that is not the list's.\n */\nexport function rowKey<T>(\n items: readonly T[], at: number, press: KeyPress, shape: RowKeysShape<T> = {}, searching = false,\n): RowKeyAction | null {\n const { key } = press;\n const chord = !!(press.ctrlKey || press.metaKey);\n if (shape.selectAll && key.toLowerCase() === 'a' && chord && !press.altKey && !press.shiftKey) return { kind: 'selectAll' };\n const step = rovingKey(items, at, key, shape);\n if (step.handled) return { kind: 'move', at: step.at, extend: !!press.shiftKey };\n if (shape.tree && (key === 'ArrowRight' || key === 'ArrowLeft')) {\n const t = treeKey(items, at, key, shape.dir && !shape.tree.dir ? { ...shape.tree, dir: shape.dir } : shape.tree);\n if (!t.handled) return null;\n return 'toggle' in t ? { kind: 'toggle', at: t.toggle } : { kind: 'move', at: t.at, extend: false };\n }\n // Mid-search a space is a typed character: \"new f\" must not activate at the space.\n if (key === ' ' && shape.typeahead && searching) return { kind: 'type', held: true };\n if (key === 'Enter' || key === ' ') return shape.activate ? { kind: 'activate' } : null;\n if (shape.typeahead && key.length === 1 && !chord && !press.altKey) return { kind: 'type', held: false };\n return null;\n}\n","import type { ListShape } from './list';\n\n/** How typeahead reads a list: its shape, and the text each item is found by. */\nexport interface TypeaheadShape<T> extends Pick<ListShape<T>, 'canStop'> {\n label(item: T): string;\n}\n\n/** How long typing can pause before the typeahead buffer starts over, in milliseconds. */\nexport const TYPEAHEAD_RESET_MS = 500;\n\n/** What has been typed so far, and when the last key came. */\nexport interface TypeaheadBuffer {\n text: string;\n at: number;\n}\n\n/** A buffer nothing has been typed into. */\nexport const EMPTY_TYPEAHEAD: TypeaheadBuffer = { text: '', at: -Infinity };\n\n/** The item a typed `query` lands on, searching forward from `from` with\n * wrap. A one-letter query starts after the highlight so repeating a letter\n * cycles; a longer one starts at it so typing on keeps the match. */\nexport function typeaheadMatch<T>(\n items: readonly T[], from: number | null, query: string, shape: TypeaheadShape<T>,\n): number | null {\n const q = query.toLowerCase();\n const n = items.length;\n const start = from == null ? 0 : q.length === 1 ? from + 1 : from;\n for (let k = 0; k < n; k++) {\n const i = (start + k) % n;\n const item = items[i]!;\n if ((shape.canStop?.(item) ?? true) && shape.label(item).toLowerCase().startsWith(q)) return i;\n }\n return null;\n}\n\n/** One typed character: the new buffer and the item it lands on. A key that\n * repeats the whole buffer searches that one letter, so it cycles. */\nexport function typeaheadStep<T>(\n items: readonly T[],\n from: number | null,\n buffer: TypeaheadBuffer,\n key: string,\n now: number,\n shape: TypeaheadShape<T>,\n resetMs: number = TYPEAHEAD_RESET_MS,\n): { buffer: TypeaheadBuffer; hit: number | null } {\n const prev = now - buffer.at < resetMs ? buffer.text : '';\n const k = key.toLowerCase();\n const cycling = prev !== '' && [...prev].every((c) => c.toLowerCase() === k);\n const text = cycling ? key : prev + key;\n return { buffer: { text, at: now }, hit: typeaheadMatch(items, from, text, shape) };\n}\n","/** How a focus change came about. */\nexport interface FocusScopeOptions {\n /** Whether the target shows its focus ring: the scope's `:focus-visible`. Keyboard moves show one, a press does\n * not. Default `false` for {@link FocusScope.focus}, `true` for {@link FocusScope.moveFocus}. */\n readonly visible?: boolean;\n}\n\n/**\n * Keyboard focus over a set of targets, whatever draws them: a HUD's widgets and a DOM subtree's elements both\n * answer it, so code that moves or reads focus is written once for either.\n */\nexport interface FocusScope<T> {\n /** The target holding keyboard focus, or null. One per scope. */\n readonly focused: T | null;\n /** Whether the focused target shows its focus ring. */\n readonly focusVisible: boolean;\n /** Focus `target`, or blur with `null`. Returns whether `target` now holds focus: false when it is not in this\n * scope or cannot take focus, and focus is then unchanged. */\n focus(target: T | null, options?: FocusScopeOptions): boolean;\n /** Move focus one step through {@link tabOrder}. From nothing, `'next'` lands on the first target and `'prev'` on\n * the last; past either end the scope blurs and returns null, which is where focus leaves it. */\n moveFocus(direction: 'next' | 'prev', options?: FocusScopeOptions): T | null;\n /** The targets Tab visits, in order. */\n tabOrder(): T[];\n /** Run `fn` whenever the focused target changes. Returns an unsubscribe. */\n subscribeFocus(fn: (focused: T | null) => void): () => void;\n}\n\n/** Tab order: targets that declare a place come first, ascending, then the rest in the order given. In the DOM a\n * positive `tabindex` declares one and `0` does not. */\nexport function tabSort<T>(targets: readonly T[], declared: (target: T) => number | undefined): T[] {\n const key = (t: T) => declared(t) ?? Number.POSITIVE_INFINITY;\n // Array.prototype.sort is stable, so ties and the undeclared keep the given order.\n return [...targets].sort((a, b) => key(a) - key(b));\n}\n\n/** The target one Tab step from `from` through `order`, or null past either end. From nothing, `'next'` is the\n * first and `'prev'` the last. A `from` that is out of the order (hidden since it took focus, say) steps from where\n * it sits, which `isBefore` says; without it, such a `from` steps as nothing does. */\nexport function tabStep<T>(\n order: readonly T[], from: T | null, direction: 'next' | 'prev', isBefore?: (a: T, b: T) => boolean,\n): T | null {\n const forward = direction === 'next';\n const here = from === null ? -1 : order.indexOf(from);\n let at: number;\n if (here !== -1) at = here + (forward ? 1 : -1);\n else if (from === null || !isBefore) at = forward ? 0 : order.length - 1;\n else {\n const after = order.findIndex((t) => isBefore(from, t));\n const end = after === -1 ? order.length : after;\n at = forward ? end : end - 1;\n }\n return order[at] ?? null;\n}\n","import { COARSE_STEPS } from '@weasel-js/quantity';\nimport { arrowDir } from './list';\n\nexport { COARSE_STEPS };\n\n/** Steps an arrow key asks for along one axis: one, or {@link COARSE_STEPS} with Shift, positive\n * toward ArrowRight and ArrowUp. Under `'rtl'` ArrowLeft and ArrowRight swap, as a native range\n * input's do. `null` for any other key. A caller multiplies by its own step. */\nexport function arrowSteps(key: string, shift: boolean, dir: 'ltr' | 'rtl' = 'ltr'): number | null {\n const steps = shift ? COARSE_STEPS : 1;\n if (key === 'ArrowUp') return steps;\n if (key === 'ArrowDown') return -steps;\n const d = arrowDir(key, { orientation: 'horizontal', dir });\n return d === 0 ? null : d * steps;\n}\n\n/** Steps an arrow key asks for on a plane, in screen axes: `dy` is positive toward ArrowDown.\n * One step, or {@link COARSE_STEPS} with Shift; `null` for any other key. */\nexport function arrowMove(key: string, shift: boolean): { dx: number; dy: number } | null {\n const steps = arrowSteps(key, shift);\n if (steps === null) return null;\n return key === 'ArrowRight' || key === 'ArrowLeft' ? { dx: steps, dy: 0 } : { dx: 0, dy: -steps };\n}\n\n/** What a key does along a range: {@link arrowSteps}, the Page keys ({@link COARSE_STEPS} steps),\n * or Home and End (an end of the range). `null` for any other key. */\nexport function rangeKey(key: string, shift: boolean, dir: 'ltr' | 'rtl' = 'ltr'): number | 'home' | 'end' | null {\n switch (key) {\n case 'PageUp': return COARSE_STEPS;\n case 'PageDown': return -COARSE_STEPS;\n case 'Home': return 'home';\n case 'End': return 'end';\n default: return arrowSteps(key, shift, dir);\n }\n}\n","import { clamp } from '@weasel-js/geom';\nimport { snapToStep } from '@weasel-js/quantity';\nimport { rangeKey } from './keySteps';\n\n/** A value on a slider's track the thumb can come to rest on, with whatever label draws under it. */\nexport interface SliderStop<L = unknown> {\n value: number;\n label?: L;\n}\n\n/** The stops that are actually reachable: inside the range, deduped, ascending.\n * A duplicate keeps the first label given for its value. */\nexport function usableStops<L>(\n stops: readonly (number | SliderStop<L>)[] | undefined, min: number, max: number,\n): SliderStop<L>[] {\n if (!stops || stops.length === 0) return [];\n const byValue = new Map<number, SliderStop<L>>();\n for (const stop of stops) {\n const s = typeof stop === 'number' ? { value: stop } : stop;\n if (s.value < min || s.value > max) continue;\n const seen = byValue.get(s.value);\n if (!seen || (seen.label === undefined && s.label !== undefined)) byValue.set(s.value, s);\n }\n return [...byValue.values()].sort((a, b) => a.value - b.value);\n}\n\n/** The stop nearest `v` along the track, or `v` itself when none lies within\n * `tolerance` (a track fraction). Measured on the track rather than in value\n * units so evenly spaced stops attract over the same distance. */\nexport function attract(v: number, stops: readonly number[], tolerance: number, toFraction: (v: number) => number): number {\n const f = toFraction(v);\n let best = v;\n let bestGap = tolerance;\n for (const stop of stops) {\n const gap = Math.abs(toFraction(stop) - f);\n if (gap <= bestGap) {\n best = stop;\n bestGap = gap;\n }\n }\n return best;\n}\n\n/** The stop `count` places past `v` in `direction`, saturating at either end. */\nexport function stepStops(stops: readonly number[], v: number, direction: 1 | -1, count: number): number {\n if (direction > 0) {\n const first = stops.findIndex(stop => stop > v);\n if (first === -1) return stops[stops.length - 1]!;\n return stops[Math.min(stops.length - 1, first + count - 1)]!;\n }\n let first = -1;\n for (let i = stops.length - 1; i >= 0; i--) {\n if (stops[i]! < v) {\n first = i;\n break;\n }\n }\n if (first === -1) return stops[0]!;\n return stops[Math.max(0, first - (count - 1))]!;\n}\n\n/** `step` when it is positive; otherwise a hundredth of the span. */\nexport function defaultStep(step: number | undefined, min: number, max: number): number {\n if (step !== undefined && step > 0) return step;\n return (max - min) / 100;\n}\n\n/** Keep thumb `index` strictly between its neighbors: a step apart, or a thousandth of the span without one. */\nexport function clampOrdered(\n v: number,\n thumbs: readonly { value: number }[],\n index: number,\n min: number,\n max: number,\n step: number | undefined,\n): number {\n const gap = step !== undefined && step > 0 ? step : (max - min) / 1000;\n const lower = index > 0 ? thumbs[index - 1]!.value + gap : min;\n const upper = index < thumbs.length - 1 ? thumbs[index + 1]!.value - gap : max;\n return clamp(v, lower, upper);\n}\n\n/**\n * Where a keystroke of `delta` takes a thumb at `from` when the move meets an\n * end of the slider's finite `range`, or `null` when it stays inside. An\n * endless end's stop lies on the `track` beyond the range, at a distance drawn\n * for the eye; to the keyboard it is one press past the range's end. A move\n * that would overshoot the end lands on the end first.\n */\nexport function crossEnd(\n from: number,\n delta: number,\n stepSize: number,\n range: readonly [number, number],\n track: readonly [number, number],\n): number | null {\n const [lo, hi] = range;\n if (from > hi) return delta < 0 ? hi + delta + stepSize : from;\n if (from < lo) return delta > 0 ? lo + delta - stepSize : from;\n if (from + delta > hi) return from < hi ? hi : track[1];\n if (from + delta < lo) return from > lo ? lo : track[0];\n return null;\n}\n\n/** What a key press on one thumb of a slider reads. */\nexport interface SliderKeyShape {\n thumbs: readonly { value: number }[];\n /** The thumb the key went to. */\n index: number;\n /** The ends of the track. */\n min: number;\n max: number;\n step?: number;\n /** Usable stops, ascending. With any, a keystroke moves by whole stops rather than by value. */\n stops?: readonly number[];\n /** Keep the thumb between its neighbors. */\n ordered?: boolean;\n /** The finite range, where the track runs past it to an endless end's stop. Default the track. */\n range?: readonly [number, number];\n /** Under `'rtl'` the horizontal arrows are mirrored, as the values run from the right end. */\n dir?: 'ltr' | 'rtl';\n /** This thumb's own limits inside the track. Read only for a key the slider answers to. */\n bounds?(): readonly [number, number];\n /** Where the thumb lands `steps` presses away, for a track whose values aren't what the user counts in. */\n keyStep?(steps: number): number;\n}\n\n/** The value one key moves thumb `index` to: arrows by a step (ten with Shift), the Page keys by ten,\n * Home and End to the thumb's ends. `null` for a key the slider does not answer to. */\nexport function sliderKey(key: string, shift: boolean, shape: SliderKeyShape): number | null {\n const move = rangeKey(key, shift, shape.dir);\n if (move === null) return null;\n const { thumbs, index, min, max, step } = shape;\n const from = thumbs[index]!.value;\n const [bLo, bHi] = shape.bounds?.() ?? [min, max];\n const lo = Math.max(min, bLo);\n const hi = Math.min(max, bHi);\n const stops = shape.stops ?? [];\n let v: number;\n if (move === 'home') v = lo;\n else if (move === 'end') v = hi;\n else if (shape.keyStep) v = shape.keyStep(move);\n else if (stops.length > 0) v = stepStops(stops, from, move > 0 ? 1 : -1, Math.abs(move));\n else {\n const range = shape.range ?? [min, max];\n const stepSize = defaultStep(step, range[0], range[1]);\n const delta = move * stepSize;\n v = crossEnd(from, delta, stepSize, range, [min, max]) ?? snapToStep(from + delta, step, min);\n }\n v = clamp(v, lo, hi);\n return shape.ordered ? clampOrdered(v, thumbs, index, lo, hi, step) : v;\n}\n","/** How a text field's edit ends: `'commit'` writes what was typed, `'cancel'` drops it and puts back what was there. */\nexport type EditEnd = 'commit' | 'cancel';\n\n/** What a field varies in how its edit ends: what Escape does (`'cancel'` by default), and whether the field holds\n * more than one line, which leaves Shift+Enter to type a line break rather than commit. */\nexport interface EditEndShape {\n escape?: EditEnd;\n multiline?: boolean;\n}\n\n/** What may end an edit: a key, with whether Shift is held, or the field losing focus. A key {@link imeKey} claims is\n * the caller's to hold back. */\nexport type EditCause = 'blur' | { key: string; shift?: boolean };\n\n/** How `cause` ends a text field's edit, or null when it leaves the edit open. Enter and blur commit, Escape cancels\n * unless `shape` says otherwise. */\nexport function editEnd(cause: EditCause, shape: EditEndShape = {}): EditEnd | null {\n if (cause === 'blur') return 'commit';\n if (cause.key === 'Enter') return shape.multiline && cause.shift ? null : 'commit';\n if (cause.key === 'Escape') return shape.escape ?? 'cancel';\n return null;\n}\n\n/** A keydown as the platform reports it, enough to tell whether an input method owns it. */\nexport interface KeySource {\n key: string;\n isComposing?: boolean;\n keyCode?: number;\n}\n\n/** Whether a keydown belongs to an input method, which a field must leave alone: Enter there confirms a candidate and\n * Escape dismisses them. Safari sends the Enter that ends a composition with `isComposing` already false, but with\n * `keyCode` 229. */\nexport function imeKey(e: KeySource): boolean {\n return e.isComposing === true || e.keyCode === 229 || e.key === 'Process' || e.key === 'Dead';\n}\n","/**\n * Moving items within an ordered list. A destination is a gap, counted before anything is taken out: 0 before the\n * first item, `length` after the last, and `i` between `items[i - 1]` and `items[i]` — the gap a UI marks while it\n * carries something. The items moved are named by their indices, so a selection of several moves in one go.\n */\n\nconst sortedSet = (at: readonly number[]): number[] => [...new Set(at)].sort((a, b) => a - b);\n\n/** `items` with the items at indices `at` taken out and put back, in their order, in gap `to`. */\nexport function moveItems<T>(items: readonly T[], at: readonly number[], to: number): T[] {\n const taken = new Set(at);\n const moved = sortedSet(at).flatMap((i) => (i >= 0 && i < items.length ? [items[i]!] : []));\n const out = items.filter((_, i) => !taken.has(i));\n out.splice(movedIndex(at, to), 0, ...moved);\n return out;\n}\n\n/** The index the first of the items at `at` holds once {@link moveItems} has put them in gap `to`. */\nexport function movedIndex(at: readonly number[], to: number): number {\n return to - sortedSet(at).filter((i) => i < to).length;\n}\n\n/** Whether putting the items at `at` in gap `to` leaves them where they are: a contiguous run dropped in a gap at\n * either of its ends or inside it. False for no items. */\nexport function isNoopDrop(at: readonly number[], to: number): boolean {\n const s = sortedSet(at);\n if (s.length === 0) return false;\n const first = s[0]!;\n const last = s[s.length - 1]!;\n return last - first + 1 === s.length && to >= first && to <= last + 1;\n}\n\n/** The gap a one-place move sends the items at `at` to: before the item ahead of the first (`by` -1), or after the\n * item past the last (`by` 1). Null where that runs off either end of a list `length` long, or for no items. */\nexport function nudgeGap(at: readonly number[], by: -1 | 1, length: number): number | null {\n const s = sortedSet(at);\n if (s.length === 0) return null;\n const to = by < 0 ? s[0]! - 1 : s[s.length - 1]! + 2;\n return to < 0 || to > length ? null : to;\n}\n\n/**\n * The gap a point `p` along a line of items falls in, where each item splits at its middle: how many of the\n * middles `mids`, given in item order, `p` has reached. A line whose middles run backward, as a right-to-left row\n * does along x, counts the other way. For items that sit side by side — rows, tabs, chips.\n */\nexport function gapByMidpoint(mids: readonly number[], p: number): number {\n const backward = mids.length > 1 && mids[mids.length - 1]! < mids[0]!;\n return mids.filter((m) => (backward ? m >= p : m <= p)).length;\n}\n\n/** Which of the gaps at positions `gaps` along a line is nearest `p`, as an index into `gaps`; -1 for none. For\n * gaps drawn at places of their own, between items set apart however the layout put them: picking the nearest\n * keeps the gap chosen the one whose mark is drawn closest. */\nexport function nearestGap(gaps: readonly number[], p: number): number {\n let best = -1;\n for (let k = 0; k < gaps.length; k++) if (best < 0 || Math.abs(gaps[k]! - p) < Math.abs(gaps[best]! - p)) best = k;\n return best;\n}\n\n/** The keys and modifiers a nudge chord reads off a keystroke. */\nexport interface NudgeKey {\n key: string;\n altKey: boolean;\n ctrlKey: boolean;\n metaKey: boolean;\n}\n\n/** Alt with neither Ctrl nor Cmd held: the chord that moves an item instead of moving focus. */\nexport function isNudgeChord(e: NudgeKey): boolean {\n return e.altKey && !e.ctrlKey && !e.metaKey;\n}\n\n/** Which way the nudge chord moves an item: `-1` for Alt+`back`, `1` for Alt+`on`, `null` for any other key.\n * The keys are Up and Down unless named. */\nexport function nudgeOf(e: NudgeKey, back = 'ArrowUp', on = 'ArrowDown'): -1 | 1 | null {\n if (!isNudgeChord(e)) return null;\n if (e.key === back) return -1;\n return e.key === on ? 1 : null;\n}\n","/** Clock and timer, injected so tests and hosts control time. */\nexport interface Scheduler {\n now(): number;\n /** Run `fn` after `ms`; returns a cancel. */\n set(fn: () => void, ms: number): () => void;\n}\n\n/** The real-time scheduler: `Date.now` and `setTimeout`. */\nexport const timerScheduler: Scheduler = {\n now: () => Date.now(),\n set: (fn, ms) => {\n const t = setTimeout(fn, ms);\n return () => clearTimeout(t);\n },\n};\n","import { timerScheduler, type Scheduler } from './scheduler';\n\n/** How long a held stepper waits before it repeats, in milliseconds. */\nexport const REPEAT_DELAY_MS = 400;\n/** How often a held stepper steps once it repeats, in milliseconds. */\nexport const REPEAT_EVERY_MS = 60;\n\n/** The timing of a {@link createStepRepeat}. Each is read when it is next used, so a getter can change it mid-hold. */\nexport interface StepRepeatOptions {\n /** Default {@link REPEAT_DELAY_MS}. */\n delayMs?: number;\n /** Default {@link REPEAT_EVERY_MS}. */\n everyMs?: number;\n /** Default {@link timerScheduler}. */\n scheduler?: Pick<Scheduler, 'set'>;\n}\n\n/** A held stepper: {@link createStepRepeat}. */\nexport interface StepRepeat<K> {\n /** Step `key` once now, then after the delay once every interval until {@link stop}. Replaces any hold. */\n start(key: K): void;\n stop(): void;\n /** The key being held, or null. */\n held(): K | null;\n}\n\n/**\n * Hold-to-repeat stepping, whatever draws the stepper: `step` moves the value by `key`, and `spent` says `key`\n * has nowhere left to go, which ends the hold. Both are called at the moment of each step.\n */\nexport function createStepRepeat<K>(\n step: (key: K) => void, spent: (key: K) => boolean, opts: StepRepeatOptions = {},\n): StepRepeat<K> {\n let cancel: (() => void) | null = null;\n let key: K | null = null;\n let hold = 0;\n const stop = () => {\n cancel?.();\n cancel = null;\n key = null;\n hold++;\n };\n const tick = (k: K, ms: number) => {\n cancel = (opts.scheduler ?? timerScheduler).set(() => {\n // A stepper at its bound may be disabled while still held, and never hear the release that ends the hold.\n if (spent(k)) return stop();\n step(k);\n tick(k, opts.everyMs ?? REPEAT_EVERY_MS);\n }, ms);\n };\n return {\n start(k) {\n stop();\n const mine = hold;\n key = k;\n step(k);\n // `step` may have ended or replaced this hold.\n if (hold === mine) tick(k, opts.delayMs ?? REPEAT_DELAY_MS);\n },\n stop,\n held: () => key,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/host.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { readFileSync } from 'node:fs';\nimport type { Browser, BrowserContext, LaunchOptions, Page } from 'playwright-core';\nimport { PAGE_GLOBAL, sceneToWire, type PageApi, type PageRequest, type PageResult, type SerializableRenderArgs } from './protocol';\n\n/** Where a {@link NodeRasterHost} renders. */\nexport interface NodeRasterHostOptions {\n /** A browser to render in, which the host leaves open on `dispose`. Default: the host launches\n * Playwright's Chromium on its first render and closes it on `dispose`. */\n browser?: Browser;\n /** Launch options for the Chromium the host starts. Ignored with `browser`. */\n launch?: LaunchOptions;\n /** The page script's source: one classic script that imports `@weasel-js/headless/page`.\n * Default: that module bundled alone (`dist/page.global.js`). Bundle your own to register\n * fonts, paint kinds or node shapes before the first render. */\n pageScript?: string;\n}\n\n/** Straight RGBA, top-down, 4 bytes a pixel: core's `RasterImage`. */\nexport interface RasterImage {\n width: number;\n height: number;\n data: Uint8ClampedArray;\n}\n\n/** One render: `renderSceneToPixels`'s arguments, the scene as JSON and no function-valued fields. */\nexport interface NodeRenderArgs extends SerializableRenderArgs {\n /** A live `Scene`, or its `toJSON()` snapshot, raw or saved through `tagTypedArrays`. A scene\n * whose nodes name registry functions (`clipFromPose` and the like) cannot be read back in the\n * page. */\n scene: object;\n /** Screen space → output pixels, as `renderSceneToPixels`'s `screen` (nine numbers, column-major). */\n screen?: ArrayLike<number>;\n}\n\n/** A Chromium page holding one raster session, kept across renders. */\nexport interface NodeRasterHost {\n /** Render to straight RGBA, top-down, as `renderSceneToPixels` returns it. Rejects once disposed. */\n render(args: NodeRenderArgs): Promise<RasterImage>;\n /** Render and encode as PNG. The page encodes through a 2D canvas, which stores premultiplied\n * color, so a translucent pixel's color can move by a step; `render` returns the exact bytes. */\n renderPng(args: NodeRenderArgs): Promise<{ width: number; height: number; png: Uint8Array }>;\n /** Close the page, and the browser when the host launched it. Idempotent. */\n dispose(): Promise<void>;\n [Symbol.asyncDispose](): Promise<void>;\n}\n\ninterface Open {\n browser: Browser;\n context: BrowserContext;\n page: Page;\n}\n\nfunction defaultPageScript(): string {\n const url = new URL('./page.global.js', import.meta.url);\n try {\n return readFileSync(url, 'utf8');\n } catch (err) {\n throw new Error(`node raster host: no page script at ${url.pathname} — pass \\`pageScript\\``, { cause: err });\n }\n}\n\nasync function open(options: NodeRasterHostOptions): Promise<Open> {\n const script = options.pageScript ?? defaultPageScript();\n const browser = options.browser\n ?? await (await import('playwright-core')).chromium.launch({ headless: true, ...options.launch });\n try {\n const context = await browser.newContext({ deviceScaleFactor: 1 });\n const page = await context.newPage();\n await page.addScriptTag({ content: script });\n const ready = await page.evaluate((key) => key in globalThis, PAGE_GLOBAL);\n if (!ready) throw new Error('node raster host: the page script ran but set no render entry');\n return { browser, context, page };\n } catch (err) {\n if (!options.browser) await browser.close();\n throw err;\n }\n}\n\n/**\n * Open a host that renders scenes to pixels from Node, through `renderSceneToPixels` in a headless\n * Chromium page. Nothing launches until the first render; every render after it reuses the page,\n * its WebGL2 context and its GPU caches.\n */\nexport function createNodeRasterHost(options: NodeRasterHostOptions = {}): NodeRasterHost {\n let opening: Promise<Open> | null = null;\n let disposed = false;\n\n async function call(args: NodeRenderArgs, format: PageRequest['format']): Promise<PageResult> {\n if (disposed) throw new Error('node raster host: render on a disposed host');\n opening ??= open(options).catch((err: unknown) => {\n opening = null;\n throw err;\n });\n const { page } = await opening;\n const { scene, screen, ...rest } = args;\n const req: PageRequest = {\n ...rest, scene: sceneToWire(scene), format, ...(screen ? { screen: Array.from(screen) } : {}),\n };\n return page.evaluate(\n ([key, r]) => ((globalThis as unknown as Record<string, PageApi>)[key]!).render(r),\n [PAGE_GLOBAL, req] as const,\n );\n }\n\n async function dispose(): Promise<void> {\n if (disposed) return;\n disposed = true;\n const pending = opening;\n opening = null;\n if (!pending) return;\n const opened = await pending.catch(() => null);\n if (!opened) return;\n if (options.browser) await opened.context.close();\n else await opened.browser.close();\n }\n\n return {\n async render(args) {\n const { width, height, base64 } = await call(args, 'rgba');\n const bytes = Buffer.from(base64, 'base64');\n return { width, height, data: new Uint8ClampedArray(bytes.buffer, bytes.byteOffset, bytes.byteLength) };\n },\n async renderPng(args) {\n const { width, height, base64 } = await call(args, 'png');\n return { width, height, png: new Uint8Array(Buffer.from(base64, 'base64')) };\n },\n dispose,\n [Symbol.asyncDispose]: dispose,\n };\n}\n"]}
|
package/dist/page.d.ts
ADDED