@pixerate/editor-react 0.28.0 → 0.30.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,70 +323,84 @@ 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.widthSpot && /* @__PURE__ */ jsxs2("g", { "data-vector-width-spot": "", children: [
352
+ /* @__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" }),
353
+ /* @__PURE__ */ jsx2("path", { d: vectorDiamondPath(overlay.widthSpot.x, overlay.widthSpot.y), fill: "none", stroke: accentColor })
267
354
  ] }),
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))
355
+ overlay.widthPoints.map((w) => /* @__PURE__ */ jsxs2("g", { "data-vector-width-point": w.ref.index, "data-selected": w.selected || void 0, children: [
356
+ /* @__PURE__ */ jsx2("line", { x1: w.left.x, y1: w.left.y, x2: w.right.x, y2: w.right.y, stroke: accentColor }),
357
+ /* @__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 }),
358
+ ["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
359
  ] }, `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(
360
+ 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 }),
361
+ overlay.builder && /* @__PURE__ */ jsxs2("g", { "data-vector-builder": overlay.builder.mode, children: [
362
+ overlay.builder.faces.map((d, i) => /* @__PURE__ */ jsx2(
276
363
  "path",
277
364
  {
278
365
  "data-vector-builder-face": "",
279
366
  d,
280
- fill: overlay.builder.mode === "erase" ? THEME.builderEraseFill : THEME.builderFill,
367
+ fill: overlay.builder.mode === "erase" ? THEME2.builderEraseFill : THEME2.builderFill,
281
368
  fillRule: "nonzero",
282
- stroke: overlay.builder.mode === "erase" ? THEME.builderErase : accentColor,
369
+ stroke: overlay.builder.mode === "erase" ? THEME2.builderErase : accentColor,
283
370
  strokeWidth: 1
284
371
  },
285
372
  `f${i}`
286
373
  )),
287
- 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}`)),
288
- 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 })
374
+ overlay.builder.hatches.map(
375
+ (d, i) => d ? /* @__PURE__ */ jsx2(
376
+ "path",
377
+ {
378
+ "data-vector-builder-hatch": "",
379
+ d,
380
+ fill: "none",
381
+ stroke: overlay.builder.mode === "erase" ? THEME2.builderErase : accentColor,
382
+ strokeOpacity: THEME2.builderHatchOpacity,
383
+ strokeWidth: 1
384
+ },
385
+ `h${i}`
386
+ ) : null
387
+ ),
388
+ 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}`)),
389
+ 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 })
289
390
  ] }),
290
- overlay.toolStroke && /* @__PURE__ */ jsx(
391
+ overlay.toolStroke && /* @__PURE__ */ jsx2(
291
392
  "path",
292
393
  {
293
394
  "data-vector-tool-stroke": overlay.toolStroke.tool,
294
395
  d: overlay.toolStroke.d,
295
- stroke: overlay.toolStroke.tool === "eraser" ? THEME.eraserFill : accentColor,
396
+ stroke: overlay.toolStroke.tool === "eraser" ? THEME2.eraserFill : accentColor,
296
397
  strokeWidth: overlay.toolStroke.tool === "eraser" ? overlay.toolStroke.width : 1,
297
398
  strokeLinecap: "round",
298
399
  strokeLinejoin: "round",
299
400
  strokeDasharray: overlay.toolStroke.tool === "knife" ? "5 3" : void 0
300
401
  }
301
402
  ),
302
- overlay.pencilString && /* @__PURE__ */ jsx(
403
+ overlay.pencilString && /* @__PURE__ */ jsx2(
303
404
  "line",
304
405
  {
305
406
  "data-vector-pencil-string": "",
@@ -311,8 +412,8 @@ function VectorCanvas({
311
412
  strokeDasharray: "2 2"
312
413
  }
313
414
  ),
314
- 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 }),
315
- overlay.marquee && /* @__PURE__ */ jsx(
415
+ 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 }),
416
+ overlay.marquee && /* @__PURE__ */ jsx2(
316
417
  "rect",
317
418
  {
318
419
  "data-vector-marquee": "",
@@ -320,13 +421,13 @@ function VectorCanvas({
320
421
  y: overlay.marquee.y,
321
422
  width: overlay.marquee.width,
322
423
  height: overlay.marquee.height,
323
- fill: THEME.marqueeFill,
424
+ fill: THEME2.marqueeFill,
324
425
  stroke: accentColor,
325
426
  strokeDasharray: "3 2"
326
427
  }
327
428
  ),
328
- 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)) }),
329
- overlay.guides.labels.map((l, i) => /* @__PURE__ */ jsx(
429
+ 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)) }),
430
+ overlay.guides.labels.map((l, i) => /* @__PURE__ */ jsx2(
330
431
  "text",
331
432
  {
332
433
  "data-vector-guide-label": l.kind,
@@ -342,8 +443,10 @@ function VectorCanvas({
342
443
  },
343
444
  i
344
445
  )),
345
- 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 })
346
- ] })
446
+ 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 })
447
+ ] }),
448
+ edit && editAt && // Width Point Edit (double-click with the width tool), beside the point.
449
+ /* @__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}`) })
347
450
  ]
348
451
  }
349
452
  );
@@ -370,7 +473,7 @@ import {
370
473
  vectorPaintToHex,
371
474
  vectorToolLabel
372
475
  } from "@pixerate/editor/vector";
373
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
476
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
374
477
  var VECTOR_SNAP_TITLE = "Snap to objects";
375
478
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
376
479
  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";
@@ -380,9 +483,9 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
380
483
  const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
381
484
  const editing = state.editing;
382
485
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
383
- 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);
384
- return /* @__PURE__ */ jsxs2("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
385
- /* @__PURE__ */ jsx2("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx2(
486
+ 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);
487
+ return /* @__PURE__ */ jsxs3("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
488
+ /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx3(
386
489
  "button",
387
490
  {
388
491
  type: "button",
@@ -396,11 +499,11 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
396
499
  },
397
500
  tool
398
501
  )) }),
399
- showHistory && /* @__PURE__ */ jsxs2("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
502
+ showHistory && /* @__PURE__ */ jsxs3("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
400
503
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
401
504
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
402
505
  ] }),
403
- showSnap && /* @__PURE__ */ jsx2("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ jsx2(
506
+ showSnap && /* @__PURE__ */ jsx3("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ jsx3(
404
507
  "button",
405
508
  {
406
509
  type: "button",
@@ -413,8 +516,8 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
413
516
  children: "Snap"
414
517
  }
415
518
  ) }),
416
- showPathEdit && /* @__PURE__ */ jsxs2("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
417
- VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx2(
519
+ showPathEdit && /* @__PURE__ */ jsxs3("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
520
+ VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx3(
418
521
  "button",
419
522
  {
420
523
  type: "button",
@@ -428,7 +531,7 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
428
531
  },
429
532
  a.id
430
533
  )),
431
- VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx2(
534
+ VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx3(
432
535
  "button",
433
536
  {
434
537
  type: "button",
@@ -442,9 +545,9 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
442
545
  },
443
546
  t.key
444
547
  )),
445
- state.tool === "pencil" && /* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
548
+ state.tool === "pencil" && /* @__PURE__ */ jsxs3("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
446
549
  "Smoothing",
447
- /* @__PURE__ */ jsx2(
550
+ /* @__PURE__ */ jsx3(
448
551
  "input",
449
552
  {
450
553
  type: "range",
@@ -458,9 +561,9 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
458
561
  }
459
562
  )
460
563
  ] }),
461
- state.tool === "eraser" && /* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
564
+ state.tool === "eraser" && /* @__PURE__ */ jsxs3("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
462
565
  "Size",
463
- /* @__PURE__ */ jsx2(
566
+ /* @__PURE__ */ jsx3(
464
567
  "input",
465
568
  {
466
569
  type: "number",
@@ -476,7 +579,7 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
476
579
  }
477
580
  )
478
581
  ] }),
479
- state.tool === "paintBucket" && /* @__PURE__ */ jsx2(
582
+ state.tool === "paintBucket" && /* @__PURE__ */ jsx3(
480
583
  "button",
481
584
  {
482
585
  type: "button",
@@ -490,8 +593,8 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
490
593
  }
491
594
  )
492
595
  ] }),
493
- showArrange && /* @__PURE__ */ jsxs2(Fragment2, { children: [
494
- /* @__PURE__ */ jsxs2("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
596
+ showArrange && /* @__PURE__ */ jsxs3(Fragment2, { children: [
597
+ /* @__PURE__ */ jsxs3("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
495
598
  act("\u222A", "Union", () => controller.booleanOp("union"), count < 2),
496
599
  act("\u2212", "Subtract", () => controller.booleanOp("subtract"), count < 2),
497
600
  act("\u2229", "Intersect", () => controller.booleanOp("intersect"), count < 2),
@@ -499,7 +602,7 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
499
602
  act("G", "Group", () => controller.group(), count < 1),
500
603
  act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
501
604
  ] }),
502
- /* @__PURE__ */ jsx2("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx2(
605
+ /* @__PURE__ */ jsx3("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx3(
503
606
  "button",
504
607
  {
505
608
  type: "button",
@@ -513,7 +616,7 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
513
616
  },
514
617
  a.id
515
618
  )) }),
516
- /* @__PURE__ */ jsxs2("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
619
+ /* @__PURE__ */ jsxs3("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
517
620
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
518
621
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
519
622
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -558,17 +661,17 @@ function VectorStylePanel({ controller, className }) {
558
661
  if (style) controller.setWidthProfile(id);
559
662
  else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
560
663
  };
561
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx2("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
664
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx3("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
562
665
  const arrowRow = (end, label) => {
563
666
  const key = end === "start" ? "startArrow" : "endArrow";
564
667
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
565
- return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
566
- /* @__PURE__ */ jsx2("span", { className: "w-16 text-xs text-foreground", children: label }),
567
- /* @__PURE__ */ jsxs2("select", { "aria-label": `${label} arrowhead`, className: selectClass, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
568
- /* @__PURE__ */ jsx2("option", { value: "", children: "None" }),
569
- VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx2("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
668
+ return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
669
+ /* @__PURE__ */ jsx3("span", { className: "w-16 text-xs text-foreground", children: label }),
670
+ /* @__PURE__ */ jsxs3("select", { "aria-label": `${label} arrowhead`, className: selectClass, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
671
+ /* @__PURE__ */ jsx3("option", { value: "", children: "None" }),
672
+ VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx3("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
570
673
  ] }),
571
- /* @__PURE__ */ jsx2(
674
+ /* @__PURE__ */ jsx3(
572
675
  "input",
573
676
  {
574
677
  type: "number",
@@ -594,12 +697,12 @@ function VectorStylePanel({ controller, className }) {
594
697
  const paint = source[key];
595
698
  const hex = vectorPaintToHex(paint);
596
699
  const on = paint !== null && paint !== "none";
597
- return /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
598
- /* @__PURE__ */ jsxs2("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
599
- /* @__PURE__ */ jsx2("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
700
+ return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
701
+ /* @__PURE__ */ jsxs3("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
702
+ /* @__PURE__ */ jsx3("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
600
703
  label
601
704
  ] }),
602
- /* @__PURE__ */ jsx2(
705
+ /* @__PURE__ */ jsx3(
603
706
  "input",
604
707
  {
605
708
  type: "color",
@@ -610,17 +713,17 @@ function VectorStylePanel({ controller, className }) {
610
713
  onChange: (e) => apply({ [key]: e.target.value })
611
714
  }
612
715
  ),
613
- on && !hex && /* @__PURE__ */ jsx2("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
614
- style?.mixed[key] && /* @__PURE__ */ jsx2("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
716
+ on && !hex && /* @__PURE__ */ jsx3("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
717
+ style?.mixed[key] && /* @__PURE__ */ jsx3("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
615
718
  ] });
616
719
  };
617
- return /* @__PURE__ */ jsxs2("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
618
- /* @__PURE__ */ jsx2("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
720
+ return /* @__PURE__ */ jsxs3("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
721
+ /* @__PURE__ */ jsx3("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
619
722
  paintRow("fill", "Fill"),
620
723
  paintRow("stroke", "Stroke"),
621
- /* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
622
- /* @__PURE__ */ jsx2("span", { className: "w-16", children: "Width" }),
623
- /* @__PURE__ */ jsx2(
724
+ /* @__PURE__ */ jsxs3("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
725
+ /* @__PURE__ */ jsx3("span", { className: "w-16", children: "Width" }),
726
+ /* @__PURE__ */ jsx3(
624
727
  "input",
625
728
  {
626
729
  type: "number",
@@ -636,14 +739,14 @@ function VectorStylePanel({ controller, className }) {
636
739
  }
637
740
  )
638
741
  ] }),
639
- /* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
640
- /* @__PURE__ */ jsx2("span", { className: "w-16", children: "Align" }),
641
- /* @__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)) }),
742
+ /* @__PURE__ */ jsxs3("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
743
+ /* @__PURE__ */ jsx3("span", { className: "w-16", children: "Align" }),
744
+ /* @__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)) }),
642
745
  mixed("strokeAlign")
643
746
  ] }),
644
- /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
645
- /* @__PURE__ */ jsx2("span", { className: "w-16", children: "Profile" }),
646
- /* @__PURE__ */ jsxs2(
747
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
748
+ /* @__PURE__ */ jsx3("span", { className: "w-16", children: "Profile" }),
749
+ /* @__PURE__ */ jsxs3(
647
750
  "select",
648
751
  {
649
752
  "aria-label": "Width profile",
@@ -653,12 +756,12 @@ function VectorStylePanel({ controller, className }) {
653
756
  if (e.target.value !== "custom") setProfile(e.target.value);
654
757
  },
655
758
  children: [
656
- VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx2("option", { value: p.id, children: p.label }, p.id)),
657
- source.widthProfile === "custom" && /* @__PURE__ */ jsx2("option", { value: "custom", disabled: true, children: "Custom" })
759
+ VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx3("option", { value: p.id, children: p.label }, p.id)),
760
+ source.widthProfile === "custom" && /* @__PURE__ */ jsx3("option", { value: "custom", disabled: true, children: "Custom" })
658
761
  ]
659
762
  }
660
763
  ),
661
- /* @__PURE__ */ jsx2(
764
+ /* @__PURE__ */ jsx3(
662
765
  "button",
663
766
  {
664
767
  type: "button",
@@ -672,20 +775,20 @@ function VectorStylePanel({ controller, className }) {
672
775
  ),
673
776
  mixed("widthProfile")
674
777
  ] }),
675
- source.hasOpenPath && /* @__PURE__ */ jsxs2(Fragment2, { children: [
778
+ source.hasOpenPath && /* @__PURE__ */ jsxs3(Fragment2, { children: [
676
779
  arrowRow("start", "Start"),
677
780
  arrowRow("end", "End"),
678
- (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
679
- /* @__PURE__ */ jsx2("span", { className: "w-16", children: "Heads" }),
680
- /* @__PURE__ */ jsxs2("select", { "aria-label": "Arrowhead alignment", className: selectClass, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
681
- /* @__PURE__ */ jsx2("option", { value: "extend", children: "Tip past end" }),
682
- /* @__PURE__ */ jsx2("option", { value: "tip", children: "Tip at end" })
781
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs3("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
782
+ /* @__PURE__ */ jsx3("span", { className: "w-16", children: "Heads" }),
783
+ /* @__PURE__ */ jsxs3("select", { "aria-label": "Arrowhead alignment", className: selectClass, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
784
+ /* @__PURE__ */ jsx3("option", { value: "extend", children: "Tip past end" }),
785
+ /* @__PURE__ */ jsx3("option", { value: "tip", children: "Tip at end" })
683
786
  ] })
684
787
  ] })
685
788
  ] }),
686
- source.corners > 0 && /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
687
- /* @__PURE__ */ jsx2("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
688
- /* @__PURE__ */ jsx2(
789
+ source.corners > 0 && /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
790
+ /* @__PURE__ */ jsx3("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
791
+ /* @__PURE__ */ jsx3(
689
792
  "input",
690
793
  {
691
794
  type: "number",
@@ -700,12 +803,12 @@ function VectorStylePanel({ controller, className }) {
700
803
  }
701
804
  }
702
805
  ),
703
- /* @__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)) }),
806
+ /* @__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)) }),
704
807
  mixed("cornerRadius")
705
808
  ] }),
706
- style && /* @__PURE__ */ jsxs2("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
707
- /* @__PURE__ */ jsx2("span", { className: "w-16", children: "Opacity" }),
708
- /* @__PURE__ */ jsx2(
809
+ style && /* @__PURE__ */ jsxs3("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
810
+ /* @__PURE__ */ jsx3("span", { className: "w-16", children: "Opacity" }),
811
+ /* @__PURE__ */ jsx3(
709
812
  "input",
710
813
  {
711
814
  type: "range",
@@ -723,13 +826,13 @@ function VectorStylePanel({ controller, className }) {
723
826
  }
724
827
 
725
828
  // src/vector/VectorEditor.tsx
726
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
829
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
727
830
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
728
- return /* @__PURE__ */ jsxs3("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
729
- /* @__PURE__ */ jsx3(VectorToolbar, { controller, tools }),
730
- /* @__PURE__ */ jsxs3("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
731
- /* @__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 }) }),
732
- showStylePanel && /* @__PURE__ */ jsx3(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
831
+ return /* @__PURE__ */ jsxs4("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
832
+ /* @__PURE__ */ jsx4(VectorToolbar, { controller, tools }),
833
+ /* @__PURE__ */ jsxs4("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
834
+ /* @__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 }) }),
835
+ showStylePanel && /* @__PURE__ */ jsx4(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
733
836
  ] })
734
837
  ] });
735
838
  }
@@ -740,6 +843,7 @@ export {
740
843
  VectorSceneNodes,
741
844
  VectorStylePanel,
742
845
  VectorToolbar,
846
+ VectorWidthPointEditor,
743
847
  useVectorCommands,
744
848
  useVectorEditor,
745
849
  useVectorEditorController,