@glissade/scene 0.35.0 → 0.36.0-pre.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.
@@ -0,0 +1,66 @@
1
+ import { B as Node, a as Group } from "./nodes.js";
2
+
3
+ //#region src/component.d.ts
4
+
5
+ /** One prop in a component's public surface — a manifest type + optionality. */
6
+ interface ComponentPropSpec {
7
+ /** the §2.2 value-type / shape id this prop accepts (e.g. 'number', 'color', 'string', 'Node[]'). */
8
+ type: string;
9
+ /** true when the factory REQUIRES it (default false). */
10
+ required?: boolean;
11
+ }
12
+ /** The props every component instance carries in addition to its own — a stable
13
+ * id (REQUIRED, the collision-safety anchor) plus the base Group passthrough. */
14
+ interface ComponentBaseProps {
15
+ /** Stable id — REQUIRED; every internal node is namespaced under it. */
16
+ id: string;
17
+ }
18
+ /** What an instance returns: the subtree + its id + the child-id helpers. */
19
+ interface ComponentInstance {
20
+ /** the built subtree (a Group whose id === the instance id). */
21
+ readonly node: Group;
22
+ /** the instance id (its own namespace root). */
23
+ readonly id: string;
24
+ /** namespace a child id: `childId('bar')` → `'<id>/bar'`; no arg → the root id. */
25
+ childId(sub?: string): string;
26
+ /** ready-to-bind track targets for a child's prop: `['<id>/<child>/<prop>']`. */
27
+ targets(child: string, prop: string): string[];
28
+ }
29
+ interface ComponentDef<P extends Record<string, unknown> = Record<string, unknown>> {
30
+ /** the component's type name — surfaced in describe().components, must be unique. */
31
+ name: string;
32
+ /** the public prop surface (names → type/optionality) for describe(). */
33
+ props: {
34
+ readonly [prop: string]: ComponentPropSpec;
35
+ };
36
+ /**
37
+ * PURE build: given the instance props (incl. the required `id`) and a
38
+ * child-id namer, return the subtree. Runs ONCE at construction; must not
39
+ * read the playhead or any cross-frame state (bind children with tracks/
40
+ * PropInit closures like any node).
41
+ */
42
+ build(props: P & ComponentBaseProps, childId: (sub?: string) => string): Group;
43
+ }
44
+ declare class ComponentError extends Error {
45
+ constructor(message: string);
46
+ }
47
+ /** Join an instance id + a child sub-id into a namespaced target root. */
48
+ declare function childId(id: string, sub?: string): string;
49
+ interface RegisteredComponent {
50
+ readonly name: string;
51
+ readonly props: {
52
+ readonly [prop: string]: ComponentPropSpec;
53
+ };
54
+ }
55
+ /** Every component defined so far, in definition order — the seam describe()
56
+ * reads to list `components` from the LIVE registry (can't drift). Pure read. */
57
+ declare function listComponents(): RegisteredComponent[];
58
+ /**
59
+ * Define a reusable component. Returns a factory `(props) → ComponentInstance`.
60
+ * Registers the component's name + prop surface so `describe().components` lists
61
+ * it. A duplicate name throws (a component library can't silently shadow).
62
+ */
63
+ declare function defineComponent<P extends Record<string, unknown> = Record<string, unknown>>(def: ComponentDef<P>): (props: P & ComponentBaseProps) => ComponentInstance;
64
+ /** re-export Node/Group types consumers of this entry name for their `build`. */
65
+ //#endregion
66
+ export { ComponentBaseProps, ComponentDef, ComponentError, ComponentInstance, ComponentPropSpec, Group, type Node, RegisteredComponent, childId, defineComponent, listComponents };
@@ -0,0 +1,82 @@
1
+ import { r as Group } from "./nodes.js";
2
+ //#region src/component.ts
3
+ /**
4
+ * `@glissade/scene/component` — `defineComponent()` (0.36): reusable, typed,
5
+ * describe()-legible animated subscenes. The missing COMPOSITION unit — the
6
+ * user-defined generalization of the built-in `Grid()`/`Chart()`/`splitText()`
7
+ * factories (all already "props → subtree" pure build-time functions).
8
+ *
9
+ * const LowerThird = defineComponent({
10
+ * name: 'LowerThird',
11
+ * props: { name: { type: 'string' }, title: { type: 'string' }, accent: { type: 'color' } },
12
+ * build: ({ name, title, accent }, cid) => new Group({ id: cid(), children: [
13
+ * new Rect({ id: cid('bar'), width: 6, height: 40, fill: accent }),
14
+ * new Text({ id: cid('name'), text: name, ... }),
15
+ * new Text({ id: cid('title'), text: title, fill: accent, ... }),
16
+ * ]}),
17
+ * });
18
+ *
19
+ * const lt = LowerThird({ id: 'intro', name: 'Ada', title: 'Engineer', accent: '#4ea1ff' });
20
+ * // scene children: [lt.node]
21
+ * // animate a child: tl.to(lt.childId('bar') + '/height', 40, { from: 0 })
22
+ * // or the ready ids: tl.stagger(lt.targets('bar', 'opacity'), …)
23
+ *
24
+ * PURE build-time fan-out: `build` runs at construction and emits ordinary nodes,
25
+ * so `evaluate()` stays a pure function of time and the goldens hold by
26
+ * construction. Nothing executes at play time.
27
+ *
28
+ * ID-SCOPING is the whole safety story: every instance carries a required stable
29
+ * `id`, and every internal node id is stamped through `cid(sub) = '<id>/<sub>'`,
30
+ * so instancing the SAME component N times (distinct ids) can't collide track
31
+ * targets. Two instances built with the SAME id DO collide — the same footgun
32
+ * `Grid`/`Chart` have; give each instance a unique id.
33
+ *
34
+ * On its OWN tree-shakeable subpath (off the base embed) + re-exported onto the
35
+ * `@glissade/browser` IIFE. `describe().components` lists every component defined
36
+ * so far (side-effect registration on definition — the value-type/examples
37
+ * pattern), so an agent or the studio sees each component's typed prop surface.
38
+ */
39
+ var ComponentError = class extends Error {
40
+ constructor(message) {
41
+ super(message);
42
+ this.name = "ComponentError";
43
+ }
44
+ };
45
+ /** Join an instance id + a child sub-id into a namespaced target root. */
46
+ function childId(id, sub) {
47
+ return sub === void 0 || sub === "" ? id : `${id}/${sub}`;
48
+ }
49
+ const registry = /* @__PURE__ */ new Map();
50
+ /** Every component defined so far, in definition order — the seam describe()
51
+ * reads to list `components` from the LIVE registry (can't drift). Pure read. */
52
+ function listComponents() {
53
+ return [...registry.values()];
54
+ }
55
+ /**
56
+ * Define a reusable component. Returns a factory `(props) → ComponentInstance`.
57
+ * Registers the component's name + prop surface so `describe().components` lists
58
+ * it. A duplicate name throws (a component library can't silently shadow).
59
+ */
60
+ function defineComponent(def) {
61
+ if (registry.has(def.name)) throw new ComponentError(`a component named '${def.name}' is already defined`);
62
+ for (const [prop, spec] of Object.entries(def.props)) if (spec === null || typeof spec !== "object" || typeof spec.type !== "string") throw new ComponentError(`${def.name}: prop '${prop}' must be { type: string, required? }, got ${JSON.stringify(spec)}`);
63
+ registry.set(def.name, {
64
+ name: def.name,
65
+ props: def.props
66
+ });
67
+ return (props) => {
68
+ if (props.id === void 0 || props.id === "") throw new ComponentError(`${def.name}(...) needs a stable id — every instance namespaces its children under it (so N instances don't collide track targets)`);
69
+ const id = props.id;
70
+ const cid = (sub) => childId(id, sub);
71
+ const node = def.build(props, cid);
72
+ if (node.id !== id) throw new ComponentError(`${def.name}(...).build must return a Group with id === childId() ('${id}'); got '${node.id ?? "<unset>"}'. Use \`new Group({ id: cid(), … })\`.`);
73
+ return {
74
+ node,
75
+ id,
76
+ childId: cid,
77
+ targets: (child, prop) => [`${cid(child)}/${prop}`]
78
+ };
79
+ };
80
+ }
81
+ //#endregion
82
+ export { ComponentError, Group, childId, defineComponent, listComponents };
@@ -72,6 +72,21 @@ interface DescribedNode {
72
72
  */
73
73
  subpath?: string;
74
74
  }
75
+ /**
76
+ * One user-defined `defineComponent()` in the manifest (0.36) — a reusable
77
+ * animated subscene's public prop surface, so an agent/studio sees what it
78
+ * accepts. Generated from the LIVE component registry, so it can't drift.
79
+ */
80
+ interface DescribedComponent {
81
+ name: string;
82
+ /** the component's public props: name → { type, required? } (construction-time). */
83
+ props: {
84
+ [prop: string]: {
85
+ type: string;
86
+ required?: boolean;
87
+ };
88
+ };
89
+ }
75
90
  interface DescribedBuilderMethod {
76
91
  name: string;
77
92
  signature: string;
@@ -117,6 +132,9 @@ interface ApiManifest {
117
132
  * `name` is also a `window.glissade.<name>` global on the IIFE.
118
133
  */
119
134
  helpers: DescribedHelper[];
135
+ /** user-defined components registered via defineComponent() (0.36); present
136
+ * from describe() (possibly empty), absent on manifests captured before 0.36. */
137
+ components?: DescribedComponent[];
120
138
  createScene: string;
121
139
  subpaths: {
122
140
  [entry: string]: string;
@@ -147,4 +165,4 @@ declare function registerExamples(corpus: {
147
165
  }): void;
148
166
  declare function describe(opts?: DescribeOptions): ApiManifest;
149
167
  //#endregion
150
- export { ApiManifest, DescribeOptions, DescribedBuilderMethod, DescribedHelper, DescribedNode, DescribedProp, describe, registerExamples };
168
+ export { ApiManifest, DescribeOptions, DescribedBuilderMethod, DescribedComponent, DescribedHelper, DescribedNode, DescribedProp, describe, registerExamples };
package/dist/describe.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { D as NODE_CONSTRUCTION_PROP_NAMES, E as BASE_CONSTRUCTION_PROP_NAMES, 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 { listComponents } from "./component.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.35.0";
26
+ const RAW_VERSION = "0.36.0-pre.1";
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) {
@@ -428,6 +429,12 @@ const HELPERS = [
428
429
  import: "@glissade/scene/chart",
429
430
  usage: "Chart({ id, data: Row[], xKey, yKey, width, height, yScale?, bandPadding?, fill?: string | ColorScale }): { node: Group, bars: Rect[], targets(prop): string[] }"
430
431
  },
432
+ {
433
+ name: "defineComponent",
434
+ summary: "Define a reusable, typed, describe()-legible animated subscene — the user-defined generalization of Grid/Chart. Returns a factory (props & { id }) => { node, childId, targets }; each instance namespaces its children under the required id so N instances never collide track targets. Pure build-time. describe().components lists every one defined. On the @glissade/scene/component subpath.",
435
+ import: "@glissade/scene/component",
436
+ usage: "defineComponent({ name, props: { <p>: { type, required? } }, build(props, childId): Group }): (props & { id }) => { node: Group, id, childId(sub?), targets(child, prop) }"
437
+ },
431
438
  {
432
439
  name: "linearScale",
433
440
  summary: "A serializable linear scale (value axis): maps a numeric domain onto a pixel/unit range. Pair with Chart({ yScale }). On the @glissade/scene/chart subpath.",
@@ -493,6 +500,14 @@ let exampleCorpus = {};
493
500
  function registerExamples(corpus) {
494
501
  exampleCorpus = corpus;
495
502
  }
503
+ function mapComponentProps(props) {
504
+ const out = {};
505
+ for (const [k, v] of Object.entries(props)) out[k] = {
506
+ type: v.type,
507
+ ...v.required ? { required: true } : {}
508
+ };
509
+ return out;
510
+ }
496
511
  function describe(opts = {}) {
497
512
  const withEx = opts.examples === true && Object.keys(exampleCorpus).length > 0;
498
513
  const ex = (key) => {
@@ -530,6 +545,10 @@ function describe(opts = {}) {
530
545
  ...h,
531
546
  ...ex(h.name)
532
547
  })) : HELPERS,
548
+ components: listComponents().map((c) => ({
549
+ name: c.name,
550
+ props: mapComponentProps(c.props)
551
+ })),
533
552
  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.",
534
553
  subpaths: SUBPATHS
535
554
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glissade/scene",
3
- "version": "0.35.0",
3
+ "version": "0.36.0-pre.1",
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,6 +31,10 @@
31
31
  "types": "./dist/chart.d.ts",
32
32
  "default": "./dist/chart.js"
33
33
  },
34
+ "./component": {
35
+ "types": "./dist/component.d.ts",
36
+ "default": "./dist/component.js"
37
+ },
34
38
  "./path": {
35
39
  "types": "./dist/path.d.ts",
36
40
  "default": "./dist/path.js"
@@ -69,7 +73,7 @@
69
73
  ],
70
74
  "dependencies": {
71
75
  "yoga-layout": "^3.2.1",
72
- "@glissade/core": "0.35.0"
76
+ "@glissade/core": "0.36.0-pre.1"
73
77
  },
74
78
  "repository": {
75
79
  "type": "git",