import "core-js/modules/es.array.splice"; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } import { polymerWrap, closest } from './helpers/dom/element'; import { hasOwnProperty } from './helpers/object'; import { isWebComponentSupportedNatively, isPassiveEventSupported } from './helpers/feature'; import { stopImmediatePropagation as _stopImmediatePropagation } from './helpers/dom/event'; /** * Counter which tracks unregistered listeners (useful for detecting memory leaks). * * @type {Number} */ var listenersCounter = 0; /** * Event DOM manager for internal use in Handsontable. * * @class EventManager * @util */ var EventManager = /*#__PURE__*/ function () { /** * @param {Object} [context=null] * @private */ function EventManager() { var context = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; _classCallCheck(this, EventManager); this.context = context || this; if (!this.context.eventListeners) { this.context.eventListeners = []; // TODO perf It would be more performant if every instance of EventManager tracked its own listeners only } } /** * Register specified listener (`eventName`) to the element. * * @param {Element} element Target element. * @param {String} eventName Event name. * @param {Function} callback Function which will be called after event occur. * @param {AddEventListenerOptions|Boolean} [options] Listener options if object or useCapture if boolean. * @returns {Function} Returns function which you can easily call to remove that event */ _createClass(EventManager, [{ key: "addEventListener", value: function addEventListener(element, eventName, callback) { var _this = this; var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; var context = this.context; function callbackProxy(event) { callback.call(this, extendEvent(context, event)); } if (typeof options !== 'boolean' && !isPassiveEventSupported()) { options = false; } this.context.eventListeners.push({ element: element, event: eventName, callback: callback, callbackProxy: callbackProxy, options: options, eventManager: this }); element.addEventListener(eventName, callbackProxy, options); listenersCounter += 1; return function () { _this.removeEventListener(element, eventName, callback); }; } /** * Remove the event listener previously registered. * * @param {Element} element Target element. * @param {String} eventName Event name. * @param {Function} callback Function to remove from the event target. It must be the same as during registration listener. * @param {Boolean} [onlyOwnEvents] Whether whould remove only events registered using this instance of EventManager */ }, { key: "removeEventListener", value: function removeEventListener(element, eventName, callback) { var onlyOwnEvents = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; var len = this.context.eventListeners.length; var tmpEvent; while (len) { len -= 1; tmpEvent = this.context.eventListeners[len]; if (tmpEvent.event === eventName && tmpEvent.element === element) { if (callback && callback !== tmpEvent.callback) { /* eslint-disable no-continue */ continue; } if (onlyOwnEvents && tmpEvent.eventManager !== this) { continue; } this.context.eventListeners.splice(len, 1); tmpEvent.element.removeEventListener(tmpEvent.event, tmpEvent.callbackProxy, tmpEvent.options); listenersCounter -= 1; } } } /** * Clear all previously registered events. * * @private * @since 0.15.0-beta3 * @param {Boolean} [onlyOwnEvents] Whether whould remove only events registered using this instance of EventManager */ }, { key: "clearEvents", value: function clearEvents() { var onlyOwnEvents = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; if (!this.context) { return; } var len = this.context.eventListeners.length; while (len) { len -= 1; var event = this.context.eventListeners[len]; if (event) { this.removeEventListener(event.element, event.event, event.callback, onlyOwnEvents); } } } /** * Clear all previously registered events. */ }, { key: "clear", value: function clear() { this.clearEvents(); } /** * Destroy instance of EventManager, clearing all events of the context */ }, { key: "destroy", value: function destroy() { this.clearEvents(); this.context = null; } /** * Destroy instance of EventManager, clearing only the own events */ }, { key: "destroyWithOwnEventsOnly", value: function destroyWithOwnEventsOnly() { this.clearEvents(true); this.context = null; } /** * Trigger event at the specified target element. * * @param {Element} element Target element. * @param {String} eventName Event name. */ }, { key: "fireEvent", value: function fireEvent(element, eventName) { var rootDocument = element.document; var rootWindow = element; if (!rootDocument) { rootDocument = element.ownerDocument ? element.ownerDocument : element; rootWindow = rootDocument.defaultView; } var options = { bubbles: true, cancelable: eventName !== 'mousemove', view: rootWindow, detail: 0, screenX: 0, screenY: 0, clientX: 1, clientY: 1, ctrlKey: false, altKey: false, shiftKey: false, metaKey: false, button: 0, relatedTarget: undefined }; var event; if (rootDocument.createEvent) { event = rootDocument.createEvent('MouseEvents'); event.initMouseEvent(eventName, options.bubbles, options.cancelable, options.view, options.detail, options.screenX, options.screenY, options.clientX, options.clientY, options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, options.relatedTarget || rootDocument.body.parentNode); } else { event = rootDocument.createEventObject(); } if (element.dispatchEvent) { element.dispatchEvent(event); } else { element.fireEvent("on".concat(eventName), event); } } }]); return EventManager; }(); /** * @param {Object} context * @param {Event} event * @private * @returns {*} */ function extendEvent(context, event) { var componentName = 'HOT-TABLE'; var isHotTableSpotted; var fromElement; var realTarget; var target; var len; event.isTargetWebComponent = false; event.realTarget = event.target; var nativeStopImmediatePropagation = event.stopImmediatePropagation; event.stopImmediatePropagation = function () { nativeStopImmediatePropagation.apply(this); _stopImmediatePropagation(this); }; if (!EventManager.isHotTableEnv) { return event; } // eslint-disable-next-line no-param-reassign event = polymerWrap(event); len = event.path ? event.path.length : 0; while (len) { len -= 1; if (event.path[len].nodeName === componentName) { isHotTableSpotted = true; } else if (isHotTableSpotted && event.path[len].shadowRoot) { target = event.path[len]; break; } if (len === 0 && !target) { target = event.path[len]; } } if (!target) { target = event.target; } event.isTargetWebComponent = true; if (isWebComponentSupportedNatively()) { event.realTarget = event.srcElement || event.toElement; } else if (hasOwnProperty(context, 'hot') || context.isHotTableEnv || context.wtTable) { // Polymer doesn't support `event.target` property properly we must emulate it ourselves if (hasOwnProperty(context, 'hot')) { // Custom element fromElement = context.hot ? context.hot.view.wt.wtTable.TABLE : null; } else if (context.isHotTableEnv) { // Handsontable.Core fromElement = context.view.activeWt.wtTable.TABLE.parentNode.parentNode; } else if (context.wtTable) { // Walkontable fromElement = context.wtTable.TABLE.parentNode.parentNode; } realTarget = closest(event.target, [componentName], fromElement); if (realTarget) { event.realTarget = fromElement.querySelector(componentName) || event.target; } else { event.realTarget = event.target; } } Object.defineProperty(event, 'target', { get: function get() { return polymerWrap(target); }, enumerable: true, configurable: true }); return event; } export default EventManager; export function getListenersCounter() { return listenersCounter; }