import React , { useEffect , useRef } from 'react' ; import { GestureType , HandlerCallbacks , BaseGesture , GestureRef , CALLBACK_TYPE , } from './gesture' ; import { Reanimated , SharedValue } from './reanimatedWrapper' ; import { registerHandler , unregisterHandler } from '../handlersRegistry' ; import RNGestureHandlerModule from '../../RNGestureHandlerModule' ; import { baseGestureHandlerWithMonitorProps , filterConfig , findNodeHandle , GestureTouchEvent , GestureUpdateEvent , GestureStateChangeEvent , } from '../gestureHandlerCommon' ; import { GestureStateManager , GestureStateManagerType , } from './gestureStateManager' ; import { flingGestureHandlerProps } from '../FlingGestureHandler' ; import { forceTouchGestureHandlerProps } from '../ForceTouchGestureHandler' ; import { longPressGestureHandlerProps } from '../LongPressGestureHandler' ; import { panGestureHandlerProps , panGestureHandlerCustomNativeProps , } from '../PanGestureHandler' ; import { tapGestureHandlerProps } from '../TapGestureHandler' ; import { State } from '../../State' ; import { EventType } from '../../EventType' ; import { ComposedGesture } from './gestureComposition' ; const ALLOWED_PROPS = [ ... baseGestureHandlerWithMonitorProps , ... tapGestureHandlerProps , ... panGestureHandlerProps , ... panGestureHandlerCustomNativeProps , ... longPressGestureHandlerProps , ... forceTouchGestureHandlerProps , ... flingGestureHandlerProps , ] ; export type GestureConfigReference = { config : GestureType [ ] ; animatedEventHandler : unknown ; animatedHandlers : SharedValue < HandlerCallbacks < Record < string , unknown > > [ ] | null > | null ; firstExecution : boolean ; useAnimated : boolean ; } ; function convertToHandlerTag ( ref ) { if ( typeof ref === 'number' ) { return ref ; } else if ( ref instanceof BaseGesture ) { return ref . handlerTag ; } else { return ref . current ?. handlerTag ?? - 1 ; } } function extractValidHandlerTags ( interactionGroup ) { return ( interactionGroup ?. map ( convertToHandlerTag ) ?. filter ( ( tag ) => tag > 0 ) ?? [ ] ) ; } function dropHandlers ( preparedGesture ) { for ( const handler of preparedGesture . config ) { RNGestureHandlerModule . dropGestureHandler ( handler . handlerTag ) ; unregisterHandler ( handler . handlerTag ) ; } } interface AttachHandlersConfig { preparedGesture : GestureConfigReference ; gestureConfig : ComposedGesture | GestureType | undefined ; gesture : GestureType [ ] ; viewTag : number ; useAnimated : boolean ; } function attachHandlers ( { preparedGesture , gestureConfig , gesture , viewTag , useAnimated , } ) { if ( ! preparedGesture . firstExecution ) { gestureConfig ?. initialize ( ) ; } else { preparedGesture . firstExecution = false ; } setImmediate ( ( ) => { gestureConfig ?. prepare ( ) ; } ) ; for ( const handler of gesture ) { RNGestureHandlerModule . createGestureHandler ( handler . handlerName , handler . handlerTag , filterConfig ( handler . config , ALLOWED_PROPS ) ) ; registerHandler ( handler . handlerTag , handler ) ; setImmediate ( ( ) => { let requireToFail = [ ] ; if ( handler . config . requireToFail ) { requireToFail = extractValidHandlerTags ( handler . config . requireToFail ) ; } let simultaneousWith = [ ] ; if ( handler . config . simultaneousWith ) { simultaneousWith = extractValidHandlerTags ( handler . config . simultaneousWith ) ; } RNGestureHandlerModule . updateGestureHandler ( handler . handlerTag , filterConfig ( handler . config , ALLOWED_PROPS , { simultaneousHandlers : simultaneousWith , waitFor : requireToFail , } ) ) ; } ) ; } preparedGesture . config = gesture ; for ( const gesture of preparedGesture . config ) { RNGestureHandlerModule . attachGestureHandler ( gesture . handlerTag , viewTag , ! useAnimated ) ; } if ( preparedGesture . animatedHandlers ) { preparedGesture . animatedHandlers . value = ( gesture . map ( ( g ) => g . handlers ) as unknown ) as HandlerCallbacks < Record < string , unknown > > [ ] ; } } function updateHandlers ( preparedGesture , gestureConfig , gesture ) { gestureConfig ?. prepare ( ) ; for ( let i = 0 ; i < gesture . length ; i ++ ) { const handler = preparedGesture . config [ i ] ; gesture [ i ] . handlerTag = handler . handlerTag ; gesture [ i ] . handlers . handlerTag = handler . handlerTag ; } setImmediate ( ( ) => { for ( let i = 0 ; i < gesture . length ; i ++ ) { const handler = preparedGesture . config [ i ] ; handler . config = gesture [ i ] . config ; handler . handlers = gesture [ i ] . handlers ; handler . handlers . handlerTag = handler . handlerTag ; const requireToFail = extractValidHandlerTags ( handler . config . requireToFail ) ; const simultaneousWith = extractValidHandlerTags ( handler . config . simultaneousWith ) ; RNGestureHandlerModule . updateGestureHandler ( handler . handlerTag , filterConfig ( handler . config , ALLOWED_PROPS , { simultaneousHandlers : simultaneousWith , waitFor : requireToFail , } ) ) ; registerHandler ( handler . handlerTag , handler ) ; } if ( preparedGesture . animatedHandlers ) { preparedGesture . animatedHandlers . value = ( preparedGesture . config . map ( ( g ) => g . handlers ) as unknown ) as HandlerCallbacks < Record < string , unknown > > [ ] ; } } ) ; } function needsToReattach ( preparedGesture , gesture ) { if ( gesture . length !== preparedGesture . config . length ) { return true ; } for ( let i = 0 ; i < gesture . length ; i ++ ) { if ( gesture [ i ] . handlerName !== preparedGesture . config [ i ] . handlerName ) { return true ; } } return false ; } function useAnimatedGesture ( preparedGesture ) { if ( ! Reanimated ) { return ; } function isStateChangeEvent ( event ) { 'worklet' ; return event . oldState != null ; } function isTouchEvent ( event ) { 'worklet' ; return event . eventType != null ; } function getHandler ( type , gesture ) { 'worklet' ; switch ( type ) { case CALLBACK_TYPE . BEGAN : return gesture . onBegin ; case CALLBACK_TYPE . START : return gesture . onStart ; case CALLBACK_TYPE . UPDATE : return gesture . onUpdate ; case CALLBACK_TYPE . CHANGE : return gesture . onChange ; case CALLBACK_TYPE . END : return gesture . onEnd ; case CALLBACK_TYPE . FINALIZE : return gesture . onFinalize ; case CALLBACK_TYPE . TOUCHES_DOWN : return gesture . onTouchesDown ; case CALLBACK_TYPE . TOUCHES_MOVE : return gesture . onTouchesMove ; case CALLBACK_TYPE . TOUCHES_UP : return gesture . onTouchesUp ; case CALLBACK_TYPE . TOUCHES_CANCELLED : return gesture . onTouchesCancelled ; } } function touchEventTypeToCallbackType ( eventType ) { 'worklet' ; switch ( eventType ) { case EventType . TOUCHES_DOWN : return CALLBACK_TYPE . TOUCHES_DOWN ; case EventType . TOUCHES_MOVE : return CALLBACK_TYPE . TOUCHES_MOVE ; case EventType . TOUCHES_UP : return CALLBACK_TYPE . TOUCHES_UP ; case EventType . TOUCHES_CANCELLED : return CALLBACK_TYPE . TOUCHES_CANCELLED ; } return CALLBACK_TYPE . UNDEFINED ; } function runWorklet ( type , gesture , event , ... args ) { 'worklet' ; const handler = getHandler ( type , gesture ) ; if ( gesture . isWorklet [ type ] ) { handler ?. ( event , ... args ) ; } else if ( handler ) { console . warn ( 'Animated gesture callback must be a worklet' ) ; } } const sharedHandlersCallbacks = Reanimated . useSharedValue < HandlerCallbacks < Record < string , unknown > > [ ] | null > ( null ) ; const lastUpdateEvent = Reanimated . useSharedValue < ( GestureUpdateEvent | undefined ) [ ] > ( [ ] ) ; const stateControllers = [ ] ; const callback = ( event ) => { 'worklet' ; const currentCallback = sharedHandlersCallbacks . value ; if ( ! currentCallback ) { return ; } for ( let i = 0 ; i < currentCallback . length ; i ++ ) { const gesture = currentCallback [ i ] ; if ( event . handlerTag === gesture . handlerTag ) { if ( isStateChangeEvent ( event ) ) { if ( event . oldState === State . UNDETERMINED && event . state === State . BEGAN ) { runWorklet ( CALLBACK_TYPE . BEGAN , gesture , event ) ; } else if ( ( event . oldState === State . BEGAN || event . oldState === State . UNDETERMINED ) && event . state === State . ACTIVE ) { runWorklet ( CALLBACK_TYPE . START , gesture , event ) ; lastUpdateEvent . value [ gesture . handlerTag ] = undefined ; } else if ( event . oldState !== event . state && event . state === State . END ) { if ( event . oldState === State . ACTIVE ) { runWorklet ( CALLBACK_TYPE . END , gesture , event , true ) ; } runWorklet ( CALLBACK_TYPE . FINALIZE , gesture , event , true ) ; } else if ( ( event . state === State . FAILED || event . state === State . CANCELLED ) && event . state !== event . oldState ) { if ( event . oldState === State . ACTIVE ) { runWorklet ( CALLBACK_TYPE . END , gesture , event , false ) ; } runWorklet ( CALLBACK_TYPE . FINALIZE , gesture , event , false ) ; } } else if ( isTouchEvent ( event ) ) { if ( ! stateControllers [ i ] ) { stateControllers [ i ] = GestureStateManager . create ( event . handlerTag ) ; } if ( event . eventType !== EventType . UNDETERMINED ) { runWorklet ( touchEventTypeToCallbackType ( event . eventType ) , gesture , event , stateControllers [ i ] ) ; } } else { runWorklet ( CALLBACK_TYPE . UPDATE , gesture , event ) ; if ( gesture . onChange && gesture . changeEventCalculator ) { runWorklet ( CALLBACK_TYPE . CHANGE , gesture , gesture . changeEventCalculator ?. ( event , lastUpdateEvent . value [ gesture . handlerTag ] ) ) ; lastUpdateEvent . value [ gesture . handlerTag ] = event ; } } } } } ; const event = Reanimated . useEvent ( callback , [ 'onGestureHandlerStateChange' , 'onGestureHandlerEvent' ] , true ) ; preparedGesture . animatedEventHandler = event ; preparedGesture . animatedHandlers = sharedHandlersCallbacks ; } interface GestureDetectorProps { gesture ? : ComposedGesture | GestureType ; } export const GestureDetector = ( props ) => { const gestureConfig = props . gesture ; const gesture = gestureConfig ?. toGestureArray ?. ( ) ?? [ ] ; const useAnimated = gesture . find ( ( gesture ) => gesture . handlers . isWorklet . reduce ( ( prev , current ) => prev || current ) ) != null ; const viewRef = useRef ( null ) ; const firstRenderRef = useRef ( true ) ; const preparedGesture = React . useRef < GestureConfigReference > ( { config : gesture , animatedEventHandler : null , animatedHandlers : null , firstExecution : true , useAnimated : useAnimated , } ) . current ; if ( useAnimated !== preparedGesture . useAnimated ) { throw new Error ( 'You cannot change whether you are using gesture or animatedGesture while the app is running' ) ; } if ( preparedGesture . firstExecution ) { gestureConfig ?. initialize ?. ( ) ; } if ( useAnimated ) { useAnimatedGesture ( preparedGesture ) ; } useEffect ( ( ) => { firstRenderRef . current = true ; const viewTag = findNodeHandle ( viewRef . current ) as number ; attachHandlers ( { preparedGesture , gestureConfig , gesture , viewTag , useAnimated , } ) ; return ( ) => { dropHandlers ( preparedGesture ) ; } ; } , [ ] ) ; useEffect ( ( ) => { if ( ! firstRenderRef . current ) { const viewTag = findNodeHandle ( viewRef . current ) as number ; if ( needsToReattach ( preparedGesture , gesture ) ) { dropHandlers ( preparedGesture ) ; attachHandlers ( { preparedGesture , gestureConfig , gesture , viewTag , useAnimated , } ) ; } else { updateHandlers ( preparedGesture , gestureConfig , gesture ) ; } } else { firstRenderRef . current = false ; } } , [ props ] ) ; if ( useAnimated ) { return ( < AnimatedWrap ref = { viewRef } onGestureHandlerEvent = { preparedGesture . animatedEventHandler } > { props . children } < /AnimatedWrap>); } else { return < Wrap ref = { viewRef } > { props . children } < /Wrap>; } } ; class Wrap extends React . Component < { onGestureHandlerEvent ? : unknown ; } > { render ( ) { const child = React . Children . only ( this . props . children ) ; return React . cloneElement ( child , { collapsable : false } , child . props . children ) ; } } const AnimatedWrap = Reanimated ?. default ?. createAnimatedComponent ( Wrap ) ?? Wrap ;