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