import React, { useImperativeHandle } from 'react'; import { View, StyleSheet, ScrollView } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import Animated, { useAnimatedRef } from 'react-native-reanimated'; import FadingView from './FadingView'; import { useScrollContainerLogic } from './useScrollContainerLogic'; import type { SharedScrollContainerProps } from './types'; const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView); type AnimatedScrollViewProps = React.ComponentProps & { children?: React.ReactNode; }; type ScrollViewWithHeadersProps = Omit< AnimatedScrollViewProps & SharedScrollContainerProps, 'onScroll' >; const ScrollViewWithHeadersInputComp = ( { largeHeaderShown, containerStyle, LargeHeaderSubtitleComponent, LargeHeaderComponent, largeHeaderContainerStyle, HeaderComponent, onLargeHeaderLayout, ignoreLeftSafeArea, ignoreRightSafeArea, // We use this to ensure that the onScroll property isn't accidentally used. // @ts-ignore onScroll: _unusedOnScroll, onScrollBeginDrag, onScrollEndDrag, onScrollWorklet, onMomentumScrollBegin, onMomentumScrollEnd, disableAutoFixScroll = false, children, absoluteHeader = false, initialAbsoluteHeaderHeight = 0, contentContainerStyle, automaticallyAdjustsScrollIndicatorInsets, headerFadeInThreshold = 1, scrollIndicatorInsets = {}, disableLargeHeaderFadeAnim = false, ...rest }: ScrollViewWithHeadersProps, ref: React.Ref ) => { if (_unusedOnScroll) { throw new Error( "The 'onScroll' property is not supported. Please use onScrollWorklet to track the scroll container's state." ); } const insets = useSafeAreaInsets(); const scrollRef = useAnimatedRef(); useImperativeHandle(ref, () => scrollRef.current); const { scrollY, showNavBar, largeHeaderHeight, largeHeaderOpacity, scrollHandler, debouncedFixScroll, absoluteHeaderHeight, onAbsoluteHeaderLayout, scrollViewAdjustments, } = useScrollContainerLogic({ scrollRef, largeHeaderShown, disableAutoFixScroll, largeHeaderExists: !!LargeHeaderComponent, absoluteHeader, initialAbsoluteHeaderHeight, headerFadeInThreshold, onScrollWorklet, }); return ( {!absoluteHeader && HeaderComponent({ showNavBar, scrollY })} { debouncedFixScroll.cancel(); if (onScrollBeginDrag) onScrollBeginDrag(e); }} onScrollEndDrag={(e) => { debouncedFixScroll(); if (onScrollEndDrag) onScrollEndDrag(e); }} onMomentumScrollBegin={(e) => { debouncedFixScroll.cancel(); if (onMomentumScrollBegin) onMomentumScrollBegin(e); }} onMomentumScrollEnd={(e) => { debouncedFixScroll(); if (onMomentumScrollEnd) onMomentumScrollEnd(e); }} contentContainerStyle={[ scrollViewAdjustments.contentContainerStyle, // @ts-ignore // Reanimated typings are causing this error - will fix in the future. contentContainerStyle, ]} automaticallyAdjustsScrollIndicatorInsets={ automaticallyAdjustsScrollIndicatorInsets !== undefined ? automaticallyAdjustsScrollIndicatorInsets : !absoluteHeader } scrollIndicatorInsets={{ top: absoluteHeader ? absoluteHeaderHeight : 0, ...scrollIndicatorInsets, }} {...rest} > {LargeHeaderComponent && ( { largeHeaderHeight.value = e.nativeEvent.layout.height; if (onLargeHeaderLayout) onLargeHeaderLayout(e.nativeEvent.layout); }} > {!disableLargeHeaderFadeAnim ? ( {LargeHeaderComponent({ scrollY, showNavBar })} ) : ( {LargeHeaderComponent({ scrollY, showNavBar })} )} )} {LargeHeaderSubtitleComponent && LargeHeaderSubtitleComponent({ showNavBar, scrollY })} {children} {absoluteHeader && ( {HeaderComponent({ showNavBar, scrollY })} )} ); }; const ScrollViewWithHeaders = React.forwardRef( ScrollViewWithHeadersInputComp ); export default ScrollViewWithHeaders; const styles = StyleSheet.create({ container: { flex: 1 }, absoluteHeader: { position: 'absolute', top: 0, right: 0, left: 0, }, });