UNPKG

7.34 kBJavaScriptView Raw
1"use strict";
2
3exports.__esModule = true;
4exports.getListenersCounter = getListenersCounter;
5require("core-js/modules/es.error.cause.js");
6require("core-js/modules/es.array.push.js");
7var _event = require("./helpers/dom/event");
8function _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; }
9function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
10function _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); }
11/**
12 * Counter which tracks unregistered listeners (useful for detecting memory leaks).
13 *
14 * @type {number}
15 */
16let listenersCounter = 0;
17
18/**
19 * Event DOM manager for internal use in Handsontable.
20 *
21 * @class EventManager
22 */
23class EventManager {
24 /**
25 * @param {object} [context=null] An object to which event listeners will be stored.
26 * @private
27 */
28 constructor() {
29 let context = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
30 /**
31 * @type {object}
32 */
33 _defineProperty(this, "context", void 0);
34 this.context = context || this;
35
36 // TODO it modify external object. Rethink that.
37 if (!this.context.eventListeners) {
38 this.context.eventListeners = []; // TODO perf It would be more performant if every instance of EventManager tracked its own listeners only
39 }
40 }
41
42 /**
43 * Register specified listener (`eventName`) to the element.
44 *
45 * @param {Element} element Target element.
46 * @param {string} eventName Event name.
47 * @param {Function} callback Function which will be called after event occur.
48 * @param {AddEventListenerOptions|boolean} [options] Listener options if object or useCapture if boolean.
49 * @returns {Function} Returns function which you can easily call to remove that event.
50 */
51 addEventListener(element, eventName, callback) {
52 let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
53 /**
54 * @private
55 * @param {Event} event The event object.
56 */
57 function callbackProxy(event) {
58 callback.call(this, extendEvent(event));
59 }
60 this.context.eventListeners.push({
61 element,
62 event: eventName,
63 callback,
64 callbackProxy,
65 options,
66 eventManager: this
67 });
68 element.addEventListener(eventName, callbackProxy, options);
69 listenersCounter += 1;
70 return () => {
71 this.removeEventListener(element, eventName, callback);
72 };
73 }
74
75 /**
76 * Remove the event listener previously registered.
77 *
78 * @param {Element} element Target element.
79 * @param {string} eventName Event name.
80 * @param {Function} callback Function to remove from the event target. It must be the same as during registration listener.
81 * @param {boolean} [onlyOwnEvents] Whether whould remove only events registered using this instance of EventManager.
82 */
83 removeEventListener(element, eventName, callback) {
84 let onlyOwnEvents = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
85 let len = this.context.eventListeners.length;
86 let tmpEvent;
87 while (len) {
88 len -= 1;
89 tmpEvent = this.context.eventListeners[len];
90 if (tmpEvent.event === eventName && tmpEvent.element === element) {
91 if (callback && callback !== tmpEvent.callback) {
92 /* eslint-disable no-continue */
93 continue;
94 }
95 // TODO rethink that, main bulk is that it needs multi instances to handle same context, but with a different scopes.
96 // TODO I suppose much more efficient way will be comparing string with scope id, or any similar approach.
97 if (onlyOwnEvents && tmpEvent.eventManager !== this) {
98 continue;
99 }
100 this.context.eventListeners.splice(len, 1);
101 tmpEvent.element.removeEventListener(tmpEvent.event, tmpEvent.callbackProxy, tmpEvent.options);
102 listenersCounter -= 1;
103 }
104 }
105 }
106
107 /**
108 * Clear all previously registered events.
109 *
110 * @private
111 * @since 0.15.0-beta3
112 * @param {boolean} [onlyOwnEvents] Whether whould remove only events registered using this instance of EventManager.
113 */
114 clearEvents() {
115 let onlyOwnEvents = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
116 if (!this.context) {
117 return;
118 }
119 let len = this.context.eventListeners.length;
120 while (len) {
121 len -= 1;
122 const event = this.context.eventListeners[len];
123 if (onlyOwnEvents && event.eventManager !== this) {
124 continue;
125 }
126 this.context.eventListeners.splice(len, 1);
127 event.element.removeEventListener(event.event, event.callbackProxy, event.options);
128 listenersCounter -= 1;
129 }
130 }
131
132 /**
133 * Clear all previously registered events.
134 */
135 clear() {
136 this.clearEvents();
137 }
138
139 /**
140 * Destroy instance of EventManager, clearing all events of the context.
141 */
142 destroy() {
143 this.clearEvents();
144 this.context = null;
145 }
146
147 /**
148 * Destroy instance of EventManager, clearing only the own events.
149 */
150 destroyWithOwnEventsOnly() {
151 this.clearEvents(true);
152 this.context = null;
153 }
154
155 /**
156 * Trigger event at the specified target element.
157 *
158 * @param {Element} element Target element.
159 * @param {string} eventName Event name.
160 */
161 fireEvent(element, eventName) {
162 let rootDocument = element.document;
163 let rootWindow = element;
164 if (!rootDocument) {
165 rootDocument = element.ownerDocument ? element.ownerDocument : element;
166 rootWindow = rootDocument.defaultView;
167 }
168 const options = {
169 bubbles: true,
170 cancelable: eventName !== 'mousemove',
171 view: rootWindow,
172 detail: 0,
173 screenX: 0,
174 screenY: 0,
175 clientX: 1,
176 clientY: 1,
177 ctrlKey: false,
178 altKey: false,
179 shiftKey: false,
180 metaKey: false,
181 button: 0,
182 relatedTarget: undefined
183 };
184 let event;
185 if (rootDocument.createEvent) {
186 event = rootDocument.createEvent('MouseEvents');
187 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);
188 } else {
189 event = rootDocument.createEventObject();
190 }
191 if (element.dispatchEvent) {
192 element.dispatchEvent(event);
193 } else {
194 element.fireEvent(`on${eventName}`, event);
195 }
196 }
197}
198
199/**
200 * @private
201 * @param {Event} event The event object.
202 * @returns {Event}
203 */
204function extendEvent(event) {
205 const nativeStopImmediatePropagation = event.stopImmediatePropagation;
206 event.stopImmediatePropagation = function () {
207 nativeStopImmediatePropagation.apply(this);
208 (0, _event.stopImmediatePropagation)(this);
209 };
210 return event;
211}
212var _default = exports.default = EventManager;
213/**
214 * @private
215 * @returns {number}
216 */
217function getListenersCounter() {
218 return listenersCounter;
219}
\No newline at end of file