canvu-react 0.4.31 → 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.
package/dist/react.d.cts CHANGED
@@ -1,10 +1,10 @@
1
- import { I as IndexedDbImageStore } from './asset-hydration-CWhld14A.cjs';
2
- export { H as HydratedSceneItemsWithAssetsResult, h as hydrateSceneItemsWithAssets } from './asset-hydration-CWhld14A.cjs';
1
+ import { I as IndexedDbImageStore } from './asset-hydration-BFGZ3igr.cjs';
2
+ export { H as HydratedSceneItemsWithAssetsResult, h as hydrateSceneItemsWithAssets } from './asset-hydration-BFGZ3igr.cjs';
3
3
  import { V as VectorSceneItem } from './types-BCCvY6ie.cjs';
4
- import { V as VectorViewportAssetKind, a as VectorViewportAssetStore } from './link-item-D870_X6P.cjs';
5
- export { d as VectorViewportAssetHydrationRequest, e as VectorViewportAssetResolveRequest, f as VectorViewportAssetResolveResult, h as VectorViewportAssetUploadRequest, j as VectorViewportAssetUploadResult } from './link-item-D870_X6P.cjs';
6
- import { B as BoardComponentPosition, V as VectorToolDefinition, C as CanvasPlugin, a as VectorSelectionInspector } from './types-Bw1SdC9v.cjs';
7
- export { b as CanvasPluginComponentProps, c as CanvasPluginContribution, d as CanvasPluginItemsChangeMiddlewareContext, e as CanvasPluginRenderContext, f as CanvuChromeActiveToolStyle, g as CanvuChromeContext, h as CanvuChromeContextValue, i as CanvuChromeSelectionStyleChange, j as CanvuPluginContext, k as CanvuPluginContextValue, l as CanvuPluginViewportSnapshot, m as CustomShapePlacementOptions, P as PlacementPreview, n as VectorCanvasSpacePosition, o as VectorSelectionInspectorProps, p as VectorViewport, q as VectorViewportHandle, r as VectorViewportProps, W as WorldPointerDownDetail, s as createCanvuPlugin, t as getBoardPositionStyle, u as useCanvuChromeContext, v as useCanvuDocumentContext, w as useCanvuPluginContext, x as useCanvuPluginContribution, y as useCanvuResolvedTools, z as useCanvuViewportContext } from './types-Bw1SdC9v.cjs';
4
+ import { V as VectorViewportAssetKind, a as VectorViewportAssetStore } from './link-item-Dncuz2d_.cjs';
5
+ export { d as VectorViewportAssetHydrationRequest, e as VectorViewportAssetResolveRequest, f as VectorViewportAssetResolveResult, h as VectorViewportAssetUploadRequest, j as VectorViewportAssetUploadResult } from './link-item-Dncuz2d_.cjs';
6
+ import { B as BoardComponentPosition, V as VectorToolDefinition, C as CanvasPlugin, a as VectorSelectionInspector } from './types-BS-YG8Hx.cjs';
7
+ export { b as CanvasPluginComponentProps, c as CanvasPluginContribution, d as CanvasPluginItemsChangeMiddlewareContext, e as CanvasPluginRenderContext, f as CanvuChromeActiveToolStyle, g as CanvuChromeContext, h as CanvuChromeContextValue, i as CanvuChromeSelectionStyleChange, j as CanvuPluginContext, k as CanvuPluginContextValue, l as CanvuPluginViewportSnapshot, m as CustomShapePlacementOptions, P as PlacementPreview, n as VectorCanvasSpacePosition, o as VectorSelectionInspectorProps, p as VectorViewport, q as VectorViewportHandle, r as VectorViewportProps, W as WorldPointerDownDetail, s as createCanvuPlugin, t as getBoardPositionStyle, u as useCanvuChromeContext, v as useCanvuDocumentContext, w as useCanvuPluginContext, x as useCanvuPluginContribution, y as useCanvuResolvedTools, z as useCanvuViewportContext } from './types-BS-YG8Hx.cjs';
8
8
  import * as react_jsx_runtime from 'react/jsx-runtime';
9
9
  import * as react from 'react';
10
10
  import { CSSProperties, ReactNode, ReactElement, SVGProps } from 'react';
package/dist/react.d.ts CHANGED
@@ -1,10 +1,10 @@
1
- import { I as IndexedDbImageStore } from './asset-hydration-D35mHbUP.js';
2
- export { H as HydratedSceneItemsWithAssetsResult, h as hydrateSceneItemsWithAssets } from './asset-hydration-D35mHbUP.js';
1
+ import { I as IndexedDbImageStore } from './asset-hydration-D6Q3TJL1.js';
2
+ export { H as HydratedSceneItemsWithAssetsResult, h as hydrateSceneItemsWithAssets } from './asset-hydration-D6Q3TJL1.js';
3
3
  import { V as VectorSceneItem } from './types-BCCvY6ie.js';
4
- import { V as VectorViewportAssetKind, a as VectorViewportAssetStore } from './link-item-Bg5vj0RI.js';
5
- export { d as VectorViewportAssetHydrationRequest, e as VectorViewportAssetResolveRequest, f as VectorViewportAssetResolveResult, h as VectorViewportAssetUploadRequest, j as VectorViewportAssetUploadResult } from './link-item-Bg5vj0RI.js';
6
- import { B as BoardComponentPosition, V as VectorToolDefinition, C as CanvasPlugin, a as VectorSelectionInspector } from './types-DeXFfs7Y.js';
7
- export { b as CanvasPluginComponentProps, c as CanvasPluginContribution, d as CanvasPluginItemsChangeMiddlewareContext, e as CanvasPluginRenderContext, f as CanvuChromeActiveToolStyle, g as CanvuChromeContext, h as CanvuChromeContextValue, i as CanvuChromeSelectionStyleChange, j as CanvuPluginContext, k as CanvuPluginContextValue, l as CanvuPluginViewportSnapshot, m as CustomShapePlacementOptions, P as PlacementPreview, n as VectorCanvasSpacePosition, o as VectorSelectionInspectorProps, p as VectorViewport, q as VectorViewportHandle, r as VectorViewportProps, W as WorldPointerDownDetail, s as createCanvuPlugin, t as getBoardPositionStyle, u as useCanvuChromeContext, v as useCanvuDocumentContext, w as useCanvuPluginContext, x as useCanvuPluginContribution, y as useCanvuResolvedTools, z as useCanvuViewportContext } from './types-DeXFfs7Y.js';
4
+ import { V as VectorViewportAssetKind, a as VectorViewportAssetStore } from './link-item-voRU0Up9.js';
5
+ export { d as VectorViewportAssetHydrationRequest, e as VectorViewportAssetResolveRequest, f as VectorViewportAssetResolveResult, h as VectorViewportAssetUploadRequest, j as VectorViewportAssetUploadResult } from './link-item-voRU0Up9.js';
6
+ import { B as BoardComponentPosition, V as VectorToolDefinition, C as CanvasPlugin, a as VectorSelectionInspector } from './types-UZYYwK-v.js';
7
+ export { b as CanvasPluginComponentProps, c as CanvasPluginContribution, d as CanvasPluginItemsChangeMiddlewareContext, e as CanvasPluginRenderContext, f as CanvuChromeActiveToolStyle, g as CanvuChromeContext, h as CanvuChromeContextValue, i as CanvuChromeSelectionStyleChange, j as CanvuPluginContext, k as CanvuPluginContextValue, l as CanvuPluginViewportSnapshot, m as CustomShapePlacementOptions, P as PlacementPreview, n as VectorCanvasSpacePosition, o as VectorSelectionInspectorProps, p as VectorViewport, q as VectorViewportHandle, r as VectorViewportProps, W as WorldPointerDownDetail, s as createCanvuPlugin, t as getBoardPositionStyle, u as useCanvuChromeContext, v as useCanvuDocumentContext, w as useCanvuPluginContext, x as useCanvuPluginContribution, y as useCanvuResolvedTools, z as useCanvuViewportContext } from './types-UZYYwK-v.js';
8
8
  import * as react_jsx_runtime from 'react/jsx-runtime';
9
9
  import * as react from 'react';
10
10
  import { CSSProperties, ReactNode, ReactElement, SVGProps } from 'react';
package/dist/react.js CHANGED
@@ -49,11 +49,170 @@ var init_custom_shape = __esm({
49
49
  }
50
50
  });
51
51
 
52
+ // src/scene/link-item.ts
53
+ function buildLinkCardSvg(width, _height, link) {
54
+ const cardWidth = Math.max(1, width);
55
+ const scale = cardWidth / DEFAULT_LINK_CARD_WIDTH;
56
+ const contentWidth = DEFAULT_LINK_CARD_WIDTH;
57
+ const contentHeight = DEFAULT_LINK_CARD_HEIGHT;
58
+ const padding = 14;
59
+ const badgeSize = 42;
60
+ const gap = 13;
61
+ const buttonSize = 34;
62
+ const textX = padding + badgeSize + gap;
63
+ const textWidth = Math.max(1, contentWidth - textX - buttonSize - gap - padding);
64
+ const hostname = getLinkHostname(link.href);
65
+ const title = link.title?.trim() || hostname || "Link";
66
+ const protocol = getLinkProtocol(link.href);
67
+ const subtitle = hostname || link.href;
68
+ const favicon = link.favicon?.trim() || buildGoogleFaviconUrl(hostname);
69
+ const idSuffix = getStableLinkIdSuffix(`${hostname}:${link.href}`);
70
+ const clipId = `canvu-link-favicon-${idSuffix}`;
71
+ const gradientId = `canvu-link-favicon-gradient-${idSuffix}`;
72
+ const buttonX = contentWidth - padding - buttonSize;
73
+ const buttonY = (contentHeight - buttonSize) / 2;
74
+ const isSecure = protocol === "https:";
75
+ const subtitleX = isSecure ? textX + 13 : textX;
76
+ const subtitleWidth = isSecure ? textWidth - 13 : textWidth;
77
+ 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})" />` : "";
78
+ return `
79
+ <style>
80
+ .canvu-link-card-root .canvu-link-card { transition: transform .18s ease, filter .18s ease, stroke .18s ease; }
81
+ .canvu-link-card-root .canvu-link-open { opacity: 0; transform: translateX(-4px); transition: opacity .18s ease, transform .18s ease; }
82
+ .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}; }
83
+ .canvu-link-card-root:hover .canvu-link-open { opacity: 1; transform: translateX(0); }
84
+ </style>
85
+ <g class="canvu-link-card-root" transform="scale(${formatNumber(scale)})">
86
+ <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))" />
87
+ <defs>
88
+ <linearGradient id="${gradientId}" x1="8" y1="48" x2="48" y2="8" gradientUnits="userSpaceOnUse">
89
+ <stop stop-color="${LINK_CARD_ACCENT}" />
90
+ <stop offset="1" stop-color="${LINK_CARD_ACCENT_DEEP}" />
91
+ </linearGradient>
92
+ <clipPath id="${clipId}">
93
+ <rect x="${formatNumber(padding)}" y="${formatNumber(padding)}" width="${formatNumber(badgeSize)}" height="${formatNumber(badgeSize)}" rx="11" />
94
+ </clipPath>
95
+ </defs>
96
+ <rect x="${formatNumber(padding)}" y="${formatNumber(padding)}" width="${formatNumber(badgeSize)}" height="${formatNumber(badgeSize)}" rx="11" fill="url(#${gradientId})" />
97
+ <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>
98
+ ${faviconImage}
99
+ ${buildLinkTextBand({ x: textX, y: 16, width: textWidth, height: 19, text: title, fontSize: 14.5, color: LINK_CARD_TITLE_COLOR, fontWeight: 700 })}
100
+ ${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>` : ""}
101
+ ${buildLinkTextBand({ x: subtitleX, y: 36, width: subtitleWidth, height: 17, text: subtitle, fontSize: 12.5, color: LINK_CARD_TEXT_COLOR })}
102
+ <g class="canvu-link-open" transform="translate(${formatNumber(buttonX)},${formatNumber(buttonY)})">
103
+ <rect width="${formatNumber(buttonSize)}" height="${formatNumber(buttonSize)}" rx="10" fill="#ffffff" stroke="${LINK_CARD_BORDER}" stroke-width="1" />
104
+ <g transform="translate(10,10)" stroke="${LINK_CARD_TEXT_COLOR}" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" fill="none">
105
+ <path d="M8 2 H14 V8" />
106
+ <path d="M14 2 L7 9" />
107
+ <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" />
108
+ </g>
109
+ </g>
110
+ </g>
111
+ `;
112
+ }
113
+ function getLinkData(item) {
114
+ const entry = item.pluginData?.[LINK_PLUGIN_KEY];
115
+ return isCanvuLinkData(entry) ? entry : null;
116
+ }
117
+ function rebuildLinkItemSvg(item) {
118
+ const link = getLinkData(item);
119
+ if (!link) return item;
120
+ const scale = clamp(
121
+ item.bounds.width / DEFAULT_LINK_CARD_WIDTH,
122
+ LINK_CARD_MIN_SCALE,
123
+ LINK_CARD_MAX_SCALE
124
+ );
125
+ const width = DEFAULT_LINK_CARD_WIDTH * scale;
126
+ const height = DEFAULT_LINK_CARD_HEIGHT * scale;
127
+ const bounds = {
128
+ ...item.bounds,
129
+ width,
130
+ height
131
+ };
132
+ const customInnerSvg = buildLinkCardSvg(
133
+ DEFAULT_LINK_CARD_WIDTH,
134
+ DEFAULT_LINK_CARD_HEIGHT,
135
+ link
136
+ );
137
+ return {
138
+ ...item,
139
+ x: bounds.x,
140
+ y: bounds.y,
141
+ bounds,
142
+ customIntrinsicSize: {
143
+ width: DEFAULT_LINK_CARD_WIDTH,
144
+ height: DEFAULT_LINK_CARD_HEIGHT
145
+ },
146
+ customInnerSvg,
147
+ childrenSvg: buildLinkCardSvg(width, height, link)
148
+ };
149
+ }
150
+ var LINK_PLUGIN_KEY, DEFAULT_LINK_CARD_WIDTH, DEFAULT_LINK_CARD_HEIGHT, LINK_CARD_MIN_SCALE, LINK_CARD_MAX_SCALE, LINK_CARD_ASPECT, LINK_CARD_BORDER, LINK_CARD_BORDER_STRONG, LINK_CARD_ACCENT, LINK_CARD_ACCENT_DEEP, LINK_CARD_TITLE_COLOR, LINK_CARD_TEXT_COLOR, clamp, formatNumber, escapeXmlAttribute, escapeHtmlText, getLinkHostname, buildLinkTextBand, getLinkProtocol, getLinkInitial, buildGoogleFaviconUrl, getStableLinkIdSuffix, isCanvuLinkData;
151
+ var init_link_item = __esm({
152
+ "src/scene/link-item.ts"() {
153
+ LINK_PLUGIN_KEY = "canvuLink";
154
+ DEFAULT_LINK_CARD_WIDTH = 320;
155
+ DEFAULT_LINK_CARD_HEIGHT = 70;
156
+ LINK_CARD_MIN_SCALE = 0.6;
157
+ LINK_CARD_MAX_SCALE = 2.5;
158
+ LINK_CARD_ASPECT = DEFAULT_LINK_CARD_WIDTH / DEFAULT_LINK_CARD_HEIGHT;
159
+ LINK_CARD_BORDER = "oklch(0.918 0.008 255)";
160
+ LINK_CARD_BORDER_STRONG = "oklch(0.86 0.012 255)";
161
+ LINK_CARD_ACCENT = "oklch(0.55 0.19 264)";
162
+ LINK_CARD_ACCENT_DEEP = "oklch(0.46 0.18 264)";
163
+ LINK_CARD_TITLE_COLOR = "oklch(0.26 0.022 265)";
164
+ LINK_CARD_TEXT_COLOR = "oklch(0.55 0.022 265)";
165
+ clamp = (value, min, max) => Math.min(max, Math.max(min, value));
166
+ formatNumber = (value) => {
167
+ const rounded = Math.round(value * 100) / 100;
168
+ return Object.is(rounded, -0) ? "0" : String(rounded);
169
+ };
170
+ escapeXmlAttribute = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
171
+ escapeHtmlText = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
172
+ getLinkHostname = (href) => {
173
+ try {
174
+ return new URL(href).hostname.replace(/^www\./, "");
175
+ } catch {
176
+ return href;
177
+ }
178
+ };
179
+ buildLinkTextBand = (band) => {
180
+ const lineHeight = band.height;
181
+ const weight = band.fontWeight != null ? `font-weight:${band.fontWeight};` : "";
182
+ 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>`;
183
+ };
184
+ getLinkProtocol = (href) => {
185
+ try {
186
+ return new URL(href).protocol;
187
+ } catch {
188
+ return "";
189
+ }
190
+ };
191
+ getLinkInitial = (hostname) => {
192
+ const first = hostname.trim().charAt(0).toUpperCase();
193
+ return first || "L";
194
+ };
195
+ buildGoogleFaviconUrl = (hostname) => hostname ? `https://www.google.com/s2/favicons?domain=${encodeURIComponent(hostname)}&sz=64` : null;
196
+ getStableLinkIdSuffix = (value) => {
197
+ let hash = 0;
198
+ for (const char of value) {
199
+ hash = hash * 31 + char.charCodeAt(0) >>> 0;
200
+ }
201
+ return hash.toString(36);
202
+ };
203
+ isCanvuLinkData = (value) => {
204
+ if (!value || typeof value !== "object") return false;
205
+ const candidate = value;
206
+ return typeof candidate.href === "string" && candidate.href.length > 0;
207
+ };
208
+ }
209
+ });
210
+
52
211
  // src/scene/text-svg.ts
53
212
  function escapeSvgTextContent(s) {
54
213
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
55
214
  }
56
- function escapeHtmlText(s) {
215
+ function escapeHtmlText2(s) {
57
216
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
58
217
  }
59
218
  function getSharedMeasureContext() {
@@ -154,9 +313,9 @@ function buildTextFixedBoundsSvg(content, width, height, fillColor = "#1d1d1d",
154
313
  const trimmed = content.trim();
155
314
  const padTop = EDIT_TOP_PAD_RATIO * fontSize;
156
315
  if (trimmed.length === 0) {
157
- return `<foreignObject width="${w}" height="${h}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;padding:${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>`;
316
+ 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>`;
158
317
  }
159
- const body = escapeHtmlText(content);
318
+ const body = escapeHtmlText2(content);
160
319
  return `<foreignObject width="${w}" height="${h}"><div xmlns="http://www.w3.org/1999/xhtml" style="box-sizing:border-box;width:100%;height:100%;margin:0;padding:${padTop}px 4px 0 4px;font-size:${fontSize}px;line-height:${lh}px;font-family:${TEXT_FONT_FAMILY};white-space:pre-wrap;word-wrap:break-word;overflow:hidden;color:${fillColor}">${body}</div></foreignObject>`;
161
320
  }
162
321
  var DEFAULT_TEXT_FONT_SIZE, DEFAULT_TEXT_TOOL_FONT_SIZE, TEXT_FONT_FAMILY, LINE_HEIGHT_RATIO, FIRST_LINE_BASELINE_RATIO, EDIT_TOP_PAD_RATIO, BOTTOM_PAD_RATIO, PLACEHOLDER, MIN_TEXT_BOX_W, MIN_TEXT_BOX_H, TEXT_PAD_X, MAX_TEXT_MEASURE_CACHE_ENTRIES, sharedMeasureContext, textMeasureCache;
@@ -813,6 +972,9 @@ function rebuildItemSvg(item) {
813
972
  )
814
973
  };
815
974
  }
975
+ if (getLinkData(item)) {
976
+ return rebuildLinkItemSvg(item);
977
+ }
816
978
  if (k === "custom" && item.customIntrinsicSize && item.customInnerSvg) {
817
979
  const b = normalizeRect(item.bounds);
818
980
  return {
@@ -1038,6 +1200,7 @@ var init_shape_builders = __esm({
1038
1200
  "src/scene/shape-builders.ts"() {
1039
1201
  init_rect();
1040
1202
  init_custom_shape();
1203
+ init_link_item();
1041
1204
  init_text_svg();
1042
1205
  DEFAULT_STROKE_STYLE = {
1043
1206
  stroke: "#1d1d1d",
@@ -6355,10 +6518,80 @@ function collectEraserTargetsAtWorldPoint(items, worldX, worldY, options) {
6355
6518
 
6356
6519
  // src/interaction/mutations.ts
6357
6520
  init_rect();
6521
+ init_link_item();
6358
6522
  init_shape_builders();
6359
6523
  init_text_svg();
6524
+ var LINK_CORNER_HANDLES = /* @__PURE__ */ new Set(["nw", "ne", "se", "sw"]);
6525
+ var clamp2 = (value, min, max) => Math.min(max, Math.max(min, value));
6526
+ var clampLinkResizeBounds = (startBounds, nextBounds, handle, intrinsicWidth) => {
6527
+ const next = normalizeRect(nextBounds);
6528
+ const scale = clamp2(
6529
+ next.width / Math.max(1e-9, intrinsicWidth),
6530
+ LINK_CARD_MIN_SCALE,
6531
+ LINK_CARD_MAX_SCALE
6532
+ );
6533
+ const width = intrinsicWidth * scale;
6534
+ const height = width / LINK_CARD_ASPECT;
6535
+ const start = normalizeRect(startBounds);
6536
+ const x0 = start.x;
6537
+ const y0 = start.y;
6538
+ const x1 = start.x + start.width;
6539
+ const y1 = start.y + start.height;
6540
+ switch (handle) {
6541
+ case "nw":
6542
+ return { x: x1 - width, y: y1 - height, width, height };
6543
+ case "ne":
6544
+ return { x: x0, y: y1 - height, width, height };
6545
+ case "sw":
6546
+ return { x: x1 - width, y: y0, width, height };
6547
+ default:
6548
+ return { x: x0, y: y0, width, height };
6549
+ }
6550
+ };
6360
6551
  function computeNewBoundsForResize(item, sb, handle, currentWorld) {
6361
6552
  const rot = getItemRotationRad(item);
6553
+ const link = getLinkData(item);
6554
+ if (link && item.customIntrinsicSize) {
6555
+ if (!LINK_CORNER_HANDLES.has(handle)) return sb;
6556
+ const intrinsicWidth = Math.max(1e-9, item.customIntrinsicSize.width);
6557
+ if (Math.abs(rot) < 1e-12) {
6558
+ const next = computeResizeBoundsFixedAspect(
6559
+ sb,
6560
+ handle,
6561
+ currentWorld,
6562
+ LINK_CARD_ASPECT
6563
+ );
6564
+ return clampLinkResizeBounds(sb, next, handle, intrinsicWidth);
6565
+ }
6566
+ const local2 = worldToItemLocal(
6567
+ currentWorld.x,
6568
+ currentWorld.y,
6569
+ sb.x,
6570
+ sb.y,
6571
+ sb.width,
6572
+ sb.height,
6573
+ rot
6574
+ );
6575
+ const localBounds2 = { x: 0, y: 0, width: sb.width, height: sb.height };
6576
+ const nextLocal = computeResizeBoundsFixedAspect(
6577
+ localBounds2,
6578
+ handle,
6579
+ local2,
6580
+ LINK_CARD_ASPECT
6581
+ );
6582
+ const clamped = clampLinkResizeBounds(
6583
+ localBounds2,
6584
+ nextLocal,
6585
+ handle,
6586
+ intrinsicWidth
6587
+ );
6588
+ return {
6589
+ x: sb.x + clamped.x,
6590
+ y: sb.y + clamped.y,
6591
+ width: clamped.width,
6592
+ height: clamped.height
6593
+ };
6594
+ }
6362
6595
  if (Math.abs(rot) < 1e-12) {
6363
6596
  if (item.toolKind === "image") {
6364
6597
  let aspect;
@@ -6831,17 +7064,8 @@ var SvgVectorRenderer = class {
6831
7064
  }
6832
7065
  };
6833
7066
 
6834
- // src/scene/link-item.ts
6835
- var LINK_PLUGIN_KEY = "canvuLink";
6836
- var isCanvuLinkData = (value) => {
6837
- if (!value || typeof value !== "object") return false;
6838
- const candidate = value;
6839
- return typeof candidate.href === "string" && candidate.href.length > 0;
6840
- };
6841
- function getLinkData(item) {
6842
- const entry = item.pluginData?.[LINK_PLUGIN_KEY];
6843
- return isCanvuLinkData(entry) ? entry : null;
6844
- }
7067
+ // src/react/VectorViewport.tsx
7068
+ init_link_item();
6845
7069
 
6846
7070
  // src/scene/scene.ts
6847
7071
  var VectorScene = class {
@@ -6935,8 +7159,10 @@ function smoothFreehandPointsToPathD(points) {
6935
7159
  }
6936
7160
 
6937
7161
  // src/react/InteractionOverlay.tsx
7162
+ init_link_item();
6938
7163
  init_shape_builders();
6939
7164
  var HANDLE_ORDER = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
7165
+ var LINK_HANDLE_ORDER = ["nw", "ne", "se", "sw"];
6940
7166
  var ERASER_TINT = "#cbd5e1";
6941
7167
  var ERASER_TINT_OPACITY = 0.95;
6942
7168
  var ERASER_PREVIEW_OPACITY = 0.3;
@@ -6989,7 +7215,7 @@ function InteractionOverlay({
6989
7215
  ) }, it.id);
6990
7216
  }),
6991
7217
  showResizeHandles && bSingle && single && rotHandlePos && /* @__PURE__ */ jsxs(Fragment, { children: [
6992
- HANDLE_ORDER.map((hid) => {
7218
+ (getLinkData(single) ? LINK_HANDLE_ORDER : HANDLE_ORDER).map((hid) => {
6993
7219
  const p = getHandleWorldPositionRotated(bSingle, hid, rotSingle);
6994
7220
  return /* @__PURE__ */ jsx(
6995
7221
  "circle",
@@ -7803,6 +8029,7 @@ function defaultPlacementWorld(tool, center) {
7803
8029
  lineWorld: [a, b]
7804
8030
  };
7805
8031
  }
8032
+ var LINK_CORNER_HANDLES2 = /* @__PURE__ */ new Set(["nw", "ne", "se", "sw"]);
7806
8033
  function pointInSelectedItemBounds(item, worldX, worldY) {
7807
8034
  const bounds = normalizeRect(item.bounds);
7808
8035
  const local = worldToItemLocal(
@@ -8280,7 +8507,11 @@ var VectorViewport = forwardRef(
8280
8507
  setEffectiveSelectedIdsRef.current = setEffectiveSelectedIds;
8281
8508
  toolIdRef.current = toolId;
8282
8509
  interactiveRef.current = interactive;
8283
- itemsRef.current = items;
8510
+ const normalizedItems = useMemo(
8511
+ () => items.map((item) => getLinkData(item) ? rebuildLinkItemSvg(item) : item),
8512
+ [items]
8513
+ );
8514
+ itemsRef.current = normalizedItems;
8284
8515
  onWorldPointerDownRef.current = onWorldPointerDown;
8285
8516
  const originalOnItemsChangeRef = useRef(onItemsChange);
8286
8517
  originalOnItemsChangeRef.current = onItemsChange;
@@ -8301,7 +8532,10 @@ var VectorViewport = forwardRef(
8301
8532
  autoResetToolToRef.current = autoResetToolTo;
8302
8533
  const onToolChangeRequestRef = useRef(onToolChangeRequest);
8303
8534
  onToolChangeRequestRef.current = onToolChangeRequest;
8304
- const resolvedItems = useMemo(() => resolveArrowBindingsInScene(items), [items]);
8535
+ const resolvedItems = useMemo(
8536
+ () => resolveArrowBindingsInScene(normalizedItems),
8537
+ [normalizedItems]
8538
+ );
8305
8539
  const resolvedItemsRef = useRef(resolvedItems);
8306
8540
  resolvedItemsRef.current = resolvedItems;
8307
8541
  const liveId = useId();
@@ -9722,7 +9956,8 @@ var VectorViewport = forwardRef(
9722
9956
  handleRadiusWorld,
9723
9957
  rot
9724
9958
  );
9725
- if (hb) {
9959
+ const isLinkResizeHandle = hb && getLinkData(selected) ? LINK_CORNER_HANDLES2.has(hb) : Boolean(hb);
9960
+ if (hb && isLinkResizeHandle) {
9726
9961
  const snapRs = bakedSnapshot(selected.id);
9727
9962
  if (!snapRs) return;
9728
9963
  dragStateRef.current = {