@pixerate/editor-react 0.83.0 → 0.85.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.
@@ -649,6 +649,11 @@ function VectorCanvas({
649
649
  sh.lines.map((d, i) => /* @__PURE__ */ jsx2("path", { d, stroke: accentColor, strokeWidth: 1, opacity: 0.7 }, i)),
650
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
651
  ] }, `shading:${sh.nodeId}`)),
652
+ overlay.perspective && /* @__PURE__ */ jsxs2("g", { "data-vector-perspective-grid": "", fill: "none", pointerEvents: "none", children: [
653
+ overlay.perspective.planes.map((pl) => /* @__PURE__ */ jsx2("path", { "data-vector-perspective-plane": pl.plane, "data-active": pl.active || void 0, d: pl.d, stroke: pl.active ? accentColor : guideColor, strokeWidth: pl.active ? 1 : 0.75, opacity: pl.active ? 0.55 : 0.25 }, pl.plane)),
654
+ overlay.perspective.vanishingPoints.map((v, i) => /* @__PURE__ */ jsx2("circle", { "data-vector-vanishing-point": i, cx: v.x, cy: v.y, r: 4, stroke: accentColor, strokeWidth: 1.5 }, i)),
655
+ /* @__PURE__ */ jsx2("circle", { "data-vector-perspective-origin": "", cx: overlay.perspective.origin.x, cy: overlay.perspective.origin.y, r: 3, fill: accentColor })
656
+ ] }),
652
657
  overlay.symmetry.map((sy) => /* @__PURE__ */ jsx2("g", { "data-vector-symmetry-axes": sy.nodeId, stroke: accentColor, strokeDasharray: sy.drawing ? void 0 : "6 4", opacity: sy.drawing ? 0.9 : 0.6, children: sy.lines.map((l, i) => /* @__PURE__ */ jsx2("line", { x1: l.x1, y1: l.y1, x2: l.x2, y2: l.y2 }, i)) }, `sym:${sy.nodeId}`)),
653
658
  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}`)),
654
659
  overlay.typedEntry && /* @__PURE__ */ jsxs2("g", { "data-vector-typed-entry": "", transform: `translate(${overlay.typedEntry.x + 14} ${overlay.typedEntry.y + 18})`, children: [
@@ -785,7 +790,7 @@ function VectorCanvas({
785
790
  }
786
791
 
787
792
  // src/vector/VectorToolbar.tsx
788
- import React9 from "react";
793
+ import React10 from "react";
789
794
  import {
790
795
  VECTOR_TOOLS,
791
796
  VECTOR_TOOL_GLYPHS,
@@ -1881,6 +1886,35 @@ function VectorFramePanel({ controller, className }) {
1881
1886
  ] });
1882
1887
  }
1883
1888
 
1889
+ // src/vector/VectorPagesPanel.tsx
1890
+ import { isVectorFrame, vectorMasterPages, vectorPages } from "@pixerate/editor/vector";
1891
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1892
+ var buttonClass9 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-[current=true]:bg-primary aria-[current=true]:text-primary-foreground";
1893
+ var selectClass7 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1894
+ function VectorPagesPanel({ controller, className }) {
1895
+ const state = useVectorEditorState(controller);
1896
+ const pages = vectorPages(state.document);
1897
+ const masters = vectorMasterPages(state.document);
1898
+ const selected = state.selection.map((id) => state.document.nodes.find((n) => n.id === id)).find((n) => isVectorFrame(n));
1899
+ const page = selected && !selected.frame.master ? selected : null;
1900
+ return /* @__PURE__ */ jsxs11("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-pages-panel": "", "aria-label": "Pages", children: [
1901
+ /* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-center gap-1", children: [
1902
+ /* @__PURE__ */ jsx11("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Pages" }),
1903
+ pages.map((p, i) => /* @__PURE__ */ jsx11("button", { type: "button", className: buttonClass9, "aria-current": state.selection.includes(p.id), "data-vector-page": p.id, title: `Go to ${p.name ?? `page ${i + 1}`}`, onClick: () => controller.goToPage(p.id), children: i + 1 }, p.id)),
1904
+ masters.map((m) => /* @__PURE__ */ jsx11("button", { type: "button", className: buttonClass9, "aria-current": state.selection.includes(m.id), "data-vector-master-page": m.id, title: `Go to master page ${m.name ?? ""}`, onClick: () => controller.goToPage(m.id), children: m.name ?? "Master" }, m.id)),
1905
+ /* @__PURE__ */ jsx11("button", { type: "button", className: buttonClass9, "data-vector-pages-action": "add", title: "Add a page after the last", onClick: () => controller.addPage(), children: "+ Page" }),
1906
+ /* @__PURE__ */ jsx11("button", { type: "button", className: buttonClass9, "data-vector-pages-action": "master", title: "Add a master page used by every page (headers, footers, {page} numbers)", onClick: () => controller.addMasterPage({ assignTo: "all" }), children: "+ Master" })
1907
+ ] }),
1908
+ page && masters.length > 0 && /* @__PURE__ */ jsxs11("div", { className: "flex flex-wrap items-center gap-1", children: [
1909
+ /* @__PURE__ */ jsx11("span", { className: "w-16" }),
1910
+ /* @__PURE__ */ jsxs11("select", { "aria-label": "Master page", className: selectClass7, value: page.frame.masterId ?? "", onChange: (e) => controller.setPageMaster(e.target.value || null, { ids: [page.id] }), children: [
1911
+ /* @__PURE__ */ jsx11("option", { value: "", children: "No master" }),
1912
+ masters.map((m) => /* @__PURE__ */ jsx11("option", { value: m.id, children: m.name ?? "Master" }, m.id))
1913
+ ] })
1914
+ ] })
1915
+ ] });
1916
+ }
1917
+
1884
1918
  // src/vector/VectorConnectorPanel.tsx
1885
1919
  import {
1886
1920
  VECTOR_CONNECTOR_ROUTES,
@@ -1889,36 +1923,36 @@ import {
1889
1923
  VECTOR_CONNECTOR_SIDE_LABELS,
1890
1924
  getVectorConnectorPanel
1891
1925
  } from "@pixerate/editor/vector";
1892
- import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1893
- var buttonClass9 = "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";
1894
- var selectClass7 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1926
+ import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1927
+ 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";
1928
+ var selectClass8 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1895
1929
  var numberClass5 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
1896
1930
  function VectorConnectorPanel({ controller, className }) {
1897
1931
  const state = useVectorEditorState(controller);
1898
1932
  const panel = getVectorConnectorPanel(state);
1899
1933
  if (!panel) return null;
1900
1934
  const ids = [panel.id];
1901
- const end = (key, label, e) => /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1", "data-vector-connector-end": key, children: [
1902
- /* @__PURE__ */ jsx11("span", { className: "w-16 text-xs text-foreground", children: label }),
1903
- e ? /* @__PURE__ */ jsxs11(Fragment2, { children: [
1904
- /* @__PURE__ */ jsx11(
1935
+ const end = (key, label, e) => /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1", "data-vector-connector-end": key, children: [
1936
+ /* @__PURE__ */ jsx12("span", { className: "w-16 text-xs text-foreground", children: label }),
1937
+ e ? /* @__PURE__ */ jsxs12(Fragment2, { children: [
1938
+ /* @__PURE__ */ jsx12(
1905
1939
  "select",
1906
1940
  {
1907
1941
  "aria-label": `${label} side`,
1908
- className: selectClass7,
1942
+ className: selectClass8,
1909
1943
  value: e.side ?? "auto",
1910
1944
  onChange: (ev) => controller.setConnector({ [key]: { nodeId: e.nodeId, side: ev.target.value } }, { ids }),
1911
- children: VECTOR_CONNECTOR_SIDES.map((s) => /* @__PURE__ */ jsx11("option", { value: s, children: VECTOR_CONNECTOR_SIDE_LABELS[s] }, s))
1945
+ children: VECTOR_CONNECTOR_SIDES.map((s) => /* @__PURE__ */ jsx12("option", { value: s, children: VECTOR_CONNECTOR_SIDE_LABELS[s] }, s))
1912
1946
  }
1913
1947
  ),
1914
- /* @__PURE__ */ jsx11("button", { type: "button", className: buttonClass9, title: "Unglue: the end stays where it is", "aria-label": `Unglue ${label.toLowerCase()}`, onClick: () => controller.setConnector({ [key]: null }, { ids }), children: "Unglue" })
1915
- ] }) : /* @__PURE__ */ jsx11("span", { className: "text-[11px] text-muted-foreground", children: "Free" })
1948
+ /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, title: "Unglue: the end stays where it is", "aria-label": `Unglue ${label.toLowerCase()}`, onClick: () => controller.setConnector({ [key]: null }, { ids }), children: "Unglue" })
1949
+ ] }) : /* @__PURE__ */ jsx12("span", { className: "text-[11px] text-muted-foreground", children: "Free" })
1916
1950
  ] });
1917
- return /* @__PURE__ */ jsxs11("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-connector-panel": panel.id, "aria-label": "Connector", children: [
1918
- /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1", children: [
1919
- /* @__PURE__ */ jsx11("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Connector" }),
1920
- /* @__PURE__ */ jsx11("select", { "aria-label": "Connector route", className: selectClass7, value: panel.route, onChange: (e) => controller.setConnector({ route: e.target.value }, { ids }), children: VECTOR_CONNECTOR_ROUTES.map((r) => /* @__PURE__ */ jsx11("option", { value: r, children: VECTOR_CONNECTOR_ROUTE_LABELS[r] }, r)) }),
1921
- panel.route === "elbow" && /* @__PURE__ */ jsx11(
1951
+ return /* @__PURE__ */ jsxs12("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-connector-panel": panel.id, "aria-label": "Connector", children: [
1952
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1", children: [
1953
+ /* @__PURE__ */ jsx12("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Connector" }),
1954
+ /* @__PURE__ */ jsx12("select", { "aria-label": "Connector route", className: selectClass8, value: panel.route, onChange: (e) => controller.setConnector({ route: e.target.value }, { ids }), children: VECTOR_CONNECTOR_ROUTES.map((r) => /* @__PURE__ */ jsx12("option", { value: r, children: VECTOR_CONNECTOR_ROUTE_LABELS[r] }, r)) }),
1955
+ panel.route === "elbow" && /* @__PURE__ */ jsx12(
1922
1956
  "input",
1923
1957
  {
1924
1958
  type: "number",
@@ -1943,8 +1977,8 @@ function VectorConnectorPanel({ controller, className }) {
1943
1977
  // src/vector/VectorShadingPanel.tsx
1944
1978
  import { useState as useState5 } from "react";
1945
1979
  import { isVectorFreeformGradient, isVectorMeshGradient, vectorPaintToHex as vectorPaintToHex4 } from "@pixerate/editor/vector";
1946
- import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1947
- 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";
1980
+ import { Fragment as Fragment3, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1981
+ 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";
1948
1982
  var numberClass6 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
1949
1983
  function VectorShadingPanel({ controller, className }) {
1950
1984
  const state = useVectorEditorState(controller);
@@ -1964,29 +1998,29 @@ function VectorShadingPanel({ controller, className }) {
1964
1998
  if (mesh) controller.setMeshPoint(path.id, sel.index, patch, merge ? { merge } : {});
1965
1999
  else controller.setFreeformPoint(path.id, sel.index, patch, merge ? { merge } : {});
1966
2000
  };
1967
- return /* @__PURE__ */ jsxs12("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-shading-panel": path.id, "aria-label": "Mesh and freeform gradient", children: [
1968
- /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-1", children: [
1969
- /* @__PURE__ */ jsx12("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Shading" }),
1970
- /* @__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))) }),
1971
- /* @__PURE__ */ jsx12("span", { className: "text-xs text-muted-foreground", children: "\xD7" }),
1972
- /* @__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))) }),
1973
- /* @__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" }),
1974
- /* @__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" })
2001
+ return /* @__PURE__ */ jsxs13("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-shading-panel": path.id, "aria-label": "Mesh and freeform gradient", children: [
2002
+ /* @__PURE__ */ jsxs13("div", { className: "flex flex-wrap items-center gap-1", children: [
2003
+ /* @__PURE__ */ jsx13("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Shading" }),
2004
+ /* @__PURE__ */ jsx13("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))) }),
2005
+ /* @__PURE__ */ jsx13("span", { className: "text-xs text-muted-foreground", children: "\xD7" }),
2006
+ /* @__PURE__ */ jsx13("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))) }),
2007
+ /* @__PURE__ */ jsx13("button", { type: "button", className: buttonClass11, "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" }),
2008
+ /* @__PURE__ */ jsx13("button", { type: "button", className: buttonClass11, "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" })
1975
2009
  ] }),
1976
- (mesh || free) && !sel && /* @__PURE__ */ jsxs12("p", { className: "text-[11px] text-muted-foreground", children: [
2010
+ (mesh || free) && !sel && /* @__PURE__ */ jsxs13("p", { className: "text-[11px] text-muted-foreground", children: [
1977
2011
  "Gradient tool (G): drag points; ",
1978
2012
  free ? "click inside to add one, Alt-click to remove." : "pick one to colour it."
1979
2013
  ] }),
1980
- sel && /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-2", "data-vector-shading-point": sel.index, children: [
1981
- /* @__PURE__ */ jsxs12("span", { className: "w-16 text-xs text-foreground", children: [
2014
+ sel && /* @__PURE__ */ jsxs13("div", { className: "flex flex-wrap items-center gap-2", "data-vector-shading-point": sel.index, children: [
2015
+ /* @__PURE__ */ jsxs13("span", { className: "w-16 text-xs text-foreground", children: [
1982
2016
  "Point ",
1983
2017
  sel.index
1984
2018
  ] }),
1985
- /* @__PURE__ */ jsx12("input", { type: "color", "aria-label": "Point colour", className: "h-7 w-10 cursor-pointer rounded border border-border bg-background", value: vectorPaintToHex4(color ?? "#000000") ?? "#000000", onChange: (e) => edit({ color: e.target.value }, "shading-color") }),
1986
- /* @__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") }),
1987
- free && /* @__PURE__ */ jsxs12(Fragment3, { children: [
1988
- /* @__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") }),
1989
- /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "aria-label": "Remove point", onClick: () => controller.removeFreeformPoint(path.id, sel.index), children: "Remove" })
2019
+ /* @__PURE__ */ jsx13("input", { type: "color", "aria-label": "Point colour", className: "h-7 w-10 cursor-pointer rounded border border-border bg-background", value: vectorPaintToHex4(color ?? "#000000") ?? "#000000", onChange: (e) => edit({ color: e.target.value }, "shading-color") }),
2020
+ /* @__PURE__ */ jsx13("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") }),
2021
+ free && /* @__PURE__ */ jsxs13(Fragment3, { children: [
2022
+ /* @__PURE__ */ jsx13("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") }),
2023
+ /* @__PURE__ */ jsx13("button", { type: "button", className: buttonClass11, "aria-label": "Remove point", onClick: () => controller.removeFreeformPoint(path.id, sel.index), children: "Remove" })
1990
2024
  ] })
1991
2025
  ] })
1992
2026
  ] });
@@ -1994,9 +2028,9 @@ function VectorShadingPanel({ controller, className }) {
1994
2028
 
1995
2029
  // src/vector/VectorTracePanel.tsx
1996
2030
  import { useRef as useRef4, useState as useState6 } from "react";
1997
- import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1998
- 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";
1999
- var selectClass8 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2031
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
2032
+ var buttonClass12 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40";
2033
+ var selectClass9 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2000
2034
  var numberClass7 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
2001
2035
  async function filePixels(file, max = 1e6) {
2002
2036
  const bitmap = await createImageBitmap(file);
@@ -2026,16 +2060,16 @@ function VectorTracePanel({ controller, className }) {
2026
2060
  setMessage("That file could not be read as an image.");
2027
2061
  }
2028
2062
  };
2029
- return /* @__PURE__ */ jsxs13("div", { className: `space-y-1 ${className ?? ""}`, "data-vector-trace-panel": "", "aria-label": "Image trace", children: [
2030
- /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-1", children: [
2031
- /* @__PURE__ */ jsx13("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Trace" }),
2032
- /* @__PURE__ */ jsxs13("select", { "aria-label": "Trace mode", className: selectClass8, value: mode, onChange: (e) => setMode(e.target.value), children: [
2033
- /* @__PURE__ */ jsx13("option", { value: "threshold", children: "Black & white" }),
2034
- /* @__PURE__ */ jsx13("option", { value: "color", children: "Colours" })
2063
+ return /* @__PURE__ */ jsxs14("div", { className: `space-y-1 ${className ?? ""}`, "data-vector-trace-panel": "", "aria-label": "Image trace", children: [
2064
+ /* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-1", children: [
2065
+ /* @__PURE__ */ jsx14("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Trace" }),
2066
+ /* @__PURE__ */ jsxs14("select", { "aria-label": "Trace mode", className: selectClass9, value: mode, onChange: (e) => setMode(e.target.value), children: [
2067
+ /* @__PURE__ */ jsx14("option", { value: "threshold", children: "Black & white" }),
2068
+ /* @__PURE__ */ jsx14("option", { value: "color", children: "Colours" })
2035
2069
  ] }),
2036
- 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))) }),
2037
- /* @__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" }),
2038
- /* @__PURE__ */ jsx13(
2070
+ mode === "color" && /* @__PURE__ */ jsx14("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))) }),
2071
+ /* @__PURE__ */ jsx14("button", { type: "button", className: buttonClass12, "data-vector-trace-action": "", title: "Image Trace: pick an image to turn into paths", onClick: () => input.current?.click(), children: "Image\u2026" }),
2072
+ /* @__PURE__ */ jsx14(
2039
2073
  "input",
2040
2074
  {
2041
2075
  ref: input,
@@ -2050,15 +2084,15 @@ function VectorTracePanel({ controller, className }) {
2050
2084
  }
2051
2085
  )
2052
2086
  ] }),
2053
- message && /* @__PURE__ */ jsx13("p", { className: "text-[11px] text-red-600", children: message })
2087
+ message && /* @__PURE__ */ jsx14("p", { className: "text-[11px] text-red-600", children: message })
2054
2088
  ] });
2055
2089
  }
2056
2090
 
2057
2091
  // src/vector/VectorSymmetryPanel.tsx
2058
2092
  import { VECTOR_SYMMETRY_MODES, VECTOR_SYMMETRY_MODE_LABELS, isVectorSymmetryGroup } from "@pixerate/editor/vector";
2059
- import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
2060
- var buttonClass12 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
2061
- var selectClass9 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2093
+ import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2094
+ var buttonClass13 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
2095
+ var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2062
2096
  var numberClass8 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
2063
2097
  function VectorSymmetryPanel({ controller, className }) {
2064
2098
  const state = useVectorEditorState(controller);
@@ -2066,35 +2100,80 @@ function VectorSymmetryPanel({ controller, className }) {
2066
2100
  const s = group?.symmetry;
2067
2101
  const ids = group ? [group.id] : void 0;
2068
2102
  const drawing = !!group && state.symmetryTarget === group.id;
2069
- return /* @__PURE__ */ jsxs14("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-symmetry-panel": "", "aria-label": "Symmetry", children: [
2070
- /* @__PURE__ */ jsxs14("div", { className: "flex flex-wrap items-center gap-1", children: [
2071
- /* @__PURE__ */ jsx14("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Symmetry" }),
2072
- !group && /* @__PURE__ */ jsx14("button", { type: "button", className: buttonClass12, "data-vector-symmetry-action": "make", title: "Make Symmetry: the selection (or nothing yet) mirrored live across a vertical axis", onClick: () => controller.makeSymmetry({ mode: "vertical" }), children: "Mirror" }),
2073
- !group && /* @__PURE__ */ jsx14("button", { type: "button", className: buttonClass12, "data-vector-symmetry-action": "radial", title: "Make Symmetry: the selection repeated round its centre", onClick: () => controller.makeSymmetry({ mode: "radial", copies: 6 }), children: "Radial" }),
2074
- group && s && /* @__PURE__ */ jsxs14(Fragment4, { children: [
2075
- /* @__PURE__ */ jsx14("select", { "aria-label": "Symmetry mode", className: selectClass9, value: s.mode, onChange: (e) => controller.setSymmetry({ mode: e.target.value }, { ids }), children: VECTOR_SYMMETRY_MODES.map((m) => /* @__PURE__ */ jsx14("option", { value: m, children: VECTOR_SYMMETRY_MODE_LABELS[m] }, m)) }),
2076
- s.mode === "radial" && /* @__PURE__ */ jsxs14(Fragment4, { children: [
2077
- /* @__PURE__ */ jsx14("input", { type: "number", min: 2, max: 64, "aria-label": "Symmetry copies", title: "Copies round the circle", className: numberClass8, value: s.copies ?? 6, onChange: (e) => controller.setSymmetry({ copies: Math.max(2, Math.min(64, parseInt(e.target.value, 10) || 2)) }, { ids }) }),
2078
- /* @__PURE__ */ jsxs14("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Mirror each copy too (kaleidoscope)", children: [
2079
- /* @__PURE__ */ jsx14("input", { type: "checkbox", "aria-label": "Mirror copies", checked: !!s.mirror, onChange: (e) => controller.setSymmetry({ mirror: e.target.checked }, { ids }) }),
2103
+ return /* @__PURE__ */ jsxs15("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-symmetry-panel": "", "aria-label": "Symmetry", children: [
2104
+ /* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-1", children: [
2105
+ /* @__PURE__ */ jsx15("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Symmetry" }),
2106
+ !group && /* @__PURE__ */ jsx15("button", { type: "button", className: buttonClass13, "data-vector-symmetry-action": "make", title: "Make Symmetry: the selection (or nothing yet) mirrored live across a vertical axis", onClick: () => controller.makeSymmetry({ mode: "vertical" }), children: "Mirror" }),
2107
+ !group && /* @__PURE__ */ jsx15("button", { type: "button", className: buttonClass13, "data-vector-symmetry-action": "radial", title: "Make Symmetry: the selection repeated round its centre", onClick: () => controller.makeSymmetry({ mode: "radial", copies: 6 }), children: "Radial" }),
2108
+ group && s && /* @__PURE__ */ jsxs15(Fragment4, { children: [
2109
+ /* @__PURE__ */ jsx15("select", { "aria-label": "Symmetry mode", className: selectClass10, value: s.mode, onChange: (e) => controller.setSymmetry({ mode: e.target.value }, { ids }), children: VECTOR_SYMMETRY_MODES.map((m) => /* @__PURE__ */ jsx15("option", { value: m, children: VECTOR_SYMMETRY_MODE_LABELS[m] }, m)) }),
2110
+ s.mode === "radial" && /* @__PURE__ */ jsxs15(Fragment4, { children: [
2111
+ /* @__PURE__ */ jsx15("input", { type: "number", min: 2, max: 64, "aria-label": "Symmetry copies", title: "Copies round the circle", className: numberClass8, value: s.copies ?? 6, onChange: (e) => controller.setSymmetry({ copies: Math.max(2, Math.min(64, parseInt(e.target.value, 10) || 2)) }, { ids }) }),
2112
+ /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Mirror each copy too (kaleidoscope)", children: [
2113
+ /* @__PURE__ */ jsx15("input", { type: "checkbox", "aria-label": "Mirror copies", checked: !!s.mirror, onChange: (e) => controller.setSymmetry({ mirror: e.target.checked }, { ids }) }),
2080
2114
  "Mirror"
2081
2115
  ] })
2082
2116
  ] })
2083
2117
  ] })
2084
2118
  ] }),
2085
- group && /* @__PURE__ */ jsxs14("div", { className: "flex flex-wrap items-center gap-1", children: [
2086
- /* @__PURE__ */ jsx14("span", { className: "w-16" }),
2087
- /* @__PURE__ */ jsx14("button", { type: "button", className: buttonClass12, "aria-pressed": drawing, "data-vector-symmetry-action": "draw", title: "Draw inside: new pen, pencil, shape and text art goes into the group, mirrored as you draw (Escape stops)", onClick: () => drawing ? controller.exitSymmetry() : controller.enterSymmetry(group.id), children: "Draw inside" }),
2088
- /* @__PURE__ */ jsx14("button", { type: "button", className: buttonClass12, title: "Expand: keep the copies as real objects", onClick: () => controller.releaseSymmetry({ ids, expand: true }), children: "Expand" }),
2089
- /* @__PURE__ */ jsx14("button", { type: "button", className: buttonClass12, title: "Release: stop repeating (the copies go)", onClick: () => controller.releaseSymmetry({ ids }), children: "Release" })
2119
+ group && /* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-1", children: [
2120
+ /* @__PURE__ */ jsx15("span", { className: "w-16" }),
2121
+ /* @__PURE__ */ jsx15("button", { type: "button", className: buttonClass13, "aria-pressed": drawing, "data-vector-symmetry-action": "draw", title: "Draw inside: new pen, pencil, shape and text art goes into the group, mirrored as you draw (Escape stops)", onClick: () => drawing ? controller.exitSymmetry() : controller.enterSymmetry(group.id), children: "Draw inside" }),
2122
+ /* @__PURE__ */ jsx15("button", { type: "button", className: buttonClass13, title: "Expand: keep the copies as real objects", onClick: () => controller.releaseSymmetry({ ids, expand: true }), children: "Expand" }),
2123
+ /* @__PURE__ */ jsx15("button", { type: "button", className: buttonClass13, title: "Release: stop repeating (the copies go)", onClick: () => controller.releaseSymmetry({ ids }), children: "Release" })
2124
+ ] })
2125
+ ] });
2126
+ }
2127
+
2128
+ // src/vector/VectorPerspectivePanel.tsx
2129
+ import React9 from "react";
2130
+ import { vectorPerspectivePlanes, vectorPerspectivePreset } from "@pixerate/editor/vector";
2131
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2132
+ var buttonClass14 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
2133
+ var selectClass11 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2134
+ var PLANE_LABELS = { floor: "Floor", left: "Left wall", right: "Right wall", side: "Side wall", front: "Front" };
2135
+ function VectorPerspectivePanel({ controller, className }) {
2136
+ const state = useVectorEditorState(controller);
2137
+ const grid = state.document.perspective ?? null;
2138
+ const planes = grid ? vectorPerspectivePlanes(grid) : [];
2139
+ const [attachTo, setAttachTo] = React9.useState("");
2140
+ const target = (state.perspectivePlane ?? attachTo) || planes[0];
2141
+ const add = (n) => controller.setPerspective(vectorPerspectivePreset(n, state.document.width, state.document.height));
2142
+ return /* @__PURE__ */ jsxs16("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-perspective-panel": "", "aria-label": "Perspective", children: [
2143
+ /* @__PURE__ */ jsxs16("div", { className: "flex flex-wrap items-center gap-1", children: [
2144
+ /* @__PURE__ */ jsx16("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Perspective" }),
2145
+ [1, 2, 3].map((n) => /* @__PURE__ */ jsxs16("button", { type: "button", className: buttonClass14, "aria-pressed": grid?.vanishingPoints.length === n, "data-vector-perspective-preset": n, title: `A ${n}-point perspective grid for the page`, onClick: () => add(n), children: [
2146
+ n,
2147
+ "-pt"
2148
+ ] }, n))
2149
+ ] }),
2150
+ grid && /* @__PURE__ */ jsxs16("div", { className: "flex flex-wrap items-center gap-1", children: [
2151
+ /* @__PURE__ */ jsx16("span", { className: "w-16" }),
2152
+ /* @__PURE__ */ jsxs16("select", { "aria-label": "Draw on plane", title: "New rectangles, ellipses, polygons and stars are drawn on this plane", className: selectClass11, value: state.perspectivePlane ?? "", onChange: (e) => controller.setPerspectivePlane(e.target.value || null), children: [
2153
+ /* @__PURE__ */ jsx16("option", { value: "", children: "Draw flat" }),
2154
+ planes.map((p) => /* @__PURE__ */ jsxs16("option", { value: p, children: [
2155
+ "Draw on ",
2156
+ PLANE_LABELS[p].toLowerCase()
2157
+ ] }, p))
2158
+ ] })
2159
+ ] }),
2160
+ grid && /* @__PURE__ */ jsxs16("div", { className: "flex flex-wrap items-center gap-1", children: [
2161
+ /* @__PURE__ */ jsx16("span", { className: "w-16" }),
2162
+ !state.perspectivePlane && /* @__PURE__ */ jsx16("select", { "aria-label": "Attach to plane", className: "h-7 w-24 rounded border border-border bg-background px-1 text-xs", value: attachTo || planes[0], onChange: (e) => setAttachTo(e.target.value), children: planes.map((p) => /* @__PURE__ */ jsx16("option", { value: p, children: PLANE_LABELS[p] }, p)) }),
2163
+ /* @__PURE__ */ jsx16("button", { type: "button", className: buttonClass14, disabled: !state.selection.length, title: "Lay the selection onto the plane (read flat from the grid's origin)", onClick: () => controller.attachToPerspective(target), children: "Attach" }),
2164
+ /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Show the grid on the canvas", children: [
2165
+ /* @__PURE__ */ jsx16("input", { type: "checkbox", "aria-label": "Show perspective grid", checked: grid.visible !== false, onChange: (e) => controller.setPerspective({ ...grid, visible: e.target.checked }) }),
2166
+ "Show"
2167
+ ] }),
2168
+ /* @__PURE__ */ jsx16("button", { type: "button", className: buttonClass14, title: "Remove the grid (art on it stays)", onClick: () => controller.setPerspective(null), children: "Remove" })
2090
2169
  ] })
2091
2170
  ] });
2092
2171
  }
2093
2172
 
2094
2173
  // src/vector/VectorLensPanel.tsx
2095
2174
  import { VECTOR_LENS_LABELS, VECTOR_LENS_SPECS, VECTOR_LENS_TYPES } from "@pixerate/editor/vector";
2096
- import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2097
- var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2175
+ import { jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
2176
+ var selectClass12 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2098
2177
  var numberClass9 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2099
2178
  function VectorLensPanel({ controller, className }) {
2100
2179
  const state = useVectorEditorState(controller);
@@ -2103,26 +2182,26 @@ function VectorLensPanel({ controller, className }) {
2103
2182
  const lens = path.lens ?? null;
2104
2183
  const spec = lens ? VECTOR_LENS_SPECS[lens.type] : null;
2105
2184
  const ids = [path.id];
2106
- return /* @__PURE__ */ jsxs15("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-lens-panel": "", "aria-label": "Lens", children: [
2107
- /* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-1", children: [
2108
- /* @__PURE__ */ jsx15("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Lens" }),
2109
- /* @__PURE__ */ jsxs15(
2185
+ return /* @__PURE__ */ jsxs17("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-lens-panel": "", "aria-label": "Lens", children: [
2186
+ /* @__PURE__ */ jsxs17("div", { className: "flex flex-wrap items-center gap-1", children: [
2187
+ /* @__PURE__ */ jsx17("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Lens" }),
2188
+ /* @__PURE__ */ jsxs17(
2110
2189
  "select",
2111
2190
  {
2112
2191
  "aria-label": "Lens type",
2113
- className: selectClass10,
2192
+ className: selectClass12,
2114
2193
  value: lens?.type ?? "",
2115
2194
  onChange: (e) => e.target.value ? controller.setLens({ type: e.target.value }, { ids }) : controller.setLens(null, { ids }),
2116
2195
  children: [
2117
- /* @__PURE__ */ jsx15("option", { value: "", children: "None" }),
2118
- VECTOR_LENS_TYPES.map((t) => /* @__PURE__ */ jsx15("option", { value: t, children: VECTOR_LENS_LABELS[t] }, t))
2196
+ /* @__PURE__ */ jsx17("option", { value: "", children: "None" }),
2197
+ VECTOR_LENS_TYPES.map((t) => /* @__PURE__ */ jsx17("option", { value: t, children: VECTOR_LENS_LABELS[t] }, t))
2119
2198
  ]
2120
2199
  }
2121
2200
  )
2122
2201
  ] }),
2123
- lens && spec && /* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-1", children: [
2124
- /* @__PURE__ */ jsx15("span", { className: "w-16" }),
2125
- /* @__PURE__ */ jsx15(
2202
+ lens && spec && /* @__PURE__ */ jsxs17("div", { className: "flex flex-wrap items-center gap-1", children: [
2203
+ /* @__PURE__ */ jsx17("span", { className: "w-16" }),
2204
+ /* @__PURE__ */ jsx17(
2126
2205
  "input",
2127
2206
  {
2128
2207
  type: "number",
@@ -2139,7 +2218,7 @@ function VectorLensPanel({ controller, className }) {
2139
2218
  }
2140
2219
  }
2141
2220
  ),
2142
- spec.color !== null && /* @__PURE__ */ jsx15("input", { type: "color", "aria-label": "Lens colour", title: "Lens colour", className: "h-7 w-9 rounded border border-border bg-background", value: lens.color ?? spec.color, onChange: (e) => controller.setLens({ color: e.target.value }, { ids, merge: `lens-color-${path.id}` }) })
2221
+ spec.color !== null && /* @__PURE__ */ jsx17("input", { type: "color", "aria-label": "Lens colour", title: "Lens colour", className: "h-7 w-9 rounded border border-border bg-background", value: lens.color ?? spec.color, onChange: (e) => controller.setLens({ color: e.target.value }, { ids, merge: `lens-color-${path.id}` }) })
2143
2222
  ] })
2144
2223
  ] });
2145
2224
  }
@@ -2157,10 +2236,10 @@ import {
2157
2236
  getVectorGradientEditor,
2158
2237
  vectorPaintToHex as vectorPaintToHex5
2159
2238
  } from "@pixerate/editor/vector";
2160
- import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2161
- var selectClass11 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2239
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
2240
+ var selectClass13 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2162
2241
  var numberClass10 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
2163
- var buttonClass13 = "inline-flex h-7 min-w-7 items-center justify-center rounded border border-border bg-background px-1.5 text-xs text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
2242
+ var buttonClass15 = "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";
2164
2243
  var percent = (v) => Math.round(v * 1e3) / 10;
2165
2244
  function VectorGradientEditor({ controller, className }) {
2166
2245
  const state = useVectorEditorState(controller);
@@ -2175,23 +2254,23 @@ function VectorGradientEditor({ controller, className }) {
2175
2254
  controller.setStyle({ [ed.target]: vectorPaintToHex5(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2176
2255
  } else controller.setGradient({ type: value });
2177
2256
  };
2178
- return /* @__PURE__ */ jsxs16("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2179
- /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-1.5", children: [
2180
- /* @__PURE__ */ jsx16("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2181
- ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx16("button", { type: "button", className: buttonClass13, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
2182
- ed.mixed && /* @__PURE__ */ jsx16("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2257
+ return /* @__PURE__ */ jsxs18("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2258
+ /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-1.5", children: [
2259
+ /* @__PURE__ */ jsx18("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2260
+ ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx18("button", { type: "button", className: buttonClass15, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
2261
+ ed.mixed && /* @__PURE__ */ jsx18("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2183
2262
  ] }),
2184
- /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-1.5", children: [
2185
- /* @__PURE__ */ jsx16("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2186
- /* @__PURE__ */ jsxs16("select", { "aria-label": "Gradient type", className: selectClass11, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2187
- /* @__PURE__ */ jsx16("option", { value: "solid", children: "Solid" }),
2188
- VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx16("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
2263
+ /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-1.5", children: [
2264
+ /* @__PURE__ */ jsx18("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2265
+ /* @__PURE__ */ jsxs18("select", { "aria-label": "Gradient type", className: selectClass13, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2266
+ /* @__PURE__ */ jsx18("option", { value: "solid", children: "Solid" }),
2267
+ VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx18("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
2189
2268
  ] }),
2190
- /* @__PURE__ */ jsx16("button", { type: "button", className: buttonClass13, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2269
+ /* @__PURE__ */ jsx18("button", { type: "button", className: buttonClass15, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2191
2270
  ] }),
2192
- g && /* @__PURE__ */ jsxs16(Fragment5, { children: [
2193
- /* @__PURE__ */ jsxs16("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2194
- /* @__PURE__ */ jsx16(
2271
+ g && /* @__PURE__ */ jsxs18(Fragment5, { children: [
2272
+ /* @__PURE__ */ jsxs18("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2273
+ /* @__PURE__ */ jsx18(
2195
2274
  "div",
2196
2275
  {
2197
2276
  role: "button",
@@ -2205,7 +2284,7 @@ function VectorGradientEditor({ controller, className }) {
2205
2284
  }
2206
2285
  }
2207
2286
  ),
2208
- ed.stops.map((s) => /* @__PURE__ */ jsx16(
2287
+ ed.stops.map((s) => /* @__PURE__ */ jsx18(
2209
2288
  "button",
2210
2289
  {
2211
2290
  type: "button",
@@ -2219,8 +2298,8 @@ function VectorGradientEditor({ controller, className }) {
2219
2298
  s.index
2220
2299
  ))
2221
2300
  ] }),
2222
- sel && /* @__PURE__ */ jsxs16("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2223
- /* @__PURE__ */ jsx16(
2301
+ sel && /* @__PURE__ */ jsxs18("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2302
+ /* @__PURE__ */ jsx18(
2224
2303
  "input",
2225
2304
  {
2226
2305
  type: "color",
@@ -2231,9 +2310,9 @@ function VectorGradientEditor({ controller, className }) {
2231
2310
  onBlur: end
2232
2311
  }
2233
2312
  ),
2234
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2313
+ /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2235
2314
  "Opacity",
2236
- /* @__PURE__ */ jsx16(
2315
+ /* @__PURE__ */ jsx18(
2237
2316
  "input",
2238
2317
  {
2239
2318
  type: "number",
@@ -2250,9 +2329,9 @@ function VectorGradientEditor({ controller, className }) {
2250
2329
  }
2251
2330
  )
2252
2331
  ] }),
2253
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2332
+ /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2254
2333
  "At",
2255
- /* @__PURE__ */ jsx16(
2334
+ /* @__PURE__ */ jsx18(
2256
2335
  "input",
2257
2336
  {
2258
2337
  type: "number",
@@ -2269,9 +2348,9 @@ function VectorGradientEditor({ controller, className }) {
2269
2348
  }
2270
2349
  )
2271
2350
  ] }),
2272
- !sel.last && /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2351
+ !sel.last && /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2273
2352
  "Midpoint",
2274
- /* @__PURE__ */ jsx16(
2353
+ /* @__PURE__ */ jsx18(
2275
2354
  "input",
2276
2355
  {
2277
2356
  type: "number",
@@ -2288,15 +2367,15 @@ function VectorGradientEditor({ controller, className }) {
2288
2367
  }
2289
2368
  )
2290
2369
  ] }),
2291
- /* @__PURE__ */ jsx16("button", { type: "button", className: buttonClass13, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2370
+ /* @__PURE__ */ jsx18("button", { type: "button", className: buttonClass15, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2292
2371
  ] }),
2293
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2294
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Colours" }),
2295
- /* @__PURE__ */ jsx16("select", { "aria-label": "Gradient interpolation", className: selectClass11, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx16("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2372
+ /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2373
+ /* @__PURE__ */ jsx18("span", { className: "w-16", children: "Colours" }),
2374
+ /* @__PURE__ */ jsx18("select", { "aria-label": "Gradient interpolation", className: selectClass13, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx18("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2296
2375
  ] }),
2297
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2298
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Smooth" }),
2299
- /* @__PURE__ */ jsx16(
2376
+ /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2377
+ /* @__PURE__ */ jsx18("span", { className: "w-16", children: "Smooth" }),
2378
+ /* @__PURE__ */ jsx18(
2300
2379
  "input",
2301
2380
  {
2302
2381
  type: "range",
@@ -2312,23 +2391,23 @@ function VectorGradientEditor({ controller, className }) {
2312
2391
  }
2313
2392
  )
2314
2393
  ] }),
2315
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2316
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Spread" }),
2317
- /* @__PURE__ */ jsx16("select", { "aria-label": "Gradient spread", className: selectClass11, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx16("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2394
+ /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2395
+ /* @__PURE__ */ jsx18("span", { className: "w-16", children: "Spread" }),
2396
+ /* @__PURE__ */ jsx18("select", { "aria-label": "Gradient spread", className: selectClass13, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx18("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2318
2397
  ] }),
2319
- ed.target === "stroke" && /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2320
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "On stroke" }),
2321
- /* @__PURE__ */ jsx16("select", { "aria-label": "Gradient on stroke", className: selectClass11, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx16("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2398
+ ed.target === "stroke" && /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2399
+ /* @__PURE__ */ jsx18("span", { className: "w-16", children: "On stroke" }),
2400
+ /* @__PURE__ */ jsx18("select", { "aria-label": "Gradient on stroke", className: selectClass13, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx18("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2322
2401
  ] })
2323
2402
  ] })
2324
2403
  ] });
2325
2404
  }
2326
2405
 
2327
2406
  // src/vector/VectorToolbar.tsx
2328
- import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
2407
+ import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2329
2408
  var VECTOR_SNAP_TITLE = "Snap to objects";
2330
2409
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
2331
- var buttonClass14 = "inline-flex h-8 min-w-8 items-center justify-center rounded-md border border-border bg-background px-2 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
2410
+ var buttonClass16 = "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";
2332
2411
  function VectorToolbar({
2333
2412
  controller,
2334
2413
  tools = VECTOR_TOOLS,
@@ -2346,16 +2425,16 @@ function VectorToolbar({
2346
2425
  const editing = state.editing;
2347
2426
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
2348
2427
  const maskSelected = state.selection.some((id) => isVectorMaskGroup(controller.getNode(id)));
2349
- const [live, setLive] = React9.useState(false);
2428
+ const [live, setLive] = React10.useState(false);
2350
2429
  const booleanDisabled = live ? count < 1 : count < 2;
2351
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx17("button", { type: "button", className: buttonClass14, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2352
- return /* @__PURE__ */ jsxs17("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2353
- /* @__PURE__ */ jsx17("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx17(
2430
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx19("button", { type: "button", className: buttonClass16, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2431
+ return /* @__PURE__ */ jsxs19("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2432
+ /* @__PURE__ */ jsx19("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx19(
2354
2433
  "button",
2355
2434
  {
2356
2435
  type: "button",
2357
2436
  "data-vector-tool": tool,
2358
- className: buttonClass14,
2437
+ className: buttonClass16,
2359
2438
  "aria-pressed": state.tool === tool,
2360
2439
  title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
2361
2440
  "aria-label": vectorToolLabel(tool),
@@ -2364,17 +2443,17 @@ function VectorToolbar({
2364
2443
  },
2365
2444
  tool
2366
2445
  )) }),
2367
- showHistory && /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2446
+ showHistory && /* @__PURE__ */ jsxs19("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2368
2447
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
2369
2448
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
2370
2449
  ] }),
2371
- showSnap && /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2372
- /* @__PURE__ */ jsx17(
2450
+ showSnap && /* @__PURE__ */ jsxs19("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2451
+ /* @__PURE__ */ jsx19(
2373
2452
  "button",
2374
2453
  {
2375
2454
  type: "button",
2376
2455
  "data-vector-snap": "",
2377
- className: buttonClass14,
2456
+ className: buttonClass16,
2378
2457
  "aria-pressed": state.snap.objects,
2379
2458
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
2380
2459
  "aria-label": VECTOR_SNAP_TITLE,
@@ -2382,7 +2461,7 @@ function VectorToolbar({
2382
2461
  children: "Snap"
2383
2462
  }
2384
2463
  ),
2385
- /* @__PURE__ */ jsxs17(
2464
+ /* @__PURE__ */ jsxs19(
2386
2465
  "select",
2387
2466
  {
2388
2467
  "data-vector-polar": "",
@@ -2392,8 +2471,8 @@ function VectorToolbar({
2392
2471
  value: String(vectorPolarIncrementOf(state.snap.angles) ?? ""),
2393
2472
  onChange: (e) => controller.setSnap({ angles: vectorPolarAngles(Number(e.target.value)) }),
2394
2473
  children: [
2395
- vectorPolarIncrementOf(state.snap.angles) === null && /* @__PURE__ */ jsx17("option", { value: "", children: "Custom" }),
2396
- [90, 45, 30, 15].map((n) => /* @__PURE__ */ jsxs17("option", { value: n, children: [
2474
+ vectorPolarIncrementOf(state.snap.angles) === null && /* @__PURE__ */ jsx19("option", { value: "", children: "Custom" }),
2475
+ [90, 45, 30, 15].map((n) => /* @__PURE__ */ jsxs19("option", { value: n, children: [
2397
2476
  n,
2398
2477
  "\xB0"
2399
2478
  ] }, n))
@@ -2401,13 +2480,13 @@ function VectorToolbar({
2401
2480
  }
2402
2481
  )
2403
2482
  ] }),
2404
- showPathEdit && /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2405
- VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx17(
2483
+ showPathEdit && /* @__PURE__ */ jsxs19("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2484
+ VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx19(
2406
2485
  "button",
2407
2486
  {
2408
2487
  type: "button",
2409
2488
  "data-vector-path-action": a.id,
2410
- className: buttonClass14,
2489
+ className: buttonClass16,
2411
2490
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
2412
2491
  "aria-label": a.title,
2413
2492
  disabled: !a.enabled(count, points),
@@ -2416,12 +2495,12 @@ function VectorToolbar({
2416
2495
  },
2417
2496
  a.id
2418
2497
  )),
2419
- VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx17(
2498
+ VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx19(
2420
2499
  "button",
2421
2500
  {
2422
2501
  type: "button",
2423
2502
  "data-vector-edit": t.key,
2424
- className: buttonClass14,
2503
+ className: buttonClass16,
2425
2504
  "aria-pressed": editing[t.key],
2426
2505
  title: t.title,
2427
2506
  "aria-label": t.title,
@@ -2430,9 +2509,9 @@ function VectorToolbar({
2430
2509
  },
2431
2510
  t.key
2432
2511
  )),
2433
- state.tool === "pencil" && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2512
+ state.tool === "pencil" && /* @__PURE__ */ jsxs19("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2434
2513
  "Smoothing",
2435
- /* @__PURE__ */ jsx17(
2514
+ /* @__PURE__ */ jsx19(
2436
2515
  "input",
2437
2516
  {
2438
2517
  type: "range",
@@ -2446,9 +2525,9 @@ function VectorToolbar({
2446
2525
  }
2447
2526
  )
2448
2527
  ] }),
2449
- state.tool === "eraser" && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2528
+ state.tool === "eraser" && /* @__PURE__ */ jsxs19("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2450
2529
  "Size",
2451
- /* @__PURE__ */ jsx17(
2530
+ /* @__PURE__ */ jsx19(
2452
2531
  "input",
2453
2532
  {
2454
2533
  type: "number",
@@ -2464,12 +2543,12 @@ function VectorToolbar({
2464
2543
  }
2465
2544
  )
2466
2545
  ] }),
2467
- state.tool === "paintBucket" && /* @__PURE__ */ jsx17(
2546
+ state.tool === "paintBucket" && /* @__PURE__ */ jsx19(
2468
2547
  "button",
2469
2548
  {
2470
2549
  type: "button",
2471
2550
  "data-vector-fill-gap": "",
2472
- className: buttonClass14,
2551
+ className: buttonClass16,
2473
2552
  "aria-pressed": editing.fillGap > 0,
2474
2553
  title: "Close small gaps between path ends when filling",
2475
2554
  "aria-label": "Close gaps",
@@ -2478,12 +2557,12 @@ function VectorToolbar({
2478
2557
  }
2479
2558
  )
2480
2559
  ] }),
2481
- showDistort && /* @__PURE__ */ jsx17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx17(
2560
+ showDistort && /* @__PURE__ */ jsx19("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx19(
2482
2561
  "button",
2483
2562
  {
2484
2563
  type: "button",
2485
2564
  "data-vector-free-distort": "",
2486
- className: buttonClass14,
2565
+ className: buttonClass16,
2487
2566
  "aria-pressed": state.freeDistort,
2488
2567
  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)`,
2489
2568
  "aria-label": "Free Distort",
@@ -2491,15 +2570,15 @@ function VectorToolbar({
2491
2570
  children: "Distort"
2492
2571
  }
2493
2572
  ) }),
2494
- showBlend && /* @__PURE__ */ jsx17(VectorBlendControls, { controller }),
2495
- showArrange && /* @__PURE__ */ jsxs17(Fragment6, { children: [
2496
- /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2497
- /* @__PURE__ */ jsx17(
2573
+ showBlend && /* @__PURE__ */ jsx19(VectorBlendControls, { controller }),
2574
+ showArrange && /* @__PURE__ */ jsxs19(Fragment6, { children: [
2575
+ /* @__PURE__ */ jsxs19("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2576
+ /* @__PURE__ */ jsx19(
2498
2577
  "button",
2499
2578
  {
2500
2579
  type: "button",
2501
2580
  "data-vector-live-boolean": "",
2502
- className: buttonClass14,
2581
+ className: buttonClass16,
2503
2582
  "aria-pressed": live,
2504
2583
  title: "Live booleans: keep the shapes editable",
2505
2584
  "aria-label": "Live booleans",
@@ -2516,12 +2595,12 @@ function VectorToolbar({
2516
2595
  act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
2517
2596
  act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
2518
2597
  ] }),
2519
- /* @__PURE__ */ jsx17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx17(
2598
+ /* @__PURE__ */ jsx19("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx19(
2520
2599
  "button",
2521
2600
  {
2522
2601
  type: "button",
2523
2602
  "data-vector-pathfinder": a.id,
2524
- className: buttonClass14,
2603
+ className: buttonClass16,
2525
2604
  title: a.title,
2526
2605
  "aria-label": a.title,
2527
2606
  disabled: count < a.min,
@@ -2530,7 +2609,7 @@ function VectorToolbar({
2530
2609
  },
2531
2610
  a.id
2532
2611
  )) }),
2533
- /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2612
+ /* @__PURE__ */ jsxs19("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2534
2613
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
2535
2614
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
2536
2615
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -2548,19 +2627,19 @@ function VectorBlendControls({ controller }) {
2548
2627
  const blend = getVectorBlendControls(state);
2549
2628
  const o = blend.options;
2550
2629
  const selected = blend.ids.length > 0;
2551
- return /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2552
- /* @__PURE__ */ jsx17("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass14, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
2553
- /* @__PURE__ */ jsx17(
2630
+ return /* @__PURE__ */ jsxs19("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2631
+ /* @__PURE__ */ jsx19("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass16, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
2632
+ /* @__PURE__ */ jsx19(
2554
2633
  "select",
2555
2634
  {
2556
2635
  "aria-label": "Blend spacing",
2557
2636
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
2558
2637
  value: o.spacing,
2559
2638
  onChange: (e) => applyVectorBlendOptions(controller, { spacing: e.target.value }),
2560
- children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx17("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2639
+ children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx19("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2561
2640
  }
2562
2641
  ),
2563
- o.spacing !== "smoothColor" && /* @__PURE__ */ jsx17(
2642
+ o.spacing !== "smoothColor" && /* @__PURE__ */ jsx19(
2564
2643
  "input",
2565
2644
  {
2566
2645
  type: "number",
@@ -2576,12 +2655,12 @@ function VectorBlendControls({ controller }) {
2576
2655
  }
2577
2656
  }
2578
2657
  ),
2579
- /* @__PURE__ */ jsx17(
2658
+ /* @__PURE__ */ jsx19(
2580
2659
  "button",
2581
2660
  {
2582
2661
  type: "button",
2583
2662
  "data-vector-blend-action": "orient",
2584
- className: buttonClass14,
2663
+ className: buttonClass16,
2585
2664
  "aria-pressed": o.orientToSpine,
2586
2665
  title: "Orient steps to the spine",
2587
2666
  "aria-label": "Orient to spine",
@@ -2589,12 +2668,12 @@ function VectorBlendControls({ controller }) {
2589
2668
  children: "\u293F"
2590
2669
  }
2591
2670
  ),
2592
- /* @__PURE__ */ jsx17(
2671
+ /* @__PURE__ */ jsx19(
2593
2672
  "button",
2594
2673
  {
2595
2674
  type: "button",
2596
2675
  "data-vector-blend-action": "spine",
2597
- className: buttonClass14,
2676
+ className: buttonClass16,
2598
2677
  title: "Replace Spine (select a blend and a path)",
2599
2678
  "aria-label": "Replace Spine",
2600
2679
  disabled: !blend.canReplaceSpine,
@@ -2602,11 +2681,11 @@ function VectorBlendControls({ controller }) {
2602
2681
  children: "\u2307"
2603
2682
  }
2604
2683
  ),
2605
- /* @__PURE__ */ jsx17("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass14, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2606
- /* @__PURE__ */ jsx17("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass14, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2684
+ /* @__PURE__ */ jsx19("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass16, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2685
+ /* @__PURE__ */ jsx19("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass16, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2607
2686
  ] });
2608
2687
  }
2609
- var selectClass12 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2688
+ var selectClass14 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2610
2689
  var numberClass11 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2611
2690
  function VectorStylePanel({ controller, className }) {
2612
2691
  const state = useVectorEditorState(controller);
@@ -2639,17 +2718,17 @@ function VectorStylePanel({ controller, className }) {
2639
2718
  if (style) controller.setWidthProfile(id);
2640
2719
  else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
2641
2720
  };
2642
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx17("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2721
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx19("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2643
2722
  const arrowRow = (end, label) => {
2644
2723
  const key = end === "start" ? "startArrow" : "endArrow";
2645
2724
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
2646
- return /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2647
- /* @__PURE__ */ jsx17("span", { className: "w-16 text-xs text-foreground", children: label }),
2648
- /* @__PURE__ */ jsxs17("select", { "aria-label": `${label} arrowhead`, className: selectClass12, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2649
- /* @__PURE__ */ jsx17("option", { value: "", children: "None" }),
2650
- VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx17("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
2725
+ return /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2726
+ /* @__PURE__ */ jsx19("span", { className: "w-16 text-xs text-foreground", children: label }),
2727
+ /* @__PURE__ */ jsxs19("select", { "aria-label": `${label} arrowhead`, className: selectClass14, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2728
+ /* @__PURE__ */ jsx19("option", { value: "", children: "None" }),
2729
+ VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx19("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
2651
2730
  ] }),
2652
- /* @__PURE__ */ jsx17(
2731
+ /* @__PURE__ */ jsx19(
2653
2732
  "input",
2654
2733
  {
2655
2734
  type: "number",
@@ -2675,12 +2754,12 @@ function VectorStylePanel({ controller, className }) {
2675
2754
  const paint = source[key];
2676
2755
  const hex = vectorPaintToHex6(paint);
2677
2756
  const on = paint !== null && paint !== "none";
2678
- return /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2679
- /* @__PURE__ */ jsxs17("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2680
- /* @__PURE__ */ jsx17("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2757
+ return /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2758
+ /* @__PURE__ */ jsxs19("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2759
+ /* @__PURE__ */ jsx19("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2681
2760
  label
2682
2761
  ] }),
2683
- /* @__PURE__ */ jsx17(
2762
+ /* @__PURE__ */ jsx19(
2684
2763
  "input",
2685
2764
  {
2686
2765
  type: "color",
@@ -2691,18 +2770,18 @@ function VectorStylePanel({ controller, className }) {
2691
2770
  onChange: (e) => apply({ [key]: e.target.value })
2692
2771
  }
2693
2772
  ),
2694
- on && !hex && /* @__PURE__ */ jsx17("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2695
- style?.mixed[key] && /* @__PURE__ */ jsx17("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2773
+ on && !hex && /* @__PURE__ */ jsx19("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2774
+ style?.mixed[key] && /* @__PURE__ */ jsx19("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2696
2775
  ] });
2697
2776
  };
2698
- return /* @__PURE__ */ jsxs17("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2699
- /* @__PURE__ */ jsx17("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2777
+ return /* @__PURE__ */ jsxs19("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2778
+ /* @__PURE__ */ jsx19("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2700
2779
  paintRow("fill", "Fill"),
2701
2780
  paintRow("stroke", "Stroke"),
2702
- style && /* @__PURE__ */ jsx17(VectorGradientEditor, { controller }),
2703
- /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2704
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Width" }),
2705
- /* @__PURE__ */ jsx17(
2781
+ style && /* @__PURE__ */ jsx19(VectorGradientEditor, { controller }),
2782
+ /* @__PURE__ */ jsxs19("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2783
+ /* @__PURE__ */ jsx19("span", { className: "w-16", children: "Width" }),
2784
+ /* @__PURE__ */ jsx19(
2706
2785
  "input",
2707
2786
  {
2708
2787
  type: "number",
@@ -2718,33 +2797,33 @@ function VectorStylePanel({ controller, className }) {
2718
2797
  }
2719
2798
  )
2720
2799
  ] }),
2721
- /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2722
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Align" }),
2723
- /* @__PURE__ */ jsx17("select", { "aria-label": "Stroke alignment", className: selectClass12, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS2).map((a) => /* @__PURE__ */ jsx17("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS2[a] }, a)) }),
2800
+ /* @__PURE__ */ jsxs19("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2801
+ /* @__PURE__ */ jsx19("span", { className: "w-16", children: "Align" }),
2802
+ /* @__PURE__ */ jsx19("select", { "aria-label": "Stroke alignment", className: selectClass14, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS2).map((a) => /* @__PURE__ */ jsx19("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS2[a] }, a)) }),
2724
2803
  mixed("strokeAlign")
2725
2804
  ] }),
2726
- /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2727
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Profile" }),
2728
- /* @__PURE__ */ jsxs17(
2805
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2806
+ /* @__PURE__ */ jsx19("span", { className: "w-16", children: "Profile" }),
2807
+ /* @__PURE__ */ jsxs19(
2729
2808
  "select",
2730
2809
  {
2731
2810
  "aria-label": "Width profile",
2732
- className: selectClass12,
2811
+ className: selectClass14,
2733
2812
  value: source.widthProfile,
2734
2813
  onChange: (e) => {
2735
2814
  if (e.target.value !== "custom") setProfile(e.target.value);
2736
2815
  },
2737
2816
  children: [
2738
- VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx17("option", { value: p.id, children: p.label }, p.id)),
2739
- source.widthProfile === "custom" && /* @__PURE__ */ jsx17("option", { value: "custom", disabled: true, children: "Custom" })
2817
+ VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx19("option", { value: p.id, children: p.label }, p.id)),
2818
+ source.widthProfile === "custom" && /* @__PURE__ */ jsx19("option", { value: "custom", disabled: true, children: "Custom" })
2740
2819
  ]
2741
2820
  }
2742
2821
  ),
2743
- /* @__PURE__ */ jsx17(
2822
+ /* @__PURE__ */ jsx19(
2744
2823
  "button",
2745
2824
  {
2746
2825
  type: "button",
2747
- className: buttonClass14,
2826
+ className: buttonClass16,
2748
2827
  title: "Reset to uniform width",
2749
2828
  "aria-label": "Reset to uniform width",
2750
2829
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -2754,20 +2833,20 @@ function VectorStylePanel({ controller, className }) {
2754
2833
  ),
2755
2834
  mixed("widthProfile")
2756
2835
  ] }),
2757
- source.hasOpenPath && /* @__PURE__ */ jsxs17(Fragment6, { children: [
2836
+ source.hasOpenPath && /* @__PURE__ */ jsxs19(Fragment6, { children: [
2758
2837
  arrowRow("start", "Start"),
2759
2838
  arrowRow("end", "End"),
2760
- (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2761
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Heads" }),
2762
- /* @__PURE__ */ jsxs17("select", { "aria-label": "Arrowhead alignment", className: selectClass12, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2763
- /* @__PURE__ */ jsx17("option", { value: "extend", children: "Tip past end" }),
2764
- /* @__PURE__ */ jsx17("option", { value: "tip", children: "Tip at end" })
2839
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs19("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2840
+ /* @__PURE__ */ jsx19("span", { className: "w-16", children: "Heads" }),
2841
+ /* @__PURE__ */ jsxs19("select", { "aria-label": "Arrowhead alignment", className: selectClass14, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2842
+ /* @__PURE__ */ jsx19("option", { value: "extend", children: "Tip past end" }),
2843
+ /* @__PURE__ */ jsx19("option", { value: "tip", children: "Tip at end" })
2765
2844
  ] })
2766
2845
  ] })
2767
2846
  ] }),
2768
- source.corners > 0 && /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2769
- /* @__PURE__ */ jsx17("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2770
- /* @__PURE__ */ jsx17(
2847
+ source.corners > 0 && /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2848
+ /* @__PURE__ */ jsx19("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2849
+ /* @__PURE__ */ jsx19(
2771
2850
  "input",
2772
2851
  {
2773
2852
  type: "number",
@@ -2782,12 +2861,12 @@ function VectorStylePanel({ controller, className }) {
2782
2861
  }
2783
2862
  }
2784
2863
  ),
2785
- /* @__PURE__ */ jsx17("select", { "aria-label": "Corner kind", className: selectClass12, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx17("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2864
+ /* @__PURE__ */ jsx19("select", { "aria-label": "Corner kind", className: selectClass14, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx19("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2786
2865
  mixed("cornerRadius")
2787
2866
  ] }),
2788
- style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ jsxs17("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: [
2789
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Smoothing" }),
2790
- /* @__PURE__ */ jsx17(
2867
+ style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ jsxs19("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: [
2868
+ /* @__PURE__ */ jsx19("span", { className: "w-16", children: "Smoothing" }),
2869
+ /* @__PURE__ */ jsx19(
2791
2870
  "input",
2792
2871
  {
2793
2872
  type: "range",
@@ -2802,9 +2881,9 @@ function VectorStylePanel({ controller, className }) {
2802
2881
  }
2803
2882
  )
2804
2883
  ] }),
2805
- style && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2806
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Opacity" }),
2807
- /* @__PURE__ */ jsx17(
2884
+ style && /* @__PURE__ */ jsxs19("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2885
+ /* @__PURE__ */ jsx19("span", { className: "w-16", children: "Opacity" }),
2886
+ /* @__PURE__ */ jsx19(
2808
2887
  "input",
2809
2888
  {
2810
2889
  type: "range",
@@ -2818,25 +2897,27 @@ function VectorStylePanel({ controller, className }) {
2818
2897
  }
2819
2898
  )
2820
2899
  ] }),
2821
- /* @__PURE__ */ jsx17(VectorTextPanel, { controller }),
2822
- style && /* @__PURE__ */ jsx17(VectorAppearancePanel, { controller }),
2823
- style && /* @__PURE__ */ jsx17(VectorEffectsPanel, { controller }),
2824
- style && /* @__PURE__ */ jsx17(VectorEnvelopePanel, { controller }),
2825
- style && /* @__PURE__ */ jsx17(VectorMaskPanel, { controller }),
2826
- /* @__PURE__ */ jsx17(VectorFramePanel, { controller }),
2827
- /* @__PURE__ */ jsx17(VectorConnectorPanel, { controller }),
2828
- /* @__PURE__ */ jsx17(VectorShadingPanel, { controller }),
2829
- /* @__PURE__ */ jsx17(VectorSymmetryPanel, { controller }),
2830
- /* @__PURE__ */ jsx17(VectorLensPanel, { controller }),
2831
- /* @__PURE__ */ jsx17(VectorTracePanel, { controller }),
2832
- /* @__PURE__ */ jsx17(VectorConstraintsPanel, { controller }),
2833
- /* @__PURE__ */ jsx17(VectorSymbolsPanel, { controller })
2900
+ /* @__PURE__ */ jsx19(VectorTextPanel, { controller }),
2901
+ style && /* @__PURE__ */ jsx19(VectorAppearancePanel, { controller }),
2902
+ style && /* @__PURE__ */ jsx19(VectorEffectsPanel, { controller }),
2903
+ style && /* @__PURE__ */ jsx19(VectorEnvelopePanel, { controller }),
2904
+ style && /* @__PURE__ */ jsx19(VectorMaskPanel, { controller }),
2905
+ /* @__PURE__ */ jsx19(VectorFramePanel, { controller }),
2906
+ /* @__PURE__ */ jsx19(VectorPagesPanel, { controller }),
2907
+ /* @__PURE__ */ jsx19(VectorConnectorPanel, { controller }),
2908
+ /* @__PURE__ */ jsx19(VectorShadingPanel, { controller }),
2909
+ /* @__PURE__ */ jsx19(VectorSymmetryPanel, { controller }),
2910
+ /* @__PURE__ */ jsx19(VectorPerspectivePanel, { controller }),
2911
+ /* @__PURE__ */ jsx19(VectorLensPanel, { controller }),
2912
+ /* @__PURE__ */ jsx19(VectorTracePanel, { controller }),
2913
+ /* @__PURE__ */ jsx19(VectorConstraintsPanel, { controller }),
2914
+ /* @__PURE__ */ jsx19(VectorSymbolsPanel, { controller })
2834
2915
  ] });
2835
2916
  }
2836
2917
 
2837
2918
  // src/vector/VectorProviderEffectsPanel.tsx
2838
2919
  import { useState as useState7, useSyncExternalStore as useSyncExternalStore2 } from "react";
2839
- import { Fragment as Fragment7, jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
2920
+ import { Fragment as Fragment7, jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
2840
2921
  var btn = "rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40";
2841
2922
  function Controls({ controller, group }) {
2842
2923
  const provider = controller.effectsProvider;
@@ -2844,11 +2925,11 @@ function Controls({ controller, group }) {
2844
2925
  const { description, error } = useEffectDescription(provider, tag.effect);
2845
2926
  const [message, setMessage] = useState7(null);
2846
2927
  const run = (work) => void work.then(() => setMessage(null), (e) => setMessage(e instanceof Error ? e.message : String(e)));
2847
- if (!provider) return /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider: the shapes are kept as they are." });
2848
- if (error) return /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-amber-500", children: error });
2849
- if (!description) return /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-muted-foreground", children: "Loading\u2026" });
2850
- return /* @__PURE__ */ jsxs18(Fragment7, { children: [
2851
- /* @__PURE__ */ jsx18(
2928
+ if (!provider) return /* @__PURE__ */ jsx20("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider: the shapes are kept as they are." });
2929
+ if (error) return /* @__PURE__ */ jsx20("p", { className: "text-[11px] text-amber-500", children: error });
2930
+ if (!description) return /* @__PURE__ */ jsx20("p", { className: "text-[11px] text-muted-foreground", children: "Loading\u2026" });
2931
+ return /* @__PURE__ */ jsxs20(Fragment7, { children: [
2932
+ /* @__PURE__ */ jsx20(
2852
2933
  EffectParameters,
2853
2934
  {
2854
2935
  description,
@@ -2858,7 +2939,7 @@ function Controls({ controller, group }) {
2858
2939
  onPreset: (v) => run(controller.updateVectorEffect(group.id, v))
2859
2940
  }
2860
2941
  ),
2861
- message && /* @__PURE__ */ jsx18("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2942
+ message && /* @__PURE__ */ jsx20("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2862
2943
  ] });
2863
2944
  }
2864
2945
  function VectorProviderEffectsPanel({ controller, className = "" }) {
@@ -2877,41 +2958,41 @@ function VectorProviderEffectsPanel({ controller, className = "" }) {
2877
2958
  setMessage(null);
2878
2959
  Promise.resolve().then(work).catch((e) => setMessage(e instanceof Error ? e.message : String(e)));
2879
2960
  };
2880
- return /* @__PURE__ */ jsxs18("section", { className: `flex flex-col gap-2 text-xs ${className}`, "aria-label": "Vector effects", "data-vector-provider-effects": "", children: [
2881
- /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-1", children: [
2882
- /* @__PURE__ */ jsx18("span", { className: "flex-1 font-semibold", children: "Effects" }),
2883
- /* @__PURE__ */ jsx18("button", { type: "button", className: btn, disabled: !provider, "aria-pressed": browse, onClick: () => setBrowse(!browse), children: "Add effect\u2026" })
2961
+ return /* @__PURE__ */ jsxs20("section", { className: `flex flex-col gap-2 text-xs ${className}`, "aria-label": "Vector effects", "data-vector-provider-effects": "", children: [
2962
+ /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-1", children: [
2963
+ /* @__PURE__ */ jsx20("span", { className: "flex-1 font-semibold", children: "Effects" }),
2964
+ /* @__PURE__ */ jsx20("button", { type: "button", className: btn, disabled: !provider, "aria-pressed": browse, onClick: () => setBrowse(!browse), children: "Add effect\u2026" })
2884
2965
  ] }),
2885
- !provider && /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider is registered. Effect groups are kept as shapes." }),
2886
- browse && /* @__PURE__ */ jsx18(EffectsBrowser, { provider, kinds: ["vector"], title: "Vector effects", onPick: (e) => (setBrowse(false), run(() => controller.addVectorEffect(e.id))) }),
2887
- selected.map((g) => /* @__PURE__ */ jsxs18("div", { className: "space-y-1 rounded border border-border p-1", "data-vector-effect-group": g.id, children: [
2888
- /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-1", children: [
2889
- /* @__PURE__ */ jsx18("span", { className: "min-w-0 flex-1 truncate font-medium", children: g.providerEffect.name ?? g.providerEffect.effect }),
2890
- /* @__PURE__ */ jsx18("button", { type: "button", className: btn, title: "Keep the shapes as a plain group", onClick: () => run(() => controller.detachVectorEffect(g.id)), children: "Detach" })
2966
+ !provider && /* @__PURE__ */ jsx20("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider is registered. Effect groups are kept as shapes." }),
2967
+ browse && /* @__PURE__ */ jsx20(EffectsBrowser, { provider, kinds: ["vector"], title: "Vector effects", onPick: (e) => (setBrowse(false), run(() => controller.addVectorEffect(e.id))) }),
2968
+ selected.map((g) => /* @__PURE__ */ jsxs20("div", { className: "space-y-1 rounded border border-border p-1", "data-vector-effect-group": g.id, children: [
2969
+ /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-1", children: [
2970
+ /* @__PURE__ */ jsx20("span", { className: "min-w-0 flex-1 truncate font-medium", children: g.providerEffect.name ?? g.providerEffect.effect }),
2971
+ /* @__PURE__ */ jsx20("button", { type: "button", className: btn, title: "Keep the shapes as a plain group", onClick: () => run(() => controller.detachVectorEffect(g.id)), children: "Detach" })
2891
2972
  ] }),
2892
- /* @__PURE__ */ jsx18(Controls, { controller, group: g })
2973
+ /* @__PURE__ */ jsx20(Controls, { controller, group: g })
2893
2974
  ] }, g.id)),
2894
- groups.length > 0 && !selected.length && /* @__PURE__ */ jsxs18("p", { className: "text-[11px] text-muted-foreground", children: [
2975
+ groups.length > 0 && !selected.length && /* @__PURE__ */ jsxs20("p", { className: "text-[11px] text-muted-foreground", children: [
2895
2976
  "Select an effect group (",
2896
2977
  groups.length,
2897
2978
  " in the drawing) to change it."
2898
2979
  ] }),
2899
- message && /* @__PURE__ */ jsx18("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2980
+ message && /* @__PURE__ */ jsx20("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2900
2981
  ] });
2901
2982
  }
2902
2983
 
2903
2984
  // src/vector/VectorEditor.tsx
2904
- import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2985
+ import { jsx as jsx21, jsxs as jsxs21 } from "react/jsx-runtime";
2905
2986
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, effects, className }) {
2906
2987
  useEffects(controller, effects);
2907
2988
  const effectsShown = !!effects || controller.vectorEffectGroups().length > 0;
2908
- return /* @__PURE__ */ jsxs19("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2909
- /* @__PURE__ */ jsx19(VectorToolbar, { controller, tools }),
2910
- /* @__PURE__ */ jsxs19("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2911
- /* @__PURE__ */ jsx19("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx19(VectorCanvas, { controller, ...canvasProps }) }),
2912
- showStylePanel && /* @__PURE__ */ jsxs19("div", { className: "flex w-full shrink-0 flex-col gap-2 md:w-56", children: [
2913
- /* @__PURE__ */ jsx19(VectorStylePanel, { controller, className: "rounded-md border border-border p-3" }),
2914
- effectsShown && /* @__PURE__ */ jsx19(VectorProviderEffectsPanel, { controller, className: "rounded-md border border-border p-3" })
2989
+ return /* @__PURE__ */ jsxs21("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2990
+ /* @__PURE__ */ jsx21(VectorToolbar, { controller, tools }),
2991
+ /* @__PURE__ */ jsxs21("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2992
+ /* @__PURE__ */ jsx21("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx21(VectorCanvas, { controller, ...canvasProps }) }),
2993
+ showStylePanel && /* @__PURE__ */ jsxs21("div", { className: "flex w-full shrink-0 flex-col gap-2 md:w-56", children: [
2994
+ /* @__PURE__ */ jsx21(VectorStylePanel, { controller, className: "rounded-md border border-border p-3" }),
2995
+ effectsShown && /* @__PURE__ */ jsx21(VectorProviderEffectsPanel, { controller, className: "rounded-md border border-border p-3" })
2915
2996
  ] })
2916
2997
  ] })
2917
2998
  ] });
@@ -2932,6 +3013,8 @@ export {
2932
3013
  VectorLensPanel,
2933
3014
  VectorMaskPanel,
2934
3015
  VectorMaskedNodes,
3016
+ VectorPagesPanel,
3017
+ VectorPerspectivePanel,
2935
3018
  VectorProviderEffectsPanel,
2936
3019
  VectorSceneNodes,
2937
3020
  VectorShadingPanel,