@reactvision/react-viro 2.54.0 → 2.55.0

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.
Files changed (133) hide show
  1. package/README.md +85 -46
  2. package/android/react_viro/react_viro-release.aar +0 -0
  3. package/android/viro_renderer/viro_renderer-release.aar +0 -0
  4. package/components/AR/ViroARCamera.tsx +5 -0
  5. package/components/AR/ViroARImageMarker.tsx +5 -0
  6. package/components/AR/ViroARObjectMarker.tsx +5 -0
  7. package/components/AR/ViroARPlane.tsx +5 -0
  8. package/components/AR/ViroARPlaneSelector.tsx +5 -0
  9. package/components/AR/ViroARScene.tsx +5 -0
  10. package/components/AR/ViroARSceneNavigator.tsx +54 -0
  11. package/components/Studio/StudioARScene.tsx +368 -0
  12. package/components/Studio/StudioSceneNavigator.tsx +191 -0
  13. package/components/Studio/VRTStudioModule.ts +40 -0
  14. package/components/Studio/domain/animationRegistry.ts +86 -0
  15. package/components/Studio/domain/collisionBindingsRuntime.ts +93 -0
  16. package/components/Studio/domain/collisionPairKey.ts +15 -0
  17. package/components/Studio/domain/dragConfiguration.ts +48 -0
  18. package/components/Studio/domain/materialConfig.ts +276 -0
  19. package/components/Studio/domain/physicsConfig.ts +204 -0
  20. package/components/Studio/domain/sceneNavigationHandler.ts +150 -0
  21. package/components/Studio/domain/studioMaterials.ts +33 -0
  22. package/components/Studio/domain/triggerImageRegistry.ts +64 -0
  23. package/components/Studio/domain/useStudioShaderTimeUniforms.ts +51 -0
  24. package/components/Studio/domain/useStudioShaderViewportUniforms.ts +52 -0
  25. package/components/Studio/domain/viroNodeFactory.tsx +323 -0
  26. package/components/Studio/index.ts +18 -0
  27. package/components/Studio/types.ts +164 -0
  28. package/components/Types/ViroEvents.ts +53 -0
  29. package/components/Utilities/VRModuleOpenXR.ts +50 -0
  30. package/components/Utilities/VRQuestNavigatorBridge.ts +168 -0
  31. package/components/Utilities/ViroPlatform.ts +52 -0
  32. package/components/Utilities/ViroVersion.ts +1 -1
  33. package/components/Utilities/useAnySourceHover.ts +55 -0
  34. package/components/Utilities/useAnySourcePressed.ts +70 -0
  35. package/components/ViroQuestEntryPoint.tsx +79 -0
  36. package/components/ViroVRSceneNavigator.tsx +44 -19
  37. package/components/ViroXRSceneNavigator.tsx +217 -0
  38. package/components/VisionOS/ViroVisionOSModule.ts +93 -0
  39. package/dist/components/AR/ViroARCamera.d.ts +1 -1
  40. package/dist/components/AR/ViroARCamera.js +5 -0
  41. package/dist/components/AR/ViroARImageMarker.d.ts +1 -1
  42. package/dist/components/AR/ViroARImageMarker.js +5 -0
  43. package/dist/components/AR/ViroARObjectMarker.d.ts +1 -1
  44. package/dist/components/AR/ViroARObjectMarker.js +5 -0
  45. package/dist/components/AR/ViroARPlane.d.ts +1 -1
  46. package/dist/components/AR/ViroARPlane.js +5 -0
  47. package/dist/components/AR/ViroARPlaneSelector.d.ts +1 -1
  48. package/dist/components/AR/ViroARPlaneSelector.js +5 -0
  49. package/dist/components/AR/ViroARScene.d.ts +1 -1
  50. package/dist/components/AR/ViroARScene.js +5 -0
  51. package/dist/components/AR/ViroARSceneNavigator.d.ts +13 -0
  52. package/dist/components/AR/ViroARSceneNavigator.js +36 -0
  53. package/dist/components/Studio/StudioARScene.d.ts +15 -0
  54. package/dist/components/Studio/StudioARScene.js +299 -0
  55. package/dist/components/Studio/StudioSceneNavigator.d.ts +31 -0
  56. package/dist/components/Studio/StudioSceneNavigator.js +174 -0
  57. package/dist/components/Studio/VRTStudioModule.d.ts +15 -0
  58. package/dist/components/Studio/VRTStudioModule.js +31 -0
  59. package/dist/components/Studio/domain/animationRegistry.d.ts +11 -0
  60. package/dist/components/Studio/domain/animationRegistry.js +67 -0
  61. package/dist/components/Studio/domain/collisionBindingsRuntime.d.ts +21 -0
  62. package/dist/components/Studio/domain/collisionBindingsRuntime.js +54 -0
  63. package/dist/components/Studio/domain/collisionPairKey.d.ts +8 -0
  64. package/dist/components/Studio/domain/collisionPairKey.js +15 -0
  65. package/dist/components/Studio/domain/dragConfiguration.d.ts +20 -0
  66. package/dist/components/Studio/domain/dragConfiguration.js +37 -0
  67. package/dist/components/Studio/domain/materialConfig.d.ts +56 -0
  68. package/dist/components/Studio/domain/materialConfig.js +239 -0
  69. package/dist/components/Studio/domain/physicsConfig.d.ts +69 -0
  70. package/dist/components/Studio/domain/physicsConfig.js +165 -0
  71. package/dist/components/Studio/domain/sceneNavigationHandler.d.ts +12 -0
  72. package/dist/components/Studio/domain/sceneNavigationHandler.js +112 -0
  73. package/dist/components/Studio/domain/studioMaterials.d.ts +6 -0
  74. package/dist/components/Studio/domain/studioMaterials.js +30 -0
  75. package/dist/components/Studio/domain/triggerImageRegistry.d.ts +13 -0
  76. package/dist/components/Studio/domain/triggerImageRegistry.js +47 -0
  77. package/dist/components/Studio/domain/useStudioShaderTimeUniforms.d.ts +6 -0
  78. package/dist/components/Studio/domain/useStudioShaderTimeUniforms.js +48 -0
  79. package/dist/components/Studio/domain/useStudioShaderViewportUniforms.d.ts +6 -0
  80. package/dist/components/Studio/domain/useStudioShaderViewportUniforms.js +48 -0
  81. package/dist/components/Studio/domain/viroNodeFactory.d.ts +28 -0
  82. package/dist/components/Studio/domain/viroNodeFactory.js +193 -0
  83. package/dist/components/Studio/index.d.ts +3 -0
  84. package/dist/components/Studio/index.js +7 -0
  85. package/dist/components/Studio/types.d.ts +149 -0
  86. package/dist/components/Studio/types.js +4 -0
  87. package/dist/components/Types/ViroEvents.d.ts +49 -1
  88. package/dist/components/Types/ViroEvents.js +1 -0
  89. package/dist/components/Utilities/VRModuleOpenXR.d.ts +32 -0
  90. package/dist/components/Utilities/VRModuleOpenXR.js +44 -0
  91. package/dist/components/Utilities/VRQuestNavigatorBridge.d.ts +85 -0
  92. package/dist/components/Utilities/VRQuestNavigatorBridge.js +124 -0
  93. package/dist/components/Utilities/ViroPlatform.d.ts +10 -0
  94. package/dist/components/Utilities/ViroPlatform.js +43 -0
  95. package/dist/components/Utilities/ViroVersion.d.ts +1 -1
  96. package/dist/components/Utilities/ViroVersion.js +1 -1
  97. package/dist/components/Utilities/useAnySourceHover.d.ts +36 -0
  98. package/dist/components/Utilities/useAnySourceHover.js +48 -0
  99. package/dist/components/Utilities/useAnySourcePressed.d.ts +37 -0
  100. package/dist/components/Utilities/useAnySourcePressed.js +61 -0
  101. package/dist/components/ViroQuestEntryPoint.d.ts +13 -0
  102. package/dist/components/ViroQuestEntryPoint.js +104 -0
  103. package/dist/components/ViroVRSceneNavigator.d.ts +24 -10
  104. package/dist/components/ViroVRSceneNavigator.js +21 -18
  105. package/dist/components/ViroXRSceneNavigator.d.ts +54 -0
  106. package/dist/components/ViroXRSceneNavigator.js +173 -0
  107. package/dist/components/VisionOS/ViroVisionOSModule.d.ts +65 -0
  108. package/dist/components/VisionOS/ViroVisionOSModule.js +91 -0
  109. package/dist/index.d.ts +15 -2
  110. package/dist/index.js +32 -2
  111. package/dist/plugins/withViro.d.ts +17 -1
  112. package/dist/plugins/withViroAndroid.js +312 -7
  113. package/dist/plugins/withViroIos.js +5 -0
  114. package/dist/plugins/withViroVisionOS.d.ts +24 -0
  115. package/dist/plugins/withViroVisionOS.js +265 -0
  116. package/index.ts +58 -0
  117. package/ios/ViroReact.podspec +13 -4
  118. package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreCoreMLSemanticsResources.bundle/Info.plist +0 -0
  119. package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreResources.bundle/Info.plist +0 -0
  120. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSession.h +10 -0
  121. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSessioniOS.h +4 -0
  122. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROInputControllerBase.h +74 -0
  123. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROInputType.h +11 -3
  124. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROPlatformUtil.h +13 -0
  125. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRORenderer.h +8 -0
  126. package/ios/dist/ViroRenderer/ViroKit.framework/Info.plist +0 -0
  127. package/ios/dist/ViroRenderer/ViroKit.framework/Shaders.dat +1 -1
  128. package/ios/dist/ViroRenderer/ViroKit.framework/ViroKit +0 -0
  129. package/ios/dist/ViroRenderer/ViroKit.podspec +5 -0
  130. package/ios/dist/include/VRTARSceneNavigator.h +3 -0
  131. package/ios/dist/include/VRTStudioModule.h +6 -0
  132. package/ios/dist/lib/libViroReact.a +0 -0
  133. package/package.json +1 -1
@@ -0,0 +1,164 @@
1
+ // Types mirror the actual server response from GET /functions/v1/scenes/{scene_id}.
2
+ // Field names are snake_case as returned by the Supabase Edge Function / Postgres RPC.
3
+
4
+ export interface StudioSceneCreatedBy {
5
+ id: string;
6
+ first_name: string | null;
7
+ last_name: string | null;
8
+ }
9
+
10
+ export interface StudioSceneMeta {
11
+ id: string;
12
+ name: string | null;
13
+ belongs_to_project: string;
14
+ plane_detection: string | null; // 'AUTOMATIC' | 'MANUAL' | 'NONE'
15
+ plane_direction: string | null; // 'Horizontal' | 'Vertical'
16
+ on_load_function: string | null;
17
+ physics_world_config: Record<string, unknown> | null;
18
+ created_at: string;
19
+ created_by: StudioSceneCreatedBy | null;
20
+ }
21
+
22
+ export interface StudioProjectMeta {
23
+ id: string;
24
+ occlusion_mode: "NONE" | "PEOPLEONLY" | "DEPTHBASED";
25
+ }
26
+
27
+ export interface StudioSceneFunction {
28
+ id: string;
29
+ scene: string;
30
+ function_type: "NAVIGATION" | "ALERT" | "ANIMATION";
31
+ navigation: string | null;
32
+ alert: string | null;
33
+ animation: string | null;
34
+ scene_navigation: { id: string; navigate_to: string } | null;
35
+ scene_alert: {
36
+ id: string;
37
+ alert_title: string | null;
38
+ alert_message: string | null;
39
+ } | null;
40
+ scene_animation: {
41
+ id: string;
42
+ animation_key: string;
43
+ duration_ms: number | null;
44
+ delay_ms: number | null;
45
+ properties: Record<string, unknown>;
46
+ } | null;
47
+ }
48
+
49
+ export interface StudioAsset {
50
+ id: string; // scene asset placement ID
51
+ name: string | null;
52
+ description: string | null;
53
+ file_url: string | null;
54
+ file_size: number | null;
55
+ asset_type_name: "3D-MODEL" | "TEXT" | "IMAGE" | "VIDEO" | null;
56
+ position_x: number | null;
57
+ position_y: number | null;
58
+ position_z: number | null;
59
+ rotation_x: number | null; // radians
60
+ rotation_y: number | null;
61
+ rotation_z: number | null;
62
+ scale: number | null;
63
+ latitude: number | null;
64
+ longitude: number | null;
65
+ is_draggable: boolean;
66
+ trigger_image_url: string | null;
67
+ trigger_image_orientation: "Up" | "Down" | "Left" | "Right" | null;
68
+ trigger_image_physical_width_m: number | null;
69
+ material_config: Record<string, unknown> | null;
70
+ physics_config: Record<string, unknown> | null;
71
+ on_click_function: string | null; // UUID → look up in functions[]
72
+ asset_id: string | null; // team asset type UUID
73
+ created_at: string;
74
+ updated_at: string;
75
+ scene_function: StudioSceneFunction | null; // resolved on_click_function inline
76
+ }
77
+
78
+ export interface StudioCollisionBinding {
79
+ id: string;
80
+ scene_id: string;
81
+ function_id: string;
82
+ asset_x_id: string;
83
+ asset_y_id: string;
84
+ scene_function: StudioSceneFunction;
85
+ }
86
+
87
+ export interface StudioAnimation {
88
+ id: string;
89
+ scene_id: string;
90
+ target_asset_id: string;
91
+ animation_key: string; // ViroAnimations registry key
92
+ properties: Record<string, unknown>; // Viro keyframe format
93
+ duration_ms: number | null;
94
+ delay_ms: number | null;
95
+ easing:
96
+ | "Linear"
97
+ | "EaseIn"
98
+ | "EaseOut"
99
+ | "EaseInEaseOut"
100
+ | "Bounce"
101
+ | null;
102
+ loop: boolean;
103
+ interruptible: boolean;
104
+ on_start_function: string | null;
105
+ on_finish_function: string | null;
106
+ }
107
+
108
+ export interface StudioProjectAsset {
109
+ id: string;
110
+ name: string | null;
111
+ url: string | null;
112
+ }
113
+
114
+ export interface StudioProjectSceneSummary {
115
+ id: string;
116
+ name: string | null;
117
+ created_at: string;
118
+ created_by: StudioSceneCreatedBy | null;
119
+ assets: StudioProjectAsset[];
120
+ }
121
+
122
+ export interface StudioProjectOpeningScene {
123
+ id: string;
124
+ name: string | null;
125
+ }
126
+
127
+ export interface StudioProjectOverview {
128
+ id: string;
129
+ name: string;
130
+ thumbnail: string | null;
131
+ occlusion_mode: "NONE" | "PEOPLEONLY" | "DEPTHBASED";
132
+ created_at: string;
133
+ created_by: StudioSceneCreatedBy | null;
134
+ opening_scene: StudioProjectOpeningScene | null;
135
+ scenes: StudioProjectSceneSummary[];
136
+ }
137
+
138
+ /** Top-level response from GET /functions/v1/projects/{project_id} (after JSON.parse) */
139
+ export interface StudioProjectApiResponse {
140
+ project: StudioProjectOverview;
141
+ meta: { request_id: string };
142
+ }
143
+
144
+ /** Top-level response from GET /functions/v1/scenes/{scene_id} (after JSON.parse) */
145
+ export interface StudioSceneResponse {
146
+ scene: StudioSceneMeta;
147
+ project: StudioProjectMeta;
148
+ assets: StudioAsset[];
149
+ collision_bindings: StudioCollisionBinding[];
150
+ animations: StudioAnimation[];
151
+ functions: StudioSceneFunction[];
152
+ meta: { request_id: string };
153
+ }
154
+
155
+ /** Viro animation prop shape passed to Viro components */
156
+ export type ViroAnimationProp = {
157
+ name: string;
158
+ run: boolean;
159
+ loop: boolean;
160
+ interruptible: boolean;
161
+ delay: number;
162
+ onStart?: () => void;
163
+ onFinish?: () => void;
164
+ };
@@ -134,6 +134,7 @@ export type ViroPlatformInfo = {
134
134
  export enum ViroPlatformTypes {
135
135
  GVR = "gvr",
136
136
  GEAR_VR = "ovr-mobile",
137
+ QUEST = "quest",
137
138
  }
138
139
 
139
140
  export enum ViroHeadsetTypes {
@@ -176,6 +177,58 @@ export type ViroErrorEvent = {
176
177
  error: Error;
177
178
  };
178
179
 
180
+ /** ===========================================================================
181
+ * Quest / OpenXR Hand Tracking Types (M4)
182
+ * ============================================================================ */
183
+
184
+ export type ViroJoint = {
185
+ position: Viro3DPoint;
186
+ /** Sphere radius representing finger pad size (meters). */
187
+ radius: number;
188
+ };
189
+
190
+ export type ViroHandJoints = {
191
+ wrist: ViroJoint;
192
+ thumbMetacarpal: ViroJoint;
193
+ thumbProximal: ViroJoint;
194
+ thumbDistal: ViroJoint;
195
+ thumbTip: ViroJoint;
196
+ indexMetacarpal: ViroJoint;
197
+ indexProximal: ViroJoint;
198
+ indexIntermediate: ViroJoint;
199
+ indexDistal: ViroJoint;
200
+ indexTip: ViroJoint;
201
+ middleMetacarpal: ViroJoint;
202
+ middleProximal: ViroJoint;
203
+ middleIntermediate: ViroJoint;
204
+ middleDistal: ViroJoint;
205
+ middleTip: ViroJoint;
206
+ ringMetacarpal: ViroJoint;
207
+ ringProximal: ViroJoint;
208
+ ringIntermediate: ViroJoint;
209
+ ringDistal: ViroJoint;
210
+ ringTip: ViroJoint;
211
+ littleMetacarpal: ViroJoint;
212
+ littleProximal: ViroJoint;
213
+ littleIntermediate: ViroJoint;
214
+ littleDistal: ViroJoint;
215
+ littleTip: ViroJoint;
216
+ };
217
+
218
+ export type ViroHandPinchEvent = {
219
+ hand: "left" | "right";
220
+ /** World-space position of the pinch point (midpoint between thumb tip and index tip). */
221
+ position: Viro3DPoint;
222
+ /** Pinch strength [0..1] from XR_FB_hand_tracking_aim, or 1.0 on pinch-complete fallback. */
223
+ pinchStrength: number;
224
+ };
225
+
226
+ /** Per-frame skeletal hand data dispatched via onHandUpdate. null when hand is not tracked. */
227
+ export type ViroHandUpdateEvent = {
228
+ left: ViroHandJoints | null;
229
+ right: ViroHandJoints | null;
230
+ };
231
+
179
232
  /** ===========================================================================
180
233
  * Viro Animation Events
181
234
  * ============================================================================ */
@@ -0,0 +1,50 @@
1
+ import { useEffect, useState } from "react";
2
+ import { NativeModules } from "react-native";
3
+ import { VRQuestNavigatorBridge } from "./VRQuestNavigatorBridge";
4
+
5
+ /**
6
+ * Finishes VRActivity and returns the user to the panel (MainActivity).
7
+ * Safe to call on any platform — no-op when VRLauncher is unavailable.
8
+ */
9
+ export function exitVRScene(): void {
10
+ VRQuestNavigatorBridge.setVRActive(false);
11
+ (NativeModules.VRLauncher as { exitVRScene?: () => void } | undefined)
12
+ ?.exitVRScene?.();
13
+ }
14
+
15
+ export type VRModuleOpenXRType = {
16
+ recenterTracking?: (viewTag: number) => void;
17
+ setPassthroughEnabled?: (viewTag: number, enabled: boolean) => void;
18
+ };
19
+
20
+ /**
21
+ * Typed reference to the VRModuleOpenXR native module.
22
+ * undefined when not running on Meta Quest (no-op calls are safe via optional chaining).
23
+ */
24
+ export const VRModuleOpenXR =
25
+ (NativeModules.VRModuleOpenXR as VRModuleOpenXRType | undefined) ?? undefined;
26
+
27
+ /**
28
+ * Returns the live viewTag of the ViroVRSceneNavigator running in VRActivity,
29
+ * kept in sync via VRQuestNavigatorBridge. null until ViroQuestEntryPoint has
30
+ * mounted and published the tag.
31
+ *
32
+ * Use this inside VR scenes when you need to call VRModuleOpenXR methods:
33
+ *
34
+ * ```tsx
35
+ * function MyVRScene() {
36
+ * const viewTag = useVRViewTag();
37
+ * const recenter = () => {
38
+ * if (viewTag != null) VRModuleOpenXR?.recenterTracking?.(viewTag);
39
+ * };
40
+ * return <ViroScene>...</ViroScene>;
41
+ * }
42
+ * ```
43
+ */
44
+ export function useVRViewTag(): number | null {
45
+ const [tag, setTag] = useState<number | null>(
46
+ () => VRQuestNavigatorBridge.getViewTag()
47
+ );
48
+ useEffect(() => VRQuestNavigatorBridge.onViewTag(setTag), []);
49
+ return tag;
50
+ }
@@ -0,0 +1,168 @@
1
+ /**
2
+ * VRQuestNavigatorBridge
3
+ *
4
+ * Carries VR "intents" and navigator operations from panel-Activity component
5
+ * trees to VRActivity. Both activities run in the same process and share the
6
+ * same Hermes JS engine, so a module-level store is sufficient — no native
7
+ * round-trip needed.
8
+ *
9
+ * Flow:
10
+ * Panel (MainActivity)
11
+ * ViroXRSceneNavigator mounts
12
+ * → setIntent(scene, config) — stores intent, notifies ViroQuestEntryPoint
13
+ * → launchVRScene() — OS switches to VRActivity
14
+ * → ref.push(StudioScene) — dispatchOp() queues the op
15
+ *
16
+ * VRActivity mounts VRQuestScene → ViroQuestEntryPoint
17
+ * → onIntent cb fires — reads intentKey + initialScene + rendererConfig
18
+ * → renders ViroVRSceneNavigator key={intentKey} (fresh mount per intent)
19
+ * → ViroQuestEntryPoint captures viewTag → setViewTag()
20
+ * → subscribeOps() drains queued ops — push(StudioScene) arrives
21
+ *
22
+ * Each call to setIntent() generates a new intentKey. ViroQuestEntryPoint
23
+ * uses key={intentKey} on ViroVRSceneNavigator, guaranteeing a clean navigator
24
+ * stack whenever a different panel screen activates VR.
25
+ */
26
+
27
+ export type VRNavigatorOp =
28
+ | { type: "push"; scene: any }
29
+ | { type: "pop" }
30
+ | { type: "popN"; n: number }
31
+ | { type: "replace"; scene: any }
32
+ | { type: "jump"; scene: any };
33
+
34
+ /** Renderer flags forwarded from ViroXRSceneNavigator to ViroVRSceneNavigator. */
35
+ export type VRQuestRendererConfig = {
36
+ hdrEnabled?: boolean;
37
+ pbrEnabled?: boolean;
38
+ bloomEnabled?: boolean;
39
+ shadowsEnabled?: boolean;
40
+ multisamplingEnabled?: boolean;
41
+ vrModeEnabled?: boolean;
42
+ passthroughEnabled?: boolean;
43
+ handTrackingEnabled?: boolean;
44
+ debug?: boolean;
45
+ onExitViro?: () => void;
46
+ };
47
+
48
+ export type VRQuestIntent = {
49
+ intentKey: string;
50
+ initialScene: any;
51
+ rendererConfig?: VRQuestRendererConfig;
52
+ };
53
+
54
+ // ── Intent store ────────────────────────────────────────────────────────────
55
+
56
+ let _intent: VRQuestIntent | null = null;
57
+ const _intentListeners = new Set<(intent: VRQuestIntent) => void>();
58
+
59
+ // ── Op queue ─────────────────────────────────────────────────────────────────
60
+
61
+ const _opListeners = new Set<(op: VRNavigatorOp) => void>();
62
+ const _opQueue: VRNavigatorOp[] = [];
63
+
64
+ let _opCounter = 0;
65
+
66
+ // ── VR active flag ────────────────────────────────────────────────────────────
67
+ // True while VRActivity is running. Set to true just before launchVRScene(),
68
+ // set to false by exitVRScene(). The AppState-based relaunch in
69
+ // ViroXRSceneNavigator checks this so it only fires for system-level
70
+ // backgrounding (Quest menu / home), not for explicit exits.
71
+
72
+ let _vrActive = false;
73
+
74
+ // ── ViewTag store ─────────────────────────────────────────────────────────────
75
+ // ViroQuestEntryPoint populates this after ViroVRSceneNavigator mounts.
76
+ // Panel-side code (e.g. VRModuleOpenXR.recenterTracking) reads it to target
77
+ // the live native view without needing a direct ref to ViroVRSceneNavigator.
78
+
79
+ let _viewTag: number | null = null;
80
+ const _viewTagListeners = new Set<(tag: number | null) => void>();
81
+
82
+ export const VRQuestNavigatorBridge = {
83
+ // ── Called by ViroXRSceneNavigator when mounting on Quest ──────────────────
84
+
85
+ /**
86
+ * Record the scene and renderer config that VRActivity should use, and
87
+ * return a unique intent key. Call launchVRScene() after this.
88
+ */
89
+ setIntent(initialScene: any, rendererConfig?: VRQuestRendererConfig): string {
90
+ const intentKey = `vr-${Date.now()}-${++_opCounter}`;
91
+ _intent = { intentKey, initialScene, rendererConfig };
92
+ // Clear any stale ops from the previous intent.
93
+ _opQueue.length = 0;
94
+ _intentListeners.forEach((l) => l(_intent!));
95
+ return intentKey;
96
+ },
97
+
98
+ /** Current intent (may be null if VR has never been launched). */
99
+ getIntent(): VRQuestIntent | null {
100
+ return _intent;
101
+ },
102
+
103
+ /**
104
+ * Subscribe to intent changes. Fires immediately with the current intent if
105
+ * one exists so that ViroQuestEntryPoint can render even if it mounts after
106
+ * setIntent() was called.
107
+ */
108
+ onIntent(cb: (intent: VRQuestIntent) => void): () => void {
109
+ _intentListeners.add(cb);
110
+ if (_intent) cb(_intent);
111
+ return () => {
112
+ _intentListeners.delete(cb);
113
+ };
114
+ },
115
+
116
+ // ── Called by ViroXRSceneNavigator ref (push / pop / etc.) ────────────────
117
+
118
+ dispatchOp(op: VRNavigatorOp): void {
119
+ if (_opListeners.size > 0) {
120
+ _opListeners.forEach((l) => l(op));
121
+ } else {
122
+ _opQueue.push(op);
123
+ }
124
+ },
125
+
126
+ // ── Called by ViroQuestEntryPoint after ViroVRSceneNavigator mounts ────────
127
+
128
+ subscribeOps(cb: (op: VRNavigatorOp) => void): () => void {
129
+ _opListeners.add(cb);
130
+ const pending = _opQueue.splice(0);
131
+ pending.forEach((op) => cb(op));
132
+ return () => {
133
+ _opListeners.delete(cb);
134
+ };
135
+ },
136
+
137
+ // ── VR active flag ────────────────────────────────────────────────────────
138
+ setVRActive(active: boolean): void {
139
+ _vrActive = active;
140
+ },
141
+ isVRActive(): boolean {
142
+ return _vrActive;
143
+ },
144
+
145
+ // ── ViewTag — native node handle of the live ViroVRSceneNavigator ──────────
146
+
147
+ setViewTag(tag: number | null): void {
148
+ _viewTag = tag;
149
+ _viewTagListeners.forEach((l) => l(tag));
150
+ },
151
+
152
+ getViewTag(): number | null {
153
+ return _viewTag;
154
+ },
155
+
156
+ /**
157
+ * Subscribe to viewTag changes. Fires immediately with the current value.
158
+ * Use this inside VR scenes that need VRModuleOpenXR (recenterTracking,
159
+ * setPassthroughEnabled) without a direct ref to ViroVRSceneNavigator.
160
+ */
161
+ onViewTag(cb: (tag: number | null) => void): () => void {
162
+ _viewTagListeners.add(cb);
163
+ cb(_viewTag);
164
+ return () => {
165
+ _viewTagListeners.delete(cb);
166
+ };
167
+ },
168
+ };
@@ -0,0 +1,52 @@
1
+ import { NativeModules, Platform } from "react-native";
2
+
3
+ type AndroidBuildInfo = {
4
+ Manufacturer?: string;
5
+ Brand?: string;
6
+ Model?: string;
7
+ };
8
+
9
+ /**
10
+ * True only on actual Meta Quest hardware (Quest 1/2/Pro/3/3S).
11
+ *
12
+ * Detection is based on `Platform.constants` (Android `Build.MANUFACTURER`,
13
+ * `BRAND`, `MODEL`) — NOT on the presence of `NativeModules.VRModuleOpenXR`.
14
+ * The OpenXR module ships with any app built against the Quest variant of
15
+ * react-viro and is therefore present on regular Android phones too when the
16
+ * same APK targets both phone and Quest. Branding strings are the
17
+ * authoritative signal for "is the user wearing a Quest right now".
18
+ *
19
+ * Quest 1/2/Pro: Manufacturer="Oculus", Brand="oculus"
20
+ * Quest 3/3S: Manufacturer="Meta", Brand="meta"
21
+ */
22
+ function detectQuest(): boolean {
23
+ if (Platform.OS !== "android") return false;
24
+ const c = (Platform.constants ?? {}) as AndroidBuildInfo;
25
+ const manufacturer = (c.Manufacturer ?? "").toLowerCase();
26
+ const brand = (c.Brand ?? "").toLowerCase();
27
+ const model = (c.Model ?? "").toLowerCase();
28
+
29
+ if (
30
+ manufacturer === "oculus" ||
31
+ manufacturer === "meta" ||
32
+ brand === "oculus" ||
33
+ brand === "meta"
34
+ ) {
35
+ return true;
36
+ }
37
+ // Defensive: future hardware shipping under new manufacturer strings.
38
+ return /\bquest\b/.test(model);
39
+ }
40
+
41
+ export const isQuest: boolean = detectQuest();
42
+
43
+ /**
44
+ * True when this app build includes the OpenXR VR native module (i.e. the
45
+ * Quest variant of react-viro is registered in `MainApplication`). Does NOT
46
+ * imply the current device is a Quest — for that, use `isQuest`.
47
+ *
48
+ * Useful when you need to decide whether `ViroVRSceneNavigator` *could* render
49
+ * if you forced VR mode (e.g., for in-app build diagnostics).
50
+ */
51
+ export const hasOpenXRSupport: boolean =
52
+ NativeModules.VRModuleOpenXR !== undefined;
@@ -1 +1 @@
1
- export const VIRO_VERSION = "2.54.0";
1
+ export const VIRO_VERSION = "2.55.0";
@@ -0,0 +1,55 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+
3
+ import type { Viro3DPoint } from "../Types/ViroUtils";
4
+
5
+ /**
6
+ * Aggregate `onHover` state across all input sources.
7
+ *
8
+ * On Quest (and any other backend that supports multiple simultaneous
9
+ * pointers — e.g. left + right controllers, or controller + tracked hand),
10
+ * Viro fires the node's `onHover` callback **per source**. A second pointer
11
+ * sweeping over an already-hovered node would otherwise produce spurious
12
+ * enter/exit toggles in JS even though the visual hover should remain "on".
13
+ *
14
+ * This hook tracks each source ID independently and exposes a single
15
+ * aggregated boolean — `true` whenever **any** source is hovering the node.
16
+ * Apps that don't care about per-source distinction (the common case for
17
+ * UI buttons) can just consume the boolean.
18
+ *
19
+ * Usage:
20
+ * ```tsx
21
+ * function MyButton() {
22
+ * const [hovered, onHover] = useAnySourceHover();
23
+ * return (
24
+ * <ViroNode onHover={onHover}>
25
+ * <ViroQuad materials={[hovered ? "btnHover" : "btnIdle"]} ... />
26
+ * </ViroNode>
27
+ * );
28
+ * }
29
+ * ```
30
+ *
31
+ * The handler signature matches Viro's `onHover` prop directly — pass it as
32
+ * `onHover={onHover}` with no wrapper. Internally the hook deduplicates per
33
+ * source so a sequence of `(true, …, src=A)` events from the same source
34
+ * produces at most one re-render.
35
+ */
36
+ export function useAnySourceHover(): readonly [
37
+ boolean,
38
+ (isHovering: boolean, position: Viro3DPoint, source: unknown) => void,
39
+ ] {
40
+ const sourcesRef = useRef<Record<string, boolean>>({});
41
+ const [hovered, setHovered] = useState(false);
42
+
43
+ const onHover = useCallback(
44
+ (isHovering: boolean, _position: Viro3DPoint, source: unknown) => {
45
+ const key = String(source ?? "default");
46
+ if (sourcesRef.current[key] === isHovering) return;
47
+ sourcesRef.current[key] = isHovering;
48
+ const any = Object.values(sourcesRef.current).some(Boolean);
49
+ setHovered(any);
50
+ },
51
+ []
52
+ );
53
+
54
+ return [hovered, onHover] as const;
55
+ }
@@ -0,0 +1,70 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+
3
+ import {
4
+ ViroClickState,
5
+ ViroClickStateTypes,
6
+ } from "../Types/ViroEvents";
7
+ import type { Viro3DPoint } from "../Types/ViroUtils";
8
+
9
+ /**
10
+ * Aggregate `onClickState` "is being pressed" state across all input sources.
11
+ *
12
+ * Mirror of `useAnySourceHover` for clicks. Tracks per-source `CLICK_DOWN` /
13
+ * `CLICK_UP` events from any input source (right + left controllers / hands)
14
+ * and returns a single aggregated boolean — `true` whenever **any** source
15
+ * is currently holding the trigger / pinch on this node.
16
+ *
17
+ * The fully-completed `CLICKED` event is intentionally ignored here: it is
18
+ * informational ("a complete click happened") and shouldn't toggle the
19
+ * held-state. Apps that need the click event itself can use the regular
20
+ * `onClick` prop — those callbacks already pass the `source`.
21
+ *
22
+ * Usage:
23
+ * ```tsx
24
+ * function MyButton() {
25
+ * const [pressed, onClickState] = useAnySourcePressed();
26
+ * return (
27
+ * <ViroNode onClickState={onClickState} onClick={...}>
28
+ * <ViroQuad
29
+ * scale={pressed ? [0.95, 0.95, 0.95] : [1, 1, 1]}
30
+ * materials={[pressed ? "btnPressed" : "btnIdle"]}
31
+ * />
32
+ * </ViroNode>
33
+ * );
34
+ * }
35
+ * ```
36
+ *
37
+ * Internally deduplicates per source so a sequence of `(CLICK_DOWN, src=A)`
38
+ * events from the same source produces at most one re-render.
39
+ */
40
+ export function useAnySourcePressed(): readonly [
41
+ boolean,
42
+ (clickState: ViroClickState, position: Viro3DPoint, source: unknown) => void,
43
+ ] {
44
+ const sourcesRef = useRef<Record<string, boolean>>({});
45
+ const [pressed, setPressed] = useState(false);
46
+
47
+ const onClickState = useCallback(
48
+ (clickState: ViroClickState, _position: Viro3DPoint, source: unknown) => {
49
+ let next: boolean;
50
+ if (clickState === ViroClickStateTypes.CLICK_DOWN) {
51
+ next = true;
52
+ } else if (clickState === ViroClickStateTypes.CLICK_UP) {
53
+ next = false;
54
+ } else {
55
+ // CLICKED is fired between DOWN and UP for completed clicks —
56
+ // doesn't change held state, ignore.
57
+ return;
58
+ }
59
+
60
+ const key = String(source ?? "default");
61
+ if (sourcesRef.current[key] === next) return;
62
+ sourcesRef.current[key] = next;
63
+ const any = Object.values(sourcesRef.current).some(Boolean);
64
+ setPressed(any);
65
+ },
66
+ []
67
+ );
68
+
69
+ return [pressed, onClickState] as const;
70
+ }