From 6f36e9ceb117d2582ab6158399a1e83277adbdd4 Mon Sep 17 00:00:00 2001 From: Mo Gorhom Date: Sat, 19 Sep 2020 23:43:28 +0200 Subject: [PATCH] refactor: rewrite code base to use reanimated v2 --- example/src/screens/MapExample.tsx | 66 ++-- example/src/screens/ShadowOverlayExample.tsx | 33 +- src/components/bottomSheet/BottomSheet.tsx | 351 ++++++------------ src/components/bottomSheet/types.d.ts | 6 +- src/components/bottomSheet/useTransition.ts | 206 ---------- .../contentWrapper/ContentWrapper.android.tsx | 6 +- .../contentWrapper/ContentWrapper.ios.tsx | 22 +- .../contentWrapper/TapGestureHandler.tsx | 8 - src/components/contentWrapper/styles.ts | 10 + src/components/contentWrapper/types.d.ts | 5 +- .../contentWrapper/useTapGestureHandler.ts | 32 -- .../draggableView/DraggableView.tsx | 8 +- src/components/flatList/FlatList.tsx | 14 +- src/components/handle/Handle.tsx | 9 +- src/components/scrollView/ScrollView.tsx | 26 +- src/components/sectionList/SectionList.tsx | 15 +- src/components/view/View.tsx | 3 +- src/constants.ts | 16 +- src/contexts/internal.ts | 16 +- src/hooks/index.ts | 5 +- src/hooks/useAnimatedGestureHandler.ts | 214 ----------- src/hooks/useInteractivePanGestureHandler.ts | 82 ++++ src/hooks/usePanGestureHandler.ts | 89 ----- src/hooks/useScrollable.ts | 17 +- .../useScrollableAnimatedProps.android.ts | 24 +- src/hooks/useScrollableInternal.ts | 74 ++-- src/hooks/useTapGestureHandler.ts | 37 ++ src/types.ts | 1 + src/utilities/clamp.ts | 8 - src/utilities/index.ts | 1 - 30 files changed, 465 insertions(+), 939 deletions(-) delete mode 100644 src/components/bottomSheet/useTransition.ts delete mode 100644 src/components/contentWrapper/TapGestureHandler.tsx create mode 100644 src/components/contentWrapper/styles.ts delete mode 100644 src/components/contentWrapper/useTapGestureHandler.ts delete mode 100644 src/hooks/useAnimatedGestureHandler.ts create mode 100644 src/hooks/useInteractivePanGestureHandler.ts delete mode 100644 src/hooks/usePanGestureHandler.ts create mode 100644 src/hooks/useTapGestureHandler.ts delete mode 100644 src/utilities/clamp.ts diff --git a/example/src/screens/MapExample.tsx b/example/src/screens/MapExample.tsx index e0d3df22..bfc78681 100644 --- a/example/src/screens/MapExample.tsx +++ b/example/src/screens/MapExample.tsx @@ -8,13 +8,18 @@ import { } from 'react-native'; import MapView from 'react-native-maps'; import { BlurView } from '@react-native-community/blur'; -import Animated, { interpolate, Extrapolate } from 'react-native-reanimated'; -import { useValue } from 'react-native-redash'; +import Animated, { + interpolate, + Extrapolate, + useSharedValue, + useAnimatedStyle, +} from 'react-native-reanimated'; import { useSafeArea } from 'react-native-safe-area-context'; import BottomSheet, { BottomSheetScrollView } from '@gorhom/bottom-sheet'; import SearchHandle from '../components/searchHandle'; import LocationItem from '../components/locationItem'; import { createLocationListMockData } from '../utils'; +import { clamp } from 'react-native-redash'; const { height: SCREEN_HEIGHT } = Dimensions.get('window'); @@ -33,7 +38,7 @@ const MapExample = () => { ], [topSafeArea] ); - const position = useValue(0); + const animatedPosition = useSharedValue(0); // callbacks const handleSheetChanges = useCallback((index: number) => { @@ -47,27 +52,34 @@ const MapExample = () => { }, []); // styles - const locationButtonStyle = useMemo( - () => ({ - ...styles.locationButton, - transform: [ - { - translateY: interpolate(position, { - inputRange: [200, 350], - outputRange: [-200, -350], - extrapolate: Extrapolate.CLAMP, - }), - }, - { - scale: interpolate(position, { - inputRange: [350, 400], - outputRange: [1, 0], - extrapolate: Extrapolate.CLAMP, - }), - }, - ], - }), - // eslint-disable-next-line react-hooks/exhaustive-deps + const locationButtonAnimatedStyle = useAnimatedStyle( + () => { + console.log( + 'X', + animatedPosition.value - snapPoints[snapPoints.length - 1] + ); + return { + transform: [ + { + translateY: clamp( + animatedPosition.value - snapPoints[snapPoints.length - 1], + -350, + -200 + ), + }, + { + scale: interpolate( + animatedPosition.value - snapPoints[snapPoints.length - 1], + [-350, -400], + [1, 0], + Extrapolate.CLAMP + ), + }, + ], + }; + }, + /** @TODO this should be fixed with reanimated alpha 7 */ + // @ts-ignore [] ); @@ -98,13 +110,15 @@ const MapExample = () => { onTouchStart={handleTouchStart} onRegionChangeComplete={handleRegionChangeComplete} /> - + { // variables const snapPoints = useMemo(() => ['25%', '50%', '90%'], []); - const animatedPositionIndex = useValue(0); + const animatedPositionIndex = useSharedValue(0); // styles - const shadowOverlayStyle = useMemo( + + const shadowOverlayAnimatedStyle = useAnimatedStyle( () => ({ - ...styles.shadowOverlay, - opacity: interpolate(animatedPositionIndex, { - inputRange: [0, 2], - outputRange: [0, 1], - extrapolate: Extrapolate.CLAMP, - }), + opacity: interpolate( + animatedPositionIndex.value, + [0, 2], + [0, 1], + Extrapolate.CLAMP + ), }), - // eslint-disable-next-line react-hooks/exhaustive-deps + /** @TODO this should be fixed with reanimated alpha 7 */ + // @ts-ignore [] ); @@ -88,7 +94,10 @@ const ShadowOverlayExample = () => { style={styles.buttonContainer} onPress={() => handleClosePress()} /> - + ( ( { @@ -83,7 +63,7 @@ const BottomSheetComponent = forwardRef( initialSnapIndex = 0, snapPoints: _snapPoints, topInset = 0, - // animated nodes callback + // animated callback shared values animatedPosition: _animatedPosition, animatedPositionIndex: _animatedPositionIndex, // callbacks @@ -143,12 +123,10 @@ const BottomSheetComponent = forwardRef( ); //#endregion - //#region refs + //#region variables const currentPositionIndexRef = useRef(initialSnapIndex); - const handlePanGestureRef = useRef(null); - //#endregion - //#region variables + // scrollable variables const { scrollableContentOffsetY, scrollableDecelerationRate, @@ -174,12 +152,14 @@ const BottomSheetComponent = forwardRef( }, [initialSnapIndex, sheetHeight, snapPoints]); // content wrapper - const contentWrapperTapGestureRef = useRef(null); - const contentWrapperTapGestureState = useSharedValue( + const contentWrapperGestureRef = useRef(null); + const contentWrapperGestureState = useSharedValue( State.UNDETERMINED ); - const contentWrapperInitialMaxDeltaY = - snapPoints[Math.max(initialSnapIndex, 0)]; + const contentWrapperMaxDeltaY = useMemo( + () => snapPoints[Math.max(initialSnapIndex, 0)], + [snapPoints, initialSnapIndex] + ); //#endregion //#region private methods @@ -187,16 +167,14 @@ const BottomSheetComponent = forwardRef( const currentPositionIndex = Math.max(currentPositionIndexRef.current, 0); if (currentPositionIndex === snapPoints.length - 1) { - console.log('refreshUIElements', 'sheet extended'); flashScrollableIndicators(); // @ts-ignore - contentWrapperTapGestureRef.current.setNativeProps({ + contentWrapperGestureRef.current.setNativeProps({ maxDeltaY: 0, }); } else { - console.log('refreshUIElements', 'sheet collapse'); // @ts-ignore - contentWrapperTapGestureRef.current.setNativeProps({ + contentWrapperGestureRef.current.setNativeProps({ maxDeltaY: snapPoints[currentPositionIndex], }); } @@ -215,23 +193,31 @@ const BottomSheetComponent = forwardRef( ); //#endregion - //#region gestures - const lastActiveGesture = useSharedValue(GESTURE.UNDETERMINED); - + //#region gesture interaction / animation + // variables + const animationState = useSharedValue(ANIMATION_STATE.UNDETERMINED); const animatedPosition = useSharedValue(initialPosition); - const animatedPositionIndex = useDerivedValue(() => - interpolate( - animatedPosition.value, - snapPoints.slice().reverse(), - snapPoints - .slice() - .map((_, index) => index) - .reverse() - ) + const animatedPositionIndex = useDerivedValue( + () => + interpolate( + animatedPosition.value, + snapPoints.slice().reverse(), + snapPoints + .slice() + .map((_, index) => index) + .reverse(), + Extrapolate.CLAMP + ), + /** @TODO this should be fixed with reanimated alpha 7 */ + // @ts-ignore + [snapPoints] ); + // callbacks const animateToPointCompleted = useCallback( isFinished => { + animationState.value = ANIMATION_STATE.STOPPED; + if (!isFinished) { return; } @@ -245,83 +231,45 @@ const BottomSheetComponent = forwardRef( refreshUIElements(); } }, - [animatedPositionIndex, handleOnChange, refreshUIElements] + // eslint-disable-next-line react-hooks/exhaustive-deps + [handleOnChange, refreshUIElements] ); const animateToPoint = useCallback( (point: number) => { 'worklet'; - - // console.log('animateToPoint', `point: ${point}`); + animationState.value = ANIMATION_STATE.RUNNING; animatedPosition.value = withTiming( point, { - duration: 250, - easing: Easing.out(Easing.quad), + duration: animationDuration, + easing: animationEasing, }, animateToPointCompleted ); }, - [animatedPosition, animateToPointCompleted] + // eslint-disable-next-line react-hooks/exhaustive-deps + [animationDuration, animationEasing, animateToPointCompleted] ); - const [ - handlePanGestureState, - handlePanGestureTranslationY, - handlePanGestureVelocityY, - handlePanGestureHandler, - ] = usePanGestureHandler({ - type: GESTURE.HANDLE, + // hooks + const [handlePanGestureHandler] = useInteractivePanGestureHandler( + GESTURE.HANDLE, animatedPosition, snapPoints, - lastActiveGesture, - animateToPoint, - }); + animateToPoint + ); - const [ - contentPanGestureState, - contentPanGestureTranslationY, - contentPanGestureVelocityY, - contentPanGestureHandler, - ] = usePanGestureHandler({ - type: GESTURE.CONTENT, + const [contentPanGestureHandler] = useInteractivePanGestureHandler( + GESTURE.CONTENT, animatedPosition, snapPoints, - lastActiveGesture, animateToPoint, - offset: scrollableContentOffsetY, - }); - - const autoSnapTo = useValue(-1); - //#endregion - - //#region animation - - //#endregion - - //#region styles - const containerStyle = useMemo( - () => ({ - ...styles.container, - height: sheetHeight, - }), - [sheetHeight] - ); - const contentContainerStyle = useMemo>( - () => ({ - ...styles.container, - height: sheetHeight, - }), - [sheetHeight] + scrollableContentOffsetY ); - const contentContainerAnimatedStyle = useAnimatedStyle(() => { - return { - transform: [{ translateY: animatedPosition.value }], - }; - }); //#endregion - //#region methods + //#region public methods const handleSnapTo = useCallback( (index: number) => { invariant( @@ -330,37 +278,34 @@ const BottomSheetComponent = forwardRef( snapPoints.length - 1 }` ); - autoSnapTo.setValue(snapPoints[index]); + animateToPoint(snapPoints[index]); }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [snapPoints] + [animateToPoint, snapPoints] ); const handleClose = useCallback(() => { - autoSnapTo.setValue(sheetHeight); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sheetHeight]); + animateToPoint(sheetHeight); + }, [animateToPoint, sheetHeight]); const handleExpand = useCallback(() => { - autoSnapTo.setValue(snapPoints[snapPoints.length - 1]); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sheetHeight]); + animateToPoint(snapPoints[snapPoints.length - 1]); + }, [animateToPoint, snapPoints]); const handleCollapse = useCallback(() => { - autoSnapTo.setValue(snapPoints[0]); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sheetHeight]); + animateToPoint(snapPoints[0]); + }, [animateToPoint, snapPoints]); + useImperativeHandle(ref, () => ({ + snapTo: handleSnapTo, + expand: handleExpand, + collapse: handleCollapse, + close: handleClose, + })); //#endregion - //#region + //#region contexts variables const internalContextVariables = useMemo( () => ({ - contentWrapperTapGestureRef, - handlePanGestureState, - handlePanGestureTranslationY, - handlePanGestureVelocityY, - contentPanGestureState, - contentPanGestureTranslationY, - contentPanGestureVelocityY, - contentPanGestureHandler, animatedPosition, + animationState, + contentWrapperGestureRef, + contentPanGestureHandler, scrollableContentOffsetY, scrollableDecelerationRate, setScrollableRef: handleSettingScrollableRef, @@ -380,106 +325,50 @@ const BottomSheetComponent = forwardRef( ); //#endregion - //#region effects - useImperativeHandle(ref, () => ({ - snapTo: handleSnapTo, - expand: handleExpand, - collapse: handleCollapse, - close: handleClose, - })); + //#region styles + const contentContainerStyle = useMemo>( + () => ({ + ...styles.container, + height: sheetHeight, + }), + [sheetHeight] + ); + const contentContainerAnimatedStyle = useAnimatedStyle(() => { + return { + transform: [{ translateY: animatedPosition.value }], + }; + /** @TODO this should be fixed with reanimated alpha 7 */ + // @ts-ignore + }, []); + //#endregion - /** - * @DEV - * here we track the current position and - * - call on change ( if provided ). - * - flash scrollable component scroll indicators. - * - manipulate the root tap gesture handler maxDeltaY, - * which allows the scrollable component to be activated. - */ - // useCode( - // () => - // onChange(animatedPosition, [ - // call([currentPosition.value], args => { - // const currentPositionIndex = snapPoints.indexOf(args[0]); - - // /** - // * if animation was interrupted, we ignore the change. - // */ - // if (currentPositionIndex === -1) { - // return; - // } - // currentPositionIndexRef.current = currentPositionIndex; - // handleOnChange(currentPositionIndex); - // refreshUIElements(); - // }), - // ]), - // [snapPoints, refreshUIElements] - // ); - - /** - * @DEV - * Once the root tap gesture handler states change to failed - * and the sheet not fully extended, we make sure to prevent the - * scrollable component from scrolling. - */ - // useDerivedValue(() => { - // // eq(tapGestureState, State.FAILED), - // // eq(currentGesture, GESTURE.CONTENT), - // // neq(position, 0) - - // // console.log( - // // `lastActiveGesture: ${lastActiveGesture.value}`, - // // `TapGestureState: ${contentWrapperTapGestureState.value}`, - // // `animatedPosition: ${animatedPosition.value}`, - // // `scrollableContentOffsetY: ${scrollableContentOffsetY.value}`, - // // lastActiveGesture.value === GESTURE.CONTENT && - // // animatedPosition.value !== 0 && - // // scrollableContentOffsetY.value !== 0 - // // // lastActiveGesture.value === GESTURE.CONTENT && - // // // contentWrapperTapGestureState.value === State.FAILED && - // // // animatedPosition.value !== 0 - // // ); - - // if ( - // lastActiveGesture.value === GESTURE.CONTENT && - // animatedPosition.value !== 0 && - // scrollableContentOffsetY.value !== 0 - // ) { - // scrollToTop(); - // } - // // if ( - // // lastActiveGesture.value === GESTURE.CONTENT && - // // contentWrapperTapGestureState.value === State.FAILED && - // // animatedPosition.value !== 0 - // // ) { - // // // scrollToTop(); - // // } - - // return animatedPosition.value; - // }); - // useCode( - // () => - // cond( - // and( - // eq(tapGestureState.value, State.FAILED), - // neq(animatedPosition.value, 0) - // ), - // call([], () => { - // scrollToTop(); - // }) - // ), - // [] - // ); + //#region effects + useAnimatedReaction( + () => (_animatedPosition ? animatedPosition.value : null), + (value: number | null) => { + if (value) { + _animatedPosition!.value = value; + } + } + ); + useAnimatedReaction( + () => (_animatedPositionIndex ? animatedPositionIndex.value : null), + (value: number | null) => { + if (value) { + _animatedPositionIndex!.value = value; + } + } + ); //#endregion // render return ( <> ( )} @@ -503,23 +391,20 @@ const BottomSheetComponent = forwardRef( - {children} - {/* */} + {typeof children === 'function' ? children() : children} - + /> */} {/* {_animatedPosition && ( */ - animatedPosition?: Animated.Value; + animatedPosition?: Animated.SharedValue; /** * Animated value to be used as a callback for the position index node internally. * @type Animated.Value */ - animatedPositionIndex?: Animated.Value; + animatedPositionIndex?: Animated.SharedValue; /** * Component to be placed as a sheet handle. * @see {BottomSheetHandleProps} @@ -53,7 +53,7 @@ export interface BottomSheetProps extends BottomSheetAnimationConfigs { * A scrollable node or normal view. * @type React.ReactNode[] | React.ReactNode */ - children: React.ReactNode[] | React.ReactNode; + children: () => React.ReactNode | React.ReactNode[] | React.ReactNode; } export interface BottomSheetAnimationConfigs { diff --git a/src/components/bottomSheet/useTransition.ts b/src/components/bottomSheet/useTransition.ts deleted file mode 100644 index e46835f3..00000000 --- a/src/components/bottomSheet/useTransition.ts +++ /dev/null @@ -1,206 +0,0 @@ -import { useMemo } from 'react'; -import Animated, { - eq, - set, - add, - greaterOrEq, - lessOrEq, - and, - not, - clockRunning, - startClock, - timing, - stopClock, - multiply, - neq, - onChange, - or, - cond, - block, - useSharedValue, - // debug, -} from 'react-native-reanimated'; -import { State } from 'react-native-gesture-handler'; -import { useClock, useValue, snapPoint } from 'react-native-redash'; -import type { BottomSheetAnimationConfigs } from './types'; -import { GESTURE } from '../../constants'; - -interface TransitionProps extends Required { - contentPanGestureState: Animated.SharedValue; - contentPanGestureTranslationY: Animated.SharedValue; - contentPanGestureVelocityY: Animated.SharedValue; - - handlePanGestureState: Animated.SharedValue; - handlePanGestureTranslationY: Animated.SharedValue; - handlePanGestureVelocityY: Animated.SharedValue; - - autoSnapTo: Animated.SharedValue; - scrollableContentOffsetY: Animated.Value; - snapPoints: number[]; - initialPosition: number; -} - -export const useTransition = ({ - handlePanGestureTranslationY, -}: TransitionProps) => { - const currentGesture = useValue(GESTURE.UNDETERMINED); - const currentPosition = useValue(initialPosition); - - const isPanningContent = eq(contentPanGestureState, State.ACTIVE); - const isPanningHandle = eq(handlePanGestureState, State.ACTIVE); - const isPanning = or(isPanningContent, isPanningHandle); - const shouldAnimate = useValue(0); - - const clock = useClock(); - const config = useMemo( - () => ({ - toValue: new Animated.Value(0), - duration: animationDuration, - easing: animationEasing, - }), - [animationEasing, animationDuration] - ); - - const animationState = useMemo( - () => ({ - finished: new Animated.Value(0), - position: new Animated.Value(initialPosition), - frameTime: new Animated.Value(0), - time: new Animated.Value(0), - }), - [initialPosition] - ); - - const finishTiming = useMemo( - () => [ - set(currentGesture, GESTURE.UNDETERMINED), - set(shouldAnimate, 0), - set(currentPosition, config.toValue), - set(animationState.frameTime, 0), - set(animationState.time, 0), - stopClock(clock), - ], - // eslint-disable-next-line react-hooks/exhaustive-deps - [] - ); - - const translateY = cond( - eq(currentGesture, GESTURE.CONTENT), - cond( - eq(currentPosition, 0), - add( - contentPanGestureTranslationY, - multiply(scrollableContentOffsetY, -1) - ), - contentPanGestureTranslationY - ), - handlePanGestureTranslationY - ); - const velocityY = cond( - eq(currentGesture, GESTURE.CONTENT), - contentPanGestureVelocityY, - handlePanGestureVelocityY - ); - const isAnimationInterrupted = and( - clockRunning(clock), - or(isPanning, neq(autoSnapTo, -1)) - ); - const position = block([ - /** - * In case animation get interrupted, we execute the finishTiming node and - * set current position the the animated position. - */ - cond(isAnimationInterrupted, [ - // debug('animation interrupted', isAnimationInterrupted), - finishTiming, - set(currentPosition, animationState.position), - ]), - - /** - * Panning node - */ - cond(isPanning, [ - set( - currentGesture, - cond(isPanningContent, GESTURE.CONTENT, GESTURE.HANDLE) - ), - // debug('start panning', translateY), - cond( - not(greaterOrEq(add(currentPosition, translateY), 0)), - [set(animationState.position, 0), set(animationState.finished, 0)], - cond( - not(lessOrEq(add(currentPosition, translateY), snapPoints[0])), - [ - set(animationState.position, snapPoints[0]), - set(animationState.finished, 0), - ], - [ - set(animationState.position, add(currentPosition, translateY)), - set(animationState.finished, 0), - ] - ) - ), - ]), - - /** - * Gesture ended node. - */ - onChange( - add(contentPanGestureState, handlePanGestureState), - cond( - or( - and( - eq(currentGesture, GESTURE.CONTENT), - eq(contentPanGestureState, State.END) - ), - and( - neq(currentGesture, GESTURE.CONTENT), - eq(handlePanGestureState, State.END) - ) - ), - [ - set( - config.toValue, - snapPoint(add(currentPosition, translateY), velocityY, snapPoints) - ), - set(shouldAnimate, 1), - ] - ) - ), - /** - * Manual snapping node. - */ - onChange(autoSnapTo, [ - cond(neq(autoSnapTo, -1), [ - // debug('Manually snap to', autoSnapTo), - set(config.toValue, autoSnapTo), - set(autoSnapTo, -1), - set(animationState.finished, 0), - set(shouldAnimate, 1), - ]), - ]), - - /** - * Animation Node. - */ - cond(shouldAnimate, [ - // debug('start animating', shouldAnimate), - cond(and(not(clockRunning(clock)), not(animationState.finished)), [ - set(animationState.finished, 0), - set(animationState.frameTime, 0), - set(animationState.time, 0), - startClock(clock), - ]), - timing(clock, animationState, config), - cond(animationState.finished, finishTiming), - ]), - - animationState.position, - ]); - - return { - position: handlePanGestureTranslationY, - currentPosition, - currentGesture, - }; -}; diff --git a/src/components/contentWrapper/ContentWrapper.android.tsx b/src/components/contentWrapper/ContentWrapper.android.tsx index 691d1254..2a6311f2 100644 --- a/src/components/contentWrapper/ContentWrapper.android.tsx +++ b/src/components/contentWrapper/ContentWrapper.android.tsx @@ -1,13 +1,13 @@ import React, { forwardRef, memo } from 'react'; import isEqual from 'lodash.isequal'; import { TapGestureHandler } from 'react-native-gesture-handler'; -import { useTapGestureHandler } from './useTapGestureHandler'; +import { useTapGestureHandler } from '../../hooks/useTapGestureHandler'; import type { BottomSheetContentWrapperProps } from './types'; const ContentWrapperComponent = forwardRef< TapGestureHandler, BottomSheetContentWrapperProps ->(({ gestureState, initialMaxDeltaY, children }, ref) => { +>(({ gestureState, maxDeltaY, children }, ref) => { // callbacks const handleGestureEvent = useTapGestureHandler(gestureState); @@ -15,7 +15,7 @@ const ContentWrapperComponent = forwardRef< (({ gestureState, initialMaxDeltaY, children, style }, ref) => { +>(({ gestureState, maxDeltaY, height, children }, ref) => { // callbacks const handleGestureEvent = useTapGestureHandler(gestureState); + // styles + const containerStyle = useMemo( + () => [ + styles.container, + { + height, + }, + ], + [height] + ); + return ( - + {children} diff --git a/src/components/contentWrapper/TapGestureHandler.tsx b/src/components/contentWrapper/TapGestureHandler.tsx deleted file mode 100644 index 7959eca8..00000000 --- a/src/components/contentWrapper/TapGestureHandler.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { TapGestureHandler as RawTapGestureHandler } from 'react-native-gesture-handler'; -import Animated from 'react-native-reanimated'; - -const TapGestureHandler = Animated.createAnimatedComponent( - RawTapGestureHandler -); - -export default (TapGestureHandler as any) as typeof RawTapGestureHandler; diff --git a/src/components/contentWrapper/styles.ts b/src/components/contentWrapper/styles.ts new file mode 100644 index 00000000..78912155 --- /dev/null +++ b/src/components/contentWrapper/styles.ts @@ -0,0 +1,10 @@ +import { StyleSheet } from 'react-native'; + +export const styles = StyleSheet.create({ + container: { + position: 'absolute', + left: 0, + right: 0, + bottom: 0, + }, +}); diff --git a/src/components/contentWrapper/types.d.ts b/src/components/contentWrapper/types.d.ts index 6b4fe674..552c04ce 100644 --- a/src/components/contentWrapper/types.d.ts +++ b/src/components/contentWrapper/types.d.ts @@ -1,12 +1,11 @@ import type { RefAttributes } from 'react'; -import type { StyleProp, ViewStyle } from 'react-native'; import type { TapGestureHandler, State } from 'react-native-gesture-handler'; import type Animated from 'react-native-reanimated'; export type BottomSheetContentWrapperProps = { gestureState: Animated.SharedValue; - initialMaxDeltaY: number; - style: StyleProp; + maxDeltaY: number; + height: number; children: React.ReactNode; }; diff --git a/src/components/contentWrapper/useTapGestureHandler.ts b/src/components/contentWrapper/useTapGestureHandler.ts deleted file mode 100644 index 658c721e..00000000 --- a/src/components/contentWrapper/useTapGestureHandler.ts +++ /dev/null @@ -1,32 +0,0 @@ -import Animated, { useAnimatedGestureHandler } from 'react-native-reanimated'; -import { - State, - TapGestureHandlerGestureEvent, -} from 'react-native-gesture-handler'; - -export const useTapGestureHandler = ( - gestureState: Animated.SharedValue -) => { - const handleGestureEvent = useAnimatedGestureHandler({ - onStart: ({ state }) => { - gestureState.value = state; - }, - onActive: ({ state }) => { - gestureState.value = state; - }, - onCancel: ({ state }) => { - gestureState.value = state; - }, - onEnd: ({ state }) => { - gestureState.value = state; - }, - onFail: ({ state }) => { - gestureState.value = state; - }, - onFinish: ({ state }) => { - gestureState.value = state; - }, - }); - - return handleGestureEvent as (event: TapGestureHandlerGestureEvent) => void; -}; diff --git a/src/components/draggableView/DraggableView.tsx b/src/components/draggableView/DraggableView.tsx index cea6368d..2610dfec 100644 --- a/src/components/draggableView/DraggableView.tsx +++ b/src/components/draggableView/DraggableView.tsx @@ -17,7 +17,7 @@ const BottomSheetDraggableViewComponent = ({ // hooks const { - contentWrapperTapGestureRef, + contentWrapperGestureRef, contentPanGestureHandler, } = useBottomSheetInternal(); @@ -25,9 +25,9 @@ const BottomSheetDraggableViewComponent = ({ const simultaneousHandlers = useMemo( () => nativeGestureRef - ? [contentWrapperTapGestureRef, nativeGestureRef] - : contentWrapperTapGestureRef, - [contentWrapperTapGestureRef, nativeGestureRef] + ? [contentWrapperGestureRef, nativeGestureRef] + : contentWrapperGestureRef, + [contentWrapperGestureRef, nativeGestureRef] ); // styles diff --git a/src/components/flatList/FlatList.tsx b/src/components/flatList/FlatList.tsx index 8be53bf6..ee8da95b 100644 --- a/src/components/flatList/FlatList.tsx +++ b/src/components/flatList/FlatList.tsx @@ -29,6 +29,8 @@ const AnimatedFlatList = Animated.createAnimatedComponent( any >; +const BottomSheetFlatListName = 'FlatList'; + const BottomSheetFlatListComponent = forwardRef( (props: BottomSheetFlatListProps, ref: Ref) => { // props @@ -40,10 +42,11 @@ const BottomSheetFlatListComponent = forwardRef( // hooks const { scrollableRef, + scrollableAnimatedProps, handleScrollEvent, handleSettingScrollable, - } = useScrollableInternal('FlatList'); - const { rootTapGestureRef, decelerationRate } = useBottomSheetInternal(); + } = useScrollableInternal(BottomSheetFlatListName); + const { contentWrapperGestureRef } = useBottomSheetInternal(); // effects // @ts-ignore @@ -54,12 +57,11 @@ const BottomSheetFlatListComponent = forwardRef( return ( diff --git a/src/components/handle/Handle.tsx b/src/components/handle/Handle.tsx index fadbfd1b..44c031c6 100644 --- a/src/components/handle/Handle.tsx +++ b/src/components/handle/Handle.tsx @@ -5,12 +5,9 @@ import { styles } from './styles'; const BottomSheetHandleComponent = () => { return ( - <> - - - - - + + + ); }; diff --git a/src/components/scrollView/ScrollView.tsx b/src/components/scrollView/ScrollView.tsx index c2a34212..41e3caa8 100644 --- a/src/components/scrollView/ScrollView.tsx +++ b/src/components/scrollView/ScrollView.tsx @@ -15,11 +15,7 @@ import isEqual from 'lodash.isequal'; import Animated from 'react-native-reanimated'; import { NativeViewGestureHandler } from 'react-native-gesture-handler'; import DraggableView from '../draggableView'; -import { - useScrollableInternal, - useBottomSheetInternal, - useScrollableAnimatedProps, -} from '../../hooks'; +import { useScrollableInternal, useBottomSheetInternal } from '../../hooks'; import type { BottomSheetScrollViewType, BottomSheetScrollViewProps, @@ -33,9 +29,7 @@ const AnimatedScrollView = Animated.createAnimatedComponent( any >; -Animated.addWhitelistedUIProps({ - decelerationRate: true, -}); +const BottomSheetScrollViewName = 'ScrollView'; const BottomSheetScrollViewComponent = forwardRef( (props: BottomSheetScrollViewProps, ref: Ref) => { @@ -48,11 +42,11 @@ const BottomSheetScrollViewComponent = forwardRef( // hooks const { scrollableRef, + scrollableAnimatedProps, handleScrollEvent, handleSettingScrollable, - } = useScrollableInternal('ScrollView'); - const { contentWrapperTapGestureRef } = useBottomSheetInternal(); - const animatedProps = useScrollableAnimatedProps(); + } = useScrollableInternal(BottomSheetScrollViewName); + const { contentWrapperGestureRef } = useBottomSheetInternal(); // effects // @ts-ignore @@ -62,22 +56,22 @@ const BottomSheetScrollViewComponent = forwardRef( return ( diff --git a/src/components/sectionList/SectionList.tsx b/src/components/sectionList/SectionList.tsx index 7f083fae..7b343eea 100644 --- a/src/components/sectionList/SectionList.tsx +++ b/src/components/sectionList/SectionList.tsx @@ -29,6 +29,8 @@ const AnimatedSectionList = Animated.createAnimatedComponent( any >; +const BottomSheetSectionListName = 'SectionList'; + const BottomSheetSectionListComponent = forwardRef( (props: BottomSheetSectionListProps, ref: Ref) => { // props @@ -40,10 +42,12 @@ const BottomSheetSectionListComponent = forwardRef( // hooks const { scrollableRef, + scrollableAnimatedProps, handleScrollEvent, handleSettingScrollable, - } = useScrollableInternal('SectionList'); - const { rootTapGestureRef, decelerationRate } = useBottomSheetInternal(); + } = useScrollableInternal(BottomSheetSectionListName); + const { contentWrapperGestureRef } = useBottomSheetInternal(); + // effects // @ts-ignore useImperativeHandle(ref, () => scrollableRef.current!.getNode()); @@ -53,12 +57,11 @@ const BottomSheetSectionListComponent = forwardRef( return ( diff --git a/src/components/view/View.tsx b/src/components/view/View.tsx index aa2cf2aa..71cbfdf9 100644 --- a/src/components/view/View.tsx +++ b/src/components/view/View.tsx @@ -25,7 +25,8 @@ const BottomSheetViewComponent = ({ // callback const handleSettingScrollable = useCallback(() => { - // scrollableContentOffsetY.setValue(0); + 'worklet'; + scrollableContentOffsetY.value = 0; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); diff --git a/src/constants.ts b/src/constants.ts index 6c7b5986..a5764c9b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,13 +1,7 @@ -import Animated from 'react-native-reanimated'; - -const { - Easing: EasingV1, - EasingNode: EasingV2, -} = require('react-native-reanimated'); -const Easing = EasingV2 || EasingV1; +import Animated, { Easing } from 'react-native-reanimated'; export const DEFAULT_ANIMATION_EASING: Animated.EasingFunction = Easing.out( - Easing.back(0.75) + Easing.exp ); export const DEFAULT_ANIMATION_DURATION = 500; @@ -16,3 +10,9 @@ export enum GESTURE { CONTENT, HANDLE, } + +export enum ANIMATION_STATE { + UNDETERMINED = 0, + RUNNING, + STOPPED, +} diff --git a/src/contexts/internal.ts b/src/contexts/internal.ts index 54d3ad5d..86d96fef 100644 --- a/src/contexts/internal.ts +++ b/src/contexts/internal.ts @@ -1,16 +1,14 @@ import { createContext, Ref, RefObject } from 'react'; -import { TapGestureHandler, State } from 'react-native-gesture-handler'; +import type { TapGestureHandler } from 'react-native-gesture-handler'; import type Animated from 'react-native-reanimated'; -import { Scrollable, ScrollableRef } from '../types'; +import { ANIMATION_STATE } from '../constants'; +import type { Scrollable, ScrollableRef } from '../types'; export type BottomSheetInternalContextType = { - contentWrapperTapGestureRef: Ref; - contentPanGestureState: Animated.SharedValue; - contentPanGestureTranslationY: Animated.SharedValue; - contentPanGestureVelocityY: Animated.SharedValue; - handlePanGestureState: Animated.SharedValue; - handlePanGestureTranslationY: Animated.SharedValue; - handlePanGestureVelocityY: Animated.SharedValue; + animatedPosition: Animated.SharedValue; + animationState: Animated.SharedValue; + contentWrapperGestureRef: Ref; + contentPanGestureHandler: any; scrollableContentOffsetY: Animated.SharedValue; scrollableDecelerationRate: Animated.SharedValue; setScrollableRef: (ref: ScrollableRef) => void; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index a57134e5..68276037 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -2,6 +2,5 @@ export { useBottomSheetInternal } from './useBottomSheetInternal'; export { useScrollable } from './useScrollable'; export { useScrollableInternal } from './useScrollableInternal'; export { useStableCallback } from './useStableCallback'; -export { usePanGestureHandler } from './usePanGestureHandler'; -export { useAnimatedGestureHandler } from './useAnimatedGestureHandler'; -export { useScrollableAnimatedProps } from './useScrollableAnimatedProps'; +export { useInteractivePanGestureHandler } from './useInteractivePanGestureHandler'; +export { useTapGestureHandler } from './useTapGestureHandler'; diff --git a/src/hooks/useAnimatedGestureHandler.ts b/src/hooks/useAnimatedGestureHandler.ts deleted file mode 100644 index fb03753a..00000000 --- a/src/hooks/useAnimatedGestureHandler.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { useRef, useCallback } from 'react'; -import { Platform } from 'react-native'; -import Animated, { - // @ts-ignore - makeRemote, - // @ts-ignore - useEvent, - useDerivedValue, -} from 'react-native-reanimated'; -import { - GestureHandlerGestureEvent, -} from 'react-native-gesture-handler'; - -function useRemoteContext(initialValue: T) { - const initRef = useRef<{ context: T } | null>(null); - if (initRef.current === null) { - initRef.current = { - context: makeRemote(initialValue ?? {}), - }; - } - const { context } = initRef.current; - - return context; -} - -export function useDiff(sharedValue: Animated.SharedValue) { - const context = useRemoteContext({ - stash: 0, - prev: null, - }); - - return useDerivedValue(() => { - context.stash = - context.prev !== null ? sharedValue.value - (context.prev ?? 0) : 0; - context.prev = sharedValue.value; - - return context.stash; - }); -} - -export function diff(context: any, name: string, value: any) { - 'worklet'; - - if (!context.___diffs) { - context.___diffs = {}; - } - - if (!context.___diffs[name]) { - context.___diffs[name] = { - stash: 0, - prev: null, - }; - } - - const d = context.___diffs[name]; - - d.stash = d.prev !== null ? value - d.prev : 0; - d.prev = value; - - return d.stash; -} - -type Context = { [key: string]: any }; -type Handler = ( - event: T, - context: TContext -) => void; -type ReturnHandler = ( - event: T, - context: TContext -) => R; - -interface GestureHandlers { - onInit?: Handler; - onEvent?: Handler; - shouldHandleEvent?: ReturnHandler; - onGesture?: Handler; - beforeEach?: Handler; - afterEach?: Handler; - onStart?: Handler; - onActive?: Handler; - onEnd?: Handler; - onFail?: Handler; - onCancel?: Handler; - onFinish?: (event: T, context: TContext, isCanceledOrFailed: boolean) => void; -} - -type OnGestureEvent = (event: T) => void; - -export function createAnimatedGestureHandler< - T extends GestureHandlerGestureEvent, - TContext extends Context ->(handlers: GestureHandlers) { - // eslint-disable-next-line react-hooks/rules-of-hooks - const context = useRemoteContext({ - __initialized: false, - }); - const isAndroid = Platform.OS === 'android'; - - const handler = (event: T['nativeEvent']) => { - 'worklet'; - - // const UNDETERMINED = 0; - const FAILED = 1; - const BEGAN = 2; - const CANCELLED = 3; - const ACTIVE = 4; - const END = 5; - - if (handlers.onInit && !context.__initialized) { - context.__initialized = true; - handlers.onInit(event, context); - } - - if (handlers.onGesture) { - handlers.onGesture(event, context); - } - - const stateDiff = diff(context, 'pinchState', event.state); - - const pinchBeganAndroid = - stateDiff === ACTIVE - BEGAN ? event.state === ACTIVE : false; - - const isBegan = isAndroid ? pinchBeganAndroid : event.state === BEGAN; - - if (isBegan) { - if (handlers.shouldHandleEvent) { - context._shouldSkip = !handlers.shouldHandleEvent(event, context); - } else { - context._shouldSkip = false; - } - } else if (typeof context._shouldSkip === 'undefined') { - return; - } - - if (!context._shouldSkip) { - if (handlers.onEvent) { - handlers.onEvent(event, context); - } - - if (handlers.beforeEach) { - handlers.beforeEach(event, context); - } - - if (isBegan && handlers.onStart) { - handlers.onStart(event, context); - } - - if (event.state === ACTIVE && handlers.onActive) { - handlers.onActive(event, context); - } - if ( - // @ts-ignore - event.oldState === ACTIVE && - event.state === END && - handlers.onEnd - ) { - handlers.onEnd(event, context); - } - if ( - // @ts-ignore - event.oldState === ACTIVE && - event.state === FAILED && - handlers.onFail - ) { - handlers.onFail(event, context); - } - if ( - // @ts-ignore - event.oldState === ACTIVE && - event.state === CANCELLED && - handlers.onCancel - ) { - handlers.onCancel(event, context); - } - // @ts-ignore - if (event.oldState === ACTIVE) { - if (handlers.onFinish) { - handlers.onFinish( - event, - context, - event.state === CANCELLED || event.state === FAILED - ); - } - } - - if (handlers.afterEach) { - handlers.afterEach(event, context); - } - } - // @ts-ignore - if (event.oldState === ACTIVE) { - context._shouldSkip = undefined; - } - }; - - return handler; -} - -export function useAnimatedGestureHandler< - T extends GestureHandlerGestureEvent, - TContext extends Context ->(handlers: GestureHandlers): OnGestureEvent { - // eslint-disable-next-line react-hooks/exhaustive-deps - const handler = useCallback( - createAnimatedGestureHandler(handlers), - [] - ); - - return useEvent(handler, [ - 'onGestureHandlerStateChange', - 'onGestureHandlerEvent', - ]); -} diff --git a/src/hooks/useInteractivePanGestureHandler.ts b/src/hooks/useInteractivePanGestureHandler.ts new file mode 100644 index 00000000..94981e8d --- /dev/null +++ b/src/hooks/useInteractivePanGestureHandler.ts @@ -0,0 +1,82 @@ +import Animated, { + useAnimatedGestureHandler, + useSharedValue, + cancelAnimation, +} from 'react-native-reanimated'; +import { + State, + PanGestureHandlerGestureEvent, +} from 'react-native-gesture-handler'; +import { clamp } from 'react-native-redash'; +import { snapPoint } from '../utilities'; +import { GESTURE } from '../constants'; + +export const useInteractivePanGestureHandler = ( + type: GESTURE, + animatedPosition: Animated.SharedValue, + snapPoints: number[], + animateToPoint: (point: number) => void, + offset?: Animated.SharedValue +): [ + (event: PanGestureHandlerGestureEvent) => void, + Animated.SharedValue, + Animated.SharedValue, + Animated.SharedValue +] => { + const gestureState = useSharedValue(State.UNDETERMINED); + const gestureTranslationY = useSharedValue(0); + const gestureVelocityY = useSharedValue(0); + + const gestureHandler = useAnimatedGestureHandler>( + { + onStart: ({ state, translationY, velocityY }, context) => { + // cancel current animation + cancelAnimation(animatedPosition); + + // store current animated position + context.lastAnimatedPosition = animatedPosition.value; + + // set variables + gestureState.value = state; + gestureTranslationY.value = translationY; + gestureVelocityY.value = velocityY; + }, + onActive: ({ state, translationY, velocityY }, context) => { + gestureState.value = state; + gestureTranslationY.value = translationY; + gestureVelocityY.value = velocityY; + + animatedPosition.value = clamp( + context.lastAnimatedPosition + + translationY + + (offset && context.lastAnimatedPosition === 0 ? offset.value : 0) * + -1, + snapPoints[snapPoints.length - 1], + snapPoints[0] + ); + }, + onEnd: ({ state }, context) => { + gestureState.value = state; + if ( + (offset ? offset.value : 0) > 0 && + context.lastAnimatedPosition === 0 && + animatedPosition.value === 0 + ) { + return; + } + animateToPoint( + snapPoint( + gestureTranslationY.value + context.lastAnimatedPosition, + gestureVelocityY.value, + snapPoints + ) + ); + }, + }, + /** @TODO this should be fixed with reanimated alpha 7 */ + // @ts-ignore + [type, snapPoints, animateToPoint] + ); + + return [gestureHandler, gestureState, gestureTranslationY, gestureVelocityY]; +}; diff --git a/src/hooks/usePanGestureHandler.ts b/src/hooks/usePanGestureHandler.ts deleted file mode 100644 index fb4d60c7..00000000 --- a/src/hooks/usePanGestureHandler.ts +++ /dev/null @@ -1,89 +0,0 @@ -import Animated, { - useAnimatedGestureHandler, - useSharedValue, - cancelAnimation, -} from 'react-native-reanimated'; -import { - State, - PanGestureHandlerGestureEvent, -} from 'react-native-gesture-handler'; -import { snapPoint, clamp } from '../utilities'; -import { GESTURE } from '../constants'; - -interface PanGestureHandlerConfigs { - type: GESTURE; - animatedPosition: Animated.SharedValue; - snapPoints: number[]; - offset?: Animated.SharedValue; - lastActiveGesture: Animated.SharedValue; - animateToPoint: (point: number) => void; -} - -export const usePanGestureHandler = ({ - type, - animatedPosition, - snapPoints, - offset, - lastActiveGesture, - animateToPoint, -}: PanGestureHandlerConfigs): [ - Animated.SharedValue, - Animated.SharedValue, - Animated.SharedValue, - (event: PanGestureHandlerGestureEvent) => void -] => { - const gestureState = useSharedValue(State.UNDETERMINED); - const gestureTranslationY = useSharedValue(0); - const gestureVelocityY = useSharedValue(0); - - const gestureHandler = useAnimatedGestureHandler>({ - onStart: ({ state, translationY, velocityY }, context) => { - // cancel current animation - cancelAnimation(animatedPosition); - - // set active gesture type - lastActiveGesture.value = type; - - // store current animated position - context.lastAnimatedPosition = animatedPosition.value; - - // set variables - gestureState.value = state; - gestureTranslationY.value = translationY; - gestureVelocityY.value = velocityY; - }, - onActive: ({ state, translationY, velocityY }, context) => { - gestureState.value = state; - gestureTranslationY.value = translationY; - gestureVelocityY.value = velocityY; - - animatedPosition.value = clamp( - context.lastAnimatedPosition + - translationY + - (offset && context.lastAnimatedPosition === 0 ? offset.value : 0) * - -1, - snapPoints[snapPoints.length - 1], - snapPoints[0] - ); - }, - onEnd: ({ state }, context) => { - gestureState.value = state; - if ( - (offset ? offset.value : 0) > 0 && - context.lastAnimatedPosition === 0 && - animatedPosition.value === 0 - ) { - return; - } - animateToPoint( - snapPoint( - gestureTranslationY.value + context.lastAnimatedPosition, - gestureVelocityY.value, - snapPoints - ) - ); - }, - }); - - return [gestureState, gestureTranslationY, gestureVelocityY, gestureHandler]; -}; diff --git a/src/hooks/useScrollable.ts b/src/hooks/useScrollable.ts index e41abe89..318e646c 100644 --- a/src/hooks/useScrollable.ts +++ b/src/hooks/useScrollable.ts @@ -1,5 +1,5 @@ import { useCallback, RefObject, useRef } from 'react'; -import { findNodeHandle } from 'react-native'; +import { findNodeHandle, Platform } from 'react-native'; import { useSharedValue } from 'react-native-reanimated'; import type { ScrollableRef, Scrollable } from '../types'; @@ -48,6 +48,7 @@ export const useScrollable = () => { const flashScrollableIndicators = useCallback(() => { let type = scrollableRef.current?.type ?? undefined; let node = scrollableRef.current?.node ?? undefined; + let didResize = scrollableRef.current?.didResize ?? false; if (!type || !node) { return; @@ -57,6 +58,20 @@ export const useScrollable = () => { if (node.current.flashScrollIndicators) { // @ts-ignore node.current.flashScrollIndicators(); + + /** + * this is a hack to resize the scroll indicator + * size on iOS. + */ + if (Platform.OS === 'ios' && !didResize) { + // @ts-ignore + node.current.setNativeProps({ + bottom: 0.5, + }); + + // @ts-ignore + scrollableRef.current.didResize = true; + } } }, []); diff --git a/src/hooks/useScrollableAnimatedProps/useScrollableAnimatedProps.android.ts b/src/hooks/useScrollableAnimatedProps/useScrollableAnimatedProps.android.ts index 390c9473..ade5d574 100644 --- a/src/hooks/useScrollableAnimatedProps/useScrollableAnimatedProps.android.ts +++ b/src/hooks/useScrollableAnimatedProps/useScrollableAnimatedProps.android.ts @@ -1,27 +1,39 @@ +/** @TODO this should be fixed with reanimated alpha 7 */ +// @ts-ignore import { useAnimatedProps, useAnimatedReaction } from 'react-native-reanimated'; +import { ANIMATION_STATE } from 'src/constants'; import { useBottomSheetInternal } from '../useBottomSheetInternal'; export const useScrollableAnimatedProps = () => { // hooks const { animatedPosition, + animationState, scrollableDecelerationRate, } = useBottomSheetInternal(); // variables - const animatedProps = useAnimatedProps(() => ({ - decelerationRate: scrollableDecelerationRate.value, - })); + const animatedProps = useAnimatedProps( + () => ({ + decelerationRate: scrollableDecelerationRate.value, + }), + /** @TODO this should be fixed with reanimated alpha 7 */ + // @ts-ignore + [] + ); // effects useAnimatedReaction( - () => animatedPosition === 0, + () => + animatedPosition.value !== 0 || + animationState.value === ANIMATION_STATE.RUNNING, (shouldLimitDecelerationRate: boolean) => { - const newDecelerationRate = shouldLimitDecelerationRate ? 0.0001 : 0.985; + const newDecelerationRate = shouldLimitDecelerationRate ? 0 : 0.985; if (scrollableDecelerationRate.value !== newDecelerationRate) { scrollableDecelerationRate.value = newDecelerationRate; } - } + }, + [] ); return animatedProps; diff --git a/src/hooks/useScrollableInternal.ts b/src/hooks/useScrollableInternal.ts index c2baba8a..bd4f35a0 100644 --- a/src/hooks/useScrollableInternal.ts +++ b/src/hooks/useScrollableInternal.ts @@ -1,12 +1,13 @@ import { useCallback } from 'react'; -import { findNodeHandle } from 'react-native'; +import { findNodeHandle, NativeScrollEvent } from 'react-native'; import { useAnimatedRef, useAnimatedScrollHandler, useSharedValue, scrollTo, } from 'react-native-reanimated'; -import { useBottomSheetInternal } from '../hooks/useBottomSheetInternal'; +import { useScrollableAnimatedProps } from './useScrollableAnimatedProps'; +import { useBottomSheetInternal } from './useBottomSheetInternal'; import type { Scrollable, ScrollableType } from '../types'; export const useScrollableInternal = (type: ScrollableType) => { @@ -16,6 +17,7 @@ export const useScrollableInternal = (type: ScrollableType) => { const scrollableContentOffsetY = useSharedValue(0); // hooks + const scrollableAnimatedProps = useScrollableAnimatedProps(); const { animatedPosition, scrollableContentOffsetY: _scrollableContentOffsetY, @@ -24,37 +26,43 @@ export const useScrollableInternal = (type: ScrollableType) => { } = useBottomSheetInternal(); // callbacks - const handleScrollEvent = useAnimatedScrollHandler({ - onBeginDrag: ({ contentOffset: { y } }) => { - if (animatedPosition.value !== 0) { - return; - } - - scrollableContentOffsetY.value = y; - scrollablePosition.value = y; - _scrollableContentOffsetY.value = y; - }, - onScroll: ({ contentOffset: { y } }) => { - if (animatedPosition.value !== 0) { - scrollTo(scrollableRef, 0, scrollablePosition.value, false); - return; - } - scrollablePosition.value = y; - }, - onEndDrag: () => { - if (animatedPosition.value !== 0) { - scrollTo(scrollableRef, 0, scrollablePosition.value, false); - return; - } - }, - onMomentumEnd: ({ contentOffset: { y } }) => { - if (animatedPosition.value !== 0) { - scrollTo(scrollableRef, 0, scrollablePosition.value, false); - return; - } - scrollablePosition.value = y; + const handleScrollEvent = useAnimatedScrollHandler( + { + onBeginDrag: ({ contentOffset: { y } }: NativeScrollEvent) => { + if (animatedPosition.value !== 0) { + return; + } + scrollablePosition.value = y; + scrollableContentOffsetY.value = y; + _scrollableContentOffsetY.value = y; + }, + onScroll: ({ contentOffset: { y } }: NativeScrollEvent) => { + if (animatedPosition.value !== 0) { + // @ts-ignore + scrollTo(scrollableRef, 0, scrollablePosition.value, false); + return; + } + scrollablePosition.value = y; + }, + onEndDrag: () => { + if (animatedPosition.value !== 0) { + // @ts-ignore + scrollTo(scrollableRef, 0, scrollablePosition.value, false); + return; + } + }, + onMomentumEnd: () => { + if (animatedPosition.value !== 0) { + // @ts-ignore + scrollTo(scrollableRef, 0, scrollablePosition.value, false); + return; + } + }, }, - }); + /** @TODO this should be fixed with reanimated alpha 7 */ + // @ts-ignore + [] + ); const handleSettingScrollable = useCallback(() => { 'worklet'; // set current content offset @@ -67,6 +75,7 @@ export const useScrollableInternal = (type: ScrollableType) => { id: id, type, node: scrollableRef, + didResize: false, }); } else { console.warn(`Couldn't find the scrollable node handle id!`); @@ -80,6 +89,7 @@ export const useScrollableInternal = (type: ScrollableType) => { return { scrollableRef, + scrollableAnimatedProps, handleScrollEvent, handleSettingScrollable, }; diff --git a/src/hooks/useTapGestureHandler.ts b/src/hooks/useTapGestureHandler.ts new file mode 100644 index 00000000..d99ad0a1 --- /dev/null +++ b/src/hooks/useTapGestureHandler.ts @@ -0,0 +1,37 @@ +import Animated, { useAnimatedGestureHandler } from 'react-native-reanimated'; +import { + State, + TapGestureHandlerGestureEvent, +} from 'react-native-gesture-handler'; + +export const useTapGestureHandler = ( + gestureState: Animated.SharedValue +) => { + const handleGestureEvent = useAnimatedGestureHandler( + { + onStart: ({ state }) => { + gestureState.value = state; + }, + onActive: ({ state }) => { + gestureState.value = state; + }, + onCancel: ({ state }) => { + gestureState.value = state; + }, + onEnd: ({ state }) => { + gestureState.value = state; + }, + onFail: ({ state }) => { + gestureState.value = state; + }, + onFinish: ({ state }) => { + gestureState.value = state; + }, + }, + /** @TODO this should be fixed with reanimated alpha 7 */ + // @ts-ignore + [] + ); + + return handleGestureEvent as (event: TapGestureHandlerGestureEvent) => void; +}; diff --git a/src/types.ts b/src/types.ts index 4ebfdf7d..28f731ef 100644 --- a/src/types.ts +++ b/src/types.ts @@ -31,4 +31,5 @@ export type ScrollableRef = { id: number; node: React.RefObject; type: ScrollableType; + didResize: boolean; }; diff --git a/src/utilities/clamp.ts b/src/utilities/clamp.ts deleted file mode 100644 index 6e005950..00000000 --- a/src/utilities/clamp.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const clamp = ( - value: number, - lowerBound: number, - upperBound: number -) => { - 'worklet'; - return Math.min(Math.max(lowerBound, value), upperBound); -}; diff --git a/src/utilities/index.ts b/src/utilities/index.ts index 8877e706..a6446e53 100644 --- a/src/utilities/index.ts +++ b/src/utilities/index.ts @@ -1,3 +1,2 @@ export { normalizeSnapPoints } from './normalizeSnapPoints'; export { snapPoint } from './snapPoint'; -export { clamp } from './clamp';