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