import * as React from 'react' ; import { Platform , Touchable , UIManager , DeviceEventEmitter , EmitterSubscription , } from 'react-native' ; import deepEqual from 'lodash/isEqual' ; import RNGestureHandlerModule from '../RNGestureHandlerModule' ; import type RNGestureHandlerModuleWeb from '../RNGestureHandlerModule.web' ; import { State } from '../State' ; import { handlerIDToTag , getNextHandlerTag } from './handlersRegistry' ; import { BaseGestureHandlerProps , filterConfig , GestureEvent , HandlerStateChangeEvent , findNodeHandle , } from './gestureHandlerCommon' ; import { ValueOf } from '../typeUtils' ; const UIManagerAny = UIManager as any ; const customGHEventsConfig = { onGestureHandlerEvent : { registrationName : 'onGestureHandlerEvent' } , onGestureHandlerStateChange : { registrationName : 'onGestureHandlerStateChange' , } , } ; UIManagerAny . genericDirectEventTypes = { ... UIManagerAny . genericDirectEventTypes , ... customGHEventsConfig , } ; const UIManagerConstants = UIManagerAny . getViewManagerConfig ?. ( 'getConstants' ) ?? UIManagerAny . getConstants ?. ( ) ; if ( UIManagerConstants ) { UIManagerConstants . genericDirectEventTypes = { ... UIManagerConstants . genericDirectEventTypes , ... customGHEventsConfig , } ; } const { setJSResponder : oldSetJSResponder = ( ) => { } , clearJSResponder : oldClearJSResponder = ( ) => { } , } = UIManagerAny ; UIManagerAny . setJSResponder = ( tag , blockNativeResponder ) => { RNGestureHandlerModule . handleSetJSResponder ( tag , blockNativeResponder ) ; oldSetJSResponder ( tag , blockNativeResponder ) ; } ; UIManagerAny . clearJSResponder = ( ) => { RNGestureHandlerModule . handleClearJSResponder ( ) ; oldClearJSResponder ( ) ; } ; let allowTouches = true ; const DEV_ON_ANDROID = __DEV__ && Platform . OS === 'android' ; if ( DEV_ON_ANDROID ) { DeviceEventEmitter . addListener ( 'toggleElementInspector' , ( ) => { allowTouches = ! allowTouches ; } ) ; } type HandlerProps < T extends Record < string , unknown > > = Readonly < React . PropsWithChildren < BaseGestureHandlerProps < T > > > ; function hasUnresolvedRefs < T extends Record < string , unknown > > ( props ) { const extract = ( refs ) => { if ( ! Array . isArray ( refs ) ) { return refs && refs . current === null ; } return refs . some ( ( r ) => r && r . current === null ) ; } ; return extract ( props [ 'simultaneousHandlers' ] ) || extract ( props [ 'waitFor' ] ) ; } const stateToPropMappings = { [ State . UNDETERMINED ] : undefined , [ State . BEGAN ] : 'onBegan' , [ State . FAILED ] : 'onFailed' , [ State . CANCELLED ] : 'onCancelled' , [ State . ACTIVE ] : 'onActivated' , [ State . END ] : 'onEnded' , } as const ; type CreateHandlerArgs < HandlerPropsT extends Record < string , unknown > > = Readonly < { name : string ; allowedProps : Readonly < Extract < keyof HandlerPropsT , string > [ ] > ; config : Readonly < Record < string , unknown > > ; transformProps ? : ( props : HandlerPropsT ) => HandlerPropsT ; customNativeProps ? : Readonly < string [ ] > ; } > ; type InternalEventHandlers = { onGestureHandlerEvent ? : ( event : any ) => void ; onGestureHandlerStateChange ? : ( event : any ) => void ; } ; let showedRngh2Notice = false ; function showRngh2NoticeIfNeeded ( ) { if ( ! showedRngh2Notice ) { console . warn ( "[react-native-gesture-handler] Seems like you're using an old API with gesture components, check out new Gestures system!" ) ; showedRngh2Notice = true ; } } export default function createHandler < T extends BaseGestureHandlerProps < U > , U extends Record < string , unknown > > ( { name , allowedProps = [ ] , config = { } , transformProps , customNativeProps = [ ] , } ) { interface HandlerState { allowTouches : boolean ; } class Handler extends React . Component < T & InternalEventHandlers , HandlerState > { static displayName = name ; private handlerTag : number ; private config : Record < string , unknown > ; private propsRef : React . MutableRefObject < unknown > ; private viewNode : any ; private viewTag ? : number ; private updateEnqueued : ReturnType < typeof setImmediate > | null = null ; private inspectorToggleListener ? : EmitterSubscription ; constructor ( props ) { super ( props ) ; this . handlerTag = getNextHandlerTag ( ) ; this . config = { } ; this . propsRef = React . createRef ( ) ; this . state = { allowTouches } ; if ( props . id ) { if ( handlerIDToTag [ props . id ] !== undefined ) { throw new Error ( `Handler with ID "${ props . id }" already registered` ) ; } handlerIDToTag [ props . id ] = this . handlerTag ; } if ( __DEV__ ) { showRngh2NoticeIfNeeded ( ) ; } } componentDidMount ( ) { const props = this . props ; if ( DEV_ON_ANDROID ) { this . inspectorToggleListener = DeviceEventEmitter . addListener ( 'toggleElementInspector' , ( ) => { this . setState ( ( _ ) => ( { allowTouches } ) ) ; this . update ( ) ; } ) ; } if ( hasUnresolvedRefs ( props ) ) { this . updateEnqueued = setImmediate ( ( ) => { this . updateEnqueued = null ; this . update ( ) ; } ) ; } this . createGestureHandler ( filterConfig ( transformProps ? transformProps ( this . props ) : this . props , [ ... allowedProps , ... customNativeProps ] , config ) ) ; this . attachGestureHandler ( findNodeHandle ( this . viewNode ) as number ) ; } componentDidUpdate ( ) { const viewTag = findNodeHandle ( this . viewNode ) ; if ( this . viewTag !== viewTag ) { this . attachGestureHandler ( viewTag as number ) ; } this . update ( ) ; } componentWillUnmount ( ) { this . inspectorToggleListener ?. remove ( ) ; RNGestureHandlerModule . dropGestureHandler ( this . handlerTag ) ; if ( this . updateEnqueued ) { clearImmediate ( this . updateEnqueued ) ; } const handlerID = this . props . id ; if ( handlerID ) { delete handlerIDToTag [ handlerID ] ; } } private onGestureHandlerEvent = ( event ) => { if ( event . nativeEvent . handlerTag === this . handlerTag ) { this . props . onGestureEvent ?. ( event ) ; } else { this . props . onGestureHandlerEvent ?. ( event ) ; } } ; private onGestureHandlerStateChange = ( event ) => { if ( event . nativeEvent . handlerTag === this . handlerTag ) { this . props . onHandlerStateChange ?. ( event ) ; const state = event . nativeEvent . state ; const stateEventName = stateToPropMappings [ state ] ; const eventHandler = stateEventName && this . props [ stateEventName ] ; if ( eventHandler && typeof eventHandler === 'function' ) { eventHandler ( event ) ; } } else { this . props . onGestureHandlerStateChange ?. ( event ) ; } } ; private refHandler = ( node ) => { this . viewNode = node ; const child = React . Children . only ( this . props . children ) ; const { ref } = child ; if ( ref !== null ) { if ( typeof ref === 'function' ) { ref ( node ) ; } else { ref . current = node ; } } } ; private createGestureHandler = ( newConfig ) => { this . config = newConfig ; RNGestureHandlerModule . createGestureHandler ( name , this . handlerTag , newConfig ) ; } ; private attachGestureHandler = ( newViewTag ) => { this . viewTag = newViewTag ; if ( Platform . OS === 'web' ) { ( RNGestureHandlerModule . attachGestureHandler as typeof RNGestureHandlerModuleWeb . attachGestureHandler ) ( this . handlerTag , newViewTag , false , this . propsRef ) ; } else { RNGestureHandlerModule . attachGestureHandler ( this . handlerTag , newViewTag , false ) ; } } ; private updateGestureHandler = ( newConfig ) => { this . config = newConfig ; RNGestureHandlerModule . updateGestureHandler ( this . handlerTag , newConfig ) ; } ; private update ( ) { const newConfig = filterConfig ( transformProps ? transformProps ( this . props ) : this . props , [ ... allowedProps , ... customNativeProps ] , config ) ; if ( ! deepEqual ( this . config , newConfig ) ) { this . updateGestureHandler ( newConfig ) ; } } setNativeProps ( updates ) { const mergedProps = { ... this . props , ... updates } ; const newConfig = filterConfig ( transformProps ? transformProps ( mergedProps ) : mergedProps , [ ... allowedProps , ... customNativeProps ] , config ) ; this . updateGestureHandler ( newConfig ) ; } render ( ) { let gestureEventHandler = this . onGestureHandlerEvent ; type OnGestureEventHandlers = { onGestureEvent ? : BaseGestureHandlerProps < U > [ 'onGestureEvent' ] ; onGestureHandlerEvent ? : InternalEventHandlers [ 'onGestureHandlerEvent' ] ; } ; const { onGestureEvent , onGestureHandlerEvent , } = this . props ; if ( onGestureEvent && typeof onGestureEvent !== 'function' ) { if ( onGestureHandlerEvent ) { throw new Error ( 'Nesting touch handlers with native animated driver is not supported yet' ) ; } gestureEventHandler = onGestureEvent ; } else { if ( onGestureHandlerEvent && typeof onGestureHandlerEvent !== 'function' ) { throw new Error ( 'Nesting touch handlers with native animated driver is not supported yet' ) ; } } let gestureStateEventHandler = this . onGestureHandlerStateChange ; type OnGestureStateChangeHandlers = { onHandlerStateChange ? : BaseGestureHandlerProps < U > [ 'onHandlerStateChange' ] ; onGestureHandlerStateChange ? : InternalEventHandlers [ 'onGestureHandlerStateChange' ] ; } ; const { onHandlerStateChange , onGestureHandlerStateChange , } = this . props ; if ( onHandlerStateChange && typeof onHandlerStateChange !== 'function' ) { if ( onGestureHandlerStateChange ) { throw new Error ( 'Nesting touch handlers with native animated driver is not supported yet' ) ; } gestureStateEventHandler = onHandlerStateChange ; } else { if ( onGestureHandlerStateChange && typeof onGestureHandlerStateChange !== 'function' ) { throw new Error ( 'Nesting touch handlers with native animated driver is not supported yet' ) ; } } const events = { onGestureHandlerEvent : this . state . allowTouches ? gestureEventHandler : undefined , onGestureHandlerStateChange : this . state . allowTouches ? gestureStateEventHandler : undefined , } ; this . propsRef . current = events ; const child = React . Children . only ( this . props . children ) ; let grandChildren = child . props . children ; if ( Touchable . TOUCH_TARGET_DEBUG && child . type && ( child . type === 'RNGestureHandlerButton' || child . type . name === 'View' || child . type . displayName === 'View' ) ) { grandChildren = React . Children . toArray ( grandChildren ) ; grandChildren . push ( Touchable . renderDebugView ( { color : 'mediumspringgreen' , hitSlop : child . props . hitSlop , } ) ) ; } return React . cloneElement ( child , { ref : this . refHandler , collapsable : false , ... events , } , grandChildren ) ; } } return Handler ; }