notes(ipad): keep dialog above the on-screen keyboard
Task #476. On iPad Safari, opening the keyboard while writing a new note or replying inside the note thread dialog covered the bottom of the dialog (textarea + send button). The dialog used `top:50%; translate(-50%,-50%)` against the layout viewport and never reacted to the visual viewport shrinking when the keyboard appeared. Changes: - artifacts/tx-os/index.html: added `interactive-widget=resizes-content` to the viewport meta so iOS 17+ resizes the layout viewport when the software keyboard opens. - artifacts/tx-os/src/hooks/use-visual-viewport.ts: new hook that subscribes to `window.visualViewport` `resize` / `scroll` / `orientationchange` (rAF-throttled) and exposes the visible height, the visual viewport `offsetTop`, and the bottom keyboard inset. - artifacts/tx-os/src/components/ui/dialog.tsx: `DialogContent` now reads the hook and, only when the keyboard inset exceeds ~80px (i.e. an actual on-screen keyboard, not browser-chrome jitter), pins its center to `offsetTop + height/2` and caps `max-height` to the visible height. With the dialog's existing `translate(-50%,-50%)`, both top and bottom edges stay inside the visible region above the keyboard. Desktop renders unchanged (inset is 0 → no inline style applied beyond what the caller passes). Deviation from plan: - Dropped the per-textarea `scrollIntoView` on focus (steps 4 of the plan). Reviewer flagged it as defensive/AI-shaped and a desktop regression risk; with the dialog now correctly capped to the visible viewport, the pinned-bottom reply / composer textareas are already fully on-screen, so the extra scroll isn't needed. Verification: - `tsc --noEmit` clean. - `notes-thread-dialog` e2e still passes.
This commit is contained in:
@@ -32,11 +32,19 @@ const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, style, ...props }, ref) => {
|
||||
const { keyboardInset, height } = useVisualViewport()
|
||||
const { keyboardInset, height, offsetTop } = useVisualViewport()
|
||||
// Only react when an on-screen keyboard is actually present. iOS Safari
|
||||
// and Android Chrome both report a non-trivial bottom inset (>= ~150px)
|
||||
// when their software keyboard opens; tiny insets from browser chrome
|
||||
// changes are ignored so desktop and normal mobile scrolling stay
|
||||
// untouched.
|
||||
const keyboardOpen = keyboardInset > 80
|
||||
const dynamicStyle: React.CSSProperties = keyboardOpen
|
||||
? {
|
||||
top: `${height / 2}px`,
|
||||
// Center the dialog inside the *visible* visual viewport (above
|
||||
// the keyboard) rather than the layout viewport. offsetTop
|
||||
// accounts for the page being scrolled or pinch-zoomed.
|
||||
top: `${offsetTop + height / 2}px`,
|
||||
maxHeight: `${height - 16}px`,
|
||||
...style,
|
||||
}
|
||||
|
||||
@@ -2410,16 +2410,6 @@ function ThreadDialog({
|
||||
ref={replyInputRef}
|
||||
value={replyText}
|
||||
onChange={(e) => setReplyText(e.target.value)}
|
||||
onFocus={(e) => {
|
||||
const el = e.currentTarget;
|
||||
setTimeout(() => {
|
||||
try {
|
||||
el.scrollIntoView({ block: "center", behavior: "smooth" });
|
||||
} catch {
|
||||
el.scrollIntoView();
|
||||
}
|
||||
}, 250);
|
||||
}}
|
||||
placeholder={t("notes.replyPlaceholder", "Write a reply…")}
|
||||
className="min-h-[70px] resize-none"
|
||||
data-testid="thread-reply-input"
|
||||
@@ -3024,16 +3014,6 @@ function Composer({
|
||||
<Textarea
|
||||
value={content}
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
onFocus={(e) => {
|
||||
const el = e.currentTarget;
|
||||
setTimeout(() => {
|
||||
try {
|
||||
el.scrollIntoView({ block: "center", behavior: "smooth" });
|
||||
} catch {
|
||||
el.scrollIntoView();
|
||||
}
|
||||
}, 250);
|
||||
}}
|
||||
placeholder={t("notes.contentPlaceholder", "Take a note...")}
|
||||
className="border-0 bg-transparent shadow-none focus-visible:ring-0 px-1 mt-1 min-h-[80px] resize-none"
|
||||
data-testid="notes-composer-content"
|
||||
|
||||
Reference in New Issue
Block a user