@pixodesk/svg-animator-web 1.0.26 → 1.0.27

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
@@ -63,114 +63,582 @@ var __objRest2 = (source, exclude) => {
63
63
  }
64
64
  return target;
65
65
  };
66
- function pathStr(path) {
67
- if (!path.length) return ".";
68
- let result = "";
69
- for (const seg of path) {
70
- if (seg.startsWith("[")) result += seg;
71
- else result += (result ? "." : "") + seg;
72
- }
73
- return result;
74
- }
75
- var Base = class {
76
- _canSanitize(raw) {
77
- return this.isValid(raw);
66
+ function bezierToSvgPath(path, forceCurves = false) {
67
+ var _a, _b, _c, _d;
68
+ const v = path.v;
69
+ const i = path.i;
70
+ const o = path.o;
71
+ const c = path.c;
72
+ if (!v.length) return "";
73
+ const d = [];
74
+ const len = v.length;
75
+ d.push("M" + v[0][0] + "," + v[0][1]);
76
+ for (let idx = 1; idx < len; idx++) {
77
+ const prevV = v[idx - 1];
78
+ const prevO = (_a = o == null ? void 0 : o[idx - 1]) != null ? _a : prevV;
79
+ const currI = (_b = i == null ? void 0 : i[idx]) != null ? _b : v[idx];
80
+ const currV = v[idx];
81
+ const isLine = !forceCurves && (prevO[0] === prevV[0] && prevO[1] === prevV[1]) && (currI[0] === currV[0] && currI[1] === currV[1]);
82
+ if (isLine) {
83
+ d.push("L" + currV[0] + "," + currV[1]);
84
+ } else {
85
+ d.push("C" + prevO[0] + "," + prevO[1] + "," + currI[0] + "," + currI[1] + "," + currV[0] + "," + currV[1]);
86
+ }
78
87
  }
79
- optional() {
80
- return new Optional(this);
88
+ if (c && len > 0) {
89
+ const lastV = v[len - 1];
90
+ const lastO = (_c = o == null ? void 0 : o[len - 1]) != null ? _c : lastV;
91
+ const firstI = (_d = i == null ? void 0 : i[0]) != null ? _d : v[0];
92
+ const firstV = v[0];
93
+ const isLine = !forceCurves && (lastO[0] === lastV[0] && lastO[1] === lastV[1]) && (firstI[0] === firstV[0] && firstI[1] === firstV[1]);
94
+ if (!isLine) {
95
+ d.push("C" + lastO[0] + "," + lastO[1] + "," + firstI[0] + "," + firstI[1] + "," + firstV[0] + "," + firstV[1]);
96
+ }
97
+ d.push("z");
81
98
  }
82
- };
83
- var Optional = class extends Base {
84
- constructor(inner) {
85
- super();
86
- this.inner = inner;
87
- this._default = void 0;
99
+ return d.join("");
100
+ }
101
+ function interpolateNum(a, b, t) {
102
+ return a + (b - a) * t;
103
+ }
104
+ function interpolateVec(a, b, t) {
105
+ const res = [];
106
+ const count = Math.max(a.length, b.length);
107
+ for (let i = 0; i < count; i++) {
108
+ res[i] = interpolateNum(a[i] || 0, b[i] || 0, t);
88
109
  }
89
- sanitize(raw) {
90
- if (raw === void 0 || raw === null) return void 0;
91
- return this.inner._canSanitize(raw) ? this.inner.sanitize(raw) : void 0;
110
+ return res;
111
+ }
112
+ function interpolateColor(a, b, t) {
113
+ return [
114
+ interpolateNum(a[0] || 0, b[0] || 0, t),
115
+ interpolateNum(a[1] || 0, b[1] || 0, t),
116
+ interpolateNum(a[2] || 0, b[2] || 0, t),
117
+ interpolateNum(a[3] === void 0 ? 1 : a[3], b[3] === void 0 ? 1 : b[3], t)
118
+ ];
119
+ }
120
+ function interpolateBeziers(paths1, paths2, progress) {
121
+ const count = Math.max(paths1.length, paths2.length);
122
+ const res = [];
123
+ for (let i = 0; i < count; i++) {
124
+ res.push(interpolateBezier(paths1[i], paths2[i], progress));
92
125
  }
93
- isValid(raw, ctx, path) {
94
- if (raw === void 0 || raw === null) return true;
95
- return this.inner.isValid(raw, ctx, path);
126
+ return res;
127
+ }
128
+ function interpolateBezier(path1, path2, progress) {
129
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
130
+ if (!path1 || !path2) return path1 || path2 || { v: [] };
131
+ const t = Math.min(Math.max(progress, 0), 1);
132
+ const len = Math.min(path1.v.length, path2.v.length);
133
+ const v = [];
134
+ const i = [];
135
+ const o = [];
136
+ for (let idx = 0; idx < len; idx++) {
137
+ const v1 = path1.v[idx];
138
+ const v2 = path2.v[idx];
139
+ v.push(interpolateVec(v1, v2, t));
140
+ const i1 = (_b = (_a = path1.i) == null ? void 0 : _a[idx]) != null ? _b : v1;
141
+ const i2 = (_d = (_c = path2.i) == null ? void 0 : _c[idx]) != null ? _d : v2;
142
+ i.push(interpolateVec(i1, i2, t));
143
+ const o1 = (_f = (_e = path1.o) == null ? void 0 : _e[idx]) != null ? _f : v1;
144
+ const o2 = (_h = (_g = path2.o) == null ? void 0 : _g[idx]) != null ? _h : v2;
145
+ o.push(interpolateVec(o1, o2, t));
96
146
  }
97
- _canSanitize(raw) {
98
- return raw === void 0 || raw === null || this.inner._canSanitize(raw);
147
+ return { v, i: i.length ? i : void 0, o: o.length ? o : void 0, c: (_i = path1.c) != null ? _i : path2.c };
148
+ }
149
+ function remap(value, inMin, inMax, outMin, outMax) {
150
+ if (inMax === inMin) return outMin;
151
+ const t = (value - inMin) / (inMax - inMin);
152
+ return outMin + t * (outMax - outMin);
153
+ }
154
+ function solveCubicBezierX(p1x, p2x, x) {
155
+ if (x <= 0) return 0;
156
+ if (x >= 1) return 1;
157
+ const cx = 3 * p1x;
158
+ const bx = 3 * (p2x - p1x) - cx;
159
+ const ax = 1 - cx - bx;
160
+ function sampleX(t) {
161
+ return ((ax * t + bx) * t + cx) * t;
99
162
  }
100
- };
101
- var Str = class extends Base {
102
- constructor(_default = "") {
103
- super();
104
- this._default = _default;
163
+ function sampleDX(t) {
164
+ return (3 * ax * t + 2 * bx) * t + cx;
105
165
  }
106
- sanitize(raw) {
107
- return typeof raw === "string" ? raw : this._default;
166
+ let t2 = x;
167
+ let t0 = 0;
168
+ let t1 = 1;
169
+ for (let i = 0; i < 8; i++) {
170
+ const x2 = sampleX(t2) - x;
171
+ if (Math.abs(x2) < 1e-6) return t2;
172
+ const d2 = sampleDX(t2);
173
+ if (Math.abs(d2) < 1e-6) break;
174
+ t2 -= x2 / d2;
108
175
  }
109
- isValid(raw, ctx, path) {
110
- if (typeof raw === "string") return true;
111
- ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected string, got " + typeof raw);
112
- return false;
176
+ t2 = x;
177
+ while (t0 < t1) {
178
+ const x2 = sampleX(t2);
179
+ if (Math.abs(x2 - x) < 1e-6) return t2;
180
+ if (x > x2) t0 = t2;
181
+ else t1 = t2;
182
+ t2 = (t1 + t0) / 2;
113
183
  }
114
- };
115
- var Num = class extends Base {
116
- constructor(_default = 0) {
117
- super();
118
- this._default = _default;
184
+ return t2;
185
+ }
186
+ function cubicBezier(easing) {
187
+ const [p1x, p1y, p2x, p2y] = easing;
188
+ const cy = 3 * p1y;
189
+ const by = 3 * (p2y - p1y) - cy;
190
+ const ay = 1 - cy - by;
191
+ function sampleCurveY(t) {
192
+ return ((ay * t + by) * t + cy) * t;
119
193
  }
120
- sanitize(raw) {
121
- return typeof raw === "number" && isFinite(raw) ? raw : this._default;
194
+ return function(x) {
195
+ return sampleCurveY(solveCubicBezierX(p1x, p2x, x));
196
+ };
197
+ }
198
+ function lerp2(a, b, t) {
199
+ return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
200
+ }
201
+ function subdivideCubicBezier(p0, p1, p2, p3, t) {
202
+ const q0 = lerp2(p0, p1, t);
203
+ const q1 = lerp2(p1, p2, t);
204
+ const q2 = lerp2(p2, p3, t);
205
+ const r0 = lerp2(q0, q1, t);
206
+ const r1 = lerp2(q1, q2, t);
207
+ const s = lerp2(r0, r1, t);
208
+ return {
209
+ left: [p0, q0, r0, s],
210
+ right: [s, r1, q2, p3]
211
+ };
212
+ }
213
+ function splitEasing(easing, xFraction) {
214
+ if (!easing) return { left: void 0, right: void 0 };
215
+ if (xFraction <= 0) return { left: void 0, right: easing };
216
+ if (xFraction >= 1) return { left: easing, right: void 0 };
217
+ const [x1, y1, x2, y2] = easing;
218
+ const t = solveCubicBezierX(x1, x2, xFraction);
219
+ const p0 = [0, 0];
220
+ const p1 = [x1, y1];
221
+ const p2 = [x2, y2];
222
+ const p3 = [1, 1];
223
+ const { left, right } = subdivideCubicBezier(p0, p1, p2, p3, t);
224
+ const sx = left[3][0];
225
+ const sy = left[3][1];
226
+ let leftEasing;
227
+ if (sx > 1e-9 && Math.abs(sy) > 1e-9) {
228
+ leftEasing = [
229
+ left[1][0] / sx,
230
+ left[1][1] / sy,
231
+ left[2][0] / sx,
232
+ left[2][1] / sy
233
+ ];
122
234
  }
123
- isValid(raw, ctx, path) {
124
- if (typeof raw === "number" && isFinite(raw)) return true;
125
- ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected finite number, got " + JSON.stringify(raw));
126
- return false;
235
+ let rightEasing;
236
+ const rx = 1 - sx;
237
+ const ry = 1 - sy;
238
+ if (rx > 1e-9 && Math.abs(ry) > 1e-9) {
239
+ rightEasing = [
240
+ (right[1][0] - sx) / rx,
241
+ (right[1][1] - sy) / ry,
242
+ (right[2][0] - sx) / rx,
243
+ (right[2][1] - sy) / ry
244
+ ];
127
245
  }
128
- };
129
- var Bool = class extends Base {
130
- constructor(_default = false) {
131
- super();
132
- this._default = _default;
246
+ return { left: leftEasing, right: rightEasing };
247
+ }
248
+ function reverseEasing(easing) {
249
+ if (!easing) return void 0;
250
+ return [1 - easing[2], 1 - easing[3], 1 - easing[0], 1 - easing[1]];
251
+ }
252
+ function toRGBA(color) {
253
+ const r = Math.round(color[0] * 255);
254
+ const g = Math.round(color[1] * 255);
255
+ const b = Math.round(color[2] * 255);
256
+ return color.length === 4 ? "rgba(" + r + "," + g + "," + b + "," + color[3] + ")" : "rgb(" + r + "," + g + "," + b + ")";
257
+ }
258
+ function parseRgba(s) {
259
+ var _a;
260
+ const inner = (_a = s.match(/rgba?\((.*)\)/)) == null ? void 0 : _a[1];
261
+ if (!inner) throw new Error("Invalid rgb/rgba format");
262
+ const parts = inner.split(",").map((v) => +v.trim());
263
+ return [parts[0] / 255, parts[1] / 255, parts[2] / 255, ...parts[3] !== void 0 ? [parts[3]] : []];
264
+ }
265
+ function parseHex(s) {
266
+ const hex = s.slice(1);
267
+ const isShort = hex.length <= 4;
268
+ const r = isShort ? hex[0] + hex[0] : hex.slice(0, 2);
269
+ const g = isShort ? hex[1] + hex[1] : hex.slice(2, 4);
270
+ const b = isShort ? hex[2] + hex[2] : hex.slice(4, 6);
271
+ const a = hex.length === 4 ? hex[3] + hex[3] : hex.length === 8 ? hex.slice(6, 8) : null;
272
+ const result = [
273
+ parseInt(r, 16) / 255,
274
+ parseInt(g, 16) / 255,
275
+ parseInt(b, 16) / 255
276
+ ];
277
+ if (a !== null) {
278
+ result.push(parseInt(a, 16) / 255);
133
279
  }
134
- sanitize(raw) {
135
- return typeof raw === "boolean" ? raw : this._default;
280
+ return result;
281
+ }
282
+ function parseColor(s) {
283
+ if (!s) return void 0;
284
+ if (Array.isArray(s)) return s;
285
+ if (typeof s !== "string") return void 0;
286
+ if (s.startsWith("#")) {
287
+ return parseHex(s);
288
+ } else if (s.startsWith("rgb")) {
289
+ return parseRgba(s);
290
+ } else {
291
+ console.warn("Unsupported color format: " + s);
136
292
  }
137
- isValid(raw, ctx, path) {
138
- if (typeof raw === "boolean") return true;
139
- ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected boolean, got " + typeof raw);
140
- return false;
293
+ return void 0;
294
+ }
295
+ var COLOUR_ATTR_NAMES = /* @__PURE__ */ new Set(["color", "fill", "flood-color", "lighting-color", "stop-color", "stroke"]);
296
+ var TRANSFORM_FN_NAMES = /* @__PURE__ */ new Set(["translate", "rotate", "scale", "skew"]);
297
+ var PCT_BASED_ATTR_NAMES = /* @__PURE__ */ new Set(["offset-distance", "offsetDistance"]);
298
+ function composeTransformParts(parts, opts) {
299
+ var _a;
300
+ if (!parts) return "";
301
+ const withUnits = (_a = opts == null ? void 0 : opts.withUnits) != null ? _a : true;
302
+ const segs = [];
303
+ const t = parts.translate;
304
+ const o = parts.origin;
305
+ const r = parts.rotate;
306
+ const k = parts.skew;
307
+ const s = parts.scale;
308
+ const tu = withUnits ? "px" : "";
309
+ const ru = withUnits ? "deg" : "";
310
+ if (t) segs.push("translate(" + t[0] + tu + "," + t[1] + tu + ")");
311
+ if (o) segs.push("translate(" + o[0] + tu + "," + o[1] + tu + ")");
312
+ if (r !== void 0 && r !== null) segs.push("rotate(" + r + ru + ")");
313
+ if (k !== void 0 && k !== null) segs.push("skewX(" + k + ru + ")");
314
+ if (s) segs.push("scale(" + s[0] + "," + s[1] + ")");
315
+ if (o) segs.push("translate(" + -o[0] + tu + "," + -o[1] + tu + ")");
316
+ return segs.join("");
317
+ }
318
+ var STYLE_ATTR_NAMES = /* @__PURE__ */ new Set(["offset-distance", "offsetDistance"]);
319
+ var DEFAULT_DURATION_MS = 1e3;
320
+ function kebabToCamelCaseWord(kebab) {
321
+ return kebab.includes("-") ? kebab.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()) : kebab;
322
+ }
323
+ function isCamelCaseWord(word) {
324
+ return !word.includes("-") && /[a-z][A-Z]/.test(word);
325
+ }
326
+ var SVG_CAMEL_CASE_ATTRS = /* @__PURE__ */ new Set([
327
+ // Transform/positioning
328
+ "viewBox",
329
+ "preserveAspectRatio",
330
+ // Gradient
331
+ "gradientUnits",
332
+ "gradientTransform",
333
+ "spreadMethod",
334
+ // Pattern
335
+ "patternUnits",
336
+ "patternContentUnits",
337
+ "patternTransform",
338
+ // Clipping/masking
339
+ "clipPathUnits",
340
+ "maskUnits",
341
+ "maskContentUnits",
342
+ // Marker (SVG spec keeps these camelCase, like viewBox)
343
+ "markerUnits",
344
+ "markerWidth",
345
+ "markerHeight",
346
+ "refX",
347
+ "refY",
348
+ // Text
349
+ "textLength",
350
+ "lengthAdjust",
351
+ "startOffset",
352
+ // Filter
353
+ "filterUnits",
354
+ "primitiveUnits",
355
+ "tableValues",
356
+ // feFuncR/G/B/A transfer table (type="table")
357
+ "stdDeviation",
358
+ "baseFrequency",
359
+ "numOctaves",
360
+ "surfaceScale",
361
+ "diffuseConstant",
362
+ "specularConstant",
363
+ "specularExponent",
364
+ "kernelMatrix",
365
+ "kernelUnitLength",
366
+ "edgeMode",
367
+ "preserveAlpha",
368
+ "targetX",
369
+ "targetY"
370
+ // // Animation
371
+ // 'attributeName',
372
+ // 'attributeType',
373
+ // 'calcMode',
374
+ // 'keyTimes',
375
+ // 'keySplines',
376
+ // 'repeatCount',
377
+ // 'repeatDur'
378
+ ]);
379
+ function camelCaseToKebabWordIfNeeded(camel) {
380
+ return SVG_CAMEL_CASE_ATTRS.has(camel) ? camel : camel.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
381
+ }
382
+ function clamp(value, min, max) {
383
+ return Math.max(min, Math.min(value, max));
384
+ }
385
+ function bezier2D_pointAt(P0, P1, P2, P3, t) {
386
+ if (t <= 0) return [P0[0], P0[1]];
387
+ if (t >= 1) return [P3[0], P3[1]];
388
+ const u = 1 - t;
389
+ const u2 = u * u;
390
+ const u3 = u2 * u;
391
+ const t2 = t * t;
392
+ const t3 = t2 * t;
393
+ const w0 = u3;
394
+ const w1 = 3 * t * u2;
395
+ const w2 = 3 * t2 * u;
396
+ const w3 = t3;
397
+ return [
398
+ w0 * P0[0] + w1 * P1[0] + w2 * P2[0] + w3 * P3[0],
399
+ w0 * P0[1] + w1 * P1[1] + w2 * P2[1] + w3 * P3[1]
400
+ ];
401
+ }
402
+ var BEZIER_T_NUDGE = 1e-4;
403
+ function bezier2D_derivativeAt(P0, P1, P2, P3, t) {
404
+ const result = _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t);
405
+ if (result[0] === 0 && result[1] === 0) {
406
+ const nudgedT = t < 0.5 ? t + BEZIER_T_NUDGE : t - BEZIER_T_NUDGE;
407
+ return _bezier2D_derivativeAtRaw(P0, P1, P2, P3, nudgedT);
408
+ }
409
+ return result;
410
+ }
411
+ function _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t) {
412
+ const u = 1 - t;
413
+ const a = 3 * u * u;
414
+ const b = 6 * t * u;
415
+ const c = 3 * t * t;
416
+ return [
417
+ a * (P1[0] - P0[0]) + b * (P2[0] - P1[0]) + c * (P3[0] - P2[0]),
418
+ a * (P1[1] - P0[1]) + b * (P2[1] - P1[1]) + c * (P3[1] - P2[1])
419
+ ];
420
+ }
421
+ function bezier2D_arcLengthLUT(P0, P1, P2, P3, steps = 100) {
422
+ const n = steps + 1;
423
+ const ts = new Float64Array(n);
424
+ const ds = new Float64Array(n);
425
+ let prev = bezier2D_pointAt(P0, P1, P2, P3, 0);
426
+ ts[0] = 0;
427
+ ds[0] = 0;
428
+ let cum = 0;
429
+ for (let i = 1; i < n; i++) {
430
+ const t = i / steps;
431
+ const cur = bezier2D_pointAt(P0, P1, P2, P3, t);
432
+ const dx = cur[0] - prev[0];
433
+ const dy = cur[1] - prev[1];
434
+ cum += Math.sqrt(dx * dx + dy * dy);
435
+ ts[i] = t;
436
+ ds[i] = cum;
437
+ prev = cur;
438
+ }
439
+ return { ts, ds };
440
+ }
441
+ function bezier2D_tForDistance(lut, distance) {
442
+ const { ts, ds } = lut;
443
+ const last = ds.length - 1;
444
+ if (distance <= 0) return ts[0];
445
+ if (distance >= ds[last]) return ts[last];
446
+ let lo = 1;
447
+ let hi = last;
448
+ while (lo < hi) {
449
+ const mid = lo + hi >>> 1;
450
+ if (ds[mid] < distance) lo = mid + 1;
451
+ else hi = mid;
452
+ }
453
+ const dPrev = ds[hi - 1];
454
+ const dCur = ds[hi];
455
+ const span = dCur - dPrev;
456
+ const frac = span > 0 ? (distance - dPrev) / span : 0;
457
+ return ts[hi - 1] + frac * (ts[hi] - ts[hi - 1]);
458
+ }
459
+ function bezier2D_arcAtT(lut, t) {
460
+ const { ts, ds } = lut;
461
+ const last = ts.length - 1;
462
+ if (t <= ts[0]) return ds[0];
463
+ if (t >= ts[last]) return ds[last];
464
+ let lo = 1, hi = last;
465
+ while (lo < hi) {
466
+ const mid = lo + hi >>> 1;
467
+ if (ts[mid] < t) lo = mid + 1;
468
+ else hi = mid;
469
+ }
470
+ const tPrev = ts[hi - 1];
471
+ const span = ts[hi] - tPrev;
472
+ const frac = span > 0 ? (t - tPrev) / span : 0;
473
+ return ds[hi - 1] + frac * (ds[hi] - ds[hi - 1]);
474
+ }
475
+ function invertEasing(easing) {
476
+ if (!easing) return (y) => y;
477
+ const flipped = [easing[1], easing[0], easing[3], easing[2]];
478
+ return cubicBezier(flipped);
479
+ }
480
+ function isScrollTimeline(config) {
481
+ return (config == null ? void 0 : config.timelineSource) === "scroll";
482
+ }
483
+ function scrollTotalDurationMs(config) {
484
+ const duration = typeof (config == null ? void 0 : config.duration) === "number" && config.duration > 0 ? config.duration : DEFAULT_DURATION_MS;
485
+ const iterations = typeof (config == null ? void 0 : config.iterations) === "number" && config.iterations > 0 ? config.iterations : 1;
486
+ return duration * iterations;
487
+ }
488
+ function scrollPhaseInterval(phase, subjectSize, scrollportSize) {
489
+ const s = subjectSize, vp = scrollportSize;
490
+ switch (phase) {
491
+ case "cover":
492
+ return [0, s + vp];
493
+ case "entry":
494
+ return [0, Math.min(s, vp)];
495
+ case "contain":
496
+ return [Math.min(s, vp), Math.max(s, vp)];
497
+ case "exit":
498
+ return [Math.max(s, vp), s + vp];
499
+ case "entry-crossing":
500
+ return [0, s];
501
+ case "exit-crossing":
502
+ return [vp, s + vp];
503
+ }
504
+ }
505
+ var DEFAULT_PHASE = "cover";
506
+ function resolveRangePointU(point, defaultFraction, subjectSize, scrollportSize) {
507
+ var _a;
508
+ const [u0, u1] = scrollPhaseInterval((_a = point == null ? void 0 : point.phase) != null ? _a : DEFAULT_PHASE, subjectSize, scrollportSize);
509
+ const fraction = typeof (point == null ? void 0 : point.fraction) === "number" ? point.fraction : defaultFraction;
510
+ return u0 + fraction * (u1 - u0);
511
+ }
512
+ function scrollViewProgress(subjectStart, subjectSize, scrollportSize, range) {
513
+ const u = scrollportSize - subjectStart;
514
+ const uStart = resolveRangePointU(range == null ? void 0 : range.start, 0, subjectSize, scrollportSize);
515
+ const uEnd = resolveRangePointU(range == null ? void 0 : range.end, 1, subjectSize, scrollportSize);
516
+ if (uEnd <= uStart) return u >= uEnd ? 1 : 0;
517
+ return clamp((u - uStart) / (uEnd - uStart), 0, 1);
518
+ }
519
+ function scrollOffsetProgress(offset, maxOffset, range) {
520
+ var _a, _b;
521
+ const raw = maxOffset > 0 ? clamp(offset / maxOffset, 0, 1) : 1;
522
+ const start = typeof ((_a = range == null ? void 0 : range.start) == null ? void 0 : _a.fraction) === "number" ? range.start.fraction : 0;
523
+ const end = typeof ((_b = range == null ? void 0 : range.end) == null ? void 0 : _b.fraction) === "number" ? range.end.fraction : 1;
524
+ if (end <= start) return raw >= end ? 1 : 0;
525
+ return clamp((raw - start) / (end - start), 0, 1);
526
+ }
527
+ function scrollResolveAxis(axis, writingMode) {
528
+ const a = axis != null ? axis : "block";
529
+ if (a === "x" || a === "y") return a;
530
+ const vertical = !!writingMode && writingMode.startsWith("vertical");
531
+ if (a === "inline") return vertical ? "y" : "x";
532
+ return vertical ? "x" : "y";
533
+ }
534
+ function pathStr(path) {
535
+ if (!path.length) return ".";
536
+ let result = "";
537
+ for (const seg of path) {
538
+ if (seg.startsWith("[")) result += seg;
539
+ else result += (result ? "." : "") + seg;
540
+ }
541
+ return result;
542
+ }
543
+ var Base = class {
544
+ _canSanitize(raw) {
545
+ return this.isValid(raw);
546
+ }
547
+ optional() {
548
+ return new Optional(this);
141
549
  }
142
550
  };
143
- var Literal = class extends Base {
144
- constructor(value) {
551
+ var Optional = class extends Base {
552
+ constructor(inner) {
145
553
  super();
146
- this.value = value;
147
- this._default = value;
554
+ this.inner = inner;
555
+ this._default = void 0;
148
556
  }
149
557
  sanitize(raw) {
150
- return raw === this.value ? this.value : this._default;
558
+ if (raw === void 0 || raw === null) return void 0;
559
+ return this.inner._canSanitize(raw) ? this.inner.sanitize(raw) : void 0;
151
560
  }
152
561
  isValid(raw, ctx, path) {
153
- if (raw === this.value) return true;
154
- ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected " + JSON.stringify(this.value) + ", got " + JSON.stringify(raw));
155
- return false;
562
+ if (raw === void 0 || raw === null) return true;
563
+ return this.inner.isValid(raw, ctx, path);
564
+ }
565
+ _canSanitize(raw) {
566
+ return raw === void 0 || raw === null || this.inner._canSanitize(raw);
156
567
  }
157
568
  };
158
- var Enum = class extends Base {
159
- constructor(values, defaultVal) {
569
+ var Str = class extends Base {
570
+ constructor(_default = "") {
160
571
  super();
161
- this.values = values;
162
- this._default = defaultVal != null ? defaultVal : values[0];
572
+ this._default = _default;
163
573
  }
164
574
  sanitize(raw) {
165
- return this.values.includes(raw) ? raw : this._default;
575
+ return typeof raw === "string" ? raw : this._default;
166
576
  }
167
577
  isValid(raw, ctx, path) {
168
- if (this.values.includes(raw)) return true;
169
- ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected one of " + this.values.map((v) => JSON.stringify(v)).join(" | ") + ", got " + JSON.stringify(raw));
578
+ if (typeof raw === "string") return true;
579
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected string, got " + typeof raw);
170
580
  return false;
171
581
  }
172
582
  };
173
- var Union = class extends Base {
583
+ var Num = class extends Base {
584
+ constructor(_default = 0) {
585
+ super();
586
+ this._default = _default;
587
+ }
588
+ sanitize(raw) {
589
+ return typeof raw === "number" && isFinite(raw) ? raw : this._default;
590
+ }
591
+ isValid(raw, ctx, path) {
592
+ if (typeof raw === "number" && isFinite(raw)) return true;
593
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected finite number, got " + JSON.stringify(raw));
594
+ return false;
595
+ }
596
+ };
597
+ var Bool = class extends Base {
598
+ constructor(_default = false) {
599
+ super();
600
+ this._default = _default;
601
+ }
602
+ sanitize(raw) {
603
+ return typeof raw === "boolean" ? raw : this._default;
604
+ }
605
+ isValid(raw, ctx, path) {
606
+ if (typeof raw === "boolean") return true;
607
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected boolean, got " + typeof raw);
608
+ return false;
609
+ }
610
+ };
611
+ var Literal = class extends Base {
612
+ constructor(value) {
613
+ super();
614
+ this.value = value;
615
+ this._default = value;
616
+ }
617
+ sanitize(raw) {
618
+ return raw === this.value ? this.value : this._default;
619
+ }
620
+ isValid(raw, ctx, path) {
621
+ if (raw === this.value) return true;
622
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected " + JSON.stringify(this.value) + ", got " + JSON.stringify(raw));
623
+ return false;
624
+ }
625
+ };
626
+ var Enum = class extends Base {
627
+ constructor(values, defaultVal) {
628
+ super();
629
+ this.values = values;
630
+ this._default = defaultVal != null ? defaultVal : values[0];
631
+ }
632
+ sanitize(raw) {
633
+ return this.values.includes(raw) ? raw : this._default;
634
+ }
635
+ isValid(raw, ctx, path) {
636
+ if (this.values.includes(raw)) return true;
637
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected one of " + this.values.map((v) => JSON.stringify(v)).join(" | ") + ", got " + JSON.stringify(raw));
638
+ return false;
639
+ }
640
+ };
641
+ var Union = class extends Base {
174
642
  constructor(schemas, defaultVal) {
175
643
  super();
176
644
  this.schemas = schemas;
@@ -725,6 +1193,22 @@ var PxDefsSchema = implementsInterface()(px.object({
725
1193
  styles: px.record(px.any()).optional(),
726
1194
  glyphs: px.record(PxGlyphFontSchema).optional()
727
1195
  }));
1196
+ var PX_SCROLL_PHASES = ["cover", "contain", "entry", "exit", "entry-crossing", "exit-crossing"];
1197
+ var PxScrollRangePointSchema = implementsInterface()(px.object({
1198
+ phase: px.enum(PX_SCROLL_PHASES).optional(),
1199
+ fraction: px.number().optional()
1200
+ }));
1201
+ var PxScrollRangeSchema = px.object({
1202
+ start: PxScrollRangePointSchema.optional(),
1203
+ end: PxScrollRangePointSchema.optional()
1204
+ });
1205
+ var PxScrollSchema = implementsInterface()(px.object({
1206
+ driver: px.enum(["custom", "native"]).optional(),
1207
+ kind: px.enum(["view", "scroll"]).optional(),
1208
+ axis: px.enum(["block", "inline", "x", "y"]).optional(),
1209
+ source: px.enum(["nearest", "root"]).optional(),
1210
+ range: PxScrollRangeSchema.optional()
1211
+ }));
728
1212
  var PxAnimatorConfigSchema = implementsInterface()(px.object({
729
1213
  mode: px.enum([PxAnimatorMode.auto, PxAnimatorMode.waapi, PxAnimatorMode.frames]).optional(),
730
1214
  duration: px.number().optional(),
@@ -740,6 +1224,7 @@ var PxAnimatorConfigSchema = implementsInterface()(px.object({
740
1224
  definitions: PxDefsSchema.optional(),
741
1225
  animateById: px.record(PxElementAnimationSchema).optional(),
742
1226
  timelineSource: px.string().optional(),
1227
+ scroll: PxScrollSchema.optional(),
743
1228
  debugInstName: px.string().optional()
744
1229
  }));
745
1230
  var PxBindingSchema = implementsInterface()(px.object({
@@ -819,531 +1304,117 @@ var PxCloneEffectSchema = implementsInterface()(px.object({
819
1304
  sourceId: px.string().optional(),
820
1305
  retime: PxRetimeEffectSchema.optional()
821
1306
  }));
822
- var PxGradientStopSchema = implementsInterface()(px.object({
823
- offset: px.number(),
824
- color: px.string()
825
- }));
826
- var PxAnimatableGradientStopsSchema = px.union([
827
- px.array(PxGradientStopSchema),
828
- px.object({ value: px.array(PxGradientStopSchema) }),
829
- PxPropertyAnimationSchema
830
- ]);
831
- var PxFillGradientEffectSchema = implementsInterface()(px.object({
832
- // Contextual kind — the `type` convention, see `PxNodeBase.type`.
833
- type: px.enum([PxGradientType.linear, PxGradientType.radial]),
834
- p1: PxAnimatableVec2Schema.optional(),
835
- p2: PxAnimatableVec2Schema.optional(),
836
- c: PxAnimatableVec2Schema.optional(),
837
- r: PxAnimatableNumberSchema.optional(),
838
- fp: PxAnimatableVec2Schema.optional(),
839
- stops: PxAnimatableGradientStopsSchema.optional(),
840
- gradientUnits: px.enum([PxGradientUnits.userSpaceOnUse, PxGradientUnits.objectBoundingBox]).optional(),
841
- spreadMethod: px.enum([PxGradientSpreadMethod.pad, PxGradientSpreadMethod.reflect, PxGradientSpreadMethod.repeat]).optional(),
842
- gradientTransform: px.string().optional()
843
- }));
844
- var PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;
845
- var PxTextPathEffectSchema = implementsInterface()(px.object({
846
- path: px.string(),
847
- pathOverflow: px.enum([PxPathOverflow.clip, PxPathOverflow.extend]).optional(),
848
- lengthAdjust: px.enum([PxLengthAdjust.spacing, PxLengthAdjust.spacingAndGlyphs]).optional(),
849
- method: px.enum([PxTextPathMethod.align, PxTextPathMethod.stretch]).optional(),
850
- spacing: px.enum([PxTextPathSpacing.auto, PxTextPathSpacing.exact]).optional(),
851
- startOffset: PxAnimatableNumberSchema.optional(),
852
- textLength: PxAnimatableNumberSchema.optional()
853
- }));
854
- var PxTextEffectSchema = implementsInterface()(px.object({
855
- useGlyphs: px.boolean().optional()
856
- }));
857
- var PxEffectsSchema = implementsInterface()(px.object({
858
- transformBy: PxTransformByEffectSchema.optional(),
859
- repeater: PxRepeaterEffectSchema.optional(),
860
- maskedBy: PxMaskedByEffectSchema.optional(),
861
- clipPath: PxClipPathEffectSchema.optional(),
862
- trimPath: PxTrimPathEffectSchema.optional(),
863
- clone: PxCloneEffectSchema.optional(),
864
- fillGradient: PxFillGradientEffectSchema.optional(),
865
- strokeGradient: PxStrokeGradientEffectSchema.optional(),
866
- textPath: PxTextPathEffectSchema.optional(),
867
- text: PxTextEffectSchema.optional()
868
- }));
869
- function validateNodeEffects(root, opts) {
870
- const warnings = [];
871
- const walk = (node, path) => {
872
- if (node && node.effects) {
873
- const ctx = { errors: [], warnings: [], strict: !!(opts == null ? void 0 : opts.strict) };
874
- const ok = PxEffectsSchema.isValid(node.effects, ctx, [path + ".effects"]);
875
- if (!ok) {
876
- for (const err of ctx.errors) warnings.push(err);
877
- }
878
- }
879
- if (node && Array.isArray(node.children)) {
880
- node.children.forEach((c, i) => walk(c, path + ".children[" + i + "]"));
881
- }
882
- };
883
- walk(root, "root");
884
- return warnings;
885
- }
886
- var PxNodeBase = px.openObject({
887
- // CONVENTION (SCHEMA-DESIGN R1 / issues N4): `type` is the ONE word for "what
888
- // kind of thing is this", discriminated by its CARRIER — here the node TAG
889
- // (`rect`, `text`), and inside a sub-object that object's kind (`clone.type`,
890
- // `fillGradient.type`, editor `preset.type`). Each sits in its own object, so
891
- // the carrier disambiguates completely; synonyms (`cloneKind`, `presetShape`)
892
- // would add words that all mean "type" and still need the carrier to read.
893
- // Guarding a `type` SLOT against a wrong VALUE is the job of strict enums
894
- // (issues V3), never of distinct key names.
895
- type: px.string(),
896
- id: px.string().optional(),
897
- meta: px.any().optional(),
898
- // Player-effects bucket emitted by the Editor's lightweight design format.
899
- // Consumed and removed by `applyPlayerEffects` before any other normalisation
900
- // (see `createAnimatorImpl`), so downstream code never sees it.
901
- effects: PxEffectsSchema.optional(),
902
- // `PxElementAnimation` (not just `PxAnimationDefinition`) — accepts
903
- // string ref / array of refs / inline definition / mixed array; mirrors
904
- // `animator.animateById` map values and what `processNode` resolves at runtime.
905
- animate: PxElementAnimationSchema.optional(),
906
- style: px.union([px.string(), px.record(px.union([px.string(), px.number()]))]).optional()
907
- }, PxAttrValueSchema);
908
- var PxNodeSchema = px.openObject(__spreadProps2(__spreadValues2({}, PxNodeBase._shape), {
909
- children: px.lazy(() => px.array(PxNodeSchema), []).optional()
910
- }), PxAttrValueSchema);
911
- var PxSvgNodeExtra = px.object({
912
- // `"100%"` and other SVG length strings are legal here — a number-only slot rejected
913
- // real documents (e.g. apple-store-look-14-main.json) at the root <svg>.
914
- width: px.union([px.number(), px.string()]).optional(),
915
- height: px.union([px.number(), px.string()]).optional(),
916
- viewBox: px.string().optional(),
917
- animator: PxAnimatorConfigSchema.optional()
918
- });
919
- var PxAnimatedSvgDocumentSchema = px.openObject(__spreadProps2(__spreadValues2(__spreadValues2({}, PxNodeBase._shape), PxSvgNodeExtra._shape), {
920
- type: px.literal("svg"),
921
- // override string → literal to require 'svg'
922
- children: px.array(PxNodeSchema).optional()
923
- }), PxAttrValueSchema);
924
- var PxBezierPathSchema = implementsInterface()(px.object({
925
- v: px.array(px.array(px.number())),
926
- i: px.array(px.array(px.number())).optional(),
927
- o: px.array(px.array(px.number())).optional(),
928
- c: px.boolean().optional()
929
- }));
930
- function isPxElementFileFormatDeep(fileJson) {
931
- const valid = PxAnimatedSvgDocumentSchema.isValid(fileJson);
932
- return { valid, errors: valid ? [] : ["Document failed schema validation"] };
933
- }
934
- function bezierToSvgPath(path, forceCurves = false) {
935
- var _a, _b, _c, _d;
936
- const v = path.v;
937
- const i = path.i;
938
- const o = path.o;
939
- const c = path.c;
940
- if (!v.length) return "";
941
- const d = [];
942
- const len = v.length;
943
- d.push("M" + v[0][0] + "," + v[0][1]);
944
- for (let idx = 1; idx < len; idx++) {
945
- const prevV = v[idx - 1];
946
- const prevO = (_a = o == null ? void 0 : o[idx - 1]) != null ? _a : prevV;
947
- const currI = (_b = i == null ? void 0 : i[idx]) != null ? _b : v[idx];
948
- const currV = v[idx];
949
- const isLine = !forceCurves && (prevO[0] === prevV[0] && prevO[1] === prevV[1]) && (currI[0] === currV[0] && currI[1] === currV[1]);
950
- if (isLine) {
951
- d.push("L" + currV[0] + "," + currV[1]);
952
- } else {
953
- d.push("C" + prevO[0] + "," + prevO[1] + "," + currI[0] + "," + currI[1] + "," + currV[0] + "," + currV[1]);
954
- }
955
- }
956
- if (c && len > 0) {
957
- const lastV = v[len - 1];
958
- const lastO = (_c = o == null ? void 0 : o[len - 1]) != null ? _c : lastV;
959
- const firstI = (_d = i == null ? void 0 : i[0]) != null ? _d : v[0];
960
- const firstV = v[0];
961
- const isLine = !forceCurves && (lastO[0] === lastV[0] && lastO[1] === lastV[1]) && (firstI[0] === firstV[0] && firstI[1] === firstV[1]);
962
- if (!isLine) {
963
- d.push("C" + lastO[0] + "," + lastO[1] + "," + firstI[0] + "," + firstI[1] + "," + firstV[0] + "," + firstV[1]);
964
- }
965
- d.push("z");
966
- }
967
- return d.join("");
968
- }
969
- function interpolateNum(a, b, t) {
970
- return a + (b - a) * t;
971
- }
972
- function interpolateVec(a, b, t) {
973
- const res = [];
974
- const count = Math.max(a.length, b.length);
975
- for (let i = 0; i < count; i++) {
976
- res[i] = interpolateNum(a[i] || 0, b[i] || 0, t);
977
- }
978
- return res;
979
- }
980
- function interpolateColor(a, b, t) {
981
- return [
982
- interpolateNum(a[0] || 0, b[0] || 0, t),
983
- interpolateNum(a[1] || 0, b[1] || 0, t),
984
- interpolateNum(a[2] || 0, b[2] || 0, t),
985
- interpolateNum(a[3] === void 0 ? 1 : a[3], b[3] === void 0 ? 1 : b[3], t)
986
- ];
987
- }
988
- function interpolateBeziers(paths1, paths2, progress) {
989
- const count = Math.max(paths1.length, paths2.length);
990
- const res = [];
991
- for (let i = 0; i < count; i++) {
992
- res.push(interpolateBezier(paths1[i], paths2[i], progress));
993
- }
994
- return res;
995
- }
996
- function interpolateBezier(path1, path2, progress) {
997
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
998
- if (!path1 || !path2) return path1 || path2 || { v: [] };
999
- const t = Math.min(Math.max(progress, 0), 1);
1000
- const len = Math.min(path1.v.length, path2.v.length);
1001
- const v = [];
1002
- const i = [];
1003
- const o = [];
1004
- for (let idx = 0; idx < len; idx++) {
1005
- const v1 = path1.v[idx];
1006
- const v2 = path2.v[idx];
1007
- v.push(interpolateVec(v1, v2, t));
1008
- const i1 = (_b = (_a = path1.i) == null ? void 0 : _a[idx]) != null ? _b : v1;
1009
- const i2 = (_d = (_c = path2.i) == null ? void 0 : _c[idx]) != null ? _d : v2;
1010
- i.push(interpolateVec(i1, i2, t));
1011
- const o1 = (_f = (_e = path1.o) == null ? void 0 : _e[idx]) != null ? _f : v1;
1012
- const o2 = (_h = (_g = path2.o) == null ? void 0 : _g[idx]) != null ? _h : v2;
1013
- o.push(interpolateVec(o1, o2, t));
1014
- }
1015
- return { v, i: i.length ? i : void 0, o: o.length ? o : void 0, c: (_i = path1.c) != null ? _i : path2.c };
1016
- }
1017
- function remap(value, inMin, inMax, outMin, outMax) {
1018
- if (inMax === inMin) return outMin;
1019
- const t = (value - inMin) / (inMax - inMin);
1020
- return outMin + t * (outMax - outMin);
1021
- }
1022
- function solveCubicBezierX(p1x, p2x, x) {
1023
- if (x <= 0) return 0;
1024
- if (x >= 1) return 1;
1025
- const cx = 3 * p1x;
1026
- const bx = 3 * (p2x - p1x) - cx;
1027
- const ax = 1 - cx - bx;
1028
- function sampleX(t) {
1029
- return ((ax * t + bx) * t + cx) * t;
1030
- }
1031
- function sampleDX(t) {
1032
- return (3 * ax * t + 2 * bx) * t + cx;
1033
- }
1034
- let t2 = x;
1035
- let t0 = 0;
1036
- let t1 = 1;
1037
- for (let i = 0; i < 8; i++) {
1038
- const x2 = sampleX(t2) - x;
1039
- if (Math.abs(x2) < 1e-6) return t2;
1040
- const d2 = sampleDX(t2);
1041
- if (Math.abs(d2) < 1e-6) break;
1042
- t2 -= x2 / d2;
1043
- }
1044
- t2 = x;
1045
- while (t0 < t1) {
1046
- const x2 = sampleX(t2);
1047
- if (Math.abs(x2 - x) < 1e-6) return t2;
1048
- if (x > x2) t0 = t2;
1049
- else t1 = t2;
1050
- t2 = (t1 + t0) / 2;
1051
- }
1052
- return t2;
1053
- }
1054
- function cubicBezier(easing) {
1055
- const [p1x, p1y, p2x, p2y] = easing;
1056
- const cy = 3 * p1y;
1057
- const by = 3 * (p2y - p1y) - cy;
1058
- const ay = 1 - cy - by;
1059
- function sampleCurveY(t) {
1060
- return ((ay * t + by) * t + cy) * t;
1061
- }
1062
- return function(x) {
1063
- return sampleCurveY(solveCubicBezierX(p1x, p2x, x));
1064
- };
1065
- }
1066
- function lerp2(a, b, t) {
1067
- return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t];
1068
- }
1069
- function subdivideCubicBezier(p0, p1, p2, p3, t) {
1070
- const q0 = lerp2(p0, p1, t);
1071
- const q1 = lerp2(p1, p2, t);
1072
- const q2 = lerp2(p2, p3, t);
1073
- const r0 = lerp2(q0, q1, t);
1074
- const r1 = lerp2(q1, q2, t);
1075
- const s = lerp2(r0, r1, t);
1076
- return {
1077
- left: [p0, q0, r0, s],
1078
- right: [s, r1, q2, p3]
1079
- };
1080
- }
1081
- function splitEasing(easing, xFraction) {
1082
- if (!easing) return { left: void 0, right: void 0 };
1083
- if (xFraction <= 0) return { left: void 0, right: easing };
1084
- if (xFraction >= 1) return { left: easing, right: void 0 };
1085
- const [x1, y1, x2, y2] = easing;
1086
- const t = solveCubicBezierX(x1, x2, xFraction);
1087
- const p0 = [0, 0];
1088
- const p1 = [x1, y1];
1089
- const p2 = [x2, y2];
1090
- const p3 = [1, 1];
1091
- const { left, right } = subdivideCubicBezier(p0, p1, p2, p3, t);
1092
- const sx = left[3][0];
1093
- const sy = left[3][1];
1094
- let leftEasing;
1095
- if (sx > 1e-9 && Math.abs(sy) > 1e-9) {
1096
- leftEasing = [
1097
- left[1][0] / sx,
1098
- left[1][1] / sy,
1099
- left[2][0] / sx,
1100
- left[2][1] / sy
1101
- ];
1102
- }
1103
- let rightEasing;
1104
- const rx = 1 - sx;
1105
- const ry = 1 - sy;
1106
- if (rx > 1e-9 && Math.abs(ry) > 1e-9) {
1107
- rightEasing = [
1108
- (right[1][0] - sx) / rx,
1109
- (right[1][1] - sy) / ry,
1110
- (right[2][0] - sx) / rx,
1111
- (right[2][1] - sy) / ry
1112
- ];
1113
- }
1114
- return { left: leftEasing, right: rightEasing };
1115
- }
1116
- function reverseEasing(easing) {
1117
- if (!easing) return void 0;
1118
- return [1 - easing[2], 1 - easing[3], 1 - easing[0], 1 - easing[1]];
1119
- }
1120
- function toRGBA(color) {
1121
- const r = Math.round(color[0] * 255);
1122
- const g = Math.round(color[1] * 255);
1123
- const b = Math.round(color[2] * 255);
1124
- return color.length === 4 ? "rgba(" + r + "," + g + "," + b + "," + color[3] + ")" : "rgb(" + r + "," + g + "," + b + ")";
1125
- }
1126
- function parseRgba(s) {
1127
- var _a;
1128
- const inner = (_a = s.match(/rgba?\((.*)\)/)) == null ? void 0 : _a[1];
1129
- if (!inner) throw new Error("Invalid rgb/rgba format");
1130
- const parts = inner.split(",").map((v) => +v.trim());
1131
- return [parts[0] / 255, parts[1] / 255, parts[2] / 255, ...parts[3] !== void 0 ? [parts[3]] : []];
1132
- }
1133
- function parseHex(s) {
1134
- const hex = s.slice(1);
1135
- const isShort = hex.length <= 4;
1136
- const r = isShort ? hex[0] + hex[0] : hex.slice(0, 2);
1137
- const g = isShort ? hex[1] + hex[1] : hex.slice(2, 4);
1138
- const b = isShort ? hex[2] + hex[2] : hex.slice(4, 6);
1139
- const a = hex.length === 4 ? hex[3] + hex[3] : hex.length === 8 ? hex.slice(6, 8) : null;
1140
- const result = [
1141
- parseInt(r, 16) / 255,
1142
- parseInt(g, 16) / 255,
1143
- parseInt(b, 16) / 255
1144
- ];
1145
- if (a !== null) {
1146
- result.push(parseInt(a, 16) / 255);
1147
- }
1148
- return result;
1149
- }
1150
- function parseColor(s) {
1151
- if (!s) return void 0;
1152
- if (Array.isArray(s)) return s;
1153
- if (typeof s !== "string") return void 0;
1154
- if (s.startsWith("#")) {
1155
- return parseHex(s);
1156
- } else if (s.startsWith("rgb")) {
1157
- return parseRgba(s);
1158
- } else {
1159
- console.warn("Unsupported color format: " + s);
1160
- }
1161
- return void 0;
1162
- }
1163
- var COLOUR_ATTR_NAMES = /* @__PURE__ */ new Set(["color", "fill", "flood-color", "lighting-color", "stop-color", "stroke"]);
1164
- var TRANSFORM_FN_NAMES = /* @__PURE__ */ new Set(["translate", "rotate", "scale", "skew"]);
1165
- var PCT_BASED_ATTR_NAMES = /* @__PURE__ */ new Set(["offset-distance", "offsetDistance"]);
1166
- function composeTransformParts(parts, opts) {
1167
- var _a;
1168
- if (!parts) return "";
1169
- const withUnits = (_a = opts == null ? void 0 : opts.withUnits) != null ? _a : true;
1170
- const segs = [];
1171
- const t = parts.translate;
1172
- const o = parts.origin;
1173
- const r = parts.rotate;
1174
- const k = parts.skew;
1175
- const s = parts.scale;
1176
- const tu = withUnits ? "px" : "";
1177
- const ru = withUnits ? "deg" : "";
1178
- if (t) segs.push("translate(" + t[0] + tu + "," + t[1] + tu + ")");
1179
- if (o) segs.push("translate(" + o[0] + tu + "," + o[1] + tu + ")");
1180
- if (r !== void 0 && r !== null) segs.push("rotate(" + r + ru + ")");
1181
- if (k !== void 0 && k !== null) segs.push("skewX(" + k + ru + ")");
1182
- if (s) segs.push("scale(" + s[0] + "," + s[1] + ")");
1183
- if (o) segs.push("translate(" + -o[0] + tu + "," + -o[1] + tu + ")");
1184
- return segs.join("");
1185
- }
1186
- var STYLE_ATTR_NAMES = /* @__PURE__ */ new Set(["offset-distance", "offsetDistance"]);
1187
- var DEFAULT_DURATION_MS = 1e3;
1188
- function kebabToCamelCaseWord(kebab) {
1189
- return kebab.includes("-") ? kebab.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()) : kebab;
1190
- }
1191
- function isCamelCaseWord(word) {
1192
- return !word.includes("-") && /[a-z][A-Z]/.test(word);
1193
- }
1194
- var SVG_CAMEL_CASE_ATTRS = /* @__PURE__ */ new Set([
1195
- // Transform/positioning
1196
- "viewBox",
1197
- "preserveAspectRatio",
1198
- // Gradient
1199
- "gradientUnits",
1200
- "gradientTransform",
1201
- "spreadMethod",
1202
- // Pattern
1203
- "patternUnits",
1204
- "patternContentUnits",
1205
- "patternTransform",
1206
- // Clipping/masking
1207
- "clipPathUnits",
1208
- "maskUnits",
1209
- "maskContentUnits",
1210
- // Marker (SVG spec keeps these camelCase, like viewBox)
1211
- "markerUnits",
1212
- "markerWidth",
1213
- "markerHeight",
1214
- "refX",
1215
- "refY",
1216
- // Text
1217
- "textLength",
1218
- "lengthAdjust",
1219
- "startOffset",
1220
- // Filter
1221
- "filterUnits",
1222
- "primitiveUnits",
1223
- "tableValues",
1224
- // feFuncR/G/B/A transfer table (type="table")
1225
- "stdDeviation",
1226
- "baseFrequency",
1227
- "numOctaves",
1228
- "surfaceScale",
1229
- "diffuseConstant",
1230
- "specularConstant",
1231
- "specularExponent",
1232
- "kernelMatrix",
1233
- "kernelUnitLength",
1234
- "edgeMode",
1235
- "preserveAlpha",
1236
- "targetX",
1237
- "targetY"
1238
- // // Animation
1239
- // 'attributeName',
1240
- // 'attributeType',
1241
- // 'calcMode',
1242
- // 'keyTimes',
1243
- // 'keySplines',
1244
- // 'repeatCount',
1245
- // 'repeatDur'
1307
+ var PxGradientStopSchema = implementsInterface()(px.object({
1308
+ offset: px.number(),
1309
+ color: px.string()
1310
+ }));
1311
+ var PxAnimatableGradientStopsSchema = px.union([
1312
+ px.array(PxGradientStopSchema),
1313
+ px.object({ value: px.array(PxGradientStopSchema) }),
1314
+ PxPropertyAnimationSchema
1246
1315
  ]);
1247
- function camelCaseToKebabWordIfNeeded(camel) {
1248
- return SVG_CAMEL_CASE_ATTRS.has(camel) ? camel : camel.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
1249
- }
1250
- function clamp(value, min, max) {
1251
- return Math.max(min, Math.min(value, max));
1252
- }
1253
- function bezier2D_pointAt(P0, P1, P2, P3, t) {
1254
- if (t <= 0) return [P0[0], P0[1]];
1255
- if (t >= 1) return [P3[0], P3[1]];
1256
- const u = 1 - t;
1257
- const u2 = u * u;
1258
- const u3 = u2 * u;
1259
- const t2 = t * t;
1260
- const t3 = t2 * t;
1261
- const w0 = u3;
1262
- const w1 = 3 * t * u2;
1263
- const w2 = 3 * t2 * u;
1264
- const w3 = t3;
1265
- return [
1266
- w0 * P0[0] + w1 * P1[0] + w2 * P2[0] + w3 * P3[0],
1267
- w0 * P0[1] + w1 * P1[1] + w2 * P2[1] + w3 * P3[1]
1268
- ];
1269
- }
1270
- var BEZIER_T_NUDGE = 1e-4;
1271
- function bezier2D_derivativeAt(P0, P1, P2, P3, t) {
1272
- const result = _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t);
1273
- if (result[0] === 0 && result[1] === 0) {
1274
- const nudgedT = t < 0.5 ? t + BEZIER_T_NUDGE : t - BEZIER_T_NUDGE;
1275
- return _bezier2D_derivativeAtRaw(P0, P1, P2, P3, nudgedT);
1276
- }
1277
- return result;
1278
- }
1279
- function _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t) {
1280
- const u = 1 - t;
1281
- const a = 3 * u * u;
1282
- const b = 6 * t * u;
1283
- const c = 3 * t * t;
1284
- return [
1285
- a * (P1[0] - P0[0]) + b * (P2[0] - P1[0]) + c * (P3[0] - P2[0]),
1286
- a * (P1[1] - P0[1]) + b * (P2[1] - P1[1]) + c * (P3[1] - P2[1])
1287
- ];
1288
- }
1289
- function bezier2D_arcLengthLUT(P0, P1, P2, P3, steps = 100) {
1290
- const n = steps + 1;
1291
- const ts = new Float64Array(n);
1292
- const ds = new Float64Array(n);
1293
- let prev = bezier2D_pointAt(P0, P1, P2, P3, 0);
1294
- ts[0] = 0;
1295
- ds[0] = 0;
1296
- let cum = 0;
1297
- for (let i = 1; i < n; i++) {
1298
- const t = i / steps;
1299
- const cur = bezier2D_pointAt(P0, P1, P2, P3, t);
1300
- const dx = cur[0] - prev[0];
1301
- const dy = cur[1] - prev[1];
1302
- cum += Math.sqrt(dx * dx + dy * dy);
1303
- ts[i] = t;
1304
- ds[i] = cum;
1305
- prev = cur;
1306
- }
1307
- return { ts, ds };
1308
- }
1309
- function bezier2D_tForDistance(lut, distance) {
1310
- const { ts, ds } = lut;
1311
- const last = ds.length - 1;
1312
- if (distance <= 0) return ts[0];
1313
- if (distance >= ds[last]) return ts[last];
1314
- let lo = 1;
1315
- let hi = last;
1316
- while (lo < hi) {
1317
- const mid = lo + hi >>> 1;
1318
- if (ds[mid] < distance) lo = mid + 1;
1319
- else hi = mid;
1320
- }
1321
- const dPrev = ds[hi - 1];
1322
- const dCur = ds[hi];
1323
- const span = dCur - dPrev;
1324
- const frac = span > 0 ? (distance - dPrev) / span : 0;
1325
- return ts[hi - 1] + frac * (ts[hi] - ts[hi - 1]);
1326
- }
1327
- function bezier2D_arcAtT(lut, t) {
1328
- const { ts, ds } = lut;
1329
- const last = ts.length - 1;
1330
- if (t <= ts[0]) return ds[0];
1331
- if (t >= ts[last]) return ds[last];
1332
- let lo = 1, hi = last;
1333
- while (lo < hi) {
1334
- const mid = lo + hi >>> 1;
1335
- if (ts[mid] < t) lo = mid + 1;
1336
- else hi = mid;
1337
- }
1338
- const tPrev = ts[hi - 1];
1339
- const span = ts[hi] - tPrev;
1340
- const frac = span > 0 ? (t - tPrev) / span : 0;
1341
- return ds[hi - 1] + frac * (ds[hi] - ds[hi - 1]);
1316
+ var PxFillGradientEffectSchema = implementsInterface()(px.object({
1317
+ // Contextual kind the `type` convention, see `PxNodeBase.type`.
1318
+ type: px.enum([PxGradientType.linear, PxGradientType.radial]),
1319
+ p1: PxAnimatableVec2Schema.optional(),
1320
+ p2: PxAnimatableVec2Schema.optional(),
1321
+ c: PxAnimatableVec2Schema.optional(),
1322
+ r: PxAnimatableNumberSchema.optional(),
1323
+ fp: PxAnimatableVec2Schema.optional(),
1324
+ stops: PxAnimatableGradientStopsSchema.optional(),
1325
+ gradientUnits: px.enum([PxGradientUnits.userSpaceOnUse, PxGradientUnits.objectBoundingBox]).optional(),
1326
+ spreadMethod: px.enum([PxGradientSpreadMethod.pad, PxGradientSpreadMethod.reflect, PxGradientSpreadMethod.repeat]).optional(),
1327
+ gradientTransform: px.string().optional()
1328
+ }));
1329
+ var PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;
1330
+ var PxTextPathEffectSchema = implementsInterface()(px.object({
1331
+ path: px.string(),
1332
+ pathOverflow: px.enum([PxPathOverflow.clip, PxPathOverflow.extend]).optional(),
1333
+ lengthAdjust: px.enum([PxLengthAdjust.spacing, PxLengthAdjust.spacingAndGlyphs]).optional(),
1334
+ method: px.enum([PxTextPathMethod.align, PxTextPathMethod.stretch]).optional(),
1335
+ spacing: px.enum([PxTextPathSpacing.auto, PxTextPathSpacing.exact]).optional(),
1336
+ startOffset: PxAnimatableNumberSchema.optional(),
1337
+ textLength: PxAnimatableNumberSchema.optional()
1338
+ }));
1339
+ var PxTextEffectSchema = implementsInterface()(px.object({
1340
+ useGlyphs: px.boolean().optional()
1341
+ }));
1342
+ var PxEffectsSchema = implementsInterface()(px.object({
1343
+ transformBy: PxTransformByEffectSchema.optional(),
1344
+ repeater: PxRepeaterEffectSchema.optional(),
1345
+ maskedBy: PxMaskedByEffectSchema.optional(),
1346
+ clipPath: PxClipPathEffectSchema.optional(),
1347
+ trimPath: PxTrimPathEffectSchema.optional(),
1348
+ clone: PxCloneEffectSchema.optional(),
1349
+ fillGradient: PxFillGradientEffectSchema.optional(),
1350
+ strokeGradient: PxStrokeGradientEffectSchema.optional(),
1351
+ textPath: PxTextPathEffectSchema.optional(),
1352
+ text: PxTextEffectSchema.optional()
1353
+ }));
1354
+ function validateNodeEffects(root, opts) {
1355
+ const warnings = [];
1356
+ const walk = (node, path) => {
1357
+ if (node && node.effects) {
1358
+ const ctx = { errors: [], warnings: [], strict: !!(opts == null ? void 0 : opts.strict) };
1359
+ const ok = PxEffectsSchema.isValid(node.effects, ctx, [path + ".effects"]);
1360
+ if (!ok) {
1361
+ for (const err of ctx.errors) warnings.push(err);
1362
+ }
1363
+ }
1364
+ if (node && Array.isArray(node.children)) {
1365
+ node.children.forEach((c, i) => walk(c, path + ".children[" + i + "]"));
1366
+ }
1367
+ };
1368
+ walk(root, "root");
1369
+ return warnings;
1342
1370
  }
1343
- function invertEasing(easing) {
1344
- if (!easing) return (y) => y;
1345
- const flipped = [easing[1], easing[0], easing[3], easing[2]];
1346
- return cubicBezier(flipped);
1371
+ var PxNodeBase = px.openObject({
1372
+ // CONVENTION (SCHEMA-DESIGN R1 / issues N4): `type` is the ONE word for "what
1373
+ // kind of thing is this", discriminated by its CARRIER — here the node TAG
1374
+ // (`rect`, `text`), and inside a sub-object that object's kind (`clone.type`,
1375
+ // `fillGradient.type`, editor `preset.type`). Each sits in its own object, so
1376
+ // the carrier disambiguates completely; synonyms (`cloneKind`, `presetShape`)
1377
+ // would add words that all mean "type" and still need the carrier to read.
1378
+ // Guarding a `type` SLOT against a wrong VALUE is the job of strict enums
1379
+ // (issues V3), never of distinct key names.
1380
+ type: px.string(),
1381
+ id: px.string().optional(),
1382
+ meta: px.any().optional(),
1383
+ // Player-effects bucket emitted by the Editor's lightweight design format.
1384
+ // Consumed and removed by `applyPlayerEffects` before any other normalisation
1385
+ // (see `createAnimatorImpl`), so downstream code never sees it.
1386
+ effects: PxEffectsSchema.optional(),
1387
+ // `PxElementAnimation` (not just `PxAnimationDefinition`) — accepts
1388
+ // string ref / array of refs / inline definition / mixed array; mirrors
1389
+ // `animator.animateById` map values and what `processNode` resolves at runtime.
1390
+ animate: PxElementAnimationSchema.optional(),
1391
+ style: px.union([px.string(), px.record(px.union([px.string(), px.number()]))]).optional()
1392
+ }, PxAttrValueSchema);
1393
+ var PxNodeSchema = px.openObject(__spreadProps2(__spreadValues2({}, PxNodeBase._shape), {
1394
+ children: px.lazy(() => px.array(PxNodeSchema), []).optional()
1395
+ }), PxAttrValueSchema);
1396
+ var PxSvgNodeExtra = px.object({
1397
+ // `"100%"` and other SVG length strings are legal here — a number-only slot rejected
1398
+ // real documents (e.g. apple-store-look-14-main.json) at the root <svg>.
1399
+ width: px.union([px.number(), px.string()]).optional(),
1400
+ height: px.union([px.number(), px.string()]).optional(),
1401
+ viewBox: px.string().optional(),
1402
+ animator: PxAnimatorConfigSchema.optional()
1403
+ });
1404
+ var PxAnimatedSvgDocumentSchema = px.openObject(__spreadProps2(__spreadValues2(__spreadValues2({}, PxNodeBase._shape), PxSvgNodeExtra._shape), {
1405
+ type: px.literal("svg"),
1406
+ // override string → literal to require 'svg'
1407
+ children: px.array(PxNodeSchema).optional()
1408
+ }), PxAttrValueSchema);
1409
+ var PxBezierPathSchema = implementsInterface()(px.object({
1410
+ v: px.array(px.array(px.number())),
1411
+ i: px.array(px.array(px.number())).optional(),
1412
+ o: px.array(px.array(px.number())).optional(),
1413
+ c: px.boolean().optional()
1414
+ }));
1415
+ function isPxElementFileFormatDeep(fileJson) {
1416
+ const valid = PxAnimatedSvgDocumentSchema.isValid(fileJson);
1417
+ return { valid, errors: valid ? [] : ["Document failed schema validation"] };
1347
1418
  }
1348
1419
  var _idCounter = 0;
1349
1420
  function generateUniqueId() {
@@ -6043,7 +6114,13 @@ function createFrameLoopAnimator(doc, adapter, callbacks, rootElement) {
6043
6114
  const api = __spreadProps(__spreadValues({}, basicApi), {
6044
6115
  "getRootElement": () => rootElement || null
6045
6116
  });
6046
- if (config.trigger) setupAnimationTriggers(api, config.trigger);
6117
+ if (config.trigger) {
6118
+ if (isScrollTimeline(config)) {
6119
+ console.warn("scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)");
6120
+ } else {
6121
+ setupAnimationTriggers(api, config.trigger);
6122
+ }
6123
+ }
6047
6124
  return api;
6048
6125
  }
6049
6126
  function createDomAdapter(rootElement) {
@@ -6171,7 +6248,7 @@ function convertToWebApiKeyframes(animDef, unsupportedSet, config) {
6171
6248
  }
6172
6249
  return result;
6173
6250
  }
6174
- function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsupportedAttrs) {
6251
+ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsupportedAttrs, scrollTimeline) {
6175
6252
  var _a;
6176
6253
  const config = getAnimatorConfig(doc) || {};
6177
6254
  if (!rootElement) {
@@ -6230,7 +6307,12 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
6230
6307
  if (keyframes.length > 0) {
6231
6308
  try {
6232
6309
  const effect = new KeyframeEffect(element, keyframes, effectOptions);
6233
- const anim = new Animation(effect, document.timeline);
6310
+ const anim = new Animation(effect, scrollTimeline ? scrollTimeline.timeline : document.timeline);
6311
+ if (scrollTimeline) {
6312
+ const a = anim;
6313
+ if (scrollTimeline.rangeStart) a.rangeStart = scrollTimeline.rangeStart;
6314
+ if (scrollTimeline.rangeEnd) a.rangeEnd = scrollTimeline.rangeEnd;
6315
+ }
6234
6316
  if (callbacks == null ? void 0 : callbacks.onFinish) anim.onfinish = () => {
6235
6317
  var _a2;
6236
6318
  if (finishNotified) return;
@@ -6324,11 +6406,136 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
6324
6406
  }
6325
6407
  };
6326
6408
  if (config.trigger) {
6327
- setupAnimationTriggers(api, config.trigger);
6409
+ if (config.timelineSource === "scroll") {
6410
+ console.warn("scroll timeline: `animator.trigger` is ignored (triggers do not apply to scroll-driven playback)");
6411
+ } else {
6412
+ setupAnimationTriggers(api, config.trigger);
6413
+ }
6414
+ }
6415
+ if (scrollTimeline) {
6416
+ animations.forEach((a) => a.play());
6328
6417
  }
6329
6418
  return api;
6330
6419
  }
6331
6420
 
6421
+ // src/PxScrollDriver.ts
6422
+ function nativeRangeOffset(point, defaultFraction, view) {
6423
+ var _a, _b, _c;
6424
+ const fraction = typeof (point == null ? void 0 : point.fraction) === "number" ? point.fraction : defaultFraction;
6425
+ const pct = (_b = (_a = globalThis.CSS) == null ? void 0 : _a.percent) == null ? void 0 : _b.call(_a, fraction * 100);
6426
+ if (pct === void 0) return void 0;
6427
+ return view ? { rangeName: (_c = point == null ? void 0 : point.phase) != null ? _c : "cover", offset: pct } : { offset: pct };
6428
+ }
6429
+ function createNativeScrollTimeline(subject, config) {
6430
+ var _a, _b, _c, _d;
6431
+ if (!config || !isScrollTimeline(config)) return null;
6432
+ const scroll = config.scroll || {};
6433
+ const kind = (_a = scroll.kind) != null ? _a : "view";
6434
+ const g = globalThis;
6435
+ const view = kind === "view";
6436
+ const Ctor = view ? g.ViewTimeline : g.ScrollTimeline;
6437
+ if (typeof Ctor !== "function") return null;
6438
+ const axis = (_b = scroll.axis) != null ? _b : "block";
6439
+ let timeline;
6440
+ try {
6441
+ if (view) {
6442
+ timeline = new Ctor({ subject, axis });
6443
+ } else {
6444
+ const source = scroll.source === "root" ? documentScroller() : findNearestScroller(subject, "y") || findNearestScroller(subject, "x") || documentScroller();
6445
+ timeline = new Ctor({ source, axis });
6446
+ }
6447
+ } catch (e) {
6448
+ console.warn("scroll timeline: native timeline construction failed \u2014 falling back to the custom driver", e);
6449
+ return null;
6450
+ }
6451
+ return {
6452
+ timeline,
6453
+ rangeStart: nativeRangeOffset((_c = scroll.range) == null ? void 0 : _c.start, 0, view),
6454
+ rangeEnd: nativeRangeOffset((_d = scroll.range) == null ? void 0 : _d.end, 1, view)
6455
+ };
6456
+ }
6457
+ function findNearestScroller(el, axis) {
6458
+ for (let p = el.parentElement; p; p = p.parentElement) {
6459
+ const style = getComputedStyle(p);
6460
+ const overflow = axis === "y" ? style.overflowY : style.overflowX;
6461
+ if (overflow === "auto" || overflow === "scroll" || overflow === "hidden" || overflow === "overlay") {
6462
+ return p;
6463
+ }
6464
+ }
6465
+ return null;
6466
+ }
6467
+ function documentScroller() {
6468
+ return document.scrollingElement || document.documentElement;
6469
+ }
6470
+ function createScrollDriver(subject, config, onProgress) {
6471
+ var _a;
6472
+ if (!config || !isScrollTimeline(config)) return null;
6473
+ const scroll = config.scroll || {};
6474
+ const kind = (_a = scroll.kind) != null ? _a : "view";
6475
+ const nearest = findNearestScroller(subject, "y") || findNearestScroller(subject, "x");
6476
+ const scroller = kind === "scroll" && scroll.source === "root" ? documentScroller() : nearest || documentScroller();
6477
+ const isRootScroller = scroller === documentScroller();
6478
+ const axis = scrollResolveAxis(scroll.axis, getComputedStyle(scroller).writingMode);
6479
+ const compute = () => {
6480
+ if (kind === "scroll") {
6481
+ const offset = axis === "y" ? scroller.scrollTop : scroller.scrollLeft;
6482
+ const maxOffset = axis === "y" ? scroller.scrollHeight - scroller.clientHeight : scroller.scrollWidth - scroller.clientWidth;
6483
+ return scrollOffsetProgress(offset, maxOffset, scroll.range);
6484
+ }
6485
+ const subjectRect = subject.getBoundingClientRect();
6486
+ let portStart, portSize;
6487
+ if (isRootScroller) {
6488
+ portStart = 0;
6489
+ portSize = axis === "y" ? document.documentElement.clientHeight : document.documentElement.clientWidth;
6490
+ } else {
6491
+ const portRect = scroller.getBoundingClientRect();
6492
+ portStart = axis === "y" ? portRect.top : portRect.left;
6493
+ portSize = axis === "y" ? scroller.clientHeight : scroller.clientWidth;
6494
+ }
6495
+ const subjectStart = (axis === "y" ? subjectRect.top : subjectRect.left) - portStart;
6496
+ const subjectSize = axis === "y" ? subjectRect.height : subjectRect.width;
6497
+ return scrollViewProgress(subjectStart, subjectSize, portSize, scroll.range);
6498
+ };
6499
+ let rafId = null;
6500
+ let destroyed = false;
6501
+ const tick = () => {
6502
+ rafId = null;
6503
+ if (destroyed) return;
6504
+ onProgress(compute());
6505
+ };
6506
+ const schedule = () => {
6507
+ if (destroyed || rafId !== null) return;
6508
+ rafId = requestAnimationFrame(tick);
6509
+ };
6510
+ const scrollTarget = isRootScroller ? window : scroller;
6511
+ scrollTarget.addEventListener("scroll", schedule, { passive: true });
6512
+ window.addEventListener("resize", schedule, { passive: true });
6513
+ let resizeObserver;
6514
+ if (typeof ResizeObserver !== "undefined") {
6515
+ resizeObserver = new ResizeObserver(schedule);
6516
+ resizeObserver.observe(subject);
6517
+ if (!isRootScroller) resizeObserver.observe(scroller);
6518
+ }
6519
+ const driver = {
6520
+ destroy: () => {
6521
+ if (destroyed) return;
6522
+ destroyed = true;
6523
+ scrollTarget.removeEventListener("scroll", schedule);
6524
+ window.removeEventListener("resize", schedule);
6525
+ resizeObserver == null ? void 0 : resizeObserver.disconnect();
6526
+ if (rafId !== null) {
6527
+ cancelAnimationFrame(rafId);
6528
+ rafId = null;
6529
+ }
6530
+ },
6531
+ refresh: () => {
6532
+ if (!destroyed) onProgress(compute());
6533
+ }
6534
+ };
6535
+ driver.refresh();
6536
+ return driver;
6537
+ }
6538
+
6332
6539
  // src/PxAnimatorBind.ts
6333
6540
  function finaliseAnimator(animatorConfig, callbacks, make) {
6334
6541
  let apiRef;
@@ -6351,6 +6558,44 @@ function finaliseAnimator(animatorConfig, callbacks, make) {
6351
6558
  }
6352
6559
  function bindWithEngineChoice(doc, adapter, callbacks, rootElement) {
6353
6560
  const animatorConfig = getAnimatorConfig(doc) || {};
6561
+ if (isScrollTimeline(animatorConfig)) {
6562
+ return finaliseAnimator(animatorConfig, callbacks, (cb) => {
6563
+ var _a, _b;
6564
+ if (animatorConfig.mode !== PxAnimatorMode.frames && ((_a = animatorConfig.scroll) == null ? void 0 : _a.driver) === "native" && rootElement) {
6565
+ const native = createNativeScrollTimeline(rootElement, animatorConfig);
6566
+ if (native) {
6567
+ const api2 = createWebApiAnimator(
6568
+ doc,
6569
+ cb,
6570
+ rootElement,
6571
+ animatorConfig.mode === PxAnimatorMode.waapi,
6572
+ native
6573
+ );
6574
+ if (api2) return api2;
6575
+ }
6576
+ }
6577
+ const api = (animatorConfig.mode !== PxAnimatorMode.frames ? createWebApiAnimator(doc, cb, rootElement, animatorConfig.mode === PxAnimatorMode.waapi) : null) || createFrameLoopAnimator(doc, adapter, cb, rootElement);
6578
+ const subject = ((_b = api.getRootElement) == null ? void 0 : _b.call(api)) || rootElement;
6579
+ if (subject) {
6580
+ const totalMs = scrollTotalDurationMs(animatorConfig);
6581
+ const driver = createScrollDriver(
6582
+ subject,
6583
+ animatorConfig,
6584
+ (progress) => api.setCurrentTime(progress * totalMs)
6585
+ );
6586
+ if (driver) {
6587
+ const destroy = api.destroy.bind(api);
6588
+ api.destroy = () => {
6589
+ driver.destroy();
6590
+ destroy();
6591
+ };
6592
+ }
6593
+ } else {
6594
+ console.warn("scroll timeline: no root element to observe \u2014 animation will stay at frame 0");
6595
+ }
6596
+ return api;
6597
+ });
6598
+ }
6354
6599
  return finaliseAnimator(animatorConfig, callbacks, (cb) => {
6355
6600
  if (animatorConfig.mode === PxAnimatorMode.frames) {
6356
6601
  return createFrameLoopAnimator(doc, adapter, cb, rootElement);