fix: reconcile frozen chat keyboard padding (#1528)
## Problem Same issue as #1493 (this PR adopts its implementation and tests — credit to @jmeistrich, kept as co-author — and addresses the review feedback there, since that PR looks stalled). Keyboard handlers in `useChatKeyboard` return early while `freeze` is true. If the keyboard closes during the frozen interval — e.g. `@legendapp/list`'s `useKeyboardScrollToEnd` freezing across `KeyboardController.dismiss()` + `scrollToEnd` (dismiss resolves on `keyboardDidHide`, after the close animation) — the hook misses the terminal keyboard height and keeps stale padding after `freeze` becomes false. `onContentInsetChange` then stops at the previous keyboard padding instead of continuing down to 0. In a chat list the phantom padding hides under `max(blankSpace, padding + extraContentPadding)` until the anchored blank space shrinks below it while a response streams in, then shows up as a keyboard-sized gap at the end of the list. The next keyboard open/close cycle clears it, which made it look intermittent. ## Solution `useChatKeyboard` is untouched. A new self-contained `useFrozenPadding` hook, wired at the `KeyboardChatScrollView` level alongside `useExtraContentPadding`/`useEndVisible`, registers its own `useKeyboardHandler` and tracks two private shared values: - `lastHeight` — written in `onMove`/`onEnd` only, so it only ever holds measured keyboard positions, never the `onStart` target. - `targetKeyboardHeight` — the one-line `onStart` guard, needed for `getEffectiveHeight` with a non-zero `offset`. When `freeze` transitions from true to false, the hook recomputes the effective padding from the latest observed keyboard height. The documented bottom-sheet freeze use case is unaffected: if the keyboard is still open at unfreeze, the recomputed effective height equals the current padding and nothing changes. If unfreeze lands mid-animation, the reconciled value is corrected within a frame by the now-unfrozen `useChatKeyboard` handlers on the remaining `onMove`/`onEnd` events. ### Review feedback addressed - **[This PR's review](https://github.com/kirillzyusko/react-native-keyboard-controller/pull/1528#discussion_r3519381040)**: `currentHeight` is no longer reused — it stays purely the Fabric commit-forcing mechanism and iOS never writes it. All `useChatKeyboard` changes are reverted (early `freeze` returns restored on both platforms); `useFrozenPadding` derives its own tracking values from its own `useKeyboardHandler`. - **[#1493 Android `onStart` tracking](https://github.com/kirillzyusko/react-native-keyboard-controller/pull/1493#discussion_r3394715087)**: the tracked height is never written in `onStart`, which carries the *target* height and would interleave with per-frame `onMove` values (336, 0, 80, 120, …). - **[#1493 shared hook](https://github.com/kirillzyusko/react-native-keyboard-controller/pull/1493#discussion_r3394731437)**: iOS and Android share the single `useFrozenPadding` implementation. ## Test plan - `yarn jest src/components/KeyboardChatScrollView --runInBand` — 194 passing, including a dedicated `useFrozenPadding` spec: keyboard close while frozen, keyboard open while frozen with non-zero `offset`, no-op when the keyboard didn't move, and no write on false→true transitions - `yarn typescript`, `eslint --quiet` on the touched files — clean - Verified manually in a chat app (iOS + Android, RN 0.85.3) via patch-package on the previous revision of this fix: keyboard-open sends no longer leave a keyboard-sized gap after long streamed responses. The reconciliation math is unchanged in this revision; only where the tracking lives moved. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Jay Meistrich <github@jayz.us>
D
Daniel Suchý committed
95e2f5835e263aae88f9bd280853c11d294cfc13
Parent: fe777b4
Committed by GitHub <noreply@github.com>
on 7/5/2026, 9:13:41 PM