@weasel-js/animator 1.10.1 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3264 +1,8 @@
1
- import { useRef, useCallback, useMemo, useInsertionEffect, useEffect } from 'react';
2
- import { useLatest, useVisibleRaf } from '@weasel-js/react';
3
- import { isPlainObject, createEmitter } from '@weasel-js/registry';
4
- import { keys, patch, tween, glide, spring, mix, kit, vec, sum } from '@msb235/blits';
5
- import { EASINGS, resolveEasing, mat3, PATH_M, PATH_L, PATH_Z, reviveTypedArrays, tagTypedArrays, easeOut, SPRING_PRESETS, linear } from '@weasel-js/geom';
1
+ import { axisIndexes } from './chunk-H7WENFPC.js';
2
+ export { ColorOverrideRegistry, axesOf, createTarget, createTargetTable, sampleTrack } from './chunk-H7WENFPC.js';
3
+ import { EASINGS, resolveEasing, mat3, PATH_M, PATH_L, PATH_Z, reviveTypedArrays, tagTypedArrays, linear } from '@weasel-js/geom';
6
4
  import { hslToSrgb, lerpColorArray } from '@weasel-js/paint';
7
5
 
8
- // src/useAnimator.ts
9
-
10
- // src/axisNames.ts
11
- var LISTED = 8;
12
- function axisIndexes(axes, wanted, what = "animator: `axes`") {
13
- const { count, names } = axes;
14
- const out = [];
15
- for (const w of wanted) {
16
- if (typeof w === "number") {
17
- if (!Number.isInteger(w) || w < 0 || w >= count) throw new Error(`${what} names axis ${w} of a target with ${count}`);
18
- out.push(w);
19
- continue;
20
- }
21
- if (!names) throw new Error(`${what} names "${w}" on a target whose axes have no names`);
22
- const before = out.length;
23
- const under = `${w}.`;
24
- for (let i = 0; i < names.length; i++) {
25
- if (names[i] === w || names[i].startsWith(under)) out.push(i);
26
- }
27
- if (out.length === before) {
28
- const some = names.slice(0, LISTED).map((n) => `"${n}"`).join(", ");
29
- throw new Error(`${what} names "${w}", and the target's axes are ${some}${names.length > LISTED ? ", \u2026" : ""}`);
30
- }
31
- }
32
- return out;
33
- }
34
- function fieldByField(keys) {
35
- const [a, b, c, e] = keys;
36
- switch (keys.length) {
37
- case 1:
38
- return {
39
- add: (v, d) => ({ [a]: v[a] + d[0] }),
40
- write: (v, o, at) => {
41
- o[at] = v[a];
42
- }
43
- };
44
- case 2:
45
- return {
46
- add: (v, d) => ({ [a]: v[a] + d[0], [b]: v[b] + d[1] }),
47
- write: (v, o, at) => {
48
- o[at] = v[a];
49
- o[at + 1] = v[b];
50
- }
51
- };
52
- case 3:
53
- return {
54
- add: (v, d) => ({ [a]: v[a] + d[0], [b]: v[b] + d[1], [c]: v[c] + d[2] }),
55
- write: (v, o, at) => {
56
- o[at] = v[a];
57
- o[at + 1] = v[b];
58
- o[at + 2] = v[c];
59
- }
60
- };
61
- case 4:
62
- return {
63
- add: (v, d) => ({ [a]: v[a] + d[0], [b]: v[b] + d[1], [c]: v[c] + d[2], [e]: v[e] + d[3] }),
64
- write: (v, o, at) => {
65
- o[at] = v[a];
66
- o[at + 1] = v[b];
67
- o[at + 2] = v[c];
68
- o[at + 3] = v[e];
69
- }
70
- };
71
- default:
72
- return null;
73
- }
74
- }
75
- function axesOf(sample) {
76
- if (typeof sample === "number") {
77
- return {
78
- count: 1,
79
- shape: "number",
80
- to: (v) => [v],
81
- from: (a, at = 0) => a[at],
82
- add: (v, delta) => v + delta[0],
83
- write: (v, out, at) => {
84
- out[at] = v;
85
- }
86
- };
87
- }
88
- if (Array.isArray(sample)) {
89
- if (!sample.every((x) => typeof x === "number")) return null;
90
- return {
91
- count: sample.length,
92
- shape: `array:${sample.length}`,
93
- to: (v) => v.slice(),
94
- from: (a, at = 0) => {
95
- const out = new Array(sample.length);
96
- for (let i = 0; i < out.length; i++) out[i] = a[at + i];
97
- return out;
98
- },
99
- add: (v, delta) => {
100
- const out = new Array(sample.length);
101
- for (let i = 0; i < out.length; i++) out[i] = v[i] + delta[i];
102
- return out;
103
- },
104
- write: (v, out, at) => {
105
- for (let i = 0; i < sample.length; i++) out[at + i] = v[i];
106
- }
107
- };
108
- }
109
- if (!isPlainObject(sample)) return null;
110
- const keys = Object.keys(sample).sort();
111
- if (keys.length === 0 || !keys.every((k) => typeof sample[k] === "number")) return null;
112
- return {
113
- count: keys.length,
114
- shape: `object:${JSON.stringify(keys)}`,
115
- names: keys,
116
- to: (v) => keys.map((k) => v[k]),
117
- from: (a, at = 0) => {
118
- const out = {};
119
- for (let i = 0; i < keys.length; i++) out[keys[i]] = a[at + i];
120
- return out;
121
- },
122
- ...fieldByField(keys) ?? {
123
- add: (v, delta) => {
124
- const out = {};
125
- for (let i = 0; i < keys.length; i++) out[keys[i]] = v[keys[i]] + delta[i];
126
- return out;
127
- },
128
- write: (v, out, at) => {
129
- for (let i = 0; i < keys.length; i++) out[at + i] = v[keys[i]];
130
- }
131
- }
132
- };
133
- }
134
-
135
- // src/target.ts
136
- var states = /* @__PURE__ */ new WeakMap();
137
- var scratch = new Float64Array(16);
138
- function hidden(s) {
139
- const { cover } = s;
140
- if (s.behind.size === 0 || !cover && !s.dimmed) return null;
141
- const out = new Array(s.axes.count).fill(0);
142
- for (const b of s.behind) {
143
- const left = b.left();
144
- for (let i = 0; i < out.length; i++) {
145
- const shows = (cover ? 1 - cover[i] : 1) * (b.kept ? b.kept[i] : 1);
146
- out[i] = out[i] + left[i] * (1 - shows);
147
- }
148
- }
149
- return out;
150
- }
151
- function committedInto(s, out, at) {
152
- if (s.write) s.write(out, at);
153
- else if (s.axes.write) s.axes.write(s.get(), out, at);
154
- else {
155
- const a = s.axes.to(s.get());
156
- for (let i = 0; i < a.length; i++) out[at + i] = a[i];
157
- }
158
- }
159
- function heldAxes(s, committed) {
160
- const a = s.axes.to(committed);
161
- if (s.heldAt && a.some((x, i) => x !== s.heldAt[i])) s.owner?.reread(s);
162
- return a;
163
- }
164
- function readTarget(s) {
165
- const committed = s.get();
166
- if (s.live === 0) return committed;
167
- const { axes, slot, beside, adjust } = s;
168
- if (!axes.add || s.heldAt || s.dimmed) {
169
- const a = heldAxes(s, committed);
170
- const hid = hidden(s);
171
- for (let i = 0; i < a.length; i++) {
172
- a[i] = a[i] + (slot ? slot.cols[slot.at + i] : 0) + adjust[i] + (beside ? beside[i] : 0) - (hid ? hid[i] : 0);
173
- }
174
- return axes.from(a);
175
- }
176
- const n = axes.count;
177
- if (scratch.length < n) scratch = new Float64Array(n);
178
- const d = scratch;
179
- for (let i = 0; i < n; i++) {
180
- d[i] = (slot ? slot.cols[slot.at + i] : 0) + adjust[i] + (beside ? beside[i] : 0);
181
- }
182
- return axes.add(committed, d);
183
- }
184
- function readTargetInto(s, out, at) {
185
- const committed = s.get();
186
- const { axes, slot, beside, adjust } = s;
187
- const moving = s.live > 0;
188
- if (axes.write && !(moving && s.heldAt)) axes.write(committed, out, at);
189
- else {
190
- const a = moving ? heldAxes(s, committed) : axes.to(committed);
191
- for (let i = 0; i < a.length; i++) out[at + i] = a[i];
192
- }
193
- if (!moving) return;
194
- const hid = hidden(s);
195
- for (let i = 0; i < axes.count; i++) {
196
- out[at + i] = out[at + i] + (slot ? slot.cols[slot.at + i] : 0) + adjust[i] + (beside ? beside[i] : 0) - (hid ? hid[i] : 0);
197
- }
198
- }
199
- function isTarget(x) {
200
- return typeof x === "object" && x !== null && states.has(x);
201
- }
202
- function stateOf(target) {
203
- const s = states.get(target);
204
- if (!s) throw new Error("animator: `target` was not made by createTarget");
205
- return s;
206
- }
207
- function createTarget(opts) {
208
- const axes = opts.axes ?? axesOf(opts.get());
209
- if (!axes) {
210
- throw new Error("createTarget: `axes` is required for a value that is not a number, an array of numbers, or an object of numeric fields");
211
- }
212
- const s = {
213
- get: opts.get,
214
- set: opts.set,
215
- show: opts.show,
216
- publish: opts.publish,
217
- axes,
218
- write: null,
219
- owner: null,
220
- discard: false,
221
- heldAt: null,
222
- held: 0,
223
- cover: null,
224
- behind: /* @__PURE__ */ new Set(),
225
- dimmed: false,
226
- beside: null,
227
- row: null,
228
- live: 0,
229
- slot: null,
230
- adjust: new Array(axes.count).fill(0),
231
- stale: false
232
- };
233
- const target = {
234
- read: () => readTarget(s),
235
- readInto: (out, at = 0) => readTargetInto(s, out, at),
236
- axisCount: axes.count,
237
- axes,
238
- axisIndexes: (wanted) => axisIndexes(axes, wanted),
239
- committed: opts.get,
240
- commit: opts.set,
241
- get active() {
242
- return s.live > 0;
243
- },
244
- stop(o) {
245
- const owner = s.owner;
246
- if (!owner) return;
247
- s.discard = o?.commit === false;
248
- try {
249
- owner.stop(s);
250
- } finally {
251
- s.discard = false;
252
- }
253
- }
254
- };
255
- states.set(target, s);
256
- return target;
257
- }
258
-
259
- // src/targetTable.ts
260
- function put(into, n, row, value) {
261
- if (value.length !== n) throw new Error(`target table: a row is ${n} numbers, and this value is ${value.length}`);
262
- for (let k = 0; k < n; k++) into[row * n + k] = value[k];
263
- }
264
- function rowTarget(n, row, rows, committed, commit) {
265
- const target = createTarget({
266
- get: () => {
267
- const from = committed();
268
- const value = new Array(n);
269
- for (let k = 0; k < n; k++) value[k] = from[row * n + k];
270
- return value;
271
- },
272
- set: (value) => {
273
- committed();
274
- commit(row, value);
275
- }
276
- });
277
- const state = stateOf(target);
278
- state.row = { table: rows, index: row };
279
- state.write = (out, at) => {
280
- const from = committed();
281
- for (let k = 0; k < n; k++) out[at + k] = from[row * n + k];
282
- };
283
- return target;
284
- }
285
- function createTargetTable(opts) {
286
- const n = opts.axes;
287
- const Shown = opts.shown ?? Float64Array;
288
- if (!Number.isInteger(n) || n < 1) throw new Error("createTargetTable: `axes` is a whole number of at least 1");
289
- let committed = new Float64Array(Math.max(opts.capacity ?? 64, 1) * n);
290
- let shown = new Shown(committed.length);
291
- let high = 0;
292
- let size = 0;
293
- const free = [];
294
- const targets = [];
295
- const states2 = [];
296
- const removals = [];
297
- const marks = /* @__PURE__ */ new Set();
298
- const flush = () => {
299
- for (const row of marks) {
300
- const s = states2[row];
301
- if (s) readTargetInto(s, shown, row * n);
302
- }
303
- };
304
- const commit = (row, value) => {
305
- put(committed, n, row, value);
306
- const s = states2[row];
307
- if (s && s.live > 0) marks.add(row);
308
- else put(shown, n, row, value);
309
- opts.set?.(row);
310
- };
311
- const rows = {
312
- mark: (row) => {
313
- marks.add(row);
314
- },
315
- settle() {
316
- flush();
317
- for (const row of marks) {
318
- const s = states2[row];
319
- if (!s || s.live === 0 || !(s.beside || s.dimmed || s.held > 0 && s.behind.size > 0)) marks.delete(row);
320
- }
321
- },
322
- fill(columns) {
323
- if (columns) {
324
- const { cols, tags, count } = columns;
325
- for (let j = 0; j < count; j++) {
326
- const at = tags[j] * n;
327
- for (let k = 0; k < n; k++) shown[at + k] = committed[at + k] + cols[j * n + k];
328
- }
329
- }
330
- flush();
331
- opts.show?.();
332
- }
333
- };
334
- return {
335
- axes: n,
336
- get size() {
337
- return size;
338
- },
339
- get committed() {
340
- return committed;
341
- },
342
- get shown() {
343
- flush();
344
- return shown;
345
- },
346
- add(value) {
347
- const row = free.pop() ?? high++;
348
- if ((row + 1) * n > committed.length) {
349
- const grown = new Float64Array(Math.max(committed.length * 2, (row + 1) * n));
350
- grown.set(committed);
351
- committed = grown;
352
- const wider = new Shown(grown.length);
353
- wider.set(shown);
354
- shown = wider;
355
- }
356
- put(committed, n, row, value);
357
- put(shown, n, row, value);
358
- size += 1;
359
- return row;
360
- },
361
- remove(row) {
362
- targets[row]?.stop();
363
- targets[row] = void 0;
364
- removals[row] = (removals[row] ?? 0) + 1;
365
- states2[row] = void 0;
366
- marks.delete(row);
367
- free.push(row);
368
- size -= 1;
369
- },
370
- set: commit,
371
- target(row) {
372
- const held = targets[row];
373
- if (held) return held;
374
- const made = removals[row] ??= 0;
375
- const mine = () => {
376
- if (removals[row] !== made) throw new Error(`target table: row ${row} was removed, and this is its old target`);
377
- };
378
- const target = rowTarget(n, row, rows, () => {
379
- mine();
380
- return committed;
381
- }, commit);
382
- targets[row] = target;
383
- states2[row] = stateOf(target);
384
- return target;
385
- }
386
- };
387
- }
388
-
389
- // src/colorRegistry.ts
390
- var ColorOverrideRegistry = class {
391
- map = /* @__PURE__ */ new Map();
392
- slots = /* @__PURE__ */ new Map();
393
- tables = /* @__PURE__ */ new Map();
394
- _version = 0;
395
- set(id, channel, override) {
396
- let entry = this.map.get(id);
397
- if (!entry) {
398
- entry = {};
399
- this.map.set(id, entry);
400
- }
401
- entry[channel] = override;
402
- this._version++;
403
- }
404
- /** Drops `id`'s override on `channel`, and the colors a finished or canceled animation on its
405
- * {@link target} left painted. An animation still running goes on. */
406
- clear(id, channel) {
407
- this.release(id, channel);
408
- const entry = this.map.get(id);
409
- if (!entry) return;
410
- if (!(channel in entry)) return;
411
- delete entry[channel];
412
- if (!entry.fill && !entry.stroke) this.map.delete(id);
413
- this._version++;
414
- }
415
- clearAll() {
416
- for (const [id, node] of this.slots) {
417
- for (const channel of Object.keys(node)) this.release(id, channel);
418
- }
419
- if (this.map.size === 0) return;
420
- this.map.clear();
421
- this._version++;
422
- }
423
- /** What `set` stored for `id`'s `channel`; failing that, the colors its {@link target} paints. */
424
- get(id, channel) {
425
- const override = this.map.get(id)?.[channel];
426
- if (override !== void 0) return override;
427
- const slot = this.slots.get(id)?.[channel];
428
- return slot?.painted ? shownOf(slot) : void 0;
429
- }
430
- /** Whether anything overrides `id`, on either channel. */
431
- has(id) {
432
- return this.map.has(id) || this.slots.has(id);
433
- }
434
- /**
435
- * The {@link Target} for `id`'s colors on `channel`: `length` numbers, flat RGBA per anchor.
436
- * Every caller for one node and channel gets the same one, so their animations add. The
437
- * registry holds its committed colors and nothing writes the scene.
438
- *
439
- * What it paints: with no override from `set`, its colors in place of the painter's own; over
440
- * one, only what its animations have left to travel, added on top. Ask again for each
441
- * animation rather than keeping it: {@link release} and {@link clear} drop a resting one, and
442
- * the target it had throws from then on.
443
- */
444
- target(id, channel, length) {
445
- if (!(length > 0) || length % 4 !== 0) {
446
- throw new Error(`vertex colors: length ${length} must be a positive multiple of 4`);
447
- }
448
- let node = this.slots.get(id);
449
- const held = node?.[channel];
450
- if (held?.length === length) return held.table.target(held.row);
451
- if (held) {
452
- if (held.table.target(held.row).active) {
453
- throw new Error(`vertex colors: ${id}'s ${channel} is moving as ${held.length} numbers, not ${length}`);
454
- }
455
- this.drop(held);
456
- }
457
- const at = this.tableOf(length);
458
- const row = at.table.add(new Float64Array(length));
459
- const slot = { table: at.table, row, length, painted: false };
460
- at.rows[row] = slot;
461
- if (!node) {
462
- node = {};
463
- this.slots.set(id, node);
464
- }
465
- node[channel] = slot;
466
- return at.table.target(row);
467
- }
468
- tableOf(length) {
469
- const held = this.tables.get(length);
470
- if (held) return held;
471
- const rows = [];
472
- const table = createTargetTable({
473
- axes: length,
474
- set: (row) => {
475
- const slot = rows[row];
476
- if (slot) slot.painted = true;
477
- this._version++;
478
- },
479
- show: () => {
480
- this._version++;
481
- }
482
- });
483
- const made = { table, rows };
484
- this.tables.set(length, made);
485
- return made;
486
- }
487
- drop(slot) {
488
- const at = this.tables.get(slot.length);
489
- at.rows[slot.row] = void 0;
490
- slot.table.remove(slot.row);
491
- if (slot.table.size === 0) this.tables.delete(slot.length);
492
- }
493
- /** Drops the colors `id`'s {@link target} on `channel` left painted, unless an animation is
494
- * still moving it. An override from `set` stays. */
495
- release(id, channel) {
496
- const node = this.slots.get(id);
497
- const slot = node?.[channel];
498
- if (!node || !slot || slot.table.target(slot.row).active) return;
499
- delete node[channel];
500
- if (!node.fill && !node.stroke) this.slots.delete(id);
501
- this.drop(slot);
502
- if (slot.painted) this._version++;
503
- }
504
- /**
505
- * The colors to paint on `id`'s `channel`, given the colors the painter would
506
- * otherwise paint and the frame's timestamp. A function override needs that
507
- * `base` and must return an array of its length; failing either, `base`
508
- * stands. A {@link target} in motion then adds what it has left to travel.
509
- */
510
- resolve(id, channel, base, tMs) {
511
- const override = this.map.get(id)?.[channel];
512
- const slot = this.slots.get(id)?.[channel];
513
- if (override === void 0) return slot?.painted ? shownOf(slot) : base;
514
- const under = typeof override === "function" ? this.apply(override, base, tMs) : override;
515
- if (!slot?.painted || under?.length !== slot.length || !slot.table.target(slot.row).active) return under;
516
- const { shown, committed } = slot.table;
517
- const at = slot.row * slot.length;
518
- return under.map((x, i) => x + shown[at + i] - committed[at + i]);
519
- }
520
- apply(override, base, tMs) {
521
- if (base === void 0) return void 0;
522
- const result = override(base, tMs);
523
- return result.length === base.length ? result : base;
524
- }
525
- version() {
526
- return this._version;
527
- }
528
- };
529
- function shownOf(slot) {
530
- const { shown } = slot.table;
531
- const at = slot.row * slot.length;
532
- const out = new Array(slot.length);
533
- for (let i = 0; i < out.length; i++) out[i] = shown[at + i];
534
- return out;
535
- }
536
- var AnimatorEventHub = class {
537
- events = createEmitter();
538
- get watched() {
539
- return this.events.size > 0;
540
- }
541
- add(listener) {
542
- return this.events.subscribe(listener);
543
- }
544
- emit(event) {
545
- this.events.emit(event);
546
- }
547
- };
548
-
549
- // src/loop.ts
550
- function createLoop(createSupervisor, factory, opts = {}) {
551
- const max = opts.count ?? Infinity;
552
- let iteration = 0;
553
- let cancelled = false;
554
- let current = null;
555
- const supervisor = createSupervisor({ kind: "loop", cancelKey: opts.cancelKey, label: opts.label });
556
- supervisor.setOnCancel(() => {
557
- cancelled = true;
558
- current?.cancel();
559
- current = null;
560
- });
561
- const applyRate = (paused, timeScale) => {
562
- if (!current) return;
563
- if (paused) current.pause();
564
- else current.resume();
565
- current.setTimeScale(timeScale);
566
- };
567
- supervisor.setOnRate(applyRate);
568
- const next = () => {
569
- if (cancelled) return;
570
- if (iteration >= max) {
571
- current = null;
572
- cancelled = true;
573
- supervisor.finish();
574
- opts.onDone?.();
575
- return;
576
- }
577
- const i = iteration++;
578
- current = factory(i, next);
579
- if (supervisor.isPaused() || supervisor.timeScale() !== 1) {
580
- applyRate(supervisor.isPaused(), supervisor.timeScale());
581
- }
582
- };
583
- next();
584
- return {
585
- id: supervisor.id,
586
- cancel: () => supervisor.cancel(),
587
- pause: () => supervisor.pause(),
588
- resume: () => supervisor.resume(),
589
- setTimeScale: (s) => supervisor.setTimeScale(s),
590
- timeScale: () => supervisor.timeScale(),
591
- isPaused: () => supervisor.isPaused()
592
- };
593
- }
594
- function createTweenLoop(animator, createSupervisor, opts) {
595
- const direction = opts.direction ?? "restart";
596
- return createLoop(
597
- (o) => createSupervisor({ ...o, kind: "tweenLoop" }),
598
- (i, next) => {
599
- const flipped = direction === "reverse" || direction === "alternate" && i % 2 === 1;
600
- const from = flipped ? opts.to : opts.from;
601
- const to = flipped ? opts.from : opts.to;
602
- return animator.tween({
603
- from,
604
- to,
605
- ms: opts.ms,
606
- easing: opts.easing,
607
- interpolate: opts.interpolate,
608
- onTick: opts.onTick,
609
- onDone: next
610
- });
611
- },
612
- { count: opts.count, onDone: opts.onDone, cancelKey: opts.cancelKey, label: opts.label }
613
- );
614
- }
615
-
616
- // src/stagger.ts
617
- var CompositeHandle = class {
618
- constructor(supervisor, totalItems) {
619
- this.supervisor = supervisor;
620
- this.id = supervisor.id;
621
- this.outstanding = totalItems;
622
- supervisor.setOnCancel(() => this.handleSupervisorCancel());
623
- supervisor.setOnRate((paused, timeScale) => {
624
- if (paused) this.applyPause();
625
- else this.applyResume();
626
- this.applyTimeScale(timeScale);
627
- });
628
- supervisor.setOnFrame((virtualNow) => this.fireDue(virtualNow));
629
- }
630
- supervisor;
631
- id;
632
- cancelled = false;
633
- paused_ = false;
634
- timeScale_ = 1;
635
- children = [];
636
- pending = [];
637
- outstanding;
638
- finished = false;
639
- /** Invoked when the supervisor is cancelled externally (animator.cancel /
640
- * cancelKey) OR via this composite's own `cancel`. */
641
- handleSupervisorCancel() {
642
- if (this.cancelled) return;
643
- this.cancelled = true;
644
- this.pending = [];
645
- const kids = this.children.slice();
646
- this.children = [];
647
- for (const c of kids) c.cancel();
648
- }
649
- noteChildDone() {
650
- if (this.cancelled || this.finished) return;
651
- this.outstanding -= 1;
652
- if (this.outstanding <= 0) {
653
- this.finished = true;
654
- this.children = [];
655
- this.supervisor.finish();
656
- }
657
- }
658
- addChild(h) {
659
- if (this.cancelled) {
660
- h.cancel();
661
- return;
662
- }
663
- this.children.push(h);
664
- if (this.paused_) h.pause();
665
- if (this.timeScale_ !== 1) h.setTimeScale(this.timeScale_);
666
- }
667
- schedule(delayMs, fire) {
668
- if (this.cancelled) return;
669
- this.pending.push({ at: delayMs, fire });
670
- }
671
- fireDue(virtualNow) {
672
- if (this.cancelled || this.pending.length === 0) return;
673
- const due = this.pending.filter((p) => p.at <= virtualNow).sort((a, b) => a.at - b.at);
674
- this.pending = this.pending.filter((p) => p.at > virtualNow);
675
- for (const p of due) {
676
- if (this.cancelled) return;
677
- p.fire();
678
- }
679
- }
680
- cancel() {
681
- this.supervisor.cancel();
682
- }
683
- pause() {
684
- this.supervisor.pause();
685
- }
686
- resume() {
687
- this.supervisor.resume();
688
- }
689
- setTimeScale(s) {
690
- this.supervisor.setTimeScale(s);
691
- }
692
- applyPause() {
693
- if (this.paused_) return;
694
- this.paused_ = true;
695
- for (const c of this.children) c.pause();
696
- }
697
- applyResume() {
698
- if (!this.paused_) return;
699
- this.paused_ = false;
700
- for (const c of this.children) c.resume();
701
- }
702
- applyTimeScale(s) {
703
- if (s === this.timeScale_) return;
704
- this.timeScale_ = s;
705
- for (const c of this.children) c.setTimeScale(s);
706
- }
707
- timeScale() {
708
- return this.timeScale_;
709
- }
710
- isPaused() {
711
- return this.paused_;
712
- }
713
- };
714
- function delayFor(delay, i) {
715
- return typeof delay === "function" ? delay(i) : delay * i;
716
- }
717
- function createStagger(animator, createSupervisor, watchCompletion, items, delay, factory, opts) {
718
- if (factory) return runStagger(createSupervisor, watchCompletion, items, delay, factory, opts);
719
- return makeBuilder(animator, createSupervisor, watchCompletion, items, delay);
720
- }
721
- function runStagger(createSupervisor, watchCompletion, items, delay, factory, opts) {
722
- const supervisor = createSupervisor({ kind: "stagger", cancelKey: opts?.cancelKey, label: opts?.label });
723
- const composite = new CompositeHandle(supervisor, items.length);
724
- if (items.length === 0) {
725
- supervisor.finish();
726
- return composite;
727
- }
728
- items.forEach((item, i) => {
729
- const ms = delayFor(delay, i);
730
- const fire = () => {
731
- const child = factory(item, i);
732
- watchCompletion(child.id, () => composite.noteChildDone());
733
- composite.addChild(child);
734
- };
735
- if (ms <= 0) fire();
736
- else composite.schedule(ms, fire);
737
- });
738
- return composite;
739
- }
740
- function resolvePerItem(v, item, i) {
741
- return typeof v === "function" ? v(item, i) : v;
742
- }
743
- function perItem(opts, item, i) {
744
- const target = opts.target !== void 0 ? resolvePerItem(opts.target, item, i) : isTarget(item) ? item : null;
745
- return {
746
- target,
747
- from: opts.from === void 0 ? void 0 : resolvePerItem(opts.from, item, i),
748
- to: resolvePerItem(opts.to, item, i),
749
- onTick: opts.onTick && ((v) => opts.onTick(v, item, i)),
750
- onDone: opts.onDone && (() => opts.onDone(item, i))
751
- };
752
- }
753
- function makeBuilder(animator, createSupervisor, watchCompletion, items, delay) {
754
- const run = (method, opts, start) => {
755
- if (opts.target === void 0 && !opts.onTick && !items.every(isTarget)) {
756
- throw new Error(`animator.stagger: \`.${method}\` needs a \`target\`, items that are targets, or an \`onTick\``);
757
- }
758
- return runStagger(createSupervisor, watchCompletion, items, delay, (item, i) => start(perItem(opts, item, i), item, i), opts);
759
- };
760
- return {
761
- each: (factory, opts) => runStagger(createSupervisor, watchCompletion, items, delay, factory, opts),
762
- tween: ((opts) => run("tween", opts, ({ target, ...own }, item, i) => {
763
- const shared = {
764
- ...own,
765
- ms: resolvePerItem(opts.ms, item, i),
766
- easing: opts.easing,
767
- interpolate: opts.interpolate,
768
- interpolator: opts.interpolator
769
- };
770
- return target ? animator.tween({ ...shared, target }) : animator.tween(shared);
771
- })),
772
- spring: ((opts) => run("spring", opts, ({ target, ...own }, item, i) => {
773
- const shared = {
774
- ...own,
775
- velocity: opts.velocity === void 0 ? void 0 : resolvePerItem(opts.velocity, item, i),
776
- preset: opts.preset,
777
- stiffness: opts.stiffness,
778
- damping: opts.damping,
779
- mass: opts.mass,
780
- restThreshold: opts.restThreshold
781
- };
782
- if (target) return animator.spring({ ...shared, target });
783
- const { interpolate, add, subtract, scale, magnitude } = opts;
784
- return animator.spring({ ...shared, interpolate, add, subtract, scale, magnitude });
785
- }))
786
- };
787
- }
788
-
789
- // src/supervisor.ts
790
- function supervisors(host) {
791
- return ({ kind, cancelKey, label }) => {
792
- const id = host.nextId();
793
- let onCancelCb;
794
- let onRateCb;
795
- let onFrameCb;
796
- const base = host.register({
797
- id,
798
- kind,
799
- cancelKey,
800
- label,
801
- tick: (virtualNow) => {
802
- if (onFrameCb) host.afterTicks(() => onFrameCb?.(virtualNow));
803
- return false;
804
- },
805
- onCancel: () => onCancelCb?.(),
806
- onRate: (paused, timeScale) => onRateCb?.(paused, timeScale)
807
- });
808
- return {
809
- id: base.id,
810
- cancel: base.cancel,
811
- pause: base.pause,
812
- resume: base.resume,
813
- setTimeScale: base.setTimeScale,
814
- timeScale: base.timeScale,
815
- isPaused: base.isPaused,
816
- setOnCancel: (cb) => {
817
- onCancelCb = cb;
818
- },
819
- setOnRate: (cb) => {
820
- onRateCb = cb;
821
- },
822
- setOnFrame: (cb) => {
823
- onFrameCb = cb;
824
- },
825
- finish: () => host.finish(id),
826
- cancelKey
827
- };
828
- };
829
- }
830
-
831
- // src/timeline/booking.ts
832
- var RESYNC_MS = 50;
833
- var SLEW = 0.05;
834
- var RETIME_MS = 1;
835
- function firstAfter(events, t) {
836
- let lo = 0;
837
- let hi = events.length;
838
- while (lo < hi) {
839
- const mid = lo + hi >> 1;
840
- if (events[mid].t > t) hi = mid;
841
- else lo = mid + 1;
842
- }
843
- return lo;
844
- }
845
- function firstAtOrAfter(events, t) {
846
- let i = firstAfter(events, t);
847
- while (i > 0 && events[i - 1].t === t) i -= 1;
848
- return i;
849
- }
850
- var isAfter = (a, b) => a.lap > b.lap || a.lap === b.lap && a.r > b.r;
851
- function createBooker(opts, host) {
852
- const lookahead = opts.lookahead ?? 100;
853
- const maxLate = opts.maxLate ?? Infinity;
854
- const cursors = /* @__PURE__ */ new Map();
855
- let start = { lap: 0, r: -Infinity };
856
- let bookings = [];
857
- let est = null;
858
- let mapVirtual = 0;
859
- let lastScale = 0;
860
- const playheadCursor = () => ({ lap: host.lap(), r: host.playhead() });
861
- const unwrapped = (b) => (b.lap - host.lap()) * host.duration() + b.r;
862
- const ahead = (b) => isAfter(b, playheadCursor());
863
- const stopWhere = (now, pred) => {
864
- bookings = bookings.filter((b) => {
865
- if (!b.handle || b.when <= now || !ahead(b) || !pred(b)) return true;
866
- try {
867
- b.handle.stop();
868
- } catch (err) {
869
- console.error("timeline: booking stop threw", err);
870
- }
871
- return false;
872
- });
873
- const at = playheadCursor();
874
- for (const [track, c] of cursors) if (isAfter(c, at)) cursors.set(track, at);
875
- if (isAfter(start, at)) start = at;
876
- };
877
- const whenFor = (u, playhead, scale, now) => Math.max(est + (u - playhead) / scale, now);
878
- const scanTrack = (track, at, horizon, lastLap, now, scale) => {
879
- const lap = host.lap();
880
- const duration = host.duration();
881
- const playhead = host.playhead();
882
- const { events } = track;
883
- let c = cursors.get(track) ?? start;
884
- for (; ; ) {
885
- const lapStart = (c.lap - lap) * duration;
886
- if (lapStart > horizon) break;
887
- const i = (c.open ? firstAtOrAfter : firstAfter)(events, c.r - at);
888
- if (i >= events.length || at + events[i].t > duration) {
889
- if (c.lap >= lastLap) break;
890
- c = { lap: Math.max(c.lap + 1, lap), r: -Infinity };
891
- continue;
892
- }
893
- const r = at + events[i].t;
894
- const u = lapStart + r;
895
- if (u > horizon) break;
896
- c = { lap: c.lap, r };
897
- for (let j = i; j < events.length && events[j].t === events[i].t; j += 1) {
898
- const ev = events[j];
899
- if (!ev.book || bookings.some((b) => b.event === ev && b.lap === c.lap)) continue;
900
- if ((playhead - u) / scale > maxLate) continue;
901
- const when = whenFor(u, playhead, scale, now);
902
- let handle;
903
- try {
904
- handle = ev.book(when) ?? void 0;
905
- } catch (err) {
906
- console.error("timeline: book threw", err);
907
- }
908
- bookings.push({ event: ev, lap: c.lap, r, when, handle });
909
- }
910
- }
911
- cursors.set(track, c);
912
- };
913
- const visit = (tracks, at, horizon, lastLap, now, scale) => {
914
- for (const track of tracks) {
915
- if (track.kind === "event") scanTrack(track, at, horizon, lastLap, now, scale);
916
- else if (track.kind === "timeline") {
917
- visit(track.timeline.tracks, at + track.at, horizon, lastLap, now, scale);
918
- }
919
- }
920
- };
921
- return {
922
- tick(virtualNow, scale) {
923
- const now = opts.clock.now();
924
- bookings = bookings.filter(ahead);
925
- if (!(scale > 0)) {
926
- if (lastScale > 0) stopWhere(now, () => true);
927
- lastScale = 0;
928
- est = null;
929
- return;
930
- }
931
- let moved = lastScale > 0 && scale !== lastScale;
932
- if (est === null) {
933
- est = now;
934
- } else {
935
- est += (virtualNow - mapVirtual) / scale;
936
- const residual = now - est;
937
- if (Math.abs(residual) > RESYNC_MS) {
938
- est = now;
939
- moved = true;
940
- } else {
941
- est += residual * SLEW;
942
- }
943
- }
944
- mapVirtual = virtualNow;
945
- lastScale = scale;
946
- const playhead = host.playhead();
947
- if (moved) {
948
- stopWhere(now, (b) => Math.abs(whenFor(unwrapped(b), playhead, scale, now) - b.when) > RETIME_MS);
949
- }
950
- const duration = host.duration();
951
- const lastLap = host.lap() + (duration > 0 ? host.lapsLeft() : 0);
952
- visit(host.tracks, 0, playhead + lookahead * scale, lastLap, now, scale);
953
- },
954
- retract() {
955
- stopWhere(opts.clock.now(), () => true);
956
- },
957
- seek() {
958
- bookings = [];
959
- cursors.clear();
960
- start = { ...playheadCursor(), open: true };
961
- },
962
- rearm() {
963
- est = null;
964
- }
965
- };
966
- }
967
-
968
- // src/timeline/fireEvents.ts
969
- function createEventFirer(report, booked) {
970
- const path = [];
971
- let warnedUnbooked = false;
972
- const fire = (tracks, from, to, fromOpen) => {
973
- for (let k = 0; k < tracks.length; k += 1) {
974
- const track = tracks[k];
975
- if (track.kind === "event") {
976
- const end = firstAfter(track.events, to);
977
- const first = (fromOpen ? firstAtOrAfter : firstAfter)(track.events, from);
978
- for (let i = first; i < end; i += 1) {
979
- const ev = track.events[i];
980
- ev.fire?.(to - ev.t);
981
- if (report?.watched) {
982
- path.push(k);
983
- report.fire(track, path, ev, to - ev.t);
984
- path.pop();
985
- }
986
- if (ev.book && !booked && !warnedUnbooked) {
987
- warnedUnbooked = true;
988
- console.warn("timeline: an event has `book` but the timeline has no `booking` clock; it never books.");
989
- }
990
- }
991
- } else if (track.kind === "timeline") {
992
- path.push(k);
993
- fire(track.timeline.tracks, from - track.at, to - track.at, fromOpen);
994
- path.pop();
995
- }
996
- }
997
- };
998
- return fire;
999
- }
1000
-
1001
- // src/engine/offsets.ts
1002
- var TAU = Math.PI * 2;
1003
- function axesFor(axes, value, what) {
1004
- const a = axes.to(value);
1005
- if (a.length !== axes.count || !a.every((x) => typeof x === "number" && !Number.isNaN(x))) {
1006
- throw new Error(`animator: \`${what}\` is not the target's shape`);
1007
- }
1008
- return a;
1009
- }
1010
- function shortWay(x) {
1011
- x %= TAU;
1012
- if (x > Math.PI) return x - TAU;
1013
- if (x <= -Math.PI) return x + TAU;
1014
- return x;
1015
- }
1016
- function travel(axes, a, b) {
1017
- const d = a.map((x, i) => x - b[i]);
1018
- for (const i of axes.angles ?? []) d[i] = shortWay(d[i]);
1019
- return d;
1020
- }
1021
- var plus = (a, b) => a.map((x, i) => x + b[i]);
1022
- var minus = (a, b) => a.map((x, i) => x - b[i]);
1023
- var norm = (a) => Math.hypot(...a);
1024
- function bump(s, delta, sign) {
1025
- for (let i = 0; i < delta.length; i++) s.adjust[i] = s.adjust[i] + sign * delta[i];
1026
- stale(s);
1027
- s.row?.table.mark(s.row.index);
1028
- }
1029
- function stale(s) {
1030
- if (s.stale || !s.owner) return;
1031
- s.stale = true;
1032
- s.owner.adjusted(s);
1033
- }
1034
- function absorb(s, delta) {
1035
- if (s.discard) return;
1036
- s.set(s.axes.from(plus(s.axes.to(s.get()), delta)));
1037
- }
1038
- function shareBehind(axes, committed, to, start) {
1039
- return travel(axes, committed, to).map((m, i) => start[i] === 0 ? 0 : m / start[i]);
1040
- }
1041
- var times = (a, b) => a.map((x, i) => x * b[i]);
1042
- function goBehind(s, left) {
1043
- const b = { left, kept: null };
1044
- s.behind.add(b);
1045
- return b;
1046
- }
1047
- function comeOut(s, b, left) {
1048
- s.behind.delete(b);
1049
- if (b.kept) {
1050
- s.dimmed = false;
1051
- for (const other of s.behind) if (other.kept) s.dimmed = true;
1052
- }
1053
- return b.kept ? times(left, b.kept) : left.slice();
1054
- }
1055
- function commitOffset(s, own, left, shown) {
1056
- const top = minus(own, left);
1057
- if (s.held === 0) {
1058
- keep(s, plus(shown, top));
1059
- return;
1060
- }
1061
- if (shown.some((x) => x !== 0)) absorb(s, shown);
1062
- if (top.some((x) => x !== 0)) keep(s, top);
1063
- }
1064
- function keep(s, delta) {
1065
- if (s.discard) return;
1066
- if (s.held === 0) absorb(s, delta);
1067
- else s.beside = s.beside ? plus(s.beside, delta) : delta.slice();
1068
- s.row?.table.mark(s.row.index);
1069
- }
1070
- function takeUp(s) {
1071
- const committed = s.axes.to(s.get());
1072
- const beside = s.beside;
1073
- s.beside = null;
1074
- return beside ? plus(committed, beside) : committed;
1075
- }
1076
- function createTargetBook(owner) {
1077
- const adjusted = [];
1078
- const token = { ...owner, adjusted: (s) => {
1079
- adjusted.push(s);
1080
- } };
1081
- const showing = /* @__PURE__ */ new Set();
1082
- const rested = /* @__PURE__ */ new Set();
1083
- const publishes = /* @__PURE__ */ new Set();
1084
- const tables = /* @__PURE__ */ new Map();
1085
- const shown = (s, value) => {
1086
- s.show(value);
1087
- if (s.publish) publishes.add(s.publish);
1088
- };
1089
- const publish = () => {
1090
- for (const p of publishes) p();
1091
- publishes.clear();
1092
- };
1093
- return {
1094
- attach(s) {
1095
- if (s.owner && s.owner !== token) {
1096
- throw new Error("animator: this target is being moved by another animator");
1097
- }
1098
- s.owner = token;
1099
- s.live += 1;
1100
- if (s.show) showing.add(s);
1101
- rested.delete(s);
1102
- if (s.row) {
1103
- tables.set(s.row.table, (tables.get(s.row.table) ?? 0) + 1);
1104
- s.row.table.mark(s.row.index);
1105
- }
1106
- },
1107
- detach(s) {
1108
- s.live -= 1;
1109
- if (s.row) {
1110
- tables.set(s.row.table, tables.get(s.row.table) - 1);
1111
- s.row.table.mark(s.row.index);
1112
- }
1113
- if (s.live > 0) return;
1114
- s.owner = null;
1115
- s.slot = null;
1116
- s.beside = null;
1117
- s.behind.clear();
1118
- s.dimmed = false;
1119
- s.adjust.fill(0);
1120
- s.stale = false;
1121
- if (s.show && showing.delete(s)) rested.add(s);
1122
- },
1123
- settle() {
1124
- for (const s of adjusted) {
1125
- if (!s.stale || s.owner !== token) continue;
1126
- s.adjust.fill(0);
1127
- s.stale = false;
1128
- }
1129
- adjusted.length = 0;
1130
- for (const t of tables.keys()) t.settle();
1131
- },
1132
- reread(s, queued) {
1133
- for (let i = 0; i < queued.length; i++) s.adjust[i] = queued[i];
1134
- stale(s);
1135
- s.row?.table.mark(s.row.index);
1136
- },
1137
- show() {
1138
- for (const s of showing) shown(s, readTarget(s));
1139
- for (const s of rested) shown(s, s.get());
1140
- rested.clear();
1141
- for (const [t, moving] of tables) {
1142
- t.fill(owner.columns(t));
1143
- if (moving === 0) tables.delete(t);
1144
- }
1145
- publish();
1146
- },
1147
- showNow(s) {
1148
- if (!s.show) return;
1149
- shown(s, readTarget(s));
1150
- publish();
1151
- },
1152
- get resting() {
1153
- return rested.size > 0;
1154
- }
1155
- };
1156
- }
1157
-
1158
- // src/timeline/targetStops.ts
1159
- function targetStops(track) {
1160
- const { axes } = stateOf(track.target);
1161
- const stops = [];
1162
- for (const k of track.keys) {
1163
- const row = axes.to(k.value);
1164
- if (row.length !== axes.count || !row.every((x) => typeof x === "number" && !Number.isNaN(x))) {
1165
- throw new Error("sampleTrack: a key's value is not its target's shape");
1166
- }
1167
- const prev = stops[stops.length - 1];
1168
- if (prev && axes.angles) {
1169
- const short = travel(axes, row, prev);
1170
- for (const i of axes.angles) if (short[i] !== row[i] - prev[i]) row[i] = prev[i] + short[i];
1171
- }
1172
- stops.push(row);
1173
- }
1174
- return { axes, stops };
1175
- }
1176
-
1177
- // src/timeline/sampleTrack.ts
1178
- var NO_SETTING = {};
1179
- var BUILT = -1;
1180
- function build(track, cache) {
1181
- const { keys: keys$1 } = track;
1182
- let t0 = Infinity;
1183
- let t1 = -Infinity;
1184
- const exact = /* @__PURE__ */ new Map();
1185
- for (const k of keys$1) {
1186
- t0 = Math.min(t0, k.t);
1187
- t1 = Math.max(t1, k.t);
1188
- exact.set(k.t, k.value);
1189
- }
1190
- const span = t1 - t0;
1191
- const out = { track, keys: keys$1, patch: null, t0, span, exact, fresh: null, first: keys$1[0].value, last: keys$1[keys$1.length - 1].value, read: (v) => v };
1192
- const { interpolator, interpolate } = track;
1193
- const onTarget = track.target && !interpolator && !interpolate ? targetStops(track) : null;
1194
- if (onTarget) {
1195
- const { axes: axes2, stops } = onTarget;
1196
- out.fresh = new Map(keys$1.map((k, i) => [k.t, () => axes2.from(stops[i])]));
1197
- out.first = axes2.from(stops[0]);
1198
- out.last = axes2.from(stops[stops.length - 1]);
1199
- }
1200
- if (span <= 0) return out;
1201
- const ease = (k) => k.easing ? resolveEasing(k.easing) : void 0;
1202
- if (onTarget) {
1203
- const { axes: axes2, stops } = onTarget;
1204
- out.patch = keys(
1205
- 1,
1206
- keys$1.map((k, i) => ({ at: (k.t - t0) / span, delta: { v: stops[i] }, ease: ease(k) }))
1207
- );
1208
- out.read = (v) => axes2.from(v);
1209
- return out;
1210
- }
1211
- if (interpolator || interpolate) {
1212
- const boxes = keys$1.map((k, i) => ({ i, value: k.value }));
1213
- const lerp = (a, b, u) => {
1214
- if (interpolator) {
1215
- let fn = cache?.get(b.i);
1216
- if (!fn) {
1217
- fn = interpolator(a.value, b.value);
1218
- cache?.set(b.i, fn);
1219
- }
1220
- return { i: -1, value: fn(u) };
1221
- }
1222
- return { i: -1, value: interpolate(a.value, b.value, u) };
1223
- };
1224
- out.patch = keys(
1225
- 1,
1226
- keys$1.map((k, i) => ({ at: (k.t - t0) / span, delta: { v: boxes[i] }, ease: ease(k) })),
1227
- { lerpBy: () => lerp }
1228
- );
1229
- out.read = (v) => v.value;
1230
- out.fresh = /* @__PURE__ */ new Map();
1231
- for (let i = 0; i < keys$1.length - 1; i++) {
1232
- const at = keys$1[i].t;
1233
- if (at > t0 && at < t1) out.fresh.set(at, () => lerp(boxes[i], boxes[i + 1], 0).value);
1234
- }
1235
- return out;
1236
- }
1237
- const axes = axesOf(keys$1[0].value);
1238
- if (!axes) return out;
1239
- for (const k of keys$1) {
1240
- if (axesOf(k.value)?.shape !== axes.shape) {
1241
- throw new Error(`sampleTrack: every key needs the shape of the first (${axes.shape})`);
1242
- }
1243
- }
1244
- const scalar = axes.shape === "number";
1245
- out.patch = keys(
1246
- 1,
1247
- keys$1.map((k) => ({ at: (k.t - t0) / span, delta: { v: scalar ? k.value : axes.to(k.value) }, ease: ease(k) }))
1248
- );
1249
- if (!scalar) out.read = (v) => axes.from(v);
1250
- return out;
1251
- }
1252
- function sampleTrack(track, t, segmentCache) {
1253
- if (track.keys.length === 0) return void 0;
1254
- const cache = segmentCache;
1255
- let b = cache?.get(BUILT);
1256
- if (!b || b.track !== track || b.keys !== track.keys) {
1257
- cache?.clear();
1258
- b = build(track, segmentCache);
1259
- cache?.set(BUILT, b);
1260
- }
1261
- if (Number.isNaN(t)) return b.first;
1262
- const fresh = b.fresh?.get(t);
1263
- if (fresh) return fresh();
1264
- const hit = b.exact.get(t);
1265
- if (hit !== void 0 || b.exact.has(t)) return hit;
1266
- if (!b.patch) {
1267
- if (t < b.t0) return b.first;
1268
- if (t >= b.t0 + b.span) return b.last;
1269
- throw new Error("sampleTrack: interpolate or interpolator is required for non-numeric keyframe values");
1270
- }
1271
- return b.read(b.patch.at((t - b.t0) / b.span, 0, NO_SETTING).v);
1272
- }
1273
-
1274
- // src/timeline/trackHolds.ts
1275
- function createTrackSampler(open) {
1276
- let caches = /* @__PURE__ */ new WeakMap();
1277
- const cacheFor = (track) => {
1278
- let c = caches.get(track);
1279
- if (!c) {
1280
- c = /* @__PURE__ */ new Map();
1281
- caches.set(track, c);
1282
- }
1283
- return c;
1284
- };
1285
- const held = /* @__PURE__ */ new Map();
1286
- let edit = 0;
1287
- let pass = 0;
1288
- const openOn = (st, h) => {
1289
- if (!open) throw new Error("timeline: a track with a `target` needs a timeline made by an animator");
1290
- const w = st.weight ?? 1;
1291
- return open(st.target, () => h.value, typeof w === "function" ? w : () => w, st.axes);
1292
- };
1293
- const place = (st, value, t) => {
1294
- let h = held.get(st);
1295
- if (!h) held.set(st, h = { hold: null, value, where: null, t, edit, pass });
1296
- const moved = h.t !== t || h.edit !== edit;
1297
- h.value = value;
1298
- h.t = t;
1299
- h.edit = edit;
1300
- h.pass = pass;
1301
- const { keys } = st;
1302
- const where = t < keys[0].t ? "before" : t > keys[keys.length - 1].t ? "after" : "inside";
1303
- if (where === "inside") {
1304
- if (!h.hold) h.hold = openOn(st, h);
1305
- else if (moved) h.hold.touch();
1306
- } else if (where === "before") {
1307
- h.hold?.leave(false);
1308
- h.hold = null;
1309
- } else if (h.where !== "after") {
1310
- (h.hold ?? openOn(st, h)).leave(true);
1311
- h.hold = null;
1312
- }
1313
- h.where = where;
1314
- };
1315
- const visit = (tracks, t) => {
1316
- for (const track of tracks) {
1317
- if (track.kind === "sampled") {
1318
- const st = track;
1319
- const v = sampleTrack(st, t, cacheFor(st));
1320
- if (v === void 0) continue;
1321
- if (st.target) place(st, v, t);
1322
- st.onTick?.(v);
1323
- } else if (track.kind === "timeline") {
1324
- visit(track.timeline.tracks, t - track.at);
1325
- }
1326
- }
1327
- };
1328
- return {
1329
- apply(tracks, t) {
1330
- pass += 1;
1331
- visit(tracks, t);
1332
- for (const [st, h] of held) {
1333
- if (h.pass === pass) continue;
1334
- h.hold?.leave(true);
1335
- held.delete(st);
1336
- }
1337
- },
1338
- edited() {
1339
- caches = /* @__PURE__ */ new WeakMap();
1340
- edit += 1;
1341
- },
1342
- release() {
1343
- for (const h of held.values()) {
1344
- if (!h.hold) continue;
1345
- h.hold.leave(true);
1346
- h.hold = null;
1347
- h.where = null;
1348
- }
1349
- }
1350
- };
1351
- }
1352
-
1353
- // src/timeline/createTimeline.ts
1354
- function trackEnd(track) {
1355
- switch (track.kind) {
1356
- case "sampled":
1357
- return track.keys.length ? track.keys[track.keys.length - 1].t : 0;
1358
- case "event":
1359
- return track.events.length ? track.events[track.events.length - 1].t : 0;
1360
- case "timeline":
1361
- return track.at + tracksEnd(track.timeline.tracks, track.timeline.duration);
1362
- }
1363
- }
1364
- function tracksEnd(tracks, explicit) {
1365
- if (explicit != null) return explicit;
1366
- let max = 0;
1367
- for (const t of tracks) max = Math.max(max, trackEnd(t));
1368
- return max;
1369
- }
1370
- function crossLaps(playhead, duration, loopsLeft, take) {
1371
- if (duration <= 0 || playhead < duration) return;
1372
- const laps = Math.min(Math.floor(playhead / duration), loopsLeft);
1373
- if (laps > 0) take(laps);
1374
- }
1375
- function createTimeline(register, id, opts, report, hold) {
1376
- let duration = tracksEnd(opts.tracks, opts.duration);
1377
- let offset = 0;
1378
- let lastVirtual = 0;
1379
- let playhead = 0;
1380
- let prevPlayhead = -Infinity;
1381
- let prevOpen = false;
1382
- let held = false;
1383
- let done = false;
1384
- let live = true;
1385
- let cancelled = false;
1386
- let wantPaused = opts.autoplay === false;
1387
- let wantScale = 1;
1388
- const edited = createEmitter();
1389
- const playback = createEmitter();
1390
- const playbackChanged = playback.emit;
1391
- const loopOpt = opts.loop ?? false;
1392
- let loopsLeft = loopOpt === true ? Infinity : loopOpt === false ? 0 : loopOpt;
1393
- let lap = 0;
1394
- const booker = opts.booking ? createBooker(opts.booking, {
1395
- tracks: opts.tracks,
1396
- duration: () => duration,
1397
- lapsLeft: () => loopsLeft,
1398
- lap: () => lap,
1399
- playhead: () => playhead
1400
- }) : null;
1401
- const sampler = createTrackSampler(hold);
1402
- const fireEvents = createEventFirer(report, booker !== null);
1403
- const onWrap = () => {
1404
- if (!held) fireEvents(opts.tracks, prevPlayhead, duration, prevOpen);
1405
- prevPlayhead = -Infinity;
1406
- prevOpen = false;
1407
- if (report?.watched) report.lap(lap);
1408
- };
1409
- const tick = (virtualNow, scale) => {
1410
- lastVirtual = virtualNow;
1411
- const rate = scale ?? (base.isPaused() ? 0 : base.timeScale());
1412
- held = rate === 0;
1413
- const parked = held && duration > 0 && playhead === duration && loopsLeft > 0;
1414
- if (!parked) {
1415
- playhead = virtualNow + offset;
1416
- crossLaps(playhead, duration, loopsLeft, (laps) => {
1417
- loopsLeft -= laps;
1418
- offset -= laps * duration;
1419
- playhead -= laps * duration;
1420
- lap += laps;
1421
- onWrap();
1422
- });
1423
- }
1424
- const finished = playhead >= duration && !parked;
1425
- playhead = Math.min(playhead, duration);
1426
- sampler.apply(opts.tracks, playhead);
1427
- if (!held) {
1428
- fireEvents(opts.tracks, prevPlayhead, playhead, prevOpen);
1429
- prevPlayhead = playhead;
1430
- prevOpen = false;
1431
- }
1432
- booker?.tick(virtualNow, rate);
1433
- if (finished && !done) {
1434
- done = true;
1435
- opts.onDone?.();
1436
- playbackChanged();
1437
- }
1438
- if (playhead < duration || parked) return false;
1439
- live = false;
1440
- sampler.release();
1441
- return true;
1442
- };
1443
- const onCancel = () => {
1444
- const first = !cancelled;
1445
- cancelled = true;
1446
- live = false;
1447
- booker?.retract();
1448
- sampler.release();
1449
- if (first) playbackChanged();
1450
- };
1451
- const seed = { id, cancelKey: opts.cancelKey, tick, onCancel, kind: "timeline", label: opts.label, progress: () => duration > 0 ? playhead / duration : 1 };
1452
- const base = register(seed);
1453
- if (opts.autoplay === false) base.pause();
1454
- const rearm = () => {
1455
- if (cancelled || playhead >= duration) return;
1456
- done = false;
1457
- if (live) return;
1458
- booker?.rearm();
1459
- lastVirtual = 0;
1460
- offset = playhead;
1461
- live = true;
1462
- register({ ...seed, keepExisting: true });
1463
- if (wantPaused) base.pause();
1464
- base.setTimeScale(wantScale);
1465
- };
1466
- const sampleNow = () => {
1467
- if (live && playhead >= 0 && playhead < duration) sampler.apply(opts.tracks, playhead);
1468
- };
1469
- return {
1470
- ...base,
1471
- // Not just `onCancel`: cancelling a finished timeline never reaches the
1472
- // animator, and must still keep a later seek from reviving it.
1473
- cancel() {
1474
- onCancel();
1475
- base.cancel();
1476
- },
1477
- // Playback intent is tracked here, not read back from the entry: a revived
1478
- // one is a fresh registration, and defaults to running at scale 1.
1479
- pause() {
1480
- wantPaused = true;
1481
- base.pause();
1482
- booker?.retract();
1483
- playbackChanged();
1484
- },
1485
- resume() {
1486
- wantPaused = false;
1487
- base.resume();
1488
- playbackChanged();
1489
- },
1490
- setTimeScale(s) {
1491
- wantScale = s;
1492
- base.setTimeScale(s);
1493
- playbackChanged();
1494
- },
1495
- timeScale: () => live ? base.timeScale() : wantScale,
1496
- isPaused: () => live ? base.isPaused() : wantPaused,
1497
- seek(t) {
1498
- booker?.retract();
1499
- offset = t - lastVirtual;
1500
- playhead = t;
1501
- prevPlayhead = t;
1502
- prevOpen = true;
1503
- booker?.seek();
1504
- rearm();
1505
- sampleNow();
1506
- playbackChanged();
1507
- },
1508
- time: () => playhead,
1509
- duration: () => duration,
1510
- tracks: () => opts.tracks,
1511
- setLoop(loop) {
1512
- loopsLeft = loop === true ? Infinity : loop === false ? 0 : loop;
1513
- booker?.retract();
1514
- playbackChanged();
1515
- },
1516
- loop: () => loopsLeft === Infinity ? true : loopsLeft === 0 ? false : loopsLeft,
1517
- edit(fn) {
1518
- fn();
1519
- sampler.edited();
1520
- duration = tracksEnd(opts.tracks, opts.duration);
1521
- booker?.retract();
1522
- rearm();
1523
- sampleNow();
1524
- edited.emit();
1525
- },
1526
- subscribe: edited.subscribe,
1527
- subscribePlayback: playback.subscribe
1528
- };
1529
- }
1530
-
1531
- // src/engine/decay.ts
1532
- function decayAsPhysics(o) {
1533
- const shared = {
1534
- from: o.from,
1535
- to: null,
1536
- velocity: o.velocity,
1537
- stiffness: 0,
1538
- damping: -Math.log(o.friction ?? 0.95),
1539
- mass: 1,
1540
- restThreshold: o.threshold ?? 0.5,
1541
- onTick: o.onTick,
1542
- onDone: o.onDone,
1543
- cancelKey: o.cancelKey,
1544
- label: o.label
1545
- };
1546
- if (o.target) return { ...shared, target: o.target, interrupt: o.interrupt };
1547
- return {
1548
- ...shared,
1549
- from: o.from,
1550
- onTick: o.onTick,
1551
- add: o.add,
1552
- // DecayOptions doesn't carry subtract; derive from add+scale.
1553
- subtract: (a, b) => o.add(a, o.scale(b, -1)),
1554
- scale: o.scale,
1555
- magnitude: o.magnitude
1556
- };
1557
- }
1558
-
1559
- // src/engine/holds.ts
1560
- var groups = /* @__PURE__ */ new WeakMap();
1561
- var share = (w) => w > 0 ? Math.min(w, 1) : 0;
1562
- function fold(group, committed, skip, into) {
1563
- const n = committed.length;
1564
- const delta = into ? into.delta : new Array(n);
1565
- const total = into ? into.total : new Array(n);
1566
- const { members, angle } = group;
1567
- for (let h = 0; h < members.length; h++) {
1568
- const m = members[h];
1569
- m.w = m === skip ? 0 : m.weight();
1570
- if (m.w > 0) m.v = m.value();
1571
- }
1572
- for (let a = 0; a < n; a++) {
1573
- const c = committed[a];
1574
- let held = 0;
1575
- let sum2 = 0;
1576
- let sin = 0;
1577
- let cos = 0;
1578
- for (let h = 0; h < members.length; h++) {
1579
- const m = members[h];
1580
- if (m === skip || !(m.w > 0) || m.covers && !m.covers[a]) continue;
1581
- held += m.w;
1582
- if (!angle[a]) sum2 += m.w * (m.v[a] - c);
1583
- else {
1584
- sin += m.w * Math.sin(m.v[a]);
1585
- cos += m.w * Math.cos(m.v[a]);
1586
- }
1587
- }
1588
- total[a] = held;
1589
- if (held > 0 && angle[a]) {
1590
- const mean = Math.hypot(sin, cos) < 1e-9 ? null : Math.atan2(sin, cos);
1591
- for (let h = 0; h < members.length; h++) {
1592
- const m = members[h];
1593
- if (m === skip || !(m.w > 0) || m.covers && !m.covers[a]) continue;
1594
- sum2 += m.w * (mean === null ? shortWay(m.v[a] - c) : shortWay(mean - c) + shortWay(m.v[a] - mean));
1595
- }
1596
- }
1597
- delta[a] = held > 0 ? sum2 / Math.max(1, held) : 0;
1598
- }
1599
- return into ?? { delta, total };
1600
- }
1601
- function settle(group, leaving, committed) {
1602
- const all = fold(group, committed);
1603
- const rest = fold(group, committed, leaving);
1604
- const own = leaving.value().slice();
1605
- return committed.map((c, a) => {
1606
- if (leaving.covers && !leaving.covers[a]) return 0;
1607
- if (rest.total[a] >= 1 - 1e-9) return group.angle[a] ? shortWay(own[a] - c) : own[a] - c;
1608
- return (all.delta[a] - rest.delta[a]) / (1 - rest.total[a]);
1609
- });
1610
- }
1611
- function dim(s, before, after) {
1612
- for (const b of s.behind) {
1613
- for (let a = 0; a < before.length; a++) {
1614
- const open = 1 - Math.min(after[a], 1);
1615
- const was = 1 - Math.min(before[a], 1);
1616
- if (open < 1e-9 || open === was) continue;
1617
- b.kept ??= new Array(before.length).fill(1);
1618
- b.kept[a] = b.kept[a] * was / open;
1619
- s.dimmed = true;
1620
- }
1621
- }
1622
- }
1623
- function coverOf(wanted, axes) {
1624
- if (!wanted) return null;
1625
- const covers = new Uint8Array(axes.count);
1626
- for (const a of axisIndexes(axes, wanted)) covers[a] = 1;
1627
- return covers;
1628
- }
1629
- function openHold(ctx, o) {
1630
- const s = o.target;
1631
- const ax = s.axes;
1632
- const state = s;
1633
- axesFor(ax, o.value(), "value");
1634
- let gone = false;
1635
- const buffer = new Array(ax.count).fill(0);
1636
- const member = {
1637
- value: ax.write ? () => {
1638
- ax.write(o.value(), buffer, 0);
1639
- return buffer;
1640
- } : () => ax.to(o.value()),
1641
- weight: () => gone ? 0 : share(o.weight()),
1642
- covers: coverOf(o.axes, ax),
1643
- fixed: o.fixed === true,
1644
- w: 0,
1645
- v: buffer
1646
- };
1647
- ctx.targets.attach(state);
1648
- s.held += 1;
1649
- let group = groups.get(state);
1650
- if (!group) {
1651
- const angle = new Uint8Array(ax.count);
1652
- for (const a of ax.angles ?? []) angle[a] = 1;
1653
- const made = { members: [], voice: ctx.nextId(), angle };
1654
- const folded = { delta: [], total: new Array(ax.count).fill(0) };
1655
- groups.set(state, group = made);
1656
- ctx.seedClock();
1657
- s.slot = ctx.codec.hold({
1658
- id: made.voice,
1659
- on: s,
1660
- axes: ax.count,
1661
- still: member.fixed,
1662
- at: () => {
1663
- const committed = s.heldAt ??= new Array(ax.count).fill(0);
1664
- committedInto(s, committed, 0);
1665
- folded.delta = new Array(ax.count);
1666
- const { delta, total } = fold(made, committed, void 0, folded);
1667
- const cover = s.cover ??= new Array(ax.count).fill(0);
1668
- for (let a = 0; a < cover.length; a++) cover[a] = Math.min(total[a], 1);
1669
- return delta;
1670
- }
1671
- });
1672
- }
1673
- const { members, voice } = group;
1674
- members.push(member);
1675
- const still = () => ctx.codec.still(voice, members.every((m) => m.fixed));
1676
- still();
1677
- ctx.touch(state, true);
1678
- return {
1679
- touch: () => {
1680
- if (!gone) ctx.touch(state);
1681
- },
1682
- leave(commit) {
1683
- if (gone) return;
1684
- if (commit && !s.discard) {
1685
- const group2 = groups.get(state);
1686
- const committed = ax.to(s.get());
1687
- absorb(s, settle(group2, member, committed));
1688
- dim(state, fold(group2, committed).total, fold(group2, committed, member).total);
1689
- }
1690
- gone = true;
1691
- members.splice(members.indexOf(member), 1);
1692
- s.held -= 1;
1693
- if (s.held === 0) {
1694
- s.heldAt = null;
1695
- s.cover = null;
1696
- groups.delete(state);
1697
- ctx.codec.stop(voice);
1698
- const beside = s.beside;
1699
- s.beside = null;
1700
- if (beside) absorb(s, beside);
1701
- } else still();
1702
- ctx.targets.detach(state);
1703
- ctx.touch(state, true);
1704
- }
1705
- };
1706
- }
1707
-
1708
- // src/engine/holdAnimation.ts
1709
- var isFn = (x) => typeof x === "function";
1710
- function startHold(ctx, o) {
1711
- const target = stateOf(o.target);
1712
- const id = ctx.nextId();
1713
- const { value, weight = 1 } = o;
1714
- const fixed = !isFn(value) && !isFn(weight);
1715
- const seed = {
1716
- id,
1717
- kind: "hold",
1718
- cancelKey: o.cancelKey,
1719
- label: o.label,
1720
- target,
1721
- keepExisting: o.interrupt === false,
1722
- idle: fixed,
1723
- // It leaves when released or canceled, and has nothing to do on a frame.
1724
- due: Infinity
1725
- };
1726
- if (o.cancelKey != null && o.interrupt !== false) ctx.interrupt(o.cancelKey, seed);
1727
- const held = openHold(ctx, {
1728
- target,
1729
- value: isFn(value) ? value : () => value,
1730
- weight: isFn(weight) ? weight : () => weight,
1731
- axes: o.axes,
1732
- fixed
1733
- });
1734
- seed.onCancel = () => held.leave(true);
1735
- seed.tick = () => {
1736
- if (!ctx.tripwire()) return false;
1737
- held.leave(true);
1738
- return true;
1739
- };
1740
- const base = ctx.register(seed);
1741
- return {
1742
- ...base,
1743
- touch: held.touch,
1744
- release(r) {
1745
- if (!ctx.has(id)) return;
1746
- held.leave(r?.commit !== false);
1747
- ctx.finish(id);
1748
- }
1749
- };
1750
- }
1751
-
1752
- // src/engine/integrator.ts
1753
- function stepSpring(ops, value, velocity, target, k, damping, mass, dt) {
1754
- const ref = target ?? value;
1755
- const displacement = ops.subtract(value, ref);
1756
- const springForce = ops.scale(displacement, target == null ? 0 : -k);
1757
- const dampingForce = ops.scale(velocity, -damping);
1758
- const accel = ops.scale(ops.add(springForce, dampingForce), 1 / mass);
1759
- const v = ops.add(velocity, ops.scale(accel, dt));
1760
- return { value: ops.add(value, ops.scale(v, dt)), velocity: v };
1761
- }
1762
-
1763
- // src/engine/physicsAnimation.ts
1764
- function resolveSpringConstants(o) {
1765
- const preset = o.preset ? SPRING_PRESETS[o.preset] : null;
1766
- return {
1767
- stiffness: o.stiffness ?? preset?.stiffness ?? 170,
1768
- damping: o.damping ?? preset?.damping ?? 26,
1769
- mass: o.mass ?? preset?.mass ?? 1
1770
- };
1771
- }
1772
- function startPhysics(ctx, o, kind) {
1773
- const { codec } = ctx;
1774
- const id = ctx.nextId();
1775
- const isNumeric = typeof o.from === "number";
1776
- if (!isNumeric && (!o.add || !o.subtract || !o.scale || !o.magnitude)) {
1777
- throw new Error("physics: add/subtract/scale/magnitude are required for non-numeric T");
1778
- }
1779
- const add = o.add ?? ((a, b) => a + b);
1780
- const subtract = o.subtract ?? ((a, b) => a - b);
1781
- const scale = o.scale ?? ((v, k) => v * k);
1782
- const magnitude = o.magnitude ?? ((v) => Math.abs(v));
1783
- const { stiffness: kBase, damping, mass } = resolveSpringConstants(o);
1784
- const restThreshold = o.restThreshold ?? 0.01;
1785
- let target = o.to ?? null;
1786
- let value = o.from;
1787
- let velocity = o.velocity ?? scale(o.from, 0);
1788
- let lastTime = null;
1789
- const solvable = damping > 0 && mass > 0 && (kBase > 0 || kind === "decay");
1790
- const fromAxes = solvable ? axesOf(o.from) : null;
1791
- const sameShape = (v) => v == null || axesOf(v)?.shape === fromAxes.shape;
1792
- const axes = fromAxes && sameShape(o.to) && sameShape(o.velocity) ? fromAxes : null;
1793
- let reader = null;
1794
- if (axes) {
1795
- ctx.seedClock();
1796
- reader = codec.spring({
1797
- id,
1798
- axes: axes.count,
1799
- from: axes.to(o.from),
1800
- to: o.to == null ? null : axes.to(o.to),
1801
- velocity: o.velocity == null ? new Array(axes.count).fill(0) : axes.to(o.velocity),
1802
- stiffness: kBase,
1803
- damping,
1804
- mass
1805
- });
1806
- }
1807
- const baseHandle = ctx.register({
1808
- id,
1809
- kind,
1810
- cancelKey: o.cancelKey,
1811
- label: o.label,
1812
- tick(nowMs) {
1813
- if (ctx.tripwire()) {
1814
- codec.stop(id);
1815
- return true;
1816
- }
1817
- if (lastTime == null) {
1818
- lastTime = nowMs;
1819
- if (target == null && magnitude(velocity) < restThreshold) {
1820
- codec.stop(id);
1821
- ctx.reportEnd(id);
1822
- o.onDone?.();
1823
- return true;
1824
- }
1825
- if (!axes) {
1826
- o.onTick(value);
1827
- return false;
1828
- }
1829
- }
1830
- if (axes) {
1831
- const m = reader.motion();
1832
- value = axes.from(m.value);
1833
- velocity = axes.from(m.velocity);
1834
- } else {
1835
- const dt = Math.min(0.064, (nowMs - lastTime) / 1e3);
1836
- const next = stepSpring({ add, subtract, scale }, value, velocity, target, kBase, damping, mass, dt);
1837
- value = next.value;
1838
- velocity = next.velocity;
1839
- }
1840
- lastTime = nowMs;
1841
- o.onTick(value);
1842
- const velRested = magnitude(velocity) < restThreshold;
1843
- const posRested = target == null ? true : magnitude(subtract(value, target)) < restThreshold;
1844
- if (velRested && posRested) {
1845
- if (target != null) o.onTick(target);
1846
- codec.stop(id);
1847
- if (ctx.has(id)) {
1848
- ctx.reportEnd(id);
1849
- o.onDone?.();
1850
- }
1851
- return true;
1852
- }
1853
- return false;
1854
- }
1855
- });
1856
- const checkShape = (v, what) => {
1857
- if (!sameShape(v)) throw new Error(`physics: ${what} is not the same shape as \`from\``);
1858
- };
1859
- return {
1860
- ...baseHandle,
1861
- setTarget: (newTo) => {
1862
- if (axes && newTo != null) checkShape(newTo, "setTarget");
1863
- target = newTo;
1864
- if (axes && codec.has(id)) codec.retarget(id, newTo == null ? null : axes.to(newTo));
1865
- },
1866
- setVelocity: (v) => {
1867
- if (axes) checkShape(v, "setVelocity");
1868
- velocity = v;
1869
- if (axes && codec.has(id)) codec.push(id, axes.to(v));
1870
- }
1871
- };
1872
- }
1873
-
1874
- // src/engine/springRest.ts
1875
- function earliestRest(value, velocity, stiffness, damping, mass, threshold) {
1876
- let energy = 0;
1877
- for (let i = 0; i < value.length; i++) energy += stiffness * value[i] ** 2 + mass * velocity[i] ** 2;
1878
- const atRest = (stiffness + mass) * threshold ** 2;
1879
- if (!(energy > atRest)) return 0;
1880
- return 1e3 * mass / (2 * damping) * Math.log(energy / atRest);
1881
- }
1882
-
1883
- // src/engine/springVoices.ts
1884
- function createSpringVoices(codec, s, o) {
1885
- const n = s.axes.count;
1886
- const zeros = () => new Array(n).fill(0);
1887
- const moving = { id: -1 };
1888
- const resting = { id: -1 };
1889
- let first = true;
1890
- let rate = 1;
1891
- let coasting = false;
1892
- const of = (v) => (v.id >= 0 ? codec.own(v.id) : void 0) ?? { value: zeros(), velocity: zeros() };
1893
- const cue = (v, from, velocity, to) => {
1894
- if (v.id >= 0) {
1895
- const was = codec.own(v.id).value;
1896
- codec.recue(v.id, from.slice(), velocity.slice(), to);
1897
- bump(s, minus(from, was), 1);
1898
- return;
1899
- }
1900
- v.id = first ? o.id : o.nextId();
1901
- first = false;
1902
- s.slot = codec.spring({
1903
- id: v.id,
1904
- on: s,
1905
- axes: n,
1906
- from: from.slice(),
1907
- to,
1908
- velocity: velocity.slice(),
1909
- stiffness: o.stiffness,
1910
- damping: o.damping,
1911
- mass: o.mass
1912
- });
1913
- if (rate !== 1) codec.rate(v.id, rate);
1914
- bump(s, from, 1);
1915
- };
1916
- const drop = (v) => {
1917
- if (v.id < 0) return;
1918
- const own = codec.own(v.id);
1919
- if (own) bump(s, own.value, -1);
1920
- codec.stop(v.id);
1921
- v.id = -1;
1922
- };
1923
- return {
1924
- own() {
1925
- const a = of(moving);
1926
- const b = of(resting);
1927
- return { value: plus(a.value, b.value), velocity: plus(a.velocity, b.velocity) };
1928
- },
1929
- moved: () => of(moving).value,
1930
- move(delta) {
1931
- const m = of(moving);
1932
- cue(moving, plus(m.value, delta), m.velocity, zeros());
1933
- },
1934
- settleMove: () => drop(moving),
1935
- rest(from, velocity, coast) {
1936
- coasting = coast;
1937
- cue(resting, from, velocity, coast ? null : zeros());
1938
- },
1939
- push(velocity) {
1940
- const v = minus(velocity, of(moving).velocity);
1941
- if (resting.id < 0) cue(resting, zeros(), v, coasting ? null : zeros());
1942
- else codec.push(resting.id, v);
1943
- },
1944
- rate(r) {
1945
- rate = r;
1946
- for (const v of [moving, resting]) if (v.id >= 0 && codec.has(v.id)) codec.rate(v.id, r);
1947
- },
1948
- stop() {
1949
- drop(moving);
1950
- drop(resting);
1951
- }
1952
- };
1953
- }
1954
-
1955
- // src/engine/offsetPhysics.ts
1956
- var any = (a) => a.some((x) => x !== 0);
1957
- function constantsFor(o, kind, toward) {
1958
- const c = resolveSpringConstants(o);
1959
- if (!(c.damping > 0) || !(c.mass > 0) || toward && !(c.stiffness > 0)) {
1960
- throw new Error(`${kind}: an animation on a target needs positive damping and mass, and stiffness to reach \`to\``);
1961
- }
1962
- return c;
1963
- }
1964
- function restWaits(ctx, id, quiet, c, threshold) {
1965
- const until = (value, velocity, coast) => earliestRest(value, velocity, coast ? 0 : c.stiffness, c.damping, c.mass, threshold);
1966
- return {
1967
- /** The `due` it registers with. */
1968
- first: (value, velocity, coast) => quiet ? until(value, velocity, coast) : void 0,
1969
- /** Leaves it unticked until then. Answers false, as the tick of one still moving does. */
1970
- again(value, velocity, coast) {
1971
- if (quiet) ctx.defer(id, until(value, velocity, coast));
1972
- return false;
1973
- }
1974
- };
1975
- }
1976
- function startOffsetPhysics(ctx, o, kind) {
1977
- const s = stateOf(o.target);
1978
- const ax = s.axes;
1979
- let dest = o.to == null ? null : axesFor(ax, o.to, "to");
1980
- const constants = constantsFor(o, kind, dest != null);
1981
- const { stiffness, damping, mass } = constants;
1982
- const given = o.from === void 0 ? null : axesFor(ax, o.from, "from");
1983
- let velocity = o.velocity === void 0 ? null : axesFor(ax, o.velocity, "velocity");
1984
- const restThreshold = o.restThreshold ?? 0.01;
1985
- const zeros = () => new Array(ax.count).fill(0);
1986
- const id = ctx.nextId();
1987
- const waits = restWaits(ctx, id, !o.onTick, constants, restThreshold);
1988
- const interrupts = o.interrupt !== false;
1989
- const seed = {
1990
- id,
1991
- kind,
1992
- cancelKey: o.cancelKey,
1993
- label: o.label,
1994
- target: s,
1995
- keepExisting: !interrupts
1996
- };
1997
- if (o.cancelKey != null && interrupts) {
1998
- velocity ??= ctx.velocityUnder(o.cancelKey, s);
1999
- ctx.interrupt(o.cancelKey, seed);
2000
- }
2001
- const before = ax.to(s.get());
2002
- const committed = dest ? takeUp(s) : before;
2003
- const start = travel(ax, given ?? committed, dest ?? committed);
2004
- const move = dest ? travel(ax, before, dest) : zeros();
2005
- ctx.targets.attach(s);
2006
- if (o.to != null) s.set(o.to);
2007
- ctx.seedClock();
2008
- const voices = createSpringVoices(ctx.codec, s, { id, nextId: ctx.nextId, stiffness, damping, mass });
2009
- if (any(move)) voices.move(move);
2010
- const top = minus(start, move);
2011
- if (!any(move) || any(top) || velocity && any(velocity)) voices.rest(top, velocity ?? zeros(), dest == null);
2012
- const behind = goBehind(s, voices.moved);
2013
- let left = false;
2014
- const leave = (commit) => {
2015
- if (left) return;
2016
- left = true;
2017
- const moved = voices.moved();
2018
- const shown = comeOut(s, behind, moved);
2019
- if (commit) commitOffset(s, voices.own().value, moved, shown);
2020
- voices.stop();
2021
- ctx.targets.detach(s);
2022
- };
2023
- const finish = () => {
2024
- leave(dest == null);
2025
- ctx.reportEnd(id);
2026
- o.onDone?.();
2027
- };
2028
- let ticked = false;
2029
- seed.due = waits.first(start, velocity ?? zeros(), dest == null);
2030
- seed.onCancel = () => leave(true);
2031
- seed.onRate = (paused, timeScale) => voices.rate(paused ? 0 : timeScale);
2032
- seed.velocity = () => voices.own().velocity;
2033
- seed.tick = () => {
2034
- if (ctx.tripwire()) {
2035
- leave(true);
2036
- return true;
2037
- }
2038
- const own = voices.own();
2039
- if (!ticked && dest == null && norm(own.velocity) < restThreshold) {
2040
- finish();
2041
- return true;
2042
- }
2043
- ticked = true;
2044
- o.onTick?.(ax.from(plus(dest ?? ax.to(s.get()), own.value)));
2045
- if (!ctx.has(id)) return true;
2046
- if (norm(own.velocity) >= restThreshold || dest && norm(own.value) >= restThreshold) {
2047
- return waits.again(own.value, own.velocity, dest == null);
2048
- }
2049
- if (dest) o.onTick?.(ax.from(dest));
2050
- if (!ctx.has(id)) return true;
2051
- finish();
2052
- return true;
2053
- };
2054
- const base = ctx.register(seed);
2055
- return {
2056
- ...base,
2057
- setTarget(next) {
2058
- if (left) return;
2059
- const own = voices.own();
2060
- if (next == null) {
2061
- if (dest == null) return;
2062
- const moved = voices.moved();
2063
- absorb(s, behind.kept ? times(moved, behind.kept) : moved);
2064
- dest = null;
2065
- voices.rest(minus(own.value, moved), own.velocity, true);
2066
- voices.settleMove();
2067
- waits.again(own.value, own.velocity, true);
2068
- return;
2069
- }
2070
- const to = axesFor(ax, next, "setTarget");
2071
- if (!(stiffness > 0)) throw new Error(`${kind}: setTarget needs a positive stiffness`);
2072
- const shift = travel(ax, ax.to(s.get()), to);
2073
- s.set(next);
2074
- if (dest == null) voices.rest(own.value, own.velocity, false);
2075
- dest = to;
2076
- voices.move(shift);
2077
- waits.again(plus(own.value, shift), own.velocity, false);
2078
- ctx.targets.showNow(s);
2079
- },
2080
- setVelocity(v) {
2081
- if (left) return;
2082
- const pushed = axesFor(ax, v, "setVelocity");
2083
- waits.again(voices.own().value, pushed, dest == null);
2084
- voices.push(pushed);
2085
- }
2086
- };
2087
- }
2088
- function startOffsetTween(ctx, o) {
2089
- const { codec } = ctx;
2090
- const s = stateOf(o.target);
2091
- const ax = s.axes;
2092
- const to = axesFor(ax, o.to, "to");
2093
- const given = o.from === void 0 ? null : axesFor(ax, o.from, "from");
2094
- const id = ctx.nextId();
2095
- const interrupts = o.interrupt !== false;
2096
- const seed = {
2097
- id,
2098
- kind: "tween",
2099
- cancelKey: o.cancelKey,
2100
- label: o.label,
2101
- target: s,
2102
- keepExisting: !interrupts
2103
- };
2104
- if (o.cancelKey != null && interrupts) ctx.interrupt(o.cancelKey, seed);
2105
- const committed = ax.to(s.get());
2106
- const base = takeUp(s);
2107
- const from = o.from === void 0 ? ax.from(base) : o.from;
2108
- const path = o.interpolator ? o.interpolator(from, o.to) : o.interpolate ? (u) => o.interpolate(from, o.to, u) : null;
2109
- const easing = resolveEasing(o.easing ?? easeOut);
2110
- const ms = Math.max(o.ms, 1);
2111
- ctx.targets.attach(s);
2112
- s.set(o.to);
2113
- ctx.seedClock();
2114
- s.slot = path ? codec.path({ id, on: s, ms, axes: ax.count, at: (u) => minus(ax.to(path(easing(u))), to) }) : codec.tween({
2115
- id,
2116
- on: s,
2117
- ms,
2118
- ease: easing,
2119
- from: travel(ax, given ?? base, to),
2120
- to: new Array(ax.count).fill(0)
2121
- });
2122
- bump(s, codec.own(id).value, 1);
2123
- const share2 = shareBehind(ax, committed, to, travel(ax, given ?? base, to));
2124
- const behind = goBehind(s, () => times(codec.own(id).value, share2));
2125
- let left = false;
2126
- const leave = (commit) => {
2127
- if (left) return;
2128
- left = true;
2129
- const own = codec.own(id);
2130
- const moved = own ? behind.left() : [];
2131
- const shown = comeOut(s, behind, moved);
2132
- if (own) {
2133
- if (commit) commitOffset(s, own.value, moved, shown);
2134
- bump(s, own.value, -1);
2135
- }
2136
- codec.stop(id);
2137
- ctx.targets.detach(s);
2138
- };
2139
- seed.progress = (virtualNow) => o.ms <= 0 ? 1 : Math.min(1, virtualNow / o.ms);
2140
- if (!o.onTick) seed.due = Math.max(o.ms, 0);
2141
- seed.onCancel = () => leave(true);
2142
- seed.tick = (nowMs) => {
2143
- if (ctx.tripwire()) {
2144
- leave(true);
2145
- return true;
2146
- }
2147
- const t = o.ms <= 0 ? 1 : Math.min(1, nowMs / o.ms);
2148
- if (o.onTick) o.onTick(t >= 1 ? o.to : ax.from(plus(to, codec.own(id).value)));
2149
- if (t < 1) return false;
2150
- if (!ctx.has(id)) return true;
2151
- leave(false);
2152
- ctx.reportEnd(id);
2153
- o.onDone?.();
2154
- return true;
2155
- };
2156
- return ctx.register(seed);
2157
- }
2158
-
2159
- // src/engine/completions.ts
2160
- function createCompletions(has) {
2161
- const listeners = /* @__PURE__ */ new Map();
2162
- return {
2163
- fire(id) {
2164
- const ls = listeners.get(id);
2165
- if (!ls) return;
2166
- listeners.delete(id);
2167
- for (const cb of ls) cb();
2168
- },
2169
- watch(id, cb) {
2170
- if (!has(id)) {
2171
- cb();
2172
- return () => {
2173
- };
2174
- }
2175
- const ls = listeners.get(id) ?? [];
2176
- ls.push(cb);
2177
- listeners.set(id, ls);
2178
- return () => {
2179
- const cur = listeners.get(id);
2180
- if (!cur) return;
2181
- const idx = cur.indexOf(cb);
2182
- if (idx >= 0) cur.splice(idx, 1);
2183
- if (cur.length === 0) listeners.delete(id);
2184
- };
2185
- }
2186
- };
2187
- }
2188
-
2189
- // src/engine/wakes.ts
2190
- function createWakes() {
2191
- const heap = [];
2192
- const place = (w, i) => {
2193
- heap[i] = w;
2194
- w.heapAt = i;
2195
- };
2196
- const up = (w, i) => {
2197
- while (i > 0) {
2198
- const parent = i - 1 >> 1;
2199
- const p = heap[parent];
2200
- if (p.wakeAt <= w.wakeAt) break;
2201
- place(p, i);
2202
- i = parent;
2203
- }
2204
- place(w, i);
2205
- };
2206
- const down = (w, i) => {
2207
- const n = heap.length;
2208
- for (; ; ) {
2209
- let child = 2 * i + 1;
2210
- if (child >= n) break;
2211
- if (child + 1 < n && heap[child + 1].wakeAt < heap[child].wakeAt) child += 1;
2212
- const c = heap[child];
2213
- if (c.wakeAt >= w.wakeAt) break;
2214
- place(c, i);
2215
- i = child;
2216
- }
2217
- place(w, i);
2218
- };
2219
- const remove = (w) => {
2220
- const i = w.heapAt;
2221
- if (i < 0) return;
2222
- w.heapAt = -1;
2223
- const last = heap.pop();
2224
- if (last === w) return;
2225
- if (i > 0 && last.wakeAt < heap[i - 1 >> 1].wakeAt) up(last, i);
2226
- else down(last, i);
2227
- };
2228
- return {
2229
- set(w, at) {
2230
- remove(w);
2231
- w.wakeAt = at;
2232
- heap.push(w);
2233
- up(w, heap.length - 1);
2234
- },
2235
- remove,
2236
- due(now) {
2237
- const out = [];
2238
- while (heap.length > 0 && heap[0].wakeAt <= now) {
2239
- const w = heap[0];
2240
- remove(w);
2241
- out.push(w);
2242
- }
2243
- if (out.length > 1) out.sort((a, b) => a.seq - b.seq);
2244
- return out;
2245
- },
2246
- clear() {
2247
- for (const w of heap) w.heapAt = -1;
2248
- heap.length = 0;
2249
- },
2250
- get size() {
2251
- return heap.length;
2252
- }
2253
- };
2254
- }
2255
-
2256
- // src/engine/schedule.ts
2257
- function createSchedule(elapsed) {
2258
- const every = /* @__PURE__ */ new Set();
2259
- const wakes = createWakes();
2260
- let seq = 0;
2261
- let awake = 0;
2262
- let waiting = 0;
2263
- const rateOf = (a) => a.paused ? 0 : a.timeScale;
2264
- const clockOf = (a) => a.base + (elapsed() - a.anchor) * rateOf(a);
2265
- const wait = (a) => {
2266
- const rate = rateOf(a);
2267
- if (rate > 0 && a.due < Infinity) wakes.set(a, a.anchor + (a.due - a.base) / rate);
2268
- else wakes.remove(a);
2269
- };
2270
- return {
2271
- clockOf,
2272
- add(a) {
2273
- a.base = 0;
2274
- a.anchor = elapsed();
2275
- a.seq = seq++;
2276
- a.heapAt = -1;
2277
- a.wakeAt = Infinity;
2278
- if (!a.idle) awake += 1;
2279
- if (a.due === void 0) {
2280
- every.add(a);
2281
- return;
2282
- }
2283
- waiting += 1;
2284
- wait(a);
2285
- },
2286
- remove(a) {
2287
- if (!a.idle) awake -= 1;
2288
- if (every.delete(a)) return;
2289
- waiting -= 1;
2290
- wakes.remove(a);
2291
- },
2292
- pace(a, paused, timeScale) {
2293
- a.base = clockOf(a);
2294
- a.anchor = elapsed();
2295
- a.paused = paused;
2296
- a.timeScale = timeScale;
2297
- if (!every.has(a)) wait(a);
2298
- },
2299
- defer(a, ms) {
2300
- if (!a) return;
2301
- a.due = clockOf(a) + ms;
2302
- if (every.delete(a)) waiting += 1;
2303
- wait(a);
2304
- },
2305
- ticks() {
2306
- const steady = [...every];
2307
- const woke = wakes.due(elapsed());
2308
- if (woke.length === 0) return steady;
2309
- waiting -= woke.length;
2310
- const out = [];
2311
- let i = 0;
2312
- for (const a of woke) {
2313
- a.base = Math.max(clockOf(a), a.due);
2314
- a.anchor = elapsed();
2315
- every.add(a);
2316
- while (i < steady.length && steady[i].seq < a.seq) out.push(steady[i++]);
2317
- out.push(a);
2318
- }
2319
- while (i < steady.length) out.push(steady[i++]);
2320
- return out;
2321
- },
2322
- get awake() {
2323
- return awake > 0;
2324
- },
2325
- get waiting() {
2326
- return waiting;
2327
- },
2328
- clear() {
2329
- every.clear();
2330
- wakes.clear();
2331
- awake = 0;
2332
- waiting = 0;
2333
- }
2334
- };
2335
- }
2336
-
2337
- // src/engine/roster.ts
2338
- var NONE = [];
2339
- function file(index, key, a, on) {
2340
- let set = index.get(key);
2341
- if (on) {
2342
- if (!set) index.set(key, set = /* @__PURE__ */ new Set());
2343
- set.add(a);
2344
- } else if (set?.delete(a) && set.size === 0) index.delete(key);
2345
- }
2346
- function createRoster(elapsed) {
2347
- const animations = /* @__PURE__ */ new Map();
2348
- const schedule = createSchedule(elapsed);
2349
- const byKey = /* @__PURE__ */ new Map();
2350
- const byTarget = /* @__PURE__ */ new Map();
2351
- const index = (a, on) => {
2352
- if (a.cancelKey != null) file(byKey, a.cancelKey, a, on);
2353
- if (a.target) file(byTarget, a.target, a, on);
2354
- };
2355
- const remove = (a) => {
2356
- animations.delete(a.id);
2357
- schedule.remove(a);
2358
- index(a, false);
2359
- };
2360
- return {
2361
- animations,
2362
- schedule,
2363
- under: (key) => byKey.get(key) ?? NONE,
2364
- on: (target) => byTarget.get(target) ?? NONE,
2365
- add(a) {
2366
- const had = animations.get(a.id);
2367
- if (had) remove(had);
2368
- animations.set(a.id, a);
2369
- schedule.add(a);
2370
- index(a, true);
2371
- },
2372
- remove,
2373
- clear() {
2374
- animations.clear();
2375
- schedule.clear();
2376
- byKey.clear();
2377
- byTarget.clear();
2378
- }
2379
- };
2380
- }
2381
-
2382
- // src/engine/table.ts
2383
- function timeScaleOf(scale) {
2384
- if (!(scale >= 0)) {
2385
- throw new Error(`animator: a time scale is 0 or above, and ${scale} was given; nothing plays backward`);
2386
- }
2387
- return scale;
2388
- }
2389
- function createTable(host) {
2390
- const { codec, hub } = host;
2391
- const roster = createRoster(host.elapsed);
2392
- const { animations, schedule, remove } = roster;
2393
- const { fire: fireCompletion, watch: watchCompletion } = createCompletions((id) => animations.has(id));
2394
- const infoOf = (a) => {
2395
- if (!a.info) {
2396
- const info = { id: a.id, kind: a.kind };
2397
- if (a.cancelKey != null) info.key = a.cancelKey;
2398
- if (a.label != null) info.label = a.label;
2399
- a.info = info;
2400
- }
2401
- return a.info;
2402
- };
2403
- const reportEnd = (id) => {
2404
- const a = animations.get(id);
2405
- if (!a || a.ended) return;
2406
- a.ended = true;
2407
- if (hub.watched) hub.emit({ type: "end", animation: infoOf(a) });
2408
- };
2409
- const retire = (id, by, error) => {
2410
- const a = animations.get(id);
2411
- if (!a) return;
2412
- a.onCancel?.();
2413
- codec.stop(id);
2414
- remove(a);
2415
- if (host.resting()) host.requestFrame();
2416
- fireCompletion(id);
2417
- if (!hub.watched || a.ended || error) return;
2418
- hub.emit(by ? { type: "interrupt", animation: infoOf(a), by: infoOf(by) } : { type: "cancel", animation: infoOf(a) });
2419
- };
2420
- const cancelByKey = (key, by) => {
2421
- for (const a of [...roster.under(key)]) retire(a.id, by);
2422
- };
2423
- const pace = (a, paused, timeScale) => {
2424
- schedule.pace(a, paused, timeScale);
2425
- if (codec.has(a.id)) codec.rate(a.id, paused ? 0 : timeScale);
2426
- a.onRate?.(paused, timeScale);
2427
- };
2428
- const register = (seed) => {
2429
- const anim = seed;
2430
- if (seed.cancelKey != null && !seed.keepExisting) cancelByKey(seed.cancelKey, anim);
2431
- anim.paused = false;
2432
- anim.timeScale = 1;
2433
- host.seedClock();
2434
- roster.add(anim);
2435
- if (anim.target) host.showNow(anim.target);
2436
- if (hub.watched) hub.emit({ type: "start", animation: infoOf(anim) });
2437
- if (animations.size > 0 && !host.framing()) host.requestFrame();
2438
- const live = () => animations.get(anim.id);
2439
- return {
2440
- id: anim.id,
2441
- cancel: () => retire(anim.id),
2442
- pause: () => {
2443
- const a = live();
2444
- if (a) pace(a, true, a.timeScale);
2445
- },
2446
- resume: () => {
2447
- const a = live();
2448
- if (a) pace(a, false, a.timeScale);
2449
- },
2450
- setTimeScale: (s) => {
2451
- const a = live();
2452
- if (a) pace(a, a.paused, timeScaleOf(s));
2453
- },
2454
- timeScale: () => live()?.timeScale ?? 1,
2455
- isPaused: () => live()?.paused ?? false
2456
- };
2457
- };
2458
- return {
2459
- animations,
2460
- infoOf,
2461
- reportEnd,
2462
- retire,
2463
- cancelByKey,
2464
- under: roster.under,
2465
- on: roster.on,
2466
- pace,
2467
- clockOf: schedule.clockOf,
2468
- defer: (id, ms) => schedule.defer(animations.get(id), ms),
2469
- ticks: schedule.ticks,
2470
- get awake() {
2471
- return schedule.awake;
2472
- },
2473
- get waiting() {
2474
- return schedule.waiting;
2475
- },
2476
- register,
2477
- finish(id) {
2478
- const a = animations.get(id);
2479
- if (!a) return;
2480
- reportEnd(id);
2481
- remove(a);
2482
- fireCompletion(id);
2483
- },
2484
- drop(a) {
2485
- remove(a);
2486
- fireCompletion(a.id);
2487
- },
2488
- cancelAll(cleared) {
2489
- const retired = [];
2490
- for (const a of animations.values()) {
2491
- a.onCancel?.();
2492
- codec.stop(a.id);
2493
- retired.push(a);
2494
- }
2495
- roster.clear();
2496
- cleared();
2497
- for (const a of retired) fireCompletion(a.id);
2498
- if (hub.watched) for (const a of retired) hub.emit({ type: "cancel", animation: infoOf(a) });
2499
- },
2500
- watchCompletion
2501
- };
2502
- }
2503
- function startTween(ctx, o) {
2504
- const { codec } = ctx;
2505
- const fromAxes = o.interpolate || o.interpolator ? null : axesOf(o.from);
2506
- const toAxes = fromAxes && axesOf(o.to);
2507
- const axes = fromAxes && toAxes && toAxes.shape === fromAxes.shape ? fromAxes : null;
2508
- if (!axes && !o.interpolate && !o.interpolator) {
2509
- throw new Error("tween: interpolate or interpolator is required for non-numeric T");
2510
- }
2511
- const id = ctx.nextId();
2512
- const easing = resolveEasing(o.easing ?? easeOut);
2513
- const factoryFn = o.interpolator ? o.interpolator(o.from, o.to) : null;
2514
- let lastValueEmitted = false;
2515
- ctx.seedClock();
2516
- const reader = codec.tween({
2517
- id,
2518
- ms: Math.max(o.ms, 1),
2519
- ease: easing,
2520
- from: axes ? axes.to(o.from) : [0],
2521
- to: axes ? axes.to(o.to) : [1]
2522
- });
2523
- return ctx.register({
2524
- id,
2525
- kind: "tween",
2526
- cancelKey: o.cancelKey,
2527
- label: o.label,
2528
- progress: (virtualNow) => o.ms <= 0 ? 1 : Math.min(1, virtualNow / o.ms),
2529
- // `nowMs` is the call's own virtual time, so it still decides when the tween ends; the codec
2530
- // supplies only the value in between.
2531
- tick(nowMs) {
2532
- if (ctx.tripwire()) {
2533
- codec.stop(id);
2534
- return true;
2535
- }
2536
- const t = o.ms <= 0 ? 1 : Math.min(1, nowMs / o.ms);
2537
- let value;
2538
- if (axes) {
2539
- value = t >= 1 ? o.to : axes.from(reader.cols, reader.at);
2540
- } else {
2541
- const eased = t >= 1 ? easing(1) : reader.cols[reader.at];
2542
- value = factoryFn ? factoryFn(eased) : o.interpolate(o.from, o.to, eased);
2543
- }
2544
- o.onTick(value);
2545
- if (t >= 1 && !lastValueEmitted) {
2546
- lastValueEmitted = true;
2547
- codec.stop(id);
2548
- if (ctx.has(id)) {
2549
- ctx.reportEnd(id);
2550
- o.onDone?.();
2551
- }
2552
- return true;
2553
- }
2554
- return false;
2555
- }
2556
- });
2557
- }
2558
-
2559
- // src/engine/animatorApi.ts
2560
- function eachUnder(table, key, pace) {
2561
- for (const a of table.under(key)) table.pace(a, ...pace(a));
2562
- }
2563
- function liveRows(table) {
2564
- const rows = [];
2565
- for (const a of table.animations.values()) {
2566
- rows.push({
2567
- ...table.infoOf(a),
2568
- paused: a.paused,
2569
- timeScale: a.timeScale,
2570
- elapsed: table.clockOf(a),
2571
- ...a.progress ? { progress: a.progress(table.clockOf(a)) } : {}
2572
- });
2573
- }
2574
- return rows;
2575
- }
2576
- function startTimeline(host, o) {
2577
- const { ctx, table, hub } = host;
2578
- const id = ctx.nextId();
2579
- const info = () => {
2580
- const a = table.animations.get(id);
2581
- return a && table.infoOf(a);
2582
- };
2583
- const hold = (target, value, weight, axes) => openHold(ctx, { target: stateOf(target), value, weight, axes });
2584
- return createTimeline(table.register, id, o, {
2585
- get watched() {
2586
- return hub.watched;
2587
- },
2588
- fire: (track, path, event, lateBy) => {
2589
- const animation = info();
2590
- if (animation) hub.emit({ type: "fire", animation, track, path: path.slice(), event, lateBy });
2591
- },
2592
- lap: (lap) => {
2593
- const animation = info();
2594
- if (animation) hub.emit({ type: "lap", animation, lap });
2595
- }
2596
- }, hold);
2597
- }
2598
- function createAnimatorApi(host) {
2599
- const { ctx, table, clock, hub } = host;
2600
- const { animations } = table;
2601
- const createSupervisor = supervisors({
2602
- nextId: ctx.nextId,
2603
- register: table.register,
2604
- finish: table.finish,
2605
- afterTicks: host.afterTicks
2606
- });
2607
- const physics = (o, kind = "physics") => o.target ? startOffsetPhysics(ctx, o, kind) : startPhysics(ctx, o, kind);
2608
- const api = {
2609
- tween: (o) => o.target ? startOffsetTween(ctx, o) : startTween(ctx, o),
2610
- spring: (o) => physics(o, "spring"),
2611
- decay: (o) => physics(decayAsPhysics(o), "decay"),
2612
- physics,
2613
- hold: (o) => startHold(ctx, o),
2614
- cancel: (handle) => table.retire(handle.id),
2615
- cancelKey: (key) => table.cancelByKey(key),
2616
- cancelAll: () => {
2617
- table.cancelAll(() => {
2618
- clock.frameT = null;
2619
- ctx.targets.show();
2620
- });
2621
- host.halt();
2622
- },
2623
- isActive: (key) => {
2624
- if (key == null) return animations.size > 0;
2625
- for (const _ of table.under(key)) return true;
2626
- return false;
2627
- },
2628
- isTicking: host.ticking,
2629
- pause: () => {
2630
- clock.paused = true;
2631
- },
2632
- resume: () => {
2633
- clock.paused = false;
2634
- },
2635
- isPaused: () => clock.paused,
2636
- setTimeScale: (s) => {
2637
- clock.timeScale = timeScaleOf(s);
2638
- },
2639
- timeScale: () => clock.timeScale,
2640
- pauseKey: (key) => eachUnder(table, key, (a) => [true, a.timeScale]),
2641
- resumeKey: (key) => eachUnder(table, key, (a) => [false, a.timeScale]),
2642
- setTimeScaleByKey: (key, s) => {
2643
- const scale = timeScaleOf(s);
2644
- eachUnder(table, key, (a) => [a.paused, scale]);
2645
- },
2646
- loop: (factory, loopOpts) => createLoop(createSupervisor, factory, loopOpts),
2647
- tweenLoop: (tweenLoopOpts) => createTweenLoop(api, createSupervisor, tweenLoopOpts),
2648
- stagger: ((items, delay, factory, staggerOpts) => createStagger(
2649
- api,
2650
- createSupervisor,
2651
- table.watchCompletion,
2652
- items,
2653
- delay,
2654
- factory,
2655
- staggerOpts
2656
- )),
2657
- timeline: (o) => startTimeline(host, o),
2658
- colorOverrides: host.colorOverrides,
2659
- onTick: (cb) => host.subscribers.subscribe(cb),
2660
- keepAlive: () => {
2661
- const sup = createSupervisor({ kind: "keepAlive" });
2662
- return () => sup.cancel();
2663
- },
2664
- watch: (listener) => hub.add(listener),
2665
- live: () => liveRows(table)
2666
- };
2667
- return api;
2668
- }
2669
- function createHoldVoices() {
2670
- const asked = /* @__PURE__ */ new Set();
2671
- const stills = /* @__PURE__ */ new Map();
2672
- const stale2 = /* @__PURE__ */ new Set();
2673
- const cue = (v) => {
2674
- const { start } = v;
2675
- const { lane, key } = v.slot;
2676
- v.handle?.fade({ over: 0 });
2677
- stale2.delete(v);
2678
- if (start.still) {
2679
- v.last = start.at();
2680
- const rest = tween("v", { from: v.last, to: v.last.slice(), ms: 1 });
2681
- v.handle = lane.mix.cue({ patch: rest, subjects: [key] });
2682
- return;
2683
- }
2684
- const each = patch(
2685
- 1,
2686
- () => {
2687
- v.last = start.at();
2688
- return { v: v.last };
2689
- },
2690
- { writes: ["v"] }
2691
- );
2692
- v.handle = lane.mix.cue({ patch: each, subjects: [key], loop: true });
2693
- };
2694
- const settle2 = () => {
2695
- for (const v of stale2) cue(v);
2696
- };
2697
- return {
2698
- add(slot, start) {
2699
- const v = { slot, start: { ...start } };
2700
- if (start.still) {
2701
- stills.set(start.on, v);
2702
- stale2.add(v);
2703
- } else {
2704
- cue(v);
2705
- asked.add(v);
2706
- }
2707
- return v;
2708
- },
2709
- still(v, still) {
2710
- if (!v.start.still === !still) return;
2711
- v.start.still = still;
2712
- if (still) {
2713
- asked.delete(v);
2714
- stills.set(v.start.on, v);
2715
- } else {
2716
- stills.delete(v.start.on);
2717
- asked.add(v);
2718
- }
2719
- cue(v);
2720
- },
2721
- touch(on) {
2722
- const v = stills.get(on);
2723
- if (v) stale2.add(v);
2724
- },
2725
- settle: settle2,
2726
- frame() {
2727
- for (const v of asked) v.slot.lane.mix.touch(v.slot.key);
2728
- settle2();
2729
- },
2730
- remove(v) {
2731
- v.handle?.fade({ over: 0 });
2732
- asked.delete(v);
2733
- stale2.delete(v);
2734
- if (stills.get(v.start.on) === v) stills.delete(v.start.on);
2735
- }
2736
- };
2737
- }
2738
- function depart(s) {
2739
- const l = s.lane;
2740
- l.dirty = true;
2741
- if (s.index < 0) {
2742
- l.pending.delete(s);
2743
- return;
2744
- }
2745
- const lastKey = l.keys.pop();
2746
- const moved = l.slots.pop();
2747
- if (moved === s) return;
2748
- const n = l.axes;
2749
- const from = l.keys.length * n;
2750
- l.cols.v.copyWithin(s.index * n, from, from + n);
2751
- l.keys[s.index] = lastKey;
2752
- l.slots[s.index] = moved;
2753
- l.tags[s.index] = moved.tag;
2754
- moved.index = s.index;
2755
- moved.at = s.index * n;
2756
- }
2757
- function rebuild(l) {
2758
- const keys = l.keys.slice();
2759
- const had = keys.length;
2760
- for (const s of l.pending) {
2761
- s.index = keys.length;
2762
- s.at = s.index * l.axes;
2763
- s.cols = l.cols.v;
2764
- keys.push(s.key);
2765
- l.slots.push(s);
2766
- }
2767
- l.pending.clear();
2768
- l.keys = keys;
2769
- if (l.tags.length < keys.length) {
2770
- const tags = new Int32Array(Math.max(keys.length, l.tags.length * 2));
2771
- tags.set(l.tags);
2772
- l.tags = tags;
2773
- }
2774
- for (let i = had; i < keys.length; i++) l.tags[i] = l.slots[i].tag;
2775
- if (l.cols.v.length !== keys.length * l.axes) {
2776
- l.cols = { v: new Float64Array(keys.length * l.axes) };
2777
- for (const s of l.slots) s.cols = l.cols.v;
2778
- }
2779
- l.dirty = false;
2780
- }
2781
- function createLanes(placeOf) {
2782
- const lanes = /* @__PURE__ */ new Map();
2783
- const slots = /* @__PURE__ */ new Map();
2784
- const touched = /* @__PURE__ */ new Set();
2785
- let now = 0;
2786
- const laneOf = (axes, group) => {
2787
- const key = group ?? axes;
2788
- let l = lanes.get(key);
2789
- if (!l) {
2790
- const m = mix(kit({ v: vec(axes, sum()) }));
2791
- m.sync(now);
2792
- l = {
2793
- key,
2794
- axes,
2795
- mix: m,
2796
- keys: [],
2797
- slots: [],
2798
- tags: new Int32Array(0),
2799
- pending: /* @__PURE__ */ new Set(),
2800
- dirty: false,
2801
- idle: 0,
2802
- cols: { v: new Float64Array(0) }
2803
- };
2804
- lanes.set(key, l);
2805
- }
2806
- return l;
2807
- };
2808
- return {
2809
- join(axes, key, held) {
2810
- const had = slots.get(key);
2811
- if (had) {
2812
- if (had.lane.axes !== axes) {
2813
- throw new Error(`codec: a subject on ${had.lane.axes} axes was given a voice on ${axes}`);
2814
- }
2815
- had.refs += 1;
2816
- return had;
2817
- }
2818
- const place = typeof key === "object" && key !== null ? placeOf?.(key) : void 0;
2819
- const lane = laneOf(axes, place?.group);
2820
- if (lane.axes !== axes) throw new Error(`codec: a group on ${lane.axes} axes was given a subject on ${axes}`);
2821
- const own = Float64Array.from(held);
2822
- const s = { key, lane, refs: 1, index: -1, held: own, cols: own, at: 0, tag: place?.tag ?? -1 };
2823
- slots.set(key, s);
2824
- lane.pending.add(s);
2825
- lane.dirty = true;
2826
- return s;
2827
- },
2828
- leave(s) {
2829
- s.refs -= 1;
2830
- if (s.refs > 0) return;
2831
- s.lane.mix.drop(s.key);
2832
- depart(s);
2833
- slots.delete(s.key);
2834
- },
2835
- frame(time) {
2836
- now = time;
2837
- for (const l of lanes.values()) {
2838
- if (l.dirty) rebuild(l);
2839
- if (l.keys.length === 0 && ++l.idle >= 2) {
2840
- lanes.delete(l.key);
2841
- continue;
2842
- }
2843
- l.mix.sync(time);
2844
- if (l.keys.length === 0) continue;
2845
- l.idle = 0;
2846
- l.mix.pull(l.keys, l.cols);
2847
- }
2848
- touched.clear();
2849
- },
2850
- columns(group) {
2851
- const l = lanes.get(group);
2852
- return l ? { cols: l.cols.v, tags: l.tags, count: l.keys.length } : null;
2853
- },
2854
- touch(key) {
2855
- const s = slots.get(key);
2856
- if (!s) return;
2857
- s.lane.mix.touch(key);
2858
- touched.add(s);
2859
- },
2860
- refresh(each) {
2861
- for (const s of touched) {
2862
- touched.delete(s);
2863
- if (slots.get(s.key) !== s) continue;
2864
- const out = s.lane.mix.probe(s.key, { v: new Array(s.lane.axes).fill(0) });
2865
- s.cols.set(out.v, s.at);
2866
- each(s);
2867
- }
2868
- }
2869
- };
2870
- }
2871
-
2872
- // src/engine/codec.ts
2873
- function createCodec(placeOf) {
2874
- const lanes = createLanes(placeOf);
2875
- const voices = /* @__PURE__ */ new Map();
2876
- const queue = [];
2877
- const holds = createHoldVoices();
2878
- let time = 0;
2879
- const springVoice = (slot, sp) => {
2880
- const patch = sp.to == null ? glide("v", { from: sp.from, ms: 1e3 * sp.mass / sp.damping }) : spring("v", {
2881
- from: sp.from,
2882
- to: sp.from,
2883
- stiffness: sp.stiffness,
2884
- damping: sp.damping,
2885
- mass: sp.mass
2886
- });
2887
- const handle = slot.lane.mix.cue({ patch, subjects: [slot.key] });
2888
- if (sp.to) patch.to(slot.key, sp.to, 0);
2889
- patch.push(slot.key, sp.velocity, 0);
2890
- return { handle, patch };
2891
- };
2892
- const get = (id) => {
2893
- const v = voices.get(id);
2894
- if (!v) throw new Error(`codec: no subject ${id}`);
2895
- return v;
2896
- };
2897
- const current = (v) => v.patch.read(v.slot.key) ?? { value: v.spring.from.slice(), velocity: v.spring.velocity.slice() };
2898
- const respring = (v, next) => {
2899
- const rate = v.handle.rate;
2900
- v.handle.fade({ over: 0 });
2901
- const { handle, patch } = springVoice(v.slot, next);
2902
- handle.rate = rate;
2903
- v.handle = handle;
2904
- v.patch = patch;
2905
- v.spring = next;
2906
- };
2907
- function stop(id) {
2908
- const v = voices.get(id);
2909
- if (!v) return;
2910
- if (v.held) holds.remove(v.held);
2911
- else if (v.queued) v.queued = void 0;
2912
- else v.handle.fade({ over: 0 });
2913
- lanes.leave(v.slot);
2914
- voices.delete(id);
2915
- }
2916
- return {
2917
- frame(dtMs) {
2918
- for (const v of queue) {
2919
- const q = v.queued;
2920
- if (!q) continue;
2921
- v.queued = void 0;
2922
- const key = v.slot.key;
2923
- const patch = tween("v", { from: q.from.slice(), to: q.from.slice(), ms: q.ms, ease: q.ease });
2924
- v.handle = v.slot.lane.mix.cue({ patch, subjects: [key] });
2925
- v.patch = patch;
2926
- patch.to(key, q.to.slice(), 0);
2927
- if (v.queuedRate !== void 0) v.handle.rate = v.queuedRate;
2928
- }
2929
- queue.length = 0;
2930
- holds.frame();
2931
- time += dtMs;
2932
- lanes.frame(time);
2933
- },
2934
- tween(s) {
2935
- if (s.to.length !== s.from.length) {
2936
- throw new Error(`codec: tween ${s.id} goes from ${s.from.length} axes to ${s.to.length}`);
2937
- }
2938
- const shared = s.on !== void 0;
2939
- const slot = lanes.join(s.from.length, shared ? s.on : s.id, shared ? new Array(s.from.length).fill(0) : s.from);
2940
- const v = { kind: "tween", slot, from: s.from, queued: s };
2941
- voices.set(s.id, v);
2942
- queue.push(v);
2943
- return slot;
2944
- },
2945
- spring(s) {
2946
- const sp = { ...s, from: s.from.slice(), to: s.to && s.to.slice(), velocity: s.velocity.slice() };
2947
- const shared = s.on !== void 0;
2948
- const slot = lanes.join(sp.axes, shared ? s.on : s.id, shared ? new Array(sp.axes).fill(0) : sp.from);
2949
- const { handle, patch } = springVoice(slot, sp);
2950
- const v = { kind: "spring", slot, handle, patch, from: sp.from, spring: sp };
2951
- voices.set(s.id, v);
2952
- const reader = slot;
2953
- if (!shared) reader.motion = () => current(v);
2954
- return reader;
2955
- },
2956
- path(s) {
2957
- const shared = s.on !== void 0;
2958
- const from = s.at(0);
2959
- const slot = lanes.join(s.axes, shared ? s.on : s.id, shared ? new Array(s.axes).fill(0) : from);
2960
- const v = { kind: "path", slot, from };
2961
- const patch$1 = patch(
2962
- s.ms,
2963
- (phase) => {
2964
- v.last = s.at(phase);
2965
- return { v: v.last };
2966
- },
2967
- { writes: ["v"] }
2968
- );
2969
- v.handle = slot.lane.mix.cue({ patch: patch$1, subjects: [slot.key], loop: false, freeze: "after" });
2970
- voices.set(s.id, v);
2971
- return slot;
2972
- },
2973
- hold(s) {
2974
- const zeros = new Array(s.axes).fill(0);
2975
- const slot = lanes.join(s.axes, s.on, zeros);
2976
- voices.set(s.id, { kind: "hold", slot, from: zeros, held: holds.add(slot, s) });
2977
- return slot;
2978
- },
2979
- still(id, still) {
2980
- const v = get(id);
2981
- if (!v.held) throw new Error(`codec: subject ${id} is a ${v.kind}, not a hold`);
2982
- holds.still(v.held, still);
2983
- },
2984
- touch(on) {
2985
- holds.touch(on);
2986
- lanes.touch(on);
2987
- },
2988
- refresh(reread) {
2989
- holds.settle();
2990
- lanes.refresh((slot) => {
2991
- const queued = new Array(slot.lane.axes).fill(0);
2992
- for (const v of queue) {
2993
- if (v.queued && v.slot === slot) v.queued.from.forEach((x, i) => {
2994
- queued[i] += x;
2995
- });
2996
- }
2997
- reread(slot.key, queued);
2998
- });
2999
- },
3000
- retarget(id, to) {
3001
- const v = get(id);
3002
- if (v.kind !== "spring") throw new Error(`codec: subject ${id} is a ${v.kind}; ${v.kind}s are not retargeted`);
3003
- const sp = v.spring;
3004
- if (to == null === (sp.to == null)) {
3005
- if (!to) return;
3006
- sp.to = to.slice();
3007
- v.patch.to(v.slot.key, sp.to);
3008
- return;
3009
- }
3010
- const now = current(v);
3011
- respring(v, { ...sp, to: to && to.slice(), from: now.value, velocity: now.velocity });
3012
- },
3013
- recue(id, from, velocity, to) {
3014
- const v = get(id);
3015
- if (v.kind !== "spring") throw new Error(`codec: subject ${id} is a ${v.kind}; only springs are re-cued`);
3016
- respring(v, { ...v.spring, from: from.slice(), velocity: velocity.slice(), to: to && to.slice() });
3017
- },
3018
- push(id, velocity) {
3019
- const v = get(id);
3020
- if (v.spring && !v.patch.read(v.slot.key)) v.spring.velocity = velocity.slice();
3021
- v.patch.push?.(v.slot.key, velocity);
3022
- },
3023
- rate(id, rate) {
3024
- const v = get(id);
3025
- if (v.kind === "hold") return;
3026
- if (v.queued) v.queuedRate = rate;
3027
- else v.handle.rate = rate;
3028
- },
3029
- stop,
3030
- has: (id) => voices.has(id),
3031
- own(id) {
3032
- const v = voices.get(id);
3033
- if (!v) return void 0;
3034
- if (v.kind === "spring") return current(v);
3035
- if (v.kind === "path" || v.kind === "hold") {
3036
- return { value: v.held?.last ?? v.last ?? v.from, velocity: new Array(v.from.length).fill(0) };
3037
- }
3038
- return !v.queued && v.patch.read(v.slot.key) || { value: v.from, velocity: new Array(v.from.length).fill(0) };
3039
- },
3040
- voiceCount: () => voices.size,
3041
- columns: (group) => lanes.columns(group)
3042
- };
3043
- }
3044
-
3045
- // src/engine/frame.ts
3046
- function createFrame(host) {
3047
- const { clock, table, hub } = host;
3048
- const { animations } = table;
3049
- let framing = false;
3050
- let tickDepth = 0;
3051
- const tickEach = () => {
3052
- const finished = [];
3053
- const threw = [];
3054
- const all = table.waiting > 0 && host.tripwire();
3055
- for (const anim of all ? [...animations.values()] : table.ticks()) {
3056
- if (animations.get(anim.id) !== anim) continue;
3057
- const scale = clock.paused ? 0 : clock.timeScale * (anim.paused ? 0 : anim.timeScale);
3058
- tickDepth += 1;
3059
- try {
3060
- if (anim.tick(table.clockOf(anim), scale)) finished.push(anim);
3061
- } catch (error) {
3062
- threw.push([anim, error]);
3063
- } finally {
3064
- tickDepth -= 1;
3065
- }
3066
- }
3067
- for (const [anim, error] of threw) {
3068
- console.error("useAnimator: animation tick threw; retiring it", error);
3069
- if (animations.get(anim.id) !== anim) continue;
3070
- if (anim.ended) table.drop(anim);
3071
- else table.retire(anim.id, void 0, true);
3072
- if (hub.watched) hub.emit({ type: "error", animation: table.infoOf(anim), error });
3073
- }
3074
- return finished;
3075
- };
3076
- return {
3077
- tick(t) {
3078
- const frameDt = clock.frameT == null ? 0 : Math.max(0, t - clock.frameT);
3079
- clock.frameT = t;
3080
- framing = true;
3081
- const dt = frameDt * (clock.paused ? 0 : clock.timeScale);
3082
- clock.elapsed += dt;
3083
- host.codec.frame(dt);
3084
- host.targets.settle();
3085
- for (const anim of tickEach()) {
3086
- if (animations.get(anim.id) === anim) table.finish(anim.id);
3087
- }
3088
- for (const cb of host.afterTicks.splice(0)) {
3089
- try {
3090
- cb();
3091
- } catch (err) {
3092
- console.error("useAnimator: a staggered start threw", err);
3093
- }
3094
- }
3095
- host.reread();
3096
- framing = false;
3097
- host.targets.show();
3098
- host.subscribers.emit();
3099
- if (table.awake) {
3100
- host.requestFrame();
3101
- return;
3102
- }
3103
- clock.frameT = null;
3104
- },
3105
- framing: () => framing,
3106
- ticking: () => tickDepth > 0
3107
- };
3108
- }
3109
- function createAnimatorCore(host) {
3110
- const clock = { frameT: null, elapsed: 0, timeScale: 1, paused: false };
3111
- const hub = new AnimatorEventHub();
3112
- const codec = createCodec((on) => {
3113
- const { row } = on;
3114
- return row ? { group: row.table, tag: row.index } : void 0;
3115
- });
3116
- const afterTicks = [];
3117
- const subscribers = createEmitter();
3118
- let nextId = 1;
3119
- const seedClock = () => {
3120
- if (clock.frameT == null) clock.frameT = host.now();
3121
- };
3122
- const reread = () => codec.refresh((on, queued) => targets.reread(on, queued));
3123
- const table = createTable({
3124
- codec,
3125
- hub,
3126
- elapsed: () => clock.elapsed,
3127
- seedClock,
3128
- showNow: (target) => targets.showNow(target),
3129
- resting: () => targets.resting,
3130
- framing: () => frame.framing(),
3131
- requestFrame: host.requestFrame
3132
- });
3133
- const targets = createTargetBook({
3134
- stop: (s) => {
3135
- for (const a of [...table.on(s)]) table.retire(a.id);
3136
- },
3137
- reread: (s) => {
3138
- codec.touch(s);
3139
- reread();
3140
- },
3141
- columns: (table2) => codec.columns(table2)
3142
- });
3143
- const frame = createFrame({
3144
- clock,
3145
- table,
3146
- codec,
3147
- targets,
3148
- hub,
3149
- afterTicks,
3150
- subscribers,
3151
- reread,
3152
- requestFrame: host.requestFrame,
3153
- tripwire: host.tripwire
3154
- });
3155
- const ctx = {
3156
- codec,
3157
- targets,
3158
- nextId: () => nextId++,
3159
- seedClock,
3160
- register: table.register,
3161
- has: (id) => table.animations.has(id),
3162
- defer: table.defer,
3163
- reportEnd: table.reportEnd,
3164
- finish: table.finish,
3165
- touch: (target, at) => {
3166
- codec.touch(target);
3167
- if (frame.framing() && !at) return;
3168
- reread();
3169
- if (!frame.framing()) host.requestFrame();
3170
- },
3171
- tripwire: host.tripwire,
3172
- interrupt: (key, by) => table.cancelByKey(key, by),
3173
- velocityUnder: (key, target) => {
3174
- let sum2 = null;
3175
- for (const a of table.under(key)) {
3176
- if (a.target !== target) continue;
3177
- const v = a.velocity?.() ?? codec.own(a.id)?.velocity;
3178
- if (v) sum2 = sum2 ? sum2.map((x, i) => x + v[i]) : v.slice();
3179
- }
3180
- return sum2;
3181
- }
3182
- };
3183
- const api = createAnimatorApi({
3184
- ctx,
3185
- table,
3186
- clock,
3187
- hub,
3188
- ticking: frame.ticking,
3189
- afterTicks: (cb) => {
3190
- afterTicks.push(cb);
3191
- },
3192
- subscribers,
3193
- colorOverrides: host.colorOverrides,
3194
- halt: host.cancelFrame
3195
- });
3196
- return {
3197
- api,
3198
- tick: frame.tick,
3199
- resumed() {
3200
- clock.frameT = null;
3201
- }
3202
- };
3203
- }
3204
-
3205
- // src/useAnimator.ts
3206
- function useAnimator(opts = {}) {
3207
- const optsRef = useLatest(opts);
3208
- const colorOverrides = useRef(new ColorOverrideRegistry());
3209
- const coreRef = useRef(null);
3210
- const frameLoop = useVisibleRaf(
3211
- useCallback((t) => {
3212
- coreRef.current?.tick(t);
3213
- }, []),
3214
- {
3215
- requestFrame: opts.requestFrame,
3216
- cancelFrame: opts.cancelFrame,
3217
- onResume: () => coreRef.current?.resumed()
3218
- }
3219
- );
3220
- const frameLoopRef = useLatest(frameLoop);
3221
- const mountedRef = useRef(true);
3222
- const trippedRef = useRef(false);
3223
- const core = useMemo(() => {
3224
- const tripwire = () => {
3225
- if (mountedRef.current) return false;
3226
- if (!trippedRef.current) {
3227
- trippedRef.current = true;
3228
- const isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true;
3229
- if (isDev) {
3230
- console.error(
3231
- "weasel useAnimator: animation tick fired after the host component unmounted. This usually means cleanup logic was bypassed \u2014 check for refactors that removed the unmount effect or replaced cancelAll with a custom path."
3232
- );
3233
- }
3234
- }
3235
- return true;
3236
- };
3237
- const now = () => (optsRef.current.now ?? (typeof performance !== "undefined" ? performance.now.bind(performance) : Date.now))();
3238
- return createAnimatorCore({
3239
- now,
3240
- requestFrame: () => frameLoopRef.current.request(),
3241
- cancelFrame: () => frameLoopRef.current.cancel(),
3242
- tripwire,
3243
- colorOverrides: colorOverrides.current
3244
- });
3245
- }, [frameLoopRef, optsRef]);
3246
- useInsertionEffect(() => {
3247
- coreRef.current = core;
3248
- }, [core]);
3249
- const { api } = core;
3250
- useEffect(() => {
3251
- const overrides = colorOverrides.current;
3252
- mountedRef.current = true;
3253
- return () => {
3254
- mountedRef.current = false;
3255
- api.cancelAll();
3256
- overrides.clearAll();
3257
- };
3258
- }, [api]);
3259
- return api;
3260
- }
3261
-
3262
6
  // src/slew.ts
3263
7
  function checkRate(name, rate) {
3264
8
  if (rate === void 0) return Infinity;
@@ -3682,13 +426,13 @@ function skeletonAxes(skeleton) {
3682
426
  }
3683
427
  };
3684
428
  }
3685
- var TAU2 = Math.PI * 2;
429
+ var TAU = Math.PI * 2;
3686
430
  function wrap(a) {
3687
- return a - TAU2 * Math.round(a / TAU2);
431
+ return a - TAU * Math.round(a / TAU);
3688
432
  }
3689
433
  function clampAngle(v, limit) {
3690
434
  if (!limit) return v;
3691
- for (const c of [v, v - TAU2, v + TAU2]) if (c >= limit.min && c <= limit.max) return c;
435
+ for (const c of [v, v - TAU, v + TAU]) if (c >= limit.min && c <= limit.max) return c;
3692
436
  return Math.abs(wrap(v - limit.min)) <= Math.abs(wrap(v - limit.max)) ? limit.min : limit.max;
3693
437
  }
3694
438
  var cross = (ax, ay, bx, by) => ax * by - ay * bx;
@@ -3821,7 +565,7 @@ function skinPath(path, skin, motions) {
3821
565
  const rest = skinnable(path);
3822
566
  const from = rest.coords;
3823
567
  const count = from.length >> 1;
3824
- const sum2 = new Float64Array(from.length);
568
+ const sum = new Float64Array(from.length);
3825
569
  const total = new Float64Array(count);
3826
570
  for (const joint in skin) {
3827
571
  const m = motions.get(joint);
@@ -3833,16 +577,16 @@ function skinPath(path, skin, motions) {
3833
577
  if (!(w > 0)) continue;
3834
578
  const x = from[2 * i];
3835
579
  const y = from[2 * i + 1];
3836
- sum2[2 * i] += w * (m[0] * x + m[3] * y + m[6]);
3837
- sum2[2 * i + 1] += w * (m[1] * x + m[4] * y + m[7]);
580
+ sum[2 * i] += w * (m[0] * x + m[3] * y + m[6]);
581
+ sum[2 * i + 1] += w * (m[1] * x + m[4] * y + m[7]);
3838
582
  total[i] += w;
3839
583
  }
3840
584
  }
3841
585
  const coords = new Float32Array(from.length);
3842
586
  for (let i = 0; i < count; i++) {
3843
587
  const t = total[i];
3844
- coords[2 * i] = t > 0 ? sum2[2 * i] / t : from[2 * i];
3845
- coords[2 * i + 1] = t > 0 ? sum2[2 * i + 1] / t : from[2 * i + 1];
588
+ coords[2 * i] = t > 0 ? sum[2 * i] / t : from[2 * i];
589
+ coords[2 * i + 1] = t > 0 ? sum[2 * i + 1] / t : from[2 * i + 1];
3846
590
  }
3847
591
  return { ...rest, coords };
3848
592
  }
@@ -4007,6 +751,6 @@ function brushWeights(coords, x, y, radius, current, mode) {
4007
751
  return out;
4008
752
  }
4009
753
 
4010
- export { ColorOverrideRegistry, IDENTITY_JOINT, addJoint, autoSkin, axesOf, bindClip, brushWeights, createSlew, createTarget, createTargetTable, cycleVertexColors, dropSkinJoints, jointAt, jointAxes, jointPositions, mirrorPose, motionsFromRest, moveJoint, rainbowVertexColors, removeJoint, resolveSkeleton, sampleTrack, setWeights, skeletonAxes, skinPath, skinnable, slewToward, solidVertexColors, solveIk, springVertexColors, staggerVertexColors, toClip, tweenVertexColors, useAnimator };
754
+ export { IDENTITY_JOINT, addJoint, autoSkin, bindClip, brushWeights, createSlew, cycleVertexColors, dropSkinJoints, jointAt, jointAxes, jointPositions, mirrorPose, motionsFromRest, moveJoint, rainbowVertexColors, removeJoint, resolveSkeleton, setWeights, skeletonAxes, skinPath, skinnable, slewToward, solidVertexColors, solveIk, springVertexColors, staggerVertexColors, toClip, tweenVertexColors };
4011
755
  //# sourceMappingURL=index.js.map
4012
756
  //# sourceMappingURL=index.js.map