"use client" import * as React from "react" /** * Close a non-modal popover when the next press lands anywhere else, even if * that press opens a modal overlay. * * Radix already dismisses a non-modal layer on an outside press, but it skips * that check for any layer sitting below one that disables outside pointer * events. A modal `DropdownMenu` opens inside React's own pointerdown handling, * which runs before Radix's `document` listener, so by the time the popover asks * whether it should dismiss, the menu is already above it and the answer is no. * The popover stays on screen, and because the modal layer has set * `pointer-events: none` on the body it is visible but dead: two editors open, * one of them inert. In the filter rail that is every column menu, every row * kebab, the properties rail, and Add filter. * * Listening in the **capture** phase moves the decision earlier than React's * handler, so the press is judged against the layers that exist when the user * presses rather than the ones that exist a tick later. * * The trigger is exempt: pressing it while open is a request to toggle, and * dismissing here would race Radix's own toggle and leave the popover stuck * open. Presses inside `content` are obviously not outside it. * * @param enabled Pass the popover's open state; the listener is only attached * while it is open. * @param content The popover content node, usually from a callback ref. * @param trigger The node that opens it. * @param onDismiss Called with no arguments on an outside press. */ export function useDismissBeforeOverlay( enabled: boolean, content: HTMLElement | null, trigger: HTMLElement | null, onDismiss: () => void, ) { // Kept in a ref so a fresh `onDismiss` each render does not detach and // reattach the listener, and written in an effect so render stays pure. const dismiss = React.useRef(onDismiss) React.useEffect(() => { dismiss.current = onDismiss }, [onDismiss]) React.useEffect(() => { if (!enabled) return const doc = content?.ownerDocument ?? document function onPointerDown(event: PointerEvent) { const target = event.target if (!(target instanceof Node)) return if (content?.contains(target)) return if (trigger?.contains(target)) return dismiss.current() } doc.addEventListener("pointerdown", onPointerDown, true) return () => doc.removeEventListener("pointerdown", onPointerDown, true) }, [enabled, content, trigger]) }