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