@react-three/fiber 8.11.7 → 8.11.9

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.
@@ -37,1156 +37,1152 @@ var threeTypes = /*#__PURE__*/Object.freeze({
37
37
  __proto__: null
38
38
  });
39
39
 
40
- var _window$document, _window$navigator;
41
- /**
42
- * Safely accesses a deeply-nested value on an object to get around static bundler analysis.
43
- */
44
- const getDeep = (obj, ...keys) => keys.reduce((acc, key) => acc == null ? void 0 : acc[key], obj);
45
- /**
46
- * The current THREE.ColorManagement instance, if present.
47
- */
48
- const ColorManagement = 'ColorManagement' in THREE__namespace && getDeep(THREE__namespace, 'ColorManagement') || null;
49
- const isOrthographicCamera = def => def && def.isOrthographicCamera;
50
- const isRef = obj => obj && obj.hasOwnProperty('current');
40
+ const catalogue = {};
41
+ const extend = objects => void Object.assign(catalogue, objects);
42
+ function createRenderer(_roots, _getEventPriority) {
43
+ function createInstance(type, {
44
+ args = [],
45
+ attach,
46
+ ...props
47
+ }, root) {
48
+ let name = `${type[0].toUpperCase()}${type.slice(1)}`;
49
+ let instance;
50
+ if (type === 'primitive') {
51
+ if (props.object === undefined) throw new Error("R3F: Primitives without 'object' are invalid!");
52
+ const object = props.object;
53
+ instance = prepare(object, {
54
+ type,
55
+ root,
56
+ attach,
57
+ primitive: true
58
+ });
59
+ } else {
60
+ const target = catalogue[name];
61
+ if (!target) {
62
+ 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`);
63
+ }
51
64
 
52
- /**
53
- * An SSR-friendly useLayoutEffect.
54
- *
55
- * React currently throws a warning when using useLayoutEffect on the server.
56
- * To get around it, we can conditionally useEffect on the server (no-op) and
57
- * useLayoutEffect elsewhere.
58
- *
59
- * @see https://github.com/facebook/react/issues/14927
60
- */
61
- 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__namespace.useLayoutEffect : React__namespace.useEffect;
62
- function useMutableCallback(fn) {
63
- const ref = React__namespace.useRef(fn);
64
- useIsomorphicLayoutEffect(() => void (ref.current = fn), [fn]);
65
- return ref;
66
- }
67
- function Block({
68
- set
69
- }) {
70
- useIsomorphicLayoutEffect(() => {
71
- set(new Promise(() => null));
72
- return () => set(false);
73
- }, [set]);
74
- return null;
75
- }
76
- class ErrorBoundary extends React__namespace.Component {
77
- constructor(...args) {
78
- super(...args);
79
- this.state = {
80
- error: false
81
- };
82
- }
83
- componentDidCatch(err) {
84
- this.props.set(err);
85
- }
86
- render() {
87
- return this.state.error ? null : this.props.children;
88
- }
89
- }
90
- ErrorBoundary.getDerivedStateFromError = () => ({
91
- error: true
92
- });
93
- const DEFAULT = '__default';
94
- const DEFAULTS = new Map();
95
- const isDiffSet = def => def && !!def.memoized && !!def.changes;
96
- function calculateDpr(dpr) {
97
- const target = typeof window !== 'undefined' ? window.devicePixelRatio : 1;
98
- return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
99
- }
65
+ // Throw if an object or literal was passed for args
66
+ if (!Array.isArray(args)) throw new Error('R3F: The args prop must be an array!');
100
67
 
101
- /**
102
- * Returns instance root state
103
- */
104
- const getRootState = obj => {
105
- var _r3f;
106
- return (_r3f = obj.__r3f) == null ? void 0 : _r3f.root.getState();
107
- };
108
- // A collection of compare functions
109
- const is = {
110
- obj: a => a === Object(a) && !is.arr(a) && typeof a !== 'function',
111
- fun: a => typeof a === 'function',
112
- str: a => typeof a === 'string',
113
- num: a => typeof a === 'number',
114
- boo: a => typeof a === 'boolean',
115
- und: a => a === void 0,
116
- arr: a => Array.isArray(a),
117
- equ(a, b, {
118
- arrays = 'shallow',
119
- objects = 'reference',
120
- strict = true
121
- } = {}) {
122
- // Wrong type or one of the two undefined, doesn't match
123
- if (typeof a !== typeof b || !!a !== !!b) return false;
124
- // Atomic, just compare a against b
125
- if (is.str(a) || is.num(a)) return a === b;
126
- const isObj = is.obj(a);
127
- if (isObj && objects === 'reference') return a === b;
128
- const isArr = is.arr(a);
129
- if (isArr && arrays === 'reference') return a === b;
130
- // Array or Object, shallow compare first to see if it's a match
131
- if ((isArr || isObj) && a === b) return true;
132
- // Last resort, go through keys
133
- let i;
134
- // Check if a has all the keys of b
135
- for (i in a) if (!(i in b)) return false;
136
- // Check if values between keys match
137
- if (isObj && arrays === 'shallow' && objects === 'shallow') {
138
- for (i in strict ? b : a) if (!is.equ(a[i], b[i], {
139
- strict,
140
- objects: 'reference'
141
- })) return false;
142
- } else {
143
- for (i in strict ? b : a) if (a[i] !== b[i]) return false;
68
+ // Instanciate new object, link it to the root
69
+ // Append memoized props with args so it's not forgotten
70
+ instance = prepare(new target(...args), {
71
+ type,
72
+ root,
73
+ attach,
74
+ // Save args in case we need to reconstruct later for HMR
75
+ memoizedProps: {
76
+ args
77
+ }
78
+ });
144
79
  }
145
- // If i is undefined
146
- if (is.und(i)) {
147
- // If both arrays are empty we consider them equal
148
- if (isArr && a.length === 0 && b.length === 0) return true;
149
- // If both objects are empty we consider them equal
150
- if (isObj && Object.keys(a).length === 0 && Object.keys(b).length === 0) return true;
151
- // Otherwise match them by value
152
- if (a !== b) return false;
80
+
81
+ // Auto-attach geometries and materials
82
+ if (instance.__r3f.attach === undefined) {
83
+ if (instance instanceof THREE__namespace.BufferGeometry) instance.__r3f.attach = 'geometry';else if (instance instanceof THREE__namespace.Material) instance.__r3f.attach = 'material';
153
84
  }
154
- return true;
155
- }
156
- };
157
85
 
158
- // Collects nodes and materials from a THREE.Object3D
159
- function buildGraph(object) {
160
- const data = {
161
- nodes: {},
162
- materials: {}
163
- };
164
- if (object) {
165
- object.traverse(obj => {
166
- if (obj.name) data.nodes[obj.name] = obj;
167
- if (obj.material && !data.materials[obj.material.name]) data.materials[obj.material.name] = obj.material;
168
- });
86
+ // It should NOT call onUpdate on object instanciation, because it hasn't been added to the
87
+ // view yet. If the callback relies on references for instance, they won't be ready yet, this is
88
+ // why it passes "true" here
89
+ // There is no reason to apply props to injects
90
+ if (name !== 'inject') applyProps$1(instance, props);
91
+ return instance;
169
92
  }
170
- return data;
171
- }
172
-
173
- // Disposes an object and all its properties
174
- function dispose(obj) {
175
- if (obj.dispose && obj.type !== 'Scene') obj.dispose();
176
- for (const p in obj) {
177
- p.dispose == null ? void 0 : p.dispose();
178
- delete obj[p];
93
+ function appendChild(parentInstance, child) {
94
+ let added = false;
95
+ if (child) {
96
+ var _child$__r3f, _parentInstance$__r3f;
97
+ // The attach attribute implies that the object attaches itself on the parent
98
+ if ((_child$__r3f = child.__r3f) != null && _child$__r3f.attach) {
99
+ attach(parentInstance, child, child.__r3f.attach);
100
+ } else if (child.isObject3D && parentInstance.isObject3D) {
101
+ // add in the usual parent-child way
102
+ parentInstance.add(child);
103
+ added = true;
104
+ }
105
+ // This is for anything that used attach, and for non-Object3Ds that don't get attached to props;
106
+ // that is, anything that's a child in React but not a child in the scenegraph.
107
+ if (!added) (_parentInstance$__r3f = parentInstance.__r3f) == null ? void 0 : _parentInstance$__r3f.objects.push(child);
108
+ if (!child.__r3f) prepare(child, {});
109
+ child.__r3f.parent = parentInstance;
110
+ updateInstance(child);
111
+ invalidateInstance(child);
112
+ }
179
113
  }
180
- }
181
-
182
- // Each object in the scene carries a small LocalState descriptor
183
- function prepare(object, state) {
184
- const instance = object;
185
- if (state != null && state.primitive || !instance.__r3f) {
186
- instance.__r3f = {
187
- type: '',
188
- root: null,
189
- previousAttach: null,
190
- memoizedProps: {},
191
- eventCount: 0,
192
- handlers: {},
193
- objects: [],
194
- parent: null,
195
- ...state
196
- };
114
+ function insertBefore(parentInstance, child, beforeChild) {
115
+ let added = false;
116
+ if (child) {
117
+ var _child$__r3f2, _parentInstance$__r3f2;
118
+ if ((_child$__r3f2 = child.__r3f) != null && _child$__r3f2.attach) {
119
+ attach(parentInstance, child, child.__r3f.attach);
120
+ } else if (child.isObject3D && parentInstance.isObject3D) {
121
+ child.parent = parentInstance;
122
+ child.dispatchEvent({
123
+ type: 'added'
124
+ });
125
+ const restSiblings = parentInstance.children.filter(sibling => sibling !== child);
126
+ const index = restSiblings.indexOf(beforeChild);
127
+ parentInstance.children = [...restSiblings.slice(0, index), child, ...restSiblings.slice(index)];
128
+ added = true;
129
+ }
130
+ if (!added) (_parentInstance$__r3f2 = parentInstance.__r3f) == null ? void 0 : _parentInstance$__r3f2.objects.push(child);
131
+ if (!child.__r3f) prepare(child, {});
132
+ child.__r3f.parent = parentInstance;
133
+ updateInstance(child);
134
+ invalidateInstance(child);
135
+ }
197
136
  }
198
- return object;
199
- }
200
- function resolve(instance, key) {
201
- let target = instance;
202
- if (key.includes('-')) {
203
- const entries = key.split('-');
204
- const last = entries.pop();
205
- target = entries.reduce((acc, key) => acc[key], instance);
206
- return {
207
- target,
208
- key: last
209
- };
210
- } else return {
211
- target,
212
- key
213
- };
214
- }
215
-
216
- // Checks if a dash-cased string ends with an integer
217
- const INDEX_REGEX = /-\d+$/;
218
- function attach(parent, child, type) {
219
- if (is.str(type)) {
220
- // If attaching into an array (foo-0), create one
221
- if (INDEX_REGEX.test(type)) {
222
- const root = type.replace(INDEX_REGEX, '');
223
- const {
224
- target,
225
- key
226
- } = resolve(parent, root);
227
- if (!Array.isArray(target[key])) target[key] = [];
228
- }
229
- const {
230
- target,
231
- key
232
- } = resolve(parent, type);
233
- child.__r3f.previousAttach = target[key];
234
- target[key] = child;
235
- } else child.__r3f.previousAttach = type(parent, child);
236
- }
237
- function detach(parent, child, type) {
238
- var _child$__r3f, _child$__r3f2;
239
- if (is.str(type)) {
240
- const {
241
- target,
242
- key
243
- } = resolve(parent, type);
244
- const previous = child.__r3f.previousAttach;
245
- // When the previous value was undefined, it means the value was never set to begin with
246
- if (previous === undefined) delete target[key];
247
- // Otherwise set the previous value
248
- else target[key] = previous;
249
- } else (_child$__r3f = child.__r3f) == null ? void 0 : _child$__r3f.previousAttach == null ? void 0 : _child$__r3f.previousAttach(parent, child);
250
- (_child$__r3f2 = child.__r3f) == null ? true : delete _child$__r3f2.previousAttach;
251
- }
252
-
253
- // This function prepares a set of changes to be applied to the instance
254
- function diffProps(instance, {
255
- children: cN,
256
- key: kN,
257
- ref: rN,
258
- ...props
259
- }, {
260
- children: cP,
261
- key: kP,
262
- ref: rP,
263
- ...previous
264
- } = {}, remove = false) {
265
- var _instance$__r3f;
266
- const localState = (_instance$__r3f = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f : {};
267
- const entries = Object.entries(props);
268
- const changes = [];
269
-
270
- // Catch removed props, prepend them so they can be reset or removed
271
- if (remove) {
272
- const previousKeys = Object.keys(previous);
273
- for (let i = 0; i < previousKeys.length; i++) {
274
- if (!props.hasOwnProperty(previousKeys[i])) entries.unshift([previousKeys[i], DEFAULT + 'remove']);
275
- }
137
+ function removeRecursive(array, parent, dispose = false) {
138
+ if (array) [...array].forEach(child => removeChild(parent, child, dispose));
276
139
  }
277
- entries.forEach(([key, value]) => {
278
- var _instance$__r3f2;
279
- // Bail out on primitive object
280
- if ((_instance$__r3f2 = instance.__r3f) != null && _instance$__r3f2.primitive && key === 'object') return;
281
- // When props match bail out
282
- if (is.equ(value, previous[key])) return;
283
- // Collect handlers and bail out
284
- if (/^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/.test(key)) return changes.push([key, value, true, []]);
285
- // Split dashed props
286
- let entries = [];
287
- if (key.includes('-')) entries = key.split('-');
288
- changes.push([key, value, false, entries]);
140
+ function removeChild(parentInstance, child, dispose) {
141
+ if (child) {
142
+ var _parentInstance$__r3f3, _child$__r3f3, _child$__r3f5;
143
+ // Clear the parent reference
144
+ if (child.__r3f) child.__r3f.parent = null;
145
+ // Remove child from the parents objects
146
+ if ((_parentInstance$__r3f3 = parentInstance.__r3f) != null && _parentInstance$__r3f3.objects) parentInstance.__r3f.objects = parentInstance.__r3f.objects.filter(x => x !== child);
147
+ // Remove attachment
148
+ if ((_child$__r3f3 = child.__r3f) != null && _child$__r3f3.attach) {
149
+ detach(parentInstance, child, child.__r3f.attach);
150
+ } else if (child.isObject3D && parentInstance.isObject3D) {
151
+ var _child$__r3f4;
152
+ parentInstance.remove(child);
153
+ // Remove interactivity
154
+ if ((_child$__r3f4 = child.__r3f) != null && _child$__r3f4.root) {
155
+ removeInteractivity(child.__r3f.root, child);
156
+ }
157
+ }
289
158
 
290
- // Reset pierced props
291
- for (const prop in props) {
292
- const value = props[prop];
293
- if (prop.startsWith(`${key}-`)) changes.push([prop, value, false, prop.split('-')]);
294
- }
295
- });
296
- const memoized = {
297
- ...props
298
- };
299
- if (localState.memoizedProps && localState.memoizedProps.args) memoized.args = localState.memoizedProps.args;
300
- if (localState.memoizedProps && localState.memoizedProps.attach) memoized.attach = localState.memoizedProps.attach;
301
- return {
302
- memoized,
303
- changes
304
- };
305
- }
159
+ // Allow objects to bail out of recursive dispose altogether by passing dispose={null}
160
+ // Never dispose of primitives because their state may be kept outside of React!
161
+ // In order for an object to be able to dispose it has to have
162
+ // - a dispose method,
163
+ // - it cannot be a <primitive object={...} />
164
+ // - it cannot be a THREE.Scene, because three has broken it's own api
165
+ //
166
+ // Since disposal is recursive, we can check the optional dispose arg, which will be undefined
167
+ // when the reconciler calls it, but then carry our own check recursively
168
+ const isPrimitive = (_child$__r3f5 = child.__r3f) == null ? void 0 : _child$__r3f5.primitive;
169
+ const shouldDispose = dispose === undefined ? child.dispose !== null && !isPrimitive : dispose;
306
170
 
307
- // This function applies a set of changes to the instance
308
- function applyProps$1(instance, data) {
309
- var _instance$__r3f3, _root$getState, _instance$__r3f4;
310
- // Filter equals, events and reserved props
311
- const localState = (_instance$__r3f3 = instance.__r3f) != null ? _instance$__r3f3 : {};
312
- const root = localState.root;
313
- const rootState = (_root$getState = root == null ? void 0 : root.getState == null ? void 0 : root.getState()) != null ? _root$getState : {};
314
- const {
315
- memoized,
316
- changes
317
- } = isDiffSet(data) ? data : diffProps(instance, data);
318
- const prevHandlers = localState.eventCount;
171
+ // Remove nested child objects. Primitives should not have objects and children that are
172
+ // attached to them declaratively ...
173
+ if (!isPrimitive) {
174
+ var _child$__r3f6;
175
+ removeRecursive((_child$__r3f6 = child.__r3f) == null ? void 0 : _child$__r3f6.objects, child, shouldDispose);
176
+ removeRecursive(child.children, child, shouldDispose);
177
+ }
319
178
 
320
- // Prepare memoized props
321
- if (instance.__r3f) instance.__r3f.memoizedProps = memoized;
322
- for (let i = 0; i < changes.length; i++) {
323
- let [key, value, isEvent, keys] = changes[i];
324
- let currentInstance = instance;
325
- let targetProp = currentInstance[key];
179
+ // Remove references
180
+ if (child.__r3f) {
181
+ delete child.__r3f.root;
182
+ delete child.__r3f.objects;
183
+ delete child.__r3f.handlers;
184
+ delete child.__r3f.memoizedProps;
185
+ if (!isPrimitive) delete child.__r3f;
186
+ }
326
187
 
327
- // Revolve dashed props
328
- if (keys.length) {
329
- targetProp = keys.reduce((acc, key) => acc[key], instance);
330
- // If the target is atomic, it forces us to switch the root
331
- if (!(targetProp && targetProp.set)) {
332
- const [name, ...reverseEntries] = keys.reverse();
333
- currentInstance = reverseEntries.reverse().reduce((acc, key) => acc[key], instance);
334
- key = name;
188
+ // Dispose item whenever the reconciler feels like it
189
+ if (shouldDispose && child.dispose && child.type !== 'Scene') {
190
+ scheduler.unstable_scheduleCallback(scheduler.unstable_IdlePriority, () => {
191
+ try {
192
+ child.dispose();
193
+ } catch (e) {
194
+ /* ... */
195
+ }
196
+ });
335
197
  }
198
+ invalidateInstance(parentInstance);
336
199
  }
200
+ }
201
+ function switchInstance(instance, type, newProps, fiber) {
202
+ var _instance$__r3f;
203
+ const parent = (_instance$__r3f = instance.__r3f) == null ? void 0 : _instance$__r3f.parent;
204
+ if (!parent) return;
205
+ const newInstance = createInstance(type, newProps, instance.__r3f.root);
337
206
 
338
- // https://github.com/mrdoob/three.js/issues/21209
339
- // HMR/fast-refresh relies on the ability to cancel out props, but threejs
340
- // has no means to do this. Hence we curate a small collection of value-classes
341
- // with their respective constructor/set arguments
342
- // For removed props, try to set default values, if possible
343
- if (value === DEFAULT + 'remove') {
344
- if (currentInstance.constructor) {
345
- // create a blank slate of the instance and copy the particular parameter.
346
- let ctor = DEFAULTS.get(currentInstance.constructor);
347
- if (!ctor) {
348
- // @ts-ignore
349
- ctor = new currentInstance.constructor();
350
- DEFAULTS.set(currentInstance.constructor, ctor);
351
- }
352
- value = ctor[key];
353
- } else {
354
- // instance does not have constructor, just set it to 0
355
- value = 0;
207
+ // https://github.com/pmndrs/react-three-fiber/issues/1348
208
+ // When args change the instance has to be re-constructed, which then
209
+ // forces r3f to re-parent the children and non-scene objects
210
+ if (instance.children) {
211
+ for (const child of instance.children) {
212
+ if (child.__r3f) appendChild(newInstance, child);
356
213
  }
214
+ instance.children = instance.children.filter(child => !child.__r3f);
215
+ }
216
+ instance.__r3f.objects.forEach(child => appendChild(newInstance, child));
217
+ instance.__r3f.objects = [];
218
+ if (!instance.__r3f.autoRemovedBeforeAppend) {
219
+ removeChild(parent, instance);
220
+ }
221
+ if (newInstance.parent) {
222
+ newInstance.__r3f.autoRemovedBeforeAppend = true;
357
223
  }
224
+ appendChild(parent, newInstance);
358
225
 
359
- // Deal with pointer events ...
360
- if (isEvent) {
361
- if (value) localState.handlers[key] = value;else delete localState.handlers[key];
362
- localState.eventCount = Object.keys(localState.handlers).length;
226
+ // Re-bind event handlers
227
+ if (newInstance.raycast && newInstance.__r3f.eventCount) {
228
+ const rootState = newInstance.__r3f.root.getState();
229
+ rootState.internal.interaction.push(newInstance);
363
230
  }
364
- // Special treatment for objects with support for set/copy, and layers
365
- else if (targetProp && targetProp.set && (targetProp.copy || targetProp instanceof THREE__namespace.Layers)) {
366
- // If value is an array
367
- if (Array.isArray(value)) {
368
- if (targetProp.fromArray) targetProp.fromArray(value);else targetProp.set(...value);
369
- }
370
- // Test again target.copy(class) next ...
371
- else if (targetProp.copy && value && value.constructor && targetProp.constructor === value.constructor) {
372
- targetProp.copy(value);
373
- }
374
- // If nothing else fits, just set the single value, ignore undefined
375
- // https://github.com/pmndrs/react-three-fiber/issues/274
376
- else if (value !== undefined) {
377
- const isColor = targetProp instanceof THREE__namespace.Color;
378
- // Allow setting array scalars
379
- if (!isColor && targetProp.setScalar) targetProp.setScalar(value);
380
- // Layers have no copy function, we must therefore copy the mask property
381
- else if (targetProp instanceof THREE__namespace.Layers && value instanceof THREE__namespace.Layers) targetProp.mask = value.mask;
382
- // Otherwise just set ...
383
- else targetProp.set(value);
384
- // For versions of three which don't support THREE.ColorManagement,
385
- // Auto-convert sRGB colors
386
- // https://github.com/pmndrs/react-three-fiber/issues/344
387
- if (!ColorManagement && !rootState.linear && isColor) targetProp.convertSRGBToLinear();
388
- }
389
- // Else, just overwrite the value
390
- } else {
391
- currentInstance[key] = value;
392
- // Auto-convert sRGB textures, for now ...
393
- // https://github.com/pmndrs/react-three-fiber/issues/344
394
- if (!rootState.linear && currentInstance[key] instanceof THREE__namespace.Texture &&
395
- // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
396
- currentInstance[key].format === THREE__namespace.RGBAFormat && currentInstance[key].type === THREE__namespace.UnsignedByteType) {
397
- currentInstance[key].encoding = THREE__namespace.sRGBEncoding;
231
+ [fiber, fiber.alternate].forEach(fiber => {
232
+ if (fiber !== null) {
233
+ fiber.stateNode = newInstance;
234
+ if (fiber.ref) {
235
+ if (typeof fiber.ref === 'function') fiber.ref(newInstance);else fiber.ref.current = newInstance;
236
+ }
398
237
  }
399
- }
400
- invalidateInstance(instance);
401
- }
402
- if (localState.parent && rootState.internal && instance.raycast && prevHandlers !== localState.eventCount) {
403
- // Pre-emptively remove the instance from the interaction manager
404
- const index = rootState.internal.interaction.indexOf(instance);
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 (localState.eventCount) rootState.internal.interaction.push(instance);
238
+ });
408
239
  }
409
240
 
410
- // Call the update lifecycle when it is being updated, but only when it is part of the scene.
411
- // Skip updates to the `onUpdate` prop itself
412
- const isCircular = changes.length === 1 && changes[0][0] === 'onUpdate';
413
- if (!isCircular && changes.length && (_instance$__r3f4 = instance.__r3f) != null && _instance$__r3f4.parent) updateInstance(instance);
414
- return instance;
415
- }
416
- function invalidateInstance(instance) {
417
- var _instance$__r3f5, _instance$__r3f5$root;
418
- const state = (_instance$__r3f5 = instance.__r3f) == null ? void 0 : (_instance$__r3f5$root = _instance$__r3f5.root) == null ? void 0 : _instance$__r3f5$root.getState == null ? void 0 : _instance$__r3f5$root.getState();
419
- if (state && state.internal.frames === 0) state.invalidate();
420
- }
421
- function updateInstance(instance) {
422
- instance.onUpdate == null ? void 0 : instance.onUpdate(instance);
423
- }
424
- function updateCamera(camera, size) {
425
- // https://github.com/pmndrs/react-three-fiber/issues/92
426
- // Do not mess with the camera if it belongs to the user
427
- if (!camera.manual) {
428
- if (isOrthographicCamera(camera)) {
429
- camera.left = size.width / -2;
430
- camera.right = size.width / 2;
431
- camera.top = size.height / 2;
432
- camera.bottom = size.height / -2;
433
- } else {
434
- camera.aspect = size.width / size.height;
435
- }
436
- camera.updateProjectionMatrix();
437
- // https://github.com/pmndrs/react-three-fiber/issues/178
438
- // Update matrix world since the renderer is a frame late
439
- camera.updateMatrixWorld();
440
- }
441
- }
241
+ // Don't handle text instances, warn on undefined behavior
242
+ const handleTextInstance = () => console.warn('Text is not allowed in the R3F tree! This could be stray whitespace or characters.');
243
+ const reconciler = Reconciler__default["default"]({
244
+ createInstance,
245
+ removeChild,
246
+ appendChild,
247
+ appendInitialChild: appendChild,
248
+ insertBefore,
249
+ supportsMutation: true,
250
+ isPrimaryRenderer: false,
251
+ supportsPersistence: false,
252
+ supportsHydration: false,
253
+ noTimeout: -1,
254
+ appendChildToContainer: (container, child) => {
255
+ if (!child) return;
442
256
 
443
- function makeId(event) {
444
- return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
445
- }
257
+ // Don't append to unmounted container
258
+ const scene = container.getState().scene;
259
+ if (!scene.__r3f) return;
446
260
 
447
- // https://github.com/facebook/react/tree/main/packages/react-reconciler#getcurrenteventpriority
448
- // Gives React a clue as to how import the current interaction is
449
- function getEventPriority() {
450
- var _globalScope$event;
451
- // Get a handle to the current global scope in window and worker contexts if able
452
- // https://github.com/pmndrs/react-three-fiber/pull/2493
453
- const globalScope = typeof self !== 'undefined' && self || typeof window !== 'undefined' && window;
454
- if (!globalScope) return constants.DefaultEventPriority;
455
- const name = (_globalScope$event = globalScope.event) == null ? void 0 : _globalScope$event.type;
456
- switch (name) {
457
- case 'click':
458
- case 'contextmenu':
459
- case 'dblclick':
460
- case 'pointercancel':
461
- case 'pointerdown':
462
- case 'pointerup':
463
- return constants.DiscreteEventPriority;
464
- case 'pointermove':
465
- case 'pointerout':
466
- case 'pointerover':
467
- case 'pointerenter':
468
- case 'pointerleave':
469
- case 'wheel':
470
- return constants.ContinuousEventPriority;
471
- default:
472
- return constants.DefaultEventPriority;
473
- }
261
+ // Link current root to the default scene
262
+ scene.__r3f.root = container;
263
+ appendChild(scene, child);
264
+ },
265
+ removeChildFromContainer: (container, child) => {
266
+ if (!child) return;
267
+ removeChild(container.getState().scene, child);
268
+ },
269
+ insertInContainerBefore: (container, child, beforeChild) => {
270
+ if (!child || !beforeChild) return;
271
+
272
+ // Don't append to unmounted container
273
+ const scene = container.getState().scene;
274
+ if (!scene.__r3f) return;
275
+ insertBefore(scene, child, beforeChild);
276
+ },
277
+ getRootHostContext: () => null,
278
+ getChildHostContext: parentHostContext => parentHostContext,
279
+ finalizeInitialChildren(instance) {
280
+ var _instance$__r3f2;
281
+ const localState = (_instance$__r3f2 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f2 : {};
282
+ // https://github.com/facebook/react/issues/20271
283
+ // Returning true will trigger commitMount
284
+ return Boolean(localState.handlers);
285
+ },
286
+ prepareUpdate(instance, _type, oldProps, newProps) {
287
+ // Create diff-sets
288
+ if (instance.__r3f.primitive && newProps.object && newProps.object !== instance) {
289
+ return [true];
290
+ } else {
291
+ // This is a data object, let's extract critical information about it
292
+ const {
293
+ args: argsNew = [],
294
+ children: cN,
295
+ ...restNew
296
+ } = newProps;
297
+ const {
298
+ args: argsOld = [],
299
+ children: cO,
300
+ ...restOld
301
+ } = oldProps;
302
+
303
+ // Throw if an object or literal was passed for args
304
+ if (!Array.isArray(argsNew)) throw new Error('R3F: the args prop must be an array!');
305
+
306
+ // If it has new props or arguments, then it needs to be re-instantiated
307
+ if (argsNew.some((value, index) => value !== argsOld[index])) return [true];
308
+ // Create a diff-set, flag if there are any changes
309
+ const diff = diffProps(instance, restNew, restOld, true);
310
+ if (diff.changes.length) return [false, diff];
311
+
312
+ // Otherwise do not touch the instance
313
+ return null;
314
+ }
315
+ },
316
+ commitUpdate(instance, [reconstruct, diff], type, _oldProps, newProps, fiber) {
317
+ // Reconstruct when args or <primitive object={...} have changes
318
+ if (reconstruct) switchInstance(instance, type, newProps, fiber);
319
+ // Otherwise just overwrite props
320
+ else applyProps$1(instance, diff);
321
+ },
322
+ commitMount(instance, _type, _props, _int) {
323
+ var _instance$__r3f3;
324
+ // https://github.com/facebook/react/issues/20271
325
+ // This will make sure events are only added once to the central container
326
+ const localState = (_instance$__r3f3 = instance.__r3f) != null ? _instance$__r3f3 : {};
327
+ if (instance.raycast && localState.handlers && localState.eventCount) {
328
+ instance.__r3f.root.getState().internal.interaction.push(instance);
329
+ }
330
+ },
331
+ getPublicInstance: instance => instance,
332
+ prepareForCommit: () => null,
333
+ preparePortalMount: container => prepare(container.getState().scene),
334
+ resetAfterCommit: () => {},
335
+ shouldSetTextContent: () => false,
336
+ clearContainer: () => false,
337
+ hideInstance(instance) {
338
+ var _instance$__r3f4;
339
+ // Detach while the instance is hidden
340
+ const {
341
+ attach: type,
342
+ parent
343
+ } = (_instance$__r3f4 = instance.__r3f) != null ? _instance$__r3f4 : {};
344
+ if (type && parent) detach(parent, instance, type);
345
+ if (instance.isObject3D) instance.visible = false;
346
+ invalidateInstance(instance);
347
+ },
348
+ unhideInstance(instance, props) {
349
+ var _instance$__r3f5;
350
+ // Re-attach when the instance is unhidden
351
+ const {
352
+ attach: type,
353
+ parent
354
+ } = (_instance$__r3f5 = instance.__r3f) != null ? _instance$__r3f5 : {};
355
+ if (type && parent) attach(parent, instance, type);
356
+ if (instance.isObject3D && props.visible == null || props.visible) instance.visible = true;
357
+ invalidateInstance(instance);
358
+ },
359
+ createTextInstance: handleTextInstance,
360
+ hideTextInstance: handleTextInstance,
361
+ unhideTextInstance: handleTextInstance,
362
+ // https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r916356874
363
+ // @ts-ignore
364
+ getCurrentEventPriority: () => _getEventPriority ? _getEventPriority() : constants.DefaultEventPriority,
365
+ beforeActiveInstanceBlur: () => {},
366
+ afterActiveInstanceBlur: () => {},
367
+ detachDeletedInstance: () => {},
368
+ now: typeof performance !== 'undefined' && is.fun(performance.now) ? performance.now : is.fun(Date.now) ? Date.now : () => 0,
369
+ // https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r920883503
370
+ scheduleTimeout: is.fun(setTimeout) ? setTimeout : undefined,
371
+ cancelTimeout: is.fun(clearTimeout) ? clearTimeout : undefined
372
+ });
373
+ return {
374
+ reconciler,
375
+ applyProps: applyProps$1
376
+ };
474
377
  }
475
378
 
379
+ var _window$document, _window$navigator;
476
380
  /**
477
- * Release pointer captures.
478
- * This is called by releasePointerCapture in the API, and when an object is removed.
381
+ * The current THREE.ColorManagement instance, if present.
479
382
  */
480
- function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
481
- const captureData = captures.get(obj);
482
- if (captureData) {
483
- captures.delete(obj);
484
- // If this was the last capturing object for this pointer
485
- if (captures.size === 0) {
486
- capturedMap.delete(pointerId);
487
- captureData.target.releasePointerCapture(pointerId);
488
- }
383
+ const getColorManagement = () => {
384
+ var _ColorManagement;
385
+ return (_ColorManagement = catalogue.ColorManagement) != null ? _ColorManagement : null;
386
+ };
387
+ const isOrthographicCamera = def => def && def.isOrthographicCamera;
388
+ const isRef = obj => obj && obj.hasOwnProperty('current');
389
+
390
+ /**
391
+ * An SSR-friendly useLayoutEffect.
392
+ *
393
+ * React currently throws a warning when using useLayoutEffect on the server.
394
+ * To get around it, we can conditionally useEffect on the server (no-op) and
395
+ * useLayoutEffect elsewhere.
396
+ *
397
+ * @see https://github.com/facebook/react/issues/14927
398
+ */
399
+ 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__namespace.useLayoutEffect : React__namespace.useEffect;
400
+ function useMutableCallback(fn) {
401
+ const ref = React__namespace.useRef(fn);
402
+ useIsomorphicLayoutEffect(() => void (ref.current = fn), [fn]);
403
+ return ref;
404
+ }
405
+ function Block({
406
+ set
407
+ }) {
408
+ useIsomorphicLayoutEffect(() => {
409
+ set(new Promise(() => null));
410
+ return () => set(false);
411
+ }, [set]);
412
+ return null;
413
+ }
414
+ class ErrorBoundary extends React__namespace.Component {
415
+ constructor(...args) {
416
+ super(...args);
417
+ this.state = {
418
+ error: false
419
+ };
420
+ }
421
+ componentDidCatch(err) {
422
+ this.props.set(err);
423
+ }
424
+ render() {
425
+ return this.state.error ? null : this.props.children;
489
426
  }
490
427
  }
491
- function removeInteractivity(store, object) {
492
- const {
493
- internal
494
- } = store.getState();
495
- // Removes every trace of an object from the data store
496
- internal.interaction = internal.interaction.filter(o => o !== object);
497
- internal.initialHits = internal.initialHits.filter(o => o !== object);
498
- internal.hovered.forEach((value, key) => {
499
- if (value.eventObject === object || value.object === object) {
500
- // Clear out intersects, they are outdated by now
501
- internal.hovered.delete(key);
502
- }
503
- });
504
- internal.capturedMap.forEach((captures, pointerId) => {
505
- releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
506
- });
428
+ ErrorBoundary.getDerivedStateFromError = () => ({
429
+ error: true
430
+ });
431
+ const DEFAULT = '__default';
432
+ const DEFAULTS = new Map();
433
+ const isDiffSet = def => def && !!def.memoized && !!def.changes;
434
+ function calculateDpr(dpr) {
435
+ const target = typeof window !== 'undefined' ? window.devicePixelRatio : 1;
436
+ return Array.isArray(dpr) ? Math.min(Math.max(dpr[0], target), dpr[1]) : dpr;
507
437
  }
508
- function createEvents(store) {
509
- /** Calculates delta */
510
- function calculateDistance(event) {
511
- const {
512
- internal
513
- } = store.getState();
514
- const dx = event.offsetX - internal.initialClick[0];
515
- const dy = event.offsetY - internal.initialClick[1];
516
- return Math.round(Math.sqrt(dx * dx + dy * dy));
517
- }
518
438
 
519
- /** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
520
- function filterPointerEvents(objects) {
521
- return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
522
- var _r3f;
523
- return (_r3f = obj.__r3f) == null ? void 0 : _r3f.handlers['onPointer' + name];
524
- }));
525
- }
526
- function intersect(event, filter) {
527
- const state = store.getState();
528
- const duplicates = new Set();
529
- const intersections = [];
530
- // Allow callers to eliminate event objects
531
- const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
532
- // Reset all raycaster cameras to undefined
533
- for (let i = 0; i < eventsObjects.length; i++) {
534
- const state = getRootState(eventsObjects[i]);
535
- if (state) {
536
- state.raycaster.camera = undefined;
537
- }
538
- }
539
- if (!state.previousRoot) {
540
- // Make sure root-level pointer and ray are set up
541
- state.events.compute == null ? void 0 : state.events.compute(event, state);
439
+ /**
440
+ * Returns instance root state
441
+ */
442
+ const getRootState = obj => {
443
+ var _r3f;
444
+ return (_r3f = obj.__r3f) == null ? void 0 : _r3f.root.getState();
445
+ };
446
+ // A collection of compare functions
447
+ const is = {
448
+ obj: a => a === Object(a) && !is.arr(a) && typeof a !== 'function',
449
+ fun: a => typeof a === 'function',
450
+ str: a => typeof a === 'string',
451
+ num: a => typeof a === 'number',
452
+ boo: a => typeof a === 'boolean',
453
+ und: a => a === void 0,
454
+ arr: a => Array.isArray(a),
455
+ equ(a, b, {
456
+ arrays = 'shallow',
457
+ objects = 'reference',
458
+ strict = true
459
+ } = {}) {
460
+ // Wrong type or one of the two undefined, doesn't match
461
+ if (typeof a !== typeof b || !!a !== !!b) return false;
462
+ // Atomic, just compare a against b
463
+ if (is.str(a) || is.num(a)) return a === b;
464
+ const isObj = is.obj(a);
465
+ if (isObj && objects === 'reference') return a === b;
466
+ const isArr = is.arr(a);
467
+ if (isArr && arrays === 'reference') return a === b;
468
+ // Array or Object, shallow compare first to see if it's a match
469
+ if ((isArr || isObj) && a === b) return true;
470
+ // Last resort, go through keys
471
+ let i;
472
+ // Check if a has all the keys of b
473
+ for (i in a) if (!(i in b)) return false;
474
+ // Check if values between keys match
475
+ if (isObj && arrays === 'shallow' && objects === 'shallow') {
476
+ for (i in strict ? b : a) if (!is.equ(a[i], b[i], {
477
+ strict,
478
+ objects: 'reference'
479
+ })) return false;
480
+ } else {
481
+ for (i in strict ? b : a) if (a[i] !== b[i]) return false;
542
482
  }
543
- function handleRaycast(obj) {
544
- const state = getRootState(obj);
545
- // Skip event handling when noEvents is set, or when the raycasters camera is null
546
- if (!state || !state.events.enabled || state.raycaster.camera === null) return [];
547
-
548
- // When the camera is undefined we have to call the event layers update function
549
- if (state.raycaster.camera === undefined) {
550
- var _state$previousRoot;
551
- state.events.compute == null ? void 0 : state.events.compute(event, state, (_state$previousRoot = state.previousRoot) == null ? void 0 : _state$previousRoot.getState());
552
- // If the camera is still undefined we have to skip this layer entirely
553
- if (state.raycaster.camera === undefined) state.raycaster.camera = null;
554
- }
555
-
556
- // Intersect object by object
557
- return state.raycaster.camera ? state.raycaster.intersectObject(obj, true) : [];
483
+ // If i is undefined
484
+ if (is.und(i)) {
485
+ // If both arrays are empty we consider them equal
486
+ if (isArr && a.length === 0 && b.length === 0) return true;
487
+ // If both objects are empty we consider them equal
488
+ if (isObj && Object.keys(a).length === 0 && Object.keys(b).length === 0) return true;
489
+ // Otherwise match them by value
490
+ if (a !== b) return false;
558
491
  }
492
+ return true;
493
+ }
494
+ };
559
495
 
560
- // Collect events
561
- let hits = eventsObjects
562
- // Intersect objects
563
- .flatMap(handleRaycast)
564
- // Sort by event priority and distance
565
- .sort((a, b) => {
566
- const aState = getRootState(a.object);
567
- const bState = getRootState(b.object);
568
- if (!aState || !bState) return a.distance - b.distance;
569
- return bState.events.priority - aState.events.priority || a.distance - b.distance;
570
- })
571
- // Filter out duplicates
572
- .filter(item => {
573
- const id = makeId(item);
574
- if (duplicates.has(id)) return false;
575
- duplicates.add(id);
576
- return true;
496
+ // Collects nodes and materials from a THREE.Object3D
497
+ function buildGraph(object) {
498
+ const data = {
499
+ nodes: {},
500
+ materials: {}
501
+ };
502
+ if (object) {
503
+ object.traverse(obj => {
504
+ if (obj.name) data.nodes[obj.name] = obj;
505
+ if (obj.material && !data.materials[obj.material.name]) data.materials[obj.material.name] = obj.material;
577
506
  });
578
-
579
- // https://github.com/mrdoob/three.js/issues/16031
580
- // Allow custom userland intersect sort order, this likely only makes sense on the root filter
581
- if (state.events.filter) hits = state.events.filter(hits, state);
582
-
583
- // Bubble up the events, find the event source (eventObject)
584
- for (const hit of hits) {
585
- let eventObject = hit.object;
586
- // Bubble event up
587
- while (eventObject) {
588
- var _r3f2;
589
- if ((_r3f2 = eventObject.__r3f) != null && _r3f2.eventCount) intersections.push({
590
- ...hit,
591
- eventObject
592
- });
593
- eventObject = eventObject.parent;
594
- }
595
- }
596
-
597
- // If the interaction is captured, make all capturing targets part of the intersect.
598
- if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) {
599
- for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
600
- if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
601
- }
602
- }
603
- return intersections;
604
507
  }
508
+ return data;
509
+ }
605
510
 
606
- /** Handles intersections by forwarding them to handlers */
607
- function handleIntersects(intersections, event, delta, callback) {
608
- const rootState = store.getState();
609
-
610
- // If anything has been found, forward it to the event listeners
611
- if (intersections.length) {
612
- const localState = {
613
- stopped: false
614
- };
615
- for (const hit of intersections) {
616
- const state = getRootState(hit.object) || rootState;
617
- const {
618
- raycaster,
619
- pointer,
620
- camera,
621
- internal
622
- } = state;
623
- const unprojectedPoint = new THREE__namespace.Vector3(pointer.x, pointer.y, 0).unproject(camera);
624
- const hasPointerCapture = id => {
625
- var _internal$capturedMap, _internal$capturedMap2;
626
- return (_internal$capturedMap = (_internal$capturedMap2 = internal.capturedMap.get(id)) == null ? void 0 : _internal$capturedMap2.has(hit.eventObject)) != null ? _internal$capturedMap : false;
627
- };
628
- const setPointerCapture = id => {
629
- const captureData = {
630
- intersection: hit,
631
- target: event.target
632
- };
633
- if (internal.capturedMap.has(id)) {
634
- // if the pointerId was previously captured, we add the hit to the
635
- // event capturedMap.
636
- internal.capturedMap.get(id).set(hit.eventObject, captureData);
637
- } else {
638
- // if the pointerId was not previously captured, we create a map
639
- // containing the hitObject, and the hit. hitObject is used for
640
- // faster access.
641
- internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
642
- }
643
- event.target.setPointerCapture(id);
644
- };
645
- const releasePointerCapture = id => {
646
- const captures = internal.capturedMap.get(id);
647
- if (captures) {
648
- releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
649
- }
650
- };
651
-
652
- // Add native event props
653
- let extractEventProps = {};
654
- // 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.
655
- for (let prop in event) {
656
- let property = event[prop];
657
- // Only copy over atomics, leave functions alone as these should be
658
- // called as event.nativeEvent.fn()
659
- if (typeof property !== 'function') extractEventProps[prop] = property;
660
- }
661
- let raycastEvent = {
662
- ...hit,
663
- ...extractEventProps,
664
- pointer,
665
- intersections,
666
- stopped: localState.stopped,
667
- delta,
668
- unprojectedPoint,
669
- ray: raycaster.ray,
670
- camera: camera,
671
- // Hijack stopPropagation, which just sets a flag
672
- stopPropagation() {
673
- // https://github.com/pmndrs/react-three-fiber/issues/596
674
- // Events are not allowed to stop propagation if the pointer has been captured
675
- const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId);
511
+ // Disposes an object and all its properties
512
+ function dispose(obj) {
513
+ if (obj.dispose && obj.type !== 'Scene') obj.dispose();
514
+ for (const p in obj) {
515
+ p.dispose == null ? void 0 : p.dispose();
516
+ delete obj[p];
517
+ }
518
+ }
676
519
 
677
- // We only authorize stopPropagation...
678
- if (
679
- // ...if this pointer hasn't been captured
680
- !capturesForPointer ||
681
- // ... or if the hit object is capturing the pointer
682
- capturesForPointer.has(hit.eventObject)) {
683
- raycastEvent.stopped = localState.stopped = true;
684
- // Propagation is stopped, remove all other hover records
685
- // An event handler is only allowed to flush other handlers if it is hovered itself
686
- if (internal.hovered.size && Array.from(internal.hovered.values()).find(i => i.eventObject === hit.eventObject)) {
687
- // Objects cannot flush out higher up objects that have already caught the event
688
- const higher = intersections.slice(0, intersections.indexOf(hit));
689
- cancelPointer([...higher, hit]);
690
- }
691
- }
692
- },
693
- // there should be a distinction between target and currentTarget
694
- target: {
695
- hasPointerCapture,
696
- setPointerCapture,
697
- releasePointerCapture
698
- },
699
- currentTarget: {
700
- hasPointerCapture,
701
- setPointerCapture,
702
- releasePointerCapture
703
- },
704
- nativeEvent: event
705
- };
520
+ // Each object in the scene carries a small LocalState descriptor
521
+ function prepare(object, state) {
522
+ const instance = object;
523
+ if (state != null && state.primitive || !instance.__r3f) {
524
+ instance.__r3f = {
525
+ type: '',
526
+ root: null,
527
+ previousAttach: null,
528
+ memoizedProps: {},
529
+ eventCount: 0,
530
+ handlers: {},
531
+ objects: [],
532
+ parent: null,
533
+ ...state
534
+ };
535
+ }
536
+ return object;
537
+ }
538
+ function resolve(instance, key) {
539
+ let target = instance;
540
+ if (key.includes('-')) {
541
+ const entries = key.split('-');
542
+ const last = entries.pop();
543
+ target = entries.reduce((acc, key) => acc[key], instance);
544
+ return {
545
+ target,
546
+ key: last
547
+ };
548
+ } else return {
549
+ target,
550
+ key
551
+ };
552
+ }
706
553
 
707
- // Call subscribers
708
- callback(raycastEvent);
709
- // Event bubbling may be interrupted by stopPropagation
710
- if (localState.stopped === true) break;
711
- }
554
+ // Checks if a dash-cased string ends with an integer
555
+ const INDEX_REGEX = /-\d+$/;
556
+ function attach(parent, child, type) {
557
+ if (is.str(type)) {
558
+ // If attaching into an array (foo-0), create one
559
+ if (INDEX_REGEX.test(type)) {
560
+ const root = type.replace(INDEX_REGEX, '');
561
+ const {
562
+ target,
563
+ key
564
+ } = resolve(parent, root);
565
+ if (!Array.isArray(target[key])) target[key] = [];
712
566
  }
713
- return intersections;
714
- }
715
- function cancelPointer(intersections) {
716
567
  const {
717
- internal
718
- } = store.getState();
719
- for (const hoveredObj of internal.hovered.values()) {
720
- // When no objects were hit or the the hovered object wasn't found underneath the cursor
721
- // we call onPointerOut and delete the object from the hovered-elements map
722
- if (!intersections.length || !intersections.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId)) {
723
- const eventObject = hoveredObj.eventObject;
724
- const instance = eventObject.__r3f;
725
- const handlers = instance == null ? void 0 : instance.handlers;
726
- internal.hovered.delete(makeId(hoveredObj));
727
- if (instance != null && instance.eventCount) {
728
- // Clear out intersects, they are outdated by now
729
- const data = {
730
- ...hoveredObj,
731
- intersections
732
- };
733
- handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
734
- handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
735
- }
736
- }
737
- }
738
- }
739
- function pointerMissed(event, objects) {
740
- for (let i = 0; i < objects.length; i++) {
741
- const instance = objects[i].__r3f;
742
- instance == null ? void 0 : instance.handlers.onPointerMissed == null ? void 0 : instance.handlers.onPointerMissed(event);
568
+ target,
569
+ key
570
+ } = resolve(parent, type);
571
+ child.__r3f.previousAttach = target[key];
572
+ target[key] = child;
573
+ } else child.__r3f.previousAttach = type(parent, child);
574
+ }
575
+ function detach(parent, child, type) {
576
+ var _child$__r3f, _child$__r3f2;
577
+ if (is.str(type)) {
578
+ const {
579
+ target,
580
+ key
581
+ } = resolve(parent, type);
582
+ const previous = child.__r3f.previousAttach;
583
+ // When the previous value was undefined, it means the value was never set to begin with
584
+ if (previous === undefined) delete target[key];
585
+ // Otherwise set the previous value
586
+ else target[key] = previous;
587
+ } else (_child$__r3f = child.__r3f) == null ? void 0 : _child$__r3f.previousAttach == null ? void 0 : _child$__r3f.previousAttach(parent, child);
588
+ (_child$__r3f2 = child.__r3f) == null ? true : delete _child$__r3f2.previousAttach;
589
+ }
590
+
591
+ // This function prepares a set of changes to be applied to the instance
592
+ function diffProps(instance, {
593
+ children: cN,
594
+ key: kN,
595
+ ref: rN,
596
+ ...props
597
+ }, {
598
+ children: cP,
599
+ key: kP,
600
+ ref: rP,
601
+ ...previous
602
+ } = {}, remove = false) {
603
+ var _instance$__r3f;
604
+ const localState = (_instance$__r3f = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f : {};
605
+ const entries = Object.entries(props);
606
+ const changes = [];
607
+
608
+ // Catch removed props, prepend them so they can be reset or removed
609
+ if (remove) {
610
+ const previousKeys = Object.keys(previous);
611
+ for (let i = 0; i < previousKeys.length; i++) {
612
+ if (!props.hasOwnProperty(previousKeys[i])) entries.unshift([previousKeys[i], DEFAULT + 'remove']);
743
613
  }
744
614
  }
745
- function handlePointer(name) {
746
- // Deal with cancelation
747
- switch (name) {
748
- case 'onPointerLeave':
749
- case 'onPointerCancel':
750
- return () => cancelPointer([]);
751
- case 'onLostPointerCapture':
752
- return event => {
753
- const {
754
- internal
755
- } = store.getState();
756
- if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
757
- // If the object event interface had onLostPointerCapture, we'd call it here on every
758
- // object that's getting removed.
759
- internal.capturedMap.delete(event.pointerId);
760
- cancelPointer([]);
761
- }
762
- };
763
- }
764
-
765
- // Any other pointer goes here ...
766
- return function handleEvent(event) {
767
- const {
768
- onPointerMissed,
769
- internal
770
- } = store.getState();
615
+ entries.forEach(([key, value]) => {
616
+ var _instance$__r3f2;
617
+ // Bail out on primitive object
618
+ if ((_instance$__r3f2 = instance.__r3f) != null && _instance$__r3f2.primitive && key === 'object') return;
619
+ // When props match bail out
620
+ if (is.equ(value, previous[key])) return;
621
+ // Collect handlers and bail out
622
+ if (/^on(Pointer|Click|DoubleClick|ContextMenu|Wheel)/.test(key)) return changes.push([key, value, true, []]);
623
+ // Split dashed props
624
+ let entries = [];
625
+ if (key.includes('-')) entries = key.split('-');
626
+ changes.push([key, value, false, entries]);
771
627
 
772
- // prepareRay(event)
773
- internal.lastEvent.current = event;
628
+ // Reset pierced props
629
+ for (const prop in props) {
630
+ const value = props[prop];
631
+ if (prop.startsWith(`${key}-`)) changes.push([prop, value, false, prop.split('-')]);
632
+ }
633
+ });
634
+ const memoized = {
635
+ ...props
636
+ };
637
+ if (localState.memoizedProps && localState.memoizedProps.args) memoized.args = localState.memoizedProps.args;
638
+ if (localState.memoizedProps && localState.memoizedProps.attach) memoized.attach = localState.memoizedProps.attach;
639
+ return {
640
+ memoized,
641
+ changes
642
+ };
643
+ }
774
644
 
775
- // Get fresh intersects
776
- const isPointerMove = name === 'onPointerMove';
777
- const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
778
- const filter = isPointerMove ? filterPointerEvents : undefined;
779
- const hits = intersect(event, filter);
780
- const delta = isClickEvent ? calculateDistance(event) : 0;
645
+ // This function applies a set of changes to the instance
646
+ function applyProps$1(instance, data) {
647
+ var _instance$__r3f3, _root$getState, _instance$__r3f4;
648
+ // Filter equals, events and reserved props
649
+ const localState = (_instance$__r3f3 = instance.__r3f) != null ? _instance$__r3f3 : {};
650
+ const root = localState.root;
651
+ const rootState = (_root$getState = root == null ? void 0 : root.getState == null ? void 0 : root.getState()) != null ? _root$getState : {};
652
+ const {
653
+ memoized,
654
+ changes
655
+ } = isDiffSet(data) ? data : diffProps(instance, data);
656
+ const prevHandlers = localState.eventCount;
781
657
 
782
- // Save initial coordinates on pointer-down
783
- if (name === 'onPointerDown') {
784
- internal.initialClick = [event.offsetX, event.offsetY];
785
- internal.initialHits = hits.map(hit => hit.eventObject);
786
- }
658
+ // Prepare memoized props
659
+ if (instance.__r3f) instance.__r3f.memoizedProps = memoized;
660
+ for (let i = 0; i < changes.length; i++) {
661
+ let [key, value, isEvent, keys] = changes[i];
662
+ let currentInstance = instance;
663
+ let targetProp = currentInstance[key];
787
664
 
788
- // If a click yields no results, pass it back to the user as a miss
789
- // Missed events have to come first in order to establish user-land side-effect clean up
790
- if (isClickEvent && !hits.length) {
791
- if (delta <= 2) {
792
- pointerMissed(event, internal.interaction);
793
- if (onPointerMissed) onPointerMissed(event);
794
- }
665
+ // Revolve dashed props
666
+ if (keys.length) {
667
+ targetProp = keys.reduce((acc, key) => acc[key], instance);
668
+ // If the target is atomic, it forces us to switch the root
669
+ if (!(targetProp && targetProp.set)) {
670
+ const [name, ...reverseEntries] = keys.reverse();
671
+ currentInstance = reverseEntries.reverse().reduce((acc, key) => acc[key], instance);
672
+ key = name;
795
673
  }
796
- // Take care of unhover
797
- if (isPointerMove) cancelPointer(hits);
798
- function onIntersect(data) {
799
- const eventObject = data.eventObject;
800
- const instance = eventObject.__r3f;
801
- const handlers = instance == null ? void 0 : instance.handlers;
674
+ }
802
675
 
803
- // Check presence of handlers
804
- if (!(instance != null && instance.eventCount)) return;
805
- if (isPointerMove) {
806
- // Move event ...
807
- if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
808
- // When enter or out is present take care of hover-state
809
- const id = makeId(data);
810
- const hoveredItem = internal.hovered.get(id);
811
- if (!hoveredItem) {
812
- // If the object wasn't previously hovered, book it and call its handler
813
- internal.hovered.set(id, data);
814
- handlers.onPointerOver == null ? void 0 : handlers.onPointerOver(data);
815
- handlers.onPointerEnter == null ? void 0 : handlers.onPointerEnter(data);
816
- } else if (hoveredItem.stopped) {
817
- // If the object was previously hovered and stopped, we shouldn't allow other items to proceed
818
- data.stopPropagation();
819
- }
820
- }
821
- // Call mouse move
822
- handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
823
- } else {
824
- // All other events ...
825
- const handler = handlers[name];
826
- if (handler) {
827
- // Forward all events back to their respective handlers with the exception of click events,
828
- // which must use the initial target
829
- if (!isClickEvent || internal.initialHits.includes(eventObject)) {
830
- // Missed events have to come first
831
- pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
832
- // Now call the handler
833
- handler(data);
834
- }
835
- } else {
836
- // Trigger onPointerMissed on all elements that have pointer over/out handlers, but not click and weren't hit
837
- if (isClickEvent && internal.initialHits.includes(eventObject)) {
838
- pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
839
- }
840
- }
676
+ // https://github.com/mrdoob/three.js/issues/21209
677
+ // HMR/fast-refresh relies on the ability to cancel out props, but threejs
678
+ // has no means to do this. Hence we curate a small collection of value-classes
679
+ // with their respective constructor/set arguments
680
+ // For removed props, try to set default values, if possible
681
+ if (value === DEFAULT + 'remove') {
682
+ if (currentInstance.constructor) {
683
+ // create a blank slate of the instance and copy the particular parameter.
684
+ let ctor = DEFAULTS.get(currentInstance.constructor);
685
+ if (!ctor) {
686
+ // @ts-ignore
687
+ ctor = new currentInstance.constructor();
688
+ DEFAULTS.set(currentInstance.constructor, ctor);
841
689
  }
690
+ value = ctor[key];
691
+ } else {
692
+ // instance does not have constructor, just set it to 0
693
+ value = 0;
842
694
  }
843
- handleIntersects(hits, event, delta, onIntersect);
844
- };
845
- }
846
- return {
847
- handlePointer
848
- };
849
- }
695
+ }
850
696
 
851
- let catalogue = {};
852
- let extend = objects => void (catalogue = {
853
- ...catalogue,
854
- ...objects
855
- });
856
- function createRenderer(_roots, _getEventPriority) {
857
- function createInstance(type, {
858
- args = [],
859
- attach,
860
- ...props
861
- }, root) {
862
- let name = `${type[0].toUpperCase()}${type.slice(1)}`;
863
- let instance;
864
- if (type === 'primitive') {
865
- if (props.object === undefined) throw new Error("R3F: Primitives without 'object' are invalid!");
866
- const object = props.object;
867
- instance = prepare(object, {
868
- type,
869
- root,
870
- attach,
871
- primitive: true
872
- });
697
+ // Deal with pointer events ...
698
+ if (isEvent) {
699
+ if (value) localState.handlers[key] = value;else delete localState.handlers[key];
700
+ localState.eventCount = Object.keys(localState.handlers).length;
701
+ }
702
+ // Special treatment for objects with support for set/copy, and layers
703
+ else if (targetProp && targetProp.set && (targetProp.copy || targetProp instanceof THREE__namespace.Layers)) {
704
+ // If value is an array
705
+ if (Array.isArray(value)) {
706
+ if (targetProp.fromArray) targetProp.fromArray(value);else targetProp.set(...value);
707
+ }
708
+ // Test again target.copy(class) next ...
709
+ else if (targetProp.copy && value && value.constructor && targetProp.constructor === value.constructor) {
710
+ targetProp.copy(value);
711
+ }
712
+ // If nothing else fits, just set the single value, ignore undefined
713
+ // https://github.com/pmndrs/react-three-fiber/issues/274
714
+ else if (value !== undefined) {
715
+ const isColor = targetProp instanceof THREE__namespace.Color;
716
+ // Allow setting array scalars
717
+ if (!isColor && targetProp.setScalar) targetProp.setScalar(value);
718
+ // Layers have no copy function, we must therefore copy the mask property
719
+ else if (targetProp instanceof THREE__namespace.Layers && value instanceof THREE__namespace.Layers) targetProp.mask = value.mask;
720
+ // Otherwise just set ...
721
+ else targetProp.set(value);
722
+ // For versions of three which don't support THREE.ColorManagement,
723
+ // Auto-convert sRGB colors
724
+ // https://github.com/pmndrs/react-three-fiber/issues/344
725
+ if (!getColorManagement() && !rootState.linear && isColor) targetProp.convertSRGBToLinear();
726
+ }
727
+ // Else, just overwrite the value
873
728
  } else {
874
- const target = catalogue[name];
875
- if (!target) {
876
- 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`);
729
+ currentInstance[key] = value;
730
+ // Auto-convert sRGB textures, for now ...
731
+ // https://github.com/pmndrs/react-three-fiber/issues/344
732
+ if (!rootState.linear && currentInstance[key] instanceof THREE__namespace.Texture &&
733
+ // sRGB textures must be RGBA8 since r137 https://github.com/mrdoob/three.js/pull/23129
734
+ currentInstance[key].format === THREE__namespace.RGBAFormat && currentInstance[key].type === THREE__namespace.UnsignedByteType) {
735
+ currentInstance[key].encoding = THREE__namespace.sRGBEncoding;
877
736
  }
878
-
879
- // Throw if an object or literal was passed for args
880
- if (!Array.isArray(args)) throw new Error('R3F: The args prop must be an array!');
881
-
882
- // Instanciate new object, link it to the root
883
- // Append memoized props with args so it's not forgotten
884
- instance = prepare(new target(...args), {
885
- type,
886
- root,
887
- attach,
888
- // Save args in case we need to reconstruct later for HMR
889
- memoizedProps: {
890
- args
891
- }
892
- });
893
737
  }
738
+ invalidateInstance(instance);
739
+ }
740
+ if (localState.parent && rootState.internal && instance.raycast && prevHandlers !== localState.eventCount) {
741
+ // Pre-emptively remove the instance from the interaction manager
742
+ const index = rootState.internal.interaction.indexOf(instance);
743
+ if (index > -1) rootState.internal.interaction.splice(index, 1);
744
+ // Add the instance to the interaction manager only when it has handlers
745
+ if (localState.eventCount) rootState.internal.interaction.push(instance);
746
+ }
894
747
 
895
- // Auto-attach geometries and materials
896
- if (instance.__r3f.attach === undefined) {
897
- if (instance instanceof THREE__namespace.BufferGeometry) instance.__r3f.attach = 'geometry';else if (instance instanceof THREE__namespace.Material) instance.__r3f.attach = 'material';
748
+ // Call the update lifecycle when it is being updated, but only when it is part of the scene.
749
+ // Skip updates to the `onUpdate` prop itself
750
+ const isCircular = changes.length === 1 && changes[0][0] === 'onUpdate';
751
+ if (!isCircular && changes.length && (_instance$__r3f4 = instance.__r3f) != null && _instance$__r3f4.parent) updateInstance(instance);
752
+ return instance;
753
+ }
754
+ function invalidateInstance(instance) {
755
+ var _instance$__r3f5, _instance$__r3f5$root;
756
+ const state = (_instance$__r3f5 = instance.__r3f) == null ? void 0 : (_instance$__r3f5$root = _instance$__r3f5.root) == null ? void 0 : _instance$__r3f5$root.getState == null ? void 0 : _instance$__r3f5$root.getState();
757
+ if (state && state.internal.frames === 0) state.invalidate();
758
+ }
759
+ function updateInstance(instance) {
760
+ instance.onUpdate == null ? void 0 : instance.onUpdate(instance);
761
+ }
762
+ function updateCamera(camera, size) {
763
+ // https://github.com/pmndrs/react-three-fiber/issues/92
764
+ // Do not mess with the camera if it belongs to the user
765
+ if (!camera.manual) {
766
+ if (isOrthographicCamera(camera)) {
767
+ camera.left = size.width / -2;
768
+ camera.right = size.width / 2;
769
+ camera.top = size.height / 2;
770
+ camera.bottom = size.height / -2;
771
+ } else {
772
+ camera.aspect = size.width / size.height;
898
773
  }
774
+ camera.updateProjectionMatrix();
775
+ // https://github.com/pmndrs/react-three-fiber/issues/178
776
+ // Update matrix world since the renderer is a frame late
777
+ camera.updateMatrixWorld();
778
+ }
779
+ }
899
780
 
900
- // It should NOT call onUpdate on object instanciation, because it hasn't been added to the
901
- // view yet. If the callback relies on references for instance, they won't be ready yet, this is
902
- // why it passes "true" here
903
- // There is no reason to apply props to injects
904
- if (name !== 'inject') applyProps$1(instance, props);
905
- return instance;
781
+ function makeId(event) {
782
+ return (event.eventObject || event.object).uuid + '/' + event.index + event.instanceId;
783
+ }
784
+
785
+ // https://github.com/facebook/react/tree/main/packages/react-reconciler#getcurrenteventpriority
786
+ // Gives React a clue as to how import the current interaction is
787
+ function getEventPriority() {
788
+ var _globalScope$event;
789
+ // Get a handle to the current global scope in window and worker contexts if able
790
+ // https://github.com/pmndrs/react-three-fiber/pull/2493
791
+ const globalScope = typeof self !== 'undefined' && self || typeof window !== 'undefined' && window;
792
+ if (!globalScope) return constants.DefaultEventPriority;
793
+ const name = (_globalScope$event = globalScope.event) == null ? void 0 : _globalScope$event.type;
794
+ switch (name) {
795
+ case 'click':
796
+ case 'contextmenu':
797
+ case 'dblclick':
798
+ case 'pointercancel':
799
+ case 'pointerdown':
800
+ case 'pointerup':
801
+ return constants.DiscreteEventPriority;
802
+ case 'pointermove':
803
+ case 'pointerout':
804
+ case 'pointerover':
805
+ case 'pointerenter':
806
+ case 'pointerleave':
807
+ case 'wheel':
808
+ return constants.ContinuousEventPriority;
809
+ default:
810
+ return constants.DefaultEventPriority;
906
811
  }
907
- function appendChild(parentInstance, child) {
908
- let added = false;
909
- if (child) {
910
- var _child$__r3f, _parentInstance$__r3f;
911
- // The attach attribute implies that the object attaches itself on the parent
912
- if ((_child$__r3f = child.__r3f) != null && _child$__r3f.attach) {
913
- attach(parentInstance, child, child.__r3f.attach);
914
- } else if (child.isObject3D && parentInstance.isObject3D) {
915
- // add in the usual parent-child way
916
- parentInstance.add(child);
917
- added = true;
918
- }
919
- // This is for anything that used attach, and for non-Object3Ds that don't get attached to props;
920
- // that is, anything that's a child in React but not a child in the scenegraph.
921
- if (!added) (_parentInstance$__r3f = parentInstance.__r3f) == null ? void 0 : _parentInstance$__r3f.objects.push(child);
922
- if (!child.__r3f) prepare(child, {});
923
- child.__r3f.parent = parentInstance;
924
- updateInstance(child);
925
- invalidateInstance(child);
812
+ }
813
+
814
+ /**
815
+ * Release pointer captures.
816
+ * This is called by releasePointerCapture in the API, and when an object is removed.
817
+ */
818
+ function releaseInternalPointerCapture(capturedMap, obj, captures, pointerId) {
819
+ const captureData = captures.get(obj);
820
+ if (captureData) {
821
+ captures.delete(obj);
822
+ // If this was the last capturing object for this pointer
823
+ if (captures.size === 0) {
824
+ capturedMap.delete(pointerId);
825
+ captureData.target.releasePointerCapture(pointerId);
926
826
  }
927
827
  }
928
- function insertBefore(parentInstance, child, beforeChild) {
929
- let added = false;
930
- if (child) {
931
- var _child$__r3f2, _parentInstance$__r3f2;
932
- if ((_child$__r3f2 = child.__r3f) != null && _child$__r3f2.attach) {
933
- attach(parentInstance, child, child.__r3f.attach);
934
- } else if (child.isObject3D && parentInstance.isObject3D) {
935
- child.parent = parentInstance;
936
- child.dispatchEvent({
937
- type: 'added'
938
- });
939
- const restSiblings = parentInstance.children.filter(sibling => sibling !== child);
940
- const index = restSiblings.indexOf(beforeChild);
941
- parentInstance.children = [...restSiblings.slice(0, index), child, ...restSiblings.slice(index)];
942
- added = true;
828
+ }
829
+ function removeInteractivity(store, object) {
830
+ const {
831
+ internal
832
+ } = store.getState();
833
+ // Removes every trace of an object from the data store
834
+ internal.interaction = internal.interaction.filter(o => o !== object);
835
+ internal.initialHits = internal.initialHits.filter(o => o !== object);
836
+ internal.hovered.forEach((value, key) => {
837
+ if (value.eventObject === object || value.object === object) {
838
+ // Clear out intersects, they are outdated by now
839
+ internal.hovered.delete(key);
840
+ }
841
+ });
842
+ internal.capturedMap.forEach((captures, pointerId) => {
843
+ releaseInternalPointerCapture(internal.capturedMap, object, captures, pointerId);
844
+ });
845
+ }
846
+ function createEvents(store) {
847
+ /** Calculates delta */
848
+ function calculateDistance(event) {
849
+ const {
850
+ internal
851
+ } = store.getState();
852
+ const dx = event.offsetX - internal.initialClick[0];
853
+ const dy = event.offsetY - internal.initialClick[1];
854
+ return Math.round(Math.sqrt(dx * dx + dy * dy));
855
+ }
856
+
857
+ /** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
858
+ function filterPointerEvents(objects) {
859
+ return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
860
+ var _r3f;
861
+ return (_r3f = obj.__r3f) == null ? void 0 : _r3f.handlers['onPointer' + name];
862
+ }));
863
+ }
864
+ function intersect(event, filter) {
865
+ const state = store.getState();
866
+ const duplicates = new Set();
867
+ const intersections = [];
868
+ // Allow callers to eliminate event objects
869
+ const eventsObjects = filter ? filter(state.internal.interaction) : state.internal.interaction;
870
+ // Reset all raycaster cameras to undefined
871
+ for (let i = 0; i < eventsObjects.length; i++) {
872
+ const state = getRootState(eventsObjects[i]);
873
+ if (state) {
874
+ state.raycaster.camera = undefined;
943
875
  }
944
- if (!added) (_parentInstance$__r3f2 = parentInstance.__r3f) == null ? void 0 : _parentInstance$__r3f2.objects.push(child);
945
- if (!child.__r3f) prepare(child, {});
946
- child.__r3f.parent = parentInstance;
947
- updateInstance(child);
948
- invalidateInstance(child);
949
876
  }
950
- }
951
- function removeRecursive(array, parent, dispose = false) {
952
- if (array) [...array].forEach(child => removeChild(parent, child, dispose));
953
- }
954
- function removeChild(parentInstance, child, dispose) {
955
- if (child) {
956
- var _parentInstance$__r3f3, _child$__r3f3, _child$__r3f5;
957
- // Clear the parent reference
958
- if (child.__r3f) child.__r3f.parent = null;
959
- // Remove child from the parents objects
960
- if ((_parentInstance$__r3f3 = parentInstance.__r3f) != null && _parentInstance$__r3f3.objects) parentInstance.__r3f.objects = parentInstance.__r3f.objects.filter(x => x !== child);
961
- // Remove attachment
962
- if ((_child$__r3f3 = child.__r3f) != null && _child$__r3f3.attach) {
963
- detach(parentInstance, child, child.__r3f.attach);
964
- } else if (child.isObject3D && parentInstance.isObject3D) {
965
- var _child$__r3f4;
966
- parentInstance.remove(child);
967
- // Remove interactivity
968
- if ((_child$__r3f4 = child.__r3f) != null && _child$__r3f4.root) {
969
- removeInteractivity(child.__r3f.root, child);
970
- }
877
+ if (!state.previousRoot) {
878
+ // Make sure root-level pointer and ray are set up
879
+ state.events.compute == null ? void 0 : state.events.compute(event, state);
880
+ }
881
+ function handleRaycast(obj) {
882
+ const state = getRootState(obj);
883
+ // Skip event handling when noEvents is set, or when the raycasters camera is null
884
+ if (!state || !state.events.enabled || state.raycaster.camera === null) return [];
885
+
886
+ // When the camera is undefined we have to call the event layers update function
887
+ if (state.raycaster.camera === undefined) {
888
+ var _state$previousRoot;
889
+ state.events.compute == null ? void 0 : state.events.compute(event, state, (_state$previousRoot = state.previousRoot) == null ? void 0 : _state$previousRoot.getState());
890
+ // If the camera is still undefined we have to skip this layer entirely
891
+ if (state.raycaster.camera === undefined) state.raycaster.camera = null;
971
892
  }
972
893
 
973
- // Allow objects to bail out of recursive dispose altogether by passing dispose={null}
974
- // Never dispose of primitives because their state may be kept outside of React!
975
- // In order for an object to be able to dispose it has to have
976
- // - a dispose method,
977
- // - it cannot be a <primitive object={...} />
978
- // - it cannot be a THREE.Scene, because three has broken it's own api
979
- //
980
- // Since disposal is recursive, we can check the optional dispose arg, which will be undefined
981
- // when the reconciler calls it, but then carry our own check recursively
982
- const isPrimitive = (_child$__r3f5 = child.__r3f) == null ? void 0 : _child$__r3f5.primitive;
983
- const shouldDispose = dispose === undefined ? child.dispose !== null && !isPrimitive : dispose;
894
+ // Intersect object by object
895
+ return state.raycaster.camera ? state.raycaster.intersectObject(obj, true) : [];
896
+ }
984
897
 
985
- // Remove nested child objects. Primitives should not have objects and children that are
986
- // attached to them declaratively ...
987
- if (!isPrimitive) {
988
- var _child$__r3f6;
989
- removeRecursive((_child$__r3f6 = child.__r3f) == null ? void 0 : _child$__r3f6.objects, child, shouldDispose);
990
- removeRecursive(child.children, child, shouldDispose);
898
+ // Collect events
899
+ let hits = eventsObjects
900
+ // Intersect objects
901
+ .flatMap(handleRaycast)
902
+ // Sort by event priority and distance
903
+ .sort((a, b) => {
904
+ const aState = getRootState(a.object);
905
+ const bState = getRootState(b.object);
906
+ if (!aState || !bState) return a.distance - b.distance;
907
+ return bState.events.priority - aState.events.priority || a.distance - b.distance;
908
+ })
909
+ // Filter out duplicates
910
+ .filter(item => {
911
+ const id = makeId(item);
912
+ if (duplicates.has(id)) return false;
913
+ duplicates.add(id);
914
+ return true;
915
+ });
916
+
917
+ // https://github.com/mrdoob/three.js/issues/16031
918
+ // Allow custom userland intersect sort order, this likely only makes sense on the root filter
919
+ if (state.events.filter) hits = state.events.filter(hits, state);
920
+
921
+ // Bubble up the events, find the event source (eventObject)
922
+ for (const hit of hits) {
923
+ let eventObject = hit.object;
924
+ // Bubble event up
925
+ while (eventObject) {
926
+ var _r3f2;
927
+ if ((_r3f2 = eventObject.__r3f) != null && _r3f2.eventCount) intersections.push({
928
+ ...hit,
929
+ eventObject
930
+ });
931
+ eventObject = eventObject.parent;
991
932
  }
933
+ }
992
934
 
993
- // Remove references
994
- if (child.__r3f) {
995
- delete child.__r3f.root;
996
- delete child.__r3f.objects;
997
- delete child.__r3f.handlers;
998
- delete child.__r3f.memoizedProps;
999
- if (!isPrimitive) delete child.__r3f;
935
+ // If the interaction is captured, make all capturing targets part of the intersect.
936
+ if ('pointerId' in event && state.internal.capturedMap.has(event.pointerId)) {
937
+ for (let captureData of state.internal.capturedMap.get(event.pointerId).values()) {
938
+ if (!duplicates.has(makeId(captureData.intersection))) intersections.push(captureData.intersection);
1000
939
  }
940
+ }
941
+ return intersections;
942
+ }
1001
943
 
1002
- // Dispose item whenever the reconciler feels like it
1003
- if (shouldDispose && child.dispose && child.type !== 'Scene') {
1004
- scheduler.unstable_scheduleCallback(scheduler.unstable_IdlePriority, () => {
1005
- try {
1006
- child.dispose();
1007
- } catch (e) {
1008
- /* ... */
944
+ /** Handles intersections by forwarding them to handlers */
945
+ function handleIntersects(intersections, event, delta, callback) {
946
+ const rootState = store.getState();
947
+
948
+ // If anything has been found, forward it to the event listeners
949
+ if (intersections.length) {
950
+ const localState = {
951
+ stopped: false
952
+ };
953
+ for (const hit of intersections) {
954
+ const state = getRootState(hit.object) || rootState;
955
+ const {
956
+ raycaster,
957
+ pointer,
958
+ camera,
959
+ internal
960
+ } = state;
961
+ const unprojectedPoint = new THREE__namespace.Vector3(pointer.x, pointer.y, 0).unproject(camera);
962
+ const hasPointerCapture = id => {
963
+ var _internal$capturedMap, _internal$capturedMap2;
964
+ return (_internal$capturedMap = (_internal$capturedMap2 = internal.capturedMap.get(id)) == null ? void 0 : _internal$capturedMap2.has(hit.eventObject)) != null ? _internal$capturedMap : false;
965
+ };
966
+ const setPointerCapture = id => {
967
+ const captureData = {
968
+ intersection: hit,
969
+ target: event.target
970
+ };
971
+ if (internal.capturedMap.has(id)) {
972
+ // if the pointerId was previously captured, we add the hit to the
973
+ // event capturedMap.
974
+ internal.capturedMap.get(id).set(hit.eventObject, captureData);
975
+ } else {
976
+ // if the pointerId was not previously captured, we create a map
977
+ // containing the hitObject, and the hit. hitObject is used for
978
+ // faster access.
979
+ internal.capturedMap.set(id, new Map([[hit.eventObject, captureData]]));
1009
980
  }
1010
- });
981
+ event.target.setPointerCapture(id);
982
+ };
983
+ const releasePointerCapture = id => {
984
+ const captures = internal.capturedMap.get(id);
985
+ if (captures) {
986
+ releaseInternalPointerCapture(internal.capturedMap, hit.eventObject, captures, id);
987
+ }
988
+ };
989
+
990
+ // Add native event props
991
+ let extractEventProps = {};
992
+ // 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.
993
+ for (let prop in event) {
994
+ let property = event[prop];
995
+ // Only copy over atomics, leave functions alone as these should be
996
+ // called as event.nativeEvent.fn()
997
+ if (typeof property !== 'function') extractEventProps[prop] = property;
998
+ }
999
+ let raycastEvent = {
1000
+ ...hit,
1001
+ ...extractEventProps,
1002
+ pointer,
1003
+ intersections,
1004
+ stopped: localState.stopped,
1005
+ delta,
1006
+ unprojectedPoint,
1007
+ ray: raycaster.ray,
1008
+ camera: camera,
1009
+ // Hijack stopPropagation, which just sets a flag
1010
+ stopPropagation() {
1011
+ // https://github.com/pmndrs/react-three-fiber/issues/596
1012
+ // Events are not allowed to stop propagation if the pointer has been captured
1013
+ const capturesForPointer = 'pointerId' in event && internal.capturedMap.get(event.pointerId);
1014
+
1015
+ // We only authorize stopPropagation...
1016
+ if (
1017
+ // ...if this pointer hasn't been captured
1018
+ !capturesForPointer ||
1019
+ // ... or if the hit object is capturing the pointer
1020
+ capturesForPointer.has(hit.eventObject)) {
1021
+ raycastEvent.stopped = localState.stopped = true;
1022
+ // Propagation is stopped, remove all other hover records
1023
+ // An event handler is only allowed to flush other handlers if it is hovered itself
1024
+ if (internal.hovered.size && Array.from(internal.hovered.values()).find(i => i.eventObject === hit.eventObject)) {
1025
+ // Objects cannot flush out higher up objects that have already caught the event
1026
+ const higher = intersections.slice(0, intersections.indexOf(hit));
1027
+ cancelPointer([...higher, hit]);
1028
+ }
1029
+ }
1030
+ },
1031
+ // there should be a distinction between target and currentTarget
1032
+ target: {
1033
+ hasPointerCapture,
1034
+ setPointerCapture,
1035
+ releasePointerCapture
1036
+ },
1037
+ currentTarget: {
1038
+ hasPointerCapture,
1039
+ setPointerCapture,
1040
+ releasePointerCapture
1041
+ },
1042
+ nativeEvent: event
1043
+ };
1044
+
1045
+ // Call subscribers
1046
+ callback(raycastEvent);
1047
+ // Event bubbling may be interrupted by stopPropagation
1048
+ if (localState.stopped === true) break;
1011
1049
  }
1012
- invalidateInstance(parentInstance);
1013
1050
  }
1051
+ return intersections;
1014
1052
  }
1015
- function switchInstance(instance, type, newProps, fiber) {
1016
- var _instance$__r3f;
1017
- const parent = (_instance$__r3f = instance.__r3f) == null ? void 0 : _instance$__r3f.parent;
1018
- if (!parent) return;
1019
- const newInstance = createInstance(type, newProps, instance.__r3f.root);
1020
-
1021
- // https://github.com/pmndrs/react-three-fiber/issues/1348
1022
- // When args change the instance has to be re-constructed, which then
1023
- // forces r3f to re-parent the children and non-scene objects
1024
- if (instance.children) {
1025
- for (const child of instance.children) {
1026
- if (child.__r3f) appendChild(newInstance, child);
1053
+ function cancelPointer(intersections) {
1054
+ const {
1055
+ internal
1056
+ } = store.getState();
1057
+ for (const hoveredObj of internal.hovered.values()) {
1058
+ // When no objects were hit or the the hovered object wasn't found underneath the cursor
1059
+ // we call onPointerOut and delete the object from the hovered-elements map
1060
+ if (!intersections.length || !intersections.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index && hit.instanceId === hoveredObj.instanceId)) {
1061
+ const eventObject = hoveredObj.eventObject;
1062
+ const instance = eventObject.__r3f;
1063
+ const handlers = instance == null ? void 0 : instance.handlers;
1064
+ internal.hovered.delete(makeId(hoveredObj));
1065
+ if (instance != null && instance.eventCount) {
1066
+ // Clear out intersects, they are outdated by now
1067
+ const data = {
1068
+ ...hoveredObj,
1069
+ intersections
1070
+ };
1071
+ handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
1072
+ handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
1073
+ }
1027
1074
  }
1028
- instance.children = instance.children.filter(child => !child.__r3f);
1029
- }
1030
- instance.__r3f.objects.forEach(child => appendChild(newInstance, child));
1031
- instance.__r3f.objects = [];
1032
- if (!instance.__r3f.autoRemovedBeforeAppend) {
1033
- removeChild(parent, instance);
1034
- }
1035
- if (newInstance.parent) {
1036
- newInstance.__r3f.autoRemovedBeforeAppend = true;
1037
1075
  }
1038
- appendChild(parent, newInstance);
1039
-
1040
- // Re-bind event handlers
1041
- if (newInstance.raycast && newInstance.__r3f.eventCount) {
1042
- const rootState = newInstance.__r3f.root.getState();
1043
- rootState.internal.interaction.push(newInstance);
1076
+ }
1077
+ function pointerMissed(event, objects) {
1078
+ for (let i = 0; i < objects.length; i++) {
1079
+ const instance = objects[i].__r3f;
1080
+ instance == null ? void 0 : instance.handlers.onPointerMissed == null ? void 0 : instance.handlers.onPointerMissed(event);
1044
1081
  }
1045
- [fiber, fiber.alternate].forEach(fiber => {
1046
- if (fiber !== null) {
1047
- fiber.stateNode = newInstance;
1048
- if (fiber.ref) {
1049
- if (typeof fiber.ref === 'function') fiber.ref(newInstance);else fiber.ref.current = newInstance;
1050
- }
1051
- }
1052
- });
1053
1082
  }
1083
+ function handlePointer(name) {
1084
+ // Deal with cancelation
1085
+ switch (name) {
1086
+ case 'onPointerLeave':
1087
+ case 'onPointerCancel':
1088
+ return () => cancelPointer([]);
1089
+ case 'onLostPointerCapture':
1090
+ return event => {
1091
+ const {
1092
+ internal
1093
+ } = store.getState();
1094
+ if ('pointerId' in event && internal.capturedMap.has(event.pointerId)) {
1095
+ // If the object event interface had onLostPointerCapture, we'd call it here on every
1096
+ // object that's getting removed.
1097
+ internal.capturedMap.delete(event.pointerId);
1098
+ cancelPointer([]);
1099
+ }
1100
+ };
1101
+ }
1054
1102
 
1055
- // Don't handle text instances, warn on undefined behavior
1056
- const handleTextInstance = () => console.warn('Text is not allowed in the R3F tree! This could be stray whitespace or characters.');
1057
- const reconciler = Reconciler__default["default"]({
1058
- createInstance,
1059
- removeChild,
1060
- appendChild,
1061
- appendInitialChild: appendChild,
1062
- insertBefore,
1063
- supportsMutation: true,
1064
- isPrimaryRenderer: false,
1065
- supportsPersistence: false,
1066
- supportsHydration: false,
1067
- noTimeout: -1,
1068
- appendChildToContainer: (container, child) => {
1069
- if (!child) return;
1070
-
1071
- // Don't append to unmounted container
1072
- const scene = container.getState().scene;
1073
- if (!scene.__r3f) return;
1074
-
1075
- // Link current root to the default scene
1076
- scene.__r3f.root = container;
1077
- appendChild(scene, child);
1078
- },
1079
- removeChildFromContainer: (container, child) => {
1080
- if (!child) return;
1081
- removeChild(container.getState().scene, child);
1082
- },
1083
- insertInContainerBefore: (container, child, beforeChild) => {
1084
- if (!child || !beforeChild) return;
1103
+ // Any other pointer goes here ...
1104
+ return function handleEvent(event) {
1105
+ const {
1106
+ onPointerMissed,
1107
+ internal
1108
+ } = store.getState();
1085
1109
 
1086
- // Don't append to unmounted container
1087
- const scene = container.getState().scene;
1088
- if (!scene.__r3f) return;
1089
- insertBefore(scene, child, beforeChild);
1090
- },
1091
- getRootHostContext: () => null,
1092
- getChildHostContext: parentHostContext => parentHostContext,
1093
- finalizeInitialChildren(instance) {
1094
- var _instance$__r3f2;
1095
- const localState = (_instance$__r3f2 = instance == null ? void 0 : instance.__r3f) != null ? _instance$__r3f2 : {};
1096
- // https://github.com/facebook/react/issues/20271
1097
- // Returning true will trigger commitMount
1098
- return Boolean(localState.handlers);
1099
- },
1100
- prepareUpdate(instance, _type, oldProps, newProps) {
1101
- // Create diff-sets
1102
- if (instance.__r3f.primitive && newProps.object && newProps.object !== instance) {
1103
- return [true];
1104
- } else {
1105
- // This is a data object, let's extract critical information about it
1106
- const {
1107
- args: argsNew = [],
1108
- children: cN,
1109
- ...restNew
1110
- } = newProps;
1111
- const {
1112
- args: argsOld = [],
1113
- children: cO,
1114
- ...restOld
1115
- } = oldProps;
1110
+ // prepareRay(event)
1111
+ internal.lastEvent.current = event;
1116
1112
 
1117
- // Throw if an object or literal was passed for args
1118
- if (!Array.isArray(argsNew)) throw new Error('R3F: the args prop must be an array!');
1113
+ // Get fresh intersects
1114
+ const isPointerMove = name === 'onPointerMove';
1115
+ const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
1116
+ const filter = isPointerMove ? filterPointerEvents : undefined;
1117
+ const hits = intersect(event, filter);
1118
+ const delta = isClickEvent ? calculateDistance(event) : 0;
1119
1119
 
1120
- // If it has new props or arguments, then it needs to be re-instantiated
1121
- if (argsNew.some((value, index) => value !== argsOld[index])) return [true];
1122
- // Create a diff-set, flag if there are any changes
1123
- const diff = diffProps(instance, restNew, restOld, true);
1124
- if (diff.changes.length) return [false, diff];
1120
+ // Save initial coordinates on pointer-down
1121
+ if (name === 'onPointerDown') {
1122
+ internal.initialClick = [event.offsetX, event.offsetY];
1123
+ internal.initialHits = hits.map(hit => hit.eventObject);
1124
+ }
1125
1125
 
1126
- // Otherwise do not touch the instance
1127
- return null;
1126
+ // If a click yields no results, pass it back to the user as a miss
1127
+ // Missed events have to come first in order to establish user-land side-effect clean up
1128
+ if (isClickEvent && !hits.length) {
1129
+ if (delta <= 2) {
1130
+ pointerMissed(event, internal.interaction);
1131
+ if (onPointerMissed) onPointerMissed(event);
1132
+ }
1128
1133
  }
1129
- },
1130
- commitUpdate(instance, [reconstruct, diff], type, _oldProps, newProps, fiber) {
1131
- // Reconstruct when args or <primitive object={...} have changes
1132
- if (reconstruct) switchInstance(instance, type, newProps, fiber);
1133
- // Otherwise just overwrite props
1134
- else applyProps$1(instance, diff);
1135
- },
1136
- commitMount(instance, _type, _props, _int) {
1137
- var _instance$__r3f3;
1138
- // https://github.com/facebook/react/issues/20271
1139
- // This will make sure events are only added once to the central container
1140
- const localState = (_instance$__r3f3 = instance.__r3f) != null ? _instance$__r3f3 : {};
1141
- if (instance.raycast && localState.handlers && localState.eventCount) {
1142
- instance.__r3f.root.getState().internal.interaction.push(instance);
1134
+ // Take care of unhover
1135
+ if (isPointerMove) cancelPointer(hits);
1136
+ function onIntersect(data) {
1137
+ const eventObject = data.eventObject;
1138
+ const instance = eventObject.__r3f;
1139
+ const handlers = instance == null ? void 0 : instance.handlers;
1140
+
1141
+ // Check presence of handlers
1142
+ if (!(instance != null && instance.eventCount)) return;
1143
+ if (isPointerMove) {
1144
+ // Move event ...
1145
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1146
+ // When enter or out is present take care of hover-state
1147
+ const id = makeId(data);
1148
+ const hoveredItem = internal.hovered.get(id);
1149
+ if (!hoveredItem) {
1150
+ // If the object wasn't previously hovered, book it and call its handler
1151
+ internal.hovered.set(id, data);
1152
+ handlers.onPointerOver == null ? void 0 : handlers.onPointerOver(data);
1153
+ handlers.onPointerEnter == null ? void 0 : handlers.onPointerEnter(data);
1154
+ } else if (hoveredItem.stopped) {
1155
+ // If the object was previously hovered and stopped, we shouldn't allow other items to proceed
1156
+ data.stopPropagation();
1157
+ }
1158
+ }
1159
+ // Call mouse move
1160
+ handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
1161
+ } else {
1162
+ // All other events ...
1163
+ const handler = handlers[name];
1164
+ if (handler) {
1165
+ // Forward all events back to their respective handlers with the exception of click events,
1166
+ // which must use the initial target
1167
+ if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1168
+ // Missed events have to come first
1169
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
1170
+ // Now call the handler
1171
+ handler(data);
1172
+ }
1173
+ } else {
1174
+ // Trigger onPointerMissed on all elements that have pointer over/out handlers, but not click and weren't hit
1175
+ if (isClickEvent && internal.initialHits.includes(eventObject)) {
1176
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
1177
+ }
1178
+ }
1179
+ }
1143
1180
  }
1144
- },
1145
- getPublicInstance: instance => instance,
1146
- prepareForCommit: () => null,
1147
- preparePortalMount: container => prepare(container.getState().scene),
1148
- resetAfterCommit: () => {},
1149
- shouldSetTextContent: () => false,
1150
- clearContainer: () => false,
1151
- hideInstance(instance) {
1152
- var _instance$__r3f4;
1153
- // Detach while the instance is hidden
1154
- const {
1155
- attach: type,
1156
- parent
1157
- } = (_instance$__r3f4 = instance.__r3f) != null ? _instance$__r3f4 : {};
1158
- if (type && parent) detach(parent, instance, type);
1159
- if (instance.isObject3D) instance.visible = false;
1160
- invalidateInstance(instance);
1161
- },
1162
- unhideInstance(instance, props) {
1163
- var _instance$__r3f5;
1164
- // Re-attach when the instance is unhidden
1165
- const {
1166
- attach: type,
1167
- parent
1168
- } = (_instance$__r3f5 = instance.__r3f) != null ? _instance$__r3f5 : {};
1169
- if (type && parent) attach(parent, instance, type);
1170
- if (instance.isObject3D && props.visible == null || props.visible) instance.visible = true;
1171
- invalidateInstance(instance);
1172
- },
1173
- createTextInstance: handleTextInstance,
1174
- hideTextInstance: handleTextInstance,
1175
- unhideTextInstance: handleTextInstance,
1176
- // https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r916356874
1177
- // @ts-ignore
1178
- getCurrentEventPriority: () => _getEventPriority ? _getEventPriority() : constants.DefaultEventPriority,
1179
- beforeActiveInstanceBlur: () => {},
1180
- afterActiveInstanceBlur: () => {},
1181
- detachDeletedInstance: () => {},
1182
- now: typeof performance !== 'undefined' && is.fun(performance.now) ? performance.now : is.fun(Date.now) ? Date.now : () => 0,
1183
- // https://github.com/pmndrs/react-three-fiber/pull/2360#discussion_r920883503
1184
- scheduleTimeout: is.fun(setTimeout) ? setTimeout : undefined,
1185
- cancelTimeout: is.fun(clearTimeout) ? clearTimeout : undefined
1186
- });
1181
+ handleIntersects(hits, event, delta, onIntersect);
1182
+ };
1183
+ }
1187
1184
  return {
1188
- reconciler,
1189
- applyProps: applyProps$1
1185
+ handlePointer
1190
1186
  };
1191
1187
  }
1192
1188
 
@@ -1874,6 +1870,7 @@ function createRoot(canvas) {
1874
1870
 
1875
1871
  // Safely set color management if available.
1876
1872
  // Avoid accessing THREE.ColorManagement to play nice with older versions
1873
+ const ColorManagement = getColorManagement();
1877
1874
  if (ColorManagement) {
1878
1875
  if ('enabled' in ColorManagement) ColorManagement.enabled = !legacy;else if ('legacyMode' in ColorManagement) ColorManagement.legacyMode = legacy;
1879
1876
  }