Repository: exadel-inc/esl
Pull request #1451 (2023-01-30 17:39:40)

docs(esl-event-listener): `esl-event-listener` module updated documentation & type fixes
Closing: #1450 


Patch:
diff --git a/pages/src/common/markdown.less b/pages/src/common/markdown.less
index 3870ae777..d1ca9898c 100644
--- a/pages/src/common/markdown.less
+++ b/pages/src/common/markdown.less
@@ -7,7 +7,7 @@
       margin-bottom: .5rem;
     }
 
-    p:last-child {
+    p:only-child {
       margin-bottom: 0;
     }
   }
diff --git a/src/modules/esl-event-listener/README.md b/src/modules/esl-event-listener/README.md
index bb2526b9a..e565f09b2 100644
--- a/src/modules/esl-event-listener/README.md
+++ b/src/modules/esl-event-listener/README.md
@@ -1,119 +1,350 @@
 # ESL Event Listener module
 
-Version: *1.0.0*.
+Version: *2.0.0*.
 
 Authors: *Alexey Stsefanovich (ala'n)*.
 
 <a name="intro"></a>
 
-Starting from the 4th release ESL has a built-in mechanism to work with DOM events. 
-ESLEventListeners has more control and more advanced features than native DOM API.
-In addition, ESLMixinElement and ESlBaseElement have even more syntax sugar 
-to make consumer's code super small and clean.
+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)
+have even more pre-built syntax sugar to make the consumer's code briefer.
 
-## `ESLEventUtils` class
+One of the main advantages of ESL listeners is the extended control of subscriptions.
+All ESL listeners and their declarations are saved and associated with the host element. 
+It means that ESL listeners can be subscribed or unsubscribed at any time in various ways.
+And most importantly, you do not need the original callback handler to do this.
 
-`ESLEventUtils` is a root class for the event listener module, it contains ESL event listeners module API.
-One of the main advantages of `ESLEventUtils` over the native addEventListener is the extended control of subscriptions.
-All ESL listeners are saved and associated with the host element, after that, 
-the listener can be unhooked at any time in a variety of ways. 
-And most importantly, you do not need the original callback handler for this.
+---
 
+## Basic concepts
 
-Here the `ESLEventUtils` API:
+The ESL event listener module is based on the following major terms and classes:
 
-### ⚡ `ESLEventUtils.subscribe`
-The main method to create ESLEventListener and subscribe to DOM events.
+### <a name="the-host">The `host`</a>
+
+Everything that happens with ESL event listeners should be associated with a `host` object.
+The `host` is an object that "owns" (registers / deletes) the subscription.
+
+By default, the `host` is used as an `EventTarget` (or an object implementing `EventTarget` interface) to subscribe.
+But the `host` object is not necessarily related to an `EventTarget`.
+We have at least two options to change the target. First of all, you can define the target explicitly.
+Another way is to specify the default DOM target of the host object by providing a special `$host` key
+(see `ESLMixinElement` implementation).
+
+The `host` object is also used as a context to call the handler function of the subscription.
+
+### <a name="the-handler">The `handler`</a>
+
+The `handler` is a function that is used to process the subscription event.
+ESL declares a generic type to describe such functions - `ESLListenerHandler`;
 
 ```typescript
-subscribe(host: HTMLElement, handler: ESLListenerHandler): ESLEventListener[];
-subscribe<EType extends keyof ESLListenerEventMap>(
-  host: HTMLElement,
-  descriptor: EType | ESLListenerDescriptor<EType>,
-  handler: ESLListenerHandler<ESLListenerEventMap[EType]>
-): ESLEventListener[];
-subscribe<EType extends keyof ESLListenerEventMap>(
-  host: HTMLElement,
-  descriptor: Partial<ESLListenerDescriptor>,
-  handler: ESLListenerDescriptorFn<EType>
-): ESLEventListener[];
+export type ESLListenerHandler<EType extends Event = Event> = (event: EType) => void;
 ```
 
+### <a name="the-esleventlistener-class-and-subscription">The `ESLEventListener` class and *subscription*</a>
+
+The subscriptions created by the ESL event listener module are instances of `ESLEventListener` class.
+All active subscriptions are stored in a hidden property of the `host` object.
+
+`ESLEventListener` has the following basic properties:
+- `event` - event type that the subscription is listening to;
+- `handler` - reference for the function to call to handle the event (see [The handler](#the-handler));
+- `host` - reference for the object that holds the subscription (see [The host](#the-host));
+- `target` - definition of `EventTarget` element (or string `TraversingQuery` to find it, see details in [`ESLEventDesriptor`](#descriptors-esleventdesriptor-esleventdesriptorfn));
+- `selector` - CSS selector to use built-in event delegation; 
+- `capture` - marker to use the capture phase of the DOM event life-cycle;
+- `passive` - marker to use passive (non-blocking) subscription of the native event (if supported);
+- `once` - marker to destroy the subscription after the first event catch.
+
+All of the `ESLEventListener` instance fields are read-only; the subscription can't be changed once created. 
+
+The `ESLEventListener`, as a class, describes the subscription behavior and
+contains static methods to create and manage subscriptions.
+
+### <a name="descriptors-esleventdesriptor-esleventdesriptorfn">Descriptors (`ESLEventDesriptor`, `ESLEventDesriptorFn`)</a>
+
+The event listener *Descriptor* is an object to describe future subscriptions.
+The ESL event listeners module has a few special details regarding such objects.
+
+A simple descriptor is an object that is passed to ESL event listener API to create a subscription.
+It contains almost the same set of keys as the `ESLEventListener` instance.
+
+In addition to that, ESL allows you to combine the `ESLEventDesriptor` data with the handler function.
+`ESLEventDesriptorFn` is a function handler that is decorated with the `ESLEventDesriptor` properties.
+
+Here is the list of supported keys of `ESLEventDesriptor`:
+
+- #### `event` key
+  <u>Type:</u> `string | PropertyProvider<string>`  
+  <u>Description:</u> the event type for subscription. 
+  Can be provided as a string or via provider function that will be called right before the subscription.
+
+  The event string (as a literal, or returned by `PropertyProvider`) can declare multiple event types separated by space.
+  ESL will create a subscription (`ESLEventListener` object) for each event type in this case.  
+
+- #### `target` key
+  <u>Type:</u> `string | EventTarget | EventTarget[] | PropertyProvider<string | EventTarget | EventTarget[]>`  
+  <u>Default Value:</u> `host` object itself or `$host` key of the `host` object  
+  <u>Description:</u> the key to declare exact EventTarget for the subscription.
+  In case the `target` key is a string it is considered as a [`TraversingQuery`](../esl-traversing-query/README.md). 
+  The query finds a target relatively to `host | host.$host` object, or in bounds of the DOM tree if it is absolute.
+  The `target` key also supports an exact reference for `EventTarget`(s). 
+
+  ⚠ Any `EventTarget` or even ESL `SynteticEventTarget` (including [`ESLMediaQuery`](../esl-media-query/README.md)) 
+  can be a target for listener API.
+  
+  The `target` property can be declared via `PropertyProvider` as well.  
+  
+- #### `selector` key
+  <u>Type:</u> `string | PropertyProvider<string>`  
+  <u>Default Value:</u> `null`  
+  <u>Description:</u> the CSS selector to filter event targets for event delegation mechanism.
+  
+  Supports `PropertyProvider` to declare the computed value as well.
+  
+- #### `capture` key
+  <u>Type:</u> `boolean`  
+  <u>Default Value:</u> `false`  
+  <u>Description:</u> marker to use capturing phase of the DOM event to handle.
+ 
+- #### `passive` key
+  <u>Type:</u> `boolean`  
+  <u>Default Value:</u> `true` if the event type is `wheel`, `mousewheel`, `touchstart` or `touchmove`
+  <u>Description:</u> marker to use passive subscription to the native event.
+
+  ⚠ ESL uses passive subscription by default for `wheel`, `mousewheel`, `touchstart`, `touchmove` events.
+  You need to declare `passive` key explicitly to override this behavior.
+  
+- #### `once` key
+  <u>Type:</u> `boolean`  
+  <u>Default Value:</u> `false`  
+  <u>Description:</u> marker to unsubscribe the listener after the first successful handling of the event.
+
+- #### `auto` key (for `ESLEventDesriptorFn` declaration only)
+  <u>Type:</u> `boolean`  
+  <u>Default Value:</u> `false` for `ESLEventUtils.initDescriptor`, `true` for `@listen` decorator
+  <u>Description:</u> marker to make an auto-subscribable descriptor. See [Automatic (collectable) descriptors](#automatic-collectable-descriptors).
+  
+- #### `inherit` key (for `ESLEventDesriptorExt` only)
+  <u>Type:</u> `boolean`  
+  <u>Description:</u> available in extended version of `ESLEventDesriptor` that is used in the descriptor declaration API.
+  Allows to inherit `ESLEventDesriptor` data from the `ESLEventDesriptorFn` from the prototype chain. 
+  See [`initDescriptor`](#-esleventutilsinitdescriptor) usages example.
+  
+
+
+### <a name="automatic-collectable-descriptors">Automatic (collectable) descriptors</a>
+
+Auto-collectable (or auto-subscribable) descriptors can be subscribed at once during the initialization of the `host` object.
+
+To make an `ESLEventDesriptorFn` auto-collectable, the consumer should declare it with the `auto` marker using
+`ESLEventUtils.initDescriptor` or `@listen` decorator.
+
+⚠ `ESLEventUtils.initDescriptor` (or `@listen`) stores the auto-collectable descriptors in the internal collection on the `host`.
+
+The `ESLBaseElment` and the `ESLMixinElement` subscribes all auto-collectable descriptors in the `connectedCallback`.
+See the usage of [`ESLEventUtils.subscibe`](#-esleventutilssubscribe) for more details.
+
+
+### `PropertyProvider` for `event`, `selector`, or `target`
+
+The descriptor declaration usually happens with the class declaration when the instance and its subscription
+do not exist. We might have a problem if we want to pass subscription parameters that depend on the instance.
+
+To resolve such a case, the `event`, `selector`, and `target` keys of ESL event listener API support
+`PropertyProvider` mechanism:
+```typescript
+type PropertyProvider<T> = (this: unknown, that: unknown) => T;
+```
+
+See examples in the [ESLEventUtils.initDescriptor](#-esleventutilsinitdescriptor) section.
+
+---
+
+## Public API (`ESLEventUtils`)
+
+The units mentioned earlier are mostly implementation details of the module.
+
+`ESLEventUtils` is a facade for all ESL event listener module capabilities.
+
+Here is the module Public API:
+
+<a name="-esleventutilssubscribe"></a>
+### ⚡ `ESLEventUtils.subscribe`
+Creates and subscribes an `ESLEventListener`.
+
+- Subscribes all auto-collectable (subscribable) descriptors of the `host` object:
+    ```typescript
+    ESLEventUtils.subscribe(host: object)
+    ```
+- Subscribes `handler` function to the DOM event declared by `eventType` string:
+    ```typescript
+    ESLEventUtils.subscribe(host: object, eventType: string, handler: ESLListenerHandler)
+    ```
+- Subscribes `handler` instance of `ESLEventDescriptorFn` using embedded meta-information:
+    ```typescript
+    ESLEventUtils.subscribe(host: object, handler: ESLEventDescriptorFn)
+    ```
+- Subscribes `handler` function using `ESLEventDescriptor`:
+    ```typescript
+    ESLEventUtils.subscribe(host: object, descriptor: ESLEventDescriptor, handler: ESLListenerHandler)
+    ```
+- Subscribes `handler` instance of `ESLEventDescriptorFn` with `ESLEventDescriptor` overriding meta-data:
+    ```typescript
+    ESLEventUtils.subscribe(host: object, descriptor: ESLEventDescriptor, handler: ESLEventDescriptorFn)
+    ```
+
 **Parameters**:
 - `host` - host element to store subscription (event target by default);
-- `eventDesc` - Event type or object of event description data;
-- `handler` - Callback handler. See [ESLListenerHandler](#listenerHandler).
+- `eventType` - string DOM event type;
+- `descriptor` - event description data (`ESLEventDescriptor`);
+- `handler` - function callback handler or instance of `ESLEventDescriptorFn`
 
 Examples:
-- `ESLEventUtils.subscribe($host, handlerFn);` - 
-subscribes decorated `handlerFn` method to the `target`;
-- `ESLEventUtils.subscribe($host, 'click', handlerFn);` - 
+- `ESLEventUtils.subscribe(host);` -
+subscribes all auto-subscriptions of the `host`;
+- `ESLEventUtils.subscribe(host, handlerFn);` - 
+subscribes `handlerFn` method (decorated as an `ESLEventDescriptorFn`) to the `handlerFn.target`;
+- `ESLEventUtils.subscribe(host, 'click', handlerFn);` - 
 subscribes `handlerFn` function with the passed event type;
-- `ESLEventUtils.subscribe($host, {event: 'scroll', target: window}, handlerFn);` - 
+- `ESLEventUtils.subscribe(host, {event: 'scroll', target: window}, handlerFn);` - 
 subscribes `handlerFn` function with the passed additional descriptor data.
 
 
+<a name="-esleventutilsunsubscribe"></a>
 ### ⚡ `ESLEventUtils.unsubscribe`
-Method of the `ESLEventUtils` interface that allows subscribing elements to DOM events.
+
+Allows unsubscribing existing subscriptions.
 
 ```typescript
-unsubscribe(
-  host: HTMLElement,
-  ...criteria: ESLListenerCriteria[]
-): ESLEventListener[];
+unsubscribe(host: HTMLElement, ...criteria: ESLListenerCriteria[]): ESLEventListener[];
 ```
 
 **Parameters**:
-- `host` - An element to unsubsribe;
-- `criteria` - An optional set of criteria to filter listeners to remove.
+- `host` - host element to find subscriptions;
+- `criteria` - optional set of criteria to filter listeners to remove.
 
 Examples:
-- `ESLEventUtils.unsubscribe($host);` will unsubscribe everything bound to $host
-- `ESLEventUtils.unsubscribe($host, handlerFn);` will unsubscribe everything that is bound to $host and is handled by handlerFn
-- `ESLEventUtils.unsubscribe($host, 'click');` will unsubscribe everything bound to $host and processes 'click' event
-- `ESLEventUtils.unsubscribe($host, 'click', handlerFn);` will unsubscribe everything that is bound to $host, processes 'click' event and handles handlerFn
+- `ESLEventUtils.unsubscribe(host);` - unsubscribes everything bound to the `host`
+- `ESLEventUtils.unsubscribe(host, handlerFn);` - unsubscribes everything that is bound to the `host` and is handled by the `handlerFn`
+- `ESLEventUtils.unsubscribe(host, 'click');` - unsubscribes everything bound to the `host` and processing `click` event
+- `ESLEventUtils.unsubscribe(host, 'click', handlerFn);` - unsubscribes everything that is bound to the `host`, processing `click` event and is handled by the `handlerFn`
 - There can be any number of criteria.
 
+
+<a name="-esleventutilsiseventdescriptor"></a>
 ### ⚡ `ESLEventUtils.isEventDescriptor`
-Predicate to check if the passed argument is a function decorated with `ESLEventListener` metadata (`ESLListenerDescriptorFn``).
+Predicate to check if the passed argument is a type of `ESLListenerDescriptorFn = ESLEventHandler & Partial<ESLEventDescriptor>`.
 
 ```typescript
 ESLEventUtils.isEventDescriptor(obj: any): obj is ESLListenerDescriptorFn;
 ```
 
+<a name="-esleventutilsgetautodescriptors"></a>
+### ⚡ `ESLEventUtils.getAutoDescriptors`
+Gathers auto-subscribable (collectable) descriptors from the passed object.
+
+```typescript
+ESLEventUtils.descriptors(host?: any): ESLListenerDescriptorFn[]
+```
+
+**Parameters**:
+- `host` - object to get auto-collectable descriptors from;
+
+<a name="-esleventutilsdescriptors"></a>
 ### ⚡ `ESLEventUtils.descriptors`
-Method of the `ESLEventUtils` interface that gathers descriptors from the passed object. See [ESLListenerDescriptorFn](#listenerDescFn).
+Deprecated alias for `ESLEventUtils.getAutoDescriptors`
+
+
+<a name="-esleventutilsinitdescriptor"></a>
+### ⚡ `ESLEventUtils.initDescriptor`
+
+Decorates the passed key of the host object as `ESLEventDescriptorFn`
 
 ```typescript
-ESLEventUtils.descriptors(
-  target?: any, 
-  auto: boolean = true
-): ESLListenerDescriptorFn[]
+ESLEventUtils.initDescriptor<T extends object>(
+  host: T, 
+  key: keyof T & string, 
+  desc: ESLEventDescriptorExt
+): ESLEventDescriptorFn;
 ```
 
 **Parameters**:
-- `target` - An object to get descriptors from;
-- `auto` - A boolean value indicating that the listener should be automatically subscribed within connected callback.
+- `host` - host object holder of decorated function;
+- `key` - key of the `host` object that contains a function to decorate;
+- `desc` - `ESLEventDescriptor` (extended) meta information to describe future subscriptions.
+
+The extended `ESLEventDescriptor` information allows passing `inherit` marker to create a new descriptor instance
+based on the descriptor declared in the prototype chain for the same key.
+
+⚠ If such a key is not found, a `ReferenceError` will be thrown.
+
+Example:
+ ```typescript
+ class MyElement {
+   onEvent() {} 
+ }
+ ESLEventUtils.initDescriptor(MyElement.prototype, 'onEvent', {event: 'event'});
+```
+
+#### `@listen` decorator
+
+The `@listen` decorator (available under `esl-utils/decorators`) is syntax sugar above `ESLEventUtils.initDescriptor` method.
+It allows you to declare class methods as an `ESLEventDescriptorFn` using TS `experimentalDecorators` feature.
+
+Listeners described by `@listen` are auto-subscribable if they are not inherited and not declared as manual explicitly.
+In case of inheritance the `auto` marker will be inherited from the parent descriptor.
+
+
+Example:
+```typescript
+class MyEl extends ESLBaseElement {
+  private event: string;
+  private selector: string;
+  // Shortcut with just an event type
+  @listen('click')
+  onClick() {}
+  // Shortcut with event type declared by PropertyProvider
+  @listen((that: MyEl) => that.event)
+  onEventProvided() {}
+  // Full list of options is available
+  @listen({event: 'click', target: 'body', capture: true})
+  onBodyClick(e) {}
+  // Property Providers example
+  @listen({
+    event: (that: MyEl) => that.event, 
+    seletor: (that: MyEl) => that.selector
+  })
+  onEventProvidedExt(e) {}
+  // Will not subscribe authomatically
+  @listen({event: 'click', auto: false})
+  onClickManual(e) {}
+}
+ ```
 
 
+<a name="-esleventutilslisteners"></a>
 ### ⚡ `ESLEventUtils.listeners`
-Method of the `ESLEventUtils` interface that gathers listeners currently subscribed to the target.
+
+Gathers listeners currently subscribed to the passed `host` object.
 
 ```typescript
-ESLEventUtils.listeners(
-  target: HTMLElement,
-  ...criteria: ESLListenerCriteria[]
-): ESLEventListener[];
+ESLEventUtils.listeners(host: object, ...criteria: ESLListenerCriteria[]): ESLEventListener[];
 ```
 
 **Parameters**:
-- `host` - an object that stores and relates to the handlers;
-- `criteria` - an optional set of criteria to filter listeners list.
+- `host` - object that stores and relates to the handlers;
+- `criteria` - optional set of criteria to filter the listeners list.
 
+
+<a name="-esleventutilsdispatch"></a>
 ### ⚡ `ESLEventUtils.dispatch`
 
-Method of the `ESLEventUtils` interface that is used to dispatch custom events.
-The event that is being dispatched is bubbling and cancelable by default.
+Dispatches custom DOM events.
+The dispatched event is bubbling and cancelable by default.
 
 ```typescript
 ESLEventUtils.dispatch(
@@ -124,32 +355,88 @@ ESLEventUtils.dispatch(
 ```
 
 **Parameters**:
-- `el` - EventTarget to dispatch event;
+- `el` - `EventTarget` to dispatch event;
 - `eventName` - name of the event to dispatch;
 - `eventInit` - object that specifies characteristics of the event.
-  This parameter can be used to overwrite the default behavior of bubbling and being cancelable.
 
 
-## Listeners methods on `ESLBaseElement` / `ESLMixinElement`
-All the inheritors of `ESLBaseElement` and `ESLMixinElement` now have `$$on` and `$$off` methods,
-which still have the same capabilities but only set the current element as the host.
+### Listeners Full Showcase Example
+```typescript
+class TestCases {
+  bind() {
+    // Subcribes all auto descriptors (onEventAutoDescSugar and onEventAutoDesc)
+    ESLEventUtils.subscribe(this); 
+
+    // Subscribes onEventManualFn on click
+    ESLEventUtils.subscribe(this, 'click', this.onEventManualFn);
+
+    // Subscribes onEventManualFn on window resize
+    ESLEventUtils.subscribe(this, {event: 'resize', target: window}, this.onEventManualFn);
+
+    // Subscribes onEventManualDesc using embeded information
+    ESLEventUtils.subscribe(this, this.onEventManualDesc);
+
+    // Subscribes onEventManualDesc using merged embeded and passed information
+    ESLEventUtils.subscribe(this, {target: window}, this.onEventManualDesc); 
+  }
+
+  unbind() {
+    // Unsubcribes all subscriptions
+    ESLEventUtils.unsubscribe(this);
+
+    // Unsubcribes just onEventAutoDesc
+    ESLEventUtils.unsubscribe(this, this.onEventAutoDesc);
+  }
+  
+  @listen('event')
+  onEventAutoDescSugar() {}
+  
+  onEventAutoDesc() {}
+  
+  onEventManualFn() {}
+  
+  onEventManualDesc() {}
+}
 
+ESLEventUtils.initDescriptor(TestCases.prototype, 'onEventAutoDesc', {event: 'event', auto: true});
+ESLEventUtils.initDescriptor(TestCases.prototype, 'onEventManualDesc', {event: 'event'});
+```
+
+---
+
+## <a name="embedded-behavior-of-eslbaseelement-eslmixinelement">Embedded behavior of `ESLBaseElement` / `ESLMixinElement`</a>
+
+### Shortcuts
+
+All the inheritors of `ESLBaseElement` and `ESLMixinElement` contain the short aliases for some `ESLEventUtils` methods.
+The host parameter of the shortcut methods is always targeting the current element/mixin.
+
+- `$$on` ~ `ESLEventUtils.subscribe(this, ...)`
+- `$$off` ~ `ESLEventUtils.unsubscribe(this, ...)`
+- `$$fire` ~ `ESLEventUtils.dispatch(this, ...)`
+
+Example:
 ```typescript
-this.$$on('click', this.onClick);
-this.$$off('click'); // или this.$$off(this.onClick)
+this.$$on('click', this.onClick); // ESLEventUtils.subscribe(this, 'click', this.onClick)
+this.$$off('click'); // ESLEventUtils.unsubscribe(this, 'click')
 ```
 
-### Auto-unbind
-All the inheritors of `ESLBaseElement` and `ESLMixinElement` unsubscribe from events attached via ESL automatically 
-on `disconnectedCallback`.
+### Auto-subscription / Auto-unbinding
 
-### Decorator `@listen`
-The `@listen` decorator is now available in ESL, which makes it even easier to work with events
+All the inheritors of `ESLBaseElement` and `ESLMixinElement` automatically subscribe to all declared auto-subscribable descriptors 
+of their prototype chain.
 
-The following short snippet of code describes a listener that will automatically subscribe and unsubscribe 
-on connected/disconnected callback inside `ESLBaseElement` or `ESLMixinElement`:
+They also unsubscribe all own listeners attached via ESL automatically on `disconnectedCallback`.
+
+The following short snippet of code describes a listener that will automatically subscribe and unsubscribe
+on connected/disconnected callback inside `ESLBaseElement`:
  ```typescript
 class MyEl extends ESLBaseElement {
+  // connectedCallback() {
+  //   super.connectedCallback(); // - already contains ESLEventUtils.subscribe(this) call
+  // }
+
+  // Will be subscribed automatically on connectedCallback and unsubscribed on disconnectedCallback
   @listen('click')
   onClick(e) { 
     //... 
@@ -157,16 +444,6 @@ class MyEl extends ESLBaseElement {
 }
  ```
 
-Full descriptor options are available with decorator as well
- ```typescript
-class MyEl extends ESLBaseElement {
-  @listen({event: 'click', target: 'body', capture: true})
-  onBodyClick(e) {
-    // ...
-  }
-}
- ```
-
 You can manage the subscription manually and link the whole meta information or part of it with the handler itself
  ```typescript
 class MyEl extends ESLBaseElement {
@@ -178,9 +455,7 @@ class MyEl extends ESLBaseElement {
   myMethod() {
     this.$$on(this.onClick); // Manual subscription
     this.$$on({target: 'body'}, this.onClick); // Manual subscription with parameters (will be merged)
-    this.$$off(this.onClick); // The same behavior as in the examples above
-
-    ESLEventUtils.subscribe($host, this.onClick); // Low-level API support
+    this.$$off(this.onClick); // Unsubscribes this.onClick method
   }
 }
  ```
diff --git a/src/modules/esl-event-listener/core/api.ts b/src/modules/esl-event-listener/core/api.ts
index 5dd9c2eae..6d1def4ad 100644
--- a/src/modules/esl-event-listener/core/api.ts
+++ b/src/modules/esl-event-listener/core/api.ts
@@ -44,7 +44,7 @@ export class ESLEventUtils {
    * @param host - host object (listeners context) to associate subscription
    * @param criteria - optional set of criteria {@link ESLListenerCriteria} to filter listeners list
    */
-  public static listeners(host: unknown, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
+  public static listeners(host: object, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
     return ESLEventListener.get(host, ...criteria);
   }
 
@@ -52,13 +52,13 @@ export class ESLEventUtils {
    * Subscribes all auto descriptors of the host
    * @param host - host object (listeners context) to find descriptors and associate subscription
    * */
-  public static subscribe(host: unknown): ESLEventListener[];
+  public static subscribe(host: object): ESLEventListener[];
   /**
    * Subscribes decorated as an {@link ESLListenerDescriptorFn} `handler` function
    * @param host - host object (listeners context) to associate subscription
    * @param handler - handler function decorated as {@link ESLListenerDescriptorFn}
    */
-  public static subscribe(host: unknown, handler: ESLListenerHandler): ESLEventListener[];
+  public static subscribe(host: object, handler: ESLListenerHandler): ESLEventListener[];
   /**
    * Subscribes `handler` function with the passed event type or {@link ESLListenerDescriptor} with event type declared
    * @param host - host object (listeners context) to associate subscription
@@ -66,7 +66,7 @@ export class ESLEventUtils {
    * @param handler - handler function to subscribe
    */
   public static subscribe<EType extends keyof ESLListenerEventMap>(
-    host: unknown,
+    host: object,
     descriptor: EType | ESLListenerDescriptor<EType>,
     handler: ESLListenerHandler<ESLListenerEventMap[EType]>
   ): ESLEventListener[];
@@ -77,12 +77,12 @@ export class ESLEventUtils {
    * @param handler - handler function decorated as {@link ESLListenerDescriptorFn}
    */
   public static subscribe<EType extends keyof ESLListenerEventMap>(
-    host: unknown,
+    host: object,
     descriptor: Partial<ESLListenerDescriptor>,
     handler: ESLListenerDescriptorFn<EType>
   ): ESLEventListener[];
   public static subscribe(
-    host: unknown,
+    host: object,
     eventDesc?: string | Partial<ESLListenerDescriptor> | ESLListenerHandler,
     handler: ESLListenerHandler = eventDesc as ESLListenerDescriptorFn
   ): ESLEventListener[] {
@@ -105,7 +105,7 @@ export class ESLEventUtils {
    * @param host - host element that stores subscriptions (listeners context)
    * @param criteria - optional set of criteria {@link ESLListenerCriteria} to filter listeners to remove
    */
-  public static unsubscribe(host: unknown, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
+  public static unsubscribe(host: object, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
     const listeners = ESLEventListener.get(host, ...criteria);
     listeners.forEach((listener) => listener.unsubscribe());
     return listeners;
diff --git a/src/modules/esl-event-listener/core/descriptors.ts b/src/modules/esl-event-listener/core/descriptors.ts
index d25e91102..f1156f8e4 100644
--- a/src/modules/esl-event-listener/core/descriptors.ts
+++ b/src/modules/esl-event-listener/core/descriptors.ts
@@ -33,7 +33,7 @@ export function isEventDescriptor(obj: any): obj is ESLListenerDescriptorFn {
 }
 
 /** Gets {@link ESLListenerDescriptorFn}s of the passed object */
-export function getAutoDescriptors(host: unknown): ESLListenerDescriptorFn[] {
+export function getAutoDescriptors(host: object): ESLListenerDescriptorFn[] {
   if (!isObject(host)) return [];
   const keys = getDescriptorsKeysFor(host);
   const values = keys.map((key) => host[key]);
diff --git a/src/modules/esl-event-listener/core/listener.ts b/src/modules/esl-event-listener/core/listener.ts
index d085a8fad..a70bc8e36 100644
--- a/src/modules/esl-event-listener/core/listener.ts
+++ b/src/modules/esl-event-listener/core/listener.ts
@@ -140,20 +140,20 @@ export class ESLEventListener implements ESLListenerDefinition, EventListenerObj
    * Gets stored listeners array the passed `host` object
    * Supports additional filtration criteria
    */
-  public static get(host?: any, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
-    if (!host) return [];
+  public static get(host?: object, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
+    if (!isObject(host)) return [];
     const listeners = (host[LISTENERS] || []) as ESLEventListener[];
     if (!criteria.length) return listeners;
     return listeners.filter((listener) => criteria.every(listener.matches, listener));
   }
   /** Adds listener to the listener store of the host object */
-  protected static add(host: any, instance: ESLEventListener): void {
-    if (!host) return;
+  protected static add(host: object, instance: ESLEventListener): void {
+    if (!isObject(host)) return;
     if (!Object.hasOwnProperty.call(host, LISTENERS)) host[LISTENERS] = [];
     host[LISTENERS].push(instance);
   }
   /** Removes listener from the listener store of the host object */
-  protected static remove(host: any, instance: ESLEventListener): void {
+  protected static remove(host: object, instance: ESLEventListener): void {
     const listeners = ESLEventListener.get(host);
     const value = listeners.filter((listener) => listener !== instance);
     Object.defineProperty(host, LISTENERS, {value, configurable: true});
@@ -166,7 +166,7 @@ export class ESLEventListener implements ESLListenerDefinition, EventListenerObj
    * @param descriptor - event or {@link ESLListenerDescriptor} with defined event type
    */
   public static subscribe(
-    host: unknown,
+    host: object,
     handler: ESLListenerHandler | ESLListenerDescriptorFn,
     descriptor: ESLListenerDescriptor = handler as ESLListenerDescriptorFn
   ): ESLEventListener[]  {
@@ -176,8 +176,8 @@ export class ESLEventListener implements ESLListenerDefinition, EventListenerObj
     return listeners.filter((listener) => listener.subscribe());
   }
 
-  /** Creates or resolve existing event listeners by handler and descriptors */
-  public static createOrResolve(host: unknown, handler: ESLListenerHandler, desc: ESLListenerDescriptor): ESLEventListener[] {
+  /** Creates or resolves existing event listeners by handler and descriptors */
+  public static createOrResolve(host: object, handler: ESLListenerHandler, desc: ESLListenerDescriptor): ESLEventListener[] {
     if (!isObject(host)) return [];
     const eventString = resolveProperty(desc.event, host);
     const listeners: ESLEventListener[] = [];
diff --git a/src/modules/esl-utils/misc/object/types.ts b/src/modules/esl-utils/misc/object/types.ts
index 422fdf7ce..c72f009f4 100644
--- a/src/modules/esl-utils/misc/object/types.ts
+++ b/src/modules/esl-utils/misc/object/types.ts
@@ -1,5 +1,5 @@
 /** Checks that passed value is object, but not a callable-object (function) */
-export const isObject = (obj: any): obj is Record<string, any> => !!obj && typeof obj === 'object';
+export const isObject = (obj: any): obj is Record<string | symbol, any> => !!obj && typeof obj === 'object';
 /** Checks that passed value is an object or function */
 export const isObjectLike = (obj: any): boolean => isObject(obj) || typeof obj === 'function';
 /** Checks if the passed value is primitive */


Test patch:
diff --git a/src/modules/esl-event-listener/test/descriptor.test.ts b/src/modules/esl-event-listener/test/descriptor.test.ts
index a7fb4e517..ee94e2f6f 100644
--- a/src/modules/esl-event-listener/test/descriptor.test.ts
+++ b/src/modules/esl-event-listener/test/descriptor.test.ts
@@ -125,7 +125,7 @@ describe('dom/events: ESLEventUtils: ESLListenerDescriptor Utils', () => {
         [{event: ''}],
         [{onEvent() {}}],
         [new (class Test {onEvent() {}})()]
-      ])('host = %p', (host) => expect(ESLEventUtils.getAutoDescriptors(host)).toEqual([]));
+      ])('host = %p', (host: any) => expect(ESLEventUtils.getAutoDescriptors(host)).toEqual([]));
     });
 
     test('ESLEventUtils.getAutoDescriptors: catch prototype-level declared descriptor', () => {
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 52ece4513..53c9b2214 100644
--- a/src/modules/esl-mixin-element/test/mixin.listener.test.ts
+++ b/src/modules/esl-mixin-element/test/mixin.listener.test.ts
@@ -18,7 +18,7 @@ describe('ESLMixinElement: listeners', () => {
     beforeAll(() => document.body.appendChild(el));
 
     test('ESLMixinElement successfully auto subscribed', () => {
-      const host = TestElement.get(el);
+      const host = TestElement.get(el) as TestElement;
       expect(ESLEventUtils.listeners(host).length).toBe(1);
       expect(ESLEventUtils.listeners(host)[0].event).toBe('click');
     });