onelaraveljs 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +87 -0
  2. package/docs/integration_analysis.md +116 -0
  3. package/docs/onejs_analysis.md +108 -0
  4. package/docs/optimization_implementation_group2.md +458 -0
  5. package/docs/optimization_plan.md +130 -0
  6. package/index.js +16 -0
  7. package/package.json +13 -0
  8. package/src/app.js +61 -0
  9. package/src/core/API.js +72 -0
  10. package/src/core/ChildrenRegistry.js +410 -0
  11. package/src/core/DOMBatcher.js +207 -0
  12. package/src/core/ErrorBoundary.js +226 -0
  13. package/src/core/EventDelegator.js +416 -0
  14. package/src/core/Helper.js +817 -0
  15. package/src/core/LoopContext.js +97 -0
  16. package/src/core/OneDOM.js +246 -0
  17. package/src/core/OneMarkup.js +444 -0
  18. package/src/core/Router.js +996 -0
  19. package/src/core/SEOConfig.js +321 -0
  20. package/src/core/SectionEngine.js +75 -0
  21. package/src/core/TemplateEngine.js +83 -0
  22. package/src/core/View.js +273 -0
  23. package/src/core/ViewConfig.js +229 -0
  24. package/src/core/ViewController.js +1410 -0
  25. package/src/core/ViewControllerOptimized.js +164 -0
  26. package/src/core/ViewIdentifier.js +361 -0
  27. package/src/core/ViewLoader.js +272 -0
  28. package/src/core/ViewManager.js +1962 -0
  29. package/src/core/ViewState.js +761 -0
  30. package/src/core/ViewSystem.js +301 -0
  31. package/src/core/ViewTemplate.js +4 -0
  32. package/src/core/helpers/BindingHelper.js +239 -0
  33. package/src/core/helpers/ConfigHelper.js +37 -0
  34. package/src/core/helpers/EventHelper.js +172 -0
  35. package/src/core/helpers/LifecycleHelper.js +17 -0
  36. package/src/core/helpers/ReactiveHelper.js +169 -0
  37. package/src/core/helpers/RenderHelper.js +15 -0
  38. package/src/core/helpers/ResourceHelper.js +89 -0
  39. package/src/core/helpers/TemplateHelper.js +11 -0
  40. package/src/core/managers/BindingManager.js +671 -0
  41. package/src/core/managers/ConfigurationManager.js +136 -0
  42. package/src/core/managers/EventManager.js +309 -0
  43. package/src/core/managers/LifecycleManager.js +356 -0
  44. package/src/core/managers/ReactiveManager.js +334 -0
  45. package/src/core/managers/RenderEngine.js +292 -0
  46. package/src/core/managers/ResourceManager.js +441 -0
  47. package/src/core/managers/ViewHierarchyManager.js +258 -0
  48. package/src/core/managers/ViewTemplateManager.js +127 -0
  49. package/src/core/reactive/ReactiveComponent.js +592 -0
  50. package/src/core/services/EventService.js +418 -0
  51. package/src/core/services/HttpService.js +106 -0
  52. package/src/core/services/LoggerService.js +57 -0
  53. package/src/core/services/StateService.js +512 -0
  54. package/src/core/services/StorageService.js +856 -0
  55. package/src/core/services/StoreService.js +258 -0
  56. package/src/core/services/TemplateDetectorService.js +361 -0
  57. package/src/core/services/Test.js +18 -0
  58. package/src/helpers/devWarnings.js +205 -0
  59. package/src/helpers/performance.js +226 -0
  60. package/src/helpers/utils.js +287 -0
  61. package/src/init.js +343 -0
  62. package/src/plugins/auto-plugin.js +34 -0
  63. package/src/services/Test.js +18 -0
  64. package/src/types/index.js +193 -0
  65. package/src/utils/date-helper.js +51 -0
  66. package/src/utils/helpers.js +39 -0
  67. package/src/utils/validation.js +32 -0
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Error Boundary System
3
+ * Provides error handling and fallback UI for views
4
+ *
5
+ * @module core/ErrorBoundary
6
+ * @author OneLaravel Team
7
+ * @since 2025-12-29
8
+ */
9
+
10
+ import logger from './services/LoggerService.js';
11
+
12
+ /**
13
+ * Error Boundary for Views
14
+ * Catches errors during view lifecycle and provides fallback UI
15
+ *
16
+ * @class ViewErrorBoundary
17
+ *
18
+ * @example
19
+ * const errorBoundary = new ViewErrorBoundary(view);
20
+ * const result = errorBoundary.wrap(() => view.render());
21
+ */
22
+ export class ViewErrorBoundary {
23
+ /**
24
+ * Create error boundary
25
+ * @param {ViewController} controller - View controller instance
26
+ */
27
+ constructor(controller) {
28
+ this.controller = controller;
29
+ this.view = controller.view;
30
+ this.hasError = false;
31
+ this.error = null;
32
+ this.errorInfo = null;
33
+ }
34
+
35
+ /**
36
+ * Wrap a function with error handling
37
+ *
38
+ * @param {Function} fn - Function to wrap
39
+ * @param {string} phase - Lifecycle phase name
40
+ * @returns {*} Function result or error fallback
41
+ *
42
+ * @example
43
+ * const html = errorBoundary.wrap(() => view.render(), 'render');
44
+ */
45
+ wrap(fn, phase = 'execution') {
46
+ try {
47
+ const result = fn();
48
+
49
+ // Handle async functions
50
+ if (result && typeof result.then === 'function') {
51
+ return result.catch(error => {
52
+ return this.handleError(error, { phase, async: true });
53
+ });
54
+ }
55
+
56
+ return result;
57
+ } catch (error) {
58
+ return this.handleError(error, { phase, async: false });
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Handle caught error
64
+ *
65
+ * @param {Error} error - The error object
66
+ * @param {Object} errorInfo - Additional error information
67
+ * @returns {string} Error fallback HTML
68
+ *
69
+ * @private
70
+ */
71
+ handleError(error, errorInfo = {}) {
72
+ this.hasError = true;
73
+ this.error = error;
74
+ this.errorInfo = errorInfo;
75
+
76
+ // Log error
77
+ logger.error('View Error Boundary:', {
78
+ view: this.controller.path,
79
+ viewId: this.controller.id,
80
+ phase: errorInfo.phase,
81
+ error: error.message,
82
+ stack: error.stack,
83
+ ...errorInfo
84
+ });
85
+
86
+ // Call error lifecycle hook if exists
87
+ if (typeof this.controller.onError === 'function') {
88
+ try {
89
+ this.controller.onError(error, errorInfo);
90
+ } catch (hookError) {
91
+ logger.error('Error in onError hook:', hookError);
92
+ }
93
+ }
94
+
95
+ // Return fallback UI
96
+ return this.renderErrorFallback(error, errorInfo);
97
+ }
98
+
99
+ /**
100
+ * Render error fallback UI
101
+ *
102
+ * @param {Error} error - The error object
103
+ * @param {Object} errorInfo - Additional error information
104
+ * @returns {string} Error HTML
105
+ *
106
+ * @private
107
+ */
108
+ renderErrorFallback(error, errorInfo) {
109
+ const isDev = this.controller.App?.env?.debug || false;
110
+ const viewPath = this.controller.path || 'unknown';
111
+ const phase = errorInfo.phase || 'unknown';
112
+
113
+ if (isDev) {
114
+ // Development mode - show detailed error
115
+ return `
116
+ <div class="error-boundary dev-error" style="
117
+ padding: 20px;
118
+ margin: 20px 0;
119
+ background: #fee;
120
+ border: 2px solid #c33;
121
+ border-radius: 8px;
122
+ font-family: monospace;
123
+ ">
124
+ <h3 style="color: #c33; margin: 0 0 10px 0;">
125
+ ⚠️ Error in View: ${viewPath}
126
+ </h3>
127
+ <p style="margin: 5px 0;"><strong>Phase:</strong> ${phase}</p>
128
+ <p style="margin: 5px 0;"><strong>Message:</strong> ${error.message}</p>
129
+ ${error.stack ? `
130
+ <details style="margin-top: 10px;">
131
+ <summary style="cursor: pointer; color: #c33;">
132
+ Stack Trace
133
+ </summary>
134
+ <pre style="
135
+ background: #f5f5f5;
136
+ padding: 10px;
137
+ overflow-x: auto;
138
+ margin-top: 10px;
139
+ ">${error.stack}</pre>
140
+ </details>
141
+ ` : ''}
142
+ <p style="margin-top: 15px; color: #666; font-size: 0.9em;">
143
+ This detailed error is only visible in development mode.
144
+ </p>
145
+ </div>
146
+ `;
147
+ } else {
148
+ // Production mode - show generic error
149
+ return `
150
+ <div class="error-boundary prod-error" style="
151
+ padding: 20px;
152
+ margin: 20px 0;
153
+ background: #f9f9f9;
154
+ border: 1px solid #ddd;
155
+ border-radius: 8px;
156
+ text-align: center;
157
+ ">
158
+ <h3 style="color: #666; margin: 0 0 10px 0;">
159
+ Something went wrong
160
+ </h3>
161
+ <p style="color: #999;">
162
+ We're sorry, but something went wrong while loading this content.
163
+ Please try refreshing the page.
164
+ </p>
165
+ </div>
166
+ `;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Reset error boundary state
172
+ */
173
+ reset() {
174
+ this.hasError = false;
175
+ this.error = null;
176
+ this.errorInfo = null;
177
+ }
178
+
179
+ /**
180
+ * Check if boundary has caught an error
181
+ * @returns {boolean}
182
+ */
183
+ get hasCaughtError() {
184
+ return this.hasError;
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Global error handler for uncaught errors
190
+ *
191
+ * @param {Application} App - App instance
192
+ * @param {Function} customHandler - Custom error handler
193
+ */
194
+ export function setupGlobalErrorHandler(App, customHandler = null) {
195
+ // Handle uncaught errors
196
+ window.addEventListener('error', (event) => {
197
+ logger.error('Uncaught Error:', {
198
+ message: event.message,
199
+ filename: event.filename,
200
+ lineno: event.lineno,
201
+ colno: event.colno,
202
+ error: event.error
203
+ });
204
+
205
+ if (customHandler) {
206
+ customHandler(event.error, { type: 'uncaught', event });
207
+ }
208
+ });
209
+
210
+ // Handle unhandled promise rejections
211
+ window.addEventListener('unhandledrejection', (event) => {
212
+ logger.error('Unhandled Promise Rejection:', {
213
+ reason: event.reason,
214
+ promise: event.promise
215
+ });
216
+
217
+ if (customHandler) {
218
+ customHandler(event.reason, { type: 'rejection', event });
219
+ }
220
+ });
221
+ }
222
+
223
+ export default {
224
+ ViewErrorBoundary,
225
+ setupGlobalErrorHandler,
226
+ };
@@ -0,0 +1,416 @@
1
+ /**
2
+ * EventDelegator
3
+ *
4
+ * Tối ưu hóa event handling bằng cách delegate events lên root element
5
+ * thay vì attach listener riêng cho từng element.
6
+ *
7
+ * Lợi ích hiệu suất:
8
+ * - Giảm số lượng event listeners từ O(n) xuống O(1)
9
+ * - Tự động hỗ trợ dynamic content (không cần re-attach)
10
+ * - Giảm memory footprint
11
+ * - Cải thiện performance khi có nhiều elements
12
+ *
13
+ * Cách hoạt động:
14
+ * - Một listener duy nhất trên root element (document hoặc container)
15
+ * - Sử dụng event bubbling để bắt events từ children
16
+ * - Match target element với selector để gọi đúng handler
17
+ *
18
+ * @class EventDelegator
19
+ * @version 1.0.0
20
+ * @since 2025-01-06
21
+ */
22
+ class EventDelegator {
23
+ constructor() {
24
+ /**
25
+ * Map lưu delegated handlers theo event type
26
+ * @type {Map<string, Map<string, Array<{selector: string, handler: Function, options: Object}>>>}
27
+ */
28
+ this.delegatedHandlers = new Map();
29
+
30
+ /**
31
+ * Map lưu root listeners đã attach
32
+ * @type {Map<HTMLElement, Map<string, Function>>}
33
+ */
34
+ this.rootListeners = new Map();
35
+
36
+ /**
37
+ * Root element mặc định cho delegation
38
+ * @type {HTMLElement}
39
+ */
40
+ this.defaultRoot = document;
41
+
42
+ /**
43
+ * Cache selector matches để improve performance
44
+ * @type {WeakMap<HTMLElement, Map<string, boolean>>}
45
+ */
46
+ this.matchCache = new WeakMap();
47
+ }
48
+
49
+ /**
50
+ * Đăng ký delegated event handler
51
+ *
52
+ * @param {string} eventType - Loại event (click, input, etc.)
53
+ * @param {string} selector - CSS selector để match elements
54
+ * @param {Function} handler - Handler function
55
+ * @param {Object} options - Tùy chọn
56
+ * @param {HTMLElement} options.root - Root element để attach listener (default: document)
57
+ * @param {boolean} options.capture - Sử dụng capture phase
58
+ * @param {boolean} options.once - Chỉ chạy một lần
59
+ * @returns {Function} Unsubscribe function
60
+ *
61
+ * @example
62
+ * const unsubscribe = EventDelegator.on('click', '.btn-submit', (e) => {
63
+ * console.log('Button clicked:', e.target);
64
+ * });
65
+ */
66
+ on(eventType, selector, handler, options = {}) {
67
+ const root = options.root || this.defaultRoot;
68
+ const capture = options.capture || false;
69
+
70
+ // Tạo structure nếu chưa tồn tại
71
+ if (!this.delegatedHandlers.has(eventType)) {
72
+ this.delegatedHandlers.set(eventType, new Map());
73
+ }
74
+
75
+ const eventMap = this.delegatedHandlers.get(eventType);
76
+ const rootKey = this._getRootKey(root);
77
+
78
+ if (!eventMap.has(rootKey)) {
79
+ eventMap.set(rootKey, []);
80
+ }
81
+
82
+ const handlers = eventMap.get(rootKey);
83
+
84
+ // Wrap handler nếu có option once
85
+ const wrappedHandler = options.once
86
+ ? (...args) => {
87
+ handler(...args);
88
+ this.off(eventType, selector, wrappedHandler, { root });
89
+ }
90
+ : handler;
91
+
92
+ // Lưu handler info
93
+ const handlerInfo = { selector, handler: wrappedHandler, options };
94
+ handlers.push(handlerInfo);
95
+
96
+ // Attach root listener nếu chưa có
97
+ this._ensureRootListener(root, eventType, capture);
98
+
99
+ // Return unsubscribe function
100
+ return () => this.off(eventType, selector, wrappedHandler, { root });
101
+ }
102
+
103
+ /**
104
+ * Gỡ bỏ delegated event handler
105
+ *
106
+ * @param {string} eventType - Loại event
107
+ * @param {string} selector - CSS selector
108
+ * @param {Function} handler - Handler function (optional, nếu không có sẽ xóa tất cả)
109
+ * @param {Object} options - Tùy chọn
110
+ * @param {HTMLElement} options.root - Root element
111
+ */
112
+ off(eventType, selector, handler = null, options = {}) {
113
+ const root = options.root || this.defaultRoot;
114
+ const eventMap = this.delegatedHandlers.get(eventType);
115
+
116
+ if (!eventMap) return;
117
+
118
+ const rootKey = this._getRootKey(root);
119
+ const handlers = eventMap.get(rootKey);
120
+
121
+ if (!handlers) return;
122
+
123
+ // Lọc handlers
124
+ const filtered = handlers.filter(info => {
125
+ if (info.selector !== selector) return true;
126
+ if (handler && info.handler !== handler) return true;
127
+ return false;
128
+ });
129
+
130
+ eventMap.set(rootKey, filtered);
131
+
132
+ // Nếu không còn handlers cho root này, remove root listener
133
+ if (filtered.length === 0) {
134
+ this._removeRootListener(root, eventType);
135
+ eventMap.delete(rootKey);
136
+ }
137
+
138
+ // Nếu không còn roots cho event type này, xóa event type
139
+ if (eventMap.size === 0) {
140
+ this.delegatedHandlers.delete(eventType);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Xóa tất cả delegated handlers cho một root
146
+ *
147
+ * @param {HTMLElement} root - Root element
148
+ */
149
+ clearRoot(root = this.defaultRoot) {
150
+ const rootKey = this._getRootKey(root);
151
+
152
+ this.delegatedHandlers.forEach((eventMap, eventType) => {
153
+ if (eventMap.has(rootKey)) {
154
+ this._removeRootListener(root, eventType);
155
+ eventMap.delete(rootKey);
156
+ }
157
+
158
+ if (eventMap.size === 0) {
159
+ this.delegatedHandlers.delete(eventType);
160
+ }
161
+ });
162
+
163
+ this.rootListeners.delete(root);
164
+ }
165
+
166
+ /**
167
+ * Xóa tất cả delegated handlers
168
+ */
169
+ clearAll() {
170
+ this.rootListeners.forEach((listenerMap, root) => {
171
+ listenerMap.forEach((listener, eventType) => {
172
+ root.removeEventListener(eventType, listener, true);
173
+ root.removeEventListener(eventType, listener, false);
174
+ });
175
+ });
176
+
177
+ this.delegatedHandlers.clear();
178
+ this.rootListeners.clear();
179
+ this.matchCache = new WeakMap();
180
+ }
181
+
182
+ /**
183
+ * Kiểm tra element có match selector không (với cache)
184
+ *
185
+ * @param {HTMLElement} element - Element cần kiểm tra
186
+ * @param {string} selector - CSS selector
187
+ * @returns {boolean}
188
+ * @private
189
+ */
190
+ _matchesSelector(element, selector) {
191
+ if (!element || !element.matches) return false;
192
+
193
+ // Check cache
194
+ let cache = this.matchCache.get(element);
195
+ if (!cache) {
196
+ cache = new Map();
197
+ this.matchCache.set(element, cache);
198
+ }
199
+
200
+ if (cache.has(selector)) {
201
+ return cache.get(selector);
202
+ }
203
+
204
+ // Compute and cache
205
+ const matches = element.matches(selector);
206
+ cache.set(selector, matches);
207
+
208
+ return matches;
209
+ }
210
+
211
+ /**
212
+ * Tìm matching element trong event path
213
+ *
214
+ * @param {Event} event - DOM event
215
+ * @param {string} selector - CSS selector
216
+ * @param {HTMLElement} root - Root element
217
+ * @returns {HTMLElement|null}
218
+ * @private
219
+ */
220
+ _findMatchingElement(event, selector, root) {
221
+ let element = event.target;
222
+
223
+ // Traverse lên parent tree cho đến root
224
+ while (element && element !== root) {
225
+ if (this._matchesSelector(element, selector)) {
226
+ return element;
227
+ }
228
+ element = element.parentElement;
229
+ }
230
+
231
+ // Check root itself
232
+ if (element === root && this._matchesSelector(element, selector)) {
233
+ return element;
234
+ }
235
+
236
+ return null;
237
+ }
238
+
239
+ /**
240
+ * Tạo root listener cho event type
241
+ *
242
+ * @param {HTMLElement} root - Root element
243
+ * @param {string} eventType - Event type
244
+ * @param {boolean} capture - Use capture phase
245
+ * @private
246
+ */
247
+ _ensureRootListener(root, eventType, capture = false) {
248
+ // Check đã có listener chưa
249
+ let listenerMap = this.rootListeners.get(root);
250
+ if (!listenerMap) {
251
+ listenerMap = new Map();
252
+ this.rootListeners.set(root, listenerMap);
253
+ }
254
+
255
+ const listenerKey = `${eventType}:${capture}`;
256
+ if (listenerMap.has(listenerKey)) {
257
+ return; // Đã có listener
258
+ }
259
+
260
+ // Tạo delegating listener
261
+ const listener = (event) => {
262
+ const eventMap = this.delegatedHandlers.get(eventType);
263
+ if (!eventMap) return;
264
+
265
+ const rootKey = this._getRootKey(root);
266
+ const handlers = eventMap.get(rootKey);
267
+ if (!handlers || handlers.length === 0) return;
268
+
269
+ // 1. Collect all matches
270
+ const matchedHandlers = [];
271
+ for (const handlerInfo of handlers) {
272
+ const matchedElement = this._findMatchingElement(event, handlerInfo.selector, root);
273
+ if (matchedElement) {
274
+ matchedHandlers.push({
275
+ ...handlerInfo,
276
+ element: matchedElement
277
+ });
278
+ }
279
+ }
280
+
281
+ // 2. Sort by depth (deepest first = child before parent)
282
+ matchedHandlers.sort((a, b) => {
283
+ if (a.element === b.element) return 0;
284
+ // If a contains b, a is parent/ancestor of b. We want b (child) first.
285
+ return a.element.contains(b.element) ? 1 : -1;
286
+ });
287
+
288
+ // 3. Shim stopPropagation to track status
289
+ let isPropagationStopped = false;
290
+ const originalStopPropagation = event.stopPropagation;
291
+ event.stopPropagation = function () {
292
+ isPropagationStopped = true;
293
+ if (originalStopPropagation) {
294
+ originalStopPropagation.apply(this, arguments);
295
+ }
296
+ };
297
+
298
+ // 4. Execute handlers
299
+ for (const { selector, handler, options, element } of matchedHandlers) {
300
+ if (isPropagationStopped) break;
301
+
302
+ // Add delegateTarget property to original event
303
+ // KHÔNG tạo Object.create(event) vì sẽ mất native Event methods
304
+ Object.defineProperty(event, 'delegateTarget', {
305
+ value: element,
306
+ writable: false,
307
+ enumerable: true,
308
+ configurable: true
309
+ });
310
+
311
+ try {
312
+ // Gọi handler với event gốc, KHÔNG thay đổi context
313
+ // Handler giữ nguyên context của nó (controller/view)
314
+ const result = handler(event);
315
+
316
+ // Xử lý return value
317
+ if (result === false) {
318
+ event.preventDefault();
319
+ event.stopPropagation();
320
+ }
321
+ } catch (error) {
322
+ console.error('[EventDelegator] Handler error:', error, {
323
+ eventType,
324
+ selector,
325
+ element
326
+ });
327
+ } finally {
328
+ // Cleanup delegateTarget property
329
+ delete event.delegateTarget;
330
+ }
331
+ }
332
+ };
333
+
334
+ // Attach listener
335
+ root.addEventListener(eventType, listener, capture);
336
+ listenerMap.set(listenerKey, listener);
337
+ }
338
+
339
+ /**
340
+ * Gỡ bỏ root listener
341
+ *
342
+ * @param {HTMLElement} root - Root element
343
+ * @param {string} eventType - Event type
344
+ * @private
345
+ */
346
+ _removeRootListener(root, eventType) {
347
+ const listenerMap = this.rootListeners.get(root);
348
+ if (!listenerMap) return;
349
+
350
+ // Remove both capture and non-capture listeners
351
+ ['true', 'false'].forEach(captureStr => {
352
+ const capture = captureStr === 'true';
353
+ const listenerKey = `${eventType}:${capture}`;
354
+ const listener = listenerMap.get(listenerKey);
355
+
356
+ if (listener) {
357
+ root.removeEventListener(eventType, listener, capture);
358
+ listenerMap.delete(listenerKey);
359
+ }
360
+ });
361
+
362
+ // Nếu không còn listeners, xóa map
363
+ if (listenerMap.size === 0) {
364
+ this.rootListeners.delete(root);
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Tạo unique key cho root element
370
+ *
371
+ * @param {HTMLElement} root - Root element
372
+ * @returns {string}
373
+ * @private
374
+ */
375
+ _getRootKey(root) {
376
+ if (root === document) return 'document';
377
+ if (root === window) return 'window';
378
+ if (root.id) return `#${root.id}`;
379
+
380
+ // Fallback: use WeakMap hoặc generate unique ID
381
+ if (!root._delegatorKey) {
382
+ root._delegatorKey = `root_${Math.random().toString(36).substr(2, 9)}`;
383
+ }
384
+ return root._delegatorKey;
385
+ }
386
+
387
+ /**
388
+ * Lấy thống kê về delegated handlers
389
+ * Hữu ích cho debugging và monitoring
390
+ *
391
+ * @returns {Object}
392
+ */
393
+ getStats() {
394
+ const stats = {
395
+ eventTypes: this.delegatedHandlers.size,
396
+ roots: this.rootListeners.size,
397
+ totalHandlers: 0,
398
+ byEventType: {}
399
+ };
400
+
401
+ this.delegatedHandlers.forEach((eventMap, eventType) => {
402
+ let count = 0;
403
+ eventMap.forEach(handlers => {
404
+ count += handlers.length;
405
+ });
406
+ stats.totalHandlers += count;
407
+ stats.byEventType[eventType] = count;
408
+ });
409
+
410
+ return stats;
411
+ }
412
+ }
413
+
414
+ // Export singleton instance
415
+ const delegator = new EventDelegator();
416
+ export default delegator;