fix(chat): release input focus when keyboard closes
This commit is contained in:
@@ -32,6 +32,7 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) {
|
||||
useKeyboardHeight({
|
||||
targetRef: textareaRef,
|
||||
active: isFocused,
|
||||
onKeyboardDismiss: () => setIsFocused(false),
|
||||
});
|
||||
|
||||
const handleInputChange = (value: string) => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user