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.
package/dist/native.d.cts CHANGED
@@ -2,6 +2,7 @@ import { C as Camera2D, S as StrokeStyle } from './shape-builders-CKEMjivV.cjs';
2
2
  export { o as createFreehandStrokeItem, q as createImageItem, t as createShapeId } from './shape-builders-CKEMjivV.cjs';
3
3
  import { V as VectorSceneItem, R as Rect } from './types-BCCvY6ie.cjs';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
+ import { R as RemotePresencePeer } from './types-BQUbxMgz.cjs';
5
6
  import { StyleProp, ViewStyle, TextStyle } from 'react-native';
6
7
  import * as react from 'react';
7
8
  import { ReactNode } from 'react';
@@ -58,8 +59,9 @@ type NativeInteractionOverlayProps = {
58
59
  readonly laserTrail?: readonly TimedTrailPoint[];
59
60
  readonly eraserPreviewItems?: readonly VectorSceneItem[];
60
61
  readonly previewStrokeStyle?: StrokeStyle;
62
+ readonly remotePresence?: readonly RemotePresencePeer[];
61
63
  };
62
- declare function NativeInteractionOverlay({ camera, width, height, selectedItems, showResizeHandles, placementPreview, eraserTrail, laserTrail, eraserPreviewItems, previewStrokeStyle, }: NativeInteractionOverlayProps): react_jsx_runtime.JSX.Element | null;
64
+ declare function NativeInteractionOverlay({ camera, width, height, selectedItems, showResizeHandles, placementPreview, eraserTrail, laserTrail, eraserPreviewItems, previewStrokeStyle, remotePresence, }: NativeInteractionOverlayProps): react_jsx_runtime.JSX.Element | null;
63
65
 
64
66
  type NativeSceneRendererProps = {
65
67
  readonly items: readonly VectorSceneItem[];
@@ -214,6 +216,7 @@ type NativeWorldPointerDownDetail = {
214
216
  type NativeVectorViewportProps = {
215
217
  readonly items: readonly VectorSceneItem[];
216
218
  readonly selectedIds?: readonly string[];
219
+ readonly remotePresence?: readonly RemotePresencePeer[];
217
220
  readonly toolId?: string;
218
221
  readonly toolLocked?: boolean;
219
222
  readonly interactive?: boolean;
@@ -221,6 +224,12 @@ type NativeVectorViewportProps = {
221
224
  readonly onItemsChange?: (items: VectorSceneItem[]) => void;
222
225
  readonly onToolChangeRequest?: (toolId: string) => void;
223
226
  readonly onWorldPointerDown?: (detail: NativeWorldPointerDownDetail) => void;
227
+ readonly onWorldPointerMove?: (world: {
228
+ readonly x: number;
229
+ readonly y: number;
230
+ }) => void;
231
+ readonly onWorldPointerLeave?: () => void;
232
+ readonly onPlacementPreviewChange?: (preview: PlacementPreview | null) => void;
224
233
  readonly onCameraChange?: () => void;
225
234
  readonly customPlacement?: NativeCustomShapePlacementOptions;
226
235
  readonly customPlacements?: readonly NativeCustomShapePlacementOptions[];
package/dist/native.d.ts CHANGED
@@ -2,6 +2,7 @@ import { C as Camera2D, S as StrokeStyle } from './shape-builders-Cyh8zvDG.js';
2
2
  export { o as createFreehandStrokeItem, q as createImageItem, t as createShapeId } from './shape-builders-Cyh8zvDG.js';
3
3
  import { V as VectorSceneItem, R as Rect } from './types-BCCvY6ie.js';
4
4
  import * as react_jsx_runtime from 'react/jsx-runtime';
5
+ import { R as RemotePresencePeer } from './types-B82WiQQh.js';
5
6
  import { StyleProp, ViewStyle, TextStyle } from 'react-native';
6
7
  import * as react from 'react';
7
8
  import { ReactNode } from 'react';
@@ -58,8 +59,9 @@ type NativeInteractionOverlayProps = {
58
59
  readonly laserTrail?: readonly TimedTrailPoint[];
59
60
  readonly eraserPreviewItems?: readonly VectorSceneItem[];
60
61
  readonly previewStrokeStyle?: StrokeStyle;
62
+ readonly remotePresence?: readonly RemotePresencePeer[];
61
63
  };
62
- declare function NativeInteractionOverlay({ camera, width, height, selectedItems, showResizeHandles, placementPreview, eraserTrail, laserTrail, eraserPreviewItems, previewStrokeStyle, }: NativeInteractionOverlayProps): react_jsx_runtime.JSX.Element | null;
64
+ declare function NativeInteractionOverlay({ camera, width, height, selectedItems, showResizeHandles, placementPreview, eraserTrail, laserTrail, eraserPreviewItems, previewStrokeStyle, remotePresence, }: NativeInteractionOverlayProps): react_jsx_runtime.JSX.Element | null;
63
65
 
64
66
  type NativeSceneRendererProps = {
65
67
  readonly items: readonly VectorSceneItem[];
@@ -214,6 +216,7 @@ type NativeWorldPointerDownDetail = {
214
216
  type NativeVectorViewportProps = {
215
217
  readonly items: readonly VectorSceneItem[];
216
218
  readonly selectedIds?: readonly string[];
219
+ readonly remotePresence?: readonly RemotePresencePeer[];
217
220
  readonly toolId?: string;
218
221
  readonly toolLocked?: boolean;
219
222
  readonly interactive?: boolean;
@@ -221,6 +224,12 @@ type NativeVectorViewportProps = {
221
224
  readonly onItemsChange?: (items: VectorSceneItem[]) => void;
222
225
  readonly onToolChangeRequest?: (toolId: string) => void;
223
226
  readonly onWorldPointerDown?: (detail: NativeWorldPointerDownDetail) => void;
227
+ readonly onWorldPointerMove?: (world: {
228
+ readonly x: number;
229
+ readonly y: number;
230
+ }) => void;
231
+ readonly onWorldPointerLeave?: () => void;
232
+ readonly onPlacementPreviewChange?: (preview: PlacementPreview | null) => void;
224
233
  readonly onCameraChange?: () => void;
225
234
  readonly customPlacement?: NativeCustomShapePlacementOptions;
226
235
  readonly customPlacements?: readonly NativeCustomShapePlacementOptions[];
package/dist/native.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import getStroke from 'perfect-freehand';
2
2
  import { Group, Canvas, Rect, Circle, Path, RoundedRect, Oval, DashPathEffect, Line, vec, matchFont, Text, useImage, Image } from '@shopify/react-native-skia';
3
- import { memo, forwardRef, useState, useRef, useEffect, useCallback, useMemo, useImperativeHandle } from 'react';
3
+ import { memo, forwardRef, useState, useRef, useCallback, useEffect, useMemo, useImperativeHandle } from 'react';
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
 
@@ -1354,6 +1354,17 @@ function computeResizeBoundsFixedAspect(bounds, handle, currentWorld, aspect) {
1354
1354
  }
1355
1355
  }
1356
1356
 
1357
+ // src/react/presence/peer-color.ts
1358
+ function defaultPresenceColorForId(id) {
1359
+ let h = 2166136261;
1360
+ for (let i = 0; i < id.length; i++) {
1361
+ h ^= id.charCodeAt(i);
1362
+ h = Math.imul(h, 16777619);
1363
+ }
1364
+ const hue = (h >>> 0) % 360;
1365
+ return `hsl(${hue} 72% 42%)`;
1366
+ }
1367
+
1357
1368
  // src/scene/freehand-path.ts
1358
1369
  function smoothFreehandPointsToPathD(points) {
1359
1370
  const n = points.length;
@@ -2272,6 +2283,26 @@ var HANDLE_ORDER = ["nw", "n", "ne", "e", "se", "s", "sw", "w"];
2272
2283
  var ERASER_PREVIEW_OPACITY = 0.3;
2273
2284
  var OVERLAY_STROKE_PX = 1.25;
2274
2285
  var MARQUEE_DASH_PX = 4;
2286
+ var REMOTE_CURSOR_SCREEN_PX = 22;
2287
+ var REMOTE_LABEL_SCREEN_PX = 12;
2288
+ function remoteStrokePaint(tool, fallback) {
2289
+ if (tool === "laser") {
2290
+ return { stroke: LASER_TINT, strokeOpacity: 0.92, widthWorld: 4 };
2291
+ }
2292
+ if (tool === "marker") {
2293
+ return { stroke: fallback, strokeOpacity: 0.45, widthWorld: 14 };
2294
+ }
2295
+ if (tool === "brush") {
2296
+ return { stroke: fallback, strokeOpacity: 0.85, widthWorld: 5 };
2297
+ }
2298
+ if (tool === "pencil") {
2299
+ return { stroke: fallback, strokeOpacity: 0.9, widthWorld: 2.5 };
2300
+ }
2301
+ return { stroke: fallback, strokeOpacity: 0.95, widthWorld: 3.5 };
2302
+ }
2303
+ function isRemoteFreehandTool(tool) {
2304
+ return tool === "draw" || tool === "marker" || tool === "pencil" || tool === "brush";
2305
+ }
2275
2306
  function pointsToSmoothPathD(points) {
2276
2307
  if (points.length < 2) return null;
2277
2308
  const d = smoothFreehandPointsToPathD(points);
@@ -2292,7 +2323,8 @@ function NativeInteractionOverlay({
2292
2323
  eraserTrail,
2293
2324
  laserTrail,
2294
2325
  eraserPreviewItems = [],
2295
- previewStrokeStyle
2326
+ previewStrokeStyle,
2327
+ remotePresence = []
2296
2328
  }) {
2297
2329
  const z = camera.zoom;
2298
2330
  const camTransform = skiaCameraTransform(z, camera.x, camera.y);
@@ -2686,6 +2718,136 @@ function NativeInteractionOverlay({
2686
2718
  )
2687
2719
  ] });
2688
2720
  }, [laserTrail, z]);
2721
+ const remotePresenceElements = useMemo(() => {
2722
+ if (remotePresence.length === 0) return null;
2723
+ const labelFont = matchFont({ fontSize: REMOTE_LABEL_SCREEN_PX / z });
2724
+ const cursorSize = REMOTE_CURSOR_SCREEN_PX / z;
2725
+ const labelOffsetX = 14 / z;
2726
+ const labelOffsetY = 18 / z;
2727
+ return /* @__PURE__ */ jsx(Fragment, { children: remotePresence.map((peer) => {
2728
+ const color = peer.color ?? defaultPresenceColorForId(peer.id);
2729
+ const markup = peer.markupStroke;
2730
+ const cursor = peer.cursor;
2731
+ const camera2 = peer.camera;
2732
+ let strokeElement = null;
2733
+ if (markup && markup.points.length > 0) {
2734
+ const fallbackPaint = remoteStrokePaint(markup.tool, color);
2735
+ const paint = {
2736
+ stroke: markup.stroke ?? fallbackPaint.stroke,
2737
+ strokeOpacity: markup.strokeOpacity ?? fallbackPaint.strokeOpacity,
2738
+ widthWorld: markup.strokeWidth ?? fallbackPaint.widthWorld
2739
+ };
2740
+ if (markup.tool === "laser") {
2741
+ const d = markup.points.length >= 2 ? smoothFreehandPointsToPathD([...markup.points]) : null;
2742
+ if (d) {
2743
+ strokeElement = /* @__PURE__ */ jsx(
2744
+ Path,
2745
+ {
2746
+ path: d,
2747
+ color: colorWithOpacity(paint.stroke, paint.strokeOpacity),
2748
+ style: "stroke",
2749
+ strokeWidth: Math.max(paint.widthWorld, OVERLAY_STROKE_PX) / z,
2750
+ strokeCap: "round",
2751
+ strokeJoin: "round",
2752
+ antiAlias: true
2753
+ }
2754
+ );
2755
+ }
2756
+ }
2757
+ if (!strokeElement && isRemoteFreehandTool(markup.tool)) {
2758
+ const payload = computeFreehandSvgPayload(
2759
+ markup.points.map((point) => ({ x: point.x, y: point.y })),
2760
+ {
2761
+ stroke: paint.stroke,
2762
+ strokeWidth: paint.widthWorld,
2763
+ strokeOpacity: paint.strokeOpacity
2764
+ },
2765
+ markup.tool,
2766
+ markup.points.length === 2
2767
+ );
2768
+ if (payload?.kind === "circle") {
2769
+ strokeElement = /* @__PURE__ */ jsx(
2770
+ Circle,
2771
+ {
2772
+ cx: payload.cx,
2773
+ cy: payload.cy,
2774
+ r: payload.r,
2775
+ color: colorWithOpacity(payload.fill, payload.fillOpacity),
2776
+ style: "fill",
2777
+ antiAlias: true
2778
+ }
2779
+ );
2780
+ }
2781
+ if (payload?.kind === "fillPath") {
2782
+ strokeElement = /* @__PURE__ */ jsx(
2783
+ Path,
2784
+ {
2785
+ path: payload.d,
2786
+ color: colorWithOpacity(payload.fill, payload.fillOpacity),
2787
+ style: "fill",
2788
+ fillType: "winding",
2789
+ antiAlias: true
2790
+ }
2791
+ );
2792
+ }
2793
+ if (payload?.kind === "strokePath") {
2794
+ strokeElement = /* @__PURE__ */ jsx(
2795
+ Path,
2796
+ {
2797
+ path: payload.d,
2798
+ color: colorWithOpacity(payload.stroke, payload.strokeOpacity),
2799
+ style: "stroke",
2800
+ strokeWidth: payload.strokeWidth,
2801
+ strokeCap: "round",
2802
+ strokeJoin: "round",
2803
+ antiAlias: true
2804
+ }
2805
+ );
2806
+ }
2807
+ }
2808
+ }
2809
+ const cameraElement = camera2 ? /* @__PURE__ */ jsx(
2810
+ Rect,
2811
+ {
2812
+ x: -camera2.x / camera2.zoom,
2813
+ y: -camera2.y / camera2.zoom,
2814
+ width: camera2.viewportWidth / camera2.zoom,
2815
+ height: camera2.viewportHeight / camera2.zoom,
2816
+ color,
2817
+ style: "stroke",
2818
+ strokeWidth: overlayStrokeWorld,
2819
+ antiAlias: true,
2820
+ children: /* @__PURE__ */ jsx(DashPathEffect, { intervals: [marqueeDashWorld, marqueeDashWorld] })
2821
+ }
2822
+ ) : null;
2823
+ const cursorElement = cursor ? /* @__PURE__ */ jsxs(Fragment, { children: [
2824
+ /* @__PURE__ */ jsx(
2825
+ Path,
2826
+ {
2827
+ 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`,
2828
+ color,
2829
+ style: "fill",
2830
+ antiAlias: true
2831
+ }
2832
+ ),
2833
+ peer.displayName ? /* @__PURE__ */ jsx(
2834
+ Text,
2835
+ {
2836
+ x: cursor.x + labelOffsetX,
2837
+ y: cursor.y + labelOffsetY,
2838
+ text: peer.displayName,
2839
+ color,
2840
+ font: labelFont
2841
+ }
2842
+ ) : null
2843
+ ] }) : null;
2844
+ return /* @__PURE__ */ jsxs(Group, { children: [
2845
+ cameraElement,
2846
+ strokeElement,
2847
+ cursorElement
2848
+ ] }, peer.clientId ?? peer.id);
2849
+ }) });
2850
+ }, [remotePresence, z, overlayStrokeWorld, marqueeDashWorld]);
2689
2851
  if (width <= 0 || height <= 0) return null;
2690
2852
  return /* @__PURE__ */ jsx(
2691
2853
  Canvas,
@@ -2703,6 +2865,7 @@ function NativeInteractionOverlay({
2703
2865
  laserTrailElements,
2704
2866
  eraserTrailElements,
2705
2867
  eraserPreviewElements,
2868
+ remotePresenceElements,
2706
2869
  selectionElements
2707
2870
  ] })
2708
2871
  }
@@ -4205,6 +4368,7 @@ function fitCameraToWorldRect(camera, viewportW, viewportH, worldRect, padding)
4205
4368
  var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4206
4369
  items,
4207
4370
  selectedIds = [],
4371
+ remotePresence = [],
4208
4372
  toolId = "hand",
4209
4373
  toolLocked = false,
4210
4374
  interactive = false,
@@ -4212,6 +4376,9 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4212
4376
  onItemsChange,
4213
4377
  onToolChangeRequest,
4214
4378
  onWorldPointerDown,
4379
+ onWorldPointerMove,
4380
+ onWorldPointerLeave,
4381
+ onPlacementPreviewChange,
4215
4382
  onCameraChange,
4216
4383
  customPlacement,
4217
4384
  customPlacements = [],
@@ -4229,6 +4396,12 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4229
4396
  onToolChangeRequestRef.current = onToolChangeRequest;
4230
4397
  const onWorldPointerDownRef = useRef(onWorldPointerDown);
4231
4398
  onWorldPointerDownRef.current = onWorldPointerDown;
4399
+ const onWorldPointerMoveRef = useRef(onWorldPointerMove);
4400
+ onWorldPointerMoveRef.current = onWorldPointerMove;
4401
+ const onWorldPointerLeaveRef = useRef(onWorldPointerLeave);
4402
+ onWorldPointerLeaveRef.current = onWorldPointerLeave;
4403
+ const onPlacementPreviewChangeRef = useRef(onPlacementPreviewChange);
4404
+ onPlacementPreviewChangeRef.current = onPlacementPreviewChange;
4232
4405
  const onCameraChangeRef = useRef(onCameraChange);
4233
4406
  onCameraChangeRef.current = onCameraChange;
4234
4407
  const onItemsChangeRef = useRef(onItemsChange);
@@ -4244,8 +4417,13 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4244
4417
  const selectedIdsRef = useRef(selectedIds);
4245
4418
  selectedIdsRef.current = selectedIds;
4246
4419
  const dragStateRef = useRef({ kind: "idle" });
4247
- const [placementPreview, setPlacementPreview] = useState(
4248
- null
4420
+ const [placementPreview, setPlacementPreviewState] = useState(null);
4421
+ const setRealtimePlacementPreview = useCallback(
4422
+ (nextPreview) => {
4423
+ setPlacementPreviewState(nextPreview);
4424
+ onPlacementPreviewChangeRef.current?.(nextPreview);
4425
+ },
4426
+ []
4249
4427
  );
4250
4428
  const [eraserTrail, setEraserTrail] = useState([]);
4251
4429
  const [laserTrail, setLaserTrail] = useState([]);
@@ -4339,6 +4517,16 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4339
4517
  },
4340
4518
  []
4341
4519
  );
4520
+ const notifyWorldPointerMove = useCallback(
4521
+ (point) => {
4522
+ const { worldX, worldY } = screenToWorld(point.x, point.y);
4523
+ onWorldPointerMoveRef.current?.({ x: worldX, y: worldY });
4524
+ },
4525
+ [screenToWorld]
4526
+ );
4527
+ const notifyWorldPointerLeave = useCallback(() => {
4528
+ onWorldPointerLeaveRef.current?.();
4529
+ }, []);
4342
4530
  const requestRender = useCallback(() => {
4343
4531
  setCameraTick((n) => n + 1);
4344
4532
  onCameraChangeRef.current?.();
@@ -4378,6 +4566,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4378
4566
  const cam = cameraRef.current;
4379
4567
  if (!cam) return;
4380
4568
  const { worldX, worldY } = screenToWorld(sx, sy);
4569
+ onWorldPointerMoveRef.current?.({ x: worldX, y: worldY });
4381
4570
  if (tool === "hand") {
4382
4571
  dragStateRef.current = { kind: "pan" };
4383
4572
  return;
@@ -4463,7 +4652,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4463
4652
  kind: "marquee",
4464
4653
  startWorld: { x: worldX, y: worldY }
4465
4654
  };
4466
- setPlacementPreview({
4655
+ setRealtimePlacementPreview({
4467
4656
  kind: "marquee",
4468
4657
  rect: { x: worldX, y: worldY, width: 0, height: 0 }
4469
4658
  });
@@ -4483,7 +4672,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4483
4672
  }
4484
4673
  setLaserTrail([{ x: worldX, y: worldY, t: Date.now() }]);
4485
4674
  } else {
4486
- setPlacementPreview({
4675
+ setRealtimePlacementPreview({
4487
4676
  kind: "stroke",
4488
4677
  tool,
4489
4678
  points: [{ x: worldX, y: worldY }],
@@ -4516,7 +4705,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4516
4705
  startWorld: { x: worldX, y: worldY },
4517
4706
  startScreen: { x: sx, y: sy }
4518
4707
  };
4519
- setPlacementPreview(
4708
+ setRealtimePlacementPreview(
4520
4709
  placementPreviewForTool(
4521
4710
  tool,
4522
4711
  { x: worldX, y: worldY },
@@ -4541,7 +4730,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4541
4730
  startWorld: { x: worldX, y: worldY },
4542
4731
  startScreen: { x: sx, y: sy }
4543
4732
  };
4544
- setPlacementPreview({
4733
+ setRealtimePlacementPreview({
4545
4734
  kind: "rect",
4546
4735
  rect: { x: worldX, y: worldY, width: 0, height: 0 }
4547
4736
  });
@@ -4570,7 +4759,13 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4570
4759
  }
4571
4760
  dragStateRef.current = { kind: "pan" };
4572
4761
  },
4573
- [interactive, requestSelectToolAfterUse, screenToWorld, updateToolCursorPoint]
4762
+ [
4763
+ interactive,
4764
+ requestSelectToolAfterUse,
4765
+ screenToWorld,
4766
+ setRealtimePlacementPreview,
4767
+ updateToolCursorPoint
4768
+ ]
4574
4769
  );
4575
4770
  const applyDragMoveAtScreenPoint = useCallback(
4576
4771
  (point, pagePoint) => {
@@ -4578,6 +4773,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4578
4773
  if (!cam) return;
4579
4774
  updateToolCursorPoint(point);
4580
4775
  const { worldX, worldY } = screenToWorld(point.x, point.y);
4776
+ onWorldPointerMoveRef.current?.({ x: worldX, y: worldY });
4581
4777
  const st = dragStateRef.current;
4582
4778
  if (st.kind === "pan") {
4583
4779
  const current = pagePoint ?? point;
@@ -4610,7 +4806,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4610
4806
  }
4611
4807
  return;
4612
4808
  }
4613
- setPlacementPreview({
4809
+ setRealtimePlacementPreview({
4614
4810
  kind: "stroke",
4615
4811
  tool: st.tool,
4616
4812
  points: [...pts],
@@ -4672,7 +4868,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4672
4868
  width: Math.abs(b.x - a.x),
4673
4869
  height: Math.abs(b.y - a.y)
4674
4870
  };
4675
- setPlacementPreview({ kind: "marquee", rect });
4871
+ setRealtimePlacementPreview({ kind: "marquee", rect });
4676
4872
  return;
4677
4873
  }
4678
4874
  if (st.kind === "erase") {
@@ -4690,7 +4886,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4690
4886
  return;
4691
4887
  }
4692
4888
  if (st.kind === "place") {
4693
- setPlacementPreview(
4889
+ setRealtimePlacementPreview(
4694
4890
  placementPreviewForTool(st.tool, st.startWorld, {
4695
4891
  x: worldX,
4696
4892
  y: worldY
@@ -4699,14 +4895,19 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4699
4895
  return;
4700
4896
  }
4701
4897
  if (st.kind === "custom-place") {
4702
- setPlacementPreview({
4898
+ setRealtimePlacementPreview({
4703
4899
  kind: "rect",
4704
4900
  rect: rectFromCorners(st.startWorld, { x: worldX, y: worldY })
4705
4901
  });
4706
4902
  return;
4707
4903
  }
4708
4904
  },
4709
- [requestRender, screenToWorld, updateToolCursorPoint]
4905
+ [
4906
+ requestRender,
4907
+ screenToWorld,
4908
+ setRealtimePlacementPreview,
4909
+ updateToolCursorPoint
4910
+ ]
4710
4911
  );
4711
4912
  const finishDragAtScreenPoint = useCallback(
4712
4913
  (point) => {
@@ -4716,7 +4917,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4716
4917
  const st = dragStateRef.current;
4717
4918
  if (st.kind === "draw") {
4718
4919
  dragStateRef.current = { kind: "idle" };
4719
- setPlacementPreview(null);
4920
+ setRealtimePlacementPreview(null);
4720
4921
  if (st.tool === "laser") {
4721
4922
  if (laserClearTimerRef.current) {
4722
4923
  clearTimeout(laserClearTimerRef.current);
@@ -4754,7 +4955,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4754
4955
  }
4755
4956
  if (st.kind === "marquee") {
4756
4957
  dragStateRef.current = { kind: "idle" };
4757
- setPlacementPreview(null);
4958
+ setRealtimePlacementPreview(null);
4758
4959
  const cam = cameraRef.current;
4759
4960
  if (!cam) return;
4760
4961
  const { worldX, worldY } = screenToWorld(point.x, point.y);
@@ -4785,7 +4986,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4785
4986
  }
4786
4987
  if (st.kind === "place") {
4787
4988
  dragStateRef.current = { kind: "idle" };
4788
- setPlacementPreview(null);
4989
+ setRealtimePlacementPreview(null);
4789
4990
  const change = onItemsChangeRef.current;
4790
4991
  if (!change) return;
4791
4992
  const { worldX, worldY } = screenToWorld(point.x, point.y);
@@ -4842,7 +5043,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4842
5043
  }
4843
5044
  if (st.kind === "custom-place") {
4844
5045
  dragStateRef.current = { kind: "idle" };
4845
- setPlacementPreview(null);
5046
+ setRealtimePlacementPreview(null);
4846
5047
  const change = onItemsChangeRef.current;
4847
5048
  if (!change) return;
4848
5049
  const { worldX, worldY } = screenToWorld(point.x, point.y);
@@ -4917,7 +5118,12 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4917
5118
  }
4918
5119
  dragStateRef.current = { kind: "idle" };
4919
5120
  },
4920
- [requestSelectToolAfterUse, screenToWorld, updateToolCursorPoint]
5121
+ [
5122
+ requestSelectToolAfterUse,
5123
+ screenToWorld,
5124
+ setRealtimePlacementPreview,
5125
+ updateToolCursorPoint
5126
+ ]
4921
5127
  );
4922
5128
  const handlePointerDown = useCallback(
4923
5129
  (event) => {
@@ -4933,9 +5139,10 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4933
5139
  applyDragMoveAtScreenPoint(point, point);
4934
5140
  return;
4935
5141
  }
5142
+ notifyWorldPointerMove(point);
4936
5143
  updateToolCursorPoint(point);
4937
5144
  },
4938
- [applyDragMoveAtScreenPoint, updateToolCursorPoint]
5145
+ [applyDragMoveAtScreenPoint, notifyWorldPointerMove, updateToolCursorPoint]
4939
5146
  );
4940
5147
  const handlePointerUp = useCallback(
4941
5148
  (event) => {
@@ -4954,6 +5161,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4954
5161
  const sy = evt.nativeEvent.locationY;
4955
5162
  if (touches && touches.length >= 2) {
4956
5163
  hideToolCursor();
5164
+ notifyWorldPointerLeave();
4957
5165
  dragStateRef.current = { kind: "pan" };
4958
5166
  return;
4959
5167
  }
@@ -4969,6 +5177,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4969
5177
  const pageY = evt.nativeEvent.pageY;
4970
5178
  if (touches && touches.length >= 2) {
4971
5179
  hideToolCursor();
5180
+ notifyWorldPointerLeave();
4972
5181
  const t0 = touches[0];
4973
5182
  const t1 = touches[1];
4974
5183
  if (t0 && t1) {
@@ -5000,8 +5209,9 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5000
5209
  lastPinchDist.current = null;
5001
5210
  lastPanPoint.current = null;
5002
5211
  hideToolCursor();
5212
+ notifyWorldPointerLeave();
5003
5213
  dragStateRef.current = { kind: "idle" };
5004
- setPlacementPreview(null);
5214
+ setRealtimePlacementPreview(null);
5005
5215
  setLaserTrail([]);
5006
5216
  setEraserTrail([]);
5007
5217
  setEraserPreviewIds([]);
@@ -5013,7 +5223,9 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5013
5223
  beginDragAtScreenPoint,
5014
5224
  finishDragAtScreenPoint,
5015
5225
  requestRender,
5016
- hideToolCursor
5226
+ hideToolCursor,
5227
+ notifyWorldPointerLeave,
5228
+ setRealtimePlacementPreview
5017
5229
  ]
5018
5230
  );
5019
5231
  useImperativeHandle(
@@ -5047,7 +5259,10 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5047
5259
  onPointerMove: handlePointerMove,
5048
5260
  onPointerUp: handlePointerUp,
5049
5261
  onPointerEnter: handlePointerMove,
5050
- onPointerLeave: hideToolCursor,
5262
+ onPointerLeave: () => {
5263
+ hideToolCursor();
5264
+ notifyWorldPointerLeave();
5265
+ },
5051
5266
  ...panResponder.panHandlers,
5052
5267
  children: size.width > 0 && size.height > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
5053
5268
  /* @__PURE__ */ jsx(
@@ -5073,7 +5288,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5073
5288
  eraserPreviewItems: items.filter(
5074
5289
  (it) => eraserPreviewIds.includes(it.id)
5075
5290
  ),
5076
- previewStrokeStyle: strokeStyleState
5291
+ previewStrokeStyle: strokeStyleState,
5292
+ remotePresence
5077
5293
  }
5078
5294
  ),
5079
5295
  interactive && showStyleInspector && activeStyleToolId ? /* @__PURE__ */ jsx(