fix(chat): release input focus when keyboard closes

This commit is contained in:
2026-06-26 12:01:24 +08:00
parent 58236453ed
commit 09cbd54c79
2 changed files with 40 additions and 16 deletions
@@ -32,6 +32,7 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) {
useKeyboardHeight({
targetRef: textareaRef,
active: isFocused,
onKeyboardDismiss: () => setIsFocused(false),
});
const handleInputChange = (value: string) => {
+39 -16
View File
@@ -24,7 +24,7 @@ const FALLBACK_KEYBOARD_MIN = 260;
const FALLBACK_KEYBOARD_MAX = 380;
const FALLBACK_CLOSE_GRACE_MS = 900;
const RECHECK_DELAYS_MS = [0, 50, 120, 240, 420, 700];
const FALLBACK_RELEASE_EVENT_TYPES = new Set([
const KEYBOARD_RELEASE_EVENT_TYPES = new Set([
"resize",
"scroll",
"geometrychange",
@@ -39,16 +39,23 @@ const log = new Logger("HooksUseKeyboardHeight");
export interface UseKeyboardHeightOptions {
targetRef?: RefObject<HTMLElement | null>;
active?: boolean;
onKeyboardDismiss?: () => void;
}
export function useKeyboardHeight({
targetRef,
active = false,
onKeyboardDismiss,
}: UseKeyboardHeightOptions = {}): void {
const activeRef = useRef(active);
const onKeyboardDismissRef = useRef(onKeyboardDismiss);
const fallbackAllowedRef = useRef(false);
const focusedAtRef = useRef(0);
useEffect(() => {
onKeyboardDismissRef.current = onKeyboardDismiss;
}, [onKeyboardDismiss]);
useEffect(() => {
activeRef.current = active;
fallbackAllowedRef.current = active;
@@ -82,20 +89,14 @@ export function useKeyboardHeight({
rafId = null;
const metrics = readViewportMetrics();
const activeNow = isKeyboardTargetActive(targetRef?.current, activeRef.current);
let activeNow = isKeyboardTargetActive(targetRef?.current, activeRef.current);
if (
activeNow &&
fallbackAllowedRef.current &&
lastMayBeKeyboardClose &&
shouldReleaseFallback(metrics, focusedAtRef.current)
shouldReleaseKeyboardState(metrics, focusedAtRef.current)
) {
fallbackAllowedRef.current = false;
blurKeyboardTarget(targetRef?.current);
log.debug("[keyboard] fallback released", {
reason: "keyboard-close-detected",
environment,
metrics: formatMetrics(metrics),
});
releaseKeyboardState("keyboard-close-detected", metrics);
activeNow = false;
}
lastMayBeKeyboardClose = false;
@@ -156,7 +157,13 @@ export function useKeyboardHeight({
};
const scheduleRechecks = (event?: Event) => {
if (event && isPossibleKeyboardCloseEvent(event, fallbackWasApplied)) {
if (
event &&
isPossibleKeyboardCloseEvent(
event,
fallbackWasApplied || lastInputLift > 0,
)
) {
lastMayBeKeyboardClose = true;
}
timeoutIds.forEach((id) => window.clearTimeout(id));
@@ -165,6 +172,22 @@ export function useKeyboardHeight({
);
};
const releaseKeyboardState = (
reason: string,
metrics: ViewportMetrics,
): void => {
activeRef.current = false;
fallbackAllowedRef.current = false;
focusedAtRef.current = 0;
blurKeyboardTarget(targetRef?.current);
onKeyboardDismissRef.current?.();
log.debug("[keyboard] released", {
reason,
environment,
metrics: formatMetrics(metrics),
});
};
const vv = window.visualViewport;
if (vv) {
vv.addEventListener("resize", scheduleRechecks);
@@ -270,7 +293,7 @@ function computeFallbackLift(metrics: ViewportMetrics): number {
);
}
function shouldReleaseFallback(
function shouldReleaseKeyboardState(
metrics: ViewportMetrics,
focusedAt: number,
): boolean {
@@ -288,12 +311,12 @@ function hasDetectedKeyboard(metrics: ViewportMetrics): boolean {
function isPossibleKeyboardCloseEvent(
event: Event,
fallbackWasApplied: boolean,
hadInputLift: boolean,
): boolean {
if (!fallbackWasApplied) {
if (!hadInputLift) {
return event.type === "resize" || event.type === "geometrychange";
}
return FALLBACK_RELEASE_EVENT_TYPES.has(event.type);
return KEYBOARD_RELEASE_EVENT_TYPES.has(event.type);
}
function isKeyboardTargetActive(