canvu-react 0.4.40 → 0.4.42

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
@@ -189,6 +189,19 @@ declare const DEFAULT_NATIVE_VECTOR_TOOLS: readonly NativeVectorToolDefinition[]
189
189
  */
190
190
  declare function NativeVectorToolbar({ value, onChange, tools, overflowToolIds, overflowMenuAccessibilityLabel, disabled, disabledToolIds, showToolLockToggle, toolLocked, onToolLockedChange, density, accessibilityLabel, style, contentContainerStyle, overflowPanelStyle, toolButtonStyle, activeToolButtonStyle, toolLabelStyle, activeToolLabelStyle, renderToolIcon, renderToolLockIcon, renderOverflowIcon, renderOverflowChevronIcon, renderToolButton, }: NativeVectorToolbarProps): react_jsx_runtime.JSX.Element;
191
191
 
192
+ type NativeRemotePresenceHitTarget = "cursor" | "label";
193
+ type NativeRemotePresenceHit = {
194
+ readonly peer: RemotePresencePeer;
195
+ readonly followId: string;
196
+ readonly target: NativeRemotePresenceHitTarget;
197
+ readonly screenX: number;
198
+ readonly screenY: number;
199
+ readonly worldX: number;
200
+ readonly worldY: number;
201
+ readonly clientId?: string;
202
+ readonly peerId?: string;
203
+ };
204
+
192
205
  type NativeCustomShapePlacementOptions = {
193
206
  readonly toolId: string;
194
207
  readonly createItem: (args: {
@@ -215,6 +228,15 @@ type NativeWorldPointerDownDetail = {
215
228
  readonly screenX: number;
216
229
  readonly screenY: number;
217
230
  };
231
+ /**
232
+ * Detail emitted when a native pointer/touch begins on a remote realtime
233
+ * participant label or cursor.
234
+ *
235
+ * Pass `followId` into your `followedPeerId` state for
236
+ * `useRealtimePeerFollow(...)`. For realtime-session peers this is the
237
+ * connection `clientId`, matching the session peer `id`.
238
+ */
239
+ type NativeRemotePresencePressDetail = NativeRemotePresenceHit;
218
240
  /**
219
241
  * Optional override for a link item inserted from the native link tool.
220
242
  *
@@ -287,6 +309,7 @@ type NativeVectorViewportProps = {
287
309
  readonly onLinkToolRequest?: (detail: NativeLinkToolRequestDetail) => void;
288
310
  readonly linkToolDialogLabels?: NativeLinkToolDialogLabels;
289
311
  readonly onWorldPointerDown?: (detail: NativeWorldPointerDownDetail) => void;
312
+ readonly onRemotePresencePress?: (detail: NativeRemotePresencePressDetail) => void;
290
313
  readonly onWorldPointerMove?: (world: {
291
314
  readonly x: number;
292
315
  readonly y: number;
@@ -434,4 +457,4 @@ type SvgNode = SvgRectNode | SvgEllipseNode | SvgCircleNode | SvgLineNode | SvgP
434
457
  */
435
458
  declare function parseSvgFragment(xml: string): SvgNode[];
436
459
 
437
- export { CanvuLinkData, DEFAULT_NATIVE_OVERFLOW_TOOL_IDS, DEFAULT_NATIVE_VECTOR_TOOLS, NATIVE_STYLE_PALETTE, type NativeCustomShapePlacementOptions, NativeInteractionOverlay, type NativeInteractionOverlayProps, type NativeLinkToolDialogLabels, 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 };
460
+ export { CanvuLinkData, DEFAULT_NATIVE_OVERFLOW_TOOL_IDS, DEFAULT_NATIVE_VECTOR_TOOLS, NATIVE_STYLE_PALETTE, type NativeCustomShapePlacementOptions, NativeInteractionOverlay, type NativeInteractionOverlayProps, type NativeLinkToolDialogLabels, type NativeLinkToolInsertOptions, type NativeLinkToolRequestDetail, type NativeRemotePresencePressDetail, 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
@@ -189,6 +189,19 @@ declare const DEFAULT_NATIVE_VECTOR_TOOLS: readonly NativeVectorToolDefinition[]
189
189
  */
190
190
  declare function NativeVectorToolbar({ value, onChange, tools, overflowToolIds, overflowMenuAccessibilityLabel, disabled, disabledToolIds, showToolLockToggle, toolLocked, onToolLockedChange, density, accessibilityLabel, style, contentContainerStyle, overflowPanelStyle, toolButtonStyle, activeToolButtonStyle, toolLabelStyle, activeToolLabelStyle, renderToolIcon, renderToolLockIcon, renderOverflowIcon, renderOverflowChevronIcon, renderToolButton, }: NativeVectorToolbarProps): react_jsx_runtime.JSX.Element;
191
191
 
192
+ type NativeRemotePresenceHitTarget = "cursor" | "label";
193
+ type NativeRemotePresenceHit = {
194
+ readonly peer: RemotePresencePeer;
195
+ readonly followId: string;
196
+ readonly target: NativeRemotePresenceHitTarget;
197
+ readonly screenX: number;
198
+ readonly screenY: number;
199
+ readonly worldX: number;
200
+ readonly worldY: number;
201
+ readonly clientId?: string;
202
+ readonly peerId?: string;
203
+ };
204
+
192
205
  type NativeCustomShapePlacementOptions = {
193
206
  readonly toolId: string;
194
207
  readonly createItem: (args: {
@@ -215,6 +228,15 @@ type NativeWorldPointerDownDetail = {
215
228
  readonly screenX: number;
216
229
  readonly screenY: number;
217
230
  };
231
+ /**
232
+ * Detail emitted when a native pointer/touch begins on a remote realtime
233
+ * participant label or cursor.
234
+ *
235
+ * Pass `followId` into your `followedPeerId` state for
236
+ * `useRealtimePeerFollow(...)`. For realtime-session peers this is the
237
+ * connection `clientId`, matching the session peer `id`.
238
+ */
239
+ type NativeRemotePresencePressDetail = NativeRemotePresenceHit;
218
240
  /**
219
241
  * Optional override for a link item inserted from the native link tool.
220
242
  *
@@ -287,6 +309,7 @@ type NativeVectorViewportProps = {
287
309
  readonly onLinkToolRequest?: (detail: NativeLinkToolRequestDetail) => void;
288
310
  readonly linkToolDialogLabels?: NativeLinkToolDialogLabels;
289
311
  readonly onWorldPointerDown?: (detail: NativeWorldPointerDownDetail) => void;
312
+ readonly onRemotePresencePress?: (detail: NativeRemotePresencePressDetail) => void;
290
313
  readonly onWorldPointerMove?: (world: {
291
314
  readonly x: number;
292
315
  readonly y: number;
@@ -434,4 +457,4 @@ type SvgNode = SvgRectNode | SvgEllipseNode | SvgCircleNode | SvgLineNode | SvgP
434
457
  */
435
458
  declare function parseSvgFragment(xml: string): SvgNode[];
436
459
 
437
- export { CanvuLinkData, DEFAULT_NATIVE_OVERFLOW_TOOL_IDS, DEFAULT_NATIVE_VECTOR_TOOLS, NATIVE_STYLE_PALETTE, type NativeCustomShapePlacementOptions, NativeInteractionOverlay, type NativeInteractionOverlayProps, type NativeLinkToolDialogLabels, 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 };
460
+ export { CanvuLinkData, DEFAULT_NATIVE_OVERFLOW_TOOL_IDS, DEFAULT_NATIVE_VECTOR_TOOLS, NATIVE_STYLE_PALETTE, type NativeCustomShapePlacementOptions, NativeInteractionOverlay, type NativeInteractionOverlayProps, type NativeLinkToolDialogLabels, type NativeLinkToolInsertOptions, type NativeLinkToolRequestDetail, type NativeRemotePresencePressDetail, 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
@@ -4672,6 +4672,84 @@ function resizeItemByHandle(item, start, handle, currentWorld) {
4672
4672
  }
4673
4673
  return { ...item, x: nb.x, y: nb.y, bounds: nb };
4674
4674
  }
4675
+
4676
+ // src/native/native-remote-presence-hit-test.ts
4677
+ var REMOTE_CURSOR_SCREEN_PX2 = 22;
4678
+ var REMOTE_LABEL_SCREEN_PX2 = 12;
4679
+ var REMOTE_LABEL_OFFSET_X = 14;
4680
+ var REMOTE_LABEL_BASELINE_OFFSET_Y = 18;
4681
+ var REMOTE_LABEL_AVERAGE_CHAR_PX = 7;
4682
+ var REMOTE_LABEL_MAX_WIDTH_PX = 180;
4683
+ var REMOTE_LABEL_MIN_HIT_WIDTH_PX = 44;
4684
+ var REMOTE_LABEL_HIT_PADDING_X = 10;
4685
+ var REMOTE_LABEL_HIT_PADDING_Y = 10;
4686
+ var REMOTE_CURSOR_MIN_HIT_SIZE_PX = 36;
4687
+ function pointInScreenRect(point, rect) {
4688
+ return point.x >= rect.x && point.x <= rect.x + rect.width && point.y >= rect.y && point.y <= rect.y + rect.height;
4689
+ }
4690
+ function displayLabelForPeer(peer) {
4691
+ const displayName = peer.displayName?.trim();
4692
+ if (displayName) return displayName;
4693
+ return null;
4694
+ }
4695
+ function followIdForPeer(peer) {
4696
+ return peer.clientId ?? peer.peerId ?? peer.id;
4697
+ }
4698
+ function estimateLabelWidth(label) {
4699
+ return Math.max(
4700
+ REMOTE_LABEL_MIN_HIT_WIDTH_PX,
4701
+ Math.min(REMOTE_LABEL_MAX_WIDTH_PX, label.length * REMOTE_LABEL_AVERAGE_CHAR_PX)
4702
+ );
4703
+ }
4704
+ function buildHit(peer, camera, point, target) {
4705
+ const world = camera.screenToWorld(point.x, point.y);
4706
+ return {
4707
+ peer,
4708
+ followId: followIdForPeer(peer),
4709
+ target,
4710
+ screenX: point.x,
4711
+ screenY: point.y,
4712
+ worldX: world.worldX,
4713
+ worldY: world.worldY,
4714
+ ...peer.clientId ? { clientId: peer.clientId } : {},
4715
+ ...peer.peerId ? { peerId: peer.peerId } : {}
4716
+ };
4717
+ }
4718
+ function hitTestNativeRemotePresence(peers, camera, point) {
4719
+ for (let index = peers.length - 1; index >= 0; index -= 1) {
4720
+ const peer = peers[index];
4721
+ if (!peer || peer.isSelf || !peer.cursor) continue;
4722
+ const cursorScreen = camera.worldToScreen(peer.cursor.x, peer.cursor.y);
4723
+ const label = displayLabelForPeer(peer);
4724
+ if (label) {
4725
+ const labelX = cursorScreen.screenX + REMOTE_LABEL_OFFSET_X;
4726
+ const labelBaselineY = cursorScreen.screenY + REMOTE_LABEL_BASELINE_OFFSET_Y;
4727
+ const labelRect = {
4728
+ x: labelX - REMOTE_LABEL_HIT_PADDING_X,
4729
+ y: labelBaselineY - REMOTE_LABEL_SCREEN_PX2 - REMOTE_LABEL_HIT_PADDING_Y,
4730
+ width: estimateLabelWidth(label) + REMOTE_LABEL_HIT_PADDING_X * 2,
4731
+ height: REMOTE_LABEL_SCREEN_PX2 + REMOTE_LABEL_HIT_PADDING_Y * 2
4732
+ };
4733
+ if (pointInScreenRect(point, labelRect)) {
4734
+ return buildHit(peer, camera, point, "label");
4735
+ }
4736
+ }
4737
+ const cursorHitSize = Math.max(
4738
+ REMOTE_CURSOR_MIN_HIT_SIZE_PX,
4739
+ REMOTE_CURSOR_SCREEN_PX2
4740
+ );
4741
+ const cursorRect = {
4742
+ x: cursorScreen.screenX - (cursorHitSize - REMOTE_CURSOR_SCREEN_PX2) / 2,
4743
+ y: cursorScreen.screenY - (cursorHitSize - REMOTE_CURSOR_SCREEN_PX2) / 2,
4744
+ width: cursorHitSize,
4745
+ height: cursorHitSize
4746
+ };
4747
+ if (pointInScreenRect(point, cursorRect)) {
4748
+ return buildHit(peer, camera, point, "cursor");
4749
+ }
4750
+ }
4751
+ return null;
4752
+ }
4675
4753
  var DEFAULT_NATIVE_LINK_TOOL_DIALOG_LABELS = {
4676
4754
  title: "Add link",
4677
4755
  description: "Paste the link you want to add to the board.",
@@ -4689,6 +4767,8 @@ var MARKER_TOOL_STYLE = {
4689
4767
  };
4690
4768
  var NATIVE_VIEWPORT_OVERLAY_Z_INDEX = 40;
4691
4769
  var NATIVE_VIEWPORT_OVERLAY_ELEVATION = 40;
4770
+ var NATIVE_VIEWPORT_EXTERNAL_OVERLAY_Z_INDEX = 20;
4771
+ var NATIVE_VIEWPORT_EXTERNAL_OVERLAY_ELEVATION = 20;
4692
4772
  function isPlacementTool(toolId) {
4693
4773
  return toolId === "rect" || toolId === "ellipse" || toolId === "architectural-cloud" || toolId === "line" || toolId === "arrow";
4694
4774
  }
@@ -4766,6 +4846,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4766
4846
  onLinkToolRequest,
4767
4847
  linkToolDialogLabels,
4768
4848
  onWorldPointerDown,
4849
+ onRemotePresencePress,
4769
4850
  onWorldPointerMove,
4770
4851
  onWorldPointerLeave,
4771
4852
  onPlacementPreviewChange,
@@ -4789,6 +4870,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4789
4870
  onLinkToolRequestRef.current = onLinkToolRequest;
4790
4871
  const onWorldPointerDownRef = useRef(onWorldPointerDown);
4791
4872
  onWorldPointerDownRef.current = onWorldPointerDown;
4873
+ const onRemotePresencePressRef = useRef(onRemotePresencePress);
4874
+ onRemotePresencePressRef.current = onRemotePresencePress;
4792
4875
  const onWorldPointerMoveRef = useRef(onWorldPointerMove);
4793
4876
  onWorldPointerMoveRef.current = onWorldPointerMove;
4794
4877
  const onWorldPointerLeaveRef = useRef(onWorldPointerLeave);
@@ -4809,6 +4892,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4809
4892
  itemsRef.current = items;
4810
4893
  const selectedIdsRef = useRef(selectedIds);
4811
4894
  selectedIdsRef.current = selectedIds;
4895
+ const remotePresenceRef = useRef(remotePresence);
4896
+ remotePresenceRef.current = remotePresence;
4812
4897
  const dragStateRef = useRef({ kind: "idle" });
4813
4898
  const [placementPreview, setPlacementPreviewState] = useState(null);
4814
4899
  const setRealtimePlacementPreview = useCallback(
@@ -4968,13 +5053,19 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4968
5053
  const sx = point.x;
4969
5054
  const sy = point.y;
4970
5055
  updateToolCursorPoint(point);
5056
+ const cam = cameraRef.current;
5057
+ if (!cam) return;
5058
+ const remotePresenceHit = interactive && onRemotePresencePressRef.current ? hitTestNativeRemotePresence(remotePresenceRef.current, cam, point) : null;
5059
+ if (remotePresenceHit) {
5060
+ dragStateRef.current = { kind: "idle" };
5061
+ onRemotePresencePressRef.current?.(remotePresenceHit);
5062
+ return;
5063
+ }
4971
5064
  if (!interactive) {
4972
5065
  dragStateRef.current = { kind: "pan" };
4973
5066
  return;
4974
5067
  }
4975
5068
  const tool = toolIdRef.current;
4976
- const cam = cameraRef.current;
4977
- if (!cam) return;
4978
5069
  const { worldX, worldY } = screenToWorld(sx, sy);
4979
5070
  onWorldPointerMoveRef.current?.({ x: worldX, y: worldY });
4980
5071
  if (tool === "hand") {
@@ -5750,7 +5841,20 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
5750
5841
  ]
5751
5842
  }
5752
5843
  ),
5753
- overlay ? /* @__PURE__ */ jsx(View, { pointerEvents: "box-none", style: StyleSheet.absoluteFill, children: overlay }) : null,
5844
+ overlay ? /* @__PURE__ */ jsx(
5845
+ View,
5846
+ {
5847
+ pointerEvents: "box-none",
5848
+ style: [
5849
+ StyleSheet.absoluteFill,
5850
+ {
5851
+ zIndex: NATIVE_VIEWPORT_EXTERNAL_OVERLAY_Z_INDEX,
5852
+ elevation: NATIVE_VIEWPORT_EXTERNAL_OVERLAY_ELEVATION
5853
+ }
5854
+ ],
5855
+ children: overlay
5856
+ }
5857
+ ) : null,
5754
5858
  interactive && showStyleInspector && activeStyleToolId ? /* @__PURE__ */ jsx(
5755
5859
  View,
5756
5860
  {