import { useEffect, type RefObject } from "react"; /** While `active`, close (call `onClose`) on Escape or a mousedown outside ALL of the given * element(s). The reusable half of the popover/dropdown pattern that several components each * hand-rolled; positioning stays with the caller (it varies per popover). Pass the panel ref * (and optionally a trigger ref so clicking the trigger doesn't immediately re-close). */ export function useDismiss( active: boolean, onClose: () => void, refs: RefObject | RefObject[], ): void { useEffect(() => { if (!active) return; const list = Array.isArray(refs) ? refs : [refs]; const onDown = (e: MouseEvent) => { const target = e.target as Node; if (!list.some((r) => r.current?.contains(target))) onClose(); }; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") { // Stop the same Escape from also reaching a player's window-level listener underneath — // this document listener bubbles first, so one Escape closes only this popover, not the // popover AND the player behind it. (U-C quick-fix.) // // KNOWN LIMITATION (accepted, → R8 popover-platform): because this is a blanket // stopPropagation, a PlexPlayer settings/tracks menu open DURING an auto-skip countdown // swallows the Escape that would otherwise ALSO cancel the skip — Escape closes the menu, // and the skip proceeds. Properly fixing this needs the shared layer-registry R8 builds // (topmost-only dispatch), not a broad stopPropagation. e.stopPropagation(); onClose(); } }; document.addEventListener("mousedown", onDown); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDown); document.removeEventListener("keydown", onKey); }; // Matches the original effects: (re)subscribe only when open/closed toggles. Refs are // stable and onClose is read fresh from this run's closure. // eslint-disable-next-line react-hooks/exhaustive-deps }, [active]); }