@sceneview-sdk/react-native 4.26.0 → 4.28.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sceneview-sdk/react-native",
3
- "version": "4.26.0",
3
+ "version": "4.28.0",
4
4
  "description": "React Native bindings for SceneView — 3D and AR scenes powered by Filament (Android) and RealityKit (iOS)",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -17,7 +17,8 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "typescript": "tsc --noEmit",
20
- "lint": "eslint \"src/**/*.{ts,tsx}\"",
20
+ "lint": "cd ../.. && react-native/react-native-sceneview/node_modules/.bin/biome check react-native/react-native-sceneview/src react-native/react-native-sceneview/__tests__ react-native/react-native-sceneview/example/src",
21
+ "lint:fix": "cd ../.. && react-native/react-native-sceneview/node_modules/.bin/biome check --write react-native/react-native-sceneview/src react-native/react-native-sceneview/__tests__ react-native/react-native-sceneview/example/src",
21
22
  "test": "jest",
22
23
  "build": "bob build",
23
24
  "prepare": "bob build"
@@ -51,6 +52,7 @@
51
52
  "react-native": ">=0.72.0"
52
53
  },
53
54
  "devDependencies": {
55
+ "@biomejs/biome": "^2.0.0",
54
56
  "@types/jest": "^29.5.0",
55
57
  "@types/react": "^18.2.0",
56
58
  "@types/react-native": "^0.72.0",
@@ -6,24 +6,49 @@ Pod::Spec.new do |s|
6
6
  s.name = "react-native-sceneview"
7
7
  s.version = package["version"]
8
8
  s.summary = package["description"]
9
- s.homepage = package["repository"]
9
+ # `package["repository"]` is an OBJECT ({type, url, directory}), and CocoaPods
10
+ # rejects a Hash here ("Unacceptable type `Hash` for `homepage`"), which made
11
+ # `pod install` fail outright for any host app consuming this module. Use the
12
+ # string `homepage` field instead.
13
+ s.homepage = package["homepage"]
10
14
  s.license = package["license"]
11
15
  s.authors = "SceneView contributors"
12
16
 
13
- s.platforms = { :ios => "17.0" }
17
+ # Must match `SceneViewSwift/Package.swift`'s `.iOS("18.0")` floor — the
18
+ # bridge's `ios/*.swift` imports SceneViewSwift, so a lower value here is a
19
+ # lie CocoaPods cannot catch until a host app fails to link.
20
+ s.platforms = { :ios => "18.0" }
14
21
  s.source = { :git => "https://github.com/sceneview/sceneview.git", :tag => "v#{s.version}" }
15
22
  s.source_files = "ios/**/*.{swift,m}"
16
23
 
17
24
  s.dependency "React-Core"
18
25
 
19
- # `SceneViewSwift` is distributed via Swift Package Manager only — there is
20
- # no published CocoaPods spec for it — so it CANNOT be declared as a
21
- # `s.dependency` here (CocoaPods would fail `pod install` resolving it).
22
- # The host app must add it once via Xcode's SwiftPM integration:
23
- # File ▸ Add Package Dependencies… ▸ https://github.com/sceneview/SceneViewSwift
24
- # The module's `ios/*.swift` `import SceneViewSwift` then resolves at the
25
- # app build, exactly like any RN native module with a SwiftPM dependency.
26
- # See this module's README "iOS" section.
26
+ # This module's `ios/*.swift` files `import SceneViewSwift`, so the module has
27
+ # to arrive as a POD, not as a Swift package on the host app's project: this
28
+ # source compiles inside the generated `Pods.xcodeproj`, which does not see
29
+ # the host project's SwiftPM packages ("Unable to find module dependency:
30
+ # 'SceneViewSwift'"). The SwiftPM route this spec used to document was never
31
+ # measured — the demo app was scaffolded and never built — and the first real
32
+ # `xcodebuild` against it failed exactly that way. Closes the RN half of #3072.
33
+ #
34
+ # `SceneViewSwift` is NOT on the CocoaPods trunk, so a host app must give the
35
+ # dependency a coordinate of its own — one of:
36
+ # pod 'SceneViewSwift',
37
+ # :podspec => 'https://raw.githubusercontent.com/sceneview/sceneview/main/SceneViewSwift.podspec'
38
+ # pod 'SceneViewSwift', :path => '<repo-root>' # in-repo consumers
39
+ # `main`, not a tag, and not `:git =>, :tag =>`: the root podspec landed after
40
+ # v4.26.0 was cut, so no existing tag carries it and a tagged coordinate 404s
41
+ # (`git cat-file -e v4.27.0:SceneViewSwift.podspec`). `flutter_sceneview.podspec`
42
+ # says `main` for the same reason; both move to a tag once a release carries
43
+ # the file. Supply the coordinate always — the name is unclaimed on the trunk,
44
+ # so a Podfile that leaves it out does not fail closed, it resolves to
45
+ # whatever someone else publishes under that name.
46
+ # `samples/react-native-demo/ios/Podfile` takes the `:path` route, exactly as
47
+ # `samples/flutter-demo/ios/Podfile` does. See this module's README "iOS".
48
+ #
49
+ # Loose `~> 4.27` rather than `= s.version`: an app pinned to an older
50
+ # SceneViewSwift still resolves, mirroring `flutter_sceneview.podspec`.
51
+ s.dependency "SceneViewSwift", "~> 4.28"
27
52
 
28
53
  s.swift_version = "5.9"
29
54
  end
package/src/index.tsx CHANGED
@@ -1,14 +1,20 @@
1
- import React from 'react';
1
+ // `tsconfig.json` sets `"jsx": "react"` — the CLASSIC runtime — so every JSX
2
+ // element below compiles to `React.createElement(...)` (verified in the
3
+ // published `lib/commonjs/index.js`). Biome's `useImportType` only sees the
4
+ // `React.FC` type annotations, not the JSX lowering, so it offers a "safe fix"
5
+ // that would erase this import and break the shipped bundle at runtime.
6
+ // biome-ignore lint/style/useImportType: React is a VALUE import — see above.
7
+ import React from "react";
2
8
  import {
3
- requireNativeComponent,
4
9
  NativeModules,
5
- Platform,
6
- type ViewStyle,
7
10
  type NativeSyntheticEvent,
8
- View,
9
- Text,
11
+ Platform,
12
+ requireNativeComponent,
10
13
  StyleSheet,
11
- } from 'react-native';
14
+ Text,
15
+ View,
16
+ type ViewStyle,
17
+ } from "react-native";
12
18
 
13
19
  // ---------------------------------------------------------------------------
14
20
  // Node type interfaces
@@ -41,7 +47,7 @@ export interface ModelNode {
41
47
  * cross-platform bridge-parity umbrella (#909). Use `modelNodes` on iOS.
42
48
  */
43
49
  export interface GeometryNode {
44
- type: 'box' | 'cube' | 'sphere' | 'cylinder' | 'plane';
50
+ type: "box" | "cube" | "sphere" | "cylinder" | "plane";
45
51
  size?: [number, number, number];
46
52
  position?: [number, number, number];
47
53
  rotation?: [number, number, number];
@@ -68,7 +74,7 @@ export interface GeometryNode {
68
74
  * cross-platform bridge-parity umbrella (#909).
69
75
  */
70
76
  export interface LightNode {
71
- type: 'directional' | 'point' | 'spot';
77
+ type: "directional" | "point" | "spot";
72
78
  intensity?: number;
73
79
  color?: string;
74
80
  position?: [number, number, number];
@@ -84,13 +90,29 @@ export interface TapEvent {
84
90
  x: number;
85
91
  y: number;
86
92
  z: number;
87
- /** Name of the tapped node, if any. */
88
- nodeName?: string;
93
+ /**
94
+ * Name of the tapped model: its file's base name without extension
95
+ * (`models/robot.glb` → `robot`). Never an asset-internal mesh name — a tap
96
+ * inside a model always reports the model.
97
+ *
98
+ * **Measured on Android and iOS** (#3086) — the iOS run named the
99
+ * tapped model on every dispatch; see {@link SceneViewProps.onTap | `onTap`}.
100
+ *
101
+ * `null` — never `undefined` — when the tap hit no model: an untitled
102
+ * geometry node or nothing at all on Android, a plane or a miss in Android
103
+ * AR, and *every* `ARSceneView` tap on iOS, where `SceneViewSwift`'s
104
+ * `ARSceneView` exposes no entity hit-test hook and so can only resolve the
105
+ * surface point (#2051).
106
+ *
107
+ * Every native dispatch path writes the key, so `nodeName == null` is the one
108
+ * guard that covers both views on both platforms.
109
+ */
110
+ nodeName: string | null;
89
111
  }
90
112
 
91
113
  export interface PlaneDetectedEvent {
92
114
  id: string;
93
- type: 'horizontal' | 'vertical';
115
+ type: "horizontal" | "vertical";
94
116
  center: [number, number, number];
95
117
  extent: [number, number];
96
118
  }
@@ -108,7 +130,7 @@ export interface PlaneDetectedEvent {
108
130
  * back to orbit (the per-mode switch is an iOS-first v4.3.0 addition —
109
131
  * the Android side is tracked in issue #1051).
110
132
  */
111
- export type CameraControlMode = 'orbit' | 'pan' | 'firstPerson';
133
+ export type CameraControlMode = "orbit" | "pan" | "firstPerson";
112
134
 
113
135
  export interface SceneViewProps {
114
136
  style?: ViewStyle;
@@ -131,7 +153,17 @@ export interface SceneViewProps {
131
153
  */
132
154
  lightNodes?: LightNode[];
133
155
 
134
- /** Enable default orbit camera controls. Default: true. */
156
+ /**
157
+ * Enable default orbit camera controls. Default: `true`.
158
+ *
159
+ * @deprecated Superseded by {@link SceneViewProps.cameraControlMode}, and
160
+ * **inert on iOS**: the two would contradict each other — nothing can say
161
+ * which wins for `cameraOrbit: false, cameraControlMode: 'orbit'` — so the
162
+ * iOS bridge deliberately reads only `cameraControlMode`. Still honoured on
163
+ * Android. There is currently no way to freeze the camera from this bridge
164
+ * on iOS; `SceneViewSwift` has `cameraGesturesEnabled` but it is not exposed
165
+ * here yet.
166
+ */
135
167
  cameraOrbit?: boolean;
136
168
 
137
169
  /**
@@ -152,9 +184,39 @@ export interface SceneViewProps {
152
184
  /**
153
185
  * Called when the user taps inside the scene.
154
186
  *
155
- * The event payload carries the world-space tap coordinates. On Android the
156
- * tapped node's `nodeName` is included when the tap hits a node; on iOS AR
157
- * the tap reports the surface point only, so `nodeName` is absent there.
187
+ * Dispatched on **Android and iOS**. The iOS half was measured on an iPhone
188
+ * 17 Pro Max simulator — 5 taps on a rendering model, 5 dispatches, the
189
+ * model's name every time
190
+ * ({@link https://github.com/sceneview/sceneview/issues/3086 | #3086}).
191
+ * The sibling Flutter bridge still never fires its 3D `onTap` on iOS
192
+ * ({@link https://github.com/sceneview/sceneview/issues/3045 | #3045}); the
193
+ * same run showed that is Flutter's platform-view touch delivery, not this
194
+ * shared RealityKit path, so it does not apply here.
195
+ *
196
+ * The event payload carries the world-space position of the tapped model and
197
+ * its `nodeName` (the model file's base name without extension). A tap that
198
+ * hits no model reports `nodeName: null` — with
199
+ * the tapped node's real world position on Android when it landed on an
200
+ * (unnamed) geometry node, and `0, 0, 0` when it hit nothing at all. That
201
+ * `0, 0, 0` miss is **in practice Android-only**: iOS resolves the tap
202
+ * through RealityKit's entity-targeted gesture, which fires only on a hit,
203
+ * so a tap on empty space dispatches no `onTap` event at all — count taps
204
+ * and you will see fewer events on iOS, not a `0, 0, 0` one. (iOS emits
205
+ * `0, 0, 0` only if a hit entity resolves outside every loaded model, which
206
+ * the content root's contents make unreachable today.)
207
+ *
208
+ * On {@link ARSceneViewProps | `ARSceneView`} *what a hit reports* differs by
209
+ * platform:
210
+ * - **Android**: the AR view hit-tests the scene, so a tap on a model reports
211
+ * that model's file base name exactly as `SceneView` does; a tap on a plane
212
+ * or on nothing reports `null`.
213
+ * - **iOS**: always `null` — `SceneViewSwift.ARSceneView` exposes no entity
214
+ * hit-test hook, so the AR tap can only resolve the surface point. Tracked
215
+ * under {@link https://github.com/sceneview/sceneview/issues/2051 | #2051}.
216
+ *
217
+ * The *key* is written on every dispatch path of both views on both
218
+ * platforms, so one `nodeName == null` check remains the correct "the tap hit
219
+ * no model" test everywhere; it is never omitted and never `undefined`.
158
220
  */
159
221
  onTap?: (event: NativeSyntheticEvent<TapEvent>) => void;
160
222
  }
@@ -200,14 +262,14 @@ export interface ARSceneViewProps extends SceneViewProps {
200
262
  // Native components (only available on Android and iOS)
201
263
  // ---------------------------------------------------------------------------
202
264
 
203
- const isNativeAvailable = Platform.OS === 'android' || Platform.OS === 'ios';
265
+ const isNativeAvailable = Platform.OS === "android" || Platform.OS === "ios";
204
266
 
205
267
  const NativeSceneView = isNativeAvailable
206
- ? requireNativeComponent<SceneViewProps>('RNSceneView')
268
+ ? requireNativeComponent<SceneViewProps>("RNSceneView")
207
269
  : null;
208
270
 
209
271
  const NativeARSceneView = isNativeAvailable
210
- ? requireNativeComponent<ARSceneViewProps>('RNARSceneView')
272
+ ? requireNativeComponent<ARSceneViewProps>("RNARSceneView")
211
273
  : null;
212
274
 
213
275
  // ---------------------------------------------------------------------------
@@ -223,12 +285,12 @@ const UnsupportedView: React.FC<{ name: string }> = ({ name }) => (
223
285
  const fallbackStyles = StyleSheet.create({
224
286
  container: {
225
287
  flex: 1,
226
- justifyContent: 'center',
227
- alignItems: 'center',
228
- backgroundColor: '#1a1a2e',
288
+ justifyContent: "center",
289
+ alignItems: "center",
290
+ backgroundColor: "#1a1a2e",
229
291
  },
230
292
  text: {
231
- color: '#aaa',
293
+ color: "#aaa",
232
294
  fontSize: 16,
233
295
  },
234
296
  });
@@ -282,8 +344,7 @@ interface RNARRecorderModule {
282
344
  saveToPhotoLibrary(movPath: string): Promise<void>;
283
345
  }
284
346
 
285
- const NativeARRecorder: RNARRecorderModule | undefined =
286
- NativeModules.RNARRecorder;
347
+ const NativeARRecorder: RNARRecorderModule | undefined = NativeModules.RNARRecorder;
287
348
 
288
349
  /**
289
350
  * Records an AR session to a video file (v4.3.0).
@@ -309,14 +370,14 @@ const NativeARRecorder: RNARRecorderModule | undefined =
309
370
  export class ARRecorder {
310
371
  /** `true` when {@link ARRecorder} is supported on the current platform. */
311
372
  static get isSupported(): boolean {
312
- return Platform.OS === 'ios' && NativeARRecorder != null;
373
+ return Platform.OS === "ios" && NativeARRecorder != null;
313
374
  }
314
375
 
315
376
  private rejectUnsupported(): Promise<never> {
316
377
  return Promise.reject(
317
378
  new Error(
318
- 'ARRecorder is currently only supported on iOS. Android AR session ' +
319
- 'recording is tracked in issue #1051.'
379
+ "ARRecorder is currently only supported on iOS. Android AR session " +
380
+ "recording is tracked in issue #1051."
320
381
  )
321
382
  );
322
383
  }