@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.cjs CHANGED
@@ -77,6 +77,8 @@ __export(index_exports, {
77
77
  PxGradientType: () => PxGradientType,
78
78
  PxGradientUnits: () => PxGradientUnits,
79
79
  PxKeyframeSchema: () => PxKeyframeSchema,
80
+ PxKeyframeValueSchema: () => PxKeyframeValueSchema,
81
+ PxLoopExtend: () => PxLoopExtend,
80
82
  PxLoopSchema: () => PxLoopSchema,
81
83
  PxMaskedByEffectSchema: () => PxMaskedByEffectSchema,
82
84
  PxNodeBase: () => PxNodeBase,
@@ -88,11 +90,12 @@ __export(index_exports, {
88
90
  PxSvgNodeExtra: () => PxSvgNodeExtra,
89
91
  PxTextEffectSchema: () => PxTextEffectSchema,
90
92
  PxTextPathEffectSchema: () => PxTextPathEffectSchema,
93
+ PxTransformByEffectSchema: () => PxTransformByEffectSchema,
91
94
  PxTransformPartsSchema: () => PxTransformPartsSchema,
92
95
  PxTransformValueSchema: () => PxTransformValueSchema,
93
- PxTransformationEffectSchema: () => PxTransformationEffectSchema,
94
96
  PxTriggerSchema: () => PxTriggerSchema,
95
97
  PxTrimPathEffectSchema: () => PxTrimPathEffectSchema,
98
+ PxTrimSubPaths: () => PxTrimSubPaths,
96
99
  STYLE_ATTR_NAMES: () => STYLE_ATTR_NAMES,
97
100
  TEXT_ATTR: () => TEXT_ATTR,
98
101
  TEXT_CONTENT_ATTR: () => TEXT_CONTENT_ATTR,
@@ -273,8 +276,9 @@ var Union = class extends Base {
273
276
  }
274
277
  isValid(raw, ctx, path) {
275
278
  var _a;
276
- if (this.schemas.some((s) => s.isValid(raw))) return true;
277
- 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));
279
+ const probe = ctx && { errors: [], warnings: [], strict: ctx.strict };
280
+ if (this.schemas.some((s) => s.isValid(raw, probe, path ? [...path] : void 0))) return true;
281
+ 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));
278
282
  return false;
279
283
  }
280
284
  _canSanitize(raw) {
@@ -350,6 +354,7 @@ var Obj = class extends Base {
350
354
  if (ctx == null ? void 0 : ctx.strict) {
351
355
  for (const key of Object.keys(obj)) {
352
356
  if (key in this._shape) continue;
357
+ if (obj[key] === void 0) continue;
353
358
  p.push(key);
354
359
  ctx.errors.push(pathStr(p) + ": unexpected extra key");
355
360
  p.pop();
@@ -490,6 +495,23 @@ var Any = class extends Base {
490
495
  return true;
491
496
  }
492
497
  };
498
+ var Defined = class extends Base {
499
+ constructor() {
500
+ super(...arguments);
501
+ this._default = void 0;
502
+ }
503
+ sanitize(raw) {
504
+ return raw;
505
+ }
506
+ isValid(raw, ctx, path) {
507
+ if (raw !== void 0) return true;
508
+ ctx == null ? void 0 : ctx.errors.push(pathStr(path != null ? path : []) + ": required value is missing");
509
+ return false;
510
+ }
511
+ _canSanitize(raw) {
512
+ return raw !== void 0;
513
+ }
514
+ };
493
515
  var Lazy = class extends Base {
494
516
  constructor(fn, _default) {
495
517
  super();
@@ -601,24 +623,65 @@ var px = {
601
623
  record: (value) => new Rec(value),
602
624
  /** Passes anything through unchanged — always valid. */
603
625
  any: () => new Any(),
626
+ /** Anything EXCEPT `undefined` — an open type whose presence is required (V6). */
627
+ defined: () => new Defined(),
604
628
  /** Fixed-length tuple — validates element count and each position individually. */
605
629
  tuple: (schemas) => new Tuple(schemas),
606
630
  /** Defers schema creation — required for recursive types. Must supply a default value. */
607
631
  lazy: (fn, defaultVal) => new Lazy(fn, defaultVal)
608
632
  };
609
633
 
610
- // src/PxAnimatorTypes.ts
634
+ // src/PxAnimatorConstants.ts
611
635
  var PX_ANIM_SRC_ATTR_NAME = "data-px-animation-src";
612
636
  var PX_ANIM_ATTR_NAME = "_px_animator";
613
637
  var PxAnimatorMode = {
614
638
  auto: "auto",
615
- webapi: "webapi",
639
+ waapi: "waapi",
616
640
  frames: "frames"
617
641
  };
618
642
  var PxAnimatorEngine = {
619
- webapi: PxAnimatorMode.webapi,
643
+ waapi: PxAnimatorMode.waapi,
620
644
  frames: PxAnimatorMode.frames
621
645
  };
646
+ var PxLoopExtend = {
647
+ /** Segment from the START; the animation is extended BEFORE the first keyframe
648
+ * (intro loops that run before the main timeline begins). */
649
+ before: "before",
650
+ /** DEFAULT — segment from the END; extended AFTER the last keyframe (idle/outro
651
+ * loops that continue once the main timeline has finished). */
652
+ after: "after"
653
+ };
654
+ var PxMaskType = {
655
+ luminance: "luminance",
656
+ alpha: "alpha"
657
+ };
658
+ var PxUnits = {
659
+ userSpaceOnUse: "userSpaceOnUse",
660
+ objectBoundingBox: "objectBoundingBox"
661
+ };
662
+ var PxCloneType = {
663
+ content: "content"
664
+ };
665
+ var PxPathOverflow = {
666
+ clip: "clip",
667
+ extend: "extend"
668
+ };
669
+ var PxLengthAdjust = {
670
+ spacing: "spacing",
671
+ spacingAndGlyphs: "spacingAndGlyphs"
672
+ };
673
+ var PxTextPathMethod = {
674
+ align: "align",
675
+ stretch: "stretch"
676
+ };
677
+ var PxTextPathSpacing = {
678
+ auto: "auto",
679
+ exact: "exact"
680
+ };
681
+ var PxTrimSubPaths = {
682
+ separate: "separate",
683
+ combined: "combined"
684
+ };
622
685
  var TEXT_ATTR = "text";
623
686
  var TEXT_CONTENT_ATTR = "textContent";
624
687
  var INTERNAL_ATTRS = /* @__PURE__ */ new Set([
@@ -627,9 +690,51 @@ var INTERNAL_ATTRS = /* @__PURE__ */ new Set([
627
690
  "animator",
628
691
  "meta",
629
692
  "animate",
693
+ "effects",
630
694
  TEXT_ATTR,
631
695
  TEXT_CONTENT_ATTR
632
696
  ]);
697
+ var PX_TRANSFORM_PART_KEYS = ["translate", "rotate", "scale", "origin"];
698
+ var PxGradientUnits = {
699
+ userSpaceOnUse: "userSpaceOnUse",
700
+ objectBoundingBox: "objectBoundingBox"
701
+ };
702
+ var PxGradientSpreadMethod = {
703
+ pad: "pad",
704
+ reflect: "reflect",
705
+ repeat: "repeat"
706
+ };
707
+ var PxGradientType = {
708
+ linear: "linear",
709
+ radial: "radial"
710
+ };
711
+ function isPxElementFileFormat(fileJson) {
712
+ if (!(fileJson && typeof fileJson === "object" && !Array.isArray(fileJson))) {
713
+ return false;
714
+ }
715
+ return fileJson["type"] === "svg";
716
+ }
717
+ function getAnimatorConfig(doc) {
718
+ var _a;
719
+ return (doc == null ? void 0 : doc.animator) || ((_a = doc == null ? void 0 : doc.meta) == null ? void 0 : _a.animator);
720
+ }
721
+ function getDefs(doc) {
722
+ var _a;
723
+ if (!doc) return void 0;
724
+ return (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.definitions;
725
+ }
726
+ function getBindings(doc) {
727
+ var _a;
728
+ if (!doc) return void 0;
729
+ const animateById = (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.animateById;
730
+ if (!animateById) return void 0;
731
+ return Object.entries(animateById).map(([id, anim]) => ({ id, animate: anim }));
732
+ }
733
+ function getChildren(doc) {
734
+ return doc == null ? void 0 : doc.children;
735
+ }
736
+
737
+ // src/PxAnimatorTypes.ts
633
738
  var PxEasingOrRefSchema = px.union([
634
739
  px.string(),
635
740
  px.tuple([px.number(), px.number(), px.number(), px.number()])
@@ -641,13 +746,15 @@ var PxKeyframeValueSchema = implementsInterface()(px.union([
641
746
  px.array(px.number()),
642
747
  px.lazy(() => PxTransformPartsSchema, {}),
643
748
  px.object({ path: px.string() }),
644
- px.lazy(() => px.object({ paths: px.array(PxBezierPathSchema) }), { paths: [] })
749
+ px.lazy(() => px.object({ paths: px.array(PxBezierPathSchema) }), { paths: [] }),
750
+ // Gradient `stops` timeline — each kf value is the full stops-array snapshot.
751
+ px.lazy(() => px.array(PxGradientStopSchema), [])
645
752
  ]));
646
753
  var PxKeyframeSchema = implementsInterface()(px.object({
647
754
  time: px.number().optional(),
648
755
  t: px.number().optional(),
649
- value: px.any().optional(),
650
- v: px.any().optional(),
756
+ value: PxKeyframeValueSchema.optional(),
757
+ v: PxKeyframeValueSchema.optional(),
651
758
  easing: PxEasingOrRefSchema.optional(),
652
759
  e: PxEasingOrRefSchema.optional(),
653
760
  tangentOut: px.tuple([px.number(), px.number()]).optional(),
@@ -661,16 +768,16 @@ var PxKeyframeSchema = implementsInterface()(px.object({
661
768
  }));
662
769
  var PxLoopSchema = implementsInterface()(px.object({
663
770
  segmentCount: px.number().optional(),
664
- before: px.boolean().optional(),
771
+ extend: px.enum([PxLoopExtend.before, PxLoopExtend.after]).optional(),
665
772
  alternate: px.boolean().optional()
666
773
  }));
667
774
  var PxPropertyAnimationSchema = implementsInterface()(px.object({
775
+ value: PxKeyframeValueSchema.optional(),
668
776
  keyframes: px.array(PxKeyframeSchema).optional(),
669
777
  kfs: px.array(PxKeyframeSchema).optional(),
670
778
  loop: px.union([PxLoopSchema, px.boolean()]).optional(),
671
779
  autoOrient: px.boolean().optional()
672
780
  }));
673
- var PX_TRANSFORM_PART_KEYS = ["translate", "rotate", "scale", "origin"];
674
781
  var PxTransformPartsSchema = implementsInterface()(px.object({
675
782
  translate: px.tuple([px.number(), px.number()]).optional(),
676
783
  rotate: px.number().optional(),
@@ -680,6 +787,7 @@ var PxTransformPartsSchema = implementsInterface()(px.object({
680
787
  }));
681
788
  var PxTransformValueSchema = px.union([
682
789
  px.string(),
790
+ PxTransformPartsSchema,
683
791
  px.object({ value: PxTransformPartsSchema }),
684
792
  PxPropertyAnimationSchema
685
793
  ]);
@@ -714,9 +822,11 @@ var PxDefsSchema = implementsInterface()(px.object({
714
822
  glyphs: px.record(PxGlyphFontSchema).optional()
715
823
  }));
716
824
  var PxAnimatorConfigSchema = implementsInterface()(px.object({
717
- mode: px.enum([PxAnimatorMode.auto, PxAnimatorMode.webapi, PxAnimatorMode.frames]).optional(),
825
+ mode: px.enum([PxAnimatorMode.auto, PxAnimatorMode.waapi, PxAnimatorMode.frames]).optional(),
718
826
  duration: px.number().optional(),
719
827
  delay: px.number().optional(),
828
+ // LAW (SCHEMA-DESIGN R3, S10): the ONE sanctioned string-in-number union
829
+ // (CSS animation-iteration-count familiarity) — do not add more mixed unions.
720
830
  iterations: px.union([px.number(), px.literal("infinite")]).optional(),
721
831
  fill: px.enum(["forwards", "backwards", "both", "none"]).optional(),
722
832
  direction: px.enum(["normal", "reverse", "alternate", "alternate-reverse"]).optional(),
@@ -724,8 +834,8 @@ var PxAnimatorConfigSchema = implementsInterface()(px.object({
724
834
  trigger: PxTriggerSchema.optional(),
725
835
  resetOnFinish: px.boolean().optional(),
726
836
  definitions: PxDefsSchema.optional(),
727
- animate: px.record(PxElementAnimationSchema).optional(),
728
- debug: px.boolean().optional(),
837
+ animateById: px.record(PxElementAnimationSchema).optional(),
838
+ timelineSource: px.string().optional(),
729
839
  debugInstName: px.string().optional()
730
840
  }));
731
841
  var PxBindingSchema = implementsInterface()(px.object({
@@ -735,26 +845,29 @@ var PxBindingSchema = implementsInterface()(px.object({
735
845
  var PxAttrValueSchema = px.union([
736
846
  px.string(),
737
847
  px.number(),
738
- px.object({ value: px.any() }),
739
- PxPropertyAnimationSchema
848
+ px.array(px.number()),
849
+ // Structured static — `{value: …}` (read-accepted transitional spelling, S1).
850
+ // `defined`, not `any`: the KEY's presence is what identifies this branch (V6).
851
+ px.object({ value: px.defined() }),
852
+ // Bare transform parts record — the canonical static `transform` on the wire (T2).
853
+ PxTransformPartsSchema
740
854
  ]);
741
855
  var PxAnimatableNumberSchema = px.union([
742
856
  px.number(),
743
857
  px.object({ value: px.number() }),
744
- px.object({
745
- keyframes: px.array(PxKeyframeSchema),
746
- autoOrient: px.boolean().optional()
747
- })
858
+ PxPropertyAnimationSchema
748
859
  ]);
749
860
  var PxAnimatableVec2Schema = px.union([
750
861
  px.tuple([px.number(), px.number()]),
751
862
  px.object({ value: px.tuple([px.number(), px.number()]) }),
752
- px.object({
753
- keyframes: px.array(PxKeyframeSchema),
754
- autoOrient: px.boolean().optional()
755
- })
863
+ PxPropertyAnimationSchema
864
+ ]);
865
+ var PxAnimatableStringSchema = px.union([
866
+ px.string(),
867
+ px.object({ value: px.string() }),
868
+ PxPropertyAnimationSchema
756
869
  ]);
757
- var PxTransformationEffectSchema = implementsInterface()(px.object({
870
+ var PxTransformByEffectSchema = implementsInterface()(px.object({
758
871
  translate: PxAnimatableVec2Schema.optional(),
759
872
  rotate: PxAnimatableNumberSchema.optional(),
760
873
  scale: PxAnimatableVec2Schema.optional(),
@@ -762,51 +875,46 @@ var PxTransformationEffectSchema = implementsInterface()(px.object({
762
875
  origin: PxAnimatableVec2Schema.optional()
763
876
  }));
764
877
  var PxRepeaterEffectSchema = implementsInterface()(px.object({
878
+ // STATIC config, not a channel (V2/SCHEMA-DESIGN R5): the copy COUNT is read
879
+ // once at expansion time and never sampled — plain number, no `keyframes`.
765
880
  copies: px.number().optional(),
766
881
  translate: PxAnimatableVec2Schema.optional(),
767
882
  rotate: PxAnimatableNumberSchema.optional(),
883
+ skew: PxAnimatableNumberSchema.optional(),
768
884
  scale: PxAnimatableVec2Schema.optional(),
769
885
  origin: PxAnimatableVec2Schema.optional()
770
886
  }));
771
887
  var PxMaskedByEffectSchema = implementsInterface()(px.object({
772
- href: px.string().optional(),
773
- maskType: px.string().optional(),
774
- maskUnits: px.string().optional(),
775
- maskContentUnits: px.string().optional()
888
+ sourceId: px.string().optional(),
889
+ maskType: px.enum([PxMaskType.luminance, PxMaskType.alpha]).optional(),
890
+ maskUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox]).optional(),
891
+ maskContentUnits: px.enum([PxUnits.userSpaceOnUse, PxUnits.objectBoundingBox]).optional(),
892
+ x: px.number().optional(),
893
+ y: px.number().optional(),
894
+ width: px.number().optional(),
895
+ height: px.number().optional()
776
896
  }));
777
897
  var PxClipPathEffectSchema = implementsInterface()(px.object({
778
- d: px.string().optional(),
898
+ d: PxAnimatableStringSchema.optional(),
779
899
  animate: PxPropertyAnimationSchema.optional()
780
900
  }));
781
901
  var PxTrimPathEffectSchema = implementsInterface()(px.object({
782
902
  offset: PxAnimatableNumberSchema.optional(),
783
903
  range: PxAnimatableVec2Schema.optional(),
784
- trimAllAsOne: px.boolean().optional()
904
+ subPaths: px.enum([PxTrimSubPaths.separate, PxTrimSubPaths.combined]).optional()
785
905
  }));
786
906
  var PxRetimeEffectSchema = implementsInterface()(px.object({
787
- baseId: px.string().optional(),
907
+ sourceId: px.string().optional(),
788
908
  start: px.number().optional(),
789
909
  stretch: px.number().optional(),
790
910
  timeCrop: px.tuple([px.number(), px.number()]).optional()
791
911
  }));
792
912
  var PxCloneEffectSchema = implementsInterface()(px.object({
793
- type: px.string().optional(),
794
- baseId: px.string().optional(),
913
+ // Contextual kind — the `type` convention, see `PxNodeBase.type`.
914
+ type: px.enum([PxCloneType.content]).optional(),
915
+ sourceId: px.string().optional(),
795
916
  retime: PxRetimeEffectSchema.optional()
796
917
  }));
797
- var PxGradientUnits = {
798
- userSpaceOnUse: "userSpaceOnUse",
799
- objectBoundingBox: "objectBoundingBox"
800
- };
801
- var PxGradientSpreadMethod = {
802
- pad: "pad",
803
- reflect: "reflect",
804
- repeat: "repeat"
805
- };
806
- var PxGradientType = {
807
- linear: "linear",
808
- radial: "radial"
809
- };
810
918
  var PxGradientStopSchema = implementsInterface()(px.object({
811
919
  offset: px.number(),
812
920
  color: px.string()
@@ -814,27 +922,28 @@ var PxGradientStopSchema = implementsInterface()(px.object({
814
922
  var PxAnimatableGradientStopsSchema = px.union([
815
923
  px.array(PxGradientStopSchema),
816
924
  px.object({ value: px.array(PxGradientStopSchema) }),
817
- px.object({ keyframes: px.array(PxKeyframeSchema) })
925
+ PxPropertyAnimationSchema
818
926
  ]);
819
927
  var PxFillGradientEffectSchema = implementsInterface()(px.object({
928
+ // Contextual kind — the `type` convention, see `PxNodeBase.type`.
820
929
  type: px.enum([PxGradientType.linear, PxGradientType.radial]),
821
- p1: px.tuple([px.number(), px.number()]).optional(),
822
- p2: px.tuple([px.number(), px.number()]).optional(),
823
- c: px.tuple([px.number(), px.number()]).optional(),
824
- r: px.number().optional(),
825
- fp: px.tuple([px.number(), px.number()]).optional(),
930
+ p1: PxAnimatableVec2Schema.optional(),
931
+ p2: PxAnimatableVec2Schema.optional(),
932
+ c: PxAnimatableVec2Schema.optional(),
933
+ r: PxAnimatableNumberSchema.optional(),
934
+ fp: PxAnimatableVec2Schema.optional(),
826
935
  stops: PxAnimatableGradientStopsSchema.optional(),
827
- gradientUnits: px.string().optional(),
828
- spreadMethod: px.string().optional(),
936
+ gradientUnits: px.enum([PxGradientUnits.userSpaceOnUse, PxGradientUnits.objectBoundingBox]).optional(),
937
+ spreadMethod: px.enum([PxGradientSpreadMethod.pad, PxGradientSpreadMethod.reflect, PxGradientSpreadMethod.repeat]).optional(),
829
938
  gradientTransform: px.string().optional()
830
939
  }));
831
940
  var PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;
832
941
  var PxTextPathEffectSchema = implementsInterface()(px.object({
833
942
  path: px.string(),
834
- pathOverflow: px.string().optional(),
835
- lengthAdjust: px.string().optional(),
836
- method: px.string().optional(),
837
- spacing: px.string().optional(),
943
+ pathOverflow: px.enum([PxPathOverflow.clip, PxPathOverflow.extend]).optional(),
944
+ lengthAdjust: px.enum([PxLengthAdjust.spacing, PxLengthAdjust.spacingAndGlyphs]).optional(),
945
+ method: px.enum([PxTextPathMethod.align, PxTextPathMethod.stretch]).optional(),
946
+ spacing: px.enum([PxTextPathSpacing.auto, PxTextPathSpacing.exact]).optional(),
838
947
  startOffset: PxAnimatableNumberSchema.optional(),
839
948
  textLength: PxAnimatableNumberSchema.optional()
840
949
  }));
@@ -842,13 +951,12 @@ var PxTextEffectSchema = implementsInterface()(px.object({
842
951
  useGlyphs: px.boolean().optional()
843
952
  }));
844
953
  var PxEffectsSchema = implementsInterface()(px.object({
845
- transformation: PxTransformationEffectSchema.optional(),
954
+ transformBy: PxTransformByEffectSchema.optional(),
846
955
  repeater: PxRepeaterEffectSchema.optional(),
847
956
  maskedBy: PxMaskedByEffectSchema.optional(),
848
957
  clipPath: PxClipPathEffectSchema.optional(),
849
958
  trimPath: PxTrimPathEffectSchema.optional(),
850
959
  clone: PxCloneEffectSchema.optional(),
851
- isCombinedShape: px.boolean().optional(),
852
960
  fillGradient: PxFillGradientEffectSchema.optional(),
853
961
  strokeGradient: PxStrokeGradientEffectSchema.optional(),
854
962
  textPath: PxTextPathEffectSchema.optional(),
@@ -872,6 +980,14 @@ function validateNodeEffects(root, opts) {
872
980
  return warnings;
873
981
  }
874
982
  var PxNodeBase = px.openObject({
983
+ // CONVENTION (SCHEMA-DESIGN R1 / issues N4): `type` is the ONE word for "what
984
+ // kind of thing is this", discriminated by its CARRIER — here the node TAG
985
+ // (`rect`, `text`), and inside a sub-object that object's kind (`clone.type`,
986
+ // `fillGradient.type`, editor `preset.type`). Each sits in its own object, so
987
+ // the carrier disambiguates completely; synonyms (`cloneKind`, `presetShape`)
988
+ // would add words that all mean "type" and still need the carrier to read.
989
+ // Guarding a `type` SLOT against a wrong VALUE is the job of strict enums
990
+ // (issues V3), never of distinct key names.
875
991
  type: px.string(),
876
992
  id: px.string().optional(),
877
993
  meta: px.any().optional(),
@@ -881,7 +997,7 @@ var PxNodeBase = px.openObject({
881
997
  effects: PxEffectsSchema.optional(),
882
998
  // `PxElementAnimation` (not just `PxAnimationDefinition`) — accepts
883
999
  // string ref / array of refs / inline definition / mixed array; mirrors
884
- // `animator.animate` map values and what `processNode` resolves at runtime.
1000
+ // `animator.animateById` map values and what `processNode` resolves at runtime.
885
1001
  animate: PxElementAnimationSchema.optional(),
886
1002
  style: px.union([px.string(), px.record(px.union([px.string(), px.number()]))]).optional()
887
1003
  }, PxAttrValueSchema);
@@ -905,35 +1021,10 @@ var PxBezierPathSchema = implementsInterface()(px.object({
905
1021
  o: px.array(px.array(px.number())).optional(),
906
1022
  c: px.boolean().optional()
907
1023
  }));
908
- function isPxElementFileFormat(fileJson) {
909
- if (!(fileJson && typeof fileJson === "object" && !Array.isArray(fileJson))) {
910
- return false;
911
- }
912
- return fileJson["type"] === "svg" || fileJson["tagName"] === "svg";
913
- }
914
1024
  function isPxElementFileFormatDeep(fileJson) {
915
1025
  const valid = PxAnimatedSvgDocumentSchema.isValid(fileJson);
916
1026
  return { valid, errors: valid ? [] : ["Document failed schema validation"] };
917
1027
  }
918
- function getAnimatorConfig(doc) {
919
- var _a, _b;
920
- 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);
921
- }
922
- function getDefs(doc) {
923
- var _a;
924
- if (!doc) return void 0;
925
- return (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.definitions;
926
- }
927
- function getBindings(doc) {
928
- var _a;
929
- if (!doc) return void 0;
930
- const animate = (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.animate;
931
- if (!animate) return void 0;
932
- return Object.entries(animate).map(([id, anim]) => ({ id, animate: anim }));
933
- }
934
- function getChildren(doc) {
935
- return doc == null ? void 0 : doc.children;
936
- }
937
1028
 
938
1029
  // src/PxAnimatorUtil.ts
939
1030
  function bezierToSvgPath(path, forceCurves = false) {
@@ -1388,7 +1479,7 @@ function generateNewIds(doc) {
1388
1479
  "flood-color",
1389
1480
  "lighting-color"
1390
1481
  ]);
1391
- const directIdRefAttrs = /* @__PURE__ */ new Set(["baseId", "targetId", "boundElementId"]);
1482
+ const directIdRefAttrs = /* @__PURE__ */ new Set(["sourceId", "targetId", "boundElementId"]);
1392
1483
  function collectIds(node) {
1393
1484
  if (!node || typeof node !== "object") return;
1394
1485
  if (node.id && typeof node.id === "string") {
@@ -1426,9 +1517,10 @@ function generateNewIds(doc) {
1426
1517
  } else if (urlRefAttrs.has(key)) {
1427
1518
  node[key] = replaceUrlRefs(value, idMap);
1428
1519
  } else if (directIdRefAttrs.has(key)) {
1429
- const newId = idMap.get(value);
1520
+ const hasHash = value.startsWith("#");
1521
+ const newId = idMap.get(hasHash ? value.slice(1) : value);
1430
1522
  if (newId) {
1431
- node[key] = newId;
1523
+ node[key] = hasHash ? "#" + newId : newId;
1432
1524
  }
1433
1525
  } else if (value.includes("url(#")) {
1434
1526
  node[key] = replaceUrlRefs(value, idMap);
@@ -1446,14 +1538,14 @@ function generateNewIds(doc) {
1446
1538
  }
1447
1539
  collectIds(cloned);
1448
1540
  updateRefs(cloned);
1449
- const docAnimate = (_a = cloned.animator) == null ? void 0 : _a.animate;
1541
+ const docAnimate = (_a = cloned.animator) == null ? void 0 : _a.animateById;
1450
1542
  if (docAnimate && typeof docAnimate === "object") {
1451
1543
  const updatedAnimate = {};
1452
1544
  for (const [id, anim] of Object.entries(docAnimate)) {
1453
1545
  const newId = (_b = idMap.get(id)) != null ? _b : id;
1454
1546
  updatedAnimate[newId] = anim;
1455
1547
  }
1456
- cloned.animator = __spreadProps(__spreadValues({}, cloned.animator), { animate: updatedAnimate });
1548
+ cloned.animator = __spreadProps(__spreadValues({}, cloned.animator), { animateById: updatedAnimate });
1457
1549
  }
1458
1550
  return cloned;
1459
1551
  }
@@ -1544,8 +1636,9 @@ function getNormalizedProps(props) {
1544
1636
  let value = props[rawKey];
1545
1637
  if (COLOUR_ATTR_NAMES.has(key) && Array.isArray(value)) {
1546
1638
  propsCopy[key] = toRGBA(value);
1547
- } else if (key === "transform" && value !== null && typeof value === "object" && !Array.isArray(value) && value.value && typeof value.value === "object") {
1548
- propsCopy["transform"] = composeTransformParts(value.value, { withUnits: false });
1639
+ } else if (key === "transform" && value !== null && typeof value === "object" && !Array.isArray(value) && !value.keyframes && !value.kfs) {
1640
+ const parts = value.value && typeof value.value === "object" ? value.value : value;
1641
+ propsCopy["transform"] = composeTransformParts(parts, { withUnits: false });
1549
1642
  } else if (TRANSFORM_FN_NAMES.has(key)) {
1550
1643
  if (Array.isArray(value)) {
1551
1644
  if (key === "translate") value = value.map((v) => v + "px");
@@ -1553,6 +1646,8 @@ function getNormalizedProps(props) {
1553
1646
  }
1554
1647
  if (key === "rotate") value = value + "deg";
1555
1648
  propsCopy["transform"] = key + "(" + value + ")";
1649
+ } else if (Array.isArray(value)) {
1650
+ propsCopy[key] = value.join(",");
1556
1651
  } else if (value !== void 0 && value !== null) {
1557
1652
  propsCopy[key] = String(value);
1558
1653
  }
@@ -1954,6 +2049,16 @@ function walkAndMaterialise(node, opts) {
1954
2049
  }
1955
2050
 
1956
2051
  // src/PxDefinitions.ts
2052
+ var LOOP_JUMP_SHIFT_MS = 10;
2053
+ function deepEqualValue(a, b) {
2054
+ if (a === b) return true;
2055
+ if (typeof a !== typeof b || a === null || b === null || typeof a !== "object") return false;
2056
+ if (Array.isArray(a) !== Array.isArray(b)) return false;
2057
+ const ka = Object.keys(a);
2058
+ const kb = Object.keys(b);
2059
+ if (ka.length !== kb.length) return false;
2060
+ return ka.every((k) => deepEqualValue(a[k], b[k]));
2061
+ }
1957
2062
  function parsePathCommands(d) {
1958
2063
  const tokens = d.split(/([MLCZmlcz]|[\s,]+)/).map((t) => t.trim()).filter((t) => t && t !== ",");
1959
2064
  const commands = [];
@@ -2156,7 +2261,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2156
2261
  const totalIntervals = keyframes.length - 1;
2157
2262
  const segCount = clamp((_a = loop.segmentCount) != null ? _a : totalIntervals, 1, totalIntervals);
2158
2263
  let segKfs;
2159
- if (loop.before) {
2264
+ if (loop.extend === PxLoopExtend.before) {
2160
2265
  segKfs = keyframes.slice(0, segCount + 1);
2161
2266
  } else {
2162
2267
  segKfs = keyframes.slice(totalIntervals - segCount);
@@ -2164,7 +2269,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2164
2269
  const firstT = (_b = keyframes[0].t) != null ? _b : 0;
2165
2270
  const lastT = (_c = keyframes[keyframes.length - 1].t) != null ? _c : 0;
2166
2271
  let fillStart, fillEnd;
2167
- if (loop.before) {
2272
+ if (loop.extend === PxLoopExtend.before) {
2168
2273
  fillStart = 0;
2169
2274
  fillEnd = firstT;
2170
2275
  } else {
@@ -2191,7 +2296,10 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2191
2296
  const remainder = fillDuration - fullReps * segDuration;
2192
2297
  const partialFraction = remainder / segDuration;
2193
2298
  const looped = [];
2299
+ const separateBoundary = loop.extend !== PxLoopExtend.before;
2300
+ const originalTerminalKf = keyframes[keyframes.length - 1];
2194
2301
  function appendRep(repStart, isReversed, partial) {
2302
+ var _a2;
2195
2303
  let entries;
2196
2304
  if (isReversed) {
2197
2305
  entries = [];
@@ -2227,8 +2335,17 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2227
2335
  looped.push({ t: repStart + cutRelT * segDuration, v: cutValue, e: void 0 });
2228
2336
  return;
2229
2337
  }
2338
+ const prevKf = looped.length > 0 ? looped[looped.length - 1] : originalTerminalKf;
2339
+ const isBoundary = separateBoundary && i === 0 && prevKf !== void 0 && Math.abs(((_a2 = prevKf.t) != null ? _a2 : 0) - (repStart + entry.relT * segDuration)) < 1e-9;
2340
+ if (isBoundary) {
2341
+ if (deepEqualValue(prevKf.v, entry.v)) continue;
2342
+ if (looped.length > 0) {
2343
+ delete prevKf.tangentIn;
2344
+ delete prevKf.tangentOut;
2345
+ }
2346
+ }
2230
2347
  const pushed = {
2231
- t: repStart + entry.relT * segDuration,
2348
+ t: repStart + entry.relT * segDuration + (isBoundary ? LOOP_JUMP_SHIFT_MS : 0),
2232
2349
  v: entry.v,
2233
2350
  e: i < entries.length - 1 ? entry.e : void 0
2234
2351
  };
@@ -2276,7 +2393,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2276
2393
  looped.push(pushed);
2277
2394
  }
2278
2395
  }
2279
- if (loop.before) {
2396
+ if (loop.extend === PxLoopExtend.before) {
2280
2397
  if (partialFraction > 1e-9) {
2281
2398
  const isReversed = !!loop.alternate && fullReps % 2 === 0;
2282
2399
  appendRepTail(fillStart, isReversed, partialFraction);
@@ -2299,7 +2416,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2299
2416
  appendRep(repStart, isReversed, partialFraction);
2300
2417
  }
2301
2418
  }
2302
- if (loop.before) {
2419
+ if (loop.extend === PxLoopExtend.before) {
2303
2420
  return [...looped, ...keyframes];
2304
2421
  } else {
2305
2422
  return [...keyframes, ...looped];
@@ -2415,7 +2532,7 @@ var _elementIdCounter = 0;
2415
2532
  function generateElementId() {
2416
2533
  return "_px_el_" + ++_elementIdCounter;
2417
2534
  }
2418
- function normalizeAnimationDefinition(animDef, duration, defs, engine = PxAnimatorEngine.webapi) {
2535
+ function normalizeAnimationDefinition(animDef, duration, defs, engine = PxAnimatorEngine.waapi) {
2419
2536
  const normalized = {};
2420
2537
  for (const [propName, propAnim] of Object.entries(animDef)) {
2421
2538
  const normalizedKfs = normalizeKeyframes(propName, propAnim, duration, defs);
@@ -2423,12 +2540,12 @@ function normalizeAnimationDefinition(animDef, duration, defs, engine = PxAnimat
2423
2540
  const out = { kfs: normalizedKfs };
2424
2541
  if (propAnim.autoOrient !== void 0) out.autoOrient = propAnim.autoOrient;
2425
2542
  if (propAnim.loop !== void 0) out.loop = propAnim.loop;
2426
- normalized[propName] = engine === PxAnimatorEngine.webapi && propName === "transform" ? materialiseMotionPathInPropAnim(out) : out;
2543
+ normalized[propName] = engine === PxAnimatorEngine.waapi && propName === "transform" ? materialiseMotionPathInPropAnim(out) : out;
2427
2544
  }
2428
2545
  }
2429
2546
  return normalized;
2430
2547
  }
2431
- function getNormalisedBindings(doc, engine = PxAnimatorEngine.webapi) {
2548
+ function getNormalisedBindings(doc, engine = PxAnimatorEngine.waapi) {
2432
2549
  const animatorConfig = getAnimatorConfig(doc) || {};
2433
2550
  const defs = getDefs(doc);
2434
2551
  const duration = animatorConfig.duration || 1e3;
@@ -2839,23 +2956,61 @@ function partsRecord(part, value, origin) {
2839
2956
  return rec;
2840
2957
  }
2841
2958
  function readAnimatable(raw) {
2959
+ var _a, _b, _c;
2842
2960
  if (raw === void 0) return { kind: "absent" /* Absent */ };
2843
2961
  if (Array.isArray(raw)) return { kind: "static" /* Static */, value: raw };
2844
2962
  if (typeof raw === "object") {
2845
2963
  const obj = raw;
2846
- if (obj.keyframes) {
2847
- return { kind: "animated" /* Animated */, keyframes: obj.keyframes, autoOrient: obj.autoOrient, loop: obj.loop };
2964
+ const kfs = (_a = obj.keyframes) != null ? _a : obj.kfs;
2965
+ if (kfs) {
2966
+ const out = { kind: "animated" /* Animated */, keyframes: kfs.map(normaliseKeyframe), autoOrient: obj.autoOrient, loop: obj.loop };
2967
+ const base = (_b = obj.value) != null ? _b : obj.v;
2968
+ if (base !== void 0 && out.kind === "animated" /* Animated */) out.base = base;
2969
+ return out;
2848
2970
  }
2849
- if (obj.value !== void 0) return { kind: "static" /* Static */, value: obj.value };
2971
+ const staticValue = (_c = obj.value) != null ? _c : obj.v;
2972
+ if (staticValue !== void 0) return { kind: "static" /* Static */, value: staticValue };
2850
2973
  }
2851
2974
  return { kind: "static" /* Static */, value: raw };
2852
2975
  }
2976
+ function writeAnimatableChannel(node, attrName, read, opts) {
2977
+ var _a, _b, _c, _d;
2978
+ const toOut = (v) => (opts == null ? void 0 : opts.asString) && v !== void 0 && v !== null ? String(v) : v;
2979
+ if (read.kind === "absent" /* Absent */) return;
2980
+ if (read.kind === "static" /* Static */) {
2981
+ node[attrName] = toOut(read.value);
2982
+ return;
2983
+ }
2984
+ const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
2985
+ const animate = __spreadValues({}, prevAnimate || {});
2986
+ const block = { keyframes: read.keyframes };
2987
+ if (read.loop !== void 0) block.loop = read.loop;
2988
+ if (read.autoOrient !== void 0) block.autoOrient = read.autoOrient;
2989
+ animate[attrName] = block;
2990
+ node.animate = animate;
2991
+ 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;
2992
+ if (baseline !== void 0) node[attrName] = toOut(baseline);
2993
+ }
2994
+ function normaliseKeyframe(kf) {
2995
+ if (!kf || typeof kf !== "object") return kf;
2996
+ const k = kf;
2997
+ if (k.t === void 0 && k.v === void 0 && k.e === void 0 && k.to === void 0 && k.ti === void 0) {
2998
+ return kf;
2999
+ }
3000
+ const out = __spreadValues({}, k);
3001
+ if (out.time === void 0 && k.t !== void 0) out.time = k.t;
3002
+ if (out.value === void 0 && k.v !== void 0) out.value = k.v;
3003
+ if (out.easing === void 0 && k.e !== void 0) out.easing = k.e;
3004
+ if (out.tangentOut === void 0 && k.to !== void 0) out.tangentOut = k.to;
3005
+ if (out.tangentIn === void 0 && k.ti !== void 0) out.tangentIn = k.ti;
3006
+ return out;
3007
+ }
2853
3008
  function readStaticOrigin(raw, ctx) {
2854
3009
  var _a;
2855
3010
  const o = readAnimatable(raw);
2856
3011
  if (o.kind === "absent" /* Absent */) return void 0;
2857
3012
  if (o.kind === "static" /* Static */) return o.value;
2858
- ctx.warnings.push("transformation.origin: animated origin approximated by its first keyframe");
3013
+ ctx.warnings.push("transformBy.origin: animated origin approximated by its first keyframe");
2859
3014
  return (_a = o.keyframes[0]) == null ? void 0 : _a.value;
2860
3015
  }
2861
3016
  function keyframeWith(kf, value) {
@@ -2868,7 +3023,7 @@ function keyframeWith(kf, value) {
2868
3023
  }
2869
3024
 
2870
3025
  // src/effects/transformationEffect.ts
2871
- function applyTransformationEffect(node, fx, ctx) {
3026
+ function applyTransformByEffect(node, fx, ctx) {
2872
3027
  if (!fx) return node;
2873
3028
  delete node.transform;
2874
3029
  let n = node;
@@ -2904,6 +3059,10 @@ function applyTransformationEffect(node, fx, ctx) {
2904
3059
  } else {
2905
3060
  n = wrapTransformPart(n, "translate" /* Translate */, fx.translate, ctx);
2906
3061
  }
3062
+ if (n !== node && node.id) {
3063
+ n.id = node.id;
3064
+ delete node.id;
3065
+ }
2907
3066
  return n;
2908
3067
  }
2909
3068
  function translateHasAutoOrient(translate) {
@@ -2913,8 +3072,6 @@ function translateHasAutoOrient(translate) {
2913
3072
  return Array.isArray(obj.keyframes) && obj.keyframes.some((kf) => kf.tangentOut || kf.tangentIn);
2914
3073
  }
2915
3074
  function normalizeScale(raw) {
2916
- if (raw === void 0) return void 0;
2917
- if (Array.isArray(raw)) return [raw[0] / 100, raw[1] / 100];
2918
3075
  return raw;
2919
3076
  }
2920
3077
  function wrapTransformPart(inner, part, raw, ctx) {
@@ -2956,35 +3113,57 @@ function wrapOrigin(inner, raw, invert) {
2956
3113
  return inner;
2957
3114
  }
2958
3115
 
3116
+ // src/effects/util.ts
3117
+ function genId(ctx, prefix) {
3118
+ return "_lw_" + prefix + "_" + ctx.nextId++;
3119
+ }
3120
+ function stripHash2(href) {
3121
+ return typeof href === "string" ? href.replace(/^#/, "") : void 0;
3122
+ }
3123
+ function indexById(node, map) {
3124
+ var _a;
3125
+ if (typeof node.id === "string") map.set(node.id, node);
3126
+ (_a = node.children) == null ? void 0 : _a.forEach((child) => indexById(child, map));
3127
+ }
3128
+ function spliceDefs(root, defs) {
3129
+ if (!defs.length) return;
3130
+ const existing = root.children || (root.children = []);
3131
+ existing.unshift({ type: "defs", children: defs });
3132
+ }
3133
+ var clone = deepClonePxNode;
3134
+ function regenerateIdsInClone(root, ctx) {
3135
+ return regenerateIdsAndRewriteRefs(root, () => genId(ctx, "retimed"));
3136
+ }
3137
+
2959
3138
  // src/effects/contentRefSplit.ts
2960
3139
  function identifyContentRefTargets(node, ctx, allocator) {
2961
3140
  var _a, _b, _c;
2962
3141
  if (node.type === "use" && ((_b = (_a = node.effects) == null ? void 0 : _a.clone) == null ? void 0 : _b.type) === "content") {
2963
- const baseId = node.effects.clone.baseId;
2964
- if (typeof baseId === "string" && baseId && !ctx.contentRefInnerIds.has(baseId)) {
2965
- ctx.contentRefInnerIds.set(baseId, allocator(baseId));
3142
+ const sourceId = stripHash2(node.effects.clone.sourceId);
3143
+ if (typeof sourceId === "string" && sourceId && !ctx.contentRefInnerIds.has(sourceId)) {
3144
+ ctx.contentRefInnerIds.set(sourceId, allocator(sourceId));
2966
3145
  }
2967
3146
  }
2968
3147
  (_c = node.children) == null ? void 0 : _c.forEach((c) => identifyContentRefTargets(c, ctx, allocator));
2969
3148
  }
2970
- function splitForContentRef(node, transformation, originalId, innerId, ctx) {
2971
- const outerBody = liftBodyTranslate(node, transformation);
3149
+ function splitForContentRef(node, transformBy, originalId, innerId, ctx) {
3150
+ const outerBody = liftBodyTranslate(node, transformBy);
2972
3151
  if (typeof node.id === "string") delete node.id;
2973
- const { outer: outerTr, inner: innerTr } = splitTransformationEffect(transformation);
3152
+ const { outer: outerTr, inner: innerTr } = splitTransformByEffect(transformBy);
2974
3153
  let innerNode = node;
2975
- innerNode = applyTransformationEffect(innerNode, innerTr, ctx);
3154
+ innerNode = applyTransformByEffect(innerNode, innerTr, ctx);
2976
3155
  const innerWrapper = { type: "g", id: innerId, children: [innerNode] };
2977
3156
  let outerWrapper = { type: "g", id: originalId, children: [innerWrapper] };
2978
3157
  if (outerBody.transform !== void 0) outerWrapper.transform = outerBody.transform;
2979
3158
  if (outerBody.animate !== void 0) outerWrapper.animate = outerBody.animate;
2980
3159
  if (outerTr) {
2981
3160
  delete outerWrapper.id;
2982
- outerWrapper = applyTransformationEffect(outerWrapper, outerTr, ctx);
3161
+ outerWrapper = applyTransformByEffect(outerWrapper, outerTr, ctx);
2983
3162
  outerWrapper.id = originalId;
2984
3163
  }
2985
3164
  return outerWrapper;
2986
3165
  }
2987
- function liftBodyTranslate(node, transformation) {
3166
+ function liftBodyTranslate(node, transformBy) {
2988
3167
  var _a, _b, _c;
2989
3168
  const out = {};
2990
3169
  let didLiftAnimate = false;
@@ -3038,7 +3217,7 @@ function liftBodyTranslate(node, transformation) {
3038
3217
  didLiftAnimate = true;
3039
3218
  }
3040
3219
  }
3041
- const transformationHasTranslate = (transformation == null ? void 0 : transformation.translate) !== void 0;
3220
+ const transformationHasTranslate = (transformBy == null ? void 0 : transformBy.translate) !== void 0;
3042
3221
  const stripBodyTranslateOnly = didLiftAnimate || transformationHasTranslate;
3043
3222
  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);
3044
3223
  if (typeof node.transform === "string") {
@@ -3057,6 +3236,24 @@ function liftBodyTranslate(node, transformation) {
3057
3236
  if (split.rest) node.transform = split.rest;
3058
3237
  else delete node.transform;
3059
3238
  }
3239
+ } else if (node.transform && typeof node.transform === "object" && !Array.isArray(node.transform) && !node.transform.keyframes && !node.transform.kfs) {
3240
+ const wrapped = node.transform.value;
3241
+ const isWrapped = !!(wrapped && typeof wrapped === "object");
3242
+ const value = isWrapped ? wrapped : node.transform;
3243
+ const rewrap = (rec) => isWrapped ? { value: rec } : rec;
3244
+ if (Array.isArray(value.translate)) {
3245
+ const rest = __spreadValues({}, value);
3246
+ delete rest.translate;
3247
+ const hasRest = Object.keys(rest).length > 0;
3248
+ if (stripBodyTranslateOnly) {
3249
+ if (hasRest) node.transform = rewrap(rest);
3250
+ else delete node.transform;
3251
+ } else {
3252
+ out.transform = rewrap({ translate: value.translate });
3253
+ if (hasRest) node.transform = rewrap(rest);
3254
+ else delete node.transform;
3255
+ }
3256
+ }
3060
3257
  }
3061
3258
  return out;
3062
3259
  }
@@ -3126,7 +3323,7 @@ function parseTranslateArgs(translateOp) {
3126
3323
  const nums = m[1].split(/[\s,]+/).filter(Boolean).map(Number);
3127
3324
  return [nums[0] || 0, nums[1] || 0];
3128
3325
  }
3129
- function splitTransformationEffect(fx) {
3326
+ function splitTransformByEffect(fx) {
3130
3327
  if (!fx) return {};
3131
3328
  const originOnOuter = needsOriginOnOuter(fx.translate);
3132
3329
  const innerHasPivotedPart = fx.rotate !== void 0 || fx.scale !== void 0;
@@ -3153,28 +3350,6 @@ function needsOriginOnOuter(translateAnim) {
3153
3350
  return false;
3154
3351
  }
3155
3352
 
3156
- // src/effects/util.ts
3157
- function genId(ctx, prefix) {
3158
- return "_lw_" + prefix + "_" + ctx.nextId++;
3159
- }
3160
- function stripHash2(href) {
3161
- return typeof href === "string" ? href.replace(/^#/, "") : void 0;
3162
- }
3163
- function indexById(node, map) {
3164
- var _a;
3165
- if (typeof node.id === "string") map.set(node.id, node);
3166
- (_a = node.children) == null ? void 0 : _a.forEach((child) => indexById(child, map));
3167
- }
3168
- function spliceDefs(root, defs) {
3169
- if (!defs.length) return;
3170
- const existing = root.children || (root.children = []);
3171
- existing.unshift({ type: "defs", children: defs });
3172
- }
3173
- var clone = deepClonePxNode;
3174
- function regenerateIdsInClone(root, ctx) {
3175
- return regenerateIdsAndRewriteRefs(root, () => genId(ctx, "retimed"));
3176
- }
3177
-
3178
3353
  // src/effects/gradientEffect.ts
3179
3354
  function applyFillGradientEffect(node, fx, ctx) {
3180
3355
  return applyGradient(node, fx, ctx, "fill");
@@ -3196,62 +3371,63 @@ function synthesiseGradientDef(fx, id, ctx) {
3196
3371
  id
3197
3372
  };
3198
3373
  if (fx.type === PxGradientType.linear) {
3199
- if (fx.p1) {
3200
- out.x1 = String(fx.p1[0]);
3201
- out.y1 = String(fx.p1[1]);
3202
- }
3203
- if (fx.p2) {
3204
- out.x2 = String(fx.p2[0]);
3205
- out.y2 = String(fx.p2[1]);
3206
- }
3374
+ applyGeomVec(out, "x1", "y1", fx.p1);
3375
+ applyGeomVec(out, "x2", "y2", fx.p2);
3207
3376
  } else {
3208
- if (fx.c) {
3209
- out.cx = String(fx.c[0]);
3210
- out.cy = String(fx.c[1]);
3211
- }
3212
- if (fx.r !== void 0) out.r = String(fx.r);
3213
- if (fx.fp) {
3214
- out.fx = String(fx.fp[0]);
3215
- out.fy = String(fx.fp[1]);
3216
- }
3377
+ applyGeomVec(out, "cx", "cy", fx.c);
3378
+ applyGeomNumber(out, "r", fx.r);
3379
+ applyGeomVec(out, "fx", "fy", fx.fp);
3217
3380
  }
3218
3381
  if (fx.gradientUnits) out.gradientUnits = fx.gradientUnits;
3219
3382
  if (fx.spreadMethod) out.spreadMethod = fx.spreadMethod;
3220
3383
  if (fx.gradientTransform) out.gradientTransform = fx.gradientTransform;
3221
- const animate = fx.animate;
3222
- if (animate) {
3223
- const mapped = {};
3224
- for (const wireKey of Object.keys(animate)) {
3225
- const attr = GRADIENT_ANIM_CHANNEL_TO_ATTR[wireKey];
3226
- if (attr) mapped[attr] = animate[wireKey];
3227
- }
3228
- if (Object.keys(mapped).length) out.animate = mapped;
3229
- }
3230
3384
  out.children = buildStopChildren(fx.stops, ctx);
3231
3385
  return out;
3232
3386
  }
3233
- var GRADIENT_ANIM_CHANNEL_TO_ATTR = {
3234
- gradientX1: "x1",
3235
- gradientY1: "y1",
3236
- gradientX2: "x2",
3237
- gradientY2: "y2",
3238
- gradientCx: "cx",
3239
- gradientCy: "cy",
3240
- gradientFx: "fx",
3241
- gradientFy: "fy",
3242
- gradientR: "r"
3243
- };
3387
+ function applyGeomVec(out, xAttr, yAttr, raw) {
3388
+ var _a, _b, _c;
3389
+ const read = readAnimatable(raw);
3390
+ if (read.kind === "absent" /* Absent */) return;
3391
+ if (read.kind === "static" /* Static */) {
3392
+ out[xAttr] = String(read.value[0]);
3393
+ out[yAttr] = String(read.value[1]);
3394
+ return;
3395
+ }
3396
+ const axisChannel = (idx) => {
3397
+ const block = {
3398
+ keyframes: read.keyframes.map((kf) => {
3399
+ const axisKf = { time: kf.time, value: Array.isArray(kf.value) ? kf.value[idx] : void 0 };
3400
+ if (kf.easing !== void 0) axisKf.easing = kf.easing;
3401
+ return axisKf;
3402
+ })
3403
+ };
3404
+ if (read.loop !== void 0) block.loop = read.loop;
3405
+ return block;
3406
+ };
3407
+ const animate = (_a = out.animate) != null ? _a : {};
3408
+ animate[xAttr] = axisChannel(0);
3409
+ animate[yAttr] = axisChannel(1);
3410
+ out.animate = animate;
3411
+ const baseline = (_c = read.base) != null ? _c : (_b = read.keyframes[0]) == null ? void 0 : _b.value;
3412
+ if (Array.isArray(baseline)) {
3413
+ out[xAttr] = String(baseline[0]);
3414
+ out[yAttr] = String(baseline[1]);
3415
+ }
3416
+ }
3417
+ function applyGeomNumber(out, attrName, raw) {
3418
+ const read = readAnimatable(raw);
3419
+ if (read.kind === "absent" /* Absent */) return;
3420
+ writeAnimatableChannel(out, attrName, read, { asString: true });
3421
+ }
3244
3422
  function buildStopChildren(stops, ctx) {
3245
3423
  var _a, _b, _c, _d;
3246
3424
  if (!stops) return [];
3247
- if (Array.isArray(stops)) return stops.map(staticStopNode);
3248
- if (typeof stops === "object" && Array.isArray(stops.value)) {
3249
- return stops.value.map(staticStopNode);
3250
- }
3251
- const animBlock = stops;
3252
- const kfs = animBlock.keyframes;
3253
- if (!Array.isArray(kfs) || !kfs.length) return [];
3254
- const loopFromSource = animBlock.loop;
3425
+ const read = readAnimatable(stops);
3426
+ if (read.kind === "absent" /* Absent */) return [];
3427
+ if (read.kind === "static" /* Static */) return Array.isArray(read.value) ? read.value.map(staticStopNode) : [];
3428
+ const kfs = read.keyframes;
3429
+ if (!kfs.length) return [];
3430
+ const loopFromSource = read.loop;
3255
3431
  let stopCount = 0;
3256
3432
  for (const kf of kfs) {
3257
3433
  const v = (_a = kf.value) != null ? _a : kf.v;
@@ -3328,38 +3504,62 @@ function formatOffset(o) {
3328
3504
 
3329
3505
  // src/effects/clipPathEffect.ts
3330
3506
  function applyClipPathEffect(node, fx, ctx) {
3507
+ var _a, _b;
3331
3508
  if (!fx || !fx.d && !fx.animate) return node;
3332
3509
  const clipId = genId(ctx, "clip");
3333
- const pathChild = { type: "path", d: fx.d };
3334
- if (fx.animate) pathChild.animate = { d: fx.animate };
3510
+ const pathChild = { type: "path" };
3511
+ const read = readAnimatable(fx.d);
3512
+ if (read.kind !== "absent" /* Absent */) {
3513
+ if (read.kind === "static" /* Static */) {
3514
+ pathChild.d = pathString(read.value);
3515
+ } else {
3516
+ writeAnimatableChannel(pathChild, "d", read);
3517
+ if (pathChild.d !== void 0) pathChild.d = pathString(pathChild.d);
3518
+ }
3519
+ }
3520
+ if (fx.animate && !((_a = pathChild.animate) == null ? void 0 : _a.d)) {
3521
+ const animate = (_b = pathChild.animate) != null ? _b : {};
3522
+ animate.d = fx.animate;
3523
+ pathChild.animate = animate;
3524
+ }
3335
3525
  ctx.defs.push({ type: "clipPath", id: clipId, children: [pathChild] });
3336
3526
  node.clipPath = "url(#" + clipId + ")";
3337
3527
  return node;
3338
3528
  }
3529
+ function pathString(v) {
3530
+ if (typeof v === "string") return v;
3531
+ if (v && typeof v === "object" && typeof v.path === "string") return v.path;
3532
+ return void 0;
3533
+ }
3339
3534
 
3340
3535
  // src/effects/maskedByEffect.ts
3341
- function applyMaskedByEffect(node, fx, transformation, ctx) {
3536
+ function applyMaskedByEffect(node, fx, transformBy, ctx) {
3342
3537
  if (!fx) return node;
3343
- if (!fx.href) {
3344
- ctx.errors.push("maskedBy.href missing \u2014 cannot build mask");
3538
+ const sourceId = stripHash2(fx.sourceId);
3539
+ if (!sourceId) {
3540
+ ctx.errors.push("maskedBy.sourceId missing \u2014 cannot build mask");
3345
3541
  return node;
3346
3542
  }
3347
3543
  const maskId = genId(ctx, "mask");
3348
- let content = { type: "use", href: "#" + fx.href };
3349
- if (transformation) {
3350
- content = wrapInverseTransform(content, transformation, ctx);
3544
+ let content = { type: "use", href: "#" + sourceId };
3545
+ if (transformBy) {
3546
+ content = wrapInverseTransform(content, transformBy, ctx);
3351
3547
  } else if (hasAnimateTransform(node)) {
3352
3548
  content = wrapInverseAnimatedBodyTransform(content, node, ctx);
3353
3549
  } else {
3354
3550
  const bodyStatic = readTransformationFromBody(node);
3355
3551
  if (bodyStatic) content = wrapInverseTransform(content, bodyStatic, ctx);
3356
3552
  }
3357
- const includeTargetOwn = transformation === void 0 && !nodeHasBodyTransform(node);
3358
- content = wrapAncestorChainCompensation(content, node, fx.href, ctx, includeTargetOwn);
3553
+ const includeTargetOwn = transformBy === void 0 && !nodeHasBodyTransform(node);
3554
+ content = wrapAncestorChainCompensation(content, node, sourceId, ctx, includeTargetOwn);
3359
3555
  const mask = { type: "mask", id: maskId, children: [content] };
3360
3556
  if (fx.maskType) mask.maskType = fx.maskType;
3361
3557
  if (fx.maskUnits) mask.maskUnits = fx.maskUnits;
3362
3558
  if (fx.maskContentUnits) mask.maskContentUnits = fx.maskContentUnits;
3559
+ if (fx.x !== void 0) mask.x = String(fx.x);
3560
+ if (fx.y !== void 0) mask.y = String(fx.y);
3561
+ if (fx.width !== void 0) mask.width = String(fx.width);
3562
+ if (fx.height !== void 0) mask.height = String(fx.height);
3363
3563
  ctx.defs.push(mask);
3364
3564
  node.mask = "url(#" + maskId + ")";
3365
3565
  return node;
@@ -3466,6 +3666,19 @@ function readTransformationFromBody(node) {
3466
3666
  if (parts.origin) out.origin = parts.origin;
3467
3667
  return Object.keys(out).length ? out : void 0;
3468
3668
  }
3669
+ if (node.transform && typeof node.transform === "object" && !node.transform.keyframes && !node.transform.kfs) {
3670
+ const wrapped = node.transform.value;
3671
+ const value = wrapped && typeof wrapped === "object" ? wrapped : node.transform;
3672
+ if (value && typeof value === "object") {
3673
+ const out = {};
3674
+ if (Array.isArray(value.translate)) out.translate = value.translate;
3675
+ if (typeof value.rotate === "number") out.rotate = value.rotate;
3676
+ if (typeof value.skew === "number") out.skew = value.skew;
3677
+ if (Array.isArray(value.scale)) out.scale = { value: value.scale };
3678
+ if (Array.isArray(value.origin)) out.origin = value.origin;
3679
+ return Object.keys(out).length ? out : void 0;
3680
+ }
3681
+ }
3469
3682
  return void 0;
3470
3683
  }
3471
3684
  function parseTransformStringToParts(s) {
@@ -3599,10 +3812,10 @@ function collectMaskAncestorChains(root, ctx) {
3599
3812
  const interestingNodes = /* @__PURE__ */ new Set();
3600
3813
  const collectInterestingNodes = (n) => {
3601
3814
  var _a, _b;
3602
- const href = (_b = (_a = n.effects) == null ? void 0 : _a.maskedBy) == null ? void 0 : _b.href;
3603
- if (typeof href === "string") {
3815
+ const maskSourceId = stripHash2((_b = (_a = n.effects) == null ? void 0 : _a.maskedBy) == null ? void 0 : _b.sourceId);
3816
+ if (typeof maskSourceId === "string") {
3604
3817
  interestingNodes.add(n);
3605
- const sourceNode = ctx.idMap.get(href);
3818
+ const sourceNode = ctx.idMap.get(maskSourceId);
3606
3819
  if (sourceNode) interestingNodes.add(sourceNode);
3607
3820
  }
3608
3821
  if (Array.isArray(n.children)) for (const ch of n.children) collectInterestingNodes(ch);
@@ -3628,8 +3841,9 @@ function extractTranslateOnly(node, ctx) {
3628
3841
  if (typeof tr === "string") {
3629
3842
  const parts = parseTranslateOnlyFromString(tr, ctx);
3630
3843
  if (parts) out.translate = parts;
3631
- } else if (tr && typeof tr === "object") {
3632
- const value = tr.value;
3844
+ } else if (tr && typeof tr === "object" && !tr.keyframes && !tr.kfs) {
3845
+ const wrapped = tr.value;
3846
+ const value = wrapped && typeof wrapped === "object" ? wrapped : tr;
3633
3847
  if (value && typeof value === "object" && Array.isArray(value.translate)) {
3634
3848
  out.translate = [value.translate[0] || 0, value.translate[1] || 0];
3635
3849
  }
@@ -3679,17 +3893,17 @@ function parseTranslateOnlyFromString(s, ctx) {
3679
3893
  var CONTENT_SUBREF = "content";
3680
3894
  function applyRefHref(node, clone2, ctx) {
3681
3895
  if (!clone2) return;
3682
- const baseId = clone2.baseId;
3683
- if (!baseId) {
3684
- if (clone2.type === CONTENT_SUBREF) ctx.errors.push("clone: content ref missing baseId");
3896
+ const sourceId = stripHash2(clone2.sourceId);
3897
+ if (!sourceId) {
3898
+ if (clone2.type === CONTENT_SUBREF) ctx.errors.push("clone: content ref missing sourceId");
3685
3899
  return;
3686
3900
  }
3687
- const targetId = clone2.type === CONTENT_SUBREF ? ctx.contentRefInnerIds.get(baseId) || baseId : baseId;
3901
+ const targetId = clone2.type === CONTENT_SUBREF ? ctx.contentRefInnerIds.get(sourceId) || sourceId : sourceId;
3688
3902
  node.href = "#" + targetId;
3689
3903
  }
3690
- function applyRefAndTransformationEffect(node, clone2, transformation, ctx) {
3904
+ function applyRefAndTransformationEffect(node, clone2, transformBy, ctx) {
3691
3905
  applyRefHref(node, clone2, ctx);
3692
- return applyTransformationEffect(node, transformation, ctx);
3906
+ return applyTransformByEffect(node, transformBy, ctx);
3693
3907
  }
3694
3908
 
3695
3909
  // src/effects/repeaterEffect.ts
@@ -3714,7 +3928,7 @@ function applyRepeaterEffect(node, fx, ctx) {
3714
3928
  for (let i = 1; i < copies; i++) {
3715
3929
  const baseClone = clone(base);
3716
3930
  const synthFx = synthesisePerCopyFx(fx, i);
3717
- const wrapped = synthFx ? applyTransformationEffect(baseClone, synthFx, ctx) : baseClone;
3931
+ const wrapped = synthFx ? applyTransformByEffect(baseClone, synthFx, ctx) : baseClone;
3718
3932
  children.push(wrapped);
3719
3933
  }
3720
3934
  const wrapper = { type: "g", children };
@@ -3726,10 +3940,13 @@ function applyRepeaterEffect(node, fx, ctx) {
3726
3940
  function synthesisePerCopyFx(fx, i) {
3727
3941
  const out = {};
3728
3942
  if (fx.translate !== void 0) {
3729
- out.translate = mapAnimatableVec2(fx.translate, (v) => [v[0] * i, v[1] * i]);
3943
+ out.translate = mapAnimatable(fx.translate, (v) => [v[0] * i, v[1] * i]);
3730
3944
  }
3731
3945
  if (fx.rotate !== void 0) {
3732
- out.rotate = mapAnimatableNumber(fx.rotate, (v) => v * i);
3946
+ out.rotate = mapAnimatable(fx.rotate, (v) => v * i);
3947
+ }
3948
+ if (fx.skew !== void 0) {
3949
+ out.skew = mapAnimatable(fx.skew, (v) => v * i);
3733
3950
  }
3734
3951
  if (fx.scale !== void 0) {
3735
3952
  out.scale = synthesiseScale(fx.scale, i);
@@ -3739,65 +3956,52 @@ function synthesisePerCopyFx(fx, i) {
3739
3956
  }
3740
3957
  return Object.keys(out).length ? out : void 0;
3741
3958
  }
3742
- function mapAnimatableVec2(raw, fn) {
3743
- if (Array.isArray(raw)) return fn(raw);
3744
- if (raw && typeof raw === "object") {
3745
- const obj = raw;
3746
- if (Array.isArray(obj.keyframes)) {
3747
- return __spreadProps(__spreadValues({}, obj), {
3748
- keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: fn(kf.value) }) : kf)
3749
- });
3750
- }
3751
- if (obj.value !== void 0) {
3752
- return __spreadProps(__spreadValues({}, obj), { value: fn(obj.value) });
3753
- }
3754
- }
3755
- return raw;
3756
- }
3757
- function mapAnimatableNumber(raw, fn) {
3758
- if (typeof raw === "number") return fn(raw);
3759
- if (raw && typeof raw === "object") {
3760
- const obj = raw;
3761
- if (Array.isArray(obj.keyframes)) {
3762
- return __spreadProps(__spreadValues({}, obj), {
3763
- keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: fn(kf.value) }) : kf)
3764
- });
3765
- }
3766
- if (obj.value !== void 0) {
3767
- return __spreadProps(__spreadValues({}, obj), { value: fn(obj.value) });
3768
- }
3959
+ function mapAnimatable(raw, fn, wrapStatic = false) {
3960
+ const read = readAnimatable(raw);
3961
+ if (read.kind === "absent" /* Absent */) return raw;
3962
+ if (read.kind === "static" /* Static */) {
3963
+ const mapped = fn(read.value);
3964
+ const wasRawStatic = typeof raw === "number" || Array.isArray(raw);
3965
+ return wasRawStatic && !wrapStatic ? mapped : { value: mapped };
3769
3966
  }
3770
- return raw;
3967
+ const out = {
3968
+ keyframes: read.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: fn(kf.value) }) : kf)
3969
+ };
3970
+ if (read.loop !== void 0) out.loop = read.loop;
3971
+ if (read.autoOrient !== void 0) out.autoOrient = read.autoOrient;
3972
+ if (read.base !== void 0) out.value = fn(read.base);
3973
+ return out;
3771
3974
  }
3772
3975
  function synthesiseScale(raw, i) {
3773
- const scalePowerFromPercent = (v) => [Math.pow(v[0] / 100, i), Math.pow(v[1] / 100, i)];
3774
- const scalePowerFromUnits = (v) => [Math.pow(v[0], i), Math.pow(v[1], i)];
3775
- if (Array.isArray(raw)) {
3776
- return { value: scalePowerFromPercent(raw) };
3777
- }
3778
- if (raw && typeof raw === "object") {
3779
- const obj = raw;
3780
- if (Array.isArray(obj.keyframes)) {
3781
- return __spreadProps(__spreadValues({}, obj), {
3782
- keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: scalePowerFromUnits(kf.value) }) : kf)
3783
- });
3784
- }
3785
- if (obj.value !== void 0) {
3786
- return __spreadProps(__spreadValues({}, obj), { value: scalePowerFromPercent(obj.value) });
3787
- }
3788
- }
3789
- return raw;
3976
+ const scalePower = (v) => [Math.pow(v[0], i), Math.pow(v[1], i)];
3977
+ return mapAnimatable(raw, scalePower, true);
3790
3978
  }
3791
3979
 
3792
3980
  // src/effects/retimeEffect.ts
3793
3981
  var RETIME_MATERIALISATION_MODE_INLINE_G = false;
3794
3982
  function asRetime(r) {
3795
3983
  var _a, _b;
3796
- if (r.timeCrop) {
3797
- console.warn("retime: `timeCrop` is not supported yet and will be ignored");
3798
- }
3799
3984
  return { start: (_a = r.start) != null ? _a : 0, stretch: (_b = r.stretch) != null ? _b : 1 };
3800
3985
  }
3986
+ var CROP_EDGE_MS = 1;
3987
+ function applyTimeCrop(useNode, crop, ctx) {
3988
+ const [start, end] = crop;
3989
+ if (!Number.isFinite(start) || !Number.isFinite(end)) {
3990
+ ctx.warnings.push("retime: timeCrop is not a pair of finite numbers \u2014 ignored");
3991
+ return;
3992
+ }
3993
+ const keyframes = end <= start ? [{ time: 0, value: 0 }] : [
3994
+ ...start > 0 ? [{ time: Math.max(0, start - CROP_EDGE_MS), value: 0 }] : [],
3995
+ { time: Math.max(0, start), value: 1 },
3996
+ { time: end, value: 1 },
3997
+ { time: end + CROP_EDGE_MS, value: 0 }
3998
+ ];
3999
+ const inner = __spreadValues({}, useNode);
4000
+ for (const k of Object.keys(useNode)) delete useNode[k];
4001
+ useNode.type = "g";
4002
+ useNode.children = [inner];
4003
+ useNode.animate = { opacity: { keyframes } };
4004
+ }
3801
4005
  function concatRetime(child, parent) {
3802
4006
  return {
3803
4007
  start: parent.start + parent.stretch * child.start,
@@ -3857,8 +4061,10 @@ function applyAllRetimeEffects(root, ctx) {
3857
4061
  for (const useNode of sites) {
3858
4062
  const retime = readCloneRetime(useNode);
3859
4063
  if (!retime) continue;
4064
+ const crop = retime.timeCrop;
3860
4065
  clearCloneRetime(useNode);
3861
4066
  materialiseRetime(useNode, asRetime(retime), ctx);
4067
+ if (crop) applyTimeCrop(useNode, crop, ctx);
3862
4068
  }
3863
4069
  }
3864
4070
  function materialiseRetime(useNode, retime, ctx) {
@@ -4105,14 +4311,11 @@ function createPathSampler(d) {
4105
4311
  // src/effects/textPathEffect.ts
4106
4312
  var EXTEND_MARGIN_FRAC = 0.15;
4107
4313
  function numRange(v) {
4108
- if (typeof v === "number") return { min: v, max: v };
4109
- if (v && typeof v === "object") {
4110
- const o = v;
4111
- if (typeof o.value === "number") return { min: o.value, max: o.value };
4112
- if (Array.isArray(o.keyframes) && o.keyframes.length) {
4113
- const vals = o.keyframes.map((k) => Number(k.value) || 0);
4114
- return { min: Math.min(...vals), max: Math.max(...vals) };
4115
- }
4314
+ const read = readAnimatable(v);
4315
+ if (read.kind === "static" /* Static */ && typeof read.value === "number") return { min: read.value, max: read.value };
4316
+ if (read.kind === "animated" /* Animated */ && read.keyframes.length) {
4317
+ const vals = read.keyframes.map((k) => Number(k.value) || 0);
4318
+ return { min: Math.min(...vals), max: Math.max(...vals) };
4116
4319
  }
4117
4320
  return { min: 0, max: 0 };
4118
4321
  }
@@ -4132,11 +4335,16 @@ function estimateTextAdvance(node) {
4132
4335
  var r3 = (n) => Math.round(n * 1e3) / 1e3;
4133
4336
  function shiftAnimatable(v, by) {
4134
4337
  if (!by || v === void 0 || v === null) return v;
4135
- if (typeof v === "number") return v + by;
4136
- const o = v;
4137
- if (typeof o.value === "number") return __spreadProps(__spreadValues({}, o), { value: o.value + by });
4138
- if (Array.isArray(o.keyframes)) return __spreadProps(__spreadValues({}, o), { keyframes: o.keyframes.map((k) => __spreadProps(__spreadValues({}, k), { value: (Number(k.value) || 0) + by })) });
4139
- return v;
4338
+ const read = readAnimatable(v);
4339
+ if (read.kind === "absent" /* Absent */) return v;
4340
+ if (read.kind === "static" /* Static */) return typeof v === "number" ? read.value + by : { value: read.value + by };
4341
+ const out = {
4342
+ keyframes: read.keyframes.map((k) => __spreadProps(__spreadValues({}, k), { value: (Number(k.value) || 0) + by }))
4343
+ };
4344
+ if (read.loop !== void 0) out.loop = read.loop;
4345
+ if (read.autoOrient !== void 0) out.autoOrient = read.autoOrient;
4346
+ if (read.base !== void 0) out.value = read.base + by;
4347
+ return out;
4140
4348
  }
4141
4349
  function extendedPathForBrowser(pathD, opts) {
4142
4350
  var _a;
@@ -4192,26 +4400,7 @@ function applyTextPathEffect(node, fx, ctx) {
4192
4400
  }
4193
4401
  function applyAnimatableNumber(node, attrName, raw) {
4194
4402
  if (raw === void 0 || raw === null) return;
4195
- if (typeof raw === "number") {
4196
- node[attrName] = String(raw);
4197
- return;
4198
- }
4199
- if (typeof raw === "object" && raw !== null) {
4200
- const obj = raw;
4201
- if (Array.isArray(obj.keyframes)) {
4202
- const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
4203
- const animate = __spreadValues({}, prevAnimate || {});
4204
- const block = { keyframes: obj.keyframes };
4205
- if (obj.loop !== void 0) block.loop = obj.loop;
4206
- animate[attrName] = block;
4207
- node.animate = animate;
4208
- return;
4209
- }
4210
- if (typeof obj.value === "number") {
4211
- node[attrName] = String(obj.value);
4212
- return;
4213
- }
4214
- }
4403
+ writeAnimatableChannel(node, attrName, readAnimatable(raw), { asString: true });
4215
4404
  }
4216
4405
 
4217
4406
  // src/effects/elementFactory.ts
@@ -4405,13 +4594,13 @@ function materialiseGlyphTextHorizontal(node, opts) {
4405
4594
  if (y !== void 0) pen.y = y;
4406
4595
  pen.x += (_a2 = parseLen(el.dx)) != null ? _a2 : 0;
4407
4596
  pen.y += (_b2 = parseLen(el.dy)) != null ? _b2 : 0;
4408
- const content = (_c2 = str(el[TEXT_ATTR])) != null ? _c2 : str(el[TEXT_CONTENT_ATTR]);
4597
+ const content = (_c2 = str(el[TEXT_CONTENT_ATTR])) != null ? _c2 : str(el[TEXT_ATTR]);
4409
4598
  if (content && !((_d2 = el.children) == null ? void 0 : _d2.length)) renderChars(content, s);
4410
4599
  if (el.children) for (const ch of el.children) walk(ch, s);
4411
4600
  };
4412
4601
  const rootStyle = rootStyleOf(node);
4413
4602
  if (node.children) for (const ch of node.children) walk(ch, rootStyle);
4414
- const rootContent = (_c = str(node[TEXT_ATTR])) != null ? _c : str(node[TEXT_CONTENT_ATTR]);
4603
+ const rootContent = (_c = str(node[TEXT_CONTENT_ATTR])) != null ? _c : str(node[TEXT_ATTR]);
4415
4604
  if (rootContent && !((_d = node.children) == null ? void 0 : _d.length)) renderChars(rootContent, rootStyle);
4416
4605
  const anchor = str(node.textAnchor);
4417
4606
  if (anchor === "middle" || anchor === "end") {
@@ -4460,7 +4649,7 @@ function layoutGlyphTextChars(node, opts) {
4460
4649
  if (y !== void 0) pen.y = y;
4461
4650
  pen.x += (_a2 = parseLen(el.dx)) != null ? _a2 : 0;
4462
4651
  pen.y += (_b2 = parseLen(el.dy)) != null ? _b2 : 0;
4463
- const content = (_c2 = str(el[TEXT_ATTR])) != null ? _c2 : str(el[TEXT_CONTENT_ATTR]);
4652
+ const content = (_c2 = str(el[TEXT_CONTENT_ATTR])) != null ? _c2 : str(el[TEXT_ATTR]);
4464
4653
  if (content && !((_d2 = el.children) == null ? void 0 : _d2.length)) renderChars(content, s);
4465
4654
  if (el.children) for (const ch of el.children) walk(ch, s);
4466
4655
  };
@@ -4475,7 +4664,7 @@ function layoutGlyphTextChars(node, opts) {
4475
4664
  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 });
4476
4665
  }
4477
4666
  }
4478
- const rootContent = (_e = str(node[TEXT_ATTR])) != null ? _e : str(node[TEXT_CONTENT_ATTR]);
4667
+ const rootContent = (_e = str(node[TEXT_CONTENT_ATTR])) != null ? _e : str(node[TEXT_ATTR]);
4479
4668
  if (rootContent && !((_f = node.children) == null ? void 0 : _f.length)) renderChars(rootContent, rootStyle);
4480
4669
  const anchor = str(node.textAnchor);
4481
4670
  if (anchor === "middle" || anchor === "end") {
@@ -4503,7 +4692,7 @@ function layoutGlyphTextCharsAlongPath(node, pathD, opts) {
4503
4692
  const walk = (el, parentStyle) => {
4504
4693
  var _a2, _b2, _c;
4505
4694
  const s = resolveStyle2(el, parentStyle);
4506
- const content = (_a2 = str(el[TEXT_ATTR])) != null ? _a2 : str(el[TEXT_CONTENT_ATTR]);
4695
+ const content = (_a2 = str(el[TEXT_CONTENT_ATTR])) != null ? _a2 : str(el[TEXT_ATTR]);
4507
4696
  if (content && !((_b2 = el.children) == null ? void 0 : _b2.length)) {
4508
4697
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
4509
4698
  const upm = (gf == null ? void 0 : gf.unitsPerEm) || 1e3;
@@ -4557,7 +4746,7 @@ function collectAlongPathCells(node, glyphs, soleFont, warnings) {
4557
4746
  const walk = (el, parentStyle) => {
4558
4747
  var _a, _b, _c;
4559
4748
  const s = resolveStyle2(el, parentStyle);
4560
- const content = (_a = str(el[TEXT_ATTR])) != null ? _a : str(el[TEXT_CONTENT_ATTR]);
4749
+ const content = (_a = str(el[TEXT_CONTENT_ATTR])) != null ? _a : str(el[TEXT_ATTR]);
4561
4750
  if (content && !((_b = el.children) == null ? void 0 : _b.length)) {
4562
4751
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
4563
4752
  if (gf) {
@@ -4781,9 +4970,9 @@ function applyTextGlyphsAlongPath(node, ctx, pathD, startOffset, textLength, pat
4781
4970
  }
4782
4971
 
4783
4972
  // src/effects/trimPathEffect.ts
4784
- function applyTrimPathEffect(node, trimPath, isCombinedShape, ctx) {
4973
+ function applyTrimPathEffect(node, trimPath, ctx) {
4785
4974
  if (!trimPath) return node;
4786
- const trimAllAsOne = !!trimPath.trimAllAsOne;
4975
+ const combined = trimPath.subPaths === PxTrimSubPaths.combined;
4787
4976
  const leafEntries = [];
4788
4977
  const measure = (n) => {
4789
4978
  if (Array.isArray(n.children) && n.children.length > 0) {
@@ -4804,16 +4993,16 @@ function applyTrimPathEffect(node, trimPath, isCombinedShape, ctx) {
4804
4993
  measure(node);
4805
4994
  if (!leafEntries.length) return node;
4806
4995
  let acc = 0;
4807
- const iterOrder = trimAllAsOne ? [...leafEntries].reverse() : leafEntries;
4996
+ const iterOrder = combined ? [...leafEntries].reverse() : leafEntries;
4808
4997
  for (const entry of iterOrder) {
4809
4998
  for (const sp of entry.subpaths) {
4810
- if (!trimAllAsOne) acc = 0;
4999
+ if (!combined) acc = 0;
4811
5000
  sp.startOffsetPx = acc;
4812
5001
  acc += sp.lengthPx;
4813
5002
  }
4814
5003
  }
4815
5004
  const chainLengthPx = acc;
4816
- if (trimAllAsOne && chainLengthPx < 1e-3) return node;
5005
+ if (combined && chainLengthPx < 1e-3) return node;
4817
5006
  const offsetReadRaw = readAnimatable(trimPath.offset);
4818
5007
  const offsetRead = offsetReadRaw.kind === "absent" /* Absent */ ? { kind: "static" /* Static */, value: 0 } : offsetReadRaw;
4819
5008
  const rangeReadRaw = readRangeWithCrossings(trimPath.range);
@@ -4825,18 +5014,18 @@ function applyTrimPathEffect(node, trimPath, isCombinedShape, ctx) {
4825
5014
  if (leafEntries.length === 1 && leafEntries[0].leaf === node && leafEntries[0].subpaths.length === 1) {
4826
5015
  const entry = leafEntries[0];
4827
5016
  const sp = entry.subpaths[0];
4828
- const pathLengthPx = trimAllAsOne ? chainLengthPx : sp.lengthPx;
5017
+ const pathLengthPx = combined ? chainLengthPx : sp.lengthPx;
4829
5018
  if (pathLengthPx < 1e-3) return node;
4830
- const startOffsetPct = trimAllAsOne ? sp.startOffsetPx / pathLengthPx : 0;
5019
+ const startOffsetPct = combined ? sp.startOffsetPx / pathLengthPx : 0;
4831
5020
  return collapseLeafWithTrim(entry.leaf, pathLengthPx, startOffsetPct, minMaxOffset, offsetRead, rangeRead);
4832
5021
  }
4833
5022
  const replacements = /* @__PURE__ */ new Map();
4834
5023
  for (const entry of leafEntries) {
4835
5024
  const newChildren = [];
4836
5025
  for (const sp of entry.subpaths) {
4837
- const pathLengthPx = trimAllAsOne ? chainLengthPx : sp.lengthPx;
5026
+ const pathLengthPx = combined ? chainLengthPx : sp.lengthPx;
4838
5027
  if (pathLengthPx < 1e-3) continue;
4839
- const startOffsetPct = trimAllAsOne ? sp.startOffsetPx / pathLengthPx : 0;
5028
+ const startOffsetPct = combined ? sp.startOffsetPx / pathLengthPx : 0;
4840
5029
  newChildren.push(...buildSubpathNodes(entry.leaf, sp.subpath, pathLengthPx, startOffsetPct, minMaxOffset, offsetRead, rangeRead, ctx));
4841
5030
  }
4842
5031
  replacements.set(entry.leaf, wrapLeafAsGroup(entry.leaf, newChildren));
@@ -4943,22 +5132,8 @@ function computeAnimAttr(read, map) {
4943
5132
  };
4944
5133
  }
4945
5134
  function applyAttr(node, attrName, attr) {
4946
- var _a, _b;
4947
5135
  if (!attr) return;
4948
- if (attr.kind === "static" /* Static */) {
4949
- node[attrName] = attr.value;
4950
- return;
4951
- }
4952
- const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
4953
- const animate = __spreadValues({}, prevAnimate || {});
4954
- const block = { keyframes: attr.keyframes };
4955
- if (attr.loop !== void 0) block.loop = attr.loop;
4956
- animate[attrName] = block;
4957
- node.animate = animate;
4958
- const firstKf = attr.keyframes[0];
4959
- if (firstKf && ((_a = firstKf.value) != null ? _a : firstKf.v) !== void 0) {
4960
- node[attrName] = (_b = firstKf.value) != null ? _b : firstKf.v;
4961
- }
5136
+ writeAnimatableChannel(node, attrName, attr);
4962
5137
  }
4963
5138
  var OPACITY_STEP_MS = 10;
4964
5139
  function computeOpacityFromRange(rangeRead) {
@@ -5142,9 +5317,9 @@ function applyPlayerEffects(root) {
5142
5317
  nextId: 0,
5143
5318
  contentRefInnerIds: /* @__PURE__ */ new Map(),
5144
5319
  maskAncestorChains: /* @__PURE__ */ new Map(),
5145
- // Resolved engine: `frames` ONLY when explicitly set; auto/webapi/unset →
5146
- // webapi (we're not 100% sure it's frames, and CSS/WAAPI need the inline form).
5147
- engine: ((_a = getAnimatorConfig(root)) == null ? void 0 : _a.mode) === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.webapi,
5320
+ // Resolved engine: `frames` ONLY when explicitly set; auto/waapi/unset →
5321
+ // waapi (we're not 100% sure it's frames, and CSS/WAAPI need the inline form).
5322
+ engine: ((_a = getAnimatorConfig(root)) == null ? void 0 : _a.mode) === PxAnimatorMode.frames ? PxAnimatorEngine.frames : PxAnimatorEngine.waapi,
5148
5323
  glyphs: (_b = getDefs(root)) == null ? void 0 : _b.glyphs
5149
5324
  };
5150
5325
  const working = clone(root);
@@ -5162,8 +5337,7 @@ function applyPlayerEffects_exceptRetime(node, ctx) {
5162
5337
  const originalId = typeof node.id === "string" ? node.id : void 0;
5163
5338
  const innerIdForContentRef = originalId ? ctx.contentRefInnerIds.get(originalId) : void 0;
5164
5339
  if (!fx && !innerIdForContentRef) return node;
5165
- const { transformation, repeater, maskedBy, clipPath, trimPath, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
5166
- const isCombinedShape = fx == null ? void 0 : fx.isCombinedShape;
5340
+ const { transformBy, repeater, maskedBy, clipPath, trimPath, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
5167
5341
  if (fx) delete node.effects;
5168
5342
  let n = node;
5169
5343
  let consumedByGlyphs = false;
@@ -5183,15 +5357,15 @@ function applyPlayerEffects_exceptRetime(node, ctx) {
5183
5357
  if (!consumedByGlyphs) n = applyTextPathEffect(n, textPath, ctx);
5184
5358
  n = applyFillGradientEffect(n, fillGradient, ctx);
5185
5359
  n = applyStrokeGradientEffect(n, strokeGradient, ctx);
5186
- n = applyTrimPathEffect(n, trimPath, isCombinedShape, ctx);
5360
+ n = applyTrimPathEffect(n, trimPath, ctx);
5187
5361
  n = applyRepeaterEffect(n, repeater, ctx);
5188
- n = applyMaskedByEffect(n, maskedBy, transformation, ctx);
5362
+ n = applyMaskedByEffect(n, maskedBy, transformBy, ctx);
5189
5363
  n = applyClipPathEffect(n, clipPath, ctx);
5190
5364
  if (innerIdForContentRef) {
5191
5365
  applyRefHref(n, cloneFx, ctx);
5192
- n = splitForContentRef(n, transformation, originalId, innerIdForContentRef, ctx);
5366
+ n = splitForContentRef(n, transformBy, originalId, innerIdForContentRef, ctx);
5193
5367
  } else {
5194
- n = applyRefAndTransformationEffect(n, cloneFx, transformation, ctx);
5368
+ n = applyRefAndTransformationEffect(n, cloneFx, transformBy, ctx);
5195
5369
  }
5196
5370
  if (cloneFx == null ? void 0 : cloneFx.retime) node.effects = { clone: { retime: cloneFx.retime } };
5197
5371
  if (originalId) ctx.idMap.set(originalId, n);
@@ -5208,7 +5382,7 @@ function materialiseAllInTree(doc, engine, opts) {
5208
5382
  let root = applyPlayerEffects(doc).root;
5209
5383
  const duration = (_b = (_a = getAnimatorConfig(root)) == null ? void 0 : _a.duration) != null ? _b : DEFAULT_DURATION_MS;
5210
5384
  root = materialiseInternalLoopsInTree(root, duration);
5211
- if (engine === PxAnimatorEngine.webapi) {
5385
+ if (engine === PxAnimatorEngine.waapi) {
5212
5386
  root = materialiseMotionPathsInTree(root, opts == null ? void 0 : opts.motionPath);
5213
5387
  root = materialiseAnimatedUseInstances(root);
5214
5388
  root = pruneUnreferencedDefs(root);
@@ -5597,6 +5771,7 @@ function evalTransformValue(v, t) {
5597
5771
  if (typeof v === "string") return parseTransformString(v);
5598
5772
  if (v.keyframes) return partsToMatrix(interpParts(v.keyframes, t));
5599
5773
  if (v.value) return partsToMatrix(v.value);
5774
+ if (typeof v === "object" && !Array.isArray(v)) return partsToMatrix(v);
5600
5775
  return IDENTITY;
5601
5776
  }
5602
5777
  function nodeMatrix(node, t) {
@@ -5759,6 +5934,8 @@ function subtractMultiset(a, b) {
5759
5934
  PxGradientType,
5760
5935
  PxGradientUnits,
5761
5936
  PxKeyframeSchema,
5937
+ PxKeyframeValueSchema,
5938
+ PxLoopExtend,
5762
5939
  PxLoopSchema,
5763
5940
  PxMaskedByEffectSchema,
5764
5941
  PxNodeBase,
@@ -5770,11 +5947,12 @@ function subtractMultiset(a, b) {
5770
5947
  PxSvgNodeExtra,
5771
5948
  PxTextEffectSchema,
5772
5949
  PxTextPathEffectSchema,
5950
+ PxTransformByEffectSchema,
5773
5951
  PxTransformPartsSchema,
5774
5952
  PxTransformValueSchema,
5775
- PxTransformationEffectSchema,
5776
5953
  PxTriggerSchema,
5777
5954
  PxTrimPathEffectSchema,
5955
+ PxTrimSubPaths,
5778
5956
  STYLE_ATTR_NAMES,
5779
5957
  TEXT_ATTR,
5780
5958
  TEXT_CONTENT_ATTR,