fix(chat): adapt input bar to soft keyboard in FB in-app browser
FB IAB on Xiaomi (and similar WebViews that don't shrink 100dvh) keeps the chat input covered by the soft keyboard. Fix by writing the visualViewport-derived keyboard height to a CSS variable on <html> and consuming it as padding-bottom on the chat input bar. - Add useKeyboardHeight hook (visualViewport + rAF batching, no re-renders) - Apply --keyboard-height + env(safe-area-inset-bottom) to .bar padding - Add scrollIntoView on textarea focus so the input stays visible - Add viewportFit: cover so safe-area-inset-bottom is reported
This commit is contained in:
@@ -5,6 +5,7 @@ import Image from "next/image";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { useAuthState } from "@/stores/auth/auth-context";
|
||||
import { useKeyboardHeight } from "@/hooks/use-keyboard-height";
|
||||
import type { LoginStatus } from "@/data/dto/auth";
|
||||
import { AppStorage } from "@/data/storage/app/app_storage";
|
||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||
@@ -42,6 +43,11 @@ export function ChatScreen() {
|
||||
const [showExternalBrowserDialog, setShowExternalBrowserDialog] =
|
||||
useState(false);
|
||||
|
||||
// 全局副作用:把软键盘高度写到 <html> 的 --keyboard-height CSS 变量,
|
||||
// 让 .bar 的 padding-bottom 跟着调整,修复 FB IAB / 小米 WebView 输入框被键盘遮挡的问题。
|
||||
// 详见 src/hooks/use-keyboard-height.ts
|
||||
useKeyboardHeight();
|
||||
|
||||
// isGuest 派生(chat-screen 是唯一知道这个值的地方 —— chat 机器无 isGuest 字段)
|
||||
const isGuest = deriveIsGuest(authState.loginStatus);
|
||||
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
/* ChatInputBar 输入栏容器样式(与 Dart chat_input_bar.dart 对齐) */
|
||||
|
||||
/* 外层:focused 时加 padding-top + 粉色背景 + 阴影 */
|
||||
/* 键盘适配(修复 FB IAB / 小米 WebView 不收缩 100dvh 导致输入框被遮挡):
|
||||
* --keyboard-height 由 src/hooks/use-keyboard-height.ts 写到 <html>,默认 0px。
|
||||
* env(safe-area-inset-bottom) 处理小米全面屏手势条 / iPhone home indicator。
|
||||
* 二者在原生已正确处理键盘的浏览器(iOS Safari、现代 Chrome Android)上都为 0,
|
||||
* 因此无视觉副作用。 */
|
||||
.bar {
|
||||
flex: 0 0 auto;
|
||||
padding: 0 var(--spacing-lg, 16px) var(--spacing-lg, 16px);
|
||||
padding: 0
|
||||
var(--spacing-lg, 16px)
|
||||
calc(
|
||||
var(--spacing-lg, 16px) + var(--keyboard-height, 0px) +
|
||||
env(safe-area-inset-bottom, 0px)
|
||||
);
|
||||
background: transparent;
|
||||
transition: padding-top 0.2s ease, background-color 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
transition: padding-top 0.2s ease, padding-bottom 0.2s ease,
|
||||
background-color 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.barFocused {
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
forwardRef,
|
||||
type FormEvent,
|
||||
type KeyboardEvent,
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
} from "react";
|
||||
@@ -54,6 +55,20 @@ export const ChatInputTextField = forwardRef<
|
||||
// 对外暴露与 forwardRef 同名的 ref(指向内部 textarea 节点)
|
||||
useImperativeHandle(ref, () => innerRef.current as HTMLTextAreaElement, []);
|
||||
|
||||
// 获得焦点时把 textarea 滚到可见区域(修复小米/FB IAB 键盘弹起后
|
||||
// 内容仍被遮挡的场景),rAF 等浏览器完成键盘布局。
|
||||
useEffect(() => {
|
||||
const el = innerRef.current;
|
||||
if (!el) return;
|
||||
const onFocus = () => {
|
||||
window.requestAnimationFrame(() => {
|
||||
el.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
||||
});
|
||||
};
|
||||
el.addEventListener("focus", onFocus);
|
||||
return () => el.removeEventListener("focus", onFocus);
|
||||
}, []);
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key !== "Enter") return;
|
||||
if (e.shiftKey || e.ctrlKey || e.metaKey) return; // 修饰键 → 换行
|
||||
|
||||
Reference in New Issue
Block a user