canvu-react 0.4.33 → 0.4.34

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,9 +1,10 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { C as CanvasPlugin } from './types-BS-YG8Hx.cjs';
2
+ import { C as CanvasPlugin } from './types-NBYvslB-.cjs';
3
3
  import 'react';
4
4
  import './types-BCCvY6ie.cjs';
5
5
  import './shape-builders-CKEMjivV.cjs';
6
6
  import './link-item-Dncuz2d_.cjs';
7
+ import './types-BQUbxMgz.cjs';
7
8
 
8
9
  type ChatbotPluginPanelProps = {
9
10
  /**
package/dist/chatbot.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
- import { C as CanvasPlugin } from './types-UZYYwK-v.js';
2
+ import { C as CanvasPlugin } from './types-DeDm865m.js';
3
3
  import 'react';
4
4
  import './types-BCCvY6ie.js';
5
5
  import './shape-builders-Cyh8zvDG.js';
6
6
  import './link-item-voRU0Up9.js';
7
+ import './types-B82WiQQh.js';
7
8
 
8
9
  type ChatbotPluginPanelProps = {
9
10
  /**
package/dist/native.cjs CHANGED
@@ -1360,6 +1360,17 @@ function computeResizeBoundsFixedAspect(bounds, handle, currentWorld, aspect) {
1360
1360
  }
1361
1361
  }
1362
1362
 
1363
+ // src/react/presence/peer-color.ts
1364
+ function defaultPresenceColorForId(id) {
1365
+ let h = 2166136261;
1366
+ for (let i = 0; i < id.length; i++) {
1367
+ h ^= id.charCodeAt(i);
1368
+ h = Math.imul(h, 16777619);
1369
+ }
1370
+ const hue = (h >>> 0) % 360;
1371
+ return `hsl(${hue} 72% 42%)`;
1372
+ }
1373
+
1363
1374
  // src/scene/freehand-path.ts
1364
1375
  function smoothFreehandPointsToPathD(points) {
1365
1376
  const n = points.length;
@@ -2278,6 +2289,26 @@ var HANDLE_ORDER = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
2278
2289
  var ERASER_PREVIEW_OPACITY = 0.3;
2279
2290
  var OVERLAY_STROKE_PX = 1.25;
2280
2291
  var MARQUEE_DASH_PX = 4;
2292
+ var REMOTE_CURSOR_SCREEN_PX = 22;
2293
+ var REMOTE_LABEL_SCREEN_PX = 12;
2294
+ function remoteStrokePaint(tool, fallback) {
2295
+ if (tool === "laser") {
2296
+ return { stroke: LASER_TINT, strokeOpacity: 0.92, widthWorld: 4 };
2297
+ }
2298
+ if (tool === "marker") {
2299
+ return { stroke: fallback, strokeOpacity: 0.45, widthWorld: 14 };
2300
+ }
2301
+ if (tool === "brush") {
2302
+ return { stroke: fallback, strokeOpacity: 0.85, widthWorld: 5 };
2303
+ }
2304
+ if (tool === "pencil") {
2305
+ return { stroke: fallback, strokeOpacity: 0.9, widthWorld: 2.5 };
2306
+ }
2307
+ return { stroke: fallback, strokeOpacity: 0.95, widthWorld: 3.5 };
2308
+ }
2309
+ function isRemoteFreehandTool(tool) {
2310
+ return tool === "draw" || tool === "marker" || tool === "pencil" || tool === "brush";
2311
+ }
2281
2312
  function pointsToSmoothPathD(points) {
2282
2313
  if (points.length < 2) return null;
2283
2314
  const d = smoothFreehandPointsToPathD(points);
@@ -2298,7 +2329,8 @@ function NativeInteractionOverlay({
2298
2329
  eraserTrail,
2299
2330
  laserTrail,
2300
2331
  eraserPreviewItems = [],
2301
- previewStrokeStyle
2332
+ previewStrokeStyle,
2333
+ remotePresence = []
2302
2334
  }) {
2303
2335
  const z = camera.zoom;
2304
2336
  const camTransform = skiaCameraTransform(z, camera.x, camera.y);
@@ -2692,6 +2724,136 @@ function NativeInteractionOverlay({
2692
2724
  )
2693
2725
  ] });
2694
2726
  }, [laserTrail, z]);
2727
+ const remotePresenceElements = react.useMemo(() => {
2728
+ if (remotePresence.length === 0) return null;
2729
+ const labelFont = reactNativeSkia.matchFont({ fontSize: REMOTE_LABEL_SCREEN_PX / z });
2730
+ const cursorSize = REMOTE_CURSOR_SCREEN_PX / z;
2731
+ const labelOffsetX = 14 / z;
2732
+ const labelOffsetY = 18 / z;
2733
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: remotePresence.map((peer) => {
2734
+ const color = peer.color ?? defaultPresenceColorForId(peer.id);
2735
+ const markup = peer.markupStroke;
2736
+ const cursor = peer.cursor;
2737
+ const camera2 = peer.camera;
2738
+ let strokeElement = null;
2739
+ if (markup && markup.points.length > 0) {
2740
+ const fallbackPaint = remoteStrokePaint(markup.tool, color);
2741
+ const paint = {
2742
+ stroke: markup.stroke ?? fallbackPaint.stroke,
2743
+ strokeOpacity: markup.strokeOpacity ?? fallbackPaint.strokeOpacity,
2744
+ widthWorld: markup.strokeWidth ?? fallbackPaint.widthWorld
2745
+ };
2746
+ if (markup.tool === "laser") {
2747
+ const d = markup.points.length >= 2 ? smoothFreehandPointsToPathD([...markup.points]) : null;
2748
+ if (d) {
2749
+ strokeElement = /* @__PURE__ */ jsxRuntime.jsx(
2750
+ reactNativeSkia.Path,
2751
+ {
2752
+ path: d,
2753
+ color: colorWithOpacity(paint.stroke, paint.strokeOpacity),
2754
+ style: "stroke",
2755
+ strokeWidth: Math.max(paint.widthWorld, OVERLAY_STROKE_PX) / z,
2756
+ strokeCap: "round",
2757
+ strokeJoin: "round",
2758
+ antiAlias: true
2759
+ }
2760
+ );
2761
+ }
2762
+ }
2763
+ if (!strokeElement && isRemoteFreehandTool(markup.tool)) {
2764
+ const payload = computeFreehandSvgPayload(
2765
+ markup.points.map((point) => ({ x: point.x, y: point.y })),
2766
+ {
2767
+ stroke: paint.stroke,
2768
+ strokeWidth: paint.widthWorld,
2769
+ strokeOpacity: paint.strokeOpacity
2770
+ },
2771
+ markup.tool,
2772
+ markup.points.length === 2
2773
+ );
2774
+ if (payload?.kind === "circle") {
2775
+ strokeElement = /* @__PURE__ */ jsxRuntime.jsx(
2776
+ reactNativeSkia.Circle,
2777
+ {
2778
+ cx: payload.cx,
2779
+ cy: payload.cy,
2780
+ r: payload.r,
2781
+ color: colorWithOpacity(payload.fill, payload.fillOpacity),
2782
+ style: "fill",
2783
+ antiAlias: true
2784
+ }
2785
+ );
2786
+ }
2787
+ if (payload?.kind === "fillPath") {
2788
+ strokeElement = /* @__PURE__ */ jsxRuntime.jsx(
2789
+ reactNativeSkia.Path,
2790
+ {
2791
+ path: payload.d,
2792
+ color: colorWithOpacity(payload.fill, payload.fillOpacity),
2793
+ style: "fill",
2794
+ fillType: "winding",
2795
+ antiAlias: true
2796
+ }
2797
+ );
2798
+ }
2799
+ if (payload?.kind === "strokePath") {
2800
+ strokeElement = /* @__PURE__ */ jsxRuntime.jsx(
2801
+ reactNativeSkia.Path,
2802
+ {
2803
+ path: payload.d,
2804
+ color: colorWithOpacity(payload.stroke, payload.strokeOpacity),
2805
+ style: "stroke",
2806
+ strokeWidth: payload.strokeWidth,
2807
+ strokeCap: "round",
2808
+ strokeJoin: "round",
2809
+ antiAlias: true
2810
+ }
2811
+ );
2812
+ }
2813
+ }
2814
+ }
2815
+ const cameraElement = camera2 ? /* @__PURE__ */ jsxRuntime.jsx(
2816
+ reactNativeSkia.Rect,
2817
+ {
2818
+ x: -camera2.x / camera2.zoom,
2819
+ y: -camera2.y / camera2.zoom,
2820
+ width: camera2.viewportWidth / camera2.zoom,
2821
+ height: camera2.viewportHeight / camera2.zoom,
2822
+ color,
2823
+ style: "stroke",
2824
+ strokeWidth: overlayStrokeWorld,
2825
+ antiAlias: true,
2826
+ children: /* @__PURE__ */ jsxRuntime.jsx(reactNativeSkia.DashPathEffect, { intervals: [marqueeDashWorld, marqueeDashWorld] })
2827
+ }
2828
+ ) : null;
2829
+ const cursorElement = cursor ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2830
+ /* @__PURE__ */ jsxRuntime.jsx(
2831
+ reactNativeSkia.Path,
2832
+ {
2833
+ path: `M ${cursor.x} ${cursor.y} L ${cursor.x + cursorSize} ${cursor.y + cursorSize * 0.34} L ${cursor.x + cursorSize * 0.42} ${cursor.y + cursorSize * 0.44} L ${cursor.x + cursorSize * 0.58} ${cursor.y + cursorSize} Z`,
2834
+ color,
2835
+ style: "fill",
2836
+ antiAlias: true
2837
+ }
2838
+ ),
2839
+ peer.displayName ? /* @__PURE__ */ jsxRuntime.jsx(
2840
+ reactNativeSkia.Text,
2841
+ {
2842
+ x: cursor.x + labelOffsetX,
2843
+ y: cursor.y + labelOffsetY,
2844
+ text: peer.displayName,
2845
+ color,
2846
+ font: labelFont
2847
+ }
2848
+ ) : null
2849
+ ] }) : null;
2850
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactNativeSkia.Group, { children: [
2851
+ cameraElement,
2852
+ strokeElement,
2853
+ cursorElement
2854
+ ] }, peer.clientId ?? peer.id);
2855
+ }) });
2856
+ }, [remotePresence, z, overlayStrokeWorld, marqueeDashWorld]);
2695
2857
  if (width <= 0 || height <= 0) return null;
2696
2858
  return /* @__PURE__ */ jsxRuntime.jsx(
2697
2859
  reactNativeSkia.Canvas,
@@ -2709,6 +2871,7 @@ function NativeInteractionOverlay({
2709
2871
  laserTrailElements,
2710
2872
  eraserTrailElements,
2711
2873
  eraserPreviewElements,
2874
+ remotePresenceElements,
2712
2875
  selectionElements
2713
2876
  ] })
2714
2877
  }
@@ -4211,6 +4374,7 @@ function fitCameraToWorldRect(camera, viewportW, viewportH, worldRect, padding)
4211
4374
  var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4212
4375
  items,
4213
4376
  selectedIds = [],
4377
+ remotePresence = [],
4214
4378
  toolId = "hand",
4215
4379
  toolLocked = false,
4216
4380
  interactive = false,
@@ -4218,6 +4382,9 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4218
4382
  onItemsChange,
4219
4383
  onToolChangeRequest,
4220
4384
  onWorldPointerDown,
4385
+ onWorldPointerMove,
4386
+ onWorldPointerLeave,
4387
+ onPlacementPreviewChange,
4221
4388
  onCameraChange,
4222
4389
  customPlacement,
4223
4390
  customPlacements = [],
@@ -4235,6 +4402,12 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4235
4402
  onToolChangeRequestRef.current = onToolChangeRequest;
4236
4403
  const onWorldPointerDownRef = react.useRef(onWorldPointerDown);
4237
4404
  onWorldPointerDownRef.current = onWorldPointerDown;
4405
+ const onWorldPointerMoveRef = react.useRef(onWorldPointerMove);
4406
+ onWorldPointerMoveRef.current = onWorldPointerMove;
4407
+ const onWorldPointerLeaveRef = react.useRef(onWorldPointerLeave);
4408
+ onWorldPointerLeaveRef.current = onWorldPointerLeave;
4409
+ const onPlacementPreviewChangeRef = react.useRef(onPlacementPreviewChange);
4410
+ onPlacementPreviewChangeRef.current = onPlacementPreviewChange;
4238
4411
  const onCameraChangeRef = react.useRef(onCameraChange);
4239
4412
  onCameraChangeRef.current = onCameraChange;
4240
4413
  const onItemsChangeRef = react.useRef(onItemsChange);
@@ -4250,8 +4423,13 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4250
4423
  const selectedIdsRef = react.useRef(selectedIds);
4251
4424
  selectedIdsRef.current = selectedIds;
4252
4425
  const dragStateRef = react.useRef({ kind: "idle" });
4253
- const [placementPreview, setPlacementPreview] = react.useState(
4254
- null
4426
+ const [placementPreview, setPlacementPreviewState] = react.useState(null);
4427
+ const setRealtimePlacementPreview = react.useCallback(
4428
+ (nextPreview) => {
4429
+ setPlacementPreviewState(nextPreview);
4430
+ onPlacementPreviewChangeRef.current?.(nextPreview);
4431
+ },
4432
+ []
4255
4433
  );
4256
4434
  const [eraserTrail, setEraserTrail] = react.useState([]);
4257
4435
  const [laserTrail, setLaserTrail] = react.useState([]);
@@ -4345,6 +4523,16 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4345
4523
  },
4346
4524
  []
4347
4525
  );
4526
+ const notifyWorldPointerMove = react.useCallback(
4527
+ (point) => {
4528
+ const { worldX, worldY } = screenToWorld(point.x, point.y);
4529
+ onWorldPointerMoveRef.current?.({ x: worldX, y: worldY });
4530
+ },
4531
+ [screenToWorld]
4532
+ );
4533
+ const notifyWorldPointerLeave = react.useCallback(() => {
4534
+ onWorldPointerLeaveRef.current?.();
4535
+ }, []);
4348
4536
  const requestRender = react.useCallback(() => {
4349
4537
  setCameraTick((n) => n + 1);
4350
4538
  onCameraChangeRef.current?.();
@@ -4384,6 +4572,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4384
4572
  const cam = cameraRef.current;
4385
4573
  if (!cam) return;
4386
4574
  const { worldX, worldY } = screenToWorld(sx, sy);
4575
+ onWorldPointerMoveRef.current?.({ x: worldX, y: worldY });
4387
4576
  if (tool === "hand") {
4388
4577
  dragStateRef.current = { kind: "pan" };
4389
4578
  return;
@@ -4469,7 +4658,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4469
4658
  kind: "marquee",
4470
4659
  startWorld: { x: worldX, y: worldY }
4471
4660
  };
4472
- setPlacementPreview({
4661
+ setRealtimePlacementPreview({
4473
4662
  kind: "marquee",
4474
4663
  rect: { x: worldX, y: worldY, width: 0, height: 0 }
4475
4664
  });
@@ -4489,7 +4678,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4489
4678
  }
4490
4679
  setLaserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
4491
4680
  } else {
4492
- setPlacementPreview({
4681
+ setRealtimePlacementPreview({
4493
4682
  kind: "stroke",
4494
4683
  tool,
4495
4684
  points: [{ x: worldX, y: worldY }],
@@ -4522,7 +4711,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4522
4711
  startWorld: { x: worldX, y: worldY },
4523
4712
  startScreen: { x: sx, y: sy }
4524
4713
  };
4525
- setPlacementPreview(
4714
+ setRealtimePlacementPreview(
4526
4715
  placementPreviewForTool(
4527
4716
  tool,
4528
4717
  { x: worldX, y: worldY },
@@ -4547,7 +4736,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4547
4736
  startWorld: { x: worldX, y: worldY },
4548
4737
  startScreen: { x: sx, y: sy }
4549
4738
  };
4550
- setPlacementPreview({
4739
+ setRealtimePlacementPreview({
4551
4740
  kind: "rect",
4552
4741
  rect: { x: worldX, y: worldY, width: 0, height: 0 }
4553
4742
  });
@@ -4576,7 +4765,13 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4576
4765
  }
4577
4766
  dragStateRef.current = { kind: "pan" };
4578
4767
  },
4579
- [interactive, requestSelectToolAfterUse, screenToWorld, updateToolCursorPoint]
4768
+ [
4769
+ interactive,
4770
+ requestSelectToolAfterUse,
4771
+ screenToWorld,
4772
+ setRealtimePlacementPreview,
4773
+ updateToolCursorPoint
4774
+ ]
4580
4775
  );
4581
4776
  const applyDragMoveAtScreenPoint = react.useCallback(
4582
4777
  (point, pagePoint) => {
@@ -4584,6 +4779,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4584
4779
  if (!cam) return;
4585
4780
  updateToolCursorPoint(point);
4586
4781
  const { worldX, worldY } = screenToWorld(point.x, point.y);
4782
+ onWorldPointerMoveRef.current?.({ x: worldX, y: worldY });
4587
4783
  const st = dragStateRef.current;
4588
4784
  if (st.kind === "pan") {
4589
4785
  const current = pagePoint ?? point;
@@ -4616,7 +4812,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4616
4812
  }
4617
4813
  return;
4618
4814
  }
4619
- setPlacementPreview({
4815
+ setRealtimePlacementPreview({
4620
4816
  kind: "stroke",
4621
4817
  tool: st.tool,
4622
4818
  points: [...pts],
@@ -4678,7 +4874,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4678
4874
  width: Math.abs(b.x - a.x),
4679
4875
  height: Math.abs(b.y - a.y)
4680
4876
  };
4681
- setPlacementPreview({ kind: "marquee", rect });
4877
+ setRealtimePlacementPreview({ kind: "marquee", rect });
4682
4878
  return;
4683
4879
  }
4684
4880
  if (st.kind === "erase") {
@@ -4696,7 +4892,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4696
4892
  return;
4697
4893
  }
4698
4894
  if (st.kind === "place") {
4699
- setPlacementPreview(
4895
+ setRealtimePlacementPreview(
4700
4896
  placementPreviewForTool(st.tool, st.startWorld, {
4701
4897
  x: worldX,
4702
4898
  y: worldY
@@ -4705,14 +4901,19 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4705
4901
  return;
4706
4902
  }
4707
4903
  if (st.kind === "custom-place") {
4708
- setPlacementPreview({
4904
+ setRealtimePlacementPreview({
4709
4905
  kind: "rect",
4710
4906
  rect: rectFromCorners(st.startWorld, { x: worldX, y: worldY })
4711
4907
  });
4712
4908
  return;
4713
4909
  }
4714
4910
  },
4715
- [requestRender, screenToWorld, updateToolCursorPoint]
4911
+ [
4912
+ requestRender,
4913
+ screenToWorld,
4914
+ setRealtimePlacementPreview,
4915
+ updateToolCursorPoint
4916
+ ]
4716
4917
  );
4717
4918
  const finishDragAtScreenPoint = react.useCallback(
4718
4919
  (point) => {
@@ -4722,7 +4923,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4722
4923
  const st = dragStateRef.current;
4723
4924
  if (st.kind === "draw") {
4724
4925
  dragStateRef.current = { kind: "idle" };
4725
- setPlacementPreview(null);
4926
+ setRealtimePlacementPreview(null);
4726
4927
  if (st.tool === "laser") {
4727
4928
  if (laserClearTimerRef.current) {
4728
4929
  clearTimeout(laserClearTimerRef.current);
@@ -4760,7 +4961,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4760
4961
  }
4761
4962
  if (st.kind === "marquee") {
4762
4963
  dragStateRef.current = { kind: "idle" };
4763
- setPlacementPreview(null);
4964
+ setRealtimePlacementPreview(null);
4764
4965
  const cam = cameraRef.current;
4765
4966
  if (!cam) return;
4766
4967
  const { worldX, worldY } = screenToWorld(point.x, point.y);
@@ -4791,7 +4992,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4791
4992
  }
4792
4993
  if (st.kind === "place") {
4793
4994
  dragStateRef.current = { kind: "idle" };
4794
- setPlacementPreview(null);
4995
+ setRealtimePlacementPreview(null);
4795
4996
  const change = onItemsChangeRef.current;
4796
4997
  if (!change) return;
4797
4998
  const { worldX, worldY } = screenToWorld(point.x, point.y);
@@ -4848,7 +5049,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4848
5049
  }
4849
5050
  if (st.kind === "custom-place") {
4850
5051
  dragStateRef.current = { kind: "idle" };
4851
- setPlacementPreview(null);
5052
+ setRealtimePlacementPreview(null);
4852
5053
  const change = onItemsChangeRef.current;
4853
5054
  if (!change) return;
4854
5055
  const { worldX, worldY } = screenToWorld(point.x, point.y);
@@ -4923,7 +5124,12 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4923
5124
  }
4924
5125
  dragStateRef.current = { kind: "idle" };
4925
5126
  },
4926
- [requestSelectToolAfterUse, screenToWorld, updateToolCursorPoint]
5127
+ [
5128
+ requestSelectToolAfterUse,
5129
+ screenToWorld,
5130
+ setRealtimePlacementPreview,
5131
+ updateToolCursorPoint
5132
+ ]
4927
5133
  );
4928
5134
  const handlePointerDown = react.useCallback(
4929
5135
  (event) => {
@@ -4939,9 +5145,10 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4939
5145
  applyDragMoveAtScreenPoint(point, point);
4940
5146
  return;
4941
5147
  }
5148
+ notifyWorldPointerMove(point);
4942
5149
  updateToolCursorPoint(point);
4943
5150
  },
4944
- [applyDragMoveAtScreenPoint, updateToolCursorPoint]
5151
+ [applyDragMoveAtScreenPoint, notifyWorldPointerMove, updateToolCursorPoint]
4945
5152
  );
4946
5153
  const handlePointerUp = react.useCallback(
4947
5154
  (event) => {
@@ -4960,6 +5167,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4960
5167
  const sy = evt.nativeEvent.locationY;
4961
5168
  if (touches && touches.length >= 2) {
4962
5169
  hideToolCursor();
5170
+ notifyWorldPointerLeave();
4963
5171
  dragStateRef.current = { kind: "pan" };
4964
5172
  return;
4965
5173
  }
@@ -4975,6 +5183,7 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
4975
5183
  const pageY = evt.nativeEvent.pageY;
4976
5184
  if (touches && touches.length >= 2) {
4977
5185
  hideToolCursor();
5186
+ notifyWorldPointerLeave();
4978
5187
  const t0 = touches[0];
4979
5188
  const t1 = touches[1];
4980
5189
  if (t0 && t1) {
@@ -5006,8 +5215,9 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5006
5215
  lastPinchDist.current = null;
5007
5216
  lastPanPoint.current = null;
5008
5217
  hideToolCursor();
5218
+ notifyWorldPointerLeave();
5009
5219
  dragStateRef.current = { kind: "idle" };
5010
- setPlacementPreview(null);
5220
+ setRealtimePlacementPreview(null);
5011
5221
  setLaserTrail([]);
5012
5222
  setEraserTrail([]);
5013
5223
  setEraserPreviewIds([]);
@@ -5019,7 +5229,9 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5019
5229
  beginDragAtScreenPoint,
5020
5230
  finishDragAtScreenPoint,
5021
5231
  requestRender,
5022
- hideToolCursor
5232
+ hideToolCursor,
5233
+ notifyWorldPointerLeave,
5234
+ setRealtimePlacementPreview
5023
5235
  ]
5024
5236
  );
5025
5237
  react.useImperativeHandle(
@@ -5053,7 +5265,10 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5053
5265
  onPointerMove: handlePointerMove,
5054
5266
  onPointerUp: handlePointerUp,
5055
5267
  onPointerEnter: handlePointerMove,
5056
- onPointerLeave: hideToolCursor,
5268
+ onPointerLeave: () => {
5269
+ hideToolCursor();
5270
+ notifyWorldPointerLeave();
5271
+ },
5057
5272
  ...panResponder.panHandlers,
5058
5273
  children: size.width > 0 && size.height > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5059
5274
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -5079,7 +5294,8 @@ var NativeVectorViewport = react.forwardRef(function NativeVectorViewport2({
5079
5294
  eraserPreviewItems: items.filter(
5080
5295
  (it) => eraserPreviewIds.includes(it.id)
5081
5296
  ),
5082
- previewStrokeStyle: strokeStyleState
5297
+ previewStrokeStyle: strokeStyleState,
5298
+ remotePresence
5083
5299
  }
5084
5300
  ),
5085
5301
  interactive && showStyleInspector && activeStyleToolId ? /* @__PURE__ */ jsxRuntime.jsx(