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
@@ -1,5 +1,5 @@
1
- import { V as VectorSceneItem } from './types-Bnq2HtHQ.cjs';
2
- import { x as VectorViewportAssetStore } from './shape-builders-CsbSRZnQ.cjs';
1
+ import { V as VectorSceneItem } from './types-BCCvY6ie.cjs';
2
+ import { E as VectorViewportAssetStore } from './shape-builders-BAWu-PxX.cjs';
3
3
 
4
4
  declare class IndexedDbImageStore {
5
5
  private dbPromise;
@@ -1,5 +1,5 @@
1
- import { V as VectorSceneItem } from './types-Bnq2HtHQ.js';
2
- import { x as VectorViewportAssetStore } from './shape-builders-CsSXKCcs.js';
1
+ import { V as VectorSceneItem } from './types-BCCvY6ie.js';
2
+ import { E as VectorViewportAssetStore } from './shape-builders-ClKv9tz9.js';
3
3
 
4
4
  declare class IndexedDbImageStore {
5
5
  private dbPromise;
@@ -1,4 +1,4 @@
1
- import { R as Rect } from './types-Bnq2HtHQ.cjs';
1
+ import { R as Rect } from './types-BCCvY6ie.cjs';
2
2
 
3
3
  type Camera2DOptions = {
4
4
  /** Minimum zoom scale (world units per CSS pixel). */
@@ -1,4 +1,4 @@
1
- import { R as Rect } from './types-Bnq2HtHQ.js';
1
+ import { R as Rect } from './types-BCCvY6ie.js';
2
2
 
3
3
  type Camera2DOptions = {
4
4
  /** Minimum zoom scale (world units per CSS pixel). */
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { C as CanvasPlugin } from './types-DWGk2_GZ.cjs';
2
+ import { C as CanvasPlugin } from './types-BC9Xgfu6.cjs';
3
3
  import 'react';
4
- import './types-Bnq2HtHQ.cjs';
5
- import './camera-Di5R_Rwl.cjs';
6
- import './shape-builders-CsbSRZnQ.cjs';
4
+ import './types-BCCvY6ie.cjs';
5
+ import './camera-CVVG7z56.cjs';
6
+ import './shape-builders-BAWu-PxX.cjs';
7
7
 
8
8
  type ChatbotPluginPanelProps = {
9
9
  /**
package/dist/chatbot.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { C as CanvasPlugin } from './types-B6PAYKzx.js';
2
+ import { C as CanvasPlugin } from './types-DlSVGX0w.js';
3
3
  import 'react';
4
- import './types-Bnq2HtHQ.js';
5
- import './camera-AoTwBSoE.js';
6
- import './shape-builders-CsSXKCcs.js';
4
+ import './types-BCCvY6ie.js';
5
+ import './camera-CoRYN_IV.js';
6
+ import './shape-builders-ClKv9tz9.js';
7
7
 
8
8
  type ChatbotPluginPanelProps = {
9
9
  /**
package/dist/index.cjs CHANGED
@@ -998,11 +998,14 @@ function escapeHtmlText(s) {
998
998
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
999
999
  }
1000
1000
  var DEFAULT_TEXT_FONT_SIZE = 18;
1001
+ var TEXT_FONT_FAMILY = "Inter, -apple-system, BlinkMacSystemFont, ui-sans-serif, system-ui, sans-serif";
1001
1002
  var LINE_HEIGHT_RATIO = 22 / 18;
1002
- var FIRST_LINE_BASELINE_RATIO = 24 / 18;
1003
+ var FIRST_LINE_BASELINE_RATIO = 20 / 18;
1004
+ var EDIT_TOP_PAD_RATIO = 4 / 18;
1005
+ var BOTTOM_PAD_RATIO = 4 / 18;
1003
1006
  var PLACEHOLDER = "Tap to type";
1004
1007
  var MIN_TEXT_BOX_W = 40;
1005
- var MIN_TEXT_BOX_H = 36;
1008
+ var MIN_TEXT_BOX_H = 26;
1006
1009
  var TEXT_PAD_X = 4;
1007
1010
  var MAX_TEXT_MEASURE_CACHE_ENTRIES = 2e3;
1008
1011
  var sharedMeasureContext;
@@ -1048,7 +1051,7 @@ function measureTextBoundsLocal(content, fontSize = DEFAULT_TEXT_FONT_SIZE) {
1048
1051
  let maxInnerW = 0;
1049
1052
  const ctx = getSharedMeasureContext();
1050
1053
  if (ctx) {
1051
- ctx.font = `${fontSize}px system-ui, sans-serif`;
1054
+ ctx.font = `${fontSize}px ${TEXT_FONT_FAMILY}`;
1052
1055
  for (const line of lines) {
1053
1056
  const toMeasure = trimmed.length === 0 ? PLACEHOLDER : line.length === 0 ? " " : line;
1054
1057
  maxInnerW = Math.max(maxInnerW, ctx.measureText(toMeasure).width);
@@ -1064,22 +1067,22 @@ function measureTextBoundsLocal(content, fontSize = DEFAULT_TEXT_FONT_SIZE) {
1064
1067
  const width = Math.max(minW, TEXT_PAD_X * 2 + maxInnerW);
1065
1068
  const height = Math.max(
1066
1069
  MIN_TEXT_BOX_H,
1067
- baselineY + (lines.length - 1) * lh + Math.max(8, fontSize * 0.35)
1070
+ baselineY + (lines.length - 1) * lh + Math.max(4, fontSize * BOTTOM_PAD_RATIO)
1068
1071
  );
1069
1072
  const measured = { width, height };
1070
1073
  cacheMeasuredBounds(cacheKey, measured);
1071
1074
  return measured;
1072
1075
  }
1073
- function buildTextSvg(content, _width, _height, fillColor = "#2563eb", fontSize = DEFAULT_TEXT_FONT_SIZE) {
1076
+ function buildTextSvg(content, _width, _height, fillColor = "#1d1d1d", fontSize = DEFAULT_TEXT_FONT_SIZE) {
1074
1077
  const lh = lineHeightFor(fontSize);
1075
1078
  const y0 = firstLineBaselineY(fontSize);
1076
1079
  const trimmed = content.trim();
1077
1080
  if (trimmed.length === 0) {
1078
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="#94a3b8" font-style="italic">${escapeSvgTextContent(PLACEHOLDER)}</text>`;
1081
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="#94a3b8" font-style="italic">${escapeSvgTextContent(PLACEHOLDER)}</text>`;
1079
1082
  }
1080
1083
  const lines = content.split("\n");
1081
1084
  if (lines.length === 1) {
1082
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="${fillColor}">${escapeSvgTextContent(lines[0] ?? "")}</text>`;
1085
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="${fillColor}">${escapeSvgTextContent(lines[0] ?? "")}</text>`;
1083
1086
  }
1084
1087
  const parts = [];
1085
1088
  for (let i = 0; i < lines.length; i++) {
@@ -1090,23 +1093,24 @@ function buildTextSvg(content, _width, _height, fillColor = "#2563eb", fontSize
1090
1093
  parts.push(`<tspan x="4" dy="${lh}">${escapeSvgTextContent(line)}</tspan>`);
1091
1094
  }
1092
1095
  }
1093
- return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="system-ui,sans-serif" fill="${fillColor}">${parts.join("")}</text>`;
1096
+ return `<text x="4" y="${y0}" font-size="${fontSize}" font-family="${TEXT_FONT_FAMILY}" fill="${fillColor}">${parts.join("")}</text>`;
1094
1097
  }
1095
- function buildTextFixedBoundsSvg(content, width, height, fillColor = "#2563eb", fontSize = DEFAULT_TEXT_FONT_SIZE) {
1098
+ function buildTextFixedBoundsSvg(content, width, height, fillColor = "#1d1d1d", fontSize = DEFAULT_TEXT_FONT_SIZE) {
1096
1099
  const w = Math.max(1, width);
1097
1100
  const h = Math.max(1, height);
1098
1101
  const lh = lineHeightFor(fontSize);
1099
1102
  const trimmed = content.trim();
1103
+ const padTop = EDIT_TOP_PAD_RATIO * fontSize;
1100
1104
  if (trimmed.length === 0) {
1101
- 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>`;
1105
+ 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>`;
1102
1106
  }
1103
1107
  const body = escapeHtmlText(content);
1104
- 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>`;
1108
+ 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>`;
1105
1109
  }
1106
1110
 
1107
1111
  // src/scene/shape-builders.ts
1108
1112
  var DEFAULT_STROKE_STYLE = {
1109
- stroke: "#2563eb",
1113
+ stroke: "#1d1d1d",
1110
1114
  strokeWidth: 2
1111
1115
  };
1112
1116
  var TOOL_FREEHAND_DEFAULTS = {
@@ -1163,7 +1167,8 @@ function resolveStrokeStyle(item) {
1163
1167
  return {
1164
1168
  stroke: item.stroke ?? DEFAULT_STROKE_STYLE.stroke,
1165
1169
  strokeWidth: item.strokeWidth ?? DEFAULT_STROKE_STYLE.strokeWidth,
1166
- strokeOpacity: item.strokeOpacity
1170
+ strokeOpacity: item.strokeOpacity,
1171
+ strokeDash: item.strokeDash
1167
1172
  };
1168
1173
  }
1169
1174
  function strokeOpacityAttr(style) {
@@ -1492,6 +1497,30 @@ function buildDrawDotSvg(r, style = DEFAULT_STROKE_STYLE) {
1492
1497
  const op = style.strokeOpacity != null ? ` fill-opacity="${style.strokeOpacity}"` : "";
1493
1498
  return `<circle cx="${r}" cy="${r}" r="${r}" fill="${style.stroke}" shape-rendering="geometricPrecision"${op} />`;
1494
1499
  }
1500
+ function dashArrayForDrawStroke(strokeWidth) {
1501
+ const dash = Math.max(strokeWidth * 1.8, 4);
1502
+ const gap = Math.max(strokeWidth * 1.4, 3);
1503
+ return `${dash} ${gap}`;
1504
+ }
1505
+ function buildSmoothedCenterlinePath(points) {
1506
+ if (points.length < 2) return null;
1507
+ const first = points[0];
1508
+ if (!first) return null;
1509
+ let d = `M ${first.x} ${first.y}`;
1510
+ for (let i = 1; i < points.length - 1; i++) {
1511
+ const a = points[i];
1512
+ const b = points[i + 1];
1513
+ if (!a || !b) continue;
1514
+ const midX = (a.x + b.x) / 2;
1515
+ const midY = (a.y + b.y) / 2;
1516
+ d += ` Q ${a.x} ${a.y} ${midX} ${midY}`;
1517
+ }
1518
+ const last = points[points.length - 1];
1519
+ if (last) {
1520
+ d += ` L ${last.x} ${last.y}`;
1521
+ }
1522
+ return d;
1523
+ }
1495
1524
  function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeComplete = true) {
1496
1525
  if (pathPointsLocal.length === 0) return null;
1497
1526
  if (pathPointsLocal.length === 1) {
@@ -1506,6 +1535,18 @@ function computeFreehandSvgPayload(pathPointsLocal, style, toolKind, strokeCompl
1506
1535
  fillOpacity: style.strokeOpacity
1507
1536
  };
1508
1537
  }
1538
+ if (style.strokeDash === "dashed" && (toolKind === "draw" || toolKind === "pencil")) {
1539
+ const d2 = buildSmoothedCenterlinePath(pathPointsLocal);
1540
+ if (!d2) return null;
1541
+ return {
1542
+ kind: "strokePath",
1543
+ d: d2,
1544
+ stroke: style.stroke,
1545
+ strokeWidth: style.strokeWidth,
1546
+ strokeOpacity: style.strokeOpacity,
1547
+ strokeDasharray: dashArrayForDrawStroke(style.strokeWidth)
1548
+ };
1549
+ }
1509
1550
  const hasPressure = pathPointsLocal.some(
1510
1551
  (p) => p.pressure != null && Number.isFinite(p.pressure)
1511
1552
  );
@@ -1548,7 +1589,8 @@ function freehandPayloadToSvgString(payload) {
1548
1589
  strokeWidth: payload.strokeWidth,
1549
1590
  strokeOpacity: payload.strokeOpacity
1550
1591
  });
1551
- return `<path d="${payload.d}" fill="none" stroke="${payload.stroke}" stroke-width="${payload.strokeWidth}" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision"${op} />`;
1592
+ const dash = payload.strokeDasharray ? ` stroke-dasharray="${payload.strokeDasharray}"` : "";
1593
+ 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} />`;
1552
1594
  }
1553
1595
  function buildFreehandPathSvg(pathPointsLocal, style, toolKind, strokeComplete = true) {
1554
1596
  const payload = computeFreehandSvgPayload(
@@ -1760,7 +1802,7 @@ function createDrawDotItem(id, worldX, worldY, radius, style) {
1760
1802
  childrenSvg: ""
1761
1803
  });
1762
1804
  }
1763
- function createTextItem(id, bounds, text = "", style) {
1805
+ function createTextItem(id, bounds, text = "", style, textFontSize) {
1764
1806
  const r = normalizeRect(bounds);
1765
1807
  const s = { ...DEFAULT_STROKE_STYLE, ...style };
1766
1808
  return rebuildItemSvg({
@@ -1773,6 +1815,7 @@ function createTextItem(id, bounds, text = "", style) {
1773
1815
  stroke: s.stroke,
1774
1816
  strokeWidth: s.strokeWidth,
1775
1817
  ...s.strokeOpacity != null ? { strokeOpacity: s.strokeOpacity } : {},
1818
+ ...textFontSize != null ? { textFontSize } : {},
1776
1819
  childrenSvg: ""
1777
1820
  });
1778
1821
  }
@@ -1809,6 +1852,7 @@ function createFreehandStrokeItem(id, pointsWorld, toolKind, style) {
1809
1852
  stroke: merged.stroke,
1810
1853
  strokeWidth: merged.strokeWidth,
1811
1854
  ...merged.strokeOpacity != null ? { strokeOpacity: merged.strokeOpacity } : {},
1855
+ ...merged.strokeDash != null ? { strokeDash: merged.strokeDash } : {},
1812
1856
  pathPointsLocal,
1813
1857
  childrenSvg: ""
1814
1858
  });
@@ -2761,6 +2805,105 @@ function cloneVectorSceneItemsWithNewIds(items) {
2761
2805
  return items.map(cloneVectorSceneItemWithNewId);
2762
2806
  }
2763
2807
 
2808
+ // src/scene/link-item.ts
2809
+ var LINK_PLUGIN_KEY = "canvuLink";
2810
+ var DEFAULT_LINK_CARD_WIDTH = 360;
2811
+ var DEFAULT_LINK_CARD_HEIGHT = 132;
2812
+ var LINK_CARD_BORDER = "#e2e8f0";
2813
+ var LINK_CARD_ACCENT = "#2563eb";
2814
+ var LINK_CARD_TITLE_COLOR = "#0f172a";
2815
+ var LINK_CARD_TEXT_COLOR = "#475569";
2816
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
2817
+ var formatNumber = (value) => {
2818
+ const rounded = Math.round(value * 100) / 100;
2819
+ return Object.is(rounded, -0) ? "0" : String(rounded);
2820
+ };
2821
+ var escapeXmlAttribute = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2822
+ var escapeHtmlText2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2823
+ var getLinkHostname = (href) => {
2824
+ try {
2825
+ return new URL(href).hostname.replace(/^www\./, "");
2826
+ } catch {
2827
+ return href;
2828
+ }
2829
+ };
2830
+ var buildLinkTextBand = (band) => {
2831
+ const lineHeight = band.fontSize * 1.3;
2832
+ const weight = band.fontWeight != null ? `font-weight:${band.fontWeight};` : "";
2833
+ const lineClampStyle = band.clampLines ? `display:-webkit-box;-webkit-line-clamp:${band.clampLines};-webkit-box-orient:vertical;` : "";
2834
+ 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}">${escapeHtmlText2(band.text)}</div></foreignObject>`;
2835
+ };
2836
+ function buildLinkCardSvg(width, height, link) {
2837
+ const cardWidth = Math.max(1, width);
2838
+ const cardHeight = Math.max(1, height);
2839
+ const padding = 14;
2840
+ const badgeSize = clamp(Math.min(72, cardHeight - padding * 2), 28, 96);
2841
+ const textX = padding + badgeSize + 14;
2842
+ const textWidth = Math.max(1, cardWidth - textX - padding);
2843
+ const hostname = getLinkHostname(link.href);
2844
+ const title = link.title?.trim() || hostname || "Link";
2845
+ const description = link.description?.trim() || link.href;
2846
+ const titleY = padding;
2847
+ const titleHeight = clamp(cardHeight * 0.22, 18, 28);
2848
+ const hostY = titleY + titleHeight + 2;
2849
+ const hostHeight = 16;
2850
+ const descY = hostY + hostHeight + 4;
2851
+ const descHeight = Math.max(1, cardHeight - descY - padding);
2852
+ 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>
2853
+ <rect x="${formatNumber(padding)}" y="${formatNumber(padding)}" width="${formatNumber(badgeSize)}" height="${formatNumber(badgeSize)}" rx="12" fill="#f8fafc" stroke="${LINK_CARD_BORDER}" stroke-width="1" />
2854
+ <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" />
2855
+ <g transform="translate(${formatNumber(padding + badgeSize / 2)},${formatNumber(padding + badgeSize / 2)})" stroke="${LINK_CARD_ACCENT}" stroke-width="2.4" stroke-linecap="round" fill="none">
2856
+ <path d="M-9 3 a6 6 0 0 1 0 -8 l4 -4 a6 6 0 0 1 8 8 l-2 2" />
2857
+ <path d="M9 -3 a6 6 0 0 1 0 8 l-4 4 a6 6 0 0 1 -8 -8 l2 -2" />
2858
+ </g>`;
2859
+ const externalIconX = cardWidth - padding - 16;
2860
+ 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">
2861
+ <path d="M6 1 H11 V6" />
2862
+ <path d="M11 1 L4.5 7.5" />
2863
+ <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" />
2864
+ </g>`;
2865
+ return `
2866
+ <rect width="${formatNumber(cardWidth)}" height="${formatNumber(cardHeight)}" rx="16" fill="#ffffff" stroke="${LINK_CARD_BORDER}" stroke-width="1.5" />
2867
+ ${badge}
2868
+ ${externalIcon}
2869
+ ${buildLinkTextBand({ x: textX, y: titleY, width: textWidth - 18, height: titleHeight, text: title, fontSize: 15, color: LINK_CARD_TITLE_COLOR, fontWeight: 700, clampLines: 1 })}
2870
+ ${buildLinkTextBand({ x: textX, y: hostY, width: textWidth, height: hostHeight, text: hostname, fontSize: 12, color: LINK_CARD_ACCENT, clampLines: 1 })}
2871
+ ${buildLinkTextBand({ x: textX, y: descY, width: textWidth, height: descHeight, text: description, fontSize: 12, color: LINK_CARD_TEXT_COLOR, clampLines: 2 })}
2872
+ `;
2873
+ }
2874
+ var isCanvuLinkData = (value) => {
2875
+ if (!value || typeof value !== "object") return false;
2876
+ const candidate = value;
2877
+ return typeof candidate.href === "string" && candidate.href.length > 0;
2878
+ };
2879
+ function getLinkData(item) {
2880
+ const entry = item.pluginData?.[LINK_PLUGIN_KEY];
2881
+ return isCanvuLinkData(entry) ? entry : null;
2882
+ }
2883
+ function isLinkItem(item) {
2884
+ return getLinkData(item) != null;
2885
+ }
2886
+ function createLinkItem(id, bounds, link) {
2887
+ const width = bounds.width || DEFAULT_LINK_CARD_WIDTH;
2888
+ const height = bounds.height || DEFAULT_LINK_CARD_HEIGHT;
2889
+ const item = createCustomShapeItem(
2890
+ id,
2891
+ { ...bounds, width, height },
2892
+ { render: (size) => buildLinkCardSvg(size.width, size.height, link) }
2893
+ );
2894
+ return {
2895
+ ...item,
2896
+ pluginData: {
2897
+ ...item.pluginData ?? {},
2898
+ [LINK_PLUGIN_KEY]: link
2899
+ }
2900
+ };
2901
+ }
2902
+ var DEFAULT_LINK_CARD_SIZE = {
2903
+ width: DEFAULT_LINK_CARD_WIDTH,
2904
+ height: DEFAULT_LINK_CARD_HEIGHT
2905
+ };
2906
+
2764
2907
  // src/scene/managed-images.ts
2765
2908
  var MANAGED_KEY = "managed";
2766
2909
  var STACK_GAP_WORLD = 16;
@@ -2865,8 +3008,10 @@ var VectorScene = class {
2865
3008
 
2866
3009
  exports.ARROW_BIND_SNAP_PX = ARROW_BIND_SNAP_PX;
2867
3010
  exports.Camera2D = Camera2D;
3011
+ exports.DEFAULT_LINK_CARD_SIZE = DEFAULT_LINK_CARD_SIZE;
2868
3012
  exports.DEFAULT_STROKE_STYLE = DEFAULT_STROKE_STYLE;
2869
3013
  exports.DEFAULT_TEXT_FONT_SIZE = DEFAULT_TEXT_FONT_SIZE;
3014
+ exports.LINK_PLUGIN_KEY = LINK_PLUGIN_KEY;
2870
3015
  exports.MANAGED_KEY = MANAGED_KEY;
2871
3016
  exports.MAX_RASTER_EMBED_DIMENSION = MAX_RASTER_EMBED_DIMENSION;
2872
3017
  exports.SvgVectorRenderer = SvgVectorRenderer;
@@ -2884,6 +3029,7 @@ exports.buildDrawDotSvg = buildDrawDotSvg;
2884
3029
  exports.buildEllipseSvg = buildEllipseSvg;
2885
3030
  exports.buildFreehandPathSvg = buildFreehandPathSvg;
2886
3031
  exports.buildLineSvg = buildLineSvg;
3032
+ exports.buildLinkCardSvg = buildLinkCardSvg;
2887
3033
  exports.buildRectSvg = buildRectSvg;
2888
3034
  exports.buildTextSvg = buildTextSvg;
2889
3035
  exports.cloneVectorSceneItemWithNewId = cloneVectorSceneItemWithNewId;
@@ -2899,6 +3045,7 @@ exports.createFreehandStrokeItem = createFreehandStrokeItem;
2899
3045
  exports.createImageFromVectorTrace = createImageFromVectorTrace;
2900
3046
  exports.createImageItem = createImageItem;
2901
3047
  exports.createLineItem = createLineItem;
3048
+ exports.createLinkItem = createLinkItem;
2902
3049
  exports.createRectangleItem = createRectangleItem;
2903
3050
  exports.createShapeId = createShapeId;
2904
3051
  exports.createTextItem = createTextItem;
@@ -2908,9 +3055,11 @@ exports.encodeCanvasToBlob = encodeCanvasToBlob;
2908
3055
  exports.expandCustomShapeTemplate = expandCustomShapeTemplate;
2909
3056
  exports.formatCameraTransform = formatCameraTransform;
2910
3057
  exports.formatItemPlacementTransform = formatItemPlacementTransform;
3058
+ exports.getLinkData = getLinkData;
2911
3059
  exports.hitTestWorldPoint = hitTestWorldPoint;
2912
3060
  exports.hydrateSceneItemsWithAssets = hydrateSceneItemsWithAssets;
2913
3061
  exports.isArrowBindTarget = isArrowBindTarget;
3062
+ exports.isLinkItem = isLinkItem;
2914
3063
  exports.isManagedImage = isManagedImage;
2915
3064
  exports.itemHitTestWorldPoint = itemHitTestWorldPoint;
2916
3065
  exports.lineEndpointsToLocal = lineEndpointsToLocal;