@pixerate/editor-react 0.29.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,84 +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
374
  overlay.builder.hatches.map(
288
- (d, i) => d ? /* @__PURE__ */ jsx(
375
+ (d, i) => d ? /* @__PURE__ */ jsx2(
289
376
  "path",
290
377
  {
291
378
  "data-vector-builder-hatch": "",
292
379
  d,
293
380
  fill: "none",
294
- stroke: overlay.builder.mode === "erase" ? THEME.builderErase : accentColor,
295
- strokeOpacity: THEME.builderHatchOpacity,
381
+ stroke: overlay.builder.mode === "erase" ? THEME2.builderErase : accentColor,
382
+ strokeOpacity: THEME2.builderHatchOpacity,
296
383
  strokeWidth: 1
297
384
  },
298
385
  `h${i}`
299
386
  ) : null
300
387
  ),
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 })
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 })
303
390
  ] }),
304
- overlay.toolStroke && /* @__PURE__ */ jsx(
391
+ overlay.toolStroke && /* @__PURE__ */ jsx2(
305
392
  "path",
306
393
  {
307
394
  "data-vector-tool-stroke": overlay.toolStroke.tool,
308
395
  d: overlay.toolStroke.d,
309
- stroke: overlay.toolStroke.tool === "eraser" ? THEME.eraserFill : accentColor,
396
+ stroke: overlay.toolStroke.tool === "eraser" ? THEME2.eraserFill : accentColor,
310
397
  strokeWidth: overlay.toolStroke.tool === "eraser" ? overlay.toolStroke.width : 1,
311
398
  strokeLinecap: "round",
312
399
  strokeLinejoin: "round",
313
400
  strokeDasharray: overlay.toolStroke.tool === "knife" ? "5 3" : void 0
314
401
  }
315
402
  ),
316
- overlay.pencilString && /* @__PURE__ */ jsx(
403
+ overlay.pencilString && /* @__PURE__ */ jsx2(
317
404
  "line",
318
405
  {
319
406
  "data-vector-pencil-string": "",
@@ -325,8 +412,8 @@ function VectorCanvas({
325
412
  strokeDasharray: "2 2"
326
413
  }
327
414
  ),
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(
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(
330
417
  "rect",
331
418
  {
332
419
  "data-vector-marquee": "",
@@ -334,13 +421,13 @@ function VectorCanvas({
334
421
  y: overlay.marquee.y,
335
422
  width: overlay.marquee.width,
336
423
  height: overlay.marquee.height,
337
- fill: THEME.marqueeFill,
424
+ fill: THEME2.marqueeFill,
338
425
  stroke: accentColor,
339
426
  strokeDasharray: "3 2"
340
427
  }
341
428
  ),
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(
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(
344
431
  "text",
345
432
  {
346
433
  "data-vector-guide-label": l.kind,
@@ -356,8 +443,10 @@ function VectorCanvas({
356
443
  },
357
444
  i
358
445
  )),
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
- ] })
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}`) })
361
450
  ]
362
451
  }
363
452
  );
@@ -384,7 +473,7 @@ import {
384
473
  vectorPaintToHex,
385
474
  vectorToolLabel
386
475
  } from "@pixerate/editor/vector";
387
- 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";
388
477
  var VECTOR_SNAP_TITLE = "Snap to objects";
389
478
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
390
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";
@@ -394,9 +483,9 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
394
483
  const points = state.tool === "direct" ? state.selectedAnchors.length : 0;
395
484
  const editing = state.editing;
396
485
  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(
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(
400
489
  "button",
401
490
  {
402
491
  type: "button",
@@ -410,11 +499,11 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
410
499
  },
411
500
  tool
412
501
  )) }),
413
- 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: [
414
503
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
415
504
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
416
505
  ] }),
417
- 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(
418
507
  "button",
419
508
  {
420
509
  type: "button",
@@ -427,8 +516,8 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
427
516
  children: "Snap"
428
517
  }
429
518
  ) }),
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(
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(
432
521
  "button",
433
522
  {
434
523
  type: "button",
@@ -442,7 +531,7 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
442
531
  },
443
532
  a.id
444
533
  )),
445
- VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx2(
534
+ VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx3(
446
535
  "button",
447
536
  {
448
537
  type: "button",
@@ -456,9 +545,9 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
456
545
  },
457
546
  t.key
458
547
  )),
459
- 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: [
460
549
  "Smoothing",
461
- /* @__PURE__ */ jsx2(
550
+ /* @__PURE__ */ jsx3(
462
551
  "input",
463
552
  {
464
553
  type: "range",
@@ -472,9 +561,9 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
472
561
  }
473
562
  )
474
563
  ] }),
475
- 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: [
476
565
  "Size",
477
- /* @__PURE__ */ jsx2(
566
+ /* @__PURE__ */ jsx3(
478
567
  "input",
479
568
  {
480
569
  type: "number",
@@ -490,7 +579,7 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
490
579
  }
491
580
  )
492
581
  ] }),
493
- state.tool === "paintBucket" && /* @__PURE__ */ jsx2(
582
+ state.tool === "paintBucket" && /* @__PURE__ */ jsx3(
494
583
  "button",
495
584
  {
496
585
  type: "button",
@@ -504,8 +593,8 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
504
593
  }
505
594
  )
506
595
  ] }),
507
- showArrange && /* @__PURE__ */ jsxs2(Fragment2, { children: [
508
- /* @__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: [
509
598
  act("\u222A", "Union", () => controller.booleanOp("union"), count < 2),
510
599
  act("\u2212", "Subtract", () => controller.booleanOp("subtract"), count < 2),
511
600
  act("\u2229", "Intersect", () => controller.booleanOp("intersect"), count < 2),
@@ -513,7 +602,7 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
513
602
  act("G", "Group", () => controller.group(), count < 1),
514
603
  act("U", "Ungroup", () => controller.ungroup(), !groupSelected)
515
604
  ] }),
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(
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(
517
606
  "button",
518
607
  {
519
608
  type: "button",
@@ -527,7 +616,7 @@ function VectorToolbar({ controller, tools = VECTOR_TOOLS, showHistory = true, s
527
616
  },
528
617
  a.id
529
618
  )) }),
530
- /* @__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: [
531
620
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
532
621
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
533
622
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -572,17 +661,17 @@ function VectorStylePanel({ controller, className }) {
572
661
  if (style) controller.setWidthProfile(id);
573
662
  else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
574
663
  };
575
- 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;
576
665
  const arrowRow = (end, label) => {
577
666
  const key = end === "start" ? "startArrow" : "endArrow";
578
667
  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))
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))
584
673
  ] }),
585
- /* @__PURE__ */ jsx2(
674
+ /* @__PURE__ */ jsx3(
586
675
  "input",
587
676
  {
588
677
  type: "number",
@@ -608,12 +697,12 @@ function VectorStylePanel({ controller, className }) {
608
697
  const paint = source[key];
609
698
  const hex = vectorPaintToHex(paint);
610
699
  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 }) }),
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 }) }),
614
703
  label
615
704
  ] }),
616
- /* @__PURE__ */ jsx2(
705
+ /* @__PURE__ */ jsx3(
617
706
  "input",
618
707
  {
619
708
  type: "color",
@@ -624,17 +713,17 @@ function VectorStylePanel({ controller, className }) {
624
713
  onChange: (e) => apply({ [key]: e.target.value })
625
714
  }
626
715
  ),
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" })
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" })
629
718
  ] });
630
719
  };
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" }),
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" }),
633
722
  paintRow("fill", "Fill"),
634
723
  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(
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(
638
727
  "input",
639
728
  {
640
729
  type: "number",
@@ -650,14 +739,14 @@ function VectorStylePanel({ controller, className }) {
650
739
  }
651
740
  )
652
741
  ] }),
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)) }),
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)) }),
656
745
  mixed("strokeAlign")
657
746
  ] }),
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(
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(
661
750
  "select",
662
751
  {
663
752
  "aria-label": "Width profile",
@@ -667,12 +756,12 @@ function VectorStylePanel({ controller, className }) {
667
756
  if (e.target.value !== "custom") setProfile(e.target.value);
668
757
  },
669
758
  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" })
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" })
672
761
  ]
673
762
  }
674
763
  ),
675
- /* @__PURE__ */ jsx2(
764
+ /* @__PURE__ */ jsx3(
676
765
  "button",
677
766
  {
678
767
  type: "button",
@@ -686,20 +775,20 @@ function VectorStylePanel({ controller, className }) {
686
775
  ),
687
776
  mixed("widthProfile")
688
777
  ] }),
689
- source.hasOpenPath && /* @__PURE__ */ jsxs2(Fragment2, { children: [
778
+ source.hasOpenPath && /* @__PURE__ */ jsxs3(Fragment2, { children: [
690
779
  arrowRow("start", "Start"),
691
780
  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" })
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" })
697
786
  ] })
698
787
  ] })
699
788
  ] }),
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(
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(
703
792
  "input",
704
793
  {
705
794
  type: "number",
@@ -714,12 +803,12 @@ function VectorStylePanel({ controller, className }) {
714
803
  }
715
804
  }
716
805
  ),
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)) }),
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)) }),
718
807
  mixed("cornerRadius")
719
808
  ] }),
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(
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(
723
812
  "input",
724
813
  {
725
814
  type: "range",
@@ -737,13 +826,13 @@ function VectorStylePanel({ controller, className }) {
737
826
  }
738
827
 
739
828
  // src/vector/VectorEditor.tsx
740
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
829
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
741
830
  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" })
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" })
747
836
  ] })
748
837
  ] });
749
838
  }
@@ -754,6 +843,7 @@ export {
754
843
  VectorSceneNodes,
755
844
  VectorStylePanel,
756
845
  VectorToolbar,
846
+ VectorWidthPointEditor,
757
847
  useVectorCommands,
758
848
  useVectorEditor,
759
849
  useVectorEditorController,