@weasel-js/d3 1.7.0 → 1.7.2
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/README.md +22 -1
- package/dist/index.d.ts +38 -8
- package/dist/index.js +290 -117
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -14,10 +14,31 @@ npm install @weasel-js/d3
|
|
|
14
14
|
|
|
15
15
|
## Usage
|
|
16
16
|
|
|
17
|
+
`d3Bind` binds an array to a scene's leaves by key. `join()` takes no
|
|
18
|
+
arguments: entering, updating and exiting are a diff it works out by key,
|
|
19
|
+
and `.exit(fn)` hands you the exit set when you want to handle it yourself.
|
|
20
|
+
|
|
17
21
|
```ts
|
|
18
|
-
import {
|
|
22
|
+
import { d3Bind } from '@weasel-js/d3';
|
|
23
|
+
|
|
24
|
+
const binding = d3Bind(scene, points, { key: (d) => d.id, animator })
|
|
25
|
+
.pose((d) => ({ x: d.x, y: d.y, width: 8, height: 8 }))
|
|
26
|
+
.data((d) => ({ label: d.name }))
|
|
27
|
+
.join();
|
|
28
|
+
|
|
29
|
+
binding.transition()
|
|
30
|
+
.duration(400)
|
|
31
|
+
.delay((_d, i) => i * 20)
|
|
32
|
+
.pose((d) => ({ x: d.x * 2, y: d.y, width: 8, height: 8 }))
|
|
33
|
+
.transition() // starts on each item once the one before finishes it
|
|
34
|
+
.remove() // deletes each node when its transition ends
|
|
35
|
+
.end(); // a promise for the whole chain
|
|
19
36
|
```
|
|
20
37
|
|
|
38
|
+
`.transition()` needs the `animator` option. Force layouts come from
|
|
39
|
+
`useSimulation` in `@weasel-js/core` rather than this package. The site's
|
|
40
|
+
`ForceGraphDemo` and `D3SortableDemo` show both.
|
|
41
|
+
|
|
21
42
|
## License
|
|
22
43
|
|
|
23
44
|
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -20,16 +20,25 @@ interface BindOptions<TData, TPose> {
|
|
|
20
20
|
/**
|
|
21
21
|
* Builder produced by `d3Bind`. Configure with `.pose()` / `.data()` / `.enterFrom()`,
|
|
22
22
|
* then call `.join()` to emit the diff as one batched scene mutation and receive
|
|
23
|
-
* a chainable `D3Selection`.
|
|
23
|
+
* a chainable `D3Selection`. `TPayload` is the scene's node data type, which
|
|
24
|
+
* `d3Bind` infers from the scene it is given.
|
|
24
25
|
*/
|
|
25
|
-
interface D3Binding<TData, TPose
|
|
26
|
+
interface D3Binding<TData, TPose, TPayload = Record<string, unknown>> {
|
|
26
27
|
/** Per-datum pose. Called for both enter (initial scene pose) and update (target). */
|
|
27
28
|
pose(fn: (d: TData, i: number) => TPose): this;
|
|
28
29
|
/** Per-datum data payload. Replaces the leaf's existing data on update. */
|
|
29
|
-
data(fn: (d: TData, i: number) =>
|
|
30
|
+
data(fn: (d: TData, i: number) => TPayload): this;
|
|
30
31
|
/** Optional initial pose for ENTER nodes — `.transition()` will animate from this
|
|
31
32
|
* to the declared `.pose()`. Defaults to the same pose (entries snap in). */
|
|
32
33
|
enterFrom(fn: (d: TData, i: number) => TPose): this;
|
|
34
|
+
/** Take over the EXIT set (d3's `join(enter, update, exit)` exit callback).
|
|
35
|
+
* Without it, `.join()` removes exiting leaves in its own batch. With it,
|
|
36
|
+
* they stay in the scene and `fn` receives them as a selection whose data
|
|
37
|
+
* is each node's scene payload — typically to run
|
|
38
|
+
* `exit.transition()…remove().end()`. A key that comes back in a later
|
|
39
|
+
* join while its node is still exiting cancels that node's pending
|
|
40
|
+
* removal and rebinds it as an UPDATE. */
|
|
41
|
+
exit(fn: (exit: D3Selection<TPayload, TPose>) => void): this;
|
|
33
42
|
/** Emit the diff against the scene as one batched op group. Returns the merged
|
|
34
43
|
* selection (enter + update). Order of the selection matches the order of the
|
|
35
44
|
* bound `data` array. */
|
|
@@ -50,15 +59,26 @@ interface D3Selection<TData, TPose> {
|
|
|
50
59
|
/** Iterate. Returns this for chaining. */
|
|
51
60
|
each(fn: (d: TData, id: NodeId, i: number) => void): this;
|
|
52
61
|
/** Spawn a transition. Throws if `BindOptions.animator` wasn't supplied. */
|
|
53
|
-
transition(name?: string): D3Transition<TData>;
|
|
62
|
+
transition(name?: string): D3Transition<TData, TPose>;
|
|
54
63
|
/** Cancel any in-flight transitions matching `name` (or all if omitted) on these nodes. */
|
|
55
64
|
interrupt(name?: string): this;
|
|
56
65
|
}
|
|
57
|
-
/**
|
|
58
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Transition handle backed by the kit's `useAnimator`. Nothing runs until
|
|
68
|
+
* `.end()` is called on this transition or on any transition chained to it;
|
|
69
|
+
* starting one starts the whole chain.
|
|
70
|
+
*/
|
|
71
|
+
interface D3Transition<TData, TPose = unknown> {
|
|
59
72
|
duration(ms: number): this;
|
|
60
73
|
ease(fn: (t: number) => number): this;
|
|
74
|
+
/** Per-item delay. On a chained transition it counts from the moment that
|
|
75
|
+
* item's previous transition ended. */
|
|
61
76
|
delay(ms: number | ((d: TData, i: number) => number)): this;
|
|
77
|
+
/** Per-item target pose. Defaults to the pose the node holds when this
|
|
78
|
+
* transition starts — for the first transition, the pose `.join()` set, so
|
|
79
|
+
* it animates prior → joined; a chained transition without `.pose()` leaves
|
|
80
|
+
* the pose where the previous one put it. */
|
|
81
|
+
pose(fn: (d: TData, i: number) => TPose): this;
|
|
62
82
|
tween<TValue>(opts: {
|
|
63
83
|
name: string;
|
|
64
84
|
from: (d: TData, i: number) => TValue;
|
|
@@ -67,6 +87,15 @@ interface D3Transition<TData> {
|
|
|
67
87
|
apply: (d: TData, id: NodeId, value: TValue) => void;
|
|
68
88
|
}): this;
|
|
69
89
|
on(event: 'start' | 'end' | 'interrupt', fn: () => void): this;
|
|
90
|
+
/** A transition that starts on each item once this one finishes that item,
|
|
91
|
+
* with the same name and this one's duration and ease (d3's
|
|
92
|
+
* `transition.transition()`). Interrupting this one interrupts it too. */
|
|
93
|
+
transition(): D3Transition<TData, TPose>;
|
|
94
|
+
/** Remove each node from the scene when this transition ends on it (d3's
|
|
95
|
+
* `transition.remove()`), as an undoable delete. An interrupted node is
|
|
96
|
+
* not removed, nor one that left the scene mid-transition — even if
|
|
97
|
+
* another node has since taken its id. */
|
|
98
|
+
remove(): this;
|
|
70
99
|
end(): Promise<void>;
|
|
71
100
|
interrupt(): void;
|
|
72
101
|
}
|
|
@@ -78,11 +107,12 @@ interface D3Transition<TData> {
|
|
|
78
107
|
* Diff semantics (by `options.key`):
|
|
79
108
|
* - data has key not in scene → ENTER (scene.add)
|
|
80
109
|
* - data has key in scene → UPDATE (scene.setPose + scene.update)
|
|
81
|
-
* - scene has leaf on the target layer with key not in data → EXIT (scene.remove
|
|
110
|
+
* - scene has leaf on the target layer with key not in data → EXIT (scene.remove,
|
|
111
|
+
* or handed to the `.exit()` callback, which decides when they go)
|
|
82
112
|
*
|
|
83
113
|
* All mutations dispatch through `scene.batch('d3Bind.join', ...)` so one join
|
|
84
114
|
* is one undo entry.
|
|
85
115
|
*/
|
|
86
|
-
declare function d3Bind<TData, TLayer extends string, TPose
|
|
116
|
+
declare function d3Bind<TData, TLayer extends string, TPose, TPayload = Record<string, unknown>>(scene: Scene<TPayload, TLayer, TPose>, data: readonly TData[], options: BindOptions<TData, TPose>): D3Binding<TData, TPose, TPayload>;
|
|
87
117
|
|
|
88
118
|
export { type BindOptions, type D3Binding, type D3Selection, type D3Transition, d3Bind };
|
package/dist/index.js
CHANGED
|
@@ -21,100 +21,149 @@ function takeCustomKeys(ns) {
|
|
|
21
21
|
LIVE_CUSTOM_KEYS.delete(ns);
|
|
22
22
|
return [...set];
|
|
23
23
|
}
|
|
24
|
+
var PENDING_REMOVALS = /* @__PURE__ */ new WeakMap();
|
|
25
|
+
function pendingFor(scene) {
|
|
26
|
+
let map = PENDING_REMOVALS.get(scene);
|
|
27
|
+
if (!map) {
|
|
28
|
+
map = /* @__PURE__ */ new Map();
|
|
29
|
+
PENDING_REMOVALS.set(scene, map);
|
|
30
|
+
}
|
|
31
|
+
return map;
|
|
32
|
+
}
|
|
33
|
+
function unregisterRemoval(scene, id, cancel) {
|
|
34
|
+
const map = PENDING_REMOVALS.get(scene);
|
|
35
|
+
const set = map?.get(id);
|
|
36
|
+
if (!map || !set) return;
|
|
37
|
+
set.delete(cancel);
|
|
38
|
+
if (set.size === 0) map.delete(id);
|
|
39
|
+
}
|
|
40
|
+
function cancelPendingRemoval(scene, id) {
|
|
41
|
+
const set = PENDING_REMOVALS.get(scene)?.get(id);
|
|
42
|
+
if (!set) return;
|
|
43
|
+
for (const cancel of [...set]) cancel();
|
|
44
|
+
}
|
|
24
45
|
function createTransition(ctx) {
|
|
25
|
-
|
|
26
|
-
|
|
46
|
+
return makeStage(ctx, null, { duration: 250, easing: void 0 }).handle;
|
|
47
|
+
}
|
|
48
|
+
function makeStage(ctx, parent, inherited) {
|
|
49
|
+
const { scene, ids, data, priorPoses, name } = ctx;
|
|
50
|
+
let duration = inherited.duration;
|
|
51
|
+
let easing = inherited.easing;
|
|
27
52
|
let delayFn = null;
|
|
28
|
-
let
|
|
53
|
+
let poseFn = null;
|
|
29
54
|
const customTweens = [];
|
|
30
55
|
const onStart = [];
|
|
31
56
|
const onEnd = [];
|
|
32
57
|
const onInterrupt = [];
|
|
33
|
-
|
|
34
|
-
let
|
|
58
|
+
const children = [];
|
|
59
|
+
let removeOnEnd = false;
|
|
60
|
+
let removalArmed = false;
|
|
61
|
+
const removalCancels = [];
|
|
62
|
+
let scheduled = false;
|
|
63
|
+
let startFired = false;
|
|
64
|
+
let canceled = false;
|
|
35
65
|
let endResolved = false;
|
|
36
|
-
const
|
|
66
|
+
const itemDone = ids.map(() => false);
|
|
67
|
+
const itemWaiters = /* @__PURE__ */ new Map();
|
|
68
|
+
let itemsLeft = ids.length;
|
|
69
|
+
const joinedPoses = /* @__PURE__ */ new Map();
|
|
70
|
+
const spawnedCustom = /* @__PURE__ */ new Map();
|
|
37
71
|
let resolveEnd = null;
|
|
38
72
|
const endPromise = new Promise((resolve) => {
|
|
39
73
|
resolveEnd = resolve;
|
|
40
74
|
});
|
|
41
|
-
const handles =
|
|
42
|
-
const pendingDelays = /* @__PURE__ */ new
|
|
75
|
+
const handles = /* @__PURE__ */ new Map();
|
|
76
|
+
const pendingDelays = /* @__PURE__ */ new Map();
|
|
77
|
+
const stopItem = (i) => {
|
|
78
|
+
const timer = pendingDelays.get(i);
|
|
79
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
80
|
+
pendingDelays.delete(i);
|
|
81
|
+
for (const h of handles.get(i) ?? []) h.cancel();
|
|
82
|
+
handles.delete(i);
|
|
83
|
+
for (const [ns, key] of spawnedCustom.get(i) ?? []) untrackCustomKey(ns, key);
|
|
84
|
+
spawnedCustom.delete(i);
|
|
85
|
+
};
|
|
86
|
+
const pushTo = (map, i, v) => {
|
|
87
|
+
const list = map.get(i) ?? [];
|
|
88
|
+
list.push(v);
|
|
89
|
+
map.set(i, list);
|
|
90
|
+
};
|
|
43
91
|
const settleIfDone = () => {
|
|
44
|
-
if (
|
|
92
|
+
if (itemsLeft === 0 && !endResolved) {
|
|
45
93
|
endResolved = true;
|
|
46
|
-
if (!
|
|
94
|
+
if (!canceled) onEnd.forEach((fn) => fn());
|
|
47
95
|
resolveEnd?.();
|
|
48
96
|
}
|
|
49
97
|
};
|
|
50
|
-
const
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
98
|
+
const finishItem = (i) => {
|
|
99
|
+
if (canceled || itemDone[i]) return;
|
|
100
|
+
itemDone[i] = true;
|
|
101
|
+
itemsLeft--;
|
|
102
|
+
const waiters = itemWaiters.get(i);
|
|
103
|
+
itemWaiters.delete(i);
|
|
104
|
+
waiters?.forEach((fn) => fn());
|
|
105
|
+
settleIfDone();
|
|
106
|
+
};
|
|
107
|
+
const present = (i) => {
|
|
108
|
+
const token = scene.incarnation(ids[i]);
|
|
109
|
+
return token !== void 0 && token === ctx.incarnations.get(ids[i]);
|
|
110
|
+
};
|
|
111
|
+
const lost = (i) => {
|
|
112
|
+
if (present(i)) return false;
|
|
113
|
+
stage.root.cancelItem(i);
|
|
114
|
+
return true;
|
|
115
|
+
};
|
|
116
|
+
const runItem = (i) => {
|
|
117
|
+
if (canceled || lost(i)) return;
|
|
118
|
+
const id = ids[i];
|
|
119
|
+
const d = data[i];
|
|
120
|
+
let from;
|
|
121
|
+
let to;
|
|
122
|
+
if (parent) {
|
|
123
|
+
from = scene.get(id).pose;
|
|
124
|
+
to = poseFn ? poseFn(d, i) : void 0;
|
|
125
|
+
} else {
|
|
126
|
+
from = priorPoses.get(id);
|
|
127
|
+
const joined = joinedPoses.get(id);
|
|
128
|
+
if (from === void 0 || joined === void 0) return finishItem(i);
|
|
129
|
+
to = poseFn ? poseFn(d, i) : joined;
|
|
64
130
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
131
|
+
const jobs = [];
|
|
132
|
+
const ns = `d3-transition:${name}:${id}`;
|
|
133
|
+
const lerp = ctx.geometry.lerp;
|
|
134
|
+
if (to !== void 0) {
|
|
135
|
+
const poseFrom = from;
|
|
136
|
+
const poseTo = to;
|
|
137
|
+
jobs.push((done) => {
|
|
138
|
+
pushTo(
|
|
139
|
+
handles,
|
|
140
|
+
i,
|
|
141
|
+
ctx.animator.tween({
|
|
142
|
+
from: poseFrom,
|
|
143
|
+
to: poseTo,
|
|
144
|
+
ms: duration,
|
|
145
|
+
easing,
|
|
146
|
+
cancelKey: ns,
|
|
147
|
+
interpolate: (a, b, t2) => lerp(a, b, t2),
|
|
148
|
+
onTick: (pose) => {
|
|
149
|
+
if (!lost(i)) scene.setPose(id, pose);
|
|
150
|
+
},
|
|
151
|
+
onDone: done
|
|
152
|
+
})
|
|
153
|
+
);
|
|
154
|
+
});
|
|
68
155
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const handle = ctx.animator.tween({
|
|
81
|
-
from,
|
|
82
|
-
to,
|
|
83
|
-
ms: duration,
|
|
84
|
-
easing,
|
|
85
|
-
cancelKey: `d3-transition:${name}:${id}`,
|
|
86
|
-
interpolate: (a, b, t2) => lerp(a, b, t2),
|
|
87
|
-
onTick: (pose) => {
|
|
88
|
-
scene.setPose(id, pose);
|
|
89
|
-
},
|
|
90
|
-
onDone: () => {
|
|
91
|
-
pendingCount--;
|
|
92
|
-
settleIfDone();
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
handles.push(handle);
|
|
96
|
-
};
|
|
97
|
-
pendingCount++;
|
|
98
|
-
if (delayMs > 0) {
|
|
99
|
-
const timer = setTimeout(() => {
|
|
100
|
-
pendingDelays.delete(timer);
|
|
101
|
-
spawnPoseTween();
|
|
102
|
-
}, delayMs);
|
|
103
|
-
pendingDelays.add(timer);
|
|
104
|
-
} else {
|
|
105
|
-
spawnPoseTween();
|
|
106
|
-
}
|
|
107
|
-
for (const ct of customTweens) {
|
|
108
|
-
const fromVal = ct.from(d, i);
|
|
109
|
-
const toVal = ct.to(d, i);
|
|
110
|
-
const ns = `d3-transition:${name}:${id}`;
|
|
111
|
-
const customKey = `${ns}:${ct.name}`;
|
|
112
|
-
const spawnCustom = () => {
|
|
113
|
-
if (cancelled) return;
|
|
114
|
-
anySpawned = true;
|
|
115
|
-
trackCustomKey(ns, customKey);
|
|
116
|
-
spawnedCustom.push([ns, customKey]);
|
|
117
|
-
const handle = ctx.animator.tween({
|
|
156
|
+
for (const ct of customTweens) {
|
|
157
|
+
const fromVal = ct.from(d, i);
|
|
158
|
+
const toVal = ct.to(d, i);
|
|
159
|
+
const customKey = `${ns}:${ct.name}`;
|
|
160
|
+
jobs.push((done) => {
|
|
161
|
+
trackCustomKey(ns, customKey);
|
|
162
|
+
pushTo(spawnedCustom, i, [ns, customKey]);
|
|
163
|
+
pushTo(
|
|
164
|
+
handles,
|
|
165
|
+
i,
|
|
166
|
+
ctx.animator.tween({
|
|
118
167
|
from: fromVal,
|
|
119
168
|
to: toVal,
|
|
120
169
|
ms: duration,
|
|
@@ -129,33 +178,121 @@ function createTransition(ctx) {
|
|
|
129
178
|
`d3Bind.transition.tween("${ct.name}"): non-numeric value without an interpolate factory`
|
|
130
179
|
);
|
|
131
180
|
}),
|
|
132
|
-
onTick: (value) =>
|
|
181
|
+
onTick: (value) => {
|
|
182
|
+
if (!lost(i)) ct.apply(d, id, value);
|
|
183
|
+
},
|
|
133
184
|
onDone: () => {
|
|
134
185
|
untrackCustomKey(ns, customKey);
|
|
135
|
-
|
|
136
|
-
settleIfDone();
|
|
186
|
+
done();
|
|
137
187
|
}
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
pendingCount++;
|
|
142
|
-
if (delayMs > 0) {
|
|
143
|
-
const timer = setTimeout(() => {
|
|
144
|
-
pendingDelays.delete(timer);
|
|
145
|
-
spawnCustom();
|
|
146
|
-
}, delayMs);
|
|
147
|
-
pendingDelays.add(timer);
|
|
148
|
-
} else {
|
|
149
|
-
spawnCustom();
|
|
150
|
-
}
|
|
151
|
-
}
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
});
|
|
152
191
|
}
|
|
153
|
-
if (
|
|
192
|
+
if (jobs.length === 0) return finishItem(i);
|
|
193
|
+
if (!startFired) {
|
|
194
|
+
startFired = true;
|
|
154
195
|
onStart.forEach((fn) => fn());
|
|
196
|
+
}
|
|
197
|
+
let jobsLeft = jobs.length;
|
|
198
|
+
const jobDone = () => {
|
|
199
|
+
if (--jobsLeft === 0) finishItem(i);
|
|
200
|
+
};
|
|
201
|
+
const spawn = () => {
|
|
202
|
+
if (canceled) return;
|
|
203
|
+
for (const job of jobs) job(jobDone);
|
|
204
|
+
};
|
|
205
|
+
const delayMs = delayFn ? Math.max(0, delayFn(d, i)) : 0;
|
|
206
|
+
if (delayMs > 0) {
|
|
207
|
+
const timer = setTimeout(() => {
|
|
208
|
+
pendingDelays.delete(i);
|
|
209
|
+
spawn();
|
|
210
|
+
}, delayMs);
|
|
211
|
+
pendingDelays.set(i, timer);
|
|
155
212
|
} else {
|
|
213
|
+
spawn();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
const armRemoval = () => {
|
|
217
|
+
if (removalArmed || canceled) return;
|
|
218
|
+
removalArmed = true;
|
|
219
|
+
ids.forEach((id, i) => {
|
|
220
|
+
const cancel = () => stage.root.cancelItem(i);
|
|
221
|
+
removalCancels.push([id, cancel]);
|
|
222
|
+
const set = pendingFor(scene).get(id) ?? /* @__PURE__ */ new Set();
|
|
223
|
+
set.add(cancel);
|
|
224
|
+
pendingFor(scene).set(id, set);
|
|
225
|
+
stage.whenItemDone(i, () => {
|
|
226
|
+
unregisterRemoval(scene, id, cancel);
|
|
227
|
+
if (present(i)) scene.remove(id);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
};
|
|
231
|
+
const stage = {
|
|
232
|
+
root: void 0,
|
|
233
|
+
schedule(deferReady = false) {
|
|
234
|
+
if (scheduled || canceled) return;
|
|
235
|
+
scheduled = true;
|
|
236
|
+
if (removeOnEnd) armRemoval();
|
|
237
|
+
if (!ctx.geometry.lerp) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
"d3Bind.transition: provided geometry has no `lerp` \u2014 pass a PoseDescriptor with lerp in BindOptions"
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
if (parent) {
|
|
243
|
+
parent.schedule();
|
|
244
|
+
ids.forEach((_, i) => parent.whenItemDone(i, () => runItem(i), deferReady));
|
|
245
|
+
} else {
|
|
246
|
+
ids.forEach((id, i) => {
|
|
247
|
+
if (present(i)) joinedPoses.set(id, scene.get(id).pose);
|
|
248
|
+
});
|
|
249
|
+
ids.forEach((id, i) => {
|
|
250
|
+
const from = priorPoses.get(id);
|
|
251
|
+
if (from !== void 0 && present(i)) scene.setPose(id, from);
|
|
252
|
+
});
|
|
253
|
+
ids.forEach((_, i) => runItem(i));
|
|
254
|
+
}
|
|
255
|
+
settleIfDone();
|
|
256
|
+
for (const child of children) child.schedule();
|
|
257
|
+
},
|
|
258
|
+
whenItemDone(i, fn, defer = false) {
|
|
259
|
+
if (!itemDone[i]) {
|
|
260
|
+
const list = itemWaiters.get(i) ?? [];
|
|
261
|
+
list.push(fn);
|
|
262
|
+
itemWaiters.set(i, list);
|
|
263
|
+
} else if (defer) {
|
|
264
|
+
queueMicrotask(fn);
|
|
265
|
+
} else {
|
|
266
|
+
fn();
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
interrupt() {
|
|
270
|
+
if (!canceled && !endResolved) {
|
|
271
|
+
canceled = true;
|
|
272
|
+
ids.forEach((_, i) => stopItem(i));
|
|
273
|
+
itemWaiters.clear();
|
|
274
|
+
for (const [id, cancel] of removalCancels) unregisterRemoval(scene, id, cancel);
|
|
275
|
+
removalCancels.length = 0;
|
|
276
|
+
onInterrupt.forEach((fn) => fn());
|
|
277
|
+
endResolved = true;
|
|
278
|
+
resolveEnd?.();
|
|
279
|
+
}
|
|
280
|
+
for (const child of children) child.interrupt();
|
|
281
|
+
},
|
|
282
|
+
cancelItem(i) {
|
|
283
|
+
if (!canceled && !itemDone[i]) {
|
|
284
|
+
itemDone[i] = true;
|
|
285
|
+
itemsLeft--;
|
|
286
|
+
itemWaiters.delete(i);
|
|
287
|
+
stopItem(i);
|
|
288
|
+
}
|
|
289
|
+
const cancel = removalCancels.find(([id]) => id === ids[i])?.[1];
|
|
290
|
+
if (cancel) unregisterRemoval(scene, ids[i], cancel);
|
|
291
|
+
for (const child of children) child.cancelItem(i);
|
|
156
292
|
settleIfDone();
|
|
157
293
|
}
|
|
158
294
|
};
|
|
295
|
+
stage.root = parent ? parent.root : stage;
|
|
159
296
|
const t = {
|
|
160
297
|
duration(ms) {
|
|
161
298
|
duration = ms;
|
|
@@ -169,6 +306,10 @@ function createTransition(ctx) {
|
|
|
169
306
|
delayFn = typeof arg === "function" ? arg : () => arg;
|
|
170
307
|
return t;
|
|
171
308
|
},
|
|
309
|
+
pose(fn) {
|
|
310
|
+
poseFn = fn;
|
|
311
|
+
return t;
|
|
312
|
+
},
|
|
172
313
|
tween(opts) {
|
|
173
314
|
customTweens.push({
|
|
174
315
|
name: opts.name,
|
|
@@ -185,28 +326,27 @@ function createTransition(ctx) {
|
|
|
185
326
|
else if (event === "interrupt") onInterrupt.push(fn);
|
|
186
327
|
return t;
|
|
187
328
|
},
|
|
329
|
+
transition() {
|
|
330
|
+
const next = makeStage(ctx, stage, { duration, easing });
|
|
331
|
+
children.push(next.stage);
|
|
332
|
+
if (canceled) next.stage.interrupt();
|
|
333
|
+
else if (scheduled && !endResolved) next.stage.schedule(true);
|
|
334
|
+
return next.handle;
|
|
335
|
+
},
|
|
188
336
|
interrupt() {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
for (const [ns, key] of spawnedCustom) untrackCustomKey(ns, key);
|
|
196
|
-
spawnedCustom.length = 0;
|
|
197
|
-
onInterrupt.forEach((fn) => fn());
|
|
198
|
-
pendingCount = 0;
|
|
199
|
-
if (!endResolved) {
|
|
200
|
-
endResolved = true;
|
|
201
|
-
resolveEnd?.();
|
|
202
|
-
}
|
|
337
|
+
stage.interrupt();
|
|
338
|
+
},
|
|
339
|
+
remove() {
|
|
340
|
+
removeOnEnd = true;
|
|
341
|
+
if (scheduled) armRemoval();
|
|
342
|
+
return t;
|
|
203
343
|
},
|
|
204
344
|
end() {
|
|
205
|
-
|
|
345
|
+
stage.schedule();
|
|
206
346
|
return endPromise;
|
|
207
347
|
}
|
|
208
348
|
};
|
|
209
|
-
return t;
|
|
349
|
+
return { handle: t, stage };
|
|
210
350
|
}
|
|
211
351
|
|
|
212
352
|
// src/bind.ts
|
|
@@ -214,6 +354,7 @@ function d3Bind(scene, data, options) {
|
|
|
214
354
|
let poseFn = null;
|
|
215
355
|
let dataFn = null;
|
|
216
356
|
let enterFromFn = null;
|
|
357
|
+
let exitFn = null;
|
|
217
358
|
const binding = {
|
|
218
359
|
pose(fn) {
|
|
219
360
|
poseFn = fn;
|
|
@@ -227,6 +368,10 @@ function d3Bind(scene, data, options) {
|
|
|
227
368
|
enterFromFn = fn;
|
|
228
369
|
return binding;
|
|
229
370
|
},
|
|
371
|
+
exit(fn) {
|
|
372
|
+
exitFn = fn;
|
|
373
|
+
return binding;
|
|
374
|
+
},
|
|
230
375
|
join() {
|
|
231
376
|
const layer = options.layer ?? scene.layers[0]?.id;
|
|
232
377
|
if (!layer) {
|
|
@@ -248,6 +393,7 @@ function d3Bind(scene, data, options) {
|
|
|
248
393
|
for (const node of scene.nodesOnLayer(layer)) {
|
|
249
394
|
if (node.kind === "leaf" && !dataKeySet.has(node.id)) exitIds.push(node.id);
|
|
250
395
|
}
|
|
396
|
+
for (const id of [...dataKeys, ...exitIds]) cancelPendingRemoval(scene, id);
|
|
251
397
|
const priorPoses = /* @__PURE__ */ new Map();
|
|
252
398
|
for (const i of updateIndices) {
|
|
253
399
|
const id = dataKeys[i];
|
|
@@ -277,9 +423,6 @@ function d3Bind(scene, data, options) {
|
|
|
277
423
|
kind: "leaf",
|
|
278
424
|
layer,
|
|
279
425
|
pose,
|
|
280
|
-
// The scene is typed `Scene<unknown, TLayer, TPose>` — payload is
|
|
281
|
-
// unknown to the kit and consumer's drawOne reads it as a typed
|
|
282
|
-
// record.
|
|
283
426
|
data: payload
|
|
284
427
|
});
|
|
285
428
|
}
|
|
@@ -288,10 +431,29 @@ function d3Bind(scene, data, options) {
|
|
|
288
431
|
if (poseFn) scene.setPose(id, poseFn(data[i], i));
|
|
289
432
|
if (dataFn) scene.update(id, { data: dataFn(data[i], i) });
|
|
290
433
|
}
|
|
291
|
-
|
|
292
|
-
scene.remove(id);
|
|
434
|
+
if (!exitFn) {
|
|
435
|
+
for (const id of exitIds) scene.remove(id);
|
|
293
436
|
}
|
|
294
437
|
});
|
|
438
|
+
if (exitFn) {
|
|
439
|
+
const exitPoses = /* @__PURE__ */ new Map();
|
|
440
|
+
const exitData = [];
|
|
441
|
+
for (const id of exitIds) {
|
|
442
|
+
const node = scene.get(id);
|
|
443
|
+
exitPoses.set(id, node.pose);
|
|
444
|
+
exitData.push(node.data);
|
|
445
|
+
}
|
|
446
|
+
exitFn(
|
|
447
|
+
createSelection(
|
|
448
|
+
scene,
|
|
449
|
+
exitIds,
|
|
450
|
+
exitData,
|
|
451
|
+
exitPoses,
|
|
452
|
+
options.animator,
|
|
453
|
+
options.geometry
|
|
454
|
+
)
|
|
455
|
+
);
|
|
456
|
+
}
|
|
295
457
|
return createSelection(
|
|
296
458
|
scene,
|
|
297
459
|
dataKeys,
|
|
@@ -304,7 +466,9 @@ function d3Bind(scene, data, options) {
|
|
|
304
466
|
};
|
|
305
467
|
return binding;
|
|
306
468
|
}
|
|
307
|
-
function createSelection(scene, ids, data, priorPoses, animator, geometry
|
|
469
|
+
function createSelection(scene, ids, data, priorPoses, animator, geometry, incarnations = new Map(
|
|
470
|
+
ids.map((id) => [id, scene.incarnation(id)])
|
|
471
|
+
)) {
|
|
308
472
|
const sel = {
|
|
309
473
|
ids,
|
|
310
474
|
data,
|
|
@@ -317,7 +481,15 @@ function createSelection(scene, ids, data, priorPoses, animator, geometry) {
|
|
|
317
481
|
subData.push(data[i]);
|
|
318
482
|
}
|
|
319
483
|
}
|
|
320
|
-
return createSelection(
|
|
484
|
+
return createSelection(
|
|
485
|
+
scene,
|
|
486
|
+
subset,
|
|
487
|
+
subData,
|
|
488
|
+
priorPoses,
|
|
489
|
+
animator,
|
|
490
|
+
geometry,
|
|
491
|
+
incarnations
|
|
492
|
+
);
|
|
321
493
|
},
|
|
322
494
|
each(fn) {
|
|
323
495
|
for (let i = 0; i < ids.length; i++) fn(data[i], ids[i], i);
|
|
@@ -337,6 +509,7 @@ function createSelection(scene, ids, data, priorPoses, animator, geometry) {
|
|
|
337
509
|
ids,
|
|
338
510
|
data,
|
|
339
511
|
priorPoses,
|
|
512
|
+
incarnations,
|
|
340
513
|
name: name ?? ""
|
|
341
514
|
});
|
|
342
515
|
},
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,IAAA,CAAK,OAAA,CAAQ,CAAC,CAAA,EAAG,CAAA,KAAM;AACrB,QAAA,MAAM,QAAA,GAAW,KAAA,CAAM,GAAA,CAAI,QAAA,CAAS,CAAC,CAAC,CAAA;AACtC,QAAA,IAAI,YAAY,QAAA,CAAS,IAAA,KAAS,MAAA,IAAU,QAAA,CAAS,UAAU,KAAA,EAAO;AACpE,UAAA,aAAA,CAAc,KAAK,CAAC,CAAA;AAAA,QACtB,CAAA,MAAO;AACL,UAAA,YAAA,CAAa,KAAK,CAAC,CAAA;AAAA,QACrB;AAAA,MACF,CAAC,CAAA;AACD,MAAA,KAAA,MAAW,IAAA,IAAQ,KAAA,CAAM,YAAA,CAAa,KAAK,CAAA,EAAG;AAC5C,QAAA,IAAI,IAAA,CAAK,IAAA,KAAS,MAAA,IAAU,CAAC,UAAA,CAAW,GAAA,CAAI,IAAA,CAAK,EAAE,CAAA,EAAG,OAAA,CAAQ,IAAA,CAAK,IAAA,CAAK,EAAE,CAAA;AAAA,MAC5E;AAIA,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 PoseDescriptor,\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: PoseDescriptor<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 PoseDescriptor 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, PoseDescriptor, 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 data.forEach((_, i) => {\n const existing = scene.get(dataKeys[i]);\n if (existing && existing.kind === 'leaf' && existing.layer === layer) {\n updateIndices.push(i);\n } else {\n enterIndices.push(i);\n }\n });\n for (const node of scene.nodesOnLayer(layer)) {\n if (node.kind === 'leaf' && !dataKeySet.has(node.id)) exitIds.push(node.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: PoseDescriptor<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 PoseDescriptor<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"]}
|
|
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 PoseDescriptor,\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: PoseDescriptor<TPose>;\n ids: readonly NodeId[];\n data: readonly TData[];\n priorPoses: ReadonlyMap<NodeId, TPose>;\n /** `scene.incarnation` of each id when the selection was made: the nodes\n * this transition belongs to, as opposed to later ones reusing an id. */\n incarnations: ReadonlyMap<NodeId, number | undefined>;\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 on it or on a transition chained to it. This lets\n * the consumer chain `.duration().ease().delay()...` after `.transition()`\n * without having to declare them in advance.\n *\n * For each selected node, the first transition in a chain spawns one pose tween\n * from `priorPoses[id]` → the joined pose (or its `.pose()` target); a chained\n * transition tweens from wherever the previous one left the node, and only\n * when it has a `.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 `takeCustomKeys`.\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 canceled 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\n/** Per scene, the nodes a `.remove()` transition will delete when it ends,\n * each with the cancel that keeps it. Keyed weakly so a dropped scene takes\n * its entries with it. */\nconst PENDING_REMOVALS = new WeakMap<object, Map<NodeId, Set<() => void>>>();\n\nfunction pendingFor(scene: object): Map<NodeId, Set<() => void>> {\n let map = PENDING_REMOVALS.get(scene);\n if (!map) {\n map = new Map();\n PENDING_REMOVALS.set(scene, map);\n }\n return map;\n}\n\nfunction unregisterRemoval(scene: object, id: NodeId, cancel: () => void): void {\n const map = PENDING_REMOVALS.get(scene);\n const set = map?.get(id);\n if (!map || !set) return;\n set.delete(cancel);\n if (set.size === 0) map.delete(id);\n}\n\n/**\n * @internal Stop every transition chain that would remove `id` when it ends,\n * for that node only, so the node stays in the scene. The join calls this for\n * a key that re-enters while its node is still exiting (d3's rebinding of an\n * exiting element).\n */\nexport function cancelPendingRemoval(scene: object, id: NodeId): void {\n const set = PENDING_REMOVALS.get(scene)?.get(id);\n if (!set) return;\n for (const cancel of [...set]) cancel();\n}\n\n/** One transition in a chain, as the transition chained after it sees it. */\ninterface Stage {\n /** The first transition in this one's chain. */\n root: Stage;\n schedule(deferReady?: boolean): void;\n /** Run `fn` once item `i` has finished this transition. Never runs if the\n * transition is interrupted first. */\n whenItemDone(i: number, fn: () => void, defer?: boolean): void;\n interrupt(): void;\n /** Stop item `i` in this transition and every one chained after it, as if\n * it had finished without running what waits on it. */\n cancelItem(i: number): void;\n}\n\nexport function createTransition<TData, TPose>(\n ctx: TransitionCtx<TData, TPose>,\n): D3Transition<TData, TPose> {\n return makeStage(ctx, null, { duration: 250, easing: undefined }).handle;\n}\n\nfunction makeStage<TData, TPose>(\n ctx: TransitionCtx<TData, TPose>,\n parent: Stage | null,\n inherited: { duration: number; easing: EasingFn | undefined },\n): { handle: D3Transition<TData, TPose>; stage: Stage } {\n const { scene, ids, data, priorPoses, name } = ctx;\n let duration = inherited.duration;\n let easing = inherited.easing;\n let delayFn: ((d: TData, i: number) => number) | null = null;\n let poseFn: ((d: TData, i: number) => TPose) | null = null;\n const customTweens: Array<CustomTween<TData>> = [];\n const onStart: Array<() => void> = [];\n const onEnd: Array<() => void> = [];\n const onInterrupt: Array<() => void> = [];\n const children: Stage[] = [];\n let removeOnEnd = false;\n let removalArmed = false;\n const removalCancels: Array<[NodeId, () => void]> = [];\n\n let scheduled = false;\n let startFired = false;\n let canceled = false;\n let endResolved = false;\n const itemDone = ids.map(() => false);\n const itemWaiters = new Map<number, Array<() => void>>();\n let itemsLeft = ids.length;\n /** Where the first transition in a chain heads by default: the post-join pose. */\n const joinedPoses = new Map<NodeId, TPose>();\n /** (namespace, key) pairs this transition put in `LIVE_CUSTOM_KEYS`, per\n * item. `handle.cancel()` does not run a tween's `onDone`, so a cancel\n * drains these itself or the keys outlive their tweens. */\n const spawnedCustom = new Map<number, Array<[string, string]>>();\n let resolveEnd: (() => void) | null = null;\n const endPromise = new Promise<void>((resolve) => {\n resolveEnd = resolve;\n });\n const handles = new Map<number, AnimationHandle[]>();\n const pendingDelays = new Map<number, ReturnType<typeof setTimeout>>();\n\n const stopItem = (i: number): void => {\n const timer = pendingDelays.get(i);\n if (timer !== undefined) clearTimeout(timer);\n pendingDelays.delete(i);\n for (const h of handles.get(i) ?? []) h.cancel();\n handles.delete(i);\n for (const [ns, key] of spawnedCustom.get(i) ?? []) untrackCustomKey(ns, key);\n spawnedCustom.delete(i);\n };\n\n const pushTo = <V>(map: Map<number, V[]>, i: number, v: V): void => {\n const list = map.get(i) ?? [];\n list.push(v);\n map.set(i, list);\n };\n\n const settleIfDone = (): void => {\n if (itemsLeft === 0 && !endResolved) {\n endResolved = true;\n if (!canceled) onEnd.forEach((fn) => fn());\n resolveEnd?.();\n }\n };\n\n const finishItem = (i: number): void => {\n if (canceled || itemDone[i]) return;\n itemDone[i] = true;\n itemsLeft--;\n const waiters = itemWaiters.get(i);\n itemWaiters.delete(i);\n waiters?.forEach((fn) => fn());\n settleIfDone();\n };\n\n const present = (i: number): boolean => {\n const token = scene.incarnation(ids[i]);\n return token !== undefined && token === ctx.incarnations.get(ids[i]);\n };\n\n /** The scene can lose a node mid-tween (an undo, any outside `remove`), or\n * swap in another under the same id. Stop the item through the whole chain,\n * so neither a tween nor a pending `.remove()` touches the id again. */\n const lost = (i: number): boolean => {\n if (present(i)) return false;\n stage.root.cancelItem(i);\n return true;\n };\n\n const runItem = (i: number): void => {\n if (canceled || lost(i)) return;\n const id = ids[i];\n const d = data[i];\n let from: TPose | undefined;\n let to: TPose | undefined;\n if (parent) {\n from = scene.get(id)!.pose;\n to = poseFn ? poseFn(d, i) : undefined;\n } else {\n from = priorPoses.get(id);\n const joined = joinedPoses.get(id);\n if (from === undefined || joined === undefined) return finishItem(i);\n to = poseFn ? poseFn(d, i) : joined;\n }\n\n const jobs: Array<(done: () => void) => void> = [];\n const ns = `d3-transition:${name}:${id}`;\n const lerp = ctx.geometry.lerp!;\n if (to !== undefined) {\n const poseFrom = from;\n const poseTo = to;\n jobs.push((done) => {\n pushTo(\n handles,\n i,\n ctx.animator.tween<TPose>({\n from: poseFrom,\n to: poseTo,\n ms: duration,\n easing,\n cancelKey: ns,\n interpolate: (a, b, t) => lerp(a, b, t),\n onTick: (pose) => {\n if (!lost(i)) scene.setPose(id, pose);\n },\n onDone: done,\n }),\n );\n });\n }\n for (const ct of customTweens) {\n const fromVal = ct.from(d, i);\n const toVal = ct.to(d, i);\n const customKey = `${ns}:${ct.name}`;\n jobs.push((done) => {\n trackCustomKey(ns, customKey);\n pushTo(spawnedCustom, i, [ns, customKey] as [string, string]);\n pushTo(\n handles,\n i,\n 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) => {\n if (!lost(i)) ct.apply(d, id, value);\n },\n onDone: () => {\n untrackCustomKey(ns, customKey);\n done();\n },\n }),\n );\n });\n }\n\n if (jobs.length === 0) return finishItem(i);\n if (!startFired) {\n startFired = true;\n onStart.forEach((fn) => fn());\n }\n let jobsLeft = jobs.length;\n const jobDone = (): void => {\n if (--jobsLeft === 0) finishItem(i);\n };\n const spawn = (): void => {\n if (canceled) return;\n for (const job of jobs) job(jobDone);\n };\n const delayMs = delayFn ? Math.max(0, delayFn(d, i)) : 0;\n if (delayMs > 0) {\n const timer = setTimeout(() => {\n pendingDelays.delete(i);\n spawn();\n }, delayMs);\n pendingDelays.set(i, timer);\n } else {\n spawn();\n }\n };\n\n const armRemoval = (): void => {\n if (removalArmed || canceled) return;\n removalArmed = true;\n ids.forEach((id, i) => {\n const cancel = (): void => stage.root.cancelItem(i);\n removalCancels.push([id, cancel]);\n const set = pendingFor(scene).get(id) ?? new Set<() => void>();\n set.add(cancel);\n pendingFor(scene).set(id, set);\n stage.whenItemDone(i, () => {\n unregisterRemoval(scene, id, cancel);\n if (present(i)) scene.remove(id);\n });\n });\n };\n\n const stage: Stage = {\n root: undefined as unknown as Stage,\n schedule(deferReady = false) {\n if (scheduled || canceled) return;\n scheduled = true;\n if (removeOnEnd) armRemoval();\n if (!ctx.geometry.lerp) {\n throw new Error(\n 'd3Bind.transition: provided geometry has no `lerp` — pass a PoseDescriptor with lerp in BindOptions',\n );\n }\n if (parent) {\n parent.schedule();\n ids.forEach((_, i) => parent.whenItemDone(i, () => runItem(i), deferReady));\n } else {\n ids.forEach((id, i) => {\n if (present(i)) joinedPoses.set(id, scene.get(id)!.pose);\n });\n // Write every from-pose before any tween spawns, or non-delayed tweens\n // flash one frame of the joined pose and delayed ones hold it for the\n // whole delay. Costs one extra setPose per node in the undo log.\n ids.forEach((id, i) => {\n const from = priorPoses.get(id);\n if (from !== undefined && present(i)) scene.setPose(id, from);\n });\n ids.forEach((_, i) => runItem(i));\n }\n settleIfDone();\n for (const child of children) child.schedule();\n },\n whenItemDone(i, fn, defer = false) {\n if (!itemDone[i]) {\n const list = itemWaiters.get(i) ?? [];\n list.push(fn);\n itemWaiters.set(i, list);\n } else if (defer) {\n queueMicrotask(fn);\n } else {\n fn();\n }\n },\n interrupt() {\n if (!canceled && !endResolved) {\n canceled = true;\n ids.forEach((_, i) => stopItem(i));\n itemWaiters.clear();\n for (const [id, cancel] of removalCancels) unregisterRemoval(scene, id, cancel);\n removalCancels.length = 0;\n onInterrupt.forEach((fn) => fn());\n endResolved = true;\n resolveEnd?.();\n }\n for (const child of children) child.interrupt();\n },\n cancelItem(i) {\n if (!canceled && !itemDone[i]) {\n itemDone[i] = true;\n itemsLeft--;\n itemWaiters.delete(i);\n stopItem(i);\n }\n const cancel = removalCancels.find(([id]) => id === ids[i])?.[1];\n if (cancel) unregisterRemoval(scene, ids[i], cancel);\n for (const child of children) child.cancelItem(i);\n settleIfDone();\n },\n };\n stage.root = parent ? parent.root : stage;\n\n const t: D3Transition<TData, TPose> = {\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 pose(fn) {\n poseFn = fn;\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 transition() {\n const next = makeStage(ctx, stage, { duration, easing });\n children.push(next.stage);\n if (canceled) next.stage.interrupt();\n // Already in flight: queue behind it now, but let the caller finish\n // configuring before an item whose turn has already come runs.\n else if (scheduled && !endResolved) next.stage.schedule(true);\n return next.handle;\n },\n interrupt() {\n stage.interrupt();\n },\n remove() {\n removeOnEnd = true;\n if (scheduled) armRemoval();\n return t;\n },\n end() {\n stage.schedule();\n return endPromise;\n },\n };\n return { handle: t, stage };\n}\n","import { asNodeId, RECT_POSE_DESCRIPTOR } from '@weasel-js/core';\nimport type { Animator, NodeId, PoseDescriptor, Scene } from '@weasel-js/core';\nimport { cancelPendingRemoval, 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 * or handed to the `.exit()` callback, which decides when they go)\n *\n * All mutations dispatch through `scene.batch('d3Bind.join', ...)` so one join\n * is one undo entry.\n */\nexport function d3Bind<\n TData,\n TLayer extends string,\n TPose,\n TPayload = Record<string, unknown>,\n>(\n scene: Scene<TPayload, TLayer, TPose>,\n data: readonly TData[],\n options: BindOptions<TData, TPose>,\n): D3Binding<TData, TPose, TPayload> {\n let poseFn: ((d: TData, i: number) => TPose) | null = null;\n let dataFn: ((d: TData, i: number) => TPayload) | null = null;\n let enterFromFn: ((d: TData, i: number) => TPose) | null = null;\n let exitFn: ((exit: D3Selection<TPayload, TPose>) => void) | null = null;\n\n const binding: D3Binding<TData, TPose, TPayload> = {\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 exit(fn) {\n exitFn = 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 data.forEach((_, i) => {\n const existing = scene.get(dataKeys[i]);\n if (existing && existing.kind === 'leaf' && existing.layer === layer) {\n updateIndices.push(i);\n } else {\n enterIndices.push(i);\n }\n });\n for (const node of scene.nodesOnLayer(layer)) {\n if (node.kind === 'leaf' && !dataKeySet.has(node.id)) exitIds.push(node.id);\n }\n // A node already on its way out stops that exit here: re-entering keys\n // rebind as UPDATE, and still-absent ones exit afresh.\n for (const id of [...dataKeys, ...exitIds]) cancelPendingRemoval(scene, id);\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 TPayload);\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 data: payload,\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) });\n }\n if (!exitFn) {\n for (const id of exitIds) scene.remove(id);\n }\n });\n\n if (exitFn) {\n const exitPoses = new Map<NodeId, TPose>();\n const exitData: TPayload[] = [];\n for (const id of exitIds) {\n const node = scene.get(id)!;\n exitPoses.set(id, node.pose);\n exitData.push(node.data as TPayload);\n }\n exitFn(\n createSelection<TPayload, TPose>(\n scene,\n exitIds,\n exitData,\n exitPoses,\n options.animator,\n options.geometry,\n ),\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: PoseDescriptor<TPose> | undefined,\n incarnations: ReadonlyMap<NodeId, number | undefined> = new Map(\n ids.map((id) => [id, scene.incarnation(id)]),\n ),\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(\n scene, subset, subData, priorPoses, animator, geometry, incarnations,\n );\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 PoseDescriptor<TPose>));\n return createTransition({\n scene,\n animator,\n geometry: resolvedGeometry,\n ids,\n data,\n priorPoses,\n incarnations,\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.7.
|
|
3
|
+
"version": "1.7.2",
|
|
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",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"react": ">=18",
|
|
20
|
-
"@weasel-js/core": "1.7.
|
|
20
|
+
"@weasel-js/core": "1.7.2"
|
|
21
21
|
},
|
|
22
22
|
"peerDependenciesMeta": {
|
|
23
23
|
"d3-force": {
|