Repository: exadel-inc/esl
Pull request #1263 (2022-10-03 11:46:39)

feat(esl-event-listener): improve `ESLEventListener` object to have exact handler for simple subscriptions
For purpose of improving debugging, now event subscription does not wrap handler function if there is no need (`once` marker or delegation (`selector`))



Patch:
diff --git a/src/modules/esl-event-listener/core/listener.ts b/src/modules/esl-event-listener/core/listener.ts
index ff01fe742..cb86cd1ee 100644
--- a/src/modules/esl-event-listener/core/listener.ts
+++ b/src/modules/esl-event-listener/core/listener.ts
@@ -91,11 +91,15 @@ export class ESLEventListener implements ESLListenerDefinition, EventListenerObj
     return false;
   }
 
-  /** Handles caught event (used as callback for low-level subscriptions) */
-  public handleEvent(e: Event): void {
-    if (!this.isDelegatedTarget(e)) return;
-    this.handler.call(this.host, e, this);
-    if (this.once) this.unsubscribe();
+  /**
+   * Memoized builder for bound and decorated low level subscription.
+   * Implements DOM API {@link EventListenerObject.handleEvent}
+   */
+  @memoize()
+  public get handleEvent(): EventListener {
+    const handlerBound = this.handler.bind(this.host);
+    const handlerFull = this.once ? ((e: Event): void => (handlerBound(e), this.unsubscribe())) : handlerBound;
+    return this.selector ? (e: Event): void => this.isDelegatedTarget(e) && handlerFull(e) : handlerFull;
   }
 
   /** Checks if the passed event can be handled by the current event listener */
@@ -116,6 +120,7 @@ export class ESLEventListener implements ESLListenerDefinition, EventListenerObj
     const {passive, capture} = this;
     this.unsubscribe();
     memoize.clear(this, '$targets');
+    memoize.clear(this, 'handleEvent');
     if (!this.$targets.length) return false;
     this.$targets.forEach((el: EventTarget) => el.addEventListener(this.event, this, {passive, capture}));
     ESLEventListener.add(this.host, this);
diff --git a/src/modules/esl-event-listener/core/types.ts b/src/modules/esl-event-listener/core/types.ts
index 818d67555..9091b3e50 100644
--- a/src/modules/esl-event-listener/core/types.ts
+++ b/src/modules/esl-event-listener/core/types.ts
@@ -1,4 +1,3 @@
-import type {ESLEventListener} from './listener';
 import type {PropertyProvider} from '../../esl-utils/misc/functions';
 
 /** String CSS selector to find the target or {@link EventTarget} object or array of {@link EventTarget}s */
@@ -47,7 +46,7 @@ export interface ESLListenerDefinition<EType extends keyof ESLListenerEventMap =
 }
 
 /** Describes callback handler */
-export type ESLListenerHandler<EType extends Event = Event> = (event: EType, listener: ESLEventListener) => void;
+export type ESLListenerHandler<EType extends Event = Event> = (event: EType) => void;
 
 /** Condition (criteria) to find {@link ESLListenerDescriptor} */
 export type ESLListenerCriteria = undefined | keyof ESLListenerEventMap | ESLListenerHandler | Partial<ESLListenerDefinition>;


Test patch:
diff --git a/src/modules/esl-base-element/test/element.listener.test.ts b/src/modules/esl-base-element/test/element.listener.test.ts
index 8e23fb46d..a9ae7d265 100644
--- a/src/modules/esl-base-element/test/element.listener.test.ts
+++ b/src/modules/esl-base-element/test/element.listener.test.ts
@@ -28,7 +28,7 @@ describe('ESLBaseElement: listeners', () => {
       mockHandler.mockReset();
       el.click();
       expect(mockHandler).toBeCalled();
-      expect(mockHandler).lastCalledWith(el, expect.any(Event), expect.any(Object));
+      expect(mockHandler).lastCalledWith(el, expect.any(Event));
     });
 
     test('ESLBaseElement successfully auto unsubscribed', async () => {
diff --git a/src/modules/esl-mixin-element/test/mixin.listener-herpers.test.ts b/src/modules/esl-mixin-element/test/mixin.listener-herpers.test.ts
index 749133ff8..64f02d6bc 100644
--- a/src/modules/esl-mixin-element/test/mixin.listener-herpers.test.ts
+++ b/src/modules/esl-mixin-element/test/mixin.listener-herpers.test.ts
@@ -1,5 +1,5 @@
 import {ESLMixinElement} from '../ui/esl-mixin-element';
-import {EventUtils} from '../../esl-event-listener/core/api';
+import {ESLEventUtils} from '../../esl-event-listener/core/api';
 
 describe('Shortcut helpers for EventUtils', () => {
   class TestHelpersMixin extends ESLMixinElement {
@@ -15,7 +15,7 @@ describe('Shortcut helpers for EventUtils', () => {
   afterAll(() => document.body.removeChild($el));
 
   test('$$on call leads to correct subscribe call with mixin as a host', () => {
-    const mock = jest.spyOn(EventUtils, 'subscribe');
+    const mock = jest.spyOn(ESLEventUtils, 'subscribe');
     const mixin = TestHelpersMixin.get($el) as TestHelpersMixin;
     const props = {event: 'test'};
     mixin.$$on(props, mixin.onEvent);
@@ -23,7 +23,7 @@ describe('Shortcut helpers for EventUtils', () => {
   });
 
   test('$$off call leads to correct unsubscribe call with mixin as a host', () => {
-    const mock = jest.spyOn(EventUtils, 'unsubscribe');
+    const mock = jest.spyOn(ESLEventUtils, 'unsubscribe');
     const mixin = TestHelpersMixin.get($el) as TestHelpersMixin;
     const props = {event: 'test'};
     mixin.$$off(props, mixin.onEvent);
diff --git a/src/modules/esl-mixin-element/test/mixin.listener.test.ts b/src/modules/esl-mixin-element/test/mixin.listener.test.ts
index a31c1ef74..52ece4513 100644
--- a/src/modules/esl-mixin-element/test/mixin.listener.test.ts
+++ b/src/modules/esl-mixin-element/test/mixin.listener.test.ts
@@ -27,7 +27,7 @@ describe('ESLMixinElement: listeners', () => {
       mockHandler.mockReset();
       el.click();
       expect(mockHandler).toBeCalled();
-      expect(mockHandler).lastCalledWith(TestElement.get(el), expect.any(Event), expect.any(Object));
+      expect(mockHandler).lastCalledWith(TestElement.get(el), expect.any(Event));
     });
 
     test('ESLMixinElement successfully auto unsubscribed', async () => {