@glissade/scene 0.19.1 → 0.20.0-pre.4
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/dist/collapseReplacer.d.ts +40 -0
- package/dist/constructionProps.js +82 -0
- package/dist/describe.d.ts +27 -1
- package/dist/describe.js +130 -24
- package/dist/diagnostics.d.ts +159 -0
- package/dist/diagnostics.js +384 -0
- package/dist/displayList.d.ts +27 -0
- package/dist/grid.d.ts +56 -0
- package/dist/grid.js +83 -0
- package/dist/identity.d.ts +33 -0
- package/dist/identity.js +67 -0
- package/dist/index.d.ts +14 -302
- package/dist/index.js +19 -523
- package/dist/layout.d.ts +3 -95
- package/dist/layout.js +11 -196
- package/dist/layoutCtors.d.ts +99 -0
- package/dist/layoutCtors.js +210 -0
- package/dist/motion.d.ts +60 -0
- package/dist/motion.js +171 -0
- package/dist/nodes.d.ts +32 -8
- package/dist/nodes.js +62 -187
- package/dist/scene.d.ts +71 -0
- package/dist/scene.js +120 -0
- package/package.json +23 -3
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/collapseReplacer.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* The byte-preserving collapse-replacer (DESIGN.md §3.3 / §3.5).
|
|
4
|
+
*
|
|
5
|
+
* Extracted to its OWN tiny module in the 0.20 budget review so the heavy
|
|
6
|
+
* DEV/CLI diagnostic surface (`diffDisplayLists`/`serializeDisplayList`/… in
|
|
7
|
+
* `displayDiff.ts`, now on the `@glissade/scene/diagnostics` subpath) can leave
|
|
8
|
+
* the base scene graph while THIS function — which IS on the render path — stays
|
|
9
|
+
* reachable from `displayList.ts` (the §3.5 cacheKey serializer) at a few bytes.
|
|
10
|
+
* Before the split, `displayList.ts` imported `collapseReplacer` from
|
|
11
|
+
* `displayDiff.ts`, dragging the entire diff/snapshot module into every embed.
|
|
12
|
+
*
|
|
13
|
+
* Shared by the cacheKey serializer (`createDisplayListBuilder().cacheKey`),
|
|
14
|
+
* `cacheColdAudit.hashDisplayList`, and `serializeDisplayList`. CRITICAL: this is
|
|
15
|
+
* BYTE-PRESERVING — the cacheKey it backs stamps into pushGroup and keys the
|
|
16
|
+
* §3.5 raster cache, so its output must not move. The three call sites previously
|
|
17
|
+
* DUPLICATED this byte-for-byte; it lives here once.
|
|
18
|
+
*
|
|
19
|
+
* - ArrayBuffer / typed-array views collapse to a `ab:<len>` / `view:<len>`
|
|
20
|
+
* length marker (opaque binary never belongs in a structural key).
|
|
21
|
+
* - Functions drop (JSON would already drop them; explicit for parity).
|
|
22
|
+
* - Non-finite numbers (`NaN`/`Infinity`/`-Infinity`) collapse to DISTINCT
|
|
23
|
+
* string sentinels. `JSON.stringify` natively serializes all three to the
|
|
24
|
+
* SAME token (`null`), which would collide the cacheKey of two DisplayLists
|
|
25
|
+
* that differ only in WHICH non-finite value reaches a draw field — a stale
|
|
26
|
+
* raster + an `auditCacheCold` false-OK. The distinct sentinels keep them
|
|
27
|
+
* apart. This does NOT touch FINITE numbers (the common path): only the
|
|
28
|
+
* three non-finite inputs change, so the §3.5 cacheKey bytes for every real
|
|
29
|
+
* (finite) list are byte-identical — pinned by the regression guard.
|
|
30
|
+
*
|
|
31
|
+
* NOTE: `-0` is intentionally NOT normalized here. The matrix layer
|
|
32
|
+
* (`matrix.ts`) already normalizes `-0 → 0` at the source, and adding a `-0`
|
|
33
|
+
* pass to THIS replacer would change the cacheKey bytes for any list that ever
|
|
34
|
+
* carried a raw `-0` — silently invalidating the cache cluster-wide. (`-0` is
|
|
35
|
+
* finite, so the non-finite branch never touches it.) Byte preservation wins;
|
|
36
|
+
* the regression guard pins the exact key.
|
|
37
|
+
*/
|
|
38
|
+
declare function collapseReplacer(_key: string, value: unknown): unknown;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { collapseReplacer as t };
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
//#region src/constructionProps.ts
|
|
2
|
+
/**
|
|
3
|
+
* The SLIM construction-prop NAME set (0.20) — the embed-path-safe half of the
|
|
4
|
+
* describe() schema. A construction prop (`animatable: false`) is passed to a
|
|
5
|
+
* node's constructor and is NEVER a track target; binding one is rejected.
|
|
6
|
+
*
|
|
7
|
+
* This module carries ONLY the per-node-type set of construction-prop NAMES
|
|
8
|
+
* (plus the shared base set), nothing else — no value types, no `required`
|
|
9
|
+
* flags, no manifest scaffolding. That keeps it tiny: it rides on the
|
|
10
|
+
* embed/bind path (the bind guard imports it to turn a generic UnboundTarget
|
|
11
|
+
* into a friendlier "that's a construction prop" message), so it must stay
|
|
12
|
+
* byte-cheap. `describe.ts` imports the SAME sets and layers its richer
|
|
13
|
+
* per-prop metadata (type/required) on top, so the manifest and this lookup
|
|
14
|
+
* can't drift.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Base `NodeProps` construction-prop names shared by EVERY node — set once at
|
|
18
|
+
* `new Node({...})`, never animatable (none is a registered target).
|
|
19
|
+
*/
|
|
20
|
+
const BASE_CONSTRUCTION_PROP_NAMES = [
|
|
21
|
+
"id",
|
|
22
|
+
"blend",
|
|
23
|
+
"filters",
|
|
24
|
+
"anchor",
|
|
25
|
+
"cache"
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Each built-in node's OWN construction-prop names (the base set is merged in
|
|
29
|
+
* separately, via {@link isConstructionProp}). Keyed by the describe TYPE NAME
|
|
30
|
+
* (e.g. `Image`, not the `ImageNode` class name).
|
|
31
|
+
*/
|
|
32
|
+
const SKETCH = [
|
|
33
|
+
"sketch",
|
|
34
|
+
"sketchFill",
|
|
35
|
+
"sketchSeed"
|
|
36
|
+
];
|
|
37
|
+
const LAYOUT = [
|
|
38
|
+
"direction",
|
|
39
|
+
"justify",
|
|
40
|
+
"align",
|
|
41
|
+
"children"
|
|
42
|
+
];
|
|
43
|
+
const NODE_CONSTRUCTION_PROP_NAMES = {
|
|
44
|
+
Group: ["children"],
|
|
45
|
+
Rect: SKETCH,
|
|
46
|
+
Circle: SKETCH,
|
|
47
|
+
Path: SKETCH,
|
|
48
|
+
Text: [
|
|
49
|
+
"fontFamily",
|
|
50
|
+
"fontWeight",
|
|
51
|
+
"fontStyle",
|
|
52
|
+
"align",
|
|
53
|
+
"lineHeight",
|
|
54
|
+
"fontVariationSettings"
|
|
55
|
+
],
|
|
56
|
+
Image: ["assetId"],
|
|
57
|
+
Video: [
|
|
58
|
+
"assetId",
|
|
59
|
+
"at",
|
|
60
|
+
"trimStart",
|
|
61
|
+
"playbackRate",
|
|
62
|
+
"clipDuration",
|
|
63
|
+
"sourceFps"
|
|
64
|
+
],
|
|
65
|
+
Layout: LAYOUT,
|
|
66
|
+
Stack: LAYOUT,
|
|
67
|
+
Row: LAYOUT,
|
|
68
|
+
Column: LAYOUT
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Is `prop` a KNOWN construction-only prop for a node of describe-type
|
|
72
|
+
* `typeName`? True for the shared base set or that type's own set. Used by the
|
|
73
|
+
* bind guard to special-case the unbound-target error: a construction prop gets
|
|
74
|
+
* a "set it at construction" message instead of the generic one.
|
|
75
|
+
*/
|
|
76
|
+
function isConstructionProp(typeName, prop) {
|
|
77
|
+
if (BASE_CONSTRUCTION_PROP_NAMES.includes(prop)) return true;
|
|
78
|
+
const own = NODE_CONSTRUCTION_PROP_NAMES[typeName];
|
|
79
|
+
return own !== void 0 && own.includes(prop);
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { NODE_CONSTRUCTION_PROP_NAMES as n, isConstructionProp as r, BASE_CONSTRUCTION_PROP_NAMES as t };
|
package/dist/describe.d.ts
CHANGED
|
@@ -62,6 +62,25 @@ interface DescribedBuilderMethod {
|
|
|
62
62
|
name: string;
|
|
63
63
|
signature: string;
|
|
64
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* One helper/factory in the manifest — the broader builder API beyond the node
|
|
67
|
+
* taxonomy and the timeline builder. These are the free functions an AI consumer
|
|
68
|
+
* reaches for (transport, motion-path, clips, snapshot, text-splitting); several
|
|
69
|
+
* live ABOVE `scene` in the dep graph (player/backend), so describe() can't
|
|
70
|
+
* import them — this is a CURATED literal, drift-guarded by a test that runs in a
|
|
71
|
+
* package above scene (`@glissade/browser`'s smoke test) and asserts every name
|
|
72
|
+
* resolves to a real `window.glissade.<name>` function.
|
|
73
|
+
*/
|
|
74
|
+
interface DescribedHelper {
|
|
75
|
+
/** The exported function name — also the `window.glissade.<name>` global on the IIFE. */
|
|
76
|
+
name: string;
|
|
77
|
+
/** One line: what it's for. */
|
|
78
|
+
summary: string;
|
|
79
|
+
/** The npm subpath to import it from (e.g. `@glissade/player`). On the IIFE it's `window.glissade.<name>`. */
|
|
80
|
+
import: string;
|
|
81
|
+
/** A minimal signature/usage string showing the call shape. */
|
|
82
|
+
usage: string;
|
|
83
|
+
}
|
|
65
84
|
/** The full machine-readable manifest `describe()` returns. */
|
|
66
85
|
interface ApiManifest {
|
|
67
86
|
version: string;
|
|
@@ -73,6 +92,13 @@ interface ApiManifest {
|
|
|
73
92
|
builder: {
|
|
74
93
|
methods: DescribedBuilderMethod[];
|
|
75
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* The curated helper/factory surface (0.20) — `createPlayer`/`mount`,
|
|
97
|
+
* `motionPath`/`followPath`, `clip`/`clipList`, `renderToDataURL`/`snapshotCanvas`,
|
|
98
|
+
* `splitText`, `Grid`, and the `Stack`/`Row`/`Column` layout factories. Every
|
|
99
|
+
* `name` is also a `window.glissade.<name>` global on the IIFE.
|
|
100
|
+
*/
|
|
101
|
+
helpers: DescribedHelper[];
|
|
76
102
|
createScene: string;
|
|
77
103
|
subpaths: {
|
|
78
104
|
[entry: string]: string;
|
|
@@ -86,4 +112,4 @@ interface ApiManifest {
|
|
|
86
112
|
*/
|
|
87
113
|
declare function describe(): ApiManifest;
|
|
88
114
|
//#endregion
|
|
89
|
-
export { ApiManifest, DescribedBuilderMethod, DescribedNode, DescribedProp, describe };
|
|
115
|
+
export { ApiManifest, DescribedBuilderMethod, DescribedHelper, DescribedNode, DescribedProp, describe };
|
package/dist/describe.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { a as Path, c as Video, i as ImageNode, o as Rect, r as Group, s as Text, t as Circle } from "./nodes.js";
|
|
2
|
+
import { n as NODE_CONSTRUCTION_PROP_NAMES, t as BASE_CONSTRUCTION_PROP_NAMES } from "./constructionProps.js";
|
|
2
3
|
import { easings, listValueTypes } from "@glissade/core";
|
|
3
4
|
//#region src/describe.ts
|
|
4
5
|
/**
|
|
@@ -22,7 +23,7 @@ import { easings, listValueTypes } from "@glissade/core";
|
|
|
22
23
|
* never pulled onto the base embed path — a scene that never calls `describe()`
|
|
23
24
|
* pays zero bytes for it.
|
|
24
25
|
*/
|
|
25
|
-
const RAW_VERSION = "0.
|
|
26
|
+
const RAW_VERSION = "0.20.0-pre.4";
|
|
26
27
|
const PACKAGE_VERSION = RAW_VERSION.includes("GLISSADE_".concat("VERSION")) ? "0.0.0-dev" : RAW_VERSION;
|
|
27
28
|
/** Arity of a value type's numeric repr: vec2/vec2-arc → 2, number → 1; others (color/paint/path/string/boolean) carry no scalar arity. */
|
|
28
29
|
function arityOf(type) {
|
|
@@ -40,15 +41,18 @@ function expectsToType(expects) {
|
|
|
40
41
|
return Array.isArray(expects) ? expects.join("|") : expects;
|
|
41
42
|
}
|
|
42
43
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
44
|
+
* Per-prop construction metadata (value type + `required`) for the manifest.
|
|
45
|
+
* The NAME sets themselves are owned by the slim `./constructionProps` module
|
|
46
|
+
* (which BOTH this file and the embed-path bind guard import); this map only
|
|
47
|
+
* layers the richer per-prop type info on top of those names. `describeNode`
|
|
48
|
+
* iterates the slim names and looks each one up here, so a name that exists in
|
|
49
|
+
* one place but not the other is a build-time-visible bug, not silent drift.
|
|
46
50
|
*
|
|
47
51
|
* Base `NodeProps` construction props (`id`, `blend`, `filters`, `anchor`,
|
|
48
52
|
* `cache`) are shared by EVERY node and merged in separately, so this map holds
|
|
49
53
|
* only each node's OWN construction surface.
|
|
50
54
|
*/
|
|
51
|
-
const
|
|
55
|
+
const CONSTRUCTION_PROP_META = {
|
|
52
56
|
Group: { children: { type: "Node[]" } },
|
|
53
57
|
Rect: {
|
|
54
58
|
sketch: { type: "SketchStyle" },
|
|
@@ -70,7 +74,8 @@ const CONSTRUCTION_PROPS = {
|
|
|
70
74
|
fontWeight: { type: "number" },
|
|
71
75
|
fontStyle: { type: "'normal'|'italic'" },
|
|
72
76
|
align: { type: "'left'|'center'|'right'" },
|
|
73
|
-
lineHeight: { type: "number" }
|
|
77
|
+
lineHeight: { type: "number" },
|
|
78
|
+
fontVariationSettings: { type: "string" }
|
|
74
79
|
},
|
|
75
80
|
Image: { assetId: {
|
|
76
81
|
type: "string",
|
|
@@ -89,10 +94,12 @@ const CONSTRUCTION_PROPS = {
|
|
|
89
94
|
}
|
|
90
95
|
};
|
|
91
96
|
/**
|
|
92
|
-
*
|
|
93
|
-
* construction, never animatable
|
|
97
|
+
* Per-prop type metadata for the shared base `NodeProps` construction props —
|
|
98
|
+
* set at construction, never animatable. The NAME set lives in the slim
|
|
99
|
+
* `./constructionProps` module (`BASE_CONSTRUCTION_PROP_NAMES`); this just maps
|
|
100
|
+
* each shared name to its manifest value type.
|
|
94
101
|
*/
|
|
95
|
-
const
|
|
102
|
+
const BASE_CONSTRUCTION_PROP_META = {
|
|
96
103
|
id: { type: "string" },
|
|
97
104
|
blend: { type: "BlendMode" },
|
|
98
105
|
filters: { type: "FilterSpec[]" },
|
|
@@ -115,15 +122,20 @@ function describeNode(node, typeName) {
|
|
|
115
122
|
...arity !== void 0 ? { arity } : {}
|
|
116
123
|
};
|
|
117
124
|
}
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
...
|
|
121
|
-
|
|
122
|
-
for (const [prop, spec] of Object.entries(construction)) props[prop] = {
|
|
123
|
-
type: spec.type,
|
|
124
|
-
animatable: false,
|
|
125
|
-
...spec.required ? { required: true } : {}
|
|
125
|
+
const ownNames = NODE_CONSTRUCTION_PROP_NAMES[typeName] ?? [];
|
|
126
|
+
const meta = {
|
|
127
|
+
...BASE_CONSTRUCTION_PROP_META,
|
|
128
|
+
...CONSTRUCTION_PROP_META[typeName] ?? {}
|
|
126
129
|
};
|
|
130
|
+
for (const prop of [...BASE_CONSTRUCTION_PROP_NAMES, ...ownNames]) {
|
|
131
|
+
const spec = meta[prop];
|
|
132
|
+
if (spec === void 0) throw new Error(`describe(): construction prop '${typeName}/${prop}' has no type metadata`);
|
|
133
|
+
props[prop] = {
|
|
134
|
+
type: spec.type,
|
|
135
|
+
animatable: false,
|
|
136
|
+
...spec.required ? { required: true } : {}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
127
139
|
return { props };
|
|
128
140
|
}
|
|
129
141
|
/**
|
|
@@ -145,7 +157,7 @@ const LAYOUT_ANIMATABLE = {
|
|
|
145
157
|
gap: { type: "number" },
|
|
146
158
|
padding: { type: "number" }
|
|
147
159
|
};
|
|
148
|
-
const
|
|
160
|
+
const LAYOUT_CONSTRUCTION_META = {
|
|
149
161
|
direction: { type: "'row'|'column'" },
|
|
150
162
|
justify: { type: "'start'|'center'|'end'|'space-between'|'space-around'" },
|
|
151
163
|
align: { type: "'start'|'center'|'end'|'stretch'" },
|
|
@@ -169,12 +181,15 @@ function describeLayoutNode() {
|
|
|
169
181
|
...arity !== void 0 ? { arity } : {}
|
|
170
182
|
};
|
|
171
183
|
}
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
184
|
+
for (const prop of NODE_CONSTRUCTION_PROP_NAMES.Layout ?? []) {
|
|
185
|
+
const spec = LAYOUT_CONSTRUCTION_META[prop];
|
|
186
|
+
if (spec === void 0) throw new Error(`describe(): Layout construction prop '${prop}' has no type metadata`);
|
|
187
|
+
props[prop] = {
|
|
188
|
+
type: spec.type,
|
|
189
|
+
animatable: false,
|
|
190
|
+
...spec.required ? { required: true } : {}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
178
193
|
return {
|
|
179
194
|
props,
|
|
180
195
|
subpath: LAYOUT_SUBPATH
|
|
@@ -252,6 +267,96 @@ const BUILDER_METHODS = [
|
|
|
252
267
|
signature: "editableDuration(): TimelineBuilder"
|
|
253
268
|
}
|
|
254
269
|
];
|
|
270
|
+
/**
|
|
271
|
+
* The curated helper/factory surface (0.20). These are the free functions the
|
|
272
|
+
* discovery doc surfaces beyond the node taxonomy + timeline builder — transport,
|
|
273
|
+
* motion-path sampling, motion clips, frame snapshot, text splitting. Several live
|
|
274
|
+
* ABOVE `scene` in the dep graph (player / backend-canvas2d), so describe() CANNOT
|
|
275
|
+
* import them — this literal is curated by hand and drift-guarded by a test that
|
|
276
|
+
* runs in a package above scene (`@glissade/browser`'s smoke test), which asserts
|
|
277
|
+
* every `name` here resolves to a real `window.glissade.<name>` function on the
|
|
278
|
+
* IIFE. Copy is kept verbatim from `docs/discovery.md` so docs and manifest agree.
|
|
279
|
+
*/
|
|
280
|
+
const HELPERS = [
|
|
281
|
+
{
|
|
282
|
+
name: "createPlayer",
|
|
283
|
+
summary: "Build the transport object (play / pause / seek / rate / loop / marker + cue callbacks) directly — what mount() returns as mounted.player.",
|
|
284
|
+
import: "@glissade/player",
|
|
285
|
+
usage: "createPlayer({ playhead: createPlayhead(), duration: 2 }, { loop?: boolean }): Player — player.play() → { finished }, player.pause(), player.seek(u), player.rate = 2, player.onMarker(name, cb), player.onCue(kind, cb)"
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: "mount",
|
|
289
|
+
summary: "The one-call embed: builds the player, the backend, the rAF render loop, and font handling for you — start here. Returns { player } among other handles.",
|
|
290
|
+
import: "@glissade/player",
|
|
291
|
+
usage: "mount(scene, timeline, canvas, opts?: { loop?: boolean }): { player: Player, ... }"
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
name: "motionPath",
|
|
295
|
+
summary: "Build an arc-length sampler over a path — a pure, deterministic table you read points and tangents from by normalized progress (constant speed, not bezier parameter).",
|
|
296
|
+
import: "@glissade/scene/motion",
|
|
297
|
+
usage: "motionPath(path: PathValue): { length, atProgress(u): [x,y], tangentAtProgress(u): [x,y] }"
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
name: "followPath",
|
|
301
|
+
summary: "A companion node that makes a target ride a path as an animatable — it owns the target position (and rotation with orient) and exposes a progress you drive with a track.",
|
|
302
|
+
import: "@glissade/scene/motion",
|
|
303
|
+
usage: "followPath(target: Node, path: Node, opts?: { id?, orient?: boolean }): FollowPath — drive '<id>/progress' with a track"
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: "clip",
|
|
307
|
+
summary: "A reusable, target-agnostic motion captured once as a relative-time key schedule, then applied to a node at a wall-clock start time. Build-time sugar: clip.apply() compiles to ordinary Track[].",
|
|
308
|
+
import: "@glissade/core/clips",
|
|
309
|
+
usage: "clip({ channels: { <name>: { path, keys: [key(t, value, ease?)] } } }): Clip — clip.apply(nodeId, startT) → { tracks, end }"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "clipList",
|
|
313
|
+
summary: "Fan one clip across many targets, staggered, in a single call — returns the combined Track[].",
|
|
314
|
+
import: "@glissade/core/clips",
|
|
315
|
+
usage: "clipList(clip: Clip, targets: string[], startT: number, opts?: { stagger?: number }): { tracks }"
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: "renderToDataURL",
|
|
319
|
+
summary: "Capture a single frame as a PNG/WebP data URL — evaluate → render → data-URL, the no-build screenshot DX helper. Browser-only.",
|
|
320
|
+
import: "@glissade/backend-canvas2d/snapshot",
|
|
321
|
+
usage: "renderToDataURL(scene, timeline, t, opts?): string (data: URL)"
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
name: "snapshotCanvas",
|
|
325
|
+
summary: "Render a single frame onto a canvas you pass in (the lower-level primitive renderToDataURL is built on). Browser-only.",
|
|
326
|
+
import: "@glissade/backend-canvas2d/snapshot",
|
|
327
|
+
usage: "snapshotCanvas(scene, timeline, t, canvas, opts?): void"
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "splitText",
|
|
331
|
+
summary: "Split a Text node into per-word / per-char parts you can animate individually (kinetic typography). Tree-shaken off the base scene index.",
|
|
332
|
+
import: "@glissade/scene/type",
|
|
333
|
+
usage: "splitText(text: Text, opts?: { by?: 'word'|'char' }): Node[]"
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
name: "Grid",
|
|
337
|
+
summary: "Build-time CSS-grid-style track resolver: position plain children into a column grid (fr/px tracks + gaps), returning a Group. Pure fan-out (no Yoga, no new target) — the goldens hold by construction. Tree-shaken off the base scene index.",
|
|
338
|
+
import: "@glissade/scene/grid",
|
|
339
|
+
usage: "Grid({ columns: number | (number | { fr })[], children: Node[], gap?, columnGap?, rowGap?, cellHeight?, width? }): Group — child[i] → row floor(i/cols), col i%cols"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: "Stack",
|
|
343
|
+
summary: "Yoga-flexbox layout factory (column by default) — a Layout subclass that stacks children with gap/padding/justify/align. Needs loadYogaLayoutEngine() before mount/render. On the @glissade/scene/layout subpath.",
|
|
344
|
+
import: "@glissade/scene/layout",
|
|
345
|
+
usage: "Stack({ children, direction?: 'row'|'column', gap?, padding?, justify?, align? }): Layout"
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: "Row",
|
|
349
|
+
summary: "Yoga-flexbox layout factory pinned to direction:\"row\" — children laid out horizontally. Needs loadYogaLayoutEngine() before mount/render. On the @glissade/scene/layout subpath.",
|
|
350
|
+
import: "@glissade/scene/layout",
|
|
351
|
+
usage: "Row({ children, gap?, padding?, justify?, align? }): Layout"
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: "Column",
|
|
355
|
+
summary: "Yoga-flexbox layout factory pinned to direction:\"column\" — children laid out vertically. Needs loadYogaLayoutEngine() before mount/render. On the @glissade/scene/layout subpath.",
|
|
356
|
+
import: "@glissade/scene/layout",
|
|
357
|
+
usage: "Column({ children, gap?, padding?, justify?, align? }): Layout"
|
|
358
|
+
}
|
|
359
|
+
];
|
|
255
360
|
/** One-line "what's there" for each documented tree-shakeable subpath entry. */
|
|
256
361
|
const SUBPATHS = {
|
|
257
362
|
"@glissade/core/clips": "motion clips: clip/clipList + the popIn/slideIn/pulse/driftLoop literals, presence (enter/exit) and morph (box-FLIP) build-time sugar.",
|
|
@@ -281,6 +386,7 @@ function describe() {
|
|
|
281
386
|
valueTypes: listValueTypes(),
|
|
282
387
|
easings: Object.keys(easings),
|
|
283
388
|
builder: { methods: BUILDER_METHODS },
|
|
389
|
+
helpers: HELPERS,
|
|
284
390
|
createScene: "createScene({ size: { w, h }, children: Node[] }): Scene — media assets are declared on the Timeline document: timeline({ assets: { <id>: { kind: 'image'|'video', url } } }); an Image/Video node's `assetId` names an entry here.",
|
|
285
391
|
subpaths: SUBPATHS
|
|
286
392
|
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { i as DrawCommand, m as Resource, n as DisplayList, r as DisplayListBuilder } from "./displayList.js";
|
|
2
|
+
import { t as collapseReplacer } from "./collapseReplacer.js";
|
|
3
|
+
import { B as NodeProps, L as EvalContext, V as PropInit, _ as WordBox, p as Text, z as Node } from "./nodes.js";
|
|
4
|
+
import { r as Scene } from "./scene.js";
|
|
5
|
+
import { Timeline, Vec2 } from "@glissade/core";
|
|
6
|
+
|
|
7
|
+
//#region src/displayDiff.d.ts
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* One index-aligned, positional delta between two DisplayList command streams.
|
|
11
|
+
*
|
|
12
|
+
* This is the natural per-command shape that `gs verify-determinism --bisect`
|
|
13
|
+
* (a later card) will consume to drill a cache-cold divergence down to the
|
|
14
|
+
* exact op/field. Keep it stable.
|
|
15
|
+
*/
|
|
16
|
+
interface CommandDelta {
|
|
17
|
+
/** Positional command index in BOTH lists (or the longer one for add/remove). */
|
|
18
|
+
index: number;
|
|
19
|
+
/**
|
|
20
|
+
* - `change` — same index present in both, but the commands differ.
|
|
21
|
+
* - `add` — present in `b` only (b is longer past this index).
|
|
22
|
+
* - `remove` — present in `a` only (a is longer past this index).
|
|
23
|
+
*/
|
|
24
|
+
kind: 'change' | 'add' | 'remove';
|
|
25
|
+
/** op of the `a` command (undefined for `add`). */
|
|
26
|
+
opA?: DrawCommand['op'];
|
|
27
|
+
/** op of the `b` command (undefined for `remove`). */
|
|
28
|
+
opB?: DrawCommand['op'];
|
|
29
|
+
/**
|
|
30
|
+
* Field-level changes when both ops match (op changed → a single `op` field).
|
|
31
|
+
* Each entry names the changed prop path with its `from`/`to` JSON values.
|
|
32
|
+
*/
|
|
33
|
+
fields: FieldChange[];
|
|
34
|
+
}
|
|
35
|
+
interface FieldChange {
|
|
36
|
+
/** dotted field path within the command, e.g. `paint.color`, `m`, `text`, `filters`. */
|
|
37
|
+
path: string;
|
|
38
|
+
from: unknown;
|
|
39
|
+
to: unknown;
|
|
40
|
+
}
|
|
41
|
+
interface DisplayDiff {
|
|
42
|
+
/** true when the two lists are byte-identical (the §3.3 determinism contract holds). */
|
|
43
|
+
equal: boolean;
|
|
44
|
+
/** per-command positional deltas, in index order (empty iff `equal`). */
|
|
45
|
+
deltas: CommandDelta[];
|
|
46
|
+
/** size mismatch, when the canvases differ (rare, but a real divergence). */
|
|
47
|
+
size?: {
|
|
48
|
+
from: DisplayList['size'];
|
|
49
|
+
to: DisplayList['size'];
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Index-aligned positional diff of two DisplayLists. Command i in `a` is
|
|
54
|
+
* compared to command i in `b`; trailing commands become `add`/`remove`. A
|
|
55
|
+
* single insert/remove cascades — this is the documented v1 cliff.
|
|
56
|
+
*/
|
|
57
|
+
declare function diffDisplayLists(a: DisplayList, b: DisplayList): DisplayDiff;
|
|
58
|
+
/** Human-readable, multi-line rendering of a DisplayDiff (CLI command-tree). */
|
|
59
|
+
declare function formatDisplayDiff(diff: DisplayDiff): string;
|
|
60
|
+
/**
|
|
61
|
+
* The `.dl.json` snapshot interchange schema (DESIGN §7.4). Users commit
|
|
62
|
+
* `.dl.json` baselines, so this carries the SAME break-policy obligation as
|
|
63
|
+
* `Timeline.version` and `SidecarDoc.sidecarVersion`: bump on a breaking shape
|
|
64
|
+
* change. Independent of the API version.
|
|
65
|
+
*/
|
|
66
|
+
declare const DL_SNAPSHOT_VERSION: 1;
|
|
67
|
+
interface DlSnapshot {
|
|
68
|
+
/** Interchange schema version (§7.4) — the third versioned interchange document. */
|
|
69
|
+
dlSnapshotVersion: typeof DL_SNAPSHOT_VERSION;
|
|
70
|
+
size: DisplayList['size'];
|
|
71
|
+
commands: DrawCommand[];
|
|
72
|
+
resources: Resource[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Serialize a DisplayList to a stable `.dl.json` document string (reusing the
|
|
76
|
+
* byte-preserving collapse-replacer). Round-trips through `parseDisplaySnapshot`.
|
|
77
|
+
*/
|
|
78
|
+
declare function serializeDisplayList(dl: DisplayList): string;
|
|
79
|
+
declare class DlSnapshotError extends Error {
|
|
80
|
+
constructor(message: string);
|
|
81
|
+
}
|
|
82
|
+
/** Parse a `.dl.json` snapshot back into a DisplayList (validates the version). */
|
|
83
|
+
declare function parseDisplaySnapshot(json: string): DisplayList;
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/cacheColdAudit.d.ts
|
|
86
|
+
interface CacheColdResult {
|
|
87
|
+
ok: boolean;
|
|
88
|
+
/** id of the first node whose isolated emit() diverged (set only when !ok). */
|
|
89
|
+
node?: string;
|
|
90
|
+
/**
|
|
91
|
+
* The FIRST command-level delta of the divergent node's isolated emit (set only
|
|
92
|
+
* when a specific leaf diverged — never for a Group fallback or a missing node).
|
|
93
|
+
* The WHOLE `CommandDelta` is embedded — index, kind, opA/opB, and every
|
|
94
|
+
* field change — so a multi-field divergence isn't flattened away. `gs
|
|
95
|
+
* verify-determinism --bisect` consumes this to name the (frame, node, op).
|
|
96
|
+
*/
|
|
97
|
+
delta?: CommandDelta;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Evaluate two fresh scenes from `createScene` at `t` and confirm the
|
|
101
|
+
* DisplayLists are byte-identical. Returns `{ ok: true }` for a pure scene, or
|
|
102
|
+
* `{ ok: false, node }` naming the first divergent node. DEV-only — never on
|
|
103
|
+
* the render hot path.
|
|
104
|
+
*/
|
|
105
|
+
declare function auditCacheCold(createScene: () => Scene, doc: Timeline, t: number): CacheColdResult;
|
|
106
|
+
//#endregion
|
|
107
|
+
//#region src/tokenHighlight.d.ts
|
|
108
|
+
interface TokenRange {
|
|
109
|
+
/** token text (whitespace-insensitive run match) or inclusive [from, to] wordBoxes indices */
|
|
110
|
+
match: string | readonly [number, number];
|
|
111
|
+
/** which occurrence of a string match; default 1 (the first) */
|
|
112
|
+
occurrence?: number;
|
|
113
|
+
/** range id for track targets ('<nodeId>/<rangeId>/fill' …); default 'r<index>' */
|
|
114
|
+
id?: string;
|
|
115
|
+
fill?: PropInit<string>;
|
|
116
|
+
opacity?: PropInit<number>;
|
|
117
|
+
/** 0→1 left-to-right reveal across the range; default 1 */
|
|
118
|
+
progress?: PropInit<number>;
|
|
119
|
+
/** scale about the range rect's center; default 1 */
|
|
120
|
+
scale?: PropInit<number>;
|
|
121
|
+
/** translation of the range's rects, px — shakes and nudges; default [0, 0] */
|
|
122
|
+
offset?: PropInit<Vec2>;
|
|
123
|
+
}
|
|
124
|
+
interface TokenHighlightProps extends NodeProps {
|
|
125
|
+
/** the Text whose tokens get highlighted; place this node as an EARLIER sibling */
|
|
126
|
+
text: Text;
|
|
127
|
+
ranges: TokenRange[];
|
|
128
|
+
/** marker overhang beyond the ink box, [x, y] px; default [4, 2] */
|
|
129
|
+
padding?: [number, number];
|
|
130
|
+
cornerRadius?: number;
|
|
131
|
+
/** re-resolve string matches every frame (animated text); default false — drift throws */
|
|
132
|
+
rematch?: boolean;
|
|
133
|
+
}
|
|
134
|
+
declare class TokenMatchError extends Error {
|
|
135
|
+
constructor(message: string);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Find the Nth whitespace-stripped consecutive run of boxes equal to token.
|
|
139
|
+
* Boundary-exact: a run that diverges mid-segment is not a match; ending
|
|
140
|
+
* mid-segment throws (with the real segment list) rather than half-boxing.
|
|
141
|
+
*/
|
|
142
|
+
declare function matchTokenRun(boxes: WordBox[], token: string, occurrence?: number): [number, number];
|
|
143
|
+
declare class TokenHighlight extends Node {
|
|
144
|
+
readonly target: Text;
|
|
145
|
+
readonly padding: [number, number];
|
|
146
|
+
readonly cornerRadius: number;
|
|
147
|
+
readonly rematch: boolean;
|
|
148
|
+
private readonly ranges;
|
|
149
|
+
constructor(props: TokenHighlightProps);
|
|
150
|
+
private resolveRun;
|
|
151
|
+
protected draw(out: DisplayListBuilder, ctx: EvalContext): void;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* `children: [tokenHighlight(para, { ranges: [{ match: '$48,200', fill: cat.money }] }), para]`
|
|
155
|
+
* — each range animates independently via '<id>/<rangeId>/fill|opacity|progress|scale'.
|
|
156
|
+
*/
|
|
157
|
+
declare function tokenHighlight(text: Text, props: Omit<TokenHighlightProps, 'text'>): TokenHighlight;
|
|
158
|
+
//#endregion
|
|
159
|
+
export { type CacheColdResult, type CommandDelta, DL_SNAPSHOT_VERSION, type DisplayDiff, type DlSnapshot, DlSnapshotError, type FieldChange, TokenHighlight, type TokenHighlightProps, TokenMatchError, type TokenRange, auditCacheCold, collapseReplacer, diffDisplayLists, formatDisplayDiff, matchTokenRun, parseDisplaySnapshot, serializeDisplayList, tokenHighlight };
|