L.Map.mergeOptions({ touchExtend: true }); /** * @class L.Map.TouchExtend * @aka TouchExtend */ L.Map.TouchExtend = L.Handler.extend({ // @method initialize(): void // Sets TouchExtend private accessor variables initialize: function (map) { this._map = map; this._container = map._container; this._pane = map._panes.overlayPane; }, // @method addHooks(): void // Adds dom listener events to the map container addHooks: function () { L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this); L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this); L.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this); if (this._detectIE()) { L.DomEvent.on(this._container, 'MSPointerDown', this._onTouchStart, this); L.DomEvent.on(this._container, 'MSPointerUp', this._onTouchEnd, this); L.DomEvent.on(this._container, 'MSPointerMove', this._onTouchMove, this); L.DomEvent.on(this._container, 'MSPointerCancel', this._onTouchCancel, this); } else { L.DomEvent.on(this._container, 'touchcancel', this._onTouchCancel, this); L.DomEvent.on(this._container, 'touchleave', this._onTouchLeave, this); } }, // @method removeHooks(): void // Removes dom listener events from the map container removeHooks: function () { L.DomEvent.off(this._container, 'touchstart', this._onTouchStart, this); L.DomEvent.off(this._container, 'touchend', this._onTouchEnd, this); L.DomEvent.off(this._container, 'touchmove', this._onTouchMove, this); if (this._detectIE()) { L.DomEvent.off(this._container, 'MSPointerDown', this._onTouchStart, this); L.DomEvent.off(this._container, 'MSPointerUp', this._onTouchEnd, this); L.DomEvent.off(this._container, 'MSPointerMove', this._onTouchMove, this); L.DomEvent.off(this._container, 'MSPointerCancel', this._onTouchCancel, this); } else { L.DomEvent.off(this._container, 'touchcancel', this._onTouchCancel, this); L.DomEvent.off(this._container, 'touchleave', this._onTouchLeave, this); } }, _touchEvent: function (e, type) { // #TODO: fix the pageX error that is do a bug in Android where a single touch triggers two click events // _filterClick is what leaflet uses as a workaround. // This is a problem with more things than just android. Another problem is touchEnd has no touches in // its touch list. var touchEvent = {}; if (typeof e.touches !== 'undefined') { if (!e.touches.length) { return; } touchEvent = e.touches[0]; } else if (e.pointerType === 'touch') { touchEvent = e; if (!this._filterClick(e)) { return; } } else { return; } var containerPoint = this._map.mouseEventToContainerPoint(touchEvent), layerPoint = this._map.mouseEventToLayerPoint(touchEvent), latlng = this._map.layerPointToLatLng(layerPoint); this._map.fire(type, { latlng: latlng, layerPoint: layerPoint, containerPoint: containerPoint, pageX: touchEvent.pageX, pageY: touchEvent.pageY, originalEvent: e }); }, /** Borrowed from Leaflet and modified for bool ops **/ _filterClick: function (e) { var timeStamp = (e.timeStamp || e.originalEvent.timeStamp), elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick); // are they closer together than 500ms yet more than 100ms? // Android typically triggers them ~300ms apart while multiple listeners // on the same event should be triggered far faster; // or check if click is simulated on the element, and if it is, reject any non-simulated events if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) { L.DomEvent.stop(e); return false; } L.DomEvent._lastClick = timeStamp; return true; }, _onTouchStart: function (e) { if (!this._map._loaded) { return; } var type = 'touchstart'; this._touchEvent(e, type); }, _onTouchEnd: function (e) { if (!this._map._loaded) { return; } var type = 'touchend'; this._touchEvent(e, type); }, _onTouchCancel: function (e) { if (!this._map._loaded) { return; } var type = 'touchcancel'; if (this._detectIE()) { type = 'pointercancel'; } this._touchEvent(e, type); }, _onTouchLeave: function (e) { if (!this._map._loaded) { return; } var type = 'touchleave'; this._touchEvent(e, type); }, _onTouchMove: function (e) { if (!this._map._loaded) { return; } var type = 'touchmove'; this._touchEvent(e, type); }, _detectIE: function () { var ua = window.navigator.userAgent; var msie = ua.indexOf('MSIE '); if (msie > 0) { // IE 10 or older => return version number return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); } var trident = ua.indexOf('Trident/'); if (trident > 0) { // IE 11 => return version number var rv = ua.indexOf('rv:'); return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); } var edge = ua.indexOf('Edge/'); if (edge > 0) { // IE 12 => return version number return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); } // other browser return false; } }); L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend); /** * @class L.Marker.Touch * @aka Marker.Touch * * This isn't full Touch support. This is just to get markers to also support dom touch events after creation * #TODO: find a better way of getting markers to support touch. */ L.Marker.Touch = L.Marker.extend({ _initInteraction: function () { if (!this.addInteractiveTarget) { // 0.7.x support return this._initInteractionLegacy(); } // TODO this may need be updated to re-add touch events for 1.0+ return L.Marker.prototype._initInteraction.apply(this); }, // This is an exact copy of https://github.com/Leaflet/Leaflet/blob/v0.7/src/layer/marker/Marker.js // with the addition of the touch events _initInteractionLegacy: function () { if (!this.options.clickable) { return; } // TODO refactor into something shared with Map/Path/etc. to DRY it up var icon = this._icon, events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu', 'touchstart', 'touchend', 'touchmove']; if (this._detectIE) { events.concat(['MSPointerDown', 'MSPointerUp', 'MSPointerMove', 'MSPointerCancel']); } else { events.concat(['touchcancel']); } L.DomUtil.addClass(icon, 'leaflet-clickable'); L.DomEvent.on(icon, 'click', this._onMouseClick, this); L.DomEvent.on(icon, 'keypress', this._onKeyPress, this); for (var i = 0; i < events.length; i++) { L.DomEvent.on(icon, events[i], this._fireMouseEvent, this); } if (L.Handler.MarkerDrag) { this.dragging = new L.Handler.MarkerDrag(this); if (this.options.draggable) { this.dragging.enable(); } } }, _detectIE: function () { var ua = window.navigator.userAgent; var msie = ua.indexOf('MSIE '); if (msie > 0) { // IE 10 or older => return version number return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10); } var trident = ua.indexOf('Trident/'); if (trident > 0) { // IE 11 => return version number var rv = ua.indexOf('rv:'); return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10); } var edge = ua.indexOf('Edge/'); if (edge > 0) { // IE 12 => return version number return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10); } // other browser return false; } });