@pixodesk/svg-animator-react 1.0.18 → 1.0.20

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.umd.js CHANGED
@@ -2728,6 +2728,10 @@ var PixodeskAnimatorReact = (() => {
2728
2728
  maskUnits: px.string().optional(),
2729
2729
  maskContentUnits: px.string().optional()
2730
2730
  }));
2731
+ var PxClipPathEffectSchema = implementsInterface()(px.object({
2732
+ d: px.string().optional(),
2733
+ animate: PxPropertyAnimationSchema.optional()
2734
+ }));
2731
2735
  var PxTrimPathEffectSchema = implementsInterface()(px.object({
2732
2736
  offset: PxAnimatableNumberSchema.optional(),
2733
2737
  range: PxAnimatableVec2Schema.optional(),
@@ -2770,8 +2774,9 @@ var PixodeskAnimatorReact = (() => {
2770
2774
  gradientTransform: px.string().optional()
2771
2775
  }));
2772
2776
  var PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;
2773
- var PxTextAlongPathEffectSchema = implementsInterface()(px.object({
2774
- href: px.string(),
2777
+ var PxTextPathEffectSchema = implementsInterface()(px.object({
2778
+ path: px.string(),
2779
+ pathOverflow: px.string().optional(),
2775
2780
  lengthAdjust: px.string().optional(),
2776
2781
  method: px.string().optional(),
2777
2782
  spacing: px.string().optional(),
@@ -2785,12 +2790,13 @@ var PixodeskAnimatorReact = (() => {
2785
2790
  transformation: PxTransformationEffectSchema.optional(),
2786
2791
  repeater: PxRepeaterEffectSchema.optional(),
2787
2792
  maskedBy: PxMaskedByEffectSchema.optional(),
2793
+ clipPath: PxClipPathEffectSchema.optional(),
2788
2794
  trimPath: PxTrimPathEffectSchema.optional(),
2789
2795
  clone: PxCloneEffectSchema.optional(),
2790
2796
  isCombinedShape: px.boolean().optional(),
2791
2797
  fillGradient: PxFillGradientEffectSchema.optional(),
2792
2798
  strokeGradient: PxStrokeGradientEffectSchema.optional(),
2793
- textAlongPath: PxTextAlongPathEffectSchema.optional(),
2799
+ textPath: PxTextPathEffectSchema.optional(),
2794
2800
  text: PxTextEffectSchema.optional()
2795
2801
  }));
2796
2802
  function validateNodeEffects(root, opts) {
@@ -3077,6 +3083,15 @@ var PixodeskAnimatorReact = (() => {
3077
3083
  const pct = Math.round(o * 1e3) / 10;
3078
3084
  return pct + "%";
3079
3085
  }
3086
+ function applyClipPathEffect(node, fx, ctx) {
3087
+ if (!fx || !fx.d && !fx.animate) return node;
3088
+ const clipId = genId(ctx, "clip");
3089
+ const pathChild = { type: "path", d: fx.d };
3090
+ if (fx.animate) pathChild.animate = { d: fx.animate };
3091
+ ctx.defs.push({ type: "clipPath", id: clipId, children: [pathChild] });
3092
+ node.clipPath = "url(#" + clipId + ")";
3093
+ return node;
3094
+ }
3080
3095
  function applyMaskedByEffect(node, fx, transformation, ctx) {
3081
3096
  if (!fx) return node;
3082
3097
  if (!fx.href) {
@@ -3512,6 +3527,9 @@ var PixodeskAnimatorReact = (() => {
3512
3527
  var RETIME_MATERIALISATION_MODE_INLINE_G = false;
3513
3528
  function asRetime(r) {
3514
3529
  var _a, _b;
3530
+ if (r.timeCrop) {
3531
+ console.warn("retime: `timeCrop` is not supported yet and will be ignored");
3532
+ }
3515
3533
  return { start: (_a = r.start) != null ? _a : 0, stretch: (_b = r.stretch) != null ? _b : 1 };
3516
3534
  }
3517
3535
  function concatRetime(child, parent) {
@@ -3641,97 +3659,6 @@ var PixodeskAnimatorReact = (() => {
3641
3659
  }
3642
3660
  }
3643
3661
  }
3644
- function applyTextAlongPathEffect(node, fx, _ctx) {
3645
- var _a;
3646
- if (!fx) return node;
3647
- const href = typeof fx.href === "string" && !fx.href.startsWith("#") ? "#" + fx.href : fx.href;
3648
- const textPath = {
3649
- type: "textPath",
3650
- href,
3651
- children: (_a = node.children) != null ? _a : []
3652
- };
3653
- if (fx.lengthAdjust !== void 0) textPath.lengthAdjust = fx.lengthAdjust;
3654
- if (fx.method !== void 0) textPath.method = fx.method;
3655
- if (fx.spacing !== void 0) textPath.spacing = fx.spacing;
3656
- applyAnimatableNumber(textPath, "startOffset", fx.startOffset);
3657
- applyAnimatableNumber(textPath, "textLength", fx.textLength);
3658
- node.children = [textPath];
3659
- return node;
3660
- }
3661
- function applyAnimatableNumber(node, attrName, raw) {
3662
- if (raw === void 0 || raw === null) return;
3663
- if (typeof raw === "number") {
3664
- node[attrName] = String(raw);
3665
- return;
3666
- }
3667
- if (typeof raw === "object" && raw !== null) {
3668
- const obj = raw;
3669
- if (Array.isArray(obj.keyframes)) {
3670
- const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
3671
- const animate = __spreadValues({}, prevAnimate || {});
3672
- const block = { keyframes: obj.keyframes };
3673
- if (obj.loop !== void 0) block.loop = obj.loop;
3674
- animate[attrName] = block;
3675
- node.animate = animate;
3676
- return;
3677
- }
3678
- if (typeof obj.value === "number") {
3679
- node[attrName] = String(obj.value);
3680
- return;
3681
- }
3682
- }
3683
- }
3684
- var jsonElementFactory = (type, props, children) => {
3685
- const node = { type };
3686
- for (const k in props) if (props[k] !== void 0) node[k] = props[k];
3687
- const arr = Array.isArray(children) ? children.filter((c) => c != null) : children != null ? [children] : [];
3688
- if (arr.length) node.children = arr;
3689
- return node;
3690
- };
3691
- function fmt(v, decimals) {
3692
- return Math.round(v) === v ? "" + Math.round(v) : v.toFixed(decimals);
3693
- }
3694
- function pack(nums, decimals) {
3695
- let s = "";
3696
- for (let i = 0; i < nums.length; i++) {
3697
- const str2 = fmt(nums[i], decimals);
3698
- if (i > 0 && str2.charCodeAt(0) !== 45) s += " ";
3699
- s += str2;
3700
- }
3701
- return s;
3702
- }
3703
- function apply(m, x, y) {
3704
- return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
3705
- }
3706
- var TOKEN_RE = /([MLCQZ])|(-?\d*\.?\d+(?:e[-+]?\d+)?)/gi;
3707
- function transformPathData(d, m, decimals = 2) {
3708
- const tokens = [];
3709
- let match;
3710
- TOKEN_RE.lastIndex = 0;
3711
- while ((match = TOKEN_RE.exec(d)) !== null) tokens.push(match[0]);
3712
- let out = "";
3713
- let i = 0;
3714
- const num2 = () => parseFloat(tokens[i++]);
3715
- while (i < tokens.length) {
3716
- const cmd = tokens[i++];
3717
- if (cmd === "M" || cmd === "L") {
3718
- const [x, y] = apply(m, num2(), num2());
3719
- out += cmd + pack([x, y], decimals);
3720
- } else if (cmd === "C") {
3721
- const [x1, y1] = apply(m, num2(), num2());
3722
- const [x2, y2] = apply(m, num2(), num2());
3723
- const [x, y] = apply(m, num2(), num2());
3724
- out += "C" + pack([x1, y1, x2, y2, x, y], decimals);
3725
- } else if (cmd === "Q") {
3726
- const [x1, y1] = apply(m, num2(), num2());
3727
- const [x, y] = apply(m, num2(), num2());
3728
- out += "Q" + pack([x1, y1, x, y], decimals);
3729
- } else if (cmd === "Z" || cmd === "z") {
3730
- out += "Z";
3731
- }
3732
- }
3733
- return out;
3734
- }
3735
3662
  function bezierToSvgPath(path) {
3736
3663
  var _a, _b, _c, _d;
3737
3664
  const v = path.v;
@@ -4019,6 +3946,8 @@ var PixodeskAnimatorReact = (() => {
4019
3946
  // Filter
4020
3947
  "filterUnits",
4021
3948
  "primitiveUnits",
3949
+ "tableValues",
3950
+ // feFuncR/G/B/A transfer table (type="table")
4022
3951
  "stdDeviation",
4023
3952
  "baseFrequency",
4024
3953
  "numOctaves",
@@ -4273,7 +4202,11 @@ var PixodeskAnimatorReact = (() => {
4273
4202
  };
4274
4203
  return {
4275
4204
  totalLength,
4205
+ closed,
4276
4206
  sampleAtDistance(dist) {
4207
+ if (closed && totalLength > 0 && (dist < 0 || dist > totalLength)) {
4208
+ return sampleOn((dist % totalLength + totalLength) % totalLength);
4209
+ }
4277
4210
  if (!closed && (dist < 0 || dist > totalLength)) {
4278
4211
  const edge = dist < 0 ? 0 : totalLength;
4279
4212
  const p = sampleOn(edge);
@@ -4284,6 +4217,167 @@ var PixodeskAnimatorReact = (() => {
4284
4217
  }
4285
4218
  };
4286
4219
  }
4220
+ var EXTEND_MARGIN_FRAC = 0.15;
4221
+ function numRange(v) {
4222
+ if (typeof v === "number") return { min: v, max: v };
4223
+ if (v && typeof v === "object") {
4224
+ const o = v;
4225
+ if (typeof o.value === "number") return { min: o.value, max: o.value };
4226
+ if (Array.isArray(o.keyframes) && o.keyframes.length) {
4227
+ const vals = o.keyframes.map((k) => Number(k.value) || 0);
4228
+ return { min: Math.min(...vals), max: Math.max(...vals) };
4229
+ }
4230
+ }
4231
+ return { min: 0, max: 0 };
4232
+ }
4233
+ function estimateTextAdvance(node) {
4234
+ let chars = 0, maxFont = 16;
4235
+ const walk = (el) => {
4236
+ var _a, _b;
4237
+ const fs = parseFloat(String((_a = el.fontSize) != null ? _a : "")) || 0;
4238
+ if (fs) maxFont = Math.max(maxFont, fs);
4239
+ const t = (_b = el.text) != null ? _b : el.textContent;
4240
+ if (typeof t === "string") chars += t.length;
4241
+ if (el.children) for (const c of el.children) walk(c);
4242
+ };
4243
+ walk(node);
4244
+ return chars * maxFont;
4245
+ }
4246
+ var r3 = (n) => Math.round(n * 1e3) / 1e3;
4247
+ function shiftAnimatable(v, by) {
4248
+ if (!by || v === void 0 || v === null) return v;
4249
+ if (typeof v === "number") return v + by;
4250
+ const o = v;
4251
+ if (typeof o.value === "number") return __spreadProps(__spreadValues({}, o), { value: o.value + by });
4252
+ if (Array.isArray(o.keyframes)) return __spreadProps(__spreadValues({}, o), { keyframes: o.keyframes.map((k) => __spreadProps(__spreadValues({}, k), { value: (Number(k.value) || 0) + by })) });
4253
+ return v;
4254
+ }
4255
+ function extendedPathForBrowser(pathD, opts) {
4256
+ var _a;
4257
+ if (opts.pathOverflow === "clip") return { d: pathD, startShift: 0 };
4258
+ const sampler = createPathSampler(pathD);
4259
+ if (!sampler || sampler.closed || sampler.totalLength <= 0) return { d: pathD, startShift: 0 };
4260
+ const L = sampler.totalLength;
4261
+ const margin = EXTEND_MARGIN_FRAC * L;
4262
+ const so = numRange(opts.startOffset);
4263
+ const runWidth = numRange(opts.textLength).max || ((_a = opts.advance) != null ? _a : 0);
4264
+ const startOverflow = Math.max(0, -so.min);
4265
+ const endOverflow = Math.max(0, so.max + runWidth - L);
4266
+ const startExt = startOverflow > 0 ? startOverflow + margin : 0;
4267
+ const endExt = endOverflow > 0 ? endOverflow + margin : 0;
4268
+ if (endExt <= 0 && startExt <= 0) return { d: pathD, startShift: 0 };
4269
+ const s = sampler.sampleAtDistance(0);
4270
+ const e = sampler.sampleAtDistance(L);
4271
+ let d = pathD;
4272
+ if (startExt > 0) {
4273
+ const sx = s.x - Math.cos(s.angle) * startExt, sy = s.y - Math.sin(s.angle) * startExt;
4274
+ const rest = pathD.replace(/^\s*[Mm]\s*-?[\d.]+[\s,]+-?[\d.]+/, "");
4275
+ d = "M" + r3(sx) + "," + r3(sy) + "L" + r3(s.x) + "," + r3(s.y) + rest;
4276
+ }
4277
+ if (endExt > 0) {
4278
+ const ex = e.x + Math.cos(e.angle) * endExt, ey = e.y + Math.sin(e.angle) * endExt;
4279
+ d += "L" + r3(ex) + "," + r3(ey);
4280
+ }
4281
+ return { d, startShift: startExt };
4282
+ }
4283
+ function applyTextPathEffect(node, fx, ctx) {
4284
+ var _a;
4285
+ if (!fx || typeof fx.path !== "string" || !fx.path) return node;
4286
+ const pathId = genId(ctx, "tpath");
4287
+ const { d, startShift } = extendedPathForBrowser(fx.path, {
4288
+ pathOverflow: fx.pathOverflow,
4289
+ startOffset: fx.startOffset,
4290
+ textLength: fx.textLength,
4291
+ advance: estimateTextAdvance(node)
4292
+ });
4293
+ ctx.defs.push({ type: "path", id: pathId, d });
4294
+ const textPath = {
4295
+ type: "textPath",
4296
+ href: "#" + pathId,
4297
+ children: (_a = node.children) != null ? _a : []
4298
+ };
4299
+ if (fx.lengthAdjust !== void 0) textPath.lengthAdjust = fx.lengthAdjust;
4300
+ if (fx.method !== void 0) textPath.method = fx.method;
4301
+ if (fx.spacing !== void 0) textPath.spacing = fx.spacing;
4302
+ applyAnimatableNumber(textPath, "startOffset", shiftAnimatable(fx.startOffset, startShift));
4303
+ applyAnimatableNumber(textPath, "textLength", fx.textLength);
4304
+ node.children = [textPath];
4305
+ return node;
4306
+ }
4307
+ function applyAnimatableNumber(node, attrName, raw) {
4308
+ if (raw === void 0 || raw === null) return;
4309
+ if (typeof raw === "number") {
4310
+ node[attrName] = String(raw);
4311
+ return;
4312
+ }
4313
+ if (typeof raw === "object" && raw !== null) {
4314
+ const obj = raw;
4315
+ if (Array.isArray(obj.keyframes)) {
4316
+ const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
4317
+ const animate = __spreadValues({}, prevAnimate || {});
4318
+ const block = { keyframes: obj.keyframes };
4319
+ if (obj.loop !== void 0) block.loop = obj.loop;
4320
+ animate[attrName] = block;
4321
+ node.animate = animate;
4322
+ return;
4323
+ }
4324
+ if (typeof obj.value === "number") {
4325
+ node[attrName] = String(obj.value);
4326
+ return;
4327
+ }
4328
+ }
4329
+ }
4330
+ var jsonElementFactory = (type, props, children) => {
4331
+ const node = { type };
4332
+ for (const k in props) if (props[k] !== void 0) node[k] = props[k];
4333
+ const arr = Array.isArray(children) ? children.filter((c) => c != null) : children != null ? [children] : [];
4334
+ if (arr.length) node.children = arr;
4335
+ return node;
4336
+ };
4337
+ function fmt(v, decimals) {
4338
+ return Math.round(v) === v ? "" + Math.round(v) : v.toFixed(decimals);
4339
+ }
4340
+ function pack(nums, decimals) {
4341
+ let s = "";
4342
+ for (let i = 0; i < nums.length; i++) {
4343
+ const str2 = fmt(nums[i], decimals);
4344
+ if (i > 0 && str2.charCodeAt(0) !== 45) s += " ";
4345
+ s += str2;
4346
+ }
4347
+ return s;
4348
+ }
4349
+ function apply(m, x, y) {
4350
+ return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
4351
+ }
4352
+ var TOKEN_RE = /([MLCQZ])|(-?\d*\.?\d+(?:e[-+]?\d+)?)/gi;
4353
+ function transformPathData(d, m, decimals = 2) {
4354
+ const tokens = [];
4355
+ let match;
4356
+ TOKEN_RE.lastIndex = 0;
4357
+ while ((match = TOKEN_RE.exec(d)) !== null) tokens.push(match[0]);
4358
+ let out = "";
4359
+ let i = 0;
4360
+ const num2 = () => parseFloat(tokens[i++]);
4361
+ while (i < tokens.length) {
4362
+ const cmd = tokens[i++];
4363
+ if (cmd === "M" || cmd === "L") {
4364
+ const [x, y] = apply(m, num2(), num2());
4365
+ out += cmd + pack([x, y], decimals);
4366
+ } else if (cmd === "C") {
4367
+ const [x1, y1] = apply(m, num2(), num2());
4368
+ const [x2, y2] = apply(m, num2(), num2());
4369
+ const [x, y] = apply(m, num2(), num2());
4370
+ out += "C" + pack([x1, y1, x2, y2, x, y], decimals);
4371
+ } else if (cmd === "Q") {
4372
+ const [x1, y1] = apply(m, num2(), num2());
4373
+ const [x, y] = apply(m, num2(), num2());
4374
+ out += "Q" + pack([x1, y1, x, y], decimals);
4375
+ } else if (cmd === "Z" || cmd === "z") {
4376
+ out += "Z";
4377
+ }
4378
+ }
4379
+ return out;
4380
+ }
4287
4381
  var DEFAULT_FONT_SIZE = 16;
4288
4382
  var TEXT_ATTR_KEYS = [
4289
4383
  "fontFamily",
@@ -4361,6 +4455,19 @@ var PixodeskAnimatorReact = (() => {
4361
4455
  const names = Object.keys(glyphs);
4362
4456
  return names.length === 1 ? glyphs[names[0]] : void 0;
4363
4457
  }
4458
+ var MISSING_GLYPH_ADVANCE_EM = 0.6;
4459
+ function missingGlyphBoxEm(advanceEm, ascentEm) {
4460
+ if (advanceEm <= 0 || ascentEm <= 0) return "";
4461
+ const inset = 0.08;
4462
+ const x0 = advanceEm * inset, x1 = advanceEm * (1 - inset);
4463
+ const y1 = -ascentEm * (1 - inset);
4464
+ const bw = x1 - x0, bh = -y1;
4465
+ const t = Math.max(1, Math.min(bw, bh) * 0.12);
4466
+ const outer = "M" + x0 + " 0L" + x1 + " 0L" + x1 + " " + y1 + "L" + x0 + " " + y1 + "Z";
4467
+ if (bw <= 2 * t || bh <= 2 * t) return outer;
4468
+ const ix0 = x0 + t, ix1 = x1 - t, iyb = -t, iyt = y1 + t;
4469
+ return outer + "M" + ix0 + " " + iyb + "L" + ix0 + " " + iyt + "L" + ix1 + " " + iyt + "L" + ix1 + " " + iyb + "Z";
4470
+ }
4364
4471
  function materialiseGlyphTextHorizontal(node, opts) {
4365
4472
  var _a, _b, _c, _d;
4366
4473
  const { glyphs, create = jsonElementFactory, warnings } = opts;
@@ -4370,15 +4477,26 @@ var PixodeskAnimatorReact = (() => {
4370
4477
  const lines = [{ start: pen.x, end: pen.x }];
4371
4478
  let line = 0;
4372
4479
  const renderChars = (content, s) => {
4480
+ var _a2;
4373
4481
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
4374
- if (!gf) return;
4375
- const scale = s.fontSize / gf.unitsPerEm;
4482
+ const upm = (gf == null ? void 0 : gf.unitsPerEm) || 1e3;
4483
+ const scale = s.fontSize / upm;
4484
+ const ascentEm = (_a2 = gf == null ? void 0 : gf.ascent) != null ? _a2 : 0.9 * upm;
4376
4485
  const paint = paintOf(s);
4377
4486
  for (let i = 0; i < content.length; i++) {
4378
4487
  const ch = content.charAt(i);
4379
- const g = gf.glyphs[ch];
4380
- if (g && g.d) placements.push({ glyphD: g.d, m: [scale, 0, 0, scale, pen.x, pen.y], paint, line, x: pen.x, y: pen.y, scale });
4381
- pen.x += (g ? g.width : 0) * scale + s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
4488
+ const g = gf == null ? void 0 : gf.glyphs[ch];
4489
+ if (g && g.d) {
4490
+ placements.push({ glyphD: g.d, m: [scale, 0, 0, scale, pen.x, pen.y], paint, line, x: pen.x, y: pen.y, scale });
4491
+ pen.x += g.width * scale;
4492
+ } else if (/\S/.test(ch)) {
4493
+ const advEm = g && g.width > 0 ? g.width : MISSING_GLYPH_ADVANCE_EM * upm;
4494
+ placements.push({ glyphD: missingGlyphBoxEm(advEm, ascentEm), m: [scale, 0, 0, scale, pen.x, pen.y], paint, line, x: pen.x, y: pen.y, scale });
4495
+ pen.x += advEm * scale;
4496
+ } else {
4497
+ pen.x += (g ? g.width : 0) * scale;
4498
+ }
4499
+ pen.x += s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
4382
4500
  lines[line].end = pen.x;
4383
4501
  }
4384
4502
  };
@@ -4417,20 +4535,31 @@ var PixodeskAnimatorReact = (() => {
4417
4535
  const cells = [];
4418
4536
  let adv = 0;
4419
4537
  const walk = (el, parentStyle) => {
4420
- var _a, _b;
4538
+ var _a, _b, _c;
4421
4539
  const s = resolveStyle(el, parentStyle);
4422
4540
  const content = (_a = str(el[TEXT_ATTR])) != null ? _a : str(el[TEXT_CONTENT_ATTR]);
4423
4541
  if (content && !((_b = el.children) == null ? void 0 : _b.length)) {
4424
4542
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
4425
4543
  if (gf) {
4426
4544
  const scale = s.fontSize / gf.unitsPerEm;
4545
+ const ascentEm = (_c = gf.ascent) != null ? _c : 0.9 * gf.unitsPerEm;
4427
4546
  const paint = paintOf(s);
4428
4547
  for (let i = 0; i < content.length; i++) {
4429
4548
  const ch = content.charAt(i);
4430
4549
  const g = gf.glyphs[ch];
4431
- const glyphAdv = (g ? g.width : 0) * scale;
4432
- if (g && g.d) cells.push({ glyphD: g.d, widthEm: g.width, scale, paint, midBase: adv + glyphAdv / 2 });
4433
- adv += glyphAdv + s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
4550
+ if (g && g.d) {
4551
+ const glyphAdv = g.width * scale;
4552
+ cells.push({ glyphD: g.d, widthEm: g.width, scale, paint, midBase: adv + glyphAdv / 2 });
4553
+ adv += glyphAdv;
4554
+ } else if (/\S/.test(ch)) {
4555
+ const wEm = g && g.width > 0 ? g.width : MISSING_GLYPH_ADVANCE_EM * gf.unitsPerEm;
4556
+ const boxAdv = wEm * scale;
4557
+ cells.push({ glyphD: missingGlyphBoxEm(wEm, ascentEm), widthEm: wEm, scale, paint, midBase: adv + boxAdv / 2 });
4558
+ adv += boxAdv;
4559
+ } else {
4560
+ adv += (g ? g.width : 0) * scale;
4561
+ }
4562
+ adv += s.letterSpacing + (ch === " " ? s.wordSpacing : 0);
4434
4563
  }
4435
4564
  }
4436
4565
  }
@@ -4439,12 +4568,19 @@ var PixodeskAnimatorReact = (() => {
4439
4568
  walk(node, rootStyleOf(node));
4440
4569
  return { cells, width: adv };
4441
4570
  }
4442
- function alongAffine(sampler, dist, scale, widthEm) {
4571
+ function alongAffine(sampler, dist, scale, widthEm, perp = 0) {
4443
4572
  const { x, y, angle } = sampler.sampleAtDistance(dist);
4444
4573
  const cos = Math.cos(angle), sin = Math.sin(angle), hw = widthEm / 2;
4445
- return [scale * cos, scale * sin, -scale * sin, scale * cos, x - scale * cos * hw, y - scale * sin * hw];
4574
+ return [scale * cos, scale * sin, -scale * sin, scale * cos, x - scale * cos * hw - perp * sin, y - scale * sin * hw + perp * cos];
4446
4575
  }
4447
- function materialiseGlyphTextAlongPath(node, pathD, startOffset, opts, textLength) {
4576
+ function alongPathNodeOffsets(node) {
4577
+ var _a, _b, _c;
4578
+ return {
4579
+ along: ((_a = parseLen(node.x)) != null ? _a : 0) + ((_b = parseLen(node.dx)) != null ? _b : 0),
4580
+ perp: (_c = parseLen(node.dy)) != null ? _c : 0
4581
+ };
4582
+ }
4583
+ function materialiseGlyphTextAlongPath(node, pathD, startOffset, opts, textLength, pathOverflow) {
4448
4584
  var _a;
4449
4585
  const { glyphs, create = jsonElementFactory, warnings } = opts;
4450
4586
  const sampler = pathD ? createPathSampler(pathD) : null;
@@ -4455,19 +4591,25 @@ var PixodeskAnimatorReact = (() => {
4455
4591
  const soleFont = soleFontOf(glyphs);
4456
4592
  const { cells, width } = collectAlongPathCells(node, glyphs, soleFont, warnings);
4457
4593
  if (!cells.length) return toGroup(node, [], create);
4594
+ const { along: alongOffset, perp } = alongPathNodeOffsets(node);
4458
4595
  if (textLength && textLength > 0 && width > 0) {
4459
4596
  const k = textLength / width;
4460
4597
  for (const c of cells) c.midBase *= k;
4461
4598
  }
4599
+ const isClip = pathOverflow === "clip" && !sampler.closed;
4462
4600
  const so = readAnimatable(startOffset);
4463
4601
  if (so.kind === "animated" && so.keyframes.length >= 2) {
4464
- return toGroup(node, buildAnimatedAlongPath(cells, sampler, so.keyframes, so.loop, create), create);
4465
- }
4466
- const base = so.kind === "animated" ? Number((_a = so.keyframes[0]) == null ? void 0 : _a.value) || 0 : so.kind === "static" ? Number(so.value) || 0 : 0;
4467
- const placements = cells.map((c) => ({
4602
+ return toGroup(node, buildAnimatedAlongPath(cells, sampler, so.keyframes, so.loop, create, isClip, alongOffset, perp), create);
4603
+ }
4604
+ const base = alongOffset + (so.kind === "animated" ? Number((_a = so.keyframes[0]) == null ? void 0 : _a.value) || 0 : so.kind === "static" ? Number(so.value) || 0 : 0);
4605
+ const placeCells = isClip ? cells.filter((c) => {
4606
+ const d = base + c.midBase;
4607
+ return d >= 0 && d <= sampler.totalLength;
4608
+ }) : cells;
4609
+ const placements = placeCells.map((c) => ({
4468
4610
  glyphD: c.glyphD,
4469
4611
  paint: c.paint,
4470
- m: alongAffine(sampler, base + c.midBase, c.scale, c.widthEm)
4612
+ m: alongAffine(sampler, base + c.midBase, c.scale, c.widthEm, perp)
4471
4613
  }));
4472
4614
  return toGroup(node, buildPaths(placements, create, warnings), create);
4473
4615
  }
@@ -4477,23 +4619,25 @@ var PixodeskAnimatorReact = (() => {
4477
4619
  const f = __pow(10, n);
4478
4620
  return Math.round(v * f) / f;
4479
4621
  }
4480
- function buildAnimatedAlongPath(cells, sampler, sokfs, loop, create) {
4622
+ function buildAnimatedAlongPath(cells, sampler, sokfs, loop, create, isClip, alongOffset = 0, perp = 0) {
4481
4623
  const step = Math.max(sampler.totalLength / ALONG_PATH_MAX_STEPS, 0.5);
4482
4624
  const timeOf = (kf) => Number(kf.time) || 0;
4483
- const offOf = (kf) => Number(kf.value) || 0;
4625
+ const offOf = (kf) => alongOffset + (Number(kf.value) || 0);
4626
+ const onPath = (dist) => dist >= 0 && dist <= sampler.totalLength;
4484
4627
  const out = [];
4485
4628
  for (const c of cells) {
4486
4629
  const centred = [c.scale, 0, 0, c.scale, -c.scale * (c.widthEm / 2), 0];
4487
4630
  const d = transformPathData(c.glyphD, centred);
4488
4631
  const sampleKf = (dist, time) => {
4489
4632
  const { x, y, angle } = sampler.sampleAtDistance(dist);
4633
+ const cos = Math.cos(angle), sin = Math.sin(angle);
4490
4634
  return {
4491
4635
  time,
4492
4636
  value: {
4493
4637
  [
4494
4638
  "translate"
4495
4639
  /* Translate */
4496
- ]: [roundN(x, 3), roundN(y, 3)],
4640
+ ]: [roundN(x - perp * sin, 3), roundN(y + perp * cos, 3)],
4497
4641
  [
4498
4642
  "rotate"
4499
4643
  /* Rotate */
@@ -4501,19 +4645,31 @@ var PixodeskAnimatorReact = (() => {
4501
4645
  }
4502
4646
  };
4503
4647
  };
4504
- const kfs = [sampleKf(offOf(sokfs[0]) + c.midBase, timeOf(sokfs[0]))];
4648
+ const kfs = [];
4649
+ const opKfs = [];
4650
+ const pushKf = (dist, time) => {
4651
+ kfs.push(sampleKf(dist, time));
4652
+ if (isClip) opKfs.push({ time, value: onPath(dist) ? 1 : 0 });
4653
+ };
4654
+ pushKf(offOf(sokfs[0]) + c.midBase, timeOf(sokfs[0]));
4505
4655
  for (let k = 1; k < sokfs.length; k++) {
4506
4656
  const t0 = timeOf(sokfs[k - 1]), t1 = timeOf(sokfs[k]);
4507
4657
  const o0 = offOf(sokfs[k - 1]), o1 = offOf(sokfs[k]);
4508
4658
  const n = Math.min(ALONG_PATH_MAX_STEPS_PER_SEGMENT, Math.max(1, Math.ceil(Math.abs(o1 - o0) / step)));
4509
4659
  for (let s = 1; s <= n; s++) {
4510
4660
  const f = s / n;
4511
- kfs.push(sampleKf(o0 + f * (o1 - o0) + c.midBase, t0 + f * (t1 - t0)));
4661
+ pushKf(o0 + f * (o1 - o0) + c.midBase, t0 + f * (t1 - t0));
4512
4662
  }
4513
4663
  }
4514
4664
  const transform = { keyframes: kfs };
4515
4665
  if (loop !== void 0) transform.loop = loop;
4516
- out.push(create("path", __spreadProps(__spreadValues({ d }, paintProps(c.paint)), { animate: { transform } }), []));
4666
+ const animate = { transform };
4667
+ if (isClip && opKfs.some((k) => k.value === 0)) {
4668
+ const op = { keyframes: opKfs };
4669
+ if (loop !== void 0) op.loop = loop;
4670
+ animate.opacity = op;
4671
+ }
4672
+ out.push(create("path", __spreadProps(__spreadValues({ d }, paintProps(c.paint)), { animate }), []));
4517
4673
  }
4518
4674
  return out;
4519
4675
  }
@@ -4564,12 +4720,12 @@ var PixodeskAnimatorReact = (() => {
4564
4720
  }
4565
4721
  return materialiseGlyphTextHorizontal(node, { glyphs: ctx.glyphs, warnings: ctx.warnings });
4566
4722
  }
4567
- function applyTextGlyphsAlongPath(node, ctx, pathD, startOffset, textLength) {
4723
+ function applyTextGlyphsAlongPath(node, ctx, pathD, startOffset, textLength, pathOverflow) {
4568
4724
  if (!ctx.glyphs) {
4569
4725
  ctx.warnings.push("textGlyphs: no definitions.glyphs");
4570
4726
  return null;
4571
4727
  }
4572
- return materialiseGlyphTextAlongPath(node, pathD, startOffset, { glyphs: ctx.glyphs, warnings: ctx.warnings }, textLength);
4728
+ return materialiseGlyphTextAlongPath(node, pathD, startOffset, { glyphs: ctx.glyphs, warnings: ctx.warnings }, textLength, pathOverflow);
4573
4729
  }
4574
4730
  function getKfTranslate(kf) {
4575
4731
  var _a;
@@ -4748,9 +4904,18 @@ var PixodeskAnimatorReact = (() => {
4748
4904
  if (pv === void 0 && nv === void 0) continue;
4749
4905
  value[k] = interpolatePart(pv, nv, p);
4750
4906
  }
4751
- if (rotateDegFromAutoOrient !== void 0) value.rotate = rotateDegFromAutoOrient;
4907
+ if (rotateDegFromAutoOrient !== void 0) {
4908
+ value.rotate = rotateDegFromAutoOrient + explicitRotateAt(prevV, nextV, p);
4909
+ }
4752
4910
  return value;
4753
4911
  }
4912
+ function explicitRotateAt(prevV, nextV, p) {
4913
+ const pv = typeof (prevV == null ? void 0 : prevV.rotate) === "number" ? prevV.rotate : void 0;
4914
+ const nv = typeof (nextV == null ? void 0 : nextV.rotate) === "number" ? nextV.rotate : void 0;
4915
+ if (pv === void 0 && nv === void 0) return 0;
4916
+ const r = interpolatePart(pv, nv, p);
4917
+ return typeof r === "number" ? r : 0;
4918
+ }
4754
4919
  function derivAngleForFirstKf(kf0, kf1) {
4755
4920
  const p0 = getKfTranslate(kf0);
4756
4921
  const p1 = getKfTranslate(kf1);
@@ -4771,10 +4936,12 @@ var PixodeskAnimatorReact = (() => {
4771
4936
  const lastV = (_a = lastKf.v) != null ? _a : lastKf.value;
4772
4937
  const prevExit = lastV == null ? void 0 : lastV.rotate;
4773
4938
  if (typeof prevExit !== "number") return;
4939
+ const boundaryV = getKfValueParts(prevKf);
4940
+ const prevExitTangent = prevExit - explicitRotateAt(boundaryV, boundaryV, 0);
4774
4941
  const seg = getSegmentCache(prevKf, nextKf, prevPos, nextPos);
4775
4942
  const tanAtStart = bezier2D_derivativeAt(seg.P0, seg.P1, seg.P2, seg.P3, 0);
4776
4943
  const nextEntry = Math.atan2(tanAtStart[1], tanAtStart[0]) * 180 / Math.PI;
4777
- const delta = wrappedAngleDelta(nextEntry, prevExit);
4944
+ const delta = wrappedAngleDelta(nextEntry, prevExitTangent);
4778
4945
  if (Math.abs(delta) <= rotationTol) return;
4779
4946
  const dupValue = buildOutKfValue(
4780
4947
  getKfValueParts(prevKf),
@@ -5967,18 +6134,17 @@ var PixodeskAnimatorReact = (() => {
5967
6134
  const originalId = typeof node.id === "string" ? node.id : void 0;
5968
6135
  const innerIdForContentRef = originalId ? ctx.contentRefInnerIds.get(originalId) : void 0;
5969
6136
  if (!fx && !innerIdForContentRef) return node;
5970
- const { transformation, repeater, maskedBy, trimPath, clone: cloneFx, fillGradient, strokeGradient, textAlongPath, text } = fx != null ? fx : {};
6137
+ const { transformation, repeater, maskedBy, clipPath, trimPath, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
5971
6138
  const isCombinedShape = fx == null ? void 0 : fx.isCombinedShape;
5972
6139
  if (fx) delete node.effects;
5973
6140
  let n = node;
5974
6141
  let consumedByGlyphs = false;
5975
6142
  if (text == null ? void 0 : text.useGlyphs) {
5976
- if (textAlongPath) {
5977
- const pathNode = typeof textAlongPath.href === "string" ? ctx.idMap.get(textAlongPath.href) : void 0;
5978
- const pathD = pathNode && typeof pathNode.d === "string" ? pathNode.d : void 0;
5979
- const rawTL = textAlongPath.textLength;
6143
+ if (textPath) {
6144
+ const pathD = typeof textPath.path === "string" ? textPath.path : void 0;
6145
+ const rawTL = textPath.textLength;
5980
6146
  const textLength = typeof rawTL === "number" ? rawTL : rawTL && typeof rawTL === "object" ? typeof rawTL.value === "number" ? rawTL.value : Array.isArray(rawTL.keyframes) && rawTL.keyframes.length ? Number((_a = rawTL.keyframes[0]) == null ? void 0 : _a.value) : void 0 : void 0;
5981
- const glyphed = applyTextGlyphsAlongPath(n, ctx, pathD, textAlongPath.startOffset, textLength);
6147
+ const glyphed = applyTextGlyphsAlongPath(n, ctx, pathD, textPath.startOffset, textLength, textPath.pathOverflow);
5982
6148
  if (glyphed) {
5983
6149
  n = glyphed;
5984
6150
  consumedByGlyphs = true;
@@ -5988,12 +6154,13 @@ var PixodeskAnimatorReact = (() => {
5988
6154
  consumedByGlyphs = true;
5989
6155
  }
5990
6156
  }
5991
- if (!consumedByGlyphs) n = applyTextAlongPathEffect(n, textAlongPath, ctx);
6157
+ if (!consumedByGlyphs) n = applyTextPathEffect(n, textPath, ctx);
5992
6158
  n = applyFillGradientEffect(n, fillGradient, ctx);
5993
6159
  n = applyStrokeGradientEffect(n, strokeGradient, ctx);
5994
6160
  n = applyTrimPathEffect(n, trimPath, isCombinedShape, ctx);
5995
6161
  n = applyRepeaterEffect(n, repeater, ctx);
5996
6162
  n = applyMaskedByEffect(n, maskedBy, transformation, ctx);
6163
+ n = applyClipPathEffect(n, clipPath, ctx);
5997
6164
  if (innerIdForContentRef) {
5998
6165
  applyRefHref(n, cloneFx, ctx);
5999
6166
  n = splitForContentRef(n, transformation, originalId, innerIdForContentRef, ctx);
@@ -6316,6 +6483,8 @@ var PixodeskAnimatorReact = (() => {
6316
6483
  function renderNode(node, defs) {
6317
6484
  if (!node) return null;
6318
6485
  const _a = node, { type, children, style } = _a, props = __objRest(_a, ["type", "children", "style"]);
6486
+ const feFuncType = props.funcType;
6487
+ if (feFuncType !== void 0) delete props.funcType;
6319
6488
  const nodeDefs = getDefs(node) || defs;
6320
6489
  const resolvedStyle = resolveStyle2(style, nodeDefs);
6321
6490
  let childElements;
@@ -6328,13 +6497,15 @@ var PixodeskAnimatorReact = (() => {
6328
6497
  }
6329
6498
  }
6330
6499
  }
6331
- return createElement(
6500
+ const element = createElement(
6332
6501
  type || "g",
6333
6502
  getNormalizedProps(props),
6334
6503
  resolvedStyle,
6335
6504
  childElements,
6336
6505
  props[TEXT_ATTR] || props[TEXT_CONTENT_ATTR]
6337
6506
  );
6507
+ if (element && feFuncType !== void 0) element.setAttribute("type", feFuncType);
6508
+ return element;
6338
6509
  }
6339
6510
  function setupAnimationTriggers(api, config) {
6340
6511
  const { startOn, outAction = "continue", scrollIntoViewThreshold = 0.5 } = config;
@@ -6343,7 +6514,12 @@ var PixodeskAnimatorReact = (() => {
6343
6514
  console.warn("setupAnimationTriggers: No root element found for animation.");
6344
6515
  return api;
6345
6516
  }
6517
+ let reversed = false;
6346
6518
  const start = () => {
6519
+ if (reversed) {
6520
+ reversed = false;
6521
+ api.setPlaybackRate(1);
6522
+ }
6347
6523
  api.play();
6348
6524
  };
6349
6525
  const handleEndAction = () => {
@@ -6355,6 +6531,8 @@ var PixodeskAnimatorReact = (() => {
6355
6531
  api.cancel();
6356
6532
  break;
6357
6533
  case "reverse":
6534
+ reversed = true;
6535
+ api.setPlaybackRate(-1);
6358
6536
  api.play();
6359
6537
  break;
6360
6538
  case "continue":
@@ -6415,6 +6593,7 @@ var PixodeskAnimatorReact = (() => {
6415
6593
  return "#" + id;
6416
6594
  }
6417
6595
  function createBasicFrameLoopAnimator(doc, adapter, callbacks) {
6596
+ var _a;
6418
6597
  const config = getAnimatorConfig(doc) || {};
6419
6598
  const bindings = getNormalisedBindings(doc, PxAnimatorEngine.frames);
6420
6599
  const _iterations = config.iterations;
@@ -6425,18 +6604,24 @@ var PixodeskAnimatorReact = (() => {
6425
6604
  const duration = +(config.duration || DEFAULT_DURATION_MS);
6426
6605
  const totalDuration = duration && iterations ? duration * (iterations === Infinity ? Infinity : iterations) : duration ? (iterations != null ? iterations : 1) * duration : 0;
6427
6606
  const direction = config.direction || "normal";
6607
+ const fill = (_a = config.fill) != null ? _a : "forwards";
6608
+ const fillsForwards = fill === "forwards" || fill === "both";
6609
+ const fillsBackwards = fill === "backwards" || fill === "both";
6428
6610
  let timerId = null;
6429
6611
  let playing = false;
6430
- let timeBeforeLastStartMs = config.delay || 0;
6612
+ let timeBeforeLastStartMs = -(config.delay || 0);
6431
6613
  let lastStartedTs = 0;
6432
6614
  let playbackRate = 1;
6433
6615
  let finishCalled = false;
6434
6616
  const frameRate = config.frameRate;
6435
6617
  const minFrameIntervalMs = frameRate && frameRate > 0 ? 1e3 / frameRate : 0;
6436
6618
  let lastRenderTs = 0;
6437
- const getAnimCurrentTime = () => {
6619
+ const getRawAnimTime = () => {
6438
6620
  const runningElapsed = lastStartedTs ? (Date.now() - lastStartedTs) * playbackRate : 0;
6439
- let time = timeBeforeLastStartMs + runningElapsed;
6621
+ return timeBeforeLastStartMs + runningElapsed;
6622
+ };
6623
+ const getAnimCurrentTime = () => {
6624
+ let time = getRawAnimTime();
6440
6625
  if (Number.isFinite(totalDuration) && time > totalDuration) time = totalDuration;
6441
6626
  if (time < 0) time = 0;
6442
6627
  return time;
@@ -6481,40 +6666,59 @@ var PixodeskAnimatorReact = (() => {
6481
6666
  }
6482
6667
  }
6483
6668
  const tick = () => {
6484
- var _a;
6669
+ var _a2, _b;
6485
6670
  if (!adapter.isConnected()) {
6486
6671
  pauseAnim();
6487
6672
  return;
6488
6673
  }
6489
6674
  const currentTime = getAnimCurrentTime();
6490
- if (minFrameIntervalMs > 0) {
6491
- const now = Date.now();
6492
- if (lastRenderTs && now - lastRenderTs < minFrameIntervalMs) {
6493
- return;
6494
- }
6495
- lastRenderTs = now;
6496
- }
6497
- if (duration <= 0) {
6675
+ const rawTime = getRawAnimTime();
6676
+ if (rawTime < 0 && playbackRate > 0) {
6677
+ if (fillsBackwards) renderFrame(0);
6678
+ return;
6498
6679
  }
6499
- if (totalDuration && Number.isFinite(totalDuration) && currentTime >= totalDuration) {
6500
- renderFrame(totalDuration);
6680
+ if (playbackRate < 0 && rawTime <= 0) {
6681
+ pauseAnim();
6682
+ renderFrame(0);
6501
6683
  if (!finishCalled) {
6502
6684
  finishCalled = true;
6503
- (_a = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a.call(callbacks);
6685
+ (_a2 = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
6504
6686
  }
6687
+ return;
6688
+ }
6689
+ if (playbackRate > 0 && totalDuration && Number.isFinite(totalDuration) && currentTime >= totalDuration) {
6505
6690
  pauseAnim();
6691
+ renderFrame(fillsForwards ? totalDuration : 0);
6692
+ if (!finishCalled) {
6693
+ finishCalled = true;
6694
+ (_b = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _b.call(callbacks);
6695
+ }
6506
6696
  return;
6507
6697
  }
6698
+ if (minFrameIntervalMs > 0) {
6699
+ const now = Date.now();
6700
+ if (lastRenderTs && now - lastRenderTs < minFrameIntervalMs) {
6701
+ return;
6702
+ }
6703
+ lastRenderTs = now;
6704
+ }
6508
6705
  renderFrame(currentTime);
6509
6706
  };
6510
6707
  if (config.delay) {
6511
- renderFrame(getAnimCurrentTime());
6708
+ if (config.delay < 0) {
6709
+ renderFrame(getAnimCurrentTime());
6710
+ } else if (fillsBackwards) {
6711
+ renderFrame(0);
6712
+ }
6512
6713
  }
6513
6714
  const _isPlaying = () => {
6514
6715
  if (!playing) return false;
6515
6716
  if (!adapter.isConnected()) {
6516
6717
  return false;
6517
6718
  }
6719
+ if (playbackRate < 0) {
6720
+ return getRawAnimTime() > 0;
6721
+ }
6518
6722
  if (Number.isFinite(totalDuration) && getAnimCurrentTime() >= totalDuration) return false;
6519
6723
  return true;
6520
6724
  };
@@ -6534,37 +6738,53 @@ var PixodeskAnimatorReact = (() => {
6534
6738
  };
6535
6739
  const startAnim = () => {
6536
6740
  if (playing) return;
6537
- if (Number.isFinite(totalDuration) && timeBeforeLastStartMs >= totalDuration) {
6538
- timeBeforeLastStartMs = 0;
6539
- finishCalled = false;
6741
+ if (playbackRate >= 0) {
6742
+ if (Number.isFinite(totalDuration) && timeBeforeLastStartMs >= totalDuration) {
6743
+ timeBeforeLastStartMs = 0;
6744
+ }
6745
+ } else {
6746
+ if (timeBeforeLastStartMs <= 0) {
6747
+ if (!Number.isFinite(totalDuration)) {
6748
+ console.warn("play: cannot start reverse playback of an infinite animation from time 0");
6749
+ return;
6750
+ }
6751
+ timeBeforeLastStartMs = totalDuration;
6752
+ }
6540
6753
  }
6754
+ finishCalled = false;
6541
6755
  playing = true;
6542
6756
  lastStartedTs = Date.now();
6543
6757
  loopAnim(true);
6544
6758
  };
6545
6759
  const pauseAnim = () => {
6546
6760
  if (!playing) return;
6547
- timeBeforeLastStartMs = getAnimCurrentTime();
6761
+ let raw = getRawAnimTime();
6762
+ if (Number.isFinite(totalDuration) && raw > totalDuration) raw = totalDuration;
6763
+ timeBeforeLastStartMs = raw;
6548
6764
  lastStartedTs = 0;
6549
6765
  playing = false;
6550
6766
  if (timerId !== null) {
6551
6767
  cancelAnimationFrame(timerId);
6552
6768
  timerId = null;
6553
6769
  }
6554
- renderFrame(timeBeforeLastStartMs);
6770
+ if (raw >= 0) {
6771
+ renderFrame(raw);
6772
+ } else if (fillsBackwards) {
6773
+ renderFrame(0);
6774
+ }
6555
6775
  };
6556
6776
  const cancelAnim = () => {
6557
- var _a;
6777
+ var _a2;
6558
6778
  pauseAnim();
6559
6779
  timeBeforeLastStartMs = 0;
6560
6780
  lastStartedTs = 0;
6561
6781
  playing = false;
6562
6782
  finishCalled = false;
6563
6783
  renderFrame(timeBeforeLastStartMs);
6564
- (_a = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a.call(callbacks);
6784
+ (_a2 = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a2.call(callbacks);
6565
6785
  };
6566
6786
  const finishAnim = (callOnFinish = true) => {
6567
- var _a;
6787
+ var _a2;
6568
6788
  if (Number.isFinite(totalDuration)) {
6569
6789
  timeBeforeLastStartMs = totalDuration;
6570
6790
  } else {
@@ -6572,10 +6792,14 @@ var PixodeskAnimatorReact = (() => {
6572
6792
  }
6573
6793
  lastStartedTs = 0;
6574
6794
  playing = false;
6575
- renderFrame(timeBeforeLastStartMs);
6795
+ if (timerId !== null) {
6796
+ cancelAnimationFrame(timerId);
6797
+ timerId = null;
6798
+ }
6799
+ renderFrame(fillsForwards ? timeBeforeLastStartMs : 0);
6576
6800
  if (callOnFinish && !finishCalled) {
6577
6801
  finishCalled = true;
6578
- (_a = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a.call(callbacks);
6802
+ (_a2 = callbacks == null ? void 0 : callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
6579
6803
  }
6580
6804
  };
6581
6805
  const api = {
@@ -6585,14 +6809,14 @@ var PixodeskAnimatorReact = (() => {
6585
6809
  return _isPlaying();
6586
6810
  },
6587
6811
  "play": () => {
6588
- var _a;
6812
+ var _a2;
6589
6813
  startAnim();
6590
- (_a = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a.call(callbacks);
6814
+ (_a2 = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a2.call(callbacks);
6591
6815
  },
6592
6816
  "pause": () => {
6593
- var _a;
6817
+ var _a2;
6594
6818
  pauseAnim();
6595
- (_a = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a.call(callbacks);
6819
+ (_a2 = callbacks == null ? void 0 : callbacks.onPause) == null ? void 0 : _a2.call(callbacks);
6596
6820
  },
6597
6821
  "cancel": () => {
6598
6822
  cancelAnim();
@@ -6605,7 +6829,9 @@ var PixodeskAnimatorReact = (() => {
6605
6829
  console.warn("setPlaybackRate: rate must be finite and non-zero");
6606
6830
  return;
6607
6831
  }
6608
- const current = getAnimCurrentTime();
6832
+ let current = getRawAnimTime();
6833
+ if (Number.isFinite(totalDuration) && current > totalDuration) current = totalDuration;
6834
+ if (rate < 0 !== playbackRate < 0) finishCalled = false;
6609
6835
  playbackRate = rate;
6610
6836
  timeBeforeLastStartMs = current;
6611
6837
  if (playing) lastStartedTs = Date.now();
@@ -6618,10 +6844,13 @@ var PixodeskAnimatorReact = (() => {
6618
6844
  if (Number.isFinite(totalDuration) && newTime > totalDuration) newTime = totalDuration;
6619
6845
  timeBeforeLastStartMs = newTime;
6620
6846
  if (playing) lastStartedTs = Date.now();
6847
+ if (!Number.isFinite(totalDuration) || newTime < totalDuration) finishCalled = false;
6621
6848
  renderFrame(getAnimCurrentTime());
6622
6849
  },
6623
6850
  "destroy": () => {
6851
+ var _a2;
6624
6852
  api.cancel();
6853
+ (_a2 = callbacks == null ? void 0 : callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
6625
6854
  }
6626
6855
  };
6627
6856
  return api;
@@ -6784,6 +7013,8 @@ var PixodeskAnimatorReact = (() => {
6784
7013
  if (typeof _iterations === "number") iterations = _iterations;
6785
7014
  if (_iterations === "infinite") iterations = Infinity;
6786
7015
  const unsupportedSet = /* @__PURE__ */ new Set();
7016
+ let finishNotified = false;
7017
+ let removeCalled = false;
6787
7018
  if (!(bindings == null ? void 0 : bindings.length)) {
6788
7019
  console.warn("createWebApiAnimator: No animation bindings defined");
6789
7020
  }
@@ -6800,7 +7031,11 @@ var PixodeskAnimatorReact = (() => {
6800
7031
  }
6801
7032
  const keyframesMap = convertToWebApiKeyframes(animDef, unsupportedSet, config);
6802
7033
  const positiveDelay = config.delay && config.delay > 0 ? config.delay : void 0;
6803
- const seekPosition = config.delay && config.delay < 0 && config.duration ? -config.delay % config.duration : void 0;
7034
+ let seekPosition;
7035
+ if (config.delay && config.delay < 0 && config.duration) {
7036
+ const rawSeek = -config.delay;
7037
+ seekPosition = iterations === Infinity ? rawSeek % config.duration : Math.min(rawSeek, config.duration * (iterations != null ? iterations : 1));
7038
+ }
6804
7039
  const effectOptions = {
6805
7040
  duration: config.duration,
6806
7041
  delay: positiveDelay,
@@ -6821,13 +7056,17 @@ var PixodeskAnimatorReact = (() => {
6821
7056
  const anim = new Animation(effect, document.timeline);
6822
7057
  if (callbacks == null ? void 0 : callbacks.onFinish) anim.onfinish = () => {
6823
7058
  var _a2;
6824
- return (_a2 = callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
7059
+ if (finishNotified) return;
7060
+ finishNotified = true;
7061
+ (_a2 = callbacks.onFinish) == null ? void 0 : _a2.call(callbacks);
6825
7062
  };
6826
7063
  if (callbacks == null ? void 0 : callbacks.onRemove) anim.onremove = () => {
6827
7064
  var _a2;
6828
- return (_a2 = callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
7065
+ if (removeCalled) return;
7066
+ removeCalled = true;
7067
+ (_a2 = callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
6829
7068
  };
6830
- if (seekPosition) {
7069
+ if (seekPosition !== void 0) {
6831
7070
  anim.currentTime = seekPosition;
6832
7071
  }
6833
7072
  animations.push(anim);
@@ -6851,6 +7090,7 @@ var PixodeskAnimatorReact = (() => {
6851
7090
  },
6852
7091
  "play": () => {
6853
7092
  var _a2;
7093
+ finishNotified = false;
6854
7094
  animations.forEach((a) => a.play());
6855
7095
  (_a2 = callbacks == null ? void 0 : callbacks.onPlay) == null ? void 0 : _a2.call(callbacks);
6856
7096
  },
@@ -6861,6 +7101,7 @@ var PixodeskAnimatorReact = (() => {
6861
7101
  },
6862
7102
  "cancel": () => {
6863
7103
  var _a2;
7104
+ finishNotified = false;
6864
7105
  animations.forEach((a) => a.cancel());
6865
7106
  (_a2 = callbacks == null ? void 0 : callbacks.onCancel) == null ? void 0 : _a2.call(callbacks);
6866
7107
  },
@@ -6890,13 +7131,19 @@ var PixodeskAnimatorReact = (() => {
6890
7131
  return res !== null ? +res : null;
6891
7132
  },
6892
7133
  "setCurrentTime": (time) => {
7134
+ finishNotified = false;
6893
7135
  animations.forEach((a) => {
6894
7136
  a.currentTime = time;
6895
7137
  });
6896
7138
  },
6897
7139
  "destroy": () => {
7140
+ var _a2;
6898
7141
  api.cancel();
6899
7142
  animations.splice(0, animations.length);
7143
+ if (!removeCalled) {
7144
+ removeCalled = true;
7145
+ (_a2 = callbacks == null ? void 0 : callbacks.onRemove) == null ? void 0 : _a2.call(callbacks);
7146
+ }
6900
7147
  }
6901
7148
  };
6902
7149
  if (config.trigger) {
@@ -7038,7 +7285,6 @@ var PixodeskAnimatorReact = (() => {
7038
7285
  const effectsWarnings = validateNodeEffects(doc);
7039
7286
  for (const w of effectsWarnings) console.warn("[PxAnimator] effects shape warning:", w);
7040
7287
  const animatorConfig = getAnimatorConfig(doc) || {};
7041
- animatorConfig.debug = true;
7042
7288
  const engine = animatorConfig.mode === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.webapi;
7043
7289
  doc = materialiseAllInTree(doc, engine);
7044
7290
  let rootElement = null;
@@ -7066,37 +7312,55 @@ var PixodeskAnimatorReact = (() => {
7066
7312
  return createAnimatorImpl(data, adapter, callbacks, container);
7067
7313
  }
7068
7314
  let animator = null;
7315
+ let pending = [];
7316
+ let destroyed = false;
7317
+ const enqueue = (call) => {
7318
+ if (animator) {
7319
+ call(animator);
7320
+ } else if (pending) {
7321
+ pending.push(call);
7322
+ }
7323
+ };
7069
7324
  fetch(src).then((res) => res.json()).then((json) => {
7325
+ if (destroyed) return;
7070
7326
  if (isPxElementFileFormat(json)) {
7071
7327
  animator = createAnimatorImpl(json, adapter, callbacks, container);
7328
+ const queued = pending;
7329
+ pending = null;
7330
+ queued == null ? void 0 : queued.forEach((call) => call(animator));
7072
7331
  } else {
7073
- console.error("Invalid animation document format");
7332
+ console.error('createAnimator: invalid animation document format at "' + src + '"');
7074
7333
  }
7334
+ }).catch((err) => {
7335
+ pending = null;
7336
+ console.error('createAnimator: failed to load "' + src + '"', err);
7075
7337
  });
7076
7338
  return {
7077
7339
  "isReady": () => !!animator,
7078
7340
  "getRootElement": () => animator ? animator.getRootElement() : null,
7079
7341
  "isPlaying": () => (animator == null ? void 0 : animator.isPlaying()) || false,
7080
7342
  "play": () => {
7081
- animator == null ? void 0 : animator.play();
7343
+ enqueue((api) => api.play());
7082
7344
  },
7083
7345
  "pause": () => {
7084
- animator == null ? void 0 : animator.pause();
7346
+ enqueue((api) => api.pause());
7085
7347
  },
7086
7348
  "cancel": () => {
7087
- animator == null ? void 0 : animator.cancel();
7349
+ enqueue((api) => api.cancel());
7088
7350
  },
7089
7351
  "finish": () => {
7090
- animator == null ? void 0 : animator.finish();
7352
+ enqueue((api) => api.finish());
7091
7353
  },
7092
7354
  "setPlaybackRate": (rate) => {
7093
- animator == null ? void 0 : animator.setPlaybackRate(rate);
7355
+ enqueue((api) => api.setPlaybackRate(rate));
7094
7356
  },
7095
- "getCurrentTime": () => (animator == null ? void 0 : animator.getCurrentTime()) || 0,
7357
+ "getCurrentTime": () => animator ? animator.getCurrentTime() : null,
7096
7358
  "setCurrentTime": (time) => {
7097
- animator == null ? void 0 : animator.setCurrentTime(time);
7359
+ enqueue((api) => api.setCurrentTime(time));
7098
7360
  },
7099
7361
  "destroy": () => {
7362
+ destroyed = true;
7363
+ pending = null;
7100
7364
  animator == null ? void 0 : animator.destroy();
7101
7365
  }
7102
7366
  };
@@ -7219,22 +7483,40 @@ var PixodeskAnimatorReact = (() => {
7219
7483
  style,
7220
7484
  doc,
7221
7485
  compMode,
7222
- apiHolderRef
7486
+ apiHolderRef,
7487
+ callbacksRef
7223
7488
  }) => {
7224
7489
  doc = generateNewIds(doc);
7225
7490
  const elementRefs = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
7226
- const renderNode2 = (node) => {
7491
+ const renderNode2 = (node, isRoot = false) => {
7227
7492
  if (!node) return null;
7228
7493
  const { type, animate, meta, children, ...props } = node;
7229
7494
  const normProps = getNormalizedProps(props);
7230
7495
  normProps["ref"] = (domEl) => {
7231
7496
  if (node["id"]) elementRefs.current.set(node["id"], domEl);
7232
7497
  };
7498
+ if (isRoot) {
7499
+ if (className) {
7500
+ normProps["className"] = normProps["className"] ? normProps["className"] + " " + className : className;
7501
+ }
7502
+ if (style) normProps["style"] = style;
7503
+ }
7233
7504
  return (0, import_react2.createElement)(type, normProps, children?.map((child) => renderNode2(child)));
7234
7505
  };
7235
- const root = doc ? renderNode2(doc) : null;
7506
+ const root = doc ? renderNode2(doc, true) : null;
7236
7507
  (0, import_react2.useEffect)(() => {
7237
- let api = createAnimator({ data: doc, adapter: createReactAdapter(elementRefs) });
7508
+ const cb = (name, alsoStop = false) => () => {
7509
+ callbacksRef.current?.[name]?.();
7510
+ if (alsoStop) callbacksRef.current?.onStop?.();
7511
+ };
7512
+ const callbacks = {
7513
+ onPlay: cb("onPlay"),
7514
+ onPause: cb("onPause", true),
7515
+ onCancel: cb("onCancel", true),
7516
+ onFinish: cb("onFinish", true),
7517
+ onRemove: cb("onRemove", true)
7518
+ };
7519
+ let api = createAnimator({ data: doc, adapter: createReactAdapter(elementRefs), callbacks });
7238
7520
  apiHolderRef.current = api;
7239
7521
  return () => {
7240
7522
  api?.destroy();
@@ -7258,7 +7540,6 @@ var PixodeskAnimatorReact = (() => {
7258
7540
  time,
7259
7541
  timeMs,
7260
7542
  apiRef,
7261
- timeline,
7262
7543
  // Overrides
7263
7544
  mode,
7264
7545
  delay,
@@ -7269,7 +7550,13 @@ var PixodeskAnimatorReact = (() => {
7269
7550
  frameRate,
7270
7551
  startOn,
7271
7552
  outAction,
7272
- scrollIntoViewThreshold
7553
+ scrollIntoViewThreshold,
7554
+ onPlay,
7555
+ onStop,
7556
+ onPause,
7557
+ onCancel,
7558
+ onFinish,
7559
+ onRemove
7273
7560
  }) => {
7274
7561
  let compMode = "static" /* static */;
7275
7562
  if (apiRef) {
@@ -7278,7 +7565,7 @@ var PixodeskAnimatorReact = (() => {
7278
7565
  compMode = "autoplay" /* autoplay */;
7279
7566
  } else if (time !== void 0 || timeMs !== void 0) {
7280
7567
  compMode = "fixedTime" /* fixedTime */;
7281
- } else {
7568
+ } else if (play !== void 0 || pause !== void 0) {
7282
7569
  compMode = "play" /* play */;
7283
7570
  }
7284
7571
  if (compMode !== "autoplay" /* autoplay */) {
@@ -7328,20 +7615,20 @@ var PixodeskAnimatorReact = (() => {
7328
7615
  }
7329
7616
  };
7330
7617
  }
7618
+ let seekMs;
7331
7619
  if (compMode === "fixedTime" /* fixedTime */) {
7332
- let delay2 = 0;
7333
- if (time !== void 0) delay2 = -time;
7334
- if (timeMs !== void 0) delay2 = -timeMs;
7335
7620
  const animator = doc.animator || {};
7336
- doc = {
7337
- ...doc,
7338
- animator: {
7339
- ...animator,
7340
- delay: delay2
7341
- }
7342
- };
7621
+ if (time !== void 0) {
7622
+ const iterationsValue = iterations ?? animator.iterations;
7623
+ const iterationsCount = typeof iterationsValue === "number" && iterationsValue >= 1 ? iterationsValue : 1;
7624
+ const singleDuration = duration ?? animator.duration ?? 1e3;
7625
+ seekMs = time * singleDuration * iterationsCount;
7626
+ }
7627
+ if (timeMs !== void 0) seekMs = timeMs;
7343
7628
  }
7344
7629
  const apiHolderRef = (0, import_react2.useRef)(null);
7630
+ const callbacksRef = (0, import_react2.useRef)({});
7631
+ callbacksRef.current = { onPlay, onStop, onPause, onCancel, onFinish, onRemove };
7345
7632
  (0, import_react2.useImperativeHandle)(apiRef, () => {
7346
7633
  return {
7347
7634
  isPlaying: () => apiHolderRef.current?.isPlaying() || false,
@@ -7349,18 +7636,22 @@ var PixodeskAnimatorReact = (() => {
7349
7636
  pause: () => apiHolderRef.current?.pause(),
7350
7637
  cancel: () => apiHolderRef.current?.cancel(),
7351
7638
  finish: () => apiHolderRef.current?.finish(),
7352
- getCurrentTime: () => apiHolderRef.current?.getCurrentTime() || null,
7639
+ setPlaybackRate: (rate) => apiHolderRef.current?.setPlaybackRate(rate),
7640
+ getCurrentTime: () => apiHolderRef.current?.getCurrentTime() ?? null,
7353
7641
  setCurrentTime: (time2) => apiHolderRef.current?.setCurrentTime(time2)
7354
7642
  };
7355
7643
  }, []);
7644
+ const key = useDepsVersion(compMode, doc, className, style);
7356
7645
  (0, import_react2.useEffect)(() => {
7357
7646
  if (compMode === "play" /* play */) {
7358
7647
  if (play && !pause) {
7359
7648
  apiHolderRef.current?.play();
7360
7649
  } else if (pause) {
7361
7650
  apiHolderRef.current?.pause();
7362
- } else {
7651
+ } else if (play === false) {
7363
7652
  apiHolderRef.current?.finish();
7653
+ } else {
7654
+ apiHolderRef.current?.play();
7364
7655
  }
7365
7656
  }
7366
7657
  return () => {
@@ -7368,14 +7659,22 @@ var PixodeskAnimatorReact = (() => {
7368
7659
  apiHolderRef.current?.pause();
7369
7660
  }
7370
7661
  };
7371
- }, [compMode, play, pause]);
7372
- const key = useDepsVersion(compMode, doc);
7662
+ }, [compMode, play, pause, key]);
7663
+ (0, import_react2.useEffect)(() => {
7664
+ if (compMode === "fixedTime" /* fixedTime */ && seekMs !== void 0) {
7665
+ apiHolderRef.current?.setCurrentTime(seekMs);
7666
+ apiHolderRef.current?.pause();
7667
+ }
7668
+ }, [compMode, seekMs, key]);
7373
7669
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
7374
7670
  PixodeskSvgAnimatorImplOnce,
7375
7671
  {
7672
+ className,
7673
+ style,
7376
7674
  compMode,
7377
7675
  doc,
7378
- apiHolderRef
7676
+ apiHolderRef,
7677
+ callbacksRef
7379
7678
  },
7380
7679
  key
7381
7680
  );
@@ -7408,7 +7707,10 @@ var PixodeskAnimatorReact = (() => {
7408
7707
  "div",
7409
7708
  {
7410
7709
  ref,
7411
- className: className + (state === "playing" ? "px-anim-enabled px-anim-playing" : state === "paused" ? "px-anim-enabled" : ""),
7710
+ className: [
7711
+ className,
7712
+ state === "playing" ? "px-anim-enabled px-anim-playing" : state === "paused" ? "px-anim-enabled" : ""
7713
+ ].filter(Boolean).join(" "),
7412
7714
  style,
7413
7715
  ...handlers,
7414
7716
  children