canvu-react 0.3.38 → 0.3.40

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.
Files changed (45) hide show
  1. package/dist/{asset-hydration-EtEuBwb7.d.cts → asset-hydration-B7yMDQE-.d.cts} +2 -2
  2. package/dist/{asset-hydration-DrTOgDdd.d.ts → asset-hydration-CbwQVAwh.d.ts} +2 -2
  3. package/dist/{camera-Di5R_Rwl.d.cts → camera-CVVG7z56.d.cts} +1 -1
  4. package/dist/{camera-AoTwBSoE.d.ts → camera-CoRYN_IV.d.ts} +1 -1
  5. package/dist/chatbot.d.cts +4 -4
  6. package/dist/chatbot.d.ts +4 -4
  7. package/dist/index.cjs +164 -15
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +6 -6
  10. package/dist/index.d.ts +6 -6
  11. package/dist/index.js +159 -16
  12. package/dist/index.js.map +1 -1
  13. package/dist/native.cjs +57 -14
  14. package/dist/native.cjs.map +1 -1
  15. package/dist/native.d.cts +2 -2
  16. package/dist/native.d.ts +2 -2
  17. package/dist/native.js +57 -14
  18. package/dist/native.js.map +1 -1
  19. package/dist/react.cjs +731 -258
  20. package/dist/react.cjs.map +1 -1
  21. package/dist/react.d.cts +10 -10
  22. package/dist/react.d.ts +10 -10
  23. package/dist/react.js +731 -258
  24. package/dist/react.js.map +1 -1
  25. package/dist/realtime.cjs +36 -0
  26. package/dist/realtime.cjs.map +1 -1
  27. package/dist/realtime.d.cts +6 -6
  28. package/dist/realtime.d.ts +6 -6
  29. package/dist/realtime.js +36 -0
  30. package/dist/realtime.js.map +1 -1
  31. package/dist/{shape-builders-CsbSRZnQ.d.cts → shape-builders-BAWu-PxX.d.cts} +46 -4
  32. package/dist/{shape-builders-CsSXKCcs.d.ts → shape-builders-ClKv9tz9.d.ts} +46 -4
  33. package/dist/tldraw.cjs +189 -78
  34. package/dist/tldraw.cjs.map +1 -1
  35. package/dist/tldraw.d.cts +1 -1
  36. package/dist/tldraw.d.ts +1 -1
  37. package/dist/tldraw.js +189 -78
  38. package/dist/tldraw.js.map +1 -1
  39. package/dist/{types-DWGk2_GZ.d.cts → types-BC9Xgfu6.d.cts} +20 -6
  40. package/dist/{types-Bnq2HtHQ.d.cts → types-BCCvY6ie.d.cts} +2 -0
  41. package/dist/{types-Bnq2HtHQ.d.ts → types-BCCvY6ie.d.ts} +2 -0
  42. package/dist/{types-B2Na677H.d.cts → types-BUPc2Zgw.d.cts} +1 -1
  43. package/dist/{types-zmUah-vP.d.ts → types-CYtq9Pr9.d.ts} +1 -1
  44. package/dist/{types-B6PAYKzx.d.ts → types-DlSVGX0w.d.ts} +20 -6
  45. package/package.json +1 -1
package/dist/react.cjs CHANGED
@@ -91,6 +91,12 @@ function lineHeightFor(fontSize) {
91
91
  function firstLineBaselineY(fontSize) {
92
92
  return fontSize * FIRST_LINE_BASELINE_RATIO;
93
93
  }
94
+ function textBaselineYFor(fontSize) {
95
+ return firstLineBaselineY(fontSize);
96
+ }
97
+ function textLineHeightFor(fontSize) {
98
+ return lineHeightFor(fontSize);
99
+ }
94
100
  function measureTextBoundsLocal(content, fontSize = DEFAULT_TEXT_FONT_SIZE) {
95
101
  const cacheKey = textMeasureCacheKey(content, fontSize);
96
102
  const cached = textMeasureCache.get(cacheKey);
@@ -104,7 +110,7 @@ function measureTextBoundsLocal(content, fontSize = DEFAULT_TEXT_FONT_SIZE) {
104
110
  let maxInnerW = 0;
105
111
  const ctx = getSharedMeasureContext();
106
112
  if (ctx) {
107
- ctx.font = `${fontSize}px system-ui, sans-serif`;
113
+ ctx.font = `${fontSize}px ${TEXT_FONT_FAMILY}`;
108
114
  for (const line of lines) {
109
115
  const toMeasure = trimmed.length === 0 ? PLACEHOLDER : line.length === 0 ? " " : line;
110
116
  maxInnerW = Math.max(maxInnerW, ctx.measureText(toMeasure).width);
@@ -120,22 +126,22 @@ function measureTextBoundsLocal(content, fontSize = DEFAULT_TEXT_FONT_SIZE) {
120
126
  const width = Math.max(minW, TEXT_PAD_X * 2 + maxInnerW);
121
127
  const height = Math.max(
122
128
  MIN_TEXT_BOX_H,
123
- baselineY + (lines.length - 1) * lh + Math.max(8, fontSize * 0.35)
129
+ baselineY + (lines.length - 1) * lh + Math.max(4, fontSize * BOTTOM_PAD_RATIO)
124
130
  );
125
131
  const measured = { width, height };
126
132
  cacheMeasuredBounds(cacheKey, measured);
127
133
  return measured;
128
134
  }
129
- function buildTextSvg(content, _width, _height, fillColor = "#2563eb", fontSize = DEFAULT_TEXT_FONT_SIZE) {
135
+ function buildTextSvg(content, _width, _height, fillColor = "#1d1d1d", fontSize = DEFAULT_TEXT_FONT_SIZE) {
130
136
  const lh = lineHeightFor(fontSize);
131
137
  const y0 = firstLineBaselineY(fontSize);
132
138
  const trimmed = content.trim();
133
139
  if (trimmed.length === 0) {
134
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="#94a3b8" font-style="italic">${escapeSvgTextContent(PLACEHOLDER)}</text>`;
140
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="#94a3b8" font-style="italic">${escapeSvgTextContent(PLACEHOLDER)}</text>`;
135
141
  }
136
142
  const lines = content.split("\n");
137
143
  if (lines.length === 1) {
138
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="${fillColor}">${escapeSvgTextContent(lines[0] ?? "")}</text>`;
144
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="${fillColor}">${escapeSvgTextContent(lines[0] ?? "")}</text>`;
139
145
  }
140
146
  const parts = [];
141
147
  for (let i = 0; i < lines.length; i++) {
@@ -146,28 +152,32 @@ function buildTextSvg(content, _width, _height, fillColor = "#2563eb", fontSize
146
152
  parts.push(`<tspan x="4" dy="${lh}">${escapeSvgTextContent(line)}</tspan>`);
147
153
  }
148
154
  }
149
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="${fillColor}">${parts.join("")}</text>`;
155
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="${fillColor}">${parts.join("")}</text>`;
150
156
  }
151
- function buildTextFixedBoundsSvg(content, width, height, fillColor = "#2563eb", fontSize = DEFAULT_TEXT_FONT_SIZE) {
157
+ function buildTextFixedBoundsSvg(content, width, height, fillColor = "#1d1d1d", fontSize = DEFAULT_TEXT_FONT_SIZE) {
152
158
  const w = Math.max(1, width);
153
159
  const h = Math.max(1, height);
154
160
  const lh = lineHeightFor(fontSize);
155
161
  const trimmed = content.trim();
162
+ const padTop = EDIT_TOP_PAD_RATIO * fontSize;
156
163
  if (trimmed.length === 0) {
157
- return `<foreignObject width="${w}" height="${h}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;padding:2px 4px;font-size:${fontSize}px;line-height:${lh}px;font-family:system-ui,sans-serif;white-space:pre-wrap;word-wrap:break-word;overflow:hidden;color:#94a3b8;font-style:italic">${escapeHtmlText(PLACEHOLDER)}</div></foreignObject>`;
164
+ return `<foreignObject width="${w}" height="${h}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;padding:${padTop}px 4px 0 4px;font-size:${fontSize}px;line-height:${lh}px;font-family:${TEXT_FONT_FAMILY};white-space:pre-wrap;word-wrap:break-word;overflow:hidden;color:#94a3b8;font-style:italic">${escapeHtmlText(PLACEHOLDER)}</div></foreignObject>`;
158
165
  }
159
166
  const body = escapeHtmlText(content);
160
- return `<foreignObject width="${w}" height="${h}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;padding:2px 4px;font-size:${fontSize}px;line-height:${lh}px;font-family:system-ui,sans-serif;white-space:pre-wrap;word-wrap:break-word;overflow:hidden;color:${fillColor}">${body}</div></foreignObject>`;
167
+ return `<foreignObject width="${w}" height="${h}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;padding:${padTop}px 4px 0 4px;font-size:${fontSize}px;line-height:${lh}px;font-family:${TEXT_FONT_FAMILY};white-space:pre-wrap;word-wrap:break-word;overflow:hidden;color:${fillColor}">${body}</div></foreignObject>`;
161
168
  }
162
- var DEFAULT_TEXT_FONT_SIZE, LINE_HEIGHT_RATIO, FIRST_LINE_BASELINE_RATIO, PLACEHOLDER, MIN_TEXT_BOX_W, MIN_TEXT_BOX_H, TEXT_PAD_X, MAX_TEXT_MEASURE_CACHE_ENTRIES, sharedMeasureContext, textMeasureCache;
169
+ var DEFAULT_TEXT_FONT_SIZE, TEXT_FONT_FAMILY, LINE_HEIGHT_RATIO, FIRST_LINE_BASELINE_RATIO, EDIT_TOP_PAD_RATIO, BOTTOM_PAD_RATIO, PLACEHOLDER, MIN_TEXT_BOX_W, MIN_TEXT_BOX_H, TEXT_PAD_X, MAX_TEXT_MEASURE_CACHE_ENTRIES, sharedMeasureContext, textMeasureCache;
163
170
  var init_text_svg = __esm({
164
171
  "src/scene/text-svg.ts"() {
165
172
  DEFAULT_TEXT_FONT_SIZE = 18;
173
+ TEXT_FONT_FAMILY = "Inter, -apple-system, BlinkMacSystemFont, ui-sans-serif, system-ui, sans-serif";
166
174
  LINE_HEIGHT_RATIO = 22 / 18;
167
- FIRST_LINE_BASELINE_RATIO = 24 / 18;
175
+ FIRST_LINE_BASELINE_RATIO = 20 / 18;
176
+ EDIT_TOP_PAD_RATIO = 4 / 18;
177
+ BOTTOM_PAD_RATIO = 4 / 18;
168
178
  PLACEHOLDER = "Tap to type";
169
179
  MIN_TEXT_BOX_W = 40;
170
- MIN_TEXT_BOX_H = 36;
180
+ MIN_TEXT_BOX_H = 26;
171
181
  TEXT_PAD_X = 4;
172
182
  MAX_TEXT_MEASURE_CACHE_ENTRIES = 2e3;
173
183
  textMeasureCache = /* @__PURE__ */ new Map();
@@ -280,7 +290,8 @@ function resolveStrokeStyle(item) {
280
290
  return {
281
291
  stroke: item.stroke ?? DEFAULT_STROKE_STYLE.stroke,
282
292
  strokeWidth: item.strokeWidth ?? DEFAULT_STROKE_STYLE.strokeWidth,
283
- strokeOpacity: item.strokeOpacity
293
+ strokeOpacity: item.strokeOpacity,
294
+ strokeDash: item.strokeDash
284
295
  };
285
296
  }
286
297
  function strokeOpacityAttr(style) {
@@ -603,6 +614,30 @@ function buildDrawDotSvg(r, style = DEFAULT_STROKE_STYLE) {
603
614
  const op = style.strokeOpacity != null ? ` fill-opacity="${style.strokeOpacity}"` : "";
604
615
  return `<circle cx="${r}" cy="${r}" r="${r}" fill="${style.stroke}" shape-rendering="geometricPrecision"${op} />`;
605
616
  }
617
+ function dashArrayForDrawStroke(strokeWidth) {
618
+ const dash = Math.max(strokeWidth * 1.8, 4);
619
+ const gap = Math.max(strokeWidth * 1.4, 3);
620
+ return `${dash} ${gap}`;
621
+ }
622
+ function buildSmoothedCenterlinePath(points) {
623
+ if (points.length < 2) return null;
624
+ const first = points[0];
625
+ if (!first) return null;
626
+ let d = `M ${first.x} ${first.y}`;
627
+ for (let i = 1; i < points.length - 1; i++) {
628
+ const a = points[i];
629
+ const b = points[i + 1];
630
+ if (!a || !b) continue;
631
+ const midX = (a.x + b.x) / 2;
632
+ const midY = (a.y + b.y) / 2;
633
+ d += ` Q ${a.x} ${a.y} ${midX} ${midY}`;
634
+ }
635
+ const last = points[points.length - 1];
636
+ if (last) {
637
+ d += ` L ${last.x} ${last.y}`;
638
+ }
639
+ return d;
640
+ }
606
641
  function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeComplete = true) {
607
642
  if (pathPointsLocal.length === 0) return null;
608
643
  if (pathPointsLocal.length === 1) {
@@ -617,6 +652,18 @@ function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeCompl
617
652
  fillOpacity: style.strokeOpacity
618
653
  };
619
654
  }
655
+ if (style.strokeDash === "dashed" && (toolKind === "draw" || toolKind === "pencil")) {
656
+ const d2 = buildSmoothedCenterlinePath(pathPointsLocal);
657
+ if (!d2) return null;
658
+ return {
659
+ kind: "strokePath",
660
+ d: d2,
661
+ stroke: style.stroke,
662
+ strokeWidth: style.strokeWidth,
663
+ strokeOpacity: style.strokeOpacity,
664
+ strokeDasharray: dashArrayForDrawStroke(style.strokeWidth)
665
+ };
666
+ }
620
667
  const hasPressure = pathPointsLocal.some(
621
668
  (p) => p.pressure != null && Number.isFinite(p.pressure)
622
669
  );
@@ -659,7 +706,8 @@ function freehandPayloadToSvgString(payload) {
659
706
  strokeWidth: payload.strokeWidth,
660
707
  strokeOpacity: payload.strokeOpacity
661
708
  });
662
- return `<path d="${payload.d}" fill="none" stroke="${payload.stroke}" stroke-width="${payload.strokeWidth}" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision"${op} />`;
709
+ const dash = payload.strokeDasharray ? ` stroke-dasharray="${payload.strokeDasharray}"` : "";
710
+ return `<path d="${payload.d}" fill="none" stroke="${payload.stroke}" stroke-width="${payload.strokeWidth}" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision"${op}${dash} />`;
663
711
  }
664
712
  function buildFreehandPathSvg(pathPointsLocal, style, toolKind, strokeComplete = true) {
665
713
  const payload = computeFreehandSvgPayload(
@@ -871,7 +919,7 @@ function createDrawDotItem(id, worldX, worldY, radius, style) {
871
919
  childrenSvg: ""
872
920
  });
873
921
  }
874
- function createTextItem(id, bounds, text = "", style) {
922
+ function createTextItem(id, bounds, text = "", style, textFontSize) {
875
923
  const r = normalizeRect(bounds);
876
924
  const s = { ...DEFAULT_STROKE_STYLE, ...style };
877
925
  return rebuildItemSvg({
@@ -884,6 +932,7 @@ function createTextItem(id, bounds, text = "", style) {
884
932
  stroke: s.stroke,
885
933
  strokeWidth: s.strokeWidth,
886
934
  ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
935
+ ...textFontSize != null ? { textFontSize } : {},
887
936
  childrenSvg: ""
888
937
  });
889
938
  }
@@ -920,6 +969,7 @@ function createFreehandStrokeItem(id, pointsWorld, toolKind, style) {
920
969
  stroke: merged.stroke,
921
970
  strokeWidth: merged.strokeWidth,
922
971
  ...merged.strokeOpacity != null ? { strokeOpacity: merged.strokeOpacity } : {},
972
+ ...merged.strokeDash != null ? { strokeDash: merged.strokeDash } : {},
923
973
  pathPointsLocal,
924
974
  childrenSvg: ""
925
975
  });
@@ -996,7 +1046,7 @@ var init_shape_builders = __esm({
996
1046
  init_custom_shape();
997
1047
  init_text_svg();
998
1048
  DEFAULT_STROKE_STYLE = {
999
- stroke: "#2563eb",
1049
+ stroke: "#1d1d1d",
1000
1050
  strokeWidth: 2
1001
1051
  };
1002
1052
  TOOL_FREEHAND_DEFAULTS = {
@@ -3488,33 +3538,416 @@ var DEFAULT_VECTOR_TOOLS = [
3488
3538
  shortcutHint: "I"
3489
3539
  }
3490
3540
  ];
3541
+
3542
+ // src/react/VectorSelectionInspector.tsx
3543
+ init_text_svg();
3491
3544
  var shellLook = {
3492
3545
  display: "flex",
3493
3546
  flexDirection: "column",
3494
- gap: 8,
3495
- minWidth: 160,
3496
- padding: "10px 12px",
3497
- borderRadius: 8,
3498
- border: "1px solid rgba(0,0,0,0.12)",
3499
- background: "rgba(255,255,255,0.96)",
3500
- boxShadow: "0 1px 3px rgba(0,0,0,0.08)",
3547
+ gap: 10,
3548
+ minWidth: 200,
3549
+ padding: "12px 14px",
3550
+ borderRadius: 10,
3551
+ border: "1px solid rgba(0,0,0,0.1)",
3552
+ background: "rgba(255,255,255,0.97)",
3553
+ boxShadow: "0 4px 16px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.08)",
3501
3554
  pointerEvents: "auto"
3502
3555
  };
3503
- var labelStyle2 = {
3556
+ var sectionLabelStyle = {
3504
3557
  display: "flex",
3505
3558
  flexDirection: "column",
3506
- gap: 4,
3559
+ gap: 6,
3507
3560
  fontSize: 11,
3508
3561
  fontWeight: 600,
3509
- color: "#52525b"
3562
+ color: "#52525b",
3563
+ letterSpacing: "0.02em",
3564
+ textTransform: "uppercase"
3565
+ };
3566
+ var mixedHintStyle = {
3567
+ fontWeight: 400,
3568
+ color: "#a1a1aa",
3569
+ textTransform: "none",
3570
+ letterSpacing: 0
3510
3571
  };
3572
+ var TLDRAW_PALETTE = [
3573
+ { name: "black", hex: "#1d1d1d" },
3574
+ { name: "grey", hex: "#9fa8b2" },
3575
+ { name: "light-violet", hex: "#e085f4" },
3576
+ { name: "violet", hex: "#ae3ec9" },
3577
+ { name: "blue", hex: "#4263eb" },
3578
+ { name: "light-blue", hex: "#4dabf7" },
3579
+ { name: "yellow", hex: "#ffc078" },
3580
+ { name: "orange", hex: "#f76707" },
3581
+ { name: "green", hex: "#099268" },
3582
+ { name: "light-green", hex: "#40c057" },
3583
+ { name: "light-red", hex: "#ff8787" },
3584
+ { name: "red", hex: "#e03131" }
3585
+ ];
3511
3586
  function normalizeHex(stroke) {
3512
3587
  if (/^#[0-9A-Fa-f]{6}$/.test(stroke)) return stroke;
3513
- return "#2563eb";
3588
+ return "#1d1d1d";
3589
+ }
3590
+ function hexesEqual(a, b) {
3591
+ return a.toLowerCase() === b.toLowerCase();
3514
3592
  }
3515
3593
  function isStylableKind(tk) {
3516
3594
  return tk === "rect" || tk === "ellipse" || tk === "architectural-cloud" || tk === "line" || tk === "arrow" || tk === "draw" || tk === "pencil" || tk === "brush" || tk === "marker" || tk === "text";
3517
3595
  }
3596
+ var FONT_SIZE_PRESETS = [
3597
+ { label: "S", size: 14 },
3598
+ { label: "M", size: 18 },
3599
+ { label: "L", size: 28 },
3600
+ { label: "XL", size: 44 }
3601
+ ];
3602
+ function ColorSwatch({ color, selected, onSelect }) {
3603
+ return /* @__PURE__ */ jsxRuntime.jsx(
3604
+ "button",
3605
+ {
3606
+ type: "button",
3607
+ "aria-label": color.name,
3608
+ "aria-pressed": selected,
3609
+ onClick: onSelect,
3610
+ onPointerDown: (e) => {
3611
+ if (e.pointerType === "mouse") e.preventDefault();
3612
+ },
3613
+ style: {
3614
+ width: 24,
3615
+ height: 24,
3616
+ padding: 0,
3617
+ borderRadius: "50%",
3618
+ border: selected ? "2px solid #18181b" : "1px solid rgba(0,0,0,0.12)",
3619
+ background: color.hex,
3620
+ cursor: "pointer",
3621
+ outline: "none",
3622
+ boxShadow: selected ? "0 0 0 2px rgba(255,255,255,0.95) inset" : "none",
3623
+ transition: "transform 0.08s ease",
3624
+ transform: selected ? "scale(1.08)" : "scale(1)",
3625
+ WebkitTapHighlightColor: "transparent"
3626
+ }
3627
+ }
3628
+ );
3629
+ }
3630
+ function ColorPalette({ value, onChange }) {
3631
+ const [showCustom, setShowCustom] = react.useState(false);
3632
+ const normalized = normalizeHex(value);
3633
+ const inPalette = TLDRAW_PALETTE.some((c) => hexesEqual(c.hex, normalized));
3634
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3635
+ "div",
3636
+ {
3637
+ style: {
3638
+ display: "grid",
3639
+ gridTemplateColumns: "repeat(6, 24px)",
3640
+ gap: 6,
3641
+ rowGap: 6,
3642
+ alignItems: "center"
3643
+ },
3644
+ children: [
3645
+ TLDRAW_PALETTE.map((c) => /* @__PURE__ */ jsxRuntime.jsx(
3646
+ ColorSwatch,
3647
+ {
3648
+ color: c,
3649
+ selected: hexesEqual(c.hex, normalized),
3650
+ onSelect: () => onChange(c.hex)
3651
+ },
3652
+ c.name
3653
+ )),
3654
+ /* @__PURE__ */ jsxRuntime.jsx(
3655
+ "button",
3656
+ {
3657
+ type: "button",
3658
+ "aria-label": "Cor personalizada",
3659
+ "aria-pressed": showCustom || !inPalette,
3660
+ onClick: () => setShowCustom((v) => !v),
3661
+ onPointerDown: (e) => {
3662
+ if (e.pointerType === "mouse") e.preventDefault();
3663
+ },
3664
+ style: {
3665
+ width: 24,
3666
+ height: 24,
3667
+ padding: 0,
3668
+ borderRadius: "50%",
3669
+ border: !inPalette || showCustom ? "2px solid #18181b" : "1px solid rgba(0,0,0,0.12)",
3670
+ background: "conic-gradient(from 0deg, #ff5757, #ffbd59, #59ff7e, #59ddff, #b259ff, #ff59c1, #ff5757)",
3671
+ cursor: "pointer",
3672
+ outline: "none",
3673
+ WebkitTapHighlightColor: "transparent"
3674
+ }
3675
+ }
3676
+ ),
3677
+ showCustom || !inPalette ? /* @__PURE__ */ jsxRuntime.jsxs(
3678
+ "label",
3679
+ {
3680
+ style: {
3681
+ gridColumn: "1 / -1",
3682
+ display: "flex",
3683
+ alignItems: "center",
3684
+ gap: 6,
3685
+ fontSize: 11,
3686
+ fontWeight: 500,
3687
+ color: "#71717a",
3688
+ textTransform: "none",
3689
+ letterSpacing: 0
3690
+ },
3691
+ children: [
3692
+ /* @__PURE__ */ jsxRuntime.jsx(
3693
+ "input",
3694
+ {
3695
+ type: "color",
3696
+ value: normalized,
3697
+ onChange: (e) => onChange(e.target.value),
3698
+ style: {
3699
+ width: 28,
3700
+ height: 28,
3701
+ padding: 0,
3702
+ border: "1px solid rgba(0,0,0,0.12)",
3703
+ borderRadius: 6,
3704
+ cursor: "pointer",
3705
+ background: "transparent"
3706
+ }
3707
+ }
3708
+ ),
3709
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: normalized.toUpperCase() })
3710
+ ]
3711
+ }
3712
+ ) : null
3713
+ ]
3714
+ }
3715
+ );
3716
+ }
3717
+ function DashTabs({ value, onChange }) {
3718
+ const tabs = [
3719
+ { id: "solid", label: "Cont\xEDnuo" },
3720
+ { id: "dashed", label: "Tracejado" }
3721
+ ];
3722
+ return /* @__PURE__ */ jsxRuntime.jsx(
3723
+ "div",
3724
+ {
3725
+ role: "radiogroup",
3726
+ "aria-label": "Estilo do tra\xE7o",
3727
+ style: {
3728
+ display: "flex",
3729
+ background: "rgba(24,24,27,0.06)",
3730
+ borderRadius: 7,
3731
+ padding: 2,
3732
+ gap: 2
3733
+ },
3734
+ children: tabs.map((t) => {
3735
+ const selected = value === t.id;
3736
+ return (
3737
+ // biome-ignore lint/a11y/useSemanticElements: styled segmented control needs button, not input[type=radio]
3738
+ /* @__PURE__ */ jsxRuntime.jsxs(
3739
+ "button",
3740
+ {
3741
+ type: "button",
3742
+ role: "radio",
3743
+ "aria-checked": selected,
3744
+ onClick: () => onChange(t.id),
3745
+ onPointerDown: (e) => {
3746
+ if (e.pointerType === "mouse") e.preventDefault();
3747
+ },
3748
+ style: {
3749
+ flex: 1,
3750
+ display: "inline-flex",
3751
+ alignItems: "center",
3752
+ justifyContent: "center",
3753
+ padding: "5px 8px",
3754
+ fontSize: 11,
3755
+ fontWeight: 600,
3756
+ color: selected ? "#18181b" : "#71717a",
3757
+ background: selected ? "#fff" : "transparent",
3758
+ borderRadius: 5,
3759
+ border: "none",
3760
+ cursor: "pointer",
3761
+ boxShadow: selected ? "0 1px 2px rgba(0,0,0,0.1)" : "none",
3762
+ outline: "none",
3763
+ WebkitTapHighlightColor: "transparent"
3764
+ },
3765
+ children: [
3766
+ /* @__PURE__ */ jsxRuntime.jsx(DashTabSwatch, { dashed: t.id === "dashed" }),
3767
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { marginLeft: 6 }, children: t.label })
3768
+ ]
3769
+ },
3770
+ t.id
3771
+ )
3772
+ );
3773
+ })
3774
+ }
3775
+ );
3776
+ }
3777
+ function DashTabSwatch({ dashed }) {
3778
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3779
+ "svg",
3780
+ {
3781
+ width: 26,
3782
+ height: 6,
3783
+ "aria-hidden": true,
3784
+ style: { flexShrink: 0, display: "block" },
3785
+ children: [
3786
+ /* @__PURE__ */ jsxRuntime.jsx("title", { children: dashed ? "dashed" : "solid" }),
3787
+ /* @__PURE__ */ jsxRuntime.jsx(
3788
+ "line",
3789
+ {
3790
+ x1: 1,
3791
+ y1: 3,
3792
+ x2: 25,
3793
+ y2: 3,
3794
+ stroke: "currentColor",
3795
+ strokeWidth: 2,
3796
+ strokeLinecap: "round",
3797
+ strokeDasharray: dashed ? "4 3" : void 0
3798
+ }
3799
+ )
3800
+ ]
3801
+ }
3802
+ );
3803
+ }
3804
+ function FontSizeTabs({ value, onChange }) {
3805
+ return /* @__PURE__ */ jsxRuntime.jsx(
3806
+ "div",
3807
+ {
3808
+ role: "radiogroup",
3809
+ "aria-label": "Tamanho do texto",
3810
+ style: {
3811
+ display: "flex",
3812
+ background: "rgba(24,24,27,0.06)",
3813
+ borderRadius: 7,
3814
+ padding: 2,
3815
+ gap: 2
3816
+ },
3817
+ children: FONT_SIZE_PRESETS.map((p) => {
3818
+ const selected = Math.abs(value - p.size) < 0.5;
3819
+ return (
3820
+ // biome-ignore lint/a11y/useSemanticElements: styled segmented control needs button, not input[type=radio]
3821
+ /* @__PURE__ */ jsxRuntime.jsx(
3822
+ "button",
3823
+ {
3824
+ type: "button",
3825
+ role: "radio",
3826
+ "aria-checked": selected,
3827
+ "aria-label": `${p.label} (${p.size}px)`,
3828
+ onClick: () => onChange(p.size),
3829
+ onPointerDown: (e) => {
3830
+ if (e.pointerType === "mouse") e.preventDefault();
3831
+ },
3832
+ style: {
3833
+ flex: 1,
3834
+ padding: "5px 0",
3835
+ fontSize: p.label === "XL" ? 14 : p.label === "L" ? 13 : 12,
3836
+ fontWeight: 700,
3837
+ color: selected ? "#18181b" : "#71717a",
3838
+ background: selected ? "#fff" : "transparent",
3839
+ borderRadius: 5,
3840
+ border: "none",
3841
+ cursor: "pointer",
3842
+ boxShadow: selected ? "0 1px 2px rgba(0,0,0,0.1)" : "none",
3843
+ outline: "none",
3844
+ WebkitTapHighlightColor: "transparent",
3845
+ lineHeight: 1
3846
+ },
3847
+ children: p.label
3848
+ },
3849
+ p.label
3850
+ )
3851
+ );
3852
+ })
3853
+ }
3854
+ );
3855
+ }
3856
+ function viewModelFromActiveTool(activeToolStyle) {
3857
+ const hex = normalizeHex(activeToolStyle.stroke);
3858
+ const strokeWidth = activeToolStyle.strokeWidth;
3859
+ const strokeOpacity = activeToolStyle.strokeOpacity ?? 1;
3860
+ const strokeDash = activeToolStyle.strokeDash === "dashed" ? "dashed" : "solid";
3861
+ const textFontSize = activeToolStyle.textFontSize ?? DEFAULT_TEXT_FONT_SIZE;
3862
+ const isText = activeToolStyle.toolKind === "text";
3863
+ const isDraw = activeToolStyle.toolKind === "draw";
3864
+ const isMarker = activeToolStyle.toolKind === "marker";
3865
+ const current = {
3866
+ stroke: hex,
3867
+ strokeWidth,
3868
+ ...activeToolStyle.strokeOpacity != null ? { strokeOpacity: activeToolStyle.strokeOpacity } : {},
3869
+ ...activeToolStyle.strokeDash != null ? { strokeDash: activeToolStyle.strokeDash } : {},
3870
+ ...isText ? { textFontSize } : {}
3871
+ };
3872
+ return {
3873
+ label: activeToolStyle.label ?? "Estilo da ferramenta",
3874
+ count: 0,
3875
+ hex,
3876
+ strokeWidth,
3877
+ strokeOpacity,
3878
+ strokeDash,
3879
+ textFontSize,
3880
+ showStrokeWidth: !isText,
3881
+ showDash: isDraw,
3882
+ showFontSize: isText,
3883
+ showMarkerOpacity: isMarker,
3884
+ mixedStroke: false,
3885
+ mixedWidth: false,
3886
+ mixedDash: false,
3887
+ mixedFontSize: false,
3888
+ mixedOpacity: false,
3889
+ current
3890
+ };
3891
+ }
3892
+ function viewModelFromSelection(stylable) {
3893
+ const first = stylable[0];
3894
+ if (!first) return null;
3895
+ const hex = normalizeHex(first.stroke ?? "#1d1d1d");
3896
+ const strokeWidth = first.strokeWidth ?? 2;
3897
+ const allSameStroke = stylable.every(
3898
+ (it) => (it.stroke ?? "#1d1d1d") === (first.stroke ?? "#1d1d1d")
3899
+ );
3900
+ const allSameWidth = stylable.every(
3901
+ (it) => (it.strokeWidth ?? 2) === (first.strokeWidth ?? 2)
3902
+ );
3903
+ const draws = stylable.filter(
3904
+ (it) => it.toolKind === "draw" || it.toolKind === "pencil"
3905
+ );
3906
+ const showDash = draws.length > 0;
3907
+ const firstDraw = draws[0];
3908
+ const strokeDash = firstDraw?.strokeDash === "dashed" ? "dashed" : "solid";
3909
+ const allSameDash = draws.length === 0 || draws.every((it) => (it.strokeDash ?? "solid") === strokeDash);
3910
+ const markers = stylable.filter((it) => it.toolKind === "marker");
3911
+ const showMarkerOpacity = markers.length > 0;
3912
+ const firstMarker = markers[0];
3913
+ const strokeOpacity = firstMarker?.strokeOpacity ?? 1;
3914
+ const allSameOpacity = markers.length === 0 || markers.every(
3915
+ (it) => (it.strokeOpacity ?? 1) === (firstMarker?.strokeOpacity ?? 1)
3916
+ );
3917
+ const texts = stylable.filter((it) => it.toolKind === "text");
3918
+ const showFontSize = texts.length > 0;
3919
+ const firstText = texts[0];
3920
+ const textFontSize = firstText?.textFontSize ?? DEFAULT_TEXT_FONT_SIZE;
3921
+ const allSameFontSize = texts.length === 0 || texts.every(
3922
+ (it) => (it.textFontSize ?? DEFAULT_TEXT_FONT_SIZE) === textFontSize
3923
+ );
3924
+ const current = {
3925
+ stroke: hex,
3926
+ strokeWidth,
3927
+ ...showMarkerOpacity ? { strokeOpacity } : {},
3928
+ ...showDash ? { strokeDash } : {},
3929
+ ...showFontSize ? { textFontSize } : {}
3930
+ };
3931
+ return {
3932
+ label: "Estilo da sele\xE7\xE3o",
3933
+ count: stylable.length,
3934
+ hex,
3935
+ strokeWidth,
3936
+ strokeOpacity,
3937
+ strokeDash,
3938
+ textFontSize,
3939
+ showStrokeWidth: true,
3940
+ showDash,
3941
+ showFontSize,
3942
+ showMarkerOpacity,
3943
+ mixedStroke: !allSameStroke,
3944
+ mixedWidth: !allSameWidth,
3945
+ mixedDash: !allSameDash,
3946
+ mixedFontSize: !allSameFontSize,
3947
+ mixedOpacity: !allSameOpacity,
3948
+ current
3949
+ };
3950
+ }
3518
3951
  function VectorSelectionInspector({
3519
3952
  items: itemsProp,
3520
3953
  activeToolStyle: activeToolStyleProp,
@@ -3530,175 +3963,93 @@ function VectorSelectionInspector({
3530
3963
  const activeToolStyle = activeToolStyleProp === void 0 ? ctx?.activeToolStyle ?? null : activeToolStyleProp;
3531
3964
  const onChange = onChangeProp ?? ctx?.onSelectionStyleChange ?? null;
3532
3965
  if (!onChange) return null;
3966
+ let vm = null;
3967
+ if (activeToolStyle) {
3968
+ vm = viewModelFromActiveTool(activeToolStyle);
3969
+ } else {
3970
+ const stylable = items.filter(
3971
+ (it) => !it.locked && it.toolKind && isStylableKind(it.toolKind)
3972
+ );
3973
+ if (stylable.length === 0) return null;
3974
+ vm = viewModelFromSelection(stylable);
3975
+ }
3976
+ if (!vm) return null;
3977
+ const apply = (changes) => onChange({ ...vm.current, ...changes });
3533
3978
  const shell = {
3534
3979
  ...getBoardPositionStyle(position, inset, zIndex),
3535
3980
  ...shellLook,
3536
3981
  ...style
3537
3982
  };
3538
- if (activeToolStyle) {
3539
- const stroke2 = activeToolStyle.stroke;
3540
- const strokeWidth2 = activeToolStyle.strokeWidth;
3541
- const hex2 = normalizeHex(stroke2);
3542
- const showMarkerOpacity2 = activeToolStyle.toolKind === "marker";
3543
- const opacityPct2 = Math.round((activeToolStyle.strokeOpacity ?? 1) * 100);
3544
- return /* @__PURE__ */ jsxRuntime.jsxs(
3545
- "section",
3546
- {
3547
- "data-slot": "vector-selection-inspector",
3548
- "data-position": position,
3549
- className,
3550
- "aria-label": activeToolStyle.label ?? "Estilo da ferramenta",
3551
- style: shell,
3552
- children: [
3553
- /* @__PURE__ */ jsxRuntime.jsxs("label", { style: labelStyle2, children: [
3554
- "Cor",
3555
- /* @__PURE__ */ jsxRuntime.jsx(
3556
- "input",
3557
- {
3558
- type: "color",
3559
- value: hex2,
3560
- onChange: (e) => onChange({
3561
- stroke: e.target.value,
3562
- strokeWidth: strokeWidth2,
3563
- ...activeToolStyle.strokeOpacity != null ? { strokeOpacity: activeToolStyle.strokeOpacity } : {}
3564
- }),
3565
- style: {
3566
- width: "100%",
3567
- height: 32,
3568
- padding: 0,
3569
- border: "none",
3570
- cursor: "pointer",
3571
- background: "transparent"
3572
- }
3573
- }
3574
- )
3575
- ] }),
3576
- /* @__PURE__ */ jsxRuntime.jsxs("label", { style: labelStyle2, children: [
3577
- "Grossura",
3578
- /* @__PURE__ */ jsxRuntime.jsx(
3579
- "input",
3580
- {
3581
- type: "range",
3582
- min: 1,
3583
- max: 48,
3584
- value: strokeWidth2,
3585
- onChange: (e) => onChange({
3586
- stroke: hex2,
3587
- strokeWidth: Number(e.target.value),
3588
- ...activeToolStyle.strokeOpacity != null ? { strokeOpacity: activeToolStyle.strokeOpacity } : {}
3589
- })
3590
- }
3591
- )
3592
- ] }),
3593
- showMarkerOpacity2 && /* @__PURE__ */ jsxRuntime.jsxs("label", { style: labelStyle2, children: [
3594
- "Opacidade (marcador)",
3595
- /* @__PURE__ */ jsxRuntime.jsx(
3596
- "input",
3597
- {
3598
- type: "range",
3599
- min: 10,
3600
- max: 100,
3601
- value: opacityPct2,
3602
- onChange: (e) => {
3603
- const v = Number(e.target.value) / 100;
3604
- onChange({
3605
- stroke: hex2,
3606
- strokeWidth: strokeWidth2,
3607
- strokeOpacity: v
3608
- });
3609
- }
3610
- }
3611
- ),
3612
- /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
3613
- opacityPct2,
3614
- "%"
3615
- ] })
3616
- ] })
3617
- ]
3618
- }
3619
- );
3620
- }
3621
- const stylable = items.filter(
3622
- (it) => !it.locked && it.toolKind && isStylableKind(it.toolKind)
3623
- );
3624
- if (stylable.length === 0) return null;
3625
- const first = stylable[0];
3626
- if (!first) return null;
3627
- const allSameStroke = stylable.every(
3628
- (it) => (it.stroke ?? "#2563eb") === (first.stroke ?? "#2563eb")
3629
- );
3630
- const allSameWidth = stylable.every(
3631
- (it) => (it.strokeWidth ?? 2) === (first.strokeWidth ?? 2)
3632
- );
3633
- const stroke = first.stroke ?? "#2563eb";
3634
- const strokeWidth = first.strokeWidth ?? 2;
3635
- const hex = normalizeHex(stroke);
3636
- const markers = stylable.filter((it) => it.toolKind === "marker");
3637
- const showMarkerOpacity = markers.length > 0;
3638
- const firstMarker = markers[0];
3639
- const allSameMarkerOpacity = markers.length > 0 && markers.every(
3640
- (it) => (it.strokeOpacity ?? 1) === (firstMarker?.strokeOpacity ?? 1)
3641
- );
3642
- const opacityPct = firstMarker ? Math.round((firstMarker.strokeOpacity ?? 1) * 100) : 100;
3983
+ const opacityPct = Math.round(vm.strokeOpacity * 100);
3643
3984
  return /* @__PURE__ */ jsxRuntime.jsxs(
3644
3985
  "section",
3645
3986
  {
3646
3987
  "data-slot": "vector-selection-inspector",
3647
3988
  "data-position": position,
3648
3989
  className,
3649
- "aria-label": "Estilo da sele\xE7\xE3o",
3990
+ "aria-label": vm.label,
3650
3991
  style: shell,
3651
3992
  children: [
3652
- stylable.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: 0, fontSize: 11, color: "#71717a" }, children: [
3653
- stylable.length,
3993
+ vm.count > 1 ? /* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: 0, fontSize: 11, color: "#71717a" }, children: [
3994
+ vm.count,
3654
3995
  " objetos selecionados"
3655
- ] }),
3656
- /* @__PURE__ */ jsxRuntime.jsxs("label", { style: labelStyle2, children: [
3657
- "Cor",
3658
- !allSameStroke && /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: [
3659
- " ",
3660
- "(valores misturados \u2014 novo valor aplica a todos)"
3996
+ ] }) : null,
3997
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-label": "Cor", style: sectionLabelStyle, children: [
3998
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
3999
+ "Cor",
4000
+ vm.mixedStroke ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: mixedHintStyle, children: " (valores misturados)" }) : null
3661
4001
  ] }),
3662
- /* @__PURE__ */ jsxRuntime.jsx(
3663
- "input",
3664
- {
3665
- type: "color",
3666
- value: hex,
3667
- onChange: (e) => onChange({
3668
- stroke: e.target.value,
3669
- strokeWidth
3670
- }),
3671
- style: {
3672
- width: "100%",
3673
- height: 32,
3674
- padding: 0,
3675
- border: "none",
3676
- cursor: "pointer",
3677
- background: "transparent"
3678
- }
3679
- }
3680
- )
4002
+ /* @__PURE__ */ jsxRuntime.jsx(ColorPalette, { value: vm.hex, onChange: (h) => apply({ stroke: h }) })
3681
4003
  ] }),
3682
- /* @__PURE__ */ jsxRuntime.jsxs("label", { style: labelStyle2, children: [
4004
+ vm.showStrokeWidth ? /* @__PURE__ */ jsxRuntime.jsxs("label", { style: sectionLabelStyle, children: [
3683
4005
  "Grossura",
3684
- !allSameWidth && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: " (misturado)" }),
4006
+ vm.mixedWidth ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: mixedHintStyle, children: " (misturado)" }) : null,
3685
4007
  /* @__PURE__ */ jsxRuntime.jsx(
3686
4008
  "input",
3687
4009
  {
3688
4010
  type: "range",
3689
4011
  min: 1,
3690
4012
  max: 48,
3691
- value: strokeWidth,
3692
- onChange: (e) => onChange({
3693
- stroke: hex,
3694
- strokeWidth: Number(e.target.value)
3695
- })
4013
+ value: vm.strokeWidth,
4014
+ onChange: (e) => apply({ strokeWidth: Number(e.target.value) })
3696
4015
  }
3697
4016
  )
3698
- ] }),
3699
- showMarkerOpacity && firstMarker && /* @__PURE__ */ jsxRuntime.jsxs("label", { style: labelStyle2, children: [
3700
- "Opacidade (marcador)",
3701
- !allSameMarkerOpacity && markers.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: " (misturado)" }),
4017
+ ] }) : null,
4018
+ vm.showDash ? (
4019
+ // biome-ignore lint/a11y/useSemanticElements: fieldset would impose default browser styling that breaks the inspector layout
4020
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-label": "Tra\xE7o", style: sectionLabelStyle, children: [
4021
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
4022
+ "Tra\xE7o",
4023
+ vm.mixedDash ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: mixedHintStyle, children: " (misturado)" }) : null
4024
+ ] }),
4025
+ /* @__PURE__ */ jsxRuntime.jsx(
4026
+ DashTabs,
4027
+ {
4028
+ value: vm.strokeDash,
4029
+ onChange: (next) => apply({ strokeDash: next })
4030
+ }
4031
+ )
4032
+ ] })
4033
+ ) : null,
4034
+ vm.showFontSize ? (
4035
+ // biome-ignore lint/a11y/useSemanticElements: fieldset would impose default browser styling that breaks the inspector layout
4036
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-label": "Tamanho", style: sectionLabelStyle, children: [
4037
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
4038
+ "Tamanho",
4039
+ vm.mixedFontSize ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: mixedHintStyle, children: " (misturado)" }) : null
4040
+ ] }),
4041
+ /* @__PURE__ */ jsxRuntime.jsx(
4042
+ FontSizeTabs,
4043
+ {
4044
+ value: vm.textFontSize,
4045
+ onChange: (size) => apply({ textFontSize: size })
4046
+ }
4047
+ )
4048
+ ] })
4049
+ ) : null,
4050
+ vm.showMarkerOpacity ? /* @__PURE__ */ jsxRuntime.jsxs("label", { style: sectionLabelStyle, children: [
4051
+ "Opacidade",
4052
+ vm.mixedOpacity ? /* @__PURE__ */ jsxRuntime.jsx("span", { style: mixedHintStyle, children: " (misturado)" }) : null,
3702
4053
  /* @__PURE__ */ jsxRuntime.jsx(
3703
4054
  "input",
3704
4055
  {
@@ -3706,21 +4057,14 @@ function VectorSelectionInspector({
3706
4057
  min: 10,
3707
4058
  max: 100,
3708
4059
  value: opacityPct,
3709
- onChange: (e) => {
3710
- const v = Number(e.target.value) / 100;
3711
- onChange({
3712
- stroke: hex,
3713
- strokeWidth,
3714
- strokeOpacity: v
3715
- });
3716
- }
4060
+ onChange: (e) => apply({ strokeOpacity: Number(e.target.value) / 100 })
3717
4061
  }
3718
4062
  ),
3719
4063
  /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
3720
4064
  opacityPct,
3721
4065
  "%"
3722
4066
  ] })
3723
- ] })
4067
+ ] }) : null
3724
4068
  ]
3725
4069
  }
3726
4070
  );
@@ -4199,6 +4543,24 @@ function splitToolbarTools(tools, overflowIds) {
4199
4543
  const overflow = overflowIds.map((id) => tools.find((t) => t.id === id)).filter((t) => Boolean(t));
4200
4544
  return { primary, overflow };
4201
4545
  }
4546
+ function usePromotedOverflowTool({
4547
+ overflowTools,
4548
+ selectedId,
4549
+ initialPromotedId
4550
+ }) {
4551
+ const [lastOverflowToolId, setLastOverflowToolId] = react.useState(() => {
4552
+ if (initialPromotedId == null) return null;
4553
+ return overflowTools.some((t) => t.id === initialPromotedId) ? initialPromotedId : null;
4554
+ });
4555
+ react.useEffect(() => {
4556
+ if (overflowTools.some((t) => t.id === selectedId)) {
4557
+ setLastOverflowToolId(selectedId);
4558
+ }
4559
+ }, [selectedId, overflowTools]);
4560
+ const promotedTool = lastOverflowToolId ? overflowTools.find((t) => t.id === lastOverflowToolId) : void 0;
4561
+ const remainingOverflowTools = promotedTool ? overflowTools.filter((t) => t.id !== promotedTool.id) : overflowTools;
4562
+ return { promotedTool, remainingOverflowTools };
4563
+ }
4202
4564
  var icOverflow = { size: 18, strokeWidth: 2 };
4203
4565
  function useOverflowDropdown() {
4204
4566
  const [open, setOpen] = react.useState(false);
@@ -4733,6 +5095,15 @@ function VectorToolbarComponent({
4733
5095
  const pluginContext = react.useContext(CanvuPluginContext);
4734
5096
  const runtimeTools = pluginContext?.resolvedTools;
4735
5097
  const resolvedTools = tools ?? runtimeTools ?? DEFAULT_VECTOR_TOOLS;
5098
+ const { primary: primaryTools, overflow: overflowTools } = splitToolbarTools(
5099
+ resolvedTools,
5100
+ overflowToolIds
5101
+ );
5102
+ const { promotedTool, remainingOverflowTools } = usePromotedOverflowTool({
5103
+ overflowTools,
5104
+ selectedId: value,
5105
+ initialPromotedId: overflowToolIds[0] ?? null
5106
+ });
4736
5107
  if (typeof children === "function") {
4737
5108
  const ctx = {
4738
5109
  tools: resolvedTools,
@@ -4777,11 +5148,8 @@ function VectorToolbarComponent({
4777
5148
  )
4778
5149
  ] });
4779
5150
  }
4780
- const { primary: primaryTools, overflow: overflowTools } = splitToolbarTools(
4781
- resolvedTools,
4782
- overflowToolIds
4783
- );
4784
5151
  const showOverflowMenu = overflowTools.length > 0;
5152
+ const showOverflowDropdown = remainingOverflowTools.length > 0;
4785
5153
  const inlineCtx = {
4786
5154
  selectedId: value,
4787
5155
  selectTool: onChange,
@@ -4832,6 +5200,7 @@ function VectorToolbarComponent({
4832
5200
  /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": true, style: toolLockDividerStyle })
4833
5201
  ] }) : null,
4834
5202
  primaryTools.map((tool) => renderInlineToolButton(tool, inlineCtx)),
5203
+ promotedTool ? renderInlineToolButton(promotedTool, inlineCtx) : null,
4835
5204
  showOverflowMenu && orientation === "horizontal" ? /* @__PURE__ */ jsxRuntime.jsx(
4836
5205
  "span",
4837
5206
  {
@@ -4840,10 +5209,10 @@ function VectorToolbarComponent({
4840
5209
  style: overflowSpacerStyle
4841
5210
  }
4842
5211
  ) : null,
4843
- showOverflowMenu ? /* @__PURE__ */ jsxRuntime.jsx(
5212
+ showOverflowDropdown ? /* @__PURE__ */ jsxRuntime.jsx(
4844
5213
  ToolbarOverflowMenu,
4845
5214
  {
4846
- tools: overflowTools,
5215
+ tools: remainingOverflowTools,
4847
5216
  selectedId: value,
4848
5217
  onSelect: onChange,
4849
5218
  density,
@@ -6463,6 +6832,18 @@ var SvgVectorRenderer = class {
6463
6832
  }
6464
6833
  };
6465
6834
 
6835
+ // src/scene/link-item.ts
6836
+ var LINK_PLUGIN_KEY = "canvuLink";
6837
+ var isCanvuLinkData = (value) => {
6838
+ if (!value || typeof value !== "object") return false;
6839
+ const candidate = value;
6840
+ return typeof candidate.href === "string" && candidate.href.length > 0;
6841
+ };
6842
+ function getLinkData(item) {
6843
+ const entry = item.pluginData?.[LINK_PLUGIN_KEY];
6844
+ return isCanvuLinkData(entry) ? entry : null;
6845
+ }
6846
+
6466
6847
  // src/scene/scene.ts
6467
6848
  var VectorScene = class {
6468
6849
  items = [];
@@ -6482,6 +6863,7 @@ var VectorScene = class {
6482
6863
 
6483
6864
  // src/react/VectorViewport.tsx
6484
6865
  init_shape_builders();
6866
+ init_text_svg();
6485
6867
 
6486
6868
  // src/react/blob-url-lifecycle.ts
6487
6869
  var SVG_BLOB_HREF_PATTERN = /\b(?:href|xlink:href)=["'](blob:[^"']+)["']/g;
@@ -6687,6 +7069,9 @@ function InteractionOverlay({
6687
7069
  );
6688
7070
  } else if (placementPreview.kind === "rect" || placementPreview.kind === "ellipse" || placementPreview.kind === "architectural-cloud") {
6689
7071
  const r = normalizeRect(placementPreview.rect);
7072
+ const shapeStroke = previewStrokeStyle?.stroke ?? "#1d1d1d";
7073
+ const shapeWidth = previewStrokeStyle?.strokeWidth ?? 2;
7074
+ const shapeOpacity = previewStrokeStyle?.strokeOpacity;
6690
7075
  preview = placementPreview.kind === "rect" ? /* @__PURE__ */ jsxRuntime.jsx(
6691
7076
  "rect",
6692
7077
  {
@@ -6694,11 +7079,11 @@ function InteractionOverlay({
6694
7079
  y: r.y,
6695
7080
  width: r.width,
6696
7081
  height: r.height,
7082
+ rx: 4,
6697
7083
  fill: "none",
6698
- stroke: "#64748b",
6699
- strokeWidth: overlayStrokePx,
6700
- strokeDasharray: dashPattern,
6701
- vectorEffect: "non-scaling-stroke"
7084
+ stroke: shapeStroke,
7085
+ strokeWidth: shapeWidth,
7086
+ strokeOpacity: shapeOpacity
6702
7087
  }
6703
7088
  ) : placementPreview.kind === "ellipse" ? /* @__PURE__ */ jsxRuntime.jsx(
6704
7089
  "ellipse",
@@ -6708,29 +7093,30 @@ function InteractionOverlay({
6708
7093
  rx: r.width / 2,
6709
7094
  ry: r.height / 2,
6710
7095
  fill: "none",
6711
- stroke: "#64748b",
6712
- strokeWidth: overlayStrokePx,
6713
- strokeDasharray: dashPattern,
6714
- vectorEffect: "non-scaling-stroke"
7096
+ stroke: shapeStroke,
7097
+ strokeWidth: shapeWidth,
7098
+ strokeOpacity: shapeOpacity
6715
7099
  }
6716
7100
  ) : /* @__PURE__ */ jsxRuntime.jsx("g", { transform: `translate(${r.x}, ${r.y})`, children: /* @__PURE__ */ jsxRuntime.jsx(
6717
7101
  "path",
6718
7102
  {
6719
- d: buildArchitecturalCloudPathD(r.width, r.height, overlayStrokePx),
7103
+ d: buildArchitecturalCloudPathD(r.width, r.height, shapeWidth),
6720
7104
  fill: "none",
6721
- stroke: "#64748b",
6722
- strokeWidth: overlayStrokePx,
6723
- strokeDasharray: dashPattern,
7105
+ stroke: shapeStroke,
7106
+ strokeWidth: shapeWidth,
7107
+ strokeOpacity: shapeOpacity,
6724
7108
  strokeLinecap: "round",
6725
- strokeLinejoin: "round",
6726
- vectorEffect: "non-scaling-stroke"
7109
+ strokeLinejoin: "round"
6727
7110
  }
6728
7111
  ) });
6729
7112
  } else if (placementPreview.kind === "line" || placementPreview.kind === "arrow") {
6730
7113
  const { start, end } = placementPreview;
7114
+ const shapeStroke = previewStrokeStyle?.stroke ?? "#1d1d1d";
7115
+ const shapeWidth = previewStrokeStyle?.strokeWidth ?? 2;
7116
+ const shapeOpacity = previewStrokeStyle?.strokeOpacity;
6731
7117
  const geometry = placementPreview.kind === "arrow" ? computeStraightArrowGeometry(
6732
7118
  { x1: start.x, y1: start.y, x2: end.x, y2: end.y },
6733
- overlayStrokePx
7119
+ shapeWidth
6734
7120
  ) : null;
6735
7121
  preview = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6736
7122
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -6740,11 +7126,10 @@ function InteractionOverlay({
6740
7126
  y1: start.y,
6741
7127
  x2: geometry?.shaftEndX ?? end.x,
6742
7128
  y2: geometry?.shaftEndY ?? end.y,
6743
- stroke: "#64748b",
6744
- strokeWidth: overlayStrokePx,
6745
- strokeDasharray: dashPattern,
6746
- strokeLinecap: "round",
6747
- vectorEffect: "non-scaling-stroke"
7129
+ stroke: shapeStroke,
7130
+ strokeWidth: shapeWidth,
7131
+ strokeOpacity: shapeOpacity,
7132
+ strokeLinecap: "round"
6748
7133
  }
6749
7134
  ),
6750
7135
  placementPreview.kind === "arrow" && geometry ? /* @__PURE__ */ jsxRuntime.jsx(
@@ -6752,10 +7137,9 @@ function InteractionOverlay({
6752
7137
  {
6753
7138
  d: `M ${geometry.headLeftX} ${geometry.headLeftY} L ${geometry.headTipX} ${geometry.headTipY} L ${geometry.headRightX} ${geometry.headRightY}`,
6754
7139
  fill: "none",
6755
- stroke: "#64748b",
6756
- strokeWidth: overlayStrokePx,
6757
- strokeOpacity: 0.9,
6758
- strokeDasharray: dashPattern,
7140
+ stroke: shapeStroke,
7141
+ strokeWidth: shapeWidth,
7142
+ strokeOpacity: shapeOpacity,
6759
7143
  strokeLinecap: "round",
6760
7144
  strokeLinejoin: "round"
6761
7145
  }
@@ -6769,7 +7153,8 @@ function InteractionOverlay({
6769
7153
  const previewStyle = {
6770
7154
  stroke: isLaser ? "#f43f5e" : previewStrokeStyle?.stroke ?? "#64748b",
6771
7155
  strokeWidth: isLaser ? 4 : previewStrokeStyle?.strokeWidth ?? (tool === "marker" ? 16 : 3),
6772
- ...previewStrokeStyle?.strokeOpacity != null && !isLaser ? { strokeOpacity: previewStrokeStyle.strokeOpacity } : {}
7156
+ ...previewStrokeStyle?.strokeOpacity != null && !isLaser ? { strokeOpacity: previewStrokeStyle.strokeOpacity } : {},
7157
+ ...previewStrokeStyle?.strokeDash != null && !isLaser ? { strokeDash: previewStrokeStyle.strokeDash } : {}
6773
7158
  };
6774
7159
  const payload = computeFreehandSvgPayload(
6775
7160
  raw,
@@ -6813,6 +7198,7 @@ function InteractionOverlay({
6813
7198
  strokeOpacity: isLaser ? 0.85 : payload.strokeOpacity,
6814
7199
  strokeLinecap: "round",
6815
7200
  strokeLinejoin: "round",
7201
+ strokeDasharray: payload.strokeDasharray,
6816
7202
  shapeRendering: "geometricPrecision"
6817
7203
  }
6818
7204
  );
@@ -7218,6 +7604,9 @@ function TextEditOverlay({
7218
7604
  const fontSize = fsWorld * z;
7219
7605
  const lineHeight = fsWorld * (22 / 18) * z;
7220
7606
  const fixedBox = !!item.textFixedBounds;
7607
+ const padTop = EDIT_TOP_PAD_RATIO * fsWorld * z;
7608
+ const padX = 4 * z;
7609
+ const textColor = item.stroke ?? "#1d1d1d";
7221
7610
  return /* @__PURE__ */ jsxRuntime.jsx(
7222
7611
  "div",
7223
7612
  {
@@ -7248,14 +7637,14 @@ function TextEditOverlay({
7248
7637
  width: "100%",
7249
7638
  height: "100%",
7250
7639
  margin: 0,
7251
- padding: 2,
7640
+ padding: `${padTop}px ${padX}px 0px ${padX}px`,
7252
7641
  border: "none",
7253
7642
  borderRadius: 0,
7254
7643
  resize: "none",
7255
- fontFamily: "system-ui, sans-serif",
7644
+ fontFamily: TEXT_FONT_FAMILY,
7256
7645
  fontSize,
7257
7646
  lineHeight: `${lineHeight}px`,
7258
- color: "#0f172a",
7647
+ color: textColor,
7259
7648
  background: "transparent",
7260
7649
  backgroundColor: "transparent",
7261
7650
  outline: "none",
@@ -7263,7 +7652,6 @@ function TextEditOverlay({
7263
7652
  appearance: "none",
7264
7653
  WebkitAppearance: "none",
7265
7654
  WebkitTapHighlightColor: "transparent",
7266
- /* Auto-sized text: no soft wrap / scrollbar; fixed box: wrap like the SVG. */
7267
7655
  whiteSpace: fixedBox ? "pre-wrap" : "pre",
7268
7656
  overflow: fixedBox ? "auto" : "hidden",
7269
7657
  overflowX: fixedBox ? "hidden" : "hidden",
@@ -7639,6 +8027,7 @@ var VectorViewport = react.forwardRef(
7639
8027
  selectedIds: selectedIdsProp,
7640
8028
  onSelectionChange,
7641
8029
  onItemsChange: consumerOnItemsChange,
8030
+ onActivateLink,
7642
8031
  onWorldPointerDown: consumerOnWorldPointerDown,
7643
8032
  toolbar,
7644
8033
  navMenu,
@@ -7819,6 +8208,8 @@ var VectorViewport = react.forwardRef(
7819
8208
  const itemsRef = react.useRef(items);
7820
8209
  const onWorldPointerDownRef = react.useRef(onWorldPointerDown);
7821
8210
  const onItemsChangeRef = react.useRef(onItemsChange);
8211
+ const onActivateLinkRef = react.useRef(onActivateLink);
8212
+ onActivateLinkRef.current = onActivateLink;
7822
8213
  const assetStoreRef = react.useRef(assetStore);
7823
8214
  assetStoreRef.current = assetStore;
7824
8215
  const customPlacementRef = react.useRef(customPlacement);
@@ -7950,14 +8341,32 @@ var VectorViewport = react.forwardRef(
7950
8341
  imageStoreRef.current = new IndexedDbImageStore();
7951
8342
  }
7952
8343
  const rememberedImageBlobHrefsRef = react.useRef(/* @__PURE__ */ new Set());
7953
- const strokeStyleRef = react.useRef({ ...DEFAULT_STROKE_STYLE });
8344
+ const strokeStyleRef = react.useRef({
8345
+ ...DEFAULT_STROKE_STYLE,
8346
+ textFontSize: DEFAULT_TEXT_FONT_SIZE
8347
+ });
7954
8348
  const [strokeStyleState, setStrokeStyleState] = react.useState({
7955
- ...DEFAULT_STROKE_STYLE
8349
+ ...DEFAULT_STROKE_STYLE,
8350
+ textFontSize: DEFAULT_TEXT_FONT_SIZE
7956
8351
  });
7957
8352
  const setCurrentStrokeStyle = react.useCallback((next) => {
7958
8353
  strokeStyleRef.current = next;
7959
8354
  setStrokeStyleState(next);
7960
8355
  }, []);
8356
+ const patchCurrentStrokeStyle = react.useCallback(
8357
+ (patch) => {
8358
+ const merged = { ...strokeStyleRef.current };
8359
+ for (const key of Object.keys(patch)) {
8360
+ const v = patch[key];
8361
+ if (v !== void 0) {
8362
+ merged[key] = v;
8363
+ }
8364
+ }
8365
+ strokeStyleRef.current = merged;
8366
+ setStrokeStyleState(merged);
8367
+ },
8368
+ []
8369
+ );
7961
8370
  const progressiveQueueRef = react.useRef(/* @__PURE__ */ new Set());
7962
8371
  react.useEffect(() => {
7963
8372
  const store = imageStoreRef.current;
@@ -8106,10 +8515,11 @@ var VectorViewport = react.forwardRef(
8106
8515
  change(
8107
8516
  exists ? replaceItem(itemsRef.current, id, item) : [...itemsRef.current, item]
8108
8517
  );
8109
- setCurrentStrokeStyle({
8518
+ patchCurrentStrokeStyle({
8110
8519
  stroke: item.stroke ?? DEFAULT_STROKE_STYLE.stroke,
8111
8520
  strokeWidth: item.strokeWidth ?? DEFAULT_STROKE_STYLE.strokeWidth,
8112
- ...item.strokeOpacity != null ? { strokeOpacity: item.strokeOpacity } : {}
8521
+ strokeOpacity: item.strokeOpacity,
8522
+ strokeDash: item.strokeDash
8113
8523
  });
8114
8524
  }
8115
8525
  if (!shouldKeepToolForContinuousPenInput(args.tool, args.pointerType)) {
@@ -8117,8 +8527,8 @@ var VectorViewport = react.forwardRef(
8117
8527
  }
8118
8528
  },
8119
8529
  [
8530
+ patchCurrentStrokeStyle,
8120
8531
  requestAutoResetTool,
8121
- setCurrentStrokeStyle,
8122
8532
  shouldKeepToolForContinuousPenInput
8123
8533
  ]
8124
8534
  );
@@ -8854,24 +9264,28 @@ var VectorViewport = react.forwardRef(
8854
9264
  renderFrame();
8855
9265
  }, [renderFrame]);
8856
9266
  react.useEffect(() => {
9267
+ const current = strokeStyleRef.current;
8857
9268
  if (toolId === "marker") {
8858
9269
  setCurrentStrokeStyle({
8859
- ...strokeStyleRef.current,
9270
+ ...current,
8860
9271
  ...MARKER_TOOL_STYLE
8861
9272
  });
8862
9273
  return;
8863
9274
  }
8864
- const current = strokeStyleRef.current;
8865
9275
  if (isDefaultMarkerToolStyle(current)) {
8866
9276
  setCurrentStrokeStyle({
8867
9277
  stroke: DEFAULT_STROKE_STYLE.stroke,
8868
- strokeWidth: toolId === "draw" ? 10 : DEFAULT_STROKE_STYLE.strokeWidth
9278
+ strokeWidth: toolId === "draw" ? 10 : DEFAULT_STROKE_STYLE.strokeWidth,
9279
+ strokeDash: current.strokeDash,
9280
+ textFontSize: current.textFontSize
8869
9281
  });
8870
9282
  return;
8871
9283
  }
8872
9284
  setCurrentStrokeStyle({
8873
9285
  stroke: current.stroke,
8874
- strokeWidth: toolId === "draw" ? 10 : current.strokeWidth
9286
+ strokeWidth: toolId === "draw" ? 10 : current.strokeWidth,
9287
+ strokeDash: current.strokeDash,
9288
+ textFontSize: current.textFontSize
8875
9289
  });
8876
9290
  }, [setCurrentStrokeStyle, toolId]);
8877
9291
  react.useEffect(() => {
@@ -8880,15 +9294,14 @@ var VectorViewport = react.forwardRef(
8880
9294
  const it = items.find((i) => i.id === primaryId);
8881
9295
  if (!it || it.locked) return;
8882
9296
  if (it.toolKind === "image") return;
8883
- const next = {
9297
+ patchCurrentStrokeStyle({
8884
9298
  stroke: it.stroke ?? DEFAULT_STROKE_STYLE.stroke,
8885
- strokeWidth: it.strokeWidth ?? DEFAULT_STROKE_STYLE.strokeWidth
8886
- };
8887
- if (it.strokeOpacity != null) {
8888
- next.strokeOpacity = it.strokeOpacity;
8889
- }
8890
- setCurrentStrokeStyle(next);
8891
- }, [effectiveSelectedIds, items, setCurrentStrokeStyle]);
9299
+ strokeWidth: it.strokeWidth ?? DEFAULT_STROKE_STYLE.strokeWidth,
9300
+ strokeOpacity: it.strokeOpacity,
9301
+ strokeDash: it.strokeDash,
9302
+ ...it.toolKind === "text" && it.textFontSize != null ? { textFontSize: it.textFontSize } : {}
9303
+ });
9304
+ }, [effectiveSelectedIds, items, patchCurrentStrokeStyle]);
8892
9305
  const handleSelectionStyleChange = react.useCallback(
8893
9306
  (patch) => {
8894
9307
  const change = onItemsChangeRef.current;
@@ -8904,25 +9317,15 @@ var VectorViewport = react.forwardRef(
8904
9317
  nextList = replaceItem(nextList, id, out);
8905
9318
  }
8906
9319
  change(nextList);
8907
- setCurrentStrokeStyle({
8908
- ...strokeStyleRef.current,
8909
- stroke: patch.stroke,
8910
- strokeWidth: patch.strokeWidth,
8911
- ...patch.strokeOpacity != null ? { strokeOpacity: patch.strokeOpacity } : {}
8912
- });
9320
+ patchCurrentStrokeStyle(patch);
8913
9321
  },
8914
- [setCurrentStrokeStyle]
9322
+ [patchCurrentStrokeStyle]
8915
9323
  );
8916
9324
  const handleActiveToolStyleChange = react.useCallback(
8917
9325
  (patch) => {
8918
- const current = strokeStyleRef.current;
8919
- setCurrentStrokeStyle({
8920
- stroke: patch.stroke,
8921
- strokeWidth: patch.strokeWidth,
8922
- ...patch.strokeOpacity != null ? { strokeOpacity: patch.strokeOpacity } : current.strokeOpacity != null ? { strokeOpacity: current.strokeOpacity } : {}
8923
- });
9326
+ patchCurrentStrokeStyle(patch);
8924
9327
  },
8925
- [setCurrentStrokeStyle]
9328
+ [patchCurrentStrokeStyle]
8926
9329
  );
8927
9330
  const commitTextEdit = react.useCallback(() => {
8928
9331
  const id = editingTextIdRef.current;
@@ -9094,13 +9497,27 @@ var VectorViewport = react.forwardRef(
9094
9497
  );
9095
9498
  const handleOverlayDoubleClick = react.useCallback(
9096
9499
  (e) => {
9097
- if (!interactiveRef.current || !onItemsChangeRef.current) return;
9098
- if (toolIdRef.current !== "select") return;
9099
- e.preventDefault();
9100
9500
  const cam = cameraRef.current;
9101
9501
  if (!cam) return;
9102
9502
  const { worldX, worldY } = screenToWorld(e.clientX, e.clientY);
9103
9503
  const lineHitWorld = 10 / cam.zoom;
9504
+ const linkHit = hitTestWorldPoint(resolvedItemsRef.current, worldX, worldY, {
9505
+ lineHitWorld,
9506
+ ignoreLocked: false
9507
+ });
9508
+ const link = linkHit ? getLinkData(linkHit) : null;
9509
+ if (linkHit && link) {
9510
+ e.preventDefault();
9511
+ if (onActivateLinkRef.current) {
9512
+ onActivateLinkRef.current({ link, item: linkHit });
9513
+ } else if (typeof window !== "undefined" && typeof window.open === "function") {
9514
+ window.open(link.href, "_blank", "noopener,noreferrer");
9515
+ }
9516
+ return;
9517
+ }
9518
+ if (!interactiveRef.current || !onItemsChangeRef.current) return;
9519
+ if (toolIdRef.current !== "select") return;
9520
+ e.preventDefault();
9104
9521
  const hit = hitTestWorldPoint(resolvedItemsRef.current, worldX, worldY, {
9105
9522
  lineHitWorld,
9106
9523
  ignoreLocked: true
@@ -10041,16 +10458,21 @@ var VectorViewport = react.forwardRef(
10041
10458
  const id = createShapeId();
10042
10459
  const { x: worldX, y: worldY } = st.startWorld;
10043
10460
  if (st.tool === "text") {
10461
+ const fs = strokeStyleRef.current.textFontSize;
10462
+ const baseline = textBaselineYFor(fs);
10463
+ const lh = textLineHeightFor(fs);
10464
+ const minH = Math.max(26, baseline + Math.max(4, lh * 0.2));
10044
10465
  const newItem = createTextItem(
10045
10466
  id,
10046
10467
  {
10047
10468
  x: worldX - 4,
10048
- y: worldY - 24,
10469
+ y: worldY - baseline,
10049
10470
  width: 160,
10050
- height: 36
10471
+ height: minH
10051
10472
  },
10052
10473
  "",
10053
- strokeStyleRef.current
10474
+ strokeStyleRef.current,
10475
+ fs
10054
10476
  );
10055
10477
  editingTextSnapshotRef.current = {
10056
10478
  ...newItem,
@@ -10201,12 +10623,63 @@ var VectorViewport = react.forwardRef(
10201
10623
  return effectiveSelectedIds.map((id) => resolvedItems.find((i) => i.id === id)).filter((i) => i != null);
10202
10624
  }, [effectiveSelectedIds, resolvedItems]);
10203
10625
  const activeToolInspectorStyle = react.useMemo(() => {
10204
- if (toolId !== "draw") return void 0;
10205
- return {
10206
- toolKind: "draw",
10207
- label: "Estilo da ferramenta",
10208
- ...strokeStyleState
10209
- };
10626
+ if (toolId === "draw") {
10627
+ return {
10628
+ toolKind: "draw",
10629
+ label: "Caneta",
10630
+ ...strokeStyleState
10631
+ };
10632
+ }
10633
+ if (toolId === "marker") {
10634
+ return {
10635
+ toolKind: "marker",
10636
+ label: "Marcador",
10637
+ ...strokeStyleState
10638
+ };
10639
+ }
10640
+ if (toolId === "text") {
10641
+ return {
10642
+ toolKind: "text",
10643
+ label: "Texto",
10644
+ ...strokeStyleState
10645
+ };
10646
+ }
10647
+ if (toolId === "rect") {
10648
+ return {
10649
+ toolKind: "rect",
10650
+ label: "Ret\xE2ngulo",
10651
+ ...strokeStyleState
10652
+ };
10653
+ }
10654
+ if (toolId === "ellipse") {
10655
+ return {
10656
+ toolKind: "ellipse",
10657
+ label: "Elipse",
10658
+ ...strokeStyleState
10659
+ };
10660
+ }
10661
+ if (toolId === "architectural-cloud") {
10662
+ return {
10663
+ toolKind: "architectural-cloud",
10664
+ label: "Nuvem",
10665
+ ...strokeStyleState
10666
+ };
10667
+ }
10668
+ if (toolId === "line") {
10669
+ return {
10670
+ toolKind: "line",
10671
+ label: "Linha",
10672
+ ...strokeStyleState
10673
+ };
10674
+ }
10675
+ if (toolId === "arrow") {
10676
+ return {
10677
+ toolKind: "arrow",
10678
+ label: "Seta",
10679
+ ...strokeStyleState
10680
+ };
10681
+ }
10682
+ return void 0;
10210
10683
  }, [strokeStyleState, toolId]);
10211
10684
  const eraserPreviewItemsForOverlay = react.useMemo(() => {
10212
10685
  return eraserPreviewIds.map((id) => resolvedItems.find((i) => i.id === id)).filter((i) => i != null);