feat(popover): fullscreen-aware portal + migrate the hover surfaces (R8 S4a)
The platform's FloatingPortal targeted <body>, which is painted UNDERNEATH whatever holds the fullscreen lock. That is why the player's in-bar menus were either non-portaled (and clipped) or, for add-to-playlist / download, disabled outright in fullscreen. PopoverSurface now portals into `document.fullscreenElement` while one exists, via a new reactive `useFullscreenElement()` on the existing WebKit-aware fullscreen lib. Two floating-ui details this had to work around: a null `root` means "the container does not exist yet" (no portal node is created at all), so the no-fullscreen case must pass undefined; and the portal node is built once and ignores a later root change, so the portal is keyed on the fullscreen state to remount on a transition. Migrated the three hand-rolled hover engines onto the platform: - Tooltip: kept as a passive caption via a new `passthrough` prop on PopoverSurface, so it cannot swallow a click aimed at a control it overlaps during the close delay. Gains aria-describedby wiring it never had. - AboutCell: the 160 ms hide-timer hover bridge is now safePolygon; the close-on-page- scroll is now hide-and-follow, so the card reappears when the row scrolls back. - TagManager: same bridge removal; the scroll-fade moves to an inner scroller so the mask rides the scrolling box rather than the glass panel. Verified in-browser: tooltip flip + centering + pointer-transparency, About card flip- above with the measured height, tag popover right-start anchoring, and the portal round-trip body -> fullscreen element -> body on a live fullscreenchange.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { useSyncExternalStore } from "react";
|
||||
|
||||
// The browser's Fullscreen API, WebKit included.
|
||||
//
|
||||
// Safari and iPadOS still ship only the prefixed spelling: no `fullscreenchange`, no
|
||||
@@ -47,3 +49,16 @@ export function onFullscreenChange(handler: () => void): () => void {
|
||||
document.removeEventListener("webkitfullscreenchange", handler);
|
||||
};
|
||||
}
|
||||
|
||||
// `fullscreenElement()` returns the live DOM node, so the same element is the same reference across
|
||||
// calls — a valid useSyncExternalStore snapshot without any caching of our own.
|
||||
const fsSnapshot = () => fullscreenElement() as HTMLElement | null;
|
||||
const fsServerSnapshot = () => null;
|
||||
|
||||
/** The fullscreen element as reactive state, for code that must RENDER differently while something
|
||||
* holds the lock — chiefly the popover platform, which has to portal INTO that element (a node
|
||||
* portaled to <body> is painted underneath the fullscreen element, i.e. invisible). Components that
|
||||
* merely react to the transition should keep using `onFullscreenChange` directly. */
|
||||
export function useFullscreenElement(): HTMLElement | null {
|
||||
return useSyncExternalStore(onFullscreenChange, fsSnapshot, fsServerSnapshot);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user