@react-three/fiber 7.0.17 → 7.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @react-three/fiber
2
2
 
3
+ ## 7.0.18
4
+
5
+ ### Patch Changes
6
+
7
+ - 6780f58: fix unmount pointer capture
8
+
3
9
  ## 7.0.17
4
10
 
5
11
  ### Patch Changes
@@ -53,6 +53,10 @@ export interface EventManager<TTarget> {
53
53
  connect?: (target: TTarget) => void;
54
54
  disconnect?: () => void;
55
55
  }
56
+ export interface PointerCaptureTarget {
57
+ intersection: Intersection;
58
+ target: Element;
59
+ }
56
60
  export declare function removeInteractivity(store: UseStore<RootState>, object: THREE.Object3D): void;
57
61
  export declare function createEvents(store: UseStore<RootState>): {
58
62
  handlePointer: (name: string) => (event: DomEvent) => void;
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import * as ReactThreeFiber from '../three-types';
4
4
  import { GetState, SetState, UseStore } from 'zustand';
5
5
  import { Instance, InstanceProps } from './renderer';
6
- import { DomEvent, EventManager, ThreeEvent } from './events';
6
+ import { DomEvent, EventManager, PointerCaptureTarget, ThreeEvent } from './events';
7
7
  export interface Intersection extends THREE.Intersection {
8
8
  eventObject: THREE.Object3D;
9
9
  }
@@ -50,7 +50,7 @@ export declare type InternalState = {
50
50
  interaction: THREE.Object3D[];
51
51
  hovered: Map<string, DomEvent>;
52
52
  subscribers: Subscription[];
53
- capturedMap: Map<number, Map<THREE.Object3D, Intersection>>;
53
+ capturedMap: Map<number, Map<THREE.Object3D, PointerCaptureTarget>>;
54
54
  initialClick: [x: number, y: number];
55
55
  initialHits: THREE.Object3D[];
56
56
  subscribe: (callback: React.MutableRefObject<RenderCallback>, priority?: number) => () => void;
@@ -76,6 +76,23 @@ const is = {
76
76
  function makeId(event) {
77
77
  return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
78
78
  }
79
+ /** Release pointer captures.
80
+ * This is called by releasePointerCapture in the API, and when an object is removed.
81
+ */
82
+
83
+
84
+ function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
85
+ const captureData = captures.get(obj);
86
+
87
+ if (captureData) {
88
+ captures.delete(obj); // If this was the last capturing object for this pointer
89
+
90
+ if (captures.size === 0) {
91
+ capturedMap.delete(pointerId);
92
+ captureData.target.releasePointerCapture(pointerId);
93
+ }
94
+ }
95
+ }
79
96
 
80
97
  function removeInteractivity(store, object) {
81
98
  const {
@@ -89,6 +106,9 @@ function removeInteractivity(store, object) {
89
106
  internal.hovered.delete(key);
90
107
  }
91
108
  });
109
+ internal.capturedMap.forEach((captures, pointerId) => {
110
+ releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
111
+ });
92
112
  }
93
113
  function createEvents(store) {
94
114
  const temp = new THREE__namespace.Vector3();
@@ -187,7 +207,9 @@ function createEvents(store) {
187
207
  // intersect.
188
208
 
189
209
  if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
190
- intersections.push(...internal.capturedMap.get(event.pointerId).values());
210
+ for (let captureData of internal.capturedMap.get(event.pointerId).values()) {
211
+ intersections.push(captureData.intersection);
212
+ }
191
213
  }
192
214
 
193
215
  return intersections;
@@ -205,9 +227,6 @@ function createEvents(store) {
205
227
 
206
228
  if (intersections.length) {
207
229
  const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
208
-
209
- const releasePointerCapture = id => event.target.releasePointerCapture(id);
210
-
211
230
  const localState = {
212
231
  stopped: false
213
232
  };
@@ -220,23 +239,36 @@ function createEvents(store) {
220
239
  };
221
240
 
222
241
  const setPointerCapture = id => {
242
+ const captureData = {
243
+ intersection: hit,
244
+ target: event.target
245
+ };
246
+
223
247
  if (internal.capturedMap.has(id)) {
224
248
  // if the pointerId was previously captured, we add the hit to the
225
249
  // event capturedMap.
226
- internal.capturedMap.get(id).set(hit.eventObject, hit);
250
+ internal.capturedMap.get(id).set(hit.eventObject, captureData);
227
251
  } else {
228
252
  // if the pointerId was not previously captured, we create a map
229
253
  // containing the hitObject, and the hit. hitObject is used for
230
254
  // faster access.
231
- internal.capturedMap.set(id, new Map([[hit.eventObject, hit]]));
255
+ internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
232
256
  } // Call the original event now
233
257
  event.target.setPointerCapture(id);
258
+ };
259
+
260
+ const releasePointerCapture = id => {
261
+ const captures = internal.capturedMap.get(id);
262
+
263
+ if (captures) {
264
+ releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
265
+ }
234
266
  }; // Add native event props
235
267
 
236
268
 
237
- let extractEventProps = {};
269
+ 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.
238
270
 
239
- for (let prop in Object.getPrototypeOf(event)) {
271
+ for (let prop in event) {
240
272
  let property = event[prop]; // Only copy over atomics, leave functions alone as these should be
241
273
  // called as event.nativeEvent.fn()
242
274
 
@@ -332,9 +364,8 @@ function createEvents(store) {
332
364
  case 'onLostPointerCapture':
333
365
  return event => {
334
366
  if ('pointerId' in event) {
335
- // this will be a problem if one target releases the pointerId
336
- // and another one is still keeping it, as the line below
337
- // indifferently deletes all capturing references.
367
+ // If the object event interface had onLostPointerCapture, we'd call it here on every
368
+ // object that's getting removed.
338
369
  store.getState().internal.capturedMap.delete(event.pointerId);
339
370
  }
340
371
 
@@ -810,7 +841,7 @@ function createRenderer(roots) {
810
841
  } else if (is.fun(detachFn)) {
811
842
  detachFn(child, parentInstance);
812
843
  }
813
- } else if (child.isObject3D) {
844
+ } else if (child.isObject3D && parentInstance.isObject3D) {
814
845
  var _child$__r3f;
815
846
 
816
847
  parentInstance.remove(child); // Remove interactivity
@@ -1056,11 +1087,11 @@ const createStore = (applyProps, invalidate, advance, props) => {
1056
1087
  if (shadows) {
1057
1088
  gl.shadowMap.enabled = true;
1058
1089
  if (typeof shadows === 'object') Object.assign(gl.shadowMap, shadows);else gl.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
1059
- } // Set color management
1090
+ } // Set color preferences
1060
1091
 
1061
1092
 
1062
- if (!linear) gl.outputEncoding = THREE__namespace.sRGBEncoding;
1063
- if (!flat) gl.toneMapping = THREE__namespace.ACESFilmicToneMapping; // clock.elapsedTime is updated using advance(timestamp)
1093
+ if (linear) gl.outputEncoding = THREE__namespace.LinearEncoding;
1094
+ if (flat) gl.toneMapping = THREE__namespace.NoToneMapping; // clock.elapsedTime is updated using advance(timestamp)
1064
1095
 
1065
1096
  if (frameloop === 'never') {
1066
1097
  clock.stop();
@@ -1644,7 +1675,11 @@ const createRendererInstance = (gl, canvas) => {
1644
1675
  antialias: true,
1645
1676
  alpha: true,
1646
1677
  ...gl
1647
- });
1678
+ }); // Set color management
1679
+
1680
+ renderer.outputEncoding = THREE__namespace.sRGBEncoding;
1681
+ renderer.toneMapping = THREE__namespace.ACESFilmicToneMapping; // Set gl props
1682
+
1648
1683
  if (gl) applyProps(renderer, gl);
1649
1684
  return renderer;
1650
1685
  };
@@ -76,6 +76,23 @@ const is = {
76
76
  function makeId(event) {
77
77
  return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
78
78
  }
79
+ /** Release pointer captures.
80
+ * This is called by releasePointerCapture in the API, and when an object is removed.
81
+ */
82
+
83
+
84
+ function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
85
+ const captureData = captures.get(obj);
86
+
87
+ if (captureData) {
88
+ captures.delete(obj); // If this was the last capturing object for this pointer
89
+
90
+ if (captures.size === 0) {
91
+ capturedMap.delete(pointerId);
92
+ captureData.target.releasePointerCapture(pointerId);
93
+ }
94
+ }
95
+ }
79
96
 
80
97
  function removeInteractivity(store, object) {
81
98
  const {
@@ -89,6 +106,9 @@ function removeInteractivity(store, object) {
89
106
  internal.hovered.delete(key);
90
107
  }
91
108
  });
109
+ internal.capturedMap.forEach((captures, pointerId) => {
110
+ releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
111
+ });
92
112
  }
93
113
  function createEvents(store) {
94
114
  const temp = new THREE__namespace.Vector3();
@@ -187,7 +207,9 @@ function createEvents(store) {
187
207
  // intersect.
188
208
 
189
209
  if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
190
- intersections.push(...internal.capturedMap.get(event.pointerId).values());
210
+ for (let captureData of internal.capturedMap.get(event.pointerId).values()) {
211
+ intersections.push(captureData.intersection);
212
+ }
191
213
  }
192
214
 
193
215
  return intersections;
@@ -205,9 +227,6 @@ function createEvents(store) {
205
227
 
206
228
  if (intersections.length) {
207
229
  const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
208
-
209
- const releasePointerCapture = id => event.target.releasePointerCapture(id);
210
-
211
230
  const localState = {
212
231
  stopped: false
213
232
  };
@@ -220,23 +239,36 @@ function createEvents(store) {
220
239
  };
221
240
 
222
241
  const setPointerCapture = id => {
242
+ const captureData = {
243
+ intersection: hit,
244
+ target: event.target
245
+ };
246
+
223
247
  if (internal.capturedMap.has(id)) {
224
248
  // if the pointerId was previously captured, we add the hit to the
225
249
  // event capturedMap.
226
- internal.capturedMap.get(id).set(hit.eventObject, hit);
250
+ internal.capturedMap.get(id).set(hit.eventObject, captureData);
227
251
  } else {
228
252
  // if the pointerId was not previously captured, we create a map
229
253
  // containing the hitObject, and the hit. hitObject is used for
230
254
  // faster access.
231
- internal.capturedMap.set(id, new Map([[hit.eventObject, hit]]));
255
+ internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
232
256
  } // Call the original event now
233
257
  event.target.setPointerCapture(id);
258
+ };
259
+
260
+ const releasePointerCapture = id => {
261
+ const captures = internal.capturedMap.get(id);
262
+
263
+ if (captures) {
264
+ releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
265
+ }
234
266
  }; // Add native event props
235
267
 
236
268
 
237
- let extractEventProps = {};
269
+ 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.
238
270
 
239
- for (let prop in Object.getPrototypeOf(event)) {
271
+ for (let prop in event) {
240
272
  let property = event[prop]; // Only copy over atomics, leave functions alone as these should be
241
273
  // called as event.nativeEvent.fn()
242
274
 
@@ -332,9 +364,8 @@ function createEvents(store) {
332
364
  case 'onLostPointerCapture':
333
365
  return event => {
334
366
  if ('pointerId' in event) {
335
- // this will be a problem if one target releases the pointerId
336
- // and another one is still keeping it, as the line below
337
- // indifferently deletes all capturing references.
367
+ // If the object event interface had onLostPointerCapture, we'd call it here on every
368
+ // object that's getting removed.
338
369
  store.getState().internal.capturedMap.delete(event.pointerId);
339
370
  }
340
371
 
@@ -810,7 +841,7 @@ function createRenderer(roots) {
810
841
  } else if (is.fun(detachFn)) {
811
842
  detachFn(child, parentInstance);
812
843
  }
813
- } else if (child.isObject3D) {
844
+ } else if (child.isObject3D && parentInstance.isObject3D) {
814
845
  var _child$__r3f;
815
846
 
816
847
  parentInstance.remove(child); // Remove interactivity
@@ -1056,11 +1087,11 @@ const createStore = (applyProps, invalidate, advance, props) => {
1056
1087
  if (shadows) {
1057
1088
  gl.shadowMap.enabled = true;
1058
1089
  if (typeof shadows === 'object') Object.assign(gl.shadowMap, shadows);else gl.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
1059
- } // Set color management
1090
+ } // Set color preferences
1060
1091
 
1061
1092
 
1062
- if (!linear) gl.outputEncoding = THREE__namespace.sRGBEncoding;
1063
- if (!flat) gl.toneMapping = THREE__namespace.ACESFilmicToneMapping; // clock.elapsedTime is updated using advance(timestamp)
1093
+ if (linear) gl.outputEncoding = THREE__namespace.LinearEncoding;
1094
+ if (flat) gl.toneMapping = THREE__namespace.NoToneMapping; // clock.elapsedTime is updated using advance(timestamp)
1064
1095
 
1065
1096
  if (frameloop === 'never') {
1066
1097
  clock.stop();
@@ -1644,7 +1675,11 @@ const createRendererInstance = (gl, canvas) => {
1644
1675
  antialias: true,
1645
1676
  alpha: true,
1646
1677
  ...gl
1647
- });
1678
+ }); // Set color management
1679
+
1680
+ renderer.outputEncoding = THREE__namespace.sRGBEncoding;
1681
+ renderer.toneMapping = THREE__namespace.ACESFilmicToneMapping; // Set gl props
1682
+
1648
1683
  if (gl) applyProps(renderer, gl);
1649
1684
  return renderer;
1650
1685
  };
@@ -42,6 +42,23 @@ const is = {
42
42
  function makeId(event) {
43
43
  return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
44
44
  }
45
+ /** Release pointer captures.
46
+ * This is called by releasePointerCapture in the API, and when an object is removed.
47
+ */
48
+
49
+
50
+ function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
51
+ const captureData = captures.get(obj);
52
+
53
+ if (captureData) {
54
+ captures.delete(obj); // If this was the last capturing object for this pointer
55
+
56
+ if (captures.size === 0) {
57
+ capturedMap.delete(pointerId);
58
+ captureData.target.releasePointerCapture(pointerId);
59
+ }
60
+ }
61
+ }
45
62
 
46
63
  function removeInteractivity(store, object) {
47
64
  const {
@@ -55,6 +72,9 @@ function removeInteractivity(store, object) {
55
72
  internal.hovered.delete(key);
56
73
  }
57
74
  });
75
+ internal.capturedMap.forEach((captures, pointerId) => {
76
+ releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
77
+ });
58
78
  }
59
79
  function createEvents(store) {
60
80
  const temp = new THREE.Vector3();
@@ -153,7 +173,9 @@ function createEvents(store) {
153
173
  // intersect.
154
174
 
155
175
  if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
156
- intersections.push(...internal.capturedMap.get(event.pointerId).values());
176
+ for (let captureData of internal.capturedMap.get(event.pointerId).values()) {
177
+ intersections.push(captureData.intersection);
178
+ }
157
179
  }
158
180
 
159
181
  return intersections;
@@ -171,9 +193,6 @@ function createEvents(store) {
171
193
 
172
194
  if (intersections.length) {
173
195
  const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
174
-
175
- const releasePointerCapture = id => event.target.releasePointerCapture(id);
176
-
177
196
  const localState = {
178
197
  stopped: false
179
198
  };
@@ -186,23 +205,36 @@ function createEvents(store) {
186
205
  };
187
206
 
188
207
  const setPointerCapture = id => {
208
+ const captureData = {
209
+ intersection: hit,
210
+ target: event.target
211
+ };
212
+
189
213
  if (internal.capturedMap.has(id)) {
190
214
  // if the pointerId was previously captured, we add the hit to the
191
215
  // event capturedMap.
192
- internal.capturedMap.get(id).set(hit.eventObject, hit);
216
+ internal.capturedMap.get(id).set(hit.eventObject, captureData);
193
217
  } else {
194
218
  // if the pointerId was not previously captured, we create a map
195
219
  // containing the hitObject, and the hit. hitObject is used for
196
220
  // faster access.
197
- internal.capturedMap.set(id, new Map([[hit.eventObject, hit]]));
221
+ internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
198
222
  } // Call the original event now
199
223
  event.target.setPointerCapture(id);
224
+ };
225
+
226
+ const releasePointerCapture = id => {
227
+ const captures = internal.capturedMap.get(id);
228
+
229
+ if (captures) {
230
+ releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
231
+ }
200
232
  }; // Add native event props
201
233
 
202
234
 
203
- let extractEventProps = {};
235
+ 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.
204
236
 
205
- for (let prop in Object.getPrototypeOf(event)) {
237
+ for (let prop in event) {
206
238
  let property = event[prop]; // Only copy over atomics, leave functions alone as these should be
207
239
  // called as event.nativeEvent.fn()
208
240
 
@@ -298,9 +330,8 @@ function createEvents(store) {
298
330
  case 'onLostPointerCapture':
299
331
  return event => {
300
332
  if ('pointerId' in event) {
301
- // this will be a problem if one target releases the pointerId
302
- // and another one is still keeping it, as the line below
303
- // indifferently deletes all capturing references.
333
+ // If the object event interface had onLostPointerCapture, we'd call it here on every
334
+ // object that's getting removed.
304
335
  store.getState().internal.capturedMap.delete(event.pointerId);
305
336
  }
306
337
 
@@ -776,7 +807,7 @@ function createRenderer(roots) {
776
807
  } else if (is.fun(detachFn)) {
777
808
  detachFn(child, parentInstance);
778
809
  }
779
- } else if (child.isObject3D) {
810
+ } else if (child.isObject3D && parentInstance.isObject3D) {
780
811
  var _child$__r3f;
781
812
 
782
813
  parentInstance.remove(child); // Remove interactivity
@@ -1022,11 +1053,11 @@ const createStore = (applyProps, invalidate, advance, props) => {
1022
1053
  if (shadows) {
1023
1054
  gl.shadowMap.enabled = true;
1024
1055
  if (typeof shadows === 'object') Object.assign(gl.shadowMap, shadows);else gl.shadowMap.type = THREE.PCFSoftShadowMap;
1025
- } // Set color management
1056
+ } // Set color preferences
1026
1057
 
1027
1058
 
1028
- if (!linear) gl.outputEncoding = THREE.sRGBEncoding;
1029
- if (!flat) gl.toneMapping = THREE.ACESFilmicToneMapping; // clock.elapsedTime is updated using advance(timestamp)
1059
+ if (linear) gl.outputEncoding = THREE.LinearEncoding;
1060
+ if (flat) gl.toneMapping = THREE.NoToneMapping; // clock.elapsedTime is updated using advance(timestamp)
1030
1061
 
1031
1062
  if (frameloop === 'never') {
1032
1063
  clock.stop();
@@ -1610,7 +1641,11 @@ const createRendererInstance = (gl, canvas) => {
1610
1641
  antialias: true,
1611
1642
  alpha: true,
1612
1643
  ...gl
1613
- });
1644
+ }); // Set color management
1645
+
1646
+ renderer.outputEncoding = THREE.sRGBEncoding;
1647
+ renderer.toneMapping = THREE.ACESFilmicToneMapping; // Set gl props
1648
+
1614
1649
  if (gl) applyProps(renderer, gl);
1615
1650
  return renderer;
1616
1651
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-three/fiber",
3
- "version": "7.0.17",
3
+ "version": "7.0.18",
4
4
  "description": "A React renderer for Threejs",
5
5
  "keywords": [
6
6
  "react",