canvu-react 0.3.37 → 0.3.39

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-EtEuBwb7.cjs';
2
- export { H as HydratedSceneItemsWithAssetsResult, h as hydrateSceneItemsWithAssets } from './asset-hydration-EtEuBwb7.cjs';
1
+ import { I as IndexedDbImageStore } from './asset-hydration-DowNdaOJ.cjs';
2
+ export { H as HydratedSceneItemsWithAssetsResult, h as hydrateSceneItemsWithAssets } from './asset-hydration-DowNdaOJ.cjs';
3
3
  import { V as VectorSceneItem } from './types-Bnq2HtHQ.cjs';
4
- import { V as VectorViewportAssetKind, x as VectorViewportAssetStore } from './shape-builders-CsbSRZnQ.cjs';
5
- export { y as VectorViewportAssetHydrationRequest, z as VectorViewportAssetResolveRequest, A as VectorViewportAssetResolveResult, B as VectorViewportAssetUploadRequest, C as VectorViewportAssetUploadResult } from './shape-builders-CsbSRZnQ.cjs';
6
- import { B as BoardComponentPosition, V as VectorToolDefinition, C as CanvasPlugin, a as VectorSelectionInspector } from './types-DWGk2_GZ.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-DWGk2_GZ.cjs';
4
+ import { V as VectorViewportAssetKind, E as VectorViewportAssetStore } from './shape-builders-Dedcl6tw.cjs';
5
+ export { G as VectorViewportAssetHydrationRequest, H as VectorViewportAssetResolveRequest, I as VectorViewportAssetResolveResult, J as VectorViewportAssetUploadRequest, K as VectorViewportAssetUploadResult } from './shape-builders-Dedcl6tw.cjs';
6
+ import { B as BoardComponentPosition, V as VectorToolDefinition, C as CanvasPlugin, a as VectorSelectionInspector } from './types-DUW61Tjy.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-DUW61Tjy.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-DrTOgDdd.js';
2
- export { H as HydratedSceneItemsWithAssetsResult, h as hydrateSceneItemsWithAssets } from './asset-hydration-DrTOgDdd.js';
1
+ import { I as IndexedDbImageStore } from './asset-hydration-DdFLdlqX.js';
2
+ export { H as HydratedSceneItemsWithAssetsResult, h as hydrateSceneItemsWithAssets } from './asset-hydration-DdFLdlqX.js';
3
3
  import { V as VectorSceneItem } from './types-Bnq2HtHQ.js';
4
- import { V as VectorViewportAssetKind, x as VectorViewportAssetStore } from './shape-builders-CsSXKCcs.js';
5
- export { y as VectorViewportAssetHydrationRequest, z as VectorViewportAssetResolveRequest, A as VectorViewportAssetResolveResult, B as VectorViewportAssetUploadRequest, C as VectorViewportAssetUploadResult } from './shape-builders-CsSXKCcs.js';
6
- import { B as BoardComponentPosition, V as VectorToolDefinition, C as CanvasPlugin, a as VectorSelectionInspector } from './types-B6PAYKzx.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-B6PAYKzx.js';
4
+ import { V as VectorViewportAssetKind, E as VectorViewportAssetStore } from './shape-builders-C7bxJBGR.js';
5
+ export { G as VectorViewportAssetHydrationRequest, H as VectorViewportAssetResolveRequest, I as VectorViewportAssetResolveResult, J as VectorViewportAssetUploadRequest, K as VectorViewportAssetUploadResult } from './shape-builders-C7bxJBGR.js';
6
+ import { B as BoardComponentPosition, V as VectorToolDefinition, C as CanvasPlugin, a as VectorSelectionInspector } from './types-BBb8KoyW.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-BBb8KoyW.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
@@ -4963,6 +4963,78 @@ var Camera2D = class {
4963
4963
  }
4964
4964
  };
4965
4965
 
4966
+ // src/input/pan-momentum.ts
4967
+ var VELOCITY_SAMPLE_WINDOW = 80;
4968
+ var FRICTION = 0.94;
4969
+ var MIN_VELOCITY = 0.3;
4970
+ function createPanMomentumController(camera, onUpdate, sensitivity) {
4971
+ const samples = [];
4972
+ let animationFrameId = null;
4973
+ const cancel = () => {
4974
+ if (animationFrameId !== null) {
4975
+ cancelAnimationFrame(animationFrameId);
4976
+ animationFrameId = null;
4977
+ }
4978
+ };
4979
+ const reset = () => {
4980
+ cancel();
4981
+ samples.length = 0;
4982
+ };
4983
+ const recordMove = (dx, dy) => {
4984
+ const now = performance.now();
4985
+ samples.push({ dx, dy, time: now });
4986
+ const cutoff = now - VELOCITY_SAMPLE_WINDOW;
4987
+ let oldestSample = samples[0];
4988
+ while (oldestSample && oldestSample.time < cutoff) {
4989
+ samples.shift();
4990
+ oldestSample = samples[0];
4991
+ }
4992
+ };
4993
+ const computeReleaseVelocity = () => {
4994
+ if (samples.length < 2) return { vx: 0, vy: 0 };
4995
+ const first = samples[0];
4996
+ const last = samples[samples.length - 1];
4997
+ if (!first || !last) return { vx: 0, vy: 0 };
4998
+ const elapsed = last.time - first.time;
4999
+ if (elapsed < 4) return { vx: 0, vy: 0 };
5000
+ let totalDx = 0;
5001
+ let totalDy = 0;
5002
+ for (const sample of samples) {
5003
+ totalDx += sample.dx;
5004
+ totalDy += sample.dy;
5005
+ }
5006
+ const msPerFrame = 1e3 / 60;
5007
+ return {
5008
+ vx: totalDx / elapsed * msPerFrame * sensitivity,
5009
+ vy: totalDy / elapsed * msPerFrame * sensitivity
5010
+ };
5011
+ };
5012
+ const startMomentum = () => {
5013
+ cancel();
5014
+ const { vx, vy } = computeReleaseVelocity();
5015
+ samples.length = 0;
5016
+ if (Math.abs(vx) < MIN_VELOCITY && Math.abs(vy) < MIN_VELOCITY) {
5017
+ return;
5018
+ }
5019
+ let currentVx = vx;
5020
+ let currentVy = vy;
5021
+ const animate = () => {
5022
+ currentVx *= FRICTION;
5023
+ currentVy *= FRICTION;
5024
+ if (Math.abs(currentVx) < MIN_VELOCITY && Math.abs(currentVy) < MIN_VELOCITY) {
5025
+ animationFrameId = null;
5026
+ return;
5027
+ }
5028
+ camera.x += currentVx;
5029
+ camera.y += currentVy;
5030
+ onUpdate();
5031
+ animationFrameId = requestAnimationFrame(animate);
5032
+ };
5033
+ animationFrameId = requestAnimationFrame(animate);
5034
+ };
5035
+ return { recordMove, startMomentum, cancel, reset };
5036
+ }
5037
+
4966
5038
  // src/input/apple-pencil-navigation.ts
4967
5039
  var DRAWING_LIKE_TOOLS = /* @__PURE__ */ new Set([
4968
5040
  "draw",
@@ -4993,6 +5065,7 @@ function attachApplePencilNavigation(options) {
4993
5065
  let pinchStartDist = 0;
4994
5066
  let pinchStartZoom = 1;
4995
5067
  let panLast = null;
5068
+ let touchMomentum = null;
4996
5069
  const shouldIntercept = (e) => {
4997
5070
  if (e.pointerType !== "touch") return false;
4998
5071
  const tool = getCurrentToolId();
@@ -5011,6 +5084,10 @@ function attachApplePencilNavigation(options) {
5011
5084
  pointers.clear();
5012
5085
  mode = "idle";
5013
5086
  panLast = null;
5087
+ if (touchMomentum) {
5088
+ touchMomentum.cancel();
5089
+ touchMomentum = null;
5090
+ }
5014
5091
  return;
5015
5092
  }
5016
5093
  if (e.pointerType === "touch" && activePenPointerIds.size > 0) {
@@ -5020,10 +5097,18 @@ function attachApplePencilNavigation(options) {
5020
5097
  return;
5021
5098
  }
5022
5099
  if (!shouldIntercept(e)) return;
5100
+ if (touchMomentum) {
5101
+ touchMomentum.cancel();
5102
+ }
5023
5103
  pointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
5024
5104
  if (pointers.size === 1) {
5025
5105
  mode = "pan";
5026
5106
  panLast = { x: e.clientX, y: e.clientY };
5107
+ touchMomentum = createPanMomentumController(
5108
+ camera,
5109
+ onUpdate,
5110
+ touchPanSensitivity
5111
+ );
5027
5112
  } else if (pointers.size === 2) {
5028
5113
  const vals = Array.from(pointers.values());
5029
5114
  const a = vals[0];
@@ -5051,6 +5136,7 @@ function attachApplePencilNavigation(options) {
5051
5136
  panLast = { x: e.clientX, y: e.clientY };
5052
5137
  camera.x += dx * touchPanSensitivity;
5053
5138
  camera.y += dy * touchPanSensitivity;
5139
+ touchMomentum?.recordMove(dx, dy);
5054
5140
  onUpdate();
5055
5141
  e.preventDefault();
5056
5142
  e.stopImmediatePropagation();
@@ -5097,12 +5183,20 @@ function attachApplePencilNavigation(options) {
5097
5183
  if (!pointers.has(e.pointerId)) return;
5098
5184
  pointers.delete(e.pointerId);
5099
5185
  if (pointers.size === 0) {
5186
+ const wasPanning = mode === "pan";
5100
5187
  mode = "idle";
5101
5188
  panLast = null;
5189
+ if (wasPanning && touchMomentum) {
5190
+ touchMomentum.startMomentum();
5191
+ touchMomentum = null;
5192
+ }
5102
5193
  } else if (pointers.size === 1 && mode === "pinch") {
5103
5194
  mode = "pan";
5104
5195
  const r = Array.from(pointers.values())[0];
5105
5196
  panLast = r ?? null;
5197
+ if (touchMomentum) {
5198
+ touchMomentum.reset();
5199
+ }
5106
5200
  }
5107
5201
  e.preventDefault();
5108
5202
  e.stopImmediatePropagation();
@@ -5112,6 +5206,10 @@ function attachApplePencilNavigation(options) {
5112
5206
  element.addEventListener("pointerup", onPointerUp, { capture: true });
5113
5207
  element.addEventListener("pointercancel", onPointerUp, { capture: true });
5114
5208
  return () => {
5209
+ if (touchMomentum) {
5210
+ touchMomentum.cancel();
5211
+ touchMomentum = null;
5212
+ }
5115
5213
  element.removeEventListener("pointerdown", onPointerDown, { capture: true });
5116
5214
  element.removeEventListener("pointermove", onPointerMove, { capture: true });
5117
5215
  element.removeEventListener("pointerup", onPointerUp, { capture: true });
@@ -5164,6 +5262,7 @@ function attachViewportInput(options) {
5164
5262
  let pinchStartZoom = 1;
5165
5263
  let panLast = null;
5166
5264
  let mousePanButton = null;
5265
+ let touchMomentum = null;
5167
5266
  const onWheel = (e) => {
5168
5267
  if (e.ctrlKey || e.metaKey) {
5169
5268
  e.preventDefault();
@@ -5190,6 +5289,9 @@ function attachViewportInput(options) {
5190
5289
  if (touchHandledElsewhere && e.pointerType === "touch") {
5191
5290
  return;
5192
5291
  }
5292
+ if (touchMomentum) {
5293
+ touchMomentum.cancel();
5294
+ }
5193
5295
  const panOk = allowPrimaryPointerPan();
5194
5296
  if (e.pointerType === "mouse" && e.button === 0) {
5195
5297
  if (!panOk) {
@@ -5215,6 +5317,11 @@ function attachViewportInput(options) {
5215
5317
  if (panOk) {
5216
5318
  mode = "pan";
5217
5319
  panLast = { x: e.clientX, y: e.clientY };
5320
+ touchMomentum = createPanMomentumController(
5321
+ camera,
5322
+ onUpdate,
5323
+ touchPanSensitivity
5324
+ );
5218
5325
  e.preventDefault();
5219
5326
  }
5220
5327
  } else if (pointers.size === 2) {
@@ -5251,6 +5358,7 @@ function attachViewportInput(options) {
5251
5358
  panLast = { x: e.clientX, y: e.clientY };
5252
5359
  camera.x += dx * touchPanSensitivity;
5253
5360
  camera.y += dy * touchPanSensitivity;
5361
+ touchMomentum?.recordMove(dx, dy);
5254
5362
  onUpdate();
5255
5363
  e.preventDefault();
5256
5364
  return;
@@ -5302,15 +5410,27 @@ function attachViewportInput(options) {
5302
5410
  }
5303
5411
  pointers.delete(e.pointerId);
5304
5412
  if (pointers.size === 0) {
5413
+ const wasPanning = mode === "pan";
5305
5414
  mode = "idle";
5306
5415
  panLast = null;
5416
+ if (wasPanning && touchMomentum) {
5417
+ touchMomentum.startMomentum();
5418
+ touchMomentum = null;
5419
+ }
5307
5420
  } else if (pointers.size === 1 && mode === "pinch") {
5308
5421
  mode = "pan";
5309
5422
  const remaining = Array.from(pointers.values())[0];
5310
5423
  panLast = remaining ?? null;
5424
+ if (touchMomentum) {
5425
+ touchMomentum.reset();
5426
+ }
5311
5427
  }
5312
5428
  };
5313
5429
  const onPointerCancel = (e) => {
5430
+ if (touchMomentum) {
5431
+ touchMomentum.cancel();
5432
+ touchMomentum = null;
5433
+ }
5314
5434
  onPointerUp(e);
5315
5435
  };
5316
5436
  wheelTarget.addEventListener("wheel", onWheel, { passive: false });
@@ -5319,6 +5439,10 @@ function attachViewportInput(options) {
5319
5439
  element.addEventListener("pointerup", onPointerUp);
5320
5440
  element.addEventListener("pointercancel", onPointerCancel);
5321
5441
  return () => {
5442
+ if (touchMomentum) {
5443
+ touchMomentum.cancel();
5444
+ touchMomentum = null;
5445
+ }
5322
5446
  wheelTarget.removeEventListener("wheel", onWheel);
5323
5447
  element.removeEventListener("pointerdown", onPointerDown);
5324
5448
  element.removeEventListener("pointermove", onPointerMove);
@@ -6200,6 +6324,7 @@ var SvgVectorRenderer = class {
6200
6324
  svg;
6201
6325
  rootG;
6202
6326
  itemNodeCache = /* @__PURE__ */ new Map();
6327
+ liveOverlay = null;
6203
6328
  resizeObserver;
6204
6329
  constructor(options) {
6205
6330
  this.container = options.container;
@@ -6234,6 +6359,50 @@ var SvgVectorRenderer = class {
6234
6359
  const items = cullItemsByViewport(this.scene.getItems(), visible);
6235
6360
  this.syncVisibleItems(items);
6236
6361
  this.rootG.setAttribute("transform", formatCameraTransform(this.camera));
6362
+ this.keepLiveOverlayOnTop();
6363
+ }
6364
+ /**
6365
+ * Updates only the in-progress (live) stroke node without re-culling or
6366
+ * re-syncing the committed scene items. Drawing tools call this on every
6367
+ * pointer move, so it must stay O(1) regardless of how many items the
6368
+ * board already contains.
6369
+ *
6370
+ * Pass `null` to remove the live overlay (e.g. when the stroke is committed).
6371
+ */
6372
+ renderLiveItem(item) {
6373
+ if (!item) {
6374
+ if (this.liveOverlay) {
6375
+ this.liveOverlay.g.remove();
6376
+ this.liveOverlay = null;
6377
+ }
6378
+ return;
6379
+ }
6380
+ if (!this.liveOverlay) {
6381
+ const g = document.createElementNS("http://www.w3.org/2000/svg", "g");
6382
+ g.setAttribute("data-live-overlay", "true");
6383
+ this.liveOverlay = {
6384
+ g,
6385
+ lastChildrenSvg: "",
6386
+ lastTransform: ""
6387
+ };
6388
+ this.rootG.appendChild(g);
6389
+ }
6390
+ const cached = this.liveOverlay;
6391
+ const t = formatItemPlacementTransform(item);
6392
+ if (cached.lastTransform !== t) {
6393
+ cached.g.setAttribute("transform", t);
6394
+ cached.lastTransform = t;
6395
+ }
6396
+ if (cached.lastChildrenSvg !== item.childrenSvg) {
6397
+ cached.g.innerHTML = item.childrenSvg;
6398
+ cached.lastChildrenSvg = item.childrenSvg;
6399
+ }
6400
+ this.keepLiveOverlayOnTop();
6401
+ }
6402
+ keepLiveOverlayOnTop() {
6403
+ if (this.liveOverlay && this.rootG.lastChild !== this.liveOverlay.g) {
6404
+ this.rootG.appendChild(this.liveOverlay.g);
6405
+ }
6237
6406
  }
6238
6407
  syncVisibleItems(items) {
6239
6408
  const visibleIds = /* @__PURE__ */ new Set();
@@ -6278,6 +6447,7 @@ var SvgVectorRenderer = class {
6278
6447
  destroy() {
6279
6448
  this.resizeObserver.disconnect();
6280
6449
  this.itemNodeCache.clear();
6450
+ this.liveOverlay = null;
6281
6451
  this.svg.remove();
6282
6452
  }
6283
6453
  /** Toggle whether the scene SVG receives pointer events (vs overlay handling them). */
@@ -6286,6 +6456,18 @@ var SvgVectorRenderer = class {
6286
6456
  }
6287
6457
  };
6288
6458
 
6459
+ // src/scene/link-item.ts
6460
+ var LINK_PLUGIN_KEY = "canvuLink";
6461
+ var isCanvuLinkData = (value) => {
6462
+ if (!value || typeof value !== "object") return false;
6463
+ const candidate = value;
6464
+ return typeof candidate.href === "string" && candidate.href.length > 0;
6465
+ };
6466
+ function getLinkData(item) {
6467
+ const entry = item.pluginData?.[LINK_PLUGIN_KEY];
6468
+ return isCanvuLinkData(entry) ? entry : null;
6469
+ }
6470
+
6289
6471
  // src/scene/scene.ts
6290
6472
  var VectorScene = class {
6291
6473
  items = [];
@@ -7462,6 +7644,7 @@ var VectorViewport = forwardRef(
7462
7644
  selectedIds: selectedIdsProp,
7463
7645
  onSelectionChange,
7464
7646
  onItemsChange: consumerOnItemsChange,
7647
+ onActivateLink,
7465
7648
  onWorldPointerDown: consumerOnWorldPointerDown,
7466
7649
  toolbar,
7467
7650
  navMenu,
@@ -7642,6 +7825,8 @@ var VectorViewport = forwardRef(
7642
7825
  const itemsRef = useRef(items);
7643
7826
  const onWorldPointerDownRef = useRef(onWorldPointerDown);
7644
7827
  const onItemsChangeRef = useRef(onItemsChange);
7828
+ const onActivateLinkRef = useRef(onActivateLink);
7829
+ onActivateLinkRef.current = onActivateLink;
7645
7830
  const assetStoreRef = useRef(assetStore);
7646
7831
  assetStoreRef.current = assetStore;
7647
7832
  const customPlacementRef = useRef(customPlacement);
@@ -7754,8 +7939,6 @@ var VectorViewport = forwardRef(
7754
7939
  const hiddenIds = new Set(eraserPreviewIds);
7755
7940
  return resolvedItems.filter((it) => !hiddenIds.has(it.id));
7756
7941
  }, [eraserPreviewIds, resolvedItems]);
7757
- const resolvedSceneItemsRef = useRef(resolvedSceneItems);
7758
- resolvedSceneItemsRef.current = resolvedSceneItems;
7759
7942
  const livePenStrokeItemRef = useRef(null);
7760
7943
  const [eraserActive, setEraserActive] = useState(false);
7761
7944
  const [editingTextId, setEditingTextId] = useState(null);
@@ -7976,12 +8159,9 @@ var VectorViewport = forwardRef(
7976
8159
  const renderSceneWithLivePenStroke = useCallback(
7977
8160
  (item) => {
7978
8161
  livePenStrokeItemRef.current = item;
7979
- const scene = sceneRef.current;
7980
8162
  const renderer = rendererRef.current;
7981
- if (!scene || !renderer) return;
7982
- const base2 = resolvedSceneItemsRef.current;
7983
- scene.setItems(item ? [...base2, item] : base2);
7984
- renderer.render();
8163
+ if (!renderer) return;
8164
+ renderer.renderLiveItem(item);
7985
8165
  },
7986
8166
  []
7987
8167
  );
@@ -8212,16 +8392,15 @@ var VectorViewport = forwardRef(
8212
8392
  }, []);
8213
8393
  useEffect(() => {
8214
8394
  const scene = sceneRef.current;
8395
+ const renderer = rendererRef.current;
8215
8396
  if (scene) {
8216
8397
  const live = livePenStrokeItemRef.current;
8217
8398
  if (live && resolvedSceneItems.some((it) => it.id === live.id)) {
8218
8399
  livePenStrokeItemRef.current = null;
8219
8400
  }
8220
- const currentLive = livePenStrokeItemRef.current;
8221
- scene.setItems(
8222
- currentLive ? [...resolvedSceneItems, currentLive] : resolvedSceneItems
8223
- );
8401
+ scene.setItems(resolvedSceneItems);
8224
8402
  renderFrame();
8403
+ renderer?.renderLiveItem(livePenStrokeItemRef.current);
8225
8404
  }
8226
8405
  }, [resolvedSceneItems, renderFrame]);
8227
8406
  useEffect(() => {
@@ -8797,11 +8976,12 @@ var VectorViewport = forwardRef(
8797
8976
  setEditingTextId(null);
8798
8977
  }, []);
8799
8978
  const placeImageFilesAtWorld = useCallback(
8800
- async (files, worldX, worldY) => {
8979
+ async (files, worldX, worldY, options) => {
8801
8980
  const change = onItemsChangeRef.current;
8802
8981
  if (!change || files.length === 0) return;
8803
8982
  const store = imageStoreRef.current;
8804
8983
  if (!store) return;
8984
+ const stackBelowExistingItems = options?.stackBelowExistingItems ?? true;
8805
8985
  try {
8806
8986
  const pdfFiles = files.filter((file) => file.type === "application/pdf");
8807
8987
  if (pdfFiles.length > 0) {
@@ -8827,7 +9007,7 @@ var VectorViewport = forwardRef(
8827
9007
  }
8828
9008
  const result = await ingestAssetFilesToSceneItems({
8829
9009
  files,
8830
- existingItems: itemsRef.current,
9010
+ existingItems: stackBelowExistingItems ? itemsRef.current : [],
8831
9011
  worldCenter: {
8832
9012
  x: worldX,
8833
9013
  y: worldY
@@ -8914,19 +9094,35 @@ var VectorViewport = forwardRef(
8914
9094
  );
8915
9095
  if (files.length === 0) return;
8916
9096
  const { worldX, worldY } = screenToWorld(e.clientX, e.clientY);
8917
- await placeImageFilesAtWorld(files, worldX, worldY);
9097
+ await placeImageFilesAtWorld(files, worldX, worldY, {
9098
+ stackBelowExistingItems: false
9099
+ });
8918
9100
  },
8919
9101
  [screenToWorld, placeImageFilesAtWorld]
8920
9102
  );
8921
9103
  const handleOverlayDoubleClick = useCallback(
8922
9104
  (e) => {
8923
- if (!interactiveRef.current || !onItemsChangeRef.current) return;
8924
- if (toolIdRef.current !== "select") return;
8925
- e.preventDefault();
8926
9105
  const cam = cameraRef.current;
8927
9106
  if (!cam) return;
8928
9107
  const { worldX, worldY } = screenToWorld(e.clientX, e.clientY);
8929
9108
  const lineHitWorld = 10 / cam.zoom;
9109
+ const linkHit = hitTestWorldPoint(resolvedItemsRef.current, worldX, worldY, {
9110
+ lineHitWorld,
9111
+ ignoreLocked: false
9112
+ });
9113
+ const link = linkHit ? getLinkData(linkHit) : null;
9114
+ if (linkHit && link) {
9115
+ e.preventDefault();
9116
+ if (onActivateLinkRef.current) {
9117
+ onActivateLinkRef.current({ link, item: linkHit });
9118
+ } else if (typeof window !== "undefined" && typeof window.open === "function") {
9119
+ window.open(link.href, "_blank", "noopener,noreferrer");
9120
+ }
9121
+ return;
9122
+ }
9123
+ if (!interactiveRef.current || !onItemsChangeRef.current) return;
9124
+ if (toolIdRef.current !== "select") return;
9125
+ e.preventDefault();
8930
9126
  const hit = hitTestWorldPoint(resolvedItemsRef.current, worldX, worldY, {
8931
9127
  lineHitWorld,
8932
9128
  ignoreLocked: true