From be6c8752d980ae9aaebd423bea15fdbf0171b708 Mon Sep 17 00:00:00 2001 From: Ronny Majani Date: Thu, 14 May 2026 15:33:47 +0300 Subject: [PATCH] fix(scrollable): preserve scroll position on keyboard dismiss When the keyboard dismisses, the inner scroll jumps back to offset 0 even though the user had scrolled. During the keyboard-down animation, the sheet `position` is between `extendedPositionWithKeyboard` and `extendedPosition`, so `animatedSheetState` falls through to `OPENED` (it only reports `EXTENDED` when `position` exactly matches `extendedPosition`). `useScrollable` then returns `LOCKED`, and `handleOnScroll` forces `scrollTo(0, 0)` because `shouldLockInitialPosition` was cleared earlier when the sheet was `EXTENDED` at drag start. The existing unlock condition only covers `KEYBOARD_STATUS.SHOWN`. Extend it to also cover animations whose `source` is `ANIMATION_SOURCE.KEYBOARD`, which handles the dismiss path where the keyboard is already `HIDDEN` but the sheet is still animating. --- src/hooks/useScrollable.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/hooks/useScrollable.ts b/src/hooks/useScrollable.ts index 9c19051d..a8bed746 100644 --- a/src/hooks/useScrollable.ts +++ b/src/hooks/useScrollable.ts @@ -6,6 +6,7 @@ import { useSharedValue, } from 'react-native-reanimated'; import { + ANIMATION_SOURCE, ANIMATION_STATUS, KEYBOARD_STATUS, SCROLLABLE_STATUS, @@ -62,13 +63,24 @@ export const useScrollable = ( } /** - * if keyboard is shown and sheet is animating - * then we do not lock the scrolling to not lose - * current scrollable scroll position. + * If the sheet is animating because of the keyboard (in either + * direction — show or hide), keep the scrollable UNLOCKED so the + * scroll position is preserved across the transition. The previous + * check only covered keyboard show (`KEYBOARD_STATUS.SHOWN`); during + * keyboard dismiss the keyboard status reports `HIDDEN` while the + * sheet is still animating back from its keyboard-offset position to + * its rest position, and during that window `animatedSheetState` + * falls through to `OPENED` (since the position no longer exactly + * equals either the extended or the extended-with-keyboard position). + * Without this branch, `LOCKED` kicks in mid-animation and + * `handleOnScroll` forces `scrollTo(0, 0)` — jumping the user back + * to the top. */ + const animationState = animatedAnimationState.get(); if ( - animatedKeyboardState.get().status === KEYBOARD_STATUS.SHOWN && - animatedAnimationState.get().status === ANIMATION_STATUS.RUNNING + animationState.status === ANIMATION_STATUS.RUNNING && + (animatedKeyboardState.get().status === KEYBOARD_STATUS.SHOWN || + animationState.source === ANIMATION_SOURCE.KEYBOARD) ) { return SCROLLABLE_STATUS.UNLOCKED; }