@pixodesk/svg-animator-core 1.0.22 → 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
@@ -72,12 +72,13 @@ __export(index_exports, {
72
72
  PxEffectsSchema: () => PxEffectsSchema,
73
73
  PxElementAnimationSchema: () => PxElementAnimationSchema,
74
74
  PxFillGradientEffectSchema: () => PxFillGradientEffectSchema,
75
- PxGradientGeometryAnimationSchema: () => PxGradientGeometryAnimationSchema,
76
75
  PxGradientSpreadMethod: () => PxGradientSpreadMethod,
77
76
  PxGradientStopSchema: () => PxGradientStopSchema,
78
77
  PxGradientType: () => PxGradientType,
79
78
  PxGradientUnits: () => PxGradientUnits,
80
79
  PxKeyframeSchema: () => PxKeyframeSchema,
80
+ PxKeyframeValueSchema: () => PxKeyframeValueSchema,
81
+ PxLoopExtend: () => PxLoopExtend,
81
82
  PxLoopSchema: () => PxLoopSchema,
82
83
  PxMaskedByEffectSchema: () => PxMaskedByEffectSchema,
83
84
  PxNodeBase: () => PxNodeBase,
@@ -89,11 +90,12 @@ __export(index_exports, {
89
90
  PxSvgNodeExtra: () => PxSvgNodeExtra,
90
91
  PxTextEffectSchema: () => PxTextEffectSchema,
91
92
  PxTextPathEffectSchema: () => PxTextPathEffectSchema,
93
+ PxTransformByEffectSchema: () => PxTransformByEffectSchema,
92
94
  PxTransformPartsSchema: () => PxTransformPartsSchema,
93
95
  PxTransformValueSchema: () => PxTransformValueSchema,
94
- PxTransformationEffectSchema: () => PxTransformationEffectSchema,
95
96
  PxTriggerSchema: () => PxTriggerSchema,
96
97
  PxTrimPathEffectSchema: () => PxTrimPathEffectSchema,
98
+ PxTrimSubPaths: () => PxTrimSubPaths,
97
99
  STYLE_ATTR_NAMES: () => STYLE_ATTR_NAMES,
98
100
  TEXT_ATTR: () => TEXT_ATTR,
99
101
  TEXT_CONTENT_ATTR: () => TEXT_CONTENT_ATTR,
@@ -274,8 +276,9 @@ var Union = class extends Base {
274
276
  }
275
277
  isValid(raw, ctx, path) {
276
278
  var _a;
277
- if (this.schemas.some((s) => s.isValid(raw))) return true;
278
- 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));
279
282
  return false;
280
283
  }
281
284
  _canSanitize(raw) {
@@ -351,6 +354,7 @@ var Obj = class extends Base {
351
354
  if (ctx == null ? void 0 : ctx.strict) {
352
355
  for (const key of Object.keys(obj)) {
353
356
  if (key in this._shape) continue;
357
+ if (obj[key] === void 0) continue;
354
358
  p.push(key);
355
359
  ctx.errors.push(pathStr(p) + ": unexpected extra key");
356
360
  p.pop();
@@ -491,6 +495,23 @@ var Any = class extends Base {
491
495
  return true;
492
496
  }
493
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
+ };
494
515
  var Lazy = class extends Base {
495
516
  constructor(fn, _default) {
496
517
  super();
@@ -602,24 +623,65 @@ var px = {
602
623
  record: (value) => new Rec(value),
603
624
  /** Passes anything through unchanged — always valid. */
604
625
  any: () => new Any(),
626
+ /** Anything EXCEPT `undefined` — an open type whose presence is required (V6). */
627
+ defined: () => new Defined(),
605
628
  /** Fixed-length tuple — validates element count and each position individually. */
606
629
  tuple: (schemas) => new Tuple(schemas),
607
630
  /** Defers schema creation — required for recursive types. Must supply a default value. */
608
631
  lazy: (fn, defaultVal) => new Lazy(fn, defaultVal)
609
632
  };
610
633
 
611
- // src/PxAnimatorTypes.ts
634
+ // src/PxAnimatorConstants.ts
612
635
  var PX_ANIM_SRC_ATTR_NAME = "data-px-animation-src";
613
636
  var PX_ANIM_ATTR_NAME = "_px_animator";
614
637
  var PxAnimatorMode = {
615
638
  auto: "auto",
616
- webapi: "webapi",
639
+ waapi: "waapi",
617
640
  frames: "frames"
618
641
  };
619
642
  var PxAnimatorEngine = {
620
- webapi: PxAnimatorMode.webapi,
643
+ waapi: PxAnimatorMode.waapi,
621
644
  frames: PxAnimatorMode.frames
622
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
+ };
623
685
  var TEXT_ATTR = "text";
624
686
  var TEXT_CONTENT_ATTR = "textContent";
625
687
  var INTERNAL_ATTRS = /* @__PURE__ */ new Set([
@@ -628,9 +690,51 @@ var INTERNAL_ATTRS = /* @__PURE__ */ new Set([
628
690
  "animator",
629
691
  "meta",
630
692
  "animate",
693
+ "effects",
631
694
  TEXT_ATTR,
632
695
  TEXT_CONTENT_ATTR
633
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
634
738
  var PxEasingOrRefSchema = px.union([
635
739
  px.string(),
636
740
  px.tuple([px.number(), px.number(), px.number(), px.number()])
@@ -642,13 +746,15 @@ var PxKeyframeValueSchema = implementsInterface()(px.union([
642
746
  px.array(px.number()),
643
747
  px.lazy(() => PxTransformPartsSchema, {}),
644
748
  px.object({ path: px.string() }),
645
- 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), [])
646
752
  ]));
647
753
  var PxKeyframeSchema = implementsInterface()(px.object({
648
754
  time: px.number().optional(),
649
755
  t: px.number().optional(),
650
- value: px.any().optional(),
651
- v: px.any().optional(),
756
+ value: PxKeyframeValueSchema.optional(),
757
+ v: PxKeyframeValueSchema.optional(),
652
758
  easing: PxEasingOrRefSchema.optional(),
653
759
  e: PxEasingOrRefSchema.optional(),
654
760
  tangentOut: px.tuple([px.number(), px.number()]).optional(),
@@ -662,16 +768,16 @@ var PxKeyframeSchema = implementsInterface()(px.object({
662
768
  }));
663
769
  var PxLoopSchema = implementsInterface()(px.object({
664
770
  segmentCount: px.number().optional(),
665
- before: px.boolean().optional(),
771
+ extend: px.enum([PxLoopExtend.before, PxLoopExtend.after]).optional(),
666
772
  alternate: px.boolean().optional()
667
773
  }));
668
774
  var PxPropertyAnimationSchema = implementsInterface()(px.object({
775
+ value: PxKeyframeValueSchema.optional(),
669
776
  keyframes: px.array(PxKeyframeSchema).optional(),
670
777
  kfs: px.array(PxKeyframeSchema).optional(),
671
778
  loop: px.union([PxLoopSchema, px.boolean()]).optional(),
672
779
  autoOrient: px.boolean().optional()
673
780
  }));
674
- var PX_TRANSFORM_PART_KEYS = ["translate", "rotate", "scale", "origin"];
675
781
  var PxTransformPartsSchema = implementsInterface()(px.object({
676
782
  translate: px.tuple([px.number(), px.number()]).optional(),
677
783
  rotate: px.number().optional(),
@@ -681,6 +787,7 @@ var PxTransformPartsSchema = implementsInterface()(px.object({
681
787
  }));
682
788
  var PxTransformValueSchema = px.union([
683
789
  px.string(),
790
+ PxTransformPartsSchema,
684
791
  px.object({ value: PxTransformPartsSchema }),
685
792
  PxPropertyAnimationSchema
686
793
  ]);
@@ -715,9 +822,11 @@ var PxDefsSchema = implementsInterface()(px.object({
715
822
  glyphs: px.record(PxGlyphFontSchema).optional()
716
823
  }));
717
824
  var PxAnimatorConfigSchema = implementsInterface()(px.object({
718
- mode: px.enum([PxAnimatorMode.auto, PxAnimatorMode.webapi, PxAnimatorMode.frames]).optional(),
825
+ mode: px.enum([PxAnimatorMode.auto, PxAnimatorMode.waapi, PxAnimatorMode.frames]).optional(),
719
826
  duration: px.number().optional(),
720
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.
721
830
  iterations: px.union([px.number(), px.literal("infinite")]).optional(),
722
831
  fill: px.enum(["forwards", "backwards", "both", "none"]).optional(),
723
832
  direction: px.enum(["normal", "reverse", "alternate", "alternate-reverse"]).optional(),
@@ -725,9 +834,8 @@ var PxAnimatorConfigSchema = implementsInterface()(px.object({
725
834
  trigger: PxTriggerSchema.optional(),
726
835
  resetOnFinish: px.boolean().optional(),
727
836
  definitions: PxDefsSchema.optional(),
728
- animate: px.record(PxElementAnimationSchema).optional(),
729
- timeline: px.string().optional(),
730
- debug: px.boolean().optional(),
837
+ animateById: px.record(PxElementAnimationSchema).optional(),
838
+ timelineSource: px.string().optional(),
731
839
  debugInstName: px.string().optional()
732
840
  }));
733
841
  var PxBindingSchema = implementsInterface()(px.object({
@@ -737,26 +845,29 @@ var PxBindingSchema = implementsInterface()(px.object({
737
845
  var PxAttrValueSchema = px.union([
738
846
  px.string(),
739
847
  px.number(),
740
- px.object({ value: px.any() }),
741
- 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
742
854
  ]);
743
855
  var PxAnimatableNumberSchema = px.union([
744
856
  px.number(),
745
857
  px.object({ value: px.number() }),
746
- px.object({
747
- keyframes: px.array(PxKeyframeSchema),
748
- autoOrient: px.boolean().optional()
749
- })
858
+ PxPropertyAnimationSchema
750
859
  ]);
751
860
  var PxAnimatableVec2Schema = px.union([
752
861
  px.tuple([px.number(), px.number()]),
753
862
  px.object({ value: px.tuple([px.number(), px.number()]) }),
754
- px.object({
755
- keyframes: px.array(PxKeyframeSchema),
756
- autoOrient: px.boolean().optional()
757
- })
863
+ PxPropertyAnimationSchema
864
+ ]);
865
+ var PxAnimatableStringSchema = px.union([
866
+ px.string(),
867
+ px.object({ value: px.string() }),
868
+ PxPropertyAnimationSchema
758
869
  ]);
759
- var PxTransformationEffectSchema = implementsInterface()(px.object({
870
+ var PxTransformByEffectSchema = implementsInterface()(px.object({
760
871
  translate: PxAnimatableVec2Schema.optional(),
761
872
  rotate: PxAnimatableNumberSchema.optional(),
762
873
  scale: PxAnimatableVec2Schema.optional(),
@@ -764,51 +875,46 @@ var PxTransformationEffectSchema = implementsInterface()(px.object({
764
875
  origin: PxAnimatableVec2Schema.optional()
765
876
  }));
766
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`.
767
880
  copies: px.number().optional(),
768
881
  translate: PxAnimatableVec2Schema.optional(),
769
882
  rotate: PxAnimatableNumberSchema.optional(),
883
+ skew: PxAnimatableNumberSchema.optional(),
770
884
  scale: PxAnimatableVec2Schema.optional(),
771
885
  origin: PxAnimatableVec2Schema.optional()
772
886
  }));
773
887
  var PxMaskedByEffectSchema = implementsInterface()(px.object({
774
- href: px.string().optional(),
775
- maskType: px.string().optional(),
776
- maskUnits: px.string().optional(),
777
- 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()
778
896
  }));
779
897
  var PxClipPathEffectSchema = implementsInterface()(px.object({
780
- d: px.string().optional(),
898
+ d: PxAnimatableStringSchema.optional(),
781
899
  animate: PxPropertyAnimationSchema.optional()
782
900
  }));
783
901
  var PxTrimPathEffectSchema = implementsInterface()(px.object({
784
902
  offset: PxAnimatableNumberSchema.optional(),
785
903
  range: PxAnimatableVec2Schema.optional(),
786
- trimAllAsOne: px.boolean().optional()
904
+ subPaths: px.enum([PxTrimSubPaths.separate, PxTrimSubPaths.combined]).optional()
787
905
  }));
788
906
  var PxRetimeEffectSchema = implementsInterface()(px.object({
789
- baseId: px.string().optional(),
907
+ sourceId: px.string().optional(),
790
908
  start: px.number().optional(),
791
909
  stretch: px.number().optional(),
792
910
  timeCrop: px.tuple([px.number(), px.number()]).optional()
793
911
  }));
794
912
  var PxCloneEffectSchema = implementsInterface()(px.object({
795
- type: px.string().optional(),
796
- 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(),
797
916
  retime: PxRetimeEffectSchema.optional()
798
917
  }));
799
- var PxGradientUnits = {
800
- userSpaceOnUse: "userSpaceOnUse",
801
- objectBoundingBox: "objectBoundingBox"
802
- };
803
- var PxGradientSpreadMethod = {
804
- pad: "pad",
805
- reflect: "reflect",
806
- repeat: "repeat"
807
- };
808
- var PxGradientType = {
809
- linear: "linear",
810
- radial: "radial"
811
- };
812
918
  var PxGradientStopSchema = implementsInterface()(px.object({
813
919
  offset: px.number(),
814
920
  color: px.string()
@@ -816,39 +922,28 @@ var PxGradientStopSchema = implementsInterface()(px.object({
816
922
  var PxAnimatableGradientStopsSchema = px.union([
817
923
  px.array(PxGradientStopSchema),
818
924
  px.object({ value: px.array(PxGradientStopSchema) }),
819
- px.object({ keyframes: px.array(PxKeyframeSchema) })
925
+ PxPropertyAnimationSchema
820
926
  ]);
821
- var PxGradientGeometryAnimationSchema = implementsInterface()(px.object({
822
- gradientX1: PxPropertyAnimationSchema.optional(),
823
- gradientY1: PxPropertyAnimationSchema.optional(),
824
- gradientX2: PxPropertyAnimationSchema.optional(),
825
- gradientY2: PxPropertyAnimationSchema.optional(),
826
- gradientCx: PxPropertyAnimationSchema.optional(),
827
- gradientCy: PxPropertyAnimationSchema.optional(),
828
- gradientFx: PxPropertyAnimationSchema.optional(),
829
- gradientFy: PxPropertyAnimationSchema.optional(),
830
- gradientR: PxPropertyAnimationSchema.optional()
831
- }));
832
927
  var PxFillGradientEffectSchema = implementsInterface()(px.object({
928
+ // Contextual kind — the `type` convention, see `PxNodeBase.type`.
833
929
  type: px.enum([PxGradientType.linear, PxGradientType.radial]),
834
- p1: px.tuple([px.number(), px.number()]).optional(),
835
- p2: px.tuple([px.number(), px.number()]).optional(),
836
- c: px.tuple([px.number(), px.number()]).optional(),
837
- r: px.number().optional(),
838
- 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(),
839
935
  stops: PxAnimatableGradientStopsSchema.optional(),
840
- gradientUnits: px.string().optional(),
841
- spreadMethod: px.string().optional(),
842
- gradientTransform: px.string().optional(),
843
- animate: PxGradientGeometryAnimationSchema.optional()
936
+ gradientUnits: px.enum([PxGradientUnits.userSpaceOnUse, PxGradientUnits.objectBoundingBox]).optional(),
937
+ spreadMethod: px.enum([PxGradientSpreadMethod.pad, PxGradientSpreadMethod.reflect, PxGradientSpreadMethod.repeat]).optional(),
938
+ gradientTransform: px.string().optional()
844
939
  }));
845
940
  var PxStrokeGradientEffectSchema = PxFillGradientEffectSchema;
846
941
  var PxTextPathEffectSchema = implementsInterface()(px.object({
847
942
  path: px.string(),
848
- pathOverflow: px.string().optional(),
849
- lengthAdjust: px.string().optional(),
850
- method: px.string().optional(),
851
- 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(),
852
947
  startOffset: PxAnimatableNumberSchema.optional(),
853
948
  textLength: PxAnimatableNumberSchema.optional()
854
949
  }));
@@ -856,13 +951,12 @@ var PxTextEffectSchema = implementsInterface()(px.object({
856
951
  useGlyphs: px.boolean().optional()
857
952
  }));
858
953
  var PxEffectsSchema = implementsInterface()(px.object({
859
- transformation: PxTransformationEffectSchema.optional(),
954
+ transformBy: PxTransformByEffectSchema.optional(),
860
955
  repeater: PxRepeaterEffectSchema.optional(),
861
956
  maskedBy: PxMaskedByEffectSchema.optional(),
862
957
  clipPath: PxClipPathEffectSchema.optional(),
863
958
  trimPath: PxTrimPathEffectSchema.optional(),
864
959
  clone: PxCloneEffectSchema.optional(),
865
- isCombinedShape: px.boolean().optional(),
866
960
  fillGradient: PxFillGradientEffectSchema.optional(),
867
961
  strokeGradient: PxStrokeGradientEffectSchema.optional(),
868
962
  textPath: PxTextPathEffectSchema.optional(),
@@ -886,6 +980,14 @@ function validateNodeEffects(root, opts) {
886
980
  return warnings;
887
981
  }
888
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.
889
991
  type: px.string(),
890
992
  id: px.string().optional(),
891
993
  meta: px.any().optional(),
@@ -895,7 +997,7 @@ var PxNodeBase = px.openObject({
895
997
  effects: PxEffectsSchema.optional(),
896
998
  // `PxElementAnimation` (not just `PxAnimationDefinition`) — accepts
897
999
  // string ref / array of refs / inline definition / mixed array; mirrors
898
- // `animator.animate` map values and what `processNode` resolves at runtime.
1000
+ // `animator.animateById` map values and what `processNode` resolves at runtime.
899
1001
  animate: PxElementAnimationSchema.optional(),
900
1002
  style: px.union([px.string(), px.record(px.union([px.string(), px.number()]))]).optional()
901
1003
  }, PxAttrValueSchema);
@@ -919,35 +1021,10 @@ var PxBezierPathSchema = implementsInterface()(px.object({
919
1021
  o: px.array(px.array(px.number())).optional(),
920
1022
  c: px.boolean().optional()
921
1023
  }));
922
- function isPxElementFileFormat(fileJson) {
923
- if (!(fileJson && typeof fileJson === "object" && !Array.isArray(fileJson))) {
924
- return false;
925
- }
926
- return fileJson["type"] === "svg" || fileJson["tagName"] === "svg";
927
- }
928
1024
  function isPxElementFileFormatDeep(fileJson) {
929
1025
  const valid = PxAnimatedSvgDocumentSchema.isValid(fileJson);
930
1026
  return { valid, errors: valid ? [] : ["Document failed schema validation"] };
931
1027
  }
932
- function getAnimatorConfig(doc) {
933
- var _a, _b;
934
- 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);
935
- }
936
- function getDefs(doc) {
937
- var _a;
938
- if (!doc) return void 0;
939
- return (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.definitions;
940
- }
941
- function getBindings(doc) {
942
- var _a;
943
- if (!doc) return void 0;
944
- const animate = (_a = getAnimatorConfig(doc)) == null ? void 0 : _a.animate;
945
- if (!animate) return void 0;
946
- return Object.entries(animate).map(([id, anim]) => ({ id, animate: anim }));
947
- }
948
- function getChildren(doc) {
949
- return doc == null ? void 0 : doc.children;
950
- }
951
1028
 
952
1029
  // src/PxAnimatorUtil.ts
953
1030
  function bezierToSvgPath(path, forceCurves = false) {
@@ -1402,7 +1479,7 @@ function generateNewIds(doc) {
1402
1479
  "flood-color",
1403
1480
  "lighting-color"
1404
1481
  ]);
1405
- const directIdRefAttrs = /* @__PURE__ */ new Set(["baseId", "targetId", "boundElementId"]);
1482
+ const directIdRefAttrs = /* @__PURE__ */ new Set(["sourceId", "targetId", "boundElementId"]);
1406
1483
  function collectIds(node) {
1407
1484
  if (!node || typeof node !== "object") return;
1408
1485
  if (node.id && typeof node.id === "string") {
@@ -1440,9 +1517,10 @@ function generateNewIds(doc) {
1440
1517
  } else if (urlRefAttrs.has(key)) {
1441
1518
  node[key] = replaceUrlRefs(value, idMap);
1442
1519
  } else if (directIdRefAttrs.has(key)) {
1443
- const newId = idMap.get(value);
1520
+ const hasHash = value.startsWith("#");
1521
+ const newId = idMap.get(hasHash ? value.slice(1) : value);
1444
1522
  if (newId) {
1445
- node[key] = newId;
1523
+ node[key] = hasHash ? "#" + newId : newId;
1446
1524
  }
1447
1525
  } else if (value.includes("url(#")) {
1448
1526
  node[key] = replaceUrlRefs(value, idMap);
@@ -1460,14 +1538,14 @@ function generateNewIds(doc) {
1460
1538
  }
1461
1539
  collectIds(cloned);
1462
1540
  updateRefs(cloned);
1463
- const docAnimate = (_a = cloned.animator) == null ? void 0 : _a.animate;
1541
+ const docAnimate = (_a = cloned.animator) == null ? void 0 : _a.animateById;
1464
1542
  if (docAnimate && typeof docAnimate === "object") {
1465
1543
  const updatedAnimate = {};
1466
1544
  for (const [id, anim] of Object.entries(docAnimate)) {
1467
1545
  const newId = (_b = idMap.get(id)) != null ? _b : id;
1468
1546
  updatedAnimate[newId] = anim;
1469
1547
  }
1470
- cloned.animator = __spreadProps(__spreadValues({}, cloned.animator), { animate: updatedAnimate });
1548
+ cloned.animator = __spreadProps(__spreadValues({}, cloned.animator), { animateById: updatedAnimate });
1471
1549
  }
1472
1550
  return cloned;
1473
1551
  }
@@ -1558,8 +1636,9 @@ function getNormalizedProps(props) {
1558
1636
  let value = props[rawKey];
1559
1637
  if (COLOUR_ATTR_NAMES.has(key) && Array.isArray(value)) {
1560
1638
  propsCopy[key] = toRGBA(value);
1561
- } else if (key === "transform" && value !== null && typeof value === "object" && !Array.isArray(value) && value.value && typeof value.value === "object") {
1562
- 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 });
1563
1642
  } else if (TRANSFORM_FN_NAMES.has(key)) {
1564
1643
  if (Array.isArray(value)) {
1565
1644
  if (key === "translate") value = value.map((v) => v + "px");
@@ -1567,6 +1646,8 @@ function getNormalizedProps(props) {
1567
1646
  }
1568
1647
  if (key === "rotate") value = value + "deg";
1569
1648
  propsCopy["transform"] = key + "(" + value + ")";
1649
+ } else if (Array.isArray(value)) {
1650
+ propsCopy[key] = value.join(",");
1570
1651
  } else if (value !== void 0 && value !== null) {
1571
1652
  propsCopy[key] = String(value);
1572
1653
  }
@@ -1968,6 +2049,16 @@ function walkAndMaterialise(node, opts) {
1968
2049
  }
1969
2050
 
1970
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
+ }
1971
2062
  function parsePathCommands(d) {
1972
2063
  const tokens = d.split(/([MLCZmlcz]|[\s,]+)/).map((t) => t.trim()).filter((t) => t && t !== ",");
1973
2064
  const commands = [];
@@ -2170,7 +2261,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2170
2261
  const totalIntervals = keyframes.length - 1;
2171
2262
  const segCount = clamp((_a = loop.segmentCount) != null ? _a : totalIntervals, 1, totalIntervals);
2172
2263
  let segKfs;
2173
- if (loop.before) {
2264
+ if (loop.extend === PxLoopExtend.before) {
2174
2265
  segKfs = keyframes.slice(0, segCount + 1);
2175
2266
  } else {
2176
2267
  segKfs = keyframes.slice(totalIntervals - segCount);
@@ -2178,7 +2269,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2178
2269
  const firstT = (_b = keyframes[0].t) != null ? _b : 0;
2179
2270
  const lastT = (_c = keyframes[keyframes.length - 1].t) != null ? _c : 0;
2180
2271
  let fillStart, fillEnd;
2181
- if (loop.before) {
2272
+ if (loop.extend === PxLoopExtend.before) {
2182
2273
  fillStart = 0;
2183
2274
  fillEnd = firstT;
2184
2275
  } else {
@@ -2205,7 +2296,10 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2205
2296
  const remainder = fillDuration - fullReps * segDuration;
2206
2297
  const partialFraction = remainder / segDuration;
2207
2298
  const looped = [];
2299
+ const separateBoundary = loop.extend !== PxLoopExtend.before;
2300
+ const originalTerminalKf = keyframes[keyframes.length - 1];
2208
2301
  function appendRep(repStart, isReversed, partial) {
2302
+ var _a2;
2209
2303
  let entries;
2210
2304
  if (isReversed) {
2211
2305
  entries = [];
@@ -2241,8 +2335,17 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2241
2335
  looped.push({ t: repStart + cutRelT * segDuration, v: cutValue, e: void 0 });
2242
2336
  return;
2243
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
+ }
2244
2347
  const pushed = {
2245
- t: repStart + entry.relT * segDuration,
2348
+ t: repStart + entry.relT * segDuration + (isBoundary ? LOOP_JUMP_SHIFT_MS : 0),
2246
2349
  v: entry.v,
2247
2350
  e: i < entries.length - 1 ? entry.e : void 0
2248
2351
  };
@@ -2290,7 +2393,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2290
2393
  looped.push(pushed);
2291
2394
  }
2292
2395
  }
2293
- if (loop.before) {
2396
+ if (loop.extend === PxLoopExtend.before) {
2294
2397
  if (partialFraction > 1e-9) {
2295
2398
  const isReversed = !!loop.alternate && fullReps % 2 === 0;
2296
2399
  appendRepTail(fillStart, isReversed, partialFraction);
@@ -2313,7 +2416,7 @@ function expandLoopKeyframes(propName, keyframes, loop, duration) {
2313
2416
  appendRep(repStart, isReversed, partialFraction);
2314
2417
  }
2315
2418
  }
2316
- if (loop.before) {
2419
+ if (loop.extend === PxLoopExtend.before) {
2317
2420
  return [...looped, ...keyframes];
2318
2421
  } else {
2319
2422
  return [...keyframes, ...looped];
@@ -2429,7 +2532,7 @@ var _elementIdCounter = 0;
2429
2532
  function generateElementId() {
2430
2533
  return "_px_el_" + ++_elementIdCounter;
2431
2534
  }
2432
- function normalizeAnimationDefinition(animDef, duration, defs, engine = PxAnimatorEngine.webapi) {
2535
+ function normalizeAnimationDefinition(animDef, duration, defs, engine = PxAnimatorEngine.waapi) {
2433
2536
  const normalized = {};
2434
2537
  for (const [propName, propAnim] of Object.entries(animDef)) {
2435
2538
  const normalizedKfs = normalizeKeyframes(propName, propAnim, duration, defs);
@@ -2437,12 +2540,12 @@ function normalizeAnimationDefinition(animDef, duration, defs, engine = PxAnimat
2437
2540
  const out = { kfs: normalizedKfs };
2438
2541
  if (propAnim.autoOrient !== void 0) out.autoOrient = propAnim.autoOrient;
2439
2542
  if (propAnim.loop !== void 0) out.loop = propAnim.loop;
2440
- normalized[propName] = engine === PxAnimatorEngine.webapi && propName === "transform" ? materialiseMotionPathInPropAnim(out) : out;
2543
+ normalized[propName] = engine === PxAnimatorEngine.waapi && propName === "transform" ? materialiseMotionPathInPropAnim(out) : out;
2441
2544
  }
2442
2545
  }
2443
2546
  return normalized;
2444
2547
  }
2445
- function getNormalisedBindings(doc, engine = PxAnimatorEngine.webapi) {
2548
+ function getNormalisedBindings(doc, engine = PxAnimatorEngine.waapi) {
2446
2549
  const animatorConfig = getAnimatorConfig(doc) || {};
2447
2550
  const defs = getDefs(doc);
2448
2551
  const duration = animatorConfig.duration || 1e3;
@@ -2853,20 +2956,41 @@ function partsRecord(part, value, origin) {
2853
2956
  return rec;
2854
2957
  }
2855
2958
  function readAnimatable(raw) {
2856
- var _a, _b;
2959
+ var _a, _b, _c;
2857
2960
  if (raw === void 0) return { kind: "absent" /* Absent */ };
2858
2961
  if (Array.isArray(raw)) return { kind: "static" /* Static */, value: raw };
2859
2962
  if (typeof raw === "object") {
2860
2963
  const obj = raw;
2861
2964
  const kfs = (_a = obj.keyframes) != null ? _a : obj.kfs;
2862
2965
  if (kfs) {
2863
- return { kind: "animated" /* Animated */, keyframes: kfs.map(normaliseKeyframe), autoOrient: obj.autoOrient, loop: obj.loop };
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;
2864
2970
  }
2865
- const staticValue = (_b = obj.value) != null ? _b : obj.v;
2971
+ const staticValue = (_c = obj.value) != null ? _c : obj.v;
2866
2972
  if (staticValue !== void 0) return { kind: "static" /* Static */, value: staticValue };
2867
2973
  }
2868
2974
  return { kind: "static" /* Static */, value: raw };
2869
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
+ }
2870
2994
  function normaliseKeyframe(kf) {
2871
2995
  if (!kf || typeof kf !== "object") return kf;
2872
2996
  const k = kf;
@@ -2886,7 +3010,7 @@ function readStaticOrigin(raw, ctx) {
2886
3010
  const o = readAnimatable(raw);
2887
3011
  if (o.kind === "absent" /* Absent */) return void 0;
2888
3012
  if (o.kind === "static" /* Static */) return o.value;
2889
- 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");
2890
3014
  return (_a = o.keyframes[0]) == null ? void 0 : _a.value;
2891
3015
  }
2892
3016
  function keyframeWith(kf, value) {
@@ -2899,7 +3023,7 @@ function keyframeWith(kf, value) {
2899
3023
  }
2900
3024
 
2901
3025
  // src/effects/transformationEffect.ts
2902
- function applyTransformationEffect(node, fx, ctx) {
3026
+ function applyTransformByEffect(node, fx, ctx) {
2903
3027
  if (!fx) return node;
2904
3028
  delete node.transform;
2905
3029
  let n = node;
@@ -2935,6 +3059,10 @@ function applyTransformationEffect(node, fx, ctx) {
2935
3059
  } else {
2936
3060
  n = wrapTransformPart(n, "translate" /* Translate */, fx.translate, ctx);
2937
3061
  }
3062
+ if (n !== node && node.id) {
3063
+ n.id = node.id;
3064
+ delete node.id;
3065
+ }
2938
3066
  return n;
2939
3067
  }
2940
3068
  function translateHasAutoOrient(translate) {
@@ -2944,8 +3072,6 @@ function translateHasAutoOrient(translate) {
2944
3072
  return Array.isArray(obj.keyframes) && obj.keyframes.some((kf) => kf.tangentOut || kf.tangentIn);
2945
3073
  }
2946
3074
  function normalizeScale(raw) {
2947
- if (raw === void 0) return void 0;
2948
- if (Array.isArray(raw)) return [raw[0] / 100, raw[1] / 100];
2949
3075
  return raw;
2950
3076
  }
2951
3077
  function wrapTransformPart(inner, part, raw, ctx) {
@@ -2987,35 +3113,57 @@ function wrapOrigin(inner, raw, invert) {
2987
3113
  return inner;
2988
3114
  }
2989
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
+
2990
3138
  // src/effects/contentRefSplit.ts
2991
3139
  function identifyContentRefTargets(node, ctx, allocator) {
2992
3140
  var _a, _b, _c;
2993
3141
  if (node.type === "use" && ((_b = (_a = node.effects) == null ? void 0 : _a.clone) == null ? void 0 : _b.type) === "content") {
2994
- const baseId = node.effects.clone.baseId;
2995
- if (typeof baseId === "string" && baseId && !ctx.contentRefInnerIds.has(baseId)) {
2996
- 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));
2997
3145
  }
2998
3146
  }
2999
3147
  (_c = node.children) == null ? void 0 : _c.forEach((c) => identifyContentRefTargets(c, ctx, allocator));
3000
3148
  }
3001
- function splitForContentRef(node, transformation, originalId, innerId, ctx) {
3002
- const outerBody = liftBodyTranslate(node, transformation);
3149
+ function splitForContentRef(node, transformBy, originalId, innerId, ctx) {
3150
+ const outerBody = liftBodyTranslate(node, transformBy);
3003
3151
  if (typeof node.id === "string") delete node.id;
3004
- const { outer: outerTr, inner: innerTr } = splitTransformationEffect(transformation);
3152
+ const { outer: outerTr, inner: innerTr } = splitTransformByEffect(transformBy);
3005
3153
  let innerNode = node;
3006
- innerNode = applyTransformationEffect(innerNode, innerTr, ctx);
3154
+ innerNode = applyTransformByEffect(innerNode, innerTr, ctx);
3007
3155
  const innerWrapper = { type: "g", id: innerId, children: [innerNode] };
3008
3156
  let outerWrapper = { type: "g", id: originalId, children: [innerWrapper] };
3009
3157
  if (outerBody.transform !== void 0) outerWrapper.transform = outerBody.transform;
3010
3158
  if (outerBody.animate !== void 0) outerWrapper.animate = outerBody.animate;
3011
3159
  if (outerTr) {
3012
3160
  delete outerWrapper.id;
3013
- outerWrapper = applyTransformationEffect(outerWrapper, outerTr, ctx);
3161
+ outerWrapper = applyTransformByEffect(outerWrapper, outerTr, ctx);
3014
3162
  outerWrapper.id = originalId;
3015
3163
  }
3016
3164
  return outerWrapper;
3017
3165
  }
3018
- function liftBodyTranslate(node, transformation) {
3166
+ function liftBodyTranslate(node, transformBy) {
3019
3167
  var _a, _b, _c;
3020
3168
  const out = {};
3021
3169
  let didLiftAnimate = false;
@@ -3069,7 +3217,7 @@ function liftBodyTranslate(node, transformation) {
3069
3217
  didLiftAnimate = true;
3070
3218
  }
3071
3219
  }
3072
- const transformationHasTranslate = (transformation == null ? void 0 : transformation.translate) !== void 0;
3220
+ const transformationHasTranslate = (transformBy == null ? void 0 : transformBy.translate) !== void 0;
3073
3221
  const stripBodyTranslateOnly = didLiftAnimate || transformationHasTranslate;
3074
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);
3075
3223
  if (typeof node.transform === "string") {
@@ -3088,6 +3236,24 @@ function liftBodyTranslate(node, transformation) {
3088
3236
  if (split.rest) node.transform = split.rest;
3089
3237
  else delete node.transform;
3090
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
+ }
3091
3257
  }
3092
3258
  return out;
3093
3259
  }
@@ -3157,7 +3323,7 @@ function parseTranslateArgs(translateOp) {
3157
3323
  const nums = m[1].split(/[\s,]+/).filter(Boolean).map(Number);
3158
3324
  return [nums[0] || 0, nums[1] || 0];
3159
3325
  }
3160
- function splitTransformationEffect(fx) {
3326
+ function splitTransformByEffect(fx) {
3161
3327
  if (!fx) return {};
3162
3328
  const originOnOuter = needsOriginOnOuter(fx.translate);
3163
3329
  const innerHasPivotedPart = fx.rotate !== void 0 || fx.scale !== void 0;
@@ -3184,28 +3350,6 @@ function needsOriginOnOuter(translateAnim) {
3184
3350
  return false;
3185
3351
  }
3186
3352
 
3187
- // src/effects/util.ts
3188
- function genId(ctx, prefix) {
3189
- return "_lw_" + prefix + "_" + ctx.nextId++;
3190
- }
3191
- function stripHash2(href) {
3192
- return typeof href === "string" ? href.replace(/^#/, "") : void 0;
3193
- }
3194
- function indexById(node, map) {
3195
- var _a;
3196
- if (typeof node.id === "string") map.set(node.id, node);
3197
- (_a = node.children) == null ? void 0 : _a.forEach((child) => indexById(child, map));
3198
- }
3199
- function spliceDefs(root, defs) {
3200
- if (!defs.length) return;
3201
- const existing = root.children || (root.children = []);
3202
- existing.unshift({ type: "defs", children: defs });
3203
- }
3204
- var clone = deepClonePxNode;
3205
- function regenerateIdsInClone(root, ctx) {
3206
- return regenerateIdsAndRewriteRefs(root, () => genId(ctx, "retimed"));
3207
- }
3208
-
3209
3353
  // src/effects/gradientEffect.ts
3210
3354
  function applyFillGradientEffect(node, fx, ctx) {
3211
3355
  return applyGradient(node, fx, ctx, "fill");
@@ -3227,62 +3371,63 @@ function synthesiseGradientDef(fx, id, ctx) {
3227
3371
  id
3228
3372
  };
3229
3373
  if (fx.type === PxGradientType.linear) {
3230
- if (fx.p1) {
3231
- out.x1 = String(fx.p1[0]);
3232
- out.y1 = String(fx.p1[1]);
3233
- }
3234
- if (fx.p2) {
3235
- out.x2 = String(fx.p2[0]);
3236
- out.y2 = String(fx.p2[1]);
3237
- }
3374
+ applyGeomVec(out, "x1", "y1", fx.p1);
3375
+ applyGeomVec(out, "x2", "y2", fx.p2);
3238
3376
  } else {
3239
- if (fx.c) {
3240
- out.cx = String(fx.c[0]);
3241
- out.cy = String(fx.c[1]);
3242
- }
3243
- if (fx.r !== void 0) out.r = String(fx.r);
3244
- if (fx.fp) {
3245
- out.fx = String(fx.fp[0]);
3246
- out.fy = String(fx.fp[1]);
3247
- }
3377
+ applyGeomVec(out, "cx", "cy", fx.c);
3378
+ applyGeomNumber(out, "r", fx.r);
3379
+ applyGeomVec(out, "fx", "fy", fx.fp);
3248
3380
  }
3249
3381
  if (fx.gradientUnits) out.gradientUnits = fx.gradientUnits;
3250
3382
  if (fx.spreadMethod) out.spreadMethod = fx.spreadMethod;
3251
3383
  if (fx.gradientTransform) out.gradientTransform = fx.gradientTransform;
3252
- const animate = fx.animate;
3253
- if (animate) {
3254
- const mapped = {};
3255
- for (const wireKey of Object.keys(animate)) {
3256
- const attr = GRADIENT_ANIM_CHANNEL_TO_ATTR[wireKey];
3257
- if (attr) mapped[attr] = animate[wireKey];
3258
- }
3259
- if (Object.keys(mapped).length) out.animate = mapped;
3260
- }
3261
3384
  out.children = buildStopChildren(fx.stops, ctx);
3262
3385
  return out;
3263
3386
  }
3264
- var GRADIENT_ANIM_CHANNEL_TO_ATTR = {
3265
- gradientX1: "x1",
3266
- gradientY1: "y1",
3267
- gradientX2: "x2",
3268
- gradientY2: "y2",
3269
- gradientCx: "cx",
3270
- gradientCy: "cy",
3271
- gradientFx: "fx",
3272
- gradientFy: "fy",
3273
- gradientR: "r"
3274
- };
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
+ }
3275
3422
  function buildStopChildren(stops, ctx) {
3276
3423
  var _a, _b, _c, _d;
3277
3424
  if (!stops) return [];
3278
- if (Array.isArray(stops)) return stops.map(staticStopNode);
3279
- if (typeof stops === "object" && Array.isArray(stops.value)) {
3280
- return stops.value.map(staticStopNode);
3281
- }
3282
- const animBlock = stops;
3283
- const kfs = animBlock.keyframes;
3284
- if (!Array.isArray(kfs) || !kfs.length) return [];
3285
- 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;
3286
3431
  let stopCount = 0;
3287
3432
  for (const kf of kfs) {
3288
3433
  const v = (_a = kf.value) != null ? _a : kf.v;
@@ -3359,38 +3504,62 @@ function formatOffset(o) {
3359
3504
 
3360
3505
  // src/effects/clipPathEffect.ts
3361
3506
  function applyClipPathEffect(node, fx, ctx) {
3507
+ var _a, _b;
3362
3508
  if (!fx || !fx.d && !fx.animate) return node;
3363
3509
  const clipId = genId(ctx, "clip");
3364
- const pathChild = { type: "path", d: fx.d };
3365
- 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
+ }
3366
3525
  ctx.defs.push({ type: "clipPath", id: clipId, children: [pathChild] });
3367
3526
  node.clipPath = "url(#" + clipId + ")";
3368
3527
  return node;
3369
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
+ }
3370
3534
 
3371
3535
  // src/effects/maskedByEffect.ts
3372
- function applyMaskedByEffect(node, fx, transformation, ctx) {
3536
+ function applyMaskedByEffect(node, fx, transformBy, ctx) {
3373
3537
  if (!fx) return node;
3374
- if (!fx.href) {
3375
- 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");
3376
3541
  return node;
3377
3542
  }
3378
3543
  const maskId = genId(ctx, "mask");
3379
- let content = { type: "use", href: "#" + fx.href };
3380
- if (transformation) {
3381
- content = wrapInverseTransform(content, transformation, ctx);
3544
+ let content = { type: "use", href: "#" + sourceId };
3545
+ if (transformBy) {
3546
+ content = wrapInverseTransform(content, transformBy, ctx);
3382
3547
  } else if (hasAnimateTransform(node)) {
3383
3548
  content = wrapInverseAnimatedBodyTransform(content, node, ctx);
3384
3549
  } else {
3385
3550
  const bodyStatic = readTransformationFromBody(node);
3386
3551
  if (bodyStatic) content = wrapInverseTransform(content, bodyStatic, ctx);
3387
3552
  }
3388
- const includeTargetOwn = transformation === void 0 && !nodeHasBodyTransform(node);
3389
- content = wrapAncestorChainCompensation(content, node, fx.href, ctx, includeTargetOwn);
3553
+ const includeTargetOwn = transformBy === void 0 && !nodeHasBodyTransform(node);
3554
+ content = wrapAncestorChainCompensation(content, node, sourceId, ctx, includeTargetOwn);
3390
3555
  const mask = { type: "mask", id: maskId, children: [content] };
3391
3556
  if (fx.maskType) mask.maskType = fx.maskType;
3392
3557
  if (fx.maskUnits) mask.maskUnits = fx.maskUnits;
3393
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);
3394
3563
  ctx.defs.push(mask);
3395
3564
  node.mask = "url(#" + maskId + ")";
3396
3565
  return node;
@@ -3497,6 +3666,19 @@ function readTransformationFromBody(node) {
3497
3666
  if (parts.origin) out.origin = parts.origin;
3498
3667
  return Object.keys(out).length ? out : void 0;
3499
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
+ }
3500
3682
  return void 0;
3501
3683
  }
3502
3684
  function parseTransformStringToParts(s) {
@@ -3630,10 +3812,10 @@ function collectMaskAncestorChains(root, ctx) {
3630
3812
  const interestingNodes = /* @__PURE__ */ new Set();
3631
3813
  const collectInterestingNodes = (n) => {
3632
3814
  var _a, _b;
3633
- const href = (_b = (_a = n.effects) == null ? void 0 : _a.maskedBy) == null ? void 0 : _b.href;
3634
- 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") {
3635
3817
  interestingNodes.add(n);
3636
- const sourceNode = ctx.idMap.get(href);
3818
+ const sourceNode = ctx.idMap.get(maskSourceId);
3637
3819
  if (sourceNode) interestingNodes.add(sourceNode);
3638
3820
  }
3639
3821
  if (Array.isArray(n.children)) for (const ch of n.children) collectInterestingNodes(ch);
@@ -3659,8 +3841,9 @@ function extractTranslateOnly(node, ctx) {
3659
3841
  if (typeof tr === "string") {
3660
3842
  const parts = parseTranslateOnlyFromString(tr, ctx);
3661
3843
  if (parts) out.translate = parts;
3662
- } else if (tr && typeof tr === "object") {
3663
- 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;
3664
3847
  if (value && typeof value === "object" && Array.isArray(value.translate)) {
3665
3848
  out.translate = [value.translate[0] || 0, value.translate[1] || 0];
3666
3849
  }
@@ -3710,17 +3893,17 @@ function parseTranslateOnlyFromString(s, ctx) {
3710
3893
  var CONTENT_SUBREF = "content";
3711
3894
  function applyRefHref(node, clone2, ctx) {
3712
3895
  if (!clone2) return;
3713
- const baseId = clone2.baseId;
3714
- if (!baseId) {
3715
- 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");
3716
3899
  return;
3717
3900
  }
3718
- 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;
3719
3902
  node.href = "#" + targetId;
3720
3903
  }
3721
- function applyRefAndTransformationEffect(node, clone2, transformation, ctx) {
3904
+ function applyRefAndTransformationEffect(node, clone2, transformBy, ctx) {
3722
3905
  applyRefHref(node, clone2, ctx);
3723
- return applyTransformationEffect(node, transformation, ctx);
3906
+ return applyTransformByEffect(node, transformBy, ctx);
3724
3907
  }
3725
3908
 
3726
3909
  // src/effects/repeaterEffect.ts
@@ -3745,7 +3928,7 @@ function applyRepeaterEffect(node, fx, ctx) {
3745
3928
  for (let i = 1; i < copies; i++) {
3746
3929
  const baseClone = clone(base);
3747
3930
  const synthFx = synthesisePerCopyFx(fx, i);
3748
- const wrapped = synthFx ? applyTransformationEffect(baseClone, synthFx, ctx) : baseClone;
3931
+ const wrapped = synthFx ? applyTransformByEffect(baseClone, synthFx, ctx) : baseClone;
3749
3932
  children.push(wrapped);
3750
3933
  }
3751
3934
  const wrapper = { type: "g", children };
@@ -3757,10 +3940,13 @@ function applyRepeaterEffect(node, fx, ctx) {
3757
3940
  function synthesisePerCopyFx(fx, i) {
3758
3941
  const out = {};
3759
3942
  if (fx.translate !== void 0) {
3760
- 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]);
3761
3944
  }
3762
3945
  if (fx.rotate !== void 0) {
3763
- 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);
3764
3950
  }
3765
3951
  if (fx.scale !== void 0) {
3766
3952
  out.scale = synthesiseScale(fx.scale, i);
@@ -3770,65 +3956,52 @@ function synthesisePerCopyFx(fx, i) {
3770
3956
  }
3771
3957
  return Object.keys(out).length ? out : void 0;
3772
3958
  }
3773
- function mapAnimatableVec2(raw, fn) {
3774
- if (Array.isArray(raw)) return fn(raw);
3775
- if (raw && typeof raw === "object") {
3776
- const obj = raw;
3777
- if (Array.isArray(obj.keyframes)) {
3778
- return __spreadProps(__spreadValues({}, obj), {
3779
- keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: fn(kf.value) }) : kf)
3780
- });
3781
- }
3782
- if (obj.value !== void 0) {
3783
- return __spreadProps(__spreadValues({}, obj), { value: fn(obj.value) });
3784
- }
3785
- }
3786
- return raw;
3787
- }
3788
- function mapAnimatableNumber(raw, fn) {
3789
- if (typeof raw === "number") return fn(raw);
3790
- if (raw && typeof raw === "object") {
3791
- const obj = raw;
3792
- if (Array.isArray(obj.keyframes)) {
3793
- return __spreadProps(__spreadValues({}, obj), {
3794
- keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: fn(kf.value) }) : kf)
3795
- });
3796
- }
3797
- if (obj.value !== void 0) {
3798
- return __spreadProps(__spreadValues({}, obj), { value: fn(obj.value) });
3799
- }
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 };
3800
3966
  }
3801
- 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;
3802
3974
  }
3803
3975
  function synthesiseScale(raw, i) {
3804
- const scalePowerFromPercent = (v) => [Math.pow(v[0] / 100, i), Math.pow(v[1] / 100, i)];
3805
- const scalePowerFromUnits = (v) => [Math.pow(v[0], i), Math.pow(v[1], i)];
3806
- if (Array.isArray(raw)) {
3807
- return { value: scalePowerFromPercent(raw) };
3808
- }
3809
- if (raw && typeof raw === "object") {
3810
- const obj = raw;
3811
- if (Array.isArray(obj.keyframes)) {
3812
- return __spreadProps(__spreadValues({}, obj), {
3813
- keyframes: obj.keyframes.map((kf) => kf && kf.value !== void 0 ? __spreadProps(__spreadValues({}, kf), { value: scalePowerFromUnits(kf.value) }) : kf)
3814
- });
3815
- }
3816
- if (obj.value !== void 0) {
3817
- return __spreadProps(__spreadValues({}, obj), { value: scalePowerFromPercent(obj.value) });
3818
- }
3819
- }
3820
- return raw;
3976
+ const scalePower = (v) => [Math.pow(v[0], i), Math.pow(v[1], i)];
3977
+ return mapAnimatable(raw, scalePower, true);
3821
3978
  }
3822
3979
 
3823
3980
  // src/effects/retimeEffect.ts
3824
3981
  var RETIME_MATERIALISATION_MODE_INLINE_G = false;
3825
3982
  function asRetime(r) {
3826
3983
  var _a, _b;
3827
- if (r.timeCrop) {
3828
- console.warn("retime: `timeCrop` is not supported yet and will be ignored");
3829
- }
3830
3984
  return { start: (_a = r.start) != null ? _a : 0, stretch: (_b = r.stretch) != null ? _b : 1 };
3831
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
+ }
3832
4005
  function concatRetime(child, parent) {
3833
4006
  return {
3834
4007
  start: parent.start + parent.stretch * child.start,
@@ -3888,8 +4061,10 @@ function applyAllRetimeEffects(root, ctx) {
3888
4061
  for (const useNode of sites) {
3889
4062
  const retime = readCloneRetime(useNode);
3890
4063
  if (!retime) continue;
4064
+ const crop = retime.timeCrop;
3891
4065
  clearCloneRetime(useNode);
3892
4066
  materialiseRetime(useNode, asRetime(retime), ctx);
4067
+ if (crop) applyTimeCrop(useNode, crop, ctx);
3893
4068
  }
3894
4069
  }
3895
4070
  function materialiseRetime(useNode, retime, ctx) {
@@ -4136,14 +4311,11 @@ function createPathSampler(d) {
4136
4311
  // src/effects/textPathEffect.ts
4137
4312
  var EXTEND_MARGIN_FRAC = 0.15;
4138
4313
  function numRange(v) {
4139
- if (typeof v === "number") return { min: v, max: v };
4140
- if (v && typeof v === "object") {
4141
- const o = v;
4142
- if (typeof o.value === "number") return { min: o.value, max: o.value };
4143
- if (Array.isArray(o.keyframes) && o.keyframes.length) {
4144
- const vals = o.keyframes.map((k) => Number(k.value) || 0);
4145
- return { min: Math.min(...vals), max: Math.max(...vals) };
4146
- }
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) };
4147
4319
  }
4148
4320
  return { min: 0, max: 0 };
4149
4321
  }
@@ -4163,11 +4335,16 @@ function estimateTextAdvance(node) {
4163
4335
  var r3 = (n) => Math.round(n * 1e3) / 1e3;
4164
4336
  function shiftAnimatable(v, by) {
4165
4337
  if (!by || v === void 0 || v === null) return v;
4166
- if (typeof v === "number") return v + by;
4167
- const o = v;
4168
- if (typeof o.value === "number") return __spreadProps(__spreadValues({}, o), { value: o.value + by });
4169
- if (Array.isArray(o.keyframes)) return __spreadProps(__spreadValues({}, o), { keyframes: o.keyframes.map((k) => __spreadProps(__spreadValues({}, k), { value: (Number(k.value) || 0) + by })) });
4170
- 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;
4171
4348
  }
4172
4349
  function extendedPathForBrowser(pathD, opts) {
4173
4350
  var _a;
@@ -4223,26 +4400,7 @@ function applyTextPathEffect(node, fx, ctx) {
4223
4400
  }
4224
4401
  function applyAnimatableNumber(node, attrName, raw) {
4225
4402
  if (raw === void 0 || raw === null) return;
4226
- if (typeof raw === "number") {
4227
- node[attrName] = String(raw);
4228
- return;
4229
- }
4230
- if (typeof raw === "object" && raw !== null) {
4231
- const obj = raw;
4232
- if (Array.isArray(obj.keyframes)) {
4233
- const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
4234
- const animate = __spreadValues({}, prevAnimate || {});
4235
- const block = { keyframes: obj.keyframes };
4236
- if (obj.loop !== void 0) block.loop = obj.loop;
4237
- animate[attrName] = block;
4238
- node.animate = animate;
4239
- return;
4240
- }
4241
- if (typeof obj.value === "number") {
4242
- node[attrName] = String(obj.value);
4243
- return;
4244
- }
4245
- }
4403
+ writeAnimatableChannel(node, attrName, readAnimatable(raw), { asString: true });
4246
4404
  }
4247
4405
 
4248
4406
  // src/effects/elementFactory.ts
@@ -4436,13 +4594,13 @@ function materialiseGlyphTextHorizontal(node, opts) {
4436
4594
  if (y !== void 0) pen.y = y;
4437
4595
  pen.x += (_a2 = parseLen(el.dx)) != null ? _a2 : 0;
4438
4596
  pen.y += (_b2 = parseLen(el.dy)) != null ? _b2 : 0;
4439
- 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]);
4440
4598
  if (content && !((_d2 = el.children) == null ? void 0 : _d2.length)) renderChars(content, s);
4441
4599
  if (el.children) for (const ch of el.children) walk(ch, s);
4442
4600
  };
4443
4601
  const rootStyle = rootStyleOf(node);
4444
4602
  if (node.children) for (const ch of node.children) walk(ch, rootStyle);
4445
- 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]);
4446
4604
  if (rootContent && !((_d = node.children) == null ? void 0 : _d.length)) renderChars(rootContent, rootStyle);
4447
4605
  const anchor = str(node.textAnchor);
4448
4606
  if (anchor === "middle" || anchor === "end") {
@@ -4491,7 +4649,7 @@ function layoutGlyphTextChars(node, opts) {
4491
4649
  if (y !== void 0) pen.y = y;
4492
4650
  pen.x += (_a2 = parseLen(el.dx)) != null ? _a2 : 0;
4493
4651
  pen.y += (_b2 = parseLen(el.dy)) != null ? _b2 : 0;
4494
- 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]);
4495
4653
  if (content && !((_d2 = el.children) == null ? void 0 : _d2.length)) renderChars(content, s);
4496
4654
  if (el.children) for (const ch of el.children) walk(ch, s);
4497
4655
  };
@@ -4506,7 +4664,7 @@ function layoutGlyphTextChars(node, opts) {
4506
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 });
4507
4665
  }
4508
4666
  }
4509
- 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]);
4510
4668
  if (rootContent && !((_f = node.children) == null ? void 0 : _f.length)) renderChars(rootContent, rootStyle);
4511
4669
  const anchor = str(node.textAnchor);
4512
4670
  if (anchor === "middle" || anchor === "end") {
@@ -4534,7 +4692,7 @@ function layoutGlyphTextCharsAlongPath(node, pathD, opts) {
4534
4692
  const walk = (el, parentStyle) => {
4535
4693
  var _a2, _b2, _c;
4536
4694
  const s = resolveStyle2(el, parentStyle);
4537
- 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]);
4538
4696
  if (content && !((_b2 = el.children) == null ? void 0 : _b2.length)) {
4539
4697
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
4540
4698
  const upm = (gf == null ? void 0 : gf.unitsPerEm) || 1e3;
@@ -4588,7 +4746,7 @@ function collectAlongPathCells(node, glyphs, soleFont, warnings) {
4588
4746
  const walk = (el, parentStyle) => {
4589
4747
  var _a, _b, _c;
4590
4748
  const s = resolveStyle2(el, parentStyle);
4591
- 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]);
4592
4750
  if (content && !((_b = el.children) == null ? void 0 : _b.length)) {
4593
4751
  const gf = glyphFontFor(s, glyphs, soleFont, warnings);
4594
4752
  if (gf) {
@@ -4812,9 +4970,9 @@ function applyTextGlyphsAlongPath(node, ctx, pathD, startOffset, textLength, pat
4812
4970
  }
4813
4971
 
4814
4972
  // src/effects/trimPathEffect.ts
4815
- function applyTrimPathEffect(node, trimPath, isCombinedShape, ctx) {
4973
+ function applyTrimPathEffect(node, trimPath, ctx) {
4816
4974
  if (!trimPath) return node;
4817
- const trimAllAsOne = !!trimPath.trimAllAsOne;
4975
+ const combined = trimPath.subPaths === PxTrimSubPaths.combined;
4818
4976
  const leafEntries = [];
4819
4977
  const measure = (n) => {
4820
4978
  if (Array.isArray(n.children) && n.children.length > 0) {
@@ -4835,16 +4993,16 @@ function applyTrimPathEffect(node, trimPath, isCombinedShape, ctx) {
4835
4993
  measure(node);
4836
4994
  if (!leafEntries.length) return node;
4837
4995
  let acc = 0;
4838
- const iterOrder = trimAllAsOne ? [...leafEntries].reverse() : leafEntries;
4996
+ const iterOrder = combined ? [...leafEntries].reverse() : leafEntries;
4839
4997
  for (const entry of iterOrder) {
4840
4998
  for (const sp of entry.subpaths) {
4841
- if (!trimAllAsOne) acc = 0;
4999
+ if (!combined) acc = 0;
4842
5000
  sp.startOffsetPx = acc;
4843
5001
  acc += sp.lengthPx;
4844
5002
  }
4845
5003
  }
4846
5004
  const chainLengthPx = acc;
4847
- if (trimAllAsOne && chainLengthPx < 1e-3) return node;
5005
+ if (combined && chainLengthPx < 1e-3) return node;
4848
5006
  const offsetReadRaw = readAnimatable(trimPath.offset);
4849
5007
  const offsetRead = offsetReadRaw.kind === "absent" /* Absent */ ? { kind: "static" /* Static */, value: 0 } : offsetReadRaw;
4850
5008
  const rangeReadRaw = readRangeWithCrossings(trimPath.range);
@@ -4856,18 +5014,18 @@ function applyTrimPathEffect(node, trimPath, isCombinedShape, ctx) {
4856
5014
  if (leafEntries.length === 1 && leafEntries[0].leaf === node && leafEntries[0].subpaths.length === 1) {
4857
5015
  const entry = leafEntries[0];
4858
5016
  const sp = entry.subpaths[0];
4859
- const pathLengthPx = trimAllAsOne ? chainLengthPx : sp.lengthPx;
5017
+ const pathLengthPx = combined ? chainLengthPx : sp.lengthPx;
4860
5018
  if (pathLengthPx < 1e-3) return node;
4861
- const startOffsetPct = trimAllAsOne ? sp.startOffsetPx / pathLengthPx : 0;
5019
+ const startOffsetPct = combined ? sp.startOffsetPx / pathLengthPx : 0;
4862
5020
  return collapseLeafWithTrim(entry.leaf, pathLengthPx, startOffsetPct, minMaxOffset, offsetRead, rangeRead);
4863
5021
  }
4864
5022
  const replacements = /* @__PURE__ */ new Map();
4865
5023
  for (const entry of leafEntries) {
4866
5024
  const newChildren = [];
4867
5025
  for (const sp of entry.subpaths) {
4868
- const pathLengthPx = trimAllAsOne ? chainLengthPx : sp.lengthPx;
5026
+ const pathLengthPx = combined ? chainLengthPx : sp.lengthPx;
4869
5027
  if (pathLengthPx < 1e-3) continue;
4870
- const startOffsetPct = trimAllAsOne ? sp.startOffsetPx / pathLengthPx : 0;
5028
+ const startOffsetPct = combined ? sp.startOffsetPx / pathLengthPx : 0;
4871
5029
  newChildren.push(...buildSubpathNodes(entry.leaf, sp.subpath, pathLengthPx, startOffsetPct, minMaxOffset, offsetRead, rangeRead, ctx));
4872
5030
  }
4873
5031
  replacements.set(entry.leaf, wrapLeafAsGroup(entry.leaf, newChildren));
@@ -4974,22 +5132,8 @@ function computeAnimAttr(read, map) {
4974
5132
  };
4975
5133
  }
4976
5134
  function applyAttr(node, attrName, attr) {
4977
- var _a, _b;
4978
5135
  if (!attr) return;
4979
- if (attr.kind === "static" /* Static */) {
4980
- node[attrName] = attr.value;
4981
- return;
4982
- }
4983
- const prevAnimate = node.animate && typeof node.animate === "object" && !Array.isArray(node.animate) ? node.animate : void 0;
4984
- const animate = __spreadValues({}, prevAnimate || {});
4985
- const block = { keyframes: attr.keyframes };
4986
- if (attr.loop !== void 0) block.loop = attr.loop;
4987
- animate[attrName] = block;
4988
- node.animate = animate;
4989
- const firstKf = attr.keyframes[0];
4990
- if (firstKf && ((_a = firstKf.value) != null ? _a : firstKf.v) !== void 0) {
4991
- node[attrName] = (_b = firstKf.value) != null ? _b : firstKf.v;
4992
- }
5136
+ writeAnimatableChannel(node, attrName, attr);
4993
5137
  }
4994
5138
  var OPACITY_STEP_MS = 10;
4995
5139
  function computeOpacityFromRange(rangeRead) {
@@ -5173,9 +5317,9 @@ function applyPlayerEffects(root) {
5173
5317
  nextId: 0,
5174
5318
  contentRefInnerIds: /* @__PURE__ */ new Map(),
5175
5319
  maskAncestorChains: /* @__PURE__ */ new Map(),
5176
- // Resolved engine: `frames` ONLY when explicitly set; auto/webapi/unset →
5177
- // webapi (we're not 100% sure it's frames, and CSS/WAAPI need the inline form).
5178
- 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,
5179
5323
  glyphs: (_b = getDefs(root)) == null ? void 0 : _b.glyphs
5180
5324
  };
5181
5325
  const working = clone(root);
@@ -5193,8 +5337,7 @@ function applyPlayerEffects_exceptRetime(node, ctx) {
5193
5337
  const originalId = typeof node.id === "string" ? node.id : void 0;
5194
5338
  const innerIdForContentRef = originalId ? ctx.contentRefInnerIds.get(originalId) : void 0;
5195
5339
  if (!fx && !innerIdForContentRef) return node;
5196
- const { transformation, repeater, maskedBy, clipPath, trimPath, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
5197
- const isCombinedShape = fx == null ? void 0 : fx.isCombinedShape;
5340
+ const { transformBy, repeater, maskedBy, clipPath, trimPath, clone: cloneFx, fillGradient, strokeGradient, textPath, text } = fx != null ? fx : {};
5198
5341
  if (fx) delete node.effects;
5199
5342
  let n = node;
5200
5343
  let consumedByGlyphs = false;
@@ -5214,15 +5357,15 @@ function applyPlayerEffects_exceptRetime(node, ctx) {
5214
5357
  if (!consumedByGlyphs) n = applyTextPathEffect(n, textPath, ctx);
5215
5358
  n = applyFillGradientEffect(n, fillGradient, ctx);
5216
5359
  n = applyStrokeGradientEffect(n, strokeGradient, ctx);
5217
- n = applyTrimPathEffect(n, trimPath, isCombinedShape, ctx);
5360
+ n = applyTrimPathEffect(n, trimPath, ctx);
5218
5361
  n = applyRepeaterEffect(n, repeater, ctx);
5219
- n = applyMaskedByEffect(n, maskedBy, transformation, ctx);
5362
+ n = applyMaskedByEffect(n, maskedBy, transformBy, ctx);
5220
5363
  n = applyClipPathEffect(n, clipPath, ctx);
5221
5364
  if (innerIdForContentRef) {
5222
5365
  applyRefHref(n, cloneFx, ctx);
5223
- n = splitForContentRef(n, transformation, originalId, innerIdForContentRef, ctx);
5366
+ n = splitForContentRef(n, transformBy, originalId, innerIdForContentRef, ctx);
5224
5367
  } else {
5225
- n = applyRefAndTransformationEffect(n, cloneFx, transformation, ctx);
5368
+ n = applyRefAndTransformationEffect(n, cloneFx, transformBy, ctx);
5226
5369
  }
5227
5370
  if (cloneFx == null ? void 0 : cloneFx.retime) node.effects = { clone: { retime: cloneFx.retime } };
5228
5371
  if (originalId) ctx.idMap.set(originalId, n);
@@ -5239,7 +5382,7 @@ function materialiseAllInTree(doc, engine, opts) {
5239
5382
  let root = applyPlayerEffects(doc).root;
5240
5383
  const duration = (_b = (_a = getAnimatorConfig(root)) == null ? void 0 : _a.duration) != null ? _b : DEFAULT_DURATION_MS;
5241
5384
  root = materialiseInternalLoopsInTree(root, duration);
5242
- if (engine === PxAnimatorEngine.webapi) {
5385
+ if (engine === PxAnimatorEngine.waapi) {
5243
5386
  root = materialiseMotionPathsInTree(root, opts == null ? void 0 : opts.motionPath);
5244
5387
  root = materialiseAnimatedUseInstances(root);
5245
5388
  root = pruneUnreferencedDefs(root);
@@ -5628,6 +5771,7 @@ function evalTransformValue(v, t) {
5628
5771
  if (typeof v === "string") return parseTransformString(v);
5629
5772
  if (v.keyframes) return partsToMatrix(interpParts(v.keyframes, t));
5630
5773
  if (v.value) return partsToMatrix(v.value);
5774
+ if (typeof v === "object" && !Array.isArray(v)) return partsToMatrix(v);
5631
5775
  return IDENTITY;
5632
5776
  }
5633
5777
  function nodeMatrix(node, t) {
@@ -5785,12 +5929,13 @@ function subtractMultiset(a, b) {
5785
5929
  PxEffectsSchema,
5786
5930
  PxElementAnimationSchema,
5787
5931
  PxFillGradientEffectSchema,
5788
- PxGradientGeometryAnimationSchema,
5789
5932
  PxGradientSpreadMethod,
5790
5933
  PxGradientStopSchema,
5791
5934
  PxGradientType,
5792
5935
  PxGradientUnits,
5793
5936
  PxKeyframeSchema,
5937
+ PxKeyframeValueSchema,
5938
+ PxLoopExtend,
5794
5939
  PxLoopSchema,
5795
5940
  PxMaskedByEffectSchema,
5796
5941
  PxNodeBase,
@@ -5802,11 +5947,12 @@ function subtractMultiset(a, b) {
5802
5947
  PxSvgNodeExtra,
5803
5948
  PxTextEffectSchema,
5804
5949
  PxTextPathEffectSchema,
5950
+ PxTransformByEffectSchema,
5805
5951
  PxTransformPartsSchema,
5806
5952
  PxTransformValueSchema,
5807
- PxTransformationEffectSchema,
5808
5953
  PxTriggerSchema,
5809
5954
  PxTrimPathEffectSchema,
5955
+ PxTrimSubPaths,
5810
5956
  STYLE_ATTR_NAMES,
5811
5957
  TEXT_ATTR,
5812
5958
  TEXT_CONTENT_ATTR,