@react-three/fiber 8.11.8 → 8.11.10

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,15 +10,353 @@ var threeTypes = /*#__PURE__*/Object.freeze({
10
10
  __proto__: null
11
11
  });
12
12
 
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
+ }
37
+
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!');
40
+
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
+ });
52
+ }
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';
57
+ }
58
+
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;
65
+ }
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
+ }
86
+ }
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
+ }
109
+ }
110
+ function removeRecursive(array, parent, dispose = false) {
111
+ if (array) [...array].forEach(child => removeChild(parent, child, dispose));
112
+ }
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
+ }
131
+
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;
143
+
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
+ }
151
+
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
+ }
160
+
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
+ });
170
+ }
171
+ invalidateInstance(parentInstance);
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);
179
+
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);
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;
196
+ }
197
+ appendChild(parent, newInstance);
198
+
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);
203
+ }
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
+ }
210
+ }
211
+ });
212
+ }
213
+
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;
229
+
230
+ // Don't append to unmounted container
231
+ const scene = container.getState().scene;
232
+ if (!scene.__r3f) return;
233
+
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
+ };
350
+ }
351
+
13
352
  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
353
  /**
19
354
  * The current THREE.ColorManagement instance, if present.
20
355
  */
21
- const ColorManagement = 'ColorManagement' in THREE && getDeep(THREE, 'ColorManagement') || null;
356
+ const getColorManagement = () => {
357
+ var _ColorManagement;
358
+ return (_ColorManagement = catalogue.ColorManagement) != null ? _ColorManagement : null;
359
+ };
22
360
  const isOrthographicCamera = def => def && def.isOrthographicCamera;
23
361
  const isRef = obj => obj && obj.hasOwnProperty('current');
24
362
 
@@ -357,7 +695,7 @@ function applyProps$1(instance, data) {
357
695
  // For versions of three which don't support THREE.ColorManagement,
358
696
  // Auto-convert sRGB colors
359
697
  // https://github.com/pmndrs/react-three-fiber/issues/344
360
- if (!ColorManagement && !rootState.linear && isColor) targetProp.convertSRGBToLinear();
698
+ if (!getColorManagement() && !rootState.linear && isColor) targetProp.convertSRGBToLinear();
361
699
  }
362
700
  // Else, just overwrite the value
363
701
  } else {
@@ -442,724 +780,405 @@ function getEventPriority() {
442
780
  case 'wheel':
443
781
  return ContinuousEventPriority;
444
782
  default:
445
- return DefaultEventPriority;
446
- }
447
- }
448
-
449
- /**
450
- * Release pointer captures.
451
- * This is called by releasePointerCapture in the API, and when an object is removed.
452
- */
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
- }
462
- }
463
- }
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
- });
480
- }
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
-
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);
515
- }
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) : [];
531
- }
532
-
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;
550
- });
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
- }
578
-
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);
649
-
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
- };
679
-
680
- // Call subscribers
681
- callback(raycastEvent);
682
- // Event bubbling may be interrupted by stopPropagation
683
- if (localState.stopped === true) break;
684
- }
783
+ return DefaultEventPriority;
784
+ }
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);
685
799
  }
686
- return intersections;
687
800
  }
688
- function cancelPointer(intersections) {
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) {
689
822
  const {
690
823
  internal
691
824
  } = 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);
716
- }
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));
717
828
  }
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();
744
-
745
- // prepareRay(event)
746
- internal.lastEvent.current = event;
747
-
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;
754
-
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
- }
760
-
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
- }
768
- }
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;
775
829
 
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
- }
814
- }
815
- }
816
- handleIntersects(hits, event, delta, onIntersect);
817
- };
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
+ }));
818
836
  }
819
- return {
820
- handlePointer
821
- };
822
- }
823
-
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
- });
846
- } 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`);
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;
850
848
  }
849
+ }
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 [];
851
858
 
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!');
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;
865
+ }
854
866
 
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
- });
867
+ // Intersect object by object
868
+ return state.raycaster.camera ? state.raycaster.intersectObject(obj, true) : [];
866
869
  }
867
870
 
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';
871
- }
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);
872
893
 
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;
879
- }
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;
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;
891
905
  }
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);
899
906
  }
900
- }
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;
907
+
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);
916
912
  }
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
913
  }
914
+ return intersections;
923
915
  }
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
- }
944
- }
945
916
 
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;
917
+ /** Handles intersections by forwarding them to handlers */
918
+ function handleIntersects(intersections, event, delta, callback) {
919
+ const rootState = store.getState();
957
920
 
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);
964
- }
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]]));
953
+ }
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
+ };
965
962
 
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;
973
- }
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);
974
987
 
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
- /* ... */
982
- }
983
- });
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
1048
  }
1003
- instance.__r3f.objects.forEach(child => appendChild(newInstance, child));
1004
- instance.__r3f.objects = [];
1005
- if (!instance.__r3f.autoRemovedBeforeAppend) {
1006
- removeChild(parent, instance);
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);
1007
1054
  }
1008
- if (newInstance.parent) {
1009
- newInstance.__r3f.autoRemovedBeforeAppend = true;
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. We call it on the next frame because onLostPointerCapture
1070
+ // fires before onPointerUp. Otherwise pointerUp would never be called if the event didn't
1071
+ // happen in the object it originated from, leaving components in a in-between state.
1072
+ requestAnimationFrame(() => {
1073
+ // Only release if pointer-up didn't do it already
1074
+ if (internal.capturedMap.has(event.pointerId)) {
1075
+ internal.capturedMap.delete(event.pointerId);
1076
+ cancelPointer([]);
1077
+ }
1078
+ });
1079
+ }
1080
+ };
1010
1081
  }
1011
- appendChild(parent, newInstance);
1012
1082
 
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);
1017
- }
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
- }
1083
+ // Any other pointer goes here ...
1084
+ return function handleEvent(event) {
1085
+ const {
1086
+ onPointerMissed,
1087
+ internal
1088
+ } = store.getState();
1027
1089
 
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;
1090
+ // prepareRay(event)
1091
+ internal.lastEvent.current = event;
1043
1092
 
1044
- // Don't append to unmounted container
1045
- const scene = container.getState().scene;
1046
- if (!scene.__r3f) return;
1093
+ // Get fresh intersects
1094
+ const isPointerMove = name === 'onPointerMove';
1095
+ const isClickEvent = name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick';
1096
+ const filter = isPointerMove ? filterPointerEvents : undefined;
1097
+ const hits = intersect(event, filter);
1098
+ const delta = isClickEvent ? calculateDistance(event) : 0;
1047
1099
 
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;
1100
+ // Save initial coordinates on pointer-down
1101
+ if (name === 'onPointerDown') {
1102
+ internal.initialClick = [event.offsetX, event.offsetY];
1103
+ internal.initialHits = hits.map(hit => hit.eventObject);
1104
+ }
1058
1105
 
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;
1106
+ // If a click yields no results, pass it back to the user as a miss
1107
+ // Missed events have to come first in order to establish user-land side-effect clean up
1108
+ if (isClickEvent && !hits.length) {
1109
+ if (delta <= 2) {
1110
+ pointerMissed(event, internal.interaction);
1111
+ if (onPointerMissed) onPointerMissed(event);
1112
+ }
1113
+ }
1114
+ // Take care of unhover
1115
+ if (isPointerMove) cancelPointer(hits);
1116
+ function onIntersect(data) {
1117
+ const eventObject = data.eventObject;
1118
+ const instance = eventObject.__r3f;
1119
+ const handlers = instance == null ? void 0 : instance.handlers;
1089
1120
 
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!');
1121
+ // Check presence of handlers
1122
+ if (!(instance != null && instance.eventCount)) return;
1092
1123
 
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];
1124
+ /*
1125
+ MAYBE TODO, DELETE IF NOT:
1126
+ Check if the object is captured, captured events should not have intersects running in parallel
1127
+ But wouldn't it be better to just replace capturedMap with a single entry?
1128
+ Also, are we OK with straight up making picking up multiple objects impossible?
1129
+
1130
+ const pointerId = (data as ThreeEvent<PointerEvent>).pointerId
1131
+ if (pointerId !== undefined) {
1132
+ const capturedMeshSet = internal.capturedMap.get(pointerId)
1133
+ if (capturedMeshSet) {
1134
+ const captured = capturedMeshSet.get(eventObject)
1135
+ if (captured && captured.localState.stopped) return
1136
+ }
1137
+ }*/
1098
1138
 
1099
- // Otherwise do not touch the instance
1100
- return null;
1101
- }
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);
1139
+ if (isPointerMove) {
1140
+ // Move event ...
1141
+ if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
1142
+ // When enter or out is present take care of hover-state
1143
+ const id = makeId(data);
1144
+ const hoveredItem = internal.hovered.get(id);
1145
+ if (!hoveredItem) {
1146
+ // If the object wasn't previously hovered, book it and call its handler
1147
+ internal.hovered.set(id, data);
1148
+ handlers.onPointerOver == null ? void 0 : handlers.onPointerOver(data);
1149
+ handlers.onPointerEnter == null ? void 0 : handlers.onPointerEnter(data);
1150
+ } else if (hoveredItem.stopped) {
1151
+ // If the object was previously hovered and stopped, we shouldn't allow other items to proceed
1152
+ data.stopPropagation();
1153
+ }
1154
+ }
1155
+ // Call mouse move
1156
+ handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
1157
+ } else {
1158
+ // All other events ...
1159
+ const handler = handlers[name];
1160
+ if (handler) {
1161
+ // Forward all events back to their respective handlers with the exception of click events,
1162
+ // which must use the initial target
1163
+ if (!isClickEvent || internal.initialHits.includes(eventObject)) {
1164
+ // Missed events have to come first
1165
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
1166
+ // Now call the handler
1167
+ handler(data);
1168
+ }
1169
+ } else {
1170
+ // Trigger onPointerMissed on all elements that have pointer over/out handlers, but not click and weren't hit
1171
+ if (isClickEvent && internal.initialHits.includes(eventObject)) {
1172
+ pointerMissed(event, internal.interaction.filter(object => !internal.initialHits.includes(object)));
1173
+ }
1174
+ }
1175
+ }
1116
1176
  }
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
- });
1177
+ handleIntersects(hits, event, delta, onIntersect);
1178
+ };
1179
+ }
1160
1180
  return {
1161
- reconciler,
1162
- applyProps: applyProps$1
1181
+ handlePointer
1163
1182
  };
1164
1183
  }
1165
1184
 
@@ -1847,6 +1866,7 @@ function createRoot(canvas) {
1847
1866
 
1848
1867
  // Safely set color management if available.
1849
1868
  // Avoid accessing THREE.ColorManagement to play nice with older versions
1869
+ const ColorManagement = getColorManagement();
1850
1870
  if (ColorManagement) {
1851
1871
  if ('enabled' in ColorManagement) ColorManagement.enabled = !legacy;else if ('legacyMode' in ColorManagement) ColorManagement.legacyMode = legacy;
1852
1872
  }