Closes #250: kill confirmation/force-kill (dialogs/KillDialog), the ? shortcuts overlay (dialogs/HelpOverlay), the general-purpose right-click menu (common/ContextMenu, closes on scroll/resize unlike the sidebar's own), the mobile drawer with swipe support (layout/Drawer), and the visual-viewport hook (hooks/useViewport). Each is a standalone, tested port of its vanilla-DOM source; wiring into the shell is a later issue.
52 lines
1.6 KiB
TypeScript
52 lines
1.6 KiB
TypeScript
// 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);
|
|
};
|
|
}, []);
|
|
}
|