fix(chat): release input focus when keyboard closes
This commit is contained in:
@@ -32,6 +32,7 @@ export function ChatInputBar({ disabled = false }: ChatInputBarProps) {
|
|||||||
useKeyboardHeight({
|
useKeyboardHeight({
|
||||||
targetRef: textareaRef,
|
targetRef: textareaRef,
|
||||||
active: isFocused,
|
active: isFocused,
|
||||||
|
onKeyboardDismiss: () => setIsFocused(false),
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleInputChange = (value: string) => {
|
const handleInputChange = (value: string) => {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const FALLBACK_KEYBOARD_MIN = 260;
|
|||||||
const FALLBACK_KEYBOARD_MAX = 380;
|
const FALLBACK_KEYBOARD_MAX = 380;
|
||||||
const FALLBACK_CLOSE_GRACE_MS = 900;
|
const FALLBACK_CLOSE_GRACE_MS = 900;
|
||||||
const RECHECK_DELAYS_MS = [0, 50, 120, 240, 420, 700];
|
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",
|
"resize",
|
||||||
"scroll",
|
"scroll",
|
||||||
"geometrychange",
|
"geometrychange",
|
||||||
@@ -39,16 +39,23 @@ const log = new Logger("HooksUseKeyboardHeight");
|
|||||||
export interface UseKeyboardHeightOptions {
|
export interface UseKeyboardHeightOptions {
|
||||||
targetRef?: RefObject<HTMLElement | null>;
|
targetRef?: RefObject<HTMLElement | null>;
|
||||||
active?: boolean;
|
active?: boolean;
|
||||||
|
onKeyboardDismiss?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useKeyboardHeight({
|
export function useKeyboardHeight({
|
||||||
targetRef,
|
targetRef,
|
||||||
active = false,
|
active = false,
|
||||||
|
onKeyboardDismiss,
|
||||||
}: UseKeyboardHeightOptions = {}): void {
|
}: UseKeyboardHeightOptions = {}): void {
|
||||||
const activeRef = useRef(active);
|
const activeRef = useRef(active);
|
||||||
|
const onKeyboardDismissRef = useRef(onKeyboardDismiss);
|
||||||
const fallbackAllowedRef = useRef(false);
|
const fallbackAllowedRef = useRef(false);
|
||||||
const focusedAtRef = useRef(0);
|
const focusedAtRef = useRef(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onKeyboardDismissRef.current = onKeyboardDismiss;
|
||||||
|
}, [onKeyboardDismiss]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
activeRef.current = active;
|
activeRef.current = active;
|
||||||
fallbackAllowedRef.current = active;
|
fallbackAllowedRef.current = active;
|
||||||
@@ -82,20 +89,14 @@ export function useKeyboardHeight({
|
|||||||
rafId = null;
|
rafId = null;
|
||||||
|
|
||||||
const metrics = readViewportMetrics();
|
const metrics = readViewportMetrics();
|
||||||
const activeNow = isKeyboardTargetActive(targetRef?.current, activeRef.current);
|
let activeNow = isKeyboardTargetActive(targetRef?.current, activeRef.current);
|
||||||
if (
|
if (
|
||||||
activeNow &&
|
activeNow &&
|
||||||
fallbackAllowedRef.current &&
|
|
||||||
lastMayBeKeyboardClose &&
|
lastMayBeKeyboardClose &&
|
||||||
shouldReleaseFallback(metrics, focusedAtRef.current)
|
shouldReleaseKeyboardState(metrics, focusedAtRef.current)
|
||||||
) {
|
) {
|
||||||
fallbackAllowedRef.current = false;
|
releaseKeyboardState("keyboard-close-detected", metrics);
|
||||||
blurKeyboardTarget(targetRef?.current);
|
activeNow = false;
|
||||||
log.debug("[keyboard] fallback released", {
|
|
||||||
reason: "keyboard-close-detected",
|
|
||||||
environment,
|
|
||||||
metrics: formatMetrics(metrics),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
lastMayBeKeyboardClose = false;
|
lastMayBeKeyboardClose = false;
|
||||||
|
|
||||||
@@ -156,7 +157,13 @@ export function useKeyboardHeight({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const scheduleRechecks = (event?: Event) => {
|
const scheduleRechecks = (event?: Event) => {
|
||||||
if (event && isPossibleKeyboardCloseEvent(event, fallbackWasApplied)) {
|
if (
|
||||||
|
event &&
|
||||||
|
isPossibleKeyboardCloseEvent(
|
||||||
|
event,
|
||||||
|
fallbackWasApplied || lastInputLift > 0,
|
||||||
|
)
|
||||||
|
) {
|
||||||
lastMayBeKeyboardClose = true;
|
lastMayBeKeyboardClose = true;
|
||||||
}
|
}
|
||||||
timeoutIds.forEach((id) => window.clearTimeout(id));
|
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;
|
const vv = window.visualViewport;
|
||||||
if (vv) {
|
if (vv) {
|
||||||
vv.addEventListener("resize", scheduleRechecks);
|
vv.addEventListener("resize", scheduleRechecks);
|
||||||
@@ -270,7 +293,7 @@ function computeFallbackLift(metrics: ViewportMetrics): number {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function shouldReleaseFallback(
|
function shouldReleaseKeyboardState(
|
||||||
metrics: ViewportMetrics,
|
metrics: ViewportMetrics,
|
||||||
focusedAt: number,
|
focusedAt: number,
|
||||||
): boolean {
|
): boolean {
|
||||||
@@ -288,12 +311,12 @@ function hasDetectedKeyboard(metrics: ViewportMetrics): boolean {
|
|||||||
|
|
||||||
function isPossibleKeyboardCloseEvent(
|
function isPossibleKeyboardCloseEvent(
|
||||||
event: Event,
|
event: Event,
|
||||||
fallbackWasApplied: boolean,
|
hadInputLift: boolean,
|
||||||
): boolean {
|
): boolean {
|
||||||
if (!fallbackWasApplied) {
|
if (!hadInputLift) {
|
||||||
return event.type === "resize" || event.type === "geometrychange";
|
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(
|
function isKeyboardTargetActive(
|
||||||
|
|||||||
Reference in New Issue
Block a user