@react-three/fiber 9.0.0-alpha.0 → 9.0.0-alpha.2

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.
@@ -33,12 +33,9 @@ var React__namespace = /*#__PURE__*/_interopNamespace(React);
33
33
  var create__default = /*#__PURE__*/_interopDefault(create);
34
34
  var Reconciler__default = /*#__PURE__*/_interopDefault(Reconciler);
35
35
 
36
- var threeTypes = /*#__PURE__*/Object.freeze({
37
- __proto__: null
38
- });
39
-
40
36
  var _window$document, _window$navigator;
41
37
  const isOrthographicCamera = def => def && def.isOrthographicCamera;
38
+ const isRef = obj => obj && obj.hasOwnProperty('current');
42
39
  /**
43
40
  * An SSR-friendly useLayoutEffect.
44
41
  *
@@ -72,8 +69,8 @@ class ErrorBoundary extends React__namespace.Component {
72
69
  };
73
70
  }
74
71
 
75
- componentDidCatch(error) {
76
- this.props.set(error);
72
+ componentDidCatch(err) {
73
+ this.props.set(err);
77
74
  }
78
75
 
79
76
  render() {
@@ -86,10 +83,9 @@ ErrorBoundary.getDerivedStateFromError = () => ({
86
83
  error: true
87
84
  });
88
85
 
89
- const DEFAULT = '__default';
90
- const isDiffSet = def => def && !!def.memoized && !!def.changes;
91
86
  function calculateDpr(dpr) {
92
- return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], window.devicePixelRatio), dpr[1]) : dpr;
87
+ const target = typeof window !== 'undefined' ? window.devicePixelRatio : 1;
88
+ return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
93
89
  }
94
90
  /**
95
91
  * Returns instance root state
@@ -157,250 +153,225 @@ function buildGraph(object) {
157
153
  }
158
154
 
159
155
  return data;
160
- } // Disposes an object and all its properties
161
-
156
+ }
157
+ // Disposes an object and all its properties
162
158
  function dispose(obj) {
163
- if (obj.dispose && obj.type !== 'Scene') obj.dispose();
159
+ if (obj.type !== 'Scene') obj.dispose == null ? void 0 : obj.dispose();
164
160
 
165
161
  for (const p in obj) {
166
- p.dispose == null ? void 0 : p.dispose();
167
- delete obj[p];
162
+ const prop = obj[p];
163
+ if ((prop == null ? void 0 : prop.type) !== 'Scene') prop == null ? void 0 : prop.dispose == null ? void 0 : prop.dispose();
164
+ }
165
+ }
166
+ const REACT_INTERNAL_PROPS = ['children', 'key', 'ref']; // Gets only instance props from reconciler fibers
167
+
168
+ function getInstanceProps(queue) {
169
+ const props = {};
170
+
171
+ for (const key in queue) {
172
+ if (!REACT_INTERNAL_PROPS.includes(key)) props[key] = queue[key];
168
173
  }
174
+
175
+ return props;
169
176
  } // Each object in the scene carries a small LocalState descriptor
170
177
 
171
- function prepare(object, state) {
172
- const instance = object;
178
+ function prepare(target, root, type, props) {
179
+ const object = target; // Create instance descriptor
173
180
 
174
- if (state != null && state.primitive || !instance.__r3f) {
175
- instance.__r3f = {
176
- type: '',
177
- root: null,
178
- previousAttach: null,
179
- memoizedProps: {},
181
+ let instance = object.__r3f;
182
+
183
+ if (!instance) {
184
+ instance = {
185
+ root,
186
+ type,
187
+ parent: null,
188
+ children: [],
189
+ props: getInstanceProps(props),
190
+ object,
180
191
  eventCount: 0,
181
192
  handlers: {},
182
- objects: [],
183
- parent: null,
184
- ...state
193
+ isHidden: false
185
194
  };
195
+ object.__r3f = instance;
186
196
  }
187
197
 
188
- return object;
198
+ return instance;
189
199
  }
200
+ function resolve(root, key) {
201
+ var _target;
190
202
 
191
- function resolve(instance, key) {
192
- let target = instance;
203
+ let target = root[key];
204
+ if (!key.includes('-')) return {
205
+ root,
206
+ key,
207
+ target
208
+ }; // Resolve pierced target
193
209
 
194
- if (key.includes('-')) {
195
- const entries = key.split('-');
196
- const last = entries.pop();
197
- target = entries.reduce((acc, key) => acc[key], instance);
198
- return {
199
- target,
200
- key: last
201
- };
202
- } else return {
203
- target,
204
- key
210
+ const chain = key.split('-');
211
+ target = chain.reduce((acc, key) => acc[key], root);
212
+ key = chain.pop(); // Switch root if atomic
213
+
214
+ if (!((_target = target) != null && _target.set)) root = chain.reduce((acc, key) => acc[key], root);
215
+ return {
216
+ root,
217
+ key,
218
+ target
205
219
  };
206
220
  } // Checks if a dash-cased string ends with an integer
207
221
 
208
-
209
222
  const INDEX_REGEX = /-\d+$/;
210
- function attach(parent, child, type) {
211
- if (is.str(type)) {
223
+ function attach(parent, child) {
224
+ if (is.str(child.props.attach)) {
212
225
  // If attaching into an array (foo-0), create one
213
- if (INDEX_REGEX.test(type)) {
214
- const root = type.replace(INDEX_REGEX, '');
226
+ if (INDEX_REGEX.test(child.props.attach)) {
227
+ const index = child.props.attach.replace(INDEX_REGEX, '');
215
228
  const {
216
- target,
229
+ root,
217
230
  key
218
- } = resolve(parent, root);
219
- if (!Array.isArray(target[key])) target[key] = [];
231
+ } = resolve(parent.object, index);
232
+ if (!Array.isArray(root[key])) root[key] = [];
220
233
  }
221
234
 
222
235
  const {
223
- target,
236
+ root,
224
237
  key
225
- } = resolve(parent, type);
226
- child.__r3f.previousAttach = target[key];
227
- target[key] = child;
228
- } else child.__r3f.previousAttach = type(parent, child);
238
+ } = resolve(parent.object, child.props.attach);
239
+ child.previousAttach = root[key];
240
+ root[key] = child.object;
241
+ } else if (is.fun(child.props.attach)) {
242
+ child.previousAttach = child.props.attach(parent.object, child.object);
243
+ }
229
244
  }
230
- function detach(parent, child, type) {
231
- var _child$__r3f, _child$__r3f2;
232
-
233
- if (is.str(type)) {
245
+ function detach(parent, child) {
246
+ if (is.str(child.props.attach)) {
234
247
  const {
235
- target,
248
+ root,
236
249
  key
237
- } = resolve(parent, type);
238
- const previous = child.__r3f.previousAttach; // When the previous value was undefined, it means the value was never set to begin with
239
-
240
- if (previous === undefined) delete target[key]; // Otherwise set the previous value
241
- else target[key] = previous;
242
- } else (_child$__r3f = child.__r3f) == null ? void 0 : _child$__r3f.previousAttach == null ? void 0 : _child$__r3f.previousAttach(parent, child);
243
-
244
- (_child$__r3f2 = child.__r3f) == null ? true : delete _child$__r3f2.previousAttach;
245
- } // This function prepares a set of changes to be applied to the instance
246
-
247
- function diffProps(instance, {
248
- children: cN,
249
- key: kN,
250
- ref: rN,
251
- ...props
252
- }, {
253
- children: cP,
254
- key: kP,
255
- ref: rP,
256
- ...previous
257
- } = {}, remove = false) {
258
- var _instance$__r3f;
259
-
260
- const localState = (_instance$__r3f = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f : {};
261
- const entries = Object.entries(props);
262
- const changes = []; // Catch removed props, prepend them so they can be reset or removed
263
-
264
- if (remove) {
265
- const previousKeys = Object.keys(previous);
266
-
267
- for (let i = 0; i < previousKeys.length; i++) {
268
- if (!props.hasOwnProperty(previousKeys[i])) entries.unshift([previousKeys[i], DEFAULT + 'remove']);
269
- }
250
+ } = resolve(parent.object, child.props.attach);
251
+ const previous = child.previousAttach; // When the previous value was undefined, it means the value was never set to begin with
252
+
253
+ if (previous === undefined) delete root[key]; // Otherwise set the previous value
254
+ else root[key] = previous;
255
+ } else {
256
+ child.previousAttach == null ? void 0 : child.previousAttach(parent.object, child.object);
270
257
  }
271
258
 
272
- entries.forEach(([key, value]) => {
273
- var _instance$__r3f2;
259
+ delete child.previousAttach;
260
+ }
261
+ const RESERVED_PROPS = [...REACT_INTERNAL_PROPS, // Instance props
262
+ 'args', 'dispose', 'attach', 'object', // Behavior flags
263
+ 'dispose']; // This function prepares a set of changes to be applied to the instance
274
264
 
275
- // Bail out on primitive object
276
- if ((_instance$__r3f2 = instance.__r3f) != null && _instance$__r3f2.primitive && key === 'object') return; // When props match bail out
265
+ function diffProps(instance, newProps, resetRemoved = false) {
266
+ const changedProps = {}; // Sort through props
277
267
 
278
- if (is.equ(value, previous[key])) return; // Collect handlers and bail out
268
+ for (const prop in newProps) {
269
+ // Skip reserved keys
270
+ if (RESERVED_PROPS.includes(prop)) continue; // Skip if props match
279
271
 
280
- if (/^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/.test(key)) return changes.push([key, value, true, []]); // Split dashed props
272
+ if (is.equ(newProps[prop], instance.props[prop])) continue; // Props changed, add them
281
273
 
282
- let entries = [];
283
- if (key.includes('-')) entries = key.split('-');
284
- changes.push([key, value, false, entries]);
285
- });
286
- const memoized = { ...props
287
- };
288
- if (localState.memoizedProps && localState.memoizedProps.args) memoized.args = localState.memoizedProps.args;
289
- if (localState.memoizedProps && localState.memoizedProps.attach) memoized.attach = localState.memoizedProps.attach;
290
- return {
291
- memoized,
292
- changes
293
- };
294
- } // This function applies a set of changes to the instance
295
-
296
- function applyProps$1(instance, data) {
297
- var _instance$__r3f3, _root$getState;
298
-
299
- // Filter equals, events and reserved props
300
- const localState = (_instance$__r3f3 = instance.__r3f) != null ? _instance$__r3f3 : {};
301
- const root = localState.root;
302
- const rootState = (_root$getState = root == null ? void 0 : root.getState == null ? void 0 : root.getState()) != null ? _root$getState : {};
303
- const {
304
- memoized,
305
- changes
306
- } = isDiffSet(data) ? data : diffProps(instance, data);
307
- const prevHandlers = localState.eventCount; // Prepare memoized props
308
-
309
- if (instance.__r3f) instance.__r3f.memoizedProps = memoized;
310
- changes.forEach(([key, value, isEvent, keys]) => {
311
- let currentInstance = instance;
312
- let targetProp = currentInstance[key]; // Revolve dashed props
313
-
314
- if (keys.length) {
315
- targetProp = keys.reduce((acc, key) => acc[key], instance); // If the target is atomic, it forces us to switch the root
316
-
317
- if (!(targetProp && targetProp.set)) {
318
- const [name, ...reverseEntries] = keys.reverse();
319
- currentInstance = reverseEntries.reverse().reduce((acc, key) => acc[key], instance);
320
- key = name;
321
- }
322
- } // https://github.com/mrdoob/three.js/issues/21209
323
- // HMR/fast-refresh relies on the ability to cancel out props, but threejs
324
- // has no means to do this. Hence we curate a small collection of value-classes
325
- // with their respective constructor/set arguments
326
- // For removed props, try to set default values, if possible
274
+ changedProps[prop] = newProps[prop];
275
+ } // Reset removed props for HMR
327
276
 
328
277
 
329
- if (value === DEFAULT + 'remove') {
330
- if (targetProp && targetProp.constructor) {
331
- var _memoized$args;
278
+ if (resetRemoved) {
279
+ for (const prop in instance.props) {
280
+ if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
281
+ const {
282
+ root,
283
+ key
284
+ } = resolve(instance.object, prop); // https://github.com/mrdoob/three.js/issues/21209
285
+ // HMR/fast-refresh relies on the ability to cancel out props, but threejs
286
+ // has no means to do this. Hence we curate a small collection of value-classes
287
+ // with their respective constructor/set arguments
288
+ // For removed props, try to set default values, if possible
332
289
 
333
- // use the prop constructor to find the default it should be
334
- value = new targetProp.constructor(...((_memoized$args = memoized.args) != null ? _memoized$args : []));
335
- } else if (currentInstance.constructor) {
336
- var _currentInstance$__r;
290
+ if (root.constructor) {
291
+ var _root$__r3f$props$arg, _root$__r3f;
337
292
 
338
293
  // create a blank slate of the instance and copy the particular parameter.
339
294
  // @ts-ignore
340
- const defaultClassCall = new currentInstance.constructor(...((_currentInstance$__r = currentInstance.__r3f.memoizedProps.args) != null ? _currentInstance$__r : []));
341
- value = defaultClassCall[targetProp]; // destory the instance
295
+ const defaultClassCall = new root.constructor(...((_root$__r3f$props$arg = (_root$__r3f = root.__r3f) == null ? void 0 : _root$__r3f.props.args) != null ? _root$__r3f$props$arg : []));
296
+ changedProps[key] = defaultClassCall[key]; // destroy the instance
342
297
 
343
- if (defaultClassCall.dispose) defaultClassCall.dispose(); // instance does not have constructor, just set it to 0
298
+ if (defaultClassCall.dispose) defaultClassCall.dispose();
344
299
  } else {
345
- value = 0;
300
+ // instance does not have constructor, just set it to 0
301
+ changedProps[key] = 0;
346
302
  }
347
- } // Deal with pointer events ...
348
-
349
-
350
- if (isEvent) {
351
- if (value) localState.handlers[key] = value;else delete localState.handlers[key];
352
- localState.eventCount = Object.keys(localState.handlers).length;
353
- } // Special treatment for objects with support for set/copy, and layers
354
- else if (targetProp && targetProp.set && (targetProp.copy || targetProp instanceof THREE__namespace.Layers)) {
355
- // If value is an array
356
- if (Array.isArray(value)) {
357
- if (targetProp.fromArray) targetProp.fromArray(value);else targetProp.set(...value);
358
- } // Test again target.copy(class) next ...
359
- else if (targetProp.copy && value && value.constructor && targetProp.constructor.name === value.constructor.name) {
360
- targetProp.copy(value);
361
- } // If nothing else fits, just set the single value, ignore undefined
362
- // https://github.com/pmndrs/react-three-fiber/issues/274
363
- else if (value !== undefined) {
364
- const isColor = targetProp instanceof THREE__namespace.Color; // Allow setting array scalars
365
-
366
- if (!isColor && targetProp.setScalar) targetProp.setScalar(value); // Layers have no copy function, we must therefore copy the mask property
367
- else if (targetProp instanceof THREE__namespace.Layers && value instanceof THREE__namespace.Layers) targetProp.mask = value.mask; // Otherwise just set ...
368
- else targetProp.set(value);
369
- } // Else, just overwrite the value
303
+ }
304
+ }
370
305
 
371
- } else {
372
- currentInstance[key] = value; // Auto-convert sRGB textures, for now ...
306
+ return changedProps;
307
+ } // This function applies a set of changes to the instance
308
+
309
+ function applyProps(object, props) {
310
+ const instance = object.__r3f;
311
+ const rootState = instance == null ? void 0 : instance.root.getState();
312
+ const prevHandlers = instance == null ? void 0 : instance.eventCount;
313
+
314
+ for (const prop in props) {
315
+ let value = props[prop]; // Don't mutate reserved keys
316
+
317
+ if (RESERVED_PROPS.includes(prop)) continue; // Deal with pointer events ...
318
+
319
+ if (instance && /^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/.test(prop)) {
320
+ if (typeof value === 'function') instance.handlers[prop] = value;else delete instance.handlers[prop];
321
+ instance.eventCount = Object.keys(instance.handlers).length;
322
+ }
323
+
324
+ const {
325
+ root,
326
+ key,
327
+ target
328
+ } = resolve(object, prop); // Copy if properties match signatures
329
+
330
+ if (target != null && target.copy && (target == null ? void 0 : target.constructor) === (value == null ? void 0 : value.constructor)) {
331
+ target.copy(value);
332
+ } // Layers have no copy function, we must therefore copy the mask property
333
+ else if (target instanceof THREE__namespace.Layers && value instanceof THREE__namespace.Layers) {
334
+ target.mask = value.mask;
335
+ } // Set array types
336
+ else if (target != null && target.set && Array.isArray(value)) {
337
+ if (target.fromArray) target.fromArray(value);else target.set(...value);
338
+ } // Set literal types, ignore undefined
339
+ // https://github.com/pmndrs/react-three-fiber/issues/274
340
+ else if (target != null && target.set && typeof value !== 'object') {
341
+ const isColor = target instanceof THREE__namespace.Color; // Allow setting array scalars
342
+
343
+ if (!isColor && target.setScalar && typeof value === 'number') target.setScalar(value); // Otherwise just set ...
344
+ else if (value !== undefined) target.set(value);
345
+ } // Else, just overwrite the value
346
+ else {
347
+ root[key] = value; // Auto-convert sRGB textures, for now ...
373
348
  // https://github.com/pmndrs/react-three-fiber/issues/344
374
349
 
375
- if (!rootState.linear && currentInstance[key] instanceof THREE__namespace.Texture) {
376
- currentInstance[key].encoding = THREE__namespace.sRGBEncoding;
350
+ if (!(rootState != null && rootState.linear) && root[key] instanceof THREE__namespace.Texture) {
351
+ root[key].encoding = THREE__namespace.sRGBEncoding;
377
352
  }
378
353
  }
354
+ }
379
355
 
380
- invalidateInstance(instance);
381
- });
382
-
383
- if (localState.parent && rootState.internal && instance.raycast && prevHandlers !== localState.eventCount) {
356
+ if (instance != null && instance.parent && rootState != null && rootState.internal && instance.object instanceof THREE__namespace.Object3D && prevHandlers !== instance.eventCount) {
384
357
  // Pre-emptively remove the instance from the interaction manager
385
- const index = rootState.internal.interaction.indexOf(instance);
358
+ const index = rootState.internal.interaction.indexOf(instance.object);
386
359
  if (index > -1) rootState.internal.interaction.splice(index, 1); // Add the instance to the interaction manager only when it has handlers
387
360
 
388
- if (localState.eventCount) rootState.internal.interaction.push(instance);
389
- } // Call the update lifecycle when it is being updated, but only when it is part of the scene
390
-
361
+ if (instance.eventCount && instance.object.raycast !== null && instance.object instanceof THREE__namespace.Object3D) {
362
+ rootState.internal.interaction.push(instance.object);
363
+ }
364
+ }
391
365
 
392
- if (changes.length && instance.parent) updateInstance(instance);
393
- return instance;
366
+ if (instance) invalidateInstance(instance);
367
+ return object;
394
368
  }
395
369
  function invalidateInstance(instance) {
396
- var _instance$__r3f4, _instance$__r3f4$root;
370
+ var _instance$root;
397
371
 
398
- const state = (_instance$__r3f4 = instance.__r3f) == null ? void 0 : (_instance$__r3f4$root = _instance$__r3f4.root) == null ? void 0 : _instance$__r3f4$root.getState == null ? void 0 : _instance$__r3f4$root.getState();
372
+ const state = (_instance$root = instance.root) == null ? void 0 : _instance$root.getState == null ? void 0 : _instance$root.getState();
399
373
  if (state && state.internal.frames === 0) state.invalidate();
400
374
  }
401
- function updateInstance(instance) {
402
- instance.onUpdate == null ? void 0 : instance.onUpdate(instance);
403
- }
404
375
  function updateCamera(camera, size) {
405
376
  // https://github.com/pmndrs/react-three-fiber/issues/92
406
377
  // Do not mess with the camera if it belongs to the user
@@ -421,6 +392,280 @@ function updateCamera(camera, size) {
421
392
  }
422
393
  }
423
394
 
395
+ // Keys that shouldn't be copied between R3F stores
396
+ const privateKeys = ['set', 'get', 'setSize', 'setFrameloop', 'setDpr', 'events', 'invalidate', 'advance', 'size', 'viewport'];
397
+ const isRenderer = def => !!(def != null && def.render);
398
+ const context = /*#__PURE__*/React__namespace.createContext(null);
399
+
400
+ const createStore = (invalidate, advance) => {
401
+ const rootStore = create__default["default"]((set, get) => {
402
+ const position = new THREE__namespace.Vector3();
403
+ const defaultTarget = new THREE__namespace.Vector3();
404
+ const tempTarget = new THREE__namespace.Vector3();
405
+
406
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
407
+ const {
408
+ width,
409
+ height,
410
+ top,
411
+ left
412
+ } = size;
413
+ const aspect = width / height;
414
+ if (target instanceof THREE__namespace.Vector3) tempTarget.copy(target);else tempTarget.set(...target);
415
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
416
+
417
+ if (isOrthographicCamera(camera)) {
418
+ return {
419
+ width: width / camera.zoom,
420
+ height: height / camera.zoom,
421
+ top,
422
+ left,
423
+ factor: 1,
424
+ distance,
425
+ aspect
426
+ };
427
+ } else {
428
+ const fov = camera.fov * Math.PI / 180; // convert vertical fov to radians
429
+
430
+ const h = 2 * Math.tan(fov / 2) * distance; // visible height
431
+
432
+ const w = h * (width / height);
433
+ return {
434
+ width: w,
435
+ height: h,
436
+ top,
437
+ left,
438
+ factor: width / w,
439
+ distance,
440
+ aspect
441
+ };
442
+ }
443
+ }
444
+
445
+ let performanceTimeout = undefined;
446
+
447
+ const setPerformanceCurrent = current => set(state => ({
448
+ performance: { ...state.performance,
449
+ current
450
+ }
451
+ }));
452
+
453
+ const pointer = new THREE__namespace.Vector2();
454
+ const rootState = {
455
+ set,
456
+ get,
457
+ // Mock objects that have to be configured
458
+ gl: null,
459
+ camera: null,
460
+ raycaster: null,
461
+ events: {
462
+ priority: 1,
463
+ enabled: true,
464
+ connected: false
465
+ },
466
+ xr: null,
467
+ invalidate: (frames = 1) => invalidate(get(), frames),
468
+ advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
469
+ legacy: false,
470
+ linear: false,
471
+ flat: false,
472
+ scene: new THREE__namespace.Scene(),
473
+ controls: null,
474
+ clock: new THREE__namespace.Clock(),
475
+ pointer,
476
+ mouse: pointer,
477
+ frameloop: 'always',
478
+ onPointerMissed: undefined,
479
+ performance: {
480
+ current: 1,
481
+ min: 0.5,
482
+ max: 1,
483
+ debounce: 200,
484
+ regress: () => {
485
+ const state = get(); // Clear timeout
486
+
487
+ if (performanceTimeout) clearTimeout(performanceTimeout); // Set lower bound performance
488
+
489
+ if (state.performance.current !== state.performance.min) setPerformanceCurrent(state.performance.min); // Go back to upper bound performance after a while unless something regresses meanwhile
490
+
491
+ performanceTimeout = setTimeout(() => setPerformanceCurrent(get().performance.max), state.performance.debounce);
492
+ }
493
+ },
494
+ size: {
495
+ width: 0,
496
+ height: 0,
497
+ top: 0,
498
+ left: 0
499
+ },
500
+ viewport: {
501
+ initialDpr: 0,
502
+ dpr: 0,
503
+ width: 0,
504
+ height: 0,
505
+ top: 0,
506
+ left: 0,
507
+ aspect: 0,
508
+ distance: 0,
509
+ factor: 0,
510
+ getCurrentViewport
511
+ },
512
+ setEvents: events => set(state => ({ ...state,
513
+ events: { ...state.events,
514
+ ...events
515
+ }
516
+ })),
517
+ setSize: (width, height, top = 0, left = 0) => {
518
+ const camera = get().camera;
519
+ const size = {
520
+ width,
521
+ height,
522
+ top,
523
+ left
524
+ };
525
+ set(state => ({
526
+ size,
527
+ viewport: { ...state.viewport,
528
+ ...getCurrentViewport(camera, defaultTarget, size)
529
+ }
530
+ }));
531
+ },
532
+ setDpr: dpr => set(state => {
533
+ const resolved = calculateDpr(dpr);
534
+ return {
535
+ viewport: { ...state.viewport,
536
+ dpr: resolved,
537
+ initialDpr: state.viewport.initialDpr || resolved
538
+ }
539
+ };
540
+ }),
541
+ setFrameloop: frameloop => {
542
+ var _frameloop$mode, _frameloop$render, _frameloop$maxDelta;
543
+
544
+ const state = get();
545
+ const mode = typeof frameloop === 'string' ? frameloop : (frameloop == null ? void 0 : frameloop.mode) === 'auto' ? 'always' : (_frameloop$mode = frameloop == null ? void 0 : frameloop.mode) != null ? _frameloop$mode : state.frameloop;
546
+ const render = typeof frameloop === 'string' ? state.internal.render : (_frameloop$render = frameloop == null ? void 0 : frameloop.render) != null ? _frameloop$render : state.internal.render;
547
+ const maxDelta = typeof frameloop === 'string' ? state.internal.maxDelta : (_frameloop$maxDelta = frameloop == null ? void 0 : frameloop.maxDelta) != null ? _frameloop$maxDelta : state.internal.maxDelta;
548
+ const clock = state.clock; // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
549
+
550
+ clock.stop();
551
+ clock.elapsedTime = 0;
552
+
553
+ if (frameloop !== 'never') {
554
+ clock.start();
555
+ clock.elapsedTime = 0;
556
+ }
557
+
558
+ set(() => ({
559
+ frameloop: mode,
560
+ internal: { ...state.internal,
561
+ render,
562
+ maxDelta
563
+ }
564
+ }));
565
+ },
566
+ previousRoot: undefined,
567
+ internal: {
568
+ // Events
569
+ interaction: [],
570
+ hovered: new Map(),
571
+ subscribers: [],
572
+ initialClick: [0, 0],
573
+ initialHits: [],
574
+ capturedMap: new Map(),
575
+ lastEvent: /*#__PURE__*/React__namespace.createRef(),
576
+ // Updates
577
+ active: false,
578
+ frames: 0,
579
+ stages: [],
580
+ render: 'auto',
581
+ maxDelta: 1 / 10,
582
+ priority: 0,
583
+ subscribe: (ref, priority, store) => {
584
+ const state = get();
585
+ const internal = state.internal; // If this subscription was given a priority, it takes rendering into its own hands
586
+ // For that reason we switch off automatic rendering and increase the manual flag
587
+ // As long as this flag is positive there can be no internal rendering at all
588
+ // because there could be multiple render subscriptions
589
+
590
+ internal.priority = internal.priority + (priority > 0 ? 1 : 0); // We use the render flag and deprecate priority
591
+
592
+ if (internal.priority && state.internal.render === 'auto') set(() => ({
593
+ internal: { ...state.internal,
594
+ render: 'manual'
595
+ }
596
+ }));
597
+ internal.subscribers.push({
598
+ ref,
599
+ priority,
600
+ store
601
+ }); // Register subscriber and sort layers from lowest to highest, meaning,
602
+ // highest priority renders last (on top of the other frames)
603
+
604
+ internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
605
+ return () => {
606
+ const state = get();
607
+ const internal = state.internal;
608
+
609
+ if (internal != null && internal.subscribers) {
610
+ // Decrease manual flag if this subscription had a priority
611
+ internal.priority = internal.priority - (priority > 0 ? 1 : 0); // We use the render flag and deprecate priority
612
+
613
+ if (!internal.priority && state.internal.render === 'manual') set(() => ({
614
+ internal: { ...state.internal,
615
+ render: 'auto'
616
+ }
617
+ })); // Remove subscriber from list
618
+
619
+ internal.subscribers = internal.subscribers.filter(s => s.ref !== ref);
620
+ }
621
+ };
622
+ }
623
+ }
624
+ };
625
+ return rootState;
626
+ });
627
+ const state = rootStore.getState();
628
+ prepare(state.scene, rootStore, '', {});
629
+ let oldSize = state.size;
630
+ let oldDpr = state.viewport.dpr;
631
+ let oldCamera = state.camera;
632
+ rootStore.subscribe(() => {
633
+ const {
634
+ camera,
635
+ size,
636
+ viewport,
637
+ gl,
638
+ set
639
+ } = rootStore.getState(); // Resize camera and renderer on changes to size and pixelratio
640
+
641
+ if (size !== oldSize || viewport.dpr !== oldDpr) {
642
+ oldSize = size;
643
+ oldDpr = viewport.dpr; // Update camera & renderer
644
+
645
+ updateCamera(camera, size);
646
+ gl.setPixelRatio(viewport.dpr); // Play nice with offscreen canvas contexts
647
+
648
+ const updateStyle = gl.domElement instanceof HTMLCanvasElement;
649
+ gl.setSize(size.width, size.height, updateStyle);
650
+ } // Update viewport once the camera changes
651
+
652
+
653
+ if (camera !== oldCamera) {
654
+ oldCamera = camera; // Update viewport
655
+
656
+ set(state => ({
657
+ viewport: { ...state.viewport,
658
+ ...state.viewport.getCurrentViewport(camera)
659
+ }
660
+ }));
661
+ }
662
+ }); // Invalidate on any change
663
+
664
+ rootStore.subscribe(state => invalidate(state)); // Return root state
665
+
666
+ return rootStore;
667
+ };
668
+
424
669
  function makeId(event) {
425
670
  return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
426
671
  } // https://github.com/facebook/react/tree/main/packages/react-reconciler#getcurrenteventpriority
@@ -428,9 +673,13 @@ function makeId(event) {
428
673
 
429
674
 
430
675
  function getEventPriority() {
431
- var _window, _window$event;
676
+ var _globalScope$event;
432
677
 
433
- let name = (_window = window) == null ? void 0 : (_window$event = _window.event) == null ? void 0 : _window$event.type;
678
+ // Get a handle to the current global scope in window and worker contexts if able
679
+ // https://github.com/pmndrs/react-three-fiber/pull/2493
680
+ const globalScope = typeof self !== 'undefined' && self || typeof window !== 'undefined' && window;
681
+ if (!globalScope) return constants.DefaultEventPriority;
682
+ const name = (_globalScope$event = globalScope.event) == null ? void 0 : _globalScope$event.type;
434
683
 
435
684
  switch (name) {
436
685
  case 'click':
@@ -489,9 +738,7 @@ function removeInteractivity(store, object) {
489
738
  });
490
739
  }
491
740
  function createEvents(store) {
492
- const temp = new THREE__namespace.Vector3();
493
741
  /** Calculates delta */
494
-
495
742
  function calculateDistance(event) {
496
743
  const {
497
744
  internal
@@ -591,108 +838,113 @@ function createEvents(store) {
591
838
 
592
839
 
593
840
  function handleIntersects(intersections, event, delta, callback) {
594
- const {
595
- raycaster,
596
- pointer,
597
- camera,
598
- internal
599
- } = store.getState(); // If anything has been found, forward it to the event listeners
600
-
841
+ // If anything has been found, forward it to the event listeners
601
842
  if (intersections.length) {
602
- const unprojectedPoint = temp.set(pointer.x, pointer.y, 0).unproject(camera);
603
843
  const localState = {
604
844
  stopped: false
605
845
  };
606
846
 
607
847
  for (const hit of intersections) {
608
- const hasPointerCapture = id => {
609
- var _internal$capturedMap, _internal$capturedMap2;
848
+ const state = getRootState(hit.object);
610
849
 
611
- return (_internal$capturedMap = (_internal$capturedMap2 = internal.capturedMap.get(id)) == null ? void 0 : _internal$capturedMap2.has(hit.eventObject)) != null ? _internal$capturedMap : false;
612
- };
850
+ if (state) {
851
+ const {
852
+ raycaster,
853
+ pointer,
854
+ camera,
855
+ internal
856
+ } = state;
857
+ const unprojectedPoint = new THREE__namespace.Vector3(pointer.x, pointer.y, 0).unproject(camera);
613
858
 
614
- const setPointerCapture = id => {
615
- const captureData = {
616
- intersection: hit,
617
- target: event.target
859
+ const hasPointerCapture = id => {
860
+ var _internal$capturedMap, _internal$capturedMap2;
861
+
862
+ return (_internal$capturedMap = (_internal$capturedMap2 = internal.capturedMap.get(id)) == null ? void 0 : _internal$capturedMap2.has(hit.eventObject)) != null ? _internal$capturedMap : false;
618
863
  };
619
864
 
620
- if (internal.capturedMap.has(id)) {
621
- // if the pointerId was previously captured, we add the hit to the
622
- // event capturedMap.
623
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
624
- } else {
625
- // if the pointerId was not previously captured, we create a map
626
- // containing the hitObject, and the hit. hitObject is used for
627
- // faster access.
628
- internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
629
- } // Call the original event now
630
- event.target.setPointerCapture(id);
631
- };
865
+ const setPointerCapture = id => {
866
+ const captureData = {
867
+ intersection: hit,
868
+ target: event.target
869
+ };
870
+
871
+ if (internal.capturedMap.has(id)) {
872
+ // if the pointerId was previously captured, we add the hit to the
873
+ // event capturedMap.
874
+ internal.capturedMap.get(id).set(hit.eventObject, captureData);
875
+ } else {
876
+ // if the pointerId was not previously captured, we create a map
877
+ // containing the hitObject, and the hit. hitObject is used for
878
+ // faster access.
879
+ internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
880
+ } // Call the original event now
881
+ event.target.setPointerCapture(id);
882
+ };
632
883
 
633
- const releasePointerCapture = id => {
634
- const captures = internal.capturedMap.get(id);
884
+ const releasePointerCapture = id => {
885
+ const captures = internal.capturedMap.get(id);
635
886
 
636
- if (captures) {
637
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
638
- }
639
- }; // Add native event props
887
+ if (captures) {
888
+ releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
889
+ }
890
+ }; // Add native event props
640
891
 
641
892
 
642
- let extractEventProps = {}; // This iterates over the event's properties including the inherited ones. Native PointerEvents have most of their props as getters which are inherited, but polyfilled PointerEvents have them all as their own properties (i.e. not inherited). We can't use Object.keys() or Object.entries() as they only return "own" properties; nor Object.getPrototypeOf(event) as that *doesn't* return "own" properties, only inherited ones.
893
+ let extractEventProps = {}; // This iterates over the event's properties including the inherited ones. Native PointerEvents have most of their props as getters which are inherited, but polyfilled PointerEvents have them all as their own properties (i.e. not inherited). We can't use Object.keys() or Object.entries() as they only return "own" properties; nor Object.getPrototypeOf(event) as that *doesn't* return "own" properties, only inherited ones.
643
894
 
644
- for (let prop in event) {
645
- let property = event[prop]; // Only copy over atomics, leave functions alone as these should be
646
- // called as event.nativeEvent.fn()
895
+ for (let prop in event) {
896
+ let property = event[prop]; // Only copy over atomics, leave functions alone as these should be
897
+ // called as event.nativeEvent.fn()
647
898
 
648
- if (typeof property !== 'function') extractEventProps[prop] = property;
649
- }
899
+ if (typeof property !== 'function') extractEventProps[prop] = property;
900
+ }
650
901
 
651
- let raycastEvent = { ...hit,
652
- ...extractEventProps,
653
- pointer,
654
- intersections,
655
- stopped: localState.stopped,
656
- delta,
657
- unprojectedPoint,
658
- ray: raycaster.ray,
659
- camera: camera,
660
- // Hijack stopPropagation, which just sets a flag
661
- stopPropagation: () => {
662
- // https://github.com/pmndrs/react-three-fiber/issues/596
663
- // Events are not allowed to stop propagation if the pointer has been captured
664
- const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId); // We only authorize stopPropagation...
665
-
666
- if ( // ...if this pointer hasn't been captured
667
- !capturesForPointer || // ... or if the hit object is capturing the pointer
668
- capturesForPointer.has(hit.eventObject)) {
669
- raycastEvent.stopped = localState.stopped = true; // Propagation is stopped, remove all other hover records
670
- // An event handler is only allowed to flush other handlers if it is hovered itself
671
-
672
- if (internal.hovered.size && Array.from(internal.hovered.values()).find(i => i.eventObject === hit.eventObject)) {
673
- // Objects cannot flush out higher up objects that have already caught the event
674
- const higher = intersections.slice(0, intersections.indexOf(hit));
675
- cancelPointer([...higher, hit]);
902
+ let raycastEvent = { ...hit,
903
+ ...extractEventProps,
904
+ pointer,
905
+ intersections,
906
+ stopped: localState.stopped,
907
+ delta,
908
+ unprojectedPoint,
909
+ ray: raycaster.ray,
910
+ camera: camera,
911
+ // Hijack stopPropagation, which just sets a flag
912
+ stopPropagation: () => {
913
+ // https://github.com/pmndrs/react-three-fiber/issues/596
914
+ // Events are not allowed to stop propagation if the pointer has been captured
915
+ const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId); // We only authorize stopPropagation...
916
+
917
+ if ( // ...if this pointer hasn't been captured
918
+ !capturesForPointer || // ... or if the hit object is capturing the pointer
919
+ capturesForPointer.has(hit.eventObject)) {
920
+ raycastEvent.stopped = localState.stopped = true; // Propagation is stopped, remove all other hover records
921
+ // An event handler is only allowed to flush other handlers if it is hovered itself
922
+
923
+ if (internal.hovered.size && Array.from(internal.hovered.values()).find(i => i.eventObject === hit.eventObject)) {
924
+ // Objects cannot flush out higher up objects that have already caught the event
925
+ const higher = intersections.slice(0, intersections.indexOf(hit));
926
+ cancelPointer([...higher, hit]);
927
+ }
676
928
  }
677
- }
678
- },
679
- // there should be a distinction between target and currentTarget
680
- target: {
681
- hasPointerCapture,
682
- setPointerCapture,
683
- releasePointerCapture
684
- },
685
- currentTarget: {
686
- hasPointerCapture,
687
- setPointerCapture,
688
- releasePointerCapture
689
- },
690
- nativeEvent: event
691
- }; // Call subscribers
692
-
693
- callback(raycastEvent); // Event bubbling may be interrupted by stopPropagation
694
-
695
- if (localState.stopped === true) break;
929
+ },
930
+ // there should be a distinction between target and currentTarget
931
+ target: {
932
+ hasPointerCapture,
933
+ setPointerCapture,
934
+ releasePointerCapture
935
+ },
936
+ currentTarget: {
937
+ hasPointerCapture,
938
+ setPointerCapture,
939
+ releasePointerCapture
940
+ },
941
+ nativeEvent: event
942
+ }; // Call subscribers
943
+
944
+ callback(raycastEvent); // Event bubbling may be interrupted by stopPropagation
945
+
946
+ if (localState.stopped === true) break;
947
+ }
696
948
  }
697
949
  }
698
950
 
@@ -709,11 +961,11 @@ function createEvents(store) {
709
961
  if (!intersections.length || !intersections.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId)) {
710
962
  const eventObject = hoveredObj.eventObject;
711
963
  const instance = eventObject.__r3f;
712
- const handlers = instance == null ? void 0 : instance.handlers;
713
964
  internal.hovered.delete(makeId(hoveredObj));
714
965
 
715
966
  if (instance != null && instance.eventCount) {
716
- // Clear out intersects, they are outdated by now
967
+ const handlers = instance.handlers; // Clear out intersects, they are outdated by now
968
+
717
969
  const data = { ...hoveredObj,
718
970
  intersections
719
971
  };
@@ -780,10 +1032,10 @@ function createEvents(store) {
780
1032
  if (isPointerMove) cancelPointer(hits);
781
1033
  handleIntersects(hits, event, delta, data => {
782
1034
  const eventObject = data.eventObject;
783
- const instance = eventObject.__r3f;
784
- const handlers = instance == null ? void 0 : instance.handlers; // Check presence of handlers
1035
+ const instance = eventObject.__r3f; // Check presence of handlers
785
1036
 
786
1037
  if (!(instance != null && instance.eventCount)) return;
1038
+ const handlers = instance.handlers;
787
1039
 
788
1040
  if (isPointerMove) {
789
1041
  // Move event ...
@@ -842,634 +1094,296 @@ function createEvents(store) {
842
1094
  };
843
1095
  }
844
1096
 
845
- let catalogue = {};
846
-
847
- let extend = objects => void (catalogue = { ...catalogue,
848
- ...objects
849
- });
1097
+ const catalogue = {};
850
1098
 
851
- function createRenderer(_roots, _getEventPriority) {
852
- function createInstance(type, {
853
- args = [],
854
- attach,
855
- ...props
856
- }, root) {
857
- let name = `${type[0].toUpperCase()}${type.slice(1)}`;
858
- let instance; // Auto-attach geometries and materials
859
-
860
- if (attach === undefined) {
861
- if (name.endsWith('Geometry')) attach = 'geometry';else if (name.endsWith('Material')) attach = 'material';
862
- }
1099
+ const extend = objects => void Object.assign(catalogue, objects);
863
1100
 
864
- if (type === 'primitive') {
865
- if (props.object === undefined) throw `Primitives without 'object' are invalid!`;
866
- const object = props.object;
867
- instance = prepare(object, {
868
- type,
869
- root,
870
- attach,
871
- primitive: true
872
- });
873
- } else {
874
- const target = catalogue[name];
1101
+ function createInstance(type, props, root) {
1102
+ var _props$object, _props$args;
875
1103
 
876
- if (!target) {
877
- throw `${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`;
878
- } // Throw if an object or literal was passed for args
1104
+ // Get target from catalogue
1105
+ const name = `${type[0].toUpperCase()}${type.slice(1)}`;
1106
+ const target = catalogue[name]; // Validate element target
879
1107
 
1108
+ if (type !== 'primitive' && !target) throw new Error(`R3F: ${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`); // Validate primitives
880
1109
 
881
- if (!Array.isArray(args)) throw 'The args prop must be an array!'; // Instanciate new object, link it to the root
882
- // Append memoized props with args so it's not forgotten
1110
+ if (type === 'primitive' && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`); // Throw if an object or literal was passed for args
883
1111
 
884
- instance = prepare(new target(...args), {
885
- type,
886
- root,
887
- attach,
888
- // Save args in case we need to reconstruct later for HMR
889
- memoizedProps: {
890
- args
891
- }
892
- });
893
- } // It should NOT call onUpdate on object instanciation, because it hasn't been added to the
894
- // view yet. If the callback relies on references for instance, they won't be ready yet, this is
895
- // why it passes "true" here
896
- // There is no reason to apply props to injects
1112
+ if (props.args !== undefined && !Array.isArray(props.args)) throw new Error('R3F: The args prop must be an array!'); // Create instance
897
1113
 
1114
+ const object = (_props$object = props.object) != null ? _props$object : new target(...((_props$args = props.args) != null ? _props$args : []));
1115
+ const instance = prepare(object, root, type, props); // Auto-attach geometries and materials
898
1116
 
899
- if (name !== 'inject') applyProps$1(instance, props);
900
- return instance;
901
- }
1117
+ if (instance.props.attach === undefined) {
1118
+ if (instance.object instanceof THREE__namespace.BufferGeometry) instance.props.attach = 'geometry';else if (instance.object instanceof THREE__namespace.Material) instance.props.attach = 'material';
1119
+ } // Set initial props
902
1120
 
903
- function appendChild(parentInstance, child) {
904
- let added = false;
905
1121
 
906
- if (child) {
907
- var _child$__r3f, _parentInstance$__r3f;
1122
+ applyProps(instance.object, props);
1123
+ return instance;
1124
+ } // https://github.com/facebook/react/issues/20271
1125
+ // This will make sure events and attach are only handled once when trees are complete
908
1126
 
909
- // The attach attribute implies that the object attaches itself on the parent
910
- if ((_child$__r3f = child.__r3f) != null && _child$__r3f.attach) {
911
- attach(parentInstance, child, child.__r3f.attach);
912
- } else if (child.isObject3D && parentInstance.isObject3D) {
913
- // add in the usual parent-child way
914
- parentInstance.add(child);
915
- added = true;
916
- } // This is for anything that used attach, and for non-Object3Ds that don't get attached to props;
917
- // that is, anything that's a child in React but not a child in the scenegraph.
918
1127
 
1128
+ function handleContainerEffects(parent, child) {
1129
+ // Bail if tree isn't mounted or parent is not a container.
1130
+ // This ensures that the tree is finalized and React won't discard results to Suspense
1131
+ const state = child.root.getState();
1132
+ if (!parent.parent && parent.object !== state.scene) return; // Handle interactivity
919
1133
 
920
- if (!added) (_parentInstance$__r3f = parentInstance.__r3f) == null ? void 0 : _parentInstance$__r3f.objects.push(child);
921
- if (!child.__r3f) prepare(child, {});
922
- child.__r3f.parent = parentInstance;
923
- updateInstance(child);
924
- invalidateInstance(child);
925
- }
926
- }
1134
+ if (child.eventCount > 0 && child.object.raycast !== null && child.object instanceof THREE__namespace.Object3D) {
1135
+ state.internal.interaction.push(child.object);
1136
+ } // Handle attach
927
1137
 
928
- function insertBefore(parentInstance, child, beforeChild) {
929
- let added = false;
930
1138
 
931
- if (child) {
932
- var _child$__r3f2, _parentInstance$__r3f2;
1139
+ if (child.props.attach) attach(parent, child);
933
1140
 
934
- if ((_child$__r3f2 = child.__r3f) != null && _child$__r3f2.attach) {
935
- attach(parentInstance, child, child.__r3f.attach);
936
- } else if (child.isObject3D && parentInstance.isObject3D) {
937
- child.parent = parentInstance;
938
- child.dispatchEvent({
939
- type: 'added'
940
- });
941
- const restSiblings = parentInstance.children.filter(sibling => sibling !== child);
942
- const index = restSiblings.indexOf(beforeChild);
943
- parentInstance.children = [...restSiblings.slice(0, index), child, ...restSiblings.slice(index)];
944
- added = true;
945
- }
1141
+ for (const childInstance of child.children) handleContainerEffects(child, childInstance);
1142
+ }
946
1143
 
947
- if (!added) (_parentInstance$__r3f2 = parentInstance.__r3f) == null ? void 0 : _parentInstance$__r3f2.objects.push(child);
948
- if (!child.__r3f) prepare(child, {});
949
- child.__r3f.parent = parentInstance;
950
- updateInstance(child);
951
- invalidateInstance(child);
952
- }
953
- }
1144
+ function appendChild(parent, child) {
1145
+ if (!child) return; // Link instances
954
1146
 
955
- function removeRecursive(array, parent, dispose = false) {
956
- if (array) [...array].forEach(child => removeChild(parent, child, dispose));
957
- }
1147
+ child.parent = parent;
1148
+ parent.children.push(child); // Add Object3Ds if able
958
1149
 
959
- function removeChild(parentInstance, child, dispose) {
960
- if (child) {
961
- var _parentInstance$__r3f3, _child$__r3f3, _child$__r3f5;
1150
+ if (!child.props.attach && parent.object instanceof THREE__namespace.Object3D && child.object instanceof THREE__namespace.Object3D) {
1151
+ parent.object.add(child.object);
1152
+ } // Attach tree once complete
962
1153
 
963
- // Clear the parent reference
964
- if (child.__r3f) child.__r3f.parent = null; // Remove child from the parents objects
965
1154
 
966
- if ((_parentInstance$__r3f3 = parentInstance.__r3f) != null && _parentInstance$__r3f3.objects) parentInstance.__r3f.objects = parentInstance.__r3f.objects.filter(x => x !== child); // Remove attachment
1155
+ handleContainerEffects(parent, child); // Tree was updated, request a frame
967
1156
 
968
- if ((_child$__r3f3 = child.__r3f) != null && _child$__r3f3.attach) {
969
- detach(parentInstance, child, child.__r3f.attach);
970
- } else if (child.isObject3D && parentInstance.isObject3D) {
971
- var _child$__r3f4;
1157
+ invalidateInstance(child);
1158
+ }
972
1159
 
973
- parentInstance.remove(child); // Remove interactivity
1160
+ function insertBefore(parent, child, beforeChild, replace = false) {
1161
+ if (!child || !beforeChild) return; // Link instances
974
1162
 
975
- if ((_child$__r3f4 = child.__r3f) != null && _child$__r3f4.root) {
976
- removeInteractivity(child.__r3f.root, child);
977
- }
978
- } // Allow objects to bail out of recursive dispose altogether by passing dispose={null}
979
- // Never dispose of primitives because their state may be kept outside of React!
980
- // In order for an object to be able to dispose it has to have
981
- // - a dispose method,
982
- // - it cannot be a <primitive object={...} />
983
- // - it cannot be a THREE.Scene, because three has broken it's own api
984
- //
985
- // Since disposal is recursive, we can check the optional dispose arg, which will be undefined
986
- // when the reconciler calls it, but then carry our own check recursively
1163
+ child.parent = parent;
1164
+ const childIndex = parent.children.indexOf(beforeChild);
1165
+ if (childIndex !== -1) parent.children.splice(childIndex, replace ? 1 : 0, child);
1166
+ if (replace) beforeChild.parent = null; // Manually splice Object3Ds
987
1167
 
1168
+ if (!child.props.attach && parent.object instanceof THREE__namespace.Object3D && child.object instanceof THREE__namespace.Object3D && beforeChild.object instanceof THREE__namespace.Object3D) {
1169
+ child.object.parent = parent.object;
1170
+ parent.object.children.splice(parent.object.children.indexOf(beforeChild.object), 0, child.object);
1171
+ child.object.dispatchEvent({
1172
+ type: 'added'
1173
+ });
1174
+ } // Attach tree once complete
988
1175
 
989
- const isPrimitive = (_child$__r3f5 = child.__r3f) == null ? void 0 : _child$__r3f5.primitive;
990
- const shouldDispose = dispose === undefined ? child.dispose !== null && !isPrimitive : dispose; // Remove nested child objects. Primitives should not have objects and children that are
991
- // attached to them declaratively ...
992
1176
 
993
- if (!isPrimitive) {
994
- var _child$__r3f6;
1177
+ handleContainerEffects(parent, child); // Tree was updated, request a frame
995
1178
 
996
- removeRecursive((_child$__r3f6 = child.__r3f) == null ? void 0 : _child$__r3f6.objects, child, shouldDispose);
997
- removeRecursive(child.children, child, shouldDispose);
998
- } // Remove references
1179
+ invalidateInstance(child);
1180
+ }
999
1181
 
1182
+ function removeChild(parent, child, dispose, recursive) {
1183
+ if (!child) return; // Unlink instances
1000
1184
 
1001
- if (child.__r3f) {
1002
- delete child.__r3f.root;
1003
- delete child.__r3f.objects;
1004
- delete child.__r3f.handlers;
1005
- delete child.__r3f.memoizedProps;
1006
- if (!isPrimitive) delete child.__r3f;
1007
- } // Dispose item whenever the reconciler feels like it
1185
+ child.parent = null;
1008
1186
 
1187
+ if (recursive === undefined) {
1188
+ const childIndex = parent.children.indexOf(child);
1189
+ if (childIndex !== -1) parent.children.splice(childIndex, 1);
1190
+ } // Eagerly tear down tree
1009
1191
 
1010
- if (shouldDispose && child.dispose && child.type !== 'Scene') {
1011
- scheduler.unstable_scheduleCallback(scheduler.unstable_IdlePriority, () => {
1012
- try {
1013
- child.dispose();
1014
- } catch (e) {
1015
- /* ... */
1016
- }
1017
- });
1018
- }
1019
1192
 
1020
- invalidateInstance(parentInstance);
1021
- }
1022
- }
1193
+ if (child.props.attach) {
1194
+ detach(parent, child);
1195
+ } else if (child.object instanceof THREE__namespace.Object3D && parent.object instanceof THREE__namespace.Object3D) {
1196
+ parent.object.remove(child.object);
1197
+ removeInteractivity(child.root, child.object);
1198
+ } // Allow objects to bail out of unmount disposal with dispose={null}
1023
1199
 
1024
- function switchInstance(instance, type, newProps, fiber) {
1025
- var _instance$__r3f;
1026
1200
 
1027
- const parent = (_instance$__r3f = instance.__r3f) == null ? void 0 : _instance$__r3f.parent;
1028
- if (!parent) return;
1029
- const newInstance = createInstance(type, newProps, instance.__r3f.root); // https://github.com/pmndrs/react-three-fiber/issues/1348
1030
- // When args change the instance has to be re-constructed, which then
1031
- // forces r3f to re-parent the children and non-scene objects
1032
- // This can not include primitives, which should not have declarative children
1201
+ const shouldDispose = child.props.dispose !== null && dispose !== false; // Recursively remove instance children
1033
1202
 
1034
- if (type !== 'primitive' && instance.children) {
1035
- instance.children.forEach(child => appendChild(newInstance, child));
1036
- instance.children = [];
1037
- }
1203
+ if (recursive !== false) {
1204
+ for (const node of child.children) removeChild(child, node, shouldDispose, true);
1038
1205
 
1039
- instance.__r3f.objects.forEach(child => appendChild(newInstance, child));
1206
+ child.children = [];
1207
+ } // Unlink instance object
1040
1208
 
1041
- instance.__r3f.objects = [];
1042
- removeChild(parent, instance);
1043
- appendChild(parent, newInstance); // Re-bind event handlers
1044
1209
 
1045
- if (newInstance.raycast && newInstance.__r3f.eventCount) {
1046
- const rootState = newInstance.__r3f.root.getState();
1210
+ delete child.object.__r3f; // Dispose object whenever the reconciler feels like it.
1211
+ // Never dispose of primitives because their state may be kept outside of React!
1212
+ // In order for an object to be able to dispose it
1213
+ // - has a dispose method
1214
+ // - cannot be a <primitive object={...} />
1215
+ // - cannot be a THREE.Scene, because three has broken its own API
1047
1216
 
1048
- rootState.internal.interaction.push(newInstance);
1049
- } // This evil hack switches the react-internal fiber node
1050
- [fiber, fiber.alternate].forEach(fiber => {
1051
- if (fiber !== null) {
1052
- fiber.stateNode = newInstance;
1217
+ if (shouldDispose && child.type !== 'primitive' && child.object.type !== 'Scene') {
1218
+ const dispose = child.object.dispose;
1053
1219
 
1054
- if (fiber.ref) {
1055
- if (typeof fiber.ref === 'function') fiber.ref(newInstance);else fiber.ref.current = newInstance;
1220
+ if (typeof dispose === 'function') {
1221
+ scheduler.unstable_scheduleCallback(scheduler.unstable_IdlePriority, () => {
1222
+ try {
1223
+ dispose();
1224
+ } catch (e) {
1225
+ /* ... */
1056
1226
  }
1057
- }
1058
- });
1059
- } // Don't handle text instances, warn on undefined behavior
1060
-
1061
-
1062
- const handleTextInstance = () => console.warn('Text is not allowed in the R3F tree! This could be stray whitespace or characters.');
1063
-
1064
- const reconciler = Reconciler__default["default"]({
1065
- createInstance,
1066
- removeChild,
1067
- appendChild,
1068
- appendInitialChild: appendChild,
1069
- insertBefore,
1070
- supportsMutation: true,
1071
- isPrimaryRenderer: false,
1072
- supportsPersistence: false,
1073
- supportsHydration: false,
1074
- noTimeout: -1,
1075
- appendChildToContainer: (container, child) => {
1076
- if (!child) return;
1077
- const scene = container.getState().scene; // Link current root to the default scene
1078
-
1079
- scene.__r3f.root = container;
1080
- appendChild(scene, child);
1081
- },
1082
- removeChildFromContainer: (container, child) => {
1083
- if (!child) return;
1084
- removeChild(container.getState().scene, child);
1085
- },
1086
- insertInContainerBefore: (container, child, beforeChild) => {
1087
- if (!child || !beforeChild) return;
1088
- insertBefore(container.getState().scene, child, beforeChild);
1089
- },
1090
- getRootHostContext: () => null,
1091
- getChildHostContext: parentHostContext => parentHostContext,
1092
-
1093
- finalizeInitialChildren(instance) {
1094
- var _instance$__r3f2;
1095
-
1096
- const localState = (_instance$__r3f2 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f2 : {}; // https://github.com/facebook/react/issues/20271
1097
- // Returning true will trigger commitMount
1098
-
1099
- return Boolean(localState.handlers);
1100
- },
1101
-
1102
- prepareUpdate(instance, _type, oldProps, newProps) {
1103
- // Create diff-sets
1104
- if (instance.__r3f.primitive && newProps.object && newProps.object !== instance) {
1105
- return [true];
1106
- } else {
1107
- // This is a data object, let's extract critical information about it
1108
- const {
1109
- args: argsNew = [],
1110
- children: cN,
1111
- ...restNew
1112
- } = newProps;
1113
- const {
1114
- args: argsOld = [],
1115
- children: cO,
1116
- ...restOld
1117
- } = oldProps; // Throw if an object or literal was passed for args
1118
-
1119
- if (!Array.isArray(argsNew)) throw 'The args prop must be an array!'; // If it has new props or arguments, then it needs to be re-instantiated
1120
-
1121
- if (argsNew.some((value, index) => value !== argsOld[index])) return [true]; // Create a diff-set, flag if there are any changes
1122
-
1123
- const diff = diffProps(instance, restNew, restOld, true);
1124
- if (diff.changes.length) return [false, diff]; // Otherwise do not touch the instance
1125
-
1126
- return null;
1127
- }
1128
- },
1129
-
1130
- commitUpdate(instance, [reconstruct, diff], type, _oldProps, newProps, fiber) {
1131
- // Reconstruct when args or <primitive object={...} have changes
1132
- if (reconstruct) switchInstance(instance, type, newProps, fiber); // Otherwise just overwrite props
1133
- else applyProps$1(instance, diff);
1134
- },
1135
-
1136
- commitMount(instance, _type, _props, _int) {
1137
- var _instance$__r3f3;
1138
-
1139
- // https://github.com/facebook/react/issues/20271
1140
- // This will make sure events are only added once to the central container
1141
- const localState = (_instance$__r3f3 = instance.__r3f) != null ? _instance$__r3f3 : {};
1142
-
1143
- if (instance.raycast && localState.handlers && localState.eventCount) {
1144
- instance.__r3f.root.getState().internal.interaction.push(instance);
1145
- }
1146
- },
1147
-
1148
- getPublicInstance: instance => instance,
1149
- prepareForCommit: () => null,
1150
- preparePortalMount: container => prepare(container.getState().scene),
1151
- resetAfterCommit: () => {},
1152
- shouldSetTextContent: () => false,
1153
- clearContainer: () => false,
1154
-
1155
- hideInstance(instance) {
1156
- var _instance$__r3f4;
1157
-
1158
- // Deatch while the instance is hidden
1159
- const {
1160
- attach: type,
1161
- parent
1162
- } = (_instance$__r3f4 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f4 : {};
1163
- if (type && parent) detach(parent, instance, type);
1164
- if (instance.isObject3D) instance.visible = false;
1165
- invalidateInstance(instance);
1166
- },
1167
-
1168
- unhideInstance(instance, props) {
1169
- var _instance$__r3f5;
1227
+ });
1228
+ }
1229
+ } // Tree was updated, request a frame for top-level instance
1170
1230
 
1171
- // Re-attach when the instance is unhidden
1172
- const {
1173
- attach: type,
1174
- parent
1175
- } = (_instance$__r3f5 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f5 : {};
1176
- if (type && parent) attach(parent, instance, type);
1177
- if (instance.isObject3D && props.visible == null || props.visible) instance.visible = true;
1178
- invalidateInstance(instance);
1179
- },
1180
1231
 
1181
- createTextInstance: handleTextInstance,
1182
- hideTextInstance: handleTextInstance,
1183
- unhideTextInstance: handleTextInstance,
1184
- // https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r916356874
1185
- // @ts-ignore
1186
- getCurrentEventPriority: () => _getEventPriority ? _getEventPriority() : constants.DefaultEventPriority,
1187
- beforeActiveInstanceBlur: () => {},
1188
- afterActiveInstanceBlur: () => {},
1189
- detachDeletedInstance: () => {},
1190
- now: typeof performance !== 'undefined' && is.fun(performance.now) ? performance.now : is.fun(Date.now) ? Date.now : () => 0,
1191
- // https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r920883503
1192
- scheduleTimeout: is.fun(setTimeout) ? setTimeout : undefined,
1193
- cancelTimeout: is.fun(clearTimeout) ? clearTimeout : undefined
1194
- });
1195
- return {
1196
- reconciler,
1197
- applyProps: applyProps$1
1198
- };
1232
+ if (dispose === undefined) invalidateInstance(child);
1199
1233
  }
1200
1234
 
1201
- // Keys that shouldn't be copied between R3F stores
1202
- const privateKeys = ['set', 'get', 'setSize', 'setFrameloop', 'setDpr', 'events', 'invalidate', 'advance', 'size', 'viewport'];
1203
- const isRenderer = def => !!(def != null && def.render);
1204
- const context = /*#__PURE__*/React__namespace.createContext(null);
1205
-
1206
- const createStore = (invalidate, advance) => {
1207
- const rootState = create__default["default"]((set, get) => {
1208
- const position = new THREE__namespace.Vector3();
1209
- const defaultTarget = new THREE__namespace.Vector3();
1210
- const tempTarget = new THREE__namespace.Vector3();
1211
-
1212
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
1213
- const {
1214
- width,
1215
- height,
1216
- top,
1217
- left
1218
- } = size;
1219
- const aspect = width / height;
1220
- if (target instanceof THREE__namespace.Vector3) tempTarget.copy(target);else tempTarget.set(...target);
1221
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1222
-
1223
- if (isOrthographicCamera(camera)) {
1224
- return {
1225
- width: width / camera.zoom,
1226
- height: height / camera.zoom,
1227
- top,
1228
- left,
1229
- factor: 1,
1230
- distance,
1231
- aspect
1232
- };
1233
- } else {
1234
- const fov = camera.fov * Math.PI / 180; // convert vertical fov to radians
1235
+ function switchInstance(oldInstance, type, props, fiber) {
1236
+ // Create a new instance
1237
+ const newInstance = createInstance(type, props, oldInstance.root); // Move children to new instance
1235
1238
 
1236
- const h = 2 * Math.tan(fov / 2) * distance; // visible height
1239
+ for (const child of oldInstance.children) {
1240
+ removeChild(oldInstance, child, false, false);
1241
+ appendChild(newInstance, child);
1242
+ }
1237
1243
 
1238
- const w = h * (width / height);
1239
- return {
1240
- width: w,
1241
- height: h,
1242
- top,
1243
- left,
1244
- factor: width / w,
1245
- distance,
1246
- aspect
1247
- };
1244
+ oldInstance.children = []; // Link up new instance
1245
+
1246
+ const parent = oldInstance.parent;
1247
+
1248
+ if (parent) {
1249
+ insertBefore(parent, newInstance, oldInstance, true);
1250
+ } // This evil hack switches the react-internal fiber node
1251
+ [fiber, fiber.alternate].forEach(fiber => {
1252
+ if (fiber !== null) {
1253
+ fiber.stateNode = newInstance;
1254
+
1255
+ if (fiber.ref) {
1256
+ if (typeof fiber.ref === 'function') fiber.ref(newInstance.object);else fiber.ref.current = newInstance.object;
1248
1257
  }
1249
1258
  }
1259
+ }); // Tree was updated, request a frame
1250
1260
 
1251
- let performanceTimeout = undefined;
1261
+ invalidateInstance(newInstance);
1262
+ return newInstance;
1263
+ } // Don't handle text instances, warn on undefined behavior
1252
1264
 
1253
- const setPerformanceCurrent = current => set(state => ({
1254
- performance: { ...state.performance,
1255
- current
1256
- }
1257
- }));
1258
1265
 
1259
- const pointer = new THREE__namespace.Vector2();
1260
- const rootState = {
1261
- set,
1262
- get,
1263
- // Mock objects that have to be configured
1264
- gl: null,
1265
- camera: null,
1266
- raycaster: null,
1267
- events: {
1268
- priority: 1,
1269
- enabled: true,
1270
- connected: false
1271
- },
1272
- xr: null,
1273
- invalidate: (frames = 1) => invalidate(get(), frames),
1274
- advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1275
- legacy: false,
1276
- linear: false,
1277
- flat: false,
1278
- scene: prepare(new THREE__namespace.Scene()),
1279
- controls: null,
1280
- clock: new THREE__namespace.Clock(),
1281
- pointer,
1282
- mouse: pointer,
1283
- frameloop: 'always',
1284
- onPointerMissed: undefined,
1285
- performance: {
1286
- current: 1,
1287
- min: 0.5,
1288
- max: 1,
1289
- debounce: 200,
1290
- regress: () => {
1291
- const state = get(); // Clear timeout
1266
+ const handleTextInstance = () => console.warn('R3F: Text is not allowed in JSX! This could be stray whitespace or characters.');
1292
1267
 
1293
- if (performanceTimeout) clearTimeout(performanceTimeout); // Set lower bound performance
1268
+ const reconciler = Reconciler__default["default"]({
1269
+ supportsMutation: true,
1270
+ isPrimaryRenderer: false,
1271
+ supportsPersistence: false,
1272
+ supportsHydration: false,
1273
+ noTimeout: -1,
1274
+ createInstance,
1275
+ removeChild,
1276
+ appendChild,
1277
+ appendInitialChild: appendChild,
1278
+ insertBefore,
1294
1279
 
1295
- if (state.performance.current !== state.performance.min) setPerformanceCurrent(state.performance.min); // Go back to upper bound performance after a while unless something regresses meanwhile
1280
+ appendChildToContainer(container, child) {
1281
+ const scene = container.getState().scene.__r3f;
1296
1282
 
1297
- performanceTimeout = setTimeout(() => setPerformanceCurrent(get().performance.max), state.performance.debounce);
1298
- }
1299
- },
1300
- size: {
1301
- width: 0,
1302
- height: 0,
1303
- top: 0,
1304
- left: 0,
1305
- updateStyle: false
1306
- },
1307
- viewport: {
1308
- initialDpr: 0,
1309
- dpr: 0,
1310
- width: 0,
1311
- height: 0,
1312
- top: 0,
1313
- left: 0,
1314
- aspect: 0,
1315
- distance: 0,
1316
- factor: 0,
1317
- getCurrentViewport
1318
- },
1319
- setEvents: events => set(state => ({ ...state,
1320
- events: { ...state.events,
1321
- ...events
1322
- }
1323
- })),
1324
- setSize: (width, height, updateStyle, top, left) => {
1325
- const camera = get().camera;
1326
- const size = {
1327
- width,
1328
- height,
1329
- top: top || 0,
1330
- left: left || 0,
1331
- updateStyle
1332
- };
1333
- set(state => ({
1334
- size,
1335
- viewport: { ...state.viewport,
1336
- ...getCurrentViewport(camera, defaultTarget, size)
1337
- }
1338
- }));
1339
- },
1340
- setDpr: dpr => set(state => {
1341
- const resolved = calculateDpr(dpr);
1342
- return {
1343
- viewport: { ...state.viewport,
1344
- dpr: resolved,
1345
- initialDpr: state.viewport.initialDpr || resolved
1346
- }
1347
- };
1348
- }),
1349
- setFrameloop: frameloop => {
1350
- var _frameloop$mode, _frameloop$render, _frameloop$maxDelta;
1283
+ if (!child || !scene) return;
1284
+ appendChild(scene, child);
1285
+ },
1351
1286
 
1352
- const state = get();
1353
- const mode = typeof frameloop === 'string' ? frameloop : (frameloop == null ? void 0 : frameloop.mode) === 'auto' ? 'always' : (_frameloop$mode = frameloop == null ? void 0 : frameloop.mode) != null ? _frameloop$mode : state.frameloop;
1354
- const render = typeof frameloop === 'string' ? state.internal.render : (_frameloop$render = frameloop == null ? void 0 : frameloop.render) != null ? _frameloop$render : state.internal.render;
1355
- const maxDelta = typeof frameloop === 'string' ? state.internal.maxDelta : (_frameloop$maxDelta = frameloop == null ? void 0 : frameloop.maxDelta) != null ? _frameloop$maxDelta : state.internal.maxDelta;
1356
- const clock = state.clock; // if frameloop === "never" clock.elapsedTime is updated using advance(timestamp)
1287
+ removeChildFromContainer(container, child) {
1288
+ const scene = container.getState().scene.__r3f;
1357
1289
 
1358
- clock.stop();
1359
- clock.elapsedTime = 0;
1290
+ if (!child || !scene) return;
1291
+ removeChild(scene, child);
1292
+ },
1360
1293
 
1361
- if (frameloop !== 'never') {
1362
- clock.start();
1363
- clock.elapsedTime = 0;
1364
- }
1294
+ insertInContainerBefore(container, child, beforeChild) {
1295
+ const scene = container.getState().scene.__r3f;
1365
1296
 
1366
- set(() => ({
1367
- frameloop: mode,
1368
- internal: { ...state.internal,
1369
- render,
1370
- maxDelta
1371
- }
1372
- }));
1373
- },
1374
- previousRoot: undefined,
1375
- internal: {
1376
- // Events
1377
- interaction: [],
1378
- hovered: new Map(),
1379
- subscribers: [],
1380
- initialClick: [0, 0],
1381
- initialHits: [],
1382
- capturedMap: new Map(),
1383
- lastEvent: /*#__PURE__*/React__namespace.createRef(),
1384
- // Updates
1385
- active: false,
1386
- frames: 0,
1387
- stages: [],
1388
- render: 'auto',
1389
- maxDelta: 1 / 10,
1390
- priority: 0,
1391
- subscribe: (ref, priority, store) => {
1392
- const state = get();
1393
- const internal = state.internal; // If this subscription was given a priority, it takes rendering into its own hands
1394
- // For that reason we switch off automatic rendering and increase the manual flag
1395
- // As long as this flag is positive there can be no internal rendering at all
1396
- // because there could be multiple render subscriptions
1297
+ if (!child || !beforeChild || !scene) return;
1298
+ insertBefore(scene, child, beforeChild);
1299
+ },
1397
1300
 
1398
- internal.priority = internal.priority + (priority > 0 ? 1 : 0); // We use the render flag and deprecate priority
1301
+ getRootHostContext: () => null,
1302
+ getChildHostContext: parentHostContext => parentHostContext,
1399
1303
 
1400
- if (internal.priority && state.internal.render === 'auto') set(() => ({
1401
- internal: { ...state.internal,
1402
- render: 'manual'
1403
- }
1404
- }));
1405
- internal.subscribers.push({
1406
- ref,
1407
- priority,
1408
- store
1409
- }); // Register subscriber and sort layers from lowest to highest, meaning,
1410
- // highest priority renders last (on top of the other frames)
1304
+ prepareUpdate(instance, _type, oldProps, newProps) {
1305
+ var _newProps$args, _oldProps$args, _newProps$args2;
1411
1306
 
1412
- internal.subscribers = internal.subscribers.sort((a, b) => a.priority - b.priority);
1413
- return () => {
1414
- const state = get();
1415
- const internal = state.internal;
1307
+ // Reconstruct primitives if object prop changes
1308
+ if (instance.type === 'primitive' && oldProps.object !== newProps.object) return [true]; // Throw if an object or literal was passed for args
1416
1309
 
1417
- if (internal != null && internal.subscribers) {
1418
- // Decrease manual flag if this subscription had a priority
1419
- internal.priority = internal.priority - (priority > 0 ? 1 : 0); // We use the render flag and deprecate priority
1310
+ if (newProps.args !== undefined && !Array.isArray(newProps.args)) throw new Error('R3F: The args prop must be an array!'); // Reconstruct instance if args change
1420
1311
 
1421
- if (!internal.priority && state.internal.render === 'manual') set(() => ({
1422
- internal: { ...state.internal,
1423
- render: 'auto'
1424
- }
1425
- })); // Remove subscriber from list
1312
+ if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) return [true];
1313
+ if ((_newProps$args2 = newProps.args) != null && _newProps$args2.some((value, index) => {
1314
+ var _oldProps$args2;
1426
1315
 
1427
- internal.subscribers = internal.subscribers.filter(s => s.ref !== ref);
1428
- }
1429
- };
1430
- }
1431
- }
1432
- };
1433
- return rootState;
1434
- });
1435
- const state = rootState.getState();
1436
- let oldSize = state.size;
1437
- let oldDpr = state.viewport.dpr;
1438
- let oldCamera = state.camera;
1439
- rootState.subscribe(() => {
1440
- const {
1441
- camera,
1442
- size,
1443
- viewport,
1444
- gl,
1445
- set
1446
- } = rootState.getState(); // Resize camera and renderer on changes to size and pixelratio
1316
+ return value !== ((_oldProps$args2 = oldProps.args) == null ? void 0 : _oldProps$args2[index]);
1317
+ })) return [true]; // Create a diff-set, flag if there are any changes
1447
1318
 
1448
- if (size !== oldSize || viewport.dpr !== oldDpr) {
1449
- oldSize = size;
1450
- oldDpr = viewport.dpr; // Update camera & renderer
1319
+ const changedProps = diffProps(instance, newProps, true);
1320
+ if (Object.keys(changedProps).length) return [false, changedProps]; // Otherwise do not touch the instance
1451
1321
 
1452
- updateCamera(camera, size);
1453
- gl.setPixelRatio(viewport.dpr);
1454
- gl.setSize(size.width, size.height, size.updateStyle);
1455
- } // Update viewport once the camera changes
1322
+ return null;
1323
+ },
1456
1324
 
1325
+ commitUpdate(instance, diff, type, _oldProps, newProps, fiber) {
1326
+ const [reconstruct, changedProps] = diff; // Reconstruct when args or <primitive object={...} have changes
1457
1327
 
1458
- if (camera !== oldCamera) {
1459
- oldCamera = camera; // Update viewport
1328
+ if (reconstruct) return switchInstance(instance, type, newProps, fiber); // Otherwise just overwrite props
1460
1329
 
1461
- set(state => ({
1462
- viewport: { ...state.viewport,
1463
- ...state.viewport.getCurrentViewport(camera)
1464
- }
1465
- }));
1330
+ Object.assign(instance.props, changedProps);
1331
+ applyProps(instance.object, changedProps);
1332
+ },
1333
+
1334
+ finalizeInitialChildren: () => false,
1335
+
1336
+ commitMount() {},
1337
+
1338
+ getPublicInstance: instance => instance == null ? void 0 : instance.object,
1339
+ prepareForCommit: () => null,
1340
+ preparePortalMount: container => prepare(container.getState().scene, container, '', {}),
1341
+ resetAfterCommit: () => {},
1342
+ shouldSetTextContent: () => false,
1343
+ clearContainer: () => false,
1344
+
1345
+ hideInstance(instance) {
1346
+ var _instance$parent;
1347
+
1348
+ if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
1349
+ detach(instance.parent, instance);
1350
+ } else if (instance.object instanceof THREE__namespace.Object3D) {
1351
+ instance.object.visible = false;
1466
1352
  }
1467
- }); // Invalidate on any change
1468
1353
 
1469
- rootState.subscribe(state => invalidate(state)); // Return root state
1354
+ instance.isHidden = true;
1355
+ invalidateInstance(instance);
1356
+ },
1357
+
1358
+ unhideInstance(instance) {
1359
+ if (instance.isHidden) {
1360
+ var _instance$parent2;
1470
1361
 
1471
- return rootState;
1472
- };
1362
+ if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
1363
+ attach(instance.parent, instance);
1364
+ } else if (instance.object instanceof THREE__namespace.Object3D && instance.props.visible !== false) {
1365
+ instance.object.visible = true;
1366
+ }
1367
+ }
1368
+
1369
+ instance.isHidden = false;
1370
+ invalidateInstance(instance);
1371
+ },
1372
+
1373
+ createTextInstance: handleTextInstance,
1374
+ hideTextInstance: handleTextInstance,
1375
+ unhideTextInstance: handleTextInstance,
1376
+ // https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r916356874
1377
+ // @ts-ignore
1378
+ getCurrentEventPriority: () => getEventPriority(),
1379
+ beforeActiveInstanceBlur: () => {},
1380
+ afterActiveInstanceBlur: () => {},
1381
+ detachDeletedInstance: () => {},
1382
+ now: typeof performance !== 'undefined' && is.fun(performance.now) ? performance.now : is.fun(Date.now) ? Date.now : () => 0,
1383
+ // https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r920883503
1384
+ scheduleTimeout: is.fun(setTimeout) ? setTimeout : undefined,
1385
+ cancelTimeout: is.fun(clearTimeout) ? clearTimeout : undefined
1386
+ });
1473
1387
 
1474
1388
  function createSubs(callback, subs) {
1475
1389
  const sub = {
@@ -1501,11 +1415,25 @@ const addAfterEffect = callback => createSubs(callback, globalAfterEffects);
1501
1415
  const addTail = callback => createSubs(callback, globalTailEffects);
1502
1416
 
1503
1417
  function run(effects, timestamp) {
1418
+ if (!effects.size) return;
1504
1419
  effects.forEach(({
1505
1420
  callback
1506
1421
  }) => callback(timestamp));
1507
1422
  }
1508
1423
 
1424
+ function flushGlobalEffects(type, timestamp) {
1425
+ switch (type) {
1426
+ case 'before':
1427
+ return run(globalEffects, timestamp);
1428
+
1429
+ case 'after':
1430
+ return run(globalAfterEffects, timestamp);
1431
+
1432
+ case 'tail':
1433
+ return run(globalTailEffects, timestamp);
1434
+ }
1435
+ }
1436
+
1509
1437
  function update(timestamp, state, frame) {
1510
1438
  // Run local effects
1511
1439
  let delta = state.clock.getDelta(); // In frameloop='never' mode, clock times are updated using the provided timestamp
@@ -1538,7 +1466,7 @@ function createLoop(roots) {
1538
1466
  running = true;
1539
1467
  repeat = 0; // Run effects
1540
1468
 
1541
- if (globalEffects.size) run(globalEffects, timestamp); // Render all roots
1469
+ flushGlobalEffects('before', timestamp); // Render all roots
1542
1470
 
1543
1471
  roots.forEach(root => {
1544
1472
  var _state$gl$xr;
@@ -1550,11 +1478,11 @@ function createLoop(roots) {
1550
1478
  }
1551
1479
  }); // Run after-effects
1552
1480
 
1553
- if (globalAfterEffects.size) run(globalAfterEffects, timestamp); // Stop the loop if nothing invalidates it
1481
+ flushGlobalEffects('after', timestamp); // Stop the loop if nothing invalidates it
1554
1482
 
1555
1483
  if (repeat === 0) {
1556
1484
  // Tail call effects, they are called when rendering stops
1557
- if (globalTailEffects.size) run(globalTailEffects, timestamp); // Flag end of operation
1485
+ flushGlobalEffects('tail', timestamp); // Flag end of operation
1558
1486
 
1559
1487
  running = false;
1560
1488
  return cancelAnimationFrame(frame);
@@ -1576,9 +1504,9 @@ function createLoop(roots) {
1576
1504
  }
1577
1505
 
1578
1506
  function advance(timestamp, runGlobalEffects = true, state, frame) {
1579
- if (runGlobalEffects) run(globalEffects, timestamp);
1507
+ if (runGlobalEffects) flushGlobalEffects('before', timestamp);
1580
1508
  if (!state) roots.forEach(root => update(timestamp, root.store.getState()));else update(timestamp, state, frame);
1581
- if (runGlobalEffects) run(globalAfterEffects, timestamp);
1509
+ if (runGlobalEffects) flushGlobalEffects('after', timestamp);
1582
1510
  }
1583
1511
 
1584
1512
  return {
@@ -1746,9 +1674,20 @@ const Stages = {
1746
1674
  };
1747
1675
  const Lifecycle = [Early, Fixed, Update, Late, Render, After];
1748
1676
 
1677
+ /**
1678
+ * Exposes an object's {@link Instance}.
1679
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useInstanceHandle
1680
+ *
1681
+ * **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
1682
+ */
1683
+ function useInstanceHandle(ref) {
1684
+ const instance = React__namespace.useRef(null);
1685
+ useIsomorphicLayoutEffect(() => void (instance.current = ref.current.__r3f), [ref]);
1686
+ return instance;
1687
+ }
1749
1688
  function useStore() {
1750
1689
  const store = React__namespace.useContext(context);
1751
- if (!store) throw `R3F hooks can only be used within the Canvas component!`;
1690
+ if (!store) throw new Error('R3F: Hooks can only be used within the Canvas component!');
1752
1691
  return store;
1753
1692
  }
1754
1693
  /**
@@ -1807,7 +1746,7 @@ function loadingFn(extensions, onProgress) {
1807
1746
  return Promise.all(input.map(input => new Promise((res, reject) => loader.load(input, data => {
1808
1747
  if (data.scene) Object.assign(data, buildGraph(data.scene));
1809
1748
  res(data);
1810
- }, onProgress, error => reject(`Could not load ${input}: ${error.message}`)))));
1749
+ }, onProgress, error => reject(new Error(`Could not load ${input}: ${error.message})`))))));
1811
1750
  };
1812
1751
  }
1813
1752
  /**
@@ -1850,10 +1789,6 @@ const {
1850
1789
  invalidate,
1851
1790
  advance
1852
1791
  } = createLoop(roots);
1853
- const {
1854
- reconciler,
1855
- applyProps
1856
- } = createRenderer(roots, getEventPriority);
1857
1792
  const shallowLoose = {
1858
1793
  objects: 'shallow',
1859
1794
  strict: false
@@ -1871,19 +1806,19 @@ const createRendererInstance = (gl, canvas) => {
1871
1806
  };
1872
1807
 
1873
1808
  const createStages = (stages, store) => {
1874
- var _stages;
1875
-
1876
1809
  const state = store.getState();
1877
1810
  let subscribers;
1878
1811
  let subscription;
1879
- stages = (_stages = stages) != null ? _stages : Lifecycle;
1880
- if (!stages.includes(Stages.Update)) throw 'The Stages.Update stage is required for R3F.';
1881
- if (!stages.includes(Stages.Render)) throw 'The Stages.Render stage is required for R3F.';
1812
+
1813
+ const _stages = stages != null ? stages : Lifecycle;
1814
+
1815
+ if (!_stages.includes(Stages.Update)) throw 'The Stages.Update stage is required for R3F.';
1816
+ if (!_stages.includes(Stages.Render)) throw 'The Stages.Render stage is required for R3F.';
1882
1817
  state.set(({
1883
1818
  internal
1884
1819
  }) => ({
1885
1820
  internal: { ...internal,
1886
- stages: stages
1821
+ stages: _stages
1887
1822
  }
1888
1823
  })); // Add useFrame loop to update stage
1889
1824
 
@@ -1907,11 +1842,36 @@ const createStages = (stages, store) => {
1907
1842
  Stages.Render.add(renderCallback, store);
1908
1843
  };
1909
1844
 
1845
+ function computeInitialSize(canvas, size) {
1846
+ if (!size && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1847
+ const {
1848
+ width,
1849
+ height,
1850
+ top,
1851
+ left
1852
+ } = canvas.parentElement.getBoundingClientRect();
1853
+ return {
1854
+ width,
1855
+ height,
1856
+ top,
1857
+ left
1858
+ };
1859
+ }
1860
+
1861
+ return {
1862
+ width: 0,
1863
+ height: 0,
1864
+ top: 0,
1865
+ left: 0,
1866
+ ...size
1867
+ };
1868
+ }
1869
+
1910
1870
  function createRoot(canvas) {
1911
1871
  // Check against mistaken use of createRoot
1912
- let prevRoot = roots.get(canvas);
1913
- let prevFiber = prevRoot == null ? void 0 : prevRoot.fiber;
1914
- let prevStore = prevRoot == null ? void 0 : prevRoot.store;
1872
+ const prevRoot = roots.get(canvas);
1873
+ const prevFiber = prevRoot == null ? void 0 : prevRoot.fiber;
1874
+ const prevStore = prevRoot == null ? void 0 : prevRoot.store;
1915
1875
  if (prevRoot) console.warn('R3F.createRoot should only be called once!'); // Report when an error was detected in a previous render
1916
1876
  // https://github.com/pmndrs/react-three-fiber/pull/2261
1917
1877
 
@@ -1935,7 +1895,7 @@ function createRoot(canvas) {
1935
1895
  configure(props = {}) {
1936
1896
  let {
1937
1897
  gl: glConfig,
1938
- size,
1898
+ size: propsSize,
1939
1899
  events,
1940
1900
  onCreated: onCreatedCallback,
1941
1901
  shadows = false,
@@ -2065,20 +2025,10 @@ function createRoot(canvas) {
2065
2025
 
2066
2026
  if (dpr && state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr); // Check size, allow it to take on container bounds initially
2067
2027
 
2068
- size = size || (canvas.parentElement ? {
2069
- width: canvas.parentElement.clientWidth,
2070
- height: canvas.parentElement.clientHeight,
2071
- top: canvas.parentElement.clientTop,
2072
- left: canvas.parentElement.clientLeft
2073
- } : {
2074
- width: 0,
2075
- height: 0,
2076
- top: 0,
2077
- left: 0
2078
- });
2028
+ const size = computeInitialSize(canvas, propsSize);
2079
2029
 
2080
2030
  if (!is.equ(size, state.size, shallowLoose)) {
2081
- state.setSize(size.width, size.height, size.updateStyle, size.top, size.left);
2031
+ state.setSize(size.width, size.height, size.top, size.left);
2082
2032
  } // Check frameloop
2083
2033
 
2084
2034
 
@@ -2092,9 +2042,9 @@ function createRoot(canvas) {
2092
2042
  performance: { ...state.performance,
2093
2043
  ...performance
2094
2044
  }
2095
- })); // Create update stages.
2045
+ })); // Create update stages. Only do this once on init
2096
2046
 
2097
- if (stages !== state.internal.stages) createStages(stages, store); // Set locals
2047
+ if (state.internal.stages.length === 0) createStages(stages, store); // Set locals
2098
2048
 
2099
2049
  onCreated = onCreatedCallback;
2100
2050
  configured = true;
@@ -2169,7 +2119,7 @@ function unmountComponentAtNode(canvas, callback) {
2169
2119
  (_state$gl = state.gl) == null ? void 0 : (_state$gl$renderLists = _state$gl.renderLists) == null ? void 0 : _state$gl$renderLists.dispose == null ? void 0 : _state$gl$renderLists.dispose();
2170
2120
  (_state$gl2 = state.gl) == null ? void 0 : _state$gl2.forceContextLoss == null ? void 0 : _state$gl2.forceContextLoss();
2171
2121
  if ((_state$gl3 = state.gl) != null && _state$gl3.xr) state.xr.disconnect();
2172
- dispose(state);
2122
+ dispose(state.scene);
2173
2123
  roots.delete(canvas);
2174
2124
  if (callback) callback(canvas);
2175
2125
  } catch (e) {
@@ -2304,6 +2254,10 @@ reconciler.injectIntoDevTools({
2304
2254
  rendererPackageName: '@react-three/fiber',
2305
2255
  version: React__namespace.version
2306
2256
  });
2257
+
2258
+ /**
2259
+ * Safely flush async effects when testing, simulating a legacy root.
2260
+ */
2307
2261
  const act = React__namespace.unstable_act;
2308
2262
 
2309
2263
  exports.Block = Block;
@@ -2325,13 +2279,14 @@ exports.dispose = dispose;
2325
2279
  exports.extend = extend;
2326
2280
  exports.getRootState = getRootState;
2327
2281
  exports.invalidate = invalidate;
2282
+ exports.isRef = isRef;
2328
2283
  exports.reconciler = reconciler;
2329
2284
  exports.render = render;
2330
2285
  exports.roots = roots;
2331
- exports.threeTypes = threeTypes;
2332
2286
  exports.unmountComponentAtNode = unmountComponentAtNode;
2333
2287
  exports.useFrame = useFrame;
2334
2288
  exports.useGraph = useGraph;
2289
+ exports.useInstanceHandle = useInstanceHandle;
2335
2290
  exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
2336
2291
  exports.useLoader = useLoader;
2337
2292
  exports.useMutableCallback = useMutableCallback;