@react-three/fiber 9.8.0 → 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.
@@ -2,11 +2,11 @@
2
2
 
3
3
  var React = require('react');
4
4
  var THREE = require('three');
5
+ var jsxRuntime = require('react/jsx-runtime');
5
6
  var traditional = require('zustand/traditional');
6
7
  var suspendReact = require('suspend-react');
7
- var w1 = require('scheduler');
8
- var jsxRuntime = require('react/jsx-runtime');
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, {
@@ -95,23 +83,29 @@ function useGate() {
95
83
  }) : null;
96
84
  return [gate, waitFor];
97
85
  }
86
+
87
+ // Activity is optional on React 19.0/19.1. A computed key also supports strict ESM bundlers.
88
+ const useActivityBridge = React__namespace['Activity' + ''] ? itsFine.useActivityBridge : () => React__namespace.Fragment;
98
89
  /**
99
- * Bridges renderer Context and StrictMode from a primary renderer.
90
+ * Bridges Context, StrictMode, and Activity visibility from a primary renderer.
100
91
  */
101
92
  function useBridge() {
102
93
  const fiber = itsFine.useFiber();
103
94
  const ContextBridge = itsFine.useContextBridge();
95
+ const ActivityBridge = useActivityBridge();
104
96
  return React__namespace.useMemo(() => ({
105
97
  children
106
98
  }) => {
107
99
  const strict = !!itsFine.traverseFiber(fiber, true, node => node.type === React__namespace.StrictMode);
108
100
  const Root = strict ? React__namespace.StrictMode : React__namespace.Fragment;
109
101
  return /*#__PURE__*/jsxRuntime.jsx(Root, {
110
- children: /*#__PURE__*/jsxRuntime.jsx(ContextBridge, {
111
- children: children
102
+ children: /*#__PURE__*/jsxRuntime.jsx(ActivityBridge, {
103
+ children: /*#__PURE__*/jsxRuntime.jsx(ContextBridge, {
104
+ children: children
105
+ })
112
106
  })
113
107
  });
114
- }, [fiber, ContextBridge]);
108
+ }, [fiber, ContextBridge, ActivityBridge]);
115
109
  }
116
110
  function Block({
117
111
  set
@@ -140,6 +134,19 @@ const ErrorBoundary = /* @__PURE__ */(_ErrorBoundary => (_ErrorBoundary = class
140
134
  }, _ErrorBoundary.getDerivedStateFromError = () => ({
141
135
  error: true
142
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 = () => {};
143
150
  function calculateDpr(dpr) {
144
151
  var _window$devicePixelRa;
145
152
  // Err on the side of progress by assuming 2x dpr if we can't detect it
@@ -148,6 +155,43 @@ function calculateDpr(dpr) {
148
155
  return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
149
156
  }
150
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
+
151
195
  /**
152
196
  * Returns instance root state
153
197
  */
@@ -257,6 +301,7 @@ function prepare(target, root, type, props) {
257
301
  object,
258
302
  eventCount: 0,
259
303
  handlers: {},
304
+ previousVisible: undefined,
260
305
  isHidden: false
261
306
  };
262
307
  if (object) object.__r3f = instance;
@@ -440,6 +485,12 @@ function applyProps(object, props) {
440
485
  // Ignore setting undefined props
441
486
  // https://github.com/pmndrs/react-three-fiber/issues/274
442
487
  if (value === undefined) continue;
488
+
489
+ // Update the visibility to restore without revealing a hidden instance.
490
+ if (prop === 'visible' && instance != null && instance.isHidden && instance.previousVisible !== undefined) {
491
+ instance.previousVisible = value;
492
+ continue;
493
+ }
443
494
  let {
444
495
  root,
445
496
  key,
@@ -590,11 +641,24 @@ function swapInteractivity(store, object, newObject) {
590
641
  internal.hovered.set(makeId(next), next);
591
642
  }
592
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.
593
647
  internal.capturedMap.forEach(captures => {
594
- const captureData = captures.get(object);
595
- if (captureData) {
596
- captures.delete(object);
597
- 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
+ });
598
662
  }
599
663
  });
600
664
  }
@@ -993,7 +1057,109 @@ function createEvents(store) {
993
1057
  };
994
1058
  }
995
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. */
996
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
+
997
1163
  const context = /* @__PURE__ */React__namespace.createContext(null);
998
1164
  const createStore = (invalidate, advance) => {
999
1165
  const rootStore = traditional.createWithEqualityFn((set, get) => {
@@ -1288,7 +1454,7 @@ function useGraph(object) {
1288
1454
  return React__namespace.useMemo(() => buildGraph(object), [object]);
1289
1455
  }
1290
1456
  const memoizedLoaders = new WeakMap();
1291
- const isConstructor$1 = value => {
1457
+ const isConstructor = value => {
1292
1458
  var _value$prototype;
1293
1459
  return typeof value === 'function' && (value == null ? void 0 : (_value$prototype = value.prototype) == null ? void 0 : _value$prototype.constructor) === value;
1294
1460
  };
@@ -1297,7 +1463,7 @@ function loadingFn(extensions, onProgress) {
1297
1463
  let loader;
1298
1464
 
1299
1465
  // Construct and cache loader if constructor was passed
1300
- if (isConstructor$1(Proto)) {
1466
+ if (isConstructor(Proto)) {
1301
1467
  loader = memoizedLoaders.get(Proto);
1302
1468
  if (!loader) {
1303
1469
  loader = new Proto();
@@ -1364,9 +1530,298 @@ useLoader.clear = function (loader, input) {
1364
1530
  e = 2,
1365
1531
  i$1 = 268435456;
1366
1532
 
1533
+ // Shim for OffscreenCanvas since it was removed from DOM types
1534
+ // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/54988
1535
+
1536
+ /** What a root has applied, so its next configuration applies only what changed */
1537
+
1538
+ const shallow = {
1539
+ objects: 'shallow'
1540
+ };
1541
+ function computeInitialSize(canvas, size) {
1542
+ if (!size && typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1543
+ const {
1544
+ width,
1545
+ height,
1546
+ top,
1547
+ left
1548
+ } = canvas.parentElement.getBoundingClientRect();
1549
+ return {
1550
+ width,
1551
+ height,
1552
+ top,
1553
+ left
1554
+ };
1555
+ } else if (!size && typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
1556
+ return {
1557
+ width: canvas.width,
1558
+ height: canvas.height,
1559
+ top: 0,
1560
+ left: 0
1561
+ };
1562
+ }
1563
+ return {
1564
+ width: 0,
1565
+ height: 0,
1566
+ top: 0,
1567
+ left: 0,
1568
+ ...size
1569
+ };
1570
+ }
1571
+
1572
+ /**
1573
+ * Applies the inputs that changed since the last configuration applied in full, to a root whose
1574
+ * renderer is installed. A runtime change, such as setFrameloop or an edit to gl.shadowMap, lasts
1575
+ * until its input changes. The pixel ratio instead follows the device on every call. Objects and
1576
+ * arrays compare shallowly, so an equal inline value is unchanged.
1577
+ */
1578
+ function applyRootConfiguration({
1579
+ store,
1580
+ configuration
1581
+ }, canvas, props) {
1582
+ const {
1583
+ gl: glConfig,
1584
+ scene: sceneOptions,
1585
+ camera: cameraOptions,
1586
+ shadows = false,
1587
+ linear = false,
1588
+ flat = false,
1589
+ legacy = false,
1590
+ frameloop = 'always',
1591
+ dpr = [1, 2],
1592
+ performance,
1593
+ onPointerMissed
1594
+ } = props;
1595
+ const size = computeInitialSize(canvas, props.size);
1596
+ const next = {
1597
+ ...props,
1598
+ shadows,
1599
+ linear,
1600
+ flat,
1601
+ legacy,
1602
+ frameloop,
1603
+ size
1604
+ };
1605
+ const state = store.getState();
1606
+ const gl = state.gl;
1607
+ const last = configuration.previous;
1608
+ const changed = key => !last || !is.equ(next[key], last[key], shallow);
1609
+ // A failure can leave inputs partly applied, so the next configuration applies all of them
1610
+ configuration.previous = undefined;
1611
+
1612
+ // Set up raycaster (one time only!)
1613
+ let raycaster = state.raycaster;
1614
+ if (!raycaster) state.set({
1615
+ raycaster: raycaster = new THREE__namespace.Raycaster()
1616
+ });
1617
+ if (changed('raycaster')) {
1618
+ var _props$raycaster;
1619
+ const {
1620
+ params,
1621
+ ...options
1622
+ } = (_props$raycaster = props.raycaster) != null ? _props$raycaster : {};
1623
+ applyProps(raycaster, {
1624
+ ...options,
1625
+ params: {
1626
+ ...raycaster.params,
1627
+ ...params
1628
+ }
1629
+ });
1630
+ }
1631
+
1632
+ // Create default camera, don't overwrite any user-set state
1633
+ if (!state.camera || state.camera === configuration.camera && changed('camera')) {
1634
+ configuration.camera = cameraOptions;
1635
+ const isCamera = cameraOptions == null ? void 0 : cameraOptions.isCamera;
1636
+ const camera = isCamera ? cameraOptions : props.orthographic ? new THREE__namespace.OrthographicCamera(0, 0, 0, 0, 0.1, 1000) : new THREE__namespace.PerspectiveCamera(75, 0, 0.1, 1000);
1637
+ if (!isCamera) {
1638
+ camera.position.z = 5;
1639
+ if (cameraOptions) {
1640
+ applyProps(camera, cameraOptions);
1641
+ // Preserve user-defined frustum if possible
1642
+ // https://github.com/pmndrs/react-three-fiber/issues/3160
1643
+ if (!camera.manual) {
1644
+ if ('aspect' in cameraOptions || 'left' in cameraOptions || 'right' in cameraOptions || 'bottom' in cameraOptions || 'top' in cameraOptions) {
1645
+ camera.manual = true;
1646
+ camera.updateProjectionMatrix();
1647
+ }
1648
+ }
1649
+ }
1650
+ // Always look at center by default
1651
+ if (!state.camera && !(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
1652
+ }
1653
+ state.set({
1654
+ camera
1655
+ });
1656
+
1657
+ // Configure raycaster
1658
+ // https://github.com/pmndrs/react-xr/issues/300
1659
+ raycaster.camera = camera;
1660
+ }
1661
+
1662
+ // Set up scene (one time only!)
1663
+ if (!state.scene) {
1664
+ let scene;
1665
+ if (sceneOptions != null && sceneOptions.isScene) {
1666
+ scene = sceneOptions;
1667
+ prepare(scene, store, '', {});
1668
+ } else {
1669
+ scene = new THREE__namespace.Scene();
1670
+ prepare(scene, store, '', {});
1671
+ if (sceneOptions) applyProps(scene, sceneOptions);
1672
+ }
1673
+ state.set({
1674
+ scene
1675
+ });
1676
+ }
1677
+
1678
+ // Store events internally
1679
+ if (props.events && !state.events.handlers) state.set({
1680
+ events: props.events(store)
1681
+ });
1682
+ if (changed('size')) state.setSize(size.width, size.height, size.top, size.left);
1683
+ // The ratio follows window.devicePixelRatio, which changes with browser zoom or another display
1684
+ if (state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
1685
+ if (changed('frameloop')) state.setFrameloop(frameloop);
1686
+ if (changed('onPointerMissed')) state.set({
1687
+ onPointerMissed
1688
+ });
1689
+ // Performance options merge, so removing them keeps the current settings
1690
+ if (performance && changed('performance')) {
1691
+ state.set(state => ({
1692
+ performance: {
1693
+ ...state.performance,
1694
+ ...performance
1695
+ }
1696
+ }));
1697
+ }
1698
+
1699
+ // Set up XR (one time only!)
1700
+ if (!state.xr) {
1701
+ var _gl$xr;
1702
+ // Handle frame behavior in WebXR
1703
+ const handleXRFrame = (timestamp, frame) => {
1704
+ const state = store.getState();
1705
+ if (state.frameloop === 'never') return;
1706
+ advance(timestamp, true, state, frame);
1707
+ };
1708
+
1709
+ // Toggle render switching on session
1710
+ const handleSessionChange = () => {
1711
+ const state = store.getState();
1712
+ state.gl.xr.enabled = state.gl.xr.isPresenting;
1713
+ state.gl.xr.setAnimationLoop(state.gl.xr.isPresenting ? handleXRFrame : null);
1714
+ if (!state.gl.xr.isPresenting) invalidate(state);
1715
+ };
1716
+
1717
+ // WebXR session manager
1718
+ const xr = {
1719
+ connect() {
1720
+ const gl = store.getState().gl;
1721
+ gl.xr.addEventListener('sessionstart', handleSessionChange);
1722
+ gl.xr.addEventListener('sessionend', handleSessionChange);
1723
+ },
1724
+ disconnect() {
1725
+ const gl = store.getState().gl;
1726
+ gl.xr.removeEventListener('sessionstart', handleSessionChange);
1727
+ gl.xr.removeEventListener('sessionend', handleSessionChange);
1728
+ }
1729
+ };
1730
+
1731
+ // Subscribe to WebXR session events
1732
+ if (typeof ((_gl$xr = gl.xr) == null ? void 0 : _gl$xr.addEventListener) === 'function') xr.connect();
1733
+ state.set({
1734
+ xr
1735
+ });
1736
+ }
1737
+
1738
+ // Set shadowmap
1739
+ if (gl.shadowMap && changed('shadows')) {
1740
+ const oldEnabled = gl.shadowMap.enabled;
1741
+ const oldType = gl.shadowMap.type;
1742
+ gl.shadowMap.enabled = !!shadows;
1743
+ if (is.boo(shadows)) {
1744
+ gl.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
1745
+ } else if (is.str(shadows)) {
1746
+ var _types$shadows;
1747
+ const types = {
1748
+ basic: THREE__namespace.BasicShadowMap,
1749
+ percentage: THREE__namespace.PCFShadowMap,
1750
+ soft: THREE__namespace.PCFSoftShadowMap,
1751
+ variance: THREE__namespace.VSMShadowMap
1752
+ };
1753
+ gl.shadowMap.type = (_types$shadows = types[shadows]) != null ? _types$shadows : THREE__namespace.PCFSoftShadowMap;
1754
+ } else if (is.obj(shadows)) {
1755
+ Object.assign(gl.shadowMap, shadows);
1756
+ }
1757
+ if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type) gl.shadowMap.needsUpdate = true;
1758
+ }
1759
+
1760
+ // Explicit renderer options take precedence over the color shorthands
1761
+ const glProps = glConfig && !is.fun(glConfig) && !isRenderer(glConfig) ? glConfig : undefined;
1762
+ if (changed('legacy')) {
1763
+ THREE__namespace.ColorManagement.enabled = !legacy;
1764
+ state.set({
1765
+ legacy
1766
+ });
1767
+ }
1768
+ if (changed('linear')) {
1769
+ if ((glProps == null ? void 0 : glProps.outputColorSpace) === undefined) {
1770
+ gl.outputColorSpace = linear ? THREE__namespace.LinearSRGBColorSpace : THREE__namespace.SRGBColorSpace;
1771
+ }
1772
+ state.set({
1773
+ linear
1774
+ });
1775
+ }
1776
+ if (changed('flat')) {
1777
+ if ((glProps == null ? void 0 : glProps.toneMapping) === undefined) gl.toneMapping = flat ? THREE__namespace.NoToneMapping : THREE__namespace.ACESFilmicToneMapping;
1778
+ state.set({
1779
+ flat
1780
+ });
1781
+ }
1782
+
1783
+ // Set gl props
1784
+ if (glProps && changed('gl')) applyProps(gl, glProps);
1785
+ configuration.previous = next;
1786
+ }
1787
+
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;
1797
+ };
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
+ }
1821
+
1367
1822
  var packageData = {
1368
1823
  name: "@react-three/fiber",
1369
- version: "9.8.0",
1824
+ version: "9.8.2",
1370
1825
  description: "A React renderer for Threejs",
1371
1826
  keywords: [
1372
1827
  "react",
@@ -1398,6 +1853,12 @@ var packageData = {
1398
1853
  module: "dist/react-three-fiber.esm.js",
1399
1854
  types: "dist/react-three-fiber.cjs.d.ts",
1400
1855
  "react-native": "native/dist/react-three-fiber-native.cjs.js",
1856
+ files: [
1857
+ "dist",
1858
+ "native",
1859
+ "react-reconciler",
1860
+ "CHANGELOG.md"
1861
+ ],
1401
1862
  sideEffects: false,
1402
1863
  preconstruct: {
1403
1864
  entrypoints: [
@@ -1417,7 +1878,7 @@ var packageData = {
1417
1878
  "@types/webxr": "*",
1418
1879
  "base64-js": "^1.5.1",
1419
1880
  buffer: "^6.0.3",
1420
- "its-fine": "^2.0.0",
1881
+ "its-fine": "^2.1.1",
1421
1882
  "react-use-measure": "^2.1.7",
1422
1883
  scheduler: "^0.28.0",
1423
1884
  "suspend-react": "^0.1.3",
@@ -7359,6 +7820,27 @@ function G1() {
7359
7820
  var J1 = G1();
7360
7821
  const Z1 = $1(J1);
7361
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
+
7362
7844
  function createReconciler(config) {
7363
7845
  const reconciler = Z1(config);
7364
7846
 
@@ -7372,24 +7854,11 @@ const NoEventPriority = 0;
7372
7854
  // https://github.com/pmndrs/react-three-fiber/pull/2931
7373
7855
  // https://github.com/microsoft/TypeScript/issues/37079
7374
7856
 
7375
- const catalogue = {};
7376
7857
  const PREFIX_REGEX = /^three(?=[A-Z])/;
7377
- const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
7378
- let i = 0;
7379
- const isConstructor = object => typeof object === 'function';
7380
- function extend(objects) {
7381
- if (isConstructor(objects)) {
7382
- const Component = `${i++}`;
7383
- catalogue[Component] = objects;
7384
- return Component;
7385
- } else {
7386
- Object.assign(catalogue, objects);
7387
- }
7388
- }
7389
7858
  function validateInstance(type, props) {
7390
7859
  // Get target from catalogue
7391
7860
  const name = toPascalCase(type);
7392
- const target = catalogue[name];
7861
+ const target = resolveConstructor(name);
7393
7862
 
7394
7863
  // Validate element target
7395
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`);
@@ -7403,7 +7872,7 @@ function validateInstance(type, props) {
7403
7872
  function createInstance(type, props, root) {
7404
7873
  var _props$object;
7405
7874
  // Remove three* prefix from elements if native element not present
7406
- type = toPascalCase(type) in catalogue ? type : type.replace(PREFIX_REGEX, '');
7875
+ type = hasConstructor(toPascalCase(type)) ? type : type.replace(PREFIX_REGEX, '');
7407
7876
  validateInstance(type, props);
7408
7877
 
7409
7878
  // Regenerate the R3F instance for primitives to simulate a new object
@@ -7416,6 +7885,7 @@ function hideInstance(instance) {
7416
7885
  if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
7417
7886
  detach(instance.parent, instance);
7418
7887
  } else if (isObject3D(instance.object)) {
7888
+ instance.previousVisible = instance.object.visible;
7419
7889
  instance.object.visible = false;
7420
7890
  }
7421
7891
  instance.isHidden = true;
@@ -7427,9 +7897,11 @@ function unhideInstance(instance) {
7427
7897
  var _instance$parent2;
7428
7898
  if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
7429
7899
  attach(instance.parent, instance);
7430
- } else if (isObject3D(instance.object) && instance.props.visible !== false) {
7431
- instance.object.visible = true;
7900
+ } else if (isObject3D(instance.object)) {
7901
+ var _instance$previousVis;
7902
+ instance.object.visible = (_instance$previousVis = instance.previousVisible) != null ? _instance$previousVis : true;
7432
7903
  }
7904
+ instance.previousVisible = undefined;
7433
7905
  instance.isHidden = false;
7434
7906
  invalidateInstance(instance);
7435
7907
  }
@@ -7447,7 +7919,7 @@ function handleContainerEffects(parent, child, beforeChild) {
7447
7919
  if (!child.object) {
7448
7920
  var _child$props$object, _child$props$args;
7449
7921
  // Get target from catalogue
7450
- const target = catalogue[toPascalCase(child.type)];
7922
+ const target = resolveConstructor(toPascalCase(child.type));
7451
7923
 
7452
7924
  // Create object
7453
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 : []));
@@ -7544,6 +8016,9 @@ function disposeOnIdle(object) {
7544
8016
  function removeChild(parent, child, dispose) {
7545
8017
  if (!child) return;
7546
8018
 
8019
+ // Unlinking clears the parent, which invalidateInstance checks, so remember it
8020
+ const wasLinked = !!child.parent;
8021
+
7547
8022
  // Unlink instances
7548
8023
  child.parent = null;
7549
8024
  const childIndex = parent.children.indexOf(child);
@@ -7581,7 +8056,11 @@ function removeChild(parent, child, dispose) {
7581
8056
  }
7582
8057
 
7583
8058
  // Tree was updated, request a frame for top-level instance
7584
- 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
+ }
7585
8064
  }
7586
8065
  function setFiberRef(fiber, publicInstance) {
7587
8066
  for (const _fiber of [fiber, fiber.alternate]) {
@@ -7642,7 +8121,7 @@ function swapReconstructedInstances() {
7642
8121
  if (parent) {
7643
8122
  var _instance$props$objec, _instance$props$args;
7644
8123
  // Get target from catalogue
7645
- const target = catalogue[toPascalCase(instance.type)];
8124
+ const target = resolveConstructor(toPascalCase(instance.type));
7646
8125
 
7647
8126
  // Create object
7648
8127
  const prevObject = instance.object;
@@ -7974,76 +8453,49 @@ const reconciler = /* @__PURE__ */createReconciler({
7974
8453
  getSuspendedCommitReason: (_state, _rootContainer) => null
7975
8454
  });
7976
8455
 
7977
- const isPromiseLike = value => typeof (value == null ? void 0 : value.then) === 'function';
7978
-
7979
- /** A promise tagged with its state, the protocol React's `use` reads */
7980
-
7981
- const fulfilled = value => Object.assign(Promise.resolve(value), {
7982
- status: 'fulfilled',
7983
- value
7984
- });
7985
- const rejected = reason => {
7986
- const promise = Promise.reject(reason);
7987
- promise.catch(() => {}); // the reason is read from `status`
7988
- return Object.assign(promise, {
7989
- status: 'rejected',
7990
- reason
7991
- });
7992
- };
7993
-
7994
- /** Tags `promise` in place once it settles. */
7995
- const tracked = promise => {
7996
- const result = Promise.resolve(promise);
7997
- result.status = 'pending';
7998
- // Statement bodies: returning `result` would make this derived promise adopt its rejection
7999
- result.then(value => {
8000
- Object.assign(result, {
8001
- status: 'fulfilled',
8002
- value
8003
- });
8004
- }, reason => {
8005
- Object.assign(result, {
8006
- status: 'rejected',
8007
- reason
8008
- });
8009
- });
8010
- return result;
8011
- };
8456
+ /**
8457
+ * Force React to flush any updates inside the provided callback synchronously and immediately.
8458
+ * All the same caveats documented for react-dom's `flushSync` apply here (see https://react.dev/reference/react-dom/flushSync).
8459
+ * Nevertheless, sometimes one needs to render synchronously, for example to keep DOM and 3D changes in lock-step without
8460
+ * having to revert to a non-React solution. Note: this will only flush updates within the `Canvas` root.
8461
+ */
8462
+ function flushSync(fn) {
8463
+ // @ts-ignore - reconciler types are not maintained
8464
+ return reconciler.flushSyncFromReconciler(fn);
8465
+ }
8012
8466
 
8013
8467
  const _roots = new Map();
8014
- const shallowLoose = {
8015
- objects: 'shallow',
8016
- strict: false
8017
- };
8018
- function computeInitialSize(canvas, size) {
8019
- if (!size && typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
8020
- const {
8021
- width,
8022
- height,
8023
- top,
8024
- left
8025
- } = canvas.parentElement.getBoundingClientRect();
8026
- return {
8027
- width,
8028
- height,
8029
- top,
8030
- left
8031
- };
8032
- } else if (!size && typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
8033
- return {
8034
- width: canvas.width,
8035
- height: canvas.height,
8036
- top: 0,
8037
- left: 0
8038
- };
8468
+ /**
8469
+ * open -> closing -> disposed
8470
+ * | use
8471
+ * v
8472
+ * open
8473
+ *
8474
+ * Only closing can be cancelled. Each unmount carries a token, so only the latest one can dispose.
8475
+ */
8476
+ function transitionRoot(root, event) {
8477
+ const state = root.state;
8478
+ switch (event.type) {
8479
+ case 'use':
8480
+ if (state.status === 'disposed') return false;
8481
+ root.state = {
8482
+ status: 'open'
8483
+ };
8484
+ return true;
8485
+ case 'unmount':
8486
+ if (state.status === 'disposed') return false;
8487
+ root.state = {
8488
+ status: 'closing',
8489
+ token: event.token
8490
+ };
8491
+ return true;
8492
+ case 'dispose':
8493
+ if (state.status !== 'closing' || state.token !== event.token) return false;
8494
+ root.state = {
8495
+ status: 'disposed'
8496
+ };
8497
+ return true;
8039
8498
  }
8040
- return {
8041
- width: 0,
8042
- height: 0,
8043
- top: 0,
8044
- left: 0,
8045
- ...size
8046
- };
8047
8499
  }
8048
8500
  function createRoot(canvas) {
8049
8501
  // Check against mistaken use of createRoot
@@ -8088,286 +8540,86 @@ function createRoot(canvas) {
8088
8540
  const root = prevRoot || {
8089
8541
  fiber,
8090
8542
  store,
8091
- unmountClaim: null,
8092
- ready: fulfilled(undefined)
8543
+ state: {
8544
+ status: 'open'
8545
+ },
8546
+ ready: fulfilled(undefined),
8547
+ configuration: {},
8548
+ ownsRenderer: false
8093
8549
  };
8094
8550
  if (!prevRoot) _roots.set(canvas, root);
8095
-
8096
- // Locals
8097
- let onCreated;
8098
- let lastCamera;
8099
8551
  let mounted = false;
8100
8552
  return {
8101
8553
  get ready() {
8102
8554
  return root.ready;
8103
8555
  },
8104
8556
  configure(props = {}) {
8105
- root.unmountClaim = null;
8106
- const {
8107
- gl: glConfig,
8108
- size: propsSize,
8109
- scene: sceneOptions,
8110
- events,
8111
- onCreated: onCreatedCallback,
8112
- shadows = false,
8113
- linear = false,
8114
- flat = false,
8115
- legacy = false,
8116
- orthographic = false,
8117
- frameloop = 'always',
8118
- dpr = [1, 2],
8119
- performance,
8120
- raycaster: raycastOptions,
8121
- camera: cameraOptions,
8122
- onPointerMissed
8123
- } = props;
8124
- const defaultProps = {
8125
- canvas: canvas,
8126
- powerPreference: 'high-performance',
8127
- antialias: true,
8128
- alpha: true
8129
- };
8557
+ if (!transitionRoot(root, {
8558
+ type: 'use'
8559
+ })) {
8560
+ return rejected(new Error('R3F: Cannot configure a root after disposal has started.'));
8561
+ }
8130
8562
 
8131
- // Synchronous, and needs the renderer to exist
8132
- const apply = customRenderer => {
8563
+ // Configuration applies in call order. Publishing first queues calls made while this one runs
8564
+ const previous = root.ready;
8565
+ const {
8566
+ promise,
8567
+ resolve,
8568
+ reject
8569
+ } = deferred();
8570
+ root.ready = promise;
8571
+ const apply = gl => {
8133
8572
  const state = store.getState();
8134
-
8135
8573
  // Set up renderer (one time only!)
8136
- const first = !state.gl;
8137
- let gl = state.gl;
8138
- if (first) {
8139
- gl = isRenderer(customRenderer) ? customRenderer : new THREE__namespace.WebGLRenderer({
8140
- ...defaultProps,
8141
- ...glConfig
8142
- });
8143
- state.set({
8144
- gl
8145
- });
8146
- }
8147
-
8148
- // Set up raycaster (one time only!)
8149
- let raycaster = state.raycaster;
8150
- if (!raycaster) state.set({
8151
- raycaster: raycaster = new THREE__namespace.Raycaster()
8152
- });
8153
-
8154
- // Set raycaster options
8155
- const {
8156
- params,
8157
- ...options
8158
- } = raycastOptions || {};
8159
- if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, {
8160
- ...options
8161
- });
8162
- if (!is.equ(params, raycaster.params, shallowLoose)) applyProps(raycaster, {
8163
- params: {
8164
- ...raycaster.params,
8165
- ...params
8166
- }
8167
- });
8168
-
8169
- // Create default camera, don't overwrite any user-set state
8170
- if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
8171
- lastCamera = cameraOptions;
8172
- const isCamera = cameraOptions == null ? void 0 : cameraOptions.isCamera;
8173
- const camera = isCamera ? cameraOptions : orthographic ? new THREE__namespace.OrthographicCamera(0, 0, 0, 0, 0.1, 1000) : new THREE__namespace.PerspectiveCamera(75, 0, 0.1, 1000);
8174
- if (!isCamera) {
8175
- camera.position.z = 5;
8176
- if (cameraOptions) {
8177
- applyProps(camera, cameraOptions);
8178
- // Preserve user-defined frustum if possible
8179
- // https://github.com/pmndrs/react-three-fiber/issues/3160
8180
- if (!camera.manual) {
8181
- if ('aspect' in cameraOptions || 'left' in cameraOptions || 'right' in cameraOptions || 'bottom' in cameraOptions || 'top' in cameraOptions) {
8182
- camera.manual = true;
8183
- camera.updateProjectionMatrix();
8184
- }
8185
- }
8186
- }
8187
- // Always look at center by default
8188
- if (!state.camera && !(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
8189
- }
8190
- state.set({
8191
- camera
8192
- });
8193
-
8194
- // Configure raycaster
8195
- // https://github.com/pmndrs/react-xr/issues/300
8196
- raycaster.camera = camera;
8197
- }
8198
-
8199
- // Set up scene (one time only!)
8200
- if (!state.scene) {
8201
- let scene;
8202
- if (sceneOptions != null && sceneOptions.isScene) {
8203
- scene = sceneOptions;
8204
- prepare(scene, store, '', {});
8205
- } else {
8206
- scene = new THREE__namespace.Scene();
8207
- prepare(scene, store, '', {});
8208
- if (sceneOptions) applyProps(scene, sceneOptions);
8209
- }
8210
- state.set({
8211
- scene
8212
- });
8213
- }
8214
-
8215
- // Store events internally
8216
- if (events && !state.events.handlers) state.set({
8217
- events: events(store)
8218
- });
8219
- // Check size, allow it to take on container bounds initially
8220
- const size = computeInitialSize(canvas, propsSize);
8221
- if (!is.equ(size, state.size, shallowLoose)) {
8222
- state.setSize(size.width, size.height, size.top, size.left);
8223
- }
8224
- // Check pixelratio
8225
- if (dpr && state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
8226
- // Check frameloop
8227
- if (state.frameloop !== frameloop) state.setFrameloop(frameloop);
8228
- // Check pointer missed
8229
- if (!state.onPointerMissed) state.set({
8230
- onPointerMissed
8231
- });
8232
- // Check performance
8233
- if (performance && !is.equ(performance, state.performance, shallowLoose)) state.set(state => ({
8234
- performance: {
8235
- ...state.performance,
8236
- ...performance
8237
- }
8238
- }));
8239
-
8240
- // Set up XR (one time only!)
8241
- if (!state.xr) {
8242
- var _gl$xr;
8243
- // Handle frame behavior in WebXR
8244
- const handleXRFrame = (timestamp, frame) => {
8245
- const state = store.getState();
8246
- if (state.frameloop === 'never') return;
8247
- advance(timestamp, true, state, frame);
8248
- };
8249
-
8250
- // Toggle render switching on session
8251
- const handleSessionChange = () => {
8252
- const state = store.getState();
8253
- state.gl.xr.enabled = state.gl.xr.isPresenting;
8254
- state.gl.xr.setAnimationLoop(state.gl.xr.isPresenting ? handleXRFrame : null);
8255
- if (!state.gl.xr.isPresenting) invalidate(state);
8256
- };
8257
-
8258
- // WebXR session manager
8259
- const xr = {
8260
- connect() {
8261
- const gl = store.getState().gl;
8262
- gl.xr.addEventListener('sessionstart', handleSessionChange);
8263
- gl.xr.addEventListener('sessionend', handleSessionChange);
8264
- },
8265
- disconnect() {
8266
- const gl = store.getState().gl;
8267
- gl.xr.removeEventListener('sessionstart', handleSessionChange);
8268
- gl.xr.removeEventListener('sessionend', handleSessionChange);
8269
- }
8270
- };
8271
-
8272
- // Subscribe to WebXR session events
8273
- if (typeof ((_gl$xr = gl.xr) == null ? void 0 : _gl$xr.addEventListener) === 'function') xr.connect();
8574
+ if (!state.gl) {
8575
+ // R3F owns what it builds, a factory's result included, since it calls the factory
8576
+ // once per root. A renderer instance belongs to the caller
8577
+ root.ownsRenderer = !isRenderer(props.gl);
8274
8578
  state.set({
8275
- xr
8579
+ gl: gl
8276
8580
  });
8277
8581
  }
8278
-
8279
- // Set shadowmap
8280
- if (gl.shadowMap) {
8281
- const oldEnabled = gl.shadowMap.enabled;
8282
- const oldType = gl.shadowMap.type;
8283
- gl.shadowMap.enabled = !!shadows;
8284
- if (is.boo(shadows)) {
8285
- gl.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
8286
- } else if (is.str(shadows)) {
8287
- var _types$shadows;
8288
- const types = {
8289
- basic: THREE__namespace.BasicShadowMap,
8290
- percentage: THREE__namespace.PCFShadowMap,
8291
- soft: THREE__namespace.PCFSoftShadowMap,
8292
- variance: THREE__namespace.VSMShadowMap
8293
- };
8294
- gl.shadowMap.type = (_types$shadows = types[shadows]) != null ? _types$shadows : THREE__namespace.PCFSoftShadowMap;
8295
- } else if (is.obj(shadows)) {
8296
- Object.assign(gl.shadowMap, shadows);
8297
- }
8298
- if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type) gl.shadowMap.needsUpdate = true;
8299
- }
8300
- THREE__namespace.ColorManagement.enabled = !legacy;
8301
-
8302
- // Set color space and tonemapping preferences
8303
- if (first) {
8304
- gl.outputColorSpace = linear ? THREE__namespace.LinearSRGBColorSpace : THREE__namespace.SRGBColorSpace;
8305
- gl.toneMapping = flat ? THREE__namespace.NoToneMapping : THREE__namespace.ACESFilmicToneMapping;
8306
- }
8307
-
8308
- // Update color management state
8309
- if (state.legacy !== legacy) state.set(() => ({
8310
- legacy
8311
- }));
8312
- if (state.linear !== linear) state.set(() => ({
8313
- linear
8314
- }));
8315
- if (state.flat !== flat) state.set(() => ({
8316
- flat
8317
- }));
8318
-
8319
- // Set gl props
8320
- if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, gl, shallowLoose)) applyProps(gl, glConfig);
8321
-
8322
- // Set locals
8323
- onCreated = onCreatedCallback;
8324
- };
8325
- const fail = error => {
8326
- const failure = rejected(error);
8327
- root.ready = failure;
8328
- return failure;
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);
8585
+ resolve(this);
8329
8586
  };
8330
- const run = customRenderer => {
8587
+ const run = () => {
8331
8588
  try {
8332
- apply(customRenderer);
8333
- root.ready = fulfilled(undefined);
8334
- return fulfilled(this);
8589
+ var _store$getState$gl;
8590
+ const gl = (_store$getState$gl = store.getState().gl) != null ? _store$getState$gl : createRenderer(canvas, props.gl);
8591
+ // Only an async renderer factory makes configuration asynchronous
8592
+ if (isPromiseLike(gl)) Promise.resolve(gl).then(apply).catch(reject);else apply(gl);
8335
8593
  } catch (error) {
8336
- return fail(error);
8594
+ reject(error);
8337
8595
  }
8338
8596
  };
8339
-
8340
- // The renderer exists, so applying props is synchronous
8341
- if (store.getState().gl) return run();
8342
-
8343
- // Another configure is creating it. Chaining keeps them in call order
8344
- if (root.ready.status === 'pending') return tracked(root.ready.then(() => run()));
8345
-
8346
- // Only an async factory makes configure asynchronous
8347
- let customRenderer;
8348
- try {
8349
- customRenderer = typeof glConfig === 'function' ? glConfig(defaultProps) : glConfig;
8350
- } catch (error) {
8351
- return fail(error);
8352
- }
8353
- if (!isPromiseLike(customRenderer)) return run(customRenderer);
8354
- const configuring = tracked(customRenderer.then(run, fail));
8355
- root.ready = configuring;
8356
- return configuring;
8597
+ if (previous.status === 'pending') previous.then(run, reject);else run();
8598
+ return promise;
8357
8599
  },
8358
8600
  render(children) {
8359
- if (_roots.get(canvas) !== root) return store;
8360
- // Using the root cancels a teardown waiting on React
8361
- root.unmountClaim = null;
8362
- const element = /*#__PURE__*/jsxRuntime.jsx(Provider, {
8363
- store: store,
8364
- children: children,
8365
- onCreated: onCreated,
8366
- rootElement: canvas
8367
- });
8601
+ if (!transitionRoot(root, {
8602
+ type: 'use'
8603
+ })) return store;
8604
+
8605
+ // The root has to be configured before it can be rendered
8606
+ if (!store.getState().gl && root.ready.status === 'fulfilled') this.configure();
8607
+ if (root.ready.status === 'rejected') throw root.ready.reason;
8368
8608
  const commit = () => {
8369
- // The root may have been unmounted or claimed while we waited
8370
- if (_roots.get(canvas) !== root || root.unmountClaim) return;
8609
+ var _root$configuration$p;
8610
+ if (root.state.status !== 'open' || root.ready.status === 'rejected') return;
8611
+ // Wait for all queued configuration, including any queued while waiting
8612
+ if (root.ready.status === 'pending') {
8613
+ root.ready.then(commit, logRecoverableError);
8614
+ return;
8615
+ }
8616
+ const onCreated = (_root$configuration$p = root.configuration.previous) == null ? void 0 : _root$configuration$p.onCreated;
8617
+ const element = /*#__PURE__*/jsxRuntime.jsx(Provider, {
8618
+ store: store,
8619
+ children: children,
8620
+ onCreated: onCreated,
8621
+ rootElement: canvas
8622
+ });
8371
8623
  if (mounted) {
8372
8624
  reconciler.updateContainer(element, fiber, null, noop);
8373
8625
  return;
@@ -8379,22 +8631,11 @@ function createRoot(canvas) {
8379
8631
  // @ts-ignore - reconciler types are not maintained
8380
8632
  reconciler.flushSyncWork();
8381
8633
  };
8382
-
8383
- // The root has to be configured before it can be rendered
8384
- if (!store.getState().gl && root.ready.status === 'fulfilled') this.configure();
8385
- switch (root.ready.status) {
8386
- case 'pending':
8387
- root.ready.then(commit, logRecoverableError);
8388
- break;
8389
- case 'rejected':
8390
- throw root.ready.reason;
8391
- default:
8392
- commit();
8393
- }
8634
+ commit();
8394
8635
  return store;
8395
8636
  },
8396
8637
  unmount() {
8397
- unmountComponentAtNode(canvas);
8638
+ if (_roots.get(canvas) === root) unmountComponentAtNode(canvas);
8398
8639
  }
8399
8640
  };
8400
8641
  }
@@ -8425,147 +8666,51 @@ function Provider({
8425
8666
  children: children
8426
8667
  });
8427
8668
  }
8669
+ function attempt(step) {
8670
+ try {
8671
+ return step();
8672
+ } catch (error) {
8673
+ console.warn('[R3F] Error while unmounting root; teardown may be incomplete:', error);
8674
+ }
8675
+ }
8428
8676
  function unmountComponentAtNode(canvas, callback) {
8429
8677
  const root = _roots.get(canvas);
8430
- if (!root) return;
8431
-
8432
- // Cleared by configure and render, which cancels the teardown
8433
- const claim = root.unmountClaim = Symbol('unmount');
8678
+ const token = Symbol('unmount');
8679
+ if (!root || !transitionRoot(root, {
8680
+ type: 'unmount',
8681
+ token
8682
+ })) return;
8683
+ const teardown = () => {
8684
+ var _state$gl;
8685
+ // Configuration accepted before the unmount finishes first, so a renderer it creates is released too
8686
+ if (root.ready.status === 'pending') {
8687
+ root.ready.then(teardown, teardown);
8688
+ return;
8689
+ }
8690
+ // Refused when the root was used again since this unmount
8691
+ if (!transitionRoot(root, {
8692
+ type: 'dispose',
8693
+ token
8694
+ })) return;
8695
+ // Close the gates before invoking user cleanup code.
8696
+ _roots.delete(canvas);
8697
+ const state = root.store.getState();
8698
+ state.internal.active = false;
8699
+
8700
+ // Release what uses the renderer before the renderer itself
8701
+ attempt(() => root.unwatchDpr == null ? void 0 : root.unwatchDpr());
8702
+ attempt(() => state.events.disconnect == null ? void 0 : state.events.disconnect());
8703
+ if ((_state$gl = state.gl) != null && _state$gl.xr) attempt(() => state.xr.disconnect());
8704
+ if (state.scene) attempt(() => dispose(state.scene));
8705
+ const disposal = attempt(() => releaseRenderer(state.gl, root.ownsRenderer));
8706
+ if (isPromiseLike(disposal)) disposal.then(() => callback == null ? void 0 : callback(canvas));else callback == null ? void 0 : callback(canvas);
8707
+ };
8434
8708
  reconciler.updateContainer(null, root.fiber, null, () => {
8435
- if (root.unmountClaim !== claim) return;
8436
-
8709
+ // A root used again since the unmount keeps its new children
8710
+ if (root.state.status !== 'closing' || root.state.token !== token) return;
8437
8711
  // Effect cleanups flush before the next update
8438
- reconciler.updateContainer(null, root.fiber, null, () => {
8439
- if (root.unmountClaim !== claim) return;
8440
- const teardown = () => {
8441
- if (root.unmountClaim !== claim) return;
8442
- root.unmountClaim = null;
8443
- const state = root.store.getState();
8444
- state.internal.active = false;
8445
- try {
8446
- var _state$gl, _state$gl$renderLists, _state$gl2, _state$gl3;
8447
- state.events.disconnect == null ? void 0 : state.events.disconnect();
8448
- (_state$gl = state.gl) == null ? void 0 : (_state$gl$renderLists = _state$gl.renderLists) == null ? void 0 : _state$gl$renderLists.dispose == null ? void 0 : _state$gl$renderLists.dispose();
8449
- (_state$gl2 = state.gl) == null ? void 0 : _state$gl2.forceContextLoss == null ? void 0 : _state$gl2.forceContextLoss();
8450
- if ((_state$gl3 = state.gl) != null && _state$gl3.xr) state.xr.disconnect();
8451
- dispose(state.scene);
8452
- } catch (e) {
8453
- /* ... */
8454
- }
8455
- _roots.delete(canvas);
8456
- if (callback) callback(canvas);
8457
- };
8458
-
8459
- // A renderer still being created has to exist before it can be disposed
8460
- if (root.ready.status === 'pending') root.ready.then(teardown, teardown);else teardown();
8461
- });
8462
- });
8463
- }
8464
- function createPortal(children, container, state) {
8465
- return /*#__PURE__*/jsxRuntime.jsx(Portal, {
8466
- children: children,
8467
- container: container,
8468
- state: state
8469
- });
8470
- }
8471
- function Portal({
8472
- state = {},
8473
- children,
8474
- container
8475
- }) {
8476
- /** This has to be a component because it would not be able to call useThree/useStore otherwise since
8477
- * if this is our environment, then we are not in r3f's renderer but in react-dom, it would trigger
8478
- * the "R3F hooks can only be used within the Canvas component!" warning:
8479
- * <Canvas>
8480
- * {createPortal(...)} */
8481
- const {
8482
- events,
8483
- size,
8484
- ...rest
8485
- } = state;
8486
- const previousRoot = useStore();
8487
- const [raycaster] = React__namespace.useState(() => new THREE__namespace.Raycaster());
8488
- const [pointer] = React__namespace.useState(() => new THREE__namespace.Vector2());
8489
- const inject = useMutableCallback((rootState, injectState) => {
8490
- let viewport = undefined;
8491
- if (injectState.camera && size) {
8492
- const camera = injectState.camera;
8493
- // Calculate the override viewport, if present
8494
- viewport = rootState.viewport.getCurrentViewport(camera, new THREE__namespace.Vector3(), size);
8495
- // Update the portal camera, if it differs from the previous layer
8496
- if (camera !== rootState.camera) updateCamera(camera, size);
8497
- }
8498
- return {
8499
- // The intersect consists of the previous root state
8500
- ...rootState,
8501
- ...injectState,
8502
- // Portals have their own scene, which forms the root, a raycaster and a pointer
8503
- scene: container,
8504
- raycaster,
8505
- pointer,
8506
- mouse: pointer,
8507
- // Their previous root is the layer before it
8508
- previousRoot,
8509
- // Events, size and viewport can be overridden by the inject layer
8510
- events: {
8511
- ...rootState.events,
8512
- ...injectState.events,
8513
- ...events
8514
- },
8515
- size: {
8516
- ...rootState.size,
8517
- ...size
8518
- },
8519
- viewport: {
8520
- ...rootState.viewport,
8521
- ...viewport
8522
- },
8523
- // Layers are allowed to override events
8524
- setEvents: events => injectState.set(state => ({
8525
- ...state,
8526
- events: {
8527
- ...state.events,
8528
- ...events
8529
- }
8530
- }))
8531
- };
8712
+ reconciler.updateContainer(null, root.fiber, null, teardown);
8532
8713
  });
8533
- const usePortalStore = React__namespace.useMemo(() => {
8534
- // Create a mirrored store, based on the previous root with a few overrides ...
8535
- const store = traditional.createWithEqualityFn((set, get) => ({
8536
- ...rest,
8537
- set,
8538
- get
8539
- }));
8540
-
8541
- // Subscribe to previous root-state and copy changes over to the mirrored portal-state
8542
- const onMutate = prev => store.setState(state => inject.current(prev, state));
8543
- onMutate(previousRoot.getState());
8544
- previousRoot.subscribe(onMutate);
8545
- return store;
8546
- // eslint-disable-next-line react-hooks/exhaustive-deps
8547
- }, [previousRoot, container]);
8548
- return (
8549
- /*#__PURE__*/
8550
- // @ts-ignore, reconciler types are not maintained
8551
- jsxRuntime.jsx(jsxRuntime.Fragment, {
8552
- children: reconciler.createPortal( /*#__PURE__*/jsxRuntime.jsx(context.Provider, {
8553
- value: usePortalStore,
8554
- children: children
8555
- }), usePortalStore, null)
8556
- })
8557
- );
8558
- }
8559
-
8560
- /**
8561
- * Force React to flush any updates inside the provided callback synchronously and immediately.
8562
- * All the same caveats documented for react-dom's `flushSync` apply here (see https://react.dev/reference/react-dom/flushSync).
8563
- * Nevertheless, sometimes one needs to render synchronously, for example to keep DOM and 3D changes in lock-step without
8564
- * having to revert to a non-React solution. Note: this will only flush updates within the `Canvas` root.
8565
- */
8566
- function flushSync(fn) {
8567
- // @ts-ignore - reconciler types are not maintained
8568
- return reconciler.flushSyncFromReconciler(fn);
8569
8714
  }
8570
8715
 
8571
8716
  function createSubs(callback, subs) {
@@ -8720,6 +8865,141 @@ function advance(timestamp, runGlobalEffects = true, state, frame) {
8720
8865
  if (runGlobalEffects) flushGlobalEffects('after', timestamp);
8721
8866
  }
8722
8867
 
8868
+ function createPortal(children, container, state) {
8869
+ return /*#__PURE__*/jsxRuntime.jsx(Portal, {
8870
+ children: children,
8871
+ container: container,
8872
+ state: state
8873
+ });
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
+ }
8904
+ function Portal({
8905
+ state = {},
8906
+ children,
8907
+ container
8908
+ }) {
8909
+ const parentStore = useStore();
8910
+ const {
8911
+ events,
8912
+ size,
8913
+ ...rest
8914
+ } = state;
8915
+ const [raycaster] = React__namespace.useState(() => new THREE__namespace.Raycaster());
8916
+ const [pointer] = React__namespace.useState(() => new THREE__namespace.Vector2());
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));
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)
8957
+ });
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
+ };
9001
+ }
9002
+
8723
9003
  const DOM_EVENTS = {
8724
9004
  onClick: ['click', false],
8725
9005
  onContextMenu: ['contextmenu', false],