@voila.dev/chart 5.0.117 → 6.0.120

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.
Files changed (43) hide show
  1. package/dist/core/motion/leaving.d.ts +2 -2
  2. package/dist/core/motion/leaving.d.ts.map +1 -1
  3. package/dist/core/motion/leaving.js +4 -11
  4. package/dist/core/motion/leaving.js.map +2 -2
  5. package/dist/core/motion/motion-store.d.ts.map +1 -1
  6. package/dist/core/motion/motion-store.js +58 -84
  7. package/dist/core/motion/motion-store.js.map +3 -3
  8. package/dist/core/motion/points-motion.d.ts.map +1 -1
  9. package/dist/core/motion/points-motion.js +9 -22
  10. package/dist/core/motion/points-motion.js.map +2 -2
  11. package/dist/core/motion/strings.d.ts +4 -2
  12. package/dist/core/motion/strings.d.ts.map +1 -1
  13. package/dist/core/motion/strings.js +5 -6
  14. package/dist/core/motion/strings.js.map +2 -2
  15. package/dist/core/motion/timing.d.ts +16 -18
  16. package/dist/core/motion/timing.d.ts.map +1 -1
  17. package/dist/core/motion/timing.js +9 -8
  18. package/dist/core/motion/timing.js.map +2 -2
  19. package/dist/core/motion/tween-timing.d.ts +17 -0
  20. package/dist/core/motion/tween-timing.d.ts.map +1 -0
  21. package/dist/core/motion/tween-timing.js +22 -0
  22. package/dist/core/motion/tween-timing.js.map +7 -0
  23. package/dist/index.d.ts +2 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +2 -0
  26. package/dist/index.js.map +2 -2
  27. package/dist/react/chart.d.ts +3 -2
  28. package/dist/react/chart.d.ts.map +1 -1
  29. package/dist/react/chart.js +3 -1
  30. package/dist/react/chart.js.map +2 -2
  31. package/dist/react/use-animated-scene.d.ts.map +1 -1
  32. package/dist/react/use-animated-scene.js +4 -4
  33. package/dist/react/use-animated-scene.js.map +2 -2
  34. package/package.json +2 -2
  35. package/src/core/motion/leaving.ts +6 -12
  36. package/src/core/motion/motion-store.ts +65 -109
  37. package/src/core/motion/points-motion.ts +12 -24
  38. package/src/core/motion/strings.ts +9 -12
  39. package/src/core/motion/timing.ts +28 -33
  40. package/src/core/motion/tween-timing.ts +35 -0
  41. package/src/index.ts +2 -0
  42. package/src/react/chart.tsx +9 -4
  43. package/src/react/use-animated-scene.ts +6 -4
@@ -7,6 +7,6 @@ export interface LeavingGuide {
7
7
  }
8
8
  /** The guides of `nodes` whose keys `stays` no longer has. */
9
9
  export declare function leavingGuides(nodes: readonly SceneNode[], stays: (key: string) => boolean, parent?: string | null, into?: LeavingGuide[]): LeavingGuide[];
10
- /** `children` with the leaving guides of `parent` back at their places, faded to `opacity`. */
11
- export declare function withLeaving(children: readonly SceneNode[], parent: string | null, leaving: ReadonlyArray<readonly [LeavingGuide, number]>): SceneNode[];
10
+ /** `children` with the leaving guides of `parent` back at their places, as `paint` draws them. */
11
+ export declare function withLeaving(children: readonly SceneNode[], parent: string | null, leaving: Iterable<LeavingGuide>, paint: (node: SceneNode) => SceneNode): SceneNode[];
12
12
  //# sourceMappingURL=leaving.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"leaving.d.ts","sourceRoot":"","sources":["../../../src/core/motion/leaving.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AASjD,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAC5B,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,EAC/B,MAAM,GAAE,MAAM,GAAG,IAAW,EAC5B,IAAI,GAAE,YAAY,EAAO,GACvB,YAAY,EAAE,CAWhB;AAED,+FAA+F;AAC/F,wBAAgB,WAAW,CAC1B,QAAQ,EAAE,SAAS,SAAS,EAAE,EAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,GACrD,SAAS,EAAE,CAcb"}
1
+ {"version":3,"file":"leaving.d.ts","sourceRoot":"","sources":["../../../src/core/motion/leaving.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AASjD,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,gDAAgD;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAC5B,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,EAC/B,MAAM,GAAE,MAAM,GAAG,IAAW,EAC5B,IAAI,GAAE,YAAY,EAAO,GACvB,YAAY,EAAE,CAWhB;AAED,kGAAkG;AAClG,wBAAgB,WAAW,CAC1B,QAAQ,EAAE,SAAS,SAAS,EAAE,EAC9B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EAC/B,KAAK,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,SAAS,GACnC,SAAS,EAAE,CAOb"}
@@ -11,18 +11,11 @@ function leavingGuides(nodes, stays, parent = null, into = []) {
11
11
  }
12
12
  return into;
13
13
  }
14
- function withLeaving(children, parent, leaving) {
14
+ function withLeaving(children, parent, leaving, paint) {
15
15
  const placed = [...children];
16
- for (const [guide, opacity] of leaving) {
17
- if (guide.parent !== parent || guide.node.kind === "group") continue;
18
- const faded = {
19
- ...guide.node,
20
- paint: {
21
- ...guide.node.paint,
22
- opacity: (guide.node.paint.opacity ?? 1) * opacity
23
- }
24
- };
25
- placed.splice(Math.min(guide.index, placed.length), 0, faded);
16
+ for (const guide of leaving) {
17
+ if (guide.parent !== parent) continue;
18
+ placed.splice(Math.min(guide.index, placed.length), 0, paint(guide.node));
26
19
  }
27
20
  return placed;
28
21
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/motion/leaving.ts"],
4
- "sourcesContent": ["import type { SceneNode } from \"#/core/types.ts\";\n\n/**\n * Guides that left the scene still fade out where they stood: a tick label\n * or a grid line vanishing at once reads as a glitch. Data marks never\n * linger \u2014 a bar the data no longer has would show a value that isn't true.\n */\nconst GUIDE_ROLES = new Set([\"grid\", \"axis\"]);\n\nexport interface LeavingGuide {\n\treadonly node: SceneNode;\n\t/** The group it sat in, or null at the root. */\n\treadonly parent: string | null;\n\treadonly index: number;\n}\n\n/** The guides of `nodes` whose keys `stays` no longer has. */\nexport function leavingGuides(\n\tnodes: readonly SceneNode[],\n\tstays: (key: string) => boolean,\n\tparent: string | null = null,\n\tinto: LeavingGuide[] = [],\n): LeavingGuide[] {\n\tfor (const [index, node] of nodes.entries()) {\n\t\tif (node.kind === \"group\") {\n\t\t\tleavingGuides(node.children, stays, node.key, into);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!stays(node.key) && GUIDE_ROLES.has(node.role ?? \"\")) {\n\t\t\tinto.push({ node, parent, index });\n\t\t}\n\t}\n\treturn into;\n}\n\n/** `children` with the leaving guides of `parent` back at their places, faded to `opacity`. */\nexport function withLeaving(\n\tchildren: readonly SceneNode[],\n\tparent: string | null,\n\tleaving: ReadonlyArray<readonly [LeavingGuide, number]>,\n): SceneNode[] {\n\tconst placed = [...children];\n\tfor (const [guide, opacity] of leaving) {\n\t\tif (guide.parent !== parent || guide.node.kind === \"group\") continue;\n\t\tconst faded = {\n\t\t\t...guide.node,\n\t\t\tpaint: {\n\t\t\t\t...guide.node.paint,\n\t\t\t\topacity: (guide.node.paint.opacity ?? 1) * opacity,\n\t\t\t},\n\t\t} as SceneNode;\n\t\tplaced.splice(Math.min(guide.index, placed.length), 0, faded);\n\t}\n\treturn placed;\n}\n"],
5
- "mappings": "AAOA,MAAM,cAAc,oBAAI,IAAI,CAAC,QAAQ,MAAM,CAAC;AAUrC,SAAS,cACf,OACA,OACA,SAAwB,MACxB,OAAuB,CAAC,GACP;AACjB,aAAW,CAAC,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG;AAC5C,QAAI,KAAK,SAAS,SAAS;AAC1B,oBAAc,KAAK,UAAU,OAAO,KAAK,KAAK,IAAI;AAClD;AAAA,IACD;AACA,QAAI,CAAC,MAAM,KAAK,GAAG,KAAK,YAAY,IAAI,KAAK,QAAQ,EAAE,GAAG;AACzD,WAAK,KAAK,EAAE,MAAM,QAAQ,MAAM,CAAC;AAAA,IAClC;AAAA,EACD;AACA,SAAO;AACR;AAGO,SAAS,YACf,UACA,QACA,SACc;AACd,QAAM,SAAS,CAAC,GAAG,QAAQ;AAC3B,aAAW,CAAC,OAAO,OAAO,KAAK,SAAS;AACvC,QAAI,MAAM,WAAW,UAAU,MAAM,KAAK,SAAS,QAAS;AAC5D,UAAM,QAAQ;AAAA,MACb,GAAG,MAAM;AAAA,MACT,OAAO;AAAA,QACN,GAAG,MAAM,KAAK;AAAA,QACd,UAAU,MAAM,KAAK,MAAM,WAAW,KAAK;AAAA,MAC5C;AAAA,IACD;AACA,WAAO,OAAO,KAAK,IAAI,MAAM,OAAO,OAAO,MAAM,GAAG,GAAG,KAAK;AAAA,EAC7D;AACA,SAAO;AACR;",
4
+ "sourcesContent": ["import type { SceneNode } from \"#/core/types.ts\";\n\n/**\n * Guides that left the scene still fade out where they stood: a tick label\n * or a grid line vanishing at once reads as a glitch. Data marks never\n * linger \u2014 a bar the data no longer has would show a value that isn't true.\n */\nconst GUIDE_ROLES = new Set([\"grid\", \"axis\"]);\n\nexport interface LeavingGuide {\n\treadonly node: SceneNode;\n\t/** The group it sat in, or null at the root. */\n\treadonly parent: string | null;\n\treadonly index: number;\n}\n\n/** The guides of `nodes` whose keys `stays` no longer has. */\nexport function leavingGuides(\n\tnodes: readonly SceneNode[],\n\tstays: (key: string) => boolean,\n\tparent: string | null = null,\n\tinto: LeavingGuide[] = [],\n): LeavingGuide[] {\n\tfor (const [index, node] of nodes.entries()) {\n\t\tif (node.kind === \"group\") {\n\t\t\tleavingGuides(node.children, stays, node.key, into);\n\t\t\tcontinue;\n\t\t}\n\t\tif (!stays(node.key) && GUIDE_ROLES.has(node.role ?? \"\")) {\n\t\t\tinto.push({ node, parent, index });\n\t\t}\n\t}\n\treturn into;\n}\n\n/** `children` with the leaving guides of `parent` back at their places, as `paint` draws them. */\nexport function withLeaving(\n\tchildren: readonly SceneNode[],\n\tparent: string | null,\n\tleaving: Iterable<LeavingGuide>,\n\tpaint: (node: SceneNode) => SceneNode,\n): SceneNode[] {\n\tconst placed = [...children];\n\tfor (const guide of leaving) {\n\t\tif (guide.parent !== parent) continue;\n\t\tplaced.splice(Math.min(guide.index, placed.length), 0, paint(guide.node));\n\t}\n\treturn placed;\n}\n"],
5
+ "mappings": "AAOA,MAAM,cAAc,oBAAI,IAAI,CAAC,QAAQ,MAAM,CAAC;AAUrC,SAAS,cACf,OACA,OACA,SAAwB,MACxB,OAAuB,CAAC,GACP;AACjB,aAAW,CAAC,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG;AAC5C,QAAI,KAAK,SAAS,SAAS;AAC1B,oBAAc,KAAK,UAAU,OAAO,KAAK,KAAK,IAAI;AAClD;AAAA,IACD;AACA,QAAI,CAAC,MAAM,KAAK,GAAG,KAAK,YAAY,IAAI,KAAK,QAAQ,EAAE,GAAG;AACzD,WAAK,KAAK,EAAE,MAAM,QAAQ,MAAM,CAAC;AAAA,IAClC;AAAA,EACD;AACA,SAAO;AACR;AAGO,SAAS,YACf,UACA,QACA,SACA,OACc;AACd,QAAM,SAAS,CAAC,GAAG,QAAQ;AAC3B,aAAW,SAAS,SAAS;AAC5B,QAAI,MAAM,WAAW,OAAQ;AAC7B,WAAO,OAAO,KAAK,IAAI,MAAM,OAAO,OAAO,MAAM,GAAG,GAAG,MAAM,MAAM,IAAI,CAAC;AAAA,EACzE;AACA,SAAO;AACR;",
6
6
  "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"motion-store.d.ts","sourceRoot":"","sources":["../../../src/core/motion/motion-store.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,yBAAyB,CAAC;AACzE,OAAO,KAAK,EACX,UAAU,EAIV,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,8FAA8F;IAC9F,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,qDAAqD;IACrD,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC;IAC/B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC9B;AA4ED,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,WAAW,GACjB,WAAW,CA0Ub"}
1
+ {"version":3,"file":"motion-store.d.ts","sourceRoot":"","sources":["../../../src/core/motion/motion-store.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,yBAAyB,CAAC;AACzE,OAAO,KAAK,EACX,UAAU,EAIV,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC3B,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/C,8FAA8F;IAC9F,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,qDAAqD;IACrD,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC;IAC/B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CAC9B;AAwED,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,WAAW,GACjB,WAAW,CAkSb"}
@@ -1,4 +1,3 @@
1
- import { spring, tween } from "@voila.dev/motion";
2
1
  import { enterFrom, exitTo } from "./enter-exit.js";
3
2
  import {
4
3
  leavingGuides,
@@ -8,7 +7,7 @@ import {
8
7
  STRING_CHANNELS,
9
8
  stringMixer,
10
9
  stringOf,
11
- withStrings
10
+ withString
12
11
  } from "./strings.js";
13
12
  import { staggerDelay } from "./timing.js";
14
13
  const FIELDS = {
@@ -43,6 +42,9 @@ function channelKey(node, channel) {
43
42
  function elapsed(motion, now) {
44
43
  return Math.max(0, (now - motion.start) / 1e3);
45
44
  }
45
+ function sample(motion, now) {
46
+ return motion.generator.at(elapsed(motion, now));
47
+ }
46
48
  function done(motion, now) {
47
49
  return elapsed(motion, now) >= motion.generator.duration;
48
50
  }
@@ -51,53 +53,37 @@ function clamp(value) {
51
53
  }
52
54
  function createMotionStore(initial, timing) {
53
55
  let target = initial;
54
- const numbers = /* @__PURE__ */ new Map();
55
- const strings = /* @__PURE__ */ new Map();
56
+ const channels = /* @__PURE__ */ new Map();
56
57
  const guides = /* @__PURE__ */ new Map();
57
58
  const shapes = /* @__PURE__ */ new Map();
58
59
  const leaving = /* @__PURE__ */ new Map();
59
- function generator(from, to, velocity) {
60
- return timing.type === "spring" ? spring({
61
- from,
62
- to,
63
- velocity,
64
- duration: timing.duration,
65
- bounce: timing.bounce
66
- }) : tween({
67
- keyframes: [from, to],
68
- duration: timing.duration,
69
- ease: timing.easing
70
- });
71
- }
72
60
  function value(node, channel, fallback, now) {
73
- const motion = numbers.get(channelKey(node, channel));
74
- return motion ? motion.generator.at(elapsed(motion, now)).value : fallback;
61
+ const motion = channels.get(channelKey(node, channel));
62
+ return motion ? sample(motion, now).value : fallback;
75
63
  }
76
64
  function spring1(node, channel, from, to, start, now) {
77
65
  const key = channelKey(node, channel);
78
- const running = numbers.get(key);
79
- const velocity = running ? running.generator.at(elapsed(running, now)).velocity : 0;
80
- numbers.delete(key);
66
+ const running = channels.get(key);
67
+ const velocity = running ? sample(running, now).velocity : 0;
68
+ channels.delete(key);
81
69
  if (from === to && velocity === 0) return;
82
- numbers.set(key, { generator: generator(from, to, velocity), start });
70
+ channels.set(key, { generator: timing.motion(from, to, velocity), start });
83
71
  }
84
72
  function mixString(from, to, channel, start) {
85
73
  const key = channelKey(to.key, channel);
86
- strings.delete(key);
74
+ channels.delete(key);
87
75
  const was = stringOf(from, channel);
88
76
  const goal = stringOf(to, channel);
89
77
  if (was === void 0 || goal === void 0 || was === goal) return;
90
- strings.set(key, {
91
- generator: generator(0, 1, 0),
78
+ channels.set(key, {
79
+ generator: timing.motion(0, 1, 0),
92
80
  start,
93
81
  mix: stringMixer(to, channel, was, goal)
94
82
  });
95
83
  }
96
84
  function drop(node) {
97
- for (const motions of [numbers, strings]) {
98
- for (const key of motions.keys()) {
99
- if (key.startsWith(node + SEPARATOR)) motions.delete(key);
100
- }
85
+ for (const key of channels.keys()) {
86
+ if (key.startsWith(node + SEPARATOR)) channels.delete(key);
101
87
  }
102
88
  shapes.delete(node);
103
89
  leaving.delete(node);
@@ -120,7 +106,7 @@ function createMotionStore(initial, timing) {
120
106
  if (to.kind !== "path" || from.kind !== "path") return;
121
107
  const previous = shapes.get(key);
122
108
  shapes.delete(key);
123
- strings.delete(channelKey(key, "d"));
109
+ channels.delete(channelKey(key, "d"));
124
110
  const motion = to.motion;
125
111
  const plan = motion && from.geometry && to.geometry ? motion.plan(from.geometry, to.geometry) : void 0;
126
112
  if (!plan) {
@@ -129,7 +115,7 @@ function createMotionStore(initial, timing) {
129
115
  }
130
116
  for (const channel of previous?.plan.targets.keys() ?? []) {
131
117
  if (!plan.targets.has(channel)) {
132
- numbers.delete(channelKey(key, `g${channel}`));
118
+ channels.delete(channelKey(key, `g${channel}`));
133
119
  }
134
120
  }
135
121
  for (const [channel, goal] of plan.targets) {
@@ -155,37 +141,31 @@ function createMotionStore(initial, timing) {
155
141
  let next = { ...node, ...moved };
156
142
  const shape = shapes.get(key);
157
143
  if (next.kind === "path" && shape) {
158
- const geometry2 = shape.plan.build(
144
+ const geometry = shape.plan.build(
159
145
  (channel) => value(key, `g${channel}`, shape.plan.targets.get(channel) ?? 0, now)
160
146
  );
161
- next = { ...next, geometry: geometry2, d: shape.motion.draw(geometry2) };
147
+ next = { ...next, geometry, d: shape.motion.draw(geometry) };
162
148
  }
163
- const mixed = {};
164
149
  for (const channel of STRING_CHANNELS) {
165
- const motion = strings.get(channelKey(key, channel));
166
- if (motion) {
167
- mixed[channel] = motion.mix(
168
- motion.generator.at(elapsed(motion, now)).value
169
- );
150
+ const motion = channels.get(channelKey(key, channel));
151
+ if (motion?.mix) {
152
+ next = withString(next, channel, motion.mix(sample(motion, now).value));
170
153
  }
171
154
  }
172
- next = withStrings(next, mixed);
173
155
  const opacity = value(key, OPACITY, 1, now);
174
156
  const drawn = value(key, DRAWN, 1, now);
175
157
  if (opacity === 1 && drawn === 1) return next;
176
- const geometry = next.kind === "path" ? next.geometry : void 0;
177
- const length = geometry && next.kind === "path" ? next.motion?.length?.(geometry) : 0;
178
158
  return {
179
159
  ...next,
180
160
  paint: {
181
161
  ...next.paint,
182
162
  opacity: (next.paint.opacity ?? 1) * clamp(opacity),
183
- ...drawn < 1 && geometry ? {
163
+ ...drawn < 1 && next.kind === "path" && next.geometry && {
184
164
  drawn: {
185
165
  fraction: clamp(drawn),
186
- length: length ?? 0
166
+ length: next.motion?.length?.(next.geometry) ?? 0
187
167
  }
188
- } : {}
168
+ }
189
169
  }
190
170
  };
191
171
  }
@@ -204,30 +184,24 @@ function createMotionStore(initial, timing) {
204
184
  place(node.key);
205
185
  }
206
186
  if (guides.size === 0) return out;
207
- const fading = [...guides.values()].map(
208
- (motion) => [
209
- motion.guide,
210
- Math.max(0, motion.generator.at(elapsed(motion, now)).value)
211
- ]
187
+ return withLeaving(
188
+ out,
189
+ parent,
190
+ guides.values(),
191
+ (node) => paintNode(node, now)
212
192
  );
213
- return withLeaving(out, parent, fading);
214
193
  }
215
194
  function prune(now) {
216
- for (const motions of [numbers, strings, guides]) {
217
- for (const [key, motion] of motions) {
218
- if (done(motion, now)) motions.delete(key);
219
- }
195
+ for (const [key, motion] of channels) {
196
+ if (done(motion, now)) channels.delete(key);
220
197
  }
221
198
  const moving = new Set(
222
- [...numbers.keys(), ...strings.keys()].map(
223
- (key) => key.split(SEPARATOR)[0]
224
- )
199
+ [...channels.keys()].map((key) => key.split(SEPARATOR)[0])
225
200
  );
226
- for (const key of shapes.keys()) {
227
- if (!moving.has(key)) shapes.delete(key);
228
- }
229
- for (const key of leaving.keys()) {
230
- if (!moving.has(key)) leaving.delete(key);
201
+ for (const byNode of [shapes, leaving, guides]) {
202
+ for (const key of byNode.keys()) {
203
+ if (!moving.has(key)) byNode.delete(key);
204
+ }
231
205
  }
232
206
  }
233
207
  return {
@@ -235,19 +209,15 @@ function createMotionStore(initial, timing) {
235
209
  const before = index(target.nodes);
236
210
  const after = index(scene.nodes);
237
211
  function painted(key) {
238
- const node = before.byKey.get(key) ?? leaving.get(key)?.node ?? guides.get(key)?.guide.node;
212
+ const node = before.byKey.get(key) ?? leaving.get(key)?.node ?? guides.get(key)?.node;
239
213
  return node && paintNode(node, now);
240
214
  }
241
- for (const guide of leavingGuides(
215
+ const fadingGuides = leavingGuides(
242
216
  target.nodes,
243
217
  (key) => after.byKey.has(key)
244
- )) {
245
- guides.set(guide.node.key, {
246
- generator: generator(value(guide.node.key, OPACITY, 1, now), 0, 0),
247
- start: now,
248
- guide
249
- });
250
- }
218
+ ).map(
219
+ (guide) => [guide, value(guide.node.key, OPACITY, 1, now)]
220
+ );
251
221
  for (const key of /* @__PURE__ */ new Set([...before.byKey.keys(), ...leaving.keys()])) {
252
222
  if (after.byKey.has(key)) continue;
253
223
  const base = before.byKey.get(key) ?? leaving.get(key)?.node;
@@ -266,6 +236,13 @@ function createMotionStore(initial, timing) {
266
236
  siblings
267
237
  });
268
238
  }
239
+ for (const [guide, shown] of fadingGuides) {
240
+ guides.set(guide.node.key, guide);
241
+ channels.set(channelKey(guide.node.key, OPACITY), {
242
+ generator: timing.motion(shown, 0, 0),
243
+ start: now
244
+ });
245
+ }
269
246
  const marks = [...after.byKey.values()].filter(
270
247
  (node) => node.role === "mark"
271
248
  );
@@ -277,10 +254,11 @@ function createMotionStore(initial, timing) {
277
254
  leaving.delete(node.key);
278
255
  const from = old ?? enterFrom(node, after.siblings.get(node.key) ?? [], before.byKey);
279
256
  if (from) move(from, node, start, now);
280
- const fading = numbers.get(channelKey(node.key, OPACITY));
281
- const returning = guides.get(node.key);
282
- if (!from && node.enter !== "none" || fading || returning) {
283
- const shown = returning ? Math.max(0, returning.generator.at(elapsed(returning, now)).value) : fading ? value(node.key, OPACITY, 1, now) : 0;
257
+ const fading = channels.get(channelKey(node.key, OPACITY));
258
+ if (!from && node.enter !== "none" || fading) {
259
+ const shown = fading ? Math.max(0, sample(fading, now).value) : 0;
260
+ if (guides.has(node.key))
261
+ channels.delete(channelKey(node.key, OPACITY));
284
262
  spring1(node.key, OPACITY, shown, 1, start, now);
285
263
  }
286
264
  }
@@ -300,22 +278,18 @@ function createMotionStore(initial, timing) {
300
278
  return any;
301
279
  },
302
280
  snap(scene) {
303
- for (const motions of [numbers, strings, guides, shapes, leaving]) {
281
+ for (const motions of [channels, guides, shapes, leaving]) {
304
282
  motions.clear();
305
283
  }
306
284
  target = scene;
307
285
  },
308
286
  frame(now) {
309
287
  prune(now);
310
- if (numbers.size + strings.size + guides.size === 0) return target;
288
+ if (channels.size === 0) return target;
311
289
  return { ...target, nodes: paintList(target.nodes, null, now) };
312
290
  },
313
291
  settled(now) {
314
- return [
315
- ...numbers.values(),
316
- ...strings.values(),
317
- ...guides.values()
318
- ].every((motion) => done(motion, now));
292
+ return [...channels.values()].every((motion) => done(motion, now));
319
293
  }
320
294
  };
321
295
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/motion/motion-store.ts"],
4
- "sourcesContent": ["import { type Generator, spring, tween } from \"@voila.dev/motion\";\nimport { enterFrom, exitTo } from \"#/core/motion/enter-exit.ts\";\nimport {\n\ttype LeavingGuide,\n\tleavingGuides,\n\twithLeaving,\n} from \"#/core/motion/leaving.ts\";\nimport {\n\tSTRING_CHANNELS,\n\ttype StringChannel,\n\tstringMixer,\n\tstringOf,\n\twithStrings,\n} from \"#/core/motion/strings.ts\";\nimport { type ChartTiming, staggerDelay } from \"#/core/motion/timing.ts\";\nimport type {\n\tChartScene,\n\tGeometryMotion,\n\tGeometryPlan,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\n/**\n * Every moving number of a chart, as springs keyed by node and channel. A\n * new scene retargets them: each channel starts from where it is painted, at\n * the speed it has, so an update landing mid-flight bends the motion instead\n * of kinking it. Pure and clocked from outside (milliseconds), so the SVG and\n * Canvas renderers move identically and tests step it by hand. Hit-testing\n * and focus never read it: they read the scene it moves towards, which never\n * holds the marks collapsing or the guides fading on their way out.\n */\nexport interface MotionStore {\n\tretarget(scene: ChartScene, now: number): void;\n\t/** The first render: lines that `enter: \"draw\"` trace themselves in. False when none does. */\n\tintroduce(now: number): boolean;\n\t/** Jumps to `scene`: a resize, or reduced motion. */\n\tsnap(scene: ChartScene): void;\n\tframe(now: number): ChartScene;\n\tsettled(now: number): boolean;\n}\n\ninterface Motion {\n\treadonly generator: Generator;\n\t/** Milliseconds, the delay included. */\n\treadonly start: number;\n}\n\n/** A path or a colour: a progress from 0 to 1, and the mix it reads. */\ninterface StringMotion extends Motion {\n\treadonly mix: (progress: number) => string;\n}\n\n/** A guide fading out where it stood. */\ninterface GuideMotion extends Motion {\n\treadonly guide: LeavingGuide;\n}\n\n/** A data mark collapsing on its way out: drawn after its old sibling until its springs settle. */\ninterface Leaving {\n\treadonly node: SceneNode;\n\treadonly after: string | undefined;\n\treadonly siblings: readonly SceneNode[];\n}\n\ntype Numeric = Record<string, number>;\n\nconst FIELDS: Record<SceneNode[\"kind\"], readonly string[]> = {\n\trect: [\"x\", \"y\", \"width\", \"height\"],\n\tcircle: [\"cx\", \"cy\", \"r\"],\n\tline: [\"x1\", \"y1\", \"x2\", \"y2\"],\n\ttext: [\"x\", \"y\"],\n\tpath: [],\n\tgroup: [],\n};\n\nconst OPACITY = \"opacity\";\nconst DRAWN = \"drawn\";\nconst SEPARATOR = \"\\u0000\";\n\nfunction walk(\n\tnodes: readonly SceneNode[],\n\tvisit: (node: SceneNode, siblings: readonly SceneNode[]) => void,\n) {\n\tfor (const node of nodes) {\n\t\tvisit(node, nodes);\n\t\tif (node.kind === \"group\") walk(node.children, visit);\n\t}\n}\n\nfunction index(nodes: readonly SceneNode[]) {\n\tconst byKey = new Map<string, SceneNode>();\n\tconst siblings = new Map<string, readonly SceneNode[]>();\n\twalk(nodes, (node, list) => {\n\t\tbyKey.set(node.key, node);\n\t\tsiblings.set(node.key, list);\n\t});\n\treturn { byKey, siblings };\n}\n\nfunction channelKey(node: string, channel: string): string {\n\treturn `${node}${SEPARATOR}${channel}`;\n}\n\nfunction elapsed(motion: Motion, now: number): number {\n\treturn Math.max(0, (now - motion.start) / 1000);\n}\n\nfunction done(motion: Motion, now: number): boolean {\n\treturn elapsed(motion, now) >= motion.generator.duration;\n}\n\nfunction clamp(value: number): number {\n\treturn Math.min(1, Math.max(0, value));\n}\n\nexport function createMotionStore(\n\tinitial: ChartScene,\n\ttiming: ChartTiming,\n): MotionStore {\n\tlet target = initial;\n\tconst numbers = new Map<string, Motion>();\n\tconst strings = new Map<string, StringMotion>();\n\tconst guides = new Map<string, GuideMotion>();\n\tconst shapes = new Map<\n\t\tstring,\n\t\t{ readonly plan: GeometryPlan; readonly motion: GeometryMotion }\n\t>();\n\tconst leaving = new Map<string, Leaving>();\n\n\tfunction generator(from: number, to: number, velocity: number): Generator {\n\t\treturn timing.type === \"spring\"\n\t\t\t? spring({\n\t\t\t\t\tfrom,\n\t\t\t\t\tto,\n\t\t\t\t\tvelocity,\n\t\t\t\t\tduration: timing.duration,\n\t\t\t\t\tbounce: timing.bounce,\n\t\t\t\t})\n\t\t\t: tween({\n\t\t\t\t\tkeyframes: [from, to],\n\t\t\t\t\tduration: timing.duration,\n\t\t\t\t\tease: timing.easing,\n\t\t\t\t});\n\t}\n\n\tfunction value(node: string, channel: string, fallback: number, now: number) {\n\t\tconst motion = numbers.get(channelKey(node, channel));\n\t\treturn motion ? motion.generator.at(elapsed(motion, now)).value : fallback;\n\t}\n\n\t/** Springs one channel from `from` to `to`, at the speed it already has. */\n\tfunction spring1(\n\t\tnode: string,\n\t\tchannel: string,\n\t\tfrom: number,\n\t\tto: number,\n\t\tstart: number,\n\t\tnow: number,\n\t) {\n\t\tconst key = channelKey(node, channel);\n\t\tconst running = numbers.get(key);\n\t\tconst velocity = running\n\t\t\t? running.generator.at(elapsed(running, now)).velocity\n\t\t\t: 0;\n\t\tnumbers.delete(key);\n\t\tif (from === to && velocity === 0) return;\n\t\tnumbers.set(key, { generator: generator(from, to, velocity), start });\n\t}\n\n\t/** Mixes one string channel from what is painted to what `to` asks for. */\n\tfunction mixString(\n\t\tfrom: SceneNode,\n\t\tto: SceneNode,\n\t\tchannel: StringChannel,\n\t\tstart: number,\n\t) {\n\t\tconst key = channelKey(to.key, channel);\n\t\tstrings.delete(key);\n\t\tconst was = stringOf(from, channel);\n\t\tconst goal = stringOf(to, channel);\n\t\tif (was === undefined || goal === undefined || was === goal) return;\n\t\tstrings.set(key, {\n\t\t\tgenerator: generator(0, 1, 0),\n\t\t\tstart,\n\t\t\tmix: stringMixer(to, channel, was, goal),\n\t\t});\n\t}\n\n\tfunction drop(node: string) {\n\t\tfor (const motions of [numbers, strings]) {\n\t\t\tfor (const key of motions.keys()) {\n\t\t\t\tif (key.startsWith(node + SEPARATOR)) motions.delete(key);\n\t\t\t}\n\t\t}\n\t\tshapes.delete(node);\n\t\tleaving.delete(node);\n\t}\n\n\t/** `from` is the node as painted now (or the shape it enters from), `to` where it goes. */\n\tfunction move(from: SceneNode, to: SceneNode, start: number, now: number) {\n\t\tconst key = to.key;\n\t\tfor (const field of FIELDS[to.kind]) {\n\t\t\tconst was = (from as unknown as Numeric)[field] as number;\n\t\t\tspring1(\n\t\t\t\tkey,\n\t\t\t\tfield,\n\t\t\t\twas,\n\t\t\t\t(to as unknown as Numeric)[field] as number,\n\t\t\t\tstart,\n\t\t\t\tnow,\n\t\t\t);\n\t\t}\n\t\tmixString(from, to, \"fill\", start);\n\t\tmixString(from, to, \"stroke\", start);\n\t\tif (to.kind !== \"path\" || from.kind !== \"path\") return;\n\t\tconst previous = shapes.get(key);\n\t\tshapes.delete(key);\n\t\tstrings.delete(channelKey(key, \"d\"));\n\t\tconst motion = to.motion;\n\t\tconst plan =\n\t\t\tmotion && from.geometry && to.geometry\n\t\t\t\t? motion.plan(from.geometry, to.geometry)\n\t\t\t\t: undefined;\n\t\tif (!plan) {\n\t\t\t// No meaning to move: the node's own morph, else the numbers of `d`.\n\t\t\tmixString(from, to, \"d\", start);\n\t\t\treturn;\n\t\t}\n\t\tfor (const channel of previous?.plan.targets.keys() ?? []) {\n\t\t\tif (!plan.targets.has(channel)) {\n\t\t\t\tnumbers.delete(channelKey(key, `g${channel}`));\n\t\t\t}\n\t\t}\n\t\tfor (const [channel, goal] of plan.targets) {\n\t\t\tconst was = plan.starts.get(channel) ?? goal;\n\t\t\tspring1(key, `g${channel}`, was, goal, start, now);\n\t\t}\n\t\tshapes.set(key, { plan, motion: motion as GeometryMotion });\n\t}\n\n\tfunction paintNode(node: SceneNode, now: number): SceneNode {\n\t\tif (node.kind === \"group\") {\n\t\t\treturn { ...node, children: paintList(node.children, node.key, now) };\n\t\t}\n\t\tconst key = node.key;\n\t\tconst moved: Numeric = {};\n\t\tfor (const field of FIELDS[node.kind]) {\n\t\t\tmoved[field] = value(\n\t\t\t\tkey,\n\t\t\t\tfield,\n\t\t\t\t(node as unknown as Numeric)[field] as number,\n\t\t\t\tnow,\n\t\t\t);\n\t\t}\n\t\tlet next = { ...node, ...moved } as Exclude<SceneNode, { kind: \"group\" }>;\n\t\tconst shape = shapes.get(key);\n\t\tif (next.kind === \"path\" && shape) {\n\t\t\tconst geometry = shape.plan.build((channel) =>\n\t\t\t\tvalue(key, `g${channel}`, shape.plan.targets.get(channel) ?? 0, now),\n\t\t\t);\n\t\t\tnext = { ...next, geometry, d: shape.motion.draw(geometry) };\n\t\t}\n\t\tconst mixed: Partial<Record<StringChannel, string>> = {};\n\t\tfor (const channel of STRING_CHANNELS) {\n\t\t\tconst motion = strings.get(channelKey(key, channel));\n\t\t\tif (motion) {\n\t\t\t\tmixed[channel] = motion.mix(\n\t\t\t\t\tmotion.generator.at(elapsed(motion, now)).value,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tnext = withStrings(next, mixed) as typeof next;\n\t\tconst opacity = value(key, OPACITY, 1, now);\n\t\tconst drawn = value(key, DRAWN, 1, now);\n\t\tif (opacity === 1 && drawn === 1) return next;\n\t\tconst geometry = next.kind === \"path\" ? next.geometry : undefined;\n\t\tconst length =\n\t\t\tgeometry && next.kind === \"path\" ? next.motion?.length?.(geometry) : 0;\n\t\treturn {\n\t\t\t...next,\n\t\t\tpaint: {\n\t\t\t\t...next.paint,\n\t\t\t\topacity: (next.paint.opacity ?? 1) * clamp(opacity),\n\t\t\t\t...(drawn < 1 && geometry\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tdrawn: {\n\t\t\t\t\t\t\t\tfraction: clamp(drawn),\n\t\t\t\t\t\t\t\tlength: length ?? 0,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t} as SceneNode;\n\t}\n\n\t/**\n\t * The painted list, with the marks still collapsing after the sibling they\n\t * followed, and the guides still fading back at their places.\n\t */\n\tfunction paintList(\n\t\tlist: readonly SceneNode[],\n\t\tparent: string | null,\n\t\tnow: number,\n\t): SceneNode[] {\n\t\tconst out: SceneNode[] = [];\n\t\tfunction place(after: string | undefined) {\n\t\t\tfor (const [key, exit] of leaving) {\n\t\t\t\tif (exit.after !== after) continue;\n\t\t\t\tout.push(paintNode(exit.node, now));\n\t\t\t\tplace(key);\n\t\t\t}\n\t\t}\n\t\tif (parent === null) place(undefined);\n\t\tfor (const node of list) {\n\t\t\tout.push(paintNode(node, now));\n\t\t\tplace(node.key);\n\t\t}\n\t\tif (guides.size === 0) return out;\n\t\tconst fading = [...guides.values()].map(\n\t\t\t(motion) =>\n\t\t\t\t[\n\t\t\t\t\tmotion.guide,\n\t\t\t\t\tMath.max(0, motion.generator.at(elapsed(motion, now)).value),\n\t\t\t\t] as const,\n\t\t);\n\t\treturn withLeaving(out, parent, fading);\n\t}\n\n\tfunction prune(now: number) {\n\t\tfor (const motions of [numbers, strings, guides]) {\n\t\t\tfor (const [key, motion] of motions) {\n\t\t\t\tif (done(motion, now)) motions.delete(key);\n\t\t\t}\n\t\t}\n\t\tconst moving = new Set(\n\t\t\t[...numbers.keys(), ...strings.keys()].map(\n\t\t\t\t(key) => key.split(SEPARATOR)[0],\n\t\t\t),\n\t\t);\n\t\tfor (const key of shapes.keys()) {\n\t\t\tif (!moving.has(key)) shapes.delete(key);\n\t\t}\n\t\tfor (const key of leaving.keys()) {\n\t\t\tif (!moving.has(key)) leaving.delete(key);\n\t\t}\n\t}\n\n\treturn {\n\t\tretarget(scene, now) {\n\t\t\tconst before = index(target.nodes);\n\t\t\tconst after = index(scene.nodes);\n\t\t\tfunction painted(key: string): SceneNode | undefined {\n\t\t\t\tconst node =\n\t\t\t\t\tbefore.byKey.get(key) ??\n\t\t\t\t\tleaving.get(key)?.node ??\n\t\t\t\t\tguides.get(key)?.guide.node;\n\t\t\t\treturn node && paintNode(node, now);\n\t\t\t}\n\t\t\t// Read before the exits below drop what the guides were showing.\n\t\t\tfor (const guide of leavingGuides(target.nodes, (key) =>\n\t\t\t\tafter.byKey.has(key),\n\t\t\t)) {\n\t\t\t\tguides.set(guide.node.key, {\n\t\t\t\t\tgenerator: generator(value(guide.node.key, OPACITY, 1, now), 0, 0),\n\t\t\t\t\tstart: now,\n\t\t\t\t\tguide,\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const key of new Set([...before.byKey.keys(), ...leaving.keys()])) {\n\t\t\t\tif (after.byKey.has(key)) continue;\n\t\t\t\tconst base = (before.byKey.get(key) ??\n\t\t\t\t\tleaving.get(key)?.node) as SceneNode;\n\t\t\t\tconst siblings =\n\t\t\t\t\tbefore.siblings.get(key) ?? leaving.get(key)?.siblings ?? [];\n\t\t\t\tconst goal = exitTo(base, siblings, after.byKey);\n\t\t\t\tconst from = painted(key);\n\t\t\t\tif (!goal || !from) {\n\t\t\t\t\tdrop(key);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tmove(from, goal, now, now);\n\t\t\t\tconst at = siblings.findIndex((node) => node.key === key);\n\t\t\t\tleaving.set(key, {\n\t\t\t\t\tnode: goal,\n\t\t\t\t\tafter: siblings[at - 1]?.key,\n\t\t\t\t\tsiblings,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst marks = [...after.byKey.values()].filter(\n\t\t\t\t(node) => node.role === \"mark\",\n\t\t\t);\n\t\t\tconst order = new Map(marks.map((node, at) => [node.key, at]));\n\t\t\tfor (const node of after.byKey.values()) {\n\t\t\t\tif (node.kind === \"group\") continue;\n\t\t\t\tconst start =\n\t\t\t\t\tnow +\n\t\t\t\t\tstaggerDelay(timing, order.get(node.key) ?? 0, marks.length) * 1000;\n\t\t\t\tconst old = painted(node.key);\n\t\t\t\tleaving.delete(node.key);\n\t\t\t\tconst from =\n\t\t\t\t\told ??\n\t\t\t\t\tenterFrom(node, after.siblings.get(node.key) ?? [], before.byKey);\n\t\t\t\tif (from) move(from, node, start, now);\n\t\t\t\tconst fading = numbers.get(channelKey(node.key, OPACITY));\n\t\t\t\tconst returning = guides.get(node.key);\n\t\t\t\tif ((!from && node.enter !== \"none\") || fading || returning) {\n\t\t\t\t\tconst shown = returning\n\t\t\t\t\t\t? Math.max(0, returning.generator.at(elapsed(returning, now)).value)\n\t\t\t\t\t\t: fading\n\t\t\t\t\t\t\t? value(node.key, OPACITY, 1, now)\n\t\t\t\t\t\t\t: 0;\n\t\t\t\t\tspring1(node.key, OPACITY, shown, 1, start, now);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const key of after.byKey.keys()) guides.delete(key);\n\t\t\ttarget = scene;\n\t\t},\n\t\tintroduce(now) {\n\t\t\tlet any = false;\n\t\t\twalk(target.nodes, (node) => {\n\t\t\t\tif (node.kind !== \"path\" || node.enter !== \"draw\") return;\n\t\t\t\tif (node.geometry?.kind !== \"points\" || node.paint.strokeDasharray) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tspring1(node.key, DRAWN, 0, 1, now, now);\n\t\t\t\tany = true;\n\t\t\t});\n\t\t\treturn any;\n\t\t},\n\t\tsnap(scene) {\n\t\t\tfor (const motions of [numbers, strings, guides, shapes, leaving]) {\n\t\t\t\tmotions.clear();\n\t\t\t}\n\t\t\ttarget = scene;\n\t\t},\n\t\tframe(now) {\n\t\t\tprune(now);\n\t\t\tif (numbers.size + strings.size + guides.size === 0) return target;\n\t\t\treturn { ...target, nodes: paintList(target.nodes, null, now) };\n\t\t},\n\t\tsettled(now) {\n\t\t\treturn [\n\t\t\t\t...numbers.values(),\n\t\t\t\t...strings.values(),\n\t\t\t\t...guides.values(),\n\t\t\t].every((motion) => done(motion, now));\n\t\t},\n\t};\n}\n"],
5
- "mappings": "AAAA,SAAyB,QAAQ,aAAa;AAC9C,SAAS,WAAW,cAAc;AAClC;AAAA,EAEC;AAAA,EACA;AAAA,OACM;AACP;AAAA,EACC;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OACM;AACP,SAA2B,oBAAoB;AAoD/C,MAAM,SAAuD;AAAA,EAC5D,MAAM,CAAC,KAAK,KAAK,SAAS,QAAQ;AAAA,EAClC,QAAQ,CAAC,MAAM,MAAM,GAAG;AAAA,EACxB,MAAM,CAAC,MAAM,MAAM,MAAM,IAAI;AAAA,EAC7B,MAAM,CAAC,KAAK,GAAG;AAAA,EACf,MAAM,CAAC;AAAA,EACP,OAAO,CAAC;AACT;AAEA,MAAM,UAAU;AAChB,MAAM,QAAQ;AACd,MAAM,YAAY;AAElB,SAAS,KACR,OACA,OACC;AACD,aAAW,QAAQ,OAAO;AACzB,UAAM,MAAM,KAAK;AACjB,QAAI,KAAK,SAAS,QAAS,MAAK,KAAK,UAAU,KAAK;AAAA,EACrD;AACD;AAEA,SAAS,MAAM,OAA6B;AAC3C,QAAM,QAAQ,oBAAI,IAAuB;AACzC,QAAM,WAAW,oBAAI,IAAkC;AACvD,OAAK,OAAO,CAAC,MAAM,SAAS;AAC3B,UAAM,IAAI,KAAK,KAAK,IAAI;AACxB,aAAS,IAAI,KAAK,KAAK,IAAI;AAAA,EAC5B,CAAC;AACD,SAAO,EAAE,OAAO,SAAS;AAC1B;AAEA,SAAS,WAAW,MAAc,SAAyB;AAC1D,SAAO,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO;AACrC;AAEA,SAAS,QAAQ,QAAgB,KAAqB;AACrD,SAAO,KAAK,IAAI,IAAI,MAAM,OAAO,SAAS,GAAI;AAC/C;AAEA,SAAS,KAAK,QAAgB,KAAsB;AACnD,SAAO,QAAQ,QAAQ,GAAG,KAAK,OAAO,UAAU;AACjD;AAEA,SAAS,MAAM,OAAuB;AACrC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC;AACtC;AAEO,SAAS,kBACf,SACA,QACc;AACd,MAAI,SAAS;AACb,QAAM,UAAU,oBAAI,IAAoB;AACxC,QAAM,UAAU,oBAAI,IAA0B;AAC9C,QAAM,SAAS,oBAAI,IAAyB;AAC5C,QAAM,SAAS,oBAAI,IAGjB;AACF,QAAM,UAAU,oBAAI,IAAqB;AAEzC,WAAS,UAAU,MAAc,IAAY,UAA6B;AACzE,WAAO,OAAO,SAAS,WACpB,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU,OAAO;AAAA,MACjB,QAAQ,OAAO;AAAA,IAChB,CAAC,IACA,MAAM;AAAA,MACN,WAAW,CAAC,MAAM,EAAE;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB,MAAM,OAAO;AAAA,IACd,CAAC;AAAA,EACJ;AAEA,WAAS,MAAM,MAAc,SAAiB,UAAkB,KAAa;AAC5E,UAAM,SAAS,QAAQ,IAAI,WAAW,MAAM,OAAO,CAAC;AACpD,WAAO,SAAS,OAAO,UAAU,GAAG,QAAQ,QAAQ,GAAG,CAAC,EAAE,QAAQ;AAAA,EACnE;AAGA,WAAS,QACR,MACA,SACA,MACA,IACA,OACA,KACC;AACD,UAAM,MAAM,WAAW,MAAM,OAAO;AACpC,UAAM,UAAU,QAAQ,IAAI,GAAG;AAC/B,UAAM,WAAW,UACd,QAAQ,UAAU,GAAG,QAAQ,SAAS,GAAG,CAAC,EAAE,WAC5C;AACH,YAAQ,OAAO,GAAG;AAClB,QAAI,SAAS,MAAM,aAAa,EAAG;AACnC,YAAQ,IAAI,KAAK,EAAE,WAAW,UAAU,MAAM,IAAI,QAAQ,GAAG,MAAM,CAAC;AAAA,EACrE;AAGA,WAAS,UACR,MACA,IACA,SACA,OACC;AACD,UAAM,MAAM,WAAW,GAAG,KAAK,OAAO;AACtC,YAAQ,OAAO,GAAG;AAClB,UAAM,MAAM,SAAS,MAAM,OAAO;AAClC,UAAM,OAAO,SAAS,IAAI,OAAO;AACjC,QAAI,QAAQ,UAAa,SAAS,UAAa,QAAQ,KAAM;AAC7D,YAAQ,IAAI,KAAK;AAAA,MAChB,WAAW,UAAU,GAAG,GAAG,CAAC;AAAA,MAC5B;AAAA,MACA,KAAK,YAAY,IAAI,SAAS,KAAK,IAAI;AAAA,IACxC,CAAC;AAAA,EACF;AAEA,WAAS,KAAK,MAAc;AAC3B,eAAW,WAAW,CAAC,SAAS,OAAO,GAAG;AACzC,iBAAW,OAAO,QAAQ,KAAK,GAAG;AACjC,YAAI,IAAI,WAAW,OAAO,SAAS,EAAG,SAAQ,OAAO,GAAG;AAAA,MACzD;AAAA,IACD;AACA,WAAO,OAAO,IAAI;AAClB,YAAQ,OAAO,IAAI;AAAA,EACpB;AAGA,WAAS,KAAK,MAAiB,IAAe,OAAe,KAAa;AACzE,UAAM,MAAM,GAAG;AACf,eAAW,SAAS,OAAO,GAAG,IAAI,GAAG;AACpC,YAAM,MAAO,KAA4B,KAAK;AAC9C;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACC,GAA0B,KAAK;AAAA,QAChC;AAAA,QACA;AAAA,MACD;AAAA,IACD;AACA,cAAU,MAAM,IAAI,QAAQ,KAAK;AACjC,cAAU,MAAM,IAAI,UAAU,KAAK;AACnC,QAAI,GAAG,SAAS,UAAU,KAAK,SAAS,OAAQ;AAChD,UAAM,WAAW,OAAO,IAAI,GAAG;AAC/B,WAAO,OAAO,GAAG;AACjB,YAAQ,OAAO,WAAW,KAAK,GAAG,CAAC;AACnC,UAAM,SAAS,GAAG;AAClB,UAAM,OACL,UAAU,KAAK,YAAY,GAAG,WAC3B,OAAO,KAAK,KAAK,UAAU,GAAG,QAAQ,IACtC;AACJ,QAAI,CAAC,MAAM;AAEV,gBAAU,MAAM,IAAI,KAAK,KAAK;AAC9B;AAAA,IACD;AACA,eAAW,WAAW,UAAU,KAAK,QAAQ,KAAK,KAAK,CAAC,GAAG;AAC1D,UAAI,CAAC,KAAK,QAAQ,IAAI,OAAO,GAAG;AAC/B,gBAAQ,OAAO,WAAW,KAAK,IAAI,OAAO,EAAE,CAAC;AAAA,MAC9C;AAAA,IACD;AACA,eAAW,CAAC,SAAS,IAAI,KAAK,KAAK,SAAS;AAC3C,YAAM,MAAM,KAAK,OAAO,IAAI,OAAO,KAAK;AACxC,cAAQ,KAAK,IAAI,OAAO,IAAI,KAAK,MAAM,OAAO,GAAG;AAAA,IAClD;AACA,WAAO,IAAI,KAAK,EAAE,MAAM,OAAiC,CAAC;AAAA,EAC3D;AAEA,WAAS,UAAU,MAAiB,KAAwB;AAC3D,QAAI,KAAK,SAAS,SAAS;AAC1B,aAAO,EAAE,GAAG,MAAM,UAAU,UAAU,KAAK,UAAU,KAAK,KAAK,GAAG,EAAE;AAAA,IACrE;AACA,UAAM,MAAM,KAAK;AACjB,UAAM,QAAiB,CAAC;AACxB,eAAW,SAAS,OAAO,KAAK,IAAI,GAAG;AACtC,YAAM,KAAK,IAAI;AAAA,QACd;AAAA,QACA;AAAA,QACC,KAA4B,KAAK;AAAA,QAClC;AAAA,MACD;AAAA,IACD;AACA,QAAI,OAAO,EAAE,GAAG,MAAM,GAAG,MAAM;AAC/B,UAAM,QAAQ,OAAO,IAAI,GAAG;AAC5B,QAAI,KAAK,SAAS,UAAU,OAAO;AAClC,YAAMA,YAAW,MAAM,KAAK;AAAA,QAAM,CAAC,YAClC,MAAM,KAAK,IAAI,OAAO,IAAI,MAAM,KAAK,QAAQ,IAAI,OAAO,KAAK,GAAG,GAAG;AAAA,MACpE;AACA,aAAO,EAAE,GAAG,MAAM,UAAAA,WAAU,GAAG,MAAM,OAAO,KAAKA,SAAQ,EAAE;AAAA,IAC5D;AACA,UAAM,QAAgD,CAAC;AACvD,eAAW,WAAW,iBAAiB;AACtC,YAAM,SAAS,QAAQ,IAAI,WAAW,KAAK,OAAO,CAAC;AACnD,UAAI,QAAQ;AACX,cAAM,OAAO,IAAI,OAAO;AAAA,UACvB,OAAO,UAAU,GAAG,QAAQ,QAAQ,GAAG,CAAC,EAAE;AAAA,QAC3C;AAAA,MACD;AAAA,IACD;AACA,WAAO,YAAY,MAAM,KAAK;AAC9B,UAAM,UAAU,MAAM,KAAK,SAAS,GAAG,GAAG;AAC1C,UAAM,QAAQ,MAAM,KAAK,OAAO,GAAG,GAAG;AACtC,QAAI,YAAY,KAAK,UAAU,EAAG,QAAO;AACzC,UAAM,WAAW,KAAK,SAAS,SAAS,KAAK,WAAW;AACxD,UAAM,SACL,YAAY,KAAK,SAAS,SAAS,KAAK,QAAQ,SAAS,QAAQ,IAAI;AACtE,WAAO;AAAA,MACN,GAAG;AAAA,MACH,OAAO;AAAA,QACN,GAAG,KAAK;AAAA,QACR,UAAU,KAAK,MAAM,WAAW,KAAK,MAAM,OAAO;AAAA,QAClD,GAAI,QAAQ,KAAK,WACd;AAAA,UACA,OAAO;AAAA,YACN,UAAU,MAAM,KAAK;AAAA,YACrB,QAAQ,UAAU;AAAA,UACnB;AAAA,QACD,IACC,CAAC;AAAA,MACL;AAAA,IACD;AAAA,EACD;AAMA,WAAS,UACR,MACA,QACA,KACc;AACd,UAAM,MAAmB,CAAC;AAC1B,aAAS,MAAM,OAA2B;AACzC,iBAAW,CAAC,KAAK,IAAI,KAAK,SAAS;AAClC,YAAI,KAAK,UAAU,MAAO;AAC1B,YAAI,KAAK,UAAU,KAAK,MAAM,GAAG,CAAC;AAClC,cAAM,GAAG;AAAA,MACV;AAAA,IACD;AACA,QAAI,WAAW,KAAM,OAAM,MAAS;AACpC,eAAW,QAAQ,MAAM;AACxB,UAAI,KAAK,UAAU,MAAM,GAAG,CAAC;AAC7B,YAAM,KAAK,GAAG;AAAA,IACf;AACA,QAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,UAAM,SAAS,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE;AAAA,MACnC,CAAC,WACA;AAAA,QACC,OAAO;AAAA,QACP,KAAK,IAAI,GAAG,OAAO,UAAU,GAAG,QAAQ,QAAQ,GAAG,CAAC,EAAE,KAAK;AAAA,MAC5D;AAAA,IACF;AACA,WAAO,YAAY,KAAK,QAAQ,MAAM;AAAA,EACvC;AAEA,WAAS,MAAM,KAAa;AAC3B,eAAW,WAAW,CAAC,SAAS,SAAS,MAAM,GAAG;AACjD,iBAAW,CAAC,KAAK,MAAM,KAAK,SAAS;AACpC,YAAI,KAAK,QAAQ,GAAG,EAAG,SAAQ,OAAO,GAAG;AAAA,MAC1C;AAAA,IACD;AACA,UAAM,SAAS,IAAI;AAAA,MAClB,CAAC,GAAG,QAAQ,KAAK,GAAG,GAAG,QAAQ,KAAK,CAAC,EAAE;AAAA,QACtC,CAAC,QAAQ,IAAI,MAAM,SAAS,EAAE,CAAC;AAAA,MAChC;AAAA,IACD;AACA,eAAW,OAAO,OAAO,KAAK,GAAG;AAChC,UAAI,CAAC,OAAO,IAAI,GAAG,EAAG,QAAO,OAAO,GAAG;AAAA,IACxC;AACA,eAAW,OAAO,QAAQ,KAAK,GAAG;AACjC,UAAI,CAAC,OAAO,IAAI,GAAG,EAAG,SAAQ,OAAO,GAAG;AAAA,IACzC;AAAA,EACD;AAEA,SAAO;AAAA,IACN,SAAS,OAAO,KAAK;AACpB,YAAM,SAAS,MAAM,OAAO,KAAK;AACjC,YAAM,QAAQ,MAAM,MAAM,KAAK;AAC/B,eAAS,QAAQ,KAAoC;AACpD,cAAM,OACL,OAAO,MAAM,IAAI,GAAG,KACpB,QAAQ,IAAI,GAAG,GAAG,QAClB,OAAO,IAAI,GAAG,GAAG,MAAM;AACxB,eAAO,QAAQ,UAAU,MAAM,GAAG;AAAA,MACnC;AAEA,iBAAW,SAAS;AAAA,QAAc,OAAO;AAAA,QAAO,CAAC,QAChD,MAAM,MAAM,IAAI,GAAG;AAAA,MACpB,GAAG;AACF,eAAO,IAAI,MAAM,KAAK,KAAK;AAAA,UAC1B,WAAW,UAAU,MAAM,MAAM,KAAK,KAAK,SAAS,GAAG,GAAG,GAAG,GAAG,CAAC;AAAA,UACjE,OAAO;AAAA,UACP;AAAA,QACD,CAAC;AAAA,MACF;AACA,iBAAW,OAAO,oBAAI,IAAI,CAAC,GAAG,OAAO,MAAM,KAAK,GAAG,GAAG,QAAQ,KAAK,CAAC,CAAC,GAAG;AACvE,YAAI,MAAM,MAAM,IAAI,GAAG,EAAG;AAC1B,cAAM,OAAQ,OAAO,MAAM,IAAI,GAAG,KACjC,QAAQ,IAAI,GAAG,GAAG;AACnB,cAAM,WACL,OAAO,SAAS,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,GAAG,YAAY,CAAC;AAC5D,cAAM,OAAO,OAAO,MAAM,UAAU,MAAM,KAAK;AAC/C,cAAM,OAAO,QAAQ,GAAG;AACxB,YAAI,CAAC,QAAQ,CAAC,MAAM;AACnB,eAAK,GAAG;AACR;AAAA,QACD;AACA,aAAK,MAAM,MAAM,KAAK,GAAG;AACzB,cAAM,KAAK,SAAS,UAAU,CAAC,SAAS,KAAK,QAAQ,GAAG;AACxD,gBAAQ,IAAI,KAAK;AAAA,UAChB,MAAM;AAAA,UACN,OAAO,SAAS,KAAK,CAAC,GAAG;AAAA,UACzB;AAAA,QACD,CAAC;AAAA,MACF;AACA,YAAM,QAAQ,CAAC,GAAG,MAAM,MAAM,OAAO,CAAC,EAAE;AAAA,QACvC,CAAC,SAAS,KAAK,SAAS;AAAA,MACzB;AACA,YAAM,QAAQ,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,OAAO,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;AAC7D,iBAAW,QAAQ,MAAM,MAAM,OAAO,GAAG;AACxC,YAAI,KAAK,SAAS,QAAS;AAC3B,cAAM,QACL,MACA,aAAa,QAAQ,MAAM,IAAI,KAAK,GAAG,KAAK,GAAG,MAAM,MAAM,IAAI;AAChE,cAAM,MAAM,QAAQ,KAAK,GAAG;AAC5B,gBAAQ,OAAO,KAAK,GAAG;AACvB,cAAM,OACL,OACA,UAAU,MAAM,MAAM,SAAS,IAAI,KAAK,GAAG,KAAK,CAAC,GAAG,OAAO,KAAK;AACjE,YAAI,KAAM,MAAK,MAAM,MAAM,OAAO,GAAG;AACrC,cAAM,SAAS,QAAQ,IAAI,WAAW,KAAK,KAAK,OAAO,CAAC;AACxD,cAAM,YAAY,OAAO,IAAI,KAAK,GAAG;AACrC,YAAK,CAAC,QAAQ,KAAK,UAAU,UAAW,UAAU,WAAW;AAC5D,gBAAM,QAAQ,YACX,KAAK,IAAI,GAAG,UAAU,UAAU,GAAG,QAAQ,WAAW,GAAG,CAAC,EAAE,KAAK,IACjE,SACC,MAAM,KAAK,KAAK,SAAS,GAAG,GAAG,IAC/B;AACJ,kBAAQ,KAAK,KAAK,SAAS,OAAO,GAAG,OAAO,GAAG;AAAA,QAChD;AAAA,MACD;AACA,iBAAW,OAAO,MAAM,MAAM,KAAK,EAAG,QAAO,OAAO,GAAG;AACvD,eAAS;AAAA,IACV;AAAA,IACA,UAAU,KAAK;AACd,UAAI,MAAM;AACV,WAAK,OAAO,OAAO,CAAC,SAAS;AAC5B,YAAI,KAAK,SAAS,UAAU,KAAK,UAAU,OAAQ;AACnD,YAAI,KAAK,UAAU,SAAS,YAAY,KAAK,MAAM,iBAAiB;AACnE;AAAA,QACD;AACA,gBAAQ,KAAK,KAAK,OAAO,GAAG,GAAG,KAAK,GAAG;AACvC,cAAM;AAAA,MACP,CAAC;AACD,aAAO;AAAA,IACR;AAAA,IACA,KAAK,OAAO;AACX,iBAAW,WAAW,CAAC,SAAS,SAAS,QAAQ,QAAQ,OAAO,GAAG;AAClE,gBAAQ,MAAM;AAAA,MACf;AACA,eAAS;AAAA,IACV;AAAA,IACA,MAAM,KAAK;AACV,YAAM,GAAG;AACT,UAAI,QAAQ,OAAO,QAAQ,OAAO,OAAO,SAAS,EAAG,QAAO;AAC5D,aAAO,EAAE,GAAG,QAAQ,OAAO,UAAU,OAAO,OAAO,MAAM,GAAG,EAAE;AAAA,IAC/D;AAAA,IACA,QAAQ,KAAK;AACZ,aAAO;AAAA,QACN,GAAG,QAAQ,OAAO;AAAA,QAClB,GAAG,QAAQ,OAAO;AAAA,QAClB,GAAG,OAAO,OAAO;AAAA,MAClB,EAAE,MAAM,CAAC,WAAW,KAAK,QAAQ,GAAG,CAAC;AAAA,IACtC;AAAA,EACD;AACD;",
6
- "names": ["geometry"]
4
+ "sourcesContent": ["import type { Generator } from \"@voila.dev/motion\";\nimport { enterFrom, exitTo } from \"#/core/motion/enter-exit.ts\";\nimport {\n\ttype LeavingGuide,\n\tleavingGuides,\n\twithLeaving,\n} from \"#/core/motion/leaving.ts\";\nimport {\n\tSTRING_CHANNELS,\n\ttype StringChannel,\n\tstringMixer,\n\tstringOf,\n\twithString,\n} from \"#/core/motion/strings.ts\";\nimport { type ChartTiming, staggerDelay } from \"#/core/motion/timing.ts\";\nimport type {\n\tChartScene,\n\tGeometryMotion,\n\tGeometryPlan,\n\tSceneNode,\n} from \"#/core/types.ts\";\n\n/**\n * Every moving number of a chart, as springs keyed by node and channel. A\n * new scene retargets them: each channel starts from where it is painted, at\n * the speed it has, so an update landing mid-flight bends the motion instead\n * of kinking it. Pure and clocked from outside (milliseconds), so the SVG and\n * Canvas renderers move identically and tests step it by hand. Hit-testing\n * and focus never read it: they read the scene it moves towards, which never\n * holds the marks collapsing or the guides fading on their way out.\n */\nexport interface MotionStore {\n\tretarget(scene: ChartScene, now: number): void;\n\t/** The first render: lines that `enter: \"draw\"` trace themselves in. False when none does. */\n\tintroduce(now: number): boolean;\n\t/** Jumps to `scene`: a resize, or reduced motion. */\n\tsnap(scene: ChartScene): void;\n\tframe(now: number): ChartScene;\n\tsettled(now: number): boolean;\n}\n\ninterface Motion {\n\treadonly generator: Generator;\n\t/** Milliseconds, the delay included. */\n\treadonly start: number;\n\t/** A path or a colour: the generator runs a progress from 0 to 1, and this reads it. */\n\treadonly mix?: (progress: number) => string;\n}\n\n/** A data mark collapsing on its way out: drawn after its old sibling until its springs settle. */\ninterface Leaving {\n\treadonly node: SceneNode;\n\treadonly after: string | undefined;\n\treadonly siblings: readonly SceneNode[];\n}\n\ntype Numeric = Record<string, number>;\n\nconst FIELDS: Record<SceneNode[\"kind\"], readonly string[]> = {\n\trect: [\"x\", \"y\", \"width\", \"height\"],\n\tcircle: [\"cx\", \"cy\", \"r\"],\n\tline: [\"x1\", \"y1\", \"x2\", \"y2\"],\n\ttext: [\"x\", \"y\"],\n\tpath: [],\n\tgroup: [],\n};\n\nconst OPACITY = \"opacity\";\nconst DRAWN = \"drawn\";\nconst SEPARATOR = \"\\u0000\";\n\nfunction walk(\n\tnodes: readonly SceneNode[],\n\tvisit: (node: SceneNode, siblings: readonly SceneNode[]) => void,\n) {\n\tfor (const node of nodes) {\n\t\tvisit(node, nodes);\n\t\tif (node.kind === \"group\") walk(node.children, visit);\n\t}\n}\n\nfunction index(nodes: readonly SceneNode[]) {\n\tconst byKey = new Map<string, SceneNode>();\n\tconst siblings = new Map<string, readonly SceneNode[]>();\n\twalk(nodes, (node, list) => {\n\t\tbyKey.set(node.key, node);\n\t\tsiblings.set(node.key, list);\n\t});\n\treturn { byKey, siblings };\n}\n\nfunction channelKey(node: string, channel: string): string {\n\treturn `${node}${SEPARATOR}${channel}`;\n}\n\nfunction elapsed(motion: Motion, now: number): number {\n\treturn Math.max(0, (now - motion.start) / 1000);\n}\n\nfunction sample(motion: Motion, now: number) {\n\treturn motion.generator.at(elapsed(motion, now));\n}\n\nfunction done(motion: Motion, now: number): boolean {\n\treturn elapsed(motion, now) >= motion.generator.duration;\n}\n\nfunction clamp(value: number): number {\n\treturn Math.min(1, Math.max(0, value));\n}\n\nexport function createMotionStore(\n\tinitial: ChartScene,\n\ttiming: ChartTiming,\n): MotionStore {\n\tlet target = initial;\n\tconst channels = new Map<string, Motion>();\n\t/** Fading out on their opacity channel. */\n\tconst guides = new Map<string, LeavingGuide>();\n\tconst shapes = new Map<\n\t\tstring,\n\t\t{ readonly plan: GeometryPlan; readonly motion: GeometryMotion }\n\t>();\n\tconst leaving = new Map<string, Leaving>();\n\n\tfunction value(node: string, channel: string, fallback: number, now: number) {\n\t\tconst motion = channels.get(channelKey(node, channel));\n\t\treturn motion ? sample(motion, now).value : fallback;\n\t}\n\n\t/** Springs one channel from `from` to `to`, at the speed it already has. */\n\tfunction spring1(\n\t\tnode: string,\n\t\tchannel: string,\n\t\tfrom: number,\n\t\tto: number,\n\t\tstart: number,\n\t\tnow: number,\n\t) {\n\t\tconst key = channelKey(node, channel);\n\t\tconst running = channels.get(key);\n\t\tconst velocity = running ? sample(running, now).velocity : 0;\n\t\tchannels.delete(key);\n\t\tif (from === to && velocity === 0) return;\n\t\tchannels.set(key, { generator: timing.motion(from, to, velocity), start });\n\t}\n\n\t/** Mixes one string channel from what is painted to what `to` asks for. */\n\tfunction mixString(\n\t\tfrom: SceneNode,\n\t\tto: SceneNode,\n\t\tchannel: StringChannel,\n\t\tstart: number,\n\t) {\n\t\tconst key = channelKey(to.key, channel);\n\t\tchannels.delete(key);\n\t\tconst was = stringOf(from, channel);\n\t\tconst goal = stringOf(to, channel);\n\t\tif (was === undefined || goal === undefined || was === goal) return;\n\t\tchannels.set(key, {\n\t\t\tgenerator: timing.motion(0, 1, 0),\n\t\t\tstart,\n\t\t\tmix: stringMixer(to, channel, was, goal),\n\t\t});\n\t}\n\n\tfunction drop(node: string) {\n\t\tfor (const key of channels.keys()) {\n\t\t\tif (key.startsWith(node + SEPARATOR)) channels.delete(key);\n\t\t}\n\t\tshapes.delete(node);\n\t\tleaving.delete(node);\n\t}\n\n\t/** `from` is the node as painted now (or the shape it enters from), `to` where it goes. */\n\tfunction move(from: SceneNode, to: SceneNode, start: number, now: number) {\n\t\tconst key = to.key;\n\t\tfor (const field of FIELDS[to.kind]) {\n\t\t\tconst was = (from as unknown as Numeric)[field] as number;\n\t\t\tspring1(\n\t\t\t\tkey,\n\t\t\t\tfield,\n\t\t\t\twas,\n\t\t\t\t(to as unknown as Numeric)[field] as number,\n\t\t\t\tstart,\n\t\t\t\tnow,\n\t\t\t);\n\t\t}\n\t\tmixString(from, to, \"fill\", start);\n\t\tmixString(from, to, \"stroke\", start);\n\t\tif (to.kind !== \"path\" || from.kind !== \"path\") return;\n\t\tconst previous = shapes.get(key);\n\t\tshapes.delete(key);\n\t\tchannels.delete(channelKey(key, \"d\"));\n\t\tconst motion = to.motion;\n\t\tconst plan =\n\t\t\tmotion && from.geometry && to.geometry\n\t\t\t\t? motion.plan(from.geometry, to.geometry)\n\t\t\t\t: undefined;\n\t\tif (!plan) {\n\t\t\t// No meaning to move: the node's own morph, else the numbers of `d`.\n\t\t\tmixString(from, to, \"d\", start);\n\t\t\treturn;\n\t\t}\n\t\tfor (const channel of previous?.plan.targets.keys() ?? []) {\n\t\t\tif (!plan.targets.has(channel)) {\n\t\t\t\tchannels.delete(channelKey(key, `g${channel}`));\n\t\t\t}\n\t\t}\n\t\tfor (const [channel, goal] of plan.targets) {\n\t\t\tconst was = plan.starts.get(channel) ?? goal;\n\t\t\tspring1(key, `g${channel}`, was, goal, start, now);\n\t\t}\n\t\tshapes.set(key, { plan, motion: motion as GeometryMotion });\n\t}\n\n\tfunction paintNode(node: SceneNode, now: number): SceneNode {\n\t\tif (node.kind === \"group\") {\n\t\t\treturn { ...node, children: paintList(node.children, node.key, now) };\n\t\t}\n\t\tconst key = node.key;\n\t\tconst moved: Numeric = {};\n\t\tfor (const field of FIELDS[node.kind]) {\n\t\t\tmoved[field] = value(\n\t\t\t\tkey,\n\t\t\t\tfield,\n\t\t\t\t(node as unknown as Numeric)[field] as number,\n\t\t\t\tnow,\n\t\t\t);\n\t\t}\n\t\tlet next = { ...node, ...moved } as Exclude<SceneNode, { kind: \"group\" }>;\n\t\tconst shape = shapes.get(key);\n\t\tif (next.kind === \"path\" && shape) {\n\t\t\tconst geometry = shape.plan.build((channel) =>\n\t\t\t\tvalue(key, `g${channel}`, shape.plan.targets.get(channel) ?? 0, now),\n\t\t\t);\n\t\t\tnext = { ...next, geometry, d: shape.motion.draw(geometry) };\n\t\t}\n\t\tfor (const channel of STRING_CHANNELS) {\n\t\t\tconst motion = channels.get(channelKey(key, channel));\n\t\t\tif (motion?.mix) {\n\t\t\t\tnext = withString(next, channel, motion.mix(sample(motion, now).value));\n\t\t\t}\n\t\t}\n\t\tconst opacity = value(key, OPACITY, 1, now);\n\t\tconst drawn = value(key, DRAWN, 1, now);\n\t\tif (opacity === 1 && drawn === 1) return next;\n\t\treturn {\n\t\t\t...next,\n\t\t\tpaint: {\n\t\t\t\t...next.paint,\n\t\t\t\topacity: (next.paint.opacity ?? 1) * clamp(opacity),\n\t\t\t\t...(drawn < 1 &&\n\t\t\t\t\tnext.kind === \"path\" &&\n\t\t\t\t\tnext.geometry && {\n\t\t\t\t\t\tdrawn: {\n\t\t\t\t\t\t\tfraction: clamp(drawn),\n\t\t\t\t\t\t\tlength: next.motion?.length?.(next.geometry) ?? 0,\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t},\n\t\t} as SceneNode;\n\t}\n\n\t/**\n\t * The painted list, with the marks still collapsing after the sibling they\n\t * followed, and the guides still fading back at their places.\n\t */\n\tfunction paintList(\n\t\tlist: readonly SceneNode[],\n\t\tparent: string | null,\n\t\tnow: number,\n\t): SceneNode[] {\n\t\tconst out: SceneNode[] = [];\n\t\tfunction place(after: string | undefined) {\n\t\t\tfor (const [key, exit] of leaving) {\n\t\t\t\tif (exit.after !== after) continue;\n\t\t\t\tout.push(paintNode(exit.node, now));\n\t\t\t\tplace(key);\n\t\t\t}\n\t\t}\n\t\tif (parent === null) place(undefined);\n\t\tfor (const node of list) {\n\t\t\tout.push(paintNode(node, now));\n\t\t\tplace(node.key);\n\t\t}\n\t\tif (guides.size === 0) return out;\n\t\treturn withLeaving(out, parent, guides.values(), (node) =>\n\t\t\tpaintNode(node, now),\n\t\t);\n\t}\n\n\tfunction prune(now: number) {\n\t\tfor (const [key, motion] of channels) {\n\t\t\tif (done(motion, now)) channels.delete(key);\n\t\t}\n\t\tconst moving = new Set(\n\t\t\t[...channels.keys()].map((key) => key.split(SEPARATOR)[0]),\n\t\t);\n\t\tfor (const byNode of [shapes, leaving, guides]) {\n\t\t\tfor (const key of byNode.keys()) {\n\t\t\t\tif (!moving.has(key)) byNode.delete(key);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tretarget(scene, now) {\n\t\t\tconst before = index(target.nodes);\n\t\t\tconst after = index(scene.nodes);\n\t\t\tfunction painted(key: string): SceneNode | undefined {\n\t\t\t\tconst node =\n\t\t\t\t\tbefore.byKey.get(key) ??\n\t\t\t\t\tleaving.get(key)?.node ??\n\t\t\t\t\tguides.get(key)?.node;\n\t\t\t\treturn node && paintNode(node, now);\n\t\t\t}\n\t\t\t// Read before the exits below drop what the guides were showing.\n\t\t\tconst fadingGuides = leavingGuides(target.nodes, (key) =>\n\t\t\t\tafter.byKey.has(key),\n\t\t\t).map(\n\t\t\t\t(guide) => [guide, value(guide.node.key, OPACITY, 1, now)] as const,\n\t\t\t);\n\t\t\tfor (const key of new Set([...before.byKey.keys(), ...leaving.keys()])) {\n\t\t\t\tif (after.byKey.has(key)) continue;\n\t\t\t\tconst base = (before.byKey.get(key) ??\n\t\t\t\t\tleaving.get(key)?.node) as SceneNode;\n\t\t\t\tconst siblings =\n\t\t\t\t\tbefore.siblings.get(key) ?? leaving.get(key)?.siblings ?? [];\n\t\t\t\tconst goal = exitTo(base, siblings, after.byKey);\n\t\t\t\tconst from = painted(key);\n\t\t\t\tif (!goal || !from) {\n\t\t\t\t\tdrop(key);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tmove(from, goal, now, now);\n\t\t\t\tconst at = siblings.findIndex((node) => node.key === key);\n\t\t\t\tleaving.set(key, {\n\t\t\t\t\tnode: goal,\n\t\t\t\t\tafter: siblings[at - 1]?.key,\n\t\t\t\t\tsiblings,\n\t\t\t\t});\n\t\t\t}\n\t\t\tfor (const [guide, shown] of fadingGuides) {\n\t\t\t\tguides.set(guide.node.key, guide);\n\t\t\t\tchannels.set(channelKey(guide.node.key, OPACITY), {\n\t\t\t\t\tgenerator: timing.motion(shown, 0, 0),\n\t\t\t\t\tstart: now,\n\t\t\t\t});\n\t\t\t}\n\t\t\tconst marks = [...after.byKey.values()].filter(\n\t\t\t\t(node) => node.role === \"mark\",\n\t\t\t);\n\t\t\tconst order = new Map(marks.map((node, at) => [node.key, at]));\n\t\t\tfor (const node of after.byKey.values()) {\n\t\t\t\tif (node.kind === \"group\") continue;\n\t\t\t\tconst start =\n\t\t\t\t\tnow +\n\t\t\t\t\tstaggerDelay(timing, order.get(node.key) ?? 0, marks.length) * 1000;\n\t\t\t\tconst old = painted(node.key);\n\t\t\t\tleaving.delete(node.key);\n\t\t\t\tconst from =\n\t\t\t\t\told ??\n\t\t\t\t\tenterFrom(node, after.siblings.get(node.key) ?? [], before.byKey);\n\t\t\t\tif (from) move(from, node, start, now);\n\t\t\t\tconst fading = channels.get(channelKey(node.key, OPACITY));\n\t\t\t\tif ((!from && node.enter !== \"none\") || fading) {\n\t\t\t\t\tconst shown = fading ? Math.max(0, sample(fading, now).value) : 0;\n\t\t\t\t\t// A guide coming back starts from rest: its fade-out speed points the wrong way.\n\t\t\t\t\tif (guides.has(node.key))\n\t\t\t\t\t\tchannels.delete(channelKey(node.key, OPACITY));\n\t\t\t\t\tspring1(node.key, OPACITY, shown, 1, start, now);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const key of after.byKey.keys()) guides.delete(key);\n\t\t\ttarget = scene;\n\t\t},\n\t\tintroduce(now) {\n\t\t\tlet any = false;\n\t\t\twalk(target.nodes, (node) => {\n\t\t\t\tif (node.kind !== \"path\" || node.enter !== \"draw\") return;\n\t\t\t\tif (node.geometry?.kind !== \"points\" || node.paint.strokeDasharray) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tspring1(node.key, DRAWN, 0, 1, now, now);\n\t\t\t\tany = true;\n\t\t\t});\n\t\t\treturn any;\n\t\t},\n\t\tsnap(scene) {\n\t\t\tfor (const motions of [channels, guides, shapes, leaving]) {\n\t\t\t\tmotions.clear();\n\t\t\t}\n\t\t\ttarget = scene;\n\t\t},\n\t\tframe(now) {\n\t\t\tprune(now);\n\t\t\tif (channels.size === 0) return target;\n\t\t\treturn { ...target, nodes: paintList(target.nodes, null, now) };\n\t\t},\n\t\tsettled(now) {\n\t\t\treturn [...channels.values()].every((motion) => done(motion, now));\n\t\t},\n\t};\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
7
  }
@@ -1 +1 @@
1
- {"version":3,"file":"points-motion.d.ts","sourceRoot":"","sources":["../../../src/core/motion/points-motion.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,cAAc,EAGd,aAAa,EAEb,MAAM,iBAAiB,CAAC;AAMzB,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAkB1D;AAoJD,uEAAuE;AACvE,eAAO,MAAM,aAAa,EAAE,cAQ3B,CAAC"}
1
+ {"version":3,"file":"points-motion.d.ts","sourceRoot":"","sources":["../../../src/core/motion/points-motion.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACX,cAAc,EAGd,aAAa,EAEb,MAAM,iBAAiB,CAAC;AAWzB,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAW1D;AA0ID,uEAAuE;AACvE,eAAO,MAAM,aAAa,EAAE,cAQ3B,CAAC"}
@@ -1,32 +1,25 @@
1
1
  import { areaPath, linePath, polygonPath } from "../paths.js";
2
2
  const POINT_FIELDS = ["x", "y", "x0", "y0"];
3
+ function lowerEdge(point) {
4
+ return { ...point, x: point.x0 ?? point.x, y: point.y0 ?? point.y };
5
+ }
3
6
  function pointsPath(geometry) {
4
7
  if (geometry.kind !== "points") return "";
5
8
  const { runs, shape, curve } = geometry;
6
9
  if (shape === "polygon") return polygonPath(runs[0] ?? []);
7
10
  return runs.map(
8
- (run) => shape === "area" ? areaPath(
9
- run,
10
- run.map((point) => ({
11
- x: point.x0 ?? point.x,
12
- y: point.y0 ?? point.y
13
- })),
14
- curve
15
- ) : linePath(run, curve)
11
+ (run) => shape === "area" ? areaPath(run, run.map(lowerEdge), curve) : linePath(run, curve)
16
12
  ).join("");
17
13
  }
18
14
  function channel(key, field) {
19
15
  return `${key}${field}`;
20
16
  }
21
- function fields(point, into) {
17
+ function fields(key, point, into) {
22
18
  for (const field of POINT_FIELDS) {
23
19
  const value = point[field];
24
- if (value !== void 0) into.set(channel(point.key, field), value);
20
+ if (value !== void 0) into.set(channel(key, field), value);
25
21
  }
26
22
  }
27
- function as(key, point) {
28
- return { ...point, key };
29
- }
30
23
  function neighbour(order, index, has) {
31
24
  for (let at = index - 1; at >= 0; at -= 1) {
32
25
  const found = has.get(order[at]);
@@ -71,8 +64,8 @@ function pointsPlan(from, to) {
71
64
  const target = next.get(key) ?? neighbour(order, at, next);
72
65
  const start = old.get(key) ?? neighbour(order, at, old) ?? target;
73
66
  if (target === void 0 || start === void 0) continue;
74
- fields(as(key, start), starts);
75
- fields(as(key, target), targets);
67
+ fields(key, start, starts);
68
+ fields(key, target, targets);
76
69
  }
77
70
  return order;
78
71
  });
@@ -117,13 +110,7 @@ function collapsed(node) {
117
110
  ...node,
118
111
  geometry: {
119
112
  ...geometry,
120
- runs: geometry.runs.map(
121
- (run) => run.map((point) => ({
122
- ...point,
123
- x: point.x0 ?? point.x,
124
- y: point.y0 ?? point.y
125
- }))
126
- )
113
+ runs: geometry.runs.map((run) => run.map(lowerEdge))
127
114
  }
128
115
  };
129
116
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/motion/points-motion.ts"],
4
- "sourcesContent": ["import { areaPath, linePath, polygonPath } from \"#/core/paths.ts\";\nimport type {\n\tGeometryMotion,\n\tGeometryPlan,\n\tGeometryPoint,\n\tSceneGeometry,\n\tScenePath,\n} from \"#/core/types.ts\";\n\ntype Points = Extract<SceneGeometry, { kind: \"points\" }>;\n\nconst POINT_FIELDS = [\"x\", \"y\", \"x0\", \"y0\"] as const;\n\n/** A line, an area or a radar outline through its points. */\nexport function pointsPath(geometry: SceneGeometry): string {\n\tif (geometry.kind !== \"points\") return \"\";\n\tconst { runs, shape, curve } = geometry;\n\tif (shape === \"polygon\") return polygonPath(runs[0] ?? []);\n\treturn runs\n\t\t.map((run) =>\n\t\t\tshape === \"area\"\n\t\t\t\t? areaPath(\n\t\t\t\t\t\trun,\n\t\t\t\t\t\trun.map((point) => ({\n\t\t\t\t\t\t\tx: point.x0 ?? point.x,\n\t\t\t\t\t\t\ty: point.y0 ?? point.y,\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurve,\n\t\t\t\t\t)\n\t\t\t\t: linePath(run, curve),\n\t\t)\n\t\t.join(\"\");\n}\n\nfunction channel(key: string, field: string): string {\n\treturn `${key}\\u0001${field}`;\n}\n\nfunction fields(point: GeometryPoint, into: Map<string, number>) {\n\tfor (const field of POINT_FIELDS) {\n\t\tconst value = point[field];\n\t\tif (value !== undefined) into.set(channel(point.key, field), value);\n\t}\n}\n\n/** `point`'s numbers under `key`'s channel names: a point entering from, or folding into, a neighbour. */\nfunction as(key: string, point: GeometryPoint): GeometryPoint {\n\treturn { ...point, key };\n}\n\n/** The nearest of `order` around `index` that `has` knows: looking back first, then ahead. */\nfunction neighbour(\n\torder: readonly string[],\n\tindex: number,\n\thas: ReadonlyMap<string, GeometryPoint>,\n): GeometryPoint | undefined {\n\tfor (let at = index - 1; at >= 0; at -= 1) {\n\t\tconst found = has.get(order[at] as string);\n\t\tif (found) return found;\n\t}\n\tfor (let at = index + 1; at < order.length; at += 1) {\n\t\tconst found = has.get(order[at] as string);\n\t\tif (found) return found;\n\t}\n\treturn undefined;\n}\n\n/** The new keys, with the ones that left kept after the old point before them. */\nfunction mergeOrder(\n\tbefore: readonly string[],\n\tafter: readonly string[],\n): string[] {\n\tconst merged = [...after];\n\tconst known = new Set(after);\n\tfor (const [index, key] of before.entries()) {\n\t\tif (known.has(key)) continue;\n\t\tconst previous = before[index - 1];\n\t\tmerged.splice(\n\t\t\tprevious === undefined ? 0 : merged.indexOf(previous) + 1,\n\t\t\t0,\n\t\t\tkey,\n\t\t);\n\t\tknown.add(key);\n\t}\n\treturn merged;\n}\n\n/**\n * Points keyed by x. A point that appears enters from its nearest old\n * neighbour (a month added on the right slides out of the last one); a\n * point that leaves folds into its nearest new neighbour. Undefined when the\n * runs don't line up: the caller falls back to tweening the path.\n */\nfunction pointsPlan(from: Points, to: Points): GeometryPlan | undefined {\n\tif (from.runs.length !== to.runs.length || from.shape !== to.shape)\n\t\treturn undefined;\n\tconst starts = new Map<string, number>();\n\tconst targets = new Map<string, number>();\n\tconst orders = to.runs.map((run, index): string[] | undefined => {\n\t\tconst old = new Map(\n\t\t\t(from.runs[index] ?? []).map((point) => [point.key, point]),\n\t\t);\n\t\tconst next = new Map(run.map((point) => [point.key, point]));\n\t\t// Two points under one key can't be told apart: tween the path instead.\n\t\tif (\n\t\t\told.size !== (from.runs[index]?.length ?? 0) ||\n\t\t\tnext.size !== run.length\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst order = mergeOrder([...old.keys()], [...next.keys()]);\n\t\tfor (const [at, key] of order.entries()) {\n\t\t\tconst target = next.get(key) ?? neighbour(order, at, next);\n\t\t\tconst start = old.get(key) ?? neighbour(order, at, old) ?? target;\n\t\t\tif (target === undefined || start === undefined) continue;\n\t\t\tfields(as(key, start), starts);\n\t\t\tfields(as(key, target), targets);\n\t\t}\n\t\treturn order;\n\t});\n\tif (orders.some((order) => order === undefined)) return undefined;\n\treturn {\n\t\tstarts,\n\t\ttargets,\n\t\tbuild(value) {\n\t\t\tconst area = to.runs.some((run) => run[0]?.y0 !== undefined);\n\t\t\treturn {\n\t\t\t\t...to,\n\t\t\t\truns: (orders as string[][]).map((order) =>\n\t\t\t\t\torder.map((key) => ({\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tx: value(channel(key, \"x\")),\n\t\t\t\t\t\ty: value(channel(key, \"y\")),\n\t\t\t\t\t\t...(area\n\t\t\t\t\t\t\t? { x0: value(channel(key, \"x0\")), y0: value(channel(key, \"y0\")) }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t})),\n\t\t\t\t),\n\t\t\t};\n\t\t},\n\t};\n}\n\n/** A line's length along its points: what Canvas dashes against to trace it in. */\nfunction pointsLength(geometry: SceneGeometry): number {\n\tif (geometry.kind !== \"points\") return 0;\n\tlet length = 0;\n\tfor (const run of geometry.runs) {\n\t\tfor (let index = 1; index < run.length; index += 1) {\n\t\t\tconst a = run[index - 1] as GeometryPoint;\n\t\t\tconst b = run[index] as GeometryPoint;\n\t\t\tlength += Math.hypot(b.x - a.x, b.y - a.y);\n\t\t}\n\t}\n\t// A curve runs a little longer than its chords; the dash must cover it all.\n\treturn length * 1.1;\n}\n\n/** An area, or the line over it, grows from its lower edge. */\nfunction collapsed(node: ScenePath): ScenePath | undefined {\n\tconst geometry = node.geometry;\n\t// A line over an area carries the area's lower edge too, so the two grow as one.\n\tif (geometry?.kind !== \"points\" || geometry.runs[0]?.[0]?.y0 === undefined) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\t...node,\n\t\tgeometry: {\n\t\t\t...geometry,\n\t\t\truns: geometry.runs.map((run) =>\n\t\t\t\trun.map((point) => ({\n\t\t\t\t\t...point,\n\t\t\t\t\tx: point.x0 ?? point.x,\n\t\t\t\t\ty: point.y0 ?? point.y,\n\t\t\t\t})),\n\t\t\t),\n\t\t},\n\t};\n}\n\n/** Lines, areas and radar outlines: points keyed by x (or by axis). */\nexport const POINTS_MOTION: GeometryMotion = {\n\tplan: (from, to) =>\n\t\tfrom.kind === \"points\" && to.kind === \"points\"\n\t\t\t? pointsPlan(from, to)\n\t\t\t: undefined,\n\tdraw: pointsPath,\n\tlength: pointsLength,\n\tenter: (node) => (node.enter === \"grow\" ? collapsed(node) : undefined),\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { areaPath, linePath, polygonPath } from \"#/core/paths.ts\";\nimport type {\n\tGeometryMotion,\n\tGeometryPlan,\n\tGeometryPoint,\n\tSceneGeometry,\n\tScenePath,\n} from \"#/core/types.ts\";\n\ntype Points = Extract<SceneGeometry, { kind: \"points\" }>;\n\nconst POINT_FIELDS = [\"x\", \"y\", \"x0\", \"y0\"] as const;\n\n/** Where `point` sits on its area's lower edge. */\nfunction lowerEdge(point: GeometryPoint): GeometryPoint {\n\treturn { ...point, x: point.x0 ?? point.x, y: point.y0 ?? point.y };\n}\n\n/** A line, an area or a radar outline through its points. */\nexport function pointsPath(geometry: SceneGeometry): string {\n\tif (geometry.kind !== \"points\") return \"\";\n\tconst { runs, shape, curve } = geometry;\n\tif (shape === \"polygon\") return polygonPath(runs[0] ?? []);\n\treturn runs\n\t\t.map((run) =>\n\t\t\tshape === \"area\"\n\t\t\t\t? areaPath(run, run.map(lowerEdge), curve)\n\t\t\t\t: linePath(run, curve),\n\t\t)\n\t\t.join(\"\");\n}\n\nfunction channel(key: string, field: string): string {\n\treturn `${key}\\u0001${field}`;\n}\n\n/** `point`'s numbers under `key`'s channel names: `point` may be a neighbour that `key` enters from or folds into. */\nfunction fields(key: string, point: GeometryPoint, into: Map<string, number>) {\n\tfor (const field of POINT_FIELDS) {\n\t\tconst value = point[field];\n\t\tif (value !== undefined) into.set(channel(key, field), value);\n\t}\n}\n\n/** The nearest of `order` around `index` that `has` knows: looking back first, then ahead. */\nfunction neighbour(\n\torder: readonly string[],\n\tindex: number,\n\thas: ReadonlyMap<string, GeometryPoint>,\n): GeometryPoint | undefined {\n\tfor (let at = index - 1; at >= 0; at -= 1) {\n\t\tconst found = has.get(order[at] as string);\n\t\tif (found) return found;\n\t}\n\tfor (let at = index + 1; at < order.length; at += 1) {\n\t\tconst found = has.get(order[at] as string);\n\t\tif (found) return found;\n\t}\n\treturn undefined;\n}\n\n/** The new keys, with the ones that left kept after the old point before them. */\nfunction mergeOrder(\n\tbefore: readonly string[],\n\tafter: readonly string[],\n): string[] {\n\tconst merged = [...after];\n\tconst known = new Set(after);\n\tfor (const [index, key] of before.entries()) {\n\t\tif (known.has(key)) continue;\n\t\tconst previous = before[index - 1];\n\t\tmerged.splice(\n\t\t\tprevious === undefined ? 0 : merged.indexOf(previous) + 1,\n\t\t\t0,\n\t\t\tkey,\n\t\t);\n\t\tknown.add(key);\n\t}\n\treturn merged;\n}\n\n/**\n * Points keyed by x. A point that appears enters from its nearest old\n * neighbour (a month added on the right slides out of the last one); a\n * point that leaves folds into its nearest new neighbour. Undefined when the\n * runs don't line up: the caller falls back to tweening the path.\n */\nfunction pointsPlan(from: Points, to: Points): GeometryPlan | undefined {\n\tif (from.runs.length !== to.runs.length || from.shape !== to.shape)\n\t\treturn undefined;\n\tconst starts = new Map<string, number>();\n\tconst targets = new Map<string, number>();\n\tconst orders = to.runs.map((run, index): string[] | undefined => {\n\t\tconst old = new Map(\n\t\t\t(from.runs[index] ?? []).map((point) => [point.key, point]),\n\t\t);\n\t\tconst next = new Map(run.map((point) => [point.key, point]));\n\t\t// Two points under one key can't be told apart: tween the path instead.\n\t\tif (\n\t\t\told.size !== (from.runs[index]?.length ?? 0) ||\n\t\t\tnext.size !== run.length\n\t\t) {\n\t\t\treturn undefined;\n\t\t}\n\t\tconst order = mergeOrder([...old.keys()], [...next.keys()]);\n\t\tfor (const [at, key] of order.entries()) {\n\t\t\tconst target = next.get(key) ?? neighbour(order, at, next);\n\t\t\tconst start = old.get(key) ?? neighbour(order, at, old) ?? target;\n\t\t\tif (target === undefined || start === undefined) continue;\n\t\t\tfields(key, start, starts);\n\t\t\tfields(key, target, targets);\n\t\t}\n\t\treturn order;\n\t});\n\tif (orders.some((order) => order === undefined)) return undefined;\n\treturn {\n\t\tstarts,\n\t\ttargets,\n\t\tbuild(value) {\n\t\t\tconst area = to.runs.some((run) => run[0]?.y0 !== undefined);\n\t\t\treturn {\n\t\t\t\t...to,\n\t\t\t\truns: (orders as string[][]).map((order) =>\n\t\t\t\t\torder.map((key) => ({\n\t\t\t\t\t\tkey,\n\t\t\t\t\t\tx: value(channel(key, \"x\")),\n\t\t\t\t\t\ty: value(channel(key, \"y\")),\n\t\t\t\t\t\t...(area\n\t\t\t\t\t\t\t? { x0: value(channel(key, \"x0\")), y0: value(channel(key, \"y0\")) }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t})),\n\t\t\t\t),\n\t\t\t};\n\t\t},\n\t};\n}\n\n/** A line's length along its points: what Canvas dashes against to trace it in. */\nfunction pointsLength(geometry: SceneGeometry): number {\n\tif (geometry.kind !== \"points\") return 0;\n\tlet length = 0;\n\tfor (const run of geometry.runs) {\n\t\tfor (let index = 1; index < run.length; index += 1) {\n\t\t\tconst a = run[index - 1] as GeometryPoint;\n\t\t\tconst b = run[index] as GeometryPoint;\n\t\t\tlength += Math.hypot(b.x - a.x, b.y - a.y);\n\t\t}\n\t}\n\t// A curve runs a little longer than its chords; the dash must cover it all.\n\treturn length * 1.1;\n}\n\n/** An area, or the line over it, grows from its lower edge. */\nfunction collapsed(node: ScenePath): ScenePath | undefined {\n\tconst geometry = node.geometry;\n\t// A line over an area carries the area's lower edge too, so the two grow as one.\n\tif (geometry?.kind !== \"points\" || geometry.runs[0]?.[0]?.y0 === undefined) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\t...node,\n\t\tgeometry: {\n\t\t\t...geometry,\n\t\t\truns: geometry.runs.map((run) => run.map(lowerEdge)),\n\t\t},\n\t};\n}\n\n/** Lines, areas and radar outlines: points keyed by x (or by axis). */\nexport const POINTS_MOTION: GeometryMotion = {\n\tplan: (from, to) =>\n\t\tfrom.kind === \"points\" && to.kind === \"points\"\n\t\t\t? pointsPlan(from, to)\n\t\t\t: undefined,\n\tdraw: pointsPath,\n\tlength: pointsLength,\n\tenter: (node) => (node.enter === \"grow\" ? collapsed(node) : undefined),\n};\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -13,6 +13,8 @@ export declare function colourBetween(from: string, to: string, progress: number
13
13
  export declare function stringOf(node: SceneNode | undefined, channel: StringChannel): string | undefined;
14
14
  /** The mix from what was painted to what `node` asks for. */
15
15
  export declare function stringMixer(node: SceneNode, channel: StringChannel, from: string, to: string): (progress: number) => string;
16
- /** `node` with each string channel set to its moving value. */
17
- export declare function withStrings(node: SceneNode, values: Partial<Record<StringChannel, string>>): SceneNode;
16
+ /** `node` with one string channel set to its moving value. */
17
+ export declare function withString<Node extends Exclude<SceneNode, {
18
+ kind: "group";
19
+ }>>(node: Node, channel: StringChannel, value: string): Node;
18
20
  //# sourceMappingURL=strings.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../../src/core/motion/strings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,6EAA6E;AAC7E,eAAO,MAAM,eAAe,YAAI,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAEhE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAK7D;;;;;GAKG;AACH,wBAAgB,aAAa,CAC5B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GACd,MAAM,CAKR;AAED,iDAAiD;AACjD,wBAAgB,QAAQ,CACvB,IAAI,EAAE,SAAS,GAAG,SAAS,EAC3B,OAAO,EAAE,aAAa,GACpB,MAAM,GAAG,SAAS,CAOpB;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAC1B,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACR,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAM9B;AAED,+DAA+D;AAC/D,wBAAgB,WAAW,CAC1B,IAAI,EAAE,SAAS,EACf,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC,GAC5C,SAAS,CAQX"}
1
+ {"version":3,"file":"strings.d.ts","sourceRoot":"","sources":["../../../src/core/motion/strings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,6EAA6E;AAC7E,eAAO,MAAM,eAAe,YAAI,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAU,CAAC;AAEhE,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAK7D;;;;;GAKG;AACH,wBAAgB,aAAa,CAC5B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,GACd,MAAM,CAKR;AAED,iDAAiD;AACjD,wBAAgB,QAAQ,CACvB,IAAI,EAAE,SAAS,GAAG,SAAS,EAC3B,OAAO,EAAE,aAAa,GACpB,MAAM,GAAG,SAAS,CAOpB;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAC1B,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,aAAa,EACtB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACR,CAAC,QAAQ,EAAE,MAAM,KAAK,MAAM,CAM9B;AAED,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,IAAI,SAAS,OAAO,CAAC,SAAS,EAAE;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC,EAC5E,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,aAAa,EACtB,KAAK,EAAE,MAAM,GACX,IAAI,CAIN"}