Keep mobile bottom chrome pinned or hidden during keyboard focus transitions, including landscape phone layouts. - track keyboard-focus intent before visual viewport metrics settle - clamp focused iOS viewport offsets so fixed bars remain behind the keyboard - cover runtime bootstrap, landscape integration, nav, focus, and offset behavior - document the mobile keyboard layout contract and add a patch changeset Files changed: .changeset/fn-9201-mobile-nav-keyboard-lift.md | 7 ++ docs/dashboard-guide.md | 4 +- packages/dashboard/app/App.tsx | 43 ++++++++-- ...ile-nav-keyboard-landscape.integration.test.tsx | 91 ++++++++++++++++++++ .../__tests__/icb-inline-script.runtime.test.ts | 74 ++++++++++++++++ .../mobile-bottom-bars-keyboard-layout.test.ts | 15 +++- .../viewport-compensation-keyboard.test.ts | 5 +- .../__tests__/MobileNavBar.keyboard-lift.test.tsx | 65 ++++++++++++++ .../__tests__/useKeyboardFocusPending.test.tsx | 99 ++++++++++++++++++++++ .../dashboard/app/hooks/useKeyboardFocusPending.ts | 43 ++++++++++ packages/dashboard/app/index.html | 12 ++- .../utils/__tests__/mobileBarKeyboardFlags.test.ts | 22 +++++ .../app/utils/__tests__/viewportOffset.test.ts | 32 +++++++ .../dashboard/app/utils/mobileBarKeyboardFlags.ts | 10 ++- packages/dashboard/app/utils/viewportOffset.ts | 8 ++ 15 files changed, 510 insertions(+), 20 deletions(-) Fusion-Task-Id: FN-9201 Fusion-Task-Lineage: 4b3b01d3-8968-432b-9537-a63ee6bbde31 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
52 lines
2.9 KiB
TypeScript
52 lines
2.9 KiB
TypeScript
export interface MobileBarKeyboardFlagsInput {
|
|
isMobile: boolean;
|
|
keyboardOpen: boolean;
|
|
keyboardFocusPending: boolean;
|
|
anyModalOpen: boolean;
|
|
/** True when a fullscreen mobile overlay owns keyboard/viewport layout. */
|
|
overlayOpen: boolean;
|
|
isIOS: boolean;
|
|
}
|
|
|
|
export interface MobileBarKeyboardFlags {
|
|
footerHidden: boolean;
|
|
navKeyboardOpen: boolean;
|
|
footerKeyboardOpen: boolean;
|
|
}
|
|
|
|
/*
|
|
FNXC:MobileChatKeyboardLayout 2026-06-26-09:04:
|
|
While the soft keyboard is up on mobile, the dashboard must NOT show the executor footer (task counts / Running indicator) and must NOT leave dead space above the keyboard. `footerHidden` drives both: it returns the ExecutorStatusBar null AND drops `.project-content`'s reserved footer+nav padding-bottom, letting the composer sit directly above the keyboard.
|
|
|
|
This now applies to BOTH iOS and Android. Previously `footerHidden` was iOS-only (FN-5707): on Android `interactive-widget=resizes-content` shrinks the layout viewport, so the footer's stacked bottom position was technically "correct" — but with the nav bar slid off-screen (`translateY(100%)`) on keyboard-open, its reserved ~80px (footer-height + nav-height) padding rendered as an empty gap between the footer and the keyboard, with the footer still visible. Matching iOS removes both the footer and the gap on Android.
|
|
|
|
FN-5707's original Android concern (stripping nav padding mid-focus could make Android Chrome treat the focused input as moving and dismiss the keyboard) is mitigated because the strip is keyed off `keyboardOpen`, which only flips true AFTER the visualViewport has settled into its keyboard-open size — not during the focus transition.
|
|
|
|
`footerKeyboardOpen` (the footer `bottom: 0` collapse class) stays iOS-only: it is only meaningful when the footer is still rendered (e.g. over a modal), and Android's resizes-content keeps the stacked position correct in that case.
|
|
|
|
Fullscreen mobile overlays (for example Quick Chat's sheet) own their own visual viewport handling. Treat them like modals for board-layout padding so overlay-local keyboards never shift the underlying board.
|
|
*/
|
|
export function computeMobileBarKeyboardFlags({
|
|
isMobile,
|
|
keyboardOpen,
|
|
keyboardFocusPending,
|
|
anyModalOpen,
|
|
overlayOpen,
|
|
isIOS,
|
|
}: MobileBarKeyboardFlagsInput): MobileBarKeyboardFlags {
|
|
/*
|
|
FNXC:MobileChatKeyboardLayout 2026-08-23-18:14:
|
|
The nav bar's off-screen slide must not wait for a settled visualViewport sample: a withheld or late metric previously left it visible and lifted on individual keyboard opens. The padding strip and iOS footer collapse stay settled-gated because FN-5707 constrained them.
|
|
*/
|
|
const boardLayoutSuppressed = anyModalOpen || overlayOpen;
|
|
const footerHidden = isMobile && keyboardOpen && !boardLayoutSuppressed;
|
|
const navKeyboardOpen = isMobile && (keyboardOpen || keyboardFocusPending);
|
|
const footerKeyboardOpen = isMobile && keyboardOpen && isIOS;
|
|
|
|
return {
|
|
footerHidden,
|
|
navKeyboardOpen,
|
|
footerKeyboardOpen,
|
|
};
|
|
}
|