@react-three/fiber 8.13.9 → 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,11 @@
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
+
3
9
  ## 8.13.9
4
10
 
5
11
  ### 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,131 +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
- if (this.path) url = this.path + url;
170
- const texture = new THREE__namespace.Texture();
171
-
172
- // @ts-ignore
173
- texture.isDataTexture = true;
174
- getAsset(url).downloadAsync().then(asset => {
175
- texture.image = {
176
- data: asset,
177
- width: asset.width,
178
- height: asset.height
179
- };
180
- texture.flipY = true;
181
- texture.unpackAlignment = 1;
182
- texture.needsUpdate = true;
183
- onLoad == null ? void 0 : onLoad(texture);
184
- }).catch(onError);
185
- return texture;
186
- };
187
-
188
- // Fetches assets via XMLHttpRequest
189
- const prevFileLoad = THREE__namespace.FileLoader.prototype.load;
190
- THREE__namespace.FileLoader.prototype.load = function (url, onLoad, onProgress, onError) {
191
- if (this.path) url = this.path + url;
192
- const request = new XMLHttpRequest();
193
- getAsset(url).downloadAsync().then(asset => {
194
- request.open('GET', asset.uri, true);
195
- request.addEventListener('load', event => {
196
- if (request.status === 200) {
197
- onLoad == null ? void 0 : onLoad(request.response);
198
- this.manager.itemEnd(url);
199
- } else {
200
- onError == null ? void 0 : onError(event);
201
- this.manager.itemError(url);
202
- this.manager.itemEnd(url);
115
+ set(state => ({
116
+ events: {
117
+ ...state.events,
118
+ connected: false
203
119
  }
204
- }, false);
205
- request.addEventListener('progress', event => {
206
- onProgress == null ? void 0 : onProgress(event);
207
- }, false);
208
- request.addEventListener('error', event => {
209
- onError == null ? void 0 : onError(event);
210
- this.manager.itemError(url);
211
- this.manager.itemEnd(url);
212
- }, false);
213
- request.addEventListener('abort', event => {
214
- onError == null ? void 0 : onError(event);
215
- this.manager.itemError(url);
216
- this.manager.itemEnd(url);
217
- }, false);
218
- if (this.responseType) request.responseType = this.responseType;
219
- if (this.withCredentials) request.withCredentials = this.withCredentials;
220
- for (const header in this.requestHeader) {
221
- request.setRequestHeader(header, this.requestHeader[header]);
222
- }
223
- request.send(null);
224
- this.manager.itemStart(url);
225
- });
226
- return request;
227
- };
228
-
229
- // Cleanup function
230
- return () => {
231
- THREE__namespace.LoaderUtils.extractUrlBase = extractUrlBase;
232
- THREE__namespace.TextureLoader.prototype.load = prevTextureLoad;
233
- THREE__namespace.FileLoader.prototype.load = prevFileLoad;
120
+ }));
121
+ }
234
122
  };
235
123
  }
236
124
 
@@ -286,9 +174,6 @@ const CanvasImpl = /*#__PURE__*/React__namespace.forwardRef(({
286
174
  if (error) throw error;
287
175
  const viewRef = React__namespace.useRef(null);
288
176
  const root = React__namespace.useRef(null);
289
-
290
- // Inject and cleanup RN polyfills if able
291
- React__namespace.useLayoutEffect(() => polyfills(), []);
292
177
  const onLayout = React__namespace.useCallback(e => {
293
178
  const {
294
179
  width,
@@ -347,8 +232,7 @@ const CanvasImpl = /*#__PURE__*/React__namespace.forwardRef(({
347
232
  // Overwrite onCreated to apply RN bindings
348
233
  onCreated: state => {
349
234
  // Bind events after creation
350
- const handlers = state.events.connect == null ? void 0 : state.events.connect(viewRef.current);
351
- setBind(handlers);
235
+ setBind(state.events.handlers);
352
236
 
353
237
  // Bind render to RN bridge
354
238
  const context = state.gl.getContext();
@@ -396,6 +280,180 @@ const Canvas = /*#__PURE__*/React__namespace.forwardRef(function CanvasWrapper(p
396
280
  })));
397
281
  });
398
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
+
399
457
  exports.ReactThreeFiber = index.threeTypes;
400
458
  exports._roots = index.roots;
401
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,131 +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
- if (this.path) url = this.path + url;
170
- const texture = new THREE__namespace.Texture();
171
-
172
- // @ts-ignore
173
- texture.isDataTexture = true;
174
- getAsset(url).downloadAsync().then(asset => {
175
- texture.image = {
176
- data: asset,
177
- width: asset.width,
178
- height: asset.height
179
- };
180
- texture.flipY = true;
181
- texture.unpackAlignment = 1;
182
- texture.needsUpdate = true;
183
- onLoad == null ? void 0 : onLoad(texture);
184
- }).catch(onError);
185
- return texture;
186
- };
187
-
188
- // Fetches assets via XMLHttpRequest
189
- const prevFileLoad = THREE__namespace.FileLoader.prototype.load;
190
- THREE__namespace.FileLoader.prototype.load = function (url, onLoad, onProgress, onError) {
191
- if (this.path) url = this.path + url;
192
- const request = new XMLHttpRequest();
193
- getAsset(url).downloadAsync().then(asset => {
194
- request.open('GET', asset.uri, true);
195
- request.addEventListener('load', event => {
196
- if (request.status === 200) {
197
- onLoad == null ? void 0 : onLoad(request.response);
198
- this.manager.itemEnd(url);
199
- } else {
200
- onError == null ? void 0 : onError(event);
201
- this.manager.itemError(url);
202
- this.manager.itemEnd(url);
115
+ set(state => ({
116
+ events: {
117
+ ...state.events,
118
+ connected: false
203
119
  }
204
- }, false);
205
- request.addEventListener('progress', event => {
206
- onProgress == null ? void 0 : onProgress(event);
207
- }, false);
208
- request.addEventListener('error', event => {
209
- onError == null ? void 0 : onError(event);
210
- this.manager.itemError(url);
211
- this.manager.itemEnd(url);
212
- }, false);
213
- request.addEventListener('abort', event => {
214
- onError == null ? void 0 : onError(event);
215
- this.manager.itemError(url);
216
- this.manager.itemEnd(url);
217
- }, false);
218
- if (this.responseType) request.responseType = this.responseType;
219
- if (this.withCredentials) request.withCredentials = this.withCredentials;
220
- for (const header in this.requestHeader) {
221
- request.setRequestHeader(header, this.requestHeader[header]);
222
- }
223
- request.send(null);
224
- this.manager.itemStart(url);
225
- });
226
- return request;
227
- };
228
-
229
- // Cleanup function
230
- return () => {
231
- THREE__namespace.LoaderUtils.extractUrlBase = extractUrlBase;
232
- THREE__namespace.TextureLoader.prototype.load = prevTextureLoad;
233
- THREE__namespace.FileLoader.prototype.load = prevFileLoad;
120
+ }));
121
+ }
234
122
  };
235
123
  }
236
124
 
@@ -286,9 +174,6 @@ const CanvasImpl = /*#__PURE__*/React__namespace.forwardRef(({
286
174
  if (error) throw error;
287
175
  const viewRef = React__namespace.useRef(null);
288
176
  const root = React__namespace.useRef(null);
289
-
290
- // Inject and cleanup RN polyfills if able
291
- React__namespace.useLayoutEffect(() => polyfills(), []);
292
177
  const onLayout = React__namespace.useCallback(e => {
293
178
  const {
294
179
  width,
@@ -347,8 +232,7 @@ const CanvasImpl = /*#__PURE__*/React__namespace.forwardRef(({
347
232
  // Overwrite onCreated to apply RN bindings
348
233
  onCreated: state => {
349
234
  // Bind events after creation
350
- const handlers = state.events.connect == null ? void 0 : state.events.connect(viewRef.current);
351
- setBind(handlers);
235
+ setBind(state.events.handlers);
352
236
 
353
237
  // Bind render to RN bridge
354
238
  const context = state.gl.getContext();
@@ -396,6 +280,180 @@ const Canvas = /*#__PURE__*/React__namespace.forwardRef(function CanvasWrapper(p
396
280
  })));
397
281
  });
398
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
+
399
457
  exports.ReactThreeFiber = index.threeTypes;
400
458
  exports._roots = index.roots;
401
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,131 +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
- if (this.path) url = this.path + url;
143
- const texture = new THREE.Texture();
144
-
145
- // @ts-ignore
146
- texture.isDataTexture = true;
147
- getAsset(url).downloadAsync().then(asset => {
148
- texture.image = {
149
- data: asset,
150
- width: asset.width,
151
- height: asset.height
152
- };
153
- texture.flipY = true;
154
- texture.unpackAlignment = 1;
155
- texture.needsUpdate = true;
156
- onLoad == null ? void 0 : onLoad(texture);
157
- }).catch(onError);
158
- return texture;
159
- };
160
-
161
- // Fetches assets via XMLHttpRequest
162
- const prevFileLoad = THREE.FileLoader.prototype.load;
163
- THREE.FileLoader.prototype.load = function (url, onLoad, onProgress, onError) {
164
- if (this.path) url = this.path + url;
165
- const request = new XMLHttpRequest();
166
- getAsset(url).downloadAsync().then(asset => {
167
- request.open('GET', asset.uri, true);
168
- request.addEventListener('load', event => {
169
- if (request.status === 200) {
170
- onLoad == null ? void 0 : onLoad(request.response);
171
- this.manager.itemEnd(url);
172
- } else {
173
- onError == null ? void 0 : onError(event);
174
- this.manager.itemError(url);
175
- this.manager.itemEnd(url);
90
+ set(state => ({
91
+ events: {
92
+ ...state.events,
93
+ connected: false
176
94
  }
177
- }, false);
178
- request.addEventListener('progress', event => {
179
- onProgress == null ? void 0 : onProgress(event);
180
- }, false);
181
- request.addEventListener('error', event => {
182
- onError == null ? void 0 : onError(event);
183
- this.manager.itemError(url);
184
- this.manager.itemEnd(url);
185
- }, false);
186
- request.addEventListener('abort', event => {
187
- onError == null ? void 0 : onError(event);
188
- this.manager.itemError(url);
189
- this.manager.itemEnd(url);
190
- }, false);
191
- if (this.responseType) request.responseType = this.responseType;
192
- if (this.withCredentials) request.withCredentials = this.withCredentials;
193
- for (const header in this.requestHeader) {
194
- request.setRequestHeader(header, this.requestHeader[header]);
195
- }
196
- request.send(null);
197
- this.manager.itemStart(url);
198
- });
199
- return request;
200
- };
201
-
202
- // Cleanup function
203
- return () => {
204
- THREE.LoaderUtils.extractUrlBase = extractUrlBase;
205
- THREE.TextureLoader.prototype.load = prevTextureLoad;
206
- THREE.FileLoader.prototype.load = prevFileLoad;
95
+ }));
96
+ }
207
97
  };
208
98
  }
209
99
 
@@ -259,9 +149,6 @@ const CanvasImpl = /*#__PURE__*/React.forwardRef(({
259
149
  if (error) throw error;
260
150
  const viewRef = React.useRef(null);
261
151
  const root = React.useRef(null);
262
-
263
- // Inject and cleanup RN polyfills if able
264
- React.useLayoutEffect(() => polyfills(), []);
265
152
  const onLayout = React.useCallback(e => {
266
153
  const {
267
154
  width,
@@ -320,8 +207,7 @@ const CanvasImpl = /*#__PURE__*/React.forwardRef(({
320
207
  // Overwrite onCreated to apply RN bindings
321
208
  onCreated: state => {
322
209
  // Bind events after creation
323
- const handlers = state.events.connect == null ? void 0 : state.events.connect(viewRef.current);
324
- setBind(handlers);
210
+ setBind(state.events.handlers);
325
211
 
326
212
  // Bind render to RN bridge
327
213
  const context = state.gl.getContext();
@@ -369,4 +255,178 @@ const Canvas = /*#__PURE__*/React.forwardRef(function CanvasWrapper(props, ref)
369
255
  })));
370
256
  });
371
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
+
372
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.9",
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
  }