@react-three/fiber 8.0.0-beta.5 → 8.0.0-beta.8

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.
@@ -44,6 +44,15 @@ const isDiffSet = def => def && !!def.memoized && !!def.changes;
44
44
  function calculateDpr(dpr) {
45
45
  return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], window.devicePixelRatio), dpr[1]) : dpr;
46
46
  }
47
+ /**
48
+ * Returns instance root state
49
+ */
50
+
51
+ const getRootState = obj => {
52
+ var _r3f;
53
+
54
+ return (_r3f = obj.__r3f) == null ? void 0 : _r3f.root.getState();
55
+ };
47
56
  /**
48
57
  * Picks or omits keys from an object
49
58
  * `omit` will filter out keys, and otherwise cherry-pick them.
@@ -53,11 +62,7 @@ function filterKeys(obj, omit, ...keys) {
53
62
  const keysToSelect = new Set(keys);
54
63
  return Object.entries(obj).reduce((acc, [key, value]) => {
55
64
  const shouldInclude = !omit;
56
-
57
- if (keysToSelect.has(key) === shouldInclude) {
58
- acc[key] = value;
59
- }
60
-
65
+ if (keysToSelect.has(key) === shouldInclude) acc[key] = value;
61
66
  return acc;
62
67
  }, {});
63
68
  }
@@ -411,6 +416,7 @@ function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
411
416
 
412
417
  function removeInteractivity(store, object) {
413
418
  const {
419
+ events,
414
420
  internal
415
421
  } = store.getState(); // Removes every trace of an object from the data store
416
422
 
@@ -418,6 +424,7 @@ function removeInteractivity(store, object) {
418
424
  internal.initialHits = internal.initialHits.filter(o => o !== object);
419
425
  internal.hovered.forEach((value, key) => {
420
426
  if (value.eventObject === object || value.object === object) {
427
+ // Clear out intersects, they are outdated by now
421
428
  internal.hovered.delete(key);
422
429
  }
423
430
  });
@@ -427,31 +434,8 @@ function removeInteractivity(store, object) {
427
434
  }
428
435
  function createEvents(store) {
429
436
  const temp = new THREE__namespace.Vector3();
430
- /** Sets up defaultRaycaster */
431
-
432
- function prepareRay(event) {
433
- var _customOffsets$offset, _customOffsets$offset2, _customOffsets$width, _customOffsets$height;
434
-
435
- const state = store.getState();
436
- const {
437
- raycaster,
438
- mouse,
439
- camera,
440
- size
441
- } = state; // https://github.com/pmndrs/react-three-fiber/pull/782
442
- // Events trigger outside of canvas when moved
443
-
444
- const customOffsets = raycaster.computeOffsets == null ? void 0 : raycaster.computeOffsets(event, state);
445
- const offsetX = (_customOffsets$offset = customOffsets == null ? void 0 : customOffsets.offsetX) != null ? _customOffsets$offset : event.offsetX;
446
- const offsetY = (_customOffsets$offset2 = customOffsets == null ? void 0 : customOffsets.offsetY) != null ? _customOffsets$offset2 : event.offsetY;
447
- const width = (_customOffsets$width = customOffsets == null ? void 0 : customOffsets.width) != null ? _customOffsets$width : size.width;
448
- const height = (_customOffsets$height = customOffsets == null ? void 0 : customOffsets.height) != null ? _customOffsets$height : size.height;
449
- mouse.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
450
- raycaster.setFromCamera(mouse, camera);
451
- }
452
437
  /** Calculates delta */
453
438
 
454
-
455
439
  function calculateDistance(event) {
456
440
  const {
457
441
  internal
@@ -471,55 +455,70 @@ function createEvents(store) {
471
455
  }));
472
456
  }
473
457
 
474
- function intersect(filter) {
458
+ function intersect(event, filter) {
475
459
  const state = store.getState();
476
- const {
477
- raycaster,
478
- internal
479
- } = state; // Skip event handling when noEvents is set
480
-
481
- if (!raycaster.enabled) return [];
482
- const seen = new Set();
460
+ const duplicates = new Set();
483
461
  const intersections = []; // Allow callers to eliminate event objects
484
462
 
485
- const eventsObjects = filter ? filter(internal.interaction) : internal.interaction; // Intersect known handler objects and filter against duplicates
463
+ const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction; // Reset all raycaster cameras to undefined
464
+
465
+ eventsObjects.forEach(obj => {
466
+ const state = getRootState(obj);
467
+
468
+ if (state) {
469
+ state.raycaster.camera = undefined;
470
+ }
471
+ }); // Collect events
472
+
473
+ let hits = eventsObjects // Intersect objects
474
+ .flatMap(obj => {
475
+ const state = getRootState(obj); // Skip event handling when noEvents is set, or when the raycasters camera is null
476
+
477
+ if (!state || !state.events.enabled || state.raycaster.camera === null) return []; // When the camera is undefined we have to call the event layers update function
478
+
479
+ if (state.raycaster.camera === undefined) {
480
+ var _state$previousRoot;
481
+
482
+ state.events.compute == null ? void 0 : state.events.compute(event, state, (_state$previousRoot = state.previousRoot) == null ? void 0 : _state$previousRoot.getState()); // If the camera is still undefined we have to skip this layer entirely
483
+
484
+ if (state.raycaster.camera === undefined) state.raycaster.camera = null;
485
+ } // Intersect object by object
486
486
 
487
- let intersects = raycaster.intersectObjects(eventsObjects, true).filter(item => {
487
+
488
+ return state.raycaster.camera ? state.raycaster.intersectObject(obj, true) : [];
489
+ }) // Sort by event priority and distance
490
+ .sort((a, b) => {
491
+ const aState = getRootState(a.object);
492
+ const bState = getRootState(b.object);
493
+ if (!aState || !bState) return 0;
494
+ return bState.events.priority - aState.events.priority || a.distance - b.distance;
495
+ }) // Filter out duplicates
496
+ .filter(item => {
488
497
  const id = makeId(item);
489
- if (seen.has(id)) return false;
490
- seen.add(id);
498
+ if (duplicates.has(id)) return false;
499
+ duplicates.add(id);
491
500
  return true;
492
501
  }); // https://github.com/mrdoob/three.js/issues/16031
493
- // Allow custom userland intersect sort order
502
+ // Allow custom userland intersect sort order, this likely only makes sense on the root filter
494
503
 
495
- if (raycaster.filter) intersects = raycaster.filter(intersects, state);
504
+ if (state.events.filter) hits = state.events.filter(hits, state); // Bubble up the events, find the event source (eventObject)
496
505
 
497
- for (const intersect of intersects) {
498
- let eventObject = intersect.object; // Bubble event up
506
+ for (const hit of hits) {
507
+ let eventObject = hit.object; // Bubble event up
499
508
 
500
509
  while (eventObject) {
501
510
  var _r3f2;
502
511
 
503
- if ((_r3f2 = eventObject.__r3f) != null && _r3f2.eventCount) intersections.push({ ...intersect,
512
+ if ((_r3f2 = eventObject.__r3f) != null && _r3f2.eventCount) intersections.push({ ...hit,
504
513
  eventObject
505
514
  });
506
515
  eventObject = eventObject.parent;
507
516
  }
508
- }
509
-
510
- return intersections;
511
- }
512
- /** Creates filtered intersects and returns an array of positive hits */
517
+ } // If the interaction is captured, make all capturing targets part of the intersect.
513
518
 
514
519
 
515
- function patchIntersects(intersections, event) {
516
- const {
517
- internal
518
- } = store.getState(); // If the interaction is captured, make all capturing targets part of the
519
- // intersect.
520
-
521
- if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
522
- for (let captureData of internal.capturedMap.get(event.pointerId).values()) {
520
+ if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) {
521
+ for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
523
522
  intersections.push(captureData.intersection);
524
523
  }
525
524
  }
@@ -532,13 +531,13 @@ function createEvents(store) {
532
531
  function handleIntersects(intersections, event, delta, callback) {
533
532
  const {
534
533
  raycaster,
535
- mouse,
534
+ pointer,
536
535
  camera,
537
536
  internal
538
537
  } = store.getState(); // If anything has been found, forward it to the event listeners
539
538
 
540
539
  if (intersections.length) {
541
- const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
540
+ const unprojectedPoint = temp.set(pointer.x, pointer.y, 0).unproject(camera);
542
541
  const localState = {
543
542
  stopped: false
544
543
  };
@@ -589,8 +588,7 @@ function createEvents(store) {
589
588
 
590
589
  let raycastEvent = { ...hit,
591
590
  ...extractEventProps,
592
- spaceX: mouse.x,
593
- spaceY: mouse.y,
591
+ pointer,
594
592
  intersections,
595
593
  stopped: localState.stopped,
596
594
  delta,
@@ -627,8 +625,6 @@ function createEvents(store) {
627
625
  setPointerCapture,
628
626
  releasePointerCapture
629
627
  },
630
- sourceEvent: event,
631
- // deprecated
632
628
  nativeEvent: event
633
629
  }; // Call subscribers
634
630
 
@@ -693,14 +689,15 @@ function createEvents(store) {
693
689
  const {
694
690
  onPointerMissed,
695
691
  internal
696
- } = store.getState();
697
- prepareRay(event);
692
+ } = store.getState(); //prepareRay(event)
693
+
698
694
  internal.lastEvent.current = event; // Get fresh intersects
699
695
 
700
696
  const isPointerMove = name === 'onPointerMove';
701
697
  const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
702
- const filter = isPointerMove ? filterPointerEvents : undefined;
703
- const hits = patchIntersects(intersect(filter), event);
698
+ const filter = isPointerMove ? filterPointerEvents : undefined; //const hits = patchIntersects(intersect(filter), event)
699
+
700
+ const hits = intersect(event, filter);
704
701
  const delta = isClickEvent ? calculateDistance(event) : 0; // Save initial coordinates on pointer-down
705
702
 
706
703
  if (name === 'onPointerDown') {
@@ -1179,17 +1176,20 @@ const createStore = (invalidate, advance) => {
1179
1176
  }
1180
1177
  }));
1181
1178
 
1179
+ const pointer = new THREE__namespace.Vector2();
1182
1180
  return {
1181
+ set,
1182
+ get,
1183
1183
  // Mock objects that have to be configured
1184
1184
  gl: null,
1185
1185
  camera: null,
1186
1186
  raycaster: null,
1187
1187
  events: {
1188
+ priority: 1,
1189
+ enabled: true,
1188
1190
  connected: false
1189
1191
  },
1190
1192
  xr: null,
1191
- set,
1192
- get,
1193
1193
  invalidate: () => invalidate(get()),
1194
1194
  advance: (timestamp, runGlobalEffects) => advance(timestamp, runGlobalEffects, get()),
1195
1195
  linear: false,
@@ -1197,7 +1197,8 @@ const createStore = (invalidate, advance) => {
1197
1197
  scene: prepare(new THREE__namespace.Scene()),
1198
1198
  controls: null,
1199
1199
  clock: new THREE__namespace.Clock(),
1200
- mouse: new THREE__namespace.Vector2(),
1200
+ pointer,
1201
+ mouse: pointer,
1201
1202
  frameloop: 'always',
1202
1203
  onPointerMissed: undefined,
1203
1204
  performance: {
@@ -1229,6 +1230,11 @@ const createStore = (invalidate, advance) => {
1229
1230
  factor: 0,
1230
1231
  getCurrentViewport
1231
1232
  },
1233
+ setEvents: events => set(state => ({ ...state,
1234
+ events: { ...state.events,
1235
+ events
1236
+ }
1237
+ })),
1232
1238
  setSize: (width, height) => {
1233
1239
  const camera = get().camera;
1234
1240
  const size = {
@@ -1266,6 +1272,7 @@ const createStore = (invalidate, advance) => {
1266
1272
  frameloop
1267
1273
  }));
1268
1274
  },
1275
+ previousRoot: undefined,
1269
1276
  internal: {
1270
1277
  active: false,
1271
1278
  priority: 0,
@@ -1467,43 +1474,6 @@ function useStore() {
1467
1474
  function useThree(selector = state => state, equalityFn) {
1468
1475
  return useStore()(selector, equalityFn);
1469
1476
  }
1470
- function useInject(state) {
1471
- const useOriginalStore = useStore();
1472
- const useInjectStore = React__namespace.useMemo(() => {
1473
- const useInjected = (sel = state => state) => {
1474
- // Execute the useStore hook with the selector once, to maintain reactivity, result doesn't matter
1475
- useOriginalStore(sel); // Inject data and return the result, either selected or raw
1476
-
1477
- return sel({ ...useOriginalStore.getState(),
1478
- ...state
1479
- });
1480
- };
1481
-
1482
- useInjected.setState = useOriginalStore.setState;
1483
- useInjected.destroy = useOriginalStore.destroy; // Patch getState
1484
-
1485
- useInjected.getState = () => {
1486
- return { ...useOriginalStore.getState(),
1487
- ...state
1488
- };
1489
- }; // Patch subscribe
1490
-
1491
-
1492
- useInjected.subscribe = listener => {
1493
- return useOriginalStore.subscribe((current, previous) => listener({ ...current,
1494
- ...state
1495
- }, previous));
1496
- };
1497
-
1498
- return useInjected;
1499
- }, [useOriginalStore, state]); // Return the patched store and a provider component
1500
-
1501
- return React__namespace.useMemo(() => [({
1502
- children
1503
- }) => /*#__PURE__*/React__namespace.createElement(context.Provider, {
1504
- value: useInjectStore
1505
- }, children), useInjectStore], [useInjectStore]);
1506
- }
1507
1477
  function useFrame(callback, renderPriority = 0) {
1508
1478
  const subscribe = useStore().getState().internal.subscribe; // Update ref
1509
1479
 
@@ -1530,6 +1500,11 @@ function loadingFn(extensions, onProgress) {
1530
1500
  };
1531
1501
  }
1532
1502
 
1503
+ function useMemoizedFn(fn) {
1504
+ const fnRef = React__namespace.useRef(fn);
1505
+ React__namespace.useLayoutEffect(() => void (fnRef.current = fn), [fn]);
1506
+ return (...args) => fnRef.current == null ? void 0 : fnRef.current(...args);
1507
+ }
1533
1508
  function useLoader(Proto, input, extensions, onProgress) {
1534
1509
  // Use suspense to load async assets
1535
1510
  const keys = Array.isArray(input) ? input : [input];
@@ -1629,9 +1604,7 @@ function createRoot(canvas) {
1629
1604
  params,
1630
1605
  ...options
1631
1606
  } = raycastOptions || {};
1632
- if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, {
1633
- enabled: true,
1634
- ...options
1607
+ if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, { ...options
1635
1608
  });
1636
1609
  if (!is.equ(params, raycaster.params, shallowLoose)) applyProps(raycaster, {
1637
1610
  params: { ...raycaster.params,
@@ -1746,14 +1719,14 @@ function createRoot(canvas) {
1746
1719
  return this;
1747
1720
  },
1748
1721
 
1749
- render(element) {
1722
+ render(children) {
1750
1723
  // The root has to be configured before it can be rendered
1751
1724
  if (!configured) this.configure();
1752
1725
  reconciler.updateContainer( /*#__PURE__*/React__namespace.createElement(Provider, {
1753
1726
  store: store,
1754
- element: element,
1727
+ children: children,
1755
1728
  onCreated: onCreated,
1756
- target: canvas
1729
+ rootElement: canvas
1757
1730
  }), fiber, null, () => undefined);
1758
1731
  return store;
1759
1732
  },
@@ -1765,35 +1738,36 @@ function createRoot(canvas) {
1765
1738
  };
1766
1739
  }
1767
1740
 
1768
- function render(element, canvas, config = {}) {
1741
+ function render(children, canvas, config) {
1769
1742
  console.warn('R3F.render is no longer supported in React 18. Use createRoot instead!');
1770
1743
  const root = createRoot(canvas);
1771
1744
  root.configure(config);
1772
- return root.render(element);
1745
+ return root.render(children);
1773
1746
  }
1774
1747
 
1775
1748
  function Provider({
1776
1749
  store,
1777
- element,
1750
+ children,
1778
1751
  onCreated,
1779
- target
1752
+ rootElement
1780
1753
  }) {
1781
- React__namespace.useEffect(() => {
1754
+ React__namespace.useLayoutEffect(() => {
1782
1755
  const state = store.getState(); // Flag the canvas active, rendering will now begin
1783
1756
 
1784
1757
  state.set(state => ({
1785
1758
  internal: { ...state.internal,
1786
1759
  active: true
1787
1760
  }
1788
- })); // Connect events
1761
+ })); // Notifiy that init is completed, the scene graph exists, but nothing has yet rendered
1789
1762
 
1790
- state.events.connect == null ? void 0 : state.events.connect(target); // Notifiy that init is completed, the scene graph exists, but nothing has yet rendered
1763
+ if (onCreated) onCreated(state); // Connect events to the targets parent, this is done to ensure events are registered on
1764
+ // a shared target, and not on the canvas itself
1791
1765
 
1792
- if (onCreated) onCreated(state); // eslint-disable-next-line react-hooks/exhaustive-deps
1766
+ if (!store.getState().events.connected) state.events.connect == null ? void 0 : state.events.connect(rootElement); // eslint-disable-next-line react-hooks/exhaustive-deps
1793
1767
  }, []);
1794
1768
  return /*#__PURE__*/React__namespace.createElement(context.Provider, {
1795
1769
  value: store
1796
- }, element);
1770
+ }, children);
1797
1771
  }
1798
1772
 
1799
1773
  function unmountComponentAtNode(canvas, callback) {
@@ -1834,20 +1808,58 @@ function createPortal(children, container, state) {
1834
1808
  }
1835
1809
 
1836
1810
  function Portal({
1837
- state,
1811
+ state = {},
1838
1812
  children,
1839
1813
  container
1840
1814
  }) {
1841
1815
  /** This has to be a component because it would not be able to call useThree/useStore otherwise since
1842
- * if this is our environment, then we are in in r3f's renderer but in react-dom, it would trigger
1816
+ * if this is our environment, then we are not in r3f's renderer but in react-dom, it would trigger
1843
1817
  * the "R3F hooks can only be used within the Canvas component!" warning:
1844
1818
  * <Canvas>
1845
1819
  * {createPortal(...)} */
1846
- const portalState = React__namespace.useMemo(() => ({ ...state,
1847
- scene: container
1848
- }), [state, container]);
1849
- const [PortalProvider, portalRoot] = useInject(portalState);
1850
- return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, reconciler.createPortal( /*#__PURE__*/React__namespace.createElement(PortalProvider, null, children), portalRoot, null));
1820
+ const {
1821
+ events,
1822
+ ...rest
1823
+ } = state;
1824
+ const previousRoot = useStore();
1825
+ const [raycaster] = React__namespace.useState(() => new THREE__namespace.Raycaster());
1826
+ const inject = React__namespace.useCallback((state, injectState) => {
1827
+ const intersect = { ...state
1828
+ };
1829
+
1830
+ if (injectState) {
1831
+ // Only the fields of "state" that do not differ from injectState
1832
+ Object.keys(state).forEach(key => {
1833
+ if ( // Some props should be off-limits
1834
+ !['size', 'viewport', 'internal', 'performance'].includes(key) && // Otherwise filter out the props that are different and let the inject layer take precedence
1835
+ state[key] !== injectState[key]) delete intersect[key];
1836
+ });
1837
+ }
1838
+
1839
+ return { ...intersect,
1840
+ scene: container,
1841
+ previousRoot,
1842
+ raycaster,
1843
+ events: { ...state.events,
1844
+ ...events
1845
+ },
1846
+ ...rest
1847
+ };
1848
+ }, [state]);
1849
+ const [useInjectStore] = React__namespace.useState(() => {
1850
+ const store = create__default['default']((set, get) => ({ ...inject(previousRoot.getState()),
1851
+ set,
1852
+ get
1853
+ }));
1854
+ previousRoot.subscribe(state => useInjectStore.setState(injectState => inject(state, injectState)));
1855
+ return store;
1856
+ });
1857
+ React__namespace.useEffect(() => {
1858
+ useInjectStore.setState(injectState => inject(previousRoot.getState(), injectState));
1859
+ }, [inject]);
1860
+ return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, reconciler.createPortal( /*#__PURE__*/React__namespace.createElement(context.Provider, {
1861
+ value: useInjectStore
1862
+ }, children), useInjectStore, null));
1851
1863
  }
1852
1864
 
1853
1865
  reconciler.injectIntoDevTools({
@@ -1869,6 +1881,7 @@ exports.createPortal = createPortal;
1869
1881
  exports.createRoot = createRoot;
1870
1882
  exports.dispose = dispose;
1871
1883
  exports.extend = extend;
1884
+ exports.getRootState = getRootState;
1872
1885
  exports.invalidate = invalidate;
1873
1886
  exports.omit = omit;
1874
1887
  exports.pick = pick;
@@ -1879,7 +1892,7 @@ exports.threeTypes = threeTypes;
1879
1892
  exports.unmountComponentAtNode = unmountComponentAtNode;
1880
1893
  exports.useFrame = useFrame;
1881
1894
  exports.useGraph = useGraph;
1882
- exports.useInject = useInject;
1883
1895
  exports.useLoader = useLoader;
1896
+ exports.useMemoizedFn = useMemoizedFn;
1884
1897
  exports.useStore = useStore;
1885
1898
  exports.useThree = useThree;