@react-three/fiber 9.0.0-rc.1 → 9.0.0-rc.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,1335 +1,889 @@
1
1
  import * as THREE from 'three';
2
2
  import * as React from 'react';
3
- import { NoEventPriority, DefaultEventPriority, ContinuousEventPriority, DiscreteEventPriority, ConcurrentRoot } from 'react-reconciler/constants';
3
+ import { DefaultEventPriority, ContinuousEventPriority, DiscreteEventPriority, ConcurrentRoot } from 'react-reconciler/constants';
4
4
  import { createWithEqualityFn } from 'zustand/traditional';
5
+ import Reconciler from 'react-reconciler';
6
+ import { unstable_scheduleCallback, unstable_IdlePriority } from 'scheduler';
5
7
  import { suspend, preload, clear } from 'suspend-react';
6
8
  import { jsx, Fragment } from 'react/jsx-runtime';
7
9
  import { useFiber, useContextBridge, traverseFiber } from 'its-fine';
8
- import Reconciler from 'react-reconciler';
9
- import { unstable_scheduleCallback, unstable_IdlePriority } from 'scheduler';
10
10
 
11
11
  var threeTypes = /*#__PURE__*/Object.freeze({
12
12
  __proto__: null
13
13
  });
14
14
 
15
- // TODO: upstream to DefinitelyTyped for React 19
16
- // https://github.com/facebook/react/issues/28956
17
-
18
- const createReconciler = Reconciler;
19
-
20
- // TODO: handle constructor overloads
21
- // https://github.com/pmndrs/react-three-fiber/pull/2931
22
- // https://github.com/microsoft/TypeScript/issues/37079
23
-
24
- const catalogue = {};
25
- let i = 0;
26
- const isConstructor$1 = object => typeof object === 'function';
27
- function extend(objects) {
28
- if (isConstructor$1(objects)) {
29
- const Component = `${i++}`;
30
- catalogue[Component] = objects;
31
- return Component;
32
- } else {
33
- Object.assign(catalogue, objects);
34
- }
15
+ /**
16
+ * Returns the instance's initial (outmost) root.
17
+ */
18
+ function findInitialRoot(instance) {
19
+ let root = instance.root;
20
+ while (root.getState().previousRoot) root = root.getState().previousRoot;
21
+ return root;
35
22
  }
36
- function validateInstance(type, props) {
37
- // Get target from catalogue
38
- const name = `${type[0].toUpperCase()}${type.slice(1)}`;
39
- const target = catalogue[name];
40
-
41
- // Validate element target
42
- 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`);
23
+ /**
24
+ * Safely flush async effects when testing, simulating a legacy root.
25
+ */
26
+ const act = React.act;
27
+ const isOrthographicCamera = def => def && def.isOrthographicCamera;
28
+ const isRef = obj => obj && obj.hasOwnProperty('current');
43
29
 
44
- // Validate primitives
45
- if (type === 'primitive' && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
30
+ /**
31
+ * An SSR-friendly useLayoutEffect.
32
+ *
33
+ * React currently throws a warning when using useLayoutEffect on the server.
34
+ * To get around it, we can conditionally useEffect on the server (no-op) and
35
+ * useLayoutEffect elsewhere.
36
+ *
37
+ * @see https://github.com/facebook/react/issues/14927
38
+ */
39
+ const useIsomorphicLayoutEffect = /* @__PURE__ */((_window$document, _window$navigator) => typeof window !== 'undefined' && (((_window$document = window.document) == null ? void 0 : _window$document.createElement) || ((_window$navigator = window.navigator) == null ? void 0 : _window$navigator.product) === 'ReactNative'))() ? React.useLayoutEffect : React.useEffect;
40
+ function useMutableCallback(fn) {
41
+ const ref = React.useRef(fn);
42
+ useIsomorphicLayoutEffect(() => void (ref.current = fn), [fn]);
43
+ return ref;
44
+ }
45
+ /**
46
+ * Bridges renderer Context and StrictMode from a primary renderer.
47
+ */
48
+ function useBridge() {
49
+ const fiber = useFiber();
50
+ const ContextBridge = useContextBridge();
51
+ return React.useMemo(() => ({
52
+ children
53
+ }) => {
54
+ const strict = !!traverseFiber(fiber, true, node => node.type === React.StrictMode);
55
+ const Root = strict ? React.StrictMode : React.Fragment;
56
+ return /*#__PURE__*/jsx(Root, {
57
+ children: /*#__PURE__*/jsx(ContextBridge, {
58
+ children: children
59
+ })
60
+ });
61
+ }, [fiber, ContextBridge]);
62
+ }
63
+ function Block({
64
+ set
65
+ }) {
66
+ useIsomorphicLayoutEffect(() => {
67
+ set(new Promise(() => null));
68
+ return () => set(false);
69
+ }, [set]);
70
+ return null;
71
+ }
46
72
 
47
- // Throw if an object or literal was passed for args
48
- if (props.args !== undefined && !Array.isArray(props.args)) throw new Error('R3F: The args prop must be an array!');
73
+ // NOTE: static members get down-level transpiled to mutations which break tree-shaking
74
+ const ErrorBoundary = /* @__PURE__ */(_ErrorBoundary => (_ErrorBoundary = class ErrorBoundary extends React.Component {
75
+ constructor(...args) {
76
+ super(...args);
77
+ this.state = {
78
+ error: false
79
+ };
80
+ }
81
+ componentDidCatch(err) {
82
+ this.props.set(err);
83
+ }
84
+ render() {
85
+ return this.state.error ? null : this.props.children;
86
+ }
87
+ }, _ErrorBoundary.getDerivedStateFromError = () => ({
88
+ error: true
89
+ }), _ErrorBoundary))();
90
+ function calculateDpr(dpr) {
91
+ var _window$devicePixelRa;
92
+ // Err on the side of progress by assuming 2x dpr if we can't detect it
93
+ // This will happen in workers where window is defined but dpr isn't.
94
+ const target = typeof window !== 'undefined' ? (_window$devicePixelRa = window.devicePixelRatio) != null ? _window$devicePixelRa : 2 : 1;
95
+ return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
49
96
  }
50
- function createInstance(type, props, root) {
51
- var _props$object;
52
- validateInstance(type, props);
53
97
 
54
- // Regenerate the R3F instance for primitives to simulate a new object
55
- if (type === 'primitive' && (_props$object = props.object) != null && _props$object.__r3f) delete props.object.__r3f;
56
- return prepare(props.object, root, type, props);
98
+ /**
99
+ * Returns instance root state
100
+ */
101
+ function getRootState(obj) {
102
+ var _r3f;
103
+ return (_r3f = obj.__r3f) == null ? void 0 : _r3f.root.getState();
57
104
  }
58
- function hideInstance(instance) {
59
- if (!instance.isHidden) {
60
- var _instance$parent;
61
- if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
62
- detach(instance.parent, instance);
63
- } else if (isObject3D(instance.object)) {
64
- instance.object.visible = false;
105
+ // A collection of compare functions
106
+ const is = {
107
+ obj: a => a === Object(a) && !is.arr(a) && typeof a !== 'function',
108
+ fun: a => typeof a === 'function',
109
+ str: a => typeof a === 'string',
110
+ num: a => typeof a === 'number',
111
+ boo: a => typeof a === 'boolean',
112
+ und: a => a === void 0,
113
+ arr: a => Array.isArray(a),
114
+ equ(a, b, {
115
+ arrays = 'shallow',
116
+ objects = 'reference',
117
+ strict = true
118
+ } = {}) {
119
+ // Wrong type or one of the two undefined, doesn't match
120
+ if (typeof a !== typeof b || !!a !== !!b) return false;
121
+ // Atomic, just compare a against b
122
+ if (is.str(a) || is.num(a) || is.boo(a)) return a === b;
123
+ const isObj = is.obj(a);
124
+ if (isObj && objects === 'reference') return a === b;
125
+ const isArr = is.arr(a);
126
+ if (isArr && arrays === 'reference') return a === b;
127
+ // Array or Object, shallow compare first to see if it's a match
128
+ if ((isArr || isObj) && a === b) return true;
129
+ // Last resort, go through keys
130
+ let i;
131
+ // Check if a has all the keys of b
132
+ for (i in a) if (!(i in b)) return false;
133
+ // Check if values between keys match
134
+ if (isObj && arrays === 'shallow' && objects === 'shallow') {
135
+ for (i in strict ? b : a) if (!is.equ(a[i], b[i], {
136
+ strict,
137
+ objects: 'reference'
138
+ })) return false;
139
+ } else {
140
+ for (i in strict ? b : a) if (a[i] !== b[i]) return false;
65
141
  }
66
- instance.isHidden = true;
67
- invalidateInstance(instance);
142
+ // If i is undefined
143
+ if (is.und(i)) {
144
+ // If both arrays are empty we consider them equal
145
+ if (isArr && a.length === 0 && b.length === 0) return true;
146
+ // If both objects are empty we consider them equal
147
+ if (isObj && Object.keys(a).length === 0 && Object.keys(b).length === 0) return true;
148
+ // Otherwise match them by value
149
+ if (a !== b) return false;
150
+ }
151
+ return true;
152
+ }
153
+ };
154
+
155
+ // Collects nodes and materials from a THREE.Object3D
156
+ function buildGraph(object) {
157
+ const data = {
158
+ nodes: {},
159
+ materials: {}
160
+ };
161
+ if (object) {
162
+ object.traverse(obj => {
163
+ if (obj.name) data.nodes[obj.name] = obj;
164
+ if (obj.material && !data.materials[obj.material.name]) data.materials[obj.material.name] = obj.material;
165
+ });
68
166
  }
167
+ return data;
69
168
  }
70
- function unhideInstance(instance) {
71
- if (instance.isHidden) {
72
- var _instance$parent2;
73
- if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
74
- attach(instance.parent, instance);
75
- } else if (isObject3D(instance.object) && instance.props.visible !== false) {
76
- instance.object.visible = true;
77
- }
78
- instance.isHidden = false;
79
- invalidateInstance(instance);
169
+ // Disposes an object and all its properties
170
+ function dispose(obj) {
171
+ if (obj.type !== 'Scene') obj.dispose == null ? void 0 : obj.dispose();
172
+ for (const p in obj) {
173
+ const prop = obj[p];
174
+ if ((prop == null ? void 0 : prop.type) !== 'Scene') prop == null ? void 0 : prop.dispose == null ? void 0 : prop.dispose();
80
175
  }
81
176
  }
177
+ const REACT_INTERNAL_PROPS = ['children', 'key', 'ref'];
82
178
 
83
- // https://github.com/facebook/react/issues/20271
84
- // This will make sure events and attach are only handled once when trees are complete
85
- function handleContainerEffects(parent, child, beforeChild) {
86
- // Bail if tree isn't mounted or parent is not a container.
87
- // This ensures that the tree is finalized and React won't discard results to Suspense
88
- const state = child.root.getState();
89
- if (!parent.parent && parent.object !== state.scene) return;
90
-
91
- // Create & link object on first run
92
- if (!child.object) {
93
- var _child$props$object, _child$props$args;
94
- // Get target from catalogue
95
- const name = `${child.type[0].toUpperCase()}${child.type.slice(1)}`;
96
- const target = catalogue[name];
97
-
98
- // Create object
99
- child.object = (_child$props$object = child.props.object) != null ? _child$props$object : new target(...((_child$props$args = child.props.args) != null ? _child$props$args : []));
100
- child.object.__r3f = child;
101
-
102
- // Set initial props
103
- applyProps(child.object, child.props);
179
+ // Gets only instance props from reconciler fibers
180
+ function getInstanceProps(queue) {
181
+ const props = {};
182
+ for (const key in queue) {
183
+ if (!REACT_INTERNAL_PROPS.includes(key)) props[key] = queue[key];
104
184
  }
185
+ return props;
186
+ }
105
187
 
106
- // Append instance
107
- if (child.props.attach) {
108
- attach(parent, child);
109
- } else if (isObject3D(child.object) && isObject3D(parent.object)) {
110
- const childIndex = parent.object.children.indexOf(beforeChild == null ? void 0 : beforeChild.object);
111
- if (beforeChild && childIndex !== -1) {
112
- child.object.parent = parent.object;
113
- parent.object.children.splice(childIndex, 0, child.object);
114
- child.object.dispatchEvent({
115
- type: 'added'
116
- });
117
- parent.object.dispatchEvent({
118
- type: 'childadded',
119
- child: child.object
120
- });
121
- } else {
122
- parent.object.add(child.object);
123
- }
124
- }
188
+ // Each object in the scene carries a small LocalState descriptor
189
+ function prepare(target, root, type, props) {
190
+ const object = target;
125
191
 
126
- // Link subtree
127
- for (const childInstance of child.children) handleContainerEffects(child, childInstance);
128
-
129
- // Tree was updated, request a frame
130
- invalidateInstance(child);
192
+ // Create instance descriptor
193
+ let instance = object == null ? void 0 : object.__r3f;
194
+ if (!instance) {
195
+ instance = {
196
+ root,
197
+ type,
198
+ parent: null,
199
+ children: [],
200
+ props: getInstanceProps(props),
201
+ object,
202
+ eventCount: 0,
203
+ handlers: {},
204
+ isHidden: false
205
+ };
206
+ if (object) {
207
+ object.__r3f = instance;
208
+ if (type) applyProps(object, instance.props);
209
+ }
210
+ }
211
+ return instance;
131
212
  }
132
- function appendChild(parent, child) {
133
- if (!child) return;
213
+ function resolve(root, key) {
214
+ var _target;
215
+ let target = root[key];
216
+ if (!key.includes('-')) return {
217
+ root,
218
+ key,
219
+ target
220
+ };
134
221
 
135
- // Link instances
136
- child.parent = parent;
137
- parent.children.push(child);
222
+ // Resolve pierced target
223
+ const chain = key.split('-');
224
+ target = chain.reduce((acc, key) => acc[key], root);
225
+ key = chain.pop();
138
226
 
139
- // Attach tree once complete
140
- handleContainerEffects(parent, child);
227
+ // Switch root if atomic
228
+ if (!((_target = target) != null && _target.set)) root = chain.reduce((acc, key) => acc[key], root);
229
+ return {
230
+ root,
231
+ key,
232
+ target
233
+ };
141
234
  }
142
- function insertBefore(parent, child, beforeChild) {
143
- if (!child || !beforeChild) return;
144
-
145
- // Link instances
146
- child.parent = parent;
147
- const childIndex = parent.children.indexOf(beforeChild);
148
- if (childIndex !== -1) parent.children.splice(childIndex, 0, child);else parent.children.push(child);
149
235
 
150
- // Attach tree once complete
151
- handleContainerEffects(parent, child, beforeChild);
236
+ // Checks if a dash-cased string ends with an integer
237
+ const INDEX_REGEX = /-\d+$/;
238
+ function attach(parent, child) {
239
+ if (is.str(child.props.attach)) {
240
+ // If attaching into an array (foo-0), create one
241
+ if (INDEX_REGEX.test(child.props.attach)) {
242
+ const index = child.props.attach.replace(INDEX_REGEX, '');
243
+ const {
244
+ root,
245
+ key
246
+ } = resolve(parent.object, index);
247
+ if (!Array.isArray(root[key])) root[key] = [];
248
+ }
249
+ const {
250
+ root,
251
+ key
252
+ } = resolve(parent.object, child.props.attach);
253
+ child.previousAttach = root[key];
254
+ root[key] = child.object;
255
+ } else if (is.fun(child.props.attach)) {
256
+ child.previousAttach = child.props.attach(parent.object, child.object);
257
+ }
152
258
  }
153
- function disposeOnIdle(object) {
154
- if (typeof object.dispose === 'function') {
155
- const handleDispose = () => {
156
- try {
157
- object.dispose();
158
- } catch {
159
- // no-op
160
- }
161
- };
162
-
163
- // In a testing environment, cleanup immediately
164
- if (typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined') handleDispose();
165
- // Otherwise, using a real GPU so schedule cleanup to prevent stalls
166
- else unstable_scheduleCallback(unstable_IdlePriority, handleDispose);
259
+ function detach(parent, child) {
260
+ if (is.str(child.props.attach)) {
261
+ const {
262
+ root,
263
+ key
264
+ } = resolve(parent.object, child.props.attach);
265
+ const previous = child.previousAttach;
266
+ // When the previous value was undefined, it means the value was never set to begin with
267
+ if (previous === undefined) delete root[key];
268
+ // Otherwise set the previous value
269
+ else root[key] = previous;
270
+ } else {
271
+ child.previousAttach == null ? void 0 : child.previousAttach(parent.object, child.object);
167
272
  }
273
+ delete child.previousAttach;
168
274
  }
169
- function removeChild(parent, child, dispose) {
170
- if (!child) return;
275
+ const RESERVED_PROPS = [...REACT_INTERNAL_PROPS,
276
+ // Instance props
277
+ 'args', 'dispose', 'attach', 'object', 'onUpdate',
278
+ // Behavior flags
279
+ 'dispose'];
280
+ const MEMOIZED_PROTOTYPES = new Map();
171
281
 
172
- // Unlink instances
173
- child.parent = null;
174
- const childIndex = parent.children.indexOf(child);
175
- if (childIndex !== -1) parent.children.splice(childIndex, 1);
282
+ // This function prepares a set of changes to be applied to the instance
283
+ function diffProps(instance, newProps) {
284
+ const changedProps = {};
176
285
 
177
- // Eagerly tear down tree
178
- if (child.props.attach) {
179
- detach(parent, child);
180
- } else if (isObject3D(child.object) && isObject3D(parent.object)) {
181
- parent.object.remove(child.object);
182
- removeInteractivity(findInitialRoot(child), child.object);
183
- }
286
+ // Sort through props
287
+ for (const prop in newProps) {
288
+ // Skip reserved keys
289
+ if (RESERVED_PROPS.includes(prop)) continue;
290
+ // Skip if props match
291
+ if (is.equ(newProps[prop], instance.props[prop])) continue;
184
292
 
185
- // Allow objects to bail out of unmount disposal with dispose={null}
186
- const shouldDispose = child.props.dispose !== null && dispose !== false;
293
+ // Props changed, add them
294
+ changedProps[prop] = newProps[prop];
187
295
 
188
- // Recursively remove instance children
189
- for (let i = child.children.length - 1; i >= 0; i--) {
190
- const node = child.children[i];
191
- removeChild(child, node, shouldDispose);
296
+ // Reset pierced props
297
+ for (const other in newProps) {
298
+ if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
299
+ }
192
300
  }
193
- child.children.length = 0;
194
-
195
- // Unlink instance object
196
- delete child.object.__r3f;
197
301
 
198
- // Dispose object whenever the reconciler feels like it.
199
- // Never dispose of primitives because their state may be kept outside of React!
200
- // In order for an object to be able to dispose it
201
- // - has a dispose method
202
- // - cannot be a <primitive object={...} />
203
- // - cannot be a THREE.Scene, because three has broken its own API
204
- if (shouldDispose && child.type !== 'primitive' && child.object.type !== 'Scene') {
205
- disposeOnIdle(child.object);
206
- }
302
+ // Reset removed props for HMR
303
+ for (const prop in instance.props) {
304
+ if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
305
+ const {
306
+ root,
307
+ key
308
+ } = resolve(instance.object, prop);
207
309
 
208
- // Tree was updated, request a frame for top-level instance
209
- if (dispose === undefined) invalidateInstance(child);
210
- }
211
- function setFiberRef(fiber, publicInstance) {
212
- for (const _fiber of [fiber, fiber.alternate]) {
213
- if (_fiber !== null) {
214
- if (typeof _fiber.ref === 'function') {
215
- _fiber.refCleanup == null ? void 0 : _fiber.refCleanup();
216
- const cleanup = _fiber.ref(publicInstance);
217
- if (typeof cleanup === 'function') _fiber.refCleanup = cleanup;
218
- } else if (_fiber.ref) {
219
- _fiber.ref.current = publicInstance;
310
+ // https://github.com/mrdoob/three.js/issues/21209
311
+ // HMR/fast-refresh relies on the ability to cancel out props, but threejs
312
+ // has no means to do this. Hence we curate a small collection of value-classes
313
+ // with their respective constructor/set arguments
314
+ // For removed props, try to set default values, if possible
315
+ if (root.constructor && root.constructor.length === 0) {
316
+ // create a blank slate of the instance and copy the particular parameter.
317
+ let ctor = MEMOIZED_PROTOTYPES.get(root.constructor);
318
+ if (!ctor) {
319
+ ctor = new root.constructor();
320
+ MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
220
321
  }
322
+ changedProps[key] = ctor[key];
323
+ } else {
324
+ // instance does not have constructor, just set it to 0
325
+ changedProps[key] = 0;
221
326
  }
222
327
  }
328
+ return changedProps;
223
329
  }
224
- const reconstructed = [];
225
- function swapInstances() {
226
- // Detach instance
227
- for (const [instance] of reconstructed) {
228
- const parent = instance.parent;
229
- if (parent) {
230
- if (instance.props.attach) {
231
- detach(parent, instance);
232
- } else if (isObject3D(instance.object) && isObject3D(parent.object)) {
233
- parent.object.remove(instance.object);
234
- }
235
- for (const child of instance.children) {
236
- if (child.props.attach) {
237
- detach(instance, child);
238
- } else if (isObject3D(child.object) && isObject3D(instance.object)) {
239
- instance.object.remove(child.object);
240
- }
241
- }
242
- }
243
330
 
244
- // If the old instance is hidden, we need to unhide it.
245
- // React assumes it can discard instances since they're pure for DOM.
246
- // This isn't true for us since our lifetimes are impure and longliving.
247
- // So, we manually check if an instance was hidden and unhide it.
248
- if (instance.isHidden) unhideInstance(instance);
331
+ // https://github.com/mrdoob/three.js/pull/27042
332
+ // https://github.com/mrdoob/three.js/pull/22748
333
+ const colorMaps = ['map', 'emissiveMap', 'sheenColorMap', 'specularColorMap', 'envMap'];
334
+ const EVENT_REGEX = /^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/;
249
335
 
250
- // Dispose of old object if able
251
- if (instance.object.__r3f) delete instance.object.__r3f;
252
- if (instance.type !== 'primitive') disposeOnIdle(instance.object);
253
- }
336
+ // This function applies a set of changes to the instance
337
+ function applyProps(object, props) {
338
+ var _instance$object;
339
+ const instance = object.__r3f;
340
+ const rootState = instance && findInitialRoot(instance).getState();
341
+ const prevHandlers = instance == null ? void 0 : instance.eventCount;
342
+ for (const prop in props) {
343
+ let value = props[prop];
254
344
 
255
- // Update instance
256
- for (const [instance, props, fiber] of reconstructed) {
257
- instance.props = props;
258
- const parent = instance.parent;
259
- if (parent) {
260
- var _instance$props$objec, _instance$props$args;
261
- // Get target from catalogue
262
- const name = `${instance.type[0].toUpperCase()}${instance.type.slice(1)}`;
263
- const target = catalogue[name];
345
+ // Don't mutate reserved keys
346
+ if (RESERVED_PROPS.includes(prop)) continue;
264
347
 
265
- // Create object
266
- instance.object = (_instance$props$objec = instance.props.object) != null ? _instance$props$objec : new target(...((_instance$props$args = instance.props.args) != null ? _instance$props$args : []));
267
- instance.object.__r3f = instance;
268
- setFiberRef(fiber, instance.object);
348
+ // Deal with pointer events, including removing them if undefined
349
+ if (instance && EVENT_REGEX.test(prop)) {
350
+ if (typeof value === 'function') instance.handlers[prop] = value;else delete instance.handlers[prop];
351
+ instance.eventCount = Object.keys(instance.handlers).length;
352
+ }
269
353
 
270
- // Set initial props
271
- applyProps(instance.object, instance.props);
272
- if (instance.props.attach) {
273
- attach(parent, instance);
274
- } else if (isObject3D(instance.object) && isObject3D(parent.object)) {
275
- parent.object.add(instance.object);
276
- }
277
- for (const child of instance.children) {
278
- if (child.props.attach) {
279
- attach(instance, child);
280
- } else if (isObject3D(child.object) && isObject3D(instance.object)) {
281
- instance.object.add(child.object);
282
- }
283
- }
354
+ // Ignore setting undefined props
355
+ // https://github.com/pmndrs/react-three-fiber/issues/274
356
+ if (value === undefined) continue;
357
+ let {
358
+ root,
359
+ key,
360
+ target
361
+ } = resolve(object, prop);
284
362
 
285
- // Tree was updated, request a frame
286
- invalidateInstance(instance);
363
+ // Copy if properties match signatures
364
+ if (typeof (target == null ? void 0 : target.copy) === 'function' && target.copy === value.copy) {
365
+ target.copy(value);
287
366
  }
288
- }
289
- reconstructed.length = 0;
290
- }
291
-
292
- // Don't handle text instances, make it no-op
293
- const handleTextInstance = () => {};
294
- const NO_CONTEXT = {};
295
- let currentUpdatePriority = NoEventPriority;
296
-
297
- // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberFlags.js
298
- const NoFlags = 0;
299
- const Update = 4;
300
- const reconciler = createReconciler({
301
- isPrimaryRenderer: false,
302
- warnsIfNotActing: false,
303
- supportsMutation: true,
304
- supportsPersistence: false,
305
- supportsHydration: false,
306
- createInstance,
307
- removeChild,
308
- appendChild,
309
- appendInitialChild: appendChild,
310
- insertBefore,
311
- appendChildToContainer(container, child) {
312
- const scene = container.getState().scene.__r3f;
313
- if (!child || !scene) return;
314
- appendChild(scene, child);
315
- },
316
- removeChildFromContainer(container, child) {
317
- const scene = container.getState().scene.__r3f;
318
- if (!child || !scene) return;
319
- removeChild(scene, child);
320
- },
321
- insertInContainerBefore(container, child, beforeChild) {
322
- const scene = container.getState().scene.__r3f;
323
- if (!child || !beforeChild || !scene) return;
324
- insertBefore(scene, child, beforeChild);
325
- },
326
- getRootHostContext: () => NO_CONTEXT,
327
- getChildHostContext: () => NO_CONTEXT,
328
- commitUpdate(instance, type, oldProps, newProps, fiber) {
329
- var _newProps$args, _oldProps$args, _newProps$args2;
330
- validateInstance(type, newProps);
331
- let reconstruct = false;
332
-
333
- // Reconstruct primitives if object prop changes
334
- if (instance.type === 'primitive' && oldProps.object !== newProps.object) reconstruct = true;
335
- // Reconstruct instance if args were added or removed
336
- else if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) reconstruct = true;
337
- // Reconstruct instance if args were changed
338
- else if ((_newProps$args2 = newProps.args) != null && _newProps$args2.some((value, index) => {
339
- var _oldProps$args2;
340
- return value !== ((_oldProps$args2 = oldProps.args) == null ? void 0 : _oldProps$args2[index]);
341
- })) reconstruct = true;
367
+ // Layers have no copy function, we must therefore copy the mask property
368
+ else if (target instanceof THREE.Layers && value instanceof THREE.Layers) {
369
+ target.mask = value.mask;
370
+ }
371
+ // Set array types
372
+ else if (target != null && target.set && Array.isArray(value)) {
373
+ if (target.fromArray) target.fromArray(value);else target.set(...value);
374
+ }
375
+ // Set literal types
376
+ else if (target != null && target.set && typeof value !== 'object') {
377
+ const isColor = target == null ? void 0 : target.isColor;
378
+ // Allow setting array scalars
379
+ if (!isColor && target.setScalar && typeof value === 'number') target.setScalar(value);
380
+ // Otherwise just set single value
381
+ else target.set(value);
382
+ }
383
+ // Else, just overwrite the value
384
+ else {
385
+ var _root$key;
386
+ root[key] = value;
342
387
 
343
- // Reconstruct when args or <primitive object={...} have changes
344
- if (reconstruct) {
345
- reconstructed.push([instance, {
346
- ...newProps
347
- }, fiber]);
348
- } else {
349
- // Create a diff-set, flag if there are any changes
350
- const changedProps = diffProps(instance, newProps);
351
- if (Object.keys(changedProps).length) {
352
- Object.assign(instance.props, changedProps);
353
- applyProps(instance.object, changedProps);
388
+ // Auto-convert sRGB texture parameters for built-in materials
389
+ // https://github.com/pmndrs/react-three-fiber/issues/344
390
+ // https://github.com/mrdoob/three.js/pull/25857
391
+ if (rootState && !rootState.linear && colorMaps.includes(key) && (_root$key = root[key]) != null && _root$key.isTexture &&
392
+ // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
393
+ root[key].format === THREE.RGBAFormat && root[key].type === THREE.UnsignedByteType) {
394
+ // NOTE: this cannot be set from the renderer (e.g. sRGB source textures rendered to P3)
395
+ root[key].colorSpace = THREE.SRGBColorSpace;
354
396
  }
355
397
  }
398
+ }
356
399
 
357
- // Flush reconstructed siblings when we hit the last updated child in a sequence
358
- const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
359
- if (isTailSibling) swapInstances();
360
- },
361
- finalizeInitialChildren: () => false,
362
- commitMount() {},
363
- getPublicInstance: instance => instance == null ? void 0 : instance.object,
364
- prepareForCommit: () => null,
365
- preparePortalMount: container => prepare(container.getState().scene, container, '', {}),
366
- resetAfterCommit: () => {},
367
- shouldSetTextContent: () => false,
368
- clearContainer: () => false,
369
- hideInstance,
370
- unhideInstance,
371
- createTextInstance: handleTextInstance,
372
- hideTextInstance: handleTextInstance,
373
- unhideTextInstance: handleTextInstance,
374
- scheduleTimeout: typeof setTimeout === 'function' ? setTimeout : undefined,
375
- cancelTimeout: typeof clearTimeout === 'function' ? clearTimeout : undefined,
376
- noTimeout: -1,
377
- getInstanceFromNode: () => null,
378
- beforeActiveInstanceBlur() {},
379
- afterActiveInstanceBlur() {},
380
- detachDeletedInstance() {},
381
- prepareScopeUpdate() {},
382
- getInstanceFromScope: () => null,
383
- shouldAttemptEagerTransition: () => false,
384
- trackSchedulerEvent: () => {},
385
- resolveEventType: () => null,
386
- resolveEventTimeStamp: () => -1.1,
387
- requestPostPaintCallback() {},
388
- maySuspendCommit: () => false,
389
- preloadInstance: () => true,
390
- // true indicates already loaded
391
- startSuspendingCommit() {},
392
- suspendInstance() {},
393
- waitForCommitToBeReady: () => null,
394
- NotPendingTransition: null,
395
- setCurrentUpdatePriority(newPriority) {
396
- currentUpdatePriority = newPriority;
397
- },
398
- getCurrentUpdatePriority() {
399
- return currentUpdatePriority;
400
- },
401
- resolveUpdatePriority() {
402
- var _window$event;
403
- if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
404
- switch (typeof window !== 'undefined' && ((_window$event = window.event) == null ? void 0 : _window$event.type)) {
405
- case 'click':
406
- case 'contextmenu':
407
- case 'dblclick':
408
- case 'pointercancel':
409
- case 'pointerdown':
410
- case 'pointerup':
411
- return DiscreteEventPriority;
412
- case 'pointermove':
413
- case 'pointerout':
414
- case 'pointerover':
415
- case 'pointerenter':
416
- case 'pointerleave':
417
- case 'wheel':
418
- return ContinuousEventPriority;
419
- default:
420
- return DefaultEventPriority;
400
+ // Register event handlers
401
+ if (instance != null && instance.parent && rootState != null && rootState.internal && (_instance$object = instance.object) != null && _instance$object.isObject3D && prevHandlers !== instance.eventCount) {
402
+ const object = instance.object;
403
+ // Pre-emptively remove the instance from the interaction manager
404
+ const index = rootState.internal.interaction.indexOf(object);
405
+ if (index > -1) rootState.internal.interaction.splice(index, 1);
406
+ // Add the instance to the interaction manager only when it has handlers
407
+ if (instance.eventCount && object.raycast !== null) {
408
+ rootState.internal.interaction.push(object);
421
409
  }
422
- },
423
- resetFormInstance() {}
424
- });
410
+ }
425
411
 
426
- var _window$document, _window$navigator;
427
- /**
428
- * Returns the instance's initial (outmost) root.
429
- */
430
- function findInitialRoot(instance) {
431
- let root = instance.root;
432
- while (root.getState().previousRoot) root = root.getState().previousRoot;
433
- return root;
434
- }
412
+ // Auto-attach geometries and materials
413
+ if (instance && instance.props.attach === undefined) {
414
+ if (instance.object.isBufferGeometry) instance.props.attach = 'geometry';else if (instance.object.isMaterial) instance.props.attach = 'material';
415
+ }
435
416
 
436
- /**
437
- * Returns `true` with correct TS type inference if an object has a configurable color space (since r152).
438
- */
439
- const hasColorSpace = object => 'colorSpace' in object || 'outputColorSpace' in object;
440
- /**
441
- * The current THREE.ColorManagement instance, if present.
442
- */
443
- const getColorManagement = () => {
444
- var _ColorManagement;
445
- return (_ColorManagement = catalogue.ColorManagement) != null ? _ColorManagement : null;
446
- };
447
- /**
448
- * Safely flush async effects when testing, simulating a legacy root.
449
- */
450
- const act = React.act;
451
- const isOrthographicCamera = def => def && def.isOrthographicCamera;
452
- const isRef = obj => obj && obj.hasOwnProperty('current');
417
+ // Instance was updated, request a frame
418
+ if (instance) invalidateInstance(instance);
419
+ return object;
420
+ }
421
+ function invalidateInstance(instance) {
422
+ var _instance$root;
423
+ if (!instance.parent) return;
424
+ instance.props.onUpdate == null ? void 0 : instance.props.onUpdate(instance.object);
425
+ const state = (_instance$root = instance.root) == null ? void 0 : _instance$root.getState == null ? void 0 : _instance$root.getState();
426
+ if (state && state.internal.frames === 0) state.invalidate();
427
+ }
428
+ function updateCamera(camera, size) {
429
+ // Do not mess with the camera if it belongs to the user
430
+ // https://github.com/pmndrs/react-three-fiber/issues/92
431
+ if (camera.manual) return;
432
+ if (isOrthographicCamera(camera)) {
433
+ camera.left = size.width / -2;
434
+ camera.right = size.width / 2;
435
+ camera.top = size.height / 2;
436
+ camera.bottom = size.height / -2;
437
+ } else {
438
+ camera.aspect = size.width / size.height;
439
+ }
440
+ camera.updateProjectionMatrix();
441
+ }
442
+ const isObject3D = object => object == null ? void 0 : object.isObject3D;
453
443
 
454
- /**
455
- * An SSR-friendly useLayoutEffect.
456
- *
457
- * React currently throws a warning when using useLayoutEffect on the server.
458
- * To get around it, we can conditionally useEffect on the server (no-op) and
459
- * useLayoutEffect elsewhere.
460
- *
461
- * @see https://github.com/facebook/react/issues/14927
462
- */
463
- const useIsomorphicLayoutEffect = typeof window !== 'undefined' && ((_window$document = window.document) != null && _window$document.createElement || ((_window$navigator = window.navigator) == null ? void 0 : _window$navigator.product) === 'ReactNative') ? React.useLayoutEffect : React.useEffect;
464
- function useMutableCallback(fn) {
465
- const ref = React.useRef(fn);
466
- useIsomorphicLayoutEffect(() => void (ref.current = fn), [fn]);
467
- return ref;
444
+ function makeId(event) {
445
+ return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
468
446
  }
447
+
469
448
  /**
470
- * Bridges renderer Context and StrictMode from a primary renderer.
449
+ * Release pointer captures.
450
+ * This is called by releasePointerCapture in the API, and when an object is removed.
471
451
  */
472
- function useBridge() {
473
- const fiber = useFiber();
474
- const ContextBridge = useContextBridge();
475
- return React.useMemo(() => ({
476
- children
477
- }) => {
478
- const strict = !!traverseFiber(fiber, true, node => node.type === React.StrictMode);
479
- const Root = strict ? React.StrictMode : React.Fragment;
480
- return /*#__PURE__*/jsx(Root, {
481
- children: /*#__PURE__*/jsx(ContextBridge, {
482
- children: children
483
- })
484
- });
485
- }, [fiber, ContextBridge]);
486
- }
487
- function Block({
488
- set
489
- }) {
490
- useIsomorphicLayoutEffect(() => {
491
- set(new Promise(() => null));
492
- return () => set(false);
493
- }, [set]);
494
- return null;
495
- }
496
- class ErrorBoundary extends React.Component {
497
- constructor(...args) {
498
- super(...args);
499
- this.state = {
500
- error: false
501
- };
502
- }
503
- componentDidCatch(err) {
504
- this.props.set(err);
505
- }
506
- render() {
507
- return this.state.error ? null : this.props.children;
452
+ function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
453
+ const captureData = captures.get(obj);
454
+ if (captureData) {
455
+ captures.delete(obj);
456
+ // If this was the last capturing object for this pointer
457
+ if (captures.size === 0) {
458
+ capturedMap.delete(pointerId);
459
+ captureData.target.releasePointerCapture(pointerId);
460
+ }
508
461
  }
509
462
  }
510
- ErrorBoundary.getDerivedStateFromError = () => ({
511
- error: true
512
- });
513
- function calculateDpr(dpr) {
514
- var _window$devicePixelRa;
515
- // Err on the side of progress by assuming 2x dpr if we can't detect it
516
- // This will happen in workers where window is defined but dpr isn't.
517
- const target = typeof window !== 'undefined' ? (_window$devicePixelRa = window.devicePixelRatio) != null ? _window$devicePixelRa : 2 : 1;
518
- return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
463
+ function removeInteractivity(store, object) {
464
+ const {
465
+ internal
466
+ } = store.getState();
467
+ // Removes every trace of an object from the data store
468
+ internal.interaction = internal.interaction.filter(o => o !== object);
469
+ internal.initialHits = internal.initialHits.filter(o => o !== object);
470
+ internal.hovered.forEach((value, key) => {
471
+ if (value.eventObject === object || value.object === object) {
472
+ // Clear out intersects, they are outdated by now
473
+ internal.hovered.delete(key);
474
+ }
475
+ });
476
+ internal.capturedMap.forEach((captures, pointerId) => {
477
+ releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
478
+ });
519
479
  }
480
+ function createEvents(store) {
481
+ /** Calculates delta */
482
+ function calculateDistance(event) {
483
+ const {
484
+ internal
485
+ } = store.getState();
486
+ const dx = event.offsetX - internal.initialClick[0];
487
+ const dy = event.offsetY - internal.initialClick[1];
488
+ return Math.round(Math.sqrt(dx * dx + dy * dy));
489
+ }
520
490
 
521
- /**
522
- * Returns instance root state
523
- */
524
- function getRootState(obj) {
525
- var _r3f;
526
- return (_r3f = obj.__r3f) == null ? void 0 : _r3f.root.getState();
527
- }
528
- // A collection of compare functions
529
- const is = {
530
- obj: a => a === Object(a) && !is.arr(a) && typeof a !== 'function',
531
- fun: a => typeof a === 'function',
532
- str: a => typeof a === 'string',
533
- num: a => typeof a === 'number',
534
- boo: a => typeof a === 'boolean',
535
- und: a => a === void 0,
536
- arr: a => Array.isArray(a),
537
- equ(a, b, {
538
- arrays = 'shallow',
539
- objects = 'reference',
540
- strict = true
541
- } = {}) {
542
- // Wrong type or one of the two undefined, doesn't match
543
- if (typeof a !== typeof b || !!a !== !!b) return false;
544
- // Atomic, just compare a against b
545
- if (is.str(a) || is.num(a) || is.boo(a)) return a === b;
546
- const isObj = is.obj(a);
547
- if (isObj && objects === 'reference') return a === b;
548
- const isArr = is.arr(a);
549
- if (isArr && arrays === 'reference') return a === b;
550
- // Array or Object, shallow compare first to see if it's a match
551
- if ((isArr || isObj) && a === b) return true;
552
- // Last resort, go through keys
553
- let i;
554
- // Check if a has all the keys of b
555
- for (i in a) if (!(i in b)) return false;
556
- // Check if values between keys match
557
- if (isObj && arrays === 'shallow' && objects === 'shallow') {
558
- for (i in strict ? b : a) if (!is.equ(a[i], b[i], {
559
- strict,
560
- objects: 'reference'
561
- })) return false;
562
- } else {
563
- for (i in strict ? b : a) if (a[i] !== b[i]) return false;
491
+ /** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
492
+ function filterPointerEvents(objects) {
493
+ return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
494
+ var _r3f;
495
+ return (_r3f = obj.__r3f) == null ? void 0 : _r3f.handlers['onPointer' + name];
496
+ }));
497
+ }
498
+ function intersect(event, filter) {
499
+ const state = store.getState();
500
+ const duplicates = new Set();
501
+ const intersections = [];
502
+ // Allow callers to eliminate event objects
503
+ const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
504
+ // Reset all raycaster cameras to undefined
505
+ for (let i = 0; i < eventsObjects.length; i++) {
506
+ const state = getRootState(eventsObjects[i]);
507
+ if (state) {
508
+ state.raycaster.camera = undefined;
509
+ }
564
510
  }
565
- // If i is undefined
566
- if (is.und(i)) {
567
- // If both arrays are empty we consider them equal
568
- if (isArr && a.length === 0 && b.length === 0) return true;
569
- // If both objects are empty we consider them equal
570
- if (isObj && Object.keys(a).length === 0 && Object.keys(b).length === 0) return true;
571
- // Otherwise match them by value
572
- if (a !== b) return false;
511
+ if (!state.previousRoot) {
512
+ // Make sure root-level pointer and ray are set up
513
+ state.events.compute == null ? void 0 : state.events.compute(event, state);
573
514
  }
574
- return true;
575
- }
576
- };
515
+ function handleRaycast(obj) {
516
+ const state = getRootState(obj);
517
+ // Skip event handling when noEvents is set, or when the raycasters camera is null
518
+ if (!state || !state.events.enabled || state.raycaster.camera === null) return [];
577
519
 
578
- // Collects nodes and materials from a THREE.Object3D
579
- function buildGraph(object) {
580
- const data = {
581
- nodes: {},
582
- materials: {}
583
- };
584
- if (object) {
585
- object.traverse(obj => {
586
- if (obj.name) data.nodes[obj.name] = obj;
587
- if (obj.material && !data.materials[obj.material.name]) data.materials[obj.material.name] = obj.material;
520
+ // When the camera is undefined we have to call the event layers update function
521
+ if (state.raycaster.camera === undefined) {
522
+ var _state$previousRoot;
523
+ state.events.compute == null ? void 0 : state.events.compute(event, state, (_state$previousRoot = state.previousRoot) == null ? void 0 : _state$previousRoot.getState());
524
+ // If the camera is still undefined we have to skip this layer entirely
525
+ if (state.raycaster.camera === undefined) state.raycaster.camera = null;
526
+ }
527
+
528
+ // Intersect object by object
529
+ return state.raycaster.camera ? state.raycaster.intersectObject(obj, true) : [];
530
+ }
531
+
532
+ // Collect events
533
+ let hits = eventsObjects
534
+ // Intersect objects
535
+ .flatMap(handleRaycast)
536
+ // Sort by event priority and distance
537
+ .sort((a, b) => {
538
+ const aState = getRootState(a.object);
539
+ const bState = getRootState(b.object);
540
+ if (!aState || !bState) return a.distance - b.distance;
541
+ return bState.events.priority - aState.events.priority || a.distance - b.distance;
542
+ })
543
+ // Filter out duplicates
544
+ .filter(item => {
545
+ const id = makeId(item);
546
+ if (duplicates.has(id)) return false;
547
+ duplicates.add(id);
548
+ return true;
588
549
  });
589
- }
590
- return data;
591
- }
592
- // Disposes an object and all its properties
593
- function dispose(obj) {
594
- if (obj.type !== 'Scene') obj.dispose == null ? void 0 : obj.dispose();
595
- for (const p in obj) {
596
- const prop = obj[p];
597
- if ((prop == null ? void 0 : prop.type) !== 'Scene') prop == null ? void 0 : prop.dispose == null ? void 0 : prop.dispose();
598
- }
599
- }
600
- const REACT_INTERNAL_PROPS = ['children', 'key', 'ref'];
601
550
 
602
- // Gets only instance props from reconciler fibers
603
- function getInstanceProps(queue) {
604
- const props = {};
605
- for (const key in queue) {
606
- if (!REACT_INTERNAL_PROPS.includes(key)) props[key] = queue[key];
607
- }
608
- return props;
609
- }
551
+ // https://github.com/mrdoob/three.js/issues/16031
552
+ // Allow custom userland intersect sort order, this likely only makes sense on the root filter
553
+ if (state.events.filter) hits = state.events.filter(hits, state);
610
554
 
611
- // Each object in the scene carries a small LocalState descriptor
612
- function prepare(target, root, type, props) {
613
- const object = target;
555
+ // Bubble up the events, find the event source (eventObject)
556
+ for (const hit of hits) {
557
+ let eventObject = hit.object;
558
+ // Bubble event up
559
+ while (eventObject) {
560
+ var _r3f2;
561
+ if ((_r3f2 = eventObject.__r3f) != null && _r3f2.eventCount) intersections.push({
562
+ ...hit,
563
+ eventObject
564
+ });
565
+ eventObject = eventObject.parent;
566
+ }
567
+ }
614
568
 
615
- // Create instance descriptor
616
- let instance = object == null ? void 0 : object.__r3f;
617
- if (!instance) {
618
- instance = {
619
- root,
620
- type,
621
- parent: null,
622
- children: [],
623
- props: getInstanceProps(props),
624
- object,
625
- eventCount: 0,
626
- handlers: {},
627
- isHidden: false
628
- };
629
- if (object) {
630
- object.__r3f = instance;
631
- if (type) applyProps(object, instance.props);
569
+ // If the interaction is captured, make all capturing targets part of the intersect.
570
+ if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) {
571
+ for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
572
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
573
+ }
632
574
  }
575
+ return intersections;
633
576
  }
634
- return instance;
635
- }
636
- function resolve(root, key) {
637
- var _target;
638
- let target = root[key];
639
- if (!key.includes('-')) return {
640
- root,
641
- key,
642
- target
643
- };
644
577
 
645
- // Resolve pierced target
646
- const chain = key.split('-');
647
- target = chain.reduce((acc, key) => acc[key], root);
648
- key = chain.pop();
578
+ /** Handles intersections by forwarding them to handlers */
579
+ function handleIntersects(intersections, event, delta, callback) {
580
+ // If anything has been found, forward it to the event listeners
581
+ if (intersections.length) {
582
+ const localState = {
583
+ stopped: false
584
+ };
585
+ for (const hit of intersections) {
586
+ const state = getRootState(hit.object);
587
+ if (state) {
588
+ const {
589
+ raycaster,
590
+ pointer,
591
+ camera,
592
+ internal
593
+ } = state;
594
+ const unprojectedPoint = new THREE.Vector3(pointer.x, pointer.y, 0).unproject(camera);
595
+ const hasPointerCapture = id => {
596
+ var _internal$capturedMap, _internal$capturedMap2;
597
+ return (_internal$capturedMap = (_internal$capturedMap2 = internal.capturedMap.get(id)) == null ? void 0 : _internal$capturedMap2.has(hit.eventObject)) != null ? _internal$capturedMap : false;
598
+ };
599
+ const setPointerCapture = id => {
600
+ const captureData = {
601
+ intersection: hit,
602
+ target: event.target
603
+ };
604
+ if (internal.capturedMap.has(id)) {
605
+ // if the pointerId was previously captured, we add the hit to the
606
+ // event capturedMap.
607
+ internal.capturedMap.get(id).set(hit.eventObject, captureData);
608
+ } else {
609
+ // if the pointerId was not previously captured, we create a map
610
+ // containing the hitObject, and the hit. hitObject is used for
611
+ // faster access.
612
+ internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
613
+ }
614
+ event.target.setPointerCapture(id);
615
+ };
616
+ const releasePointerCapture = id => {
617
+ const captures = internal.capturedMap.get(id);
618
+ if (captures) {
619
+ releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
620
+ }
621
+ };
649
622
 
650
- // Switch root if atomic
651
- if (!((_target = target) != null && _target.set)) root = chain.reduce((acc, key) => acc[key], root);
652
- return {
653
- root,
654
- key,
655
- target
656
- };
657
- }
623
+ // Add native event props
624
+ let extractEventProps = {};
625
+ // This iterates over the event's properties including the inherited ones. Native PointerEvents have most of their props as getters which are inherited, but polyfilled PointerEvents have them all as their own properties (i.e. not inherited). We can't use Object.keys() or Object.entries() as they only return "own" properties; nor Object.getPrototypeOf(event) as that *doesn't* return "own" properties, only inherited ones.
626
+ for (let prop in event) {
627
+ let property = event[prop];
628
+ // Only copy over atomics, leave functions alone as these should be
629
+ // called as event.nativeEvent.fn()
630
+ if (typeof property !== 'function') extractEventProps[prop] = property;
631
+ }
632
+ let raycastEvent = {
633
+ ...hit,
634
+ ...extractEventProps,
635
+ pointer,
636
+ intersections,
637
+ stopped: localState.stopped,
638
+ delta,
639
+ unprojectedPoint,
640
+ ray: raycaster.ray,
641
+ camera: camera,
642
+ // Hijack stopPropagation, which just sets a flag
643
+ stopPropagation() {
644
+ // https://github.com/pmndrs/react-three-fiber/issues/596
645
+ // Events are not allowed to stop propagation if the pointer has been captured
646
+ const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId);
658
647
 
659
- // Checks if a dash-cased string ends with an integer
660
- const INDEX_REGEX = /-\d+$/;
661
- function attach(parent, child) {
662
- if (is.str(child.props.attach)) {
663
- // If attaching into an array (foo-0), create one
664
- if (INDEX_REGEX.test(child.props.attach)) {
665
- const index = child.props.attach.replace(INDEX_REGEX, '');
666
- const {
667
- root,
668
- key
669
- } = resolve(parent.object, index);
670
- if (!Array.isArray(root[key])) root[key] = [];
648
+ // We only authorize stopPropagation...
649
+ if (
650
+ // ...if this pointer hasn't been captured
651
+ !capturesForPointer ||
652
+ // ... or if the hit object is capturing the pointer
653
+ capturesForPointer.has(hit.eventObject)) {
654
+ raycastEvent.stopped = localState.stopped = true;
655
+ // Propagation is stopped, remove all other hover records
656
+ // An event handler is only allowed to flush other handlers if it is hovered itself
657
+ if (internal.hovered.size && Array.from(internal.hovered.values()).find(i => i.eventObject === hit.eventObject)) {
658
+ // Objects cannot flush out higher up objects that have already caught the event
659
+ const higher = intersections.slice(0, intersections.indexOf(hit));
660
+ cancelPointer([...higher, hit]);
661
+ }
662
+ }
663
+ },
664
+ // there should be a distinction between target and currentTarget
665
+ target: {
666
+ hasPointerCapture,
667
+ setPointerCapture,
668
+ releasePointerCapture
669
+ },
670
+ currentTarget: {
671
+ hasPointerCapture,
672
+ setPointerCapture,
673
+ releasePointerCapture
674
+ },
675
+ nativeEvent: event
676
+ };
677
+
678
+ // Call subscribers
679
+ callback(raycastEvent);
680
+ // Event bubbling may be interrupted by stopPropagation
681
+ if (localState.stopped === true) break;
682
+ }
683
+ }
671
684
  }
672
- const {
673
- root,
674
- key
675
- } = resolve(parent.object, child.props.attach);
676
- child.previousAttach = root[key];
677
- root[key] = child.object;
678
- } else if (is.fun(child.props.attach)) {
679
- child.previousAttach = child.props.attach(parent.object, child.object);
685
+ return intersections;
680
686
  }
681
- }
682
- function detach(parent, child) {
683
- if (is.str(child.props.attach)) {
687
+ function cancelPointer(intersections) {
684
688
  const {
685
- root,
686
- key
687
- } = resolve(parent.object, child.props.attach);
688
- const previous = child.previousAttach;
689
- // When the previous value was undefined, it means the value was never set to begin with
690
- if (previous === undefined) delete root[key];
691
- // Otherwise set the previous value
692
- else root[key] = previous;
693
- } else {
694
- child.previousAttach == null ? void 0 : child.previousAttach(parent.object, child.object);
689
+ internal
690
+ } = store.getState();
691
+ for (const hoveredObj of internal.hovered.values()) {
692
+ // When no objects were hit or the the hovered object wasn't found underneath the cursor
693
+ // we call onPointerOut and delete the object from the hovered-elements map
694
+ if (!intersections.length || !intersections.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId)) {
695
+ const eventObject = hoveredObj.eventObject;
696
+ const instance = eventObject.__r3f;
697
+ internal.hovered.delete(makeId(hoveredObj));
698
+ if (instance != null && instance.eventCount) {
699
+ const handlers = instance.handlers;
700
+ // Clear out intersects, they are outdated by now
701
+ const data = {
702
+ ...hoveredObj,
703
+ intersections
704
+ };
705
+ handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
706
+ handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
707
+ }
708
+ }
709
+ }
695
710
  }
696
- delete child.previousAttach;
697
- }
698
- const RESERVED_PROPS = [...REACT_INTERNAL_PROPS,
699
- // Instance props
700
- 'args', 'dispose', 'attach', 'object', 'onUpdate',
701
- // Behavior flags
702
- 'dispose'];
703
- const MEMOIZED_PROTOTYPES = new Map();
704
-
705
- // This function prepares a set of changes to be applied to the instance
706
- function diffProps(instance, newProps) {
707
- const changedProps = {};
708
-
709
- // Sort through props
710
- for (const prop in newProps) {
711
- // Skip reserved keys
712
- if (RESERVED_PROPS.includes(prop)) continue;
713
- // Skip if props match
714
- if (is.equ(newProps[prop], instance.props[prop])) continue;
715
-
716
- // Props changed, add them
717
- changedProps[prop] = newProps[prop];
718
-
719
- // Reset pierced props
720
- for (const other in newProps) {
721
- if (other.startsWith(`${prop}-`)) changedProps[other] = newProps[other];
711
+ function pointerMissed(event, objects) {
712
+ for (let i = 0; i < objects.length; i++) {
713
+ const instance = objects[i].__r3f;
714
+ instance == null ? void 0 : instance.handlers.onPointerMissed == null ? void 0 : instance.handlers.onPointerMissed(event);
722
715
  }
723
716
  }
717
+ function handlePointer(name) {
718
+ // Deal with cancelation
719
+ switch (name) {
720
+ case 'onPointerLeave':
721
+ case 'onPointerCancel':
722
+ return () => cancelPointer([]);
723
+ case 'onLostPointerCapture':
724
+ return event => {
725
+ const {
726
+ internal
727
+ } = store.getState();
728
+ if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
729
+ // If the object event interface had onLostPointerCapture, we'd call it here on every
730
+ // object that's getting removed. We call it on the next frame because onLostPointerCapture
731
+ // fires before onPointerUp. Otherwise pointerUp would never be called if the event didn't
732
+ // happen in the object it originated from, leaving components in a in-between state.
733
+ requestAnimationFrame(() => {
734
+ // Only release if pointer-up didn't do it already
735
+ if (internal.capturedMap.has(event.pointerId)) {
736
+ internal.capturedMap.delete(event.pointerId);
737
+ cancelPointer([]);
738
+ }
739
+ });
740
+ }
741
+ };
742
+ }
724
743
 
725
- // Reset removed props for HMR
726
- for (const prop in instance.props) {
727
- if (RESERVED_PROPS.includes(prop) || newProps.hasOwnProperty(prop)) continue;
728
- const {
729
- root,
730
- key
731
- } = resolve(instance.object, prop);
744
+ // Any other pointer goes here ...
745
+ return function handleEvent(event) {
746
+ const {
747
+ onPointerMissed,
748
+ internal
749
+ } = store.getState();
732
750
 
733
- // https://github.com/mrdoob/three.js/issues/21209
734
- // HMR/fast-refresh relies on the ability to cancel out props, but threejs
735
- // has no means to do this. Hence we curate a small collection of value-classes
736
- // with their respective constructor/set arguments
737
- // For removed props, try to set default values, if possible
738
- if (root.constructor && root.constructor.length === 0) {
739
- // create a blank slate of the instance and copy the particular parameter.
740
- let ctor = MEMOIZED_PROTOTYPES.get(root.constructor);
741
- if (!ctor) {
742
- ctor = new root.constructor();
743
- MEMOIZED_PROTOTYPES.set(root.constructor, ctor);
744
- }
745
- changedProps[key] = ctor[key];
746
- } else {
747
- // instance does not have constructor, just set it to 0
748
- changedProps[key] = 0;
749
- }
750
- }
751
- return changedProps;
752
- }
753
- typeof process !== 'undefined' && process.env.NODE_ENV !== 'production';
754
-
755
- // const LinearEncoding = 3000
756
- const sRGBEncoding = 3001;
757
- const SRGBColorSpace = 'srgb';
758
- const LinearSRGBColorSpace = 'srgb-linear';
759
-
760
- // https://github.com/mrdoob/three.js/pull/27042
761
- // https://github.com/mrdoob/three.js/pull/22748
762
- const colorMaps = ['map', 'emissiveMap', 'sheenTintMap',
763
- // <r134
764
- 'sheenColorMap', 'specularTintMap',
765
- // <r134
766
- 'specularColorMap', 'envMap'];
767
- const EVENT_REGEX = /^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/;
768
-
769
- // This function applies a set of changes to the instance
770
- function applyProps(object, props) {
771
- const instance = object.__r3f;
772
- const rootState = instance && findInitialRoot(instance).getState();
773
- const prevHandlers = instance == null ? void 0 : instance.eventCount;
774
- for (const prop in props) {
775
- let value = props[prop];
776
-
777
- // Don't mutate reserved keys
778
- if (RESERVED_PROPS.includes(prop)) continue;
779
-
780
- // Deal with pointer events, including removing them if undefined
781
- if (instance && EVENT_REGEX.test(prop)) {
782
- if (typeof value === 'function') instance.handlers[prop] = value;else delete instance.handlers[prop];
783
- instance.eventCount = Object.keys(instance.handlers).length;
784
- }
751
+ // prepareRay(event)
752
+ internal.lastEvent.current = event;
785
753
 
786
- // Ignore setting undefined props
787
- // https://github.com/pmndrs/react-three-fiber/issues/274
788
- if (value === undefined) continue;
789
- let {
790
- root,
791
- key,
792
- target
793
- } = resolve(object, prop);
754
+ // Get fresh intersects
755
+ const isPointerMove = name === 'onPointerMove';
756
+ const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
757
+ const filter = isPointerMove ? filterPointerEvents : undefined;
758
+ const hits = intersect(event, filter);
759
+ const delta = isClickEvent ? calculateDistance(event) : 0;
794
760
 
795
- // Alias (output)encoding => (output)colorSpace (since r152)
796
- // https://github.com/pmndrs/react-three-fiber/pull/2829
797
- if (hasColorSpace(root)) {
798
- if (key === 'encoding') {
799
- key = 'colorSpace';
800
- value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
801
- } else if (key === 'outputEncoding') {
802
- key = 'outputColorSpace';
803
- value = value === sRGBEncoding ? SRGBColorSpace : LinearSRGBColorSpace;
761
+ // Save initial coordinates on pointer-down
762
+ if (name === 'onPointerDown') {
763
+ internal.initialClick = [event.offsetX, event.offsetY];
764
+ internal.initialHits = hits.map(hit => hit.eventObject);
804
765
  }
805
- }
806
-
807
- // Copy if properties match signatures
808
- if (typeof (target == null ? void 0 : target.copy) === 'function' && target.copy === value.copy) {
809
- target.copy(value);
810
- }
811
- // Layers have no copy function, we must therefore copy the mask property
812
- else if (target instanceof THREE.Layers && value instanceof THREE.Layers) {
813
- target.mask = value.mask;
814
- }
815
- // Set array types
816
- else if (target != null && target.set && Array.isArray(value)) {
817
- if (target.fromArray) target.fromArray(value);else target.set(...value);
818
- }
819
- // Set literal types
820
- else if (target != null && target.set && typeof value !== 'object') {
821
- const isColor = target instanceof THREE.Color;
822
- // Allow setting array scalars
823
- if (!isColor && target.setScalar && typeof value === 'number') target.setScalar(value);
824
- // Otherwise just set single value
825
- else target.set(value);
826
-
827
- // Emulate THREE.ColorManagement for older three.js versions
828
- // https://github.com/pmndrs/react-three-fiber/issues/344
829
- if (!getColorManagement() && !(rootState != null && rootState.linear) && isColor) target.convertSRGBToLinear();
830
- }
831
- // Else, just overwrite the value
832
- else {
833
- root[key] = value;
834
766
 
835
- // Auto-convert sRGB texture parameters for built-in materials
836
- // https://github.com/pmndrs/react-three-fiber/issues/344
837
- // https://github.com/mrdoob/three.js/pull/25857
838
- if (rootState && !rootState.linear && colorMaps.includes(key) && root[key] instanceof THREE.Texture &&
839
- // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
840
- root[key].format === THREE.RGBAFormat && root[key].type === THREE.UnsignedByteType) {
841
- // NOTE: this cannot be set from the renderer (e.g. sRGB source textures rendered to P3)
842
- if (hasColorSpace(root[key])) root[key].colorSpace = 'srgb';else root[key].encoding = sRGBEncoding;
767
+ // If a click yields no results, pass it back to the user as a miss
768
+ // Missed events have to come first in order to establish user-land side-effect clean up
769
+ if (isClickEvent && !hits.length) {
770
+ if (delta <= 2) {
771
+ pointerMissed(event, internal.interaction);
772
+ if (onPointerMissed) onPointerMissed(event);
773
+ }
843
774
  }
844
- }
845
- }
775
+ // Take care of unhover
776
+ if (isPointerMove) cancelPointer(hits);
777
+ function onIntersect(data) {
778
+ const eventObject = data.eventObject;
779
+ const instance = eventObject.__r3f;
846
780
 
847
- // Register event handlers
848
- if (instance != null && instance.parent && rootState != null && rootState.internal && instance.object instanceof THREE.Object3D && prevHandlers !== instance.eventCount) {
849
- // Pre-emptively remove the instance from the interaction manager
850
- const index = rootState.internal.interaction.indexOf(instance.object);
851
- if (index > -1) rootState.internal.interaction.splice(index, 1);
852
- // Add the instance to the interaction manager only when it has handlers
853
- if (instance.eventCount && instance.object.raycast !== null && instance.object instanceof THREE.Object3D) {
854
- rootState.internal.interaction.push(instance.object);
855
- }
856
- }
781
+ // Check presence of handlers
782
+ if (!(instance != null && instance.eventCount)) return;
783
+ const handlers = instance.handlers;
857
784
 
858
- // Auto-attach geometries and materials
859
- if (instance && instance.props.attach === undefined) {
860
- if (instance.object instanceof THREE.BufferGeometry) instance.props.attach = 'geometry';else if (instance.object instanceof THREE.Material) instance.props.attach = 'material';
861
- }
785
+ /*
786
+ MAYBE TODO, DELETE IF NOT:
787
+ Check if the object is captured, captured events should not have intersects running in parallel
788
+ But wouldn't it be better to just replace capturedMap with a single entry?
789
+ Also, are we OK with straight up making picking up multiple objects impossible?
790
+
791
+ const pointerId = (data as ThreeEvent<PointerEvent>).pointerId
792
+ if (pointerId !== undefined) {
793
+ const capturedMeshSet = internal.capturedMap.get(pointerId)
794
+ if (capturedMeshSet) {
795
+ const captured = capturedMeshSet.get(eventObject)
796
+ if (captured && captured.localState.stopped) return
797
+ }
798
+ }*/
862
799
 
863
- // Instance was updated, request a frame
864
- if (instance) invalidateInstance(instance);
865
- return object;
866
- }
867
- function invalidateInstance(instance) {
868
- var _instance$root;
869
- if (!instance.parent) return;
870
- instance.props.onUpdate == null ? void 0 : instance.props.onUpdate(instance.object);
871
- const state = (_instance$root = instance.root) == null ? void 0 : _instance$root.getState == null ? void 0 : _instance$root.getState();
872
- if (state && state.internal.frames === 0) state.invalidate();
873
- }
874
- function updateCamera(camera, size) {
875
- // Do not mess with the camera if it belongs to the user
876
- // https://github.com/pmndrs/react-three-fiber/issues/92
877
- if (camera.manual) return;
878
- if (isOrthographicCamera(camera)) {
879
- camera.left = size.width / -2;
880
- camera.right = size.width / 2;
881
- camera.top = size.height / 2;
882
- camera.bottom = size.height / -2;
883
- } else {
884
- camera.aspect = size.width / size.height;
800
+ if (isPointerMove) {
801
+ // Move event ...
802
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
803
+ // When enter or out is present take care of hover-state
804
+ const id = makeId(data);
805
+ const hoveredItem = internal.hovered.get(id);
806
+ if (!hoveredItem) {
807
+ // If the object wasn't previously hovered, book it and call its handler
808
+ internal.hovered.set(id, data);
809
+ handlers.onPointerOver == null ? void 0 : handlers.onPointerOver(data);
810
+ handlers.onPointerEnter == null ? void 0 : handlers.onPointerEnter(data);
811
+ } else if (hoveredItem.stopped) {
812
+ // If the object was previously hovered and stopped, we shouldn't allow other items to proceed
813
+ data.stopPropagation();
814
+ }
815
+ }
816
+ // Call mouse move
817
+ handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
818
+ } else {
819
+ // All other events ...
820
+ const handler = handlers[name];
821
+ if (handler) {
822
+ // Forward all events back to their respective handlers with the exception of click events,
823
+ // which must use the initial target
824
+ if (!isClickEvent || internal.initialHits.includes(eventObject)) {
825
+ // Missed events have to come first
826
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
827
+ // Now call the handler
828
+ handler(data);
829
+ }
830
+ } else {
831
+ // Trigger onPointerMissed on all elements that have pointer over/out handlers, but not click and weren't hit
832
+ if (isClickEvent && internal.initialHits.includes(eventObject)) {
833
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
834
+ }
835
+ }
836
+ }
837
+ }
838
+ handleIntersects(hits, event, delta, onIntersect);
839
+ };
885
840
  }
886
- camera.updateProjectionMatrix();
887
- }
888
- const isObject3D = object => object == null ? void 0 : object.isObject3D;
889
-
890
- function makeId(event) {
891
- return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
841
+ return {
842
+ handlePointer
843
+ };
892
844
  }
893
845
 
894
- /**
895
- * Release pointer captures.
896
- * This is called by releasePointerCapture in the API, and when an object is removed.
897
- */
898
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
899
- const captureData = captures.get(obj);
900
- if (captureData) {
901
- captures.delete(obj);
902
- // If this was the last capturing object for this pointer
903
- if (captures.size === 0) {
904
- capturedMap.delete(pointerId);
905
- captureData.target.releasePointerCapture(pointerId);
906
- }
907
- }
908
- }
909
- function removeInteractivity(store, object) {
910
- const {
911
- internal
912
- } = store.getState();
913
- // Removes every trace of an object from the data store
914
- internal.interaction = internal.interaction.filter(o => o !== object);
915
- internal.initialHits = internal.initialHits.filter(o => o !== object);
916
- internal.hovered.forEach((value, key) => {
917
- if (value.eventObject === object || value.object === object) {
918
- // Clear out intersects, they are outdated by now
919
- internal.hovered.delete(key);
920
- }
921
- });
922
- internal.capturedMap.forEach((captures, pointerId) => {
923
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
924
- });
925
- }
926
- function createEvents(store) {
927
- /** Calculates delta */
928
- function calculateDistance(event) {
929
- const {
930
- internal
931
- } = store.getState();
932
- const dx = event.offsetX - internal.initialClick[0];
933
- const dy = event.offsetY - internal.initialClick[1];
934
- return Math.round(Math.sqrt(dx * dx + dy * dy));
935
- }
936
-
937
- /** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
938
- function filterPointerEvents(objects) {
939
- return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
940
- var _r3f;
941
- return (_r3f = obj.__r3f) == null ? void 0 : _r3f.handlers['onPointer' + name];
942
- }));
943
- }
944
- function intersect(event, filter) {
945
- const state = store.getState();
946
- const duplicates = new Set();
947
- const intersections = [];
948
- // Allow callers to eliminate event objects
949
- const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
950
- // Reset all raycaster cameras to undefined
951
- for (let i = 0; i < eventsObjects.length; i++) {
952
- const state = getRootState(eventsObjects[i]);
953
- if (state) {
954
- state.raycaster.camera = undefined;
955
- }
956
- }
957
- if (!state.previousRoot) {
958
- // Make sure root-level pointer and ray are set up
959
- state.events.compute == null ? void 0 : state.events.compute(event, state);
960
- }
961
- function handleRaycast(obj) {
962
- const state = getRootState(obj);
963
- // Skip event handling when noEvents is set, or when the raycasters camera is null
964
- if (!state || !state.events.enabled || state.raycaster.camera === null) return [];
965
-
966
- // When the camera is undefined we have to call the event layers update function
967
- if (state.raycaster.camera === undefined) {
968
- var _state$previousRoot;
969
- state.events.compute == null ? void 0 : state.events.compute(event, state, (_state$previousRoot = state.previousRoot) == null ? void 0 : _state$previousRoot.getState());
970
- // If the camera is still undefined we have to skip this layer entirely
971
- if (state.raycaster.camera === undefined) state.raycaster.camera = null;
972
- }
973
-
974
- // Intersect object by object
975
- return state.raycaster.camera ? state.raycaster.intersectObject(obj, true) : [];
976
- }
977
-
978
- // Collect events
979
- let hits = eventsObjects
980
- // Intersect objects
981
- .flatMap(handleRaycast)
982
- // Sort by event priority and distance
983
- .sort((a, b) => {
984
- const aState = getRootState(a.object);
985
- const bState = getRootState(b.object);
986
- if (!aState || !bState) return a.distance - b.distance;
987
- return bState.events.priority - aState.events.priority || a.distance - b.distance;
988
- })
989
- // Filter out duplicates
990
- .filter(item => {
991
- const id = makeId(item);
992
- if (duplicates.has(id)) return false;
993
- duplicates.add(id);
994
- return true;
995
- });
996
-
997
- // https://github.com/mrdoob/three.js/issues/16031
998
- // Allow custom userland intersect sort order, this likely only makes sense on the root filter
999
- if (state.events.filter) hits = state.events.filter(hits, state);
1000
-
1001
- // Bubble up the events, find the event source (eventObject)
1002
- for (const hit of hits) {
1003
- let eventObject = hit.object;
1004
- // Bubble event up
1005
- while (eventObject) {
1006
- var _r3f2;
1007
- if ((_r3f2 = eventObject.__r3f) != null && _r3f2.eventCount) intersections.push({
1008
- ...hit,
1009
- eventObject
1010
- });
1011
- eventObject = eventObject.parent;
1012
- }
1013
- }
1014
-
1015
- // If the interaction is captured, make all capturing targets part of the intersect.
1016
- if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) {
1017
- for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
1018
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1019
- }
1020
- }
1021
- return intersections;
1022
- }
1023
-
1024
- /** Handles intersections by forwarding them to handlers */
1025
- function handleIntersects(intersections, event, delta, callback) {
1026
- // If anything has been found, forward it to the event listeners
1027
- if (intersections.length) {
1028
- const localState = {
1029
- stopped: false
1030
- };
1031
- for (const hit of intersections) {
1032
- const state = getRootState(hit.object);
1033
- if (state) {
1034
- const {
1035
- raycaster,
1036
- pointer,
1037
- camera,
1038
- internal
1039
- } = state;
1040
- const unprojectedPoint = new THREE.Vector3(pointer.x, pointer.y, 0).unproject(camera);
1041
- const hasPointerCapture = id => {
1042
- var _internal$capturedMap, _internal$capturedMap2;
1043
- return (_internal$capturedMap = (_internal$capturedMap2 = internal.capturedMap.get(id)) == null ? void 0 : _internal$capturedMap2.has(hit.eventObject)) != null ? _internal$capturedMap : false;
1044
- };
1045
- const setPointerCapture = id => {
1046
- const captureData = {
1047
- intersection: hit,
1048
- target: event.target
1049
- };
1050
- if (internal.capturedMap.has(id)) {
1051
- // if the pointerId was previously captured, we add the hit to the
1052
- // event capturedMap.
1053
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
1054
- } else {
1055
- // if the pointerId was not previously captured, we create a map
1056
- // containing the hitObject, and the hit. hitObject is used for
1057
- // faster access.
1058
- internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
1059
- }
1060
- event.target.setPointerCapture(id);
1061
- };
1062
- const releasePointerCapture = id => {
1063
- const captures = internal.capturedMap.get(id);
1064
- if (captures) {
1065
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
1066
- }
1067
- };
1068
-
1069
- // Add native event props
1070
- let extractEventProps = {};
1071
- // This iterates over the event's properties including the inherited ones. Native PointerEvents have most of their props as getters which are inherited, but polyfilled PointerEvents have them all as their own properties (i.e. not inherited). We can't use Object.keys() or Object.entries() as they only return "own" properties; nor Object.getPrototypeOf(event) as that *doesn't* return "own" properties, only inherited ones.
1072
- for (let prop in event) {
1073
- let property = event[prop];
1074
- // Only copy over atomics, leave functions alone as these should be
1075
- // called as event.nativeEvent.fn()
1076
- if (typeof property !== 'function') extractEventProps[prop] = property;
1077
- }
1078
- let raycastEvent = {
1079
- ...hit,
1080
- ...extractEventProps,
1081
- pointer,
1082
- intersections,
1083
- stopped: localState.stopped,
1084
- delta,
1085
- unprojectedPoint,
1086
- ray: raycaster.ray,
1087
- camera: camera,
1088
- // Hijack stopPropagation, which just sets a flag
1089
- stopPropagation() {
1090
- // https://github.com/pmndrs/react-three-fiber/issues/596
1091
- // Events are not allowed to stop propagation if the pointer has been captured
1092
- const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId);
1093
-
1094
- // We only authorize stopPropagation...
1095
- if (
1096
- // ...if this pointer hasn't been captured
1097
- !capturesForPointer ||
1098
- // ... or if the hit object is capturing the pointer
1099
- capturesForPointer.has(hit.eventObject)) {
1100
- raycastEvent.stopped = localState.stopped = true;
1101
- // Propagation is stopped, remove all other hover records
1102
- // An event handler is only allowed to flush other handlers if it is hovered itself
1103
- if (internal.hovered.size && Array.from(internal.hovered.values()).find(i => i.eventObject === hit.eventObject)) {
1104
- // Objects cannot flush out higher up objects that have already caught the event
1105
- const higher = intersections.slice(0, intersections.indexOf(hit));
1106
- cancelPointer([...higher, hit]);
1107
- }
1108
- }
1109
- },
1110
- // there should be a distinction between target and currentTarget
1111
- target: {
1112
- hasPointerCapture,
1113
- setPointerCapture,
1114
- releasePointerCapture
1115
- },
1116
- currentTarget: {
1117
- hasPointerCapture,
1118
- setPointerCapture,
1119
- releasePointerCapture
1120
- },
1121
- nativeEvent: event
1122
- };
1123
-
1124
- // Call subscribers
1125
- callback(raycastEvent);
1126
- // Event bubbling may be interrupted by stopPropagation
1127
- if (localState.stopped === true) break;
1128
- }
1129
- }
1130
- }
1131
- return intersections;
1132
- }
1133
- function cancelPointer(intersections) {
1134
- const {
1135
- internal
1136
- } = store.getState();
1137
- for (const hoveredObj of internal.hovered.values()) {
1138
- // When no objects were hit or the the hovered object wasn't found underneath the cursor
1139
- // we call onPointerOut and delete the object from the hovered-elements map
1140
- if (!intersections.length || !intersections.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId)) {
1141
- const eventObject = hoveredObj.eventObject;
1142
- const instance = eventObject.__r3f;
1143
- internal.hovered.delete(makeId(hoveredObj));
1144
- if (instance != null && instance.eventCount) {
1145
- const handlers = instance.handlers;
1146
- // Clear out intersects, they are outdated by now
1147
- const data = {
1148
- ...hoveredObj,
1149
- intersections
1150
- };
1151
- handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
1152
- handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
1153
- }
1154
- }
1155
- }
1156
- }
1157
- function pointerMissed(event, objects) {
1158
- for (let i = 0; i < objects.length; i++) {
1159
- const instance = objects[i].__r3f;
1160
- instance == null ? void 0 : instance.handlers.onPointerMissed == null ? void 0 : instance.handlers.onPointerMissed(event);
1161
- }
1162
- }
1163
- function handlePointer(name) {
1164
- // Deal with cancelation
1165
- switch (name) {
1166
- case 'onPointerLeave':
1167
- case 'onPointerCancel':
1168
- return () => cancelPointer([]);
1169
- case 'onLostPointerCapture':
1170
- return event => {
1171
- const {
1172
- internal
1173
- } = store.getState();
1174
- if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
1175
- // If the object event interface had onLostPointerCapture, we'd call it here on every
1176
- // object that's getting removed. We call it on the next frame because onLostPointerCapture
1177
- // fires before onPointerUp. Otherwise pointerUp would never be called if the event didn't
1178
- // happen in the object it originated from, leaving components in a in-between state.
1179
- requestAnimationFrame(() => {
1180
- // Only release if pointer-up didn't do it already
1181
- if (internal.capturedMap.has(event.pointerId)) {
1182
- internal.capturedMap.delete(event.pointerId);
1183
- cancelPointer([]);
1184
- }
1185
- });
1186
- }
1187
- };
1188
- }
1189
-
1190
- // Any other pointer goes here ...
1191
- return function handleEvent(event) {
1192
- const {
1193
- onPointerMissed,
1194
- internal
1195
- } = store.getState();
1196
-
1197
- // prepareRay(event)
1198
- internal.lastEvent.current = event;
1199
-
1200
- // Get fresh intersects
1201
- const isPointerMove = name === 'onPointerMove';
1202
- const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
1203
- const filter = isPointerMove ? filterPointerEvents : undefined;
1204
- const hits = intersect(event, filter);
1205
- const delta = isClickEvent ? calculateDistance(event) : 0;
1206
-
1207
- // Save initial coordinates on pointer-down
1208
- if (name === 'onPointerDown') {
1209
- internal.initialClick = [event.offsetX, event.offsetY];
1210
- internal.initialHits = hits.map(hit => hit.eventObject);
1211
- }
1212
-
1213
- // If a click yields no results, pass it back to the user as a miss
1214
- // Missed events have to come first in order to establish user-land side-effect clean up
1215
- if (isClickEvent && !hits.length) {
1216
- if (delta <= 2) {
1217
- pointerMissed(event, internal.interaction);
1218
- if (onPointerMissed) onPointerMissed(event);
1219
- }
1220
- }
1221
- // Take care of unhover
1222
- if (isPointerMove) cancelPointer(hits);
1223
- function onIntersect(data) {
1224
- const eventObject = data.eventObject;
1225
- const instance = eventObject.__r3f;
1226
-
1227
- // Check presence of handlers
1228
- if (!(instance != null && instance.eventCount)) return;
1229
- const handlers = instance.handlers;
1230
-
1231
- /*
1232
- MAYBE TODO, DELETE IF NOT:
1233
- Check if the object is captured, captured events should not have intersects running in parallel
1234
- But wouldn't it be better to just replace capturedMap with a single entry?
1235
- Also, are we OK with straight up making picking up multiple objects impossible?
1236
-
1237
- const pointerId = (data as ThreeEvent<PointerEvent>).pointerId
1238
- if (pointerId !== undefined) {
1239
- const capturedMeshSet = internal.capturedMap.get(pointerId)
1240
- if (capturedMeshSet) {
1241
- const captured = capturedMeshSet.get(eventObject)
1242
- if (captured && captured.localState.stopped) return
1243
- }
1244
- }*/
1245
-
1246
- if (isPointerMove) {
1247
- // Move event ...
1248
- if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1249
- // When enter or out is present take care of hover-state
1250
- const id = makeId(data);
1251
- const hoveredItem = internal.hovered.get(id);
1252
- if (!hoveredItem) {
1253
- // If the object wasn't previously hovered, book it and call its handler
1254
- internal.hovered.set(id, data);
1255
- handlers.onPointerOver == null ? void 0 : handlers.onPointerOver(data);
1256
- handlers.onPointerEnter == null ? void 0 : handlers.onPointerEnter(data);
1257
- } else if (hoveredItem.stopped) {
1258
- // If the object was previously hovered and stopped, we shouldn't allow other items to proceed
1259
- data.stopPropagation();
1260
- }
1261
- }
1262
- // Call mouse move
1263
- handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
1264
- } else {
1265
- // All other events ...
1266
- const handler = handlers[name];
1267
- if (handler) {
1268
- // Forward all events back to their respective handlers with the exception of click events,
1269
- // which must use the initial target
1270
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1271
- // Missed events have to come first
1272
- pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
1273
- // Now call the handler
1274
- handler(data);
1275
- }
1276
- } else {
1277
- // Trigger onPointerMissed on all elements that have pointer over/out handlers, but not click and weren't hit
1278
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
1279
- pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
1280
- }
1281
- }
1282
- }
1283
- }
1284
- handleIntersects(hits, event, delta, onIntersect);
1285
- };
1286
- }
1287
- return {
1288
- handlePointer
1289
- };
1290
- }
1291
-
1292
- const isRenderer = def => !!(def != null && def.render);
1293
- const context = /*#__PURE__*/React.createContext(null);
1294
- const createStore = (invalidate, advance) => {
1295
- const rootStore = createWithEqualityFn((set, get) => {
1296
- const position = new THREE.Vector3();
1297
- const defaultTarget = new THREE.Vector3();
1298
- const tempTarget = new THREE.Vector3();
1299
- function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
1300
- const {
1301
- width,
1302
- height,
1303
- top,
1304
- left
1305
- } = size;
1306
- const aspect = width / height;
1307
- if (target instanceof THREE.Vector3) tempTarget.copy(target);else tempTarget.set(...target);
1308
- const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
1309
- if (isOrthographicCamera(camera)) {
1310
- return {
1311
- width: width / camera.zoom,
1312
- height: height / camera.zoom,
1313
- top,
1314
- left,
1315
- factor: 1,
1316
- distance,
1317
- aspect
1318
- };
1319
- } else {
1320
- const fov = camera.fov * Math.PI / 180; // convert vertical fov to radians
1321
- const h = 2 * Math.tan(fov / 2) * distance; // visible height
1322
- const w = h * (width / height);
1323
- return {
1324
- width: w,
1325
- height: h,
1326
- top,
1327
- left,
1328
- factor: width / w,
1329
- distance,
1330
- aspect
1331
- };
1332
- }
846
+ const isRenderer = def => !!(def != null && def.render);
847
+ const context = /* @__PURE__ */React.createContext(null);
848
+ const createStore = (invalidate, advance) => {
849
+ const rootStore = createWithEqualityFn((set, get) => {
850
+ const position = new THREE.Vector3();
851
+ const defaultTarget = new THREE.Vector3();
852
+ const tempTarget = new THREE.Vector3();
853
+ function getCurrentViewport(camera = get().camera, target = defaultTarget, size = get().size) {
854
+ const {
855
+ width,
856
+ height,
857
+ top,
858
+ left
859
+ } = size;
860
+ const aspect = width / height;
861
+ if (target.isVector3) tempTarget.copy(target);else tempTarget.set(...target);
862
+ const distance = camera.getWorldPosition(position).distanceTo(tempTarget);
863
+ if (isOrthographicCamera(camera)) {
864
+ return {
865
+ width: width / camera.zoom,
866
+ height: height / camera.zoom,
867
+ top,
868
+ left,
869
+ factor: 1,
870
+ distance,
871
+ aspect
872
+ };
873
+ } else {
874
+ const fov = camera.fov * Math.PI / 180; // convert vertical fov to radians
875
+ const h = 2 * Math.tan(fov / 2) * distance; // visible height
876
+ const w = h * (width / height);
877
+ return {
878
+ width: w,
879
+ height: h,
880
+ top,
881
+ left,
882
+ factor: width / w,
883
+ distance,
884
+ aspect
885
+ };
886
+ }
1333
887
  }
1334
888
  let performanceTimeout = undefined;
1335
889
  const setPerformanceCurrent = current => set(state => ({
@@ -1500,148 +1054,573 @@ const createStore = (invalidate, advance) => {
1500
1054
  set
1501
1055
  } = rootStore.getState();
1502
1056
 
1503
- // Resize camera and renderer on changes to size and pixelratio
1504
- if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1505
- oldSize = size;
1506
- oldDpr = viewport.dpr;
1507
- // Update camera & renderer
1508
- updateCamera(camera, size);
1509
- gl.setPixelRatio(viewport.dpr);
1510
- const updateStyle = typeof HTMLCanvasElement !== 'undefined' && gl.domElement instanceof HTMLCanvasElement;
1511
- gl.setSize(size.width, size.height, updateStyle);
1057
+ // Resize camera and renderer on changes to size and pixelratio
1058
+ if (size.width !== oldSize.width || size.height !== oldSize.height || viewport.dpr !== oldDpr) {
1059
+ oldSize = size;
1060
+ oldDpr = viewport.dpr;
1061
+ // Update camera & renderer
1062
+ updateCamera(camera, size);
1063
+ gl.setPixelRatio(viewport.dpr);
1064
+ const updateStyle = typeof HTMLCanvasElement !== 'undefined' && gl.domElement instanceof HTMLCanvasElement;
1065
+ gl.setSize(size.width, size.height, updateStyle);
1066
+ }
1067
+
1068
+ // Update viewport once the camera changes
1069
+ if (camera !== oldCamera) {
1070
+ oldCamera = camera;
1071
+ // Update viewport
1072
+ set(state => ({
1073
+ viewport: {
1074
+ ...state.viewport,
1075
+ ...state.viewport.getCurrentViewport(camera)
1076
+ }
1077
+ }));
1078
+ }
1079
+ });
1080
+
1081
+ // Invalidate on any change
1082
+ rootStore.subscribe(state => invalidate(state));
1083
+
1084
+ // Return root state
1085
+ return rootStore;
1086
+ };
1087
+
1088
+ /**
1089
+ * Exposes an object's {@link Instance}.
1090
+ * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useInstanceHandle
1091
+ *
1092
+ * **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
1093
+ */
1094
+ function useInstanceHandle(ref) {
1095
+ const instance = React.useRef(null);
1096
+ React.useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
1097
+ return instance;
1098
+ }
1099
+
1100
+ /**
1101
+ * Returns the R3F Canvas' Zustand store. Useful for [transient updates](https://github.com/pmndrs/zustand#transient-updates-for-often-occurring-state-changes).
1102
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usestore
1103
+ */
1104
+ function useStore() {
1105
+ const store = React.useContext(context);
1106
+ if (!store) throw new Error('R3F: Hooks can only be used within the Canvas component!');
1107
+ return store;
1108
+ }
1109
+
1110
+ /**
1111
+ * Accesses R3F's internal state, containing renderer, canvas, scene, etc.
1112
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree
1113
+ */
1114
+ function useThree(selector = state => state, equalityFn) {
1115
+ return useStore()(selector, equalityFn);
1116
+ }
1117
+
1118
+ /**
1119
+ * Executes a callback before render in a shared frame loop.
1120
+ * Can order effects with render priority or manually render with a positive priority.
1121
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe
1122
+ */
1123
+ function useFrame(callback, renderPriority = 0) {
1124
+ const store = useStore();
1125
+ const subscribe = store.getState().internal.subscribe;
1126
+ // Memoize ref
1127
+ const ref = useMutableCallback(callback);
1128
+ // Subscribe on mount, unsubscribe on unmount
1129
+ useIsomorphicLayoutEffect(() => subscribe(ref, renderPriority, store), [renderPriority, subscribe, store]);
1130
+ return null;
1131
+ }
1132
+
1133
+ /**
1134
+ * Returns a node graph of an object with named nodes & materials.
1135
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usegraph
1136
+ */
1137
+ function useGraph(object) {
1138
+ return React.useMemo(() => buildGraph(object), [object]);
1139
+ }
1140
+ const memoizedLoaders = new WeakMap();
1141
+ const isConstructor$1 = value => {
1142
+ var _value$prototype;
1143
+ return typeof value === 'function' && (value == null ? void 0 : (_value$prototype = value.prototype) == null ? void 0 : _value$prototype.constructor) === value;
1144
+ };
1145
+ function loadingFn(extensions, onProgress) {
1146
+ return function (Proto, ...input) {
1147
+ let loader;
1148
+
1149
+ // Construct and cache loader if constructor was passed
1150
+ if (isConstructor$1(Proto)) {
1151
+ loader = memoizedLoaders.get(Proto);
1152
+ if (!loader) {
1153
+ loader = new Proto();
1154
+ memoizedLoaders.set(Proto, loader);
1155
+ }
1156
+ } else {
1157
+ loader = Proto;
1158
+ }
1159
+
1160
+ // Apply loader extensions
1161
+ if (extensions) extensions(loader);
1162
+
1163
+ // Go through the urls and load them
1164
+ return Promise.all(input.map(input => new Promise((res, reject) => loader.load(input, data => {
1165
+ if (isObject3D(data == null ? void 0 : data.scene)) Object.assign(data, buildGraph(data.scene));
1166
+ res(data);
1167
+ }, onProgress, error => reject(new Error(`Could not load ${input}: ${error == null ? void 0 : error.message}`))))));
1168
+ };
1169
+ }
1170
+
1171
+ /**
1172
+ * Synchronously loads and caches assets with a three loader.
1173
+ *
1174
+ * Note: this hook's caller must be wrapped with `React.Suspense`
1175
+ * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useloader
1176
+ */
1177
+ function useLoader(loader, input, extensions, onProgress) {
1178
+ // Use suspense to load async assets
1179
+ const keys = Array.isArray(input) ? input : [input];
1180
+ const results = suspend(loadingFn(extensions, onProgress), [loader, ...keys], {
1181
+ equal: is.equ
1182
+ });
1183
+ // Return the object(s)
1184
+ return Array.isArray(input) ? results : results[0];
1185
+ }
1186
+
1187
+ /**
1188
+ * Preloads an asset into cache as a side-effect.
1189
+ */
1190
+ useLoader.preload = function (loader, input, extensions) {
1191
+ const keys = Array.isArray(input) ? input : [input];
1192
+ return preload(loadingFn(extensions), [loader, ...keys]);
1193
+ };
1194
+
1195
+ /**
1196
+ * Removes a loaded asset from cache.
1197
+ */
1198
+ useLoader.clear = function (loader, input) {
1199
+ const keys = Array.isArray(input) ? input : [input];
1200
+ return clear([loader, ...keys]);
1201
+ };
1202
+
1203
+ // TODO: upstream to DefinitelyTyped for React 19
1204
+ // https://github.com/facebook/react/issues/28956
1205
+
1206
+ function createReconciler(config) {
1207
+ const reconciler = Reconciler(config);
1208
+ reconciler.injectIntoDevTools({
1209
+ bundleType: typeof process !== 'undefined' && process.env.NODE_ENV !== 'production' ? 1 : 0,
1210
+ rendererPackageName: '@react-three/fiber',
1211
+ version: React.version
1212
+ });
1213
+ return reconciler;
1214
+ }
1215
+ const NoEventPriority = 0;
1216
+
1217
+ // TODO: handle constructor overloads
1218
+ // https://github.com/pmndrs/react-three-fiber/pull/2931
1219
+ // https://github.com/microsoft/TypeScript/issues/37079
1220
+
1221
+ const catalogue = {};
1222
+ let i = 0;
1223
+ const isConstructor = object => typeof object === 'function';
1224
+ function extend(objects) {
1225
+ if (isConstructor(objects)) {
1226
+ const Component = `${i++}`;
1227
+ catalogue[Component] = objects;
1228
+ return Component;
1229
+ } else {
1230
+ Object.assign(catalogue, objects);
1231
+ }
1232
+ }
1233
+ function validateInstance(type, props) {
1234
+ // Get target from catalogue
1235
+ const name = `${type[0].toUpperCase()}${type.slice(1)}`;
1236
+ const target = catalogue[name];
1237
+
1238
+ // Validate element target
1239
+ 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`);
1240
+
1241
+ // Validate primitives
1242
+ if (type === 'primitive' && !props.object) throw new Error(`R3F: Primitives without 'object' are invalid!`);
1243
+
1244
+ // Throw if an object or literal was passed for args
1245
+ if (props.args !== undefined && !Array.isArray(props.args)) throw new Error('R3F: The args prop must be an array!');
1246
+ }
1247
+ function createInstance(type, props, root) {
1248
+ var _props$object;
1249
+ validateInstance(type, props);
1250
+
1251
+ // Regenerate the R3F instance for primitives to simulate a new object
1252
+ if (type === 'primitive' && (_props$object = props.object) != null && _props$object.__r3f) delete props.object.__r3f;
1253
+ return prepare(props.object, root, type, props);
1254
+ }
1255
+ function hideInstance(instance) {
1256
+ if (!instance.isHidden) {
1257
+ var _instance$parent;
1258
+ if (instance.props.attach && (_instance$parent = instance.parent) != null && _instance$parent.object) {
1259
+ detach(instance.parent, instance);
1260
+ } else if (isObject3D(instance.object)) {
1261
+ instance.object.visible = false;
1262
+ }
1263
+ instance.isHidden = true;
1264
+ invalidateInstance(instance);
1265
+ }
1266
+ }
1267
+ function unhideInstance(instance) {
1268
+ if (instance.isHidden) {
1269
+ var _instance$parent2;
1270
+ if (instance.props.attach && (_instance$parent2 = instance.parent) != null && _instance$parent2.object) {
1271
+ attach(instance.parent, instance);
1272
+ } else if (isObject3D(instance.object) && instance.props.visible !== false) {
1273
+ instance.object.visible = true;
1512
1274
  }
1275
+ instance.isHidden = false;
1276
+ invalidateInstance(instance);
1277
+ }
1278
+ }
1513
1279
 
1514
- // Update viewport once the camera changes
1515
- if (camera !== oldCamera) {
1516
- oldCamera = camera;
1517
- // Update viewport
1518
- set(state => ({
1519
- viewport: {
1520
- ...state.viewport,
1521
- ...state.viewport.getCurrentViewport(camera)
1522
- }
1523
- }));
1280
+ // https://github.com/facebook/react/issues/20271
1281
+ // This will make sure events and attach are only handled once when trees are complete
1282
+ function handleContainerEffects(parent, child, beforeChild) {
1283
+ // Bail if tree isn't mounted or parent is not a container.
1284
+ // This ensures that the tree is finalized and React won't discard results to Suspense
1285
+ const state = child.root.getState();
1286
+ if (!parent.parent && parent.object !== state.scene) return;
1287
+
1288
+ // Create & link object on first run
1289
+ if (!child.object) {
1290
+ var _child$props$object, _child$props$args;
1291
+ // Get target from catalogue
1292
+ const name = `${child.type[0].toUpperCase()}${child.type.slice(1)}`;
1293
+ const target = catalogue[name];
1294
+
1295
+ // Create object
1296
+ child.object = (_child$props$object = child.props.object) != null ? _child$props$object : new target(...((_child$props$args = child.props.args) != null ? _child$props$args : []));
1297
+ child.object.__r3f = child;
1298
+
1299
+ // Set initial props
1300
+ applyProps(child.object, child.props);
1301
+ }
1302
+
1303
+ // Append instance
1304
+ if (child.props.attach) {
1305
+ attach(parent, child);
1306
+ } else if (isObject3D(child.object) && isObject3D(parent.object)) {
1307
+ const childIndex = parent.object.children.indexOf(beforeChild == null ? void 0 : beforeChild.object);
1308
+ if (beforeChild && childIndex !== -1) {
1309
+ child.object.parent = parent.object;
1310
+ parent.object.children.splice(childIndex, 0, child.object);
1311
+ child.object.dispatchEvent({
1312
+ type: 'added'
1313
+ });
1314
+ // @ts-expect-error https://github.com/mrdoob/three.js/pull/16934
1315
+ parent.object.dispatchEvent({
1316
+ type: 'childadded',
1317
+ child: child.object
1318
+ });
1319
+ } else {
1320
+ parent.object.add(child.object);
1524
1321
  }
1525
- });
1322
+ }
1526
1323
 
1527
- // Invalidate on any change
1528
- rootStore.subscribe(state => invalidate(state));
1324
+ // Link subtree
1325
+ for (const childInstance of child.children) handleContainerEffects(child, childInstance);
1529
1326
 
1530
- // Return root state
1531
- return rootStore;
1532
- };
1327
+ // Tree was updated, request a frame
1328
+ invalidateInstance(child);
1329
+ }
1330
+ function appendChild(parent, child) {
1331
+ if (!child) return;
1533
1332
 
1534
- /**
1535
- * Exposes an object's {@link Instance}.
1536
- * @see https://docs.pmnd.rs/react-three-fiber/api/additional-exports#useinstancehandle
1537
- *
1538
- * **Note**: this is an escape hatch to react-internal fields. Expect this to change significantly between versions.
1539
- */
1540
- function useInstanceHandle(ref) {
1541
- const instance = React.useRef(null);
1542
- React.useImperativeHandle(instance, () => ref.current.__r3f, [ref]);
1543
- return instance;
1333
+ // Link instances
1334
+ child.parent = parent;
1335
+ parent.children.push(child);
1336
+
1337
+ // Attach tree once complete
1338
+ handleContainerEffects(parent, child);
1544
1339
  }
1340
+ function insertBefore(parent, child, beforeChild) {
1341
+ if (!child || !beforeChild) return;
1545
1342
 
1546
- /**
1547
- * Returns the R3F Canvas' Zustand store. Useful for [transient updates](https://github.com/pmndrs/zustand#transient-updates-for-often-occurring-state-changes).
1548
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usestore
1549
- */
1550
- function useStore() {
1551
- const store = React.useContext(context);
1552
- if (!store) throw new Error('R3F: Hooks can only be used within the Canvas component!');
1553
- return store;
1343
+ // Link instances
1344
+ child.parent = parent;
1345
+ const childIndex = parent.children.indexOf(beforeChild);
1346
+ if (childIndex !== -1) parent.children.splice(childIndex, 0, child);else parent.children.push(child);
1347
+
1348
+ // Attach tree once complete
1349
+ handleContainerEffects(parent, child, beforeChild);
1350
+ }
1351
+ function disposeOnIdle(object) {
1352
+ if (typeof object.dispose === 'function') {
1353
+ const handleDispose = () => {
1354
+ try {
1355
+ object.dispose();
1356
+ } catch {
1357
+ // no-op
1358
+ }
1359
+ };
1360
+
1361
+ // In a testing environment, cleanup immediately
1362
+ if (typeof IS_REACT_ACT_ENVIRONMENT !== 'undefined') handleDispose();
1363
+ // Otherwise, using a real GPU so schedule cleanup to prevent stalls
1364
+ else unstable_scheduleCallback(unstable_IdlePriority, handleDispose);
1365
+ }
1366
+ }
1367
+ function removeChild(parent, child, dispose) {
1368
+ if (!child) return;
1369
+
1370
+ // Unlink instances
1371
+ child.parent = null;
1372
+ const childIndex = parent.children.indexOf(child);
1373
+ if (childIndex !== -1) parent.children.splice(childIndex, 1);
1374
+
1375
+ // Eagerly tear down tree
1376
+ if (child.props.attach) {
1377
+ detach(parent, child);
1378
+ } else if (isObject3D(child.object) && isObject3D(parent.object)) {
1379
+ parent.object.remove(child.object);
1380
+ removeInteractivity(findInitialRoot(child), child.object);
1381
+ }
1382
+
1383
+ // Allow objects to bail out of unmount disposal with dispose={null}
1384
+ const shouldDispose = child.props.dispose !== null && dispose !== false;
1385
+
1386
+ // Recursively remove instance children
1387
+ for (let i = child.children.length - 1; i >= 0; i--) {
1388
+ const node = child.children[i];
1389
+ removeChild(child, node, shouldDispose);
1390
+ }
1391
+ child.children.length = 0;
1392
+
1393
+ // Unlink instance object
1394
+ delete child.object.__r3f;
1395
+
1396
+ // Dispose object whenever the reconciler feels like it.
1397
+ // Never dispose of primitives because their state may be kept outside of React!
1398
+ // In order for an object to be able to dispose it
1399
+ // - has a dispose method
1400
+ // - cannot be a <primitive object={...} />
1401
+ // - cannot be a THREE.Scene, because three has broken its own API
1402
+ if (shouldDispose && child.type !== 'primitive' && child.object.type !== 'Scene') {
1403
+ disposeOnIdle(child.object);
1404
+ }
1405
+
1406
+ // Tree was updated, request a frame for top-level instance
1407
+ if (dispose === undefined) invalidateInstance(child);
1408
+ }
1409
+ function setFiberRef(fiber, publicInstance) {
1410
+ for (const _fiber of [fiber, fiber.alternate]) {
1411
+ if (_fiber !== null) {
1412
+ if (typeof _fiber.ref === 'function') {
1413
+ _fiber.refCleanup == null ? void 0 : _fiber.refCleanup();
1414
+ const cleanup = _fiber.ref(publicInstance);
1415
+ if (typeof cleanup === 'function') _fiber.refCleanup = cleanup;
1416
+ } else if (_fiber.ref) {
1417
+ _fiber.ref.current = publicInstance;
1418
+ }
1419
+ }
1420
+ }
1554
1421
  }
1422
+ const reconstructed = [];
1423
+ function swapInstances() {
1424
+ // Detach instance
1425
+ for (const [instance] of reconstructed) {
1426
+ const parent = instance.parent;
1427
+ if (parent) {
1428
+ if (instance.props.attach) {
1429
+ detach(parent, instance);
1430
+ } else if (isObject3D(instance.object) && isObject3D(parent.object)) {
1431
+ parent.object.remove(instance.object);
1432
+ }
1433
+ for (const child of instance.children) {
1434
+ if (child.props.attach) {
1435
+ detach(instance, child);
1436
+ } else if (isObject3D(child.object) && isObject3D(instance.object)) {
1437
+ instance.object.remove(child.object);
1438
+ }
1439
+ }
1440
+ }
1555
1441
 
1556
- /**
1557
- * Accesses R3F's internal state, containing renderer, canvas, scene, etc.
1558
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usethree
1559
- */
1560
- function useThree(selector = state => state, equalityFn) {
1561
- return useStore()(selector, equalityFn);
1562
- }
1442
+ // If the old instance is hidden, we need to unhide it.
1443
+ // React assumes it can discard instances since they're pure for DOM.
1444
+ // This isn't true for us since our lifetimes are impure and longliving.
1445
+ // So, we manually check if an instance was hidden and unhide it.
1446
+ if (instance.isHidden) unhideInstance(instance);
1563
1447
 
1564
- /**
1565
- * Executes a callback before render in a shared frame loop.
1566
- * Can order effects with render priority or manually render with a positive priority.
1567
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useframe
1568
- */
1569
- function useFrame(callback, renderPriority = 0) {
1570
- const store = useStore();
1571
- const subscribe = store.getState().internal.subscribe;
1572
- // Memoize ref
1573
- const ref = useMutableCallback(callback);
1574
- // Subscribe on mount, unsubscribe on unmount
1575
- useIsomorphicLayoutEffect(() => subscribe(ref, renderPriority, store), [renderPriority, subscribe, store]);
1576
- return null;
1577
- }
1448
+ // Dispose of old object if able
1449
+ if (instance.object.__r3f) delete instance.object.__r3f;
1450
+ if (instance.type !== 'primitive') disposeOnIdle(instance.object);
1451
+ }
1578
1452
 
1579
- /**
1580
- * Returns a node graph of an object with named nodes & materials.
1581
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#usegraph
1582
- */
1583
- function useGraph(object) {
1584
- return React.useMemo(() => buildGraph(object), [object]);
1585
- }
1586
- const memoizedLoaders = new WeakMap();
1587
- const isConstructor = value => {
1588
- var _value$prototype;
1589
- return typeof value === 'function' && (value == null ? void 0 : (_value$prototype = value.prototype) == null ? void 0 : _value$prototype.constructor) === value;
1590
- };
1591
- function loadingFn(extensions, onProgress) {
1592
- return async function (Proto, ...input) {
1593
- let loader;
1453
+ // Update instance
1454
+ for (const [instance, props, fiber] of reconstructed) {
1455
+ instance.props = props;
1456
+ const parent = instance.parent;
1457
+ if (parent) {
1458
+ var _instance$props$objec, _instance$props$args;
1459
+ // Get target from catalogue
1460
+ const name = `${instance.type[0].toUpperCase()}${instance.type.slice(1)}`;
1461
+ const target = catalogue[name];
1594
1462
 
1595
- // Construct and cache loader if constructor was passed
1596
- if (isConstructor(Proto)) {
1597
- loader = memoizedLoaders.get(Proto);
1598
- if (!loader) {
1599
- loader = new Proto();
1600
- memoizedLoaders.set(Proto, loader);
1463
+ // Create object
1464
+ instance.object = (_instance$props$objec = instance.props.object) != null ? _instance$props$objec : new target(...((_instance$props$args = instance.props.args) != null ? _instance$props$args : []));
1465
+ instance.object.__r3f = instance;
1466
+ setFiberRef(fiber, instance.object);
1467
+
1468
+ // Set initial props
1469
+ applyProps(instance.object, instance.props);
1470
+ if (instance.props.attach) {
1471
+ attach(parent, instance);
1472
+ } else if (isObject3D(instance.object) && isObject3D(parent.object)) {
1473
+ parent.object.add(instance.object);
1601
1474
  }
1602
- } else {
1603
- loader = Proto;
1475
+ for (const child of instance.children) {
1476
+ if (child.props.attach) {
1477
+ attach(instance, child);
1478
+ } else if (isObject3D(child.object) && isObject3D(instance.object)) {
1479
+ instance.object.add(child.object);
1480
+ }
1481
+ }
1482
+
1483
+ // Tree was updated, request a frame
1484
+ invalidateInstance(instance);
1604
1485
  }
1486
+ }
1487
+ reconstructed.length = 0;
1488
+ }
1605
1489
 
1606
- // Apply loader extensions
1607
- if (extensions) extensions(loader);
1490
+ // Don't handle text instances, make it no-op
1491
+ const handleTextInstance = () => {};
1492
+ const NO_CONTEXT = {};
1493
+ let currentUpdatePriority = NoEventPriority;
1608
1494
 
1609
- // Go through the urls and load them
1610
- return Promise.all(input.map(input => new Promise((res, reject) => loader.load(input, data => res(isObject3D(data == null ? void 0 : data.scene) ? Object.assign(data, buildGraph(data.scene)) : data), onProgress, error => reject(new Error(`Could not load ${input}: ${error == null ? void 0 : error.message}`))))));
1611
- };
1612
- }
1495
+ // https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberFlags.js
1496
+ const NoFlags = 0;
1497
+ const Update = 4;
1498
+ const reconciler = /* @__PURE__ */createReconciler({
1499
+ isPrimaryRenderer: false,
1500
+ warnsIfNotActing: false,
1501
+ supportsMutation: true,
1502
+ supportsPersistence: false,
1503
+ supportsHydration: false,
1504
+ createInstance,
1505
+ removeChild,
1506
+ appendChild,
1507
+ appendInitialChild: appendChild,
1508
+ insertBefore,
1509
+ appendChildToContainer(container, child) {
1510
+ const scene = container.getState().scene.__r3f;
1511
+ if (!child || !scene) return;
1512
+ appendChild(scene, child);
1513
+ },
1514
+ removeChildFromContainer(container, child) {
1515
+ const scene = container.getState().scene.__r3f;
1516
+ if (!child || !scene) return;
1517
+ removeChild(scene, child);
1518
+ },
1519
+ insertInContainerBefore(container, child, beforeChild) {
1520
+ const scene = container.getState().scene.__r3f;
1521
+ if (!child || !beforeChild || !scene) return;
1522
+ insertBefore(scene, child, beforeChild);
1523
+ },
1524
+ getRootHostContext: () => NO_CONTEXT,
1525
+ getChildHostContext: () => NO_CONTEXT,
1526
+ commitUpdate(instance, type, oldProps, newProps, fiber) {
1527
+ var _newProps$args, _oldProps$args, _newProps$args2;
1528
+ validateInstance(type, newProps);
1529
+ let reconstruct = false;
1613
1530
 
1614
- /**
1615
- * Synchronously loads and caches assets with a three loader.
1616
- *
1617
- * Note: this hook's caller must be wrapped with `React.Suspense`
1618
- * @see https://docs.pmnd.rs/react-three-fiber/api/hooks#useloader
1619
- */
1620
- function useLoader(loader, input, extensions, onProgress) {
1621
- // Use suspense to load async assets
1622
- const keys = Array.isArray(input) ? input : [input];
1623
- const results = suspend(loadingFn(extensions, onProgress), [loader, ...keys], {
1624
- equal: is.equ
1625
- });
1626
- // Return the object(s)
1627
- return Array.isArray(input) ? results : results[0];
1628
- }
1531
+ // Reconstruct primitives if object prop changes
1532
+ if (instance.type === 'primitive' && oldProps.object !== newProps.object) reconstruct = true;
1533
+ // Reconstruct instance if args were added or removed
1534
+ else if (((_newProps$args = newProps.args) == null ? void 0 : _newProps$args.length) !== ((_oldProps$args = oldProps.args) == null ? void 0 : _oldProps$args.length)) reconstruct = true;
1535
+ // Reconstruct instance if args were changed
1536
+ else if ((_newProps$args2 = newProps.args) != null && _newProps$args2.some((value, index) => {
1537
+ var _oldProps$args2;
1538
+ return value !== ((_oldProps$args2 = oldProps.args) == null ? void 0 : _oldProps$args2[index]);
1539
+ })) reconstruct = true;
1629
1540
 
1630
- /**
1631
- * Preloads an asset into cache as a side-effect.
1632
- */
1633
- useLoader.preload = function (loader, input, extensions) {
1634
- const keys = Array.isArray(input) ? input : [input];
1635
- return preload(loadingFn(extensions), [loader, ...keys]);
1636
- };
1541
+ // Reconstruct when args or <primitive object={...} have changes
1542
+ if (reconstruct) {
1543
+ reconstructed.push([instance, {
1544
+ ...newProps
1545
+ }, fiber]);
1546
+ } else {
1547
+ // Create a diff-set, flag if there are any changes
1548
+ const changedProps = diffProps(instance, newProps);
1549
+ if (Object.keys(changedProps).length) {
1550
+ Object.assign(instance.props, changedProps);
1551
+ applyProps(instance.object, changedProps);
1552
+ }
1553
+ }
1637
1554
 
1638
- /**
1639
- * Removes a loaded asset from cache.
1640
- */
1641
- useLoader.clear = function (loader, input) {
1642
- const keys = Array.isArray(input) ? input : [input];
1643
- return clear([loader, ...keys]);
1644
- };
1555
+ // Flush reconstructed siblings when we hit the last updated child in a sequence
1556
+ const isTailSibling = fiber.sibling === null || (fiber.flags & Update) === NoFlags;
1557
+ if (isTailSibling) swapInstances();
1558
+ },
1559
+ finalizeInitialChildren: () => false,
1560
+ commitMount() {},
1561
+ getPublicInstance: instance => instance == null ? void 0 : instance.object,
1562
+ prepareForCommit: () => null,
1563
+ preparePortalMount: container => prepare(container.getState().scene, container, '', {}),
1564
+ resetAfterCommit: () => {},
1565
+ shouldSetTextContent: () => false,
1566
+ clearContainer: () => false,
1567
+ hideInstance,
1568
+ unhideInstance,
1569
+ createTextInstance: handleTextInstance,
1570
+ hideTextInstance: handleTextInstance,
1571
+ unhideTextInstance: handleTextInstance,
1572
+ scheduleTimeout: typeof setTimeout === 'function' ? setTimeout : undefined,
1573
+ cancelTimeout: typeof clearTimeout === 'function' ? clearTimeout : undefined,
1574
+ noTimeout: -1,
1575
+ getInstanceFromNode: () => null,
1576
+ beforeActiveInstanceBlur() {},
1577
+ afterActiveInstanceBlur() {},
1578
+ detachDeletedInstance() {},
1579
+ prepareScopeUpdate() {},
1580
+ getInstanceFromScope: () => null,
1581
+ shouldAttemptEagerTransition: () => false,
1582
+ trackSchedulerEvent: () => {},
1583
+ resolveEventType: () => null,
1584
+ resolveEventTimeStamp: () => -1.1,
1585
+ requestPostPaintCallback() {},
1586
+ maySuspendCommit: () => false,
1587
+ preloadInstance: () => true,
1588
+ // true indicates already loaded
1589
+ startSuspendingCommit() {},
1590
+ suspendInstance() {},
1591
+ waitForCommitToBeReady: () => null,
1592
+ NotPendingTransition: null,
1593
+ HostTransitionContext: /* @__PURE__ */React.createContext(null),
1594
+ setCurrentUpdatePriority(newPriority) {
1595
+ currentUpdatePriority = newPriority;
1596
+ },
1597
+ getCurrentUpdatePriority() {
1598
+ return currentUpdatePriority;
1599
+ },
1600
+ resolveUpdatePriority() {
1601
+ var _window$event;
1602
+ if (currentUpdatePriority !== NoEventPriority) return currentUpdatePriority;
1603
+ switch (typeof window !== 'undefined' && ((_window$event = window.event) == null ? void 0 : _window$event.type)) {
1604
+ case 'click':
1605
+ case 'contextmenu':
1606
+ case 'dblclick':
1607
+ case 'pointercancel':
1608
+ case 'pointerdown':
1609
+ case 'pointerup':
1610
+ return DiscreteEventPriority;
1611
+ case 'pointermove':
1612
+ case 'pointerout':
1613
+ case 'pointerover':
1614
+ case 'pointerenter':
1615
+ case 'pointerleave':
1616
+ case 'wheel':
1617
+ return ContinuousEventPriority;
1618
+ default:
1619
+ return DefaultEventPriority;
1620
+ }
1621
+ },
1622
+ resetFormInstance() {}
1623
+ });
1645
1624
 
1646
1625
  const _roots = new Map();
1647
1626
  const shallowLoose = {
@@ -1660,7 +1639,7 @@ const createRendererInstance = (gl, canvas) => {
1660
1639
  });
1661
1640
  };
1662
1641
  function computeInitialSize(canvas, size) {
1663
- if (!size && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1642
+ if (!size && typeof HTMLCanvasElement !== 'undefined' && canvas instanceof HTMLCanvasElement && canvas.parentElement) {
1664
1643
  const {
1665
1644
  width,
1666
1645
  height,
@@ -1790,7 +1769,7 @@ function createRoot(canvas) {
1790
1769
  // Create default camera, don't overwrite any user-set state
1791
1770
  if (!state.camera || state.camera === lastCamera && !is.equ(lastCamera, cameraOptions, shallowLoose)) {
1792
1771
  lastCamera = cameraOptions;
1793
- const isCamera = cameraOptions instanceof THREE.Camera;
1772
+ const isCamera = cameraOptions == null ? void 0 : cameraOptions.isCamera;
1794
1773
  const camera = isCamera ? cameraOptions : orthographic ? new THREE.OrthographicCamera(0, 0, 0, 0, 0.1, 1000) : new THREE.PerspectiveCamera(75, 0, 0.1, 1000);
1795
1774
  if (!isCamera) {
1796
1775
  camera.position.z = 5;
@@ -1820,7 +1799,7 @@ function createRoot(canvas) {
1820
1799
  // Set up scene (one time only!)
1821
1800
  if (!state.scene) {
1822
1801
  let scene;
1823
- if (sceneOptions instanceof THREE.Scene) {
1802
+ if (sceneOptions != null && sceneOptions.isScene) {
1824
1803
  scene = sceneOptions;
1825
1804
  prepare(scene, store, '', {});
1826
1805
  } else {
@@ -1835,6 +1814,7 @@ function createRoot(canvas) {
1835
1814
 
1836
1815
  // Set up XR (one time only!)
1837
1816
  if (!state.xr) {
1817
+ var _gl$xr;
1838
1818
  // Handle frame behavior in WebXR
1839
1819
  const handleXRFrame = (timestamp, frame) => {
1840
1820
  const state = store.getState();
@@ -1865,7 +1845,7 @@ function createRoot(canvas) {
1865
1845
  };
1866
1846
 
1867
1847
  // Subscribe to WebXR session events
1868
- if (gl.xr) xr.connect();
1848
+ if (typeof ((_gl$xr = gl.xr) == null ? void 0 : _gl$xr.addEventListener) === 'function') xr.connect();
1869
1849
  state.set({
1870
1850
  xr
1871
1851
  });
@@ -1892,22 +1872,12 @@ function createRoot(canvas) {
1892
1872
  }
1893
1873
  if (oldEnabled !== gl.shadowMap.enabled || oldType !== gl.shadowMap.type) gl.shadowMap.needsUpdate = true;
1894
1874
  }
1895
-
1896
- // Safely set color management if available.
1897
- // Avoid accessing THREE.ColorManagement to play nice with older versions
1898
- const ColorManagement = getColorManagement();
1899
- if (ColorManagement) {
1900
- if ('enabled' in ColorManagement) ColorManagement.enabled = !legacy;else if ('legacyMode' in ColorManagement) ColorManagement.legacyMode = legacy;
1901
- }
1875
+ THREE.ColorManagement.enabled = !legacy;
1902
1876
 
1903
1877
  // Set color space and tonemapping preferences
1904
1878
  if (!configured) {
1905
- const LinearEncoding = 3000;
1906
- const sRGBEncoding = 3001;
1907
- applyProps(gl, {
1908
- outputEncoding: linear ? LinearEncoding : sRGBEncoding,
1909
- toneMapping: flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping
1910
- });
1879
+ gl.outputColorSpace = linear ? THREE.LinearSRGBColorSpace : THREE.SRGBColorSpace;
1880
+ gl.toneMapping = flat ? THREE.NoToneMapping : THREE.ACESFilmicToneMapping;
1911
1881
  }
1912
1882
 
1913
1883
  // Update color management state
@@ -2119,11 +2089,6 @@ function Portal({
2119
2089
  }), usePortalStore, null)
2120
2090
  });
2121
2091
  }
2122
- reconciler.injectIntoDevTools({
2123
- bundleType: process.env.NODE_ENV === 'production' ? 0 : 1,
2124
- rendererPackageName: '@react-three/fiber',
2125
- version: React.version
2126
- });
2127
2092
 
2128
2093
  function createSubs(callback, subs) {
2129
2094
  const sub = {