UNPKG

@ariakit/react-core

Version:

Ariakit React core

137 lines (134 loc) 4.73 kB
"use client"; import { usePreviousMouseDownRef } from "./R3NHSIAL.js"; import { isElementMarked } from "./3NDVDEB4.js"; import { useStoreState } from "./SOQQIDO4.js"; import { useEvent, useSafeLayoutEffect } from "./W2TDKEPX.js"; // src/dialog/utils/use-hide-on-interact-outside.ts import { contains, getDocument, getWindow } from "@ariakit/core/utils/dom"; import { addGlobalEventListener } from "@ariakit/core/utils/events"; import { useEffect, useRef } from "react"; function isInDocument(target) { if (target.tagName === "HTML") return true; return contains(getDocument(target).body, target); } function isDisclosure(disclosure, target) { if (!disclosure) return false; if (contains(disclosure, target)) return true; const activeId = target.getAttribute("aria-activedescendant"); if (activeId) { const activeElement = getDocument(disclosure).getElementById(activeId); if (activeElement) { return contains(disclosure, activeElement); } } return false; } function isMouseEventOnDialog(event, dialog) { if (!("clientY" in event)) return false; const rect = dialog.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) return false; return rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width; } function useEventOutside({ store, type, listener, capture, domReady }) { const callListener = useEvent(listener); const open = useStoreState(store, "open"); const contentElement = useStoreState(store, "contentElement"); const focusedRef = useRef(false); useSafeLayoutEffect(() => { if (!open) return; if (!domReady) return; if (!contentElement) return; const onFocus = () => { focusedRef.current = true; }; contentElement.addEventListener("focusin", onFocus, true); return () => contentElement.removeEventListener("focusin", onFocus, true); }, [open, domReady, contentElement]); useEffect(() => { if (!open) return; const onEvent = (event) => { const { contentElement: contentElement2, disclosureElement } = store.getState(); const target = event.target; if (!contentElement2) return; if (!target) return; if (!isInDocument(target)) return; if (contains(contentElement2, target)) return; if (isDisclosure(disclosureElement, target)) return; if (target.hasAttribute("data-focus-trap")) return; if (isMouseEventOnDialog(event, contentElement2)) return; const focused = focusedRef.current; if (focused && !isElementMarked(target, contentElement2.id)) return; callListener(event); }; const win = contentElement ? getWindow(contentElement) : void 0; return addGlobalEventListener(type, onEvent, capture, win); }, [open, capture, store, type, callListener, contentElement]); } function shouldHideOnInteractOutside(hideOnInteractOutside, event) { if (typeof hideOnInteractOutside === "function") { return hideOnInteractOutside(event); } return !!hideOnInteractOutside; } function useHideOnInteractOutside(store, hideOnInteractOutside, domReady, interactedOutsideRef) { const open = useStoreState(store, "open"); const contentElement = useStoreState(store, "contentElement"); const contentWindow = contentElement ? getWindow(contentElement) : void 0; const previousMouseDownRef = usePreviousMouseDownRef(open, contentWindow); const props = { store, domReady, capture: true }; useEventOutside({ ...props, type: "click", listener: (event) => { const { contentElement: contentElement2 } = store.getState(); const previousMouseDown = previousMouseDownRef.current; if (!previousMouseDown) return; if (!isElementMarked(previousMouseDown, contentElement2 == null ? void 0 : contentElement2.id)) return; if (!shouldHideOnInteractOutside(hideOnInteractOutside, event)) return; if (interactedOutsideRef) { interactedOutsideRef.current = true; } store.hide(); } }); useEventOutside({ ...props, type: "focusin", listener: (event) => { const { contentElement: contentElement2 } = store.getState(); if (!contentElement2) return; if (event.target === getDocument(contentElement2)) return; if (!shouldHideOnInteractOutside(hideOnInteractOutside, event)) return; store.hide(); } }); useEventOutside({ ...props, type: "contextmenu", listener: (event) => { if (!shouldHideOnInteractOutside(hideOnInteractOutside, event)) return; if (interactedOutsideRef) { interactedOutsideRef.current = true; } store.hide(); } }); } export { useHideOnInteractOutside };