Mobile browsers scroll the whole page up to show the focused input and do not always scroll it back when the keyboard closes; the app never scrolls the page itself, so the top bar (menu) stayed out of reach. - Viewport interactive-widget=resizes-content: on Android the keyboard shrinks the page instead of shifting it. - Elsewhere (iOS Safari) the page offset is undone when the visual viewport grows again or the input loses focus. - The chat keeps following the latest message when its pane shrinks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
56 lines
1.8 KiB
TypeScript
56 lines
1.8 KiB
TypeScript
import { StrictMode } from "react";
|
||
import { createRoot } from "react-dom/client";
|
||
import { registerSW } from "virtual:pwa-register";
|
||
import "./install";
|
||
import "./styles.css";
|
||
import { App } from "./App";
|
||
import { applyTheme } from "./theme";
|
||
import { captureShare } from "./share";
|
||
|
||
applyTheme();
|
||
captureShare();
|
||
registerSW({ immediate: true });
|
||
undoKeyboardShift();
|
||
|
||
/**
|
||
* Browsers that do not resize the page for the on-screen keyboard (iOS Safari, older
|
||
* Android) scroll the whole page up to show the input – and do not always scroll it back
|
||
* when the keyboard closes, leaving the top bar (menu) out of reach. The app never
|
||
* scrolls the page itself (only its panes), so any page offset is undone once the
|
||
* visible area grows again.
|
||
*/
|
||
function undoKeyboardShift() {
|
||
const vv = window.visualViewport;
|
||
if (!vv) return;
|
||
let last = vv.height;
|
||
const reset = () => {
|
||
const el = document.scrollingElement ?? document.documentElement;
|
||
if (window.scrollY !== 0 || el.scrollTop !== 0 || document.body.scrollTop !== 0) {
|
||
window.scrollTo(0, 0);
|
||
el.scrollTop = 0;
|
||
document.body.scrollTop = 0;
|
||
}
|
||
};
|
||
vv.addEventListener("resize", () => {
|
||
// Grown back (keyboard closed or closing): after the browser's own adjustment.
|
||
if (vv.height > last + 1) {
|
||
requestAnimationFrame(reset);
|
||
setTimeout(reset, 350);
|
||
}
|
||
last = vv.height;
|
||
});
|
||
// Keyboard closed by leaving the input (no resize on some browsers).
|
||
document.addEventListener("focusout", () =>
|
||
setTimeout(() => {
|
||
const a = document.activeElement;
|
||
if (!a || !(a instanceof HTMLInputElement || a instanceof HTMLTextAreaElement || (a as HTMLElement).isContentEditable)) reset();
|
||
}, 300),
|
||
);
|
||
}
|
||
|
||
createRoot(document.getElementById("root")!).render(
|
||
<StrictMode>
|
||
<App />
|
||
</StrictMode>,
|
||
);
|