const MOVE_TOLERANCE = GestureRecognizer.SupportsTouches ? 40 : 0; const WAITING_FOR_NEXT_TAP_TO_START_TIMEOUT = 350; const WAITING_FOR_TAP_COMPLETION_TIMEOUT = 750; class TapGestureRecognizer extends GestureRecognizer { constructor(target, delegate) { super(target, delegate); this.numberOfTapsRequired = 1; this.numberOfTouchesRequired = 1; this.allowsRightMouseButton = false; } // Protected touchesBegan(event) { if (event.currentTarget !== this.target) return; if (event.button === 2 && !this.allowsRightMouseButton) return; super.touchesBegan(event); if (this.numberOfTouches !== this.numberOfTouchesRequired) { this.enterFailedState(); return; } this._startPoint = super.locationInElement(); this._startClientPoint = super.locationInClient(); this._rewindTimer(WAITING_FOR_TAP_COMPLETION_TIMEOUT); } touchesMoved(event) { const touchLocation = super.locationInElement(); const distance = Math.sqrt(Math.pow(this._startPoint.x - touchLocation.x, 2) + Math.pow(this._startPoint.y - touchLocation.y, 2)); if (distance > MOVE_TOLERANCE) this.enterFailedState(); } touchesEnded(event) { this._taps++; if (this._taps === this.numberOfTapsRequired) { // We call prevent default here to override the potential double-tap-to-zoom // behavior of the browser. event.preventDefault(); this.enterRecognizedState(); this.reset(); } this._rewindTimer(WAITING_FOR_NEXT_TAP_TO_START_TIMEOUT); } reset() { this._taps = 0; this._clearTimer(); } locationInElement(element) { const p = this._startPoint || new DOMPoint; if (!element) return p; // FIXME: are WebKitPoint and DOMPoint interchangeable? const wkPoint = window.webkitConvertPointFromPageToNode(element, new WebKitPoint(p.x, p.y)); return new DOMPoint(wkPoint.x, wkPoint.y); } locationInClient() { return this._startClientPoint || new DOMPoint; } // Private _clearTimer() { window.clearTimeout(this._timerId); delete this._timerId; } _rewindTimer(timeout) { this._clearTimer(); this._timerId = window.setTimeout(this._timerFired.bind(this), timeout); } _timerFired() { this.enterFailedState(); } }