@pixerate/editor-react 0.83.0 → 0.84.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,7 @@ __export(vector_exports, {
45
45
  VectorLensPanel: () => VectorLensPanel,
46
46
  VectorMaskPanel: () => VectorMaskPanel,
47
47
  VectorMaskedNodes: () => VectorMaskedNodes,
48
+ VectorPerspectivePanel: () => VectorPerspectivePanel,
48
49
  VectorProviderEffectsPanel: () => VectorProviderEffectsPanel,
49
50
  VectorSceneNodes: () => VectorSceneNodes,
50
51
  VectorShadingPanel: () => VectorShadingPanel,
@@ -694,6 +695,11 @@ function VectorCanvas({
694
695
  sh.lines.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d, stroke: accentColor, strokeWidth: 1, opacity: 0.7 }, i)),
695
696
  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
697
  ] }, `shading:${sh.nodeId}`)),
698
+ overlay.perspective && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-perspective-grid": "", fill: "none", pointerEvents: "none", children: [
699
+ 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)),
700
+ 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)),
701
+ /* @__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 })
702
+ ] }),
697
703
  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
704
  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
705
  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 +836,8 @@ function VectorCanvas({
830
836
  }
831
837
 
832
838
  // src/vector/VectorToolbar.tsx
833
- var import_react10 = __toESM(require("react"), 1);
834
- var import_vector17 = require("@pixerate/editor/vector");
839
+ var import_react11 = __toESM(require("react"), 1);
840
+ var import_vector18 = require("@pixerate/editor/vector");
835
841
 
836
842
  // src/vector/VectorEffectsPanel.tsx
837
843
  var import_vector4 = require("@pixerate/editor/vector");
@@ -2082,38 +2088,83 @@ function VectorSymmetryPanel({ controller, className }) {
2082
2088
  ] });
2083
2089
  }
2084
2090
 
2085
- // src/vector/VectorLensPanel.tsx
2091
+ // src/vector/VectorPerspectivePanel.tsx
2092
+ var import_react10 = __toESM(require("react"), 1);
2086
2093
  var import_vector15 = require("@pixerate/editor/vector");
2087
2094
  var import_jsx_runtime15 = require("react/jsx-runtime");
2095
+ 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
2096
  var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2097
+ var PLANE_LABELS = { floor: "Floor", left: "Left wall", right: "Right wall", side: "Side wall", front: "Front" };
2098
+ function VectorPerspectivePanel({ controller, className }) {
2099
+ const state = useVectorEditorState(controller);
2100
+ const grid = state.document.perspective ?? null;
2101
+ const planes = grid ? (0, import_vector15.vectorPerspectivePlanes)(grid) : [];
2102
+ const [attachTo, setAttachTo] = import_react10.default.useState("");
2103
+ const target = (state.perspectivePlane ?? attachTo) || planes[0];
2104
+ const add = (n) => controller.setPerspective((0, import_vector15.vectorPerspectivePreset)(n, state.document.width, state.document.height));
2105
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-perspective-panel": "", "aria-label": "Perspective", children: [
2106
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2107
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Perspective" }),
2108
+ [1, 2, 3].map((n) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { type: "button", className: buttonClass13, "aria-pressed": grid?.vanishingPoints.length === n, "data-vector-perspective-preset": n, title: `A ${n}-point perspective grid for the page`, onClick: () => add(n), children: [
2109
+ n,
2110
+ "-pt"
2111
+ ] }, n))
2112
+ ] }),
2113
+ grid && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2114
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16" }),
2115
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("select", { "aria-label": "Draw on plane", title: "New rectangles, ellipses, polygons and stars are drawn on this plane", className: selectClass10, value: state.perspectivePlane ?? "", onChange: (e) => controller.setPerspectivePlane(e.target.value || null), children: [
2116
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("option", { value: "", children: "Draw flat" }),
2117
+ planes.map((p) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("option", { value: p, children: [
2118
+ "Draw on ",
2119
+ PLANE_LABELS[p].toLowerCase()
2120
+ ] }, p))
2121
+ ] })
2122
+ ] }),
2123
+ grid && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2124
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { className: "w-16" }),
2125
+ !state.perspectivePlane && /* @__PURE__ */ (0, import_jsx_runtime15.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_runtime15.jsx)("option", { value: p, children: PLANE_LABELS[p] }, p)) }),
2126
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: buttonClass13, disabled: !state.selection.length, title: "Lay the selection onto the plane (read flat from the grid's origin)", onClick: () => controller.attachToPerspective(target), children: "Attach" }),
2127
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Show the grid on the canvas", children: [
2128
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("input", { type: "checkbox", "aria-label": "Show perspective grid", checked: grid.visible !== false, onChange: (e) => controller.setPerspective({ ...grid, visible: e.target.checked }) }),
2129
+ "Show"
2130
+ ] }),
2131
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("button", { type: "button", className: buttonClass13, title: "Remove the grid (art on it stays)", onClick: () => controller.setPerspective(null), children: "Remove" })
2132
+ ] })
2133
+ ] });
2134
+ }
2135
+
2136
+ // src/vector/VectorLensPanel.tsx
2137
+ var import_vector16 = require("@pixerate/editor/vector");
2138
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2139
+ var selectClass11 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2089
2140
  var numberClass9 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2090
2141
  function VectorLensPanel({ controller, className }) {
2091
2142
  const state = useVectorEditorState(controller);
2092
2143
  const path = state.selection.map((id) => controller.getNode(id)).find((n) => n?.type === "path" && n.subpaths.some((sp) => sp.closed) && !n.connector);
2093
2144
  if (!path) return null;
2094
2145
  const lens = path.lens ?? null;
2095
- const spec = lens ? import_vector15.VECTOR_LENS_SPECS[lens.type] : null;
2146
+ const spec = lens ? import_vector16.VECTOR_LENS_SPECS[lens.type] : null;
2096
2147
  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)(
2148
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-lens-panel": "", "aria-label": "Lens", children: [
2149
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2150
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Lens" }),
2151
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2101
2152
  "select",
2102
2153
  {
2103
2154
  "aria-label": "Lens type",
2104
- className: selectClass10,
2155
+ className: selectClass11,
2105
2156
  value: lens?.type ?? "",
2106
2157
  onChange: (e) => e.target.value ? controller.setLens({ type: e.target.value }, { ids }) : controller.setLens(null, { ids }),
2107
2158
  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))
2159
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: "", children: "None" }),
2160
+ import_vector16.VECTOR_LENS_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("option", { value: t, children: import_vector16.VECTOR_LENS_LABELS[t] }, t))
2110
2161
  ]
2111
2162
  }
2112
2163
  )
2113
2164
  ] }),
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)(
2165
+ lens && spec && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex flex-wrap items-center gap-1", children: [
2166
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { className: "w-16" }),
2167
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2117
2168
  "input",
2118
2169
  {
2119
2170
  type: "number",
@@ -2130,21 +2181,21 @@ function VectorLensPanel({ controller, className }) {
2130
2181
  }
2131
2182
  }
2132
2183
  ),
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}` }) })
2184
+ spec.color !== null && /* @__PURE__ */ (0, import_jsx_runtime16.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
2185
  ] })
2135
2186
  ] });
2136
2187
  }
2137
2188
 
2138
2189
  // 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";
2190
+ var import_vector17 = require("@pixerate/editor/vector");
2191
+ var import_jsx_runtime17 = require("react/jsx-runtime");
2192
+ var selectClass12 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2142
2193
  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";
2194
+ var buttonClass14 = "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
2195
  var percent = (v) => Math.round(v * 1e3) / 10;
2145
2196
  function VectorGradientEditor({ controller, className }) {
2146
2197
  const state = useVectorEditorState(controller);
2147
- const ed = (0, import_vector16.getVectorGradientEditor)(state);
2198
+ const ed = (0, import_vector17.getVectorGradientEditor)(state);
2148
2199
  if (!ed) return null;
2149
2200
  const g = ed.gradient;
2150
2201
  const sel = ed.selected;
@@ -2152,26 +2203,26 @@ function VectorGradientEditor({ controller, className }) {
2152
2203
  const setType = (value) => {
2153
2204
  if (value === "solid") {
2154
2205
  const first = ed.stops[0];
2155
- controller.setStyle({ [ed.target]: (0, import_vector16.vectorPaintToHex)(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2206
+ controller.setStyle({ [ed.target]: (0, import_vector17.vectorPaintToHex)(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2156
2207
  } else controller.setGradient({ type: value });
2157
2208
  };
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" })
2209
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2210
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2211
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2212
+ ["fill", "stroke"].map((t) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: buttonClass14, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
2213
+ ed.mixed && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2163
2214
  ] }),
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))
2215
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex items-center gap-1.5", children: [
2216
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2217
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("select", { "aria-label": "Gradient type", className: selectClass12, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2218
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: "solid", children: "Solid" }),
2219
+ import_vector17.VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: t, children: import_vector17.VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
2169
2220
  ] }),
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" })
2221
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: buttonClass14, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2171
2222
  ] }),
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)(
2223
+ g && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
2224
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2225
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2175
2226
  "div",
2176
2227
  {
2177
2228
  role: "button",
@@ -2185,7 +2236,7 @@ function VectorGradientEditor({ controller, className }) {
2185
2236
  }
2186
2237
  }
2187
2238
  ),
2188
- ed.stops.map((s) => /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2239
+ ed.stops.map((s) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2189
2240
  "button",
2190
2241
  {
2191
2242
  type: "button",
@@ -2199,21 +2250,21 @@ function VectorGradientEditor({ controller, className }) {
2199
2250
  s.index
2200
2251
  ))
2201
2252
  ] }),
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)(
2253
+ sel && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2254
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2204
2255
  "input",
2205
2256
  {
2206
2257
  type: "color",
2207
2258
  "aria-label": "Stop colour",
2208
2259
  className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
2209
- value: (0, import_vector16.vectorPaintToHex)(sel.color) ?? "#000000",
2260
+ value: (0, import_vector17.vectorPaintToHex)(sel.color) ?? "#000000",
2210
2261
  onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
2211
2262
  onBlur: end
2212
2263
  }
2213
2264
  ),
2214
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2265
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2215
2266
  "Opacity",
2216
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2267
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2217
2268
  "input",
2218
2269
  {
2219
2270
  type: "number",
@@ -2230,9 +2281,9 @@ function VectorGradientEditor({ controller, className }) {
2230
2281
  }
2231
2282
  )
2232
2283
  ] }),
2233
- /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2284
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2234
2285
  "At",
2235
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2286
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2236
2287
  "input",
2237
2288
  {
2238
2289
  type: "number",
@@ -2249,9 +2300,9 @@ function VectorGradientEditor({ controller, className }) {
2249
2300
  }
2250
2301
  )
2251
2302
  ] }),
2252
- !sel.last && /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2303
+ !sel.last && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2253
2304
  "Midpoint",
2254
- /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2305
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2255
2306
  "input",
2256
2307
  {
2257
2308
  type: "number",
@@ -2268,15 +2319,15 @@ function VectorGradientEditor({ controller, className }) {
2268
2319
  }
2269
2320
  )
2270
2321
  ] }),
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" })
2322
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("button", { type: "button", className: buttonClass14, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2272
2323
  ] }),
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)) })
2324
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2325
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16", children: "Colours" }),
2326
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("select", { "aria-label": "Gradient interpolation", className: selectClass12, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: import_vector17.VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: i, children: import_vector17.VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2276
2327
  ] }),
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)(
2328
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2329
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16", children: "Smooth" }),
2330
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2280
2331
  "input",
2281
2332
  {
2282
2333
  type: "range",
@@ -2292,26 +2343,26 @@ function VectorGradientEditor({ controller, className }) {
2292
2343
  }
2293
2344
  )
2294
2345
  ] }),
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)) })
2346
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2347
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16", children: "Spread" }),
2348
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("select", { "aria-label": "Gradient spread", className: selectClass12, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: import_vector17.VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: sp, children: import_vector17.VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2298
2349
  ] }),
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)) })
2350
+ ed.target === "stroke" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2351
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "w-16", children: "On stroke" }),
2352
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("select", { "aria-label": "Gradient on stroke", className: selectClass12, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: import_vector17.VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("option", { value: m, children: import_vector17.VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2302
2353
  ] })
2303
2354
  ] })
2304
2355
  ] });
2305
2356
  }
2306
2357
 
2307
2358
  // src/vector/VectorToolbar.tsx
2308
- var import_jsx_runtime17 = require("react/jsx-runtime");
2359
+ var import_jsx_runtime18 = require("react/jsx-runtime");
2309
2360
  var VECTOR_SNAP_TITLE = "Snap to objects";
2310
2361
  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";
2362
+ var buttonClass15 = "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
2363
  function VectorToolbar({
2313
2364
  controller,
2314
- tools = import_vector17.VECTOR_TOOLS,
2365
+ tools = import_vector18.VECTOR_TOOLS,
2315
2366
  showHistory = true,
2316
2367
  showArrange = true,
2317
2368
  showSnap = true,
@@ -2325,36 +2376,36 @@ function VectorToolbar({
2325
2376
  const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
2326
2377
  const editing = state.editing;
2327
2378
  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);
2379
+ const maskSelected = state.selection.some((id) => (0, import_vector18.isVectorMaskGroup)(controller.getNode(id)));
2380
+ const [live, setLive] = import_react11.default.useState(false);
2330
2381
  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)(
2382
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", className: buttonClass15, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2383
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2384
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2334
2385
  "button",
2335
2386
  {
2336
2387
  type: "button",
2337
2388
  "data-vector-tool": tool,
2338
- className: buttonClass14,
2389
+ className: buttonClass15,
2339
2390
  "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),
2391
+ title: `${(0, import_vector18.vectorToolLabel)(tool)} (${import_vector18.VECTOR_TOOL_SHORTCUTS[tool]})`,
2392
+ "aria-label": (0, import_vector18.vectorToolLabel)(tool),
2342
2393
  onClick: () => controller.setTool(tool),
2343
- children: import_vector17.VECTOR_TOOL_GLYPHS[tool]
2394
+ children: import_vector18.VECTOR_TOOL_GLYPHS[tool]
2344
2395
  },
2345
2396
  tool
2346
2397
  )) }),
2347
- showHistory && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2398
+ showHistory && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2348
2399
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
2349
2400
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
2350
2401
  ] }),
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)(
2402
+ showSnap && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2403
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2353
2404
  "button",
2354
2405
  {
2355
2406
  type: "button",
2356
2407
  "data-vector-snap": "",
2357
- className: buttonClass14,
2408
+ className: buttonClass15,
2358
2409
  "aria-pressed": state.snap.objects,
2359
2410
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
2360
2411
  "aria-label": VECTOR_SNAP_TITLE,
@@ -2362,18 +2413,18 @@ function VectorToolbar({
2362
2413
  children: "Snap"
2363
2414
  }
2364
2415
  ),
2365
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(
2416
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
2366
2417
  "select",
2367
2418
  {
2368
2419
  "data-vector-polar": "",
2369
2420
  "aria-label": "Polar tracking increment",
2370
2421
  title: "Polar tracking: the angles drawn points snap to from where they leave",
2371
2422
  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)) }),
2423
+ value: String((0, import_vector18.vectorPolarIncrementOf)(state.snap.angles) ?? ""),
2424
+ onChange: (e) => controller.setSnap({ angles: (0, import_vector18.vectorPolarAngles)(Number(e.target.value)) }),
2374
2425
  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: [
2426
+ (0, import_vector18.vectorPolarIncrementOf)(state.snap.angles) === null && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "", children: "Custom" }),
2427
+ [90, 45, 30, 15].map((n) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("option", { value: n, children: [
2377
2428
  n,
2378
2429
  "\xB0"
2379
2430
  ] }, n))
@@ -2381,13 +2432,13 @@ function VectorToolbar({
2381
2432
  }
2382
2433
  )
2383
2434
  ] }),
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)(
2435
+ showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2436
+ import_vector18.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2386
2437
  "button",
2387
2438
  {
2388
2439
  type: "button",
2389
2440
  "data-vector-path-action": a.id,
2390
- className: buttonClass14,
2441
+ className: buttonClass15,
2391
2442
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
2392
2443
  "aria-label": a.title,
2393
2444
  disabled: !a.enabled(count, points),
@@ -2396,12 +2447,12 @@ function VectorToolbar({
2396
2447
  },
2397
2448
  a.id
2398
2449
  )),
2399
- import_vector17.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2450
+ import_vector18.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2400
2451
  "button",
2401
2452
  {
2402
2453
  type: "button",
2403
2454
  "data-vector-edit": t.key,
2404
- className: buttonClass14,
2455
+ className: buttonClass15,
2405
2456
  "aria-pressed": editing[t.key],
2406
2457
  title: t.title,
2407
2458
  "aria-label": t.title,
@@ -2410,9 +2461,9 @@ function VectorToolbar({
2410
2461
  },
2411
2462
  t.key
2412
2463
  )),
2413
- state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2464
+ state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2414
2465
  "Smoothing",
2415
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2466
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2416
2467
  "input",
2417
2468
  {
2418
2469
  type: "range",
@@ -2426,9 +2477,9 @@ function VectorToolbar({
2426
2477
  }
2427
2478
  )
2428
2479
  ] }),
2429
- state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2480
+ state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2430
2481
  "Size",
2431
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2482
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2432
2483
  "input",
2433
2484
  {
2434
2485
  type: "number",
@@ -2444,26 +2495,26 @@ function VectorToolbar({
2444
2495
  }
2445
2496
  )
2446
2497
  ] }),
2447
- state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2498
+ state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2448
2499
  "button",
2449
2500
  {
2450
2501
  type: "button",
2451
2502
  "data-vector-fill-gap": "",
2452
- className: buttonClass14,
2503
+ className: buttonClass15,
2453
2504
  "aria-pressed": editing.fillGap > 0,
2454
2505
  title: "Close small gaps between path ends when filling",
2455
2506
  "aria-label": "Close gaps",
2456
- onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector17.VECTOR_FILL_GAP }),
2507
+ onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector18.VECTOR_FILL_GAP }),
2457
2508
  children: "Gaps"
2458
2509
  }
2459
2510
  )
2460
2511
  ] }),
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)(
2512
+ showDistort && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2462
2513
  "button",
2463
2514
  {
2464
2515
  type: "button",
2465
2516
  "data-vector-free-distort": "",
2466
- className: buttonClass14,
2517
+ className: buttonClass15,
2467
2518
  "aria-pressed": state.freeDistort,
2468
2519
  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
2520
  "aria-label": "Free Distort",
@@ -2471,15 +2522,15 @@ function VectorToolbar({
2471
2522
  children: "Distort"
2472
2523
  }
2473
2524
  ) }),
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)(
2525
+ showBlend && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorBlendControls, { controller }),
2526
+ showArrange && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
2527
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2528
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2478
2529
  "button",
2479
2530
  {
2480
2531
  type: "button",
2481
2532
  "data-vector-live-boolean": "",
2482
- className: buttonClass14,
2533
+ className: buttonClass15,
2483
2534
  "aria-pressed": live,
2484
2535
  title: "Live booleans: keep the shapes editable",
2485
2536
  "aria-label": "Live booleans",
@@ -2496,12 +2547,12 @@ function VectorToolbar({
2496
2547
  act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
2497
2548
  act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
2498
2549
  ] }),
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)(
2550
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: import_vector18.VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2500
2551
  "button",
2501
2552
  {
2502
2553
  type: "button",
2503
2554
  "data-vector-pathfinder": a.id,
2504
- className: buttonClass14,
2555
+ className: buttonClass15,
2505
2556
  title: a.title,
2506
2557
  "aria-label": a.title,
2507
2558
  disabled: count < a.min,
@@ -2510,7 +2561,7 @@ function VectorToolbar({
2510
2561
  },
2511
2562
  a.id
2512
2563
  )) }),
2513
- /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2564
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2514
2565
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
2515
2566
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
2516
2567
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -2525,22 +2576,22 @@ function VectorToolbar({
2525
2576
  }
2526
2577
  function VectorBlendControls({ controller }) {
2527
2578
  const state = useVectorEditorState(controller);
2528
- const blend = (0, import_vector17.getVectorBlendControls)(state);
2579
+ const blend = (0, import_vector18.getVectorBlendControls)(state);
2529
2580
  const o = blend.options;
2530
2581
  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)(
2582
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2583
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass15, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
2584
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2534
2585
  "select",
2535
2586
  {
2536
2587
  "aria-label": "Blend spacing",
2537
2588
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
2538
2589
  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))
2590
+ onChange: (e) => (0, import_vector18.applyVectorBlendOptions)(controller, { spacing: e.target.value }),
2591
+ children: import_vector18.VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: sp, children: import_vector18.VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2541
2592
  }
2542
2593
  ),
2543
- o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2594
+ o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2544
2595
  "input",
2545
2596
  {
2546
2597
  type: "number",
@@ -2552,29 +2603,29 @@ function VectorBlendControls({ controller }) {
2552
2603
  onChange: (e) => {
2553
2604
  const n = parseFloat(e.target.value);
2554
2605
  if (!Number.isFinite(n) || n <= 0) return;
2555
- (0, import_vector17.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
2606
+ (0, import_vector18.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
2556
2607
  }
2557
2608
  }
2558
2609
  ),
2559
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2610
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2560
2611
  "button",
2561
2612
  {
2562
2613
  type: "button",
2563
2614
  "data-vector-blend-action": "orient",
2564
- className: buttonClass14,
2615
+ className: buttonClass15,
2565
2616
  "aria-pressed": o.orientToSpine,
2566
2617
  title: "Orient steps to the spine",
2567
2618
  "aria-label": "Orient to spine",
2568
- onClick: () => (0, import_vector17.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
2619
+ onClick: () => (0, import_vector18.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
2569
2620
  children: "\u293F"
2570
2621
  }
2571
2622
  ),
2572
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2623
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2573
2624
  "button",
2574
2625
  {
2575
2626
  type: "button",
2576
2627
  "data-vector-blend-action": "spine",
2577
- className: buttonClass14,
2628
+ className: buttonClass15,
2578
2629
  title: "Replace Spine (select a blend and a path)",
2579
2630
  "aria-label": "Replace Spine",
2580
2631
  disabled: !blend.canReplaceSpine,
@@ -2582,15 +2633,15 @@ function VectorBlendControls({ controller }) {
2582
2633
  children: "\u2307"
2583
2634
  }
2584
2635
  ),
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" })
2636
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass15, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2637
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass15, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2587
2638
  ] });
2588
2639
  }
2589
- var selectClass12 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2640
+ var selectClass13 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2590
2641
  var numberClass11 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2591
2642
  function VectorStylePanel({ controller, className }) {
2592
2643
  const state = useVectorEditorState(controller);
2593
- const style = (0, import_vector17.getVectorSelectionStyle)(state);
2644
+ const style = (0, import_vector18.getVectorSelectionStyle)(state);
2594
2645
  const d = state.defaults;
2595
2646
  const source = style ?? {
2596
2647
  fill: d.fill ?? null,
@@ -2603,7 +2654,7 @@ function VectorStylePanel({ controller, className }) {
2603
2654
  arrowAlign: d.arrowAlign ?? "extend",
2604
2655
  startArrowScale: d.startArrowScale ?? 1,
2605
2656
  endArrowScale: d.endArrowScale ?? 1,
2606
- widthProfile: (0, import_vector17.widthProfilePresetId)(d.strokeWidthProfile),
2657
+ widthProfile: (0, import_vector18.widthProfilePresetId)(d.strokeWidthProfile),
2607
2658
  cornerRadius: d.cornerRadius,
2608
2659
  cornerKind: d.cornerKind ?? "round",
2609
2660
  cornerSmoothing: 0,
@@ -2617,19 +2668,19 @@ function VectorStylePanel({ controller, className }) {
2617
2668
  };
2618
2669
  const setProfile = (id) => {
2619
2670
  if (style) controller.setWidthProfile(id);
2620
- else controller.setDefaults({ strokeWidthProfile: (0, import_vector17.widthProfilePreset)(id)?.length ? (0, import_vector17.widthProfilePreset)(id) : null });
2671
+ else controller.setDefaults({ strokeWidthProfile: (0, import_vector18.widthProfilePreset)(id)?.length ? (0, import_vector18.widthProfilePreset)(id) : null });
2621
2672
  };
2622
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2673
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2623
2674
  const arrowRow = (end, label) => {
2624
2675
  const key = end === "start" ? "startArrow" : "endArrow";
2625
2676
  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))
2677
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2678
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
2679
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("select", { "aria-label": `${label} arrowhead`, className: selectClass13, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2680
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "", children: "None" }),
2681
+ import_vector18.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: h, children: import_vector18.VECTOR_ARROWHEAD_LABELS[h] }, h))
2631
2682
  ] }),
2632
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2683
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2633
2684
  "input",
2634
2685
  {
2635
2686
  type: "number",
@@ -2653,14 +2704,14 @@ function VectorStylePanel({ controller, className }) {
2653
2704
  };
2654
2705
  const paintRow = (key, label) => {
2655
2706
  const paint = source[key];
2656
- const hex = (0, import_vector17.vectorPaintToHex)(paint);
2707
+ const hex = (0, import_vector18.vectorPaintToHex)(paint);
2657
2708
  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 }) }),
2709
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2710
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2711
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2661
2712
  label
2662
2713
  ] }),
2663
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2714
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2664
2715
  "input",
2665
2716
  {
2666
2717
  type: "color",
@@ -2671,18 +2722,18 @@ function VectorStylePanel({ controller, className }) {
2671
2722
  onChange: (e) => apply({ [key]: e.target.value })
2672
2723
  }
2673
2724
  ),
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" })
2725
+ on && !hex && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2726
+ style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2676
2727
  ] });
2677
2728
  };
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" }),
2729
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2730
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2680
2731
  paintRow("fill", "Fill"),
2681
2732
  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)(
2733
+ style && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorGradientEditor, { controller }),
2734
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2735
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16", children: "Width" }),
2736
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2686
2737
  "input",
2687
2738
  {
2688
2739
  type: "number",
@@ -2698,33 +2749,33 @@ function VectorStylePanel({ controller, className }) {
2698
2749
  }
2699
2750
  )
2700
2751
  ] }),
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)) }),
2752
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2753
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16", children: "Align" }),
2754
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("select", { "aria-label": "Stroke alignment", className: selectClass13, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(import_vector18.VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: a, children: import_vector18.VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
2704
2755
  mixed("strokeAlign")
2705
2756
  ] }),
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)(
2757
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2758
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16", children: "Profile" }),
2759
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
2709
2760
  "select",
2710
2761
  {
2711
2762
  "aria-label": "Width profile",
2712
- className: selectClass12,
2763
+ className: selectClass13,
2713
2764
  value: source.widthProfile,
2714
2765
  onChange: (e) => {
2715
2766
  if (e.target.value !== "custom") setProfile(e.target.value);
2716
2767
  },
2717
2768
  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" })
2769
+ import_vector18.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: p.id, children: p.label }, p.id)),
2770
+ source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
2720
2771
  ]
2721
2772
  }
2722
2773
  ),
2723
- /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
2774
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2724
2775
  "button",
2725
2776
  {
2726
2777
  type: "button",
2727
- className: buttonClass14,
2778
+ className: buttonClass15,
2728
2779
  title: "Reset to uniform width",
2729
2780
  "aria-label": "Reset to uniform width",
2730
2781
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -2734,20 +2785,20 @@ function VectorStylePanel({ controller, className }) {
2734
2785
  ),
2735
2786
  mixed("widthProfile")
2736
2787
  ] }),
2737
- source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)(import_jsx_runtime17.Fragment, { children: [
2788
+ source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(import_jsx_runtime18.Fragment, { children: [
2738
2789
  arrowRow("start", "Start"),
2739
2790
  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" })
2791
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2792
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16", children: "Heads" }),
2793
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass13, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2794
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "extend", children: "Tip past end" }),
2795
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: "tip", children: "Tip at end" })
2745
2796
  ] })
2746
2797
  ] })
2747
2798
  ] }),
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)(
2799
+ source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2800
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2801
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2751
2802
  "input",
2752
2803
  {
2753
2804
  type: "number",
@@ -2762,12 +2813,12 @@ function VectorStylePanel({ controller, className }) {
2762
2813
  }
2763
2814
  }
2764
2815
  ),
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)) }),
2816
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("select", { "aria-label": "Corner kind", className: selectClass13, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: import_vector18.VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("option", { value: k, children: import_vector18.VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2766
2817
  mixed("cornerRadius")
2767
2818
  ] }),
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)(
2819
+ style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.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: [
2820
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16", children: "Smoothing" }),
2821
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2771
2822
  "input",
2772
2823
  {
2773
2824
  type: "range",
@@ -2782,9 +2833,9 @@ function VectorStylePanel({ controller, className }) {
2782
2833
  }
2783
2834
  )
2784
2835
  ] }),
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)(
2836
+ style && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2837
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "w-16", children: "Opacity" }),
2838
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2788
2839
  "input",
2789
2840
  {
2790
2841
  type: "range",
@@ -2798,34 +2849,35 @@ function VectorStylePanel({ controller, className }) {
2798
2849
  }
2799
2850
  )
2800
2851
  ] }),
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 })
2852
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorTextPanel, { controller }),
2853
+ style && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorAppearancePanel, { controller }),
2854
+ style && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorEffectsPanel, { controller }),
2855
+ style && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorEnvelopePanel, { controller }),
2856
+ style && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorMaskPanel, { controller }),
2857
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorFramePanel, { controller }),
2858
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorConnectorPanel, { controller }),
2859
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorShadingPanel, { controller }),
2860
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorSymmetryPanel, { controller }),
2861
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorPerspectivePanel, { controller }),
2862
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorLensPanel, { controller }),
2863
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorTracePanel, { controller }),
2864
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorConstraintsPanel, { controller }),
2865
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(VectorSymbolsPanel, { controller })
2814
2866
  ] });
2815
2867
  }
2816
2868
 
2817
2869
  // src/effects/useEffects.ts
2818
- var import_react11 = require("react");
2870
+ var import_react12 = require("react");
2819
2871
  function useEffects(host, provider) {
2820
- (0, import_react11.useEffect)(() => {
2872
+ (0, import_react12.useEffect)(() => {
2821
2873
  if (!host || !provider) return;
2822
2874
  return host.useEffects(provider);
2823
2875
  }, [host, provider]);
2824
2876
  }
2825
2877
  function useEffectDescription(provider, effect) {
2826
- const [state, setState] = (0, import_react11.useState)({ key: "", description: null, error: null });
2878
+ const [state, setState] = (0, import_react12.useState)({ key: "", description: null, error: null });
2827
2879
  const key = provider && effect ? `${provider.id}:${effect}` : "";
2828
- (0, import_react11.useEffect)(() => {
2880
+ (0, import_react12.useEffect)(() => {
2829
2881
  if (!provider || !effect) return;
2830
2882
  let live = true;
2831
2883
  Promise.resolve().then(() => provider.describe(effect)).then(
@@ -2840,13 +2892,13 @@ function useEffectDescription(provider, effect) {
2840
2892
  }
2841
2893
 
2842
2894
  // src/effects/EffectThumbnail.tsx
2843
- var import_react12 = require("react");
2895
+ var import_react13 = require("react");
2844
2896
  var import_effects = require("@pixerate/editor/effects");
2845
- var import_jsx_runtime18 = require("react/jsx-runtime");
2897
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2846
2898
  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)(() => {
2899
+ const ref = (0, import_react13.useRef)(null);
2900
+ const [failed, setFailed] = (0, import_react13.useState)(effect.kind === "motion");
2901
+ (0, import_react13.useEffect)(() => {
2850
2902
  if (effect.kind === "motion") return;
2851
2903
  let live = true;
2852
2904
  (0, import_effects.renderEffectThumbnail)(provider, effect, { width, height }).then(
@@ -2861,28 +2913,28 @@ function EffectThumbnail({ provider, effect, width = 96, height = 54, className
2861
2913
  live = false;
2862
2914
  };
2863
2915
  }, [provider, effect, width, height]);
2864
- return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2916
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2865
2917
  "div",
2866
2918
  {
2867
2919
  className: `relative overflow-hidden rounded border border-border bg-[repeating-conic-gradient(#8884_0_25%,transparent_0_50%)] bg-[length:12px_12px] ${className}`,
2868
2920
  style: { aspectRatio: `${width} / ${height}` },
2869
2921
  "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" })
2922
+ children: failed ? /* @__PURE__ */ (0, import_jsx_runtime19.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_runtime19.jsx)("canvas", { ref, width, height, className: "h-full w-full", "aria-hidden": "true" })
2871
2923
  }
2872
2924
  );
2873
2925
  }
2874
2926
 
2875
2927
  // src/effects/EffectsBrowser.tsx
2876
- var import_react13 = require("react");
2928
+ var import_react14 = require("react");
2877
2929
  var import_effects2 = require("@pixerate/editor/effects");
2878
- var import_jsx_runtime19 = require("react/jsx-runtime");
2930
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2879
2931
  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)(() => {
2932
+ const [all, setAll] = (0, import_react14.useState)([]);
2933
+ const [query, setQuery] = (0, import_react14.useState)("");
2934
+ const [category, setCategory] = (0, import_react14.useState)("");
2935
+ const [use, setUse] = (0, import_react14.useState)("");
2936
+ const [error, setError] = (0, import_react14.useState)(null);
2937
+ (0, import_react14.useEffect)(() => {
2886
2938
  if (!provider) return setAll([]);
2887
2939
  let live = true;
2888
2940
  Promise.resolve().then(() => provider.list()).then(
@@ -2893,8 +2945,8 @@ function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, titl
2893
2945
  live = false;
2894
2946
  };
2895
2947
  }, [provider]);
2896
- const [found, setFound] = (0, import_react13.useState)([]);
2897
- (0, import_react13.useEffect)(() => {
2948
+ const [found, setFound] = (0, import_react14.useState)([]);
2949
+ (0, import_react14.useEffect)(() => {
2898
2950
  if (!provider) return setFound([]);
2899
2951
  let live = true;
2900
2952
  Promise.resolve().then(() => provider.search(query, { ...category ? { category } : {}, ...use ? { use } : {} })).then((list) => live && setFound(list), () => live && setFound([]));
@@ -2903,14 +2955,14 @@ function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, titl
2903
2955
  };
2904
2956
  }, [provider, query, category, use]);
2905
2957
  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]);
2958
+ const categories = (0, import_react14.useMemo)(() => [...new Set(all.filter(allowed).map((e) => e.category))], [all, uses, kinds]);
2907
2959
  const shown = found.filter(allowed);
2908
2960
  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)(
2961
+ return /* @__PURE__ */ (0, import_jsx_runtime20.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." });
2962
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("section", { className: `flex flex-col gap-2 ${className}`, "data-effects-browser": "", "aria-label": "Effects browser", children: [
2963
+ title && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("h3", { className: "text-xs font-semibold", children: title }),
2964
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex flex-wrap gap-1", children: [
2965
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2914
2966
  "input",
2915
2967
  {
2916
2968
  type: "search",
@@ -2921,17 +2973,17 @@ function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, titl
2921
2973
  onChange: (e) => setQuery(e.currentTarget.value)
2922
2974
  }
2923
2975
  ),
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))
2976
+ /* @__PURE__ */ (0, import_jsx_runtime20.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: [
2977
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: "", children: "All categories" }),
2978
+ categories.map((c) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: c, children: c }, c))
2927
2979
  ] }),
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))
2980
+ uses && uses.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime20.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: [
2981
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: "", children: "Any use" }),
2982
+ uses.map((u) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("option", { value: u, children: import_effects2.EFFECT_USE_LABELS[u] }, u))
2931
2983
  ] })
2932
2984
  ] }),
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)(
2985
+ error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: error }),
2986
+ /* @__PURE__ */ (0, import_jsx_runtime20.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_runtime20.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
2935
2987
  "button",
2936
2988
  {
2937
2989
  type: "button",
@@ -2940,25 +2992,25 @@ function EffectsBrowser({ provider, uses, kinds, onPick, thumbnails = true, titl
2940
2992
  "data-effect-id": e.id,
2941
2993
  onClick: () => onPick(e),
2942
2994
  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 ") })
2995
+ thumbnails && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(EffectThumbnail, { provider, effect: e }),
2996
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "truncate text-[11px] font-medium", children: e.name }),
2997
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "truncate text-[10px] text-muted-foreground", children: e.uses.map((u) => import_effects2.EFFECT_USE_LABELS[u]).join(" \xB7 ") })
2946
2998
  ]
2947
2999
  }
2948
3000
  ) }, e.id)) }),
2949
- !shown.length && !error && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects match." })
3001
+ !shown.length && !error && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects match." })
2950
3002
  ] });
2951
3003
  }
2952
3004
 
2953
3005
  // src/effects/EffectParameters.tsx
2954
- var import_react15 = __toESM(require("react"), 1);
3006
+ var import_react16 = __toESM(require("react"), 1);
2955
3007
  var import_effects3 = require("@pixerate/editor/effects");
2956
3008
  var import_parameters2 = require("@pixerate/editor/parameters");
2957
3009
 
2958
3010
  // src/parameters/fields.tsx
2959
- var import_react14 = require("react");
3011
+ var import_react15 = require("react");
2960
3012
  var import_parameters = require("@pixerate/editor/parameters");
2961
- var import_jsx_runtime20 = require("react/jsx-runtime");
3013
+ var import_jsx_runtime21 = require("react/jsx-runtime");
2962
3014
  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
3015
  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
3016
  var rangeClass = "h-4 w-full min-w-0 flex-1 cursor-pointer accent-primary disabled:cursor-not-allowed disabled:opacity-50";
@@ -2982,7 +3034,7 @@ function ParameterCommitInput({ value, onCommit, multiline, className, onKeyDown
2982
3034
  onKeyDown: keyDown,
2983
3035
  ...rest
2984
3036
  };
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);
3037
+ return multiline ? /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("textarea", { ...props, className: `${props.className} h-auto min-h-[3.5rem] py-1` }, value) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("input", { type: "text", ...props }, value);
2986
3038
  }
2987
3039
  var plainNumber = (field, value) => (0, import_parameters.formatParameterNumber)({ ...field, unit: void 0 }, value);
2988
3040
  function ParameterNumberInput({
@@ -2993,8 +3045,8 @@ function ParameterNumberInput({
2993
3045
  id,
2994
3046
  "aria-label": ariaLabel
2995
3047
  }) {
2996
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
2997
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3048
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "relative min-w-0 flex-1", children: [
3049
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
2998
3050
  ParameterCommitInput,
2999
3051
  {
3000
3052
  id,
@@ -3016,21 +3068,21 @@ function ParameterNumberInput({
3016
3068
  }
3017
3069
  }
3018
3070
  ),
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 })
3071
+ field.unit && /* @__PURE__ */ (0, import_jsx_runtime21.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
3072
  ] });
3021
3073
  }
3022
3074
  function ParameterSlider(props) {
3023
3075
  const { field, value, onChange, onGestureStart, onGestureEnd, disabled } = props;
3024
- const dragging = (0, import_react14.useRef)(false);
3076
+ const dragging = (0, import_react15.useRef)(false);
3025
3077
  const end = () => {
3026
3078
  if (!dragging.current) return;
3027
3079
  dragging.current = false;
3028
3080
  onGestureEnd?.();
3029
3081
  };
3030
- if (!(0, import_parameters.isSliderField)(field)) return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterNumberInput, { ...props });
3082
+ if (!(0, import_parameters.isSliderField)(field)) return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ParameterNumberInput, { ...props });
3031
3083
  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)(
3084
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-2", children: [
3085
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3034
3086
  "input",
3035
3087
  {
3036
3088
  type: "range",
@@ -3053,11 +3105,11 @@ function ParameterSlider(props) {
3053
3105
  onChange: (e) => onChange((0, import_parameters.clampParameterNumber)(field, Number(e.target.value)))
3054
3106
  }
3055
3107
  ),
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` }) })
3108
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "w-20 shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ParameterNumberInput, { ...props, id: props.id, "aria-label": `${(0, import_parameters.parameterLabel)(field)} value` }) })
3057
3109
  ] });
3058
3110
  }
3059
3111
  function ParameterToggle({ field, value, onChange, disabled, id }) {
3060
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3112
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3061
3113
  "button",
3062
3114
  {
3063
3115
  type: "button",
@@ -3068,7 +3120,7 @@ function ParameterToggle({ field, value, onChange, disabled, id }) {
3068
3120
  disabled: disabled || field.readOnly,
3069
3121
  onClick: () => onChange(!value),
3070
3122
  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)(
3123
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3072
3124
  "span",
3073
3125
  {
3074
3126
  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 +3133,7 @@ function ParameterSelect({ field, value, onChange, disabled, id }) {
3081
3133
  const options = (0, import_parameters.getChoiceOptions)(field);
3082
3134
  const off = disabled || field.readOnly;
3083
3135
  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)(
3136
+ return /* @__PURE__ */ (0, import_jsx_runtime21.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_runtime21.jsx)(
3085
3137
  "button",
3086
3138
  {
3087
3139
  type: "button",
@@ -3097,7 +3149,7 @@ function ParameterSelect({ field, value, onChange, disabled, id }) {
3097
3149
  )) });
3098
3150
  }
3099
3151
  const index = options.findIndex((o) => o.value === value);
3100
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3152
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3101
3153
  "select",
3102
3154
  {
3103
3155
  id,
@@ -3109,12 +3161,12 @@ function ParameterSelect({ field, value, onChange, disabled, id }) {
3109
3161
  if (option) onChange(option.value);
3110
3162
  },
3111
3163
  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)))
3164
+ children: options.map((o, i) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("option", { value: String(i), title: o.description, children: (0, import_parameters.choiceOptionLabel)(o) }, String(o.value)))
3113
3165
  }
3114
3166
  );
3115
3167
  }
3116
3168
  function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd, disabled, id }) {
3117
- const picking = (0, import_react14.useRef)(false);
3169
+ const picking = (0, import_react15.useRef)(false);
3118
3170
  const alpha = !!field.alpha;
3119
3171
  const hex = (0, import_parameters.normalizeHexColor)(value, true) ?? "#000000ff";
3120
3172
  const rgb = hex.slice(0, 7);
@@ -3130,11 +3182,11 @@ function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd,
3130
3182
  picking.current = false;
3131
3183
  onGestureEnd?.();
3132
3184
  };
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)(
3185
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "space-y-1.5", children: [
3186
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-2", children: [
3187
+ /* @__PURE__ */ (0, import_jsx_runtime21.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: [
3188
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "absolute inset-0", style: { background: alpha ? hex : rgb } }),
3189
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3138
3190
  "input",
3139
3191
  {
3140
3192
  type: "color",
@@ -3150,7 +3202,7 @@ function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd,
3150
3202
  }
3151
3203
  )
3152
3204
  ] }),
3153
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3205
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3154
3206
  ParameterCommitInput,
3155
3207
  {
3156
3208
  id,
@@ -3166,7 +3218,7 @@ function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd,
3166
3218
  }
3167
3219
  )
3168
3220
  ] }),
3169
- alpha && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3221
+ alpha && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3170
3222
  "input",
3171
3223
  {
3172
3224
  type: "range",
@@ -3184,10 +3236,10 @@ function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd,
3184
3236
  onChange: (e) => onChange(`${rgb}${Number(e.target.value).toString(16).padStart(2, "0")}`)
3185
3237
  }
3186
3238
  ),
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) => {
3239
+ field.swatches && field.swatches.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex flex-wrap gap-1", children: field.swatches.map((s) => {
3188
3240
  const swatch = (0, import_parameters.normalizeHexColor)(s, alpha);
3189
3241
  if (!swatch) return null;
3190
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3242
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3191
3243
  "button",
3192
3244
  {
3193
3245
  type: "button",
@@ -3206,11 +3258,11 @@ function ParameterColor({ field, value, onChange, onGestureStart, onGestureEnd,
3206
3258
  }
3207
3259
  function ParameterVector({ field, value, onChange, disabled, id }) {
3208
3260
  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) => {
3261
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "grid gap-1.5", style: { gridTemplateColumns: `repeat(${size}, minmax(0, 1fr))` }, children: Array.from({ length: size }, (_, i) => {
3210
3262
  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)(
3263
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { className: "flex min-w-0 items-center gap-1", children: [
3264
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "w-3 shrink-0 text-[10px] uppercase text-muted-foreground", children: component.label }),
3265
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3214
3266
  ParameterNumberInput,
3215
3267
  {
3216
3268
  field: component,
@@ -3227,11 +3279,11 @@ function ParameterVector({ field, value, onChange, disabled, id }) {
3227
3279
  function ParameterImage({ field, value, onChange, disabled, id, resolveFile }) {
3228
3280
  const off = disabled || field.readOnly;
3229
3281
  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)(
3282
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "space-y-1.5", children: [
3283
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex items-center gap-2", children: [
3284
+ /* @__PURE__ */ (0, import_jsx_runtime21.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_runtime21.jsx)("img", { src: value, alt: name, className: "h-full w-full object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "None" }) }),
3285
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
3286
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3235
3287
  ParameterCommitInput,
3236
3288
  {
3237
3289
  id,
@@ -3242,10 +3294,10 @@ function ParameterImage({ field, value, onChange, disabled, id, resolveFile }) {
3242
3294
  onCommit: (text) => onChange(text.trim())
3243
3295
  }
3244
3296
  ),
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: [
3297
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { className: "flex gap-1", children: [
3298
+ resolveFile && /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("label", { className: `${parameterButtonClass} cursor-pointer ${off ? "pointer-events-none opacity-50" : ""}`, children: [
3247
3299
  "Choose file\u2026",
3248
- /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3300
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3249
3301
  "input",
3250
3302
  {
3251
3303
  type: "file",
@@ -3261,11 +3313,11 @@ function ParameterImage({ field, value, onChange, disabled, id, resolveFile }) {
3261
3313
  }
3262
3314
  )
3263
3315
  ] }),
3264
- value && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { type: "button", className: parameterButtonClass, disabled: off, onClick: () => onChange(""), "aria-label": `Clear ${name}`, children: "Clear" })
3316
+ value && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("button", { type: "button", className: parameterButtonClass, disabled: off, onClick: () => onChange(""), "aria-label": `Clear ${name}`, children: "Clear" })
3265
3317
  ] })
3266
3318
  ] })
3267
3319
  ] }),
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)(
3320
+ field.options && field.options.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { className: "flex flex-wrap gap-1", children: field.options.map((o) => /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3269
3321
  "button",
3270
3322
  {
3271
3323
  type: "button",
@@ -3275,14 +3327,14 @@ function ParameterImage({ field, value, onChange, disabled, id, resolveFile }) {
3275
3327
  disabled: off,
3276
3328
  onClick: () => onChange(o.url),
3277
3329
  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" })
3330
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("img", { src: o.url, alt: "", className: "h-full w-full object-cover" })
3279
3331
  },
3280
3332
  o.url
3281
3333
  )) })
3282
3334
  ] });
3283
3335
  }
3284
3336
  function ParameterText({ field, value, onChange, disabled, id }) {
3285
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3337
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3286
3338
  ParameterCommitInput,
3287
3339
  {
3288
3340
  id,
@@ -3301,31 +3353,31 @@ function ParameterFieldControl({ field, value, onChange, resolveFile, ...rest })
3301
3353
  switch (field.type) {
3302
3354
  case "number":
3303
3355
  case "int":
3304
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterSlider, { field, value, onChange: set, ...rest });
3356
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ParameterSlider, { field, value, onChange: set, ...rest });
3305
3357
  case "boolean":
3306
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterToggle, { field, value, onChange: set, ...rest });
3358
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ParameterToggle, { field, value, onChange: set, ...rest });
3307
3359
  case "choice":
3308
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterSelect, { field, value, onChange: set, ...rest });
3360
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ParameterSelect, { field, value, onChange: set, ...rest });
3309
3361
  case "color":
3310
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterColor, { field, value, onChange: set, ...rest });
3362
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ParameterColor, { field, value, onChange: set, ...rest });
3311
3363
  case "vector":
3312
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterVector, { field, value, onChange: set, ...rest });
3364
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ParameterVector, { field, value, onChange: set, ...rest });
3313
3365
  case "image":
3314
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterImage, { field, value, onChange: set, resolveFile, ...rest });
3366
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ParameterImage, { field, value, onChange: set, resolveFile, ...rest });
3315
3367
  case "text":
3316
- return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(ParameterText, { field, value, onChange: set, ...rest });
3368
+ return /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(ParameterText, { field, value, onChange: set, ...rest });
3317
3369
  }
3318
3370
  }
3319
3371
 
3320
3372
  // src/effects/EffectParameters.tsx
3321
- var import_jsx_runtime21 = require("react/jsx-runtime");
3373
+ var import_jsx_runtime22 = require("react/jsx-runtime");
3322
3374
  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)(
3375
+ const fields = (0, import_react16.useMemo)(() => (0, import_effects3.effectParameterFields)(description), [description]);
3376
+ const idBase = (0, import_react16.useId)();
3377
+ const gesture = import_react16.default.useRef(null);
3378
+ const count = import_react16.default.useRef(0);
3379
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: `space-y-2 ${className}`, "data-effect-parameters": description.id, children: [
3380
+ onPreset && description.presets.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)(
3329
3381
  "select",
3330
3382
  {
3331
3383
  className: "w-full rounded border border-border bg-transparent px-1 py-0.5 text-xs",
@@ -3337,8 +3389,8 @@ function EffectParameters({ description, values, onChange, onReset, keyframed, o
3337
3389
  if (preset) onPreset({ ...description.defaults, ...preset.values });
3338
3390
  },
3339
3391
  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))
3392
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: "", children: "Presets\u2026" }),
3393
+ description.presets.map((p) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("option", { value: p.name, children: p.name }, p.name))
3342
3394
  ]
3343
3395
  }
3344
3396
  ),
@@ -3348,13 +3400,13 @@ function EffectParameters({ description, values, onChange, onReset, keyframed, o
3348
3400
  const keyed = keyframed?.has(field.id) ?? false;
3349
3401
  const value = (0, import_effects3.effectFieldValue)(field, values[field.id]);
3350
3402
  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" })
3403
+ return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "space-y-1", "data-effect-control": field.id, children: [
3404
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-1.5", children: [
3405
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("label", { htmlFor: inputId, className: "min-w-0 flex-1 truncate text-xs font-medium", title: field.description, children: label }),
3406
+ keyed && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "keyframed" }),
3407
+ onReset && changed && !disabled && /* @__PURE__ */ (0, import_jsx_runtime22.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
3408
  ] }),
3357
- /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
3409
+ /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
3358
3410
  ParameterFieldControl,
3359
3411
  {
3360
3412
  field,
@@ -3368,25 +3420,25 @@ function EffectParameters({ description, values, onChange, onReset, keyframed, o
3368
3420
  )
3369
3421
  ] }, field.id);
3370
3422
  }),
3371
- !fields.length && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No controls." })
3423
+ !fields.length && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No controls." })
3372
3424
  ] });
3373
3425
  }
3374
3426
 
3375
3427
  // src/vector/VectorProviderEffectsPanel.tsx
3376
- var import_react16 = require("react");
3377
- var import_jsx_runtime22 = require("react/jsx-runtime");
3428
+ var import_react17 = require("react");
3429
+ var import_jsx_runtime23 = require("react/jsx-runtime");
3378
3430
  var btn = "rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40";
3379
3431
  function Controls({ controller, group }) {
3380
3432
  const provider = controller.effectsProvider;
3381
3433
  const tag = group.providerEffect;
3382
3434
  const { description, error } = useEffectDescription(provider, tag.effect);
3383
- const [message, setMessage] = (0, import_react16.useState)(null);
3435
+ const [message, setMessage] = (0, import_react17.useState)(null);
3384
3436
  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)(
3437
+ if (!provider) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider: the shapes are kept as they are." });
3438
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-[11px] text-amber-500", children: error });
3439
+ if (!description) return /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "Loading\u2026" });
3440
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
3441
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
3390
3442
  EffectParameters,
3391
3443
  {
3392
3444
  description,
@@ -3396,18 +3448,18 @@ function Controls({ controller, group }) {
3396
3448
  onPreset: (v) => run(controller.updateVectorEffect(group.id, v))
3397
3449
  }
3398
3450
  ),
3399
- message && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
3451
+ message && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
3400
3452
  ] });
3401
3453
  }
3402
3454
  function VectorProviderEffectsPanel({ controller, className = "" }) {
3403
3455
  const state = useVectorEditorState(controller);
3404
- const provider = (0, import_react16.useSyncExternalStore)(
3456
+ const provider = (0, import_react17.useSyncExternalStore)(
3405
3457
  (cb) => controller.effects.subscribe(cb),
3406
3458
  () => controller.effects.provider,
3407
3459
  () => controller.effects.provider
3408
3460
  );
3409
- const [browse, setBrowse] = (0, import_react16.useState)(false);
3410
- const [message, setMessage] = (0, import_react16.useState)(null);
3461
+ const [browse, setBrowse] = (0, import_react17.useState)(false);
3462
+ const [message, setMessage] = (0, import_react17.useState)(null);
3411
3463
  void state;
3412
3464
  const groups = controller.vectorEffectGroups();
3413
3465
  const selected = groups.filter((g) => state.selection.includes(g.id));
@@ -3415,41 +3467,41 @@ function VectorProviderEffectsPanel({ controller, className = "" }) {
3415
3467
  setMessage(null);
3416
3468
  Promise.resolve().then(work).catch((e) => setMessage(e instanceof Error ? e.message : String(e)));
3417
3469
  };
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" })
3470
+ return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("section", { className: `flex flex-col gap-2 text-xs ${className}`, "aria-label": "Vector effects", "data-vector-provider-effects": "", children: [
3471
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center gap-1", children: [
3472
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "flex-1 font-semibold", children: "Effects" }),
3473
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: btn, disabled: !provider, "aria-pressed": browse, onClick: () => setBrowse(!browse), children: "Add effect\u2026" })
3422
3474
  ] }),
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" })
3475
+ !provider && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider is registered. Effect groups are kept as shapes." }),
3476
+ browse && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(EffectsBrowser, { provider, kinds: ["vector"], title: "Vector effects", onPick: (e) => (setBrowse(false), run(() => controller.addVectorEffect(e.id))) }),
3477
+ selected.map((g) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "space-y-1 rounded border border-border p-1", "data-vector-effect-group": g.id, children: [
3478
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex items-center gap-1", children: [
3479
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "min-w-0 flex-1 truncate font-medium", children: g.providerEffect.name ?? g.providerEffect.effect }),
3480
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { type: "button", className: btn, title: "Keep the shapes as a plain group", onClick: () => run(() => controller.detachVectorEffect(g.id)), children: "Detach" })
3429
3481
  ] }),
3430
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(Controls, { controller, group: g })
3482
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(Controls, { controller, group: g })
3431
3483
  ] }, g.id)),
3432
- groups.length > 0 && !selected.length && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("p", { className: "text-[11px] text-muted-foreground", children: [
3484
+ groups.length > 0 && !selected.length && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("p", { className: "text-[11px] text-muted-foreground", children: [
3433
3485
  "Select an effect group (",
3434
3486
  groups.length,
3435
3487
  " in the drawing) to change it."
3436
3488
  ] }),
3437
- message && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
3489
+ message && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
3438
3490
  ] });
3439
3491
  }
3440
3492
 
3441
3493
  // src/vector/VectorEditor.tsx
3442
- var import_jsx_runtime23 = require("react/jsx-runtime");
3494
+ var import_jsx_runtime24 = require("react/jsx-runtime");
3443
3495
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, effects, className }) {
3444
3496
  useEffects(controller, effects);
3445
3497
  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" })
3498
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
3499
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(VectorToolbar, { controller, tools }),
3500
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
3501
+ /* @__PURE__ */ (0, import_jsx_runtime24.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_runtime24.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
3502
+ showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "flex w-full shrink-0 flex-col gap-2 md:w-56", children: [
3503
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(VectorStylePanel, { controller, className: "rounded-md border border-border p-3" }),
3504
+ effectsShown && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(VectorProviderEffectsPanel, { controller, className: "rounded-md border border-border p-3" })
3453
3505
  ] })
3454
3506
  ] })
3455
3507
  ] });
@@ -3471,6 +3523,7 @@ function VectorEditor({ controller, tools, showStylePanel = true, height = 520,
3471
3523
  VectorLensPanel,
3472
3524
  VectorMaskPanel,
3473
3525
  VectorMaskedNodes,
3526
+ VectorPerspectivePanel,
3474
3527
  VectorProviderEffectsPanel,
3475
3528
  VectorSceneNodes,
3476
3529
  VectorShadingPanel,