@svgsketch/core 0.2.0 → 0.4.0

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.d.mts CHANGED
@@ -27,9 +27,20 @@ declare enum EasingType {
27
27
  EASE_IN_SINE = "ease-in-sine",
28
28
  EASE_OUT_SINE = "ease-out-sine",
29
29
  EASE_IN_OUT_SINE = "ease-in-out-sine",
30
+ EASE_IN_QUINT = "ease-in-quint",
31
+ EASE_OUT_QUINT = "ease-out-quint",
32
+ EASE_IN_OUT_QUINT = "ease-in-out-quint",
33
+ EASE_IN_EXPO = "ease-in-expo",
34
+ EASE_OUT_EXPO = "ease-out-expo",
35
+ EASE_IN_OUT_EXPO = "ease-in-out-expo",
36
+ EASE_IN_CIRC = "ease-in-circ",
37
+ EASE_OUT_CIRC = "ease-out-circ",
38
+ EASE_IN_OUT_CIRC = "ease-in-out-circ",
30
39
  EASE_IN_BACK = "ease-in-back",
31
40
  EASE_OUT_BACK = "ease-out-back",
32
- EASE_IN_OUT_BACK = "ease-in-out-back"
41
+ EASE_IN_OUT_BACK = "ease-in-out-back",
42
+ CUSTOM_BEZIER = "custom-bezier",
43
+ LINEAR_FUNCTION = "linear-function"
33
44
  }
34
45
  /**
35
46
  * Map from EasingType → cubic-bezier control points [x1, y1, x2, y2].
@@ -82,6 +93,17 @@ interface AnimationKeyframe {
82
93
  time: number;
83
94
  value: number | string;
84
95
  easing: EasingType;
96
+ /** Custom cubic-bezier control points [x1, y1, x2, y2] when easing is CUSTOM_BEZIER. */
97
+ customBezier?: [number, number, number, number];
98
+ /** Control points for CSS linear() easing when easing is LINEAR_FUNCTION. */
99
+ linearPoints?: LinearEasingPoint[];
100
+ }
101
+ /** A control point for a CSS linear() easing function. */
102
+ interface LinearEasingPoint {
103
+ /** Output value (y-axis). Can exceed 0-1 for overshoot/elastic effects. */
104
+ value: number;
105
+ /** Position on the x-axis (0-1). */
106
+ position: number;
85
107
  }
86
108
  /**
87
109
  * An animation track binds a single property of a single shape
@@ -174,10 +196,32 @@ interface SerializedAnimationTimeline {
174
196
  };
175
197
  motionRotate?: 'auto' | 'auto-reverse' | number;
176
198
  additive?: 'sum' | 'replace';
199
+ /**
200
+ * 6-element CSS matrix of the track's ancestor transform context, captured
201
+ * at the time the animation was authored. Preserves visual fidelity when
202
+ * a shape is animated inside a transformed group and later copied, pasted,
203
+ * or imported into a different transform context.
204
+ */
205
+ ancestorMatrix?: number[];
206
+ /**
207
+ * Translation offset applied to transform-property keyframe values when
208
+ * the animated shape was pasted or imported into a new document, so that
209
+ * absolute transforms (rotation origins, translate values) remain correct
210
+ * relative to the shape's new location.
211
+ */
212
+ pasteOffset?: {
213
+ x: number;
214
+ y: number;
215
+ };
177
216
  keyframes: {
178
217
  time: number;
179
218
  value: number | string;
180
219
  easing: string;
220
+ customBezier?: [number, number, number, number];
221
+ linearPoints?: {
222
+ value: number;
223
+ position: number;
224
+ }[];
181
225
  }[];
182
226
  }[];
183
227
  }
@@ -191,8 +235,8 @@ interface AnimatablePropertyDescriptor {
191
235
  key: string;
192
236
  /** Human-readable label. */
193
237
  label: string;
194
- /** Value type: number, color (hex string), or path (SVG path data). */
195
- type: 'number' | 'color' | 'path';
238
+ /** Value type: number, color (hex string), path (SVG path data), or points (polyline/polygon). */
239
+ type: 'number' | 'color' | 'path' | 'points';
196
240
  /** The SVG attribute name this maps to (e.g. 'cx', 'fill'). */
197
241
  attr?: string;
198
242
  /** Minimum value for numeric properties. */
@@ -652,6 +696,33 @@ interface ShapeMetadata {
652
696
  linkTarget: LinkTarget;
653
697
  customData: Record<string, string>;
654
698
  }
699
+ /**
700
+ * Provider attribution metadata for an image shape sourced from a third-party
701
+ * stock photo service. Stored on the shape so the editor can render the
702
+ * required photographer + provider credit links wherever the image is shown,
703
+ * and so the data round-trips through document save/load. Required by stock
704
+ * photo provider API guidelines (e.g. Unsplash).
705
+ */
706
+ interface ImageAttribution {
707
+ /** Slug for the provider, e.g. 'unsplash', 'openverse'. */
708
+ source: string;
709
+ /** Display name for the provider, e.g. 'Unsplash', 'OpenVerse'. */
710
+ sourceName: string;
711
+ /** URL to the photo's HTML page on the provider site. */
712
+ sourceUrl: string;
713
+ /** Photographer's display name. */
714
+ photographer: string;
715
+ /** Photographer's profile URL on the provider site. */
716
+ photographerUrl: string;
717
+ /**
718
+ * Optional SPDX-style license display label, e.g. 'CC BY 4.0', 'CC0 1.0',
719
+ * 'Public Domain'. Set by aggregators (OpenVerse) that surface per-asset
720
+ * license info; absent for providers with a single global license model.
721
+ */
722
+ license?: string;
723
+ /** Optional URL to the full license text. */
724
+ licenseUrl?: string;
725
+ }
655
726
  type LicenseType = '' | 'cc0' | 'cc-by' | 'cc-by-sa' | 'cc-by-nc' | 'cc-by-nc-sa' | 'cc-by-nd' | 'cc-by-nc-nd' | 'mit' | 'apache-2.0' | 'custom';
656
727
  interface DocumentMetadata {
657
728
  title: string;
@@ -665,9 +736,36 @@ interface DocumentMetadata {
665
736
  }
666
737
  /** Type of a template variable's value. */
667
738
  type TemplateVariableType = 'string' | 'color' | 'number';
668
- /** A template variable definition stored in the document. */
739
+ /**
740
+ * Where a TemplateVariable came from. `user` is hand-authored in the
741
+ * Variables panel; `palette` is auto-generated by the palette-token
742
+ * integration and is read-mostly (the source of truth lives in user
743
+ * settings, not the document).
744
+ */
745
+ type TemplateVariableSource = {
746
+ kind: 'user';
747
+ } | {
748
+ kind: 'palette';
749
+ paletteId: string;
750
+ index: number;
751
+ };
752
+ /**
753
+ * A template variable definition stored in the document.
754
+ *
755
+ * Two complementary roles:
756
+ *
757
+ * 1. **Build-time substitution** — `{{name}}` placeholders in shape
758
+ * properties are replaced via `substituteVariables()` (CLI / SDK
759
+ * template builds).
760
+ *
761
+ * 2. **Runtime CSS custom property** — when the editor is open, each
762
+ * variable is mirrored as `--name: defaultValue` inside a single
763
+ * `:root, svg { ... }` style block in the canvas SVG. Shapes can
764
+ * bind a geometry property to a variable via `var(--name)` and
765
+ * get live updates whenever the value changes.
766
+ */
669
767
  interface TemplateVariable {
670
- /** Variable name (used in `{{name}}` placeholders). */
768
+ /** Variable name (used in `{{name}}` placeholders and as CSS custom property `--name`). */
671
769
  name: string;
672
770
  /** The type of value this variable holds. */
673
771
  type: TemplateVariableType;
@@ -677,6 +775,13 @@ interface TemplateVariable {
677
775
  label?: string;
678
776
  /** Description / help text. */
679
777
  description?: string;
778
+ /**
779
+ * Provenance tag. Defaults to `{ kind: 'user' }` when omitted.
780
+ * Palette-sourced variables are surfaced in the panel as a read-only
781
+ * group — editing them in place would break the round-trip with the
782
+ * settings palette.
783
+ */
784
+ source?: TemplateVariableSource;
680
785
  }
681
786
  interface Guide {
682
787
  id: string;
@@ -696,6 +801,248 @@ interface Measurement {
696
801
  opacity?: number;
697
802
  }
698
803
 
804
+ /**
805
+ * Typed property interfaces for every node type in the scene graph.
806
+ *
807
+ * This is the **single source of truth** for shape property types across
808
+ * the entire SVGSketch stack (editor, API worker, server renderer, etc.).
809
+ *
810
+ * Each shape type declares the exact set of properties it owns.
811
+ * These interfaces drive:
812
+ * - Compile-time type safety on `SceneNode.get()` / `.set()` calls
813
+ * - The `NodeTypePropsMap` lookup used by `SceneNode.create()`
814
+ * - Schema defaults and validators in the editor's `node-schema.ts`
815
+ * - Serialization format for the `.svgs` document format
816
+ */
817
+
818
+ interface CommonNodeProps {
819
+ fillColor: string;
820
+ borderColor: string;
821
+ borderWidth: number;
822
+ opacity: number;
823
+ fillOpacity: number;
824
+ strokeOpacity: number;
825
+ rotation: number;
826
+ skewX: number;
827
+ skewY: number;
828
+ customPivot: Point | null;
829
+ locked: boolean;
830
+ visible: boolean;
831
+ fillType: string;
832
+ fillGradient: unknown | null;
833
+ strokeType: string;
834
+ strokeGradient: unknown | null;
835
+ fillRule: 'nonzero' | 'evenodd';
836
+ strokeLinejoin: 'miter' | 'round' | 'bevel';
837
+ strokeLinecap: 'butt' | 'round' | 'square';
838
+ strokeMiterlimit: number;
839
+ lineStyle: string;
840
+ dashLength: number;
841
+ gapLength: number;
842
+ dashOffset: number;
843
+ strokeDasharray: string | null;
844
+ filters: unknown[];
845
+ metadata: unknown | null;
846
+ cssClipPath: string | null;
847
+ cssMaskProperties: Record<string, string> | null;
848
+ groupId: string | null;
849
+ }
850
+ type CornerShapeValue = 'round' | 'notch' | 'bevel' | 'scoop';
851
+ interface RectangleNodeProps extends CommonNodeProps {
852
+ x: number;
853
+ y: number;
854
+ width: number;
855
+ height: number;
856
+ cornerRadius: number;
857
+ cornerShape: CornerShapeValue;
858
+ cornerMode: 'uniform' | 'non-uniform';
859
+ cornerRadiusTL: number;
860
+ cornerRadiusTR: number;
861
+ cornerRadiusBL: number;
862
+ cornerRadiusBR: number;
863
+ cornerShapeTL: CornerShapeValue;
864
+ cornerShapeTR: CornerShapeValue;
865
+ cornerShapeBL: CornerShapeValue;
866
+ cornerShapeBR: CornerShapeValue;
867
+ }
868
+ type SquareNodeProps = RectangleNodeProps;
869
+ interface CircleNodeProps extends CommonNodeProps {
870
+ x: number;
871
+ y: number;
872
+ radius: number;
873
+ }
874
+ interface EllipseNodeProps extends CommonNodeProps {
875
+ x: number;
876
+ y: number;
877
+ rx: number;
878
+ ry: number;
879
+ }
880
+ type LineEndpointValue = 'none' | 'arrow' | 'open-arrow' | 'circle' | 'diamond' | 'square';
881
+ interface LineNodeProps extends CommonNodeProps {
882
+ x1: number;
883
+ y1: number;
884
+ x2: number;
885
+ y2: number;
886
+ startEndpoint: LineEndpointValue;
887
+ endEndpoint: LineEndpointValue;
888
+ }
889
+ interface TextNodeProps extends CommonNodeProps {
890
+ x: number;
891
+ y: number;
892
+ width: number;
893
+ height: number;
894
+ textX: number;
895
+ textY: number;
896
+ fontSize: number;
897
+ text: string;
898
+ fontFamily: string;
899
+ fontWeight: string;
900
+ fontStyle: string;
901
+ textDecoration: Record<string, boolean>;
902
+ textTransform: string;
903
+ baselineShift: string;
904
+ dominantBaseline: string;
905
+ writingMode: string;
906
+ textAnchor: string;
907
+ letterSpacing: number;
908
+ wordSpacing: number;
909
+ lineHeight: number;
910
+ inlineSize: number;
911
+ overflowWrap: string;
912
+ whiteSpace: string;
913
+ textDirection: string;
914
+ unicodeBidi: string;
915
+ scaleX: number;
916
+ scaleY: number;
917
+ scaleAnchor: Point | null;
918
+ useRichText: boolean;
919
+ richTextData: unknown | null;
920
+ charOffsets: {
921
+ x: number;
922
+ y: number;
923
+ rotate: number;
924
+ }[] | null;
925
+ fontVariationSettings: Record<string, number>;
926
+ isTextPath: boolean;
927
+ textPathPoints: unknown[] | null;
928
+ textPathStartOffset: number;
929
+ textPathSide: 'left' | 'right';
930
+ shapeInsideRef: string | null;
931
+ shapePadding: number;
932
+ }
933
+ interface ImageNodeProps extends CommonNodeProps {
934
+ x: number;
935
+ y: number;
936
+ width: number;
937
+ height: number;
938
+ href: string;
939
+ originalWidth: number;
940
+ originalHeight: number;
941
+ preserveAspectRatio: boolean;
942
+ imageOpacity: number;
943
+ }
944
+ interface SplineNodeProps extends CommonNodeProps {
945
+ x: number;
946
+ y: number;
947
+ width: number;
948
+ height: number;
949
+ splinePoints: unknown[];
950
+ splineArcParams: unknown[];
951
+ splineControlBounds: {
952
+ x: number;
953
+ y: number;
954
+ width: number;
955
+ height: number;
956
+ } | null;
957
+ startEndpoint: LineEndpointValue;
958
+ endEndpoint: LineEndpointValue;
959
+ }
960
+ interface PolylineNodeProps extends CommonNodeProps {
961
+ x: number;
962
+ y: number;
963
+ width: number;
964
+ height: number;
965
+ polylinePoints: Point[];
966
+ polylineClosed: boolean;
967
+ }
968
+ interface PolygonBaseNodeProps extends CommonNodeProps {
969
+ cx: number;
970
+ cy: number;
971
+ radius: number;
972
+ cornerRadius: number;
973
+ shiftAngle: number;
974
+ }
975
+ interface TriangleNodeProps extends PolygonBaseNodeProps {
976
+ sides: 3;
977
+ }
978
+ interface NGonNodeProps extends PolygonBaseNodeProps {
979
+ sides: number;
980
+ }
981
+ interface StarNodeProps extends PolygonBaseNodeProps {
982
+ arms: number;
983
+ innerRadiusPercent: number;
984
+ }
985
+ interface CrossNodeProps extends PolygonBaseNodeProps {
986
+ armWidthPercent: number;
987
+ }
988
+ interface RingNodeProps extends PolygonBaseNodeProps {
989
+ innerRadiusPercent: number;
990
+ }
991
+ interface SpiralNodeProps extends PolygonBaseNodeProps {
992
+ turns: number;
993
+ thicknessPercent: number;
994
+ spiralDirection: number;
995
+ }
996
+ interface GearNodeProps extends PolygonBaseNodeProps {
997
+ teeth: number;
998
+ toothDepthPercent: number;
999
+ holeRadiusPercent: number;
1000
+ }
1001
+ interface ArrowNodeProps extends PolygonBaseNodeProps {
1002
+ headWidthPercent: number;
1003
+ headLengthPercent: number;
1004
+ shaftWidthPercent: number;
1005
+ }
1006
+ interface SymbolInstanceNodeProps extends CommonNodeProps {
1007
+ x: number;
1008
+ y: number;
1009
+ width: number;
1010
+ height: number;
1011
+ symbolId: string;
1012
+ }
1013
+ interface GroupNodeProps {
1014
+ groupId: string | null;
1015
+ }
1016
+ type DocumentNodeProps = Record<string, never>;
1017
+ /**
1018
+ * Maps each node type string to its typed property interface.
1019
+ * Used by `SceneNode.create()` for compile-time type safety.
1020
+ */
1021
+ interface NodeTypePropsMap {
1022
+ rectangle: RectangleNodeProps;
1023
+ square: SquareNodeProps;
1024
+ circle: CircleNodeProps;
1025
+ ellipse: EllipseNodeProps;
1026
+ line: LineNodeProps;
1027
+ text: TextNodeProps;
1028
+ image: ImageNodeProps;
1029
+ spline: SplineNodeProps;
1030
+ polyline: PolylineNodeProps;
1031
+ triangle: TriangleNodeProps;
1032
+ ngon: NGonNodeProps;
1033
+ star: StarNodeProps;
1034
+ cross: CrossNodeProps;
1035
+ ring: RingNodeProps;
1036
+ spiral: SpiralNodeProps;
1037
+ gear: GearNodeProps;
1038
+ arrow: ArrowNodeProps;
1039
+ 'symbol-instance': SymbolInstanceNodeProps;
1040
+ group: GroupNodeProps;
1041
+ document: DocumentNodeProps;
1042
+ }
1043
+ /** Union of all node prop types. */
1044
+ type AnyNodeProps = RectangleNodeProps | SquareNodeProps | CircleNodeProps | EllipseNodeProps | LineNodeProps | TextNodeProps | ImageNodeProps | SplineNodeProps | PolylineNodeProps | TriangleNodeProps | NGonNodeProps | StarNodeProps | CrossNodeProps | RingNodeProps | SpiralNodeProps | GearNodeProps | ArrowNodeProps | SymbolInstanceNodeProps | GroupNodeProps | DocumentNodeProps;
1045
+
699
1046
  /**
700
1047
  * @svgsketch/core — Serialized document types.
701
1048
  *
@@ -746,6 +1093,17 @@ interface SerializedShape {
746
1093
  y: number;
747
1094
  rotate: number;
748
1095
  }[];
1096
+ linePositions?: {
1097
+ x: number;
1098
+ dy: number;
1099
+ }[];
1100
+ /**
1101
+ * Whether this text element uses the rich-text model (styled runs with
1102
+ * per-character formatting) versus the plain-text model. When true,
1103
+ * `richTextData` is the authoritative source for text content and
1104
+ * formatting; when false/absent, `text` is authoritative.
1105
+ */
1106
+ useRichText?: boolean;
749
1107
  richTextData?: RichTextData;
750
1108
  scaleX?: number;
751
1109
  scaleY?: number;
@@ -773,7 +1131,8 @@ interface SerializedShape {
773
1131
  holeRadiusPercent?: number;
774
1132
  turns?: number;
775
1133
  thicknessPercent?: number;
776
- direction?: number;
1134
+ /** Spiral winding direction: 1 = counterclockwise, -1 = clockwise. */
1135
+ spiralDirection?: number;
777
1136
  headWidthPercent?: number;
778
1137
  headLengthPercent?: number;
779
1138
  shaftWidthPercent?: number;
@@ -819,11 +1178,42 @@ interface SerializedShape {
819
1178
  strokeLinecap?: 'butt' | 'round' | 'square';
820
1179
  strokeMiterlimit?: number;
821
1180
  opacity?: number;
1181
+ blendMode?: string;
1182
+ shapeRendering?: 'auto' | 'optimizeSpeed' | 'crispEdges' | 'geometricPrecision';
822
1183
  metadata?: Partial<ShapeMetadata>;
823
1184
  groupId?: string;
824
1185
  cssClipPath?: string;
825
1186
  cssMaskProperties?: Record<string, string>;
826
1187
  symbolId?: string;
1188
+ /**
1189
+ * Per-shape property overrides applied to inner shapes of a symbol instance.
1190
+ * Keyed by the inner shape's ID inside the symbol definition. Each value is
1191
+ * a partial state object whose fields override the corresponding fields on
1192
+ * the resolved variant shape at render time. Only meaningful on shapes of
1193
+ * type `symbol-instance`.
1194
+ */
1195
+ symbolOverrides?: Record<string, Partial<SerializedShape['state']>>;
1196
+ /**
1197
+ * Canonical variant axis selection for a symbol instance, encoded as
1198
+ * `axis1=val1,axis2=val2`. Empty / missing → use the symbol's default
1199
+ * variant (`def.shapes`). Only meaningful on `symbol-instance` shapes.
1200
+ */
1201
+ variantKey?: string;
1202
+ /**
1203
+ * Map of geometry-property name → CSS custom-property name (without
1204
+ * the leading `--`) the property is bound to.
1205
+ *
1206
+ * When a property is bound, the literal value in `state[property]`
1207
+ * is the **resolved** value (last seen value of the variable). The
1208
+ * binding is what survives editing — typing into a bound field in
1209
+ * the panel updates the variable, not the literal — and on document
1210
+ * load the literal is re-resolved from the current variable value.
1211
+ *
1212
+ * Example: `{ width: 'card-width', cornerRadius: 'unit' }` means
1213
+ * `state.width = var(--card-width)` and `state.cornerRadius = var(--unit)`
1214
+ * when serialized to standalone SVG.
1215
+ */
1216
+ bindings?: Record<string, string>;
827
1217
  };
828
1218
  }
829
1219
  type SerializedViewbox = Viewbox;
@@ -874,9 +1264,27 @@ interface HistorySnapshot {
874
1264
  /** Symbol definitions (reusable component templates). */
875
1265
  symbols?: SerializedSymbolDef[];
876
1266
  }
1267
+ /**
1268
+ * A variant axis on a component symbol. Each axis has a name (e.g.
1269
+ * `state`, `size`, `theme`) and an ordered list of allowed values
1270
+ * (e.g. `[default, hover, pressed]`). The default value is always
1271
+ * the first entry in `values`.
1272
+ */
1273
+ interface SerializedVariantAxis {
1274
+ name: string;
1275
+ values: string[];
1276
+ }
877
1277
  /**
878
1278
  * A reusable symbol definition. Contains the shapes that make up the
879
1279
  * symbol template, plus metadata for the symbols panel.
1280
+ *
1281
+ * **Variants:** when `variantAxes` is present, the symbol carries
1282
+ * multiple alternative shape arrays keyed by variant combination
1283
+ * (e.g. `state=hover,size=md`). The base `shapes` field is always
1284
+ * the **default variant** — the combination where every axis is at
1285
+ * its first value. Other variants live in `variants[variantKey]`.
1286
+ * Variant resolution falls back to `shapes` whenever `variantKey`
1287
+ * is missing or empty.
880
1288
  */
881
1289
  interface SerializedSymbolDef {
882
1290
  /** Unique identifier for this symbol definition. */
@@ -885,12 +1293,34 @@ interface SerializedSymbolDef {
885
1293
  name: string;
886
1294
  /** SVG viewBox string ("minX minY width height"). */
887
1295
  viewBox: string;
888
- /** The shapes that make up this symbol's content. */
1296
+ /**
1297
+ * Default variant shapes. When `variantAxes` is present, this represents
1298
+ * the variant where every axis is at its first value.
1299
+ */
889
1300
  shapes: SerializedShape[];
890
1301
  /** Groups within the symbol. */
891
1302
  groups?: SerializedGroup[];
892
1303
  /** Base64 data-URI thumbnail for the symbols panel. */
893
1304
  thumbnail?: string;
1305
+ /**
1306
+ * Ordered list of variant axes available on this symbol. The order is
1307
+ * stable so that variant keys can be canonicalized as
1308
+ * `axis1=val1,axis2=val2`.
1309
+ */
1310
+ variantAxes?: SerializedVariantAxis[];
1311
+ /**
1312
+ * Non-default variant shape arrays keyed by canonical axis combination
1313
+ * string (e.g. `state=hover,size=md`). Missing keys fall back to the
1314
+ * base `shapes` array.
1315
+ */
1316
+ variants?: Record<string, SerializedShape[]>;
1317
+ /**
1318
+ * Per-variant thumbnails as base64 data URIs, keyed by canonical
1319
+ * variant combination string. The empty string `""` keys the default
1320
+ * variant (which also lives in `thumbnail` for backwards compatibility).
1321
+ * Used by the variant matrix view to show real previews of each cell.
1322
+ */
1323
+ variantThumbnails?: Record<string, string>;
894
1324
  }
895
1325
  interface SerializedGroup {
896
1326
  id: string;
@@ -910,6 +1340,15 @@ interface SerializedGroup {
910
1340
  pluginData?: string;
911
1341
  /** Additional custom data-* attributes set by plugins */
912
1342
  attributes?: Record<string, string>;
1343
+ fill?: string;
1344
+ fillOpacity?: string;
1345
+ strokeOpacity?: string;
1346
+ opacity?: string;
1347
+ filter?: string;
1348
+ cssFilter?: string;
1349
+ mixBlendMode?: string;
1350
+ clipPath?: string;
1351
+ mask?: string;
913
1352
  }
914
1353
  /** Serialized clip or mask group */
915
1354
  interface SerializedClipMaskGroup {
@@ -979,25 +1418,62 @@ interface SerializedClipMaskGroup {
979
1418
  * @svgsketch/core — Schema migrations.
980
1419
  *
981
1420
  * Migrates a HistorySnapshot from any previous schema version to
982
- * CURRENT_SCHEMA_VERSION. Each migration step is a pure function
983
- * that transforms version N to N+1.
984
- *
985
- * To add a new migration:
986
- * 1. Bump CURRENT_SCHEMA_VERSION in types/serialized.ts
987
- * 2. Add a function migrateVNtoVN+1(snapshot) below
988
- * 3. Add a case in the switch inside migrateSnapshot()
1421
+ * CURRENT_SCHEMA_VERSION. Each migration step is a pure function that
1422
+ * transforms version N to N+1 and lives in the `MIGRATIONS` registry
1423
+ * below.
1424
+ *
1425
+ * **Rules for adding a migration (e.g. v1 → v2):**
1426
+ * 1. Bump `CURRENT_SCHEMA_VERSION` in `types/serialized.ts`.
1427
+ * 2. Write a pure function `migrateV1toV2(snapshot) => snapshot` that
1428
+ * returns the transformed document (it may mutate — the dispatcher
1429
+ * clones input first).
1430
+ * 3. Register it in `MIGRATIONS` below with the *source* version as the
1431
+ * key: `1: migrateV1toV2`.
1432
+ * 4. Add a dedicated test in `migrations.test.ts` that exercises the
1433
+ * before-and-after shape.
1434
+ *
1435
+ * **Behavior contract:**
1436
+ * - Unknown older version (gap with no registered migration) → throws.
1437
+ * Silently stamping would risk corrupting data.
1438
+ * - Unknown newer version (snapshot from the future) → returned unchanged.
1439
+ * Loaders that can't understand a newer document should refuse it at a
1440
+ * higher layer, not here.
1441
+ * - Missing/malformed `schemaVersion` → treated as version 1 (the oldest
1442
+ * known format).
989
1443
  */
990
1444
 
1445
+ /**
1446
+ * A migration function: transforms a snapshot from version N to N+1.
1447
+ *
1448
+ * The function receives a deep-cloned snapshot and may mutate it freely.
1449
+ * It must return the transformed snapshot (typically the same object).
1450
+ */
1451
+ type Migration = (snapshot: HistorySnapshot) => HistorySnapshot;
1452
+ /**
1453
+ * Registry of schema migrations, keyed by **source** version.
1454
+ *
1455
+ * Entry `N: fn` means "`fn` transforms a version-N snapshot into a
1456
+ * version-(N+1) snapshot." The dispatcher in {@link migrateSnapshot}
1457
+ * walks this registry from the snapshot's current version up to
1458
+ * {@link CURRENT_SCHEMA_VERSION}.
1459
+ *
1460
+ * This object is exported so tests (and diagnostic tooling) can read
1461
+ * the registered migrations, but it should not be mutated at runtime.
1462
+ */
1463
+ declare const MIGRATIONS: Readonly<Record<number, Migration>>;
991
1464
  /**
992
1465
  * Migrate a snapshot from any older schema version to the current version.
993
1466
  *
994
1467
  * The function is idempotent — if the snapshot is already at the current
995
- * version (or newer), it is returned unchanged.
1468
+ * version (or newer), it is returned as a deep clone with no migrations
1469
+ * applied.
996
1470
  *
997
- * @param snapshot - The snapshot to migrate (not mutated; a new object is returned)
998
- * @returns The migrated snapshot at CURRENT_SCHEMA_VERSION
1471
+ * @param snapshot - The snapshot to migrate. Not mutated a new object is returned.
1472
+ * @param registry - Override the migration registry (for tests). Defaults to {@link MIGRATIONS}.
1473
+ * @returns The migrated snapshot, stamped with {@link CURRENT_SCHEMA_VERSION}.
1474
+ * @throws If the snapshot is at an older version for which no migration is registered.
999
1475
  */
1000
- declare function migrateSnapshot(snapshot: HistorySnapshot): HistorySnapshot;
1476
+ declare function migrateSnapshot(snapshot: HistorySnapshot, registry?: Record<number, Migration>): HistorySnapshot;
1001
1477
 
1002
1478
  /**
1003
1479
  * @svgsketch/core — Document validation.
@@ -1116,11 +1592,15 @@ declare function createViewbox(x: number, y: number, width: number, height: numb
1116
1592
  *
1117
1593
  * - Sorts top-level sections in a fixed order (schemaVersion, documentMetadata,
1118
1594
  * viewboxes, guides, measurements, groups, clipMaskGroups, shapes)
1119
- * - Sorts shapes, viewboxes, groups, guides by `id`
1595
+ * - Preserves `shapes` array order because shape-array position IS the
1596
+ * SVG painter's-model z-order (no separate z-index field exists).
1597
+ * Sorting would silently reorder rendering and destroy user intent.
1598
+ * - Sorts other id-keyed collections (groups, viewboxes, guides, etc.)
1599
+ * by `id` for stable diffs — their order has no semantic meaning
1600
+ * (groups use siblingIndex + parentId to record position).
1120
1601
  * - Sorts object keys alphabetically within each shape/state
1121
1602
  * - Uses 2-space indentation, one property per line
1122
1603
  * - Strips `undefined` values (but keeps explicit `null`)
1123
- * - Produces consistent output regardless of insertion order
1124
1604
  *
1125
1605
  * The result can be saved as a `.svgs` file and tracked in Git with
1126
1606
  * meaningful line-by-line diffs.
@@ -2212,7 +2692,7 @@ declare class Spiral extends PolygonShapeBuilder<Spiral> {
2212
2692
  turns(n: number): Spiral;
2213
2693
  /** Set the stroke thickness as a percent (0–100). */
2214
2694
  thickness(percent: number): Spiral;
2215
- /** Set the spiral direction: 1 = clockwise, -1 = counter-clockwise. */
2695
+ /** Set the spiral winding direction: 1 = counterclockwise, -1 = clockwise. */
2216
2696
  direction(d: 1 | -1): Spiral;
2217
2697
  }
2218
2698
  declare class Gear extends PolygonShapeBuilder<Gear> {
@@ -2332,6 +2812,22 @@ declare class Track {
2332
2812
  * Defaults to LINEAR.
2333
2813
  */
2334
2814
  keyframe(time: number, value: number | string, easing?: EasingType): Track;
2815
+ /**
2816
+ * Add a keyframe with a custom cubic-bezier easing.
2817
+ *
2818
+ * @param time - Time in seconds.
2819
+ * @param value - The property value.
2820
+ * @param points - Cubic-bezier control points [x1, y1, x2, y2].
2821
+ */
2822
+ keyframeBezier(time: number, value: number | string, points: [number, number, number, number]): Track;
2823
+ /**
2824
+ * Add a keyframe with a CSS linear() easing function.
2825
+ *
2826
+ * @param time - Time in seconds.
2827
+ * @param value - The property value.
2828
+ * @param linearPoints - Control points for the piecewise linear function.
2829
+ */
2830
+ keyframeLinear(time: number, value: number | string, linearPoints: LinearEasingPoint[]): Track;
2335
2831
  /** Add multiple keyframes at once. */
2336
2832
  keyframes(...kfs: AnimationKeyframe[]): Track;
2337
2833
  /** Disable this track (excluded from playback/export). */
@@ -2759,4 +3255,4 @@ declare class Document {
2759
3255
  private _ensureMetadata;
2760
3256
  }
2761
3257
 
2762
- export { type AnimatablePropertyDescriptor, type AnimationKeyframe, type AnimationTimeline, type AnimationTrack, type AnimationTrigger, type AnimationTriggerType, type ArcParams$1 as ArcParams, type AriaRole, Arrow, type BaseFilter, type BlackAndWhiteFilter, type BlurQuality, type BrightnessFilter, CURRENT_SCHEMA_VERSION, type ChannelPainterFilter, Circle, type CodeFormat, type CodegenOptions, type ContouringDiscreteFilter, type ContouringTableFilter, type ContrastFilter, type CreateDocumentOptions, type CreateShapeOptions, Cross, type CrumpledPlasticFilter, type CustomPatternDef, type CycleBehavior, Document, type DocumentMetadata, type DocumentOptions, type DropShadowFilter, type DuotoneFilter, EASING_CURVES, EVENT_TRIGGER_OPTIONS, EasingType, Ellipse, type EmbossFilter, type ExtractedVariables, type FillType, type FilmGrainFilter, type FilterType, type GaussianBlurFilter, Gear, type GlowFilter, type GouacheFilter, type GradientDefinition, type GradientSpreadMethod, type GradientStop, type GrayscaleFilter, type Guide, type HistorySnapshot, type HueRotateFilter, ImageShape, type InkBlotFilter, type InnerGlowFilter, type InnerShadowFilter, type InvertFilter, type LicenseType, Line, type LinearGradient, LinearGradientBuilder, type LinkTarget, MARKER_HEIGHT, MARKER_VIEWBOX, MARKER_WIDTH, type MarkerDescriptor, type Measurement, type MorphologyFilter, type MorphologyOperator, type MotionBlurFilter, NGon, type NoiseFilter, type OpacityFilter, type OutlineFilter, type OutlinePosition, type ParseOptions, PatternBuilder, type PatternElement, type PatternFill, type PatternParams, type PatternSvgContentResult, type PatternType, type PixelateFilter, type Point, type PointLightFilter, Polyline, type RadialGradient, RadialGradientBuilder, Rectangle, type RenderOptions, type RichTextData, type RichTextLine, type RichTextSegment, type RichTextSegmentStyle, type RiddledFilter, Ring, type RoundEdgesFilter, type SaturateFilter, type SegmentCurveType, type SepiaFilter, type SerializedAnimationTimeline, type SerializedClipMaskGroup, type SerializedGroup, type SerializedShape, type SerializedSymbolDef, type SerializedViewbox, ShapeBuilder, type ShapeFilter, type ShapeMetadata, type SharpenFilter, Spiral, Spline, SplineCurveType, type SplinePoint, SplinePointType, type SpotLightFilter, Square, Star, type StringifyOptions, type StrokeType, type TemplateVariable, type TemplateVariableType, Text, Timeline, Track, Triangle, type ValidationError, type ValidationResult, type VariableMap, type Viewbox, type WarpFilter, type WarpType, type WatercolorFilter, type XrayFilter, computeArrowVertices, computeCrossVertices, computeDashArray, computeGearPath, computeGearVertices, computePolygonVertices, computeRectanglePath, computeRingPath, computeSpiralPath, computeSplinePath, computeStarVertices, createDocument, createShape, createViewbox, escXml, extractVariables, filterAttr, generateCode, generateCssCode, generateD3Code, generateId, generatePatternSvgContent, generateReactCode, generateSvgCode, generateVueCode, getEasingCubicBezier, getMarkerDescriptors, getPatternElements, linearGradient, migrateSnapshot, parseDocument, parseVariableArgs, pattern, radialGradient, renderAnimationElements, renderCircle, renderEllipse, renderFilterDefs, renderFilterPrimitivesForType, renderImage, renderLine, renderPolygonShape, renderPolyline, renderRectangle, renderShape, renderSpline, renderText, renderToSvg, stringifyDocument, substituteString, substituteVariables, validateSnapshot, verticesToPath };
3258
+ export { type AnimatablePropertyDescriptor, type AnimationKeyframe, type AnimationTimeline, type AnimationTrack, type AnimationTrigger, type AnimationTriggerType, type AnyNodeProps, type ArcParams$1 as ArcParams, type AriaRole, Arrow, type ArrowNodeProps, type BaseFilter, type BlackAndWhiteFilter, type BlurQuality, type BrightnessFilter, CURRENT_SCHEMA_VERSION, type ChannelPainterFilter, Circle, type CircleNodeProps, type CodeFormat, type CodegenOptions, type CommonNodeProps, type ContouringDiscreteFilter, type ContouringTableFilter, type ContrastFilter, type CornerShapeValue, type CreateDocumentOptions, type CreateShapeOptions, Cross, type CrossNodeProps, type CrumpledPlasticFilter, type CustomPatternDef, type CycleBehavior, Document, type DocumentMetadata, type DocumentNodeProps, type DocumentOptions, type DropShadowFilter, type DuotoneFilter, EASING_CURVES, EVENT_TRIGGER_OPTIONS, EasingType, Ellipse, type EllipseNodeProps, type EmbossFilter, type ExtractedVariables, type FillType, type FilmGrainFilter, type FilterType, type GaussianBlurFilter, Gear, type GearNodeProps, type GlowFilter, type GouacheFilter, type GradientDefinition, type GradientSpreadMethod, type GradientStop, type GrayscaleFilter, type GroupNodeProps, type Guide, type HistorySnapshot, type HueRotateFilter, type ImageAttribution, type ImageNodeProps, ImageShape, type InkBlotFilter, type InnerGlowFilter, type InnerShadowFilter, type InvertFilter, type LicenseType, Line, type LineEndpointValue, type LineNodeProps, type LinearEasingPoint, type LinearGradient, LinearGradientBuilder, type LinkTarget, MARKER_HEIGHT, MARKER_VIEWBOX, MARKER_WIDTH, MIGRATIONS, type MarkerDescriptor, type Measurement, type Migration, type MorphologyFilter, type MorphologyOperator, type MotionBlurFilter, NGon, type NGonNodeProps, type NodeTypePropsMap, type NoiseFilter, type OpacityFilter, type OutlineFilter, type OutlinePosition, type ParseOptions, PatternBuilder, type PatternElement, type PatternFill, type PatternParams, type PatternSvgContentResult, type PatternType, type PixelateFilter, type Point, type PointLightFilter, type PolygonBaseNodeProps, Polyline, type PolylineNodeProps, type RadialGradient, RadialGradientBuilder, Rectangle, type RectangleNodeProps, type RenderOptions, type RichTextData, type RichTextLine, type RichTextSegment, type RichTextSegmentStyle, type RiddledFilter, Ring, type RingNodeProps, type RoundEdgesFilter, type SaturateFilter, type SegmentCurveType, type SepiaFilter, type SerializedAnimationTimeline, type SerializedClipMaskGroup, type SerializedGroup, type SerializedShape, type SerializedSymbolDef, type SerializedVariantAxis, type SerializedViewbox, ShapeBuilder, type ShapeFilter, type ShapeMetadata, type SharpenFilter, Spiral, type SpiralNodeProps, Spline, SplineCurveType, type SplineNodeProps, type SplinePoint, SplinePointType, type SpotLightFilter, Square, type SquareNodeProps, Star, type StarNodeProps, type StringifyOptions, type StrokeType, type SymbolInstanceNodeProps, type TemplateVariable, type TemplateVariableSource, type TemplateVariableType, Text, type TextNodeProps, Timeline, Track, Triangle, type TriangleNodeProps, type ValidationError, type ValidationResult, type VariableMap, type Viewbox, type WarpFilter, type WarpType, type WatercolorFilter, type XrayFilter, computeArrowVertices, computeCrossVertices, computeDashArray, computeGearPath, computeGearVertices, computePolygonVertices, computeRectanglePath, computeRingPath, computeSpiralPath, computeSplinePath, computeStarVertices, createDocument, createShape, createViewbox, escXml, extractVariables, filterAttr, generateCode, generateCssCode, generateD3Code, generateId, generatePatternSvgContent, generateReactCode, generateSvgCode, generateVueCode, getEasingCubicBezier, getMarkerDescriptors, getPatternElements, linearGradient, migrateSnapshot, parseDocument, parseVariableArgs, pattern, radialGradient, renderAnimationElements, renderCircle, renderEllipse, renderFilterDefs, renderFilterPrimitivesForType, renderImage, renderLine, renderPolygonShape, renderPolyline, renderRectangle, renderShape, renderSpline, renderText, renderToSvg, stringifyDocument, substituteString, substituteVariables, validateSnapshot, verticesToPath };