@pixerate/editor 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +25 -2
  2. package/dist/animation/index.cjs +62 -0
  3. package/dist/animation/index.cjs.map +1 -0
  4. package/dist/animation/index.d.cts +296 -0
  5. package/dist/animation/index.d.ts +296 -0
  6. package/dist/animation/index.js +62 -0
  7. package/dist/animation/index.js.map +1 -0
  8. package/dist/canvas/index.cjs +177 -743
  9. package/dist/canvas/index.cjs.map +1 -1
  10. package/dist/canvas/index.d.cts +3 -288
  11. package/dist/canvas/index.d.ts +3 -288
  12. package/dist/canvas/index.js +49 -615
  13. package/dist/canvas/index.js.map +1 -1
  14. package/dist/{chunk-JT7AXQTE.js → chunk-3RPOW4CS.js} +7 -7
  15. package/dist/chunk-A6PHCAGX.cjs +87 -0
  16. package/dist/chunk-A6PHCAGX.cjs.map +1 -0
  17. package/dist/{chunk-HRMEDT2L.js → chunk-D6AVOTSD.js} +6 -85
  18. package/dist/chunk-D6AVOTSD.js.map +1 -0
  19. package/dist/{chunk-4BVP64JJ.cjs → chunk-K2UYKJ6M.cjs} +24 -103
  20. package/dist/chunk-K2UYKJ6M.cjs.map +1 -0
  21. package/dist/{chunk-6IO7WUSU.js → chunk-MXZJ4PKN.js} +14 -14
  22. package/dist/{chunk-AGR7EVRW.cjs → chunk-OYEFRO3D.cjs} +6 -6
  23. package/dist/{chunk-AGR7EVRW.cjs.map → chunk-OYEFRO3D.cjs.map} +1 -1
  24. package/dist/chunk-PWJARCI4.js +637 -0
  25. package/dist/chunk-PWJARCI4.js.map +1 -0
  26. package/dist/chunk-TGD52FEZ.js +87 -0
  27. package/dist/chunk-TGD52FEZ.js.map +1 -0
  28. package/dist/chunk-TTYCN6ZH.cjs +637 -0
  29. package/dist/chunk-TTYCN6ZH.cjs.map +1 -0
  30. package/dist/{chunk-YK5TATOF.cjs → chunk-U7VGDSNY.cjs} +3 -3
  31. package/dist/{chunk-YK5TATOF.cjs.map → chunk-U7VGDSNY.cjs.map} +1 -1
  32. package/dist/extensions/index.cjs +7 -7
  33. package/dist/extensions/index.cjs.map +1 -1
  34. package/dist/extensions/index.js +12 -12
  35. package/dist/generation/index.cjs +3 -3
  36. package/dist/generation/index.cjs.map +1 -1
  37. package/dist/generation/index.js +3 -3
  38. package/dist/index.cjs +20 -20
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.js +103 -103
  41. package/dist/spreadsheet/index.cjs +4 -4
  42. package/dist/spreadsheet/index.cjs.map +1 -1
  43. package/dist/spreadsheet/index.js +8 -8
  44. package/dist/tokens/index.cjs +4 -2
  45. package/dist/tokens/index.cjs.map +1 -1
  46. package/dist/tokens/index.js +6 -4
  47. package/package.json +6 -1
  48. package/dist/chunk-4BVP64JJ.cjs.map +0 -1
  49. package/dist/chunk-HRMEDT2L.js.map +0 -1
  50. /package/dist/{chunk-JT7AXQTE.js.map → chunk-3RPOW4CS.js.map} +0 -0
  51. /package/dist/{chunk-6IO7WUSU.js.map → chunk-MXZJ4PKN.js.map} +0 -0
@@ -0,0 +1,637 @@
1
+ import {
2
+ hexToRgba,
3
+ normalizeTokenColor,
4
+ rgbaToHex
5
+ } from "./chunk-TGD52FEZ.js";
6
+
7
+ // src/animation/easing.ts
8
+ var ANIMATION_EASING_PRESETS = [
9
+ "linear",
10
+ "ease",
11
+ "ease-in",
12
+ "ease-out",
13
+ "ease-in-out",
14
+ "ease-in-back",
15
+ "ease-out-back",
16
+ "spring",
17
+ "hold"
18
+ ];
19
+ var ANIMATION_EASING_LABELS = {
20
+ linear: "Linear",
21
+ hold: "Hold",
22
+ ease: "Ease",
23
+ "ease-in": "Ease in",
24
+ "ease-out": "Ease out",
25
+ "ease-in-out": "Ease in-out",
26
+ "ease-in-back": "Back in",
27
+ "ease-out-back": "Back out",
28
+ spring: "Spring"
29
+ };
30
+ var CUBIC_BEZIER_PRESETS = {
31
+ linear: [0, 0, 1, 1],
32
+ ease: [0.25, 0.1, 0.25, 1],
33
+ "ease-in": [0.42, 0, 1, 1],
34
+ "ease-out": [0, 0, 0.58, 1],
35
+ "ease-in-out": [0.42, 0, 0.58, 1],
36
+ "ease-in-back": [0.36, 0, 0.66, -0.56],
37
+ "ease-out-back": [0.34, 1.56, 0.64, 1]
38
+ };
39
+ var DEFAULT_SPRING = { stiffness: 170, damping: 26, mass: 1, velocity: 0 };
40
+ var clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
41
+ var finite = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? v : fallback;
42
+ function cubicBezierAt(x1, y1, x2, y2, x) {
43
+ if (x <= 0) return 0;
44
+ if (x >= 1) return 1;
45
+ x1 = clamp(x1, 0, 1);
46
+ x2 = clamp(x2, 0, 1);
47
+ const bx = (s2) => 3 * (1 - s2) * (1 - s2) * s2 * x1 + 3 * (1 - s2) * s2 * s2 * x2 + s2 * s2 * s2;
48
+ const by = (s2) => 3 * (1 - s2) * (1 - s2) * s2 * y1 + 3 * (1 - s2) * s2 * s2 * y2 + s2 * s2 * s2;
49
+ const dbx = (s2) => 3 * (1 - s2) * (1 - s2) * x1 + 6 * (1 - s2) * s2 * (x2 - x1) + 3 * s2 * s2 * (1 - x2);
50
+ let s = x;
51
+ for (let i = 0; i < 8; i++) {
52
+ const d = dbx(s);
53
+ if (Math.abs(d) < 1e-9) break;
54
+ const next = s - (bx(s) - x) / d;
55
+ if (!(next >= 0 && next <= 1)) break;
56
+ s = next;
57
+ }
58
+ if (Math.abs(bx(s) - x) > 1e-7) {
59
+ let lo = 0;
60
+ let hi = 1;
61
+ for (let i = 0; i < 60; i++) {
62
+ s = (lo + hi) / 2;
63
+ if (bx(s) < x) lo = s;
64
+ else hi = s;
65
+ }
66
+ }
67
+ return by(s);
68
+ }
69
+ function springAt(spring, seconds) {
70
+ const stiffness = Math.max(1e-6, finite(spring.stiffness, DEFAULT_SPRING.stiffness));
71
+ const damping = Math.max(0, finite(spring.damping, DEFAULT_SPRING.damping));
72
+ const mass = Math.max(1e-6, finite(spring.mass, DEFAULT_SPRING.mass));
73
+ const v0 = -finite(spring.velocity, DEFAULT_SPRING.velocity);
74
+ const t = Math.max(0, seconds);
75
+ const w0 = Math.sqrt(stiffness / mass);
76
+ const zeta = damping / (2 * Math.sqrt(stiffness * mass));
77
+ let x;
78
+ if (zeta < 1 - 1e-6) {
79
+ const wd = w0 * Math.sqrt(1 - zeta * zeta);
80
+ x = Math.exp(-zeta * w0 * t) * (Math.cos(wd * t) + (zeta * w0 + v0) / wd * Math.sin(wd * t));
81
+ } else if (zeta <= 1 + 1e-6) {
82
+ x = Math.exp(-w0 * t) * (1 + (w0 + v0) * t);
83
+ } else {
84
+ const r = Math.sqrt(zeta * zeta - 1);
85
+ const s1 = -w0 * (zeta - r);
86
+ const s2 = -w0 * (zeta + r);
87
+ const c2 = (v0 - s1) / (s2 - s1);
88
+ const c1 = 1 - c2;
89
+ x = c1 * Math.exp(s1 * t) + c2 * Math.exp(s2 * t);
90
+ }
91
+ return 1 - x;
92
+ }
93
+ function resolveEasing(easing) {
94
+ if (easing === "hold") return "hold";
95
+ if (easing === "spring") return { type: "spring", ...DEFAULT_SPRING };
96
+ if (typeof easing === "string") {
97
+ const preset = CUBIC_BEZIER_PRESETS[easing];
98
+ if (preset) return { type: "cubic-bezier", x1: preset[0], y1: preset[1], x2: preset[2], y2: preset[3] };
99
+ const parsed = parseCssEasing(easing);
100
+ return parsed ? resolveEasing(parsed) : { type: "cubic-bezier", x1: 0, y1: 0, x2: 1, y2: 1 };
101
+ }
102
+ if (easing && typeof easing === "object") {
103
+ if (easing.type === "spring") {
104
+ return {
105
+ type: "spring",
106
+ stiffness: finite(easing.stiffness, DEFAULT_SPRING.stiffness),
107
+ damping: finite(easing.damping, DEFAULT_SPRING.damping),
108
+ mass: finite(easing.mass, DEFAULT_SPRING.mass),
109
+ velocity: finite(easing.velocity, DEFAULT_SPRING.velocity)
110
+ };
111
+ }
112
+ if (easing.type === "cubic-bezier") {
113
+ return {
114
+ type: "cubic-bezier",
115
+ x1: clamp(finite(easing.x1, 0), 0, 1),
116
+ y1: finite(easing.y1, 0),
117
+ x2: clamp(finite(easing.x2, 1), 0, 1),
118
+ y2: finite(easing.y2, 1)
119
+ };
120
+ }
121
+ }
122
+ return { type: "cubic-bezier", x1: 0, y1: 0, x2: 1, y2: 1 };
123
+ }
124
+ function evaluateEasing(easing, u, durationMs = 1e3) {
125
+ if (!(u > 0)) return 0;
126
+ if (u >= 1) return 1;
127
+ const resolved = resolveEasing(easing);
128
+ if (resolved === "hold") return 0;
129
+ if (resolved.type === "spring") return springAt(resolved, u * Math.max(0, durationMs) / 1e3);
130
+ const { x1, y1, x2, y2 } = resolved;
131
+ if (x1 === y1 && x2 === y2) return u;
132
+ return cubicBezierAt(x1, y1, x2, y2, u);
133
+ }
134
+ function easingFromInfluence(outInfluence = 1 / 3, inInfluence = 1 / 3) {
135
+ return {
136
+ type: "cubic-bezier",
137
+ x1: clamp(outInfluence, 0.01, 1),
138
+ y1: 0,
139
+ x2: 1 - clamp(inInfluence, 0.01, 1),
140
+ y2: 1
141
+ };
142
+ }
143
+ var NUMBER = String.raw`\s*(-?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?)\s*`;
144
+ var CUBIC_RE = new RegExp(`^cubic-bezier\\(${NUMBER},${NUMBER},${NUMBER},${NUMBER}\\)$`, "i");
145
+ function parseCssEasing(text) {
146
+ const value = text.trim().toLowerCase();
147
+ if (value.length > 200) return null;
148
+ if (ANIMATION_EASING_PRESETS.includes(value)) return value;
149
+ if (value === "step-end" || /^steps\(\s*1\s*(,\s*(end|jump-end)\s*)?\)$/.test(value)) return "hold";
150
+ const m = CUBIC_RE.exec(value);
151
+ if (!m) return null;
152
+ const [x1, y1, x2, y2] = m.slice(1).map(Number);
153
+ if (![x1, y1, x2, y2].every(Number.isFinite) || x1 < 0 || x1 > 1 || x2 < 0 || x2 > 1) return null;
154
+ return { type: "cubic-bezier", x1, y1, x2, y2 };
155
+ }
156
+ function isAnimationEasing(value) {
157
+ if (typeof value === "string") return parseCssEasing(value) !== null;
158
+ if (typeof value !== "object" || value === null) return false;
159
+ const e = value;
160
+ if (e.type === "spring") return true;
161
+ return e.type === "cubic-bezier" && [e.x1, e.y1, e.x2, e.y2].every((v) => typeof v === "number" && Number.isFinite(v));
162
+ }
163
+ var round = (v, digits = 4) => {
164
+ const p = 10 ** digits;
165
+ return Math.round(v * p) / p;
166
+ };
167
+ function easingToCss(easing, durationMs = 1e3, samples = 24) {
168
+ const resolved = resolveEasing(easing);
169
+ if (resolved === "hold") return "steps(1, end)";
170
+ if (resolved.type === "spring") {
171
+ const n = Math.max(2, Math.floor(samples));
172
+ const points = [];
173
+ for (let i = 0; i <= n; i++) points.push(String(round(i === n ? 1 : springAt(resolved, i / n * durationMs / 1e3))));
174
+ return `linear(${points.join(", ")})`;
175
+ }
176
+ const { x1, y1, x2, y2 } = resolved;
177
+ for (const [name, p] of Object.entries(CUBIC_BEZIER_PRESETS)) {
178
+ if (["linear", "ease", "ease-in", "ease-out", "ease-in-out"].includes(name) && p[0] === x1 && p[1] === y1 && p[2] === x2 && p[3] === y2) {
179
+ return name;
180
+ }
181
+ }
182
+ return `cubic-bezier(${round(x1)}, ${round(y1)}, ${round(x2)}, ${round(y2)})`;
183
+ }
184
+ function easingLabel(easing) {
185
+ if (easing === void 0 || easing === null) return ANIMATION_EASING_LABELS.linear;
186
+ if (typeof easing === "string") return ANIMATION_EASING_LABELS[easing] ?? easing;
187
+ return easing.type === "spring" ? "Spring" : "Custom";
188
+ }
189
+
190
+ // src/animation/color.ts
191
+ var toLinear = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
192
+ var toGamma = (c) => c <= 31308e-7 ? 12.92 * c : 1.055 * Math.sign(c) * Math.abs(c) ** (1 / 2.4) - 0.055;
193
+ function rgbToOklab(r, g, b, alpha = 1) {
194
+ const lr = toLinear(r);
195
+ const lg = toLinear(g);
196
+ const lb = toLinear(b);
197
+ const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
198
+ const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
199
+ const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
200
+ return {
201
+ l: 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
202
+ a: 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
203
+ b: 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
204
+ alpha
205
+ };
206
+ }
207
+ function oklabToRgb(color) {
208
+ const l_ = color.l + 0.3963377774 * color.a + 0.2158037573 * color.b;
209
+ const m_ = color.l - 0.1055613458 * color.a - 0.0638541728 * color.b;
210
+ const s_ = color.l - 0.0894841775 * color.a - 1.291485548 * color.b;
211
+ const l = l_ ** 3;
212
+ const m = m_ ** 3;
213
+ const s = s_ ** 3;
214
+ const clamp01 = (v) => Math.min(1, Math.max(0, v));
215
+ return {
216
+ r: clamp01(toGamma(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s)),
217
+ g: clamp01(toGamma(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s)),
218
+ b: clamp01(toGamma(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s)),
219
+ a: clamp01(color.alpha)
220
+ };
221
+ }
222
+ function parseOklab(input) {
223
+ const hex = normalizeTokenColor(input);
224
+ if (hex === null) return null;
225
+ const c = hexToRgba(hex);
226
+ return rgbToOklab(c.r, c.g, c.b, c.a);
227
+ }
228
+ function formatOklab(color) {
229
+ const { r, g, b, a } = oklabToRgb(color);
230
+ return rgbaToHex(r, g, b, a);
231
+ }
232
+ function mixColorsOklab(from, to, t) {
233
+ const a = parseOklab(from);
234
+ const b = parseOklab(to);
235
+ if (!a || !b) return null;
236
+ const alpha = a.alpha + (b.alpha - a.alpha) * t;
237
+ const premul = (k) => a[k] * a.alpha + (b[k] * b.alpha - a[k] * a.alpha) * t;
238
+ if (alpha <= 1e-9) return formatOklab({ l: a.l + (b.l - a.l) * t, a: 0, b: 0, alpha: 0 });
239
+ return formatOklab({ l: premul("l") / alpha, a: premul("a") / alpha, b: premul("b") / alpha, alpha });
240
+ }
241
+
242
+ // src/animation/keyframes.ts
243
+ var ANIMATION_TIME_EPSILON = 1e-6;
244
+ function isValue(value) {
245
+ return typeof value === "number" && Number.isFinite(value) || typeof value === "string";
246
+ }
247
+ function isAnimationKeyframe(value) {
248
+ if (typeof value !== "object" || value === null) return false;
249
+ const k = value;
250
+ return typeof k.time === "number" && Number.isFinite(k.time) && isValue(k.value) && (k.easing === void 0 || isAnimationEasing(k.easing));
251
+ }
252
+ var sortedCache = /* @__PURE__ */ new WeakMap();
253
+ function normalizeKeyframes(keyframes) {
254
+ if (!Array.isArray(keyframes)) return [];
255
+ const cached = sortedCache.get(keyframes);
256
+ if (cached && cached.items.length === keyframes.length && cached.items.every((k, i) => k === keyframes[i])) return cached.out;
257
+ const indexed = keyframes.map((k, i) => ({ k, i })).filter(({ k }) => isAnimationKeyframe(k)).sort((a, b) => a.k.time - b.k.time || a.i - b.i);
258
+ const out = [];
259
+ let lastIndex = -1;
260
+ for (const { k, i } of indexed) {
261
+ const prev = out[out.length - 1];
262
+ if (prev && Math.abs(k.time - prev.time) <= ANIMATION_TIME_EPSILON) {
263
+ if (i > lastIndex) {
264
+ out[out.length - 1] = k;
265
+ lastIndex = i;
266
+ }
267
+ continue;
268
+ }
269
+ out.push(k);
270
+ lastIndex = i;
271
+ }
272
+ sortedCache.set(keyframes, { items: [...keyframes], out });
273
+ return out;
274
+ }
275
+
276
+ // src/animation/evaluate.ts
277
+ function interpolateAnimationValue(from, to, t, type = "number") {
278
+ if (typeof from === "number" && typeof to === "number") return from + (to - from) * t;
279
+ if (t <= 0) return from;
280
+ if (t >= 1) return to;
281
+ if (type === "color" || typeof from === "string" && typeof to === "string") {
282
+ const mixed = mixColorsOklab(from, to, t);
283
+ if (mixed !== null) return mixed;
284
+ }
285
+ return from;
286
+ }
287
+ function segmentIndex(keys, time) {
288
+ let lo = 0;
289
+ let hi = keys.length - 1;
290
+ let found = -1;
291
+ while (lo <= hi) {
292
+ const mid = lo + hi >> 1;
293
+ if (keys[mid].time <= time) {
294
+ found = mid;
295
+ lo = mid + 1;
296
+ } else hi = mid - 1;
297
+ }
298
+ return found;
299
+ }
300
+ function evaluateTrack(track, time, type = "number") {
301
+ const keyframes = Array.isArray(track) ? track : track?.keyframes;
302
+ const keys = normalizeKeyframes(keyframes);
303
+ if (keys.length === 0) return void 0;
304
+ if (!(time > keys[0].time)) return keys[0].value;
305
+ const last = keys[keys.length - 1];
306
+ if (time >= last.time) return last.value;
307
+ const i = segmentIndex(keys, time);
308
+ const a = keys[i];
309
+ const b = keys[i + 1];
310
+ if (time === a.time) return a.value;
311
+ const span = b.time - a.time;
312
+ const u = (time - a.time) / span;
313
+ if (a.easing === "hold") return a.value;
314
+ return interpolateAnimationValue(a.value, b.value, evaluateEasing(a.easing, u, span), type);
315
+ }
316
+ function wrapAnimationTime(time, duration, loop = "none") {
317
+ if (!Number.isFinite(time)) return 0;
318
+ if (!(duration > 0)) return 0;
319
+ if (loop === "loop") return (time % duration + duration) % duration;
320
+ if (loop === "ping-pong") {
321
+ const period = duration * 2;
322
+ const m = (time % period + period) % period;
323
+ return m <= duration ? m : period - m;
324
+ }
325
+ return Math.min(duration, Math.max(0, time));
326
+ }
327
+
328
+ // src/animation/clock.ts
329
+ var pageHidden = () => typeof document !== "undefined" && document.hidden;
330
+ var realClock = {
331
+ live: true,
332
+ now: () => typeof performance !== "undefined" ? performance.now() : Date.now(),
333
+ frame: () => new Promise((resolve) => {
334
+ if (typeof requestAnimationFrame === "function" && !pageHidden()) {
335
+ requestAnimationFrame(() => resolve());
336
+ } else {
337
+ setTimeout(resolve, 16);
338
+ }
339
+ }),
340
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)))
341
+ };
342
+ var settle = () => new Promise((resolve) => setTimeout(resolve, 0));
343
+ var ManualClock = class {
344
+ live = false;
345
+ time;
346
+ frames = [];
347
+ sleepers = [];
348
+ seq = 0;
349
+ constructor(start = 0) {
350
+ this.time = start;
351
+ }
352
+ now() {
353
+ return this.time;
354
+ }
355
+ frame() {
356
+ return new Promise((resolve) => this.frames.push(resolve));
357
+ }
358
+ sleep(ms) {
359
+ return new Promise(
360
+ (resolve) => this.sleepers.push({
361
+ at: this.time + Math.max(0, ms),
362
+ seq: this.seq++,
363
+ resolve
364
+ })
365
+ );
366
+ }
367
+ /** Whether any frames or sleep timers are currently pending. */
368
+ get pending() {
369
+ return this.frames.length > 0 || this.sleepers.length > 0;
370
+ }
371
+ /** Advances time by `ms`, resolving due sleep timers in order, followed by queued frames. */
372
+ async advance(ms) {
373
+ const end = this.time + Math.max(0, ms);
374
+ await settle();
375
+ while (true) {
376
+ let next;
377
+ for (const s of this.sleepers) {
378
+ if (s.at <= end && (!next || s.at < next.at || s.at === next.at && s.seq < next.seq)) {
379
+ next = s;
380
+ }
381
+ }
382
+ if (!next) break;
383
+ this.sleepers.splice(this.sleepers.indexOf(next), 1);
384
+ this.time = Math.max(this.time, next.at);
385
+ next.resolve();
386
+ await settle();
387
+ }
388
+ this.time = end;
389
+ const dueFrames = this.frames.splice(0, this.frames.length);
390
+ for (const resolveFrame of dueFrames) {
391
+ resolveFrame();
392
+ }
393
+ await settle();
394
+ }
395
+ /** Convenience method to advance by 1 frame at a given fps (default: 60fps = 16.67ms). */
396
+ async tick(fps = 60) {
397
+ await this.advance(Math.round(1e3 / fps));
398
+ }
399
+ };
400
+
401
+ // src/animation/player.ts
402
+ var AnimationPlayer = class {
403
+ clock;
404
+ durationSource;
405
+ onTime;
406
+ onEnd;
407
+ time;
408
+ playing = false;
409
+ scrubbing = false;
410
+ resumeAfterScrub = false;
411
+ loop;
412
+ rate;
413
+ /** Unwrapped time at `anchorAt` (keeps the ping-pong direction across frames). */
414
+ anchorRaw = 0;
415
+ anchorAt = 0;
416
+ generation = 0;
417
+ cached;
418
+ listeners = /* @__PURE__ */ new Set();
419
+ constructor(options = {}) {
420
+ this.clock = options.clock ?? realClock;
421
+ this.durationSource = options.duration ?? 1e3;
422
+ this.loop = options.loop ?? "none";
423
+ this.rate = Number.isFinite(options.playbackRate) && options.playbackRate !== 0 ? options.playbackRate : 1;
424
+ this.onTime = options.onTime;
425
+ this.onEnd = options.onEnd;
426
+ this.time = this.clamp(options.time ?? 0);
427
+ this.cached = this.snapshot();
428
+ }
429
+ /** Current timeline length (ms). */
430
+ get duration() {
431
+ const d = typeof this.durationSource === "function" ? this.durationSource() : this.durationSource;
432
+ return Number.isFinite(d) && d > 0 ? d : 0;
433
+ }
434
+ get currentTime() {
435
+ return this.time;
436
+ }
437
+ get isPlaying() {
438
+ return this.playing;
439
+ }
440
+ getState() {
441
+ return this.cached;
442
+ }
443
+ subscribe(listener) {
444
+ this.listeners.add(listener);
445
+ return () => {
446
+ this.listeners.delete(listener);
447
+ };
448
+ }
449
+ /** Replaces the callbacks (hooks keep them current without recreating the player). */
450
+ setCallbacks(callbacks) {
451
+ this.onTime = callbacks.onTime;
452
+ this.onEnd = callbacks.onEnd;
453
+ }
454
+ setDuration(duration) {
455
+ this.durationSource = duration;
456
+ this.time = this.clamp(this.time);
457
+ this.emit(false);
458
+ }
459
+ setLoop(loop) {
460
+ if (loop === this.loop) return;
461
+ this.reanchor();
462
+ this.loop = loop;
463
+ this.emit(false);
464
+ }
465
+ setPlaybackRate(rate) {
466
+ if (!Number.isFinite(rate) || rate === 0 || rate === this.rate) return;
467
+ this.reanchor();
468
+ this.rate = rate;
469
+ this.emit(false);
470
+ }
471
+ /** Starts playing from the playhead (from the start again when a one-shot play had ended). */
472
+ play() {
473
+ if (this.playing) return;
474
+ const d = this.duration;
475
+ if (this.loop === "none" && d > 0) {
476
+ if (this.rate > 0 && this.time >= d) this.time = 0;
477
+ if (this.rate < 0 && this.time <= 0) this.time = d;
478
+ }
479
+ this.playing = true;
480
+ this.scrubbing = false;
481
+ this.anchorRaw = this.time;
482
+ this.anchorAt = this.clock.now();
483
+ this.emit(false);
484
+ this.schedule();
485
+ }
486
+ pause() {
487
+ if (!this.playing) return;
488
+ this.tick(false);
489
+ this.playing = false;
490
+ this.generation++;
491
+ this.emit(true);
492
+ }
493
+ toggle() {
494
+ if (this.playing) this.pause();
495
+ else this.play();
496
+ }
497
+ /** Pauses and returns to the start. */
498
+ stop() {
499
+ this.playing = false;
500
+ this.generation++;
501
+ this.seek(0);
502
+ }
503
+ /** Moves the playhead (clamped to the timeline); playback continues from there. */
504
+ seek(time) {
505
+ if (!Number.isFinite(time)) return;
506
+ this.time = this.clamp(time);
507
+ this.anchorRaw = this.time;
508
+ this.anchorAt = this.clock.now();
509
+ this.emit(true);
510
+ }
511
+ /** Seeks while dragging the playhead: playback pauses until `endScrub`. */
512
+ scrub(time) {
513
+ if (!this.scrubbing) {
514
+ this.resumeAfterScrub = this.playing;
515
+ if (this.playing) {
516
+ this.playing = false;
517
+ this.generation++;
518
+ }
519
+ this.scrubbing = true;
520
+ }
521
+ this.seek(time);
522
+ }
523
+ /** Ends a scrub, resuming playback if it was playing when the scrub began. */
524
+ endScrub() {
525
+ if (!this.scrubbing) return;
526
+ this.scrubbing = false;
527
+ const resume = this.resumeAfterScrub;
528
+ this.resumeAfterScrub = false;
529
+ if (resume) this.play();
530
+ else this.emit(false);
531
+ }
532
+ /** Stops scheduling frames and drops listeners. */
533
+ destroy() {
534
+ this.playing = false;
535
+ this.generation++;
536
+ this.listeners.clear();
537
+ }
538
+ clamp(time) {
539
+ return Math.min(this.duration, Math.max(0, Number.isFinite(time) ? time : 0));
540
+ }
541
+ reanchor() {
542
+ if (!this.playing) return;
543
+ this.tick(false);
544
+ this.anchorRaw = this.rawNow();
545
+ this.anchorAt = this.clock.now();
546
+ }
547
+ rawNow() {
548
+ return this.anchorRaw + (this.clock.now() - this.anchorAt) * this.rate;
549
+ }
550
+ schedule() {
551
+ const generation = this.generation;
552
+ this.clock.frame().then(() => {
553
+ if (generation !== this.generation || !this.playing) return;
554
+ this.tick(true);
555
+ if (this.playing) this.schedule();
556
+ });
557
+ }
558
+ /** Advances the playhead to the clock's time; `notify` emits the change. */
559
+ tick(notify) {
560
+ if (!this.playing) return;
561
+ const d = this.duration;
562
+ const raw = this.rawNow();
563
+ let ended = false;
564
+ if (this.loop === "none" || d <= 0) {
565
+ if (this.rate > 0 && raw >= d) {
566
+ this.time = d;
567
+ ended = true;
568
+ } else if (this.rate < 0 && raw <= 0) {
569
+ this.time = 0;
570
+ ended = true;
571
+ } else this.time = this.clamp(raw);
572
+ } else {
573
+ this.time = wrapAnimationTime(raw, d, this.loop);
574
+ }
575
+ if (ended) {
576
+ this.playing = false;
577
+ this.generation++;
578
+ }
579
+ if (notify || ended) this.emit(true);
580
+ if (ended) this.onEnd?.();
581
+ }
582
+ snapshot() {
583
+ return {
584
+ time: this.time,
585
+ playing: this.playing,
586
+ scrubbing: this.scrubbing,
587
+ loop: this.loop,
588
+ playbackRate: this.rate,
589
+ duration: this.duration
590
+ };
591
+ }
592
+ emit(timeChanged) {
593
+ const next = this.snapshot();
594
+ const prev = this.cached;
595
+ if (prev.time === next.time && prev.playing === next.playing && prev.scrubbing === next.scrubbing && prev.loop === next.loop && prev.playbackRate === next.playbackRate && prev.duration === next.duration) {
596
+ return;
597
+ }
598
+ this.cached = next;
599
+ if (timeChanged && prev.time !== next.time) this.onTime?.(next.time);
600
+ for (const listener of this.listeners) listener(next);
601
+ }
602
+ };
603
+ function createAnimationPlayer(options) {
604
+ return new AnimationPlayer(options);
605
+ }
606
+
607
+ export {
608
+ ANIMATION_EASING_PRESETS,
609
+ ANIMATION_EASING_LABELS,
610
+ CUBIC_BEZIER_PRESETS,
611
+ cubicBezierAt,
612
+ springAt,
613
+ resolveEasing,
614
+ evaluateEasing,
615
+ easingFromInfluence,
616
+ parseCssEasing,
617
+ isAnimationEasing,
618
+ easingToCss,
619
+ easingLabel,
620
+ rgbToOklab,
621
+ oklabToRgb,
622
+ parseOklab,
623
+ formatOklab,
624
+ mixColorsOklab,
625
+ ANIMATION_TIME_EPSILON,
626
+ isAnimationKeyframe,
627
+ normalizeKeyframes,
628
+ interpolateAnimationValue,
629
+ evaluateTrack,
630
+ wrapAnimationTime,
631
+ pageHidden,
632
+ realClock,
633
+ ManualClock,
634
+ AnimationPlayer,
635
+ createAnimationPlayer
636
+ };
637
+ //# sourceMappingURL=chunk-PWJARCI4.js.map