| 1 | import { useSyncExternalStore } from 'react'; |
| 2 | |
| 3 | /** |
| 4 | * Firefox on Android keeps its address bar above the page, and on a phone held |
| 5 | * sideways that bar is a sixth of the height the whole table has to work with. |
| 6 | * The Fullscreen API is the only thing that takes it back, and browsers grant |
| 7 | * it only from inside a click — so this is always a button somebody presses, |
| 8 | * never something the table asks for on its own. |
| 9 | * |
| 10 | * Installing the page to the home screen does the same thing for good; see |
| 11 | * public/manifest.webmanifest. |
| 12 | */ |
| 13 | |
| 14 | const subscribe = (cb: () => void) => { |
| 15 | document.addEventListener('fullscreenchange', cb); |
| 16 | return () => document.removeEventListener('fullscreenchange', cb); |
| 17 | }; |
| 18 | |
| 19 | /** Absent on iPhone, where only a <video> may go fullscreen. */ |
| 20 | export const fullscreenSupported = () => |
| 21 | typeof document !== 'undefined' && |
| 22 | document.fullscreenEnabled && |
| 23 | typeof document.documentElement.requestFullscreen === 'function'; |
| 24 | |
| 25 | export function enterFullscreen() { |
| 26 | // `navigationUI` is a hint at the browser's own chrome; the ones that don't |
| 27 | // take it ignore it. Refusals — no gesture behind the call, or a frame that |
| 28 | // disallows it — are nothing to report: the page is simply not fullscreen. |
| 29 | document.documentElement.requestFullscreen({ navigationUI: 'hide' }).catch(() => {}); |
| 30 | } |
| 31 | |
| 32 | export function exitFullscreen() { |
| 33 | if (document.fullscreenElement) document.exitFullscreen().catch(() => {}); |
| 34 | } |
| 35 | |
| 36 | /** Whether the page holds the screen right now. */ |
| 37 | export function useFullscreen(): boolean { |
| 38 | return useSyncExternalStore( |
| 39 | subscribe, |
| 40 | () => document.fullscreenElement !== null, |
| 41 | () => false, |
| 42 | ); |
| 43 | } |