UNPKG

@navikt/ds-react

Version:

React components from the Norwegian Labour and Welfare Administration.

85 lines 4.71 kB
"use strict"; 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