import * as React from 'react' ; import { Component } from 'react' ; import { Animated , Platform , StyleProp , ViewStyle , TouchableWithoutFeedbackProps , } from 'react-native' ; import { State } from '../../State' ; import { BaseButton } from '../GestureButtons' ; import { GestureEvent , HandlerStateChangeEvent , } from '../../handlers/gestureHandlerCommon' ; import { NativeViewGestureHandlerPayload } from '../../handlers/NativeViewGestureHandler' ; import { TouchableNativeFeedbackExtraProps } from './TouchableNativeFeedback.android' ; /**
 * Each touchable is a states' machine which preforms transitions.
 * On very beginning (and on the very end or recognition) touchable is
 * UNDETERMINED. Then it moves to BEGAN. If touchable recognizes that finger
 * travel outside it transits to special MOVED_OUTSIDE state. Gesture recognition
 * finishes in UNDETERMINED state.
 */ export const TOUCHABLE_STATE = { UNDETERMINED : 0 , BEGAN : 1 , MOVED_OUTSIDE : 2 , } as const ; type TouchableState = typeof TOUCHABLE_STATE [ keyof typeof TOUCHABLE_STATE ] ; export interface GenericTouchableProps extends TouchableWithoutFeedbackProps { onPress ? : ( ) => void ; onPressIn ? : ( ) => void ; onPressOut ? : ( ) => void ; onLongPress ? : ( ) => void ; nativeID ? : string ; shouldActivateOnStart ? : boolean ; disallowInterruption ? : boolean ; containerStyle ? : StyleProp < ViewStyle > ; } interface InternalProps { extraButtonProps : TouchableNativeFeedbackExtraProps ; onStateChange ? : ( oldState : TouchableState , newState : TouchableState ) => void ; } type Timeout = ReturnType < typeof setTimeout > | null | undefined ; /**
 * GenericTouchable is not intented to be used as it is.
 * Should be treated as a source for the rest of touchables
 */ export default class GenericTouchable extends Component < GenericTouchableProps & InternalProps > { static defaultProps = { delayLongPress : 600 , extraButtonProps : { rippleColor : 'transparent' , exclusive : true , } , } ; pressInTimeout : Timeout ; pressOutTimeout : Timeout ; longPressTimeout : Timeout ; longPressDetected = false ; pointerInside = true ; STATE : TouchableState = TOUCHABLE_STATE . UNDETERMINED ; handlePressIn ( ) { if ( this . props . delayPressIn ) { this . pressInTimeout = setTimeout ( ( ) => { this . moveToState ( TOUCHABLE_STATE . BEGAN ) ; this . pressInTimeout = null ; } , this . props . delayPressIn ) ; } else { this . moveToState ( TOUCHABLE_STATE . BEGAN ) ; } if ( this . props . onLongPress ) { const time = ( this . props . delayPressIn || 0 ) + ( this . props . delayLongPress || 0 ) ; this . longPressTimeout = setTimeout ( this . onLongPressDetected , time ) ; } } handleMoveOutside ( ) { if ( this . props . delayPressOut ) { this . pressOutTimeout = this . pressOutTimeout || setTimeout ( ( ) => { this . moveToState ( TOUCHABLE_STATE . MOVED_OUTSIDE ) ; this . pressOutTimeout = null ; } , this . props . delayPressOut ) ; } else { this . moveToState ( TOUCHABLE_STATE . MOVED_OUTSIDE ) ; } } handleGoToUndetermined ( ) { clearTimeout ( this . pressOutTimeout ! ) ; if ( this . props . delayPressOut ) { this . pressOutTimeout = setTimeout ( ( ) => { if ( this . STATE === TOUCHABLE_STATE . UNDETERMINED ) { this . moveToState ( TOUCHABLE_STATE . BEGAN ) ; } this . moveToState ( TOUCHABLE_STATE . UNDETERMINED ) ; this . pressOutTimeout = null ; } , this . props . delayPressOut ) ; } else { if ( this . STATE === TOUCHABLE_STATE . UNDETERMINED ) { this . moveToState ( TOUCHABLE_STATE . BEGAN ) ; } this . moveToState ( TOUCHABLE_STATE . UNDETERMINED ) ; } } componentDidMount ( ) { this . reset ( ) ; } reset ( ) { this . longPressDetected = false ; this . pointerInside = true ; clearTimeout ( this . pressInTimeout ! ) ; clearTimeout ( this . pressOutTimeout ! ) ; clearTimeout ( this . longPressTimeout ! ) ; this . pressOutTimeout = null ; this . longPressTimeout = null ; this . pressInTimeout = null ; } moveToState ( newState ) { if ( newState === this . STATE ) { return ; } if ( newState === TOUCHABLE_STATE . BEGAN ) { this . props . onPressIn ?. ( ) ; } else if ( newState === TOUCHABLE_STATE . MOVED_OUTSIDE ) { this . props . onPressOut ?. ( ) ; } else if ( newState === TOUCHABLE_STATE . UNDETERMINED ) { this . reset ( ) ; if ( this . STATE === TOUCHABLE_STATE . BEGAN ) { this . props . onPressOut ?. ( ) ; } } this . props . onStateChange ?. ( this . STATE , newState ) ; this . STATE = newState ; } onGestureEvent = ( { nativeEvent : { pointerInside } , } ) => { if ( this . pointerInside !== pointerInside ) { if ( pointerInside ) { this . onMoveIn ( ) ; } else { this . onMoveOut ( ) ; } } this . pointerInside = pointerInside ; } ; onHandlerStateChange = ( { nativeEvent , } ) => { const { state } = nativeEvent ; if ( state === State . CANCELLED || state === State . FAILED ) { this . moveToState ( TOUCHABLE_STATE . UNDETERMINED ) ; } else if ( state === ( Platform . OS !== 'android' ? State . ACTIVE : State . BEGAN ) && this . STATE === TOUCHABLE_STATE . UNDETERMINED ) { this . handlePressIn ( ) ; } else if ( state === State . END ) { const shouldCallOnPress = ! this . longPressDetected && this . STATE !== TOUCHABLE_STATE . MOVED_OUTSIDE && this . pressOutTimeout === null ; this . handleGoToUndetermined ( ) ; if ( shouldCallOnPress ) { this . props . onPress ?. ( ) ; } } } ; onLongPressDetected = ( ) => { this . longPressDetected = true ; this . props . onLongPress ?. ( ) ; } ; componentWillUnmount ( ) { this . reset ( ) ; } onMoveIn ( ) { if ( this . STATE === TOUCHABLE_STATE . MOVED_OUTSIDE ) { this . moveToState ( TOUCHABLE_STATE . BEGAN ) ; } } onMoveOut ( ) { clearTimeout ( this . longPressTimeout ! ) ; this . longPressTimeout = null ; if ( this . STATE === TOUCHABLE_STATE . BEGAN ) { this . handleMoveOutside ( ) ; } } render ( ) { const coreProps = { accessible : this . props . accessible !== false , accessibilityLabel : this . props . accessibilityLabel , accessibilityHint : this . props . accessibilityHint , accessibilityRole : this . props . accessibilityRole , accessibilityState : this . props . accessibilityState , nativeID : this . props . nativeID , onLayout : this . props . onLayout , hitSlop : this . props . hitSlop , } ; return ( < BaseButton style = { this . props . containerStyle } onHandlerStateChange = { this . props . disabled ? undefined : this . onHandlerStateChange } onGestureEvent = { this . onGestureEvent } hitSlop = { this . props . hitSlop } shouldActivateOnStart = { this . props . shouldActivateOnStart } disallowInterruption = { this . props . disallowInterruption } testID = { this . props . testID } { ... this . props . extraButtonProps } > < Animated . View { ... coreProps } style = { this . props . style } > { this . props . children } < /Animated.View> < /BaseButton>); } }