handsontable
Version:
Handsontable is a JavaScript Data Grid available for React, Angular and Vue.
219 lines (208 loc) • 7.34 kB
JavaScript
exports.__esModule = true;
exports.getListenersCounter = getListenersCounter;
require("core-js/modules/es.error.cause.js");
require("core-js/modules/es.array.push.js");
var _event = require("./helpers/dom/event");
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
/**
* Counter which tracks unregistered listeners (useful for detecting memory leaks).
*
* @type {number}
*/
let listenersCounter = 0;
/**
* Event DOM manager for internal use in Handsontable.
*
* @class EventManager
*/
class EventManager {
/**
* @param {object} [context=null] An object to which event listeners will be stored.
* @private
*/
constructor() {
let context = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
/**
* @type {object}
*/
_defineProperty(this, "context", void 0);
this.context = context || this;
// TODO it modify external object. Rethink that.
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.
*/
addEventListener(element, eventName, callback) {
let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
/**
* @private
* @param {Event} event The event object.
*/
function callbackProxy(event) {
callback.call(this, extendEvent(event));
}
this.context.eventListeners.push({
element,
event: eventName,
callback,
callbackProxy,
options,
eventManager: this
});
element.addEventListener(eventName, callbackProxy, options);
listenersCounter += 1;
return () => {
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.
*/
removeEventListener(element, eventName, callback) {
let onlyOwnEvents = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
let len = this.context.eventListeners.length;
let 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;
}
// TODO rethink that, main bulk is that it needs multi instances to handle same context, but with a different scopes.
// TODO I suppose much more efficient way will be comparing string with scope id, or any similar approach.
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.
*/
clearEvents() {
let onlyOwnEvents = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
if (!this.context) {
return;
}
let len = this.context.eventListeners.length;
while (len) {
len -= 1;
const event = this.context.eventListeners[len];
if (onlyOwnEvents && event.eventManager !== this) {
continue;
}
this.context.eventListeners.splice(len, 1);
event.element.removeEventListener(event.event, event.callbackProxy, event.options);
listenersCounter -= 1;
}
}
/**
* Clear all previously registered events.
*/
clear() {
this.clearEvents();
}
/**
* Destroy instance of EventManager, clearing all events of the context.
*/
destroy() {
this.clearEvents();
this.context = null;
}
/**
* Destroy instance of EventManager, clearing only the own events.
*/
destroyWithOwnEventsOnly() {
this.clearEvents(true);
this.context = null;
}
/**
* Trigger event at the specified target element.
*
* @param {Element} element Target element.
* @param {string} eventName Event name.
*/
fireEvent(element, eventName) {
let rootDocument = element.document;
let rootWindow = element;
if (!rootDocument) {
rootDocument = element.ownerDocument ? element.ownerDocument : element;
rootWindow = rootDocument.defaultView;
}
const 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
};
let 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${eventName}`, event);
}
}
}
/**
* @private
* @param {Event} event The event object.
* @returns {Event}
*/
function extendEvent(event) {
const nativeStopImmediatePropagation = event.stopImmediatePropagation;
event.stopImmediatePropagation = function () {
nativeStopImmediatePropagation.apply(this);
(0, _event.stopImmediatePropagation)(this);
};
return event;
}
var _default = exports.default = EventManager;
/**
* @private
* @returns {number}
*/
function getListenersCounter() {
return listenersCounter;
}
;