// React port of ../../viewport.ts. Binds the app shell's height and position // to the *visual* viewport rather than the layout one, so a pinned bottom bar // (the attach view's special-key bar, NAP-342) lands above an open on-screen // keyboard instead of behind it. See viewport.ts for the full mechanism. import { useEffect } from "react"; const KB_OPEN_THRESHOLD = 150; export function useViewport(): void { useEffect(() => { const vv = window.visualViewport; if (!vv) return; const root = document.documentElement; let raf = 0; let baseline = vv.height; const isEditing = () => { const a = document.activeElement as HTMLElement | null; if (!a) return false; return a.tagName === "TEXTAREA" || a.tagName === "INPUT" || a.isContentEditable; }; const sync = () => { raf = 0; root.style.setProperty("--app-vh", `${Math.round(vv.height)}px`); root.style.setProperty("--app-top", `${Math.round(vv.offsetTop)}px`); // Nothing focused ⇒ keyboard can't be open ⇒ this height is the baseline. const editing = isEditing(); if (!editing) baseline = vv.height; const kbOpen = editing && baseline - vv.height > KB_OPEN_THRESHOLD; root.classList.toggle("kb-open", kbOpen); }; const schedule = () => { if (raf) return; raf = requestAnimationFrame(sync); }; vv.addEventListener("resize", schedule); vv.addEventListener("scroll", schedule); sync(); return () => { vv.removeEventListener("resize", schedule); vv.removeEventListener("scroll", schedule); if (raf) cancelAnimationFrame(raf); }; }, []); }