@pixerate/editor-react 0.29.0 → 0.31.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.
@@ -26,6 +26,7 @@ __export(vector_exports, {
26
26
  VectorSceneNodes: () => VectorSceneNodes,
27
27
  VectorStylePanel: () => VectorStylePanel,
28
28
  VectorToolbar: () => VectorToolbar,
29
+ VectorWidthPointEditor: () => VectorWidthPointEditor,
29
30
  useVectorCommands: () => useVectorCommands,
30
31
  useVectorEditor: () => useVectorEditor,
31
32
  useVectorEditorController: () => useVectorEditorController,
@@ -76,19 +77,104 @@ function useVectorCommands(registry, controller, options = {}) {
76
77
  }
77
78
 
78
79
  // src/vector/VectorCanvas.tsx
80
+ var import_react3 = require("react");
81
+ var import_vector3 = require("@pixerate/editor/vector");
82
+
83
+ // src/vector/VectorWidthPointEditor.tsx
79
84
  var import_react2 = require("react");
80
85
  var import_vector2 = require("@pixerate/editor/vector");
81
86
  var import_jsx_runtime = require("react/jsx-runtime");
87
+ var round = (v, digits) => String(Math.round(v * 10 ** digits) / 10 ** digits);
88
+ var fieldStyle = { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 6 };
89
+ var inputStyle = { width: 72, font: "inherit", padding: "1px 4px" };
90
+ function VectorWidthPointEditor({ controller, edit, accentColor = import_vector2.VECTOR_OVERLAY_THEME.accent, className, style }) {
91
+ const [position, setPosition] = (0, import_react2.useState)(() => round(edit.t * 100, 2));
92
+ const [left, setLeft] = (0, import_react2.useState)(() => round(edit.left, 3));
93
+ const [right, setRight] = (0, import_react2.useState)(() => round(edit.right, 3));
94
+ const formRef = (0, import_react2.useRef)(null);
95
+ const refocus = () => formRef.current?.closest("svg")?.focus?.({ preventScroll: true });
96
+ const close = () => {
97
+ refocus();
98
+ controller.closeWidthPointEdit();
99
+ };
100
+ const num = (v) => v.trim() !== "" && Number.isFinite(Number(v)) ? Number(v) : void 0;
101
+ const apply = () => {
102
+ const t = num(position);
103
+ refocus();
104
+ controller.commitWidthPointEdit({ t: t === void 0 ? void 0 : Math.min(Math.max(t, 0), 100) / 100, left: num(left), right: num(right) });
105
+ };
106
+ const stop = (e) => e.stopPropagation();
107
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
108
+ "form",
109
+ {
110
+ ref: formRef,
111
+ "data-vector-width-point-editor": "",
112
+ "aria-label": "Width point",
113
+ className,
114
+ style: {
115
+ display: "grid",
116
+ gap: 4,
117
+ padding: 8,
118
+ background: "#ffffff",
119
+ border: "1px solid rgba(0,0,0,0.15)",
120
+ borderRadius: 6,
121
+ boxShadow: "0 2px 8px rgba(0,0,0,0.15)",
122
+ font: "12px system-ui, sans-serif",
123
+ color: "#18181b",
124
+ ...style
125
+ },
126
+ onPointerDown: stop,
127
+ onPointerMove: stop,
128
+ onPointerUp: stop,
129
+ onDoubleClick: stop,
130
+ onCopy: stop,
131
+ onCut: stop,
132
+ onPaste: stop,
133
+ onKeyDown: (e) => {
134
+ e.stopPropagation();
135
+ if (e.key === "Escape") {
136
+ e.preventDefault();
137
+ close();
138
+ }
139
+ },
140
+ onSubmit: (e) => {
141
+ e.preventDefault();
142
+ apply();
143
+ },
144
+ children: [
145
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: fieldStyle, children: [
146
+ "Position %",
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "aria-label": "Width point position", type: "number", min: 0, max: 100, step: "any", value: position, onChange: (e) => setPosition(e.target.value), style: inputStyle, autoFocus: true })
148
+ ] }),
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: fieldStyle, children: [
150
+ "Left",
151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "aria-label": "Width point left side", type: "number", min: 0, step: "any", value: left, onChange: (e) => setLeft(e.target.value), style: inputStyle })
152
+ ] }),
153
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { style: fieldStyle, children: [
154
+ "Right",
155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { "aria-label": "Width point right side", type: "number", min: 0, step: "any", value: right, onChange: (e) => setRight(e.target.value), style: inputStyle })
156
+ ] }),
157
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "flex-end", gap: 4, marginTop: 2 }, children: [
158
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: close, children: "Cancel" }),
159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "submit", style: { background: accentColor, color: "#ffffff", border: "none", borderRadius: 4, padding: "2px 8px" }, children: edit.index === null ? "Add" : "Apply" })
160
+ ] })
161
+ ]
162
+ }
163
+ );
164
+ }
165
+
166
+ // src/vector/VectorCanvas.tsx
167
+ var import_jsx_runtime2 = require("react/jsx-runtime");
82
168
  function VectorGradientDefs({ defs }) {
83
169
  if (!defs.length) return null;
84
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: defs.map((def) => {
85
- const stops = def.stops.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("stop", { offset: s.offset, stopColor: s.stopColor, stopOpacity: s.stopOpacity }, i));
86
- return def.type === "radial" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("radialGradient", { id: def.id, ...def.attrs, children: stops }, def.id) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("linearGradient", { id: def.id, ...def.attrs, children: stops }, def.id);
170
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: defs.map((def) => {
171
+ const stops = def.stops.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("stop", { offset: s.offset, stopColor: s.stopColor, stopOpacity: s.stopOpacity }, i));
172
+ return def.type === "radial" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("radialGradient", { id: def.id, ...def.attrs, children: stops }, def.id) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("linearGradient", { id: def.id, ...def.attrs, children: stops }, def.id);
87
173
  }) });
88
174
  }
89
175
  function VectorSceneNodes({ nodes }) {
90
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: nodes.map(
91
- (node) => node.kind === "group" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", { "data-vector-id": node.id, transform: node.transform, opacity: node.opacity < 1 ? node.opacity : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VectorSceneNodes, { nodes: node.children }) }, node.id) : node.strokeParts ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VectorPartedPath, { node }, node.id) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
176
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: nodes.map(
177
+ (node) => node.kind === "group" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { "data-vector-id": node.id, transform: node.transform, opacity: node.opacity < 1 ? node.opacity : void 0, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorSceneNodes, { nodes: node.children }) }, node.id) : node.strokeParts ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorPartedPath, { node }, node.id) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
92
178
  "path",
93
179
  {
94
180
  "data-vector-id": node.id,
@@ -111,15 +197,15 @@ function VectorPartedPath({ node }) {
111
197
  const sp = node.strokeParts;
112
198
  const clip = sp.clip;
113
199
  const b = clip?.bounds;
114
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { "data-vector-id": node.id, "data-vector-stroke-align": sp.align, transform: node.transform, opacity: node.opacity < 1 ? node.opacity : void 0, children: [
115
- clip && b && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: clip.kind === "clip" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("clipPath", { id: clip.id, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: node.d, clipRule: node.fillRule }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("mask", { id: clip.id, maskUnits: "userSpaceOnUse", x: b.x, y: b.y, width: b.width, height: b.height, children: [
116
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: b.x, y: b.y, width: b.width, height: b.height, fill: "#fff" }),
117
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: node.d, fill: "#000", fillRule: node.fillRule })
200
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-id": node.id, "data-vector-stroke-align": sp.align, transform: node.transform, opacity: node.opacity < 1 ? node.opacity : void 0, children: [
201
+ clip && b && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: clip.kind === "clip" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("clipPath", { id: clip.id, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: node.d, clipRule: node.fillRule }) }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("mask", { id: clip.id, maskUnits: "userSpaceOnUse", x: b.x, y: b.y, width: b.width, height: b.height, children: [
202
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: b.x, y: b.y, width: b.width, height: b.height, fill: "#fff" }),
203
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: node.d, fill: "#000", fillRule: node.fillRule })
118
204
  ] }) }),
119
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { "data-vector-part": "fill", d: node.d, fill: node.fill, fillRule: node.fillRule, stroke: "none" }),
205
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { "data-vector-part": "fill", d: node.d, fill: node.fill, fillRule: node.fillRule, stroke: "none" }),
120
206
  sp.outline !== void 0 ? (
121
207
  // A variable-width stroke: its outline, filled.
122
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
208
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
123
209
  "path",
124
210
  {
125
211
  "data-vector-part": "stroke-outline",
@@ -129,7 +215,7 @@ function VectorPartedPath({ node }) {
129
215
  mask: clip?.kind === "mask" ? `url(#${clip.id})` : void 0
130
216
  }
131
217
  )
132
- ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
218
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
133
219
  "path",
134
220
  {
135
221
  "data-vector-part": "stroke",
@@ -144,26 +230,26 @@ function VectorPartedPath({ node }) {
144
230
  mask: clip?.kind === "mask" ? `url(#${clip.id})` : void 0
145
231
  }
146
232
  ),
147
- sp.heads.map((head, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { "data-vector-part": "head", d: head, fill: node.stroke }, i))
233
+ sp.heads.map((head, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { "data-vector-part": "head", d: head, fill: node.stroke }, i))
148
234
  ] });
149
235
  }
150
236
  function VectorCanvas({
151
237
  controller,
152
238
  fitOnMount = true,
153
239
  checkerboard = true,
154
- accentColor = import_vector2.VECTOR_OVERLAY_THEME.accent,
155
- guideColor = import_vector2.VECTOR_OVERLAY_THEME.guide,
240
+ accentColor = import_vector3.VECTOR_OVERLAY_THEME.accent,
241
+ guideColor = import_vector3.VECTOR_OVERLAY_THEME.guide,
156
242
  className,
157
243
  style,
158
244
  "aria-label": ariaLabel = "Vector canvas"
159
245
  }) {
160
246
  const state = useVectorEditorState(controller);
161
- const svgRef = (0, import_react2.useRef)(null);
162
- const fitted = (0, import_react2.useRef)(!fitOnMount);
163
- const uid = (0, import_react2.useId)().replace(/:/g, "");
164
- const scene = (0, import_vector2.getVectorSceneRenderData)(state, { idPrefix: `ve${uid}-` });
165
- const overlay = (0, import_vector2.getVectorOverlayRenderData)(state);
166
- (0, import_react2.useEffect)(() => {
247
+ const svgRef = (0, import_react3.useRef)(null);
248
+ const fitted = (0, import_react3.useRef)(!fitOnMount);
249
+ const uid = (0, import_react3.useId)().replace(/:/g, "");
250
+ const scene = (0, import_vector3.getVectorSceneRenderData)(state, { idPrefix: `ve${uid}-` });
251
+ const overlay = (0, import_vector3.getVectorOverlayRenderData)(state);
252
+ (0, import_react3.useEffect)(() => {
167
253
  const el = svgRef.current;
168
254
  if (!el || fitted.current) return;
169
255
  const fit = () => {
@@ -178,12 +264,12 @@ function VectorCanvas({
178
264
  observer.observe(el);
179
265
  return () => observer.disconnect();
180
266
  }, [controller]);
181
- (0, import_react2.useEffect)(() => {
267
+ (0, import_react3.useEffect)(() => {
182
268
  const el = svgRef.current;
183
269
  if (!el) return;
184
270
  const onWheel = (e) => {
185
271
  e.preventDefault();
186
- controller.wheel(local(e), { x: e.deltaX, y: e.deltaY }, (0, import_vector2.toVectorPointerInput)(e));
272
+ controller.wheel(local(e), { x: e.deltaX, y: e.deltaY }, (0, import_vector3.toVectorPointerInput)(e));
187
273
  };
188
274
  el.addEventListener("wheel", onWheel, { passive: false });
189
275
  return () => el.removeEventListener("wheel", onWheel);
@@ -207,10 +293,12 @@ function VectorCanvas({
207
293
  event.preventDefault?.();
208
294
  }
209
295
  };
210
- const hs = import_vector2.VECTOR_OVERLAY_THEME.transformHandleSize;
211
- const as = import_vector2.VECTOR_OVERLAY_THEME.anchorSize;
296
+ const hs = import_vector3.VECTOR_OVERLAY_THEME.transformHandleSize;
297
+ const as = import_vector3.VECTOR_OVERLAY_THEME.anchorSize;
212
298
  const box = overlay.selectionBox;
213
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
299
+ const edit = state.widthPointEdit;
300
+ const editAt = edit && { x: edit.point.x * state.viewport.zoom + state.viewport.x, y: edit.point.y * state.viewport.zoom + state.viewport.y };
301
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
214
302
  "svg",
215
303
  {
216
304
  ref: svgRef,
@@ -223,32 +311,32 @@ function VectorCanvas({
223
311
  onPointerDown: (e) => {
224
312
  svgRef.current?.focus({ preventScroll: true });
225
313
  e.currentTarget.setPointerCapture?.(e.pointerId);
226
- controller.pointerDown(local(e), (0, import_vector2.toVectorPointerInput)(e));
314
+ controller.pointerDown(local(e), (0, import_vector3.toVectorPointerInput)(e));
227
315
  },
228
- onPointerMove: (e) => controller.pointerMove(local(e), (0, import_vector2.toVectorPointerInput)(e)),
316
+ onPointerMove: (e) => controller.pointerMove(local(e), (0, import_vector3.toVectorPointerInput)(e)),
229
317
  onPointerUp: (e) => {
230
318
  e.currentTarget.releasePointerCapture?.(e.pointerId);
231
- controller.pointerUp(local(e), (0, import_vector2.toVectorPointerInput)(e));
319
+ controller.pointerUp(local(e), (0, import_vector3.toVectorPointerInput)(e));
232
320
  },
233
321
  onPointerCancel: () => controller.cancelDrag(),
234
322
  onPointerLeave: () => controller.pointerLeave(),
235
323
  onDoubleClick: (e) => controller.doubleClick(local(e)),
236
324
  onKeyDown: (e) => {
237
- if ((0, import_vector2.isVectorTextTarget)(e.target)) return;
238
- if (controller.keyDown((0, import_vector2.toVectorKeyInput)(e))) e.preventDefault();
325
+ if ((0, import_vector3.isVectorTextTarget)(e.target)) return;
326
+ if (controller.keyDown((0, import_vector3.toVectorKeyInput)(e))) e.preventDefault();
239
327
  },
240
- onKeyUp: (e) => controller.keyUp((0, import_vector2.toVectorKeyInput)(e)),
328
+ onKeyUp: (e) => controller.keyUp((0, import_vector3.toVectorKeyInput)(e)),
241
329
  onCopy: clipboard("copy"),
242
330
  onCut: clipboard("cut"),
243
331
  onPaste: clipboard("paste"),
244
332
  children: [
245
- checkerboard && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("pattern", { id: `ve${uid}-checker`, width: 16, height: 16, patternUnits: "userSpaceOnUse", children: [
246
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: 16, height: 16, fill: "#ffffff" }),
247
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { width: 8, height: 8, fill: "#e5e7eb" }),
248
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: 8, y: 8, width: 8, height: 8, fill: "#e5e7eb" })
333
+ checkerboard && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("defs", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("pattern", { id: `ve${uid}-checker`, width: 16, height: 16, patternUnits: "userSpaceOnUse", children: [
334
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { width: 16, height: 16, fill: "#ffffff" }),
335
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { width: 8, height: 8, fill: "#e5e7eb" }),
336
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: 8, y: 8, width: 8, height: 8, fill: "#e5e7eb" })
249
337
  ] }) }),
250
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VectorGradientDefs, { defs: scene.defs }),
251
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
338
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorGradientDefs, { defs: scene.defs }),
339
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
252
340
  "rect",
253
341
  {
254
342
  "data-vector-artboard": "",
@@ -260,84 +348,105 @@ function VectorCanvas({
260
348
  stroke: "rgba(0,0,0,0.12)"
261
349
  }
262
350
  ),
263
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", { "data-vector-scene": "", transform: scene.transform, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VectorSceneNodes, { nodes: scene.nodes }) }),
264
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { "data-vector-overlay": "", pointerEvents: "none", fill: "none", children: [
265
- overlay.outlines.map((o) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: o.d, stroke: accentColor, strokeWidth: o.selected ? 1 : 1.5, opacity: o.selected ? 1 : 0.7 }, o.id)),
266
- overlay.penPreview && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: overlay.penPreview, stroke: accentColor, strokeDasharray: "4 3" }),
267
- box && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { "data-vector-selection": "", transform: box.transform || void 0, children: [
268
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { x: box.x, y: box.y, width: box.width, height: box.height, stroke: accentColor }),
269
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: box.x + box.width / 2, y1: box.y, x2: box.x + box.width / 2, y2: box.y - 24, stroke: accentColor }),
351
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { "data-vector-scene": "", transform: scene.transform, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorSceneNodes, { nodes: scene.nodes }) }),
352
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-overlay": "", pointerEvents: "none", fill: "none", children: [
353
+ overlay.outlines.map((o) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: o.d, stroke: accentColor, strokeWidth: o.selected ? 1 : 1.5, opacity: o.selected ? 1 : 0.7 }, o.id)),
354
+ overlay.penPreview && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: overlay.penPreview, stroke: accentColor, strokeDasharray: "4 3" }),
355
+ box && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-selection": "", transform: box.transform || void 0, children: [
356
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { x: box.x, y: box.y, width: box.width, height: box.height, stroke: accentColor }),
357
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: box.x + box.width / 2, y1: box.y, x2: box.x + box.width / 2, y2: box.y - 24, stroke: accentColor }),
270
358
  box.handles.map(
271
- (h) => h.handle === "rotate" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { "data-handle": h.handle, cx: h.x, cy: h.y, r: hs / 2 + 1, fill: import_vector2.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, h.handle) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { "data-handle": h.handle, x: h.x - hs / 2, y: h.y - hs / 2, width: hs, height: hs, fill: import_vector2.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, h.handle)
359
+ (h) => h.handle === "rotate" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { "data-handle": h.handle, cx: h.x, cy: h.y, r: hs / 2 + 1, fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, h.handle) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { "data-handle": h.handle, x: h.x - hs / 2, y: h.y - hs / 2, width: hs, height: hs, fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, h.handle)
272
360
  )
273
361
  ] }),
274
- overlay.handles.map((h) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { "data-vector-handle": h.ref.handle, children: [
275
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: h.anchorX, y1: h.anchorY, x2: h.x, y2: h.y, stroke: accentColor }),
276
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: h.x, cy: h.y, r: import_vector2.VECTOR_OVERLAY_THEME.handleRadius + (h.hovered ? 1 : 0), fill: import_vector2.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor })
362
+ overlay.handles.map((h) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-handle": h.ref.handle, children: [
363
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: h.anchorX, y1: h.anchorY, x2: h.x, y2: h.y, stroke: accentColor }),
364
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: h.x, cy: h.y, r: import_vector3.VECTOR_OVERLAY_THEME.handleRadius + (h.hovered ? 1 : 0), fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor })
277
365
  ] }, `${h.ref.nodeId}:${h.ref.subpath}:${h.ref.index}:${h.ref.handle}`)),
278
366
  overlay.anchors.map((a) => {
279
367
  const size = as + (a.hovered ? 2 : 0);
280
- const fill = a.selected ? accentColor : import_vector2.VECTOR_OVERLAY_THEME.handleFill;
368
+ const fill = a.selected ? accentColor : import_vector3.VECTOR_OVERLAY_THEME.handleFill;
281
369
  const key = `${a.ref.nodeId}:${a.ref.subpath}:${a.ref.index}`;
282
- return a.type === "corner" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("rect", { "data-vector-anchor": a.ref.index, x: a.x - size / 2, y: a.y - size / 2, width: size, height: size, fill, stroke: accentColor }, key) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { "data-vector-anchor": a.ref.index, cx: a.x, cy: a.y, r: size / 2, fill, stroke: accentColor }, key);
370
+ return a.type === "corner" ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("rect", { "data-vector-anchor": a.ref.index, x: a.x - size / 2, y: a.y - size / 2, width: size, height: size, fill, stroke: accentColor }, key) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { "data-vector-anchor": a.ref.index, cx: a.x, cy: a.y, r: size / 2, fill, stroke: accentColor }, key);
283
371
  }),
284
- overlay.cornerWidgets.map((w) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { "data-vector-corner-widget": w.ref.index, "data-corner-kind": w.kind, children: [
285
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: w.x, cy: w.y, r: import_vector2.VECTOR_OVERLAY_THEME.cornerWidgetRadius + (w.hovered ? 1 : 0), fill: import_vector2.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }),
286
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: w.x, cy: w.y, r: 1.5, fill: accentColor })
372
+ overlay.cornerWidgets.map((w) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-corner-widget": w.ref.index, "data-corner-kind": w.kind, children: [
373
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: w.x, cy: w.y, r: import_vector3.VECTOR_OVERLAY_THEME.cornerWidgetRadius + (w.hovered ? 1 : 0), fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }),
374
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: w.x, cy: w.y, r: 1.5, fill: accentColor })
287
375
  ] }, `corner:${w.ref.nodeId}:${w.ref.subpath}:${w.ref.index}`)),
288
- overlay.widthSpot && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { "data-vector-width-spot": "", children: [
289
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: overlay.widthSpot.left.x, y1: overlay.widthSpot.left.y, x2: overlay.widthSpot.right.x, y2: overlay.widthSpot.right.y, stroke: accentColor, strokeDasharray: "3 2" }),
290
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: (0, import_vector2.vectorDiamondPath)(overlay.widthSpot.x, overlay.widthSpot.y), fill: "none", stroke: accentColor })
376
+ overlay.blend.spines.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-blend-spine": sp.id, "data-replaced": sp.replaced || void 0, children: [
377
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: sp.d, fill: "none", stroke: accentColor, strokeDasharray: "4 3" }),
378
+ sp.keys.map((k, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: k.x, cy: k.y, r: import_vector3.VECTOR_OVERLAY_THEME.handleRadius, fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, i))
379
+ ] }, `blend:${sp.id}`)),
380
+ overlay.blend.start && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-blend-start": "", children: [
381
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: overlay.blend.start.d, fill: "none", stroke: accentColor, strokeWidth: 2 }),
382
+ overlay.blend.start.anchor && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: overlay.blend.start.anchor.x, cy: overlay.blend.start.anchor.y, r: import_vector3.VECTOR_OVERLAY_THEME.handleRadius + 1, fill: accentColor })
383
+ ] }),
384
+ overlay.blend.anchors.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
385
+ "rect",
386
+ {
387
+ "data-vector-blend-anchor": i,
388
+ x: a.x - as / 2,
389
+ y: a.y - as / 2,
390
+ width: as,
391
+ height: as,
392
+ fill: a.hovered ? accentColor : import_vector3.VECTOR_OVERLAY_THEME.handleFill,
393
+ stroke: accentColor
394
+ },
395
+ `blend-anchor:${i}`
396
+ )),
397
+ overlay.widthSpot && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-width-spot": "", children: [
398
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: overlay.widthSpot.left.x, y1: overlay.widthSpot.left.y, x2: overlay.widthSpot.right.x, y2: overlay.widthSpot.right.y, stroke: accentColor, strokeDasharray: "3 2" }),
399
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: (0, import_vector3.vectorDiamondPath)(overlay.widthSpot.x, overlay.widthSpot.y), fill: "none", stroke: accentColor })
291
400
  ] }),
292
- overlay.widthPoints.map((w) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { "data-vector-width-point": w.ref.index, "data-selected": w.selected || void 0, children: [
293
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { x1: w.left.x, y1: w.left.y, x2: w.right.x, y2: w.right.y, stroke: accentColor }),
294
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: (0, import_vector2.vectorDiamondPath)(w.x, w.y), fill: w.selected || w.active ? accentColor : import_vector2.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor, strokeWidth: w.hovered === "center" ? 2 : 1 }),
295
- ["left", "right"].map((side) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { "data-vector-width-side": side, cx: w[side].x, cy: w[side].y, r: import_vector2.VECTOR_OVERLAY_THEME.handleRadius + (w.hovered === side ? 1 : 0), fill: import_vector2.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, side))
401
+ overlay.widthPoints.map((w) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-width-point": w.ref.index, "data-selected": w.selected || void 0, children: [
402
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { x1: w.left.x, y1: w.left.y, x2: w.right.x, y2: w.right.y, stroke: accentColor }),
403
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: (0, import_vector3.vectorDiamondPath)(w.x, w.y), fill: w.selected || w.active ? accentColor : import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor, strokeWidth: w.hovered === "center" ? 2 : 1 }),
404
+ ["left", "right"].map((side) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { "data-vector-width-side": side, cx: w[side].x, cy: w[side].y, r: import_vector3.VECTOR_OVERLAY_THEME.handleRadius + (w.hovered === side ? 1 : 0), fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }, side))
296
405
  ] }, `width:${w.ref.nodeId}:${w.ref.subpath}:${w.ref.index}`)),
297
- state.tool === "pen" && overlay.anchors.some((a) => a.closes && a.hovered) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: overlay.anchors.find((a) => a.closes).x, cy: overlay.anchors.find((a) => a.closes).y, r: as, stroke: accentColor }),
298
- overlay.builder && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { "data-vector-builder": overlay.builder.mode, children: [
299
- overlay.builder.faces.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
406
+ state.tool === "pen" && overlay.anchors.some((a) => a.closes && a.hovered) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { cx: overlay.anchors.find((a) => a.closes).x, cy: overlay.anchors.find((a) => a.closes).y, r: as, stroke: accentColor }),
407
+ overlay.builder && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("g", { "data-vector-builder": overlay.builder.mode, children: [
408
+ overlay.builder.faces.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
300
409
  "path",
301
410
  {
302
411
  "data-vector-builder-face": "",
303
412
  d,
304
- fill: overlay.builder.mode === "erase" ? import_vector2.VECTOR_OVERLAY_THEME.builderEraseFill : import_vector2.VECTOR_OVERLAY_THEME.builderFill,
413
+ fill: overlay.builder.mode === "erase" ? import_vector3.VECTOR_OVERLAY_THEME.builderEraseFill : import_vector3.VECTOR_OVERLAY_THEME.builderFill,
305
414
  fillRule: "nonzero",
306
- stroke: overlay.builder.mode === "erase" ? import_vector2.VECTOR_OVERLAY_THEME.builderErase : accentColor,
415
+ stroke: overlay.builder.mode === "erase" ? import_vector3.VECTOR_OVERLAY_THEME.builderErase : accentColor,
307
416
  strokeWidth: 1
308
417
  },
309
418
  `f${i}`
310
419
  )),
311
420
  overlay.builder.hatches.map(
312
- (d, i) => d ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
421
+ (d, i) => d ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
313
422
  "path",
314
423
  {
315
424
  "data-vector-builder-hatch": "",
316
425
  d,
317
426
  fill: "none",
318
- stroke: overlay.builder.mode === "erase" ? import_vector2.VECTOR_OVERLAY_THEME.builderErase : accentColor,
319
- strokeOpacity: import_vector2.VECTOR_OVERLAY_THEME.builderHatchOpacity,
427
+ stroke: overlay.builder.mode === "erase" ? import_vector3.VECTOR_OVERLAY_THEME.builderErase : accentColor,
428
+ strokeOpacity: import_vector3.VECTOR_OVERLAY_THEME.builderHatchOpacity,
320
429
  strokeWidth: 1
321
430
  },
322
431
  `h${i}`
323
432
  ) : null
324
433
  ),
325
- overlay.builder.lines.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { "data-vector-builder-line": "", d, fill: "none", stroke: import_vector2.VECTOR_OVERLAY_THEME.builderErase, strokeWidth: 3, strokeLinecap: "round" }, `l${i}`)),
326
- overlay.builder.trail && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { "data-vector-builder-trail": "", d: overlay.builder.trail, fill: "none", stroke: overlay.builder.mode === "erase" ? import_vector2.VECTOR_OVERLAY_THEME.builderErase : accentColor, strokeWidth: 1 })
434
+ overlay.builder.lines.map((d, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { "data-vector-builder-line": "", d, fill: "none", stroke: import_vector3.VECTOR_OVERLAY_THEME.builderErase, strokeWidth: 3, strokeLinecap: "round" }, `l${i}`)),
435
+ overlay.builder.trail && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { "data-vector-builder-trail": "", d: overlay.builder.trail, fill: "none", stroke: overlay.builder.mode === "erase" ? import_vector3.VECTOR_OVERLAY_THEME.builderErase : accentColor, strokeWidth: 1 })
327
436
  ] }),
328
- overlay.toolStroke && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
437
+ overlay.toolStroke && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
329
438
  "path",
330
439
  {
331
440
  "data-vector-tool-stroke": overlay.toolStroke.tool,
332
441
  d: overlay.toolStroke.d,
333
- stroke: overlay.toolStroke.tool === "eraser" ? import_vector2.VECTOR_OVERLAY_THEME.eraserFill : accentColor,
442
+ stroke: overlay.toolStroke.tool === "eraser" ? import_vector3.VECTOR_OVERLAY_THEME.eraserFill : accentColor,
334
443
  strokeWidth: overlay.toolStroke.tool === "eraser" ? overlay.toolStroke.width : 1,
335
444
  strokeLinecap: "round",
336
445
  strokeLinejoin: "round",
337
446
  strokeDasharray: overlay.toolStroke.tool === "knife" ? "5 3" : void 0
338
447
  }
339
448
  ),
340
- overlay.pencilString && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
449
+ overlay.pencilString && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
341
450
  "line",
342
451
  {
343
452
  "data-vector-pencil-string": "",
@@ -349,8 +458,8 @@ function VectorCanvas({
349
458
  strokeDasharray: "2 2"
350
459
  }
351
460
  ),
352
- overlay.cutPoint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { "data-vector-cut-point": "", cx: overlay.cutPoint.x, cy: overlay.cutPoint.y, r: import_vector2.VECTOR_OVERLAY_THEME.handleRadius + 1, fill: import_vector2.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }),
353
- overlay.marquee && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
461
+ overlay.cutPoint && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("circle", { "data-vector-cut-point": "", cx: overlay.cutPoint.x, cy: overlay.cutPoint.y, r: import_vector3.VECTOR_OVERLAY_THEME.handleRadius + 1, fill: import_vector3.VECTOR_OVERLAY_THEME.handleFill, stroke: accentColor }),
462
+ overlay.marquee && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
354
463
  "rect",
355
464
  {
356
465
  "data-vector-marquee": "",
@@ -358,13 +467,13 @@ function VectorCanvas({
358
467
  y: overlay.marquee.y,
359
468
  width: overlay.marquee.width,
360
469
  height: overlay.marquee.height,
361
- fill: import_vector2.VECTOR_OVERLAY_THEME.marqueeFill,
470
+ fill: import_vector3.VECTOR_OVERLAY_THEME.marqueeFill,
362
471
  stroke: accentColor,
363
472
  strokeDasharray: "3 2"
364
473
  }
365
474
  ),
366
- overlay.guides.lines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("g", { "data-vector-guides": "", stroke: guideColor, children: overlay.guides.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("line", { "data-vector-guide": l.kind, x1: l.x1, y1: l.y1, x2: l.x2, y2: l.y2, strokeDasharray: l.kind === "construction" ? "4 3" : void 0 }, i)) }),
367
- overlay.guides.labels.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
475
+ overlay.guides.lines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("g", { "data-vector-guides": "", stroke: guideColor, children: overlay.guides.lines.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("line", { "data-vector-guide": l.kind, x1: l.x1, y1: l.y1, x2: l.x2, y2: l.y2, strokeDasharray: l.kind === "construction" ? "4 3" : void 0 }, i)) }),
476
+ overlay.guides.labels.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
368
477
  "text",
369
478
  {
370
479
  "data-vector-guide-label": l.kind,
@@ -380,46 +489,57 @@ function VectorCanvas({
380
489
  },
381
490
  i
382
491
  )),
383
- overlay.status && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("text", { "data-vector-status": "", x: 8, y: "100%", dy: -8, fontSize: 11, fill: accentColor, style: { fontFamily: "system-ui, sans-serif" }, children: overlay.status })
384
- ] })
492
+ overlay.status && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("text", { "data-vector-status": "", x: 8, y: "100%", dy: -8, fontSize: 11, fill: accentColor, style: { fontFamily: "system-ui, sans-serif" }, children: overlay.status })
493
+ ] }),
494
+ edit && editAt && // Width Point Edit (double-click with the width tool), beside the point.
495
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("foreignObject", { "data-vector-width-point-edit": "", x: editAt.x + 12, y: editAt.y + 12, width: 180, height: 140, style: { overflow: "visible" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(VectorWidthPointEditor, { controller, edit, accentColor }, `${edit.nodeId}:${edit.index}:${edit.t}`) })
385
496
  ]
386
497
  }
387
498
  );
388
499
  }
389
500
 
390
501
  // src/vector/VectorToolbar.tsx
391
- var import_vector3 = require("@pixerate/editor/vector");
392
- var import_jsx_runtime2 = require("react/jsx-runtime");
502
+ var import_vector4 = require("@pixerate/editor/vector");
503
+ var import_jsx_runtime3 = require("react/jsx-runtime");
393
504
  var VECTOR_SNAP_TITLE = "Snap to objects";
394
505
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
395
506
  var buttonClass = "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";
396
- function VectorToolbar({ controller, tools = import_vector3.VECTOR_TOOLS, showHistory = true, showArrange = true, showSnap = true, showPathEdit = true, className }) {
507
+ function VectorToolbar({
508
+ controller,
509
+ tools = import_vector4.VECTOR_TOOLS,
510
+ showHistory = true,
511
+ showArrange = true,
512
+ showSnap = true,
513
+ showPathEdit = true,
514
+ showBlend = true,
515
+ className
516
+ }) {
397
517
  const state = useVectorEditorState(controller);
398
518
  const count = state.selection.length;
399
519
  const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
400
520
  const editing = state.editing;
401
521
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
402
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: buttonClass, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
403
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
404
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
522
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", className: buttonClass, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
523
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
524
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
405
525
  "button",
406
526
  {
407
527
  type: "button",
408
528
  "data-vector-tool": tool,
409
529
  className: buttonClass,
410
530
  "aria-pressed": state.tool === tool,
411
- title: `${(0, import_vector3.vectorToolLabel)(tool)} (${import_vector3.VECTOR_TOOL_SHORTCUTS[tool]})`,
412
- "aria-label": (0, import_vector3.vectorToolLabel)(tool),
531
+ title: `${(0, import_vector4.vectorToolLabel)(tool)} (${import_vector4.VECTOR_TOOL_SHORTCUTS[tool]})`,
532
+ "aria-label": (0, import_vector4.vectorToolLabel)(tool),
413
533
  onClick: () => controller.setTool(tool),
414
- children: import_vector3.VECTOR_TOOL_GLYPHS[tool]
534
+ children: import_vector4.VECTOR_TOOL_GLYPHS[tool]
415
535
  },
416
536
  tool
417
537
  )) }),
418
- showHistory && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
538
+ showHistory && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
419
539
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
420
540
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
421
541
  ] }),
422
- showSnap && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
542
+ showSnap && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
423
543
  "button",
424
544
  {
425
545
  type: "button",
@@ -432,8 +552,8 @@ function VectorToolbar({ controller, tools = import_vector3.VECTOR_TOOLS, showHi
432
552
  children: "Snap"
433
553
  }
434
554
  ) }),
435
- showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
436
- import_vector3.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
555
+ showPathEdit && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
556
+ import_vector4.VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
437
557
  "button",
438
558
  {
439
559
  type: "button",
@@ -447,7 +567,7 @@ function VectorToolbar({ controller, tools = import_vector3.VECTOR_TOOLS, showHi
447
567
  },
448
568
  a.id
449
569
  )),
450
- import_vector3.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
570
+ import_vector4.VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
451
571
  "button",
452
572
  {
453
573
  type: "button",
@@ -461,9 +581,9 @@ function VectorToolbar({ controller, tools = import_vector3.VECTOR_TOOLS, showHi
461
581
  },
462
582
  t.key
463
583
  )),
464
- state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
584
+ state.tool === "pencil" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
465
585
  "Smoothing",
466
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
586
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
467
587
  "input",
468
588
  {
469
589
  type: "range",
@@ -477,9 +597,9 @@ function VectorToolbar({ controller, tools = import_vector3.VECTOR_TOOLS, showHi
477
597
  }
478
598
  )
479
599
  ] }),
480
- state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
600
+ state.tool === "eraser" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
481
601
  "Size",
482
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
602
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
483
603
  "input",
484
604
  {
485
605
  type: "number",
@@ -495,7 +615,7 @@ function VectorToolbar({ controller, tools = import_vector3.VECTOR_TOOLS, showHi
495
615
  }
496
616
  )
497
617
  ] }),
498
- state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
618
+ state.tool === "paintBucket" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
499
619
  "button",
500
620
  {
501
621
  type: "button",
@@ -504,13 +624,14 @@ function VectorToolbar({ controller, tools = import_vector3.VECTOR_TOOLS, showHi
504
624
  "aria-pressed": editing.fillGap > 0,
505
625
  title: "Close small gaps between path ends when filling",
506
626
  "aria-label": "Close gaps",
507
- onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector3.VECTOR_FILL_GAP }),
627
+ onClick: () => controller.setEditing({ fillGap: editing.fillGap > 0 ? 0 : import_vector4.VECTOR_FILL_GAP }),
508
628
  children: "Gaps"
509
629
  }
510
630
  )
511
631
  ] }),
512
- showArrange && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
513
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
632
+ showBlend && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(VectorBlendControls, { controller }),
633
+ showArrange && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
634
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
514
635
  act("\u222A", "Union", () => controller.booleanOp("union"), count < 2),
515
636
  act("\u2212", "Subtract", () => controller.booleanOp("subtract"), count < 2),
516
637
  act("\u2229", "Intersect", () => controller.booleanOp("intersect"), count < 2),
@@ -518,7 +639,7 @@ function VectorToolbar({ controller, tools = import_vector3.VECTOR_TOOLS, showHi
518
639
  act("G", "Group", () => controller.group(), count < 1),
519
640
  act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
520
641
  ] }),
521
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: import_vector3.VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
642
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: import_vector4.VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
522
643
  "button",
523
644
  {
524
645
  type: "button",
@@ -532,7 +653,7 @@ function VectorToolbar({ controller, tools = import_vector3.VECTOR_TOOLS, showHi
532
653
  },
533
654
  a.id
534
655
  )) }),
535
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
656
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
536
657
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
537
658
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
538
659
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -545,11 +666,74 @@ function VectorToolbar({ controller, tools = import_vector3.VECTOR_TOOLS, showHi
545
666
  ] })
546
667
  ] });
547
668
  }
669
+ function VectorBlendControls({ controller }) {
670
+ const state = useVectorEditorState(controller);
671
+ const blend = (0, import_vector4.getVectorBlendControls)(state);
672
+ const o = blend.options;
673
+ const selected = blend.ids.length > 0;
674
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
675
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
676
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
677
+ "select",
678
+ {
679
+ "aria-label": "Blend spacing",
680
+ className: "h-7 rounded border border-border bg-background px-1 text-xs",
681
+ value: o.spacing,
682
+ onChange: (e) => (0, import_vector4.applyVectorBlendOptions)(controller, { spacing: e.target.value }),
683
+ children: import_vector4.VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: sp, children: import_vector4.VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
684
+ }
685
+ ),
686
+ o.spacing !== "smoothColor" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
687
+ "input",
688
+ {
689
+ type: "number",
690
+ min: o.spacing === "steps" ? 1 : 0.5,
691
+ step: 1,
692
+ "aria-label": o.spacing === "steps" ? "Blend steps" : "Blend distance",
693
+ className: "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs",
694
+ value: o.spacing === "steps" ? o.steps : o.distance,
695
+ onChange: (e) => {
696
+ const n = parseFloat(e.target.value);
697
+ if (!Number.isFinite(n) || n <= 0) return;
698
+ (0, import_vector4.applyVectorBlendOptions)(controller, o.spacing === "steps" ? { steps: n } : { distance: n });
699
+ }
700
+ }
701
+ ),
702
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
703
+ "button",
704
+ {
705
+ type: "button",
706
+ "data-vector-blend-action": "orient",
707
+ className: buttonClass,
708
+ "aria-pressed": o.orientToSpine,
709
+ title: "Orient steps to the spine",
710
+ "aria-label": "Orient to spine",
711
+ onClick: () => (0, import_vector4.applyVectorBlendOptions)(controller, { orientToSpine: !o.orientToSpine }),
712
+ children: "\u293F"
713
+ }
714
+ ),
715
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
716
+ "button",
717
+ {
718
+ type: "button",
719
+ "data-vector-blend-action": "spine",
720
+ className: buttonClass,
721
+ title: "Replace Spine (select a blend and a path)",
722
+ "aria-label": "Replace Spine",
723
+ disabled: !blend.canReplaceSpine,
724
+ onClick: () => controller.replaceBlendSpine(),
725
+ children: "\u2307"
726
+ }
727
+ ),
728
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
729
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
730
+ ] });
731
+ }
548
732
  var selectClass = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
549
733
  var numberClass = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
550
734
  function VectorStylePanel({ controller, className }) {
551
735
  const state = useVectorEditorState(controller);
552
- const style = (0, import_vector3.getVectorSelectionStyle)(state);
736
+ const style = (0, import_vector4.getVectorSelectionStyle)(state);
553
737
  const d = state.defaults;
554
738
  const source = style ?? {
555
739
  fill: d.fill ?? null,
@@ -562,7 +746,7 @@ function VectorStylePanel({ controller, className }) {
562
746
  arrowAlign: d.arrowAlign ?? "extend",
563
747
  startArrowScale: d.startArrowScale ?? 1,
564
748
  endArrowScale: d.endArrowScale ?? 1,
565
- widthProfile: (0, import_vector3.widthProfilePresetId)(d.strokeWidthProfile),
749
+ widthProfile: (0, import_vector4.widthProfilePresetId)(d.strokeWidthProfile),
566
750
  cornerRadius: d.cornerRadius,
567
751
  cornerKind: d.cornerKind ?? "round",
568
752
  corners: 1,
@@ -575,19 +759,19 @@ function VectorStylePanel({ controller, className }) {
575
759
  };
576
760
  const setProfile = (id) => {
577
761
  if (style) controller.setWidthProfile(id);
578
- else controller.setDefaults({ strokeWidthProfile: (0, import_vector3.widthProfilePreset)(id)?.length ? (0, import_vector3.widthProfilePreset)(id) : null });
762
+ else controller.setDefaults({ strokeWidthProfile: (0, import_vector4.widthProfilePreset)(id)?.length ? (0, import_vector4.widthProfilePreset)(id) : null });
579
763
  };
580
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
764
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
581
765
  const arrowRow = (end, label) => {
582
766
  const key = end === "start" ? "startArrow" : "endArrow";
583
767
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
584
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
585
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
586
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { "aria-label": `${label} arrowhead`, className: selectClass, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
587
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "", children: "None" }),
588
- import_vector3.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: h, children: import_vector3.VECTOR_ARROWHEAD_LABELS[h] }, h))
768
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
769
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16 text-xs text-foreground", children: label }),
770
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("select", { "aria-label": `${label} arrowhead`, className: selectClass, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
771
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "None" }),
772
+ import_vector4.VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: h, children: import_vector4.VECTOR_ARROWHEAD_LABELS[h] }, h))
589
773
  ] }),
590
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
774
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
591
775
  "input",
592
776
  {
593
777
  type: "number",
@@ -611,14 +795,14 @@ function VectorStylePanel({ controller, className }) {
611
795
  };
612
796
  const paintRow = (key, label) => {
613
797
  const paint = source[key];
614
- const hex = (0, import_vector3.vectorPaintToHex)(paint);
798
+ const hex = (0, import_vector4.vectorPaintToHex)(paint);
615
799
  const on = paint !== null && paint !== "none";
616
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
617
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
618
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
800
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
801
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
802
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
619
803
  label
620
804
  ] }),
621
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
805
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
622
806
  "input",
623
807
  {
624
808
  type: "color",
@@ -629,17 +813,17 @@ function VectorStylePanel({ controller, className }) {
629
813
  onChange: (e) => apply({ [key]: e.target.value })
630
814
  }
631
815
  ),
632
- on && !hex && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
633
- style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
816
+ on && !hex && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
817
+ style?.mixed[key] && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
634
818
  ] });
635
819
  };
636
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
637
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
820
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
821
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
638
822
  paintRow("fill", "Fill"),
639
823
  paintRow("stroke", "Stroke"),
640
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
641
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-16", children: "Width" }),
642
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
824
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
825
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16", children: "Width" }),
826
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
643
827
  "input",
644
828
  {
645
829
  type: "number",
@@ -655,14 +839,14 @@ function VectorStylePanel({ controller, className }) {
655
839
  }
656
840
  )
657
841
  ] }),
658
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
659
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-16", children: "Align" }),
660
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("select", { "aria-label": "Stroke alignment", className: selectClass, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(import_vector3.VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: a, children: import_vector3.VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
842
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
843
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16", children: "Align" }),
844
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("select", { "aria-label": "Stroke alignment", className: selectClass, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(import_vector4.VECTOR_STROKE_ALIGN_LABELS).map((a) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: a, children: import_vector4.VECTOR_STROKE_ALIGN_LABELS[a] }, a)) }),
661
845
  mixed("strokeAlign")
662
846
  ] }),
663
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
664
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-16", children: "Profile" }),
665
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
847
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
848
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16", children: "Profile" }),
849
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
666
850
  "select",
667
851
  {
668
852
  "aria-label": "Width profile",
@@ -672,12 +856,12 @@ function VectorStylePanel({ controller, className }) {
672
856
  if (e.target.value !== "custom") setProfile(e.target.value);
673
857
  },
674
858
  children: [
675
- import_vector3.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: p.id, children: p.label }, p.id)),
676
- source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
859
+ import_vector4.VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: p.id, children: p.label }, p.id)),
860
+ source.widthProfile === "custom" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "custom", disabled: true, children: "Custom" })
677
861
  ]
678
862
  }
679
863
  ),
680
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
864
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
681
865
  "button",
682
866
  {
683
867
  type: "button",
@@ -691,20 +875,20 @@ function VectorStylePanel({ controller, className }) {
691
875
  ),
692
876
  mixed("widthProfile")
693
877
  ] }),
694
- source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
878
+ source.hasOpenPath && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
695
879
  arrowRow("start", "Start"),
696
880
  arrowRow("end", "End"),
697
- (source.startArrow || source.endArrow) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
698
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-16", children: "Heads" }),
699
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
700
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "extend", children: "Tip past end" }),
701
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "tip", children: "Tip at end" })
881
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
882
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16", children: "Heads" }),
883
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("select", { "aria-label": "Arrowhead alignment", className: selectClass, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
884
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "extend", children: "Tip past end" }),
885
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "tip", children: "Tip at end" })
702
886
  ] })
703
887
  ] })
704
888
  ] }),
705
- source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
706
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
707
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
889
+ source.corners > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
890
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
891
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
708
892
  "input",
709
893
  {
710
894
  type: "number",
@@ -719,12 +903,12 @@ function VectorStylePanel({ controller, className }) {
719
903
  }
720
904
  }
721
905
  ),
722
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("select", { "aria-label": "Corner kind", className: selectClass, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: import_vector3.VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: k, children: import_vector3.VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
906
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("select", { "aria-label": "Corner kind", className: selectClass, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: import_vector4.VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: k, children: import_vector4.VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
723
907
  mixed("cornerRadius")
724
908
  ] }),
725
- style && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
726
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "w-16", children: "Opacity" }),
727
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
909
+ style && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
910
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "w-16", children: "Opacity" }),
911
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
728
912
  "input",
729
913
  {
730
914
  type: "range",
@@ -742,13 +926,13 @@ function VectorStylePanel({ controller, className }) {
742
926
  }
743
927
 
744
928
  // src/vector/VectorEditor.tsx
745
- var import_jsx_runtime3 = require("react/jsx-runtime");
929
+ var import_jsx_runtime4 = require("react/jsx-runtime");
746
930
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
747
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
748
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(VectorToolbar, { controller, tools }),
749
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
750
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
751
- showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
931
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
932
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VectorToolbar, { controller, tools }),
933
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
934
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VectorCanvas, { controller, ...canvasProps }) }),
935
+ showStylePanel && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
752
936
  ] })
753
937
  ] });
754
938
  }
@@ -760,6 +944,7 @@ function VectorEditor({ controller, tools, showStylePanel = true, height = 520,
760
944
  VectorSceneNodes,
761
945
  VectorStylePanel,
762
946
  VectorToolbar,
947
+ VectorWidthPointEditor,
763
948
  useVectorCommands,
764
949
  useVectorEditor,
765
950
  useVectorEditorController,