UNPKG

leaflet

Version:

JavaScript library for mobile-friendly interactive maps

309 lines (241 loc) 10.1 kB
/* * @namespace DomEvent * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally. */ // Inspired by John Resig, Dean Edwards and YUI addEvent implementations. var eventsKey = '_leaflet_events'; L.DomEvent = { // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this // Adds a listener function (`fn`) to a particular DOM event type of the // element `el`. You can optionally specify the context of the listener // (object the `this` keyword will point to). You can also pass several // space-separated types (e.g. `'click dblclick'`). // @alternative // @function on(el: HTMLElement, eventMap: Object, context?: Object): this // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` on: function (obj, types, fn, context) { if (typeof types === 'object') { for (var type in types) { this._on(obj, type, types[type], fn); } } else { types = L.Util.splitWords(types); for (var i = 0, len = types.length; i < len; i++) { this._on(obj, types[i], fn, context); } } return this; }, // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this // Removes a previously added listener function. If no function is specified, // it will remove all the listeners of that particular DOM event from the element. // Note that if you passed a custom context to on, you must pass the same // context to `off` in order to remove the listener. // @alternative // @function off(el: HTMLElement, eventMap: Object, context?: Object): this // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` off: function (obj, types, fn, context) { if (typeof types === 'object') { for (var type in types) { this._off(obj, type, types[type], fn); } } else { types = L.Util.splitWords(types); for (var i = 0, len = types.length; i < len; i++) { this._off(obj, types[i], fn, context); } } return this; }, _on: function (obj, type, fn, context) { var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''); if (obj[eventsKey] && obj[eventsKey][id]) { return this; } var handler = function (e) { return fn.call(context || obj, e || window.event); }; var originalHandler = handler; if (L.Browser.pointer && type.indexOf('touch') === 0) { this.addPointerListener(obj, type, handler, id); } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { this.addDoubleTapListener(obj, handler, id); } else if ('addEventListener' in obj) { if (type === 'mousewheel') { obj.addEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); } else if ((type === 'mouseenter') || (type === 'mouseleave')) { handler = function (e) { e = e || window.event; if (L.DomEvent._isExternalTarget(obj, e)) { originalHandler(e); } }; obj.addEventListener(type === 'mouseenter' ? 'mouseover' : 'mouseout', handler, false); } else { if (type === 'click' && L.Browser.android) { handler = function (e) { return L.DomEvent._filterClick(e, originalHandler); }; } obj.addEventListener(type, handler, false); } } else if ('attachEvent' in obj) { obj.attachEvent('on' + type, handler); } obj[eventsKey] = obj[eventsKey] || {}; obj[eventsKey][id] = handler; return this; }, _off: function (obj, type, fn, context) { var id = type + L.stamp(fn) + (context ? '_' + L.stamp(context) : ''), handler = obj[eventsKey] && obj[eventsKey][id]; if (!handler) { return this; } if (L.Browser.pointer && type.indexOf('touch') === 0) { this.removePointerListener(obj, type, id); } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) { this.removeDoubleTapListener(obj, id); } else if ('removeEventListener' in obj) { if (type === 'mousewheel') { obj.removeEventListener('onwheel' in obj ? 'wheel' : 'mousewheel', handler, false); } else { obj.removeEventListener( type === 'mouseenter' ? 'mouseover' : type === 'mouseleave' ? 'mouseout' : type, handler, false); } } else if ('detachEvent' in obj) { obj.detachEvent('on' + type, handler); } obj[eventsKey][id] = null; return this; }, // @function stopPropagation(ev: DOMEvent): this // Stop the given event from propagation to parent elements. Used inside the listener functions: // ```js // L.DomEvent.on(div, 'click', function (ev) { // L.DomEvent.stopPropagation(ev); // }); // ``` stopPropagation: function (e) { if (e.stopPropagation) { e.stopPropagation(); } else if (e.originalEvent) { // In case of Leaflet event. e.originalEvent._stopped = true; } else { e.cancelBubble = true; } L.DomEvent._skipped(e); return this; }, // @function disableScrollPropagation(el: HTMLElement): this // Adds `stopPropagation` to the element's `'mousewheel'` events (plus browser variants). disableScrollPropagation: function (el) { return L.DomEvent.on(el, 'mousewheel', L.DomEvent.stopPropagation); }, // @function disableClickPropagation(el: HTMLElement): this // Adds `stopPropagation` to the element's `'click'`, `'doubleclick'`, // `'mousedown'` and `'touchstart'` events (plus browser variants). disableClickPropagation: function (el) { var stop = L.DomEvent.stopPropagation; L.DomEvent.on(el, L.Draggable.START.join(' '), stop); return L.DomEvent.on(el, { click: L.DomEvent._fakeStop, dblclick: stop }); }, // @function preventDefault(ev: DOMEvent): this // Prevents the default action of the DOM Event `ev` from happening (such as // following a link in the href of the a element, or doing a POST request // with page reload when a `<form>` is submitted). // Use it inside listener functions. preventDefault: function (e) { if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } return this; }, // @function stop(ev): this // Does `stopPropagation` and `preventDefault` at the same time. stop: function (e) { return L.DomEvent .preventDefault(e) .stopPropagation(e); }, // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point // Gets normalized mouse position from a DOM event relative to the // `container` or to the whole page if not specified. getMousePosition: function (e, container) { if (!container) { return new L.Point(e.clientX, e.clientY); } var rect = container.getBoundingClientRect(); return new L.Point( e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop); }, // Chrome on Win scrolls double the pixels as in other platforms (see #4538), // and Firefox scrolls device pixels, not CSS pixels _wheelPxFactor: (L.Browser.win && L.Browser.chrome) ? 2 : L.Browser.gecko ? window.devicePixelRatio : 1, // @function getWheelDelta(ev: DOMEvent): Number // Gets normalized wheel delta from a mousewheel DOM event, in vertical // pixels scrolled (negative if scrolling down). // Events from pointing devices without precise scrolling are mapped to // a best guess of 60 pixels. getWheelDelta: function (e) { return (L.Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta (e.deltaY && e.deltaMode === 0) ? -e.deltaY / L.DomEvent._wheelPxFactor : // Pixels (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages (e.deltaX || e.deltaZ) ? 0 : // Skip horizontal/depth wheel events e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages 0; }, _skipEvents: {}, _fakeStop: function (e) { // fakes stopPropagation by setting a special event flag, checked/reset with L.DomEvent._skipped(e) L.DomEvent._skipEvents[e.type] = true; }, _skipped: function (e) { var skipped = this._skipEvents[e.type]; // reset when checking, as it's only used in map container and propagates outside of the map this._skipEvents[e.type] = false; return skipped; }, // check if element really left/entered the event target (for mouseenter/mouseleave) _isExternalTarget: function (el, e) { var related = e.relatedTarget; if (!related) { return true; } try { while (related && (related !== el)) { related = related.parentNode; } } catch (err) { return false; } return (related !== el); }, // this is a horrible workaround for a bug in Android where a single touch triggers two click events _filterClick: function (e, handler) { var timeStamp = (e.timeStamp || (e.originalEvent && 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; } L.DomEvent._lastClick = timeStamp; handler(e); } }; // @function addListener(…): this // Alias to [`L.DomEvent.on`](#domevent-on) L.DomEvent.addListener = L.DomEvent.on; // @function removeListener(…): this // Alias to [`L.DomEvent.off`](#domevent-off) L.DomEvent.removeListener = L.DomEvent.off;