Repository: exadel-inc/esl
Pull request #1602 (2023-04-11 01:43:50)

refactor(esl-event-listener): change API of extended EventTargets
BREAKING CHANGE: beta `ESLEventUtils.resize` replaced with `ESLResizeObserverTarget.for`
BREAKING CHANGE: beta `ESLEventUtils.decorate` replaced with `ESLDecoratedEventTarget.for`



Patch:
diff --git a/src/modules/esl-event-listener/README.md b/src/modules/esl-event-listener/README.md
index 5f018f158..978bb4d6d 100644
--- a/src/modules/esl-event-listener/README.md
+++ b/src/modules/esl-event-listener/README.md
@@ -8,7 +8,7 @@ Authors: _Alexey Stsefanovich (ala'n)_.
 
 Starting from the 4th release ESL has a built-in mechanism to work with DOM events.
 ESL event listeners have more control and advanced features than native DOM API.
-Besides, the [`ESlBaseElement`](../esl-base-element/README.md) and the [`ESLMixinElement`](../esl-mixin-element/README.md)
+Besides, the [`ESLBaseElement`](../esl-base-element/README.md) and the [`ESLMixinElement`](../esl-mixin-element/README.md)
 have even more pre-built syntax sugar to make the consumer's code briefer.
 
 One of the main advantages of ESL listeners is the extended control of subscriptions.
@@ -431,26 +431,26 @@ ESLEventUtils.initDescriptor(TestCases.prototype, 'onEventManualDesc', {event: '
 
 <a name="-esleventutilsdecorate"></a>
 
-### ⚡ `ESLEventUtils.decorate` and `ESLEventTargetDecorator`
+### ⚡ `ESLDecoratedEventTarget.for`
 
 In cases where the original event of the target happens too frequently to be handled every time,
 it might be helpful to limit its processing. In purpose to do that ESL allows the creation of decorated `EventTargets`.
 The decorated target will process the original target events dispatching with the passed async call decoration function
 (such as debounce or throttle).
 
-The `ESLEventUtils.decorate` creates an instance of `ESLEventTargetDecorator` that decorates
-passed original `EventTarget` event emitting. The instances of `ESLEventTargetDecorator` are lazy
-and do not subscribe to the original event until they have their own subscriptions of the same event type.
+The `ESLDecoratedEventTarget.for` creates an instance that decorates passed original `EventTarget` event emitting. 
+The instances of `ESLDecoratedEventTarget` are lazy and do not subscribe to the original event 
+until they have their own subscriptions of the same event type.
 
-⚠ Note `ESLEventUtils.decorate` method is cached, so created instances will be reused if the inner cache does not
+⚠ Note `ESLDecoratedEventTarget.for` method is cached, so created instances will be reused if the inner cache does not
 refuse additional arguments of the decorator. The cache does not handle multiple and non-primitive arguments.
 
 ```typescript
-ESLEventUtils.decorate(
+ESLDecoratedEventTarget.for(
   target: EventTarget,
   decorator: (fn: EventListener, ...args: any[]) => EventListener,
   ...args: any[]
-): ESLEventTargetDecorator;
+): ESLDecoratedEventTarget;
 ```
 
 **Parameters**:
@@ -459,9 +459,20 @@ ESLEventUtils.decorate(
 - `decorator` - decoration function to decorate original target `EventListener`s;
 - `args` - optional arguments to pass to `decorator`.
 
+**Example:**
+```typescript
+class Component {
+  @listen({
+    event: 'scroll', 
+    target: ESLDecoratedEventTarget.for(window, throttle)
+  })
+  onScroll() {}
+}
+```
+
 #### Sharing of the decorated targets
 
-As was mentioned above, the method `ESLEventUtils.decorate` (alias for `ESLEventTargetDecorator.cached`) works with
+As was mentioned above, the method `ESLDecoratedEventTarget.for` works with
 a cache for simple cases. But in some cases, we might be interested in creating wrappers with a complex
 param, or we want to limit params usage across the project.
 
@@ -469,7 +480,7 @@ It might sound obvious, but there are no restrictions on sharing exact instances
 
 ```typescript
 // shared-event-targets.ts
-export const DEBOUNCED_WINDOW = ESLEventUtils.decorate(window, debounce, 1000);
+export const DEBOUNCED_WINDOW = ESLDecoratedEventTarget.for(window, debounce, 1000);
 ```
 
 ```typescript
@@ -487,7 +498,7 @@ import {debounce} from '.../debounce';
 
 ESLEventUtils.subscribe(host, {
   event: 'resize',
-  target: /* instead just window */ ESLEventUtils.decorate(window, debounce, 250)
+  target: /* instead just window */ ESLDecoratedEventTarget.for(window, debounce, 250)
 }, onResizeDebounced);
 ```
 
@@ -502,7 +513,7 @@ import {throttle} from '.../throttle';
 
 ESLEventUtils.subscribe(host, {
   event: 'scroll',
-  target: /* instead just window */ ESLEventUtils.decorate(window, throttle, 250)
+  target: /* instead just window */ ESLDecoratedEventTarget.for(window, throttle, 250)
 }, onScrollThrottled);
 ```
 
@@ -512,17 +523,17 @@ to receive no more than one event per 250 milliseconds `scroll` events
 
 <a name="-esleventutilsresize"></a>
 
-### ⚡ `ESLEventUtils.resize` and `ESLResizeObserverTarget`
+### ⚡ `ESLResizeObserverTarget.for`
 
 When you deal with responsive interfaces, you might need to observe an element resizes instead of
 responding to the whole window change. There is a tool for this in the native DOM API - `ResizeObserver'.
 The only problem is that it does not use events, while in practice, we work with it in the same way.
 
-`ESLEventUtils.resize` creates cached `ResizeObserver` adaptation to `EventTarget` (`ESLResizeObserverTarget`)
+`ESLResizeObserverTarget.for` creates cached `ResizeObserver` adaptation to `EventTarget` (`ESLResizeObserverTarget`)
 that allows you to get `resize` events when the observed element changes its size.
 
 ```typescript
-ESLEventUtils.resize(el: Element): ESLResizeObserverTarget;
+ESLResizeObserverTarget.for(el: Element): ESLResizeObserverTarget;
 ```
 
 **Parameters**:
@@ -532,7 +543,7 @@ ESLEventUtils.resize(el: Element): ESLResizeObserverTarget;
 `ESLResizeObserverTarget` creates itself once for an observed object with a weak reference-based cache.
 So any way of creating `ESLResizeObserverTarget` will always produce the same instance.
 
-`ESLEventUtils.resize(el) /**always*/ === ESLEventUtils.resize(el)`
+`ESLResizeObserverTarget.for(el) /**always*/ === ESLResizeObserverTarget.for(el)`
 So there is no reason to cache it manually.
 
 Usage example:
@@ -540,12 +551,12 @@ Usage example:
 ```typescript
 ESLEventUtils.subscribe(host, {
   event: 'resize',
-  target: ESLEventUtils.resize(el)
+  target: ESLResizeObserverTarget.for(el)
 }, onResize);
 // or
 ESLEventUtils.subscribe(host, {
   event: 'resize',
-  target: (host) => ESLEventUtils.resize(host.el)
+  target: (host) => ESLResizeObserverTarget.for(host.el)
 }, onResize);
 ```
 
diff --git a/src/modules/esl-event-listener/core.ts b/src/modules/esl-event-listener/core.ts
index 0f92ad9b9..cef682b1f 100644
--- a/src/modules/esl-event-listener/core.ts
+++ b/src/modules/esl-event-listener/core.ts
@@ -1,4 +1,6 @@
 export * from './core/api';
+export * from './core/targets/resize.adapter';
+export * from './core/targets/decorated.target';
 export type {
   ESLListenerHandler,
   ESLListenerCriteria,
diff --git a/src/modules/esl-event-listener/core/api.ts b/src/modules/esl-event-listener/core/api.ts
index 83b213168..76e7c2e17 100644
--- a/src/modules/esl-event-listener/core/api.ts
+++ b/src/modules/esl-event-listener/core/api.ts
@@ -3,9 +3,6 @@ import {dispatchCustomEvent} from '../../esl-utils/dom/events/misc';
 import {ESLEventListener} from './listener';
 import {getAutoDescriptors, isEventDescriptor, initDescriptor} from './descriptors';
 
-import {ESLEventTargetDecorator} from './targets/decorated.target';
-import {ESLResizeObserverTarget} from './targets/resize.adapter';
-
 import type {
   ESLListenerHandler,
   ESLListenerCriteria,
@@ -113,21 +110,6 @@ export class ESLEventUtils {
     listeners.forEach((listener) => listener.unsubscribe());
     return listeners;
   }
-
-  // === EventTargets adapters ===
-  /**
-   * Creates an {@link EventTarget} adapter ({@link ESLResizeObserverTarget}) for {@link ResizeObserver}
-   * Note: the {@link ESLResizeObserverTarget} instances are unique for the related `targets`
-   * @deprecated it is an experimental functionality it can be changed in future releases
-   */
-  public static resize = ESLResizeObserverTarget.create;
-
-  /**
-   * Creates an {@link ESLEventTargetDecorator} decorator for any {@link EventTarget}
-   * Decorated {@link EventTarget} produces event according provided handler decoration
-   * @deprecated it is an experimental functionality it can be changed in future releases
-   */
-  public static decorate = ESLEventTargetDecorator.cached;
 }
 
 /** @deprecated alias for {@link ESLEventUtils} */
diff --git a/src/modules/esl-event-listener/core/targets/decorated.target.ts b/src/modules/esl-event-listener/core/targets/decorated.target.ts
index af3dd0bb2..e60cbe1bf 100644
--- a/src/modules/esl-event-listener/core/targets/decorated.target.ts
+++ b/src/modules/esl-event-listener/core/targets/decorated.target.ts
@@ -8,7 +8,7 @@ type ESLListenerDecorator<Args extends any[]> = (target: EventListener, ...args:
 const cache = memoizeOne((target: EventTarget) => {
   return memoizeOne(<Args extends any[]>(decorator: ESLListenerDecorator<Args>) => {
     return memoizeOne((...args: any[]) => {
-      return ESLEventTargetDecorator.create(target, decorator, ...args);
+      return ESLDecoratedEventTarget.create(target, decorator, ...args);
     }, Map);
   }, WeakMap);
 }, WeakMap);
@@ -16,12 +16,12 @@ const cache = memoizeOne((target: EventTarget) => {
 /**
  * {@link EventTarget} proxy that decorates original target listening
  */
-export class ESLEventTargetDecorator<Args extends any[]> extends SyntheticEventTarget {
-  public static cached<Args extends any[]>(
+export class ESLDecoratedEventTarget<Args extends any[]> extends SyntheticEventTarget {
+  public static for<Args extends any[]>(
     target: EventTarget,
     decorator: ESLListenerDecorator<Args>,
     ...args: Args
-  ): ESLEventTargetDecorator<Args> {
+  ): ESLDecoratedEventTarget<Args> {
     if (args.length > 1 || isObject(args[0])) {
       console.debug('[ESL]: Can\'t cache multi-argument decoration or decoration with object param');
       return this.create(target, decorator, ...args);
@@ -33,7 +33,7 @@ export class ESLEventTargetDecorator<Args extends any[]> extends SyntheticEventT
     target: EventTarget,
     decorator: ESLListenerDecorator<Args>,
     ...args: Args
-  ): ESLEventTargetDecorator<Args> {
+  ): ESLDecoratedEventTarget<Args> {
     return new this(target, decorator, args);
   }
 
diff --git a/src/modules/esl-event-listener/core/targets/resize.adapter.ts b/src/modules/esl-event-listener/core/targets/resize.adapter.ts
index febd11bd2..3afcfc8c2 100644
--- a/src/modules/esl-event-listener/core/targets/resize.adapter.ts
+++ b/src/modules/esl-event-listener/core/targets/resize.adapter.ts
@@ -1,9 +1,11 @@
 import {SyntheticEventTarget} from '../../../esl-utils/dom/events/target';
+import {resolveDomTarget} from '../../../esl-utils/abstract/dom-target';
 import {ESLElementResizeEvent} from './resize.adapter.event';
 
-import {resolveDomTarget} from '../../../esl-utils/abstract/dom-target';
 import type {ESLDomElementTarget} from '../../../esl-utils/abstract/dom-target';
 
+export {ESLElementResizeEvent};
+
 /** Adapter class for {@link ResizeObserver} that implements {@link EventTarget} */
 export class ESLResizeObserverTarget extends SyntheticEventTarget {
   /** {@link ESLResizeObserverTarget} instances holder */
@@ -27,7 +29,7 @@ export class ESLResizeObserverTarget extends SyntheticEventTarget {
   }
 
   /** Creates {@link ESLResizeObserverTarget} instance for the {@link ESLDomElementTarget} */
-  public static create(target: ESLDomElementTarget): ESLResizeObserverTarget {
+  public static for(target: ESLDomElementTarget): ESLResizeObserverTarget {
     return new ESLResizeObserverTarget(target);
   }
 
@@ -39,6 +41,7 @@ export class ESLResizeObserverTarget extends SyntheticEventTarget {
     target = resolveDomTarget(target);
     const instance = ESLResizeObserverTarget.mapping.get(target);
     if (instance) return instance;
+
     super();
     this.target = target;
     ESLResizeObserverTarget.mapping.set(this.target, this);


Test patch:
diff --git a/src/modules/esl-event-listener/test/targets/decorator.test.ts b/src/modules/esl-event-listener/test/targets/decorator.test.ts
index 3a88776f5..2a15d614f 100644
--- a/src/modules/esl-event-listener/test/targets/decorator.test.ts
+++ b/src/modules/esl-event-listener/test/targets/decorator.test.ts
@@ -1,53 +1,53 @@
 import {identity} from '../../../esl-utils/misc/functions';
 import {debounce} from '../../../esl-utils/async/debounce';
 import {throttle} from '../../../esl-utils/async/throttle';
-import {ESLEventUtils} from '../../core/api';
+import {ESLEventUtils, ESLDecoratedEventTarget} from '../../core';
 
-describe('ESLEventUtils.decorate proxy', () => {
+describe('ESLDecoratedEventTarget proxy', () => {
   const DEFAULT_TIMEOUT = 250;
 
-  describe('ESLEventUtils.decorate caching',  () => {
-    test('ESLEventUtils.decorate cached for target and fake fn', () => {
+  describe('ESLDecoratedEventTarget.for caching',  () => {
+    test('ESLDecoratedEventTarget.for cached for target and fake fn', () => {
       const fn = (arg: any) => arg;
-      expect(ESLEventUtils.decorate(window, fn) === ESLEventUtils.decorate(window, fn));
-      expect(ESLEventUtils.decorate(document, fn) !== ESLEventUtils.decorate(window, fn));
+      expect(ESLDecoratedEventTarget.for(window, fn) === ESLDecoratedEventTarget.for(window, fn));
+      expect(ESLDecoratedEventTarget.for(document, fn) !== ESLDecoratedEventTarget.for(window, fn));
     });
 
-    test('ESLEventUtils.decorate cached for target and debounce fn', () => {
-      expect(ESLEventUtils.decorate(window, debounce) === ESLEventUtils.decorate(window, debounce));
-      expect(ESLEventUtils.decorate(document, debounce) !== ESLEventUtils.decorate(window, debounce));
+    test('ESLDecoratedEventTarget.for cached for target and debounce fn', () => {
+      expect(ESLDecoratedEventTarget.for(window, debounce) === ESLDecoratedEventTarget.for(window, debounce));
+      expect(ESLDecoratedEventTarget.for(document, debounce) !== ESLDecoratedEventTarget.for(window, debounce));
     });
 
-    test('ESLEventUtils.decorate cached for target, fake fn and timeout', () => {
+    test('ESLDecoratedEventTarget.for cached for target, fake fn and timeout', () => {
       const fn = (arg: any, num: number) => arg;
-      expect(ESLEventUtils.decorate(window, fn, 100) === ESLEventUtils.decorate(window, fn, 100));
-      expect(ESLEventUtils.decorate(window, fn, 100) !== ESLEventUtils.decorate(window, fn, 150));
+      expect(ESLDecoratedEventTarget.for(window, fn, 100) === ESLDecoratedEventTarget.for(window, fn, 100));
+      expect(ESLDecoratedEventTarget.for(window, fn, 100) !== ESLDecoratedEventTarget.for(window, fn, 150));
     });
   });
 
-  describe('ESLEventUtils.decorate event target',  () => {
+  describe('ESLDecoratedEventTarget.for event target',  () => {
     const et = document.createElement('div');
-    const dec = ESLEventUtils.decorate(et, identity);
+    const dec = ESLDecoratedEventTarget.for(et, identity);
     const handler = jest.fn();
 
     beforeEach(() => dec.addEventListener('click', handler));
     afterEach(() => dec.removeEventListener('click', handler));
 
-    test('ESLEventUtils.decorate does not replace event.target', () => {
+    test('ESLDecoratedEventTarget.for does not replace event.target', () => {
       et.dispatchEvent(new Event('click'));
       expect(handler).lastCalledWith(expect.objectContaining({target: et}));
     });
-    test('ESLEventUtils.decorate uses ESLEventTargetDecorator instance as `event.currentTarget`', () => {
+    test('ESLDecoratedEventTarget.for uses ESLDecoratedEventTarget instance as `event.currentTarget`', () => {
       et.dispatchEvent(new Event('click'));
       expect(handler).lastCalledWith(expect.objectContaining({currentTarget: dec}));
     });
   });
 
-  describe('ESLEventUtils.decorate attribute processing', () => {
+  describe('ESLDecoratedEventTarget.for attribute processing', () => {
     const el = document.createElement('div');
     const handler = jest.fn();
     const dec = jest.fn(() => handler);
-    const decorated = ESLEventUtils.decorate(el, dec);
+    const decorated = ESLDecoratedEventTarget.for(el, dec);
 
     test('Creation does not cause execution', () => expect(dec).not.toBeCalled());
 
@@ -78,7 +78,7 @@ describe('ESLEventUtils.decorate proxy', () => {
     beforeAll(() => {
       ESLEventUtils.subscribe(host, {
         event: 'resize',
-        target: ESLEventUtils.decorate(window, debounce, DEFAULT_TIMEOUT)
+        target: ESLDecoratedEventTarget.for(window, debounce, DEFAULT_TIMEOUT)
       }, fn);
       jest.useFakeTimers();
     });
@@ -127,7 +127,7 @@ describe('ESLEventUtils.decorate proxy', () => {
     beforeAll(() => {
       ESLEventUtils.subscribe(host, {
         event: 'scroll',
-        target: ESLEventUtils.decorate(window, throttle, DEFAULT_TIMEOUT)
+        target: ESLDecoratedEventTarget.for(window, throttle, DEFAULT_TIMEOUT)
       }, fn);
       jest.useFakeTimers();
     });
diff --git a/src/modules/esl-event-listener/test/targets/resize.test.ts b/src/modules/esl-event-listener/test/targets/resize.test.ts
index e25203751..ae3599b0f 100644
--- a/src/modules/esl-event-listener/test/targets/resize.test.ts
+++ b/src/modules/esl-event-listener/test/targets/resize.test.ts
@@ -1,4 +1,4 @@
-import {ESLEventUtils} from '../../core/api';
+import {ESLResizeObserverTarget} from '../../core';
 import {getLastResizeObserverMock} from '../../../esl-utils/test/resizeObserver.mock';
 
 describe('ESLEventUtils: ResizeObserver EventTarget adapter', () => {
@@ -8,11 +8,11 @@ describe('ESLEventUtils: ResizeObserver EventTarget adapter', () => {
 
     test(
       'The same element produces the same event target',
-      () => expect(ESLEventUtils.resize(el1) === ESLEventUtils.resize(el1)).toBe(true)
+      () => expect(ESLResizeObserverTarget.for(el1) === ESLResizeObserverTarget.for(el1)).toBe(true)
     );
     test(
       'Different elements produces different event targets',
-      () => expect(ESLEventUtils.resize(el1) === ESLEventUtils.resize(el2)).toBe(false)
+      () => expect(ESLResizeObserverTarget.for(el1) === ESLResizeObserverTarget.for(el2)).toBe(false)
     );
   });
 
@@ -20,7 +20,7 @@ describe('ESLEventUtils: ResizeObserver EventTarget adapter', () => {
     describe('Element as a taget', () => {
       const mock = getLastResizeObserverMock();
       const el = document.createElement('div');
-      const target = ESLEventUtils.resize(el);
+      const target = ESLResizeObserverTarget.for(el);
 
       const cb1 = jest.fn();
       const cb2 = jest.fn();
@@ -56,8 +56,8 @@ describe('ESLEventUtils: ResizeObserver EventTarget adapter', () => {
       const mock = getLastResizeObserverMock();
       const el = document.createElement('div');
       const mixin = {$host: el};
-      const target = ESLEventUtils.resize(el);
-      const targetMixin = ESLEventUtils.resize(mixin);
+      const target = ESLResizeObserverTarget.for(el);
+      const targetMixin = ESLResizeObserverTarget.for(mixin);
 
       const cb1 = jest.fn();
       const cb2 = jest.fn();
@@ -104,19 +104,19 @@ describe('ESLEventUtils: ResizeObserver EventTarget adapter', () => {
     };
 
     const cb1_1 = jest.fn();
-    ESLEventUtils.resize(el1).addEventListener(cb1_1);
+    ESLResizeObserverTarget.for(el1).addEventListener(cb1_1);
     expect(cb1_1).not.toBeCalled();
 
     const cb1_2 = jest.fn();
-    ESLEventUtils.resize(el1).addEventListener(cb1_2);
+    ESLResizeObserverTarget.for(el1).addEventListener(cb1_2);
     expect(cb1_2).not.toBeCalled();
 
     const cb2_1 = jest.fn();
-    ESLEventUtils.resize(el2).addEventListener(cb2_1);
+    ESLResizeObserverTarget.for(el2).addEventListener(cb2_1);
     expect(cb2_1).not.toBeCalled();
 
     const cb3_1 = jest.fn();
-    ESLEventUtils.resize(mixin).addEventListener(cb3_1);
+    ESLResizeObserverTarget.for(mixin).addEventListener(cb3_1);
 
     const fakeEntry = (el: Element) => ({
       target: el,