@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.
@@ -649,6 +649,11 @@ function VectorCanvas({
649
649
  sh.lines.map((d, i) => /* @__PURE__ */ jsx2("path", { d, stroke: accentColor, strokeWidth: 1, opacity: 0.7 }, i)),
650
650
  sh.points.map((p) => /* @__PURE__ */ jsx2("circle", { "data-vector-shading-point": p.index, "data-selected": p.selected || void 0, cx: p.x, cy: p.y, r: p.selected ? 6 : 4.5, fill: p.color, stroke: p.selected ? accentColor : "#ffffff", strokeWidth: p.selected ? 2 : 1.5 }, p.index))
651
651
  ] }, `shading:${sh.nodeId}`)),
652
+ overlay.perspective && /* @__PURE__ */ jsxs2("g", { "data-vector-perspective-grid": "", fill: "none", pointerEvents: "none", children: [
653
+ overlay.perspective.planes.map((pl) => /* @__PURE__ */ jsx2("path", { "data-vector-perspective-plane": pl.plane, "data-active": pl.active || void 0, d: pl.d, stroke: pl.active ? accentColor : guideColor, strokeWidth: pl.active ? 1 : 0.75, opacity: pl.active ? 0.55 : 0.25 }, pl.plane)),
654
+ overlay.perspective.vanishingPoints.map((v, i) => /* @__PURE__ */ jsx2("circle", { "data-vector-vanishing-point": i, cx: v.x, cy: v.y, r: 4, stroke: accentColor, strokeWidth: 1.5 }, i)),
655
+ /* @__PURE__ */ jsx2("circle", { "data-vector-perspective-origin": "", cx: overlay.perspective.origin.x, cy: overlay.perspective.origin.y, r: 3, fill: accentColor })
656
+ ] }),
652
657
  overlay.symmetry.map((sy) => /* @__PURE__ */ jsx2("g", { "data-vector-symmetry-axes": sy.nodeId, stroke: accentColor, strokeDasharray: sy.drawing ? void 0 : "6 4", opacity: sy.drawing ? 0.9 : 0.6, children: sy.lines.map((l, i) => /* @__PURE__ */ jsx2("line", { x1: l.x1, y1: l.y1, x2: l.x2, y2: l.y2 }, i)) }, `sym:${sy.nodeId}`)),
653
658
  overlay.trackingPoints.map((t, i) => /* @__PURE__ */ jsx2("path", { "data-vector-tracking-point": "", d: `M${t.x - 5} ${t.y}H${t.x + 5}M${t.x} ${t.y - 5}V${t.y + 5}`, stroke: guideColor, strokeWidth: 1.5 }, `track:${i}`)),
654
659
  overlay.typedEntry && /* @__PURE__ */ jsxs2("g", { "data-vector-typed-entry": "", transform: `translate(${overlay.typedEntry.x + 14} ${overlay.typedEntry.y + 18})`, children: [
@@ -785,7 +790,7 @@ function VectorCanvas({
785
790
  }
786
791
 
787
792
  // src/vector/VectorToolbar.tsx
788
- import React9 from "react";
793
+ import React10 from "react";
789
794
  import {
790
795
  VECTOR_TOOLS,
791
796
  VECTOR_TOOL_GLYPHS,
@@ -2091,10 +2096,55 @@ function VectorSymmetryPanel({ controller, className }) {
2091
2096
  ] });
2092
2097
  }
2093
2098
 
2094
- // src/vector/VectorLensPanel.tsx
2095
- import { VECTOR_LENS_LABELS, VECTOR_LENS_SPECS, VECTOR_LENS_TYPES } from "@pixerate/editor/vector";
2099
+ // src/vector/VectorPerspectivePanel.tsx
2100
+ import React9 from "react";
2101
+ import { vectorPerspectivePlanes, vectorPerspectivePreset } from "@pixerate/editor/vector";
2096
2102
  import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2103
+ 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";
2097
2104
  var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2105
+ var PLANE_LABELS = { floor: "Floor", left: "Left wall", right: "Right wall", side: "Side wall", front: "Front" };
2106
+ function VectorPerspectivePanel({ controller, className }) {
2107
+ const state = useVectorEditorState(controller);
2108
+ const grid = state.document.perspective ?? null;
2109
+ const planes = grid ? vectorPerspectivePlanes(grid) : [];
2110
+ const [attachTo, setAttachTo] = React9.useState("");
2111
+ const target = (state.perspectivePlane ?? attachTo) || planes[0];
2112
+ const add = (n) => controller.setPerspective(vectorPerspectivePreset(n, state.document.width, state.document.height));
2113
+ return /* @__PURE__ */ jsxs15("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-perspective-panel": "", "aria-label": "Perspective", children: [
2114
+ /* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-1", children: [
2115
+ /* @__PURE__ */ jsx15("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Perspective" }),
2116
+ [1, 2, 3].map((n) => /* @__PURE__ */ jsxs15("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: [
2117
+ n,
2118
+ "-pt"
2119
+ ] }, n))
2120
+ ] }),
2121
+ grid && /* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-1", children: [
2122
+ /* @__PURE__ */ jsx15("span", { className: "w-16" }),
2123
+ /* @__PURE__ */ jsxs15("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: [
2124
+ /* @__PURE__ */ jsx15("option", { value: "", children: "Draw flat" }),
2125
+ planes.map((p) => /* @__PURE__ */ jsxs15("option", { value: p, children: [
2126
+ "Draw on ",
2127
+ PLANE_LABELS[p].toLowerCase()
2128
+ ] }, p))
2129
+ ] })
2130
+ ] }),
2131
+ grid && /* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-1", children: [
2132
+ /* @__PURE__ */ jsx15("span", { className: "w-16" }),
2133
+ !state.perspectivePlane && /* @__PURE__ */ jsx15("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__ */ jsx15("option", { value: p, children: PLANE_LABELS[p] }, p)) }),
2134
+ /* @__PURE__ */ jsx15("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" }),
2135
+ /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Show the grid on the canvas", children: [
2136
+ /* @__PURE__ */ jsx15("input", { type: "checkbox", "aria-label": "Show perspective grid", checked: grid.visible !== false, onChange: (e) => controller.setPerspective({ ...grid, visible: e.target.checked }) }),
2137
+ "Show"
2138
+ ] }),
2139
+ /* @__PURE__ */ jsx15("button", { type: "button", className: buttonClass13, title: "Remove the grid (art on it stays)", onClick: () => controller.setPerspective(null), children: "Remove" })
2140
+ ] })
2141
+ ] });
2142
+ }
2143
+
2144
+ // src/vector/VectorLensPanel.tsx
2145
+ import { VECTOR_LENS_LABELS, VECTOR_LENS_SPECS, VECTOR_LENS_TYPES } from "@pixerate/editor/vector";
2146
+ import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2147
+ var selectClass11 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2098
2148
  var numberClass9 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2099
2149
  function VectorLensPanel({ controller, className }) {
2100
2150
  const state = useVectorEditorState(controller);
@@ -2103,26 +2153,26 @@ function VectorLensPanel({ controller, className }) {
2103
2153
  const lens = path.lens ?? null;
2104
2154
  const spec = lens ? VECTOR_LENS_SPECS[lens.type] : null;
2105
2155
  const ids = [path.id];
2106
- return /* @__PURE__ */ jsxs15("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-lens-panel": "", "aria-label": "Lens", children: [
2107
- /* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-1", children: [
2108
- /* @__PURE__ */ jsx15("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Lens" }),
2109
- /* @__PURE__ */ jsxs15(
2156
+ return /* @__PURE__ */ jsxs16("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-lens-panel": "", "aria-label": "Lens", children: [
2157
+ /* @__PURE__ */ jsxs16("div", { className: "flex flex-wrap items-center gap-1", children: [
2158
+ /* @__PURE__ */ jsx16("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Lens" }),
2159
+ /* @__PURE__ */ jsxs16(
2110
2160
  "select",
2111
2161
  {
2112
2162
  "aria-label": "Lens type",
2113
- className: selectClass10,
2163
+ className: selectClass11,
2114
2164
  value: lens?.type ?? "",
2115
2165
  onChange: (e) => e.target.value ? controller.setLens({ type: e.target.value }, { ids }) : controller.setLens(null, { ids }),
2116
2166
  children: [
2117
- /* @__PURE__ */ jsx15("option", { value: "", children: "None" }),
2118
- VECTOR_LENS_TYPES.map((t) => /* @__PURE__ */ jsx15("option", { value: t, children: VECTOR_LENS_LABELS[t] }, t))
2167
+ /* @__PURE__ */ jsx16("option", { value: "", children: "None" }),
2168
+ VECTOR_LENS_TYPES.map((t) => /* @__PURE__ */ jsx16("option", { value: t, children: VECTOR_LENS_LABELS[t] }, t))
2119
2169
  ]
2120
2170
  }
2121
2171
  )
2122
2172
  ] }),
2123
- lens && spec && /* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-1", children: [
2124
- /* @__PURE__ */ jsx15("span", { className: "w-16" }),
2125
- /* @__PURE__ */ jsx15(
2173
+ lens && spec && /* @__PURE__ */ jsxs16("div", { className: "flex flex-wrap items-center gap-1", children: [
2174
+ /* @__PURE__ */ jsx16("span", { className: "w-16" }),
2175
+ /* @__PURE__ */ jsx16(
2126
2176
  "input",
2127
2177
  {
2128
2178
  type: "number",
@@ -2139,7 +2189,7 @@ function VectorLensPanel({ controller, className }) {
2139
2189
  }
2140
2190
  }
2141
2191
  ),
2142
- spec.color !== null && /* @__PURE__ */ jsx15("input", { type: "color", "aria-label": "Lens colour", title: "Lens colour", className: "h-7 w-9 rounded border border-border bg-background", value: lens.color ?? spec.color, onChange: (e) => controller.setLens({ color: e.target.value }, { ids, merge: `lens-color-${path.id}` }) })
2192
+ spec.color !== null && /* @__PURE__ */ jsx16("input", { type: "color", "aria-label": "Lens colour", title: "Lens colour", className: "h-7 w-9 rounded border border-border bg-background", value: lens.color ?? spec.color, onChange: (e) => controller.setLens({ color: e.target.value }, { ids, merge: `lens-color-${path.id}` }) })
2143
2193
  ] })
2144
2194
  ] });
2145
2195
  }
@@ -2157,10 +2207,10 @@ import {
2157
2207
  getVectorGradientEditor,
2158
2208
  vectorPaintToHex as vectorPaintToHex5
2159
2209
  } from "@pixerate/editor/vector";
2160
- import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2161
- var selectClass11 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2210
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
2211
+ var selectClass12 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2162
2212
  var numberClass10 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
2163
- var buttonClass13 = "inline-flex h-7 min-w-7 items-center justify-center rounded border border-border bg-background px-1.5 text-xs text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
2213
+ 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";
2164
2214
  var percent = (v) => Math.round(v * 1e3) / 10;
2165
2215
  function VectorGradientEditor({ controller, className }) {
2166
2216
  const state = useVectorEditorState(controller);
@@ -2175,23 +2225,23 @@ function VectorGradientEditor({ controller, className }) {
2175
2225
  controller.setStyle({ [ed.target]: vectorPaintToHex5(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2176
2226
  } else controller.setGradient({ type: value });
2177
2227
  };
2178
- return /* @__PURE__ */ jsxs16("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2179
- /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-1.5", children: [
2180
- /* @__PURE__ */ jsx16("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2181
- ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx16("button", { type: "button", className: buttonClass13, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
2182
- ed.mixed && /* @__PURE__ */ jsx16("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2228
+ return /* @__PURE__ */ jsxs17("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2229
+ /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-1.5", children: [
2230
+ /* @__PURE__ */ jsx17("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2231
+ ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx17("button", { type: "button", className: buttonClass14, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
2232
+ ed.mixed && /* @__PURE__ */ jsx17("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2183
2233
  ] }),
2184
- /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-1.5", children: [
2185
- /* @__PURE__ */ jsx16("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2186
- /* @__PURE__ */ jsxs16("select", { "aria-label": "Gradient type", className: selectClass11, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2187
- /* @__PURE__ */ jsx16("option", { value: "solid", children: "Solid" }),
2188
- VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx16("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
2234
+ /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-1.5", children: [
2235
+ /* @__PURE__ */ jsx17("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2236
+ /* @__PURE__ */ jsxs17("select", { "aria-label": "Gradient type", className: selectClass12, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2237
+ /* @__PURE__ */ jsx17("option", { value: "solid", children: "Solid" }),
2238
+ VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx17("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
2189
2239
  ] }),
2190
- /* @__PURE__ */ jsx16("button", { type: "button", className: buttonClass13, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2240
+ /* @__PURE__ */ jsx17("button", { type: "button", className: buttonClass14, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2191
2241
  ] }),
2192
- g && /* @__PURE__ */ jsxs16(Fragment5, { children: [
2193
- /* @__PURE__ */ jsxs16("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2194
- /* @__PURE__ */ jsx16(
2242
+ g && /* @__PURE__ */ jsxs17(Fragment5, { children: [
2243
+ /* @__PURE__ */ jsxs17("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2244
+ /* @__PURE__ */ jsx17(
2195
2245
  "div",
2196
2246
  {
2197
2247
  role: "button",
@@ -2205,7 +2255,7 @@ function VectorGradientEditor({ controller, className }) {
2205
2255
  }
2206
2256
  }
2207
2257
  ),
2208
- ed.stops.map((s) => /* @__PURE__ */ jsx16(
2258
+ ed.stops.map((s) => /* @__PURE__ */ jsx17(
2209
2259
  "button",
2210
2260
  {
2211
2261
  type: "button",
@@ -2219,8 +2269,8 @@ function VectorGradientEditor({ controller, className }) {
2219
2269
  s.index
2220
2270
  ))
2221
2271
  ] }),
2222
- sel && /* @__PURE__ */ jsxs16("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2223
- /* @__PURE__ */ jsx16(
2272
+ sel && /* @__PURE__ */ jsxs17("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2273
+ /* @__PURE__ */ jsx17(
2224
2274
  "input",
2225
2275
  {
2226
2276
  type: "color",
@@ -2231,9 +2281,9 @@ function VectorGradientEditor({ controller, className }) {
2231
2281
  onBlur: end
2232
2282
  }
2233
2283
  ),
2234
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2284
+ /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2235
2285
  "Opacity",
2236
- /* @__PURE__ */ jsx16(
2286
+ /* @__PURE__ */ jsx17(
2237
2287
  "input",
2238
2288
  {
2239
2289
  type: "number",
@@ -2250,9 +2300,9 @@ function VectorGradientEditor({ controller, className }) {
2250
2300
  }
2251
2301
  )
2252
2302
  ] }),
2253
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2303
+ /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2254
2304
  "At",
2255
- /* @__PURE__ */ jsx16(
2305
+ /* @__PURE__ */ jsx17(
2256
2306
  "input",
2257
2307
  {
2258
2308
  type: "number",
@@ -2269,9 +2319,9 @@ function VectorGradientEditor({ controller, className }) {
2269
2319
  }
2270
2320
  )
2271
2321
  ] }),
2272
- !sel.last && /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2322
+ !sel.last && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2273
2323
  "Midpoint",
2274
- /* @__PURE__ */ jsx16(
2324
+ /* @__PURE__ */ jsx17(
2275
2325
  "input",
2276
2326
  {
2277
2327
  type: "number",
@@ -2288,15 +2338,15 @@ function VectorGradientEditor({ controller, className }) {
2288
2338
  }
2289
2339
  )
2290
2340
  ] }),
2291
- /* @__PURE__ */ jsx16("button", { type: "button", className: buttonClass13, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2341
+ /* @__PURE__ */ jsx17("button", { type: "button", className: buttonClass14, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2292
2342
  ] }),
2293
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2294
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Colours" }),
2295
- /* @__PURE__ */ jsx16("select", { "aria-label": "Gradient interpolation", className: selectClass11, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx16("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2343
+ /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2344
+ /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Colours" }),
2345
+ /* @__PURE__ */ jsx17("select", { "aria-label": "Gradient interpolation", className: selectClass12, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx17("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2296
2346
  ] }),
2297
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2298
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Smooth" }),
2299
- /* @__PURE__ */ jsx16(
2347
+ /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2348
+ /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Smooth" }),
2349
+ /* @__PURE__ */ jsx17(
2300
2350
  "input",
2301
2351
  {
2302
2352
  type: "range",
@@ -2312,23 +2362,23 @@ function VectorGradientEditor({ controller, className }) {
2312
2362
  }
2313
2363
  )
2314
2364
  ] }),
2315
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2316
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Spread" }),
2317
- /* @__PURE__ */ jsx16("select", { "aria-label": "Gradient spread", className: selectClass11, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx16("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2365
+ /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2366
+ /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Spread" }),
2367
+ /* @__PURE__ */ jsx17("select", { "aria-label": "Gradient spread", className: selectClass12, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx17("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2318
2368
  ] }),
2319
- ed.target === "stroke" && /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2320
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "On stroke" }),
2321
- /* @__PURE__ */ jsx16("select", { "aria-label": "Gradient on stroke", className: selectClass11, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx16("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2369
+ ed.target === "stroke" && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2370
+ /* @__PURE__ */ jsx17("span", { className: "w-16", children: "On stroke" }),
2371
+ /* @__PURE__ */ jsx17("select", { "aria-label": "Gradient on stroke", className: selectClass12, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx17("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2322
2372
  ] })
2323
2373
  ] })
2324
2374
  ] });
2325
2375
  }
2326
2376
 
2327
2377
  // src/vector/VectorToolbar.tsx
2328
- import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
2378
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
2329
2379
  var VECTOR_SNAP_TITLE = "Snap to objects";
2330
2380
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
2331
- var buttonClass14 = "inline-flex h-8 min-w-8 items-center justify-center rounded-md border border-border bg-background px-2 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
2381
+ 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";
2332
2382
  function VectorToolbar({
2333
2383
  controller,
2334
2384
  tools = VECTOR_TOOLS,
@@ -2346,16 +2396,16 @@ function VectorToolbar({
2346
2396
  const editing = state.editing;
2347
2397
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
2348
2398
  const maskSelected = state.selection.some((id) => isVectorMaskGroup(controller.getNode(id)));
2349
- const [live, setLive] = React9.useState(false);
2399
+ const [live, setLive] = React10.useState(false);
2350
2400
  const booleanDisabled = live ? count < 1 : count < 2;
2351
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx17("button", { type: "button", className: buttonClass14, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2352
- return /* @__PURE__ */ jsxs17("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2353
- /* @__PURE__ */ jsx17("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx17(
2401
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx18("button", { type: "button", className: buttonClass15, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2402
+ return /* @__PURE__ */ jsxs18("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2403
+ /* @__PURE__ */ jsx18("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx18(
2354
2404
  "button",
2355
2405
  {
2356
2406
  type: "button",
2357
2407
  "data-vector-tool": tool,
2358
- className: buttonClass14,
2408
+ className: buttonClass15,
2359
2409
  "aria-pressed": state.tool === tool,
2360
2410
  title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
2361
2411
  "aria-label": vectorToolLabel(tool),
@@ -2364,17 +2414,17 @@ function VectorToolbar({
2364
2414
  },
2365
2415
  tool
2366
2416
  )) }),
2367
- showHistory && /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2417
+ showHistory && /* @__PURE__ */ jsxs18("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2368
2418
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
2369
2419
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
2370
2420
  ] }),
2371
- showSnap && /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2372
- /* @__PURE__ */ jsx17(
2421
+ showSnap && /* @__PURE__ */ jsxs18("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2422
+ /* @__PURE__ */ jsx18(
2373
2423
  "button",
2374
2424
  {
2375
2425
  type: "button",
2376
2426
  "data-vector-snap": "",
2377
- className: buttonClass14,
2427
+ className: buttonClass15,
2378
2428
  "aria-pressed": state.snap.objects,
2379
2429
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
2380
2430
  "aria-label": VECTOR_SNAP_TITLE,
@@ -2382,7 +2432,7 @@ function VectorToolbar({
2382
2432
  children: "Snap"
2383
2433
  }
2384
2434
  ),
2385
- /* @__PURE__ */ jsxs17(
2435
+ /* @__PURE__ */ jsxs18(
2386
2436
  "select",
2387
2437
  {
2388
2438
  "data-vector-polar": "",
@@ -2392,8 +2442,8 @@ function VectorToolbar({
2392
2442
  value: String(vectorPolarIncrementOf(state.snap.angles) ?? ""),
2393
2443
  onChange: (e) => controller.setSnap({ angles: vectorPolarAngles(Number(e.target.value)) }),
2394
2444
  children: [
2395
- vectorPolarIncrementOf(state.snap.angles) === null && /* @__PURE__ */ jsx17("option", { value: "", children: "Custom" }),
2396
- [90, 45, 30, 15].map((n) => /* @__PURE__ */ jsxs17("option", { value: n, children: [
2445
+ vectorPolarIncrementOf(state.snap.angles) === null && /* @__PURE__ */ jsx18("option", { value: "", children: "Custom" }),
2446
+ [90, 45, 30, 15].map((n) => /* @__PURE__ */ jsxs18("option", { value: n, children: [
2397
2447
  n,
2398
2448
  "\xB0"
2399
2449
  ] }, n))
@@ -2401,13 +2451,13 @@ function VectorToolbar({
2401
2451
  }
2402
2452
  )
2403
2453
  ] }),
2404
- showPathEdit && /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2405
- VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx17(
2454
+ showPathEdit && /* @__PURE__ */ jsxs18("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2455
+ VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx18(
2406
2456
  "button",
2407
2457
  {
2408
2458
  type: "button",
2409
2459
  "data-vector-path-action": a.id,
2410
- className: buttonClass14,
2460
+ className: buttonClass15,
2411
2461
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
2412
2462
  "aria-label": a.title,
2413
2463
  disabled: !a.enabled(count, points),
@@ -2416,12 +2466,12 @@ function VectorToolbar({
2416
2466
  },
2417
2467
  a.id
2418
2468
  )),
2419
- VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx17(
2469
+ VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx18(
2420
2470
  "button",
2421
2471
  {
2422
2472
  type: "button",
2423
2473
  "data-vector-edit": t.key,
2424
- className: buttonClass14,
2474
+ className: buttonClass15,
2425
2475
  "aria-pressed": editing[t.key],
2426
2476
  title: t.title,
2427
2477
  "aria-label": t.title,
@@ -2430,9 +2480,9 @@ function VectorToolbar({
2430
2480
  },
2431
2481
  t.key
2432
2482
  )),
2433
- state.tool === "pencil" && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2483
+ state.tool === "pencil" && /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2434
2484
  "Smoothing",
2435
- /* @__PURE__ */ jsx17(
2485
+ /* @__PURE__ */ jsx18(
2436
2486
  "input",
2437
2487
  {
2438
2488
  type: "range",
@@ -2446,9 +2496,9 @@ function VectorToolbar({
2446
2496
  }
2447
2497
  )
2448
2498
  ] }),
2449
- state.tool === "eraser" && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2499
+ state.tool === "eraser" && /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2450
2500
  "Size",
2451
- /* @__PURE__ */ jsx17(
2501
+ /* @__PURE__ */ jsx18(
2452
2502
  "input",
2453
2503
  {
2454
2504
  type: "number",
@@ -2464,12 +2514,12 @@ function VectorToolbar({
2464
2514
  }
2465
2515
  )
2466
2516
  ] }),
2467
- state.tool === "paintBucket" && /* @__PURE__ */ jsx17(
2517
+ state.tool === "paintBucket" && /* @__PURE__ */ jsx18(
2468
2518
  "button",
2469
2519
  {
2470
2520
  type: "button",
2471
2521
  "data-vector-fill-gap": "",
2472
- className: buttonClass14,
2522
+ className: buttonClass15,
2473
2523
  "aria-pressed": editing.fillGap > 0,
2474
2524
  title: "Close small gaps between path ends when filling",
2475
2525
  "aria-label": "Close gaps",
@@ -2478,12 +2528,12 @@ function VectorToolbar({
2478
2528
  }
2479
2529
  )
2480
2530
  ] }),
2481
- showDistort && /* @__PURE__ */ jsx17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx17(
2531
+ showDistort && /* @__PURE__ */ jsx18("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx18(
2482
2532
  "button",
2483
2533
  {
2484
2534
  type: "button",
2485
2535
  "data-vector-free-distort": "",
2486
- className: buttonClass14,
2536
+ className: buttonClass15,
2487
2537
  "aria-pressed": state.freeDistort,
2488
2538
  title: `Free Distort: drag a corner of the selection box to distort in perspective (Shift+Alt+${modKeyLabel()}: symmetric perspective; ${modKeyLabel()}-drag a side to skew)`,
2489
2539
  "aria-label": "Free Distort",
@@ -2491,15 +2541,15 @@ function VectorToolbar({
2491
2541
  children: "Distort"
2492
2542
  }
2493
2543
  ) }),
2494
- showBlend && /* @__PURE__ */ jsx17(VectorBlendControls, { controller }),
2495
- showArrange && /* @__PURE__ */ jsxs17(Fragment6, { children: [
2496
- /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2497
- /* @__PURE__ */ jsx17(
2544
+ showBlend && /* @__PURE__ */ jsx18(VectorBlendControls, { controller }),
2545
+ showArrange && /* @__PURE__ */ jsxs18(Fragment6, { children: [
2546
+ /* @__PURE__ */ jsxs18("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2547
+ /* @__PURE__ */ jsx18(
2498
2548
  "button",
2499
2549
  {
2500
2550
  type: "button",
2501
2551
  "data-vector-live-boolean": "",
2502
- className: buttonClass14,
2552
+ className: buttonClass15,
2503
2553
  "aria-pressed": live,
2504
2554
  title: "Live booleans: keep the shapes editable",
2505
2555
  "aria-label": "Live booleans",
@@ -2516,12 +2566,12 @@ function VectorToolbar({
2516
2566
  act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
2517
2567
  act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
2518
2568
  ] }),
2519
- /* @__PURE__ */ jsx17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx17(
2569
+ /* @__PURE__ */ jsx18("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx18(
2520
2570
  "button",
2521
2571
  {
2522
2572
  type: "button",
2523
2573
  "data-vector-pathfinder": a.id,
2524
- className: buttonClass14,
2574
+ className: buttonClass15,
2525
2575
  title: a.title,
2526
2576
  "aria-label": a.title,
2527
2577
  disabled: count < a.min,
@@ -2530,7 +2580,7 @@ function VectorToolbar({
2530
2580
  },
2531
2581
  a.id
2532
2582
  )) }),
2533
- /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2583
+ /* @__PURE__ */ jsxs18("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2534
2584
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
2535
2585
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
2536
2586
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -2548,19 +2598,19 @@ function VectorBlendControls({ controller }) {
2548
2598
  const blend = getVectorBlendControls(state);
2549
2599
  const o = blend.options;
2550
2600
  const selected = blend.ids.length > 0;
2551
- return /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2552
- /* @__PURE__ */ jsx17("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass14, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
2553
- /* @__PURE__ */ jsx17(
2601
+ return /* @__PURE__ */ jsxs18("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2602
+ /* @__PURE__ */ jsx18("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" }),
2603
+ /* @__PURE__ */ jsx18(
2554
2604
  "select",
2555
2605
  {
2556
2606
  "aria-label": "Blend spacing",
2557
2607
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
2558
2608
  value: o.spacing,
2559
2609
  onChange: (e) => applyVectorBlendOptions(controller, { spacing: e.target.value }),
2560
- children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx17("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2610
+ children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx18("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2561
2611
  }
2562
2612
  ),
2563
- o.spacing !== "smoothColor" && /* @__PURE__ */ jsx17(
2613
+ o.spacing !== "smoothColor" && /* @__PURE__ */ jsx18(
2564
2614
  "input",
2565
2615
  {
2566
2616
  type: "number",
@@ -2576,12 +2626,12 @@ function VectorBlendControls({ controller }) {
2576
2626
  }
2577
2627
  }
2578
2628
  ),
2579
- /* @__PURE__ */ jsx17(
2629
+ /* @__PURE__ */ jsx18(
2580
2630
  "button",
2581
2631
  {
2582
2632
  type: "button",
2583
2633
  "data-vector-blend-action": "orient",
2584
- className: buttonClass14,
2634
+ className: buttonClass15,
2585
2635
  "aria-pressed": o.orientToSpine,
2586
2636
  title: "Orient steps to the spine",
2587
2637
  "aria-label": "Orient to spine",
@@ -2589,12 +2639,12 @@ function VectorBlendControls({ controller }) {
2589
2639
  children: "\u293F"
2590
2640
  }
2591
2641
  ),
2592
- /* @__PURE__ */ jsx17(
2642
+ /* @__PURE__ */ jsx18(
2593
2643
  "button",
2594
2644
  {
2595
2645
  type: "button",
2596
2646
  "data-vector-blend-action": "spine",
2597
- className: buttonClass14,
2647
+ className: buttonClass15,
2598
2648
  title: "Replace Spine (select a blend and a path)",
2599
2649
  "aria-label": "Replace Spine",
2600
2650
  disabled: !blend.canReplaceSpine,
@@ -2602,11 +2652,11 @@ function VectorBlendControls({ controller }) {
2602
2652
  children: "\u2307"
2603
2653
  }
2604
2654
  ),
2605
- /* @__PURE__ */ jsx17("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass14, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2606
- /* @__PURE__ */ jsx17("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass14, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2655
+ /* @__PURE__ */ jsx18("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass15, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2656
+ /* @__PURE__ */ jsx18("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass15, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2607
2657
  ] });
2608
2658
  }
2609
- var selectClass12 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2659
+ var selectClass13 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2610
2660
  var numberClass11 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2611
2661
  function VectorStylePanel({ controller, className }) {
2612
2662
  const state = useVectorEditorState(controller);
@@ -2639,17 +2689,17 @@ function VectorStylePanel({ controller, className }) {
2639
2689
  if (style) controller.setWidthProfile(id);
2640
2690
  else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
2641
2691
  };
2642
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx17("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2692
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx18("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2643
2693
  const arrowRow = (end, label) => {
2644
2694
  const key = end === "start" ? "startArrow" : "endArrow";
2645
2695
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
2646
- return /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2647
- /* @__PURE__ */ jsx17("span", { className: "w-16 text-xs text-foreground", children: label }),
2648
- /* @__PURE__ */ jsxs17("select", { "aria-label": `${label} arrowhead`, className: selectClass12, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2649
- /* @__PURE__ */ jsx17("option", { value: "", children: "None" }),
2650
- VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx17("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
2696
+ return /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2697
+ /* @__PURE__ */ jsx18("span", { className: "w-16 text-xs text-foreground", children: label }),
2698
+ /* @__PURE__ */ jsxs18("select", { "aria-label": `${label} arrowhead`, className: selectClass13, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2699
+ /* @__PURE__ */ jsx18("option", { value: "", children: "None" }),
2700
+ VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx18("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
2651
2701
  ] }),
2652
- /* @__PURE__ */ jsx17(
2702
+ /* @__PURE__ */ jsx18(
2653
2703
  "input",
2654
2704
  {
2655
2705
  type: "number",
@@ -2675,12 +2725,12 @@ function VectorStylePanel({ controller, className }) {
2675
2725
  const paint = source[key];
2676
2726
  const hex = vectorPaintToHex6(paint);
2677
2727
  const on = paint !== null && paint !== "none";
2678
- return /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2679
- /* @__PURE__ */ jsxs17("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2680
- /* @__PURE__ */ jsx17("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2728
+ return /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2729
+ /* @__PURE__ */ jsxs18("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2730
+ /* @__PURE__ */ jsx18("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2681
2731
  label
2682
2732
  ] }),
2683
- /* @__PURE__ */ jsx17(
2733
+ /* @__PURE__ */ jsx18(
2684
2734
  "input",
2685
2735
  {
2686
2736
  type: "color",
@@ -2691,18 +2741,18 @@ function VectorStylePanel({ controller, className }) {
2691
2741
  onChange: (e) => apply({ [key]: e.target.value })
2692
2742
  }
2693
2743
  ),
2694
- on && !hex && /* @__PURE__ */ jsx17("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2695
- style?.mixed[key] && /* @__PURE__ */ jsx17("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2744
+ on && !hex && /* @__PURE__ */ jsx18("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2745
+ style?.mixed[key] && /* @__PURE__ */ jsx18("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2696
2746
  ] });
2697
2747
  };
2698
- return /* @__PURE__ */ jsxs17("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2699
- /* @__PURE__ */ jsx17("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2748
+ return /* @__PURE__ */ jsxs18("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2749
+ /* @__PURE__ */ jsx18("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2700
2750
  paintRow("fill", "Fill"),
2701
2751
  paintRow("stroke", "Stroke"),
2702
- style && /* @__PURE__ */ jsx17(VectorGradientEditor, { controller }),
2703
- /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2704
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Width" }),
2705
- /* @__PURE__ */ jsx17(
2752
+ style && /* @__PURE__ */ jsx18(VectorGradientEditor, { controller }),
2753
+ /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2754
+ /* @__PURE__ */ jsx18("span", { className: "w-16", children: "Width" }),
2755
+ /* @__PURE__ */ jsx18(
2706
2756
  "input",
2707
2757
  {
2708
2758
  type: "number",
@@ -2718,33 +2768,33 @@ function VectorStylePanel({ controller, className }) {
2718
2768
  }
2719
2769
  )
2720
2770
  ] }),
2721
- /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2722
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Align" }),
2723
- /* @__PURE__ */ jsx17("select", { "aria-label": "Stroke alignment", className: selectClass12, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS2).map((a) => /* @__PURE__ */ jsx17("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS2[a] }, a)) }),
2771
+ /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2772
+ /* @__PURE__ */ jsx18("span", { className: "w-16", children: "Align" }),
2773
+ /* @__PURE__ */ jsx18("select", { "aria-label": "Stroke alignment", className: selectClass13, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS2).map((a) => /* @__PURE__ */ jsx18("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS2[a] }, a)) }),
2724
2774
  mixed("strokeAlign")
2725
2775
  ] }),
2726
- /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2727
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Profile" }),
2728
- /* @__PURE__ */ jsxs17(
2776
+ /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2777
+ /* @__PURE__ */ jsx18("span", { className: "w-16", children: "Profile" }),
2778
+ /* @__PURE__ */ jsxs18(
2729
2779
  "select",
2730
2780
  {
2731
2781
  "aria-label": "Width profile",
2732
- className: selectClass12,
2782
+ className: selectClass13,
2733
2783
  value: source.widthProfile,
2734
2784
  onChange: (e) => {
2735
2785
  if (e.target.value !== "custom") setProfile(e.target.value);
2736
2786
  },
2737
2787
  children: [
2738
- VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx17("option", { value: p.id, children: p.label }, p.id)),
2739
- source.widthProfile === "custom" && /* @__PURE__ */ jsx17("option", { value: "custom", disabled: true, children: "Custom" })
2788
+ VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx18("option", { value: p.id, children: p.label }, p.id)),
2789
+ source.widthProfile === "custom" && /* @__PURE__ */ jsx18("option", { value: "custom", disabled: true, children: "Custom" })
2740
2790
  ]
2741
2791
  }
2742
2792
  ),
2743
- /* @__PURE__ */ jsx17(
2793
+ /* @__PURE__ */ jsx18(
2744
2794
  "button",
2745
2795
  {
2746
2796
  type: "button",
2747
- className: buttonClass14,
2797
+ className: buttonClass15,
2748
2798
  title: "Reset to uniform width",
2749
2799
  "aria-label": "Reset to uniform width",
2750
2800
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -2754,20 +2804,20 @@ function VectorStylePanel({ controller, className }) {
2754
2804
  ),
2755
2805
  mixed("widthProfile")
2756
2806
  ] }),
2757
- source.hasOpenPath && /* @__PURE__ */ jsxs17(Fragment6, { children: [
2807
+ source.hasOpenPath && /* @__PURE__ */ jsxs18(Fragment6, { children: [
2758
2808
  arrowRow("start", "Start"),
2759
2809
  arrowRow("end", "End"),
2760
- (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2761
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Heads" }),
2762
- /* @__PURE__ */ jsxs17("select", { "aria-label": "Arrowhead alignment", className: selectClass12, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2763
- /* @__PURE__ */ jsx17("option", { value: "extend", children: "Tip past end" }),
2764
- /* @__PURE__ */ jsx17("option", { value: "tip", children: "Tip at end" })
2810
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2811
+ /* @__PURE__ */ jsx18("span", { className: "w-16", children: "Heads" }),
2812
+ /* @__PURE__ */ jsxs18("select", { "aria-label": "Arrowhead alignment", className: selectClass13, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2813
+ /* @__PURE__ */ jsx18("option", { value: "extend", children: "Tip past end" }),
2814
+ /* @__PURE__ */ jsx18("option", { value: "tip", children: "Tip at end" })
2765
2815
  ] })
2766
2816
  ] })
2767
2817
  ] }),
2768
- source.corners > 0 && /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2769
- /* @__PURE__ */ jsx17("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2770
- /* @__PURE__ */ jsx17(
2818
+ source.corners > 0 && /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2819
+ /* @__PURE__ */ jsx18("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2820
+ /* @__PURE__ */ jsx18(
2771
2821
  "input",
2772
2822
  {
2773
2823
  type: "number",
@@ -2782,12 +2832,12 @@ function VectorStylePanel({ controller, className }) {
2782
2832
  }
2783
2833
  }
2784
2834
  ),
2785
- /* @__PURE__ */ jsx17("select", { "aria-label": "Corner kind", className: selectClass12, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx17("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2835
+ /* @__PURE__ */ jsx18("select", { "aria-label": "Corner kind", className: selectClass13, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx18("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2786
2836
  mixed("cornerRadius")
2787
2837
  ] }),
2788
- style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "corner-smoothing", title: "Corner smoothing: squircle corners that ease into the curve (0.6 is iOS-like)", children: [
2789
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Smoothing" }),
2790
- /* @__PURE__ */ jsx17(
2838
+ style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ jsxs18("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: [
2839
+ /* @__PURE__ */ jsx18("span", { className: "w-16", children: "Smoothing" }),
2840
+ /* @__PURE__ */ jsx18(
2791
2841
  "input",
2792
2842
  {
2793
2843
  type: "range",
@@ -2802,9 +2852,9 @@ function VectorStylePanel({ controller, className }) {
2802
2852
  }
2803
2853
  )
2804
2854
  ] }),
2805
- style && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2806
- /* @__PURE__ */ jsx17("span", { className: "w-16", children: "Opacity" }),
2807
- /* @__PURE__ */ jsx17(
2855
+ style && /* @__PURE__ */ jsxs18("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2856
+ /* @__PURE__ */ jsx18("span", { className: "w-16", children: "Opacity" }),
2857
+ /* @__PURE__ */ jsx18(
2808
2858
  "input",
2809
2859
  {
2810
2860
  type: "range",
@@ -2818,25 +2868,26 @@ function VectorStylePanel({ controller, className }) {
2818
2868
  }
2819
2869
  )
2820
2870
  ] }),
2821
- /* @__PURE__ */ jsx17(VectorTextPanel, { controller }),
2822
- style && /* @__PURE__ */ jsx17(VectorAppearancePanel, { controller }),
2823
- style && /* @__PURE__ */ jsx17(VectorEffectsPanel, { controller }),
2824
- style && /* @__PURE__ */ jsx17(VectorEnvelopePanel, { controller }),
2825
- style && /* @__PURE__ */ jsx17(VectorMaskPanel, { controller }),
2826
- /* @__PURE__ */ jsx17(VectorFramePanel, { controller }),
2827
- /* @__PURE__ */ jsx17(VectorConnectorPanel, { controller }),
2828
- /* @__PURE__ */ jsx17(VectorShadingPanel, { controller }),
2829
- /* @__PURE__ */ jsx17(VectorSymmetryPanel, { controller }),
2830
- /* @__PURE__ */ jsx17(VectorLensPanel, { controller }),
2831
- /* @__PURE__ */ jsx17(VectorTracePanel, { controller }),
2832
- /* @__PURE__ */ jsx17(VectorConstraintsPanel, { controller }),
2833
- /* @__PURE__ */ jsx17(VectorSymbolsPanel, { controller })
2871
+ /* @__PURE__ */ jsx18(VectorTextPanel, { controller }),
2872
+ style && /* @__PURE__ */ jsx18(VectorAppearancePanel, { controller }),
2873
+ style && /* @__PURE__ */ jsx18(VectorEffectsPanel, { controller }),
2874
+ style && /* @__PURE__ */ jsx18(VectorEnvelopePanel, { controller }),
2875
+ style && /* @__PURE__ */ jsx18(VectorMaskPanel, { controller }),
2876
+ /* @__PURE__ */ jsx18(VectorFramePanel, { controller }),
2877
+ /* @__PURE__ */ jsx18(VectorConnectorPanel, { controller }),
2878
+ /* @__PURE__ */ jsx18(VectorShadingPanel, { controller }),
2879
+ /* @__PURE__ */ jsx18(VectorSymmetryPanel, { controller }),
2880
+ /* @__PURE__ */ jsx18(VectorPerspectivePanel, { controller }),
2881
+ /* @__PURE__ */ jsx18(VectorLensPanel, { controller }),
2882
+ /* @__PURE__ */ jsx18(VectorTracePanel, { controller }),
2883
+ /* @__PURE__ */ jsx18(VectorConstraintsPanel, { controller }),
2884
+ /* @__PURE__ */ jsx18(VectorSymbolsPanel, { controller })
2834
2885
  ] });
2835
2886
  }
2836
2887
 
2837
2888
  // src/vector/VectorProviderEffectsPanel.tsx
2838
2889
  import { useState as useState7, useSyncExternalStore as useSyncExternalStore2 } from "react";
2839
- import { Fragment as Fragment7, jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
2890
+ import { Fragment as Fragment7, jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2840
2891
  var btn = "rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40";
2841
2892
  function Controls({ controller, group }) {
2842
2893
  const provider = controller.effectsProvider;
@@ -2844,11 +2895,11 @@ function Controls({ controller, group }) {
2844
2895
  const { description, error } = useEffectDescription(provider, tag.effect);
2845
2896
  const [message, setMessage] = useState7(null);
2846
2897
  const run = (work) => void work.then(() => setMessage(null), (e) => setMessage(e instanceof Error ? e.message : String(e)));
2847
- if (!provider) return /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider: the shapes are kept as they are." });
2848
- if (error) return /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-amber-500", children: error });
2849
- if (!description) return /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-muted-foreground", children: "Loading\u2026" });
2850
- return /* @__PURE__ */ jsxs18(Fragment7, { children: [
2851
- /* @__PURE__ */ jsx18(
2898
+ if (!provider) return /* @__PURE__ */ jsx19("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider: the shapes are kept as they are." });
2899
+ if (error) return /* @__PURE__ */ jsx19("p", { className: "text-[11px] text-amber-500", children: error });
2900
+ if (!description) return /* @__PURE__ */ jsx19("p", { className: "text-[11px] text-muted-foreground", children: "Loading\u2026" });
2901
+ return /* @__PURE__ */ jsxs19(Fragment7, { children: [
2902
+ /* @__PURE__ */ jsx19(
2852
2903
  EffectParameters,
2853
2904
  {
2854
2905
  description,
@@ -2858,7 +2909,7 @@ function Controls({ controller, group }) {
2858
2909
  onPreset: (v) => run(controller.updateVectorEffect(group.id, v))
2859
2910
  }
2860
2911
  ),
2861
- message && /* @__PURE__ */ jsx18("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2912
+ message && /* @__PURE__ */ jsx19("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2862
2913
  ] });
2863
2914
  }
2864
2915
  function VectorProviderEffectsPanel({ controller, className = "" }) {
@@ -2877,41 +2928,41 @@ function VectorProviderEffectsPanel({ controller, className = "" }) {
2877
2928
  setMessage(null);
2878
2929
  Promise.resolve().then(work).catch((e) => setMessage(e instanceof Error ? e.message : String(e)));
2879
2930
  };
2880
- return /* @__PURE__ */ jsxs18("section", { className: `flex flex-col gap-2 text-xs ${className}`, "aria-label": "Vector effects", "data-vector-provider-effects": "", children: [
2881
- /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-1", children: [
2882
- /* @__PURE__ */ jsx18("span", { className: "flex-1 font-semibold", children: "Effects" }),
2883
- /* @__PURE__ */ jsx18("button", { type: "button", className: btn, disabled: !provider, "aria-pressed": browse, onClick: () => setBrowse(!browse), children: "Add effect\u2026" })
2931
+ return /* @__PURE__ */ jsxs19("section", { className: `flex flex-col gap-2 text-xs ${className}`, "aria-label": "Vector effects", "data-vector-provider-effects": "", children: [
2932
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-1", children: [
2933
+ /* @__PURE__ */ jsx19("span", { className: "flex-1 font-semibold", children: "Effects" }),
2934
+ /* @__PURE__ */ jsx19("button", { type: "button", className: btn, disabled: !provider, "aria-pressed": browse, onClick: () => setBrowse(!browse), children: "Add effect\u2026" })
2884
2935
  ] }),
2885
- !provider && /* @__PURE__ */ jsx18("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider is registered. Effect groups are kept as shapes." }),
2886
- browse && /* @__PURE__ */ jsx18(EffectsBrowser, { provider, kinds: ["vector"], title: "Vector effects", onPick: (e) => (setBrowse(false), run(() => controller.addVectorEffect(e.id))) }),
2887
- selected.map((g) => /* @__PURE__ */ jsxs18("div", { className: "space-y-1 rounded border border-border p-1", "data-vector-effect-group": g.id, children: [
2888
- /* @__PURE__ */ jsxs18("div", { className: "flex items-center gap-1", children: [
2889
- /* @__PURE__ */ jsx18("span", { className: "min-w-0 flex-1 truncate font-medium", children: g.providerEffect.name ?? g.providerEffect.effect }),
2890
- /* @__PURE__ */ jsx18("button", { type: "button", className: btn, title: "Keep the shapes as a plain group", onClick: () => run(() => controller.detachVectorEffect(g.id)), children: "Detach" })
2936
+ !provider && /* @__PURE__ */ jsx19("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider is registered. Effect groups are kept as shapes." }),
2937
+ browse && /* @__PURE__ */ jsx19(EffectsBrowser, { provider, kinds: ["vector"], title: "Vector effects", onPick: (e) => (setBrowse(false), run(() => controller.addVectorEffect(e.id))) }),
2938
+ selected.map((g) => /* @__PURE__ */ jsxs19("div", { className: "space-y-1 rounded border border-border p-1", "data-vector-effect-group": g.id, children: [
2939
+ /* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-1", children: [
2940
+ /* @__PURE__ */ jsx19("span", { className: "min-w-0 flex-1 truncate font-medium", children: g.providerEffect.name ?? g.providerEffect.effect }),
2941
+ /* @__PURE__ */ jsx19("button", { type: "button", className: btn, title: "Keep the shapes as a plain group", onClick: () => run(() => controller.detachVectorEffect(g.id)), children: "Detach" })
2891
2942
  ] }),
2892
- /* @__PURE__ */ jsx18(Controls, { controller, group: g })
2943
+ /* @__PURE__ */ jsx19(Controls, { controller, group: g })
2893
2944
  ] }, g.id)),
2894
- groups.length > 0 && !selected.length && /* @__PURE__ */ jsxs18("p", { className: "text-[11px] text-muted-foreground", children: [
2945
+ groups.length > 0 && !selected.length && /* @__PURE__ */ jsxs19("p", { className: "text-[11px] text-muted-foreground", children: [
2895
2946
  "Select an effect group (",
2896
2947
  groups.length,
2897
2948
  " in the drawing) to change it."
2898
2949
  ] }),
2899
- message && /* @__PURE__ */ jsx18("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2950
+ message && /* @__PURE__ */ jsx19("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2900
2951
  ] });
2901
2952
  }
2902
2953
 
2903
2954
  // src/vector/VectorEditor.tsx
2904
- import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2955
+ import { jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
2905
2956
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, effects, className }) {
2906
2957
  useEffects(controller, effects);
2907
2958
  const effectsShown = !!effects || controller.vectorEffectGroups().length > 0;
2908
- return /* @__PURE__ */ jsxs19("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2909
- /* @__PURE__ */ jsx19(VectorToolbar, { controller, tools }),
2910
- /* @__PURE__ */ jsxs19("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2911
- /* @__PURE__ */ jsx19("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx19(VectorCanvas, { controller, ...canvasProps }) }),
2912
- showStylePanel && /* @__PURE__ */ jsxs19("div", { className: "flex w-full shrink-0 flex-col gap-2 md:w-56", children: [
2913
- /* @__PURE__ */ jsx19(VectorStylePanel, { controller, className: "rounded-md border border-border p-3" }),
2914
- effectsShown && /* @__PURE__ */ jsx19(VectorProviderEffectsPanel, { controller, className: "rounded-md border border-border p-3" })
2959
+ return /* @__PURE__ */ jsxs20("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2960
+ /* @__PURE__ */ jsx20(VectorToolbar, { controller, tools }),
2961
+ /* @__PURE__ */ jsxs20("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2962
+ /* @__PURE__ */ jsx20("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx20(VectorCanvas, { controller, ...canvasProps }) }),
2963
+ showStylePanel && /* @__PURE__ */ jsxs20("div", { className: "flex w-full shrink-0 flex-col gap-2 md:w-56", children: [
2964
+ /* @__PURE__ */ jsx20(VectorStylePanel, { controller, className: "rounded-md border border-border p-3" }),
2965
+ effectsShown && /* @__PURE__ */ jsx20(VectorProviderEffectsPanel, { controller, className: "rounded-md border border-border p-3" })
2915
2966
  ] })
2916
2967
  ] })
2917
2968
  ] });
@@ -2932,6 +2983,7 @@ export {
2932
2983
  VectorLensPanel,
2933
2984
  VectorMaskPanel,
2934
2985
  VectorMaskedNodes,
2986
+ VectorPerspectivePanel,
2935
2987
  VectorProviderEffectsPanel,
2936
2988
  VectorSceneNodes,
2937
2989
  VectorShadingPanel,