@navikt/ds-react
Version:
React components from the Norwegian Labour and Welfare Administration.
74 lines • 3.63 kB
JavaScript
import { useEffect, useRef } from "react";
import { useEventCallback } from "../../../utils/hooks/index.js";
import { CUSTOM_EVENTS, dispatchCustomEvent, } from "./dispatchCustomEvent.js";
/**
* Listens for `pointerup` outside a react subtree.
* Returns props to pass to the node we want to check for outside events.
* By checking `isPointerInsideReactTreeRef` we can determine if the event happened outside the subtree of the node, saving some element-comparisons.
*/
export function usePointerUpOutside(callback, ownerDocument = globalThis === null || globalThis === void 0 ? void 0 : globalThis.document, enabled = true) {
// Keep callback ref stable
const handlePointerUpOutside = useEventCallback(callback);
// Tracks if the pointer interaction started inside the React subtree.
const isPointerInsideReactTreeRef = useRef(false);
useEffect(() => {
if (!enabled) {
return;
}
const handlePointerUp = (event) => {
/**
* The `DismisableLayer`-API is based on the ability to stop events from propagating and in the end calling `onDismiss`
* if `usePointerUpOutside`-callback does not run `event.preventDefault()`.
*
* Although `pointerup` is already a cancelable event, we still dispatch a custom event
* to keep parity with focus outside handling and ensure ordering.
*/
if (event.target && !isPointerInsideReactTreeRef.current) {
dispatchCustomEvent(CUSTOM_EVENTS.POINTER_UP_OUTSIDE, handlePointerUpOutside, { originalEvent: event });
}
/* Reset for next interaction. */
isPointerInsideReactTreeRef.current = false;
};
/* Mostly relevant if user moved touch after touch-start */
const handlePointerCancel = () => {
/* Reset state if interaction is cancelled */
isPointerInsideReactTreeRef.current = false;
};
/**
* If this hook executes in a component that mounts via a `pointerup` event, the event
* would bubble up to the document and trigger a `pointerUpOutside` event. We avoid
* this by delaying the event listener registration on the document.
* This is not React specific, but rather how the DOM works, ie:
* ```
* button.addEventListener('pointerup', () => {
* console.log('I will log');
* document.addEventListener('pointerup', () => {
* console.log('I will also log');
* })
* });
*/
const timerId = window.setTimeout(() => {
ownerDocument.addEventListener("pointerup", handlePointerUp);
ownerDocument.addEventListener("pointercancel", handlePointerCancel);
}, 0);
return () => {
window.clearTimeout(timerId);
ownerDocument.removeEventListener("pointerup", handlePointerUp);
ownerDocument.removeEventListener("pointercancel", handlePointerCancel);
};
}, [ownerDocument, handlePointerUpOutside, enabled]);
/**
* Ensures we check React component tree (not just DOM tree).
* This makes sure that if you start or end a pointer interaction inside the
* React tree (e.g. Modal), we don't trigger the outside event on pointer up.
*/
return {
onPointerDownCapture: () => {
isPointerInsideReactTreeRef.current = true;
},
onPointerUpCapture: () => {
isPointerInsideReactTreeRef.current = true;
},
};
}
//# sourceMappingURL=usePointerUpOutside.js.map