@@ -3,13 +3,15 @@ import React, {useEffect, useRef} from 'react';
33import type { LayoutChangeEvent } from 'react-native' ;
44import { useReanimatedKeyboardAnimation } from 'react-native-keyboard-controller' ;
55import Reanimated , { Easing , useAnimatedReaction , useAnimatedStyle , useSharedValue , withTiming } from 'react-native-reanimated' ;
6+ import useDebounce from '@hooks/useDebounce' ;
67import usePrevious from '@hooks/usePrevious' ;
78import useWindowDimensions from '@hooks/useWindowDimensions' ;
89import isInLandscapeModeUtil from '@libs/isInLandscapeMode' ;
910import type { CollapsibleHeaderOnKeyboardProps } from './types' ;
1011
1112const COLLAPSE_DURATION = 100 ;
1213const RESTORE_DURATION = 300 ;
14+ const HEADER_CONTENT_LAYOUT_STABILIZATION_TIME_ON_ORIENTATION_CHANGE = 300 ;
1315// Assumed vertical space for the focused input field — used to reserve space above the keyboard.
1416const VERTICAL_SPACE_FOR_FOCUSED_INPUT = 120 ;
1517const KEYBOARD_OPENING_PROGRESS_THRESHOLDS = [ 0.5 , 0.7 , 0.8 , 0.85 , 0.9 , 0.95 , 0.99 ] ;
@@ -60,12 +62,33 @@ function CollapsibleHeaderOnKeyboard({children, collapsibleHeaderOffset = 0}: Co
6062 isInLandscapeModeSV . set ( isInLandscapeMode ) ;
6163 } , [ isInLandscapeMode , isInLandscapeModeSV ] ) ;
6264
65+ const debouncedUpdateNaturalHeightRef = useDebounce ( ( height : number ) => {
66+ if ( isInLandscapeMode ) {
67+ return ;
68+ }
69+ naturalHeightRef . current = height ;
70+ } , HEADER_CONTENT_LAYOUT_STABILIZATION_TIME_ON_ORIENTATION_CHANGE ) ;
71+
6372 const onLayout = ( e : LayoutChangeEvent ) => {
6473 const height = e . nativeEvent . layout . height ;
6574
6675 if ( height <= 0 ) {
6776 return ;
6877 }
78+
79+ // Header re-measured in portrait mode and the content is taller than the previous measurement,
80+ // set the natural height to the new height and animate the height to the new height.
81+ // This can happen when header has different height on portrait and landscape mode.
82+ if ( ! isInLandscapeMode && height > naturalHeightRef . current && naturalHeightRef . current !== - 1 ) {
83+ naturalHeight . set ( height ) ;
84+ animatedHeight . set ( withTiming ( height , { duration : RESTORE_DURATION } ) ) ;
85+
86+ // we need to debounce the update of the natural height ref to make sure we don't block height updates
87+ // on landscape -> portrait mode change where header layout might not be stable at first.
88+ debouncedUpdateNaturalHeightRef ( height ) ;
89+ return ;
90+ }
91+
6992 // First measurement, or content changed while header is fully open
7093 // (to skip onLayout calls triggered by our own height animation collapsing the view to 0)
7194 if ( naturalHeightRef . current === - 1 || animatedHeight . get ( ) >= naturalHeightRef . current ) {
@@ -77,19 +100,20 @@ function CollapsibleHeaderOnKeyboard({children, collapsibleHeaderOffset = 0}: Co
77100
78101 // Restores the header when the screen goes from landscape to portrait mode.
79102 useEffect ( ( ) => {
80- const naturalHeightValue = naturalHeightRef . current ;
103+ const naturalHeightValue = naturalHeight . get ( ) ;
81104 if ( ! isInLandscapeMode && isFocused && naturalHeightValue !== - 1 ) {
82105 animatedHeight . set ( withTiming ( naturalHeightValue , { duration : RESTORE_DURATION } ) ) ;
83106 }
84- } , [ isInLandscapeMode , isFocused , animatedHeight ] ) ;
107+ // eslint-disable-next-line react-hooks/exhaustive-deps
108+ } , [ isInLandscapeMode ] ) ;
85109
86110 // Restores the header when the screen loses focus
87111 useEffect ( ( ) => {
88112 if ( ! prevIsFocused || isFocused ) {
89113 return ;
90114 }
91115
92- const naturalHeightValue = naturalHeightRef . current ;
116+ const naturalHeightValue = naturalHeight . get ( ) ;
93117 if ( naturalHeightValue === - 1 ) {
94118 return ;
95119 }
@@ -159,7 +183,7 @@ function CollapsibleHeaderOnKeyboard({children, collapsibleHeaderOffset = 0}: Co
159183 // When fully open, leave height undefined so the view sizes itself naturally.
160184 // This avoids fighting the layout engine during orientation changes.
161185 if ( animatedHeight . get ( ) >= naturalHeight . get ( ) ) {
162- return { overflow : 'hidden' } ;
186+ return { overflow : 'hidden' , height : 'auto' } ;
163187 }
164188 return { height : animatedHeight . get ( ) , overflow : 'hidden' } ;
165189 } ) ;
0 commit comments