@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/README.md +152 -32
- package/android/build.gradle.kts +21 -0
- package/android/src/main/java/io/github/sceneview/reactnative/ARSceneViewManager.kt +5 -0
- package/android/src/main/java/io/github/sceneview/reactnative/SceneViewEvents.kt +8 -2
- package/android/src/main/java/io/github/sceneview/reactnative/SceneViewManager.kt +29 -1
- package/ios/SceneViewModule.swift +147 -114
- package/lib/commonjs/index.js +16 -9
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/index.js +17 -11
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/index.d.ts +68 -12
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +4 -2
- package/react-native-sceneview.podspec +35 -10
- package/src/index.tsx +90 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sceneview-sdk/react-native",
|
|
3
|
-
"version": "4.
|
|
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": "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
# `
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
9
|
-
|
|
11
|
+
Platform,
|
|
12
|
+
requireNativeComponent,
|
|
10
13
|
StyleSheet,
|
|
11
|
-
|
|
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:
|
|
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:
|
|
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
|
-
/**
|
|
88
|
-
|
|
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:
|
|
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 =
|
|
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
|
-
/**
|
|
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
|
-
*
|
|
156
|
-
*
|
|
157
|
-
*
|
|
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 ===
|
|
265
|
+
const isNativeAvailable = Platform.OS === "android" || Platform.OS === "ios";
|
|
204
266
|
|
|
205
267
|
const NativeSceneView = isNativeAvailable
|
|
206
|
-
? requireNativeComponent<SceneViewProps>(
|
|
268
|
+
? requireNativeComponent<SceneViewProps>("RNSceneView")
|
|
207
269
|
: null;
|
|
208
270
|
|
|
209
271
|
const NativeARSceneView = isNativeAvailable
|
|
210
|
-
? requireNativeComponent<ARSceneViewProps>(
|
|
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:
|
|
227
|
-
alignItems:
|
|
228
|
-
backgroundColor:
|
|
288
|
+
justifyContent: "center",
|
|
289
|
+
alignItems: "center",
|
|
290
|
+
backgroundColor: "#1a1a2e",
|
|
229
291
|
},
|
|
230
292
|
text: {
|
|
231
|
-
color:
|
|
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 ===
|
|
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
|
-
|
|
319
|
-
|
|
379
|
+
"ARRecorder is currently only supported on iOS. Android AR session " +
|
|
380
|
+
"recording is tracked in issue #1051."
|
|
320
381
|
)
|
|
321
382
|
);
|
|
322
383
|
}
|