UNPKG

react-native-navigation-bottom-sheet

Version:

A performant customizable bottom sheet component made on top of wix react-native-navigation library.

236 lines (205 loc) 6.21 kB
import Animated from 'react-native-reanimated'; import { PanGestureHandler, TapGestureHandler, State as GestureState, gestureHandlerRootHOC, } from 'react-native-gesture-handler'; import { runDecay, normalizeSnapPoints } from './utility'; import type {LayoutChangeEvent} from 'react-native'; const { call, cond, greaterThan, lessThan, neq, clockRunning, not, and, set, sub, or, stopClock, lessOrEq, proc, add, max, min, eq, multiply, block, onChange, Value } = Animated; import { AnimatedStoreSheet as ASBS } from "./AnimatedStoreSheet"; /** * Namespace for animated values associated with the scrolling content. */ class AnimatedStoreScrolling { /* Clock used for an animation of a scrolling view (not implemented yet) */ static _scrollingClock = new Animated.Clock(); /* Value of a current dragging postion of content in the scroll view */ static _scrollY: Animated.Value<number> = new Animated.Value(0); /* Value of a current speed of content dragging in the scroll view */ static _velocityScrollY: Animated.Value<number> = new Animated.Value(0); /* State of a gesture for a scroll view */ static _panScrollState: Animated.Value<number> = new Animated.Value( GestureState.END ); static _onGestureEventScrolling = Animated.event([ { nativeEvent: { translationY: this._scrollY, velocityY: this._velocityScrollY, state: this._panScrollState, }, }, ]); static enabledContentGestureInteraction: boolean = true; static contentHeight: Animated.Value<number> = new Animated.Value(0); static headerHeight: number = 0; static snapPoints: readonly number[] = []; static init = ( enabledContentGestureInteraction: boolean, snapPoints: readonly number[] ) => { this.snapPoints = snapPoints; this.enabledContentGestureInteraction = enabledContentGestureInteraction; }; static handleLayoutHeader = ({ nativeEvent: { layout: { height: heightOfHeader }, }, }: LayoutChangeEvent) => { this.headerHeight = heightOfHeader; }; static handleLayoutContent = ({ nativeEvent: { layout: { height }, }, }: LayoutChangeEvent) => { const resultHeight = this.enabledContentGestureInteraction ? height + this.headerHeight - this.snapPoints[this.snapPoints.length - 1] : 0; this.contentHeight.setValue(Math.max(resultHeight, 0)); }; static limitedScroll = proc((val: Animated.Value<number>) => max(min(val, 0), multiply(this.contentHeight, -1)) ); static _prevTransY: Animated.Value<number> = new Animated.Value(0); static _transY: Animated.Value<number> = new Animated.Value(0); static _startedAtTheTop: Animated.Value<number> = new Animated.Value(0); /** * 1 - scrolling content * 2 - sheet dragging */ static _lastState: Animated.Value<number> = new Animated.Value(0); static scrollOffset: Animated.Value<number> = new Animated.Value(0); static scrollOffsetWhileSnapped: Animated.Value<number> = new Animated.Value( 0 ); static _masterScrollY = block([ /** * Debugging section. */ /* onChange( this._prevTransY, call([this._prevTransY], (snapPoints: readonly number[]) => console.log('Changed prevTransY: ' + snapPoints[0]) ) ), onChange( this.scrollOffsetWhileSnapped, call([this.scrollOffsetWhileSnapped], (snapPoints: readonly number[]) => console.log('Changed scrollOffsetWhileSnapped: ' + snapPoints[0]) ) ), onChange( this._transY, call([this._transY], (snapPoints: readonly number[]) => console.log('Changed scrollOffset: ' + snapPoints[0]) ) ), */ /* onChange( this._lastState, call([this._lastState], (snapPoints: readonly number[]) => console.log('Changed state: ' + snapPoints[0]) ) ), */ cond(eq(this._lastState, 2), [ set( ASBS._scrollToDragVal, sub(this._scrollY, this.scrollOffsetWhileSnapped) ), /** * Setting scrollOffset is not required if the bottom sheet is * at the very top. * TODO: describe different cases this solution solves */ cond(not(this._startedAtTheTop), [ set( this.scrollOffset, sub(this._scrollY, this.scrollOffsetWhileSnapped) ), ]), set(ASBS._velocityY, this._velocityScrollY), ]), cond(eq(this._lastState, 1), [ /** * During scrolling we need to record content offset and then use it when * calculating ASBS._scrollToDragVal value, so that dragging doesn't * include unnecessary scrolling position value. */ set(this.scrollOffsetWhileSnapped, this._scrollY), /** * Contrary to the previous line of code this one covers the special case * when we started dragging the bottom sheet not from the top. Here we need to * include this._prevTransY as an offset. */ cond( not(this._startedAtTheTop), set(this.scrollOffsetWhileSnapped, multiply(-1, this._prevTransY)) ), ]), cond( or( eq(this._panScrollState, GestureState.ACTIVE), eq(this._panScrollState, GestureState.BEGAN) ), [ // TODO: stop draggin sheet clock stopClock(this._scrollingClock), set( this._transY, this.limitedScroll( add( this._scrollY, this._prevTransY, multiply(-1, this.scrollOffset) ) as Animated.Value<number> ) ), this._transY, ], [ set(this.scrollOffset, 0), set(this.scrollOffsetWhileSnapped, 0), set(this._scrollY, 0), cond( eq(this._lastState, 1), runDecay( this._scrollingClock, this._transY, this._velocityScrollY, this.contentHeight ), ), set(this._velocityScrollY, 0), set(this._prevTransY, this._transY), this._transY, ] ), this._transY, ]); } export { AnimatedStoreScrolling };