FN-7281: fix folded terminal viewport baselines
Keep mobile terminals sized against the settled folded viewport before keyboard overlap is applied. - Re-baseline terminal keyboard metrics when folded devices settle to a new closed-posture width. - Avoid replacing the baseline from focused keyboard-open samples that would erase real keyboard overlap. - Cover TerminalModal and SessionTerminal mobile keyboard spacing behavior with regression tests. - Document the folded-phone terminal spacing fix and add a patch changeset. Files changed: .changeset/fn-7281-mobile-terminal-spacing.md | 7 ++ .../mobile-terminal-folded-viewport-baseline.md | 56 +++++++++++++++ .../dashboard/app/components/TerminalModal.tsx | 57 ++++++++++++++-- .../__tests__/SessionTerminal.mobile.test.tsx | 39 +++++++++++ .../components/__tests__/TerminalModal.test.tsx | 79 ++++++++++++++++++++++ .../app/hooks/__tests__/useMobileKeyboard.test.ts | 53 ++++++++++++++- packages/dashboard/app/hooks/useMobileKeyboard.ts | 31 +++++++-- 7 files changed, 309 insertions(+), 13 deletions(-) Fusion-Task-Id: FN-7281 Fusion-Task-Lineage: 90ab7398-c583-4312-8cf6-0ef7d6328c8d Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7281-mobile-terminal-spacing.md
Normal file
7
.changeset/fn-7281-mobile-terminal-spacing.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Fix mobile terminal spacing after folded viewport changes.
|
||||
category: fix
|
||||
dev: Re-baselines terminal keyboard viewport metrics when foldable devices settle to a narrower posture.
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
title: "Mobile terminal folded viewport baseline"
|
||||
date: 2026-06-30
|
||||
category: ui-bugs
|
||||
module: packages/dashboard/app/components/TerminalModal.tsx
|
||||
problem_type: ui_bug
|
||||
component: frontend_terminal
|
||||
applies_when: "A foldable or narrow mobile viewport settles to a closed posture before or during soft-keyboard entry for an xterm surface."
|
||||
symptoms:
|
||||
- "Terminal commands render with excessive inter-character spacing or premature wrapping in folded mobile posture"
|
||||
- "Keyboard-open terminal height/overlap is computed from an earlier unfolded viewport"
|
||||
- "Embedded CLI session terminal input bar is lifted too far after a fold/narrow transition"
|
||||
root_cause: stale_unfolded_visualviewport_baseline
|
||||
resolution_type: code_fix
|
||||
severity: medium
|
||||
related_components:
|
||||
- packages/dashboard/app/components/TerminalModal.tsx
|
||||
- packages/dashboard/app/hooks/useMobileKeyboard.ts
|
||||
- packages/dashboard/app/components/SessionTerminal.tsx
|
||||
- FN-7281
|
||||
tags:
|
||||
- terminal
|
||||
- xterm
|
||||
- mobile-keyboard
|
||||
- visualviewport
|
||||
- foldable
|
||||
- ios
|
||||
---
|
||||
|
||||
# Mobile terminal folded viewport baseline
|
||||
|
||||
## Problem
|
||||
|
||||
The terminal has two mobile xterm surfaces: the PTY `TerminalModal` and the embedded `SessionTerminal`. Both depend on visualViewport-derived keyboard metrics before fitting xterm rows/cols. On iOS-style browsers, `innerHeight` can shrink with the keyboard, so the code keeps a baseline viewport height captured while the keyboard is closed.
|
||||
|
||||
A foldable device can first expose an unfolded/wide closed baseline, then settle to a narrower folded baseline before the keyboard opens. If the folded closed sample is shorter than the previous baseline and the baseline only ever grows, the later keyboard-open sample overestimates the overlap. Conversely, a fold/orientation width sample can arrive after xterm's helper textarea is focused and the soft keyboard is already open; if that focused sample replaces the baseline, the keyboard-open height looks closed and clears the terminal CSS variables. Both stale geometries make the terminal fit against the wrong box and can surface as premature wrapping or spaced ASCII such as `p n p m b u i l d`.
|
||||
|
||||
## Solution
|
||||
|
||||
Treat a keyboard-closed width/posture change as a new baseline, not as keyboard overlap.
|
||||
|
||||
- Track the viewport width alongside the baseline height.
|
||||
- Preserve the max-observed baseline behavior for same-posture recovery from keyboard-open first samples.
|
||||
- When width changes and the viewport height is a settled folded value, replace the baseline before computing iOS fallback overlap.
|
||||
- Gate that replacement to keyboard-closed samples; if a keyboard-focusable element is active, keep the previous baseline so a focused keyboard-open folded sample cannot zero out the overlap.
|
||||
- Keep xterm's measured font family symbols-free; the fix is viewport measurement, not a letter-spacing or cell-width workaround.
|
||||
|
||||
## Regression coverage
|
||||
|
||||
Guard the invariant at three seams:
|
||||
|
||||
- `TerminalModal.test.tsx` simulates unfolded closed → folded closed → folded keyboard-open and asserts `--keyboard-overlap` / `--vv-height` use the folded baseline. It also covers a focused folded keyboard-open sample so posture re-baselining cannot clear those CSS variables.
|
||||
- `SessionTerminal.mobile.test.tsx` proves the embedded mobile input bar uses the folded baseline instead of the stale unfolded height.
|
||||
- `useMobileKeyboard.test.ts` covers the shared hook so future consumers inherit the posture-aware baseline behavior.
|
||||
|
||||
Existing terminal tests continue to cover symbols-free xterm font stacks, glyph fallback for Nerd Font/powerline output, duplicate visualViewport resize coalescing, keyboard close clearing, undefined visualViewport, tab-switch scrollback replay, and desktop/tablet terminal modes.
|
||||
@@ -182,6 +182,8 @@ const TERMINAL_KEY_LABELS = {
|
||||
pxUnit: "px",
|
||||
} as const;
|
||||
|
||||
const SETTLED_FOLDED_VIEWPORT_MIN_HEIGHT_PX = 480;
|
||||
|
||||
export function ctrlChar(key: string): string {
|
||||
if (!key) {
|
||||
return "";
|
||||
@@ -310,6 +312,16 @@ function isMacPlatform(): boolean {
|
||||
return /mac/i.test(platform) || /mac/i.test(userAgent);
|
||||
}
|
||||
|
||||
function isKeyboardFocusableElement(el: Element | null): boolean {
|
||||
if (!el) return false;
|
||||
if (el instanceof HTMLTextAreaElement) return true;
|
||||
if (el instanceof HTMLInputElement) {
|
||||
const nonTextTypes = new Set(["checkbox", "radio", "button", "submit", "reset", "file", "range", "color", "hidden"]);
|
||||
return !nonTextTypes.has(el.type);
|
||||
}
|
||||
return el instanceof HTMLElement && el.isContentEditable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute how many CSS pixels the virtual keyboard covers from the bottom
|
||||
* of the layout viewport. Returns 0 on desktop or when visualViewport is
|
||||
@@ -324,35 +336,70 @@ function isMacPlatform(): boolean {
|
||||
function getKeyboardOverlap(): number {
|
||||
if (typeof window === "undefined" || !window.visualViewport) return 0;
|
||||
const vv = window.visualViewport;
|
||||
const viewportWidth = vv.width > 0 ? vv.width : window.innerWidth;
|
||||
const viewportHeight = Math.max(window.innerHeight, vv.height);
|
||||
const chromeOverlap = Math.max(0, window.innerHeight - vv.offsetTop - vv.height);
|
||||
if (chromeOverlap > 0) return chromeOverlap;
|
||||
|
||||
/*
|
||||
FNXC:Terminal 2026-06-30-08:48:
|
||||
Folded phones can report an unfolded iOS fallback baseline first, then settle to a narrower closed-posture viewport before the keyboard opens. If that closed sample does not replace the old baseline, the terminal overestimates --keyboard-overlap, fits against a too-short/wrong-width box, and commands like `pnpm build` wrap into spaced glyphs. Re-baseline on settled width/posture changes before computing the iOS gap; do not touch xterm's symbols-free font stack.
|
||||
|
||||
FNXC:Terminal 2026-06-30-09:38:
|
||||
A later folded-posture width sample can arrive while xterm's helper textarea is focused and the soft keyboard is already open. Never re-baseline from that focused keyboard-open sample, because it makes the keyboard height look like the closed viewport and clears --keyboard-overlap/--vv-height before the final fit.
|
||||
*/
|
||||
if (!isKeyboardFocusableElement(document.activeElement) && hasSettledViewportPostureChange(viewportWidth, viewportHeight)) {
|
||||
setInitialViewportBaseline(viewportHeight, viewportWidth);
|
||||
}
|
||||
|
||||
// On iOS Safari, window.innerHeight shrinks to match visualViewport.
|
||||
// Detect keyboard by checking if visual viewport is shorter than initial
|
||||
// height by more than 80px (with a 30px noise filter).
|
||||
const initialHeight = getInitialViewportHeight();
|
||||
const initialHeight = getInitialViewportHeight(viewportWidth, viewportHeight);
|
||||
const gap = initialHeight - vv.offsetTop - vv.height;
|
||||
// Minimum 30px gap required to filter noise (address bar, toolbar changes).
|
||||
// Threshold of 80px: only consider keyboard present when gap exceeds this.
|
||||
return gap >= 30 && gap > 80 ? gap : 0;
|
||||
if (gap >= 30 && gap > 80) {
|
||||
return gap;
|
||||
}
|
||||
|
||||
setInitialViewportBaseline(viewportHeight, viewportWidth);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** Cached initial viewport height before any keyboard opened. */
|
||||
let _initialViewportHeight: number | null = null;
|
||||
let _initialViewportWidth: number | null = null;
|
||||
|
||||
function setInitialViewportBaseline(height: number, width: number): void {
|
||||
_initialViewportHeight = height;
|
||||
_initialViewportWidth = width;
|
||||
}
|
||||
|
||||
function hasSettledViewportPostureChange(width: number, height: number): boolean {
|
||||
return (
|
||||
_initialViewportHeight !== null &&
|
||||
_initialViewportWidth !== null &&
|
||||
Math.abs(width - _initialViewportWidth) >= 1 &&
|
||||
height >= SETTLED_FOLDED_VIEWPORT_MIN_HEIGHT_PX
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the viewport height at page load (before any keyboard opens).
|
||||
* Cached after first read.
|
||||
*/
|
||||
function getInitialViewportHeight(): number {
|
||||
function getInitialViewportHeight(width: number, height: number): number {
|
||||
if (_initialViewportHeight === null) {
|
||||
_initialViewportHeight = window.innerHeight;
|
||||
setInitialViewportBaseline(height, width);
|
||||
}
|
||||
return _initialViewportHeight;
|
||||
return _initialViewportHeight ?? height;
|
||||
}
|
||||
|
||||
/** Reset the cached initial viewport height. Exported for tests only. */
|
||||
export function _resetInitialViewportHeight(): void {
|
||||
_initialViewportHeight = null;
|
||||
_initialViewportWidth = null;
|
||||
}
|
||||
|
||||
interface TerminalModalProps {
|
||||
|
||||
@@ -469,6 +469,45 @@ describe("SessionTerminal (mobile) — keyboard-open behavior", () => {
|
||||
input.remove();
|
||||
});
|
||||
|
||||
it("re-baselines folded iOS viewport before lifting the mobile input bar", async () => {
|
||||
const { listeners, mockVV } = installVisualViewport({ innerHeight: 844, vvHeight: 844 });
|
||||
Object.defineProperty(window, "innerWidth", { value: 700, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "width", { value: 700, writable: true, configurable: true });
|
||||
|
||||
render(<SessionTerminal sessionId="s1" />);
|
||||
await waitFor(() => expect(FakeWS.instances.length).toBe(1));
|
||||
|
||||
// Fold/narrow the device while the keyboard is still closed; this must
|
||||
// replace the prior unfolded baseline before a focused input opens.
|
||||
Object.defineProperty(window, "innerWidth", { value: 375, writable: true, configurable: true });
|
||||
Object.defineProperty(window, "innerHeight", { value: 667, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "width", { value: 375, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "height", { value: 667, writable: true, configurable: true });
|
||||
|
||||
act(() => {
|
||||
for (const cb of listeners.resize) cb();
|
||||
});
|
||||
|
||||
const input = document.createElement("textarea");
|
||||
document.body.appendChild(input);
|
||||
input.focus();
|
||||
|
||||
Object.defineProperty(window, "innerHeight", { value: 300, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "height", { value: 300, writable: true, configurable: true });
|
||||
|
||||
act(() => {
|
||||
for (const cb of listeners.resize) cb();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const bar = screen.getByTestId("cli-terminal-mobile-bar");
|
||||
expect(bar.className).toContain("cli-session-terminal__mobile-bar--keyboard-open");
|
||||
expect(bar.style.bottom).toBe("367px");
|
||||
});
|
||||
|
||||
input.remove();
|
||||
});
|
||||
|
||||
it("pinch-zoom (vv.scale > 1) is NOT treated as keyboard-open", async () => {
|
||||
installVisualViewport({ innerHeight: 800, vvHeight: 600, scale: 2 });
|
||||
const input = document.createElement("textarea");
|
||||
|
||||
@@ -4774,6 +4774,85 @@ describe("TerminalModal — FN-872 real-device keyboard overlap refinement", ()
|
||||
});
|
||||
});
|
||||
|
||||
it("re-baselines iOS keyboard overlap after folded posture settles before input", async () => {
|
||||
const { listeners, mockVV } = simulateIOSSafari(false, 844);
|
||||
Object.defineProperty(mockVV, "width", { value: 700, writable: true, configurable: true });
|
||||
Object.defineProperty(window, "innerWidth", { value: 700, writable: true, configurable: true });
|
||||
|
||||
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("terminal-modal").style.getPropertyValue("--keyboard-overlap")).toBe("");
|
||||
});
|
||||
|
||||
// Device is folded/narrow while the keyboard is still closed; this settled
|
||||
// viewport must replace the previous unfolded baseline before input opens.
|
||||
Object.defineProperty(window, "innerWidth", { value: 375, writable: true, configurable: true });
|
||||
Object.defineProperty(window, "innerHeight", { value: 667, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "width", { value: 375, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "height", { value: 667, writable: true, configurable: true });
|
||||
|
||||
act(() => {
|
||||
for (const cb of listeners.resize) cb();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("terminal-modal").style.getPropertyValue("--keyboard-overlap")).toBe("");
|
||||
});
|
||||
|
||||
Object.defineProperty(window, "innerHeight", { value: 300, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "height", { value: 300, writable: true, configurable: true });
|
||||
|
||||
act(() => {
|
||||
for (const cb of listeners.resize) cb();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const modal = screen.getByTestId("terminal-modal");
|
||||
expect(modal.style.getPropertyValue("--keyboard-overlap")).toBe("367px");
|
||||
expect(modal.style.getPropertyValue("--vv-height")).toBe("300px");
|
||||
});
|
||||
});
|
||||
|
||||
it("does not re-baseline folded viewport width changes from focused keyboard-open samples", async () => {
|
||||
const { listeners, mockVV } = simulateIOSSafari(false, 844);
|
||||
Object.defineProperty(mockVV, "width", { value: 700, writable: true, configurable: true });
|
||||
Object.defineProperty(window, "innerWidth", { value: 700, writable: true, configurable: true });
|
||||
|
||||
render(<TerminalModal isOpen={true} onClose={mockOnClose} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("terminal-modal").style.getPropertyValue("--keyboard-overlap")).toBe("");
|
||||
});
|
||||
|
||||
const helperTextarea = document.createElement("textarea");
|
||||
document.body.appendChild(helperTextarea);
|
||||
helperTextarea.focus();
|
||||
|
||||
try {
|
||||
// Fold/orientation can deliver the first narrow width sample while the
|
||||
// soft keyboard is already open. With iOS-style innerHeight==vv.height,
|
||||
// re-baselining from this focused sample would make gap=0 and clear the
|
||||
// terminal keyboard CSS vars that drive the final xterm fit.
|
||||
Object.defineProperty(window, "innerWidth", { value: 375, writable: true, configurable: true });
|
||||
Object.defineProperty(window, "innerHeight", { value: 520, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "width", { value: 375, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "height", { value: 520, writable: true, configurable: true });
|
||||
|
||||
act(() => {
|
||||
for (const cb of listeners.resize) cb();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
const modal = screen.getByTestId("terminal-modal");
|
||||
expect(modal.style.getPropertyValue("--keyboard-overlap")).toBe("324px");
|
||||
expect(modal.style.getPropertyValue("--vv-height")).toBe("520px");
|
||||
});
|
||||
} finally {
|
||||
helperTextarea.remove();
|
||||
}
|
||||
});
|
||||
|
||||
it("does not set --vv-height when no keyboard overlap", async () => {
|
||||
const { listeners } = simulateChromeAndroid(0);
|
||||
|
||||
|
||||
@@ -51,10 +51,12 @@ describe("useMobileKeyboard", () => {
|
||||
innerHeight,
|
||||
vvHeight,
|
||||
vvOffsetTop = 0,
|
||||
width = 375,
|
||||
}: {
|
||||
innerHeight: number;
|
||||
vvHeight: number;
|
||||
vvOffsetTop?: number;
|
||||
width?: number;
|
||||
}) {
|
||||
(window as any).ontouchstart = null;
|
||||
Object.defineProperty(navigator, "maxTouchPoints", {
|
||||
@@ -62,7 +64,7 @@ describe("useMobileKeyboard", () => {
|
||||
configurable: true,
|
||||
});
|
||||
Object.defineProperty(window, "innerWidth", {
|
||||
value: 375,
|
||||
value: width,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
@@ -78,7 +80,7 @@ describe("useMobileKeyboard", () => {
|
||||
};
|
||||
|
||||
const mockVV = {
|
||||
width: 375,
|
||||
width,
|
||||
height: vvHeight,
|
||||
offsetTop: vvOffsetTop,
|
||||
offsetLeft: 0,
|
||||
@@ -233,6 +235,53 @@ describe("useMobileKeyboard", () => {
|
||||
input.remove();
|
||||
});
|
||||
|
||||
it("re-baselines iOS fallback after a folded viewport settles while the keyboard is closed", async () => {
|
||||
const { listeners, mockVV } = setupMobileVisualViewport({
|
||||
innerHeight: 844,
|
||||
vvHeight: 844,
|
||||
width: 700,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useMobileKeyboard());
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.keyboardOverlap).toBe(0);
|
||||
expect(result.current.viewportHeight).toBeNull();
|
||||
});
|
||||
|
||||
Object.defineProperty(window, "innerWidth", { value: 375, writable: true, configurable: true });
|
||||
Object.defineProperty(window, "innerHeight", { value: 667, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "width", { value: 375, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "height", { value: 667, writable: true, configurable: true });
|
||||
|
||||
act(() => {
|
||||
for (const cb of listeners.resize) cb();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.keyboardOverlap).toBe(0);
|
||||
expect(result.current.viewportHeight).toBeNull();
|
||||
});
|
||||
|
||||
const input = document.createElement("textarea");
|
||||
document.body.appendChild(input);
|
||||
input.focus();
|
||||
|
||||
Object.defineProperty(window, "innerHeight", { value: 300, writable: true, configurable: true });
|
||||
Object.defineProperty(mockVV, "height", { value: 300, writable: true, configurable: true });
|
||||
|
||||
act(() => {
|
||||
for (const cb of listeners.resize) cb();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.keyboardOverlap).toBe(367);
|
||||
expect(result.current.viewportHeight).toBe(300);
|
||||
});
|
||||
|
||||
input.remove();
|
||||
});
|
||||
|
||||
it("reports moderate iOS fallback overlap below 80px", async () => {
|
||||
const { listeners, mockVV } = setupMobileVisualViewport({
|
||||
innerHeight: 844,
|
||||
|
||||
@@ -4,6 +4,7 @@ const IOS_FALLBACK_MIN_GAP_PX = 30;
|
||||
const IOS_FALLBACK_MIN_FOCUSED_GAP_PX = 16;
|
||||
const IOS_VIEWPORT_SHRINK_MIN_PX = 16;
|
||||
const IMPOSSIBLE_VIEWPORT_EPSILON_PX = 2;
|
||||
const SETTLED_FOLDED_VIEWPORT_MIN_HEIGHT_PX = 480;
|
||||
|
||||
/** Whether the current device is likely mobile (touch-primary, small viewport). */
|
||||
function isMobileDevice(): boolean {
|
||||
@@ -19,23 +20,41 @@ function isMobileDevice(): boolean {
|
||||
* Kept as max-observed value to recover if first sample was keyboard-open.
|
||||
*/
|
||||
let _baselineViewportHeight: number | null = null;
|
||||
let _baselineViewportWidth: number | null = null;
|
||||
|
||||
function getCurrentViewportWidth(): number {
|
||||
return window.visualViewport?.width && window.visualViewport.width > 0
|
||||
? window.visualViewport.width
|
||||
: window.innerWidth;
|
||||
}
|
||||
|
||||
function setBaselineViewport(height: number, width: number): void {
|
||||
_baselineViewportHeight = height;
|
||||
_baselineViewportWidth = width;
|
||||
}
|
||||
|
||||
function getBaselineViewportHeight(): number {
|
||||
if (_baselineViewportHeight === null) {
|
||||
_baselineViewportHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||
setBaselineViewport(window.visualViewport?.height ?? window.innerHeight, getCurrentViewportWidth());
|
||||
}
|
||||
return _baselineViewportHeight;
|
||||
return _baselineViewportHeight ?? (window.visualViewport?.height ?? window.innerHeight);
|
||||
}
|
||||
|
||||
function updateBaselineViewportHeight(nextHeight: number): void {
|
||||
function updateBaselineViewportHeight(nextHeight: number, nextWidth: number): void {
|
||||
const current = getBaselineViewportHeight();
|
||||
if (nextHeight > current) {
|
||||
_baselineViewportHeight = nextHeight;
|
||||
const widthChanged = _baselineViewportWidth !== null && Math.abs(nextWidth - _baselineViewportWidth) >= 1;
|
||||
/*
|
||||
FNXC:Terminal 2026-06-30-08:51:
|
||||
SessionTerminal shares the folded-phone root cause: a keyboard-closed width/posture settle can be shorter than the previous unfolded baseline, so max-only baselines overestimate later iOS keyboard overlap and lift/refit the embedded terminal against stale geometry. Replace the baseline on settled folded posture changes while preserving the max-observed recovery for same-posture keyboard-open first samples.
|
||||
*/
|
||||
if (nextHeight > current || (widthChanged && nextHeight >= SETTLED_FOLDED_VIEWPORT_MIN_HEIGHT_PX)) {
|
||||
setBaselineViewport(nextHeight, nextWidth);
|
||||
}
|
||||
}
|
||||
|
||||
function resetBaselineViewportHeight(): void {
|
||||
_baselineViewportHeight = null;
|
||||
_baselineViewportWidth = null;
|
||||
}
|
||||
|
||||
function isKeyboardFocusableElement(el: Element | null): boolean {
|
||||
@@ -101,7 +120,7 @@ function getKeyboardMetrics(
|
||||
|
||||
// Only refresh baseline while keyboard is likely closed.
|
||||
if (!focused) {
|
||||
updateBaselineViewportHeight(vv.height);
|
||||
updateBaselineViewportHeight(vv.height, getCurrentViewportWidth());
|
||||
}
|
||||
|
||||
// FN-5155: iOS focus/restore can briefly report offsetTop from the keyboard
|
||||
|
||||
Reference in New Issue
Block a user