@weasel-js/d3 1.0.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +30 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,24 @@ import { asNodeId, RECT_POSE_DESCRIPTOR } from '@weasel-js/core';
|
|
|
3
3
|
// src/bind.ts
|
|
4
4
|
|
|
5
5
|
// src/transition.ts
|
|
6
|
+
var LIVE_CUSTOM_KEYS = /* @__PURE__ */ new Map();
|
|
7
|
+
function trackCustomKey(ns, key) {
|
|
8
|
+
const set = LIVE_CUSTOM_KEYS.get(ns) ?? /* @__PURE__ */ new Set();
|
|
9
|
+
set.add(key);
|
|
10
|
+
LIVE_CUSTOM_KEYS.set(ns, set);
|
|
11
|
+
}
|
|
12
|
+
function untrackCustomKey(ns, key) {
|
|
13
|
+
const set = LIVE_CUSTOM_KEYS.get(ns);
|
|
14
|
+
if (!set) return;
|
|
15
|
+
set.delete(key);
|
|
16
|
+
if (set.size === 0) LIVE_CUSTOM_KEYS.delete(ns);
|
|
17
|
+
}
|
|
18
|
+
function takeCustomKeys(ns) {
|
|
19
|
+
const set = LIVE_CUSTOM_KEYS.get(ns);
|
|
20
|
+
if (!set) return [];
|
|
21
|
+
LIVE_CUSTOM_KEYS.delete(ns);
|
|
22
|
+
return [...set];
|
|
23
|
+
}
|
|
6
24
|
function createTransition(ctx) {
|
|
7
25
|
let duration = 250;
|
|
8
26
|
let easing;
|
|
@@ -15,6 +33,7 @@ function createTransition(ctx) {
|
|
|
15
33
|
let pendingCount = 0;
|
|
16
34
|
let cancelled = false;
|
|
17
35
|
let endResolved = false;
|
|
36
|
+
const spawnedCustom = [];
|
|
18
37
|
let resolveEnd = null;
|
|
19
38
|
const endPromise = new Promise((resolve) => {
|
|
20
39
|
resolveEnd = resolve;
|
|
@@ -88,15 +107,19 @@ function createTransition(ctx) {
|
|
|
88
107
|
for (const ct of customTweens) {
|
|
89
108
|
const fromVal = ct.from(d, i);
|
|
90
109
|
const toVal = ct.to(d, i);
|
|
110
|
+
const ns = `d3-transition:${name}:${id}`;
|
|
111
|
+
const customKey = `${ns}:${ct.name}`;
|
|
91
112
|
const spawnCustom = () => {
|
|
92
113
|
if (cancelled) return;
|
|
93
114
|
anySpawned = true;
|
|
115
|
+
trackCustomKey(ns, customKey);
|
|
116
|
+
spawnedCustom.push([ns, customKey]);
|
|
94
117
|
const handle = ctx.animator.tween({
|
|
95
118
|
from: fromVal,
|
|
96
119
|
to: toVal,
|
|
97
120
|
ms: duration,
|
|
98
121
|
easing,
|
|
99
|
-
cancelKey:
|
|
122
|
+
cancelKey: customKey,
|
|
100
123
|
interpolator: ct.interpolate,
|
|
101
124
|
interpolate: ct.interpolate ? void 0 : ((a, b, t2) => {
|
|
102
125
|
if (typeof a === "number" && typeof b === "number") {
|
|
@@ -108,6 +131,7 @@ function createTransition(ctx) {
|
|
|
108
131
|
}),
|
|
109
132
|
onTick: (value) => ct.apply(d, id, value),
|
|
110
133
|
onDone: () => {
|
|
134
|
+
untrackCustomKey(ns, customKey);
|
|
111
135
|
pendingCount--;
|
|
112
136
|
settleIfDone();
|
|
113
137
|
}
|
|
@@ -168,6 +192,8 @@ function createTransition(ctx) {
|
|
|
168
192
|
pendingDelays.clear();
|
|
169
193
|
for (const h of handles) h.cancel();
|
|
170
194
|
handles.length = 0;
|
|
195
|
+
for (const [ns, key] of spawnedCustom) untrackCustomKey(ns, key);
|
|
196
|
+
spawnedCustom.length = 0;
|
|
171
197
|
onInterrupt.forEach((fn) => fn());
|
|
172
198
|
pendingCount = 0;
|
|
173
199
|
if (!endResolved) {
|
|
@@ -320,7 +346,9 @@ function createSelection(scene, ids, data, priorPoses, animator, geometry) {
|
|
|
320
346
|
if (!animator) return sel;
|
|
321
347
|
const transitionName = name ?? "";
|
|
322
348
|
for (const id of ids) {
|
|
323
|
-
|
|
349
|
+
const ns = `d3-transition:${transitionName}:${id}`;
|
|
350
|
+
for (const key of takeCustomKeys(ns)) animator.cancelKey(key);
|
|
351
|
+
animator.cancelKey(ns);
|
|
324
352
|
}
|
|
325
353
|
return sel;
|
|
326
354
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/transition.ts","../src/bind.ts"],"names":["t"],"mappings":"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","file":"index.js","sourcesContent":["import type {\n Animator,\n AnimationHandle,\n EasingFn,\n NodeId,\n PoseProjection,\n Scene,\n} from '@weasel-js/core';\nimport type { D3Transition } from './types';\n\ninterface TransitionCtx<TData, TPose> {\n scene: Scene<unknown, string, TPose>;\n animator: Animator;\n geometry: PoseProjection<TPose>;\n ids: readonly NodeId[];\n data: readonly TData[];\n priorPoses: ReadonlyMap<NodeId, TPose>;\n name: string;\n}\n\ninterface CustomTween<TData> {\n name: string;\n from: (d: TData, i: number) => unknown;\n to: (d: TData, i: number) => unknown;\n interpolate?: (from: unknown, to: unknown) => (t: number) => unknown;\n apply: (d: TData, id: NodeId, value: unknown) => void;\n}\n\n/**\n * Build a transition handle. The transition is *lazy*: tweens don't spawn\n * until `.end()` is called (or `await`-ed). This lets the consumer chain\n * `.duration().ease().delay()...` after `.transition()` without having\n * to declare them in advance.\n *\n * For each selected node, one pose tween is spawned from `priorPoses[id]` →\n * current scene pose. Custom `.tween()` declarations spawn one extra tween\n * per node per declaration, using the kit's animator factory-interpolator\n * slot when an `interpolate` factory is provided.\n *\n * `interrupt()` cancels via the kit's `animator.cancelKey`. The cancelKey\n * format is `d3-transition:<name>:<nodeId>` for pose tweens and\n * `d3-transition:<name>:<nodeId>:<tweenName>` for custom tweens, so\n * `.interrupt(name)` on the selection cancels every tween in this\n * transition by namespace.\n */\nexport function createTransition<TData, TPose>(\n ctx: TransitionCtx<TData, TPose>,\n): D3Transition<TData> {\n let duration = 250;\n let easing: EasingFn | undefined;\n let delayFn: ((d: TData, i: number) => number) | null = null;\n let started = false;\n const customTweens: Array<CustomTween<TData>> = [];\n const onStart: Array<() => void> = [];\n const onEnd: Array<() => void> = [];\n const onInterrupt: Array<() => void> = [];\n\n let pendingCount = 0;\n let cancelled = false;\n let endResolved = false;\n let resolveEnd: (() => void) | null = null;\n const endPromise = new Promise<void>((resolve) => {\n resolveEnd = resolve;\n });\n const handles: AnimationHandle[] = [];\n const pendingDelays = new Set<ReturnType<typeof setTimeout>>();\n\n const settleIfDone = (): void => {\n if (pendingCount === 0 && !endResolved) {\n endResolved = true;\n if (!cancelled) onEnd.forEach((fn) => fn());\n resolveEnd?.();\n }\n };\n\n const ensureStarted = (): void => {\n if (started) return;\n started = true;\n\n const { scene, geometry, ids, data, priorPoses, name } = ctx;\n const lerp = geometry.lerp;\n if (!lerp) {\n throw new Error(\n 'd3Bind.transition: provided geometry has no `lerp` — pass a PoseProjection with lerp in BindOptions',\n );\n }\n\n // Snapshot target poses NOW (post-join scene state) for each selected id.\n const targetPoses = new Map<NodeId, TPose>();\n for (const id of ids) {\n const node = scene.get(id);\n if (node) targetPoses.set(id, node.pose);\n }\n\n // Eagerly write the from-pose for each selected node BEFORE any tween\n // spawns. Without this:\n // - Non-delayed tweens flash a single frame of the post-join pose\n // before the animator's first tick writes from-state.\n // - Delayed tweens show the post-join pose for the entire delay window.\n // The eager write produces one extra setPose per node in the undo log;\n // history-bypass for transition ticks is a v2 follow-up (see spec).\n for (const id of ids) {\n const from = priorPoses.get(id);\n if (from !== undefined) scene.setPose(id, from);\n }\n\n let anySpawned = false;\n\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i];\n const d = data[i];\n const from = priorPoses.get(id);\n const to = targetPoses.get(id);\n if (from === undefined || to === undefined) continue;\n\n const delayMs = delayFn ? Math.max(0, delayFn(d, i)) : 0;\n\n const spawnPoseTween = (): void => {\n if (cancelled) return;\n anySpawned = true;\n const handle = ctx.animator.tween<TPose>({\n from,\n to,\n ms: duration,\n easing,\n cancelKey: `d3-transition:${name}:${id}`,\n interpolate: (a, b, t) => lerp(a, b, t),\n onTick: (pose) => {\n // setPose writes through to the scene; SceneCanvas redraws.\n scene.setPose(id, pose);\n },\n onDone: () => {\n pendingCount--;\n settleIfDone();\n },\n });\n handles.push(handle);\n };\n\n pendingCount++;\n if (delayMs > 0) {\n const timer = setTimeout(() => {\n pendingDelays.delete(timer);\n spawnPoseTween();\n }, delayMs);\n pendingDelays.add(timer);\n } else {\n spawnPoseTween();\n }\n\n // Custom tweens — spawn one per registered .tween() per node.\n for (const ct of customTweens) {\n const fromVal = ct.from(d, i);\n const toVal = ct.to(d, i);\n const spawnCustom = (): void => {\n if (cancelled) return;\n anySpawned = true;\n const handle = ctx.animator.tween({\n from: fromVal,\n to: toVal,\n ms: duration,\n easing,\n cancelKey: `d3-transition:${name}:${id}:${ct.name}`,\n interpolator: ct.interpolate,\n interpolate: ct.interpolate\n ? undefined\n : ((a, b, t) => {\n // Fallback per-tick interpolator for numeric values when no factory is given.\n if (typeof a === 'number' && typeof b === 'number') {\n return (a as number) + ((b as number) - (a as number)) * t;\n }\n throw new Error(\n `d3Bind.transition.tween(\"${ct.name}\"): non-numeric value without an interpolate factory`,\n );\n }),\n onTick: (value) => ct.apply(d, id, value),\n onDone: () => {\n pendingCount--;\n settleIfDone();\n },\n });\n handles.push(handle);\n };\n\n pendingCount++;\n if (delayMs > 0) {\n const timer = setTimeout(() => {\n pendingDelays.delete(timer);\n spawnCustom();\n }, delayMs);\n pendingDelays.add(timer);\n } else {\n spawnCustom();\n }\n }\n }\n\n if (anySpawned || pendingDelays.size > 0) {\n onStart.forEach((fn) => fn());\n } else {\n // Nothing to animate (empty selection, no pose deltas). Resolve immediately.\n settleIfDone();\n }\n };\n\n const t: D3Transition<TData> = {\n duration(ms) {\n duration = ms;\n return t;\n },\n ease(fn) {\n easing = fn;\n return t;\n },\n delay(arg) {\n delayFn = typeof arg === 'function' ? arg : () => arg;\n return t;\n },\n tween(opts) {\n customTweens.push({\n name: opts.name,\n from: opts.from as (d: TData, i: number) => unknown,\n to: opts.to as (d: TData, i: number) => unknown,\n interpolate: opts.interpolate as\n | ((from: unknown, to: unknown) => (t: number) => unknown)\n | undefined,\n apply: opts.apply as (d: TData, id: NodeId, value: unknown) => void,\n });\n return t;\n },\n on(event, fn) {\n if (event === 'start') onStart.push(fn);\n else if (event === 'end') onEnd.push(fn);\n else if (event === 'interrupt') onInterrupt.push(fn);\n return t;\n },\n interrupt() {\n if (cancelled || endResolved) return;\n cancelled = true;\n for (const timer of pendingDelays) clearTimeout(timer);\n pendingDelays.clear();\n for (const h of handles) h.cancel();\n handles.length = 0;\n onInterrupt.forEach((fn) => fn());\n // Drain pendingCount so resolveEnd fires; any in-flight onDone callbacks\n // will see endResolved=true and short-circuit.\n pendingCount = 0;\n if (!endResolved) {\n endResolved = true;\n resolveEnd?.();\n }\n },\n end() {\n ensureStarted();\n return endPromise;\n },\n };\n return t;\n}\n","import { asNodeId, RECT_POSE_DESCRIPTOR } from '@weasel-js/core';\nimport type { Animator, NodeId, PoseProjection, Scene } from '@weasel-js/core';\nimport { createTransition } from './transition';\nimport type {\n BindOptions,\n D3Binding,\n D3Selection,\n} from './types';\n\n/**\n * Bind a data array to a scene. Returns a builder; configure with `.pose()` /\n * `.data()` / `.enterFrom()`, then call `.join()` to emit the diff.\n *\n * Diff semantics (by `options.key`):\n * - data has key not in scene → ENTER (scene.add)\n * - data has key in scene → UPDATE (scene.setPose + scene.update)\n * - scene has leaf on the target layer with key not in data → EXIT (scene.remove)\n *\n * All mutations dispatch through `scene.batch('d3Bind.join', ...)` so one join\n * is one undo entry.\n */\nexport function d3Bind<TData, TLayer extends string, TPose>(\n scene: Scene<unknown, TLayer, TPose>,\n data: readonly TData[],\n options: BindOptions<TData, TPose>,\n): D3Binding<TData, TPose> {\n let poseFn: ((d: TData, i: number) => TPose) | null = null;\n let dataFn: ((d: TData, i: number) => Record<string, unknown>) | null = null;\n let enterFromFn: ((d: TData, i: number) => TPose) | null = null;\n\n const binding: D3Binding<TData, TPose> = {\n pose(fn) {\n poseFn = fn;\n return binding;\n },\n data(fn) {\n dataFn = fn;\n return binding;\n },\n enterFrom(fn) {\n enterFromFn = fn;\n return binding;\n },\n join() {\n const layer = (options.layer ?? scene.layers[0]?.id) as TLayer;\n if (!layer) {\n throw new Error('d3Bind: scene has no layers; pass `layer` in options');\n }\n\n // Compute the diff.\n const dataKeys = data.map((d, i) => asNodeId(options.key(d, i)));\n const dataKeySet = new Set(dataKeys);\n const enterIndices: number[] = [];\n const updateIndices: number[] = [];\n const exitIds: NodeId[] = [];\n\n const sceneLeafIdsOnLayer = new Set<NodeId>();\n for (const node of scene.nodes.values()) {\n if (node.kind === 'leaf' && node.layer === layer) {\n sceneLeafIdsOnLayer.add(node.id);\n }\n }\n\n data.forEach((_, i) => {\n if (sceneLeafIdsOnLayer.has(dataKeys[i])) updateIndices.push(i);\n else enterIndices.push(i);\n });\n sceneLeafIdsOnLayer.forEach((id) => {\n if (!dataKeySet.has(id)) exitIds.push(id);\n });\n\n // Snapshot prior poses for nodes that will be updated. Captured BEFORE the\n // batch mutates them so `.transition()` can interpolate from prior → new.\n const priorPoses = new Map<NodeId, TPose>();\n for (const i of updateIndices) {\n const id = dataKeys[i];\n const node = scene.get(id);\n if (node) priorPoses.set(id, node.pose);\n }\n // Entering nodes use enterFrom if set, otherwise their declared pose (snap-in).\n for (const i of enterIndices) {\n const id = dataKeys[i];\n if (enterFromFn) {\n priorPoses.set(id, enterFromFn(data[i], i));\n } else if (poseFn) {\n priorPoses.set(id, poseFn(data[i], i));\n }\n }\n\n // Apply mutations in one batched op group.\n scene.batch('d3Bind.join', () => {\n for (const i of enterIndices) {\n const id = dataKeys[i];\n const pose = poseFn ? poseFn(data[i], i) : (undefined as unknown as TPose);\n const payload = dataFn ? dataFn(data[i], i) : ({} as Record<string, unknown>);\n if (pose === undefined) {\n throw new Error(\n 'd3Bind.join: enter node has no pose. Call `.pose(fn)` before `.join()`.',\n );\n }\n scene.add({\n id,\n kind: 'leaf',\n layer,\n pose,\n // The scene is typed `Scene<unknown, TLayer, TPose>` — payload is\n // unknown to the kit and consumer's drawOne reads it as a typed\n // record.\n data: payload as never,\n });\n }\n for (const i of updateIndices) {\n const id = dataKeys[i];\n if (poseFn) scene.setPose(id, poseFn(data[i], i));\n if (dataFn) scene.update(id, { data: dataFn(data[i], i) as never });\n }\n for (const id of exitIds) {\n scene.remove(id);\n }\n });\n\n return createSelection<TData, TPose>(\n scene,\n dataKeys,\n [...data],\n priorPoses,\n options.animator,\n options.geometry,\n );\n },\n };\n\n return binding;\n}\n\n/** @internal — exported for `selection.filter` reuse. */\nfunction createSelection<TData, TPose>(\n scene: Scene<unknown, string, TPose>,\n ids: readonly NodeId[],\n data: readonly TData[],\n priorPoses: ReadonlyMap<NodeId, TPose>,\n animator: Animator | undefined,\n geometry: PoseProjection<TPose> | undefined,\n): D3Selection<TData, TPose> {\n const sel: D3Selection<TData, TPose> = {\n ids,\n data,\n filter(pred) {\n const subset: NodeId[] = [];\n const subData: TData[] = [];\n for (let i = 0; i < data.length; i++) {\n if (pred(data[i], i)) {\n subset.push(ids[i]);\n subData.push(data[i]);\n }\n }\n return createSelection(scene, subset, subData, priorPoses, animator, geometry);\n },\n each(fn) {\n for (let i = 0; i < ids.length; i++) fn(data[i], ids[i], i);\n return sel;\n },\n transition(name?: string) {\n if (!animator) {\n throw new Error(\n 'd3Bind.transition: pass `animator` in BindOptions to enable transitions',\n );\n }\n const resolvedGeometry = (geometry ??\n (RECT_POSE_DESCRIPTOR as unknown as PoseProjection<TPose>));\n return createTransition({\n scene,\n animator,\n geometry: resolvedGeometry,\n ids,\n data,\n priorPoses,\n name: name ?? '',\n });\n },\n interrupt(name?: string) {\n if (!animator) return sel;\n const transitionName = name ?? '';\n // Cancel all pose tweens registered for this transition name.\n for (const id of ids) {\n animator.cancelKey(`d3-transition:${transitionName}:${id}`);\n }\n return sel;\n },\n };\n return sel;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/transition.ts","../src/bind.ts"],"names":["t"],"mappings":";;;;;AAmDA,IAAM,gBAAA,uBAAuB,GAAA,EAAyB;AAEtD,SAAS,cAAA,CAAe,IAAY,GAAA,EAAmB;AACrD,EAAA,MAAM,MAAM,gBAAA,CAAiB,GAAA,CAAI,EAAE,CAAA,wBAAS,GAAA,EAAY;AACxD,EAAA,GAAA,CAAI,IAAI,GAAG,CAAA;AACX,EAAA,gBAAA,CAAiB,GAAA,CAAI,IAAI,GAAG,CAAA;AAC9B;AAEA,SAAS,gBAAA,CAAiB,IAAY,GAAA,EAAmB;AACvD,EAAA,MAAM,GAAA,GAAM,gBAAA,CAAiB,GAAA,CAAI,EAAE,CAAA;AACnC,EAAA,IAAI,CAAC,GAAA,EAAK;AACV,EAAA,GAAA,CAAI,OAAO,GAAG,CAAA;AACd,EAAA,IAAI,GAAA,CAAI,IAAA,KAAS,CAAA,EAAG,gBAAA,CAAiB,OAAO,EAAE,CAAA;AAChD;AASO,SAAS,eAAe,EAAA,EAAsB;AACnD,EAAA,MAAM,GAAA,GAAM,gBAAA,CAAiB,GAAA,CAAI,EAAE,CAAA;AACnC,EAAA,IAAI,CAAC,GAAA,EAAK,OAAO,EAAC;AAClB,EAAA,gBAAA,CAAiB,OAAO,EAAE,CAAA;AAC1B,EAAA,OAAO,CAAC,GAAG,GAAG,CAAA;AAChB;AAEO,SAAS,iBACd,GAAA,EACqB;AACrB,EAAA,IAAI,QAAA,GAAW,GAAA;AACf,EAAA,IAAI,MAAA;AACJ,EAAA,IAAI,OAAA,GAAoD,IAAA;AACxD,EAAA,IAAI,OAAA,GAAU,KAAA;AACd,EAAA,MAAM,eAA0C,EAAC;AACjD,EAAA,MAAM,UAA6B,EAAC;AACpC,EAAA,MAAM,QAA2B,EAAC;AAClC,EAAA,MAAM,cAAiC,EAAC;AAExC,EAAA,IAAI,YAAA,GAAe,CAAA;AACnB,EAAA,IAAI,SAAA,GAAY,KAAA;AAChB,EAAA,IAAI,WAAA,GAAc,KAAA;AAIlB,EAAA,MAAM,gBAAyC,EAAC;AAChD,EAAA,IAAI,UAAA,GAAkC,IAAA;AACtC,EAAA,MAAM,UAAA,GAAa,IAAI,OAAA,CAAc,CAAC,OAAA,KAAY;AAChD,IAAA,UAAA,GAAa,OAAA;AAAA,EACf,CAAC,CAAA;AACD,EAAA,MAAM,UAA6B,EAAC;AACpC,EAAA,MAAM,aAAA,uBAAoB,GAAA,EAAmC;AAE7D,EAAA,MAAM,eAAe,MAAY;AAC/B,IAAA,IAAI,YAAA,KAAiB,CAAA,IAAK,CAAC,WAAA,EAAa;AACtC,MAAA,WAAA,GAAc,IAAA;AACd,MAAA,IAAI,CAAC,SAAA,EAAW,KAAA,CAAM,QAAQ,CAAC,EAAA,KAAO,IAAI,CAAA;AAC1C,MAAA,UAAA,IAAa;AAAA,IACf;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,gBAAgB,MAAY;AAChC,IAAA,IAAI,OAAA,EAAS;AACb,IAAA,OAAA,GAAU,IAAA;AAEV,IAAA,MAAM,EAAE,KAAA,EAAO,QAAA,EAAU,KAAK,IAAA,EAAM,UAAA,EAAY,MAAK,GAAI,GAAA;AACzD,IAAA,MAAM,OAAO,QAAA,CAAS,IAAA;AACtB,IAAA,IAAI,CAAC,IAAA,EAAM;AACT,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AAGA,IAAA,MAAM,WAAA,uBAAkB,GAAA,EAAmB;AAC3C,IAAA,KAAA,MAAW,MAAM,GAAA,EAAK;AACpB,MAAA,MAAM,IAAA,GAAO,KAAA,CAAM,GAAA,CAAI,EAAE,CAAA;AACzB,MAAA,IAAI,IAAA,EAAM,WAAA,CAAY,GAAA,CAAI,EAAA,EAAI,KAAK,IAAI,CAAA;AAAA,IACzC;AASA,IAAA,KAAA,MAAW,MAAM,GAAA,EAAK;AACpB,MAAA,MAAM,IAAA,GAAO,UAAA,CAAW,GAAA,CAAI,EAAE,CAAA;AAC9B,MAAA,IAAI,IAAA,KAAS,MAAA,EAAW,KAAA,CAAM,OAAA,CAAQ,IAAI,IAAI,CAAA;AAAA,IAChD;AAEA,IAAA,IAAI,UAAA,GAAa,KAAA;AAEjB,IAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,QAAQ,CAAA,EAAA,EAAK;AACnC,MAAA,MAAM,EAAA,GAAK,IAAI,CAAC,CAAA;AAChB,MAAA,MAAM,CAAA,GAAI,KAAK,CAAC,CAAA;AAChB,MAAA,MAAM,IAAA,GAAO,UAAA,CAAW,GAAA,CAAI,EAAE,CAAA;AAC9B,MAAA,MAAM,EAAA,GAAK,WAAA,CAAY,GAAA,CAAI,EAAE,CAAA;AAC7B,MAAA,IAAI,IAAA,KAAS,MAAA,IAAa,EAAA,KAAO,MAAA,EAAW;AAE5C,MAAA,MAAM,OAAA,GAAU,UAAU,IAAA,CAAK,GAAA,CAAI,GAAG,OAAA,CAAQ,CAAA,EAAG,CAAC,CAAC,CAAA,GAAI,CAAA;AAEvD,MAAA,MAAM,iBAAiB,MAAY;AACjC,QAAA,IAAI,SAAA,EAAW;AACf,QAAA,UAAA,GAAa,IAAA;AACb,QAAA,MAAM,MAAA,GAAS,GAAA,CAAI,QAAA,CAAS,KAAA,CAAa;AAAA,UACvC,IAAA;AAAA,UACA,EAAA;AAAA,UACA,EAAA,EAAI,QAAA;AAAA,UACJ,MAAA;AAAA,UACA,SAAA,EAAW,CAAA,cAAA,EAAiB,IAAI,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA;AAAA,UACtC,WAAA,EAAa,CAAC,CAAA,EAAG,CAAA,EAAGA,OAAM,IAAA,CAAK,CAAA,EAAG,GAAGA,EAAC,CAAA;AAAA,UACtC,MAAA,EAAQ,CAAC,IAAA,KAAS;AAEhB,YAAA,KAAA,CAAM,OAAA,CAAQ,IAAI,IAAI,CAAA;AAAA,UACxB,CAAA;AAAA,UACA,QAAQ,MAAM;AACZ,YAAA,YAAA,EAAA;AACA,YAAA,YAAA,EAAa;AAAA,UACf;AAAA,SACD,CAAA;AACD,QAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AAAA,MACrB,CAAA;AAEA,MAAA,YAAA,EAAA;AACA,MAAA,IAAI,UAAU,CAAA,EAAG;AACf,QAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,UAAA,aAAA,CAAc,OAAO,KAAK,CAAA;AAC1B,UAAA,cAAA,EAAe;AAAA,QACjB,GAAG,OAAO,CAAA;AACV,QAAA,aAAA,CAAc,IAAI,KAAK,CAAA;AAAA,MACzB,CAAA,MAAO;AACL,QAAA,cAAA,EAAe;AAAA,MACjB;AAGA,MAAA,KAAA,MAAW,MAAM,YAAA,EAAc;AAC7B,QAAA,MAAM,OAAA,GAAU,EAAA,CAAG,IAAA,CAAK,CAAA,EAAG,CAAC,CAAA;AAC5B,QAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,EAAA,CAAG,CAAA,EAAG,CAAC,CAAA;AACxB,QAAA,MAAM,EAAA,GAAK,CAAA,cAAA,EAAiB,IAAI,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA;AACtC,QAAA,MAAM,SAAA,GAAY,CAAA,EAAG,EAAE,CAAA,CAAA,EAAI,GAAG,IAAI,CAAA,CAAA;AAClC,QAAA,MAAM,cAAc,MAAY;AAC9B,UAAA,IAAI,SAAA,EAAW;AACf,UAAA,UAAA,GAAa,IAAA;AACb,UAAA,cAAA,CAAe,IAAI,SAAS,CAAA;AAC5B,UAAA,aAAA,CAAc,IAAA,CAAK,CAAC,EAAA,EAAI,SAAS,CAAC,CAAA;AAClC,UAAA,MAAM,MAAA,GAAS,GAAA,CAAI,QAAA,CAAS,KAAA,CAAM;AAAA,YAChC,IAAA,EAAM,OAAA;AAAA,YACN,EAAA,EAAI,KAAA;AAAA,YACJ,EAAA,EAAI,QAAA;AAAA,YACJ,MAAA;AAAA,YACA,SAAA,EAAW,SAAA;AAAA,YACX,cAAc,EAAA,CAAG,WAAA;AAAA,YACjB,aAAa,EAAA,CAAG,WAAA,GACZ,UACC,CAAC,CAAA,EAAG,GAAGA,EAAAA,KAAM;AAEZ,cAAA,IAAI,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,MAAM,QAAA,EAAU;AAClD,gBAAA,OAAQ,CAAA,GAAA,CAAiB,IAAgB,CAAA,IAAgBA,EAAAA;AAAA,cAC3D;AACA,cAAA,MAAM,IAAI,KAAA;AAAA,gBACR,CAAA,yBAAA,EAA4B,GAAG,IAAI,CAAA,oDAAA;AAAA,eACrC;AAAA,YACF,CAAA,CAAA;AAAA,YACJ,QAAQ,CAAC,KAAA,KAAU,GAAG,KAAA,CAAM,CAAA,EAAG,IAAI,KAAK,CAAA;AAAA,YACxC,QAAQ,MAAM;AACZ,cAAA,gBAAA,CAAiB,IAAI,SAAS,CAAA;AAC9B,cAAA,YAAA,EAAA;AACA,cAAA,YAAA,EAAa;AAAA,YACf;AAAA,WACD,CAAA;AACD,UAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AAAA,QACrB,CAAA;AAEA,QAAA,YAAA,EAAA;AACA,QAAA,IAAI,UAAU,CAAA,EAAG;AACf,UAAA,MAAM,KAAA,GAAQ,WAAW,MAAM;AAC7B,YAAA,aAAA,CAAc,OAAO,KAAK,CAAA;AAC1B,YAAA,WAAA,EAAY;AAAA,UACd,GAAG,OAAO,CAAA;AACV,UAAA,aAAA,CAAc,IAAI,KAAK,CAAA;AAAA,QACzB,CAAA,MAAO;AACL,UAAA,WAAA,EAAY;AAAA,QACd;AAAA,MACF;AAAA,IACF;AAEA,IAAA,IAAI,UAAA,IAAc,aAAA,CAAc,IAAA,GAAO,CAAA,EAAG;AACxC,MAAA,OAAA,CAAQ,OAAA,CAAQ,CAAC,EAAA,KAAO,EAAA,EAAI,CAAA;AAAA,IAC9B,CAAA,MAAO;AAEL,MAAA,YAAA,EAAa;AAAA,IACf;AAAA,EACF,CAAA;AAEA,EAAA,MAAM,CAAA,GAAyB;AAAA,IAC7B,SAAS,EAAA,EAAI;AACX,MAAA,QAAA,GAAW,EAAA;AACX,MAAA,OAAO,CAAA;AAAA,IACT,CAAA;AAAA,IACA,KAAK,EAAA,EAAI;AACP,MAAA,MAAA,GAAS,EAAA;AACT,MAAA,OAAO,CAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAM,GAAA,EAAK;AACT,MAAA,OAAA,GAAU,OAAO,GAAA,KAAQ,UAAA,GAAa,GAAA,GAAM,MAAM,GAAA;AAClD,MAAA,OAAO,CAAA;AAAA,IACT,CAAA;AAAA,IACA,MAAM,IAAA,EAAM;AACV,MAAA,YAAA,CAAa,IAAA,CAAK;AAAA,QAChB,MAAM,IAAA,CAAK,IAAA;AAAA,QACX,MAAM,IAAA,CAAK,IAAA;AAAA,QACX,IAAI,IAAA,CAAK,EAAA;AAAA,QACT,aAAa,IAAA,CAAK,WAAA;AAAA,QAGlB,OAAO,IAAA,CAAK;AAAA,OACb,CAAA;AACD,MAAA,OAAO,CAAA;AAAA,IACT,CAAA;AAAA,IACA,EAAA,CAAG,OAAO,EAAA,EAAI;AACZ,MAAA,IAAI,KAAA,KAAU,OAAA,EAAS,OAAA,CAAQ,IAAA,CAAK,EAAE,CAAA;AAAA,WAAA,IAC7B,KAAA,KAAU,KAAA,EAAO,KAAA,CAAM,IAAA,CAAK,EAAE,CAAA;AAAA,WAAA,IAC9B,KAAA,KAAU,WAAA,EAAa,WAAA,CAAY,IAAA,CAAK,EAAE,CAAA;AACnD,MAAA,OAAO,CAAA;AAAA,IACT,CAAA;AAAA,IACA,SAAA,GAAY;AACV,MAAA,IAAI,aAAa,WAAA,EAAa;AAC9B,MAAA,SAAA,GAAY,IAAA;AACZ,MAAA,KAAA,MAAW,KAAA,IAAS,aAAA,EAAe,YAAA,CAAa,KAAK,CAAA;AACrD,MAAA,aAAA,CAAc,KAAA,EAAM;AACpB,MAAA,KAAA,MAAW,CAAA,IAAK,OAAA,EAAS,CAAA,CAAE,MAAA,EAAO;AAClC,MAAA,OAAA,CAAQ,MAAA,GAAS,CAAA;AACjB,MAAA,KAAA,MAAW,CAAC,EAAA,EAAI,GAAG,KAAK,aAAA,EAAe,gBAAA,CAAiB,IAAI,GAAG,CAAA;AAC/D,MAAA,aAAA,CAAc,MAAA,GAAS,CAAA;AACvB,MAAA,WAAA,CAAY,OAAA,CAAQ,CAAC,EAAA,KAAO,EAAA,EAAI,CAAA;AAGhC,MAAA,YAAA,GAAe,CAAA;AACf,MAAA,IAAI,CAAC,WAAA,EAAa;AAChB,QAAA,WAAA,GAAc,IAAA;AACd,QAAA,UAAA,IAAa;AAAA,MACf;AAAA,IACF,CAAA;AAAA,IACA,GAAA,GAAM;AACJ,MAAA,aAAA,EAAc;AACd,MAAA,OAAO,UAAA;AAAA,IACT;AAAA,GACF;AACA,EAAA,OAAO,CAAA;AACT;;;AC3RO,SAAS,MAAA,CACd,KAAA,EACA,IAAA,EACA,OAAA,EACyB;AACzB,EAAA,IAAI,MAAA,GAAkD,IAAA;AACtD,EAAA,IAAI,MAAA,GAAoE,IAAA;AACxE,EAAA,IAAI,WAAA,GAAuD,IAAA;AAE3D,EAAA,MAAM,OAAA,GAAmC;AAAA,IACvC,KAAK,EAAA,EAAI;AACP,MAAA,MAAA,GAAS,EAAA;AACT,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAAA,IACA,KAAK,EAAA,EAAI;AACP,MAAA,MAAA,GAAS,EAAA;AACT,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAAA,IACA,UAAU,EAAA,EAAI;AACZ,MAAA,WAAA,GAAc,EAAA;AACd,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAAA,IACA,IAAA,GAAO;AACL,MAAA,MAAM,QAAS,OAAA,CAAQ,KAAA,IAAS,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,EAAG,EAAA;AACjD,MAAA,IAAI,CAAC,KAAA,EAAO;AACV,QAAA,MAAM,IAAI,MAAM,sDAAsD,CAAA;AAAA,MACxE;AAGA,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,KAAM,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,CAAA,EAAG,CAAC,CAAC,CAAC,CAAA;AAC/D,MAAA,MAAM,UAAA,GAAa,IAAI,GAAA,CAAI,QAAQ,CAAA;AACnC,MAAA,MAAM,eAAyB,EAAC;AAChC,MAAA,MAAM,gBAA0B,EAAC;AACjC,MAAA,MAAM,UAAoB,EAAC;AAE3B,MAAA,MAAM,mBAAA,uBAA0B,GAAA,EAAY;AAC5C,MAAA,KAAA,MAAW,IAAA,IAAQ,KAAA,CAAM,KAAA,CAAM,MAAA,EAAO,EAAG;AACvC,QAAA,IAAI,IAAA,CAAK,IAAA,KAAS,MAAA,IAAU,IAAA,CAAK,UAAU,KAAA,EAAO;AAChD,UAAA,mBAAA,CAAoB,GAAA,CAAI,KAAK,EAAE,CAAA;AAAA,QACjC;AAAA,MACF;AAEA,MAAA,IAAA,CAAK,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA,KAAM;AACrB,QAAA,IAAI,mBAAA,CAAoB,IAAI,QAAA,CAAS,CAAC,CAAC,CAAA,EAAG,aAAA,CAAc,KAAK,CAAC,CAAA;AAAA,aACzD,YAAA,CAAa,KAAK,CAAC,CAAA;AAAA,MAC1B,CAAC,CAAA;AACD,MAAA,mBAAA,CAAoB,OAAA,CAAQ,CAAC,EAAA,KAAO;AAClC,QAAA,IAAI,CAAC,UAAA,CAAW,GAAA,CAAI,EAAE,CAAA,EAAG,OAAA,CAAQ,KAAK,EAAE,CAAA;AAAA,MAC1C,CAAC,CAAA;AAID,MAAA,MAAM,UAAA,uBAAiB,GAAA,EAAmB;AAC1C,MAAA,KAAA,MAAW,KAAK,aAAA,EAAe;AAC7B,QAAA,MAAM,EAAA,GAAK,SAAS,CAAC,CAAA;AACrB,QAAA,MAAM,IAAA,GAAO,KAAA,CAAM,GAAA,CAAI,EAAE,CAAA;AACzB,QAAA,IAAI,IAAA,EAAM,UAAA,CAAW,GAAA,CAAI,EAAA,EAAI,KAAK,IAAI,CAAA;AAAA,MACxC;AAEA,MAAA,KAAA,MAAW,KAAK,YAAA,EAAc;AAC5B,QAAA,MAAM,EAAA,GAAK,SAAS,CAAC,CAAA;AACrB,QAAA,IAAI,WAAA,EAAa;AACf,UAAA,UAAA,CAAW,IAAI,EAAA,EAAI,WAAA,CAAY,KAAK,CAAC,CAAA,EAAG,CAAC,CAAC,CAAA;AAAA,QAC5C,WAAW,MAAA,EAAQ;AACjB,UAAA,UAAA,CAAW,IAAI,EAAA,EAAI,MAAA,CAAO,KAAK,CAAC,CAAA,EAAG,CAAC,CAAC,CAAA;AAAA,QACvC;AAAA,MACF;AAGA,MAAA,KAAA,CAAM,KAAA,CAAM,eAAe,MAAM;AAC/B,QAAA,KAAA,MAAW,KAAK,YAAA,EAAc;AAC5B,UAAA,MAAM,EAAA,GAAK,SAAS,CAAC,CAAA;AACrB,UAAA,MAAM,OAAO,MAAA,GAAS,MAAA,CAAO,KAAK,CAAC,CAAA,EAAG,CAAC,CAAA,GAAK,MAAA;AAC5C,UAAA,MAAM,OAAA,GAAU,SAAS,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,EAAG,CAAC,IAAK,EAAC;AAChD,UAAA,IAAI,SAAS,MAAA,EAAW;AACtB,YAAA,MAAM,IAAI,KAAA;AAAA,cACR;AAAA,aACF;AAAA,UACF;AACA,UAAA,KAAA,CAAM,GAAA,CAAI;AAAA,YACR,EAAA;AAAA,YACA,IAAA,EAAM,MAAA;AAAA,YACN,KAAA;AAAA,YACA,IAAA;AAAA;AAAA;AAAA;AAAA,YAIA,IAAA,EAAM;AAAA,WACP,CAAA;AAAA,QACH;AACA,QAAA,KAAA,MAAW,KAAK,aAAA,EAAe;AAC7B,UAAA,MAAM,EAAA,GAAK,SAAS,CAAC,CAAA;AACrB,UAAA,IAAI,MAAA,QAAc,OAAA,CAAQ,EAAA,EAAI,OAAO,IAAA,CAAK,CAAC,CAAA,EAAG,CAAC,CAAC,CAAA;AAChD,UAAA,IAAI,MAAA,EAAQ,KAAA,CAAM,MAAA,CAAO,EAAA,EAAI,EAAE,IAAA,EAAM,MAAA,CAAO,IAAA,CAAK,CAAC,CAAA,EAAG,CAAC,CAAA,EAAY,CAAA;AAAA,QACpE;AACA,QAAA,KAAA,MAAW,MAAM,OAAA,EAAS;AACxB,UAAA,KAAA,CAAM,OAAO,EAAE,CAAA;AAAA,QACjB;AAAA,MACF,CAAC,CAAA;AAED,MAAA,OAAO,eAAA;AAAA,QACL,KAAA;AAAA,QACA,QAAA;AAAA,QACA,CAAC,GAAG,IAAI,CAAA;AAAA,QACR,UAAA;AAAA,QACA,OAAA,CAAQ,QAAA;AAAA,QACR,OAAA,CAAQ;AAAA,OACV;AAAA,IACF;AAAA,GACF;AAEA,EAAA,OAAO,OAAA;AACT;AAGA,SAAS,gBACP,KAAA,EACA,GAAA,EACA,IAAA,EACA,UAAA,EACA,UACA,QAAA,EAC2B;AAC3B,EAAA,MAAM,GAAA,GAAiC;AAAA,IACrC,GAAA;AAAA,IACA,IAAA;AAAA,IACA,OAAO,IAAA,EAAM;AACX,MAAA,MAAM,SAAmB,EAAC;AAC1B,MAAA,MAAM,UAAmB,EAAC;AAC1B,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,IAAA,CAAK,QAAQ,CAAA,EAAA,EAAK;AACpC,QAAA,IAAI,IAAA,CAAK,IAAA,CAAK,CAAC,CAAA,EAAG,CAAC,CAAA,EAAG;AACpB,UAAA,MAAA,CAAO,IAAA,CAAK,GAAA,CAAI,CAAC,CAAC,CAAA;AAClB,UAAA,OAAA,CAAQ,IAAA,CAAK,IAAA,CAAK,CAAC,CAAC,CAAA;AAAA,QACtB;AAAA,MACF;AACA,MAAA,OAAO,gBAAgB,KAAA,EAAO,MAAA,EAAQ,OAAA,EAAS,UAAA,EAAY,UAAU,QAAQ,CAAA;AAAA,IAC/E,CAAA;AAAA,IACA,KAAK,EAAA,EAAI;AACP,MAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,GAAA,CAAI,MAAA,EAAQ,CAAA,EAAA,EAAK,EAAA,CAAG,IAAA,CAAK,CAAC,CAAA,EAAG,GAAA,CAAI,CAAC,GAAG,CAAC,CAAA;AAC1D,MAAA,OAAO,GAAA;AAAA,IACT,CAAA;AAAA,IACA,WAAW,IAAA,EAAe;AACxB,MAAA,IAAI,CAAC,QAAA,EAAU;AACb,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,SACF;AAAA,MACF;AACA,MAAA,MAAM,mBAAoB,QAAA,IACvB,oBAAA;AACH,MAAA,OAAO,gBAAA,CAAiB;AAAA,QACtB,KAAA;AAAA,QACA,QAAA;AAAA,QACA,QAAA,EAAU,gBAAA;AAAA,QACV,GAAA;AAAA,QACA,IAAA;AAAA,QACA,UAAA;AAAA,QACA,MAAM,IAAA,IAAQ;AAAA,OACf,CAAA;AAAA,IACH,CAAA;AAAA,IACA,UAAU,IAAA,EAAe;AACvB,MAAA,IAAI,CAAC,UAAU,OAAO,GAAA;AACtB,MAAA,MAAM,iBAAiB,IAAA,IAAQ,EAAA;AAG/B,MAAA,KAAA,MAAW,MAAM,GAAA,EAAK;AACpB,QAAA,MAAM,EAAA,GAAK,CAAA,cAAA,EAAiB,cAAc,CAAA,CAAA,EAAI,EAAE,CAAA,CAAA;AAChD,QAAA,KAAA,MAAW,OAAO,cAAA,CAAe,EAAE,CAAA,EAAG,QAAA,CAAS,UAAU,GAAG,CAAA;AAC5D,QAAA,QAAA,CAAS,UAAU,EAAE,CAAA;AAAA,MACvB;AACA,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,GACF;AACA,EAAA,OAAO,GAAA;AACT","file":"index.js","sourcesContent":["import type {\n Animator,\n AnimationHandle,\n EasingFn,\n NodeId,\n PoseProjection,\n Scene,\n} from '@weasel-js/core';\nimport type { D3Transition } from './types';\n\ninterface TransitionCtx<TData, TPose> {\n scene: Scene<unknown, string, TPose>;\n animator: Animator;\n geometry: PoseProjection<TPose>;\n ids: readonly NodeId[];\n data: readonly TData[];\n priorPoses: ReadonlyMap<NodeId, TPose>;\n name: string;\n}\n\ninterface CustomTween<TData> {\n name: string;\n from: (d: TData, i: number) => unknown;\n to: (d: TData, i: number) => unknown;\n interpolate?: (from: unknown, to: unknown) => (t: number) => unknown;\n apply: (d: TData, id: NodeId, value: unknown) => void;\n}\n\n/**\n * Build a transition handle. The transition is *lazy*: tweens don't spawn\n * until `.end()` is called (or `await`-ed). This lets the consumer chain\n * `.duration().ease().delay()...` after `.transition()` without having\n * to declare them in advance.\n *\n * For each selected node, one pose tween is spawned from `priorPoses[id]` →\n * current scene pose. Custom `.tween()` declarations spawn one extra tween\n * per node per declaration, using the kit's animator factory-interpolator\n * slot when an `interpolate` factory is provided.\n *\n * `interrupt()` cancels via the kit's `animator.cancelKey`. The cancelKey\n * format is `d3-transition:<name>:<nodeId>` for pose tweens and\n * `d3-transition:<name>:<nodeId>:<tweenName>` for custom tweens.\n * `animator.cancelKey` matches a key exactly, so a selection-level\n * `.interrupt(name)` cannot reach the custom keys by prefix — live custom\n * keys are tracked in `LIVE_CUSTOM_KEYS` and read back by `customKeysFor`.\n */\n\n/** Live custom-tween cancelKeys, grouped by their pose-tween namespace\n * (`d3-transition:<name>:<nodeId>`). Populated on spawn and drained on\n * completion or cancel, so a selection-level interrupt can find the keys a\n * prefix match would otherwise have given it. */\nconst LIVE_CUSTOM_KEYS = new Map<string, Set<string>>();\n\nfunction trackCustomKey(ns: string, key: string): void {\n const set = LIVE_CUSTOM_KEYS.get(ns) ?? new Set<string>();\n set.add(key);\n LIVE_CUSTOM_KEYS.set(ns, set);\n}\n\nfunction untrackCustomKey(ns: string, key: string): void {\n const set = LIVE_CUSTOM_KEYS.get(ns);\n if (!set) return;\n set.delete(key);\n if (set.size === 0) LIVE_CUSTOM_KEYS.delete(ns);\n}\n\n/**\n * @internal Take the live custom-tween cancelKeys under `ns`, clearing them.\n *\n * Draining is what keeps the table bounded: the caller's next move is\n * `animator.cancelKey`, and a cancelled tween never runs its `onDone`, so\n * leaving the keys behind would strand one entry per interrupted tween.\n */\nexport function takeCustomKeys(ns: string): string[] {\n const set = LIVE_CUSTOM_KEYS.get(ns);\n if (!set) return [];\n LIVE_CUSTOM_KEYS.delete(ns);\n return [...set];\n}\n\nexport function createTransition<TData, TPose>(\n ctx: TransitionCtx<TData, TPose>,\n): D3Transition<TData> {\n let duration = 250;\n let easing: EasingFn | undefined;\n let delayFn: ((d: TData, i: number) => number) | null = null;\n let started = false;\n const customTweens: Array<CustomTween<TData>> = [];\n const onStart: Array<() => void> = [];\n const onEnd: Array<() => void> = [];\n const onInterrupt: Array<() => void> = [];\n\n let pendingCount = 0;\n let cancelled = false;\n let endResolved = false;\n /** (namespace, key) pairs this transition put in `LIVE_CUSTOM_KEYS`.\n * `handle.cancel()` does not run a tween's `onDone`, so `interrupt()`\n * drains these itself or the keys outlive their tweens. */\n const spawnedCustom: Array<[string, string]> = [];\n let resolveEnd: (() => void) | null = null;\n const endPromise = new Promise<void>((resolve) => {\n resolveEnd = resolve;\n });\n const handles: AnimationHandle[] = [];\n const pendingDelays = new Set<ReturnType<typeof setTimeout>>();\n\n const settleIfDone = (): void => {\n if (pendingCount === 0 && !endResolved) {\n endResolved = true;\n if (!cancelled) onEnd.forEach((fn) => fn());\n resolveEnd?.();\n }\n };\n\n const ensureStarted = (): void => {\n if (started) return;\n started = true;\n\n const { scene, geometry, ids, data, priorPoses, name } = ctx;\n const lerp = geometry.lerp;\n if (!lerp) {\n throw new Error(\n 'd3Bind.transition: provided geometry has no `lerp` — pass a PoseProjection with lerp in BindOptions',\n );\n }\n\n // Snapshot target poses NOW (post-join scene state) for each selected id.\n const targetPoses = new Map<NodeId, TPose>();\n for (const id of ids) {\n const node = scene.get(id);\n if (node) targetPoses.set(id, node.pose);\n }\n\n // Eagerly write the from-pose for each selected node BEFORE any tween\n // spawns. Without this:\n // - Non-delayed tweens flash a single frame of the post-join pose\n // before the animator's first tick writes from-state.\n // - Delayed tweens show the post-join pose for the entire delay window.\n // The eager write produces one extra setPose per node in the undo log;\n // history-bypass for transition ticks is a v2 follow-up (see spec).\n for (const id of ids) {\n const from = priorPoses.get(id);\n if (from !== undefined) scene.setPose(id, from);\n }\n\n let anySpawned = false;\n\n for (let i = 0; i < ids.length; i++) {\n const id = ids[i];\n const d = data[i];\n const from = priorPoses.get(id);\n const to = targetPoses.get(id);\n if (from === undefined || to === undefined) continue;\n\n const delayMs = delayFn ? Math.max(0, delayFn(d, i)) : 0;\n\n const spawnPoseTween = (): void => {\n if (cancelled) return;\n anySpawned = true;\n const handle = ctx.animator.tween<TPose>({\n from,\n to,\n ms: duration,\n easing,\n cancelKey: `d3-transition:${name}:${id}`,\n interpolate: (a, b, t) => lerp(a, b, t),\n onTick: (pose) => {\n // setPose writes through to the scene; SceneCanvas redraws.\n scene.setPose(id, pose);\n },\n onDone: () => {\n pendingCount--;\n settleIfDone();\n },\n });\n handles.push(handle);\n };\n\n pendingCount++;\n if (delayMs > 0) {\n const timer = setTimeout(() => {\n pendingDelays.delete(timer);\n spawnPoseTween();\n }, delayMs);\n pendingDelays.add(timer);\n } else {\n spawnPoseTween();\n }\n\n // Custom tweens — spawn one per registered .tween() per node.\n for (const ct of customTweens) {\n const fromVal = ct.from(d, i);\n const toVal = ct.to(d, i);\n const ns = `d3-transition:${name}:${id}`;\n const customKey = `${ns}:${ct.name}`;\n const spawnCustom = (): void => {\n if (cancelled) return;\n anySpawned = true;\n trackCustomKey(ns, customKey);\n spawnedCustom.push([ns, customKey]);\n const handle = ctx.animator.tween({\n from: fromVal,\n to: toVal,\n ms: duration,\n easing,\n cancelKey: customKey,\n interpolator: ct.interpolate,\n interpolate: ct.interpolate\n ? undefined\n : ((a, b, t) => {\n // Fallback per-tick interpolator for numeric values when no factory is given.\n if (typeof a === 'number' && typeof b === 'number') {\n return (a as number) + ((b as number) - (a as number)) * t;\n }\n throw new Error(\n `d3Bind.transition.tween(\"${ct.name}\"): non-numeric value without an interpolate factory`,\n );\n }),\n onTick: (value) => ct.apply(d, id, value),\n onDone: () => {\n untrackCustomKey(ns, customKey);\n pendingCount--;\n settleIfDone();\n },\n });\n handles.push(handle);\n };\n\n pendingCount++;\n if (delayMs > 0) {\n const timer = setTimeout(() => {\n pendingDelays.delete(timer);\n spawnCustom();\n }, delayMs);\n pendingDelays.add(timer);\n } else {\n spawnCustom();\n }\n }\n }\n\n if (anySpawned || pendingDelays.size > 0) {\n onStart.forEach((fn) => fn());\n } else {\n // Nothing to animate (empty selection, no pose deltas). Resolve immediately.\n settleIfDone();\n }\n };\n\n const t: D3Transition<TData> = {\n duration(ms) {\n duration = ms;\n return t;\n },\n ease(fn) {\n easing = fn;\n return t;\n },\n delay(arg) {\n delayFn = typeof arg === 'function' ? arg : () => arg;\n return t;\n },\n tween(opts) {\n customTweens.push({\n name: opts.name,\n from: opts.from as (d: TData, i: number) => unknown,\n to: opts.to as (d: TData, i: number) => unknown,\n interpolate: opts.interpolate as\n | ((from: unknown, to: unknown) => (t: number) => unknown)\n | undefined,\n apply: opts.apply as (d: TData, id: NodeId, value: unknown) => void,\n });\n return t;\n },\n on(event, fn) {\n if (event === 'start') onStart.push(fn);\n else if (event === 'end') onEnd.push(fn);\n else if (event === 'interrupt') onInterrupt.push(fn);\n return t;\n },\n interrupt() {\n if (cancelled || endResolved) return;\n cancelled = true;\n for (const timer of pendingDelays) clearTimeout(timer);\n pendingDelays.clear();\n for (const h of handles) h.cancel();\n handles.length = 0;\n for (const [ns, key] of spawnedCustom) untrackCustomKey(ns, key);\n spawnedCustom.length = 0;\n onInterrupt.forEach((fn) => fn());\n // Drain pendingCount so resolveEnd fires; any in-flight onDone callbacks\n // will see endResolved=true and short-circuit.\n pendingCount = 0;\n if (!endResolved) {\n endResolved = true;\n resolveEnd?.();\n }\n },\n end() {\n ensureStarted();\n return endPromise;\n },\n };\n return t;\n}\n","import { asNodeId, RECT_POSE_DESCRIPTOR } from '@weasel-js/core';\nimport type { Animator, NodeId, PoseProjection, Scene } from '@weasel-js/core';\nimport { createTransition, takeCustomKeys } from './transition';\nimport type {\n BindOptions,\n D3Binding,\n D3Selection,\n} from './types';\n\n/**\n * Bind a data array to a scene. Returns a builder; configure with `.pose()` /\n * `.data()` / `.enterFrom()`, then call `.join()` to emit the diff.\n *\n * Diff semantics (by `options.key`):\n * - data has key not in scene → ENTER (scene.add)\n * - data has key in scene → UPDATE (scene.setPose + scene.update)\n * - scene has leaf on the target layer with key not in data → EXIT (scene.remove)\n *\n * All mutations dispatch through `scene.batch('d3Bind.join', ...)` so one join\n * is one undo entry.\n */\nexport function d3Bind<TData, TLayer extends string, TPose>(\n scene: Scene<unknown, TLayer, TPose>,\n data: readonly TData[],\n options: BindOptions<TData, TPose>,\n): D3Binding<TData, TPose> {\n let poseFn: ((d: TData, i: number) => TPose) | null = null;\n let dataFn: ((d: TData, i: number) => Record<string, unknown>) | null = null;\n let enterFromFn: ((d: TData, i: number) => TPose) | null = null;\n\n const binding: D3Binding<TData, TPose> = {\n pose(fn) {\n poseFn = fn;\n return binding;\n },\n data(fn) {\n dataFn = fn;\n return binding;\n },\n enterFrom(fn) {\n enterFromFn = fn;\n return binding;\n },\n join() {\n const layer = (options.layer ?? scene.layers[0]?.id) as TLayer;\n if (!layer) {\n throw new Error('d3Bind: scene has no layers; pass `layer` in options');\n }\n\n // Compute the diff.\n const dataKeys = data.map((d, i) => asNodeId(options.key(d, i)));\n const dataKeySet = new Set(dataKeys);\n const enterIndices: number[] = [];\n const updateIndices: number[] = [];\n const exitIds: NodeId[] = [];\n\n const sceneLeafIdsOnLayer = new Set<NodeId>();\n for (const node of scene.nodes.values()) {\n if (node.kind === 'leaf' && node.layer === layer) {\n sceneLeafIdsOnLayer.add(node.id);\n }\n }\n\n data.forEach((_, i) => {\n if (sceneLeafIdsOnLayer.has(dataKeys[i])) updateIndices.push(i);\n else enterIndices.push(i);\n });\n sceneLeafIdsOnLayer.forEach((id) => {\n if (!dataKeySet.has(id)) exitIds.push(id);\n });\n\n // Snapshot prior poses for nodes that will be updated. Captured BEFORE the\n // batch mutates them so `.transition()` can interpolate from prior → new.\n const priorPoses = new Map<NodeId, TPose>();\n for (const i of updateIndices) {\n const id = dataKeys[i];\n const node = scene.get(id);\n if (node) priorPoses.set(id, node.pose);\n }\n // Entering nodes use enterFrom if set, otherwise their declared pose (snap-in).\n for (const i of enterIndices) {\n const id = dataKeys[i];\n if (enterFromFn) {\n priorPoses.set(id, enterFromFn(data[i], i));\n } else if (poseFn) {\n priorPoses.set(id, poseFn(data[i], i));\n }\n }\n\n // Apply mutations in one batched op group.\n scene.batch('d3Bind.join', () => {\n for (const i of enterIndices) {\n const id = dataKeys[i];\n const pose = poseFn ? poseFn(data[i], i) : (undefined as unknown as TPose);\n const payload = dataFn ? dataFn(data[i], i) : ({} as Record<string, unknown>);\n if (pose === undefined) {\n throw new Error(\n 'd3Bind.join: enter node has no pose. Call `.pose(fn)` before `.join()`.',\n );\n }\n scene.add({\n id,\n kind: 'leaf',\n layer,\n pose,\n // The scene is typed `Scene<unknown, TLayer, TPose>` — payload is\n // unknown to the kit and consumer's drawOne reads it as a typed\n // record.\n data: payload as never,\n });\n }\n for (const i of updateIndices) {\n const id = dataKeys[i];\n if (poseFn) scene.setPose(id, poseFn(data[i], i));\n if (dataFn) scene.update(id, { data: dataFn(data[i], i) as never });\n }\n for (const id of exitIds) {\n scene.remove(id);\n }\n });\n\n return createSelection<TData, TPose>(\n scene,\n dataKeys,\n [...data],\n priorPoses,\n options.animator,\n options.geometry,\n );\n },\n };\n\n return binding;\n}\n\n/** @internal — exported for `selection.filter` reuse. */\nfunction createSelection<TData, TPose>(\n scene: Scene<unknown, string, TPose>,\n ids: readonly NodeId[],\n data: readonly TData[],\n priorPoses: ReadonlyMap<NodeId, TPose>,\n animator: Animator | undefined,\n geometry: PoseProjection<TPose> | undefined,\n): D3Selection<TData, TPose> {\n const sel: D3Selection<TData, TPose> = {\n ids,\n data,\n filter(pred) {\n const subset: NodeId[] = [];\n const subData: TData[] = [];\n for (let i = 0; i < data.length; i++) {\n if (pred(data[i], i)) {\n subset.push(ids[i]);\n subData.push(data[i]);\n }\n }\n return createSelection(scene, subset, subData, priorPoses, animator, geometry);\n },\n each(fn) {\n for (let i = 0; i < ids.length; i++) fn(data[i], ids[i], i);\n return sel;\n },\n transition(name?: string) {\n if (!animator) {\n throw new Error(\n 'd3Bind.transition: pass `animator` in BindOptions to enable transitions',\n );\n }\n const resolvedGeometry = (geometry ??\n (RECT_POSE_DESCRIPTOR as unknown as PoseProjection<TPose>));\n return createTransition({\n scene,\n animator,\n geometry: resolvedGeometry,\n ids,\n data,\n priorPoses,\n name: name ?? '',\n });\n },\n interrupt(name?: string) {\n if (!animator) return sel;\n const transitionName = name ?? '';\n // `animator.cancelKey` matches exactly, so the custom tweens — whose\n // keys carry a `:<tweenName>` suffix — need naming individually.\n for (const id of ids) {\n const ns = `d3-transition:${transitionName}:${id}`;\n for (const key of takeCustomKeys(ns)) animator.cancelKey(key);\n animator.cancelKey(ns);\n }\n return sel;\n },\n };\n return sel;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weasel-js/d3",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "d3 bridge for weasel: data-join + transition chain over useScene and useAnimator. d3-force already integrates via useSimulation in the main kit.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|