@pixerate/editor-react 0.75.0 → 0.76.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.
@@ -48,6 +48,7 @@ __export(vector_exports, {
48
48
  VectorShadingPanel: () => VectorShadingPanel,
49
49
  VectorStylePanel: () => VectorStylePanel,
50
50
  VectorSymbolsPanel: () => VectorSymbolsPanel,
51
+ VectorSymmetryPanel: () => VectorSymmetryPanel,
51
52
  VectorTextElement: () => VectorTextElement,
52
53
  VectorTextPanel: () => VectorTextPanel,
53
54
  VectorToolbar: () => VectorToolbar,
@@ -277,48 +278,19 @@ function VectorTextElement({ node }) {
277
278
  )) });
278
279
  }
279
280
  function VectorSceneNodes({ nodes }) {
280
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: nodes.map(
281
- (node) => node.kind === "text" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorTextElement, { node }, node.id) : node.kind === "group" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
282
- "g",
283
- {
284
- "data-vector-id": node.id,
285
- "data-vector-mask": node.mask?.type,
286
- transform: node.transform,
287
- opacity: node.opacity < 1 ? node.opacity : void 0,
288
- filter: node.filter,
289
- style: blendStyle(node.blendMode),
290
- children: node.mask ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorMaskedNodes, { mask: node.mask, nodes: node.children }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorSceneNodes, { nodes: node.children })
291
- },
292
- node.id
293
- ) : node.layers ? (
294
- // Fill and stroke stacks: each layer in order, bottom to top.
295
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
296
- "g",
297
- {
298
- "data-vector-id": node.id,
299
- transform: node.transform,
300
- opacity: node.opacity < 1 ? node.opacity : void 0,
301
- filter: node.filter,
302
- style: blendStyle(node.blendMode),
303
- children: node.layers.map((layer) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorPaintPath, { paint: layer, d: node.d, fillRule: node.fillRule, layer: `${layer.kind}-${layer.index}` }, `${layer.kind}-${layer.index}`))
304
- },
305
- node.id
306
- )
307
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
308
- VectorPaintPath,
309
- {
310
- paint: node,
311
- d: node.d,
312
- fillRule: node.fillRule,
313
- id: node.id,
314
- transform: node.transform,
315
- filter: node.filter,
316
- blendMode: node.blendMode
317
- },
318
- node.id
319
- )
320
- ) });
281
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorSceneNode, { node }, node.id)) });
321
282
  }
283
+ var VectorSceneNode = import_react3.default.memo(function VectorSceneNode2({ node }) {
284
+ if (node.kind === "text") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorTextElement, { node });
285
+ if (node.kind === "group")
286
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { "data-vector-id": node.id, "data-vector-mask": node.mask?.type, transform: node.transform, opacity: node.opacity < 1 ? node.opacity : void 0, filter: node.filter, style: blendStyle(node.blendMode), children: node.mask ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorMaskedNodes, { mask: node.mask, nodes: node.children }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorSceneNodes, { nodes: node.children }) });
287
+ if (node.layers)
288
+ return (
289
+ // Fill and stroke stacks: each layer in order, bottom to top.
290
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { "data-vector-id": node.id, transform: node.transform, opacity: node.opacity < 1 ? node.opacity : void 0, filter: node.filter, style: blendStyle(node.blendMode), children: node.layers.map((layer) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorPaintPath, { paint: layer, d: node.d, fillRule: node.fillRule, layer: `${layer.kind}-${layer.index}` }, `${layer.kind}-${layer.index}`)) })
291
+ );
292
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorPaintPath, { paint: node, d: node.d, fillRule: node.fillRule, id: node.id, transform: node.transform, filter: node.filter, blendMode: node.blendMode });
293
+ });
322
294
  function VectorMaskedNodes({ mask, nodes }) {
323
295
  const b = mask.bounds;
324
296
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
@@ -413,7 +385,8 @@ function VectorCanvas({
413
385
  const editingText = state.textEdit?.nodeId ?? null;
414
386
  const fitted = (0, import_react3.useRef)(!fitOnMount);
415
387
  const uid = (0, import_react3.useId)().replace(/:/g, "");
416
- const scene = (0, import_vector3.getVectorSceneRenderData)(state, { idPrefix: `ve${uid}-` });
388
+ const [size, setSize] = (0, import_react3.useState)(null);
389
+ const scene = (0, import_vector3.getVectorSceneRenderData)(state, { idPrefix: `ve${uid}-`, ...size ? { viewportSize: size } : {} });
417
390
  const overlay = (0, import_vector3.getVectorOverlayRenderData)(state);
418
391
  const host = (0, import_vector3.getVectorSymbolEditBackground)(state, { idPrefix: `ve${uid}-host-` });
419
392
  (0, import_react3.useEffect)(() => {
@@ -431,6 +404,18 @@ function VectorCanvas({
431
404
  observer.observe(el);
432
405
  return () => observer.disconnect();
433
406
  }, [controller]);
407
+ (0, import_react3.useEffect)(() => {
408
+ const el = svgRef.current;
409
+ if (!el || typeof ResizeObserver === "undefined") return;
410
+ const measure = () => {
411
+ const r = el.getBoundingClientRect();
412
+ if (r.width > 0 && r.height > 0) setSize((s) => s && s.width === r.width && s.height === r.height ? s : { width: r.width, height: r.height });
413
+ };
414
+ measure();
415
+ const observer = new ResizeObserver(measure);
416
+ observer.observe(el);
417
+ return () => observer.disconnect();
418
+ }, []);
434
419
  (0, import_react3.useEffect)(() => {
435
420
  if (!editingText) return;
436
421
  const t = setTimeout(() => textRef.current?.focus({ preventScroll: true }), 0);
@@ -566,8 +551,8 @@ function VectorCanvas({
566
551
  /* @__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 })
567
552
  ] }, h.index)),
568
553
  overlay.envelope.anchors.map((a) => {
569
- const size = as + (a.hovered || a.active ? 2 : 0);
570
- 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);
554
+ const size2 = as + (a.hovered || a.active ? 2 : 0);
555
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { "data-vector-envelope-anchor": a.index, x: a.x - size2 / 2, y: a.y - size2 / 2, width: size2, height: size2, fill: a.active ? accentColor : import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, a.index);
571
556
  })
572
557
  ] }),
573
558
  overlay.handles.map((h) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-handle": h.ref.handle, children: [
@@ -575,10 +560,10 @@ function VectorCanvas({
575
560
  /* @__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 })
576
561
  ] }, `${h.ref.nodeId}:${h.ref.subpath}:${h.ref.index}:${h.ref.handle}`)),
577
562
  overlay.anchors.map((a) => {
578
- const size = as + (a.hovered ? 2 : 0);
563
+ const size2 = as + (a.hovered ? 2 : 0);
579
564
  const fill = a.selected ? accentColor : import_vector3.VECTOR_OVERLAY_THEME.handleFill;
580
565
  const key = `${a.ref.nodeId}:${a.ref.subpath}:${a.ref.index}`;
581
- return a.type === "corner" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { "data-vector-anchor": a.ref.index, x: a.x - size / 2, y: a.y - size / 2, width: size, height: size, fill, stroke: accentColor }, key) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { "data-vector-anchor": a.ref.index, cx: a.x, cy: a.y, r: size / 2, fill, stroke: accentColor }, key);
566
+ return a.type === "corner" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { "data-vector-anchor": a.ref.index, x: a.x - size2 / 2, y: a.y - size2 / 2, width: size2, height: size2, fill, stroke: accentColor }, key) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { "data-vector-anchor": a.ref.index, cx: a.x, cy: a.y, r: size2 / 2, fill, stroke: accentColor }, key);
582
567
  }),
583
568
  overlay.cornerWidgets.map((w) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-corner-widget": w.ref.index, "data-corner-kind": w.kind, children: [
584
569
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: w.x, cy: w.y, r: import_vector3.VECTOR_OVERLAY_THEME.cornerWidgetRadius + (w.hovered ? 1 : 0), fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }),
@@ -699,6 +684,7 @@ function VectorCanvas({
699
684
  sh.lines.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d, stroke: accentColor, strokeWidth: 1, opacity: 0.7 }, i)),
700
685
  sh.points.map((p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { "data-vector-shading-point": p.index, "data-selected": p.selected || void 0, cx: p.x, cy: p.y, r: p.selected ? 6 : 4.5, fill: p.color, stroke: p.selected ? accentColor : "#ffffff", strokeWidth: p.selected ? 2 : 1.5 }, p.index))
701
686
  ] }, `shading:${sh.nodeId}`)),
687
+ overlay.symmetry.map((sy) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { "data-vector-symmetry-axes": sy.nodeId, stroke: accentColor, strokeDasharray: sy.drawing ? void 0 : "6 4", opacity: sy.drawing ? 0.9 : 0.6, children: sy.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: l.x1, y1: l.y1, x2: l.x2, y2: l.y2 }, i)) }, `sym:${sy.nodeId}`)),
702
688
  overlay.trackingPoints.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { "data-vector-tracking-point": "", d: `M${t.x - 5} ${t.y}H${t.x + 5}M${t.x} ${t.y - 5}V${t.y + 5}`, stroke: guideColor, strokeWidth: 1.5 }, `track:${i}`)),
703
689
  overlay.typedEntry && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-typed-entry": "", transform: `translate(${overlay.typedEntry.x + 14} ${overlay.typedEntry.y + 18})`, children: [
704
690
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: -3, y: -12, width: overlay.typedEntry.text.length * 7 + 8, height: 17, rx: 3, fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }),
@@ -835,7 +821,7 @@ function VectorCanvas({
835
821
 
836
822
  // src/vector/VectorToolbar.tsx
837
823
  var import_react10 = __toESM(require("react"), 1);
838
- var import_vector15 = require("@pixerate/editor/vector");
824
+ var import_vector16 = require("@pixerate/editor/vector");
839
825
 
840
826
  // src/vector/VectorEffectsPanel.tsx
841
827
  var import_vector4 = require("@pixerate/editor/vector");
@@ -925,6 +911,15 @@ function VectorEffectsPanel({ controller, className }) {
925
911
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "min-w-0 flex-1 truncate", children: spec.label }),
926
912
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { type: "checkbox", "aria-label": `${e.label} ${spec.label}`, checked: !!e.params[spec.key], onChange: (ev) => set(e, spec.key, ev.target.checked, false) })
927
913
  ] }, spec.key);
914
+ if (spec.kind === "color") {
915
+ const c = e.params[spec.key];
916
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-effect-param": spec.key, children: [
917
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: labelClass, children: spec.label }),
918
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { type: "checkbox", "aria-label": `${e.label} ${spec.label} on`, checked: c !== null, onChange: (ev) => set(e, spec.key, ev.target.checked ? "#ffffff" : null, false) }),
919
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { type: "color", "aria-label": `${e.label} ${spec.label}`, className: "h-6 w-8 cursor-pointer rounded border border-border bg-background disabled:opacity-40", disabled: c === null, value: (0, import_vector4.vectorPaintToHex)(c ?? "#ffffff") ?? "#ffffff", onChange: (ev) => set(e, spec.key, ev.target.value, true), onBlur: end })
920
+ ] }, spec.key);
921
+ }
922
+ if (spec.kind !== "select") return null;
928
923
  return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs text-foreground", "data-vector-effect-param": spec.key, children: [
929
924
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: labelClass, children: spec.label }),
930
925
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("select", { "aria-label": `${e.label} ${spec.label}`, className: selectClass, value: String(e.params[spec.key] ?? ""), onChange: (ev) => set(e, spec.key, ev.target.value, false), children: spec.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: o.value, children: o.label }, o.value)) })
@@ -2040,16 +2035,53 @@ function VectorTracePanel({ controller, className }) {
2040
2035
  ] });
2041
2036
  }
2042
2037
 
2043
- // src/vector/VectorGradientEditor.tsx
2038
+ // src/vector/VectorSymmetryPanel.tsx
2044
2039
  var import_vector14 = require("@pixerate/editor/vector");
2045
2040
  var import_jsx_runtime14 = require("react/jsx-runtime");
2041
+ var buttonClass12 = "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 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
2046
2042
  var selectClass9 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2047
- var numberClass8 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
2048
- var buttonClass12 = "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";
2043
+ var numberClass8 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
2044
+ function VectorSymmetryPanel({ controller, className }) {
2045
+ const state = useVectorEditorState(controller);
2046
+ const group = state.selection.map((id) => controller.getNode(id)).find(import_vector14.isVectorSymmetryGroup) ?? null;
2047
+ const s = group?.symmetry;
2048
+ const ids = group ? [group.id] : void 0;
2049
+ const drawing = !!group && state.symmetryTarget === group.id;
2050
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-symmetry-panel": "", "aria-label": "Symmetry", children: [
2051
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2052
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Symmetry" }),
2053
+ !group && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, "data-vector-symmetry-action": "make", title: "Make Symmetry: the selection (or nothing yet) mirrored live across a vertical axis", onClick: () => controller.makeSymmetry({ mode: "vertical" }), children: "Mirror" }),
2054
+ !group && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, "data-vector-symmetry-action": "radial", title: "Make Symmetry: the selection repeated round its centre", onClick: () => controller.makeSymmetry({ mode: "radial", copies: 6 }), children: "Radial" }),
2055
+ group && s && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2056
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("select", { "aria-label": "Symmetry mode", className: selectClass9, value: s.mode, onChange: (e) => controller.setSymmetry({ mode: e.target.value }, { ids }), children: import_vector14.VECTOR_SYMMETRY_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: m, children: import_vector14.VECTOR_SYMMETRY_MODE_LABELS[m] }, m)) }),
2057
+ s.mode === "radial" && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2058
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("input", { type: "number", min: 2, max: 64, "aria-label": "Symmetry copies", title: "Copies round the circle", className: numberClass8, value: s.copies ?? 6, onChange: (e) => controller.setSymmetry({ copies: Math.max(2, Math.min(64, parseInt(e.target.value, 10) || 2)) }, { ids }) }),
2059
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Mirror each copy too (kaleidoscope)", children: [
2060
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("input", { type: "checkbox", "aria-label": "Mirror copies", checked: !!s.mirror, onChange: (e) => controller.setSymmetry({ mirror: e.target.checked }, { ids }) }),
2061
+ "Mirror"
2062
+ ] })
2063
+ ] })
2064
+ ] })
2065
+ ] }),
2066
+ group && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2067
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16" }),
2068
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, "aria-pressed": drawing, "data-vector-symmetry-action": "draw", title: "Draw inside: new pen, pencil, shape and text art goes into the group, mirrored as you draw (Escape stops)", onClick: () => drawing ? controller.exitSymmetry() : controller.enterSymmetry(group.id), children: "Draw inside" }),
2069
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, title: "Expand: keep the copies as real objects", onClick: () => controller.releaseSymmetry({ ids, expand: true }), children: "Expand" }),
2070
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, title: "Release: stop repeating (the copies go)", onClick: () => controller.releaseSymmetry({ ids }), children: "Release" })
2071
+ ] })
2072
+ ] });
2073
+ }
2074
+
2075
+ // src/vector/VectorGradientEditor.tsx
2076
+ var import_vector15 = require("@pixerate/editor/vector");
2077
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2078
+ var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2079
+ var numberClass9 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
2080
+ var buttonClass13 = "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";
2049
2081
  var percent = (v) => Math.round(v * 1e3) / 10;
2050
2082
  function VectorGradientEditor({ controller, className }) {
2051
2083
  const state = useVectorEditorState(controller);
2052
- const ed = (0, import_vector14.getVectorGradientEditor)(state);
2084
+ const ed = (0, import_vector15.getVectorGradientEditor)(state);
2053
2085
  if (!ed) return null;
2054
2086
  const g = ed.gradient;
2055
2087
  const sel = ed.selected;
@@ -2057,26 +2089,26 @@ function VectorGradientEditor({ controller, className }) {
2057
2089
  const setType = (value) => {
2058
2090
  if (value === "solid") {
2059
2091
  const first = ed.stops[0];
2060
- controller.setStyle({ [ed.target]: (0, import_vector14.vectorPaintToHex)(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2092
+ controller.setStyle({ [ed.target]: (0, import_vector15.vectorPaintToHex)(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2061
2093
  } else controller.setGradient({ type: value });
2062
2094
  };
2063
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2064
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2065
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2066
- ["fill", "stroke"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
2067
- ed.mixed && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2095
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2096
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2097
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2098
+ ["fill", "stroke"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: buttonClass13, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
2099
+ ed.mixed && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2068
2100
  ] }),
2069
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2070
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2071
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("select", { "aria-label": "Gradient type", className: selectClass9, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2072
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "solid", children: "Solid" }),
2073
- import_vector14.VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: t, children: import_vector14.VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
2101
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2102
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2103
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("select", { "aria-label": "Gradient type", className: selectClass10, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2104
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "solid", children: "Solid" }),
2105
+ import_vector15.VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: t, children: import_vector15.VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
2074
2106
  ] }),
2075
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2107
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: buttonClass13, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2076
2108
  ] }),
2077
- g && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2078
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2079
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2109
+ g && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2110
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2111
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2080
2112
  "div",
2081
2113
  {
2082
2114
  role: "button",
@@ -2090,7 +2122,7 @@ function VectorGradientEditor({ controller, className }) {
2090
2122
  }
2091
2123
  }
2092
2124
  ),
2093
- ed.stops.map((s) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2125
+ ed.stops.map((s) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2094
2126
  "button",
2095
2127
  {
2096
2128
  type: "button",
@@ -2104,21 +2136,21 @@ function VectorGradientEditor({ controller, className }) {
2104
2136
  s.index
2105
2137
  ))
2106
2138
  ] }),
2107
- sel && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2108
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2139
+ sel && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2140
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2109
2141
  "input",
2110
2142
  {
2111
2143
  type: "color",
2112
2144
  "aria-label": "Stop colour",
2113
2145
  className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
2114
- value: (0, import_vector14.vectorPaintToHex)(sel.color) ?? "#000000",
2146
+ value: (0, import_vector15.vectorPaintToHex)(sel.color) ?? "#000000",
2115
2147
  onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
2116
2148
  onBlur: end
2117
2149
  }
2118
2150
  ),
2119
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2151
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2120
2152
  "Opacity",
2121
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2153
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2122
2154
  "input",
2123
2155
  {
2124
2156
  type: "number",
@@ -2126,7 +2158,7 @@ function VectorGradientEditor({ controller, className }) {
2126
2158
  max: 100,
2127
2159
  step: 5,
2128
2160
  "aria-label": "Stop opacity",
2129
- className: numberClass8,
2161
+ className: numberClass9,
2130
2162
  value: percent(sel.opacity ?? 1),
2131
2163
  onChange: (e) => {
2132
2164
  const n = parseFloat(e.target.value);
@@ -2135,9 +2167,9 @@ function VectorGradientEditor({ controller, className }) {
2135
2167
  }
2136
2168
  )
2137
2169
  ] }),
2138
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2170
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2139
2171
  "At",
2140
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2172
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2141
2173
  "input",
2142
2174
  {
2143
2175
  type: "number",
@@ -2145,7 +2177,7 @@ function VectorGradientEditor({ controller, className }) {
2145
2177
  max: 100,
2146
2178
  step: 1,
2147
2179
  "aria-label": "Stop position",
2148
- className: numberClass8,
2180
+ className: numberClass9,
2149
2181
  value: percent(sel.offset),
2150
2182
  onChange: (e) => {
2151
2183
  const n = parseFloat(e.target.value);
@@ -2154,9 +2186,9 @@ function VectorGradientEditor({ controller, className }) {
2154
2186
  }
2155
2187
  )
2156
2188
  ] }),
2157
- !sel.last && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2189
+ !sel.last && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2158
2190
  "Midpoint",
2159
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2191
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2160
2192
  "input",
2161
2193
  {
2162
2194
  type: "number",
@@ -2164,7 +2196,7 @@ function VectorGradientEditor({ controller, className }) {
2164
2196
  max: 95,
2165
2197
  step: 1,
2166
2198
  "aria-label": "Stop midpoint",
2167
- className: numberClass8,
2199
+ className: numberClass9,
2168
2200
  value: percent(sel.midpoint ?? 0.5),
2169
2201
  onChange: (e) => {
2170
2202
  const n = parseFloat(e.target.value);
@@ -2173,15 +2205,15 @@ function VectorGradientEditor({ controller, className }) {
2173
2205
  }
2174
2206
  )
2175
2207
  ] }),
2176
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2208
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: buttonClass13, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2177
2209
  ] }),
2178
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2179
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16", children: "Colours" }),
2180
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("select", { "aria-label": "Gradient interpolation", className: selectClass9, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: import_vector14.VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: i, children: import_vector14.VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2210
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2211
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Colours" }),
2212
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("select", { "aria-label": "Gradient interpolation", className: selectClass10, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: import_vector15.VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: i, children: import_vector15.VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2181
2213
  ] }),
2182
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2183
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16", children: "Smooth" }),
2184
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2214
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2215
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Smooth" }),
2216
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2185
2217
  "input",
2186
2218
  {
2187
2219
  type: "range",
@@ -2197,26 +2229,26 @@ function VectorGradientEditor({ controller, className }) {
2197
2229
  }
2198
2230
  )
2199
2231
  ] }),
2200
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2201
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16", children: "Spread" }),
2202
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("select", { "aria-label": "Gradient spread", className: selectClass9, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: import_vector14.VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: sp, children: import_vector14.VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2232
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2233
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Spread" }),
2234
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("select", { "aria-label": "Gradient spread", className: selectClass10, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: import_vector15.VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: sp, children: import_vector15.VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2203
2235
  ] }),
2204
- ed.target === "stroke" && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2205
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16", children: "On stroke" }),
2206
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("select", { "aria-label": "Gradient on stroke", className: selectClass9, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: import_vector14.VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: m, children: import_vector14.VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2236
+ ed.target === "stroke" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2237
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "On stroke" }),
2238
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("select", { "aria-label": "Gradient on stroke", className: selectClass10, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: import_vector15.VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: m, children: import_vector15.VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2207
2239
  ] })
2208
2240
  ] })
2209
2241
  ] });
2210
2242
  }
2211
2243
 
2212
2244
  // src/vector/VectorToolbar.tsx
2213
- var import_jsx_runtime15 = require("react/jsx-runtime");
2245
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2214
2246
  var VECTOR_SNAP_TITLE = "Snap to objects";
2215
2247
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
2216
- var buttonClass13 = "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";
2248
+ var buttonClass14 = "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";
2217
2249
  function VectorToolbar({
2218
2250
  controller,
2219
- tools = import_vector15.VECTOR_TOOLS,
2251
+ tools = import_vector16.VECTOR_TOOLS,
2220
2252
  showHistory = true,
2221
2253
  showArrange = true,
2222
2254
  showSnap = true,
@@ -2230,36 +2262,36 @@ function VectorToolbar({
2230
2262
  const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
2231
2263
  const editing = state.editing;
2232
2264
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
2233
- const maskSelected = state.selection.some((id) => (0, import_vector15.isVectorMaskGroup)(controller.getNode(id)));
2265
+ const maskSelected = state.selection.some((id) => (0, import_vector16.isVectorMaskGroup)(controller.getNode(id)));
2234
2266
  const [live, setLive] = import_react10.default.useState(false);
2235
2267
  const booleanDisabled = live ? count < 1 : count < 2;
2236
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: buttonClass13, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2237
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2238
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2268
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: buttonClass14, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2269
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2270
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2239
2271
  "button",
2240
2272
  {
2241
2273
  type: "button",
2242
2274
  "data-vector-tool": tool,
2243
- className: buttonClass13,
2275
+ className: buttonClass14,
2244
2276
  "aria-pressed": state.tool === tool,
2245
- title: `${(0, import_vector15.vectorToolLabel)(tool)} (${import_vector15.VECTOR_TOOL_SHORTCUTS[tool]})`,
2246
- "aria-label": (0, import_vector15.vectorToolLabel)(tool),
2277
+ title: `${(0, import_vector16.vectorToolLabel)(tool)} (${import_vector16.VECTOR_TOOL_SHORTCUTS[tool]})`,
2278
+ "aria-label": (0, import_vector16.vectorToolLabel)(tool),
2247
2279
  onClick: () => controller.setTool(tool),
2248
- children: import_vector15.VECTOR_TOOL_GLYPHS[tool]
2280
+ children: import_vector16.VECTOR_TOOL_GLYPHS[tool]
2249
2281
  },
2250
2282
  tool
2251
2283
  )) }),
2252
- showHistory && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2284
+ showHistory && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2253
2285
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
2254
2286
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
2255
2287
  ] }),
2256
- showSnap && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2257
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2288
+ showSnap && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2289
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2258
2290
  "button",
2259
2291
  {
2260
2292
  type: "button",
2261
2293
  "data-vector-snap": "",
2262
- className: buttonClass13,
2294
+ className: buttonClass14,
2263
2295
  "aria-pressed": state.snap.objects,
2264
2296
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
2265
2297
  "aria-label": VECTOR_SNAP_TITLE,
@@ -2267,18 +2299,18 @@ function VectorToolbar({
2267
2299
  children: "Snap"
2268
2300
  }
2269
2301
  ),
2270
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2302
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2271
2303
  "select",
2272
2304
  {
2273
2305
  "data-vector-polar": "",
2274
2306
  "aria-label": "Polar tracking increment",
2275
2307
  title: "Polar tracking: the angles drawn points snap to from where they leave",
2276
2308
  className: "h-8 rounded-md border border-border bg-background px-1 text-xs",
2277
- value: String((0, import_vector15.vectorPolarIncrementOf)(state.snap.angles) ?? ""),
2278
- onChange: (e) => controller.setSnap({ angles: (0, import_vector15.vectorPolarAngles)(Number(e.target.value)) }),
2309
+ value: String((0, import_vector16.vectorPolarIncrementOf)(state.snap.angles) ?? ""),
2310
+ onChange: (e) => controller.setSnap({ angles: (0, import_vector16.vectorPolarAngles)(Number(e.target.value)) }),
2279
2311
  children: [
2280
- (0, import_vector15.vectorPolarIncrementOf)(state.snap.angles) === null && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "", children: "Custom" }),
2281
- [90, 45, 30, 15].map((n) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("option", { value: n, children: [
2312
+ (0, import_vector16.vectorPolarIncrementOf)(state.snap.angles) === null && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: "", children: "Custom" }),
2313
+ [90, 45, 30, 15].map((n) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("option", { value: n, children: [
2282
2314
  n,
2283
2315
  "\xB0"
2284
2316
  ] }, n))
@@ -2286,13 +2318,13 @@ function VectorToolbar({
2286
2318
  }
2287
2319
  )
2288
2320
  ] }),
2289
- showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2290
- import_vector15.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2321
+ showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2322
+ import_vector16.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2291
2323
  "button",
2292
2324
  {
2293
2325
  type: "button",
2294
2326
  "data-vector-path-action": a.id,
2295
- className: buttonClass13,
2327
+ className: buttonClass14,
2296
2328
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
2297
2329
  "aria-label": a.title,
2298
2330
  disabled: !a.enabled(count, points),
@@ -2301,12 +2333,12 @@ function VectorToolbar({
2301
2333
  },
2302
2334
  a.id
2303
2335
  )),
2304
- import_vector15.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2336
+ import_vector16.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2305
2337
  "button",
2306
2338
  {
2307
2339
  type: "button",
2308
2340
  "data-vector-edit": t.key,
2309
- className: buttonClass13,
2341
+ className: buttonClass14,
2310
2342
  "aria-pressed": editing[t.key],
2311
2343
  title: t.title,
2312
2344
  "aria-label": t.title,
@@ -2315,9 +2347,9 @@ function VectorToolbar({
2315
2347
  },
2316
2348
  t.key
2317
2349
  )),
2318
- state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2350
+ state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2319
2351
  "Smoothing",
2320
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2352
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2321
2353
  "input",
2322
2354
  {
2323
2355
  type: "range",
@@ -2331,9 +2363,9 @@ function VectorToolbar({
2331
2363
  }
2332
2364
  )
2333
2365
  ] }),
2334
- state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2366
+ state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2335
2367
  "Size",
2336
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2368
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2337
2369
  "input",
2338
2370
  {
2339
2371
  type: "number",
@@ -2349,26 +2381,26 @@ function VectorToolbar({
2349
2381
  }
2350
2382
  )
2351
2383
  ] }),
2352
- state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2384
+ state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2353
2385
  "button",
2354
2386
  {
2355
2387
  type: "button",
2356
2388
  "data-vector-fill-gap": "",
2357
- className: buttonClass13,
2389
+ className: buttonClass14,
2358
2390
  "aria-pressed": editing.fillGap > 0,
2359
2391
  title: "Close small gaps between path ends when filling",
2360
2392
  "aria-label": "Close gaps",
2361
- onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector15.VECTOR_FILL_GAP }),
2393
+ onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector16.VECTOR_FILL_GAP }),
2362
2394
  children: "Gaps"
2363
2395
  }
2364
2396
  )
2365
2397
  ] }),
2366
- showDistort && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2398
+ showDistort && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2367
2399
  "button",
2368
2400
  {
2369
2401
  type: "button",
2370
2402
  "data-vector-free-distort": "",
2371
- className: buttonClass13,
2403
+ className: buttonClass14,
2372
2404
  "aria-pressed": state.freeDistort,
2373
2405
  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)`,
2374
2406
  "aria-label": "Free Distort",
@@ -2376,15 +2408,15 @@ function VectorToolbar({
2376
2408
  children: "Distort"
2377
2409
  }
2378
2410
  ) }),
2379
- showBlend && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorBlendControls, { controller }),
2380
- showArrange && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2381
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2382
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2411
+ showBlend && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorBlendControls, { controller }),
2412
+ showArrange && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2413
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2414
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2383
2415
  "button",
2384
2416
  {
2385
2417
  type: "button",
2386
2418
  "data-vector-live-boolean": "",
2387
- className: buttonClass13,
2419
+ className: buttonClass14,
2388
2420
  "aria-pressed": live,
2389
2421
  title: "Live booleans: keep the shapes editable",
2390
2422
  "aria-label": "Live booleans",
@@ -2401,12 +2433,12 @@ function VectorToolbar({
2401
2433
  act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
2402
2434
  act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
2403
2435
  ] }),
2404
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: import_vector15.VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2436
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: import_vector16.VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2405
2437
  "button",
2406
2438
  {
2407
2439
  type: "button",
2408
2440
  "data-vector-pathfinder": a.id,
2409
- className: buttonClass13,
2441
+ className: buttonClass14,
2410
2442
  title: a.title,
2411
2443
  "aria-label": a.title,
2412
2444
  disabled: count < a.min,
@@ -2415,7 +2447,7 @@ function VectorToolbar({
2415
2447
  },
2416
2448
  a.id
2417
2449
  )) }),
2418
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2450
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2419
2451
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
2420
2452
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
2421
2453
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -2430,22 +2462,22 @@ function VectorToolbar({
2430
2462
  }
2431
2463
  function VectorBlendControls({ controller }) {
2432
2464
  const state = useVectorEditorState(controller);
2433
- const blend = (0, import_vector15.getVectorBlendControls)(state);
2465
+ const blend = (0, import_vector16.getVectorBlendControls)(state);
2434
2466
  const o = blend.options;
2435
2467
  const selected = blend.ids.length > 0;
2436
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2437
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass13, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
2438
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2468
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2469
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass14, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
2470
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2439
2471
  "select",
2440
2472
  {
2441
2473
  "aria-label": "Blend spacing",
2442
2474
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
2443
2475
  value: o.spacing,
2444
- onChange: (e) => (0, import_vector15.applyVectorBlendOptions)(controller, { spacing: e.target.value }),
2445
- children: import_vector15.VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: sp, children: import_vector15.VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2476
+ onChange: (e) => (0, import_vector16.applyVectorBlendOptions)(controller, { spacing: e.target.value }),
2477
+ children: import_vector16.VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: sp, children: import_vector16.VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2446
2478
  }
2447
2479
  ),
2448
- o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2480
+ o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2449
2481
  "input",
2450
2482
  {
2451
2483
  type: "number",
@@ -2457,29 +2489,29 @@ function VectorBlendControls({ controller }) {
2457
2489
  onChange: (e) => {
2458
2490
  const n = parseFloat(e.target.value);
2459
2491
  if (!Number.isFinite(n) || n <= 0) return;
2460
- (0, import_vector15.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
2492
+ (0, import_vector16.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
2461
2493
  }
2462
2494
  }
2463
2495
  ),
2464
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2496
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2465
2497
  "button",
2466
2498
  {
2467
2499
  type: "button",
2468
2500
  "data-vector-blend-action": "orient",
2469
- className: buttonClass13,
2501
+ className: buttonClass14,
2470
2502
  "aria-pressed": o.orientToSpine,
2471
2503
  title: "Orient steps to the spine",
2472
2504
  "aria-label": "Orient to spine",
2473
- onClick: () => (0, import_vector15.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
2505
+ onClick: () => (0, import_vector16.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
2474
2506
  children: "\u293F"
2475
2507
  }
2476
2508
  ),
2477
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2509
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2478
2510
  "button",
2479
2511
  {
2480
2512
  type: "button",
2481
2513
  "data-vector-blend-action": "spine",
2482
- className: buttonClass13,
2514
+ className: buttonClass14,
2483
2515
  title: "Replace Spine (select a blend and a path)",
2484
2516
  "aria-label": "Replace Spine",
2485
2517
  disabled: !blend.canReplaceSpine,
@@ -2487,15 +2519,15 @@ function VectorBlendControls({ controller }) {
2487
2519
  children: "\u2307"
2488
2520
  }
2489
2521
  ),
2490
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass13, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2491
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass13, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2522
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass14, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2523
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass14, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2492
2524
  ] });
2493
2525
  }
2494
- var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2495
- var numberClass9 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2526
+ var selectClass11 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2527
+ var numberClass10 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2496
2528
  function VectorStylePanel({ controller, className }) {
2497
2529
  const state = useVectorEditorState(controller);
2498
- const style = (0, import_vector15.getVectorSelectionStyle)(state);
2530
+ const style = (0, import_vector16.getVectorSelectionStyle)(state);
2499
2531
  const d = state.defaults;
2500
2532
  const source = style ?? {
2501
2533
  fill: d.fill ?? null,
@@ -2508,7 +2540,7 @@ function VectorStylePanel({ controller, className }) {
2508
2540
  arrowAlign: d.arrowAlign ?? "extend",
2509
2541
  startArrowScale: d.startArrowScale ?? 1,
2510
2542
  endArrowScale: d.endArrowScale ?? 1,
2511
- widthProfile: (0, import_vector15.widthProfilePresetId)(d.strokeWidthProfile),
2543
+ widthProfile: (0, import_vector16.widthProfilePresetId)(d.strokeWidthProfile),
2512
2544
  cornerRadius: d.cornerRadius,
2513
2545
  cornerKind: d.cornerKind ?? "round",
2514
2546
  cornerSmoothing: 0,
@@ -2522,19 +2554,19 @@ function VectorStylePanel({ controller, className }) {
2522
2554
  };
2523
2555
  const setProfile = (id) => {
2524
2556
  if (style) controller.setWidthProfile(id);
2525
- else controller.setDefaults({ strokeWidthProfile: (0, import_vector15.widthProfilePreset)(id)?.length ? (0, import_vector15.widthProfilePreset)(id) : null });
2557
+ else controller.setDefaults({ strokeWidthProfile: (0, import_vector16.widthProfilePreset)(id)?.length ? (0, import_vector16.widthProfilePreset)(id) : null });
2526
2558
  };
2527
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2559
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2528
2560
  const arrowRow = (end, label) => {
2529
2561
  const key = end === "start" ? "startArrow" : "endArrow";
2530
2562
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
2531
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2532
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
2533
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("select", { "aria-label": `${label} arrowhead`, className: selectClass10, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2534
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "", children: "None" }),
2535
- import_vector15.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: h, children: import_vector15.VECTOR_ARROWHEAD_LABELS[h] }, h))
2563
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2564
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
2565
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("select", { "aria-label": `${label} arrowhead`, className: selectClass11, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2566
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: "", children: "None" }),
2567
+ import_vector16.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: h, children: import_vector16.VECTOR_ARROWHEAD_LABELS[h] }, h))
2536
2568
  ] }),
2537
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2569
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2538
2570
  "input",
2539
2571
  {
2540
2572
  type: "number",
@@ -2542,7 +2574,7 @@ function VectorStylePanel({ controller, className }) {
2542
2574
  step: 0.25,
2543
2575
  "aria-label": `${label} arrowhead scale`,
2544
2576
  title: "Scale (\xD7 stroke width)",
2545
- className: numberClass9,
2577
+ className: numberClass10,
2546
2578
  value: source[scaleKey],
2547
2579
  onChange: (e) => {
2548
2580
  const n = parseFloat(e.target.value);
@@ -2558,14 +2590,14 @@ function VectorStylePanel({ controller, className }) {
2558
2590
  };
2559
2591
  const paintRow = (key, label) => {
2560
2592
  const paint = source[key];
2561
- const hex = (0, import_vector15.vectorPaintToHex)(paint);
2593
+ const hex = (0, import_vector16.vectorPaintToHex)(paint);
2562
2594
  const on = paint !== null && paint !== "none";
2563
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2564
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2565
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2595
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2596
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2597
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2566
2598
  label
2567
2599
  ] }),
2568
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2600
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2569
2601
  "input",
2570
2602
  {
2571
2603
  type: "color",
@@ -2576,18 +2608,18 @@ function VectorStylePanel({ controller, className }) {
2576
2608
  onChange: (e) => apply({ [key]: e.target.value })
2577
2609
  }
2578
2610
  ),
2579
- on && !hex && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2580
- style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2611
+ on && !hex && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2612
+ style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2581
2613
  ] });
2582
2614
  };
2583
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2584
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2615
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2616
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2585
2617
  paintRow("fill", "Fill"),
2586
2618
  paintRow("stroke", "Stroke"),
2587
- style && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorGradientEditor, { controller }),
2588
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2589
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Width" }),
2590
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2619
+ style && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorGradientEditor, { controller }),
2620
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2621
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16", children: "Width" }),
2622
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2591
2623
  "input",
2592
2624
  {
2593
2625
  type: "number",
@@ -2603,33 +2635,33 @@ function VectorStylePanel({ controller, className }) {
2603
2635
  }
2604
2636
  )
2605
2637
  ] }),
2606
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2607
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Align" }),
2608
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("select", { "aria-label": "Stroke alignment", className: selectClass10, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(import_vector15.VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: a, children: import_vector15.VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
2638
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2639
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16", children: "Align" }),
2640
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("select", { "aria-label": "Stroke alignment", className: selectClass11, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(import_vector16.VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: a, children: import_vector16.VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
2609
2641
  mixed("strokeAlign")
2610
2642
  ] }),
2611
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2612
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Profile" }),
2613
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2643
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2644
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16", children: "Profile" }),
2645
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2614
2646
  "select",
2615
2647
  {
2616
2648
  "aria-label": "Width profile",
2617
- className: selectClass10,
2649
+ className: selectClass11,
2618
2650
  value: source.widthProfile,
2619
2651
  onChange: (e) => {
2620
2652
  if (e.target.value !== "custom") setProfile(e.target.value);
2621
2653
  },
2622
2654
  children: [
2623
- import_vector15.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: p.id, children: p.label }, p.id)),
2624
- source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
2655
+ import_vector16.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: p.id, children: p.label }, p.id)),
2656
+ source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
2625
2657
  ]
2626
2658
  }
2627
2659
  ),
2628
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2660
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2629
2661
  "button",
2630
2662
  {
2631
2663
  type: "button",
2632
- className: buttonClass13,
2664
+ className: buttonClass14,
2633
2665
  title: "Reset to uniform width",
2634
2666
  "aria-label": "Reset to uniform width",
2635
2667
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -2639,27 +2671,27 @@ function VectorStylePanel({ controller, className }) {
2639
2671
  ),
2640
2672
  mixed("widthProfile")
2641
2673
  ] }),
2642
- source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2674
+ source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2643
2675
  arrowRow("start", "Start"),
2644
2676
  arrowRow("end", "End"),
2645
- (source.startArrow || source.endArrow) && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2646
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Heads" }),
2647
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass10, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2648
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "extend", children: "Tip past end" }),
2649
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "tip", children: "Tip at end" })
2677
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2678
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16", children: "Heads" }),
2679
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass11, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2680
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: "extend", children: "Tip past end" }),
2681
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: "tip", children: "Tip at end" })
2650
2682
  ] })
2651
2683
  ] })
2652
2684
  ] }),
2653
- source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2654
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2655
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2685
+ source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2686
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2687
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2656
2688
  "input",
2657
2689
  {
2658
2690
  type: "number",
2659
2691
  min: 0,
2660
2692
  step: 1,
2661
2693
  "aria-label": "Corner radius",
2662
- className: numberClass9,
2694
+ className: numberClass10,
2663
2695
  value: Math.round(source.cornerRadius * 100) / 100,
2664
2696
  onChange: (e) => {
2665
2697
  const n = parseFloat(e.target.value);
@@ -2667,12 +2699,12 @@ function VectorStylePanel({ controller, className }) {
2667
2699
  }
2668
2700
  }
2669
2701
  ),
2670
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("select", { "aria-label": "Corner kind", className: selectClass10, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: import_vector15.VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: k, children: import_vector15.VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2702
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("select", { "aria-label": "Corner kind", className: selectClass11, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: import_vector16.VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: k, children: import_vector16.VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2671
2703
  mixed("cornerRadius")
2672
2704
  ] }),
2673
- style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "corner-smoothing", title: "Corner smoothing: squircle corners that ease into the curve (0.6 is iOS-like)", children: [
2674
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Smoothing" }),
2675
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2705
+ style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "corner-smoothing", title: "Corner smoothing: squircle corners that ease into the curve (0.6 is iOS-like)", children: [
2706
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16", children: "Smoothing" }),
2707
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2676
2708
  "input",
2677
2709
  {
2678
2710
  type: "range",
@@ -2687,9 +2719,9 @@ function VectorStylePanel({ controller, className }) {
2687
2719
  }
2688
2720
  )
2689
2721
  ] }),
2690
- style && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2691
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Opacity" }),
2692
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2722
+ style && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2723
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16", children: "Opacity" }),
2724
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2693
2725
  "input",
2694
2726
  {
2695
2727
  type: "range",
@@ -2703,28 +2735,29 @@ function VectorStylePanel({ controller, className }) {
2703
2735
  }
2704
2736
  )
2705
2737
  ] }),
2706
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorTextPanel, { controller }),
2707
- style && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorAppearancePanel, { controller }),
2708
- style && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorEffectsPanel, { controller }),
2709
- style && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorEnvelopePanel, { controller }),
2710
- style && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorMaskPanel, { controller }),
2711
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorFramePanel, { controller }),
2712
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorConnectorPanel, { controller }),
2713
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorShadingPanel, { controller }),
2714
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorTracePanel, { controller }),
2715
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorConstraintsPanel, { controller }),
2716
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorSymbolsPanel, { controller })
2738
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorTextPanel, { controller }),
2739
+ style && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorAppearancePanel, { controller }),
2740
+ style && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorEffectsPanel, { controller }),
2741
+ style && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorEnvelopePanel, { controller }),
2742
+ style && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorMaskPanel, { controller }),
2743
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorFramePanel, { controller }),
2744
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorConnectorPanel, { controller }),
2745
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorShadingPanel, { controller }),
2746
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorSymmetryPanel, { controller }),
2747
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorTracePanel, { controller }),
2748
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorConstraintsPanel, { controller }),
2749
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorSymbolsPanel, { controller })
2717
2750
  ] });
2718
2751
  }
2719
2752
 
2720
2753
  // src/vector/VectorEditor.tsx
2721
- var import_jsx_runtime16 = require("react/jsx-runtime");
2754
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2722
2755
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
2723
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2724
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorToolbar, { controller, tools }),
2725
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2726
- /* @__PURE__ */ (0, import_jsx_runtime16.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_runtime16.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
2727
- showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
2756
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2757
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorToolbar, { controller, tools }),
2758
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2759
+ /* @__PURE__ */ (0, import_jsx_runtime17.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_runtime17.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
2760
+ showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
2728
2761
  ] })
2729
2762
  ] });
2730
2763
  }
@@ -2748,6 +2781,7 @@ function VectorEditor({ controller, tools, showStylePanel = true, height = 520,
2748
2781
  VectorShadingPanel,
2749
2782
  VectorStylePanel,
2750
2783
  VectorSymbolsPanel,
2784
+ VectorSymmetryPanel,
2751
2785
  VectorTextElement,
2752
2786
  VectorTextPanel,
2753
2787
  VectorToolbar,