canvu-react 0.3.39 → 0.4.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.
Files changed (45) hide show
  1. package/dist/{asset-hydration-DowNdaOJ.d.cts → asset-hydration-B7yMDQE-.d.cts} +2 -2
  2. package/dist/{asset-hydration-DdFLdlqX.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 +59 -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 +59 -15
  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 +699 -255
  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 +699 -255
  24. package/dist/react.js.map +1 -1
  25. package/dist/realtime.cjs +498 -129
  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 +479 -129
  30. package/dist/realtime.js.map +1 -1
  31. package/dist/{shape-builders-Dedcl6tw.d.cts → shape-builders-BAWu-PxX.d.cts} +7 -3
  32. package/dist/{shape-builders-C7bxJBGR.d.ts → shape-builders-ClKv9tz9.d.ts} +7 -3
  33. package/dist/tldraw.cjs +56 -14
  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 +56 -14
  38. package/dist/tldraw.js.map +1 -1
  39. package/dist/{types-DUW61Tjy.d.cts → types-BC9Xgfu6.d.cts} +11 -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-BBb8KoyW.d.ts → types-DlSVGX0w.d.ts} +11 -6
  45. package/package.json +3 -2
package/dist/react.js CHANGED
@@ -84,6 +84,12 @@ function lineHeightFor(fontSize) {
84
84
  function firstLineBaselineY(fontSize) {
85
85
  return fontSize * FIRST_LINE_BASELINE_RATIO;
86
86
  }
87
+ function textBaselineYFor(fontSize) {
88
+ return firstLineBaselineY(fontSize);
89
+ }
90
+ function textLineHeightFor(fontSize) {
91
+ return lineHeightFor(fontSize);
92
+ }
87
93
  function measureTextBoundsLocal(content, fontSize = DEFAULT_TEXT_FONT_SIZE) {
88
94
  const cacheKey = textMeasureCacheKey(content, fontSize);
89
95
  const cached = textMeasureCache.get(cacheKey);
@@ -97,7 +103,7 @@ function measureTextBoundsLocal(content, fontSize = DEFAULT_TEXT_FONT_SIZE) {
97
103
  let maxInnerW = 0;
98
104
  const ctx = getSharedMeasureContext();
99
105
  if (ctx) {
100
- ctx.font = `${fontSize}px system-ui, sans-serif`;
106
+ ctx.font = `${fontSize}px ${TEXT_FONT_FAMILY}`;
101
107
  for (const line of lines) {
102
108
  const toMeasure = trimmed.length === 0 ? PLACEHOLDER : line.length === 0 ? " " : line;
103
109
  maxInnerW = Math.max(maxInnerW, ctx.measureText(toMeasure).width);
@@ -113,22 +119,22 @@ function measureTextBoundsLocal(content, fontSize = DEFAULT_TEXT_FONT_SIZE) {
113
119
  const width = Math.max(minW, TEXT_PAD_X * 2 + maxInnerW);
114
120
  const height = Math.max(
115
121
  MIN_TEXT_BOX_H,
116
- baselineY + (lines.length - 1) * lh + Math.max(8, fontSize * 0.35)
122
+ baselineY + (lines.length - 1) * lh + Math.max(4, fontSize * BOTTOM_PAD_RATIO)
117
123
  );
118
124
  const measured = { width, height };
119
125
  cacheMeasuredBounds(cacheKey, measured);
120
126
  return measured;
121
127
  }
122
- function buildTextSvg(content, _width, _height, fillColor = "#2563eb", fontSize = DEFAULT_TEXT_FONT_SIZE) {
128
+ function buildTextSvg(content, _width, _height, fillColor = "#1d1d1d", fontSize = DEFAULT_TEXT_FONT_SIZE) {
123
129
  const lh = lineHeightFor(fontSize);
124
130
  const y0 = firstLineBaselineY(fontSize);
125
131
  const trimmed = content.trim();
126
132
  if (trimmed.length === 0) {
127
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="#94a3b8" font-style="italic">${escapeSvgTextContent(PLACEHOLDER)}</text>`;
133
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="#94a3b8" font-style="italic">${escapeSvgTextContent(PLACEHOLDER)}</text>`;
128
134
  }
129
135
  const lines = content.split("\n");
130
136
  if (lines.length === 1) {
131
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="${fillColor}">${escapeSvgTextContent(lines[0] ?? "")}</text>`;
137
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="${fillColor}">${escapeSvgTextContent(lines[0] ?? "")}</text>`;
132
138
  }
133
139
  const parts = [];
134
140
  for (let i = 0; i < lines.length; i++) {
@@ -139,28 +145,32 @@ function buildTextSvg(content, _width, _height, fillColor = "#2563eb", fontSize
139
145
  parts.push(`<tspan x="4" dy="${lh}">${escapeSvgTextContent(line)}</tspan>`);
140
146
  }
141
147
  }
142
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="${fillColor}">${parts.join("")}</text>`;
148
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="${fillColor}">${parts.join("")}</text>`;
143
149
  }
144
- function buildTextFixedBoundsSvg(content, width, height, fillColor = "#2563eb", fontSize = DEFAULT_TEXT_FONT_SIZE) {
150
+ function buildTextFixedBoundsSvg(content, width, height, fillColor = "#1d1d1d", fontSize = DEFAULT_TEXT_FONT_SIZE) {
145
151
  const w = Math.max(1, width);
146
152
  const h = Math.max(1, height);
147
153
  const lh = lineHeightFor(fontSize);
148
154
  const trimmed = content.trim();
155
+ const padTop = EDIT_TOP_PAD_RATIO * fontSize;
149
156
  if (trimmed.length === 0) {
150
- 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>`;
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:${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>`;
151
158
  }
152
159
  const body = escapeHtmlText(content);
153
- 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>`;
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:${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>`;
154
161
  }
155
- 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;
162
+ 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;
156
163
  var init_text_svg = __esm({
157
164
  "src/scene/text-svg.ts"() {
158
165
  DEFAULT_TEXT_FONT_SIZE = 18;
166
+ TEXT_FONT_FAMILY = "Inter, -apple-system, BlinkMacSystemFont, ui-sans-serif, system-ui, sans-serif";
159
167
  LINE_HEIGHT_RATIO = 22 / 18;
160
- FIRST_LINE_BASELINE_RATIO = 24 / 18;
168
+ FIRST_LINE_BASELINE_RATIO = 20 / 18;
169
+ EDIT_TOP_PAD_RATIO = 4 / 18;
170
+ BOTTOM_PAD_RATIO = 4 / 18;
161
171
  PLACEHOLDER = "Tap to type";
162
172
  MIN_TEXT_BOX_W = 40;
163
- MIN_TEXT_BOX_H = 36;
173
+ MIN_TEXT_BOX_H = 26;
164
174
  TEXT_PAD_X = 4;
165
175
  MAX_TEXT_MEASURE_CACHE_ENTRIES = 2e3;
166
176
  textMeasureCache = /* @__PURE__ */ new Map();
@@ -273,7 +283,8 @@ function resolveStrokeStyle(item) {
273
283
  return {
274
284
  stroke: item.stroke ?? DEFAULT_STROKE_STYLE.stroke,
275
285
  strokeWidth: item.strokeWidth ?? DEFAULT_STROKE_STYLE.strokeWidth,
276
- strokeOpacity: item.strokeOpacity
286
+ strokeOpacity: item.strokeOpacity,
287
+ strokeDash: item.strokeDash
277
288
  };
278
289
  }
279
290
  function strokeOpacityAttr(style) {
@@ -596,6 +607,30 @@ function buildDrawDotSvg(r, style = DEFAULT_STROKE_STYLE) {
596
607
  const op = style.strokeOpacity != null ? ` fill-opacity="${style.strokeOpacity}"` : "";
597
608
  return `<circle cx="${r}" cy="${r}" r="${r}" fill="${style.stroke}" shape-rendering="geometricPrecision"${op} />`;
598
609
  }
610
+ function dashArrayForDrawStroke(strokeWidth) {
611
+ const dash = Math.max(strokeWidth * 1.8, 4);
612
+ const gap = Math.max(strokeWidth * 1.4, 3);
613
+ return `${dash} ${gap}`;
614
+ }
615
+ function buildSmoothedCenterlinePath(points) {
616
+ if (points.length < 2) return null;
617
+ const first = points[0];
618
+ if (!first) return null;
619
+ let d = `M ${first.x} ${first.y}`;
620
+ for (let i = 1; i < points.length - 1; i++) {
621
+ const a = points[i];
622
+ const b = points[i + 1];
623
+ if (!a || !b) continue;
624
+ const midX = (a.x + b.x) / 2;
625
+ const midY = (a.y + b.y) / 2;
626
+ d += ` Q ${a.x} ${a.y} ${midX} ${midY}`;
627
+ }
628
+ const last = points[points.length - 1];
629
+ if (last) {
630
+ d += ` L ${last.x} ${last.y}`;
631
+ }
632
+ return d;
633
+ }
599
634
  function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeComplete = true) {
600
635
  if (pathPointsLocal.length === 0) return null;
601
636
  if (pathPointsLocal.length === 1) {
@@ -610,6 +645,18 @@ function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeCompl
610
645
  fillOpacity: style.strokeOpacity
611
646
  };
612
647
  }
648
+ if (style.strokeDash === "dashed" && (toolKind === "draw" || toolKind === "pencil")) {
649
+ const d2 = buildSmoothedCenterlinePath(pathPointsLocal);
650
+ if (!d2) return null;
651
+ return {
652
+ kind: "strokePath",
653
+ d: d2,
654
+ stroke: style.stroke,
655
+ strokeWidth: style.strokeWidth,
656
+ strokeOpacity: style.strokeOpacity,
657
+ strokeDasharray: dashArrayForDrawStroke(style.strokeWidth)
658
+ };
659
+ }
613
660
  const hasPressure = pathPointsLocal.some(
614
661
  (p) => p.pressure != null && Number.isFinite(p.pressure)
615
662
  );
@@ -652,7 +699,8 @@ function freehandPayloadToSvgString(payload) {
652
699
  strokeWidth: payload.strokeWidth,
653
700
  strokeOpacity: payload.strokeOpacity
654
701
  });
655
- return `<path d="${payload.d}" fill="none" stroke="${payload.stroke}" stroke-width="${payload.strokeWidth}" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision"${op} />`;
702
+ const dash = payload.strokeDasharray ? ` stroke-dasharray="${payload.strokeDasharray}"` : "";
703
+ 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} />`;
656
704
  }
657
705
  function buildFreehandPathSvg(pathPointsLocal, style, toolKind, strokeComplete = true) {
658
706
  const payload = computeFreehandSvgPayload(
@@ -864,7 +912,7 @@ function createDrawDotItem(id, worldX, worldY, radius, style) {
864
912
  childrenSvg: ""
865
913
  });
866
914
  }
867
- function createTextItem(id, bounds, text = "", style) {
915
+ function createTextItem(id, bounds, text = "", style, textFontSize) {
868
916
  const r = normalizeRect(bounds);
869
917
  const s = { ...DEFAULT_STROKE_STYLE, ...style };
870
918
  return rebuildItemSvg({
@@ -877,6 +925,7 @@ function createTextItem(id, bounds, text = "", style) {
877
925
  stroke: s.stroke,
878
926
  strokeWidth: s.strokeWidth,
879
927
  ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
928
+ ...textFontSize != null ? { textFontSize } : {},
880
929
  childrenSvg: ""
881
930
  });
882
931
  }
@@ -913,6 +962,7 @@ function createFreehandStrokeItem(id, pointsWorld, toolKind, style) {
913
962
  stroke: merged.stroke,
914
963
  strokeWidth: merged.strokeWidth,
915
964
  ...merged.strokeOpacity != null ? { strokeOpacity: merged.strokeOpacity } : {},
965
+ ...merged.strokeDash != null ? { strokeDash: merged.strokeDash } : {},
916
966
  pathPointsLocal,
917
967
  childrenSvg: ""
918
968
  });
@@ -989,7 +1039,7 @@ var init_shape_builders = __esm({
989
1039
  init_custom_shape();
990
1040
  init_text_svg();
991
1041
  DEFAULT_STROKE_STYLE = {
992
- stroke: "#2563eb",
1042
+ stroke: "#1d1d1d",
993
1043
  strokeWidth: 2
994
1044
  };
995
1045
  TOOL_FREEHAND_DEFAULTS = {
@@ -3481,33 +3531,416 @@ var DEFAULT_VECTOR_TOOLS = [
3481
3531
  shortcutHint: "I"
3482
3532
  }
3483
3533
  ];
3534
+
3535
+ // src/react/VectorSelectionInspector.tsx
3536
+ init_text_svg();
3484
3537
  var shellLook = {
3485
3538
  display: "flex",
3486
3539
  flexDirection: "column",
3487
- gap: 8,
3488
- minWidth: 160,
3489
- padding: "10px 12px",
3490
- borderRadius: 8,
3491
- border: "1px solid rgba(0,0,0,0.12)",
3492
- background: "rgba(255,255,255,0.96)",
3493
- boxShadow: "0 1px 3px rgba(0,0,0,0.08)",
3540
+ gap: 10,
3541
+ minWidth: 200,
3542
+ padding: "12px 14px",
3543
+ borderRadius: 10,
3544
+ border: "1px solid rgba(0,0,0,0.1)",
3545
+ background: "rgba(255,255,255,0.97)",
3546
+ boxShadow: "0 4px 16px rgba(0,0,0,0.06), 0 1px 3px rgba(0,0,0,0.08)",
3494
3547
  pointerEvents: "auto"
3495
3548
  };
3496
- var labelStyle2 = {
3549
+ var sectionLabelStyle = {
3497
3550
  display: "flex",
3498
3551
  flexDirection: "column",
3499
- gap: 4,
3552
+ gap: 6,
3500
3553
  fontSize: 11,
3501
3554
  fontWeight: 600,
3502
- color: "#52525b"
3555
+ color: "#52525b",
3556
+ letterSpacing: "0.02em",
3557
+ textTransform: "uppercase"
3503
3558
  };
3559
+ var mixedHintStyle = {
3560
+ fontWeight: 400,
3561
+ color: "#a1a1aa",
3562
+ textTransform: "none",
3563
+ letterSpacing: 0
3564
+ };
3565
+ var TLDRAW_PALETTE = [
3566
+ { name: "black", hex: "#1d1d1d" },
3567
+ { name: "grey", hex: "#9fa8b2" },
3568
+ { name: "light-violet", hex: "#e085f4" },
3569
+ { name: "violet", hex: "#ae3ec9" },
3570
+ { name: "blue", hex: "#4263eb" },
3571
+ { name: "light-blue", hex: "#4dabf7" },
3572
+ { name: "yellow", hex: "#ffc078" },
3573
+ { name: "orange", hex: "#f76707" },
3574
+ { name: "green", hex: "#099268" },
3575
+ { name: "light-green", hex: "#40c057" },
3576
+ { name: "light-red", hex: "#ff8787" },
3577
+ { name: "red", hex: "#e03131" }
3578
+ ];
3504
3579
  function normalizeHex(stroke) {
3505
3580
  if (/^#[0-9A-Fa-f]{6}$/.test(stroke)) return stroke;
3506
- return "#2563eb";
3581
+ return "#1d1d1d";
3582
+ }
3583
+ function hexesEqual(a, b) {
3584
+ return a.toLowerCase() === b.toLowerCase();
3507
3585
  }
3508
3586
  function isStylableKind(tk) {
3509
3587
  return tk === "rect" || tk === "ellipse" || tk === "architectural-cloud" || tk === "line" || tk === "arrow" || tk === "draw" || tk === "pencil" || tk === "brush" || tk === "marker" || tk === "text";
3510
3588
  }
3589
+ var FONT_SIZE_PRESETS = [
3590
+ { label: "S", size: 14 },
3591
+ { label: "M", size: 18 },
3592
+ { label: "L", size: 28 },
3593
+ { label: "XL", size: 44 }
3594
+ ];
3595
+ function ColorSwatch({ color, selected, onSelect }) {
3596
+ return /* @__PURE__ */ jsx(
3597
+ "button",
3598
+ {
3599
+ type: "button",
3600
+ "aria-label": color.name,
3601
+ "aria-pressed": selected,
3602
+ onClick: onSelect,
3603
+ onPointerDown: (e) => {
3604
+ if (e.pointerType === "mouse") e.preventDefault();
3605
+ },
3606
+ style: {
3607
+ width: 24,
3608
+ height: 24,
3609
+ padding: 0,
3610
+ borderRadius: "50%",
3611
+ border: selected ? "2px solid #18181b" : "1px solid rgba(0,0,0,0.12)",
3612
+ background: color.hex,
3613
+ cursor: "pointer",
3614
+ outline: "none",
3615
+ boxShadow: selected ? "0 0 0 2px rgba(255,255,255,0.95) inset" : "none",
3616
+ transition: "transform 0.08s ease",
3617
+ transform: selected ? "scale(1.08)" : "scale(1)",
3618
+ WebkitTapHighlightColor: "transparent"
3619
+ }
3620
+ }
3621
+ );
3622
+ }
3623
+ function ColorPalette({ value, onChange }) {
3624
+ const [showCustom, setShowCustom] = useState(false);
3625
+ const normalized = normalizeHex(value);
3626
+ const inPalette = TLDRAW_PALETTE.some((c) => hexesEqual(c.hex, normalized));
3627
+ return /* @__PURE__ */ jsxs(
3628
+ "div",
3629
+ {
3630
+ style: {
3631
+ display: "grid",
3632
+ gridTemplateColumns: "repeat(6, 24px)",
3633
+ gap: 6,
3634
+ rowGap: 6,
3635
+ alignItems: "center"
3636
+ },
3637
+ children: [
3638
+ TLDRAW_PALETTE.map((c) => /* @__PURE__ */ jsx(
3639
+ ColorSwatch,
3640
+ {
3641
+ color: c,
3642
+ selected: hexesEqual(c.hex, normalized),
3643
+ onSelect: () => onChange(c.hex)
3644
+ },
3645
+ c.name
3646
+ )),
3647
+ /* @__PURE__ */ jsx(
3648
+ "button",
3649
+ {
3650
+ type: "button",
3651
+ "aria-label": "Cor personalizada",
3652
+ "aria-pressed": showCustom || !inPalette,
3653
+ onClick: () => setShowCustom((v) => !v),
3654
+ onPointerDown: (e) => {
3655
+ if (e.pointerType === "mouse") e.preventDefault();
3656
+ },
3657
+ style: {
3658
+ width: 24,
3659
+ height: 24,
3660
+ padding: 0,
3661
+ borderRadius: "50%",
3662
+ border: !inPalette || showCustom ? "2px solid #18181b" : "1px solid rgba(0,0,0,0.12)",
3663
+ background: "conic-gradient(from 0deg, #ff5757, #ffbd59, #59ff7e, #59ddff, #b259ff, #ff59c1, #ff5757)",
3664
+ cursor: "pointer",
3665
+ outline: "none",
3666
+ WebkitTapHighlightColor: "transparent"
3667
+ }
3668
+ }
3669
+ ),
3670
+ showCustom || !inPalette ? /* @__PURE__ */ jsxs(
3671
+ "label",
3672
+ {
3673
+ style: {
3674
+ gridColumn: "1 / -1",
3675
+ display: "flex",
3676
+ alignItems: "center",
3677
+ gap: 6,
3678
+ fontSize: 11,
3679
+ fontWeight: 500,
3680
+ color: "#71717a",
3681
+ textTransform: "none",
3682
+ letterSpacing: 0
3683
+ },
3684
+ children: [
3685
+ /* @__PURE__ */ jsx(
3686
+ "input",
3687
+ {
3688
+ type: "color",
3689
+ value: normalized,
3690
+ onChange: (e) => onChange(e.target.value),
3691
+ style: {
3692
+ width: 28,
3693
+ height: 28,
3694
+ padding: 0,
3695
+ border: "1px solid rgba(0,0,0,0.12)",
3696
+ borderRadius: 6,
3697
+ cursor: "pointer",
3698
+ background: "transparent"
3699
+ }
3700
+ }
3701
+ ),
3702
+ /* @__PURE__ */ jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: normalized.toUpperCase() })
3703
+ ]
3704
+ }
3705
+ ) : null
3706
+ ]
3707
+ }
3708
+ );
3709
+ }
3710
+ function DashTabs({ value, onChange }) {
3711
+ const tabs = [
3712
+ { id: "solid", label: "Cont\xEDnuo" },
3713
+ { id: "dashed", label: "Tracejado" }
3714
+ ];
3715
+ return /* @__PURE__ */ jsx(
3716
+ "div",
3717
+ {
3718
+ role: "radiogroup",
3719
+ "aria-label": "Estilo do tra\xE7o",
3720
+ style: {
3721
+ display: "flex",
3722
+ background: "rgba(24,24,27,0.06)",
3723
+ borderRadius: 7,
3724
+ padding: 2,
3725
+ gap: 2
3726
+ },
3727
+ children: tabs.map((t) => {
3728
+ const selected = value === t.id;
3729
+ return (
3730
+ // biome-ignore lint/a11y/useSemanticElements: styled segmented control needs button, not input[type=radio]
3731
+ /* @__PURE__ */ jsxs(
3732
+ "button",
3733
+ {
3734
+ type: "button",
3735
+ role: "radio",
3736
+ "aria-checked": selected,
3737
+ onClick: () => onChange(t.id),
3738
+ onPointerDown: (e) => {
3739
+ if (e.pointerType === "mouse") e.preventDefault();
3740
+ },
3741
+ style: {
3742
+ flex: 1,
3743
+ display: "inline-flex",
3744
+ alignItems: "center",
3745
+ justifyContent: "center",
3746
+ padding: "5px 8px",
3747
+ fontSize: 11,
3748
+ fontWeight: 600,
3749
+ color: selected ? "#18181b" : "#71717a",
3750
+ background: selected ? "#fff" : "transparent",
3751
+ borderRadius: 5,
3752
+ border: "none",
3753
+ cursor: "pointer",
3754
+ boxShadow: selected ? "0 1px 2px rgba(0,0,0,0.1)" : "none",
3755
+ outline: "none",
3756
+ WebkitTapHighlightColor: "transparent"
3757
+ },
3758
+ children: [
3759
+ /* @__PURE__ */ jsx(DashTabSwatch, { dashed: t.id === "dashed" }),
3760
+ /* @__PURE__ */ jsx("span", { style: { marginLeft: 6 }, children: t.label })
3761
+ ]
3762
+ },
3763
+ t.id
3764
+ )
3765
+ );
3766
+ })
3767
+ }
3768
+ );
3769
+ }
3770
+ function DashTabSwatch({ dashed }) {
3771
+ return /* @__PURE__ */ jsxs(
3772
+ "svg",
3773
+ {
3774
+ width: 26,
3775
+ height: 6,
3776
+ "aria-hidden": true,
3777
+ style: { flexShrink: 0, display: "block" },
3778
+ children: [
3779
+ /* @__PURE__ */ jsx("title", { children: dashed ? "dashed" : "solid" }),
3780
+ /* @__PURE__ */ jsx(
3781
+ "line",
3782
+ {
3783
+ x1: 1,
3784
+ y1: 3,
3785
+ x2: 25,
3786
+ y2: 3,
3787
+ stroke: "currentColor",
3788
+ strokeWidth: 2,
3789
+ strokeLinecap: "round",
3790
+ strokeDasharray: dashed ? "4 3" : void 0
3791
+ }
3792
+ )
3793
+ ]
3794
+ }
3795
+ );
3796
+ }
3797
+ function FontSizeTabs({ value, onChange }) {
3798
+ return /* @__PURE__ */ jsx(
3799
+ "div",
3800
+ {
3801
+ role: "radiogroup",
3802
+ "aria-label": "Tamanho do texto",
3803
+ style: {
3804
+ display: "flex",
3805
+ background: "rgba(24,24,27,0.06)",
3806
+ borderRadius: 7,
3807
+ padding: 2,
3808
+ gap: 2
3809
+ },
3810
+ children: FONT_SIZE_PRESETS.map((p) => {
3811
+ const selected = Math.abs(value - p.size) < 0.5;
3812
+ return (
3813
+ // biome-ignore lint/a11y/useSemanticElements: styled segmented control needs button, not input[type=radio]
3814
+ /* @__PURE__ */ jsx(
3815
+ "button",
3816
+ {
3817
+ type: "button",
3818
+ role: "radio",
3819
+ "aria-checked": selected,
3820
+ "aria-label": `${p.label} (${p.size}px)`,
3821
+ onClick: () => onChange(p.size),
3822
+ onPointerDown: (e) => {
3823
+ if (e.pointerType === "mouse") e.preventDefault();
3824
+ },
3825
+ style: {
3826
+ flex: 1,
3827
+ padding: "5px 0",
3828
+ fontSize: p.label === "XL" ? 14 : p.label === "L" ? 13 : 12,
3829
+ fontWeight: 700,
3830
+ color: selected ? "#18181b" : "#71717a",
3831
+ background: selected ? "#fff" : "transparent",
3832
+ borderRadius: 5,
3833
+ border: "none",
3834
+ cursor: "pointer",
3835
+ boxShadow: selected ? "0 1px 2px rgba(0,0,0,0.1)" : "none",
3836
+ outline: "none",
3837
+ WebkitTapHighlightColor: "transparent",
3838
+ lineHeight: 1
3839
+ },
3840
+ children: p.label
3841
+ },
3842
+ p.label
3843
+ )
3844
+ );
3845
+ })
3846
+ }
3847
+ );
3848
+ }
3849
+ function viewModelFromActiveTool(activeToolStyle) {
3850
+ const hex = normalizeHex(activeToolStyle.stroke);
3851
+ const strokeWidth = activeToolStyle.strokeWidth;
3852
+ const strokeOpacity = activeToolStyle.strokeOpacity ?? 1;
3853
+ const strokeDash = activeToolStyle.strokeDash === "dashed" ? "dashed" : "solid";
3854
+ const textFontSize = activeToolStyle.textFontSize ?? DEFAULT_TEXT_FONT_SIZE;
3855
+ const isText = activeToolStyle.toolKind === "text";
3856
+ const isDraw = activeToolStyle.toolKind === "draw";
3857
+ const isMarker = activeToolStyle.toolKind === "marker";
3858
+ const current = {
3859
+ stroke: hex,
3860
+ strokeWidth,
3861
+ ...activeToolStyle.strokeOpacity != null ? { strokeOpacity: activeToolStyle.strokeOpacity } : {},
3862
+ ...activeToolStyle.strokeDash != null ? { strokeDash: activeToolStyle.strokeDash } : {},
3863
+ ...isText ? { textFontSize } : {}
3864
+ };
3865
+ return {
3866
+ label: activeToolStyle.label ?? "Estilo da ferramenta",
3867
+ count: 0,
3868
+ hex,
3869
+ strokeWidth,
3870
+ strokeOpacity,
3871
+ strokeDash,
3872
+ textFontSize,
3873
+ showStrokeWidth: !isText,
3874
+ showDash: isDraw,
3875
+ showFontSize: isText,
3876
+ showMarkerOpacity: isMarker,
3877
+ mixedStroke: false,
3878
+ mixedWidth: false,
3879
+ mixedDash: false,
3880
+ mixedFontSize: false,
3881
+ mixedOpacity: false,
3882
+ current
3883
+ };
3884
+ }
3885
+ function viewModelFromSelection(stylable) {
3886
+ const first = stylable[0];
3887
+ if (!first) return null;
3888
+ const hex = normalizeHex(first.stroke ?? "#1d1d1d");
3889
+ const strokeWidth = first.strokeWidth ?? 2;
3890
+ const allSameStroke = stylable.every(
3891
+ (it) => (it.stroke ?? "#1d1d1d") === (first.stroke ?? "#1d1d1d")
3892
+ );
3893
+ const allSameWidth = stylable.every(
3894
+ (it) => (it.strokeWidth ?? 2) === (first.strokeWidth ?? 2)
3895
+ );
3896
+ const draws = stylable.filter(
3897
+ (it) => it.toolKind === "draw" || it.toolKind === "pencil"
3898
+ );
3899
+ const showDash = draws.length > 0;
3900
+ const firstDraw = draws[0];
3901
+ const strokeDash = firstDraw?.strokeDash === "dashed" ? "dashed" : "solid";
3902
+ const allSameDash = draws.length === 0 || draws.every((it) => (it.strokeDash ?? "solid") === strokeDash);
3903
+ const markers = stylable.filter((it) => it.toolKind === "marker");
3904
+ const showMarkerOpacity = markers.length > 0;
3905
+ const firstMarker = markers[0];
3906
+ const strokeOpacity = firstMarker?.strokeOpacity ?? 1;
3907
+ const allSameOpacity = markers.length === 0 || markers.every(
3908
+ (it) => (it.strokeOpacity ?? 1) === (firstMarker?.strokeOpacity ?? 1)
3909
+ );
3910
+ const texts = stylable.filter((it) => it.toolKind === "text");
3911
+ const showFontSize = texts.length > 0;
3912
+ const firstText = texts[0];
3913
+ const textFontSize = firstText?.textFontSize ?? DEFAULT_TEXT_FONT_SIZE;
3914
+ const allSameFontSize = texts.length === 0 || texts.every(
3915
+ (it) => (it.textFontSize ?? DEFAULT_TEXT_FONT_SIZE) === textFontSize
3916
+ );
3917
+ const current = {
3918
+ stroke: hex,
3919
+ strokeWidth,
3920
+ ...showMarkerOpacity ? { strokeOpacity } : {},
3921
+ ...showDash ? { strokeDash } : {},
3922
+ ...showFontSize ? { textFontSize } : {}
3923
+ };
3924
+ return {
3925
+ label: "Estilo da sele\xE7\xE3o",
3926
+ count: stylable.length,
3927
+ hex,
3928
+ strokeWidth,
3929
+ strokeOpacity,
3930
+ strokeDash,
3931
+ textFontSize,
3932
+ showStrokeWidth: true,
3933
+ showDash,
3934
+ showFontSize,
3935
+ showMarkerOpacity,
3936
+ mixedStroke: !allSameStroke,
3937
+ mixedWidth: !allSameWidth,
3938
+ mixedDash: !allSameDash,
3939
+ mixedFontSize: !allSameFontSize,
3940
+ mixedOpacity: !allSameOpacity,
3941
+ current
3942
+ };
3943
+ }
3511
3944
  function VectorSelectionInspector({
3512
3945
  items: itemsProp,
3513
3946
  activeToolStyle: activeToolStyleProp,
@@ -3523,175 +3956,93 @@ function VectorSelectionInspector({
3523
3956
  const activeToolStyle = activeToolStyleProp === void 0 ? ctx?.activeToolStyle ?? null : activeToolStyleProp;
3524
3957
  const onChange = onChangeProp ?? ctx?.onSelectionStyleChange ?? null;
3525
3958
  if (!onChange) return null;
3959
+ let vm = null;
3960
+ if (activeToolStyle) {
3961
+ vm = viewModelFromActiveTool(activeToolStyle);
3962
+ } else {
3963
+ const stylable = items.filter(
3964
+ (it) => !it.locked && it.toolKind && isStylableKind(it.toolKind)
3965
+ );
3966
+ if (stylable.length === 0) return null;
3967
+ vm = viewModelFromSelection(stylable);
3968
+ }
3969
+ if (!vm) return null;
3970
+ const apply = (changes) => onChange({ ...vm.current, ...changes });
3526
3971
  const shell = {
3527
3972
  ...getBoardPositionStyle(position, inset, zIndex),
3528
3973
  ...shellLook,
3529
3974
  ...style
3530
3975
  };
3531
- if (activeToolStyle) {
3532
- const stroke2 = activeToolStyle.stroke;
3533
- const strokeWidth2 = activeToolStyle.strokeWidth;
3534
- const hex2 = normalizeHex(stroke2);
3535
- const showMarkerOpacity2 = activeToolStyle.toolKind === "marker";
3536
- const opacityPct2 = Math.round((activeToolStyle.strokeOpacity ?? 1) * 100);
3537
- return /* @__PURE__ */ jsxs(
3538
- "section",
3539
- {
3540
- "data-slot": "vector-selection-inspector",
3541
- "data-position": position,
3542
- className,
3543
- "aria-label": activeToolStyle.label ?? "Estilo da ferramenta",
3544
- style: shell,
3545
- children: [
3546
- /* @__PURE__ */ jsxs("label", { style: labelStyle2, children: [
3547
- "Cor",
3548
- /* @__PURE__ */ jsx(
3549
- "input",
3550
- {
3551
- type: "color",
3552
- value: hex2,
3553
- onChange: (e) => onChange({
3554
- stroke: e.target.value,
3555
- strokeWidth: strokeWidth2,
3556
- ...activeToolStyle.strokeOpacity != null ? { strokeOpacity: activeToolStyle.strokeOpacity } : {}
3557
- }),
3558
- style: {
3559
- width: "100%",
3560
- height: 32,
3561
- padding: 0,
3562
- border: "none",
3563
- cursor: "pointer",
3564
- background: "transparent"
3565
- }
3566
- }
3567
- )
3568
- ] }),
3569
- /* @__PURE__ */ jsxs("label", { style: labelStyle2, children: [
3570
- "Grossura",
3571
- /* @__PURE__ */ jsx(
3572
- "input",
3573
- {
3574
- type: "range",
3575
- min: 1,
3576
- max: 48,
3577
- value: strokeWidth2,
3578
- onChange: (e) => onChange({
3579
- stroke: hex2,
3580
- strokeWidth: Number(e.target.value),
3581
- ...activeToolStyle.strokeOpacity != null ? { strokeOpacity: activeToolStyle.strokeOpacity } : {}
3582
- })
3583
- }
3584
- )
3585
- ] }),
3586
- showMarkerOpacity2 && /* @__PURE__ */ jsxs("label", { style: labelStyle2, children: [
3587
- "Opacidade (marcador)",
3588
- /* @__PURE__ */ jsx(
3589
- "input",
3590
- {
3591
- type: "range",
3592
- min: 10,
3593
- max: 100,
3594
- value: opacityPct2,
3595
- onChange: (e) => {
3596
- const v = Number(e.target.value) / 100;
3597
- onChange({
3598
- stroke: hex2,
3599
- strokeWidth: strokeWidth2,
3600
- strokeOpacity: v
3601
- });
3602
- }
3603
- }
3604
- ),
3605
- /* @__PURE__ */ jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
3606
- opacityPct2,
3607
- "%"
3608
- ] })
3609
- ] })
3610
- ]
3611
- }
3612
- );
3613
- }
3614
- const stylable = items.filter(
3615
- (it) => !it.locked && it.toolKind && isStylableKind(it.toolKind)
3616
- );
3617
- if (stylable.length === 0) return null;
3618
- const first = stylable[0];
3619
- if (!first) return null;
3620
- const allSameStroke = stylable.every(
3621
- (it) => (it.stroke ?? "#2563eb") === (first.stroke ?? "#2563eb")
3622
- );
3623
- const allSameWidth = stylable.every(
3624
- (it) => (it.strokeWidth ?? 2) === (first.strokeWidth ?? 2)
3625
- );
3626
- const stroke = first.stroke ?? "#2563eb";
3627
- const strokeWidth = first.strokeWidth ?? 2;
3628
- const hex = normalizeHex(stroke);
3629
- const markers = stylable.filter((it) => it.toolKind === "marker");
3630
- const showMarkerOpacity = markers.length > 0;
3631
- const firstMarker = markers[0];
3632
- const allSameMarkerOpacity = markers.length > 0 && markers.every(
3633
- (it) => (it.strokeOpacity ?? 1) === (firstMarker?.strokeOpacity ?? 1)
3634
- );
3635
- const opacityPct = firstMarker ? Math.round((firstMarker.strokeOpacity ?? 1) * 100) : 100;
3976
+ const opacityPct = Math.round(vm.strokeOpacity * 100);
3636
3977
  return /* @__PURE__ */ jsxs(
3637
3978
  "section",
3638
3979
  {
3639
3980
  "data-slot": "vector-selection-inspector",
3640
3981
  "data-position": position,
3641
3982
  className,
3642
- "aria-label": "Estilo da sele\xE7\xE3o",
3983
+ "aria-label": vm.label,
3643
3984
  style: shell,
3644
3985
  children: [
3645
- stylable.length > 1 && /* @__PURE__ */ jsxs("p", { style: { margin: 0, fontSize: 11, color: "#71717a" }, children: [
3646
- stylable.length,
3986
+ vm.count > 1 ? /* @__PURE__ */ jsxs("p", { style: { margin: 0, fontSize: 11, color: "#71717a" }, children: [
3987
+ vm.count,
3647
3988
  " objetos selecionados"
3648
- ] }),
3649
- /* @__PURE__ */ jsxs("label", { style: labelStyle2, children: [
3650
- "Cor",
3651
- !allSameStroke && /* @__PURE__ */ jsxs("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: [
3652
- " ",
3653
- "(valores misturados \u2014 novo valor aplica a todos)"
3989
+ ] }) : null,
3990
+ /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Cor", style: sectionLabelStyle, children: [
3991
+ /* @__PURE__ */ jsxs("span", { children: [
3992
+ "Cor",
3993
+ vm.mixedStroke ? /* @__PURE__ */ jsx("span", { style: mixedHintStyle, children: " (valores misturados)" }) : null
3654
3994
  ] }),
3655
- /* @__PURE__ */ jsx(
3656
- "input",
3657
- {
3658
- type: "color",
3659
- value: hex,
3660
- onChange: (e) => onChange({
3661
- stroke: e.target.value,
3662
- strokeWidth
3663
- }),
3664
- style: {
3665
- width: "100%",
3666
- height: 32,
3667
- padding: 0,
3668
- border: "none",
3669
- cursor: "pointer",
3670
- background: "transparent"
3671
- }
3672
- }
3673
- )
3995
+ /* @__PURE__ */ jsx(ColorPalette, { value: vm.hex, onChange: (h) => apply({ stroke: h }) })
3674
3996
  ] }),
3675
- /* @__PURE__ */ jsxs("label", { style: labelStyle2, children: [
3997
+ vm.showStrokeWidth ? /* @__PURE__ */ jsxs("label", { style: sectionLabelStyle, children: [
3676
3998
  "Grossura",
3677
- !allSameWidth && /* @__PURE__ */ jsx("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: " (misturado)" }),
3999
+ vm.mixedWidth ? /* @__PURE__ */ jsx("span", { style: mixedHintStyle, children: " (misturado)" }) : null,
3678
4000
  /* @__PURE__ */ jsx(
3679
4001
  "input",
3680
4002
  {
3681
4003
  type: "range",
3682
4004
  min: 1,
3683
4005
  max: 48,
3684
- value: strokeWidth,
3685
- onChange: (e) => onChange({
3686
- stroke: hex,
3687
- strokeWidth: Number(e.target.value)
3688
- })
4006
+ value: vm.strokeWidth,
4007
+ onChange: (e) => apply({ strokeWidth: Number(e.target.value) })
3689
4008
  }
3690
4009
  )
3691
- ] }),
3692
- showMarkerOpacity && firstMarker && /* @__PURE__ */ jsxs("label", { style: labelStyle2, children: [
3693
- "Opacidade (marcador)",
3694
- !allSameMarkerOpacity && markers.length > 1 && /* @__PURE__ */ jsx("span", { style: { fontWeight: 400, color: "#a1a1aa" }, children: " (misturado)" }),
4010
+ ] }) : null,
4011
+ vm.showDash ? (
4012
+ // biome-ignore lint/a11y/useSemanticElements: fieldset would impose default browser styling that breaks the inspector layout
4013
+ /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Tra\xE7o", style: sectionLabelStyle, children: [
4014
+ /* @__PURE__ */ jsxs("span", { children: [
4015
+ "Tra\xE7o",
4016
+ vm.mixedDash ? /* @__PURE__ */ jsx("span", { style: mixedHintStyle, children: " (misturado)" }) : null
4017
+ ] }),
4018
+ /* @__PURE__ */ jsx(
4019
+ DashTabs,
4020
+ {
4021
+ value: vm.strokeDash,
4022
+ onChange: (next) => apply({ strokeDash: next })
4023
+ }
4024
+ )
4025
+ ] })
4026
+ ) : null,
4027
+ vm.showFontSize ? (
4028
+ // biome-ignore lint/a11y/useSemanticElements: fieldset would impose default browser styling that breaks the inspector layout
4029
+ /* @__PURE__ */ jsxs("div", { role: "group", "aria-label": "Tamanho", style: sectionLabelStyle, children: [
4030
+ /* @__PURE__ */ jsxs("span", { children: [
4031
+ "Tamanho",
4032
+ vm.mixedFontSize ? /* @__PURE__ */ jsx("span", { style: mixedHintStyle, children: " (misturado)" }) : null
4033
+ ] }),
4034
+ /* @__PURE__ */ jsx(
4035
+ FontSizeTabs,
4036
+ {
4037
+ value: vm.textFontSize,
4038
+ onChange: (size) => apply({ textFontSize: size })
4039
+ }
4040
+ )
4041
+ ] })
4042
+ ) : null,
4043
+ vm.showMarkerOpacity ? /* @__PURE__ */ jsxs("label", { style: sectionLabelStyle, children: [
4044
+ "Opacidade",
4045
+ vm.mixedOpacity ? /* @__PURE__ */ jsx("span", { style: mixedHintStyle, children: " (misturado)" }) : null,
3695
4046
  /* @__PURE__ */ jsx(
3696
4047
  "input",
3697
4048
  {
@@ -3699,21 +4050,14 @@ function VectorSelectionInspector({
3699
4050
  min: 10,
3700
4051
  max: 100,
3701
4052
  value: opacityPct,
3702
- onChange: (e) => {
3703
- const v = Number(e.target.value) / 100;
3704
- onChange({
3705
- stroke: hex,
3706
- strokeWidth,
3707
- strokeOpacity: v
3708
- });
3709
- }
4053
+ onChange: (e) => apply({ strokeOpacity: Number(e.target.value) / 100 })
3710
4054
  }
3711
4055
  ),
3712
4056
  /* @__PURE__ */ jsxs("span", { style: { fontWeight: 500, color: "#71717a" }, children: [
3713
4057
  opacityPct,
3714
4058
  "%"
3715
4059
  ] })
3716
- ] })
4060
+ ] }) : null
3717
4061
  ]
3718
4062
  }
3719
4063
  );
@@ -4192,6 +4536,24 @@ function splitToolbarTools(tools, overflowIds) {
4192
4536
  const overflow = overflowIds.map((id) => tools.find((t) => t.id === id)).filter((t) => Boolean(t));
4193
4537
  return { primary, overflow };
4194
4538
  }
4539
+ function usePromotedOverflowTool({
4540
+ overflowTools,
4541
+ selectedId,
4542
+ initialPromotedId
4543
+ }) {
4544
+ const [lastOverflowToolId, setLastOverflowToolId] = useState(() => {
4545
+ if (initialPromotedId == null) return null;
4546
+ return overflowTools.some((t) => t.id === initialPromotedId) ? initialPromotedId : null;
4547
+ });
4548
+ useEffect(() => {
4549
+ if (overflowTools.some((t) => t.id === selectedId)) {
4550
+ setLastOverflowToolId(selectedId);
4551
+ }
4552
+ }, [selectedId, overflowTools]);
4553
+ const promotedTool = lastOverflowToolId ? overflowTools.find((t) => t.id === lastOverflowToolId) : void 0;
4554
+ const remainingOverflowTools = promotedTool ? overflowTools.filter((t) => t.id !== promotedTool.id) : overflowTools;
4555
+ return { promotedTool, remainingOverflowTools };
4556
+ }
4195
4557
  var icOverflow = { size: 18, strokeWidth: 2 };
4196
4558
  function useOverflowDropdown() {
4197
4559
  const [open, setOpen] = useState(false);
@@ -4726,6 +5088,15 @@ function VectorToolbarComponent({
4726
5088
  const pluginContext = useContext(CanvuPluginContext);
4727
5089
  const runtimeTools = pluginContext?.resolvedTools;
4728
5090
  const resolvedTools = tools ?? runtimeTools ?? DEFAULT_VECTOR_TOOLS;
5091
+ const { primary: primaryTools, overflow: overflowTools } = splitToolbarTools(
5092
+ resolvedTools,
5093
+ overflowToolIds
5094
+ );
5095
+ const { promotedTool, remainingOverflowTools } = usePromotedOverflowTool({
5096
+ overflowTools,
5097
+ selectedId: value,
5098
+ initialPromotedId: overflowToolIds[0] ?? null
5099
+ });
4729
5100
  if (typeof children === "function") {
4730
5101
  const ctx = {
4731
5102
  tools: resolvedTools,
@@ -4770,11 +5141,8 @@ function VectorToolbarComponent({
4770
5141
  )
4771
5142
  ] });
4772
5143
  }
4773
- const { primary: primaryTools, overflow: overflowTools } = splitToolbarTools(
4774
- resolvedTools,
4775
- overflowToolIds
4776
- );
4777
5144
  const showOverflowMenu = overflowTools.length > 0;
5145
+ const showOverflowDropdown = remainingOverflowTools.length > 0;
4778
5146
  const inlineCtx = {
4779
5147
  selectedId: value,
4780
5148
  selectTool: onChange,
@@ -4825,6 +5193,7 @@ function VectorToolbarComponent({
4825
5193
  /* @__PURE__ */ jsx("span", { "aria-hidden": true, style: toolLockDividerStyle })
4826
5194
  ] }) : null,
4827
5195
  primaryTools.map((tool) => renderInlineToolButton(tool, inlineCtx)),
5196
+ promotedTool ? renderInlineToolButton(promotedTool, inlineCtx) : null,
4828
5197
  showOverflowMenu && orientation === "horizontal" ? /* @__PURE__ */ jsx(
4829
5198
  "span",
4830
5199
  {
@@ -4833,10 +5202,10 @@ function VectorToolbarComponent({
4833
5202
  style: overflowSpacerStyle
4834
5203
  }
4835
5204
  ) : null,
4836
- showOverflowMenu ? /* @__PURE__ */ jsx(
5205
+ showOverflowDropdown ? /* @__PURE__ */ jsx(
4837
5206
  ToolbarOverflowMenu,
4838
5207
  {
4839
- tools: overflowTools,
5208
+ tools: remainingOverflowTools,
4840
5209
  selectedId: value,
4841
5210
  onSelect: onChange,
4842
5211
  density,
@@ -6487,6 +6856,7 @@ var VectorScene = class {
6487
6856
 
6488
6857
  // src/react/VectorViewport.tsx
6489
6858
  init_shape_builders();
6859
+ init_text_svg();
6490
6860
 
6491
6861
  // src/react/blob-url-lifecycle.ts
6492
6862
  var SVG_BLOB_HREF_PATTERN = /\b(?:href|xlink:href)=["'](blob:[^"']+)["']/g;
@@ -6692,6 +7062,9 @@ function InteractionOverlay({
6692
7062
  );
6693
7063
  } else if (placementPreview.kind === "rect" || placementPreview.kind === "ellipse" || placementPreview.kind === "architectural-cloud") {
6694
7064
  const r = normalizeRect(placementPreview.rect);
7065
+ const shapeStroke = previewStrokeStyle?.stroke ?? "#1d1d1d";
7066
+ const shapeWidth = previewStrokeStyle?.strokeWidth ?? 2;
7067
+ const shapeOpacity = previewStrokeStyle?.strokeOpacity;
6695
7068
  preview = placementPreview.kind === "rect" ? /* @__PURE__ */ jsx(
6696
7069
  "rect",
6697
7070
  {
@@ -6699,11 +7072,11 @@ function InteractionOverlay({
6699
7072
  y: r.y,
6700
7073
  width: r.width,
6701
7074
  height: r.height,
7075
+ rx: 4,
6702
7076
  fill: "none",
6703
- stroke: "#64748b",
6704
- strokeWidth: overlayStrokePx,
6705
- strokeDasharray: dashPattern,
6706
- vectorEffect: "non-scaling-stroke"
7077
+ stroke: shapeStroke,
7078
+ strokeWidth: shapeWidth,
7079
+ strokeOpacity: shapeOpacity
6707
7080
  }
6708
7081
  ) : placementPreview.kind === "ellipse" ? /* @__PURE__ */ jsx(
6709
7082
  "ellipse",
@@ -6713,29 +7086,30 @@ function InteractionOverlay({
6713
7086
  rx: r.width / 2,
6714
7087
  ry: r.height / 2,
6715
7088
  fill: "none",
6716
- stroke: "#64748b",
6717
- strokeWidth: overlayStrokePx,
6718
- strokeDasharray: dashPattern,
6719
- vectorEffect: "non-scaling-stroke"
7089
+ stroke: shapeStroke,
7090
+ strokeWidth: shapeWidth,
7091
+ strokeOpacity: shapeOpacity
6720
7092
  }
6721
7093
  ) : /* @__PURE__ */ jsx("g", { transform: `translate(${r.x}, ${r.y})`, children: /* @__PURE__ */ jsx(
6722
7094
  "path",
6723
7095
  {
6724
- d: buildArchitecturalCloudPathD(r.width, r.height, overlayStrokePx),
7096
+ d: buildArchitecturalCloudPathD(r.width, r.height, shapeWidth),
6725
7097
  fill: "none",
6726
- stroke: "#64748b",
6727
- strokeWidth: overlayStrokePx,
6728
- strokeDasharray: dashPattern,
7098
+ stroke: shapeStroke,
7099
+ strokeWidth: shapeWidth,
7100
+ strokeOpacity: shapeOpacity,
6729
7101
  strokeLinecap: "round",
6730
- strokeLinejoin: "round",
6731
- vectorEffect: "non-scaling-stroke"
7102
+ strokeLinejoin: "round"
6732
7103
  }
6733
7104
  ) });
6734
7105
  } else if (placementPreview.kind === "line" || placementPreview.kind === "arrow") {
6735
7106
  const { start, end } = placementPreview;
7107
+ const shapeStroke = previewStrokeStyle?.stroke ?? "#1d1d1d";
7108
+ const shapeWidth = previewStrokeStyle?.strokeWidth ?? 2;
7109
+ const shapeOpacity = previewStrokeStyle?.strokeOpacity;
6736
7110
  const geometry = placementPreview.kind === "arrow" ? computeStraightArrowGeometry(
6737
7111
  { x1: start.x, y1: start.y, x2: end.x, y2: end.y },
6738
- overlayStrokePx
7112
+ shapeWidth
6739
7113
  ) : null;
6740
7114
  preview = /* @__PURE__ */ jsxs(Fragment, { children: [
6741
7115
  /* @__PURE__ */ jsx(
@@ -6745,11 +7119,10 @@ function InteractionOverlay({
6745
7119
  y1: start.y,
6746
7120
  x2: geometry?.shaftEndX ?? end.x,
6747
7121
  y2: geometry?.shaftEndY ?? end.y,
6748
- stroke: "#64748b",
6749
- strokeWidth: overlayStrokePx,
6750
- strokeDasharray: dashPattern,
6751
- strokeLinecap: "round",
6752
- vectorEffect: "non-scaling-stroke"
7122
+ stroke: shapeStroke,
7123
+ strokeWidth: shapeWidth,
7124
+ strokeOpacity: shapeOpacity,
7125
+ strokeLinecap: "round"
6753
7126
  }
6754
7127
  ),
6755
7128
  placementPreview.kind === "arrow" && geometry ? /* @__PURE__ */ jsx(
@@ -6757,10 +7130,9 @@ function InteractionOverlay({
6757
7130
  {
6758
7131
  d: `M ${geometry.headLeftX} ${geometry.headLeftY} L ${geometry.headTipX} ${geometry.headTipY} L ${geometry.headRightX} ${geometry.headRightY}`,
6759
7132
  fill: "none",
6760
- stroke: "#64748b",
6761
- strokeWidth: overlayStrokePx,
6762
- strokeOpacity: 0.9,
6763
- strokeDasharray: dashPattern,
7133
+ stroke: shapeStroke,
7134
+ strokeWidth: shapeWidth,
7135
+ strokeOpacity: shapeOpacity,
6764
7136
  strokeLinecap: "round",
6765
7137
  strokeLinejoin: "round"
6766
7138
  }
@@ -6774,7 +7146,8 @@ function InteractionOverlay({
6774
7146
  const previewStyle = {
6775
7147
  stroke: isLaser ? "#f43f5e" : previewStrokeStyle?.stroke ?? "#64748b",
6776
7148
  strokeWidth: isLaser ? 4 : previewStrokeStyle?.strokeWidth ?? (tool === "marker" ? 16 : 3),
6777
- ...previewStrokeStyle?.strokeOpacity != null && !isLaser ? { strokeOpacity: previewStrokeStyle.strokeOpacity } : {}
7149
+ ...previewStrokeStyle?.strokeOpacity != null && !isLaser ? { strokeOpacity: previewStrokeStyle.strokeOpacity } : {},
7150
+ ...previewStrokeStyle?.strokeDash != null && !isLaser ? { strokeDash: previewStrokeStyle.strokeDash } : {}
6778
7151
  };
6779
7152
  const payload = computeFreehandSvgPayload(
6780
7153
  raw,
@@ -6818,6 +7191,7 @@ function InteractionOverlay({
6818
7191
  strokeOpacity: isLaser ? 0.85 : payload.strokeOpacity,
6819
7192
  strokeLinecap: "round",
6820
7193
  strokeLinejoin: "round",
7194
+ strokeDasharray: payload.strokeDasharray,
6821
7195
  shapeRendering: "geometricPrecision"
6822
7196
  }
6823
7197
  );
@@ -7223,6 +7597,9 @@ function TextEditOverlay({
7223
7597
  const fontSize = fsWorld * z;
7224
7598
  const lineHeight = fsWorld * (22 / 18) * z;
7225
7599
  const fixedBox = !!item.textFixedBounds;
7600
+ const padTop = EDIT_TOP_PAD_RATIO * fsWorld * z;
7601
+ const padX = 4 * z;
7602
+ const textColor = item.stroke ?? "#1d1d1d";
7226
7603
  return /* @__PURE__ */ jsx(
7227
7604
  "div",
7228
7605
  {
@@ -7253,14 +7630,14 @@ function TextEditOverlay({
7253
7630
  width: "100%",
7254
7631
  height: "100%",
7255
7632
  margin: 0,
7256
- padding: 2,
7633
+ padding: `${padTop}px ${padX}px 0px ${padX}px`,
7257
7634
  border: "none",
7258
7635
  borderRadius: 0,
7259
7636
  resize: "none",
7260
- fontFamily: "system-ui, sans-serif",
7637
+ fontFamily: TEXT_FONT_FAMILY,
7261
7638
  fontSize,
7262
7639
  lineHeight: `${lineHeight}px`,
7263
- color: "#0f172a",
7640
+ color: textColor,
7264
7641
  background: "transparent",
7265
7642
  backgroundColor: "transparent",
7266
7643
  outline: "none",
@@ -7268,7 +7645,6 @@ function TextEditOverlay({
7268
7645
  appearance: "none",
7269
7646
  WebkitAppearance: "none",
7270
7647
  WebkitTapHighlightColor: "transparent",
7271
- /* Auto-sized text: no soft wrap / scrollbar; fixed box: wrap like the SVG. */
7272
7648
  whiteSpace: fixedBox ? "pre-wrap" : "pre",
7273
7649
  overflow: fixedBox ? "auto" : "hidden",
7274
7650
  overflowX: fixedBox ? "hidden" : "hidden",
@@ -7958,14 +8334,32 @@ var VectorViewport = forwardRef(
7958
8334
  imageStoreRef.current = new IndexedDbImageStore();
7959
8335
  }
7960
8336
  const rememberedImageBlobHrefsRef = useRef(/* @__PURE__ */ new Set());
7961
- const strokeStyleRef = useRef({ ...DEFAULT_STROKE_STYLE });
8337
+ const strokeStyleRef = useRef({
8338
+ ...DEFAULT_STROKE_STYLE,
8339
+ textFontSize: DEFAULT_TEXT_FONT_SIZE
8340
+ });
7962
8341
  const [strokeStyleState, setStrokeStyleState] = useState({
7963
- ...DEFAULT_STROKE_STYLE
8342
+ ...DEFAULT_STROKE_STYLE,
8343
+ textFontSize: DEFAULT_TEXT_FONT_SIZE
7964
8344
  });
7965
8345
  const setCurrentStrokeStyle = useCallback((next) => {
7966
8346
  strokeStyleRef.current = next;
7967
8347
  setStrokeStyleState(next);
7968
8348
  }, []);
8349
+ const patchCurrentStrokeStyle = useCallback(
8350
+ (patch) => {
8351
+ const merged = { ...strokeStyleRef.current };
8352
+ for (const key of Object.keys(patch)) {
8353
+ const v = patch[key];
8354
+ if (v !== void 0) {
8355
+ merged[key] = v;
8356
+ }
8357
+ }
8358
+ strokeStyleRef.current = merged;
8359
+ setStrokeStyleState(merged);
8360
+ },
8361
+ []
8362
+ );
7969
8363
  const progressiveQueueRef = useRef(/* @__PURE__ */ new Set());
7970
8364
  useEffect(() => {
7971
8365
  const store = imageStoreRef.current;
@@ -8114,10 +8508,11 @@ var VectorViewport = forwardRef(
8114
8508
  change(
8115
8509
  exists ? replaceItem(itemsRef.current, id, item) : [...itemsRef.current, item]
8116
8510
  );
8117
- setCurrentStrokeStyle({
8511
+ patchCurrentStrokeStyle({
8118
8512
  stroke: item.stroke ?? DEFAULT_STROKE_STYLE.stroke,
8119
8513
  strokeWidth: item.strokeWidth ?? DEFAULT_STROKE_STYLE.strokeWidth,
8120
- ...item.strokeOpacity != null ? { strokeOpacity: item.strokeOpacity } : {}
8514
+ strokeOpacity: item.strokeOpacity,
8515
+ strokeDash: item.strokeDash
8121
8516
  });
8122
8517
  }
8123
8518
  if (!shouldKeepToolForContinuousPenInput(args.tool, args.pointerType)) {
@@ -8125,8 +8520,8 @@ var VectorViewport = forwardRef(
8125
8520
  }
8126
8521
  },
8127
8522
  [
8523
+ patchCurrentStrokeStyle,
8128
8524
  requestAutoResetTool,
8129
- setCurrentStrokeStyle,
8130
8525
  shouldKeepToolForContinuousPenInput
8131
8526
  ]
8132
8527
  );
@@ -8862,24 +9257,28 @@ var VectorViewport = forwardRef(
8862
9257
  renderFrame();
8863
9258
  }, [renderFrame]);
8864
9259
  useEffect(() => {
9260
+ const current = strokeStyleRef.current;
8865
9261
  if (toolId === "marker") {
8866
9262
  setCurrentStrokeStyle({
8867
- ...strokeStyleRef.current,
9263
+ ...current,
8868
9264
  ...MARKER_TOOL_STYLE
8869
9265
  });
8870
9266
  return;
8871
9267
  }
8872
- const current = strokeStyleRef.current;
8873
9268
  if (isDefaultMarkerToolStyle(current)) {
8874
9269
  setCurrentStrokeStyle({
8875
9270
  stroke: DEFAULT_STROKE_STYLE.stroke,
8876
- strokeWidth: toolId === "draw" ? 10 : DEFAULT_STROKE_STYLE.strokeWidth
9271
+ strokeWidth: toolId === "draw" ? 10 : DEFAULT_STROKE_STYLE.strokeWidth,
9272
+ strokeDash: current.strokeDash,
9273
+ textFontSize: current.textFontSize
8877
9274
  });
8878
9275
  return;
8879
9276
  }
8880
9277
  setCurrentStrokeStyle({
8881
9278
  stroke: current.stroke,
8882
- strokeWidth: toolId === "draw" ? 10 : current.strokeWidth
9279
+ strokeWidth: toolId === "draw" ? 10 : current.strokeWidth,
9280
+ strokeDash: current.strokeDash,
9281
+ textFontSize: current.textFontSize
8883
9282
  });
8884
9283
  }, [setCurrentStrokeStyle, toolId]);
8885
9284
  useEffect(() => {
@@ -8888,15 +9287,14 @@ var VectorViewport = forwardRef(
8888
9287
  const it = items.find((i) => i.id === primaryId);
8889
9288
  if (!it || it.locked) return;
8890
9289
  if (it.toolKind === "image") return;
8891
- const next = {
9290
+ patchCurrentStrokeStyle({
8892
9291
  stroke: it.stroke ?? DEFAULT_STROKE_STYLE.stroke,
8893
- strokeWidth: it.strokeWidth ?? DEFAULT_STROKE_STYLE.strokeWidth
8894
- };
8895
- if (it.strokeOpacity != null) {
8896
- next.strokeOpacity = it.strokeOpacity;
8897
- }
8898
- setCurrentStrokeStyle(next);
8899
- }, [effectiveSelectedIds, items, setCurrentStrokeStyle]);
9292
+ strokeWidth: it.strokeWidth ?? DEFAULT_STROKE_STYLE.strokeWidth,
9293
+ strokeOpacity: it.strokeOpacity,
9294
+ strokeDash: it.strokeDash,
9295
+ ...it.toolKind === "text" && it.textFontSize != null ? { textFontSize: it.textFontSize } : {}
9296
+ });
9297
+ }, [effectiveSelectedIds, items, patchCurrentStrokeStyle]);
8900
9298
  const handleSelectionStyleChange = useCallback(
8901
9299
  (patch) => {
8902
9300
  const change = onItemsChangeRef.current;
@@ -8912,25 +9310,15 @@ var VectorViewport = forwardRef(
8912
9310
  nextList = replaceItem(nextList, id, out);
8913
9311
  }
8914
9312
  change(nextList);
8915
- setCurrentStrokeStyle({
8916
- ...strokeStyleRef.current,
8917
- stroke: patch.stroke,
8918
- strokeWidth: patch.strokeWidth,
8919
- ...patch.strokeOpacity != null ? { strokeOpacity: patch.strokeOpacity } : {}
8920
- });
9313
+ patchCurrentStrokeStyle(patch);
8921
9314
  },
8922
- [setCurrentStrokeStyle]
9315
+ [patchCurrentStrokeStyle]
8923
9316
  );
8924
9317
  const handleActiveToolStyleChange = useCallback(
8925
9318
  (patch) => {
8926
- const current = strokeStyleRef.current;
8927
- setCurrentStrokeStyle({
8928
- stroke: patch.stroke,
8929
- strokeWidth: patch.strokeWidth,
8930
- ...patch.strokeOpacity != null ? { strokeOpacity: patch.strokeOpacity } : current.strokeOpacity != null ? { strokeOpacity: current.strokeOpacity } : {}
8931
- });
9319
+ patchCurrentStrokeStyle(patch);
8932
9320
  },
8933
- [setCurrentStrokeStyle]
9321
+ [patchCurrentStrokeStyle]
8934
9322
  );
8935
9323
  const commitTextEdit = useCallback(() => {
8936
9324
  const id = editingTextIdRef.current;
@@ -10063,16 +10451,21 @@ var VectorViewport = forwardRef(
10063
10451
  const id = createShapeId();
10064
10452
  const { x: worldX, y: worldY } = st.startWorld;
10065
10453
  if (st.tool === "text") {
10454
+ const fs = strokeStyleRef.current.textFontSize;
10455
+ const baseline = textBaselineYFor(fs);
10456
+ const lh = textLineHeightFor(fs);
10457
+ const minH = Math.max(26, baseline + Math.max(4, lh * 0.2));
10066
10458
  const newItem = createTextItem(
10067
10459
  id,
10068
10460
  {
10069
10461
  x: worldX - 4,
10070
- y: worldY - 24,
10462
+ y: worldY - baseline,
10071
10463
  width: 160,
10072
- height: 36
10464
+ height: minH
10073
10465
  },
10074
10466
  "",
10075
- strokeStyleRef.current
10467
+ strokeStyleRef.current,
10468
+ fs
10076
10469
  );
10077
10470
  editingTextSnapshotRef.current = {
10078
10471
  ...newItem,
@@ -10223,12 +10616,63 @@ var VectorViewport = forwardRef(
10223
10616
  return effectiveSelectedIds.map((id) => resolvedItems.find((i) => i.id === id)).filter((i) => i != null);
10224
10617
  }, [effectiveSelectedIds, resolvedItems]);
10225
10618
  const activeToolInspectorStyle = useMemo(() => {
10226
- if (toolId !== "draw") return void 0;
10227
- return {
10228
- toolKind: "draw",
10229
- label: "Estilo da ferramenta",
10230
- ...strokeStyleState
10231
- };
10619
+ if (toolId === "draw") {
10620
+ return {
10621
+ toolKind: "draw",
10622
+ label: "Caneta",
10623
+ ...strokeStyleState
10624
+ };
10625
+ }
10626
+ if (toolId === "marker") {
10627
+ return {
10628
+ toolKind: "marker",
10629
+ label: "Marcador",
10630
+ ...strokeStyleState
10631
+ };
10632
+ }
10633
+ if (toolId === "text") {
10634
+ return {
10635
+ toolKind: "text",
10636
+ label: "Texto",
10637
+ ...strokeStyleState
10638
+ };
10639
+ }
10640
+ if (toolId === "rect") {
10641
+ return {
10642
+ toolKind: "rect",
10643
+ label: "Ret\xE2ngulo",
10644
+ ...strokeStyleState
10645
+ };
10646
+ }
10647
+ if (toolId === "ellipse") {
10648
+ return {
10649
+ toolKind: "ellipse",
10650
+ label: "Elipse",
10651
+ ...strokeStyleState
10652
+ };
10653
+ }
10654
+ if (toolId === "architectural-cloud") {
10655
+ return {
10656
+ toolKind: "architectural-cloud",
10657
+ label: "Nuvem",
10658
+ ...strokeStyleState
10659
+ };
10660
+ }
10661
+ if (toolId === "line") {
10662
+ return {
10663
+ toolKind: "line",
10664
+ label: "Linha",
10665
+ ...strokeStyleState
10666
+ };
10667
+ }
10668
+ if (toolId === "arrow") {
10669
+ return {
10670
+ toolKind: "arrow",
10671
+ label: "Seta",
10672
+ ...strokeStyleState
10673
+ };
10674
+ }
10675
+ return void 0;
10232
10676
  }, [strokeStyleState, toolId]);
10233
10677
  const eraserPreviewItemsForOverlay = useMemo(() => {
10234
10678
  return eraserPreviewIds.map((id) => resolvedItems.find((i) => i.id === id)).filter((i) => i != null);