| 13 | * Uses the VisualViewport API so we don't rely on position:fixed (which breaks on iOS). |
| 14 | */ |
| 15 | export function useViewportHeight(): ViewportHeightState { |
| 16 | const [state, setState] = useState<ViewportHeightState>({ |
| 17 | viewportHeight: typeof window !== "undefined" ? window.innerHeight : 0, |
| 18 | keyboardHeight: 0, |
| 19 | isKeyboardOpen: false, |
| 20 | }); |
| 21 | |
| 22 | useEffect(() => { |
| 23 | const vv = window.visualViewport; |
| 24 | |
| 25 | const update = () => { |
| 26 | const fullHeight = window.innerHeight; |
| 27 | const visibleHeight = vv ? vv.height : fullHeight; |
| 28 | const keyboardHeight = Math.max(0, fullHeight - visibleHeight); |
| 29 | setState({ |
| 30 | viewportHeight: visibleHeight, |
| 31 | keyboardHeight, |
| 32 | isKeyboardOpen: keyboardHeight > 50, |
| 33 | }); |
| 34 | }; |
| 35 | |
| 36 | update(); |
| 37 | |
| 38 | if (vv) { |
| 39 | vv.addEventListener("resize", update); |
| 40 | vv.addEventListener("scroll", update); |
| 41 | return () => { |
| 42 | vv.removeEventListener("resize", update); |
| 43 | vv.removeEventListener("scroll", update); |
| 44 | }; |
| 45 | } else { |
| 46 | window.addEventListener("resize", update); |
| 47 | return () => window.removeEventListener("resize", update); |
| 48 | } |
| 49 | }, []); |
| 50 | |
| 51 | return state; |
| 52 | } |