@pixerate/editor-react 0.32.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,7 +33,10 @@ __export(vector_exports, {
33
33
  VectorCanvas: () => VectorCanvas,
34
34
  VectorEditor: () => VectorEditor,
35
35
  VectorEffectsPanel: () => VectorEffectsPanel,
36
+ VectorEnvelopePanel: () => VectorEnvelopePanel,
37
+ VectorGradientAnnotator: () => VectorGradientAnnotator,
36
38
  VectorGradientDefs: () => VectorGradientDefs,
39
+ VectorGradientEditor: () => VectorGradientEditor,
37
40
  VectorSceneNodes: () => VectorSceneNodes,
38
41
  VectorStylePanel: () => VectorStylePanel,
39
42
  VectorToolbar: () => VectorToolbar,
@@ -176,12 +179,50 @@ function VectorWidthPointEditor({ controller, edit, accentColor = import_vector2
176
179
 
177
180
  // src/vector/VectorCanvas.tsx
178
181
  var import_jsx_runtime2 = require("react/jsx-runtime");
182
+ function GradientElement({ def }) {
183
+ const stops = def.stops.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("stop", { offset: s.offset, stopColor: s.stopColor, stopOpacity: s.stopOpacity }, i));
184
+ 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 });
185
+ }
179
186
  function VectorGradientDefs({ defs }) {
180
187
  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
- }) });
188
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: defs.map(
189
+ (def) => def.type === "pattern" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("pattern", { id: def.id, ...def.attrs, children: [
190
+ def.gradients.map((g) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GradientElement, { def: g }, g.id)),
191
+ def.tiles.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: t.d, fill: t.fill }, i))
192
+ ] }, def.id) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(GradientElement, { def }, def.id)
193
+ ) });
194
+ }
195
+ function VectorGradientAnnotator({ annotator: g, accentColor = import_vector3.VECTOR_OVERLAY_THEME.accent }) {
196
+ const r = import_vector3.VECTOR_OVERLAY_THEME.handleRadius + 1.5;
197
+ 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 });
198
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-gradient-annotator": g.type, children: [
199
+ g.extent && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { "data-vector-gradient-extent": "", d: g.extent, fill: "none", stroke: accentColor, strokeDasharray: "4 3" }),
200
+ /* @__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 }),
201
+ /* @__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 }),
202
+ 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" }),
203
+ ring(g.start, "start"),
204
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
205
+ "rect",
206
+ {
207
+ "data-vector-gradient-handle": g.end.handle,
208
+ x: g.end.x - r,
209
+ y: g.end.y - r,
210
+ width: 2 * r,
211
+ height: 2 * r,
212
+ fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill,
213
+ stroke: accentColor,
214
+ strokeWidth: g.end.hovered ? 2 : 1
215
+ }
216
+ ),
217
+ g.aspect && ring(g.aspect, "aspect", import_vector3.VECTOR_OVERLAY_THEME.handleRadius),
218
+ 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 }) }),
219
+ 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)),
220
+ 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: [
221
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: s.barX, y1: s.barY, x2: s.x, y2: s.y, stroke: accentColor }),
222
+ /* @__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 }),
223
+ /* @__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 })
224
+ ] }, s.index))
225
+ ] });
185
226
  }
186
227
  function VectorSceneNodes({ nodes }) {
187
228
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: nodes.map(
@@ -363,13 +404,28 @@ function VectorCanvas({
363
404
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-overlay": "", pointerEvents: "none", fill: "none", children: [
364
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)),
365
406
  overlay.penPreview && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: overlay.penPreview, stroke: accentColor, strokeDasharray: "4 3" }),
366
- 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: [
367
408
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: box.x, y: box.y, width: box.width, height: box.height, stroke: accentColor }),
368
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 }),
369
410
  box.handles.map(
370
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)
371
412
  )
372
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
+ ] }),
373
429
  overlay.handles.map((h) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-handle": h.ref.handle, children: [
374
430
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: h.anchorX, y1: h.anchorY, x2: h.x, y2: h.y, stroke: accentColor }),
375
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 })
@@ -405,6 +461,7 @@ function VectorCanvas({
405
461
  },
406
462
  `blend-anchor:${i}`
407
463
  )),
464
+ overlay.gradient && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorGradientAnnotator, { annotator: overlay.gradient, accentColor }),
408
465
  overlay.widthSpot && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-width-spot": "", children: [
409
466
  /* @__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
467
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: (0, import_vector3.vectorDiamondPath)(overlay.widthSpot.x, overlay.widthSpot.y), fill: "none", stroke: accentColor })
@@ -510,8 +567,8 @@ function VectorCanvas({
510
567
  }
511
568
 
512
569
  // src/vector/VectorToolbar.tsx
513
- var import_react4 = __toESM(require("react"), 1);
514
- var import_vector5 = require("@pixerate/editor/vector");
570
+ var import_react5 = __toESM(require("react"), 1);
571
+ var import_vector7 = require("@pixerate/editor/vector");
515
572
 
516
573
  // src/vector/VectorEffectsPanel.tsx
517
574
  var import_vector4 = require("@pixerate/editor/vector");
@@ -653,19 +710,300 @@ function VectorEffectsPanel({ controller, className }) {
653
710
  ] });
654
711
  }
655
712
 
656
- // src/vector/VectorToolbar.tsx
713
+ // src/vector/VectorEnvelopePanel.tsx
714
+ var import_react4 = __toESM(require("react"), 1);
715
+ var import_vector5 = require("@pixerate/editor/vector");
657
716
  var import_jsx_runtime4 = require("react/jsx-runtime");
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";
718
+ var numberClass2 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
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";
830
+ var percent = (v) => Math.round(v * 1e3) / 10;
831
+ function VectorGradientEditor({ controller, className }) {
832
+ const state = useVectorEditorState(controller);
833
+ const ed = (0, import_vector6.getVectorGradientEditor)(state);
834
+ if (!ed) return null;
835
+ const g = ed.gradient;
836
+ const sel = ed.selected;
837
+ const end = () => controller.endMergedEdit();
838
+ const setType = (value) => {
839
+ if (value === "solid") {
840
+ const first = ed.stops[0];
841
+ controller.setStyle({ [ed.target]: (0, import_vector6.vectorPaintToHex)(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
842
+ } else controller.setGradient({ type: value });
843
+ };
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" })
849
+ ] }),
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))
855
+ ] }),
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" })
857
+ ] }),
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)(
861
+ "div",
862
+ {
863
+ role: "button",
864
+ tabIndex: -1,
865
+ "aria-label": "Add gradient stop",
866
+ className: "absolute inset-x-1.5 top-0 h-4 cursor-copy rounded border border-border",
867
+ style: { backgroundImage: `${ed.preview}, repeating-conic-gradient(#ddd 0% 25%, #fff 0% 50%)`, backgroundSize: "auto, 8px 8px" },
868
+ onClick: (e) => {
869
+ const r = e.currentTarget.getBoundingClientRect();
870
+ controller.addGradientStop(r.width > 0 ? Math.min(1, Math.max(0, (e.clientX - r.left) / r.width)) : 0.5);
871
+ }
872
+ }
873
+ ),
874
+ ed.stops.map((s) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
875
+ "button",
876
+ {
877
+ type: "button",
878
+ "data-vector-gradient-stop": s.index,
879
+ "aria-label": `Gradient stop ${s.index + 1} at ${percent(s.offset)}%`,
880
+ "aria-pressed": s.selected,
881
+ 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",
882
+ style: { left: `calc(6px + (100% - 12px) * ${s.offset})`, background: s.color, opacity: 0.4 + 0.6 * (s.opacity ?? 1) },
883
+ onClick: () => controller.selectGradientStop(s.selected ? null : s.index)
884
+ },
885
+ s.index
886
+ ))
887
+ ] }),
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)(
890
+ "input",
891
+ {
892
+ type: "color",
893
+ "aria-label": "Stop colour",
894
+ className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
895
+ value: (0, import_vector6.vectorPaintToHex)(sel.color) ?? "#000000",
896
+ onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
897
+ onBlur: end
898
+ }
899
+ ),
900
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
901
+ "Opacity",
902
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
903
+ "input",
904
+ {
905
+ type: "number",
906
+ min: 0,
907
+ max: 100,
908
+ step: 5,
909
+ "aria-label": "Stop opacity",
910
+ className: numberClass3,
911
+ value: percent(sel.opacity ?? 1),
912
+ onChange: (e) => {
913
+ const n = parseFloat(e.target.value);
914
+ if (Number.isFinite(n)) controller.setGradientStop(sel.index, { opacity: n / 100 });
915
+ }
916
+ }
917
+ )
918
+ ] }),
919
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
920
+ "At",
921
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
922
+ "input",
923
+ {
924
+ type: "number",
925
+ min: 0,
926
+ max: 100,
927
+ step: 1,
928
+ "aria-label": "Stop position",
929
+ className: numberClass3,
930
+ value: percent(sel.offset),
931
+ onChange: (e) => {
932
+ const n = parseFloat(e.target.value);
933
+ if (Number.isFinite(n)) controller.setGradientStop(sel.index, { offset: n / 100 });
934
+ }
935
+ }
936
+ )
937
+ ] }),
938
+ !sel.last && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
939
+ "Midpoint",
940
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
941
+ "input",
942
+ {
943
+ type: "number",
944
+ min: 5,
945
+ max: 95,
946
+ step: 1,
947
+ "aria-label": "Stop midpoint",
948
+ className: numberClass3,
949
+ value: percent(sel.midpoint ?? 0.5),
950
+ onChange: (e) => {
951
+ const n = parseFloat(e.target.value);
952
+ if (Number.isFinite(n)) controller.setGradientStop(sel.index, { midpoint: n / 100 });
953
+ }
954
+ }
955
+ )
956
+ ] }),
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" })
958
+ ] }),
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)) })
962
+ ] }),
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)(
966
+ "input",
967
+ {
968
+ type: "range",
969
+ min: 0,
970
+ max: 1,
971
+ step: 0.05,
972
+ "aria-label": "Gradient smoothness",
973
+ className: "h-4 flex-1 accent-primary",
974
+ value: ed.smoothness,
975
+ onChange: (e) => controller.setGradient({ smoothness: parseFloat(e.target.value) }, { merge: "gradient-smoothness" }),
976
+ onPointerUp: end,
977
+ onBlur: end
978
+ }
979
+ )
980
+ ] }),
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)) })
984
+ ] }),
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)) })
988
+ ] })
989
+ ] })
990
+ ] });
991
+ }
992
+
993
+ // src/vector/VectorToolbar.tsx
994
+ var import_jsx_runtime6 = require("react/jsx-runtime");
658
995
  var VECTOR_SNAP_TITLE = "Snap to objects";
659
996
  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";
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";
661
998
  function VectorToolbar({
662
999
  controller,
663
- tools = import_vector5.VECTOR_TOOLS,
1000
+ tools = import_vector7.VECTOR_TOOLS,
664
1001
  showHistory = true,
665
1002
  showArrange = true,
666
1003
  showSnap = true,
667
1004
  showPathEdit = true,
668
1005
  showBlend = true,
1006
+ showDistort = true,
669
1007
  className
670
1008
  }) {
671
1009
  const state = useVectorEditorState(controller);
@@ -673,34 +1011,34 @@ function VectorToolbar({
673
1011
  const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
674
1012
  const editing = state.editing;
675
1013
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
676
- const [live, setLive] = import_react4.default.useState(false);
1014
+ const [live, setLive] = import_react5.default.useState(false);
677
1015
  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)(
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)(
681
1019
  "button",
682
1020
  {
683
1021
  type: "button",
684
1022
  "data-vector-tool": tool,
685
- className: buttonClass2,
1023
+ className: buttonClass4,
686
1024
  "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),
1025
+ title: `${(0, import_vector7.vectorToolLabel)(tool)} (${import_vector7.VECTOR_TOOL_SHORTCUTS[tool]})`,
1026
+ "aria-label": (0, import_vector7.vectorToolLabel)(tool),
689
1027
  onClick: () => controller.setTool(tool),
690
- children: import_vector5.VECTOR_TOOL_GLYPHS[tool]
1028
+ children: import_vector7.VECTOR_TOOL_GLYPHS[tool]
691
1029
  },
692
1030
  tool
693
1031
  )) }),
694
- showHistory && /* @__PURE__ */ (0, import_jsx_runtime4.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: [
695
1033
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
696
1034
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
697
1035
  ] }),
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)(
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)(
699
1037
  "button",
700
1038
  {
701
1039
  type: "button",
702
1040
  "data-vector-snap": "",
703
- className: buttonClass2,
1041
+ className: buttonClass4,
704
1042
  "aria-pressed": state.snap.objects,
705
1043
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
706
1044
  "aria-label": VECTOR_SNAP_TITLE,
@@ -708,13 +1046,13 @@ function VectorToolbar({
708
1046
  children: "Snap"
709
1047
  }
710
1048
  ) }),
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)(
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)(
713
1051
  "button",
714
1052
  {
715
1053
  type: "button",
716
1054
  "data-vector-path-action": a.id,
717
- className: buttonClass2,
1055
+ className: buttonClass4,
718
1056
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
719
1057
  "aria-label": a.title,
720
1058
  disabled: !a.enabled(count, points),
@@ -723,12 +1061,12 @@ function VectorToolbar({
723
1061
  },
724
1062
  a.id
725
1063
  )),
726
- import_vector5.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1064
+ import_vector7.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
727
1065
  "button",
728
1066
  {
729
1067
  type: "button",
730
1068
  "data-vector-edit": t.key,
731
- className: buttonClass2,
1069
+ className: buttonClass4,
732
1070
  "aria-pressed": editing[t.key],
733
1071
  title: t.title,
734
1072
  "aria-label": t.title,
@@ -737,9 +1075,9 @@ function VectorToolbar({
737
1075
  },
738
1076
  t.key
739
1077
  )),
740
- state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime4.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: [
741
1079
  "Smoothing",
742
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1080
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
743
1081
  "input",
744
1082
  {
745
1083
  type: "range",
@@ -753,9 +1091,9 @@ function VectorToolbar({
753
1091
  }
754
1092
  )
755
1093
  ] }),
756
- state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime4.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: [
757
1095
  "Size",
758
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1096
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
759
1097
  "input",
760
1098
  {
761
1099
  type: "number",
@@ -771,29 +1109,42 @@ function VectorToolbar({
771
1109
  }
772
1110
  )
773
1111
  ] }),
774
- state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1112
+ state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
775
1113
  "button",
776
1114
  {
777
1115
  type: "button",
778
1116
  "data-vector-fill-gap": "",
779
- className: buttonClass2,
1117
+ className: buttonClass4,
780
1118
  "aria-pressed": editing.fillGap > 0,
781
1119
  title: "Close small gaps between path ends when filling",
782
1120
  "aria-label": "Close gaps",
783
- onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector5.VECTOR_FILL_GAP }),
1121
+ onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector7.VECTOR_FILL_GAP }),
784
1122
  children: "Gaps"
785
1123
  }
786
1124
  )
787
1125
  ] }),
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)(
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)(
792
1143
  "button",
793
1144
  {
794
1145
  type: "button",
795
1146
  "data-vector-live-boolean": "",
796
- className: buttonClass2,
1147
+ className: buttonClass4,
797
1148
  "aria-pressed": live,
798
1149
  title: "Live booleans: keep the shapes editable",
799
1150
  "aria-label": "Live booleans",
@@ -808,12 +1159,12 @@ function VectorToolbar({
808
1159
  act("G", "Group", () => controller.group(), count < 1),
809
1160
  act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
810
1161
  ] }),
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)(
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)(
812
1163
  "button",
813
1164
  {
814
1165
  type: "button",
815
1166
  "data-vector-pathfinder": a.id,
816
- className: buttonClass2,
1167
+ className: buttonClass4,
817
1168
  title: a.title,
818
1169
  "aria-label": a.title,
819
1170
  disabled: count < a.min,
@@ -822,7 +1173,7 @@ function VectorToolbar({
822
1173
  },
823
1174
  a.id
824
1175
  )) }),
825
- /* @__PURE__ */ (0, import_jsx_runtime4.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: [
826
1177
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
827
1178
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
828
1179
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -837,22 +1188,22 @@ function VectorToolbar({
837
1188
  }
838
1189
  function VectorBlendControls({ controller }) {
839
1190
  const state = useVectorEditorState(controller);
840
- const blend = (0, import_vector5.getVectorBlendControls)(state);
1191
+ const blend = (0, import_vector7.getVectorBlendControls)(state);
841
1192
  const o = blend.options;
842
1193
  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)(
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)(
846
1197
  "select",
847
1198
  {
848
1199
  "aria-label": "Blend spacing",
849
1200
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
850
1201
  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))
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))
853
1204
  }
854
1205
  ),
855
- o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1206
+ o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
856
1207
  "input",
857
1208
  {
858
1209
  type: "number",
@@ -864,29 +1215,29 @@ function VectorBlendControls({ controller }) {
864
1215
  onChange: (e) => {
865
1216
  const n = parseFloat(e.target.value);
866
1217
  if (!Number.isFinite(n) || n <= 0) return;
867
- (0, import_vector5.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
1218
+ (0, import_vector7.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
868
1219
  }
869
1220
  }
870
1221
  ),
871
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1222
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
872
1223
  "button",
873
1224
  {
874
1225
  type: "button",
875
1226
  "data-vector-blend-action": "orient",
876
- className: buttonClass2,
1227
+ className: buttonClass4,
877
1228
  "aria-pressed": o.orientToSpine,
878
1229
  title: "Orient steps to the spine",
879
1230
  "aria-label": "Orient to spine",
880
- onClick: () => (0, import_vector5.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
1231
+ onClick: () => (0, import_vector7.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
881
1232
  children: "\u293F"
882
1233
  }
883
1234
  ),
884
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1235
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
885
1236
  "button",
886
1237
  {
887
1238
  type: "button",
888
1239
  "data-vector-blend-action": "spine",
889
- className: buttonClass2,
1240
+ className: buttonClass4,
890
1241
  title: "Replace Spine (select a blend and a path)",
891
1242
  "aria-label": "Replace Spine",
892
1243
  disabled: !blend.canReplaceSpine,
@@ -894,15 +1245,15 @@ function VectorBlendControls({ controller }) {
894
1245
  children: "\u2307"
895
1246
  }
896
1247
  ),
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" })
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" })
899
1250
  ] });
900
1251
  }
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";
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";
903
1254
  function VectorStylePanel({ controller, className }) {
904
1255
  const state = useVectorEditorState(controller);
905
- const style = (0, import_vector5.getVectorSelectionStyle)(state);
1256
+ const style = (0, import_vector7.getVectorSelectionStyle)(state);
906
1257
  const d = state.defaults;
907
1258
  const source = style ?? {
908
1259
  fill: d.fill ?? null,
@@ -915,7 +1266,7 @@ function VectorStylePanel({ controller, className }) {
915
1266
  arrowAlign: d.arrowAlign ?? "extend",
916
1267
  startArrowScale: d.startArrowScale ?? 1,
917
1268
  endArrowScale: d.endArrowScale ?? 1,
918
- widthProfile: (0, import_vector5.widthProfilePresetId)(d.strokeWidthProfile),
1269
+ widthProfile: (0, import_vector7.widthProfilePresetId)(d.strokeWidthProfile),
919
1270
  cornerRadius: d.cornerRadius,
920
1271
  cornerKind: d.cornerKind ?? "round",
921
1272
  corners: 1,
@@ -928,19 +1279,19 @@ function VectorStylePanel({ controller, className }) {
928
1279
  };
929
1280
  const setProfile = (id) => {
930
1281
  if (style) controller.setWidthProfile(id);
931
- else controller.setDefaults({ strokeWidthProfile: (0, import_vector5.widthProfilePreset)(id)?.length ? (0, import_vector5.widthProfilePreset)(id) : null });
1282
+ else controller.setDefaults({ strokeWidthProfile: (0, import_vector7.widthProfilePreset)(id)?.length ? (0, import_vector7.widthProfilePreset)(id) : null });
932
1283
  };
933
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime4.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;
934
1285
  const arrowRow = (end, label) => {
935
1286
  const key = end === "start" ? "startArrow" : "endArrow";
936
1287
  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))
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))
942
1293
  ] }),
943
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1294
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
944
1295
  "input",
945
1296
  {
946
1297
  type: "number",
@@ -948,7 +1299,7 @@ function VectorStylePanel({ controller, className }) {
948
1299
  step: 0.25,
949
1300
  "aria-label": `${label} arrowhead scale`,
950
1301
  title: "Scale (\xD7 stroke width)",
951
- className: numberClass2,
1302
+ className: numberClass4,
952
1303
  value: source[scaleKey],
953
1304
  onChange: (e) => {
954
1305
  const n = parseFloat(e.target.value);
@@ -964,14 +1315,14 @@ function VectorStylePanel({ controller, className }) {
964
1315
  };
965
1316
  const paintRow = (key, label) => {
966
1317
  const paint = source[key];
967
- const hex = (0, import_vector5.vectorPaintToHex)(paint);
1318
+ const hex = (0, import_vector7.vectorPaintToHex)(paint);
968
1319
  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 }) }),
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 }) }),
972
1323
  label
973
1324
  ] }),
974
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1325
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
975
1326
  "input",
976
1327
  {
977
1328
  type: "color",
@@ -982,17 +1333,18 @@ function VectorStylePanel({ controller, className }) {
982
1333
  onChange: (e) => apply({ [key]: e.target.value })
983
1334
  }
984
1335
  ),
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" })
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" })
987
1338
  ] });
988
1339
  };
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" }),
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" }),
991
1342
  paintRow("fill", "Fill"),
992
1343
  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)(
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)(
996
1348
  "input",
997
1349
  {
998
1350
  type: "number",
@@ -1008,33 +1360,33 @@ function VectorStylePanel({ controller, className }) {
1008
1360
  }
1009
1361
  )
1010
1362
  ] }),
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)) }),
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)) }),
1014
1366
  mixed("strokeAlign")
1015
1367
  ] }),
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)(
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)(
1019
1371
  "select",
1020
1372
  {
1021
1373
  "aria-label": "Width profile",
1022
- className: selectClass2,
1374
+ className: selectClass4,
1023
1375
  value: source.widthProfile,
1024
1376
  onChange: (e) => {
1025
1377
  if (e.target.value !== "custom") setProfile(e.target.value);
1026
1378
  },
1027
1379
  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" })
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" })
1030
1382
  ]
1031
1383
  }
1032
1384
  ),
1033
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
1385
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1034
1386
  "button",
1035
1387
  {
1036
1388
  type: "button",
1037
- className: buttonClass2,
1389
+ className: buttonClass4,
1038
1390
  title: "Reset to uniform width",
1039
1391
  "aria-label": "Reset to uniform width",
1040
1392
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -1044,27 +1396,27 @@ function VectorStylePanel({ controller, className }) {
1044
1396
  ),
1045
1397
  mixed("widthProfile")
1046
1398
  ] }),
1047
- source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
1399
+ source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
1048
1400
  arrowRow("start", "Start"),
1049
1401
  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" })
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" })
1055
1407
  ] })
1056
1408
  ] })
1057
1409
  ] }),
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)(
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)(
1061
1413
  "input",
1062
1414
  {
1063
1415
  type: "number",
1064
1416
  min: 0,
1065
1417
  step: 1,
1066
1418
  "aria-label": "Corner radius",
1067
- className: numberClass2,
1419
+ className: numberClass4,
1068
1420
  value: Math.round(source.cornerRadius * 100) / 100,
1069
1421
  onChange: (e) => {
1070
1422
  const n = parseFloat(e.target.value);
@@ -1072,12 +1424,12 @@ function VectorStylePanel({ controller, className }) {
1072
1424
  }
1073
1425
  }
1074
1426
  ),
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)) }),
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)) }),
1076
1428
  mixed("cornerRadius")
1077
1429
  ] }),
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)(
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)(
1081
1433
  "input",
1082
1434
  {
1083
1435
  type: "range",
@@ -1091,18 +1443,19 @@ function VectorStylePanel({ controller, className }) {
1091
1443
  }
1092
1444
  )
1093
1445
  ] }),
1094
- style && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VectorEffectsPanel, { controller })
1446
+ style && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(VectorEffectsPanel, { controller }),
1447
+ style && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(VectorEnvelopePanel, { controller })
1095
1448
  ] });
1096
1449
  }
1097
1450
 
1098
1451
  // src/vector/VectorEditor.tsx
1099
- var import_jsx_runtime5 = require("react/jsx-runtime");
1452
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1100
1453
  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" })
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" })
1106
1459
  ] })
1107
1460
  ] });
1108
1461
  }
@@ -1111,7 +1464,10 @@ function VectorEditor({ controller, tools, showStylePanel = true, height = 520,
1111
1464
  VectorCanvas,
1112
1465
  VectorEditor,
1113
1466
  VectorEffectsPanel,
1467
+ VectorEnvelopePanel,
1468
+ VectorGradientAnnotator,
1114
1469
  VectorGradientDefs,
1470
+ VectorGradientEditor,
1115
1471
  VectorSceneNodes,
1116
1472
  VectorStylePanel,
1117
1473
  VectorToolbar,