@pixerate/editor-react 0.82.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,6 +2096,104 @@ function VectorSymmetryPanel({ controller, className }) {
2091
2096
  ] });
2092
2097
  }
2093
2098
 
2099
+ // src/vector/VectorPerspectivePanel.tsx
2100
+ import React9 from "react";
2101
+ import { vectorPerspectivePlanes, vectorPerspectivePreset } from "@pixerate/editor/vector";
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";
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";
2148
+ var numberClass9 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2149
+ function VectorLensPanel({ controller, className }) {
2150
+ const state = useVectorEditorState(controller);
2151
+ const path = state.selection.map((id) => controller.getNode(id)).find((n) => n?.type === "path" && n.subpaths.some((sp) => sp.closed) && !n.connector);
2152
+ if (!path) return null;
2153
+ const lens = path.lens ?? null;
2154
+ const spec = lens ? VECTOR_LENS_SPECS[lens.type] : null;
2155
+ const ids = [path.id];
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(
2160
+ "select",
2161
+ {
2162
+ "aria-label": "Lens type",
2163
+ className: selectClass11,
2164
+ value: lens?.type ?? "",
2165
+ onChange: (e) => e.target.value ? controller.setLens({ type: e.target.value }, { ids }) : controller.setLens(null, { ids }),
2166
+ children: [
2167
+ /* @__PURE__ */ jsx16("option", { value: "", children: "None" }),
2168
+ VECTOR_LENS_TYPES.map((t) => /* @__PURE__ */ jsx16("option", { value: t, children: VECTOR_LENS_LABELS[t] }, t))
2169
+ ]
2170
+ }
2171
+ )
2172
+ ] }),
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(
2176
+ "input",
2177
+ {
2178
+ type: "number",
2179
+ "aria-label": "Lens amount",
2180
+ title: `Amount: ${spec.unit} (${spec.min} to ${spec.max})`,
2181
+ className: numberClass9,
2182
+ min: spec.min,
2183
+ max: spec.max,
2184
+ step: lens.type === "magnify" ? 0.1 : 1,
2185
+ value: lens.amount,
2186
+ onChange: (e) => {
2187
+ const v = parseFloat(e.target.value);
2188
+ if (Number.isFinite(v)) controller.setLens({ amount: Math.min(spec.max, Math.max(spec.min, v)) }, { ids, merge: `lens-amount-${path.id}` });
2189
+ }
2190
+ }
2191
+ ),
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}` }) })
2193
+ ] })
2194
+ ] });
2195
+ }
2196
+
2094
2197
  // src/vector/VectorGradientEditor.tsx
2095
2198
  import {
2096
2199
  VECTOR_GRADIENT_TYPES,
@@ -2104,10 +2207,10 @@ import {
2104
2207
  getVectorGradientEditor,
2105
2208
  vectorPaintToHex as vectorPaintToHex5
2106
2209
  } from "@pixerate/editor/vector";
2107
- import { Fragment as Fragment5, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2108
- var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2109
- var numberClass9 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
2110
- 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";
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";
2212
+ var numberClass10 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
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";
2111
2214
  var percent = (v) => Math.round(v * 1e3) / 10;
2112
2215
  function VectorGradientEditor({ controller, className }) {
2113
2216
  const state = useVectorEditorState(controller);
@@ -2122,23 +2225,23 @@ function VectorGradientEditor({ controller, className }) {
2122
2225
  controller.setStyle({ [ed.target]: vectorPaintToHex5(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2123
2226
  } else controller.setGradient({ type: value });
2124
2227
  };
2125
- return /* @__PURE__ */ jsxs15("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
2126
- /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-1.5", children: [
2127
- /* @__PURE__ */ jsx15("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
2128
- ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx15("button", { type: "button", className: buttonClass13, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
2129
- ed.mixed && /* @__PURE__ */ jsx15("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" })
2130
2233
  ] }),
2131
- /* @__PURE__ */ jsxs15("div", { className: "flex items-center gap-1.5", children: [
2132
- /* @__PURE__ */ jsx15("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
2133
- /* @__PURE__ */ jsxs15("select", { "aria-label": "Gradient type", className: selectClass10, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
2134
- /* @__PURE__ */ jsx15("option", { value: "solid", children: "Solid" }),
2135
- VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx15("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))
2136
2239
  ] }),
2137
- /* @__PURE__ */ jsx15("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" })
2138
2241
  ] }),
2139
- g && /* @__PURE__ */ jsxs15(Fragment5, { children: [
2140
- /* @__PURE__ */ jsxs15("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2141
- /* @__PURE__ */ jsx15(
2242
+ g && /* @__PURE__ */ jsxs17(Fragment5, { children: [
2243
+ /* @__PURE__ */ jsxs17("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2244
+ /* @__PURE__ */ jsx17(
2142
2245
  "div",
2143
2246
  {
2144
2247
  role: "button",
@@ -2152,7 +2255,7 @@ function VectorGradientEditor({ controller, className }) {
2152
2255
  }
2153
2256
  }
2154
2257
  ),
2155
- ed.stops.map((s) => /* @__PURE__ */ jsx15(
2258
+ ed.stops.map((s) => /* @__PURE__ */ jsx17(
2156
2259
  "button",
2157
2260
  {
2158
2261
  type: "button",
@@ -2166,8 +2269,8 @@ function VectorGradientEditor({ controller, className }) {
2166
2269
  s.index
2167
2270
  ))
2168
2271
  ] }),
2169
- sel && /* @__PURE__ */ jsxs15("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2170
- /* @__PURE__ */ jsx15(
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(
2171
2274
  "input",
2172
2275
  {
2173
2276
  type: "color",
@@ -2178,9 +2281,9 @@ function VectorGradientEditor({ controller, className }) {
2178
2281
  onBlur: end
2179
2282
  }
2180
2283
  ),
2181
- /* @__PURE__ */ jsxs15("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: [
2182
2285
  "Opacity",
2183
- /* @__PURE__ */ jsx15(
2286
+ /* @__PURE__ */ jsx17(
2184
2287
  "input",
2185
2288
  {
2186
2289
  type: "number",
@@ -2188,7 +2291,7 @@ function VectorGradientEditor({ controller, className }) {
2188
2291
  max: 100,
2189
2292
  step: 5,
2190
2293
  "aria-label": "Stop opacity",
2191
- className: numberClass9,
2294
+ className: numberClass10,
2192
2295
  value: percent(sel.opacity ?? 1),
2193
2296
  onChange: (e) => {
2194
2297
  const n = parseFloat(e.target.value);
@@ -2197,9 +2300,9 @@ function VectorGradientEditor({ controller, className }) {
2197
2300
  }
2198
2301
  )
2199
2302
  ] }),
2200
- /* @__PURE__ */ jsxs15("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: [
2201
2304
  "At",
2202
- /* @__PURE__ */ jsx15(
2305
+ /* @__PURE__ */ jsx17(
2203
2306
  "input",
2204
2307
  {
2205
2308
  type: "number",
@@ -2207,7 +2310,7 @@ function VectorGradientEditor({ controller, className }) {
2207
2310
  max: 100,
2208
2311
  step: 1,
2209
2312
  "aria-label": "Stop position",
2210
- className: numberClass9,
2313
+ className: numberClass10,
2211
2314
  value: percent(sel.offset),
2212
2315
  onChange: (e) => {
2213
2316
  const n = parseFloat(e.target.value);
@@ -2216,9 +2319,9 @@ function VectorGradientEditor({ controller, className }) {
2216
2319
  }
2217
2320
  )
2218
2321
  ] }),
2219
- !sel.last && /* @__PURE__ */ jsxs15("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: [
2220
2323
  "Midpoint",
2221
- /* @__PURE__ */ jsx15(
2324
+ /* @__PURE__ */ jsx17(
2222
2325
  "input",
2223
2326
  {
2224
2327
  type: "number",
@@ -2226,7 +2329,7 @@ function VectorGradientEditor({ controller, className }) {
2226
2329
  max: 95,
2227
2330
  step: 1,
2228
2331
  "aria-label": "Stop midpoint",
2229
- className: numberClass9,
2332
+ className: numberClass10,
2230
2333
  value: percent(sel.midpoint ?? 0.5),
2231
2334
  onChange: (e) => {
2232
2335
  const n = parseFloat(e.target.value);
@@ -2235,15 +2338,15 @@ function VectorGradientEditor({ controller, className }) {
2235
2338
  }
2236
2339
  )
2237
2340
  ] }),
2238
- /* @__PURE__ */ jsx15("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" })
2239
2342
  ] }),
2240
- /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2241
- /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Colours" }),
2242
- /* @__PURE__ */ jsx15("select", { "aria-label": "Gradient interpolation", className: selectClass10, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx15("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)) })
2243
2346
  ] }),
2244
- /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2245
- /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Smooth" }),
2246
- /* @__PURE__ */ jsx15(
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(
2247
2350
  "input",
2248
2351
  {
2249
2352
  type: "range",
@@ -2259,23 +2362,23 @@ function VectorGradientEditor({ controller, className }) {
2259
2362
  }
2260
2363
  )
2261
2364
  ] }),
2262
- /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2263
- /* @__PURE__ */ jsx15("span", { className: "w-16", children: "Spread" }),
2264
- /* @__PURE__ */ jsx15("select", { "aria-label": "Gradient spread", className: selectClass10, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx15("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)) })
2265
2368
  ] }),
2266
- ed.target === "stroke" && /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2267
- /* @__PURE__ */ jsx15("span", { className: "w-16", children: "On stroke" }),
2268
- /* @__PURE__ */ jsx15("select", { "aria-label": "Gradient on stroke", className: selectClass10, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx15("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)) })
2269
2372
  ] })
2270
2373
  ] })
2271
2374
  ] });
2272
2375
  }
2273
2376
 
2274
2377
  // src/vector/VectorToolbar.tsx
2275
- import { Fragment as Fragment6, jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2378
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
2276
2379
  var VECTOR_SNAP_TITLE = "Snap to objects";
2277
2380
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
2278
- 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";
2279
2382
  function VectorToolbar({
2280
2383
  controller,
2281
2384
  tools = VECTOR_TOOLS,
@@ -2293,16 +2396,16 @@ function VectorToolbar({
2293
2396
  const editing = state.editing;
2294
2397
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
2295
2398
  const maskSelected = state.selection.some((id) => isVectorMaskGroup(controller.getNode(id)));
2296
- const [live, setLive] = React9.useState(false);
2399
+ const [live, setLive] = React10.useState(false);
2297
2400
  const booleanDisabled = live ? count < 1 : count < 2;
2298
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx16("button", { type: "button", className: buttonClass14, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2299
- return /* @__PURE__ */ jsxs16("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2300
- /* @__PURE__ */ jsx16("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx16(
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(
2301
2404
  "button",
2302
2405
  {
2303
2406
  type: "button",
2304
2407
  "data-vector-tool": tool,
2305
- className: buttonClass14,
2408
+ className: buttonClass15,
2306
2409
  "aria-pressed": state.tool === tool,
2307
2410
  title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
2308
2411
  "aria-label": vectorToolLabel(tool),
@@ -2311,17 +2414,17 @@ function VectorToolbar({
2311
2414
  },
2312
2415
  tool
2313
2416
  )) }),
2314
- showHistory && /* @__PURE__ */ jsxs16("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: [
2315
2418
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
2316
2419
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
2317
2420
  ] }),
2318
- showSnap && /* @__PURE__ */ jsxs16("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2319
- /* @__PURE__ */ jsx16(
2421
+ showSnap && /* @__PURE__ */ jsxs18("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2422
+ /* @__PURE__ */ jsx18(
2320
2423
  "button",
2321
2424
  {
2322
2425
  type: "button",
2323
2426
  "data-vector-snap": "",
2324
- className: buttonClass14,
2427
+ className: buttonClass15,
2325
2428
  "aria-pressed": state.snap.objects,
2326
2429
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
2327
2430
  "aria-label": VECTOR_SNAP_TITLE,
@@ -2329,7 +2432,7 @@ function VectorToolbar({
2329
2432
  children: "Snap"
2330
2433
  }
2331
2434
  ),
2332
- /* @__PURE__ */ jsxs16(
2435
+ /* @__PURE__ */ jsxs18(
2333
2436
  "select",
2334
2437
  {
2335
2438
  "data-vector-polar": "",
@@ -2339,8 +2442,8 @@ function VectorToolbar({
2339
2442
  value: String(vectorPolarIncrementOf(state.snap.angles) ?? ""),
2340
2443
  onChange: (e) => controller.setSnap({ angles: vectorPolarAngles(Number(e.target.value)) }),
2341
2444
  children: [
2342
- vectorPolarIncrementOf(state.snap.angles) === null && /* @__PURE__ */ jsx16("option", { value: "", children: "Custom" }),
2343
- [90, 45, 30, 15].map((n) => /* @__PURE__ */ jsxs16("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: [
2344
2447
  n,
2345
2448
  "\xB0"
2346
2449
  ] }, n))
@@ -2348,13 +2451,13 @@ function VectorToolbar({
2348
2451
  }
2349
2452
  )
2350
2453
  ] }),
2351
- showPathEdit && /* @__PURE__ */ jsxs16("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2352
- VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx16(
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(
2353
2456
  "button",
2354
2457
  {
2355
2458
  type: "button",
2356
2459
  "data-vector-path-action": a.id,
2357
- className: buttonClass14,
2460
+ className: buttonClass15,
2358
2461
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
2359
2462
  "aria-label": a.title,
2360
2463
  disabled: !a.enabled(count, points),
@@ -2363,12 +2466,12 @@ function VectorToolbar({
2363
2466
  },
2364
2467
  a.id
2365
2468
  )),
2366
- VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx16(
2469
+ VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx18(
2367
2470
  "button",
2368
2471
  {
2369
2472
  type: "button",
2370
2473
  "data-vector-edit": t.key,
2371
- className: buttonClass14,
2474
+ className: buttonClass15,
2372
2475
  "aria-pressed": editing[t.key],
2373
2476
  title: t.title,
2374
2477
  "aria-label": t.title,
@@ -2377,9 +2480,9 @@ function VectorToolbar({
2377
2480
  },
2378
2481
  t.key
2379
2482
  )),
2380
- state.tool === "pencil" && /* @__PURE__ */ jsxs16("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: [
2381
2484
  "Smoothing",
2382
- /* @__PURE__ */ jsx16(
2485
+ /* @__PURE__ */ jsx18(
2383
2486
  "input",
2384
2487
  {
2385
2488
  type: "range",
@@ -2393,9 +2496,9 @@ function VectorToolbar({
2393
2496
  }
2394
2497
  )
2395
2498
  ] }),
2396
- state.tool === "eraser" && /* @__PURE__ */ jsxs16("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: [
2397
2500
  "Size",
2398
- /* @__PURE__ */ jsx16(
2501
+ /* @__PURE__ */ jsx18(
2399
2502
  "input",
2400
2503
  {
2401
2504
  type: "number",
@@ -2411,12 +2514,12 @@ function VectorToolbar({
2411
2514
  }
2412
2515
  )
2413
2516
  ] }),
2414
- state.tool === "paintBucket" && /* @__PURE__ */ jsx16(
2517
+ state.tool === "paintBucket" && /* @__PURE__ */ jsx18(
2415
2518
  "button",
2416
2519
  {
2417
2520
  type: "button",
2418
2521
  "data-vector-fill-gap": "",
2419
- className: buttonClass14,
2522
+ className: buttonClass15,
2420
2523
  "aria-pressed": editing.fillGap > 0,
2421
2524
  title: "Close small gaps between path ends when filling",
2422
2525
  "aria-label": "Close gaps",
@@ -2425,12 +2528,12 @@ function VectorToolbar({
2425
2528
  }
2426
2529
  )
2427
2530
  ] }),
2428
- showDistort && /* @__PURE__ */ jsx16("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx16(
2531
+ showDistort && /* @__PURE__ */ jsx18("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx18(
2429
2532
  "button",
2430
2533
  {
2431
2534
  type: "button",
2432
2535
  "data-vector-free-distort": "",
2433
- className: buttonClass14,
2536
+ className: buttonClass15,
2434
2537
  "aria-pressed": state.freeDistort,
2435
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)`,
2436
2539
  "aria-label": "Free Distort",
@@ -2438,15 +2541,15 @@ function VectorToolbar({
2438
2541
  children: "Distort"
2439
2542
  }
2440
2543
  ) }),
2441
- showBlend && /* @__PURE__ */ jsx16(VectorBlendControls, { controller }),
2442
- showArrange && /* @__PURE__ */ jsxs16(Fragment6, { children: [
2443
- /* @__PURE__ */ jsxs16("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2444
- /* @__PURE__ */ jsx16(
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(
2445
2548
  "button",
2446
2549
  {
2447
2550
  type: "button",
2448
2551
  "data-vector-live-boolean": "",
2449
- className: buttonClass14,
2552
+ className: buttonClass15,
2450
2553
  "aria-pressed": live,
2451
2554
  title: "Live booleans: keep the shapes editable",
2452
2555
  "aria-label": "Live booleans",
@@ -2463,12 +2566,12 @@ function VectorToolbar({
2463
2566
  act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
2464
2567
  act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
2465
2568
  ] }),
2466
- /* @__PURE__ */ jsx16("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx16(
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(
2467
2570
  "button",
2468
2571
  {
2469
2572
  type: "button",
2470
2573
  "data-vector-pathfinder": a.id,
2471
- className: buttonClass14,
2574
+ className: buttonClass15,
2472
2575
  title: a.title,
2473
2576
  "aria-label": a.title,
2474
2577
  disabled: count < a.min,
@@ -2477,7 +2580,7 @@ function VectorToolbar({
2477
2580
  },
2478
2581
  a.id
2479
2582
  )) }),
2480
- /* @__PURE__ */ jsxs16("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: [
2481
2584
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
2482
2585
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
2483
2586
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -2495,19 +2598,19 @@ function VectorBlendControls({ controller }) {
2495
2598
  const blend = getVectorBlendControls(state);
2496
2599
  const o = blend.options;
2497
2600
  const selected = blend.ids.length > 0;
2498
- return /* @__PURE__ */ jsxs16("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2499
- /* @__PURE__ */ jsx16("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" }),
2500
- /* @__PURE__ */ jsx16(
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(
2501
2604
  "select",
2502
2605
  {
2503
2606
  "aria-label": "Blend spacing",
2504
2607
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
2505
2608
  value: o.spacing,
2506
2609
  onChange: (e) => applyVectorBlendOptions(controller, { spacing: e.target.value }),
2507
- children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx16("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))
2508
2611
  }
2509
2612
  ),
2510
- o.spacing !== "smoothColor" && /* @__PURE__ */ jsx16(
2613
+ o.spacing !== "smoothColor" && /* @__PURE__ */ jsx18(
2511
2614
  "input",
2512
2615
  {
2513
2616
  type: "number",
@@ -2523,12 +2626,12 @@ function VectorBlendControls({ controller }) {
2523
2626
  }
2524
2627
  }
2525
2628
  ),
2526
- /* @__PURE__ */ jsx16(
2629
+ /* @__PURE__ */ jsx18(
2527
2630
  "button",
2528
2631
  {
2529
2632
  type: "button",
2530
2633
  "data-vector-blend-action": "orient",
2531
- className: buttonClass14,
2634
+ className: buttonClass15,
2532
2635
  "aria-pressed": o.orientToSpine,
2533
2636
  title: "Orient steps to the spine",
2534
2637
  "aria-label": "Orient to spine",
@@ -2536,12 +2639,12 @@ function VectorBlendControls({ controller }) {
2536
2639
  children: "\u293F"
2537
2640
  }
2538
2641
  ),
2539
- /* @__PURE__ */ jsx16(
2642
+ /* @__PURE__ */ jsx18(
2540
2643
  "button",
2541
2644
  {
2542
2645
  type: "button",
2543
2646
  "data-vector-blend-action": "spine",
2544
- className: buttonClass14,
2647
+ className: buttonClass15,
2545
2648
  title: "Replace Spine (select a blend and a path)",
2546
2649
  "aria-label": "Replace Spine",
2547
2650
  disabled: !blend.canReplaceSpine,
@@ -2549,12 +2652,12 @@ function VectorBlendControls({ controller }) {
2549
2652
  children: "\u2307"
2550
2653
  }
2551
2654
  ),
2552
- /* @__PURE__ */ jsx16("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass14, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2553
- /* @__PURE__ */ jsx16("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" })
2554
2657
  ] });
2555
2658
  }
2556
- var selectClass11 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2557
- var numberClass10 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2659
+ var selectClass13 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2660
+ var numberClass11 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2558
2661
  function VectorStylePanel({ controller, className }) {
2559
2662
  const state = useVectorEditorState(controller);
2560
2663
  const style = getVectorSelectionStyle(state);
@@ -2586,17 +2689,17 @@ function VectorStylePanel({ controller, className }) {
2586
2689
  if (style) controller.setWidthProfile(id);
2587
2690
  else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
2588
2691
  };
2589
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx16("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;
2590
2693
  const arrowRow = (end, label) => {
2591
2694
  const key = end === "start" ? "startArrow" : "endArrow";
2592
2695
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
2593
- return /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2594
- /* @__PURE__ */ jsx16("span", { className: "w-16 text-xs text-foreground", children: label }),
2595
- /* @__PURE__ */ jsxs16("select", { "aria-label": `${label} arrowhead`, className: selectClass11, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2596
- /* @__PURE__ */ jsx16("option", { value: "", children: "None" }),
2597
- VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx16("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))
2598
2701
  ] }),
2599
- /* @__PURE__ */ jsx16(
2702
+ /* @__PURE__ */ jsx18(
2600
2703
  "input",
2601
2704
  {
2602
2705
  type: "number",
@@ -2604,7 +2707,7 @@ function VectorStylePanel({ controller, className }) {
2604
2707
  step: 0.25,
2605
2708
  "aria-label": `${label} arrowhead scale`,
2606
2709
  title: "Scale (\xD7 stroke width)",
2607
- className: numberClass10,
2710
+ className: numberClass11,
2608
2711
  value: source[scaleKey],
2609
2712
  onChange: (e) => {
2610
2713
  const n = parseFloat(e.target.value);
@@ -2622,12 +2725,12 @@ function VectorStylePanel({ controller, className }) {
2622
2725
  const paint = source[key];
2623
2726
  const hex = vectorPaintToHex6(paint);
2624
2727
  const on = paint !== null && paint !== "none";
2625
- return /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2626
- /* @__PURE__ */ jsxs16("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2627
- /* @__PURE__ */ jsx16("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 }) }),
2628
2731
  label
2629
2732
  ] }),
2630
- /* @__PURE__ */ jsx16(
2733
+ /* @__PURE__ */ jsx18(
2631
2734
  "input",
2632
2735
  {
2633
2736
  type: "color",
@@ -2638,18 +2741,18 @@ function VectorStylePanel({ controller, className }) {
2638
2741
  onChange: (e) => apply({ [key]: e.target.value })
2639
2742
  }
2640
2743
  ),
2641
- on && !hex && /* @__PURE__ */ jsx16("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2642
- style?.mixed[key] && /* @__PURE__ */ jsx16("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" })
2643
2746
  ] });
2644
2747
  };
2645
- return /* @__PURE__ */ jsxs16("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2646
- /* @__PURE__ */ jsx16("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" }),
2647
2750
  paintRow("fill", "Fill"),
2648
2751
  paintRow("stroke", "Stroke"),
2649
- style && /* @__PURE__ */ jsx16(VectorGradientEditor, { controller }),
2650
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2651
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Width" }),
2652
- /* @__PURE__ */ jsx16(
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(
2653
2756
  "input",
2654
2757
  {
2655
2758
  type: "number",
@@ -2665,33 +2768,33 @@ function VectorStylePanel({ controller, className }) {
2665
2768
  }
2666
2769
  )
2667
2770
  ] }),
2668
- /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2669
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Align" }),
2670
- /* @__PURE__ */ jsx16("select", { "aria-label": "Stroke alignment", className: selectClass11, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS2).map((a) => /* @__PURE__ */ jsx16("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)) }),
2671
2774
  mixed("strokeAlign")
2672
2775
  ] }),
2673
- /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2674
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Profile" }),
2675
- /* @__PURE__ */ jsxs16(
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(
2676
2779
  "select",
2677
2780
  {
2678
2781
  "aria-label": "Width profile",
2679
- className: selectClass11,
2782
+ className: selectClass13,
2680
2783
  value: source.widthProfile,
2681
2784
  onChange: (e) => {
2682
2785
  if (e.target.value !== "custom") setProfile(e.target.value);
2683
2786
  },
2684
2787
  children: [
2685
- VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx16("option", { value: p.id, children: p.label }, p.id)),
2686
- source.widthProfile === "custom" && /* @__PURE__ */ jsx16("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" })
2687
2790
  ]
2688
2791
  }
2689
2792
  ),
2690
- /* @__PURE__ */ jsx16(
2793
+ /* @__PURE__ */ jsx18(
2691
2794
  "button",
2692
2795
  {
2693
2796
  type: "button",
2694
- className: buttonClass14,
2797
+ className: buttonClass15,
2695
2798
  title: "Reset to uniform width",
2696
2799
  "aria-label": "Reset to uniform width",
2697
2800
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -2701,27 +2804,27 @@ function VectorStylePanel({ controller, className }) {
2701
2804
  ),
2702
2805
  mixed("widthProfile")
2703
2806
  ] }),
2704
- source.hasOpenPath && /* @__PURE__ */ jsxs16(Fragment6, { children: [
2807
+ source.hasOpenPath && /* @__PURE__ */ jsxs18(Fragment6, { children: [
2705
2808
  arrowRow("start", "Start"),
2706
2809
  arrowRow("end", "End"),
2707
- (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2708
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Heads" }),
2709
- /* @__PURE__ */ jsxs16("select", { "aria-label": "Arrowhead alignment", className: selectClass11, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2710
- /* @__PURE__ */ jsx16("option", { value: "extend", children: "Tip past end" }),
2711
- /* @__PURE__ */ jsx16("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" })
2712
2815
  ] })
2713
2816
  ] })
2714
2817
  ] }),
2715
- source.corners > 0 && /* @__PURE__ */ jsxs16("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2716
- /* @__PURE__ */ jsx16("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2717
- /* @__PURE__ */ jsx16(
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(
2718
2821
  "input",
2719
2822
  {
2720
2823
  type: "number",
2721
2824
  min: 0,
2722
2825
  step: 1,
2723
2826
  "aria-label": "Corner radius",
2724
- className: numberClass10,
2827
+ className: numberClass11,
2725
2828
  value: Math.round(source.cornerRadius * 100) / 100,
2726
2829
  onChange: (e) => {
2727
2830
  const n = parseFloat(e.target.value);
@@ -2729,12 +2832,12 @@ function VectorStylePanel({ controller, className }) {
2729
2832
  }
2730
2833
  }
2731
2834
  ),
2732
- /* @__PURE__ */ jsx16("select", { "aria-label": "Corner kind", className: selectClass11, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx16("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)) }),
2733
2836
  mixed("cornerRadius")
2734
2837
  ] }),
2735
- style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ jsxs16("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: [
2736
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Smoothing" }),
2737
- /* @__PURE__ */ jsx16(
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(
2738
2841
  "input",
2739
2842
  {
2740
2843
  type: "range",
@@ -2749,9 +2852,9 @@ function VectorStylePanel({ controller, className }) {
2749
2852
  }
2750
2853
  )
2751
2854
  ] }),
2752
- style && /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2753
- /* @__PURE__ */ jsx16("span", { className: "w-16", children: "Opacity" }),
2754
- /* @__PURE__ */ jsx16(
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(
2755
2858
  "input",
2756
2859
  {
2757
2860
  type: "range",
@@ -2765,24 +2868,26 @@ function VectorStylePanel({ controller, className }) {
2765
2868
  }
2766
2869
  )
2767
2870
  ] }),
2768
- /* @__PURE__ */ jsx16(VectorTextPanel, { controller }),
2769
- style && /* @__PURE__ */ jsx16(VectorAppearancePanel, { controller }),
2770
- style && /* @__PURE__ */ jsx16(VectorEffectsPanel, { controller }),
2771
- style && /* @__PURE__ */ jsx16(VectorEnvelopePanel, { controller }),
2772
- style && /* @__PURE__ */ jsx16(VectorMaskPanel, { controller }),
2773
- /* @__PURE__ */ jsx16(VectorFramePanel, { controller }),
2774
- /* @__PURE__ */ jsx16(VectorConnectorPanel, { controller }),
2775
- /* @__PURE__ */ jsx16(VectorShadingPanel, { controller }),
2776
- /* @__PURE__ */ jsx16(VectorSymmetryPanel, { controller }),
2777
- /* @__PURE__ */ jsx16(VectorTracePanel, { controller }),
2778
- /* @__PURE__ */ jsx16(VectorConstraintsPanel, { controller }),
2779
- /* @__PURE__ */ jsx16(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 })
2780
2885
  ] });
2781
2886
  }
2782
2887
 
2783
2888
  // src/vector/VectorProviderEffectsPanel.tsx
2784
2889
  import { useState as useState7, useSyncExternalStore as useSyncExternalStore2 } from "react";
2785
- import { Fragment as Fragment7, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
2890
+ import { Fragment as Fragment7, jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2786
2891
  var btn = "rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40";
2787
2892
  function Controls({ controller, group }) {
2788
2893
  const provider = controller.effectsProvider;
@@ -2790,11 +2895,11 @@ function Controls({ controller, group }) {
2790
2895
  const { description, error } = useEffectDescription(provider, tag.effect);
2791
2896
  const [message, setMessage] = useState7(null);
2792
2897
  const run = (work) => void work.then(() => setMessage(null), (e) => setMessage(e instanceof Error ? e.message : String(e)));
2793
- if (!provider) return /* @__PURE__ */ jsx17("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider: the shapes are kept as they are." });
2794
- if (error) return /* @__PURE__ */ jsx17("p", { className: "text-[11px] text-amber-500", children: error });
2795
- if (!description) return /* @__PURE__ */ jsx17("p", { className: "text-[11px] text-muted-foreground", children: "Loading\u2026" });
2796
- return /* @__PURE__ */ jsxs17(Fragment7, { children: [
2797
- /* @__PURE__ */ jsx17(
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(
2798
2903
  EffectParameters,
2799
2904
  {
2800
2905
  description,
@@ -2804,7 +2909,7 @@ function Controls({ controller, group }) {
2804
2909
  onPreset: (v) => run(controller.updateVectorEffect(group.id, v))
2805
2910
  }
2806
2911
  ),
2807
- message && /* @__PURE__ */ jsx17("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2912
+ message && /* @__PURE__ */ jsx19("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2808
2913
  ] });
2809
2914
  }
2810
2915
  function VectorProviderEffectsPanel({ controller, className = "" }) {
@@ -2823,41 +2928,41 @@ function VectorProviderEffectsPanel({ controller, className = "" }) {
2823
2928
  setMessage(null);
2824
2929
  Promise.resolve().then(work).catch((e) => setMessage(e instanceof Error ? e.message : String(e)));
2825
2930
  };
2826
- return /* @__PURE__ */ jsxs17("section", { className: `flex flex-col gap-2 text-xs ${className}`, "aria-label": "Vector effects", "data-vector-provider-effects": "", children: [
2827
- /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-1", children: [
2828
- /* @__PURE__ */ jsx17("span", { className: "flex-1 font-semibold", children: "Effects" }),
2829
- /* @__PURE__ */ jsx17("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" })
2830
2935
  ] }),
2831
- !provider && /* @__PURE__ */ jsx17("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider is registered. Effect groups are kept as shapes." }),
2832
- browse && /* @__PURE__ */ jsx17(EffectsBrowser, { provider, kinds: ["vector"], title: "Vector effects", onPick: (e) => (setBrowse(false), run(() => controller.addVectorEffect(e.id))) }),
2833
- selected.map((g) => /* @__PURE__ */ jsxs17("div", { className: "space-y-1 rounded border border-border p-1", "data-vector-effect-group": g.id, children: [
2834
- /* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-1", children: [
2835
- /* @__PURE__ */ jsx17("span", { className: "min-w-0 flex-1 truncate font-medium", children: g.providerEffect.name ?? g.providerEffect.effect }),
2836
- /* @__PURE__ */ jsx17("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" })
2837
2942
  ] }),
2838
- /* @__PURE__ */ jsx17(Controls, { controller, group: g })
2943
+ /* @__PURE__ */ jsx19(Controls, { controller, group: g })
2839
2944
  ] }, g.id)),
2840
- groups.length > 0 && !selected.length && /* @__PURE__ */ jsxs17("p", { className: "text-[11px] text-muted-foreground", children: [
2945
+ groups.length > 0 && !selected.length && /* @__PURE__ */ jsxs19("p", { className: "text-[11px] text-muted-foreground", children: [
2841
2946
  "Select an effect group (",
2842
2947
  groups.length,
2843
2948
  " in the drawing) to change it."
2844
2949
  ] }),
2845
- message && /* @__PURE__ */ jsx17("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2950
+ message && /* @__PURE__ */ jsx19("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2846
2951
  ] });
2847
2952
  }
2848
2953
 
2849
2954
  // src/vector/VectorEditor.tsx
2850
- import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
2955
+ import { jsx as jsx20, jsxs as jsxs20 } from "react/jsx-runtime";
2851
2956
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, effects, className }) {
2852
2957
  useEffects(controller, effects);
2853
2958
  const effectsShown = !!effects || controller.vectorEffectGroups().length > 0;
2854
- return /* @__PURE__ */ jsxs18("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2855
- /* @__PURE__ */ jsx18(VectorToolbar, { controller, tools }),
2856
- /* @__PURE__ */ jsxs18("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2857
- /* @__PURE__ */ jsx18("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx18(VectorCanvas, { controller, ...canvasProps }) }),
2858
- showStylePanel && /* @__PURE__ */ jsxs18("div", { className: "flex w-full shrink-0 flex-col gap-2 md:w-56", children: [
2859
- /* @__PURE__ */ jsx18(VectorStylePanel, { controller, className: "rounded-md border border-border p-3" }),
2860
- effectsShown && /* @__PURE__ */ jsx18(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" })
2861
2966
  ] })
2862
2967
  ] })
2863
2968
  ] });
@@ -2875,8 +2980,10 @@ export {
2875
2980
  VectorGradientAnnotator,
2876
2981
  VectorGradientDefs,
2877
2982
  VectorGradientEditor,
2983
+ VectorLensPanel,
2878
2984
  VectorMaskPanel,
2879
2985
  VectorMaskedNodes,
2986
+ VectorPerspectivePanel,
2880
2987
  VectorProviderEffectsPanel,
2881
2988
  VectorSceneNodes,
2882
2989
  VectorShadingPanel,