canvu-react 0.4.41 → 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.",
@@ -4768,6 +4846,7 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4768
4846
  onLinkToolRequest,
4769
4847
  linkToolDialogLabels,
4770
4848
  onWorldPointerDown,
4849
+ onRemotePresencePress,
4771
4850
  onWorldPointerMove,
4772
4851
  onWorldPointerLeave,
4773
4852
  onPlacementPreviewChange,
@@ -4791,6 +4870,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4791
4870
  onLinkToolRequestRef.current = onLinkToolRequest;
4792
4871
  const onWorldPointerDownRef = useRef(onWorldPointerDown);
4793
4872
  onWorldPointerDownRef.current = onWorldPointerDown;
4873
+ const onRemotePresencePressRef = useRef(onRemotePresencePress);
4874
+ onRemotePresencePressRef.current = onRemotePresencePress;
4794
4875
  const onWorldPointerMoveRef = useRef(onWorldPointerMove);
4795
4876
  onWorldPointerMoveRef.current = onWorldPointerMove;
4796
4877
  const onWorldPointerLeaveRef = useRef(onWorldPointerLeave);
@@ -4811,6 +4892,8 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4811
4892
  itemsRef.current = items;
4812
4893
  const selectedIdsRef = useRef(selectedIds);
4813
4894
  selectedIdsRef.current = selectedIds;
4895
+ const remotePresenceRef = useRef(remotePresence);
4896
+ remotePresenceRef.current = remotePresence;
4814
4897
  const dragStateRef = useRef({ kind: "idle" });
4815
4898
  const [placementPreview, setPlacementPreviewState] = useState(null);
4816
4899
  const setRealtimePlacementPreview = useCallback(
@@ -4970,13 +5053,19 @@ var NativeVectorViewport = forwardRef(function NativeVectorViewport2({
4970
5053
  const sx = point.x;
4971
5054
  const sy = point.y;
4972
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
+ }
4973
5064
  if (!interactive) {
4974
5065
  dragStateRef.current = { kind: "pan" };
4975
5066
  return;
4976
5067
  }
4977
5068
  const tool = toolIdRef.current;
4978
- const cam = cameraRef.current;
4979
- if (!cam) return;
4980
5069
  const { worldX, worldY } = screenToWorld(sx, sy);
4981
5070
  onWorldPointerMoveRef.current?.({ x: worldX, y: worldY });
4982
5071
  if (tool === "hand") {