UNPKG

@attio/react-native-bottom-sheet-toolbox

Version:
102 lines (86 loc) 3.5 kB
import type {SectionList} from "react-native" import type Animated from "react-native-reanimated" import {type FlatList, Gesture, type ScrollView} from "react-native-gesture-handler" import { type AnimatedRef, type SharedValue, scrollTo, useDerivedValue, useSharedValue, } from "react-native-reanimated" import {useBottomSheetToolboxInternalContext} from "../context" import {useBottomSheetDragGesture} from "../hooks/use-bottom-sheet-drag-gesture" export type Scrollable<T> = | FlatList<T> | ScrollView | SectionList<T> | Animated.FlatList<T> | Animated.ScrollView // biome-ignore lint/suspicious/noExplicitAny: not useful to type | any export interface UseBottomSheetScrollGestureArgs<T> { scrollRef: AnimatedRef<Scrollable<T>> scrollOffsetYSharedValue: SharedValue<number> scrollContentHeightSharedValue: SharedValue<number | null> scrollLayoutHeightSharedValue: SharedValue<number | null> inverted: boolean isFrozenSharedValue?: SharedValue<boolean> } export function useBottomSheetScrollGesture<T>({ scrollRef, scrollOffsetYSharedValue, scrollContentHeightSharedValue, scrollLayoutHeightSharedValue, inverted, isFrozenSharedValue, }: UseBottomSheetScrollGestureArgs<T>) { const {normalizedSnapPointsSharedValue, currentPositionSharedValue} = useBottomSheetToolboxInternalContext() const isExpandedSharedValue = useDerivedValue(() => { const maxSnap = Math.max(...(normalizedSnapPointsSharedValue.get() || [])) return currentPositionSharedValue.get() === maxSnap }, [normalizedSnapPointsSharedValue, currentPositionSharedValue]) const panFrozenSharedValue = useDerivedValue(() => { let isAtTop = false const scrollOffsetY = scrollOffsetYSharedValue.get() if (inverted) { const scrollLayoutHeight = scrollLayoutHeightSharedValue.get() const scrollContentHeight = scrollContentHeightSharedValue.get() const isReady = scrollLayoutHeight !== null && scrollContentHeight !== null const ROUNDING_ERROR_EPSILON = 0.1 isAtTop = isReady && scrollContentHeight - scrollOffsetY - scrollLayoutHeight <= ROUNDING_ERROR_EPSILON } else { isAtTop = scrollOffsetY <= 0 } return isFrozenSharedValue?.get() === true || (isExpandedSharedValue.get() && !isAtTop) }, [ scrollOffsetYSharedValue, isFrozenSharedValue, inverted, scrollContentHeightSharedValue.get, scrollLayoutHeightSharedValue.get, ]) const panGesture = useBottomSheetDragGesture({ id: "scroll", clamped: true, frozenSharedValue: panFrozenSharedValue, }) const nativeGesture = Gesture.Native() const initialScrollOffsetYSharedValue = useSharedValue(0) const scrollControllerGesture = Gesture.Pan() .onBegin(() => { initialScrollOffsetYSharedValue.set(scrollOffsetYSharedValue.get()) }) .onUpdate(() => { if (!isExpandedSharedValue.get()) { scrollTo(scrollRef, 0, initialScrollOffsetYSharedValue.get(), false) } else { initialScrollOffsetYSharedValue.set(scrollOffsetYSharedValue.get()) } }) .failOffsetY(9999999999) .failOffsetX(9999999999) return Gesture.Simultaneous(nativeGesture, panGesture, scrollControllerGesture) }