@pixerate/editor-react 0.33.0 → 0.34.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,6 +33,7 @@ __export(vector_exports, {
33
33
  VectorCanvas: () => VectorCanvas,
34
34
  VectorEditor: () => VectorEditor,
35
35
  VectorEffectsPanel: () => VectorEffectsPanel,
36
+ VectorEnvelopePanel: () => VectorEnvelopePanel,
36
37
  VectorGradientAnnotator: () => VectorGradientAnnotator,
37
38
  VectorGradientDefs: () => VectorGradientDefs,
38
39
  VectorGradientEditor: () => VectorGradientEditor,
@@ -403,13 +404,28 @@ function VectorCanvas({
403
404
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-overlay": "", pointerEvents: "none", fill: "none", children: [
404
405
  overlay.outlines.map((o) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: o.d, stroke: accentColor, strokeWidth: o.selected ? 1 : 1.5, opacity: o.selected ? 1 : 0.7 }, o.id)),
405
406
  overlay.penPreview && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: overlay.penPreview, stroke: accentColor, strokeDasharray: "4 3" }),
406
- box && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-selection": "", transform: box.transform || void 0, children: [
407
+ box && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-selection": "", "data-free-distort": box.freeDistort || void 0, transform: box.transform || void 0, children: [
407
408
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: box.x, y: box.y, width: box.width, height: box.height, stroke: accentColor }),
408
409
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: box.x + box.width / 2, y1: box.y, x2: box.x + box.width / 2, y2: box.y - 24, stroke: accentColor }),
409
410
  box.handles.map(
410
411
  (h) => h.handle === "rotate" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { "data-handle": h.handle, cx: h.x, cy: h.y, r: hs / 2 + 1, fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, h.handle) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { "data-handle": h.handle, x: h.x - hs / 2, y: h.y - hs / 2, width: hs, height: hs, fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, h.handle)
411
412
  )
412
413
  ] }),
414
+ overlay.distort && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-distort": overlay.distort.mode, children: [
415
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: overlay.distort.d, stroke: accentColor }),
416
+ overlay.distort.corners.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: c.x - hs / 2, y: c.y - hs / 2, width: hs, height: hs, fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, i))
417
+ ] }),
418
+ overlay.envelope && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-envelope-mesh": overlay.envelope.nodeId, children: [
419
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { "data-vector-envelope-grid": "", d: overlay.envelope.grid, stroke: accentColor, strokeOpacity: 0.8 }),
420
+ overlay.envelope.handles.map((h) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-envelope-handle": h.index, children: [
421
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: h.anchorX, y1: h.anchorY, x2: h.x, y2: h.y, stroke: accentColor, strokeOpacity: 0.6 }),
422
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: h.x, cy: h.y, r: import_vector3.VECTOR_OVERLAY_THEME.handleRadius + (h.hovered || h.active ? 1 : 0), fill: h.active ? accentColor : import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor })
423
+ ] }, h.index)),
424
+ overlay.envelope.anchors.map((a) => {
425
+ const size = as + (a.hovered || a.active ? 2 : 0);
426
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { "data-vector-envelope-anchor": a.index, x: a.x - size / 2, y: a.y - size / 2, width: size, height: size, fill: a.active ? accentColor : import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, a.index);
427
+ })
428
+ ] }),
413
429
  overlay.handles.map((h) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-handle": h.ref.handle, children: [
414
430
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: h.anchorX, y1: h.anchorY, x2: h.x, y2: h.y, stroke: accentColor }),
415
431
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: h.x, cy: h.y, r: import_vector3.VECTOR_OVERLAY_THEME.handleRadius + (h.hovered ? 1 : 0), fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor })
@@ -551,8 +567,8 @@ function VectorCanvas({
551
567
  }
552
568
 
553
569
  // src/vector/VectorToolbar.tsx
554
- var import_react4 = __toESM(require("react"), 1);
555
- var import_vector6 = require("@pixerate/editor/vector");
570
+ var import_react5 = __toESM(require("react"), 1);
571
+ var import_vector7 = require("@pixerate/editor/vector");
556
572
 
557
573
  // src/vector/VectorEffectsPanel.tsx
558
574
  var import_vector4 = require("@pixerate/editor/vector");
@@ -694,16 +710,127 @@ function VectorEffectsPanel({ controller, className }) {
694
710
  ] });
695
711
  }
696
712
 
697
- // src/vector/VectorGradientEditor.tsx
713
+ // src/vector/VectorEnvelopePanel.tsx
714
+ var import_react4 = __toESM(require("react"), 1);
698
715
  var import_vector5 = require("@pixerate/editor/vector");
699
716
  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";
717
+ var buttonClass2 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40";
701
718
  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";
719
+ var selectClass2 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
720
+ var labelClass2 = "w-full truncate text-[11px] text-muted-foreground";
721
+ var SLIDERS = [
722
+ { key: "bend", label: "Bend %" },
723
+ { key: "horizontal", label: "Horizontal distortion %" },
724
+ { key: "vertical", label: "Vertical distortion %" }
725
+ ];
726
+ function VectorEnvelopePanel({ controller, className }) {
727
+ const state = useVectorEditorState(controller);
728
+ const panel = (0, import_vector5.getVectorEnvelopePanel)(state);
729
+ const [size, setSize] = import_react4.default.useState({ rows: 2, columns: 2 });
730
+ if (!panel) return null;
731
+ const env = panel.envelope;
732
+ const ids = panel.nodeId ? [panel.nodeId] : void 0;
733
+ const end = () => controller.endMergedEdit();
734
+ const set = (patch, merge = false) => controller.envelope(patch, { ids, merge });
735
+ const rows = env?.params.rows ?? size.rows;
736
+ const columns = env?.params.columns ?? size.columns;
737
+ const setCount = (key, raw) => {
738
+ const n = Math.round(parseFloat(raw));
739
+ if (!Number.isFinite(n) || n < 1 || n > 16) return;
740
+ if (env) set({ [key]: n });
741
+ else setSize({ ...size, [key]: n });
742
+ };
743
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-envelope": "", "aria-label": "Envelope", children: [
744
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
745
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Envelope" }),
746
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "data-vector-envelope-action": "warp", className: buttonClass2, title: "Make with Warp", "aria-label": "Make with Warp", disabled: !panel.canMake || !!env, onClick: () => set({ style: "arc", bend: 50 }), children: "Warp" }),
747
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
748
+ "button",
749
+ {
750
+ type: "button",
751
+ "data-vector-envelope-action": "mesh",
752
+ className: buttonClass2,
753
+ title: "Make with Mesh",
754
+ "aria-label": "Make with Mesh",
755
+ disabled: !panel.canMake || !!env,
756
+ onClick: () => set({ style: "mesh", rows: size.rows, columns: size.columns }),
757
+ children: "Mesh"
758
+ }
759
+ ),
760
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
761
+ "button",
762
+ {
763
+ type: "button",
764
+ "data-vector-envelope-action": "top",
765
+ className: buttonClass2,
766
+ title: "Make with Top Object",
767
+ "aria-label": "Make with Top Object",
768
+ disabled: !panel.canMakeWithTopObject,
769
+ onClick: () => controller.makeEnvelopeWithTopObject({ rows: size.rows, columns: size.columns }),
770
+ children: "Top"
771
+ }
772
+ ),
773
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "data-vector-envelope-action": "release", className: buttonClass2, title: "Release Envelope", "aria-label": "Release Envelope", disabled: !panel.canRelease, onClick: () => controller.releaseEnvelope(), children: "Release" }),
774
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("button", { type: "button", "data-vector-envelope-action": "expand", className: buttonClass2, title: "Expand Envelope", "aria-label": "Expand Envelope", disabled: !panel.canRelease, onClick: () => controller.expandAppearance(), children: "Expand" })
775
+ ] }),
776
+ env && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground", "data-vector-envelope-param": "style", children: [
777
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: labelClass2, children: "Warp" }),
778
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("select", { "aria-label": "Envelope warp", className: selectClass2, value: env.params.style, onChange: (e) => set({ style: e.target.value }), children: import_vector5.VECTOR_ENVELOPE_STYLE_OPTIONS.map((o) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: o.value, children: o.label }, o.value)) })
779
+ ] }),
780
+ env && env.params.style !== "mesh" && SLIDERS.map((s) => {
781
+ const value = env.params[s.key];
782
+ const onChange = (ev) => {
783
+ const n = parseFloat(ev.target.value);
784
+ if (Number.isFinite(n)) set({ [s.key]: n }, true);
785
+ };
786
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground", "data-vector-envelope-param": s.key, children: [
787
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: labelClass2, children: s.label }),
788
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
789
+ "input",
790
+ {
791
+ type: "range",
792
+ min: -100,
793
+ max: 100,
794
+ step: 1,
795
+ "aria-label": `Envelope ${s.label} slider`,
796
+ className: "h-4 min-w-0 flex-1 accent-primary",
797
+ value,
798
+ onChange,
799
+ onPointerUp: end,
800
+ onKeyUp: end,
801
+ onBlur: end
802
+ }
803
+ ),
804
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "number", min: -100, max: 100, step: 1, "aria-label": `Envelope ${s.label}`, className: numberClass2, value, onChange, onBlur: end })
805
+ ] }, s.key);
806
+ }),
807
+ env && env.params.style !== "mesh" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground", "data-vector-envelope-param": "orientation", children: [
808
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: labelClass2, children: "Orientation" }),
809
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("select", { "aria-label": "Envelope orientation", className: selectClass2, value: env.params.orientation, onChange: (e) => set({ orientation: e.target.value }), children: [
810
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "horizontal", children: "Horizontal" }),
811
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "vertical", children: "Vertical" })
812
+ ] })
813
+ ] }),
814
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-envelope-param": "mesh", children: [
815
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "min-w-0 flex-1 truncate text-[11px] text-muted-foreground", children: "Mesh rows \xD7 columns" }),
816
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "number", min: 1, max: 16, step: 1, "aria-label": "Envelope mesh rows", className: numberClass2, value: rows, onChange: (e) => setCount("rows", e.target.value) }),
817
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("input", { type: "number", min: 1, max: 16, step: 1, "aria-label": "Envelope mesh columns", className: numberClass2, value: columns, onChange: (e) => setCount("columns", e.target.value) })
818
+ ] }),
819
+ panel.count > 1 && !panel.canMakeWithTopObject && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Select one object to edit its envelope." }),
820
+ env && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Edit the mesh with the Envelope tool (U)." })
821
+ ] });
822
+ }
823
+
824
+ // src/vector/VectorGradientEditor.tsx
825
+ var import_vector6 = require("@pixerate/editor/vector");
826
+ var import_jsx_runtime5 = require("react/jsx-runtime");
827
+ var selectClass3 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
828
+ var numberClass3 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
829
+ var buttonClass3 = "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
830
  var percent = (v) => Math.round(v * 1e3) / 10;
704
831
  function VectorGradientEditor({ controller, className }) {
705
832
  const state = useVectorEditorState(controller);
706
- const ed = (0, import_vector5.getVectorGradientEditor)(state);
833
+ const ed = (0, import_vector6.getVectorGradientEditor)(state);
707
834
  if (!ed) return null;
708
835
  const g = ed.gradient;
709
836
  const sel = ed.selected;
@@ -711,26 +838,26 @@ function VectorGradientEditor({ controller, className }) {
711
838
  const setType = (value) => {
712
839
  if (value === "solid") {
713
840
  const first = ed.stops[0];
714
- controller.setStyle({ [ed.target]: (0, import_vector5.vectorPaintToHex)(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
841
+ controller.setStyle({ [ed.target]: (0, import_vector6.vectorPaintToHex)(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
715
842
  } else controller.setGradient({ type: value });
716
843
  };
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" })
844
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
845
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-1.5", children: [
846
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
847
+ ["fill", "stroke"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: buttonClass3, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
848
+ ed.mixed && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
722
849
  ] }),
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))
850
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-1.5", children: [
851
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
852
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("select", { "aria-label": "Gradient type", className: selectClass3, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
853
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: "solid", children: "Solid" }),
854
+ import_vector6.VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: t, children: import_vector6.VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
728
855
  ] }),
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" })
856
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: buttonClass3, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
730
857
  ] }),
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)(
858
+ g && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
859
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
860
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
734
861
  "div",
735
862
  {
736
863
  role: "button",
@@ -744,7 +871,7 @@ function VectorGradientEditor({ controller, className }) {
744
871
  }
745
872
  }
746
873
  ),
747
- ed.stops.map((s) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
874
+ ed.stops.map((s) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
748
875
  "button",
749
876
  {
750
877
  type: "button",
@@ -758,21 +885,21 @@ function VectorGradientEditor({ controller, className }) {
758
885
  s.index
759
886
  ))
760
887
  ] }),
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)(
888
+ sel && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
889
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
763
890
  "input",
764
891
  {
765
892
  type: "color",
766
893
  "aria-label": "Stop colour",
767
894
  className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
768
- value: (0, import_vector5.vectorPaintToHex)(sel.color) ?? "#000000",
895
+ value: (0, import_vector6.vectorPaintToHex)(sel.color) ?? "#000000",
769
896
  onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
770
897
  onBlur: end
771
898
  }
772
899
  ),
773
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
900
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
774
901
  "Opacity",
775
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
902
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
776
903
  "input",
777
904
  {
778
905
  type: "number",
@@ -780,7 +907,7 @@ function VectorGradientEditor({ controller, className }) {
780
907
  max: 100,
781
908
  step: 5,
782
909
  "aria-label": "Stop opacity",
783
- className: numberClass2,
910
+ className: numberClass3,
784
911
  value: percent(sel.opacity ?? 1),
785
912
  onChange: (e) => {
786
913
  const n = parseFloat(e.target.value);
@@ -789,9 +916,9 @@ function VectorGradientEditor({ controller, className }) {
789
916
  }
790
917
  )
791
918
  ] }),
792
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
919
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
793
920
  "At",
794
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
921
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
795
922
  "input",
796
923
  {
797
924
  type: "number",
@@ -799,7 +926,7 @@ function VectorGradientEditor({ controller, className }) {
799
926
  max: 100,
800
927
  step: 1,
801
928
  "aria-label": "Stop position",
802
- className: numberClass2,
929
+ className: numberClass3,
803
930
  value: percent(sel.offset),
804
931
  onChange: (e) => {
805
932
  const n = parseFloat(e.target.value);
@@ -808,9 +935,9 @@ function VectorGradientEditor({ controller, className }) {
808
935
  }
809
936
  )
810
937
  ] }),
811
- !sel.last && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
938
+ !sel.last && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
812
939
  "Midpoint",
813
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
940
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
814
941
  "input",
815
942
  {
816
943
  type: "number",
@@ -818,7 +945,7 @@ function VectorGradientEditor({ controller, className }) {
818
945
  max: 95,
819
946
  step: 1,
820
947
  "aria-label": "Stop midpoint",
821
- className: numberClass2,
948
+ className: numberClass3,
822
949
  value: percent(sel.midpoint ?? 0.5),
823
950
  onChange: (e) => {
824
951
  const n = parseFloat(e.target.value);
@@ -827,15 +954,15 @@ function VectorGradientEditor({ controller, className }) {
827
954
  }
828
955
  )
829
956
  ] }),
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" })
957
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { type: "button", className: buttonClass3, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
831
958
  ] }),
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)) })
959
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
960
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "w-16", children: "Colours" }),
961
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("select", { "aria-label": "Gradient interpolation", className: selectClass3, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: import_vector6.VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: i, children: import_vector6.VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
835
962
  ] }),
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)(
963
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
964
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "w-16", children: "Smooth" }),
965
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
839
966
  "input",
840
967
  {
841
968
  type: "range",
@@ -851,31 +978,32 @@ function VectorGradientEditor({ controller, className }) {
851
978
  }
852
979
  )
853
980
  ] }),
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)) })
981
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
982
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "w-16", children: "Spread" }),
983
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("select", { "aria-label": "Gradient spread", className: selectClass3, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: import_vector6.VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: sp, children: import_vector6.VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
857
984
  ] }),
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)) })
985
+ ed.target === "stroke" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
986
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "w-16", children: "On stroke" }),
987
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("select", { "aria-label": "Gradient on stroke", className: selectClass3, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: import_vector6.VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("option", { value: m, children: import_vector6.VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
861
988
  ] })
862
989
  ] })
863
990
  ] });
864
991
  }
865
992
 
866
993
  // src/vector/VectorToolbar.tsx
867
- var import_jsx_runtime5 = require("react/jsx-runtime");
994
+ var import_jsx_runtime6 = require("react/jsx-runtime");
868
995
  var VECTOR_SNAP_TITLE = "Snap to objects";
869
996
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
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";
997
+ var buttonClass4 = "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";
871
998
  function VectorToolbar({
872
999
  controller,
873
- tools = import_vector6.VECTOR_TOOLS,
1000
+ tools = import_vector7.VECTOR_TOOLS,
874
1001
  showHistory = true,
875
1002
  showArrange = true,
876
1003
  showSnap = true,
877
1004
  showPathEdit = true,
878
1005
  showBlend = true,
1006
+ showDistort = true,
879
1007
  className
880
1008
  }) {
881
1009
  const state = useVectorEditorState(controller);
@@ -883,34 +1011,34 @@ function VectorToolbar({
883
1011
  const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
884
1012
  const editing = state.editing;
885
1013
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
886
- const [live, setLive] = import_react4.default.useState(false);
1014
+ const [live, setLive] = import_react5.default.useState(false);
887
1015
  const booleanDisabled = live ? count < 1 : count < 2;
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)(
1016
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", className: buttonClass4, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
1017
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
1018
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
891
1019
  "button",
892
1020
  {
893
1021
  type: "button",
894
1022
  "data-vector-tool": tool,
895
- className: buttonClass3,
1023
+ className: buttonClass4,
896
1024
  "aria-pressed": state.tool === tool,
897
- title: `${(0, import_vector6.vectorToolLabel)(tool)} (${import_vector6.VECTOR_TOOL_SHORTCUTS[tool]})`,
898
- "aria-label": (0, import_vector6.vectorToolLabel)(tool),
1025
+ title: `${(0, import_vector7.vectorToolLabel)(tool)} (${import_vector7.VECTOR_TOOL_SHORTCUTS[tool]})`,
1026
+ "aria-label": (0, import_vector7.vectorToolLabel)(tool),
899
1027
  onClick: () => controller.setTool(tool),
900
- children: import_vector6.VECTOR_TOOL_GLYPHS[tool]
1028
+ children: import_vector7.VECTOR_TOOL_GLYPHS[tool]
901
1029
  },
902
1030
  tool
903
1031
  )) }),
904
- showHistory && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
1032
+ showHistory && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
905
1033
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
906
1034
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
907
1035
  ] }),
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)(
1036
+ showSnap && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
909
1037
  "button",
910
1038
  {
911
1039
  type: "button",
912
1040
  "data-vector-snap": "",
913
- className: buttonClass3,
1041
+ className: buttonClass4,
914
1042
  "aria-pressed": state.snap.objects,
915
1043
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
916
1044
  "aria-label": VECTOR_SNAP_TITLE,
@@ -918,13 +1046,13 @@ function VectorToolbar({
918
1046
  children: "Snap"
919
1047
  }
920
1048
  ) }),
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)(
1049
+ showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
1050
+ import_vector7.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
923
1051
  "button",
924
1052
  {
925
1053
  type: "button",
926
1054
  "data-vector-path-action": a.id,
927
- className: buttonClass3,
1055
+ className: buttonClass4,
928
1056
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
929
1057
  "aria-label": a.title,
930
1058
  disabled: !a.enabled(count, points),
@@ -933,12 +1061,12 @@ function VectorToolbar({
933
1061
  },
934
1062
  a.id
935
1063
  )),
936
- import_vector6.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1064
+ import_vector7.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
937
1065
  "button",
938
1066
  {
939
1067
  type: "button",
940
1068
  "data-vector-edit": t.key,
941
- className: buttonClass3,
1069
+ className: buttonClass4,
942
1070
  "aria-pressed": editing[t.key],
943
1071
  title: t.title,
944
1072
  "aria-label": t.title,
@@ -947,9 +1075,9 @@ function VectorToolbar({
947
1075
  },
948
1076
  t.key
949
1077
  )),
950
- state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
1078
+ state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
951
1079
  "Smoothing",
952
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1080
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
953
1081
  "input",
954
1082
  {
955
1083
  type: "range",
@@ -963,9 +1091,9 @@ function VectorToolbar({
963
1091
  }
964
1092
  )
965
1093
  ] }),
966
- state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
1094
+ state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
967
1095
  "Size",
968
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1096
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
969
1097
  "input",
970
1098
  {
971
1099
  type: "number",
@@ -981,29 +1109,42 @@ function VectorToolbar({
981
1109
  }
982
1110
  )
983
1111
  ] }),
984
- state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1112
+ state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
985
1113
  "button",
986
1114
  {
987
1115
  type: "button",
988
1116
  "data-vector-fill-gap": "",
989
- className: buttonClass3,
1117
+ className: buttonClass4,
990
1118
  "aria-pressed": editing.fillGap > 0,
991
1119
  title: "Close small gaps between path ends when filling",
992
1120
  "aria-label": "Close gaps",
993
- onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector6.VECTOR_FILL_GAP }),
1121
+ onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector7.VECTOR_FILL_GAP }),
994
1122
  children: "Gaps"
995
1123
  }
996
1124
  )
997
1125
  ] }),
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)(
1126
+ showDistort && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1127
+ "button",
1128
+ {
1129
+ type: "button",
1130
+ "data-vector-free-distort": "",
1131
+ className: buttonClass4,
1132
+ "aria-pressed": state.freeDistort,
1133
+ title: `Free Distort: drag a corner of the selection box to distort in perspective (Shift+Alt+${modKeyLabel()}: symmetric perspective; ${modKeyLabel()}-drag a side to skew)`,
1134
+ "aria-label": "Free Distort",
1135
+ onClick: () => controller.setFreeDistort(!state.freeDistort),
1136
+ children: "Distort"
1137
+ }
1138
+ ) }),
1139
+ showBlend && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(VectorBlendControls, { controller }),
1140
+ showArrange && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1141
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
1142
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1002
1143
  "button",
1003
1144
  {
1004
1145
  type: "button",
1005
1146
  "data-vector-live-boolean": "",
1006
- className: buttonClass3,
1147
+ className: buttonClass4,
1007
1148
  "aria-pressed": live,
1008
1149
  title: "Live booleans: keep the shapes editable",
1009
1150
  "aria-label": "Live booleans",
@@ -1018,12 +1159,12 @@ function VectorToolbar({
1018
1159
  act("G", "Group", () => controller.group(), count < 1),
1019
1160
  act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
1020
1161
  ] }),
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)(
1162
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: import_vector7.VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1022
1163
  "button",
1023
1164
  {
1024
1165
  type: "button",
1025
1166
  "data-vector-pathfinder": a.id,
1026
- className: buttonClass3,
1167
+ className: buttonClass4,
1027
1168
  title: a.title,
1028
1169
  "aria-label": a.title,
1029
1170
  disabled: count < a.min,
@@ -1032,7 +1173,7 @@ function VectorToolbar({
1032
1173
  },
1033
1174
  a.id
1034
1175
  )) }),
1035
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
1176
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
1036
1177
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
1037
1178
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
1038
1179
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -1047,22 +1188,22 @@ function VectorToolbar({
1047
1188
  }
1048
1189
  function VectorBlendControls({ controller }) {
1049
1190
  const state = useVectorEditorState(controller);
1050
- const blend = (0, import_vector6.getVectorBlendControls)(state);
1191
+ const blend = (0, import_vector7.getVectorBlendControls)(state);
1051
1192
  const o = blend.options;
1052
1193
  const selected = blend.ids.length > 0;
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)(
1194
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
1195
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass4, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
1196
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1056
1197
  "select",
1057
1198
  {
1058
1199
  "aria-label": "Blend spacing",
1059
1200
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
1060
1201
  value: o.spacing,
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))
1202
+ onChange: (e) => (0, import_vector7.applyVectorBlendOptions)(controller, { spacing: e.target.value }),
1203
+ children: import_vector7.VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: sp, children: import_vector7.VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
1063
1204
  }
1064
1205
  ),
1065
- o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1206
+ o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1066
1207
  "input",
1067
1208
  {
1068
1209
  type: "number",
@@ -1074,29 +1215,29 @@ function VectorBlendControls({ controller }) {
1074
1215
  onChange: (e) => {
1075
1216
  const n = parseFloat(e.target.value);
1076
1217
  if (!Number.isFinite(n) || n <= 0) return;
1077
- (0, import_vector6.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
1218
+ (0, import_vector7.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
1078
1219
  }
1079
1220
  }
1080
1221
  ),
1081
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1222
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1082
1223
  "button",
1083
1224
  {
1084
1225
  type: "button",
1085
1226
  "data-vector-blend-action": "orient",
1086
- className: buttonClass3,
1227
+ className: buttonClass4,
1087
1228
  "aria-pressed": o.orientToSpine,
1088
1229
  title: "Orient steps to the spine",
1089
1230
  "aria-label": "Orient to spine",
1090
- onClick: () => (0, import_vector6.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
1231
+ onClick: () => (0, import_vector7.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
1091
1232
  children: "\u293F"
1092
1233
  }
1093
1234
  ),
1094
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1235
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1095
1236
  "button",
1096
1237
  {
1097
1238
  type: "button",
1098
1239
  "data-vector-blend-action": "spine",
1099
- className: buttonClass3,
1240
+ className: buttonClass4,
1100
1241
  title: "Replace Spine (select a blend and a path)",
1101
1242
  "aria-label": "Replace Spine",
1102
1243
  disabled: !blend.canReplaceSpine,
@@ -1104,15 +1245,15 @@ function VectorBlendControls({ controller }) {
1104
1245
  children: "\u2307"
1105
1246
  }
1106
1247
  ),
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" })
1248
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass4, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
1249
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass4, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
1109
1250
  ] });
1110
1251
  }
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";
1252
+ var selectClass4 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1253
+ var numberClass4 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
1113
1254
  function VectorStylePanel({ controller, className }) {
1114
1255
  const state = useVectorEditorState(controller);
1115
- const style = (0, import_vector6.getVectorSelectionStyle)(state);
1256
+ const style = (0, import_vector7.getVectorSelectionStyle)(state);
1116
1257
  const d = state.defaults;
1117
1258
  const source = style ?? {
1118
1259
  fill: d.fill ?? null,
@@ -1125,7 +1266,7 @@ function VectorStylePanel({ controller, className }) {
1125
1266
  arrowAlign: d.arrowAlign ?? "extend",
1126
1267
  startArrowScale: d.startArrowScale ?? 1,
1127
1268
  endArrowScale: d.endArrowScale ?? 1,
1128
- widthProfile: (0, import_vector6.widthProfilePresetId)(d.strokeWidthProfile),
1269
+ widthProfile: (0, import_vector7.widthProfilePresetId)(d.strokeWidthProfile),
1129
1270
  cornerRadius: d.cornerRadius,
1130
1271
  cornerKind: d.cornerKind ?? "round",
1131
1272
  corners: 1,
@@ -1138,19 +1279,19 @@ function VectorStylePanel({ controller, className }) {
1138
1279
  };
1139
1280
  const setProfile = (id) => {
1140
1281
  if (style) controller.setWidthProfile(id);
1141
- else controller.setDefaults({ strokeWidthProfile: (0, import_vector6.widthProfilePreset)(id)?.length ? (0, import_vector6.widthProfilePreset)(id) : null });
1282
+ else controller.setDefaults({ strokeWidthProfile: (0, import_vector7.widthProfilePreset)(id)?.length ? (0, import_vector7.widthProfilePreset)(id) : null });
1142
1283
  };
1143
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
1284
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
1144
1285
  const arrowRow = (end, label) => {
1145
1286
  const key = end === "start" ? "startArrow" : "endArrow";
1146
1287
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
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))
1288
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
1289
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
1290
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("select", { "aria-label": `${label} arrowhead`, className: selectClass4, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
1291
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "", children: "None" }),
1292
+ import_vector7.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: h, children: import_vector7.VECTOR_ARROWHEAD_LABELS[h] }, h))
1152
1293
  ] }),
1153
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1294
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1154
1295
  "input",
1155
1296
  {
1156
1297
  type: "number",
@@ -1158,7 +1299,7 @@ function VectorStylePanel({ controller, className }) {
1158
1299
  step: 0.25,
1159
1300
  "aria-label": `${label} arrowhead scale`,
1160
1301
  title: "Scale (\xD7 stroke width)",
1161
- className: numberClass3,
1302
+ className: numberClass4,
1162
1303
  value: source[scaleKey],
1163
1304
  onChange: (e) => {
1164
1305
  const n = parseFloat(e.target.value);
@@ -1174,14 +1315,14 @@ function VectorStylePanel({ controller, className }) {
1174
1315
  };
1175
1316
  const paintRow = (key, label) => {
1176
1317
  const paint = source[key];
1177
- const hex = (0, import_vector6.vectorPaintToHex)(paint);
1318
+ const hex = (0, import_vector7.vectorPaintToHex)(paint);
1178
1319
  const on = paint !== null && paint !== "none";
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 }) }),
1320
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
1321
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
1322
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
1182
1323
  label
1183
1324
  ] }),
1184
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1325
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1185
1326
  "input",
1186
1327
  {
1187
1328
  type: "color",
@@ -1192,18 +1333,18 @@ function VectorStylePanel({ controller, className }) {
1192
1333
  onChange: (e) => apply({ [key]: e.target.value })
1193
1334
  }
1194
1335
  ),
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" })
1336
+ on && !hex && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
1337
+ style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1197
1338
  ] });
1198
1339
  };
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" }),
1340
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
1341
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
1201
1342
  paintRow("fill", "Fill"),
1202
1343
  paintRow("stroke", "Stroke"),
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)(
1344
+ style && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(VectorGradientEditor, { controller }),
1345
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1346
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "w-16", children: "Width" }),
1347
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1207
1348
  "input",
1208
1349
  {
1209
1350
  type: "number",
@@ -1219,33 +1360,33 @@ function VectorStylePanel({ controller, className }) {
1219
1360
  }
1220
1361
  )
1221
1362
  ] }),
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)) }),
1363
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
1364
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "w-16", children: "Align" }),
1365
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { "aria-label": "Stroke alignment", className: selectClass4, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(import_vector7.VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: a, children: import_vector7.VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
1225
1366
  mixed("strokeAlign")
1226
1367
  ] }),
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)(
1368
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
1369
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "w-16", children: "Profile" }),
1370
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
1230
1371
  "select",
1231
1372
  {
1232
1373
  "aria-label": "Width profile",
1233
- className: selectClass3,
1374
+ className: selectClass4,
1234
1375
  value: source.widthProfile,
1235
1376
  onChange: (e) => {
1236
1377
  if (e.target.value !== "custom") setProfile(e.target.value);
1237
1378
  },
1238
1379
  children: [
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" })
1380
+ import_vector7.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: p.id, children: p.label }, p.id)),
1381
+ source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
1241
1382
  ]
1242
1383
  }
1243
1384
  ),
1244
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1385
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1245
1386
  "button",
1246
1387
  {
1247
1388
  type: "button",
1248
- className: buttonClass3,
1389
+ className: buttonClass4,
1249
1390
  title: "Reset to uniform width",
1250
1391
  "aria-label": "Reset to uniform width",
1251
1392
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -1255,27 +1396,27 @@ function VectorStylePanel({ controller, className }) {
1255
1396
  ),
1256
1397
  mixed("widthProfile")
1257
1398
  ] }),
1258
- source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1399
+ source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1259
1400
  arrowRow("start", "Start"),
1260
1401
  arrowRow("end", "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" })
1402
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
1403
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "w-16", children: "Heads" }),
1404
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass4, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
1405
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "extend", children: "Tip past end" }),
1406
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: "tip", children: "Tip at end" })
1266
1407
  ] })
1267
1408
  ] })
1268
1409
  ] }),
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)(
1410
+ source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
1411
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
1412
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1272
1413
  "input",
1273
1414
  {
1274
1415
  type: "number",
1275
1416
  min: 0,
1276
1417
  step: 1,
1277
1418
  "aria-label": "Corner radius",
1278
- className: numberClass3,
1419
+ className: numberClass4,
1279
1420
  value: Math.round(source.cornerRadius * 100) / 100,
1280
1421
  onChange: (e) => {
1281
1422
  const n = parseFloat(e.target.value);
@@ -1283,12 +1424,12 @@ function VectorStylePanel({ controller, className }) {
1283
1424
  }
1284
1425
  }
1285
1426
  ),
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)) }),
1427
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("select", { "aria-label": "Corner kind", className: selectClass4, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: import_vector7.VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("option", { value: k, children: import_vector7.VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
1287
1428
  mixed("cornerRadius")
1288
1429
  ] }),
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)(
1430
+ style && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1431
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "w-16", children: "Opacity" }),
1432
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1292
1433
  "input",
1293
1434
  {
1294
1435
  type: "range",
@@ -1302,18 +1443,19 @@ function VectorStylePanel({ controller, className }) {
1302
1443
  }
1303
1444
  )
1304
1445
  ] }),
1305
- style && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(VectorEffectsPanel, { controller })
1446
+ style && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(VectorEffectsPanel, { controller }),
1447
+ style && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(VectorEnvelopePanel, { controller })
1306
1448
  ] });
1307
1449
  }
1308
1450
 
1309
1451
  // src/vector/VectorEditor.tsx
1310
- var import_jsx_runtime6 = require("react/jsx-runtime");
1452
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1311
1453
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
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" })
1454
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
1455
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VectorToolbar, { controller, tools }),
1456
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
1457
+ /* @__PURE__ */ (0, import_jsx_runtime7.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_runtime7.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
1458
+ showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
1317
1459
  ] })
1318
1460
  ] });
1319
1461
  }
@@ -1322,6 +1464,7 @@ function VectorEditor({ controller, tools, showStylePanel = true, height = 520,
1322
1464
  VectorCanvas,
1323
1465
  VectorEditor,
1324
1466
  VectorEffectsPanel,
1467
+ VectorEnvelopePanel,
1325
1468
  VectorGradientAnnotator,
1326
1469
  VectorGradientDefs,
1327
1470
  VectorGradientEditor,