diff --git a/src/hooks/use-keyboard-height.ts b/src/hooks/use-keyboard-height.ts index a60b61c8..795d8dfe 100644 --- a/src/hooks/use-keyboard-height.ts +++ b/src/hooks/use-keyboard-height.ts @@ -73,6 +73,7 @@ export function useKeyboardHeight({ let lastInputLift = -1; let lastMayBeKeyboardClose = false; let fallbackWasApplied = false; + let lastEventType = "initial"; const environment = getKeyboardAdaptEnvironment(); log.debug("[keyboard] environment", environment); @@ -90,11 +91,45 @@ export function useKeyboardHeight({ const metrics = readViewportMetrics(); let activeNow = isKeyboardTargetActive(targetRef?.current, activeRef.current); + const shouldTryRelease = + activeNow && + lastMayBeKeyboardClose && + shouldReleaseKeyboardState(metrics, focusedAtRef.current); + if ( activeNow && lastMayBeKeyboardClose && - shouldReleaseKeyboardState(metrics, focusedAtRef.current) + !shouldTryRelease ) { + log.debug("[keyboard] release skipped", { + reason: getReleaseSkipReason(metrics, focusedAtRef.current), + lastEventType, + environment, + active: activeNow, + reactActive: activeRef.current, + fallbackAllowed: fallbackAllowedRef.current, + lastInputLift, + fallbackWasApplied, + focusedForMs: getFocusedDurationMs(focusedAtRef.current), + metrics: formatMetrics(metrics), + }); + } + + if (shouldTryRelease) { + log.debug("[keyboard] release confirmed", { + lastEventType, + environment, + active: activeNow, + reactActive: activeRef.current, + fallbackAllowed: fallbackAllowedRef.current, + lastInputLift, + fallbackWasApplied, + focusedForMs: getFocusedDurationMs(focusedAtRef.current), + metrics: formatMetrics(metrics), + }); + } + + if (shouldTryRelease) { releaseKeyboardState("keyboard-close-detected", metrics); activeNow = false; } @@ -157,14 +192,31 @@ export function useKeyboardHeight({ }; const scheduleRechecks = (event?: Event) => { + lastEventType = event?.type ?? "manual"; + const hadInputLift = fallbackWasApplied || lastInputLift > 0; + const possibleKeyboardClose = + event != null && isPossibleKeyboardCloseEvent(event, hadInputLift); + log.debug("[keyboard] schedule rechecks", { + eventType: lastEventType, + possibleKeyboardClose, + hadInputLift, + fallbackWasApplied, + lastInputLift, + active: activeRef.current, + fallbackAllowed: fallbackAllowedRef.current, + focusedForMs: getFocusedDurationMs(focusedAtRef.current), + }); if ( - event && - isPossibleKeyboardCloseEvent( - event, - fallbackWasApplied || lastInputLift > 0, - ) + possibleKeyboardClose ) { lastMayBeKeyboardClose = true; + log.debug("[keyboard] possible close detected", { + eventType: lastEventType, + hadInputLift, + fallbackWasApplied, + lastInputLift, + focusedForMs: getFocusedDurationMs(focusedAtRef.current), + }); } timeoutIds.forEach((id) => window.clearTimeout(id)); timeoutIds = RECHECK_DELAYS_MS.map((delay) => @@ -302,6 +354,23 @@ function shouldReleaseKeyboardState( return !hasDetectedKeyboard(metrics); } +function getFocusedDurationMs(focusedAt: number): number | null { + if (focusedAt <= 0) return null; + return Math.round(performance.now() - focusedAt); +} + +function getReleaseSkipReason( + metrics: ViewportMetrics, + focusedAt: number, +): string { + if (focusedAt <= 0) return "not-focused-by-hook"; + if (performance.now() - focusedAt < FALLBACK_CLOSE_GRACE_MS) { + return "within-close-grace-window"; + } + if (hasDetectedKeyboard(metrics)) return "keyboard-still-detected"; + return "unknown"; +} + function hasDetectedKeyboard(metrics: ViewportMetrics): boolean { return ( metrics.keyboardHeight > 80 ||