@react-three/fiber 9.8.1 → 9.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,8 +5,8 @@ var THREE = require('three');
5
5
  var jsxRuntime = require('react/jsx-runtime');
6
6
  var traditional = require('zustand/traditional');
7
7
  var suspendReact = require('suspend-react');
8
- var w1 = require('scheduler');
9
8
  var itsFine = require('its-fine');
9
+ var w1 = require('scheduler');
10
10
 
11
11
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
12
12
 
@@ -36,14 +36,6 @@ var threeTypes = /*#__PURE__*/Object.freeze({
36
36
  __proto__: null
37
37
  });
38
38
 
39
- /**
40
- * Returns the instance's initial (outmost) root.
41
- */
42
- function findInitialRoot(instance) {
43
- let root = instance.root;
44
- while (root.getState().previousRoot) root = root.getState().previousRoot;
45
- return root;
46
- }
47
39
  /**
48
40
  * Safely flush async effects when testing, simulating a legacy root.
49
41
  * @deprecated Import from React instead. import { act } from 'react'
@@ -51,9 +43,6 @@ function findInitialRoot(instance) {
51
43
  // Reference with computed key to break Webpack static analysis
52
44
  // https://github.com/webpack/webpack/issues/14814
53
45
  const act = React__namespace['act' + ''];
54
- const isOrthographicCamera = def => def && def.isOrthographicCamera;
55
- const isRef = obj => obj && obj.hasOwnProperty('current');
56
- const isColorRepresentation = value => value != null && (typeof value === 'string' || typeof value === 'number' || value.isColor);
57
46
 
58
47
  /**
59
48
  * An SSR-friendly useLayoutEffect.
@@ -70,7 +59,6 @@ function useMutableCallback(fn) {
70
59
  useIsomorphicLayoutEffect(() => void (ref.current = fn), [fn]);
71
60
  return ref;
72
61
  }
73
- const noop = () => {};
74
62
  function Gate({
75
63
  promise,
76
64
  onSettled
@@ -85,7 +73,7 @@ function useGate() {
85
73
  const [promise, setPromise] = React__namespace.useState(null);
86
74
  const onSettled = React__namespace.useCallback(() => setPromise(null), []);
87
75
  // Keep the first promise, or a repeated call would loop
88
- const waitFor = React__namespace.useCallback(next => setPromise(current => current != null ? current : Promise.resolve(next).then(noop, noop)), []);
76
+ const waitFor = React__namespace.useCallback(next => setPromise(current => current != null ? current : Promise.resolve(next).then(() => {}, () => {})), []);
89
77
  const gate = promise ? /*#__PURE__*/jsxRuntime.jsx(React__namespace.Suspense, {
90
78
  fallback: null,
91
79
  children: /*#__PURE__*/jsxRuntime.jsx(Gate, {
@@ -146,6 +134,19 @@ const ErrorBoundary = /* @__PURE__ */(_ErrorBoundary => (_ErrorBoundary = class
146
134
  }, _ErrorBoundary.getDerivedStateFromError = () => ({
147
135
  error: true
148
136
  }), _ErrorBoundary))();
137
+
138
+ /**
139
+ * Returns the instance's initial (outmost) root.
140
+ */
141
+ function findInitialRoot(instance) {
142
+ let root = instance.root;
143
+ while (root.getState().previousRoot) root = root.getState().previousRoot;
144
+ return root;
145
+ }
146
+ const isOrthographicCamera = def => def && def.isOrthographicCamera;
147
+ const isRef = obj => obj && obj.hasOwnProperty('current');
148
+ const isColorRepresentation = value => value != null && (typeof value === 'string' || typeof value === 'number' || value.isColor);
149
+ const noop = () => {};
149
150
  function calculateDpr(dpr) {
150
151
  var _window$devicePixelRa;
151
152
  // Err on the side of progress by assuming 2x dpr if we can't detect it
@@ -154,6 +155,43 @@ function calculateDpr(dpr) {
154
155
  return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
155
156
  }
156
157
 
158
+ /**
159
+ * Calls `onChange` whenever window.devicePixelRatio changes: moving to another display or zooming.
160
+ * Neither resizes the canvas, so resize observers won't see it. A resolution query only matches the
161
+ * current ratio, so it is re-created after every change. Returns the unsubscribe function.
162
+ */
163
+ function watchDpr(onChange) {
164
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function' || !window.devicePixelRatio) {
165
+ return () => {};
166
+ }
167
+
168
+ // Partial matchMedia polyfills (test setups, older environments) may return no listener methods,
169
+ // or throw on a query they can't parse. Following the ratio is best-effort: it runs inside
170
+ // configuration, which must not fail because of it
171
+ let query;
172
+ const listen = () => {
173
+ try {
174
+ var _query, _query2;
175
+ query = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
176
+ if (typeof ((_query = query) == null ? void 0 : _query.addEventListener) === 'function') query.addEventListener('change', handleChange);else (_query2 = query) == null ? void 0 : _query2.addListener == null ? void 0 : _query2.addListener(handleChange);
177
+ } catch {
178
+ query = undefined;
179
+ }
180
+ };
181
+ const unlisten = () => {
182
+ var _query3, _query4;
183
+ if (typeof ((_query3 = query) == null ? void 0 : _query3.removeEventListener) === 'function') query.removeEventListener('change', handleChange);else (_query4 = query) == null ? void 0 : _query4.removeListener == null ? void 0 : _query4.removeListener(handleChange);
184
+ query = undefined;
185
+ };
186
+ const handleChange = () => {
187
+ unlisten();
188
+ listen();
189
+ onChange();
190
+ };
191
+ listen();
192
+ return unlisten;
193
+ }
194
+
157
195
  /**
158
196
  * Returns instance root state
159
197
  */
@@ -603,11 +641,24 @@ function swapInteractivity(store, object, newObject) {
603
641
  internal.hovered.set(makeId(next), next);
604
642
  }
605
643
  });
644
+
645
+ // A capture is keyed by its eventObject, but it also carries the intersection that is replayed into
646
+ // every subsequent event. Both must follow the new instance, or the capture resolves to the discarded object.
606
647
  internal.capturedMap.forEach(captures => {
607
- const captureData = captures.get(object);
608
- if (captureData) {
609
- captures.delete(object);
610
- captures.set(newObject, captureData);
648
+ for (const [key, captureData] of Array.from(captures)) {
649
+ const {
650
+ intersection
651
+ } = captureData;
652
+ if (key !== object && intersection.object !== object && intersection.eventObject !== object) continue;
653
+ if (key === object) captures.delete(key);
654
+ captures.set(key === object ? newObject : key, {
655
+ ...captureData,
656
+ intersection: {
657
+ ...intersection,
658
+ object: intersection.object === object ? newObject : intersection.object,
659
+ eventObject: intersection.eventObject === object ? newObject : intersection.eventObject
660
+ }
661
+ });
611
662
  }
612
663
  });
613
664
  }
@@ -1006,7 +1057,109 @@ function createEvents(store) {
1006
1057
  };
1007
1058
  }
1008
1059
 
1060
+ const isPromiseLike = value => typeof (value == null ? void 0 : value.then) === 'function';
1061
+
1062
+ /** A promise tagged with its state, the protocol React's `use` reads */
1063
+
1064
+ const fulfilled = value => Object.assign(Promise.resolve(value), {
1065
+ status: 'fulfilled',
1066
+ value
1067
+ });
1068
+ const rejected = reason => {
1069
+ const promise = Promise.reject(reason);
1070
+ promise.catch(() => {}); // the reason is read from `status`
1071
+ return Object.assign(promise, {
1072
+ status: 'rejected',
1073
+ reason
1074
+ });
1075
+ };
1076
+
1077
+ /** A pending promise, settled once from outside. Its `status` updates the moment it settles */
1078
+ const deferred = () => {
1079
+ let resolve;
1080
+ let reject;
1081
+ const promise = new Promise((yes, no) => {
1082
+ resolve = value => {
1083
+ Object.assign(promise, {
1084
+ status: 'fulfilled',
1085
+ value
1086
+ });
1087
+ yes(value);
1088
+ };
1089
+ reject = reason => {
1090
+ Object.assign(promise, {
1091
+ status: 'rejected',
1092
+ reason
1093
+ });
1094
+ no(reason);
1095
+ };
1096
+ });
1097
+ promise.status = 'pending';
1098
+ promise.catch(() => {}); // the reason is read from `status`
1099
+ return {
1100
+ promise,
1101
+ resolve,
1102
+ reject
1103
+ };
1104
+ };
1105
+
1106
+ /** Renderer creation and resource release. Root lifetime decides when release is safe. */
1009
1107
  const isRenderer = def => !!(def != null && def.render);
1108
+
1109
+ /** The renderer to configure, or a promise for one from an async factory */
1110
+ function createRenderer(canvas, gl) {
1111
+ const defaults = {
1112
+ canvas,
1113
+ powerPreference: 'high-performance',
1114
+ antialias: true,
1115
+ alpha: true
1116
+ };
1117
+ if (typeof gl === 'function') return gl(defaults);
1118
+ if (isRenderer(gl)) return gl;
1119
+ return new THREE__namespace.WebGLRenderer({
1120
+ ...defaults,
1121
+ ...gl
1122
+ });
1123
+ }
1124
+
1125
+ /**
1126
+ * Frees a renderer R3F built. WebGLRenderer.dispose() releases programs and caches but keeps its
1127
+ * context until garbage collection, and browsers cap live WebGL contexts, so the context is lost
1128
+ * after it. A WebGPURenderer (from a factory) releases its device and context inside dispose().
1129
+ */
1130
+ function disposeRenderer(gl) {
1131
+ // Avoid starting initialization through three's dispose(). A factory must await init()
1132
+ // before returning its renderer so readiness includes initialization and teardown can wait.
1133
+ if ((gl.hasInitialized == null ? void 0 : gl.hasInitialized()) === false) return;
1134
+ const disposed = attempt$1(() => {
1135
+ var _gl$renderLists;
1136
+ return gl.dispose ? gl.dispose() : (_gl$renderLists = gl.renderLists) == null ? void 0 : _gl$renderLists.dispose == null ? void 0 : _gl$renderLists.dispose();
1137
+ });
1138
+ // WebGPURenderer.dispose() is async from three r186
1139
+ if (isPromiseLike(disposed)) {
1140
+ return Promise.resolve(disposed).catch(error => console.warn('[R3F] Error disposing renderer', error)).then(() => attempt$1(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss()));
1141
+ }
1142
+ attempt$1(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
1143
+ }
1144
+
1145
+ /** Owned renderers are disposed. Caller-provided renderers release caches and their context. */
1146
+ function releaseRenderer(gl, owned) {
1147
+ if (!gl) return;
1148
+ if (owned) return disposeRenderer(gl);
1149
+ attempt$1(() => {
1150
+ var _gl$renderLists2;
1151
+ return (_gl$renderLists2 = gl.renderLists) == null ? void 0 : _gl$renderLists2.dispose == null ? void 0 : _gl$renderLists2.dispose();
1152
+ });
1153
+ attempt$1(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
1154
+ }
1155
+ function attempt$1(step) {
1156
+ try {
1157
+ return step();
1158
+ } catch (error) {
1159
+ console.warn('[R3F] Error while unmounting root; teardown may be incomplete:', error);
1160
+ }
1161
+ }
1162
+
1010
1163
  const context = /* @__PURE__ */React__namespace.createContext(null);
1011
1164
  const createStore = (invalidate, advance) => {
1012
1165
  const rootStore = traditional.createWithEqualityFn((set, get) => {
@@ -1301,7 +1454,7 @@ function useGraph(object) {
1301
1454
  return React__namespace.useMemo(() => buildGraph(object), [object]);
1302
1455
  }
1303
1456
  const memoizedLoaders = new WeakMap();
1304
- const isConstructor$1 = value => {
1457
+ const isConstructor = value => {
1305
1458
  var _value$prototype;
1306
1459
  return typeof value === 'function' && (value == null ? void 0 : (_value$prototype = value.prototype) == null ? void 0 : _value$prototype.constructor) === value;
1307
1460
  };
@@ -1310,7 +1463,7 @@ function loadingFn(extensions, onProgress) {
1310
1463
  let loader;
1311
1464
 
1312
1465
  // Construct and cache loader if constructor was passed
1313
- if (isConstructor$1(Proto)) {
1466
+ if (isConstructor(Proto)) {
1314
1467
  loader = memoizedLoaders.get(Proto);
1315
1468
  if (!loader) {
1316
1469
  loader = new Proto();
@@ -1385,22 +1538,6 @@ useLoader.clear = function (loader, input) {
1385
1538
  const shallow = {
1386
1539
  objects: 'shallow'
1387
1540
  };
1388
-
1389
- /** The renderer to configure, or a promise for one from an async factory */
1390
- function createRenderer(canvas, gl) {
1391
- const defaults = {
1392
- canvas,
1393
- powerPreference: 'high-performance',
1394
- antialias: true,
1395
- alpha: true
1396
- };
1397
- if (typeof gl === 'function') return gl(defaults);
1398
- if (isRenderer(gl)) return gl;
1399
- return new THREE__namespace.WebGLRenderer({
1400
- ...defaults,
1401
- ...gl
1402
- });
1403
- }
1404
1541
  function computeInitialSize(canvas, size) {
1405
1542
  if (!size && typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1406
1543
  const {
@@ -1648,55 +1785,43 @@ function applyRootConfiguration({
1648
1785
  configuration.previous = next;
1649
1786
  }
1650
1787
 
1651
- const isPromiseLike = value => typeof (value == null ? void 0 : value.then) === 'function';
1652
-
1653
- /** A promise tagged with its state, the protocol React's `use` reads */
1654
-
1655
- const fulfilled = value => Object.assign(Promise.resolve(value), {
1656
- status: 'fulfilled',
1657
- value
1658
- });
1659
- const rejected = reason => {
1660
- const promise = Promise.reject(reason);
1661
- promise.catch(() => {}); // the reason is read from `status`
1662
- return Object.assign(promise, {
1663
- status: 'rejected',
1664
- reason
1665
- });
1666
- };
1667
-
1668
- /** A pending promise, settled once from outside. Its `status` updates the moment it settles */
1669
- const deferred = () => {
1670
- let resolve;
1671
- let reject;
1672
- const promise = new Promise((yes, no) => {
1673
- resolve = value => {
1674
- Object.assign(promise, {
1675
- status: 'fulfilled',
1676
- value
1677
- });
1678
- yes(value);
1679
- };
1680
- reject = reason => {
1681
- Object.assign(promise, {
1682
- status: 'rejected',
1683
- reason
1684
- });
1685
- no(reason);
1686
- };
1687
- });
1688
- promise.status = 'pending';
1689
- promise.catch(() => {}); // the reason is read from `status`
1690
- return {
1691
- promise,
1692
- resolve,
1693
- reject
1788
+ /**
1789
+ * Moving to another display or zooming changes window.devicePixelRatio without resizing the canvas,
1790
+ * so nothing configures the root again. Resolves the ratio the way configuration does, from the last
1791
+ * applied dpr. Returns the function that stops following.
1792
+ */
1793
+ function followDpr(root) {
1794
+ const xr = () => {
1795
+ var _root$store$getState$;
1796
+ return (_root$store$getState$ = root.store.getState().gl) == null ? void 0 : _root$store$getState$.xr;
1694
1797
  };
1695
- };
1798
+ const follow = () => {
1799
+ var _xr, _xr2, _xr3, _last$dpr;
1800
+ const state = root.store.getState();
1801
+ // three doesn't resize during XR and restores its own pixel ratio once the session ends. It holds
1802
+ // the session before it sets isPresenting. Listeners are deduplicated, so this waits once
1803
+ if ((_xr = xr()) != null && _xr.isPresenting || (_xr2 = xr()) != null && _xr2.getSession != null && _xr2.getSession()) {
1804
+ xr().addEventListener('sessionend', follow);
1805
+ return;
1806
+ }
1807
+ (_xr3 = xr()) == null ? void 0 : _xr3.removeEventListener == null ? void 0 : _xr3.removeEventListener('sessionend', follow);
1808
+ // A failed configuration applies everything on the next one
1809
+ const last = root.configuration.previous;
1810
+ if (!last) return;
1811
+ const dpr = (_last$dpr = last.dpr) != null ? _last$dpr : [1, 2];
1812
+ if (state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
1813
+ };
1814
+ const unwatch = watchDpr(follow);
1815
+ return () => {
1816
+ var _xr4;
1817
+ unwatch();
1818
+ (_xr4 = xr()) == null ? void 0 : _xr4.removeEventListener == null ? void 0 : _xr4.removeEventListener('sessionend', follow);
1819
+ };
1820
+ }
1696
1821
 
1697
1822
  var packageData = {
1698
1823
  name: "@react-three/fiber",
1699
- version: "9.8.1",
1824
+ version: "9.8.2",
1700
1825
  description: "A React renderer for Threejs",
1701
1826
  keywords: [
1702
1827
  "react",
@@ -1728,6 +1853,12 @@ var packageData = {
1728
1853
  module: "dist/react-three-fiber.esm.js",
1729
1854
  types: "dist/react-three-fiber.cjs.d.ts",
1730
1855
  "react-native": "native/dist/react-three-fiber-native.cjs.js",
1856
+ files: [
1857
+ "dist",
1858
+ "native",
1859
+ "react-reconciler",
1860
+ "CHANGELOG.md"
1861
+ ],
1731
1862
  sideEffects: false,
1732
1863
  preconstruct: {
1733
1864
  entrypoints: [
@@ -7689,6 +7820,27 @@ function G1() {
7689
7820
  var J1 = G1();
7690
7821
  const Z1 = $1(J1);
7691
7822
 
7823
+ /** Constructor registration and lookup for JSX elements. */
7824
+
7825
+ const catalogue = {};
7826
+ let i = 0;
7827
+ const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
7828
+ function hasConstructor(name) {
7829
+ return name in catalogue;
7830
+ }
7831
+ function resolveConstructor(name) {
7832
+ return catalogue[name];
7833
+ }
7834
+ function extend(objects) {
7835
+ if (typeof objects === 'function') {
7836
+ const Component = `${i++}`;
7837
+ catalogue[Component] = objects;
7838
+ return Component;
7839
+ } else {
7840
+ Object.assign(catalogue, objects);
7841
+ }
7842
+ }
7843
+
7692
7844
  function createReconciler(config) {
7693
7845
  const reconciler = Z1(config);
7694
7846
 
@@ -7702,24 +7854,11 @@ const NoEventPriority = 0;
7702
7854
  // https://github.com/pmndrs/react-three-fiber/pull/2931
7703
7855
  // https://github.com/microsoft/TypeScript/issues/37079
7704
7856
 
7705
- const catalogue = {};
7706
7857
  const PREFIX_REGEX = /^three(?=[A-Z])/;
7707
- const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
7708
- let i = 0;
7709
- const isConstructor = object => typeof object === 'function';
7710
- function extend(objects) {
7711
- if (isConstructor(objects)) {
7712
- const Component = `${i++}`;
7713
- catalogue[Component] = objects;
7714
- return Component;
7715
- } else {
7716
- Object.assign(catalogue, objects);
7717
- }
7718
- }
7719
7858
  function validateInstance(type, props) {
7720
7859
  // Get target from catalogue
7721
7860
  const name = toPascalCase(type);
7722
- const target = catalogue[name];
7861
+ const target = resolveConstructor(name);
7723
7862
 
7724
7863
  // Validate element target
7725
7864
  if (type !== 'primitive' && !target) throw new Error(`R3F: ${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`);
@@ -7733,7 +7872,7 @@ function validateInstance(type, props) {
7733
7872
  function createInstance(type, props, root) {
7734
7873
  var _props$object;
7735
7874
  // Remove three* prefix from elements if native element not present
7736
- type = toPascalCase(type) in catalogue ? type : type.replace(PREFIX_REGEX, '');
7875
+ type = hasConstructor(toPascalCase(type)) ? type : type.replace(PREFIX_REGEX, '');
7737
7876
  validateInstance(type, props);
7738
7877
 
7739
7878
  // Regenerate the R3F instance for primitives to simulate a new object
@@ -7780,7 +7919,7 @@ function handleContainerEffects(parent, child, beforeChild) {
7780
7919
  if (!child.object) {
7781
7920
  var _child$props$object, _child$props$args;
7782
7921
  // Get target from catalogue
7783
- const target = catalogue[toPascalCase(child.type)];
7922
+ const target = resolveConstructor(toPascalCase(child.type));
7784
7923
 
7785
7924
  // Create object
7786
7925
  child.object = (_child$props$object = child.props.object) != null ? _child$props$object : new target(...((_child$props$args = child.props.args) != null ? _child$props$args : []));
@@ -7877,6 +8016,9 @@ function disposeOnIdle(object) {
7877
8016
  function removeChild(parent, child, dispose) {
7878
8017
  if (!child) return;
7879
8018
 
8019
+ // Unlinking clears the parent, which invalidateInstance checks, so remember it
8020
+ const wasLinked = !!child.parent;
8021
+
7880
8022
  // Unlink instances
7881
8023
  child.parent = null;
7882
8024
  const childIndex = parent.children.indexOf(child);
@@ -7914,7 +8056,11 @@ function removeChild(parent, child, dispose) {
7914
8056
  }
7915
8057
 
7916
8058
  // Tree was updated, request a frame for top-level instance
7917
- if (dispose === undefined) invalidateInstance(child);
8059
+ if (dispose === undefined && wasLinked) {
8060
+ var _child$root;
8061
+ const state = (_child$root = child.root) == null ? void 0 : _child$root.getState == null ? void 0 : _child$root.getState();
8062
+ if (state && state.internal.frames === 0) state.invalidate();
8063
+ }
7918
8064
  }
7919
8065
  function setFiberRef(fiber, publicInstance) {
7920
8066
  for (const _fiber of [fiber, fiber.alternate]) {
@@ -7975,7 +8121,7 @@ function swapReconstructedInstances() {
7975
8121
  if (parent) {
7976
8122
  var _instance$props$objec, _instance$props$args;
7977
8123
  // Get target from catalogue
7978
- const target = catalogue[toPascalCase(instance.type)];
8124
+ const target = resolveConstructor(toPascalCase(instance.type));
7979
8125
 
7980
8126
  // Create object
7981
8127
  const prevObject = instance.object;
@@ -8434,6 +8580,8 @@ function createRoot(canvas) {
8434
8580
  });
8435
8581
  }
8436
8582
  applyRootConfiguration(root, canvas, props);
8583
+ // Only a range, including the default one, follows the display: a fixed dpr never touches matchMedia
8584
+ if (!root.unwatchDpr && (props.dpr === undefined || Array.isArray(props.dpr))) root.unwatchDpr = followDpr(root);
8437
8585
  resolve(this);
8438
8586
  };
8439
8587
  const run = () => {
@@ -8518,26 +8666,6 @@ function Provider({
8518
8666
  children: children
8519
8667
  });
8520
8668
  }
8521
-
8522
- /**
8523
- * Frees a renderer R3F built. WebGLRenderer.dispose() releases programs and caches but keeps its
8524
- * context until garbage collection, and browsers cap live WebGL contexts, so the context is lost
8525
- * after it. A WebGPURenderer (from a factory) releases its device and context inside dispose().
8526
- */
8527
- function disposeRenderer(gl) {
8528
- // Avoid starting initialization through three's dispose(). A factory must await init()
8529
- // before returning its renderer so readiness includes initialization and teardown can wait.
8530
- if ((gl.hasInitialized == null ? void 0 : gl.hasInitialized()) === false) return;
8531
- const disposed = attempt(() => {
8532
- var _gl$renderLists;
8533
- return gl.dispose ? gl.dispose() : (_gl$renderLists = gl.renderLists) == null ? void 0 : _gl$renderLists.dispose == null ? void 0 : _gl$renderLists.dispose();
8534
- });
8535
- // WebGPURenderer.dispose() is async from three r186
8536
- if (isPromiseLike(disposed)) {
8537
- return Promise.resolve(disposed).catch(error => console.warn('[R3F] Error disposing renderer', error)).then(() => attempt(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss()));
8538
- }
8539
- attempt(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
8540
- }
8541
8669
  function attempt(step) {
8542
8670
  try {
8543
8671
  return step();
@@ -8570,21 +8698,11 @@ function unmountComponentAtNode(canvas, callback) {
8570
8698
  state.internal.active = false;
8571
8699
 
8572
8700
  // Release what uses the renderer before the renderer itself
8701
+ attempt(() => root.unwatchDpr == null ? void 0 : root.unwatchDpr());
8573
8702
  attempt(() => state.events.disconnect == null ? void 0 : state.events.disconnect());
8574
8703
  if ((_state$gl = state.gl) != null && _state$gl.xr) attempt(() => state.xr.disconnect());
8575
8704
  if (state.scene) attempt(() => dispose(state.scene));
8576
- const gl = state.gl;
8577
- let disposal = undefined;
8578
- if (gl && root.ownsRenderer) {
8579
- disposal = attempt(() => disposeRenderer(gl));
8580
- } else if (gl) {
8581
- // A renderer passed in keeps its 9.x teardown: its context is lost but it is not disposed
8582
- attempt(() => {
8583
- var _gl$renderLists2;
8584
- return (_gl$renderLists2 = gl.renderLists) == null ? void 0 : _gl$renderLists2.dispose == null ? void 0 : _gl$renderLists2.dispose();
8585
- });
8586
- attempt(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
8587
- }
8705
+ const disposal = attempt(() => releaseRenderer(state.gl, root.ownsRenderer));
8588
8706
  if (isPromiseLike(disposal)) disposal.then(() => callback == null ? void 0 : callback(canvas));else callback == null ? void 0 : callback(canvas);
8589
8707
  };
8590
8708
  reconciler.updateContainer(null, root.fiber, null, () => {
@@ -8754,93 +8872,132 @@ function createPortal(children, container, state) {
8754
8872
  state: state
8755
8873
  });
8756
8874
  }
8875
+ function initializePortal(options, initialState) {
8876
+ const {
8877
+ parentStore,
8878
+ container
8879
+ } = options;
8880
+ const portalStore = traditional.createWithEqualityFn((set, get) => ({
8881
+ ...initialState,
8882
+ set,
8883
+ get,
8884
+ // Create once with the store's setter, without retaining a state snapshot.
8885
+ setEvents: events => set(state => ({
8886
+ ...state,
8887
+ events: {
8888
+ ...state.events,
8889
+ ...events
8890
+ }
8891
+ }))
8892
+ }));
8893
+ const parentStateRef = {
8894
+ current: parentStore.getState()
8895
+ };
8896
+ portalStore.setState(portalState => inject(parentStateRef.current, portalState, options));
8897
+ return {
8898
+ parentStore,
8899
+ container,
8900
+ portalStore,
8901
+ parentStateRef
8902
+ };
8903
+ }
8757
8904
  function Portal({
8758
8905
  state = {},
8759
8906
  children,
8760
8907
  container
8761
8908
  }) {
8762
- /** This has to be a component because it would not be able to call useThree/useStore otherwise since
8763
- * if this is our environment, then we are not in r3f's renderer but in react-dom, it would trigger
8764
- * the "R3F hooks can only be used within the Canvas component!" warning:
8765
- * <Canvas>
8766
- * {createPortal(...)} */
8909
+ const parentStore = useStore();
8767
8910
  const {
8768
8911
  events,
8769
8912
  size,
8770
8913
  ...rest
8771
8914
  } = state;
8772
- const previousRoot = useStore();
8773
8915
  const [raycaster] = React__namespace.useState(() => new THREE__namespace.Raycaster());
8774
8916
  const [pointer] = React__namespace.useState(() => new THREE__namespace.Vector2());
8775
- const inject = useMutableCallback((rootState, injectState) => {
8776
- let viewport = undefined;
8777
- if (injectState.camera && size) {
8778
- const camera = injectState.camera;
8779
- // Calculate the override viewport, if present
8780
- viewport = rootState.viewport.getCurrentViewport(camera, new THREE__namespace.Vector3(), size);
8781
- // Update the portal camera, if it differs from the previous layer
8782
- if (camera !== rootState.camera) updateCamera(camera, size);
8783
- }
8784
- return {
8785
- // The intersect consists of the previous root state
8786
- ...rootState,
8787
- ...injectState,
8788
- // Portals have their own scene, which forms the root, a raycaster and a pointer
8789
- scene: container,
8790
- raycaster,
8791
- pointer,
8792
- mouse: pointer,
8793
- // Their previous root is the layer before it
8794
- previousRoot,
8795
- // Events, size and viewport can be overridden by the inject layer
8796
- events: {
8797
- ...rootState.events,
8798
- ...injectState.events,
8799
- ...events
8800
- },
8801
- size: {
8802
- ...rootState.size,
8803
- ...size
8804
- },
8805
- viewport: {
8806
- ...rootState.viewport,
8807
- ...viewport
8808
- },
8809
- // Layers are allowed to override events
8810
- setEvents: events => injectState.set(state => ({
8811
- ...state,
8812
- events: {
8813
- ...state.events,
8814
- ...events
8815
- }
8816
- }))
8917
+ const options = {
8918
+ parentStore,
8919
+ container,
8920
+ raycaster,
8921
+ pointer,
8922
+ events,
8923
+ size
8924
+ };
8925
+ const injectRef = useMutableCallback((parentState, portalState) => inject(parentState, portalState, options));
8926
+
8927
+ // Initialize during render. Keep the store in state so Activity can preserve it.
8928
+ const [portal, setPortal] = React__namespace.useState(() => initializePortal(options, rest));
8929
+ if (portal.parentStore !== parentStore || portal.container !== container) {
8930
+ setPortal(initializePortal(options, rest));
8931
+ }
8932
+ const {
8933
+ portalStore,
8934
+ parentStateRef
8935
+ } = portal;
8936
+
8937
+ // Subscribe after commit and update the portal on each parent change.
8938
+ useIsomorphicLayoutEffect(() => {
8939
+ const update = parentState => {
8940
+ parentStateRef.current = parentState;
8941
+ portalStore.setState(portalState => injectRef.current(parentState, portalState));
8817
8942
  };
8943
+ const unsubscribe = parentStore.subscribe(update);
8944
+
8945
+ // Catch up on changes made before subscribing or while Activity was hidden.
8946
+ const parentState = parentStore.getState();
8947
+ if (parentState !== parentStateRef.current) update(parentState);
8948
+
8949
+ // Clean up when hidden, unmounted or switching stores.
8950
+ return unsubscribe;
8951
+ }, [parentStore, portalStore, parentStateRef]);
8952
+ return /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {
8953
+ children: reconciler.createPortal( /*#__PURE__*/jsxRuntime.jsx(context.Provider, {
8954
+ value: portalStore,
8955
+ children: children
8956
+ }), portalStore, null)
8818
8957
  });
8819
- const usePortalStore = React__namespace.useMemo(() => {
8820
- // Create a mirrored store, based on the previous root with a few overrides ...
8821
- const store = traditional.createWithEqualityFn((set, get) => ({
8822
- ...rest,
8823
- set,
8824
- get
8825
- }));
8826
-
8827
- // Subscribe to previous root-state and copy changes over to the mirrored portal-state
8828
- const onMutate = prev => store.setState(state => inject.current(prev, state));
8829
- onMutate(previousRoot.getState());
8830
- previousRoot.subscribe(onMutate);
8831
- return store;
8832
- // eslint-disable-next-line react-hooks/exhaustive-deps
8833
- }, [previousRoot, container]);
8834
- return (
8835
- /*#__PURE__*/
8836
- // @ts-ignore, reconciler types are not maintained
8837
- jsxRuntime.jsx(jsxRuntime.Fragment, {
8838
- children: reconciler.createPortal( /*#__PURE__*/jsxRuntime.jsx(context.Provider, {
8839
- value: usePortalStore,
8840
- children: children
8841
- }), usePortalStore, null)
8842
- })
8843
- );
8958
+ }
8959
+ function inject(parentState, portalState, options) {
8960
+ const {
8961
+ parentStore,
8962
+ container,
8963
+ raycaster,
8964
+ pointer,
8965
+ events,
8966
+ size
8967
+ } = options;
8968
+ let viewport = undefined;
8969
+ if (portalState.camera && size) {
8970
+ const camera = portalState.camera;
8971
+ // Calculate the override viewport, if present
8972
+ viewport = parentState.viewport.getCurrentViewport(camera, new THREE__namespace.Vector3(), size);
8973
+ // Update the portal camera, if it differs from the previous layer
8974
+ if (camera !== parentState.camera) updateCamera(camera, size);
8975
+ }
8976
+ return {
8977
+ // Inherit parent values and preserve the portal's own state.
8978
+ ...parentState,
8979
+ ...portalState,
8980
+ // Portals have their own scene, raycaster and pointer.
8981
+ scene: container,
8982
+ raycaster,
8983
+ pointer,
8984
+ mouse: pointer,
8985
+ previousRoot: parentStore,
8986
+ // Apply the portal's event, size and viewport overrides.
8987
+ events: {
8988
+ ...parentState.events,
8989
+ ...portalState.events,
8990
+ ...events
8991
+ },
8992
+ size: {
8993
+ ...parentState.size,
8994
+ ...size
8995
+ },
8996
+ viewport: {
8997
+ ...parentState.viewport,
8998
+ ...viewport
8999
+ }
9000
+ };
8844
9001
  }
8845
9002
 
8846
9003
  const DOM_EVENTS = {