@pixodesk/svg-animator-web 1.0.10 → 1.0.16

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
@@ -30,281 +30,1070 @@ var __objRest = (source, exclude) => {
30
30
  return target;
31
31
  };
32
32
 
33
- // src/PxAnimatorTypes.ts
34
- var PX_ANIM_SRC_ATTR_NAME = "data-px-animation-src";
35
- var PX_ANIM_ATTR_NAME = "_px_animator";
36
- var ANIMATE_ATTR = "animate";
37
- var TEXT_ATTR = "text";
38
- var TEXT_CONTENT_ATTR = "textContent";
39
- var INTERNAL_ATTRS = /* @__PURE__ */ new Set([
40
- "type",
41
- "children",
42
- ANIMATE_ATTR,
43
- "animator",
44
- "meta",
45
- "defs",
46
- "bindings",
47
- TEXT_ATTR,
48
- TEXT_CONTENT_ATTR
49
- ]);
50
- function isPxElementFileFormat(fileJson) {
51
- if (!(fileJson && typeof fileJson === "object" && !Array.isArray(fileJson))) {
52
- return false;
33
+ // src/effects/transformParts.ts
34
+ function partsRecord(part, value, origin) {
35
+ const rec = {};
36
+ if (part === "translate" /* Translate */) rec.translate = value;
37
+ else if (part === "rotate" /* Rotate */) rec.rotate = value;
38
+ else rec.scale = value;
39
+ if (origin && part !== "translate" /* Translate */) rec.origin = origin;
40
+ return rec;
41
+ }
42
+ function readAnimatable(raw) {
43
+ if (raw === void 0) return { kind: "absent" /* Absent */ };
44
+ if (Array.isArray(raw)) return { kind: "static" /* Static */, value: raw };
45
+ if (typeof raw === "object") {
46
+ const obj = raw;
47
+ if (obj.keyframes) {
48
+ return { kind: "animated" /* Animated */, keyframes: obj.keyframes, autoOrient: obj.autoOrient };
49
+ }
50
+ if (obj.value !== void 0) return { kind: "static" /* Static */, value: obj.value };
53
51
  }
54
- return fileJson["type"] === "svg" || fileJson["tagName"] === "svg";
52
+ return { kind: "static" /* Static */, value: raw };
55
53
  }
56
- function isObject(v) {
57
- return v && typeof v === "object" && !Array.isArray(v);
54
+ function readStaticOrigin(raw, ctx) {
55
+ var _a;
56
+ const o = readAnimatable(raw);
57
+ if (o.kind === "absent" /* Absent */) return void 0;
58
+ if (o.kind === "static" /* Static */) return o.value;
59
+ ctx.warnings.push("transformation.origin: animated origin approximated by its first keyframe");
60
+ return (_a = o.keyframes[0]) == null ? void 0 : _a.value;
58
61
  }
59
- function validateFillMode(v, path, errors) {
60
- if (v === "forwards" || v === "backwards" || v === "both" || v === "none") return true;
61
- errors.push(path + ': invalid FillMode "' + v + `", expected 'forwards'|'backwards'|'both'|'none'`);
62
- return false;
62
+ function keyframeWith(kf, value) {
63
+ const out = { value };
64
+ if (kf.time !== void 0) out.time = kf.time;
65
+ if (kf.easing !== void 0) out.easing = kf.easing;
66
+ if (kf.tangentOut !== void 0) out.tangentOut = kf.tangentOut;
67
+ if (kf.tangentIn !== void 0) out.tangentIn = kf.tangentIn;
68
+ return out;
63
69
  }
64
- function validatePlaybackDirection(v, path, errors) {
65
- if (v === "normal" || v === "reverse" || v === "alternate" || v === "alternate-reverse") return true;
66
- errors.push(path + ': invalid PlaybackDirection "' + v + `", expected 'normal'|'reverse'|'alternate'|'alternate-reverse'`);
67
- return false;
70
+
71
+ // src/effects/transformationEffect.ts
72
+ function applyTransformationEffect(node, fx, ctx) {
73
+ if (!fx) return node;
74
+ delete node.transform;
75
+ let n = node;
76
+ n = wrapTransformPart(n, "skew" /* Skew */, fx.skew, ctx);
77
+ n = wrapOrigin(
78
+ n,
79
+ fx.origin,
80
+ /*invert=*/
81
+ true
82
+ );
83
+ n = wrapTransformPart(n, "scale" /* Scale */, normalizeScale(fx.scale), ctx);
84
+ n = wrapTransformPart(n, "rotate" /* Rotate */, fx.rotate, ctx);
85
+ n = wrapOrigin(
86
+ n,
87
+ fx.origin,
88
+ /*invert=*/
89
+ false
90
+ );
91
+ if (translateHasAutoOrient(fx.translate)) {
92
+ n = wrapOrigin(
93
+ n,
94
+ fx.origin,
95
+ /*invert=*/
96
+ true
97
+ );
98
+ n = wrapTransformPart(n, "translate" /* Translate */, fx.translate, ctx);
99
+ n = wrapOrigin(
100
+ n,
101
+ fx.origin,
102
+ /*invert=*/
103
+ false
104
+ );
105
+ } else {
106
+ n = wrapTransformPart(n, "translate" /* Translate */, fx.translate, ctx);
107
+ }
108
+ return n;
68
109
  }
69
- function validatePxEasingOrRef(v, path, errors) {
70
- if (typeof v === "string") return true;
71
- if (Array.isArray(v) && v.length === 4 && v.every((n) => typeof n === "number")) return true;
72
- errors.push(path + ": invalid easing, expected string or [number, number, number, number]");
73
- return false;
110
+ function translateHasAutoOrient(translate) {
111
+ if (!translate || typeof translate !== "object") return false;
112
+ const obj = translate;
113
+ if (obj.autoOrient) return true;
114
+ return Array.isArray(obj.keyframes) && obj.keyframes.some((kf) => kf.tangentOut || kf.tangentIn);
74
115
  }
75
- function validatePxKeyframe(v, path, errors) {
76
- if (!isObject(v)) {
77
- errors.push(path + ": expected object");
78
- return false;
116
+ function normalizeScale(raw) {
117
+ if (raw === void 0) return void 0;
118
+ if (Array.isArray(raw)) return [raw[0] / 100, raw[1] / 100];
119
+ return raw;
120
+ }
121
+ function wrapTransformPart(inner, part, raw, ctx) {
122
+ if (raw === void 0) return inner;
123
+ if (part === "skew" /* Skew */) {
124
+ const skew = readAnimatable(raw);
125
+ if (skew.kind !== "static" /* Static */) {
126
+ ctx.warnings.push("transformation.skew: only static skew is supported");
127
+ return inner;
128
+ }
129
+ return { type: "g", transform: "skewX(" + skew.value[0] + ")skewY(" + skew.value[1] + ")", children: [inner] };
79
130
  }
80
- let valid = true;
81
- if (v.time !== void 0 && typeof v.time !== "number") {
82
- errors.push(path + ".time: expected number, got " + typeof v.time);
83
- valid = false;
131
+ const v = readAnimatable(raw);
132
+ if (v.kind === "static" /* Static */) {
133
+ return { type: "g", transform: { value: partsRecord(part, v.value, void 0) }, children: [inner] };
84
134
  }
85
- if (v.t !== void 0 && typeof v.t !== "number") {
86
- errors.push(path + ".t: expected number, got " + typeof v.t);
87
- valid = false;
135
+ if (v.kind === "animated" /* Animated */) {
136
+ const animTr = { keyframes: v.keyframes.map((kf) => keyframeWith(kf, partsRecord(part, kf.value, void 0))) };
137
+ if (v.autoOrient) animTr.autoOrient = true;
138
+ return {
139
+ type: "g",
140
+ animate: { transform: animTr },
141
+ children: [inner]
142
+ };
88
143
  }
89
- if (v.easing !== void 0 && !validatePxEasingOrRef(v.easing, path + ".easing", errors)) valid = false;
90
- if (v.e !== void 0 && !validatePxEasingOrRef(v.e, path + ".e", errors)) valid = false;
91
- return valid;
144
+ return inner;
92
145
  }
93
- function validatePxPropertyAnimation(v, path, errors) {
94
- if (!isObject(v)) {
95
- errors.push(path + ": expected object");
96
- return false;
146
+ function wrapOrigin(inner, raw, invert) {
147
+ if (raw === void 0) return inner;
148
+ const v = readAnimatable(raw);
149
+ const sign = (value) => invert ? [-value[0], -value[1]] : value;
150
+ if (v.kind === "absent" /* Absent */) return inner;
151
+ if (v.kind === "static" /* Static */) {
152
+ if (v.value[0] === 0 && v.value[1] === 0) return inner;
153
+ return { type: "g", transform: { value: { translate: sign(v.value) } }, children: [inner] };
97
154
  }
98
- let valid = true;
99
- if (v.keyframes !== void 0) {
100
- if (!Array.isArray(v.keyframes)) {
101
- errors.push(path + ".keyframes: expected array");
102
- valid = false;
103
- } else {
104
- v.keyframes.forEach((kf, i) => {
105
- if (!validatePxKeyframe(kf, path + ".keyframes[" + i + "]", errors)) valid = false;
106
- });
155
+ if (v.kind === "animated" /* Animated */) {
156
+ return {
157
+ type: "g",
158
+ animate: { transform: { keyframes: v.keyframes.map((kf) => keyframeWith(kf, { translate: sign(kf.value) })) } },
159
+ children: [inner]
160
+ };
161
+ }
162
+ return inner;
163
+ }
164
+
165
+ // src/effects/contentRefSplit.ts
166
+ function identifyContentRefTargets(node, ctx, allocator) {
167
+ var _a, _b, _c;
168
+ if (node.type === "use" && ((_b = (_a = node.effects) == null ? void 0 : _a.ref) == null ? void 0 : _b.type) === "content") {
169
+ const baseId = node.effects.ref.baseId;
170
+ if (typeof baseId === "string" && baseId && !ctx.contentRefInnerIds.has(baseId)) {
171
+ ctx.contentRefInnerIds.set(baseId, allocator(baseId));
107
172
  }
108
173
  }
109
- if (v.kfs !== void 0) {
110
- if (!Array.isArray(v.kfs)) {
111
- errors.push(path + ".kfs: expected array");
112
- valid = false;
113
- } else {
114
- v.kfs.forEach((kf, i) => {
115
- if (!validatePxKeyframe(kf, path + ".kfs[" + i + "]", errors)) valid = false;
174
+ (_c = node.children) == null ? void 0 : _c.forEach((c) => identifyContentRefTargets(c, ctx, allocator));
175
+ }
176
+ function splitForContentRef(node, transformation, originalId, innerId, ctx) {
177
+ const outerBody = liftBodyTranslate(node, transformation);
178
+ if (typeof node.id === "string") delete node.id;
179
+ const { outer: outerTr, inner: innerTr } = splitTransformationEffect(transformation);
180
+ let innerNode = node;
181
+ innerNode = applyTransformationEffect(innerNode, innerTr, ctx);
182
+ const innerWrapper = { type: "g", id: innerId, children: [innerNode] };
183
+ let outerWrapper = { type: "g", id: originalId, children: [innerWrapper] };
184
+ if (outerBody.transform !== void 0) outerWrapper.transform = outerBody.transform;
185
+ if (outerBody.animate !== void 0) outerWrapper.animate = outerBody.animate;
186
+ if (outerTr) {
187
+ delete outerWrapper.id;
188
+ outerWrapper = applyTransformationEffect(outerWrapper, outerTr, ctx);
189
+ outerWrapper.id = originalId;
190
+ }
191
+ return outerWrapper;
192
+ }
193
+ function liftBodyTranslate(node, transformation) {
194
+ var _a, _b, _c;
195
+ const out = {};
196
+ let didLiftAnimate = false;
197
+ const animTr = (_a = node.animate) == null ? void 0 : _a.transform;
198
+ if (animTr && typeof animTr === "object" && Array.isArray(animTr.keyframes)) {
199
+ const kfs = animTr.keyframes;
200
+ const hasTranslate = kfs.some((kf) => kf.value && kf.value.translate);
201
+ if (hasTranslate) {
202
+ const outerHasOrigin = needsOriginOnOuter(animTr);
203
+ const outerKfs = kfs.map((kf) => {
204
+ const v = kf.value || {};
205
+ const newValue = {};
206
+ if (v.translate !== void 0) newValue.translate = v.translate;
207
+ if (outerHasOrigin && v.origin !== void 0) newValue.origin = v.origin;
208
+ const outerKf = { value: newValue };
209
+ if (kf.time !== void 0) outerKf.time = kf.time;
210
+ if (kf.easing !== void 0) outerKf.easing = kf.easing;
211
+ if (kf.tangentOut !== void 0) outerKf.tangentOut = kf.tangentOut;
212
+ if (kf.tangentIn !== void 0) outerKf.tangentIn = kf.tangentIn;
213
+ return outerKf;
214
+ });
215
+ const outerAnimTr = { keyframes: outerKfs };
216
+ if (animTr.autoOrient) outerAnimTr.autoOrient = true;
217
+ out.animate = { transform: outerAnimTr };
218
+ const innerHasPivotedPart = kfs.some((kf) => {
219
+ const v = kf.value || {};
220
+ return v.rotate !== void 0 || v.scale !== void 0;
116
221
  });
222
+ const innerKfs = kfs.map((kf) => {
223
+ const v = kf.value || {};
224
+ const newValue = {};
225
+ if (v.rotate !== void 0) newValue.rotate = v.rotate;
226
+ if (v.scale !== void 0) newValue.scale = v.scale;
227
+ if (v.origin !== void 0 && (!outerHasOrigin || innerHasPivotedPart)) newValue.origin = v.origin;
228
+ const innerKf = { value: newValue };
229
+ if (kf.time !== void 0) innerKf.time = kf.time;
230
+ if (kf.easing !== void 0) innerKf.easing = kf.easing;
231
+ return innerKf;
232
+ });
233
+ const allInnerEmpty = innerKfs.every((kf) => Object.keys(kf.value).length === 0);
234
+ if (allInnerEmpty) {
235
+ delete node.animate.transform;
236
+ if (node.animate && Object.keys(node.animate).length === 0) delete node.animate;
237
+ } else {
238
+ node.animate.transform = { keyframes: innerKfs };
239
+ }
240
+ didLiftAnimate = true;
241
+ }
242
+ }
243
+ const transformationHasTranslate = (transformation == null ? void 0 : transformation.translate) !== void 0;
244
+ const stripBodyTranslateOnly = didLiftAnimate || transformationHasTranslate;
245
+ const liftedAnimateIsAutoOriented = didLiftAnimate && needsOriginOnOuter(((_b = node.animate) == null ? void 0 : _b.transform) || void 0) || didLiftAnimate && needsOriginOnOuter(((_c = out.animate) == null ? void 0 : _c.transform) || void 0);
246
+ if (typeof node.transform === "string") {
247
+ const split = splitTransformString(node.transform);
248
+ if (stripBodyTranslateOnly) {
249
+ if (split.translate !== void 0) {
250
+ if (split.rest) node.transform = split.rest;
251
+ else delete node.transform;
252
+ } else if (isPureTranslateBody(node.transform)) {
253
+ delete node.transform;
254
+ } else if (liftedAnimateIsAutoOriented && isSingleMatrixBody(node.transform)) {
255
+ delete node.transform;
256
+ }
257
+ } else if (split.translate) {
258
+ out.transform = split.translate;
259
+ if (split.rest) node.transform = split.rest;
260
+ else delete node.transform;
117
261
  }
118
262
  }
119
- return valid;
263
+ return out;
120
264
  }
121
- function validatePxAnimationDefinition(v, path, errors) {
122
- if (!isObject(v)) {
123
- errors.push(path + ": expected object");
124
- return false;
265
+ function isSingleMatrixBody(s) {
266
+ const re = /(translate|rotate|scale|matrix|skewX|skewY)\(([^)]*)\)/g;
267
+ let count = 0;
268
+ let isMatrix = false;
269
+ let m;
270
+ while (m = re.exec(s)) {
271
+ count++;
272
+ if (m[1] === "matrix") isMatrix = true;
273
+ }
274
+ return count === 1 && isMatrix;
275
+ }
276
+ function isPureTranslateBody(s) {
277
+ const re = /(translate|rotate|scale|matrix|skewX|skewY)\(([^)]*)\)/g;
278
+ const ops = [];
279
+ let m;
280
+ while (m = re.exec(s)) ops.push({ name: m[1], full: m[0] });
281
+ if (ops.length !== 1) return false;
282
+ if (ops[0].name === "translate") return true;
283
+ if (ops[0].name !== "matrix") return false;
284
+ const args = /matrix\(([^)]*)\)/.exec(ops[0].full);
285
+ if (!args) return false;
286
+ const nums = args[1].split(/[\s,]+/).filter(Boolean).map(Number);
287
+ return nums.length >= 4 && nums[0] === 1 && nums[1] === 0 && nums[2] === 0 && nums[3] === 1;
288
+ }
289
+ function splitTransformString(s) {
290
+ const ops = [];
291
+ const re = /(translate|rotate|scale|matrix|skewX|skewY)\(([^)]*)\)/g;
292
+ let m;
293
+ while (m = re.exec(s)) ops.push({ name: m[1], full: m[0] });
294
+ if (!ops.length) return { rest: s || void 0 };
295
+ if (ops.every((o) => o.name === "translate")) {
296
+ return { translate: ops.map((o) => o.full).join("") };
125
297
  }
126
- let valid = true;
127
- for (const key of Object.keys(v)) {
128
- if (!validatePxPropertyAnimation(v[key], path + "." + key, errors)) valid = false;
298
+ const leading = ops[0];
299
+ const trailing = ops[ops.length - 1];
300
+ if (trailing.name === "translate" && leading.name === "translate") {
301
+ const trailingVec = parseTranslateArgs(trailing.full);
302
+ const leadingVec = parseTranslateArgs(leading.full);
303
+ const ox = -trailingVec[0];
304
+ const oy = -trailingVec[1];
305
+ const userTx = leadingVec[0] - ox;
306
+ const userTy = leadingVec[1] - oy;
307
+ if (userTx === 0 && userTy === 0) return { rest: s };
308
+ const middleAndTrailing = "translate(" + ox + "," + oy + ")" + ops.slice(1).map((o) => o.full).join("");
309
+ return { translate: "translate(" + userTx + "," + userTy + ")", rest: middleAndTrailing };
129
310
  }
130
- return valid;
311
+ if (trailing.name === "translate") return { rest: s };
312
+ const lifted = [];
313
+ let i = 0;
314
+ while (i < ops.length && ops[i].name === "translate") {
315
+ lifted.push(ops[i].full);
316
+ i++;
317
+ }
318
+ if (!lifted.length) return { rest: s };
319
+ const rest = ops.slice(i).map((o) => o.full).join("");
320
+ return {
321
+ translate: lifted.join(""),
322
+ rest: rest || void 0
323
+ };
131
324
  }
132
- function validatePxElementAnimation(v, path, errors) {
133
- if (typeof v === "string") return true;
134
- if (Array.isArray(v)) {
135
- let valid = true;
136
- v.forEach((item, i) => {
137
- if (typeof item !== "string" && !validatePxAnimationDefinition(item, path + "[" + i + "]", errors)) {
138
- valid = false;
139
- }
140
- });
141
- return valid;
325
+ function parseTranslateArgs(translateOp) {
326
+ const m = /translate\(([^)]*)\)/.exec(translateOp);
327
+ if (!m) return [0, 0];
328
+ const nums = m[1].split(/[\s,]+/).filter(Boolean).map(Number);
329
+ return [nums[0] || 0, nums[1] || 0];
330
+ }
331
+ function splitTransformationEffect(fx) {
332
+ if (!fx) return {};
333
+ const originOnOuter = needsOriginOnOuter(fx.translate);
334
+ const innerHasPivotedPart = fx.rotate !== void 0 || fx.scale !== void 0;
335
+ const outer = {};
336
+ const inner = {};
337
+ if (fx.translate !== void 0) outer.translate = fx.translate;
338
+ if (originOnOuter && fx.origin !== void 0) outer.origin = fx.origin;
339
+ if (fx.rotate !== void 0) inner.rotate = fx.rotate;
340
+ if (fx.scale !== void 0) inner.scale = fx.scale;
341
+ if (fx.skew !== void 0) inner.skew = fx.skew;
342
+ if (fx.origin !== void 0 && (!originOnOuter || innerHasPivotedPart)) inner.origin = fx.origin;
343
+ return {
344
+ outer: Object.keys(outer).length ? outer : void 0,
345
+ inner: Object.keys(inner).length ? inner : void 0
346
+ };
347
+ }
348
+ function needsOriginOnOuter(translateAnim) {
349
+ if (!translateAnim || typeof translateAnim !== "object") return false;
350
+ const obj = translateAnim;
351
+ if (obj.autoOrient) return true;
352
+ if (Array.isArray(obj.keyframes)) {
353
+ return obj.keyframes.some((kf) => kf.tangentOut || kf.tangentIn);
142
354
  }
143
- if (isObject(v)) return validatePxAnimationDefinition(v, path, errors);
144
- errors.push(path + ": expected string, array, or PxAnimationDefinition object");
145
355
  return false;
146
356
  }
147
- function validatePxTrigger(v, path, errors) {
148
- if (!isObject(v)) {
149
- errors.push(path + ": expected object");
357
+
358
+ // src/PxSchema.ts
359
+ function pathStr(path) {
360
+ if (!path.length) return ".";
361
+ let result = "";
362
+ for (const seg of path) {
363
+ if (seg.startsWith("[")) result += seg;
364
+ else result += (result ? "." : "") + seg;
365
+ }
366
+ return result;
367
+ }
368
+ var Base = class {
369
+ _canSanitize(raw) {
370
+ return this.isValid(raw);
371
+ }
372
+ optional() {
373
+ return new Optional(this);
374
+ }
375
+ };
376
+ var Optional = class extends Base {
377
+ constructor(inner) {
378
+ super();
379
+ this.inner = inner;
380
+ this._default = void 0;
381
+ }
382
+ sanitize(raw) {
383
+ if (raw === void 0 || raw === null) return void 0;
384
+ return this.inner._canSanitize(raw) ? this.inner.sanitize(raw) : void 0;
385
+ }
386
+ isValid(raw, ctx, path) {
387
+ if (raw === void 0 || raw === null) return true;
388
+ return this.inner.isValid(raw, ctx, path);
389
+ }
390
+ _canSanitize(raw) {
391
+ return raw === void 0 || raw === null || this.inner._canSanitize(raw);
392
+ }
393
+ };
394
+ var Str = class extends Base {
395
+ constructor(_default = "") {
396
+ super();
397
+ this._default = _default;
398
+ }
399
+ sanitize(raw) {
400
+ return typeof raw === "string" ? raw : this._default;
401
+ }
402
+ isValid(raw, ctx, path) {
403
+ if (typeof raw === "string") return true;
404
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected string, got " + typeof raw);
150
405
  return false;
151
406
  }
152
- let valid = true;
153
- const validStartOn = ["load", "mouseOver", "click", "scrollIntoView", "programmatic"];
154
- if (!validStartOn.includes(v.startOn)) {
155
- errors.push(path + '.startOn: invalid value "' + v.startOn + '", expected ' + validStartOn.join("|"));
156
- valid = false;
407
+ };
408
+ var Num = class extends Base {
409
+ constructor(_default = 0) {
410
+ super();
411
+ this._default = _default;
412
+ }
413
+ sanitize(raw) {
414
+ return typeof raw === "number" && isFinite(raw) ? raw : this._default;
157
415
  }
158
- if (v.outAction !== void 0) {
159
- const validOutAction = ["continue", "pause", "reset", "reverse"];
160
- if (!validOutAction.includes(v.outAction)) {
161
- errors.push(path + '.outAction: invalid value "' + v.outAction + '", expected ' + validOutAction.join("|"));
162
- valid = false;
163
- }
416
+ isValid(raw, ctx, path) {
417
+ if (typeof raw === "number" && isFinite(raw)) return true;
418
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected finite number, got " + JSON.stringify(raw));
419
+ return false;
420
+ }
421
+ };
422
+ var Bool = class extends Base {
423
+ constructor(_default = false) {
424
+ super();
425
+ this._default = _default;
164
426
  }
165
- if (v.scrollIntoViewThreshold !== void 0 && typeof v.scrollIntoViewThreshold !== "number") {
166
- errors.push(path + ".scrollIntoViewThreshold: expected number, got " + typeof v.scrollIntoViewThreshold);
167
- valid = false;
427
+ sanitize(raw) {
428
+ return typeof raw === "boolean" ? raw : this._default;
168
429
  }
169
- return valid;
170
- }
171
- function validatePxAnimatorConfig(v, path, errors) {
172
- if (!isObject(v)) {
173
- errors.push(path + ": expected object");
430
+ isValid(raw, ctx, path) {
431
+ if (typeof raw === "boolean") return true;
432
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected boolean, got " + typeof raw);
174
433
  return false;
175
434
  }
176
- let valid = true;
177
- if (v.mode !== void 0 && !["auto", "webapi", "frames"].includes(v.mode)) {
178
- errors.push(path + '.mode: invalid value "' + v.mode + `", expected 'auto'|'webapi'|'frames'`);
179
- valid = false;
435
+ };
436
+ var Literal = class extends Base {
437
+ constructor(value) {
438
+ super();
439
+ this.value = value;
440
+ this._default = value;
180
441
  }
181
- if (v.duration !== void 0 && typeof v.duration !== "number") {
182
- errors.push(path + ".duration: expected number, got " + typeof v.duration);
183
- valid = false;
442
+ sanitize(raw) {
443
+ return raw === this.value ? this.value : this._default;
184
444
  }
185
- if (v.delay !== void 0 && typeof v.delay !== "number") {
186
- errors.push(path + ".delay: expected number, got " + typeof v.delay);
187
- valid = false;
445
+ isValid(raw, ctx, path) {
446
+ if (raw === this.value) return true;
447
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected " + JSON.stringify(this.value) + ", got " + JSON.stringify(raw));
448
+ return false;
188
449
  }
189
- if (v.iterations !== void 0 && typeof v.iterations !== "number" && v.iterations !== "infinite") {
190
- errors.push(path + ".iterations: expected number or 'infinite', got " + typeof v.iterations);
191
- valid = false;
450
+ };
451
+ var Enum = class extends Base {
452
+ constructor(values, defaultVal) {
453
+ super();
454
+ this.values = values;
455
+ this._default = defaultVal != null ? defaultVal : values[0];
192
456
  }
193
- if (v.fill !== void 0 && !validateFillMode(v.fill, path + ".fill", errors)) valid = false;
194
- if (v.direction !== void 0 && !validatePlaybackDirection(v.direction, path + ".direction", errors)) valid = false;
195
- if (v.frameRate !== void 0 && typeof v.frameRate !== "number") {
196
- errors.push(path + ".frameRate: expected number, got " + typeof v.frameRate);
197
- valid = false;
457
+ sanitize(raw) {
458
+ return this.values.includes(raw) ? raw : this._default;
198
459
  }
199
- if (v.trigger !== void 0 && !validatePxTrigger(v.trigger, path + ".trigger", errors)) valid = false;
200
- return valid;
201
- }
202
- function validatePxDefs(v, path, errors) {
203
- if (!isObject(v)) {
204
- errors.push(path + ": expected object");
460
+ isValid(raw, ctx, path) {
461
+ if (this.values.includes(raw)) return true;
462
+ 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));
205
463
  return false;
206
464
  }
207
- let valid = true;
208
- if (v.easings !== void 0) {
209
- if (!isObject(v.easings)) {
210
- errors.push(path + ".easings: expected object");
211
- valid = false;
212
- } else {
213
- for (const key of Object.keys(v.easings)) {
214
- if (!validatePxEasingOrRef(v.easings[key], path + ".easings." + key, errors)) valid = false;
465
+ };
466
+ var Union = class extends Base {
467
+ constructor(schemas, defaultVal) {
468
+ super();
469
+ this.schemas = schemas;
470
+ this._default = defaultVal != null ? defaultVal : schemas[0]._default;
471
+ }
472
+ sanitize(raw) {
473
+ for (const s of this.schemas) {
474
+ if (s.isValid(raw)) return s.sanitize(raw);
475
+ }
476
+ return this._default;
477
+ }
478
+ isValid(raw, ctx, path) {
479
+ var _a;
480
+ if (this.schemas.some((s) => s.isValid(raw))) return true;
481
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": no union member matched for value " + ((_a = JSON.stringify(raw)) != null ? _a : "").slice(0, 60));
482
+ return false;
483
+ }
484
+ _canSanitize(raw) {
485
+ return this.schemas.some((s) => s._canSanitize(raw));
486
+ }
487
+ };
488
+ var DiscriminatedUnion = class extends Base {
489
+ constructor(_key, _schemas, defaultVal) {
490
+ super();
491
+ this._key = _key;
492
+ this._schemas = _schemas;
493
+ this._default = defaultVal != null ? defaultVal : _schemas[0]._default;
494
+ this._map = /* @__PURE__ */ new Map();
495
+ for (const s of _schemas) {
496
+ const keySchema = s._shape[_key];
497
+ if (keySchema) this._map.set(keySchema._default, s);
498
+ }
499
+ }
500
+ _findSchema(raw) {
501
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
502
+ const val = raw[this._key];
503
+ if (val === void 0 || val === null) return void 0;
504
+ return this._map.get(val);
505
+ }
506
+ sanitize(raw) {
507
+ var _a;
508
+ return ((_a = this._findSchema(raw)) != null ? _a : this._schemas[0]).sanitize(raw);
509
+ }
510
+ isValid(raw, ctx, path) {
511
+ const schema = this._findSchema(raw);
512
+ if (!schema) {
513
+ const val = raw !== null && typeof raw === "object" && !Array.isArray(raw) ? raw[this._key] : void 0;
514
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": no discriminated union member matched " + this._key + "=" + JSON.stringify(val));
515
+ return false;
516
+ }
517
+ return schema.isValid(raw, ctx, path);
518
+ }
519
+ _canSanitize(raw) {
520
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return false;
521
+ const schema = this._findSchema(raw);
522
+ return schema ? schema._canSanitize(raw) : this._schemas[0]._canSanitize(raw);
523
+ }
524
+ };
525
+ var Obj = class extends Base {
526
+ constructor(_shape) {
527
+ super();
528
+ this._shape = _shape;
529
+ const d = {};
530
+ for (const key of Object.keys(_shape)) d[key] = _shape[key]._default;
531
+ this._default = d;
532
+ }
533
+ sanitize(raw) {
534
+ const src = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
535
+ const out = {};
536
+ for (const key of Object.keys(this._shape)) {
537
+ out[key] = this._shape[key].sanitize(src[key]);
538
+ }
539
+ return out;
540
+ }
541
+ isValid(raw, ctx, path) {
542
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
543
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected object, got " + (Array.isArray(raw) ? "array" : typeof raw));
544
+ return false;
545
+ }
546
+ const obj = raw;
547
+ const p = path != null ? path : [];
548
+ let ok = true;
549
+ for (const key of Object.keys(this._shape)) {
550
+ p.push(key);
551
+ if (!this._shape[key].isValid(obj[key], ctx, p)) ok = false;
552
+ p.pop();
553
+ }
554
+ if (ctx == null ? void 0 : ctx.strict) {
555
+ for (const key of Object.keys(obj)) {
556
+ if (key in this._shape) continue;
557
+ p.push(key);
558
+ ctx.errors.push(pathStr(p) + ": unexpected extra key");
559
+ p.pop();
560
+ ok = false;
215
561
  }
216
562
  }
563
+ return ok;
217
564
  }
218
- if (v.animations !== void 0) {
219
- if (!isObject(v.animations)) {
220
- errors.push(path + ".animations: expected object");
221
- valid = false;
222
- } else {
223
- for (const key of Object.keys(v.animations)) {
224
- if (!validatePxAnimationDefinition(v.animations[key], path + ".animations." + key, errors)) valid = false;
565
+ _canSanitize(raw) {
566
+ return !!raw && typeof raw === "object" && !Array.isArray(raw);
567
+ }
568
+ };
569
+ var OpenObj = class extends Base {
570
+ constructor(_shape, _openSchema) {
571
+ super();
572
+ this._shape = _shape;
573
+ this._openSchema = _openSchema;
574
+ const d = {};
575
+ for (const key of Object.keys(_shape)) d[key] = _shape[key]._default;
576
+ this._default = d;
577
+ }
578
+ sanitize(raw) {
579
+ const src = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
580
+ const out = __spreadValues({}, src);
581
+ for (const key of Object.keys(this._shape)) {
582
+ out[key] = this._shape[key].sanitize(src[key]);
583
+ }
584
+ if (this._openSchema) {
585
+ for (const key of Object.keys(src)) {
586
+ if (!(key in this._shape)) out[key] = this._openSchema.sanitize(src[key]);
587
+ }
588
+ }
589
+ return out;
590
+ }
591
+ isValid(raw, ctx, path) {
592
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
593
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected object, got " + (Array.isArray(raw) ? "array" : typeof raw));
594
+ return false;
595
+ }
596
+ const obj = raw;
597
+ const p = path != null ? path : [];
598
+ let ok = true;
599
+ for (const key of Object.keys(this._shape)) {
600
+ p.push(key);
601
+ if (!this._shape[key].isValid(obj[key], ctx, p)) ok = false;
602
+ p.pop();
603
+ }
604
+ if (this._openSchema) {
605
+ for (const key of Object.keys(obj)) {
606
+ if (key in this._shape) continue;
607
+ p.push(key);
608
+ if (!this._openSchema.isValid(obj[key], ctx, p)) ok = false;
609
+ p.pop();
225
610
  }
226
611
  }
612
+ return ok;
227
613
  }
228
- if (v.styles !== void 0 && !isObject(v.styles)) {
229
- errors.push(path + ".styles: expected object");
230
- valid = false;
614
+ _canSanitize(raw) {
615
+ return !!raw && typeof raw === "object" && !Array.isArray(raw);
231
616
  }
232
- return valid;
233
- }
234
- function validatePxBinding(v, path, errors) {
235
- if (!isObject(v)) {
236
- errors.push(path + ": expected object");
237
- return false;
617
+ };
618
+ var Arr = class extends Base {
619
+ constructor(item) {
620
+ super();
621
+ this.item = item;
622
+ this._default = [];
238
623
  }
239
- let valid = true;
240
- if (typeof v.id !== "string") {
241
- errors.push(path + ".id: expected string, got " + typeof v.id);
242
- valid = false;
624
+ sanitize(raw) {
625
+ if (!Array.isArray(raw)) return [];
626
+ const out = [];
627
+ for (const el of raw) {
628
+ if (this.item._canSanitize(el)) out.push(this.item.sanitize(el));
629
+ }
630
+ return out;
243
631
  }
244
- if (!validatePxElementAnimation(v.animate, path + ".animate", errors)) valid = false;
245
- return valid;
246
- }
247
- function validatePxNode(v, path, errors) {
248
- if (!isObject(v)) {
249
- errors.push(path + ": expected object");
250
- return false;
632
+ isValid(raw, ctx, path) {
633
+ if (!Array.isArray(raw)) {
634
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected array, got " + typeof raw);
635
+ return false;
636
+ }
637
+ const p = path != null ? path : [];
638
+ let ok = true;
639
+ for (let i = 0; i < raw.length; i++) {
640
+ p.push("[" + i + "]");
641
+ if (!this.item.isValid(raw[i], ctx, p)) ok = false;
642
+ p.pop();
643
+ }
644
+ return ok;
251
645
  }
252
- let valid = true;
253
- if (typeof v.type !== "string") {
254
- errors.push(path + ".type: expected string, got " + typeof v.type);
255
- valid = false;
646
+ _canSanitize(raw) {
647
+ return Array.isArray(raw);
256
648
  }
257
- if (v.children !== void 0) {
258
- if (!Array.isArray(v.children)) {
259
- errors.push(path + ".children: expected array");
260
- valid = false;
261
- } else {
262
- v.children.forEach((child, i) => {
263
- if (!validatePxNode(child, path + ".children[" + i + "]", errors)) valid = false;
264
- });
649
+ };
650
+ var Rec = class extends Base {
651
+ constructor(value) {
652
+ super();
653
+ this.value = value;
654
+ this._default = {};
655
+ }
656
+ sanitize(raw) {
657
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return {};
658
+ const out = {};
659
+ for (const [k, v] of Object.entries(raw)) {
660
+ if (this.value._canSanitize(v)) out[k] = this.value.sanitize(v);
265
661
  }
662
+ return out;
266
663
  }
267
- if (v.animate !== void 0 && !validatePxElementAnimation(v.animate, path + ".animate", errors)) valid = false;
268
- return valid;
269
- }
270
- function validatePxSvgNode(v, path, errors) {
271
- if (!validatePxNode(v, path, errors)) return false;
272
- let valid = true;
273
- if (v.type !== "svg") {
274
- errors.push(path + ".type: expected 'svg', got '" + v.type + "'");
275
- valid = false;
664
+ isValid(raw, ctx, path) {
665
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
666
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected object/record, got " + (Array.isArray(raw) ? "array" : typeof raw));
667
+ return false;
668
+ }
669
+ const p = path != null ? path : [];
670
+ let ok = true;
671
+ for (const [k, v] of Object.entries(raw)) {
672
+ p.push(k);
673
+ if (!this.value.isValid(v, ctx, p)) ok = false;
674
+ p.pop();
675
+ }
676
+ return ok;
276
677
  }
277
- if (v.width !== void 0 && typeof v.width !== "number") {
278
- errors.push(path + ".width: expected number, got " + typeof v.width);
279
- valid = false;
678
+ _canSanitize(raw) {
679
+ return !!raw && typeof raw === "object" && !Array.isArray(raw);
280
680
  }
281
- if (v.height !== void 0 && typeof v.height !== "number") {
282
- errors.push(path + ".height: expected number, got " + typeof v.height);
283
- valid = false;
681
+ };
682
+ var Any = class extends Base {
683
+ constructor() {
684
+ super(...arguments);
685
+ this._default = void 0;
284
686
  }
285
- if (v.viewBox !== void 0 && typeof v.viewBox !== "string") {
286
- errors.push(path + ".viewBox: expected string, got " + typeof v.viewBox);
287
- valid = false;
687
+ sanitize(raw) {
688
+ return raw;
288
689
  }
289
- if (v.animator !== void 0 && !validatePxAnimatorConfig(v.animator, path + ".animator", errors)) valid = false;
290
- if (v.defs !== void 0 && !validatePxDefs(v.defs, path + ".defs", errors)) valid = false;
291
- if (v.bindings !== void 0) {
292
- if (!Array.isArray(v.bindings)) {
293
- errors.push(path + ".bindings: expected array");
294
- valid = false;
295
- } else {
296
- v.bindings.forEach((binding, i) => {
297
- if (!validatePxBinding(binding, path + ".bindings[" + i + "]", errors)) valid = false;
298
- });
690
+ isValid(_raw, _ctx, _path) {
691
+ return true;
692
+ }
693
+ _canSanitize(_raw) {
694
+ return true;
695
+ }
696
+ };
697
+ var Lazy = class extends Base {
698
+ constructor(fn, _default) {
699
+ super();
700
+ this.fn = fn;
701
+ this._default = _default;
702
+ this.resolved = null;
703
+ }
704
+ get schema() {
705
+ var _a;
706
+ return (_a = this.resolved) != null ? _a : this.resolved = this.fn();
707
+ }
708
+ sanitize(raw) {
709
+ return this.schema.sanitize(raw);
710
+ }
711
+ isValid(raw, ctx, path) {
712
+ return this.schema.isValid(raw, ctx, path);
713
+ }
714
+ _canSanitize(raw) {
715
+ return this.schema._canSanitize(raw);
716
+ }
717
+ };
718
+ var Tuple = class extends Base {
719
+ constructor(schemas) {
720
+ super();
721
+ this.schemas = schemas;
722
+ this._default = schemas.map((s) => s._default);
723
+ }
724
+ sanitize(raw) {
725
+ if (!Array.isArray(raw) || raw.length !== this.schemas.length) return this._default;
726
+ return this.schemas.map((s, i) => s.sanitize(raw[i]));
727
+ }
728
+ isValid(raw, ctx, path) {
729
+ if (!Array.isArray(raw) || raw.length !== this.schemas.length) {
730
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": expected tuple of length " + this.schemas.length + ", got " + (Array.isArray(raw) ? "array[" + raw.length + "]" : typeof raw));
731
+ return false;
732
+ }
733
+ const p = path != null ? path : [];
734
+ let ok = true;
735
+ for (let i = 0; i < this.schemas.length; i++) {
736
+ p.push("[" + i + "]");
737
+ if (!this.schemas[i].isValid(raw[i], ctx, p)) ok = false;
738
+ p.pop();
739
+ }
740
+ return ok;
741
+ }
742
+ // Require exact length so wrong-length arrays are dropped rather than repaired to default.
743
+ _canSanitize(raw) {
744
+ return Array.isArray(raw) && raw.length === this.schemas.length;
745
+ }
746
+ };
747
+ function implementsInterface() {
748
+ return (schema) => schema;
749
+ }
750
+ function schemaKeys(schema) {
751
+ return Object.fromEntries(
752
+ Object.keys(schema["_shape"]).map((k) => [k, k])
753
+ );
754
+ }
755
+ function describeSchema(schema) {
756
+ var _a;
757
+ const s = schema;
758
+ if ("_shape" in s) return { kind: "shape", shape: s._shape };
759
+ if ("item" in s) return { kind: "array", item: s.item };
760
+ if ("inner" in s) return { kind: "optional", inner: s.inner };
761
+ if ("fn" in s) return { kind: "lazy", resolved: (_a = s.resolved) != null ? _a : s.fn() };
762
+ return { kind: "leaf" };
763
+ }
764
+ var px = {
765
+ /** Matches a string. Default: '' or provided value. */
766
+ string: (defaultVal = "") => new Str(defaultVal),
767
+ /** Matches a finite number. Default: 0 or provided value. */
768
+ number: (defaultVal = 0) => new Num(defaultVal),
769
+ /** Matches a boolean. Default: false or provided value. */
770
+ boolean: (defaultVal = false) => new Bool(defaultVal),
771
+ /** Matches one exact primitive value; its default is the value itself. */
772
+ literal: (value) => new Literal(value),
773
+ /** Matches one of a fixed set of string/number values. Default: first value. */
774
+ enum: (values, defaultVal) => new Enum(values, defaultVal),
775
+ /**
776
+ * Returns the first schema whose isValid passes.
777
+ * TypeScript infers the union of all member types automatically.
778
+ */
779
+ union: (schemas, defaultVal) => new Union(schemas, defaultVal),
780
+ /**
781
+ * Discriminated union — reads `raw[key]`, finds the member schema whose
782
+ * literal at `key` matches, then delegates sanitize/isValid to that member.
783
+ * Each member must be an object schema with a `px.literal(...)` at `key`.
784
+ * TypeScript infers the union of all member types automatically.
785
+ */
786
+ discriminatedUnion: (key, schemas) => new DiscriminatedUnion(key, schemas),
787
+ /** Typed object — unknown keys are stripped. Required fields fall back to their default. */
788
+ object: (shape) => new Obj(shape),
789
+ /**
790
+ * Open object — validates known keys; passes unknown keys through as-is,
791
+ * or validates/sanitizes them against `openSchema` when provided.
792
+ */
793
+ openObject: (shape, openSchema) => new OpenObj(shape, openSchema),
794
+ /**
795
+ * Creates a new closed object schema by merging a base schema's shape with additional fields.
796
+ * The base can be the result of px.object() or px.openObject() — anything with a _shape property.
797
+ *
798
+ * @example
799
+ * const PxSvgNodeSchema = px.extendedObject(PxNodeBase, { width: px.number().optional() });
800
+ */
801
+ extendedObject: (base, extra) => new Obj(__spreadValues(__spreadValues({}, base._shape), extra)),
802
+ /** Array whose unrecoverable items are filtered out. Default: []. */
803
+ array: (item) => new Arr(item),
804
+ /** String-keyed record whose unrecoverable values are dropped. Default: {}. */
805
+ record: (value) => new Rec(value),
806
+ /** Passes anything through unchanged — always valid. */
807
+ any: () => new Any(),
808
+ /** Fixed-length tuple — validates element count and each position individually. */
809
+ tuple: (schemas) => new Tuple(schemas),
810
+ /** Defers schema creation — required for recursive types. Must supply a default value. */
811
+ lazy: (fn, defaultVal) => new Lazy(fn, defaultVal)
812
+ };
813
+
814
+ // src/PxAnimatorTypes.ts
815
+ var PX_ANIM_SRC_ATTR_NAME = "data-px-animation-src";
816
+ var PX_ANIM_ATTR_NAME = "_px_animator";
817
+ var PxAnimatorMode = {
818
+ auto: "auto",
819
+ webapi: "webapi",
820
+ frames: "frames"
821
+ };
822
+ var PxAnimatorEngine = {
823
+ webapi: PxAnimatorMode.webapi,
824
+ frames: PxAnimatorMode.frames
825
+ };
826
+ var TEXT_ATTR = "text";
827
+ var TEXT_CONTENT_ATTR = "textContent";
828
+ var INTERNAL_ATTRS = /* @__PURE__ */ new Set([
829
+ "type",
830
+ "children",
831
+ "animator",
832
+ "meta",
833
+ "animate",
834
+ TEXT_ATTR,
835
+ TEXT_CONTENT_ATTR
836
+ ]);
837
+ var PxEasingOrRefSchema = px.union([
838
+ px.string(),
839
+ px.tuple([px.number(), px.number(), px.number(), px.number()])
840
+ ]);
841
+ var PxKeyframeValueSchema = implementsInterface()(px.union([
842
+ px.string(),
843
+ // e.g. for colors
844
+ px.number(),
845
+ px.array(px.number()),
846
+ px.lazy(() => PxTransformPartsSchema, {}),
847
+ px.object({ path: px.string() }),
848
+ px.lazy(() => px.object({ paths: px.array(PxBezierPathSchema) }), { paths: [] })
849
+ ]));
850
+ var PxKeyframeSchema = implementsInterface()(px.object({
851
+ time: px.number().optional(),
852
+ t: px.number().optional(),
853
+ value: px.any().optional(),
854
+ v: px.any().optional(),
855
+ easing: PxEasingOrRefSchema.optional(),
856
+ e: PxEasingOrRefSchema.optional(),
857
+ tangentOut: px.tuple([px.number(), px.number()]).optional(),
858
+ to: px.tuple([px.number(), px.number()]).optional(),
859
+ // short alias
860
+ tangentIn: px.tuple([px.number(), px.number()]).optional(),
861
+ ti: px.tuple([px.number(), px.number()]).optional(),
862
+ // short alias
863
+ selected: px.boolean().optional()
864
+ // editor-side UI state (Player ignores it)
865
+ }));
866
+ var PxLoopSchema = implementsInterface()(px.object({
867
+ segmentCount: px.number().optional(),
868
+ before: px.boolean().optional(),
869
+ alternate: px.boolean().optional()
870
+ }));
871
+ var PxPropertyAnimationSchema = implementsInterface()(px.object({
872
+ keyframes: px.array(PxKeyframeSchema).optional(),
873
+ kfs: px.array(PxKeyframeSchema).optional(),
874
+ loop: px.union([PxLoopSchema, px.boolean()]).optional(),
875
+ autoOrient: px.boolean().optional()
876
+ }));
877
+ var PX_TRANSFORM_PART_KEYS = ["translate", "rotate", "scale", "origin"];
878
+ var PxTransformPartsSchema = implementsInterface()(px.object({
879
+ translate: px.tuple([px.number(), px.number()]).optional(),
880
+ rotate: px.number().optional(),
881
+ scale: px.tuple([px.number(), px.number()]).optional(),
882
+ origin: px.tuple([px.number(), px.number()]).optional()
883
+ }));
884
+ var PxTransformValueSchema = px.union([
885
+ px.string(),
886
+ px.object({ value: PxTransformPartsSchema }),
887
+ PxPropertyAnimationSchema
888
+ ]);
889
+ var PxAnimationDefinitionSchema = implementsInterface()(
890
+ px.record(PxPropertyAnimationSchema)
891
+ );
892
+ var PxElementAnimationSchema = implementsInterface()(px.union([
893
+ px.string(),
894
+ px.array(px.union([px.string(), PxAnimationDefinitionSchema])),
895
+ PxAnimationDefinitionSchema
896
+ ]));
897
+ var PxTriggerSchema = implementsInterface()(px.object({
898
+ startOn: px.enum(["load", "mouseOver", "click", "scrollIntoView", "programmatic"]).optional(),
899
+ outAction: px.enum(["continue", "pause", "reset", "reverse"]).optional(),
900
+ scrollIntoViewThreshold: px.number().optional()
901
+ }));
902
+ var PxDefsSchema = implementsInterface()(px.object({
903
+ easings: px.record(px.tuple([px.number(), px.number(), px.number(), px.number()])).optional(),
904
+ animations: px.record(PxAnimationDefinitionSchema).optional(),
905
+ styles: px.record(px.any()).optional()
906
+ }));
907
+ var PxAnimatorConfigSchema = implementsInterface()(px.object({
908
+ mode: px.enum([PxAnimatorMode.auto, PxAnimatorMode.webapi, PxAnimatorMode.frames]).optional(),
909
+ duration: px.number().optional(),
910
+ delay: px.number().optional(),
911
+ iterations: px.union([px.number(), px.literal("infinite")]).optional(),
912
+ fill: px.enum(["forwards", "backwards", "both", "none"]).optional(),
913
+ direction: px.enum(["normal", "reverse", "alternate", "alternate-reverse"]).optional(),
914
+ frameRate: px.number().optional(),
915
+ trigger: PxTriggerSchema.optional(),
916
+ definitions: PxDefsSchema.optional(),
917
+ animate: px.record(PxElementAnimationSchema).optional(),
918
+ debug: px.boolean().optional(),
919
+ debugInstName: px.string().optional()
920
+ }));
921
+ var PxBindingSchema = implementsInterface()(px.object({
922
+ id: px.string(),
923
+ animate: PxElementAnimationSchema
924
+ }));
925
+ var PxAttrValueSchema = px.union([
926
+ px.string(),
927
+ px.number(),
928
+ px.object({ value: px.any() }),
929
+ PxPropertyAnimationSchema
930
+ ]);
931
+ var PxAnimatableNumberSchema = px.union([
932
+ px.number(),
933
+ px.object({ value: px.number() }),
934
+ px.object({
935
+ keyframes: px.array(PxKeyframeSchema),
936
+ autoOrient: px.boolean().optional()
937
+ })
938
+ ]);
939
+ var PxAnimatableVec2Schema = px.union([
940
+ px.tuple([px.number(), px.number()]),
941
+ px.object({ value: px.tuple([px.number(), px.number()]) }),
942
+ px.object({
943
+ keyframes: px.array(PxKeyframeSchema),
944
+ autoOrient: px.boolean().optional()
945
+ })
946
+ ]);
947
+ var PxTransformationEffectSchema = implementsInterface()(px.object({
948
+ translate: PxAnimatableVec2Schema.optional(),
949
+ rotate: PxAnimatableNumberSchema.optional(),
950
+ scale: PxAnimatableVec2Schema.optional(),
951
+ skew: PxAnimatableVec2Schema.optional(),
952
+ origin: PxAnimatableVec2Schema.optional()
953
+ }));
954
+ var PxRepeaterEffectSchema = implementsInterface()(px.object({
955
+ copies: px.number().optional(),
956
+ translate: PxAnimatableVec2Schema.optional(),
957
+ rotate: PxAnimatableNumberSchema.optional(),
958
+ scale: PxAnimatableVec2Schema.optional(),
959
+ origin: PxAnimatableVec2Schema.optional()
960
+ }));
961
+ var PxMaskedByEffectSchema = implementsInterface()(px.object({
962
+ href: px.string().optional(),
963
+ maskType: px.string().optional(),
964
+ maskUnits: px.string().optional(),
965
+ maskContentUnits: px.string().optional()
966
+ }));
967
+ var PxTrimPathEffectSchema = implementsInterface()(px.object({
968
+ offset: PxAnimatableNumberSchema.optional(),
969
+ range: PxAnimatableVec2Schema.optional(),
970
+ trimAllAsOne: px.boolean().optional()
971
+ }));
972
+ var PxRetimeEffectSchema = implementsInterface()(px.object({
973
+ baseId: px.string().optional(),
974
+ start: px.number().optional(),
975
+ stretch: px.number().optional(),
976
+ timeCrop: px.tuple([px.number(), px.number()]).optional()
977
+ }));
978
+ var PxRefEffectSchema = implementsInterface()(px.object({
979
+ baseId: px.string().optional(),
980
+ type: px.string().optional()
981
+ }));
982
+ var PxGradientUnits = {
983
+ userSpaceOnUse: "userSpaceOnUse",
984
+ objectBoundingBox: "objectBoundingBox"
985
+ };
986
+ var PxGradientSpreadMethod = {
987
+ pad: "pad",
988
+ reflect: "reflect",
989
+ repeat: "repeat"
990
+ };
991
+ var PxGradientType = {
992
+ linear: "linear",
993
+ radial: "radial"
994
+ };
995
+ var PxGradientStopSchema = implementsInterface()(px.object({
996
+ offset: px.number(),
997
+ color: px.string()
998
+ }));
999
+ var PxAnimatableGradientStopsSchema = px.union([
1000
+ px.array(PxGradientStopSchema),
1001
+ px.object({ value: px.array(PxGradientStopSchema) }),
1002
+ px.object({ keyframes: px.array(PxKeyframeSchema) })
1003
+ ]);
1004
+ var PxFillGradientEffectSchema = implementsInterface()(px.object({
1005
+ type: px.enum([PxGradientType.linear, PxGradientType.radial]),
1006
+ p1: px.tuple([px.number(), px.number()]).optional(),
1007
+ p2: px.tuple([px.number(), px.number()]).optional(),
1008
+ c: px.tuple([px.number(), px.number()]).optional(),
1009
+ r: px.number().optional(),
1010
+ fp: px.tuple([px.number(), px.number()]).optional(),
1011
+ stops: PxAnimatableGradientStopsSchema.optional(),
1012
+ gradientUnits: px.string().optional(),
1013
+ spreadMethod: px.string().optional(),
1014
+ gradientTransform: px.string().optional()
1015
+ }));
1016
+ var PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;
1017
+ var PxTextAlongPathEffectSchema = implementsInterface()(px.object({
1018
+ href: px.string(),
1019
+ lengthAdjust: px.string().optional(),
1020
+ method: px.string().optional(),
1021
+ spacing: px.string().optional(),
1022
+ startOffset: PxAnimatableNumberSchema.optional(),
1023
+ textLength: PxAnimatableNumberSchema.optional()
1024
+ }));
1025
+ var PxEffectsSchema = implementsInterface()(px.object({
1026
+ transformation: PxTransformationEffectSchema.optional(),
1027
+ repeater: PxRepeaterEffectSchema.optional(),
1028
+ maskedBy: PxMaskedByEffectSchema.optional(),
1029
+ trimPath: PxTrimPathEffectSchema.optional(),
1030
+ retime: PxRetimeEffectSchema.optional(),
1031
+ isCombinedShape: px.boolean().optional(),
1032
+ ref: PxRefEffectSchema.optional(),
1033
+ fillGradient: PxFillGradientEffectSchema.optional(),
1034
+ strokeGradient: PxStrokeGradientEffectSchema.optional(),
1035
+ textAlongPath: PxTextAlongPathEffectSchema.optional()
1036
+ }));
1037
+ function validateNodeEffects(root, opts) {
1038
+ const warnings = [];
1039
+ const walk = (node, path) => {
1040
+ if (node && node.effects) {
1041
+ const ctx = { errors: [], warnings: [], strict: !!(opts == null ? void 0 : opts.strict) };
1042
+ const ok = PxEffectsSchema.isValid(node.effects, ctx, [path + ".effects"]);
1043
+ if (!ok) {
1044
+ for (const err of ctx.errors) warnings.push(err);
1045
+ }
1046
+ }
1047
+ if (node && Array.isArray(node.children)) {
1048
+ node.children.forEach((c, i) => walk(c, path + ".children[" + i + "]"));
299
1049
  }
1050
+ };
1051
+ walk(root, "root");
1052
+ return warnings;
1053
+ }
1054
+ var PxNodeBase = px.openObject({
1055
+ type: px.string(),
1056
+ id: px.string().optional(),
1057
+ meta: px.any().optional(),
1058
+ // Player-effects bucket emitted by the Editor's lightweight design format.
1059
+ // Consumed and removed by `applyPlayerEffects` before any other normalisation
1060
+ // (see `createAnimatorImpl`), so downstream code never sees it.
1061
+ effects: PxEffectsSchema.optional(),
1062
+ // `PxElementAnimation` (not just `PxAnimationDefinition`) — accepts
1063
+ // string ref / array of refs / inline definition / mixed array; mirrors
1064
+ // `animator.animate` map values and what `processNode` resolves at runtime.
1065
+ animate: PxElementAnimationSchema.optional(),
1066
+ style: px.union([px.string(), px.record(px.union([px.string(), px.number()]))]).optional()
1067
+ }, PxAttrValueSchema);
1068
+ var PxNodeSchema = px.openObject(__spreadProps(__spreadValues({}, PxNodeBase._shape), {
1069
+ children: px.lazy(() => px.array(PxNodeSchema), []).optional()
1070
+ }), PxAttrValueSchema);
1071
+ var PxSvgNodeExtra = px.object({
1072
+ width: px.number().optional(),
1073
+ height: px.number().optional(),
1074
+ viewBox: px.string().optional(),
1075
+ animator: PxAnimatorConfigSchema.optional()
1076
+ });
1077
+ var PxAnimatedSvgDocumentSchema = px.openObject(__spreadProps(__spreadValues(__spreadValues({}, PxNodeBase._shape), PxSvgNodeExtra._shape), {
1078
+ type: px.literal("svg"),
1079
+ // override string → literal to require 'svg'
1080
+ children: px.array(PxNodeSchema).optional()
1081
+ }), PxAttrValueSchema);
1082
+ var PxBezierPathSchema = implementsInterface()(px.object({
1083
+ v: px.array(px.array(px.number())),
1084
+ i: px.array(px.array(px.number())).optional(),
1085
+ o: px.array(px.array(px.number())).optional(),
1086
+ c: px.boolean().optional()
1087
+ }));
1088
+ function isPxElementFileFormat(fileJson) {
1089
+ if (!(fileJson && typeof fileJson === "object" && !Array.isArray(fileJson))) {
1090
+ return false;
300
1091
  }
301
- if (v.design !== void 0 && !validatePxNode(v.design, path + ".design", errors)) valid = false;
302
- return valid;
1092
+ return fileJson["type"] === "svg" || fileJson["tagName"] === "svg";
303
1093
  }
304
1094
  function isPxElementFileFormatDeep(fileJson) {
305
- const errors = [];
306
- const valid = validatePxSvgNode(fileJson, "root", errors);
307
- return { valid, errors };
1095
+ const valid = PxAnimatedSvgDocumentSchema.isValid(fileJson);
1096
+ return { valid, errors: valid ? [] : ["Document failed schema validation"] };
308
1097
  }
309
1098
  function getAnimatorConfig(doc) {
310
1099
  var _a, _b;
@@ -313,55 +1102,842 @@ function getAnimatorConfig(doc) {
313
1102
  function getDefs(doc) {
314
1103
  var _a;
315
1104
  if (!doc) return void 0;
316
- return doc.defs || ((_a = doc.meta) == null ? void 0 : _a.defs);
1105
+ return (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.definitions;
317
1106
  }
318
1107
  function getBindings(doc) {
319
1108
  var _a;
320
1109
  if (!doc) return void 0;
321
- return doc.bindings || ((_a = doc.meta) == null ? void 0 : _a.bindings);
1110
+ const animate = (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.animate;
1111
+ if (!animate) return void 0;
1112
+ return Object.entries(animate).map(([id, anim]) => ({ id, animate: anim }));
322
1113
  }
323
1114
  function getChildren(doc) {
324
1115
  return doc == null ? void 0 : doc.children;
325
1116
  }
326
1117
 
327
- // src/PxAnimatorUtil.ts
328
- function bezierToSvgPath(path) {
329
- var _a, _b, _c, _d;
330
- const v = path.v;
331
- const i = path.i;
332
- const o = path.o;
333
- const c = path.c;
334
- if (!v.length) return "";
335
- const d = [];
336
- const len = v.length;
337
- d.push("M" + v[0][0] + "," + v[0][1]);
338
- for (let idx = 1; idx < len; idx++) {
339
- const prevV = v[idx - 1];
340
- const prevO = (_a = o == null ? void 0 : o[idx - 1]) != null ? _a : prevV;
341
- const currI = (_b = i == null ? void 0 : i[idx]) != null ? _b : v[idx];
342
- const currV = v[idx];
343
- const isLine = prevO[0] === prevV[0] && prevO[1] === prevV[1] && (currI[0] === currV[0] && currI[1] === currV[1]);
344
- if (isLine) {
345
- d.push("L" + currV[0] + "," + currV[1]);
346
- } else {
347
- d.push("C" + prevO[0] + "," + prevO[1] + "," + currI[0] + "," + currI[1] + "," + currV[0] + "," + currV[1]);
1118
+ // src/PxNodeCloneUtil.ts
1119
+ function deepClonePxNode(value) {
1120
+ if (value === null || typeof value !== "object") return value;
1121
+ if (Array.isArray(value)) return value.map(deepClonePxNode);
1122
+ const out = {};
1123
+ for (const k of Object.keys(value)) out[k] = deepClonePxNode(value[k]);
1124
+ return out;
1125
+ }
1126
+ function regenerateIdsAndRewriteRefs(root, genId3) {
1127
+ const oldToNew = /* @__PURE__ */ new Map();
1128
+ const walkAssign = (n) => {
1129
+ var _a;
1130
+ if (typeof n.id === "string") {
1131
+ const newId = genId3();
1132
+ oldToNew.set(n.id, newId);
1133
+ n.id = newId;
348
1134
  }
349
- }
350
- if (c && len > 0) {
351
- const lastV = v[len - 1];
352
- const lastO = (_c = o == null ? void 0 : o[len - 1]) != null ? _c : lastV;
353
- const firstI = (_d = i == null ? void 0 : i[0]) != null ? _d : v[0];
354
- const firstV = v[0];
355
- const isLine = lastO[0] === lastV[0] && lastO[1] === lastV[1] && (firstI[0] === firstV[0] && firstI[1] === firstV[1]);
356
- if (!isLine) {
357
- d.push("C" + lastO[0] + "," + lastO[1] + "," + firstI[0] + "," + firstI[1] + "," + firstV[0] + "," + firstV[1]);
1135
+ (_a = n.children) == null ? void 0 : _a.forEach(walkAssign);
1136
+ };
1137
+ walkAssign(root);
1138
+ const rewriteUrl = (s) => s.replace(/url\(#([^)]+)\)/g, (m, oldId) => {
1139
+ const newId = oldToNew.get(oldId);
1140
+ return newId ? "url(#" + newId + ")" : m;
1141
+ });
1142
+ const walkRewrite = (n) => {
1143
+ var _a;
1144
+ if (typeof n.href === "string" && n.href.startsWith("#")) {
1145
+ const newId = oldToNew.get(n.href.slice(1));
1146
+ if (newId) n.href = "#" + newId;
358
1147
  }
359
- d.push("z");
1148
+ for (const k of Object.keys(n)) {
1149
+ if (k === "children" || k === "effects" || k === "meta" || k === "animate" || k === "href" || k === "id") continue;
1150
+ const v = n[k];
1151
+ if (typeof v === "string" && v.indexOf("url(#") !== -1) {
1152
+ n[k] = rewriteUrl(v);
1153
+ }
1154
+ }
1155
+ (_a = n.children) == null ? void 0 : _a.forEach(walkRewrite);
1156
+ };
1157
+ walkRewrite(root);
1158
+ return oldToNew;
1159
+ }
1160
+ function toFiniteNum(v) {
1161
+ const n = typeof v === "number" ? v : typeof v === "string" ? parseFloat(v) : NaN;
1162
+ return Number.isFinite(n) ? n : 0;
1163
+ }
1164
+ function applyUseOffsetToG(gNode) {
1165
+ var _a;
1166
+ const x = toFiniteNum(gNode.x);
1167
+ const y = toFiniteNum(gNode.y);
1168
+ delete gNode.x;
1169
+ delete gNode.y;
1170
+ if (!x && !y) return gNode;
1171
+ const offset = "translate(" + x + "," + y + ")";
1172
+ const carriesTransform = gNode.transform !== void 0 || gNode.animate !== void 0;
1173
+ if (carriesTransform) {
1174
+ const inner = { type: "g", transform: offset, children: (_a = gNode.children) != null ? _a : [] };
1175
+ gNode.children = [inner];
1176
+ } else {
1177
+ gNode.transform = offset;
360
1178
  }
361
- return d.join("");
1179
+ return gNode;
362
1180
  }
363
- function interpolateNum(a, b, t) {
364
- return a + (b - a) * t;
1181
+
1182
+ // src/effects/util.ts
1183
+ function genId(ctx, prefix) {
1184
+ return "_lw_" + prefix + "_" + ctx.nextId++;
1185
+ }
1186
+ function stripHash(href) {
1187
+ return typeof href === "string" ? href.replace(/^#/, "") : void 0;
1188
+ }
1189
+ function indexById(node, map) {
1190
+ var _a;
1191
+ if (typeof node.id === "string") map.set(node.id, node);
1192
+ (_a = node.children) == null ? void 0 : _a.forEach((child) => indexById(child, map));
1193
+ }
1194
+ function spliceDefs(root, defs) {
1195
+ if (!defs.length) return;
1196
+ const existing = root.children || (root.children = []);
1197
+ existing.unshift({ type: "defs", children: defs });
1198
+ }
1199
+ var clone = deepClonePxNode;
1200
+ function regenerateIdsInClone(root, ctx) {
1201
+ return regenerateIdsAndRewriteRefs(root, () => genId(ctx, "retimed"));
1202
+ }
1203
+
1204
+ // src/effects/gradientEffect.ts
1205
+ function applyFillGradientEffect(node, fx, ctx) {
1206
+ return applyGradient(node, fx, ctx, "fill");
1207
+ }
1208
+ function applyStrokeGradientEffect(node, fx, ctx) {
1209
+ return applyGradient(node, fx, ctx, "stroke");
1210
+ }
1211
+ function applyGradient(node, fx, ctx, attr) {
1212
+ if (!fx) return node;
1213
+ const id = genId(ctx, "grad");
1214
+ const def = synthesiseGradientDef(fx, id, ctx);
1215
+ ctx.defs.push(def);
1216
+ node[attr] = "url(#" + id + ")";
1217
+ return node;
1218
+ }
1219
+ function synthesiseGradientDef(fx, id, ctx) {
1220
+ const out = {
1221
+ type: fx.type === PxGradientType.radial ? "radialGradient" : "linearGradient",
1222
+ id
1223
+ };
1224
+ if (fx.type === PxGradientType.linear) {
1225
+ if (fx.p1) {
1226
+ out.x1 = String(fx.p1[0]);
1227
+ out.y1 = String(fx.p1[1]);
1228
+ }
1229
+ if (fx.p2) {
1230
+ out.x2 = String(fx.p2[0]);
1231
+ out.y2 = String(fx.p2[1]);
1232
+ }
1233
+ } else {
1234
+ if (fx.c) {
1235
+ out.cx = String(fx.c[0]);
1236
+ out.cy = String(fx.c[1]);
1237
+ }
1238
+ if (fx.r !== void 0) out.r = String(fx.r);
1239
+ if (fx.fp) {
1240
+ out.fx = String(fx.fp[0]);
1241
+ out.fy = String(fx.fp[1]);
1242
+ }
1243
+ }
1244
+ if (fx.gradientUnits) out.gradientUnits = fx.gradientUnits;
1245
+ if (fx.spreadMethod) out.spreadMethod = fx.spreadMethod;
1246
+ if (fx.gradientTransform) out.gradientTransform = fx.gradientTransform;
1247
+ out.children = buildStopChildren(fx.stops, ctx);
1248
+ return out;
1249
+ }
1250
+ function buildStopChildren(stops, ctx) {
1251
+ var _a, _b, _c, _d;
1252
+ if (!stops) return [];
1253
+ if (Array.isArray(stops)) return stops.map(staticStopNode);
1254
+ if (typeof stops === "object" && Array.isArray(stops.value)) {
1255
+ return stops.value.map(staticStopNode);
1256
+ }
1257
+ const animBlock = stops;
1258
+ const kfs = animBlock.keyframes;
1259
+ if (!Array.isArray(kfs) || !kfs.length) return [];
1260
+ let stopCount = 0;
1261
+ for (const kf of kfs) {
1262
+ const v = (_a = kf.value) != null ? _a : kf.v;
1263
+ if (Array.isArray(v) && v.length > stopCount) stopCount = v.length;
1264
+ }
1265
+ if (!stopCount) return [];
1266
+ const firstKfValue = (_b = kfs[0].value) != null ? _b : kfs[0].v;
1267
+ const baselineStops = [];
1268
+ for (let i = 0; i < stopCount; i++) {
1269
+ const s = (_d = (_c = firstKfValue == null ? void 0 : firstKfValue[i]) != null ? _c : prevDefinedStop(kfs, 0, i)) != null ? _d : { offset: i / Math.max(1, stopCount - 1), color: "#000000" };
1270
+ baselineStops.push({ offset: s.offset, color: s.color });
1271
+ }
1272
+ return baselineStops.map((bs, i) => animatedStopNode(bs, kfs, i, ctx));
1273
+ }
1274
+ function staticStopNode(s) {
1275
+ return {
1276
+ type: "stop",
1277
+ offset: formatOffset(s.offset),
1278
+ stopColor: s.color
1279
+ };
1280
+ }
1281
+ function animatedStopNode(baseline, kfs, stopIdx, _ctx) {
1282
+ var _a, _b, _c, _d, _e;
1283
+ const colorKfs = [];
1284
+ const offsetKfs = [];
1285
+ let offsetVaries = false;
1286
+ for (const kf of kfs) {
1287
+ const t = (_b = (_a = kf.time) != null ? _a : kf.t) != null ? _b : 0;
1288
+ const arr = (_c = kf.value) != null ? _c : kf.v;
1289
+ const sliced = (_d = arr == null ? void 0 : arr[stopIdx]) != null ? _d : prevDefinedStop(kfs, kfs.indexOf(kf), stopIdx);
1290
+ if (!sliced) continue;
1291
+ const easing = (_e = kf.easing) != null ? _e : kf.e;
1292
+ const colorOut = { time: t, value: sliced.color };
1293
+ if (easing !== void 0) colorOut.easing = easing;
1294
+ colorKfs.push(colorOut);
1295
+ const offsetOut = { time: t, value: sliced.offset };
1296
+ if (easing !== void 0) offsetOut.easing = easing;
1297
+ offsetKfs.push(offsetOut);
1298
+ if (sliced.offset !== baseline.offset) offsetVaries = true;
1299
+ }
1300
+ const stop = {
1301
+ type: "stop",
1302
+ offset: formatOffset(baseline.offset),
1303
+ stopColor: baseline.color
1304
+ };
1305
+ const animate = {};
1306
+ if (colorKfs.length) animate.stopColor = { keyframes: colorKfs };
1307
+ if (offsetVaries && offsetKfs.length) animate.offset = { keyframes: offsetKfs };
1308
+ if (Object.keys(animate).length) stop.animate = animate;
1309
+ return stop;
1310
+ }
1311
+ function prevDefinedStop(kfs, fromIdx, stopIdx) {
1312
+ var _a, _b;
1313
+ for (let i = fromIdx; i >= 0; i--) {
1314
+ const arr = (_a = kfs[i].value) != null ? _a : kfs[i].v;
1315
+ if (arr == null ? void 0 : arr[stopIdx]) return arr[stopIdx];
1316
+ }
1317
+ for (let i = fromIdx + 1; i < kfs.length; i++) {
1318
+ const arr = (_b = kfs[i].value) != null ? _b : kfs[i].v;
1319
+ if (arr == null ? void 0 : arr[stopIdx]) return arr[stopIdx];
1320
+ }
1321
+ return void 0;
1322
+ }
1323
+ function formatOffset(o) {
1324
+ const pct = Math.round(o * 1e3) / 10;
1325
+ return pct + "%";
1326
+ }
1327
+
1328
+ // src/effects/maskedByEffect.ts
1329
+ function applyMaskedByEffect(node, fx, transformation, ctx) {
1330
+ if (!fx) return node;
1331
+ if (!fx.href) {
1332
+ ctx.errors.push("maskedBy.href missing \u2014 cannot build mask");
1333
+ return node;
1334
+ }
1335
+ const maskId = genId(ctx, "mask");
1336
+ let content = { type: "use", href: "#" + fx.href };
1337
+ if (transformation) {
1338
+ content = wrapInverseTransform(content, transformation, ctx);
1339
+ } else if (hasAnimateTransform(node)) {
1340
+ content = wrapInverseAnimatedBodyTransform(content, node, ctx);
1341
+ } else {
1342
+ const bodyStatic = readTransformationFromBody(node);
1343
+ if (bodyStatic) content = wrapInverseTransform(content, bodyStatic, ctx);
1344
+ }
1345
+ const includeTargetOwn = transformation === void 0 && !nodeHasBodyTransform(node);
1346
+ content = wrapAncestorChainCompensation(content, node, fx.href, ctx, includeTargetOwn);
1347
+ const mask = { type: "mask", id: maskId, children: [content] };
1348
+ if (fx.maskType) mask.maskType = fx.maskType;
1349
+ if (fx.maskUnits) mask.maskUnits = fx.maskUnits;
1350
+ if (fx.maskContentUnits) mask.maskContentUnits = fx.maskContentUnits;
1351
+ ctx.defs.push(mask);
1352
+ node.mask = "url(#" + maskId + ")";
1353
+ return node;
1354
+ }
1355
+ function wrapInverseTransform(inner, fx, ctx) {
1356
+ if (!fx) return inner;
1357
+ const origin = readStaticOrigin(fx.origin, ctx);
1358
+ let n = inner;
1359
+ n = wrapInversePart(n, "translate" /* Translate */, fx.translate, void 0, ctx);
1360
+ n = wrapInversePart(n, "rotate" /* Rotate */, fx.rotate, origin, ctx);
1361
+ n = wrapInversePart(n, "scale" /* Scale */, fx.scale, origin, ctx);
1362
+ return n;
1363
+ }
1364
+ function wrapInversePart(inner, part, raw, origin, ctx) {
1365
+ if (raw === void 0) return inner;
1366
+ const normalisedRaw = part === "scale" /* Scale */ && Array.isArray(raw) ? [raw[0] / 100, raw[1] / 100] : raw;
1367
+ const v = readAnimatable(normalisedRaw);
1368
+ if (v.kind === "static" /* Static */) {
1369
+ return { type: "g", transform: { value: partsRecord(part, invertPartValue(part, v.value), origin) }, children: [inner] };
1370
+ }
1371
+ if (v.kind === "animated" /* Animated */) {
1372
+ return {
1373
+ type: "g",
1374
+ animate: { transform: { keyframes: v.keyframes.map((kf) => keyframeWith(kf, partsRecord(part, invertPartValue(part, kf.value), origin))) } },
1375
+ children: [inner]
1376
+ };
1377
+ }
1378
+ return inner;
1379
+ }
1380
+ function invertPartValue(part, value) {
1381
+ if (part === "translate" /* Translate */) return [-value[0], -value[1]];
1382
+ if (part === "rotate" /* Rotate */) return -value;
1383
+ return [1 / value[0], 1 / value[1]];
1384
+ }
1385
+ function wrapInverseAnimatedBodyTransform(inner, node, _ctx) {
1386
+ var _a;
1387
+ const animate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
1388
+ const animTr = animate == null ? void 0 : animate.transform;
1389
+ const kfs = animTr && typeof animTr === "object" && Array.isArray(animTr.keyframes) ? animTr.keyframes : void 0;
1390
+ if (!kfs || !kfs.length) return inner;
1391
+ const translateKfs = [];
1392
+ const rotateKfs = [];
1393
+ const scaleKfs = [];
1394
+ for (const kf of kfs) {
1395
+ const v = ((_a = kf.value) != null ? _a : kf.v) || {};
1396
+ const baseKf = keyframeWith(kf, void 0);
1397
+ if (Array.isArray(v.translate)) {
1398
+ translateKfs.push(__spreadProps(__spreadValues({}, baseKf), { value: { translate: [-v.translate[0], -v.translate[1]] } }));
1399
+ }
1400
+ if (typeof v.rotate === "number") {
1401
+ const rec = { rotate: -v.rotate };
1402
+ if (Array.isArray(v.origin)) rec.origin = [v.origin[0], v.origin[1]];
1403
+ rotateKfs.push(__spreadProps(__spreadValues({}, baseKf), { value: rec }));
1404
+ }
1405
+ if (Array.isArray(v.scale)) {
1406
+ const rec = { scale: [1 / v.scale[0], 1 / v.scale[1]] };
1407
+ if (Array.isArray(v.origin)) rec.origin = [v.origin[0], v.origin[1]];
1408
+ scaleKfs.push(__spreadProps(__spreadValues({}, baseKf), { value: rec }));
1409
+ }
1410
+ }
1411
+ let n = inner;
1412
+ if (translateKfs.length) n = { type: "g", animate: { transform: { keyframes: translateKfs } }, children: [n] };
1413
+ if (rotateKfs.length) n = { type: "g", animate: { transform: { keyframes: rotateKfs } }, children: [n] };
1414
+ if (scaleKfs.length) n = { type: "g", animate: { transform: { keyframes: scaleKfs } }, children: [n] };
1415
+ return n;
1416
+ }
1417
+ function nodeHasBodyTransform(node) {
1418
+ if (typeof node.transform === "string") return true;
1419
+ if (node.transform && typeof node.transform === "object") return true;
1420
+ return hasAnimateTransform(node);
1421
+ }
1422
+ function hasAnimateTransform(node) {
1423
+ const animate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
1424
+ return !!(animate && animate.transform);
1425
+ }
1426
+ function readTransformationFromBody(node) {
1427
+ if (typeof node.transform === "string") {
1428
+ const parts = parseTransformStringToParts(node.transform);
1429
+ if (!parts) return void 0;
1430
+ const out = {};
1431
+ if (parts.translate) out.translate = parts.translate;
1432
+ if (parts.rotate !== void 0) out.rotate = parts.rotate;
1433
+ if (parts.scale) out.scale = { value: parts.scale };
1434
+ if (parts.origin) out.origin = parts.origin;
1435
+ return Object.keys(out).length ? out : void 0;
1436
+ }
1437
+ return void 0;
1438
+ }
1439
+ function parseTransformStringToParts(s) {
1440
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
1441
+ const re = /([a-zA-Z]+)\s*\(([^)]*)\)/g;
1442
+ let m;
1443
+ const ops = [];
1444
+ while ((m = re.exec(s)) !== null) {
1445
+ const args = m[2].split(/[\s,]+/).filter((a) => a.length > 0).map(Number);
1446
+ ops.push({ name: m[1], args });
1447
+ }
1448
+ if (!ops.length) return void 0;
1449
+ const last = ops[ops.length - 1];
1450
+ if (last.name === "translate") {
1451
+ for (let j = ops.length - 2; j >= 0; j--) {
1452
+ const cand = ops[j];
1453
+ if (cand.name !== "translate") continue;
1454
+ const ox = (_a = cand.args[0]) != null ? _a : 0;
1455
+ const oy = (_b = cand.args[1]) != null ? _b : 0;
1456
+ const lx = (_c = last.args[0]) != null ? _c : 0;
1457
+ const ly = (_d = last.args[1]) != null ? _d : 0;
1458
+ if (lx !== -ox || ly !== -oy) continue;
1459
+ const out2 = {};
1460
+ out2.origin = [ox, oy];
1461
+ for (let k = 0; k < j; k++) {
1462
+ if (ops[k].name === "translate") {
1463
+ const tx = (_e = ops[k].args[0]) != null ? _e : 0;
1464
+ const ty = (_f = ops[k].args[1]) != null ? _f : 0;
1465
+ out2.translate = out2.translate ? [out2.translate[0] + tx, out2.translate[1] + ty] : [tx, ty];
1466
+ }
1467
+ }
1468
+ for (let k = j + 1; k < ops.length - 1; k++) {
1469
+ const op = ops[k];
1470
+ if (op.name === "rotate") out2.rotate = ((_g = out2.rotate) != null ? _g : 0) + ((_h = op.args[0]) != null ? _h : 0);
1471
+ else if (op.name === "scale") {
1472
+ const sx = (_i = op.args[0]) != null ? _i : 1;
1473
+ const sy = op.args.length > 1 ? op.args[1] : sx;
1474
+ out2.scale = out2.scale ? [out2.scale[0] * sx, out2.scale[1] * sy] : [sx, sy];
1475
+ }
1476
+ }
1477
+ return out2;
1478
+ }
1479
+ }
1480
+ let translate;
1481
+ let rotate;
1482
+ let scale;
1483
+ for (const op of ops) {
1484
+ if (op.name === "translate") {
1485
+ const dx = (_j = op.args[0]) != null ? _j : 0;
1486
+ const dy = (_k = op.args[1]) != null ? _k : 0;
1487
+ translate = translate ? [translate[0] + dx, translate[1] + dy] : [dx, dy];
1488
+ } else if (op.name === "rotate") {
1489
+ rotate = (rotate != null ? rotate : 0) + ((_l = op.args[0]) != null ? _l : 0);
1490
+ } else if (op.name === "scale") {
1491
+ const sx = (_m = op.args[0]) != null ? _m : 1;
1492
+ const sy = op.args.length > 1 ? op.args[1] : sx;
1493
+ scale = scale ? [scale[0] * sx, scale[1] * sy] : [sx, sy];
1494
+ }
1495
+ }
1496
+ const out = {};
1497
+ if (translate) out.translate = translate;
1498
+ if (rotate !== void 0) out.rotate = rotate;
1499
+ if (scale) out.scale = scale;
1500
+ return Object.keys(out).length ? out : void 0;
1501
+ }
1502
+ function wrapAncestorChainCompensation(inner, maskedNode, sourceId, ctx, includeTargetOwn) {
1503
+ const sourceNode = ctx.idMap.get(sourceId);
1504
+ const targetAncestors = ctx.maskAncestorChains.get(maskedNode) || [];
1505
+ const targetOwn = includeTargetOwn ? extractTranslateOnly(maskedNode, ctx) : void 0;
1506
+ const targetChain = targetOwn ? [...targetAncestors, targetOwn] : targetAncestors;
1507
+ const sourceChain = sourceNode && ctx.maskAncestorChains.get(sourceNode) || [];
1508
+ if (!targetChain.length && !sourceChain.length) return inner;
1509
+ const times = /* @__PURE__ */ new Set();
1510
+ for (const a of targetChain) if (a.translateKeyframes) for (const kf of a.translateKeyframes) times.add(kf.time);
1511
+ for (const a of sourceChain) if (a.translateKeyframes) for (const kf of a.translateKeyframes) times.add(kf.time);
1512
+ const animated = times.size > 0;
1513
+ if (!animated) {
1514
+ const tgt = sumStaticTranslate(targetChain);
1515
+ const src = sumStaticTranslate(sourceChain);
1516
+ const dx = src[0] - tgt[0];
1517
+ const dy = src[1] - tgt[1];
1518
+ if (dx === 0 && dy === 0) return inner;
1519
+ return { type: "g", transform: "translate(" + dx + "," + dy + ")", children: [inner] };
1520
+ }
1521
+ const sortedTimes = Array.from(times).sort((a, b) => a - b);
1522
+ const keyframes = sortedTimes.map((t) => {
1523
+ const tgt = sumTranslateAt(targetChain, t);
1524
+ const src = sumTranslateAt(sourceChain, t);
1525
+ return { time: t, value: { translate: [src[0] - tgt[0], src[1] - tgt[1]] } };
1526
+ });
1527
+ return { type: "g", animate: { transform: { keyframes } }, children: [inner] };
1528
+ }
1529
+ function sumStaticTranslate(chain) {
1530
+ let x = 0, y = 0;
1531
+ for (const a of chain) {
1532
+ if (a.translate) {
1533
+ x += a.translate[0];
1534
+ y += a.translate[1];
1535
+ }
1536
+ }
1537
+ return [x, y];
1538
+ }
1539
+ function sumTranslateAt(chain, t) {
1540
+ let x = 0, y = 0;
1541
+ for (const a of chain) {
1542
+ if (a.translateKeyframes && a.translateKeyframes.length) {
1543
+ const v = interpKfs(a.translateKeyframes, t);
1544
+ x += v[0];
1545
+ y += v[1];
1546
+ } else if (a.translate) {
1547
+ x += a.translate[0];
1548
+ y += a.translate[1];
1549
+ }
1550
+ }
1551
+ return [x, y];
1552
+ }
1553
+ function interpKfs(kfs, t) {
1554
+ if (t <= kfs[0].time) return kfs[0].value;
1555
+ if (t >= kfs[kfs.length - 1].time) return kfs[kfs.length - 1].value;
1556
+ for (let i = 1; i < kfs.length; i++) {
1557
+ if (t <= kfs[i].time) {
1558
+ const prev = kfs[i - 1];
1559
+ const cur = kfs[i];
1560
+ const a = (t - prev.time) / (cur.time - prev.time);
1561
+ return [prev.value[0] + (cur.value[0] - prev.value[0]) * a, prev.value[1] + (cur.value[1] - prev.value[1]) * a];
1562
+ }
1563
+ }
1564
+ return kfs[kfs.length - 1].value;
1565
+ }
1566
+ function collectMaskAncestorChains(root, ctx) {
1567
+ const interestingNodes = /* @__PURE__ */ new Set();
1568
+ const collectInterestingNodes = (n) => {
1569
+ var _a, _b;
1570
+ const href = (_b = (_a = n.effects) == null ? void 0 : _a.maskedBy) == null ? void 0 : _b.href;
1571
+ if (typeof href === "string") {
1572
+ interestingNodes.add(n);
1573
+ const sourceNode = ctx.idMap.get(href);
1574
+ if (sourceNode) interestingNodes.add(sourceNode);
1575
+ }
1576
+ if (Array.isArray(n.children)) for (const ch of n.children) collectInterestingNodes(ch);
1577
+ };
1578
+ collectInterestingNodes(root);
1579
+ if (interestingNodes.size === 0) return;
1580
+ const walk = (node, chain) => {
1581
+ if (interestingNodes.has(node)) ctx.maskAncestorChains.set(node, chain);
1582
+ if (Array.isArray(node.children)) {
1583
+ const own = extractTranslateOnly(node, ctx);
1584
+ const next = own ? [...chain, own] : chain;
1585
+ for (const ch of node.children) walk(ch, next);
1586
+ }
1587
+ };
1588
+ walk(root, []);
1589
+ }
1590
+ function extractTranslateOnly(node, ctx) {
1591
+ var _a, _b, _c;
1592
+ const tr = node.transform;
1593
+ const animateBlock = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate.transform : void 0;
1594
+ if (tr === void 0 && !animateBlock) return void 0;
1595
+ const out = {};
1596
+ if (typeof tr === "string") {
1597
+ const parts = parseTranslateOnlyFromString(tr, ctx);
1598
+ if (parts) out.translate = parts;
1599
+ } else if (tr && typeof tr === "object") {
1600
+ const value = tr.value;
1601
+ if (value && typeof value === "object" && Array.isArray(value.translate)) {
1602
+ out.translate = [value.translate[0] || 0, value.translate[1] || 0];
1603
+ }
1604
+ if (value && (value.rotate !== void 0 || value.scale !== void 0 || value.skew !== void 0)) {
1605
+ ctx.warnings.push("maskedBy ancestor: non-translate transform parts ignored (rotate/scale not yet supported)");
1606
+ }
1607
+ }
1608
+ if (animateBlock && Array.isArray(animateBlock.keyframes)) {
1609
+ const kfs = animateBlock.keyframes;
1610
+ const translateKfs = [];
1611
+ for (const kf of kfs) {
1612
+ const v = (_a = kf.value) != null ? _a : kf.v;
1613
+ const t = (_c = (_b = kf.time) != null ? _b : kf.t) != null ? _c : 0;
1614
+ if (v && typeof v === "object" && Array.isArray(v.translate)) {
1615
+ translateKfs.push({ time: t, value: [v.translate[0] || 0, v.translate[1] || 0] });
1616
+ if (v.rotate !== void 0 || v.scale !== void 0 || v.skew !== void 0) {
1617
+ ctx.warnings.push("maskedBy ancestor: animated non-translate parts ignored");
1618
+ }
1619
+ }
1620
+ }
1621
+ if (translateKfs.length) out.translateKeyframes = translateKfs;
1622
+ }
1623
+ return out.translate || out.translateKeyframes ? out : void 0;
1624
+ }
1625
+ function parseTranslateOnlyFromString(s, ctx) {
1626
+ const re = /([a-zA-Z]+)\s*\(([^)]*)\)/g;
1627
+ let m;
1628
+ let x = 0, y = 0;
1629
+ let seen = false;
1630
+ let droppedNonTranslate = false;
1631
+ while ((m = re.exec(s)) !== null) {
1632
+ const name = m[1];
1633
+ const args = m[2].split(/[\s,]+/).filter((a) => a.length > 0).map(Number);
1634
+ if (name === "translate") {
1635
+ x += args[0] || 0;
1636
+ y += args[1] || 0;
1637
+ seen = true;
1638
+ } else {
1639
+ droppedNonTranslate = true;
1640
+ }
1641
+ }
1642
+ if (droppedNonTranslate) ctx.warnings.push("maskedBy ancestor: non-translate transform in string ignored: " + s);
1643
+ return seen ? [x, y] : void 0;
1644
+ }
1645
+
1646
+ // src/effects/refEffect.ts
1647
+ var CONTENT_SUBREF = "content";
1648
+ function applyRefAndTransformationEffect(node, ref, transformation, ctx) {
1649
+ if (ref) {
1650
+ const baseId = ref.baseId;
1651
+ if (!baseId) {
1652
+ ctx.errors.push("ref: missing baseId");
1653
+ } else {
1654
+ const targetId = ref.type === CONTENT_SUBREF ? ctx.contentRefInnerIds.get(baseId) || baseId : baseId;
1655
+ node.href = "#" + targetId;
1656
+ }
1657
+ }
1658
+ return applyTransformationEffect(node, transformation, ctx);
1659
+ }
1660
+
1661
+ // src/effects/repeaterEffect.ts
1662
+ function applyRepeaterEffect(node, fx, ctx) {
1663
+ var _a, _b;
1664
+ if (!fx) return node;
1665
+ const copies = (_a = fx.copies) != null ? _a : 1;
1666
+ if (copies < 1) {
1667
+ ctx.errors.push("repeater.copies invalid: " + fx.copies);
1668
+ return node;
1669
+ }
1670
+ const sharedTransform = node.transform;
1671
+ const sharedAnimTransform = (_b = node.animate) == null ? void 0 : _b.transform;
1672
+ const base = clone(node);
1673
+ delete base.transform;
1674
+ if (base.animate) {
1675
+ delete base.animate.transform;
1676
+ if (Object.keys(base.animate).length === 0) delete base.animate;
1677
+ }
1678
+ const children = [base];
1679
+ for (let i = 1; i < copies; i++) {
1680
+ const baseClone = clone(base);
1681
+ const synthFx = synthesisePerCopyFx(fx, i);
1682
+ const wrapped = synthFx ? applyTransformationEffect(baseClone, synthFx, ctx) : baseClone;
1683
+ children.push(wrapped);
1684
+ }
1685
+ const wrapper = { type: "g", children };
1686
+ if (sharedTransform !== void 0) wrapper.transform = sharedTransform;
1687
+ if (sharedAnimTransform !== void 0) wrapper.animate = { transform: sharedAnimTransform };
1688
+ return wrapper;
1689
+ }
1690
+ function synthesisePerCopyFx(fx, i) {
1691
+ const out = {};
1692
+ if (fx.translate !== void 0) {
1693
+ out.translate = mapAnimatableVec2(fx.translate, (v) => [v[0] * i, v[1] * i]);
1694
+ }
1695
+ if (fx.rotate !== void 0) {
1696
+ out.rotate = mapAnimatableNumber(fx.rotate, (v) => v * i);
1697
+ }
1698
+ if (fx.scale !== void 0) {
1699
+ out.scale = synthesiseScale(fx.scale, i);
1700
+ }
1701
+ if (fx.origin !== void 0) {
1702
+ out.origin = fx.origin;
1703
+ }
1704
+ return Object.keys(out).length ? out : void 0;
1705
+ }
1706
+ function mapAnimatableVec2(raw, fn) {
1707
+ if (Array.isArray(raw)) return fn(raw);
1708
+ if (raw && typeof raw === "object") {
1709
+ const obj = raw;
1710
+ if (Array.isArray(obj.keyframes)) {
1711
+ return __spreadProps(__spreadValues({}, obj), {
1712
+ keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: fn(kf.value) }) : kf)
1713
+ });
1714
+ }
1715
+ if (obj.value !== void 0) {
1716
+ return __spreadProps(__spreadValues({}, obj), { value: fn(obj.value) });
1717
+ }
1718
+ }
1719
+ return raw;
1720
+ }
1721
+ function mapAnimatableNumber(raw, fn) {
1722
+ if (typeof raw === "number") return fn(raw);
1723
+ if (raw && typeof raw === "object") {
1724
+ const obj = raw;
1725
+ if (Array.isArray(obj.keyframes)) {
1726
+ return __spreadProps(__spreadValues({}, obj), {
1727
+ keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: fn(kf.value) }) : kf)
1728
+ });
1729
+ }
1730
+ if (obj.value !== void 0) {
1731
+ return __spreadProps(__spreadValues({}, obj), { value: fn(obj.value) });
1732
+ }
1733
+ }
1734
+ return raw;
1735
+ }
1736
+ function synthesiseScale(raw, i) {
1737
+ const scalePowerFromPercent = (v) => [Math.pow(v[0] / 100, i), Math.pow(v[1] / 100, i)];
1738
+ const scalePowerFromUnits = (v) => [Math.pow(v[0], i), Math.pow(v[1], i)];
1739
+ if (Array.isArray(raw)) {
1740
+ return { value: scalePowerFromPercent(raw) };
1741
+ }
1742
+ if (raw && typeof raw === "object") {
1743
+ const obj = raw;
1744
+ if (Array.isArray(obj.keyframes)) {
1745
+ return __spreadProps(__spreadValues({}, obj), {
1746
+ keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: scalePowerFromUnits(kf.value) }) : kf)
1747
+ });
1748
+ }
1749
+ if (obj.value !== void 0) {
1750
+ return __spreadProps(__spreadValues({}, obj), { value: scalePowerFromPercent(obj.value) });
1751
+ }
1752
+ }
1753
+ return raw;
1754
+ }
1755
+
1756
+ // src/effects/retimeEffect.ts
1757
+ var RETIME_MATERIALISATION_MODE_INLINE_G = false;
1758
+ function asRetime(r) {
1759
+ var _a, _b;
1760
+ return { start: (_a = r.start) != null ? _a : 0, stretch: (_b = r.stretch) != null ? _b : 1 };
1761
+ }
1762
+ function concatRetime(child, parent) {
1763
+ return {
1764
+ start: parent.start + parent.stretch * child.start,
1765
+ stretch: parent.stretch * child.stretch
1766
+ };
1767
+ }
1768
+ function applyAllRetimeEffects(root, ctx) {
1769
+ var _a;
1770
+ ctx.idMap.clear();
1771
+ indexById(root, ctx.idMap);
1772
+ const sites = [];
1773
+ const collect = (n) => {
1774
+ var _a2, _b;
1775
+ if ((_a2 = n.effects) == null ? void 0 : _a2.retime) sites.push(n);
1776
+ (_b = n.children) == null ? void 0 : _b.forEach(collect);
1777
+ };
1778
+ collect(root);
1779
+ for (const useNode of sites) {
1780
+ const retime = (_a = useNode.effects) == null ? void 0 : _a.retime;
1781
+ if (!retime) continue;
1782
+ delete useNode.effects.retime;
1783
+ if (Object.keys(useNode.effects).length === 0) delete useNode.effects;
1784
+ materialiseRetime(useNode, asRetime(retime), ctx);
1785
+ }
1786
+ }
1787
+ function materialiseRetime(useNode, retime, ctx) {
1788
+ const targetId = stripHash(useNode.href);
1789
+ if (!targetId) {
1790
+ ctx.errors.push("retime: <use> has no href to follow");
1791
+ return;
1792
+ }
1793
+ const chainRootId = buildChainClone(targetId, retime, ctx, /* @__PURE__ */ new Set());
1794
+ if (!chainRootId) return;
1795
+ if (RETIME_MATERIALISATION_MODE_INLINE_G) {
1796
+ const cloneNode = ctx.idMap.get(chainRootId);
1797
+ useNode.type = "g";
1798
+ delete useNode.href;
1799
+ useNode.children = [cloneNode];
1800
+ applyUseOffsetToG(useNode);
1801
+ ctx.defs = ctx.defs.filter((d) => d !== cloneNode);
1802
+ } else {
1803
+ useNode.href = "#" + chainRootId;
1804
+ }
1805
+ }
1806
+ function buildChainClone(targetId, accum, ctx, chain) {
1807
+ var _a, _b;
1808
+ if (chain.has(targetId)) {
1809
+ ctx.errors.push('retime: loop via "' + targetId + '"');
1810
+ return void 0;
1811
+ }
1812
+ const target = ctx.idMap.get(targetId);
1813
+ if (!target) {
1814
+ ctx.warnings.push('retime: target "' + targetId + '" not found');
1815
+ return void 0;
1816
+ }
1817
+ const cloneNode = clone(target);
1818
+ regenerateIdsInClone(cloneNode, ctx);
1819
+ if (target.type === "use") {
1820
+ remapKeyframeTimesOnly(cloneNode, accum.start, accum.stretch);
1821
+ } else {
1822
+ remapKeyframeTimes(cloneNode, accum.start, accum.stretch);
1823
+ }
1824
+ if ((_a = cloneNode.effects) == null ? void 0 : _a.retime) {
1825
+ delete cloneNode.effects.retime;
1826
+ if (Object.keys(cloneNode.effects).length === 0) delete cloneNode.effects;
1827
+ }
1828
+ if (target.type === "use" && target.href) {
1829
+ const subId = stripHash(target.href);
1830
+ if (subId) {
1831
+ const innerRetime = (_b = target.effects) == null ? void 0 : _b.retime;
1832
+ const subAccum = innerRetime ? concatRetime(asRetime(innerRetime), accum) : accum;
1833
+ const subChain = new Set(chain);
1834
+ subChain.add(targetId);
1835
+ const subId2 = buildChainClone(subId, subAccum, ctx, subChain);
1836
+ if (subId2) cloneNode.href = "#" + subId2;
1837
+ }
1838
+ }
1839
+ ctx.defs.push(cloneNode);
1840
+ if (typeof cloneNode.id === "string") ctx.idMap.set(cloneNode.id, cloneNode);
1841
+ return typeof cloneNode.id === "string" ? cloneNode.id : void 0;
1842
+ }
1843
+ function remapKeyframeTimes(node, start, stretch) {
1844
+ var _a;
1845
+ remapKeyframeTimesOnly(node, start, stretch);
1846
+ (_a = node.children) == null ? void 0 : _a.forEach((c) => remapKeyframeTimes(c, start, stretch));
1847
+ }
1848
+ function remapKeyframeTimesOnly(node, start, stretch) {
1849
+ const remap2 = (kfs) => {
1850
+ for (const kf of kfs) if (typeof kf.time === "number") kf.time = start + kf.time * stretch;
1851
+ };
1852
+ if (node.transform && typeof node.transform === "object" && Array.isArray(node.transform.keyframes)) {
1853
+ remap2(node.transform.keyframes);
1854
+ }
1855
+ if (node.animate && typeof node.animate === "object") {
1856
+ for (const prop of Object.keys(node.animate)) {
1857
+ const anim = node.animate[prop];
1858
+ if (anim && Array.isArray(anim.keyframes)) remap2(anim.keyframes);
1859
+ }
1860
+ }
1861
+ }
1862
+
1863
+ // src/effects/textAlongPathEffect.ts
1864
+ function applyTextAlongPathEffect(node, fx, _ctx) {
1865
+ var _a;
1866
+ if (!fx) return node;
1867
+ const href = typeof fx.href === "string" && !fx.href.startsWith("#") ? "#" + fx.href : fx.href;
1868
+ const textPath = {
1869
+ type: "textPath",
1870
+ href,
1871
+ children: (_a = node.children) != null ? _a : []
1872
+ };
1873
+ if (fx.lengthAdjust !== void 0) textPath.lengthAdjust = fx.lengthAdjust;
1874
+ if (fx.method !== void 0) textPath.method = fx.method;
1875
+ if (fx.spacing !== void 0) textPath.spacing = fx.spacing;
1876
+ applyAnimatableNumber(textPath, "startOffset", fx.startOffset);
1877
+ applyAnimatableNumber(textPath, "textLength", fx.textLength);
1878
+ node.children = [textPath];
1879
+ return node;
1880
+ }
1881
+ function applyAnimatableNumber(node, attrName, raw) {
1882
+ if (raw === void 0 || raw === null) return;
1883
+ if (typeof raw === "number") {
1884
+ node[attrName] = String(raw);
1885
+ return;
1886
+ }
1887
+ if (typeof raw === "object") {
1888
+ const obj = raw;
1889
+ if (Array.isArray(obj.keyframes)) {
1890
+ const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
1891
+ const animate = __spreadValues({}, prevAnimate || {});
1892
+ animate[attrName] = { keyframes: obj.keyframes };
1893
+ node.animate = animate;
1894
+ return;
1895
+ }
1896
+ if (typeof obj.value === "number") {
1897
+ node[attrName] = String(obj.value);
1898
+ return;
1899
+ }
1900
+ }
1901
+ }
1902
+
1903
+ // src/PxAnimatorUtil.ts
1904
+ function bezierToSvgPath(path) {
1905
+ var _a, _b, _c, _d;
1906
+ const v = path.v;
1907
+ const i = path.i;
1908
+ const o = path.o;
1909
+ const c = path.c;
1910
+ if (!v.length) return "";
1911
+ const d = [];
1912
+ const len = v.length;
1913
+ d.push("M" + v[0][0] + "," + v[0][1]);
1914
+ for (let idx = 1; idx < len; idx++) {
1915
+ const prevV = v[idx - 1];
1916
+ const prevO = (_a = o == null ? void 0 : o[idx - 1]) != null ? _a : prevV;
1917
+ const currI = (_b = i == null ? void 0 : i[idx]) != null ? _b : v[idx];
1918
+ const currV = v[idx];
1919
+ const isLine = prevO[0] === prevV[0] && prevO[1] === prevV[1] && (currI[0] === currV[0] && currI[1] === currV[1]);
1920
+ if (isLine) {
1921
+ d.push("L" + currV[0] + "," + currV[1]);
1922
+ } else {
1923
+ d.push("C" + prevO[0] + "," + prevO[1] + "," + currI[0] + "," + currI[1] + "," + currV[0] + "," + currV[1]);
1924
+ }
1925
+ }
1926
+ if (c && len > 0) {
1927
+ const lastV = v[len - 1];
1928
+ const lastO = (_c = o == null ? void 0 : o[len - 1]) != null ? _c : lastV;
1929
+ const firstI = (_d = i == null ? void 0 : i[0]) != null ? _d : v[0];
1930
+ const firstV = v[0];
1931
+ const isLine = lastO[0] === lastV[0] && lastO[1] === lastV[1] && (firstI[0] === firstV[0] && firstI[1] === firstV[1]);
1932
+ if (!isLine) {
1933
+ d.push("C" + lastO[0] + "," + lastO[1] + "," + firstI[0] + "," + firstI[1] + "," + firstV[0] + "," + firstV[1]);
1934
+ }
1935
+ d.push("z");
1936
+ }
1937
+ return d.join("");
1938
+ }
1939
+ function interpolateNum(a, b, t) {
1940
+ return a + (b - a) * t;
365
1941
  }
366
1942
  function interpolateVec(a, b, t) {
367
1943
  const res = [];
@@ -557,6 +2133,24 @@ function parseColor(s) {
557
2133
  var COLOUR_ATTR_NAMES = /* @__PURE__ */ new Set(["color", "fill", "flood-color", "lighting-color", "stop-color", "stroke"]);
558
2134
  var TRANSFORM_FN_NAMES = /* @__PURE__ */ new Set(["translate", "rotate", "scale", "skew"]);
559
2135
  var PCT_BASED_ATTR_NAMES = /* @__PURE__ */ new Set(["offset-distance", "offsetDistance"]);
2136
+ function composeTransformParts(parts, opts) {
2137
+ var _a;
2138
+ if (!parts) return "";
2139
+ const withUnits = (_a = opts == null ? void 0 : opts.withUnits) != null ? _a : true;
2140
+ const segs = [];
2141
+ const t = parts.translate;
2142
+ const o = parts.origin;
2143
+ const r = parts.rotate;
2144
+ const s = parts.scale;
2145
+ const tu = withUnits ? "px" : "";
2146
+ const ru = withUnits ? "deg" : "";
2147
+ if (t) segs.push("translate(" + t[0] + tu + "," + t[1] + tu + ")");
2148
+ if (o) segs.push("translate(" + o[0] + tu + "," + o[1] + tu + ")");
2149
+ if (r !== void 0 && r !== null) segs.push("rotate(" + r + ru + ")");
2150
+ if (s) segs.push("scale(" + s[0] + "," + s[1] + ")");
2151
+ if (o) segs.push("translate(" + -o[0] + tu + "," + -o[1] + tu + ")");
2152
+ return segs.join("");
2153
+ }
560
2154
  var STYLE_ATTR_NAMES = /* @__PURE__ */ new Set(["offset-distance", "offsetDistance"]);
561
2155
  var DEFAULT_DURATION_MS = 1e3;
562
2156
  function kebabToCamelCaseWord(kebab) {
@@ -581,6 +2175,12 @@ var SVG_CAMEL_CASE_ATTRS = /* @__PURE__ */ new Set([
581
2175
  "clipPathUnits",
582
2176
  "maskUnits",
583
2177
  "maskContentUnits",
2178
+ // Marker (SVG spec keeps these camelCase, like viewBox)
2179
+ "markerUnits",
2180
+ "markerWidth",
2181
+ "markerHeight",
2182
+ "refX",
2183
+ "refY",
584
2184
  // Text
585
2185
  "textLength",
586
2186
  "lengthAdjust",
@@ -616,288 +2216,456 @@ function camelCaseToKebabWordIfNeeded(camel) {
616
2216
  function clamp(value, min, max) {
617
2217
  return Math.max(min, Math.min(value, max));
618
2218
  }
619
-
620
- // src/PxAnimatorDOM.ts
621
- var SVG_NS = "http://www.w3.org/2000/svg";
622
- var ALLOWED_SVG_TAGS_LOWER_CASE = new Set([
623
- "svg",
624
- "g",
625
- "path",
626
- "circle",
627
- "ellipse",
628
- "rect",
629
- "line",
630
- "polyline",
631
- "polygon",
632
- "text",
633
- "tspan",
634
- "defs",
635
- "clipPath",
636
- "mask",
637
- "pattern",
638
- "linearGradient",
639
- "radialGradient",
640
- "stop",
641
- "use",
642
- "symbol",
643
- "marker",
644
- "filter",
645
- "feGaussianBlur",
646
- "feOffset",
647
- "feBlend",
648
- "feColorMatrix",
649
- "feMerge",
650
- "feMergeNode"
651
- ].map((tagName) => tagName.toLowerCase()));
652
- var ALLOWED_RESOURCE_ATTRIBUTES = [
653
- "href",
654
- // <use>
655
- "src",
656
- // <image>
657
- "filter",
658
- // url(#filterId)
659
- "clipPath",
660
- // clip-path="url(#clipPathId)"
661
- "mask",
662
- // url(#maskId)
663
- "markerStart",
664
- // marker-start="url(#markerId)"
665
- "markerMid",
666
- // marker-mid="url(#markerId)"
667
- "markerEnd"
668
- // marker-end="url(#markerId)"
669
- // 'fill', // url(#gradientId) or url(#patternId)
670
- // 'stroke', // url(#gradientId) or url(#patternId)
671
- // Don't allow 'cursor', can use external SVG, // url(cursor.svg)
672
- ];
673
- var ALLOWED_RESOURCE_ATTRIBUTES_SET = new Set(ALLOWED_RESOURCE_ATTRIBUTES);
674
- var ALLOWED_ATTRIBUTES_SET = /* @__PURE__ */ new Set([
675
- "href",
676
- "src",
677
- // Presentation
678
- "fill",
679
- "stroke",
680
- "strokeWidth",
681
- "opacity",
682
- "transform",
683
- // Geometry
684
- "x",
685
- "y",
686
- "cx",
687
- "cy",
688
- "r",
689
- "rx",
690
- "ry",
691
- "width",
692
- "height",
693
- "d",
694
- "x1",
695
- "y1",
696
- "x2",
697
- "y2",
698
- "points",
699
- // Text
700
- "fontSize",
701
- "fontFamily",
702
- "textAnchor",
703
- // Structure
704
- "id",
705
- "class",
706
- "viewBox",
707
- "preserveAspectRatio",
708
- // Gradient/Pattern
709
- "offset",
710
- "stopColor",
711
- "stopOpacity",
712
- "gradientTransform",
713
- // Clippath/Mask
714
- "clipPath",
715
- "mask",
716
- // Filter
717
- "filter",
718
- "stdDeviation",
719
- "in",
720
- "in2",
721
- "result",
722
- "mode",
723
- ...ALLOWED_RESOURCE_ATTRIBUTES
724
- ]);
725
- function sanitiseAttributeValue(name, value) {
726
- const nameLower = name.toLowerCase();
727
- if (!ALLOWED_ATTRIBUTES_SET.has(nameLower)) {
728
- console.warn("Attribute not in whitelist: ", nameLower);
729
- return void 0;
2219
+ function bezier2D_pointAt(P0, P1, P2, P3, t) {
2220
+ if (t <= 0) return [P0[0], P0[1]];
2221
+ if (t >= 1) return [P3[0], P3[1]];
2222
+ const u = 1 - t;
2223
+ const u2 = u * u;
2224
+ const u3 = u2 * u;
2225
+ const t2 = t * t;
2226
+ const t3 = t2 * t;
2227
+ const w0 = u3;
2228
+ const w1 = 3 * t * u2;
2229
+ const w2 = 3 * t2 * u;
2230
+ const w3 = t3;
2231
+ return [
2232
+ w0 * P0[0] + w1 * P1[0] + w2 * P2[0] + w3 * P3[0],
2233
+ w0 * P0[1] + w1 * P1[1] + w2 * P2[1] + w3 * P3[1]
2234
+ ];
2235
+ }
2236
+ var BEZIER_T_NUDGE = 1e-4;
2237
+ function bezier2D_derivativeAt(P0, P1, P2, P3, t) {
2238
+ const result = _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t);
2239
+ if (result[0] === 0 && result[1] === 0) {
2240
+ const nudgedT = t < 0.5 ? t + BEZIER_T_NUDGE : t - BEZIER_T_NUDGE;
2241
+ return _bezier2D_derivativeAtRaw(P0, P1, P2, P3, nudgedT);
730
2242
  }
731
- if (nameLower === "fill" || nameLower === "stroke" || nameLower === "stopColor") {
732
- const str = String(value);
733
- if (str.includes("url(") && !/^url\(#[^)]+\)$/.test(str)) {
734
- console.warn('Attribute "' + nameLower + '" blocked: url() references must be internal url(#id), got:', value);
735
- return void 0;
736
- }
737
- return value;
2243
+ return result;
2244
+ }
2245
+ function _bezier2D_derivativeAtRaw(P0, P1, P2, P3, t) {
2246
+ const u = 1 - t;
2247
+ const a = 3 * u * u;
2248
+ const b = 6 * t * u;
2249
+ const c = 3 * t * t;
2250
+ return [
2251
+ a * (P1[0] - P0[0]) + b * (P2[0] - P1[0]) + c * (P3[0] - P2[0]),
2252
+ a * (P1[1] - P0[1]) + b * (P2[1] - P1[1]) + c * (P3[1] - P2[1])
2253
+ ];
2254
+ }
2255
+ function bezier2D_arcLengthLUT(P0, P1, P2, P3, steps = 100) {
2256
+ const n = steps + 1;
2257
+ const ts = new Float64Array(n);
2258
+ const ds = new Float64Array(n);
2259
+ let prev = bezier2D_pointAt(P0, P1, P2, P3, 0);
2260
+ ts[0] = 0;
2261
+ ds[0] = 0;
2262
+ let cum = 0;
2263
+ for (let i = 1; i < n; i++) {
2264
+ const t = i / steps;
2265
+ const cur = bezier2D_pointAt(P0, P1, P2, P3, t);
2266
+ const dx = cur[0] - prev[0];
2267
+ const dy = cur[1] - prev[1];
2268
+ cum += Math.sqrt(dx * dx + dy * dy);
2269
+ ts[i] = t;
2270
+ ds[i] = cum;
2271
+ prev = cur;
738
2272
  }
739
- if (ALLOWED_RESOURCE_ATTRIBUTES_SET.has(nameLower)) {
740
- const str = String(value);
741
- if (str.startsWith("#")) {
742
- return value;
743
- }
744
- if (/^url\(#[^)]+\)$/.test(str)) {
745
- return value;
746
- }
747
- return void 0;
2273
+ return { ts, ds };
2274
+ }
2275
+ function bezier2D_arcAtT(lut, t) {
2276
+ const { ts, ds } = lut;
2277
+ const last = ts.length - 1;
2278
+ if (t <= ts[0]) return ds[0];
2279
+ if (t >= ts[last]) return ds[last];
2280
+ let lo = 1, hi = last;
2281
+ while (lo < hi) {
2282
+ const mid = lo + hi >>> 1;
2283
+ if (ts[mid] < t) lo = mid + 1;
2284
+ else hi = mid;
748
2285
  }
749
- return value;
2286
+ const tPrev = ts[hi - 1];
2287
+ const span = ts[hi] - tPrev;
2288
+ const frac = span > 0 ? (t - tPrev) / span : 0;
2289
+ return ds[hi - 1] + frac * (ds[hi] - ds[hi - 1]);
750
2290
  }
751
- function createElement(tagName, normalisedProps, style, children, textContent) {
752
- if (!ALLOWED_SVG_TAGS_LOWER_CASE.has(tagName.toLowerCase())) return null;
753
- const element = document.createElementNS(SVG_NS, tagName);
754
- for (const propName in normalisedProps) {
755
- element.setAttribute(
756
- camelCaseToKebabWordIfNeeded(propName),
757
- sanitiseAttributeValue(propName, normalisedProps[propName])
758
- );
2291
+ function invertEasing(easing) {
2292
+ if (!easing) return (y) => y;
2293
+ const flipped = [easing[1], easing[0], easing[3], easing[2]];
2294
+ return cubicBezier(flipped);
2295
+ }
2296
+
2297
+ // src/PxMotionPath.ts
2298
+ function getKfTranslate(kf) {
2299
+ var _a;
2300
+ const v = (_a = kf.value) != null ? _a : kf.v;
2301
+ if (!v) return void 0;
2302
+ if (Array.isArray(v) && v.length >= 2 && typeof v[0] === "number" && typeof v[1] === "number") {
2303
+ return [v[0], v[1]];
759
2304
  }
760
- if (style) {
761
- for (const styleProp in style) {
762
- element.style[styleProp] = String(style[styleProp]);
2305
+ const tr = v.translate;
2306
+ if (Array.isArray(tr) && tr.length >= 2) return [tr[0], tr[1]];
2307
+ return void 0;
2308
+ }
2309
+ function getKfTime(kf) {
2310
+ var _a, _b;
2311
+ return (_b = (_a = kf.time) != null ? _a : kf.t) != null ? _b : 0;
2312
+ }
2313
+ function getKfEasing(kf) {
2314
+ var _a;
2315
+ return (_a = kf.easing) != null ? _a : kf.e;
2316
+ }
2317
+ function propAnimIsMotionPath(anim) {
2318
+ var _a, _b, _c;
2319
+ const kfs = (_a = anim.keyframes) != null ? _a : anim.kfs;
2320
+ if (!Array.isArray(kfs)) return false;
2321
+ if (anim.autoOrient) return true;
2322
+ for (const kf of kfs) {
2323
+ if (((_b = kf.tangentIn) != null ? _b : kf.ti) || ((_c = kf.tangentOut) != null ? _c : kf.to)) return true;
2324
+ }
2325
+ return false;
2326
+ }
2327
+ var _segmentCache = /* @__PURE__ */ new WeakMap();
2328
+ function getSegmentCache(prevKf, nextKf, prevPos, nextPos) {
2329
+ var _a, _b;
2330
+ const existing = _segmentCache.get(prevKf);
2331
+ if (existing) return existing;
2332
+ const to = (_a = prevKf.tangentOut) != null ? _a : prevKf.to;
2333
+ const ti = (_b = nextKf.tangentIn) != null ? _b : nextKf.ti;
2334
+ const P1 = [prevPos[0] + (to ? to[0] : 0), prevPos[1] + (to ? to[1] : 0)];
2335
+ const P2 = [nextPos[0] + (ti ? ti[0] : 0), nextPos[1] + (ti ? ti[1] : 0)];
2336
+ const lut = bezier2D_arcLengthLUT(prevPos, P1, P2, nextPos);
2337
+ const entry = {
2338
+ P0: prevPos,
2339
+ P1,
2340
+ P2,
2341
+ P3: nextPos,
2342
+ lut,
2343
+ totalArc: lut.ds[lut.ds.length - 1]
2344
+ };
2345
+ _segmentCache.set(prevKf, entry);
2346
+ return entry;
2347
+ }
2348
+ function evaluateMotionPathSegment(prevKf, nextKf, prevPos, nextPos, localProgress, autoOrient) {
2349
+ const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);
2350
+ const t = seg.totalArc === 0 ? localProgress : tFromArcFraction(seg.lut, localProgress);
2351
+ const point = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, t);
2352
+ if (!autoOrient) return { translate: point };
2353
+ const tan = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, t);
2354
+ const rotateDeg = Math.atan2(tan[1], tan[0]) * 180 / Math.PI;
2355
+ return { translate: point, rotateDeg };
2356
+ }
2357
+ function tFromArcFraction(lut, arcFrac) {
2358
+ const total = lut.ds[lut.ds.length - 1];
2359
+ const target = arcFrac * total;
2360
+ const { ts, ds } = lut;
2361
+ const last = ds.length - 1;
2362
+ if (target <= 0) return ts[0];
2363
+ if (target >= ds[last]) return ts[last];
2364
+ let lo = 1, hi = last;
2365
+ while (lo < hi) {
2366
+ const mid = lo + hi >>> 1;
2367
+ if (ds[mid] < target) lo = mid + 1;
2368
+ else hi = mid;
2369
+ }
2370
+ const dPrev = ds[hi - 1];
2371
+ const span = ds[hi] - dPrev;
2372
+ const frac = span > 0 ? (target - dPrev) / span : 0;
2373
+ return ts[hi - 1] + frac * (ts[hi] - ts[hi - 1]);
2374
+ }
2375
+ var DEFAULT_FLATNESS_TOL = 0.5;
2376
+ var DEFAULT_ROTATION_TOL = 5;
2377
+ var DEFAULT_MAX_SAMPLES = 32;
2378
+ function materialiseMotionPathInPropAnim(anim, opts) {
2379
+ var _a, _b, _c, _d;
2380
+ if (!propAnimIsMotionPath(anim)) return anim;
2381
+ const kfs = (_a = anim.keyframes) != null ? _a : anim.kfs;
2382
+ if (!Array.isArray(kfs) || kfs.length < 2) return anim;
2383
+ const autoOrient = !!anim.autoOrient;
2384
+ const flatnessTol = (_b = opts == null ? void 0 : opts.flatnessTolerance) != null ? _b : DEFAULT_FLATNESS_TOL;
2385
+ const rotationTol = (_c = opts == null ? void 0 : opts.rotationTolerance) != null ? _c : DEFAULT_ROTATION_TOL;
2386
+ const maxSamples = (_d = opts == null ? void 0 : opts.maxSamplesPerSegment) != null ? _d : DEFAULT_MAX_SAMPLES;
2387
+ const out = [];
2388
+ const firstPos = getKfTranslate(kfs[0]);
2389
+ if (!firstPos) return anim;
2390
+ const firstRotate = autoOrient ? derivAngleForFirstKf(kfs[0], kfs[1]) : void 0;
2391
+ out.push(makeOutKf(
2392
+ getKfTime(kfs[0]),
2393
+ buildOutKfValue(getKfValueParts(kfs[0]), getKfValueParts(kfs[0]), 0, firstPos, firstRotate, autoOrient)
2394
+ ));
2395
+ for (let i = 0; i < kfs.length - 1; i++) {
2396
+ const prevKf = kfs[i];
2397
+ const nextKf = kfs[i + 1];
2398
+ const prevPos = getKfTranslate(prevKf);
2399
+ const nextPos = getKfTranslate(nextKf);
2400
+ if (!prevPos || !nextPos) {
2401
+ out.push(makeOutKf(
2402
+ getKfTime(nextKf),
2403
+ buildOutKfValue(getKfValueParts(nextKf), getKfValueParts(nextKf), 1, nextPos != null ? nextPos : [0, 0], void 0, autoOrient)
2404
+ ));
2405
+ continue;
2406
+ }
2407
+ if (autoOrient && i > 0) {
2408
+ insertSharpCornerStepKfIfNeeded(out, prevKf, nextKf, prevPos, nextPos, rotationTol);
763
2409
  }
2410
+ materialiseSegment(out, prevKf, nextKf, prevPos, nextPos, autoOrient, flatnessTol, rotationTol, maxSamples);
764
2411
  }
765
- if (children) {
766
- for (const child of children) {
767
- element.appendChild(child);
2412
+ const lastInE = getKfEasing(kfs[kfs.length - 1]);
2413
+ if (lastInE) out[out.length - 1].e = lastInE;
2414
+ if (autoOrient) unwrapAutoOrientRotations(out);
2415
+ const result = { kfs: out };
2416
+ if (anim.loop !== void 0) result.loop = anim.loop;
2417
+ return result;
2418
+ }
2419
+ function unwrapAutoOrientRotations(kfs) {
2420
+ var _a;
2421
+ let prev;
2422
+ for (const kf of kfs) {
2423
+ const v = (_a = kf.v) != null ? _a : kf.value;
2424
+ if (!v || typeof v.rotate !== "number") continue;
2425
+ if (prev === void 0) {
2426
+ prev = v.rotate;
2427
+ continue;
768
2428
  }
2429
+ let r = v.rotate;
2430
+ while (r - prev > 180) r -= 360;
2431
+ while (r - prev < -180) r += 360;
2432
+ v.rotate = r;
2433
+ prev = r;
769
2434
  }
770
- if (textContent) element.textContent = textContent;
771
- return element;
772
2435
  }
773
- function resolveStyle(style, defs) {
2436
+ function makeOutKf(time, value) {
2437
+ return { t: time, v: value };
2438
+ }
2439
+ function getKfValueParts(kf) {
774
2440
  var _a;
775
- if (!style) return void 0;
776
- if (typeof style === "string") {
777
- return (_a = defs == null ? void 0 : defs.styles) == null ? void 0 : _a[style];
2441
+ const v = (_a = kf.value) != null ? _a : kf.v;
2442
+ if (!v || typeof v !== "object" || Array.isArray(v)) return void 0;
2443
+ return v;
2444
+ }
2445
+ function interpolatePart(prev, next, p) {
2446
+ if (prev === void 0) return next;
2447
+ if (next === void 0) return prev;
2448
+ if (typeof prev === "number" && typeof next === "number") {
2449
+ return prev + (next - prev) * p;
778
2450
  }
779
- return style;
2451
+ if (Array.isArray(prev) && Array.isArray(next) && prev.length === next.length) {
2452
+ const out = new Array(prev.length);
2453
+ for (let i = 0; i < prev.length; i++) {
2454
+ const a = typeof prev[i] === "number" ? prev[i] : 0;
2455
+ const b = typeof next[i] === "number" ? next[i] : 0;
2456
+ out[i] = a + (b - a) * p;
2457
+ }
2458
+ return out;
2459
+ }
2460
+ return p < 0.5 ? prev : next;
780
2461
  }
781
- function getNormalizedProps(props) {
782
- const propsCopy = {};
783
- for (const key of Object.keys(props)) {
784
- if (INTERNAL_ATTRS.has(key)) continue;
785
- if (key === "style") continue;
786
- let value = props[key];
787
- if (COLOUR_ATTR_NAMES.has(key) && Array.isArray(value)) {
788
- propsCopy[key] = toRGBA(value);
789
- } else if (TRANSFORM_FN_NAMES.has(key)) {
790
- if (Array.isArray(value)) {
791
- if (key === "translate") value = value.map((v) => v + "px");
792
- value = value.join(",");
793
- }
794
- if (key === "rotate") value = value + "deg";
795
- propsCopy["transform"] = key + "(" + value + ")";
796
- } else if (value !== void 0 && value !== null) {
797
- propsCopy[key] = String(value);
2462
+ function buildOutKfValue(prevV, nextV, p, translate, rotateDegFromAutoOrient, autoOrient) {
2463
+ const value = { translate };
2464
+ const keys = /* @__PURE__ */ new Set();
2465
+ if (prevV) for (const k of Object.keys(prevV)) keys.add(k);
2466
+ if (nextV) for (const k of Object.keys(nextV)) keys.add(k);
2467
+ for (const k of keys) {
2468
+ if (k === "translate") continue;
2469
+ if (k === "rotate" && autoOrient) continue;
2470
+ const pv = prevV == null ? void 0 : prevV[k];
2471
+ const nv = nextV == null ? void 0 : nextV[k];
2472
+ if (pv === void 0 && nv === void 0) continue;
2473
+ value[k] = interpolatePart(pv, nv, p);
2474
+ }
2475
+ if (rotateDegFromAutoOrient !== void 0) value.rotate = rotateDegFromAutoOrient;
2476
+ return value;
2477
+ }
2478
+ function derivAngleForFirstKf(kf0, kf1) {
2479
+ const p0 = getKfTranslate(kf0);
2480
+ const p1 = getKfTranslate(kf1);
2481
+ if (!p0 || !p1) return 0;
2482
+ const seg = getSegmentCache(kf0, kf1, p0, p1);
2483
+ const tan = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, 0);
2484
+ return Math.atan2(tan[1], tan[0]) * 180 / Math.PI;
2485
+ }
2486
+ function wrappedAngleDelta(a, b) {
2487
+ let d = a - b;
2488
+ while (d > 180) d -= 360;
2489
+ while (d < -180) d += 360;
2490
+ return d;
2491
+ }
2492
+ function insertSharpCornerStepKfIfNeeded(out, prevKf, nextKf, prevPos, nextPos, rotationTol) {
2493
+ var _a;
2494
+ const lastKf = out[out.length - 1];
2495
+ const lastV = (_a = lastKf.v) != null ? _a : lastKf.value;
2496
+ const prevExit = lastV == null ? void 0 : lastV.rotate;
2497
+ if (typeof prevExit !== "number") return;
2498
+ const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);
2499
+ const tanAtStart = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, 0);
2500
+ const nextEntry = Math.atan2(tanAtStart[1], tanAtStart[0]) * 180 / Math.PI;
2501
+ const delta = wrappedAngleDelta(nextEntry, prevExit);
2502
+ if (Math.abs(delta) <= rotationTol) return;
2503
+ const dupValue = buildOutKfValue(
2504
+ getKfValueParts(prevKf),
2505
+ getKfValueParts(prevKf),
2506
+ 0,
2507
+ prevPos,
2508
+ nextEntry,
2509
+ true
2510
+ );
2511
+ out.push(makeOutKf(getKfTime(prevKf), dupValue));
2512
+ }
2513
+ function materialiseSegment(out, prevKf, nextKf, prevPos, nextPos, autoOrient, flatnessTol, rotationTol, maxSamples) {
2514
+ const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);
2515
+ const prevTime = getKfTime(prevKf);
2516
+ const nextTime = getKfTime(nextKf);
2517
+ const prevEasing = getKfEasing(prevKf);
2518
+ const invertFn = invertEasing(prevEasing);
2519
+ const prevV = getKfValueParts(prevKf);
2520
+ const nextV = getKfValueParts(nextKf);
2521
+ const interiorTs = computeSampleTs(seg, autoOrient, flatnessTol, rotationTol, maxSamples);
2522
+ const samples = [];
2523
+ for (const t of interiorTs) {
2524
+ const arc = bezier2D_arcAtT(seg.lut, t);
2525
+ const p = clamp(seg.totalArc > 0 ? arc / seg.totalArc : t, 0, 1);
2526
+ const u = clamp(invertFn(p), 0, 1);
2527
+ const pos = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, t);
2528
+ const sample = { u, p, pos };
2529
+ if (autoOrient) {
2530
+ const tan = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, t);
2531
+ sample.rotateDeg = Math.atan2(tan[1], tan[0]) * 180 / Math.PI;
798
2532
  }
2533
+ samples.push(sample);
2534
+ }
2535
+ let remaining = prevEasing;
2536
+ let prevU = 0;
2537
+ const startIdx = out.length - 1;
2538
+ for (let i = 0; i < samples.length; i++) {
2539
+ const s = samples[i];
2540
+ const xFrac = prevU < 1 ? clamp((s.u - prevU) / (1 - prevU), 0, 1) : 1;
2541
+ const { left, right } = splitEasing(remaining, xFrac);
2542
+ const ownerIdx = i === 0 ? startIdx : out.length - 1;
2543
+ if (left) out[ownerIdx].e = left;
2544
+ else delete out[ownerIdx].e;
2545
+ const tGlobal = prevTime + s.u * (nextTime - prevTime);
2546
+ const value = buildOutKfValue(prevV, nextV, s.p, s.pos, s.rotateDeg, autoOrient);
2547
+ out.push(makeOutKf(tGlobal, value));
2548
+ remaining = right;
2549
+ prevU = s.u;
799
2550
  }
800
- return propsCopy;
801
2551
  }
802
- function renderNode(node, defs) {
803
- if (!node) return null;
804
- const _a = node, { type, children, animate, style } = _a, props = __objRest(_a, ["type", "children", "animate", "style"]);
805
- const nodeDefs = node.defs || defs;
806
- const resolvedStyle = resolveStyle(style, nodeDefs);
807
- let childElements;
808
- if (children) {
809
- for (const ch of children) {
810
- const child = renderNode(ch, nodeDefs);
811
- if (child) {
812
- if (!childElements) childElements = [];
813
- childElements.push(child);
814
- }
2552
+ function computeSampleTs(seg, autoOrient, flatnessTol, rotationTol, maxSamples) {
2553
+ const extremes = [];
2554
+ addAxisExtremes(seg.P0[0], seg.P1[0], seg.P2[0], seg.P3[0], extremes);
2555
+ addAxisExtremes(seg.P0[1], seg.P1[1], seg.P2[1], seg.P3[1], extremes);
2556
+ extremes.sort((a, b) => a - b);
2557
+ const critical = [0];
2558
+ for (const t of extremes) {
2559
+ if (t > critical[critical.length - 1] + 1e-6 && t < 1 - 1e-6) {
2560
+ critical.push(t);
815
2561
  }
816
2562
  }
817
- return createElement(
818
- type || "g",
819
- getNormalizedProps(props),
820
- resolvedStyle,
821
- childElements,
822
- props[TEXT_ATTR] || props[TEXT_CONTENT_ATTR]
2563
+ critical.push(1);
2564
+ const out = [];
2565
+ const budget = { remaining: maxSamples - critical.length };
2566
+ for (let i = 0; i < critical.length - 1; i++) {
2567
+ bisect(critical[i], critical[i + 1], out, seg, autoOrient, flatnessTol, rotationTol, budget);
2568
+ }
2569
+ return out;
2570
+ }
2571
+ function addAxisExtremes(p0, p1, p2, p3, out) {
2572
+ const a = p1 - p0;
2573
+ const b = p2 - p1;
2574
+ const c = p3 - p2;
2575
+ const A = a - 2 * b + c;
2576
+ const B = 2 * (b - a);
2577
+ const C = a;
2578
+ if (Math.abs(A) < 1e-10) {
2579
+ if (Math.abs(B) > 1e-10) {
2580
+ const t = -C / B;
2581
+ if (t > 1e-6 && t < 1 - 1e-6) out.push(t);
2582
+ }
2583
+ return;
2584
+ }
2585
+ const disc = B * B - 4 * A * C;
2586
+ if (disc < 0) return;
2587
+ const sq = Math.sqrt(disc);
2588
+ const t1 = (-B - sq) / (2 * A);
2589
+ const t2 = (-B + sq) / (2 * A);
2590
+ if (t1 > 1e-6 && t1 < 1 - 1e-6) out.push(t1);
2591
+ if (t2 > 1e-6 && t2 < 1 - 1e-6) out.push(t2);
2592
+ }
2593
+ function bisect(tA, tB, out, seg, autoOrient, flatnessTol, rotationTol, budget) {
2594
+ const tMid = (tA + tB) / 2;
2595
+ const pA = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tA);
2596
+ const pB = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tB);
2597
+ const span = tB - tA;
2598
+ const p25 = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tA + span * 0.25);
2599
+ const p50 = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tMid);
2600
+ const p75 = bezier2D_pointAt(seg.P0, seg.P1, seg.P2, seg.P3, tA + span * 0.75);
2601
+ const dev = Math.max(
2602
+ perpDist(p25, pA, pB),
2603
+ perpDist(p50, pA, pB),
2604
+ perpDist(p75, pA, pB)
823
2605
  );
2606
+ let rotOk = true;
2607
+ if (autoOrient) {
2608
+ const tanA = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, tA);
2609
+ const tanB = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, tB);
2610
+ const angA = Math.atan2(tanA[1], tanA[0]) * 180 / Math.PI;
2611
+ const angB = Math.atan2(tanB[1], tanB[0]) * 180 / Math.PI;
2612
+ let delta = Math.abs(angA - angB);
2613
+ if (delta > 180) delta = 360 - delta;
2614
+ if (delta > rotationTol) rotOk = false;
2615
+ }
2616
+ if (dev <= flatnessTol && rotOk || budget.remaining <= 0 || span < 1e-6) {
2617
+ out.push(tB);
2618
+ return;
2619
+ }
2620
+ budget.remaining -= 1;
2621
+ bisect(tA, tMid, out, seg, autoOrient, flatnessTol, rotationTol, budget);
2622
+ bisect(tMid, tB, out, seg, autoOrient, flatnessTol, rotationTol, budget);
824
2623
  }
825
-
826
- // src/PxAnimatorTriggers.ts
827
- function setupAnimationTriggers(api, config) {
828
- const { startOn, outAction = "continue", scrollIntoViewThreshold = 0.5 } = config;
829
- const root = api.getRootElement();
830
- if (!root) {
831
- console.warn("setupAnimationTriggers: No root element found for animation.");
832
- return api;
2624
+ function perpDist(q, pA, pB) {
2625
+ const dx = pB[0] - pA[0];
2626
+ const dy = pB[1] - pA[1];
2627
+ const len2 = dx * dx + dy * dy;
2628
+ if (len2 < 1e-20) {
2629
+ const qdx = q[0] - pA[0];
2630
+ const qdy = q[1] - pA[1];
2631
+ return Math.sqrt(qdx * qdx + qdy * qdy);
833
2632
  }
834
- const start = () => {
835
- api.play();
836
- };
837
- const handleEndAction = () => {
838
- switch (outAction) {
839
- case "pause":
840
- api.pause();
841
- break;
842
- case "reset":
843
- api.cancel();
844
- break;
845
- case "reverse":
846
- api.play();
847
- break;
848
- case "continue":
849
- default:
850
- break;
851
- }
852
- };
853
- switch (startOn) {
854
- case "load": {
855
- const startHandler = () => start();
856
- if (document.readyState === "complete") {
857
- startHandler();
858
- } else {
859
- window.addEventListener("load", startHandler, { once: true });
2633
+ const cross = (q[0] - pA[0]) * dy - (q[1] - pA[1]) * dx;
2634
+ return Math.abs(cross) / Math.sqrt(len2);
2635
+ }
2636
+ function materialiseMotionPathsInTree(root, opts) {
2637
+ const out = walkAndMaterialise(root, opts);
2638
+ return out != null ? out : root;
2639
+ }
2640
+ function walkAndMaterialise(node, opts) {
2641
+ let newChildren;
2642
+ if (node.children) {
2643
+ for (let i = 0; i < node.children.length; i++) {
2644
+ const ch = node.children[i];
2645
+ const ret = walkAndMaterialise(ch, opts);
2646
+ if (ret !== null) {
2647
+ if (!newChildren) newChildren = node.children.slice();
2648
+ newChildren[i] = ret;
860
2649
  }
861
- break;
862
- }
863
- case "mouseOver": {
864
- const mouseOverHandler = () => start();
865
- const mouseOutHandler = () => handleEndAction();
866
- root.addEventListener("mouseenter", mouseOverHandler);
867
- root.addEventListener("mouseleave", mouseOutHandler);
868
- break;
869
- }
870
- case "click": {
871
- const clickHandler = () => {
872
- if (api.isPlaying()) {
873
- handleEndAction();
874
- } else {
875
- start();
876
- }
877
- };
878
- root.addEventListener("click", clickHandler);
879
- break;
880
2650
  }
881
- case "scrollIntoView": {
882
- const observer = new IntersectionObserver(
883
- (entries) => {
884
- entries.forEach((entry) => {
885
- if (entry.isIntersecting && entry.intersectionRatio >= scrollIntoViewThreshold) {
886
- start();
887
- } else {
888
- handleEndAction();
889
- }
890
- });
891
- },
892
- { threshold: scrollIntoViewThreshold }
893
- );
894
- observer.observe(root);
895
- break;
2651
+ }
2652
+ let newAnimate;
2653
+ const animBucket = node.animate;
2654
+ if (animBucket && typeof animBucket === "object" && !Array.isArray(animBucket)) {
2655
+ const animDef = animBucket;
2656
+ const transformAnim = animDef.transform;
2657
+ if (transformAnim && typeof transformAnim === "object" && propAnimIsMotionPath(transformAnim)) {
2658
+ const materialised = materialiseMotionPathInPropAnim(transformAnim, opts);
2659
+ if (materialised !== transformAnim) {
2660
+ newAnimate = __spreadProps(__spreadValues({}, animDef), { transform: materialised });
2661
+ }
896
2662
  }
897
- case "programmatic":
898
- break;
899
2663
  }
900
- return api;
2664
+ if (!newChildren && !newAnimate) return null;
2665
+ const cloned = __spreadValues({}, node);
2666
+ if (newChildren) cloned.children = newChildren;
2667
+ if (newAnimate) cloned.animate = newAnimate;
2668
+ return cloned;
901
2669
  }
902
2670
 
903
2671
  // src/PxDefinitions.ts
@@ -963,6 +2731,8 @@ function parseSvgPathToBezier(d) {
963
2731
  currentPath.o.push([x2, y2]);
964
2732
  } else if (type === "Z" || type === "z") {
965
2733
  currentPath.c = true;
2734
+ } else {
2735
+ console.warn('Unsupported path command "' + type + '"');
966
2736
  }
967
2737
  }
968
2738
  return res;
@@ -980,13 +2750,17 @@ function isPathString(value) {
980
2750
  return typeof value === "string" && extractPathData(value) !== void 0;
981
2751
  }
982
2752
  function normalizePathValue(value) {
2753
+ if (value && typeof value === "object" && typeof value.path === "string") {
2754
+ const d = extractPathData(value.path);
2755
+ return d ? { paths: parseSvgPathToBezier(d) } : value;
2756
+ }
983
2757
  if (value && typeof value === "object" && "paths" in value) {
984
2758
  const pathsArray = value.paths;
985
2759
  if (Array.isArray(pathsArray) && pathsArray.length > 0) {
986
2760
  if (isPathString(pathsArray[0])) {
987
2761
  const paths = [];
988
- for (const pathStr of pathsArray) {
989
- const d = extractPathData(pathStr);
2762
+ for (const pathStr2 of pathsArray) {
2763
+ const d = extractPathData(pathStr2);
990
2764
  if (d) {
991
2765
  paths.push(...parseSvgPathToBezier(d));
992
2766
  }
@@ -999,8 +2773,8 @@ function normalizePathValue(value) {
999
2773
  if (Array.isArray(value)) {
1000
2774
  if (value.length > 0 && isPathString(value[0])) {
1001
2775
  const paths = [];
1002
- for (const pathStr of value) {
1003
- const d = extractPathData(pathStr);
2776
+ for (const pathStr2 of value) {
2777
+ const d = extractPathData(pathStr2);
1004
2778
  if (d) {
1005
2779
  paths.push(...parseSvgPathToBezier(d));
1006
2780
  }
@@ -1064,11 +2838,34 @@ function interpolateValue(propName, a, b, t) {
1064
2838
  if (COLOUR_ATTR_NAMES.has(propName)) {
1065
2839
  return interpolateColor(a || [0, 0, 0, 1], b || [0, 0, 0, 1], t);
1066
2840
  }
2841
+ if (propName === "transform" && typeof a === "object" && a !== null && !Array.isArray(a) && typeof b === "object" && b !== null && !Array.isArray(b)) {
2842
+ return interpolateTransformParts(a, b, t);
2843
+ }
2844
+ if (propName === "rotate" && typeof a === "number" && typeof b === "number") {
2845
+ return interpolateNum(a, b, t);
2846
+ }
1067
2847
  if (TRANSFORM_FN_NAMES.has(propName) || propName === "stroke-dasharray" || propName === "strokeDasharray") {
1068
2848
  return interpolateVec(a || [], b || [], t);
1069
2849
  }
1070
2850
  return interpolateNum(+(a || 0), +(b || 0), t);
1071
2851
  }
2852
+ function interpolateTransformParts(a, b, t) {
2853
+ const keys = /* @__PURE__ */ new Set([...Object.keys(a != null ? a : {}), ...Object.keys(b != null ? b : {})]);
2854
+ const out = {};
2855
+ for (const k of keys) {
2856
+ const av = a == null ? void 0 : a[k];
2857
+ const bv = b == null ? void 0 : b[k];
2858
+ if (k === "rotate") {
2859
+ out[k] = interpolateNum(+(av != null ? av : 0), +(bv != null ? bv : 0), t);
2860
+ } else if (k === "translate" || k === "scale" || k === "origin") {
2861
+ const fallback = k === "scale" ? [1, 1] : [0, 0];
2862
+ out[k] = interpolateVec(av || fallback, bv || fallback, t);
2863
+ } else {
2864
+ out[k] = bv != null ? bv : av;
2865
+ }
2866
+ }
2867
+ return out;
2868
+ }
1072
2869
  function expandLoopKeyframes(propName, keyframes, loop, duration) {
1073
2870
  var _a, _b, _c, _d, _e;
1074
2871
  const totalIntervals = keyframes.length - 1;
@@ -1095,11 +2892,16 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
1095
2892
  const segEndT = (_e = segKfs[segKfs.length - 1].t) != null ? _e : 0;
1096
2893
  const segDuration = segEndT - segStartT;
1097
2894
  if (segDuration <= 0) return keyframes;
1098
- const template = segKfs.map((kf) => ({
1099
- relT: (kf.t - segStartT) / segDuration,
1100
- v: kf.v,
1101
- e: kf.e
1102
- }));
2895
+ const template = segKfs.map((kf) => {
2896
+ var _a2, _b2;
2897
+ return {
2898
+ relT: (kf.t - segStartT) / segDuration,
2899
+ v: kf.v,
2900
+ e: kf.e,
2901
+ tangentIn: (_a2 = kf.tangentIn) != null ? _a2 : kf.ti,
2902
+ tangentOut: (_b2 = kf.tangentOut) != null ? _b2 : kf.to
2903
+ };
2904
+ });
1103
2905
  const fullReps = Math.floor(fillDuration / segDuration);
1104
2906
  const remainder = fillDuration - fullReps * segDuration;
1105
2907
  const partialFraction = remainder / segDuration;
@@ -1113,7 +2915,12 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
1113
2915
  relT: 1 - template[i].relT,
1114
2916
  v: template[i].v,
1115
2917
  // Easing for reversed transition: use reversed easing from the forward "from" keyframe
1116
- e: i > 0 ? reverseEasing(template[i - 1].e) : void 0
2918
+ e: i > 0 ? reverseEasing(template[i - 1].e) : void 0,
2919
+ // Reversed traversal swaps each vertex's in/out spatial tangents
2920
+ // (geometry is identical, walked backwards), so curvature and
2921
+ // auto-orientation survive the reversed rep.
2922
+ tangentIn: template[i].tangentOut,
2923
+ tangentOut: template[i].tangentIn
1117
2924
  });
1118
2925
  }
1119
2926
  } else {
@@ -1135,11 +2942,14 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
1135
2942
  looped.push({ t: repStart + cutRelT * segDuration, v: cutValue, e: void 0 });
1136
2943
  return;
1137
2944
  }
1138
- looped.push({
2945
+ const pushed = {
1139
2946
  t: repStart + entry.relT * segDuration,
1140
2947
  v: entry.v,
1141
2948
  e: i < entries.length - 1 ? entry.e : void 0
1142
- });
2949
+ };
2950
+ if (entry.tangentIn) pushed.tangentIn = entry.tangentIn;
2951
+ if (entry.tangentOut) pushed.tangentOut = entry.tangentOut;
2952
+ looped.push(pushed);
1143
2953
  }
1144
2954
  }
1145
2955
  for (let rep = 0; rep < fullReps; rep++) {
@@ -1160,7 +2970,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
1160
2970
  }
1161
2971
  }
1162
2972
  function normalizeKeyframes(propName, propAnim, duration, defs) {
1163
- var _a, _b, _c, _d, _e;
2973
+ var _a, _b, _c, _d, _e, _f, _g;
1164
2974
  const keyframes = propAnim.keyframes || propAnim.kfs || [];
1165
2975
  const normalized = [];
1166
2976
  for (const kf of keyframes) {
@@ -1170,14 +2980,20 @@ function normalizeKeyframes(propName, propAnim, duration, defs) {
1170
2980
  if (propName === "d") {
1171
2981
  value = normalizePathValue(value);
1172
2982
  }
1173
- if (COLOUR_ATTR_NAMES.has(propName)) {
2983
+ const propNameKebab = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;
2984
+ if (COLOUR_ATTR_NAMES.has(propNameKebab)) {
1174
2985
  value = (_e = parseColor(value)) != null ? _e : value;
1175
2986
  }
1176
- normalized.push({
2987
+ const normKf = {
1177
2988
  t: timePct,
1178
2989
  v: value,
1179
2990
  e: resolveEasing(easing, defs)
1180
- });
2991
+ };
2992
+ const tIn = (_f = kf.tangentIn) != null ? _f : kf.ti;
2993
+ const tOut = (_g = kf.tangentOut) != null ? _g : kf.to;
2994
+ if (tIn) normKf.tangentIn = tIn;
2995
+ if (tOut) normKf.tangentOut = tOut;
2996
+ normalized.push(normKf);
1181
2997
  }
1182
2998
  normalized.sort((a, b) => {
1183
2999
  var _a2, _b2;
@@ -1199,21 +3015,84 @@ function mergeAnimationDefinitions(animations) {
1199
3015
  }
1200
3016
  return merged;
1201
3017
  }
3018
+ function materialiseInternalLoopsInPropAnim(propName, propAnim, duration) {
3019
+ var _a;
3020
+ const loopRaw = propAnim.loop;
3021
+ if (loopRaw === void 0 || loopRaw === null || loopRaw === false) return propAnim;
3022
+ const loop = loopRaw === true ? {} : loopRaw;
3023
+ const rawKfs = (_a = propAnim.keyframes) != null ? _a : propAnim.kfs;
3024
+ if (!Array.isArray(rawKfs) || rawKfs.length < 2) return propAnim;
3025
+ const propNameKebab = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;
3026
+ const isColour = COLOUR_ATTR_NAMES.has(propNameKebab);
3027
+ const kfs = rawKfs.map((kf) => {
3028
+ var _a2, _b, _c, _d, _e, _f, _g, _h;
3029
+ const t = (_a2 = kf.t) != null ? _a2 : kf.time;
3030
+ let v = (_b = kf.v) != null ? _b : kf.value;
3031
+ if (propName === "d") v = normalizePathValue(v);
3032
+ if (isColour) v = (_c = parseColor(v)) != null ? _c : v;
3033
+ const e = (_d = kf.e) != null ? _d : kf.easing;
3034
+ const out2 = { t, v, e };
3035
+ if ((_e = kf.tangentIn) != null ? _e : kf.ti) out2.tangentIn = (_f = kf.tangentIn) != null ? _f : kf.ti;
3036
+ if ((_g = kf.tangentOut) != null ? _g : kf.to) out2.tangentOut = (_h = kf.tangentOut) != null ? _h : kf.to;
3037
+ return out2;
3038
+ });
3039
+ const expanded = expandLoopKeyframes(propName, kfs, loop, duration);
3040
+ const out = { kfs: expanded };
3041
+ if (propAnim.autoOrient !== void 0) out.autoOrient = propAnim.autoOrient;
3042
+ return out;
3043
+ }
3044
+ function materialiseInternalLoopsInTree(root, duration) {
3045
+ const ret = walkAndMaterialiseLoops(root, duration);
3046
+ return ret != null ? ret : root;
3047
+ }
3048
+ function walkAndMaterialiseLoops(node, duration) {
3049
+ let newChildren;
3050
+ if (node.children) {
3051
+ for (let i = 0; i < node.children.length; i++) {
3052
+ const ret = walkAndMaterialiseLoops(node.children[i], duration);
3053
+ if (ret !== null) {
3054
+ if (!newChildren) newChildren = node.children.slice();
3055
+ newChildren[i] = ret;
3056
+ }
3057
+ }
3058
+ }
3059
+ let newAnimate;
3060
+ const animBucket = node.animate;
3061
+ if (animBucket && typeof animBucket === "object" && !Array.isArray(animBucket)) {
3062
+ const animDef = animBucket;
3063
+ for (const propName of Object.keys(animDef)) {
3064
+ const propAnim = animDef[propName];
3065
+ const materialised = materialiseInternalLoopsInPropAnim(propName, propAnim, duration);
3066
+ if (materialised !== propAnim) {
3067
+ if (!newAnimate) newAnimate = __spreadValues({}, animDef);
3068
+ newAnimate[propName] = materialised;
3069
+ }
3070
+ }
3071
+ }
3072
+ if (!newChildren && !newAnimate) return null;
3073
+ const cloned = __spreadValues({}, node);
3074
+ if (newChildren) cloned.children = newChildren;
3075
+ if (newAnimate) cloned.animate = newAnimate;
3076
+ return cloned;
3077
+ }
1202
3078
  var _elementIdCounter = 0;
1203
3079
  function generateElementId() {
1204
3080
  return "_px_el_" + ++_elementIdCounter;
1205
3081
  }
1206
- function normalizeAnimationDefinition(animDef, duration, defs) {
3082
+ function normalizeAnimationDefinition(animDef, duration, defs, engine = PxAnimatorEngine.webapi) {
1207
3083
  const normalized = {};
1208
3084
  for (const [propName, propAnim] of Object.entries(animDef)) {
1209
3085
  const normalizedKfs = normalizeKeyframes(propName, propAnim, duration, defs);
1210
3086
  if (normalizedKfs.length > 0) {
1211
- normalized[propName] = { kfs: normalizedKfs };
3087
+ const out = { kfs: normalizedKfs };
3088
+ if (propAnim.autoOrient !== void 0) out.autoOrient = propAnim.autoOrient;
3089
+ if (propAnim.loop !== void 0) out.loop = propAnim.loop;
3090
+ normalized[propName] = engine === PxAnimatorEngine.webapi && propName === "transform" ? materialiseMotionPathInPropAnim(out) : out;
1212
3091
  }
1213
3092
  }
1214
3093
  return normalized;
1215
3094
  }
1216
- function getNormalisedBindings(doc) {
3095
+ function getNormalisedBindings(doc, engine = PxAnimatorEngine.webapi) {
1217
3096
  const animatorConfig = getAnimatorConfig(doc) || {};
1218
3097
  const defs = getDefs(doc);
1219
3098
  const duration = animatorConfig.duration || 1e3;
@@ -1223,7 +3102,7 @@ function getNormalisedBindings(doc) {
1223
3102
  const animDefs = resolveElementAnimation(animate, defs);
1224
3103
  if (animDefs.length === 0) return null;
1225
3104
  const merged = mergeAnimationDefinitions(animDefs);
1226
- const normalizedAnim = normalizeAnimationDefinition(merged, duration, defs);
3105
+ const normalizedAnim = normalizeAnimationDefinition(merged, duration, defs, engine);
1227
3106
  if (Object.keys(normalizedAnim).length === 0) return null;
1228
3107
  return {
1229
3108
  id,
@@ -1238,10 +3117,11 @@ function getNormalisedBindings(doc) {
1238
3117
  }
1239
3118
  }
1240
3119
  const processNode = (node) => {
1241
- if (node.animate) {
3120
+ const inlineAnim = node.animate;
3121
+ if (inlineAnim && Object.keys(inlineAnim).length > 0) {
1242
3122
  const nodeId = node.id || generateElementId();
1243
3123
  node.id = nodeId;
1244
- const normalized = processAnimation(nodeId, node.animate);
3124
+ const normalized = processAnimation(nodeId, inlineAnim);
1245
3125
  if (normalized) bindings.push(normalized);
1246
3126
  }
1247
3127
  if (node.children) {
@@ -1249,115 +3129,870 @@ function getNormalisedBindings(doc) {
1249
3129
  processNode(node.children[i]);
1250
3130
  }
1251
3131
  }
1252
- };
1253
- if (doc.children) {
1254
- for (let i = 0; i < doc.children.length; i++) {
1255
- processNode(doc.children[i]);
1256
- }
3132
+ };
3133
+ if (doc.children) {
3134
+ for (let i = 0; i < doc.children.length; i++) {
3135
+ processNode(doc.children[i]);
3136
+ }
3137
+ }
3138
+ return bindings;
3139
+ }
3140
+ function getKeyframesPair(keyframes, progress) {
3141
+ var _a, _b;
3142
+ let prevKf = keyframes[0];
3143
+ let nextKf = keyframes[keyframes.length - 1];
3144
+ for (let j = 0; j < keyframes.length - 1; j++) {
3145
+ const aOff = (_a = keyframes[j].t) != null ? _a : 0;
3146
+ const bOff = (_b = keyframes[j + 1].t) != null ? _b : 0;
3147
+ if (aOff <= progress && progress <= bOff) {
3148
+ prevKf = keyframes[j];
3149
+ nextKf = keyframes[j + 1];
3150
+ break;
3151
+ }
3152
+ }
3153
+ return { prevKf, nextKf };
3154
+ }
3155
+ function calcPropertyValue(propName, propAnim, progress) {
3156
+ var _a, _b, _c, _d, _e, _f, _g;
3157
+ const keyframes = propAnim.kfs || propAnim.keyframes || [];
3158
+ if (keyframes.length === 0) return null;
3159
+ const { prevKf, nextKf } = getKeyframesPair(keyframes, progress);
3160
+ let localProgress = prevKf === nextKf ? 0 : remap(progress, (_a = prevKf.t) != null ? _a : 0, (_b = nextKf.t) != null ? _b : 0, 0, 1);
3161
+ localProgress = clamp(localProgress, 0, 1);
3162
+ const easing = (_c = prevKf.e) != null ? _c : prevKf.easing;
3163
+ if (easing && Array.isArray(easing)) {
3164
+ try {
3165
+ localProgress = cubicBezier(easing)(localProgress);
3166
+ } catch (e) {
3167
+ }
3168
+ }
3169
+ let cssAttrName = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;
3170
+ let cssValue = null;
3171
+ const prevV = (_d = prevKf == null ? void 0 : prevKf.v) != null ? _d : prevKf == null ? void 0 : prevKf.value;
3172
+ const nextV = (_e = nextKf == null ? void 0 : nextKf.v) != null ? _e : nextKf == null ? void 0 : nextKf.value;
3173
+ if (cssAttrName === "d") {
3174
+ const prevPaths = (_f = prevV == null ? void 0 : prevV.paths) != null ? _f : Array.isArray(prevV) ? prevV : [];
3175
+ const nextPaths = (_g = nextV == null ? void 0 : nextV.paths) != null ? _g : Array.isArray(nextV) ? nextV : [];
3176
+ cssValue = interpolateBeziers(
3177
+ prevPaths,
3178
+ nextPaths,
3179
+ localProgress
3180
+ ).map((bz) => bezierToSvgPath(bz)).join("");
3181
+ } else if (COLOUR_ATTR_NAMES.has(cssAttrName)) {
3182
+ cssValue = toRGBA(interpolateColor(
3183
+ prevV || [0, 0, 0, 1],
3184
+ nextV || [0, 0, 0, 1],
3185
+ localProgress
3186
+ ));
3187
+ cssAttrName = propName;
3188
+ } else if (cssAttrName === "stroke-dasharray") {
3189
+ cssValue = interpolateVec(
3190
+ prevV || [],
3191
+ nextV || [],
3192
+ localProgress
3193
+ ).join(" ");
3194
+ cssAttrName = propName;
3195
+ } else if (cssAttrName === "transform" && prevV !== null && typeof prevV === "object" && !Array.isArray(prevV)) {
3196
+ const partKeys = /* @__PURE__ */ new Set([
3197
+ ...prevV ? Object.keys(prevV) : [],
3198
+ ...nextV ? Object.keys(nextV) : []
3199
+ ]);
3200
+ const partsResult = {};
3201
+ for (const partKey of partKeys) {
3202
+ const prevPart = prevV == null ? void 0 : prevV[partKey];
3203
+ const nextPart = nextV == null ? void 0 : nextV[partKey];
3204
+ if (partKey === "rotate") {
3205
+ partsResult.rotate = interpolateNum(+(prevPart != null ? prevPart : 0), +(nextPart != null ? nextPart : 0), localProgress);
3206
+ } else if (partKey === "translate" || partKey === "scale" || partKey === "origin") {
3207
+ const fallback = partKey === "scale" ? [1, 1] : [0, 0];
3208
+ const interp = interpolateVec(prevPart || fallback, nextPart || fallback, localProgress);
3209
+ partsResult[partKey] = interp;
3210
+ }
3211
+ }
3212
+ if (propAnimIsMotionPath(propAnim)) {
3213
+ const prevTr = prevV.translate;
3214
+ const nextTr = nextV.translate;
3215
+ if (Array.isArray(prevTr) && Array.isArray(nextTr)) {
3216
+ const sample = evaluateMotionPathSegment(
3217
+ prevKf,
3218
+ nextKf,
3219
+ [+prevTr[0], +prevTr[1]],
3220
+ [+nextTr[0], +nextTr[1]],
3221
+ localProgress,
3222
+ !!propAnim.autoOrient
3223
+ );
3224
+ partsResult.translate = [sample.translate[0], sample.translate[1]];
3225
+ if (sample.rotateDeg !== void 0) partsResult.rotate = sample.rotateDeg;
3226
+ }
3227
+ }
3228
+ cssValue = composeTransformParts(partsResult, { withUnits: false });
3229
+ cssAttrName = "transform";
3230
+ } else if (cssAttrName === "translate") {
3231
+ const v = interpolateVec(
3232
+ prevV || [0, 0],
3233
+ nextV || [0, 0],
3234
+ localProgress
3235
+ );
3236
+ cssValue = "translate(" + v.join(",") + ")";
3237
+ cssAttrName = "transform";
3238
+ } else if (cssAttrName === "rotate") {
3239
+ const v = interpolateNum(
3240
+ +(prevV || 0),
3241
+ +(nextV || 0),
3242
+ localProgress
3243
+ );
3244
+ cssValue = "rotate(" + v + ")";
3245
+ cssAttrName = "transform";
3246
+ } else if (cssAttrName === "scale") {
3247
+ const v = interpolateVec(
3248
+ prevV || [1, 1],
3249
+ nextV || [1, 1],
3250
+ localProgress
3251
+ );
3252
+ cssValue = "scale(" + v.join(",") + ")";
3253
+ cssAttrName = "transform";
3254
+ } else {
3255
+ const num2 = interpolateNum(
3256
+ +(prevV || 0),
3257
+ +(nextV || 0),
3258
+ localProgress
3259
+ );
3260
+ cssValue = num2;
3261
+ }
3262
+ if (PCT_BASED_ATTR_NAMES.has(cssAttrName) && typeof cssValue === "number") {
3263
+ cssValue = cssValue * 100 + "%";
3264
+ }
3265
+ return { k: cssAttrName, v: cssValue === null ? "" : "" + cssValue };
3266
+ }
3267
+ function calcAnimationValues(animDef, progress) {
3268
+ const result = {};
3269
+ for (const [propName, propAnim] of Object.entries(animDef)) {
3270
+ const computed = calcPropertyValue(propName, propAnim, progress);
3271
+ if (computed) {
3272
+ result[computed.k] = computed.v;
3273
+ }
3274
+ }
3275
+ return result;
3276
+ }
3277
+
3278
+ // src/effects/trimPathEffect.ts
3279
+ function applyTrimPathEffect(node, trimPath, isCombinedShape, ctx) {
3280
+ if (!trimPath) return node;
3281
+ const trimAllAsOne = !!trimPath.trimAllAsOne;
3282
+ const leafEntries = [];
3283
+ let acc = 0;
3284
+ const measure = (n) => {
3285
+ if (Array.isArray(n.children) && n.children.length > 0) {
3286
+ for (const ch of n.children) measure(ch);
3287
+ return;
3288
+ }
3289
+ const d = typeof n.d === "string" ? n.d : rectToPathD(n);
3290
+ if (d === void 0) return;
3291
+ const subpaths = parseSvgPathToBezier(d);
3292
+ if (!subpaths.length) return;
3293
+ const entry = { leaf: n, subpaths: [] };
3294
+ for (const sp of subpaths) {
3295
+ if (!trimAllAsOne) acc = 0;
3296
+ const lengthPx = pxBezierPathLength(sp);
3297
+ entry.subpaths.push({ subpath: sp, lengthPx, startOffsetPx: acc });
3298
+ acc += lengthPx;
3299
+ }
3300
+ leafEntries.push(entry);
3301
+ };
3302
+ measure(node);
3303
+ if (!leafEntries.length) return node;
3304
+ const chainLengthPx = acc;
3305
+ if (trimAllAsOne && chainLengthPx < 1e-3) return node;
3306
+ const offsetReadRaw = readAnimatable(trimPath.offset);
3307
+ const offsetRead = offsetReadRaw.kind === "absent" /* Absent */ ? { kind: "static" /* Static */, value: 0 } : offsetReadRaw;
3308
+ const rangeReadRaw = readRangeWithCrossings(trimPath.range);
3309
+ const rangeRead = rangeReadRaw.kind === "absent" /* Absent */ ? { kind: "static" /* Static */, value: [0, 1] } : rangeReadRaw;
3310
+ const offsetValues = readScalarValues(offsetRead);
3311
+ const minOffset = offsetValues.length ? Math.min(...offsetValues) : 0;
3312
+ const maxOffset = offsetValues.length ? Math.max(...offsetValues) : 0;
3313
+ const minMaxOffset = [minOffset, maxOffset];
3314
+ if (leafEntries.length === 1 && leafEntries[0].leaf === node && leafEntries[0].subpaths.length === 1) {
3315
+ const entry = leafEntries[0];
3316
+ const sp = entry.subpaths[0];
3317
+ const pathLengthPx = trimAllAsOne ? chainLengthPx : sp.lengthPx;
3318
+ if (pathLengthPx < 1e-3) return node;
3319
+ const startOffsetPct = trimAllAsOne ? sp.startOffsetPx / pathLengthPx : 0;
3320
+ return collapseLeafWithTrim(entry.leaf, pathLengthPx, startOffsetPct, minMaxOffset, offsetRead, rangeRead);
3321
+ }
3322
+ const replacements = /* @__PURE__ */ new Map();
3323
+ for (const entry of leafEntries) {
3324
+ const newChildren = [];
3325
+ for (const sp of entry.subpaths) {
3326
+ const pathLengthPx = trimAllAsOne ? chainLengthPx : sp.lengthPx;
3327
+ if (pathLengthPx < 1e-3) continue;
3328
+ const startOffsetPct = trimAllAsOne ? sp.startOffsetPx / pathLengthPx : 0;
3329
+ newChildren.push(...buildSubpathNodes(entry.leaf, sp.subpath, pathLengthPx, startOffsetPct, minMaxOffset, offsetRead, rangeRead, ctx));
3330
+ }
3331
+ replacements.set(entry.leaf, wrapLeafAsGroup(entry.leaf, newChildren));
3332
+ }
3333
+ const swap = (n) => {
3334
+ const r = replacements.get(n);
3335
+ if (r) return r;
3336
+ if (Array.isArray(n.children) && n.children.length > 0) {
3337
+ return __spreadProps(__spreadValues({}, n), { children: n.children.map(swap) });
3338
+ }
3339
+ return n;
3340
+ };
3341
+ return swap(node);
3342
+ }
3343
+ function wrapLeafAsGroup(leaf, children) {
3344
+ const wrapper = __spreadProps(__spreadValues({}, leaf), { type: "g", children });
3345
+ delete wrapper.d;
3346
+ delete wrapper.strokeDasharray;
3347
+ delete wrapper.strokeDashoffset;
3348
+ delete wrapper.effects;
3349
+ return wrapper;
3350
+ }
3351
+ function buildSubpathNodes(leaf, subpath, pathLengthPx, startOffsetPct, minMaxOffset, offsetRead, rangeRead, ctx) {
3352
+ const offsetToDashOffset = makeOffsetToDashOffset(startOffsetPct, pathLengthPx, minMaxOffset);
3353
+ const rangeToDasharray = makeRangeToDasharray(pathLengthPx, minMaxOffset);
3354
+ const dashOffsetAttr = computeAnimAttr(offsetRead, offsetToDashOffset);
3355
+ const dashArrayAttr = computeAnimAttr(rangeRead, rangeToDasharray);
3356
+ const strokeOpacityAttr = computeOpacityFromRange(rangeRead);
3357
+ const dStr = bezierToSvgPath(subpath);
3358
+ const base = makeBareSubpath(dStr);
3359
+ applyAttr(base, "strokeDasharray", dashArrayAttr);
3360
+ applyAttr(base, "strokeDashoffset", dashOffsetAttr);
3361
+ applyAttr(base, "strokeOpacity", strokeOpacityAttr);
3362
+ return [base];
3363
+ }
3364
+ function collapseLeafWithTrim(leaf, pathLengthPx, startOffsetPct, minMaxOffset, offsetRead, rangeRead) {
3365
+ const offsetToDashOffset = makeOffsetToDashOffset(startOffsetPct, pathLengthPx, minMaxOffset);
3366
+ const rangeToDasharray = makeRangeToDasharray(pathLengthPx, minMaxOffset);
3367
+ const node = __spreadValues({}, leaf);
3368
+ delete node.effects;
3369
+ applyAttr(node, "strokeDasharray", computeAnimAttr(rangeRead, rangeToDasharray));
3370
+ applyAttr(node, "strokeDashoffset", computeAnimAttr(offsetRead, offsetToDashOffset));
3371
+ applyAttr(node, "strokeOpacity", computeOpacityFromRange(rangeRead));
3372
+ return node;
3373
+ }
3374
+ function makeBareSubpath(dStr) {
3375
+ return { type: "path", d: dStr };
3376
+ }
3377
+ var SMALL_PADDING_PX = 1;
3378
+ function makeOffsetToDashOffset(startOffsetPct, pathLengthPx, minMaxOffset) {
3379
+ const [minIdx] = getOffsetIndexRange(minMaxOffset);
3380
+ return (offsetVal) => pathLengthPx * (-offsetVal - minIdx + startOffsetPct) + SMALL_PADDING_PX;
3381
+ }
3382
+ function makeRangeToDasharray(pathLengthPx, minMaxOffset) {
3383
+ const [minIdx, maxIdx] = getOffsetIndexRange(minMaxOffset);
3384
+ const repeats = maxIdx - minIdx + 1;
3385
+ return (rangeVal) => {
3386
+ const a = clamp(rangeVal[0], 0, 1);
3387
+ const b = clamp(rangeVal[1], 0, 1);
3388
+ const minR = Math.min(a, b);
3389
+ const maxR = Math.max(a, b);
3390
+ const out = [0];
3391
+ let gap = SMALL_PADDING_PX;
3392
+ for (let i = 0; i < repeats; i++) {
3393
+ out.push(gap + minR * pathLengthPx);
3394
+ out.push((maxR - minR) * pathLengthPx);
3395
+ gap = (1 - maxR) * pathLengthPx;
3396
+ }
3397
+ out.push(gap + SMALL_PADDING_PX);
3398
+ return out;
3399
+ };
3400
+ }
3401
+ function getOffsetIndexRange(minMaxOffset) {
3402
+ return [
3403
+ Math.floor(Math.min(-minMaxOffset[0], -minMaxOffset[1])),
3404
+ Math.ceil(Math.max(-minMaxOffset[0], -minMaxOffset[1]))
3405
+ ];
3406
+ }
3407
+ function readScalarValues(r) {
3408
+ var _a;
3409
+ if (r.kind === "absent" /* Absent */) return [];
3410
+ if (r.kind === "static" /* Static */) return [r.value];
3411
+ const out = [];
3412
+ for (const kf of r.keyframes) {
3413
+ const v = (_a = kf.value) != null ? _a : kf.v;
3414
+ if (typeof v === "number") out.push(v);
3415
+ }
3416
+ return out;
3417
+ }
3418
+ function computeAnimAttr(read, map) {
3419
+ if (read.kind === "absent" /* Absent */) return void 0;
3420
+ if (read.kind === "static" /* Static */) return { kind: "static" /* Static */, value: map(read.value) };
3421
+ return {
3422
+ kind: "animated" /* Animated */,
3423
+ keyframes: read.keyframes.map((kf) => {
3424
+ var _a, _b, _c, _d;
3425
+ return {
3426
+ time: (_b = (_a = kf.time) != null ? _a : kf.t) != null ? _b : 0,
3427
+ value: map((_c = kf.value) != null ? _c : kf.v),
3428
+ easing: (_d = kf.easing) != null ? _d : kf.e
3429
+ };
3430
+ })
3431
+ };
3432
+ }
3433
+ function applyAttr(node, attrName, attr) {
3434
+ if (!attr) return;
3435
+ if (attr.kind === "static" /* Static */) {
3436
+ node[attrName] = attr.value;
3437
+ return;
3438
+ }
3439
+ const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
3440
+ const animate = __spreadValues({}, prevAnimate || {});
3441
+ animate[attrName] = { keyframes: attr.keyframes };
3442
+ node.animate = animate;
3443
+ }
3444
+ function computeOpacityFromRange(rangeRead) {
3445
+ var _a, _b, _c, _d, _e, _f;
3446
+ const hide = (v) => v[0] === v[1];
3447
+ if (rangeRead.kind === "absent" /* Absent */) return void 0;
3448
+ if (rangeRead.kind === "static" /* Static */) return hide(rangeRead.value) ? { kind: "static" /* Static */, value: 0 } : void 0;
3449
+ const kfs = rangeRead.keyframes;
3450
+ let anyHide = false;
3451
+ let allHide = true;
3452
+ for (const kf of kfs) {
3453
+ if (hide((_a = kf.value) != null ? _a : kf.v)) anyHide = true;
3454
+ else allHide = false;
3455
+ }
3456
+ if (!anyHide) return void 0;
3457
+ if (allHide) return { kind: "static" /* Static */, value: 0 };
3458
+ const out = [];
3459
+ for (let i = 0; i < kfs.length; i++) {
3460
+ const kf = kfs[i];
3461
+ const prevKf = i > 0 ? kfs[i - 1] : void 0;
3462
+ const nextKf = i < kfs.length - 1 ? kfs[i + 1] : void 0;
3463
+ const t = (_c = (_b = kf.time) != null ? _b : kf.t) != null ? _c : 0;
3464
+ const thisHide = hide((_d = kf.value) != null ? _d : kf.v);
3465
+ const prevHide = prevKf ? thisHide && hide((_e = prevKf.value) != null ? _e : prevKf.v) : thisHide;
3466
+ const nextHide = nextKf ? thisHide && hide((_f = nextKf.value) != null ? _f : nextKf.v) : thisHide;
3467
+ if (prevHide && !nextHide) {
3468
+ out.push({ time: t, value: 0 });
3469
+ out.push({ time: t + 1, value: 1 });
3470
+ } else if (!prevHide && nextHide) {
3471
+ out.push({ time: t - 1, value: 1 });
3472
+ out.push({ time: t, value: 0 });
3473
+ }
3474
+ }
3475
+ if (out.length <= 1) return void 0;
3476
+ return { kind: "animated" /* Animated */, keyframes: out };
3477
+ }
3478
+ function readRangeWithCrossings(raw) {
3479
+ const r = readAnimatable(raw);
3480
+ if (r.kind !== "animated" /* Animated */) return r;
3481
+ const kfs = r.keyframes.map((kf) => {
3482
+ var _a, _b, _c, _d;
3483
+ return {
3484
+ time: (_b = (_a = kf.time) != null ? _a : kf.t) != null ? _b : 0,
3485
+ value: (_c = kf.value) != null ? _c : kf.v,
3486
+ easing: (_d = kf.easing) != null ? _d : kf.e
3487
+ };
3488
+ });
3489
+ const hasReverse = kfs.some((kf) => kf.value[0] > kf.value[1]);
3490
+ if (!hasReverse) {
3491
+ return {
3492
+ kind: "animated" /* Animated */,
3493
+ keyframes: kfs.map((kf) => ({ time: kf.time, value: kf.value, easing: kf.easing }))
3494
+ };
3495
+ }
3496
+ const crossingTimes = [];
3497
+ for (let i = 1; i < kfs.length; i++) {
3498
+ const prev = kfs[i - 1];
3499
+ const cur = kfs[i];
3500
+ const dPrev = prev.value[1] - prev.value[0];
3501
+ const dCur = cur.value[1] - cur.value[0];
3502
+ if (dPrev * dCur < 0) {
3503
+ const t = bisectionForRangeCrossing(prev, cur);
3504
+ if (t !== null && t > prev.time && t < cur.time) {
3505
+ crossingTimes.push(Math.round(t));
3506
+ }
3507
+ }
3508
+ }
3509
+ const uniqueTs = Array.from(new Set(crossingTimes)).sort((a, b) => a - b);
3510
+ const out = [];
3511
+ let j = 0;
3512
+ for (const kf of kfs) {
3513
+ while (j < uniqueTs.length && uniqueTs[j] < kf.time) {
3514
+ const t = uniqueTs[j++];
3515
+ const v2 = interpolateRangeAt(kfs, t);
3516
+ const m = (v2[0] + v2[1]) / 2;
3517
+ out.push({ time: t, value: [m, m] });
3518
+ }
3519
+ const v = kf.value[0] > kf.value[1] ? [kf.value[1], kf.value[0]] : kf.value;
3520
+ out.push({ time: kf.time, value: v, easing: kf.easing });
3521
+ }
3522
+ while (j < uniqueTs.length) {
3523
+ const t = uniqueTs[j++];
3524
+ const v = interpolateRangeAt(kfs, t);
3525
+ const m = (v[0] + v[1]) / 2;
3526
+ out.push({ time: t, value: [m, m] });
3527
+ }
3528
+ return { kind: "animated" /* Animated */, keyframes: out };
3529
+ }
3530
+ function bisectionForRangeCrossing(prev, cur) {
3531
+ const f = (t) => {
3532
+ const a = (t - prev.time) / (cur.time - prev.time);
3533
+ const v0 = prev.value[0] + (cur.value[0] - prev.value[0]) * a;
3534
+ const v1 = prev.value[1] + (cur.value[1] - prev.value[1]) * a;
3535
+ return v1 - v0;
3536
+ };
3537
+ let lo = prev.time, hi = cur.time;
3538
+ let fLo = f(lo);
3539
+ if (fLo === 0) return lo;
3540
+ const fHi = f(hi);
3541
+ if (fHi === 0) return hi;
3542
+ if (fLo * fHi > 0) return null;
3543
+ for (let i = 0; i < 100; i++) {
3544
+ const mid = (lo + hi) / 2;
3545
+ const fMid = f(mid);
3546
+ if (fMid === 0 || Math.abs(hi - lo) < 1e-4) return mid;
3547
+ if (fLo * fMid < 0) {
3548
+ hi = mid;
3549
+ } else {
3550
+ lo = mid;
3551
+ fLo = fMid;
3552
+ }
3553
+ }
3554
+ return (lo + hi) / 2;
3555
+ }
3556
+ function interpolateRangeAt(kfs, t) {
3557
+ if (t <= kfs[0].time) return kfs[0].value;
3558
+ if (t >= kfs[kfs.length - 1].time) return kfs[kfs.length - 1].value;
3559
+ for (let i = 1; i < kfs.length; i++) {
3560
+ if (t <= kfs[i].time) {
3561
+ const prev = kfs[i - 1];
3562
+ const cur = kfs[i];
3563
+ const a = (t - prev.time) / (cur.time - prev.time);
3564
+ return [
3565
+ prev.value[0] + (cur.value[0] - prev.value[0]) * a,
3566
+ prev.value[1] + (cur.value[1] - prev.value[1]) * a
3567
+ ];
3568
+ }
3569
+ }
3570
+ return kfs[kfs.length - 1].value;
3571
+ }
3572
+ function pxBezierPathLength(path) {
3573
+ const v = path.v;
3574
+ if (!v || v.length < 2) return 0;
3575
+ let total = 0;
3576
+ for (let i = 0; i < v.length - 1; i++) {
3577
+ total += segmentLength(path, i, i + 1);
3578
+ }
3579
+ if (path.c && v.length > 1) {
3580
+ total += segmentLength(path, v.length - 1, 0);
3581
+ }
3582
+ return total;
3583
+ }
3584
+ function segmentLength(path, from, to) {
3585
+ var _a, _b, _c, _d;
3586
+ const v = path.v;
3587
+ const p0 = v[from];
3588
+ const p3 = v[to];
3589
+ const p1 = (_b = (_a = path.o) == null ? void 0 : _a[from]) != null ? _b : p0;
3590
+ const p2 = (_d = (_c = path.i) == null ? void 0 : _c[to]) != null ? _d : p3;
3591
+ const lut = bezier2D_arcLengthLUT(p0, p1, p2, p3);
3592
+ return lut.ds[lut.ds.length - 1];
3593
+ }
3594
+ function rectToPathD(node) {
3595
+ var _a, _b, _c, _d;
3596
+ if (node.type !== "rect") return void 0;
3597
+ const x = Number((_a = node.x) != null ? _a : 0), y = Number((_b = node.y) != null ? _b : 0);
3598
+ const w = Number((_c = node.width) != null ? _c : 0), h = Number((_d = node.height) != null ? _d : 0);
3599
+ return "M" + (x + w) + "," + y + "L" + (x + w) + "," + (y + h) + "L" + x + "," + (y + h) + "L" + x + "," + y + "L" + (x + w) + "," + y + "z";
3600
+ }
3601
+
3602
+ // src/effects/PlayerEffectsUtil.ts
3603
+ function applyPlayerEffects(root) {
3604
+ const ctx = {
3605
+ defs: [],
3606
+ warnings: [],
3607
+ errors: [],
3608
+ idMap: /* @__PURE__ */ new Map(),
3609
+ nextId: 0,
3610
+ contentRefInnerIds: /* @__PURE__ */ new Map(),
3611
+ maskAncestorChains: /* @__PURE__ */ new Map()
3612
+ };
3613
+ const working = clone(root);
3614
+ indexById(working, ctx.idMap);
3615
+ identifyContentRefTargets(working, ctx, () => genId(ctx, "inner"));
3616
+ collectMaskAncestorChains(working, ctx);
3617
+ const afterPass1 = applyPlayerEffects_exceptRetime(working, ctx);
3618
+ const out = applyPlayerEffects_retime(afterPass1, ctx);
3619
+ spliceDefs(out, ctx.defs);
3620
+ return { root: out, defs: ctx.defs, warnings: ctx.warnings, errors: ctx.errors };
3621
+ }
3622
+ function applyPlayerEffects_exceptRetime(node, ctx) {
3623
+ if (node.children) node.children = node.children.map((child) => applyPlayerEffects_exceptRetime(child, ctx));
3624
+ const fx = node.effects;
3625
+ const originalId = typeof node.id === "string" ? node.id : void 0;
3626
+ const innerIdForContentRef = originalId ? ctx.contentRefInnerIds.get(originalId) : void 0;
3627
+ if (!fx && !innerIdForContentRef) return node;
3628
+ const { transformation, repeater, maskedBy, trimPath, retime, ref, fillGradient, strokeGradient, textAlongPath } = fx != null ? fx : {};
3629
+ const isCombinedShape = fx == null ? void 0 : fx.isCombinedShape;
3630
+ if (fx) delete node.effects;
3631
+ let n = node;
3632
+ n = applyTextAlongPathEffect(n, textAlongPath, ctx);
3633
+ n = applyFillGradientEffect(n, fillGradient, ctx);
3634
+ n = applyStrokeGradientEffect(n, strokeGradient, ctx);
3635
+ n = applyTrimPathEffect(n, trimPath, isCombinedShape, ctx);
3636
+ n = applyRepeaterEffect(n, repeater, ctx);
3637
+ n = applyMaskedByEffect(n, maskedBy, transformation, ctx);
3638
+ if (innerIdForContentRef) {
3639
+ n = splitForContentRef(n, transformation, originalId, innerIdForContentRef, ctx);
3640
+ } else {
3641
+ n = applyRefAndTransformationEffect(n, ref, transformation, ctx);
3642
+ }
3643
+ if (retime) node.effects = { retime };
3644
+ if (originalId) ctx.idMap.set(originalId, n);
3645
+ return n;
3646
+ }
3647
+ function applyPlayerEffects_retime(node, ctx) {
3648
+ applyAllRetimeEffects(node, ctx);
3649
+ return node;
3650
+ }
3651
+
3652
+ // src/PxAnimatorUseMaterialiser.ts
3653
+ function materialiseAnimatedUseInstances(root) {
3654
+ var _a;
3655
+ const idMap = buildIdMap(root);
3656
+ const animatedIds = computeAnimatedSubtreeIds(root, idMap);
3657
+ if (animatedIds.size === 0) return root;
3658
+ let idCounter = 0;
3659
+ const genId3 = () => "_lw_use_mat_" + ++idCounter;
3660
+ const defsCollector = [];
3661
+ const walked = walkAndMaterialise2(root, idMap, animatedIds, genId3, defsCollector);
3662
+ if (defsCollector.length === 0) return walked;
3663
+ const defsNode = { type: "defs", children: defsCollector };
3664
+ const newChildren = [...(_a = walked.children) != null ? _a : [], defsNode];
3665
+ return __spreadProps(__spreadValues({}, walked), { children: newChildren });
3666
+ }
3667
+ function buildIdMap(root) {
3668
+ const map = /* @__PURE__ */ new Map();
3669
+ const visit = (n) => {
3670
+ var _a;
3671
+ if (typeof n.id === "string") map.set(n.id, n);
3672
+ (_a = n.children) == null ? void 0 : _a.forEach(visit);
3673
+ };
3674
+ visit(root);
3675
+ return map;
3676
+ }
3677
+ function computeAnimatedSubtreeIds(root, idMap) {
3678
+ const cache = /* @__PURE__ */ new WeakMap();
3679
+ const result = /* @__PURE__ */ new Set();
3680
+ const hasAnim = (n, visiting) => {
3681
+ const cached = cache.get(n);
3682
+ if (cached !== void 0) return cached;
3683
+ if (visiting.has(n)) return false;
3684
+ visiting.add(n);
3685
+ let r = false;
3686
+ if (n.animate && typeof n.animate === "object" && !Array.isArray(n.animate)) {
3687
+ for (const _ in n.animate) {
3688
+ r = true;
3689
+ break;
3690
+ }
3691
+ }
3692
+ if (!r && n.children) {
3693
+ for (const ch of n.children) {
3694
+ if (hasAnim(ch, visiting)) {
3695
+ r = true;
3696
+ break;
3697
+ }
3698
+ }
3699
+ }
3700
+ if (!r && n.type === "use" && typeof n.href === "string") {
3701
+ const targetId = stripHash2(n.href);
3702
+ const target = targetId ? idMap.get(targetId) : void 0;
3703
+ if (target) r = hasAnim(target, visiting);
3704
+ }
3705
+ visiting.delete(n);
3706
+ cache.set(n, r);
3707
+ return r;
3708
+ };
3709
+ for (const [id, node] of idMap) {
3710
+ if (hasAnim(node, /* @__PURE__ */ new Set())) result.add(id);
3711
+ }
3712
+ return result;
3713
+ }
3714
+ function stripHash2(href) {
3715
+ if (typeof href !== "string") return void 0;
3716
+ return href.startsWith("#") ? href.slice(1) : href;
3717
+ }
3718
+ function materialiseOneUse(useNode, target, idMap, animatedIds, genId3, defsCollector) {
3719
+ const clone2 = deepClonePxNode(target);
3720
+ regenerateIdsAndRewriteRefs(clone2, genId3);
3721
+ const rewrittenClone = clone2.type === "symbol" ? rewriteSymbolRootToGroup(clone2, genId3, defsCollector) : clone2;
3722
+ const materialisedClone = walkAndMaterialise2(rewrittenClone, idMap, animatedIds, genId3, defsCollector);
3723
+ const newNode = __spreadProps(__spreadValues({}, useNode), { type: "g", children: [materialisedClone] });
3724
+ delete newNode.href;
3725
+ return applyUseOffsetToG(newNode);
3726
+ }
3727
+ function rewriteSymbolRootToGroup(symbolNode, genId3, defsCollector) {
3728
+ const viewBox = parseViewBox(symbolNode.viewBox);
3729
+ const g = __spreadProps(__spreadValues({}, symbolNode), { type: "g" });
3730
+ delete g.viewBox;
3731
+ delete g.preserveAspectRatio;
3732
+ delete g.width;
3733
+ delete g.height;
3734
+ if (!viewBox) return g;
3735
+ const [vbX, vbY, vbW, vbH] = viewBox;
3736
+ if (vbX !== 0 || vbY !== 0) {
3737
+ g.transform = "translate(" + -vbX + "," + -vbY + ")";
3738
+ }
3739
+ const clipId = genId3();
3740
+ defsCollector.push({
3741
+ type: "clipPath",
3742
+ id: clipId,
3743
+ children: [{ type: "rect", x: vbX, y: vbY, width: vbW, height: vbH }]
3744
+ });
3745
+ g.clipPath = "url(#" + clipId + ")";
3746
+ return g;
3747
+ }
3748
+ function parseViewBox(v) {
3749
+ if (typeof v !== "string") return void 0;
3750
+ const parts = v.trim().split(/[\s,]+/).map(Number);
3751
+ if (parts.length < 4 || parts.some((n) => !Number.isFinite(n))) return void 0;
3752
+ return [parts[0], parts[1], parts[2], parts[3]];
3753
+ }
3754
+ function walkAndMaterialise2(node, idMap, animatedIds, genId3, defsCollector) {
3755
+ if (node.type === "use" && typeof node.href === "string") {
3756
+ const targetId = stripHash2(node.href);
3757
+ if (targetId && animatedIds.has(targetId)) {
3758
+ const target = idMap.get(targetId);
3759
+ if (target) return materialiseOneUse(node, target, idMap, animatedIds, genId3, defsCollector);
3760
+ }
3761
+ }
3762
+ if (!node.children) return node;
3763
+ let changed = false;
3764
+ const newChildren = node.children.map((ch) => {
3765
+ const m = walkAndMaterialise2(ch, idMap, animatedIds, genId3, defsCollector);
3766
+ if (m !== ch) changed = true;
3767
+ return m;
3768
+ });
3769
+ return changed ? __spreadProps(__spreadValues({}, node), { children: newChildren }) : node;
3770
+ }
3771
+
3772
+ // src/PxAnimatorMaterialiseAll.ts
3773
+ function materialiseAllInTree(doc, engine, opts) {
3774
+ var _a, _b;
3775
+ let root = applyPlayerEffects(doc).root;
3776
+ const duration = (_b = (_a = getAnimatorConfig(root)) == null ? void 0 : _a.duration) != null ? _b : DEFAULT_DURATION_MS;
3777
+ root = materialiseInternalLoopsInTree(root, duration);
3778
+ if (engine === PxAnimatorEngine.webapi) {
3779
+ root = materialiseMotionPathsInTree(root, opts == null ? void 0 : opts.motionPath);
3780
+ root = materialiseAnimatedUseInstances(root);
3781
+ }
3782
+ return root;
3783
+ }
3784
+
3785
+ // src/PxAnimatorDOM.ts
3786
+ var SVG_NS = "http://www.w3.org/2000/svg";
3787
+ var DISALLOWED_SVG_TAGS_LOWER = /* @__PURE__ */ new Set([
3788
+ "script",
3789
+ "foreignobject"
3790
+ ]);
3791
+ var URL_VALUE_ATTRS_LOWER = /* @__PURE__ */ new Set([
3792
+ "href",
3793
+ // <use>, <image>
3794
+ "xlink:href",
3795
+ // legacy <use>
3796
+ "src",
3797
+ // <image>
3798
+ "filter",
3799
+ // url(#filterId)
3800
+ "clippath",
3801
+ // clip-path="url(#…)"
3802
+ "mask",
3803
+ // url(#maskId)
3804
+ "markerstart",
3805
+ // marker-start="url(#…)"
3806
+ "markermid",
3807
+ // marker-mid="url(#…)"
3808
+ "markerend"
3809
+ // marker-end="url(#…)"
3810
+ ]);
3811
+ var IMAGE_REF_ATTRS_LOWER = /* @__PURE__ */ new Set(["href", "xlink:href", "src"]);
3812
+ var DATA_RASTER_IMAGE_RE = /^data:image\/(?:png|jpe?g|gif|webp|bmp);base64,/i;
3813
+ function isDangerousAttrName(nameLower) {
3814
+ if (nameLower.startsWith("on")) return true;
3815
+ return false;
3816
+ }
3817
+ function sanitiseAttributeValue(name, value) {
3818
+ const nameLower = name.toLowerCase();
3819
+ if (isDangerousAttrName(nameLower)) {
3820
+ console.warn("Attribute blocked (event handler / dangerous): ", nameLower);
3821
+ return void 0;
3822
+ }
3823
+ if (nameLower === "fill" || nameLower === "stroke" || nameLower === "stopcolor") {
3824
+ const str = String(value);
3825
+ if (str.includes("url(") && !/^url\(#[^)]+\)$/.test(str)) {
3826
+ console.warn('Attribute "' + nameLower + '" blocked: url() must be internal url(#id), got:', value);
3827
+ return void 0;
3828
+ }
3829
+ return value;
3830
+ }
3831
+ if (URL_VALUE_ATTRS_LOWER.has(nameLower)) {
3832
+ const str = String(value);
3833
+ if (str.startsWith("#")) return value;
3834
+ if (/^url\(#[^)]+\)$/.test(str)) return value;
3835
+ if (IMAGE_REF_ATTRS_LOWER.has(nameLower) && DATA_RASTER_IMAGE_RE.test(str)) return value;
3836
+ console.warn('Attribute "' + nameLower + '" blocked: must be #id, url(#id), or base64 raster data: URI, got:', value);
3837
+ return void 0;
3838
+ }
3839
+ return value;
3840
+ }
3841
+ function createElement(tagName, normalisedProps, style, children, textContent) {
3842
+ if (DISALLOWED_SVG_TAGS_LOWER.has(tagName.toLowerCase())) {
3843
+ console.warn("SVG tag blocked (dangerous): ", tagName);
3844
+ return null;
3845
+ }
3846
+ const element = document.createElementNS(SVG_NS, tagName);
3847
+ for (const propName in normalisedProps) {
3848
+ const sanitised = sanitiseAttributeValue(propName, normalisedProps[propName]);
3849
+ if (sanitised === void 0) continue;
3850
+ element.setAttribute(camelCaseToKebabWordIfNeeded(propName), sanitised);
3851
+ }
3852
+ if (style) {
3853
+ for (const styleProp in style) {
3854
+ element.style[styleProp] = String(style[styleProp]);
3855
+ }
3856
+ }
3857
+ if (children) {
3858
+ for (const child of children) {
3859
+ element.appendChild(child);
3860
+ }
3861
+ }
3862
+ if (textContent) element.textContent = textContent;
3863
+ return element;
3864
+ }
3865
+ function resolveStyle(style, defs) {
3866
+ var _a;
3867
+ if (!style) return void 0;
3868
+ if (typeof style === "string") {
3869
+ return (_a = defs == null ? void 0 : defs.styles) == null ? void 0 : _a[style];
3870
+ }
3871
+ return style;
3872
+ }
3873
+ function getNormalizedProps(props) {
3874
+ const propsCopy = {};
3875
+ for (const rawKey of Object.keys(props)) {
3876
+ const key = kebabToCamelCaseWord(rawKey);
3877
+ if (INTERNAL_ATTRS.has(key)) continue;
3878
+ if (key === "style") continue;
3879
+ let value = props[rawKey];
3880
+ if (COLOUR_ATTR_NAMES.has(key) && Array.isArray(value)) {
3881
+ propsCopy[key] = toRGBA(value);
3882
+ } else if (key === "transform" && value !== null && typeof value === "object" && !Array.isArray(value) && value.value && typeof value.value === "object") {
3883
+ propsCopy["transform"] = composeTransformParts(value.value, { withUnits: false });
3884
+ } else if (TRANSFORM_FN_NAMES.has(key)) {
3885
+ if (Array.isArray(value)) {
3886
+ if (key === "translate") value = value.map((v) => v + "px");
3887
+ value = value.join(",");
3888
+ }
3889
+ if (key === "rotate") value = value + "deg";
3890
+ propsCopy["transform"] = key + "(" + value + ")";
3891
+ } else if (value !== void 0 && value !== null) {
3892
+ propsCopy[key] = String(value);
3893
+ }
3894
+ }
3895
+ return propsCopy;
3896
+ }
3897
+ function renderNode(node, defs) {
3898
+ if (!node) return null;
3899
+ const _a = node, { type, children, style } = _a, props = __objRest(_a, ["type", "children", "style"]);
3900
+ const nodeDefs = getDefs(node) || defs;
3901
+ const resolvedStyle = resolveStyle(style, nodeDefs);
3902
+ let childElements;
3903
+ if (children) {
3904
+ for (const ch of children) {
3905
+ const child = renderNode(ch, nodeDefs);
3906
+ if (child) {
3907
+ if (!childElements) childElements = [];
3908
+ childElements.push(child);
3909
+ }
3910
+ }
1257
3911
  }
1258
- return bindings;
3912
+ return createElement(
3913
+ type || "g",
3914
+ getNormalizedProps(props),
3915
+ resolvedStyle,
3916
+ childElements,
3917
+ props[TEXT_ATTR] || props[TEXT_CONTENT_ATTR]
3918
+ );
1259
3919
  }
1260
- function getKeyframesPair(keyframes, progress) {
1261
- var _a, _b;
1262
- let prevKf = keyframes[0];
1263
- let nextKf = keyframes[keyframes.length - 1];
1264
- for (let j = 0; j < keyframes.length - 1; j++) {
1265
- const aOff = (_a = keyframes[j].t) != null ? _a : 0;
1266
- const bOff = (_b = keyframes[j + 1].t) != null ? _b : 0;
1267
- if (aOff <= progress && progress <= bOff) {
1268
- prevKf = keyframes[j];
1269
- nextKf = keyframes[j + 1];
3920
+
3921
+ // src/PxAnimatorTriggers.ts
3922
+ function setupAnimationTriggers(api, config) {
3923
+ const { startOn, outAction = "continue", scrollIntoViewThreshold = 0.5 } = config;
3924
+ const root = api.getRootElement();
3925
+ if (!root) {
3926
+ console.warn("setupAnimationTriggers: No root element found for animation.");
3927
+ return api;
3928
+ }
3929
+ const start = () => {
3930
+ api.play();
3931
+ };
3932
+ const handleEndAction = () => {
3933
+ switch (outAction) {
3934
+ case "pause":
3935
+ api.pause();
3936
+ break;
3937
+ case "reset":
3938
+ api.cancel();
3939
+ break;
3940
+ case "reverse":
3941
+ api.play();
3942
+ break;
3943
+ case "continue":
3944
+ default:
3945
+ break;
3946
+ }
3947
+ };
3948
+ switch (startOn) {
3949
+ case "load": {
3950
+ const startHandler = () => start();
3951
+ if (document.readyState === "complete") {
3952
+ startHandler();
3953
+ } else {
3954
+ window.addEventListener("load", startHandler, { once: true });
3955
+ }
1270
3956
  break;
1271
3957
  }
1272
- }
1273
- return { prevKf, nextKf };
1274
- }
1275
- function calcPropertyValue(propName, propAnim, progress) {
1276
- var _a, _b, _c, _d, _e, _f, _g;
1277
- const keyframes = propAnim.kfs || propAnim.keyframes || [];
1278
- if (keyframes.length === 0) return null;
1279
- const { prevKf, nextKf } = getKeyframesPair(keyframes, progress);
1280
- let localProgress = prevKf === nextKf ? 0 : remap(progress, (_a = prevKf.t) != null ? _a : 0, (_b = nextKf.t) != null ? _b : 0, 0, 1);
1281
- localProgress = clamp(localProgress, 0, 1);
1282
- const easing = (_c = prevKf.e) != null ? _c : prevKf.easing;
1283
- if (easing && Array.isArray(easing)) {
1284
- try {
1285
- localProgress = cubicBezier(easing)(localProgress);
1286
- } catch (e) {
3958
+ case "mouseOver": {
3959
+ const mouseOverHandler = () => start();
3960
+ const mouseOutHandler = () => handleEndAction();
3961
+ root.addEventListener("mouseenter", mouseOverHandler);
3962
+ root.addEventListener("mouseleave", mouseOutHandler);
3963
+ break;
1287
3964
  }
1288
- }
1289
- let cssAttrName = isCamelCaseWord(propName) ? camelCaseToKebabWordIfNeeded(propName) : propName;
1290
- let cssValue = null;
1291
- const prevV = (_d = prevKf == null ? void 0 : prevKf.v) != null ? _d : prevKf == null ? void 0 : prevKf.value;
1292
- const nextV = (_e = nextKf == null ? void 0 : nextKf.v) != null ? _e : nextKf == null ? void 0 : nextKf.value;
1293
- if (cssAttrName === "d") {
1294
- const prevPaths = (_f = prevV == null ? void 0 : prevV.paths) != null ? _f : Array.isArray(prevV) ? prevV : [];
1295
- const nextPaths = (_g = nextV == null ? void 0 : nextV.paths) != null ? _g : Array.isArray(nextV) ? nextV : [];
1296
- cssValue = interpolateBeziers(
1297
- prevPaths,
1298
- nextPaths,
1299
- localProgress
1300
- ).map((bz) => bezierToSvgPath(bz)).join("");
1301
- } else if (COLOUR_ATTR_NAMES.has(cssAttrName)) {
1302
- cssValue = toRGBA(interpolateColor(
1303
- prevV || [0, 0, 0, 1],
1304
- nextV || [0, 0, 0, 1],
1305
- localProgress
1306
- ));
1307
- cssAttrName = propName;
1308
- } else if (cssAttrName === "stroke-dasharray") {
1309
- cssValue = interpolateVec(
1310
- prevV || [],
1311
- nextV || [],
1312
- localProgress
1313
- ).join(" ");
1314
- cssAttrName = propName;
1315
- } else if (cssAttrName === "translate") {
1316
- const v = interpolateVec(
1317
- prevV || [0, 0],
1318
- nextV || [0, 0],
1319
- localProgress
1320
- );
1321
- cssValue = "translate(" + v.join(",") + ")";
1322
- cssAttrName = "transform";
1323
- } else if (cssAttrName === "rotate") {
1324
- const v = interpolateNum(
1325
- +(prevV || 0),
1326
- +(nextV || 0),
1327
- localProgress
1328
- );
1329
- cssValue = "rotate(" + v + ")";
1330
- cssAttrName = "transform";
1331
- } else if (cssAttrName === "scale") {
1332
- const v = interpolateVec(
1333
- prevV || [1, 1],
1334
- nextV || [1, 1],
1335
- localProgress
1336
- );
1337
- cssValue = "scale(" + v.join(",") + ")";
1338
- cssAttrName = "transform";
1339
- } else {
1340
- const num = interpolateNum(
1341
- +(prevV || 0),
1342
- +(nextV || 0),
1343
- localProgress
1344
- );
1345
- cssValue = num;
1346
- }
1347
- if (PCT_BASED_ATTR_NAMES.has(cssAttrName) && typeof cssValue === "number") {
1348
- cssValue = cssValue * 100 + "%";
1349
- }
1350
- return { k: cssAttrName, v: cssValue === null ? "" : "" + cssValue };
1351
- }
1352
- function calcAnimationValues(animDef, progress) {
1353
- const result = {};
1354
- for (const [propName, propAnim] of Object.entries(animDef)) {
1355
- const computed = calcPropertyValue(propName, propAnim, progress);
1356
- if (computed) {
1357
- result[computed.k] = computed.v;
3965
+ case "click": {
3966
+ const clickHandler = () => {
3967
+ if (api.isPlaying()) {
3968
+ handleEndAction();
3969
+ } else {
3970
+ start();
3971
+ }
3972
+ };
3973
+ root.addEventListener("click", clickHandler);
3974
+ break;
3975
+ }
3976
+ case "scrollIntoView": {
3977
+ const observer = new IntersectionObserver(
3978
+ (entries) => {
3979
+ entries.forEach((entry) => {
3980
+ if (entry.isIntersecting && entry.intersectionRatio >= scrollIntoViewThreshold) {
3981
+ start();
3982
+ } else {
3983
+ handleEndAction();
3984
+ }
3985
+ });
3986
+ },
3987
+ { threshold: scrollIntoViewThreshold }
3988
+ );
3989
+ observer.observe(root);
3990
+ break;
1358
3991
  }
3992
+ case "programmatic":
3993
+ break;
1359
3994
  }
1360
- return result;
3995
+ return api;
1361
3996
  }
1362
3997
 
1363
3998
  // src/PxAnimatorFrameLoop.ts
@@ -1366,7 +4001,7 @@ function getSelector(id) {
1366
4001
  }
1367
4002
  function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
1368
4003
  const config = getAnimatorConfig(doc) || {};
1369
- const bindings = getNormalisedBindings(doc);
4004
+ const bindings = getNormalisedBindings(doc, PxAnimatorEngine.frames);
1370
4005
  const _iterations = config.iterations;
1371
4006
  let iterations = 1;
1372
4007
  if (typeof _iterations === "number") iterations = _iterations || 1;
@@ -1484,6 +4119,10 @@ function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
1484
4119
  };
1485
4120
  const startAnim = () => {
1486
4121
  if (playing) return;
4122
+ if (Number.isFinite(totalDuration) && timeBeforeLastStartMs >= totalDuration) {
4123
+ timeBeforeLastStartMs = 0;
4124
+ finishCalled = false;
4125
+ }
1487
4126
  playing = true;
1488
4127
  lastStartedTs = Date.now();
1489
4128
  loopAnim(true);
@@ -1634,6 +4273,9 @@ function createCssKf(kf, t, propName, unsupportedSet) {
1634
4273
  let cssKey = propName;
1635
4274
  if (COLOUR_ATTR_NAMES.has(propName) && Array.isArray(value)) {
1636
4275
  cssValue = toRGBA(value);
4276
+ } else if (propName === "transform" && value !== null && typeof value === "object" && !Array.isArray(value)) {
4277
+ cssValue = composeTransformParts(value, { withUnits: true });
4278
+ cssKey = "transform";
1637
4279
  } else if (TRANSFORM_FN_NAMES.has(propName)) {
1638
4280
  if (Array.isArray(value)) {
1639
4281
  if (propName === "translate") value = value.map((v) => v + "px");
@@ -1650,28 +4292,59 @@ function createCssKf(kf, t, propName, unsupportedSet) {
1650
4292
  cssKf[cssKey] = cssValue;
1651
4293
  return cssKf;
1652
4294
  }
4295
+ function clipKeyframesToDuration(propName, keyframes, duration) {
4296
+ var _a, _b, _c, _d, _e;
4297
+ const result = [];
4298
+ for (let i = 0; i < keyframes.length; i++) {
4299
+ const kf = keyframes[i];
4300
+ const t = (_a = kf.t) != null ? _a : 0;
4301
+ if (t < 0) {
4302
+ const next = keyframes[i + 1];
4303
+ if (next && ((_b = next.t) != null ? _b : 0) >= 0) {
4304
+ const nextT = (_c = next.t) != null ? _c : 0;
4305
+ const localFrac = (0 - t) / (nextT - t);
4306
+ const easedFrac = kf.e ? cubicBezier(kf.e)(localFrac) : localFrac;
4307
+ const { right: rightEasing } = splitEasing(kf.e, localFrac);
4308
+ result.push({ t: 0, v: interpolateValue(propName, kf.v, next.v, easedFrac), e: rightEasing });
4309
+ }
4310
+ continue;
4311
+ }
4312
+ if (t > duration) {
4313
+ const prev = keyframes[i - 1];
4314
+ if (prev && ((_d = prev.t) != null ? _d : 0) <= duration) {
4315
+ const prevT = (_e = prev.t) != null ? _e : 0;
4316
+ const localFrac = (duration - prevT) / (t - prevT);
4317
+ const easedFrac = prev.e ? cubicBezier(prev.e)(localFrac) : localFrac;
4318
+ const { left: leftEasing } = splitEasing(prev.e, localFrac);
4319
+ if (result.length > 0) result[result.length - 1] = __spreadProps(__spreadValues({}, result[result.length - 1]), { e: leftEasing });
4320
+ result.push({ t: duration, v: interpolateValue(propName, prev.v, kf.v, easedFrac), e: void 0 });
4321
+ }
4322
+ break;
4323
+ }
4324
+ result.push(kf);
4325
+ }
4326
+ return result;
4327
+ }
1653
4328
  function convertToWebApiKeyframes(animDef, unsupportedSet, config) {
1654
- var _a, _b;
4329
+ var _a;
1655
4330
  const result = /* @__PURE__ */ new Map();
1656
4331
  for (const [propName, propAnim] of Object.entries(animDef)) {
1657
- const keyframes = propAnim.kfs || propAnim.keyframes || [];
4332
+ const duration = config.duration || 1;
4333
+ const clippedKeyframes = clipKeyframesToDuration(propName, propAnim.kfs || propAnim.keyframes || [], duration);
1658
4334
  const cssKeyframes = [];
1659
- for (let i = 0; i < keyframes.length; i++) {
1660
- const kf = keyframes[i];
1661
- let t = (_b = (_a = kf.t) != null ? _a : kf.time) != null ? _b : 0;
1662
- t = clamp(t / (config.duration || 1), 0, 1);
4335
+ for (let i = 0; i < clippedKeyframes.length; i++) {
4336
+ const kf = clippedKeyframes[i];
4337
+ const t = clamp(((_a = kf.t) != null ? _a : 0) / duration, 0, 1);
1663
4338
  const cssKf = createCssKf(kf, t, propName, unsupportedSet);
1664
4339
  if (i === 0 && (cssKf.offset || 0) > 0) {
1665
- cssKeyframes.push(__spreadProps(__spreadValues({}, cssKf), {
1666
- offset: 0
1667
- }));
4340
+ cssKeyframes.push(__spreadProps(__spreadValues({}, cssKf), { offset: 0 }));
1668
4341
  }
1669
4342
  cssKeyframes.push(cssKf);
1670
- if (i === keyframes.length - 1 && (cssKf.offset || 0) < 1) {
1671
- cssKeyframes.push(__spreadProps(__spreadValues({}, cssKf), {
1672
- offset: 1
1673
- }));
1674
- }
4343
+ }
4344
+ if (cssKeyframes.length > 0 && (cssKeyframes[cssKeyframes.length - 1].offset || 0) < 1) {
4345
+ cssKeyframes.push(__spreadProps(__spreadValues({}, cssKeyframes[cssKeyframes.length - 1]), {
4346
+ offset: 1
4347
+ }));
1675
4348
  }
1676
4349
  if (cssKeyframes.length > 0) {
1677
4350
  result.set(propName, cssKeyframes);
@@ -1680,8 +4353,8 @@ function convertToWebApiKeyframes(animDef, unsupportedSet, config) {
1680
4353
  return result;
1681
4354
  }
1682
4355
  function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsupportedAttrs) {
4356
+ var _a;
1683
4357
  const config = getAnimatorConfig(doc) || {};
1684
- const bindings = getNormalisedBindings(doc);
1685
4358
  if (!rootElement) {
1686
4359
  if (doc.id) {
1687
4360
  const rootSelector = getSelector(doc.id);
@@ -1691,6 +4364,7 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
1691
4364
  console.warn("createFrameLoopAnimator: No root element provided");
1692
4365
  }
1693
4366
  }
4367
+ const bindings = getNormalisedBindings(doc, PxAnimatorEngine.webapi);
1694
4368
  const animations = [];
1695
4369
  const _iterations = config.iterations;
1696
4370
  let iterations;
@@ -1717,7 +4391,11 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
1717
4391
  const effectOptions = {
1718
4392
  duration: config.duration,
1719
4393
  delay: positiveDelay,
1720
- fill: config.fill,
4394
+ // Default to 'forwards' so elements hold their final state after the
4395
+ // animation ends — consistent with Lottie and other animation runtimes.
4396
+ // Without this, seeking to the last frame reverts elements to their
4397
+ // pre-animation state (the Web Animations API "after" phase with fill:'none').
4398
+ fill: (_a = config.fill) != null ? _a : "forwards",
1721
4399
  direction: config.direction,
1722
4400
  iterations
1723
4401
  };
@@ -1729,12 +4407,12 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
1729
4407
  const effect = new KeyframeEffect(element, keyframes, effectOptions);
1730
4408
  const anim = new Animation(effect, document.timeline);
1731
4409
  if (callbacks == null ? void 0 : callbacks.onFinish) anim.onfinish = () => {
1732
- var _a;
1733
- return (_a = callbacks.onFinish) == null ? void 0 : _a.call(callbacks);
4410
+ var _a2;
4411
+ return (_a2 = callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
1734
4412
  };
1735
4413
  if (callbacks == null ? void 0 : callbacks.onRemove) anim.onremove = () => {
1736
- var _a;
1737
- return (_a = callbacks.onRemove) == null ? void 0 : _a.call(callbacks);
4414
+ var _a2;
4415
+ return (_a2 = callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
1738
4416
  };
1739
4417
  if (seekPosition) {
1740
4418
  anim.currentTime = seekPosition;
@@ -1755,29 +4433,29 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
1755
4433
  "isReady": () => true,
1756
4434
  "getRootElement": () => rootElement || null,
1757
4435
  "isPlaying": () => {
1758
- var _a;
1759
- return ((_a = animations[0]) == null ? void 0 : _a.playState) === "running";
4436
+ var _a2;
4437
+ return ((_a2 = animations[0]) == null ? void 0 : _a2.playState) === "running";
1760
4438
  },
1761
4439
  "play": () => {
1762
- var _a;
4440
+ var _a2;
1763
4441
  animations.forEach((a) => a.play());
1764
- (_a = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a.call(callbacks);
4442
+ (_a2 = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a2.call(callbacks);
1765
4443
  },
1766
4444
  "pause": () => {
1767
- var _a;
4445
+ var _a2;
1768
4446
  animations.forEach((a) => a.pause());
1769
- (_a = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a.call(callbacks);
4447
+ (_a2 = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a2.call(callbacks);
1770
4448
  },
1771
4449
  "cancel": () => {
1772
- var _a;
4450
+ var _a2;
1773
4451
  animations.forEach((a) => a.cancel());
1774
- (_a = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a.call(callbacks);
4452
+ (_a2 = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a2.call(callbacks);
1775
4453
  },
1776
4454
  "finish": () => {
1777
- var _a;
4455
+ var _a2;
1778
4456
  for (const a of animations) {
1779
4457
  try {
1780
- if (((_a = a.effect) == null ? void 0 : _a.getTiming().iterations) === Infinity) {
4458
+ if (((_a2 = a.effect) == null ? void 0 : _a2.getTiming().iterations) === Infinity) {
1781
4459
  a.effect.updateTiming({ iterations: 1 });
1782
4460
  a.finish();
1783
4461
  a.effect.updateTiming({ iterations: Infinity });
@@ -1794,8 +4472,8 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
1794
4472
  return api;
1795
4473
  },
1796
4474
  "getCurrentTime": () => {
1797
- var _a, _b;
1798
- const res = (_b = (_a = animations[0]) == null ? void 0 : _a.currentTime) != null ? _b : null;
4475
+ var _a2, _b;
4476
+ const res = (_b = (_a2 = animations[0]) == null ? void 0 : _a2.currentTime) != null ? _b : null;
1799
4477
  return res !== null ? +res : null;
1800
4478
  },
1801
4479
  "setCurrentTime": (time) => {
@@ -1818,15 +4496,15 @@ function createWebApiAnimator(doc, callbacks, rootElement, forceEvenIfHasUnsuppo
1818
4496
  function createAnimatorFromConfig(doc, adapter, callbacks, rootElement) {
1819
4497
  const animatorConfig = getAnimatorConfig(doc) || {};
1820
4498
  let res;
1821
- if (animatorConfig.mode === "frames") {
4499
+ if (animatorConfig.mode === PxAnimatorMode.frames) {
1822
4500
  res = createFrameLoopAnimator(doc, adapter, callbacks, rootElement);
1823
4501
  } else {
1824
4502
  res = createWebApiAnimator(
1825
4503
  doc,
1826
4504
  callbacks,
1827
4505
  rootElement,
1828
- animatorConfig.mode === "webapi"
1829
- // Forcing "webapi"
4506
+ animatorConfig.mode === PxAnimatorMode.webapi
4507
+ // forcing webapi
1830
4508
  ) || createFrameLoopAnimator(doc, adapter, callbacks, rootElement);
1831
4509
  }
1832
4510
  if (animatorConfig.debugInstName) {
@@ -1852,6 +4530,7 @@ function deepClone(value) {
1852
4530
  return cloned;
1853
4531
  }
1854
4532
  function generateNewIds(doc) {
4533
+ var _a, _b;
1855
4534
  const cloned = deepClone(doc);
1856
4535
  const idMap = /* @__PURE__ */ new Map();
1857
4536
  const hashRefAttrs = /* @__PURE__ */ new Set(["href", "xlink:href"]);
@@ -1877,6 +4556,8 @@ function generateNewIds(doc) {
1877
4556
  const newId = generateUniqueId();
1878
4557
  idMap.set(oldId, newId);
1879
4558
  node.id = newId;
4559
+ } else if (node.animate) {
4560
+ node.id = generateUniqueId();
1880
4561
  }
1881
4562
  if (Array.isArray(node.children)) {
1882
4563
  for (const child of node.children) {
@@ -1918,23 +4599,21 @@ function generateNewIds(doc) {
1918
4599
  value[styleProp] = replaceUrlRefs(styleValue, idMap);
1919
4600
  }
1920
4601
  }
1921
- } else if (typeof value === "object" && value !== null && key !== ANIMATE_ATTR) {
4602
+ } else if (typeof value === "object" && value !== null) {
1922
4603
  updateRefs(value);
1923
4604
  }
1924
4605
  }
1925
4606
  }
1926
4607
  collectIds(cloned);
1927
4608
  updateRefs(cloned);
1928
- const docBindings = cloned.bindings;
1929
- if (Array.isArray(docBindings)) {
1930
- for (const binding of docBindings) {
1931
- if (binding.id) {
1932
- const newId = idMap.get(binding.id);
1933
- if (newId) {
1934
- binding.id = newId;
1935
- }
1936
- }
4609
+ const docAnimate = (_a = cloned.animator) == null ? void 0 : _a.animate;
4610
+ if (docAnimate && typeof docAnimate === "object") {
4611
+ const updatedAnimate = {};
4612
+ for (const [id, anim] of Object.entries(docAnimate)) {
4613
+ const newId = (_b = idMap.get(id)) != null ? _b : id;
4614
+ updatedAnimate[newId] = anim;
1937
4615
  }
4616
+ cloned.animator = __spreadProps(__spreadValues({}, cloned.animator), { animate: updatedAnimate });
1938
4617
  }
1939
4618
  return cloned;
1940
4619
  }
@@ -1945,8 +4624,12 @@ function replaceUrlRefs(value, idMap) {
1945
4624
  });
1946
4625
  }
1947
4626
  function createAnimatorImpl(doc, adapter, callbacks, containerElement) {
4627
+ const effectsWarnings = validateNodeEffects(doc);
4628
+ for (const w of effectsWarnings) console.warn("[PxAnimator] effects shape warning:", w);
1948
4629
  const animatorConfig = getAnimatorConfig(doc) || {};
1949
4630
  animatorConfig.debug = true;
4631
+ const engine = animatorConfig.mode === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.webapi;
4632
+ doc = materialiseAllInTree(doc, engine);
1950
4633
  let rootElement = null;
1951
4634
  if (containerElement && doc.children) {
1952
4635
  doc = generateNewIds(doc);
@@ -1960,14 +4643,22 @@ function createAnimatorImpl(doc, adapter, callbacks, containerElement) {
1960
4643
  }
1961
4644
  return createAnimatorFromConfig(doc, adapter, callbacks, rootElement);
1962
4645
  }
1963
- function createAnimator(docOrUrl, adapter, callbacks, containerElement) {
1964
- if (typeof docOrUrl === "object") {
1965
- return createAnimatorImpl(docOrUrl, adapter, callbacks, containerElement);
4646
+ var PX_ANIMATOR_DATA_KEY = "data";
4647
+ function createAnimator(options) {
4648
+ const { src, data, adapter, callbacks, container } = options;
4649
+ if (data !== void 0 && src !== void 0) {
4650
+ throw new Error("createAnimator: provide either `src` or `data`, not both");
4651
+ }
4652
+ if (data === void 0 && src === void 0) {
4653
+ throw new Error("createAnimator: either `src` or `data` is required");
4654
+ }
4655
+ if (data !== void 0) {
4656
+ return createAnimatorImpl(data, adapter, callbacks, container);
1966
4657
  }
1967
4658
  let animator = null;
1968
- fetch(docOrUrl).then((res) => res.json()).then((json) => {
4659
+ fetch(src).then((res) => res.json()).then((json) => {
1969
4660
  if (isPxElementFileFormat(json)) {
1970
- animator = createAnimatorImpl(json, adapter, callbacks, containerElement);
4661
+ animator = createAnimatorImpl(json, adapter, callbacks, container);
1971
4662
  } else {
1972
4663
  console.error("Invalid animation document format");
1973
4664
  }
@@ -2007,7 +4698,7 @@ function loadTagAnimators() {
2007
4698
  if (!element[PX_ANIM_ATTR_NAME]) {
2008
4699
  const src = element.getAttribute(PX_ANIM_SRC_ATTR_NAME);
2009
4700
  if (src) {
2010
- element[PX_ANIM_ATTR_NAME] = createAnimator(src, void 0, void 0, element);
4701
+ element[PX_ANIM_ATTR_NAME] = createAnimator({ src, container: element });
2011
4702
  }
2012
4703
  }
2013
4704
  }
@@ -2017,19 +4708,268 @@ if (typeof window !== "undefined") {
2017
4708
  window["createAnimator"] = createAnimator;
2018
4709
  window["setupAnimationTriggers"] = setupAnimationTriggers;
2019
4710
  }
4711
+
4712
+ // src/effects/PlayerEffectsUtil.visualModel.ts
4713
+ var IDENTITY = [1, 0, 0, 1, 0, 0];
4714
+ function mul(m, n) {
4715
+ return [
4716
+ m[0] * n[0] + m[2] * n[1],
4717
+ m[1] * n[0] + m[3] * n[1],
4718
+ m[0] * n[2] + m[2] * n[3],
4719
+ m[1] * n[2] + m[3] * n[3],
4720
+ m[0] * n[4] + m[2] * n[5] + m[4],
4721
+ m[1] * n[4] + m[3] * n[5] + m[5]
4722
+ ];
4723
+ }
4724
+ var translateM = (x, y) => [1, 0, 0, 1, x, y];
4725
+ var scaleM = (sx, sy) => [sx, 0, 0, sy, 0, 0];
4726
+ function rotateM(deg) {
4727
+ const r = deg * Math.PI / 180;
4728
+ return [Math.cos(r), Math.sin(r), -Math.sin(r), Math.cos(r), 0, 0];
4729
+ }
4730
+ var skewXM = (deg) => [1, 0, Math.tan(deg * Math.PI / 180), 1, 0, 0];
4731
+ var skewYM = (deg) => [1, Math.tan(deg * Math.PI / 180), 0, 1, 0, 0];
4732
+ function parseTransformString(s) {
4733
+ let m = IDENTITY;
4734
+ const re = /(translate|rotate|scale|matrix|skewX|skewY)\(([^)]*)\)/g;
4735
+ let hit;
4736
+ while (hit = re.exec(s)) {
4737
+ const fn = hit[1];
4738
+ const a = hit[2].split(/[\s,]+/).filter(Boolean).map(Number);
4739
+ if (fn === "translate") m = mul(m, translateM(a[0] || 0, a[1] || 0));
4740
+ else if (fn === "scale") m = mul(m, scaleM(a[0], a.length > 1 ? a[1] : a[0]));
4741
+ else if (fn === "rotate") m = mul(m, rotateM(a[0]));
4742
+ else if (fn === "skewX") m = mul(m, skewXM(a[0]));
4743
+ else if (fn === "skewY") m = mul(m, skewYM(a[0]));
4744
+ else if (fn === "matrix") m = mul(m, [a[0], a[1], a[2], a[3], a[4], a[5]]);
4745
+ }
4746
+ return m;
4747
+ }
4748
+ function partsToMatrix(p) {
4749
+ let m = IDENTITY;
4750
+ if (p.translate) m = mul(m, translateM(p.translate[0], p.translate[1]));
4751
+ const pivot = p.origin && (p.rotate !== void 0 || p.scale);
4752
+ if (pivot) m = mul(m, translateM(p.origin[0], p.origin[1]));
4753
+ if (p.rotate !== void 0) m = mul(m, rotateM(p.rotate));
4754
+ if (p.scale) m = mul(m, scaleM(p.scale[0], p.scale[1]));
4755
+ if (pivot) m = mul(m, translateM(-p.origin[0], -p.origin[1]));
4756
+ return m;
4757
+ }
4758
+ function lerp(a, b, f) {
4759
+ return a + (b - a) * f;
4760
+ }
4761
+ function interpParts(kfs, t) {
4762
+ var _a, _b, _c, _d, _e, _f, _g;
4763
+ if (!kfs.length) return {};
4764
+ if (t <= ((_a = kfs[0].time) != null ? _a : 0)) return kfs[0].value || {};
4765
+ if (t >= ((_b = kfs[kfs.length - 1].time) != null ? _b : 0)) return kfs[kfs.length - 1].value || {};
4766
+ let i = 0;
4767
+ while (i < kfs.length - 1 && ((_c = kfs[i + 1].time) != null ? _c : 0) < t) i++;
4768
+ const a = kfs[i], b = kfs[i + 1];
4769
+ const f = (t - ((_d = a.time) != null ? _d : 0)) / (((_e = b.time) != null ? _e : 0) - ((_f = a.time) != null ? _f : 0) || 1);
4770
+ const va = a.value || {}, vb = b.value || {};
4771
+ const out = {};
4772
+ if (va.translate && vb.translate) out.translate = [lerp(va.translate[0], vb.translate[0], f), lerp(va.translate[1], vb.translate[1], f)];
4773
+ else out.translate = va.translate || vb.translate;
4774
+ if (va.rotate !== void 0 && vb.rotate !== void 0) out.rotate = lerp(va.rotate, vb.rotate, f);
4775
+ else out.rotate = (_g = va.rotate) != null ? _g : vb.rotate;
4776
+ if (va.scale && vb.scale) out.scale = [lerp(va.scale[0], vb.scale[0], f), lerp(va.scale[1], vb.scale[1], f)];
4777
+ else out.scale = va.scale || vb.scale;
4778
+ out.origin = va.origin || vb.origin;
4779
+ return out;
4780
+ }
4781
+ function evalTransformValue(v, t) {
4782
+ if (v === void 0 || v === null) return IDENTITY;
4783
+ if (typeof v === "string") return parseTransformString(v);
4784
+ if (v.keyframes) return partsToMatrix(interpParts(v.keyframes, t));
4785
+ if (v.value) return partsToMatrix(v.value);
4786
+ return IDENTITY;
4787
+ }
4788
+ function nodeMatrix(node, t) {
4789
+ if (node.animate && node.animate.transform) return evalTransformValue(node.animate.transform, t);
4790
+ if (node.transform !== void 0) return evalTransformValue(node.transform, t);
4791
+ return IDENTITY;
4792
+ }
4793
+ function evalScalar(animated, staticVal, fallback, t) {
4794
+ var _a, _b, _c, _d, _e, _f;
4795
+ if (animated && animated.keyframes && animated.keyframes.length) {
4796
+ const kfs = animated.keyframes;
4797
+ if (t <= ((_a = kfs[0].time) != null ? _a : 0)) return kfs[0].value;
4798
+ if (t >= ((_b = kfs[kfs.length - 1].time) != null ? _b : 0)) return kfs[kfs.length - 1].value;
4799
+ let i = 0;
4800
+ while (i < kfs.length - 1 && ((_c = kfs[i + 1].time) != null ? _c : 0) < t) i++;
4801
+ const a = kfs[i], b = kfs[i + 1];
4802
+ const f = (t - ((_d = a.time) != null ? _d : 0)) / (((_e = b.time) != null ? _e : 0) - ((_f = a.time) != null ? _f : 0) || 1);
4803
+ return lerp(a.value, b.value, f);
4804
+ }
4805
+ return staticVal !== void 0 ? Number(staticVal) : fallback;
4806
+ }
4807
+ var CONTAINER_TYPES = /* @__PURE__ */ new Set(["svg", "g", "symbol"]);
4808
+ var SKIP_TYPES = /* @__PURE__ */ new Set(["defs", "mask", "clipPath", "title"]);
4809
+ function buildIdMap2(node, map) {
4810
+ var _a;
4811
+ if (typeof node.id === "string") map.set(node.id, node);
4812
+ (_a = node.children) == null ? void 0 : _a.forEach((c) => buildIdMap2(c, map));
4813
+ }
4814
+ function num(v) {
4815
+ return v === void 0 || v === null ? 0 : Number(v);
4816
+ }
4817
+ function round(n) {
4818
+ return Math.round(n * 100) / 100 + 0;
4819
+ }
4820
+ function geomKey(node) {
4821
+ var _a;
4822
+ switch (node.type) {
4823
+ case "rect":
4824
+ return num(node.width) + "," + num(node.height) + "," + num(node.x) + "," + num(node.y);
4825
+ case "ellipse":
4826
+ return num(node.rx) + "," + num(node.ry) + "," + num(node.cx) + "," + num(node.cy);
4827
+ case "circle":
4828
+ return num(node.r) + "," + num(node.cx) + "," + num(node.cy);
4829
+ case "path":
4830
+ return String((_a = node.d) != null ? _a : "");
4831
+ default:
4832
+ return "";
4833
+ }
4834
+ }
4835
+ function describePrimitive(node, m, t) {
4836
+ var _a, _b, _c, _d, _e;
4837
+ const fill = (_a = node.fill) != null ? _a : "";
4838
+ const stroke = (_b = node.stroke) != null ? _b : "";
4839
+ const sw = (_d = (_c = node["stroke-width"]) != null ? _c : node.strokeWidth) != null ? _d : "";
4840
+ const opacity = round(evalScalar((_e = node.animate) == null ? void 0 : _e.opacity, node.opacity, 1, t));
4841
+ const masked = node.mask ? 1 : 0;
4842
+ const mat = m.map(round).join(",");
4843
+ return node.type + "|" + geomKey(node) + "|[" + mat + "]|f:" + fill + "|s:" + stroke + "|sw:" + (num(sw) || "") + "|o:" + opacity + "|m:" + masked;
4844
+ }
4845
+ function flatten(node, parent, t, idMap, out) {
4846
+ var _a;
4847
+ const type = node.type || "";
4848
+ if (SKIP_TYPES.has(type)) return;
4849
+ const m = mul(parent, nodeMatrix(node, t));
4850
+ if (type === "use") {
4851
+ const targetId = typeof node.href === "string" ? node.href.replace(/^#/, "") : "";
4852
+ const target = idMap.get(targetId);
4853
+ const useM = mul(m, translateM(num(node.x), num(node.y)));
4854
+ if (target) flatten(target, useM, t, idMap, out);
4855
+ else out.push("UNRESOLVED_USE:#" + targetId);
4856
+ return;
4857
+ }
4858
+ if (CONTAINER_TYPES.has(type)) {
4859
+ (_a = node.children) == null ? void 0 : _a.forEach((c) => flatten(c, m, t, idMap, out));
4860
+ return;
4861
+ }
4862
+ out.push(describePrimitive(node, m, t));
4863
+ }
4864
+ function collectSampleTimes(node, into) {
4865
+ var _a;
4866
+ into.add(0);
4867
+ const scanAnim = (anim) => {
4868
+ var _a2;
4869
+ if (!anim || typeof anim !== "object") return;
4870
+ for (const key of Object.keys(anim)) {
4871
+ const kfs = (_a2 = anim[key]) == null ? void 0 : _a2.keyframes;
4872
+ if (Array.isArray(kfs)) kfs.forEach((kf) => {
4873
+ var _a3;
4874
+ return into.add((_a3 = kf.time) != null ? _a3 : 0);
4875
+ });
4876
+ }
4877
+ };
4878
+ scanAnim(node.animate);
4879
+ if (node.transform && typeof node.transform === "object" && node.transform.keyframes) {
4880
+ node.transform.keyframes.forEach((kf) => {
4881
+ var _a2;
4882
+ return into.add((_a2 = kf.time) != null ? _a2 : 0);
4883
+ });
4884
+ }
4885
+ (_a = node.children) == null ? void 0 : _a.forEach((c) => collectSampleTimes(c, into));
4886
+ }
4887
+ function visualModelAt(root, t) {
4888
+ const idMap = /* @__PURE__ */ new Map();
4889
+ buildIdMap2(root, idMap);
4890
+ const out = [];
4891
+ flatten(root, IDENTITY, t, idMap, out);
4892
+ return out.sort();
4893
+ }
4894
+ function diffInEffect(a, b) {
4895
+ const times = /* @__PURE__ */ new Set();
4896
+ collectSampleTimes(a, times);
4897
+ collectSampleTimes(b, times);
4898
+ const diffs = [];
4899
+ for (const t of Array.from(times).sort((x, y) => x - y)) {
4900
+ const ma = visualModelAt(a, t);
4901
+ const mb = visualModelAt(b, t);
4902
+ const onlyInA = subtractMultiset(ma, mb);
4903
+ const onlyInB = subtractMultiset(mb, ma);
4904
+ if (onlyInA.length || onlyInB.length) diffs.push({ time: t, onlyInA, onlyInB });
4905
+ }
4906
+ return diffs;
4907
+ }
4908
+ function subtractMultiset(a, b) {
4909
+ const counts = /* @__PURE__ */ new Map();
4910
+ for (const x of b) counts.set(x, (counts.get(x) || 0) + 1);
4911
+ const extra = [];
4912
+ for (const x of a) {
4913
+ const c = counts.get(x) || 0;
4914
+ if (c > 0) counts.set(x, c - 1);
4915
+ else extra.push(x);
4916
+ }
4917
+ return extra;
4918
+ }
2020
4919
  export {
2021
4920
  COLOUR_ATTR_NAMES,
4921
+ PX_ANIMATOR_DATA_KEY,
2022
4922
  PX_ANIM_ATTR_NAME,
2023
4923
  PX_ANIM_SRC_ATTR_NAME,
4924
+ PX_TRANSFORM_PART_KEYS,
4925
+ PxAnimatedSvgDocumentSchema,
4926
+ PxAnimationDefinitionSchema,
4927
+ PxAnimatorConfigSchema,
4928
+ PxAnimatorEngine,
4929
+ PxAnimatorMode,
4930
+ PxAttrValueSchema,
4931
+ PxBezierPathSchema,
4932
+ PxBindingSchema,
4933
+ PxDefsSchema,
4934
+ PxEasingOrRefSchema,
4935
+ PxEffectsSchema,
4936
+ PxElementAnimationSchema,
4937
+ PxFillGradientEffectSchema,
4938
+ PxGradientSpreadMethod,
4939
+ PxGradientStopSchema,
4940
+ PxGradientType,
4941
+ PxGradientUnits,
4942
+ PxKeyframeSchema,
4943
+ PxLoopSchema,
4944
+ PxMaskedByEffectSchema,
4945
+ PxNodeBase,
4946
+ PxNodeSchema,
4947
+ PxPropertyAnimationSchema,
4948
+ PxRefEffectSchema,
4949
+ PxRepeaterEffectSchema,
4950
+ PxRetimeEffectSchema,
4951
+ PxStrokeGradientEffectSchema,
4952
+ PxSvgNodeExtra,
4953
+ PxTextAlongPathEffectSchema,
4954
+ PxTransformPartsSchema,
4955
+ PxTransformValueSchema,
4956
+ PxTransformationEffectSchema,
4957
+ PxTriggerSchema,
4958
+ PxTrimPathEffectSchema,
2024
4959
  STYLE_ATTR_NAMES,
2025
4960
  TRANSFORM_FN_NAMES,
4961
+ applyPlayerEffects,
2026
4962
  calcAnimationValues,
2027
4963
  camelCaseToKebabWordIfNeeded,
4964
+ collectSampleTimes,
2028
4965
  createAnimator,
2029
4966
  createAnimatorImpl,
2030
4967
  createBasicFrameLoopAnimator,
2031
4968
  createFrameLoopAnimator,
2032
4969
  createWebApiAnimator,
4970
+ describeSchema,
4971
+ diffInEffect,
4972
+ evaluateMotionPathSegment,
2033
4973
  generateNewIds,
2034
4974
  getAnimatorConfig,
2035
4975
  getBindings,
@@ -2039,9 +4979,20 @@ export {
2039
4979
  isPxElementFileFormat,
2040
4980
  isPxElementFileFormatDeep,
2041
4981
  loadTagAnimators,
4982
+ materialiseAllInTree,
4983
+ materialiseAnimatedUseInstances,
4984
+ materialiseInternalLoopsInPropAnim,
4985
+ materialiseInternalLoopsInTree,
4986
+ materialiseMotionPathInPropAnim,
4987
+ materialiseMotionPathsInTree,
2042
4988
  getNormalisedBindings as normalizeDocument,
4989
+ propAnimIsMotionPath,
4990
+ px,
2043
4991
  renderNode,
4992
+ schemaKeys,
2044
4993
  setupAnimationTriggers,
2045
- toRGBA
4994
+ toRGBA,
4995
+ validateNodeEffects,
4996
+ visualModelAt
2046
4997
  };
2047
4998
  //# sourceMappingURL=index.js.map