UNPKG

leaflet-event-forwarder

Version:

Catches unhandled canvas layer events and re-dispatches them to the next layer in the stack

163 lines (137 loc) 4.63 kB
import L from 'leaflet'; let _options; let _prevTarget = null; const EventForwarder = L.Class.extend({ initialize: function (options) { _options = options; }, enable: function() { if (_options.events.click === true) { L.DomEvent.on(_options.map, 'click', this._handleClick, this); } if (_options.events.mousemove === true) { L.DomEvent.on(_options.map, 'mousemove', this._throttle(this._handleMouseMove, _options.throttleMs, _options.throttleOptions), this); } }, disable: function() { L.DomEvent.off(_options.map, 'click', this._handleClick, this); L.DomEvent.off(_options.map, 'mousemove', this._throttle(this._handleMouseMove, _options.throttleMs, _options.throttleOptions), this); }, /** * Handle `mousemove` event from map, i.e. forwards unhandled events * @param event * @private */ _handleMouseMove: function(event) { // we use the maps mousemove event to avoid registering listeners // for each individual layer, however this means we don't receive // the layers mouseover/out events so we need to fudge it a little if (event.originalEvent._stopped) { return; } // get the target pane var currentTarget = event.originalEvent.target; var stopped; var removed; // hide the target node removed = { node: currentTarget, pointerEvents: currentTarget.style.pointerEvents }; currentTarget.style.pointerEvents = 'none'; // attempt to grab the next layer below const nextTarget = document.elementFromPoint(event.originalEvent.clientX, event.originalEvent.clientY); const isCanvas = nextTarget.nodeName.toLowerCase() === 'canvas'; // target has changed so trigger mouseout previous if (_prevTarget && _prevTarget != nextTarget) { _prevTarget.dispatchEvent(new MouseEvent('mouseout', event.originalEvent)); } _prevTarget = nextTarget; // we keep drilling down until we get stopped, // or we reach the map container itself if ( nextTarget && nextTarget.nodeName.toLowerCase() !== 'body' && nextTarget.classList.value.indexOf('leaflet-container') === -1 ) { let eventType = isCanvas ? 'mousemove' : 'mouseover'; var ev = new MouseEvent(eventType, event.originalEvent); stopped = !nextTarget.dispatchEvent(ev); if (stopped || ev._stopped) { L.DomEvent.stop(event); } } // restore pointerEvents removed.node.style.pointerEvents = removed.pointerEvents; }, /** * Handle `click` event from map, i.e. forwards unhandled events * @param event * @private */ _handleClick: function(event) { if (event.originalEvent._stopped) { return; } // get the target pane var currentTarget = event.originalEvent.target; var stopped; var removed; // hide the target node removed = { node: currentTarget, pointerEvents: currentTarget.style.pointerEvents }; currentTarget.style.pointerEvents = 'none'; // attempt to grab the next layer below let nextTarget = document.elementFromPoint(event.originalEvent.clientX, event.originalEvent.clientY); // we keep drilling down until we get stopped, // or we reach the map container itself if ( nextTarget && nextTarget.nodeName.toLowerCase() !== 'body' && nextTarget.classList.value.indexOf('leaflet-container') === -1 ) { var ev = new MouseEvent(event.originalEvent.type, event.originalEvent); stopped = !nextTarget.dispatchEvent(ev); if (stopped || ev._stopped) { L.DomEvent.stop(event); } } // restore pointerEvents removed.node.style.pointerEvents = removed.pointerEvents; }, /** * Pinched from underscore * @param func * @param wait * @param options * @returns {Function} * @private */ _throttle: function (func, wait, options) { var context, args, result; var timeout = null; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : Date.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { var now = Date.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; } }); L.eventForwarder = function (options) { return new EventForwarder(options); }; export default L.eventForwarder;