import CoordinatesRecognizer from './CoordinatesRecognizer' ; import { getPointerEventValues , getGenericEventData } from '../utils/event' ; import { calculateDistance , sign } from '../utils/math' ; import { getStartGestureState , getGenericPayload } from './Recognizer' ; import { addBindings , updateWindowListeners , clearWindowListeners } from '../Controller' ; export const TAP_DISTANCE_THRESHOLD = 3 ; export const SWIPE_MAX_ELAPSED_TIME = 220 ; export class DragRecognizer extends CoordinatesRecognizer < 'drag' > { readonly ingKey = 'dragging' ; readonly stateKey = 'drag' ; /**
     * TODO add back when setPointerCapture is widely wupported
     * https://caniuse.com/#search=setPointerCapture
     * private setPointers = (event: UseGestureEvent<PointerEvent>) => {
     *   const { currentTarget, pointerId } = event
     *   if (currentTarget) currentTarget.setPointerCapture(pointerId)
     *   this.updateGestureState({ currentTarget, pointerId })
     * }
  
     * private removePointers = () => {
     *   const { currentTarget, pointerId } = this.state
     *   if (currentTarget && pointerId) currentTarget.releasePointerCapture(pointerId)
     * }
     */ onDragStart = ( event ) : void => { if ( ! this . enabled || this . state . _active ) return ; /**
         * TODO add back when setPointerCapture is widely supported
         * this.setPointers(event as PointerEvent)
         */ updateWindowListeners ( this . controller , this . stateKey , [ [ 'pointermove' , this . onDragChange ] , [ 'pointerup' , this . onDragEnd ] , [ 'pointercancel' , this . onDragEnd ] , ] ) ; this . updateGestureState ( { _pointerId : event . pointerId } ) ; if ( this . config . delay > 0 ) { this . state . _delayedEvent = true ; } else { this . startDrag ( event ) ; } } ; startDrag ( event ) { const values = getPointerEventValues ( event ) ; this . updateSharedState ( getGenericEventData ( event ) ) ; this . updateGestureState ( { ... getStartGestureState ( this , values , event ) , ... getGenericPayload ( this , event , true ) , _pointerId : event . pointerId , cancel : this . onCancel , } ) ; this . updateGestureState ( this . getMovement ( values ) ) ; this . fireGestureHandler ( ) ; } onDragChange = ( event ) : void => { if ( this . state . canceled ) return ; if ( event . pointerId !== this . state . _pointerId ) return ; if ( ! this . state . _active ) { if ( this . state . _delayedEvent ) { this . clearTimeout ( ) ; this . startDrag ( event ) ; } return ; } const genericEventData = getGenericEventData ( event ) ; if ( ! genericEventData . down ) { this . onDragEnd ( event ) ; return ; } this . updateSharedState ( genericEventData ) ; const values = getPointerEventValues ( event ) ; const kinematics = this . getKinematics ( values , event ) ; const genericPayload = getGenericPayload ( this , event ) ; let { _isTap } = this . state ; const realDistance = calculateDistance ( kinematics . _movement ! ) ; if ( _isTap && realDistance >= TAP_DISTANCE_THRESHOLD ) _isTap = false ; this . updateGestureState ( { ... genericPayload , ... kinematics , _isTap } ) ; this . fireGestureHandler ( ) ; } ; onDragEnd = ( event ) : void => { if ( event . pointerId !== this . state . _pointerId ) return ; this . state . _active = false ; this . updateSharedState ( { down : false , buttons : 0 , touches : 0 } ) ; const tap = this . state . _isTap ; const [ vx , vy ] = this . state . velocities ; const [ mx , my ] = this . state . movement ; const [ ix , iy ] = this . state . _intentional ; const [ svx , svy ] = this . config . swipeVelocity ; const [ sx , sy ] = this . config . swipeDistance ; const endState = { ... getGenericPayload ( this , event ) , ... this . getMovement ( this . state . values ) , } ; const swipe = [ 0 , 0 ] ; if ( endState . elapsedTime < SWIPE_MAX_ELAPSED_TIME ) { if ( ix !== false && Math . abs ( vx ) > svx && Math . abs ( mx ) > sx ) swipe [ 0 ] = sign ( vx ) ; if ( iy !== false && Math . abs ( vy ) > svy && Math . abs ( my ) > sy ) swipe [ 1 ] = sign ( vy ) ; } this . updateGestureState ( { ... endState , tap , swipe } ) ; this . fireGestureHandler ( tap === true ) ; } ; clean = ( ) : void => { super . clean ( ) ; this . state . _delayedEvent = false ; clearWindowListeners ( this . controller , this . stateKey ) ; } ; onCancel = ( ) : void => { if ( this . state . canceled ) return ; this . updateGestureState ( { canceled : true } ) ; this . state . _active = false ; this . updateSharedState ( { down : false , buttons : 0 , touches : 0 } ) ; requestAnimationFrame ( ( ) => this . fireGestureHandler ( ) ) ; } ; onClick = ( event ) : void => { if ( ! this . state . _isTap ) event . stopPropagation ( ) ; } ; addBindings ( bindings ) { addBindings ( bindings , 'onPointerDown' , this . onDragStart ) ; if ( this . config . filterTaps ) { const handler = this . controller . config . eventOptions . capture ? 'onClick' : 'onClickCapture' ; addBindings ( bindings , handler , this . onClick ) ; } } }