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/tldraw.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { V as VectorSceneItem } from './types-Bnq2HtHQ.cjs';
1
+ import { V as VectorSceneItem } from './types-BCCvY6ie.cjs';
2
2
 
3
3
  /** Types used by tldraw snapshots (subset needed for conversion). */
4
4
  type TlUnknownObject = Record<string, unknown>;
package/dist/tldraw.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { V as VectorSceneItem } from './types-Bnq2HtHQ.js';
1
+ import { V as VectorSceneItem } from './types-BCCvY6ie.js';
2
2
 
3
3
  /** Types used by tldraw snapshots (subset needed for conversion). */
4
4
  type TlUnknownObject = Record<string, unknown>;
package/dist/tldraw.js CHANGED
@@ -44,19 +44,87 @@ function createCustomShapeItem(id, bounds, content) {
44
44
  };
45
45
  }
46
46
 
47
+ // src/scene/link-item.ts
48
+ var LINK_PLUGIN_KEY = "canvuLink";
49
+ var LINK_CARD_BORDER = "#e2e8f0";
50
+ var LINK_CARD_ACCENT = "#2563eb";
51
+ var LINK_CARD_TITLE_COLOR = "#0f172a";
52
+ var LINK_CARD_TEXT_COLOR = "#475569";
53
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
54
+ var formatNumber = (value) => {
55
+ const rounded = Math.round(value * 100) / 100;
56
+ return Object.is(rounded, -0) ? "0" : String(rounded);
57
+ };
58
+ var escapeXmlAttribute = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
59
+ var escapeHtmlText = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
60
+ var getLinkHostname = (href) => {
61
+ try {
62
+ return new URL(href).hostname.replace(/^www\./, "");
63
+ } catch {
64
+ return href;
65
+ }
66
+ };
67
+ var buildLinkTextBand = (band) => {
68
+ const lineHeight = band.fontSize * 1.3;
69
+ const weight = band.fontWeight != null ? `font-weight:${band.fontWeight};` : "";
70
+ const lineClampStyle = band.clampLines ? `display:-webkit-box;-webkit-line-clamp:${band.clampLines};-webkit-box-orient:vertical;` : "";
71
+ return `<foreignObject x="${formatNumber(band.x)}" y="${formatNumber(band.y)}" width="${formatNumber(Math.max(1, band.width))}" height="${formatNumber(Math.max(1, band.height))}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;font-family:system-ui,sans-serif;font-size:${formatNumber(band.fontSize)}px;line-height:${formatNumber(lineHeight)}px;color:${band.color};overflow:hidden;word-break:break-word;${lineClampStyle}${weight}">${escapeHtmlText(band.text)}</div></foreignObject>`;
72
+ };
73
+ function buildLinkCardSvg(width, height, link) {
74
+ const cardWidth = Math.max(1, width);
75
+ const cardHeight = Math.max(1, height);
76
+ const padding = 14;
77
+ const badgeSize = clamp(Math.min(72, cardHeight - padding * 2), 28, 96);
78
+ const textX = padding + badgeSize + 14;
79
+ const textWidth = Math.max(1, cardWidth - textX - padding);
80
+ const hostname = getLinkHostname(link.href);
81
+ const title = link.title?.trim() || hostname || "Link";
82
+ const description = link.description?.trim() || link.href;
83
+ const titleY = padding;
84
+ const titleHeight = clamp(cardHeight * 0.22, 18, 28);
85
+ const hostY = titleY + titleHeight + 2;
86
+ const hostHeight = 16;
87
+ const descY = hostY + hostHeight + 4;
88
+ const descHeight = Math.max(1, cardHeight - descY - padding);
89
+ const badge = link.favicon ? `<clipPath id="canvu-link-badge"><rect x="${formatNumber(padding)}" y="${formatNumber(padding)}" width="${formatNumber(badgeSize)}" height="${formatNumber(badgeSize)}" rx="12" /></clipPath>
90
+ <rect x="${formatNumber(padding)}" y="${formatNumber(padding)}" width="${formatNumber(badgeSize)}" height="${formatNumber(badgeSize)}" rx="12" fill="#f8fafc" stroke="${LINK_CARD_BORDER}" stroke-width="1" />
91
+ <image href="${escapeXmlAttribute(link.favicon)}" x="${formatNumber(padding)}" y="${formatNumber(padding)}" width="${formatNumber(badgeSize)}" height="${formatNumber(badgeSize)}" preserveAspectRatio="xMidYMid slice" clip-path="url(#canvu-link-badge)" />` : `<rect x="${formatNumber(padding)}" y="${formatNumber(padding)}" width="${formatNumber(badgeSize)}" height="${formatNumber(badgeSize)}" rx="12" fill="${LINK_CARD_ACCENT}" fill-opacity="0.1" stroke="${LINK_CARD_ACCENT}" stroke-opacity="0.3" stroke-width="1" />
92
+ <g transform="translate(${formatNumber(padding + badgeSize / 2)},${formatNumber(padding + badgeSize / 2)})" stroke="${LINK_CARD_ACCENT}" stroke-width="2.4" stroke-linecap="round" fill="none">
93
+ <path d="M-9 3 a6 6 0 0 1 0 -8 l4 -4 a6 6 0 0 1 8 8 l-2 2" />
94
+ <path d="M9 -3 a6 6 0 0 1 0 8 l-4 4 a6 6 0 0 1 -8 -8 l2 -2" />
95
+ </g>`;
96
+ const externalIconX = cardWidth - padding - 16;
97
+ const externalIcon = `<g transform="translate(${formatNumber(externalIconX)},${formatNumber(padding)})" stroke="${LINK_CARD_TEXT_COLOR}" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" fill="none">
98
+ <path d="M6 1 H11 V6" />
99
+ <path d="M11 1 L4.5 7.5" />
100
+ <path d="M9 7 V10 a1 1 0 0 1 -1 1 H2 a1 1 0 0 1 -1 -1 V4 a1 1 0 0 1 1 -1 H5" />
101
+ </g>`;
102
+ return `
103
+ <rect width="${formatNumber(cardWidth)}" height="${formatNumber(cardHeight)}" rx="16" fill="#ffffff" stroke="${LINK_CARD_BORDER}" stroke-width="1.5" />
104
+ ${badge}
105
+ ${externalIcon}
106
+ ${buildLinkTextBand({ x: textX, y: titleY, width: textWidth - 18, height: titleHeight, text: title, fontSize: 15, color: LINK_CARD_TITLE_COLOR, fontWeight: 700, clampLines: 1 })}
107
+ ${buildLinkTextBand({ x: textX, y: hostY, width: textWidth, height: hostHeight, text: hostname, fontSize: 12, color: LINK_CARD_ACCENT, clampLines: 1 })}
108
+ ${buildLinkTextBand({ x: textX, y: descY, width: textWidth, height: descHeight, text: description, fontSize: 12, color: LINK_CARD_TEXT_COLOR, clampLines: 2 })}
109
+ `;
110
+ }
111
+
47
112
  // src/scene/text-svg.ts
48
113
  function escapeSvgTextContent(s) {
49
114
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
50
115
  }
51
- function escapeHtmlText(s) {
116
+ function escapeHtmlText2(s) {
52
117
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
53
118
  }
54
119
  var DEFAULT_TEXT_FONT_SIZE = 18;
120
+ var TEXT_FONT_FAMILY = "Inter, -apple-system, BlinkMacSystemFont, ui-sans-serif, system-ui, sans-serif";
55
121
  var LINE_HEIGHT_RATIO = 22 / 18;
56
- var FIRST_LINE_BASELINE_RATIO = 24 / 18;
122
+ var FIRST_LINE_BASELINE_RATIO = 20 / 18;
123
+ var EDIT_TOP_PAD_RATIO = 4 / 18;
124
+ var BOTTOM_PAD_RATIO = 4 / 18;
57
125
  var PLACEHOLDER = "Tap to type";
58
126
  var MIN_TEXT_BOX_W = 40;
59
- var MIN_TEXT_BOX_H = 36;
127
+ var MIN_TEXT_BOX_H = 26;
60
128
  var TEXT_PAD_X = 4;
61
129
  var MAX_TEXT_MEASURE_CACHE_ENTRIES = 2e3;
62
130
  var sharedMeasureContext;
@@ -102,7 +170,7 @@ function measureTextBoundsLocal(content, fontSize = DEFAULT_TEXT_FONT_SIZE) {
102
170
  let maxInnerW = 0;
103
171
  const ctx = getSharedMeasureContext();
104
172
  if (ctx) {
105
- ctx.font = `${fontSize}px system-ui, sans-serif`;
173
+ ctx.font = `${fontSize}px ${TEXT_FONT_FAMILY}`;
106
174
  for (const line of lines) {
107
175
  const toMeasure = trimmed.length === 0 ? PLACEHOLDER : line.length === 0 ? " " : line;
108
176
  maxInnerW = Math.max(maxInnerW, ctx.measureText(toMeasure).width);
@@ -118,22 +186,22 @@ function measureTextBoundsLocal(content, fontSize = DEFAULT_TEXT_FONT_SIZE) {
118
186
  const width = Math.max(minW, TEXT_PAD_X * 2 + maxInnerW);
119
187
  const height = Math.max(
120
188
  MIN_TEXT_BOX_H,
121
- baselineY + (lines.length - 1) * lh + Math.max(8, fontSize * 0.35)
189
+ baselineY + (lines.length - 1) * lh + Math.max(4, fontSize * BOTTOM_PAD_RATIO)
122
190
  );
123
191
  const measured = { width, height };
124
192
  cacheMeasuredBounds(cacheKey, measured);
125
193
  return measured;
126
194
  }
127
- function buildTextSvg(content, _width, _height, fillColor = "#2563eb", fontSize = DEFAULT_TEXT_FONT_SIZE) {
195
+ function buildTextSvg(content, _width, _height, fillColor = "#1d1d1d", fontSize = DEFAULT_TEXT_FONT_SIZE) {
128
196
  const lh = lineHeightFor(fontSize);
129
197
  const y0 = firstLineBaselineY(fontSize);
130
198
  const trimmed = content.trim();
131
199
  if (trimmed.length === 0) {
132
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="#94a3b8" font-style="italic">${escapeSvgTextContent(PLACEHOLDER)}</text>`;
200
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="#94a3b8" font-style="italic">${escapeSvgTextContent(PLACEHOLDER)}</text>`;
133
201
  }
134
202
  const lines = content.split("\n");
135
203
  if (lines.length === 1) {
136
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="${fillColor}">${escapeSvgTextContent(lines[0] ?? "")}</text>`;
204
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="${fillColor}">${escapeSvgTextContent(lines[0] ?? "")}</text>`;
137
205
  }
138
206
  const parts = [];
139
207
  for (let i = 0; i < lines.length; i++) {
@@ -144,23 +212,24 @@ function buildTextSvg(content, _width, _height, fillColor = "#2563eb", fontSize
144
212
  parts.push(`<tspan x="4" dy="${lh}">${escapeSvgTextContent(line)}</tspan>`);
145
213
  }
146
214
  }
147
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="${fillColor}">${parts.join("")}</text>`;
215
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="${fillColor}">${parts.join("")}</text>`;
148
216
  }
149
- function buildTextFixedBoundsSvg(content, width, height, fillColor = "#2563eb", fontSize = DEFAULT_TEXT_FONT_SIZE) {
217
+ function buildTextFixedBoundsSvg(content, width, height, fillColor = "#1d1d1d", fontSize = DEFAULT_TEXT_FONT_SIZE) {
150
218
  const w = Math.max(1, width);
151
219
  const h = Math.max(1, height);
152
220
  const lh = lineHeightFor(fontSize);
153
221
  const trimmed = content.trim();
222
+ const padTop = EDIT_TOP_PAD_RATIO * fontSize;
154
223
  if (trimmed.length === 0) {
155
- 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>`;
224
+ 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">${escapeHtmlText2(PLACEHOLDER)}</div></foreignObject>`;
156
225
  }
157
- const body = escapeHtmlText(content);
158
- 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>`;
226
+ const body = escapeHtmlText2(content);
227
+ 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>`;
159
228
  }
160
229
 
161
230
  // src/scene/shape-builders.ts
162
231
  var DEFAULT_STROKE_STYLE = {
163
- stroke: "#2563eb",
232
+ stroke: "#1d1d1d",
164
233
  strokeWidth: 2
165
234
  };
166
235
  function perfectFreehandOptions(toolKind, style, strokeComplete, pressureAware = false) {
@@ -211,7 +280,8 @@ function resolveStrokeStyle(item) {
211
280
  return {
212
281
  stroke: item.stroke ?? DEFAULT_STROKE_STYLE.stroke,
213
282
  strokeWidth: item.strokeWidth ?? DEFAULT_STROKE_STYLE.strokeWidth,
214
- strokeOpacity: item.strokeOpacity
283
+ strokeOpacity: item.strokeOpacity,
284
+ strokeDash: item.strokeDash
215
285
  };
216
286
  }
217
287
  function strokeOpacityAttr(style) {
@@ -540,6 +610,30 @@ function buildDrawDotSvg(r, style = DEFAULT_STROKE_STYLE) {
540
610
  const op = style.strokeOpacity != null ? ` fill-opacity="${style.strokeOpacity}"` : "";
541
611
  return `<circle cx="${r}" cy="${r}" r="${r}" fill="${style.stroke}" shape-rendering="geometricPrecision"${op} />`;
542
612
  }
613
+ function dashArrayForDrawStroke(strokeWidth) {
614
+ const dash = Math.max(strokeWidth * 1.8, 4);
615
+ const gap = Math.max(strokeWidth * 1.4, 3);
616
+ return `${dash} ${gap}`;
617
+ }
618
+ function buildSmoothedCenterlinePath(points) {
619
+ if (points.length < 2) return null;
620
+ const first = points[0];
621
+ if (!first) return null;
622
+ let d = `M ${first.x} ${first.y}`;
623
+ for (let i = 1; i < points.length - 1; i++) {
624
+ const a = points[i];
625
+ const b = points[i + 1];
626
+ if (!a || !b) continue;
627
+ const midX = (a.x + b.x) / 2;
628
+ const midY = (a.y + b.y) / 2;
629
+ d += ` Q ${a.x} ${a.y} ${midX} ${midY}`;
630
+ }
631
+ const last = points[points.length - 1];
632
+ if (last) {
633
+ d += ` L ${last.x} ${last.y}`;
634
+ }
635
+ return d;
636
+ }
543
637
  function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeComplete = true) {
544
638
  if (pathPointsLocal.length === 0) return null;
545
639
  if (pathPointsLocal.length === 1) {
@@ -554,6 +648,18 @@ function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeCompl
554
648
  fillOpacity: style.strokeOpacity
555
649
  };
556
650
  }
651
+ if (style.strokeDash === "dashed" && (toolKind === "draw" || toolKind === "pencil")) {
652
+ const d2 = buildSmoothedCenterlinePath(pathPointsLocal);
653
+ if (!d2) return null;
654
+ return {
655
+ kind: "strokePath",
656
+ d: d2,
657
+ stroke: style.stroke,
658
+ strokeWidth: style.strokeWidth,
659
+ strokeOpacity: style.strokeOpacity,
660
+ strokeDasharray: dashArrayForDrawStroke(style.strokeWidth)
661
+ };
662
+ }
557
663
  const hasPressure = pathPointsLocal.some(
558
664
  (p) => p.pressure != null && Number.isFinite(p.pressure)
559
665
  );
@@ -596,7 +702,8 @@ function freehandPayloadToSvgString(payload) {
596
702
  strokeWidth: payload.strokeWidth,
597
703
  strokeOpacity: payload.strokeOpacity
598
704
  });
599
- return `<path d="${payload.d}" fill="none" stroke="${payload.stroke}" stroke-width="${payload.strokeWidth}" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision"${op} />`;
705
+ const dash = payload.strokeDasharray ? ` stroke-dasharray="${payload.strokeDasharray}"` : "";
706
+ 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} />`;
600
707
  }
601
708
  function buildFreehandPathSvg(pathPointsLocal, style, toolKind, strokeComplete = true) {
602
709
  const payload = computeFreehandSvgPayload(
@@ -831,10 +938,10 @@ function resolveColor(value) {
831
938
  function escapeXml(value) {
832
939
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
833
940
  }
834
- function escapeHtmlText2(value) {
941
+ function escapeHtmlText3(value) {
835
942
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
836
943
  }
837
- function formatNumber(value) {
944
+ function formatNumber2(value) {
838
945
  if (!Number.isFinite(value)) return "0";
839
946
  const rounded = Math.round(value * 100) / 100;
840
947
  return Number.isInteger(rounded) ? String(rounded) : String(rounded);
@@ -886,13 +993,13 @@ function sizeToFontPx(size) {
886
993
  function dashArrayForStyle(dash, strokeWidth) {
887
994
  if (!dash || dash === "solid") return void 0;
888
995
  if (dash === "dotted") {
889
- return `${formatNumber(Math.max(1.25, strokeWidth))} ${formatNumber(Math.max(2.5, strokeWidth * 2.2))}`;
996
+ return `${formatNumber2(Math.max(1.25, strokeWidth))} ${formatNumber2(Math.max(2.5, strokeWidth * 2.2))}`;
890
997
  }
891
998
  if (dash === "dashed") {
892
- return `${formatNumber(Math.max(5, strokeWidth * 4))} ${formatNumber(Math.max(3, strokeWidth * 2.2))}`;
999
+ return `${formatNumber2(Math.max(5, strokeWidth * 4))} ${formatNumber2(Math.max(3, strokeWidth * 2.2))}`;
893
1000
  }
894
1001
  if (dash === "draw") {
895
- return `${formatNumber(Math.max(3, strokeWidth * 2.4))} ${formatNumber(Math.max(2.4, strokeWidth * 1.6))}`;
1002
+ return `${formatNumber2(Math.max(3, strokeWidth * 2.4))} ${formatNumber2(Math.max(2.4, strokeWidth * 1.6))}`;
896
1003
  }
897
1004
  return dash;
898
1005
  }
@@ -908,11 +1015,11 @@ function strokeAttrs(style) {
908
1015
  const dashAttr = dashArray ? ` stroke-dasharray="${dashArray}"` : "";
909
1016
  const lineCap = style.lineCap ? ` stroke-linecap="${style.lineCap}"` : "";
910
1017
  const lineJoin = style.lineJoin ? ` stroke-linejoin="${style.lineJoin}"` : "";
911
- return `stroke="${style.stroke}" stroke-width="${formatNumber(style.strokeWidth)}"${dashAttr}${lineCap}${lineJoin}`;
1018
+ return `stroke="${style.stroke}" stroke-width="${formatNumber2(style.strokeWidth)}"${dashAttr}${lineCap}${lineJoin}`;
912
1019
  }
913
1020
  function wrapOpacity(svg, opacity) {
914
1021
  if (opacity >= 0.999) return svg;
915
- return `<g opacity="${formatNumber(opacity)}">${svg}</g>`;
1022
+ return `<g opacity="${formatNumber2(opacity)}">${svg}</g>`;
916
1023
  }
917
1024
  function buildForeignObjectTextSvg(options) {
918
1025
  const x = options.x ?? 0;
@@ -927,8 +1034,8 @@ function buildForeignObjectTextSvg(options) {
927
1034
  const weight = options.fontWeight != null ? `font-weight:${options.fontWeight};` : "";
928
1035
  const fontStyle = options.italic ? "font-style:italic;" : "";
929
1036
  const background = options.background ? `background:${options.background};` : "";
930
- const radius = options.borderRadius != null ? `border-radius:${formatNumber(options.borderRadius)}px;` : "";
931
- return `<foreignObject x="${formatNumber(x)}" y="${formatNumber(y)}" width="${formatNumber(w)}" height="${formatNumber(h)}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;padding:${formatNumber(padding)}px;display:flex;align-items:${vertical};justify-content:${justify};text-align:${align};white-space:pre-wrap;word-break:break-word;overflow:hidden;color:${options.color};font-size:${formatNumber(options.fontSize)}px;line-height:${formatNumber(lineHeight)}px;font-family:system-ui,sans-serif;${weight}${fontStyle}${background}${radius}">${escapeHtmlText2(options.text)}</div></foreignObject>`;
1037
+ const radius = options.borderRadius != null ? `border-radius:${formatNumber2(options.borderRadius)}px;` : "";
1038
+ return `<foreignObject x="${formatNumber2(x)}" y="${formatNumber2(y)}" width="${formatNumber2(w)}" height="${formatNumber2(h)}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;padding:${formatNumber2(padding)}px;display:flex;align-items:${vertical};justify-content:${justify};text-align:${align};white-space:pre-wrap;word-break:break-word;overflow:hidden;color:${options.color};font-size:${formatNumber2(options.fontSize)}px;line-height:${formatNumber2(lineHeight)}px;font-family:system-ui,sans-serif;${weight}${fontStyle}${background}${radius}">${escapeHtmlText3(options.text)}</div></foreignObject>`;
932
1039
  }
933
1040
  function richTextToPlainText(value) {
934
1041
  const parts = [];
@@ -1456,17 +1563,17 @@ function createCustomImportedItem(snapshot, shape, localBounds, innerSvg, style)
1456
1563
  }
1457
1564
  function polygonPath(points) {
1458
1565
  if (points.length === 0) return "";
1459
- let path = `M${formatNumber(points[0]?.x ?? 0)} ${formatNumber(points[0]?.y ?? 0)}`;
1566
+ let path = `M${formatNumber2(points[0]?.x ?? 0)} ${formatNumber2(points[0]?.y ?? 0)}`;
1460
1567
  for (let index = 1; index < points.length; index++) {
1461
1568
  const point = points[index];
1462
1569
  if (!point) continue;
1463
- path += ` L${formatNumber(point.x)} ${formatNumber(point.y)}`;
1570
+ path += ` L${formatNumber2(point.x)} ${formatNumber2(point.y)}`;
1464
1571
  }
1465
1572
  return `${path} Z`;
1466
1573
  }
1467
1574
  function cloudPath(width, height) {
1468
1575
  const r = Math.min(width, height) * 0.15;
1469
- return `M${formatNumber(r)} ${formatNumber(height * 0.3)} Q0 ${formatNumber(height * 0.1)} ${formatNumber(width * 0.15)} ${formatNumber(height * 0.05)} Q${formatNumber(width * 0.3)} 0 ${formatNumber(width * 0.45)} ${formatNumber(height * 0.1)} Q${formatNumber(width * 0.7)} 0 ${formatNumber(width * 0.8)} ${formatNumber(height * 0.15)} Q${formatNumber(width)} ${formatNumber(height * 0.2)} ${formatNumber(width * 0.9)} ${formatNumber(height * 0.5)} Q${formatNumber(width)} ${formatNumber(height * 0.7)} ${formatNumber(width * 0.85)} ${formatNumber(height * 0.8)} Q${formatNumber(width * 0.7)} ${formatNumber(height)} ${formatNumber(width * 0.5)} ${formatNumber(height * 0.9)} Q${formatNumber(width * 0.3)} ${formatNumber(height)} ${formatNumber(width * 0.15)} ${formatNumber(height * 0.85)} Q0 ${formatNumber(height * 0.8)} ${formatNumber(r * 0.5)} ${formatNumber(height * 0.6)} Q0 ${formatNumber(height * 0.5)} ${formatNumber(r)} ${formatNumber(height * 0.3)} Z`;
1576
+ return `M${formatNumber2(r)} ${formatNumber2(height * 0.3)} Q0 ${formatNumber2(height * 0.1)} ${formatNumber2(width * 0.15)} ${formatNumber2(height * 0.05)} Q${formatNumber2(width * 0.3)} 0 ${formatNumber2(width * 0.45)} ${formatNumber2(height * 0.1)} Q${formatNumber2(width * 0.7)} 0 ${formatNumber2(width * 0.8)} ${formatNumber2(height * 0.15)} Q${formatNumber2(width)} ${formatNumber2(height * 0.2)} ${formatNumber2(width * 0.9)} ${formatNumber2(height * 0.5)} Q${formatNumber2(width)} ${formatNumber2(height * 0.7)} ${formatNumber2(width * 0.85)} ${formatNumber2(height * 0.8)} Q${formatNumber2(width * 0.7)} ${formatNumber2(height)} ${formatNumber2(width * 0.5)} ${formatNumber2(height * 0.9)} Q${formatNumber2(width * 0.3)} ${formatNumber2(height)} ${formatNumber2(width * 0.15)} ${formatNumber2(height * 0.85)} Q0 ${formatNumber2(height * 0.8)} ${formatNumber2(r * 0.5)} ${formatNumber2(height * 0.6)} Q0 ${formatNumber2(height * 0.5)} ${formatNumber2(r)} ${formatNumber2(height * 0.3)} Z`;
1470
1577
  }
1471
1578
  function geoPath(geo, width, height) {
1472
1579
  if (geo === "diamond" || geo === "rhombus") {
@@ -1545,10 +1652,10 @@ function geoPath(geo, width, height) {
1545
1652
  ]);
1546
1653
  }
1547
1654
  if (geo === "check-box") {
1548
- return `M0 0 H${formatNumber(width)} V${formatNumber(height)} H0 Z M${formatNumber(width * 0.2)} ${formatNumber(height * 0.56)} L${formatNumber(width * 0.42)} ${formatNumber(height * 0.78)} L${formatNumber(width * 0.82)} ${formatNumber(height * 0.24)}`;
1655
+ return `M0 0 H${formatNumber2(width)} V${formatNumber2(height)} H0 Z M${formatNumber2(width * 0.2)} ${formatNumber2(height * 0.56)} L${formatNumber2(width * 0.42)} ${formatNumber2(height * 0.78)} L${formatNumber2(width * 0.82)} ${formatNumber2(height * 0.24)}`;
1549
1656
  }
1550
1657
  if (geo === "x-box") {
1551
- return `M0 0 H${formatNumber(width)} V${formatNumber(height)} H0 Z M${formatNumber(width * 0.22)} ${formatNumber(height * 0.22)} L${formatNumber(width * 0.78)} ${formatNumber(height * 0.78)} M${formatNumber(width * 0.78)} ${formatNumber(height * 0.22)} L${formatNumber(width * 0.22)} ${formatNumber(height * 0.78)}`;
1658
+ return `M0 0 H${formatNumber2(width)} V${formatNumber2(height)} H0 Z M${formatNumber2(width * 0.22)} ${formatNumber2(height * 0.22)} L${formatNumber2(width * 0.78)} ${formatNumber2(height * 0.78)} M${formatNumber2(width * 0.78)} ${formatNumber2(height * 0.22)} L${formatNumber2(width * 0.22)} ${formatNumber2(height * 0.78)}`;
1552
1659
  }
1553
1660
  return null;
1554
1661
  }
@@ -1564,7 +1671,7 @@ function renderGeoShape(snapshot, shape) {
1564
1671
  const text = extractPlainText(props);
1565
1672
  const fontSize = getNumber(props.fontSize) ?? sizeToFontPx(getString(props.size));
1566
1673
  const customPath = geoPath(geo, width, height);
1567
- const shapeMarkup = geo === "ellipse" || geo === "oval" ? `<ellipse cx="${formatNumber(width / 2)}" cy="${formatNumber(height / 2)}" rx="${formatNumber(width / 2)}" ry="${formatNumber(height / 2)}" ${fillAttrs(getString(props.fill), stroke)} ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />` : customPath ? `<path d="${customPath}" ${fillAttrs(getString(props.fill), stroke)} ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />` : `<rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="8" ${fillAttrs(getString(props.fill), stroke)} ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />`;
1674
+ const shapeMarkup = geo === "ellipse" || geo === "oval" ? `<ellipse cx="${formatNumber2(width / 2)}" cy="${formatNumber2(height / 2)}" rx="${formatNumber2(width / 2)}" ry="${formatNumber2(height / 2)}" ${fillAttrs(getString(props.fill), stroke)} ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />` : customPath ? `<path d="${customPath}" ${fillAttrs(getString(props.fill), stroke)} ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />` : `<rect width="${formatNumber2(width)}" height="${formatNumber2(height)}" rx="8" ${fillAttrs(getString(props.fill), stroke)} ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />`;
1568
1675
  const textMarkup = text ? buildForeignObjectTextSvg({
1569
1676
  width,
1570
1677
  height,
@@ -1649,11 +1756,11 @@ function renderStrokeShape(snapshot, shape) {
1649
1756
  }
1650
1757
  function polylinePath(points) {
1651
1758
  if (points.length === 0) return "";
1652
- let path = `M${formatNumber(points[0]?.x ?? 0)} ${formatNumber(points[0]?.y ?? 0)}`;
1759
+ let path = `M${formatNumber2(points[0]?.x ?? 0)} ${formatNumber2(points[0]?.y ?? 0)}`;
1653
1760
  for (let index = 1; index < points.length; index++) {
1654
1761
  const point = points[index];
1655
1762
  if (!point) continue;
1656
- path += ` L${formatNumber(point.x)} ${formatNumber(point.y)}`;
1763
+ path += ` L${formatNumber2(point.x)} ${formatNumber2(point.y)}`;
1657
1764
  }
1658
1765
  return path;
1659
1766
  }
@@ -1675,22 +1782,22 @@ function buildArrowHeadSvg(options) {
1675
1782
  const left = { x: bx + px * (headWidth / 2), y: by + py * (headWidth / 2) };
1676
1783
  const right = { x: bx - px * (headWidth / 2), y: by - py * (headWidth / 2) };
1677
1784
  if (type === "triangle") {
1678
- return `<polygon points="${formatNumber(options.tip.x)},${formatNumber(options.tip.y)} ${formatNumber(left.x)},${formatNumber(left.y)} ${formatNumber(right.x)},${formatNumber(right.y)}" fill="${options.stroke}" />`;
1785
+ return `<polygon points="${formatNumber2(options.tip.x)},${formatNumber2(options.tip.y)} ${formatNumber2(left.x)},${formatNumber2(left.y)} ${formatNumber2(right.x)},${formatNumber2(right.y)}" fill="${options.stroke}" />`;
1679
1786
  }
1680
1787
  if (type === "dot") {
1681
- return `<circle cx="${formatNumber(options.tip.x)}" cy="${formatNumber(options.tip.y)}" r="${formatNumber(Math.max(3, options.strokeWidth * 1.5))}" fill="${options.stroke}" />`;
1788
+ return `<circle cx="${formatNumber2(options.tip.x)}" cy="${formatNumber2(options.tip.y)}" r="${formatNumber2(Math.max(3, options.strokeWidth * 1.5))}" fill="${options.stroke}" />`;
1682
1789
  }
1683
1790
  if (type === "diamond") {
1684
1791
  const mid = {
1685
1792
  x: options.tip.x - ux * (headLength / 2),
1686
1793
  y: options.tip.y - uy * (headLength / 2)
1687
1794
  };
1688
- return `<polygon points="${formatNumber(options.tip.x)},${formatNumber(options.tip.y)} ${formatNumber(left.x)},${formatNumber(left.y)} ${formatNumber(mid.x - ux * (headLength / 2))},${formatNumber(mid.y - uy * (headLength / 2))} ${formatNumber(right.x)},${formatNumber(right.y)}" fill="none" stroke="${options.stroke}" stroke-width="${formatNumber(options.strokeWidth)}" stroke-linejoin="round" />`;
1795
+ return `<polygon points="${formatNumber2(options.tip.x)},${formatNumber2(options.tip.y)} ${formatNumber2(left.x)},${formatNumber2(left.y)} ${formatNumber2(mid.x - ux * (headLength / 2))},${formatNumber2(mid.y - uy * (headLength / 2))} ${formatNumber2(right.x)},${formatNumber2(right.y)}" fill="none" stroke="${options.stroke}" stroke-width="${formatNumber2(options.strokeWidth)}" stroke-linejoin="round" />`;
1689
1796
  }
1690
1797
  if (type === "bar") {
1691
- return `<line x1="${formatNumber(left.x)}" y1="${formatNumber(left.y)}" x2="${formatNumber(right.x)}" y2="${formatNumber(right.y)}" stroke="${options.stroke}" stroke-width="${formatNumber(options.strokeWidth)}" stroke-linecap="round" />`;
1798
+ return `<line x1="${formatNumber2(left.x)}" y1="${formatNumber2(left.y)}" x2="${formatNumber2(right.x)}" y2="${formatNumber2(right.y)}" stroke="${options.stroke}" stroke-width="${formatNumber2(options.strokeWidth)}" stroke-linecap="round" />`;
1692
1799
  }
1693
- return `<path d="M ${formatNumber(left.x)} ${formatNumber(left.y)} L ${formatNumber(options.tip.x)} ${formatNumber(options.tip.y)} L ${formatNumber(right.x)} ${formatNumber(right.y)}" fill="none" stroke="${options.stroke}" stroke-width="${formatNumber(options.strokeWidth)}" stroke-linecap="round" stroke-linejoin="round" />`;
1800
+ return `<path d="M ${formatNumber2(left.x)} ${formatNumber2(left.y)} L ${formatNumber2(options.tip.x)} ${formatNumber2(options.tip.y)} L ${formatNumber2(right.x)} ${formatNumber2(right.y)}" fill="none" stroke="${options.stroke}" stroke-width="${formatNumber2(options.strokeWidth)}" stroke-linecap="round" stroke-linejoin="round" />`;
1694
1801
  }
1695
1802
  function renderLineShape(snapshot, shape) {
1696
1803
  const props = asRecord(shape.props) ?? {};
@@ -1759,7 +1866,7 @@ function renderArrowShape(snapshot, shape) {
1759
1866
  const translatedPoints = route.points.map(translate);
1760
1867
  const translatedStartRef = translate(route.startRef);
1761
1868
  const translatedEndRef = translate(route.endRef);
1762
- const shaft = route.kind === "quadratic" && route.control ? `<path d="M${formatNumber(translatedPoints[0]?.x ?? 0)} ${formatNumber(translatedPoints[0]?.y ?? 0)} Q${formatNumber(route.control.x - localBounds.x)} ${formatNumber(route.control.y - localBounds.y)} ${formatNumber(translatedPoints[1]?.x ?? 0)} ${formatNumber(translatedPoints[1]?.y ?? 0)}" fill="none" ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />` : `<path d="${polylinePath(translatedPoints)}" fill="none" ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />`;
1869
+ const shaft = route.kind === "quadratic" && route.control ? `<path d="M${formatNumber2(translatedPoints[0]?.x ?? 0)} ${formatNumber2(translatedPoints[0]?.y ?? 0)} Q${formatNumber2(route.control.x - localBounds.x)} ${formatNumber2(route.control.y - localBounds.y)} ${formatNumber2(translatedPoints[1]?.x ?? 0)} ${formatNumber2(translatedPoints[1]?.y ?? 0)}" fill="none" ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />` : `<path d="${polylinePath(translatedPoints)}" fill="none" ${strokeAttrs({ stroke, strokeWidth, dash, lineCap: "round", lineJoin: "round" })} />`;
1763
1870
  const startPoint = translatedPoints[0];
1764
1871
  const endPoint = translatedPoints[translatedPoints.length - 1];
1765
1872
  if (!startPoint || !endPoint) return null;
@@ -1791,9 +1898,9 @@ function renderMissingAssetPlaceholder(snapshot, shape, kind, detail) {
1791
1898
  const width = localBounds.width;
1792
1899
  const height = localBounds.height;
1793
1900
  const inner = `
1794
- <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="12" fill="#f4f4f5" stroke="#a1a1aa" stroke-width="1.5" stroke-dasharray="6 6" />
1795
- <path d="M${formatNumber(width * 0.18)} ${formatNumber(height * 0.7)} L${formatNumber(width * 0.38)} ${formatNumber(height * 0.48)} L${formatNumber(width * 0.56)} ${formatNumber(height * 0.62)} L${formatNumber(width * 0.76)} ${formatNumber(height * 0.32)} L${formatNumber(width * 0.84)} ${formatNumber(height * 0.4)} L${formatNumber(width * 0.84)} ${formatNumber(height * 0.82)} L${formatNumber(width * 0.18)} ${formatNumber(height * 0.82)} Z" fill="#d4d4d8" stroke="#a1a1aa" stroke-width="1" />
1796
- <circle cx="${formatNumber(width * 0.34)}" cy="${formatNumber(height * 0.34)}" r="${formatNumber(Math.max(6, Math.min(width, height) * 0.05))}" fill="#a1a1aa" />
1901
+ <rect width="${formatNumber2(width)}" height="${formatNumber2(height)}" rx="12" fill="#f4f4f5" stroke="#a1a1aa" stroke-width="1.5" stroke-dasharray="6 6" />
1902
+ <path d="M${formatNumber2(width * 0.18)} ${formatNumber2(height * 0.7)} L${formatNumber2(width * 0.38)} ${formatNumber2(height * 0.48)} L${formatNumber2(width * 0.56)} ${formatNumber2(height * 0.62)} L${formatNumber2(width * 0.76)} ${formatNumber2(height * 0.32)} L${formatNumber2(width * 0.84)} ${formatNumber2(height * 0.4)} L${formatNumber2(width * 0.84)} ${formatNumber2(height * 0.82)} L${formatNumber2(width * 0.18)} ${formatNumber2(height * 0.82)} Z" fill="#d4d4d8" stroke="#a1a1aa" stroke-width="1" />
1903
+ <circle cx="${formatNumber2(width * 0.34)}" cy="${formatNumber2(height * 0.34)}" r="${formatNumber2(Math.max(6, Math.min(width, height) * 0.05))}" fill="#a1a1aa" />
1797
1904
  ${buildForeignObjectTextSvg({ x: 10, y: height * 0.04, width: width - 20, height: height * 0.28, text: kind, fontSize: 15, color: "#18181b", fontWeight: 700, align: "center", verticalAlign: "middle" })}
1798
1905
  ${buildForeignObjectTextSvg({ x: 14, y: height * 0.78, width: width - 28, height: height * 0.16, text: detail, fontSize: 12, color: "#52525b", align: "center", verticalAlign: "middle" })}
1799
1906
  `;
@@ -1847,10 +1954,10 @@ function renderVideoShape(snapshot, shape) {
1847
1954
  const label = getString(asRecord(asset?.props)?.name) ?? "Video";
1848
1955
  const subtitle = src ?? getString(props.assetId) ?? "Playback not supported in this importer";
1849
1956
  const inner = `
1850
- <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="14" fill="#18181b" stroke="#3f3f46" stroke-width="1.5" />
1851
- <rect x="${formatNumber(width * 0.04)}" y="${formatNumber(height * 0.08)}" width="${formatNumber(width * 0.92)}" height="${formatNumber(height * 0.66)}" rx="10" fill="#27272a" />
1852
- <circle cx="${formatNumber(width * 0.5)}" cy="${formatNumber(height * 0.41)}" r="${formatNumber(Math.min(width, height) * 0.12)}" fill="#fafafa" fill-opacity="0.92" />
1853
- <polygon points="${formatNumber(width * 0.48)},${formatNumber(height * 0.35)} ${formatNumber(width * 0.48)},${formatNumber(height * 0.47)} ${formatNumber(width * 0.57)},${formatNumber(height * 0.41)}" fill="#18181b" />
1957
+ <rect width="${formatNumber2(width)}" height="${formatNumber2(height)}" rx="14" fill="#18181b" stroke="#3f3f46" stroke-width="1.5" />
1958
+ <rect x="${formatNumber2(width * 0.04)}" y="${formatNumber2(height * 0.08)}" width="${formatNumber2(width * 0.92)}" height="${formatNumber2(height * 0.66)}" rx="10" fill="#27272a" />
1959
+ <circle cx="${formatNumber2(width * 0.5)}" cy="${formatNumber2(height * 0.41)}" r="${formatNumber2(Math.min(width, height) * 0.12)}" fill="#fafafa" fill-opacity="0.92" />
1960
+ <polygon points="${formatNumber2(width * 0.48)},${formatNumber2(height * 0.35)} ${formatNumber2(width * 0.48)},${formatNumber2(height * 0.47)} ${formatNumber2(width * 0.57)},${formatNumber2(height * 0.41)}" fill="#18181b" />
1854
1961
  ${buildForeignObjectTextSvg({ x: 16, y: height * 0.76, width: width - 32, height: height * 0.12, text: label, fontSize: 15, color: "#fafafa", fontWeight: 700 })}
1855
1962
  ${buildForeignObjectTextSvg({ x: 16, y: height * 0.88, width: width - 32, height: height * 0.08, text: subtitle, fontSize: 11, color: "#d4d4d8" })}
1856
1963
  `;
@@ -1876,8 +1983,8 @@ function renderNoteShape(snapshot, shape) {
1876
1983
  const fold = Math.min(width, height) * 0.14;
1877
1984
  const fontSize = sizeToFontPx(getString(props.size)) + numberOr(props.fontSizeAdjustment, 0);
1878
1985
  const inner = `
1879
- <path d="M0 0 H${formatNumber(width - fold)} L${formatNumber(width)} ${formatNumber(fold)} V${formatNumber(height)} H0 Z" fill="${noteColor}" fill-opacity="0.22" stroke="${noteColor}" stroke-width="1.5" />
1880
- <path d="M${formatNumber(width - fold)} 0 V${formatNumber(fold)} H${formatNumber(width)}" fill="${noteColor}" fill-opacity="0.3" stroke="${noteColor}" stroke-width="1.5" />
1986
+ <path d="M0 0 H${formatNumber2(width - fold)} L${formatNumber2(width)} ${formatNumber2(fold)} V${formatNumber2(height)} H0 Z" fill="${noteColor}" fill-opacity="0.22" stroke="${noteColor}" stroke-width="1.5" />
1987
+ <path d="M${formatNumber2(width - fold)} 0 V${formatNumber2(fold)} H${formatNumber2(width)}" fill="${noteColor}" fill-opacity="0.3" stroke="${noteColor}" stroke-width="1.5" />
1881
1988
  ${buildForeignObjectTextSvg({ x: 12, y: 12, width: width - 24, height: height - 24, text, fontSize, color: labelColor, padding: 4, align: getString(props.align), verticalAlign: getString(props.verticalAlign) })}
1882
1989
  `;
1883
1990
  return createCustomImportedItem(
@@ -1893,31 +2000,35 @@ function renderNoteShape(snapshot, shape) {
1893
2000
  }
1894
2001
  function renderBookmarkShape(snapshot, shape) {
1895
2002
  const props = asRecord(shape.props) ?? {};
2003
+ const asset = resolveAsset(snapshot, shape);
2004
+ const assetProps = asRecord(asset?.props) ?? {};
1896
2005
  const localBounds = resolveShapeLocalBounds(snapshot, shape.id);
1897
- const width = localBounds.width;
1898
- const height = localBounds.height;
1899
- const title = getString(props.title) ?? getString(props.label) ?? "Bookmark";
1900
- const url = getString(props.url) ?? "URL unavailable";
1901
- const description = getString(props.description) ?? getString(props.hostname) ?? "Imported from tldraw";
1902
- const stroke = shapeStrokeColor(shape);
1903
- const inner = `
1904
- <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="16" fill="#ffffff" stroke="${stroke}" stroke-width="1.5" />
1905
- <rect x="16" y="16" width="${formatNumber(Math.max(56, width * 0.22))}" height="${formatNumber(Math.max(56, height * 0.32))}" rx="12" fill="${stroke}" fill-opacity="0.12" />
1906
- <path d="M${formatNumber(width * 0.11)} ${formatNumber(height * 0.25)} H${formatNumber(width * 0.18)}" stroke="${stroke}" stroke-width="3" stroke-linecap="round" />
1907
- ${buildForeignObjectTextSvg({ x: width * 0.28, y: 16, width: width * 0.64, height: height * 0.22, text: title, fontSize: 17, color: "#111827", fontWeight: 700 })}
1908
- ${buildForeignObjectTextSvg({ x: width * 0.28, y: height * 0.24, width: width * 0.64, height: height * 0.14, text: url, fontSize: 12, color: stroke })}
1909
- ${buildForeignObjectTextSvg({ x: 16, y: height * 0.46, width: width - 32, height: height * 0.42, text: description, fontSize: 13, color: "#4b5563" })}
1910
- `;
1911
- return createCustomImportedItem(
2006
+ const href = getString(props.url) ?? getString(assetProps.src) ?? getString(props.src) ?? "";
2007
+ const title = getString(assetProps.title) ?? getString(props.title) ?? getString(props.label) ?? void 0;
2008
+ const description = getString(assetProps.description) ?? getString(props.description) ?? void 0;
2009
+ const image = getString(assetProps.image) ?? getString(props.image) ?? void 0;
2010
+ const favicon = getString(assetProps.favicon) ?? getString(props.favicon) ?? void 0;
2011
+ const link = {
2012
+ href: href || "URL unavailable",
2013
+ ...title ? { title } : {},
2014
+ ...description ? { description } : {},
2015
+ ...image ? { image } : {},
2016
+ ...favicon ? { favicon } : {}
2017
+ };
2018
+ const inner = buildLinkCardSvg(localBounds.width, localBounds.height, link);
2019
+ const item = createCustomImportedItem(
1912
2020
  snapshot,
1913
2021
  shape,
1914
2022
  localBounds,
1915
- wrapOpacity(inner, shapeOpacity(shape)),
1916
- {
1917
- stroke,
1918
- strokeWidth: 1.5
1919
- }
2023
+ wrapOpacity(inner, shapeOpacity(shape))
1920
2024
  );
2025
+ return {
2026
+ ...item,
2027
+ pluginData: {
2028
+ ...item.pluginData ?? {},
2029
+ [LINK_PLUGIN_KEY]: link
2030
+ }
2031
+ };
1921
2032
  }
1922
2033
  function renderEmbedShape(snapshot, shape) {
1923
2034
  const props = asRecord(shape.props) ?? {};
@@ -1928,11 +2039,11 @@ function renderEmbedShape(snapshot, shape) {
1928
2039
  const title = getString(props.title) ?? getString(props.embedTitle) ?? "Embed";
1929
2040
  const url = getString(props.url) ?? getString(props.src) ?? "Embedded content";
1930
2041
  const inner = `
1931
- <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="16" fill="#0f172a" stroke="${stroke}" stroke-width="1.5" />
1932
- <rect x="16" y="16" width="${formatNumber(width - 32)}" height="${formatNumber(height * 0.62)}" rx="12" fill="#111827" stroke="#334155" stroke-width="1" />
1933
- <circle cx="${formatNumber(width * 0.14)}" cy="${formatNumber(height * 0.14)}" r="5" fill="#ef4444" />
1934
- <circle cx="${formatNumber(width * 0.18)}" cy="${formatNumber(height * 0.14)}" r="5" fill="#f59e0b" />
1935
- <circle cx="${formatNumber(width * 0.22)}" cy="${formatNumber(height * 0.14)}" r="5" fill="#22c55e" />
2042
+ <rect width="${formatNumber2(width)}" height="${formatNumber2(height)}" rx="16" fill="#0f172a" stroke="${stroke}" stroke-width="1.5" />
2043
+ <rect x="16" y="16" width="${formatNumber2(width - 32)}" height="${formatNumber2(height * 0.62)}" rx="12" fill="#111827" stroke="#334155" stroke-width="1" />
2044
+ <circle cx="${formatNumber2(width * 0.14)}" cy="${formatNumber2(height * 0.14)}" r="5" fill="#ef4444" />
2045
+ <circle cx="${formatNumber2(width * 0.18)}" cy="${formatNumber2(height * 0.14)}" r="5" fill="#f59e0b" />
2046
+ <circle cx="${formatNumber2(width * 0.22)}" cy="${formatNumber2(height * 0.14)}" r="5" fill="#22c55e" />
1936
2047
  ${buildForeignObjectTextSvg({ x: 18, y: height * 0.72, width: width - 36, height: height * 0.11, text: title, fontSize: 16, color: "#f8fafc", fontWeight: 700 })}
1937
2048
  ${buildForeignObjectTextSvg({ x: 18, y: height * 0.84, width: width - 36, height: height * 0.08, text: url, fontSize: 11, color: "#cbd5e1" })}
1938
2049
  `;
@@ -1955,8 +2066,8 @@ function renderFrameShape(snapshot, shape) {
1955
2066
  const stroke = shapeStrokeColor(shape);
1956
2067
  const title = (getString(props.name) ?? getString(props.title) ?? extractPlainText(props)) || "Frame";
1957
2068
  const inner = `
1958
- <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="14" fill="none" stroke="${stroke}" stroke-width="2" stroke-dasharray="10 6" />
1959
- <rect x="12" y="10" width="${formatNumber(Math.min(width - 24, Math.max(88, width * 0.34)))}" height="28" rx="8" fill="#ffffff" stroke="${stroke}" stroke-width="1.5" />
2069
+ <rect width="${formatNumber2(width)}" height="${formatNumber2(height)}" rx="14" fill="none" stroke="${stroke}" stroke-width="2" stroke-dasharray="10 6" />
2070
+ <rect x="12" y="10" width="${formatNumber2(Math.min(width - 24, Math.max(88, width * 0.34)))}" height="28" rx="8" fill="#ffffff" stroke="${stroke}" stroke-width="1.5" />
1960
2071
  ${buildForeignObjectTextSvg({ x: 16, y: 12, width: Math.min(width - 32, Math.max(80, width * 0.3)), height: 24, text: title, fontSize: 13, color: "#111827", fontWeight: 700, verticalAlign: "middle" })}
1961
2072
  `;
1962
2073
  return createCustomImportedItem(
@@ -1979,8 +2090,8 @@ function renderAnnotationShape(snapshot, shape) {
1979
2090
  const fill = shape.type === "annotationBubble" ? "#3b82f6" : resolveColor(getString(props.color) ?? "#ef4444");
1980
2091
  const text = shape.type === "label" ? extractPlainText(props) : String(getNumber(props.number) ?? "");
1981
2092
  const inner = `
1982
- <circle cx="${formatNumber(width / 2)}" cy="${formatNumber(height / 2)}" r="${formatNumber(radius)}" fill="${fill}" stroke="#ffffff" stroke-width="2" />
1983
- <text x="${formatNumber(width / 2)}" y="${formatNumber(height / 2 + 4)}" fill="#ffffff" font-size="10" font-weight="700" text-anchor="middle" dominant-baseline="central">${escapeXml(text)}</text>
2093
+ <circle cx="${formatNumber2(width / 2)}" cy="${formatNumber2(height / 2)}" r="${formatNumber2(radius)}" fill="${fill}" stroke="#ffffff" stroke-width="2" />
2094
+ <text x="${formatNumber2(width / 2)}" y="${formatNumber2(height / 2 + 4)}" fill="#ffffff" font-size="10" font-weight="700" text-anchor="middle" dominant-baseline="central">${escapeXml(text)}</text>
1984
2095
  `;
1985
2096
  return createCustomImportedItem(
1986
2097
  snapshot,
@@ -2000,7 +2111,7 @@ function renderUnsupportedShape(snapshot, shape, reason) {
2000
2111
  const width = localBounds.width;
2001
2112
  const height = localBounds.height;
2002
2113
  const inner = `
2003
- <rect width="${formatNumber(width)}" height="${formatNumber(height)}" rx="12" fill="#fafafa" stroke="#a1a1aa" stroke-width="1.5" stroke-dasharray="6 6" />
2114
+ <rect width="${formatNumber2(width)}" height="${formatNumber2(height)}" rx="12" fill="#fafafa" stroke="#a1a1aa" stroke-width="1.5" stroke-dasharray="6 6" />
2004
2115
  ${buildForeignObjectTextSvg({ x: 12, y: 14, width: width - 24, height: 28, text: shape.type, fontSize: 14, color: "#111827", fontWeight: 700, verticalAlign: "middle" })}
2005
2116
  ${buildForeignObjectTextSvg({ x: 12, y: 50, width: width - 24, height: Math.max(20, height - 62), text: reason, fontSize: 12, color: "#52525b" })}
2006
2117
  `;