fix: chat UI fixes
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Tracks the *visual* viewport — the part of the page not covered by the
|
||||
* software keyboard.
|
||||
*
|
||||
* Why this exists: on iOS Safari `100dvh` does not shrink when the keyboard
|
||||
* opens. The dynamic viewport units respond to browser chrome collapsing, not
|
||||
* to the keyboard, so a composer pinned to the bottom of a `100dvh` column
|
||||
* ends up underneath it. `window.visualViewport` is the only thing iOS updates
|
||||
* reliably, so the thread is sized from that instead while the keyboard is up.
|
||||
*
|
||||
* `offsetTop` matters as much as `height`: iOS scrolls the layout viewport to
|
||||
* reveal the focused field rather than resizing it, so a panel pinned to the
|
||||
* visible area has to follow that offset or it drifts off-screen.
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export type VisualViewport = {
|
||||
/** Visible height in px, excluding the keyboard. */
|
||||
height: number;
|
||||
/** How far the layout viewport has scrolled to reveal the focused field. */
|
||||
offsetTop: number;
|
||||
/** True once the visible area is materially shorter than the window. */
|
||||
keyboardOpen: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* A URL bar collapsing costs ~50–60px; a keyboard costs several hundred. The
|
||||
* threshold keeps ordinary chrome changes from being read as a keyboard.
|
||||
*/
|
||||
const KEYBOARD_THRESHOLD_PX = 120;
|
||||
|
||||
export function useVisualViewport(): VisualViewport | null {
|
||||
const [state, setState] = useState<VisualViewport | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const vv = window.visualViewport;
|
||||
// Absent on older browsers — callers fall back to their CSS height.
|
||||
if (!vv) return;
|
||||
|
||||
const apply = () => {
|
||||
setState({
|
||||
height: vv.height,
|
||||
offsetTop: vv.offsetTop,
|
||||
keyboardOpen: window.innerHeight - vv.height > KEYBOARD_THRESHOLD_PX,
|
||||
});
|
||||
};
|
||||
|
||||
apply();
|
||||
vv.addEventListener("resize", apply);
|
||||
// iOS fires scroll (not resize) as the layout viewport shifts under the
|
||||
// keyboard, so both are needed to stay pinned.
|
||||
vv.addEventListener("scroll", apply);
|
||||
return () => {
|
||||
vv.removeEventListener("resize", apply);
|
||||
vv.removeEventListener("scroll", apply);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return state;
|
||||
}
|
||||
Reference in New Issue
Block a user