diff --git a/packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx b/packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx index 9cc1ed1712..ec82f83ddf 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx +++ b/packages/react-native-gesture-handler/src/components/ReanimatedDrawerLayout.tsx @@ -36,16 +36,18 @@ import Animated, { withSpring, } from 'react-native-reanimated'; -import { GestureObjects as Gesture } from '../handlers/gestures/gestureObjects'; -import { GestureDetector } from '../handlers/gestures/GestureDetector'; import { UserSelect, ActiveCursor, MouseButton, HitSlop, - GestureStateChangeEvent, } from '../handlers/gestureHandlerCommon'; -import { PanGestureHandlerEventPayload } from '../handlers/GestureHandlerEventPayload'; +import { + PanGestureStateChangeEvent, + usePan, + useTap, +} from '../v3/hooks/gestures'; +import { GestureDetector } from '../v3/detectors'; const DRAG_TOSS = 0.05; @@ -432,9 +434,9 @@ const DrawerLayout = forwardRef( ); const handleRelease = useCallback( - (event: GestureStateChangeEvent) => { + (event: PanGestureStateChangeEvent) => { 'worklet'; - let { translationX: dragX, velocityX, x: touchX } = event; + let { translationX: dragX, velocityX, x: touchX } = event.handlerData; if (drawerPosition !== DrawerPosition.LEFT) { // See description in _updateAnimatedEvent about why events are flipped @@ -497,20 +499,18 @@ const DrawerLayout = forwardRef( [animateDrawer] ); - const overlayDismissGesture = useMemo( - () => - Gesture.Tap() - .maxDistance(25) - .onEnd(() => { - if ( - isDrawerOpen.value && - drawerLockMode !== DrawerLockMode.LOCKED_OPEN - ) { - closeDrawer(); - } - }), - [closeDrawer, isDrawerOpen, drawerLockMode] - ); + const overlayDismissGesture = useTap({ + maxDistance: 25, + onEnd: () => { + 'worklet'; + if ( + isDrawerOpen.value && + drawerLockMode !== DrawerLockMode.LOCKED_OPEN + ) { + closeDrawer(); + } + }, + }); const overlayAnimatedStyle = useAnimatedStyle(() => ({ opacity: openValue.value, @@ -522,86 +522,63 @@ const DrawerLayout = forwardRef( [drawerWidth, isFromLeft] ); - const panGesture = useMemo(() => { - return Gesture.Pan() - .activeCursor(activeCursor) - .mouseButton(mouseButton) - .hitSlop(drawerOpened ? fillHitSlop : edgeHitSlop) - .minDistance(drawerOpened ? 100 : 0) - .activeOffsetX(gestureOrientation * minSwipeDistance) - .failOffsetY([-15, 15]) - .simultaneousWithExternalGesture(overlayDismissGesture) - .enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture) - .enabled( - drawerState !== DrawerState.SETTLING && - (drawerOpened - ? drawerLockMode !== DrawerLockMode.LOCKED_OPEN - : drawerLockMode !== DrawerLockMode.LOCKED_CLOSED) - ) - .onStart(() => { - emitStateChanged(DrawerState.DRAGGING, false); - runOnJS(setDrawerState)(DrawerState.DRAGGING); - if (keyboardDismissMode === DrawerKeyboardDismissMode.ON_DRAG) { - runOnJS(dismissKeyboard)(); - } - if (hideStatusBar) { - runOnJS(setStatusBarHidden)(true, statusBarAnimation); - } - }) - .onUpdate((event) => { - const startedOutsideTranslation = isFromLeft - ? interpolate( - event.x, - [0, drawerWidth, drawerWidth + 1], - [0, drawerWidth, drawerWidth] - ) - : interpolate( - event.x - containerWidth, - [-drawerWidth - 1, -drawerWidth, 0], - [drawerWidth, drawerWidth, 0] - ); - - const startedInsideTranslation = - sideCorrection * - (event.translationX + - (drawerOpened ? drawerWidth * -gestureOrientation : 0)); - - const adjustedTranslation = Math.max( - drawerOpened ? startedOutsideTranslation : 0, - startedInsideTranslation - ); - - openValue.value = interpolate( - adjustedTranslation, - [-drawerWidth, 0, drawerWidth], - [1, 0, 1], - Extrapolation.CLAMP - ); - }) - .onEnd(handleRelease); - }, [ - drawerLockMode, - openValue, - drawerWidth, - emitStateChanged, - gestureOrientation, - handleRelease, - edgeHitSlop, - fillHitSlop, - minSwipeDistance, - hideStatusBar, - keyboardDismissMode, - overlayDismissGesture, - drawerOpened, - isFromLeft, - containerWidth, - sideCorrection, - drawerState, - activeCursor, - enableTrackpadTwoFingerGesture, - mouseButton, - statusBarAnimation, - ]); + const panGesture = usePan({ + activeCursor: activeCursor, + mouseButton: mouseButton, + hitSlop: drawerOpened ? fillHitSlop : edgeHitSlop, + activeOffsetX: gestureOrientation * minSwipeDistance, + failOffsetY: [-15, 15], + simultaneousWith: overlayDismissGesture, + enableTrackpadTwoFingerGesture: enableTrackpadTwoFingerGesture, + enabled: + drawerState !== DrawerState.SETTLING && + (drawerOpened + ? drawerLockMode !== DrawerLockMode.LOCKED_OPEN + : drawerLockMode !== DrawerLockMode.LOCKED_CLOSED), + onStart: () => { + 'worklet'; + emitStateChanged(DrawerState.DRAGGING, false); + runOnJS(setDrawerState)(DrawerState.DRAGGING); + if (keyboardDismissMode === DrawerKeyboardDismissMode.ON_DRAG) { + runOnJS(dismissKeyboard)(); + } + if (hideStatusBar) { + runOnJS(setStatusBarHidden)(true, statusBarAnimation); + } + }, + onUpdate: (event) => { + 'worklet'; + const startedOutsideTranslation = isFromLeft + ? interpolate( + event.handlerData.x, + [0, drawerWidth, drawerWidth + 1], + [0, drawerWidth, drawerWidth] + ) + : interpolate( + event.handlerData.x - containerWidth, + [-drawerWidth - 1, -drawerWidth, 0], + [drawerWidth, drawerWidth, 0] + ); + + const startedInsideTranslation = + sideCorrection * + (event.handlerData.translationX + + (drawerOpened ? drawerWidth * -gestureOrientation : 0)); + + const adjustedTranslation = Math.max( + drawerOpened ? startedOutsideTranslation : 0, + startedInsideTranslation + ); + + openValue.value = interpolate( + adjustedTranslation, + [-drawerWidth, 0, drawerWidth], + [1, 0, 1], + Extrapolation.CLAMP + ); + }, + onEnd: handleRelease, + }); // When using RTL, row and row-reverse flex directions are flipped. const reverseContentDirection = I18nManager.isRTL diff --git a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx index 365f573943..b50a0a76f1 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeable.tsx @@ -1,10 +1,4 @@ -import { - useMemo, - useCallback, - useImperativeHandle, - ForwardedRef, - useState, -} from 'react'; +import { useMemo, useCallback, useImperativeHandle, ForwardedRef } from 'react'; import { LayoutChangeEvent, View, I18nManager, StyleSheet } from 'react-native'; import Animated, { useSharedValue, @@ -22,18 +16,13 @@ import { SwipeableMethods, SwipeDirection, } from './ReanimatedSwipeableProps'; -import { Gesture } from '../..'; -import { - GestureStateChangeEvent, - GestureUpdateEvent, -} from '../../handlers/gestureHandlerCommon'; -import { PanGestureHandlerEventPayload } from '../../handlers/GestureHandlerEventPayload'; -import { GestureDetector } from '../../handlers/gestures/GestureDetector'; import { - applyRelationProp, - RelationPropName, - RelationPropType, -} from '../utils'; + PanGestureStateChangeEvent, + PanGestureUpdateEvent, + usePan, + useTap, +} from '../../v3/hooks/gestures'; +import { GestureDetector } from '../../v3/detectors'; const DRAG_TOSS = 0.05; @@ -75,20 +64,7 @@ const Swipeable = (props: SwipeableProps) => { ...remainingProps } = props; - const relationProps = useMemo( - () => ({ - simultaneousWithExternalGesture, - requireExternalGestureToFail, - blocksExternalGesture, - }), - [ - blocksExternalGesture, - requireExternalGestureToFail, - simultaneousWithExternalGesture, - ] - ); - - const [shouldEnableTap, setShouldEnableTap] = useState(false); + const shouldEnableTap = useSharedValue(false); const rowState = useSharedValue(0); const userDrag = useSharedValue(0); @@ -263,7 +239,7 @@ const Swipeable = (props: SwipeableProps) => { rowState.value = Math.sign(toValue); - runOnJS(setShouldEnableTap)(rowState.value !== 0); + shouldEnableTap.value = rowState.value !== 0; }, [ rowState, @@ -427,10 +403,10 @@ const Swipeable = (props: SwipeableProps) => { ); const handleRelease = useCallback( - (event: GestureStateChangeEvent) => { + (event: PanGestureStateChangeEvent) => { 'worklet'; - const { velocityX } = event; - userDrag.value = event.translationX; + const { velocityX } = event.handlerData; + userDrag.value = event.handlerData.translationX; const leftThresholdProp = leftThreshold ?? leftWidth.value / 2; const rightThresholdProp = rightThreshold ?? rightWidth.value / 2; @@ -478,88 +454,61 @@ const Swipeable = (props: SwipeableProps) => { const dragStarted = useSharedValue(false); - const tapGesture = useMemo(() => { - const tap = Gesture.Tap() - .shouldCancelWhenOutside(true) - .enabled(shouldEnableTap) - .onStart(() => { - if (rowState.value !== 0) { - close(); - } - }); - - Object.entries(relationProps).forEach(([relationName, relation]) => { - applyRelationProp( - tap, - relationName as RelationPropName, - relation as RelationPropType - ); - }); - return tap; - }, [close, relationProps, rowState, shouldEnableTap]); - - const panGesture = useMemo(() => { - const pan = Gesture.Pan() - .enabled(enabled !== false) - .enableTrackpadTwoFingerGesture(enableTrackpadTwoFingerGesture) - .activeOffsetX([-dragOffsetFromRightEdge, dragOffsetFromLeftEdge]) - .onStart(updateElementWidths) - .onUpdate((event: GestureUpdateEvent) => { - userDrag.value = event.translationX; - - const direction = - rowState.value === -1 - ? SwipeDirection.RIGHT - : rowState.value === 1 - ? SwipeDirection.LEFT - : event.translationX > 0 - ? SwipeDirection.RIGHT - : SwipeDirection.LEFT; - - if (!dragStarted.value) { - dragStarted.value = true; - if (rowState.value === 0 && onSwipeableOpenStartDrag) { - runOnJS(onSwipeableOpenStartDrag)(direction); - } else if (onSwipeableCloseStartDrag) { - runOnJS(onSwipeableCloseStartDrag)(direction); - } - } + const tapGesture = useTap({ + shouldCancelWhenOutside: true, + enabled: shouldEnableTap, + simultaneousWith: simultaneousWithExternalGesture, + requireToFail: requireExternalGestureToFail, + block: blocksExternalGesture, + onStart: () => { + 'worklet'; + if (rowState.value !== 0) { + close(); + } + }, + }); - updateAnimatedEvent(); - }) - .onEnd( - (event: GestureStateChangeEvent) => { - handleRelease(event); + const panGesture = usePan({ + enabled: enabled !== false, + enableTrackpadTwoFingerGesture: enableTrackpadTwoFingerGesture, + activeOffsetX: [-dragOffsetFromRightEdge, dragOffsetFromLeftEdge], + simultaneousWith: simultaneousWithExternalGesture, + requireToFail: requireExternalGestureToFail, + block: blocksExternalGesture, + onStart: updateElementWidths, + onUpdate: (event: PanGestureUpdateEvent) => { + 'worklet'; + userDrag.value = event.handlerData.translationX; + + const direction = + rowState.value === -1 + ? SwipeDirection.RIGHT + : rowState.value === 1 + ? SwipeDirection.LEFT + : event.handlerData.translationX > 0 + ? SwipeDirection.RIGHT + : SwipeDirection.LEFT; + + if (!dragStarted.value) { + dragStarted.value = true; + if (rowState.value === 0 && onSwipeableOpenStartDrag) { + runOnJS(onSwipeableOpenStartDrag)(direction); + } else if (onSwipeableCloseStartDrag) { + runOnJS(onSwipeableCloseStartDrag)(direction); } - ) - .onFinalize(() => { - dragStarted.value = false; - }); - - Object.entries(relationProps).forEach(([relationName, relation]) => { - applyRelationProp( - pan, - relationName as RelationPropName, - relation as RelationPropType - ); - }); + } - return pan; - }, [ - enabled, - enableTrackpadTwoFingerGesture, - dragOffsetFromRightEdge, - dragOffsetFromLeftEdge, - updateElementWidths, - relationProps, - userDrag, - rowState, - dragStarted, - updateAnimatedEvent, - onSwipeableOpenStartDrag, - onSwipeableCloseStartDrag, - handleRelease, - ]); + updateAnimatedEvent(); + }, + onEnd: (event: PanGestureStateChangeEvent) => { + 'worklet'; + handleRelease(event); + }, + onFinalize: () => { + 'worklet'; + dragStarted.value = false; + }, + }); useImperativeHandle(ref, () => swipeableMethods, [swipeableMethods]); diff --git a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts index 3aa66b9a2d..a14cac033f 100644 --- a/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts +++ b/packages/react-native-gesture-handler/src/components/ReanimatedSwipeable/ReanimatedSwipeableProps.ts @@ -1,21 +1,17 @@ import React from 'react'; -import type { PanGestureHandlerProps } from '../../handlers/PanGestureHandler'; import { SharedValue } from 'react-native-reanimated'; import { StyleProp, ViewStyle } from 'react-native'; -import { RelationPropType } from '../utils'; - -type SwipeableExcludes = Exclude< - keyof PanGestureHandlerProps, - 'onGestureEvent' | 'onHandlerStateChange' ->; - +import { PanGestureProperties } from '../../v3/hooks/gestures/pan/usePan'; +import { CommonGestureConfig } from '../../handlers/gestureHandlerCommon'; +import { AnyGesture } from '../../v3/types'; export enum SwipeDirection { LEFT = 'left', RIGHT = 'right', } export interface SwipeableProps - extends Pick { + extends PanGestureProperties, + CommonGestureConfig { /** * */ @@ -176,19 +172,27 @@ export interface SwipeableProps * A gesture object or an array of gesture objects containing the configuration and callbacks to be * used with the swipeable's gesture handler. */ - simultaneousWithExternalGesture?: RelationPropType; + simultaneousWithExternalGesture?: AnyGesture | AnyGesture[]; /** * A gesture object or an array of gesture objects containing the configuration and callbacks to be * used with the swipeable's gesture handler. */ - requireExternalGestureToFail?: RelationPropType; + requireExternalGestureToFail?: AnyGesture | AnyGesture[]; /** * A gesture object or an array of gesture objects containing the configuration and callbacks to be * used with the swipeable's gesture handler. */ - blocksExternalGesture?: RelationPropType; + blocksExternalGesture?: AnyGesture | AnyGesture[]; + + children?: React.ReactNode; + + id?: string; + + testID?: string; + + cancelsTouchesInView?: boolean; } export interface SwipeableMethods { diff --git a/packages/react-native-gesture-handler/src/v3/detectors/common.ts b/packages/react-native-gesture-handler/src/v3/detectors/common.ts index e42e261d2b..ae1560381e 100644 --- a/packages/react-native-gesture-handler/src/v3/detectors/common.ts +++ b/packages/react-native-gesture-handler/src/v3/detectors/common.ts @@ -29,7 +29,5 @@ export const ReanimatedNativeDetector = export const nativeDetectorStyles = StyleSheet.create({ detector: { display: 'contents', - // TODO: remove, debug info only - backgroundColor: 'red', }, }); diff --git a/packages/react-native-gesture-handler/src/v3/types/GestureTypes.ts b/packages/react-native-gesture-handler/src/v3/types/GestureTypes.ts index 776ad7d694..5673c4c789 100644 --- a/packages/react-native-gesture-handler/src/v3/types/GestureTypes.ts +++ b/packages/react-native-gesture-handler/src/v3/types/GestureTypes.ts @@ -10,9 +10,9 @@ import { FilterNeverProperties } from './UtilityTypes'; // Unfortunately, this type cannot be moved into ConfigTypes.ts because of circular dependency export type ExternalRelations = { - simultaneousWith?: Gesture | Gesture[]; - requireToFail?: Gesture | Gesture[]; - block?: Gesture | Gesture[]; + simultaneousWith?: AnyGesture | AnyGesture[]; + requireToFail?: AnyGesture | AnyGesture[]; + block?: AnyGesture | AnyGesture[]; }; // Similarly, this type cannot be moved into ConfigTypes.ts because it depends on `ExternalRelations`