@react-three/fiber 7.0.9 → 7.0.13

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,30 @@
1
1
  # @react-three/fiber
2
2
 
3
+ ## 7.0.13
4
+
5
+ ### Patch Changes
6
+
7
+ - f256558: fix(core): don't overwrite camera rotation
8
+ - 51e6fc9: fix(core): safely handle external instances
9
+
10
+ ## 7.0.12
11
+
12
+ ### Patch Changes
13
+
14
+ - 0df6073: fix: missed events
15
+
16
+ ## 7.0.11
17
+
18
+ ### Patch Changes
19
+
20
+ - 62b0a3a: fix: event order of missed pointers
21
+
22
+ ## 7.0.10
23
+
24
+ ### Patch Changes
25
+
26
+ - e019dd4: fixes
27
+
3
28
  ## 7.0.9
4
29
 
5
30
  ### Patch Changes
@@ -10,6 +10,7 @@ export declare type Root = {
10
10
  export declare type LocalState = {
11
11
  root: UseStore<RootState>;
12
12
  objects: Instance[];
13
+ parent: Instance | null;
13
14
  primitive?: boolean;
14
15
  handlers: {
15
16
  count: number;
@@ -23,9 +24,8 @@ export declare type ClassConstructor = {
23
24
  };
24
25
  export declare type AttachFnType = (self: Instance, parent: Instance) => void;
25
26
  export declare type AttachFnsType = [attach: string | AttachFnType, detach: string | AttachFnType];
26
- export declare type BaseInstance = Omit<THREE.Object3D, 'parent' | 'children' | 'attach' | 'add' | 'remove' | 'raycast'> & {
27
+ export declare type BaseInstance = Omit<THREE.Object3D, 'children' | 'attach' | 'add' | 'remove' | 'raycast'> & {
27
28
  __r3f: LocalState;
28
- parent: Instance | null;
29
29
  children: Instance[];
30
30
  attach?: string;
31
31
  attachFns?: AttachFnsType;
@@ -101,6 +101,7 @@ export declare type StoreProps = {
101
101
  onPointerMissed?: (event: ThreeEvent<PointerEvent>) => void;
102
102
  };
103
103
  export declare type ApplyProps = (instance: Instance, newProps: InstanceProps) => void;
104
+ export declare function calculateDpr(dpr: Dpr): number;
104
105
  declare const context: React.Context<UseStore<RootState>>;
105
106
  declare const createStore: (applyProps: ApplyProps, invalidate: (state?: RootState | undefined) => void, advance: (timestamp: number, runGlobalEffects?: boolean | undefined, state?: RootState | undefined) => void, props: StoreProps) => UseStore<RootState>;
106
107
  export { createStore, context };
@@ -58,7 +58,8 @@ const is = {
58
58
  // Wrong type or one of the two undefined, doesn't match
59
59
  if (typeof a !== typeof b || !!a !== !!b) return false; // Atomic, just compare a against b
60
60
 
61
- if (is.str(a) || is.num(a) || is.obj(a)) return a === b; // Array, shallow compare first to see if it's a match
61
+ if (is.str(a) || is.num(a)) return a === b;
62
+ if (is.obj(a) && a === b) return true; // Array, shallow compare first to see if it's a match
62
63
 
63
64
  if (is.arr(a) && a == b) return true; // Last resort, go through keys
64
65
 
@@ -132,7 +133,11 @@ function createEvents(store) {
132
133
 
133
134
 
134
135
  function filterPointerEvents(objects) {
135
- return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => obj.__r3f.handlers['onPointer' + name]));
136
+ return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
137
+ var _r3f;
138
+
139
+ return (_r3f = obj.__r3f) == null ? void 0 : _r3f.handlers['onPointer' + name];
140
+ }));
136
141
  }
137
142
 
138
143
  function intersect(filter) {
@@ -162,7 +167,9 @@ function createEvents(store) {
162
167
  let eventObject = intersect.object; // Bubble event up
163
168
 
164
169
  while (eventObject) {
165
- if (eventObject.__r3f.handlers.count) intersections.push({ ...intersect,
170
+ var _r3f2;
171
+
172
+ if ((_r3f2 = eventObject.__r3f) != null && _r3f2.handlers.count) intersections.push({ ...intersect,
166
173
  eventObject
167
174
  });
168
175
  eventObject = eventObject.parent;
@@ -189,7 +196,7 @@ function createEvents(store) {
189
196
  /** Handles intersections by forwarding them to handlers */
190
197
 
191
198
 
192
- function handleIntersects(intersections, event, callback) {
199
+ function handleIntersects(intersections, event, delta, callback) {
193
200
  const {
194
201
  raycaster,
195
202
  mouse,
@@ -199,7 +206,6 @@ function createEvents(store) {
199
206
 
200
207
  if (intersections.length) {
201
208
  const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
202
- const delta = event.type === 'click' ? calculateDistance(event) : 0;
203
209
 
204
210
  const releasePointerCapture = id => event.target.releasePointerCapture(id);
205
211
 
@@ -300,11 +306,13 @@ function createEvents(store) {
300
306
  // When no objects were hit or the the hovered object wasn't found underneath the cursor
301
307
  // we call onPointerOut and delete the object from the hovered-elements map
302
308
  if (!hits.length || !hits.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId)) {
309
+ var _r3f3;
310
+
303
311
  const eventObject = hoveredObj.eventObject;
304
- const handlers = eventObject.__r3f.handlers;
312
+ const handlers = (_r3f3 = eventObject.__r3f) == null ? void 0 : _r3f3.handlers;
305
313
  internal.hovered.delete(makeId(hoveredObj));
306
314
 
307
- if (handlers.count) {
315
+ if (handlers != null && handlers.count) {
308
316
  // Clear out intersects, they are outdated by now
309
317
  const data = { ...hoveredObj,
310
318
  intersections: hits || []
@@ -345,15 +353,34 @@ function createEvents(store) {
345
353
  prepareRay(event); // Get fresh intersects
346
354
 
347
355
  const isPointerMove = name === 'onPointerMove';
356
+ const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
348
357
  const filter = isPointerMove ? filterPointerEvents : undefined;
349
- const hits = patchIntersects(intersect(filter), event); // Take care of unhover
358
+ const hits = patchIntersects(intersect(filter), event);
359
+ const delta = isClickEvent ? calculateDistance(event) : 0; // Save initial coordinates on pointer-down
360
+
361
+ if (name === 'onPointerDown') {
362
+ internal.initialClick = [event.offsetX, event.offsetY];
363
+ internal.initialHits = hits.map(hit => hit.eventObject);
364
+ } // If a click yields no results, pass it back to the user as a miss
365
+ // Missed events have to come first in order to establish user-land side-effect clean up
366
+
367
+
368
+ if (isClickEvent && !hits.length) {
369
+ if (delta <= 2) {
370
+ pointerMissed(event, internal.interaction);
371
+ if (onPointerMissed) onPointerMissed(event);
372
+ }
373
+ } // Take care of unhover
374
+
350
375
 
351
376
  if (isPointerMove) cancelPointer(hits);
352
- handleIntersects(hits, event, data => {
377
+ handleIntersects(hits, event, delta, data => {
378
+ var _r3f4;
379
+
353
380
  const eventObject = data.eventObject;
354
- const handlers = eventObject.__r3f.handlers; // Check presence of handlers
381
+ const handlers = (_r3f4 = eventObject.__r3f) == null ? void 0 : _r3f4.handlers; // Check presence of handlers
355
382
 
356
- if (!handlers.count) return;
383
+ if (!(handlers != null && handlers.count)) return;
357
384
 
358
385
  if (isPointerMove) {
359
386
  // Move event ...
@@ -383,33 +410,22 @@ function createEvents(store) {
383
410
  // Forward all events back to their respective handlers with the exception of click events,
384
411
  // which must use the initial target
385
412
  if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
413
+ // Missed events have to come first
414
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object))); // Now call the handler
415
+
386
416
  handler(data);
387
- pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
388
417
  }
389
418
  }
390
419
  }
391
- }); // Save initial coordinates on pointer-down
392
-
393
- if (name === 'onPointerDown') {
394
- internal.initialClick = [event.offsetX, event.offsetY];
395
- internal.initialHits = hits.map(hit => hit.eventObject);
396
- } // If a click yields no results, pass it back to the user as a miss
397
-
398
-
399
- if ((name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick') && !hits.length) {
400
- if (calculateDistance(event) <= 2) {
401
- pointerMissed(event, internal.interaction);
402
- if (onPointerMissed) onPointerMissed(event);
403
- }
404
- }
420
+ });
405
421
  };
406
422
  };
407
423
 
408
424
  function pointerMissed(event, objects) {
409
425
  objects.forEach(object => {
410
- var _r3f$handlers$onPoin, _r3f$handlers;
426
+ var _r3f5;
411
427
 
412
- return (_r3f$handlers$onPoin = (_r3f$handlers = object.__r3f.handlers).onPointerMissed) == null ? void 0 : _r3f$handlers$onPoin.call(_r3f$handlers, event);
428
+ return (_r3f5 = object.__r3f) == null ? void 0 : _r3f5.handlers.onPointerMissed == null ? void 0 : _r3f5.handlers.onPointerMissed(event);
413
429
  });
414
430
  }
415
431
 
@@ -464,6 +480,7 @@ function prepare(object, state) {
464
480
  count: 0
465
481
  },
466
482
  objects: [],
483
+ parent: null,
467
484
  ...state
468
485
  };
469
486
  }
@@ -522,7 +539,7 @@ function createRenderer(roots) {
522
539
  }
523
540
 
524
541
  function applyProps(instance, data) {
525
- var _instance$__r3f3, _root$getState, _localState$handlers, _localState$handlers2;
542
+ var _instance$__r3f3, _root$getState, _localState$handlers, _localState$handlers2, _instance$__r3f4;
526
543
 
527
544
  // Filter equals, events and reserved props
528
545
  const localState = (_instance$__r3f3 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f3 : {};
@@ -607,16 +624,16 @@ function createRenderer(roots) {
607
624
  if (index > -1) rootState.internal.interaction.splice(index, 1); // Add the instance to the interaction manager only when it has handlers
608
625
 
609
626
  if (localState.handlers.count) rootState.internal.interaction.push(instance);
610
- } // Call the update lifecycle when it is being updated, but only when it is part of the scene
627
+ } // Call the update lifecycle when it is being updated
611
628
 
612
629
 
613
- if (changes.length && instance.parent) updateInstance(instance);
630
+ if (changes.length && (_instance$__r3f4 = instance.__r3f) != null && _instance$__r3f4.parent) updateInstance(instance);
614
631
  }
615
632
 
616
633
  function invalidateInstance(instance) {
617
- var _instance$__r3f4, _instance$__r3f4$root;
634
+ var _instance$__r3f5, _instance$__r3f5$root;
618
635
 
619
- 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();
636
+ const state = (_instance$__r3f5 = instance.__r3f) == null ? void 0 : (_instance$__r3f5$root = _instance$__r3f5.root) == null ? void 0 : _instance$__r3f5$root.getState == null ? void 0 : _instance$__r3f5$root.getState();
620
637
  if (state && state.internal.frames === 0) state.invalidate();
621
638
  }
622
639
 
@@ -708,7 +725,7 @@ function createRenderer(roots) {
708
725
  } else if (is.fun(attachFn)) {
709
726
  attachFn(child, parentInstance);
710
727
  }
711
- } else if (child.isObject3D) {
728
+ } else if (child.isObject3D && parentInstance.isObject3D) {
712
729
  // add in the usual parent-child way
713
730
  parentInstance.add(child);
714
731
  addedAsChild = true;
@@ -718,10 +735,13 @@ function createRenderer(roots) {
718
735
  // This is for anything that used attach, and for non-Object3Ds that don't get attached to props;
719
736
  // that is, anything that's a child in React but not a child in the scenegraph.
720
737
  parentInstance.__r3f.objects.push(child);
738
+ }
721
739
 
722
- child.parent = parentInstance;
740
+ if (!child.__r3f) {
741
+ prepare(child, {});
723
742
  }
724
743
 
744
+ child.__r3f.parent = parentInstance;
725
745
  updateInstance(child);
726
746
  invalidateInstance(child);
727
747
  }
@@ -738,7 +758,7 @@ function createRenderer(roots) {
738
758
  } else if (child.attachObject || child.attach && !is.fun(child.attach)) {
739
759
  // attach and attachObject don't have an order anyway, so just append
740
760
  return appendChild(parentInstance, child);
741
- } else if (child.isObject3D) {
761
+ } else if (child.isObject3D && parentInstance.isObject3D) {
742
762
  child.parent = parentInstance;
743
763
  child.dispatchEvent({
744
764
  type: 'added'
@@ -751,10 +771,13 @@ function createRenderer(roots) {
751
771
 
752
772
  if (!added) {
753
773
  parentInstance.__r3f.objects.push(child);
774
+ }
754
775
 
755
- child.parent = parentInstance;
776
+ if (!child.__r3f) {
777
+ prepare(child, {});
756
778
  }
757
779
 
780
+ child.__r3f.parent = parentInstance;
758
781
  updateInstance(child);
759
782
  invalidateInstance(child);
760
783
  }
@@ -766,17 +789,14 @@ function createRenderer(roots) {
766
789
 
767
790
  function removeChild(parentInstance, child, dispose) {
768
791
  if (child) {
769
- var _child$__r3f2;
792
+ var _parentInstance$__r3f, _child$__r3f2;
770
793
 
771
- if (parentInstance.__r3f.objects) {
772
- const oldLength = parentInstance.__r3f.objects.length;
773
- parentInstance.__r3f.objects = parentInstance.__r3f.objects.filter(x => x !== child);
774
- const newLength = parentInstance.__r3f.objects.length; // was it in the list?
794
+ if (child.__r3f) {
795
+ child.__r3f.parent = null;
796
+ }
775
797
 
776
- if (newLength < oldLength) {
777
- // we had also set this, so we must clear it now
778
- child.parent = null;
779
- }
798
+ if ((_parentInstance$__r3f = parentInstance.__r3f) != null && _parentInstance$__r3f.objects) {
799
+ parentInstance.__r3f.objects = parentInstance.__r3f.objects.filter(x => x !== child);
780
800
  } // Remove attachment
781
801
 
782
802
 
@@ -849,7 +869,9 @@ function createRenderer(roots) {
849
869
  }
850
870
 
851
871
  function switchInstance(instance, type, newProps, fiber) {
852
- const parent = instance.parent;
872
+ var _instance$__r3f6;
873
+
874
+ const parent = (_instance$__r3f6 = instance.__r3f) == null ? void 0 : _instance$__r3f6.parent;
853
875
  if (!parent) return;
854
876
  const newInstance = createInstance(type, newProps, instance.__r3f.root); // https://github.com/pmndrs/react-three-fiber/issues/1348
855
877
  // When args change the instance has to be re-constructed, which then
@@ -1012,6 +1034,9 @@ function createRenderer(roots) {
1012
1034
 
1013
1035
  const isRenderer = def => def && !!def.render;
1014
1036
  const isOrthographicCamera = def => def && def.isOrthographicCamera;
1037
+ function calculateDpr(dpr) {
1038
+ return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], window.devicePixelRatio), dpr[1]) : dpr;
1039
+ }
1015
1040
  const context = /*#__PURE__*/React__namespace.createContext(null);
1016
1041
 
1017
1042
  const createStore = (applyProps, invalidate, advance, props) => {
@@ -1068,14 +1093,10 @@ const createStore = (applyProps, invalidate, advance, props) => {
1068
1093
  camera.position.z = 5;
1069
1094
  if (cameraOptions) applyProps(camera, cameraOptions); // Always look at center by default
1070
1095
 
1071
- camera.lookAt(0, 0, 0);
1096
+ if (!(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
1072
1097
  }
1073
1098
 
1074
- function setDpr(dpr) {
1075
- return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], window.devicePixelRatio), dpr[1]) : dpr;
1076
- }
1077
-
1078
- const initialDpr = setDpr(dpr);
1099
+ const initialDpr = calculateDpr(dpr);
1079
1100
  const position = new THREE__namespace.Vector3();
1080
1101
  const defaultTarget = new THREE__namespace.Vector3();
1081
1102
  const tempTarget = new THREE__namespace.Vector3();
@@ -1182,7 +1203,7 @@ const createStore = (applyProps, invalidate, advance, props) => {
1182
1203
  },
1183
1204
  setDpr: dpr => set(state => ({
1184
1205
  viewport: { ...state.viewport,
1185
- dpr: setDpr(dpr)
1206
+ dpr: calculateDpr(dpr)
1186
1207
  }
1187
1208
  })),
1188
1209
  events: {
@@ -1473,7 +1494,10 @@ const Canvas = /*#__PURE__*/React__namespace.forwardRef(function Canvas({
1473
1494
  events,
1474
1495
  ...props
1475
1496
  }, forwardedRef) {
1476
- const [containerRef, size] = useMeasure__default['default']({
1497
+ const [containerRef, {
1498
+ width,
1499
+ height
1500
+ }] = useMeasure__default['default']({
1477
1501
  scroll: true,
1478
1502
  debounce: {
1479
1503
  scroll: 50,
@@ -1490,7 +1514,7 @@ const Canvas = /*#__PURE__*/React__namespace.forwardRef(function Canvas({
1490
1514
  if (error) throw error; // Execute JSX in the reconciler as a layout-effect
1491
1515
 
1492
1516
  useIsomorphicLayoutEffect(() => {
1493
- if (size.width > 0 && size.height > 0) {
1517
+ if (width > 0 && height > 0) {
1494
1518
  render( /*#__PURE__*/React__namespace.createElement(ErrorBoundary, {
1495
1519
  set: setError
1496
1520
  }, /*#__PURE__*/React__namespace.createElement(React__namespace.Suspense, {
@@ -1498,11 +1522,14 @@ const Canvas = /*#__PURE__*/React__namespace.forwardRef(function Canvas({
1498
1522
  set: setBlock
1499
1523
  })
1500
1524
  }, children)), canvasRef.current, { ...props,
1501
- size,
1525
+ size: {
1526
+ width,
1527
+ height
1528
+ },
1502
1529
  events: events || createPointerEvents
1503
1530
  });
1504
1531
  }
1505
- }, [size, children]);
1532
+ }, [width, height, children]);
1506
1533
  useIsomorphicLayoutEffect(() => {
1507
1534
  const container = canvasRef.current;
1508
1535
  return () => unmountComponentAtNode(container);
@@ -1646,15 +1673,14 @@ function render(element, canvas, {
1646
1673
  let state = (_store = store) == null ? void 0 : _store.getState();
1647
1674
 
1648
1675
  if (fiber && state) {
1649
- const lastProps = state.internal.lastProps; // When a root was found, see if any fundamental props must be changed or exchanged
1676
+ // When a root was found, see if any fundamental props must be changed or exchanged
1650
1677
  // Check pixelratio
1678
+ if (props.dpr !== undefined && !is.equ(state.viewport.dpr, calculateDpr(props.dpr))) state.setDpr(props.dpr); // Check size
1651
1679
 
1652
- if (props.dpr !== undefined && !is.equ(lastProps.dpr, props.dpr)) state.setDpr(props.dpr); // Check size
1653
-
1654
- if (!is.equ(lastProps.size, size)) state.setSize(size.width, size.height); // For some props we want to reset the entire root
1680
+ if (!is.equ(state.size, size)) state.setSize(size.width, size.height); // For some props we want to reset the entire root
1655
1681
  // Changes to the color-space
1656
1682
 
1657
- const linearChanged = props.linear !== lastProps.linear;
1683
+ const linearChanged = props.linear !== state.internal.lastProps.linear;
1658
1684
 
1659
1685
  if (linearChanged) {
1660
1686
  unmountComponentAtNode(canvas);
@@ -58,7 +58,8 @@ const is = {
58
58
  // Wrong type or one of the two undefined, doesn't match
59
59
  if (typeof a !== typeof b || !!a !== !!b) return false; // Atomic, just compare a against b
60
60
 
61
- if (is.str(a) || is.num(a) || is.obj(a)) return a === b; // Array, shallow compare first to see if it's a match
61
+ if (is.str(a) || is.num(a)) return a === b;
62
+ if (is.obj(a) && a === b) return true; // Array, shallow compare first to see if it's a match
62
63
 
63
64
  if (is.arr(a) && a == b) return true; // Last resort, go through keys
64
65
 
@@ -132,7 +133,11 @@ function createEvents(store) {
132
133
 
133
134
 
134
135
  function filterPointerEvents(objects) {
135
- return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => obj.__r3f.handlers['onPointer' + name]));
136
+ return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
137
+ var _r3f;
138
+
139
+ return (_r3f = obj.__r3f) == null ? void 0 : _r3f.handlers['onPointer' + name];
140
+ }));
136
141
  }
137
142
 
138
143
  function intersect(filter) {
@@ -162,7 +167,9 @@ function createEvents(store) {
162
167
  let eventObject = intersect.object; // Bubble event up
163
168
 
164
169
  while (eventObject) {
165
- if (eventObject.__r3f.handlers.count) intersections.push({ ...intersect,
170
+ var _r3f2;
171
+
172
+ if ((_r3f2 = eventObject.__r3f) != null && _r3f2.handlers.count) intersections.push({ ...intersect,
166
173
  eventObject
167
174
  });
168
175
  eventObject = eventObject.parent;
@@ -189,7 +196,7 @@ function createEvents(store) {
189
196
  /** Handles intersections by forwarding them to handlers */
190
197
 
191
198
 
192
- function handleIntersects(intersections, event, callback) {
199
+ function handleIntersects(intersections, event, delta, callback) {
193
200
  const {
194
201
  raycaster,
195
202
  mouse,
@@ -199,7 +206,6 @@ function createEvents(store) {
199
206
 
200
207
  if (intersections.length) {
201
208
  const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
202
- const delta = event.type === 'click' ? calculateDistance(event) : 0;
203
209
 
204
210
  const releasePointerCapture = id => event.target.releasePointerCapture(id);
205
211
 
@@ -300,11 +306,13 @@ function createEvents(store) {
300
306
  // When no objects were hit or the the hovered object wasn't found underneath the cursor
301
307
  // we call onPointerOut and delete the object from the hovered-elements map
302
308
  if (!hits.length || !hits.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId)) {
309
+ var _r3f3;
310
+
303
311
  const eventObject = hoveredObj.eventObject;
304
- const handlers = eventObject.__r3f.handlers;
312
+ const handlers = (_r3f3 = eventObject.__r3f) == null ? void 0 : _r3f3.handlers;
305
313
  internal.hovered.delete(makeId(hoveredObj));
306
314
 
307
- if (handlers.count) {
315
+ if (handlers != null && handlers.count) {
308
316
  // Clear out intersects, they are outdated by now
309
317
  const data = { ...hoveredObj,
310
318
  intersections: hits || []
@@ -345,15 +353,34 @@ function createEvents(store) {
345
353
  prepareRay(event); // Get fresh intersects
346
354
 
347
355
  const isPointerMove = name === 'onPointerMove';
356
+ const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
348
357
  const filter = isPointerMove ? filterPointerEvents : undefined;
349
- const hits = patchIntersects(intersect(filter), event); // Take care of unhover
358
+ const hits = patchIntersects(intersect(filter), event);
359
+ const delta = isClickEvent ? calculateDistance(event) : 0; // Save initial coordinates on pointer-down
360
+
361
+ if (name === 'onPointerDown') {
362
+ internal.initialClick = [event.offsetX, event.offsetY];
363
+ internal.initialHits = hits.map(hit => hit.eventObject);
364
+ } // If a click yields no results, pass it back to the user as a miss
365
+ // Missed events have to come first in order to establish user-land side-effect clean up
366
+
367
+
368
+ if (isClickEvent && !hits.length) {
369
+ if (delta <= 2) {
370
+ pointerMissed(event, internal.interaction);
371
+ if (onPointerMissed) onPointerMissed(event);
372
+ }
373
+ } // Take care of unhover
374
+
350
375
 
351
376
  if (isPointerMove) cancelPointer(hits);
352
- handleIntersects(hits, event, data => {
377
+ handleIntersects(hits, event, delta, data => {
378
+ var _r3f4;
379
+
353
380
  const eventObject = data.eventObject;
354
- const handlers = eventObject.__r3f.handlers; // Check presence of handlers
381
+ const handlers = (_r3f4 = eventObject.__r3f) == null ? void 0 : _r3f4.handlers; // Check presence of handlers
355
382
 
356
- if (!handlers.count) return;
383
+ if (!(handlers != null && handlers.count)) return;
357
384
 
358
385
  if (isPointerMove) {
359
386
  // Move event ...
@@ -383,33 +410,22 @@ function createEvents(store) {
383
410
  // Forward all events back to their respective handlers with the exception of click events,
384
411
  // which must use the initial target
385
412
  if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
413
+ // Missed events have to come first
414
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object))); // Now call the handler
415
+
386
416
  handler(data);
387
- pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
388
417
  }
389
418
  }
390
419
  }
391
- }); // Save initial coordinates on pointer-down
392
-
393
- if (name === 'onPointerDown') {
394
- internal.initialClick = [event.offsetX, event.offsetY];
395
- internal.initialHits = hits.map(hit => hit.eventObject);
396
- } // If a click yields no results, pass it back to the user as a miss
397
-
398
-
399
- if ((name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick') && !hits.length) {
400
- if (calculateDistance(event) <= 2) {
401
- pointerMissed(event, internal.interaction);
402
- if (onPointerMissed) onPointerMissed(event);
403
- }
404
- }
420
+ });
405
421
  };
406
422
  };
407
423
 
408
424
  function pointerMissed(event, objects) {
409
425
  objects.forEach(object => {
410
- var _r3f$handlers$onPoin, _r3f$handlers;
426
+ var _r3f5;
411
427
 
412
- return (_r3f$handlers$onPoin = (_r3f$handlers = object.__r3f.handlers).onPointerMissed) == null ? void 0 : _r3f$handlers$onPoin.call(_r3f$handlers, event);
428
+ return (_r3f5 = object.__r3f) == null ? void 0 : _r3f5.handlers.onPointerMissed == null ? void 0 : _r3f5.handlers.onPointerMissed(event);
413
429
  });
414
430
  }
415
431
 
@@ -464,6 +480,7 @@ function prepare(object, state) {
464
480
  count: 0
465
481
  },
466
482
  objects: [],
483
+ parent: null,
467
484
  ...state
468
485
  };
469
486
  }
@@ -522,7 +539,7 @@ function createRenderer(roots) {
522
539
  }
523
540
 
524
541
  function applyProps(instance, data) {
525
- var _instance$__r3f3, _root$getState, _localState$handlers, _localState$handlers2;
542
+ var _instance$__r3f3, _root$getState, _localState$handlers, _localState$handlers2, _instance$__r3f4;
526
543
 
527
544
  // Filter equals, events and reserved props
528
545
  const localState = (_instance$__r3f3 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f3 : {};
@@ -607,16 +624,16 @@ function createRenderer(roots) {
607
624
  if (index > -1) rootState.internal.interaction.splice(index, 1); // Add the instance to the interaction manager only when it has handlers
608
625
 
609
626
  if (localState.handlers.count) rootState.internal.interaction.push(instance);
610
- } // Call the update lifecycle when it is being updated, but only when it is part of the scene
627
+ } // Call the update lifecycle when it is being updated
611
628
 
612
629
 
613
- if (changes.length && instance.parent) updateInstance(instance);
630
+ if (changes.length && (_instance$__r3f4 = instance.__r3f) != null && _instance$__r3f4.parent) updateInstance(instance);
614
631
  }
615
632
 
616
633
  function invalidateInstance(instance) {
617
- var _instance$__r3f4, _instance$__r3f4$root;
634
+ var _instance$__r3f5, _instance$__r3f5$root;
618
635
 
619
- 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();
636
+ const state = (_instance$__r3f5 = instance.__r3f) == null ? void 0 : (_instance$__r3f5$root = _instance$__r3f5.root) == null ? void 0 : _instance$__r3f5$root.getState == null ? void 0 : _instance$__r3f5$root.getState();
620
637
  if (state && state.internal.frames === 0) state.invalidate();
621
638
  }
622
639
 
@@ -708,7 +725,7 @@ function createRenderer(roots) {
708
725
  } else if (is.fun(attachFn)) {
709
726
  attachFn(child, parentInstance);
710
727
  }
711
- } else if (child.isObject3D) {
728
+ } else if (child.isObject3D && parentInstance.isObject3D) {
712
729
  // add in the usual parent-child way
713
730
  parentInstance.add(child);
714
731
  addedAsChild = true;
@@ -718,10 +735,13 @@ function createRenderer(roots) {
718
735
  // This is for anything that used attach, and for non-Object3Ds that don't get attached to props;
719
736
  // that is, anything that's a child in React but not a child in the scenegraph.
720
737
  parentInstance.__r3f.objects.push(child);
738
+ }
721
739
 
722
- child.parent = parentInstance;
740
+ if (!child.__r3f) {
741
+ prepare(child, {});
723
742
  }
724
743
 
744
+ child.__r3f.parent = parentInstance;
725
745
  updateInstance(child);
726
746
  invalidateInstance(child);
727
747
  }
@@ -738,7 +758,7 @@ function createRenderer(roots) {
738
758
  } else if (child.attachObject || child.attach && !is.fun(child.attach)) {
739
759
  // attach and attachObject don't have an order anyway, so just append
740
760
  return appendChild(parentInstance, child);
741
- } else if (child.isObject3D) {
761
+ } else if (child.isObject3D && parentInstance.isObject3D) {
742
762
  child.parent = parentInstance;
743
763
  child.dispatchEvent({
744
764
  type: 'added'
@@ -751,10 +771,13 @@ function createRenderer(roots) {
751
771
 
752
772
  if (!added) {
753
773
  parentInstance.__r3f.objects.push(child);
774
+ }
754
775
 
755
- child.parent = parentInstance;
776
+ if (!child.__r3f) {
777
+ prepare(child, {});
756
778
  }
757
779
 
780
+ child.__r3f.parent = parentInstance;
758
781
  updateInstance(child);
759
782
  invalidateInstance(child);
760
783
  }
@@ -766,17 +789,14 @@ function createRenderer(roots) {
766
789
 
767
790
  function removeChild(parentInstance, child, dispose) {
768
791
  if (child) {
769
- var _child$__r3f2;
792
+ var _parentInstance$__r3f, _child$__r3f2;
770
793
 
771
- if (parentInstance.__r3f.objects) {
772
- const oldLength = parentInstance.__r3f.objects.length;
773
- parentInstance.__r3f.objects = parentInstance.__r3f.objects.filter(x => x !== child);
774
- const newLength = parentInstance.__r3f.objects.length; // was it in the list?
794
+ if (child.__r3f) {
795
+ child.__r3f.parent = null;
796
+ }
775
797
 
776
- if (newLength < oldLength) {
777
- // we had also set this, so we must clear it now
778
- child.parent = null;
779
- }
798
+ if ((_parentInstance$__r3f = parentInstance.__r3f) != null && _parentInstance$__r3f.objects) {
799
+ parentInstance.__r3f.objects = parentInstance.__r3f.objects.filter(x => x !== child);
780
800
  } // Remove attachment
781
801
 
782
802
 
@@ -849,7 +869,9 @@ function createRenderer(roots) {
849
869
  }
850
870
 
851
871
  function switchInstance(instance, type, newProps, fiber) {
852
- const parent = instance.parent;
872
+ var _instance$__r3f6;
873
+
874
+ const parent = (_instance$__r3f6 = instance.__r3f) == null ? void 0 : _instance$__r3f6.parent;
853
875
  if (!parent) return;
854
876
  const newInstance = createInstance(type, newProps, instance.__r3f.root); // https://github.com/pmndrs/react-three-fiber/issues/1348
855
877
  // When args change the instance has to be re-constructed, which then
@@ -1012,6 +1034,9 @@ function createRenderer(roots) {
1012
1034
 
1013
1035
  const isRenderer = def => def && !!def.render;
1014
1036
  const isOrthographicCamera = def => def && def.isOrthographicCamera;
1037
+ function calculateDpr(dpr) {
1038
+ return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], window.devicePixelRatio), dpr[1]) : dpr;
1039
+ }
1015
1040
  const context = /*#__PURE__*/React__namespace.createContext(null);
1016
1041
 
1017
1042
  const createStore = (applyProps, invalidate, advance, props) => {
@@ -1068,14 +1093,10 @@ const createStore = (applyProps, invalidate, advance, props) => {
1068
1093
  camera.position.z = 5;
1069
1094
  if (cameraOptions) applyProps(camera, cameraOptions); // Always look at center by default
1070
1095
 
1071
- camera.lookAt(0, 0, 0);
1096
+ if (!(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
1072
1097
  }
1073
1098
 
1074
- function setDpr(dpr) {
1075
- return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], window.devicePixelRatio), dpr[1]) : dpr;
1076
- }
1077
-
1078
- const initialDpr = setDpr(dpr);
1099
+ const initialDpr = calculateDpr(dpr);
1079
1100
  const position = new THREE__namespace.Vector3();
1080
1101
  const defaultTarget = new THREE__namespace.Vector3();
1081
1102
  const tempTarget = new THREE__namespace.Vector3();
@@ -1182,7 +1203,7 @@ const createStore = (applyProps, invalidate, advance, props) => {
1182
1203
  },
1183
1204
  setDpr: dpr => set(state => ({
1184
1205
  viewport: { ...state.viewport,
1185
- dpr: setDpr(dpr)
1206
+ dpr: calculateDpr(dpr)
1186
1207
  }
1187
1208
  })),
1188
1209
  events: {
@@ -1473,7 +1494,10 @@ const Canvas = /*#__PURE__*/React__namespace.forwardRef(function Canvas({
1473
1494
  events,
1474
1495
  ...props
1475
1496
  }, forwardedRef) {
1476
- const [containerRef, size] = useMeasure__default['default']({
1497
+ const [containerRef, {
1498
+ width,
1499
+ height
1500
+ }] = useMeasure__default['default']({
1477
1501
  scroll: true,
1478
1502
  debounce: {
1479
1503
  scroll: 50,
@@ -1490,7 +1514,7 @@ const Canvas = /*#__PURE__*/React__namespace.forwardRef(function Canvas({
1490
1514
  if (error) throw error; // Execute JSX in the reconciler as a layout-effect
1491
1515
 
1492
1516
  useIsomorphicLayoutEffect(() => {
1493
- if (size.width > 0 && size.height > 0) {
1517
+ if (width > 0 && height > 0) {
1494
1518
  render( /*#__PURE__*/React__namespace.createElement(ErrorBoundary, {
1495
1519
  set: setError
1496
1520
  }, /*#__PURE__*/React__namespace.createElement(React__namespace.Suspense, {
@@ -1498,11 +1522,14 @@ const Canvas = /*#__PURE__*/React__namespace.forwardRef(function Canvas({
1498
1522
  set: setBlock
1499
1523
  })
1500
1524
  }, children)), canvasRef.current, { ...props,
1501
- size,
1525
+ size: {
1526
+ width,
1527
+ height
1528
+ },
1502
1529
  events: events || createPointerEvents
1503
1530
  });
1504
1531
  }
1505
- }, [size, children]);
1532
+ }, [width, height, children]);
1506
1533
  useIsomorphicLayoutEffect(() => {
1507
1534
  const container = canvasRef.current;
1508
1535
  return () => unmountComponentAtNode(container);
@@ -1646,15 +1673,14 @@ function render(element, canvas, {
1646
1673
  let state = (_store = store) == null ? void 0 : _store.getState();
1647
1674
 
1648
1675
  if (fiber && state) {
1649
- const lastProps = state.internal.lastProps; // When a root was found, see if any fundamental props must be changed or exchanged
1676
+ // When a root was found, see if any fundamental props must be changed or exchanged
1650
1677
  // Check pixelratio
1678
+ if (props.dpr !== undefined && !is.equ(state.viewport.dpr, calculateDpr(props.dpr))) state.setDpr(props.dpr); // Check size
1651
1679
 
1652
- if (props.dpr !== undefined && !is.equ(lastProps.dpr, props.dpr)) state.setDpr(props.dpr); // Check size
1653
-
1654
- if (!is.equ(lastProps.size, size)) state.setSize(size.width, size.height); // For some props we want to reset the entire root
1680
+ if (!is.equ(state.size, size)) state.setSize(size.width, size.height); // For some props we want to reset the entire root
1655
1681
  // Changes to the color-space
1656
1682
 
1657
- const linearChanged = props.linear !== lastProps.linear;
1683
+ const linearChanged = props.linear !== state.internal.lastProps.linear;
1658
1684
 
1659
1685
  if (linearChanged) {
1660
1686
  unmountComponentAtNode(canvas);
@@ -24,7 +24,8 @@ const is = {
24
24
  // Wrong type or one of the two undefined, doesn't match
25
25
  if (typeof a !== typeof b || !!a !== !!b) return false; // Atomic, just compare a against b
26
26
 
27
- if (is.str(a) || is.num(a) || is.obj(a)) return a === b; // Array, shallow compare first to see if it's a match
27
+ if (is.str(a) || is.num(a)) return a === b;
28
+ if (is.obj(a) && a === b) return true; // Array, shallow compare first to see if it's a match
28
29
 
29
30
  if (is.arr(a) && a == b) return true; // Last resort, go through keys
30
31
 
@@ -98,7 +99,11 @@ function createEvents(store) {
98
99
 
99
100
 
100
101
  function filterPointerEvents(objects) {
101
- return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => obj.__r3f.handlers['onPointer' + name]));
102
+ return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
103
+ var _r3f;
104
+
105
+ return (_r3f = obj.__r3f) == null ? void 0 : _r3f.handlers['onPointer' + name];
106
+ }));
102
107
  }
103
108
 
104
109
  function intersect(filter) {
@@ -128,7 +133,9 @@ function createEvents(store) {
128
133
  let eventObject = intersect.object; // Bubble event up
129
134
 
130
135
  while (eventObject) {
131
- if (eventObject.__r3f.handlers.count) intersections.push({ ...intersect,
136
+ var _r3f2;
137
+
138
+ if ((_r3f2 = eventObject.__r3f) != null && _r3f2.handlers.count) intersections.push({ ...intersect,
132
139
  eventObject
133
140
  });
134
141
  eventObject = eventObject.parent;
@@ -155,7 +162,7 @@ function createEvents(store) {
155
162
  /** Handles intersections by forwarding them to handlers */
156
163
 
157
164
 
158
- function handleIntersects(intersections, event, callback) {
165
+ function handleIntersects(intersections, event, delta, callback) {
159
166
  const {
160
167
  raycaster,
161
168
  mouse,
@@ -165,7 +172,6 @@ function createEvents(store) {
165
172
 
166
173
  if (intersections.length) {
167
174
  const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
168
- const delta = event.type === 'click' ? calculateDistance(event) : 0;
169
175
 
170
176
  const releasePointerCapture = id => event.target.releasePointerCapture(id);
171
177
 
@@ -266,11 +272,13 @@ function createEvents(store) {
266
272
  // When no objects were hit or the the hovered object wasn't found underneath the cursor
267
273
  // we call onPointerOut and delete the object from the hovered-elements map
268
274
  if (!hits.length || !hits.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId)) {
275
+ var _r3f3;
276
+
269
277
  const eventObject = hoveredObj.eventObject;
270
- const handlers = eventObject.__r3f.handlers;
278
+ const handlers = (_r3f3 = eventObject.__r3f) == null ? void 0 : _r3f3.handlers;
271
279
  internal.hovered.delete(makeId(hoveredObj));
272
280
 
273
- if (handlers.count) {
281
+ if (handlers != null && handlers.count) {
274
282
  // Clear out intersects, they are outdated by now
275
283
  const data = { ...hoveredObj,
276
284
  intersections: hits || []
@@ -311,15 +319,34 @@ function createEvents(store) {
311
319
  prepareRay(event); // Get fresh intersects
312
320
 
313
321
  const isPointerMove = name === 'onPointerMove';
322
+ const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
314
323
  const filter = isPointerMove ? filterPointerEvents : undefined;
315
- const hits = patchIntersects(intersect(filter), event); // Take care of unhover
324
+ const hits = patchIntersects(intersect(filter), event);
325
+ const delta = isClickEvent ? calculateDistance(event) : 0; // Save initial coordinates on pointer-down
326
+
327
+ if (name === 'onPointerDown') {
328
+ internal.initialClick = [event.offsetX, event.offsetY];
329
+ internal.initialHits = hits.map(hit => hit.eventObject);
330
+ } // If a click yields no results, pass it back to the user as a miss
331
+ // Missed events have to come first in order to establish user-land side-effect clean up
332
+
333
+
334
+ if (isClickEvent && !hits.length) {
335
+ if (delta <= 2) {
336
+ pointerMissed(event, internal.interaction);
337
+ if (onPointerMissed) onPointerMissed(event);
338
+ }
339
+ } // Take care of unhover
340
+
316
341
 
317
342
  if (isPointerMove) cancelPointer(hits);
318
- handleIntersects(hits, event, data => {
343
+ handleIntersects(hits, event, delta, data => {
344
+ var _r3f4;
345
+
319
346
  const eventObject = data.eventObject;
320
- const handlers = eventObject.__r3f.handlers; // Check presence of handlers
347
+ const handlers = (_r3f4 = eventObject.__r3f) == null ? void 0 : _r3f4.handlers; // Check presence of handlers
321
348
 
322
- if (!handlers.count) return;
349
+ if (!(handlers != null && handlers.count)) return;
323
350
 
324
351
  if (isPointerMove) {
325
352
  // Move event ...
@@ -349,33 +376,22 @@ function createEvents(store) {
349
376
  // Forward all events back to their respective handlers with the exception of click events,
350
377
  // which must use the initial target
351
378
  if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
379
+ // Missed events have to come first
380
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object))); // Now call the handler
381
+
352
382
  handler(data);
353
- pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
354
383
  }
355
384
  }
356
385
  }
357
- }); // Save initial coordinates on pointer-down
358
-
359
- if (name === 'onPointerDown') {
360
- internal.initialClick = [event.offsetX, event.offsetY];
361
- internal.initialHits = hits.map(hit => hit.eventObject);
362
- } // If a click yields no results, pass it back to the user as a miss
363
-
364
-
365
- if ((name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick') && !hits.length) {
366
- if (calculateDistance(event) <= 2) {
367
- pointerMissed(event, internal.interaction);
368
- if (onPointerMissed) onPointerMissed(event);
369
- }
370
- }
386
+ });
371
387
  };
372
388
  };
373
389
 
374
390
  function pointerMissed(event, objects) {
375
391
  objects.forEach(object => {
376
- var _r3f$handlers$onPoin, _r3f$handlers;
392
+ var _r3f5;
377
393
 
378
- return (_r3f$handlers$onPoin = (_r3f$handlers = object.__r3f.handlers).onPointerMissed) == null ? void 0 : _r3f$handlers$onPoin.call(_r3f$handlers, event);
394
+ return (_r3f5 = object.__r3f) == null ? void 0 : _r3f5.handlers.onPointerMissed == null ? void 0 : _r3f5.handlers.onPointerMissed(event);
379
395
  });
380
396
  }
381
397
 
@@ -430,6 +446,7 @@ function prepare(object, state) {
430
446
  count: 0
431
447
  },
432
448
  objects: [],
449
+ parent: null,
433
450
  ...state
434
451
  };
435
452
  }
@@ -488,7 +505,7 @@ function createRenderer(roots) {
488
505
  }
489
506
 
490
507
  function applyProps(instance, data) {
491
- var _instance$__r3f3, _root$getState, _localState$handlers, _localState$handlers2;
508
+ var _instance$__r3f3, _root$getState, _localState$handlers, _localState$handlers2, _instance$__r3f4;
492
509
 
493
510
  // Filter equals, events and reserved props
494
511
  const localState = (_instance$__r3f3 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f3 : {};
@@ -573,16 +590,16 @@ function createRenderer(roots) {
573
590
  if (index > -1) rootState.internal.interaction.splice(index, 1); // Add the instance to the interaction manager only when it has handlers
574
591
 
575
592
  if (localState.handlers.count) rootState.internal.interaction.push(instance);
576
- } // Call the update lifecycle when it is being updated, but only when it is part of the scene
593
+ } // Call the update lifecycle when it is being updated
577
594
 
578
595
 
579
- if (changes.length && instance.parent) updateInstance(instance);
596
+ if (changes.length && (_instance$__r3f4 = instance.__r3f) != null && _instance$__r3f4.parent) updateInstance(instance);
580
597
  }
581
598
 
582
599
  function invalidateInstance(instance) {
583
- var _instance$__r3f4, _instance$__r3f4$root;
600
+ var _instance$__r3f5, _instance$__r3f5$root;
584
601
 
585
- 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();
602
+ const state = (_instance$__r3f5 = instance.__r3f) == null ? void 0 : (_instance$__r3f5$root = _instance$__r3f5.root) == null ? void 0 : _instance$__r3f5$root.getState == null ? void 0 : _instance$__r3f5$root.getState();
586
603
  if (state && state.internal.frames === 0) state.invalidate();
587
604
  }
588
605
 
@@ -674,7 +691,7 @@ function createRenderer(roots) {
674
691
  } else if (is.fun(attachFn)) {
675
692
  attachFn(child, parentInstance);
676
693
  }
677
- } else if (child.isObject3D) {
694
+ } else if (child.isObject3D && parentInstance.isObject3D) {
678
695
  // add in the usual parent-child way
679
696
  parentInstance.add(child);
680
697
  addedAsChild = true;
@@ -684,10 +701,13 @@ function createRenderer(roots) {
684
701
  // This is for anything that used attach, and for non-Object3Ds that don't get attached to props;
685
702
  // that is, anything that's a child in React but not a child in the scenegraph.
686
703
  parentInstance.__r3f.objects.push(child);
704
+ }
687
705
 
688
- child.parent = parentInstance;
706
+ if (!child.__r3f) {
707
+ prepare(child, {});
689
708
  }
690
709
 
710
+ child.__r3f.parent = parentInstance;
691
711
  updateInstance(child);
692
712
  invalidateInstance(child);
693
713
  }
@@ -704,7 +724,7 @@ function createRenderer(roots) {
704
724
  } else if (child.attachObject || child.attach && !is.fun(child.attach)) {
705
725
  // attach and attachObject don't have an order anyway, so just append
706
726
  return appendChild(parentInstance, child);
707
- } else if (child.isObject3D) {
727
+ } else if (child.isObject3D && parentInstance.isObject3D) {
708
728
  child.parent = parentInstance;
709
729
  child.dispatchEvent({
710
730
  type: 'added'
@@ -717,10 +737,13 @@ function createRenderer(roots) {
717
737
 
718
738
  if (!added) {
719
739
  parentInstance.__r3f.objects.push(child);
740
+ }
720
741
 
721
- child.parent = parentInstance;
742
+ if (!child.__r3f) {
743
+ prepare(child, {});
722
744
  }
723
745
 
746
+ child.__r3f.parent = parentInstance;
724
747
  updateInstance(child);
725
748
  invalidateInstance(child);
726
749
  }
@@ -732,17 +755,14 @@ function createRenderer(roots) {
732
755
 
733
756
  function removeChild(parentInstance, child, dispose) {
734
757
  if (child) {
735
- var _child$__r3f2;
758
+ var _parentInstance$__r3f, _child$__r3f2;
736
759
 
737
- if (parentInstance.__r3f.objects) {
738
- const oldLength = parentInstance.__r3f.objects.length;
739
- parentInstance.__r3f.objects = parentInstance.__r3f.objects.filter(x => x !== child);
740
- const newLength = parentInstance.__r3f.objects.length; // was it in the list?
760
+ if (child.__r3f) {
761
+ child.__r3f.parent = null;
762
+ }
741
763
 
742
- if (newLength < oldLength) {
743
- // we had also set this, so we must clear it now
744
- child.parent = null;
745
- }
764
+ if ((_parentInstance$__r3f = parentInstance.__r3f) != null && _parentInstance$__r3f.objects) {
765
+ parentInstance.__r3f.objects = parentInstance.__r3f.objects.filter(x => x !== child);
746
766
  } // Remove attachment
747
767
 
748
768
 
@@ -815,7 +835,9 @@ function createRenderer(roots) {
815
835
  }
816
836
 
817
837
  function switchInstance(instance, type, newProps, fiber) {
818
- const parent = instance.parent;
838
+ var _instance$__r3f6;
839
+
840
+ const parent = (_instance$__r3f6 = instance.__r3f) == null ? void 0 : _instance$__r3f6.parent;
819
841
  if (!parent) return;
820
842
  const newInstance = createInstance(type, newProps, instance.__r3f.root); // https://github.com/pmndrs/react-three-fiber/issues/1348
821
843
  // When args change the instance has to be re-constructed, which then
@@ -978,6 +1000,9 @@ function createRenderer(roots) {
978
1000
 
979
1001
  const isRenderer = def => def && !!def.render;
980
1002
  const isOrthographicCamera = def => def && def.isOrthographicCamera;
1003
+ function calculateDpr(dpr) {
1004
+ return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], window.devicePixelRatio), dpr[1]) : dpr;
1005
+ }
981
1006
  const context = /*#__PURE__*/React.createContext(null);
982
1007
 
983
1008
  const createStore = (applyProps, invalidate, advance, props) => {
@@ -1034,14 +1059,10 @@ const createStore = (applyProps, invalidate, advance, props) => {
1034
1059
  camera.position.z = 5;
1035
1060
  if (cameraOptions) applyProps(camera, cameraOptions); // Always look at center by default
1036
1061
 
1037
- camera.lookAt(0, 0, 0);
1062
+ if (!(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
1038
1063
  }
1039
1064
 
1040
- function setDpr(dpr) {
1041
- return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], window.devicePixelRatio), dpr[1]) : dpr;
1042
- }
1043
-
1044
- const initialDpr = setDpr(dpr);
1065
+ const initialDpr = calculateDpr(dpr);
1045
1066
  const position = new THREE.Vector3();
1046
1067
  const defaultTarget = new THREE.Vector3();
1047
1068
  const tempTarget = new THREE.Vector3();
@@ -1148,7 +1169,7 @@ const createStore = (applyProps, invalidate, advance, props) => {
1148
1169
  },
1149
1170
  setDpr: dpr => set(state => ({
1150
1171
  viewport: { ...state.viewport,
1151
- dpr: setDpr(dpr)
1172
+ dpr: calculateDpr(dpr)
1152
1173
  }
1153
1174
  })),
1154
1175
  events: {
@@ -1439,7 +1460,10 @@ const Canvas = /*#__PURE__*/React.forwardRef(function Canvas({
1439
1460
  events,
1440
1461
  ...props
1441
1462
  }, forwardedRef) {
1442
- const [containerRef, size] = useMeasure({
1463
+ const [containerRef, {
1464
+ width,
1465
+ height
1466
+ }] = useMeasure({
1443
1467
  scroll: true,
1444
1468
  debounce: {
1445
1469
  scroll: 50,
@@ -1456,7 +1480,7 @@ const Canvas = /*#__PURE__*/React.forwardRef(function Canvas({
1456
1480
  if (error) throw error; // Execute JSX in the reconciler as a layout-effect
1457
1481
 
1458
1482
  useIsomorphicLayoutEffect(() => {
1459
- if (size.width > 0 && size.height > 0) {
1483
+ if (width > 0 && height > 0) {
1460
1484
  render( /*#__PURE__*/React.createElement(ErrorBoundary, {
1461
1485
  set: setError
1462
1486
  }, /*#__PURE__*/React.createElement(React.Suspense, {
@@ -1464,11 +1488,14 @@ const Canvas = /*#__PURE__*/React.forwardRef(function Canvas({
1464
1488
  set: setBlock
1465
1489
  })
1466
1490
  }, children)), canvasRef.current, { ...props,
1467
- size,
1491
+ size: {
1492
+ width,
1493
+ height
1494
+ },
1468
1495
  events: events || createPointerEvents
1469
1496
  });
1470
1497
  }
1471
- }, [size, children]);
1498
+ }, [width, height, children]);
1472
1499
  useIsomorphicLayoutEffect(() => {
1473
1500
  const container = canvasRef.current;
1474
1501
  return () => unmountComponentAtNode(container);
@@ -1612,15 +1639,14 @@ function render(element, canvas, {
1612
1639
  let state = (_store = store) == null ? void 0 : _store.getState();
1613
1640
 
1614
1641
  if (fiber && state) {
1615
- const lastProps = state.internal.lastProps; // When a root was found, see if any fundamental props must be changed or exchanged
1642
+ // When a root was found, see if any fundamental props must be changed or exchanged
1616
1643
  // Check pixelratio
1644
+ if (props.dpr !== undefined && !is.equ(state.viewport.dpr, calculateDpr(props.dpr))) state.setDpr(props.dpr); // Check size
1617
1645
 
1618
- if (props.dpr !== undefined && !is.equ(lastProps.dpr, props.dpr)) state.setDpr(props.dpr); // Check size
1619
-
1620
- if (!is.equ(lastProps.size, size)) state.setSize(size.width, size.height); // For some props we want to reset the entire root
1646
+ if (!is.equ(state.size, size)) state.setSize(size.width, size.height); // For some props we want to reset the entire root
1621
1647
  // Changes to the color-space
1622
1648
 
1623
- const linearChanged = props.linear !== lastProps.linear;
1649
+ const linearChanged = props.linear !== state.internal.lastProps.linear;
1624
1650
 
1625
1651
  if (linearChanged) {
1626
1652
  unmountComponentAtNode(canvas);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/fiber",
3
- "version": "7.0.9",
3
+ "version": "7.0.13",
4
4
  "description": "A React renderer for Threejs",
5
5
  "keywords": [
6
6
  "react",