@pixodesk/svg-animator-core 1.0.21 → 1.0.24

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
@@ -153,8 +153,9 @@ var Union = class extends Base {
153
153
  }
154
154
  isValid(raw, ctx, path) {
155
155
  var _a;
156
- if (this.schemas.some((s) => s.isValid(raw))) return true;
157
- 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));
156
+ const probe = ctx && { errors: [], warnings: [], strict: ctx.strict };
157
+ if (this.schemas.some((s) => s.isValid(raw, probe, path ? [...path] : void 0))) return true;
158
+ 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, 240));
158
159
  return false;
159
160
  }
160
161
  _canSanitize(raw) {
@@ -230,6 +231,7 @@ var Obj = class extends Base {
230
231
  if (ctx == null ? void 0 : ctx.strict) {
231
232
  for (const key of Object.keys(obj)) {
232
233
  if (key in this._shape) continue;
234
+ if (obj[key] === void 0) continue;
233
235
  p.push(key);
234
236
  ctx.errors.push(pathStr(p) + ": unexpected extra key");
235
237
  p.pop();
@@ -370,6 +372,23 @@ var Any = class extends Base {
370
372
  return true;
371
373
  }
372
374
  };
375
+ var Defined = class extends Base {
376
+ constructor() {
377
+ super(...arguments);
378
+ this._default = void 0;
379
+ }
380
+ sanitize(raw) {
381
+ return raw;
382
+ }
383
+ isValid(raw, ctx, path) {
384
+ if (raw !== void 0) return true;
385
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": required value is missing");
386
+ return false;
387
+ }
388
+ _canSanitize(raw) {
389
+ return raw !== void 0;
390
+ }
391
+ };
373
392
  var Lazy = class extends Base {
374
393
  constructor(fn, _default) {
375
394
  super();
@@ -481,24 +500,65 @@ var px = {
481
500
  record: (value) => new Rec(value),
482
501
  /** Passes anything through unchanged — always valid. */
483
502
  any: () => new Any(),
503
+ /** Anything EXCEPT `undefined` — an open type whose presence is required (V6). */
504
+ defined: () => new Defined(),
484
505
  /** Fixed-length tuple — validates element count and each position individually. */
485
506
  tuple: (schemas) => new Tuple(schemas),
486
507
  /** Defers schema creation — required for recursive types. Must supply a default value. */
487
508
  lazy: (fn, defaultVal) => new Lazy(fn, defaultVal)
488
509
  };
489
510
 
490
- // src/PxAnimatorTypes.ts
511
+ // src/PxAnimatorConstants.ts
491
512
  var PX_ANIM_SRC_ATTR_NAME = "data-px-animation-src";
492
513
  var PX_ANIM_ATTR_NAME = "_px_animator";
493
514
  var PxAnimatorMode = {
494
515
  auto: "auto",
495
- webapi: "webapi",
516
+ waapi: "waapi",
496
517
  frames: "frames"
497
518
  };
498
519
  var PxAnimatorEngine = {
499
- webapi: PxAnimatorMode.webapi,
520
+ waapi: PxAnimatorMode.waapi,
500
521
  frames: PxAnimatorMode.frames
501
522
  };
523
+ var PxLoopExtend = {
524
+ /** Segment from the START; the animation is extended BEFORE the first keyframe
525
+ * (intro loops that run before the main timeline begins). */
526
+ before: "before",
527
+ /** DEFAULT — segment from the END; extended AFTER the last keyframe (idle/outro
528
+ * loops that continue once the main timeline has finished). */
529
+ after: "after"
530
+ };
531
+ var PxMaskType = {
532
+ luminance: "luminance",
533
+ alpha: "alpha"
534
+ };
535
+ var PxUnits = {
536
+ userSpaceOnUse: "userSpaceOnUse",
537
+ objectBoundingBox: "objectBoundingBox"
538
+ };
539
+ var PxCloneType = {
540
+ content: "content"
541
+ };
542
+ var PxPathOverflow = {
543
+ clip: "clip",
544
+ extend: "extend"
545
+ };
546
+ var PxLengthAdjust = {
547
+ spacing: "spacing",
548
+ spacingAndGlyphs: "spacingAndGlyphs"
549
+ };
550
+ var PxTextPathMethod = {
551
+ align: "align",
552
+ stretch: "stretch"
553
+ };
554
+ var PxTextPathSpacing = {
555
+ auto: "auto",
556
+ exact: "exact"
557
+ };
558
+ var PxTrimSubPaths = {
559
+ separate: "separate",
560
+ combined: "combined"
561
+ };
502
562
  var TEXT_ATTR = "text";
503
563
  var TEXT_CONTENT_ATTR = "textContent";
504
564
  var INTERNAL_ATTRS = /* @__PURE__ */ new Set([
@@ -507,9 +567,51 @@ var INTERNAL_ATTRS = /* @__PURE__ */ new Set([
507
567
  "animator",
508
568
  "meta",
509
569
  "animate",
570
+ "effects",
510
571
  TEXT_ATTR,
511
572
  TEXT_CONTENT_ATTR
512
573
  ]);
574
+ var PX_TRANSFORM_PART_KEYS = ["translate", "rotate", "scale", "origin"];
575
+ var PxGradientUnits = {
576
+ userSpaceOnUse: "userSpaceOnUse",
577
+ objectBoundingBox: "objectBoundingBox"
578
+ };
579
+ var PxGradientSpreadMethod = {
580
+ pad: "pad",
581
+ reflect: "reflect",
582
+ repeat: "repeat"
583
+ };
584
+ var PxGradientType = {
585
+ linear: "linear",
586
+ radial: "radial"
587
+ };
588
+ function isPxElementFileFormat(fileJson) {
589
+ if (!(fileJson && typeof fileJson === "object" && !Array.isArray(fileJson))) {
590
+ return false;
591
+ }
592
+ return fileJson["type"] === "svg";
593
+ }
594
+ function getAnimatorConfig(doc) {
595
+ var _a;
596
+ return (doc == null ? void 0 : doc.animator) || ((_a = doc == null ? void 0 : doc.meta) == null ? void 0 : _a.animator);
597
+ }
598
+ function getDefs(doc) {
599
+ var _a;
600
+ if (!doc) return void 0;
601
+ return (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.definitions;
602
+ }
603
+ function getBindings(doc) {
604
+ var _a;
605
+ if (!doc) return void 0;
606
+ const animateById = (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.animateById;
607
+ if (!animateById) return void 0;
608
+ return Object.entries(animateById).map(([id, anim]) => ({ id, animate: anim }));
609
+ }
610
+ function getChildren(doc) {
611
+ return doc == null ? void 0 : doc.children;
612
+ }
613
+
614
+ // src/PxAnimatorTypes.ts
513
615
  var PxEasingOrRefSchema = px.union([
514
616
  px.string(),
515
617
  px.tuple([px.number(), px.number(), px.number(), px.number()])
@@ -521,13 +623,15 @@ var PxKeyframeValueSchema = implementsInterface()(px.union([
521
623
  px.array(px.number()),
522
624
  px.lazy(() => PxTransformPartsSchema, {}),
523
625
  px.object({ path: px.string() }),
524
- px.lazy(() => px.object({ paths: px.array(PxBezierPathSchema) }), { paths: [] })
626
+ px.lazy(() => px.object({ paths: px.array(PxBezierPathSchema) }), { paths: [] }),
627
+ // Gradient `stops` timeline — each kf value is the full stops-array snapshot.
628
+ px.lazy(() => px.array(PxGradientStopSchema), [])
525
629
  ]));
526
630
  var PxKeyframeSchema = implementsInterface()(px.object({
527
631
  time: px.number().optional(),
528
632
  t: px.number().optional(),
529
- value: px.any().optional(),
530
- v: px.any().optional(),
633
+ value: PxKeyframeValueSchema.optional(),
634
+ v: PxKeyframeValueSchema.optional(),
531
635
  easing: PxEasingOrRefSchema.optional(),
532
636
  e: PxEasingOrRefSchema.optional(),
533
637
  tangentOut: px.tuple([px.number(), px.number()]).optional(),
@@ -541,16 +645,16 @@ var PxKeyframeSchema = implementsInterface()(px.object({
541
645
  }));
542
646
  var PxLoopSchema = implementsInterface()(px.object({
543
647
  segmentCount: px.number().optional(),
544
- before: px.boolean().optional(),
648
+ extend: px.enum([PxLoopExtend.before, PxLoopExtend.after]).optional(),
545
649
  alternate: px.boolean().optional()
546
650
  }));
547
651
  var PxPropertyAnimationSchema = implementsInterface()(px.object({
652
+ value: PxKeyframeValueSchema.optional(),
548
653
  keyframes: px.array(PxKeyframeSchema).optional(),
549
654
  kfs: px.array(PxKeyframeSchema).optional(),
550
655
  loop: px.union([PxLoopSchema, px.boolean()]).optional(),
551
656
  autoOrient: px.boolean().optional()
552
657
  }));
553
- var PX_TRANSFORM_PART_KEYS = ["translate", "rotate", "scale", "origin"];
554
658
  var PxTransformPartsSchema = implementsInterface()(px.object({
555
659
  translate: px.tuple([px.number(), px.number()]).optional(),
556
660
  rotate: px.number().optional(),
@@ -560,6 +664,7 @@ var PxTransformPartsSchema = implementsInterface()(px.object({
560
664
  }));
561
665
  var PxTransformValueSchema = px.union([
562
666
  px.string(),
667
+ PxTransformPartsSchema,
563
668
  px.object({ value: PxTransformPartsSchema }),
564
669
  PxPropertyAnimationSchema
565
670
  ]);
@@ -594,9 +699,11 @@ var PxDefsSchema = implementsInterface()(px.object({
594
699
  glyphs: px.record(PxGlyphFontSchema).optional()
595
700
  }));
596
701
  var PxAnimatorConfigSchema = implementsInterface()(px.object({
597
- mode: px.enum([PxAnimatorMode.auto, PxAnimatorMode.webapi, PxAnimatorMode.frames]).optional(),
702
+ mode: px.enum([PxAnimatorMode.auto, PxAnimatorMode.waapi, PxAnimatorMode.frames]).optional(),
598
703
  duration: px.number().optional(),
599
704
  delay: px.number().optional(),
705
+ // LAW (SCHEMA-DESIGN R3, S10): the ONE sanctioned string-in-number union
706
+ // (CSS animation-iteration-count familiarity) — do not add more mixed unions.
600
707
  iterations: px.union([px.number(), px.literal("infinite")]).optional(),
601
708
  fill: px.enum(["forwards", "backwards", "both", "none"]).optional(),
602
709
  direction: px.enum(["normal", "reverse", "alternate", "alternate-reverse"]).optional(),
@@ -604,8 +711,8 @@ var PxAnimatorConfigSchema = implementsInterface()(px.object({
604
711
  trigger: PxTriggerSchema.optional(),
605
712
  resetOnFinish: px.boolean().optional(),
606
713
  definitions: PxDefsSchema.optional(),
607
- animate: px.record(PxElementAnimationSchema).optional(),
608
- debug: px.boolean().optional(),
714
+ animateById: px.record(PxElementAnimationSchema).optional(),
715
+ timelineSource: px.string().optional(),
609
716
  debugInstName: px.string().optional()
610
717
  }));
611
718
  var PxBindingSchema = implementsInterface()(px.object({
@@ -615,26 +722,29 @@ var PxBindingSchema = implementsInterface()(px.object({
615
722
  var PxAttrValueSchema = px.union([
616
723
  px.string(),
617
724
  px.number(),
618
- px.object({ value: px.any() }),
619
- PxPropertyAnimationSchema
725
+ px.array(px.number()),
726
+ // Structured static — `{value: …}` (read-accepted transitional spelling, S1).
727
+ // `defined`, not `any`: the KEY's presence is what identifies this branch (V6).
728
+ px.object({ value: px.defined() }),
729
+ // Bare transform parts record — the canonical static `transform` on the wire (T2).
730
+ PxTransformPartsSchema
620
731
  ]);
621
732
  var PxAnimatableNumberSchema = px.union([
622
733
  px.number(),
623
734
  px.object({ value: px.number() }),
624
- px.object({
625
- keyframes: px.array(PxKeyframeSchema),
626
- autoOrient: px.boolean().optional()
627
- })
735
+ PxPropertyAnimationSchema
628
736
  ]);
629
737
  var PxAnimatableVec2Schema = px.union([
630
738
  px.tuple([px.number(), px.number()]),
631
739
  px.object({ value: px.tuple([px.number(), px.number()]) }),
632
- px.object({
633
- keyframes: px.array(PxKeyframeSchema),
634
- autoOrient: px.boolean().optional()
635
- })
740
+ PxPropertyAnimationSchema
741
+ ]);
742
+ var PxAnimatableStringSchema = px.union([
743
+ px.string(),
744
+ px.object({ value: px.string() }),
745
+ PxPropertyAnimationSchema
636
746
  ]);
637
- var PxTransformationEffectSchema = implementsInterface()(px.object({
747
+ var PxTransformByEffectSchema = implementsInterface()(px.object({
638
748
  translate: PxAnimatableVec2Schema.optional(),
639
749
  rotate: PxAnimatableNumberSchema.optional(),
640
750
  scale: PxAnimatableVec2Schema.optional(),
@@ -642,51 +752,46 @@ var PxTransformationEffectSchema = implementsInterface()(px.object({
642
752
  origin: PxAnimatableVec2Schema.optional()
643
753
  }));
644
754
  var PxRepeaterEffectSchema = implementsInterface()(px.object({
755
+ // STATIC config, not a channel (V2/SCHEMA-DESIGN R5): the copy COUNT is read
756
+ // once at expansion time and never sampled — plain number, no `keyframes`.
645
757
  copies: px.number().optional(),
646
758
  translate: PxAnimatableVec2Schema.optional(),
647
759
  rotate: PxAnimatableNumberSchema.optional(),
760
+ skew: PxAnimatableNumberSchema.optional(),
648
761
  scale: PxAnimatableVec2Schema.optional(),
649
762
  origin: PxAnimatableVec2Schema.optional()
650
763
  }));
651
764
  var PxMaskedByEffectSchema = implementsInterface()(px.object({
652
- href: px.string().optional(),
653
- maskType: px.string().optional(),
654
- maskUnits: px.string().optional(),
655
- maskContentUnits: px.string().optional()
765
+ sourceId: px.string().optional(),
766
+ maskType: px.enum([PxMaskType.luminance, PxMaskType.alpha]).optional(),
767
+ maskUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox]).optional(),
768
+ maskContentUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox]).optional(),
769
+ x: px.number().optional(),
770
+ y: px.number().optional(),
771
+ width: px.number().optional(),
772
+ height: px.number().optional()
656
773
  }));
657
774
  var PxClipPathEffectSchema = implementsInterface()(px.object({
658
- d: px.string().optional(),
775
+ d: PxAnimatableStringSchema.optional(),
659
776
  animate: PxPropertyAnimationSchema.optional()
660
777
  }));
661
778
  var PxTrimPathEffectSchema = implementsInterface()(px.object({
662
779
  offset: PxAnimatableNumberSchema.optional(),
663
780
  range: PxAnimatableVec2Schema.optional(),
664
- trimAllAsOne: px.boolean().optional()
781
+ subPaths: px.enum([PxTrimSubPaths.separate, PxTrimSubPaths.combined]).optional()
665
782
  }));
666
783
  var PxRetimeEffectSchema = implementsInterface()(px.object({
667
- baseId: px.string().optional(),
784
+ sourceId: px.string().optional(),
668
785
  start: px.number().optional(),
669
786
  stretch: px.number().optional(),
670
787
  timeCrop: px.tuple([px.number(), px.number()]).optional()
671
788
  }));
672
789
  var PxCloneEffectSchema = implementsInterface()(px.object({
673
- type: px.string().optional(),
674
- baseId: px.string().optional(),
790
+ // Contextual kind — the `type` convention, see `PxNodeBase.type`.
791
+ type: px.enum([PxCloneType.content]).optional(),
792
+ sourceId: px.string().optional(),
675
793
  retime: PxRetimeEffectSchema.optional()
676
794
  }));
677
- var PxGradientUnits = {
678
- userSpaceOnUse: "userSpaceOnUse",
679
- objectBoundingBox: "objectBoundingBox"
680
- };
681
- var PxGradientSpreadMethod = {
682
- pad: "pad",
683
- reflect: "reflect",
684
- repeat: "repeat"
685
- };
686
- var PxGradientType = {
687
- linear: "linear",
688
- radial: "radial"
689
- };
690
795
  var PxGradientStopSchema = implementsInterface()(px.object({
691
796
  offset: px.number(),
692
797
  color: px.string()
@@ -694,27 +799,28 @@ var PxGradientStopSchema = implementsInterface()(px.object({
694
799
  var PxAnimatableGradientStopsSchema = px.union([
695
800
  px.array(PxGradientStopSchema),
696
801
  px.object({ value: px.array(PxGradientStopSchema) }),
697
- px.object({ keyframes: px.array(PxKeyframeSchema) })
802
+ PxPropertyAnimationSchema
698
803
  ]);
699
804
  var PxFillGradientEffectSchema = implementsInterface()(px.object({
805
+ // Contextual kind — the `type` convention, see `PxNodeBase.type`.
700
806
  type: px.enum([PxGradientType.linear, PxGradientType.radial]),
701
- p1: px.tuple([px.number(), px.number()]).optional(),
702
- p2: px.tuple([px.number(), px.number()]).optional(),
703
- c: px.tuple([px.number(), px.number()]).optional(),
704
- r: px.number().optional(),
705
- fp: px.tuple([px.number(), px.number()]).optional(),
807
+ p1: PxAnimatableVec2Schema.optional(),
808
+ p2: PxAnimatableVec2Schema.optional(),
809
+ c: PxAnimatableVec2Schema.optional(),
810
+ r: PxAnimatableNumberSchema.optional(),
811
+ fp: PxAnimatableVec2Schema.optional(),
706
812
  stops: PxAnimatableGradientStopsSchema.optional(),
707
- gradientUnits: px.string().optional(),
708
- spreadMethod: px.string().optional(),
813
+ gradientUnits: px.enum([PxGradientUnits.userSpaceOnUse, PxGradientUnits.objectBoundingBox]).optional(),
814
+ spreadMethod: px.enum([PxGradientSpreadMethod.pad, PxGradientSpreadMethod.reflect, PxGradientSpreadMethod.repeat]).optional(),
709
815
  gradientTransform: px.string().optional()
710
816
  }));
711
817
  var PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;
712
818
  var PxTextPathEffectSchema = implementsInterface()(px.object({
713
819
  path: px.string(),
714
- pathOverflow: px.string().optional(),
715
- lengthAdjust: px.string().optional(),
716
- method: px.string().optional(),
717
- spacing: px.string().optional(),
820
+ pathOverflow: px.enum([PxPathOverflow.clip, PxPathOverflow.extend]).optional(),
821
+ lengthAdjust: px.enum([PxLengthAdjust.spacing, PxLengthAdjust.spacingAndGlyphs]).optional(),
822
+ method: px.enum([PxTextPathMethod.align, PxTextPathMethod.stretch]).optional(),
823
+ spacing: px.enum([PxTextPathSpacing.auto, PxTextPathSpacing.exact]).optional(),
718
824
  startOffset: PxAnimatableNumberSchema.optional(),
719
825
  textLength: PxAnimatableNumberSchema.optional()
720
826
  }));
@@ -722,13 +828,12 @@ var PxTextEffectSchema = implementsInterface()(px.object({
722
828
  useGlyphs: px.boolean().optional()
723
829
  }));
724
830
  var PxEffectsSchema = implementsInterface()(px.object({
725
- transformation: PxTransformationEffectSchema.optional(),
831
+ transformBy: PxTransformByEffectSchema.optional(),
726
832
  repeater: PxRepeaterEffectSchema.optional(),
727
833
  maskedBy: PxMaskedByEffectSchema.optional(),
728
834
  clipPath: PxClipPathEffectSchema.optional(),
729
835
  trimPath: PxTrimPathEffectSchema.optional(),
730
836
  clone: PxCloneEffectSchema.optional(),
731
- isCombinedShape: px.boolean().optional(),
732
837
  fillGradient: PxFillGradientEffectSchema.optional(),
733
838
  strokeGradient: PxStrokeGradientEffectSchema.optional(),
734
839
  textPath: PxTextPathEffectSchema.optional(),
@@ -752,6 +857,14 @@ function validateNodeEffects(root, opts) {
752
857
  return warnings;
753
858
  }
754
859
  var PxNodeBase = px.openObject({
860
+ // CONVENTION (SCHEMA-DESIGN R1 / issues N4): `type` is the ONE word for "what
861
+ // kind of thing is this", discriminated by its CARRIER — here the node TAG
862
+ // (`rect`, `text`), and inside a sub-object that object's kind (`clone.type`,
863
+ // `fillGradient.type`, editor `preset.type`). Each sits in its own object, so
864
+ // the carrier disambiguates completely; synonyms (`cloneKind`, `presetShape`)
865
+ // would add words that all mean "type" and still need the carrier to read.
866
+ // Guarding a `type` SLOT against a wrong VALUE is the job of strict enums
867
+ // (issues V3), never of distinct key names.
755
868
  type: px.string(),
756
869
  id: px.string().optional(),
757
870
  meta: px.any().optional(),
@@ -761,7 +874,7 @@ var PxNodeBase = px.openObject({
761
874
  effects: PxEffectsSchema.optional(),
762
875
  // `PxElementAnimation` (not just `PxAnimationDefinition`) — accepts
763
876
  // string ref / array of refs / inline definition / mixed array; mirrors
764
- // `animator.animate` map values and what `processNode` resolves at runtime.
877
+ // `animator.animateById` map values and what `processNode` resolves at runtime.
765
878
  animate: PxElementAnimationSchema.optional(),
766
879
  style: px.union([px.string(), px.record(px.union([px.string(), px.number()]))]).optional()
767
880
  }, PxAttrValueSchema);
@@ -785,35 +898,10 @@ var PxBezierPathSchema = implementsInterface()(px.object({
785
898
  o: px.array(px.array(px.number())).optional(),
786
899
  c: px.boolean().optional()
787
900
  }));
788
- function isPxElementFileFormat(fileJson) {
789
- if (!(fileJson && typeof fileJson === "object" && !Array.isArray(fileJson))) {
790
- return false;
791
- }
792
- return fileJson["type"] === "svg" || fileJson["tagName"] === "svg";
793
- }
794
901
  function isPxElementFileFormatDeep(fileJson) {
795
902
  const valid = PxAnimatedSvgDocumentSchema.isValid(fileJson);
796
903
  return { valid, errors: valid ? [] : ["Document failed schema validation"] };
797
904
  }
798
- function getAnimatorConfig(doc) {
799
- var _a, _b;
800
- return (doc == null ? void 0 : doc.animator) || ((_a = doc == null ? void 0 : doc.meta) == null ? void 0 : _a.animator) || (doc == null ? void 0 : doc.animation) || ((_b = doc == null ? void 0 : doc.meta) == null ? void 0 : _b.animation);
801
- }
802
- function getDefs(doc) {
803
- var _a;
804
- if (!doc) return void 0;
805
- return (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.definitions;
806
- }
807
- function getBindings(doc) {
808
- var _a;
809
- if (!doc) return void 0;
810
- const animate = (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.animate;
811
- if (!animate) return void 0;
812
- return Object.entries(animate).map(([id, anim]) => ({ id, animate: anim }));
813
- }
814
- function getChildren(doc) {
815
- return doc == null ? void 0 : doc.children;
816
- }
817
905
 
818
906
  // src/PxAnimatorUtil.ts
819
907
  function bezierToSvgPath(path, forceCurves = false) {
@@ -1268,7 +1356,7 @@ function generateNewIds(doc) {
1268
1356
  "flood-color",
1269
1357
  "lighting-color"
1270
1358
  ]);
1271
- const directIdRefAttrs = /* @__PURE__ */ new Set(["baseId", "targetId", "boundElementId"]);
1359
+ const directIdRefAttrs = /* @__PURE__ */ new Set(["sourceId", "targetId", "boundElementId"]);
1272
1360
  function collectIds(node) {
1273
1361
  if (!node || typeof node !== "object") return;
1274
1362
  if (node.id && typeof node.id === "string") {
@@ -1306,9 +1394,10 @@ function generateNewIds(doc) {
1306
1394
  } else if (urlRefAttrs.has(key)) {
1307
1395
  node[key] = replaceUrlRefs(value, idMap);
1308
1396
  } else if (directIdRefAttrs.has(key)) {
1309
- const newId = idMap.get(value);
1397
+ const hasHash = value.startsWith("#");
1398
+ const newId = idMap.get(hasHash ? value.slice(1) : value);
1310
1399
  if (newId) {
1311
- node[key] = newId;
1400
+ node[key] = hasHash ? "#" + newId : newId;
1312
1401
  }
1313
1402
  } else if (value.includes("url(#")) {
1314
1403
  node[key] = replaceUrlRefs(value, idMap);
@@ -1326,14 +1415,14 @@ function generateNewIds(doc) {
1326
1415
  }
1327
1416
  collectIds(cloned);
1328
1417
  updateRefs(cloned);
1329
- const docAnimate = (_a = cloned.animator) == null ? void 0 : _a.animate;
1418
+ const docAnimate = (_a = cloned.animator) == null ? void 0 : _a.animateById;
1330
1419
  if (docAnimate && typeof docAnimate === "object") {
1331
1420
  const updatedAnimate = {};
1332
1421
  for (const [id, anim] of Object.entries(docAnimate)) {
1333
1422
  const newId = (_b = idMap.get(id)) != null ? _b : id;
1334
1423
  updatedAnimate[newId] = anim;
1335
1424
  }
1336
- cloned.animator = __spreadProps(__spreadValues({}, cloned.animator), { animate: updatedAnimate });
1425
+ cloned.animator = __spreadProps(__spreadValues({}, cloned.animator), { animateById: updatedAnimate });
1337
1426
  }
1338
1427
  return cloned;
1339
1428
  }
@@ -1424,8 +1513,9 @@ function getNormalizedProps(props) {
1424
1513
  let value = props[rawKey];
1425
1514
  if (COLOUR_ATTR_NAMES.has(key) && Array.isArray(value)) {
1426
1515
  propsCopy[key] = toRGBA(value);
1427
- } else if (key === "transform" && value !== null && typeof value === "object" && !Array.isArray(value) && value.value && typeof value.value === "object") {
1428
- propsCopy["transform"] = composeTransformParts(value.value, { withUnits: false });
1516
+ } else if (key === "transform" && value !== null && typeof value === "object" && !Array.isArray(value) && !value.keyframes && !value.kfs) {
1517
+ const parts = value.value && typeof value.value === "object" ? value.value : value;
1518
+ propsCopy["transform"] = composeTransformParts(parts, { withUnits: false });
1429
1519
  } else if (TRANSFORM_FN_NAMES.has(key)) {
1430
1520
  if (Array.isArray(value)) {
1431
1521
  if (key === "translate") value = value.map((v) => v + "px");
@@ -1433,6 +1523,8 @@ function getNormalizedProps(props) {
1433
1523
  }
1434
1524
  if (key === "rotate") value = value + "deg";
1435
1525
  propsCopy["transform"] = key + "(" + value + ")";
1526
+ } else if (Array.isArray(value)) {
1527
+ propsCopy[key] = value.join(",");
1436
1528
  } else if (value !== void 0 && value !== null) {
1437
1529
  propsCopy[key] = String(value);
1438
1530
  }
@@ -1834,6 +1926,16 @@ function walkAndMaterialise(node, opts) {
1834
1926
  }
1835
1927
 
1836
1928
  // src/PxDefinitions.ts
1929
+ var LOOP_JUMP_SHIFT_MS = 10;
1930
+ function deepEqualValue(a, b) {
1931
+ if (a === b) return true;
1932
+ if (typeof a !== typeof b || a === null || b === null || typeof a !== "object") return false;
1933
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
1934
+ const ka = Object.keys(a);
1935
+ const kb = Object.keys(b);
1936
+ if (ka.length !== kb.length) return false;
1937
+ return ka.every((k) => deepEqualValue(a[k], b[k]));
1938
+ }
1837
1939
  function parsePathCommands(d) {
1838
1940
  const tokens = d.split(/([MLCZmlcz]|[\s,]+)/).map((t) => t.trim()).filter((t) => t && t !== ",");
1839
1941
  const commands = [];
@@ -2036,7 +2138,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2036
2138
  const totalIntervals = keyframes.length - 1;
2037
2139
  const segCount = clamp((_a = loop.segmentCount) != null ? _a : totalIntervals, 1, totalIntervals);
2038
2140
  let segKfs;
2039
- if (loop.before) {
2141
+ if (loop.extend === PxLoopExtend.before) {
2040
2142
  segKfs = keyframes.slice(0, segCount + 1);
2041
2143
  } else {
2042
2144
  segKfs = keyframes.slice(totalIntervals - segCount);
@@ -2044,7 +2146,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2044
2146
  const firstT = (_b = keyframes[0].t) != null ? _b : 0;
2045
2147
  const lastT = (_c = keyframes[keyframes.length - 1].t) != null ? _c : 0;
2046
2148
  let fillStart, fillEnd;
2047
- if (loop.before) {
2149
+ if (loop.extend === PxLoopExtend.before) {
2048
2150
  fillStart = 0;
2049
2151
  fillEnd = firstT;
2050
2152
  } else {
@@ -2071,7 +2173,10 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2071
2173
  const remainder = fillDuration - fullReps * segDuration;
2072
2174
  const partialFraction = remainder / segDuration;
2073
2175
  const looped = [];
2176
+ const separateBoundary = loop.extend !== PxLoopExtend.before;
2177
+ const originalTerminalKf = keyframes[keyframes.length - 1];
2074
2178
  function appendRep(repStart, isReversed, partial) {
2179
+ var _a2;
2075
2180
  let entries;
2076
2181
  if (isReversed) {
2077
2182
  entries = [];
@@ -2107,8 +2212,17 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2107
2212
  looped.push({ t: repStart + cutRelT * segDuration, v: cutValue, e: void 0 });
2108
2213
  return;
2109
2214
  }
2215
+ const prevKf = looped.length > 0 ? looped[looped.length - 1] : originalTerminalKf;
2216
+ const isBoundary = separateBoundary && i === 0 && prevKf !== void 0 && Math.abs(((_a2 = prevKf.t) != null ? _a2 : 0) - (repStart + entry.relT * segDuration)) < 1e-9;
2217
+ if (isBoundary) {
2218
+ if (deepEqualValue(prevKf.v, entry.v)) continue;
2219
+ if (looped.length > 0) {
2220
+ delete prevKf.tangentIn;
2221
+ delete prevKf.tangentOut;
2222
+ }
2223
+ }
2110
2224
  const pushed = {
2111
- t: repStart + entry.relT * segDuration,
2225
+ t: repStart + entry.relT * segDuration + (isBoundary ? LOOP_JUMP_SHIFT_MS : 0),
2112
2226
  v: entry.v,
2113
2227
  e: i < entries.length - 1 ? entry.e : void 0
2114
2228
  };
@@ -2156,7 +2270,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2156
2270
  looped.push(pushed);
2157
2271
  }
2158
2272
  }
2159
- if (loop.before) {
2273
+ if (loop.extend === PxLoopExtend.before) {
2160
2274
  if (partialFraction > 1e-9) {
2161
2275
  const isReversed = !!loop.alternate && fullReps % 2 === 0;
2162
2276
  appendRepTail(fillStart, isReversed, partialFraction);
@@ -2179,7 +2293,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2179
2293
  appendRep(repStart, isReversed, partialFraction);
2180
2294
  }
2181
2295
  }
2182
- if (loop.before) {
2296
+ if (loop.extend === PxLoopExtend.before) {
2183
2297
  return [...looped, ...keyframes];
2184
2298
  } else {
2185
2299
  return [...keyframes, ...looped];
@@ -2295,7 +2409,7 @@ var _elementIdCounter = 0;
2295
2409
  function generateElementId() {
2296
2410
  return "_px_el_" + ++_elementIdCounter;
2297
2411
  }
2298
- function normalizeAnimationDefinition(animDef, duration, defs, engine = PxAnimatorEngine.webapi) {
2412
+ function normalizeAnimationDefinition(animDef, duration, defs, engine = PxAnimatorEngine.waapi) {
2299
2413
  const normalized = {};
2300
2414
  for (const [propName, propAnim] of Object.entries(animDef)) {
2301
2415
  const normalizedKfs = normalizeKeyframes(propName, propAnim, duration, defs);
@@ -2303,12 +2417,12 @@ function normalizeAnimationDefinition(animDef, duration, defs, engine = PxAnimat
2303
2417
  const out = { kfs: normalizedKfs };
2304
2418
  if (propAnim.autoOrient !== void 0) out.autoOrient = propAnim.autoOrient;
2305
2419
  if (propAnim.loop !== void 0) out.loop = propAnim.loop;
2306
- normalized[propName] = engine === PxAnimatorEngine.webapi && propName === "transform" ? materialiseMotionPathInPropAnim(out) : out;
2420
+ normalized[propName] = engine === PxAnimatorEngine.waapi && propName === "transform" ? materialiseMotionPathInPropAnim(out) : out;
2307
2421
  }
2308
2422
  }
2309
2423
  return normalized;
2310
2424
  }
2311
- function getNormalisedBindings(doc, engine = PxAnimatorEngine.webapi) {
2425
+ function getNormalisedBindings(doc, engine = PxAnimatorEngine.waapi) {
2312
2426
  const animatorConfig = getAnimatorConfig(doc) || {};
2313
2427
  const defs = getDefs(doc);
2314
2428
  const duration = animatorConfig.duration || 1e3;
@@ -2719,23 +2833,61 @@ function partsRecord(part, value, origin) {
2719
2833
  return rec;
2720
2834
  }
2721
2835
  function readAnimatable(raw) {
2836
+ var _a, _b, _c;
2722
2837
  if (raw === void 0) return { kind: "absent" /* Absent */ };
2723
2838
  if (Array.isArray(raw)) return { kind: "static" /* Static */, value: raw };
2724
2839
  if (typeof raw === "object") {
2725
2840
  const obj = raw;
2726
- if (obj.keyframes) {
2727
- return { kind: "animated" /* Animated */, keyframes: obj.keyframes, autoOrient: obj.autoOrient, loop: obj.loop };
2841
+ const kfs = (_a = obj.keyframes) != null ? _a : obj.kfs;
2842
+ if (kfs) {
2843
+ const out = { kind: "animated" /* Animated */, keyframes: kfs.map(normaliseKeyframe), autoOrient: obj.autoOrient, loop: obj.loop };
2844
+ const base = (_b = obj.value) != null ? _b : obj.v;
2845
+ if (base !== void 0 && out.kind === "animated" /* Animated */) out.base = base;
2846
+ return out;
2728
2847
  }
2729
- if (obj.value !== void 0) return { kind: "static" /* Static */, value: obj.value };
2848
+ const staticValue = (_c = obj.value) != null ? _c : obj.v;
2849
+ if (staticValue !== void 0) return { kind: "static" /* Static */, value: staticValue };
2730
2850
  }
2731
2851
  return { kind: "static" /* Static */, value: raw };
2732
2852
  }
2853
+ function writeAnimatableChannel(node, attrName, read, opts) {
2854
+ var _a, _b, _c, _d;
2855
+ const toOut = (v) => (opts == null ? void 0 : opts.asString) && v !== void 0 && v !== null ? String(v) : v;
2856
+ if (read.kind === "absent" /* Absent */) return;
2857
+ if (read.kind === "static" /* Static */) {
2858
+ node[attrName] = toOut(read.value);
2859
+ return;
2860
+ }
2861
+ const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
2862
+ const animate = __spreadValues({}, prevAnimate || {});
2863
+ const block = { keyframes: read.keyframes };
2864
+ if (read.loop !== void 0) block.loop = read.loop;
2865
+ if (read.autoOrient !== void 0) block.autoOrient = read.autoOrient;
2866
+ animate[attrName] = block;
2867
+ node.animate = animate;
2868
+ const baseline = (_d = (_b = read.base) != null ? _b : (_a = read.keyframes[0]) == null ? void 0 : _a.value) != null ? _d : (_c = read.keyframes[0]) == null ? void 0 : _c.v;
2869
+ if (baseline !== void 0) node[attrName] = toOut(baseline);
2870
+ }
2871
+ function normaliseKeyframe(kf) {
2872
+ if (!kf || typeof kf !== "object") return kf;
2873
+ const k = kf;
2874
+ if (k.t === void 0 && k.v === void 0 && k.e === void 0 && k.to === void 0 && k.ti === void 0) {
2875
+ return kf;
2876
+ }
2877
+ const out = __spreadValues({}, k);
2878
+ if (out.time === void 0 && k.t !== void 0) out.time = k.t;
2879
+ if (out.value === void 0 && k.v !== void 0) out.value = k.v;
2880
+ if (out.easing === void 0 && k.e !== void 0) out.easing = k.e;
2881
+ if (out.tangentOut === void 0 && k.to !== void 0) out.tangentOut = k.to;
2882
+ if (out.tangentIn === void 0 && k.ti !== void 0) out.tangentIn = k.ti;
2883
+ return out;
2884
+ }
2733
2885
  function readStaticOrigin(raw, ctx) {
2734
2886
  var _a;
2735
2887
  const o = readAnimatable(raw);
2736
2888
  if (o.kind === "absent" /* Absent */) return void 0;
2737
2889
  if (o.kind === "static" /* Static */) return o.value;
2738
- ctx.warnings.push("transformation.origin: animated origin approximated by its first keyframe");
2890
+ ctx.warnings.push("transformBy.origin: animated origin approximated by its first keyframe");
2739
2891
  return (_a = o.keyframes[0]) == null ? void 0 : _a.value;
2740
2892
  }
2741
2893
  function keyframeWith(kf, value) {
@@ -2748,7 +2900,7 @@ function keyframeWith(kf, value) {
2748
2900
  }
2749
2901
 
2750
2902
  // src/effects/transformationEffect.ts
2751
- function applyTransformationEffect(node, fx, ctx) {
2903
+ function applyTransformByEffect(node, fx, ctx) {
2752
2904
  if (!fx) return node;
2753
2905
  delete node.transform;
2754
2906
  let n = node;
@@ -2784,6 +2936,10 @@ function applyTransformationEffect(node, fx, ctx) {
2784
2936
  } else {
2785
2937
  n = wrapTransformPart(n, "translate" /* Translate */, fx.translate, ctx);
2786
2938
  }
2939
+ if (n !== node && node.id) {
2940
+ n.id = node.id;
2941
+ delete node.id;
2942
+ }
2787
2943
  return n;
2788
2944
  }
2789
2945
  function translateHasAutoOrient(translate) {
@@ -2793,8 +2949,6 @@ function translateHasAutoOrient(translate) {
2793
2949
  return Array.isArray(obj.keyframes) && obj.keyframes.some((kf) => kf.tangentOut || kf.tangentIn);
2794
2950
  }
2795
2951
  function normalizeScale(raw) {
2796
- if (raw === void 0) return void 0;
2797
- if (Array.isArray(raw)) return [raw[0] / 100, raw[1] / 100];
2798
2952
  return raw;
2799
2953
  }
2800
2954
  function wrapTransformPart(inner, part, raw, ctx) {
@@ -2836,35 +2990,57 @@ function wrapOrigin(inner, raw, invert) {
2836
2990
  return inner;
2837
2991
  }
2838
2992
 
2993
+ // src/effects/util.ts
2994
+ function genId(ctx, prefix) {
2995
+ return "_lw_" + prefix + "_" + ctx.nextId++;
2996
+ }
2997
+ function stripHash2(href) {
2998
+ return typeof href === "string" ? href.replace(/^#/, "") : void 0;
2999
+ }
3000
+ function indexById(node, map) {
3001
+ var _a;
3002
+ if (typeof node.id === "string") map.set(node.id, node);
3003
+ (_a = node.children) == null ? void 0 : _a.forEach((child) => indexById(child, map));
3004
+ }
3005
+ function spliceDefs(root, defs) {
3006
+ if (!defs.length) return;
3007
+ const existing = root.children || (root.children = []);
3008
+ existing.unshift({ type: "defs", children: defs });
3009
+ }
3010
+ var clone = deepClonePxNode;
3011
+ function regenerateIdsInClone(root, ctx) {
3012
+ return regenerateIdsAndRewriteRefs(root, () => genId(ctx, "retimed"));
3013
+ }
3014
+
2839
3015
  // src/effects/contentRefSplit.ts
2840
3016
  function identifyContentRefTargets(node, ctx, allocator) {
2841
3017
  var _a, _b, _c;
2842
3018
  if (node.type === "use" && ((_b = (_a = node.effects) == null ? void 0 : _a.clone) == null ? void 0 : _b.type) === "content") {
2843
- const baseId = node.effects.clone.baseId;
2844
- if (typeof baseId === "string" && baseId && !ctx.contentRefInnerIds.has(baseId)) {
2845
- ctx.contentRefInnerIds.set(baseId, allocator(baseId));
3019
+ const sourceId = stripHash2(node.effects.clone.sourceId);
3020
+ if (typeof sourceId === "string" && sourceId && !ctx.contentRefInnerIds.has(sourceId)) {
3021
+ ctx.contentRefInnerIds.set(sourceId, allocator(sourceId));
2846
3022
  }
2847
3023
  }
2848
3024
  (_c = node.children) == null ? void 0 : _c.forEach((c) => identifyContentRefTargets(c, ctx, allocator));
2849
3025
  }
2850
- function splitForContentRef(node, transformation, originalId, innerId, ctx) {
2851
- const outerBody = liftBodyTranslate(node, transformation);
3026
+ function splitForContentRef(node, transformBy, originalId, innerId, ctx) {
3027
+ const outerBody = liftBodyTranslate(node, transformBy);
2852
3028
  if (typeof node.id === "string") delete node.id;
2853
- const { outer: outerTr, inner: innerTr } = splitTransformationEffect(transformation);
3029
+ const { outer: outerTr, inner: innerTr } = splitTransformByEffect(transformBy);
2854
3030
  let innerNode = node;
2855
- innerNode = applyTransformationEffect(innerNode, innerTr, ctx);
3031
+ innerNode = applyTransformByEffect(innerNode, innerTr, ctx);
2856
3032
  const innerWrapper = { type: "g", id: innerId, children: [innerNode] };
2857
3033
  let outerWrapper = { type: "g", id: originalId, children: [innerWrapper] };
2858
3034
  if (outerBody.transform !== void 0) outerWrapper.transform = outerBody.transform;
2859
3035
  if (outerBody.animate !== void 0) outerWrapper.animate = outerBody.animate;
2860
3036
  if (outerTr) {
2861
3037
  delete outerWrapper.id;
2862
- outerWrapper = applyTransformationEffect(outerWrapper, outerTr, ctx);
3038
+ outerWrapper = applyTransformByEffect(outerWrapper, outerTr, ctx);
2863
3039
  outerWrapper.id = originalId;
2864
3040
  }
2865
3041
  return outerWrapper;
2866
3042
  }
2867
- function liftBodyTranslate(node, transformation) {
3043
+ function liftBodyTranslate(node, transformBy) {
2868
3044
  var _a, _b, _c;
2869
3045
  const out = {};
2870
3046
  let didLiftAnimate = false;
@@ -2918,7 +3094,7 @@ function liftBodyTranslate(node, transformation) {
2918
3094
  didLiftAnimate = true;
2919
3095
  }
2920
3096
  }
2921
- const transformationHasTranslate = (transformation == null ? void 0 : transformation.translate) !== void 0;
3097
+ const transformationHasTranslate = (transformBy == null ? void 0 : transformBy.translate) !== void 0;
2922
3098
  const stripBodyTranslateOnly = didLiftAnimate || transformationHasTranslate;
2923
3099
  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);
2924
3100
  if (typeof node.transform === "string") {
@@ -2937,6 +3113,24 @@ function liftBodyTranslate(node, transformation) {
2937
3113
  if (split.rest) node.transform = split.rest;
2938
3114
  else delete node.transform;
2939
3115
  }
3116
+ } else if (node.transform && typeof node.transform === "object" && !Array.isArray(node.transform) && !node.transform.keyframes && !node.transform.kfs) {
3117
+ const wrapped = node.transform.value;
3118
+ const isWrapped = !!(wrapped && typeof wrapped === "object");
3119
+ const value = isWrapped ? wrapped : node.transform;
3120
+ const rewrap = (rec) => isWrapped ? { value: rec } : rec;
3121
+ if (Array.isArray(value.translate)) {
3122
+ const rest = __spreadValues({}, value);
3123
+ delete rest.translate;
3124
+ const hasRest = Object.keys(rest).length > 0;
3125
+ if (stripBodyTranslateOnly) {
3126
+ if (hasRest) node.transform = rewrap(rest);
3127
+ else delete node.transform;
3128
+ } else {
3129
+ out.transform = rewrap({ translate: value.translate });
3130
+ if (hasRest) node.transform = rewrap(rest);
3131
+ else delete node.transform;
3132
+ }
3133
+ }
2940
3134
  }
2941
3135
  return out;
2942
3136
  }
@@ -3006,7 +3200,7 @@ function parseTranslateArgs(translateOp) {
3006
3200
  const nums = m[1].split(/[\s,]+/).filter(Boolean).map(Number);
3007
3201
  return [nums[0] || 0, nums[1] || 0];
3008
3202
  }
3009
- function splitTransformationEffect(fx) {
3203
+ function splitTransformByEffect(fx) {
3010
3204
  if (!fx) return {};
3011
3205
  const originOnOuter = needsOriginOnOuter(fx.translate);
3012
3206
  const innerHasPivotedPart = fx.rotate !== void 0 || fx.scale !== void 0;
@@ -3033,28 +3227,6 @@ function needsOriginOnOuter(translateAnim) {
3033
3227
  return false;
3034
3228
  }
3035
3229
 
3036
- // src/effects/util.ts
3037
- function genId(ctx, prefix) {
3038
- return "_lw_" + prefix + "_" + ctx.nextId++;
3039
- }
3040
- function stripHash2(href) {
3041
- return typeof href === "string" ? href.replace(/^#/, "") : void 0;
3042
- }
3043
- function indexById(node, map) {
3044
- var _a;
3045
- if (typeof node.id === "string") map.set(node.id, node);
3046
- (_a = node.children) == null ? void 0 : _a.forEach((child) => indexById(child, map));
3047
- }
3048
- function spliceDefs(root, defs) {
3049
- if (!defs.length) return;
3050
- const existing = root.children || (root.children = []);
3051
- existing.unshift({ type: "defs", children: defs });
3052
- }
3053
- var clone = deepClonePxNode;
3054
- function regenerateIdsInClone(root, ctx) {
3055
- return regenerateIdsAndRewriteRefs(root, () => genId(ctx, "retimed"));
3056
- }
3057
-
3058
3230
  // src/effects/gradientEffect.ts
3059
3231
  function applyFillGradientEffect(node, fx, ctx) {
3060
3232
  return applyGradient(node, fx, ctx, "fill");
@@ -3076,62 +3248,63 @@ function synthesiseGradientDef(fx, id, ctx) {
3076
3248
  id
3077
3249
  };
3078
3250
  if (fx.type === PxGradientType.linear) {
3079
- if (fx.p1) {
3080
- out.x1 = String(fx.p1[0]);
3081
- out.y1 = String(fx.p1[1]);
3082
- }
3083
- if (fx.p2) {
3084
- out.x2 = String(fx.p2[0]);
3085
- out.y2 = String(fx.p2[1]);
3086
- }
3251
+ applyGeomVec(out, "x1", "y1", fx.p1);
3252
+ applyGeomVec(out, "x2", "y2", fx.p2);
3087
3253
  } else {
3088
- if (fx.c) {
3089
- out.cx = String(fx.c[0]);
3090
- out.cy = String(fx.c[1]);
3091
- }
3092
- if (fx.r !== void 0) out.r = String(fx.r);
3093
- if (fx.fp) {
3094
- out.fx = String(fx.fp[0]);
3095
- out.fy = String(fx.fp[1]);
3096
- }
3254
+ applyGeomVec(out, "cx", "cy", fx.c);
3255
+ applyGeomNumber(out, "r", fx.r);
3256
+ applyGeomVec(out, "fx", "fy", fx.fp);
3097
3257
  }
3098
3258
  if (fx.gradientUnits) out.gradientUnits = fx.gradientUnits;
3099
3259
  if (fx.spreadMethod) out.spreadMethod = fx.spreadMethod;
3100
3260
  if (fx.gradientTransform) out.gradientTransform = fx.gradientTransform;
3101
- const animate = fx.animate;
3102
- if (animate) {
3103
- const mapped = {};
3104
- for (const wireKey of Object.keys(animate)) {
3105
- const attr = GRADIENT_ANIM_CHANNEL_TO_ATTR[wireKey];
3106
- if (attr) mapped[attr] = animate[wireKey];
3107
- }
3108
- if (Object.keys(mapped).length) out.animate = mapped;
3109
- }
3110
3261
  out.children = buildStopChildren(fx.stops, ctx);
3111
3262
  return out;
3112
3263
  }
3113
- var GRADIENT_ANIM_CHANNEL_TO_ATTR = {
3114
- gradientX1: "x1",
3115
- gradientY1: "y1",
3116
- gradientX2: "x2",
3117
- gradientY2: "y2",
3118
- gradientCx: "cx",
3119
- gradientCy: "cy",
3120
- gradientFx: "fx",
3121
- gradientFy: "fy",
3122
- gradientR: "r"
3123
- };
3264
+ function applyGeomVec(out, xAttr, yAttr, raw) {
3265
+ var _a, _b, _c;
3266
+ const read = readAnimatable(raw);
3267
+ if (read.kind === "absent" /* Absent */) return;
3268
+ if (read.kind === "static" /* Static */) {
3269
+ out[xAttr] = String(read.value[0]);
3270
+ out[yAttr] = String(read.value[1]);
3271
+ return;
3272
+ }
3273
+ const axisChannel = (idx) => {
3274
+ const block = {
3275
+ keyframes: read.keyframes.map((kf) => {
3276
+ const axisKf = { time: kf.time, value: Array.isArray(kf.value) ? kf.value[idx] : void 0 };
3277
+ if (kf.easing !== void 0) axisKf.easing = kf.easing;
3278
+ return axisKf;
3279
+ })
3280
+ };
3281
+ if (read.loop !== void 0) block.loop = read.loop;
3282
+ return block;
3283
+ };
3284
+ const animate = (_a = out.animate) != null ? _a : {};
3285
+ animate[xAttr] = axisChannel(0);
3286
+ animate[yAttr] = axisChannel(1);
3287
+ out.animate = animate;
3288
+ const baseline = (_c = read.base) != null ? _c : (_b = read.keyframes[0]) == null ? void 0 : _b.value;
3289
+ if (Array.isArray(baseline)) {
3290
+ out[xAttr] = String(baseline[0]);
3291
+ out[yAttr] = String(baseline[1]);
3292
+ }
3293
+ }
3294
+ function applyGeomNumber(out, attrName, raw) {
3295
+ const read = readAnimatable(raw);
3296
+ if (read.kind === "absent" /* Absent */) return;
3297
+ writeAnimatableChannel(out, attrName, read, { asString: true });
3298
+ }
3124
3299
  function buildStopChildren(stops, ctx) {
3125
3300
  var _a, _b, _c, _d;
3126
3301
  if (!stops) return [];
3127
- if (Array.isArray(stops)) return stops.map(staticStopNode);
3128
- if (typeof stops === "object" && Array.isArray(stops.value)) {
3129
- return stops.value.map(staticStopNode);
3130
- }
3131
- const animBlock = stops;
3132
- const kfs = animBlock.keyframes;
3133
- if (!Array.isArray(kfs) || !kfs.length) return [];
3134
- const loopFromSource = animBlock.loop;
3302
+ const read = readAnimatable(stops);
3303
+ if (read.kind === "absent" /* Absent */) return [];
3304
+ if (read.kind === "static" /* Static */) return Array.isArray(read.value) ? read.value.map(staticStopNode) : [];
3305
+ const kfs = read.keyframes;
3306
+ if (!kfs.length) return [];
3307
+ const loopFromSource = read.loop;
3135
3308
  let stopCount = 0;
3136
3309
  for (const kf of kfs) {
3137
3310
  const v = (_a = kf.value) != null ? _a : kf.v;
@@ -3208,38 +3381,62 @@ function formatOffset(o) {
3208
3381
 
3209
3382
  // src/effects/clipPathEffect.ts
3210
3383
  function applyClipPathEffect(node, fx, ctx) {
3384
+ var _a, _b;
3211
3385
  if (!fx || !fx.d && !fx.animate) return node;
3212
3386
  const clipId = genId(ctx, "clip");
3213
- const pathChild = { type: "path", d: fx.d };
3214
- if (fx.animate) pathChild.animate = { d: fx.animate };
3387
+ const pathChild = { type: "path" };
3388
+ const read = readAnimatable(fx.d);
3389
+ if (read.kind !== "absent" /* Absent */) {
3390
+ if (read.kind === "static" /* Static */) {
3391
+ pathChild.d = pathString(read.value);
3392
+ } else {
3393
+ writeAnimatableChannel(pathChild, "d", read);
3394
+ if (pathChild.d !== void 0) pathChild.d = pathString(pathChild.d);
3395
+ }
3396
+ }
3397
+ if (fx.animate && !((_a = pathChild.animate) == null ? void 0 : _a.d)) {
3398
+ const animate = (_b = pathChild.animate) != null ? _b : {};
3399
+ animate.d = fx.animate;
3400
+ pathChild.animate = animate;
3401
+ }
3215
3402
  ctx.defs.push({ type: "clipPath", id: clipId, children: [pathChild] });
3216
3403
  node.clipPath = "url(#" + clipId + ")";
3217
3404
  return node;
3218
3405
  }
3406
+ function pathString(v) {
3407
+ if (typeof v === "string") return v;
3408
+ if (v && typeof v === "object" && typeof v.path === "string") return v.path;
3409
+ return void 0;
3410
+ }
3219
3411
 
3220
3412
  // src/effects/maskedByEffect.ts
3221
- function applyMaskedByEffect(node, fx, transformation, ctx) {
3413
+ function applyMaskedByEffect(node, fx, transformBy, ctx) {
3222
3414
  if (!fx) return node;
3223
- if (!fx.href) {
3224
- ctx.errors.push("maskedBy.href missing \u2014 cannot build mask");
3415
+ const sourceId = stripHash2(fx.sourceId);
3416
+ if (!sourceId) {
3417
+ ctx.errors.push("maskedBy.sourceId missing \u2014 cannot build mask");
3225
3418
  return node;
3226
3419
  }
3227
3420
  const maskId = genId(ctx, "mask");
3228
- let content = { type: "use", href: "#" + fx.href };
3229
- if (transformation) {
3230
- content = wrapInverseTransform(content, transformation, ctx);
3421
+ let content = { type: "use", href: "#" + sourceId };
3422
+ if (transformBy) {
3423
+ content = wrapInverseTransform(content, transformBy, ctx);
3231
3424
  } else if (hasAnimateTransform(node)) {
3232
3425
  content = wrapInverseAnimatedBodyTransform(content, node, ctx);
3233
3426
  } else {
3234
3427
  const bodyStatic = readTransformationFromBody(node);
3235
3428
  if (bodyStatic) content = wrapInverseTransform(content, bodyStatic, ctx);
3236
3429
  }
3237
- const includeTargetOwn = transformation === void 0 && !nodeHasBodyTransform(node);
3238
- content = wrapAncestorChainCompensation(content, node, fx.href, ctx, includeTargetOwn);
3430
+ const includeTargetOwn = transformBy === void 0 && !nodeHasBodyTransform(node);
3431
+ content = wrapAncestorChainCompensation(content, node, sourceId, ctx, includeTargetOwn);
3239
3432
  const mask = { type: "mask", id: maskId, children: [content] };
3240
3433
  if (fx.maskType) mask.maskType = fx.maskType;
3241
3434
  if (fx.maskUnits) mask.maskUnits = fx.maskUnits;
3242
3435
  if (fx.maskContentUnits) mask.maskContentUnits = fx.maskContentUnits;
3436
+ if (fx.x !== void 0) mask.x = String(fx.x);
3437
+ if (fx.y !== void 0) mask.y = String(fx.y);
3438
+ if (fx.width !== void 0) mask.width = String(fx.width);
3439
+ if (fx.height !== void 0) mask.height = String(fx.height);
3243
3440
  ctx.defs.push(mask);
3244
3441
  node.mask = "url(#" + maskId + ")";
3245
3442
  return node;
@@ -3346,6 +3543,19 @@ function readTransformationFromBody(node) {
3346
3543
  if (parts.origin) out.origin = parts.origin;
3347
3544
  return Object.keys(out).length ? out : void 0;
3348
3545
  }
3546
+ if (node.transform && typeof node.transform === "object" && !node.transform.keyframes && !node.transform.kfs) {
3547
+ const wrapped = node.transform.value;
3548
+ const value = wrapped && typeof wrapped === "object" ? wrapped : node.transform;
3549
+ if (value && typeof value === "object") {
3550
+ const out = {};
3551
+ if (Array.isArray(value.translate)) out.translate = value.translate;
3552
+ if (typeof value.rotate === "number") out.rotate = value.rotate;
3553
+ if (typeof value.skew === "number") out.skew = value.skew;
3554
+ if (Array.isArray(value.scale)) out.scale = { value: value.scale };
3555
+ if (Array.isArray(value.origin)) out.origin = value.origin;
3556
+ return Object.keys(out).length ? out : void 0;
3557
+ }
3558
+ }
3349
3559
  return void 0;
3350
3560
  }
3351
3561
  function parseTransformStringToParts(s) {
@@ -3479,10 +3689,10 @@ function collectMaskAncestorChains(root, ctx) {
3479
3689
  const interestingNodes = /* @__PURE__ */ new Set();
3480
3690
  const collectInterestingNodes = (n) => {
3481
3691
  var _a, _b;
3482
- const href = (_b = (_a = n.effects) == null ? void 0 : _a.maskedBy) == null ? void 0 : _b.href;
3483
- if (typeof href === "string") {
3692
+ const maskSourceId = stripHash2((_b = (_a = n.effects) == null ? void 0 : _a.maskedBy) == null ? void 0 : _b.sourceId);
3693
+ if (typeof maskSourceId === "string") {
3484
3694
  interestingNodes.add(n);
3485
- const sourceNode = ctx.idMap.get(href);
3695
+ const sourceNode = ctx.idMap.get(maskSourceId);
3486
3696
  if (sourceNode) interestingNodes.add(sourceNode);
3487
3697
  }
3488
3698
  if (Array.isArray(n.children)) for (const ch of n.children) collectInterestingNodes(ch);
@@ -3508,8 +3718,9 @@ function extractTranslateOnly(node, ctx) {
3508
3718
  if (typeof tr === "string") {
3509
3719
  const parts = parseTranslateOnlyFromString(tr, ctx);
3510
3720
  if (parts) out.translate = parts;
3511
- } else if (tr && typeof tr === "object") {
3512
- const value = tr.value;
3721
+ } else if (tr && typeof tr === "object" && !tr.keyframes && !tr.kfs) {
3722
+ const wrapped = tr.value;
3723
+ const value = wrapped && typeof wrapped === "object" ? wrapped : tr;
3513
3724
  if (value && typeof value === "object" && Array.isArray(value.translate)) {
3514
3725
  out.translate = [value.translate[0] || 0, value.translate[1] || 0];
3515
3726
  }
@@ -3559,17 +3770,17 @@ function parseTranslateOnlyFromString(s, ctx) {
3559
3770
  var CONTENT_SUBREF = "content";
3560
3771
  function applyRefHref(node, clone2, ctx) {
3561
3772
  if (!clone2) return;
3562
- const baseId = clone2.baseId;
3563
- if (!baseId) {
3564
- if (clone2.type === CONTENT_SUBREF) ctx.errors.push("clone: content ref missing baseId");
3773
+ const sourceId = stripHash2(clone2.sourceId);
3774
+ if (!sourceId) {
3775
+ if (clone2.type === CONTENT_SUBREF) ctx.errors.push("clone: content ref missing sourceId");
3565
3776
  return;
3566
3777
  }
3567
- const targetId = clone2.type === CONTENT_SUBREF ? ctx.contentRefInnerIds.get(baseId) || baseId : baseId;
3778
+ const targetId = clone2.type === CONTENT_SUBREF ? ctx.contentRefInnerIds.get(sourceId) || sourceId : sourceId;
3568
3779
  node.href = "#" + targetId;
3569
3780
  }
3570
- function applyRefAndTransformationEffect(node, clone2, transformation, ctx) {
3781
+ function applyRefAndTransformationEffect(node, clone2, transformBy, ctx) {
3571
3782
  applyRefHref(node, clone2, ctx);
3572
- return applyTransformationEffect(node, transformation, ctx);
3783
+ return applyTransformByEffect(node, transformBy, ctx);
3573
3784
  }
3574
3785
 
3575
3786
  // src/effects/repeaterEffect.ts
@@ -3594,7 +3805,7 @@ function applyRepeaterEffect(node, fx, ctx) {
3594
3805
  for (let i = 1; i < copies; i++) {
3595
3806
  const baseClone = clone(base);
3596
3807
  const synthFx = synthesisePerCopyFx(fx, i);
3597
- const wrapped = synthFx ? applyTransformationEffect(baseClone, synthFx, ctx) : baseClone;
3808
+ const wrapped = synthFx ? applyTransformByEffect(baseClone, synthFx, ctx) : baseClone;
3598
3809
  children.push(wrapped);
3599
3810
  }
3600
3811
  const wrapper = { type: "g", children };
@@ -3606,10 +3817,13 @@ function applyRepeaterEffect(node, fx, ctx) {
3606
3817
  function synthesisePerCopyFx(fx, i) {
3607
3818
  const out = {};
3608
3819
  if (fx.translate !== void 0) {
3609
- out.translate = mapAnimatableVec2(fx.translate, (v) => [v[0] * i, v[1] * i]);
3820
+ out.translate = mapAnimatable(fx.translate, (v) => [v[0] * i, v[1] * i]);
3610
3821
  }
3611
3822
  if (fx.rotate !== void 0) {
3612
- out.rotate = mapAnimatableNumber(fx.rotate, (v) => v * i);
3823
+ out.rotate = mapAnimatable(fx.rotate, (v) => v * i);
3824
+ }
3825
+ if (fx.skew !== void 0) {
3826
+ out.skew = mapAnimatable(fx.skew, (v) => v * i);
3613
3827
  }
3614
3828
  if (fx.scale !== void 0) {
3615
3829
  out.scale = synthesiseScale(fx.scale, i);
@@ -3619,65 +3833,52 @@ function synthesisePerCopyFx(fx, i) {
3619
3833
  }
3620
3834
  return Object.keys(out).length ? out : void 0;
3621
3835
  }
3622
- function mapAnimatableVec2(raw, fn) {
3623
- if (Array.isArray(raw)) return fn(raw);
3624
- if (raw && typeof raw === "object") {
3625
- const obj = raw;
3626
- if (Array.isArray(obj.keyframes)) {
3627
- return __spreadProps(__spreadValues({}, obj), {
3628
- keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: fn(kf.value) }) : kf)
3629
- });
3630
- }
3631
- if (obj.value !== void 0) {
3632
- return __spreadProps(__spreadValues({}, obj), { value: fn(obj.value) });
3633
- }
3634
- }
3635
- return raw;
3636
- }
3637
- function mapAnimatableNumber(raw, fn) {
3638
- if (typeof raw === "number") return fn(raw);
3639
- if (raw && typeof raw === "object") {
3640
- const obj = raw;
3641
- if (Array.isArray(obj.keyframes)) {
3642
- return __spreadProps(__spreadValues({}, obj), {
3643
- keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: fn(kf.value) }) : kf)
3644
- });
3645
- }
3646
- if (obj.value !== void 0) {
3647
- return __spreadProps(__spreadValues({}, obj), { value: fn(obj.value) });
3648
- }
3836
+ function mapAnimatable(raw, fn, wrapStatic = false) {
3837
+ const read = readAnimatable(raw);
3838
+ if (read.kind === "absent" /* Absent */) return raw;
3839
+ if (read.kind === "static" /* Static */) {
3840
+ const mapped = fn(read.value);
3841
+ const wasRawStatic = typeof raw === "number" || Array.isArray(raw);
3842
+ return wasRawStatic && !wrapStatic ? mapped : { value: mapped };
3649
3843
  }
3650
- return raw;
3844
+ const out = {
3845
+ keyframes: read.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: fn(kf.value) }) : kf)
3846
+ };
3847
+ if (read.loop !== void 0) out.loop = read.loop;
3848
+ if (read.autoOrient !== void 0) out.autoOrient = read.autoOrient;
3849
+ if (read.base !== void 0) out.value = fn(read.base);
3850
+ return out;
3651
3851
  }
3652
3852
  function synthesiseScale(raw, i) {
3653
- const scalePowerFromPercent = (v) => [Math.pow(v[0] / 100, i), Math.pow(v[1] / 100, i)];
3654
- const scalePowerFromUnits = (v) => [Math.pow(v[0], i), Math.pow(v[1], i)];
3655
- if (Array.isArray(raw)) {
3656
- return { value: scalePowerFromPercent(raw) };
3657
- }
3658
- if (raw && typeof raw === "object") {
3659
- const obj = raw;
3660
- if (Array.isArray(obj.keyframes)) {
3661
- return __spreadProps(__spreadValues({}, obj), {
3662
- keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: scalePowerFromUnits(kf.value) }) : kf)
3663
- });
3664
- }
3665
- if (obj.value !== void 0) {
3666
- return __spreadProps(__spreadValues({}, obj), { value: scalePowerFromPercent(obj.value) });
3667
- }
3668
- }
3669
- return raw;
3853
+ const scalePower = (v) => [Math.pow(v[0], i), Math.pow(v[1], i)];
3854
+ return mapAnimatable(raw, scalePower, true);
3670
3855
  }
3671
3856
 
3672
3857
  // src/effects/retimeEffect.ts
3673
3858
  var RETIME_MATERIALISATION_MODE_INLINE_G = false;
3674
3859
  function asRetime(r) {
3675
3860
  var _a, _b;
3676
- if (r.timeCrop) {
3677
- console.warn("retime: `timeCrop` is not supported yet and will be ignored");
3678
- }
3679
3861
  return { start: (_a = r.start) != null ? _a : 0, stretch: (_b = r.stretch) != null ? _b : 1 };
3680
3862
  }
3863
+ var CROP_EDGE_MS = 1;
3864
+ function applyTimeCrop(useNode, crop, ctx) {
3865
+ const [start, end] = crop;
3866
+ if (!Number.isFinite(start) || !Number.isFinite(end)) {
3867
+ ctx.warnings.push("retime: timeCrop is not a pair of finite numbers \u2014 ignored");
3868
+ return;
3869
+ }
3870
+ const keyframes = end <= start ? [{ time: 0, value: 0 }] : [
3871
+ ...start > 0 ? [{ time: Math.max(0, start - CROP_EDGE_MS), value: 0 }] : [],
3872
+ { time: Math.max(0, start), value: 1 },
3873
+ { time: end, value: 1 },
3874
+ { time: end + CROP_EDGE_MS, value: 0 }
3875
+ ];
3876
+ const inner = __spreadValues({}, useNode);
3877
+ for (const k of Object.keys(useNode)) delete useNode[k];
3878
+ useNode.type = "g";
3879
+ useNode.children = [inner];
3880
+ useNode.animate = { opacity: { keyframes } };
3881
+ }
3681
3882
  function concatRetime(child, parent) {
3682
3883
  return {
3683
3884
  start: parent.start + parent.stretch * child.start,
@@ -3737,8 +3938,10 @@ function applyAllRetimeEffects(root, ctx) {
3737
3938
  for (const useNode of sites) {
3738
3939
  const retime = readCloneRetime(useNode);
3739
3940
  if (!retime) continue;
3941
+ const crop = retime.timeCrop;
3740
3942
  clearCloneRetime(useNode);
3741
3943
  materialiseRetime(useNode, asRetime(retime), ctx);
3944
+ if (crop) applyTimeCrop(useNode, crop, ctx);
3742
3945
  }
3743
3946
  }
3744
3947
  function materialiseRetime(useNode, retime, ctx) {
@@ -3985,14 +4188,11 @@ function createPathSampler(d) {
3985
4188
  // src/effects/textPathEffect.ts
3986
4189
  var EXTEND_MARGIN_FRAC = 0.15;
3987
4190
  function numRange(v) {
3988
- if (typeof v === "number") return { min: v, max: v };
3989
- if (v && typeof v === "object") {
3990
- const o = v;
3991
- if (typeof o.value === "number") return { min: o.value, max: o.value };
3992
- if (Array.isArray(o.keyframes) && o.keyframes.length) {
3993
- const vals = o.keyframes.map((k) => Number(k.value) || 0);
3994
- return { min: Math.min(...vals), max: Math.max(...vals) };
3995
- }
4191
+ const read = readAnimatable(v);
4192
+ if (read.kind === "static" /* Static */ && typeof read.value === "number") return { min: read.value, max: read.value };
4193
+ if (read.kind === "animated" /* Animated */ && read.keyframes.length) {
4194
+ const vals = read.keyframes.map((k) => Number(k.value) || 0);
4195
+ return { min: Math.min(...vals), max: Math.max(...vals) };
3996
4196
  }
3997
4197
  return { min: 0, max: 0 };
3998
4198
  }
@@ -4012,11 +4212,16 @@ function estimateTextAdvance(node) {
4012
4212
  var r3 = (n) => Math.round(n * 1e3) / 1e3;
4013
4213
  function shiftAnimatable(v, by) {
4014
4214
  if (!by || v === void 0 || v === null) return v;
4015
- if (typeof v === "number") return v + by;
4016
- const o = v;
4017
- if (typeof o.value === "number") return __spreadProps(__spreadValues({}, o), { value: o.value + by });
4018
- if (Array.isArray(o.keyframes)) return __spreadProps(__spreadValues({}, o), { keyframes: o.keyframes.map((k) => __spreadProps(__spreadValues({}, k), { value: (Number(k.value) || 0) + by })) });
4019
- return v;
4215
+ const read = readAnimatable(v);
4216
+ if (read.kind === "absent" /* Absent */) return v;
4217
+ if (read.kind === "static" /* Static */) return typeof v === "number" ? read.value + by : { value: read.value + by };
4218
+ const out = {
4219
+ keyframes: read.keyframes.map((k) => __spreadProps(__spreadValues({}, k), { value: (Number(k.value) || 0) + by }))
4220
+ };
4221
+ if (read.loop !== void 0) out.loop = read.loop;
4222
+ if (read.autoOrient !== void 0) out.autoOrient = read.autoOrient;
4223
+ if (read.base !== void 0) out.value = read.base + by;
4224
+ return out;
4020
4225
  }
4021
4226
  function extendedPathForBrowser(pathD, opts) {
4022
4227
  var _a;
@@ -4072,26 +4277,7 @@ function applyTextPathEffect(node, fx, ctx) {
4072
4277
  }
4073
4278
  function applyAnimatableNumber(node, attrName, raw) {
4074
4279
  if (raw === void 0 || raw === null) return;
4075
- if (typeof raw === "number") {
4076
- node[attrName] = String(raw);
4077
- return;
4078
- }
4079
- if (typeof raw === "object" && raw !== null) {
4080
- const obj = raw;
4081
- if (Array.isArray(obj.keyframes)) {
4082
- const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
4083
- const animate = __spreadValues({}, prevAnimate || {});
4084
- const block = { keyframes: obj.keyframes };
4085
- if (obj.loop !== void 0) block.loop = obj.loop;
4086
- animate[attrName] = block;
4087
- node.animate = animate;
4088
- return;
4089
- }
4090
- if (typeof obj.value === "number") {
4091
- node[attrName] = String(obj.value);
4092
- return;
4093
- }
4094
- }
4280
+ writeAnimatableChannel(node, attrName, readAnimatable(raw), { asString: true });
4095
4281
  }
4096
4282
 
4097
4283
  // src/effects/elementFactory.ts
@@ -4285,13 +4471,13 @@ function materialiseGlyphTextHorizontal(node, opts) {
4285
4471
  if (y !== void 0) pen.y = y;
4286
4472
  pen.x += (_a2 = parseLen(el.dx)) != null ? _a2 : 0;
4287
4473
  pen.y += (_b2 = parseLen(el.dy)) != null ? _b2 : 0;
4288
- const content = (_c2 = str(el[TEXT_ATTR])) != null ? _c2 : str(el[TEXT_CONTENT_ATTR]);
4474
+ const content = (_c2 = str(el[TEXT_CONTENT_ATTR])) != null ? _c2 : str(el[TEXT_ATTR]);
4289
4475
  if (content && !((_d2 = el.children) == null ? void 0 : _d2.length)) renderChars(content, s);
4290
4476
  if (el.children) for (const ch of el.children) walk(ch, s);
4291
4477
  };
4292
4478
  const rootStyle = rootStyleOf(node);
4293
4479
  if (node.children) for (const ch of node.children) walk(ch, rootStyle);
4294
- const rootContent = (_c = str(node[TEXT_ATTR])) != null ? _c : str(node[TEXT_CONTENT_ATTR]);
4480
+ const rootContent = (_c = str(node[TEXT_CONTENT_ATTR])) != null ? _c : str(node[TEXT_ATTR]);
4295
4481
  if (rootContent && !((_d = node.children) == null ? void 0 : _d.length)) renderChars(rootContent, rootStyle);
4296
4482
  const anchor = str(node.textAnchor);
4297
4483
  if (anchor === "middle" || anchor === "end") {
@@ -4340,7 +4526,7 @@ function layoutGlyphTextChars(node, opts) {
4340
4526
  if (y !== void 0) pen.y = y;
4341
4527
  pen.x += (_a2 = parseLen(el.dx)) != null ? _a2 : 0;
4342
4528
  pen.y += (_b2 = parseLen(el.dy)) != null ? _b2 : 0;
4343
- const content = (_c2 = str(el[TEXT_ATTR])) != null ? _c2 : str(el[TEXT_CONTENT_ATTR]);
4529
+ const content = (_c2 = str(el[TEXT_CONTENT_ATTR])) != null ? _c2 : str(el[TEXT_ATTR]);
4344
4530
  if (content && !((_d2 = el.children) == null ? void 0 : _d2.length)) renderChars(content, s);
4345
4531
  if (el.children) for (const ch of el.children) walk(ch, s);
4346
4532
  };
@@ -4355,7 +4541,7 @@ function layoutGlyphTextChars(node, opts) {
4355
4541
  boxes.push({ x: pen.x, y: pen.y, width: 0, ascent: ((_d = gf == null ? void 0 : gf.ascent) != null ? _d : 0.9 * upm) * (s.fontSize / upm), fontSize: s.fontSize, line });
4356
4542
  }
4357
4543
  }
4358
- const rootContent = (_e = str(node[TEXT_ATTR])) != null ? _e : str(node[TEXT_CONTENT_ATTR]);
4544
+ const rootContent = (_e = str(node[TEXT_CONTENT_ATTR])) != null ? _e : str(node[TEXT_ATTR]);
4359
4545
  if (rootContent && !((_f = node.children) == null ? void 0 : _f.length)) renderChars(rootContent, rootStyle);
4360
4546
  const anchor = str(node.textAnchor);
4361
4547
  if (anchor === "middle" || anchor === "end") {
@@ -4383,7 +4569,7 @@ function layoutGlyphTextCharsAlongPath(node, pathD, opts) {
4383
4569
  const walk = (el, parentStyle) => {
4384
4570
  var _a2, _b2, _c;
4385
4571
  const s = resolveStyle2(el, parentStyle);
4386
- const content = (_a2 = str(el[TEXT_ATTR])) != null ? _a2 : str(el[TEXT_CONTENT_ATTR]);
4572
+ const content = (_a2 = str(el[TEXT_CONTENT_ATTR])) != null ? _a2 : str(el[TEXT_ATTR]);
4387
4573
  if (content && !((_b2 = el.children) == null ? void 0 : _b2.length)) {
4388
4574
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
4389
4575
  const upm = (gf == null ? void 0 : gf.unitsPerEm) || 1e3;
@@ -4437,7 +4623,7 @@ function collectAlongPathCells(node, glyphs, soleFont, warnings) {
4437
4623
  const walk = (el, parentStyle) => {
4438
4624
  var _a, _b, _c;
4439
4625
  const s = resolveStyle2(el, parentStyle);
4440
- const content = (_a = str(el[TEXT_ATTR])) != null ? _a : str(el[TEXT_CONTENT_ATTR]);
4626
+ const content = (_a = str(el[TEXT_CONTENT_ATTR])) != null ? _a : str(el[TEXT_ATTR]);
4441
4627
  if (content && !((_b = el.children) == null ? void 0 : _b.length)) {
4442
4628
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
4443
4629
  if (gf) {
@@ -4661,9 +4847,9 @@ function applyTextGlyphsAlongPath(node, ctx, pathD, startOffset, textLength, pat
4661
4847
  }
4662
4848
 
4663
4849
  // src/effects/trimPathEffect.ts
4664
- function applyTrimPathEffect(node, trimPath, isCombinedShape, ctx) {
4850
+ function applyTrimPathEffect(node, trimPath, ctx) {
4665
4851
  if (!trimPath) return node;
4666
- const trimAllAsOne = !!trimPath.trimAllAsOne;
4852
+ const combined = trimPath.subPaths === PxTrimSubPaths.combined;
4667
4853
  const leafEntries = [];
4668
4854
  const measure = (n) => {
4669
4855
  if (Array.isArray(n.children) && n.children.length > 0) {
@@ -4684,16 +4870,16 @@ function applyTrimPathEffect(node, trimPath, isCombinedShape, ctx) {
4684
4870
  measure(node);
4685
4871
  if (!leafEntries.length) return node;
4686
4872
  let acc = 0;
4687
- const iterOrder = trimAllAsOne ? [...leafEntries].reverse() : leafEntries;
4873
+ const iterOrder = combined ? [...leafEntries].reverse() : leafEntries;
4688
4874
  for (const entry of iterOrder) {
4689
4875
  for (const sp of entry.subpaths) {
4690
- if (!trimAllAsOne) acc = 0;
4876
+ if (!combined) acc = 0;
4691
4877
  sp.startOffsetPx = acc;
4692
4878
  acc += sp.lengthPx;
4693
4879
  }
4694
4880
  }
4695
4881
  const chainLengthPx = acc;
4696
- if (trimAllAsOne && chainLengthPx < 1e-3) return node;
4882
+ if (combined && chainLengthPx < 1e-3) return node;
4697
4883
  const offsetReadRaw = readAnimatable(trimPath.offset);
4698
4884
  const offsetRead = offsetReadRaw.kind === "absent" /* Absent */ ? { kind: "static" /* Static */, value: 0 } : offsetReadRaw;
4699
4885
  const rangeReadRaw = readRangeWithCrossings(trimPath.range);
@@ -4705,18 +4891,18 @@ function applyTrimPathEffect(node, trimPath, isCombinedShape, ctx) {
4705
4891
  if (leafEntries.length === 1 && leafEntries[0].leaf === node && leafEntries[0].subpaths.length === 1) {
4706
4892
  const entry = leafEntries[0];
4707
4893
  const sp = entry.subpaths[0];
4708
- const pathLengthPx = trimAllAsOne ? chainLengthPx : sp.lengthPx;
4894
+ const pathLengthPx = combined ? chainLengthPx : sp.lengthPx;
4709
4895
  if (pathLengthPx < 1e-3) return node;
4710
- const startOffsetPct = trimAllAsOne ? sp.startOffsetPx / pathLengthPx : 0;
4896
+ const startOffsetPct = combined ? sp.startOffsetPx / pathLengthPx : 0;
4711
4897
  return collapseLeafWithTrim(entry.leaf, pathLengthPx, startOffsetPct, minMaxOffset, offsetRead, rangeRead);
4712
4898
  }
4713
4899
  const replacements = /* @__PURE__ */ new Map();
4714
4900
  for (const entry of leafEntries) {
4715
4901
  const newChildren = [];
4716
4902
  for (const sp of entry.subpaths) {
4717
- const pathLengthPx = trimAllAsOne ? chainLengthPx : sp.lengthPx;
4903
+ const pathLengthPx = combined ? chainLengthPx : sp.lengthPx;
4718
4904
  if (pathLengthPx < 1e-3) continue;
4719
- const startOffsetPct = trimAllAsOne ? sp.startOffsetPx / pathLengthPx : 0;
4905
+ const startOffsetPct = combined ? sp.startOffsetPx / pathLengthPx : 0;
4720
4906
  newChildren.push(...buildSubpathNodes(entry.leaf, sp.subpath, pathLengthPx, startOffsetPct, minMaxOffset, offsetRead, rangeRead, ctx));
4721
4907
  }
4722
4908
  replacements.set(entry.leaf, wrapLeafAsGroup(entry.leaf, newChildren));
@@ -4823,22 +5009,8 @@ function computeAnimAttr(read, map) {
4823
5009
  };
4824
5010
  }
4825
5011
  function applyAttr(node, attrName, attr) {
4826
- var _a, _b;
4827
5012
  if (!attr) return;
4828
- if (attr.kind === "static" /* Static */) {
4829
- node[attrName] = attr.value;
4830
- return;
4831
- }
4832
- const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
4833
- const animate = __spreadValues({}, prevAnimate || {});
4834
- const block = { keyframes: attr.keyframes };
4835
- if (attr.loop !== void 0) block.loop = attr.loop;
4836
- animate[attrName] = block;
4837
- node.animate = animate;
4838
- const firstKf = attr.keyframes[0];
4839
- if (firstKf && ((_a = firstKf.value) != null ? _a : firstKf.v) !== void 0) {
4840
- node[attrName] = (_b = firstKf.value) != null ? _b : firstKf.v;
4841
- }
5013
+ writeAnimatableChannel(node, attrName, attr);
4842
5014
  }
4843
5015
  var OPACITY_STEP_MS = 10;
4844
5016
  function computeOpacityFromRange(rangeRead) {
@@ -5022,9 +5194,9 @@ function applyPlayerEffects(root) {
5022
5194
  nextId: 0,
5023
5195
  contentRefInnerIds: /* @__PURE__ */ new Map(),
5024
5196
  maskAncestorChains: /* @__PURE__ */ new Map(),
5025
- // Resolved engine: `frames` ONLY when explicitly set; auto/webapi/unset →
5026
- // webapi (we're not 100% sure it's frames, and CSS/WAAPI need the inline form).
5027
- engine: ((_a = getAnimatorConfig(root)) == null ? void 0 : _a.mode) === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.webapi,
5197
+ // Resolved engine: `frames` ONLY when explicitly set; auto/waapi/unset →
5198
+ // waapi (we're not 100% sure it's frames, and CSS/WAAPI need the inline form).
5199
+ engine: ((_a = getAnimatorConfig(root)) == null ? void 0 : _a.mode) === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.waapi,
5028
5200
  glyphs: (_b = getDefs(root)) == null ? void 0 : _b.glyphs
5029
5201
  };
5030
5202
  const working = clone(root);
@@ -5042,8 +5214,7 @@ function applyPlayerEffects_exceptRetime(node, ctx) {
5042
5214
  const originalId = typeof node.id === "string" ? node.id : void 0;
5043
5215
  const innerIdForContentRef = originalId ? ctx.contentRefInnerIds.get(originalId) : void 0;
5044
5216
  if (!fx && !innerIdForContentRef) return node;
5045
- const { transformation, repeater, maskedBy, clipPath, trimPath, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
5046
- const isCombinedShape = fx == null ? void 0 : fx.isCombinedShape;
5217
+ const { transformBy, repeater, maskedBy, clipPath, trimPath, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
5047
5218
  if (fx) delete node.effects;
5048
5219
  let n = node;
5049
5220
  let consumedByGlyphs = false;
@@ -5063,15 +5234,15 @@ function applyPlayerEffects_exceptRetime(node, ctx) {
5063
5234
  if (!consumedByGlyphs) n = applyTextPathEffect(n, textPath, ctx);
5064
5235
  n = applyFillGradientEffect(n, fillGradient, ctx);
5065
5236
  n = applyStrokeGradientEffect(n, strokeGradient, ctx);
5066
- n = applyTrimPathEffect(n, trimPath, isCombinedShape, ctx);
5237
+ n = applyTrimPathEffect(n, trimPath, ctx);
5067
5238
  n = applyRepeaterEffect(n, repeater, ctx);
5068
- n = applyMaskedByEffect(n, maskedBy, transformation, ctx);
5239
+ n = applyMaskedByEffect(n, maskedBy, transformBy, ctx);
5069
5240
  n = applyClipPathEffect(n, clipPath, ctx);
5070
5241
  if (innerIdForContentRef) {
5071
5242
  applyRefHref(n, cloneFx, ctx);
5072
- n = splitForContentRef(n, transformation, originalId, innerIdForContentRef, ctx);
5243
+ n = splitForContentRef(n, transformBy, originalId, innerIdForContentRef, ctx);
5073
5244
  } else {
5074
- n = applyRefAndTransformationEffect(n, cloneFx, transformation, ctx);
5245
+ n = applyRefAndTransformationEffect(n, cloneFx, transformBy, ctx);
5075
5246
  }
5076
5247
  if (cloneFx == null ? void 0 : cloneFx.retime) node.effects = { clone: { retime: cloneFx.retime } };
5077
5248
  if (originalId) ctx.idMap.set(originalId, n);
@@ -5088,7 +5259,7 @@ function materialiseAllInTree(doc, engine, opts) {
5088
5259
  let root = applyPlayerEffects(doc).root;
5089
5260
  const duration = (_b = (_a = getAnimatorConfig(root)) == null ? void 0 : _a.duration) != null ? _b : DEFAULT_DURATION_MS;
5090
5261
  root = materialiseInternalLoopsInTree(root, duration);
5091
- if (engine === PxAnimatorEngine.webapi) {
5262
+ if (engine === PxAnimatorEngine.waapi) {
5092
5263
  root = materialiseMotionPathsInTree(root, opts == null ? void 0 : opts.motionPath);
5093
5264
  root = materialiseAnimatedUseInstances(root);
5094
5265
  root = pruneUnreferencedDefs(root);
@@ -5477,6 +5648,7 @@ function evalTransformValue(v, t) {
5477
5648
  if (typeof v === "string") return parseTransformString(v);
5478
5649
  if (v.keyframes) return partsToMatrix(interpParts(v.keyframes, t));
5479
5650
  if (v.value) return partsToMatrix(v.value);
5651
+ if (typeof v === "object" && !Array.isArray(v)) return partsToMatrix(v);
5480
5652
  return IDENTITY;
5481
5653
  }
5482
5654
  function nodeMatrix(node, t) {
@@ -5638,6 +5810,8 @@ export {
5638
5810
  PxGradientType,
5639
5811
  PxGradientUnits,
5640
5812
  PxKeyframeSchema,
5813
+ PxKeyframeValueSchema,
5814
+ PxLoopExtend,
5641
5815
  PxLoopSchema,
5642
5816
  PxMaskedByEffectSchema,
5643
5817
  PxNodeBase,
@@ -5649,11 +5823,12 @@ export {
5649
5823
  PxSvgNodeExtra,
5650
5824
  PxTextEffectSchema,
5651
5825
  PxTextPathEffectSchema,
5826
+ PxTransformByEffectSchema,
5652
5827
  PxTransformPartsSchema,
5653
5828
  PxTransformValueSchema,
5654
- PxTransformationEffectSchema,
5655
5829
  PxTriggerSchema,
5656
5830
  PxTrimPathEffectSchema,
5831
+ PxTrimSubPaths,
5657
5832
  STYLE_ATTR_NAMES,
5658
5833
  TEXT_ATTR,
5659
5834
  TEXT_CONTENT_ATTR,