@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",
@@ -16138,6 +16599,27 @@ function G1() {
16138
16599
  var J1 = G1();
16139
16600
  const Z1 = $1(J1);
16140
16601
 
16602
+ /** Constructor registration and lookup for JSX elements. */
16603
+
16604
+ const catalogue = {};
16605
+ let i = 0;
16606
+ const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
16607
+ function hasConstructor(name) {
16608
+ return name in catalogue;
16609
+ }
16610
+ function resolveConstructor(name) {
16611
+ return catalogue[name];
16612
+ }
16613
+ function extend(objects) {
16614
+ if (typeof objects === 'function') {
16615
+ const Component = `${i++}`;
16616
+ catalogue[Component] = objects;
16617
+ return Component;
16618
+ } else {
16619
+ Object.assign(catalogue, objects);
16620
+ }
16621
+ }
16622
+
16141
16623
  function createReconciler(config) {
16142
16624
  const reconciler = Z1(config);
16143
16625
 
@@ -16151,24 +16633,11 @@ const NoEventPriority = 0;
16151
16633
  // https://github.com/pmndrs/react-three-fiber/pull/2931
16152
16634
  // https://github.com/microsoft/TypeScript/issues/37079
16153
16635
 
16154
- const catalogue = {};
16155
16636
  const PREFIX_REGEX = /^three(?=[A-Z])/;
16156
- const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
16157
- let i = 0;
16158
- const isConstructor = object => typeof object === 'function';
16159
- function extend(objects) {
16160
- if (isConstructor(objects)) {
16161
- const Component = `${i++}`;
16162
- catalogue[Component] = objects;
16163
- return Component;
16164
- } else {
16165
- Object.assign(catalogue, objects);
16166
- }
16167
- }
16168
16637
  function validateInstance(type, props) {
16169
16638
  // Get target from catalogue
16170
16639
  const name = toPascalCase(type);
16171
- const target = catalogue[name];
16640
+ const target = resolveConstructor(name);
16172
16641
 
16173
16642
  // Validate element target
16174
16643
  if (type !== 'primitive' && !target) throw new Error(`R3F: ${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`);
@@ -16182,7 +16651,7 @@ function validateInstance(type, props) {
16182
16651
  function createInstance(type, props, root) {
16183
16652
  var _props$object;
16184
16653
  // Remove three* prefix from elements if native element not present
16185
- type = toPascalCase(type) in catalogue ? type : type.replace(PREFIX_REGEX, '');
16654
+ type = hasConstructor(toPascalCase(type)) ? type : type.replace(PREFIX_REGEX, '');
16186
16655
  validateInstance(type, props);
16187
16656
 
16188
16657
  // Regenerate the R3F instance for primitives to simulate a new object
@@ -16195,6 +16664,7 @@ function hideInstance(instance) {
16195
16664
  if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
16196
16665
  detach(instance.parent, instance);
16197
16666
  } else if (isObject3D(instance.object)) {
16667
+ instance.previousVisible = instance.object.visible;
16198
16668
  instance.object.visible = false;
16199
16669
  }
16200
16670
  instance.isHidden = true;
@@ -16206,9 +16676,11 @@ function unhideInstance(instance) {
16206
16676
  var _instance$parent2;
16207
16677
  if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
16208
16678
  attach(instance.parent, instance);
16209
- } else if (isObject3D(instance.object) && instance.props.visible !== false) {
16210
- instance.object.visible = true;
16679
+ } else if (isObject3D(instance.object)) {
16680
+ var _instance$previousVis;
16681
+ instance.object.visible = (_instance$previousVis = instance.previousVisible) != null ? _instance$previousVis : true;
16211
16682
  }
16683
+ instance.previousVisible = undefined;
16212
16684
  instance.isHidden = false;
16213
16685
  invalidateInstance(instance);
16214
16686
  }
@@ -16226,7 +16698,7 @@ function handleContainerEffects(parent, child, beforeChild) {
16226
16698
  if (!child.object) {
16227
16699
  var _child$props$object, _child$props$args;
16228
16700
  // Get target from catalogue
16229
- const target = catalogue[toPascalCase(child.type)];
16701
+ const target = resolveConstructor(toPascalCase(child.type));
16230
16702
 
16231
16703
  // Create object
16232
16704
  child.object = (_child$props$object = child.props.object) != null ? _child$props$object : new target(...((_child$props$args = child.props.args) != null ? _child$props$args : []));
@@ -16323,6 +16795,9 @@ function disposeOnIdle(object) {
16323
16795
  function removeChild(parent, child, dispose) {
16324
16796
  if (!child) return;
16325
16797
 
16798
+ // Unlinking clears the parent, which invalidateInstance checks, so remember it
16799
+ const wasLinked = !!child.parent;
16800
+
16326
16801
  // Unlink instances
16327
16802
  child.parent = null;
16328
16803
  const childIndex = parent.children.indexOf(child);
@@ -16360,7 +16835,11 @@ function removeChild(parent, child, dispose) {
16360
16835
  }
16361
16836
 
16362
16837
  // Tree was updated, request a frame for top-level instance
16363
- if (dispose === undefined) invalidateInstance(child);
16838
+ if (dispose === undefined && wasLinked) {
16839
+ var _child$root;
16840
+ const state = (_child$root = child.root) == null ? void 0 : _child$root.getState == null ? void 0 : _child$root.getState();
16841
+ if (state && state.internal.frames === 0) state.invalidate();
16842
+ }
16364
16843
  }
16365
16844
  function setFiberRef(fiber, publicInstance) {
16366
16845
  for (const _fiber of [fiber, fiber.alternate]) {
@@ -16421,7 +16900,7 @@ function swapReconstructedInstances() {
16421
16900
  if (parent) {
16422
16901
  var _instance$props$objec, _instance$props$args;
16423
16902
  // Get target from catalogue
16424
- const target = catalogue[toPascalCase(instance.type)];
16903
+ const target = resolveConstructor(toPascalCase(instance.type));
16425
16904
 
16426
16905
  // Create object
16427
16906
  const prevObject = instance.object;
@@ -16753,76 +17232,49 @@ const reconciler = /* @__PURE__ */createReconciler({
16753
17232
  getSuspendedCommitReason: (_state, _rootContainer) => null
16754
17233
  });
16755
17234
 
16756
- const isPromiseLike = value => typeof (value == null ? void 0 : value.then) === 'function';
16757
-
16758
- /** A promise tagged with its state, the protocol React's `use` reads */
16759
-
16760
- const fulfilled = value => Object.assign(Promise.resolve(value), {
16761
- status: 'fulfilled',
16762
- value
16763
- });
16764
- const rejected = reason => {
16765
- const promise = Promise.reject(reason);
16766
- promise.catch(() => {}); // the reason is read from `status`
16767
- return Object.assign(promise, {
16768
- status: 'rejected',
16769
- reason
16770
- });
16771
- };
16772
-
16773
- /** Tags `promise` in place once it settles. */
16774
- const tracked = promise => {
16775
- const result = Promise.resolve(promise);
16776
- result.status = 'pending';
16777
- // Statement bodies: returning `result` would make this derived promise adopt its rejection
16778
- result.then(value => {
16779
- Object.assign(result, {
16780
- status: 'fulfilled',
16781
- value
16782
- });
16783
- }, reason => {
16784
- Object.assign(result, {
16785
- status: 'rejected',
16786
- reason
16787
- });
16788
- });
16789
- return result;
16790
- };
17235
+ /**
17236
+ * Force React to flush any updates inside the provided callback synchronously and immediately.
17237
+ * All the same caveats documented for react-dom's `flushSync` apply here (see https://react.dev/reference/react-dom/flushSync).
17238
+ * Nevertheless, sometimes one needs to render synchronously, for example to keep DOM and 3D changes in lock-step without
17239
+ * having to revert to a non-React solution. Note: this will only flush updates within the `Canvas` root.
17240
+ */
17241
+ function flushSync(fn) {
17242
+ // @ts-ignore - reconciler types are not maintained
17243
+ return reconciler.flushSyncFromReconciler(fn);
17244
+ }
16791
17245
 
16792
17246
  const _roots = new Map();
16793
- const shallowLoose = {
16794
- objects: 'shallow',
16795
- strict: false
16796
- };
16797
- function computeInitialSize(canvas, size) {
16798
- if (!size && typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
16799
- const {
16800
- width,
16801
- height,
16802
- top,
16803
- left
16804
- } = canvas.parentElement.getBoundingClientRect();
16805
- return {
16806
- width,
16807
- height,
16808
- top,
16809
- left
16810
- };
16811
- } else if (!size && typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
16812
- return {
16813
- width: canvas.width,
16814
- height: canvas.height,
16815
- top: 0,
16816
- left: 0
16817
- };
17247
+ /**
17248
+ * open -> closing -> disposed
17249
+ * | use
17250
+ * v
17251
+ * open
17252
+ *
17253
+ * Only closing can be cancelled. Each unmount carries a token, so only the latest one can dispose.
17254
+ */
17255
+ function transitionRoot(root, event) {
17256
+ const state = root.state;
17257
+ switch (event.type) {
17258
+ case 'use':
17259
+ if (state.status === 'disposed') return false;
17260
+ root.state = {
17261
+ status: 'open'
17262
+ };
17263
+ return true;
17264
+ case 'unmount':
17265
+ if (state.status === 'disposed') return false;
17266
+ root.state = {
17267
+ status: 'closing',
17268
+ token: event.token
17269
+ };
17270
+ return true;
17271
+ case 'dispose':
17272
+ if (state.status !== 'closing' || state.token !== event.token) return false;
17273
+ root.state = {
17274
+ status: 'disposed'
17275
+ };
17276
+ return true;
16818
17277
  }
16819
- return {
16820
- width: 0,
16821
- height: 0,
16822
- top: 0,
16823
- left: 0,
16824
- ...size
16825
- };
16826
17278
  }
16827
17279
  function createRoot(canvas) {
16828
17280
  // Check against mistaken use of createRoot
@@ -16867,286 +17319,86 @@ function createRoot(canvas) {
16867
17319
  const root = prevRoot || {
16868
17320
  fiber,
16869
17321
  store,
16870
- unmountClaim: null,
16871
- ready: fulfilled(undefined)
17322
+ state: {
17323
+ status: 'open'
17324
+ },
17325
+ ready: fulfilled(undefined),
17326
+ configuration: {},
17327
+ ownsRenderer: false
16872
17328
  };
16873
17329
  if (!prevRoot) _roots.set(canvas, root);
16874
-
16875
- // Locals
16876
- let onCreated;
16877
- let lastCamera;
16878
17330
  let mounted = false;
16879
17331
  return {
16880
17332
  get ready() {
16881
17333
  return root.ready;
16882
17334
  },
16883
17335
  configure(props = {}) {
16884
- root.unmountClaim = null;
16885
- const {
16886
- gl: glConfig,
16887
- size: propsSize,
16888
- scene: sceneOptions,
16889
- events,
16890
- onCreated: onCreatedCallback,
16891
- shadows = false,
16892
- linear = false,
16893
- flat = false,
16894
- legacy = false,
16895
- orthographic = false,
16896
- frameloop = 'always',
16897
- dpr = [1, 2],
16898
- performance,
16899
- raycaster: raycastOptions,
16900
- camera: cameraOptions,
16901
- onPointerMissed
16902
- } = props;
16903
- const defaultProps = {
16904
- canvas: canvas,
16905
- powerPreference: 'high-performance',
16906
- antialias: true,
16907
- alpha: true
16908
- };
17336
+ if (!transitionRoot(root, {
17337
+ type: 'use'
17338
+ })) {
17339
+ return rejected(new Error('R3F: Cannot configure a root after disposal has started.'));
17340
+ }
16909
17341
 
16910
- // Synchronous, and needs the renderer to exist
16911
- const apply = customRenderer => {
17342
+ // Configuration applies in call order. Publishing first queues calls made while this one runs
17343
+ const previous = root.ready;
17344
+ const {
17345
+ promise,
17346
+ resolve,
17347
+ reject
17348
+ } = deferred();
17349
+ root.ready = promise;
17350
+ const apply = gl => {
16912
17351
  const state = store.getState();
16913
-
16914
17352
  // Set up renderer (one time only!)
16915
- const first = !state.gl;
16916
- let gl = state.gl;
16917
- if (first) {
16918
- gl = isRenderer(customRenderer) ? customRenderer : new THREE__namespace.WebGLRenderer({
16919
- ...defaultProps,
16920
- ...glConfig
16921
- });
16922
- state.set({
16923
- gl
16924
- });
16925
- }
16926
-
16927
- // Set up raycaster (one time only!)
16928
- let raycaster = state.raycaster;
16929
- if (!raycaster) state.set({
16930
- raycaster: raycaster = new THREE__namespace.Raycaster()
16931
- });
16932
-
16933
- // Set raycaster options
16934
- const {
16935
- params,
16936
- ...options
16937
- } = raycastOptions || {};
16938
- if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, {
16939
- ...options
16940
- });
16941
- if (!is.equ(params, raycaster.params, shallowLoose)) applyProps(raycaster, {
16942
- params: {
16943
- ...raycaster.params,
16944
- ...params
16945
- }
16946
- });
16947
-
16948
- // Create default camera, don't overwrite any user-set state
16949
- if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
16950
- lastCamera = cameraOptions;
16951
- const isCamera = cameraOptions == null ? void 0 : cameraOptions.isCamera;
16952
- 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);
16953
- if (!isCamera) {
16954
- camera.position.z = 5;
16955
- if (cameraOptions) {
16956
- applyProps(camera, cameraOptions);
16957
- // Preserve user-defined frustum if possible
16958
- // https://github.com/pmndrs/react-three-fiber/issues/3160
16959
- if (!camera.manual) {
16960
- if ('aspect' in cameraOptions || 'left' in cameraOptions || 'right' in cameraOptions || 'bottom' in cameraOptions || 'top' in cameraOptions) {
16961
- camera.manual = true;
16962
- camera.updateProjectionMatrix();
16963
- }
16964
- }
16965
- }
16966
- // Always look at center by default
16967
- if (!state.camera && !(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
16968
- }
16969
- state.set({
16970
- camera
16971
- });
16972
-
16973
- // Configure raycaster
16974
- // https://github.com/pmndrs/react-xr/issues/300
16975
- raycaster.camera = camera;
16976
- }
16977
-
16978
- // Set up scene (one time only!)
16979
- if (!state.scene) {
16980
- let scene;
16981
- if (sceneOptions != null && sceneOptions.isScene) {
16982
- scene = sceneOptions;
16983
- prepare(scene, store, '', {});
16984
- } else {
16985
- scene = new THREE__namespace.Scene();
16986
- prepare(scene, store, '', {});
16987
- if (sceneOptions) applyProps(scene, sceneOptions);
16988
- }
16989
- state.set({
16990
- scene
16991
- });
16992
- }
16993
-
16994
- // Store events internally
16995
- if (events && !state.events.handlers) state.set({
16996
- events: events(store)
16997
- });
16998
- // Check size, allow it to take on container bounds initially
16999
- const size = computeInitialSize(canvas, propsSize);
17000
- if (!is.equ(size, state.size, shallowLoose)) {
17001
- state.setSize(size.width, size.height, size.top, size.left);
17002
- }
17003
- // Check pixelratio
17004
- if (dpr && state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
17005
- // Check frameloop
17006
- if (state.frameloop !== frameloop) state.setFrameloop(frameloop);
17007
- // Check pointer missed
17008
- if (!state.onPointerMissed) state.set({
17009
- onPointerMissed
17010
- });
17011
- // Check performance
17012
- if (performance && !is.equ(performance, state.performance, shallowLoose)) state.set(state => ({
17013
- performance: {
17014
- ...state.performance,
17015
- ...performance
17016
- }
17017
- }));
17018
-
17019
- // Set up XR (one time only!)
17020
- if (!state.xr) {
17021
- var _gl$xr;
17022
- // Handle frame behavior in WebXR
17023
- const handleXRFrame = (timestamp, frame) => {
17024
- const state = store.getState();
17025
- if (state.frameloop === 'never') return;
17026
- advance(timestamp, true, state, frame);
17027
- };
17028
-
17029
- // Toggle render switching on session
17030
- const handleSessionChange = () => {
17031
- const state = store.getState();
17032
- state.gl.xr.enabled = state.gl.xr.isPresenting;
17033
- state.gl.xr.setAnimationLoop(state.gl.xr.isPresenting ? handleXRFrame : null);
17034
- if (!state.gl.xr.isPresenting) invalidate(state);
17035
- };
17036
-
17037
- // WebXR session manager
17038
- const xr = {
17039
- connect() {
17040
- const gl = store.getState().gl;
17041
- gl.xr.addEventListener('sessionstart', handleSessionChange);
17042
- gl.xr.addEventListener('sessionend', handleSessionChange);
17043
- },
17044
- disconnect() {
17045
- const gl = store.getState().gl;
17046
- gl.xr.removeEventListener('sessionstart', handleSessionChange);
17047
- gl.xr.removeEventListener('sessionend', handleSessionChange);
17048
- }
17049
- };
17050
-
17051
- // Subscribe to WebXR session events
17052
- if (typeof ((_gl$xr = gl.xr) == null ? void 0 : _gl$xr.addEventListener) === 'function') xr.connect();
17353
+ if (!state.gl) {
17354
+ // R3F owns what it builds, a factory's result included, since it calls the factory
17355
+ // once per root. A renderer instance belongs to the caller
17356
+ root.ownsRenderer = !isRenderer(props.gl);
17053
17357
  state.set({
17054
- xr
17358
+ gl: gl
17055
17359
  });
17056
17360
  }
17057
-
17058
- // Set shadowmap
17059
- if (gl.shadowMap) {
17060
- const oldEnabled = gl.shadowMap.enabled;
17061
- const oldType = gl.shadowMap.type;
17062
- gl.shadowMap.enabled = !!shadows;
17063
- if (is.boo(shadows)) {
17064
- gl.shadowMap.type = THREE__namespace.PCFSoftShadowMap;
17065
- } else if (is.str(shadows)) {
17066
- var _types$shadows;
17067
- const types = {
17068
- basic: THREE__namespace.BasicShadowMap,
17069
- percentage: THREE__namespace.PCFShadowMap,
17070
- soft: THREE__namespace.PCFSoftShadowMap,
17071
- variance: THREE__namespace.VSMShadowMap
17072
- };
17073
- gl.shadowMap.type = (_types$shadows = types[shadows]) != null ? _types$shadows : THREE__namespace.PCFSoftShadowMap;
17074
- } else if (is.obj(shadows)) {
17075
- Object.assign(gl.shadowMap, shadows);
17076
- }
17077
- if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type) gl.shadowMap.needsUpdate = true;
17078
- }
17079
- THREE__namespace.ColorManagement.enabled = !legacy;
17080
-
17081
- // Set color space and tonemapping preferences
17082
- if (first) {
17083
- gl.outputColorSpace = linear ? THREE__namespace.LinearSRGBColorSpace : THREE__namespace.SRGBColorSpace;
17084
- gl.toneMapping = flat ? THREE__namespace.NoToneMapping : THREE__namespace.ACESFilmicToneMapping;
17085
- }
17086
-
17087
- // Update color management state
17088
- if (state.legacy !== legacy) state.set(() => ({
17089
- legacy
17090
- }));
17091
- if (state.linear !== linear) state.set(() => ({
17092
- linear
17093
- }));
17094
- if (state.flat !== flat) state.set(() => ({
17095
- flat
17096
- }));
17097
-
17098
- // Set gl props
17099
- if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, gl, shallowLoose)) applyProps(gl, glConfig);
17100
-
17101
- // Set locals
17102
- onCreated = onCreatedCallback;
17361
+ applyRootConfiguration(root, canvas, props);
17362
+ // Only a range, including the default one, follows the display: a fixed dpr never touches matchMedia
17363
+ if (!root.unwatchDpr && (props.dpr === undefined || Array.isArray(props.dpr))) root.unwatchDpr = followDpr(root);
17364
+ resolve(this);
17103
17365
  };
17104
- const fail = error => {
17105
- const failure = rejected(error);
17106
- root.ready = failure;
17107
- return failure;
17108
- };
17109
- const run = customRenderer => {
17366
+ const run = () => {
17110
17367
  try {
17111
- apply(customRenderer);
17112
- root.ready = fulfilled(undefined);
17113
- return fulfilled(this);
17368
+ var _store$getState$gl;
17369
+ const gl = (_store$getState$gl = store.getState().gl) != null ? _store$getState$gl : createRenderer(canvas, props.gl);
17370
+ // Only an async renderer factory makes configuration asynchronous
17371
+ if (isPromiseLike(gl)) Promise.resolve(gl).then(apply).catch(reject);else apply(gl);
17114
17372
  } catch (error) {
17115
- return fail(error);
17373
+ reject(error);
17116
17374
  }
17117
17375
  };
17118
-
17119
- // The renderer exists, so applying props is synchronous
17120
- if (store.getState().gl) return run();
17121
-
17122
- // Another configure is creating it. Chaining keeps them in call order
17123
- if (root.ready.status === 'pending') return tracked(root.ready.then(() => run()));
17124
-
17125
- // Only an async factory makes configure asynchronous
17126
- let customRenderer;
17127
- try {
17128
- customRenderer = typeof glConfig === 'function' ? glConfig(defaultProps) : glConfig;
17129
- } catch (error) {
17130
- return fail(error);
17131
- }
17132
- if (!isPromiseLike(customRenderer)) return run(customRenderer);
17133
- const configuring = tracked(customRenderer.then(run, fail));
17134
- root.ready = configuring;
17135
- return configuring;
17376
+ if (previous.status === 'pending') previous.then(run, reject);else run();
17377
+ return promise;
17136
17378
  },
17137
17379
  render(children) {
17138
- if (_roots.get(canvas) !== root) return store;
17139
- // Using the root cancels a teardown waiting on React
17140
- root.unmountClaim = null;
17141
- const element = /*#__PURE__*/jsxRuntime.jsx(Provider, {
17142
- store: store,
17143
- children: children,
17144
- onCreated: onCreated,
17145
- rootElement: canvas
17146
- });
17380
+ if (!transitionRoot(root, {
17381
+ type: 'use'
17382
+ })) return store;
17383
+
17384
+ // The root has to be configured before it can be rendered
17385
+ if (!store.getState().gl && root.ready.status === 'fulfilled') this.configure();
17386
+ if (root.ready.status === 'rejected') throw root.ready.reason;
17147
17387
  const commit = () => {
17148
- // The root may have been unmounted or claimed while we waited
17149
- if (_roots.get(canvas) !== root || root.unmountClaim) return;
17388
+ var _root$configuration$p;
17389
+ if (root.state.status !== 'open' || root.ready.status === 'rejected') return;
17390
+ // Wait for all queued configuration, including any queued while waiting
17391
+ if (root.ready.status === 'pending') {
17392
+ root.ready.then(commit, logRecoverableError);
17393
+ return;
17394
+ }
17395
+ const onCreated = (_root$configuration$p = root.configuration.previous) == null ? void 0 : _root$configuration$p.onCreated;
17396
+ const element = /*#__PURE__*/jsxRuntime.jsx(Provider, {
17397
+ store: store,
17398
+ children: children,
17399
+ onCreated: onCreated,
17400
+ rootElement: canvas
17401
+ });
17150
17402
  if (mounted) {
17151
17403
  reconciler.updateContainer(element, fiber, null, noop);
17152
17404
  return;
@@ -17158,22 +17410,11 @@ function createRoot(canvas) {
17158
17410
  // @ts-ignore - reconciler types are not maintained
17159
17411
  reconciler.flushSyncWork();
17160
17412
  };
17161
-
17162
- // The root has to be configured before it can be rendered
17163
- if (!store.getState().gl && root.ready.status === 'fulfilled') this.configure();
17164
- switch (root.ready.status) {
17165
- case 'pending':
17166
- root.ready.then(commit, logRecoverableError);
17167
- break;
17168
- case 'rejected':
17169
- throw root.ready.reason;
17170
- default:
17171
- commit();
17172
- }
17413
+ commit();
17173
17414
  return store;
17174
17415
  },
17175
17416
  unmount() {
17176
- unmountComponentAtNode(canvas);
17417
+ if (_roots.get(canvas) === root) unmountComponentAtNode(canvas);
17177
17418
  }
17178
17419
  };
17179
17420
  }
@@ -17204,147 +17445,51 @@ function Provider({
17204
17445
  children: children
17205
17446
  });
17206
17447
  }
17448
+ function attempt(step) {
17449
+ try {
17450
+ return step();
17451
+ } catch (error) {
17452
+ console.warn('[R3F] Error while unmounting root; teardown may be incomplete:', error);
17453
+ }
17454
+ }
17207
17455
  function unmountComponentAtNode(canvas, callback) {
17208
17456
  const root = _roots.get(canvas);
17209
- if (!root) return;
17457
+ const token = Symbol('unmount');
17458
+ if (!root || !transitionRoot(root, {
17459
+ type: 'unmount',
17460
+ token
17461
+ })) return;
17462
+ const teardown = () => {
17463
+ var _state$gl;
17464
+ // Configuration accepted before the unmount finishes first, so a renderer it creates is released too
17465
+ if (root.ready.status === 'pending') {
17466
+ root.ready.then(teardown, teardown);
17467
+ return;
17468
+ }
17469
+ // Refused when the root was used again since this unmount
17470
+ if (!transitionRoot(root, {
17471
+ type: 'dispose',
17472
+ token
17473
+ })) return;
17474
+ // Close the gates before invoking user cleanup code.
17475
+ _roots.delete(canvas);
17476
+ const state = root.store.getState();
17477
+ state.internal.active = false;
17210
17478
 
17211
- // Cleared by configure and render, which cancels the teardown
17212
- const claim = root.unmountClaim = Symbol('unmount');
17479
+ // Release what uses the renderer before the renderer itself
17480
+ attempt(() => root.unwatchDpr == null ? void 0 : root.unwatchDpr());
17481
+ attempt(() => state.events.disconnect == null ? void 0 : state.events.disconnect());
17482
+ if ((_state$gl = state.gl) != null && _state$gl.xr) attempt(() => state.xr.disconnect());
17483
+ if (state.scene) attempt(() => dispose(state.scene));
17484
+ const disposal = attempt(() => releaseRenderer(state.gl, root.ownsRenderer));
17485
+ if (isPromiseLike(disposal)) disposal.then(() => callback == null ? void 0 : callback(canvas));else callback == null ? void 0 : callback(canvas);
17486
+ };
17213
17487
  reconciler.updateContainer(null, root.fiber, null, () => {
17214
- if (root.unmountClaim !== claim) return;
17215
-
17488
+ // A root used again since the unmount keeps its new children
17489
+ if (root.state.status !== 'closing' || root.state.token !== token) return;
17216
17490
  // Effect cleanups flush before the next update
17217
- reconciler.updateContainer(null, root.fiber, null, () => {
17218
- if (root.unmountClaim !== claim) return;
17219
- const teardown = () => {
17220
- if (root.unmountClaim !== claim) return;
17221
- root.unmountClaim = null;
17222
- const state = root.store.getState();
17223
- state.internal.active = false;
17224
- try {
17225
- var _state$gl, _state$gl$renderLists, _state$gl2, _state$gl3;
17226
- state.events.disconnect == null ? void 0 : state.events.disconnect();
17227
- (_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();
17228
- (_state$gl2 = state.gl) == null ? void 0 : _state$gl2.forceContextLoss == null ? void 0 : _state$gl2.forceContextLoss();
17229
- if ((_state$gl3 = state.gl) != null && _state$gl3.xr) state.xr.disconnect();
17230
- dispose(state.scene);
17231
- } catch (e) {
17232
- /* ... */
17233
- }
17234
- _roots.delete(canvas);
17235
- if (callback) callback(canvas);
17236
- };
17237
-
17238
- // A renderer still being created has to exist before it can be disposed
17239
- if (root.ready.status === 'pending') root.ready.then(teardown, teardown);else teardown();
17240
- });
17241
- });
17242
- }
17243
- function createPortal(children, container, state) {
17244
- return /*#__PURE__*/jsxRuntime.jsx(Portal, {
17245
- children: children,
17246
- container: container,
17247
- state: state
17248
- });
17249
- }
17250
- function Portal({
17251
- state = {},
17252
- children,
17253
- container
17254
- }) {
17255
- /** This has to be a component because it would not be able to call useThree/useStore otherwise since
17256
- * if this is our environment, then we are not in r3f's renderer but in react-dom, it would trigger
17257
- * the "R3F hooks can only be used within the Canvas component!" warning:
17258
- * <Canvas>
17259
- * {createPortal(...)} */
17260
- const {
17261
- events,
17262
- size,
17263
- ...rest
17264
- } = state;
17265
- const previousRoot = useStore();
17266
- const [raycaster] = React__namespace.useState(() => new THREE__namespace.Raycaster());
17267
- const [pointer] = React__namespace.useState(() => new THREE__namespace.Vector2());
17268
- const inject = useMutableCallback((rootState, injectState) => {
17269
- let viewport = undefined;
17270
- if (injectState.camera && size) {
17271
- const camera = injectState.camera;
17272
- // Calculate the override viewport, if present
17273
- viewport = rootState.viewport.getCurrentViewport(camera, new THREE__namespace.Vector3(), size);
17274
- // Update the portal camera, if it differs from the previous layer
17275
- if (camera !== rootState.camera) updateCamera(camera, size);
17276
- }
17277
- return {
17278
- // The intersect consists of the previous root state
17279
- ...rootState,
17280
- ...injectState,
17281
- // Portals have their own scene, which forms the root, a raycaster and a pointer
17282
- scene: container,
17283
- raycaster,
17284
- pointer,
17285
- mouse: pointer,
17286
- // Their previous root is the layer before it
17287
- previousRoot,
17288
- // Events, size and viewport can be overridden by the inject layer
17289
- events: {
17290
- ...rootState.events,
17291
- ...injectState.events,
17292
- ...events
17293
- },
17294
- size: {
17295
- ...rootState.size,
17296
- ...size
17297
- },
17298
- viewport: {
17299
- ...rootState.viewport,
17300
- ...viewport
17301
- },
17302
- // Layers are allowed to override events
17303
- setEvents: events => injectState.set(state => ({
17304
- ...state,
17305
- events: {
17306
- ...state.events,
17307
- ...events
17308
- }
17309
- }))
17310
- };
17491
+ reconciler.updateContainer(null, root.fiber, null, teardown);
17311
17492
  });
17312
- const usePortalStore = React__namespace.useMemo(() => {
17313
- // Create a mirrored store, based on the previous root with a few overrides ...
17314
- const store = traditional.createWithEqualityFn((set, get) => ({
17315
- ...rest,
17316
- set,
17317
- get
17318
- }));
17319
-
17320
- // Subscribe to previous root-state and copy changes over to the mirrored portal-state
17321
- const onMutate = prev => store.setState(state => inject.current(prev, state));
17322
- onMutate(previousRoot.getState());
17323
- previousRoot.subscribe(onMutate);
17324
- return store;
17325
- // eslint-disable-next-line react-hooks/exhaustive-deps
17326
- }, [previousRoot, container]);
17327
- return (
17328
- /*#__PURE__*/
17329
- // @ts-ignore, reconciler types are not maintained
17330
- jsxRuntime.jsx(jsxRuntime.Fragment, {
17331
- children: reconciler.createPortal( /*#__PURE__*/jsxRuntime.jsx(context.Provider, {
17332
- value: usePortalStore,
17333
- children: children
17334
- }), usePortalStore, null)
17335
- })
17336
- );
17337
- }
17338
-
17339
- /**
17340
- * Force React to flush any updates inside the provided callback synchronously and immediately.
17341
- * All the same caveats documented for react-dom's `flushSync` apply here (see https://react.dev/reference/react-dom/flushSync).
17342
- * Nevertheless, sometimes one needs to render synchronously, for example to keep DOM and 3D changes in lock-step without
17343
- * having to revert to a non-React solution. Note: this will only flush updates within the `Canvas` root.
17344
- */
17345
- function flushSync(fn) {
17346
- // @ts-ignore - reconciler types are not maintained
17347
- return reconciler.flushSyncFromReconciler(fn);
17348
17493
  }
17349
17494
 
17350
17495
  function createSubs(callback, subs) {
@@ -17499,6 +17644,141 @@ function advance(timestamp, runGlobalEffects = true, state, frame) {
17499
17644
  if (runGlobalEffects) flushGlobalEffects('after', timestamp);
17500
17645
  }
17501
17646
 
17647
+ function createPortal(children, container, state) {
17648
+ return /*#__PURE__*/jsxRuntime.jsx(Portal, {
17649
+ children: children,
17650
+ container: container,
17651
+ state: state
17652
+ });
17653
+ }
17654
+ function initializePortal(options, initialState) {
17655
+ const {
17656
+ parentStore,
17657
+ container
17658
+ } = options;
17659
+ const portalStore = traditional.createWithEqualityFn((set, get) => ({
17660
+ ...initialState,
17661
+ set,
17662
+ get,
17663
+ // Create once with the store's setter, without retaining a state snapshot.
17664
+ setEvents: events => set(state => ({
17665
+ ...state,
17666
+ events: {
17667
+ ...state.events,
17668
+ ...events
17669
+ }
17670
+ }))
17671
+ }));
17672
+ const parentStateRef = {
17673
+ current: parentStore.getState()
17674
+ };
17675
+ portalStore.setState(portalState => inject(parentStateRef.current, portalState, options));
17676
+ return {
17677
+ parentStore,
17678
+ container,
17679
+ portalStore,
17680
+ parentStateRef
17681
+ };
17682
+ }
17683
+ function Portal({
17684
+ state = {},
17685
+ children,
17686
+ container
17687
+ }) {
17688
+ const parentStore = useStore();
17689
+ const {
17690
+ events,
17691
+ size,
17692
+ ...rest
17693
+ } = state;
17694
+ const [raycaster] = React__namespace.useState(() => new THREE__namespace.Raycaster());
17695
+ const [pointer] = React__namespace.useState(() => new THREE__namespace.Vector2());
17696
+ const options = {
17697
+ parentStore,
17698
+ container,
17699
+ raycaster,
17700
+ pointer,
17701
+ events,
17702
+ size
17703
+ };
17704
+ const injectRef = useMutableCallback((parentState, portalState) => inject(parentState, portalState, options));
17705
+
17706
+ // Initialize during render. Keep the store in state so Activity can preserve it.
17707
+ const [portal, setPortal] = React__namespace.useState(() => initializePortal(options, rest));
17708
+ if (portal.parentStore !== parentStore || portal.container !== container) {
17709
+ setPortal(initializePortal(options, rest));
17710
+ }
17711
+ const {
17712
+ portalStore,
17713
+ parentStateRef
17714
+ } = portal;
17715
+
17716
+ // Subscribe after commit and update the portal on each parent change.
17717
+ useIsomorphicLayoutEffect(() => {
17718
+ const update = parentState => {
17719
+ parentStateRef.current = parentState;
17720
+ portalStore.setState(portalState => injectRef.current(parentState, portalState));
17721
+ };
17722
+ const unsubscribe = parentStore.subscribe(update);
17723
+
17724
+ // Catch up on changes made before subscribing or while Activity was hidden.
17725
+ const parentState = parentStore.getState();
17726
+ if (parentState !== parentStateRef.current) update(parentState);
17727
+
17728
+ // Clean up when hidden, unmounted or switching stores.
17729
+ return unsubscribe;
17730
+ }, [parentStore, portalStore, parentStateRef]);
17731
+ return /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {
17732
+ children: reconciler.createPortal( /*#__PURE__*/jsxRuntime.jsx(context.Provider, {
17733
+ value: portalStore,
17734
+ children: children
17735
+ }), portalStore, null)
17736
+ });
17737
+ }
17738
+ function inject(parentState, portalState, options) {
17739
+ const {
17740
+ parentStore,
17741
+ container,
17742
+ raycaster,
17743
+ pointer,
17744
+ events,
17745
+ size
17746
+ } = options;
17747
+ let viewport = undefined;
17748
+ if (portalState.camera && size) {
17749
+ const camera = portalState.camera;
17750
+ // Calculate the override viewport, if present
17751
+ viewport = parentState.viewport.getCurrentViewport(camera, new THREE__namespace.Vector3(), size);
17752
+ // Update the portal camera, if it differs from the previous layer
17753
+ if (camera !== parentState.camera) updateCamera(camera, size);
17754
+ }
17755
+ return {
17756
+ // Inherit parent values and preserve the portal's own state.
17757
+ ...parentState,
17758
+ ...portalState,
17759
+ // Portals have their own scene, raycaster and pointer.
17760
+ scene: container,
17761
+ raycaster,
17762
+ pointer,
17763
+ mouse: pointer,
17764
+ previousRoot: parentStore,
17765
+ // Apply the portal's event, size and viewport overrides.
17766
+ events: {
17767
+ ...parentState.events,
17768
+ ...portalState.events,
17769
+ ...events
17770
+ },
17771
+ size: {
17772
+ ...parentState.size,
17773
+ ...size
17774
+ },
17775
+ viewport: {
17776
+ ...parentState.viewport,
17777
+ ...viewport
17778
+ }
17779
+ };
17780
+ }
17781
+
17502
17782
  const DOM_EVENTS = {
17503
17783
  onClick: ['click', false],
17504
17784
  onContextMenu: ['contextmenu', false],