UNPKG

react-native-gesture-handler

Version:

Declarative API exposing native platform touch and gesture system to React Native

1,122 lines (943 loc) 30.1 kB
/* eslint-disable @typescript-eslint/no-empty-function */ import { ActionType } from '../../ActionType'; import type { ActiveCursor, GestureTouchEvent, TouchAction, UserSelect, } from '../../handlers/gestureHandlerCommon'; import { MouseButton } from '../../handlers/gestureHandlerCommon'; import { PointerType } from '../../PointerType'; import { State } from '../../State'; import { TouchEventType } from '../../TouchEventType'; import { tagMessage } from '../../utils'; import type { GestureStateChangeEventWithHandlerData, GestureUpdateEventWithHandlerData, HandlerData, SingleGestureName, } from '../../v3/types'; import type { AdaptedEvent, Config, GestureHandlerNativeEvent, HitSlop, PointerData, PropsRef, ResultEvent, } from '../interfaces'; import { EventTypes } from '../interfaces'; import type EventManager from '../tools/EventManager'; import type { GestureHandlerDelegate } from '../tools/GestureHandlerDelegate'; import GestureHandlerOrchestrator from '../tools/GestureHandlerOrchestrator'; import InteractionManager from '../tools/InteractionManager'; import type { TrackerElement } from '../tools/PointerTracker'; import PointerTracker from '../tools/PointerTracker'; import type IGestureHandler from './IGestureHandler'; export default abstract class GestureHandler implements IGestureHandler { private _name!: SingleGestureName; private lastSentState: State | null = null; private _state: State = State.UNDETERMINED; private _shouldCancelWhenOutside = false; private _enabled: boolean | null = null; private viewRef: number | null = null; private propsRef: React.RefObject<PropsRef> | null = null; protected actionType: ActionType | null = null; private forAnimated: boolean = false; private forReanimated: boolean = false; private _handlerTag!: number; private _testID?: string | undefined = undefined; private hitSlop?: HitSlop | undefined = undefined; private manualActivation: boolean = false; private mouseButton?: MouseButton | undefined = undefined; private needsPointerData: boolean = false; private _tracker: PointerTracker = new PointerTracker(); private _enableContextMenu: boolean = false; private _activeCursor?: ActiveCursor | undefined = undefined; private _touchAction?: TouchAction | undefined = undefined; private _userSelect?: UserSelect | undefined = undefined; // Orchestrator properties private _activationIndex = 0; private _awaiting = false; private _active = false; private _attached = false; private _shouldResetProgress = false; private _pointerType: PointerType = PointerType.MOUSE; private _delegate: GestureHandlerDelegate<unknown, IGestureHandler>; public constructor( delegate: GestureHandlerDelegate<unknown, IGestureHandler> ) { this._delegate = delegate; } // // Initializing handler // protected init( viewRef: number, propsRef: React.RefObject<PropsRef>, actionType: ActionType ) { this.attached = true; this.propsRef = propsRef; this.viewRef = viewRef; this.actionType = actionType; this.state = State.UNDETERMINED; this.delegate.init(viewRef, this); } public usesNativeOrVirtualDetector(): boolean { return ( this.actionType === ActionType.NATIVE_DETECTOR || this.actionType === ActionType.VIRTUAL_DETECTOR ); } public detach() { if (this.state === State.ACTIVE) { this.cancel(); } else { this.fail(); } this.propsRef = null; this.viewRef = null; this.actionType = null; this.state = State.UNDETERMINED; this.forAnimated = false; this.forReanimated = false; this.attached = false; this.delegate.detach(); } public attachEventManager(manager: EventManager<unknown>): void { manager.setOnPointerDown(this.onPointerDown.bind(this)); manager.setOnPointerAdd(this.onPointerAdd.bind(this)); manager.setOnPointerUp(this.onPointerUp.bind(this)); manager.setOnPointerRemove(this.onPointerRemove.bind(this)); manager.setOnPointerMove(this.onPointerMove.bind(this)); manager.setOnPointerEnter(this.onPointerEnter.bind(this)); manager.setOnPointerLeave(this.onPointerLeave.bind(this)); manager.setOnPointerCancel(this.onPointerCancel.bind(this)); manager.setOnPointerOutOfBounds(this.onPointerOutOfBounds.bind(this)); manager.setOnPointerMoveOver(this.onPointerMoveOver.bind(this)); manager.setOnPointerMoveOut(this.onPointerMoveOut.bind(this)); manager.setOnWheel(this.onWheel.bind(this)); manager.registerListeners(); } // // Resetting handler // protected onCancel(): void {} protected onReset(): void {} protected resetProgress(): void {} public reset(): void { this.tracker.resetTracker(); this.onReset(); this.resetProgress(); this.delegate.reset(); this.state = State.UNDETERMINED; } // // State logic // public moveToState(newState: State, sendIfDisabled?: boolean) { if (this.state === newState) { return; } const oldState = this.state; this.state = newState; if ( this.tracker.trackedPointersCount > 0 && this.needsPointerData && this.isFinished() ) { this.cancelTouches(); } GestureHandlerOrchestrator.instance.onHandlerStateChange( this, newState, oldState, sendIfDisabled ); this.onStateChange(newState, oldState); if (!this.enabled && this.isFinished()) { this.state = State.UNDETERMINED; } } protected onStateChange(_newState: State, _oldState: State): void {} public begin(): void { if (!this.checkHitSlop()) { return; } if (this.state === State.UNDETERMINED) { this.moveToState(State.BEGAN); } } /** * @param {boolean} sendIfDisabled - Used when handler becomes disabled. With this flag orchestrator will be forced to send fail event */ public fail(sendIfDisabled?: boolean): void { if (this.state === State.ACTIVE || this.state === State.BEGAN) { // Here the order of calling the delegate and moveToState is important. // At this point we can use currentState as previuos state, because immediately after changing cursor we call moveToState method. this.delegate.onFail(); this.moveToState(State.FAILED, sendIfDisabled); } this.resetProgress(); } /** * @param {boolean} sendIfDisabled - Used when handler becomes disabled. With this flag orchestrator will be forced to send cancel event */ public cancel(sendIfDisabled?: boolean): void { if ( this.state === State.ACTIVE || this.state === State.UNDETERMINED || this.state === State.BEGAN ) { this.onCancel(); // Same as above - order matters this.delegate.onCancel(); this.moveToState(State.CANCELLED, sendIfDisabled); } } public activate(force = false) { if ( (this.manualActivation !== true || force) && this.state === State.BEGAN ) { this.delegate.onActivate(); this.moveToState(State.ACTIVE); } } public end() { if (this.state === State.BEGAN || this.state === State.ACTIVE) { // Same as above - order matters this.delegate.onEnd(); this.moveToState(State.END); } this.resetProgress(); } // // Methods for orchestrator // public getShouldResetProgress(): boolean { return this.shouldResetProgress; } public setShouldResetProgress(value: boolean): void { this.shouldResetProgress = value; } public shouldWaitForHandlerFailure(handler: IGestureHandler): boolean { if (handler === this) { return false; } return InteractionManager.instance.shouldWaitForHandlerFailure( this, handler ); } public shouldRequireToWaitForFailure(handler: IGestureHandler): boolean { if (handler === this) { return false; } return InteractionManager.instance.shouldRequireHandlerToWaitForFailure( this, handler ); } public shouldRecognizeSimultaneously(handler: IGestureHandler): boolean { if (handler === this) { return true; } return InteractionManager.instance.shouldRecognizeSimultaneously( this, handler ); } public shouldBeCancelledByOther(handler: IGestureHandler): boolean { if (handler === this) { return false; } return InteractionManager.instance.shouldHandlerBeCancelledBy( this, handler ); } public shouldBeginWithRecordedHandlers( _recorded: IGestureHandler[] ): boolean { return true; } public shouldAttachGestureToChildView(): boolean { return false; } // // Event actions // protected onPointerDown(event: AdaptedEvent): void { GestureHandlerOrchestrator.instance.recordHandlerIfNotPresent(this); this._pointerType = event.pointerType; if (this.pointerType === PointerType.TOUCH) { GestureHandlerOrchestrator.instance.cancelMouseAndPenGestures(this); } this.tryToSendTouchEvent(event); } // Adding another pointer to existing ones protected onPointerAdd(event: AdaptedEvent): void { this.tryToSendTouchEvent(event); } protected onPointerUp(event: AdaptedEvent): void { this.tryToSendTouchEvent(event); } // Removing pointer, when there is more than one pointers protected onPointerRemove(event: AdaptedEvent): void { this.tryToSendTouchEvent(event); } protected onPointerMove(event: AdaptedEvent): void { this.tryToSendMoveEvent(false, event); } protected onPointerLeave(event: AdaptedEvent): void { if (this.shouldCancelWhenOutside) { switch (this.state) { case State.ACTIVE: this.cancel(); break; case State.BEGAN: this.fail(); break; } return; } this.tryToSendTouchEvent(event); } protected onPointerEnter(event: AdaptedEvent): void { this.tryToSendTouchEvent(event); } protected onPointerCancel(_event: AdaptedEvent): void { // No need to send a cancel touch event explicitly here. `cancel` will // handle cancelling all tracked touches if the handler expects pointer data. if (GestureHandlerOrchestrator.instance.isHandlerRecorded(this)) { this.cancel(); } } protected onPointerOutOfBounds(event: AdaptedEvent): void { this.tryToSendMoveEvent(true, event); } protected onPointerMoveOver(_event: AdaptedEvent): void { // Used only by hover gesture handler atm } protected onPointerMoveOut(_event: AdaptedEvent): void { // Used only by hover gesture handler atm } protected onWheel(_event: AdaptedEvent): void { // Used only by pan gesture handler } protected tryToSendMoveEvent(out: boolean, event: AdaptedEvent): void { if ((out && this.shouldCancelWhenOutside) || !this.enabled) { return; } this.tryToSendTouchEvent(event); if (this.active) { this.sendEvent(this.state, this.state); } } protected tryToSendTouchEvent(event: AdaptedEvent): void { if (this.needsPointerData) { this.sendTouchEvent(event); } } public sendTouchEvent(event: AdaptedEvent): void { if (!this.enabled) { return; } this.ensurePropsRef(); const { onGestureHandlerEvent, onGestureHandlerTouchEvent, onGestureHandlerReanimatedTouchEvent, }: PropsRef = this.propsRef!.current; const touchEvent: ResultEvent<GestureTouchEvent> | undefined = this.transformTouchEvent(event); if (!touchEvent) { return; } if (!this.usesNativeOrVirtualDetector()) { onGestureHandlerEvent?.(touchEvent); return; } if (this.forReanimated) { onGestureHandlerReanimatedTouchEvent?.(touchEvent); } else { onGestureHandlerTouchEvent?.(touchEvent); } } // // Events Sending // public sendEvent = (newState: State, oldState: State): void => { const { onGestureHandlerEvent, onGestureHandlerStateChange, onGestureHandlerAnimatedEvent, onGestureHandlerReanimatedEvent, onGestureHandlerReanimatedStateChange, }: PropsRef = this.propsRef!.current; const isStateChange = this.lastSentState !== newState; const resultEvent: ResultEvent = !this.usesNativeOrVirtualDetector() ? this.transformEventData(newState, oldState) : isStateChange ? this.transformStateChangeEvent(newState, oldState) : this.transformUpdateEvent(newState); // In the v2 API oldState field has to be undefined, unless we send event state changed // Here the order is flipped to avoid workarounds such as making backup of the state and setting it to undefined first, then changing it back // Flipping order with setting oldState to undefined solves issue, when events were being sent twice instead of once // However, this may cause trouble in the future (but for now we don't know that) if (isStateChange) { this.lastSentState = newState; if (this.forReanimated) { onGestureHandlerReanimatedStateChange?.(resultEvent); } else { onGestureHandlerStateChange?.(resultEvent); } } if (this.state !== State.ACTIVE) { return; } // Cover only V3 path due to different event shape if (!isStateChange && this.usesNativeOrVirtualDetector()) { const handlerData = ( resultEvent.nativeEvent as GestureUpdateEventWithHandlerData<unknown> ).handlerData; if (this.shouldSuppressActiveUpdate(handlerData)) { return; } } (resultEvent.nativeEvent as GestureHandlerNativeEvent).oldState = undefined; if (this.forReanimated) { onGestureHandlerReanimatedEvent?.(resultEvent); } else if (this.forAnimated) { onGestureHandlerAnimatedEvent?.(resultEvent); } else { onGestureHandlerEvent?.(resultEvent); } }; protected shouldSuppressActiveUpdate( _handlerData: HandlerData<unknown> ): boolean { return false; } private transformEventData( newState: State, oldState: State ): ResultEvent<GestureHandlerNativeEvent> { this.ensureViewRef(this.viewRef); return { nativeEvent: { numberOfPointers: this.tracker.trackedPointersCount, state: newState, ...this.transformNativeEvent(), handlerTag: this.handlerTag, oldState: newState !== oldState ? oldState : undefined, pointerType: this.pointerType, }, timeStamp: Date.now(), }; } private transformStateChangeEvent( newState: State, oldState: State ): ResultEvent<GestureStateChangeEventWithHandlerData<unknown>> { this.ensureViewRef(this.viewRef); return { nativeEvent: { state: newState, handlerTag: this.handlerTag, oldState: oldState, handlerData: { numberOfPointers: this.tracker.trackedPointersCount, pointerType: this.pointerType, ...this.transformNativeEvent(), }, }, timeStamp: Date.now(), }; } private transformUpdateEvent( newState: State ): ResultEvent<GestureUpdateEventWithHandlerData<unknown>> { this.ensureViewRef(this.viewRef); return { nativeEvent: { state: newState, handlerTag: this.handlerTag, handlerData: { pointerType: this.pointerType, numberOfPointers: this.tracker.trackedPointersCount, ...this.transformNativeEvent(), }, }, timeStamp: Date.now(), }; } private transformTouchEvent( event: AdaptedEvent ): ResultEvent<GestureTouchEvent> | undefined { const rect = this.delegate.measureView(); const all: PointerData[] = []; const changed: PointerData[] = []; const trackerData = this.tracker.trackedPointers; // This if handles edge case where all pointers have been cancelled // When pointercancel is triggered, reset method is called. This means that tracker will be reset after first pointer being cancelled // The problem is, that handler will receive another pointercancel event from the rest of the pointers // To avoid crashing, we don't send event if tracker tracks no pointers, i.e. has been reset if (trackerData.size === 0 || !trackerData.has(event.pointerId)) { return; } trackerData.forEach((element: TrackerElement, key: number): void => { const id: number = this.tracker.getMappedTouchEventId(key); all.push({ id: id, x: element.abosoluteCoords.x - rect.pageX, y: element.abosoluteCoords.y - rect.pageY, absoluteX: element.abosoluteCoords.x, absoluteY: element.abosoluteCoords.y, }); }); // Each pointer sends its own event, so we want changed touches to contain only the pointer that has changed. // However, if the event is cancel, we want to cancel all pointers to avoid crashes if (event.eventType !== EventTypes.CANCEL) { changed.push({ id: this.tracker.getMappedTouchEventId(event.pointerId), x: event.x - rect.pageX, y: event.y - rect.pageY, absoluteX: event.x, absoluteY: event.y, }); } else { trackerData.forEach((element: TrackerElement, key: number): void => { const id: number = this.tracker.getMappedTouchEventId(key); changed.push({ id: id, x: element.abosoluteCoords.x - rect.pageX, y: element.abosoluteCoords.y - rect.pageY, absoluteX: element.abosoluteCoords.x, absoluteY: element.abosoluteCoords.y, }); }); } let eventType: TouchEventType = TouchEventType.UNDETERMINED; switch (event.eventType) { case EventTypes.DOWN: case EventTypes.ADDITIONAL_POINTER_DOWN: eventType = TouchEventType.TOUCHES_DOWN; break; case EventTypes.UP: case EventTypes.ADDITIONAL_POINTER_UP: eventType = TouchEventType.TOUCHES_UP; break; case EventTypes.MOVE: eventType = TouchEventType.TOUCHES_MOVE; break; case EventTypes.CANCEL: eventType = TouchEventType.TOUCHES_CANCEL; break; } // Here, when we receive up event, we want to decrease number of touches // That's because we want handler to send information that there's one pointer less // However, we still want this pointer to be present in allTouches array, so that its data can be accessed let numberOfTouches: number = all.length; if ( event.eventType === EventTypes.UP || event.eventType === EventTypes.ADDITIONAL_POINTER_UP ) { --numberOfTouches; } return { nativeEvent: { handlerTag: this.handlerTag, state: this.state, eventType: eventType, changedTouches: changed, allTouches: all, numberOfTouches: numberOfTouches, pointerType: this.pointerType, }, timeStamp: Date.now(), }; } private cancelTouches(): void { this.ensurePropsRef(); const rect = this.delegate.measureView(); const all: PointerData[] = []; const changed: PointerData[] = []; const trackerData = this.tracker.trackedPointers; if (trackerData.size === 0) { return; } trackerData.forEach((element: TrackerElement, key: number): void => { const id: number = this.tracker.getMappedTouchEventId(key); all.push({ id: id, x: element.abosoluteCoords.x - rect.pageX, y: element.abosoluteCoords.y - rect.pageY, absoluteX: element.abosoluteCoords.x, absoluteY: element.abosoluteCoords.y, }); changed.push({ id: id, x: element.abosoluteCoords.x - rect.pageX, y: element.abosoluteCoords.y - rect.pageY, absoluteX: element.abosoluteCoords.x, absoluteY: element.abosoluteCoords.y, }); }); const cancelEvent: ResultEvent<GestureTouchEvent> = { nativeEvent: { handlerTag: this.handlerTag, state: this.state, eventType: TouchEventType.TOUCHES_CANCEL, changedTouches: changed, allTouches: all, numberOfTouches: all.length, pointerType: this.pointerType, }, timeStamp: Date.now(), }; const { onGestureHandlerEvent, onGestureHandlerReanimatedTouchEvent, onGestureHandlerTouchEvent, }: PropsRef = this.propsRef!.current; if (this.actionType !== ActionType.NATIVE_DETECTOR) { onGestureHandlerEvent?.(cancelEvent); return; } if (this.forReanimated) { onGestureHandlerReanimatedTouchEvent?.(cancelEvent); } else { onGestureHandlerTouchEvent?.(cancelEvent); } } private ensurePropsRef(): void { if (!this.propsRef) { throw new Error( tagMessage('Cannot handle event when component props are null') ); } } private ensureViewRef(viewRef: any): asserts viewRef is number { if (!viewRef) { throw new Error(tagMessage('Cannot handle event when target is null')); } } protected transformNativeEvent(): Record<string, unknown> { // Those properties are shared by most handlers and if not this method will be overriden const lastCoords = this.tracker.getAbsoluteCoordsAverage(); const lastRelativeCoords = this.tracker.getRelativeCoordsAverage(); return { x: lastRelativeCoords.x, y: lastRelativeCoords.y, absoluteX: lastCoords.x, absoluteY: lastCoords.y, }; } // // Handling config // // Helper function to correctly set enabled property private maybeUpdateEnabled(enabled: boolean | undefined): boolean { if (enabled === undefined) { if (this._enabled !== null) { return false; } this._enabled = true; return true; } const prevEnabled = this._enabled; this._enabled = enabled; return enabled !== prevEnabled; } public setGestureConfig(config: Config) { this.resetConfig(); this.updateGestureConfig(config); } public updateGestureConfig(config: Partial<Config>): void { const enabledChanged = this.maybeUpdateEnabled(config.enabled); if (config.hitSlop !== undefined) { this.hitSlop = config.hitSlop; this.validateHitSlops(); } if (config.testID !== undefined) { this._testID = config.testID; } if (config.dispatchesAnimatedEvents !== undefined) { this.forAnimated = config.dispatchesAnimatedEvents; } if (config.dispatchesReanimatedEvents !== undefined) { this.forReanimated = config.dispatchesReanimatedEvents; } if (config.manualActivation !== undefined) { this.manualActivation = config.manualActivation; } if (config.mouseButton !== undefined) { this.mouseButton = config.mouseButton; } if (config.needsPointerData !== undefined) { this.needsPointerData = config.needsPointerData; } if (config.shouldCancelWhenOutside !== undefined) { this.shouldCancelWhenOutside = config.shouldCancelWhenOutside; } if (config.activeCursor !== undefined) { this._activeCursor = config.activeCursor; } let shouldUpdateDOM = false; if (config.enableContextMenu !== undefined) { this.enableContextMenu = config.enableContextMenu; shouldUpdateDOM = true; } if (config.touchAction !== undefined) { this._touchAction = config.touchAction; shouldUpdateDOM = true; } if (config.userSelect !== undefined) { this._userSelect = config.userSelect; shouldUpdateDOM = true; } if (enabledChanged) { this.delegate.onEnabledChange(); } else if (shouldUpdateDOM) { this.delegate.updateDOM(); } if (this.enabled) { return; } switch (this.state) { case State.ACTIVE: this.fail(true); break; case State.UNDETERMINED: GestureHandlerOrchestrator.instance.removeHandlerFromOrchestrator(this); break; default: this.cancel(true); break; } } private validateHitSlops(): void { if (!this.hitSlop) { return; } if ( this.hitSlop.left !== undefined && this.hitSlop.right !== undefined && this.hitSlop.width !== undefined ) { throw new Error( 'HitSlop Error: Cannot define left, right and width at the same time' ); } if ( this.hitSlop.width !== undefined && this.hitSlop.left === undefined && this.hitSlop.right === undefined ) { throw new Error( 'HitSlop Error: When width is defined, either left or right has to be defined' ); } if ( this.hitSlop.height !== undefined && this.hitSlop.top !== undefined && this.hitSlop.bottom !== undefined ) { throw new Error( 'HitSlop Error: Cannot define top, bottom and height at the same time' ); } if ( this.hitSlop.height !== undefined && this.hitSlop.top === undefined && this.hitSlop.bottom === undefined ) { throw new Error( 'HitSlop Error: When height is defined, either top or bottom has to be defined' ); } } private checkHitSlop(): boolean { if (!this.hitSlop) { return true; } const { width, height } = this.delegate.measureView(); let left = 0; let top = 0; let right: number = width; let bottom: number = height; if (this.hitSlop.horizontal !== undefined) { left -= this.hitSlop.horizontal; right += this.hitSlop.horizontal; } if (this.hitSlop.vertical !== undefined) { top -= this.hitSlop.vertical; bottom += this.hitSlop.vertical; } if (this.hitSlop.left !== undefined) { left = -this.hitSlop.left; } if (this.hitSlop.right !== undefined) { right = width + this.hitSlop.right; } if (this.hitSlop.top !== undefined) { top = -this.hitSlop.top; } if (this.hitSlop.bottom !== undefined) { bottom = height + this.hitSlop.bottom; } if (this.hitSlop.width !== undefined) { if (this.hitSlop.left !== undefined) { right = left + this.hitSlop.width; } else if (this.hitSlop.right !== undefined) { left = right - this.hitSlop.width; } } if (this.hitSlop.height !== undefined) { if (this.hitSlop.top !== undefined) { bottom = top + this.hitSlop.height; } else if (this.hitSlop.bottom !== undefined) { top = bottom - this.hitSlop.height; } } const rect = this.delegate.measureView(); const lastCoords = this.tracker.getLastAbsoluteCoords(); if (!lastCoords) { return false; } const offsetX: number = lastCoords.x - rect.pageX; const offsetY: number = lastCoords.y - rect.pageY; return ( offsetX >= left && offsetX <= right && offsetY >= top && offsetY <= bottom ); } public isButtonInConfig(mouseButton: MouseButton | undefined) { return ( !mouseButton || (!this.mouseButton && mouseButton === MouseButton.LEFT) || (this.mouseButton && mouseButton & this.mouseButton) ); } protected resetConfig(): void { this._testID = undefined; this.manualActivation = false; this.shouldCancelWhenOutside = false; this.mouseButton = undefined; this.hitSlop = undefined; this.needsPointerData = false; this.forAnimated = false; this.forReanimated = false; this.enableContextMenu = false; this._activeCursor = undefined; this._touchAction = undefined; this._userSelect = undefined; } public onDestroy(): void { GestureHandlerOrchestrator.instance.removeHandlerFromOrchestrator(this); this.delegate.destroy(); } // // Getters and setters // public get handlerTag() { return this._handlerTag; } public set handlerTag(value: number) { this._handlerTag = value; } public get testID() { return this._testID; } public get delegate() { return this._delegate; } public get tracker() { return this._tracker; } public get state(): State { return this._state; } protected set state(value: State) { this._state = value; } public get shouldCancelWhenOutside() { return this._shouldCancelWhenOutside; } protected set shouldCancelWhenOutside(value) { this._shouldCancelWhenOutside = value; } public get enabled() { return this._enabled; } public get pointerType(): PointerType { return this._pointerType; } public get active() { return this._active; } protected set active(value) { this._active = value; } public get awaiting() { return this._awaiting; } protected set awaiting(value) { this._awaiting = value; } public get attached() { return this._attached; } protected set attached(value) { this._attached = value; } public get activationIndex() { return this._activationIndex; } protected set activationIndex(value) { this._activationIndex = value; } public get shouldResetProgress() { return this._shouldResetProgress; } protected set shouldResetProgress(value) { this._shouldResetProgress = value; } public get enableContextMenu() { return this._enableContextMenu; } protected set enableContextMenu(value) { this._enableContextMenu = value; } public get activeCursor() { return this._activeCursor; } public get touchAction() { return this._touchAction; } public get userSelect() { return this._userSelect; } public get name() { return this._name; } protected set name(value: SingleGestureName) { this._name = value; } /** * Whether the handler represents a continuous gesture rather than a discrete one. */ public readonly isContinuous: boolean = false; public getTrackedPointersID(): number[] { return this.tracker.trackedPointersIDs; } private isFinished(): boolean { return ( this.state === State.END || this.state === State.FAILED || this.state === State.CANCELLED ); } }