@react-three/fiber 8.13.8 → 8.14.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @react-three/fiber
2
2
 
3
+ ## 8.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 89e96bf4: feat: react-native-web, native globals fixes
8
+
9
+ ## 8.13.9
10
+
11
+ ### Patch Changes
12
+
13
+ - 44d57b3c: fix(native): TextureLoader should remain consistent with FileLoader
14
+
3
15
  ## 8.13.8
4
16
 
5
17
  ### Patch Changes
@@ -1 +1 @@
1
- export declare function polyfills(): (() => void) | undefined;
1
+ export declare function polyfills(): void;
@@ -9,15 +9,15 @@ var THREE = require('three');
9
9
  var reactNative = require('react-native');
10
10
  var expoGl = require('expo-gl');
11
11
  var itsFine = require('its-fine');
12
- var Pressability = require('react-native/Libraries/Pressability/Pressability');
12
+ var expoAsset = require('expo-asset');
13
+ var fs = require('expo-file-system');
14
+ var base64Js = require('base64-js');
13
15
  require('react-reconciler/constants');
14
16
  require('zustand');
15
17
  require('react-reconciler');
16
18
  require('scheduler');
17
19
  require('suspend-react');
18
20
 
19
- function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
20
-
21
21
  function _interopNamespace(e) {
22
22
  if (e && e.__esModule) return e;
23
23
  var n = Object.create(null);
@@ -38,28 +38,7 @@ function _interopNamespace(e) {
38
38
 
39
39
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
40
40
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
41
- var Pressability__default = /*#__PURE__*/_interopDefault(Pressability);
42
-
43
- /* eslint-enable import/default, import/no-named-as-default, import/no-named-as-default-member */
44
-
45
- const EVENTS = {
46
- PRESS: 'onPress',
47
- PRESSIN: 'onPressIn',
48
- PRESSOUT: 'onPressOut',
49
- LONGPRESS: 'onLongPress',
50
- HOVERIN: 'onHoverIn',
51
- HOVEROUT: 'onHoverOut',
52
- PRESSMOVE: 'onPressMove'
53
- };
54
- const DOM_EVENTS = {
55
- [EVENTS.PRESS]: 'onClick',
56
- [EVENTS.PRESSIN]: 'onPointerDown',
57
- [EVENTS.PRESSOUT]: 'onPointerUp',
58
- [EVENTS.LONGPRESS]: 'onDoubleClick',
59
- [EVENTS.HOVERIN]: 'onPointerOver',
60
- [EVENTS.HOVEROUT]: 'onPointerOut',
61
- [EVENTS.PRESSMOVE]: 'onPointerMove'
62
- };
41
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
63
42
 
64
43
  /** Default R3F event manager for react-native */
65
44
  function createTouchEvents(store) {
@@ -75,9 +54,26 @@ function createTouchEvents(store) {
75
54
  event.nativeEvent.offsetY = event.nativeEvent.locationY;
76
55
 
77
56
  // Emulate DOM event
78
- const callback = handlePointer(DOM_EVENTS[name]);
79
- return callback(event.nativeEvent);
57
+ const callback = handlePointer(name);
58
+ callback(event.nativeEvent);
59
+ return true;
80
60
  };
61
+ const responder = reactNative.PanResponder.create({
62
+ onStartShouldSetPanResponder: () => true,
63
+ onMoveShouldSetPanResponder: () => true,
64
+ onMoveShouldSetPanResponderCapture: () => true,
65
+ onPanResponderTerminationRequest: () => true,
66
+ onStartShouldSetPanResponderCapture: e => handleTouch(e, 'onPointerCapture'),
67
+ onPanResponderStart: e => handleTouch(e, 'onPointerDown'),
68
+ onPanResponderMove: e => handleTouch(e, 'onPointerMove'),
69
+ onPanResponderEnd: (e, state) => {
70
+ handleTouch(e, 'onPointerUp');
71
+ if (Math.hypot(state.dx, state.dy) < 20) handleTouch(e, 'onClick');
72
+ },
73
+ onPanResponderRelease: e => handleTouch(e, 'onPointerLeave'),
74
+ onPanResponderTerminate: e => handleTouch(e, 'onLostPointerCapture'),
75
+ onPanResponderReject: e => handleTouch(e, 'onLostPointerCapture')
76
+ });
81
77
  return {
82
78
  priority: 1,
83
79
  enabled: true,
@@ -88,17 +84,16 @@ function createTouchEvents(store) {
88
84
  state.raycaster.setFromCamera(state.pointer, state.camera);
89
85
  },
90
86
  connected: undefined,
91
- handlers: Object.values(EVENTS).reduce((acc, name) => ({
92
- ...acc,
93
- [name]: event => handleTouch(event, name)
94
- }), {}),
87
+ handlers: responder.panHandlers,
95
88
  update: () => {
96
89
  var _internal$lastEvent;
97
90
  const {
98
91
  events,
99
92
  internal
100
93
  } = store.getState();
101
- if ((_internal$lastEvent = internal.lastEvent) != null && _internal$lastEvent.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
94
+ if ((_internal$lastEvent = internal.lastEvent) != null && _internal$lastEvent.current && events.handlers) {
95
+ handlePointer('onPointerMove')(internal.lastEvent.current);
96
+ }
102
97
  },
103
98
  connect: () => {
104
99
  const {
@@ -106,130 +101,24 @@ function createTouchEvents(store) {
106
101
  events
107
102
  } = store.getState();
108
103
  events.disconnect == null ? void 0 : events.disconnect();
109
- const connected = new Pressability__default["default"](events.handlers);
110
104
  set(state => ({
111
105
  events: {
112
106
  ...state.events,
113
- connected
107
+ connected: true
114
108
  }
115
109
  }));
116
- const handlers = connected.getEventHandlers();
117
- return handlers;
118
110
  },
119
111
  disconnect: () => {
120
112
  const {
121
- set,
122
- events
113
+ set
123
114
  } = store.getState();
124
- if (events.connected) {
125
- events.connected.reset();
126
- set(state => ({
127
- events: {
128
- ...state.events,
129
- connected: undefined
130
- }
131
- }));
132
- }
133
- }
134
- };
135
- }
136
-
137
- // Check if expo-asset is installed (available with expo modules)
138
- let expAsset;
139
- try {
140
- var _require;
141
- expAsset = (_require = require('expo-asset')) == null ? void 0 : _require.Asset;
142
- } catch (_) {}
143
-
144
- /**
145
- * Generates an asset based on input type.
146
- */
147
- function getAsset(input) {
148
- switch (typeof input) {
149
- case 'string':
150
- return expAsset.fromURI(input);
151
- case 'number':
152
- return expAsset.fromModule(input);
153
- default:
154
- throw new Error('R3F: Invalid asset! Must be a URI or module.');
155
- }
156
- }
157
- let injected = false;
158
- function polyfills() {
159
- if (!expAsset || injected) return;
160
- injected = true;
161
-
162
- // Don't pre-process urls, let expo-asset generate an absolute URL
163
- const extractUrlBase = THREE__namespace.LoaderUtils.extractUrlBase.bind(THREE__namespace.LoaderUtils);
164
- THREE__namespace.LoaderUtils.extractUrlBase = url => typeof url === 'string' ? extractUrlBase(url) : './';
165
-
166
- // There's no Image in native, so create a data texture instead
167
- const prevTextureLoad = THREE__namespace.TextureLoader.prototype.load;
168
- THREE__namespace.TextureLoader.prototype.load = function load(url, onLoad, onProgress, onError) {
169
- const texture = new THREE__namespace.Texture();
170
-
171
- // @ts-ignore
172
- texture.isDataTexture = true;
173
- getAsset(url).downloadAsync().then(asset => {
174
- texture.image = {
175
- data: asset,
176
- width: asset.width,
177
- height: asset.height
178
- };
179
- texture.flipY = true;
180
- texture.unpackAlignment = 1;
181
- texture.needsUpdate = true;
182
- onLoad == null ? void 0 : onLoad(texture);
183
- }).catch(onError);
184
- return texture;
185
- };
186
-
187
- // Fetches assets via XMLHttpRequest
188
- const prevFileLoad = THREE__namespace.FileLoader.prototype.load;
189
- THREE__namespace.FileLoader.prototype.load = function (url, onLoad, onProgress, onError) {
190
- if (this.path) url = this.path + url;
191
- const request = new XMLHttpRequest();
192
- getAsset(url).downloadAsync().then(asset => {
193
- request.open('GET', asset.uri, true);
194
- request.addEventListener('load', event => {
195
- if (request.status === 200) {
196
- onLoad == null ? void 0 : onLoad(request.response);
197
- this.manager.itemEnd(url);
198
- } else {
199
- onError == null ? void 0 : onError(event);
200
- this.manager.itemError(url);
201
- this.manager.itemEnd(url);
115
+ set(state => ({
116
+ events: {
117
+ ...state.events,
118
+ connected: false
202
119
  }
203
- }, false);
204
- request.addEventListener('progress', event => {
205
- onProgress == null ? void 0 : onProgress(event);
206
- }, false);
207
- request.addEventListener('error', event => {
208
- onError == null ? void 0 : onError(event);
209
- this.manager.itemError(url);
210
- this.manager.itemEnd(url);
211
- }, false);
212
- request.addEventListener('abort', event => {
213
- onError == null ? void 0 : onError(event);
214
- this.manager.itemError(url);
215
- this.manager.itemEnd(url);
216
- }, false);
217
- if (this.responseType) request.responseType = this.responseType;
218
- if (this.withCredentials) request.withCredentials = this.withCredentials;
219
- for (const header in this.requestHeader) {
220
- request.setRequestHeader(header, this.requestHeader[header]);
221
- }
222
- request.send(null);
223
- this.manager.itemStart(url);
224
- });
225
- return request;
226
- };
227
-
228
- // Cleanup function
229
- return () => {
230
- THREE__namespace.LoaderUtils.extractUrlBase = extractUrlBase;
231
- THREE__namespace.TextureLoader.prototype.load = prevTextureLoad;
232
- THREE__namespace.FileLoader.prototype.load = prevFileLoad;
120
+ }));
121
+ }
233
122
  };
234
123
  }
235
124
 
@@ -285,9 +174,6 @@ const CanvasImpl = /*#__PURE__*/React__namespace.forwardRef(({
285
174
  if (error) throw error;
286
175
  const viewRef = React__namespace.useRef(null);
287
176
  const root = React__namespace.useRef(null);
288
-
289
- // Inject and cleanup RN polyfills if able
290
- React__namespace.useLayoutEffect(() => polyfills(), []);
291
177
  const onLayout = React__namespace.useCallback(e => {
292
178
  const {
293
179
  width,
@@ -346,8 +232,7 @@ const CanvasImpl = /*#__PURE__*/React__namespace.forwardRef(({
346
232
  // Overwrite onCreated to apply RN bindings
347
233
  onCreated: state => {
348
234
  // Bind events after creation
349
- const handlers = state.events.connect == null ? void 0 : state.events.connect(viewRef.current);
350
- setBind(handlers);
235
+ setBind(state.events.handlers);
351
236
 
352
237
  // Bind render to RN bridge
353
238
  const context = state.gl.getContext();
@@ -395,6 +280,180 @@ const Canvas = /*#__PURE__*/React__namespace.forwardRef(function CanvasWrapper(p
395
280
  })));
396
281
  });
397
282
 
283
+ function polyfills() {
284
+ // Patch Blob for ArrayBuffer if unsupported
285
+ try {
286
+ new Blob([new ArrayBuffer(4)]);
287
+ } catch (_) {
288
+ global.Blob = class extends Blob {
289
+ constructor(parts, options) {
290
+ super(parts == null ? void 0 : parts.map(part => {
291
+ if (part instanceof ArrayBuffer || ArrayBuffer.isView(part)) {
292
+ part = base64Js.fromByteArray(new Uint8Array(part));
293
+ }
294
+ return part;
295
+ }), options);
296
+ }
297
+ };
298
+ }
299
+ function uuidv4() {
300
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
301
+ const r = Math.random() * 16 | 0,
302
+ v = c == 'x' ? r : r & 0x3 | 0x8;
303
+ return v.toString(16);
304
+ });
305
+ }
306
+ async function getAsset(input) {
307
+ if (typeof input === 'string') {
308
+ // Point to storage if preceded with fs path
309
+ if (input.startsWith('file:')) return {
310
+ localUri: input
311
+ };
312
+
313
+ // Unpack Blobs from react-native BlobManager
314
+ if (input.startsWith('blob:')) {
315
+ const blob = await new Promise((res, rej) => {
316
+ const xhr = new XMLHttpRequest();
317
+ xhr.open('GET', input);
318
+ xhr.responseType = 'blob';
319
+ xhr.onload = () => res(xhr.response);
320
+ xhr.onerror = rej;
321
+ xhr.send();
322
+ });
323
+ const data = await new Promise((res, rej) => {
324
+ const reader = new FileReader();
325
+ reader.onload = () => res(reader.result);
326
+ reader.onerror = rej;
327
+ reader.readAsText(blob);
328
+ });
329
+ input = `data:${blob.type};base64,${data}`;
330
+ }
331
+
332
+ // Create safe URI for JSI
333
+ if (input.startsWith('data:')) {
334
+ const [header, data] = input.split(',');
335
+ const [, type] = header.split('/');
336
+ const localUri = fs__namespace.cacheDirectory + uuidv4() + `.${type}`;
337
+ await fs__namespace.writeAsStringAsync(localUri, data, {
338
+ encoding: fs__namespace.EncodingType.Base64
339
+ });
340
+ return {
341
+ localUri
342
+ };
343
+ }
344
+ }
345
+
346
+ // Download bundler module or external URL
347
+ const asset = expoAsset.Asset.fromModule(input);
348
+
349
+ // Unpack assets in Android Release Mode
350
+ if (!asset.uri.includes(':')) {
351
+ const localUri = `${fs__namespace.cacheDirectory}ExponentAsset-${asset.hash}.${asset.type}`;
352
+ await fs__namespace.copyAsync({
353
+ from: asset.uri,
354
+ to: localUri
355
+ });
356
+ return {
357
+ localUri
358
+ };
359
+ }
360
+
361
+ // Otherwise, resolve from registry
362
+ return asset.downloadAsync();
363
+ }
364
+
365
+ // Don't pre-process urls, let expo-asset generate an absolute URL
366
+ const extractUrlBase = THREE__namespace.LoaderUtils.extractUrlBase.bind(THREE__namespace.LoaderUtils);
367
+ THREE__namespace.LoaderUtils.extractUrlBase = url => typeof url === 'string' ? extractUrlBase(url) : './';
368
+
369
+ // There's no Image in native, so create a data texture instead
370
+ THREE__namespace.TextureLoader.prototype.load = function load(url, onLoad, onProgress, onError) {
371
+ if (this.path) url = this.path + url;
372
+ const texture = new THREE__namespace.Texture();
373
+ getAsset(url).then(async asset => {
374
+ const uri = asset.localUri || asset.uri;
375
+ if (!asset.width || !asset.height) {
376
+ const {
377
+ width,
378
+ height
379
+ } = await new Promise((res, rej) => reactNative.Image.getSize(uri, (width, height) => res({
380
+ width,
381
+ height
382
+ }), rej));
383
+ asset.width = width;
384
+ asset.height = height;
385
+ }
386
+ texture.image = {
387
+ data: {
388
+ localUri: uri
389
+ },
390
+ width: asset.width,
391
+ height: asset.height
392
+ };
393
+ texture.flipY = true;
394
+ texture.unpackAlignment = 1;
395
+ texture.needsUpdate = true;
396
+
397
+ // Force non-DOM upload for EXGL fast paths
398
+ // @ts-ignore
399
+ texture.isDataTexture = true;
400
+ onLoad == null ? void 0 : onLoad(texture);
401
+ }).catch(onError);
402
+ return texture;
403
+ };
404
+
405
+ // Fetches assets via XMLHttpRequest
406
+ THREE__namespace.FileLoader.prototype.load = function load(url, onLoad, onProgress, onError) {
407
+ if (this.path) url = this.path + url;
408
+ const request = new XMLHttpRequest();
409
+ getAsset(url).then(async asset => {
410
+ let uri = asset.localUri || asset.uri;
411
+
412
+ // Make FS paths web-safe
413
+ if (asset.uri.startsWith('file://')) {
414
+ const data = await fs__namespace.readAsStringAsync(asset.uri, {
415
+ encoding: fs__namespace.EncodingType.Base64
416
+ });
417
+ uri = `data:application/octet-stream;base64,${data}`;
418
+ }
419
+ request.open('GET', uri, true);
420
+ request.addEventListener('load', event => {
421
+ if (request.status === 200) {
422
+ onLoad == null ? void 0 : onLoad(request.response);
423
+ this.manager.itemEnd(url);
424
+ } else {
425
+ onError == null ? void 0 : onError(event);
426
+ this.manager.itemError(url);
427
+ this.manager.itemEnd(url);
428
+ }
429
+ }, false);
430
+ request.addEventListener('progress', event => {
431
+ onProgress == null ? void 0 : onProgress(event);
432
+ }, false);
433
+ request.addEventListener('error', event => {
434
+ onError == null ? void 0 : onError(event);
435
+ this.manager.itemError(url);
436
+ this.manager.itemEnd(url);
437
+ }, false);
438
+ request.addEventListener('abort', event => {
439
+ onError == null ? void 0 : onError(event);
440
+ this.manager.itemError(url);
441
+ this.manager.itemEnd(url);
442
+ }, false);
443
+ if (this.responseType) request.responseType = this.responseType;
444
+ if (this.withCredentials) request.withCredentials = this.withCredentials;
445
+ for (const header in this.requestHeader) {
446
+ request.setRequestHeader(header, this.requestHeader[header]);
447
+ }
448
+ request.send(null);
449
+ this.manager.itemStart(url);
450
+ }).catch(onError);
451
+ return request;
452
+ };
453
+ }
454
+
455
+ polyfills();
456
+
398
457
  exports.ReactThreeFiber = index.threeTypes;
399
458
  exports._roots = index.roots;
400
459
  exports.act = index.act;
@@ -9,15 +9,15 @@ var THREE = require('three');
9
9
  var reactNative = require('react-native');
10
10
  var expoGl = require('expo-gl');
11
11
  var itsFine = require('its-fine');
12
- var Pressability = require('react-native/Libraries/Pressability/Pressability');
12
+ var expoAsset = require('expo-asset');
13
+ var fs = require('expo-file-system');
14
+ var base64Js = require('base64-js');
13
15
  require('react-reconciler/constants');
14
16
  require('zustand');
15
17
  require('react-reconciler');
16
18
  require('scheduler');
17
19
  require('suspend-react');
18
20
 
19
- function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
20
-
21
21
  function _interopNamespace(e) {
22
22
  if (e && e.__esModule) return e;
23
23
  var n = Object.create(null);
@@ -38,28 +38,7 @@ function _interopNamespace(e) {
38
38
 
39
39
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
40
40
  var THREE__namespace = /*#__PURE__*/_interopNamespace(THREE);
41
- var Pressability__default = /*#__PURE__*/_interopDefault(Pressability);
42
-
43
- /* eslint-enable import/default, import/no-named-as-default, import/no-named-as-default-member */
44
-
45
- const EVENTS = {
46
- PRESS: 'onPress',
47
- PRESSIN: 'onPressIn',
48
- PRESSOUT: 'onPressOut',
49
- LONGPRESS: 'onLongPress',
50
- HOVERIN: 'onHoverIn',
51
- HOVEROUT: 'onHoverOut',
52
- PRESSMOVE: 'onPressMove'
53
- };
54
- const DOM_EVENTS = {
55
- [EVENTS.PRESS]: 'onClick',
56
- [EVENTS.PRESSIN]: 'onPointerDown',
57
- [EVENTS.PRESSOUT]: 'onPointerUp',
58
- [EVENTS.LONGPRESS]: 'onDoubleClick',
59
- [EVENTS.HOVERIN]: 'onPointerOver',
60
- [EVENTS.HOVEROUT]: 'onPointerOut',
61
- [EVENTS.PRESSMOVE]: 'onPointerMove'
62
- };
41
+ var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
63
42
 
64
43
  /** Default R3F event manager for react-native */
65
44
  function createTouchEvents(store) {
@@ -75,9 +54,26 @@ function createTouchEvents(store) {
75
54
  event.nativeEvent.offsetY = event.nativeEvent.locationY;
76
55
 
77
56
  // Emulate DOM event
78
- const callback = handlePointer(DOM_EVENTS[name]);
79
- return callback(event.nativeEvent);
57
+ const callback = handlePointer(name);
58
+ callback(event.nativeEvent);
59
+ return true;
80
60
  };
61
+ const responder = reactNative.PanResponder.create({
62
+ onStartShouldSetPanResponder: () => true,
63
+ onMoveShouldSetPanResponder: () => true,
64
+ onMoveShouldSetPanResponderCapture: () => true,
65
+ onPanResponderTerminationRequest: () => true,
66
+ onStartShouldSetPanResponderCapture: e => handleTouch(e, 'onPointerCapture'),
67
+ onPanResponderStart: e => handleTouch(e, 'onPointerDown'),
68
+ onPanResponderMove: e => handleTouch(e, 'onPointerMove'),
69
+ onPanResponderEnd: (e, state) => {
70
+ handleTouch(e, 'onPointerUp');
71
+ if (Math.hypot(state.dx, state.dy) < 20) handleTouch(e, 'onClick');
72
+ },
73
+ onPanResponderRelease: e => handleTouch(e, 'onPointerLeave'),
74
+ onPanResponderTerminate: e => handleTouch(e, 'onLostPointerCapture'),
75
+ onPanResponderReject: e => handleTouch(e, 'onLostPointerCapture')
76
+ });
81
77
  return {
82
78
  priority: 1,
83
79
  enabled: true,
@@ -88,17 +84,16 @@ function createTouchEvents(store) {
88
84
  state.raycaster.setFromCamera(state.pointer, state.camera);
89
85
  },
90
86
  connected: undefined,
91
- handlers: Object.values(EVENTS).reduce((acc, name) => ({
92
- ...acc,
93
- [name]: event => handleTouch(event, name)
94
- }), {}),
87
+ handlers: responder.panHandlers,
95
88
  update: () => {
96
89
  var _internal$lastEvent;
97
90
  const {
98
91
  events,
99
92
  internal
100
93
  } = store.getState();
101
- if ((_internal$lastEvent = internal.lastEvent) != null && _internal$lastEvent.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
94
+ if ((_internal$lastEvent = internal.lastEvent) != null && _internal$lastEvent.current && events.handlers) {
95
+ handlePointer('onPointerMove')(internal.lastEvent.current);
96
+ }
102
97
  },
103
98
  connect: () => {
104
99
  const {
@@ -106,130 +101,24 @@ function createTouchEvents(store) {
106
101
  events
107
102
  } = store.getState();
108
103
  events.disconnect == null ? void 0 : events.disconnect();
109
- const connected = new Pressability__default["default"](events.handlers);
110
104
  set(state => ({
111
105
  events: {
112
106
  ...state.events,
113
- connected
107
+ connected: true
114
108
  }
115
109
  }));
116
- const handlers = connected.getEventHandlers();
117
- return handlers;
118
110
  },
119
111
  disconnect: () => {
120
112
  const {
121
- set,
122
- events
113
+ set
123
114
  } = store.getState();
124
- if (events.connected) {
125
- events.connected.reset();
126
- set(state => ({
127
- events: {
128
- ...state.events,
129
- connected: undefined
130
- }
131
- }));
132
- }
133
- }
134
- };
135
- }
136
-
137
- // Check if expo-asset is installed (available with expo modules)
138
- let expAsset;
139
- try {
140
- var _require;
141
- expAsset = (_require = require('expo-asset')) == null ? void 0 : _require.Asset;
142
- } catch (_) {}
143
-
144
- /**
145
- * Generates an asset based on input type.
146
- */
147
- function getAsset(input) {
148
- switch (typeof input) {
149
- case 'string':
150
- return expAsset.fromURI(input);
151
- case 'number':
152
- return expAsset.fromModule(input);
153
- default:
154
- throw new Error('R3F: Invalid asset! Must be a URI or module.');
155
- }
156
- }
157
- let injected = false;
158
- function polyfills() {
159
- if (!expAsset || injected) return;
160
- injected = true;
161
-
162
- // Don't pre-process urls, let expo-asset generate an absolute URL
163
- const extractUrlBase = THREE__namespace.LoaderUtils.extractUrlBase.bind(THREE__namespace.LoaderUtils);
164
- THREE__namespace.LoaderUtils.extractUrlBase = url => typeof url === 'string' ? extractUrlBase(url) : './';
165
-
166
- // There's no Image in native, so create a data texture instead
167
- const prevTextureLoad = THREE__namespace.TextureLoader.prototype.load;
168
- THREE__namespace.TextureLoader.prototype.load = function load(url, onLoad, onProgress, onError) {
169
- const texture = new THREE__namespace.Texture();
170
-
171
- // @ts-ignore
172
- texture.isDataTexture = true;
173
- getAsset(url).downloadAsync().then(asset => {
174
- texture.image = {
175
- data: asset,
176
- width: asset.width,
177
- height: asset.height
178
- };
179
- texture.flipY = true;
180
- texture.unpackAlignment = 1;
181
- texture.needsUpdate = true;
182
- onLoad == null ? void 0 : onLoad(texture);
183
- }).catch(onError);
184
- return texture;
185
- };
186
-
187
- // Fetches assets via XMLHttpRequest
188
- const prevFileLoad = THREE__namespace.FileLoader.prototype.load;
189
- THREE__namespace.FileLoader.prototype.load = function (url, onLoad, onProgress, onError) {
190
- if (this.path) url = this.path + url;
191
- const request = new XMLHttpRequest();
192
- getAsset(url).downloadAsync().then(asset => {
193
- request.open('GET', asset.uri, true);
194
- request.addEventListener('load', event => {
195
- if (request.status === 200) {
196
- onLoad == null ? void 0 : onLoad(request.response);
197
- this.manager.itemEnd(url);
198
- } else {
199
- onError == null ? void 0 : onError(event);
200
- this.manager.itemError(url);
201
- this.manager.itemEnd(url);
115
+ set(state => ({
116
+ events: {
117
+ ...state.events,
118
+ connected: false
202
119
  }
203
- }, false);
204
- request.addEventListener('progress', event => {
205
- onProgress == null ? void 0 : onProgress(event);
206
- }, false);
207
- request.addEventListener('error', event => {
208
- onError == null ? void 0 : onError(event);
209
- this.manager.itemError(url);
210
- this.manager.itemEnd(url);
211
- }, false);
212
- request.addEventListener('abort', event => {
213
- onError == null ? void 0 : onError(event);
214
- this.manager.itemError(url);
215
- this.manager.itemEnd(url);
216
- }, false);
217
- if (this.responseType) request.responseType = this.responseType;
218
- if (this.withCredentials) request.withCredentials = this.withCredentials;
219
- for (const header in this.requestHeader) {
220
- request.setRequestHeader(header, this.requestHeader[header]);
221
- }
222
- request.send(null);
223
- this.manager.itemStart(url);
224
- });
225
- return request;
226
- };
227
-
228
- // Cleanup function
229
- return () => {
230
- THREE__namespace.LoaderUtils.extractUrlBase = extractUrlBase;
231
- THREE__namespace.TextureLoader.prototype.load = prevTextureLoad;
232
- THREE__namespace.FileLoader.prototype.load = prevFileLoad;
120
+ }));
121
+ }
233
122
  };
234
123
  }
235
124
 
@@ -285,9 +174,6 @@ const CanvasImpl = /*#__PURE__*/React__namespace.forwardRef(({
285
174
  if (error) throw error;
286
175
  const viewRef = React__namespace.useRef(null);
287
176
  const root = React__namespace.useRef(null);
288
-
289
- // Inject and cleanup RN polyfills if able
290
- React__namespace.useLayoutEffect(() => polyfills(), []);
291
177
  const onLayout = React__namespace.useCallback(e => {
292
178
  const {
293
179
  width,
@@ -346,8 +232,7 @@ const CanvasImpl = /*#__PURE__*/React__namespace.forwardRef(({
346
232
  // Overwrite onCreated to apply RN bindings
347
233
  onCreated: state => {
348
234
  // Bind events after creation
349
- const handlers = state.events.connect == null ? void 0 : state.events.connect(viewRef.current);
350
- setBind(handlers);
235
+ setBind(state.events.handlers);
351
236
 
352
237
  // Bind render to RN bridge
353
238
  const context = state.gl.getContext();
@@ -395,6 +280,180 @@ const Canvas = /*#__PURE__*/React__namespace.forwardRef(function CanvasWrapper(p
395
280
  })));
396
281
  });
397
282
 
283
+ function polyfills() {
284
+ // Patch Blob for ArrayBuffer if unsupported
285
+ try {
286
+ new Blob([new ArrayBuffer(4)]);
287
+ } catch (_) {
288
+ global.Blob = class extends Blob {
289
+ constructor(parts, options) {
290
+ super(parts == null ? void 0 : parts.map(part => {
291
+ if (part instanceof ArrayBuffer || ArrayBuffer.isView(part)) {
292
+ part = base64Js.fromByteArray(new Uint8Array(part));
293
+ }
294
+ return part;
295
+ }), options);
296
+ }
297
+ };
298
+ }
299
+ function uuidv4() {
300
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
301
+ const r = Math.random() * 16 | 0,
302
+ v = c == 'x' ? r : r & 0x3 | 0x8;
303
+ return v.toString(16);
304
+ });
305
+ }
306
+ async function getAsset(input) {
307
+ if (typeof input === 'string') {
308
+ // Point to storage if preceded with fs path
309
+ if (input.startsWith('file:')) return {
310
+ localUri: input
311
+ };
312
+
313
+ // Unpack Blobs from react-native BlobManager
314
+ if (input.startsWith('blob:')) {
315
+ const blob = await new Promise((res, rej) => {
316
+ const xhr = new XMLHttpRequest();
317
+ xhr.open('GET', input);
318
+ xhr.responseType = 'blob';
319
+ xhr.onload = () => res(xhr.response);
320
+ xhr.onerror = rej;
321
+ xhr.send();
322
+ });
323
+ const data = await new Promise((res, rej) => {
324
+ const reader = new FileReader();
325
+ reader.onload = () => res(reader.result);
326
+ reader.onerror = rej;
327
+ reader.readAsText(blob);
328
+ });
329
+ input = `data:${blob.type};base64,${data}`;
330
+ }
331
+
332
+ // Create safe URI for JSI
333
+ if (input.startsWith('data:')) {
334
+ const [header, data] = input.split(',');
335
+ const [, type] = header.split('/');
336
+ const localUri = fs__namespace.cacheDirectory + uuidv4() + `.${type}`;
337
+ await fs__namespace.writeAsStringAsync(localUri, data, {
338
+ encoding: fs__namespace.EncodingType.Base64
339
+ });
340
+ return {
341
+ localUri
342
+ };
343
+ }
344
+ }
345
+
346
+ // Download bundler module or external URL
347
+ const asset = expoAsset.Asset.fromModule(input);
348
+
349
+ // Unpack assets in Android Release Mode
350
+ if (!asset.uri.includes(':')) {
351
+ const localUri = `${fs__namespace.cacheDirectory}ExponentAsset-${asset.hash}.${asset.type}`;
352
+ await fs__namespace.copyAsync({
353
+ from: asset.uri,
354
+ to: localUri
355
+ });
356
+ return {
357
+ localUri
358
+ };
359
+ }
360
+
361
+ // Otherwise, resolve from registry
362
+ return asset.downloadAsync();
363
+ }
364
+
365
+ // Don't pre-process urls, let expo-asset generate an absolute URL
366
+ const extractUrlBase = THREE__namespace.LoaderUtils.extractUrlBase.bind(THREE__namespace.LoaderUtils);
367
+ THREE__namespace.LoaderUtils.extractUrlBase = url => typeof url === 'string' ? extractUrlBase(url) : './';
368
+
369
+ // There's no Image in native, so create a data texture instead
370
+ THREE__namespace.TextureLoader.prototype.load = function load(url, onLoad, onProgress, onError) {
371
+ if (this.path) url = this.path + url;
372
+ const texture = new THREE__namespace.Texture();
373
+ getAsset(url).then(async asset => {
374
+ const uri = asset.localUri || asset.uri;
375
+ if (!asset.width || !asset.height) {
376
+ const {
377
+ width,
378
+ height
379
+ } = await new Promise((res, rej) => reactNative.Image.getSize(uri, (width, height) => res({
380
+ width,
381
+ height
382
+ }), rej));
383
+ asset.width = width;
384
+ asset.height = height;
385
+ }
386
+ texture.image = {
387
+ data: {
388
+ localUri: uri
389
+ },
390
+ width: asset.width,
391
+ height: asset.height
392
+ };
393
+ texture.flipY = true;
394
+ texture.unpackAlignment = 1;
395
+ texture.needsUpdate = true;
396
+
397
+ // Force non-DOM upload for EXGL fast paths
398
+ // @ts-ignore
399
+ texture.isDataTexture = true;
400
+ onLoad == null ? void 0 : onLoad(texture);
401
+ }).catch(onError);
402
+ return texture;
403
+ };
404
+
405
+ // Fetches assets via XMLHttpRequest
406
+ THREE__namespace.FileLoader.prototype.load = function load(url, onLoad, onProgress, onError) {
407
+ if (this.path) url = this.path + url;
408
+ const request = new XMLHttpRequest();
409
+ getAsset(url).then(async asset => {
410
+ let uri = asset.localUri || asset.uri;
411
+
412
+ // Make FS paths web-safe
413
+ if (asset.uri.startsWith('file://')) {
414
+ const data = await fs__namespace.readAsStringAsync(asset.uri, {
415
+ encoding: fs__namespace.EncodingType.Base64
416
+ });
417
+ uri = `data:application/octet-stream;base64,${data}`;
418
+ }
419
+ request.open('GET', uri, true);
420
+ request.addEventListener('load', event => {
421
+ if (request.status === 200) {
422
+ onLoad == null ? void 0 : onLoad(request.response);
423
+ this.manager.itemEnd(url);
424
+ } else {
425
+ onError == null ? void 0 : onError(event);
426
+ this.manager.itemError(url);
427
+ this.manager.itemEnd(url);
428
+ }
429
+ }, false);
430
+ request.addEventListener('progress', event => {
431
+ onProgress == null ? void 0 : onProgress(event);
432
+ }, false);
433
+ request.addEventListener('error', event => {
434
+ onError == null ? void 0 : onError(event);
435
+ this.manager.itemError(url);
436
+ this.manager.itemEnd(url);
437
+ }, false);
438
+ request.addEventListener('abort', event => {
439
+ onError == null ? void 0 : onError(event);
440
+ this.manager.itemError(url);
441
+ this.manager.itemEnd(url);
442
+ }, false);
443
+ if (this.responseType) request.responseType = this.responseType;
444
+ if (this.withCredentials) request.withCredentials = this.withCredentials;
445
+ for (const header in this.requestHeader) {
446
+ request.setRequestHeader(header, this.requestHeader[header]);
447
+ }
448
+ request.send(null);
449
+ this.manager.itemStart(url);
450
+ }).catch(onError);
451
+ return request;
452
+ };
453
+ }
454
+
455
+ polyfills();
456
+
398
457
  exports.ReactThreeFiber = index.threeTypes;
399
458
  exports._roots = index.roots;
400
459
  exports.act = index.act;
@@ -3,37 +3,18 @@ export { t as ReactThreeFiber, w as _roots, v as act, o as addAfterEffect, n as
3
3
  import _extends from '@babel/runtime/helpers/esm/extends';
4
4
  import * as React from 'react';
5
5
  import * as THREE from 'three';
6
- import { PixelRatio, View, StyleSheet } from 'react-native';
6
+ import { PanResponder, PixelRatio, View, StyleSheet, Image } from 'react-native';
7
7
  import { GLView } from 'expo-gl';
8
8
  import { FiberProvider, useContextBridge } from 'its-fine';
9
- import Pressability from 'react-native/Libraries/Pressability/Pressability';
9
+ import { Asset } from 'expo-asset';
10
+ import * as fs from 'expo-file-system';
11
+ import { fromByteArray } from 'base64-js';
10
12
  import 'react-reconciler/constants';
11
13
  import 'zustand';
12
14
  import 'react-reconciler';
13
15
  import 'scheduler';
14
16
  import 'suspend-react';
15
17
 
16
- /* eslint-enable import/default, import/no-named-as-default, import/no-named-as-default-member */
17
-
18
- const EVENTS = {
19
- PRESS: 'onPress',
20
- PRESSIN: 'onPressIn',
21
- PRESSOUT: 'onPressOut',
22
- LONGPRESS: 'onLongPress',
23
- HOVERIN: 'onHoverIn',
24
- HOVEROUT: 'onHoverOut',
25
- PRESSMOVE: 'onPressMove'
26
- };
27
- const DOM_EVENTS = {
28
- [EVENTS.PRESS]: 'onClick',
29
- [EVENTS.PRESSIN]: 'onPointerDown',
30
- [EVENTS.PRESSOUT]: 'onPointerUp',
31
- [EVENTS.LONGPRESS]: 'onDoubleClick',
32
- [EVENTS.HOVERIN]: 'onPointerOver',
33
- [EVENTS.HOVEROUT]: 'onPointerOut',
34
- [EVENTS.PRESSMOVE]: 'onPointerMove'
35
- };
36
-
37
18
  /** Default R3F event manager for react-native */
38
19
  function createTouchEvents(store) {
39
20
  const {
@@ -48,9 +29,26 @@ function createTouchEvents(store) {
48
29
  event.nativeEvent.offsetY = event.nativeEvent.locationY;
49
30
 
50
31
  // Emulate DOM event
51
- const callback = handlePointer(DOM_EVENTS[name]);
52
- return callback(event.nativeEvent);
32
+ const callback = handlePointer(name);
33
+ callback(event.nativeEvent);
34
+ return true;
53
35
  };
36
+ const responder = PanResponder.create({
37
+ onStartShouldSetPanResponder: () => true,
38
+ onMoveShouldSetPanResponder: () => true,
39
+ onMoveShouldSetPanResponderCapture: () => true,
40
+ onPanResponderTerminationRequest: () => true,
41
+ onStartShouldSetPanResponderCapture: e => handleTouch(e, 'onPointerCapture'),
42
+ onPanResponderStart: e => handleTouch(e, 'onPointerDown'),
43
+ onPanResponderMove: e => handleTouch(e, 'onPointerMove'),
44
+ onPanResponderEnd: (e, state) => {
45
+ handleTouch(e, 'onPointerUp');
46
+ if (Math.hypot(state.dx, state.dy) < 20) handleTouch(e, 'onClick');
47
+ },
48
+ onPanResponderRelease: e => handleTouch(e, 'onPointerLeave'),
49
+ onPanResponderTerminate: e => handleTouch(e, 'onLostPointerCapture'),
50
+ onPanResponderReject: e => handleTouch(e, 'onLostPointerCapture')
51
+ });
54
52
  return {
55
53
  priority: 1,
56
54
  enabled: true,
@@ -61,17 +59,16 @@ function createTouchEvents(store) {
61
59
  state.raycaster.setFromCamera(state.pointer, state.camera);
62
60
  },
63
61
  connected: undefined,
64
- handlers: Object.values(EVENTS).reduce((acc, name) => ({
65
- ...acc,
66
- [name]: event => handleTouch(event, name)
67
- }), {}),
62
+ handlers: responder.panHandlers,
68
63
  update: () => {
69
64
  var _internal$lastEvent;
70
65
  const {
71
66
  events,
72
67
  internal
73
68
  } = store.getState();
74
- if ((_internal$lastEvent = internal.lastEvent) != null && _internal$lastEvent.current && events.handlers) events.handlers.onPointerMove(internal.lastEvent.current);
69
+ if ((_internal$lastEvent = internal.lastEvent) != null && _internal$lastEvent.current && events.handlers) {
70
+ handlePointer('onPointerMove')(internal.lastEvent.current);
71
+ }
75
72
  },
76
73
  connect: () => {
77
74
  const {
@@ -79,130 +76,24 @@ function createTouchEvents(store) {
79
76
  events
80
77
  } = store.getState();
81
78
  events.disconnect == null ? void 0 : events.disconnect();
82
- const connected = new Pressability(events.handlers);
83
79
  set(state => ({
84
80
  events: {
85
81
  ...state.events,
86
- connected
82
+ connected: true
87
83
  }
88
84
  }));
89
- const handlers = connected.getEventHandlers();
90
- return handlers;
91
85
  },
92
86
  disconnect: () => {
93
87
  const {
94
- set,
95
- events
88
+ set
96
89
  } = store.getState();
97
- if (events.connected) {
98
- events.connected.reset();
99
- set(state => ({
100
- events: {
101
- ...state.events,
102
- connected: undefined
103
- }
104
- }));
105
- }
106
- }
107
- };
108
- }
109
-
110
- // Check if expo-asset is installed (available with expo modules)
111
- let expAsset;
112
- try {
113
- var _require;
114
- expAsset = (_require = require('expo-asset')) == null ? void 0 : _require.Asset;
115
- } catch (_) {}
116
-
117
- /**
118
- * Generates an asset based on input type.
119
- */
120
- function getAsset(input) {
121
- switch (typeof input) {
122
- case 'string':
123
- return expAsset.fromURI(input);
124
- case 'number':
125
- return expAsset.fromModule(input);
126
- default:
127
- throw new Error('R3F: Invalid asset! Must be a URI or module.');
128
- }
129
- }
130
- let injected = false;
131
- function polyfills() {
132
- if (!expAsset || injected) return;
133
- injected = true;
134
-
135
- // Don't pre-process urls, let expo-asset generate an absolute URL
136
- const extractUrlBase = THREE.LoaderUtils.extractUrlBase.bind(THREE.LoaderUtils);
137
- THREE.LoaderUtils.extractUrlBase = url => typeof url === 'string' ? extractUrlBase(url) : './';
138
-
139
- // There's no Image in native, so create a data texture instead
140
- const prevTextureLoad = THREE.TextureLoader.prototype.load;
141
- THREE.TextureLoader.prototype.load = function load(url, onLoad, onProgress, onError) {
142
- const texture = new THREE.Texture();
143
-
144
- // @ts-ignore
145
- texture.isDataTexture = true;
146
- getAsset(url).downloadAsync().then(asset => {
147
- texture.image = {
148
- data: asset,
149
- width: asset.width,
150
- height: asset.height
151
- };
152
- texture.flipY = true;
153
- texture.unpackAlignment = 1;
154
- texture.needsUpdate = true;
155
- onLoad == null ? void 0 : onLoad(texture);
156
- }).catch(onError);
157
- return texture;
158
- };
159
-
160
- // Fetches assets via XMLHttpRequest
161
- const prevFileLoad = THREE.FileLoader.prototype.load;
162
- THREE.FileLoader.prototype.load = function (url, onLoad, onProgress, onError) {
163
- if (this.path) url = this.path + url;
164
- const request = new XMLHttpRequest();
165
- getAsset(url).downloadAsync().then(asset => {
166
- request.open('GET', asset.uri, true);
167
- request.addEventListener('load', event => {
168
- if (request.status === 200) {
169
- onLoad == null ? void 0 : onLoad(request.response);
170
- this.manager.itemEnd(url);
171
- } else {
172
- onError == null ? void 0 : onError(event);
173
- this.manager.itemError(url);
174
- this.manager.itemEnd(url);
90
+ set(state => ({
91
+ events: {
92
+ ...state.events,
93
+ connected: false
175
94
  }
176
- }, false);
177
- request.addEventListener('progress', event => {
178
- onProgress == null ? void 0 : onProgress(event);
179
- }, false);
180
- request.addEventListener('error', event => {
181
- onError == null ? void 0 : onError(event);
182
- this.manager.itemError(url);
183
- this.manager.itemEnd(url);
184
- }, false);
185
- request.addEventListener('abort', event => {
186
- onError == null ? void 0 : onError(event);
187
- this.manager.itemError(url);
188
- this.manager.itemEnd(url);
189
- }, false);
190
- if (this.responseType) request.responseType = this.responseType;
191
- if (this.withCredentials) request.withCredentials = this.withCredentials;
192
- for (const header in this.requestHeader) {
193
- request.setRequestHeader(header, this.requestHeader[header]);
194
- }
195
- request.send(null);
196
- this.manager.itemStart(url);
197
- });
198
- return request;
199
- };
200
-
201
- // Cleanup function
202
- return () => {
203
- THREE.LoaderUtils.extractUrlBase = extractUrlBase;
204
- THREE.TextureLoader.prototype.load = prevTextureLoad;
205
- THREE.FileLoader.prototype.load = prevFileLoad;
95
+ }));
96
+ }
206
97
  };
207
98
  }
208
99
 
@@ -258,9 +149,6 @@ const CanvasImpl = /*#__PURE__*/React.forwardRef(({
258
149
  if (error) throw error;
259
150
  const viewRef = React.useRef(null);
260
151
  const root = React.useRef(null);
261
-
262
- // Inject and cleanup RN polyfills if able
263
- React.useLayoutEffect(() => polyfills(), []);
264
152
  const onLayout = React.useCallback(e => {
265
153
  const {
266
154
  width,
@@ -319,8 +207,7 @@ const CanvasImpl = /*#__PURE__*/React.forwardRef(({
319
207
  // Overwrite onCreated to apply RN bindings
320
208
  onCreated: state => {
321
209
  // Bind events after creation
322
- const handlers = state.events.connect == null ? void 0 : state.events.connect(viewRef.current);
323
- setBind(handlers);
210
+ setBind(state.events.handlers);
324
211
 
325
212
  // Bind render to RN bridge
326
213
  const context = state.gl.getContext();
@@ -368,4 +255,178 @@ const Canvas = /*#__PURE__*/React.forwardRef(function CanvasWrapper(props, ref)
368
255
  })));
369
256
  });
370
257
 
258
+ function polyfills() {
259
+ // Patch Blob for ArrayBuffer if unsupported
260
+ try {
261
+ new Blob([new ArrayBuffer(4)]);
262
+ } catch (_) {
263
+ global.Blob = class extends Blob {
264
+ constructor(parts, options) {
265
+ super(parts == null ? void 0 : parts.map(part => {
266
+ if (part instanceof ArrayBuffer || ArrayBuffer.isView(part)) {
267
+ part = fromByteArray(new Uint8Array(part));
268
+ }
269
+ return part;
270
+ }), options);
271
+ }
272
+ };
273
+ }
274
+ function uuidv4() {
275
+ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
276
+ const r = Math.random() * 16 | 0,
277
+ v = c == 'x' ? r : r & 0x3 | 0x8;
278
+ return v.toString(16);
279
+ });
280
+ }
281
+ async function getAsset(input) {
282
+ if (typeof input === 'string') {
283
+ // Point to storage if preceded with fs path
284
+ if (input.startsWith('file:')) return {
285
+ localUri: input
286
+ };
287
+
288
+ // Unpack Blobs from react-native BlobManager
289
+ if (input.startsWith('blob:')) {
290
+ const blob = await new Promise((res, rej) => {
291
+ const xhr = new XMLHttpRequest();
292
+ xhr.open('GET', input);
293
+ xhr.responseType = 'blob';
294
+ xhr.onload = () => res(xhr.response);
295
+ xhr.onerror = rej;
296
+ xhr.send();
297
+ });
298
+ const data = await new Promise((res, rej) => {
299
+ const reader = new FileReader();
300
+ reader.onload = () => res(reader.result);
301
+ reader.onerror = rej;
302
+ reader.readAsText(blob);
303
+ });
304
+ input = `data:${blob.type};base64,${data}`;
305
+ }
306
+
307
+ // Create safe URI for JSI
308
+ if (input.startsWith('data:')) {
309
+ const [header, data] = input.split(',');
310
+ const [, type] = header.split('/');
311
+ const localUri = fs.cacheDirectory + uuidv4() + `.${type}`;
312
+ await fs.writeAsStringAsync(localUri, data, {
313
+ encoding: fs.EncodingType.Base64
314
+ });
315
+ return {
316
+ localUri
317
+ };
318
+ }
319
+ }
320
+
321
+ // Download bundler module or external URL
322
+ const asset = Asset.fromModule(input);
323
+
324
+ // Unpack assets in Android Release Mode
325
+ if (!asset.uri.includes(':')) {
326
+ const localUri = `${fs.cacheDirectory}ExponentAsset-${asset.hash}.${asset.type}`;
327
+ await fs.copyAsync({
328
+ from: asset.uri,
329
+ to: localUri
330
+ });
331
+ return {
332
+ localUri
333
+ };
334
+ }
335
+
336
+ // Otherwise, resolve from registry
337
+ return asset.downloadAsync();
338
+ }
339
+
340
+ // Don't pre-process urls, let expo-asset generate an absolute URL
341
+ const extractUrlBase = THREE.LoaderUtils.extractUrlBase.bind(THREE.LoaderUtils);
342
+ THREE.LoaderUtils.extractUrlBase = url => typeof url === 'string' ? extractUrlBase(url) : './';
343
+
344
+ // There's no Image in native, so create a data texture instead
345
+ THREE.TextureLoader.prototype.load = function load(url, onLoad, onProgress, onError) {
346
+ if (this.path) url = this.path + url;
347
+ const texture = new THREE.Texture();
348
+ getAsset(url).then(async asset => {
349
+ const uri = asset.localUri || asset.uri;
350
+ if (!asset.width || !asset.height) {
351
+ const {
352
+ width,
353
+ height
354
+ } = await new Promise((res, rej) => Image.getSize(uri, (width, height) => res({
355
+ width,
356
+ height
357
+ }), rej));
358
+ asset.width = width;
359
+ asset.height = height;
360
+ }
361
+ texture.image = {
362
+ data: {
363
+ localUri: uri
364
+ },
365
+ width: asset.width,
366
+ height: asset.height
367
+ };
368
+ texture.flipY = true;
369
+ texture.unpackAlignment = 1;
370
+ texture.needsUpdate = true;
371
+
372
+ // Force non-DOM upload for EXGL fast paths
373
+ // @ts-ignore
374
+ texture.isDataTexture = true;
375
+ onLoad == null ? void 0 : onLoad(texture);
376
+ }).catch(onError);
377
+ return texture;
378
+ };
379
+
380
+ // Fetches assets via XMLHttpRequest
381
+ THREE.FileLoader.prototype.load = function load(url, onLoad, onProgress, onError) {
382
+ if (this.path) url = this.path + url;
383
+ const request = new XMLHttpRequest();
384
+ getAsset(url).then(async asset => {
385
+ let uri = asset.localUri || asset.uri;
386
+
387
+ // Make FS paths web-safe
388
+ if (asset.uri.startsWith('file://')) {
389
+ const data = await fs.readAsStringAsync(asset.uri, {
390
+ encoding: fs.EncodingType.Base64
391
+ });
392
+ uri = `data:application/octet-stream;base64,${data}`;
393
+ }
394
+ request.open('GET', uri, true);
395
+ request.addEventListener('load', event => {
396
+ if (request.status === 200) {
397
+ onLoad == null ? void 0 : onLoad(request.response);
398
+ this.manager.itemEnd(url);
399
+ } else {
400
+ onError == null ? void 0 : onError(event);
401
+ this.manager.itemError(url);
402
+ this.manager.itemEnd(url);
403
+ }
404
+ }, false);
405
+ request.addEventListener('progress', event => {
406
+ onProgress == null ? void 0 : onProgress(event);
407
+ }, false);
408
+ request.addEventListener('error', event => {
409
+ onError == null ? void 0 : onError(event);
410
+ this.manager.itemError(url);
411
+ this.manager.itemEnd(url);
412
+ }, false);
413
+ request.addEventListener('abort', event => {
414
+ onError == null ? void 0 : onError(event);
415
+ this.manager.itemError(url);
416
+ this.manager.itemEnd(url);
417
+ }, false);
418
+ if (this.responseType) request.responseType = this.responseType;
419
+ if (this.withCredentials) request.withCredentials = this.withCredentials;
420
+ for (const header in this.requestHeader) {
421
+ request.setRequestHeader(header, this.requestHeader[header]);
422
+ }
423
+ request.send(null);
424
+ this.manager.itemStart(url);
425
+ }).catch(onError);
426
+ return request;
427
+ };
428
+ }
429
+
430
+ polyfills();
431
+
371
432
  export { Canvas, createTouchEvents as events };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/fiber",
3
- "version": "8.13.8",
3
+ "version": "8.14.0",
4
4
  "description": "A React renderer for Threejs",
5
5
  "keywords": [
6
6
  "react",
@@ -44,6 +44,7 @@
44
44
  "dependencies": {
45
45
  "@babel/runtime": "^7.17.8",
46
46
  "@types/react-reconciler": "^0.26.7",
47
+ "base64-js": "^1.5.1",
47
48
  "its-fine": "^1.0.6",
48
49
  "react-reconciler": "^0.27.0",
49
50
  "react-use-measure": "^2.1.1",
@@ -55,6 +56,7 @@
55
56
  "expo": ">=43.0",
56
57
  "expo-asset": ">=8.4",
57
58
  "expo-gl": ">=11.0",
59
+ "expo-file-system": ">=11.0",
58
60
  "react": ">=18.0",
59
61
  "react-dom": ">=18.0",
60
62
  "react-native": ">=0.64",
@@ -73,6 +75,9 @@
73
75
  "expo-asset": {
74
76
  "optional": true
75
77
  },
78
+ "expo-file-system": {
79
+ "optional": true
80
+ },
76
81
  "expo-gl": {
77
82
  "optional": true
78
83
  }