fix(dashboard): treat keyboard as closed on blur instead of waiting for vv settle
useMobileKeyboard now requires a focused input for keyboardOpen=true (both the chrome-overlap and iOS-gap paths). The moment an input blurs, the hook reports keyboardOpen=false instead of waiting hundreds of ms for iOS's visualViewport dismissal animation to settle. This makes App-level mobileKeyboardOpen flip false instantly on blur, so MobileNavBar reappears and project-content regains nav-bar padding in the same frame. The ChatView composer (and TodoModal/PlanningModeModal) snap to their post-keyboard layout in one move instead of crawling down with iOS's keyboard slide. Replaces the per-component 450ms suppress hack in ChatView (also dropped in this commit) which couldn't reach the parent layout's nav padding state and produced "below tab bar then snap up" jitter. Adds a regression test and updates four existing tests that assumed "vv shrinks → keyboard up" without focus (focus is now required). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,17 +64,24 @@ function getKeyboardMetrics(): KeyboardMetrics {
|
||||
updateBaselineViewportHeight(vv.height);
|
||||
}
|
||||
|
||||
// Android/Chrome style overlap.
|
||||
// Android/Chrome style overlap. Only treat as open while an input is
|
||||
// actually focused — without this, the (often slow) visualViewport
|
||||
// dismissal animation keeps reporting overlap > 0 for hundreds of ms
|
||||
// after the user has tapped Done, which leaves App-level layout (mobile
|
||||
// nav bar visibility, project-content padding) stuck in keyboard-up
|
||||
// mode and makes downstream components (ChatView) jump on settle.
|
||||
const chromeOverlap = Math.max(0, window.innerHeight - vv.offsetTop - vv.height);
|
||||
if (chromeOverlap > 0) {
|
||||
if (chromeOverlap > 0 && focused) {
|
||||
return { overlap: chromeOverlap, open: true, vvHeight: vv.height, vvOffsetTop: offsetTop };
|
||||
}
|
||||
|
||||
// iOS fallback (window.innerHeight shrinks with keyboard).
|
||||
// iOS fallback (window.innerHeight shrinks with keyboard). Same focused
|
||||
// requirement as above — the dismissal animation otherwise leaves the
|
||||
// gap > the open-threshold for the duration of the slide.
|
||||
const baselineHeight = getBaselineViewportHeight();
|
||||
const gap = Math.max(0, baselineHeight - vv.offsetTop - vv.height);
|
||||
|
||||
if (gap >= IOS_FALLBACK_MIN_GAP_PX) {
|
||||
if (gap >= IOS_FALLBACK_MIN_GAP_PX && focused) {
|
||||
return { overlap: gap, open: true, vvHeight: vv.height, vvOffsetTop: offsetTop };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user