@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.
@@ -45,11 +45,13 @@ __export(vector_exports, {
45
45
  VectorMaskPanel: () => VectorMaskPanel,
46
46
  VectorMaskedNodes: () => VectorMaskedNodes,
47
47
  VectorSceneNodes: () => VectorSceneNodes,
48
+ VectorShadingPanel: () => VectorShadingPanel,
48
49
  VectorStylePanel: () => VectorStylePanel,
49
50
  VectorSymbolsPanel: () => VectorSymbolsPanel,
50
51
  VectorTextElement: () => VectorTextElement,
51
52
  VectorTextPanel: () => VectorTextPanel,
52
53
  VectorToolbar: () => VectorToolbar,
54
+ VectorTracePanel: () => VectorTracePanel,
53
55
  VectorWidthPointEditor: () => VectorWidthPointEditor,
54
56
  useVectorCommands: () => useVectorCommands,
55
57
  useVectorEditor: () => useVectorEditor,
@@ -275,48 +277,19 @@ function VectorTextElement({ node }) {
275
277
  )) });
276
278
  }
277
279
  function VectorSceneNodes({ nodes }) {
278
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: nodes.map(
279
- (node) => node.kind === "text" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorTextElement, { node }, node.id) : node.kind === "group" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
280
- "g",
281
- {
282
- "data-vector-id": node.id,
283
- "data-vector-mask": node.mask?.type,
284
- transform: node.transform,
285
- opacity: node.opacity < 1 ? node.opacity : void 0,
286
- filter: node.filter,
287
- style: blendStyle(node.blendMode),
288
- 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 })
289
- },
290
- node.id
291
- ) : node.layers ? (
292
- // Fill and stroke stacks: each layer in order, bottom to top.
293
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
294
- "g",
295
- {
296
- "data-vector-id": node.id,
297
- transform: node.transform,
298
- opacity: node.opacity < 1 ? node.opacity : void 0,
299
- filter: node.filter,
300
- style: blendStyle(node.blendMode),
301
- 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}`))
302
- },
303
- node.id
304
- )
305
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
306
- VectorPaintPath,
307
- {
308
- paint: node,
309
- d: node.d,
310
- fillRule: node.fillRule,
311
- id: node.id,
312
- transform: node.transform,
313
- filter: node.filter,
314
- blendMode: node.blendMode
315
- },
316
- node.id
317
- )
318
- ) });
280
+ 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)) });
319
281
  }
282
+ var VectorSceneNode = import_react3.default.memo(function VectorSceneNode2({ node }) {
283
+ if (node.kind === "text") return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorTextElement, { node });
284
+ if (node.kind === "group")
285
+ 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 }) });
286
+ if (node.layers)
287
+ return (
288
+ // Fill and stroke stacks: each layer in order, bottom to top.
289
+ /* @__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}`)) })
290
+ );
291
+ 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 });
292
+ });
320
293
  function VectorMaskedNodes({ mask, nodes }) {
321
294
  const b = mask.bounds;
322
295
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
@@ -411,7 +384,8 @@ function VectorCanvas({
411
384
  const editingText = state.textEdit?.nodeId ?? null;
412
385
  const fitted = (0, import_react3.useRef)(!fitOnMount);
413
386
  const uid = (0, import_react3.useId)().replace(/:/g, "");
414
- const scene = (0, import_vector3.getVectorSceneRenderData)(state, { idPrefix: `ve${uid}-` });
387
+ const [size, setSize] = (0, import_react3.useState)(null);
388
+ const scene = (0, import_vector3.getVectorSceneRenderData)(state, { idPrefix: `ve${uid}-`, ...size ? { viewportSize: size } : {} });
415
389
  const overlay = (0, import_vector3.getVectorOverlayRenderData)(state);
416
390
  const host = (0, import_vector3.getVectorSymbolEditBackground)(state, { idPrefix: `ve${uid}-host-` });
417
391
  (0, import_react3.useEffect)(() => {
@@ -429,6 +403,18 @@ function VectorCanvas({
429
403
  observer.observe(el);
430
404
  return () => observer.disconnect();
431
405
  }, [controller]);
406
+ (0, import_react3.useEffect)(() => {
407
+ const el = svgRef.current;
408
+ if (!el || typeof ResizeObserver === "undefined") return;
409
+ const measure = () => {
410
+ const r = el.getBoundingClientRect();
411
+ 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 });
412
+ };
413
+ measure();
414
+ const observer = new ResizeObserver(measure);
415
+ observer.observe(el);
416
+ return () => observer.disconnect();
417
+ }, []);
432
418
  (0, import_react3.useEffect)(() => {
433
419
  if (!editingText) return;
434
420
  const t = setTimeout(() => textRef.current?.focus({ preventScroll: true }), 0);
@@ -564,8 +550,8 @@ function VectorCanvas({
564
550
  /* @__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 })
565
551
  ] }, h.index)),
566
552
  overlay.envelope.anchors.map((a) => {
567
- const size = as + (a.hovered || a.active ? 2 : 0);
568
- 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);
553
+ const size2 = as + (a.hovered || a.active ? 2 : 0);
554
+ 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);
569
555
  })
570
556
  ] }),
571
557
  overlay.handles.map((h) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-handle": h.ref.handle, children: [
@@ -573,10 +559,10 @@ function VectorCanvas({
573
559
  /* @__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 })
574
560
  ] }, `${h.ref.nodeId}:${h.ref.subpath}:${h.ref.index}:${h.ref.handle}`)),
575
561
  overlay.anchors.map((a) => {
576
- const size = as + (a.hovered ? 2 : 0);
562
+ const size2 = as + (a.hovered ? 2 : 0);
577
563
  const fill = a.selected ? accentColor : import_vector3.VECTOR_OVERLAY_THEME.handleFill;
578
564
  const key = `${a.ref.nodeId}:${a.ref.subpath}:${a.ref.index}`;
579
- 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);
565
+ 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);
580
566
  }),
581
567
  overlay.cornerWidgets.map((w) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-corner-widget": w.ref.index, "data-corner-kind": w.kind, children: [
582
568
  /* @__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 }),
@@ -693,6 +679,10 @@ function VectorCanvas({
693
679
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M-3 0H3M0 -3V3", stroke: "#dc2626" })
694
680
  ] })
695
681
  ] }),
682
+ overlay.shading.map((sh) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-shading": sh.kind, children: [
683
+ sh.lines.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d, stroke: accentColor, strokeWidth: 1, opacity: 0.7 }, i)),
684
+ 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))
685
+ ] }, `shading:${sh.nodeId}`)),
696
686
  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}`)),
697
687
  overlay.typedEntry && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-typed-entry": "", transform: `translate(${overlay.typedEntry.x + 14} ${overlay.typedEntry.y + 18})`, children: [
698
688
  /* @__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 }),
@@ -828,8 +818,8 @@ function VectorCanvas({
828
818
  }
829
819
 
830
820
  // src/vector/VectorToolbar.tsx
831
- var import_react8 = __toESM(require("react"), 1);
832
- var import_vector14 = require("@pixerate/editor/vector");
821
+ var import_react10 = __toESM(require("react"), 1);
822
+ var import_vector15 = require("@pixerate/editor/vector");
833
823
 
834
824
  // src/vector/VectorEffectsPanel.tsx
835
825
  var import_vector4 = require("@pixerate/editor/vector");
@@ -1920,16 +1910,130 @@ function VectorConnectorPanel({ controller, className }) {
1920
1910
  ] });
1921
1911
  }
1922
1912
 
1923
- // src/vector/VectorGradientEditor.tsx
1913
+ // src/vector/VectorShadingPanel.tsx
1914
+ var import_react8 = require("react");
1924
1915
  var import_vector13 = require("@pixerate/editor/vector");
1925
1916
  var import_jsx_runtime12 = require("react/jsx-runtime");
1917
+ 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";
1918
+ var numberClass6 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
1919
+ function VectorShadingPanel({ controller, className }) {
1920
+ const state = useVectorEditorState(controller);
1921
+ const [rows, setRows] = (0, import_react8.useState)(2);
1922
+ const [columns, setColumns] = (0, import_react8.useState)(2);
1923
+ const path = state.selection.map((id) => controller.getNode(id)).find((n) => n?.type === "path");
1924
+ if (!path) return null;
1925
+ const fill = path.fill;
1926
+ const sel = state.shadingPoint?.nodeId === path.id ? state.shadingPoint : null;
1927
+ const mesh = (0, import_vector13.isVectorMeshGradient)(fill) ? fill : null;
1928
+ const free = (0, import_vector13.isVectorFreeformGradient)(fill) ? fill : null;
1929
+ const color = sel ? mesh ? mesh.colors[sel.index] : free?.points[sel.index]?.color : void 0;
1930
+ const opacity = sel ? mesh ? mesh.opacities?.[sel.index] ?? 1 : free?.points[sel.index]?.opacity ?? 1 : 1;
1931
+ const spread = sel && free ? free.points[sel.index]?.spread ?? 0 : 0;
1932
+ const edit = (patch, merge) => {
1933
+ if (!sel) return;
1934
+ if (mesh) controller.setMeshPoint(path.id, sel.index, patch, merge ? { merge } : {});
1935
+ else controller.setFreeformPoint(path.id, sel.index, patch, merge ? { merge } : {});
1936
+ };
1937
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-shading-panel": path.id, "aria-label": "Mesh and freeform gradient", children: [
1938
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
1939
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Shading" }),
1940
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("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))) }),
1941
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-xs text-muted-foreground", children: "\xD7" }),
1942
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("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))) }),
1943
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("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" }),
1944
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("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" })
1945
+ ] }),
1946
+ (mesh || free) && !sel && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-[11px] text-muted-foreground", children: [
1947
+ "Gradient tool (G): drag points; ",
1948
+ free ? "click inside to add one, Alt-click to remove." : "pick one to colour it."
1949
+ ] }),
1950
+ sel && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap items-center gap-2", "data-vector-shading-point": sel.index, children: [
1951
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "w-16 text-xs text-foreground", children: [
1952
+ "Point ",
1953
+ sel.index
1954
+ ] }),
1955
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("input", { type: "color", "aria-label": "Point colour", className: "h-7 w-10 cursor-pointer rounded border border-border bg-background", value: (0, import_vector13.vectorPaintToHex)(color ?? "#000000") ?? "#000000", onChange: (e) => edit({ color: e.target.value }, "shading-color") }),
1956
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("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") }),
1957
+ free && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1958
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("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") }),
1959
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: buttonClass10, "aria-label": "Remove point", onClick: () => controller.removeFreeformPoint(path.id, sel.index), children: "Remove" })
1960
+ ] })
1961
+ ] })
1962
+ ] });
1963
+ }
1964
+
1965
+ // src/vector/VectorTracePanel.tsx
1966
+ var import_react9 = require("react");
1967
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1968
+ 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";
1926
1969
  var selectClass8 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1927
- var numberClass6 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
1928
- 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";
1970
+ var numberClass7 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
1971
+ async function filePixels(file, max = 1e6) {
1972
+ const bitmap = await createImageBitmap(file);
1973
+ const k = Math.min(1, Math.sqrt(max / (bitmap.width * bitmap.height)));
1974
+ const width = Math.max(1, Math.round(bitmap.width * k));
1975
+ const height = Math.max(1, Math.round(bitmap.height * k));
1976
+ const canvas = document.createElement("canvas");
1977
+ canvas.width = width;
1978
+ canvas.height = height;
1979
+ const ctx = canvas.getContext("2d");
1980
+ ctx.drawImage(bitmap, 0, 0, width, height);
1981
+ return { width, height, data: ctx.getImageData(0, 0, width, height).data };
1982
+ }
1983
+ function VectorTracePanel({ controller, className }) {
1984
+ const input = (0, import_react9.useRef)(null);
1985
+ const [mode, setMode] = (0, import_react9.useState)("threshold");
1986
+ const [colors, setColors] = (0, import_react9.useState)(6);
1987
+ const [message, setMessage] = (0, import_react9.useState)(null);
1988
+ const trace = async (file) => {
1989
+ try {
1990
+ const pixels = await filePixels(file);
1991
+ const doc = controller.getState().document;
1992
+ const width = Math.min(doc.width, pixels.width);
1993
+ const r = controller.traceImage(pixels, { mode, colors, width, name: file.name.replace(/\.[^.]+$/, "") || "Image Trace" });
1994
+ setMessage(r.ok ? null : r.message);
1995
+ } catch {
1996
+ setMessage("That file could not be read as an image.");
1997
+ }
1998
+ };
1999
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `space-y-1 ${className ?? ""}`, "data-vector-trace-panel": "", "aria-label": "Image trace", children: [
2000
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1", children: [
2001
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Trace" }),
2002
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("select", { "aria-label": "Trace mode", className: selectClass8, value: mode, onChange: (e) => setMode(e.target.value), children: [
2003
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "threshold", children: "Black & white" }),
2004
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "color", children: "Colours" })
2005
+ ] }),
2006
+ mode === "color" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("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))) }),
2007
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("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" }),
2008
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2009
+ "input",
2010
+ {
2011
+ ref: input,
2012
+ type: "file",
2013
+ accept: "image/*",
2014
+ hidden: true,
2015
+ onChange: (e) => {
2016
+ const file = e.target.files?.[0];
2017
+ e.target.value = "";
2018
+ if (file) void trace(file);
2019
+ }
2020
+ }
2021
+ )
2022
+ ] }),
2023
+ message && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-[11px] text-red-600", children: message })
2024
+ ] });
2025
+ }
2026
+
2027
+ // src/vector/VectorGradientEditor.tsx
2028
+ var import_vector14 = require("@pixerate/editor/vector");
2029
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2030
+ var selectClass9 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2031
+ var numberClass8 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
2032
+ 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";
1929
2033
  var percent = (v) => Math.round(v * 1e3) / 10;
1930
2034
  function VectorGradientEditor({ controller, className }) {
1931
2035
  const state = useVectorEditorState(controller);
1932
- const ed = (0, import_vector13.getVectorGradientEditor)(state);
2036
+ const ed = (0, import_vector14.getVectorGradientEditor)(state);
1933
2037
  if (!ed) return null;
1934
2038
  const g = ed.gradient;
1935
2039
  const sel = ed.selected;
@@ -1937,26 +2041,26 @@ function VectorGradientEditor({ controller, className }) {
1937
2041
  const setType = (value) => {
1938
2042
  if (value === "solid") {
1939
2043
  const first = ed.stops[0];
1940
- controller.setStyle({ [ed.target]: (0, import_vector13.vectorPaintToHex)(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2044
+ controller.setStyle({ [ed.target]: (0, import_vector14.vectorPaintToHex)(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
1941
2045
  } else controller.setGradient({ type: value });
1942
2046
  };
1943
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
1944
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1.5", children: [
1945
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
1946
- ["fill", "stroke"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: buttonClass10, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
1947
- ed.mixed && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2047
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2048
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2049
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2050
+ ["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)),
2051
+ ed.mixed && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1948
2052
  ] }),
1949
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1.5", children: [
1950
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
1951
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("select", { "aria-label": "Gradient type", className: selectClass8, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
1952
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: "solid", children: "Solid" }),
1953
- import_vector13.VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: t, children: import_vector13.VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
2053
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2054
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2055
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("select", { "aria-label": "Gradient type", className: selectClass9, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2056
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "solid", children: "Solid" }),
2057
+ 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))
1954
2058
  ] }),
1955
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: buttonClass10, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2059
+ /* @__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" })
1956
2060
  ] }),
1957
- g && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1958
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
1959
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2061
+ g && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2062
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2063
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1960
2064
  "div",
1961
2065
  {
1962
2066
  role: "button",
@@ -1970,7 +2074,7 @@ function VectorGradientEditor({ controller, className }) {
1970
2074
  }
1971
2075
  }
1972
2076
  ),
1973
- ed.stops.map((s) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2077
+ ed.stops.map((s) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1974
2078
  "button",
1975
2079
  {
1976
2080
  type: "button",
@@ -1984,21 +2088,21 @@ function VectorGradientEditor({ controller, className }) {
1984
2088
  s.index
1985
2089
  ))
1986
2090
  ] }),
1987
- sel && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
1988
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2091
+ 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: [
2092
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1989
2093
  "input",
1990
2094
  {
1991
2095
  type: "color",
1992
2096
  "aria-label": "Stop colour",
1993
2097
  className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
1994
- value: (0, import_vector13.vectorPaintToHex)(sel.color) ?? "#000000",
2098
+ value: (0, import_vector14.vectorPaintToHex)(sel.color) ?? "#000000",
1995
2099
  onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
1996
2100
  onBlur: end
1997
2101
  }
1998
2102
  ),
1999
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2103
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2000
2104
  "Opacity",
2001
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2105
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2002
2106
  "input",
2003
2107
  {
2004
2108
  type: "number",
@@ -2006,7 +2110,7 @@ function VectorGradientEditor({ controller, className }) {
2006
2110
  max: 100,
2007
2111
  step: 5,
2008
2112
  "aria-label": "Stop opacity",
2009
- className: numberClass6,
2113
+ className: numberClass8,
2010
2114
  value: percent(sel.opacity ?? 1),
2011
2115
  onChange: (e) => {
2012
2116
  const n = parseFloat(e.target.value);
@@ -2015,9 +2119,9 @@ function VectorGradientEditor({ controller, className }) {
2015
2119
  }
2016
2120
  )
2017
2121
  ] }),
2018
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2122
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2019
2123
  "At",
2020
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2124
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2021
2125
  "input",
2022
2126
  {
2023
2127
  type: "number",
@@ -2025,7 +2129,7 @@ function VectorGradientEditor({ controller, className }) {
2025
2129
  max: 100,
2026
2130
  step: 1,
2027
2131
  "aria-label": "Stop position",
2028
- className: numberClass6,
2132
+ className: numberClass8,
2029
2133
  value: percent(sel.offset),
2030
2134
  onChange: (e) => {
2031
2135
  const n = parseFloat(e.target.value);
@@ -2034,9 +2138,9 @@ function VectorGradientEditor({ controller, className }) {
2034
2138
  }
2035
2139
  )
2036
2140
  ] }),
2037
- !sel.last && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2141
+ !sel.last && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2038
2142
  "Midpoint",
2039
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2143
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2040
2144
  "input",
2041
2145
  {
2042
2146
  type: "number",
@@ -2044,7 +2148,7 @@ function VectorGradientEditor({ controller, className }) {
2044
2148
  max: 95,
2045
2149
  step: 1,
2046
2150
  "aria-label": "Stop midpoint",
2047
- className: numberClass6,
2151
+ className: numberClass8,
2048
2152
  value: percent(sel.midpoint ?? 0.5),
2049
2153
  onChange: (e) => {
2050
2154
  const n = parseFloat(e.target.value);
@@ -2053,15 +2157,15 @@ function VectorGradientEditor({ controller, className }) {
2053
2157
  }
2054
2158
  )
2055
2159
  ] }),
2056
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: buttonClass10, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2160
+ /* @__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" })
2057
2161
  ] }),
2058
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2059
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-16", children: "Colours" }),
2060
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("select", { "aria-label": "Gradient interpolation", className: selectClass8, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: import_vector13.VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: i, children: import_vector13.VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2162
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2163
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16", children: "Colours" }),
2164
+ /* @__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)) })
2061
2165
  ] }),
2062
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2063
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-16", children: "Smooth" }),
2064
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2166
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2167
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16", children: "Smooth" }),
2168
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2065
2169
  "input",
2066
2170
  {
2067
2171
  type: "range",
@@ -2077,26 +2181,26 @@ function VectorGradientEditor({ controller, className }) {
2077
2181
  }
2078
2182
  )
2079
2183
  ] }),
2080
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2081
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-16", children: "Spread" }),
2082
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("select", { "aria-label": "Gradient spread", className: selectClass8, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: import_vector13.VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: sp, children: import_vector13.VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2184
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2185
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16", children: "Spread" }),
2186
+ /* @__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)) })
2083
2187
  ] }),
2084
- ed.target === "stroke" && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2085
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-16", children: "On stroke" }),
2086
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("select", { "aria-label": "Gradient on stroke", className: selectClass8, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: import_vector13.VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: m, children: import_vector13.VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2188
+ ed.target === "stroke" && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2189
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16", children: "On stroke" }),
2190
+ /* @__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)) })
2087
2191
  ] })
2088
2192
  ] })
2089
2193
  ] });
2090
2194
  }
2091
2195
 
2092
2196
  // src/vector/VectorToolbar.tsx
2093
- var import_jsx_runtime13 = require("react/jsx-runtime");
2197
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2094
2198
  var VECTOR_SNAP_TITLE = "Snap to objects";
2095
2199
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
2096
- 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";
2200
+ 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";
2097
2201
  function VectorToolbar({
2098
2202
  controller,
2099
- tools = import_vector14.VECTOR_TOOLS,
2203
+ tools = import_vector15.VECTOR_TOOLS,
2100
2204
  showHistory = true,
2101
2205
  showArrange = true,
2102
2206
  showSnap = true,
@@ -2110,36 +2214,36 @@ function VectorToolbar({
2110
2214
  const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
2111
2215
  const editing = state.editing;
2112
2216
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
2113
- const maskSelected = state.selection.some((id) => (0, import_vector14.isVectorMaskGroup)(controller.getNode(id)));
2114
- const [live, setLive] = import_react8.default.useState(false);
2217
+ const maskSelected = state.selection.some((id) => (0, import_vector15.isVectorMaskGroup)(controller.getNode(id)));
2218
+ const [live, setLive] = import_react10.default.useState(false);
2115
2219
  const booleanDisabled = live ? count < 1 : count < 2;
2116
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", className: buttonClass11, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2117
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2118
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2220
+ 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);
2221
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2222
+ /* @__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)(
2119
2223
  "button",
2120
2224
  {
2121
2225
  type: "button",
2122
2226
  "data-vector-tool": tool,
2123
- className: buttonClass11,
2227
+ className: buttonClass13,
2124
2228
  "aria-pressed": state.tool === tool,
2125
- title: `${(0, import_vector14.vectorToolLabel)(tool)} (${import_vector14.VECTOR_TOOL_SHORTCUTS[tool]})`,
2126
- "aria-label": (0, import_vector14.vectorToolLabel)(tool),
2229
+ title: `${(0, import_vector15.vectorToolLabel)(tool)} (${import_vector15.VECTOR_TOOL_SHORTCUTS[tool]})`,
2230
+ "aria-label": (0, import_vector15.vectorToolLabel)(tool),
2127
2231
  onClick: () => controller.setTool(tool),
2128
- children: import_vector14.VECTOR_TOOL_GLYPHS[tool]
2232
+ children: import_vector15.VECTOR_TOOL_GLYPHS[tool]
2129
2233
  },
2130
2234
  tool
2131
2235
  )) }),
2132
- showHistory && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2236
+ showHistory && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2133
2237
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
2134
2238
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
2135
2239
  ] }),
2136
- showSnap && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2137
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2240
+ showSnap && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2241
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2138
2242
  "button",
2139
2243
  {
2140
2244
  type: "button",
2141
2245
  "data-vector-snap": "",
2142
- className: buttonClass11,
2246
+ className: buttonClass13,
2143
2247
  "aria-pressed": state.snap.objects,
2144
2248
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
2145
2249
  "aria-label": VECTOR_SNAP_TITLE,
@@ -2147,18 +2251,18 @@ function VectorToolbar({
2147
2251
  children: "Snap"
2148
2252
  }
2149
2253
  ),
2150
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2254
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2151
2255
  "select",
2152
2256
  {
2153
2257
  "data-vector-polar": "",
2154
2258
  "aria-label": "Polar tracking increment",
2155
2259
  title: "Polar tracking: the angles drawn points snap to from where they leave",
2156
2260
  className: "h-8 rounded-md border border-border bg-background px-1 text-xs",
2157
- value: String((0, import_vector14.vectorPolarIncrementOf)(state.snap.angles) ?? ""),
2158
- onChange: (e) => controller.setSnap({ angles: (0, import_vector14.vectorPolarAngles)(Number(e.target.value)) }),
2261
+ value: String((0, import_vector15.vectorPolarIncrementOf)(state.snap.angles) ?? ""),
2262
+ onChange: (e) => controller.setSnap({ angles: (0, import_vector15.vectorPolarAngles)(Number(e.target.value)) }),
2159
2263
  children: [
2160
- (0, import_vector14.vectorPolarIncrementOf)(state.snap.angles) === null && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "", children: "Custom" }),
2161
- [90, 45, 30, 15].map((n) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("option", { value: n, children: [
2264
+ (0, import_vector15.vectorPolarIncrementOf)(state.snap.angles) === null && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "", children: "Custom" }),
2265
+ [90, 45, 30, 15].map((n) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("option", { value: n, children: [
2162
2266
  n,
2163
2267
  "\xB0"
2164
2268
  ] }, n))
@@ -2166,13 +2270,13 @@ function VectorToolbar({
2166
2270
  }
2167
2271
  )
2168
2272
  ] }),
2169
- showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2170
- import_vector14.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2273
+ showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2274
+ import_vector15.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2171
2275
  "button",
2172
2276
  {
2173
2277
  type: "button",
2174
2278
  "data-vector-path-action": a.id,
2175
- className: buttonClass11,
2279
+ className: buttonClass13,
2176
2280
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
2177
2281
  "aria-label": a.title,
2178
2282
  disabled: !a.enabled(count, points),
@@ -2181,12 +2285,12 @@ function VectorToolbar({
2181
2285
  },
2182
2286
  a.id
2183
2287
  )),
2184
- import_vector14.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2288
+ import_vector15.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2185
2289
  "button",
2186
2290
  {
2187
2291
  type: "button",
2188
2292
  "data-vector-edit": t.key,
2189
- className: buttonClass11,
2293
+ className: buttonClass13,
2190
2294
  "aria-pressed": editing[t.key],
2191
2295
  title: t.title,
2192
2296
  "aria-label": t.title,
@@ -2195,9 +2299,9 @@ function VectorToolbar({
2195
2299
  },
2196
2300
  t.key
2197
2301
  )),
2198
- state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2302
+ state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2199
2303
  "Smoothing",
2200
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2304
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2201
2305
  "input",
2202
2306
  {
2203
2307
  type: "range",
@@ -2211,9 +2315,9 @@ function VectorToolbar({
2211
2315
  }
2212
2316
  )
2213
2317
  ] }),
2214
- state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2318
+ state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2215
2319
  "Size",
2216
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2320
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2217
2321
  "input",
2218
2322
  {
2219
2323
  type: "number",
@@ -2229,26 +2333,26 @@ function VectorToolbar({
2229
2333
  }
2230
2334
  )
2231
2335
  ] }),
2232
- state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2336
+ state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2233
2337
  "button",
2234
2338
  {
2235
2339
  type: "button",
2236
2340
  "data-vector-fill-gap": "",
2237
- className: buttonClass11,
2341
+ className: buttonClass13,
2238
2342
  "aria-pressed": editing.fillGap > 0,
2239
2343
  title: "Close small gaps between path ends when filling",
2240
2344
  "aria-label": "Close gaps",
2241
- onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector14.VECTOR_FILL_GAP }),
2345
+ onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector15.VECTOR_FILL_GAP }),
2242
2346
  children: "Gaps"
2243
2347
  }
2244
2348
  )
2245
2349
  ] }),
2246
- showDistort && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2350
+ 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)(
2247
2351
  "button",
2248
2352
  {
2249
2353
  type: "button",
2250
2354
  "data-vector-free-distort": "",
2251
- className: buttonClass11,
2355
+ className: buttonClass13,
2252
2356
  "aria-pressed": state.freeDistort,
2253
2357
  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)`,
2254
2358
  "aria-label": "Free Distort",
@@ -2256,15 +2360,15 @@ function VectorToolbar({
2256
2360
  children: "Distort"
2257
2361
  }
2258
2362
  ) }),
2259
- showBlend && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VectorBlendControls, { controller }),
2260
- showArrange && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2261
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2262
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2363
+ showBlend && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorBlendControls, { controller }),
2364
+ showArrange && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2365
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2366
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2263
2367
  "button",
2264
2368
  {
2265
2369
  type: "button",
2266
2370
  "data-vector-live-boolean": "",
2267
- className: buttonClass11,
2371
+ className: buttonClass13,
2268
2372
  "aria-pressed": live,
2269
2373
  title: "Live booleans: keep the shapes editable",
2270
2374
  "aria-label": "Live booleans",
@@ -2281,12 +2385,12 @@ function VectorToolbar({
2281
2385
  act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
2282
2386
  act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
2283
2387
  ] }),
2284
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: import_vector14.VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2388
+ /* @__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)(
2285
2389
  "button",
2286
2390
  {
2287
2391
  type: "button",
2288
2392
  "data-vector-pathfinder": a.id,
2289
- className: buttonClass11,
2393
+ className: buttonClass13,
2290
2394
  title: a.title,
2291
2395
  "aria-label": a.title,
2292
2396
  disabled: count < a.min,
@@ -2295,7 +2399,7 @@ function VectorToolbar({
2295
2399
  },
2296
2400
  a.id
2297
2401
  )) }),
2298
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2402
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2299
2403
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
2300
2404
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
2301
2405
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -2310,22 +2414,22 @@ function VectorToolbar({
2310
2414
  }
2311
2415
  function VectorBlendControls({ controller }) {
2312
2416
  const state = useVectorEditorState(controller);
2313
- const blend = (0, import_vector14.getVectorBlendControls)(state);
2417
+ const blend = (0, import_vector15.getVectorBlendControls)(state);
2314
2418
  const o = blend.options;
2315
2419
  const selected = blend.ids.length > 0;
2316
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2317
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("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" }),
2318
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2420
+ 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: [
2421
+ /* @__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" }),
2422
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2319
2423
  "select",
2320
2424
  {
2321
2425
  "aria-label": "Blend spacing",
2322
2426
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
2323
2427
  value: o.spacing,
2324
- onChange: (e) => (0, import_vector14.applyVectorBlendOptions)(controller, { spacing: e.target.value }),
2325
- children: import_vector14.VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: sp, children: import_vector14.VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2428
+ onChange: (e) => (0, import_vector15.applyVectorBlendOptions)(controller, { spacing: e.target.value }),
2429
+ 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))
2326
2430
  }
2327
2431
  ),
2328
- o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2432
+ o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2329
2433
  "input",
2330
2434
  {
2331
2435
  type: "number",
@@ -2337,29 +2441,29 @@ function VectorBlendControls({ controller }) {
2337
2441
  onChange: (e) => {
2338
2442
  const n = parseFloat(e.target.value);
2339
2443
  if (!Number.isFinite(n) || n <= 0) return;
2340
- (0, import_vector14.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
2444
+ (0, import_vector15.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
2341
2445
  }
2342
2446
  }
2343
2447
  ),
2344
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2448
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2345
2449
  "button",
2346
2450
  {
2347
2451
  type: "button",
2348
2452
  "data-vector-blend-action": "orient",
2349
- className: buttonClass11,
2453
+ className: buttonClass13,
2350
2454
  "aria-pressed": o.orientToSpine,
2351
2455
  title: "Orient steps to the spine",
2352
2456
  "aria-label": "Orient to spine",
2353
- onClick: () => (0, import_vector14.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
2457
+ onClick: () => (0, import_vector15.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
2354
2458
  children: "\u293F"
2355
2459
  }
2356
2460
  ),
2357
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2461
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2358
2462
  "button",
2359
2463
  {
2360
2464
  type: "button",
2361
2465
  "data-vector-blend-action": "spine",
2362
- className: buttonClass11,
2466
+ className: buttonClass13,
2363
2467
  title: "Replace Spine (select a blend and a path)",
2364
2468
  "aria-label": "Replace Spine",
2365
2469
  disabled: !blend.canReplaceSpine,
@@ -2367,15 +2471,15 @@ function VectorBlendControls({ controller }) {
2367
2471
  children: "\u2307"
2368
2472
  }
2369
2473
  ),
2370
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass11, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2371
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass11, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2474
+ /* @__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" }),
2475
+ /* @__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" })
2372
2476
  ] });
2373
2477
  }
2374
- var selectClass9 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2375
- var numberClass7 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2478
+ var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2479
+ var numberClass9 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2376
2480
  function VectorStylePanel({ controller, className }) {
2377
2481
  const state = useVectorEditorState(controller);
2378
- const style = (0, import_vector14.getVectorSelectionStyle)(state);
2482
+ const style = (0, import_vector15.getVectorSelectionStyle)(state);
2379
2483
  const d = state.defaults;
2380
2484
  const source = style ?? {
2381
2485
  fill: d.fill ?? null,
@@ -2388,7 +2492,7 @@ function VectorStylePanel({ controller, className }) {
2388
2492
  arrowAlign: d.arrowAlign ?? "extend",
2389
2493
  startArrowScale: d.startArrowScale ?? 1,
2390
2494
  endArrowScale: d.endArrowScale ?? 1,
2391
- widthProfile: (0, import_vector14.widthProfilePresetId)(d.strokeWidthProfile),
2495
+ widthProfile: (0, import_vector15.widthProfilePresetId)(d.strokeWidthProfile),
2392
2496
  cornerRadius: d.cornerRadius,
2393
2497
  cornerKind: d.cornerKind ?? "round",
2394
2498
  cornerSmoothing: 0,
@@ -2402,19 +2506,19 @@ function VectorStylePanel({ controller, className }) {
2402
2506
  };
2403
2507
  const setProfile = (id) => {
2404
2508
  if (style) controller.setWidthProfile(id);
2405
- else controller.setDefaults({ strokeWidthProfile: (0, import_vector14.widthProfilePreset)(id)?.length ? (0, import_vector14.widthProfilePreset)(id) : null });
2509
+ else controller.setDefaults({ strokeWidthProfile: (0, import_vector15.widthProfilePreset)(id)?.length ? (0, import_vector15.widthProfilePreset)(id) : null });
2406
2510
  };
2407
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2511
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2408
2512
  const arrowRow = (end, label) => {
2409
2513
  const key = end === "start" ? "startArrow" : "endArrow";
2410
2514
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
2411
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2412
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
2413
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("select", { "aria-label": `${label} arrowhead`, className: selectClass9, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2414
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "", children: "None" }),
2415
- import_vector14.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: h, children: import_vector14.VECTOR_ARROWHEAD_LABELS[h] }, h))
2515
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2516
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
2517
+ /* @__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: [
2518
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "", children: "None" }),
2519
+ import_vector15.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: h, children: import_vector15.VECTOR_ARROWHEAD_LABELS[h] }, h))
2416
2520
  ] }),
2417
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2521
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2418
2522
  "input",
2419
2523
  {
2420
2524
  type: "number",
@@ -2422,7 +2526,7 @@ function VectorStylePanel({ controller, className }) {
2422
2526
  step: 0.25,
2423
2527
  "aria-label": `${label} arrowhead scale`,
2424
2528
  title: "Scale (\xD7 stroke width)",
2425
- className: numberClass7,
2529
+ className: numberClass9,
2426
2530
  value: source[scaleKey],
2427
2531
  onChange: (e) => {
2428
2532
  const n = parseFloat(e.target.value);
@@ -2438,14 +2542,14 @@ function VectorStylePanel({ controller, className }) {
2438
2542
  };
2439
2543
  const paintRow = (key, label) => {
2440
2544
  const paint = source[key];
2441
- const hex = (0, import_vector14.vectorPaintToHex)(paint);
2545
+ const hex = (0, import_vector15.vectorPaintToHex)(paint);
2442
2546
  const on = paint !== null && paint !== "none";
2443
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2444
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2445
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2547
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2548
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2549
+ /* @__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 }) }),
2446
2550
  label
2447
2551
  ] }),
2448
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2552
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2449
2553
  "input",
2450
2554
  {
2451
2555
  type: "color",
@@ -2456,18 +2560,18 @@ function VectorStylePanel({ controller, className }) {
2456
2560
  onChange: (e) => apply({ [key]: e.target.value })
2457
2561
  }
2458
2562
  ),
2459
- on && !hex && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2460
- style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2563
+ on && !hex && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2564
+ style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2461
2565
  ] });
2462
2566
  };
2463
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2464
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2567
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2568
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2465
2569
  paintRow("fill", "Fill"),
2466
2570
  paintRow("stroke", "Stroke"),
2467
- style && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VectorGradientEditor, { controller }),
2468
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2469
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-16", children: "Width" }),
2470
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2571
+ style && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorGradientEditor, { controller }),
2572
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2573
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Width" }),
2574
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2471
2575
  "input",
2472
2576
  {
2473
2577
  type: "number",
@@ -2483,33 +2587,33 @@ function VectorStylePanel({ controller, className }) {
2483
2587
  }
2484
2588
  )
2485
2589
  ] }),
2486
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2487
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-16", children: "Align" }),
2488
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("select", { "aria-label": "Stroke alignment", className: selectClass9, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(import_vector14.VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: a, children: import_vector14.VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
2590
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2591
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Align" }),
2592
+ /* @__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)) }),
2489
2593
  mixed("strokeAlign")
2490
2594
  ] }),
2491
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2492
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-16", children: "Profile" }),
2493
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2595
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2596
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Profile" }),
2597
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2494
2598
  "select",
2495
2599
  {
2496
2600
  "aria-label": "Width profile",
2497
- className: selectClass9,
2601
+ className: selectClass10,
2498
2602
  value: source.widthProfile,
2499
2603
  onChange: (e) => {
2500
2604
  if (e.target.value !== "custom") setProfile(e.target.value);
2501
2605
  },
2502
2606
  children: [
2503
- import_vector14.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: p.id, children: p.label }, p.id)),
2504
- source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
2607
+ import_vector15.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: p.id, children: p.label }, p.id)),
2608
+ source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
2505
2609
  ]
2506
2610
  }
2507
2611
  ),
2508
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2612
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2509
2613
  "button",
2510
2614
  {
2511
2615
  type: "button",
2512
- className: buttonClass11,
2616
+ className: buttonClass13,
2513
2617
  title: "Reset to uniform width",
2514
2618
  "aria-label": "Reset to uniform width",
2515
2619
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -2519,27 +2623,27 @@ function VectorStylePanel({ controller, className }) {
2519
2623
  ),
2520
2624
  mixed("widthProfile")
2521
2625
  ] }),
2522
- source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2626
+ source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2523
2627
  arrowRow("start", "Start"),
2524
2628
  arrowRow("end", "End"),
2525
- (source.startArrow || source.endArrow) && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2526
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-16", children: "Heads" }),
2527
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass9, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2528
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "extend", children: "Tip past end" }),
2529
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "tip", children: "Tip at end" })
2629
+ (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: [
2630
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Heads" }),
2631
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass10, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2632
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "extend", children: "Tip past end" }),
2633
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "tip", children: "Tip at end" })
2530
2634
  ] })
2531
2635
  ] })
2532
2636
  ] }),
2533
- source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2534
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2535
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2637
+ source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2638
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2639
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2536
2640
  "input",
2537
2641
  {
2538
2642
  type: "number",
2539
2643
  min: 0,
2540
2644
  step: 1,
2541
2645
  "aria-label": "Corner radius",
2542
- className: numberClass7,
2646
+ className: numberClass9,
2543
2647
  value: Math.round(source.cornerRadius * 100) / 100,
2544
2648
  onChange: (e) => {
2545
2649
  const n = parseFloat(e.target.value);
@@ -2547,12 +2651,12 @@ function VectorStylePanel({ controller, className }) {
2547
2651
  }
2548
2652
  }
2549
2653
  ),
2550
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("select", { "aria-label": "Corner kind", className: selectClass9, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: import_vector14.VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: k, children: import_vector14.VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2654
+ /* @__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)) }),
2551
2655
  mixed("cornerRadius")
2552
2656
  ] }),
2553
- style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.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: [
2554
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-16", children: "Smoothing" }),
2555
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2657
+ 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: [
2658
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Smoothing" }),
2659
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2556
2660
  "input",
2557
2661
  {
2558
2662
  type: "range",
@@ -2567,9 +2671,9 @@ function VectorStylePanel({ controller, className }) {
2567
2671
  }
2568
2672
  )
2569
2673
  ] }),
2570
- style && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2571
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-16", children: "Opacity" }),
2572
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2674
+ style && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2675
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16", children: "Opacity" }),
2676
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2573
2677
  "input",
2574
2678
  {
2575
2679
  type: "range",
@@ -2583,26 +2687,28 @@ function VectorStylePanel({ controller, className }) {
2583
2687
  }
2584
2688
  )
2585
2689
  ] }),
2586
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VectorTextPanel, { controller }),
2587
- style && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VectorAppearancePanel, { controller }),
2588
- style && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VectorEffectsPanel, { controller }),
2589
- style && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VectorEnvelopePanel, { controller }),
2590
- style && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VectorMaskPanel, { controller }),
2591
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VectorFramePanel, { controller }),
2592
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VectorConnectorPanel, { controller }),
2593
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VectorConstraintsPanel, { controller }),
2594
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(VectorSymbolsPanel, { controller })
2690
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorTextPanel, { controller }),
2691
+ style && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorAppearancePanel, { controller }),
2692
+ style && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorEffectsPanel, { controller }),
2693
+ style && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorEnvelopePanel, { controller }),
2694
+ style && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorMaskPanel, { controller }),
2695
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorFramePanel, { controller }),
2696
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorConnectorPanel, { controller }),
2697
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorShadingPanel, { controller }),
2698
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorTracePanel, { controller }),
2699
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorConstraintsPanel, { controller }),
2700
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(VectorSymbolsPanel, { controller })
2595
2701
  ] });
2596
2702
  }
2597
2703
 
2598
2704
  // src/vector/VectorEditor.tsx
2599
- var import_jsx_runtime14 = require("react/jsx-runtime");
2705
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2600
2706
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
2601
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2602
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(VectorToolbar, { controller, tools }),
2603
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2604
- /* @__PURE__ */ (0, import_jsx_runtime14.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_runtime14.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
2605
- showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
2707
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2708
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorToolbar, { controller, tools }),
2709
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2710
+ /* @__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 }) }),
2711
+ showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
2606
2712
  ] })
2607
2713
  ] });
2608
2714
  }
@@ -2623,11 +2729,13 @@ function VectorEditor({ controller, tools, showStylePanel = true, height = 520,
2623
2729
  VectorMaskPanel,
2624
2730
  VectorMaskedNodes,
2625
2731
  VectorSceneNodes,
2732
+ VectorShadingPanel,
2626
2733
  VectorStylePanel,
2627
2734
  VectorSymbolsPanel,
2628
2735
  VectorTextElement,
2629
2736
  VectorTextPanel,
2630
2737
  VectorToolbar,
2738
+ VectorTracePanel,
2631
2739
  VectorWidthPointEditor,
2632
2740
  useVectorCommands,
2633
2741
  useVectorEditor,