@pixerate/editor-react 0.68.0 → 0.69.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.
@@ -187,7 +187,8 @@ function VectorGradientAnnotator({ annotator: g, accentColor = THEME2.accent })
187
187
  }
188
188
  function VectorFilterDefs({ filters }) {
189
189
  if (!filters.length) return null;
190
- const primitive = (p, key) => React2.createElement(p.tag, { key, ...p.attrs }, p.children?.map(primitive));
190
+ const props = (attrs) => Object.fromEntries(Object.entries(attrs).map(([k, v]) => [k.replace(/-([a-z])/g, (_, c) => c.toUpperCase()), v]));
191
+ const primitive = (p, key) => React2.createElement(p.tag, { key, ...props(p.attrs) }, p.children?.map(primitive));
191
192
  return /* @__PURE__ */ jsx2("defs", { children: filters.map((f) => /* @__PURE__ */ jsx2("filter", { id: f.id, filterUnits: "userSpaceOnUse", x: f.x, y: f.y, width: f.width, height: f.height, colorInterpolationFilters: "sRGB", children: f.primitives.map(primitive) }, f.id)) });
192
193
  }
193
194
  var blendStyle = (blendMode) => blendMode ? { mixBlendMode: blendMode } : void 0;
@@ -197,11 +198,12 @@ function VectorSceneNodes({ nodes }) {
197
198
  "g",
198
199
  {
199
200
  "data-vector-id": node.id,
201
+ "data-vector-mask": node.mask?.type,
200
202
  transform: node.transform,
201
203
  opacity: node.opacity < 1 ? node.opacity : void 0,
202
204
  filter: node.filter,
203
205
  style: blendStyle(node.blendMode),
204
- children: /* @__PURE__ */ jsx2(VectorSceneNodes, { nodes: node.children })
206
+ children: node.mask ? /* @__PURE__ */ jsx2(VectorMaskedNodes, { mask: node.mask, nodes: node.children }) : /* @__PURE__ */ jsx2(VectorSceneNodes, { nodes: node.children })
205
207
  },
206
208
  node.id
207
209
  ) : node.layers ? (
@@ -233,6 +235,13 @@ function VectorSceneNodes({ nodes }) {
233
235
  )
234
236
  ) });
235
237
  }
238
+ function VectorMaskedNodes({ mask, nodes }) {
239
+ const b = mask.bounds;
240
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
241
+ /* @__PURE__ */ jsx2("defs", { children: mask.clip ? /* @__PURE__ */ jsx2("clipPath", { id: mask.id, children: mask.clip.map((c, i) => /* @__PURE__ */ jsx2("path", { d: c.d, clipRule: c.clipRule }, i)) }) : b && /* @__PURE__ */ jsx2("mask", { id: mask.id, maskUnits: "userSpaceOnUse", x: b.x, y: b.y, width: b.width, height: b.height, style: mask.type === "alpha" ? { maskType: "alpha" } : void 0, children: mask.filter ? /* @__PURE__ */ jsx2("g", { filter: mask.filter, children: /* @__PURE__ */ jsx2(VectorSceneNodes, { nodes: mask.nodes ?? [] }) }) : /* @__PURE__ */ jsx2(VectorSceneNodes, { nodes: mask.nodes ?? [] }) }) }),
242
+ /* @__PURE__ */ jsx2("g", { "data-vector-masked": "", clipPath: mask.clip ? `url(#${mask.id})` : void 0, mask: mask.clip ? void 0 : `url(#${mask.id})`, children: /* @__PURE__ */ jsx2(VectorSceneNodes, { nodes }) })
243
+ ] });
244
+ }
236
245
  function VectorPaintPath({
237
246
  paint,
238
247
  d,
@@ -460,6 +469,21 @@ function VectorCanvas({
460
469
  /* @__PURE__ */ jsx2("circle", { cx: w.x, cy: w.y, r: THEME2.cornerWidgetRadius + (w.hovered ? 1 : 0), fill: THEME2.handleFill, stroke: accentColor }),
461
470
  /* @__PURE__ */ jsx2("circle", { cx: w.x, cy: w.y, r: 1.5, fill: accentColor })
462
471
  ] }, `corner:${w.ref.nodeId}:${w.ref.subpath}:${w.ref.index}`)),
472
+ overlay.masks.map((m) => /* @__PURE__ */ jsx2(
473
+ "path",
474
+ {
475
+ "data-vector-mask-indicator": m.id,
476
+ "data-mask-type": m.type,
477
+ "data-inverted": m.invert || void 0,
478
+ d: m.d,
479
+ fill: "none",
480
+ stroke: THEME2.mask,
481
+ strokeWidth: m.editing ? 1.5 : 1,
482
+ strokeDasharray: m.editing ? void 0 : THEME2.maskDash,
483
+ opacity: m.enabled ? 1 : 0.5
484
+ },
485
+ `mask:${m.id}`
486
+ )),
463
487
  overlay.blend.spines.map((sp) => /* @__PURE__ */ jsxs2("g", { "data-vector-blend-spine": sp.id, "data-replaced": sp.replaced || void 0, children: [
464
488
  /* @__PURE__ */ jsx2("path", { d: sp.d, fill: "none", stroke: accentColor, strokeDasharray: "4 3" }),
465
489
  sp.keys.map((k, i) => /* @__PURE__ */ jsx2("circle", { cx: k.x, cy: k.y, r: THEME2.handleRadius, fill: THEME2.handleFill, stroke: accentColor }, i))
@@ -607,6 +631,7 @@ import {
607
631
  applyVectorBlendOptions,
608
632
  getVectorBlendControls,
609
633
  getVectorSelectionStyle,
634
+ isVectorMaskGroup,
610
635
  widthProfilePreset,
611
636
  widthProfilePresetId,
612
637
  vectorPaintToHex as vectorPaintToHex2,
@@ -1080,6 +1105,124 @@ function VectorEnvelopePanel({ controller, className }) {
1080
1105
  ] });
1081
1106
  }
1082
1107
 
1108
+ // src/vector/VectorMaskPanel.tsx
1109
+ import { VECTOR_MASK_TYPE_OPTIONS, getVectorMaskPanel } from "@pixerate/editor/vector";
1110
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1111
+ var buttonClass4 = "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";
1112
+ var selectClass4 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1113
+ function VectorMaskPanel({ controller, className }) {
1114
+ const state = useVectorEditorState(controller);
1115
+ const panel = getVectorMaskPanel(state);
1116
+ if (!panel) return null;
1117
+ const g = panel.group;
1118
+ const ids = g ? [g.id] : void 0;
1119
+ return /* @__PURE__ */ jsxs6("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-mask-panel": "", "aria-label": "Mask", children: [
1120
+ /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-1", children: [
1121
+ /* @__PURE__ */ jsx6("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Mask" }),
1122
+ /* @__PURE__ */ jsx6(
1123
+ "button",
1124
+ {
1125
+ type: "button",
1126
+ "data-vector-mask-action": "clip",
1127
+ className: buttonClass4,
1128
+ title: panel.makeHint ?? "Make Clipping Mask: the topmost object clips the others",
1129
+ "aria-label": "Make Clipping Mask",
1130
+ disabled: !panel.canMake,
1131
+ onClick: () => controller.makeMask("clip"),
1132
+ children: "Clip"
1133
+ }
1134
+ ),
1135
+ /* @__PURE__ */ jsx6(
1136
+ "button",
1137
+ {
1138
+ type: "button",
1139
+ "data-vector-mask-action": "opacity",
1140
+ className: buttonClass4,
1141
+ title: panel.makeHint ?? "Make Opacity Mask: the topmost object fades the others by its brightness",
1142
+ "aria-label": "Make Opacity Mask",
1143
+ disabled: !panel.canMake,
1144
+ onClick: () => controller.makeMask("luminance"),
1145
+ children: "Opacity"
1146
+ }
1147
+ ),
1148
+ /* @__PURE__ */ jsx6(
1149
+ "button",
1150
+ {
1151
+ type: "button",
1152
+ "data-vector-mask-action": "release",
1153
+ className: buttonClass4,
1154
+ title: "Release: the mask goes back on top and draws as itself",
1155
+ "aria-label": "Release Mask",
1156
+ disabled: !g,
1157
+ onClick: () => controller.releaseMask({ ids }),
1158
+ children: "Release"
1159
+ }
1160
+ )
1161
+ ] }),
1162
+ g && /* @__PURE__ */ jsxs6("div", { className: "space-y-2", "data-vector-mask-group": g.id, "data-mask-type": g.type, children: [
1163
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1", children: [
1164
+ /* @__PURE__ */ jsx6(
1165
+ "select",
1166
+ {
1167
+ "aria-label": "Mask type",
1168
+ className: selectClass4,
1169
+ value: g.type,
1170
+ title: VECTOR_MASK_TYPE_OPTIONS.find((o) => o.value === g.type)?.title,
1171
+ onChange: (e) => controller.setMaskOptions({ type: e.target.value }, { ids }),
1172
+ children: VECTOR_MASK_TYPE_OPTIONS.map((o) => /* @__PURE__ */ jsx6("option", { value: o.value, title: o.title, children: o.label }, o.value))
1173
+ }
1174
+ ),
1175
+ /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Show the contents where the mask hides them", children: [
1176
+ /* @__PURE__ */ jsx6("input", { type: "checkbox", "aria-label": "Invert mask", checked: g.invert, onChange: (e) => controller.setMaskOptions({ invert: e.target.checked }, { ids }) }),
1177
+ "Invert"
1178
+ ] })
1179
+ ] }),
1180
+ /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-1", children: [
1181
+ /* @__PURE__ */ jsx6(
1182
+ "button",
1183
+ {
1184
+ type: "button",
1185
+ "data-vector-mask-action": "edit-mask",
1186
+ className: buttonClass4,
1187
+ "aria-pressed": g.editing === "mask",
1188
+ title: "Edit Mask: select the mask (a path opens for anchor editing)",
1189
+ "aria-label": "Edit Mask",
1190
+ onClick: () => controller.editMask({ id: g.id }),
1191
+ children: "Edit mask"
1192
+ }
1193
+ ),
1194
+ /* @__PURE__ */ jsx6(
1195
+ "button",
1196
+ {
1197
+ type: "button",
1198
+ "data-vector-mask-action": "edit-contents",
1199
+ className: buttonClass4,
1200
+ "aria-pressed": g.editing === "contents",
1201
+ title: "Edit Contents: select what the mask masks",
1202
+ "aria-label": "Edit Contents",
1203
+ disabled: !g.contents.length,
1204
+ onClick: () => controller.editMaskContents({ id: g.id }),
1205
+ children: "Contents"
1206
+ }
1207
+ ),
1208
+ /* @__PURE__ */ jsx6(
1209
+ "button",
1210
+ {
1211
+ type: "button",
1212
+ "data-vector-mask-action": "toggle",
1213
+ className: buttonClass4,
1214
+ "aria-pressed": g.enabled,
1215
+ title: g.enabled ? "Turn the mask off (hide it): the contents draw whole" : "Turn the mask on",
1216
+ "aria-label": "Mask on",
1217
+ onClick: () => controller.setMaskOptions({ enabled: !g.enabled }, { ids }),
1218
+ children: g.enabled ? "On" : "Off"
1219
+ }
1220
+ )
1221
+ ] })
1222
+ ] })
1223
+ ] });
1224
+ }
1225
+
1083
1226
  // src/vector/VectorGradientEditor.tsx
1084
1227
  import {
1085
1228
  VECTOR_GRADIENT_TYPES,
@@ -1093,10 +1236,10 @@ import {
1093
1236
  getVectorGradientEditor,
1094
1237
  vectorPaintToHex
1095
1238
  } from "@pixerate/editor/vector";
1096
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1097
- var selectClass4 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1239
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1240
+ var selectClass5 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1098
1241
  var numberClass4 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
1099
- var buttonClass4 = "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";
1242
+ var buttonClass5 = "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";
1100
1243
  var percent = (v) => Math.round(v * 1e3) / 10;
1101
1244
  function VectorGradientEditor({ controller, className }) {
1102
1245
  const state = useVectorEditorState(controller);
@@ -1111,23 +1254,23 @@ function VectorGradientEditor({ controller, className }) {
1111
1254
  controller.setStyle({ [ed.target]: vectorPaintToHex(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
1112
1255
  } else controller.setGradient({ type: value });
1113
1256
  };
1114
- return /* @__PURE__ */ jsxs6("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
1115
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5", children: [
1116
- /* @__PURE__ */ jsx6("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
1117
- ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx6("button", { type: "button", className: buttonClass4, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
1118
- ed.mixed && /* @__PURE__ */ jsx6("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1257
+ return /* @__PURE__ */ jsxs7("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
1258
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1.5", children: [
1259
+ /* @__PURE__ */ jsx7("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
1260
+ ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass5, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
1261
+ ed.mixed && /* @__PURE__ */ jsx7("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1119
1262
  ] }),
1120
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1.5", children: [
1121
- /* @__PURE__ */ jsx6("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
1122
- /* @__PURE__ */ jsxs6("select", { "aria-label": "Gradient type", className: selectClass4, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
1123
- /* @__PURE__ */ jsx6("option", { value: "solid", children: "Solid" }),
1124
- VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx6("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
1263
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1.5", children: [
1264
+ /* @__PURE__ */ jsx7("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
1265
+ /* @__PURE__ */ jsxs7("select", { "aria-label": "Gradient type", className: selectClass5, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
1266
+ /* @__PURE__ */ jsx7("option", { value: "solid", children: "Solid" }),
1267
+ VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx7("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
1125
1268
  ] }),
1126
- /* @__PURE__ */ jsx6("button", { type: "button", className: buttonClass4, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
1269
+ /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass5, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
1127
1270
  ] }),
1128
- g && /* @__PURE__ */ jsxs6(Fragment2, { children: [
1129
- /* @__PURE__ */ jsxs6("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
1130
- /* @__PURE__ */ jsx6(
1271
+ g && /* @__PURE__ */ jsxs7(Fragment2, { children: [
1272
+ /* @__PURE__ */ jsxs7("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
1273
+ /* @__PURE__ */ jsx7(
1131
1274
  "div",
1132
1275
  {
1133
1276
  role: "button",
@@ -1141,7 +1284,7 @@ function VectorGradientEditor({ controller, className }) {
1141
1284
  }
1142
1285
  }
1143
1286
  ),
1144
- ed.stops.map((s) => /* @__PURE__ */ jsx6(
1287
+ ed.stops.map((s) => /* @__PURE__ */ jsx7(
1145
1288
  "button",
1146
1289
  {
1147
1290
  type: "button",
@@ -1155,8 +1298,8 @@ function VectorGradientEditor({ controller, className }) {
1155
1298
  s.index
1156
1299
  ))
1157
1300
  ] }),
1158
- sel && /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
1159
- /* @__PURE__ */ jsx6(
1301
+ sel && /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
1302
+ /* @__PURE__ */ jsx7(
1160
1303
  "input",
1161
1304
  {
1162
1305
  type: "color",
@@ -1167,9 +1310,9 @@ function VectorGradientEditor({ controller, className }) {
1167
1310
  onBlur: end
1168
1311
  }
1169
1312
  ),
1170
- /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
1313
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
1171
1314
  "Opacity",
1172
- /* @__PURE__ */ jsx6(
1315
+ /* @__PURE__ */ jsx7(
1173
1316
  "input",
1174
1317
  {
1175
1318
  type: "number",
@@ -1186,9 +1329,9 @@ function VectorGradientEditor({ controller, className }) {
1186
1329
  }
1187
1330
  )
1188
1331
  ] }),
1189
- /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
1332
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
1190
1333
  "At",
1191
- /* @__PURE__ */ jsx6(
1334
+ /* @__PURE__ */ jsx7(
1192
1335
  "input",
1193
1336
  {
1194
1337
  type: "number",
@@ -1205,9 +1348,9 @@ function VectorGradientEditor({ controller, className }) {
1205
1348
  }
1206
1349
  )
1207
1350
  ] }),
1208
- !sel.last && /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
1351
+ !sel.last && /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
1209
1352
  "Midpoint",
1210
- /* @__PURE__ */ jsx6(
1353
+ /* @__PURE__ */ jsx7(
1211
1354
  "input",
1212
1355
  {
1213
1356
  type: "number",
@@ -1224,15 +1367,15 @@ function VectorGradientEditor({ controller, className }) {
1224
1367
  }
1225
1368
  )
1226
1369
  ] }),
1227
- /* @__PURE__ */ jsx6("button", { type: "button", className: buttonClass4, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
1370
+ /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass5, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
1228
1371
  ] }),
1229
- /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1230
- /* @__PURE__ */ jsx6("span", { className: "w-16", children: "Colours" }),
1231
- /* @__PURE__ */ jsx6("select", { "aria-label": "Gradient interpolation", className: selectClass4, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx6("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
1372
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1373
+ /* @__PURE__ */ jsx7("span", { className: "w-16", children: "Colours" }),
1374
+ /* @__PURE__ */ jsx7("select", { "aria-label": "Gradient interpolation", className: selectClass5, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx7("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
1232
1375
  ] }),
1233
- /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1234
- /* @__PURE__ */ jsx6("span", { className: "w-16", children: "Smooth" }),
1235
- /* @__PURE__ */ jsx6(
1376
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1377
+ /* @__PURE__ */ jsx7("span", { className: "w-16", children: "Smooth" }),
1378
+ /* @__PURE__ */ jsx7(
1236
1379
  "input",
1237
1380
  {
1238
1381
  type: "range",
@@ -1248,23 +1391,23 @@ function VectorGradientEditor({ controller, className }) {
1248
1391
  }
1249
1392
  )
1250
1393
  ] }),
1251
- /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1252
- /* @__PURE__ */ jsx6("span", { className: "w-16", children: "Spread" }),
1253
- /* @__PURE__ */ jsx6("select", { "aria-label": "Gradient spread", className: selectClass4, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx6("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
1394
+ /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1395
+ /* @__PURE__ */ jsx7("span", { className: "w-16", children: "Spread" }),
1396
+ /* @__PURE__ */ jsx7("select", { "aria-label": "Gradient spread", className: selectClass5, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx7("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
1254
1397
  ] }),
1255
- ed.target === "stroke" && /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1256
- /* @__PURE__ */ jsx6("span", { className: "w-16", children: "On stroke" }),
1257
- /* @__PURE__ */ jsx6("select", { "aria-label": "Gradient on stroke", className: selectClass4, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx6("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
1398
+ ed.target === "stroke" && /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1399
+ /* @__PURE__ */ jsx7("span", { className: "w-16", children: "On stroke" }),
1400
+ /* @__PURE__ */ jsx7("select", { "aria-label": "Gradient on stroke", className: selectClass5, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx7("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
1258
1401
  ] })
1259
1402
  ] })
1260
1403
  ] });
1261
1404
  }
1262
1405
 
1263
1406
  // src/vector/VectorToolbar.tsx
1264
- import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1407
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1265
1408
  var VECTOR_SNAP_TITLE = "Snap to objects";
1266
1409
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
1267
- var buttonClass5 = "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";
1410
+ var buttonClass6 = "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";
1268
1411
  function VectorToolbar({
1269
1412
  controller,
1270
1413
  tools = VECTOR_TOOLS,
@@ -1281,16 +1424,17 @@ function VectorToolbar({
1281
1424
  const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
1282
1425
  const editing = state.editing;
1283
1426
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
1427
+ const maskSelected = state.selection.some((id) => isVectorMaskGroup(controller.getNode(id)));
1284
1428
  const [live, setLive] = React4.useState(false);
1285
1429
  const booleanDisabled = live ? count < 1 : count < 2;
1286
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass5, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
1287
- return /* @__PURE__ */ jsxs7("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
1288
- /* @__PURE__ */ jsx7("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx7(
1430
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass6, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
1431
+ return /* @__PURE__ */ jsxs8("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
1432
+ /* @__PURE__ */ jsx8("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx8(
1289
1433
  "button",
1290
1434
  {
1291
1435
  type: "button",
1292
1436
  "data-vector-tool": tool,
1293
- className: buttonClass5,
1437
+ className: buttonClass6,
1294
1438
  "aria-pressed": state.tool === tool,
1295
1439
  title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
1296
1440
  "aria-label": vectorToolLabel(tool),
@@ -1299,16 +1443,16 @@ function VectorToolbar({
1299
1443
  },
1300
1444
  tool
1301
1445
  )) }),
1302
- showHistory && /* @__PURE__ */ jsxs7("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
1446
+ showHistory && /* @__PURE__ */ jsxs8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
1303
1447
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
1304
1448
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
1305
1449
  ] }),
1306
- showSnap && /* @__PURE__ */ jsx7("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ jsx7(
1450
+ showSnap && /* @__PURE__ */ jsx8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ jsx8(
1307
1451
  "button",
1308
1452
  {
1309
1453
  type: "button",
1310
1454
  "data-vector-snap": "",
1311
- className: buttonClass5,
1455
+ className: buttonClass6,
1312
1456
  "aria-pressed": state.snap.objects,
1313
1457
  title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
1314
1458
  "aria-label": VECTOR_SNAP_TITLE,
@@ -1316,13 +1460,13 @@ function VectorToolbar({
1316
1460
  children: "Snap"
1317
1461
  }
1318
1462
  ) }),
1319
- showPathEdit && /* @__PURE__ */ jsxs7("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
1320
- VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx7(
1463
+ showPathEdit && /* @__PURE__ */ jsxs8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
1464
+ VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx8(
1321
1465
  "button",
1322
1466
  {
1323
1467
  type: "button",
1324
1468
  "data-vector-path-action": a.id,
1325
- className: buttonClass5,
1469
+ className: buttonClass6,
1326
1470
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
1327
1471
  "aria-label": a.title,
1328
1472
  disabled: !a.enabled(count, points),
@@ -1331,12 +1475,12 @@ function VectorToolbar({
1331
1475
  },
1332
1476
  a.id
1333
1477
  )),
1334
- VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx7(
1478
+ VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx8(
1335
1479
  "button",
1336
1480
  {
1337
1481
  type: "button",
1338
1482
  "data-vector-edit": t.key,
1339
- className: buttonClass5,
1483
+ className: buttonClass6,
1340
1484
  "aria-pressed": editing[t.key],
1341
1485
  title: t.title,
1342
1486
  "aria-label": t.title,
@@ -1345,9 +1489,9 @@ function VectorToolbar({
1345
1489
  },
1346
1490
  t.key
1347
1491
  )),
1348
- state.tool === "pencil" && /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
1492
+ state.tool === "pencil" && /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
1349
1493
  "Smoothing",
1350
- /* @__PURE__ */ jsx7(
1494
+ /* @__PURE__ */ jsx8(
1351
1495
  "input",
1352
1496
  {
1353
1497
  type: "range",
@@ -1361,9 +1505,9 @@ function VectorToolbar({
1361
1505
  }
1362
1506
  )
1363
1507
  ] }),
1364
- state.tool === "eraser" && /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
1508
+ state.tool === "eraser" && /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
1365
1509
  "Size",
1366
- /* @__PURE__ */ jsx7(
1510
+ /* @__PURE__ */ jsx8(
1367
1511
  "input",
1368
1512
  {
1369
1513
  type: "number",
@@ -1379,12 +1523,12 @@ function VectorToolbar({
1379
1523
  }
1380
1524
  )
1381
1525
  ] }),
1382
- state.tool === "paintBucket" && /* @__PURE__ */ jsx7(
1526
+ state.tool === "paintBucket" && /* @__PURE__ */ jsx8(
1383
1527
  "button",
1384
1528
  {
1385
1529
  type: "button",
1386
1530
  "data-vector-fill-gap": "",
1387
- className: buttonClass5,
1531
+ className: buttonClass6,
1388
1532
  "aria-pressed": editing.fillGap > 0,
1389
1533
  title: "Close small gaps between path ends when filling",
1390
1534
  "aria-label": "Close gaps",
@@ -1393,12 +1537,12 @@ function VectorToolbar({
1393
1537
  }
1394
1538
  )
1395
1539
  ] }),
1396
- showDistort && /* @__PURE__ */ jsx7("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx7(
1540
+ showDistort && /* @__PURE__ */ jsx8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx8(
1397
1541
  "button",
1398
1542
  {
1399
1543
  type: "button",
1400
1544
  "data-vector-free-distort": "",
1401
- className: buttonClass5,
1545
+ className: buttonClass6,
1402
1546
  "aria-pressed": state.freeDistort,
1403
1547
  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)`,
1404
1548
  "aria-label": "Free Distort",
@@ -1406,15 +1550,15 @@ function VectorToolbar({
1406
1550
  children: "Distort"
1407
1551
  }
1408
1552
  ) }),
1409
- showBlend && /* @__PURE__ */ jsx7(VectorBlendControls, { controller }),
1410
- showArrange && /* @__PURE__ */ jsxs7(Fragment3, { children: [
1411
- /* @__PURE__ */ jsxs7("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
1412
- /* @__PURE__ */ jsx7(
1553
+ showBlend && /* @__PURE__ */ jsx8(VectorBlendControls, { controller }),
1554
+ showArrange && /* @__PURE__ */ jsxs8(Fragment3, { children: [
1555
+ /* @__PURE__ */ jsxs8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
1556
+ /* @__PURE__ */ jsx8(
1413
1557
  "button",
1414
1558
  {
1415
1559
  type: "button",
1416
1560
  "data-vector-live-boolean": "",
1417
- className: buttonClass5,
1561
+ className: buttonClass6,
1418
1562
  "aria-pressed": live,
1419
1563
  title: "Live booleans: keep the shapes editable",
1420
1564
  "aria-label": "Live booleans",
@@ -1427,14 +1571,16 @@ function VectorToolbar({
1427
1571
  act("\u2229", "Intersect", () => controller.booleanOp("intersect", { live }), booleanDisabled),
1428
1572
  act("\u22BB", "Exclude", () => controller.booleanOp("exclude", { live }), booleanDisabled),
1429
1573
  act("G", "Group", () => controller.group(), count < 1),
1430
- act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
1574
+ act("U", "Ungroup", () => controller.ungroup(), !groupSelected),
1575
+ act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
1576
+ act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
1431
1577
  ] }),
1432
- /* @__PURE__ */ jsx7("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx7(
1578
+ /* @__PURE__ */ jsx8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx8(
1433
1579
  "button",
1434
1580
  {
1435
1581
  type: "button",
1436
1582
  "data-vector-pathfinder": a.id,
1437
- className: buttonClass5,
1583
+ className: buttonClass6,
1438
1584
  title: a.title,
1439
1585
  "aria-label": a.title,
1440
1586
  disabled: count < a.min,
@@ -1443,7 +1589,7 @@ function VectorToolbar({
1443
1589
  },
1444
1590
  a.id
1445
1591
  )) }),
1446
- /* @__PURE__ */ jsxs7("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
1592
+ /* @__PURE__ */ jsxs8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
1447
1593
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
1448
1594
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
1449
1595
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -1461,19 +1607,19 @@ function VectorBlendControls({ controller }) {
1461
1607
  const blend = getVectorBlendControls(state);
1462
1608
  const o = blend.options;
1463
1609
  const selected = blend.ids.length > 0;
1464
- return /* @__PURE__ */ jsxs7("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
1465
- /* @__PURE__ */ jsx7("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass5, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
1466
- /* @__PURE__ */ jsx7(
1610
+ return /* @__PURE__ */ jsxs8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
1611
+ /* @__PURE__ */ jsx8("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass6, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
1612
+ /* @__PURE__ */ jsx8(
1467
1613
  "select",
1468
1614
  {
1469
1615
  "aria-label": "Blend spacing",
1470
1616
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
1471
1617
  value: o.spacing,
1472
1618
  onChange: (e) => applyVectorBlendOptions(controller, { spacing: e.target.value }),
1473
- children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx7("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
1619
+ children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx8("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
1474
1620
  }
1475
1621
  ),
1476
- o.spacing !== "smoothColor" && /* @__PURE__ */ jsx7(
1622
+ o.spacing !== "smoothColor" && /* @__PURE__ */ jsx8(
1477
1623
  "input",
1478
1624
  {
1479
1625
  type: "number",
@@ -1489,12 +1635,12 @@ function VectorBlendControls({ controller }) {
1489
1635
  }
1490
1636
  }
1491
1637
  ),
1492
- /* @__PURE__ */ jsx7(
1638
+ /* @__PURE__ */ jsx8(
1493
1639
  "button",
1494
1640
  {
1495
1641
  type: "button",
1496
1642
  "data-vector-blend-action": "orient",
1497
- className: buttonClass5,
1643
+ className: buttonClass6,
1498
1644
  "aria-pressed": o.orientToSpine,
1499
1645
  title: "Orient steps to the spine",
1500
1646
  "aria-label": "Orient to spine",
@@ -1502,12 +1648,12 @@ function VectorBlendControls({ controller }) {
1502
1648
  children: "\u293F"
1503
1649
  }
1504
1650
  ),
1505
- /* @__PURE__ */ jsx7(
1651
+ /* @__PURE__ */ jsx8(
1506
1652
  "button",
1507
1653
  {
1508
1654
  type: "button",
1509
1655
  "data-vector-blend-action": "spine",
1510
- className: buttonClass5,
1656
+ className: buttonClass6,
1511
1657
  title: "Replace Spine (select a blend and a path)",
1512
1658
  "aria-label": "Replace Spine",
1513
1659
  disabled: !blend.canReplaceSpine,
@@ -1515,11 +1661,11 @@ function VectorBlendControls({ controller }) {
1515
1661
  children: "\u2307"
1516
1662
  }
1517
1663
  ),
1518
- /* @__PURE__ */ jsx7("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass5, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
1519
- /* @__PURE__ */ jsx7("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass5, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
1664
+ /* @__PURE__ */ jsx8("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass6, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
1665
+ /* @__PURE__ */ jsx8("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass6, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
1520
1666
  ] });
1521
1667
  }
1522
- var selectClass5 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1668
+ var selectClass6 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1523
1669
  var numberClass5 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
1524
1670
  function VectorStylePanel({ controller, className }) {
1525
1671
  const state = useVectorEditorState(controller);
@@ -1551,17 +1697,17 @@ function VectorStylePanel({ controller, className }) {
1551
1697
  if (style) controller.setWidthProfile(id);
1552
1698
  else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
1553
1699
  };
1554
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx7("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
1700
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx8("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
1555
1701
  const arrowRow = (end, label) => {
1556
1702
  const key = end === "start" ? "startArrow" : "endArrow";
1557
1703
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
1558
- return /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
1559
- /* @__PURE__ */ jsx7("span", { className: "w-16 text-xs text-foreground", children: label }),
1560
- /* @__PURE__ */ jsxs7("select", { "aria-label": `${label} arrowhead`, className: selectClass5, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
1561
- /* @__PURE__ */ jsx7("option", { value: "", children: "None" }),
1562
- VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx7("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
1704
+ return /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
1705
+ /* @__PURE__ */ jsx8("span", { className: "w-16 text-xs text-foreground", children: label }),
1706
+ /* @__PURE__ */ jsxs8("select", { "aria-label": `${label} arrowhead`, className: selectClass6, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
1707
+ /* @__PURE__ */ jsx8("option", { value: "", children: "None" }),
1708
+ VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx8("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
1563
1709
  ] }),
1564
- /* @__PURE__ */ jsx7(
1710
+ /* @__PURE__ */ jsx8(
1565
1711
  "input",
1566
1712
  {
1567
1713
  type: "number",
@@ -1587,12 +1733,12 @@ function VectorStylePanel({ controller, className }) {
1587
1733
  const paint = source[key];
1588
1734
  const hex = vectorPaintToHex2(paint);
1589
1735
  const on = paint !== null && paint !== "none";
1590
- return /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
1591
- /* @__PURE__ */ jsxs7("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
1592
- /* @__PURE__ */ jsx7("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
1736
+ return /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
1737
+ /* @__PURE__ */ jsxs8("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
1738
+ /* @__PURE__ */ jsx8("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
1593
1739
  label
1594
1740
  ] }),
1595
- /* @__PURE__ */ jsx7(
1741
+ /* @__PURE__ */ jsx8(
1596
1742
  "input",
1597
1743
  {
1598
1744
  type: "color",
@@ -1603,18 +1749,18 @@ function VectorStylePanel({ controller, className }) {
1603
1749
  onChange: (e) => apply({ [key]: e.target.value })
1604
1750
  }
1605
1751
  ),
1606
- on && !hex && /* @__PURE__ */ jsx7("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
1607
- style?.mixed[key] && /* @__PURE__ */ jsx7("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1752
+ on && !hex && /* @__PURE__ */ jsx8("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
1753
+ style?.mixed[key] && /* @__PURE__ */ jsx8("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1608
1754
  ] });
1609
1755
  };
1610
- return /* @__PURE__ */ jsxs7("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
1611
- /* @__PURE__ */ jsx7("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
1756
+ return /* @__PURE__ */ jsxs8("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
1757
+ /* @__PURE__ */ jsx8("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
1612
1758
  paintRow("fill", "Fill"),
1613
1759
  paintRow("stroke", "Stroke"),
1614
- style && /* @__PURE__ */ jsx7(VectorGradientEditor, { controller }),
1615
- /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1616
- /* @__PURE__ */ jsx7("span", { className: "w-16", children: "Width" }),
1617
- /* @__PURE__ */ jsx7(
1760
+ style && /* @__PURE__ */ jsx8(VectorGradientEditor, { controller }),
1761
+ /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1762
+ /* @__PURE__ */ jsx8("span", { className: "w-16", children: "Width" }),
1763
+ /* @__PURE__ */ jsx8(
1618
1764
  "input",
1619
1765
  {
1620
1766
  type: "number",
@@ -1630,33 +1776,33 @@ function VectorStylePanel({ controller, className }) {
1630
1776
  }
1631
1777
  )
1632
1778
  ] }),
1633
- /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
1634
- /* @__PURE__ */ jsx7("span", { className: "w-16", children: "Align" }),
1635
- /* @__PURE__ */ jsx7("select", { "aria-label": "Stroke alignment", className: selectClass5, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS2).map((a) => /* @__PURE__ */ jsx7("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS2[a] }, a)) }),
1779
+ /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
1780
+ /* @__PURE__ */ jsx8("span", { className: "w-16", children: "Align" }),
1781
+ /* @__PURE__ */ jsx8("select", { "aria-label": "Stroke alignment", className: selectClass6, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS2).map((a) => /* @__PURE__ */ jsx8("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS2[a] }, a)) }),
1636
1782
  mixed("strokeAlign")
1637
1783
  ] }),
1638
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
1639
- /* @__PURE__ */ jsx7("span", { className: "w-16", children: "Profile" }),
1640
- /* @__PURE__ */ jsxs7(
1784
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
1785
+ /* @__PURE__ */ jsx8("span", { className: "w-16", children: "Profile" }),
1786
+ /* @__PURE__ */ jsxs8(
1641
1787
  "select",
1642
1788
  {
1643
1789
  "aria-label": "Width profile",
1644
- className: selectClass5,
1790
+ className: selectClass6,
1645
1791
  value: source.widthProfile,
1646
1792
  onChange: (e) => {
1647
1793
  if (e.target.value !== "custom") setProfile(e.target.value);
1648
1794
  },
1649
1795
  children: [
1650
- VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx7("option", { value: p.id, children: p.label }, p.id)),
1651
- source.widthProfile === "custom" && /* @__PURE__ */ jsx7("option", { value: "custom", disabled: true, children: "Custom" })
1796
+ VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx8("option", { value: p.id, children: p.label }, p.id)),
1797
+ source.widthProfile === "custom" && /* @__PURE__ */ jsx8("option", { value: "custom", disabled: true, children: "Custom" })
1652
1798
  ]
1653
1799
  }
1654
1800
  ),
1655
- /* @__PURE__ */ jsx7(
1801
+ /* @__PURE__ */ jsx8(
1656
1802
  "button",
1657
1803
  {
1658
1804
  type: "button",
1659
- className: buttonClass5,
1805
+ className: buttonClass6,
1660
1806
  title: "Reset to uniform width",
1661
1807
  "aria-label": "Reset to uniform width",
1662
1808
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -1666,20 +1812,20 @@ function VectorStylePanel({ controller, className }) {
1666
1812
  ),
1667
1813
  mixed("widthProfile")
1668
1814
  ] }),
1669
- source.hasOpenPath && /* @__PURE__ */ jsxs7(Fragment3, { children: [
1815
+ source.hasOpenPath && /* @__PURE__ */ jsxs8(Fragment3, { children: [
1670
1816
  arrowRow("start", "Start"),
1671
1817
  arrowRow("end", "End"),
1672
- (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
1673
- /* @__PURE__ */ jsx7("span", { className: "w-16", children: "Heads" }),
1674
- /* @__PURE__ */ jsxs7("select", { "aria-label": "Arrowhead alignment", className: selectClass5, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
1675
- /* @__PURE__ */ jsx7("option", { value: "extend", children: "Tip past end" }),
1676
- /* @__PURE__ */ jsx7("option", { value: "tip", children: "Tip at end" })
1818
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
1819
+ /* @__PURE__ */ jsx8("span", { className: "w-16", children: "Heads" }),
1820
+ /* @__PURE__ */ jsxs8("select", { "aria-label": "Arrowhead alignment", className: selectClass6, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
1821
+ /* @__PURE__ */ jsx8("option", { value: "extend", children: "Tip past end" }),
1822
+ /* @__PURE__ */ jsx8("option", { value: "tip", children: "Tip at end" })
1677
1823
  ] })
1678
1824
  ] })
1679
1825
  ] }),
1680
- source.corners > 0 && /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
1681
- /* @__PURE__ */ jsx7("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
1682
- /* @__PURE__ */ jsx7(
1826
+ source.corners > 0 && /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
1827
+ /* @__PURE__ */ jsx8("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
1828
+ /* @__PURE__ */ jsx8(
1683
1829
  "input",
1684
1830
  {
1685
1831
  type: "number",
@@ -1694,12 +1840,12 @@ function VectorStylePanel({ controller, className }) {
1694
1840
  }
1695
1841
  }
1696
1842
  ),
1697
- /* @__PURE__ */ jsx7("select", { "aria-label": "Corner kind", className: selectClass5, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx7("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
1843
+ /* @__PURE__ */ jsx8("select", { "aria-label": "Corner kind", className: selectClass6, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx8("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
1698
1844
  mixed("cornerRadius")
1699
1845
  ] }),
1700
- style && /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1701
- /* @__PURE__ */ jsx7("span", { className: "w-16", children: "Opacity" }),
1702
- /* @__PURE__ */ jsx7(
1846
+ style && /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1847
+ /* @__PURE__ */ jsx8("span", { className: "w-16", children: "Opacity" }),
1848
+ /* @__PURE__ */ jsx8(
1703
1849
  "input",
1704
1850
  {
1705
1851
  type: "range",
@@ -1713,20 +1859,21 @@ function VectorStylePanel({ controller, className }) {
1713
1859
  }
1714
1860
  )
1715
1861
  ] }),
1716
- style && /* @__PURE__ */ jsx7(VectorAppearancePanel, { controller }),
1717
- style && /* @__PURE__ */ jsx7(VectorEffectsPanel, { controller }),
1718
- style && /* @__PURE__ */ jsx7(VectorEnvelopePanel, { controller })
1862
+ style && /* @__PURE__ */ jsx8(VectorAppearancePanel, { controller }),
1863
+ style && /* @__PURE__ */ jsx8(VectorEffectsPanel, { controller }),
1864
+ style && /* @__PURE__ */ jsx8(VectorEnvelopePanel, { controller }),
1865
+ style && /* @__PURE__ */ jsx8(VectorMaskPanel, { controller })
1719
1866
  ] });
1720
1867
  }
1721
1868
 
1722
1869
  // src/vector/VectorEditor.tsx
1723
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1870
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1724
1871
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
1725
- return /* @__PURE__ */ jsxs8("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
1726
- /* @__PURE__ */ jsx8(VectorToolbar, { controller, tools }),
1727
- /* @__PURE__ */ jsxs8("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
1728
- /* @__PURE__ */ jsx8("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx8(VectorCanvas, { controller, ...canvasProps }) }),
1729
- showStylePanel && /* @__PURE__ */ jsx8(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
1872
+ return /* @__PURE__ */ jsxs9("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
1873
+ /* @__PURE__ */ jsx9(VectorToolbar, { controller, tools }),
1874
+ /* @__PURE__ */ jsxs9("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
1875
+ /* @__PURE__ */ jsx9("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx9(VectorCanvas, { controller, ...canvasProps }) }),
1876
+ showStylePanel && /* @__PURE__ */ jsx9(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
1730
1877
  ] })
1731
1878
  ] });
1732
1879
  }
@@ -1740,6 +1887,8 @@ export {
1740
1887
  VectorGradientAnnotator,
1741
1888
  VectorGradientDefs,
1742
1889
  VectorGradientEditor,
1890
+ VectorMaskPanel,
1891
+ VectorMaskedNodes,
1743
1892
  VectorSceneNodes,
1744
1893
  VectorStylePanel,
1745
1894
  VectorToolbar,