canvu-react 0.4.30 → 0.4.32

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.
@@ -1,5 +1,5 @@
1
1
  import { V as VectorSceneItem } from './types-BCCvY6ie.cjs';
2
- import { a as VectorViewportAssetStore } from './link-item-D870_X6P.cjs';
2
+ import { a as VectorViewportAssetStore } from './link-item-Dncuz2d_.cjs';
3
3
 
4
4
  declare class IndexedDbImageStore {
5
5
  private dbPromise;
@@ -1,5 +1,5 @@
1
1
  import { V as VectorSceneItem } from './types-BCCvY6ie.js';
2
- import { a as VectorViewportAssetStore } from './link-item-Bg5vj0RI.js';
2
+ import { a as VectorViewportAssetStore } from './link-item-voRU0Up9.js';
3
3
 
4
4
  declare class IndexedDbImageStore {
5
5
  private dbPromise;
@@ -1,9 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { C as CanvasPlugin } from './types-Bw1SdC9v.cjs';
2
+ import { C as CanvasPlugin } from './types-BS-YG8Hx.cjs';
3
3
  import 'react';
4
4
  import './types-BCCvY6ie.cjs';
5
5
  import './shape-builders-CKEMjivV.cjs';
6
- import './link-item-D870_X6P.cjs';
6
+ import './link-item-Dncuz2d_.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-DeXFfs7Y.js';
2
+ import { C as CanvasPlugin } from './types-UZYYwK-v.js';
3
3
  import 'react';
4
4
  import './types-BCCvY6ie.js';
5
5
  import './shape-builders-Cyh8zvDG.js';
6
- import './link-item-Bg5vj0RI.js';
6
+ import './link-item-voRU0Up9.js';
7
7
 
8
8
  type ChatbotPluginPanelProps = {
9
9
  /**
package/dist/index.cjs CHANGED
@@ -990,11 +990,187 @@ function createCustomShapeItem(id, bounds, content) {
990
990
  };
991
991
  }
992
992
 
993
+ // src/scene/link-item.ts
994
+ var LINK_PLUGIN_KEY = "canvuLink";
995
+ var DEFAULT_LINK_CARD_WIDTH = 320;
996
+ var DEFAULT_LINK_CARD_HEIGHT = 70;
997
+ var LINK_CARD_MIN_SCALE = 0.6;
998
+ var LINK_CARD_MAX_SCALE = 2.5;
999
+ var LINK_CARD_BORDER = "oklch(0.918 0.008 255)";
1000
+ var LINK_CARD_BORDER_STRONG = "oklch(0.86 0.012 255)";
1001
+ var LINK_CARD_ACCENT = "oklch(0.55 0.19 264)";
1002
+ var LINK_CARD_ACCENT_DEEP = "oklch(0.46 0.18 264)";
1003
+ var LINK_CARD_TITLE_COLOR = "oklch(0.26 0.022 265)";
1004
+ var LINK_CARD_TEXT_COLOR = "oklch(0.55 0.022 265)";
1005
+ var clamp = (value, min, max) => Math.min(max, Math.max(min, value));
1006
+ var formatNumber = (value) => {
1007
+ const rounded = Math.round(value * 100) / 100;
1008
+ return Object.is(rounded, -0) ? "0" : String(rounded);
1009
+ };
1010
+ var escapeXmlAttribute = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
1011
+ var escapeHtmlText = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
1012
+ var getLinkHostname = (href) => {
1013
+ try {
1014
+ return new URL(href).hostname.replace(/^www\./, "");
1015
+ } catch {
1016
+ return href;
1017
+ }
1018
+ };
1019
+ var buildLinkTextBand = (band) => {
1020
+ const lineHeight = band.height;
1021
+ const weight = band.fontWeight != null ? `font-weight:${band.fontWeight};` : "";
1022
+ 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;letter-spacing:-0.01em;color:${band.color};overflow:hidden;white-space:nowrap;text-overflow:ellipsis;${weight}">${escapeHtmlText(band.text)}</div></foreignObject>`;
1023
+ };
1024
+ var getLinkProtocol = (href) => {
1025
+ try {
1026
+ return new URL(href).protocol;
1027
+ } catch {
1028
+ return "";
1029
+ }
1030
+ };
1031
+ var getLinkInitial = (hostname) => {
1032
+ const first = hostname.trim().charAt(0).toUpperCase();
1033
+ return first || "L";
1034
+ };
1035
+ var buildGoogleFaviconUrl = (hostname) => hostname ? `https://www.google.com/s2/favicons?domain=${encodeURIComponent(hostname)}&sz=64` : null;
1036
+ var getStableLinkIdSuffix = (value) => {
1037
+ let hash = 0;
1038
+ for (const char of value) {
1039
+ hash = hash * 31 + char.charCodeAt(0) >>> 0;
1040
+ }
1041
+ return hash.toString(36);
1042
+ };
1043
+ function buildLinkCardSvg(width, _height, link) {
1044
+ const cardWidth = Math.max(1, width);
1045
+ const scale = cardWidth / DEFAULT_LINK_CARD_WIDTH;
1046
+ const contentWidth = DEFAULT_LINK_CARD_WIDTH;
1047
+ const contentHeight = DEFAULT_LINK_CARD_HEIGHT;
1048
+ const padding = 14;
1049
+ const badgeSize = 42;
1050
+ const gap = 13;
1051
+ const buttonSize = 34;
1052
+ const textX = padding + badgeSize + gap;
1053
+ const textWidth = Math.max(1, contentWidth - textX - buttonSize - gap - padding);
1054
+ const hostname = getLinkHostname(link.href);
1055
+ const title = link.title?.trim() || hostname || "Link";
1056
+ const protocol = getLinkProtocol(link.href);
1057
+ const subtitle = hostname || link.href;
1058
+ const favicon = link.favicon?.trim() || buildGoogleFaviconUrl(hostname);
1059
+ const idSuffix = getStableLinkIdSuffix(`${hostname}:${link.href}`);
1060
+ const clipId = `canvu-link-favicon-${idSuffix}`;
1061
+ const gradientId = `canvu-link-favicon-gradient-${idSuffix}`;
1062
+ const buttonX = contentWidth - padding - buttonSize;
1063
+ const buttonY = (contentHeight - buttonSize) / 2;
1064
+ const isSecure = protocol === "https:";
1065
+ const subtitleX = isSecure ? textX + 13 : textX;
1066
+ const subtitleWidth = isSecure ? textWidth - 13 : textWidth;
1067
+ const faviconImage = favicon ? `<image class="canvu-link-favicon-img" href="${escapeXmlAttribute(favicon)}" x="${formatNumber(padding)}" y="${formatNumber(padding)}" width="${formatNumber(badgeSize)}" height="${formatNumber(badgeSize)}" preserveAspectRatio="xMidYMid slice" clip-path="url(#${clipId})" />` : "";
1068
+ return `
1069
+ <style>
1070
+ .canvu-link-card-root .canvu-link-card { transition: transform .18s ease, filter .18s ease, stroke .18s ease; }
1071
+ .canvu-link-card-root .canvu-link-open { opacity: 0; transform: translateX(-4px); transition: opacity .18s ease, transform .18s ease; }
1072
+ .canvu-link-card-root:hover .canvu-link-card { transform: translateY(-2px); filter: drop-shadow(0 4px 14px oklch(0.4 0.05 265 / .08)) drop-shadow(0 1px 3px oklch(0.4 0.05 265 / .06)); stroke: ${LINK_CARD_BORDER_STRONG}; }
1073
+ .canvu-link-card-root:hover .canvu-link-open { opacity: 1; transform: translateX(0); }
1074
+ </style>
1075
+ <g class="canvu-link-card-root" transform="scale(${formatNumber(scale)})">
1076
+ <rect class="canvu-link-card" width="${formatNumber(contentWidth)}" height="${formatNumber(contentHeight)}" rx="16" fill="#ffffff" stroke="${LINK_CARD_BORDER}" stroke-width="1" filter="drop-shadow(0 1px 2px oklch(0.4 0.03 265 / .05)) drop-shadow(0 1px 1px oklch(0.4 0.03 265 / .04))" />
1077
+ <defs>
1078
+ <linearGradient id="${gradientId}" x1="8" y1="48" x2="48" y2="8" gradientUnits="userSpaceOnUse">
1079
+ <stop stop-color="${LINK_CARD_ACCENT}" />
1080
+ <stop offset="1" stop-color="${LINK_CARD_ACCENT_DEEP}" />
1081
+ </linearGradient>
1082
+ <clipPath id="${clipId}">
1083
+ <rect x="${formatNumber(padding)}" y="${formatNumber(padding)}" width="${formatNumber(badgeSize)}" height="${formatNumber(badgeSize)}" rx="11" />
1084
+ </clipPath>
1085
+ </defs>
1086
+ <rect x="${formatNumber(padding)}" y="${formatNumber(padding)}" width="${formatNumber(badgeSize)}" height="${formatNumber(badgeSize)}" rx="11" fill="url(#${gradientId})" />
1087
+ <text x="${formatNumber(padding + badgeSize / 2)}" y="${formatNumber(padding + badgeSize / 2 + 5)}" text-anchor="middle" font-family="system-ui,sans-serif" font-size="17" font-weight="700" fill="#ffffff">${escapeHtmlText(getLinkInitial(hostname))}</text>
1088
+ ${faviconImage}
1089
+ ${buildLinkTextBand({ x: textX, y: 16, width: textWidth, height: 19, text: title, fontSize: 14.5, color: LINK_CARD_TITLE_COLOR, fontWeight: 700 })}
1090
+ ${isSecure ? `<g transform="translate(${formatNumber(textX)},40)" stroke="${LINK_CARD_TEXT_COLOR}" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" fill="none"><rect x="1.5" y="4.5" width="7" height="6" rx="1" /><path d="M3 4.5 V3 a2 2 0 0 1 4 0 v1.5" /></g>` : ""}
1091
+ ${buildLinkTextBand({ x: subtitleX, y: 36, width: subtitleWidth, height: 17, text: subtitle, fontSize: 12.5, color: LINK_CARD_TEXT_COLOR })}
1092
+ <g class="canvu-link-open" transform="translate(${formatNumber(buttonX)},${formatNumber(buttonY)})">
1093
+ <rect width="${formatNumber(buttonSize)}" height="${formatNumber(buttonSize)}" rx="10" fill="#ffffff" stroke="${LINK_CARD_BORDER}" stroke-width="1" />
1094
+ <g transform="translate(10,10)" stroke="${LINK_CARD_TEXT_COLOR}" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" fill="none">
1095
+ <path d="M8 2 H14 V8" />
1096
+ <path d="M14 2 L7 9" />
1097
+ <path d="M12 10 V13 a1 1 0 0 1 -1 1 H3 a1 1 0 0 1 -1 -1 V5 a1 1 0 0 1 1 -1 H6" />
1098
+ </g>
1099
+ </g>
1100
+ </g>
1101
+ `;
1102
+ }
1103
+ var isCanvuLinkData = (value) => {
1104
+ if (!value || typeof value !== "object") return false;
1105
+ const candidate = value;
1106
+ return typeof candidate.href === "string" && candidate.href.length > 0;
1107
+ };
1108
+ function getLinkData(item) {
1109
+ const entry = item.pluginData?.[LINK_PLUGIN_KEY];
1110
+ return isCanvuLinkData(entry) ? entry : null;
1111
+ }
1112
+ function isLinkItem(item) {
1113
+ return getLinkData(item) != null;
1114
+ }
1115
+ function rebuildLinkItemSvg(item) {
1116
+ const link = getLinkData(item);
1117
+ if (!link) return item;
1118
+ const scale = clamp(
1119
+ item.bounds.width / DEFAULT_LINK_CARD_WIDTH,
1120
+ LINK_CARD_MIN_SCALE,
1121
+ LINK_CARD_MAX_SCALE
1122
+ );
1123
+ const width = DEFAULT_LINK_CARD_WIDTH * scale;
1124
+ const height = DEFAULT_LINK_CARD_HEIGHT * scale;
1125
+ const bounds = {
1126
+ ...item.bounds,
1127
+ width,
1128
+ height
1129
+ };
1130
+ const customInnerSvg = buildLinkCardSvg(
1131
+ DEFAULT_LINK_CARD_WIDTH,
1132
+ DEFAULT_LINK_CARD_HEIGHT,
1133
+ link
1134
+ );
1135
+ return {
1136
+ ...item,
1137
+ x: bounds.x,
1138
+ y: bounds.y,
1139
+ bounds,
1140
+ customIntrinsicSize: {
1141
+ width: DEFAULT_LINK_CARD_WIDTH,
1142
+ height: DEFAULT_LINK_CARD_HEIGHT
1143
+ },
1144
+ customInnerSvg,
1145
+ childrenSvg: buildLinkCardSvg(width, height, link)
1146
+ };
1147
+ }
1148
+ function createLinkItem(id, bounds, link) {
1149
+ const width = bounds.width || DEFAULT_LINK_CARD_WIDTH;
1150
+ const height = bounds.height || DEFAULT_LINK_CARD_HEIGHT;
1151
+ const item = createCustomShapeItem(
1152
+ id,
1153
+ { ...bounds, width, height },
1154
+ { render: (size) => buildLinkCardSvg(size.width, size.height, link) }
1155
+ );
1156
+ return rebuildLinkItemSvg({
1157
+ ...item,
1158
+ pluginData: {
1159
+ ...item.pluginData ?? {},
1160
+ [LINK_PLUGIN_KEY]: link
1161
+ }
1162
+ });
1163
+ }
1164
+ var DEFAULT_LINK_CARD_SIZE = {
1165
+ width: DEFAULT_LINK_CARD_WIDTH,
1166
+ height: DEFAULT_LINK_CARD_HEIGHT
1167
+ };
1168
+
993
1169
  // src/scene/text-svg.ts
994
1170
  function escapeSvgTextContent(s) {
995
1171
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
996
1172
  }
997
- function escapeHtmlText(s) {
1173
+ function escapeHtmlText2(s) {
998
1174
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
999
1175
  }
1000
1176
  var DEFAULT_TEXT_FONT_SIZE = 18;
@@ -1102,9 +1278,9 @@ function buildTextFixedBoundsSvg(content, width, height, fillColor = "#1d1d1d",
1102
1278
  const trimmed = content.trim();
1103
1279
  const padTop = EDIT_TOP_PAD_RATIO * fontSize;
1104
1280
  if (trimmed.length === 0) {
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>`;
1281
+ 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>`;
1106
1282
  }
1107
- const body = escapeHtmlText(content);
1283
+ const body = escapeHtmlText2(content);
1108
1284
  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>`;
1109
1285
  }
1110
1286
 
@@ -1702,6 +1878,9 @@ function rebuildItemSvg(item) {
1702
1878
  )
1703
1879
  };
1704
1880
  }
1881
+ if (getLinkData(item)) {
1882
+ return rebuildLinkItemSvg(item);
1883
+ }
1705
1884
  if (k === "custom" && item.customIntrinsicSize && item.customInnerSvg) {
1706
1885
  const b = normalizeRect(item.bounds);
1707
1886
  return {
@@ -2805,105 +2984,6 @@ function cloneVectorSceneItemsWithNewIds(items) {
2805
2984
  return items.map(cloneVectorSceneItemWithNewId);
2806
2985
  }
2807
2986
 
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
-
2907
2987
  // src/scene/managed-images.ts
2908
2988
  var MANAGED_KEY = "managed";
2909
2989
  var STACK_GAP_WORLD = 16;