Review found two low-severity items: - toast: armDismiss now always clears the prior timer (and arms only when duration is truthy), so a coalesced repeat that upgrades a toast to requiresInteraction no longer lets the original timer fire and dismiss it. Was a pre-existing gap the C-3.25 change hadn't closed. - overlay: documented the known limitation of useDismiss's blanket stopPropagation — a PlexPlayer menu open during an auto-skip countdown swallows the Escape that would also cancel the skip. Proper fix needs R8's shared layer-registry (topmost-only dispatch), not a broad stopPropagation; accepted until then.
45 lines
2.1 KiB
TypeScript
45 lines
2.1 KiB
TypeScript
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<HTMLElement | null> | RefObject<HTMLElement | null>[],
|
|
): 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]);
|
|
}
|