@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.
@@ -4,21 +4,13 @@ import * as THREE from 'three';
4
4
  import { jsx, Fragment } from 'react/jsx-runtime';
5
5
  import { createWithEqualityFn } from 'zustand/traditional';
6
6
  import { suspend, preload, clear } from 'suspend-react';
7
- import w1, { unstable_getCurrentPriorityLevel, unstable_IdlePriority, unstable_LowPriority, unstable_NormalPriority, unstable_UserBlockingPriority, unstable_ImmediatePriority, unstable_scheduleCallback } from 'scheduler';
8
7
  import { useFiber, useContextBridge, traverseFiber, useActivityBridge as useActivityBridge$1 } from 'its-fine';
8
+ import w1, { unstable_getCurrentPriorityLevel, unstable_IdlePriority, unstable_LowPriority, unstable_NormalPriority, unstable_UserBlockingPriority, unstable_ImmediatePriority, unstable_scheduleCallback } from 'scheduler';
9
9
 
10
10
  var threeTypes = /*#__PURE__*/Object.freeze({
11
11
  __proto__: null
12
12
  });
13
13
 
14
- /**
15
- * Returns the instance's initial (outmost) root.
16
- */
17
- function findInitialRoot(instance) {
18
- let root = instance.root;
19
- while (root.getState().previousRoot) root = root.getState().previousRoot;
20
- return root;
21
- }
22
14
  /**
23
15
  * Safely flush async effects when testing, simulating a legacy root.
24
16
  * @deprecated Import from React instead. import { act } from 'react'
@@ -26,9 +18,6 @@ function findInitialRoot(instance) {
26
18
  // Reference with computed key to break Webpack static analysis
27
19
  // https://github.com/webpack/webpack/issues/14814
28
20
  const act = React['act' + ''];
29
- const isOrthographicCamera = def => def && def.isOrthographicCamera;
30
- const isRef = obj => obj && obj.hasOwnProperty('current');
31
- const isColorRepresentation = value => value != null && (typeof value === 'string' || typeof value === 'number' || value.isColor);
32
21
 
33
22
  /**
34
23
  * An SSR-friendly useLayoutEffect.
@@ -45,7 +34,6 @@ function useMutableCallback(fn) {
45
34
  useIsomorphicLayoutEffect(() => void (ref.current = fn), [fn]);
46
35
  return ref;
47
36
  }
48
- const noop = () => {};
49
37
  function Gate({
50
38
  promise,
51
39
  onSettled
@@ -60,7 +48,7 @@ function useGate() {
60
48
  const [promise, setPromise] = React.useState(null);
61
49
  const onSettled = React.useCallback(() => setPromise(null), []);
62
50
  // Keep the first promise, or a repeated call would loop
63
- const waitFor = React.useCallback(next => setPromise(current => current != null ? current : Promise.resolve(next).then(noop, noop)), []);
51
+ const waitFor = React.useCallback(next => setPromise(current => current != null ? current : Promise.resolve(next).then(() => {}, () => {})), []);
64
52
  const gate = promise ? /*#__PURE__*/jsx(React.Suspense, {
65
53
  fallback: null,
66
54
  children: /*#__PURE__*/jsx(Gate, {
@@ -121,6 +109,19 @@ const ErrorBoundary = /* @__PURE__ */(_ErrorBoundary => (_ErrorBoundary = class
121
109
  }, _ErrorBoundary.getDerivedStateFromError = () => ({
122
110
  error: true
123
111
  }), _ErrorBoundary))();
112
+
113
+ /**
114
+ * Returns the instance's initial (outmost) root.
115
+ */
116
+ function findInitialRoot(instance) {
117
+ let root = instance.root;
118
+ while (root.getState().previousRoot) root = root.getState().previousRoot;
119
+ return root;
120
+ }
121
+ const isOrthographicCamera = def => def && def.isOrthographicCamera;
122
+ const isRef = obj => obj && obj.hasOwnProperty('current');
123
+ const isColorRepresentation = value => value != null && (typeof value === 'string' || typeof value === 'number' || value.isColor);
124
+ const noop = () => {};
124
125
  function calculateDpr(dpr) {
125
126
  var _window$devicePixelRa;
126
127
  // Err on the side of progress by assuming 2x dpr if we can't detect it
@@ -129,6 +130,43 @@ function calculateDpr(dpr) {
129
130
  return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
130
131
  }
131
132
 
133
+ /**
134
+ * Calls `onChange` whenever window.devicePixelRatio changes: moving to another display or zooming.
135
+ * Neither resizes the canvas, so resize observers won't see it. A resolution query only matches the
136
+ * current ratio, so it is re-created after every change. Returns the unsubscribe function.
137
+ */
138
+ function watchDpr(onChange) {
139
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function' || !window.devicePixelRatio) {
140
+ return () => {};
141
+ }
142
+
143
+ // Partial matchMedia polyfills (test setups, older environments) may return no listener methods,
144
+ // or throw on a query they can't parse. Following the ratio is best-effort: it runs inside
145
+ // configuration, which must not fail because of it
146
+ let query;
147
+ const listen = () => {
148
+ try {
149
+ var _query, _query2;
150
+ query = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
151
+ 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);
152
+ } catch {
153
+ query = undefined;
154
+ }
155
+ };
156
+ const unlisten = () => {
157
+ var _query3, _query4;
158
+ 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);
159
+ query = undefined;
160
+ };
161
+ const handleChange = () => {
162
+ unlisten();
163
+ listen();
164
+ onChange();
165
+ };
166
+ listen();
167
+ return unlisten;
168
+ }
169
+
132
170
  /**
133
171
  * Returns instance root state
134
172
  */
@@ -578,11 +616,24 @@ function swapInteractivity(store, object, newObject) {
578
616
  internal.hovered.set(makeId(next), next);
579
617
  }
580
618
  });
619
+
620
+ // A capture is keyed by its eventObject, but it also carries the intersection that is replayed into
621
+ // every subsequent event. Both must follow the new instance, or the capture resolves to the discarded object.
581
622
  internal.capturedMap.forEach(captures => {
582
- const captureData = captures.get(object);
583
- if (captureData) {
584
- captures.delete(object);
585
- captures.set(newObject, captureData);
623
+ for (const [key, captureData] of Array.from(captures)) {
624
+ const {
625
+ intersection
626
+ } = captureData;
627
+ if (key !== object && intersection.object !== object && intersection.eventObject !== object) continue;
628
+ if (key === object) captures.delete(key);
629
+ captures.set(key === object ? newObject : key, {
630
+ ...captureData,
631
+ intersection: {
632
+ ...intersection,
633
+ object: intersection.object === object ? newObject : intersection.object,
634
+ eventObject: intersection.eventObject === object ? newObject : intersection.eventObject
635
+ }
636
+ });
586
637
  }
587
638
  });
588
639
  }
@@ -981,7 +1032,109 @@ function createEvents(store) {
981
1032
  };
982
1033
  }
983
1034
 
1035
+ const isPromiseLike = value => typeof (value == null ? void 0 : value.then) === 'function';
1036
+
1037
+ /** A promise tagged with its state, the protocol React's `use` reads */
1038
+
1039
+ const fulfilled = value => Object.assign(Promise.resolve(value), {
1040
+ status: 'fulfilled',
1041
+ value
1042
+ });
1043
+ const rejected = reason => {
1044
+ const promise = Promise.reject(reason);
1045
+ promise.catch(() => {}); // the reason is read from `status`
1046
+ return Object.assign(promise, {
1047
+ status: 'rejected',
1048
+ reason
1049
+ });
1050
+ };
1051
+
1052
+ /** A pending promise, settled once from outside. Its `status` updates the moment it settles */
1053
+ const deferred = () => {
1054
+ let resolve;
1055
+ let reject;
1056
+ const promise = new Promise((yes, no) => {
1057
+ resolve = value => {
1058
+ Object.assign(promise, {
1059
+ status: 'fulfilled',
1060
+ value
1061
+ });
1062
+ yes(value);
1063
+ };
1064
+ reject = reason => {
1065
+ Object.assign(promise, {
1066
+ status: 'rejected',
1067
+ reason
1068
+ });
1069
+ no(reason);
1070
+ };
1071
+ });
1072
+ promise.status = 'pending';
1073
+ promise.catch(() => {}); // the reason is read from `status`
1074
+ return {
1075
+ promise,
1076
+ resolve,
1077
+ reject
1078
+ };
1079
+ };
1080
+
1081
+ /** Renderer creation and resource release. Root lifetime decides when release is safe. */
984
1082
  const isRenderer = def => !!(def != null && def.render);
1083
+
1084
+ /** The renderer to configure, or a promise for one from an async factory */
1085
+ function createRenderer(canvas, gl) {
1086
+ const defaults = {
1087
+ canvas,
1088
+ powerPreference: 'high-performance',
1089
+ antialias: true,
1090
+ alpha: true
1091
+ };
1092
+ if (typeof gl === 'function') return gl(defaults);
1093
+ if (isRenderer(gl)) return gl;
1094
+ return new THREE.WebGLRenderer({
1095
+ ...defaults,
1096
+ ...gl
1097
+ });
1098
+ }
1099
+
1100
+ /**
1101
+ * Frees a renderer R3F built. WebGLRenderer.dispose() releases programs and caches but keeps its
1102
+ * context until garbage collection, and browsers cap live WebGL contexts, so the context is lost
1103
+ * after it. A WebGPURenderer (from a factory) releases its device and context inside dispose().
1104
+ */
1105
+ function disposeRenderer(gl) {
1106
+ // Avoid starting initialization through three's dispose(). A factory must await init()
1107
+ // before returning its renderer so readiness includes initialization and teardown can wait.
1108
+ if ((gl.hasInitialized == null ? void 0 : gl.hasInitialized()) === false) return;
1109
+ const disposed = attempt$1(() => {
1110
+ var _gl$renderLists;
1111
+ return gl.dispose ? gl.dispose() : (_gl$renderLists = gl.renderLists) == null ? void 0 : _gl$renderLists.dispose == null ? void 0 : _gl$renderLists.dispose();
1112
+ });
1113
+ // WebGPURenderer.dispose() is async from three r186
1114
+ if (isPromiseLike(disposed)) {
1115
+ return Promise.resolve(disposed).catch(error => console.warn('[R3F] Error disposing renderer', error)).then(() => attempt$1(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss()));
1116
+ }
1117
+ attempt$1(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
1118
+ }
1119
+
1120
+ /** Owned renderers are disposed. Caller-provided renderers release caches and their context. */
1121
+ function releaseRenderer(gl, owned) {
1122
+ if (!gl) return;
1123
+ if (owned) return disposeRenderer(gl);
1124
+ attempt$1(() => {
1125
+ var _gl$renderLists2;
1126
+ return (_gl$renderLists2 = gl.renderLists) == null ? void 0 : _gl$renderLists2.dispose == null ? void 0 : _gl$renderLists2.dispose();
1127
+ });
1128
+ attempt$1(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
1129
+ }
1130
+ function attempt$1(step) {
1131
+ try {
1132
+ return step();
1133
+ } catch (error) {
1134
+ console.warn('[R3F] Error while unmounting root; teardown may be incomplete:', error);
1135
+ }
1136
+ }
1137
+
985
1138
  const context = /* @__PURE__ */React.createContext(null);
986
1139
  const createStore = (invalidate, advance) => {
987
1140
  const rootStore = createWithEqualityFn((set, get) => {
@@ -1276,7 +1429,7 @@ function useGraph(object) {
1276
1429
  return React.useMemo(() => buildGraph(object), [object]);
1277
1430
  }
1278
1431
  const memoizedLoaders = new WeakMap();
1279
- const isConstructor$1 = value => {
1432
+ const isConstructor = value => {
1280
1433
  var _value$prototype;
1281
1434
  return typeof value === 'function' && (value == null ? void 0 : (_value$prototype = value.prototype) == null ? void 0 : _value$prototype.constructor) === value;
1282
1435
  };
@@ -1285,7 +1438,7 @@ function loadingFn(extensions, onProgress) {
1285
1438
  let loader;
1286
1439
 
1287
1440
  // Construct and cache loader if constructor was passed
1288
- if (isConstructor$1(Proto)) {
1441
+ if (isConstructor(Proto)) {
1289
1442
  loader = memoizedLoaders.get(Proto);
1290
1443
  if (!loader) {
1291
1444
  loader = new Proto();
@@ -1360,22 +1513,6 @@ useLoader.clear = function (loader, input) {
1360
1513
  const shallow = {
1361
1514
  objects: 'shallow'
1362
1515
  };
1363
-
1364
- /** The renderer to configure, or a promise for one from an async factory */
1365
- function createRenderer(canvas, gl) {
1366
- const defaults = {
1367
- canvas,
1368
- powerPreference: 'high-performance',
1369
- antialias: true,
1370
- alpha: true
1371
- };
1372
- if (typeof gl === 'function') return gl(defaults);
1373
- if (isRenderer(gl)) return gl;
1374
- return new THREE.WebGLRenderer({
1375
- ...defaults,
1376
- ...gl
1377
- });
1378
- }
1379
1516
  function computeInitialSize(canvas, size) {
1380
1517
  if (!size && typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1381
1518
  const {
@@ -1623,55 +1760,43 @@ function applyRootConfiguration({
1623
1760
  configuration.previous = next;
1624
1761
  }
1625
1762
 
1626
- const isPromiseLike = value => typeof (value == null ? void 0 : value.then) === 'function';
1627
-
1628
- /** A promise tagged with its state, the protocol React's `use` reads */
1629
-
1630
- const fulfilled = value => Object.assign(Promise.resolve(value), {
1631
- status: 'fulfilled',
1632
- value
1633
- });
1634
- const rejected = reason => {
1635
- const promise = Promise.reject(reason);
1636
- promise.catch(() => {}); // the reason is read from `status`
1637
- return Object.assign(promise, {
1638
- status: 'rejected',
1639
- reason
1640
- });
1641
- };
1642
-
1643
- /** A pending promise, settled once from outside. Its `status` updates the moment it settles */
1644
- const deferred = () => {
1645
- let resolve;
1646
- let reject;
1647
- const promise = new Promise((yes, no) => {
1648
- resolve = value => {
1649
- Object.assign(promise, {
1650
- status: 'fulfilled',
1651
- value
1652
- });
1653
- yes(value);
1654
- };
1655
- reject = reason => {
1656
- Object.assign(promise, {
1657
- status: 'rejected',
1658
- reason
1659
- });
1660
- no(reason);
1661
- };
1662
- });
1663
- promise.status = 'pending';
1664
- promise.catch(() => {}); // the reason is read from `status`
1665
- return {
1666
- promise,
1667
- resolve,
1668
- reject
1763
+ /**
1764
+ * Moving to another display or zooming changes window.devicePixelRatio without resizing the canvas,
1765
+ * so nothing configures the root again. Resolves the ratio the way configuration does, from the last
1766
+ * applied dpr. Returns the function that stops following.
1767
+ */
1768
+ function followDpr(root) {
1769
+ const xr = () => {
1770
+ var _root$store$getState$;
1771
+ return (_root$store$getState$ = root.store.getState().gl) == null ? void 0 : _root$store$getState$.xr;
1669
1772
  };
1670
- };
1773
+ const follow = () => {
1774
+ var _xr, _xr2, _xr3, _last$dpr;
1775
+ const state = root.store.getState();
1776
+ // three doesn't resize during XR and restores its own pixel ratio once the session ends. It holds
1777
+ // the session before it sets isPresenting. Listeners are deduplicated, so this waits once
1778
+ if ((_xr = xr()) != null && _xr.isPresenting || (_xr2 = xr()) != null && _xr2.getSession != null && _xr2.getSession()) {
1779
+ xr().addEventListener('sessionend', follow);
1780
+ return;
1781
+ }
1782
+ (_xr3 = xr()) == null ? void 0 : _xr3.removeEventListener == null ? void 0 : _xr3.removeEventListener('sessionend', follow);
1783
+ // A failed configuration applies everything on the next one
1784
+ const last = root.configuration.previous;
1785
+ if (!last) return;
1786
+ const dpr = (_last$dpr = last.dpr) != null ? _last$dpr : [1, 2];
1787
+ if (state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
1788
+ };
1789
+ const unwatch = watchDpr(follow);
1790
+ return () => {
1791
+ var _xr4;
1792
+ unwatch();
1793
+ (_xr4 = xr()) == null ? void 0 : _xr4.removeEventListener == null ? void 0 : _xr4.removeEventListener('sessionend', follow);
1794
+ };
1795
+ }
1671
1796
 
1672
1797
  var packageData = {
1673
1798
  name: "@react-three/fiber",
1674
- version: "9.8.1",
1799
+ version: "9.8.2",
1675
1800
  description: "A React renderer for Threejs",
1676
1801
  keywords: [
1677
1802
  "react",
@@ -1703,6 +1828,12 @@ var packageData = {
1703
1828
  module: "dist/react-three-fiber.esm.js",
1704
1829
  types: "dist/react-three-fiber.cjs.d.ts",
1705
1830
  "react-native": "native/dist/react-three-fiber-native.cjs.js",
1831
+ files: [
1832
+ "dist",
1833
+ "native",
1834
+ "react-reconciler",
1835
+ "CHANGELOG.md"
1836
+ ],
1706
1837
  sideEffects: false,
1707
1838
  preconstruct: {
1708
1839
  entrypoints: [
@@ -16443,6 +16574,27 @@ function G1() {
16443
16574
  var J1 = G1();
16444
16575
  const Z1 = $1(J1);
16445
16576
 
16577
+ /** Constructor registration and lookup for JSX elements. */
16578
+
16579
+ const catalogue = {};
16580
+ let i = 0;
16581
+ const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
16582
+ function hasConstructor(name) {
16583
+ return name in catalogue;
16584
+ }
16585
+ function resolveConstructor(name) {
16586
+ return catalogue[name];
16587
+ }
16588
+ function extend(objects) {
16589
+ if (typeof objects === 'function') {
16590
+ const Component = `${i++}`;
16591
+ catalogue[Component] = objects;
16592
+ return Component;
16593
+ } else {
16594
+ Object.assign(catalogue, objects);
16595
+ }
16596
+ }
16597
+
16446
16598
  function createReconciler(config) {
16447
16599
  const reconciler = Z1(config);
16448
16600
 
@@ -16456,24 +16608,11 @@ const NoEventPriority = 0;
16456
16608
  // https://github.com/pmndrs/react-three-fiber/pull/2931
16457
16609
  // https://github.com/microsoft/TypeScript/issues/37079
16458
16610
 
16459
- const catalogue = {};
16460
16611
  const PREFIX_REGEX = /^three(?=[A-Z])/;
16461
- const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
16462
- let i = 0;
16463
- const isConstructor = object => typeof object === 'function';
16464
- function extend(objects) {
16465
- if (isConstructor(objects)) {
16466
- const Component = `${i++}`;
16467
- catalogue[Component] = objects;
16468
- return Component;
16469
- } else {
16470
- Object.assign(catalogue, objects);
16471
- }
16472
- }
16473
16612
  function validateInstance(type, props) {
16474
16613
  // Get target from catalogue
16475
16614
  const name = toPascalCase(type);
16476
- const target = catalogue[name];
16615
+ const target = resolveConstructor(name);
16477
16616
 
16478
16617
  // Validate element target
16479
16618
  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`);
@@ -16487,7 +16626,7 @@ function validateInstance(type, props) {
16487
16626
  function createInstance(type, props, root) {
16488
16627
  var _props$object;
16489
16628
  // Remove three* prefix from elements if native element not present
16490
- type = toPascalCase(type) in catalogue ? type : type.replace(PREFIX_REGEX, '');
16629
+ type = hasConstructor(toPascalCase(type)) ? type : type.replace(PREFIX_REGEX, '');
16491
16630
  validateInstance(type, props);
16492
16631
 
16493
16632
  // Regenerate the R3F instance for primitives to simulate a new object
@@ -16534,7 +16673,7 @@ function handleContainerEffects(parent, child, beforeChild) {
16534
16673
  if (!child.object) {
16535
16674
  var _child$props$object, _child$props$args;
16536
16675
  // Get target from catalogue
16537
- const target = catalogue[toPascalCase(child.type)];
16676
+ const target = resolveConstructor(toPascalCase(child.type));
16538
16677
 
16539
16678
  // Create object
16540
16679
  child.object = (_child$props$object = child.props.object) != null ? _child$props$object : new target(...((_child$props$args = child.props.args) != null ? _child$props$args : []));
@@ -16631,6 +16770,9 @@ function disposeOnIdle(object) {
16631
16770
  function removeChild(parent, child, dispose) {
16632
16771
  if (!child) return;
16633
16772
 
16773
+ // Unlinking clears the parent, which invalidateInstance checks, so remember it
16774
+ const wasLinked = !!child.parent;
16775
+
16634
16776
  // Unlink instances
16635
16777
  child.parent = null;
16636
16778
  const childIndex = parent.children.indexOf(child);
@@ -16668,7 +16810,11 @@ function removeChild(parent, child, dispose) {
16668
16810
  }
16669
16811
 
16670
16812
  // Tree was updated, request a frame for top-level instance
16671
- if (dispose === undefined) invalidateInstance(child);
16813
+ if (dispose === undefined && wasLinked) {
16814
+ var _child$root;
16815
+ const state = (_child$root = child.root) == null ? void 0 : _child$root.getState == null ? void 0 : _child$root.getState();
16816
+ if (state && state.internal.frames === 0) state.invalidate();
16817
+ }
16672
16818
  }
16673
16819
  function setFiberRef(fiber, publicInstance) {
16674
16820
  for (const _fiber of [fiber, fiber.alternate]) {
@@ -16729,7 +16875,7 @@ function swapReconstructedInstances() {
16729
16875
  if (parent) {
16730
16876
  var _instance$props$objec, _instance$props$args;
16731
16877
  // Get target from catalogue
16732
- const target = catalogue[toPascalCase(instance.type)];
16878
+ const target = resolveConstructor(toPascalCase(instance.type));
16733
16879
 
16734
16880
  // Create object
16735
16881
  const prevObject = instance.object;
@@ -17188,6 +17334,8 @@ function createRoot(canvas) {
17188
17334
  });
17189
17335
  }
17190
17336
  applyRootConfiguration(root, canvas, props);
17337
+ // Only a range, including the default one, follows the display: a fixed dpr never touches matchMedia
17338
+ if (!root.unwatchDpr && (props.dpr === undefined || Array.isArray(props.dpr))) root.unwatchDpr = followDpr(root);
17191
17339
  resolve(this);
17192
17340
  };
17193
17341
  const run = () => {
@@ -17272,26 +17420,6 @@ function Provider({
17272
17420
  children: children
17273
17421
  });
17274
17422
  }
17275
-
17276
- /**
17277
- * Frees a renderer R3F built. WebGLRenderer.dispose() releases programs and caches but keeps its
17278
- * context until garbage collection, and browsers cap live WebGL contexts, so the context is lost
17279
- * after it. A WebGPURenderer (from a factory) releases its device and context inside dispose().
17280
- */
17281
- function disposeRenderer(gl) {
17282
- // Avoid starting initialization through three's dispose(). A factory must await init()
17283
- // before returning its renderer so readiness includes initialization and teardown can wait.
17284
- if ((gl.hasInitialized == null ? void 0 : gl.hasInitialized()) === false) return;
17285
- const disposed = attempt(() => {
17286
- var _gl$renderLists;
17287
- return gl.dispose ? gl.dispose() : (_gl$renderLists = gl.renderLists) == null ? void 0 : _gl$renderLists.dispose == null ? void 0 : _gl$renderLists.dispose();
17288
- });
17289
- // WebGPURenderer.dispose() is async from three r186
17290
- if (isPromiseLike(disposed)) {
17291
- return Promise.resolve(disposed).catch(error => console.warn('[R3F] Error disposing renderer', error)).then(() => attempt(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss()));
17292
- }
17293
- attempt(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
17294
- }
17295
17423
  function attempt(step) {
17296
17424
  try {
17297
17425
  return step();
@@ -17324,21 +17452,11 @@ function unmountComponentAtNode(canvas, callback) {
17324
17452
  state.internal.active = false;
17325
17453
 
17326
17454
  // Release what uses the renderer before the renderer itself
17455
+ attempt(() => root.unwatchDpr == null ? void 0 : root.unwatchDpr());
17327
17456
  attempt(() => state.events.disconnect == null ? void 0 : state.events.disconnect());
17328
17457
  if ((_state$gl = state.gl) != null && _state$gl.xr) attempt(() => state.xr.disconnect());
17329
17458
  if (state.scene) attempt(() => dispose(state.scene));
17330
- const gl = state.gl;
17331
- let disposal = undefined;
17332
- if (gl && root.ownsRenderer) {
17333
- disposal = attempt(() => disposeRenderer(gl));
17334
- } else if (gl) {
17335
- // A renderer passed in keeps its 9.x teardown: its context is lost but it is not disposed
17336
- attempt(() => {
17337
- var _gl$renderLists2;
17338
- return (_gl$renderLists2 = gl.renderLists) == null ? void 0 : _gl$renderLists2.dispose == null ? void 0 : _gl$renderLists2.dispose();
17339
- });
17340
- attempt(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
17341
- }
17459
+ const disposal = attempt(() => releaseRenderer(state.gl, root.ownsRenderer));
17342
17460
  if (isPromiseLike(disposal)) disposal.then(() => callback == null ? void 0 : callback(canvas));else callback == null ? void 0 : callback(canvas);
17343
17461
  };
17344
17462
  reconciler.updateContainer(null, root.fiber, null, () => {
@@ -17508,93 +17626,132 @@ function createPortal(children, container, state) {
17508
17626
  state: state
17509
17627
  });
17510
17628
  }
17629
+ function initializePortal(options, initialState) {
17630
+ const {
17631
+ parentStore,
17632
+ container
17633
+ } = options;
17634
+ const portalStore = createWithEqualityFn((set, get) => ({
17635
+ ...initialState,
17636
+ set,
17637
+ get,
17638
+ // Create once with the store's setter, without retaining a state snapshot.
17639
+ setEvents: events => set(state => ({
17640
+ ...state,
17641
+ events: {
17642
+ ...state.events,
17643
+ ...events
17644
+ }
17645
+ }))
17646
+ }));
17647
+ const parentStateRef = {
17648
+ current: parentStore.getState()
17649
+ };
17650
+ portalStore.setState(portalState => inject(parentStateRef.current, portalState, options));
17651
+ return {
17652
+ parentStore,
17653
+ container,
17654
+ portalStore,
17655
+ parentStateRef
17656
+ };
17657
+ }
17511
17658
  function Portal({
17512
17659
  state = {},
17513
17660
  children,
17514
17661
  container
17515
17662
  }) {
17516
- /** This has to be a component because it would not be able to call useThree/useStore otherwise since
17517
- * if this is our environment, then we are not in r3f's renderer but in react-dom, it would trigger
17518
- * the "R3F hooks can only be used within the Canvas component!" warning:
17519
- * <Canvas>
17520
- * {createPortal(...)} */
17663
+ const parentStore = useStore();
17521
17664
  const {
17522
17665
  events,
17523
17666
  size,
17524
17667
  ...rest
17525
17668
  } = state;
17526
- const previousRoot = useStore();
17527
17669
  const [raycaster] = React.useState(() => new THREE.Raycaster());
17528
17670
  const [pointer] = React.useState(() => new THREE.Vector2());
17529
- const inject = useMutableCallback((rootState, injectState) => {
17530
- let viewport = undefined;
17531
- if (injectState.camera && size) {
17532
- const camera = injectState.camera;
17533
- // Calculate the override viewport, if present
17534
- viewport = rootState.viewport.getCurrentViewport(camera, new THREE.Vector3(), size);
17535
- // Update the portal camera, if it differs from the previous layer
17536
- if (camera !== rootState.camera) updateCamera(camera, size);
17537
- }
17538
- return {
17539
- // The intersect consists of the previous root state
17540
- ...rootState,
17541
- ...injectState,
17542
- // Portals have their own scene, which forms the root, a raycaster and a pointer
17543
- scene: container,
17544
- raycaster,
17545
- pointer,
17546
- mouse: pointer,
17547
- // Their previous root is the layer before it
17548
- previousRoot,
17549
- // Events, size and viewport can be overridden by the inject layer
17550
- events: {
17551
- ...rootState.events,
17552
- ...injectState.events,
17553
- ...events
17554
- },
17555
- size: {
17556
- ...rootState.size,
17557
- ...size
17558
- },
17559
- viewport: {
17560
- ...rootState.viewport,
17561
- ...viewport
17562
- },
17563
- // Layers are allowed to override events
17564
- setEvents: events => injectState.set(state => ({
17565
- ...state,
17566
- events: {
17567
- ...state.events,
17568
- ...events
17569
- }
17570
- }))
17671
+ const options = {
17672
+ parentStore,
17673
+ container,
17674
+ raycaster,
17675
+ pointer,
17676
+ events,
17677
+ size
17678
+ };
17679
+ const injectRef = useMutableCallback((parentState, portalState) => inject(parentState, portalState, options));
17680
+
17681
+ // Initialize during render. Keep the store in state so Activity can preserve it.
17682
+ const [portal, setPortal] = React.useState(() => initializePortal(options, rest));
17683
+ if (portal.parentStore !== parentStore || portal.container !== container) {
17684
+ setPortal(initializePortal(options, rest));
17685
+ }
17686
+ const {
17687
+ portalStore,
17688
+ parentStateRef
17689
+ } = portal;
17690
+
17691
+ // Subscribe after commit and update the portal on each parent change.
17692
+ useIsomorphicLayoutEffect(() => {
17693
+ const update = parentState => {
17694
+ parentStateRef.current = parentState;
17695
+ portalStore.setState(portalState => injectRef.current(parentState, portalState));
17571
17696
  };
17572
- });
17573
- const usePortalStore = React.useMemo(() => {
17574
- // Create a mirrored store, based on the previous root with a few overrides ...
17575
- const store = createWithEqualityFn((set, get) => ({
17576
- ...rest,
17577
- set,
17578
- get
17579
- }));
17697
+ const unsubscribe = parentStore.subscribe(update);
17580
17698
 
17581
- // Subscribe to previous root-state and copy changes over to the mirrored portal-state
17582
- const onMutate = prev => store.setState(state => inject.current(prev, state));
17583
- onMutate(previousRoot.getState());
17584
- previousRoot.subscribe(onMutate);
17585
- return store;
17586
- // eslint-disable-next-line react-hooks/exhaustive-deps
17587
- }, [previousRoot, container]);
17588
- return (
17589
- /*#__PURE__*/
17590
- // @ts-ignore, reconciler types are not maintained
17591
- jsx(Fragment, {
17592
- children: reconciler.createPortal( /*#__PURE__*/jsx(context.Provider, {
17593
- value: usePortalStore,
17594
- children: children
17595
- }), usePortalStore, null)
17596
- })
17597
- );
17699
+ // Catch up on changes made before subscribing or while Activity was hidden.
17700
+ const parentState = parentStore.getState();
17701
+ if (parentState !== parentStateRef.current) update(parentState);
17702
+
17703
+ // Clean up when hidden, unmounted or switching stores.
17704
+ return unsubscribe;
17705
+ }, [parentStore, portalStore, parentStateRef]);
17706
+ return /*#__PURE__*/jsx(Fragment, {
17707
+ children: reconciler.createPortal( /*#__PURE__*/jsx(context.Provider, {
17708
+ value: portalStore,
17709
+ children: children
17710
+ }), portalStore, null)
17711
+ });
17712
+ }
17713
+ function inject(parentState, portalState, options) {
17714
+ const {
17715
+ parentStore,
17716
+ container,
17717
+ raycaster,
17718
+ pointer,
17719
+ events,
17720
+ size
17721
+ } = options;
17722
+ let viewport = undefined;
17723
+ if (portalState.camera && size) {
17724
+ const camera = portalState.camera;
17725
+ // Calculate the override viewport, if present
17726
+ viewport = parentState.viewport.getCurrentViewport(camera, new THREE.Vector3(), size);
17727
+ // Update the portal camera, if it differs from the previous layer
17728
+ if (camera !== parentState.camera) updateCamera(camera, size);
17729
+ }
17730
+ return {
17731
+ // Inherit parent values and preserve the portal's own state.
17732
+ ...parentState,
17733
+ ...portalState,
17734
+ // Portals have their own scene, raycaster and pointer.
17735
+ scene: container,
17736
+ raycaster,
17737
+ pointer,
17738
+ mouse: pointer,
17739
+ previousRoot: parentStore,
17740
+ // Apply the portal's event, size and viewport overrides.
17741
+ events: {
17742
+ ...parentState.events,
17743
+ ...portalState.events,
17744
+ ...events
17745
+ },
17746
+ size: {
17747
+ ...parentState.size,
17748
+ ...size
17749
+ },
17750
+ viewport: {
17751
+ ...parentState.viewport,
17752
+ ...viewport
17753
+ }
17754
+ };
17598
17755
  }
17599
17756
 
17600
17757
  const DOM_EVENTS = {