@pixerate/editor-react 0.81.0 → 0.83.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.
@@ -2091,6 +2091,59 @@ function VectorSymmetryPanel({ controller, className }) {
2091
2091
  ] });
2092
2092
  }
2093
2093
 
2094
+ // src/vector/VectorLensPanel.tsx
2095
+ import { VECTOR_LENS_LABELS, VECTOR_LENS_SPECS, VECTOR_LENS_TYPES } from "@pixerate/editor/vector";
2096
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2097
+ var selectClass10 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2098
+ var numberClass9 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2099
+ function VectorLensPanel({ controller, className }) {
2100
+ const state = useVectorEditorState(controller);
2101
+ const path = state.selection.map((id) => controller.getNode(id)).find((n) => n?.type === "path" && n.subpaths.some((sp) => sp.closed) && !n.connector);
2102
+ if (!path) return null;
2103
+ const lens = path.lens ?? null;
2104
+ const spec = lens ? VECTOR_LENS_SPECS[lens.type] : null;
2105
+ 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(
2110
+ "select",
2111
+ {
2112
+ "aria-label": "Lens type",
2113
+ className: selectClass10,
2114
+ value: lens?.type ?? "",
2115
+ onChange: (e) => e.target.value ? controller.setLens({ type: e.target.value }, { ids }) : controller.setLens(null, { ids }),
2116
+ 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))
2119
+ ]
2120
+ }
2121
+ )
2122
+ ] }),
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(
2126
+ "input",
2127
+ {
2128
+ type: "number",
2129
+ "aria-label": "Lens amount",
2130
+ title: `Amount: ${spec.unit} (${spec.min} to ${spec.max})`,
2131
+ className: numberClass9,
2132
+ min: spec.min,
2133
+ max: spec.max,
2134
+ step: lens.type === "magnify" ? 0.1 : 1,
2135
+ value: lens.amount,
2136
+ onChange: (e) => {
2137
+ const v = parseFloat(e.target.value);
2138
+ if (Number.isFinite(v)) controller.setLens({ amount: Math.min(spec.max, Math.max(spec.min, v)) }, { ids, merge: `lens-amount-${path.id}` });
2139
+ }
2140
+ }
2141
+ ),
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}` }) })
2143
+ ] })
2144
+ ] });
2145
+ }
2146
+
2094
2147
  // src/vector/VectorGradientEditor.tsx
2095
2148
  import {
2096
2149
  VECTOR_GRADIENT_TYPES,
@@ -2104,9 +2157,9 @@ import {
2104
2157
  getVectorGradientEditor,
2105
2158
  vectorPaintToHex as vectorPaintToHex5
2106
2159
  } 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";
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";
2162
+ var numberClass10 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
2110
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";
2111
2164
  var percent = (v) => Math.round(v * 1e3) / 10;
2112
2165
  function VectorGradientEditor({ controller, className }) {
@@ -2122,23 +2175,23 @@ function VectorGradientEditor({ controller, className }) {
2122
2175
  controller.setStyle({ [ed.target]: vectorPaintToHex5(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
2123
2176
  } else controller.setGradient({ type: value });
2124
2177
  };
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" })
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" })
2130
2183
  ] }),
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))
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))
2136
2189
  ] }),
2137
- /* @__PURE__ */ jsx15("button", { type: "button", className: buttonClass13, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2190
+ /* @__PURE__ */ jsx16("button", { type: "button", className: buttonClass13, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
2138
2191
  ] }),
2139
- g && /* @__PURE__ */ jsxs15(Fragment5, { children: [
2140
- /* @__PURE__ */ jsxs15("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2141
- /* @__PURE__ */ jsx15(
2192
+ g && /* @__PURE__ */ jsxs16(Fragment5, { children: [
2193
+ /* @__PURE__ */ jsxs16("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
2194
+ /* @__PURE__ */ jsx16(
2142
2195
  "div",
2143
2196
  {
2144
2197
  role: "button",
@@ -2152,7 +2205,7 @@ function VectorGradientEditor({ controller, className }) {
2152
2205
  }
2153
2206
  }
2154
2207
  ),
2155
- ed.stops.map((s) => /* @__PURE__ */ jsx15(
2208
+ ed.stops.map((s) => /* @__PURE__ */ jsx16(
2156
2209
  "button",
2157
2210
  {
2158
2211
  type: "button",
@@ -2166,8 +2219,8 @@ function VectorGradientEditor({ controller, className }) {
2166
2219
  s.index
2167
2220
  ))
2168
2221
  ] }),
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(
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(
2171
2224
  "input",
2172
2225
  {
2173
2226
  type: "color",
@@ -2178,9 +2231,9 @@ function VectorGradientEditor({ controller, className }) {
2178
2231
  onBlur: end
2179
2232
  }
2180
2233
  ),
2181
- /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2234
+ /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2182
2235
  "Opacity",
2183
- /* @__PURE__ */ jsx15(
2236
+ /* @__PURE__ */ jsx16(
2184
2237
  "input",
2185
2238
  {
2186
2239
  type: "number",
@@ -2188,7 +2241,7 @@ function VectorGradientEditor({ controller, className }) {
2188
2241
  max: 100,
2189
2242
  step: 5,
2190
2243
  "aria-label": "Stop opacity",
2191
- className: numberClass9,
2244
+ className: numberClass10,
2192
2245
  value: percent(sel.opacity ?? 1),
2193
2246
  onChange: (e) => {
2194
2247
  const n = parseFloat(e.target.value);
@@ -2197,9 +2250,9 @@ function VectorGradientEditor({ controller, className }) {
2197
2250
  }
2198
2251
  )
2199
2252
  ] }),
2200
- /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2253
+ /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2201
2254
  "At",
2202
- /* @__PURE__ */ jsx15(
2255
+ /* @__PURE__ */ jsx16(
2203
2256
  "input",
2204
2257
  {
2205
2258
  type: "number",
@@ -2207,7 +2260,7 @@ function VectorGradientEditor({ controller, className }) {
2207
2260
  max: 100,
2208
2261
  step: 1,
2209
2262
  "aria-label": "Stop position",
2210
- className: numberClass9,
2263
+ className: numberClass10,
2211
2264
  value: percent(sel.offset),
2212
2265
  onChange: (e) => {
2213
2266
  const n = parseFloat(e.target.value);
@@ -2216,9 +2269,9 @@ function VectorGradientEditor({ controller, className }) {
2216
2269
  }
2217
2270
  )
2218
2271
  ] }),
2219
- !sel.last && /* @__PURE__ */ jsxs15("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2272
+ !sel.last && /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2220
2273
  "Midpoint",
2221
- /* @__PURE__ */ jsx15(
2274
+ /* @__PURE__ */ jsx16(
2222
2275
  "input",
2223
2276
  {
2224
2277
  type: "number",
@@ -2226,7 +2279,7 @@ function VectorGradientEditor({ controller, className }) {
2226
2279
  max: 95,
2227
2280
  step: 1,
2228
2281
  "aria-label": "Stop midpoint",
2229
- className: numberClass9,
2282
+ className: numberClass10,
2230
2283
  value: percent(sel.midpoint ?? 0.5),
2231
2284
  onChange: (e) => {
2232
2285
  const n = parseFloat(e.target.value);
@@ -2235,15 +2288,15 @@ function VectorGradientEditor({ controller, className }) {
2235
2288
  }
2236
2289
  )
2237
2290
  ] }),
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" })
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" })
2239
2292
  ] }),
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)) })
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)) })
2243
2296
  ] }),
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(
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(
2247
2300
  "input",
2248
2301
  {
2249
2302
  type: "range",
@@ -2259,20 +2312,20 @@ function VectorGradientEditor({ controller, className }) {
2259
2312
  }
2260
2313
  )
2261
2314
  ] }),
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)) })
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)) })
2265
2318
  ] }),
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)) })
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)) })
2269
2322
  ] })
2270
2323
  ] })
2271
2324
  ] });
2272
2325
  }
2273
2326
 
2274
2327
  // src/vector/VectorToolbar.tsx
2275
- import { Fragment as Fragment6, jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2328
+ import { Fragment as Fragment6, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
2276
2329
  var VECTOR_SNAP_TITLE = "Snap to objects";
2277
2330
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
2278
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";
@@ -2295,9 +2348,9 @@ function VectorToolbar({
2295
2348
  const maskSelected = state.selection.some((id) => isVectorMaskGroup(controller.getNode(id)));
2296
2349
  const [live, setLive] = React9.useState(false);
2297
2350
  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(
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(
2301
2354
  "button",
2302
2355
  {
2303
2356
  type: "button",
@@ -2311,12 +2364,12 @@ function VectorToolbar({
2311
2364
  },
2312
2365
  tool
2313
2366
  )) }),
2314
- showHistory && /* @__PURE__ */ jsxs16("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2367
+ showHistory && /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2315
2368
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
2316
2369
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
2317
2370
  ] }),
2318
- showSnap && /* @__PURE__ */ jsxs16("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2319
- /* @__PURE__ */ jsx16(
2371
+ showSnap && /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2372
+ /* @__PURE__ */ jsx17(
2320
2373
  "button",
2321
2374
  {
2322
2375
  type: "button",
@@ -2329,7 +2382,7 @@ function VectorToolbar({
2329
2382
  children: "Snap"
2330
2383
  }
2331
2384
  ),
2332
- /* @__PURE__ */ jsxs16(
2385
+ /* @__PURE__ */ jsxs17(
2333
2386
  "select",
2334
2387
  {
2335
2388
  "data-vector-polar": "",
@@ -2339,8 +2392,8 @@ function VectorToolbar({
2339
2392
  value: String(vectorPolarIncrementOf(state.snap.angles) ?? ""),
2340
2393
  onChange: (e) => controller.setSnap({ angles: vectorPolarAngles(Number(e.target.value)) }),
2341
2394
  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: [
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: [
2344
2397
  n,
2345
2398
  "\xB0"
2346
2399
  ] }, n))
@@ -2348,8 +2401,8 @@ function VectorToolbar({
2348
2401
  }
2349
2402
  )
2350
2403
  ] }),
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(
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(
2353
2406
  "button",
2354
2407
  {
2355
2408
  type: "button",
@@ -2363,7 +2416,7 @@ function VectorToolbar({
2363
2416
  },
2364
2417
  a.id
2365
2418
  )),
2366
- VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx16(
2419
+ VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx17(
2367
2420
  "button",
2368
2421
  {
2369
2422
  type: "button",
@@ -2377,9 +2430,9 @@ function VectorToolbar({
2377
2430
  },
2378
2431
  t.key
2379
2432
  )),
2380
- state.tool === "pencil" && /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2433
+ state.tool === "pencil" && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2381
2434
  "Smoothing",
2382
- /* @__PURE__ */ jsx16(
2435
+ /* @__PURE__ */ jsx17(
2383
2436
  "input",
2384
2437
  {
2385
2438
  type: "range",
@@ -2393,9 +2446,9 @@ function VectorToolbar({
2393
2446
  }
2394
2447
  )
2395
2448
  ] }),
2396
- state.tool === "eraser" && /* @__PURE__ */ jsxs16("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2449
+ state.tool === "eraser" && /* @__PURE__ */ jsxs17("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2397
2450
  "Size",
2398
- /* @__PURE__ */ jsx16(
2451
+ /* @__PURE__ */ jsx17(
2399
2452
  "input",
2400
2453
  {
2401
2454
  type: "number",
@@ -2411,7 +2464,7 @@ function VectorToolbar({
2411
2464
  }
2412
2465
  )
2413
2466
  ] }),
2414
- state.tool === "paintBucket" && /* @__PURE__ */ jsx16(
2467
+ state.tool === "paintBucket" && /* @__PURE__ */ jsx17(
2415
2468
  "button",
2416
2469
  {
2417
2470
  type: "button",
@@ -2425,7 +2478,7 @@ function VectorToolbar({
2425
2478
  }
2426
2479
  )
2427
2480
  ] }),
2428
- showDistort && /* @__PURE__ */ jsx16("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx16(
2481
+ showDistort && /* @__PURE__ */ jsx17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx17(
2429
2482
  "button",
2430
2483
  {
2431
2484
  type: "button",
@@ -2438,10 +2491,10 @@ function VectorToolbar({
2438
2491
  children: "Distort"
2439
2492
  }
2440
2493
  ) }),
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(
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(
2445
2498
  "button",
2446
2499
  {
2447
2500
  type: "button",
@@ -2463,7 +2516,7 @@ function VectorToolbar({
2463
2516
  act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
2464
2517
  act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
2465
2518
  ] }),
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(
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(
2467
2520
  "button",
2468
2521
  {
2469
2522
  type: "button",
@@ -2477,7 +2530,7 @@ function VectorToolbar({
2477
2530
  },
2478
2531
  a.id
2479
2532
  )) }),
2480
- /* @__PURE__ */ jsxs16("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2533
+ /* @__PURE__ */ jsxs17("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2481
2534
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
2482
2535
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
2483
2536
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -2495,19 +2548,19 @@ function VectorBlendControls({ controller }) {
2495
2548
  const blend = getVectorBlendControls(state);
2496
2549
  const o = blend.options;
2497
2550
  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(
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(
2501
2554
  "select",
2502
2555
  {
2503
2556
  "aria-label": "Blend spacing",
2504
2557
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
2505
2558
  value: o.spacing,
2506
2559
  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))
2560
+ children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx17("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2508
2561
  }
2509
2562
  ),
2510
- o.spacing !== "smoothColor" && /* @__PURE__ */ jsx16(
2563
+ o.spacing !== "smoothColor" && /* @__PURE__ */ jsx17(
2511
2564
  "input",
2512
2565
  {
2513
2566
  type: "number",
@@ -2523,7 +2576,7 @@ function VectorBlendControls({ controller }) {
2523
2576
  }
2524
2577
  }
2525
2578
  ),
2526
- /* @__PURE__ */ jsx16(
2579
+ /* @__PURE__ */ jsx17(
2527
2580
  "button",
2528
2581
  {
2529
2582
  type: "button",
@@ -2536,7 +2589,7 @@ function VectorBlendControls({ controller }) {
2536
2589
  children: "\u293F"
2537
2590
  }
2538
2591
  ),
2539
- /* @__PURE__ */ jsx16(
2592
+ /* @__PURE__ */ jsx17(
2540
2593
  "button",
2541
2594
  {
2542
2595
  type: "button",
@@ -2549,12 +2602,12 @@ function VectorBlendControls({ controller }) {
2549
2602
  children: "\u2307"
2550
2603
  }
2551
2604
  ),
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" })
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" })
2554
2607
  ] });
2555
2608
  }
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";
2609
+ var selectClass12 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2610
+ var numberClass11 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2558
2611
  function VectorStylePanel({ controller, className }) {
2559
2612
  const state = useVectorEditorState(controller);
2560
2613
  const style = getVectorSelectionStyle(state);
@@ -2586,17 +2639,17 @@ function VectorStylePanel({ controller, className }) {
2586
2639
  if (style) controller.setWidthProfile(id);
2587
2640
  else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
2588
2641
  };
2589
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx16("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2642
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx17("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2590
2643
  const arrowRow = (end, label) => {
2591
2644
  const key = end === "start" ? "startArrow" : "endArrow";
2592
2645
  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))
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))
2598
2651
  ] }),
2599
- /* @__PURE__ */ jsx16(
2652
+ /* @__PURE__ */ jsx17(
2600
2653
  "input",
2601
2654
  {
2602
2655
  type: "number",
@@ -2604,7 +2657,7 @@ function VectorStylePanel({ controller, className }) {
2604
2657
  step: 0.25,
2605
2658
  "aria-label": `${label} arrowhead scale`,
2606
2659
  title: "Scale (\xD7 stroke width)",
2607
- className: numberClass10,
2660
+ className: numberClass11,
2608
2661
  value: source[scaleKey],
2609
2662
  onChange: (e) => {
2610
2663
  const n = parseFloat(e.target.value);
@@ -2622,12 +2675,12 @@ function VectorStylePanel({ controller, className }) {
2622
2675
  const paint = source[key];
2623
2676
  const hex = vectorPaintToHex6(paint);
2624
2677
  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 }) }),
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 }) }),
2628
2681
  label
2629
2682
  ] }),
2630
- /* @__PURE__ */ jsx16(
2683
+ /* @__PURE__ */ jsx17(
2631
2684
  "input",
2632
2685
  {
2633
2686
  type: "color",
@@ -2638,18 +2691,18 @@ function VectorStylePanel({ controller, className }) {
2638
2691
  onChange: (e) => apply({ [key]: e.target.value })
2639
2692
  }
2640
2693
  ),
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" })
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" })
2643
2696
  ] });
2644
2697
  };
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" }),
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" }),
2647
2700
  paintRow("fill", "Fill"),
2648
2701
  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(
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(
2653
2706
  "input",
2654
2707
  {
2655
2708
  type: "number",
@@ -2665,29 +2718,29 @@ function VectorStylePanel({ controller, className }) {
2665
2718
  }
2666
2719
  )
2667
2720
  ] }),
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)) }),
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)) }),
2671
2724
  mixed("strokeAlign")
2672
2725
  ] }),
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(
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(
2676
2729
  "select",
2677
2730
  {
2678
2731
  "aria-label": "Width profile",
2679
- className: selectClass11,
2732
+ className: selectClass12,
2680
2733
  value: source.widthProfile,
2681
2734
  onChange: (e) => {
2682
2735
  if (e.target.value !== "custom") setProfile(e.target.value);
2683
2736
  },
2684
2737
  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" })
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" })
2687
2740
  ]
2688
2741
  }
2689
2742
  ),
2690
- /* @__PURE__ */ jsx16(
2743
+ /* @__PURE__ */ jsx17(
2691
2744
  "button",
2692
2745
  {
2693
2746
  type: "button",
@@ -2701,27 +2754,27 @@ function VectorStylePanel({ controller, className }) {
2701
2754
  ),
2702
2755
  mixed("widthProfile")
2703
2756
  ] }),
2704
- source.hasOpenPath && /* @__PURE__ */ jsxs16(Fragment6, { children: [
2757
+ source.hasOpenPath && /* @__PURE__ */ jsxs17(Fragment6, { children: [
2705
2758
  arrowRow("start", "Start"),
2706
2759
  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" })
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" })
2712
2765
  ] })
2713
2766
  ] })
2714
2767
  ] }),
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(
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(
2718
2771
  "input",
2719
2772
  {
2720
2773
  type: "number",
2721
2774
  min: 0,
2722
2775
  step: 1,
2723
2776
  "aria-label": "Corner radius",
2724
- className: numberClass10,
2777
+ className: numberClass11,
2725
2778
  value: Math.round(source.cornerRadius * 100) / 100,
2726
2779
  onChange: (e) => {
2727
2780
  const n = parseFloat(e.target.value);
@@ -2729,12 +2782,12 @@ function VectorStylePanel({ controller, className }) {
2729
2782
  }
2730
2783
  }
2731
2784
  ),
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)) }),
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)) }),
2733
2786
  mixed("cornerRadius")
2734
2787
  ] }),
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(
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(
2738
2791
  "input",
2739
2792
  {
2740
2793
  type: "range",
@@ -2749,9 +2802,9 @@ function VectorStylePanel({ controller, className }) {
2749
2802
  }
2750
2803
  )
2751
2804
  ] }),
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(
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(
2755
2808
  "input",
2756
2809
  {
2757
2810
  type: "range",
@@ -2765,24 +2818,25 @@ function VectorStylePanel({ controller, className }) {
2765
2818
  }
2766
2819
  )
2767
2820
  ] }),
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 })
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 })
2780
2834
  ] });
2781
2835
  }
2782
2836
 
2783
2837
  // src/vector/VectorProviderEffectsPanel.tsx
2784
2838
  import { useState as useState7, useSyncExternalStore as useSyncExternalStore2 } from "react";
2785
- import { Fragment as Fragment7, jsx as jsx17, jsxs as jsxs17 } from "react/jsx-runtime";
2839
+ import { Fragment as Fragment7, jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
2786
2840
  var btn = "rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40";
2787
2841
  function Controls({ controller, group }) {
2788
2842
  const provider = controller.effectsProvider;
@@ -2790,11 +2844,11 @@ function Controls({ controller, group }) {
2790
2844
  const { description, error } = useEffectDescription(provider, tag.effect);
2791
2845
  const [message, setMessage] = useState7(null);
2792
2846
  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(
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(
2798
2852
  EffectParameters,
2799
2853
  {
2800
2854
  description,
@@ -2804,7 +2858,7 @@ function Controls({ controller, group }) {
2804
2858
  onPreset: (v) => run(controller.updateVectorEffect(group.id, v))
2805
2859
  }
2806
2860
  ),
2807
- message && /* @__PURE__ */ jsx17("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2861
+ message && /* @__PURE__ */ jsx18("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2808
2862
  ] });
2809
2863
  }
2810
2864
  function VectorProviderEffectsPanel({ controller, className = "" }) {
@@ -2823,41 +2877,41 @@ function VectorProviderEffectsPanel({ controller, className = "" }) {
2823
2877
  setMessage(null);
2824
2878
  Promise.resolve().then(work).catch((e) => setMessage(e instanceof Error ? e.message : String(e)));
2825
2879
  };
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" })
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" })
2830
2884
  ] }),
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" })
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" })
2837
2891
  ] }),
2838
- /* @__PURE__ */ jsx17(Controls, { controller, group: g })
2892
+ /* @__PURE__ */ jsx18(Controls, { controller, group: g })
2839
2893
  ] }, g.id)),
2840
- groups.length > 0 && !selected.length && /* @__PURE__ */ jsxs17("p", { className: "text-[11px] text-muted-foreground", children: [
2894
+ groups.length > 0 && !selected.length && /* @__PURE__ */ jsxs18("p", { className: "text-[11px] text-muted-foreground", children: [
2841
2895
  "Select an effect group (",
2842
2896
  groups.length,
2843
2897
  " in the drawing) to change it."
2844
2898
  ] }),
2845
- message && /* @__PURE__ */ jsx17("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2899
+ message && /* @__PURE__ */ jsx18("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
2846
2900
  ] });
2847
2901
  }
2848
2902
 
2849
2903
  // src/vector/VectorEditor.tsx
2850
- import { jsx as jsx18, jsxs as jsxs18 } from "react/jsx-runtime";
2904
+ import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
2851
2905
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, effects, className }) {
2852
2906
  useEffects(controller, effects);
2853
2907
  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" })
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" })
2861
2915
  ] })
2862
2916
  ] })
2863
2917
  ] });
@@ -2875,6 +2929,7 @@ export {
2875
2929
  VectorGradientAnnotator,
2876
2930
  VectorGradientDefs,
2877
2931
  VectorGradientEditor,
2932
+ VectorLensPanel,
2878
2933
  VectorMaskPanel,
2879
2934
  VectorMaskedNodes,
2880
2935
  VectorProviderEffectsPanel,