@react-three/fiber 7.0.20 → 7.0.24

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,29 @@
1
1
  # @react-three/fiber
2
2
 
3
+ ## 7.0.24
4
+
5
+ ### Patch Changes
6
+
7
+ - 7f46ddf: cleanup captured pointers when released (#1914)
8
+
9
+ ## 7.0.23
10
+
11
+ ### Patch Changes
12
+
13
+ - 30d38b1: remove logs
14
+
15
+ ## 7.0.22
16
+
17
+ ### Patch Changes
18
+
19
+ - 259e1fa: add camera:manual
20
+
21
+ ## 7.0.21
22
+
23
+ ### Patch Changes
24
+
25
+ - 65e4147: up usemeasure, add last event to internals"
26
+
3
27
  ## 7.0.20
4
28
 
5
29
  ### Patch Changes
@@ -4,7 +4,7 @@ import type { RootState } from './store';
4
4
  export interface Intersection extends THREE.Intersection {
5
5
  eventObject: THREE.Object3D;
6
6
  }
7
- export interface IntesectionEvent<TSourceEvent> extends Intersection {
7
+ export interface IntersectionEvent<TSourceEvent> extends Intersection {
8
8
  intersections: Intersection[];
9
9
  stopped: boolean;
10
10
  unprojectedPoint: THREE.Vector3;
@@ -18,8 +18,8 @@ export interface IntesectionEvent<TSourceEvent> extends Intersection {
18
18
  spaceY: number;
19
19
  }
20
20
  export declare type Camera = THREE.OrthographicCamera | THREE.PerspectiveCamera;
21
- export declare type ThreeEvent<TEvent> = TEvent & IntesectionEvent<TEvent>;
22
- export declare type DomEvent = ThreeEvent<PointerEvent | MouseEvent | WheelEvent>;
21
+ export declare type ThreeEvent<TEvent> = IntersectionEvent<TEvent>;
22
+ export declare type DomEvent = PointerEvent | MouseEvent | WheelEvent;
23
23
  export declare type Events = {
24
24
  onClick: EventListener;
25
25
  onContextMenu: EventListener;
@@ -43,7 +43,7 @@ export declare type EventHandlers = {
43
43
  onPointerEnter?: (event: ThreeEvent<PointerEvent>) => void;
44
44
  onPointerLeave?: (event: ThreeEvent<PointerEvent>) => void;
45
45
  onPointerMove?: (event: ThreeEvent<PointerEvent>) => void;
46
- onPointerMissed?: (event: ThreeEvent<PointerEvent>) => void;
46
+ onPointerMissed?: (event: MouseEvent) => void;
47
47
  onPointerCancel?: (event: ThreeEvent<PointerEvent>) => void;
48
48
  onWheel?: (event: ThreeEvent<WheelEvent>) => void;
49
49
  };
@@ -47,8 +47,9 @@ export declare type InternalState = {
47
47
  priority: number;
48
48
  frames: number;
49
49
  lastProps: StoreProps;
50
+ lastEvent: React.MutableRefObject<DomEvent | null>;
50
51
  interaction: THREE.Object3D[];
51
- hovered: Map<string, DomEvent>;
52
+ hovered: Map<string, ThreeEvent<DomEvent>>;
52
53
  subscribers: Subscription[];
53
54
  capturedMap: Map<number, Map<THREE.Object3D, PointerCaptureTarget>>;
54
55
  initialClick: [x: number, y: number];
@@ -58,7 +59,9 @@ export declare type InternalState = {
58
59
  export declare type RootState = {
59
60
  gl: THREE.WebGLRenderer;
60
61
  scene: THREE.Scene;
61
- camera: Camera;
62
+ camera: Camera & {
63
+ manual?: boolean;
64
+ };
62
65
  controls: THREE.EventDispatcher | null;
63
66
  raycaster: Raycaster;
64
67
  mouse: THREE.Vector2;
@@ -78,7 +81,8 @@ export declare type RootState = {
78
81
  advance: (timestamp: number, runGlobalEffects?: boolean) => void;
79
82
  setSize: (width: number, height: number) => void;
80
83
  setDpr: (dpr: Dpr) => void;
81
- onPointerMissed?: (event: ThreeEvent<PointerEvent>) => void;
84
+ setFrameloop: (frameloop?: 'always' | 'demand' | 'never') => void;
85
+ onPointerMissed?: (event: MouseEvent) => void;
82
86
  events: EventManager<any>;
83
87
  internal: InternalState;
84
88
  };
@@ -100,8 +104,10 @@ export declare type StoreProps = {
100
104
  dpr?: Dpr;
101
105
  clock?: THREE.Clock;
102
106
  raycaster?: Partial<Raycaster>;
103
- camera?: Camera | Partial<ReactThreeFiber.Object3DNode<THREE.Camera, typeof THREE.Camera> & ReactThreeFiber.Object3DNode<THREE.PerspectiveCamera, typeof THREE.PerspectiveCamera> & ReactThreeFiber.Object3DNode<THREE.OrthographicCamera, typeof THREE.OrthographicCamera>>;
104
- onPointerMissed?: (event: ThreeEvent<PointerEvent>) => void;
107
+ camera?: (Camera | Partial<ReactThreeFiber.Object3DNode<THREE.Camera, typeof THREE.Camera> & ReactThreeFiber.Object3DNode<THREE.PerspectiveCamera, typeof THREE.PerspectiveCamera> & ReactThreeFiber.Object3DNode<THREE.OrthographicCamera, typeof THREE.OrthographicCamera>>) & {
108
+ manual?: boolean;
109
+ };
110
+ onPointerMissed?: (event: MouseEvent) => void;
105
111
  };
106
112
  export declare type ApplyProps = (instance: Instance, newProps: InstanceProps) => void;
107
113
  export declare function calculateDpr(dpr: Dpr): number;
@@ -23,7 +23,7 @@ export interface NodeProps<T, P> {
23
23
  attachFns?: [AttachCallback, AttachCallback];
24
24
  args?: Args<P>;
25
25
  children?: React.ReactNode;
26
- ref?: React.Ref<React.ReactNode>;
26
+ ref?: React.RefCallback<T> | React.RefObject<React.ReactNode> | null;
27
27
  key?: React.Key;
28
28
  onUpdate?: (self: T) => void;
29
29
  }
@@ -174,6 +174,7 @@ export declare type Matrix3Props = Node<THREE.Matrix3, typeof THREE.Matrix3>;
174
174
  export declare type Matrix4Props = Node<THREE.Matrix4, typeof THREE.Matrix4>;
175
175
  export declare type QuaternionProps = Node<THREE.Quaternion, typeof THREE.Quaternion>;
176
176
  export declare type BufferAttributeProps = Node<THREE.BufferAttribute, typeof THREE.BufferAttribute>;
177
+ export declare type Float32BufferAttributeProps = Node<THREE.Float32BufferAttribute, typeof THREE.Float32BufferAttribute>;
177
178
  export declare type InstancedBufferAttributeProps = Node<THREE.InstancedBufferAttribute, typeof THREE.InstancedBufferAttribute>;
178
179
  export declare type ColorProps = Node<THREE.Color, ColorArray>;
179
180
  export declare type FogProps = Node<THREE.Fog, typeof THREE.Fog>;
@@ -307,6 +308,7 @@ declare global {
307
308
  matrix4: Matrix4Props;
308
309
  quaternion: QuaternionProps;
309
310
  bufferAttribute: BufferAttributeProps;
311
+ float32BufferAttribute: Float32BufferAttributeProps;
310
312
  instancedBufferAttribute: InstancedBufferAttributeProps;
311
313
  color: ColorProps;
312
314
  fog: FogProps;
@@ -74,7 +74,8 @@ const is = {
74
74
  function makeId(event) {
75
75
  return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
76
76
  }
77
- /** Release pointer captures.
77
+ /**
78
+ * Release pointer captures.
78
79
  * This is called by releasePointerCapture in the API, and when an object is removed.
79
80
  */
80
81
 
@@ -361,13 +362,16 @@ function createEvents(store) {
361
362
 
362
363
  case 'onLostPointerCapture':
363
364
  return event => {
364
- if ('pointerId' in event) {
365
+ const {
366
+ internal
367
+ } = store.getState();
368
+
369
+ if ('pointerId' in event && !internal.capturedMap.has(event.pointerId)) {
365
370
  // If the object event interface had onLostPointerCapture, we'd call it here on every
366
371
  // object that's getting removed.
367
- store.getState().internal.capturedMap.delete(event.pointerId);
372
+ internal.capturedMap.delete(event.pointerId);
373
+ cancelPointer([]);
368
374
  }
369
-
370
- cancelPointer([]);
371
375
  };
372
376
  } // Any other pointer goes here ...
373
377
 
@@ -377,7 +381,8 @@ function createEvents(store) {
377
381
  onPointerMissed,
378
382
  internal
379
383
  } = store.getState();
380
- prepareRay(event); // Get fresh intersects
384
+ prepareRay(event);
385
+ internal.lastEvent.current = event; // Get fresh intersects
381
386
 
382
387
  const isPointerMove = name === 'onPointerMove';
383
388
  const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
@@ -435,12 +440,17 @@ function createEvents(store) {
435
440
  if (handler) {
436
441
  // Forward all events back to their respective handlers with the exception of click events,
437
442
  // which must use the initial target
438
- if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
443
+ if (!isClickEvent || internal.initialHits.includes(eventObject)) {
439
444
  // Missed events have to come first
440
445
  pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object))); // Now call the handler
441
446
 
442
447
  handler(data);
443
448
  }
449
+ } else {
450
+ // Trigger onPointerMissed on all elements that have pointer over/out handlers, but not click and weren't hit
451
+ if (isClickEvent && internal.initialHits.includes(eventObject)) {
452
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
453
+ }
444
454
  }
445
455
  }
446
456
  });
@@ -643,7 +653,7 @@ function createRenderer(roots) {
643
653
  invalidateInstance(instance);
644
654
  });
645
655
 
646
- if (rootState.internal && instance.raycast && prevHandlers !== localState.eventCount) {
656
+ if (localState.parent && rootState.internal && instance.raycast && prevHandlers !== localState.eventCount) {
647
657
  // Pre-emptively remove the instance from the interaction manager
648
658
  const index = rootState.internal.interaction.indexOf(instance);
649
659
  if (index > -1) rootState.internal.interaction.splice(index, 1); // Add the instance to the interaction manager only when it has handlers
@@ -696,7 +706,9 @@ function createRenderer(roots) {
696
706
  });
697
707
  } else {
698
708
  const target = catalogue[name] || THREE__namespace[name];
699
- if (!target) throw `${name} is not part of the THREE namespace! Did you forget to extend? See: https://github.com/pmndrs/react-three-fiber/blob/master/markdown/api.md#using-3rd-party-objects-declaratively`; // Instanciate new object, link it to the root
709
+ if (!target) throw `${name} is not part of the THREE namespace! Did you forget to extend? See: https://github.com/pmndrs/react-three-fiber/blob/master/markdown/api.md#using-3rd-party-objects-declaratively`; // Throw if an object or literal was passed for args
710
+
711
+ if (!Array.isArray(args)) throw 'The args prop must be an array!'; // Instanciate new object, link it to the root
700
712
  // Append memoized props with args so it's not forgotten
701
713
 
702
714
  instance = prepare(new target(...args), {
@@ -829,7 +841,7 @@ function createRenderer(roots) {
829
841
  parentInstance[child.attachArray] = parentInstance[child.attachArray].filter(x => x !== child);
830
842
  } else if (child.attachObject) {
831
843
  delete parentInstance[child.attachObject[0]][child.attachObject[1]];
832
- } else if (child.attach && !is.fun(child.attach)) {
844
+ } else if (child.attach && !is.fun(child.attach) && parentInstance[child.attach] === child) {
833
845
  parentInstance[child.attach] = null;
834
846
  } else if (is.arr(child.attachFns)) {
835
847
  const [, detachFn] = child.attachFns;
@@ -970,12 +982,24 @@ function createRenderer(roots) {
970
982
  args: argsOld = [],
971
983
  children: cO,
972
984
  ...restOld
973
- } = oldProps; // If it has new props or arguments, then it needs to be re-instanciated
985
+ } = oldProps; // Throw if an object or literal was passed for args
986
+
987
+ 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-instanciated
974
988
 
975
989
  if (argsNew.some((value, index) => value !== argsOld[index])) return [true]; // Create a diff-set, flag if there are any changes
976
990
 
977
991
  const diff = diffProps(instance, restNew, restOld, true);
978
- if (diff.changes.length) return [false, diff]; // Otherwise do not touch the instance
992
+ if (diff.changes.length) return [false, diff]; // If instance was never attached, attach it
993
+
994
+ if (instance.attach && typeof instance.attach !== 'function') {
995
+ const localState = instance.__r3f;
996
+ const parent = localState.parent;
997
+
998
+ if (parent && parent[instance.attach] !== instance) {
999
+ appendChild(parent, instance);
1000
+ }
1001
+ } // Otherwise do not touch the instance
1002
+
979
1003
 
980
1004
  return null;
981
1005
  }
@@ -1020,11 +1044,24 @@ function createRenderer(roots) {
1020
1044
 
1021
1045
  createTextInstance() {},
1022
1046
 
1023
- finalizeInitialChildren() {
1024
- return false;
1047
+ finalizeInitialChildren(instance) {
1048
+ var _instance$__r3f7;
1049
+
1050
+ // https://github.com/facebook/react/issues/20271
1051
+ // Returning true will trigger commitMount
1052
+ const localState = (_instance$__r3f7 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f7 : {};
1053
+ return !!localState.handlers;
1025
1054
  },
1026
1055
 
1027
- commitMount() {// noop
1056
+ commitMount(instance)
1057
+ /*, type, props*/
1058
+ {
1059
+ var _instance$__r3f8;
1060
+
1061
+ // https://github.com/facebook/react/issues/20271
1062
+ // This will make sure events are only added once to the central container
1063
+ const localState = (_instance$__r3f8 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f8 : {};
1064
+ if (instance.raycast && localState.handlers && localState.eventCount) instance.__r3f.root.getState().internal.interaction.push(instance);
1028
1065
  },
1029
1066
 
1030
1067
  shouldDeprioritizeSubtree() {
@@ -1231,6 +1268,9 @@ const createStore = (applyProps, invalidate, advance, props) => {
1231
1268
  dpr: calculateDpr(dpr)
1232
1269
  }
1233
1270
  })),
1271
+ setFrameloop: (frameloop = 'always') => set(() => ({
1272
+ frameloop
1273
+ })),
1234
1274
  events: {
1235
1275
  connected: false
1236
1276
  },
@@ -1239,6 +1279,7 @@ const createStore = (applyProps, invalidate, advance, props) => {
1239
1279
  priority: 0,
1240
1280
  frames: 0,
1241
1281
  lastProps: props,
1282
+ lastEvent: /*#__PURE__*/React__namespace.createRef(),
1242
1283
  interaction: [],
1243
1284
  hovered: new Map(),
1244
1285
  subscribers: [],
@@ -1294,7 +1335,7 @@ const createStore = (applyProps, invalidate, advance, props) => {
1294
1335
  if (size !== oldSize || viewport.dpr !== oldDpr) {
1295
1336
  // https://github.com/pmndrs/react-three-fiber/issues/92
1296
1337
  // Do not mess with the camera if it belongs to the user
1297
- if (!(internal.lastProps.camera instanceof THREE__namespace.Camera)) {
1338
+ if (!camera.manual && !(internal.lastProps.camera instanceof THREE__namespace.Camera)) {
1298
1339
  if (isOrthographicCamera(camera)) {
1299
1340
  camera.left = size.width / -2;
1300
1341
  camera.right = size.width / 2;
@@ -1719,7 +1760,9 @@ function render(element, canvas, {
1719
1760
  // Check pixelratio
1720
1761
  if (props.dpr !== undefined && !is.equ(state.viewport.dpr, calculateDpr(props.dpr))) state.setDpr(props.dpr); // Check size
1721
1762
 
1722
- if (state.size.width !== size.width || state.size.height !== size.height) state.setSize(size.width, size.height); // For some props we want to reset the entire root
1763
+ if (state.size.width !== size.width || state.size.height !== size.height) state.setSize(size.width, size.height); // Check frameloop
1764
+
1765
+ if (state.frameloop !== props.frameloop) state.setFrameloop(props.frameloop); // For some props we want to reset the entire root
1723
1766
  // Changes to the color-space
1724
1767
 
1725
1768
  const linearChanged = props.linear !== state.internal.lastProps.linear;
@@ -74,7 +74,8 @@ const is = {
74
74
  function makeId(event) {
75
75
  return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
76
76
  }
77
- /** Release pointer captures.
77
+ /**
78
+ * Release pointer captures.
78
79
  * This is called by releasePointerCapture in the API, and when an object is removed.
79
80
  */
80
81
 
@@ -361,13 +362,16 @@ function createEvents(store) {
361
362
 
362
363
  case 'onLostPointerCapture':
363
364
  return event => {
364
- if ('pointerId' in event) {
365
+ const {
366
+ internal
367
+ } = store.getState();
368
+
369
+ if ('pointerId' in event && !internal.capturedMap.has(event.pointerId)) {
365
370
  // If the object event interface had onLostPointerCapture, we'd call it here on every
366
371
  // object that's getting removed.
367
- store.getState().internal.capturedMap.delete(event.pointerId);
372
+ internal.capturedMap.delete(event.pointerId);
373
+ cancelPointer([]);
368
374
  }
369
-
370
- cancelPointer([]);
371
375
  };
372
376
  } // Any other pointer goes here ...
373
377
 
@@ -377,7 +381,8 @@ function createEvents(store) {
377
381
  onPointerMissed,
378
382
  internal
379
383
  } = store.getState();
380
- prepareRay(event); // Get fresh intersects
384
+ prepareRay(event);
385
+ internal.lastEvent.current = event; // Get fresh intersects
381
386
 
382
387
  const isPointerMove = name === 'onPointerMove';
383
388
  const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
@@ -435,12 +440,17 @@ function createEvents(store) {
435
440
  if (handler) {
436
441
  // Forward all events back to their respective handlers with the exception of click events,
437
442
  // which must use the initial target
438
- if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
443
+ if (!isClickEvent || internal.initialHits.includes(eventObject)) {
439
444
  // Missed events have to come first
440
445
  pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object))); // Now call the handler
441
446
 
442
447
  handler(data);
443
448
  }
449
+ } else {
450
+ // Trigger onPointerMissed on all elements that have pointer over/out handlers, but not click and weren't hit
451
+ if (isClickEvent && internal.initialHits.includes(eventObject)) {
452
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
453
+ }
444
454
  }
445
455
  }
446
456
  });
@@ -643,7 +653,7 @@ function createRenderer(roots) {
643
653
  invalidateInstance(instance);
644
654
  });
645
655
 
646
- if (rootState.internal && instance.raycast && prevHandlers !== localState.eventCount) {
656
+ if (localState.parent && rootState.internal && instance.raycast && prevHandlers !== localState.eventCount) {
647
657
  // Pre-emptively remove the instance from the interaction manager
648
658
  const index = rootState.internal.interaction.indexOf(instance);
649
659
  if (index > -1) rootState.internal.interaction.splice(index, 1); // Add the instance to the interaction manager only when it has handlers
@@ -696,7 +706,9 @@ function createRenderer(roots) {
696
706
  });
697
707
  } else {
698
708
  const target = catalogue[name] || THREE__namespace[name];
699
- if (!target) throw `${name} is not part of the THREE namespace! Did you forget to extend? See: https://github.com/pmndrs/react-three-fiber/blob/master/markdown/api.md#using-3rd-party-objects-declaratively`; // Instanciate new object, link it to the root
709
+ if (!target) throw `${name} is not part of the THREE namespace! Did you forget to extend? See: https://github.com/pmndrs/react-three-fiber/blob/master/markdown/api.md#using-3rd-party-objects-declaratively`; // Throw if an object or literal was passed for args
710
+
711
+ if (!Array.isArray(args)) throw 'The args prop must be an array!'; // Instanciate new object, link it to the root
700
712
  // Append memoized props with args so it's not forgotten
701
713
 
702
714
  instance = prepare(new target(...args), {
@@ -829,7 +841,7 @@ function createRenderer(roots) {
829
841
  parentInstance[child.attachArray] = parentInstance[child.attachArray].filter(x => x !== child);
830
842
  } else if (child.attachObject) {
831
843
  delete parentInstance[child.attachObject[0]][child.attachObject[1]];
832
- } else if (child.attach && !is.fun(child.attach)) {
844
+ } else if (child.attach && !is.fun(child.attach) && parentInstance[child.attach] === child) {
833
845
  parentInstance[child.attach] = null;
834
846
  } else if (is.arr(child.attachFns)) {
835
847
  const [, detachFn] = child.attachFns;
@@ -970,12 +982,24 @@ function createRenderer(roots) {
970
982
  args: argsOld = [],
971
983
  children: cO,
972
984
  ...restOld
973
- } = oldProps; // If it has new props or arguments, then it needs to be re-instanciated
985
+ } = oldProps; // Throw if an object or literal was passed for args
986
+
987
+ 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-instanciated
974
988
 
975
989
  if (argsNew.some((value, index) => value !== argsOld[index])) return [true]; // Create a diff-set, flag if there are any changes
976
990
 
977
991
  const diff = diffProps(instance, restNew, restOld, true);
978
- if (diff.changes.length) return [false, diff]; // Otherwise do not touch the instance
992
+ if (diff.changes.length) return [false, diff]; // If instance was never attached, attach it
993
+
994
+ if (instance.attach && typeof instance.attach !== 'function') {
995
+ const localState = instance.__r3f;
996
+ const parent = localState.parent;
997
+
998
+ if (parent && parent[instance.attach] !== instance) {
999
+ appendChild(parent, instance);
1000
+ }
1001
+ } // Otherwise do not touch the instance
1002
+
979
1003
 
980
1004
  return null;
981
1005
  }
@@ -1020,11 +1044,24 @@ function createRenderer(roots) {
1020
1044
 
1021
1045
  createTextInstance() {},
1022
1046
 
1023
- finalizeInitialChildren() {
1024
- return false;
1047
+ finalizeInitialChildren(instance) {
1048
+ var _instance$__r3f7;
1049
+
1050
+ // https://github.com/facebook/react/issues/20271
1051
+ // Returning true will trigger commitMount
1052
+ const localState = (_instance$__r3f7 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f7 : {};
1053
+ return !!localState.handlers;
1025
1054
  },
1026
1055
 
1027
- commitMount() {// noop
1056
+ commitMount(instance)
1057
+ /*, type, props*/
1058
+ {
1059
+ var _instance$__r3f8;
1060
+
1061
+ // https://github.com/facebook/react/issues/20271
1062
+ // This will make sure events are only added once to the central container
1063
+ const localState = (_instance$__r3f8 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f8 : {};
1064
+ if (instance.raycast && localState.handlers && localState.eventCount) instance.__r3f.root.getState().internal.interaction.push(instance);
1028
1065
  },
1029
1066
 
1030
1067
  shouldDeprioritizeSubtree() {
@@ -1231,6 +1268,9 @@ const createStore = (applyProps, invalidate, advance, props) => {
1231
1268
  dpr: calculateDpr(dpr)
1232
1269
  }
1233
1270
  })),
1271
+ setFrameloop: (frameloop = 'always') => set(() => ({
1272
+ frameloop
1273
+ })),
1234
1274
  events: {
1235
1275
  connected: false
1236
1276
  },
@@ -1239,6 +1279,7 @@ const createStore = (applyProps, invalidate, advance, props) => {
1239
1279
  priority: 0,
1240
1280
  frames: 0,
1241
1281
  lastProps: props,
1282
+ lastEvent: /*#__PURE__*/React__namespace.createRef(),
1242
1283
  interaction: [],
1243
1284
  hovered: new Map(),
1244
1285
  subscribers: [],
@@ -1294,7 +1335,7 @@ const createStore = (applyProps, invalidate, advance, props) => {
1294
1335
  if (size !== oldSize || viewport.dpr !== oldDpr) {
1295
1336
  // https://github.com/pmndrs/react-three-fiber/issues/92
1296
1337
  // Do not mess with the camera if it belongs to the user
1297
- if (!(internal.lastProps.camera instanceof THREE__namespace.Camera)) {
1338
+ if (!camera.manual && !(internal.lastProps.camera instanceof THREE__namespace.Camera)) {
1298
1339
  if (isOrthographicCamera(camera)) {
1299
1340
  camera.left = size.width / -2;
1300
1341
  camera.right = size.width / 2;
@@ -1719,7 +1760,9 @@ function render(element, canvas, {
1719
1760
  // Check pixelratio
1720
1761
  if (props.dpr !== undefined && !is.equ(state.viewport.dpr, calculateDpr(props.dpr))) state.setDpr(props.dpr); // Check size
1721
1762
 
1722
- if (state.size.width !== size.width || state.size.height !== size.height) state.setSize(size.width, size.height); // For some props we want to reset the entire root
1763
+ if (state.size.width !== size.width || state.size.height !== size.height) state.setSize(size.width, size.height); // Check frameloop
1764
+
1765
+ if (state.frameloop !== props.frameloop) state.setFrameloop(props.frameloop); // For some props we want to reset the entire root
1723
1766
  // Changes to the color-space
1724
1767
 
1725
1768
  const linearChanged = props.linear !== state.internal.lastProps.linear;
@@ -41,7 +41,8 @@ const is = {
41
41
  function makeId(event) {
42
42
  return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
43
43
  }
44
- /** Release pointer captures.
44
+ /**
45
+ * Release pointer captures.
45
46
  * This is called by releasePointerCapture in the API, and when an object is removed.
46
47
  */
47
48
 
@@ -328,13 +329,16 @@ function createEvents(store) {
328
329
 
329
330
  case 'onLostPointerCapture':
330
331
  return event => {
331
- if ('pointerId' in event) {
332
+ const {
333
+ internal
334
+ } = store.getState();
335
+
336
+ if ('pointerId' in event && !internal.capturedMap.has(event.pointerId)) {
332
337
  // If the object event interface had onLostPointerCapture, we'd call it here on every
333
338
  // object that's getting removed.
334
- store.getState().internal.capturedMap.delete(event.pointerId);
339
+ internal.capturedMap.delete(event.pointerId);
340
+ cancelPointer([]);
335
341
  }
336
-
337
- cancelPointer([]);
338
342
  };
339
343
  } // Any other pointer goes here ...
340
344
 
@@ -344,7 +348,8 @@ function createEvents(store) {
344
348
  onPointerMissed,
345
349
  internal
346
350
  } = store.getState();
347
- prepareRay(event); // Get fresh intersects
351
+ prepareRay(event);
352
+ internal.lastEvent.current = event; // Get fresh intersects
348
353
 
349
354
  const isPointerMove = name === 'onPointerMove';
350
355
  const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
@@ -402,12 +407,17 @@ function createEvents(store) {
402
407
  if (handler) {
403
408
  // Forward all events back to their respective handlers with the exception of click events,
404
409
  // which must use the initial target
405
- if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
410
+ if (!isClickEvent || internal.initialHits.includes(eventObject)) {
406
411
  // Missed events have to come first
407
412
  pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object))); // Now call the handler
408
413
 
409
414
  handler(data);
410
415
  }
416
+ } else {
417
+ // Trigger onPointerMissed on all elements that have pointer over/out handlers, but not click and weren't hit
418
+ if (isClickEvent && internal.initialHits.includes(eventObject)) {
419
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
420
+ }
411
421
  }
412
422
  }
413
423
  });
@@ -610,7 +620,7 @@ function createRenderer(roots) {
610
620
  invalidateInstance(instance);
611
621
  });
612
622
 
613
- if (rootState.internal && instance.raycast && prevHandlers !== localState.eventCount) {
623
+ if (localState.parent && rootState.internal && instance.raycast && prevHandlers !== localState.eventCount) {
614
624
  // Pre-emptively remove the instance from the interaction manager
615
625
  const index = rootState.internal.interaction.indexOf(instance);
616
626
  if (index > -1) rootState.internal.interaction.splice(index, 1); // Add the instance to the interaction manager only when it has handlers
@@ -663,7 +673,9 @@ function createRenderer(roots) {
663
673
  });
664
674
  } else {
665
675
  const target = catalogue[name] || THREE[name];
666
- if (!target) throw `${name} is not part of the THREE namespace! Did you forget to extend? See: https://github.com/pmndrs/react-three-fiber/blob/master/markdown/api.md#using-3rd-party-objects-declaratively`; // Instanciate new object, link it to the root
676
+ if (!target) throw `${name} is not part of the THREE namespace! Did you forget to extend? See: https://github.com/pmndrs/react-three-fiber/blob/master/markdown/api.md#using-3rd-party-objects-declaratively`; // Throw if an object or literal was passed for args
677
+
678
+ if (!Array.isArray(args)) throw 'The args prop must be an array!'; // Instanciate new object, link it to the root
667
679
  // Append memoized props with args so it's not forgotten
668
680
 
669
681
  instance = prepare(new target(...args), {
@@ -796,7 +808,7 @@ function createRenderer(roots) {
796
808
  parentInstance[child.attachArray] = parentInstance[child.attachArray].filter(x => x !== child);
797
809
  } else if (child.attachObject) {
798
810
  delete parentInstance[child.attachObject[0]][child.attachObject[1]];
799
- } else if (child.attach && !is.fun(child.attach)) {
811
+ } else if (child.attach && !is.fun(child.attach) && parentInstance[child.attach] === child) {
800
812
  parentInstance[child.attach] = null;
801
813
  } else if (is.arr(child.attachFns)) {
802
814
  const [, detachFn] = child.attachFns;
@@ -937,12 +949,24 @@ function createRenderer(roots) {
937
949
  args: argsOld = [],
938
950
  children: cO,
939
951
  ...restOld
940
- } = oldProps; // If it has new props or arguments, then it needs to be re-instanciated
952
+ } = oldProps; // Throw if an object or literal was passed for args
953
+
954
+ 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-instanciated
941
955
 
942
956
  if (argsNew.some((value, index) => value !== argsOld[index])) return [true]; // Create a diff-set, flag if there are any changes
943
957
 
944
958
  const diff = diffProps(instance, restNew, restOld, true);
945
- if (diff.changes.length) return [false, diff]; // Otherwise do not touch the instance
959
+ if (diff.changes.length) return [false, diff]; // If instance was never attached, attach it
960
+
961
+ if (instance.attach && typeof instance.attach !== 'function') {
962
+ const localState = instance.__r3f;
963
+ const parent = localState.parent;
964
+
965
+ if (parent && parent[instance.attach] !== instance) {
966
+ appendChild(parent, instance);
967
+ }
968
+ } // Otherwise do not touch the instance
969
+
946
970
 
947
971
  return null;
948
972
  }
@@ -987,11 +1011,24 @@ function createRenderer(roots) {
987
1011
 
988
1012
  createTextInstance() {},
989
1013
 
990
- finalizeInitialChildren() {
991
- return false;
1014
+ finalizeInitialChildren(instance) {
1015
+ var _instance$__r3f7;
1016
+
1017
+ // https://github.com/facebook/react/issues/20271
1018
+ // Returning true will trigger commitMount
1019
+ const localState = (_instance$__r3f7 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f7 : {};
1020
+ return !!localState.handlers;
992
1021
  },
993
1022
 
994
- commitMount() {// noop
1023
+ commitMount(instance)
1024
+ /*, type, props*/
1025
+ {
1026
+ var _instance$__r3f8;
1027
+
1028
+ // https://github.com/facebook/react/issues/20271
1029
+ // This will make sure events are only added once to the central container
1030
+ const localState = (_instance$__r3f8 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f8 : {};
1031
+ if (instance.raycast && localState.handlers && localState.eventCount) instance.__r3f.root.getState().internal.interaction.push(instance);
995
1032
  },
996
1033
 
997
1034
  shouldDeprioritizeSubtree() {
@@ -1198,6 +1235,9 @@ const createStore = (applyProps, invalidate, advance, props) => {
1198
1235
  dpr: calculateDpr(dpr)
1199
1236
  }
1200
1237
  })),
1238
+ setFrameloop: (frameloop = 'always') => set(() => ({
1239
+ frameloop
1240
+ })),
1201
1241
  events: {
1202
1242
  connected: false
1203
1243
  },
@@ -1206,6 +1246,7 @@ const createStore = (applyProps, invalidate, advance, props) => {
1206
1246
  priority: 0,
1207
1247
  frames: 0,
1208
1248
  lastProps: props,
1249
+ lastEvent: /*#__PURE__*/React.createRef(),
1209
1250
  interaction: [],
1210
1251
  hovered: new Map(),
1211
1252
  subscribers: [],
@@ -1261,7 +1302,7 @@ const createStore = (applyProps, invalidate, advance, props) => {
1261
1302
  if (size !== oldSize || viewport.dpr !== oldDpr) {
1262
1303
  // https://github.com/pmndrs/react-three-fiber/issues/92
1263
1304
  // Do not mess with the camera if it belongs to the user
1264
- if (!(internal.lastProps.camera instanceof THREE.Camera)) {
1305
+ if (!camera.manual && !(internal.lastProps.camera instanceof THREE.Camera)) {
1265
1306
  if (isOrthographicCamera(camera)) {
1266
1307
  camera.left = size.width / -2;
1267
1308
  camera.right = size.width / 2;
@@ -1686,7 +1727,9 @@ function render(element, canvas, {
1686
1727
  // Check pixelratio
1687
1728
  if (props.dpr !== undefined && !is.equ(state.viewport.dpr, calculateDpr(props.dpr))) state.setDpr(props.dpr); // Check size
1688
1729
 
1689
- if (state.size.width !== size.width || state.size.height !== size.height) state.setSize(size.width, size.height); // For some props we want to reset the entire root
1730
+ if (state.size.width !== size.width || state.size.height !== size.height) state.setSize(size.width, size.height); // Check frameloop
1731
+
1732
+ if (state.frameloop !== props.frameloop) state.setFrameloop(props.frameloop); // For some props we want to reset the entire root
1690
1733
  // Changes to the color-space
1691
1734
 
1692
1735
  const linearChanged = props.linear !== state.internal.lastProps.linear;