refactor(keyboard): remove input restore fallback logic

This commit is contained in:
2026-07-03 11:32:34 +08:00
parent e5bfb4e373
commit f5b3c9fb2b
5 changed files with 8 additions and 102 deletions
@@ -28,11 +28,7 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) {
useKeyboardHeight({ useKeyboardHeight({
targetRef: textareaRef, targetRef: textareaRef,
containerRef: barRef,
active: isFocused, active: isFocused,
onKeyboardDismiss: () => {
setIsFocused(false);
},
}); });
const handleInputChange = (value: string) => { const handleInputChange = (value: string) => {
@@ -4,6 +4,5 @@ export const FOCUS_GAP_PX = 14;
export const FALLBACK_INPUT_LIFT_PX = 320; export const FALLBACK_INPUT_LIFT_PX = 320;
export const KEYBOARD_VISIBLE_THRESHOLD_PX = 80; export const KEYBOARD_VISIBLE_THRESHOLD_PX = 80;
export const KEYBOARD_VISIBLE_RATIO = 0.82; export const KEYBOARD_VISIBLE_RATIO = 0.82;
export const KEYBOARD_RELEASE_GRACE_MS = 900;
export const KEYBOARD_MEASURE_DELAYS_MS = [0, 100, 250, 500]; export const KEYBOARD_MEASURE_DELAYS_MS = [0, 100, 250, 500];
export const KEYBOARD_POLL_INTERVAL_MS = 300; export const KEYBOARD_POLL_INTERVAL_MS = 300;
+8 -75
View File
@@ -10,9 +10,9 @@
* - 非目标 Android Facebook IAB 设备:完全不生效。 * - 非目标 Android Facebook IAB 设备:完全不生效。
* - focus 后短期多次测量输入框是否被 visibleViewport 遮挡。 * - focus 后短期多次测量输入框是否被 visibleViewport 遮挡。
* - 若 viewport 没有可靠变化,则使用固定 fallback 抬升。 * - 若 viewport 没有可靠变化,则使用固定 fallback 抬升。
* - 聚焦期间轻量轮询;一旦检测到 viewport 恢复,主动 blur 并清除抬升 * - 聚焦期间轻量轮询,持续保持输入框避让
*/ */
import { useEffect, useRef } from "react"; import { useEffect } from "react";
import { Logger } from "@/utils"; import { Logger } from "@/utils";
@@ -21,7 +21,6 @@ import {
FOCUS_GAP_PX, FOCUS_GAP_PX,
KEYBOARD_MEASURE_DELAYS_MS, KEYBOARD_MEASURE_DELAYS_MS,
KEYBOARD_POLL_INTERVAL_MS, KEYBOARD_POLL_INTERVAL_MS,
KEYBOARD_RELEASE_GRACE_MS,
KEYBOARD_VISIBLE_RATIO, KEYBOARD_VISIBLE_RATIO,
KEYBOARD_VISIBLE_THRESHOLD_PX, KEYBOARD_VISIBLE_THRESHOLD_PX,
} from "./constants"; } from "./constants";
@@ -33,10 +32,7 @@ import {
getKeyboardAdaptEnvironment, getKeyboardAdaptEnvironment,
shouldEnableKeyboardAdaptation, shouldEnableKeyboardAdaptation,
} from "./environment"; } from "./environment";
import { import { isKeyboardTargetActive } from "./keyboard-target";
blurKeyboardTarget,
isKeyboardTargetActive,
} from "./keyboard-target";
import type { UseKeyboardHeightOptions } from "./types"; import type { UseKeyboardHeightOptions } from "./types";
const log = new Logger("HooksUseKeyboardHeight"); const log = new Logger("HooksUseKeyboardHeight");
@@ -51,16 +47,8 @@ interface KeyboardSnapshot {
export function useKeyboardHeight({ export function useKeyboardHeight({
targetRef, targetRef,
containerRef,
active = false, active = false,
onKeyboardDismiss,
}: UseKeyboardHeightOptions = {}): void { }: UseKeyboardHeightOptions = {}): void {
const onKeyboardDismissRef = useRef(onKeyboardDismiss);
useEffect(() => {
onKeyboardDismissRef.current = onKeyboardDismiss;
}, [onKeyboardDismiss]);
useEffect(() => { useEffect(() => {
if (typeof window === "undefined") return; if (typeof window === "undefined") return;
@@ -77,10 +65,7 @@ export function useKeyboardHeight({
let rafId: number | null = null; let rafId: number | null = null;
let pollId: number | null = null; let pollId: number | null = null;
let timeoutIds: number[] = []; let timeoutIds: number[] = [];
let released = false;
let keyboardWasDetected = false;
let lastInputLift = -1; let lastInputLift = -1;
const focusedAt = active ? performance.now() : 0;
log.debug("[keyboard] environment", environment); log.debug("[keyboard] environment", environment);
@@ -107,39 +92,18 @@ export function useKeyboardHeight({
}); });
}; };
const release = (reason: string) => {
if (released) return;
released = true;
clearTimers();
applyLift(0, reason);
blurKeyboardTarget(targetRef?.current);
onKeyboardDismissRef.current?.();
log.debug("[keyboard] released", {
reason,
snapshot: formatSnapshot(readKeyboardSnapshot()),
});
};
const measure = (reason: string) => { const measure = (reason: string) => {
rafId = null; rafId = null;
const target = targetRef?.current; const target = targetRef?.current;
const activeNow = isKeyboardTargetActive(target, active); const activeNow = isKeyboardTargetActive(target, active);
const snapshot = readKeyboardSnapshot(); const snapshot = readKeyboardSnapshot();
if (snapshot.keyboardVisible) keyboardWasDetected = true;
if (!activeNow) { if (!activeNow) {
release("target-inactive"); log.debug("[keyboard] skip inactive target", {
return; reason,
} snapshot: formatSnapshot(snapshot),
});
const focusedFor = focusedAt > 0 ? performance.now() - focusedAt : 0;
if (
keyboardWasDetected &&
!snapshot.keyboardVisible &&
focusedFor >= KEYBOARD_RELEASE_GRACE_MS
) {
release("viewport-restored");
return; return;
} }
@@ -163,26 +127,7 @@ export function useKeyboardHeight({
scheduleMeasure("viewport-signal"); scheduleMeasure("viewport-signal");
}; };
const handleFocusOut = () => {
release("focusout");
};
const handlePointerDown = (event: PointerEvent | TouchEvent) => {
const target = targetRef?.current;
const container = containerRef?.current;
if (event.target instanceof Node && container?.contains(event.target)) {
log.debug("[keyboard] keep active for input container tap", {
snapshot: formatSnapshot(readKeyboardSnapshot()),
});
return;
}
if (!target || event.target === target) return;
if (event.target instanceof Node && target.contains(event.target)) return;
release("outside-interaction");
};
if (!active) { if (!active) {
release("react-inactive");
return; return;
} }
@@ -191,11 +136,7 @@ export function useKeyboardHeight({
visualViewport?.addEventListener("scroll", handleViewportSignal); visualViewport?.addEventListener("scroll", handleViewportSignal);
window.addEventListener("resize", handleViewportSignal); window.addEventListener("resize", handleViewportSignal);
window.addEventListener("orientationchange", handleViewportSignal); window.addEventListener("orientationchange", handleViewportSignal);
window.addEventListener("focusout", handleFocusOut);
window.addEventListener("blur", handleFocusOut);
document.addEventListener("visibilitychange", handleViewportSignal); document.addEventListener("visibilitychange", handleViewportSignal);
document.addEventListener("pointerdown", handlePointerDown, true);
document.addEventListener("touchstart", handlePointerDown, true);
scheduleInitialMeasures(); scheduleInitialMeasures();
pollId = window.setInterval( pollId = window.setInterval(
@@ -210,18 +151,10 @@ export function useKeyboardHeight({
visualViewport?.removeEventListener("scroll", handleViewportSignal); visualViewport?.removeEventListener("scroll", handleViewportSignal);
window.removeEventListener("resize", handleViewportSignal); window.removeEventListener("resize", handleViewportSignal);
window.removeEventListener("orientationchange", handleViewportSignal); window.removeEventListener("orientationchange", handleViewportSignal);
window.removeEventListener("focusout", handleFocusOut);
window.removeEventListener("blur", handleFocusOut);
document.removeEventListener("visibilitychange", handleViewportSignal); document.removeEventListener("visibilitychange", handleViewportSignal);
document.removeEventListener("pointerdown", handlePointerDown, true);
document.removeEventListener("touchstart", handlePointerDown, true);
resetInputLiftVar(); resetInputLiftVar();
}; };
}, [ }, [active, targetRef]);
active,
containerRef,
targetRef,
]);
} }
function readKeyboardSnapshot(): KeyboardSnapshot { function readKeyboardSnapshot(): KeyboardSnapshot {
@@ -4,23 +4,3 @@ export function isKeyboardTargetActive(
): boolean { ): boolean {
return reactActive || (target != null && document.activeElement === target); return reactActive || (target != null && document.activeElement === target);
} }
export function blurKeyboardTarget(
target: HTMLElement | null | undefined,
): void {
if (!target || document.activeElement !== target) return;
target.blur();
}
export function getActiveElementTag(): string | null {
const activeElement = document.activeElement;
if (!activeElement) return null;
const id = activeElement.id ? `#${activeElement.id}` : "";
const className =
typeof activeElement.className === "string" && activeElement.className
? `.${activeElement.className.split(/\s+/).filter(Boolean).join(".")}`
: "";
return `${activeElement.tagName.toLowerCase()}${id}${className}`;
}
-2
View File
@@ -2,9 +2,7 @@ import type { RefObject } from "react";
export interface UseKeyboardHeightOptions { export interface UseKeyboardHeightOptions {
targetRef?: RefObject<HTMLElement | null>; targetRef?: RefObject<HTMLElement | null>;
containerRef?: RefObject<HTMLElement | null>;
active?: boolean; active?: boolean;
onKeyboardDismiss?: () => void;
} }
export interface KeyboardAdaptEnvironment { export interface KeyboardAdaptEnvironment {