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