@pixerate/editor-react 0.73.0 → 0.75.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -645,6 +645,10 @@ function VectorCanvas({
645
645
  /* @__PURE__ */ jsx2("path", { d: "M-3 0H3M0 -3V3", stroke: "#dc2626" })
646
646
  ] })
647
647
  ] }),
648
+ overlay.shading.map((sh) => /* @__PURE__ */ jsxs2("g", { "data-vector-shading": sh.kind, children: [
649
+ sh.lines.map((d, i) => /* @__PURE__ */ jsx2("path", { d, stroke: accentColor, strokeWidth: 1, opacity: 0.7 }, i)),
650
+ 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))
651
+ ] }, `shading:${sh.nodeId}`)),
648
652
  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
653
  overlay.typedEntry && /* @__PURE__ */ jsxs2("g", { "data-vector-typed-entry": "", transform: `translate(${overlay.typedEntry.x + 14} ${overlay.typedEntry.y + 18})`, children: [
650
654
  /* @__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 +784,7 @@ function VectorCanvas({
780
784
  }
781
785
 
782
786
  // src/vector/VectorToolbar.tsx
783
- import React7 from "react";
787
+ import React9 from "react";
784
788
  import {
785
789
  VECTOR_TOOLS,
786
790
  VECTOR_TOOL_GLYPHS,
@@ -803,7 +807,7 @@ import {
803
807
  isVectorMaskGroup,
804
808
  widthProfilePreset,
805
809
  widthProfilePresetId,
806
- vectorPaintToHex as vectorPaintToHex4,
810
+ vectorPaintToHex as vectorPaintToHex5,
807
811
  vectorToolLabel,
808
812
  vectorPolarAngles,
809
813
  vectorPolarIncrementOf
@@ -1925,6 +1929,120 @@ function VectorConnectorPanel({ controller, className }) {
1925
1929
  ] });
1926
1930
  }
1927
1931
 
1932
+ // src/vector/VectorShadingPanel.tsx
1933
+ import { useState as useState4 } from "react";
1934
+ import { isVectorFreeformGradient, isVectorMeshGradient, vectorPaintToHex as vectorPaintToHex3 } from "@pixerate/editor/vector";
1935
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1936
+ 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";
1937
+ var numberClass6 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
1938
+ function VectorShadingPanel({ controller, className }) {
1939
+ const state = useVectorEditorState(controller);
1940
+ const [rows, setRows] = useState4(2);
1941
+ const [columns, setColumns] = useState4(2);
1942
+ const path = state.selection.map((id) => controller.getNode(id)).find((n) => n?.type === "path");
1943
+ if (!path) return null;
1944
+ const fill = path.fill;
1945
+ const sel = state.shadingPoint?.nodeId === path.id ? state.shadingPoint : null;
1946
+ const mesh = isVectorMeshGradient(fill) ? fill : null;
1947
+ const free = isVectorFreeformGradient(fill) ? fill : null;
1948
+ const color = sel ? mesh ? mesh.colors[sel.index] : free?.points[sel.index]?.color : void 0;
1949
+ const opacity = sel ? mesh ? mesh.opacities?.[sel.index] ?? 1 : free?.points[sel.index]?.opacity ?? 1 : 1;
1950
+ const spread = sel && free ? free.points[sel.index]?.spread ?? 0 : 0;
1951
+ const edit = (patch, merge) => {
1952
+ if (!sel) return;
1953
+ if (mesh) controller.setMeshPoint(path.id, sel.index, patch, merge ? { merge } : {});
1954
+ else controller.setFreeformPoint(path.id, sel.index, patch, merge ? { merge } : {});
1955
+ };
1956
+ return /* @__PURE__ */ jsxs12("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-shading-panel": path.id, "aria-label": "Mesh and freeform gradient", children: [
1957
+ /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-1", children: [
1958
+ /* @__PURE__ */ jsx12("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Shading" }),
1959
+ /* @__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))) }),
1960
+ /* @__PURE__ */ jsx12("span", { className: "text-xs text-muted-foreground", children: "\xD7" }),
1961
+ /* @__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))) }),
1962
+ /* @__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" }),
1963
+ /* @__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" })
1964
+ ] }),
1965
+ (mesh || free) && !sel && /* @__PURE__ */ jsxs12("p", { className: "text-[11px] text-muted-foreground", children: [
1966
+ "Gradient tool (G): drag points; ",
1967
+ free ? "click inside to add one, Alt-click to remove." : "pick one to colour it."
1968
+ ] }),
1969
+ sel && /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-2", "data-vector-shading-point": sel.index, children: [
1970
+ /* @__PURE__ */ jsxs12("span", { className: "w-16 text-xs text-foreground", children: [
1971
+ "Point ",
1972
+ sel.index
1973
+ ] }),
1974
+ /* @__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") }),
1975
+ /* @__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") }),
1976
+ free && /* @__PURE__ */ jsxs12(Fragment3, { children: [
1977
+ /* @__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") }),
1978
+ /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "aria-label": "Remove point", onClick: () => controller.removeFreeformPoint(path.id, sel.index), children: "Remove" })
1979
+ ] })
1980
+ ] })
1981
+ ] });
1982
+ }
1983
+
1984
+ // src/vector/VectorTracePanel.tsx
1985
+ import { useRef as useRef4, useState as useState5 } from "react";
1986
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1987
+ 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";
1988
+ var selectClass8 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1989
+ var numberClass7 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
1990
+ async function filePixels(file, max = 1e6) {
1991
+ const bitmap = await createImageBitmap(file);
1992
+ const k = Math.min(1, Math.sqrt(max / (bitmap.width * bitmap.height)));
1993
+ const width = Math.max(1, Math.round(bitmap.width * k));
1994
+ const height = Math.max(1, Math.round(bitmap.height * k));
1995
+ const canvas = document.createElement("canvas");
1996
+ canvas.width = width;
1997
+ canvas.height = height;
1998
+ const ctx = canvas.getContext("2d");
1999
+ ctx.drawImage(bitmap, 0, 0, width, height);
2000
+ return { width, height, data: ctx.getImageData(0, 0, width, height).data };
2001
+ }
2002
+ function VectorTracePanel({ controller, className }) {
2003
+ const input = useRef4(null);
2004
+ const [mode, setMode] = useState5("threshold");
2005
+ const [colors, setColors] = useState5(6);
2006
+ const [message, setMessage] = useState5(null);
2007
+ const trace = async (file) => {
2008
+ try {
2009
+ const pixels = await filePixels(file);
2010
+ const doc = controller.getState().document;
2011
+ const width = Math.min(doc.width, pixels.width);
2012
+ const r = controller.traceImage(pixels, { mode, colors, width, name: file.name.replace(/\.[^.]+$/, "") || "Image Trace" });
2013
+ setMessage(r.ok ? null : r.message);
2014
+ } catch {
2015
+ setMessage("That file could not be read as an image.");
2016
+ }
2017
+ };
2018
+ return /* @__PURE__ */ jsxs13("div", { className: `space-y-1 ${className ?? ""}`, "data-vector-trace-panel": "", "aria-label": "Image trace", children: [
2019
+ /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1", children: [
2020
+ /* @__PURE__ */ jsx13("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Trace" }),
2021
+ /* @__PURE__ */ jsxs13("select", { "aria-label": "Trace mode", className: selectClass8, value: mode, onChange: (e) => setMode(e.target.value), children: [
2022
+ /* @__PURE__ */ jsx13("option", { value: "threshold", children: "Black & white" }),
2023
+ /* @__PURE__ */ jsx13("option", { value: "color", children: "Colours" })
2024
+ ] }),
2025
+ 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))) }),
2026
+ /* @__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" }),
2027
+ /* @__PURE__ */ jsx13(
2028
+ "input",
2029
+ {
2030
+ ref: input,
2031
+ type: "file",
2032
+ accept: "image/*",
2033
+ hidden: true,
2034
+ onChange: (e) => {
2035
+ const file = e.target.files?.[0];
2036
+ e.target.value = "";
2037
+ if (file) void trace(file);
2038
+ }
2039
+ }
2040
+ )
2041
+ ] }),
2042
+ message && /* @__PURE__ */ jsx13("p", { className: "text-[11px] text-red-600", children: message })
2043
+ ] });
2044
+ }
2045
+
1928
2046
  // src/vector/VectorGradientEditor.tsx
1929
2047
  import {
1930
2048
  VECTOR_GRADIENT_TYPES,
@@ -1936,12 +2054,12 @@ import {
1936
2054
  VECTOR_GRADIENT_INTERPOLATION_LABELS,
1937
2055
  VECTOR_STROKE_GRADIENT_MODE_LABELS,
1938
2056
  getVectorGradientEditor,
1939
- vectorPaintToHex as vectorPaintToHex3
2057
+ vectorPaintToHex as vectorPaintToHex4
1940
2058
  } 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";
2059
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
2060
+ var selectClass9 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2061
+ var numberClass8 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
2062
+ 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
2063
  var percent = (v) => Math.round(v * 1e3) / 10;
1946
2064
  function VectorGradientEditor({ controller, className }) {
1947
2065
  const state = useVectorEditorState(controller);
@@ -1953,26 +2071,26 @@ function VectorGradientEditor({ controller, className }) {
1953
2071
  const setType = (value) => {
1954
2072
  if (value === "solid") {
1955
2073
  const first = ed.stops[0];
1956
- controller.setStyle({ [ed.target]: vectorPaintToHex3(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2074
+ controller.setStyle({ [ed.target]: vectorPaintToHex4(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
1957
2075
  } else controller.setGradient({ type: value });
1958
2076
  };
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" })
2077
+ return /* @__PURE__ */ jsxs14("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2078
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1.5", children: [
2079
+ /* @__PURE__ */ jsx14("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2080
+ ["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)),
2081
+ ed.mixed && /* @__PURE__ */ jsx14("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1964
2082
  ] }),
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))
2083
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1.5", children: [
2084
+ /* @__PURE__ */ jsx14("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2085
+ /* @__PURE__ */ jsxs14("select", { "aria-label": "Gradient type", className: selectClass9, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2086
+ /* @__PURE__ */ jsx14("option", { value: "solid", children: "Solid" }),
2087
+ VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx14("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
1970
2088
  ] }),
1971
- /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2089
+ /* @__PURE__ */ jsx14("button", { type: "button", className: buttonClass12, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
1972
2090
  ] }),
1973
- g && /* @__PURE__ */ jsxs12(Fragment3, { children: [
1974
- /* @__PURE__ */ jsxs12("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
1975
- /* @__PURE__ */ jsx12(
2091
+ g && /* @__PURE__ */ jsxs14(Fragment4, { children: [
2092
+ /* @__PURE__ */ jsxs14("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2093
+ /* @__PURE__ */ jsx14(
1976
2094
  "div",
1977
2095
  {
1978
2096
  role: "button",
@@ -1986,7 +2104,7 @@ function VectorGradientEditor({ controller, className }) {
1986
2104
  }
1987
2105
  }
1988
2106
  ),
1989
- ed.stops.map((s) => /* @__PURE__ */ jsx12(
2107
+ ed.stops.map((s) => /* @__PURE__ */ jsx14(
1990
2108
  "button",
1991
2109
  {
1992
2110
  type: "button",
@@ -2000,21 +2118,21 @@ function VectorGradientEditor({ controller, className }) {
2000
2118
  s.index
2001
2119
  ))
2002
2120
  ] }),
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(
2121
+ sel && /* @__PURE__ */ jsxs14("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2122
+ /* @__PURE__ */ jsx14(
2005
2123
  "input",
2006
2124
  {
2007
2125
  type: "color",
2008
2126
  "aria-label": "Stop colour",
2009
2127
  className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
2010
- value: vectorPaintToHex3(sel.color) ?? "#000000",
2128
+ value: vectorPaintToHex4(sel.color) ?? "#000000",
2011
2129
  onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
2012
2130
  onBlur: end
2013
2131
  }
2014
2132
  ),
2015
- /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2133
+ /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2016
2134
  "Opacity",
2017
- /* @__PURE__ */ jsx12(
2135
+ /* @__PURE__ */ jsx14(
2018
2136
  "input",
2019
2137
  {
2020
2138
  type: "number",
@@ -2022,7 +2140,7 @@ function VectorGradientEditor({ controller, className }) {
2022
2140
  max: 100,
2023
2141
  step: 5,
2024
2142
  "aria-label": "Stop opacity",
2025
- className: numberClass6,
2143
+ className: numberClass8,
2026
2144
  value: percent(sel.opacity ?? 1),
2027
2145
  onChange: (e) => {
2028
2146
  const n = parseFloat(e.target.value);
@@ -2031,9 +2149,9 @@ function VectorGradientEditor({ controller, className }) {
2031
2149
  }
2032
2150
  )
2033
2151
  ] }),
2034
- /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2152
+ /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2035
2153
  "At",
2036
- /* @__PURE__ */ jsx12(
2154
+ /* @__PURE__ */ jsx14(
2037
2155
  "input",
2038
2156
  {
2039
2157
  type: "number",
@@ -2041,7 +2159,7 @@ function VectorGradientEditor({ controller, className }) {
2041
2159
  max: 100,
2042
2160
  step: 1,
2043
2161
  "aria-label": "Stop position",
2044
- className: numberClass6,
2162
+ className: numberClass8,
2045
2163
  value: percent(sel.offset),
2046
2164
  onChange: (e) => {
2047
2165
  const n = parseFloat(e.target.value);
@@ -2050,9 +2168,9 @@ function VectorGradientEditor({ controller, className }) {
2050
2168
  }
2051
2169
  )
2052
2170
  ] }),
2053
- !sel.last && /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2171
+ !sel.last && /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2054
2172
  "Midpoint",
2055
- /* @__PURE__ */ jsx12(
2173
+ /* @__PURE__ */ jsx14(
2056
2174
  "input",
2057
2175
  {
2058
2176
  type: "number",
@@ -2060,7 +2178,7 @@ function VectorGradientEditor({ controller, className }) {
2060
2178
  max: 95,
2061
2179
  step: 1,
2062
2180
  "aria-label": "Stop midpoint",
2063
- className: numberClass6,
2181
+ className: numberClass8,
2064
2182
  value: percent(sel.midpoint ?? 0.5),
2065
2183
  onChange: (e) => {
2066
2184
  const n = parseFloat(e.target.value);
@@ -2069,15 +2187,15 @@ function VectorGradientEditor({ controller, className }) {
2069
2187
  }
2070
2188
  )
2071
2189
  ] }),
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" })
2190
+ /* @__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
2191
  ] }),
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)) })
2192
+ /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2193
+ /* @__PURE__ */ jsx14("span", { className: "w-16", children: "Colours" }),
2194
+ /* @__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
2195
  ] }),
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(
2196
+ /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2197
+ /* @__PURE__ */ jsx14("span", { className: "w-16", children: "Smooth" }),
2198
+ /* @__PURE__ */ jsx14(
2081
2199
  "input",
2082
2200
  {
2083
2201
  type: "range",
@@ -2093,23 +2211,23 @@ function VectorGradientEditor({ controller, className }) {
2093
2211
  }
2094
2212
  )
2095
2213
  ] }),
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)) })
2214
+ /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2215
+ /* @__PURE__ */ jsx14("span", { className: "w-16", children: "Spread" }),
2216
+ /* @__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
2217
  ] }),
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)) })
2218
+ ed.target === "stroke" && /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2219
+ /* @__PURE__ */ jsx14("span", { className: "w-16", children: "On stroke" }),
2220
+ /* @__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
2221
  ] })
2104
2222
  ] })
2105
2223
  ] });
2106
2224
  }
2107
2225
 
2108
2226
  // src/vector/VectorToolbar.tsx
2109
- import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
2227
+ import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2110
2228
  var VECTOR_SNAP_TITLE = "Snap to objects";
2111
2229
  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";
2230
+ 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
2231
  function VectorToolbar({
2114
2232
  controller,
2115
2233
  tools = VECTOR_TOOLS,
@@ -2127,16 +2245,16 @@ function VectorToolbar({
2127
2245
  const editing = state.editing;
2128
2246
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
2129
2247
  const maskSelected = state.selection.some((id) => isVectorMaskGroup(controller.getNode(id)));
2130
- const [live, setLive] = React7.useState(false);
2248
+ const [live, setLive] = React9.useState(false);
2131
2249
  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(
2250
+ 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);
2251
+ return /* @__PURE__ */ jsxs15("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2252
+ /* @__PURE__ */ jsx15("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx15(
2135
2253
  "button",
2136
2254
  {
2137
2255
  type: "button",
2138
2256
  "data-vector-tool": tool,
2139
- className: buttonClass11,
2257
+ className: buttonClass13,
2140
2258
  "aria-pressed": state.tool === tool,
2141
2259
  title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
2142
2260
  "aria-label": vectorToolLabel(tool),
@@ -2145,17 +2263,17 @@ function VectorToolbar({
2145
2263
  },
2146
2264
  tool
2147
2265
  )) }),
2148
- showHistory && /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2266
+ showHistory && /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2149
2267
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
2150
2268
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
2151
2269
  ] }),
2152
- showSnap && /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2153
- /* @__PURE__ */ jsx13(
2270
+ showSnap && /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2271
+ /* @__PURE__ */ jsx15(
2154
2272
  "button",
2155
2273
  {
2156
2274
  type: "button",
2157
2275
  "data-vector-snap": "",
2158
- className: buttonClass11,
2276
+ className: buttonClass13,
2159
2277
  "aria-pressed": state.snap.objects,
2160
2278
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
2161
2279
  "aria-label": VECTOR_SNAP_TITLE,
@@ -2163,7 +2281,7 @@ function VectorToolbar({
2163
2281
  children: "Snap"
2164
2282
  }
2165
2283
  ),
2166
- /* @__PURE__ */ jsxs13(
2284
+ /* @__PURE__ */ jsxs15(
2167
2285
  "select",
2168
2286
  {
2169
2287
  "data-vector-polar": "",
@@ -2173,8 +2291,8 @@ function VectorToolbar({
2173
2291
  value: String(vectorPolarIncrementOf(state.snap.angles) ?? ""),
2174
2292
  onChange: (e) => controller.setSnap({ angles: vectorPolarAngles(Number(e.target.value)) }),
2175
2293
  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: [
2294
+ vectorPolarIncrementOf(state.snap.angles) === null && /* @__PURE__ */ jsx15("option", { value: "", children: "Custom" }),
2295
+ [90, 45, 30, 15].map((n) => /* @__PURE__ */ jsxs15("option", { value: n, children: [
2178
2296
  n,
2179
2297
  "\xB0"
2180
2298
  ] }, n))
@@ -2182,13 +2300,13 @@ function VectorToolbar({
2182
2300
  }
2183
2301
  )
2184
2302
  ] }),
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(
2303
+ showPathEdit && /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2304
+ VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx15(
2187
2305
  "button",
2188
2306
  {
2189
2307
  type: "button",
2190
2308
  "data-vector-path-action": a.id,
2191
- className: buttonClass11,
2309
+ className: buttonClass13,
2192
2310
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
2193
2311
  "aria-label": a.title,
2194
2312
  disabled: !a.enabled(count, points),
@@ -2197,12 +2315,12 @@ function VectorToolbar({
2197
2315
  },
2198
2316
  a.id
2199
2317
  )),
2200
- VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx13(
2318
+ VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx15(
2201
2319
  "button",
2202
2320
  {
2203
2321
  type: "button",
2204
2322
  "data-vector-edit": t.key,
2205
- className: buttonClass11,
2323
+ className: buttonClass13,
2206
2324
  "aria-pressed": editing[t.key],
2207
2325
  title: t.title,
2208
2326
  "aria-label": t.title,
@@ -2211,9 +2329,9 @@ function VectorToolbar({
2211
2329
  },
2212
2330
  t.key
2213
2331
  )),
2214
- state.tool === "pencil" && /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2332
+ state.tool === "pencil" && /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2215
2333
  "Smoothing",
2216
- /* @__PURE__ */ jsx13(
2334
+ /* @__PURE__ */ jsx15(
2217
2335
  "input",
2218
2336
  {
2219
2337
  type: "range",
@@ -2227,9 +2345,9 @@ function VectorToolbar({
2227
2345
  }
2228
2346
  )
2229
2347
  ] }),
2230
- state.tool === "eraser" && /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2348
+ state.tool === "eraser" && /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2231
2349
  "Size",
2232
- /* @__PURE__ */ jsx13(
2350
+ /* @__PURE__ */ jsx15(
2233
2351
  "input",
2234
2352
  {
2235
2353
  type: "number",
@@ -2245,12 +2363,12 @@ function VectorToolbar({
2245
2363
  }
2246
2364
  )
2247
2365
  ] }),
2248
- state.tool === "paintBucket" && /* @__PURE__ */ jsx13(
2366
+ state.tool === "paintBucket" && /* @__PURE__ */ jsx15(
2249
2367
  "button",
2250
2368
  {
2251
2369
  type: "button",
2252
2370
  "data-vector-fill-gap": "",
2253
- className: buttonClass11,
2371
+ className: buttonClass13,
2254
2372
  "aria-pressed": editing.fillGap > 0,
2255
2373
  title: "Close small gaps between path ends when filling",
2256
2374
  "aria-label": "Close gaps",
@@ -2259,12 +2377,12 @@ function VectorToolbar({
2259
2377
  }
2260
2378
  )
2261
2379
  ] }),
2262
- showDistort && /* @__PURE__ */ jsx13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx13(
2380
+ showDistort && /* @__PURE__ */ jsx15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx15(
2263
2381
  "button",
2264
2382
  {
2265
2383
  type: "button",
2266
2384
  "data-vector-free-distort": "",
2267
- className: buttonClass11,
2385
+ className: buttonClass13,
2268
2386
  "aria-pressed": state.freeDistort,
2269
2387
  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
2388
  "aria-label": "Free Distort",
@@ -2272,15 +2390,15 @@ function VectorToolbar({
2272
2390
  children: "Distort"
2273
2391
  }
2274
2392
  ) }),
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(
2393
+ showBlend && /* @__PURE__ */ jsx15(VectorBlendControls, { controller }),
2394
+ showArrange && /* @__PURE__ */ jsxs15(Fragment5, { children: [
2395
+ /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2396
+ /* @__PURE__ */ jsx15(
2279
2397
  "button",
2280
2398
  {
2281
2399
  type: "button",
2282
2400
  "data-vector-live-boolean": "",
2283
- className: buttonClass11,
2401
+ className: buttonClass13,
2284
2402
  "aria-pressed": live,
2285
2403
  title: "Live booleans: keep the shapes editable",
2286
2404
  "aria-label": "Live booleans",
@@ -2297,12 +2415,12 @@ function VectorToolbar({
2297
2415
  act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
2298
2416
  act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
2299
2417
  ] }),
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(
2418
+ /* @__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
2419
  "button",
2302
2420
  {
2303
2421
  type: "button",
2304
2422
  "data-vector-pathfinder": a.id,
2305
- className: buttonClass11,
2423
+ className: buttonClass13,
2306
2424
  title: a.title,
2307
2425
  "aria-label": a.title,
2308
2426
  disabled: count < a.min,
@@ -2311,7 +2429,7 @@ function VectorToolbar({
2311
2429
  },
2312
2430
  a.id
2313
2431
  )) }),
2314
- /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2432
+ /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2315
2433
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
2316
2434
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
2317
2435
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -2329,19 +2447,19 @@ function VectorBlendControls({ controller }) {
2329
2447
  const blend = getVectorBlendControls(state);
2330
2448
  const o = blend.options;
2331
2449
  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(
2450
+ return /* @__PURE__ */ jsxs15("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2451
+ /* @__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" }),
2452
+ /* @__PURE__ */ jsx15(
2335
2453
  "select",
2336
2454
  {
2337
2455
  "aria-label": "Blend spacing",
2338
2456
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
2339
2457
  value: o.spacing,
2340
2458
  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))
2459
+ children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx15("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2342
2460
  }
2343
2461
  ),
2344
- o.spacing !== "smoothColor" && /* @__PURE__ */ jsx13(
2462
+ o.spacing !== "smoothColor" && /* @__PURE__ */ jsx15(
2345
2463
  "input",
2346
2464
  {
2347
2465
  type: "number",
@@ -2357,12 +2475,12 @@ function VectorBlendControls({ controller }) {
2357
2475
  }
2358
2476
  }
2359
2477
  ),
2360
- /* @__PURE__ */ jsx13(
2478
+ /* @__PURE__ */ jsx15(
2361
2479
  "button",
2362
2480
  {
2363
2481
  type: "button",
2364
2482
  "data-vector-blend-action": "orient",
2365
- className: buttonClass11,
2483
+ className: buttonClass13,
2366
2484
  "aria-pressed": o.orientToSpine,
2367
2485
  title: "Orient steps to the spine",
2368
2486
  "aria-label": "Orient to spine",
@@ -2370,12 +2488,12 @@ function VectorBlendControls({ controller }) {
2370
2488
  children: "\u293F"
2371
2489
  }
2372
2490
  ),
2373
- /* @__PURE__ */ jsx13(
2491
+ /* @__PURE__ */ jsx15(
2374
2492
  "button",
2375
2493
  {
2376
2494
  type: "button",
2377
2495
  "data-vector-blend-action": "spine",
2378
- className: buttonClass11,
2496
+ className: buttonClass13,
2379
2497
  title: "Replace Spine (select a blend and a path)",
2380
2498
  "aria-label": "Replace Spine",
2381
2499
  disabled: !blend.canReplaceSpine,
@@ -2383,12 +2501,12 @@ function VectorBlendControls({ controller }) {
2383
2501
  children: "\u2307"
2384
2502
  }
2385
2503
  ),
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" })
2504
+ /* @__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" }),
2505
+ /* @__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
2506
  ] });
2389
2507
  }
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";
2508
+ var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2509
+ var numberClass9 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2392
2510
  function VectorStylePanel({ controller, className }) {
2393
2511
  const state = useVectorEditorState(controller);
2394
2512
  const style = getVectorSelectionStyle(state);
@@ -2420,17 +2538,17 @@ function VectorStylePanel({ controller, className }) {
2420
2538
  if (style) controller.setWidthProfile(id);
2421
2539
  else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
2422
2540
  };
2423
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx13("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2541
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx15("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2424
2542
  const arrowRow = (end, label) => {
2425
2543
  const key = end === "start" ? "startArrow" : "endArrow";
2426
2544
  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))
2545
+ return /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2546
+ /* @__PURE__ */ jsx15("span", { className: "w-16 text-xs text-foreground", children: label }),
2547
+ /* @__PURE__ */ jsxs15("select", { "aria-label": `${label} arrowhead`, className: selectClass10, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2548
+ /* @__PURE__ */ jsx15("option", { value: "", children: "None" }),
2549
+ VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx15("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
2432
2550
  ] }),
2433
- /* @__PURE__ */ jsx13(
2551
+ /* @__PURE__ */ jsx15(
2434
2552
  "input",
2435
2553
  {
2436
2554
  type: "number",
@@ -2438,7 +2556,7 @@ function VectorStylePanel({ controller, className }) {
2438
2556
  step: 0.25,
2439
2557
  "aria-label": `${label} arrowhead scale`,
2440
2558
  title: "Scale (\xD7 stroke width)",
2441
- className: numberClass7,
2559
+ className: numberClass9,
2442
2560
  value: source[scaleKey],
2443
2561
  onChange: (e) => {
2444
2562
  const n = parseFloat(e.target.value);
@@ -2454,14 +2572,14 @@ function VectorStylePanel({ controller, className }) {
2454
2572
  };
2455
2573
  const paintRow = (key, label) => {
2456
2574
  const paint = source[key];
2457
- const hex = vectorPaintToHex4(paint);
2575
+ const hex = vectorPaintToHex5(paint);
2458
2576
  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 }) }),
2577
+ return /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2578
+ /* @__PURE__ */ jsxs15("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2579
+ /* @__PURE__ */ jsx15("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2462
2580
  label
2463
2581
  ] }),
2464
- /* @__PURE__ */ jsx13(
2582
+ /* @__PURE__ */ jsx15(
2465
2583
  "input",
2466
2584
  {
2467
2585
  type: "color",
@@ -2472,18 +2590,18 @@ function VectorStylePanel({ controller, className }) {
2472
2590
  onChange: (e) => apply({ [key]: e.target.value })
2473
2591
  }
2474
2592
  ),
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" })
2593
+ on && !hex && /* @__PURE__ */ jsx15("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2594
+ style?.mixed[key] && /* @__PURE__ */ jsx15("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2477
2595
  ] });
2478
2596
  };
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" }),
2597
+ return /* @__PURE__ */ jsxs15("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2598
+ /* @__PURE__ */ jsx15("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2481
2599
  paintRow("fill", "Fill"),
2482
2600
  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(
2601
+ style && /* @__PURE__ */ jsx15(VectorGradientEditor, { controller }),
2602
+ /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2603
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Width" }),
2604
+ /* @__PURE__ */ jsx15(
2487
2605
  "input",
2488
2606
  {
2489
2607
  type: "number",
@@ -2499,33 +2617,33 @@ function VectorStylePanel({ controller, className }) {
2499
2617
  }
2500
2618
  )
2501
2619
  ] }),
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)) }),
2620
+ /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2621
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Align" }),
2622
+ /* @__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
2623
  mixed("strokeAlign")
2506
2624
  ] }),
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(
2625
+ /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2626
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Profile" }),
2627
+ /* @__PURE__ */ jsxs15(
2510
2628
  "select",
2511
2629
  {
2512
2630
  "aria-label": "Width profile",
2513
- className: selectClass9,
2631
+ className: selectClass10,
2514
2632
  value: source.widthProfile,
2515
2633
  onChange: (e) => {
2516
2634
  if (e.target.value !== "custom") setProfile(e.target.value);
2517
2635
  },
2518
2636
  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" })
2637
+ VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx15("option", { value: p.id, children: p.label }, p.id)),
2638
+ source.widthProfile === "custom" && /* @__PURE__ */ jsx15("option", { value: "custom", disabled: true, children: "Custom" })
2521
2639
  ]
2522
2640
  }
2523
2641
  ),
2524
- /* @__PURE__ */ jsx13(
2642
+ /* @__PURE__ */ jsx15(
2525
2643
  "button",
2526
2644
  {
2527
2645
  type: "button",
2528
- className: buttonClass11,
2646
+ className: buttonClass13,
2529
2647
  title: "Reset to uniform width",
2530
2648
  "aria-label": "Reset to uniform width",
2531
2649
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -2535,27 +2653,27 @@ function VectorStylePanel({ controller, className }) {
2535
2653
  ),
2536
2654
  mixed("widthProfile")
2537
2655
  ] }),
2538
- source.hasOpenPath && /* @__PURE__ */ jsxs13(Fragment4, { children: [
2656
+ source.hasOpenPath && /* @__PURE__ */ jsxs15(Fragment5, { children: [
2539
2657
  arrowRow("start", "Start"),
2540
2658
  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" })
2659
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2660
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Heads" }),
2661
+ /* @__PURE__ */ jsxs15("select", { "aria-label": "Arrowhead alignment", className: selectClass10, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2662
+ /* @__PURE__ */ jsx15("option", { value: "extend", children: "Tip past end" }),
2663
+ /* @__PURE__ */ jsx15("option", { value: "tip", children: "Tip at end" })
2546
2664
  ] })
2547
2665
  ] })
2548
2666
  ] }),
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(
2667
+ source.corners > 0 && /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2668
+ /* @__PURE__ */ jsx15("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2669
+ /* @__PURE__ */ jsx15(
2552
2670
  "input",
2553
2671
  {
2554
2672
  type: "number",
2555
2673
  min: 0,
2556
2674
  step: 1,
2557
2675
  "aria-label": "Corner radius",
2558
- className: numberClass7,
2676
+ className: numberClass9,
2559
2677
  value: Math.round(source.cornerRadius * 100) / 100,
2560
2678
  onChange: (e) => {
2561
2679
  const n = parseFloat(e.target.value);
@@ -2563,12 +2681,12 @@ function VectorStylePanel({ controller, className }) {
2563
2681
  }
2564
2682
  }
2565
2683
  ),
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)) }),
2684
+ /* @__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
2685
  mixed("cornerRadius")
2568
2686
  ] }),
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(
2687
+ 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: [
2688
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Smoothing" }),
2689
+ /* @__PURE__ */ jsx15(
2572
2690
  "input",
2573
2691
  {
2574
2692
  type: "range",
@@ -2583,9 +2701,9 @@ function VectorStylePanel({ controller, className }) {
2583
2701
  }
2584
2702
  )
2585
2703
  ] }),
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(
2704
+ style && /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2705
+ /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Opacity" }),
2706
+ /* @__PURE__ */ jsx15(
2589
2707
  "input",
2590
2708
  {
2591
2709
  type: "range",
@@ -2599,26 +2717,28 @@ function VectorStylePanel({ controller, className }) {
2599
2717
  }
2600
2718
  )
2601
2719
  ] }),
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 })
2720
+ /* @__PURE__ */ jsx15(VectorTextPanel, { controller }),
2721
+ style && /* @__PURE__ */ jsx15(VectorAppearancePanel, { controller }),
2722
+ style && /* @__PURE__ */ jsx15(VectorEffectsPanel, { controller }),
2723
+ style && /* @__PURE__ */ jsx15(VectorEnvelopePanel, { controller }),
2724
+ style && /* @__PURE__ */ jsx15(VectorMaskPanel, { controller }),
2725
+ /* @__PURE__ */ jsx15(VectorFramePanel, { controller }),
2726
+ /* @__PURE__ */ jsx15(VectorConnectorPanel, { controller }),
2727
+ /* @__PURE__ */ jsx15(VectorShadingPanel, { controller }),
2728
+ /* @__PURE__ */ jsx15(VectorTracePanel, { controller }),
2729
+ /* @__PURE__ */ jsx15(VectorConstraintsPanel, { controller }),
2730
+ /* @__PURE__ */ jsx15(VectorSymbolsPanel, { controller })
2611
2731
  ] });
2612
2732
  }
2613
2733
 
2614
2734
  // src/vector/VectorEditor.tsx
2615
- import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
2735
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2616
2736
  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" })
2737
+ return /* @__PURE__ */ jsxs16("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2738
+ /* @__PURE__ */ jsx16(VectorToolbar, { controller, tools }),
2739
+ /* @__PURE__ */ jsxs16("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2740
+ /* @__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 }) }),
2741
+ showStylePanel && /* @__PURE__ */ jsx16(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
2622
2742
  ] })
2623
2743
  ] });
2624
2744
  }
@@ -2638,11 +2758,13 @@ export {
2638
2758
  VectorMaskPanel,
2639
2759
  VectorMaskedNodes,
2640
2760
  VectorSceneNodes,
2761
+ VectorShadingPanel,
2641
2762
  VectorStylePanel,
2642
2763
  VectorSymbolsPanel,
2643
2764
  VectorTextElement,
2644
2765
  VectorTextPanel,
2645
2766
  VectorToolbar,
2767
+ VectorTracePanel,
2646
2768
  VectorWidthPointEditor,
2647
2769
  useVectorCommands,
2648
2770
  useVectorEditor,