@navikt/ds-react
Version:
React components from the Norwegian Labour and Welfare Administration.
85 lines • 4.71 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.usePointerDownOutside = usePointerDownOutside;
const react_1 = require("react");
const hooks_1 = require("../../../util/hooks");
const dispatchCustomEvent_1 = require("./dispatchCustomEvent");
/**
* Listens for `pointerdown` outside a react subtree. We use `pointerdown` rather than `pointerup`
* to mimic layer dismissing behaviour present in OS.
* 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.
*/
function usePointerDownOutside(callback, ownerDocument = globalThis === null || globalThis === void 0 ? void 0 : globalThis.document) {
const handlePointerDownOutside = (0, hooks_1.useCallbackRef)(callback);
const isPointerInsideReactTreeRef = (0, react_1.useRef)(false);
const handleClickRef = (0, react_1.useRef)(() => { });
(0, react_1.useEffect)(() => {
const handlePointerDown = (event) => {
/**
* The `DismisableLayer`-API is based on the ability to stop events from propagating and in the end calling `onDismiss`
* if `usePointerDownOutside` runs `event.preventDefault()`.
*
* Altrough `pointerdown` is already a cancelable event,
* to to make sure the batching of events works corretly with `focusIn` in `useFocusOutside`,
* we still use a custom event like in `useFocusOutside`.
*
* Since pointer-events are `discrete` events in React: https://github.com/facebook/react/blob/a8a4742f1c54493df00da648a3f9d26e3db9c8b5/packages/react-dom/src/events/ReactDOMEventListener.js#L318
* we need to to use flushSync to ensure that the event is dispatched before the next event is raised.
*/
function dispatchPointerEvent() {
(0, dispatchCustomEvent_1.dispatchCustomEvent)(dispatchCustomEvent_1.CUSTOM_EVENTS.POINTER_DOWN_OUTSIDE, handlePointerDownOutside, { originalEvent: event }, { discrete: true });
}
if (event.target && !isPointerInsideReactTreeRef.current) {
/**
* On touch devices, we delay reactivating pointer-events to account for the browser's delay in executing events after touch ends.
* This also handles cancellations when no click event is raised due to scrolling or long-pressing.
* We continuously remove the previous listener as we can't be sure it was raised and cleaned up.
*/
if (event.pointerType === "touch") {
ownerDocument.removeEventListener("click", handleClickRef.current);
handleClickRef.current = dispatchPointerEvent;
ownerDocument.addEventListener("click", handleClickRef.current, {
once: true,
});
}
else {
dispatchPointerEvent();
}
}
else {
// We need to remove the event listener in case the outside click has been canceled.
ownerDocument.removeEventListener("click", handleClickRef.current);
}
isPointerInsideReactTreeRef.current = false;
};
/**
* If this hook executes in a component that mounts via a `pointerdown` event, the event
* would bubble up to the document and trigger a `pointerDownOutside` 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('pointerdown', () => {
* console.log('I will log');
* document.addEventListener('pointerdown', () => {
* console.log('I will also log');
* })
* });
*/
const timerId = window.setTimeout(() => {
ownerDocument.addEventListener("pointerdown", handlePointerDown);
}, 0);
return () => {
window.clearTimeout(timerId);
ownerDocument.removeEventListener("pointerdown", handlePointerDown);
ownerDocument.removeEventListener("click", handleClickRef.current);
};
}, [ownerDocument, handlePointerDownOutside]);
return {
// ensures we check React component tree (not just DOM tree)
onPointerDownCapture: () => {
isPointerInsideReactTreeRef.current = true;
},
};
}
//# sourceMappingURL=usePointerDownOutside.js.map
;