@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.
@@ -1,24 +1,16 @@
1
1
  import * as React from 'react';
2
2
  import React__default from 'react';
3
3
  import * as THREE from 'three';
4
+ import { jsx, Fragment } from 'react/jsx-runtime';
4
5
  import { createWithEqualityFn } from 'zustand/traditional';
5
6
  import { suspend, preload, clear } from 'suspend-react';
7
+ import { useFiber, useContextBridge, traverseFiber, useActivityBridge as useActivityBridge$1 } from 'its-fine';
6
8
  import w1, { unstable_getCurrentPriorityLevel, unstable_IdlePriority, unstable_LowPriority, unstable_NormalPriority, unstable_UserBlockingPriority, unstable_ImmediatePriority, unstable_scheduleCallback } from 'scheduler';
7
- import { jsx, Fragment } from 'react/jsx-runtime';
8
- import { useFiber, useContextBridge, traverseFiber } from 'its-fine';
9
9
 
10
10
  var threeTypes = /*#__PURE__*/Object.freeze({
11
11
  __proto__: null
12
12
  });
13
13
 
14
- /**
15
- * Returns the instance's initial (outmost) root.
16
- */
17
- function findInitialRoot(instance) {
18
- let root = instance.root;
19
- while (root.getState().previousRoot) root = root.getState().previousRoot;
20
- return root;
21
- }
22
14
  /**
23
15
  * Safely flush async effects when testing, simulating a legacy root.
24
16
  * @deprecated Import from React instead. import { act } from 'react'
@@ -26,9 +18,6 @@ function findInitialRoot(instance) {
26
18
  // Reference with computed key to break Webpack static analysis
27
19
  // https://github.com/webpack/webpack/issues/14814
28
20
  const act = React['act' + ''];
29
- const isOrthographicCamera = def => def && def.isOrthographicCamera;
30
- const isRef = obj => obj && obj.hasOwnProperty('current');
31
- const isColorRepresentation = value => value != null && (typeof value === 'string' || typeof value === 'number' || value.isColor);
32
21
 
33
22
  /**
34
23
  * An SSR-friendly useLayoutEffect.
@@ -45,7 +34,6 @@ function useMutableCallback(fn) {
45
34
  useIsomorphicLayoutEffect(() => void (ref.current = fn), [fn]);
46
35
  return ref;
47
36
  }
48
- const noop = () => {};
49
37
  function Gate({
50
38
  promise,
51
39
  onSettled
@@ -60,7 +48,7 @@ function useGate() {
60
48
  const [promise, setPromise] = React.useState(null);
61
49
  const onSettled = React.useCallback(() => setPromise(null), []);
62
50
  // Keep the first promise, or a repeated call would loop
63
- const waitFor = React.useCallback(next => setPromise(current => current != null ? current : Promise.resolve(next).then(noop, noop)), []);
51
+ const waitFor = React.useCallback(next => setPromise(current => current != null ? current : Promise.resolve(next).then(() => {}, () => {})), []);
64
52
  const gate = promise ? /*#__PURE__*/jsx(React.Suspense, {
65
53
  fallback: null,
66
54
  children: /*#__PURE__*/jsx(Gate, {
@@ -70,23 +58,29 @@ function useGate() {
70
58
  }) : null;
71
59
  return [gate, waitFor];
72
60
  }
61
+
62
+ // Activity is optional on React 19.0/19.1. A computed key also supports strict ESM bundlers.
63
+ const useActivityBridge = React['Activity' + ''] ? useActivityBridge$1 : () => React.Fragment;
73
64
  /**
74
- * Bridges renderer Context and StrictMode from a primary renderer.
65
+ * Bridges Context, StrictMode, and Activity visibility from a primary renderer.
75
66
  */
76
67
  function useBridge() {
77
68
  const fiber = useFiber();
78
69
  const ContextBridge = useContextBridge();
70
+ const ActivityBridge = useActivityBridge();
79
71
  return React.useMemo(() => ({
80
72
  children
81
73
  }) => {
82
74
  const strict = !!traverseFiber(fiber, true, node => node.type === React.StrictMode);
83
75
  const Root = strict ? React.StrictMode : React.Fragment;
84
76
  return /*#__PURE__*/jsx(Root, {
85
- children: /*#__PURE__*/jsx(ContextBridge, {
86
- children: children
77
+ children: /*#__PURE__*/jsx(ActivityBridge, {
78
+ children: /*#__PURE__*/jsx(ContextBridge, {
79
+ children: children
80
+ })
87
81
  })
88
82
  });
89
- }, [fiber, ContextBridge]);
83
+ }, [fiber, ContextBridge, ActivityBridge]);
90
84
  }
91
85
  function Block({
92
86
  set
@@ -115,6 +109,19 @@ const ErrorBoundary = /* @__PURE__ */(_ErrorBoundary => (_ErrorBoundary = class
115
109
  }, _ErrorBoundary.getDerivedStateFromError = () => ({
116
110
  error: true
117
111
  }), _ErrorBoundary))();
112
+
113
+ /**
114
+ * Returns the instance's initial (outmost) root.
115
+ */
116
+ function findInitialRoot(instance) {
117
+ let root = instance.root;
118
+ while (root.getState().previousRoot) root = root.getState().previousRoot;
119
+ return root;
120
+ }
121
+ const isOrthographicCamera = def => def && def.isOrthographicCamera;
122
+ const isRef = obj => obj && obj.hasOwnProperty('current');
123
+ const isColorRepresentation = value => value != null && (typeof value === 'string' || typeof value === 'number' || value.isColor);
124
+ const noop = () => {};
118
125
  function calculateDpr(dpr) {
119
126
  var _window$devicePixelRa;
120
127
  // Err on the side of progress by assuming 2x dpr if we can't detect it
@@ -123,6 +130,43 @@ function calculateDpr(dpr) {
123
130
  return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
124
131
  }
125
132
 
133
+ /**
134
+ * Calls `onChange` whenever window.devicePixelRatio changes: moving to another display or zooming.
135
+ * Neither resizes the canvas, so resize observers won't see it. A resolution query only matches the
136
+ * current ratio, so it is re-created after every change. Returns the unsubscribe function.
137
+ */
138
+ function watchDpr(onChange) {
139
+ if (typeof window === 'undefined' || typeof window.matchMedia !== 'function' || !window.devicePixelRatio) {
140
+ return () => {};
141
+ }
142
+
143
+ // Partial matchMedia polyfills (test setups, older environments) may return no listener methods,
144
+ // or throw on a query they can't parse. Following the ratio is best-effort: it runs inside
145
+ // configuration, which must not fail because of it
146
+ let query;
147
+ const listen = () => {
148
+ try {
149
+ var _query, _query2;
150
+ query = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
151
+ if (typeof ((_query = query) == null ? void 0 : _query.addEventListener) === 'function') query.addEventListener('change', handleChange);else (_query2 = query) == null ? void 0 : _query2.addListener == null ? void 0 : _query2.addListener(handleChange);
152
+ } catch {
153
+ query = undefined;
154
+ }
155
+ };
156
+ const unlisten = () => {
157
+ var _query3, _query4;
158
+ if (typeof ((_query3 = query) == null ? void 0 : _query3.removeEventListener) === 'function') query.removeEventListener('change', handleChange);else (_query4 = query) == null ? void 0 : _query4.removeListener == null ? void 0 : _query4.removeListener(handleChange);
159
+ query = undefined;
160
+ };
161
+ const handleChange = () => {
162
+ unlisten();
163
+ listen();
164
+ onChange();
165
+ };
166
+ listen();
167
+ return unlisten;
168
+ }
169
+
126
170
  /**
127
171
  * Returns instance root state
128
172
  */
@@ -232,6 +276,7 @@ function prepare(target, root, type, props) {
232
276
  object,
233
277
  eventCount: 0,
234
278
  handlers: {},
279
+ previousVisible: undefined,
235
280
  isHidden: false
236
281
  };
237
282
  if (object) object.__r3f = instance;
@@ -415,6 +460,12 @@ function applyProps(object, props) {
415
460
  // Ignore setting undefined props
416
461
  // https://github.com/pmndrs/react-three-fiber/issues/274
417
462
  if (value === undefined) continue;
463
+
464
+ // Update the visibility to restore without revealing a hidden instance.
465
+ if (prop === 'visible' && instance != null && instance.isHidden && instance.previousVisible !== undefined) {
466
+ instance.previousVisible = value;
467
+ continue;
468
+ }
418
469
  let {
419
470
  root,
420
471
  key,
@@ -565,11 +616,24 @@ function swapInteractivity(store, object, newObject) {
565
616
  internal.hovered.set(makeId(next), next);
566
617
  }
567
618
  });
619
+
620
+ // A capture is keyed by its eventObject, but it also carries the intersection that is replayed into
621
+ // every subsequent event. Both must follow the new instance, or the capture resolves to the discarded object.
568
622
  internal.capturedMap.forEach(captures => {
569
- const captureData = captures.get(object);
570
- if (captureData) {
571
- captures.delete(object);
572
- captures.set(newObject, captureData);
623
+ for (const [key, captureData] of Array.from(captures)) {
624
+ const {
625
+ intersection
626
+ } = captureData;
627
+ if (key !== object && intersection.object !== object && intersection.eventObject !== object) continue;
628
+ if (key === object) captures.delete(key);
629
+ captures.set(key === object ? newObject : key, {
630
+ ...captureData,
631
+ intersection: {
632
+ ...intersection,
633
+ object: intersection.object === object ? newObject : intersection.object,
634
+ eventObject: intersection.eventObject === object ? newObject : intersection.eventObject
635
+ }
636
+ });
573
637
  }
574
638
  });
575
639
  }
@@ -968,7 +1032,109 @@ function createEvents(store) {
968
1032
  };
969
1033
  }
970
1034
 
1035
+ const isPromiseLike = value => typeof (value == null ? void 0 : value.then) === 'function';
1036
+
1037
+ /** A promise tagged with its state, the protocol React's `use` reads */
1038
+
1039
+ const fulfilled = value => Object.assign(Promise.resolve(value), {
1040
+ status: 'fulfilled',
1041
+ value
1042
+ });
1043
+ const rejected = reason => {
1044
+ const promise = Promise.reject(reason);
1045
+ promise.catch(() => {}); // the reason is read from `status`
1046
+ return Object.assign(promise, {
1047
+ status: 'rejected',
1048
+ reason
1049
+ });
1050
+ };
1051
+
1052
+ /** A pending promise, settled once from outside. Its `status` updates the moment it settles */
1053
+ const deferred = () => {
1054
+ let resolve;
1055
+ let reject;
1056
+ const promise = new Promise((yes, no) => {
1057
+ resolve = value => {
1058
+ Object.assign(promise, {
1059
+ status: 'fulfilled',
1060
+ value
1061
+ });
1062
+ yes(value);
1063
+ };
1064
+ reject = reason => {
1065
+ Object.assign(promise, {
1066
+ status: 'rejected',
1067
+ reason
1068
+ });
1069
+ no(reason);
1070
+ };
1071
+ });
1072
+ promise.status = 'pending';
1073
+ promise.catch(() => {}); // the reason is read from `status`
1074
+ return {
1075
+ promise,
1076
+ resolve,
1077
+ reject
1078
+ };
1079
+ };
1080
+
1081
+ /** Renderer creation and resource release. Root lifetime decides when release is safe. */
971
1082
  const isRenderer = def => !!(def != null && def.render);
1083
+
1084
+ /** The renderer to configure, or a promise for one from an async factory */
1085
+ function createRenderer(canvas, gl) {
1086
+ const defaults = {
1087
+ canvas,
1088
+ powerPreference: 'high-performance',
1089
+ antialias: true,
1090
+ alpha: true
1091
+ };
1092
+ if (typeof gl === 'function') return gl(defaults);
1093
+ if (isRenderer(gl)) return gl;
1094
+ return new THREE.WebGLRenderer({
1095
+ ...defaults,
1096
+ ...gl
1097
+ });
1098
+ }
1099
+
1100
+ /**
1101
+ * Frees a renderer R3F built. WebGLRenderer.dispose() releases programs and caches but keeps its
1102
+ * context until garbage collection, and browsers cap live WebGL contexts, so the context is lost
1103
+ * after it. A WebGPURenderer (from a factory) releases its device and context inside dispose().
1104
+ */
1105
+ function disposeRenderer(gl) {
1106
+ // Avoid starting initialization through three's dispose(). A factory must await init()
1107
+ // before returning its renderer so readiness includes initialization and teardown can wait.
1108
+ if ((gl.hasInitialized == null ? void 0 : gl.hasInitialized()) === false) return;
1109
+ const disposed = attempt$1(() => {
1110
+ var _gl$renderLists;
1111
+ return gl.dispose ? gl.dispose() : (_gl$renderLists = gl.renderLists) == null ? void 0 : _gl$renderLists.dispose == null ? void 0 : _gl$renderLists.dispose();
1112
+ });
1113
+ // WebGPURenderer.dispose() is async from three r186
1114
+ if (isPromiseLike(disposed)) {
1115
+ return Promise.resolve(disposed).catch(error => console.warn('[R3F] Error disposing renderer', error)).then(() => attempt$1(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss()));
1116
+ }
1117
+ attempt$1(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
1118
+ }
1119
+
1120
+ /** Owned renderers are disposed. Caller-provided renderers release caches and their context. */
1121
+ function releaseRenderer(gl, owned) {
1122
+ if (!gl) return;
1123
+ if (owned) return disposeRenderer(gl);
1124
+ attempt$1(() => {
1125
+ var _gl$renderLists2;
1126
+ return (_gl$renderLists2 = gl.renderLists) == null ? void 0 : _gl$renderLists2.dispose == null ? void 0 : _gl$renderLists2.dispose();
1127
+ });
1128
+ attempt$1(() => gl.forceContextLoss == null ? void 0 : gl.forceContextLoss());
1129
+ }
1130
+ function attempt$1(step) {
1131
+ try {
1132
+ return step();
1133
+ } catch (error) {
1134
+ console.warn('[R3F] Error while unmounting root; teardown may be incomplete:', error);
1135
+ }
1136
+ }
1137
+
972
1138
  const context = /* @__PURE__ */React.createContext(null);
973
1139
  const createStore = (invalidate, advance) => {
974
1140
  const rootStore = createWithEqualityFn((set, get) => {
@@ -1263,7 +1429,7 @@ function useGraph(object) {
1263
1429
  return React.useMemo(() => buildGraph(object), [object]);
1264
1430
  }
1265
1431
  const memoizedLoaders = new WeakMap();
1266
- const isConstructor$1 = value => {
1432
+ const isConstructor = value => {
1267
1433
  var _value$prototype;
1268
1434
  return typeof value === 'function' && (value == null ? void 0 : (_value$prototype = value.prototype) == null ? void 0 : _value$prototype.constructor) === value;
1269
1435
  };
@@ -1272,7 +1438,7 @@ function loadingFn(extensions, onProgress) {
1272
1438
  let loader;
1273
1439
 
1274
1440
  // Construct and cache loader if constructor was passed
1275
- if (isConstructor$1(Proto)) {
1441
+ if (isConstructor(Proto)) {
1276
1442
  loader = memoizedLoaders.get(Proto);
1277
1443
  if (!loader) {
1278
1444
  loader = new Proto();
@@ -1339,9 +1505,298 @@ useLoader.clear = function (loader, input) {
1339
1505
  e = 2,
1340
1506
  i$1 = 268435456;
1341
1507
 
1508
+ // Shim for OffscreenCanvas since it was removed from DOM types
1509
+ // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/54988
1510
+
1511
+ /** What a root has applied, so its next configuration applies only what changed */
1512
+
1513
+ const shallow = {
1514
+ objects: 'shallow'
1515
+ };
1516
+ function computeInitialSize(canvas, size) {
1517
+ if (!size && typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1518
+ const {
1519
+ width,
1520
+ height,
1521
+ top,
1522
+ left
1523
+ } = canvas.parentElement.getBoundingClientRect();
1524
+ return {
1525
+ width,
1526
+ height,
1527
+ top,
1528
+ left
1529
+ };
1530
+ } else if (!size && typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
1531
+ return {
1532
+ width: canvas.width,
1533
+ height: canvas.height,
1534
+ top: 0,
1535
+ left: 0
1536
+ };
1537
+ }
1538
+ return {
1539
+ width: 0,
1540
+ height: 0,
1541
+ top: 0,
1542
+ left: 0,
1543
+ ...size
1544
+ };
1545
+ }
1546
+
1547
+ /**
1548
+ * Applies the inputs that changed since the last configuration applied in full, to a root whose
1549
+ * renderer is installed. A runtime change, such as setFrameloop or an edit to gl.shadowMap, lasts
1550
+ * until its input changes. The pixel ratio instead follows the device on every call. Objects and
1551
+ * arrays compare shallowly, so an equal inline value is unchanged.
1552
+ */
1553
+ function applyRootConfiguration({
1554
+ store,
1555
+ configuration
1556
+ }, canvas, props) {
1557
+ const {
1558
+ gl: glConfig,
1559
+ scene: sceneOptions,
1560
+ camera: cameraOptions,
1561
+ shadows = false,
1562
+ linear = false,
1563
+ flat = false,
1564
+ legacy = false,
1565
+ frameloop = 'always',
1566
+ dpr = [1, 2],
1567
+ performance,
1568
+ onPointerMissed
1569
+ } = props;
1570
+ const size = computeInitialSize(canvas, props.size);
1571
+ const next = {
1572
+ ...props,
1573
+ shadows,
1574
+ linear,
1575
+ flat,
1576
+ legacy,
1577
+ frameloop,
1578
+ size
1579
+ };
1580
+ const state = store.getState();
1581
+ const gl = state.gl;
1582
+ const last = configuration.previous;
1583
+ const changed = key => !last || !is.equ(next[key], last[key], shallow);
1584
+ // A failure can leave inputs partly applied, so the next configuration applies all of them
1585
+ configuration.previous = undefined;
1586
+
1587
+ // Set up raycaster (one time only!)
1588
+ let raycaster = state.raycaster;
1589
+ if (!raycaster) state.set({
1590
+ raycaster: raycaster = new THREE.Raycaster()
1591
+ });
1592
+ if (changed('raycaster')) {
1593
+ var _props$raycaster;
1594
+ const {
1595
+ params,
1596
+ ...options
1597
+ } = (_props$raycaster = props.raycaster) != null ? _props$raycaster : {};
1598
+ applyProps(raycaster, {
1599
+ ...options,
1600
+ params: {
1601
+ ...raycaster.params,
1602
+ ...params
1603
+ }
1604
+ });
1605
+ }
1606
+
1607
+ // Create default camera, don't overwrite any user-set state
1608
+ if (!state.camera || state.camera === configuration.camera && changed('camera')) {
1609
+ configuration.camera = cameraOptions;
1610
+ const isCamera = cameraOptions == null ? void 0 : cameraOptions.isCamera;
1611
+ const camera = isCamera ? cameraOptions : props.orthographic ? new THREE.OrthographicCamera(0, 0, 0, 0, 0.1, 1000) : new THREE.PerspectiveCamera(75, 0, 0.1, 1000);
1612
+ if (!isCamera) {
1613
+ camera.position.z = 5;
1614
+ if (cameraOptions) {
1615
+ applyProps(camera, cameraOptions);
1616
+ // Preserve user-defined frustum if possible
1617
+ // https://github.com/pmndrs/react-three-fiber/issues/3160
1618
+ if (!camera.manual) {
1619
+ if ('aspect' in cameraOptions || 'left' in cameraOptions || 'right' in cameraOptions || 'bottom' in cameraOptions || 'top' in cameraOptions) {
1620
+ camera.manual = true;
1621
+ camera.updateProjectionMatrix();
1622
+ }
1623
+ }
1624
+ }
1625
+ // Always look at center by default
1626
+ if (!state.camera && !(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
1627
+ }
1628
+ state.set({
1629
+ camera
1630
+ });
1631
+
1632
+ // Configure raycaster
1633
+ // https://github.com/pmndrs/react-xr/issues/300
1634
+ raycaster.camera = camera;
1635
+ }
1636
+
1637
+ // Set up scene (one time only!)
1638
+ if (!state.scene) {
1639
+ let scene;
1640
+ if (sceneOptions != null && sceneOptions.isScene) {
1641
+ scene = sceneOptions;
1642
+ prepare(scene, store, '', {});
1643
+ } else {
1644
+ scene = new THREE.Scene();
1645
+ prepare(scene, store, '', {});
1646
+ if (sceneOptions) applyProps(scene, sceneOptions);
1647
+ }
1648
+ state.set({
1649
+ scene
1650
+ });
1651
+ }
1652
+
1653
+ // Store events internally
1654
+ if (props.events && !state.events.handlers) state.set({
1655
+ events: props.events(store)
1656
+ });
1657
+ if (changed('size')) state.setSize(size.width, size.height, size.top, size.left);
1658
+ // The ratio follows window.devicePixelRatio, which changes with browser zoom or another display
1659
+ if (state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
1660
+ if (changed('frameloop')) state.setFrameloop(frameloop);
1661
+ if (changed('onPointerMissed')) state.set({
1662
+ onPointerMissed
1663
+ });
1664
+ // Performance options merge, so removing them keeps the current settings
1665
+ if (performance && changed('performance')) {
1666
+ state.set(state => ({
1667
+ performance: {
1668
+ ...state.performance,
1669
+ ...performance
1670
+ }
1671
+ }));
1672
+ }
1673
+
1674
+ // Set up XR (one time only!)
1675
+ if (!state.xr) {
1676
+ var _gl$xr;
1677
+ // Handle frame behavior in WebXR
1678
+ const handleXRFrame = (timestamp, frame) => {
1679
+ const state = store.getState();
1680
+ if (state.frameloop === 'never') return;
1681
+ advance(timestamp, true, state, frame);
1682
+ };
1683
+
1684
+ // Toggle render switching on session
1685
+ const handleSessionChange = () => {
1686
+ const state = store.getState();
1687
+ state.gl.xr.enabled = state.gl.xr.isPresenting;
1688
+ state.gl.xr.setAnimationLoop(state.gl.xr.isPresenting ? handleXRFrame : null);
1689
+ if (!state.gl.xr.isPresenting) invalidate(state);
1690
+ };
1691
+
1692
+ // WebXR session manager
1693
+ const xr = {
1694
+ connect() {
1695
+ const gl = store.getState().gl;
1696
+ gl.xr.addEventListener('sessionstart', handleSessionChange);
1697
+ gl.xr.addEventListener('sessionend', handleSessionChange);
1698
+ },
1699
+ disconnect() {
1700
+ const gl = store.getState().gl;
1701
+ gl.xr.removeEventListener('sessionstart', handleSessionChange);
1702
+ gl.xr.removeEventListener('sessionend', handleSessionChange);
1703
+ }
1704
+ };
1705
+
1706
+ // Subscribe to WebXR session events
1707
+ if (typeof ((_gl$xr = gl.xr) == null ? void 0 : _gl$xr.addEventListener) === 'function') xr.connect();
1708
+ state.set({
1709
+ xr
1710
+ });
1711
+ }
1712
+
1713
+ // Set shadowmap
1714
+ if (gl.shadowMap && changed('shadows')) {
1715
+ const oldEnabled = gl.shadowMap.enabled;
1716
+ const oldType = gl.shadowMap.type;
1717
+ gl.shadowMap.enabled = !!shadows;
1718
+ if (is.boo(shadows)) {
1719
+ gl.shadowMap.type = THREE.PCFSoftShadowMap;
1720
+ } else if (is.str(shadows)) {
1721
+ var _types$shadows;
1722
+ const types = {
1723
+ basic: THREE.BasicShadowMap,
1724
+ percentage: THREE.PCFShadowMap,
1725
+ soft: THREE.PCFSoftShadowMap,
1726
+ variance: THREE.VSMShadowMap
1727
+ };
1728
+ gl.shadowMap.type = (_types$shadows = types[shadows]) != null ? _types$shadows : THREE.PCFSoftShadowMap;
1729
+ } else if (is.obj(shadows)) {
1730
+ Object.assign(gl.shadowMap, shadows);
1731
+ }
1732
+ if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type) gl.shadowMap.needsUpdate = true;
1733
+ }
1734
+
1735
+ // Explicit renderer options take precedence over the color shorthands
1736
+ const glProps = glConfig && !is.fun(glConfig) && !isRenderer(glConfig) ? glConfig : undefined;
1737
+ if (changed('legacy')) {
1738
+ THREE.ColorManagement.enabled = !legacy;
1739
+ state.set({
1740
+ legacy
1741
+ });
1742
+ }
1743
+ if (changed('linear')) {
1744
+ if ((glProps == null ? void 0 : glProps.outputColorSpace) === undefined) {
1745
+ gl.outputColorSpace = linear ? THREE.LinearSRGBColorSpace : THREE.SRGBColorSpace;
1746
+ }
1747
+ state.set({
1748
+ linear
1749
+ });
1750
+ }
1751
+ if (changed('flat')) {
1752
+ if ((glProps == null ? void 0 : glProps.toneMapping) === undefined) gl.toneMapping = flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping;
1753
+ state.set({
1754
+ flat
1755
+ });
1756
+ }
1757
+
1758
+ // Set gl props
1759
+ if (glProps && changed('gl')) applyProps(gl, glProps);
1760
+ configuration.previous = next;
1761
+ }
1762
+
1763
+ /**
1764
+ * Moving to another display or zooming changes window.devicePixelRatio without resizing the canvas,
1765
+ * so nothing configures the root again. Resolves the ratio the way configuration does, from the last
1766
+ * applied dpr. Returns the function that stops following.
1767
+ */
1768
+ function followDpr(root) {
1769
+ const xr = () => {
1770
+ var _root$store$getState$;
1771
+ return (_root$store$getState$ = root.store.getState().gl) == null ? void 0 : _root$store$getState$.xr;
1772
+ };
1773
+ const follow = () => {
1774
+ var _xr, _xr2, _xr3, _last$dpr;
1775
+ const state = root.store.getState();
1776
+ // three doesn't resize during XR and restores its own pixel ratio once the session ends. It holds
1777
+ // the session before it sets isPresenting. Listeners are deduplicated, so this waits once
1778
+ if ((_xr = xr()) != null && _xr.isPresenting || (_xr2 = xr()) != null && _xr2.getSession != null && _xr2.getSession()) {
1779
+ xr().addEventListener('sessionend', follow);
1780
+ return;
1781
+ }
1782
+ (_xr3 = xr()) == null ? void 0 : _xr3.removeEventListener == null ? void 0 : _xr3.removeEventListener('sessionend', follow);
1783
+ // A failed configuration applies everything on the next one
1784
+ const last = root.configuration.previous;
1785
+ if (!last) return;
1786
+ const dpr = (_last$dpr = last.dpr) != null ? _last$dpr : [1, 2];
1787
+ if (state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
1788
+ };
1789
+ const unwatch = watchDpr(follow);
1790
+ return () => {
1791
+ var _xr4;
1792
+ unwatch();
1793
+ (_xr4 = xr()) == null ? void 0 : _xr4.removeEventListener == null ? void 0 : _xr4.removeEventListener('sessionend', follow);
1794
+ };
1795
+ }
1796
+
1342
1797
  var packageData = {
1343
1798
  name: "@react-three/fiber",
1344
- version: "9.8.0",
1799
+ version: "9.8.2",
1345
1800
  description: "A React renderer for Threejs",
1346
1801
  keywords: [
1347
1802
  "react",
@@ -1373,6 +1828,12 @@ var packageData = {
1373
1828
  module: "dist/react-three-fiber.esm.js",
1374
1829
  types: "dist/react-three-fiber.cjs.d.ts",
1375
1830
  "react-native": "native/dist/react-three-fiber-native.cjs.js",
1831
+ files: [
1832
+ "dist",
1833
+ "native",
1834
+ "react-reconciler",
1835
+ "CHANGELOG.md"
1836
+ ],
1376
1837
  sideEffects: false,
1377
1838
  preconstruct: {
1378
1839
  entrypoints: [
@@ -1392,7 +1853,7 @@ var packageData = {
1392
1853
  "@types/webxr": "*",
1393
1854
  "base64-js": "^1.5.1",
1394
1855
  buffer: "^6.0.3",
1395
- "its-fine": "^2.0.0",
1856
+ "its-fine": "^2.1.1",
1396
1857
  "react-use-measure": "^2.1.7",
1397
1858
  scheduler: "^0.28.0",
1398
1859
  "suspend-react": "^0.1.3",
@@ -16113,6 +16574,27 @@ function G1() {
16113
16574
  var J1 = G1();
16114
16575
  const Z1 = $1(J1);
16115
16576
 
16577
+ /** Constructor registration and lookup for JSX elements. */
16578
+
16579
+ const catalogue = {};
16580
+ let i = 0;
16581
+ const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
16582
+ function hasConstructor(name) {
16583
+ return name in catalogue;
16584
+ }
16585
+ function resolveConstructor(name) {
16586
+ return catalogue[name];
16587
+ }
16588
+ function extend(objects) {
16589
+ if (typeof objects === 'function') {
16590
+ const Component = `${i++}`;
16591
+ catalogue[Component] = objects;
16592
+ return Component;
16593
+ } else {
16594
+ Object.assign(catalogue, objects);
16595
+ }
16596
+ }
16597
+
16116
16598
  function createReconciler(config) {
16117
16599
  const reconciler = Z1(config);
16118
16600
 
@@ -16126,24 +16608,11 @@ const NoEventPriority = 0;
16126
16608
  // https://github.com/pmndrs/react-three-fiber/pull/2931
16127
16609
  // https://github.com/microsoft/TypeScript/issues/37079
16128
16610
 
16129
- const catalogue = {};
16130
16611
  const PREFIX_REGEX = /^three(?=[A-Z])/;
16131
- const toPascalCase = type => `${type[0].toUpperCase()}${type.slice(1)}`;
16132
- let i = 0;
16133
- const isConstructor = object => typeof object === 'function';
16134
- function extend(objects) {
16135
- if (isConstructor(objects)) {
16136
- const Component = `${i++}`;
16137
- catalogue[Component] = objects;
16138
- return Component;
16139
- } else {
16140
- Object.assign(catalogue, objects);
16141
- }
16142
- }
16143
16612
  function validateInstance(type, props) {
16144
16613
  // Get target from catalogue
16145
16614
  const name = toPascalCase(type);
16146
- const target = catalogue[name];
16615
+ const target = resolveConstructor(name);
16147
16616
 
16148
16617
  // Validate element target
16149
16618
  if (type !== 'primitive' && !target) throw new Error(`R3F: ${name} is not part of the THREE namespace! Did you forget to extend? See: https://docs.pmnd.rs/react-three-fiber/api/objects#using-3rd-party-objects-declaratively`);
@@ -16157,7 +16626,7 @@ function validateInstance(type, props) {
16157
16626
  function createInstance(type, props, root) {
16158
16627
  var _props$object;
16159
16628
  // Remove three* prefix from elements if native element not present
16160
- type = toPascalCase(type) in catalogue ? type : type.replace(PREFIX_REGEX, '');
16629
+ type = hasConstructor(toPascalCase(type)) ? type : type.replace(PREFIX_REGEX, '');
16161
16630
  validateInstance(type, props);
16162
16631
 
16163
16632
  // Regenerate the R3F instance for primitives to simulate a new object
@@ -16170,6 +16639,7 @@ function hideInstance(instance) {
16170
16639
  if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
16171
16640
  detach(instance.parent, instance);
16172
16641
  } else if (isObject3D(instance.object)) {
16642
+ instance.previousVisible = instance.object.visible;
16173
16643
  instance.object.visible = false;
16174
16644
  }
16175
16645
  instance.isHidden = true;
@@ -16181,9 +16651,11 @@ function unhideInstance(instance) {
16181
16651
  var _instance$parent2;
16182
16652
  if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
16183
16653
  attach(instance.parent, instance);
16184
- } else if (isObject3D(instance.object) && instance.props.visible !== false) {
16185
- instance.object.visible = true;
16654
+ } else if (isObject3D(instance.object)) {
16655
+ var _instance$previousVis;
16656
+ instance.object.visible = (_instance$previousVis = instance.previousVisible) != null ? _instance$previousVis : true;
16186
16657
  }
16658
+ instance.previousVisible = undefined;
16187
16659
  instance.isHidden = false;
16188
16660
  invalidateInstance(instance);
16189
16661
  }
@@ -16201,7 +16673,7 @@ function handleContainerEffects(parent, child, beforeChild) {
16201
16673
  if (!child.object) {
16202
16674
  var _child$props$object, _child$props$args;
16203
16675
  // Get target from catalogue
16204
- const target = catalogue[toPascalCase(child.type)];
16676
+ const target = resolveConstructor(toPascalCase(child.type));
16205
16677
 
16206
16678
  // Create object
16207
16679
  child.object = (_child$props$object = child.props.object) != null ? _child$props$object : new target(...((_child$props$args = child.props.args) != null ? _child$props$args : []));
@@ -16298,6 +16770,9 @@ function disposeOnIdle(object) {
16298
16770
  function removeChild(parent, child, dispose) {
16299
16771
  if (!child) return;
16300
16772
 
16773
+ // Unlinking clears the parent, which invalidateInstance checks, so remember it
16774
+ const wasLinked = !!child.parent;
16775
+
16301
16776
  // Unlink instances
16302
16777
  child.parent = null;
16303
16778
  const childIndex = parent.children.indexOf(child);
@@ -16335,7 +16810,11 @@ function removeChild(parent, child, dispose) {
16335
16810
  }
16336
16811
 
16337
16812
  // Tree was updated, request a frame for top-level instance
16338
- if (dispose === undefined) invalidateInstance(child);
16813
+ if (dispose === undefined && wasLinked) {
16814
+ var _child$root;
16815
+ const state = (_child$root = child.root) == null ? void 0 : _child$root.getState == null ? void 0 : _child$root.getState();
16816
+ if (state && state.internal.frames === 0) state.invalidate();
16817
+ }
16339
16818
  }
16340
16819
  function setFiberRef(fiber, publicInstance) {
16341
16820
  for (const _fiber of [fiber, fiber.alternate]) {
@@ -16396,7 +16875,7 @@ function swapReconstructedInstances() {
16396
16875
  if (parent) {
16397
16876
  var _instance$props$objec, _instance$props$args;
16398
16877
  // Get target from catalogue
16399
- const target = catalogue[toPascalCase(instance.type)];
16878
+ const target = resolveConstructor(toPascalCase(instance.type));
16400
16879
 
16401
16880
  // Create object
16402
16881
  const prevObject = instance.object;
@@ -16728,76 +17207,49 @@ const reconciler = /* @__PURE__ */createReconciler({
16728
17207
  getSuspendedCommitReason: (_state, _rootContainer) => null
16729
17208
  });
16730
17209
 
16731
- const isPromiseLike = value => typeof (value == null ? void 0 : value.then) === 'function';
16732
-
16733
- /** A promise tagged with its state, the protocol React's `use` reads */
16734
-
16735
- const fulfilled = value => Object.assign(Promise.resolve(value), {
16736
- status: 'fulfilled',
16737
- value
16738
- });
16739
- const rejected = reason => {
16740
- const promise = Promise.reject(reason);
16741
- promise.catch(() => {}); // the reason is read from `status`
16742
- return Object.assign(promise, {
16743
- status: 'rejected',
16744
- reason
16745
- });
16746
- };
16747
-
16748
- /** Tags `promise` in place once it settles. */
16749
- const tracked = promise => {
16750
- const result = Promise.resolve(promise);
16751
- result.status = 'pending';
16752
- // Statement bodies: returning `result` would make this derived promise adopt its rejection
16753
- result.then(value => {
16754
- Object.assign(result, {
16755
- status: 'fulfilled',
16756
- value
16757
- });
16758
- }, reason => {
16759
- Object.assign(result, {
16760
- status: 'rejected',
16761
- reason
16762
- });
16763
- });
16764
- return result;
16765
- };
17210
+ /**
17211
+ * Force React to flush any updates inside the provided callback synchronously and immediately.
17212
+ * All the same caveats documented for react-dom's `flushSync` apply here (see https://react.dev/reference/react-dom/flushSync).
17213
+ * Nevertheless, sometimes one needs to render synchronously, for example to keep DOM and 3D changes in lock-step without
17214
+ * having to revert to a non-React solution. Note: this will only flush updates within the `Canvas` root.
17215
+ */
17216
+ function flushSync(fn) {
17217
+ // @ts-ignore - reconciler types are not maintained
17218
+ return reconciler.flushSyncFromReconciler(fn);
17219
+ }
16766
17220
 
16767
17221
  const _roots = new Map();
16768
- const shallowLoose = {
16769
- objects: 'shallow',
16770
- strict: false
16771
- };
16772
- function computeInitialSize(canvas, size) {
16773
- if (!size && typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
16774
- const {
16775
- width,
16776
- height,
16777
- top,
16778
- left
16779
- } = canvas.parentElement.getBoundingClientRect();
16780
- return {
16781
- width,
16782
- height,
16783
- top,
16784
- left
16785
- };
16786
- } else if (!size && typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
16787
- return {
16788
- width: canvas.width,
16789
- height: canvas.height,
16790
- top: 0,
16791
- left: 0
16792
- };
17222
+ /**
17223
+ * open -> closing -> disposed
17224
+ * | use
17225
+ * v
17226
+ * open
17227
+ *
17228
+ * Only closing can be cancelled. Each unmount carries a token, so only the latest one can dispose.
17229
+ */
17230
+ function transitionRoot(root, event) {
17231
+ const state = root.state;
17232
+ switch (event.type) {
17233
+ case 'use':
17234
+ if (state.status === 'disposed') return false;
17235
+ root.state = {
17236
+ status: 'open'
17237
+ };
17238
+ return true;
17239
+ case 'unmount':
17240
+ if (state.status === 'disposed') return false;
17241
+ root.state = {
17242
+ status: 'closing',
17243
+ token: event.token
17244
+ };
17245
+ return true;
17246
+ case 'dispose':
17247
+ if (state.status !== 'closing' || state.token !== event.token) return false;
17248
+ root.state = {
17249
+ status: 'disposed'
17250
+ };
17251
+ return true;
16793
17252
  }
16794
- return {
16795
- width: 0,
16796
- height: 0,
16797
- top: 0,
16798
- left: 0,
16799
- ...size
16800
- };
16801
17253
  }
16802
17254
  function createRoot(canvas) {
16803
17255
  // Check against mistaken use of createRoot
@@ -16842,286 +17294,86 @@ function createRoot(canvas) {
16842
17294
  const root = prevRoot || {
16843
17295
  fiber,
16844
17296
  store,
16845
- unmountClaim: null,
16846
- ready: fulfilled(undefined)
17297
+ state: {
17298
+ status: 'open'
17299
+ },
17300
+ ready: fulfilled(undefined),
17301
+ configuration: {},
17302
+ ownsRenderer: false
16847
17303
  };
16848
17304
  if (!prevRoot) _roots.set(canvas, root);
16849
-
16850
- // Locals
16851
- let onCreated;
16852
- let lastCamera;
16853
17305
  let mounted = false;
16854
17306
  return {
16855
17307
  get ready() {
16856
17308
  return root.ready;
16857
17309
  },
16858
17310
  configure(props = {}) {
16859
- root.unmountClaim = null;
16860
- const {
16861
- gl: glConfig,
16862
- size: propsSize,
16863
- scene: sceneOptions,
16864
- events,
16865
- onCreated: onCreatedCallback,
16866
- shadows = false,
16867
- linear = false,
16868
- flat = false,
16869
- legacy = false,
16870
- orthographic = false,
16871
- frameloop = 'always',
16872
- dpr = [1, 2],
16873
- performance,
16874
- raycaster: raycastOptions,
16875
- camera: cameraOptions,
16876
- onPointerMissed
16877
- } = props;
16878
- const defaultProps = {
16879
- canvas: canvas,
16880
- powerPreference: 'high-performance',
16881
- antialias: true,
16882
- alpha: true
16883
- };
17311
+ if (!transitionRoot(root, {
17312
+ type: 'use'
17313
+ })) {
17314
+ return rejected(new Error('R3F: Cannot configure a root after disposal has started.'));
17315
+ }
16884
17316
 
16885
- // Synchronous, and needs the renderer to exist
16886
- const apply = customRenderer => {
17317
+ // Configuration applies in call order. Publishing first queues calls made while this one runs
17318
+ const previous = root.ready;
17319
+ const {
17320
+ promise,
17321
+ resolve,
17322
+ reject
17323
+ } = deferred();
17324
+ root.ready = promise;
17325
+ const apply = gl => {
16887
17326
  const state = store.getState();
16888
-
16889
17327
  // Set up renderer (one time only!)
16890
- const first = !state.gl;
16891
- let gl = state.gl;
16892
- if (first) {
16893
- gl = isRenderer(customRenderer) ? customRenderer : new THREE.WebGLRenderer({
16894
- ...defaultProps,
16895
- ...glConfig
16896
- });
16897
- state.set({
16898
- gl
16899
- });
16900
- }
16901
-
16902
- // Set up raycaster (one time only!)
16903
- let raycaster = state.raycaster;
16904
- if (!raycaster) state.set({
16905
- raycaster: raycaster = new THREE.Raycaster()
16906
- });
16907
-
16908
- // Set raycaster options
16909
- const {
16910
- params,
16911
- ...options
16912
- } = raycastOptions || {};
16913
- if (!is.equ(options, raycaster, shallowLoose)) applyProps(raycaster, {
16914
- ...options
16915
- });
16916
- if (!is.equ(params, raycaster.params, shallowLoose)) applyProps(raycaster, {
16917
- params: {
16918
- ...raycaster.params,
16919
- ...params
16920
- }
16921
- });
16922
-
16923
- // Create default camera, don't overwrite any user-set state
16924
- if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
16925
- lastCamera = cameraOptions;
16926
- const isCamera = cameraOptions == null ? void 0 : cameraOptions.isCamera;
16927
- const camera = isCamera ? cameraOptions : orthographic ? new THREE.OrthographicCamera(0, 0, 0, 0, 0.1, 1000) : new THREE.PerspectiveCamera(75, 0, 0.1, 1000);
16928
- if (!isCamera) {
16929
- camera.position.z = 5;
16930
- if (cameraOptions) {
16931
- applyProps(camera, cameraOptions);
16932
- // Preserve user-defined frustum if possible
16933
- // https://github.com/pmndrs/react-three-fiber/issues/3160
16934
- if (!camera.manual) {
16935
- if ('aspect' in cameraOptions || 'left' in cameraOptions || 'right' in cameraOptions || 'bottom' in cameraOptions || 'top' in cameraOptions) {
16936
- camera.manual = true;
16937
- camera.updateProjectionMatrix();
16938
- }
16939
- }
16940
- }
16941
- // Always look at center by default
16942
- if (!state.camera && !(cameraOptions != null && cameraOptions.rotation)) camera.lookAt(0, 0, 0);
16943
- }
16944
- state.set({
16945
- camera
16946
- });
16947
-
16948
- // Configure raycaster
16949
- // https://github.com/pmndrs/react-xr/issues/300
16950
- raycaster.camera = camera;
16951
- }
16952
-
16953
- // Set up scene (one time only!)
16954
- if (!state.scene) {
16955
- let scene;
16956
- if (sceneOptions != null && sceneOptions.isScene) {
16957
- scene = sceneOptions;
16958
- prepare(scene, store, '', {});
16959
- } else {
16960
- scene = new THREE.Scene();
16961
- prepare(scene, store, '', {});
16962
- if (sceneOptions) applyProps(scene, sceneOptions);
16963
- }
16964
- state.set({
16965
- scene
16966
- });
16967
- }
16968
-
16969
- // Store events internally
16970
- if (events && !state.events.handlers) state.set({
16971
- events: events(store)
16972
- });
16973
- // Check size, allow it to take on container bounds initially
16974
- const size = computeInitialSize(canvas, propsSize);
16975
- if (!is.equ(size, state.size, shallowLoose)) {
16976
- state.setSize(size.width, size.height, size.top, size.left);
16977
- }
16978
- // Check pixelratio
16979
- if (dpr && state.viewport.dpr !== calculateDpr(dpr)) state.setDpr(dpr);
16980
- // Check frameloop
16981
- if (state.frameloop !== frameloop) state.setFrameloop(frameloop);
16982
- // Check pointer missed
16983
- if (!state.onPointerMissed) state.set({
16984
- onPointerMissed
16985
- });
16986
- // Check performance
16987
- if (performance && !is.equ(performance, state.performance, shallowLoose)) state.set(state => ({
16988
- performance: {
16989
- ...state.performance,
16990
- ...performance
16991
- }
16992
- }));
16993
-
16994
- // Set up XR (one time only!)
16995
- if (!state.xr) {
16996
- var _gl$xr;
16997
- // Handle frame behavior in WebXR
16998
- const handleXRFrame = (timestamp, frame) => {
16999
- const state = store.getState();
17000
- if (state.frameloop === 'never') return;
17001
- advance(timestamp, true, state, frame);
17002
- };
17003
-
17004
- // Toggle render switching on session
17005
- const handleSessionChange = () => {
17006
- const state = store.getState();
17007
- state.gl.xr.enabled = state.gl.xr.isPresenting;
17008
- state.gl.xr.setAnimationLoop(state.gl.xr.isPresenting ? handleXRFrame : null);
17009
- if (!state.gl.xr.isPresenting) invalidate(state);
17010
- };
17011
-
17012
- // WebXR session manager
17013
- const xr = {
17014
- connect() {
17015
- const gl = store.getState().gl;
17016
- gl.xr.addEventListener('sessionstart', handleSessionChange);
17017
- gl.xr.addEventListener('sessionend', handleSessionChange);
17018
- },
17019
- disconnect() {
17020
- const gl = store.getState().gl;
17021
- gl.xr.removeEventListener('sessionstart', handleSessionChange);
17022
- gl.xr.removeEventListener('sessionend', handleSessionChange);
17023
- }
17024
- };
17025
-
17026
- // Subscribe to WebXR session events
17027
- if (typeof ((_gl$xr = gl.xr) == null ? void 0 : _gl$xr.addEventListener) === 'function') xr.connect();
17328
+ if (!state.gl) {
17329
+ // R3F owns what it builds, a factory's result included, since it calls the factory
17330
+ // once per root. A renderer instance belongs to the caller
17331
+ root.ownsRenderer = !isRenderer(props.gl);
17028
17332
  state.set({
17029
- xr
17333
+ gl: gl
17030
17334
  });
17031
17335
  }
17032
-
17033
- // Set shadowmap
17034
- if (gl.shadowMap) {
17035
- const oldEnabled = gl.shadowMap.enabled;
17036
- const oldType = gl.shadowMap.type;
17037
- gl.shadowMap.enabled = !!shadows;
17038
- if (is.boo(shadows)) {
17039
- gl.shadowMap.type = THREE.PCFSoftShadowMap;
17040
- } else if (is.str(shadows)) {
17041
- var _types$shadows;
17042
- const types = {
17043
- basic: THREE.BasicShadowMap,
17044
- percentage: THREE.PCFShadowMap,
17045
- soft: THREE.PCFSoftShadowMap,
17046
- variance: THREE.VSMShadowMap
17047
- };
17048
- gl.shadowMap.type = (_types$shadows = types[shadows]) != null ? _types$shadows : THREE.PCFSoftShadowMap;
17049
- } else if (is.obj(shadows)) {
17050
- Object.assign(gl.shadowMap, shadows);
17051
- }
17052
- if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type) gl.shadowMap.needsUpdate = true;
17053
- }
17054
- THREE.ColorManagement.enabled = !legacy;
17055
-
17056
- // Set color space and tonemapping preferences
17057
- if (first) {
17058
- gl.outputColorSpace = linear ? THREE.LinearSRGBColorSpace : THREE.SRGBColorSpace;
17059
- gl.toneMapping = flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping;
17060
- }
17061
-
17062
- // Update color management state
17063
- if (state.legacy !== legacy) state.set(() => ({
17064
- legacy
17065
- }));
17066
- if (state.linear !== linear) state.set(() => ({
17067
- linear
17068
- }));
17069
- if (state.flat !== flat) state.set(() => ({
17070
- flat
17071
- }));
17072
-
17073
- // Set gl props
17074
- if (glConfig && !is.fun(glConfig) && !isRenderer(glConfig) && !is.equ(glConfig, gl, shallowLoose)) applyProps(gl, glConfig);
17075
-
17076
- // Set locals
17077
- onCreated = onCreatedCallback;
17078
- };
17079
- const fail = error => {
17080
- const failure = rejected(error);
17081
- root.ready = failure;
17082
- return failure;
17336
+ applyRootConfiguration(root, canvas, props);
17337
+ // Only a range, including the default one, follows the display: a fixed dpr never touches matchMedia
17338
+ if (!root.unwatchDpr && (props.dpr === undefined || Array.isArray(props.dpr))) root.unwatchDpr = followDpr(root);
17339
+ resolve(this);
17083
17340
  };
17084
- const run = customRenderer => {
17341
+ const run = () => {
17085
17342
  try {
17086
- apply(customRenderer);
17087
- root.ready = fulfilled(undefined);
17088
- return fulfilled(this);
17343
+ var _store$getState$gl;
17344
+ const gl = (_store$getState$gl = store.getState().gl) != null ? _store$getState$gl : createRenderer(canvas, props.gl);
17345
+ // Only an async renderer factory makes configuration asynchronous
17346
+ if (isPromiseLike(gl)) Promise.resolve(gl).then(apply).catch(reject);else apply(gl);
17089
17347
  } catch (error) {
17090
- return fail(error);
17348
+ reject(error);
17091
17349
  }
17092
17350
  };
17093
-
17094
- // The renderer exists, so applying props is synchronous
17095
- if (store.getState().gl) return run();
17096
-
17097
- // Another configure is creating it. Chaining keeps them in call order
17098
- if (root.ready.status === 'pending') return tracked(root.ready.then(() => run()));
17099
-
17100
- // Only an async factory makes configure asynchronous
17101
- let customRenderer;
17102
- try {
17103
- customRenderer = typeof glConfig === 'function' ? glConfig(defaultProps) : glConfig;
17104
- } catch (error) {
17105
- return fail(error);
17106
- }
17107
- if (!isPromiseLike(customRenderer)) return run(customRenderer);
17108
- const configuring = tracked(customRenderer.then(run, fail));
17109
- root.ready = configuring;
17110
- return configuring;
17351
+ if (previous.status === 'pending') previous.then(run, reject);else run();
17352
+ return promise;
17111
17353
  },
17112
17354
  render(children) {
17113
- if (_roots.get(canvas) !== root) return store;
17114
- // Using the root cancels a teardown waiting on React
17115
- root.unmountClaim = null;
17116
- const element = /*#__PURE__*/jsx(Provider, {
17117
- store: store,
17118
- children: children,
17119
- onCreated: onCreated,
17120
- rootElement: canvas
17121
- });
17355
+ if (!transitionRoot(root, {
17356
+ type: 'use'
17357
+ })) return store;
17358
+
17359
+ // The root has to be configured before it can be rendered
17360
+ if (!store.getState().gl && root.ready.status === 'fulfilled') this.configure();
17361
+ if (root.ready.status === 'rejected') throw root.ready.reason;
17122
17362
  const commit = () => {
17123
- // The root may have been unmounted or claimed while we waited
17124
- if (_roots.get(canvas) !== root || root.unmountClaim) return;
17363
+ var _root$configuration$p;
17364
+ if (root.state.status !== 'open' || root.ready.status === 'rejected') return;
17365
+ // Wait for all queued configuration, including any queued while waiting
17366
+ if (root.ready.status === 'pending') {
17367
+ root.ready.then(commit, logRecoverableError);
17368
+ return;
17369
+ }
17370
+ const onCreated = (_root$configuration$p = root.configuration.previous) == null ? void 0 : _root$configuration$p.onCreated;
17371
+ const element = /*#__PURE__*/jsx(Provider, {
17372
+ store: store,
17373
+ children: children,
17374
+ onCreated: onCreated,
17375
+ rootElement: canvas
17376
+ });
17125
17377
  if (mounted) {
17126
17378
  reconciler.updateContainer(element, fiber, null, noop);
17127
17379
  return;
@@ -17133,22 +17385,11 @@ function createRoot(canvas) {
17133
17385
  // @ts-ignore - reconciler types are not maintained
17134
17386
  reconciler.flushSyncWork();
17135
17387
  };
17136
-
17137
- // The root has to be configured before it can be rendered
17138
- if (!store.getState().gl && root.ready.status === 'fulfilled') this.configure();
17139
- switch (root.ready.status) {
17140
- case 'pending':
17141
- root.ready.then(commit, logRecoverableError);
17142
- break;
17143
- case 'rejected':
17144
- throw root.ready.reason;
17145
- default:
17146
- commit();
17147
- }
17388
+ commit();
17148
17389
  return store;
17149
17390
  },
17150
17391
  unmount() {
17151
- unmountComponentAtNode(canvas);
17392
+ if (_roots.get(canvas) === root) unmountComponentAtNode(canvas);
17152
17393
  }
17153
17394
  };
17154
17395
  }
@@ -17179,147 +17420,51 @@ function Provider({
17179
17420
  children: children
17180
17421
  });
17181
17422
  }
17423
+ function attempt(step) {
17424
+ try {
17425
+ return step();
17426
+ } catch (error) {
17427
+ console.warn('[R3F] Error while unmounting root; teardown may be incomplete:', error);
17428
+ }
17429
+ }
17182
17430
  function unmountComponentAtNode(canvas, callback) {
17183
17431
  const root = _roots.get(canvas);
17184
- if (!root) return;
17432
+ const token = Symbol('unmount');
17433
+ if (!root || !transitionRoot(root, {
17434
+ type: 'unmount',
17435
+ token
17436
+ })) return;
17437
+ const teardown = () => {
17438
+ var _state$gl;
17439
+ // Configuration accepted before the unmount finishes first, so a renderer it creates is released too
17440
+ if (root.ready.status === 'pending') {
17441
+ root.ready.then(teardown, teardown);
17442
+ return;
17443
+ }
17444
+ // Refused when the root was used again since this unmount
17445
+ if (!transitionRoot(root, {
17446
+ type: 'dispose',
17447
+ token
17448
+ })) return;
17449
+ // Close the gates before invoking user cleanup code.
17450
+ _roots.delete(canvas);
17451
+ const state = root.store.getState();
17452
+ state.internal.active = false;
17185
17453
 
17186
- // Cleared by configure and render, which cancels the teardown
17187
- const claim = root.unmountClaim = Symbol('unmount');
17454
+ // Release what uses the renderer before the renderer itself
17455
+ attempt(() => root.unwatchDpr == null ? void 0 : root.unwatchDpr());
17456
+ attempt(() => state.events.disconnect == null ? void 0 : state.events.disconnect());
17457
+ if ((_state$gl = state.gl) != null && _state$gl.xr) attempt(() => state.xr.disconnect());
17458
+ if (state.scene) attempt(() => dispose(state.scene));
17459
+ const disposal = attempt(() => releaseRenderer(state.gl, root.ownsRenderer));
17460
+ if (isPromiseLike(disposal)) disposal.then(() => callback == null ? void 0 : callback(canvas));else callback == null ? void 0 : callback(canvas);
17461
+ };
17188
17462
  reconciler.updateContainer(null, root.fiber, null, () => {
17189
- if (root.unmountClaim !== claim) return;
17190
-
17463
+ // A root used again since the unmount keeps its new children
17464
+ if (root.state.status !== 'closing' || root.state.token !== token) return;
17191
17465
  // Effect cleanups flush before the next update
17192
- reconciler.updateContainer(null, root.fiber, null, () => {
17193
- if (root.unmountClaim !== claim) return;
17194
- const teardown = () => {
17195
- if (root.unmountClaim !== claim) return;
17196
- root.unmountClaim = null;
17197
- const state = root.store.getState();
17198
- state.internal.active = false;
17199
- try {
17200
- var _state$gl, _state$gl$renderLists, _state$gl2, _state$gl3;
17201
- state.events.disconnect == null ? void 0 : state.events.disconnect();
17202
- (_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();
17203
- (_state$gl2 = state.gl) == null ? void 0 : _state$gl2.forceContextLoss == null ? void 0 : _state$gl2.forceContextLoss();
17204
- if ((_state$gl3 = state.gl) != null && _state$gl3.xr) state.xr.disconnect();
17205
- dispose(state.scene);
17206
- } catch (e) {
17207
- /* ... */
17208
- }
17209
- _roots.delete(canvas);
17210
- if (callback) callback(canvas);
17211
- };
17212
-
17213
- // A renderer still being created has to exist before it can be disposed
17214
- if (root.ready.status === 'pending') root.ready.then(teardown, teardown);else teardown();
17215
- });
17216
- });
17217
- }
17218
- function createPortal(children, container, state) {
17219
- return /*#__PURE__*/jsx(Portal, {
17220
- children: children,
17221
- container: container,
17222
- state: state
17223
- });
17224
- }
17225
- function Portal({
17226
- state = {},
17227
- children,
17228
- container
17229
- }) {
17230
- /** This has to be a component because it would not be able to call useThree/useStore otherwise since
17231
- * if this is our environment, then we are not in r3f's renderer but in react-dom, it would trigger
17232
- * the "R3F hooks can only be used within the Canvas component!" warning:
17233
- * <Canvas>
17234
- * {createPortal(...)} */
17235
- const {
17236
- events,
17237
- size,
17238
- ...rest
17239
- } = state;
17240
- const previousRoot = useStore();
17241
- const [raycaster] = React.useState(() => new THREE.Raycaster());
17242
- const [pointer] = React.useState(() => new THREE.Vector2());
17243
- const inject = useMutableCallback((rootState, injectState) => {
17244
- let viewport = undefined;
17245
- if (injectState.camera && size) {
17246
- const camera = injectState.camera;
17247
- // Calculate the override viewport, if present
17248
- viewport = rootState.viewport.getCurrentViewport(camera, new THREE.Vector3(), size);
17249
- // Update the portal camera, if it differs from the previous layer
17250
- if (camera !== rootState.camera) updateCamera(camera, size);
17251
- }
17252
- return {
17253
- // The intersect consists of the previous root state
17254
- ...rootState,
17255
- ...injectState,
17256
- // Portals have their own scene, which forms the root, a raycaster and a pointer
17257
- scene: container,
17258
- raycaster,
17259
- pointer,
17260
- mouse: pointer,
17261
- // Their previous root is the layer before it
17262
- previousRoot,
17263
- // Events, size and viewport can be overridden by the inject layer
17264
- events: {
17265
- ...rootState.events,
17266
- ...injectState.events,
17267
- ...events
17268
- },
17269
- size: {
17270
- ...rootState.size,
17271
- ...size
17272
- },
17273
- viewport: {
17274
- ...rootState.viewport,
17275
- ...viewport
17276
- },
17277
- // Layers are allowed to override events
17278
- setEvents: events => injectState.set(state => ({
17279
- ...state,
17280
- events: {
17281
- ...state.events,
17282
- ...events
17283
- }
17284
- }))
17285
- };
17466
+ reconciler.updateContainer(null, root.fiber, null, teardown);
17286
17467
  });
17287
- const usePortalStore = React.useMemo(() => {
17288
- // Create a mirrored store, based on the previous root with a few overrides ...
17289
- const store = createWithEqualityFn((set, get) => ({
17290
- ...rest,
17291
- set,
17292
- get
17293
- }));
17294
-
17295
- // Subscribe to previous root-state and copy changes over to the mirrored portal-state
17296
- const onMutate = prev => store.setState(state => inject.current(prev, state));
17297
- onMutate(previousRoot.getState());
17298
- previousRoot.subscribe(onMutate);
17299
- return store;
17300
- // eslint-disable-next-line react-hooks/exhaustive-deps
17301
- }, [previousRoot, container]);
17302
- return (
17303
- /*#__PURE__*/
17304
- // @ts-ignore, reconciler types are not maintained
17305
- jsx(Fragment, {
17306
- children: reconciler.createPortal( /*#__PURE__*/jsx(context.Provider, {
17307
- value: usePortalStore,
17308
- children: children
17309
- }), usePortalStore, null)
17310
- })
17311
- );
17312
- }
17313
-
17314
- /**
17315
- * Force React to flush any updates inside the provided callback synchronously and immediately.
17316
- * All the same caveats documented for react-dom's `flushSync` apply here (see https://react.dev/reference/react-dom/flushSync).
17317
- * Nevertheless, sometimes one needs to render synchronously, for example to keep DOM and 3D changes in lock-step without
17318
- * having to revert to a non-React solution. Note: this will only flush updates within the `Canvas` root.
17319
- */
17320
- function flushSync(fn) {
17321
- // @ts-ignore - reconciler types are not maintained
17322
- return reconciler.flushSyncFromReconciler(fn);
17323
17468
  }
17324
17469
 
17325
17470
  function createSubs(callback, subs) {
@@ -17474,6 +17619,141 @@ function advance(timestamp, runGlobalEffects = true, state, frame) {
17474
17619
  if (runGlobalEffects) flushGlobalEffects('after', timestamp);
17475
17620
  }
17476
17621
 
17622
+ function createPortal(children, container, state) {
17623
+ return /*#__PURE__*/jsx(Portal, {
17624
+ children: children,
17625
+ container: container,
17626
+ state: state
17627
+ });
17628
+ }
17629
+ function initializePortal(options, initialState) {
17630
+ const {
17631
+ parentStore,
17632
+ container
17633
+ } = options;
17634
+ const portalStore = createWithEqualityFn((set, get) => ({
17635
+ ...initialState,
17636
+ set,
17637
+ get,
17638
+ // Create once with the store's setter, without retaining a state snapshot.
17639
+ setEvents: events => set(state => ({
17640
+ ...state,
17641
+ events: {
17642
+ ...state.events,
17643
+ ...events
17644
+ }
17645
+ }))
17646
+ }));
17647
+ const parentStateRef = {
17648
+ current: parentStore.getState()
17649
+ };
17650
+ portalStore.setState(portalState => inject(parentStateRef.current, portalState, options));
17651
+ return {
17652
+ parentStore,
17653
+ container,
17654
+ portalStore,
17655
+ parentStateRef
17656
+ };
17657
+ }
17658
+ function Portal({
17659
+ state = {},
17660
+ children,
17661
+ container
17662
+ }) {
17663
+ const parentStore = useStore();
17664
+ const {
17665
+ events,
17666
+ size,
17667
+ ...rest
17668
+ } = state;
17669
+ const [raycaster] = React.useState(() => new THREE.Raycaster());
17670
+ const [pointer] = React.useState(() => new THREE.Vector2());
17671
+ const options = {
17672
+ parentStore,
17673
+ container,
17674
+ raycaster,
17675
+ pointer,
17676
+ events,
17677
+ size
17678
+ };
17679
+ const injectRef = useMutableCallback((parentState, portalState) => inject(parentState, portalState, options));
17680
+
17681
+ // Initialize during render. Keep the store in state so Activity can preserve it.
17682
+ const [portal, setPortal] = React.useState(() => initializePortal(options, rest));
17683
+ if (portal.parentStore !== parentStore || portal.container !== container) {
17684
+ setPortal(initializePortal(options, rest));
17685
+ }
17686
+ const {
17687
+ portalStore,
17688
+ parentStateRef
17689
+ } = portal;
17690
+
17691
+ // Subscribe after commit and update the portal on each parent change.
17692
+ useIsomorphicLayoutEffect(() => {
17693
+ const update = parentState => {
17694
+ parentStateRef.current = parentState;
17695
+ portalStore.setState(portalState => injectRef.current(parentState, portalState));
17696
+ };
17697
+ const unsubscribe = parentStore.subscribe(update);
17698
+
17699
+ // Catch up on changes made before subscribing or while Activity was hidden.
17700
+ const parentState = parentStore.getState();
17701
+ if (parentState !== parentStateRef.current) update(parentState);
17702
+
17703
+ // Clean up when hidden, unmounted or switching stores.
17704
+ return unsubscribe;
17705
+ }, [parentStore, portalStore, parentStateRef]);
17706
+ return /*#__PURE__*/jsx(Fragment, {
17707
+ children: reconciler.createPortal( /*#__PURE__*/jsx(context.Provider, {
17708
+ value: portalStore,
17709
+ children: children
17710
+ }), portalStore, null)
17711
+ });
17712
+ }
17713
+ function inject(parentState, portalState, options) {
17714
+ const {
17715
+ parentStore,
17716
+ container,
17717
+ raycaster,
17718
+ pointer,
17719
+ events,
17720
+ size
17721
+ } = options;
17722
+ let viewport = undefined;
17723
+ if (portalState.camera && size) {
17724
+ const camera = portalState.camera;
17725
+ // Calculate the override viewport, if present
17726
+ viewport = parentState.viewport.getCurrentViewport(camera, new THREE.Vector3(), size);
17727
+ // Update the portal camera, if it differs from the previous layer
17728
+ if (camera !== parentState.camera) updateCamera(camera, size);
17729
+ }
17730
+ return {
17731
+ // Inherit parent values and preserve the portal's own state.
17732
+ ...parentState,
17733
+ ...portalState,
17734
+ // Portals have their own scene, raycaster and pointer.
17735
+ scene: container,
17736
+ raycaster,
17737
+ pointer,
17738
+ mouse: pointer,
17739
+ previousRoot: parentStore,
17740
+ // Apply the portal's event, size and viewport overrides.
17741
+ events: {
17742
+ ...parentState.events,
17743
+ ...portalState.events,
17744
+ ...events
17745
+ },
17746
+ size: {
17747
+ ...parentState.size,
17748
+ ...size
17749
+ },
17750
+ viewport: {
17751
+ ...parentState.viewport,
17752
+ ...viewport
17753
+ }
17754
+ };
17755
+ }
17756
+
17477
17757
  const DOM_EVENTS = {
17478
17758
  onClick: ['click', false],
17479
17759
  onContextMenu: ['contextmenu', false],
@@ -17560,4 +17840,4 @@ function createPointerEvents(store) {
17560
17840
  };
17561
17841
  }
17562
17842
 
17563
- export { useInstanceHandle as A, Block as B, useStore as C, useThree as D, ErrorBoundary as E, useFrame as F, useGraph as G, useLoader as H, _roots as _, useMutableCallback as a, useGate as b, useIsomorphicLayoutEffect as c, createRoot as d, extend as e, unmountComponentAtNode as f, createPointerEvents as g, createEvents as h, isRef as i, flushGlobalEffects as j, addEffect as k, addAfterEffect as l, addTail as m, invalidate as n, advance as o, createPortal as p, flushSync as q, reconciler as r, context as s, threeTypes as t, useBridge as u, applyProps as v, getRootState as w, dispose as x, act as y, buildGraph as z };
17843
+ export { useInstanceHandle as A, Block as B, useStore as C, useThree as D, ErrorBoundary as E, useFrame as F, useGraph as G, useLoader as H, _roots as _, useMutableCallback as a, useGate as b, useIsomorphicLayoutEffect as c, createRoot as d, extend as e, createPointerEvents as f, createEvents as g, flushGlobalEffects as h, isRef as i, addEffect as j, addAfterEffect as k, addTail as l, invalidate as m, advance as n, flushSync as o, unmountComponentAtNode as p, createPortal as q, reconciler as r, context as s, threeTypes as t, useBridge as u, applyProps as v, getRootState as w, dispose as x, act as y, buildGraph as z };