338d80cf0a
Root causes the prior attempts missed: 1) Safari iOS/iPadOS ignores `interactive-widget=resizes-content`, so `100dvh` never shrinks when the soft keyboard opens. Every previous override that used `dvh` to size the dialog was sized to the full screen and ran behind the keyboard. 2) The CSS rule from #622 (`[role="dialog"]:has(:focus)`) was unconditional. On desktop, focusing any input inside a Radix DialogContent instantly stretched it to width:100vw + top:0, which collided with Radix's open-state slide-animation transforms and rendered the dialog as a broken narrow strip pinned in the top-left corner — visible in Tahani's laptop screenshot when opening "Add Meeting". 3) `AdminFormDialog` puts `role="dialog"` on a NON-fixed inner card, so the rule's `top/left/right` had no effect there anyway; the card stayed centred in a layout-viewport-sized wrapper which iOS does not shrink. Fix: - New `useVisualViewportVars` hook mounted in NotificationsSocketBridge. Writes `--vv-height` and `--vv-offset-top` on `<html>` from `window.visualViewport`, updated on resize / scroll / orientationchange. Falls back to `innerHeight`/0 if visualViewport is absent. - Rewrote the index.css dialog rule: * Gated inside `@media (any-pointer: coarse)` so desktop is completely unaffected. * Uses `var(--vv-height, 100vh)` and `var(--vv-offset-top, 0px)` instead of `100dvh` / `0`, so dialogs actually track the visible band on iOS. - New `.dialog-vv-wrapper` helper class that applies the same vv-sized top/height to bespoke full-screen overlay wrappers. - Applied `.dialog-vv-wrapper` to AdminFormDialog and the admin ConfirmDialog wrappers; switched their `fixed inset-0` to `fixed inset-x-0 top-0 h-screen` and their inner cards from `max-h-[90vh]`/`max-h-[85vh]` to `max-h-full` so the card shrinks with the wrapper. Kept `useScrollFocusedDialogField` unchanged — works correctly once the dialog itself is sized to the visible band. `tsc --noEmit` clean.
57 lines
2.0 KiB
TypeScript
57 lines
2.0 KiB
TypeScript
import { useEffect } from "react";
|
|
|
|
/**
|
|
* Publishes the live visual-viewport geometry as CSS custom properties
|
|
* on `<html>` so stylesheets can size dialogs/sheets correctly above
|
|
* the iOS soft keyboard.
|
|
*
|
|
* Why a JS hook (not CSS `dvh`):
|
|
* - Safari iOS/iPadOS ignores the `interactive-widget=resizes-content`
|
|
* viewport hint, so `100dvh` does NOT shrink when the soft keyboard
|
|
* opens — it stays equal to the full screen. Any layout pinned with
|
|
* `dvh` therefore sits behind the keyboard.
|
|
* - `window.visualViewport` IS updated on iOS (including standalone
|
|
* PWAs) whenever the keyboard, page-pinch zoom, or browser chrome
|
|
* resizes the visible area. It's the only reliable signal.
|
|
*
|
|
* The hook writes two vars on `document.documentElement`:
|
|
* --vv-height : current visualViewport.height in `px`
|
|
* --vv-offset-top : current visualViewport.offsetTop in `px`
|
|
* On browsers without `visualViewport` it falls back to `window.innerHeight`
|
|
* and `0`, which matches the pre-existing `100vh` behaviour.
|
|
*
|
|
* Mount once at the app root (e.g. inside `NotificationsSocketBridge`).
|
|
*/
|
|
export function useVisualViewportVars(): void {
|
|
useEffect(() => {
|
|
if (typeof window === "undefined") return;
|
|
const root = document.documentElement;
|
|
const vv = window.visualViewport ?? null;
|
|
|
|
const apply = () => {
|
|
const h = vv ? vv.height : window.innerHeight;
|
|
const top = vv ? vv.offsetTop : 0;
|
|
root.style.setProperty("--vv-height", `${Math.round(h)}px`);
|
|
root.style.setProperty("--vv-offset-top", `${Math.round(top)}px`);
|
|
};
|
|
|
|
apply();
|
|
|
|
if (vv) {
|
|
vv.addEventListener("resize", apply);
|
|
vv.addEventListener("scroll", apply);
|
|
}
|
|
window.addEventListener("resize", apply);
|
|
window.addEventListener("orientationchange", apply);
|
|
|
|
return () => {
|
|
if (vv) {
|
|
vv.removeEventListener("resize", apply);
|
|
vv.removeEventListener("scroll", apply);
|
|
}
|
|
window.removeEventListener("resize", apply);
|
|
window.removeEventListener("orientationchange", apply);
|
|
};
|
|
}, []);
|
|
}
|