New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@react-three/fiber

Package Overview
Dependencies
Maintainers
14
Versions
266
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@react-three/fiber - npm Package Compare versions

Comparing version 6.0.8 to 6.0.9

1

dist/declarations/src/core/events.d.ts

@@ -53,4 +53,5 @@ import * as THREE from 'three';

}
export declare function removeInteractivity(store: UseStore<RootState>, object: THREE.Object3D): void;
export declare function createEvents(store: UseStore<RootState>): {
handlePointer: (name: string) => (event: DomEvent) => void;
};

3

dist/declarations/src/core/store.d.ts

@@ -6,3 +6,3 @@ import * as THREE from 'three';

import { Instance, InstanceProps } from './renderer';
import { EventManager } from './events';
import { DomEvent, EventManager } from './events';
export interface Intersection extends THREE.Intersection {

@@ -49,2 +49,3 @@ eventObject: THREE.Object3D;

interaction: THREE.Object3D[];
hovered: Map<string, DomEvent>;
subscribers: Subscription[];

@@ -51,0 +52,0 @@ captured: Intersection[] | undefined;

@@ -74,2 +74,329 @@ 'use strict';

function makeId(event) {
return (event.eventObject || event.object).uuid + '/' + event.index;
}
function removeInteractivity(store, object) {
const {
internal
} = store.getState(); // Removes every trace of an object from the data store
internal.interaction = internal.interaction.filter(o => o !== object);
internal.initialHits = internal.initialHits.filter(o => o !== object);
internal.hovered.forEach((value, key) => {
if (value.eventObject === object || value.object === object) {
internal.hovered.delete(key);
}
});
}
function createEvents(store) {
const temp = new THREE__namespace.Vector3();
/** Sets up defaultRaycaster */
function prepareRay(event) {
var _raycaster$computeOff;
const state = store.getState();
const {
raycaster,
mouse,
camera,
size
} = state; // https://github.com/pmndrs/react-three-fiber/pull/782
// Events trigger outside of canvas when moved
const {
offsetX,
offsetY
} = (_raycaster$computeOff = raycaster.computeOffsets == null ? void 0 : raycaster.computeOffsets(event, state)) != null ? _raycaster$computeOff : event;
const {
width,
height
} = size;
mouse.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
raycaster.setFromCamera(mouse, camera);
}
/** Calculates delta */
function calculateDistance(event) {
const {
internal
} = store.getState();
const dx = event.offsetX - internal.initialClick[0];
const dy = event.offsetY - internal.initialClick[1];
return Math.round(Math.sqrt(dx * dx + dy * dy));
}
/** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
function filterPointerEvents(objects) {
return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
var _r3f$handlers;
return (_r3f$handlers = obj.__r3f.handlers) == null ? void 0 : _r3f$handlers['onPointer' + name];
}));
}
function intersect(filter) {
const state = store.getState();
const {
raycaster,
internal
} = state; // Skip event handling when noEvents is set
if (!raycaster.enabled) return [];
const seen = new Set();
const intersections = []; // Allow callers to eliminate event objects
const eventsObjects = filter ? filter(internal.interaction) : internal.interaction; // Intersect known handler objects and filter against duplicates
let intersects = raycaster.intersectObjects(eventsObjects, true).filter(item => {
const id = makeId(item);
if (seen.has(id)) return false;
seen.add(id);
return true;
}); // https://github.com/mrdoob/three.js/issues/16031
// Allow custom userland intersect sort order
if (raycaster.filter) intersects = raycaster.filter(intersects, state);
for (const intersect of intersects) {
let eventObject = intersect.object; // Bubble event up
while (eventObject) {
var _r3f;
const handlers = (_r3f = eventObject.__r3f) == null ? void 0 : _r3f.handlers;
if (handlers) intersections.push({ ...intersect,
eventObject
});
eventObject = eventObject.parent;
}
}
return intersections;
}
/** Creates filtered intersects and returns an array of positive hits */
function patchIntersects(intersections, event) {
const {
internal
} = store.getState(); // If the interaction is captured take that into account, the captured event has to be part of the intersects
if (internal.captured && event.type !== 'click' && event.type !== 'wheel') {
internal.captured.forEach(captured => {
if (!intersections.find(hit => hit.eventObject === captured.eventObject)) intersections.push(captured);
});
}
return intersections;
}
/** Handles intersections by forwarding them to handlers */
function handleIntersects(intersections, event, callback) {
const {
raycaster,
mouse,
camera,
internal
} = store.getState(); // If anything has been found, forward it to the event listeners
if (intersections.length) {
const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
const delta = event.type === 'click' ? calculateDistance(event) : 0;
const releasePointerCapture = id => event.target.releasePointerCapture(id);
const localState = {
stopped: false,
captured: false
};
for (const hit of intersections) {
const setPointerCapture = id => {
// If the hit is going to be captured flag that we're in captured state
if (!localState.captured) {
localState.captured = true; // The captured hit array is reset to collect hits
internal.captured = [];
} // Push hits to the array
if (internal.captured) internal.captured.push(hit) // Call the original event now
;
event.target.setPointerCapture(id);
}; // Add native event props
let extractEventProps = {};
for (let prop in Object.getPrototypeOf(event)) {
extractEventProps[prop] = event[prop];
}
let raycastEvent = { ...hit,
...extractEventProps,
intersections,
stopped: localState.stopped,
delta,
unprojectedPoint,
ray: raycaster.ray,
camera: camera,
// Hijack stopPropagation, which just sets a flag
stopPropagation: () => {
// https://github.com/pmndrs/react-three-fiber/issues/596
// Events are not allowed to stop propagation if the pointer has been captured
const cap = internal.captured;
if (!cap || cap.find(h => h.eventObject.id === hit.eventObject.id)) {
raycastEvent.stopped = localState.stopped = true; // Propagation is stopped, remove all other hover records
// An event handler is only allowed to flush other handlers if it is hovered itself
if (internal.hovered.size && Array.from(internal.hovered.values()).find(i => i.eventObject === hit.eventObject)) {
// Objects cannot flush out higher up objects that have already caught the event
const higher = intersections.slice(0, intersections.indexOf(hit));
cancelPointer([...higher, hit]);
}
}
},
target: { ...event.target,
setPointerCapture,
releasePointerCapture
},
currentTarget: { ...event.currentTarget,
setPointerCapture,
releasePointerCapture
},
sourceEvent: event
}; // Call subscribers
callback(raycastEvent); // Event bubbling may be interrupted by stopPropagation
if (localState.stopped === true) break;
}
}
return intersections;
}
function cancelPointer(hits) {
const {
internal
} = store.getState();
Array.from(internal.hovered.values()).forEach(hoveredObj => {
// When no objects were hit or the the hovered object wasn't found underneath the cursor
// we call onPointerOut and delete the object from the hovered-elements map
if (!hits.length || !hits.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index)) {
const eventObject = hoveredObj.eventObject;
const handlers = eventObject.__r3f.handlers;
internal.hovered.delete(makeId(hoveredObj));
if (handlers) {
// Clear out intersects, they are outdated by now
const data = { ...hoveredObj,
intersections: hits || []
};
handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
}
}
});
}
const handlePointer = name => {
// Deal with cancelation
switch (name) {
case 'onPointerLeave':
case 'onPointerCancel':
return () => cancelPointer([]);
case 'onLostPointerCapture':
return () => (store.getState().internal.captured = undefined, cancelPointer([]));
} // Any other pointer goes here ...
return event => {
const {
onPointerMissed,
internal
} = store.getState();
prepareRay(event); // Get fresh intersects
const isPointerMove = name === 'onPointerMove';
const filter = isPointerMove ? filterPointerEvents : undefined;
const hits = patchIntersects(intersect(filter), event); // Take care of unhover
if (isPointerMove) cancelPointer(hits);
handleIntersects(hits, event, data => {
const eventObject = data.eventObject;
const handlers = eventObject.__r3f.handlers; // Check presence of handlers
if (!handlers) return;
if (isPointerMove) {
// Move event ...
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
// When enter or out is present take care of hover-state
const id = makeId(data);
const hoveredItem = internal.hovered.get(id);
if (!hoveredItem) {
// If the object wasn't previously hovered, book it and call its handler
internal.hovered.set(id, data);
handlers.onPointerOver == null ? void 0 : handlers.onPointerOver(data);
handlers.onPointerEnter == null ? void 0 : handlers.onPointerEnter(data);
} else if (hoveredItem.stopped) {
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
data.stopPropagation();
}
} // Call mouse move
handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
} else {
// All other events ...
const handler = handlers == null ? void 0 : handlers[name];
if (handler) {
// Forward all events back to their respective handlers with the exception of click events,
// which must use the initial target
if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
handler(data);
pointerMissed(event, internal.interaction.filter(object => object !== eventObject));
}
}
}
}); // Save initial coordinates on pointer-down
if (name === 'onPointerDown') {
internal.initialClick = [event.offsetX, event.offsetY];
internal.initialHits = hits.map(hit => hit.eventObject);
} // If a click yields no results, pass it back to the user as a miss
if ((name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick') && !hits.length) {
if (calculateDistance(event) <= 2) {
pointerMissed(event, internal.interaction);
if (onPointerMissed) onPointerMissed();
}
}
};
};
function pointerMissed(event, objects) {
objects.forEach(object => {
var _r3f$handlers2;
return (_r3f$handlers2 = object.__r3f.handlers) == null ? void 0 : _r3f$handlers2.onPointerMissed == null ? void 0 : _r3f$handlers2.onPointerMissed(event);
});
}
return {
handlePointer
};
}
// Type guard to tell a store from a portal

@@ -418,14 +745,16 @@ const isStore = def => def && !!def.getState;

function removeRecursive(array, parent, clone = false) {
if (array) {
// Three uses splice op's internally we may have to shallow-clone the array in order to safely remove items
const target = clone ? [...array] : array;
target.forEach(child => removeChild(parent, child));
}
function removeRecursive(array, parent, dispose = false) {
if (array) [...array].forEach(child => removeChild(parent, child, dispose));
}
function removeChild(parentInstance, child) {
function removeChild(parentInstance, child, dispose) {
if (child) {
if (child.isObject3D) {
parentInstance.remove(child);
var _child$__r3f;
parentInstance.remove(child); // Remove interactivity
if ((_child$__r3f = child.__r3f) != null && _child$__r3f.root) {
removeInteractivity(child.__r3f.root, child);
}
} else {

@@ -442,23 +771,20 @@ child.parent = null;

}
} // Remove interactivity
} // Allow objects to bail out of recursive dispose alltogether by passing dispose={null}
// Never dispose of primitives because their state may be kept outside of React!
// In order for an object to be able to dispose it has to have
// - a dispose method,
// - it cannot be an <instance object={...} />
// - it cannot be a THREE.Scene, because three has broken it's own api
//
// Since disposal is recursive, we can check the optional dispose arg, which will be undefined
// when the reconciler calls it, but then carry our own check recursively
if (child.__r3f.root) {
const rootState = child.__r3f.root.getState();
const shouldDispose = dispose === undefined ? child.dispose !== null && !child.__r3f.instance : dispose; // Remove nested child objects
rootState.internal.interaction = rootState.internal.interaction.filter(x => x !== child);
}
removeRecursive(child.__r3f.objects, child, shouldDispose);
removeRecursive(child.children, child, shouldDispose); // Dispose item whenever the reconciler feels like it
invalidateInstance(parentInstance); // Allow objects to bail out of recursive dispose alltogether by passing dispose={null}
// Never dispose of primitives because their state may be kept outside of React!
if (child.dispose && !child.__r3f.instance) {
const objects = child.__r3f.objects;
scheduler.unstable_runWithPriority(scheduler.unstable_IdlePriority, () => {
// Remove nested child objects
removeRecursive(objects, child);
removeRecursive(child.children, child, true); // Dispose item
if (child.dispose && child.type !== 'Scene') child.dispose();
});
if (shouldDispose && child.dispose && child.type !== 'Scene') {
scheduler.unstable_runWithPriority(scheduler.unstable_IdlePriority, () => child.dispose());
} // Remove references

@@ -472,2 +798,3 @@

delete child.__r3f;
invalidateInstance(parentInstance);
}

@@ -814,2 +1141,3 @@ }

interaction: [],
hovered: new Map(),
subscribers: [],

@@ -972,314 +1300,2 @@ captured: undefined,

function createEvents(store) {
const hovered = new Map();
const temp = new THREE__namespace.Vector3();
/** Sets up defaultRaycaster */
function prepareRay(event) {
var _raycaster$computeOff;
const state = store.getState();
const {
raycaster,
mouse,
camera,
size
} = state; // https://github.com/pmndrs/react-three-fiber/pull/782
// Events trigger outside of canvas when moved
const {
offsetX,
offsetY
} = (_raycaster$computeOff = raycaster.computeOffsets == null ? void 0 : raycaster.computeOffsets(event, state)) != null ? _raycaster$computeOff : event;
const {
width,
height
} = size;
mouse.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
raycaster.setFromCamera(mouse, camera);
}
/** Calculates delta */
function calculateDistance(event) {
const {
internal
} = store.getState();
const dx = event.offsetX - internal.initialClick[0];
const dy = event.offsetY - internal.initialClick[1];
return Math.round(Math.sqrt(dx * dx + dy * dy));
}
/** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
function filterPointerEvents(objects) {
return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
var _r3f$handlers;
return (_r3f$handlers = obj.__r3f.handlers) == null ? void 0 : _r3f$handlers['onPointer' + name];
}));
}
function makeId(event) {
return (event.eventObject || event.object).uuid + '/' + event.index;
}
function intersect(filter) {
const state = store.getState();
const {
raycaster,
internal
} = state; // Skip event handling when noEvents is set
if (!raycaster.enabled) return [];
const seen = new Set();
const intersections = []; // Allow callers to eliminate event objects
const eventsObjects = filter ? filter(internal.interaction) : internal.interaction; // Intersect known handler objects and filter against duplicates
let intersects = raycaster.intersectObjects(eventsObjects, true).filter(item => {
const id = makeId(item);
if (seen.has(id)) return false;
seen.add(id);
return true;
}); // https://github.com/mrdoob/three.js/issues/16031
// Allow custom userland intersect sort order
if (raycaster.filter) intersects = raycaster.filter(intersects, state);
for (const intersect of intersects) {
let eventObject = intersect.object; // Bubble event up
while (eventObject) {
var _r3f;
const handlers = (_r3f = eventObject.__r3f) == null ? void 0 : _r3f.handlers;
if (handlers) intersections.push({ ...intersect,
eventObject
});
eventObject = eventObject.parent;
}
}
return intersections;
}
/** Creates filtered intersects and returns an array of positive hits */
function patchIntersects(intersections, event) {
const {
internal
} = store.getState(); // If the interaction is captured take that into account, the captured event has to be part of the intersects
if (internal.captured && event.type !== 'click' && event.type !== 'wheel') {
internal.captured.forEach(captured => {
if (!intersections.find(hit => hit.eventObject === captured.eventObject)) intersections.push(captured);
});
}
return intersections;
}
/** Handles intersections by forwarding them to handlers */
function handleIntersects(intersections, event, callback) {
const {
raycaster,
mouse,
camera,
internal
} = store.getState(); // If anything has been found, forward it to the event listeners
if (intersections.length) {
const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
const delta = event.type === 'click' ? calculateDistance(event) : 0;
const releasePointerCapture = id => event.target.releasePointerCapture(id);
const localState = {
stopped: false,
captured: false
};
for (const hit of intersections) {
const setPointerCapture = id => {
// If the hit is going to be captured flag that we're in captured state
if (!localState.captured) {
localState.captured = true; // The captured hit array is reset to collect hits
internal.captured = [];
} // Push hits to the array
if (internal.captured) internal.captured.push(hit) // Call the original event now
;
event.target.setPointerCapture(id);
}; // Add native event props
let extractEventProps = {};
for (let prop in Object.getPrototypeOf(event)) {
extractEventProps[prop] = event[prop];
}
let raycastEvent = { ...hit,
...extractEventProps,
intersections,
stopped: localState.stopped,
delta,
unprojectedPoint,
ray: raycaster.ray,
camera: camera,
// Hijack stopPropagation, which just sets a flag
stopPropagation: () => {
// https://github.com/pmndrs/react-three-fiber/issues/596
// Events are not allowed to stop propagation if the pointer has been captured
const cap = internal.captured;
if (!cap || cap.find(h => h.eventObject.id === hit.eventObject.id)) {
raycastEvent.stopped = localState.stopped = true; // Propagation is stopped, remove all other hover records
// An event handler is only allowed to flush other handlers if it is hovered itself
if (hovered.size && Array.from(hovered.values()).find(i => i.eventObject === hit.eventObject)) {
// Objects cannot flush out higher up objects that have already caught the event
const higher = intersections.slice(0, intersections.indexOf(hit));
cancelPointer([...higher, hit]);
}
}
},
target: { ...event.target,
setPointerCapture,
releasePointerCapture
},
currentTarget: { ...event.currentTarget,
setPointerCapture,
releasePointerCapture
},
sourceEvent: event
}; // Call subscribers
callback(raycastEvent); // Event bubbling may be interrupted by stopPropagation
if (localState.stopped === true) break;
}
}
return intersections;
}
function cancelPointer(hits) {
Array.from(hovered.values()).forEach(hoveredObj => {
// When no objects were hit or the the hovered object wasn't found underneath the cursor
// we call onPointerOut and delete the object from the hovered-elements map
if (!hits.length || !hits.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index)) {
const eventObject = hoveredObj.eventObject;
const handlers = eventObject.__r3f.handlers;
hovered.delete(makeId(hoveredObj));
if (handlers) {
// Clear out intersects, they are outdated by now
const data = { ...hoveredObj,
intersections: hits || []
};
handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
}
}
});
}
const handlePointer = name => {
// Deal with cancelation
switch (name) {
case 'onPointerLeave':
case 'onPointerCancel':
return () => cancelPointer([]);
case 'onLostPointerCapture':
return () => (store.getState().internal.captured = undefined, cancelPointer([]));
} // Any other pointer goes here ...
return event => {
const {
onPointerMissed,
internal
} = store.getState();
prepareRay(event); // Get fresh intersects
const isPointerMove = name === 'onPointerMove';
const filter = isPointerMove ? filterPointerEvents : undefined;
const hits = patchIntersects(intersect(filter), event); // Take care of unhover
if (isPointerMove) cancelPointer(hits);
handleIntersects(hits, event, data => {
const eventObject = data.eventObject;
const handlers = eventObject.__r3f.handlers; // Check presence of handlers
if (!handlers) return;
if (isPointerMove) {
// Move event ...
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
// When enter or out is present take care of hover-state
const id = makeId(data);
const hoveredItem = hovered.get(id);
if (!hoveredItem) {
// If the object wasn't previously hovered, book it and call its handler
hovered.set(id, data);
handlers.onPointerOver == null ? void 0 : handlers.onPointerOver(data);
handlers.onPointerEnter == null ? void 0 : handlers.onPointerEnter(data);
} else if (hoveredItem.stopped) {
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
data.stopPropagation();
}
} // Call mouse move
handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
} else {
// All other events ...
const handler = handlers == null ? void 0 : handlers[name];
if (handler) {
// Forward all events back to their respective handlers with the exception of click events,
// which must use the initial target
if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
handler(data);
pointerMissed(event, internal.interaction.filter(object => object !== eventObject));
}
}
}
}); // Save initial coordinates on pointer-down
if (name === 'onPointerDown') {
internal.initialClick = [event.offsetX, event.offsetY];
internal.initialHits = hits.map(hit => hit.eventObject);
} // If a click yields no results, pass it back to the user as a miss
if ((name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick') && !hits.length) {
if (calculateDistance(event) <= 2) {
pointerMissed(event, internal.interaction);
if (onPointerMissed) onPointerMissed();
}
}
};
};
function pointerMissed(event, objects) {
objects.forEach(object => {
var _r3f$handlers2;
return (_r3f$handlers2 = object.__r3f.handlers) == null ? void 0 : _r3f$handlers2.onPointerMissed == null ? void 0 : _r3f$handlers2.onPointerMissed(event);
});
}
return {
handlePointer
};
}
function createPointerEvents(store) {

@@ -1286,0 +1302,0 @@ const {

@@ -74,2 +74,329 @@ 'use strict';

function makeId(event) {
return (event.eventObject || event.object).uuid + '/' + event.index;
}
function removeInteractivity(store, object) {
const {
internal
} = store.getState(); // Removes every trace of an object from the data store
internal.interaction = internal.interaction.filter(o => o !== object);
internal.initialHits = internal.initialHits.filter(o => o !== object);
internal.hovered.forEach((value, key) => {
if (value.eventObject === object || value.object === object) {
internal.hovered.delete(key);
}
});
}
function createEvents(store) {
const temp = new THREE__namespace.Vector3();
/** Sets up defaultRaycaster */
function prepareRay(event) {
var _raycaster$computeOff;
const state = store.getState();
const {
raycaster,
mouse,
camera,
size
} = state; // https://github.com/pmndrs/react-three-fiber/pull/782
// Events trigger outside of canvas when moved
const {
offsetX,
offsetY
} = (_raycaster$computeOff = raycaster.computeOffsets == null ? void 0 : raycaster.computeOffsets(event, state)) != null ? _raycaster$computeOff : event;
const {
width,
height
} = size;
mouse.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
raycaster.setFromCamera(mouse, camera);
}
/** Calculates delta */
function calculateDistance(event) {
const {
internal
} = store.getState();
const dx = event.offsetX - internal.initialClick[0];
const dy = event.offsetY - internal.initialClick[1];
return Math.round(Math.sqrt(dx * dx + dy * dy));
}
/** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
function filterPointerEvents(objects) {
return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
var _r3f$handlers;
return (_r3f$handlers = obj.__r3f.handlers) == null ? void 0 : _r3f$handlers['onPointer' + name];
}));
}
function intersect(filter) {
const state = store.getState();
const {
raycaster,
internal
} = state; // Skip event handling when noEvents is set
if (!raycaster.enabled) return [];
const seen = new Set();
const intersections = []; // Allow callers to eliminate event objects
const eventsObjects = filter ? filter(internal.interaction) : internal.interaction; // Intersect known handler objects and filter against duplicates
let intersects = raycaster.intersectObjects(eventsObjects, true).filter(item => {
const id = makeId(item);
if (seen.has(id)) return false;
seen.add(id);
return true;
}); // https://github.com/mrdoob/three.js/issues/16031
// Allow custom userland intersect sort order
if (raycaster.filter) intersects = raycaster.filter(intersects, state);
for (const intersect of intersects) {
let eventObject = intersect.object; // Bubble event up
while (eventObject) {
var _r3f;
const handlers = (_r3f = eventObject.__r3f) == null ? void 0 : _r3f.handlers;
if (handlers) intersections.push({ ...intersect,
eventObject
});
eventObject = eventObject.parent;
}
}
return intersections;
}
/** Creates filtered intersects and returns an array of positive hits */
function patchIntersects(intersections, event) {
const {
internal
} = store.getState(); // If the interaction is captured take that into account, the captured event has to be part of the intersects
if (internal.captured && event.type !== 'click' && event.type !== 'wheel') {
internal.captured.forEach(captured => {
if (!intersections.find(hit => hit.eventObject === captured.eventObject)) intersections.push(captured);
});
}
return intersections;
}
/** Handles intersections by forwarding them to handlers */
function handleIntersects(intersections, event, callback) {
const {
raycaster,
mouse,
camera,
internal
} = store.getState(); // If anything has been found, forward it to the event listeners
if (intersections.length) {
const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
const delta = event.type === 'click' ? calculateDistance(event) : 0;
const releasePointerCapture = id => event.target.releasePointerCapture(id);
const localState = {
stopped: false,
captured: false
};
for (const hit of intersections) {
const setPointerCapture = id => {
// If the hit is going to be captured flag that we're in captured state
if (!localState.captured) {
localState.captured = true; // The captured hit array is reset to collect hits
internal.captured = [];
} // Push hits to the array
if (internal.captured) internal.captured.push(hit) // Call the original event now
;
event.target.setPointerCapture(id);
}; // Add native event props
let extractEventProps = {};
for (let prop in Object.getPrototypeOf(event)) {
extractEventProps[prop] = event[prop];
}
let raycastEvent = { ...hit,
...extractEventProps,
intersections,
stopped: localState.stopped,
delta,
unprojectedPoint,
ray: raycaster.ray,
camera: camera,
// Hijack stopPropagation, which just sets a flag
stopPropagation: () => {
// https://github.com/pmndrs/react-three-fiber/issues/596
// Events are not allowed to stop propagation if the pointer has been captured
const cap = internal.captured;
if (!cap || cap.find(h => h.eventObject.id === hit.eventObject.id)) {
raycastEvent.stopped = localState.stopped = true; // Propagation is stopped, remove all other hover records
// An event handler is only allowed to flush other handlers if it is hovered itself
if (internal.hovered.size && Array.from(internal.hovered.values()).find(i => i.eventObject === hit.eventObject)) {
// Objects cannot flush out higher up objects that have already caught the event
const higher = intersections.slice(0, intersections.indexOf(hit));
cancelPointer([...higher, hit]);
}
}
},
target: { ...event.target,
setPointerCapture,
releasePointerCapture
},
currentTarget: { ...event.currentTarget,
setPointerCapture,
releasePointerCapture
},
sourceEvent: event
}; // Call subscribers
callback(raycastEvent); // Event bubbling may be interrupted by stopPropagation
if (localState.stopped === true) break;
}
}
return intersections;
}
function cancelPointer(hits) {
const {
internal
} = store.getState();
Array.from(internal.hovered.values()).forEach(hoveredObj => {
// When no objects were hit or the the hovered object wasn't found underneath the cursor
// we call onPointerOut and delete the object from the hovered-elements map
if (!hits.length || !hits.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index)) {
const eventObject = hoveredObj.eventObject;
const handlers = eventObject.__r3f.handlers;
internal.hovered.delete(makeId(hoveredObj));
if (handlers) {
// Clear out intersects, they are outdated by now
const data = { ...hoveredObj,
intersections: hits || []
};
handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
}
}
});
}
const handlePointer = name => {
// Deal with cancelation
switch (name) {
case 'onPointerLeave':
case 'onPointerCancel':
return () => cancelPointer([]);
case 'onLostPointerCapture':
return () => (store.getState().internal.captured = undefined, cancelPointer([]));
} // Any other pointer goes here ...
return event => {
const {
onPointerMissed,
internal
} = store.getState();
prepareRay(event); // Get fresh intersects
const isPointerMove = name === 'onPointerMove';
const filter = isPointerMove ? filterPointerEvents : undefined;
const hits = patchIntersects(intersect(filter), event); // Take care of unhover
if (isPointerMove) cancelPointer(hits);
handleIntersects(hits, event, data => {
const eventObject = data.eventObject;
const handlers = eventObject.__r3f.handlers; // Check presence of handlers
if (!handlers) return;
if (isPointerMove) {
// Move event ...
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
// When enter or out is present take care of hover-state
const id = makeId(data);
const hoveredItem = internal.hovered.get(id);
if (!hoveredItem) {
// If the object wasn't previously hovered, book it and call its handler
internal.hovered.set(id, data);
handlers.onPointerOver == null ? void 0 : handlers.onPointerOver(data);
handlers.onPointerEnter == null ? void 0 : handlers.onPointerEnter(data);
} else if (hoveredItem.stopped) {
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
data.stopPropagation();
}
} // Call mouse move
handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
} else {
// All other events ...
const handler = handlers == null ? void 0 : handlers[name];
if (handler) {
// Forward all events back to their respective handlers with the exception of click events,
// which must use the initial target
if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
handler(data);
pointerMissed(event, internal.interaction.filter(object => object !== eventObject));
}
}
}
}); // Save initial coordinates on pointer-down
if (name === 'onPointerDown') {
internal.initialClick = [event.offsetX, event.offsetY];
internal.initialHits = hits.map(hit => hit.eventObject);
} // If a click yields no results, pass it back to the user as a miss
if ((name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick') && !hits.length) {
if (calculateDistance(event) <= 2) {
pointerMissed(event, internal.interaction);
if (onPointerMissed) onPointerMissed();
}
}
};
};
function pointerMissed(event, objects) {
objects.forEach(object => {
var _r3f$handlers2;
return (_r3f$handlers2 = object.__r3f.handlers) == null ? void 0 : _r3f$handlers2.onPointerMissed == null ? void 0 : _r3f$handlers2.onPointerMissed(event);
});
}
return {
handlePointer
};
}
// Type guard to tell a store from a portal

@@ -418,14 +745,16 @@ const isStore = def => def && !!def.getState;

function removeRecursive(array, parent, clone = false) {
if (array) {
// Three uses splice op's internally we may have to shallow-clone the array in order to safely remove items
const target = clone ? [...array] : array;
target.forEach(child => removeChild(parent, child));
}
function removeRecursive(array, parent, dispose = false) {
if (array) [...array].forEach(child => removeChild(parent, child, dispose));
}
function removeChild(parentInstance, child) {
function removeChild(parentInstance, child, dispose) {
if (child) {
if (child.isObject3D) {
parentInstance.remove(child);
var _child$__r3f;
parentInstance.remove(child); // Remove interactivity
if ((_child$__r3f = child.__r3f) != null && _child$__r3f.root) {
removeInteractivity(child.__r3f.root, child);
}
} else {

@@ -442,23 +771,20 @@ child.parent = null;

}
} // Remove interactivity
} // Allow objects to bail out of recursive dispose alltogether by passing dispose={null}
// Never dispose of primitives because their state may be kept outside of React!
// In order for an object to be able to dispose it has to have
// - a dispose method,
// - it cannot be an <instance object={...} />
// - it cannot be a THREE.Scene, because three has broken it's own api
//
// Since disposal is recursive, we can check the optional dispose arg, which will be undefined
// when the reconciler calls it, but then carry our own check recursively
if (child.__r3f.root) {
const rootState = child.__r3f.root.getState();
const shouldDispose = dispose === undefined ? child.dispose !== null && !child.__r3f.instance : dispose; // Remove nested child objects
rootState.internal.interaction = rootState.internal.interaction.filter(x => x !== child);
}
removeRecursive(child.__r3f.objects, child, shouldDispose);
removeRecursive(child.children, child, shouldDispose); // Dispose item whenever the reconciler feels like it
invalidateInstance(parentInstance); // Allow objects to bail out of recursive dispose alltogether by passing dispose={null}
// Never dispose of primitives because their state may be kept outside of React!
if (child.dispose && !child.__r3f.instance) {
const objects = child.__r3f.objects;
scheduler.unstable_runWithPriority(scheduler.unstable_IdlePriority, () => {
// Remove nested child objects
removeRecursive(objects, child);
removeRecursive(child.children, child, true); // Dispose item
if (child.dispose && child.type !== 'Scene') child.dispose();
});
if (shouldDispose && child.dispose && child.type !== 'Scene') {
scheduler.unstable_runWithPriority(scheduler.unstable_IdlePriority, () => child.dispose());
} // Remove references

@@ -472,2 +798,3 @@

delete child.__r3f;
invalidateInstance(parentInstance);
}

@@ -814,2 +1141,3 @@ }

interaction: [],
hovered: new Map(),
subscribers: [],

@@ -972,314 +1300,2 @@ captured: undefined,

function createEvents(store) {
const hovered = new Map();
const temp = new THREE__namespace.Vector3();
/** Sets up defaultRaycaster */
function prepareRay(event) {
var _raycaster$computeOff;
const state = store.getState();
const {
raycaster,
mouse,
camera,
size
} = state; // https://github.com/pmndrs/react-three-fiber/pull/782
// Events trigger outside of canvas when moved
const {
offsetX,
offsetY
} = (_raycaster$computeOff = raycaster.computeOffsets == null ? void 0 : raycaster.computeOffsets(event, state)) != null ? _raycaster$computeOff : event;
const {
width,
height
} = size;
mouse.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
raycaster.setFromCamera(mouse, camera);
}
/** Calculates delta */
function calculateDistance(event) {
const {
internal
} = store.getState();
const dx = event.offsetX - internal.initialClick[0];
const dy = event.offsetY - internal.initialClick[1];
return Math.round(Math.sqrt(dx * dx + dy * dy));
}
/** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
function filterPointerEvents(objects) {
return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
var _r3f$handlers;
return (_r3f$handlers = obj.__r3f.handlers) == null ? void 0 : _r3f$handlers['onPointer' + name];
}));
}
function makeId(event) {
return (event.eventObject || event.object).uuid + '/' + event.index;
}
function intersect(filter) {
const state = store.getState();
const {
raycaster,
internal
} = state; // Skip event handling when noEvents is set
if (!raycaster.enabled) return [];
const seen = new Set();
const intersections = []; // Allow callers to eliminate event objects
const eventsObjects = filter ? filter(internal.interaction) : internal.interaction; // Intersect known handler objects and filter against duplicates
let intersects = raycaster.intersectObjects(eventsObjects, true).filter(item => {
const id = makeId(item);
if (seen.has(id)) return false;
seen.add(id);
return true;
}); // https://github.com/mrdoob/three.js/issues/16031
// Allow custom userland intersect sort order
if (raycaster.filter) intersects = raycaster.filter(intersects, state);
for (const intersect of intersects) {
let eventObject = intersect.object; // Bubble event up
while (eventObject) {
var _r3f;
const handlers = (_r3f = eventObject.__r3f) == null ? void 0 : _r3f.handlers;
if (handlers) intersections.push({ ...intersect,
eventObject
});
eventObject = eventObject.parent;
}
}
return intersections;
}
/** Creates filtered intersects and returns an array of positive hits */
function patchIntersects(intersections, event) {
const {
internal
} = store.getState(); // If the interaction is captured take that into account, the captured event has to be part of the intersects
if (internal.captured && event.type !== 'click' && event.type !== 'wheel') {
internal.captured.forEach(captured => {
if (!intersections.find(hit => hit.eventObject === captured.eventObject)) intersections.push(captured);
});
}
return intersections;
}
/** Handles intersections by forwarding them to handlers */
function handleIntersects(intersections, event, callback) {
const {
raycaster,
mouse,
camera,
internal
} = store.getState(); // If anything has been found, forward it to the event listeners
if (intersections.length) {
const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
const delta = event.type === 'click' ? calculateDistance(event) : 0;
const releasePointerCapture = id => event.target.releasePointerCapture(id);
const localState = {
stopped: false,
captured: false
};
for (const hit of intersections) {
const setPointerCapture = id => {
// If the hit is going to be captured flag that we're in captured state
if (!localState.captured) {
localState.captured = true; // The captured hit array is reset to collect hits
internal.captured = [];
} // Push hits to the array
if (internal.captured) internal.captured.push(hit) // Call the original event now
;
event.target.setPointerCapture(id);
}; // Add native event props
let extractEventProps = {};
for (let prop in Object.getPrototypeOf(event)) {
extractEventProps[prop] = event[prop];
}
let raycastEvent = { ...hit,
...extractEventProps,
intersections,
stopped: localState.stopped,
delta,
unprojectedPoint,
ray: raycaster.ray,
camera: camera,
// Hijack stopPropagation, which just sets a flag
stopPropagation: () => {
// https://github.com/pmndrs/react-three-fiber/issues/596
// Events are not allowed to stop propagation if the pointer has been captured
const cap = internal.captured;
if (!cap || cap.find(h => h.eventObject.id === hit.eventObject.id)) {
raycastEvent.stopped = localState.stopped = true; // Propagation is stopped, remove all other hover records
// An event handler is only allowed to flush other handlers if it is hovered itself
if (hovered.size && Array.from(hovered.values()).find(i => i.eventObject === hit.eventObject)) {
// Objects cannot flush out higher up objects that have already caught the event
const higher = intersections.slice(0, intersections.indexOf(hit));
cancelPointer([...higher, hit]);
}
}
},
target: { ...event.target,
setPointerCapture,
releasePointerCapture
},
currentTarget: { ...event.currentTarget,
setPointerCapture,
releasePointerCapture
},
sourceEvent: event
}; // Call subscribers
callback(raycastEvent); // Event bubbling may be interrupted by stopPropagation
if (localState.stopped === true) break;
}
}
return intersections;
}
function cancelPointer(hits) {
Array.from(hovered.values()).forEach(hoveredObj => {
// When no objects were hit or the the hovered object wasn't found underneath the cursor
// we call onPointerOut and delete the object from the hovered-elements map
if (!hits.length || !hits.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index)) {
const eventObject = hoveredObj.eventObject;
const handlers = eventObject.__r3f.handlers;
hovered.delete(makeId(hoveredObj));
if (handlers) {
// Clear out intersects, they are outdated by now
const data = { ...hoveredObj,
intersections: hits || []
};
handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
}
}
});
}
const handlePointer = name => {
// Deal with cancelation
switch (name) {
case 'onPointerLeave':
case 'onPointerCancel':
return () => cancelPointer([]);
case 'onLostPointerCapture':
return () => (store.getState().internal.captured = undefined, cancelPointer([]));
} // Any other pointer goes here ...
return event => {
const {
onPointerMissed,
internal
} = store.getState();
prepareRay(event); // Get fresh intersects
const isPointerMove = name === 'onPointerMove';
const filter = isPointerMove ? filterPointerEvents : undefined;
const hits = patchIntersects(intersect(filter), event); // Take care of unhover
if (isPointerMove) cancelPointer(hits);
handleIntersects(hits, event, data => {
const eventObject = data.eventObject;
const handlers = eventObject.__r3f.handlers; // Check presence of handlers
if (!handlers) return;
if (isPointerMove) {
// Move event ...
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
// When enter or out is present take care of hover-state
const id = makeId(data);
const hoveredItem = hovered.get(id);
if (!hoveredItem) {
// If the object wasn't previously hovered, book it and call its handler
hovered.set(id, data);
handlers.onPointerOver == null ? void 0 : handlers.onPointerOver(data);
handlers.onPointerEnter == null ? void 0 : handlers.onPointerEnter(data);
} else if (hoveredItem.stopped) {
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
data.stopPropagation();
}
} // Call mouse move
handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
} else {
// All other events ...
const handler = handlers == null ? void 0 : handlers[name];
if (handler) {
// Forward all events back to their respective handlers with the exception of click events,
// which must use the initial target
if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
handler(data);
pointerMissed(event, internal.interaction.filter(object => object !== eventObject));
}
}
}
}); // Save initial coordinates on pointer-down
if (name === 'onPointerDown') {
internal.initialClick = [event.offsetX, event.offsetY];
internal.initialHits = hits.map(hit => hit.eventObject);
} // If a click yields no results, pass it back to the user as a miss
if ((name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick') && !hits.length) {
if (calculateDistance(event) <= 2) {
pointerMissed(event, internal.interaction);
if (onPointerMissed) onPointerMissed();
}
}
};
};
function pointerMissed(event, objects) {
objects.forEach(object => {
var _r3f$handlers2;
return (_r3f$handlers2 = object.__r3f.handlers) == null ? void 0 : _r3f$handlers2.onPointerMissed == null ? void 0 : _r3f$handlers2.onPointerMissed(event);
});
}
return {
handlePointer
};
}
function createPointerEvents(store) {

@@ -1286,0 +1302,0 @@ const {

@@ -41,2 +41,329 @@ import * as THREE from 'three';

function makeId(event) {
return (event.eventObject || event.object).uuid + '/' + event.index;
}
function removeInteractivity(store, object) {
const {
internal
} = store.getState(); // Removes every trace of an object from the data store
internal.interaction = internal.interaction.filter(o => o !== object);
internal.initialHits = internal.initialHits.filter(o => o !== object);
internal.hovered.forEach((value, key) => {
if (value.eventObject === object || value.object === object) {
internal.hovered.delete(key);
}
});
}
function createEvents(store) {
const temp = new THREE.Vector3();
/** Sets up defaultRaycaster */
function prepareRay(event) {
var _raycaster$computeOff;
const state = store.getState();
const {
raycaster,
mouse,
camera,
size
} = state; // https://github.com/pmndrs/react-three-fiber/pull/782
// Events trigger outside of canvas when moved
const {
offsetX,
offsetY
} = (_raycaster$computeOff = raycaster.computeOffsets == null ? void 0 : raycaster.computeOffsets(event, state)) != null ? _raycaster$computeOff : event;
const {
width,
height
} = size;
mouse.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
raycaster.setFromCamera(mouse, camera);
}
/** Calculates delta */
function calculateDistance(event) {
const {
internal
} = store.getState();
const dx = event.offsetX - internal.initialClick[0];
const dy = event.offsetY - internal.initialClick[1];
return Math.round(Math.sqrt(dx * dx + dy * dy));
}
/** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
function filterPointerEvents(objects) {
return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
var _r3f$handlers;
return (_r3f$handlers = obj.__r3f.handlers) == null ? void 0 : _r3f$handlers['onPointer' + name];
}));
}
function intersect(filter) {
const state = store.getState();
const {
raycaster,
internal
} = state; // Skip event handling when noEvents is set
if (!raycaster.enabled) return [];
const seen = new Set();
const intersections = []; // Allow callers to eliminate event objects
const eventsObjects = filter ? filter(internal.interaction) : internal.interaction; // Intersect known handler objects and filter against duplicates
let intersects = raycaster.intersectObjects(eventsObjects, true).filter(item => {
const id = makeId(item);
if (seen.has(id)) return false;
seen.add(id);
return true;
}); // https://github.com/mrdoob/three.js/issues/16031
// Allow custom userland intersect sort order
if (raycaster.filter) intersects = raycaster.filter(intersects, state);
for (const intersect of intersects) {
let eventObject = intersect.object; // Bubble event up
while (eventObject) {
var _r3f;
const handlers = (_r3f = eventObject.__r3f) == null ? void 0 : _r3f.handlers;
if (handlers) intersections.push({ ...intersect,
eventObject
});
eventObject = eventObject.parent;
}
}
return intersections;
}
/** Creates filtered intersects and returns an array of positive hits */
function patchIntersects(intersections, event) {
const {
internal
} = store.getState(); // If the interaction is captured take that into account, the captured event has to be part of the intersects
if (internal.captured && event.type !== 'click' && event.type !== 'wheel') {
internal.captured.forEach(captured => {
if (!intersections.find(hit => hit.eventObject === captured.eventObject)) intersections.push(captured);
});
}
return intersections;
}
/** Handles intersections by forwarding them to handlers */
function handleIntersects(intersections, event, callback) {
const {
raycaster,
mouse,
camera,
internal
} = store.getState(); // If anything has been found, forward it to the event listeners
if (intersections.length) {
const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
const delta = event.type === 'click' ? calculateDistance(event) : 0;
const releasePointerCapture = id => event.target.releasePointerCapture(id);
const localState = {
stopped: false,
captured: false
};
for (const hit of intersections) {
const setPointerCapture = id => {
// If the hit is going to be captured flag that we're in captured state
if (!localState.captured) {
localState.captured = true; // The captured hit array is reset to collect hits
internal.captured = [];
} // Push hits to the array
if (internal.captured) internal.captured.push(hit) // Call the original event now
;
event.target.setPointerCapture(id);
}; // Add native event props
let extractEventProps = {};
for (let prop in Object.getPrototypeOf(event)) {
extractEventProps[prop] = event[prop];
}
let raycastEvent = { ...hit,
...extractEventProps,
intersections,
stopped: localState.stopped,
delta,
unprojectedPoint,
ray: raycaster.ray,
camera: camera,
// Hijack stopPropagation, which just sets a flag
stopPropagation: () => {
// https://github.com/pmndrs/react-three-fiber/issues/596
// Events are not allowed to stop propagation if the pointer has been captured
const cap = internal.captured;
if (!cap || cap.find(h => h.eventObject.id === hit.eventObject.id)) {
raycastEvent.stopped = localState.stopped = true; // Propagation is stopped, remove all other hover records
// An event handler is only allowed to flush other handlers if it is hovered itself
if (internal.hovered.size && Array.from(internal.hovered.values()).find(i => i.eventObject === hit.eventObject)) {
// Objects cannot flush out higher up objects that have already caught the event
const higher = intersections.slice(0, intersections.indexOf(hit));
cancelPointer([...higher, hit]);
}
}
},
target: { ...event.target,
setPointerCapture,
releasePointerCapture
},
currentTarget: { ...event.currentTarget,
setPointerCapture,
releasePointerCapture
},
sourceEvent: event
}; // Call subscribers
callback(raycastEvent); // Event bubbling may be interrupted by stopPropagation
if (localState.stopped === true) break;
}
}
return intersections;
}
function cancelPointer(hits) {
const {
internal
} = store.getState();
Array.from(internal.hovered.values()).forEach(hoveredObj => {
// When no objects were hit or the the hovered object wasn't found underneath the cursor
// we call onPointerOut and delete the object from the hovered-elements map
if (!hits.length || !hits.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index)) {
const eventObject = hoveredObj.eventObject;
const handlers = eventObject.__r3f.handlers;
internal.hovered.delete(makeId(hoveredObj));
if (handlers) {
// Clear out intersects, they are outdated by now
const data = { ...hoveredObj,
intersections: hits || []
};
handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
}
}
});
}
const handlePointer = name => {
// Deal with cancelation
switch (name) {
case 'onPointerLeave':
case 'onPointerCancel':
return () => cancelPointer([]);
case 'onLostPointerCapture':
return () => (store.getState().internal.captured = undefined, cancelPointer([]));
} // Any other pointer goes here ...
return event => {
const {
onPointerMissed,
internal
} = store.getState();
prepareRay(event); // Get fresh intersects
const isPointerMove = name === 'onPointerMove';
const filter = isPointerMove ? filterPointerEvents : undefined;
const hits = patchIntersects(intersect(filter), event); // Take care of unhover
if (isPointerMove) cancelPointer(hits);
handleIntersects(hits, event, data => {
const eventObject = data.eventObject;
const handlers = eventObject.__r3f.handlers; // Check presence of handlers
if (!handlers) return;
if (isPointerMove) {
// Move event ...
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
// When enter or out is present take care of hover-state
const id = makeId(data);
const hoveredItem = internal.hovered.get(id);
if (!hoveredItem) {
// If the object wasn't previously hovered, book it and call its handler
internal.hovered.set(id, data);
handlers.onPointerOver == null ? void 0 : handlers.onPointerOver(data);
handlers.onPointerEnter == null ? void 0 : handlers.onPointerEnter(data);
} else if (hoveredItem.stopped) {
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
data.stopPropagation();
}
} // Call mouse move
handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
} else {
// All other events ...
const handler = handlers == null ? void 0 : handlers[name];
if (handler) {
// Forward all events back to their respective handlers with the exception of click events,
// which must use the initial target
if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
handler(data);
pointerMissed(event, internal.interaction.filter(object => object !== eventObject));
}
}
}
}); // Save initial coordinates on pointer-down
if (name === 'onPointerDown') {
internal.initialClick = [event.offsetX, event.offsetY];
internal.initialHits = hits.map(hit => hit.eventObject);
} // If a click yields no results, pass it back to the user as a miss
if ((name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick') && !hits.length) {
if (calculateDistance(event) <= 2) {
pointerMissed(event, internal.interaction);
if (onPointerMissed) onPointerMissed();
}
}
};
};
function pointerMissed(event, objects) {
objects.forEach(object => {
var _r3f$handlers2;
return (_r3f$handlers2 = object.__r3f.handlers) == null ? void 0 : _r3f$handlers2.onPointerMissed == null ? void 0 : _r3f$handlers2.onPointerMissed(event);
});
}
return {
handlePointer
};
}
// Type guard to tell a store from a portal

@@ -385,14 +712,16 @@ const isStore = def => def && !!def.getState;

function removeRecursive(array, parent, clone = false) {
if (array) {
// Three uses splice op's internally we may have to shallow-clone the array in order to safely remove items
const target = clone ? [...array] : array;
target.forEach(child => removeChild(parent, child));
}
function removeRecursive(array, parent, dispose = false) {
if (array) [...array].forEach(child => removeChild(parent, child, dispose));
}
function removeChild(parentInstance, child) {
function removeChild(parentInstance, child, dispose) {
if (child) {
if (child.isObject3D) {
parentInstance.remove(child);
var _child$__r3f;
parentInstance.remove(child); // Remove interactivity
if ((_child$__r3f = child.__r3f) != null && _child$__r3f.root) {
removeInteractivity(child.__r3f.root, child);
}
} else {

@@ -409,23 +738,20 @@ child.parent = null;

}
} // Remove interactivity
} // Allow objects to bail out of recursive dispose alltogether by passing dispose={null}
// Never dispose of primitives because their state may be kept outside of React!
// In order for an object to be able to dispose it has to have
// - a dispose method,
// - it cannot be an <instance object={...} />
// - it cannot be a THREE.Scene, because three has broken it's own api
//
// Since disposal is recursive, we can check the optional dispose arg, which will be undefined
// when the reconciler calls it, but then carry our own check recursively
if (child.__r3f.root) {
const rootState = child.__r3f.root.getState();
const shouldDispose = dispose === undefined ? child.dispose !== null && !child.__r3f.instance : dispose; // Remove nested child objects
rootState.internal.interaction = rootState.internal.interaction.filter(x => x !== child);
}
removeRecursive(child.__r3f.objects, child, shouldDispose);
removeRecursive(child.children, child, shouldDispose); // Dispose item whenever the reconciler feels like it
invalidateInstance(parentInstance); // Allow objects to bail out of recursive dispose alltogether by passing dispose={null}
// Never dispose of primitives because their state may be kept outside of React!
if (child.dispose && !child.__r3f.instance) {
const objects = child.__r3f.objects;
unstable_runWithPriority(unstable_IdlePriority, () => {
// Remove nested child objects
removeRecursive(objects, child);
removeRecursive(child.children, child, true); // Dispose item
if (child.dispose && child.type !== 'Scene') child.dispose();
});
if (shouldDispose && child.dispose && child.type !== 'Scene') {
unstable_runWithPriority(unstable_IdlePriority, () => child.dispose());
} // Remove references

@@ -439,2 +765,3 @@

delete child.__r3f;
invalidateInstance(parentInstance);
}

@@ -781,2 +1108,3 @@ }

interaction: [],
hovered: new Map(),
subscribers: [],

@@ -939,314 +1267,2 @@ captured: undefined,

function createEvents(store) {
const hovered = new Map();
const temp = new THREE.Vector3();
/** Sets up defaultRaycaster */
function prepareRay(event) {
var _raycaster$computeOff;
const state = store.getState();
const {
raycaster,
mouse,
camera,
size
} = state; // https://github.com/pmndrs/react-three-fiber/pull/782
// Events trigger outside of canvas when moved
const {
offsetX,
offsetY
} = (_raycaster$computeOff = raycaster.computeOffsets == null ? void 0 : raycaster.computeOffsets(event, state)) != null ? _raycaster$computeOff : event;
const {
width,
height
} = size;
mouse.set(offsetX / width * 2 - 1, -(offsetY / height) * 2 + 1);
raycaster.setFromCamera(mouse, camera);
}
/** Calculates delta */
function calculateDistance(event) {
const {
internal
} = store.getState();
const dx = event.offsetX - internal.initialClick[0];
const dy = event.offsetY - internal.initialClick[1];
return Math.round(Math.sqrt(dx * dx + dy * dy));
}
/** Returns true if an instance has a valid pointer-event registered, this excludes scroll, clicks etc */
function filterPointerEvents(objects) {
return objects.filter(obj => ['Move', 'Over', 'Enter', 'Out', 'Leave'].some(name => {
var _r3f$handlers;
return (_r3f$handlers = obj.__r3f.handlers) == null ? void 0 : _r3f$handlers['onPointer' + name];
}));
}
function makeId(event) {
return (event.eventObject || event.object).uuid + '/' + event.index;
}
function intersect(filter) {
const state = store.getState();
const {
raycaster,
internal
} = state; // Skip event handling when noEvents is set
if (!raycaster.enabled) return [];
const seen = new Set();
const intersections = []; // Allow callers to eliminate event objects
const eventsObjects = filter ? filter(internal.interaction) : internal.interaction; // Intersect known handler objects and filter against duplicates
let intersects = raycaster.intersectObjects(eventsObjects, true).filter(item => {
const id = makeId(item);
if (seen.has(id)) return false;
seen.add(id);
return true;
}); // https://github.com/mrdoob/three.js/issues/16031
// Allow custom userland intersect sort order
if (raycaster.filter) intersects = raycaster.filter(intersects, state);
for (const intersect of intersects) {
let eventObject = intersect.object; // Bubble event up
while (eventObject) {
var _r3f;
const handlers = (_r3f = eventObject.__r3f) == null ? void 0 : _r3f.handlers;
if (handlers) intersections.push({ ...intersect,
eventObject
});
eventObject = eventObject.parent;
}
}
return intersections;
}
/** Creates filtered intersects and returns an array of positive hits */
function patchIntersects(intersections, event) {
const {
internal
} = store.getState(); // If the interaction is captured take that into account, the captured event has to be part of the intersects
if (internal.captured && event.type !== 'click' && event.type !== 'wheel') {
internal.captured.forEach(captured => {
if (!intersections.find(hit => hit.eventObject === captured.eventObject)) intersections.push(captured);
});
}
return intersections;
}
/** Handles intersections by forwarding them to handlers */
function handleIntersects(intersections, event, callback) {
const {
raycaster,
mouse,
camera,
internal
} = store.getState(); // If anything has been found, forward it to the event listeners
if (intersections.length) {
const unprojectedPoint = temp.set(mouse.x, mouse.y, 0).unproject(camera);
const delta = event.type === 'click' ? calculateDistance(event) : 0;
const releasePointerCapture = id => event.target.releasePointerCapture(id);
const localState = {
stopped: false,
captured: false
};
for (const hit of intersections) {
const setPointerCapture = id => {
// If the hit is going to be captured flag that we're in captured state
if (!localState.captured) {
localState.captured = true; // The captured hit array is reset to collect hits
internal.captured = [];
} // Push hits to the array
if (internal.captured) internal.captured.push(hit) // Call the original event now
;
event.target.setPointerCapture(id);
}; // Add native event props
let extractEventProps = {};
for (let prop in Object.getPrototypeOf(event)) {
extractEventProps[prop] = event[prop];
}
let raycastEvent = { ...hit,
...extractEventProps,
intersections,
stopped: localState.stopped,
delta,
unprojectedPoint,
ray: raycaster.ray,
camera: camera,
// Hijack stopPropagation, which just sets a flag
stopPropagation: () => {
// https://github.com/pmndrs/react-three-fiber/issues/596
// Events are not allowed to stop propagation if the pointer has been captured
const cap = internal.captured;
if (!cap || cap.find(h => h.eventObject.id === hit.eventObject.id)) {
raycastEvent.stopped = localState.stopped = true; // Propagation is stopped, remove all other hover records
// An event handler is only allowed to flush other handlers if it is hovered itself
if (hovered.size && Array.from(hovered.values()).find(i => i.eventObject === hit.eventObject)) {
// Objects cannot flush out higher up objects that have already caught the event
const higher = intersections.slice(0, intersections.indexOf(hit));
cancelPointer([...higher, hit]);
}
}
},
target: { ...event.target,
setPointerCapture,
releasePointerCapture
},
currentTarget: { ...event.currentTarget,
setPointerCapture,
releasePointerCapture
},
sourceEvent: event
}; // Call subscribers
callback(raycastEvent); // Event bubbling may be interrupted by stopPropagation
if (localState.stopped === true) break;
}
}
return intersections;
}
function cancelPointer(hits) {
Array.from(hovered.values()).forEach(hoveredObj => {
// When no objects were hit or the the hovered object wasn't found underneath the cursor
// we call onPointerOut and delete the object from the hovered-elements map
if (!hits.length || !hits.find(hit => hit.object === hoveredObj.object && hit.index === hoveredObj.index)) {
const eventObject = hoveredObj.eventObject;
const handlers = eventObject.__r3f.handlers;
hovered.delete(makeId(hoveredObj));
if (handlers) {
// Clear out intersects, they are outdated by now
const data = { ...hoveredObj,
intersections: hits || []
};
handlers.onPointerOut == null ? void 0 : handlers.onPointerOut(data);
handlers.onPointerLeave == null ? void 0 : handlers.onPointerLeave(data);
}
}
});
}
const handlePointer = name => {
// Deal with cancelation
switch (name) {
case 'onPointerLeave':
case 'onPointerCancel':
return () => cancelPointer([]);
case 'onLostPointerCapture':
return () => (store.getState().internal.captured = undefined, cancelPointer([]));
} // Any other pointer goes here ...
return event => {
const {
onPointerMissed,
internal
} = store.getState();
prepareRay(event); // Get fresh intersects
const isPointerMove = name === 'onPointerMove';
const filter = isPointerMove ? filterPointerEvents : undefined;
const hits = patchIntersects(intersect(filter), event); // Take care of unhover
if (isPointerMove) cancelPointer(hits);
handleIntersects(hits, event, data => {
const eventObject = data.eventObject;
const handlers = eventObject.__r3f.handlers; // Check presence of handlers
if (!handlers) return;
if (isPointerMove) {
// Move event ...
if (handlers.onPointerOver || handlers.onPointerEnter || handlers.onPointerOut || handlers.onPointerLeave) {
// When enter or out is present take care of hover-state
const id = makeId(data);
const hoveredItem = hovered.get(id);
if (!hoveredItem) {
// If the object wasn't previously hovered, book it and call its handler
hovered.set(id, data);
handlers.onPointerOver == null ? void 0 : handlers.onPointerOver(data);
handlers.onPointerEnter == null ? void 0 : handlers.onPointerEnter(data);
} else if (hoveredItem.stopped) {
// If the object was previously hovered and stopped, we shouldn't allow other items to proceed
data.stopPropagation();
}
} // Call mouse move
handlers.onPointerMove == null ? void 0 : handlers.onPointerMove(data);
} else {
// All other events ...
const handler = handlers == null ? void 0 : handlers[name];
if (handler) {
// Forward all events back to their respective handlers with the exception of click events,
// which must use the initial target
if (name !== 'onClick' && name !== 'onContextMenu' && name !== 'onDoubleClick' || internal.initialHits.includes(eventObject)) {
handler(data);
pointerMissed(event, internal.interaction.filter(object => object !== eventObject));
}
}
}
}); // Save initial coordinates on pointer-down
if (name === 'onPointerDown') {
internal.initialClick = [event.offsetX, event.offsetY];
internal.initialHits = hits.map(hit => hit.eventObject);
} // If a click yields no results, pass it back to the user as a miss
if ((name === 'onClick' || name === 'onContextMenu' || name === 'onDoubleClick') && !hits.length) {
if (calculateDistance(event) <= 2) {
pointerMissed(event, internal.interaction);
if (onPointerMissed) onPointerMissed();
}
}
};
};
function pointerMissed(event, objects) {
objects.forEach(object => {
var _r3f$handlers2;
return (_r3f$handlers2 = object.__r3f.handlers) == null ? void 0 : _r3f$handlers2.onPointerMissed == null ? void 0 : _r3f$handlers2.onPointerMissed(event);
});
}
return {
handlePointer
};
}
function createPointerEvents(store) {

@@ -1253,0 +1269,0 @@ const {

{
"name": "@react-three/fiber",
"version": "6.0.8",
"version": "6.0.9",
"description": "A React renderer for Threejs",

@@ -5,0 +5,0 @@ "keywords": [

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc