@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.
@@ -45,6 +45,8 @@ __export(vector_exports, {
45
45
  VectorLensPanel: () => VectorLensPanel,
46
46
  VectorMaskPanel: () => VectorMaskPanel,
47
47
  VectorMaskedNodes: () => VectorMaskedNodes,
48
+ VectorPagesPanel: () => VectorPagesPanel,
49
+ VectorPerspectivePanel: () => VectorPerspectivePanel,
48
50
  VectorProviderEffectsPanel: () => VectorProviderEffectsPanel,
49
51
  VectorSceneNodes: () => VectorSceneNodes,
50
52
  VectorShadingPanel: () => VectorShadingPanel,
@@ -694,6 +696,11 @@ function VectorCanvas({
694
696
  sh.lines.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d, stroke: accentColor, strokeWidth: 1, opacity: 0.7 }, i)),
695
697
  sh.points.map((p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { "data-vector-shading-point": p.index, "data-selected": p.selected || void 0, cx: p.x, cy: p.y, r: p.selected ? 6 : 4.5, fill: p.color, stroke: p.selected ? accentColor : "#ffffff", strokeWidth: p.selected ? 2 : 1.5 }, p.index))
696
698
  ] }, `shading:${sh.nodeId}`)),
699
+ overlay.perspective && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-perspective-grid": "", fill: "none", pointerEvents: "none", children: [
700
+ overlay.perspective.planes.map((pl) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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)),
701
+ overlay.perspective.vanishingPoints.map((v, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { "data-vector-vanishing-point": i, cx: v.x, cy: v.y, r: 4, stroke: accentColor, strokeWidth: 1.5 }, i)),
702
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { "data-vector-perspective-origin": "", cx: overlay.perspective.origin.x, cy: overlay.perspective.origin.y, r: 3, fill: accentColor })
703
+ ] }),
697
704
  overlay.symmetry.map((sy) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { "data-vector-symmetry-axes": sy.nodeId, stroke: accentColor, strokeDasharray: sy.drawing ? void 0 : "6 4", opacity: sy.drawing ? 0.9 : 0.6, children: sy.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: l.x1, y1: l.y1, x2: l.x2, y2: l.y2 }, i)) }, `sym:${sy.nodeId}`)),
698
705
  overlay.trackingPoints.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { "data-vector-tracking-point": "", d: `M${t.x - 5} ${t.y}H${t.x + 5}M${t.x} ${t.y - 5}V${t.y + 5}`, stroke: guideColor, strokeWidth: 1.5 }, `track:${i}`)),
699
706
  overlay.typedEntry && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-typed-entry": "", transform: `translate(${overlay.typedEntry.x + 14} ${overlay.typedEntry.y + 18})`, children: [
@@ -830,8 +837,8 @@ function VectorCanvas({
830
837
  }
831
838
 
832
839
  // src/vector/VectorToolbar.tsx
833
- var import_react10 = __toESM(require("react"), 1);
834
- var import_vector17 = require("@pixerate/editor/vector");
840
+ var import_react11 = __toESM(require("react"), 1);
841
+ var import_vector19 = require("@pixerate/editor/vector");
835
842
 
836
843
  // src/vector/VectorEffectsPanel.tsx
837
844
  var import_vector4 = require("@pixerate/editor/vector");
@@ -1878,38 +1885,67 @@ function VectorFramePanel({ controller, className }) {
1878
1885
  ] });
1879
1886
  }
1880
1887
 
1881
- // src/vector/VectorConnectorPanel.tsx
1888
+ // src/vector/VectorPagesPanel.tsx
1882
1889
  var import_vector12 = require("@pixerate/editor/vector");
1883
1890
  var import_jsx_runtime11 = require("react/jsx-runtime");
1884
- 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";
1891
+ 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";
1885
1892
  var selectClass7 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1893
+ function VectorPagesPanel({ controller, className }) {
1894
+ const state = useVectorEditorState(controller);
1895
+ const pages = (0, import_vector12.vectorPages)(state.document);
1896
+ const masters = (0, import_vector12.vectorMasterPages)(state.document);
1897
+ const selected = state.selection.map((id) => state.document.nodes.find((n) => n.id === id)).find((n) => (0, import_vector12.isVectorFrame)(n));
1898
+ const page = selected && !selected.frame.master ? selected : null;
1899
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-pages-panel": "", "aria-label": "Pages", children: [
1900
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
1901
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Pages" }),
1902
+ pages.map((p, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("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)),
1903
+ masters.map((m) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("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)),
1904
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "button", className: buttonClass9, "data-vector-pages-action": "add", title: "Add a page after the last", onClick: () => controller.addPage(), children: "+ Page" }),
1905
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("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" })
1906
+ ] }),
1907
+ page && masters.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
1908
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "w-16" }),
1909
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("select", { "aria-label": "Master page", className: selectClass7, value: page.frame.masterId ?? "", onChange: (e) => controller.setPageMaster(e.target.value || null, { ids: [page.id] }), children: [
1910
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "", children: "No master" }),
1911
+ masters.map((m) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: m.id, children: m.name ?? "Master" }, m.id))
1912
+ ] })
1913
+ ] })
1914
+ ] });
1915
+ }
1916
+
1917
+ // src/vector/VectorConnectorPanel.tsx
1918
+ var import_vector13 = require("@pixerate/editor/vector");
1919
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1920
+ var buttonClass10 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40";
1921
+ var selectClass8 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1886
1922
  var numberClass5 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
1887
1923
  function VectorConnectorPanel({ controller, className }) {
1888
1924
  const state = useVectorEditorState(controller);
1889
- const panel = (0, import_vector12.getVectorConnectorPanel)(state);
1925
+ const panel = (0, import_vector13.getVectorConnectorPanel)(state);
1890
1926
  if (!panel) return null;
1891
1927
  const ids = [panel.id];
1892
- const end = (key, label, e) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1", "data-vector-connector-end": key, children: [
1893
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
1894
- e ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1895
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1928
+ const end = (key, label, e) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1", "data-vector-connector-end": key, children: [
1929
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
1930
+ e ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1931
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1896
1932
  "select",
1897
1933
  {
1898
1934
  "aria-label": `${label} side`,
1899
- className: selectClass7,
1935
+ className: selectClass8,
1900
1936
  value: e.side ?? "auto",
1901
1937
  onChange: (ev) => controller.setConnector({ [key]: { nodeId: e.nodeId, side: ev.target.value } }, { ids }),
1902
- children: import_vector12.VECTOR_CONNECTOR_SIDES.map((s) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: s, children: import_vector12.VECTOR_CONNECTOR_SIDE_LABELS[s] }, s))
1938
+ children: import_vector13.VECTOR_CONNECTOR_SIDES.map((s) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: s, children: import_vector13.VECTOR_CONNECTOR_SIDE_LABELS[s] }, s))
1903
1939
  }
1904
1940
  ),
1905
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("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" })
1906
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Free" })
1941
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("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" })
1942
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Free" })
1907
1943
  ] });
1908
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-connector-panel": panel.id, "aria-label": "Connector", children: [
1909
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "flex items-center gap-1", children: [
1910
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Connector" }),
1911
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("select", { "aria-label": "Connector route", className: selectClass7, value: panel.route, onChange: (e) => controller.setConnector({ route: e.target.value }, { ids }), children: import_vector12.VECTOR_CONNECTOR_ROUTES.map((r) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: r, children: import_vector12.VECTOR_CONNECTOR_ROUTE_LABELS[r] }, r)) }),
1912
- panel.route === "elbow" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1944
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-connector-panel": panel.id, "aria-label": "Connector", children: [
1945
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-1", children: [
1946
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Connector" }),
1947
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("select", { "aria-label": "Connector route", className: selectClass8, value: panel.route, onChange: (e) => controller.setConnector({ route: e.target.value }, { ids }), children: import_vector13.VECTOR_CONNECTOR_ROUTES.map((r) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("option", { value: r, children: import_vector13.VECTOR_CONNECTOR_ROUTE_LABELS[r] }, r)) }),
1948
+ panel.route === "elbow" && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1913
1949
  "input",
1914
1950
  {
1915
1951
  type: "number",
@@ -1933,9 +1969,9 @@ function VectorConnectorPanel({ controller, className }) {
1933
1969
 
1934
1970
  // src/vector/VectorShadingPanel.tsx
1935
1971
  var import_react8 = require("react");
1936
- var import_vector13 = require("@pixerate/editor/vector");
1937
- var import_jsx_runtime12 = require("react/jsx-runtime");
1938
- 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";
1972
+ var import_vector14 = require("@pixerate/editor/vector");
1973
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1974
+ 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";
1939
1975
  var numberClass6 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
1940
1976
  function VectorShadingPanel({ controller, className }) {
1941
1977
  const state = useVectorEditorState(controller);
@@ -1945,8 +1981,8 @@ function VectorShadingPanel({ controller, className }) {
1945
1981
  if (!path) return null;
1946
1982
  const fill = path.fill;
1947
1983
  const sel = state.shadingPoint?.nodeId === path.id ? state.shadingPoint : null;
1948
- const mesh = (0, import_vector13.isVectorMeshGradient)(fill) ? fill : null;
1949
- const free = (0, import_vector13.isVectorFreeformGradient)(fill) ? fill : null;
1984
+ const mesh = (0, import_vector14.isVectorMeshGradient)(fill) ? fill : null;
1985
+ const free = (0, import_vector14.isVectorFreeformGradient)(fill) ? fill : null;
1950
1986
  const color = sel ? mesh ? mesh.colors[sel.index] : free?.points[sel.index]?.color : void 0;
1951
1987
  const opacity = sel ? mesh ? mesh.opacities?.[sel.index] ?? 1 : free?.points[sel.index]?.opacity ?? 1 : 1;
1952
1988
  const spread = sel && free ? free.points[sel.index]?.spread ?? 0 : 0;
@@ -1955,29 +1991,29 @@ function VectorShadingPanel({ controller, className }) {
1955
1991
  if (mesh) controller.setMeshPoint(path.id, sel.index, patch, merge ? { merge } : {});
1956
1992
  else controller.setFreeformPoint(path.id, sel.index, patch, merge ? { merge } : {});
1957
1993
  };
1958
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-shading-panel": path.id, "aria-label": "Mesh and freeform gradient", children: [
1959
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
1960
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Shading" }),
1961
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("input", { type: "number", min: 1, max: 32, "aria-label": "Mesh rows", title: "Mesh rows", className: numberClass6, value: rows, onChange: (e) => setRows(Math.max(1, Math.min(32, parseInt(e.target.value, 10) || 1))) }),
1962
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-xs text-muted-foreground", children: "\xD7" }),
1963
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("input", { type: "number", min: 1, max: 32, "aria-label": "Mesh columns", title: "Mesh columns", className: numberClass6, value: columns, onChange: (e) => setColumns(Math.max(1, Math.min(32, parseInt(e.target.value, 10) || 1))) }),
1964
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: buttonClass10, "data-vector-shading-action": "mesh", title: "Create Gradient Mesh: smooth patches whose points blend colours (edit them with the Gradient tool)", onClick: () => controller.setMeshGradient({ rows, columns }, { ids: [path.id] }), children: "Mesh" }),
1965
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: buttonClass10, "data-vector-shading-action": "freeform", title: "Freeform Gradient: colour points blended across the shape (click inside with the Gradient tool to add one)", onClick: () => controller.setFreeformGradient({}, { ids: [path.id] }), children: "Freeform" })
1994
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-shading-panel": path.id, "aria-label": "Mesh and freeform gradient", children: [
1995
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
1996
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Shading" }),
1997
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("input", { type: "number", min: 1, max: 32, "aria-label": "Mesh rows", title: "Mesh rows", className: numberClass6, value: rows, onChange: (e) => setRows(Math.max(1, Math.min(32, parseInt(e.target.value, 10) || 1))) }),
1998
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "text-xs text-muted-foreground", children: "\xD7" }),
1999
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("input", { type: "number", min: 1, max: 32, "aria-label": "Mesh columns", title: "Mesh columns", className: numberClass6, value: columns, onChange: (e) => setColumns(Math.max(1, Math.min(32, parseInt(e.target.value, 10) || 1))) }),
2000
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("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" }),
2001
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("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" })
1966
2002
  ] }),
1967
- (mesh || free) && !sel && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-[11px] text-muted-foreground", children: [
2003
+ (mesh || free) && !sel && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("p", { className: "text-[11px] text-muted-foreground", children: [
1968
2004
  "Gradient tool (G): drag points; ",
1969
2005
  free ? "click inside to add one, Alt-click to remove." : "pick one to colour it."
1970
2006
  ] }),
1971
- sel && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-wrap items-center gap-2", "data-vector-shading-point": sel.index, children: [
1972
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { className: "w-16 text-xs text-foreground", children: [
2007
+ sel && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex flex-wrap items-center gap-2", "data-vector-shading-point": sel.index, children: [
2008
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { className: "w-16 text-xs text-foreground", children: [
1973
2009
  "Point ",
1974
2010
  sel.index
1975
2011
  ] }),
1976
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("input", { type: "color", "aria-label": "Point colour", className: "h-7 w-10 cursor-pointer rounded border border-border bg-background", value: (0, import_vector13.vectorPaintToHex)(color ?? "#000000") ?? "#000000", onChange: (e) => edit({ color: e.target.value }, "shading-color") }),
1977
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("input", { type: "range", min: 0, max: 1, step: 0.05, "aria-label": "Point opacity", title: "Opacity", className: "h-4 w-20 accent-primary", value: opacity, onChange: (e) => edit({ opacity: parseFloat(e.target.value) }, "shading-opacity") }),
1978
- free && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1979
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("input", { type: "range", min: 0, max: 1, step: 0.05, "aria-label": "Point spread", title: "Spread: pure colour round the point", className: "h-4 w-20 accent-primary", value: spread, onChange: (e) => edit({ spread: parseFloat(e.target.value) }, "shading-spread") }),
1980
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", className: buttonClass10, "aria-label": "Remove point", onClick: () => controller.removeFreeformPoint(path.id, sel.index), children: "Remove" })
2012
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("input", { type: "color", "aria-label": "Point colour", className: "h-7 w-10 cursor-pointer rounded border border-border bg-background", value: (0, import_vector14.vectorPaintToHex)(color ?? "#000000") ?? "#000000", onChange: (e) => edit({ color: e.target.value }, "shading-color") }),
2013
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("input", { type: "range", min: 0, max: 1, step: 0.05, "aria-label": "Point opacity", title: "Opacity", className: "h-4 w-20 accent-primary", value: opacity, onChange: (e) => edit({ opacity: parseFloat(e.target.value) }, "shading-opacity") }),
2014
+ free && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2015
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("input", { type: "range", min: 0, max: 1, step: 0.05, "aria-label": "Point spread", title: "Spread: pure colour round the point", className: "h-4 w-20 accent-primary", value: spread, onChange: (e) => edit({ spread: parseFloat(e.target.value) }, "shading-spread") }),
2016
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", className: buttonClass11, "aria-label": "Remove point", onClick: () => controller.removeFreeformPoint(path.id, sel.index), children: "Remove" })
1981
2017
  ] })
1982
2018
  ] })
1983
2019
  ] });
@@ -1985,9 +2021,9 @@ function VectorShadingPanel({ controller, className }) {
1985
2021
 
1986
2022
  // src/vector/VectorTracePanel.tsx
1987
2023
  var import_react9 = require("react");
1988
- var import_jsx_runtime13 = require("react/jsx-runtime");
1989
- 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";
1990
- var selectClass8 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2024
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2025
+ 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";
2026
+ var selectClass9 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1991
2027
  var numberClass7 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
1992
2028
  async function filePixels(file, max = 1e6) {
1993
2029
  const bitmap = await createImageBitmap(file);
@@ -2017,16 +2053,16 @@ function VectorTracePanel({ controller, className }) {
2017
2053
  setMessage("That file could not be read as an image.");
2018
2054
  }
2019
2055
  };
2020
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `space-y-1 ${className ?? ""}`, "data-vector-trace-panel": "", "aria-label": "Image trace", children: [
2021
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center gap-1", children: [
2022
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Trace" }),
2023
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("select", { "aria-label": "Trace mode", className: selectClass8, value: mode, onChange: (e) => setMode(e.target.value), children: [
2024
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "threshold", children: "Black & white" }),
2025
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "color", children: "Colours" })
2056
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: `space-y-1 ${className ?? ""}`, "data-vector-trace-panel": "", "aria-label": "Image trace", children: [
2057
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-1", children: [
2058
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Trace" }),
2059
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("select", { "aria-label": "Trace mode", className: selectClass9, value: mode, onChange: (e) => setMode(e.target.value), children: [
2060
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "threshold", children: "Black & white" }),
2061
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "color", children: "Colours" })
2026
2062
  ] }),
2027
- mode === "color" && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("input", { type: "number", min: 2, max: 32, "aria-label": "Trace colours", title: "Colours", className: numberClass7, value: colors, onChange: (e) => setColors(Math.max(2, Math.min(32, parseInt(e.target.value, 10) || 2))) }),
2028
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("button", { type: "button", className: buttonClass11, "data-vector-trace-action": "", title: "Image Trace: pick an image to turn into paths", onClick: () => input.current?.click(), children: "Image\u2026" }),
2029
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2063
+ mode === "color" && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("input", { type: "number", min: 2, max: 32, "aria-label": "Trace colours", title: "Colours", className: numberClass7, value: colors, onChange: (e) => setColors(Math.max(2, Math.min(32, parseInt(e.target.value, 10) || 2))) }),
2064
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("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" }),
2065
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2030
2066
  "input",
2031
2067
  {
2032
2068
  ref: input,
@@ -2041,79 +2077,124 @@ function VectorTracePanel({ controller, className }) {
2041
2077
  }
2042
2078
  )
2043
2079
  ] }),
2044
- message && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "text-[11px] text-red-600", children: message })
2080
+ message && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { className: "text-[11px] text-red-600", children: message })
2045
2081
  ] });
2046
2082
  }
2047
2083
 
2048
2084
  // src/vector/VectorSymmetryPanel.tsx
2049
- var import_vector14 = require("@pixerate/editor/vector");
2050
- var import_jsx_runtime14 = require("react/jsx-runtime");
2051
- 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";
2052
- var selectClass9 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2085
+ var import_vector15 = require("@pixerate/editor/vector");
2086
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2087
+ 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";
2088
+ var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2053
2089
  var numberClass8 = "h-7 w-12 rounded border border-border bg-background px-1.5 text-xs";
2054
2090
  function VectorSymmetryPanel({ controller, className }) {
2055
2091
  const state = useVectorEditorState(controller);
2056
- const group = state.selection.map((id) => controller.getNode(id)).find(import_vector14.isVectorSymmetryGroup) ?? null;
2092
+ const group = state.selection.map((id) => controller.getNode(id)).find(import_vector15.isVectorSymmetryGroup) ?? null;
2057
2093
  const s = group?.symmetry;
2058
2094
  const ids = group ? [group.id] : void 0;
2059
2095
  const drawing = !!group && state.symmetryTarget === group.id;
2060
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-symmetry-panel": "", "aria-label": "Symmetry", children: [
2061
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2062
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Symmetry" }),
2063
- !group && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, "data-vector-symmetry-action": "make", title: "Make Symmetry: the selection (or nothing yet) mirrored live across a vertical axis", onClick: () => controller.makeSymmetry({ mode: "vertical" }), children: "Mirror" }),
2064
- !group && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, "data-vector-symmetry-action": "radial", title: "Make Symmetry: the selection repeated round its centre", onClick: () => controller.makeSymmetry({ mode: "radial", copies: 6 }), children: "Radial" }),
2065
- group && s && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2066
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("select", { "aria-label": "Symmetry mode", className: selectClass9, value: s.mode, onChange: (e) => controller.setSymmetry({ mode: e.target.value }, { ids }), children: import_vector14.VECTOR_SYMMETRY_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: m, children: import_vector14.VECTOR_SYMMETRY_MODE_LABELS[m] }, m)) }),
2067
- s.mode === "radial" && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2068
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("input", { type: "number", min: 2, max: 64, "aria-label": "Symmetry copies", title: "Copies round the circle", className: numberClass8, value: s.copies ?? 6, onChange: (e) => controller.setSymmetry({ copies: Math.max(2, Math.min(64, parseInt(e.target.value, 10) || 2)) }, { ids }) }),
2069
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Mirror each copy too (kaleidoscope)", children: [
2070
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("input", { type: "checkbox", "aria-label": "Mirror copies", checked: !!s.mirror, onChange: (e) => controller.setSymmetry({ mirror: e.target.checked }, { ids }) }),
2096
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-symmetry-panel": "", "aria-label": "Symmetry", children: [
2097
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2098
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Symmetry" }),
2099
+ !group && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("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" }),
2100
+ !group && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("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" }),
2101
+ group && s && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2102
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("select", { "aria-label": "Symmetry mode", className: selectClass10, value: s.mode, onChange: (e) => controller.setSymmetry({ mode: e.target.value }, { ids }), children: import_vector15.VECTOR_SYMMETRY_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: m, children: import_vector15.VECTOR_SYMMETRY_MODE_LABELS[m] }, m)) }),
2103
+ s.mode === "radial" && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2104
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("input", { type: "number", min: 2, max: 64, "aria-label": "Symmetry copies", title: "Copies round the circle", className: numberClass8, value: s.copies ?? 6, onChange: (e) => controller.setSymmetry({ copies: Math.max(2, Math.min(64, parseInt(e.target.value, 10) || 2)) }, { ids }) }),
2105
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Mirror each copy too (kaleidoscope)", children: [
2106
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("input", { type: "checkbox", "aria-label": "Mirror copies", checked: !!s.mirror, onChange: (e) => controller.setSymmetry({ mirror: e.target.checked }, { ids }) }),
2071
2107
  "Mirror"
2072
2108
  ] })
2073
2109
  ] })
2074
2110
  ] })
2075
2111
  ] }),
2076
- group && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2077
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "w-16" }),
2078
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, "aria-pressed": drawing, "data-vector-symmetry-action": "draw", title: "Draw inside: new pen, pencil, shape and text art goes into the group, mirrored as you draw (Escape stops)", onClick: () => drawing ? controller.exitSymmetry() : controller.enterSymmetry(group.id), children: "Draw inside" }),
2079
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, title: "Expand: keep the copies as real objects", onClick: () => controller.releaseSymmetry({ ids, expand: true }), children: "Expand" }),
2080
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("button", { type: "button", className: buttonClass12, title: "Release: stop repeating (the copies go)", onClick: () => controller.releaseSymmetry({ ids }), children: "Release" })
2112
+ group && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2113
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16" }),
2114
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("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" }),
2115
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: buttonClass13, title: "Expand: keep the copies as real objects", onClick: () => controller.releaseSymmetry({ ids, expand: true }), children: "Expand" }),
2116
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: buttonClass13, title: "Release: stop repeating (the copies go)", onClick: () => controller.releaseSymmetry({ ids }), children: "Release" })
2117
+ ] })
2118
+ ] });
2119
+ }
2120
+
2121
+ // src/vector/VectorPerspectivePanel.tsx
2122
+ var import_react10 = __toESM(require("react"), 1);
2123
+ var import_vector16 = require("@pixerate/editor/vector");
2124
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2125
+ 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";
2126
+ var selectClass11 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2127
+ var PLANE_LABELS = { floor: "Floor", left: "Left wall", right: "Right wall", side: "Side wall", front: "Front" };
2128
+ function VectorPerspectivePanel({ controller, className }) {
2129
+ const state = useVectorEditorState(controller);
2130
+ const grid = state.document.perspective ?? null;
2131
+ const planes = grid ? (0, import_vector16.vectorPerspectivePlanes)(grid) : [];
2132
+ const [attachTo, setAttachTo] = import_react10.default.useState("");
2133
+ const target = (state.perspectivePlane ?? attachTo) || planes[0];
2134
+ const add = (n) => controller.setPerspective((0, import_vector16.vectorPerspectivePreset)(n, state.document.width, state.document.height));
2135
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-perspective-panel": "", "aria-label": "Perspective", children: [
2136
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2137
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Perspective" }),
2138
+ [1, 2, 3].map((n) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("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: [
2139
+ n,
2140
+ "-pt"
2141
+ ] }, n))
2142
+ ] }),
2143
+ grid && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2144
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16" }),
2145
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("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: [
2146
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: "", children: "Draw flat" }),
2147
+ planes.map((p) => /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("option", { value: p, children: [
2148
+ "Draw on ",
2149
+ PLANE_LABELS[p].toLowerCase()
2150
+ ] }, p))
2151
+ ] })
2152
+ ] }),
2153
+ grid && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2154
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16" }),
2155
+ !state.perspectivePlane && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("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__ */ (0, import_jsx_runtime16.jsx)("option", { value: p, children: PLANE_LABELS[p] }, p)) }),
2156
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("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" }),
2157
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Show the grid on the canvas", children: [
2158
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("input", { type: "checkbox", "aria-label": "Show perspective grid", checked: grid.visible !== false, onChange: (e) => controller.setPerspective({ ...grid, visible: e.target.checked }) }),
2159
+ "Show"
2160
+ ] }),
2161
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: buttonClass14, title: "Remove the grid (art on it stays)", onClick: () => controller.setPerspective(null), children: "Remove" })
2081
2162
  ] })
2082
2163
  ] });
2083
2164
  }
2084
2165
 
2085
2166
  // src/vector/VectorLensPanel.tsx
2086
- var import_vector15 = require("@pixerate/editor/vector");
2087
- var import_jsx_runtime15 = require("react/jsx-runtime");
2088
- var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2167
+ var import_vector17 = require("@pixerate/editor/vector");
2168
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2169
+ var selectClass12 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2089
2170
  var numberClass9 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2090
2171
  function VectorLensPanel({ controller, className }) {
2091
2172
  const state = useVectorEditorState(controller);
2092
2173
  const path = state.selection.map((id) => controller.getNode(id)).find((n) => n?.type === "path" && n.subpaths.some((sp) => sp.closed) && !n.connector);
2093
2174
  if (!path) return null;
2094
2175
  const lens = path.lens ?? null;
2095
- const spec = lens ? import_vector15.VECTOR_LENS_SPECS[lens.type] : null;
2176
+ const spec = lens ? import_vector17.VECTOR_LENS_SPECS[lens.type] : null;
2096
2177
  const ids = [path.id];
2097
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-lens-panel": "", "aria-label": "Lens", children: [
2098
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2099
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Lens" }),
2100
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2178
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-lens-panel": "", "aria-label": "Lens", children: [
2179
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2180
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Lens" }),
2181
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2101
2182
  "select",
2102
2183
  {
2103
2184
  "aria-label": "Lens type",
2104
- className: selectClass10,
2185
+ className: selectClass12,
2105
2186
  value: lens?.type ?? "",
2106
2187
  onChange: (e) => e.target.value ? controller.setLens({ type: e.target.value }, { ids }) : controller.setLens(null, { ids }),
2107
2188
  children: [
2108
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "", children: "None" }),
2109
- import_vector15.VECTOR_LENS_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: t, children: import_vector15.VECTOR_LENS_LABELS[t] }, t))
2189
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: "", children: "None" }),
2190
+ import_vector17.VECTOR_LENS_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: t, children: import_vector17.VECTOR_LENS_LABELS[t] }, t))
2110
2191
  ]
2111
2192
  }
2112
2193
  )
2113
2194
  ] }),
2114
- lens && spec && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2115
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16" }),
2116
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2195
+ lens && spec && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2196
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16" }),
2197
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2117
2198
  "input",
2118
2199
  {
2119
2200
  type: "number",
@@ -2130,21 +2211,21 @@ function VectorLensPanel({ controller, className }) {
2130
2211
  }
2131
2212
  }
2132
2213
  ),
2133
- spec.color !== null && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("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}` }) })
2214
+ spec.color !== null && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("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}` }) })
2134
2215
  ] })
2135
2216
  ] });
2136
2217
  }
2137
2218
 
2138
2219
  // src/vector/VectorGradientEditor.tsx
2139
- var import_vector16 = require("@pixerate/editor/vector");
2140
- var import_jsx_runtime16 = require("react/jsx-runtime");
2141
- var selectClass11 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2220
+ var import_vector18 = require("@pixerate/editor/vector");
2221
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2222
+ var selectClass13 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2142
2223
  var numberClass10 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
2143
- 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";
2224
+ 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";
2144
2225
  var percent = (v) => Math.round(v * 1e3) / 10;
2145
2226
  function VectorGradientEditor({ controller, className }) {
2146
2227
  const state = useVectorEditorState(controller);
2147
- const ed = (0, import_vector16.getVectorGradientEditor)(state);
2228
+ const ed = (0, import_vector18.getVectorGradientEditor)(state);
2148
2229
  if (!ed) return null;
2149
2230
  const g = ed.gradient;
2150
2231
  const sel = ed.selected;
@@ -2152,26 +2233,26 @@ function VectorGradientEditor({ controller, className }) {
2152
2233
  const setType = (value) => {
2153
2234
  if (value === "solid") {
2154
2235
  const first = ed.stops[0];
2155
- controller.setStyle({ [ed.target]: (0, import_vector16.vectorPaintToHex)(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2236
+ controller.setStyle({ [ed.target]: (0, import_vector18.vectorPaintToHex)(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2156
2237
  } else controller.setGradient({ type: value });
2157
2238
  };
2158
- return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2159
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2160
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2161
- ["fill", "stroke"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: buttonClass13, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
2162
- ed.mixed && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2239
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2240
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2241
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2242
+ ["fill", "stroke"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: buttonClass15, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
2243
+ ed.mixed && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2163
2244
  ] }),
2164
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2165
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2166
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("select", { "aria-label": "Gradient type", className: selectClass11, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2167
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: "solid", children: "Solid" }),
2168
- import_vector16.VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: t, children: import_vector16.VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
2245
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2246
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2247
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("select", { "aria-label": "Gradient type", className: selectClass13, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2248
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "solid", children: "Solid" }),
2249
+ import_vector18.VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: t, children: import_vector18.VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
2169
2250
  ] }),
2170
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: buttonClass13, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2251
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: buttonClass15, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2171
2252
  ] }),
2172
- g && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2173
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2174
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2253
+ g && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
2254
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2255
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2175
2256
  "div",
2176
2257
  {
2177
2258
  role: "button",
@@ -2185,7 +2266,7 @@ function VectorGradientEditor({ controller, className }) {
2185
2266
  }
2186
2267
  }
2187
2268
  ),
2188
- ed.stops.map((s) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2269
+ ed.stops.map((s) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2189
2270
  "button",
2190
2271
  {
2191
2272
  type: "button",
@@ -2199,21 +2280,21 @@ function VectorGradientEditor({ controller, className }) {
2199
2280
  s.index
2200
2281
  ))
2201
2282
  ] }),
2202
- sel && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2203
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2283
+ sel && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2284
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2204
2285
  "input",
2205
2286
  {
2206
2287
  type: "color",
2207
2288
  "aria-label": "Stop colour",
2208
2289
  className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
2209
- value: (0, import_vector16.vectorPaintToHex)(sel.color) ?? "#000000",
2290
+ value: (0, import_vector18.vectorPaintToHex)(sel.color) ?? "#000000",
2210
2291
  onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
2211
2292
  onBlur: end
2212
2293
  }
2213
2294
  ),
2214
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2295
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2215
2296
  "Opacity",
2216
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2297
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2217
2298
  "input",
2218
2299
  {
2219
2300
  type: "number",
@@ -2230,9 +2311,9 @@ function VectorGradientEditor({ controller, className }) {
2230
2311
  }
2231
2312
  )
2232
2313
  ] }),
2233
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2314
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2234
2315
  "At",
2235
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2316
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2236
2317
  "input",
2237
2318
  {
2238
2319
  type: "number",
@@ -2249,9 +2330,9 @@ function VectorGradientEditor({ controller, className }) {
2249
2330
  }
2250
2331
  )
2251
2332
  ] }),
2252
- !sel.last && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2333
+ !sel.last && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2253
2334
  "Midpoint",
2254
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2335
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2255
2336
  "input",
2256
2337
  {
2257
2338
  type: "number",
@@ -2268,15 +2349,15 @@ function VectorGradientEditor({ controller, className }) {
2268
2349
  }
2269
2350
  )
2270
2351
  ] }),
2271
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("button", { type: "button", className: buttonClass13, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2352
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: buttonClass15, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2272
2353
  ] }),
2273
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2274
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16", children: "Colours" }),
2275
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("select", { "aria-label": "Gradient interpolation", className: selectClass11, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: import_vector16.VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: i, children: import_vector16.VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2354
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2355
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16", children: "Colours" }),
2356
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("select", { "aria-label": "Gradient interpolation", className: selectClass13, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: import_vector18.VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: i, children: import_vector18.VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2276
2357
  ] }),
2277
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2278
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16", children: "Smooth" }),
2279
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2358
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2359
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16", children: "Smooth" }),
2360
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2280
2361
  "input",
2281
2362
  {
2282
2363
  type: "range",
@@ -2292,26 +2373,26 @@ function VectorGradientEditor({ controller, className }) {
2292
2373
  }
2293
2374
  )
2294
2375
  ] }),
2295
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2296
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16", children: "Spread" }),
2297
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("select", { "aria-label": "Gradient spread", className: selectClass11, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: import_vector16.VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: sp, children: import_vector16.VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2376
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2377
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16", children: "Spread" }),
2378
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("select", { "aria-label": "Gradient spread", className: selectClass13, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: import_vector18.VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: sp, children: import_vector18.VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2298
2379
  ] }),
2299
- ed.target === "stroke" && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2300
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16", children: "On stroke" }),
2301
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("select", { "aria-label": "Gradient on stroke", className: selectClass11, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: import_vector16.VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: m, children: import_vector16.VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2380
+ ed.target === "stroke" && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2381
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16", children: "On stroke" }),
2382
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("select", { "aria-label": "Gradient on stroke", className: selectClass13, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: import_vector18.VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: m, children: import_vector18.VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2302
2383
  ] })
2303
2384
  ] })
2304
2385
  ] });
2305
2386
  }
2306
2387
 
2307
2388
  // src/vector/VectorToolbar.tsx
2308
- var import_jsx_runtime17 = require("react/jsx-runtime");
2389
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2309
2390
  var VECTOR_SNAP_TITLE = "Snap to objects";
2310
2391
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
2311
- 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";
2392
+ 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";
2312
2393
  function VectorToolbar({
2313
2394
  controller,
2314
- tools = import_vector17.VECTOR_TOOLS,
2395
+ tools = import_vector19.VECTOR_TOOLS,
2315
2396
  showHistory = true,
2316
2397
  showArrange = true,
2317
2398
  showSnap = true,
@@ -2325,36 +2406,36 @@ function VectorToolbar({
2325
2406
  const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
2326
2407
  const editing = state.editing;
2327
2408
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
2328
- const maskSelected = state.selection.some((id) => (0, import_vector17.isVectorMaskGroup)(controller.getNode(id)));
2329
- const [live, setLive] = import_react10.default.useState(false);
2409
+ const maskSelected = state.selection.some((id) => (0, import_vector19.isVectorMaskGroup)(controller.getNode(id)));
2410
+ const [live, setLive] = import_react11.default.useState(false);
2330
2411
  const booleanDisabled = live ? count < 1 : count < 2;
2331
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: buttonClass14, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2332
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2333
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2412
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", className: buttonClass16, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2413
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2414
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2334
2415
  "button",
2335
2416
  {
2336
2417
  type: "button",
2337
2418
  "data-vector-tool": tool,
2338
- className: buttonClass14,
2419
+ className: buttonClass16,
2339
2420
  "aria-pressed": state.tool === tool,
2340
- title: `${(0, import_vector17.vectorToolLabel)(tool)} (${import_vector17.VECTOR_TOOL_SHORTCUTS[tool]})`,
2341
- "aria-label": (0, import_vector17.vectorToolLabel)(tool),
2421
+ title: `${(0, import_vector19.vectorToolLabel)(tool)} (${import_vector19.VECTOR_TOOL_SHORTCUTS[tool]})`,
2422
+ "aria-label": (0, import_vector19.vectorToolLabel)(tool),
2342
2423
  onClick: () => controller.setTool(tool),
2343
- children: import_vector17.VECTOR_TOOL_GLYPHS[tool]
2424
+ children: import_vector19.VECTOR_TOOL_GLYPHS[tool]
2344
2425
  },
2345
2426
  tool
2346
2427
  )) }),
2347
- showHistory && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2428
+ showHistory && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2348
2429
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
2349
2430
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
2350
2431
  ] }),
2351
- showSnap && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2352
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2432
+ showSnap && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2433
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2353
2434
  "button",
2354
2435
  {
2355
2436
  type: "button",
2356
2437
  "data-vector-snap": "",
2357
- className: buttonClass14,
2438
+ className: buttonClass16,
2358
2439
  "aria-pressed": state.snap.objects,
2359
2440
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
2360
2441
  "aria-label": VECTOR_SNAP_TITLE,
@@ -2362,18 +2443,18 @@ function VectorToolbar({
2362
2443
  children: "Snap"
2363
2444
  }
2364
2445
  ),
2365
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2446
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
2366
2447
  "select",
2367
2448
  {
2368
2449
  "data-vector-polar": "",
2369
2450
  "aria-label": "Polar tracking increment",
2370
2451
  title: "Polar tracking: the angles drawn points snap to from where they leave",
2371
2452
  className: "h-8 rounded-md border border-border bg-background px-1 text-xs",
2372
- value: String((0, import_vector17.vectorPolarIncrementOf)(state.snap.angles) ?? ""),
2373
- onChange: (e) => controller.setSnap({ angles: (0, import_vector17.vectorPolarAngles)(Number(e.target.value)) }),
2453
+ value: String((0, import_vector19.vectorPolarIncrementOf)(state.snap.angles) ?? ""),
2454
+ onChange: (e) => controller.setSnap({ angles: (0, import_vector19.vectorPolarAngles)(Number(e.target.value)) }),
2374
2455
  children: [
2375
- (0, import_vector17.vectorPolarIncrementOf)(state.snap.angles) === null && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: "", children: "Custom" }),
2376
- [90, 45, 30, 15].map((n) => /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("option", { value: n, children: [
2456
+ (0, import_vector19.vectorPolarIncrementOf)(state.snap.angles) === null && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "", children: "Custom" }),
2457
+ [90, 45, 30, 15].map((n) => /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("option", { value: n, children: [
2377
2458
  n,
2378
2459
  "\xB0"
2379
2460
  ] }, n))
@@ -2381,13 +2462,13 @@ function VectorToolbar({
2381
2462
  }
2382
2463
  )
2383
2464
  ] }),
2384
- showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2385
- import_vector17.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2465
+ showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2466
+ import_vector19.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2386
2467
  "button",
2387
2468
  {
2388
2469
  type: "button",
2389
2470
  "data-vector-path-action": a.id,
2390
- className: buttonClass14,
2471
+ className: buttonClass16,
2391
2472
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
2392
2473
  "aria-label": a.title,
2393
2474
  disabled: !a.enabled(count, points),
@@ -2396,12 +2477,12 @@ function VectorToolbar({
2396
2477
  },
2397
2478
  a.id
2398
2479
  )),
2399
- import_vector17.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2480
+ import_vector19.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2400
2481
  "button",
2401
2482
  {
2402
2483
  type: "button",
2403
2484
  "data-vector-edit": t.key,
2404
- className: buttonClass14,
2485
+ className: buttonClass16,
2405
2486
  "aria-pressed": editing[t.key],
2406
2487
  title: t.title,
2407
2488
  "aria-label": t.title,
@@ -2410,9 +2491,9 @@ function VectorToolbar({
2410
2491
  },
2411
2492
  t.key
2412
2493
  )),
2413
- state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2494
+ state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2414
2495
  "Smoothing",
2415
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2496
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2416
2497
  "input",
2417
2498
  {
2418
2499
  type: "range",
@@ -2426,9 +2507,9 @@ function VectorToolbar({
2426
2507
  }
2427
2508
  )
2428
2509
  ] }),
2429
- state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2510
+ state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2430
2511
  "Size",
2431
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2512
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2432
2513
  "input",
2433
2514
  {
2434
2515
  type: "number",
@@ -2444,26 +2525,26 @@ function VectorToolbar({
2444
2525
  }
2445
2526
  )
2446
2527
  ] }),
2447
- state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2528
+ state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2448
2529
  "button",
2449
2530
  {
2450
2531
  type: "button",
2451
2532
  "data-vector-fill-gap": "",
2452
- className: buttonClass14,
2533
+ className: buttonClass16,
2453
2534
  "aria-pressed": editing.fillGap > 0,
2454
2535
  title: "Close small gaps between path ends when filling",
2455
2536
  "aria-label": "Close gaps",
2456
- onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector17.VECTOR_FILL_GAP }),
2537
+ onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector19.VECTOR_FILL_GAP }),
2457
2538
  children: "Gaps"
2458
2539
  }
2459
2540
  )
2460
2541
  ] }),
2461
- showDistort && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2542
+ showDistort && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2462
2543
  "button",
2463
2544
  {
2464
2545
  type: "button",
2465
2546
  "data-vector-free-distort": "",
2466
- className: buttonClass14,
2547
+ className: buttonClass16,
2467
2548
  "aria-pressed": state.freeDistort,
2468
2549
  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)`,
2469
2550
  "aria-label": "Free Distort",
@@ -2471,15 +2552,15 @@ function VectorToolbar({
2471
2552
  children: "Distort"
2472
2553
  }
2473
2554
  ) }),
2474
- showBlend && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorBlendControls, { controller }),
2475
- showArrange && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
2476
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2477
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2555
+ showBlend && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorBlendControls, { controller }),
2556
+ showArrange && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
2557
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2558
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2478
2559
  "button",
2479
2560
  {
2480
2561
  type: "button",
2481
2562
  "data-vector-live-boolean": "",
2482
- className: buttonClass14,
2563
+ className: buttonClass16,
2483
2564
  "aria-pressed": live,
2484
2565
  title: "Live booleans: keep the shapes editable",
2485
2566
  "aria-label": "Live booleans",
@@ -2496,12 +2577,12 @@ function VectorToolbar({
2496
2577
  act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
2497
2578
  act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
2498
2579
  ] }),
2499
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: import_vector17.VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2580
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: import_vector19.VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2500
2581
  "button",
2501
2582
  {
2502
2583
  type: "button",
2503
2584
  "data-vector-pathfinder": a.id,
2504
- className: buttonClass14,
2585
+ className: buttonClass16,
2505
2586
  title: a.title,
2506
2587
  "aria-label": a.title,
2507
2588
  disabled: count < a.min,
@@ -2510,7 +2591,7 @@ function VectorToolbar({
2510
2591
  },
2511
2592
  a.id
2512
2593
  )) }),
2513
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2594
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2514
2595
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
2515
2596
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
2516
2597
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -2525,22 +2606,22 @@ function VectorToolbar({
2525
2606
  }
2526
2607
  function VectorBlendControls({ controller }) {
2527
2608
  const state = useVectorEditorState(controller);
2528
- const blend = (0, import_vector17.getVectorBlendControls)(state);
2609
+ const blend = (0, import_vector19.getVectorBlendControls)(state);
2529
2610
  const o = blend.options;
2530
2611
  const selected = blend.ids.length > 0;
2531
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2532
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass14, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
2533
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2612
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2613
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("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" }),
2614
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2534
2615
  "select",
2535
2616
  {
2536
2617
  "aria-label": "Blend spacing",
2537
2618
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
2538
2619
  value: o.spacing,
2539
- onChange: (e) => (0, import_vector17.applyVectorBlendOptions)(controller, { spacing: e.target.value }),
2540
- children: import_vector17.VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: sp, children: import_vector17.VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2620
+ onChange: (e) => (0, import_vector19.applyVectorBlendOptions)(controller, { spacing: e.target.value }),
2621
+ children: import_vector19.VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: sp, children: import_vector19.VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2541
2622
  }
2542
2623
  ),
2543
- o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2624
+ o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2544
2625
  "input",
2545
2626
  {
2546
2627
  type: "number",
@@ -2552,29 +2633,29 @@ function VectorBlendControls({ controller }) {
2552
2633
  onChange: (e) => {
2553
2634
  const n = parseFloat(e.target.value);
2554
2635
  if (!Number.isFinite(n) || n <= 0) return;
2555
- (0, import_vector17.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
2636
+ (0, import_vector19.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
2556
2637
  }
2557
2638
  }
2558
2639
  ),
2559
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2640
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2560
2641
  "button",
2561
2642
  {
2562
2643
  type: "button",
2563
2644
  "data-vector-blend-action": "orient",
2564
- className: buttonClass14,
2645
+ className: buttonClass16,
2565
2646
  "aria-pressed": o.orientToSpine,
2566
2647
  title: "Orient steps to the spine",
2567
2648
  "aria-label": "Orient to spine",
2568
- onClick: () => (0, import_vector17.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
2649
+ onClick: () => (0, import_vector19.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
2569
2650
  children: "\u293F"
2570
2651
  }
2571
2652
  ),
2572
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2653
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2573
2654
  "button",
2574
2655
  {
2575
2656
  type: "button",
2576
2657
  "data-vector-blend-action": "spine",
2577
- className: buttonClass14,
2658
+ className: buttonClass16,
2578
2659
  title: "Replace Spine (select a blend and a path)",
2579
2660
  "aria-label": "Replace Spine",
2580
2661
  disabled: !blend.canReplaceSpine,
@@ -2582,15 +2663,15 @@ function VectorBlendControls({ controller }) {
2582
2663
  children: "\u2307"
2583
2664
  }
2584
2665
  ),
2585
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass14, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2586
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass14, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2666
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass16, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2667
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass16, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2587
2668
  ] });
2588
2669
  }
2589
- var selectClass12 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2670
+ var selectClass14 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2590
2671
  var numberClass11 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2591
2672
  function VectorStylePanel({ controller, className }) {
2592
2673
  const state = useVectorEditorState(controller);
2593
- const style = (0, import_vector17.getVectorSelectionStyle)(state);
2674
+ const style = (0, import_vector19.getVectorSelectionStyle)(state);
2594
2675
  const d = state.defaults;
2595
2676
  const source = style ?? {
2596
2677
  fill: d.fill ?? null,
@@ -2603,7 +2684,7 @@ function VectorStylePanel({ controller, className }) {
2603
2684
  arrowAlign: d.arrowAlign ?? "extend",
2604
2685
  startArrowScale: d.startArrowScale ?? 1,
2605
2686
  endArrowScale: d.endArrowScale ?? 1,
2606
- widthProfile: (0, import_vector17.widthProfilePresetId)(d.strokeWidthProfile),
2687
+ widthProfile: (0, import_vector19.widthProfilePresetId)(d.strokeWidthProfile),
2607
2688
  cornerRadius: d.cornerRadius,
2608
2689
  cornerKind: d.cornerKind ?? "round",
2609
2690
  cornerSmoothing: 0,
@@ -2617,19 +2698,19 @@ function VectorStylePanel({ controller, className }) {
2617
2698
  };
2618
2699
  const setProfile = (id) => {
2619
2700
  if (style) controller.setWidthProfile(id);
2620
- else controller.setDefaults({ strokeWidthProfile: (0, import_vector17.widthProfilePreset)(id)?.length ? (0, import_vector17.widthProfilePreset)(id) : null });
2701
+ else controller.setDefaults({ strokeWidthProfile: (0, import_vector19.widthProfilePreset)(id)?.length ? (0, import_vector19.widthProfilePreset)(id) : null });
2621
2702
  };
2622
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2703
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2623
2704
  const arrowRow = (end, label) => {
2624
2705
  const key = end === "start" ? "startArrow" : "endArrow";
2625
2706
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
2626
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2627
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
2628
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("select", { "aria-label": `${label} arrowhead`, className: selectClass12, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2629
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: "", children: "None" }),
2630
- import_vector17.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: h, children: import_vector17.VECTOR_ARROWHEAD_LABELS[h] }, h))
2707
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2708
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
2709
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("select", { "aria-label": `${label} arrowhead`, className: selectClass14, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2710
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "", children: "None" }),
2711
+ import_vector19.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: h, children: import_vector19.VECTOR_ARROWHEAD_LABELS[h] }, h))
2631
2712
  ] }),
2632
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2713
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2633
2714
  "input",
2634
2715
  {
2635
2716
  type: "number",
@@ -2653,14 +2734,14 @@ function VectorStylePanel({ controller, className }) {
2653
2734
  };
2654
2735
  const paintRow = (key, label) => {
2655
2736
  const paint = source[key];
2656
- const hex = (0, import_vector17.vectorPaintToHex)(paint);
2737
+ const hex = (0, import_vector19.vectorPaintToHex)(paint);
2657
2738
  const on = paint !== null && paint !== "none";
2658
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2659
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2660
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2739
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2740
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2741
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2661
2742
  label
2662
2743
  ] }),
2663
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2744
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2664
2745
  "input",
2665
2746
  {
2666
2747
  type: "color",
@@ -2671,18 +2752,18 @@ function VectorStylePanel({ controller, className }) {
2671
2752
  onChange: (e) => apply({ [key]: e.target.value })
2672
2753
  }
2673
2754
  ),
2674
- on && !hex && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2675
- style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2755
+ on && !hex && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2756
+ style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2676
2757
  ] });
2677
2758
  };
2678
- return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2679
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2759
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2760
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2680
2761
  paintRow("fill", "Fill"),
2681
2762
  paintRow("stroke", "Stroke"),
2682
- style && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorGradientEditor, { controller }),
2683
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2684
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16", children: "Width" }),
2685
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2763
+ style && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorGradientEditor, { controller }),
2764
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2765
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16", children: "Width" }),
2766
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2686
2767
  "input",
2687
2768
  {
2688
2769
  type: "number",
@@ -2698,33 +2779,33 @@ function VectorStylePanel({ controller, className }) {
2698
2779
  }
2699
2780
  )
2700
2781
  ] }),
2701
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2702
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16", children: "Align" }),
2703
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("select", { "aria-label": "Stroke alignment", className: selectClass12, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(import_vector17.VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: a, children: import_vector17.VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
2782
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2783
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16", children: "Align" }),
2784
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("select", { "aria-label": "Stroke alignment", className: selectClass14, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(import_vector19.VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: a, children: import_vector19.VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
2704
2785
  mixed("strokeAlign")
2705
2786
  ] }),
2706
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2707
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16", children: "Profile" }),
2708
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2787
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2788
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16", children: "Profile" }),
2789
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
2709
2790
  "select",
2710
2791
  {
2711
2792
  "aria-label": "Width profile",
2712
- className: selectClass12,
2793
+ className: selectClass14,
2713
2794
  value: source.widthProfile,
2714
2795
  onChange: (e) => {
2715
2796
  if (e.target.value !== "custom") setProfile(e.target.value);
2716
2797
  },
2717
2798
  children: [
2718
- import_vector17.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: p.id, children: p.label }, p.id)),
2719
- source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
2799
+ import_vector19.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: p.id, children: p.label }, p.id)),
2800
+ source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
2720
2801
  ]
2721
2802
  }
2722
2803
  ),
2723
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2804
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2724
2805
  "button",
2725
2806
  {
2726
2807
  type: "button",
2727
- className: buttonClass14,
2808
+ className: buttonClass16,
2728
2809
  title: "Reset to uniform width",
2729
2810
  "aria-label": "Reset to uniform width",
2730
2811
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -2734,20 +2815,20 @@ function VectorStylePanel({ controller, className }) {
2734
2815
  ),
2735
2816
  mixed("widthProfile")
2736
2817
  ] }),
2737
- source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
2818
+ source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
2738
2819
  arrowRow("start", "Start"),
2739
2820
  arrowRow("end", "End"),
2740
- (source.startArrow || source.endArrow) && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2741
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16", children: "Heads" }),
2742
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass12, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2743
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: "extend", children: "Tip past end" }),
2744
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: "tip", children: "Tip at end" })
2821
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2822
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16", children: "Heads" }),
2823
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass14, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2824
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "extend", children: "Tip past end" }),
2825
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "tip", children: "Tip at end" })
2745
2826
  ] })
2746
2827
  ] })
2747
2828
  ] }),
2748
- source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2749
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2750
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2829
+ source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2830
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2831
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2751
2832
  "input",
2752
2833
  {
2753
2834
  type: "number",
@@ -2762,12 +2843,12 @@ function VectorStylePanel({ controller, className }) {
2762
2843
  }
2763
2844
  }
2764
2845
  ),
2765
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("select", { "aria-label": "Corner kind", className: selectClass12, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: import_vector17.VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: k, children: import_vector17.VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2846
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("select", { "aria-label": "Corner kind", className: selectClass14, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: import_vector19.VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: k, children: import_vector19.VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2766
2847
  mixed("cornerRadius")
2767
2848
  ] }),
2768
- style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "corner-smoothing", title: "Corner smoothing: squircle corners that ease into the curve (0.6 is iOS-like)", children: [
2769
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16", children: "Smoothing" }),
2770
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2849
+ style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "corner-smoothing", title: "Corner smoothing: squircle corners that ease into the curve (0.6 is iOS-like)", children: [
2850
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16", children: "Smoothing" }),
2851
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2771
2852
  "input",
2772
2853
  {
2773
2854
  type: "range",
@@ -2782,9 +2863,9 @@ function VectorStylePanel({ controller, className }) {
2782
2863
  }
2783
2864
  )
2784
2865
  ] }),
2785
- style && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2786
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16", children: "Opacity" }),
2787
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2866
+ style && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2867
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "w-16", children: "Opacity" }),
2868
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2788
2869
  "input",
2789
2870
  {
2790
2871
  type: "range",
@@ -2798,34 +2879,36 @@ function VectorStylePanel({ controller, className }) {
2798
2879
  }
2799
2880
  )
2800
2881
  ] }),
2801
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorTextPanel, { controller }),
2802
- style && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorAppearancePanel, { controller }),
2803
- style && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorEffectsPanel, { controller }),
2804
- style && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorEnvelopePanel, { controller }),
2805
- style && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorMaskPanel, { controller }),
2806
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorFramePanel, { controller }),
2807
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorConnectorPanel, { controller }),
2808
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorShadingPanel, { controller }),
2809
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorSymmetryPanel, { controller }),
2810
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorLensPanel, { controller }),
2811
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorTracePanel, { controller }),
2812
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorConstraintsPanel, { controller }),
2813
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(VectorSymbolsPanel, { controller })
2882
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorTextPanel, { controller }),
2883
+ style && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorAppearancePanel, { controller }),
2884
+ style && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorEffectsPanel, { controller }),
2885
+ style && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorEnvelopePanel, { controller }),
2886
+ style && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorMaskPanel, { controller }),
2887
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorFramePanel, { controller }),
2888
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorPagesPanel, { controller }),
2889
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorConnectorPanel, { controller }),
2890
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorShadingPanel, { controller }),
2891
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorSymmetryPanel, { controller }),
2892
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorPerspectivePanel, { controller }),
2893
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorLensPanel, { controller }),
2894
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorTracePanel, { controller }),
2895
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorConstraintsPanel, { controller }),
2896
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(VectorSymbolsPanel, { controller })
2814
2897
  ] });
2815
2898
  }
2816
2899
 
2817
2900
  // src/effects/useEffects.ts
2818
- var import_react11 = require("react");
2901
+ var import_react12 = require("react");
2819
2902
  function useEffects(host, provider) {
2820
- (0, import_react11.useEffect)(() => {
2903
+ (0, import_react12.useEffect)(() => {
2821
2904
  if (!host || !provider) return;
2822
2905
  return host.useEffects(provider);
2823
2906
  }, [host, provider]);
2824
2907
  }
2825
2908
  function useEffectDescription(provider, effect) {
2826
- const [state, setState] = (0, import_react11.useState)({ key: "", description: null, error: null });
2909
+ const [state, setState] = (0, import_react12.useState)({ key: "", description: null, error: null });
2827
2910
  const key = provider && effect ? `${provider.id}:${effect}` : "";
2828
- (0, import_react11.useEffect)(() => {
2911
+ (0, import_react12.useEffect)(() => {
2829
2912
  if (!provider || !effect) return;
2830
2913
  let live = true;
2831
2914
  Promise.resolve().then(() => provider.describe(effect)).then(
@@ -2840,13 +2923,13 @@ function useEffectDescription(provider, effect) {
2840
2923
  }
2841
2924
 
2842
2925
  // src/effects/EffectThumbnail.tsx
2843
- var import_react12 = require("react");
2926
+ var import_react13 = require("react");
2844
2927
  var import_effects = require("@pixerate/editor/effects");
2845
- var import_jsx_runtime18 = require("react/jsx-runtime");
2928
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2846
2929
  function EffectThumbnail({ provider, effect, width = 96, height = 54, className = "" }) {
2847
- const ref = (0, import_react12.useRef)(null);
2848
- const [failed, setFailed] = (0, import_react12.useState)(effect.kind === "motion");
2849
- (0, import_react12.useEffect)(() => {
2930
+ const ref = (0, import_react13.useRef)(null);
2931
+ const [failed, setFailed] = (0, import_react13.useState)(effect.kind === "motion");
2932
+ (0, import_react13.useEffect)(() => {
2850
2933
  if (effect.kind === "motion") return;
2851
2934
  let live = true;
2852
2935
  (0, import_effects.renderEffectThumbnail)(provider, effect, { width, height }).then(
@@ -2861,28 +2944,28 @@ function EffectThumbnail({ provider, effect, width = 96, height = 54, className
2861
2944
  live = false;
2862
2945
  };
2863
2946
  }, [provider, effect, width, height]);
2864
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2947
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2865
2948
  "div",
2866
2949
  {
2867
2950
  className: `relative overflow-hidden rounded border border-border bg-[repeating-conic-gradient(#8884_0_25%,transparent_0_50%)] bg-[length:12px_12px] ${className}`,
2868
2951
  style: { aspectRatio: `${width} / ${height}` },
2869
2952
  "data-effect-thumbnail": effect.id,
2870
- children: failed ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "absolute inset-0 flex items-center justify-center text-[10px] text-muted-foreground", children: effect.kind === "motion" ? "Motion" : "No preview" }) : /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("canvas", { ref, width, height, className: "h-full w-full", "aria-hidden": "true" })
2953
+ children: failed ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "absolute inset-0 flex items-center justify-center text-[10px] text-muted-foreground", children: effect.kind === "motion" ? "Motion" : "No preview" }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("canvas", { ref, width, height, className: "h-full w-full", "aria-hidden": "true" })
2871
2954
  }
2872
2955
  );
2873
2956
  }
2874
2957
 
2875
2958
  // src/effects/EffectsBrowser.tsx
2876
- var import_react13 = require("react");
2959
+ var import_react14 = require("react");
2877
2960
  var import_effects2 = require("@pixerate/editor/effects");
2878
- var import_jsx_runtime19 = require("react/jsx-runtime");
2961
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2879
2962
  function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, title = "Effects", className = "" }) {
2880
- const [all, setAll] = (0, import_react13.useState)([]);
2881
- const [query, setQuery] = (0, import_react13.useState)("");
2882
- const [category, setCategory] = (0, import_react13.useState)("");
2883
- const [use, setUse] = (0, import_react13.useState)("");
2884
- const [error, setError] = (0, import_react13.useState)(null);
2885
- (0, import_react13.useEffect)(() => {
2963
+ const [all, setAll] = (0, import_react14.useState)([]);
2964
+ const [query, setQuery] = (0, import_react14.useState)("");
2965
+ const [category, setCategory] = (0, import_react14.useState)("");
2966
+ const [use, setUse] = (0, import_react14.useState)("");
2967
+ const [error, setError] = (0, import_react14.useState)(null);
2968
+ (0, import_react14.useEffect)(() => {
2886
2969
  if (!provider) return setAll([]);
2887
2970
  let live = true;
2888
2971
  Promise.resolve().then(() => provider.list()).then(
@@ -2893,8 +2976,8 @@ function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, titl
2893
2976
  live = false;
2894
2977
  };
2895
2978
  }, [provider]);
2896
- const [found, setFound] = (0, import_react13.useState)([]);
2897
- (0, import_react13.useEffect)(() => {
2979
+ const [found, setFound] = (0, import_react14.useState)([]);
2980
+ (0, import_react14.useEffect)(() => {
2898
2981
  if (!provider) return setFound([]);
2899
2982
  let live = true;
2900
2983
  Promise.resolve().then(() => provider.search(query, { ...category ? { category } : {}, ...use ? { use } : {} })).then((list) => live && setFound(list), () => live && setFound([]));
@@ -2903,14 +2986,14 @@ function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, titl
2903
2986
  };
2904
2987
  }, [provider, query, category, use]);
2905
2988
  const allowed = (e) => (!kinds || kinds.includes(e.kind)) && (uses ? e.kind !== "motion" || uses.includes("motion") : e.kind !== "motion") && (!uses || e.uses.some((u) => uses.includes(u)));
2906
- const categories = (0, import_react13.useMemo)(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses, kinds]);
2989
+ const categories = (0, import_react14.useMemo)(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses, kinds]);
2907
2990
  const shown = found.filter(allowed);
2908
2991
  if (!provider)
2909
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: `rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground ${className}`, "data-effects-browser": "", "data-effects-unavailable": "", children: "No effects provider is registered, so effects cannot be added. Effects already in the document are kept." });
2910
- return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("section", { className: `flex flex-col gap-2 ${className}`, "data-effects-browser": "", "aria-label": "Effects browser", children: [
2911
- title && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { className: "text-xs font-semibold", children: title }),
2912
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
2913
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2992
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: `rounded-md border border-dashed border-border p-3 text-xs text-muted-foreground ${className}`, "data-effects-browser": "", "data-effects-unavailable": "", children: "No effects provider is registered, so effects cannot be added. Effects already in the document are kept." });
2993
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("section", { className: `flex flex-col gap-2 ${className}`, "data-effects-browser": "", "aria-label": "Effects browser", children: [
2994
+ title && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("h3", { className: "text-xs font-semibold", children: title }),
2995
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
2996
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2914
2997
  "input",
2915
2998
  {
2916
2999
  type: "search",
@@ -2921,17 +3004,17 @@ function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, titl
2921
3004
  onChange: (e) => setQuery(e.currentTarget.value)
2922
3005
  }
2923
3006
  ),
2924
- /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("select", { className: "rounded border border-border bg-transparent px-1 py-0.5 text-xs", "aria-label": "Category", value: category, onChange: (e) => setCategory(e.currentTarget.value), children: [
2925
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "", children: "All categories" }),
2926
- categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: c, children: c }, c))
3007
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("select", { className: "rounded border border-border bg-transparent px-1 py-0.5 text-xs", "aria-label": "Category", value: category, onChange: (e) => setCategory(e.currentTarget.value), children: [
3008
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: "", children: "All categories" }),
3009
+ categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: c, children: c }, c))
2927
3010
  ] }),
2928
- uses && uses.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("select", { className: "rounded border border-border bg-transparent px-1 py-0.5 text-xs", "aria-label": "Use", value: use, onChange: (e) => setUse(e.currentTarget.value), children: [
2929
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: "", children: "Any use" }),
2930
- uses.map((u) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("option", { value: u, children: import_effects2.EFFECT_USE_LABELS[u] }, u))
3011
+ uses && uses.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("select", { className: "rounded border border-border bg-transparent px-1 py-0.5 text-xs", "aria-label": "Use", value: use, onChange: (e) => setUse(e.currentTarget.value), children: [
3012
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: "", children: "Any use" }),
3013
+ uses.map((u) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: u, children: import_effects2.EFFECT_USE_LABELS[u] }, u))
2931
3014
  ] })
2932
3015
  ] }),
2933
- error && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: error }),
2934
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("ul", { className: "grid max-h-96 grid-cols-[repeat(auto-fill,minmax(6.5rem,1fr))] gap-1.5 overflow-y-auto", role: "list", children: shown.map((e) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
3016
+ error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: error }),
3017
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("ul", { className: "grid max-h-96 grid-cols-[repeat(auto-fill,minmax(6.5rem,1fr))] gap-1.5 overflow-y-auto", role: "list", children: shown.map((e) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
2935
3018
  "button",
2936
3019
  {
2937
3020
  type: "button",
@@ -2940,25 +3023,25 @@ function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, titl
2940
3023
  "data-effect-id": e.id,
2941
3024
  onClick: () => onPick(e),
2942
3025
  children: [
2943
- thumbnails && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(EffectThumbnail, { provider, effect: e }),
2944
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "truncate text-[11px] font-medium", children: e.name }),
2945
- /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("span", { className: "truncate text-[10px] text-muted-foreground", children: e.uses.map((u) => import_effects2.EFFECT_USE_LABELS[u]).join(" \xB7 ") })
3026
+ thumbnails && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(EffectThumbnail, { provider, effect: e }),
3027
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "truncate text-[11px] font-medium", children: e.name }),
3028
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "truncate text-[10px] text-muted-foreground", children: e.uses.map((u) => import_effects2.EFFECT_USE_LABELS[u]).join(" \xB7 ") })
2946
3029
  ]
2947
3030
  }
2948
3031
  ) }, e.id)) }),
2949
- !shown.length && !error && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects match." })
3032
+ !shown.length && !error && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects match." })
2950
3033
  ] });
2951
3034
  }
2952
3035
 
2953
3036
  // src/effects/EffectParameters.tsx
2954
- var import_react15 = __toESM(require("react"), 1);
3037
+ var import_react16 = __toESM(require("react"), 1);
2955
3038
  var import_effects3 = require("@pixerate/editor/effects");
2956
3039
  var import_parameters2 = require("@pixerate/editor/parameters");
2957
3040
 
2958
3041
  // src/parameters/fields.tsx
2959
- var import_react14 = require("react");
3042
+ var import_react15 = require("react");
2960
3043
  var import_parameters = require("@pixerate/editor/parameters");
2961
- var import_jsx_runtime20 = require("react/jsx-runtime");
3044
+ var import_jsx_runtime22 = require("react/jsx-runtime");
2962
3045
  var parameterInputClass = "h-7 w-full min-w-0 rounded-md border border-input bg-background px-2 text-xs text-foreground tabular-nums focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:cursor-not-allowed disabled:opacity-50";
2963
3046
  var parameterButtonClass = "inline-flex h-7 items-center justify-center gap-1 rounded-md border border-input bg-background px-2 text-xs text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:pointer-events-none disabled:opacity-50";
2964
3047
  var rangeClass = "h-4 w-full min-w-0 flex-1 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50";
@@ -2982,7 +3065,7 @@ function ParameterCommitInput({ value, onCommit, multiline, className, onKeyDown
2982
3065
  onKeyDown: keyDown,
2983
3066
  ...rest
2984
3067
  };
2985
- return multiline ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("textarea", { ...props, className: `${props.className} h-auto min-h-[3.5rem] py-1` }, value) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("input", { type: "text", ...props }, value);
3068
+ return multiline ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("textarea", { ...props, className: `${props.className} h-auto min-h-[3.5rem] py-1` }, value) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("input", { type: "text", ...props }, value);
2986
3069
  }
2987
3070
  var plainNumber = (field, value) => (0, import_parameters.formatParameterNumber)({ ...field, unit: void 0 }, value);
2988
3071
  function ParameterNumberInput({
@@ -2993,8 +3076,8 @@ function ParameterNumberInput({
2993
3076
  id,
2994
3077
  "aria-label": ariaLabel
2995
3078
  }) {
2996
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
2997
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3079
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
3080
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
2998
3081
  ParameterCommitInput,
2999
3082
  {
3000
3083
  id,
@@ -3016,21 +3099,21 @@ function ParameterNumberInput({
3016
3099
  }
3017
3100
  }
3018
3101
  ),
3019
- field.unit && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-muted-foreground", children: field.unit })
3102
+ field.unit && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 text-[10px] text-muted-foreground", children: field.unit })
3020
3103
  ] });
3021
3104
  }
3022
3105
  function ParameterSlider(props) {
3023
3106
  const { field, value, onChange, onGestureStart, onGestureEnd, disabled } = props;
3024
- const dragging = (0, import_react14.useRef)(false);
3107
+ const dragging = (0, import_react15.useRef)(false);
3025
3108
  const end = () => {
3026
3109
  if (!dragging.current) return;
3027
3110
  dragging.current = false;
3028
3111
  onGestureEnd?.();
3029
3112
  };
3030
- if (!(0, import_parameters.isSliderField)(field)) return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterNumberInput, { ...props });
3113
+ if (!(0, import_parameters.isSliderField)(field)) return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ParameterNumberInput, { ...props });
3031
3114
  const step = (0, import_parameters.parameterStep)(field);
3032
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-2", children: [
3033
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3115
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-2", children: [
3116
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3034
3117
  "input",
3035
3118
  {
3036
3119
  type: "range",
@@ -3053,11 +3136,11 @@ function ParameterSlider(props) {
3053
3136
  onChange: (e) => onChange((0, import_parameters.clampParameterNumber)(field, Number(e.target.value)))
3054
3137
  }
3055
3138
  ),
3056
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "w-20 shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterNumberInput, { ...props, id: props.id, "aria-label": `${(0, import_parameters.parameterLabel)(field)} value` }) })
3139
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "w-20 shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ParameterNumberInput, { ...props, id: props.id, "aria-label": `${(0, import_parameters.parameterLabel)(field)} value` }) })
3057
3140
  ] });
3058
3141
  }
3059
3142
  function ParameterToggle({ field, value, onChange, disabled, id }) {
3060
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3143
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3061
3144
  "button",
3062
3145
  {
3063
3146
  type: "button",
@@ -3068,7 +3151,7 @@ function ParameterToggle({ field, value, onChange, disabled, id }) {
3068
3151
  disabled: disabled || field.readOnly,
3069
3152
  onClick: () => onChange(!value),
3070
3153
  className: `relative inline-flex h-5 w-9 shrink-0 items-center rounded-full border border-transparent transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary disabled:opacity-50 ${value ? "bg-primary" : "bg-muted"}`,
3071
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3154
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3072
3155
  "span",
3073
3156
  {
3074
3157
  className: `pointer-events-none block h-4 w-4 rounded-full bg-background shadow transition-transform ${value ? "translate-x-4" : "translate-x-0.5"}`
@@ -3081,7 +3164,7 @@ function ParameterSelect({ field, value, onChange, disabled, id }) {
3081
3164
  const options = (0, import_parameters.getChoiceOptions)(field);
3082
3165
  const off = disabled || field.readOnly;
3083
3166
  if (field.control === "segmented") {
3084
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { role: "radiogroup", "aria-label": (0, import_parameters.parameterLabel)(field), className: "inline-flex flex-wrap rounded-md border border-input bg-muted p-0.5", children: options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3167
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { role: "radiogroup", "aria-label": (0, import_parameters.parameterLabel)(field), className: "inline-flex flex-wrap rounded-md border border-input bg-muted p-0.5", children: options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3085
3168
  "button",
3086
3169
  {
3087
3170
  type: "button",
@@ -3097,7 +3180,7 @@ function ParameterSelect({ field, value, onChange, disabled, id }) {
3097
3180
  )) });
3098
3181
  }
3099
3182
  const index = options.findIndex((o) => o.value === value);
3100
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3183
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3101
3184
  "select",
3102
3185
  {
3103
3186
  id,
@@ -3109,12 +3192,12 @@ function ParameterSelect({ field, value, onChange, disabled, id }) {
3109
3192
  if (option) onChange(option.value);
3110
3193
  },
3111
3194
  className: parameterInputClass,
3112
- children: options.map((o, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: String(i), title: o.description, children: (0, import_parameters.choiceOptionLabel)(o) }, String(o.value)))
3195
+ children: options.map((o, i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: String(i), title: o.description, children: (0, import_parameters.choiceOptionLabel)(o) }, String(o.value)))
3113
3196
  }
3114
3197
  );
3115
3198
  }
3116
3199
  function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd, disabled, id }) {
3117
- const picking = (0, import_react14.useRef)(false);
3200
+ const picking = (0, import_react15.useRef)(false);
3118
3201
  const alpha = !!field.alpha;
3119
3202
  const hex = (0, import_parameters.normalizeHexColor)(value, true) ?? "#000000ff";
3120
3203
  const rgb = hex.slice(0, 7);
@@ -3130,11 +3213,11 @@ function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd,
3130
3213
  picking.current = false;
3131
3214
  onGestureEnd?.();
3132
3215
  };
3133
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1.5", children: [
3134
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-2", children: [
3135
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "relative h-7 w-9 shrink-0 overflow-hidden rounded-md border border-input bg-[repeating-conic-gradient(#d4d4d8_0_25%,#fff_0_50%)] bg-[length:8px_8px]", children: [
3136
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "absolute inset-0", style: { background: alpha ? hex : rgb } }),
3137
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3216
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "space-y-1.5", children: [
3217
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-2", children: [
3218
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("span", { className: "relative h-7 w-9 shrink-0 overflow-hidden rounded-md border border-input bg-[repeating-conic-gradient(#d4d4d8_0_25%,#fff_0_50%)] bg-[length:8px_8px]", children: [
3219
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "absolute inset-0", style: { background: alpha ? hex : rgb } }),
3220
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3138
3221
  "input",
3139
3222
  {
3140
3223
  type: "color",
@@ -3150,7 +3233,7 @@ function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd,
3150
3233
  }
3151
3234
  )
3152
3235
  ] }),
3153
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3236
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3154
3237
  ParameterCommitInput,
3155
3238
  {
3156
3239
  id,
@@ -3166,7 +3249,7 @@ function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd,
3166
3249
  }
3167
3250
  )
3168
3251
  ] }),
3169
- alpha && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3252
+ alpha && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3170
3253
  "input",
3171
3254
  {
3172
3255
  type: "range",
@@ -3184,10 +3267,10 @@ function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd,
3184
3267
  onChange: (e) => onChange(`${rgb}${Number(e.target.value).toString(16).padStart(2, "0")}`)
3185
3268
  }
3186
3269
  ),
3187
- field.swatches && field.swatches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex flex-wrap gap-1", children: field.swatches.map((s) => {
3270
+ field.swatches && field.swatches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex flex-wrap gap-1", children: field.swatches.map((s) => {
3188
3271
  const swatch = (0, import_parameters.normalizeHexColor)(s, alpha);
3189
3272
  if (!swatch) return null;
3190
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3273
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3191
3274
  "button",
3192
3275
  {
3193
3276
  type: "button",
@@ -3206,11 +3289,11 @@ function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd,
3206
3289
  }
3207
3290
  function ParameterVector({ field, value, onChange, disabled, id }) {
3208
3291
  const size = (0, import_parameters.vectorSize)(field);
3209
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "grid gap-1.5", style: { gridTemplateColumns: `repeat(${size}, minmax(0, 1fr))` }, children: Array.from({ length: size }, (_, i) => {
3292
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "grid gap-1.5", style: { gridTemplateColumns: `repeat(${size}, minmax(0, 1fr))` }, children: Array.from({ length: size }, (_, i) => {
3210
3293
  const component = (0, import_parameters.vectorComponentField)(field, i);
3211
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("label", { className: "flex min-w-0 items-center gap-1", children: [
3212
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "w-3 shrink-0 text-[10px] uppercase text-muted-foreground", children: component.label }),
3213
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3294
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { className: "flex min-w-0 items-center gap-1", children: [
3295
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "w-3 shrink-0 text-[10px] uppercase text-muted-foreground", children: component.label }),
3296
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3214
3297
  ParameterNumberInput,
3215
3298
  {
3216
3299
  field: component,
@@ -3227,11 +3310,11 @@ function ParameterVector({ field, value, onChange, disabled, id }) {
3227
3310
  function ParameterImage({ field, value, onChange, disabled, id, resolveFile }) {
3228
3311
  const off = disabled || field.readOnly;
3229
3312
  const name = (0, import_parameters.parameterLabel)(field);
3230
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "space-y-1.5", children: [
3231
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-2", children: [
3232
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex h-12 w-16 shrink-0 items-center justify-center overflow-hidden rounded-md border border-input bg-muted", children: value ? /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("img", { src: value, alt: name, className: "h-full w-full object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "None" }) }),
3233
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
3234
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3313
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "space-y-1.5", children: [
3314
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-2", children: [
3315
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex h-12 w-16 shrink-0 items-center justify-center overflow-hidden rounded-md border border-input bg-muted", children: value ? /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("img", { src: value, alt: name, className: "h-full w-full object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "None" }) }),
3316
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
3317
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3235
3318
  ParameterCommitInput,
3236
3319
  {
3237
3320
  id,
@@ -3242,10 +3325,10 @@ function ParameterImage({ field, value, onChange, disabled, id, resolveFile }) {
3242
3325
  onCommit: (text) => onChange(text.trim())
3243
3326
  }
3244
3327
  ),
3245
- /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex gap-1", children: [
3246
- resolveFile && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("label", { className: `${parameterButtonClass} cursor-pointer ${off ? "pointer-events-none opacity-50" : ""}`, children: [
3328
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex gap-1", children: [
3329
+ resolveFile && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("label", { className: `${parameterButtonClass} cursor-pointer ${off ? "pointer-events-none opacity-50" : ""}`, children: [
3247
3330
  "Choose file\u2026",
3248
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3331
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3249
3332
  "input",
3250
3333
  {
3251
3334
  type: "file",
@@ -3261,11 +3344,11 @@ function ParameterImage({ field, value, onChange, disabled, id, resolveFile }) {
3261
3344
  }
3262
3345
  )
3263
3346
  ] }),
3264
- value && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { type: "button", className: parameterButtonClass, disabled: off, onClick: () => onChange(""), "aria-label": `Clear ${name}`, children: "Clear" })
3347
+ value && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", className: parameterButtonClass, disabled: off, onClick: () => onChange(""), "aria-label": `Clear ${name}`, children: "Clear" })
3265
3348
  ] })
3266
3349
  ] })
3267
3350
  ] }),
3268
- field.options && field.options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "flex flex-wrap gap-1", children: field.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3351
+ field.options && field.options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex flex-wrap gap-1", children: field.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3269
3352
  "button",
3270
3353
  {
3271
3354
  type: "button",
@@ -3275,14 +3358,14 @@ function ParameterImage({ field, value, onChange, disabled, id, resolveFile }) {
3275
3358
  disabled: off,
3276
3359
  onClick: () => onChange(o.url),
3277
3360
  className: "h-9 w-12 overflow-hidden rounded border border-input aria-pressed:ring-2 aria-pressed:ring-primary",
3278
- children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("img", { src: o.url, alt: "", className: "h-full w-full object-cover" })
3361
+ children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("img", { src: o.url, alt: "", className: "h-full w-full object-cover" })
3279
3362
  },
3280
3363
  o.url
3281
3364
  )) })
3282
3365
  ] });
3283
3366
  }
3284
3367
  function ParameterText({ field, value, onChange, disabled, id }) {
3285
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3368
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3286
3369
  ParameterCommitInput,
3287
3370
  {
3288
3371
  id,
@@ -3301,31 +3384,31 @@ function ParameterFieldControl({ field, value, onChange, resolveFile, ...rest })
3301
3384
  switch (field.type) {
3302
3385
  case "number":
3303
3386
  case "int":
3304
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterSlider, { field, value, onChange: set, ...rest });
3387
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ParameterSlider, { field, value, onChange: set, ...rest });
3305
3388
  case "boolean":
3306
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterToggle, { field, value, onChange: set, ...rest });
3389
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ParameterToggle, { field, value, onChange: set, ...rest });
3307
3390
  case "choice":
3308
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterSelect, { field, value, onChange: set, ...rest });
3391
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ParameterSelect, { field, value, onChange: set, ...rest });
3309
3392
  case "color":
3310
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterColor, { field, value, onChange: set, ...rest });
3393
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ParameterColor, { field, value, onChange: set, ...rest });
3311
3394
  case "vector":
3312
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterVector, { field, value, onChange: set, ...rest });
3395
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ParameterVector, { field, value, onChange: set, ...rest });
3313
3396
  case "image":
3314
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterImage, { field, value, onChange: set, resolveFile, ...rest });
3397
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ParameterImage, { field, value, onChange: set, resolveFile, ...rest });
3315
3398
  case "text":
3316
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterText, { field, value, onChange: set, ...rest });
3399
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(ParameterText, { field, value, onChange: set, ...rest });
3317
3400
  }
3318
3401
  }
3319
3402
 
3320
3403
  // src/effects/EffectParameters.tsx
3321
- var import_jsx_runtime21 = require("react/jsx-runtime");
3404
+ var import_jsx_runtime23 = require("react/jsx-runtime");
3322
3405
  function EffectParameters({ description, values, onChange, onReset, keyframed, onPreset, disabled = false, className = "" }) {
3323
- const fields = (0, import_react15.useMemo)(() => (0, import_effects3.effectParameterFields)(description), [description]);
3324
- const idBase = (0, import_react15.useId)();
3325
- const gesture = import_react15.default.useRef(null);
3326
- const count = import_react15.default.useRef(0);
3327
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: `space-y-2 ${className}`, "data-effect-parameters": description.id, children: [
3328
- onPreset && description.presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
3406
+ const fields = (0, import_react16.useMemo)(() => (0, import_effects3.effectParameterFields)(description), [description]);
3407
+ const idBase = (0, import_react16.useId)();
3408
+ const gesture = import_react16.default.useRef(null);
3409
+ const count = import_react16.default.useRef(0);
3410
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: `space-y-2 ${className}`, "data-effect-parameters": description.id, children: [
3411
+ onPreset && description.presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
3329
3412
  "select",
3330
3413
  {
3331
3414
  className: "w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs",
@@ -3337,8 +3420,8 @@ function EffectParameters({ description, values, onChange, onReset, keyframed, o
3337
3420
  if (preset) onPreset({ ...description.defaults, ...preset.values });
3338
3421
  },
3339
3422
  children: [
3340
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: "", children: "Presets\u2026" }),
3341
- description.presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: p.name, children: p.name }, p.name))
3423
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("option", { value: "", children: "Presets\u2026" }),
3424
+ description.presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("option", { value: p.name, children: p.name }, p.name))
3342
3425
  ]
3343
3426
  }
3344
3427
  ),
@@ -3348,13 +3431,13 @@ function EffectParameters({ description, values, onChange, onReset, keyframed, o
3348
3431
  const keyed = keyframed?.has(field.id) ?? false;
3349
3432
  const value = (0, import_effects3.effectFieldValue)(field, values[field.id]);
3350
3433
  const changed = !keyed && !(0, import_parameters2.parameterValuesEqual)(value, field.default);
3351
- return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "space-y-1", "data-effect-control": field.id, children: [
3352
- /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-1.5", children: [
3353
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("label", { htmlFor: inputId, className: "min-w-0 flex-1 truncate text-xs font-medium", title: field.description, children: label }),
3354
- keyed && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "keyframed" }),
3355
- onReset && changed && !disabled && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { type: "button", className: "rounded px-1 text-[11px] text-muted-foreground hover:text-foreground", "aria-label": `Reset ${label}`, onClick: () => onReset(field.id), children: "\u21BA" })
3434
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "space-y-1", "data-effect-control": field.id, children: [
3435
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center gap-1.5", children: [
3436
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("label", { htmlFor: inputId, className: "min-w-0 flex-1 truncate text-xs font-medium", title: field.description, children: label }),
3437
+ keyed && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "keyframed" }),
3438
+ onReset && changed && !disabled && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: "rounded px-1 text-[11px] text-muted-foreground hover:text-foreground", "aria-label": `Reset ${label}`, onClick: () => onReset(field.id), children: "\u21BA" })
3356
3439
  ] }),
3357
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3440
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
3358
3441
  ParameterFieldControl,
3359
3442
  {
3360
3443
  field,
@@ -3368,25 +3451,25 @@ function EffectParameters({ description, values, onChange, onReset, keyframed, o
3368
3451
  )
3369
3452
  ] }, field.id);
3370
3453
  }),
3371
- !fields.length && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No controls." })
3454
+ !fields.length && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No controls." })
3372
3455
  ] });
3373
3456
  }
3374
3457
 
3375
3458
  // src/vector/VectorProviderEffectsPanel.tsx
3376
- var import_react16 = require("react");
3377
- var import_jsx_runtime22 = require("react/jsx-runtime");
3459
+ var import_react17 = require("react");
3460
+ var import_jsx_runtime24 = require("react/jsx-runtime");
3378
3461
  var btn = "rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40";
3379
3462
  function Controls({ controller, group }) {
3380
3463
  const provider = controller.effectsProvider;
3381
3464
  const tag = group.providerEffect;
3382
3465
  const { description, error } = useEffectDescription(provider, tag.effect);
3383
- const [message, setMessage] = (0, import_react16.useState)(null);
3466
+ const [message, setMessage] = (0, import_react17.useState)(null);
3384
3467
  const run = (work) => void work.then(() => setMessage(null), (e) => setMessage(e instanceof Error ? e.message : String(e)));
3385
- if (!provider) return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider: the shapes are kept as they are." });
3386
- if (error) return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-[11px] text-amber-500", children: error });
3387
- if (!description) return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Loading\u2026" });
3388
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(import_jsx_runtime22.Fragment, { children: [
3389
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3468
+ if (!provider) return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider: the shapes are kept as they are." });
3469
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-[11px] text-amber-500", children: error });
3470
+ if (!description) return /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Loading\u2026" });
3471
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
3472
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
3390
3473
  EffectParameters,
3391
3474
  {
3392
3475
  description,
@@ -3396,18 +3479,18 @@ function Controls({ controller, group }) {
3396
3479
  onPreset: (v) => run(controller.updateVectorEffect(group.id, v))
3397
3480
  }
3398
3481
  ),
3399
- message && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
3482
+ message && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
3400
3483
  ] });
3401
3484
  }
3402
3485
  function VectorProviderEffectsPanel({ controller, className = "" }) {
3403
3486
  const state = useVectorEditorState(controller);
3404
- const provider = (0, import_react16.useSyncExternalStore)(
3487
+ const provider = (0, import_react17.useSyncExternalStore)(
3405
3488
  (cb) => controller.effects.subscribe(cb),
3406
3489
  () => controller.effects.provider,
3407
3490
  () => controller.effects.provider
3408
3491
  );
3409
- const [browse, setBrowse] = (0, import_react16.useState)(false);
3410
- const [message, setMessage] = (0, import_react16.useState)(null);
3492
+ const [browse, setBrowse] = (0, import_react17.useState)(false);
3493
+ const [message, setMessage] = (0, import_react17.useState)(null);
3411
3494
  void state;
3412
3495
  const groups = controller.vectorEffectGroups();
3413
3496
  const selected = groups.filter((g) => state.selection.includes(g.id));
@@ -3415,41 +3498,41 @@ function VectorProviderEffectsPanel({ controller, className = "" }) {
3415
3498
  setMessage(null);
3416
3499
  Promise.resolve().then(work).catch((e) => setMessage(e instanceof Error ? e.message : String(e)));
3417
3500
  };
3418
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("section", { className: `flex flex-col gap-2 text-xs ${className}`, "aria-label": "Vector effects", "data-vector-provider-effects": "", children: [
3419
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1", children: [
3420
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "flex-1 font-semibold", children: "Effects" }),
3421
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", className: btn, disabled: !provider, "aria-pressed": browse, onClick: () => setBrowse(!browse), children: "Add effect\u2026" })
3501
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("section", { className: `flex flex-col gap-2 text-xs ${className}`, "aria-label": "Vector effects", "data-vector-provider-effects": "", children: [
3502
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex items-center gap-1", children: [
3503
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "flex-1 font-semibold", children: "Effects" }),
3504
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { type: "button", className: btn, disabled: !provider, "aria-pressed": browse, onClick: () => setBrowse(!browse), children: "Add effect\u2026" })
3422
3505
  ] }),
3423
- !provider && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider is registered. Effect groups are kept as shapes." }),
3424
- browse && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(EffectsBrowser, { provider, kinds: ["vector"], title: "Vector effects", onPick: (e) => (setBrowse(false), run(() => controller.addVectorEffect(e.id))) }),
3425
- selected.map((g) => /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "space-y-1 rounded border border-border p-1", "data-vector-effect-group": g.id, children: [
3426
- /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1", children: [
3427
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "min-w-0 flex-1 truncate font-medium", children: g.providerEffect.name ?? g.providerEffect.effect }),
3428
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { type: "button", className: btn, title: "Keep the shapes as a plain group", onClick: () => run(() => controller.detachVectorEffect(g.id)), children: "Detach" })
3506
+ !provider && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider is registered. Effect groups are kept as shapes." }),
3507
+ browse && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(EffectsBrowser, { provider, kinds: ["vector"], title: "Vector effects", onPick: (e) => (setBrowse(false), run(() => controller.addVectorEffect(e.id))) }),
3508
+ selected.map((g) => /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "space-y-1 rounded border border-border p-1", "data-vector-effect-group": g.id, children: [
3509
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex items-center gap-1", children: [
3510
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { className: "min-w-0 flex-1 truncate font-medium", children: g.providerEffect.name ?? g.providerEffect.effect }),
3511
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("button", { type: "button", className: btn, title: "Keep the shapes as a plain group", onClick: () => run(() => controller.detachVectorEffect(g.id)), children: "Detach" })
3429
3512
  ] }),
3430
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Controls, { controller, group: g })
3513
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(Controls, { controller, group: g })
3431
3514
  ] }, g.id)),
3432
- groups.length > 0 && !selected.length && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("p", { className: "text-[11px] text-muted-foreground", children: [
3515
+ groups.length > 0 && !selected.length && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { className: "text-[11px] text-muted-foreground", children: [
3433
3516
  "Select an effect group (",
3434
3517
  groups.length,
3435
3518
  " in the drawing) to change it."
3436
3519
  ] }),
3437
- message && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
3520
+ message && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
3438
3521
  ] });
3439
3522
  }
3440
3523
 
3441
3524
  // src/vector/VectorEditor.tsx
3442
- var import_jsx_runtime23 = require("react/jsx-runtime");
3525
+ var import_jsx_runtime25 = require("react/jsx-runtime");
3443
3526
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, effects, className }) {
3444
3527
  useEffects(controller, effects);
3445
3528
  const effectsShown = !!effects || controller.vectorEffectGroups().length > 0;
3446
- return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
3447
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(VectorToolbar, { controller, tools }),
3448
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
3449
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
3450
- showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex w-full shrink-0 flex-col gap-2 md:w-56", children: [
3451
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(VectorStylePanel, { controller, className: "rounded-md border border-border p-3" }),
3452
- effectsShown && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(VectorProviderEffectsPanel, { controller, className: "rounded-md border border-border p-3" })
3529
+ return /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
3530
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(VectorToolbar, { controller, tools }),
3531
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
3532
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
3533
+ showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime25.jsxs)("div", { className: "flex w-full shrink-0 flex-col gap-2 md:w-56", children: [
3534
+ /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(VectorStylePanel, { controller, className: "rounded-md border border-border p-3" }),
3535
+ effectsShown && /* @__PURE__ */ (0, import_jsx_runtime25.jsx)(VectorProviderEffectsPanel, { controller, className: "rounded-md border border-border p-3" })
3453
3536
  ] })
3454
3537
  ] })
3455
3538
  ] });
@@ -3471,6 +3554,8 @@ function VectorEditor({ controller, tools, showStylePanel = true, height = 520,
3471
3554
  VectorLensPanel,
3472
3555
  VectorMaskPanel,
3473
3556
  VectorMaskedNodes,
3557
+ VectorPagesPanel,
3558
+ VectorPerspectivePanel,
3474
3559
  VectorProviderEffectsPanel,
3475
3560
  VectorSceneNodes,
3476
3561
  VectorShadingPanel,