@glissade/scene 0.18.0-pre.5 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,21 +20,43 @@
20
20
  * never pulled onto the base embed path — a scene that never calls `describe()`
21
21
  * pays zero bytes for it.
22
22
  */
23
- /** One animatable / settable prop in the manifest. */
23
+ /**
24
+ * One prop in the manifest. The `animatable` flag is the load-bearing
25
+ * distinction an AI reads to tell "animate this" from "set at construction":
26
+ *
27
+ * - An ANIMATABLE prop (`animatable: true`) carries a `target` — a real track
28
+ * target you bind via `to`/`fromTo`/`set`. Generated from `listTargets()`,
29
+ * so it is a registered target by construction.
30
+ * - A CONSTRUCTION prop (`animatable: false`) has NO `target` — it is passed to
31
+ * the node constructor and NEVER bindable (binding it is rejected by the bind
32
+ * guard). `required: true` marks one you can't omit (e.g. Image `assetId`).
33
+ *
34
+ * This negative space is the manifest's point: it prevents an AI from
35
+ * attempting to animate a construction-only prop (an `assetId` track, a
36
+ * `fontFamily` tween) — those are construction-time decisions, not tracks.
37
+ */
24
38
  interface DescribedProp {
25
39
  /** The §2.2 value-type id this prop accepts (e.g. `'vec2'`, `'number'`, `'color'`). */
26
40
  type: string;
27
- /** Whether a Track can drive it (every registered target is animatable). */
41
+ /** Whether a Track can drive it. `true` a `target` is present; `false` ⇒ construction-only, no `target`. */
28
42
  animatable: boolean;
29
- /** The track-target template, `'<id>/<path>'` — substitute the node's real id. */
43
+ /** The track-target template, `'<id>/<path>'` — present ONLY on animatable props; substitute the node's real id. */
30
44
  target?: string;
31
45
  /** Component count of the value (`vec2` → 2, scalar → 1); omitted for non-numeric reprs. */
32
46
  arity?: number;
47
+ /** Construction-only props: `true` when the constructor REQUIRES it (e.g. Image/Video `assetId`). */
48
+ required?: boolean;
33
49
  }
34
50
  interface DescribedNode {
35
51
  props: {
36
52
  [prop: string]: DescribedProp;
37
53
  };
54
+ /**
55
+ * The tree-shakeable subpath this node is imported from, when not the base
56
+ * `@glissade/scene` index (e.g. the Layout family lives on
57
+ * `@glissade/scene/layout`). Omitted for base-index nodes.
58
+ */
59
+ subpath?: string;
38
60
  }
39
61
  interface DescribedBuilderMethod {
40
62
  name: string;
package/dist/describe.js CHANGED
@@ -22,7 +22,7 @@ import { easings, listValueTypes } from "@glissade/core";
22
22
  * never pulled onto the base embed path — a scene that never calls `describe()`
23
23
  * pays zero bytes for it.
24
24
  */
25
- const RAW_VERSION = "0.18.0-pre.5";
25
+ const RAW_VERSION = "0.18.0";
26
26
  const PACKAGE_VERSION = RAW_VERSION.includes("GLISSADE_".concat("VERSION")) ? "0.0.0-dev" : RAW_VERSION;
27
27
  /** 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
28
  function arityOf(type) {
@@ -39,9 +39,71 @@ function expectsToType(expects) {
39
39
  if (expects === void 0) return "unknown";
40
40
  return Array.isArray(expects) ? expects.join("|") : expects;
41
41
  }
42
- /** Introspect one freshly-instantiated node into its prop manifest, reading the
43
- * REAL `registerTarget` calls via `listTargets()` (so it can't drift). */
44
- function describeNode(node) {
42
+ /**
43
+ * Construction-only props by node type the difference between a node's Props
44
+ * interface and its `registerTarget` set (its animatable props). These are set
45
+ * once at `new Node({...})` and can never be animated.
46
+ *
47
+ * Base `NodeProps` construction props (`id`, `blend`, `filters`, `anchor`,
48
+ * `cache`) are shared by EVERY node and merged in separately, so this map holds
49
+ * only each node's OWN construction surface.
50
+ */
51
+ const CONSTRUCTION_PROPS = {
52
+ Group: { children: { type: "Node[]" } },
53
+ Rect: {
54
+ sketch: { type: "SketchStyle" },
55
+ sketchFill: { type: "HachureSpec" },
56
+ sketchSeed: { type: "number" }
57
+ },
58
+ Circle: {
59
+ sketch: { type: "SketchStyle" },
60
+ sketchFill: { type: "HachureSpec" },
61
+ sketchSeed: { type: "number" }
62
+ },
63
+ Path: {
64
+ sketch: { type: "SketchStyle" },
65
+ sketchFill: { type: "HachureSpec" },
66
+ sketchSeed: { type: "number" }
67
+ },
68
+ Text: {
69
+ fontFamily: { type: "string" },
70
+ fontWeight: { type: "number" },
71
+ fontStyle: { type: "'normal'|'italic'" },
72
+ align: { type: "'left'|'center'|'right'" },
73
+ lineHeight: { type: "number" }
74
+ },
75
+ Image: { assetId: {
76
+ type: "string",
77
+ required: true
78
+ } },
79
+ Video: {
80
+ assetId: {
81
+ type: "string",
82
+ required: true
83
+ },
84
+ at: { type: "number" },
85
+ trimStart: { type: "number" },
86
+ playbackRate: { type: "number" },
87
+ clipDuration: { type: "number" },
88
+ sourceFps: { type: "number" }
89
+ }
90
+ };
91
+ /**
92
+ * Base-`NodeProps` construction props shared by every node — set at
93
+ * construction, never animatable (none is a registered target).
94
+ */
95
+ const BASE_CONSTRUCTION_PROPS = {
96
+ id: { type: "string" },
97
+ blend: { type: "BlendMode" },
98
+ filters: { type: "FilterSpec[]" },
99
+ anchor: { type: "AnchorSpec" },
100
+ cache: { type: "boolean" }
101
+ };
102
+ /** Introspect one freshly-instantiated node into its prop manifest: the
103
+ * ANIMATABLE props come from the REAL `registerTarget` calls via `listTargets()`
104
+ * (so they can't drift); the CONSTRUCTION-only props are merged from the curated
105
+ * schema (drift-guarded by a constructor test). */
106
+ function describeNode(node, typeName) {
45
107
  const props = {};
46
108
  for (const { path, expects } of node.listTargets()) {
47
109
  const type = expectsToType(expects);
@@ -53,8 +115,71 @@ function describeNode(node) {
53
115
  ...arity !== void 0 ? { arity } : {}
54
116
  };
55
117
  }
118
+ const construction = {
119
+ ...BASE_CONSTRUCTION_PROPS,
120
+ ...CONSTRUCTION_PROPS[typeName] ?? {}
121
+ };
122
+ for (const [prop, spec] of Object.entries(construction)) props[prop] = {
123
+ type: spec.type,
124
+ animatable: false,
125
+ ...spec.required ? { required: true } : {}
126
+ };
56
127
  return { props };
57
128
  }
129
+ /**
130
+ * The Layout family — `Layout` and its `Stack`/`Row`/`Column` ergonomic
131
+ * factories — lives on the budgeted `@glissade/scene/layout` entry (it ships
132
+ * Yoga wasm), so describe() does NOT import it (that would drag Yoga onto the
133
+ * describe/browser bundle). Instead it carries a CURATED schema, drift-guarded
134
+ * by `describe.test.ts`, which imports the real Layout and asserts these
135
+ * animatable props match its `listTargets()` and these construction props match
136
+ * its constructor.
137
+ *
138
+ * `width`/`height` are animatable number targets; `direction`/`justify`/`align`
139
+ * are construction-only (set once, not tweened). `children` is construction.
140
+ */
141
+ const LAYOUT_SUBPATH = "@glissade/scene/layout";
142
+ const LAYOUT_ANIMATABLE = {
143
+ width: { type: "number" },
144
+ height: { type: "number" },
145
+ gap: { type: "number" },
146
+ padding: { type: "number" }
147
+ };
148
+ const LAYOUT_CONSTRUCTION = {
149
+ direction: { type: "'row'|'column'" },
150
+ justify: { type: "'start'|'center'|'end'|'space-between'|'space-around'" },
151
+ align: { type: "'start'|'center'|'end'|'stretch'" },
152
+ children: { type: "Node[]" }
153
+ };
154
+ /**
155
+ * Build a Layout-family node manifest from the curated schema. Layout is a
156
+ * `Group` subclass, so it inherits every base transform target — reuse a
157
+ * throwaway `Group`'s `listTargets()` for those (no drift on the inherited set),
158
+ * then add the layout-specific animatable + construction props.
159
+ */
160
+ function describeLayoutNode() {
161
+ const props = describeNode(new Group(), "Group").props;
162
+ delete props.children;
163
+ for (const [prop, spec] of Object.entries(LAYOUT_ANIMATABLE)) {
164
+ const arity = arityOf(spec.type);
165
+ props[prop] = {
166
+ type: spec.type,
167
+ animatable: true,
168
+ target: `<id>/${prop}`,
169
+ ...arity !== void 0 ? { arity } : {}
170
+ };
171
+ }
172
+ const construction = LAYOUT_CONSTRUCTION;
173
+ for (const [prop, spec] of Object.entries(construction)) props[prop] = {
174
+ type: spec.type,
175
+ animatable: false,
176
+ ...spec.required ? { required: true } : {}
177
+ };
178
+ return {
179
+ props,
180
+ subpath: LAYOUT_SUBPATH
181
+ };
182
+ }
58
183
  const NODE_FACTORIES = {
59
184
  Group: () => new Group(),
60
185
  Rect: () => new Rect(),
@@ -80,7 +205,7 @@ const BUILDER_METHODS = [
80
205
  },
81
206
  {
82
207
  name: "stagger",
83
- signature: "stagger<T>(targets, { to, from?, duration?, ease? }, { each, anchor?, at? }): TimelineBuilder"
208
+ signature: "stagger<T>(targets, { to, from?, duration?, ease? }, { each: number | ((rank, count) => number), anchor?, at? }): TimelineBuilder"
84
209
  },
85
210
  {
86
211
  name: "set",
@@ -138,14 +263,21 @@ const SUBPATHS = {
138
263
  */
139
264
  function describe() {
140
265
  const nodes = {};
141
- for (const [name, factory] of Object.entries(NODE_FACTORIES)) nodes[name] = describeNode(factory());
266
+ for (const [name, factory] of Object.entries(NODE_FACTORIES)) nodes[name] = describeNode(factory(), name);
267
+ const layout = describeLayoutNode();
268
+ for (const name of [
269
+ "Layout",
270
+ "Stack",
271
+ "Row",
272
+ "Column"
273
+ ]) nodes[name] = layout;
142
274
  return {
143
275
  version: PACKAGE_VERSION,
144
276
  nodes,
145
277
  valueTypes: listValueTypes(),
146
278
  easings: Object.keys(easings),
147
279
  builder: { methods: BUILDER_METHODS },
148
- createScene: "createScene({ size: { w, h }, children: Node[] }): Scene",
280
+ 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.",
149
281
  subpaths: SUBPATHS
150
282
  };
151
283
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/scene",
3
- "version": "0.18.0-pre.5",
3
+ "version": "0.18.0",
4
4
  "description": "glissade scene graph: nodes, transforms, DisplayList emission. Renderer-agnostic; zero DOM/Node dependencies.",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -31,7 +31,7 @@
31
31
  ],
32
32
  "dependencies": {
33
33
  "yoga-layout": "^3.2.1",
34
- "@glissade/core": "0.18.0-pre.5"
34
+ "@glissade/core": "0.18.0"
35
35
  },
36
36
  "repository": {
37
37
  "type": "git",