@pixerate/editor-react 0.71.0 → 0.73.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.
@@ -49,6 +49,7 @@ import {
49
49
  VECTOR_OVERLAY_THEME as THEME2,
50
50
  getVectorOverlayRenderData,
51
51
  getVectorSceneRenderData,
52
+ getVectorSymbolEditBackground,
52
53
  isVectorTextTarget,
53
54
  toVectorKeyInput,
54
55
  toVectorPointerInput,
@@ -72,11 +73,11 @@ function VectorWidthPointEditor({ controller, edit, accentColor = THEME.accent,
72
73
  refocus();
73
74
  controller.closeWidthPointEdit();
74
75
  };
75
- const num = (v) => v.trim() !== "" && Number.isFinite(Number(v)) ? Number(v) : void 0;
76
+ const num2 = (v) => v.trim() !== "" && Number.isFinite(Number(v)) ? Number(v) : void 0;
76
77
  const apply = () => {
77
- const t = num(position);
78
+ const t = num2(position);
78
79
  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
+ controller.commitWidthPointEdit({ t: t === void 0 ? void 0 : Math.min(Math.max(t, 0), 100) / 100, left: num2(left), right: num2(right) });
80
81
  };
81
82
  const stop = (e) => e.stopPropagation();
82
83
  return /* @__PURE__ */ jsxs(
@@ -192,9 +193,42 @@ function VectorFilterDefs({ filters }) {
192
193
  return /* @__PURE__ */ jsx2("defs", { children: filters.map((f) => /* @__PURE__ */ jsx2("filter", { id: f.id, filterUnits: "userSpaceOnUse", x: f.x, y: f.y, width: f.width, height: f.height, colorInterpolationFilters: "sRGB", children: f.primitives.map(primitive) }, f.id)) });
193
194
  }
194
195
  var blendStyle = (blendMode) => blendMode ? { mixBlendMode: blendMode } : void 0;
196
+ function VectorTextElement({ node }) {
197
+ return /* @__PURE__ */ jsx2("g", { "data-vector-id": node.id, "data-vector-text": "", transform: node.transform, opacity: node.opacity < 1 ? node.opacity : void 0, filter: node.filter, style: blendStyle(node.blendMode), children: node.layers.map((layer, i) => /* @__PURE__ */ jsx2(
198
+ "text",
199
+ {
200
+ "data-vector-layer": layer.kind,
201
+ xmlSpace: "preserve",
202
+ fill: layer.fill,
203
+ stroke: layer.stroke,
204
+ strokeWidth: layer.stroke === "none" ? void 0 : layer.strokeWidth,
205
+ strokeLinejoin: layer.strokeLinejoin,
206
+ strokeDasharray: layer.strokeDasharray,
207
+ opacity: layer.opacity < 1 ? layer.opacity : void 0,
208
+ style: { whiteSpace: "pre", ...blendStyle(layer.blendMode) },
209
+ children: node.spans.map((sp, k) => /* @__PURE__ */ jsx2(
210
+ "tspan",
211
+ {
212
+ x: sp.x,
213
+ y: sp.y,
214
+ rotate: sp.rotate,
215
+ fontFamily: sp.fontFamily,
216
+ fontSize: sp.fontSize,
217
+ fontWeight: sp.fontWeight,
218
+ fontStyle: sp.fontStyle,
219
+ textDecoration: sp.textDecoration,
220
+ fill: layer.runFills && sp.fill ? sp.fill : void 0,
221
+ children: sp.text
222
+ },
223
+ k
224
+ ))
225
+ },
226
+ i
227
+ )) });
228
+ }
195
229
  function VectorSceneNodes({ nodes }) {
196
230
  return /* @__PURE__ */ jsx2(Fragment, { children: nodes.map(
197
- (node) => node.kind === "group" ? /* @__PURE__ */ jsx2(
231
+ (node) => node.kind === "text" ? /* @__PURE__ */ jsx2(VectorTextElement, { node }, node.id) : node.kind === "group" ? /* @__PURE__ */ jsx2(
198
232
  "g",
199
233
  {
200
234
  "data-vector-id": node.id,
@@ -325,10 +359,13 @@ function VectorCanvas({
325
359
  }) {
326
360
  const state = useVectorEditorState(controller);
327
361
  const svgRef = useRef3(null);
362
+ const textRef = useRef3(null);
363
+ const editingText = state.textEdit?.nodeId ?? null;
328
364
  const fitted = useRef3(!fitOnMount);
329
365
  const uid = useId().replace(/:/g, "");
330
366
  const scene = getVectorSceneRenderData(state, { idPrefix: `ve${uid}-` });
331
367
  const overlay = getVectorOverlayRenderData(state);
368
+ const host = getVectorSymbolEditBackground(state, { idPrefix: `ve${uid}-host-` });
332
369
  useEffect2(() => {
333
370
  const el = svgRef.current;
334
371
  if (!el || fitted.current) return;
@@ -344,6 +381,11 @@ function VectorCanvas({
344
381
  observer.observe(el);
345
382
  return () => observer.disconnect();
346
383
  }, [controller]);
384
+ useEffect2(() => {
385
+ if (!editingText) return;
386
+ const t = setTimeout(() => textRef.current?.focus({ preventScroll: true }), 0);
387
+ return () => clearTimeout(t);
388
+ }, [editingText]);
347
389
  useEffect2(() => {
348
390
  const el = svgRef.current;
349
391
  if (!el) return;
@@ -429,7 +471,30 @@ function VectorCanvas({
429
471
  stroke: "rgba(0,0,0,0.12)"
430
472
  }
431
473
  ),
474
+ host && /* @__PURE__ */ jsxs2("g", { "data-vector-symbol-host": "", opacity: 0.3, pointerEvents: "none", transform: host.transform, children: [
475
+ /* @__PURE__ */ jsx2(VectorGradientDefs, { defs: host.defs }),
476
+ /* @__PURE__ */ jsx2(VectorFilterDefs, { filters: host.filters }),
477
+ /* @__PURE__ */ jsx2(VectorSceneNodes, { nodes: host.nodes })
478
+ ] }),
432
479
  /* @__PURE__ */ jsx2("g", { "data-vector-scene": "", transform: scene.transform, children: /* @__PURE__ */ jsx2(VectorSceneNodes, { nodes: scene.nodes }) }),
480
+ overlay.frames.length > 0 && /* @__PURE__ */ jsx2("g", { "data-vector-frame-labels": "", children: overlay.frames.map((f) => /* @__PURE__ */ jsx2(
481
+ "text",
482
+ {
483
+ "data-vector-frame-label": f.id,
484
+ x: f.x,
485
+ y: f.y - 6,
486
+ fontSize: 11,
487
+ fontWeight: f.selected ? 600 : 400,
488
+ fill: f.selected ? accentColor : "#737373",
489
+ style: { fontFamily: "system-ui, sans-serif", cursor: "default", userSelect: "none" },
490
+ onPointerDown: (e) => {
491
+ e.stopPropagation();
492
+ controller.select([f.id]);
493
+ },
494
+ children: f.name
495
+ },
496
+ f.id
497
+ )) }),
433
498
  /* @__PURE__ */ jsxs2("g", { "data-vector-overlay": "", pointerEvents: "none", fill: "none", children: [
434
499
  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)),
435
500
  overlay.penPreview && /* @__PURE__ */ jsx2("path", { d: overlay.penPreview, stroke: accentColor, strokeDasharray: "4 3" }),
@@ -571,6 +636,38 @@ function VectorCanvas({
571
636
  }
572
637
  ),
573
638
  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 }),
639
+ overlay.text && /* @__PURE__ */ jsxs2("g", { "data-vector-text-edit": overlay.text.nodeId, "data-editing": overlay.text.editing || void 0, children: [
640
+ /* @__PURE__ */ jsx2("path", { d: overlay.text.frame, stroke: accentColor, strokeDasharray: overlay.text.editing ? "3 2" : void 0, opacity: 0.8 }),
641
+ overlay.text.selection.map((d, i) => /* @__PURE__ */ jsx2("path", { "data-vector-text-selection": "", d, fill: accentColor, fillOpacity: 0.25, stroke: "none" }, i)),
642
+ overlay.text.caret && /* @__PURE__ */ jsx2("line", { "data-vector-text-caret": "", x1: overlay.text.caret.x1, y1: overlay.text.caret.y1, x2: overlay.text.caret.x2, y2: overlay.text.caret.y2, stroke: accentColor, strokeWidth: 1.5 }),
643
+ overlay.text.overflow && /* @__PURE__ */ jsxs2("g", { "data-vector-text-overflow": "", transform: `translate(${overlay.text.overflow.x} ${overlay.text.overflow.y})`, children: [
644
+ /* @__PURE__ */ jsx2("rect", { x: -5, y: -5, width: 10, height: 10, fill: THEME2.handleFill, stroke: "#dc2626" }),
645
+ /* @__PURE__ */ jsx2("path", { d: "M-3 0H3M0 -3V3", stroke: "#dc2626" })
646
+ ] })
647
+ ] }),
648
+ overlay.trackingPoints.map((t, i) => /* @__PURE__ */ jsx2("path", { "data-vector-tracking-point": "", d: `M${t.x - 5} ${t.y}H${t.x + 5}M${t.x} ${t.y - 5}V${t.y + 5}`, stroke: guideColor, strokeWidth: 1.5 }, `track:${i}`)),
649
+ overlay.typedEntry && /* @__PURE__ */ jsxs2("g", { "data-vector-typed-entry": "", transform: `translate(${overlay.typedEntry.x + 14} ${overlay.typedEntry.y + 18})`, children: [
650
+ /* @__PURE__ */ jsx2("rect", { x: -3, y: -12, width: overlay.typedEntry.text.length * 7 + 8, height: 17, rx: 3, fill: THEME2.handleFill, stroke: accentColor }),
651
+ /* @__PURE__ */ jsx2("text", { x: 1, y: 1, fontSize: 11, fill: accentColor, style: { fontFamily: "ui-monospace, monospace" }, children: overlay.typedEntry.text })
652
+ ] }),
653
+ overlay.connectorSites.map((s) => /* @__PURE__ */ jsx2(
654
+ "circle",
655
+ {
656
+ "data-vector-connection-site": s.side,
657
+ "data-active": s.active || void 0,
658
+ cx: s.x,
659
+ cy: s.y,
660
+ r: s.active ? 5 : 3.5,
661
+ fill: s.active ? accentColor : THEME2.handleFill,
662
+ stroke: accentColor
663
+ },
664
+ `site:${s.nodeId}:${s.side}`
665
+ )),
666
+ overlay.constraints.map((c) => /* @__PURE__ */ jsxs2("g", { "data-vector-constraint-glyph": c.kind, "data-satisfied": c.satisfied ? "true" : "false", children: [
667
+ c.dimension && /* @__PURE__ */ jsx2("line", { x1: c.dimension.x1, y1: c.dimension.y1, x2: c.dimension.x2, y2: c.dimension.y2, stroke: c.satisfied ? THEME2.guide : "#dc2626", strokeDasharray: "2 2" }),
668
+ /* @__PURE__ */ jsx2("text", { x: c.x, y: c.y, fontSize: 10, textAnchor: "middle", fill: c.satisfied ? accentColor : "#dc2626", stroke: "#ffffff", strokeWidth: 3, paintOrder: "stroke", style: { fontFamily: "system-ui, sans-serif" }, children: c.label })
669
+ ] }, `constraint:${c.id}`)),
670
+ overlay.textBox && /* @__PURE__ */ jsx2("rect", { "data-vector-text-box": "", x: overlay.textBox.x, y: overlay.textBox.y, width: overlay.textBox.width, height: overlay.textBox.height, stroke: accentColor, strokeDasharray: "3 2" }),
574
671
  overlay.marquee && /* @__PURE__ */ jsx2(
575
672
  "rect",
576
673
  {
@@ -603,6 +700,78 @@ function VectorCanvas({
603
700
  )),
604
701
  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 })
605
702
  ] }),
703
+ state.textEdit && overlay.text && // A hidden textarea at the caret takes typing and composed (IME) text.
704
+ /* @__PURE__ */ jsx2("foreignObject", { x: overlay.text.caret?.x1 ?? 0, y: overlay.text.caret?.y1 ?? 0, width: 1, height: 1, style: { overflow: "visible" }, children: /* @__PURE__ */ jsx2(
705
+ "textarea",
706
+ {
707
+ ref: textRef,
708
+ "data-testid": "vector-type-input",
709
+ "aria-label": "Text",
710
+ autoCapitalize: "off",
711
+ autoCorrect: "off",
712
+ spellCheck: false,
713
+ style: { position: "absolute", width: 1, height: 1, padding: 0, border: 0, opacity: 0, resize: "none", overflow: "hidden" },
714
+ onPointerDown: (e) => e.stopPropagation(),
715
+ onInput: (e) => {
716
+ const el = e.currentTarget;
717
+ if (e.nativeEvent.isComposing) return;
718
+ if (el.value) controller.textInput(el.value);
719
+ el.value = "";
720
+ },
721
+ onCompositionEnd: (e) => {
722
+ const el = e.currentTarget;
723
+ if (el.value) controller.textInput(el.value);
724
+ el.value = "";
725
+ },
726
+ onKeyDown: (e) => {
727
+ e.stopPropagation();
728
+ if (e.nativeEvent.isComposing) return;
729
+ if (e.key.length === 1 && !e.metaKey && !e.ctrlKey) return;
730
+ if (controller.keyDown(toVectorKeyInput(e))) e.preventDefault();
731
+ if (!controller.getState().textEdit) svgRef.current?.focus({ preventScroll: true });
732
+ },
733
+ onKeyUp: (e) => e.stopPropagation(),
734
+ onCopy: (e) => {
735
+ const text = controller.getSelectedText();
736
+ if (text) {
737
+ e.clipboardData.setData("text/plain", text);
738
+ e.preventDefault();
739
+ }
740
+ e.stopPropagation();
741
+ },
742
+ onCut: (e) => {
743
+ const text = controller.cutText();
744
+ if (text) {
745
+ e.clipboardData.setData("text/plain", text);
746
+ e.preventDefault();
747
+ }
748
+ e.stopPropagation();
749
+ },
750
+ onPaste: (e) => {
751
+ const text = e.clipboardData.getData("text/plain");
752
+ if (text) controller.textInput(text);
753
+ e.preventDefault();
754
+ e.stopPropagation();
755
+ }
756
+ }
757
+ ) }),
758
+ state.symbolEdit && // Isolation mode: which symbol, and the way out (Escape with nothing selected works too).
759
+ /* @__PURE__ */ jsx2("foreignObject", { "data-vector-symbol-edit": state.symbolEdit.symbolId, x: 8, y: 8, width: 320, height: 32, style: { overflow: "visible" }, children: /* @__PURE__ */ jsxs2(
760
+ "div",
761
+ {
762
+ style: { display: "inline-flex", alignItems: "center", gap: 8, padding: "4px 8px", borderRadius: 6, background: "rgba(15,23,42,0.85)", color: "#fff", font: "12px system-ui, sans-serif" },
763
+ onPointerDown: (e) => e.stopPropagation(),
764
+ onDoubleClick: (e) => e.stopPropagation(),
765
+ onKeyDown: (e) => e.stopPropagation(),
766
+ children: [
767
+ /* @__PURE__ */ jsxs2("span", { children: [
768
+ "Symbol: ",
769
+ state.symbolEdit.name
770
+ ] }),
771
+ /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": "Exit symbol", style: { border: "1px solid rgba(255,255,255,0.4)", borderRadius: 4, padding: "0 6px", background: "transparent", color: "#fff", cursor: "pointer" }, onClick: () => controller.exitSymbolEdit(), children: "Done" })
772
+ ]
773
+ }
774
+ ) }),
606
775
  edit && editAt && // Width Point Edit (double-click with the width tool), beside the point.
607
776
  /* @__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}`) })
608
777
  ]
@@ -611,7 +780,7 @@ function VectorCanvas({
611
780
  }
612
781
 
613
782
  // src/vector/VectorToolbar.tsx
614
- import React4 from "react";
783
+ import React7 from "react";
615
784
  import {
616
785
  VECTOR_TOOLS,
617
786
  VECTOR_TOOL_GLYPHS,
@@ -634,8 +803,10 @@ import {
634
803
  isVectorMaskGroup,
635
804
  widthProfilePreset,
636
805
  widthProfilePresetId,
637
- vectorPaintToHex as vectorPaintToHex2,
638
- vectorToolLabel
806
+ vectorPaintToHex as vectorPaintToHex4,
807
+ vectorToolLabel,
808
+ vectorPolarAngles,
809
+ vectorPolarIncrementOf
639
810
  } from "@pixerate/editor/vector";
640
811
 
641
812
  // src/vector/VectorEffectsPanel.tsx
@@ -1223,6 +1394,537 @@ function VectorMaskPanel({ controller, className }) {
1223
1394
  ] });
1224
1395
  }
1225
1396
 
1397
+ // src/vector/VectorTextPanel.tsx
1398
+ import { useId as useId2 } from "react";
1399
+ import {
1400
+ VECTOR_FONT_WEIGHTS,
1401
+ VECTOR_TEXT_ALIGN_OPTIONS,
1402
+ applyVectorParagraphStyle,
1403
+ applyVectorTextStyle,
1404
+ getVectorTextPanel
1405
+ } from "@pixerate/editor/vector";
1406
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1407
+ var buttonClass5 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
1408
+ var inputClass = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1409
+ var labelClass4 = "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground";
1410
+ var num = (v) => {
1411
+ const n = parseFloat(v);
1412
+ return Number.isFinite(n) ? n : null;
1413
+ };
1414
+ function VectorTextPanel({ controller, className }) {
1415
+ const state = useVectorEditorState(controller);
1416
+ const listId = `vector-fonts-${useId2().replace(/:/g, "")}`;
1417
+ const panel = getVectorTextPanel(state);
1418
+ if (!panel) return null;
1419
+ const f = panel.font;
1420
+ const style = (patch, merge) => applyVectorTextStyle(controller, patch, merge ? { merge } : {});
1421
+ const where = panel.target === "range" ? "Selected characters" : panel.target === "nodes" ? "Selected text" : "New text";
1422
+ return /* @__PURE__ */ jsxs7("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-text-panel": panel.target, "aria-label": "Text", children: [
1423
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
1424
+ /* @__PURE__ */ jsx7("span", { className: labelClass4, children: "Text" }),
1425
+ /* @__PURE__ */ jsx7("span", { className: "text-[11px] text-muted-foreground", children: where })
1426
+ ] }),
1427
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
1428
+ /* @__PURE__ */ jsx7(
1429
+ "input",
1430
+ {
1431
+ "aria-label": "Font family",
1432
+ className: inputClass,
1433
+ list: listId,
1434
+ placeholder: "Mixed",
1435
+ defaultValue: f.fontFamily ?? "",
1436
+ onKeyDown: (e) => {
1437
+ if (e.key === "Enter") e.target.blur();
1438
+ },
1439
+ onBlur: (e) => {
1440
+ const v = e.target.value.trim();
1441
+ if (v && v !== f.fontFamily) style({ fontFamily: v });
1442
+ }
1443
+ },
1444
+ `family:${f.fontFamily}`
1445
+ ),
1446
+ /* @__PURE__ */ jsx7("datalist", { id: listId, children: panel.families.map((fam) => /* @__PURE__ */ jsx7("option", { value: fam }, fam)) })
1447
+ ] }),
1448
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
1449
+ /* @__PURE__ */ jsxs7("select", { "aria-label": "Font weight", className: inputClass, value: f.fontWeight ?? "", onChange: (e) => style({ fontWeight: Number(e.target.value) }), children: [
1450
+ f.fontWeight === null && /* @__PURE__ */ jsx7("option", { value: "", children: "Mixed" }),
1451
+ VECTOR_FONT_WEIGHTS.map((w) => /* @__PURE__ */ jsx7("option", { value: w.value, children: w.label }, w.value)),
1452
+ f.fontWeight !== null && !VECTOR_FONT_WEIGHTS.some((w) => w.value === f.fontWeight) && /* @__PURE__ */ jsx7("option", { value: f.fontWeight, children: f.fontWeight })
1453
+ ] }),
1454
+ /* @__PURE__ */ jsx7(
1455
+ "input",
1456
+ {
1457
+ type: "number",
1458
+ "aria-label": "Font size",
1459
+ title: "Font size",
1460
+ className: `${inputClass} w-14 flex-none`,
1461
+ min: 0.5,
1462
+ step: 1,
1463
+ placeholder: "\u2014",
1464
+ value: f.fontSize === null ? "" : Math.round(f.fontSize * 100) / 100,
1465
+ onChange: (e) => {
1466
+ const v = num(e.target.value);
1467
+ if (v !== null && v >= 0.5) style({ fontSize: v }, "text-size");
1468
+ },
1469
+ onBlur: () => controller.endMergedEdit()
1470
+ }
1471
+ )
1472
+ ] }),
1473
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-1", children: [
1474
+ /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass5, "aria-label": "Bold", "aria-pressed": (f.fontWeight ?? 0) >= 600, onClick: () => style({ fontWeight: (f.fontWeight ?? 400) >= 600 ? 400 : 700 }), children: /* @__PURE__ */ jsx7("b", { children: "B" }) }),
1475
+ /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass5, "aria-label": "Italic", "aria-pressed": f.italic === true, onClick: () => style({ italic: !f.italic }), children: /* @__PURE__ */ jsx7("i", { children: "I" }) }),
1476
+ /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass5, "aria-label": "Underline", "aria-pressed": f.underline === true, onClick: () => style({ underline: !f.underline }), children: /* @__PURE__ */ jsx7("u", { children: "U" }) }),
1477
+ /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass5, "aria-label": "Strike through", "aria-pressed": f.strikethrough === true, onClick: () => style({ strikethrough: !f.strikethrough }), children: /* @__PURE__ */ jsx7("s", { children: "S" }) })
1478
+ ] }),
1479
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
1480
+ /* @__PURE__ */ jsxs7("label", { className: "flex flex-1 items-center gap-1 text-xs text-foreground", title: "Tracking (1/1000 em)", children: [
1481
+ /* @__PURE__ */ jsx7("span", { className: "text-[11px] text-muted-foreground", children: "VA" }),
1482
+ /* @__PURE__ */ jsx7(
1483
+ "input",
1484
+ {
1485
+ type: "number",
1486
+ "aria-label": "Tracking",
1487
+ className: inputClass,
1488
+ step: 10,
1489
+ placeholder: "\u2014",
1490
+ value: f.tracking ?? "",
1491
+ onChange: (e) => style({ tracking: num(e.target.value) ?? 0 }, "text-tracking"),
1492
+ onBlur: () => controller.endMergedEdit()
1493
+ }
1494
+ )
1495
+ ] }),
1496
+ /* @__PURE__ */ jsxs7("label", { className: "flex flex-1 items-center gap-1 text-xs text-foreground", title: "Leading (empty: auto)", children: [
1497
+ /* @__PURE__ */ jsx7("span", { className: "text-[11px] text-muted-foreground", children: "\u2195" }),
1498
+ /* @__PURE__ */ jsx7(
1499
+ "input",
1500
+ {
1501
+ type: "number",
1502
+ "aria-label": "Leading",
1503
+ className: inputClass,
1504
+ min: 0,
1505
+ step: 1,
1506
+ placeholder: "Auto",
1507
+ value: f.leading ?? "",
1508
+ onChange: (e) => style({ leading: num(e.target.value) }, "text-leading"),
1509
+ onBlur: () => controller.endMergedEdit()
1510
+ }
1511
+ )
1512
+ ] })
1513
+ ] }),
1514
+ /* @__PURE__ */ jsx7("div", { className: "flex flex-wrap items-center gap-1", role: "group", "aria-label": "Alignment", children: VECTOR_TEXT_ALIGN_OPTIONS.map((o) => /* @__PURE__ */ jsx7(
1515
+ "button",
1516
+ {
1517
+ type: "button",
1518
+ className: buttonClass5,
1519
+ "aria-label": `Align ${o.label}`,
1520
+ "aria-pressed": panel.align === o.value,
1521
+ onClick: () => applyVectorParagraphStyle(controller, { align: o.value }),
1522
+ children: o.label
1523
+ },
1524
+ o.value
1525
+ )) }),
1526
+ panel.target !== "defaults" && panel.ids.length === 1 && /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-1", children: [
1527
+ /* @__PURE__ */ jsx7(
1528
+ "button",
1529
+ {
1530
+ type: "button",
1531
+ className: buttonClass5,
1532
+ "aria-pressed": panel.frame === "point",
1533
+ title: "Point text: no wrapping",
1534
+ onClick: () => controller.setTextFrame(panel.ids[0], { kind: "point" }),
1535
+ children: "Point"
1536
+ }
1537
+ ),
1538
+ /* @__PURE__ */ jsx7(
1539
+ "button",
1540
+ {
1541
+ type: "button",
1542
+ className: buttonClass5,
1543
+ "aria-pressed": panel.frame === "area",
1544
+ title: "Area text: wraps in a box",
1545
+ onClick: () => controller.setTextFrame(panel.ids[0], { kind: "area" }),
1546
+ children: "Area"
1547
+ }
1548
+ )
1549
+ ] }),
1550
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-1", children: [
1551
+ /* @__PURE__ */ jsx7(
1552
+ "button",
1553
+ {
1554
+ type: "button",
1555
+ className: buttonClass5,
1556
+ "data-vector-text-action": "type-on-path",
1557
+ disabled: !panel.pathId,
1558
+ title: panel.pathId ? "Type on a Path: set text along the selected path" : "Select a path to set text along it",
1559
+ onClick: () => {
1560
+ const r = controller.typeOnPath(panel.pathId, { text: "Text on a path" });
1561
+ if (r.ok && r.id) controller.editText(r.id, { anchor: 0, focus: "Text on a path".length });
1562
+ },
1563
+ children: "Type on path"
1564
+ }
1565
+ ),
1566
+ /* @__PURE__ */ jsx7(
1567
+ "button",
1568
+ {
1569
+ type: "button",
1570
+ className: buttonClass5,
1571
+ "data-vector-text-action": "create-outlines",
1572
+ disabled: panel.target !== "nodes" || !panel.outlines,
1573
+ title: panel.outlines ? "Create Outlines: turn the text into paths" : "Create Outlines needs a font with outlines (load one)",
1574
+ onClick: () => controller.createOutlines({ ids: panel.ids }),
1575
+ children: "Create outlines"
1576
+ }
1577
+ )
1578
+ ] })
1579
+ ] });
1580
+ }
1581
+
1582
+ // src/vector/VectorSymbolsPanel.tsx
1583
+ import { useId as useId3, useState as useState2 } from "react";
1584
+ import { getVectorSymbolsPanel, vectorPaintToHex } from "@pixerate/editor/vector";
1585
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1586
+ var buttonClass6 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
1587
+ var selectClass5 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1588
+ var labelClass5 = "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground";
1589
+ function VectorSymbolsPanel({ controller, className }) {
1590
+ const state = useVectorEditorState(controller);
1591
+ const uid = useId3().replace(/:/g, "");
1592
+ const panel = getVectorSymbolsPanel(state, { idPrefix: `vsym${uid}-` });
1593
+ const [error, setError] = useState2(null);
1594
+ const report = (r) => setError(r.ok ? null : r.message ?? null);
1595
+ const inst = panel.instance;
1596
+ return /* @__PURE__ */ jsxs8("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-symbols-panel": "", "aria-label": "Symbols", children: [
1597
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1", children: [
1598
+ /* @__PURE__ */ jsx8("span", { className: labelClass5, children: "Symbols" }),
1599
+ /* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass6, "data-vector-symbol-action": "make", disabled: !panel.canMake, title: "New Symbol from the selection (F8)", onClick: () => report(controller.makeSymbol()), children: "New" }),
1600
+ panel.editing && /* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass6, "data-vector-symbol-action": "exit", title: "Leave the symbol (Escape)", onClick: () => controller.exitSymbolEdit(), children: "Done" })
1601
+ ] }),
1602
+ panel.symbols.length > 0 && /* @__PURE__ */ jsx8("ul", { className: "space-y-1", children: panel.symbols.map((s) => /* @__PURE__ */ jsxs8("li", { "data-vector-symbol": s.id, "data-editing": s.editing || void 0, className: "flex items-center gap-1", children: [
1603
+ /* @__PURE__ */ jsxs8("svg", { viewBox: s.viewBox, width: 28, height: 28, className: "shrink-0 rounded border border-border bg-background", "aria-hidden": "true", children: [
1604
+ /* @__PURE__ */ jsx8(VectorGradientDefs, { defs: s.scene.defs }),
1605
+ /* @__PURE__ */ jsx8(VectorFilterDefs, { filters: s.scene.filters }),
1606
+ /* @__PURE__ */ jsx8(VectorSceneNodes, { nodes: s.scene.nodes })
1607
+ ] }),
1608
+ /* @__PURE__ */ jsxs8("span", { className: "min-w-0 flex-1 truncate text-xs text-foreground", title: `${s.name} \xB7 ${s.instances} instance${s.instances === 1 ? "" : "s"}`, children: [
1609
+ s.name,
1610
+ " ",
1611
+ /* @__PURE__ */ jsxs8("span", { className: "text-muted-foreground", children: [
1612
+ "\xB7 ",
1613
+ s.instances
1614
+ ] })
1615
+ ] }),
1616
+ /* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass6, "aria-label": `Place ${s.name}`, title: "Place an instance", onClick: () => report(controller.placeSymbol(s.id)), children: "+" }),
1617
+ /* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass6, "aria-label": `Edit ${s.name}`, "aria-pressed": s.editing, title: "Edit Symbol (isolation mode)", onClick: () => report(controller.editSymbol(s.id)), children: "\u270E" }),
1618
+ /* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass6, "aria-label": `Delete ${s.name}`, title: "Delete the symbol (its instances become groups)", onClick: () => report(controller.deleteSymbol(s.id, { detach: true })), children: "\xD7" })
1619
+ ] }, s.id)) }),
1620
+ inst && /* @__PURE__ */ jsxs8("div", { className: "space-y-1", "data-vector-instance": inst.id, children: [
1621
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1", children: [
1622
+ /* @__PURE__ */ jsx8("select", { "aria-label": "Instance symbol", className: selectClass5, value: inst.symbolId, onChange: (e) => report(controller.replaceSymbol(e.target.value, { ids: [inst.id] })), children: panel.symbols.map((s) => /* @__PURE__ */ jsx8("option", { value: s.id, children: s.name }, s.id)) }),
1623
+ /* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass6, title: "Break Link to Symbol: a plain group of its art", onClick: () => report(controller.detachInstances({ ids: [inst.id] })), children: "Detach" })
1624
+ ] }),
1625
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
1626
+ /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
1627
+ /* @__PURE__ */ jsx8("input", { type: "checkbox", "aria-label": "Scale strokes", checked: inst.scaleStrokes, onChange: (e) => report(controller.setInstanceOptions({ scaleStrokes: e.target.checked }, { ids: [inst.id] })) }),
1628
+ "Scale strokes"
1629
+ ] }),
1630
+ /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Paint every fill of the art in this instance", children: [
1631
+ "Tint",
1632
+ /* @__PURE__ */ jsx8(
1633
+ "input",
1634
+ {
1635
+ type: "color",
1636
+ "aria-label": "Instance fill",
1637
+ className: "h-6 w-8 rounded border border-border",
1638
+ value: vectorPaintToHex(inst.fill ?? void 0) ?? "#000000",
1639
+ onChange: (e) => report(controller.setInstanceOptions({ fill: e.target.value }, { ids: [inst.id] }))
1640
+ }
1641
+ )
1642
+ ] }),
1643
+ inst.fill !== null && /* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass6, "aria-label": "Clear tint", onClick: () => report(controller.setInstanceOptions({ fill: null }, { ids: [inst.id] })), children: "\xD7" })
1644
+ ] })
1645
+ ] }),
1646
+ error && /* @__PURE__ */ jsx8("p", { className: "text-[11px] text-destructive", children: error })
1647
+ ] });
1648
+ }
1649
+
1650
+ // src/vector/VectorConstraintsPanel.tsx
1651
+ import { useState as useState3 } from "react";
1652
+ import { getVectorConstraintPanel } from "@pixerate/editor/vector";
1653
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1654
+ var buttonClass7 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40";
1655
+ var inputClass2 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1656
+ var labelClass6 = "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground";
1657
+ function VectorConstraintsPanel({ controller, className }) {
1658
+ const state = useVectorEditorState(controller);
1659
+ const panel = getVectorConstraintPanel(state);
1660
+ const [error, setError] = useState3(null);
1661
+ const [param, setParam] = useState3({ name: "", value: "" });
1662
+ const report = (r) => setError(r.ok ? null : r.message ?? null);
1663
+ const add = (a) => report(controller.addConstraint({ kind: a.kind, refs: a.refs }));
1664
+ if (!panel.actions.length && !panel.constraints.length && !state.document.constraints?.length && state.tool !== "direct") return null;
1665
+ return /* @__PURE__ */ jsxs9("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-constraints-panel": "", "data-dof-status": panel.dof.status, "aria-label": "Constraints", children: [
1666
+ /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1", children: [
1667
+ /* @__PURE__ */ jsx9("span", { className: labelClass6, children: "Constraints" }),
1668
+ /* @__PURE__ */ jsx9("span", { className: "flex-1 truncate text-[11px] text-muted-foreground", "data-vector-dof": "", children: panel.dof.message }),
1669
+ /* @__PURE__ */ jsx9("button", { type: "button", className: buttonClass7, title: "Auto Constrain: add what the selection (or the drawing) nearly satisfies", onClick: () => report(controller.autoConstrain()), children: "Auto" })
1670
+ ] }),
1671
+ panel.actions.length > 0 && /* @__PURE__ */ jsx9("div", { className: "flex flex-wrap gap-1", role: "group", "aria-label": "Add constraint", children: panel.actions.map((a) => /* @__PURE__ */ jsx9("button", { type: "button", className: buttonClass7, "data-vector-constraint-action": a.kind, title: `Add ${a.label}`, onClick: () => add(a), children: a.label }, a.kind)) }),
1672
+ panel.constraints.length > 0 && /* @__PURE__ */ jsx9("ul", { className: "space-y-1", children: panel.constraints.map((c) => /* @__PURE__ */ jsxs9("li", { "data-vector-constraint": c.id, "data-satisfied": c.satisfied && !c.conflict ? "true" : "false", className: "flex items-center gap-1 text-xs text-foreground", children: [
1673
+ /* @__PURE__ */ jsxs9("span", { className: `min-w-0 flex-1 truncate ${c.conflict || !c.satisfied ? "text-destructive" : ""}`, children: [
1674
+ c.label,
1675
+ c.name ? ` ${c.name}` : ""
1676
+ ] }),
1677
+ c.value !== void 0 && /* @__PURE__ */ jsx9(
1678
+ "input",
1679
+ {
1680
+ "aria-label": `Value of ${c.name ?? c.id}`,
1681
+ className: `${inputClass2} w-16 flex-none`,
1682
+ defaultValue: c.value,
1683
+ onKeyDown: (e) => {
1684
+ if (e.key === "Enter") e.target.blur();
1685
+ },
1686
+ onBlur: (e) => {
1687
+ if (e.target.value.trim() !== c.value) report(controller.setConstraintValue(c.id, e.target.value));
1688
+ }
1689
+ },
1690
+ `${c.id}:${c.value}`
1691
+ ),
1692
+ /* @__PURE__ */ jsx9("button", { type: "button", className: buttonClass7, "aria-label": `Remove ${c.label} constraint`, onClick: () => report(controller.removeConstraints([c.id])), children: "\xD7" })
1693
+ ] }, c.id)) }),
1694
+ /* @__PURE__ */ jsxs9("div", { className: "space-y-1", "aria-label": "Parameters", children: [
1695
+ panel.parameters.map((p) => /* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-1 text-xs", children: [
1696
+ /* @__PURE__ */ jsx9("span", { className: "w-12 truncate font-mono", children: p.name }),
1697
+ /* @__PURE__ */ jsx9(
1698
+ "input",
1699
+ {
1700
+ "aria-label": `Parameter ${p.name}`,
1701
+ className: inputClass2,
1702
+ defaultValue: p.value,
1703
+ onKeyDown: (e) => {
1704
+ if (e.key === "Enter") e.target.blur();
1705
+ },
1706
+ onBlur: (e) => {
1707
+ if (e.target.value.trim() !== p.value) report(controller.setConstraintValue(p.name, e.target.value));
1708
+ }
1709
+ },
1710
+ `${p.name}:${p.value}`
1711
+ ),
1712
+ /* @__PURE__ */ jsx9("span", { className: "w-10 text-right text-muted-foreground", children: p.result === null ? "!" : Math.round(p.result * 100) / 100 })
1713
+ ] }, p.name)),
1714
+ /* @__PURE__ */ jsxs9(
1715
+ "form",
1716
+ {
1717
+ className: "flex items-center gap-1",
1718
+ onSubmit: (e) => {
1719
+ e.preventDefault();
1720
+ if (!param.name.trim()) return;
1721
+ const r = controller.setConstraintValue(param.name.trim(), param.value);
1722
+ report(r);
1723
+ if (r.ok) setParam({ name: "", value: "" });
1724
+ },
1725
+ children: [
1726
+ /* @__PURE__ */ jsx9("input", { "aria-label": "New parameter name", placeholder: "name", className: `${inputClass2} w-12 flex-none`, value: param.name, onChange: (e) => setParam({ ...param, name: e.target.value }) }),
1727
+ /* @__PURE__ */ jsx9("input", { "aria-label": "New parameter value", placeholder: "value", className: inputClass2, value: param.value, onChange: (e) => setParam({ ...param, value: e.target.value }) }),
1728
+ /* @__PURE__ */ jsx9("button", { type: "submit", className: buttonClass7, "aria-label": "Add parameter", children: "+" })
1729
+ ]
1730
+ }
1731
+ )
1732
+ ] }),
1733
+ error && /* @__PURE__ */ jsx9("p", { className: "text-[11px] text-destructive", children: error })
1734
+ ] });
1735
+ }
1736
+
1737
+ // src/vector/VectorFramePanel.tsx
1738
+ import {
1739
+ VECTOR_HORIZONTAL_RESIZING,
1740
+ VECTOR_HORIZONTAL_RESIZING_LABELS,
1741
+ VECTOR_VERTICAL_RESIZING,
1742
+ VECTOR_VERTICAL_RESIZING_LABELS,
1743
+ getVectorFramePanel,
1744
+ vectorPaintToHex as vectorPaintToHex2
1745
+ } from "@pixerate/editor/vector";
1746
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1747
+ var buttonClass8 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40";
1748
+ var selectClass6 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1749
+ var numberClass4 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
1750
+ function VectorFramePanel({ controller, className }) {
1751
+ const state = useVectorEditorState(controller);
1752
+ const panel = getVectorFramePanel(state);
1753
+ const f = panel.frame;
1754
+ const ids = f ? [f.id] : void 0;
1755
+ const fillHex = f ? vectorPaintToHex2(f.frame.fill ?? null) : null;
1756
+ const number = (key, label) => /* @__PURE__ */ jsxs10("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
1757
+ /* @__PURE__ */ jsx10("span", { className: "w-4 text-muted-foreground", children: label }),
1758
+ /* @__PURE__ */ jsx10(
1759
+ "input",
1760
+ {
1761
+ type: "number",
1762
+ step: "1",
1763
+ min: key === "width" || key === "height" ? 1 : void 0,
1764
+ "aria-label": `Frame ${key}`,
1765
+ className: numberClass4,
1766
+ defaultValue: Math.round(f.frame[key] * 100) / 100,
1767
+ onKeyDown: (e) => {
1768
+ if (e.key === "Enter") e.currentTarget.blur();
1769
+ },
1770
+ onBlur: (e) => {
1771
+ const n = parseFloat(e.target.value);
1772
+ if (Number.isFinite(n) && n !== Math.round(f.frame[key] * 100) / 100 && (key === "x" || key === "y" || n > 0)) controller.setFrame({ [key]: n }, { ids });
1773
+ }
1774
+ },
1775
+ `${f.id}:${f.frame[key]}`
1776
+ )
1777
+ ] });
1778
+ return /* @__PURE__ */ jsxs10("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-frame-panel": "", "aria-label": "Frame", children: [
1779
+ /* @__PURE__ */ jsxs10("div", { className: "flex flex-wrap items-center gap-1", children: [
1780
+ /* @__PURE__ */ jsx10("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Frame" }),
1781
+ /* @__PURE__ */ jsx10(
1782
+ "button",
1783
+ {
1784
+ type: "button",
1785
+ "data-vector-frame-action": "frame-selection",
1786
+ className: buttonClass8,
1787
+ title: "Frame Selection: put the selection in a frame fitted to it",
1788
+ "aria-label": "Frame Selection",
1789
+ disabled: !state.selection.length,
1790
+ onClick: () => controller.frameSelection(),
1791
+ children: "Frame selection"
1792
+ }
1793
+ )
1794
+ ] }),
1795
+ f && /* @__PURE__ */ jsxs10("div", { className: "space-y-2", "data-vector-frame": f.id, children: [
1796
+ /* @__PURE__ */ jsxs10("div", { className: "grid grid-cols-2 gap-1", children: [
1797
+ number("x", "X"),
1798
+ number("y", "Y"),
1799
+ number("width", "W"),
1800
+ number("height", "H")
1801
+ ] }),
1802
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2", children: [
1803
+ /* @__PURE__ */ jsxs10("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
1804
+ /* @__PURE__ */ jsx10(
1805
+ "input",
1806
+ {
1807
+ type: "checkbox",
1808
+ "aria-label": "Frame fill on",
1809
+ checked: f.frame.fill !== void 0 && f.frame.fill !== null,
1810
+ onChange: (e) => controller.setFrame({ fill: e.target.checked ? fillHex ?? "#ffffff" : null }, { ids })
1811
+ }
1812
+ ),
1813
+ "Fill"
1814
+ ] }),
1815
+ /* @__PURE__ */ jsx10(
1816
+ "input",
1817
+ {
1818
+ type: "color",
1819
+ "aria-label": "Frame fill colour",
1820
+ className: "h-7 w-10 cursor-pointer rounded border border-border bg-background disabled:opacity-40",
1821
+ disabled: f.frame.fill === void 0 || f.frame.fill === null,
1822
+ value: fillHex ?? "#ffffff",
1823
+ onChange: (e) => controller.setFrame({ fill: e.target.value }, { ids, merge: "frame-fill" })
1824
+ }
1825
+ ),
1826
+ /* @__PURE__ */ jsxs10("label", { className: "flex items-center gap-1 text-xs text-foreground", title: "Hide what lies outside the frame", children: [
1827
+ /* @__PURE__ */ jsx10("input", { type: "checkbox", "aria-label": "Clip content", checked: f.frame.clip !== false, onChange: (e) => controller.setFrame({ clip: e.target.checked }, { ids }) }),
1828
+ "Clip"
1829
+ ] })
1830
+ ] })
1831
+ ] }),
1832
+ panel.resizing && /* @__PURE__ */ jsxs10("div", { className: "space-y-1", "data-vector-resizing": "", children: [
1833
+ /* @__PURE__ */ jsxs10("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1834
+ /* @__PURE__ */ jsx10("span", { className: "w-16", children: "Horizontal" }),
1835
+ /* @__PURE__ */ jsxs10(
1836
+ "select",
1837
+ {
1838
+ "aria-label": "Horizontal constraint",
1839
+ className: selectClass6,
1840
+ value: panel.resizing.horizontal,
1841
+ onChange: (e) => controller.setResizing({ horizontal: e.target.value }, { ids: panel.resizing.ids }),
1842
+ children: [
1843
+ panel.resizing.horizontal === "mixed" && /* @__PURE__ */ jsx10("option", { value: "mixed", disabled: true, children: "Mixed" }),
1844
+ VECTOR_HORIZONTAL_RESIZING.map((h) => /* @__PURE__ */ jsx10("option", { value: h, children: VECTOR_HORIZONTAL_RESIZING_LABELS[h] }, h))
1845
+ ]
1846
+ }
1847
+ )
1848
+ ] }),
1849
+ /* @__PURE__ */ jsxs10("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1850
+ /* @__PURE__ */ jsx10("span", { className: "w-16", children: "Vertical" }),
1851
+ /* @__PURE__ */ jsxs10(
1852
+ "select",
1853
+ {
1854
+ "aria-label": "Vertical constraint",
1855
+ className: selectClass6,
1856
+ value: panel.resizing.vertical,
1857
+ onChange: (e) => controller.setResizing({ vertical: e.target.value }, { ids: panel.resizing.ids }),
1858
+ children: [
1859
+ panel.resizing.vertical === "mixed" && /* @__PURE__ */ jsx10("option", { value: "mixed", disabled: true, children: "Mixed" }),
1860
+ VECTOR_VERTICAL_RESIZING.map((v) => /* @__PURE__ */ jsx10("option", { value: v, children: VECTOR_VERTICAL_RESIZING_LABELS[v] }, v))
1861
+ ]
1862
+ }
1863
+ )
1864
+ ] })
1865
+ ] })
1866
+ ] });
1867
+ }
1868
+
1869
+ // src/vector/VectorConnectorPanel.tsx
1870
+ import {
1871
+ VECTOR_CONNECTOR_ROUTES,
1872
+ VECTOR_CONNECTOR_ROUTE_LABELS,
1873
+ VECTOR_CONNECTOR_SIDES,
1874
+ VECTOR_CONNECTOR_SIDE_LABELS,
1875
+ getVectorConnectorPanel
1876
+ } from "@pixerate/editor/vector";
1877
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1878
+ var buttonClass9 = "inline-flex h-7 min-w-7 items-center justify-center rounded-md border border-border bg-background px-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40";
1879
+ var selectClass7 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1880
+ var numberClass5 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
1881
+ function VectorConnectorPanel({ controller, className }) {
1882
+ const state = useVectorEditorState(controller);
1883
+ const panel = getVectorConnectorPanel(state);
1884
+ if (!panel) return null;
1885
+ const ids = [panel.id];
1886
+ const end = (key, label, e) => /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1", "data-vector-connector-end": key, children: [
1887
+ /* @__PURE__ */ jsx11("span", { className: "w-16 text-xs text-foreground", children: label }),
1888
+ e ? /* @__PURE__ */ jsxs11(Fragment2, { children: [
1889
+ /* @__PURE__ */ jsx11(
1890
+ "select",
1891
+ {
1892
+ "aria-label": `${label} side`,
1893
+ className: selectClass7,
1894
+ value: e.side ?? "auto",
1895
+ onChange: (ev) => controller.setConnector({ [key]: { nodeId: e.nodeId, side: ev.target.value } }, { ids }),
1896
+ children: VECTOR_CONNECTOR_SIDES.map((s) => /* @__PURE__ */ jsx11("option", { value: s, children: VECTOR_CONNECTOR_SIDE_LABELS[s] }, s))
1897
+ }
1898
+ ),
1899
+ /* @__PURE__ */ jsx11("button", { type: "button", className: buttonClass9, title: "Unglue: the end stays where it is", "aria-label": `Unglue ${label.toLowerCase()}`, onClick: () => controller.setConnector({ [key]: null }, { ids }), children: "Unglue" })
1900
+ ] }) : /* @__PURE__ */ jsx11("span", { className: "text-[11px] text-muted-foreground", children: "Free" })
1901
+ ] });
1902
+ return /* @__PURE__ */ jsxs11("div", { className: `space-y-2 ${className ?? ""}`, "data-vector-connector-panel": panel.id, "aria-label": "Connector", children: [
1903
+ /* @__PURE__ */ jsxs11("div", { className: "flex items-center gap-1", children: [
1904
+ /* @__PURE__ */ jsx11("span", { className: "w-16 text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: "Connector" }),
1905
+ /* @__PURE__ */ jsx11("select", { "aria-label": "Connector route", className: selectClass7, value: panel.route, onChange: (e) => controller.setConnector({ route: e.target.value }, { ids }), children: VECTOR_CONNECTOR_ROUTES.map((r) => /* @__PURE__ */ jsx11("option", { value: r, children: VECTOR_CONNECTOR_ROUTE_LABELS[r] }, r)) }),
1906
+ panel.route === "elbow" && /* @__PURE__ */ jsx11(
1907
+ "input",
1908
+ {
1909
+ type: "number",
1910
+ min: "0",
1911
+ step: "1",
1912
+ "aria-label": "Elbow radius",
1913
+ title: "Corner radius of the bends",
1914
+ className: numberClass5,
1915
+ value: panel.radius,
1916
+ onChange: (e) => {
1917
+ const n = parseFloat(e.target.value);
1918
+ if (Number.isFinite(n) && n >= 0) controller.setConnector({ radius: n }, { ids });
1919
+ }
1920
+ }
1921
+ )
1922
+ ] }),
1923
+ end("start", "Start", panel.start),
1924
+ end("end", "End", panel.end)
1925
+ ] });
1926
+ }
1927
+
1226
1928
  // src/vector/VectorGradientEditor.tsx
1227
1929
  import {
1228
1930
  VECTOR_GRADIENT_TYPES,
@@ -1234,12 +1936,12 @@ import {
1234
1936
  VECTOR_GRADIENT_INTERPOLATION_LABELS,
1235
1937
  VECTOR_STROKE_GRADIENT_MODE_LABELS,
1236
1938
  getVectorGradientEditor,
1237
- vectorPaintToHex
1939
+ vectorPaintToHex as vectorPaintToHex3
1238
1940
  } from "@pixerate/editor/vector";
1239
- import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1240
- var selectClass5 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1241
- var numberClass4 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
1242
- var buttonClass5 = "inline-flex h-7 min-w-7 items-center justify-center rounded border border-border bg-background px-1.5 text-xs text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
1941
+ import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1942
+ var selectClass8 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1943
+ var numberClass6 = "h-7 w-14 rounded border border-border bg-background px-1.5 text-xs";
1944
+ var buttonClass10 = "inline-flex h-7 min-w-7 items-center justify-center rounded border border-border bg-background px-1.5 text-xs text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
1243
1945
  var percent = (v) => Math.round(v * 1e3) / 10;
1244
1946
  function VectorGradientEditor({ controller, className }) {
1245
1947
  const state = useVectorEditorState(controller);
@@ -1251,26 +1953,26 @@ function VectorGradientEditor({ controller, className }) {
1251
1953
  const setType = (value) => {
1252
1954
  if (value === "solid") {
1253
1955
  const first = ed.stops[0];
1254
- controller.setStyle({ [ed.target]: vectorPaintToHex(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
1956
+ controller.setStyle({ [ed.target]: vectorPaintToHex3(first?.color) ?? first?.color ?? "#000000" }, void 0, { label: "Solid Colour" });
1255
1957
  } else controller.setGradient({ type: value });
1256
1958
  };
1257
- return /* @__PURE__ */ jsxs7("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
1258
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1.5", children: [
1259
- /* @__PURE__ */ jsx7("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
1260
- ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass5, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
1261
- ed.mixed && /* @__PURE__ */ jsx7("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1959
+ return /* @__PURE__ */ jsxs12("div", { className: `space-y-1.5 ${className ?? ""}`, "data-vector-gradient-editor": ed.target, "aria-label": "Gradient", children: [
1960
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5", children: [
1961
+ /* @__PURE__ */ jsx12("span", { className: "w-16 text-xs text-foreground", children: "Gradient" }),
1962
+ ["fill", "stroke"].map((t) => /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "aria-pressed": ed.target === t, onClick: () => controller.setGradientTarget(t), children: t === "fill" ? "Fill" : "Stroke" }, t)),
1963
+ ed.mixed && /* @__PURE__ */ jsx12("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1262
1964
  ] }),
1263
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1.5", children: [
1264
- /* @__PURE__ */ jsx7("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
1265
- /* @__PURE__ */ jsxs7("select", { "aria-label": "Gradient type", className: selectClass5, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
1266
- /* @__PURE__ */ jsx7("option", { value: "solid", children: "Solid" }),
1267
- VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx7("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
1965
+ /* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-1.5", children: [
1966
+ /* @__PURE__ */ jsx12("span", { className: "w-16 text-xs text-foreground", children: "Type" }),
1967
+ /* @__PURE__ */ jsxs12("select", { "aria-label": "Gradient type", className: selectClass8, value: ed.type ?? "solid", onChange: (e) => setType(e.target.value), children: [
1968
+ /* @__PURE__ */ jsx12("option", { value: "solid", children: "Solid" }),
1969
+ VECTOR_GRADIENT_TYPES.map((t) => /* @__PURE__ */ jsx12("option", { value: t, children: VECTOR_GRADIENT_TYPE_LABELS[t] }, t))
1268
1970
  ] }),
1269
- /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass5, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
1971
+ /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "aria-label": "Reverse gradient", title: "Reverse gradient", disabled: !g, onClick: () => controller.reverseGradient(), children: "\u21C4" })
1270
1972
  ] }),
1271
- g && /* @__PURE__ */ jsxs7(Fragment2, { children: [
1272
- /* @__PURE__ */ jsxs7("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
1273
- /* @__PURE__ */ jsx7(
1973
+ g && /* @__PURE__ */ jsxs12(Fragment3, { children: [
1974
+ /* @__PURE__ */ jsxs12("div", { className: "relative h-8", "data-vector-gradient-ramp": "", children: [
1975
+ /* @__PURE__ */ jsx12(
1274
1976
  "div",
1275
1977
  {
1276
1978
  role: "button",
@@ -1284,7 +1986,7 @@ function VectorGradientEditor({ controller, className }) {
1284
1986
  }
1285
1987
  }
1286
1988
  ),
1287
- ed.stops.map((s) => /* @__PURE__ */ jsx7(
1989
+ ed.stops.map((s) => /* @__PURE__ */ jsx12(
1288
1990
  "button",
1289
1991
  {
1290
1992
  type: "button",
@@ -1298,21 +2000,21 @@ function VectorGradientEditor({ controller, className }) {
1298
2000
  s.index
1299
2001
  ))
1300
2002
  ] }),
1301
- sel && /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
1302
- /* @__PURE__ */ jsx7(
2003
+ sel && /* @__PURE__ */ jsxs12("div", { className: "flex flex-wrap items-center gap-1.5", "data-vector-gradient-selected-stop": sel.index, children: [
2004
+ /* @__PURE__ */ jsx12(
1303
2005
  "input",
1304
2006
  {
1305
2007
  type: "color",
1306
2008
  "aria-label": "Stop colour",
1307
2009
  className: "h-7 w-9 cursor-pointer rounded border border-border bg-background",
1308
- value: vectorPaintToHex(sel.color) ?? "#000000",
2010
+ value: vectorPaintToHex3(sel.color) ?? "#000000",
1309
2011
  onChange: (e) => controller.setGradientStop(sel.index, { color: e.target.value }, { merge: `gradient-stop-color-${sel.index}` }),
1310
2012
  onBlur: end
1311
2013
  }
1312
2014
  ),
1313
- /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2015
+ /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
1314
2016
  "Opacity",
1315
- /* @__PURE__ */ jsx7(
2017
+ /* @__PURE__ */ jsx12(
1316
2018
  "input",
1317
2019
  {
1318
2020
  type: "number",
@@ -1320,7 +2022,7 @@ function VectorGradientEditor({ controller, className }) {
1320
2022
  max: 100,
1321
2023
  step: 5,
1322
2024
  "aria-label": "Stop opacity",
1323
- className: numberClass4,
2025
+ className: numberClass6,
1324
2026
  value: percent(sel.opacity ?? 1),
1325
2027
  onChange: (e) => {
1326
2028
  const n = parseFloat(e.target.value);
@@ -1329,9 +2031,9 @@ function VectorGradientEditor({ controller, className }) {
1329
2031
  }
1330
2032
  )
1331
2033
  ] }),
1332
- /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2034
+ /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
1333
2035
  "At",
1334
- /* @__PURE__ */ jsx7(
2036
+ /* @__PURE__ */ jsx12(
1335
2037
  "input",
1336
2038
  {
1337
2039
  type: "number",
@@ -1339,7 +2041,7 @@ function VectorGradientEditor({ controller, className }) {
1339
2041
  max: 100,
1340
2042
  step: 1,
1341
2043
  "aria-label": "Stop position",
1342
- className: numberClass4,
2044
+ className: numberClass6,
1343
2045
  value: percent(sel.offset),
1344
2046
  onChange: (e) => {
1345
2047
  const n = parseFloat(e.target.value);
@@ -1348,9 +2050,9 @@ function VectorGradientEditor({ controller, className }) {
1348
2050
  }
1349
2051
  )
1350
2052
  ] }),
1351
- !sel.last && /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
2053
+ !sel.last && /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1 text-[11px] text-muted-foreground", children: [
1352
2054
  "Midpoint",
1353
- /* @__PURE__ */ jsx7(
2055
+ /* @__PURE__ */ jsx12(
1354
2056
  "input",
1355
2057
  {
1356
2058
  type: "number",
@@ -1358,7 +2060,7 @@ function VectorGradientEditor({ controller, className }) {
1358
2060
  max: 95,
1359
2061
  step: 1,
1360
2062
  "aria-label": "Stop midpoint",
1361
- className: numberClass4,
2063
+ className: numberClass6,
1362
2064
  value: percent(sel.midpoint ?? 0.5),
1363
2065
  onChange: (e) => {
1364
2066
  const n = parseFloat(e.target.value);
@@ -1367,15 +2069,15 @@ function VectorGradientEditor({ controller, className }) {
1367
2069
  }
1368
2070
  )
1369
2071
  ] }),
1370
- /* @__PURE__ */ jsx7("button", { type: "button", className: buttonClass5, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
2072
+ /* @__PURE__ */ jsx12("button", { type: "button", className: buttonClass10, "aria-label": "Delete stop", title: "Delete stop", disabled: ed.stops.length <= 2, onClick: () => controller.removeGradientStop(sel.index), children: "\u2715" })
1371
2073
  ] }),
1372
- /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1373
- /* @__PURE__ */ jsx7("span", { className: "w-16", children: "Colours" }),
1374
- /* @__PURE__ */ jsx7("select", { "aria-label": "Gradient interpolation", className: selectClass5, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx7("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
2074
+ /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2075
+ /* @__PURE__ */ jsx12("span", { className: "w-16", children: "Colours" }),
2076
+ /* @__PURE__ */ jsx12("select", { "aria-label": "Gradient interpolation", className: selectClass8, value: ed.interpolation, onChange: (e) => controller.setGradient({ interpolation: e.target.value }), children: VECTOR_GRADIENT_INTERPOLATIONS.map((i) => /* @__PURE__ */ jsx12("option", { value: i, children: VECTOR_GRADIENT_INTERPOLATION_LABELS[i] }, i)) })
1375
2077
  ] }),
1376
- /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1377
- /* @__PURE__ */ jsx7("span", { className: "w-16", children: "Smooth" }),
1378
- /* @__PURE__ */ jsx7(
2078
+ /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2079
+ /* @__PURE__ */ jsx12("span", { className: "w-16", children: "Smooth" }),
2080
+ /* @__PURE__ */ jsx12(
1379
2081
  "input",
1380
2082
  {
1381
2083
  type: "range",
@@ -1391,23 +2093,23 @@ function VectorGradientEditor({ controller, className }) {
1391
2093
  }
1392
2094
  )
1393
2095
  ] }),
1394
- /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1395
- /* @__PURE__ */ jsx7("span", { className: "w-16", children: "Spread" }),
1396
- /* @__PURE__ */ jsx7("select", { "aria-label": "Gradient spread", className: selectClass5, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx7("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
2096
+ /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2097
+ /* @__PURE__ */ jsx12("span", { className: "w-16", children: "Spread" }),
2098
+ /* @__PURE__ */ jsx12("select", { "aria-label": "Gradient spread", className: selectClass8, value: ed.spread, onChange: (e) => controller.setGradient({ spread: e.target.value }), children: VECTOR_GRADIENT_SPREADS.map((sp) => /* @__PURE__ */ jsx12("option", { value: sp, children: VECTOR_GRADIENT_SPREAD_LABELS[sp] }, sp)) })
1397
2099
  ] }),
1398
- ed.target === "stroke" && /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
1399
- /* @__PURE__ */ jsx7("span", { className: "w-16", children: "On stroke" }),
1400
- /* @__PURE__ */ jsx7("select", { "aria-label": "Gradient on stroke", className: selectClass5, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx7("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
2100
+ ed.target === "stroke" && /* @__PURE__ */ jsxs12("label", { className: "flex items-center gap-1.5 text-xs text-foreground", children: [
2101
+ /* @__PURE__ */ jsx12("span", { className: "w-16", children: "On stroke" }),
2102
+ /* @__PURE__ */ jsx12("select", { "aria-label": "Gradient on stroke", className: selectClass8, value: ed.strokeMode, onChange: (e) => controller.setGradient({ strokeMode: e.target.value }), children: VECTOR_STROKE_GRADIENT_MODES.map((m) => /* @__PURE__ */ jsx12("option", { value: m, children: VECTOR_STROKE_GRADIENT_MODE_LABELS[m] }, m)) })
1401
2103
  ] })
1402
2104
  ] })
1403
2105
  ] });
1404
2106
  }
1405
2107
 
1406
2108
  // src/vector/VectorToolbar.tsx
1407
- import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2109
+ import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1408
2110
  var VECTOR_SNAP_TITLE = "Snap to objects";
1409
2111
  var modKeyLabel = () => typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform ?? "") ? "\u2318" : "Ctrl";
1410
- var buttonClass6 = "inline-flex h-8 min-w-8 items-center justify-center rounded-md border border-border bg-background px-2 text-xs font-medium text-foreground hover:bg-muted disabled:cursor-not-allowed disabled:opacity-40 aria-pressed:bg-primary aria-pressed:text-primary-foreground";
2112
+ var buttonClass11 = "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";
1411
2113
  function VectorToolbar({
1412
2114
  controller,
1413
2115
  tools = VECTOR_TOOLS,
@@ -1425,16 +2127,16 @@ function VectorToolbar({
1425
2127
  const editing = state.editing;
1426
2128
  const groupSelected = state.selection.some((id) => controller.getNode(id)?.type === "group");
1427
2129
  const maskSelected = state.selection.some((id) => isVectorMaskGroup(controller.getNode(id)));
1428
- const [live, setLive] = React4.useState(false);
2130
+ const [live, setLive] = React7.useState(false);
1429
2131
  const booleanDisabled = live ? count < 1 : count < 2;
1430
- const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx8("button", { type: "button", className: buttonClass6, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
1431
- return /* @__PURE__ */ jsxs8("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
1432
- /* @__PURE__ */ jsx8("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx8(
2132
+ const act = (label, title, run, disabled = false) => /* @__PURE__ */ jsx13("button", { type: "button", className: buttonClass11, title, "aria-label": title, disabled, onClick: () => void run(), children: label }, label);
2133
+ return /* @__PURE__ */ jsxs13("div", { role: "toolbar", "aria-label": "Vector tools", className: `flex flex-wrap items-center gap-1 ${className ?? ""}`, children: [
2134
+ /* @__PURE__ */ jsx13("div", { className: "flex items-center gap-1", role: "group", "aria-label": "Tools", children: tools.map((tool) => /* @__PURE__ */ jsx13(
1433
2135
  "button",
1434
2136
  {
1435
2137
  type: "button",
1436
2138
  "data-vector-tool": tool,
1437
- className: buttonClass6,
2139
+ className: buttonClass11,
1438
2140
  "aria-pressed": state.tool === tool,
1439
2141
  title: `${vectorToolLabel(tool)} (${VECTOR_TOOL_SHORTCUTS[tool]})`,
1440
2142
  "aria-label": vectorToolLabel(tool),
@@ -1443,30 +2145,50 @@ function VectorToolbar({
1443
2145
  },
1444
2146
  tool
1445
2147
  )) }),
1446
- showHistory && /* @__PURE__ */ jsxs8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
2148
+ showHistory && /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "History", children: [
1447
2149
  act("\u21B6", state.canUndo ? `Undo ${state.undoLabel}` : "Undo", () => controller.undo(), !state.canUndo),
1448
2150
  act("\u21B7", state.canRedo ? `Redo ${state.redoLabel}` : "Redo", () => controller.redo(), !state.canRedo)
1449
2151
  ] }),
1450
- showSnap && /* @__PURE__ */ jsx8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: /* @__PURE__ */ jsx8(
1451
- "button",
1452
- {
1453
- type: "button",
1454
- "data-vector-snap": "",
1455
- className: buttonClass6,
1456
- "aria-pressed": state.snap.objects,
1457
- title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
1458
- "aria-label": VECTOR_SNAP_TITLE,
1459
- onClick: () => controller.toggleSnapping(),
1460
- children: "Snap"
1461
- }
1462
- ) }),
1463
- showPathEdit && /* @__PURE__ */ jsxs8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
1464
- VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx8(
2152
+ showSnap && /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Snapping", children: [
2153
+ /* @__PURE__ */ jsx13(
2154
+ "button",
2155
+ {
2156
+ type: "button",
2157
+ "data-vector-snap": "",
2158
+ className: buttonClass11,
2159
+ "aria-pressed": state.snap.objects,
2160
+ title: `${VECTOR_SNAP_TITLE} (hold ${modKeyLabel()} to bypass)`,
2161
+ "aria-label": VECTOR_SNAP_TITLE,
2162
+ onClick: () => controller.toggleSnapping(),
2163
+ children: "Snap"
2164
+ }
2165
+ ),
2166
+ /* @__PURE__ */ jsxs13(
2167
+ "select",
2168
+ {
2169
+ "data-vector-polar": "",
2170
+ "aria-label": "Polar tracking increment",
2171
+ title: "Polar tracking: the angles drawn points snap to from where they leave",
2172
+ className: "h-8 rounded-md border border-border bg-background px-1 text-xs",
2173
+ value: String(vectorPolarIncrementOf(state.snap.angles) ?? ""),
2174
+ onChange: (e) => controller.setSnap({ angles: vectorPolarAngles(Number(e.target.value)) }),
2175
+ children: [
2176
+ vectorPolarIncrementOf(state.snap.angles) === null && /* @__PURE__ */ jsx13("option", { value: "", children: "Custom" }),
2177
+ [90, 45, 30, 15].map((n) => /* @__PURE__ */ jsxs13("option", { value: n, children: [
2178
+ n,
2179
+ "\xB0"
2180
+ ] }, n))
2181
+ ]
2182
+ }
2183
+ )
2184
+ ] }),
2185
+ showPathEdit && /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Path editing", children: [
2186
+ VECTOR_PATH_ACTIONS.map((a) => /* @__PURE__ */ jsx13(
1465
2187
  "button",
1466
2188
  {
1467
2189
  type: "button",
1468
2190
  "data-vector-path-action": a.id,
1469
- className: buttonClass6,
2191
+ className: buttonClass11,
1470
2192
  title: a.shortcut ? `${a.title} (${modKeyLabel()}+${a.shortcut})` : a.title,
1471
2193
  "aria-label": a.title,
1472
2194
  disabled: !a.enabled(count, points),
@@ -1475,12 +2197,12 @@ function VectorToolbar({
1475
2197
  },
1476
2198
  a.id
1477
2199
  )),
1478
- VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx8(
2200
+ VECTOR_EDIT_TOGGLES.map((t) => /* @__PURE__ */ jsx13(
1479
2201
  "button",
1480
2202
  {
1481
2203
  type: "button",
1482
2204
  "data-vector-edit": t.key,
1483
- className: buttonClass6,
2205
+ className: buttonClass11,
1484
2206
  "aria-pressed": editing[t.key],
1485
2207
  title: t.title,
1486
2208
  "aria-label": t.title,
@@ -1489,9 +2211,9 @@ function VectorToolbar({
1489
2211
  },
1490
2212
  t.key
1491
2213
  )),
1492
- state.tool === "pencil" && /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2214
+ state.tool === "pencil" && /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
1493
2215
  "Smoothing",
1494
- /* @__PURE__ */ jsx8(
2216
+ /* @__PURE__ */ jsx13(
1495
2217
  "input",
1496
2218
  {
1497
2219
  type: "range",
@@ -1505,9 +2227,9 @@ function VectorToolbar({
1505
2227
  }
1506
2228
  )
1507
2229
  ] }),
1508
- state.tool === "eraser" && /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
2230
+ state.tool === "eraser" && /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-1 text-xs text-foreground", children: [
1509
2231
  "Size",
1510
- /* @__PURE__ */ jsx8(
2232
+ /* @__PURE__ */ jsx13(
1511
2233
  "input",
1512
2234
  {
1513
2235
  type: "number",
@@ -1523,12 +2245,12 @@ function VectorToolbar({
1523
2245
  }
1524
2246
  )
1525
2247
  ] }),
1526
- state.tool === "paintBucket" && /* @__PURE__ */ jsx8(
2248
+ state.tool === "paintBucket" && /* @__PURE__ */ jsx13(
1527
2249
  "button",
1528
2250
  {
1529
2251
  type: "button",
1530
2252
  "data-vector-fill-gap": "",
1531
- className: buttonClass6,
2253
+ className: buttonClass11,
1532
2254
  "aria-pressed": editing.fillGap > 0,
1533
2255
  title: "Close small gaps between path ends when filling",
1534
2256
  "aria-label": "Close gaps",
@@ -1537,12 +2259,12 @@ function VectorToolbar({
1537
2259
  }
1538
2260
  )
1539
2261
  ] }),
1540
- showDistort && /* @__PURE__ */ jsx8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx8(
2262
+ showDistort && /* @__PURE__ */ jsx13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Distort", children: /* @__PURE__ */ jsx13(
1541
2263
  "button",
1542
2264
  {
1543
2265
  type: "button",
1544
2266
  "data-vector-free-distort": "",
1545
- className: buttonClass6,
2267
+ className: buttonClass11,
1546
2268
  "aria-pressed": state.freeDistort,
1547
2269
  title: `Free Distort: drag a corner of the selection box to distort in perspective (Shift+Alt+${modKeyLabel()}: symmetric perspective; ${modKeyLabel()}-drag a side to skew)`,
1548
2270
  "aria-label": "Free Distort",
@@ -1550,15 +2272,15 @@ function VectorToolbar({
1550
2272
  children: "Distort"
1551
2273
  }
1552
2274
  ) }),
1553
- showBlend && /* @__PURE__ */ jsx8(VectorBlendControls, { controller }),
1554
- showArrange && /* @__PURE__ */ jsxs8(Fragment3, { children: [
1555
- /* @__PURE__ */ jsxs8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
1556
- /* @__PURE__ */ jsx8(
2275
+ showBlend && /* @__PURE__ */ jsx13(VectorBlendControls, { controller }),
2276
+ showArrange && /* @__PURE__ */ jsxs13(Fragment4, { children: [
2277
+ /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Combine", children: [
2278
+ /* @__PURE__ */ jsx13(
1557
2279
  "button",
1558
2280
  {
1559
2281
  type: "button",
1560
2282
  "data-vector-live-boolean": "",
1561
- className: buttonClass6,
2283
+ className: buttonClass11,
1562
2284
  "aria-pressed": live,
1563
2285
  title: "Live booleans: keep the shapes editable",
1564
2286
  "aria-label": "Live booleans",
@@ -1575,12 +2297,12 @@ function VectorToolbar({
1575
2297
  act("\u25D0", `Make Clipping Mask (${modKeyLabel()}+7)`, () => controller.makeMask("clip"), count < 2),
1576
2298
  act("\u25D1", `Release Mask (${modKeyLabel()}+Alt+7)`, () => controller.releaseMask(), !maskSelected)
1577
2299
  ] }),
1578
- /* @__PURE__ */ jsx8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx8(
2300
+ /* @__PURE__ */ jsx13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Pathfinder", children: VECTOR_PATHFINDER_ACTIONS.map((a) => /* @__PURE__ */ jsx13(
1579
2301
  "button",
1580
2302
  {
1581
2303
  type: "button",
1582
2304
  "data-vector-pathfinder": a.id,
1583
- className: buttonClass6,
2305
+ className: buttonClass11,
1584
2306
  title: a.title,
1585
2307
  "aria-label": a.title,
1586
2308
  disabled: count < a.min,
@@ -1589,7 +2311,7 @@ function VectorToolbar({
1589
2311
  },
1590
2312
  a.id
1591
2313
  )) }),
1592
- /* @__PURE__ */ jsxs8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
2314
+ /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Arrange", children: [
1593
2315
  act("\u21C8", "Bring to Front", () => controller.arrange("front"), count < 1),
1594
2316
  act("\u21CA", "Send to Back", () => controller.arrange("back"), count < 1),
1595
2317
  act("\u21E4", "Align Left", () => controller.align("left"), count < 1),
@@ -1607,19 +2329,19 @@ function VectorBlendControls({ controller }) {
1607
2329
  const blend = getVectorBlendControls(state);
1608
2330
  const o = blend.options;
1609
2331
  const selected = blend.ids.length > 0;
1610
- return /* @__PURE__ */ jsxs8("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
1611
- /* @__PURE__ */ jsx8("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass6, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
1612
- /* @__PURE__ */ jsx8(
2332
+ return /* @__PURE__ */ jsxs13("div", { className: "ml-1 flex items-center gap-1", role: "group", "aria-label": "Blend", "data-vector-blend-controls": "", children: [
2333
+ /* @__PURE__ */ jsx13("button", { type: "button", "data-vector-blend-action": "make", className: buttonClass11, title: "Make Blend", "aria-label": "Make Blend", disabled: !blend.canBlend, onClick: () => controller.blend(), children: "\u29D3" }),
2334
+ /* @__PURE__ */ jsx13(
1613
2335
  "select",
1614
2336
  {
1615
2337
  "aria-label": "Blend spacing",
1616
2338
  className: "h-7 rounded border border-border bg-background px-1 text-xs",
1617
2339
  value: o.spacing,
1618
2340
  onChange: (e) => applyVectorBlendOptions(controller, { spacing: e.target.value }),
1619
- children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx8("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
2341
+ children: VECTOR_BLEND_SPACINGS.map((sp) => /* @__PURE__ */ jsx13("option", { value: sp, children: VECTOR_BLEND_SPACING_LABELS[sp] }, sp))
1620
2342
  }
1621
2343
  ),
1622
- o.spacing !== "smoothColor" && /* @__PURE__ */ jsx8(
2344
+ o.spacing !== "smoothColor" && /* @__PURE__ */ jsx13(
1623
2345
  "input",
1624
2346
  {
1625
2347
  type: "number",
@@ -1635,12 +2357,12 @@ function VectorBlendControls({ controller }) {
1635
2357
  }
1636
2358
  }
1637
2359
  ),
1638
- /* @__PURE__ */ jsx8(
2360
+ /* @__PURE__ */ jsx13(
1639
2361
  "button",
1640
2362
  {
1641
2363
  type: "button",
1642
2364
  "data-vector-blend-action": "orient",
1643
- className: buttonClass6,
2365
+ className: buttonClass11,
1644
2366
  "aria-pressed": o.orientToSpine,
1645
2367
  title: "Orient steps to the spine",
1646
2368
  "aria-label": "Orient to spine",
@@ -1648,12 +2370,12 @@ function VectorBlendControls({ controller }) {
1648
2370
  children: "\u293F"
1649
2371
  }
1650
2372
  ),
1651
- /* @__PURE__ */ jsx8(
2373
+ /* @__PURE__ */ jsx13(
1652
2374
  "button",
1653
2375
  {
1654
2376
  type: "button",
1655
2377
  "data-vector-blend-action": "spine",
1656
- className: buttonClass6,
2378
+ className: buttonClass11,
1657
2379
  title: "Replace Spine (select a blend and a path)",
1658
2380
  "aria-label": "Replace Spine",
1659
2381
  disabled: !blend.canReplaceSpine,
@@ -1661,12 +2383,12 @@ function VectorBlendControls({ controller }) {
1661
2383
  children: "\u2307"
1662
2384
  }
1663
2385
  ),
1664
- /* @__PURE__ */ jsx8("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass6, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
1665
- /* @__PURE__ */ jsx8("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass6, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
2386
+ /* @__PURE__ */ jsx13("button", { type: "button", "data-vector-blend-action": "expand", className: buttonClass11, title: "Expand Blend", "aria-label": "Expand Blend", disabled: !selected, onClick: () => controller.expandBlend(), children: "\u29C9" }),
2387
+ /* @__PURE__ */ jsx13("button", { type: "button", "data-vector-blend-action": "release", className: buttonClass11, title: "Release Blend", "aria-label": "Release Blend", disabled: !selected, onClick: () => controller.releaseBlend(), children: "\u29C4" })
1666
2388
  ] });
1667
2389
  }
1668
- var selectClass6 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
1669
- var numberClass5 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
2390
+ var selectClass9 = "h-7 min-w-0 flex-1 rounded border border-border bg-background px-1 text-xs";
2391
+ var numberClass7 = "h-7 w-16 rounded border border-border bg-background px-1.5 text-xs";
1670
2392
  function VectorStylePanel({ controller, className }) {
1671
2393
  const state = useVectorEditorState(controller);
1672
2394
  const style = getVectorSelectionStyle(state);
@@ -1685,6 +2407,7 @@ function VectorStylePanel({ controller, className }) {
1685
2407
  widthProfile: widthProfilePresetId(d.strokeWidthProfile),
1686
2408
  cornerRadius: d.cornerRadius,
1687
2409
  cornerKind: d.cornerKind ?? "round",
2410
+ cornerSmoothing: 0,
1688
2411
  corners: 1,
1689
2412
  hasOpenPath: true
1690
2413
  };
@@ -1697,17 +2420,17 @@ function VectorStylePanel({ controller, className }) {
1697
2420
  if (style) controller.setWidthProfile(id);
1698
2421
  else controller.setDefaults({ strokeWidthProfile: widthProfilePreset(id)?.length ? widthProfilePreset(id) : null });
1699
2422
  };
1700
- const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx8("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
2423
+ const mixed = (key) => style?.mixed[key] ? /* @__PURE__ */ jsx13("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" }) : null;
1701
2424
  const arrowRow = (end, label) => {
1702
2425
  const key = end === "start" ? "startArrow" : "endArrow";
1703
2426
  const scaleKey = end === "start" ? "startArrowScale" : "endArrowScale";
1704
- return /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
1705
- /* @__PURE__ */ jsx8("span", { className: "w-16 text-xs text-foreground", children: label }),
1706
- /* @__PURE__ */ jsxs8("select", { "aria-label": `${label} arrowhead`, className: selectClass6, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
1707
- /* @__PURE__ */ jsx8("option", { value: "", children: "None" }),
1708
- VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx8("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
2427
+ return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2428
+ /* @__PURE__ */ jsx13("span", { className: "w-16 text-xs text-foreground", children: label }),
2429
+ /* @__PURE__ */ jsxs13("select", { "aria-label": `${label} arrowhead`, className: selectClass9, value: source[key] ?? "", onChange: (e) => apply({ [key]: e.target.value || null }), children: [
2430
+ /* @__PURE__ */ jsx13("option", { value: "", children: "None" }),
2431
+ VECTOR_ARROWHEADS.map((h) => /* @__PURE__ */ jsx13("option", { value: h, children: VECTOR_ARROWHEAD_LABELS[h] }, h))
1709
2432
  ] }),
1710
- /* @__PURE__ */ jsx8(
2433
+ /* @__PURE__ */ jsx13(
1711
2434
  "input",
1712
2435
  {
1713
2436
  type: "number",
@@ -1715,7 +2438,7 @@ function VectorStylePanel({ controller, className }) {
1715
2438
  step: 0.25,
1716
2439
  "aria-label": `${label} arrowhead scale`,
1717
2440
  title: "Scale (\xD7 stroke width)",
1718
- className: numberClass5,
2441
+ className: numberClass7,
1719
2442
  value: source[scaleKey],
1720
2443
  onChange: (e) => {
1721
2444
  const n = parseFloat(e.target.value);
@@ -1731,14 +2454,14 @@ function VectorStylePanel({ controller, className }) {
1731
2454
  };
1732
2455
  const paintRow = (key, label) => {
1733
2456
  const paint = source[key];
1734
- const hex = vectorPaintToHex2(paint);
2457
+ const hex = vectorPaintToHex4(paint);
1735
2458
  const on = paint !== null && paint !== "none";
1736
- return /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
1737
- /* @__PURE__ */ jsxs8("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
1738
- /* @__PURE__ */ jsx8("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
2459
+ return /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", "data-vector-style": key, children: [
2460
+ /* @__PURE__ */ jsxs13("label", { className: "flex w-16 items-center gap-1.5 text-xs text-foreground", children: [
2461
+ /* @__PURE__ */ jsx13("input", { type: "checkbox", checked: on, "aria-label": `${label} on`, onChange: (e) => apply({ [key]: e.target.checked ? hex ?? "#000000" : null }) }),
1739
2462
  label
1740
2463
  ] }),
1741
- /* @__PURE__ */ jsx8(
2464
+ /* @__PURE__ */ jsx13(
1742
2465
  "input",
1743
2466
  {
1744
2467
  type: "color",
@@ -1749,18 +2472,18 @@ function VectorStylePanel({ controller, className }) {
1749
2472
  onChange: (e) => apply({ [key]: e.target.value })
1750
2473
  }
1751
2474
  ),
1752
- on && !hex && /* @__PURE__ */ jsx8("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
1753
- style?.mixed[key] && /* @__PURE__ */ jsx8("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
2475
+ on && !hex && /* @__PURE__ */ jsx13("span", { className: "truncate text-[11px] text-muted-foreground", children: typeof paint === "string" ? paint : "Gradient" }),
2476
+ style?.mixed[key] && /* @__PURE__ */ jsx13("span", { className: "text-[11px] text-muted-foreground", children: "Mixed" })
1754
2477
  ] });
1755
2478
  };
1756
- return /* @__PURE__ */ jsxs8("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
1757
- /* @__PURE__ */ jsx8("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
2479
+ return /* @__PURE__ */ jsxs13("div", { className: `space-y-2 ${className ?? ""}`, "aria-label": style ? "Selection style" : "New shape style", children: [
2480
+ /* @__PURE__ */ jsx13("p", { className: "text-[11px] font-medium uppercase tracking-wide text-muted-foreground", children: style ? "Selection" : "New shapes" }),
1758
2481
  paintRow("fill", "Fill"),
1759
2482
  paintRow("stroke", "Stroke"),
1760
- style && /* @__PURE__ */ jsx8(VectorGradientEditor, { controller }),
1761
- /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1762
- /* @__PURE__ */ jsx8("span", { className: "w-16", children: "Width" }),
1763
- /* @__PURE__ */ jsx8(
2483
+ style && /* @__PURE__ */ jsx13(VectorGradientEditor, { controller }),
2484
+ /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2485
+ /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Width" }),
2486
+ /* @__PURE__ */ jsx13(
1764
2487
  "input",
1765
2488
  {
1766
2489
  type: "number",
@@ -1776,33 +2499,33 @@ function VectorStylePanel({ controller, className }) {
1776
2499
  }
1777
2500
  )
1778
2501
  ] }),
1779
- /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
1780
- /* @__PURE__ */ jsx8("span", { className: "w-16", children: "Align" }),
1781
- /* @__PURE__ */ jsx8("select", { "aria-label": "Stroke alignment", className: selectClass6, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS2).map((a) => /* @__PURE__ */ jsx8("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS2[a] }, a)) }),
2502
+ /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "strokeAlign", children: [
2503
+ /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Align" }),
2504
+ /* @__PURE__ */ jsx13("select", { "aria-label": "Stroke alignment", className: selectClass9, value: source.strokeAlign, onChange: (e) => apply({ strokeAlign: e.target.value }), children: Object.keys(VECTOR_STROKE_ALIGN_LABELS2).map((a) => /* @__PURE__ */ jsx13("option", { value: a, children: VECTOR_STROKE_ALIGN_LABELS2[a] }, a)) }),
1782
2505
  mixed("strokeAlign")
1783
2506
  ] }),
1784
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
1785
- /* @__PURE__ */ jsx8("span", { className: "w-16", children: "Profile" }),
1786
- /* @__PURE__ */ jsxs8(
2507
+ /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "widthProfile", children: [
2508
+ /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Profile" }),
2509
+ /* @__PURE__ */ jsxs13(
1787
2510
  "select",
1788
2511
  {
1789
2512
  "aria-label": "Width profile",
1790
- className: selectClass6,
2513
+ className: selectClass9,
1791
2514
  value: source.widthProfile,
1792
2515
  onChange: (e) => {
1793
2516
  if (e.target.value !== "custom") setProfile(e.target.value);
1794
2517
  },
1795
2518
  children: [
1796
- VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx8("option", { value: p.id, children: p.label }, p.id)),
1797
- source.widthProfile === "custom" && /* @__PURE__ */ jsx8("option", { value: "custom", disabled: true, children: "Custom" })
2519
+ VECTOR_WIDTH_PRESETS.map((p) => /* @__PURE__ */ jsx13("option", { value: p.id, children: p.label }, p.id)),
2520
+ source.widthProfile === "custom" && /* @__PURE__ */ jsx13("option", { value: "custom", disabled: true, children: "Custom" })
1798
2521
  ]
1799
2522
  }
1800
2523
  ),
1801
- /* @__PURE__ */ jsx8(
2524
+ /* @__PURE__ */ jsx13(
1802
2525
  "button",
1803
2526
  {
1804
2527
  type: "button",
1805
- className: buttonClass6,
2528
+ className: buttonClass11,
1806
2529
  title: "Reset to uniform width",
1807
2530
  "aria-label": "Reset to uniform width",
1808
2531
  disabled: source.widthProfile === "uniform" && !style?.mixed.widthProfile,
@@ -1812,27 +2535,27 @@ function VectorStylePanel({ controller, className }) {
1812
2535
  ),
1813
2536
  mixed("widthProfile")
1814
2537
  ] }),
1815
- source.hasOpenPath && /* @__PURE__ */ jsxs8(Fragment3, { children: [
2538
+ source.hasOpenPath && /* @__PURE__ */ jsxs13(Fragment4, { children: [
1816
2539
  arrowRow("start", "Start"),
1817
2540
  arrowRow("end", "End"),
1818
- (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
1819
- /* @__PURE__ */ jsx8("span", { className: "w-16", children: "Heads" }),
1820
- /* @__PURE__ */ jsxs8("select", { "aria-label": "Arrowhead alignment", className: selectClass6, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
1821
- /* @__PURE__ */ jsx8("option", { value: "extend", children: "Tip past end" }),
1822
- /* @__PURE__ */ jsx8("option", { value: "tip", children: "Tip at end" })
2541
+ (source.startArrow || source.endArrow) && /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "arrowAlign", children: [
2542
+ /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Heads" }),
2543
+ /* @__PURE__ */ jsxs13("select", { "aria-label": "Arrowhead alignment", className: selectClass9, value: source.arrowAlign, onChange: (e) => apply({ arrowAlign: e.target.value }), children: [
2544
+ /* @__PURE__ */ jsx13("option", { value: "extend", children: "Tip past end" }),
2545
+ /* @__PURE__ */ jsx13("option", { value: "tip", children: "Tip at end" })
1823
2546
  ] })
1824
2547
  ] })
1825
2548
  ] }),
1826
- source.corners > 0 && /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
1827
- /* @__PURE__ */ jsx8("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
1828
- /* @__PURE__ */ jsx8(
2549
+ source.corners > 0 && /* @__PURE__ */ jsxs13("div", { className: "flex items-center gap-2", "data-vector-style": "corners", children: [
2550
+ /* @__PURE__ */ jsx13("span", { className: "w-16 text-xs text-foreground", children: "Corners" }),
2551
+ /* @__PURE__ */ jsx13(
1829
2552
  "input",
1830
2553
  {
1831
2554
  type: "number",
1832
2555
  min: 0,
1833
2556
  step: 1,
1834
2557
  "aria-label": "Corner radius",
1835
- className: numberClass5,
2558
+ className: numberClass7,
1836
2559
  value: Math.round(source.cornerRadius * 100) / 100,
1837
2560
  onChange: (e) => {
1838
2561
  const n = parseFloat(e.target.value);
@@ -1840,12 +2563,29 @@ function VectorStylePanel({ controller, className }) {
1840
2563
  }
1841
2564
  }
1842
2565
  ),
1843
- /* @__PURE__ */ jsx8("select", { "aria-label": "Corner kind", className: selectClass6, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx8("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
2566
+ /* @__PURE__ */ jsx13("select", { "aria-label": "Corner kind", className: selectClass9, value: source.cornerKind, onChange: (e) => setCorners({ kind: e.target.value }), children: VECTOR_CORNER_KINDS.map((k) => /* @__PURE__ */ jsx13("option", { value: k, children: VECTOR_CORNER_KIND_LABELS[k] }, k)) }),
1844
2567
  mixed("cornerRadius")
1845
2568
  ] }),
1846
- style && /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
1847
- /* @__PURE__ */ jsx8("span", { className: "w-16", children: "Opacity" }),
1848
- /* @__PURE__ */ jsx8(
2569
+ style && source.corners > 0 && source.cornerKind === "round" && source.cornerRadius > 0 && /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-2 text-xs text-foreground", "data-vector-style": "corner-smoothing", title: "Corner smoothing: squircle corners that ease into the curve (0.6 is iOS-like)", children: [
2570
+ /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Smoothing" }),
2571
+ /* @__PURE__ */ jsx13(
2572
+ "input",
2573
+ {
2574
+ type: "range",
2575
+ min: 0,
2576
+ max: 1,
2577
+ step: 0.05,
2578
+ "aria-label": "Corner smoothing",
2579
+ className: "h-4 flex-1 accent-primary",
2580
+ value: source.cornerSmoothing,
2581
+ onChange: (e) => controller.setCornerSmoothing(parseFloat(e.target.value), { merge: "corner-smoothing" }),
2582
+ onPointerUp: () => controller.endMergedEdit()
2583
+ }
2584
+ )
2585
+ ] }),
2586
+ style && /* @__PURE__ */ jsxs13("label", { className: "flex items-center gap-2 text-xs text-foreground", children: [
2587
+ /* @__PURE__ */ jsx13("span", { className: "w-16", children: "Opacity" }),
2588
+ /* @__PURE__ */ jsx13(
1849
2589
  "input",
1850
2590
  {
1851
2591
  type: "range",
@@ -1859,31 +2599,39 @@ function VectorStylePanel({ controller, className }) {
1859
2599
  }
1860
2600
  )
1861
2601
  ] }),
1862
- style && /* @__PURE__ */ jsx8(VectorAppearancePanel, { controller }),
1863
- style && /* @__PURE__ */ jsx8(VectorEffectsPanel, { controller }),
1864
- style && /* @__PURE__ */ jsx8(VectorEnvelopePanel, { controller }),
1865
- style && /* @__PURE__ */ jsx8(VectorMaskPanel, { controller })
2602
+ /* @__PURE__ */ jsx13(VectorTextPanel, { controller }),
2603
+ style && /* @__PURE__ */ jsx13(VectorAppearancePanel, { controller }),
2604
+ style && /* @__PURE__ */ jsx13(VectorEffectsPanel, { controller }),
2605
+ style && /* @__PURE__ */ jsx13(VectorEnvelopePanel, { controller }),
2606
+ style && /* @__PURE__ */ jsx13(VectorMaskPanel, { controller }),
2607
+ /* @__PURE__ */ jsx13(VectorFramePanel, { controller }),
2608
+ /* @__PURE__ */ jsx13(VectorConnectorPanel, { controller }),
2609
+ /* @__PURE__ */ jsx13(VectorConstraintsPanel, { controller }),
2610
+ /* @__PURE__ */ jsx13(VectorSymbolsPanel, { controller })
1866
2611
  ] });
1867
2612
  }
1868
2613
 
1869
2614
  // src/vector/VectorEditor.tsx
1870
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2615
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1871
2616
  function VectorEditor({ controller, tools, showStylePanel = true, height = 520, canvasProps, className }) {
1872
- return /* @__PURE__ */ jsxs9("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
1873
- /* @__PURE__ */ jsx9(VectorToolbar, { controller, tools }),
1874
- /* @__PURE__ */ jsxs9("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
1875
- /* @__PURE__ */ jsx9("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx9(VectorCanvas, { controller, ...canvasProps }) }),
1876
- showStylePanel && /* @__PURE__ */ jsx9(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
2617
+ return /* @__PURE__ */ jsxs14("div", { className: `flex flex-col gap-2 ${className ?? ""}`, "data-vector-editor": "", children: [
2618
+ /* @__PURE__ */ jsx14(VectorToolbar, { controller, tools }),
2619
+ /* @__PURE__ */ jsxs14("div", { className: "flex min-h-0 flex-col gap-2 md:flex-row", children: [
2620
+ /* @__PURE__ */ jsx14("div", { className: "relative min-h-[320px] flex-1 overflow-hidden rounded-md border border-border bg-muted/40", style: { height }, children: /* @__PURE__ */ jsx14(VectorCanvas, { controller, ...canvasProps }) }),
2621
+ showStylePanel && /* @__PURE__ */ jsx14(VectorStylePanel, { controller, className: "w-full shrink-0 rounded-md border border-border p-3 md:w-56" })
1877
2622
  ] })
1878
2623
  ] });
1879
2624
  }
1880
2625
  export {
1881
2626
  VectorAppearancePanel,
1882
2627
  VectorCanvas,
2628
+ VectorConnectorPanel,
2629
+ VectorConstraintsPanel,
1883
2630
  VectorEditor,
1884
2631
  VectorEffectsPanel,
1885
2632
  VectorEnvelopePanel,
1886
2633
  VectorFilterDefs,
2634
+ VectorFramePanel,
1887
2635
  VectorGradientAnnotator,
1888
2636
  VectorGradientDefs,
1889
2637
  VectorGradientEditor,
@@ -1891,6 +2639,9 @@ export {
1891
2639
  VectorMaskedNodes,
1892
2640
  VectorSceneNodes,
1893
2641
  VectorStylePanel,
2642
+ VectorSymbolsPanel,
2643
+ VectorTextElement,
2644
+ VectorTextPanel,
1894
2645
  VectorToolbar,
1895
2646
  VectorWidthPointEditor,
1896
2647
  useVectorCommands,