canvu-react 0.4.35 → 0.4.36

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/native.d.cts CHANGED
@@ -1,3 +1,5 @@
1
+ import { C as CanvuLinkData } from './link-item-DwzXOwU5.cjs';
2
+ export { D as DEFAULT_LINK_CARD_SIZE, c as createLinkItem, g as getLinkData, i as isLinkItem } from './link-item-DwzXOwU5.cjs';
1
3
  import { C as Camera2D, S as StrokeStyle } from './shape-builders-CKEMjivV.cjs';
2
4
  export { o as createFreehandStrokeItem, q as createImageItem, t as createShapeId } from './shape-builders-CKEMjivV.cjs';
3
5
  import { V as VectorSceneItem, R as Rect } from './types-BCCvY6ie.cjs';
@@ -162,7 +164,7 @@ type NativeVectorToolbarProps = {
162
164
  readonly renderOverflowChevronIcon?: (input: NativeVectorToolbarRenderOverflowInput) => ReactNode;
163
165
  readonly renderToolButton?: (input: NativeVectorToolbarRenderToolInput) => ReactNode;
164
166
  };
165
- declare const DEFAULT_NATIVE_OVERFLOW_TOOL_IDS: readonly ["rect", "ellipse", "architectural-cloud", "line", "marker", "laser", "image"];
167
+ declare const DEFAULT_NATIVE_OVERFLOW_TOOL_IDS: readonly ["rect", "ellipse", "architectural-cloud", "line", "marker", "laser", "image", "link"];
166
168
  /**
167
169
  * Default mobile-friendly tool list for {@link NativeVectorToolbar}.
168
170
  *
@@ -213,6 +215,46 @@ type NativeWorldPointerDownDetail = {
213
215
  readonly screenX: number;
214
216
  readonly screenY: number;
215
217
  };
218
+ /**
219
+ * Optional override for a link item inserted from the native link tool.
220
+ *
221
+ * Use this when your app has already reserved an item id or wants to place the
222
+ * link card at a custom box. Most apps can omit it and use the default card
223
+ * centered on the tapped world point.
224
+ */
225
+ type NativeLinkToolInsertOptions = {
226
+ readonly id?: string;
227
+ readonly bounds?: Rect;
228
+ };
229
+ /**
230
+ * Detail passed when the built-in native `"link"` tool receives a tap.
231
+ *
232
+ * This is the high-level native integration point: show an app-native URL
233
+ * dialog, then call {@link NativeLinkToolRequestDetail.insertLink} with the
234
+ * submitted link metadata. Use `onWorldPointerDown` only for fully custom tools
235
+ * or when you do not want canvu to insert the card.
236
+ *
237
+ * @example
238
+ * ```tsx
239
+ * <NativeVectorViewport
240
+ * toolId={toolId}
241
+ * onLinkToolRequest={(detail) => {
242
+ * openLinkDialog({
243
+ * onSubmit: (href) => detail.insertLink({ href }),
244
+ * });
245
+ * }}
246
+ * />
247
+ * ```
248
+ */
249
+ type NativeLinkToolRequestDetail = {
250
+ readonly toolId: "link";
251
+ readonly worldX: number;
252
+ readonly worldY: number;
253
+ readonly screenX: number;
254
+ readonly screenY: number;
255
+ readonly suggestedBounds: Rect;
256
+ readonly insertLink: (link: CanvuLinkData, options?: NativeLinkToolInsertOptions) => VectorSceneItem | null;
257
+ };
216
258
  type NativeVectorViewportProps = {
217
259
  readonly items: readonly VectorSceneItem[];
218
260
  readonly selectedIds?: readonly string[];
@@ -223,6 +265,14 @@ type NativeVectorViewportProps = {
223
265
  readonly onSelectionChange?: (ids: string[]) => void;
224
266
  readonly onItemsChange?: (items: VectorSceneItem[]) => void;
225
267
  readonly onToolChangeRequest?: (toolId: string) => void;
268
+ /**
269
+ * Called after the built-in native `"link"` tool is tapped on the canvas.
270
+ *
271
+ * Use this to open your app's native modal or sheet for URL entry. Call
272
+ * `detail.insertLink(...)` after the user confirms to append and select the
273
+ * link card at the tapped world point.
274
+ */
275
+ readonly onLinkToolRequest?: (detail: NativeLinkToolRequestDetail) => void;
226
276
  readonly onWorldPointerDown?: (detail: NativeWorldPointerDownDetail) => void;
227
277
  readonly onWorldPointerMove?: (world: {
228
278
  readonly x: number;
@@ -369,4 +419,4 @@ type SvgNode = SvgRectNode | SvgEllipseNode | SvgCircleNode | SvgLineNode | SvgP
369
419
  */
370
420
  declare function parseSvgFragment(xml: string): SvgNode[];
371
421
 
372
- export { DEFAULT_NATIVE_OVERFLOW_TOOL_IDS, DEFAULT_NATIVE_VECTOR_TOOLS, NATIVE_STYLE_PALETTE, type NativeCustomShapePlacementOptions, NativeInteractionOverlay, type NativeInteractionOverlayProps, NativeSceneRenderer, type NativeSceneRendererProps, NativeShapeRenderer, type NativeShapeRendererProps, type NativeStyleColor, NativeVectorStyleInspector, type NativeVectorStyleInspectorProps, type NativeVectorStyleToolId, type NativeVectorToolDefinition, NativeVectorToolbar, type NativeVectorToolbarDensity, type NativeVectorToolbarProps, type NativeVectorToolbarRenderOverflowInput, type NativeVectorToolbarRenderToolInput, type NativeVectorToolbarRenderToolLockInput, NativeVectorViewport, type NativeVectorViewportHandle, type NativeVectorViewportProps, type NativeWorldPointerDownDetail, type PlacementPreview, type SvgNode, VectorSceneItem, nativeStyleColorWithOpacity, normalizeNativeStyleHex, parseSvgFragment };
422
+ export { CanvuLinkData, DEFAULT_NATIVE_OVERFLOW_TOOL_IDS, DEFAULT_NATIVE_VECTOR_TOOLS, NATIVE_STYLE_PALETTE, type NativeCustomShapePlacementOptions, NativeInteractionOverlay, type NativeInteractionOverlayProps, type NativeLinkToolInsertOptions, type NativeLinkToolRequestDetail, NativeSceneRenderer, type NativeSceneRendererProps, NativeShapeRenderer, type NativeShapeRendererProps, type NativeStyleColor, NativeVectorStyleInspector, type NativeVectorStyleInspectorProps, type NativeVectorStyleToolId, type NativeVectorToolDefinition, NativeVectorToolbar, type NativeVectorToolbarDensity, type NativeVectorToolbarProps, type NativeVectorToolbarRenderOverflowInput, type NativeVectorToolbarRenderToolInput, type NativeVectorToolbarRenderToolLockInput, NativeVectorViewport, type NativeVectorViewportHandle, type NativeVectorViewportProps, type NativeWorldPointerDownDetail, type PlacementPreview, type SvgNode, VectorSceneItem, nativeStyleColorWithOpacity, normalizeNativeStyleHex, parseSvgFragment };
package/dist/native.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { C as CanvuLinkData } from './link-item-IW4GTnxl.js';
2
+ export { D as DEFAULT_LINK_CARD_SIZE, c as createLinkItem, g as getLinkData, i as isLinkItem } from './link-item-IW4GTnxl.js';
1
3
  import { C as Camera2D, S as StrokeStyle } from './shape-builders-Cyh8zvDG.js';
2
4
  export { o as createFreehandStrokeItem, q as createImageItem, t as createShapeId } from './shape-builders-Cyh8zvDG.js';
3
5
  import { V as VectorSceneItem, R as Rect } from './types-BCCvY6ie.js';
@@ -162,7 +164,7 @@ type NativeVectorToolbarProps = {
162
164
  readonly renderOverflowChevronIcon?: (input: NativeVectorToolbarRenderOverflowInput) => ReactNode;
163
165
  readonly renderToolButton?: (input: NativeVectorToolbarRenderToolInput) => ReactNode;
164
166
  };
165
- declare const DEFAULT_NATIVE_OVERFLOW_TOOL_IDS: readonly ["rect", "ellipse", "architectural-cloud", "line", "marker", "laser", "image"];
167
+ declare const DEFAULT_NATIVE_OVERFLOW_TOOL_IDS: readonly ["rect", "ellipse", "architectural-cloud", "line", "marker", "laser", "image", "link"];
166
168
  /**
167
169
  * Default mobile-friendly tool list for {@link NativeVectorToolbar}.
168
170
  *
@@ -213,6 +215,46 @@ type NativeWorldPointerDownDetail = {
213
215
  readonly screenX: number;
214
216
  readonly screenY: number;
215
217
  };
218
+ /**
219
+ * Optional override for a link item inserted from the native link tool.
220
+ *
221
+ * Use this when your app has already reserved an item id or wants to place the
222
+ * link card at a custom box. Most apps can omit it and use the default card
223
+ * centered on the tapped world point.
224
+ */
225
+ type NativeLinkToolInsertOptions = {
226
+ readonly id?: string;
227
+ readonly bounds?: Rect;
228
+ };
229
+ /**
230
+ * Detail passed when the built-in native `"link"` tool receives a tap.
231
+ *
232
+ * This is the high-level native integration point: show an app-native URL
233
+ * dialog, then call {@link NativeLinkToolRequestDetail.insertLink} with the
234
+ * submitted link metadata. Use `onWorldPointerDown` only for fully custom tools
235
+ * or when you do not want canvu to insert the card.
236
+ *
237
+ * @example
238
+ * ```tsx
239
+ * <NativeVectorViewport
240
+ * toolId={toolId}
241
+ * onLinkToolRequest={(detail) => {
242
+ * openLinkDialog({
243
+ * onSubmit: (href) => detail.insertLink({ href }),
244
+ * });
245
+ * }}
246
+ * />
247
+ * ```
248
+ */
249
+ type NativeLinkToolRequestDetail = {
250
+ readonly toolId: "link";
251
+ readonly worldX: number;
252
+ readonly worldY: number;
253
+ readonly screenX: number;
254
+ readonly screenY: number;
255
+ readonly suggestedBounds: Rect;
256
+ readonly insertLink: (link: CanvuLinkData, options?: NativeLinkToolInsertOptions) => VectorSceneItem | null;
257
+ };
216
258
  type NativeVectorViewportProps = {
217
259
  readonly items: readonly VectorSceneItem[];
218
260
  readonly selectedIds?: readonly string[];
@@ -223,6 +265,14 @@ type NativeVectorViewportProps = {
223
265
  readonly onSelectionChange?: (ids: string[]) => void;
224
266
  readonly onItemsChange?: (items: VectorSceneItem[]) => void;
225
267
  readonly onToolChangeRequest?: (toolId: string) => void;
268
+ /**
269
+ * Called after the built-in native `"link"` tool is tapped on the canvas.
270
+ *
271
+ * Use this to open your app's native modal or sheet for URL entry. Call
272
+ * `detail.insertLink(...)` after the user confirms to append and select the
273
+ * link card at the tapped world point.
274
+ */
275
+ readonly onLinkToolRequest?: (detail: NativeLinkToolRequestDetail) => void;
226
276
  readonly onWorldPointerDown?: (detail: NativeWorldPointerDownDetail) => void;
227
277
  readonly onWorldPointerMove?: (world: {
228
278
  readonly x: number;
@@ -369,4 +419,4 @@ type SvgNode = SvgRectNode | SvgEllipseNode | SvgCircleNode | SvgLineNode | SvgP
369
419
  */
370
420
  declare function parseSvgFragment(xml: string): SvgNode[];
371
421
 
372
- export { DEFAULT_NATIVE_OVERFLOW_TOOL_IDS, DEFAULT_NATIVE_VECTOR_TOOLS, NATIVE_STYLE_PALETTE, type NativeCustomShapePlacementOptions, NativeInteractionOverlay, type NativeInteractionOverlayProps, NativeSceneRenderer, type NativeSceneRendererProps, NativeShapeRenderer, type NativeShapeRendererProps, type NativeStyleColor, NativeVectorStyleInspector, type NativeVectorStyleInspectorProps, type NativeVectorStyleToolId, type NativeVectorToolDefinition, NativeVectorToolbar, type NativeVectorToolbarDensity, type NativeVectorToolbarProps, type NativeVectorToolbarRenderOverflowInput, type NativeVectorToolbarRenderToolInput, type NativeVectorToolbarRenderToolLockInput, NativeVectorViewport, type NativeVectorViewportHandle, type NativeVectorViewportProps, type NativeWorldPointerDownDetail, type PlacementPreview, type SvgNode, VectorSceneItem, nativeStyleColorWithOpacity, normalizeNativeStyleHex, parseSvgFragment };
422
+ export { CanvuLinkData, DEFAULT_NATIVE_OVERFLOW_TOOL_IDS, DEFAULT_NATIVE_VECTOR_TOOLS, NATIVE_STYLE_PALETTE, type NativeCustomShapePlacementOptions, NativeInteractionOverlay, type NativeInteractionOverlayProps, type NativeLinkToolInsertOptions, type NativeLinkToolRequestDetail, NativeSceneRenderer, type NativeSceneRendererProps, NativeShapeRenderer, type NativeShapeRendererProps, type NativeStyleColor, NativeVectorStyleInspector, type NativeVectorStyleInspectorProps, type NativeVectorStyleToolId, type NativeVectorToolDefinition, NativeVectorToolbar, type NativeVectorToolbarDensity, type NativeVectorToolbarProps, type NativeVectorToolbarRenderOverflowInput, type NativeVectorToolbarRenderToolInput, type NativeVectorToolbarRenderToolLockInput, NativeVectorViewport, type NativeVectorViewportHandle, type NativeVectorViewportProps, type NativeWorldPointerDownDetail, type PlacementPreview, type SvgNode, VectorSceneItem, nativeStyleColorWithOpacity, normalizeNativeStyleHex, parseSvgFragment };
package/dist/native.js CHANGED
@@ -4,8 +4,6 @@ import { memo, forwardRef, useState, useRef, useCallback, useEffect, useMemo, us
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { StyleSheet, PanResponder, View, Pressable, Text as Text$1, ScrollView } from 'react-native';
6
6
 
7
- // src/scene/shape-builders.ts
8
-
9
7
  // src/math/rect.ts
10
8
  function rectsIntersect(a, b) {
11
9
  return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
@@ -22,12 +20,36 @@ function normalizeRect(r) {
22
20
  }
23
21
 
24
22
  // src/scene/custom-shape.ts
23
+ function expandCustomShapeTemplate(template, width, height) {
24
+ return template.replace(/\{\{w\}\}/g, String(width)).replace(/\{\{h\}\}/g, String(height)).replace(/\{\{width\}\}/g, String(width)).replace(/\{\{height\}\}/g, String(height));
25
+ }
26
+ function resolveCustomInner(content, size) {
27
+ if ("render" in content) {
28
+ return content.render(size);
29
+ }
30
+ return expandCustomShapeTemplate(content.svg, size.width, size.height);
31
+ }
25
32
  function buildCustomShapeChildrenSvg(inner, intrinsic, bounds) {
26
33
  const b = normalizeRect(bounds);
27
34
  const sx = b.width / intrinsic.width;
28
35
  const sy = b.height / intrinsic.height;
29
36
  return `<g transform="scale(${sx},${sy})">${inner}</g>`;
30
37
  }
38
+ function createCustomShapeItem(id, bounds, content) {
39
+ const r = normalizeRect(bounds);
40
+ const intrinsic = { width: r.width, height: r.height };
41
+ const inner = resolveCustomInner(content, intrinsic);
42
+ return {
43
+ id,
44
+ x: r.x,
45
+ y: r.y,
46
+ bounds: { ...r },
47
+ toolKind: "custom",
48
+ customIntrinsicSize: intrinsic,
49
+ customInnerSvg: inner,
50
+ childrenSvg: buildCustomShapeChildrenSvg(inner, intrinsic, r)
51
+ };
52
+ }
31
53
 
32
54
  // src/scene/link-item.ts
33
55
  var LINK_PLUGIN_KEY = "canvuLink";
@@ -140,6 +162,9 @@ function getLinkData(item) {
140
162
  const entry = item.pluginData?.[LINK_PLUGIN_KEY];
141
163
  return isCanvuLinkData(entry) ? entry : null;
142
164
  }
165
+ function isLinkItem(item) {
166
+ return getLinkData(item) != null;
167
+ }
143
168
  function rebuildLinkItemSvg(item) {
144
169
  const link = getLinkData(item);
145
170
  if (!link) return item;
@@ -173,6 +198,26 @@ function rebuildLinkItemSvg(item) {
173
198
  childrenSvg: buildLinkCardSvg(width, height, link)
174
199
  };
175
200
  }
201
+ function createLinkItem(id, bounds, link) {
202
+ const width = bounds.width || DEFAULT_LINK_CARD_WIDTH;
203
+ const height = bounds.height || DEFAULT_LINK_CARD_HEIGHT;
204
+ const item = createCustomShapeItem(
205
+ id,
206
+ { ...bounds, width, height },
207
+ { render: (size) => buildLinkCardSvg(size.width, size.height, link) }
208
+ );
209
+ return rebuildLinkItemSvg({
210
+ ...item,
211
+ pluginData: {
212
+ ...item.pluginData ?? {},
213
+ [LINK_PLUGIN_KEY]: link
214
+ }
215
+ });
216
+ }
217
+ var DEFAULT_LINK_CARD_SIZE = {
218
+ width: DEFAULT_LINK_CARD_WIDTH,
219
+ height: DEFAULT_LINK_CARD_HEIGHT
220
+ };
176
221
 
177
222
  // src/scene/text-svg.ts
178
223
  function escapeSvgTextContent(s) {
@@ -1398,6 +1443,44 @@ function smoothFreehandPointsToPathD(points) {
1398
1443
  d += ` Q ${pLast.x} ${pLast.y} ${pEnd.x} ${pEnd.y}`;
1399
1444
  return d;
1400
1445
  }
1446
+
1447
+ // src/native/native-link-card.ts
1448
+ function linkHostname(href) {
1449
+ try {
1450
+ return new URL(href).hostname.replace(/^www\./, "");
1451
+ } catch {
1452
+ return href;
1453
+ }
1454
+ }
1455
+ function linkProtocol(href) {
1456
+ try {
1457
+ return new URL(href).protocol;
1458
+ } catch {
1459
+ return "";
1460
+ }
1461
+ }
1462
+ function linkInitial(value) {
1463
+ const first = value.trim().charAt(0).toUpperCase();
1464
+ return first || "L";
1465
+ }
1466
+ function buildNativeLinkCardDisplay(link) {
1467
+ const hostname = linkHostname(link.href);
1468
+ const title = link.title?.trim() || hostname || "Link";
1469
+ return {
1470
+ title,
1471
+ subtitle: hostname || link.href,
1472
+ initial: linkInitial(hostname || title),
1473
+ secure: linkProtocol(link.href) === "https:"
1474
+ };
1475
+ }
1476
+ function createNativeLinkCardBoundsAtPoint(point) {
1477
+ return {
1478
+ x: point.x - DEFAULT_LINK_CARD_SIZE.width / 2,
1479
+ y: point.y - DEFAULT_LINK_CARD_SIZE.height / 2,
1480
+ width: DEFAULT_LINK_CARD_SIZE.width,
1481
+ height: DEFAULT_LINK_CARD_SIZE.height
1482
+ };
1483
+ }
1401
1484
  var DEFAULT_NATIVE_IMAGE_CACHE_MAX_ENTRIES = 96;
1402
1485
  function disposeCachedImage(image) {
1403
1486
  try {
@@ -2087,7 +2170,119 @@ function localBounds(bounds) {
2087
2170
  const b = normalizeRect(bounds);
2088
2171
  return { w: Math.max(0, b.width), h: Math.max(0, b.height) };
2089
2172
  }
2173
+ function truncateText(value, maxChars) {
2174
+ if (value.length <= maxChars) return value;
2175
+ return `${value.slice(0, Math.max(0, maxChars - 3)).trimEnd()}...`;
2176
+ }
2177
+ function NativeLinkCardRenderer({
2178
+ item,
2179
+ link
2180
+ }) {
2181
+ const bounds = normalizeRect(item.bounds);
2182
+ const scaleX = Math.max(0.01, bounds.width / DEFAULT_LINK_CARD_SIZE.width);
2183
+ const scaleY = Math.max(0.01, bounds.height / DEFAULT_LINK_CARD_SIZE.height);
2184
+ const display = buildNativeLinkCardDisplay(link);
2185
+ const titleFont = matchFont({ fontSize: 14.5, fontWeight: "700" });
2186
+ const subtitleFont = matchFont({ fontSize: 12.5 });
2187
+ const initialFont = matchFont({ fontSize: 17, fontWeight: "700" });
2188
+ const title = truncateText(display.title, 28);
2189
+ const subtitle = truncateText(display.subtitle, display.secure ? 28 : 31);
2190
+ const subtitleX = display.secure ? 82 : 69;
2191
+ const subtitleWidth = display.secure ? 188 : 201;
2192
+ return /* @__PURE__ */ jsxs(Group, { transform: [{ scaleX }, { scaleY }], children: [
2193
+ /* @__PURE__ */ jsx(
2194
+ RoundedRect,
2195
+ {
2196
+ x: 0,
2197
+ y: 0,
2198
+ width: DEFAULT_LINK_CARD_SIZE.width,
2199
+ height: DEFAULT_LINK_CARD_SIZE.height,
2200
+ r: 16,
2201
+ color: "#ffffff",
2202
+ style: "fill",
2203
+ antiAlias: true
2204
+ }
2205
+ ),
2206
+ /* @__PURE__ */ jsx(
2207
+ RoundedRect,
2208
+ {
2209
+ x: 0.5,
2210
+ y: 0.5,
2211
+ width: DEFAULT_LINK_CARD_SIZE.width - 1,
2212
+ height: DEFAULT_LINK_CARD_SIZE.height - 1,
2213
+ r: 15.5,
2214
+ color: "#dfe4ea",
2215
+ style: "stroke",
2216
+ strokeWidth: 1,
2217
+ antiAlias: true
2218
+ }
2219
+ ),
2220
+ /* @__PURE__ */ jsx(
2221
+ RoundedRect,
2222
+ {
2223
+ x: 14,
2224
+ y: 14,
2225
+ width: 42,
2226
+ height: 42,
2227
+ r: 11,
2228
+ color: "#315bd6",
2229
+ style: "fill",
2230
+ antiAlias: true
2231
+ }
2232
+ ),
2233
+ /* @__PURE__ */ jsx(
2234
+ Text,
2235
+ {
2236
+ x: 29.5,
2237
+ y: 41,
2238
+ text: display.initial,
2239
+ color: "#ffffff",
2240
+ font: initialFont
2241
+ }
2242
+ ),
2243
+ /* @__PURE__ */ jsx(Group, { clip: { x: 69, y: 13, width: 215, height: 24 }, children: /* @__PURE__ */ jsx(Text, { x: 69, y: 32, text: title, color: "#1f2937", font: titleFont }) }),
2244
+ display.secure ? /* @__PURE__ */ jsxs(Group, { transform: [{ translateX: 69 }, { translateY: 41 }], children: [
2245
+ /* @__PURE__ */ jsx(
2246
+ Rect,
2247
+ {
2248
+ x: 1.5,
2249
+ y: 4.5,
2250
+ width: 7,
2251
+ height: 6,
2252
+ color: "#6b7280",
2253
+ style: "stroke",
2254
+ strokeWidth: 1.3
2255
+ }
2256
+ ),
2257
+ /* @__PURE__ */ jsx(
2258
+ Path,
2259
+ {
2260
+ path: "M3 4.5 V3 a2 2 0 0 1 4 0 v1.5",
2261
+ color: "#6b7280",
2262
+ style: "stroke",
2263
+ strokeWidth: 1.3,
2264
+ strokeCap: "round",
2265
+ strokeJoin: "round"
2266
+ }
2267
+ )
2268
+ ] }) : null,
2269
+ /* @__PURE__ */ jsx(Group, { clip: { x: subtitleX, y: 34, width: subtitleWidth, height: 22 }, children: /* @__PURE__ */ jsx(
2270
+ Text,
2271
+ {
2272
+ x: subtitleX,
2273
+ y: 51,
2274
+ text: subtitle,
2275
+ color: "#6b7280",
2276
+ font: subtitleFont
2277
+ }
2278
+ ) })
2279
+ ] });
2280
+ }
2090
2281
  function NativeShapeRenderer({ item }) {
2282
+ const link = getLinkData(item);
2283
+ if (link) {
2284
+ return /* @__PURE__ */ jsx(NativeLinkCardRenderer, { item, link });
2285
+ }
2091
2286
  const style = resolveStrokeStyle(item);
2092
2287
  const k = item.toolKind;
2093
2288
  if (k === "rect") {
@@ -3510,9 +3705,14 @@ var DEFAULT_NATIVE_OVERFLOW_TOOL_IDS = [
3510
3705
  "line",
3511
3706
  "marker",
3512
3707
  "laser",
3513
- "image"
3708
+ "image",
3709
+ "link"
3514
3710
  ];
3515
3711
  var CLOUD_TOOL_ICON_PATH = "M17.5 19H8.5a6.5 6.5 0 1 1 6.17-8.55A4.75 4.75 0 0 1 17.5 9.5a4.75 4.75 0 0 1 0 9.5Z";
3712
+ var LINK_TOOL_ICON_PATHS = [
3713
+ "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71",
3714
+ "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"
3715
+ ];
3516
3716
  function NativeCloudToolIcon({
3517
3717
  color,
3518
3718
  size = 19,
@@ -3531,6 +3731,25 @@ function NativeCloudToolIcon({
3531
3731
  }
3532
3732
  ) }) });
3533
3733
  }
3734
+ function NativeLinkToolIcon({
3735
+ color,
3736
+ size = 20,
3737
+ strokeWidth = 2.4
3738
+ }) {
3739
+ const scale = size / 24;
3740
+ return /* @__PURE__ */ jsx(Canvas, { style: { width: size, height: size }, children: /* @__PURE__ */ jsx(Group, { transform: [{ scale }], children: LINK_TOOL_ICON_PATHS.map((path) => /* @__PURE__ */ jsx(
3741
+ Path,
3742
+ {
3743
+ path,
3744
+ color,
3745
+ style: "stroke",
3746
+ strokeWidth,
3747
+ strokeCap: "round",
3748
+ strokeJoin: "round"
3749
+ },
3750
+ path
3751
+ )) }) });
3752
+ }
3534
3753
  var DEFAULT_NATIVE_VECTOR_TOOLS = [
3535
3754
  { id: "hand", label: "Hand", shortcutHint: "H", shortLabel: "H" },
3536
3755
  { id: "select", label: "Select", shortcutHint: "V", shortLabel: "V" },
@@ -3568,7 +3787,8 @@ var DEFAULT_NATIVE_VECTOR_TOOLS = [
3568
3787
  shortLabel: "E"
3569
3788
  },
3570
3789
  { id: "text", label: "Text", shortcutHint: "T", shortLabel: "T" },
3571
- { id: "image", label: "File", shortcutHint: "I", shortLabel: "I" }
3790
+ { id: "image", label: "File", shortcutHint: "I", shortLabel: "I" },
3791
+ { id: "link", label: "Link", shortcutHint: "N", shortLabel: "L" }
3572
3792
  ];
3573
3793
  function splitToolbarTools(tools, overflowToolIds) {
3574
3794
  const overflowIds = new Set(overflowToolIds);
@@ -3830,6 +4050,9 @@ function renderNativeToolFallback(tool, foregroundColor, toolLabelStyle) {
3830
4050
  if (tool.id === "architectural-cloud") {
3831
4051
  return /* @__PURE__ */ jsx(NativeCloudToolIcon, { color: foregroundColor });
3832
4052
  }
4053
+ if (tool.id === "link") {
4054
+ return /* @__PURE__ */ jsx(NativeLinkToolIcon, { color: foregroundColor });
4055
+ }
3833
4056
  return /* @__PURE__ */ jsx(Text$1, { style: [styles2.shortLabel, { color: foregroundColor }, toolLabelStyle], children: tool.shortLabel ?? tool.label.slice(0, 1).toUpperCase() });
3834
4057
  }
3835
4058
  var styles2 = StyleSheet.create({
@@ -4510,6 +4733,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4510
4733
  onSelectionChange,
4511
4734
  onItemsChange,
4512
4735
  onToolChangeRequest,
4736
+ onLinkToolRequest,
4513
4737
  onWorldPointerDown,
4514
4738
  onWorldPointerMove,
4515
4739
  onWorldPointerLeave,
@@ -4529,6 +4753,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4529
4753
  toolLockedRef.current = toolLocked;
4530
4754
  const onToolChangeRequestRef = useRef(onToolChangeRequest);
4531
4755
  onToolChangeRequestRef.current = onToolChangeRequest;
4756
+ const onLinkToolRequestRef = useRef(onLinkToolRequest);
4757
+ onLinkToolRequestRef.current = onLinkToolRequest;
4532
4758
  const onWorldPointerDownRef = useRef(onWorldPointerDown);
4533
4759
  onWorldPointerDownRef.current = onWorldPointerDown;
4534
4760
  const onWorldPointerMoveRef = useRef(onWorldPointerMove);
@@ -4871,7 +5097,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4871
5097
  });
4872
5098
  return;
4873
5099
  }
4874
- if (tool === "note" || tool === "text") {
5100
+ if (tool === "link" || tool === "note" || tool === "text") {
4875
5101
  dragStateRef.current = {
4876
5102
  kind: "tap",
4877
5103
  tool,
@@ -5207,6 +5433,44 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5207
5433
  const screenDy = point.y - st.startScreen.y;
5208
5434
  if (Math.hypot(screenDx, screenDy) > TAP_PX) return;
5209
5435
  const change = onItemsChangeRef.current;
5436
+ if (st.tool === "link") {
5437
+ const suggestedBounds = createNativeLinkCardBoundsAtPoint(st.startWorld);
5438
+ const insertLink = (link, options = {}) => {
5439
+ const currentChange = onItemsChangeRef.current;
5440
+ if (!currentChange) return null;
5441
+ const id = options.id ?? createShapeId();
5442
+ const item = createLinkItem(id, options.bounds ?? suggestedBounds, link);
5443
+ currentChange([...itemsRef.current, item]);
5444
+ onSelectionChangeRef.current?.([id]);
5445
+ requestSelectToolAfterUse();
5446
+ return item;
5447
+ };
5448
+ const requestLink = onLinkToolRequestRef.current;
5449
+ if (requestLink) {
5450
+ requestLink({
5451
+ toolId: "link",
5452
+ worldX: st.startWorld.x,
5453
+ worldY: st.startWorld.y,
5454
+ screenX: st.startScreen.x,
5455
+ screenY: st.startScreen.y,
5456
+ suggestedBounds,
5457
+ insertLink
5458
+ });
5459
+ return;
5460
+ }
5461
+ const handleWorldPointerDown = onWorldPointerDownRef.current;
5462
+ if (handleWorldPointerDown) {
5463
+ handleWorldPointerDown({
5464
+ toolId: "link",
5465
+ worldX: st.startWorld.x,
5466
+ worldY: st.startWorld.y,
5467
+ screenX: st.startScreen.x,
5468
+ screenY: st.startScreen.y
5469
+ });
5470
+ requestSelectToolAfterUse();
5471
+ }
5472
+ return;
5473
+ }
5210
5474
  if (!change) return;
5211
5475
  if (st.tool === "text") {
5212
5476
  const id = createShapeId();
@@ -5474,6 +5738,6 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5474
5738
  );
5475
5739
  });
5476
5740
 
5477
- export { DEFAULT_NATIVE_OVERFLOW_TOOL_IDS, DEFAULT_NATIVE_VECTOR_TOOLS, NATIVE_STYLE_PALETTE, NativeInteractionOverlay, NativeSceneRenderer, NativeShapeRenderer, NativeVectorStyleInspector, NativeVectorToolbar, NativeVectorViewport, createFreehandStrokeItem, createImageItem, createShapeId, nativeStyleColorWithOpacity, normalizeNativeStyleHex, parseSvgFragment };
5741
+ export { DEFAULT_LINK_CARD_SIZE, DEFAULT_NATIVE_OVERFLOW_TOOL_IDS, DEFAULT_NATIVE_VECTOR_TOOLS, NATIVE_STYLE_PALETTE, NativeInteractionOverlay, NativeSceneRenderer, NativeShapeRenderer, NativeVectorStyleInspector, NativeVectorToolbar, NativeVectorViewport, createFreehandStrokeItem, createImageItem, createLinkItem, createShapeId, getLinkData, isLinkItem, nativeStyleColorWithOpacity, normalizeNativeStyleHex, parseSvgFragment };
5478
5742
  //# sourceMappingURL=native.js.map
5479
5743
  //# sourceMappingURL=native.js.map