Files
fusion/packages/dashboard/app/utils/viewportOffset.ts
gsxdsm 129e17d16c FN-5702: keep mobile footer and nav pinned when keyboard opens
Add viewport-offset handling so mobile bottom bars stay anchored while virtual keyboards resize the visual viewport.

- add a shared viewportOffset utility to compute keyboard-related visual viewport compensation
- update dashboard app bootstrap/index logic to apply compensation variables for pinned footer/nav layout
- extend mobile keyboard layout coverage with dedicated viewport compensation and bottom-bar tests
- document the keyboard/pinned-bar behavior update in dashboard guide

Files changed:
 docs/dashboard-guide.md                            |  2 +-
 .../mobile-bottom-bars-keyboard-layout.test.ts     | 12 ++-
 .../viewport-compensation-keyboard.test.ts         | 24 ++++++
 packages/dashboard/app/index.html                  | 39 +++++++++-
 .../app/utils/__tests__/viewportOffset.test.ts     | 86 ++++++++++++++++++++++
 packages/dashboard/app/utils/viewportOffset.ts     | 64 ++++++++++++++++
 6 files changed, 223 insertions(+), 4 deletions(-)

Fusion-Task-Id: FN-5702

Fusion-Task-Lineage: be47ad0c-82df-4c38-9577-94c2eee49a0e
2026-05-29 20:05:14 -07:00

65 lines
1.5 KiB
TypeScript

const NON_TEXT_INPUT_TYPES = new Set([
"checkbox",
"radio",
"button",
"submit",
"reset",
"file",
"range",
"color",
"hidden",
]);
export interface ComputeIcbOffsetsInput {
innerWidth: number;
innerHeight: number;
vvWidth: number;
vvHeight: number;
vvOffsetTop: number;
vvOffsetLeft: number;
vvScale: number;
activeElementIsKeyboardFocusable: boolean;
baselineViewportHeight: number | null;
}
export interface IcbOffsets {
rightOffset: number;
bottomOffset: number;
}
export function isKeyboardFocusableInputType(type: string | null | undefined): boolean {
if (!type) return true;
return !NON_TEXT_INPUT_TYPES.has(type.toLowerCase());
}
export function computeIcbOffsets(input: ComputeIcbOffsetsInput): IcbOffsets {
const {
innerWidth,
innerHeight,
vvWidth,
vvHeight,
vvOffsetTop,
vvOffsetLeft,
vvScale,
activeElementIsKeyboardFocusable,
baselineViewportHeight,
} = input;
const rightOffset = Math.max(0, innerWidth - vvOffsetLeft - vvWidth);
const rawBottomOffset = Math.max(0, innerHeight - vvOffsetTop - vvHeight);
if (!activeElementIsKeyboardFocusable || vvScale > 1.01 || baselineViewportHeight == null) {
return { rightOffset, bottomOffset: rawBottomOffset };
}
const keyboardShrink = Math.max(0, baselineViewportHeight - vvHeight);
if (keyboardShrink <= 0) {
return { rightOffset, bottomOffset: rawBottomOffset };
}
return {
rightOffset,
bottomOffset: Math.max(0, rawBottomOffset - keyboardShrink),
};
}