@pixerate/editor-react 0.74.0 → 0.75.1

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.
@@ -44,7 +44,7 @@ function useVectorCommands(registry, controller, options = {}) {
44
44
  }
45
45
 
46
46
  // src/vector/VectorCanvas.tsx
47
- import React2, { useEffect as useEffect2, useId, useRef as useRef3 } from "react";
47
+ import React2, { useEffect as useEffect2, useId, useRef as useRef3, useState as useState2 } from "react";
48
48
  import {
49
49
  VECTOR_OVERLAY_THEME as THEME2,
50
50
  getVectorOverlayRenderData,
@@ -227,48 +227,19 @@ function VectorTextElement({ node }) {
227
227
  )) });
228
228
  }
229
229
  function VectorSceneNodes({ nodes }) {
230
- return /* @__PURE__ */ jsx2(Fragment, { children: nodes.map(
231
- (node) => node.kind === "text" ? /* @__PURE__ */ jsx2(VectorTextElement, { node }, node.id) : node.kind === "group" ? /* @__PURE__ */ jsx2(
232
- "g",
233
- {
234
- "data-vector-id": node.id,
235
- "data-vector-mask": node.mask?.type,
236
- transform: node.transform,
237
- opacity: node.opacity < 1 ? node.opacity : void 0,
238
- filter: node.filter,
239
- style: blendStyle(node.blendMode),
240
- children: node.mask ? /* @__PURE__ */ jsx2(VectorMaskedNodes, { mask: node.mask, nodes: node.children }) : /* @__PURE__ */ jsx2(VectorSceneNodes, { nodes: node.children })
241
- },
242
- node.id
243
- ) : node.layers ? (
244
- // Fill and stroke stacks: each layer in order, bottom to top.
245
- /* @__PURE__ */ jsx2(
246
- "g",
247
- {
248
- "data-vector-id": node.id,
249
- transform: node.transform,
250
- opacity: node.opacity < 1 ? node.opacity : void 0,
251
- filter: node.filter,
252
- style: blendStyle(node.blendMode),
253
- children: node.layers.map((layer) => /* @__PURE__ */ jsx2(VectorPaintPath, { paint: layer, d: node.d, fillRule: node.fillRule, layer: `${layer.kind}-${layer.index}` }, `${layer.kind}-${layer.index}`))
254
- },
255
- node.id
256
- )
257
- ) : /* @__PURE__ */ jsx2(
258
- VectorPaintPath,
259
- {
260
- paint: node,
261
- d: node.d,
262
- fillRule: node.fillRule,
263
- id: node.id,
264
- transform: node.transform,
265
- filter: node.filter,
266
- blendMode: node.blendMode
267
- },
268
- node.id
269
- )
270
- ) });
230
+ return /* @__PURE__ */ jsx2(Fragment, { children: nodes.map((node) => /* @__PURE__ */ jsx2(VectorSceneNode, { node }, node.id)) });
271
231
  }
232
+ var VectorSceneNode = React2.memo(function VectorSceneNode2({ node }) {
233
+ if (node.kind === "text") return /* @__PURE__ */ jsx2(VectorTextElement, { node });
234
+ if (node.kind === "group")
235
+ return /* @__PURE__ */ jsx2("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__ */ jsx2(VectorMaskedNodes, { mask: node.mask, nodes: node.children }) : /* @__PURE__ */ jsx2(VectorSceneNodes, { nodes: node.children }) });
236
+ if (node.layers)
237
+ return (
238
+ // Fill and stroke stacks: each layer in order, bottom to top.
239
+ /* @__PURE__ */ jsx2("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__ */ jsx2(VectorPaintPath, { paint: layer, d: node.d, fillRule: node.fillRule, layer: `${layer.kind}-${layer.index}` }, `${layer.kind}-${layer.index}`)) })
240
+ );
241
+ return /* @__PURE__ */ jsx2(VectorPaintPath, { paint: node, d: node.d, fillRule: node.fillRule, id: node.id, transform: node.transform, filter: node.filter, blendMode: node.blendMode });
242
+ });
272
243
  function VectorMaskedNodes({ mask, nodes }) {
273
244
  const b = mask.bounds;
274
245
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
@@ -363,7 +334,8 @@ function VectorCanvas({
363
334
  const editingText = state.textEdit?.nodeId ?? null;
364
335
  const fitted = useRef3(!fitOnMount);
365
336
  const uid = useId().replace(/:/g, "");
366
- const scene = getVectorSceneRenderData(state, { idPrefix: `ve${uid}-` });
337
+ const [size, setSize] = useState2(null);
338
+ const scene = getVectorSceneRenderData(state, { idPrefix: `ve${uid}-`, ...size ? { viewportSize: size } : {} });
367
339
  const overlay = getVectorOverlayRenderData(state);
368
340
  const host = getVectorSymbolEditBackground(state, { idPrefix: `ve${uid}-host-` });
369
341
  useEffect2(() => {
@@ -381,6 +353,18 @@ function VectorCanvas({
381
353
  observer.observe(el);
382
354
  return () => observer.disconnect();
383
355
  }, [controller]);
356
+ useEffect2(() => {
357
+ const el = svgRef.current;
358
+ if (!el || typeof ResizeObserver === "undefined") return;
359
+ const measure = () => {
360
+ const r = el.getBoundingClientRect();
361
+ 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 });
362
+ };
363
+ measure();
364
+ const observer = new ResizeObserver(measure);
365
+ observer.observe(el);
366
+ return () => observer.disconnect();
367
+ }, []);
384
368
  useEffect2(() => {
385
369
  if (!editingText) return;
386
370
  const t = setTimeout(() => textRef.current?.focus({ preventScroll: true }), 0);
@@ -516,8 +500,8 @@ function VectorCanvas({
516
500
  /* @__PURE__ */ jsx2("circle", { cx: h.x, cy: h.y, r: THEME2.handleRadius + (h.hovered || h.active ? 1 : 0), fill: h.active ? accentColor : THEME2.handleFill, stroke: accentColor })
517
501
  ] }, h.index)),
518
502
  overlay.envelope.anchors.map((a) => {
519
- const size = as + (a.hovered || a.active ? 2 : 0);
520
- return /* @__PURE__ */ jsx2("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 : THEME2.handleFill, stroke: accentColor }, a.index);
503
+ const size2 = as + (a.hovered || a.active ? 2 : 0);
504
+ return /* @__PURE__ */ jsx2("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 : THEME2.handleFill, stroke: accentColor }, a.index);
521
505
  })
522
506
  ] }),
523
507
  overlay.handles.map((h) => /* @__PURE__ */ jsxs2("g", { "data-vector-handle": h.ref.handle, children: [
@@ -525,10 +509,10 @@ function VectorCanvas({
525
509
  /* @__PURE__ */ jsx2("circle", { cx: h.x, cy: h.y, r: THEME2.handleRadius + (h.hovered ? 1 : 0), fill: THEME2.handleFill, stroke: accentColor })
526
510
  ] }, `${h.ref.nodeId}:${h.ref.subpath}:${h.ref.index}:${h.ref.handle}`)),
527
511
  overlay.anchors.map((a) => {
528
- const size = as + (a.hovered ? 2 : 0);
512
+ const size2 = as + (a.hovered ? 2 : 0);
529
513
  const fill = a.selected ? accentColor : THEME2.handleFill;
530
514
  const key = `${a.ref.nodeId}:${a.ref.subpath}:${a.ref.index}`;
531
- return a.type === "corner" ? /* @__PURE__ */ jsx2("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__ */ jsx2("circle", { "data-vector-anchor": a.ref.index, cx: a.x, cy: a.y, r: size / 2, fill, stroke: accentColor }, key);
515
+ return a.type === "corner" ? /* @__PURE__ */ jsx2("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__ */ jsx2("circle", { "data-vector-anchor": a.ref.index, cx: a.x, cy: a.y, r: size2 / 2, fill, stroke: accentColor }, key);
532
516
  }),
533
517
  overlay.cornerWidgets.map((w) => /* @__PURE__ */ jsxs2("g", { "data-vector-corner-widget": w.ref.index, "data-corner-kind": w.kind, children: [
534
518
  /* @__PURE__ */ jsx2("circle", { cx: w.x, cy: w.y, r: THEME2.cornerWidgetRadius + (w.hovered ? 1 : 0), fill: THEME2.handleFill, stroke: accentColor }),
@@ -645,6 +629,10 @@ function VectorCanvas({
645
629
  /* @__PURE__ */ jsx2("path", { d: "M-3 0H3M0 -3V3", stroke: "#dc2626" })
646
630
  ] })
647
631
  ] }),
632
+ overlay.shading.map((sh) => /* @__PURE__ */ jsxs2("g", { "data-vector-shading": sh.kind, children: [
633
+ sh.lines.map((d, i) => /* @__PURE__ */ jsx2("path", { d, stroke: accentColor, strokeWidth: 1, opacity: 0.7 }, i)),
634
+ sh.points.map((p) => /* @__PURE__ */ jsx2("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))
635
+ ] }, `shading:${sh.nodeId}`)),
648
636
  overlay.trackingPoints.map((t, i) => /* @__PURE__ */ jsx2("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}`)),
649
637
  overlay.typedEntry && /* @__PURE__ */ jsxs2("g", { "data-vector-typed-entry": "", transform: `translate(${overlay.typedEntry.x + 14} ${overlay.typedEntry.y + 18})`, children: [
650
638
  /* @__PURE__ */ jsx2("rect", { x: -3, y: -12, width: overlay.typedEntry.text.length * 7 + 8, height: 17, rx: 3, fill: THEME2.handleFill, stroke: accentColor }),
@@ -780,7 +768,7 @@ function VectorCanvas({
780
768
  }
781
769
 
782
770
  // src/vector/VectorToolbar.tsx
783
- import React7 from "react";
771
+ import React9 from "react";
784
772
  import {
785
773
  VECTOR_TOOLS,
786
774
  VECTOR_TOOL_GLYPHS,
@@ -803,7 +791,7 @@ import {
803
791
  isVectorMaskGroup,
804
792
  widthProfilePreset,
805
793
  widthProfilePresetId,
806
- vectorPaintToHex as vectorPaintToHex4,
794
+ vectorPaintToHex as vectorPaintToHex5,
807
795
  vectorToolLabel,
808
796
  vectorPolarAngles,
809
797
  vectorPolarIncrementOf
@@ -1580,7 +1568,7 @@ function VectorTextPanel({ controller, className }) {
1580
1568
  }
1581
1569
 
1582
1570
  // src/vector/VectorSymbolsPanel.tsx
1583
- import { useId as useId3, useState as useState2 } from "react";
1571
+ import { useId as useId3, useState as useState3 } from "react";
1584
1572
  import { getVectorSymbolsPanel, vectorPaintToHex } from "@pixerate/editor/vector";
1585
1573
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1586
1574
  var buttonClass6 = "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";
@@ -1590,7 +1578,7 @@ function VectorSymbolsPanel({ controller, className }) {
1590
1578
  const state = useVectorEditorState(controller);
1591
1579
  const uid = useId3().replace(/:/g, "");
1592
1580
  const panel = getVectorSymbolsPanel(state, { idPrefix: `vsym${uid}-` });
1593
- const [error, setError] = useState2(null);
1581
+ const [error, setError] = useState3(null);
1594
1582
  const report = (r) => setError(r.ok ? null : r.message ?? null);
1595
1583
  const inst = panel.instance;
1596
1584
  return /* @__PURE__ */ jsxs8("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-symbols-panel": "", "aria-label": "Symbols", children: [
@@ -1648,7 +1636,7 @@ function VectorSymbolsPanel({ controller, className }) {
1648
1636
  }
1649
1637
 
1650
1638
  // src/vector/VectorConstraintsPanel.tsx
1651
- import { useState as useState3 } from "react";
1639
+ import { useState as useState4 } from "react";
1652
1640
  import { getVectorConstraintPanel } from "@pixerate/editor/vector";
1653
1641
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1654
1642
  var buttonClass7 = "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";
@@ -1657,8 +1645,8 @@ var labelClass6 = "w-16 text-[11px] font-medium uppercase tracking-wide text-mut
1657
1645
  function VectorConstraintsPanel({ controller, className }) {
1658
1646
  const state = useVectorEditorState(controller);
1659
1647
  const panel = getVectorConstraintPanel(state);
1660
- const [error, setError] = useState3(null);
1661
- const [param, setParam] = useState3({ name: "", value: "" });
1648
+ const [error, setError] = useState4(null);
1649
+ const [param, setParam] = useState4({ name: "", value: "" });
1662
1650
  const report = (r) => setError(r.ok ? null : r.message ?? null);
1663
1651
  const add = (a) => report(controller.addConstraint({ kind: a.kind, refs: a.refs }));
1664
1652
  if (!panel.actions.length && !panel.constraints.length && !state.document.constraints?.length && state.tool !== "direct") return null;
@@ -1925,6 +1913,120 @@ function VectorConnectorPanel({ controller, className }) {
1925
1913
  ] });
1926
1914
  }
1927
1915
 
1916
+ // src/vector/VectorShadingPanel.tsx
1917
+ import { useState as useState5 } from "react";
1918
+ import { isVectorFreeformGradient, isVectorMeshGradient, vectorPaintToHex as vectorPaintToHex3 } from "@pixerate/editor/vector";
1919
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1920
+ var buttonClass10 = "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";
1921
+ var numberClass6 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
1922
+ function VectorShadingPanel({ controller, className }) {
1923
+ const state = useVectorEditorState(controller);
1924
+ const [rows, setRows] = useState5(2);
1925
+ const [columns, setColumns] = useState5(2);
1926
+ const path = state.selection.map((id) => controller.getNode(id)).find((n) => n?.type === "path");
1927
+ if (!path) return null;
1928
+ const fill = path.fill;
1929
+ const sel = state.shadingPoint?.nodeId === path.id ? state.shadingPoint : null;
1930
+ const mesh = isVectorMeshGradient(fill) ? fill : null;
1931
+ const free = isVectorFreeformGradient(fill) ? fill : null;
1932
+ const color = sel ? mesh ? mesh.colors[sel.index] : free?.points[sel.index]?.color : void 0;
1933
+ const opacity = sel ? mesh ? mesh.opacities?.[sel.index] ?? 1 : free?.points[sel.index]?.opacity ?? 1 : 1;
1934
+ const spread = sel && free ? free.points[sel.index]?.spread ?? 0 : 0;
1935
+ const edit = (patch, merge) => {
1936
+ if (!sel) return;
1937
+ if (mesh) controller.setMeshPoint(path.id, sel.index, patch, merge ? { merge } : {});
1938
+ else controller.setFreeformPoint(path.id, sel.index, patch, merge ? { merge } : {});
1939
+ };
1940
+ return /* @__PURE__ */ jsxs12("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-shading-panel": path.id, "aria-label": "Mesh and freeform gradient", children: [
1941
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-1", children: [
1942
+ /* @__PURE__ */ jsx12("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Shading" }),
1943
+ /* @__PURE__ */ jsx12("input", { type: "number", min: 1, max: 32, "aria-label": "Mesh rows", title: "Mesh rows", className: numberClass6, value: rows, onChange: (e) => setRows(Math.max(1, Math.min(32, parseInt(e.target.value, 10) || 1))) }),
1944
+ /* @__PURE__ */ jsx12("span", { className: "text-xs text-muted-foreground", children: "\xD7" }),
1945
+ /* @__PURE__ */ jsx12("input", { type: "number", min: 1, max: 32, "aria-label": "Mesh columns", title: "Mesh columns", className: numberClass6, value: columns, onChange: (e) => setColumns(Math.max(1, Math.min(32, parseInt(e.target.value, 10) || 1))) }),
1946
+ /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "data-vector-shading-action": "mesh", title: "Create Gradient Mesh: smooth patches whose points blend colours (edit them with the Gradient tool)", onClick: () => controller.setMeshGradient({ rows, columns }, { ids: [path.id] }), children: "Mesh" }),
1947
+ /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "data-vector-shading-action": "freeform", title: "Freeform Gradient: colour points blended across the shape (click inside with the Gradient tool to add one)", onClick: () => controller.setFreeformGradient({}, { ids: [path.id] }), children: "Freeform" })
1948
+ ] }),
1949
+ (mesh || free) && !sel && /* @__PURE__ */ jsxs12("p", { className: "text-[11px] text-muted-foreground", children: [
1950
+ "Gradient tool (G): drag points; ",
1951
+ free ? "click inside to add one, Alt-click to remove." : "pick one to colour it."
1952
+ ] }),
1953
+ sel && /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-2", "data-vector-shading-point": sel.index, children: [
1954
+ /* @__PURE__ */ jsxs12("span", { className: "w-16 text-xs text-foreground", children: [
1955
+ "Point ",
1956
+ sel.index
1957
+ ] }),
1958
+ /* @__PURE__ */ jsx12("input", { type: "color", "aria-label": "Point colour", className: "h-7 w-10 cursor-pointer rounded border border-border bg-background", value: vectorPaintToHex3(color ?? "#000000") ?? "#000000", onChange: (e) => edit({ color: e.target.value }, "shading-color") }),
1959
+ /* @__PURE__ */ jsx12("input", { type: "range", min: 0, max: 1, step: 0.05, "aria-label": "Point opacity", title: "Opacity", className: "h-4 w-20 accent-primary", value: opacity, onChange: (e) => edit({ opacity: parseFloat(e.target.value) }, "shading-opacity") }),
1960
+ free && /* @__PURE__ */ jsxs12(Fragment3, { children: [
1961
+ /* @__PURE__ */ jsx12("input", { type: "range", min: 0, max: 1, step: 0.05, "aria-label": "Point spread", title: "Spread: pure colour round the point", className: "h-4 w-20 accent-primary", value: spread, onChange: (e) => edit({ spread: parseFloat(e.target.value) }, "shading-spread") }),
1962
+ /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "aria-label": "Remove point", onClick: () => controller.removeFreeformPoint(path.id, sel.index), children: "Remove" })
1963
+ ] })
1964
+ ] })
1965
+ ] });
1966
+ }
1967
+
1968
+ // src/vector/VectorTracePanel.tsx
1969
+ import { useRef as useRef4, useState as useState6 } from "react";
1970
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1971
+ var buttonClass11 = "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";
1972
+ var selectClass8 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1973
+ var numberClass7 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
1974
+ async function filePixels(file, max = 1e6) {
1975
+ const bitmap = await createImageBitmap(file);
1976
+ const k = Math.min(1, Math.sqrt(max / (bitmap.width * bitmap.height)));
1977
+ const width = Math.max(1, Math.round(bitmap.width * k));
1978
+ const height = Math.max(1, Math.round(bitmap.height * k));
1979
+ const canvas = document.createElement("canvas");
1980
+ canvas.width = width;
1981
+ canvas.height = height;
1982
+ const ctx = canvas.getContext("2d");
1983
+ ctx.drawImage(bitmap, 0, 0, width, height);
1984
+ return { width, height, data: ctx.getImageData(0, 0, width, height).data };
1985
+ }
1986
+ function VectorTracePanel({ controller, className }) {
1987
+ const input = useRef4(null);
1988
+ const [mode, setMode] = useState6("threshold");
1989
+ const [colors, setColors] = useState6(6);
1990
+ const [message, setMessage] = useState6(null);
1991
+ const trace = async (file) => {
1992
+ try {
1993
+ const pixels = await filePixels(file);
1994
+ const doc = controller.getState().document;
1995
+ const width = Math.min(doc.width, pixels.width);
1996
+ const r = controller.traceImage(pixels, { mode, colors, width, name: file.name.replace(/\.[^.]+$/, "") || "Image Trace" });
1997
+ setMessage(r.ok ? null : r.message);
1998
+ } catch {
1999
+ setMessage("That file could not be read as an image.");
2000
+ }
2001
+ };
2002
+ return /* @__PURE__ */ jsxs13("div", { className: `space-y-1 ${className ?? ""}`, "data-vector-trace-panel": "", "aria-label": "Image trace", children: [
2003
+ /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1", children: [
2004
+ /* @__PURE__ */ jsx13("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Trace" }),
2005
+ /* @__PURE__ */ jsxs13("select", { "aria-label": "Trace mode", className: selectClass8, value: mode, onChange: (e) => setMode(e.target.value), children: [
2006
+ /* @__PURE__ */ jsx13("option", { value: "threshold", children: "Black & white" }),
2007
+ /* @__PURE__ */ jsx13("option", { value: "color", children: "Colours" })
2008
+ ] }),
2009
+ mode === "color" && /* @__PURE__ */ jsx13("input", { type: "number", min: 2, max: 32, "aria-label": "Trace colours", title: "Colours", className: numberClass7, value: colors, onChange: (e) => setColors(Math.max(2, Math.min(32, parseInt(e.target.value, 10) || 2))) }),
2010
+ /* @__PURE__ */ jsx13("button", { type: "button", className: buttonClass11, "data-vector-trace-action": "", title: "Image Trace: pick an image to turn into paths", onClick: () => input.current?.click(), children: "Image\u2026" }),
2011
+ /* @__PURE__ */ jsx13(
2012
+ "input",
2013
+ {
2014
+ ref: input,
2015
+ type: "file",
2016
+ accept: "image/*",
2017
+ hidden: true,
2018
+ onChange: (e) => {
2019
+ const file = e.target.files?.[0];
2020
+ e.target.value = "";
2021
+ if (file) void trace(file);
2022
+ }
2023
+ }
2024
+ )
2025
+ ] }),
2026
+ message && /* @__PURE__ */ jsx13("p", { className: "text-[11px] text-red-600", children: message })
2027
+ ] });
2028
+ }
2029
+
1928
2030
  // src/vector/VectorGradientEditor.tsx
1929
2031
  import {
1930
2032
  VECTOR_GRADIENT_TYPES,
@@ -1936,12 +2038,12 @@ import {
1936
2038
  VECTOR_GRADIENT_INTERPOLATION_LABELS,
1937
2039
  VECTOR_STROKE_GRADIENT_MODE_LABELS,
1938
2040
  getVectorGradientEditor,
1939
- vectorPaintToHex as vectorPaintToHex3
2041
+ vectorPaintToHex as vectorPaintToHex4
1940
2042
  } from "@pixerate/editor/vector";
1941
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1942
- var selectClass8 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1943
- var numberClass6 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
1944
- var buttonClass10 = "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
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
2044
+ var selectClass9 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2045
+ var numberClass8 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
2046
+ 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";
1945
2047
  var percent = (v) => Math.round(v * 1e3) / 10;
1946
2048
  function VectorGradientEditor({ controller, className }) {
1947
2049
  const state = useVectorEditorState(controller);
@@ -1953,26 +2055,26 @@ function VectorGradientEditor({ controller, className }) {
1953
2055
  const setType = (value) => {
1954
2056
  if (value === "solid") {
1955
2057
  const first = ed.stops[0];
1956
- controller.setStyle({ [ed.target]: vectorPaintToHex3(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2058
+ controller.setStyle({ [ed.target]: vectorPaintToHex4(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
1957
2059
  } else controller.setGradient({ type: value });
1958
2060
  };
1959
- return /* @__PURE__ */ jsxs12("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
1960
- /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5", children: [
1961
- /* @__PURE__ */ jsx12("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
1962
- ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
1963
- ed.mixed && /* @__PURE__ */ jsx12("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2061
+ return /* @__PURE__ */ jsxs14("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2062
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1.5", children: [
2063
+ /* @__PURE__ */ jsx14("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2064
+ ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx14("button", { type: "button", className: buttonClass12, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
2065
+ ed.mixed && /* @__PURE__ */ jsx14("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1964
2066
  ] }),
1965
- /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5", children: [
1966
- /* @__PURE__ */ jsx12("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
1967
- /* @__PURE__ */ jsxs12("select", { "aria-label": "Gradient type", className: selectClass8, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
1968
- /* @__PURE__ */ jsx12("option", { value: "solid", children: "Solid" }),
1969
- VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx12("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
2067
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1.5", children: [
2068
+ /* @__PURE__ */ jsx14("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2069
+ /* @__PURE__ */ jsxs14("select", { "aria-label": "Gradient type", className: selectClass9, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2070
+ /* @__PURE__ */ jsx14("option", { value: "solid", children: "Solid" }),
2071
+ VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx14("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
1970
2072
  ] }),
1971
- /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2073
+ /* @__PURE__ */ jsx14("button", { type: "button", className: buttonClass12, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
1972
2074
  ] }),
1973
- g && /* @__PURE__ */ jsxs12(Fragment3, { children: [
1974
- /* @__PURE__ */ jsxs12("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
1975
- /* @__PURE__ */ jsx12(
2075
+ g && /* @__PURE__ */ jsxs14(Fragment4, { children: [
2076
+ /* @__PURE__ */ jsxs14("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2077
+ /* @__PURE__ */ jsx14(
1976
2078
  "div",
1977
2079
  {
1978
2080
  role: "button",
@@ -1986,7 +2088,7 @@ function VectorGradientEditor({ controller, className }) {
1986
2088
  }
1987
2089
  }
1988
2090
  ),
1989
- ed.stops.map((s) => /* @__PURE__ */ jsx12(
2091
+ ed.stops.map((s) => /* @__PURE__ */ jsx14(
1990
2092
  "button",
1991
2093
  {
1992
2094
  type: "button",
@@ -2000,21 +2102,21 @@ function VectorGradientEditor({ controller, className }) {
2000
2102
  s.index
2001
2103
  ))
2002
2104
  ] }),
2003
- sel && /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2004
- /* @__PURE__ */ jsx12(
2105
+ sel && /* @__PURE__ */ jsxs14("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2106
+ /* @__PURE__ */ jsx14(
2005
2107
  "input",
2006
2108
  {
2007
2109
  type: "color",
2008
2110
  "aria-label": "Stop colour",
2009
2111
  className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
2010
- value: vectorPaintToHex3(sel.color) ?? "#000000",
2112
+ value: vectorPaintToHex4(sel.color) ?? "#000000",
2011
2113
  onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
2012
2114
  onBlur: end
2013
2115
  }
2014
2116
  ),
2015
- /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2117
+ /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2016
2118
  "Opacity",
2017
- /* @__PURE__ */ jsx12(
2119
+ /* @__PURE__ */ jsx14(
2018
2120
  "input",
2019
2121
  {
2020
2122
  type: "number",
@@ -2022,7 +2124,7 @@ function VectorGradientEditor({ controller, className }) {
2022
2124
  max: 100,
2023
2125
  step: 5,
2024
2126
  "aria-label": "Stop opacity",
2025
- className: numberClass6,
2127
+ className: numberClass8,
2026
2128
  value: percent(sel.opacity ?? 1),
2027
2129
  onChange: (e) => {
2028
2130
  const n = parseFloat(e.target.value);
@@ -2031,9 +2133,9 @@ function VectorGradientEditor({ controller, className }) {
2031
2133
  }
2032
2134
  )
2033
2135
  ] }),
2034
- /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2136
+ /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2035
2137
  "At",
2036
- /* @__PURE__ */ jsx12(
2138
+ /* @__PURE__ */ jsx14(
2037
2139
  "input",
2038
2140
  {
2039
2141
  type: "number",
@@ -2041,7 +2143,7 @@ function VectorGradientEditor({ controller, className }) {
2041
2143
  max: 100,
2042
2144
  step: 1,
2043
2145
  "aria-label": "Stop position",
2044
- className: numberClass6,
2146
+ className: numberClass8,
2045
2147
  value: percent(sel.offset),
2046
2148
  onChange: (e) => {
2047
2149
  const n = parseFloat(e.target.value);
@@ -2050,9 +2152,9 @@ function VectorGradientEditor({ controller, className }) {
2050
2152
  }
2051
2153
  )
2052
2154
  ] }),
2053
- !sel.last && /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2155
+ !sel.last && /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2054
2156
  "Midpoint",
2055
- /* @__PURE__ */ jsx12(
2157
+ /* @__PURE__ */ jsx14(
2056
2158
  "input",
2057
2159
  {
2058
2160
  type: "number",
@@ -2060,7 +2162,7 @@ function VectorGradientEditor({ controller, className }) {
2060
2162
  max: 95,
2061
2163
  step: 1,
2062
2164
  "aria-label": "Stop midpoint",
2063
- className: numberClass6,
2165
+ className: numberClass8,
2064
2166
  value: percent(sel.midpoint ?? 0.5),
2065
2167
  onChange: (e) => {
2066
2168
  const n = parseFloat(e.target.value);
@@ -2069,15 +2171,15 @@ function VectorGradientEditor({ controller, className }) {
2069
2171
  }
2070
2172
  )
2071
2173
  ] }),
2072
- /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2174
+ /* @__PURE__ */ jsx14("button", { type: "button", className: buttonClass12, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2073
2175
  ] }),
2074
- /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2075
- /* @__PURE__ */ jsx12("span", { className: "w-16", children: "Colours" }),
2076
- /* @__PURE__ */ jsx12("select", { "aria-label": "Gradient interpolation", className: selectClass8, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx12("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2176
+ /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2177
+ /* @__PURE__ */ jsx14("span", { className: "w-16", children: "Colours" }),
2178
+ /* @__PURE__ */ jsx14("select", { "aria-label": "Gradient interpolation", className: selectClass9, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx14("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2077
2179
  ] }),
2078
- /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2079
- /* @__PURE__ */ jsx12("span", { className: "w-16", children: "Smooth" }),
2080
- /* @__PURE__ */ jsx12(
2180
+ /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2181
+ /* @__PURE__ */ jsx14("span", { className: "w-16", children: "Smooth" }),
2182
+ /* @__PURE__ */ jsx14(
2081
2183
  "input",
2082
2184
  {
2083
2185
  type: "range",
@@ -2093,23 +2195,23 @@ function VectorGradientEditor({ controller, className }) {
2093
2195
  }
2094
2196
  )
2095
2197
  ] }),
2096
- /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2097
- /* @__PURE__ */ jsx12("span", { className: "w-16", children: "Spread" }),
2098
- /* @__PURE__ */ jsx12("select", { "aria-label": "Gradient spread", className: selectClass8, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx12("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2198
+ /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2199
+ /* @__PURE__ */ jsx14("span", { className: "w-16", children: "Spread" }),
2200
+ /* @__PURE__ */ jsx14("select", { "aria-label": "Gradient spread", className: selectClass9, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx14("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2099
2201
  ] }),
2100
- ed.target === "stroke" && /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2101
- /* @__PURE__ */ jsx12("span", { className: "w-16", children: "On stroke" }),
2102
- /* @__PURE__ */ jsx12("select", { "aria-label": "Gradient on stroke", className: selectClass8, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx12("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2202
+ ed.target === "stroke" && /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2203
+ /* @__PURE__ */ jsx14("span", { className: "w-16", children: "On stroke" }),
2204
+ /* @__PURE__ */ jsx14("select", { "aria-label": "Gradient on stroke", className: selectClass9, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx14("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2103
2205
  ] })
2104
2206
  ] })
2105
2207
  ] });
2106
2208
  }
2107
2209
 
2108
2210
  // src/vector/VectorToolbar.tsx
2109
- import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
2211
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2110
2212
  var VECTOR_SNAP_TITLE = "Snap to objects";
2111
2213
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
2112
- var buttonClass11 = "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";
2214
+ 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";
2113
2215
  function VectorToolbar({
2114
2216
  controller,
2115
2217
  tools = VECTOR_TOOLS,
@@ -2127,16 +2229,16 @@ function VectorToolbar({
2127
2229
  const editing = state.editing;
2128
2230
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
2129
2231
  const maskSelected = state.selection.some((id) => isVectorMaskGroup(controller.getNode(id)));
2130
- const [live, setLive] = React7.useState(false);
2232
+ const [live, setLive] = React9.useState(false);
2131
2233
  const booleanDisabled = live ? count < 1 : count < 2;
2132
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx13("button", { type: "button", className: buttonClass11, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2133
- return /* @__PURE__ */ jsxs13("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2134
- /* @__PURE__ */ jsx13("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx13(
2234
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx15("button", { type: "button", className: buttonClass13, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2235
+ return /* @__PURE__ */ jsxs15("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2236
+ /* @__PURE__ */ jsx15("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx15(
2135
2237
  "button",
2136
2238
  {
2137
2239
  type: "button",
2138
2240
  "data-vector-tool": tool,
2139
- className: buttonClass11,
2241
+ className: buttonClass13,
2140
2242
  "aria-pressed": state.tool === tool,
2141
2243
  title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
2142
2244
  "aria-label": vectorToolLabel(tool),
@@ -2145,17 +2247,17 @@ function VectorToolbar({
2145
2247
  },
2146
2248
  tool
2147
2249
  )) }),
2148
- showHistory && /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2250
+ showHistory && /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2149
2251
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
2150
2252
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
2151
2253
  ] }),
2152
- showSnap && /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2153
- /* @__PURE__ */ jsx13(
2254
+ showSnap && /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2255
+ /* @__PURE__ */ jsx15(
2154
2256
  "button",
2155
2257
  {
2156
2258
  type: "button",
2157
2259
  "data-vector-snap": "",
2158
- className: buttonClass11,
2260
+ className: buttonClass13,
2159
2261
  "aria-pressed": state.snap.objects,
2160
2262
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
2161
2263
  "aria-label": VECTOR_SNAP_TITLE,
@@ -2163,7 +2265,7 @@ function VectorToolbar({
2163
2265
  children: "Snap"
2164
2266
  }
2165
2267
  ),
2166
- /* @__PURE__ */ jsxs13(
2268
+ /* @__PURE__ */ jsxs15(
2167
2269
  "select",
2168
2270
  {
2169
2271
  "data-vector-polar": "",
@@ -2173,8 +2275,8 @@ function VectorToolbar({
2173
2275
  value: String(vectorPolarIncrementOf(state.snap.angles) ?? ""),
2174
2276
  onChange: (e) => controller.setSnap({ angles: vectorPolarAngles(Number(e.target.value)) }),
2175
2277
  children: [
2176
- vectorPolarIncrementOf(state.snap.angles) === null && /* @__PURE__ */ jsx13("option", { value: "", children: "Custom" }),
2177
- [90, 45, 30, 15].map((n) => /* @__PURE__ */ jsxs13("option", { value: n, children: [
2278
+ vectorPolarIncrementOf(state.snap.angles) === null && /* @__PURE__ */ jsx15("option", { value: "", children: "Custom" }),
2279
+ [90, 45, 30, 15].map((n) => /* @__PURE__ */ jsxs15("option", { value: n, children: [
2178
2280
  n,
2179
2281
  "\xB0"
2180
2282
  ] }, n))
@@ -2182,13 +2284,13 @@ function VectorToolbar({
2182
2284
  }
2183
2285
  )
2184
2286
  ] }),
2185
- showPathEdit && /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2186
- VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx13(
2287
+ showPathEdit && /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2288
+ VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx15(
2187
2289
  "button",
2188
2290
  {
2189
2291
  type: "button",
2190
2292
  "data-vector-path-action": a.id,
2191
- className: buttonClass11,
2293
+ className: buttonClass13,
2192
2294
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
2193
2295
  "aria-label": a.title,
2194
2296
  disabled: !a.enabled(count, points),
@@ -2197,12 +2299,12 @@ function VectorToolbar({
2197
2299
  },
2198
2300
  a.id
2199
2301
  )),
2200
- VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx13(
2302
+ VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx15(
2201
2303
  "button",
2202
2304
  {
2203
2305
  type: "button",
2204
2306
  "data-vector-edit": t.key,
2205
- className: buttonClass11,
2307
+ className: buttonClass13,
2206
2308
  "aria-pressed": editing[t.key],
2207
2309
  title: t.title,
2208
2310
  "aria-label": t.title,
@@ -2211,9 +2313,9 @@ function VectorToolbar({
2211
2313
  },
2212
2314
  t.key
2213
2315
  )),
2214
- state.tool === "pencil" && /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2316
+ state.tool === "pencil" && /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2215
2317
  "Smoothing",
2216
- /* @__PURE__ */ jsx13(
2318
+ /* @__PURE__ */ jsx15(
2217
2319
  "input",
2218
2320
  {
2219
2321
  type: "range",
@@ -2227,9 +2329,9 @@ function VectorToolbar({
2227
2329
  }
2228
2330
  )
2229
2331
  ] }),
2230
- state.tool === "eraser" && /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2332
+ state.tool === "eraser" && /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2231
2333
  "Size",
2232
- /* @__PURE__ */ jsx13(
2334
+ /* @__PURE__ */ jsx15(
2233
2335
  "input",
2234
2336
  {
2235
2337
  type: "number",
@@ -2245,12 +2347,12 @@ function VectorToolbar({
2245
2347
  }
2246
2348
  )
2247
2349
  ] }),
2248
- state.tool === "paintBucket" && /* @__PURE__ */ jsx13(
2350
+ state.tool === "paintBucket" && /* @__PURE__ */ jsx15(
2249
2351
  "button",
2250
2352
  {
2251
2353
  type: "button",
2252
2354
  "data-vector-fill-gap": "",
2253
- className: buttonClass11,
2355
+ className: buttonClass13,
2254
2356
  "aria-pressed": editing.fillGap > 0,
2255
2357
  title: "Close small gaps between path ends when filling",
2256
2358
  "aria-label": "Close gaps",
@@ -2259,12 +2361,12 @@ function VectorToolbar({
2259
2361
  }
2260
2362
  )
2261
2363
  ] }),
2262
- showDistort && /* @__PURE__ */ jsx13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx13(
2364
+ showDistort && /* @__PURE__ */ jsx15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx15(
2263
2365
  "button",
2264
2366
  {
2265
2367
  type: "button",
2266
2368
  "data-vector-free-distort": "",
2267
- className: buttonClass11,
2369
+ className: buttonClass13,
2268
2370
  "aria-pressed": state.freeDistort,
2269
2371
  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)`,
2270
2372
  "aria-label": "Free Distort",
@@ -2272,15 +2374,15 @@ function VectorToolbar({
2272
2374
  children: "Distort"
2273
2375
  }
2274
2376
  ) }),
2275
- showBlend && /* @__PURE__ */ jsx13(VectorBlendControls, { controller }),
2276
- showArrange && /* @__PURE__ */ jsxs13(Fragment4, { children: [
2277
- /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2278
- /* @__PURE__ */ jsx13(
2377
+ showBlend && /* @__PURE__ */ jsx15(VectorBlendControls, { controller }),
2378
+ showArrange && /* @__PURE__ */ jsxs15(Fragment5, { children: [
2379
+ /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2380
+ /* @__PURE__ */ jsx15(
2279
2381
  "button",
2280
2382
  {
2281
2383
  type: "button",
2282
2384
  "data-vector-live-boolean": "",
2283
- className: buttonClass11,
2385
+ className: buttonClass13,
2284
2386
  "aria-pressed": live,
2285
2387
  title: "Live booleans: keep the shapes editable",
2286
2388
  "aria-label": "Live booleans",
@@ -2297,12 +2399,12 @@ function VectorToolbar({
2297
2399
  act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
2298
2400
  act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
2299
2401
  ] }),
2300
- /* @__PURE__ */ jsx13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx13(
2402
+ /* @__PURE__ */ jsx15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx15(
2301
2403
  "button",
2302
2404
  {
2303
2405
  type: "button",
2304
2406
  "data-vector-pathfinder": a.id,
2305
- className: buttonClass11,
2407
+ className: buttonClass13,
2306
2408
  title: a.title,
2307
2409
  "aria-label": a.title,
2308
2410
  disabled: count < a.min,
@@ -2311,7 +2413,7 @@ function VectorToolbar({
2311
2413
  },
2312
2414
  a.id
2313
2415
  )) }),
2314
- /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2416
+ /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2315
2417
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
2316
2418
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
2317
2419
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -2329,19 +2431,19 @@ function VectorBlendControls({ controller }) {
2329
2431
  const blend = getVectorBlendControls(state);
2330
2432
  const o = blend.options;
2331
2433
  const selected = blend.ids.length > 0;
2332
- return /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2333
- /* @__PURE__ */ jsx13("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass11, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
2334
- /* @__PURE__ */ jsx13(
2434
+ return /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2435
+ /* @__PURE__ */ jsx15("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" }),
2436
+ /* @__PURE__ */ jsx15(
2335
2437
  "select",
2336
2438
  {
2337
2439
  "aria-label": "Blend spacing",
2338
2440
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
2339
2441
  value: o.spacing,
2340
2442
  onChange: (e) => applyVectorBlendOptions(controller, { spacing: e.target.value }),
2341
- children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx13("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2443
+ children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx15("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2342
2444
  }
2343
2445
  ),
2344
- o.spacing !== "smoothColor" && /* @__PURE__ */ jsx13(
2446
+ o.spacing !== "smoothColor" && /* @__PURE__ */ jsx15(
2345
2447
  "input",
2346
2448
  {
2347
2449
  type: "number",
@@ -2357,12 +2459,12 @@ function VectorBlendControls({ controller }) {
2357
2459
  }
2358
2460
  }
2359
2461
  ),
2360
- /* @__PURE__ */ jsx13(
2462
+ /* @__PURE__ */ jsx15(
2361
2463
  "button",
2362
2464
  {
2363
2465
  type: "button",
2364
2466
  "data-vector-blend-action": "orient",
2365
- className: buttonClass11,
2467
+ className: buttonClass13,
2366
2468
  "aria-pressed": o.orientToSpine,
2367
2469
  title: "Orient steps to the spine",
2368
2470
  "aria-label": "Orient to spine",
@@ -2370,12 +2472,12 @@ function VectorBlendControls({ controller }) {
2370
2472
  children: "\u293F"
2371
2473
  }
2372
2474
  ),
2373
- /* @__PURE__ */ jsx13(
2475
+ /* @__PURE__ */ jsx15(
2374
2476
  "button",
2375
2477
  {
2376
2478
  type: "button",
2377
2479
  "data-vector-blend-action": "spine",
2378
- className: buttonClass11,
2480
+ className: buttonClass13,
2379
2481
  title: "Replace Spine (select a blend and a path)",
2380
2482
  "aria-label": "Replace Spine",
2381
2483
  disabled: !blend.canReplaceSpine,
@@ -2383,12 +2485,12 @@ function VectorBlendControls({ controller }) {
2383
2485
  children: "\u2307"
2384
2486
  }
2385
2487
  ),
2386
- /* @__PURE__ */ jsx13("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass11, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2387
- /* @__PURE__ */ jsx13("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass11, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2488
+ /* @__PURE__ */ jsx15("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass13, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2489
+ /* @__PURE__ */ jsx15("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass13, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2388
2490
  ] });
2389
2491
  }
2390
- var selectClass9 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2391
- var numberClass7 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2492
+ var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2493
+ var numberClass9 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2392
2494
  function VectorStylePanel({ controller, className }) {
2393
2495
  const state = useVectorEditorState(controller);
2394
2496
  const style = getVectorSelectionStyle(state);
@@ -2420,17 +2522,17 @@ function VectorStylePanel({ controller, className }) {
2420
2522
  if (style) controller.setWidthProfile(id);
2421
2523
  else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
2422
2524
  };
2423
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx13("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2525
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx15("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2424
2526
  const arrowRow = (end, label) => {
2425
2527
  const key = end === "start" ? "startArrow" : "endArrow";
2426
2528
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
2427
- return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2428
- /* @__PURE__ */ jsx13("span", { className: "w-16 text-xs text-foreground", children: label }),
2429
- /* @__PURE__ */ jsxs13("select", { "aria-label": `${label} arrowhead`, className: selectClass9, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2430
- /* @__PURE__ */ jsx13("option", { value: "", children: "None" }),
2431
- VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx13("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
2529
+ return /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2530
+ /* @__PURE__ */ jsx15("span", { className: "w-16 text-xs text-foreground", children: label }),
2531
+ /* @__PURE__ */ jsxs15("select", { "aria-label": `${label} arrowhead`, className: selectClass10, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2532
+ /* @__PURE__ */ jsx15("option", { value: "", children: "None" }),
2533
+ VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx15("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
2432
2534
  ] }),
2433
- /* @__PURE__ */ jsx13(
2535
+ /* @__PURE__ */ jsx15(
2434
2536
  "input",
2435
2537
  {
2436
2538
  type: "number",
@@ -2438,7 +2540,7 @@ function VectorStylePanel({ controller, className }) {
2438
2540
  step: 0.25,
2439
2541
  "aria-label": `${label} arrowhead scale`,
2440
2542
  title: "Scale (\xD7 stroke width)",
2441
- className: numberClass7,
2543
+ className: numberClass9,
2442
2544
  value: source[scaleKey],
2443
2545
  onChange: (e) => {
2444
2546
  const n = parseFloat(e.target.value);
@@ -2454,14 +2556,14 @@ function VectorStylePanel({ controller, className }) {
2454
2556
  };
2455
2557
  const paintRow = (key, label) => {
2456
2558
  const paint = source[key];
2457
- const hex = vectorPaintToHex4(paint);
2559
+ const hex = vectorPaintToHex5(paint);
2458
2560
  const on = paint !== null && paint !== "none";
2459
- return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2460
- /* @__PURE__ */ jsxs13("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2461
- /* @__PURE__ */ jsx13("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2561
+ return /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2562
+ /* @__PURE__ */ jsxs15("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2563
+ /* @__PURE__ */ jsx15("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2462
2564
  label
2463
2565
  ] }),
2464
- /* @__PURE__ */ jsx13(
2566
+ /* @__PURE__ */ jsx15(
2465
2567
  "input",
2466
2568
  {
2467
2569
  type: "color",
@@ -2472,18 +2574,18 @@ function VectorStylePanel({ controller, className }) {
2472
2574
  onChange: (e) => apply({ [key]: e.target.value })
2473
2575
  }
2474
2576
  ),
2475
- on && !hex && /* @__PURE__ */ jsx13("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2476
- style?.mixed[key] && /* @__PURE__ */ jsx13("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2577
+ on && !hex && /* @__PURE__ */ jsx15("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2578
+ style?.mixed[key] && /* @__PURE__ */ jsx15("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2477
2579
  ] });
2478
2580
  };
2479
- return /* @__PURE__ */ jsxs13("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2480
- /* @__PURE__ */ jsx13("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2581
+ return /* @__PURE__ */ jsxs15("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2582
+ /* @__PURE__ */ jsx15("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2481
2583
  paintRow("fill", "Fill"),
2482
2584
  paintRow("stroke", "Stroke"),
2483
- style && /* @__PURE__ */ jsx13(VectorGradientEditor, { controller }),
2484
- /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2485
- /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Width" }),
2486
- /* @__PURE__ */ jsx13(
2585
+ style && /* @__PURE__ */ jsx15(VectorGradientEditor, { controller }),
2586
+ /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2587
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Width" }),
2588
+ /* @__PURE__ */ jsx15(
2487
2589
  "input",
2488
2590
  {
2489
2591
  type: "number",
@@ -2499,33 +2601,33 @@ function VectorStylePanel({ controller, className }) {
2499
2601
  }
2500
2602
  )
2501
2603
  ] }),
2502
- /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2503
- /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Align" }),
2504
- /* @__PURE__ */ jsx13("select", { "aria-label": "Stroke alignment", className: selectClass9, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS2).map((a) => /* @__PURE__ */ jsx13("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS2[a] }, a)) }),
2604
+ /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2605
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Align" }),
2606
+ /* @__PURE__ */ jsx15("select", { "aria-label": "Stroke alignment", className: selectClass10, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS2).map((a) => /* @__PURE__ */ jsx15("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS2[a] }, a)) }),
2505
2607
  mixed("strokeAlign")
2506
2608
  ] }),
2507
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2508
- /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Profile" }),
2509
- /* @__PURE__ */ jsxs13(
2609
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2610
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Profile" }),
2611
+ /* @__PURE__ */ jsxs15(
2510
2612
  "select",
2511
2613
  {
2512
2614
  "aria-label": "Width profile",
2513
- className: selectClass9,
2615
+ className: selectClass10,
2514
2616
  value: source.widthProfile,
2515
2617
  onChange: (e) => {
2516
2618
  if (e.target.value !== "custom") setProfile(e.target.value);
2517
2619
  },
2518
2620
  children: [
2519
- VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx13("option", { value: p.id, children: p.label }, p.id)),
2520
- source.widthProfile === "custom" && /* @__PURE__ */ jsx13("option", { value: "custom", disabled: true, children: "Custom" })
2621
+ VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx15("option", { value: p.id, children: p.label }, p.id)),
2622
+ source.widthProfile === "custom" && /* @__PURE__ */ jsx15("option", { value: "custom", disabled: true, children: "Custom" })
2521
2623
  ]
2522
2624
  }
2523
2625
  ),
2524
- /* @__PURE__ */ jsx13(
2626
+ /* @__PURE__ */ jsx15(
2525
2627
  "button",
2526
2628
  {
2527
2629
  type: "button",
2528
- className: buttonClass11,
2630
+ className: buttonClass13,
2529
2631
  title: "Reset to uniform width",
2530
2632
  "aria-label": "Reset to uniform width",
2531
2633
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -2535,27 +2637,27 @@ function VectorStylePanel({ controller, className }) {
2535
2637
  ),
2536
2638
  mixed("widthProfile")
2537
2639
  ] }),
2538
- source.hasOpenPath && /* @__PURE__ */ jsxs13(Fragment4, { children: [
2640
+ source.hasOpenPath && /* @__PURE__ */ jsxs15(Fragment5, { children: [
2539
2641
  arrowRow("start", "Start"),
2540
2642
  arrowRow("end", "End"),
2541
- (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2542
- /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Heads" }),
2543
- /* @__PURE__ */ jsxs13("select", { "aria-label": "Arrowhead alignment", className: selectClass9, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2544
- /* @__PURE__ */ jsx13("option", { value: "extend", children: "Tip past end" }),
2545
- /* @__PURE__ */ jsx13("option", { value: "tip", children: "Tip at end" })
2643
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2644
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Heads" }),
2645
+ /* @__PURE__ */ jsxs15("select", { "aria-label": "Arrowhead alignment", className: selectClass10, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2646
+ /* @__PURE__ */ jsx15("option", { value: "extend", children: "Tip past end" }),
2647
+ /* @__PURE__ */ jsx15("option", { value: "tip", children: "Tip at end" })
2546
2648
  ] })
2547
2649
  ] })
2548
2650
  ] }),
2549
- source.corners > 0 && /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2550
- /* @__PURE__ */ jsx13("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2551
- /* @__PURE__ */ jsx13(
2651
+ source.corners > 0 && /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2652
+ /* @__PURE__ */ jsx15("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2653
+ /* @__PURE__ */ jsx15(
2552
2654
  "input",
2553
2655
  {
2554
2656
  type: "number",
2555
2657
  min: 0,
2556
2658
  step: 1,
2557
2659
  "aria-label": "Corner radius",
2558
- className: numberClass7,
2660
+ className: numberClass9,
2559
2661
  value: Math.round(source.cornerRadius * 100) / 100,
2560
2662
  onChange: (e) => {
2561
2663
  const n = parseFloat(e.target.value);
@@ -2563,12 +2665,12 @@ function VectorStylePanel({ controller, className }) {
2563
2665
  }
2564
2666
  }
2565
2667
  ),
2566
- /* @__PURE__ */ jsx13("select", { "aria-label": "Corner kind", className: selectClass9, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx13("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2668
+ /* @__PURE__ */ jsx15("select", { "aria-label": "Corner kind", className: selectClass10, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx15("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2567
2669
  mixed("cornerRadius")
2568
2670
  ] }),
2569
- style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ jsxs13("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: [
2570
- /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Smoothing" }),
2571
- /* @__PURE__ */ jsx13(
2671
+ style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ jsxs15("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: [
2672
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Smoothing" }),
2673
+ /* @__PURE__ */ jsx15(
2572
2674
  "input",
2573
2675
  {
2574
2676
  type: "range",
@@ -2583,9 +2685,9 @@ function VectorStylePanel({ controller, className }) {
2583
2685
  }
2584
2686
  )
2585
2687
  ] }),
2586
- style && /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2587
- /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Opacity" }),
2588
- /* @__PURE__ */ jsx13(
2688
+ style && /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2689
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Opacity" }),
2690
+ /* @__PURE__ */ jsx15(
2589
2691
  "input",
2590
2692
  {
2591
2693
  type: "range",
@@ -2599,26 +2701,28 @@ function VectorStylePanel({ controller, className }) {
2599
2701
  }
2600
2702
  )
2601
2703
  ] }),
2602
- /* @__PURE__ */ jsx13(VectorTextPanel, { controller }),
2603
- style && /* @__PURE__ */ jsx13(VectorAppearancePanel, { controller }),
2604
- style && /* @__PURE__ */ jsx13(VectorEffectsPanel, { controller }),
2605
- style && /* @__PURE__ */ jsx13(VectorEnvelopePanel, { controller }),
2606
- style && /* @__PURE__ */ jsx13(VectorMaskPanel, { controller }),
2607
- /* @__PURE__ */ jsx13(VectorFramePanel, { controller }),
2608
- /* @__PURE__ */ jsx13(VectorConnectorPanel, { controller }),
2609
- /* @__PURE__ */ jsx13(VectorConstraintsPanel, { controller }),
2610
- /* @__PURE__ */ jsx13(VectorSymbolsPanel, { controller })
2704
+ /* @__PURE__ */ jsx15(VectorTextPanel, { controller }),
2705
+ style && /* @__PURE__ */ jsx15(VectorAppearancePanel, { controller }),
2706
+ style && /* @__PURE__ */ jsx15(VectorEffectsPanel, { controller }),
2707
+ style && /* @__PURE__ */ jsx15(VectorEnvelopePanel, { controller }),
2708
+ style && /* @__PURE__ */ jsx15(VectorMaskPanel, { controller }),
2709
+ /* @__PURE__ */ jsx15(VectorFramePanel, { controller }),
2710
+ /* @__PURE__ */ jsx15(VectorConnectorPanel, { controller }),
2711
+ /* @__PURE__ */ jsx15(VectorShadingPanel, { controller }),
2712
+ /* @__PURE__ */ jsx15(VectorTracePanel, { controller }),
2713
+ /* @__PURE__ */ jsx15(VectorConstraintsPanel, { controller }),
2714
+ /* @__PURE__ */ jsx15(VectorSymbolsPanel, { controller })
2611
2715
  ] });
2612
2716
  }
2613
2717
 
2614
2718
  // src/vector/VectorEditor.tsx
2615
- import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
2719
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2616
2720
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
2617
- return /* @__PURE__ */ jsxs14("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2618
- /* @__PURE__ */ jsx14(VectorToolbar, { controller, tools }),
2619
- /* @__PURE__ */ jsxs14("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2620
- /* @__PURE__ */ jsx14("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx14(VectorCanvas, { controller, ...canvasProps }) }),
2621
- showStylePanel && /* @__PURE__ */ jsx14(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
2721
+ return /* @__PURE__ */ jsxs16("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2722
+ /* @__PURE__ */ jsx16(VectorToolbar, { controller, tools }),
2723
+ /* @__PURE__ */ jsxs16("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2724
+ /* @__PURE__ */ jsx16("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx16(VectorCanvas, { controller, ...canvasProps }) }),
2725
+ showStylePanel && /* @__PURE__ */ jsx16(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
2622
2726
  ] })
2623
2727
  ] });
2624
2728
  }
@@ -2638,11 +2742,13 @@ export {
2638
2742
  VectorMaskPanel,
2639
2743
  VectorMaskedNodes,
2640
2744
  VectorSceneNodes,
2745
+ VectorShadingPanel,
2641
2746
  VectorStylePanel,
2642
2747
  VectorSymbolsPanel,
2643
2748
  VectorTextElement,
2644
2749
  VectorTextPanel,
2645
2750
  VectorToolbar,
2751
+ VectorTracePanel,
2646
2752
  VectorWidthPointEditor,
2647
2753
  useVectorCommands,
2648
2754
  useVectorEditor,