fix(chat): improve in-app keyboard avoidance

This commit is contained in:
2026-06-24 15:13:11 +08:00
parent 853ae776f9
commit 08c954903f
7 changed files with 193 additions and 66 deletions
@@ -1,16 +1,16 @@
/* ChatInputBar 输入栏容器样式(与 Dart chat_input_bar.dart 对齐) */
/* 键盘适配(修复 FB IAB / 小米 WebView 不收缩 100dvh 导致输入框被遮挡)
* --keyboard-height 由 src/hooks/use-keyboard-height.ts 写到 <html>,默认 0px
/* 键盘适配:
* --app-viewport-height 会让聊天容器优先贴合 visualViewport
* --chat-input-lift 是 Android 应用内浏览器的兜底抬升值。
* env(safe-area-inset-bottom) 处理小米全面屏手势条 / iPhone home indicator。
* 二者在原生已正确处理键盘的浏览器(iOS Safari、现代 Chrome Android)上都为 0
* 因此无视觉副作用。 */
* 正常浏览器中 --chat-input-lift 为 0,因此无视觉副作用。 */
.bar {
flex: 0 0 auto;
padding: 0
var(--spacing-lg, 16px)
calc(
var(--spacing-lg, 16px) + var(--keyboard-height, 0px) +
var(--spacing-lg, 16px) + var(--chat-input-lift, 0px) +
env(safe-area-inset-bottom, 0px)
);
background: transparent;
@@ -3,6 +3,7 @@ import { useRef, useState } from "react";
import { useChatDispatch } from "@/stores/chat/chat-context";
import { useUserState } from "@/stores/user/user-context";
import { useKeyboardHeight } from "@/hooks";
import { Logger } from "@/utils";
import { ChatInputActionPanel } from "./chat-input-action-panel";
@@ -28,6 +29,11 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) {
const isActive = isFocused || isActionPanelOpen;
const voiceMinutesRemaining = currentUser?.voiceMinutesRemaining ?? 0;
useKeyboardHeight({
targetRef: textareaRef,
active: isFocused,
});
const handleInputChange = (value: string) => {
setInput(value);
if (value.trim().length > 0) {
@@ -18,7 +18,6 @@ import {
forwardRef,
type FormEvent,
type KeyboardEvent,
useEffect,
useImperativeHandle,
useRef,
} from "react";
@@ -55,20 +54,6 @@ 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; // 修饰键 → 换行
@@ -3,7 +3,7 @@
.shell {
display: flex;
flex-direction: column;
height: 100dvh;
height: var(--app-viewport-height, 100dvh);
background: var(--color-dark-background, #111);
color: var(--color-text-primary, #fff);
position: relative;