requestFullscreen() on the voice container puts only its descendants in the browser's top layer; overlays portaled to document.body were rendered outside that layer and stayed invisible — most visibly the right-click context menu on stream tiles and voice user panels. Add usePortalContainer() hook returning document.fullscreenElement ?? document.body and re-rendering on fullscreenchange. Migrate every overlay reachable during a call: ContextMenuRenderer (desktop, submenu, mobile sheet), Tooltip, ConfirmDialog, ConnectionInfoPopover, ScreenShareSettingsPopover, and ScreenSharePicker (which previously rendered inline at App root).
32 lines
1.4 KiB
TypeScript
32 lines
1.4 KiB
TypeScript
import { useSyncExternalStore } from 'react';
|
|
|
|
// When an element enters browser fullscreen via the Fullscreen API, the user
|
|
// agent renders only that element and its descendants. Anything portaled to
|
|
// `document.body` (the conventional overlay target) is rendered outside the
|
|
// fullscreen layer and is therefore invisible while fullscreen is active.
|
|
//
|
|
// Overlays that need to remain visible across fullscreen transitions (context
|
|
// menus, tooltips, popovers, modals, the screen-share picker) must portal into
|
|
// `document.fullscreenElement` while it is non-null and into `document.body`
|
|
// otherwise. This hook provides a reactive container that reflects the current
|
|
// fullscreen state and re-renders subscribers on every `fullscreenchange`.
|
|
|
|
function subscribe(onChange: () => void): () => void {
|
|
document.addEventListener('fullscreenchange', onChange);
|
|
return () => document.removeEventListener('fullscreenchange', onChange);
|
|
}
|
|
|
|
function getSnapshot(): Element {
|
|
return document.fullscreenElement ?? document.body;
|
|
}
|
|
|
|
function getServerSnapshot(): Element {
|
|
// Vite SPA — never invoked, but useSyncExternalStore requires a server snapshot
|
|
// for type completeness. Return body so SSR-style renders never crash.
|
|
return document.body;
|
|
}
|
|
|
|
export function usePortalContainer(): Element {
|
|
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
}
|