@voila.dev/chart 6.0.120 → 6.0.122

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.
@@ -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;AAwED,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,WAAW,GACjB,WAAW,CAkSb"}
1
+ {"version":3,"file":"motion-store.d.ts","sourceRoot":"","sources":["../../../src/core/motion/motion-store.ts"],"names":[],"mappings":"AASA,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;AAiGD,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,UAAU,EACnB,MAAM,EAAE,WAAW,GACjB,WAAW,CA4Qb"}
@@ -1,8 +1,4 @@
1
1
  import { enterFrom, exitTo } from "./enter-exit.js";
2
- import {
3
- leavingGuides,
4
- withLeaving
5
- } from "./leaving.js";
6
2
  import {
7
3
  STRING_CHANNELS,
8
4
  stringMixer,
@@ -21,20 +17,28 @@ const FIELDS = {
21
17
  const OPACITY = "opacity";
22
18
  const DRAWN = "drawn";
23
19
  const SEPARATOR = "\0";
24
- function walk(nodes, visit) {
20
+ const GUIDE_ROLES = /* @__PURE__ */ new Set(["grid", "axis"]);
21
+ function first(parent) {
22
+ return parent === null ? void 0 : parent + SEPARATOR;
23
+ }
24
+ function walk(nodes, visit, parent = null) {
25
+ let after = first(parent);
25
26
  for (const node of nodes) {
26
- visit(node, nodes);
27
- if (node.kind === "group") walk(node.children, visit);
27
+ visit(node, nodes, after);
28
+ if (node.kind === "group") walk(node.children, visit, node.key);
29
+ after = node.key;
28
30
  }
29
31
  }
30
32
  function index(nodes) {
31
33
  const byKey = /* @__PURE__ */ new Map();
32
34
  const siblings = /* @__PURE__ */ new Map();
33
- walk(nodes, (node, list) => {
35
+ const afters = /* @__PURE__ */ new Map();
36
+ walk(nodes, (node, list, after) => {
34
37
  byKey.set(node.key, node);
35
38
  siblings.set(node.key, list);
39
+ afters.set(node.key, after);
36
40
  });
37
- return { byKey, siblings };
41
+ return { byKey, siblings, afters };
38
42
  }
39
43
  function channelKey(node, channel) {
40
44
  return `${node}${SEPARATOR}${channel}`;
@@ -54,7 +58,6 @@ function clamp(value) {
54
58
  function createMotionStore(initial, timing) {
55
59
  let target = initial;
56
60
  const channels = /* @__PURE__ */ new Map();
57
- const guides = /* @__PURE__ */ new Map();
58
61
  const shapes = /* @__PURE__ */ new Map();
59
62
  const leaving = /* @__PURE__ */ new Map();
60
63
  function value(node, channel, fallback, now) {
@@ -178,18 +181,12 @@ function createMotionStore(initial, timing) {
178
181
  place(key);
179
182
  }
180
183
  }
181
- if (parent === null) place(void 0);
184
+ place(first(parent));
182
185
  for (const node of list) {
183
186
  out.push(paintNode(node, now));
184
187
  place(node.key);
185
188
  }
186
- if (guides.size === 0) return out;
187
- return withLeaving(
188
- out,
189
- parent,
190
- guides.values(),
191
- (node) => paintNode(node, now)
192
- );
189
+ return out;
193
190
  }
194
191
  function prune(now) {
195
192
  for (const [key, motion] of channels) {
@@ -198,7 +195,7 @@ function createMotionStore(initial, timing) {
198
195
  const moving = new Set(
199
196
  [...channels.keys()].map((key) => key.split(SEPARATOR)[0])
200
197
  );
201
- for (const byNode of [shapes, leaving, guides]) {
198
+ for (const byNode of [shapes, leaving]) {
202
199
  for (const key of byNode.keys()) {
203
200
  if (!moving.has(key)) byNode.delete(key);
204
201
  }
@@ -209,40 +206,31 @@ function createMotionStore(initial, timing) {
209
206
  const before = index(target.nodes);
210
207
  const after = index(scene.nodes);
211
208
  function painted(key) {
212
- const node = before.byKey.get(key) ?? leaving.get(key)?.node ?? guides.get(key)?.node;
209
+ const node = before.byKey.get(key) ?? leaving.get(key)?.node;
213
210
  return node && paintNode(node, now);
214
211
  }
215
- const fadingGuides = leavingGuides(
216
- target.nodes,
217
- (key) => after.byKey.has(key)
218
- ).map(
219
- (guide) => [guide, value(guide.node.key, OPACITY, 1, now)]
220
- );
221
212
  for (const key of /* @__PURE__ */ new Set([...before.byKey.keys(), ...leaving.keys()])) {
222
213
  if (after.byKey.has(key)) continue;
223
214
  const base = before.byKey.get(key) ?? leaving.get(key)?.node;
224
- const siblings = before.siblings.get(key) ?? leaving.get(key)?.siblings ?? [];
225
- const goal = exitTo(base, siblings, after.byKey);
215
+ const exit = leaving.get(key);
216
+ const siblings = exit?.siblings ?? before.siblings.get(key) ?? [];
217
+ const guide = base.kind !== "group" && GUIDE_ROLES.has(base.role ?? "");
218
+ const goal = exitTo(base, siblings, after.byKey) ?? (guide && base);
226
219
  const from = painted(key);
227
220
  if (!goal || !from) {
228
221
  drop(key);
229
222
  continue;
230
223
  }
231
224
  move(from, goal, now, now);
232
- const at = siblings.findIndex((node) => node.key === key);
225
+ if (guide) {
226
+ spring1(key, OPACITY, value(key, OPACITY, 1, now), 0, now, now);
227
+ }
233
228
  leaving.set(key, {
234
229
  node: goal,
235
- after: siblings[at - 1]?.key,
230
+ after: exit ? exit.after : before.afters.get(key),
236
231
  siblings
237
232
  });
238
233
  }
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
- }
246
234
  const marks = [...after.byKey.values()].filter(
247
235
  (node) => node.role === "mark"
248
236
  );
@@ -251,18 +239,16 @@ function createMotionStore(initial, timing) {
251
239
  if (node.kind === "group") continue;
252
240
  const start = now + staggerDelay(timing, order.get(node.key) ?? 0, marks.length) * 1e3;
253
241
  const old = painted(node.key);
254
- leaving.delete(node.key);
242
+ const returning = leaving.delete(node.key);
255
243
  const from = old ?? enterFrom(node, after.siblings.get(node.key) ?? [], before.byKey);
256
244
  if (from) move(from, node, start, now);
257
245
  const fading = channels.get(channelKey(node.key, OPACITY));
258
246
  if (!from && node.enter !== "none" || fading) {
259
247
  const shown = fading ? Math.max(0, sample(fading, now).value) : 0;
260
- if (guides.has(node.key))
261
- channels.delete(channelKey(node.key, OPACITY));
248
+ if (returning) channels.delete(channelKey(node.key, OPACITY));
262
249
  spring1(node.key, OPACITY, shown, 1, start, now);
263
250
  }
264
251
  }
265
- for (const key of after.byKey.keys()) guides.delete(key);
266
252
  target = scene;
267
253
  },
268
254
  introduce(now) {
@@ -278,7 +264,7 @@ function createMotionStore(initial, timing) {
278
264
  return any;
279
265
  },
280
266
  snap(scene) {
281
- for (const motions of [channels, guides, shapes, leaving]) {
267
+ for (const motions of [channels, shapes, leaving]) {
282
268
  motions.clear();
283
269
  }
284
270
  target = scene;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/core/motion/motion-store.ts"],
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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",
4
+ "sourcesContent": ["import type { Generator } from \"@voila.dev/motion\";\nimport { enterFrom, exitTo } from \"#/core/motion/enter-exit.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/**\n * A node on its way out, drawn after its old sibling until its springs\n * settle: a data mark collapsing, or a guide fading where it stood.\n */\ninterface Leaving {\n\treadonly node: SceneNode;\n\t/** Its old previous sibling's key, else its group's `first` marker. */\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/**\n * A tick label or a grid line vanishing at once reads as a glitch, so they\n * fade out. Data marks never linger: a bar the data no longer has would show\n * a value that isn't true.\n */\nconst GUIDE_ROLES = new Set([\"grid\", \"axis\"]);\n\n/** Where the leaving first children of the group `parent` are drawn. */\nfunction first(parent: string | null): string | undefined {\n\treturn parent === null ? undefined : parent + SEPARATOR;\n}\n\n/** Visits every node with its list and the key it is drawn after when it leaves (see `Leaving`). */\nfunction walk(\n\tnodes: readonly SceneNode[],\n\tvisit: (\n\t\tnode: SceneNode,\n\t\tsiblings: readonly SceneNode[],\n\t\tafter: string | undefined,\n\t) => void,\n\tparent: string | null = null,\n) {\n\tlet after = first(parent);\n\tfor (const node of nodes) {\n\t\tvisit(node, nodes, after);\n\t\tif (node.kind === \"group\") walk(node.children, visit, node.key);\n\t\tafter = node.key;\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\tconst afters = new Map<string, string | undefined>();\n\twalk(nodes, (node, list, after) => {\n\t\tbyKey.set(node.key, node);\n\t\tsiblings.set(node.key, list);\n\t\tafters.set(node.key, after);\n\t});\n\treturn { byKey, siblings, afters };\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\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/** The painted list, with the nodes still leaving after the sibling they followed. */\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\tplace(first(parent));\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\treturn out;\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]) {\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 = before.byKey.get(key) ?? leaving.get(key)?.node;\n\t\t\t\treturn node && paintNode(node, now);\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 exit = leaving.get(key);\n\t\t\t\tconst siblings = exit?.siblings ?? before.siblings.get(key) ?? [];\n\t\t\t\t// A whole guide group leaving goes at once, its lines with it.\n\t\t\t\tconst guide = base.kind !== \"group\" && GUIDE_ROLES.has(base.role ?? \"\");\n\t\t\t\tconst goal = exitTo(base, siblings, after.byKey) ?? (guide && base);\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\tif (guide) {\n\t\t\t\t\tspring1(key, OPACITY, value(key, OPACITY, 1, now), 0, now, now);\n\t\t\t\t}\n\t\t\t\tleaving.set(key, {\n\t\t\t\t\tnode: goal,\n\t\t\t\t\tafter: exit ? exit.after : before.afters.get(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\tconst returning = leaving.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 (returning) channels.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\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, 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
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voila.dev/chart",
3
- "version": "6.0.120",
3
+ "version": "6.0.122",
4
4
  "type": "module",
5
5
  "description": "Charts as data: a definition of marks, channels and scales, drawn to SVG or Canvas, keyboard and screen-reader accessible. The core has no dependencies; the heavy layouts (geo, hierarchy, sankey…) sit behind their own subpaths.",
6
6
  "license": "MIT",
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "sideEffects": false,
66
66
  "dependencies": {
67
- "@voila.dev/motion": "6.0.120",
67
+ "@voila.dev/motion": "6.0.122",
68
68
  "d3-contour": "4.0.2",
69
69
  "d3-delaunay": "6.0.4",
70
70
  "d3-force": "3.0.0",
@@ -1,10 +1,5 @@
1
1
  import type { Generator } from "@voila.dev/motion";
2
2
  import { enterFrom, exitTo } from "#/core/motion/enter-exit.ts";
3
- import {
4
- type LeavingGuide,
5
- leavingGuides,
6
- withLeaving,
7
- } from "#/core/motion/leaving.ts";
8
3
  import {
9
4
  STRING_CHANNELS,
10
5
  type StringChannel,
@@ -47,9 +42,13 @@ interface Motion {
47
42
  readonly mix?: (progress: number) => string;
48
43
  }
49
44
 
50
- /** A data mark collapsing on its way out: drawn after its old sibling until its springs settle. */
45
+ /**
46
+ * A node on its way out, drawn after its old sibling until its springs
47
+ * settle: a data mark collapsing, or a guide fading where it stood.
48
+ */
51
49
  interface Leaving {
52
50
  readonly node: SceneNode;
51
+ /** Its old previous sibling's key, else its group's `first` marker. */
53
52
  readonly after: string | undefined;
54
53
  readonly siblings: readonly SceneNode[];
55
54
  }
@@ -68,25 +67,46 @@ const FIELDS: Record<SceneNode["kind"], readonly string[]> = {
68
67
  const OPACITY = "opacity";
69
68
  const DRAWN = "drawn";
70
69
  const SEPARATOR = "\u0000";
70
+ /**
71
+ * A tick label or a grid line vanishing at once reads as a glitch, so they
72
+ * fade out. Data marks never linger: a bar the data no longer has would show
73
+ * a value that isn't true.
74
+ */
75
+ const GUIDE_ROLES = new Set(["grid", "axis"]);
76
+
77
+ /** Where the leaving first children of the group `parent` are drawn. */
78
+ function first(parent: string | null): string | undefined {
79
+ return parent === null ? undefined : parent + SEPARATOR;
80
+ }
71
81
 
82
+ /** Visits every node with its list and the key it is drawn after when it leaves (see `Leaving`). */
72
83
  function walk(
73
84
  nodes: readonly SceneNode[],
74
- visit: (node: SceneNode, siblings: readonly SceneNode[]) => void,
85
+ visit: (
86
+ node: SceneNode,
87
+ siblings: readonly SceneNode[],
88
+ after: string | undefined,
89
+ ) => void,
90
+ parent: string | null = null,
75
91
  ) {
92
+ let after = first(parent);
76
93
  for (const node of nodes) {
77
- visit(node, nodes);
78
- if (node.kind === "group") walk(node.children, visit);
94
+ visit(node, nodes, after);
95
+ if (node.kind === "group") walk(node.children, visit, node.key);
96
+ after = node.key;
79
97
  }
80
98
  }
81
99
 
82
100
  function index(nodes: readonly SceneNode[]) {
83
101
  const byKey = new Map<string, SceneNode>();
84
102
  const siblings = new Map<string, readonly SceneNode[]>();
85
- walk(nodes, (node, list) => {
103
+ const afters = new Map<string, string | undefined>();
104
+ walk(nodes, (node, list, after) => {
86
105
  byKey.set(node.key, node);
87
106
  siblings.set(node.key, list);
107
+ afters.set(node.key, after);
88
108
  });
89
- return { byKey, siblings };
109
+ return { byKey, siblings, afters };
90
110
  }
91
111
 
92
112
  function channelKey(node: string, channel: string): string {
@@ -115,8 +135,6 @@ export function createMotionStore(
115
135
  ): MotionStore {
116
136
  let target = initial;
117
137
  const channels = new Map<string, Motion>();
118
- /** Fading out on their opacity channel. */
119
- const guides = new Map<string, LeavingGuide>();
120
138
  const shapes = new Map<
121
139
  string,
122
140
  { readonly plan: GeometryPlan; readonly motion: GeometryMotion }
@@ -262,10 +280,7 @@ export function createMotionStore(
262
280
  } as SceneNode;
263
281
  }
264
282
 
265
- /**
266
- * The painted list, with the marks still collapsing after the sibling they
267
- * followed, and the guides still fading back at their places.
268
- */
283
+ /** The painted list, with the nodes still leaving after the sibling they followed. */
269
284
  function paintList(
270
285
  list: readonly SceneNode[],
271
286
  parent: string | null,
@@ -279,15 +294,12 @@ export function createMotionStore(
279
294
  place(key);
280
295
  }
281
296
  }
282
- if (parent === null) place(undefined);
297
+ place(first(parent));
283
298
  for (const node of list) {
284
299
  out.push(paintNode(node, now));
285
300
  place(node.key);
286
301
  }
287
- if (guides.size === 0) return out;
288
- return withLeaving(out, parent, guides.values(), (node) =>
289
- paintNode(node, now),
290
- );
302
+ return out;
291
303
  }
292
304
 
293
305
  function prune(now: number) {
@@ -297,7 +309,7 @@ export function createMotionStore(
297
309
  const moving = new Set(
298
310
  [...channels.keys()].map((key) => key.split(SEPARATOR)[0]),
299
311
  );
300
- for (const byNode of [shapes, leaving, guides]) {
312
+ for (const byNode of [shapes, leaving]) {
301
313
  for (const key of byNode.keys()) {
302
314
  if (!moving.has(key)) byNode.delete(key);
303
315
  }
@@ -309,45 +321,33 @@ export function createMotionStore(
309
321
  const before = index(target.nodes);
310
322
  const after = index(scene.nodes);
311
323
  function painted(key: string): SceneNode | undefined {
312
- const node =
313
- before.byKey.get(key) ??
314
- leaving.get(key)?.node ??
315
- guides.get(key)?.node;
324
+ const node = before.byKey.get(key) ?? leaving.get(key)?.node;
316
325
  return node && paintNode(node, now);
317
326
  }
318
- // Read before the exits below drop what the guides were showing.
319
- const fadingGuides = leavingGuides(target.nodes, (key) =>
320
- after.byKey.has(key),
321
- ).map(
322
- (guide) => [guide, value(guide.node.key, OPACITY, 1, now)] as const,
323
- );
324
327
  for (const key of new Set([...before.byKey.keys(), ...leaving.keys()])) {
325
328
  if (after.byKey.has(key)) continue;
326
329
  const base = (before.byKey.get(key) ??
327
330
  leaving.get(key)?.node) as SceneNode;
328
- const siblings =
329
- before.siblings.get(key) ?? leaving.get(key)?.siblings ?? [];
330
- const goal = exitTo(base, siblings, after.byKey);
331
+ const exit = leaving.get(key);
332
+ const siblings = exit?.siblings ?? before.siblings.get(key) ?? [];
333
+ // A whole guide group leaving goes at once, its lines with it.
334
+ const guide = base.kind !== "group" && GUIDE_ROLES.has(base.role ?? "");
335
+ const goal = exitTo(base, siblings, after.byKey) ?? (guide && base);
331
336
  const from = painted(key);
332
337
  if (!goal || !from) {
333
338
  drop(key);
334
339
  continue;
335
340
  }
336
341
  move(from, goal, now, now);
337
- const at = siblings.findIndex((node) => node.key === key);
342
+ if (guide) {
343
+ spring1(key, OPACITY, value(key, OPACITY, 1, now), 0, now, now);
344
+ }
338
345
  leaving.set(key, {
339
346
  node: goal,
340
- after: siblings[at - 1]?.key,
347
+ after: exit ? exit.after : before.afters.get(key),
341
348
  siblings,
342
349
  });
343
350
  }
344
- for (const [guide, shown] of fadingGuides) {
345
- guides.set(guide.node.key, guide);
346
- channels.set(channelKey(guide.node.key, OPACITY), {
347
- generator: timing.motion(shown, 0, 0),
348
- start: now,
349
- });
350
- }
351
351
  const marks = [...after.byKey.values()].filter(
352
352
  (node) => node.role === "mark",
353
353
  );
@@ -358,7 +358,7 @@ export function createMotionStore(
358
358
  now +
359
359
  staggerDelay(timing, order.get(node.key) ?? 0, marks.length) * 1000;
360
360
  const old = painted(node.key);
361
- leaving.delete(node.key);
361
+ const returning = leaving.delete(node.key);
362
362
  const from =
363
363
  old ??
364
364
  enterFrom(node, after.siblings.get(node.key) ?? [], before.byKey);
@@ -367,12 +367,10 @@ export function createMotionStore(
367
367
  if ((!from && node.enter !== "none") || fading) {
368
368
  const shown = fading ? Math.max(0, sample(fading, now).value) : 0;
369
369
  // A guide coming back starts from rest: its fade-out speed points the wrong way.
370
- if (guides.has(node.key))
371
- channels.delete(channelKey(node.key, OPACITY));
370
+ if (returning) channels.delete(channelKey(node.key, OPACITY));
372
371
  spring1(node.key, OPACITY, shown, 1, start, now);
373
372
  }
374
373
  }
375
- for (const key of after.byKey.keys()) guides.delete(key);
376
374
  target = scene;
377
375
  },
378
376
  introduce(now) {
@@ -388,7 +386,7 @@ export function createMotionStore(
388
386
  return any;
389
387
  },
390
388
  snap(scene) {
391
- for (const motions of [channels, guides, shapes, leaving]) {
389
+ for (const motions of [channels, shapes, leaving]) {
392
390
  motions.clear();
393
391
  }
394
392
  target = scene;
@@ -1,12 +0,0 @@
1
- import type { SceneNode } from "../types.js";
2
- export interface LeavingGuide {
3
- readonly node: SceneNode;
4
- /** The group it sat in, or null at the root. */
5
- readonly parent: string | null;
6
- readonly index: number;
7
- }
8
- /** The guides of `nodes` whose keys `stays` no longer has. */
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, as `paint` draws them. */
11
- export declare function withLeaving(children: readonly SceneNode[], parent: string | null, leaving: Iterable<LeavingGuide>, paint: (node: SceneNode) => SceneNode): SceneNode[];
12
- //# sourceMappingURL=leaving.d.ts.map
@@ -1 +0,0 @@
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"}
@@ -1,26 +0,0 @@
1
- const GUIDE_ROLES = /* @__PURE__ */ new Set(["grid", "axis"]);
2
- function leavingGuides(nodes, stays, parent = null, into = []) {
3
- for (const [index, node] of nodes.entries()) {
4
- if (node.kind === "group") {
5
- leavingGuides(node.children, stays, node.key, into);
6
- continue;
7
- }
8
- if (!stays(node.key) && GUIDE_ROLES.has(node.role ?? "")) {
9
- into.push({ node, parent, index });
10
- }
11
- }
12
- return into;
13
- }
14
- function withLeaving(children, parent, leaving, paint) {
15
- const placed = [...children];
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));
19
- }
20
- return placed;
21
- }
22
- export {
23
- leavingGuides,
24
- withLeaving
25
- };
26
- //# sourceMappingURL=leaving.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 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, 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
- "names": []
7
- }
@@ -1,49 +0,0 @@
1
- import type { SceneNode } from "#/core/types.ts";
2
-
3
- /**
4
- * Guides that left the scene still fade out where they stood: a tick label
5
- * or a grid line vanishing at once reads as a glitch. Data marks never
6
- * linger — a bar the data no longer has would show a value that isn't true.
7
- */
8
- const GUIDE_ROLES = new Set(["grid", "axis"]);
9
-
10
- export interface LeavingGuide {
11
- readonly node: SceneNode;
12
- /** The group it sat in, or null at the root. */
13
- readonly parent: string | null;
14
- readonly index: number;
15
- }
16
-
17
- /** The guides of `nodes` whose keys `stays` no longer has. */
18
- export function leavingGuides(
19
- nodes: readonly SceneNode[],
20
- stays: (key: string) => boolean,
21
- parent: string | null = null,
22
- into: LeavingGuide[] = [],
23
- ): LeavingGuide[] {
24
- for (const [index, node] of nodes.entries()) {
25
- if (node.kind === "group") {
26
- leavingGuides(node.children, stays, node.key, into);
27
- continue;
28
- }
29
- if (!stays(node.key) && GUIDE_ROLES.has(node.role ?? "")) {
30
- into.push({ node, parent, index });
31
- }
32
- }
33
- return into;
34
- }
35
-
36
- /** `children` with the leaving guides of `parent` back at their places, as `paint` draws them. */
37
- export function withLeaving(
38
- children: readonly SceneNode[],
39
- parent: string | null,
40
- leaving: Iterable<LeavingGuide>,
41
- paint: (node: SceneNode) => SceneNode,
42
- ): SceneNode[] {
43
- const placed = [...children];
44
- for (const guide of leaving) {
45
- if (guide.parent !== parent) continue;
46
- placed.splice(Math.min(guide.index, placed.length), 0, paint(guide.node));
47
- }
48
- return placed;
49
- }