ci / rust (push) Successful in 5m29s
Focus alone was the wrong signal: dismissing the soft keyboard (e.g. Android back button) keeps the terminal focused, so the key bar lingered. Detect the keyboard's actual visibility from the visual-viewport shrink — while an editable element is focused, a height past a threshold below the focus-free baseline means the keyboard is up — and gate the bar on a new `html.kb-open` class in addition to the per-terminal focus class. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
68 lines
2.9 KiB
TypeScript
68 lines
2.9 KiB
TypeScript
/**
|
|
* Bind the app shell's height *and position* to the *visual* viewport.
|
|
*
|
|
* On mobile the layout viewport does not shrink when the on-screen keyboard
|
|
* opens — only the visual viewport does. A shell sized at `height: 100%` (or
|
|
* `100dvh`) therefore extends behind the keyboard, hiding anything pinned to the
|
|
* bottom: the attach view's special-key bar (NAP-342). Mirroring
|
|
* `visualViewport.height` onto a CSS custom property keeps the shell exactly as
|
|
* tall as the visible area, so the key bar lands just above the keyboard.
|
|
*
|
|
* Height alone isn't enough: when the keyboard opens the browser also scrolls
|
|
* the layout viewport to reveal the focused element, which drags the shell
|
|
* (anchored at layout-top) up and out of view — the key bar ends up at the top
|
|
* of the screen and the user has to scroll back down. `visualViewport.offsetTop`
|
|
* is how far the layout viewport has scrolled under the keyboard; translating
|
|
* the shell down by that amount re-pins it to the visible region. (Chrome's
|
|
* `interactive-widget=resizes-content`, set in the viewport meta, prevents the
|
|
* scroll entirely on Android; this covers iOS and older browsers that ignore
|
|
* it, where offsetTop stays non-zero.)
|
|
*
|
|
* No-ops where `visualViewport` is unavailable; the CSS falls back to `100%`.
|
|
*
|
|
* It also toggles an `html.kb-open` class that reflects whether the soft
|
|
* keyboard is actually up, used to gate the mobile key bar. Focus is the wrong
|
|
* signal: dismissing the keyboard (e.g. the Android back button) keeps the
|
|
* terminal focused, so the bar would linger. Instead we watch the visual
|
|
* viewport shrink: while an editable element is focused, a height noticeably
|
|
* below the tallest height seen with nothing focused means the keyboard is open.
|
|
* The baseline resets whenever nothing is being edited, so it self-corrects
|
|
* across orientation changes and URL-bar collapse.
|
|
*/
|
|
const KB_OPEN_THRESHOLD = 150;
|
|
|
|
export function installViewportSizing(): void {
|
|
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();
|
|
}
|