clone

a deno framework for local-first web apps that open instantly, work offline and sync when users choose

feat/prf-provision-custody
lofi / package / recipes / window-controls-overlay.ts
window-controls-overlay.ts 3.1K
1/**2 * Opt-in geometry observation for desktop Window Controls Overlay layouts.3 *4 * @module5 */67/** A value-only copy of the titlebar area available to application content. */8export type WindowControlsOverlayGeometry = {9 readonly visible: boolean;10 readonly titlebarArea: {11 readonly x: number;12 readonly y: number;13 readonly width: number;14 readonly height: number;15 };16};1718/** Minimal browser surface consumed by this recipe. */19export type WindowControlsOverlayClient = {20 readonly visible: boolean;21 getTitlebarAreaRect(): Pick<DOMRectReadOnly, "x" | "y" | "width" | "height">;22 addEventListener(type: "geometrychange", listener: EventListener): void;23 removeEventListener(type: "geometrychange", listener: EventListener): void;24};2526/** Disposable result from installing the geometry observer. */27export type WindowControlsOverlayObserver = {28 readonly supported: boolean;29 dispose(): void;30};3132/** Options for observing overlay visibility and geometry changes. */33export type ObserveWindowControlsOverlayOptions = {34 /** Called initially and whenever browser geometry changes. */35 onGeometry(geometry: WindowControlsOverlayGeometry): void;36 /** Override the browser API for tests. */37 client?: WindowControlsOverlayClient;38};3940function browserClient(): WindowControlsOverlayClient | undefined {41 if (typeof navigator === "undefined") return undefined;42 const candidate = navigator as Navigator & {43 windowControlsOverlay?: WindowControlsOverlayClient;44 };45 const client = candidate.windowControlsOverlay;46 return client && typeof client.getTitlebarAreaRect === "function" &&47 typeof client.addEventListener === "function" &&48 typeof client.removeEventListener === "function"49 ? client50 : undefined;51}5253function finiteNonNegative(value: number): number {54 return Number.isFinite(value) && value >= 0 ? value : 0;55}5657function readGeometry(client: WindowControlsOverlayClient): WindowControlsOverlayGeometry {58 const rect = client.getTitlebarAreaRect();59 return {60 visible: client.visible === true,61 titlebarArea: {62 x: finiteNonNegative(rect.x),63 y: finiteNonNegative(rect.y),64 width: finiteNonNegative(rect.width),65 height: finiteNonNegative(rect.height),66 },67 };68}6970/**71 * Observe the experimental Window Controls Overlay geometry surface.72 *73 * CSS `titlebar-area-*` environment variables should own collision-free74 * layout. This observer is for state that genuinely needs geometry changes.75 * Unsupported browsers retain the ordinary standalone header and receive no76 * callback.77 */78export function observeWindowControlsOverlay(79 options: ObserveWindowControlsOverlayOptions,80): WindowControlsOverlayObserver {81 const client = options.client ?? browserClient();82 if (!client) return { supported: false, dispose() {} };83 let active = true;84 const update = () => {85 if (active) options.onGeometry(readGeometry(client));86 };87 client.addEventListener("geometrychange", update);88 update();89 return {90 supported: true,91 dispose() {92 if (!active) return;93 active = false;94 client.removeEventListener("geometrychange", update);95 },96 };97}