@ariakit/react-core
Version:
Ariakit React core
137 lines (134 loc) • 4.73 kB
JavaScript
"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
};