fix(viewport): suppress ios facebook keyboard resize
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import { useEffect, type ReactNode } from "react";
|
import { useEffect, type ReactNode } from "react";
|
||||||
|
|
||||||
|
import { BrowserDetector, Logger, PlatformDetector } from "@/utils";
|
||||||
|
|
||||||
const CSS_VAR_VIEWPORT_HEIGHT = "--app-viewport-height";
|
const CSS_VAR_VIEWPORT_HEIGHT = "--app-viewport-height";
|
||||||
const CSS_VAR_VISIBLE_HEIGHT = "--app-visible-height";
|
const CSS_VAR_VISIBLE_HEIGHT = "--app-visible-height";
|
||||||
const CSS_VAR_VISIBLE_OFFSET_TOP = "--app-visible-offset-top";
|
const CSS_VAR_VISIBLE_OFFSET_TOP = "--app-visible-offset-top";
|
||||||
@@ -9,6 +11,9 @@ const CSS_VAR_SAFE_TOP = "--app-safe-top";
|
|||||||
const CSS_VAR_SAFE_RIGHT = "--app-safe-right";
|
const CSS_VAR_SAFE_RIGHT = "--app-safe-right";
|
||||||
const CSS_VAR_SAFE_BOTTOM = "--app-safe-bottom";
|
const CSS_VAR_SAFE_BOTTOM = "--app-safe-bottom";
|
||||||
const CSS_VAR_SAFE_LEFT = "--app-safe-left";
|
const CSS_VAR_SAFE_LEFT = "--app-safe-left";
|
||||||
|
const IOS_KEYBOARD_VIEWPORT_DELTA_PX = 120;
|
||||||
|
|
||||||
|
const log = new Logger("ProvidersViewportCssVarsProvider");
|
||||||
|
|
||||||
export interface ViewportCssVarsProviderProps {
|
export interface ViewportCssVarsProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
@@ -28,6 +33,12 @@ export function ViewportCssVarsProvider({
|
|||||||
if (typeof window === "undefined") return;
|
if (typeof window === "undefined") return;
|
||||||
|
|
||||||
let frameId: number | null = null;
|
let frameId: number | null = null;
|
||||||
|
let stableLayoutHeight = window.innerHeight;
|
||||||
|
let stableVisibleHeight = window.visualViewport?.height ?? window.innerHeight;
|
||||||
|
let stableVisibleOffsetTop = window.visualViewport?.offsetTop ?? 0;
|
||||||
|
let iosFacebookKeyboardSuppressed = false;
|
||||||
|
const isIOSFacebookInAppBrowser =
|
||||||
|
PlatformDetector.isIOS() && BrowserDetector.isFacebookInAppBrowser();
|
||||||
|
|
||||||
const applyVars = () => {
|
const applyVars = () => {
|
||||||
frameId = null;
|
frameId = null;
|
||||||
@@ -38,12 +49,42 @@ export function ViewportCssVarsProvider({
|
|||||||
const visibleHeight = viewport?.height ?? layoutHeight;
|
const visibleHeight = viewport?.height ?? layoutHeight;
|
||||||
const visibleWidth = viewport?.width ?? layoutWidth;
|
const visibleWidth = viewport?.width ?? layoutWidth;
|
||||||
const visibleOffsetTop = viewport?.offsetTop ?? 0;
|
const visibleOffsetTop = viewport?.offsetTop ?? 0;
|
||||||
|
const shouldSuppressIOSFacebookKeyboardViewport =
|
||||||
|
isIOSFacebookInAppBrowser &&
|
||||||
|
isEditableElementFocused() &&
|
||||||
|
stableLayoutHeight - visibleHeight > IOS_KEYBOARD_VIEWPORT_DELTA_PX;
|
||||||
|
const nextLayoutHeight = shouldSuppressIOSFacebookKeyboardViewport
|
||||||
|
? stableLayoutHeight
|
||||||
|
: layoutHeight;
|
||||||
|
const nextVisibleHeight = shouldSuppressIOSFacebookKeyboardViewport
|
||||||
|
? stableVisibleHeight
|
||||||
|
: visibleHeight;
|
||||||
|
const nextVisibleOffsetTop = shouldSuppressIOSFacebookKeyboardViewport
|
||||||
|
? stableVisibleOffsetTop
|
||||||
|
: visibleOffsetTop;
|
||||||
|
|
||||||
root.style.setProperty(CSS_VAR_VIEWPORT_HEIGHT, `${layoutHeight}px`);
|
if (!shouldSuppressIOSFacebookKeyboardViewport) {
|
||||||
root.style.setProperty(CSS_VAR_VISIBLE_HEIGHT, `${visibleHeight}px`);
|
stableLayoutHeight = layoutHeight;
|
||||||
|
stableVisibleHeight = visibleHeight;
|
||||||
|
stableVisibleOffsetTop = visibleOffsetTop;
|
||||||
|
iosFacebookKeyboardSuppressed = false;
|
||||||
|
} else if (!iosFacebookKeyboardSuppressed) {
|
||||||
|
iosFacebookKeyboardSuppressed = true;
|
||||||
|
log.debug("[viewport] suppress ios facebook keyboard viewport", {
|
||||||
|
layoutHeight: Math.round(layoutHeight),
|
||||||
|
visibleHeight: Math.round(visibleHeight),
|
||||||
|
visibleOffsetTop: Math.round(visibleOffsetTop),
|
||||||
|
stableLayoutHeight: Math.round(stableLayoutHeight),
|
||||||
|
stableVisibleHeight: Math.round(stableVisibleHeight),
|
||||||
|
stableVisibleOffsetTop: Math.round(stableVisibleOffsetTop),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
root.style.setProperty(CSS_VAR_VIEWPORT_HEIGHT, `${nextLayoutHeight}px`);
|
||||||
|
root.style.setProperty(CSS_VAR_VISIBLE_HEIGHT, `${nextVisibleHeight}px`);
|
||||||
root.style.setProperty(
|
root.style.setProperty(
|
||||||
CSS_VAR_VISIBLE_OFFSET_TOP,
|
CSS_VAR_VISIBLE_OFFSET_TOP,
|
||||||
`${visibleOffsetTop}px`,
|
`${nextVisibleOffsetTop}px`,
|
||||||
);
|
);
|
||||||
root.style.setProperty(CSS_VAR_SAFE_TOP, "env(safe-area-inset-top, 0px)");
|
root.style.setProperty(CSS_VAR_SAFE_TOP, "env(safe-area-inset-top, 0px)");
|
||||||
root.style.setProperty(
|
root.style.setProperty(
|
||||||
@@ -59,7 +100,7 @@ export function ViewportCssVarsProvider({
|
|||||||
"env(safe-area-inset-left, 0px)",
|
"env(safe-area-inset-left, 0px)",
|
||||||
);
|
);
|
||||||
root.dataset.viewportWidth = String(Math.round(visibleWidth));
|
root.dataset.viewportWidth = String(Math.round(visibleWidth));
|
||||||
root.dataset.viewportHeight = String(Math.round(visibleHeight));
|
root.dataset.viewportHeight = String(Math.round(nextVisibleHeight));
|
||||||
};
|
};
|
||||||
|
|
||||||
const scheduleApply = () => {
|
const scheduleApply = () => {
|
||||||
@@ -85,3 +126,15 @@ export function ViewportCssVarsProvider({
|
|||||||
|
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isEditableElementFocused(): boolean {
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
if (!(activeElement instanceof HTMLElement)) return false;
|
||||||
|
|
||||||
|
const tagName = activeElement.tagName.toLowerCase();
|
||||||
|
return (
|
||||||
|
tagName === "input" ||
|
||||||
|
tagName === "textarea" ||
|
||||||
|
activeElement.isContentEditable
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user