@svgsketch/core 0.2.0 → 0.5.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,31 @@ 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",
44
+ STEPS = "steps"
45
+ }
46
+ /**
47
+ * Step position for CSS `steps()` easing. Follows CSS Easing 1 §3.2.
48
+ * `start`/`end` are legacy aliases for `jump-start`/`jump-end`.
49
+ */
50
+ type StepPosition = 'jump-start' | 'jump-end' | 'jump-none' | 'jump-both';
51
+ /** Parameters for a `steps(count, position)` easing function. */
52
+ interface StepsParams {
53
+ count: number;
54
+ position: StepPosition;
33
55
  }
34
56
  /**
35
57
  * Map from EasingType → cubic-bezier control points [x1, y1, x2, y2].
@@ -82,6 +104,19 @@ interface AnimationKeyframe {
82
104
  time: number;
83
105
  value: number | string;
84
106
  easing: EasingType;
107
+ /** Custom cubic-bezier control points [x1, y1, x2, y2] when easing is CUSTOM_BEZIER. */
108
+ customBezier?: [number, number, number, number];
109
+ /** Control points for CSS linear() easing when easing is LINEAR_FUNCTION. */
110
+ linearPoints?: LinearEasingPoint[];
111
+ /** Step count and jump position when easing is STEPS. */
112
+ stepsParams?: StepsParams;
113
+ }
114
+ /** A control point for a CSS linear() easing function. */
115
+ interface LinearEasingPoint {
116
+ /** Output value (y-axis). Can exceed 0-1 for overshoot/elastic effects. */
117
+ value: number;
118
+ /** Position on the x-axis (0-1). */
119
+ position: number;
85
120
  }
86
121
  /**
87
122
  * An animation track binds a single property of a single shape
@@ -174,10 +209,33 @@ interface SerializedAnimationTimeline {
174
209
  };
175
210
  motionRotate?: 'auto' | 'auto-reverse' | number;
176
211
  additive?: 'sum' | 'replace';
212
+ /**
213
+ * 6-element CSS matrix of the track's ancestor transform context, captured
214
+ * at the time the animation was authored. Preserves visual fidelity when
215
+ * a shape is animated inside a transformed group and later copied, pasted,
216
+ * or imported into a different transform context.
217
+ */
218
+ ancestorMatrix?: number[];
219
+ /**
220
+ * Translation offset applied to transform-property keyframe values when
221
+ * the animated shape was pasted or imported into a new document, so that
222
+ * absolute transforms (rotation origins, translate values) remain correct
223
+ * relative to the shape's new location.
224
+ */
225
+ pasteOffset?: {
226
+ x: number;
227
+ y: number;
228
+ };
177
229
  keyframes: {
178
230
  time: number;
179
231
  value: number | string;
180
232
  easing: string;
233
+ customBezier?: [number, number, number, number];
234
+ linearPoints?: {
235
+ value: number;
236
+ position: number;
237
+ }[];
238
+ stepsParams?: StepsParams;
181
239
  }[];
182
240
  }[];
183
241
  }
@@ -191,8 +249,8 @@ interface AnimatablePropertyDescriptor {
191
249
  key: string;
192
250
  /** Human-readable label. */
193
251
  label: string;
194
- /** Value type: number, color (hex string), or path (SVG path data). */
195
- type: 'number' | 'color' | 'path';
252
+ /** Value type: number, color (hex string), path (SVG path data), or points (polyline/polygon). */
253
+ type: 'number' | 'color' | 'path' | 'points';
196
254
  /** The SVG attribute name this maps to (e.g. 'cx', 'fill'). */
197
255
  attr?: string;
198
256
  /** Minimum value for numeric properties. */
@@ -253,6 +311,23 @@ interface GradientStop {
253
311
  color: string;
254
312
  opacity: number;
255
313
  }
314
+ /**
315
+ * How gradient coordinates are interpreted.
316
+ *
317
+ * - `'objectBoundingBox'` (default): coordinates are fractions of the
318
+ * painted element's bounding box, where `1` equals the full width or
319
+ * height. The gradient rescales to each shape.
320
+ * - `'userSpaceOnUse'`: coordinates are absolute user-space values, so
321
+ * the gradient is a fixed color field in the document. Multiple
322
+ * shapes referencing the same gradient see a consistent field, and
323
+ * clipped shapes reveal a specific slice of it. Required for
324
+ * round-tripping source SVGs that author gradients this way.
325
+ *
326
+ * When omitted, the renderer treats the gradient as `objectBoundingBox`
327
+ * for backward compatibility with documents authored before this field
328
+ * was added.
329
+ */
330
+ type GradientUnits = 'objectBoundingBox' | 'userSpaceOnUse';
256
331
  interface LinearGradient {
257
332
  type: 'linear-gradient';
258
333
  id: string;
@@ -263,6 +338,12 @@ interface LinearGradient {
263
338
  stops: GradientStop[];
264
339
  spreadMethod: GradientSpreadMethod;
265
340
  opacity: number;
341
+ /**
342
+ * When present and equal to `'userSpaceOnUse'`, `x1`/`y1`/`x2`/`y2`
343
+ * are in document user space (post any ancestor bake), not in the
344
+ * element's bbox-normalised [0, 1] space. See `GradientUnits`.
345
+ */
346
+ gradientUnits?: GradientUnits;
266
347
  }
267
348
  interface RadialGradient {
268
349
  type: 'radial-gradient';
@@ -277,6 +358,11 @@ interface RadialGradient {
277
358
  stops: GradientStop[];
278
359
  spreadMethod: GradientSpreadMethod;
279
360
  opacity: number;
361
+ /**
362
+ * When present and equal to `'userSpaceOnUse'`, `cx`/`cy`/`fx`/`fy`/
363
+ * `r`/`ry` are in document user space. See `GradientUnits`.
364
+ */
365
+ gradientUnits?: GradientUnits;
280
366
  }
281
367
  /**
282
368
  * Per-pattern-type tuneable parameters.
@@ -652,6 +738,33 @@ interface ShapeMetadata {
652
738
  linkTarget: LinkTarget;
653
739
  customData: Record<string, string>;
654
740
  }
741
+ /**
742
+ * Provider attribution metadata for an image shape sourced from a third-party
743
+ * stock photo service. Stored on the shape so the editor can render the
744
+ * required photographer + provider credit links wherever the image is shown,
745
+ * and so the data round-trips through document save/load. Required by stock
746
+ * photo provider API guidelines (e.g. Unsplash).
747
+ */
748
+ interface ImageAttribution {
749
+ /** Slug for the provider, e.g. 'unsplash', 'openverse'. */
750
+ source: string;
751
+ /** Display name for the provider, e.g. 'Unsplash', 'OpenVerse'. */
752
+ sourceName: string;
753
+ /** URL to the photo's HTML page on the provider site. */
754
+ sourceUrl: string;
755
+ /** Photographer's display name. */
756
+ photographer: string;
757
+ /** Photographer's profile URL on the provider site. */
758
+ photographerUrl: string;
759
+ /**
760
+ * Optional SPDX-style license display label, e.g. 'CC BY 4.0', 'CC0 1.0',
761
+ * 'Public Domain'. Set by aggregators (OpenVerse) that surface per-asset
762
+ * license info; absent for providers with a single global license model.
763
+ */
764
+ license?: string;
765
+ /** Optional URL to the full license text. */
766
+ licenseUrl?: string;
767
+ }
655
768
  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
769
  interface DocumentMetadata {
657
770
  title: string;
@@ -665,9 +778,36 @@ interface DocumentMetadata {
665
778
  }
666
779
  /** Type of a template variable's value. */
667
780
  type TemplateVariableType = 'string' | 'color' | 'number';
668
- /** A template variable definition stored in the document. */
781
+ /**
782
+ * Where a TemplateVariable came from. `user` is hand-authored in the
783
+ * Variables panel; `palette` is auto-generated by the palette-token
784
+ * integration and is read-mostly (the source of truth lives in user
785
+ * settings, not the document).
786
+ */
787
+ type TemplateVariableSource = {
788
+ kind: 'user';
789
+ } | {
790
+ kind: 'palette';
791
+ paletteId: string;
792
+ index: number;
793
+ };
794
+ /**
795
+ * A template variable definition stored in the document.
796
+ *
797
+ * Two complementary roles:
798
+ *
799
+ * 1. **Build-time substitution** — `{{name}}` placeholders in shape
800
+ * properties are replaced via `substituteVariables()` (CLI / SDK
801
+ * template builds).
802
+ *
803
+ * 2. **Runtime CSS custom property** — when the editor is open, each
804
+ * variable is mirrored as `--name: defaultValue` inside a single
805
+ * `:root, svg { ... }` style block in the canvas SVG. Shapes can
806
+ * bind a geometry property to a variable via `var(--name)` and
807
+ * get live updates whenever the value changes.
808
+ */
669
809
  interface TemplateVariable {
670
- /** Variable name (used in `{{name}}` placeholders). */
810
+ /** Variable name (used in `{{name}}` placeholders and as CSS custom property `--name`). */
671
811
  name: string;
672
812
  /** The type of value this variable holds. */
673
813
  type: TemplateVariableType;
@@ -677,6 +817,13 @@ interface TemplateVariable {
677
817
  label?: string;
678
818
  /** Description / help text. */
679
819
  description?: string;
820
+ /**
821
+ * Provenance tag. Defaults to `{ kind: 'user' }` when omitted.
822
+ * Palette-sourced variables are surfaced in the panel as a read-only
823
+ * group — editing them in place would break the round-trip with the
824
+ * settings palette.
825
+ */
826
+ source?: TemplateVariableSource;
680
827
  }
681
828
  interface Guide {
682
829
  id: string;
@@ -696,6 +843,248 @@ interface Measurement {
696
843
  opacity?: number;
697
844
  }
698
845
 
846
+ /**
847
+ * Typed property interfaces for every node type in the scene graph.
848
+ *
849
+ * This is the **single source of truth** for shape property types across
850
+ * the entire SVGSketch stack (editor, API worker, server renderer, etc.).
851
+ *
852
+ * Each shape type declares the exact set of properties it owns.
853
+ * These interfaces drive:
854
+ * - Compile-time type safety on `SceneNode.get()` / `.set()` calls
855
+ * - The `NodeTypePropsMap` lookup used by `SceneNode.create()`
856
+ * - Schema defaults and validators in the editor's `node-schema.ts`
857
+ * - Serialization format for the `.svgs` document format
858
+ */
859
+
860
+ interface CommonNodeProps {
861
+ fillColor: string;
862
+ borderColor: string;
863
+ borderWidth: number;
864
+ opacity: number;
865
+ fillOpacity: number;
866
+ strokeOpacity: number;
867
+ rotation: number;
868
+ skewX: number;
869
+ skewY: number;
870
+ customPivot: Point | null;
871
+ locked: boolean;
872
+ visible: boolean;
873
+ fillType: string;
874
+ fillGradient: unknown | null;
875
+ strokeType: string;
876
+ strokeGradient: unknown | null;
877
+ fillRule: 'nonzero' | 'evenodd';
878
+ strokeLinejoin: 'miter' | 'round' | 'bevel';
879
+ strokeLinecap: 'butt' | 'round' | 'square';
880
+ strokeMiterlimit: number;
881
+ lineStyle: string;
882
+ dashLength: number;
883
+ gapLength: number;
884
+ dashOffset: number;
885
+ strokeDasharray: string | null;
886
+ filters: unknown[];
887
+ metadata: unknown | null;
888
+ cssClipPath: string | null;
889
+ cssMaskProperties: Record<string, string> | null;
890
+ groupId: string | null;
891
+ }
892
+ type CornerShapeValue = 'round' | 'notch' | 'bevel' | 'scoop';
893
+ interface RectangleNodeProps extends CommonNodeProps {
894
+ x: number;
895
+ y: number;
896
+ width: number;
897
+ height: number;
898
+ cornerRadius: number;
899
+ cornerShape: CornerShapeValue;
900
+ cornerMode: 'uniform' | 'non-uniform';
901
+ cornerRadiusTL: number;
902
+ cornerRadiusTR: number;
903
+ cornerRadiusBL: number;
904
+ cornerRadiusBR: number;
905
+ cornerShapeTL: CornerShapeValue;
906
+ cornerShapeTR: CornerShapeValue;
907
+ cornerShapeBL: CornerShapeValue;
908
+ cornerShapeBR: CornerShapeValue;
909
+ }
910
+ type SquareNodeProps = RectangleNodeProps;
911
+ interface CircleNodeProps extends CommonNodeProps {
912
+ x: number;
913
+ y: number;
914
+ radius: number;
915
+ }
916
+ interface EllipseNodeProps extends CommonNodeProps {
917
+ x: number;
918
+ y: number;
919
+ rx: number;
920
+ ry: number;
921
+ }
922
+ type LineEndpointValue = 'none' | 'arrow' | 'open-arrow' | 'circle' | 'diamond' | 'square';
923
+ interface LineNodeProps extends CommonNodeProps {
924
+ x1: number;
925
+ y1: number;
926
+ x2: number;
927
+ y2: number;
928
+ startEndpoint: LineEndpointValue;
929
+ endEndpoint: LineEndpointValue;
930
+ }
931
+ interface TextNodeProps extends CommonNodeProps {
932
+ x: number;
933
+ y: number;
934
+ width: number;
935
+ height: number;
936
+ textX: number;
937
+ textY: number;
938
+ fontSize: number;
939
+ text: string;
940
+ fontFamily: string;
941
+ fontWeight: string;
942
+ fontStyle: string;
943
+ textDecoration: Record<string, boolean>;
944
+ textTransform: string;
945
+ baselineShift: string;
946
+ dominantBaseline: string;
947
+ writingMode: string;
948
+ textAnchor: string;
949
+ letterSpacing: number;
950
+ wordSpacing: number;
951
+ lineHeight: number;
952
+ inlineSize: number;
953
+ overflowWrap: string;
954
+ whiteSpace: string;
955
+ textDirection: string;
956
+ unicodeBidi: string;
957
+ scaleX: number;
958
+ scaleY: number;
959
+ scaleAnchor: Point | null;
960
+ useRichText: boolean;
961
+ richTextData: unknown | null;
962
+ charOffsets: {
963
+ x: number;
964
+ y: number;
965
+ rotate: number;
966
+ }[] | null;
967
+ fontVariationSettings: Record<string, number>;
968
+ isTextPath: boolean;
969
+ textPathPoints: unknown[] | null;
970
+ textPathStartOffset: number;
971
+ textPathSide: 'left' | 'right';
972
+ shapeInsideRef: string | null;
973
+ shapePadding: number;
974
+ }
975
+ interface ImageNodeProps extends CommonNodeProps {
976
+ x: number;
977
+ y: number;
978
+ width: number;
979
+ height: number;
980
+ href: string;
981
+ originalWidth: number;
982
+ originalHeight: number;
983
+ preserveAspectRatio: boolean;
984
+ imageOpacity: number;
985
+ }
986
+ interface SplineNodeProps extends CommonNodeProps {
987
+ x: number;
988
+ y: number;
989
+ width: number;
990
+ height: number;
991
+ splinePoints: unknown[];
992
+ splineArcParams: unknown[];
993
+ splineControlBounds: {
994
+ x: number;
995
+ y: number;
996
+ width: number;
997
+ height: number;
998
+ } | null;
999
+ startEndpoint: LineEndpointValue;
1000
+ endEndpoint: LineEndpointValue;
1001
+ }
1002
+ interface PolylineNodeProps extends CommonNodeProps {
1003
+ x: number;
1004
+ y: number;
1005
+ width: number;
1006
+ height: number;
1007
+ polylinePoints: Point[];
1008
+ polylineClosed: boolean;
1009
+ }
1010
+ interface PolygonBaseNodeProps extends CommonNodeProps {
1011
+ cx: number;
1012
+ cy: number;
1013
+ radius: number;
1014
+ cornerRadius: number;
1015
+ shiftAngle: number;
1016
+ }
1017
+ interface TriangleNodeProps extends PolygonBaseNodeProps {
1018
+ sides: 3;
1019
+ }
1020
+ interface NGonNodeProps extends PolygonBaseNodeProps {
1021
+ sides: number;
1022
+ }
1023
+ interface StarNodeProps extends PolygonBaseNodeProps {
1024
+ arms: number;
1025
+ innerRadiusPercent: number;
1026
+ }
1027
+ interface CrossNodeProps extends PolygonBaseNodeProps {
1028
+ armWidthPercent: number;
1029
+ }
1030
+ interface RingNodeProps extends PolygonBaseNodeProps {
1031
+ innerRadiusPercent: number;
1032
+ }
1033
+ interface SpiralNodeProps extends PolygonBaseNodeProps {
1034
+ turns: number;
1035
+ thicknessPercent: number;
1036
+ spiralDirection: number;
1037
+ }
1038
+ interface GearNodeProps extends PolygonBaseNodeProps {
1039
+ teeth: number;
1040
+ toothDepthPercent: number;
1041
+ holeRadiusPercent: number;
1042
+ }
1043
+ interface ArrowNodeProps extends PolygonBaseNodeProps {
1044
+ headWidthPercent: number;
1045
+ headLengthPercent: number;
1046
+ shaftWidthPercent: number;
1047
+ }
1048
+ interface SymbolInstanceNodeProps extends CommonNodeProps {
1049
+ x: number;
1050
+ y: number;
1051
+ width: number;
1052
+ height: number;
1053
+ symbolId: string;
1054
+ }
1055
+ interface GroupNodeProps {
1056
+ groupId: string | null;
1057
+ }
1058
+ type DocumentNodeProps = Record<string, never>;
1059
+ /**
1060
+ * Maps each node type string to its typed property interface.
1061
+ * Used by `SceneNode.create()` for compile-time type safety.
1062
+ */
1063
+ interface NodeTypePropsMap {
1064
+ rectangle: RectangleNodeProps;
1065
+ square: SquareNodeProps;
1066
+ circle: CircleNodeProps;
1067
+ ellipse: EllipseNodeProps;
1068
+ line: LineNodeProps;
1069
+ text: TextNodeProps;
1070
+ image: ImageNodeProps;
1071
+ spline: SplineNodeProps;
1072
+ polyline: PolylineNodeProps;
1073
+ triangle: TriangleNodeProps;
1074
+ ngon: NGonNodeProps;
1075
+ star: StarNodeProps;
1076
+ cross: CrossNodeProps;
1077
+ ring: RingNodeProps;
1078
+ spiral: SpiralNodeProps;
1079
+ gear: GearNodeProps;
1080
+ arrow: ArrowNodeProps;
1081
+ 'symbol-instance': SymbolInstanceNodeProps;
1082
+ group: GroupNodeProps;
1083
+ document: DocumentNodeProps;
1084
+ }
1085
+ /** Union of all node prop types. */
1086
+ type AnyNodeProps = RectangleNodeProps | SquareNodeProps | CircleNodeProps | EllipseNodeProps | LineNodeProps | TextNodeProps | ImageNodeProps | SplineNodeProps | PolylineNodeProps | TriangleNodeProps | NGonNodeProps | StarNodeProps | CrossNodeProps | RingNodeProps | SpiralNodeProps | GearNodeProps | ArrowNodeProps | SymbolInstanceNodeProps | GroupNodeProps | DocumentNodeProps;
1087
+
699
1088
  /**
700
1089
  * @svgsketch/core — Serialized document types.
701
1090
  *
@@ -746,6 +1135,17 @@ interface SerializedShape {
746
1135
  y: number;
747
1136
  rotate: number;
748
1137
  }[];
1138
+ linePositions?: {
1139
+ x: number;
1140
+ dy: number;
1141
+ }[];
1142
+ /**
1143
+ * Whether this text element uses the rich-text model (styled runs with
1144
+ * per-character formatting) versus the plain-text model. When true,
1145
+ * `richTextData` is the authoritative source for text content and
1146
+ * formatting; when false/absent, `text` is authoritative.
1147
+ */
1148
+ useRichText?: boolean;
749
1149
  richTextData?: RichTextData;
750
1150
  scaleX?: number;
751
1151
  scaleY?: number;
@@ -773,10 +1173,21 @@ interface SerializedShape {
773
1173
  holeRadiusPercent?: number;
774
1174
  turns?: number;
775
1175
  thicknessPercent?: number;
776
- direction?: number;
1176
+ /** Spiral winding direction: 1 = counterclockwise, -1 = clockwise. */
1177
+ spiralDirection?: number;
777
1178
  headWidthPercent?: number;
778
1179
  headLengthPercent?: number;
779
1180
  shaftWidthPercent?: number;
1181
+ tailAngleDeg?: number;
1182
+ tailLengthPercent?: number;
1183
+ tailWidthPercent?: number;
1184
+ lobeRadiusPercent?: number;
1185
+ cleftDepthPercent?: number;
1186
+ boltSegments?: number;
1187
+ boltJaggednessPercent?: number;
1188
+ boltWidthPercent?: number;
1189
+ cloudBumps?: number;
1190
+ cloudPuffinessPercent?: number;
780
1191
  x1?: number;
781
1192
  y1?: number;
782
1193
  x2?: number;
@@ -791,6 +1202,16 @@ interface SerializedShape {
791
1202
  endEndpoint?: 'none' | 'arrow' | 'open-arrow' | 'circle' | 'diamond' | 'square';
792
1203
  locked?: boolean;
793
1204
  visible?: boolean;
1205
+ /**
1206
+ * SVG `visibility` presentation attribute. Distinct from `visible`:
1207
+ * `visible` (boolean) is the editor's user-toggled layer hide
1208
+ * (display:none semantics); `visibility` is the SVG attribute
1209
+ * (preserves layout, can be flipped by SMIL
1210
+ * `<animate attributeName="visibility">`). Conflating the two breaks
1211
+ * SMIL visibility animations on imported SVGs because display:none
1212
+ * overrides the SVG visibility attribute.
1213
+ */
1214
+ visibility?: 'visible' | 'hidden' | 'collapse';
794
1215
  fillType?: FillType;
795
1216
  fillGradient?: LinearGradient | RadialGradient | PatternFill;
796
1217
  strokeType?: StrokeType;
@@ -819,11 +1240,42 @@ interface SerializedShape {
819
1240
  strokeLinecap?: 'butt' | 'round' | 'square';
820
1241
  strokeMiterlimit?: number;
821
1242
  opacity?: number;
1243
+ blendMode?: string;
1244
+ shapeRendering?: 'auto' | 'optimizeSpeed' | 'crispEdges' | 'geometricPrecision';
822
1245
  metadata?: Partial<ShapeMetadata>;
823
1246
  groupId?: string;
824
1247
  cssClipPath?: string;
825
1248
  cssMaskProperties?: Record<string, string>;
826
1249
  symbolId?: string;
1250
+ /**
1251
+ * Per-shape property overrides applied to inner shapes of a symbol instance.
1252
+ * Keyed by the inner shape's ID inside the symbol definition. Each value is
1253
+ * a partial state object whose fields override the corresponding fields on
1254
+ * the resolved variant shape at render time. Only meaningful on shapes of
1255
+ * type `symbol-instance`.
1256
+ */
1257
+ symbolOverrides?: Record<string, Partial<SerializedShape['state']>>;
1258
+ /**
1259
+ * Canonical variant axis selection for a symbol instance, encoded as
1260
+ * `axis1=val1,axis2=val2`. Empty / missing → use the symbol's default
1261
+ * variant (`def.shapes`). Only meaningful on `symbol-instance` shapes.
1262
+ */
1263
+ variantKey?: string;
1264
+ /**
1265
+ * Map of geometry-property name → CSS custom-property name (without
1266
+ * the leading `--`) the property is bound to.
1267
+ *
1268
+ * When a property is bound, the literal value in `state[property]`
1269
+ * is the **resolved** value (last seen value of the variable). The
1270
+ * binding is what survives editing — typing into a bound field in
1271
+ * the panel updates the variable, not the literal — and on document
1272
+ * load the literal is re-resolved from the current variable value.
1273
+ *
1274
+ * Example: `{ width: 'card-width', cornerRadius: 'unit' }` means
1275
+ * `state.width = var(--card-width)` and `state.cornerRadius = var(--unit)`
1276
+ * when serialized to standalone SVG.
1277
+ */
1278
+ bindings?: Record<string, string>;
827
1279
  };
828
1280
  }
829
1281
  type SerializedViewbox = Viewbox;
@@ -874,9 +1326,27 @@ interface HistorySnapshot {
874
1326
  /** Symbol definitions (reusable component templates). */
875
1327
  symbols?: SerializedSymbolDef[];
876
1328
  }
1329
+ /**
1330
+ * A variant axis on a component symbol. Each axis has a name (e.g.
1331
+ * `state`, `size`, `theme`) and an ordered list of allowed values
1332
+ * (e.g. `[default, hover, pressed]`). The default value is always
1333
+ * the first entry in `values`.
1334
+ */
1335
+ interface SerializedVariantAxis {
1336
+ name: string;
1337
+ values: string[];
1338
+ }
877
1339
  /**
878
1340
  * A reusable symbol definition. Contains the shapes that make up the
879
1341
  * symbol template, plus metadata for the symbols panel.
1342
+ *
1343
+ * **Variants:** when `variantAxes` is present, the symbol carries
1344
+ * multiple alternative shape arrays keyed by variant combination
1345
+ * (e.g. `state=hover,size=md`). The base `shapes` field is always
1346
+ * the **default variant** — the combination where every axis is at
1347
+ * its first value. Other variants live in `variants[variantKey]`.
1348
+ * Variant resolution falls back to `shapes` whenever `variantKey`
1349
+ * is missing or empty.
880
1350
  */
881
1351
  interface SerializedSymbolDef {
882
1352
  /** Unique identifier for this symbol definition. */
@@ -885,12 +1355,34 @@ interface SerializedSymbolDef {
885
1355
  name: string;
886
1356
  /** SVG viewBox string ("minX minY width height"). */
887
1357
  viewBox: string;
888
- /** The shapes that make up this symbol's content. */
1358
+ /**
1359
+ * Default variant shapes. When `variantAxes` is present, this represents
1360
+ * the variant where every axis is at its first value.
1361
+ */
889
1362
  shapes: SerializedShape[];
890
1363
  /** Groups within the symbol. */
891
1364
  groups?: SerializedGroup[];
892
1365
  /** Base64 data-URI thumbnail for the symbols panel. */
893
1366
  thumbnail?: string;
1367
+ /**
1368
+ * Ordered list of variant axes available on this symbol. The order is
1369
+ * stable so that variant keys can be canonicalized as
1370
+ * `axis1=val1,axis2=val2`.
1371
+ */
1372
+ variantAxes?: SerializedVariantAxis[];
1373
+ /**
1374
+ * Non-default variant shape arrays keyed by canonical axis combination
1375
+ * string (e.g. `state=hover,size=md`). Missing keys fall back to the
1376
+ * base `shapes` array.
1377
+ */
1378
+ variants?: Record<string, SerializedShape[]>;
1379
+ /**
1380
+ * Per-variant thumbnails as base64 data URIs, keyed by canonical
1381
+ * variant combination string. The empty string `""` keys the default
1382
+ * variant (which also lives in `thumbnail` for backwards compatibility).
1383
+ * Used by the variant matrix view to show real previews of each cell.
1384
+ */
1385
+ variantThumbnails?: Record<string, string>;
894
1386
  }
895
1387
  interface SerializedGroup {
896
1388
  id: string;
@@ -910,6 +1402,15 @@ interface SerializedGroup {
910
1402
  pluginData?: string;
911
1403
  /** Additional custom data-* attributes set by plugins */
912
1404
  attributes?: Record<string, string>;
1405
+ fill?: string;
1406
+ fillOpacity?: string;
1407
+ strokeOpacity?: string;
1408
+ opacity?: string;
1409
+ filter?: string;
1410
+ cssFilter?: string;
1411
+ mixBlendMode?: string;
1412
+ clipPath?: string;
1413
+ mask?: string;
913
1414
  }
914
1415
  /** Serialized clip or mask group */
915
1416
  interface SerializedClipMaskGroup {
@@ -979,25 +1480,62 @@ interface SerializedClipMaskGroup {
979
1480
  * @svgsketch/core — Schema migrations.
980
1481
  *
981
1482
  * 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()
1483
+ * CURRENT_SCHEMA_VERSION. Each migration step is a pure function that
1484
+ * transforms version N to N+1 and lives in the `MIGRATIONS` registry
1485
+ * below.
1486
+ *
1487
+ * **Rules for adding a migration (e.g. v1 → v2):**
1488
+ * 1. Bump `CURRENT_SCHEMA_VERSION` in `types/serialized.ts`.
1489
+ * 2. Write a pure function `migrateV1toV2(snapshot) => snapshot` that
1490
+ * returns the transformed document (it may mutate — the dispatcher
1491
+ * clones input first).
1492
+ * 3. Register it in `MIGRATIONS` below with the *source* version as the
1493
+ * key: `1: migrateV1toV2`.
1494
+ * 4. Add a dedicated test in `migrations.test.ts` that exercises the
1495
+ * before-and-after shape.
1496
+ *
1497
+ * **Behavior contract:**
1498
+ * - Unknown older version (gap with no registered migration) → throws.
1499
+ * Silently stamping would risk corrupting data.
1500
+ * - Unknown newer version (snapshot from the future) → returned unchanged.
1501
+ * Loaders that can't understand a newer document should refuse it at a
1502
+ * higher layer, not here.
1503
+ * - Missing/malformed `schemaVersion` → treated as version 1 (the oldest
1504
+ * known format).
989
1505
  */
990
1506
 
1507
+ /**
1508
+ * A migration function: transforms a snapshot from version N to N+1.
1509
+ *
1510
+ * The function receives a deep-cloned snapshot and may mutate it freely.
1511
+ * It must return the transformed snapshot (typically the same object).
1512
+ */
1513
+ type Migration = (snapshot: HistorySnapshot) => HistorySnapshot;
1514
+ /**
1515
+ * Registry of schema migrations, keyed by **source** version.
1516
+ *
1517
+ * Entry `N: fn` means "`fn` transforms a version-N snapshot into a
1518
+ * version-(N+1) snapshot." The dispatcher in {@link migrateSnapshot}
1519
+ * walks this registry from the snapshot's current version up to
1520
+ * {@link CURRENT_SCHEMA_VERSION}.
1521
+ *
1522
+ * This object is exported so tests (and diagnostic tooling) can read
1523
+ * the registered migrations, but it should not be mutated at runtime.
1524
+ */
1525
+ declare const MIGRATIONS: Readonly<Record<number, Migration>>;
991
1526
  /**
992
1527
  * Migrate a snapshot from any older schema version to the current version.
993
1528
  *
994
1529
  * The function is idempotent — if the snapshot is already at the current
995
- * version (or newer), it is returned unchanged.
1530
+ * version (or newer), it is returned as a deep clone with no migrations
1531
+ * applied.
996
1532
  *
997
- * @param snapshot - The snapshot to migrate (not mutated; a new object is returned)
998
- * @returns The migrated snapshot at CURRENT_SCHEMA_VERSION
1533
+ * @param snapshot - The snapshot to migrate. Not mutated a new object is returned.
1534
+ * @param registry - Override the migration registry (for tests). Defaults to {@link MIGRATIONS}.
1535
+ * @returns The migrated snapshot, stamped with {@link CURRENT_SCHEMA_VERSION}.
1536
+ * @throws If the snapshot is at an older version for which no migration is registered.
999
1537
  */
1000
- declare function migrateSnapshot(snapshot: HistorySnapshot): HistorySnapshot;
1538
+ declare function migrateSnapshot(snapshot: HistorySnapshot, registry?: Record<number, Migration>): HistorySnapshot;
1001
1539
 
1002
1540
  /**
1003
1541
  * @svgsketch/core — Document validation.
@@ -1116,11 +1654,15 @@ declare function createViewbox(x: number, y: number, width: number, height: numb
1116
1654
  *
1117
1655
  * - Sorts top-level sections in a fixed order (schemaVersion, documentMetadata,
1118
1656
  * viewboxes, guides, measurements, groups, clipMaskGroups, shapes)
1119
- * - Sorts shapes, viewboxes, groups, guides by `id`
1657
+ * - Preserves `shapes` array order because shape-array position IS the
1658
+ * SVG painter's-model z-order (no separate z-index field exists).
1659
+ * Sorting would silently reorder rendering and destroy user intent.
1660
+ * - Sorts other id-keyed collections (groups, viewboxes, guides, etc.)
1661
+ * by `id` for stable diffs — their order has no semantic meaning
1662
+ * (groups use siblingIndex + parentId to record position).
1120
1663
  * - Sorts object keys alphabetically within each shape/state
1121
1664
  * - Uses 2-space indentation, one property per line
1122
1665
  * - Strips `undefined` values (but keeps explicit `null`)
1123
- * - Produces consistent output regardless of insertion order
1124
1666
  *
1125
1667
  * The result can be saved as a `.svgs` file and tracked in Git with
1126
1668
  * meaningful line-by-line diffs.
@@ -1352,6 +1894,41 @@ declare function computeCrossVertices(radius: number, armWidthPercent: number, s
1352
1894
  * Compute arrow vertices (7 points forming an arrow).
1353
1895
  */
1354
1896
  declare function computeArrowVertices(radius: number, headWidthPercent: number, headLengthPercent: number, shaftWidthPercent: number, shiftAngleDeg?: number): Point[];
1897
+ /**
1898
+ * Compute speech-bubble vertices — square body with a tail protruding at a
1899
+ * chosen angle. The base endpoints lie on the chosen body edge (keeping the
1900
+ * body outline continuous); the tip extends in the direction of `tailAngleDeg`
1901
+ * at length `tailLengthPercent * radius`.
1902
+ *
1903
+ * The `shiftAngleDeg` rotates the entire shape (body + tail).
1904
+ */
1905
+ declare function computeSpeechBubbleVertices(radius: number, tailAngleDeg: number, tailLengthPercent: number, tailWidthPercent: number, shiftAngleDeg?: number): Point[];
1906
+ /**
1907
+ * Compute lightning-bolt vertices — a zigzag ribbon from top tip to bottom tip.
1908
+ * `boltSegments` line segments make the zigzag (alternating x offsets of
1909
+ * `boltJaggednessPercent * radius`); the ribbon is `boltWidthPercent * radius`
1910
+ * wide, perpendicular to each segment's bisector normal.
1911
+ */
1912
+ declare function computeLightningVertices(radius: number, boltSegments: number, boltJaggednessPercent: number, boltWidthPercent: number, shiftAngleDeg?: number): Point[];
1913
+ /**
1914
+ * Compute heart path — two cubic Béziers meeting at the bottom tip and the
1915
+ * upper cleft. `lobeRadiusPercent` controls the horizontal extent of the two
1916
+ * top lobes; `cleftDepthPercent` controls how deep the central dip sits.
1917
+ */
1918
+ declare function computeHeartPath(radius: number, lobeRadiusPercent: number, cleftDepthPercent: number, shiftAngleDeg?: number): string;
1919
+ /**
1920
+ * Compute cloud path — a classic cartoon-cloud silhouette built as the outer
1921
+ * boundary of N overlapping circles placed around an elongated ellipse.
1922
+ * Circles go all the way around (top, sides, bottom), and the silhouette is
1923
+ * closed by walking each circle's outer arc via the intersection with its
1924
+ * next neighbor. Every transition between bumps is tangent-smooth — no flat
1925
+ * edges, no corners.
1926
+ *
1927
+ * `cloudBumps` (3..8) — number of bump circles forming the silhouette.
1928
+ * `cloudPuffinessPercent` (40..80) — drives bump overlap; higher values
1929
+ * smooth the underside; lower values leave the bumps more pronounced.
1930
+ */
1931
+ declare function computeCloudPath(radius: number, cloudBumps: number, cloudPuffinessPercent: number, shiftAngleDeg?: number): string;
1355
1932
  /**
1356
1933
  * Convert an array of vertices to an SVG path string.
1357
1934
  * Supports optional rounded corners via quadratic Bézier.
@@ -1365,9 +1942,6 @@ declare function computeRingPath(radius: number, innerRadiusPercent: number, fil
1365
1942
  * Generate SVG path for an Archimedean spiral.
1366
1943
  */
1367
1944
  declare function computeSpiralPath(radius: number, turns: number, thicknessPercent: number, spiralDirection?: number, shiftAngleDeg?: number): string;
1368
- /**
1369
- * Compute gear vertices (4 vertices per tooth).
1370
- */
1371
1945
  declare function computeGearVertices(teeth: number, radius: number, toothDepthPercent: number, shiftAngleDeg?: number): Point[];
1372
1946
  /**
1373
1947
  * Generate full gear path including optional center hole.
@@ -2212,7 +2786,7 @@ declare class Spiral extends PolygonShapeBuilder<Spiral> {
2212
2786
  turns(n: number): Spiral;
2213
2787
  /** Set the stroke thickness as a percent (0–100). */
2214
2788
  thickness(percent: number): Spiral;
2215
- /** Set the spiral direction: 1 = clockwise, -1 = counter-clockwise. */
2789
+ /** Set the spiral winding direction: 1 = counterclockwise, -1 = clockwise. */
2216
2790
  direction(d: 1 | -1): Spiral;
2217
2791
  }
2218
2792
  declare class Gear extends PolygonShapeBuilder<Gear> {
@@ -2332,6 +2906,22 @@ declare class Track {
2332
2906
  * Defaults to LINEAR.
2333
2907
  */
2334
2908
  keyframe(time: number, value: number | string, easing?: EasingType): Track;
2909
+ /**
2910
+ * Add a keyframe with a custom cubic-bezier easing.
2911
+ *
2912
+ * @param time - Time in seconds.
2913
+ * @param value - The property value.
2914
+ * @param points - Cubic-bezier control points [x1, y1, x2, y2].
2915
+ */
2916
+ keyframeBezier(time: number, value: number | string, points: [number, number, number, number]): Track;
2917
+ /**
2918
+ * Add a keyframe with a CSS linear() easing function.
2919
+ *
2920
+ * @param time - Time in seconds.
2921
+ * @param value - The property value.
2922
+ * @param linearPoints - Control points for the piecewise linear function.
2923
+ */
2924
+ keyframeLinear(time: number, value: number | string, linearPoints: LinearEasingPoint[]): Track;
2335
2925
  /** Add multiple keyframes at once. */
2336
2926
  keyframes(...kfs: AnimationKeyframe[]): Track;
2337
2927
  /** Disable this track (excluded from playback/export). */
@@ -2759,4 +3349,4 @@ declare class Document {
2759
3349
  private _ensureMetadata;
2760
3350
  }
2761
3351
 
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 };
3352
+ 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 GradientUnits, 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 StepPosition, type StepsParams, 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, computeCloudPath, computeCrossVertices, computeDashArray, computeGearPath, computeGearVertices, computeHeartPath, computeLightningVertices, computePolygonVertices, computeRectanglePath, computeRingPath, computeSpeechBubbleVertices, 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 };