@pixerate/editor-react 0.32.0 → 0.33.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.
@@ -140,12 +140,50 @@ function VectorWidthPointEditor({ controller, edit, accentColor = THEME.accent,
140
140
 
141
141
  // src/vector/VectorCanvas.tsx
142
142
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
143
+ function GradientElement({ def }) {
144
+ const stops = def.stops.map((s, i) => /* @__PURE__ */ jsx2("stop", { offset: s.offset, stopColor: s.stopColor, stopOpacity: s.stopOpacity }, i));
145
+ return def.type === "radial" ? /* @__PURE__ */ jsx2("radialGradient", { id: def.id, ...def.attrs, children: stops }) : /* @__PURE__ */ jsx2("linearGradient", { id: def.id, ...def.attrs, children: stops });
146
+ }
143
147
  function VectorGradientDefs({ defs }) {
144
148
  if (!defs.length) return null;
145
- return /* @__PURE__ */ jsx2("defs", { children: defs.map((def) => {
146
- const stops = def.stops.map((s, i) => /* @__PURE__ */ jsx2("stop", { offset: s.offset, stopColor: s.stopColor, stopOpacity: s.stopOpacity }, i));
147
- return def.type === "radial" ? /* @__PURE__ */ jsx2("radialGradient", { id: def.id, ...def.attrs, children: stops }, def.id) : /* @__PURE__ */ jsx2("linearGradient", { id: def.id, ...def.attrs, children: stops }, def.id);
148
- }) });
149
+ return /* @__PURE__ */ jsx2("defs", { children: defs.map(
150
+ (def) => def.type === "pattern" ? /* @__PURE__ */ jsxs2("pattern", { id: def.id, ...def.attrs, children: [
151
+ def.gradients.map((g) => /* @__PURE__ */ jsx2(GradientElement, { def: g }, g.id)),
152
+ def.tiles.map((t, i) => /* @__PURE__ */ jsx2("path", { d: t.d, fill: t.fill }, i))
153
+ ] }, def.id) : /* @__PURE__ */ jsx2(GradientElement, { def }, def.id)
154
+ ) });
155
+ }
156
+ function VectorGradientAnnotator({ annotator: g, accentColor = THEME2.accent }) {
157
+ const r = THEME2.handleRadius + 1.5;
158
+ const ring = (p, part, size = r) => /* @__PURE__ */ jsx2("circle", { "data-vector-gradient-handle": part, cx: p.x, cy: p.y, r: size + (p.hovered ? 1 : 0), fill: THEME2.handleFill, stroke: accentColor, strokeWidth: p.hovered ? 2 : 1 });
159
+ return /* @__PURE__ */ jsxs2("g", { "data-vector-gradient-annotator": g.type, children: [
160
+ g.extent && /* @__PURE__ */ jsx2("path", { "data-vector-gradient-extent": "", d: g.extent, fill: "none", stroke: accentColor, strokeDasharray: "4 3" }),
161
+ /* @__PURE__ */ jsx2("line", { x1: g.start.x, y1: g.start.y, x2: g.end.x, y2: g.end.y, stroke: "#ffffff", strokeWidth: 3 }),
162
+ /* @__PURE__ */ jsx2("line", { "data-vector-gradient-bar": "", x1: g.start.x, y1: g.start.y, x2: g.end.x, y2: g.end.y, stroke: accentColor }),
163
+ g.aspect && /* @__PURE__ */ jsx2("line", { x1: g.start.x, y1: g.start.y, x2: g.aspect.x, y2: g.aspect.y, stroke: accentColor, strokeDasharray: "2 2" }),
164
+ ring(g.start, "start"),
165
+ /* @__PURE__ */ jsx2(
166
+ "rect",
167
+ {
168
+ "data-vector-gradient-handle": g.end.handle,
169
+ x: g.end.x - r,
170
+ y: g.end.y - r,
171
+ width: 2 * r,
172
+ height: 2 * r,
173
+ fill: THEME2.handleFill,
174
+ stroke: accentColor,
175
+ strokeWidth: g.end.hovered ? 2 : 1
176
+ }
177
+ ),
178
+ g.aspect && ring(g.aspect, "aspect", THEME2.handleRadius),
179
+ g.focal && /* @__PURE__ */ jsx2("g", { "data-vector-gradient-handle": "focal", children: /* @__PURE__ */ jsx2("circle", { cx: g.focal.x, cy: g.focal.y, r: THEME2.handleRadius - 1 + (g.focal.hovered ? 1 : 0), fill: accentColor, stroke: THEME2.handleFill }) }),
180
+ g.midpoints.map((m) => /* @__PURE__ */ jsx2("path", { "data-vector-gradient-midpoint": m.index, d: vectorDiamondPath(m.x, m.y, 4 + (m.hovered ? 1 : 0)), fill: THEME2.handleFill, stroke: accentColor }, m.index)),
181
+ g.stops.map((s) => /* @__PURE__ */ jsxs2("g", { "data-vector-gradient-stop": s.index, "data-selected": s.selected || void 0, opacity: g.removing && s.selected ? 0.35 : 1, children: [
182
+ /* @__PURE__ */ jsx2("line", { x1: s.barX, y1: s.barY, x2: s.x, y2: s.y, stroke: accentColor }),
183
+ /* @__PURE__ */ jsx2("rect", { x: s.x - 5, y: s.y - 5, width: 10, height: 10, rx: 2, fill: s.color, fillOpacity: s.opacity, stroke: s.selected ? accentColor : "#ffffff", strokeWidth: s.selected || s.hovered ? 2 : 1 }),
184
+ /* @__PURE__ */ jsx2("rect", { x: s.x - 6.5, y: s.y - 6.5, width: 13, height: 13, rx: 3, fill: "none", stroke: s.selected ? "#ffffff" : accentColor })
185
+ ] }, s.index))
186
+ ] });
149
187
  }
150
188
  function VectorSceneNodes({ nodes }) {
151
189
  return /* @__PURE__ */ jsx2(Fragment, { children: nodes.map(
@@ -369,6 +407,7 @@ function VectorCanvas({
369
407
  },
370
408
  `blend-anchor:${i}`
371
409
  )),
410
+ overlay.gradient && /* @__PURE__ */ jsx2(VectorGradientAnnotator, { annotator: overlay.gradient, accentColor }),
372
411
  overlay.widthSpot && /* @__PURE__ */ jsxs2("g", { "data-vector-width-spot": "", children: [
373
412
  /* @__PURE__ */ jsx2("line", { x1: overlay.widthSpot.left.x, y1: overlay.widthSpot.left.y, x2: overlay.widthSpot.right.x, y2: overlay.widthSpot.right.y, stroke: accentColor, strokeDasharray: "3 2" }),
374
413
  /* @__PURE__ */ jsx2("path", { d: vectorDiamondPath(overlay.widthSpot.x, overlay.widthSpot.y), fill: "none", stroke: accentColor })
@@ -496,7 +535,7 @@ import {
496
535
  getVectorSelectionStyle,
497
536
  widthProfilePreset,
498
537
  widthProfilePresetId,
499
- vectorPaintToHex,
538
+ vectorPaintToHex as vectorPaintToHex2,
500
539
  vectorToolLabel
501
540
  } from "@pixerate/editor/vector";
502
541
 
@@ -643,11 +682,191 @@ function VectorEffectsPanel({ controller, className }) {
643
682
  ] });
644
683
  }
645
684
 
646
- // src/vector/VectorToolbar.tsx
685
+ // src/vector/VectorGradientEditor.tsx
686
+ import {
687
+ VECTOR_GRADIENT_TYPES,
688
+ VECTOR_GRADIENT_SPREADS,
689
+ VECTOR_GRADIENT_INTERPOLATIONS,
690
+ VECTOR_STROKE_GRADIENT_MODES,
691
+ VECTOR_GRADIENT_TYPE_LABELS,
692
+ VECTOR_GRADIENT_SPREAD_LABELS,
693
+ VECTOR_GRADIENT_INTERPOLATION_LABELS,
694
+ VECTOR_STROKE_GRADIENT_MODE_LABELS,
695
+ getVectorGradientEditor,
696
+ vectorPaintToHex
697
+ } from "@pixerate/editor/vector";
647
698
  import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
699
+ var selectClass2 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
700
+ var numberClass2 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
701
+ var buttonClass2 = "inline-flex h-7 min-w-7 items-center justify-center rounded border border-border bg-background px-1.5 text-xs text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
702
+ var percent = (v) => Math.round(v * 1e3) / 10;
703
+ function VectorGradientEditor({ controller, className }) {
704
+ const state = useVectorEditorState(controller);
705
+ const ed = getVectorGradientEditor(state);
706
+ if (!ed) return null;
707
+ const g = ed.gradient;
708
+ const sel = ed.selected;
709
+ const end = () => controller.endMergedEdit();
710
+ const setType = (value) => {
711
+ if (value === "solid") {
712
+ const first = ed.stops[0];
713
+ controller.setStyle({ [ed.target]: vectorPaintToHex(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
714
+ } else controller.setGradient({ type: value });
715
+ };
716
+ return /* @__PURE__ */ jsxs4("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
717
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5", children: [
718
+ /* @__PURE__ */ jsx4("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
719
+ ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx4("button", { type: "button", className: buttonClass2, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
720
+ ed.mixed && /* @__PURE__ */ jsx4("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
721
+ ] }),
722
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5", children: [
723
+ /* @__PURE__ */ jsx4("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
724
+ /* @__PURE__ */ jsxs4("select", { "aria-label": "Gradient type", className: selectClass2, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
725
+ /* @__PURE__ */ jsx4("option", { value: "solid", children: "Solid" }),
726
+ VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx4("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
727
+ ] }),
728
+ /* @__PURE__ */ jsx4("button", { type: "button", className: buttonClass2, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
729
+ ] }),
730
+ g && /* @__PURE__ */ jsxs4(Fragment2, { children: [
731
+ /* @__PURE__ */ jsxs4("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
732
+ /* @__PURE__ */ jsx4(
733
+ "div",
734
+ {
735
+ role: "button",
736
+ tabIndex: -1,
737
+ "aria-label": "Add gradient stop",
738
+ className: "absolute inset-x-1.5 top-0 h-4 cursor-copy rounded border border-border",
739
+ style: { backgroundImage: `${ed.preview}, repeating-conic-gradient(#ddd 0% 25%, #fff 0% 50%)`, backgroundSize: "auto, 8px 8px" },
740
+ onClick: (e) => {
741
+ const r = e.currentTarget.getBoundingClientRect();
742
+ controller.addGradientStop(r.width > 0 ? Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)) : 0.5);
743
+ }
744
+ }
745
+ ),
746
+ ed.stops.map((s) => /* @__PURE__ */ jsx4(
747
+ "button",
748
+ {
749
+ type: "button",
750
+ "data-vector-gradient-stop": s.index,
751
+ "aria-label": `Gradient stop ${s.index + 1} at ${percent(s.offset)}%`,
752
+ "aria-pressed": s.selected,
753
+ className: "absolute top-4 h-3.5 w-3 -translate-x-1/2 rounded-sm border border-border aria-pressed:ring-2 aria-pressed:ring-primary",
754
+ style: { left: `calc(6px + (100% - 12px) * ${s.offset})`, background: s.color, opacity: 0.4 + 0.6 * (s.opacity ?? 1) },
755
+ onClick: () => controller.selectGradientStop(s.selected ? null : s.index)
756
+ },
757
+ s.index
758
+ ))
759
+ ] }),
760
+ sel && /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
761
+ /* @__PURE__ */ jsx4(
762
+ "input",
763
+ {
764
+ type: "color",
765
+ "aria-label": "Stop colour",
766
+ className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
767
+ value: vectorPaintToHex(sel.color) ?? "#000000",
768
+ onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
769
+ onBlur: end
770
+ }
771
+ ),
772
+ /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
773
+ "Opacity",
774
+ /* @__PURE__ */ jsx4(
775
+ "input",
776
+ {
777
+ type: "number",
778
+ min: 0,
779
+ max: 100,
780
+ step: 5,
781
+ "aria-label": "Stop opacity",
782
+ className: numberClass2,
783
+ value: percent(sel.opacity ?? 1),
784
+ onChange: (e) => {
785
+ const n = parseFloat(e.target.value);
786
+ if (Number.isFinite(n)) controller.setGradientStop(sel.index, { opacity: n / 100 });
787
+ }
788
+ }
789
+ )
790
+ ] }),
791
+ /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
792
+ "At",
793
+ /* @__PURE__ */ jsx4(
794
+ "input",
795
+ {
796
+ type: "number",
797
+ min: 0,
798
+ max: 100,
799
+ step: 1,
800
+ "aria-label": "Stop position",
801
+ className: numberClass2,
802
+ value: percent(sel.offset),
803
+ onChange: (e) => {
804
+ const n = parseFloat(e.target.value);
805
+ if (Number.isFinite(n)) controller.setGradientStop(sel.index, { offset: n / 100 });
806
+ }
807
+ }
808
+ )
809
+ ] }),
810
+ !sel.last && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
811
+ "Midpoint",
812
+ /* @__PURE__ */ jsx4(
813
+ "input",
814
+ {
815
+ type: "number",
816
+ min: 5,
817
+ max: 95,
818
+ step: 1,
819
+ "aria-label": "Stop midpoint",
820
+ className: numberClass2,
821
+ value: percent(sel.midpoint ?? 0.5),
822
+ onChange: (e) => {
823
+ const n = parseFloat(e.target.value);
824
+ if (Number.isFinite(n)) controller.setGradientStop(sel.index, { midpoint: n / 100 });
825
+ }
826
+ }
827
+ )
828
+ ] }),
829
+ /* @__PURE__ */ jsx4("button", { type: "button", className: buttonClass2, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
830
+ ] }),
831
+ /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
832
+ /* @__PURE__ */ jsx4("span", { className: "w-16", children: "Colours" }),
833
+ /* @__PURE__ */ jsx4("select", { "aria-label": "Gradient interpolation", className: selectClass2, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx4("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
834
+ ] }),
835
+ /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
836
+ /* @__PURE__ */ jsx4("span", { className: "w-16", children: "Smooth" }),
837
+ /* @__PURE__ */ jsx4(
838
+ "input",
839
+ {
840
+ type: "range",
841
+ min: 0,
842
+ max: 1,
843
+ step: 0.05,
844
+ "aria-label": "Gradient smoothness",
845
+ className: "h-4 flex-1 accent-primary",
846
+ value: ed.smoothness,
847
+ onChange: (e) => controller.setGradient({ smoothness: parseFloat(e.target.value) }, { merge: "gradient-smoothness" }),
848
+ onPointerUp: end,
849
+ onBlur: end
850
+ }
851
+ )
852
+ ] }),
853
+ /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
854
+ /* @__PURE__ */ jsx4("span", { className: "w-16", children: "Spread" }),
855
+ /* @__PURE__ */ jsx4("select", { "aria-label": "Gradient spread", className: selectClass2, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx4("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
856
+ ] }),
857
+ ed.target === "stroke" && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
858
+ /* @__PURE__ */ jsx4("span", { className: "w-16", children: "On stroke" }),
859
+ /* @__PURE__ */ jsx4("select", { "aria-label": "Gradient on stroke", className: selectClass2, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx4("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
860
+ ] })
861
+ ] })
862
+ ] });
863
+ }
864
+
865
+ // src/vector/VectorToolbar.tsx
866
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
648
867
  var VECTOR_SNAP_TITLE = "Snap to objects";
649
868
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
650
- var buttonClass2 = "inline-flex h-8 min-w-8 items-center justify-center rounded-md border border-border bg-background px-2 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
869
+ var buttonClass3 = "inline-flex h-8 min-w-8 items-center justify-center rounded-md border border-border bg-background px-2 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
651
870
  function VectorToolbar({
652
871
  controller,
653
872
  tools = VECTOR_TOOLS,
@@ -665,14 +884,14 @@ function VectorToolbar({
665
884
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
666
885
  const [live, setLive] = React3.useState(false);
667
886
  const booleanDisabled = live ? count < 1 : count < 2;
668
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx4("button", { type: "button", className: buttonClass2, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
669
- return /* @__PURE__ */ jsxs4("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
670
- /* @__PURE__ */ jsx4("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx4(
887
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx5("button", { type: "button", className: buttonClass3, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
888
+ return /* @__PURE__ */ jsxs5("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
889
+ /* @__PURE__ */ jsx5("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx5(
671
890
  "button",
672
891
  {
673
892
  type: "button",
674
893
  "data-vector-tool": tool,
675
- className: buttonClass2,
894
+ className: buttonClass3,
676
895
  "aria-pressed": state.tool === tool,
677
896
  title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
678
897
  "aria-label": vectorToolLabel(tool),
@@ -681,16 +900,16 @@ function VectorToolbar({
681
900
  },
682
901
  tool
683
902
  )) }),
684
- showHistory && /* @__PURE__ */ jsxs4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
903
+ showHistory && /* @__PURE__ */ jsxs5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
685
904
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
686
905
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
687
906
  ] }),
688
- showSnap && /* @__PURE__ */ jsx4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ jsx4(
907
+ showSnap && /* @__PURE__ */ jsx5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ jsx5(
689
908
  "button",
690
909
  {
691
910
  type: "button",
692
911
  "data-vector-snap": "",
693
- className: buttonClass2,
912
+ className: buttonClass3,
694
913
  "aria-pressed": state.snap.objects,
695
914
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
696
915
  "aria-label": VECTOR_SNAP_TITLE,
@@ -698,13 +917,13 @@ function VectorToolbar({
698
917
  children: "Snap"
699
918
  }
700
919
  ) }),
701
- showPathEdit && /* @__PURE__ */ jsxs4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
702
- VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx4(
920
+ showPathEdit && /* @__PURE__ */ jsxs5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
921
+ VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx5(
703
922
  "button",
704
923
  {
705
924
  type: "button",
706
925
  "data-vector-path-action": a.id,
707
- className: buttonClass2,
926
+ className: buttonClass3,
708
927
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
709
928
  "aria-label": a.title,
710
929
  disabled: !a.enabled(count, points),
@@ -713,12 +932,12 @@ function VectorToolbar({
713
932
  },
714
933
  a.id
715
934
  )),
716
- VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx4(
935
+ VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx5(
717
936
  "button",
718
937
  {
719
938
  type: "button",
720
939
  "data-vector-edit": t.key,
721
- className: buttonClass2,
940
+ className: buttonClass3,
722
941
  "aria-pressed": editing[t.key],
723
942
  title: t.title,
724
943
  "aria-label": t.title,
@@ -727,9 +946,9 @@ function VectorToolbar({
727
946
  },
728
947
  t.key
729
948
  )),
730
- state.tool === "pencil" && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
949
+ state.tool === "pencil" && /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
731
950
  "Smoothing",
732
- /* @__PURE__ */ jsx4(
951
+ /* @__PURE__ */ jsx5(
733
952
  "input",
734
953
  {
735
954
  type: "range",
@@ -743,9 +962,9 @@ function VectorToolbar({
743
962
  }
744
963
  )
745
964
  ] }),
746
- state.tool === "eraser" && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
965
+ state.tool === "eraser" && /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
747
966
  "Size",
748
- /* @__PURE__ */ jsx4(
967
+ /* @__PURE__ */ jsx5(
749
968
  "input",
750
969
  {
751
970
  type: "number",
@@ -761,12 +980,12 @@ function VectorToolbar({
761
980
  }
762
981
  )
763
982
  ] }),
764
- state.tool === "paintBucket" && /* @__PURE__ */ jsx4(
983
+ state.tool === "paintBucket" && /* @__PURE__ */ jsx5(
765
984
  "button",
766
985
  {
767
986
  type: "button",
768
987
  "data-vector-fill-gap": "",
769
- className: buttonClass2,
988
+ className: buttonClass3,
770
989
  "aria-pressed": editing.fillGap > 0,
771
990
  title: "Close small gaps between path ends when filling",
772
991
  "aria-label": "Close gaps",
@@ -775,15 +994,15 @@ function VectorToolbar({
775
994
  }
776
995
  )
777
996
  ] }),
778
- showBlend && /* @__PURE__ */ jsx4(VectorBlendControls, { controller }),
779
- showArrange && /* @__PURE__ */ jsxs4(Fragment2, { children: [
780
- /* @__PURE__ */ jsxs4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
781
- /* @__PURE__ */ jsx4(
997
+ showBlend && /* @__PURE__ */ jsx5(VectorBlendControls, { controller }),
998
+ showArrange && /* @__PURE__ */ jsxs5(Fragment3, { children: [
999
+ /* @__PURE__ */ jsxs5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
1000
+ /* @__PURE__ */ jsx5(
782
1001
  "button",
783
1002
  {
784
1003
  type: "button",
785
1004
  "data-vector-live-boolean": "",
786
- className: buttonClass2,
1005
+ className: buttonClass3,
787
1006
  "aria-pressed": live,
788
1007
  title: "Live booleans: keep the shapes editable",
789
1008
  "aria-label": "Live booleans",
@@ -798,12 +1017,12 @@ function VectorToolbar({
798
1017
  act("G", "Group", () => controller.group(), count < 1),
799
1018
  act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
800
1019
  ] }),
801
- /* @__PURE__ */ jsx4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx4(
1020
+ /* @__PURE__ */ jsx5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx5(
802
1021
  "button",
803
1022
  {
804
1023
  type: "button",
805
1024
  "data-vector-pathfinder": a.id,
806
- className: buttonClass2,
1025
+ className: buttonClass3,
807
1026
  title: a.title,
808
1027
  "aria-label": a.title,
809
1028
  disabled: count < a.min,
@@ -812,7 +1031,7 @@ function VectorToolbar({
812
1031
  },
813
1032
  a.id
814
1033
  )) }),
815
- /* @__PURE__ */ jsxs4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
1034
+ /* @__PURE__ */ jsxs5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
816
1035
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
817
1036
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
818
1037
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -830,19 +1049,19 @@ function VectorBlendControls({ controller }) {
830
1049
  const blend = getVectorBlendControls(state);
831
1050
  const o = blend.options;
832
1051
  const selected = blend.ids.length > 0;
833
- return /* @__PURE__ */ jsxs4("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
834
- /* @__PURE__ */ jsx4("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass2, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
835
- /* @__PURE__ */ jsx4(
1052
+ return /* @__PURE__ */ jsxs5("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
1053
+ /* @__PURE__ */ jsx5("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass3, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
1054
+ /* @__PURE__ */ jsx5(
836
1055
  "select",
837
1056
  {
838
1057
  "aria-label": "Blend spacing",
839
1058
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
840
1059
  value: o.spacing,
841
1060
  onChange: (e) => applyVectorBlendOptions(controller, { spacing: e.target.value }),
842
- children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx4("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
1061
+ children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx5("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
843
1062
  }
844
1063
  ),
845
- o.spacing !== "smoothColor" && /* @__PURE__ */ jsx4(
1064
+ o.spacing !== "smoothColor" && /* @__PURE__ */ jsx5(
846
1065
  "input",
847
1066
  {
848
1067
  type: "number",
@@ -858,12 +1077,12 @@ function VectorBlendControls({ controller }) {
858
1077
  }
859
1078
  }
860
1079
  ),
861
- /* @__PURE__ */ jsx4(
1080
+ /* @__PURE__ */ jsx5(
862
1081
  "button",
863
1082
  {
864
1083
  type: "button",
865
1084
  "data-vector-blend-action": "orient",
866
- className: buttonClass2,
1085
+ className: buttonClass3,
867
1086
  "aria-pressed": o.orientToSpine,
868
1087
  title: "Orient steps to the spine",
869
1088
  "aria-label": "Orient to spine",
@@ -871,12 +1090,12 @@ function VectorBlendControls({ controller }) {
871
1090
  children: "\u293F"
872
1091
  }
873
1092
  ),
874
- /* @__PURE__ */ jsx4(
1093
+ /* @__PURE__ */ jsx5(
875
1094
  "button",
876
1095
  {
877
1096
  type: "button",
878
1097
  "data-vector-blend-action": "spine",
879
- className: buttonClass2,
1098
+ className: buttonClass3,
880
1099
  title: "Replace Spine (select a blend and a path)",
881
1100
  "aria-label": "Replace Spine",
882
1101
  disabled: !blend.canReplaceSpine,
@@ -884,12 +1103,12 @@ function VectorBlendControls({ controller }) {
884
1103
  children: "\u2307"
885
1104
  }
886
1105
  ),
887
- /* @__PURE__ */ jsx4("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass2, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
888
- /* @__PURE__ */ jsx4("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass2, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
1106
+ /* @__PURE__ */ jsx5("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass3, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
1107
+ /* @__PURE__ */ jsx5("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass3, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
889
1108
  ] });
890
1109
  }
891
- var selectClass2 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
892
- var numberClass2 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
1110
+ var selectClass3 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1111
+ var numberClass3 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
893
1112
  function VectorStylePanel({ controller, className }) {
894
1113
  const state = useVectorEditorState(controller);
895
1114
  const style = getVectorSelectionStyle(state);
@@ -920,17 +1139,17 @@ function VectorStylePanel({ controller, className }) {
920
1139
  if (style) controller.setWidthProfile(id);
921
1140
  else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
922
1141
  };
923
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx4("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
1142
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx5("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
924
1143
  const arrowRow = (end, label) => {
925
1144
  const key = end === "start" ? "startArrow" : "endArrow";
926
1145
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
927
- return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
928
- /* @__PURE__ */ jsx4("span", { className: "w-16 text-xs text-foreground", children: label }),
929
- /* @__PURE__ */ jsxs4("select", { "aria-label": `${label} arrowhead`, className: selectClass2, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
930
- /* @__PURE__ */ jsx4("option", { value: "", children: "None" }),
931
- VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx4("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
1146
+ return /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
1147
+ /* @__PURE__ */ jsx5("span", { className: "w-16 text-xs text-foreground", children: label }),
1148
+ /* @__PURE__ */ jsxs5("select", { "aria-label": `${label} arrowhead`, className: selectClass3, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
1149
+ /* @__PURE__ */ jsx5("option", { value: "", children: "None" }),
1150
+ VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx5("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
932
1151
  ] }),
933
- /* @__PURE__ */ jsx4(
1152
+ /* @__PURE__ */ jsx5(
934
1153
  "input",
935
1154
  {
936
1155
  type: "number",
@@ -938,7 +1157,7 @@ function VectorStylePanel({ controller, className }) {
938
1157
  step: 0.25,
939
1158
  "aria-label": `${label} arrowhead scale`,
940
1159
  title: "Scale (\xD7 stroke width)",
941
- className: numberClass2,
1160
+ className: numberClass3,
942
1161
  value: source[scaleKey],
943
1162
  onChange: (e) => {
944
1163
  const n = parseFloat(e.target.value);
@@ -954,14 +1173,14 @@ function VectorStylePanel({ controller, className }) {
954
1173
  };
955
1174
  const paintRow = (key, label) => {
956
1175
  const paint = source[key];
957
- const hex = vectorPaintToHex(paint);
1176
+ const hex = vectorPaintToHex2(paint);
958
1177
  const on = paint !== null && paint !== "none";
959
- return /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
960
- /* @__PURE__ */ jsxs4("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
961
- /* @__PURE__ */ jsx4("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
1178
+ return /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
1179
+ /* @__PURE__ */ jsxs5("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
1180
+ /* @__PURE__ */ jsx5("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
962
1181
  label
963
1182
  ] }),
964
- /* @__PURE__ */ jsx4(
1183
+ /* @__PURE__ */ jsx5(
965
1184
  "input",
966
1185
  {
967
1186
  type: "color",
@@ -972,17 +1191,18 @@ function VectorStylePanel({ controller, className }) {
972
1191
  onChange: (e) => apply({ [key]: e.target.value })
973
1192
  }
974
1193
  ),
975
- on && !hex && /* @__PURE__ */ jsx4("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
976
- style?.mixed[key] && /* @__PURE__ */ jsx4("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1194
+ on && !hex && /* @__PURE__ */ jsx5("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
1195
+ style?.mixed[key] && /* @__PURE__ */ jsx5("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
977
1196
  ] });
978
1197
  };
979
- return /* @__PURE__ */ jsxs4("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
980
- /* @__PURE__ */ jsx4("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
1198
+ return /* @__PURE__ */ jsxs5("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
1199
+ /* @__PURE__ */ jsx5("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
981
1200
  paintRow("fill", "Fill"),
982
1201
  paintRow("stroke", "Stroke"),
983
- /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
984
- /* @__PURE__ */ jsx4("span", { className: "w-16", children: "Width" }),
985
- /* @__PURE__ */ jsx4(
1202
+ style && /* @__PURE__ */ jsx5(VectorGradientEditor, { controller }),
1203
+ /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1204
+ /* @__PURE__ */ jsx5("span", { className: "w-16", children: "Width" }),
1205
+ /* @__PURE__ */ jsx5(
986
1206
  "input",
987
1207
  {
988
1208
  type: "number",
@@ -998,33 +1218,33 @@ function VectorStylePanel({ controller, className }) {
998
1218
  }
999
1219
  )
1000
1220
  ] }),
1001
- /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
1002
- /* @__PURE__ */ jsx4("span", { className: "w-16", children: "Align" }),
1003
- /* @__PURE__ */ jsx4("select", { "aria-label": "Stroke alignment", className: selectClass2, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ jsx4("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
1221
+ /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
1222
+ /* @__PURE__ */ jsx5("span", { className: "w-16", children: "Align" }),
1223
+ /* @__PURE__ */ jsx5("select", { "aria-label": "Stroke alignment", className: selectClass3, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ jsx5("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
1004
1224
  mixed("strokeAlign")
1005
1225
  ] }),
1006
- /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
1007
- /* @__PURE__ */ jsx4("span", { className: "w-16", children: "Profile" }),
1008
- /* @__PURE__ */ jsxs4(
1226
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
1227
+ /* @__PURE__ */ jsx5("span", { className: "w-16", children: "Profile" }),
1228
+ /* @__PURE__ */ jsxs5(
1009
1229
  "select",
1010
1230
  {
1011
1231
  "aria-label": "Width profile",
1012
- className: selectClass2,
1232
+ className: selectClass3,
1013
1233
  value: source.widthProfile,
1014
1234
  onChange: (e) => {
1015
1235
  if (e.target.value !== "custom") setProfile(e.target.value);
1016
1236
  },
1017
1237
  children: [
1018
- VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx4("option", { value: p.id, children: p.label }, p.id)),
1019
- source.widthProfile === "custom" && /* @__PURE__ */ jsx4("option", { value: "custom", disabled: true, children: "Custom" })
1238
+ VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx5("option", { value: p.id, children: p.label }, p.id)),
1239
+ source.widthProfile === "custom" && /* @__PURE__ */ jsx5("option", { value: "custom", disabled: true, children: "Custom" })
1020
1240
  ]
1021
1241
  }
1022
1242
  ),
1023
- /* @__PURE__ */ jsx4(
1243
+ /* @__PURE__ */ jsx5(
1024
1244
  "button",
1025
1245
  {
1026
1246
  type: "button",
1027
- className: buttonClass2,
1247
+ className: buttonClass3,
1028
1248
  title: "Reset to uniform width",
1029
1249
  "aria-label": "Reset to uniform width",
1030
1250
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -1034,27 +1254,27 @@ function VectorStylePanel({ controller, className }) {
1034
1254
  ),
1035
1255
  mixed("widthProfile")
1036
1256
  ] }),
1037
- source.hasOpenPath && /* @__PURE__ */ jsxs4(Fragment2, { children: [
1257
+ source.hasOpenPath && /* @__PURE__ */ jsxs5(Fragment3, { children: [
1038
1258
  arrowRow("start", "Start"),
1039
1259
  arrowRow("end", "End"),
1040
- (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
1041
- /* @__PURE__ */ jsx4("span", { className: "w-16", children: "Heads" }),
1042
- /* @__PURE__ */ jsxs4("select", { "aria-label": "Arrowhead alignment", className: selectClass2, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
1043
- /* @__PURE__ */ jsx4("option", { value: "extend", children: "Tip past end" }),
1044
- /* @__PURE__ */ jsx4("option", { value: "tip", children: "Tip at end" })
1260
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
1261
+ /* @__PURE__ */ jsx5("span", { className: "w-16", children: "Heads" }),
1262
+ /* @__PURE__ */ jsxs5("select", { "aria-label": "Arrowhead alignment", className: selectClass3, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
1263
+ /* @__PURE__ */ jsx5("option", { value: "extend", children: "Tip past end" }),
1264
+ /* @__PURE__ */ jsx5("option", { value: "tip", children: "Tip at end" })
1045
1265
  ] })
1046
1266
  ] })
1047
1267
  ] }),
1048
- source.corners > 0 && /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
1049
- /* @__PURE__ */ jsx4("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
1050
- /* @__PURE__ */ jsx4(
1268
+ source.corners > 0 && /* @__PURE__ */ jsxs5("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
1269
+ /* @__PURE__ */ jsx5("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
1270
+ /* @__PURE__ */ jsx5(
1051
1271
  "input",
1052
1272
  {
1053
1273
  type: "number",
1054
1274
  min: 0,
1055
1275
  step: 1,
1056
1276
  "aria-label": "Corner radius",
1057
- className: numberClass2,
1277
+ className: numberClass3,
1058
1278
  value: Math.round(source.cornerRadius * 100) / 100,
1059
1279
  onChange: (e) => {
1060
1280
  const n = parseFloat(e.target.value);
@@ -1062,12 +1282,12 @@ function VectorStylePanel({ controller, className }) {
1062
1282
  }
1063
1283
  }
1064
1284
  ),
1065
- /* @__PURE__ */ jsx4("select", { "aria-label": "Corner kind", className: selectClass2, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx4("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
1285
+ /* @__PURE__ */ jsx5("select", { "aria-label": "Corner kind", className: selectClass3, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx5("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
1066
1286
  mixed("cornerRadius")
1067
1287
  ] }),
1068
- style && /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1069
- /* @__PURE__ */ jsx4("span", { className: "w-16", children: "Opacity" }),
1070
- /* @__PURE__ */ jsx4(
1288
+ style && /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1289
+ /* @__PURE__ */ jsx5("span", { className: "w-16", children: "Opacity" }),
1290
+ /* @__PURE__ */ jsx5(
1071
1291
  "input",
1072
1292
  {
1073
1293
  type: "range",
@@ -1081,18 +1301,18 @@ function VectorStylePanel({ controller, className }) {
1081
1301
  }
1082
1302
  )
1083
1303
  ] }),
1084
- style && /* @__PURE__ */ jsx4(VectorEffectsPanel, { controller })
1304
+ style && /* @__PURE__ */ jsx5(VectorEffectsPanel, { controller })
1085
1305
  ] });
1086
1306
  }
1087
1307
 
1088
1308
  // src/vector/VectorEditor.tsx
1089
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1309
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1090
1310
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
1091
- return /* @__PURE__ */ jsxs5("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
1092
- /* @__PURE__ */ jsx5(VectorToolbar, { controller, tools }),
1093
- /* @__PURE__ */ jsxs5("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
1094
- /* @__PURE__ */ jsx5("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx5(VectorCanvas, { controller, ...canvasProps }) }),
1095
- showStylePanel && /* @__PURE__ */ jsx5(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
1311
+ return /* @__PURE__ */ jsxs6("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
1312
+ /* @__PURE__ */ jsx6(VectorToolbar, { controller, tools }),
1313
+ /* @__PURE__ */ jsxs6("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
1314
+ /* @__PURE__ */ jsx6("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx6(VectorCanvas, { controller, ...canvasProps }) }),
1315
+ showStylePanel && /* @__PURE__ */ jsx6(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
1096
1316
  ] })
1097
1317
  ] });
1098
1318
  }
@@ -1100,7 +1320,9 @@ export {
1100
1320
  VectorCanvas,
1101
1321
  VectorEditor,
1102
1322
  VectorEffectsPanel,
1323
+ VectorGradientAnnotator,
1103
1324
  VectorGradientDefs,
1325
+ VectorGradientEditor,
1104
1326
  VectorSceneNodes,
1105
1327
  VectorStylePanel,
1106
1328
  VectorToolbar,