Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/MobilePwaLayout.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ test("tracks the visual viewport while the software keyboard is open", () => {
assert.match(appShellSource, /height: "var\(--app-viewport-height, 100dvh\)"/);
assert.match(appShellSource, /right: "env\(safe-area-inset-right\)"/);
assert.match(viewportHookSource, /window\.visualViewport/);
assert.match(viewportHookSource, /window\.requestAnimationFrame\(update\)/);
assert.match(viewportHookSource, /window\.addEventListener\("resize", scheduleUpdate\)/);
assert.match(viewportHookSource, /window\.addEventListener\("focusout", scheduleUpdate\)/);
assert.match(viewportHookSource, /--app-viewport-height/);
assert.match(viewportHookSource, /window\.scrollTo\(0, 0\)/);
assert.match(cssSource, /height: var\(--app-viewport-height, 100dvh\)/);
Expand Down
11 changes: 10 additions & 1 deletion hooks/useViewportHeight.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,16 @@ test("uses the visual viewport for a focused editor when the keyboard shrinks it
}), true);
});

test("keeps the dynamic viewport height when no editor is focused", () => {
test("does not keep the keyboard height after the visual viewport restores", () => {
assert.equal(shouldUseVisualViewportHeight({
hasFocusedEditable: true,
innerHeight: 844,
viewportHeight: 844,
viewportScale: 1,
}), false);
});

test("restores the dynamic height as soon as the editor loses focus", () => {
assert.equal(shouldUseVisualViewportHeight({
hasFocusedEditable: false,
innerHeight: 844,
Expand Down
39 changes: 31 additions & 8 deletions hooks/useViewportHeight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,10 @@ export function useViewportHeight(): void {
if (!viewport) return;

const root = document.documentElement;
let frameId: number | null = null;

const update = () => {
frameId = null;
const keyboardOpen = shouldUseVisualViewportHeight({
hasFocusedEditable: hasFocusedEditableElement(),
innerHeight: window.innerHeight,
Expand All @@ -50,21 +52,42 @@ export function useViewportHeight(): void {
});
if (keyboardOpen) {
root.style.setProperty("--app-viewport-height", `${viewport.height}px`);
if (window.scrollX !== 0 || window.scrollY !== 0) {
window.scrollTo(0, 0);
}
} else {
root.style.removeProperty("--app-viewport-height");
}

const pageWasShifted = window.scrollX !== 0 || window.scrollY !== 0;
const isUnscaled = Math.abs(viewport.scale - 1) < 0.01;
if (pageWasShifted && isUnscaled) {
window.scrollTo(0, 0);
}
};

// WebKit can dispatch the resize event before visualViewport.height has
// settled, especially when an installed PWA dismisses the keyboard. Reading
// it on the next animation frame prevents the keyboard-height CSS value
// from remaining after the keyboard has closed.
const scheduleUpdate = () => {
if (frameId !== null) window.cancelAnimationFrame(frameId);
frameId = window.requestAnimationFrame(update);
};

update();
viewport.addEventListener("resize", update);
viewport.addEventListener("scroll", update);
scheduleUpdate();
viewport.addEventListener("resize", scheduleUpdate);
viewport.addEventListener("scroll", scheduleUpdate);
window.addEventListener("resize", scheduleUpdate);
window.addEventListener("focusin", scheduleUpdate);
window.addEventListener("focusout", scheduleUpdate);
window.addEventListener("pageshow", scheduleUpdate);

return () => {
viewport.removeEventListener("resize", update);
viewport.removeEventListener("scroll", update);
viewport.removeEventListener("resize", scheduleUpdate);
viewport.removeEventListener("scroll", scheduleUpdate);
window.removeEventListener("resize", scheduleUpdate);
window.removeEventListener("focusin", scheduleUpdate);
window.removeEventListener("focusout", scheduleUpdate);
window.removeEventListener("pageshow", scheduleUpdate);
if (frameId !== null) window.cancelAnimationFrame(frameId);
root.style.removeProperty("--app-viewport-height");
};
}, []);
Expand Down