@weasel-js/headless 1.15.0 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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/README.md
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# @weasel-js/headless
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
each drawing it its own way. Today that is a list's roving highlight and typeahead, a tree's
|
|
8
|
-
expand and collapse keys, and keyboard focus as one contract. No React, no DOM.
|
|
3
|
+
Render a weasel scene to pixels from Node — on a server, in a build script, in a CLI. The host
|
|
4
|
+
runs core's `renderSceneToPixels` in a headless Chromium page, so the pixels come from the same
|
|
5
|
+
WebGL2 renderer a browser draws with. Node itself has no WebGL2 binding the renderer can rely on.
|
|
9
6
|
|
|
10
7
|
Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
|
|
11
8
|
scene-graph canvas kit for React. See the
|
|
@@ -15,83 +12,59 @@ scene-graph canvas kit for React. See the
|
|
|
15
12
|
|
|
16
13
|
```sh
|
|
17
14
|
npm install @weasel-js/headless
|
|
15
|
+
npx playwright install chromium
|
|
18
16
|
```
|
|
19
17
|
|
|
18
|
+
The second line downloads the Chromium the host launches. `playwright-core` comes with this
|
|
19
|
+
package; the browser does not.
|
|
20
|
+
|
|
20
21
|
## Usage
|
|
21
22
|
|
|
22
23
|
```ts
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (t.hit !== null) at = t.hit;
|
|
38
|
-
}
|
|
24
|
+
import { writeFile } from 'node:fs/promises';
|
|
25
|
+
import { createNodeRasterHost } from '@weasel-js/headless';
|
|
26
|
+
|
|
27
|
+
const host = createNodeRasterHost();
|
|
28
|
+
try {
|
|
29
|
+
const { png } = await host.renderPng({
|
|
30
|
+
scene, // a live Scene, or its toJSON() snapshot
|
|
31
|
+
sourceRect: { x: 0, y: 0, width: 800, height: 600 },
|
|
32
|
+
scale: { x: 2, y: 2 },
|
|
33
|
+
background: '#ffffff',
|
|
34
|
+
});
|
|
35
|
+
await writeFile('page.png', png);
|
|
36
|
+
} finally {
|
|
37
|
+
await host.dispose();
|
|
39
38
|
}
|
|
40
39
|
```
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
## Trees and rows
|
|
48
|
-
|
|
49
|
-
`treeKey` is a tree's Right and Left, the WAI-ARIA way: Right opens a closed branch or steps to
|
|
50
|
-
its first child, Left closes an open branch or steps to the parent. Its `TreeShape` says which
|
|
51
|
-
items are branches, which are open, and which is whose child.
|
|
52
|
-
|
|
53
|
-
`rowKey` is every key a list of rows answers, in one place: Cmd/Ctrl+A, the arrows with Home and
|
|
54
|
-
End (Shift extends), a tree's Right and Left, Enter and Space, and typeahead. It returns what the
|
|
55
|
-
key asks for (`selectAll`, `move`, `toggle`, `activate`, `type`) and the caller carries it out.
|
|
56
|
-
`@weasel-js/ui`'s `useRowNavigation` runs on it.
|
|
57
|
-
|
|
58
|
-
## Focus
|
|
59
|
-
|
|
60
|
-
`FocusScope<T>` is keyboard focus over a set of targets, whatever draws them: which one is
|
|
61
|
-
focused, whether its ring shows, `focus`, `moveFocus` through a `tabOrder`, and
|
|
62
|
-
`subscribeFocus`. A `@weasel-js/hud` HUD is one over its widgets, and `@weasel-js/ui`'s
|
|
63
|
-
`domFocusScope(root)` is one over the elements under `root`, so code that moves focus is
|
|
64
|
-
written once for both. `tabSort` and `tabStep` are the order and the step both use.
|
|
65
|
-
|
|
66
|
-
## Steps and sliders
|
|
67
|
-
|
|
68
|
-
`arrowSteps`, `arrowMove` and `rangeKey` are what an arrow key asks for: one step, or `COARSE_STEPS`
|
|
69
|
-
(ten) with Shift or a Page key, and Home and End for a range's ends. `sliderKey` turns one key into
|
|
70
|
-
a thumb's next value, by step or by whole stops, across an endless end and inside its neighbors;
|
|
71
|
-
`@weasel-js/ui`'s `Slider` runs on it.
|
|
41
|
+
`render` returns the raw raster instead: `{ width, height, data }`, straight RGBA, top-down, the
|
|
42
|
+
bytes `renderSceneToPixels` returns. `renderPng` encodes in the page through a 2D canvas, which
|
|
43
|
+
stores premultiplied color, so a translucent pixel can move by a step; use `render` when the exact
|
|
44
|
+
bytes matter.
|
|
72
45
|
|
|
73
|
-
|
|
46
|
+
Nothing launches until the first render. Every render after it reuses the page, its WebGL2 context
|
|
47
|
+
and its compiled programs, so open one host for a batch and `dispose` it at the end (or hold it in
|
|
48
|
+
`await using`). `dispose` closes the browser the host launched. Pass `browser` to render in one you
|
|
49
|
+
launched yourself; the host then closes only its own page.
|
|
74
50
|
|
|
75
|
-
|
|
76
|
-
A field's shape can make Escape commit too, and a `multiline` one leaves Shift+Enter to type a
|
|
77
|
-
line break. Caret movement stays with each field: a DOM field gets it from the browser.
|
|
51
|
+
## What crosses into the page
|
|
78
52
|
|
|
79
|
-
|
|
53
|
+
The scene and the arguments go as JSON. What `renderSceneToPixels` takes as a function stays
|
|
54
|
+
behind: `drawOne`, `resolveImage`, `alphaFor` and `colorOverrides`. Nodes draw through the
|
|
55
|
+
painters registered in the page, which by default are core's built-ins. A path's typed arrays
|
|
56
|
+
are tagged on the way over (`tagTypedArrays` from `@weasel-js/geom`), so a snapshot saved that way
|
|
57
|
+
passes as it is.
|
|
80
58
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
one-place move takes them to, and `nudgeOf` reads the Alt+arrow chord that asks for one. A pointer finds its gap by
|
|
85
|
-
`gapByMidpoint`, for items side by side, each split at its middle, or by `nearestGap`, for gaps drawn at places of
|
|
86
|
-
their own.
|
|
59
|
+
The default page registers no fonts, so text draws nothing. To render text, or a paint kind or
|
|
60
|
+
node shape of your own, bundle a page script that registers it and then imports
|
|
61
|
+
`@weasel-js/headless/page`, as one classic script, and pass its source as `pageScript`.
|
|
87
62
|
|
|
88
|
-
##
|
|
63
|
+
## Pixels and the browser's
|
|
89
64
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
default `timerScheduler`; pass your own to drive it from another clock or a test. `@weasel-js/ui`'s
|
|
94
|
-
number field steppers and `@weasel-js/hud`'s menu number chips both hold through it.
|
|
65
|
+
On one machine, the host's pixels equal the ones a page in Playwright's Chromium draws for the
|
|
66
|
+
same scene: one WebGL2 implementation, given the same commands, draws the same bytes. Across GPUs
|
|
67
|
+
and drivers they need not match, so don't compare a render against committed pixels.
|
|
95
68
|
|
|
96
69
|
## License
|
|
97
70
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { tagTypedArrays, reviveTypedArrays } from '@weasel-js/geom';
|
|
2
|
+
|
|
3
|
+
// src/protocol.ts
|
|
4
|
+
var PAGE_GLOBAL = "__weaselHeadless";
|
|
5
|
+
function sceneToWire(scene) {
|
|
6
|
+
const toJSON = scene.toJSON;
|
|
7
|
+
return tagTypedArrays(typeof toJSON === "function" ? toJSON.call(scene) : scene);
|
|
8
|
+
}
|
|
9
|
+
function sceneFromWire(wire) {
|
|
10
|
+
return reviveTypedArrays(wire);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { PAGE_GLOBAL, sceneFromWire, sceneToWire };
|
|
14
|
+
//# sourceMappingURL=chunk-36DNDFOQ.js.map
|
|
15
|
+
//# sourceMappingURL=chunk-36DNDFOQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/protocol.ts"],"names":[],"mappings":";;;AAKO,IAAM,WAAA,GAAc;AAsCpB,SAAS,YAAY,KAAA,EAAuB;AACjD,EAAA,MAAM,SAAU,KAAA,CAA+B,MAAA;AAC/C,EAAA,OAAO,cAAA,CAAe,OAAO,MAAA,KAAW,UAAA,GAAa,OAAO,IAAA,CAAK,KAAK,IAAI,KAAK,CAAA;AACjF;AAGO,SAAS,cAAc,IAAA,EAAsB;AAClD,EAAA,OAAO,kBAAkB,IAAI,CAAA;AAC/B","file":"chunk-36DNDFOQ.js","sourcesContent":["// What crosses between Node and the page: JSON only, so pixels travel as base64. Typed here rather\n// than from core, whose types reach React and the DOM, neither of which a Node caller has.\nimport { reviveTypedArrays, tagTypedArrays } from '@weasel-js/geom';\n\n/** The page script's global, set once the script has run. */\nexport const PAGE_GLOBAL = '__weaselHeadless';\n\n/** `renderSceneToPixels`'s arguments that survive JSON: no `drawOne`, `resolveImage`, `alphaFor`\n * or `colorOverrides`. Each means what it means there. */\nexport interface SerializableRenderArgs {\n /** Scene-space rect to render. */\n sourceRect: { x: number; y: number; width: number; height: number };\n /** Output pixels per scene unit, per axis. */\n scale: { x: number; y: number };\n /** Background fill, any CSS color. Default: transparent. */\n background?: string;\n /** Max curve-flattening error in output pixels. Default 0.25. */\n flattenTolerancePx?: number;\n /** Draw commands over the scene in output-pixel space, as `renderSceneToPixels` takes them. */\n overlay?: readonly object[];\n /** Draw the scene's annotation layers. Default `false`. */\n annotations?: boolean;\n}\n\nexport interface PageRequest extends SerializableRenderArgs {\n /** A `scene.toJSON()` snapshot. */\n scene: object;\n screen?: readonly number[];\n format: 'rgba' | 'png';\n}\n\nexport interface PageResult {\n width: number;\n height: number;\n base64: string;\n}\n\nexport interface PageApi {\n render(req: PageRequest): Promise<PageResult>;\n}\n\n/** A live scene or its snapshot, as JSON carries it: a path's typed arrays tagged, as\n * `tagTypedArrays` writes them. A snapshot already saved that way passes through unchanged. */\nexport function sceneToWire(scene: object): object {\n const toJSON = (scene as { toJSON?: unknown }).toJSON;\n return tagTypedArrays(typeof toJSON === 'function' ? toJSON.call(scene) : scene) as object;\n}\n\n/** The snapshot `sceneToWire` sent, its typed arrays restored. */\nexport function sceneFromWire(wire: object): object {\n return reviveTypedArrays(wire) as object;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,326 +1,77 @@
|
|
|
1
|
-
|
|
1
|
+
import { Browser, LaunchOptions } from 'playwright-core';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
/** `renderSceneToPixels`'s arguments that survive JSON: no `drawOne`, `resolveImage`, `alphaFor`
|
|
4
|
+
* or `colorOverrides`. Each means what it means there. */
|
|
5
|
+
interface SerializableRenderArgs {
|
|
6
|
+
/** Scene-space rect to render. */
|
|
7
|
+
sourceRect: {
|
|
8
|
+
x: number;
|
|
9
|
+
y: number;
|
|
10
|
+
width: number;
|
|
11
|
+
height: number;
|
|
12
|
+
};
|
|
13
|
+
/** Output pixels per scene unit, per axis. */
|
|
14
|
+
scale: {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
};
|
|
18
|
+
/** Background fill, any CSS color. Default: transparent. */
|
|
19
|
+
background?: string;
|
|
20
|
+
/** Max curve-flattening error in output pixels. Default 0.25. */
|
|
21
|
+
flattenTolerancePx?: number;
|
|
22
|
+
/** Draw commands over the scene in output-pixel space, as `renderSceneToPixels` takes them. */
|
|
23
|
+
overlay?: readonly object[];
|
|
24
|
+
/** Draw the scene's annotation layers. Default `false`. */
|
|
25
|
+
annotations?: boolean;
|
|
14
26
|
}
|
|
15
|
-
/** What Right or Left does on one item of a tree: open or close it, move the highlight, or nothing (`handled` false).
|
|
16
|
-
* `at` null is a key the tree takes without moving. */
|
|
17
|
-
type TreeKeyResult = {
|
|
18
|
-
handled: false;
|
|
19
|
-
} | {
|
|
20
|
-
handled: true;
|
|
21
|
-
toggle: number;
|
|
22
|
-
} | {
|
|
23
|
-
handled: true;
|
|
24
|
-
at: number | null;
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Right and Left on the item at `at`, the WAI-ARIA tree way: Right opens a closed branch, else steps to its first
|
|
28
|
-
* child; Left closes an open branch, else steps to the parent. Mirrored under `dir: 'rtl'`. `items` are the rows
|
|
29
|
-
* shown, in drawn order.
|
|
30
|
-
*/
|
|
31
|
-
declare function treeKey<T>(items: readonly T[], at: number, key: string, shape: TreeShape<T>): TreeKeyResult;
|
|
32
|
-
|
|
33
|
-
/** How a list's items are read: which ones the highlight can land on, and which way its arrows run. */
|
|
34
|
-
interface ListShape<T> {
|
|
35
|
-
/** Whether the highlight can land on `item`. A separator or a heading can't. Default: every item. */
|
|
36
|
-
canStop?(item: T): boolean;
|
|
37
|
-
/** Which arrow keys move the highlight. `'both'` answers to all four, the way a radio group does. Default `'vertical'`. */
|
|
38
|
-
orientation?: 'vertical' | 'horizontal' | 'both';
|
|
39
|
-
/** The inline direction the items are laid out in. Under `'rtl'` Left steps forward and Right back. Default `'ltr'`. */
|
|
40
|
-
dir?: 'ltr' | 'rtl';
|
|
41
|
-
/** An arrow past either end comes round to the other. Default true. */
|
|
42
|
-
wrap?: boolean;
|
|
43
|
-
}
|
|
44
|
-
/** Index of the first item the highlight can land on, or null for a list with none. */
|
|
45
|
-
declare function firstStop<T>(items: readonly T[], shape?: ListShape<T>): number | null;
|
|
46
|
-
/** The next item the highlight can land on from `from` in direction `dir`; from `null`, the first or last.
|
|
47
|
-
* Without wrap, an end stays where it is. Null for a list with no such item. */
|
|
48
|
-
declare function stepStop<T>(items: readonly T[], from: number | null, dir: 1 | -1, shape?: ListShape<T>): number | null;
|
|
49
|
-
/** What one key does to a list's highlight. `handled` false for a key that is not the list's to move. */
|
|
50
|
-
type RovingResult = {
|
|
51
|
-
handled: false;
|
|
52
|
-
} | {
|
|
53
|
-
handled: true;
|
|
54
|
-
at: number | null;
|
|
55
|
-
};
|
|
56
|
-
/** Move the highlight `at` by one key: the arrows along the list's orientation, Home and End. */
|
|
57
|
-
declare function rovingKey<T>(items: readonly T[], at: number | null, key: string, shape?: ListShape<T>): RovingResult;
|
|
58
|
-
/** A key as {@link rowKey} reads it: the key and the modifiers held. */
|
|
59
|
-
interface KeyPress {
|
|
60
|
-
key: string;
|
|
61
|
-
shiftKey?: boolean;
|
|
62
|
-
ctrlKey?: boolean;
|
|
63
|
-
metaKey?: boolean;
|
|
64
|
-
altKey?: boolean;
|
|
65
|
-
}
|
|
66
|
-
/** A list of rows, and which of the keys past the arrows its caller answers. */
|
|
67
|
-
interface RowKeysShape<T> extends ListShape<T> {
|
|
68
|
-
/** Set for a list that folds: Right and Left open, close, and walk it ({@link treeKey}). */
|
|
69
|
-
tree?: TreeShape<T>;
|
|
70
|
-
/** Cmd/Ctrl+A selects every row. */
|
|
71
|
-
selectAll?: boolean;
|
|
72
|
-
/** Enter and Space activate a row. */
|
|
73
|
-
activate?: boolean;
|
|
74
|
-
/** Typing a character searches the rows' text. */
|
|
75
|
-
typeahead?: boolean;
|
|
76
|
-
}
|
|
77
|
-
/** What one key on a row asks for, for the caller to carry out. */
|
|
78
|
-
type RowKeyAction =
|
|
79
|
-
/** Select every row. */
|
|
80
|
-
{
|
|
81
|
-
kind: 'selectAll';
|
|
82
|
-
}
|
|
83
|
-
/** Move to row `at`, extending a range from the anchor when `extend`. `at` null takes the key without moving. */
|
|
84
|
-
| {
|
|
85
|
-
kind: 'move';
|
|
86
|
-
at: number | null;
|
|
87
|
-
extend: boolean;
|
|
88
|
-
}
|
|
89
|
-
/** Open or close the branch at `at`. */
|
|
90
|
-
| {
|
|
91
|
-
kind: 'toggle';
|
|
92
|
-
at: number;
|
|
93
|
-
} | {
|
|
94
|
-
kind: 'activate';
|
|
95
|
-
}
|
|
96
|
-
/** Hand the key to typeahead. `held` when the key is the list's whether or not the search finds a row. */
|
|
97
|
-
| {
|
|
98
|
-
kind: 'type';
|
|
99
|
-
held: boolean;
|
|
100
|
-
};
|
|
101
|
-
/**
|
|
102
|
-
* 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
|
|
103
|
-
* and End (Shift to extend), a tree's Right and Left, a space typed mid-search, Enter or Space to activate, then any
|
|
104
|
-
* other character to search. `searching` says a typeahead search is under way. Null for a key that is not the list's.
|
|
105
|
-
*/
|
|
106
|
-
declare function rowKey<T>(items: readonly T[], at: number, press: KeyPress, shape?: RowKeysShape<T>, searching?: boolean): RowKeyAction | null;
|
|
107
|
-
|
|
108
|
-
/** How typeahead reads a list: its shape, and the text each item is found by. */
|
|
109
|
-
interface TypeaheadShape<T> extends Pick<ListShape<T>, 'canStop'> {
|
|
110
|
-
label(item: T): string;
|
|
111
|
-
}
|
|
112
|
-
/** How long typing can pause before the typeahead buffer starts over, in milliseconds. */
|
|
113
|
-
declare const TYPEAHEAD_RESET_MS = 500;
|
|
114
|
-
/** What has been typed so far, and when the last key came. */
|
|
115
|
-
interface TypeaheadBuffer {
|
|
116
|
-
text: string;
|
|
117
|
-
at: number;
|
|
118
|
-
}
|
|
119
|
-
/** A buffer nothing has been typed into. */
|
|
120
|
-
declare const EMPTY_TYPEAHEAD: TypeaheadBuffer;
|
|
121
|
-
/** The item a typed `query` lands on, searching forward from `from` with
|
|
122
|
-
* wrap. A one-letter query starts after the highlight so repeating a letter
|
|
123
|
-
* cycles; a longer one starts at it so typing on keeps the match. */
|
|
124
|
-
declare function typeaheadMatch<T>(items: readonly T[], from: number | null, query: string, shape: TypeaheadShape<T>): number | null;
|
|
125
|
-
/** One typed character: the new buffer and the item it lands on. A key that
|
|
126
|
-
* repeats the whole buffer searches that one letter, so it cycles. */
|
|
127
|
-
declare function typeaheadStep<T>(items: readonly T[], from: number | null, buffer: TypeaheadBuffer, key: string, now: number, shape: TypeaheadShape<T>, resetMs?: number): {
|
|
128
|
-
buffer: TypeaheadBuffer;
|
|
129
|
-
hit: number | null;
|
|
130
|
-
};
|
|
131
27
|
|
|
132
|
-
/**
|
|
133
|
-
interface
|
|
134
|
-
/**
|
|
135
|
-
*
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
*/
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
dx: number;
|
|
174
|
-
dy: number;
|
|
175
|
-
} | null;
|
|
176
|
-
/** What a key does along a range: {@link arrowSteps}, the Page keys ({@link COARSE_STEPS} steps),
|
|
177
|
-
* or Home and End (an end of the range). `null` for any other key. */
|
|
178
|
-
declare function rangeKey(key: string, shift: boolean, dir?: 'ltr' | 'rtl'): number | 'home' | 'end' | null;
|
|
179
|
-
|
|
180
|
-
/** A value on a slider's track the thumb can come to rest on, with whatever label draws under it. */
|
|
181
|
-
interface SliderStop<L = unknown> {
|
|
182
|
-
value: number;
|
|
183
|
-
label?: L;
|
|
184
|
-
}
|
|
185
|
-
/** The stops that are actually reachable: inside the range, deduped, ascending.
|
|
186
|
-
* A duplicate keeps the first label given for its value. */
|
|
187
|
-
declare function usableStops<L>(stops: readonly (number | SliderStop<L>)[] | undefined, min: number, max: number): SliderStop<L>[];
|
|
188
|
-
/** The stop nearest `v` along the track, or `v` itself when none lies within
|
|
189
|
-
* `tolerance` (a track fraction). Measured on the track rather than in value
|
|
190
|
-
* units so evenly spaced stops attract over the same distance. */
|
|
191
|
-
declare function attract(v: number, stops: readonly number[], tolerance: number, toFraction: (v: number) => number): number;
|
|
192
|
-
/** Keep thumb `index` strictly between its neighbors: a step apart, or a thousandth of the span without one. */
|
|
193
|
-
declare function clampOrdered(v: number, thumbs: readonly {
|
|
194
|
-
value: number;
|
|
195
|
-
}[], index: number, min: number, max: number, step: number | undefined): number;
|
|
196
|
-
/** What a key press on one thumb of a slider reads. */
|
|
197
|
-
interface SliderKeyShape {
|
|
198
|
-
thumbs: readonly {
|
|
199
|
-
value: number;
|
|
200
|
-
}[];
|
|
201
|
-
/** The thumb the key went to. */
|
|
202
|
-
index: number;
|
|
203
|
-
/** The ends of the track. */
|
|
204
|
-
min: number;
|
|
205
|
-
max: number;
|
|
206
|
-
step?: number;
|
|
207
|
-
/** Usable stops, ascending. With any, a keystroke moves by whole stops rather than by value. */
|
|
208
|
-
stops?: readonly number[];
|
|
209
|
-
/** Keep the thumb between its neighbors. */
|
|
210
|
-
ordered?: boolean;
|
|
211
|
-
/** The finite range, where the track runs past it to an endless end's stop. Default the track. */
|
|
212
|
-
range?: readonly [number, number];
|
|
213
|
-
/** Under `'rtl'` the horizontal arrows are mirrored, as the values run from the right end. */
|
|
214
|
-
dir?: 'ltr' | 'rtl';
|
|
215
|
-
/** This thumb's own limits inside the track. Read only for a key the slider answers to. */
|
|
216
|
-
bounds?(): readonly [number, number];
|
|
217
|
-
/** Where the thumb lands `steps` presses away, for a track whose values aren't what the user counts in. */
|
|
218
|
-
keyStep?(steps: number): number;
|
|
219
|
-
}
|
|
220
|
-
/** The value one key moves thumb `index` to: arrows by a step (ten with Shift), the Page keys by ten,
|
|
221
|
-
* Home and End to the thumb's ends. `null` for a key the slider does not answer to. */
|
|
222
|
-
declare function sliderKey(key: string, shift: boolean, shape: SliderKeyShape): number | null;
|
|
223
|
-
|
|
224
|
-
/** How a text field's edit ends: `'commit'` writes what was typed, `'cancel'` drops it and puts back what was there. */
|
|
225
|
-
type EditEnd = 'commit' | 'cancel';
|
|
226
|
-
/** What a field varies in how its edit ends: what Escape does (`'cancel'` by default), and whether the field holds
|
|
227
|
-
* more than one line, which leaves Shift+Enter to type a line break rather than commit. */
|
|
228
|
-
interface EditEndShape {
|
|
229
|
-
escape?: EditEnd;
|
|
230
|
-
multiline?: boolean;
|
|
231
|
-
}
|
|
232
|
-
/** What may end an edit: a key, with whether Shift is held, or the field losing focus. A key {@link imeKey} claims is
|
|
233
|
-
* the caller's to hold back. */
|
|
234
|
-
type EditCause = 'blur' | {
|
|
235
|
-
key: string;
|
|
236
|
-
shift?: boolean;
|
|
237
|
-
};
|
|
238
|
-
/** How `cause` ends a text field's edit, or null when it leaves the edit open. Enter and blur commit, Escape cancels
|
|
239
|
-
* unless `shape` says otherwise. */
|
|
240
|
-
declare function editEnd(cause: EditCause, shape?: EditEndShape): EditEnd | null;
|
|
241
|
-
/** A keydown as the platform reports it, enough to tell whether an input method owns it. */
|
|
242
|
-
interface KeySource {
|
|
243
|
-
key: string;
|
|
244
|
-
isComposing?: boolean;
|
|
245
|
-
keyCode?: number;
|
|
246
|
-
}
|
|
247
|
-
/** Whether a keydown belongs to an input method, which a field must leave alone: Enter there confirms a candidate and
|
|
248
|
-
* Escape dismisses them. Safari sends the Enter that ends a composition with `isComposing` already false, but with
|
|
249
|
-
* `keyCode` 229. */
|
|
250
|
-
declare function imeKey(e: KeySource): boolean;
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Moving items within an ordered list. A destination is a gap, counted before anything is taken out: 0 before the
|
|
254
|
-
* first item, `length` after the last, and `i` between `items[i - 1]` and `items[i]` — the gap a UI marks while it
|
|
255
|
-
* carries something. The items moved are named by their indices, so a selection of several moves in one go.
|
|
256
|
-
*/
|
|
257
|
-
/** `items` with the items at indices `at` taken out and put back, in their order, in gap `to`. */
|
|
258
|
-
declare function moveItems<T>(items: readonly T[], at: readonly number[], to: number): T[];
|
|
259
|
-
/** The index the first of the items at `at` holds once {@link moveItems} has put them in gap `to`. */
|
|
260
|
-
declare function movedIndex(at: readonly number[], to: number): number;
|
|
261
|
-
/** Whether putting the items at `at` in gap `to` leaves them where they are: a contiguous run dropped in a gap at
|
|
262
|
-
* either of its ends or inside it. False for no items. */
|
|
263
|
-
declare function isNoopDrop(at: readonly number[], to: number): boolean;
|
|
264
|
-
/** The gap a one-place move sends the items at `at` to: before the item ahead of the first (`by` -1), or after the
|
|
265
|
-
* item past the last (`by` 1). Null where that runs off either end of a list `length` long, or for no items. */
|
|
266
|
-
declare function nudgeGap(at: readonly number[], by: -1 | 1, length: number): number | null;
|
|
267
|
-
/**
|
|
268
|
-
* The gap a point `p` along a line of items falls in, where each item splits at its middle: how many of the
|
|
269
|
-
* middles `mids`, given in item order, `p` has reached. A line whose middles run backward, as a right-to-left row
|
|
270
|
-
* does along x, counts the other way. For items that sit side by side — rows, tabs, chips.
|
|
271
|
-
*/
|
|
272
|
-
declare function gapByMidpoint(mids: readonly number[], p: number): number;
|
|
273
|
-
/** Which of the gaps at positions `gaps` along a line is nearest `p`, as an index into `gaps`; -1 for none. For
|
|
274
|
-
* gaps drawn at places of their own, between items set apart however the layout put them: picking the nearest
|
|
275
|
-
* keeps the gap chosen the one whose mark is drawn closest. */
|
|
276
|
-
declare function nearestGap(gaps: readonly number[], p: number): number;
|
|
277
|
-
/** The keys and modifiers a nudge chord reads off a keystroke. */
|
|
278
|
-
interface NudgeKey {
|
|
279
|
-
key: string;
|
|
280
|
-
altKey: boolean;
|
|
281
|
-
ctrlKey: boolean;
|
|
282
|
-
metaKey: boolean;
|
|
283
|
-
}
|
|
284
|
-
/** Alt with neither Ctrl nor Cmd held: the chord that moves an item instead of moving focus. */
|
|
285
|
-
declare function isNudgeChord(e: NudgeKey): boolean;
|
|
286
|
-
/** Which way the nudge chord moves an item: `-1` for Alt+`back`, `1` for Alt+`on`, `null` for any other key.
|
|
287
|
-
* The keys are Up and Down unless named. */
|
|
288
|
-
declare function nudgeOf(e: NudgeKey, back?: string, on?: string): -1 | 1 | null;
|
|
289
|
-
|
|
290
|
-
/** Clock and timer, injected so tests and hosts control time. */
|
|
291
|
-
interface Scheduler {
|
|
292
|
-
now(): number;
|
|
293
|
-
/** Run `fn` after `ms`; returns a cancel. */
|
|
294
|
-
set(fn: () => void, ms: number): () => void;
|
|
295
|
-
}
|
|
296
|
-
/** The real-time scheduler: `Date.now` and `setTimeout`. */
|
|
297
|
-
declare const timerScheduler: Scheduler;
|
|
298
|
-
|
|
299
|
-
/** How long a held stepper waits before it repeats, in milliseconds. */
|
|
300
|
-
declare const REPEAT_DELAY_MS = 400;
|
|
301
|
-
/** How often a held stepper steps once it repeats, in milliseconds. */
|
|
302
|
-
declare const REPEAT_EVERY_MS = 60;
|
|
303
|
-
/** The timing of a {@link createStepRepeat}. Each is read when it is next used, so a getter can change it mid-hold. */
|
|
304
|
-
interface StepRepeatOptions {
|
|
305
|
-
/** Default {@link REPEAT_DELAY_MS}. */
|
|
306
|
-
delayMs?: number;
|
|
307
|
-
/** Default {@link REPEAT_EVERY_MS}. */
|
|
308
|
-
everyMs?: number;
|
|
309
|
-
/** Default {@link timerScheduler}. */
|
|
310
|
-
scheduler?: Pick<Scheduler, 'set'>;
|
|
311
|
-
}
|
|
312
|
-
/** A held stepper: {@link createStepRepeat}. */
|
|
313
|
-
interface StepRepeat<K> {
|
|
314
|
-
/** Step `key` once now, then after the delay once every interval until {@link stop}. Replaces any hold. */
|
|
315
|
-
start(key: K): void;
|
|
316
|
-
stop(): void;
|
|
317
|
-
/** The key being held, or null. */
|
|
318
|
-
held(): K | null;
|
|
28
|
+
/** Where a {@link NodeRasterHost} renders. */
|
|
29
|
+
interface NodeRasterHostOptions {
|
|
30
|
+
/** A browser to render in, which the host leaves open on `dispose`. Default: the host launches
|
|
31
|
+
* Playwright's Chromium on its first render and closes it on `dispose`. */
|
|
32
|
+
browser?: Browser;
|
|
33
|
+
/** Launch options for the Chromium the host starts. Ignored with `browser`. */
|
|
34
|
+
launch?: LaunchOptions;
|
|
35
|
+
/** The page script's source: one classic script that imports `@weasel-js/headless/page`.
|
|
36
|
+
* Default: that module bundled alone (`dist/page.global.js`). Bundle your own to register
|
|
37
|
+
* fonts, paint kinds or node shapes before the first render. */
|
|
38
|
+
pageScript?: string;
|
|
39
|
+
}
|
|
40
|
+
/** Straight RGBA, top-down, 4 bytes a pixel: core's `RasterImage`. */
|
|
41
|
+
interface RasterImage {
|
|
42
|
+
width: number;
|
|
43
|
+
height: number;
|
|
44
|
+
data: Uint8ClampedArray;
|
|
45
|
+
}
|
|
46
|
+
/** One render: `renderSceneToPixels`'s arguments, the scene as JSON and no function-valued fields. */
|
|
47
|
+
interface NodeRenderArgs extends SerializableRenderArgs {
|
|
48
|
+
/** A live `Scene`, or its `toJSON()` snapshot, raw or saved through `tagTypedArrays`. A scene
|
|
49
|
+
* whose nodes name registry functions (`clipFromPose` and the like) cannot be read back in the
|
|
50
|
+
* page. */
|
|
51
|
+
scene: object;
|
|
52
|
+
/** Screen space → output pixels, as `renderSceneToPixels`'s `screen` (nine numbers, column-major). */
|
|
53
|
+
screen?: ArrayLike<number>;
|
|
54
|
+
}
|
|
55
|
+
/** A Chromium page holding one raster session, kept across renders. */
|
|
56
|
+
interface NodeRasterHost {
|
|
57
|
+
/** Render to straight RGBA, top-down, as `renderSceneToPixels` returns it. Rejects once disposed. */
|
|
58
|
+
render(args: NodeRenderArgs): Promise<RasterImage>;
|
|
59
|
+
/** Render and encode as PNG. The page encodes through a 2D canvas, which stores premultiplied
|
|
60
|
+
* color, so a translucent pixel's color can move by a step; `render` returns the exact bytes. */
|
|
61
|
+
renderPng(args: NodeRenderArgs): Promise<{
|
|
62
|
+
width: number;
|
|
63
|
+
height: number;
|
|
64
|
+
png: Uint8Array;
|
|
65
|
+
}>;
|
|
66
|
+
/** Close the page, and the browser when the host launched it. Idempotent. */
|
|
67
|
+
dispose(): Promise<void>;
|
|
68
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
319
69
|
}
|
|
320
70
|
/**
|
|
321
|
-
*
|
|
322
|
-
*
|
|
71
|
+
* Open a host that renders scenes to pixels from Node, through `renderSceneToPixels` in a headless
|
|
72
|
+
* Chromium page. Nothing launches until the first render; every render after it reuses the page,
|
|
73
|
+
* its WebGL2 context and its GPU caches.
|
|
323
74
|
*/
|
|
324
|
-
declare function
|
|
75
|
+
declare function createNodeRasterHost(options?: NodeRasterHostOptions): NodeRasterHost;
|
|
325
76
|
|
|
326
|
-
export {
|
|
77
|
+
export { type NodeRasterHost, type NodeRasterHostOptions, type NodeRenderArgs, type RasterImage, type SerializableRenderArgs, createNodeRasterHost };
|