@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: [
@@ -16468,6 +16599,27 @@ function G1() {
16468
16599
  var J1 = G1();
16469
16600
  const Z1 = $1(J1);
16470
16601
 
16602
+ /** Constructor registration and lookup for JSX elements. */
16603
+
16604
+ const catalogue = {};
16605
+ let i = 0;
16606
+ const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
16607
+ function hasConstructor(name) {
16608
+ return name in catalogue;
16609
+ }
16610
+ function resolveConstructor(name) {
16611
+ return catalogue[name];
16612
+ }
16613
+ function extend(objects) {
16614
+ if (typeof objects === 'function') {
16615
+ const Component = `${i++}`;
16616
+ catalogue[Component] = objects;
16617
+ return Component;
16618
+ } else {
16619
+ Object.assign(catalogue, objects);
16620
+ }
16621
+ }
16622
+
16471
16623
  function createReconciler(config) {
16472
16624
  const reconciler = Z1(config);
16473
16625
 
@@ -16481,24 +16633,11 @@ const NoEventPriority = 0;
16481
16633
  // https://github.com/pmndrs/react-three-fiber/pull/2931
16482
16634
  // https://github.com/microsoft/TypeScript/issues/37079
16483
16635
 
16484
- const catalogue = {};
16485
16636
  const PREFIX_REGEX = /^three(?=[A-Z])/;
16486
- const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
16487
- let i = 0;
16488
- const isConstructor = object => typeof object === 'function';
16489
- function extend(objects) {
16490
- if (isConstructor(objects)) {
16491
- const Component = `${i++}`;
16492
- catalogue[Component] = objects;
16493
- return Component;
16494
- } else {
16495
- Object.assign(catalogue, objects);
16496
- }
16497
- }
16498
16637
  function validateInstance(type, props) {
16499
16638
  // Get target from catalogue
16500
16639
  const name = toPascalCase(type);
16501
- const target = catalogue[name];
16640
+ const target = resolveConstructor(name);
16502
16641
 
16503
16642
  // Validate element target
16504
16643
  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`);
@@ -16512,7 +16651,7 @@ function validateInstance(type, props) {
16512
16651
  function createInstance(type, props, root) {
16513
16652
  var _props$object;
16514
16653
  // Remove three* prefix from elements if native element not present
16515
- type = toPascalCase(type) in catalogue ? type : type.replace(PREFIX_REGEX, '');
16654
+ type = hasConstructor(toPascalCase(type)) ? type : type.replace(PREFIX_REGEX, '');
16516
16655
  validateInstance(type, props);
16517
16656
 
16518
16657
  // Regenerate the R3F instance for primitives to simulate a new object
@@ -16559,7 +16698,7 @@ function handleContainerEffects(parent, child, beforeChild) {
16559
16698
  if (!child.object) {
16560
16699
  var _child$props$object, _child$props$args;
16561
16700
  // Get target from catalogue
16562
- const target = catalogue[toPascalCase(child.type)];
16701
+ const target = resolveConstructor(toPascalCase(child.type));
16563
16702
 
16564
16703
  // Create object
16565
16704
  child.object = (_child$props$object = child.props.object) != null ? _child$props$object : new target(...((_child$props$args = child.props.args) != null ? _child$props$args : []));
@@ -16656,6 +16795,9 @@ function disposeOnIdle(object) {
16656
16795
  function removeChild(parent, child, dispose) {
16657
16796
  if (!child) return;
16658
16797
 
16798
+ // Unlinking clears the parent, which invalidateInstance checks, so remember it
16799
+ const wasLinked = !!child.parent;
16800
+
16659
16801
  // Unlink instances
16660
16802
  child.parent = null;
16661
16803
  const childIndex = parent.children.indexOf(child);
@@ -16693,7 +16835,11 @@ function removeChild(parent, child, dispose) {
16693
16835
  }
16694
16836
 
16695
16837
  // Tree was updated, request a frame for top-level instance
16696
- if (dispose === undefined) invalidateInstance(child);
16838
+ if (dispose === undefined && wasLinked) {
16839
+ var _child$root;
16840
+ const state = (_child$root = child.root) == null ? void 0 : _child$root.getState == null ? void 0 : _child$root.getState();
16841
+ if (state && state.internal.frames === 0) state.invalidate();
16842
+ }
16697
16843
  }
16698
16844
  function setFiberRef(fiber, publicInstance) {
16699
16845
  for (const _fiber of [fiber, fiber.alternate]) {
@@ -16754,7 +16900,7 @@ function swapReconstructedInstances() {
16754
16900
  if (parent) {
16755
16901
  var _instance$props$objec, _instance$props$args;
16756
16902
  // Get target from catalogue
16757
- const target = catalogue[toPascalCase(instance.type)];
16903
+ const target = resolveConstructor(toPascalCase(instance.type));
16758
16904
 
16759
16905
  // Create object
16760
16906
  const prevObject = instance.object;
@@ -17213,6 +17359,8 @@ function createRoot(canvas) {
17213
17359
  });
17214
17360
  }
17215
17361
  applyRootConfiguration(root, canvas, props);
17362
+ // Only a range, including the default one, follows the display: a fixed dpr never touches matchMedia
17363
+ if (!root.unwatchDpr && (props.dpr === undefined || Array.isArray(props.dpr))) root.unwatchDpr = followDpr(root);
17216
17364
  resolve(this);
17217
17365
  };
17218
17366
  const run = () => {
@@ -17297,26 +17445,6 @@ function Provider({
17297
17445
  children: children
17298
17446
  });
17299
17447
  }
17300
-
17301
- /**
17302
- * Frees a renderer R3F built. WebGLRenderer.dispose() releases programs and caches but keeps its
17303
- * context until garbage collection, and browsers cap live WebGL contexts, so the context is lost
17304
- * after it. A WebGPURenderer (from a factory) releases its device and context inside dispose().
17305
- */
17306
- function disposeRenderer(gl) {
17307
- // Avoid starting initialization through three's dispose(). A factory must await init()
17308
- // before returning its renderer so readiness includes initialization and teardown can wait.
17309
- if ((gl.hasInitialized == null ? void 0 : gl.hasInitialized()) === false) return;
17310
- const disposed = attempt(() => {
17311
- var _gl$renderLists;
17312
- return gl.dispose ? gl.dispose() : (_gl$renderLists = gl.renderLists) == null ? void 0 : _gl$renderLists.dispose == null ? void 0 : _gl$renderLists.dispose();
17313
- });
17314
- // WebGPURenderer.dispose() is async from three r186
17315
- if (isPromiseLike(disposed)) {
17316
- return Promise.resolve(disposed).catch(error => console.warn('[R3F] Error disposing renderer', error)).then(() => attempt(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss()));
17317
- }
17318
- attempt(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
17319
- }
17320
17448
  function attempt(step) {
17321
17449
  try {
17322
17450
  return step();
@@ -17349,21 +17477,11 @@ function unmountComponentAtNode(canvas, callback) {
17349
17477
  state.internal.active = false;
17350
17478
 
17351
17479
  // Release what uses the renderer before the renderer itself
17480
+ attempt(() => root.unwatchDpr == null ? void 0 : root.unwatchDpr());
17352
17481
  attempt(() => state.events.disconnect == null ? void 0 : state.events.disconnect());
17353
17482
  if ((_state$gl = state.gl) != null && _state$gl.xr) attempt(() => state.xr.disconnect());
17354
17483
  if (state.scene) attempt(() => dispose(state.scene));
17355
- const gl = state.gl;
17356
- let disposal = undefined;
17357
- if (gl && root.ownsRenderer) {
17358
- disposal = attempt(() => disposeRenderer(gl));
17359
- } else if (gl) {
17360
- // A renderer passed in keeps its 9.x teardown: its context is lost but it is not disposed
17361
- attempt(() => {
17362
- var _gl$renderLists2;
17363
- return (_gl$renderLists2 = gl.renderLists) == null ? void 0 : _gl$renderLists2.dispose == null ? void 0 : _gl$renderLists2.dispose();
17364
- });
17365
- attempt(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
17366
- }
17484
+ const disposal = attempt(() => releaseRenderer(state.gl, root.ownsRenderer));
17367
17485
  if (isPromiseLike(disposal)) disposal.then(() => callback == null ? void 0 : callback(canvas));else callback == null ? void 0 : callback(canvas);
17368
17486
  };
17369
17487
  reconciler.updateContainer(null, root.fiber, null, () => {
@@ -17533,93 +17651,132 @@ function createPortal(children, container, state) {
17533
17651
  state: state
17534
17652
  });
17535
17653
  }
17654
+ function initializePortal(options, initialState) {
17655
+ const {
17656
+ parentStore,
17657
+ container
17658
+ } = options;
17659
+ const portalStore = traditional.createWithEqualityFn((set, get) => ({
17660
+ ...initialState,
17661
+ set,
17662
+ get,
17663
+ // Create once with the store's setter, without retaining a state snapshot.
17664
+ setEvents: events => set(state => ({
17665
+ ...state,
17666
+ events: {
17667
+ ...state.events,
17668
+ ...events
17669
+ }
17670
+ }))
17671
+ }));
17672
+ const parentStateRef = {
17673
+ current: parentStore.getState()
17674
+ };
17675
+ portalStore.setState(portalState => inject(parentStateRef.current, portalState, options));
17676
+ return {
17677
+ parentStore,
17678
+ container,
17679
+ portalStore,
17680
+ parentStateRef
17681
+ };
17682
+ }
17536
17683
  function Portal({
17537
17684
  state = {},
17538
17685
  children,
17539
17686
  container
17540
17687
  }) {
17541
- /** This has to be a component because it would not be able to call useThree/useStore otherwise since
17542
- * if this is our environment, then we are not in r3f's renderer but in react-dom, it would trigger
17543
- * the "R3F hooks can only be used within the Canvas component!" warning:
17544
- * <Canvas>
17545
- * {createPortal(...)} */
17688
+ const parentStore = useStore();
17546
17689
  const {
17547
17690
  events,
17548
17691
  size,
17549
17692
  ...rest
17550
17693
  } = state;
17551
- const previousRoot = useStore();
17552
17694
  const [raycaster] = React__namespace.useState(() => new THREE__namespace.Raycaster());
17553
17695
  const [pointer] = React__namespace.useState(() => new THREE__namespace.Vector2());
17554
- const inject = useMutableCallback((rootState, injectState) => {
17555
- let viewport = undefined;
17556
- if (injectState.camera && size) {
17557
- const camera = injectState.camera;
17558
- // Calculate the override viewport, if present
17559
- viewport = rootState.viewport.getCurrentViewport(camera, new THREE__namespace.Vector3(), size);
17560
- // Update the portal camera, if it differs from the previous layer
17561
- if (camera !== rootState.camera) updateCamera(camera, size);
17562
- }
17563
- return {
17564
- // The intersect consists of the previous root state
17565
- ...rootState,
17566
- ...injectState,
17567
- // Portals have their own scene, which forms the root, a raycaster and a pointer
17568
- scene: container,
17569
- raycaster,
17570
- pointer,
17571
- mouse: pointer,
17572
- // Their previous root is the layer before it
17573
- previousRoot,
17574
- // Events, size and viewport can be overridden by the inject layer
17575
- events: {
17576
- ...rootState.events,
17577
- ...injectState.events,
17578
- ...events
17579
- },
17580
- size: {
17581
- ...rootState.size,
17582
- ...size
17583
- },
17584
- viewport: {
17585
- ...rootState.viewport,
17586
- ...viewport
17587
- },
17588
- // Layers are allowed to override events
17589
- setEvents: events => injectState.set(state => ({
17590
- ...state,
17591
- events: {
17592
- ...state.events,
17593
- ...events
17594
- }
17595
- }))
17696
+ const options = {
17697
+ parentStore,
17698
+ container,
17699
+ raycaster,
17700
+ pointer,
17701
+ events,
17702
+ size
17703
+ };
17704
+ const injectRef = useMutableCallback((parentState, portalState) => inject(parentState, portalState, options));
17705
+
17706
+ // Initialize during render. Keep the store in state so Activity can preserve it.
17707
+ const [portal, setPortal] = React__namespace.useState(() => initializePortal(options, rest));
17708
+ if (portal.parentStore !== parentStore || portal.container !== container) {
17709
+ setPortal(initializePortal(options, rest));
17710
+ }
17711
+ const {
17712
+ portalStore,
17713
+ parentStateRef
17714
+ } = portal;
17715
+
17716
+ // Subscribe after commit and update the portal on each parent change.
17717
+ useIsomorphicLayoutEffect(() => {
17718
+ const update = parentState => {
17719
+ parentStateRef.current = parentState;
17720
+ portalStore.setState(portalState => injectRef.current(parentState, portalState));
17596
17721
  };
17597
- });
17598
- const usePortalStore = React__namespace.useMemo(() => {
17599
- // Create a mirrored store, based on the previous root with a few overrides ...
17600
- const store = traditional.createWithEqualityFn((set, get) => ({
17601
- ...rest,
17602
- set,
17603
- get
17604
- }));
17722
+ const unsubscribe = parentStore.subscribe(update);
17605
17723
 
17606
- // Subscribe to previous root-state and copy changes over to the mirrored portal-state
17607
- const onMutate = prev => store.setState(state => inject.current(prev, state));
17608
- onMutate(previousRoot.getState());
17609
- previousRoot.subscribe(onMutate);
17610
- return store;
17611
- // eslint-disable-next-line react-hooks/exhaustive-deps
17612
- }, [previousRoot, container]);
17613
- return (
17614
- /*#__PURE__*/
17615
- // @ts-ignore, reconciler types are not maintained
17616
- jsxRuntime.jsx(jsxRuntime.Fragment, {
17617
- children: reconciler.createPortal( /*#__PURE__*/jsxRuntime.jsx(context.Provider, {
17618
- value: usePortalStore,
17619
- children: children
17620
- }), usePortalStore, null)
17621
- })
17622
- );
17724
+ // Catch up on changes made before subscribing or while Activity was hidden.
17725
+ const parentState = parentStore.getState();
17726
+ if (parentState !== parentStateRef.current) update(parentState);
17727
+
17728
+ // Clean up when hidden, unmounted or switching stores.
17729
+ return unsubscribe;
17730
+ }, [parentStore, portalStore, parentStateRef]);
17731
+ return /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {
17732
+ children: reconciler.createPortal( /*#__PURE__*/jsxRuntime.jsx(context.Provider, {
17733
+ value: portalStore,
17734
+ children: children
17735
+ }), portalStore, null)
17736
+ });
17737
+ }
17738
+ function inject(parentState, portalState, options) {
17739
+ const {
17740
+ parentStore,
17741
+ container,
17742
+ raycaster,
17743
+ pointer,
17744
+ events,
17745
+ size
17746
+ } = options;
17747
+ let viewport = undefined;
17748
+ if (portalState.camera && size) {
17749
+ const camera = portalState.camera;
17750
+ // Calculate the override viewport, if present
17751
+ viewport = parentState.viewport.getCurrentViewport(camera, new THREE__namespace.Vector3(), size);
17752
+ // Update the portal camera, if it differs from the previous layer
17753
+ if (camera !== parentState.camera) updateCamera(camera, size);
17754
+ }
17755
+ return {
17756
+ // Inherit parent values and preserve the portal's own state.
17757
+ ...parentState,
17758
+ ...portalState,
17759
+ // Portals have their own scene, raycaster and pointer.
17760
+ scene: container,
17761
+ raycaster,
17762
+ pointer,
17763
+ mouse: pointer,
17764
+ previousRoot: parentStore,
17765
+ // Apply the portal's event, size and viewport overrides.
17766
+ events: {
17767
+ ...parentState.events,
17768
+ ...portalState.events,
17769
+ ...events
17770
+ },
17771
+ size: {
17772
+ ...parentState.size,
17773
+ ...size
17774
+ },
17775
+ viewport: {
17776
+ ...parentState.viewport,
17777
+ ...viewport
17778
+ }
17779
+ };
17623
17780
  }
17624
17781
 
17625
17782
  const DOM_EVENTS = {