import {Map} from '../Map'; import {Handler} from '../../core/Handler'; import * as DomEvent from '../../dom/DomEvent'; import {Point} from '../../geometry/Point'; import * as Util from '../../core/Util'; import * as DomUtil from '../../dom/DomUtil'; import * as Browser from '../../core/Browser'; /* * L.Map.Tap is used to enable mobile hacks like quick taps and long hold. */ // @namespace Map // @section Interaction Options Map.mergeOptions({ // @section Touch interaction options // @option tap: Boolean = true // Enables mobile hacks for supporting instant taps (fixing 200ms click // delay on iOS/Android) and touch holds (fired as `contextmenu` events). tap: true, // @option tapTolerance: Number = 15 // The max number of pixels a user can shift his finger during touch // for it to be considered a valid tap. tapTolerance: 15 }); export var Tap = Handler.extend({ addHooks: function () { DomEvent.on(this._map._container, 'touchstart', this._onDown, this); }, removeHooks: function () { DomEvent.off(this._map._container, 'touchstart', this._onDown, this); }, _onDown: function (e) { if (!e.touches) { return; } DomEvent.preventDefault(e); this._fireClick = true; // don't simulate click or track longpress if more than 1 touch if (e.touches.length > 1) { this._fireClick = false; clearTimeout(this._holdTimeout); return; } var first = e.touches[0], el = first.target; this._startPos = this._newPos = new Point(first.clientX, first.clientY); // if touching a link, highlight it if (el.tagName && el.tagName.toLowerCase() === 'a') { DomUtil.addClass(el, 'leaflet-active'); } // simulate long hold but setting a timeout this._holdTimeout = setTimeout(Util.bind(function () { if (this._isTapValid()) { this._fireClick = false; this._onUp(); this._simulateEvent('contextmenu', first); } }, this), 1000); this._simulateEvent('mousedown', first); DomEvent.on(document, { touchmove: this._onMove, touchend: this._onUp }, this); }, _onUp: function (e) { clearTimeout(this._holdTimeout); DomEvent.off(document, { touchmove: this._onMove, touchend: this._onUp }, this); if (this._fireClick && e && e.changedTouches) { var first = e.changedTouches[0], el = first.target; if (el && el.tagName && el.tagName.toLowerCase() === 'a') { DomUtil.removeClass(el, 'leaflet-active'); } this._simulateEvent('mouseup', first); // simulate click if the touch didn't move too much if (this._isTapValid()) { this._simulateEvent('click', first); } } }, _isTapValid: function () { return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; }, _onMove: function (e) { var first = e.touches[0]; this._newPos = new Point(first.clientX, first.clientY); this._simulateEvent('mousemove', first); }, _simulateEvent: function (type, e) { var simulatedEvent = document.createEvent('MouseEvents'); simulatedEvent._simulated = true; e.target._simulatedClick = true; simulatedEvent.initMouseEvent( type, true, true, window, 1, e.screenX, e.screenY, e.clientX, e.clientY, false, false, false, false, 0, null); e.target.dispatchEvent(simulatedEvent); } }); // @section Handlers // @property tap: Handler // Mobile touch hacks (quick tap and touch hold) handler. if (Browser.touch && !Browser.pointer) { Map.addInitHook('addHandler', 'tap', Tap); }