Repository: exadel-inc/esl
Pull request #1235 (2022-09-21 15:49:43)

feat(esl-base-element): add `create` shortcut to create current custo…
- add shortcut to create current custom element 

Closes: #1204 



Patch:
diff --git a/package-lock.json b/package-lock.json
index ce3d7b783..c4988c1c1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,10 +24,10 @@
         "@semantic-release/github": "^8.0.6",
         "@semantic-release/npm": "^9.0.1",
         "@semantic-release/release-notes-generator": "^10.0.3",
-        "@types/jest": "^29.0.3",
+        "@types/jest": "^29.0.1",
         "@types/smoothscroll-polyfill": "^0.3.1",
-        "@typescript-eslint/eslint-plugin": "^5.37.0",
-        "@typescript-eslint/parser": "^5.37.0",
+        "@typescript-eslint/eslint-plugin": "^5.36.2",
+        "@typescript-eslint/parser": "^5.36.2",
         "@webcomponents/custom-elements": "1.5.0",
         "browser-sync": "^2.27.10",
         "chokidar-cli": "^3.0.0",
@@ -38,7 +38,7 @@
         "eslint-plugin-editorconfig": "^4.0.2",
         "eslint-plugin-import": "^2.26.0",
         "eslint-plugin-sonarjs": "^0.15.0",
-        "eslint-plugin-tsdoc": "^0.2.17",
+        "eslint-plugin-tsdoc": "^0.2.16",
         "foreach-cli": "^1.8.1",
         "html-minifier": "^4.0.0",
         "husky": "^8.0.1",
@@ -58,7 +58,7 @@
         "semantic-release": "^19.0.5",
         "smoothscroll-polyfill": "^0.4.4",
         "stylelint": "^14.11.0",
-        "ts-jest": "^29.0.1",
+        "ts-jest": "^29.0.0",
         "ts-loader": "^9.3.1",
         "typescript": "4.8.3",
         "webpack": "^5.74.0",
@@ -1717,18 +1717,18 @@
       "dev": true
     },
     "node_modules/@microsoft/tsdoc": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz",
-      "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==",
+      "version": "0.14.1",
+      "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz",
+      "integrity": "sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw==",
       "dev": true
     },
     "node_modules/@microsoft/tsdoc-config": {
-      "version": "0.16.2",
-      "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz",
-      "integrity": "sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==",
+      "version": "0.16.1",
+      "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.1.tgz",
+      "integrity": "sha512-2RqkwiD4uN6MLnHFljqBlZIXlt/SaUT6cuogU1w2ARw4nKuuppSmR0+s+NC+7kXBQykd9zzu0P4HtBpZT5zBpQ==",
       "dev": true,
       "dependencies": {
-        "@microsoft/tsdoc": "0.14.2",
+        "@microsoft/tsdoc": "0.14.1",
         "ajv": "~6.12.6",
         "jju": "~1.4.0",
         "resolve": "~1.19.0"
@@ -2349,9 +2349,9 @@
       }
     },
     "node_modules/@types/jest": {
-      "version": "29.0.3",
-      "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.0.3.tgz",
-      "integrity": "sha512-F6ukyCTwbfsEX5F2YmVYmM5TcTHy1q9P5rWlRbrk56KyMh3v9xRGUO3aa8+SkvMi0SHXtASJv1283enXimC0Og==",
+      "version": "29.0.1",
+      "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.0.1.tgz",
+      "integrity": "sha512-CAZrjLRZs4xEdIrfrdV74xK1Vo/BKQZwUcjJv3gp6gMeV3BsVxMnXTcgtYOKyphT4DPPo7jxVEVhuwJTQn3oPQ==",
       "dev": true,
       "dependencies": {
         "expect": "^29.0.0",
@@ -2472,14 +2472,14 @@
       "integrity": "sha512-uwqm0DUeg+2pff/8y9b22JJb+qWKOcG5aCn2yyT7hmLdK/M8+VECcK6QuNqdAR93IAqTmZeqK2nizTlQg5j+XA=="
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.37.0.tgz",
-      "integrity": "sha512-Fde6W0IafXktz1UlnhGkrrmnnGpAo1kyX7dnyHHVrmwJOn72Oqm3eYtddrpOwwel2W8PAK9F3pIL5S+lfoM0og==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz",
+      "integrity": "sha512-OwwR8LRwSnI98tdc2z7mJYgY60gf7I9ZfGjN5EjCwwns9bdTuQfAXcsjSB2wSQ/TVNYSGKf4kzVXbNGaZvwiXw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.37.0",
-        "@typescript-eslint/type-utils": "5.37.0",
-        "@typescript-eslint/utils": "5.37.0",
+        "@typescript-eslint/scope-manager": "5.36.2",
+        "@typescript-eslint/type-utils": "5.36.2",
+        "@typescript-eslint/utils": "5.36.2",
         "debug": "^4.3.4",
         "functional-red-black-tree": "^1.0.1",
         "ignore": "^5.2.0",
@@ -2505,14 +2505,14 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.37.0.tgz",
-      "integrity": "sha512-01VzI/ipYKuaG5PkE5+qyJ6m02fVALmMPY3Qq5BHflDx3y4VobbLdHQkSMg9VPRS4KdNt4oYTMaomFoHonBGAw==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.36.2.tgz",
+      "integrity": "sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/scope-manager": "5.37.0",
-        "@typescript-eslint/types": "5.37.0",
-        "@typescript-eslint/typescript-estree": "5.37.0",
+        "@typescript-eslint/scope-manager": "5.36.2",
+        "@typescript-eslint/types": "5.36.2",
+        "@typescript-eslint/typescript-estree": "5.36.2",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -2532,13 +2532,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.37.0.tgz",
-      "integrity": "sha512-F67MqrmSXGd/eZnujjtkPgBQzgespu/iCZ+54Ok9X5tALb9L2v3G+QBSoWkXG0p3lcTJsL+iXz5eLUEdSiJU9Q==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz",
+      "integrity": "sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.37.0",
-        "@typescript-eslint/visitor-keys": "5.37.0"
+        "@typescript-eslint/types": "5.36.2",
+        "@typescript-eslint/visitor-keys": "5.36.2"
       },
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2549,13 +2549,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.37.0.tgz",
-      "integrity": "sha512-BSx/O0Z0SXOF5tY0bNTBcDEKz2Ec20GVYvq/H/XNKiUorUFilH7NPbFUuiiyzWaSdN3PA8JV0OvYx0gH/5aFAQ==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.36.2.tgz",
+      "integrity": "sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "5.37.0",
-        "@typescript-eslint/utils": "5.37.0",
+        "@typescript-eslint/typescript-estree": "5.36.2",
+        "@typescript-eslint/utils": "5.36.2",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
       },
@@ -2576,9 +2576,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.37.0.tgz",
-      "integrity": "sha512-3frIJiTa5+tCb2iqR/bf7XwU20lnU05r/sgPJnRpwvfZaqCJBrl8Q/mw9vr3NrNdB/XtVyMA0eppRMMBqdJ1bA==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.36.2.tgz",
+      "integrity": "sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2589,13 +2589,13 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.37.0.tgz",
-      "integrity": "sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz",
+      "integrity": "sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.37.0",
-        "@typescript-eslint/visitor-keys": "5.37.0",
+        "@typescript-eslint/types": "5.36.2",
+        "@typescript-eslint/visitor-keys": "5.36.2",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -2616,15 +2616,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.37.0.tgz",
-      "integrity": "sha512-jUEJoQrWbZhmikbcWSMDuUSxEE7ID2W/QCV/uz10WtQqfOuKZUqFGjqLJ+qhDd17rjgp+QJPqTdPIBWwoob2NQ==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.36.2.tgz",
+      "integrity": "sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg==",
       "dev": true,
       "dependencies": {
         "@types/json-schema": "^7.0.9",
-        "@typescript-eslint/scope-manager": "5.37.0",
-        "@typescript-eslint/types": "5.37.0",
-        "@typescript-eslint/typescript-estree": "5.37.0",
+        "@typescript-eslint/scope-manager": "5.36.2",
+        "@typescript-eslint/types": "5.36.2",
+        "@typescript-eslint/typescript-estree": "5.36.2",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
       },
@@ -2640,12 +2640,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.37.0.tgz",
-      "integrity": "sha512-Hp7rT4cENBPIzMwrlehLW/28EVCOcE9U1Z1BQTc8EA8v5qpr7GRGuG+U58V5tTY48zvUOA3KHvw3rA8tY9fbdA==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz",
+      "integrity": "sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/types": "5.37.0",
+        "@typescript-eslint/types": "5.36.2",
         "eslint-visitor-keys": "^3.3.0"
       },
       "engines": {
@@ -5782,13 +5782,13 @@
       }
     },
     "node_modules/eslint-plugin-tsdoc": {
-      "version": "0.2.17",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.2.17.tgz",
-      "integrity": "sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA==",
+      "version": "0.2.16",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.2.16.tgz",
+      "integrity": "sha512-F/RWMnyDQuGlg82vQEFHQtGyWi7++XJKdYNn0ulIbyMOFqYIjoJOUdE6olORxgwgLkpJxsCJpJbTHgxJ/ggfXw==",
       "dev": true,
       "dependencies": {
-        "@microsoft/tsdoc": "0.14.2",
-        "@microsoft/tsdoc-config": "0.16.2"
+        "@microsoft/tsdoc": "0.14.1",
+        "@microsoft/tsdoc-config": "0.16.1"
       }
     },
     "node_modules/eslint-scope": {
@@ -16409,9 +16409,9 @@
       }
     },
     "node_modules/ts-jest": {
-      "version": "29.0.1",
-      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.1.tgz",
-      "integrity": "sha512-htQOHshgvhn93QLxrmxpiQPk69+M1g7govO1g6kf6GsjCv4uvRV0znVmDrrvjUrVCnTYeY4FBxTYYYD4airyJA==",
+      "version": "29.0.0",
+      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.0.tgz",
+      "integrity": "sha512-OxUaigbv5Aon3OMLY9HBtwkGMs1upWE/URrmmVQFzzOcGlEPVuWzGmXUIkWGt/95Dj/T6MGuTrHHGL6kT6Yn8g==",
       "dev": true,
       "dependencies": {
         "bs-logger": "0.x",
@@ -18685,18 +18685,18 @@
       "dev": true
     },
     "@microsoft/tsdoc": {
-      "version": "0.14.2",
-      "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz",
-      "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==",
+      "version": "0.14.1",
+      "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz",
+      "integrity": "sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw==",
       "dev": true
     },
     "@microsoft/tsdoc-config": {
-      "version": "0.16.2",
-      "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz",
-      "integrity": "sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==",
+      "version": "0.16.1",
+      "resolved": "https://registry.npmjs.org/@microsoft/tsdoc-config/-/tsdoc-config-0.16.1.tgz",
+      "integrity": "sha512-2RqkwiD4uN6MLnHFljqBlZIXlt/SaUT6cuogU1w2ARw4nKuuppSmR0+s+NC+7kXBQykd9zzu0P4HtBpZT5zBpQ==",
       "dev": true,
       "requires": {
-        "@microsoft/tsdoc": "0.14.2",
+        "@microsoft/tsdoc": "0.14.1",
         "ajv": "~6.12.6",
         "jju": "~1.4.0",
         "resolve": "~1.19.0"
@@ -19230,9 +19230,9 @@
       }
     },
     "@types/jest": {
-      "version": "29.0.3",
-      "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.0.3.tgz",
-      "integrity": "sha512-F6ukyCTwbfsEX5F2YmVYmM5TcTHy1q9P5rWlRbrk56KyMh3v9xRGUO3aa8+SkvMi0SHXtASJv1283enXimC0Og==",
+      "version": "29.0.1",
+      "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.0.1.tgz",
+      "integrity": "sha512-CAZrjLRZs4xEdIrfrdV74xK1Vo/BKQZwUcjJv3gp6gMeV3BsVxMnXTcgtYOKyphT4DPPo7jxVEVhuwJTQn3oPQ==",
       "dev": true,
       "requires": {
         "expect": "^29.0.0",
@@ -19353,14 +19353,14 @@
       "integrity": "sha512-uwqm0DUeg+2pff/8y9b22JJb+qWKOcG5aCn2yyT7hmLdK/M8+VECcK6QuNqdAR93IAqTmZeqK2nizTlQg5j+XA=="
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.37.0.tgz",
-      "integrity": "sha512-Fde6W0IafXktz1UlnhGkrrmnnGpAo1kyX7dnyHHVrmwJOn72Oqm3eYtddrpOwwel2W8PAK9F3pIL5S+lfoM0og==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz",
+      "integrity": "sha512-OwwR8LRwSnI98tdc2z7mJYgY60gf7I9ZfGjN5EjCwwns9bdTuQfAXcsjSB2wSQ/TVNYSGKf4kzVXbNGaZvwiXw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/scope-manager": "5.37.0",
-        "@typescript-eslint/type-utils": "5.37.0",
-        "@typescript-eslint/utils": "5.37.0",
+        "@typescript-eslint/scope-manager": "5.36.2",
+        "@typescript-eslint/type-utils": "5.36.2",
+        "@typescript-eslint/utils": "5.36.2",
         "debug": "^4.3.4",
         "functional-red-black-tree": "^1.0.1",
         "ignore": "^5.2.0",
@@ -19370,53 +19370,53 @@
       }
     },
     "@typescript-eslint/parser": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.37.0.tgz",
-      "integrity": "sha512-01VzI/ipYKuaG5PkE5+qyJ6m02fVALmMPY3Qq5BHflDx3y4VobbLdHQkSMg9VPRS4KdNt4oYTMaomFoHonBGAw==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.36.2.tgz",
+      "integrity": "sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/scope-manager": "5.37.0",
-        "@typescript-eslint/types": "5.37.0",
-        "@typescript-eslint/typescript-estree": "5.37.0",
+        "@typescript-eslint/scope-manager": "5.36.2",
+        "@typescript-eslint/types": "5.36.2",
+        "@typescript-eslint/typescript-estree": "5.36.2",
         "debug": "^4.3.4"
       }
     },
     "@typescript-eslint/scope-manager": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.37.0.tgz",
-      "integrity": "sha512-F67MqrmSXGd/eZnujjtkPgBQzgespu/iCZ+54Ok9X5tALb9L2v3G+QBSoWkXG0p3lcTJsL+iXz5eLUEdSiJU9Q==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz",
+      "integrity": "sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "5.37.0",
-        "@typescript-eslint/visitor-keys": "5.37.0"
+        "@typescript-eslint/types": "5.36.2",
+        "@typescript-eslint/visitor-keys": "5.36.2"
       }
     },
     "@typescript-eslint/type-utils": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.37.0.tgz",
-      "integrity": "sha512-BSx/O0Z0SXOF5tY0bNTBcDEKz2Ec20GVYvq/H/XNKiUorUFilH7NPbFUuiiyzWaSdN3PA8JV0OvYx0gH/5aFAQ==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.36.2.tgz",
+      "integrity": "sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/typescript-estree": "5.37.0",
-        "@typescript-eslint/utils": "5.37.0",
+        "@typescript-eslint/typescript-estree": "5.36.2",
+        "@typescript-eslint/utils": "5.36.2",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
       }
     },
     "@typescript-eslint/types": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.37.0.tgz",
-      "integrity": "sha512-3frIJiTa5+tCb2iqR/bf7XwU20lnU05r/sgPJnRpwvfZaqCJBrl8Q/mw9vr3NrNdB/XtVyMA0eppRMMBqdJ1bA==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.36.2.tgz",
+      "integrity": "sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ==",
       "dev": true
     },
     "@typescript-eslint/typescript-estree": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.37.0.tgz",
-      "integrity": "sha512-JkFoFIt/cx59iqEDSgIGnQpCTRv96MQnXCYvJi7QhBC24uyuzbD8wVbajMB1b9x4I0octYFJ3OwjAwNqk1AjDA==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz",
+      "integrity": "sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "5.37.0",
-        "@typescript-eslint/visitor-keys": "5.37.0",
+        "@typescript-eslint/types": "5.36.2",
+        "@typescript-eslint/visitor-keys": "5.36.2",
         "debug": "^4.3.4",
         "globby": "^11.1.0",
         "is-glob": "^4.0.3",
@@ -19425,26 +19425,26 @@
       }
     },
     "@typescript-eslint/utils": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.37.0.tgz",
-      "integrity": "sha512-jUEJoQrWbZhmikbcWSMDuUSxEE7ID2W/QCV/uz10WtQqfOuKZUqFGjqLJ+qhDd17rjgp+QJPqTdPIBWwoob2NQ==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.36.2.tgz",
+      "integrity": "sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg==",
       "dev": true,
       "requires": {
         "@types/json-schema": "^7.0.9",
-        "@typescript-eslint/scope-manager": "5.37.0",
-        "@typescript-eslint/types": "5.37.0",
-        "@typescript-eslint/typescript-estree": "5.37.0",
+        "@typescript-eslint/scope-manager": "5.36.2",
+        "@typescript-eslint/types": "5.36.2",
+        "@typescript-eslint/typescript-estree": "5.36.2",
         "eslint-scope": "^5.1.1",
         "eslint-utils": "^3.0.0"
       }
     },
     "@typescript-eslint/visitor-keys": {
-      "version": "5.37.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.37.0.tgz",
-      "integrity": "sha512-Hp7rT4cENBPIzMwrlehLW/28EVCOcE9U1Z1BQTc8EA8v5qpr7GRGuG+U58V5tTY48zvUOA3KHvw3rA8tY9fbdA==",
+      "version": "5.36.2",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz",
+      "integrity": "sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/types": "5.37.0",
+        "@typescript-eslint/types": "5.36.2",
         "eslint-visitor-keys": "^3.3.0"
       }
     },
@@ -21935,13 +21935,13 @@
       "requires": {}
     },
     "eslint-plugin-tsdoc": {
-      "version": "0.2.17",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.2.17.tgz",
-      "integrity": "sha512-xRmVi7Zx44lOBuYqG8vzTXuL6IdGOeF9nHX17bjJ8+VE6fsxpdGem0/SBTmAwgYMKYB1WBkqRJVQ+n8GK041pA==",
+      "version": "0.2.16",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-tsdoc/-/eslint-plugin-tsdoc-0.2.16.tgz",
+      "integrity": "sha512-F/RWMnyDQuGlg82vQEFHQtGyWi7++XJKdYNn0ulIbyMOFqYIjoJOUdE6olORxgwgLkpJxsCJpJbTHgxJ/ggfXw==",
       "dev": true,
       "requires": {
-        "@microsoft/tsdoc": "0.14.2",
-        "@microsoft/tsdoc-config": "0.16.2"
+        "@microsoft/tsdoc": "0.14.1",
+        "@microsoft/tsdoc-config": "0.16.1"
       }
     },
     "eslint-scope": {
@@ -29850,9 +29850,9 @@
       "dev": true
     },
     "ts-jest": {
-      "version": "29.0.1",
-      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.1.tgz",
-      "integrity": "sha512-htQOHshgvhn93QLxrmxpiQPk69+M1g7govO1g6kf6GsjCv4uvRV0znVmDrrvjUrVCnTYeY4FBxTYYYD4airyJA==",
+      "version": "29.0.0",
+      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.0.tgz",
+      "integrity": "sha512-OxUaigbv5Aon3OMLY9HBtwkGMs1upWE/URrmmVQFzzOcGlEPVuWzGmXUIkWGt/95Dj/T6MGuTrHHGL6kT6Yn8g==",
       "dev": true,
       "requires": {
         "bs-logger": "0.x",
diff --git a/package.json b/package.json
index 07a238e1c..c67310f8c 100644
--- a/package.json
+++ b/package.json
@@ -92,10 +92,10 @@
     "@semantic-release/github": "^8.0.6",
     "@semantic-release/npm": "^9.0.1",
     "@semantic-release/release-notes-generator": "^10.0.3",
-    "@types/jest": "^29.0.3",
+    "@types/jest": "^29.0.1",
     "@types/smoothscroll-polyfill": "^0.3.1",
-    "@typescript-eslint/eslint-plugin": "^5.37.0",
-    "@typescript-eslint/parser": "^5.37.0",
+    "@typescript-eslint/eslint-plugin": "^5.36.2",
+    "@typescript-eslint/parser": "^5.36.2",
     "@webcomponents/custom-elements": "1.5.0",
     "browser-sync": "^2.27.10",
     "chokidar-cli": "^3.0.0",
@@ -106,7 +106,7 @@
     "eslint-plugin-editorconfig": "^4.0.2",
     "eslint-plugin-import": "^2.26.0",
     "eslint-plugin-sonarjs": "^0.15.0",
-    "eslint-plugin-tsdoc": "^0.2.17",
+    "eslint-plugin-tsdoc": "^0.2.16",
     "foreach-cli": "^1.8.1",
     "html-minifier": "^4.0.0",
     "husky": "^8.0.1",
@@ -126,7 +126,7 @@
     "semantic-release": "^19.0.5",
     "smoothscroll-polyfill": "^0.4.4",
     "stylelint": "^14.11.0",
-    "ts-jest": "^29.0.1",
+    "ts-jest": "^29.0.0",
     "ts-loader": "^9.3.1",
     "typescript": "4.8.3",
     "webpack": "^5.74.0",
@@ -158,10 +158,6 @@
     {
       "name": "abarmina",
       "email": "abarmina@exadel.com"
-    },
-    {
-      "name": "alesun",
-      "email": "alesun@exadel.com"
     }
   ],
   "maintainers": [
@@ -184,10 +180,6 @@
     {
       "name": "abarmina",
       "email": "abarmina@exadel.com"
-    },
-    {
-      "name": "alesun",
-      "email": "alesun@exadel.com"
     }
   ]
 }
diff --git a/pages/src/anchor/anchor-link.ts b/pages/src/anchor/anchor-link.ts
index 1d0bcf43c..f8a15c3e6 100644
--- a/pages/src/anchor/anchor-link.ts
+++ b/pages/src/anchor/anchor-link.ts
@@ -1,7 +1,7 @@
 import {ESLMixinElement} from '../../../src/modules/esl-mixin-element/ui/esl-mixin-element';
 import {listen, memoize} from '../../../src/modules/esl-utils/decorators';
 import {afterNextRender} from '../../../src/modules/esl-utils/async/raf';
-import {EventUtils} from '../../../src/modules/esl-utils/dom/events';
+import {EventUtils} from '../../../src/modules/esl-utils/dom/events/utils';
 
 /**
  * Demo sample of anchor mixin
diff --git a/pages/src/collection-grid/collection-grid.less b/pages/src/collection-grid/collection-grid.less
index 8278dc333..0f11a2eec 100644
--- a/pages/src/collection-grid/collection-grid.less
+++ b/pages/src/collection-grid/collection-grid.less
@@ -35,7 +35,7 @@
 
   &-title {
     height: 60px;
-    font-size: 1.2rem;
+    font-size: 1.1rem;
     font-weight: 600;
     color: black;
     padding: 5px 15px;
@@ -104,6 +104,10 @@
       }
       border-color: @primary-blue;
     }
+
+    &.draft:not(:hover) {
+      opacity: 60%;
+    }
   }
 
   &-column &-svg {
diff --git a/pages/src/common/badge.less b/pages/src/common/badge.less
index 1e988b1f9..4e8a6ac82 100644
--- a/pages/src/common/badge.less
+++ b/pages/src/common/badge.less
@@ -2,7 +2,6 @@
 
 .badge {
   display: inline-block;
-  text-transform: uppercase;
   padding: .25em .4em;
   font-size: 75%;
   font-weight: 700;
@@ -20,20 +19,13 @@
     display: none;
   }
 
-  &-sidebar {
-    position: absolute;
-    top: 50%;
-    left: 35px;
-    transform: translate(-50%, -50%);
-  }
-
   &-md {
     padding: .4em .8em;
     font-size: 80%;
   }
 
   &-warning {
-    color: #fff;
+    color: #212529;
     background-color: @warning;
   }
 
@@ -43,7 +35,7 @@
   }
 
   &-success {
-    color: #fff;
+    color: #212529;
     background-color: @success;
   }
 
@@ -55,16 +47,9 @@
 
   &-sup {
     display: inline-block;
-    font-size: 52%;
+    font-size: 50%;
     font-weight: 700;
     vertical-align: super;
-    letter-spacing: .5px;
-    text-shadow: 0 0 1px #2125293d;
-  }
-
-  &-img {
-    width: 20px;
-    height: 20px;
   }
 
   @media (prefers-reduced-motion: reduce) {
diff --git a/pages/src/common/colors.less b/pages/src/common/colors.less
index 9d3b2968a..dd0a0ab80 100644
--- a/pages/src/common/colors.less
+++ b/pages/src/common/colors.less
@@ -26,8 +26,8 @@
 @nav-dark-selected: #00a2ff;
 @nav-dark-accent: #3eb2ff;
 
-@success: #02b048;
-@warning: #ffad24;
+@success: #04c250;
+@warning: #ffc107;
 @danger: #dc3545;
 
 // Color Helpers
diff --git a/pages/src/navigation/sidebar/sidebar-seccondary-nav.less b/pages/src/navigation/sidebar/sidebar-seccondary-nav.less
index 0745e0081..adc54e09f 100644
--- a/pages/src/navigation/sidebar/sidebar-seccondary-nav.less
+++ b/pages/src/navigation/sidebar/sidebar-seccondary-nav.less
@@ -23,6 +23,21 @@ esl-d-sidebar {
       color: @nav-dark-selected;
       background: linear-gradient(to right, fade(@nav-dark-selected, 25%), fade(@nav-dark-selected, 0%));
     }
+
+    &.draft::before {
+      content: '';
+      position: absolute;
+      top: 50%;
+      left: 24px;
+      width: 1.15rem;
+      height: 1.15rem;
+      transform: translateY(-50%);
+
+      background-size: contain;
+      background-repeat: no-repeat;
+      background-position: center;
+      background-image: url('/assets/common/flask.svg');
+    }
   }
   .sidebar-nav-secondary-link {
     display: block;
diff --git a/pages/views/_includes/navigation/footer-compact.njk b/pages/views/_includes/navigation/footer-compact.njk
index f154e1c66..e041794ce 100644
--- a/pages/views/_includes/navigation/footer-compact.njk
+++ b/pages/views/_includes/navigation/footer-compact.njk
@@ -8,8 +8,7 @@
       <a target="_blank" href="https://github.com/julia-murashko">Julia Murashko</a>,
       <a target="_blank" href="https://github.com/yadamskaya">Yuliya Adamskaya</a>,
       <a target="_blank" href="https://github.com/dshovchko">Dmytro Shovchko</a>,
-      <a target="_blank" href="https://github.com/abarmina">Anna Barmina</a>,
-      <a target="_blank" href="https://github.com/NastaLeo">Anastasiya Lesun</a>
+      <a target="_blank" href="https://github.com/abarmina">Anna Barmina</a>
     </div>
     <div>
       Design by
diff --git a/pages/views/_includes/navigation/sidebar-item.njk b/pages/views/_includes/navigation/sidebar-item.njk
index 42b6aa1d5..de618b21e 100644
--- a/pages/views/_includes/navigation/sidebar-item.njk
+++ b/pages/views/_includes/navigation/sidebar-item.njk
@@ -31,22 +31,15 @@
         {% set isActive = page.url === item.url %}
         {% set isDraft = [].concat(item.data.tags).includes('draft') %}
         {% set isNew = [].concat(item.data.tags).includes('new') %}
-        {% set isBeta = [].concat(item.data.tags).includes('beta') %}
         <li class="sidebar-nav-secondary-item {{ 'active' if isActive }} {{ 'draft' if isDraft }}"
             {% if isActive %}aria-selected="true"{% endif %}>
           <a class="sidebar-nav-secondary-link"
              {% if isActive %}aria-current="page"{% endif %}
              href="{{ item.url | url }}">
+            {{ item.data.name }}
             {% if isNew %}
-              <sup class="badge badge-sup badge-success badge-sidebar">new</sup>
-            {% endif %}
-            {% if isBeta %}
-              <sup class="badge badge-sup badge-warning badge-sidebar">beta</sup>
+              <sup class="badge badge-sup badge-success">new</sup>
             {% endif %}
-            {% if isDraft %}
-              <sup class="badge badge-sup badge-danger badge-sidebar">draft</sup>
-            {% endif %}
-            {{ item.data.name }}
           </a>
         </li>
       {% endfor %}
diff --git a/pages/views/_includes/navigation/sidebar.njk b/pages/views/_includes/navigation/sidebar.njk
index 58592562e..32fc69bda 100644
--- a/pages/views/_includes/navigation/sidebar.njk
+++ b/pages/views/_includes/navigation/sidebar.njk
@@ -34,14 +34,9 @@
         {% set releasedBlogs = collections.blogs | released %}
         {% if releasedBlogs.length %}
         <li class="sidebar-nav-item">
-          {{ navitem ('Blogs', 'blogs', 'static/assets/sidebar/blogs.svg') }}
+          {{ navitem ('Blogs', 'blogs', 'static/assets/sidebar/blogs.svg', { new: true }) }}
         </li>
         {% endif %}
-        {% if env.isDev %}
-          <li class="sidebar-nav-item">
-            {{ navitem ('Drafts', 'draft', 'static/assets/common/flask.svg') }}
-          </li>
-        {% endif %}
       </ul>
 
       <esl-scrollbar class="sidebar-scrollbar" target="::prev"></esl-scrollbar>
diff --git a/pages/views/_layouts/collection-grid.njk b/pages/views/_layouts/collection-grid.njk
index 3cc9196ae..3154bbb19 100644
--- a/pages/views/_layouts/collection-grid.njk
+++ b/pages/views/_layouts/collection-grid.njk
@@ -4,7 +4,7 @@ layout: content
 <link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght@500&display=swap" rel="stylesheet">
 
 <ul class="collection-grid unstyled-list {{ 'collection-grid-column' if list }}">
-  {% for item in collections[collection] | notHidden | released | sortByNameAndOrder %}
+  {% for item in collections[collection] | notHidden | released | sortByName %}
     {% set isDraft = [].concat(item.data.tags).includes('draft') %}
     {% set isBeta = [].concat(item.data.tags).includes('beta') %}
     {% set isNew = [].concat(item.data.tags).includes('new') %}
diff --git a/pages/views/blogs/index.njk b/pages/views/blogs/index.njk
index 5409b9803..a4091422f 100644
--- a/pages/views/blogs/index.njk
+++ b/pages/views/blogs/index.njk
@@ -7,6 +7,6 @@ order: 1000
 collection: blogs
 hidden: true
 list: true
-tags: [blogs]
+tags: [blogs, new]
 collectionIcon: sidebar/blogs.svg
 ---
diff --git a/pages/views/components/esl-footnotes.njk b/pages/views/components/esl-footnotes.njk
index d2d3ab18e..c80a5e7ee 100644
--- a/pages/views/components/esl-footnotes.njk
+++ b/pages/views/components/esl-footnotes.njk
@@ -3,7 +3,7 @@ layout: content
 title: ESL Footnotes
 seoTitle: ESL Footnotes - custom elements for auto-collected footnotes in the page content
 name: ESL Footnotes
-tags: [components, beta]
+tags: [components, draft]
 aside:
   examples:
     - footnotes
diff --git a/pages/views/core/esl-event-listener.njk b/pages/views/core/esl-event-listener.njk
deleted file mode 100644
index 2ac6f9e8b..000000000
--- a/pages/views/core/esl-event-listener.njk
+++ /dev/null
@@ -1,10 +0,0 @@
----
-layout: content
-title: ESL Event Listeners
-seoTitle: Built-in ESL EventListeners
-name: ESL Event Listeners
-tags: [core, new]
-order: 2
----
-
-{% mdRender 'src/modules/esl-event-listener/README.md', 'intro' %}
diff --git a/pages/views/core/esl-utils.njk b/pages/views/core/esl-utils.njk
index b36d57a80..1772b9483 100644
--- a/pages/views/core/esl-utils.njk
+++ b/pages/views/core/esl-utils.njk
@@ -4,7 +4,7 @@ title: ESL Utils
 seoTitle: Universal JS, TS and Web API utilities set from Exadel Smart Library
 name: ESL Utils
 tags: core
-order: 3
+
 ---
 
 {% mdRender 'src/modules/esl-utils/README.md', 'intro' %}
diff --git a/pages/views/draft/index.njk b/pages/views/draft/index.njk
deleted file mode 100644
index 7b12cdf36..000000000
--- a/pages/views/draft/index.njk
+++ /dev/null
@@ -1,9 +0,0 @@
----
-layout: collection-grid
-title: Drafts
-name: All
-collection: draft
-hidden: true
-list: true
-collectionIcon: common/flask.svg
----
diff --git a/pages/views/draft/accordion-regression.njk b/pages/views/examples/accordion-regression.njk
similarity index 99%
rename from pages/views/draft/accordion-regression.njk
rename to pages/views/examples/accordion-regression.njk
index 1d0eb73ef..ca51d4b6f 100644
--- a/pages/views/draft/accordion-regression.njk
+++ b/pages/views/examples/accordion-regression.njk
@@ -2,7 +2,7 @@
 layout: content
 title: Accordion (Regression)
 name: Accordion (Regression)
-tags: [draft]
+tags: [examples, draft]
 icon: examples/accordion.svg
 ---
 {% import 'lorem.njk' as lorem %}
diff --git a/pages/views/draft/carousel.njk b/pages/views/examples/carousel.njk
similarity index 99%
rename from pages/views/draft/carousel.njk
rename to pages/views/examples/carousel.njk
index 5bbadaf4b..e087b2e7b 100644
--- a/pages/views/draft/carousel.njk
+++ b/pages/views/examples/carousel.njk
@@ -3,7 +3,7 @@ layout: content
 title: Carousel
 seoTitle: Carousel UI examples based on ESL web components
 name: Carousel
-tags: [draft]
+tags: [examples, draft]
 icon: examples/carousel.svg
 ---
 
diff --git a/pages/views/draft/nested-accordion.njk b/pages/views/examples/nested-accordion.njk
similarity index 99%
rename from pages/views/draft/nested-accordion.njk
rename to pages/views/examples/nested-accordion.njk
index ea54f2439..7ca0d99ad 100644
--- a/pages/views/draft/nested-accordion.njk
+++ b/pages/views/examples/nested-accordion.njk
@@ -2,7 +2,7 @@
 layout: empty
 title: Nested Accordion & Anchors
 name: Nested Accordion & Anchors
-tags: [draft]
+tags: [examples, draft]
 icon: examples/accordion.svg
 scrollbar: true
 aside:
diff --git a/pages/views/draft/popup-scroll-parents.njk b/pages/views/examples/popup-scroll-parents.njk
similarity index 99%
rename from pages/views/draft/popup-scroll-parents.njk
rename to pages/views/examples/popup-scroll-parents.njk
index 84eeead9c..dc7bebf31 100644
--- a/pages/views/draft/popup-scroll-parents.njk
+++ b/pages/views/examples/popup-scroll-parents.njk
@@ -3,7 +3,7 @@ layout: content
 title: Popup scroll parents example
 seoTitle: Popup UI examples based on ESL web components
 name: Popup scroll parents
-tags: [draft]
+tags: [examples, draft]
 icon: examples/popup.svg
 aside:
   components:
@@ -94,4 +94,4 @@ aside:
   </div>
 </section>
 
-{% include '../examples/popup.njk' %}
+{% include 'popup.njk' %}
diff --git a/pages/views/draft/popup-transition.njk b/pages/views/examples/popup-transition.njk
similarity index 99%
rename from pages/views/draft/popup-transition.njk
rename to pages/views/examples/popup-transition.njk
index 661f92859..8482cb0d3 100644
--- a/pages/views/draft/popup-transition.njk
+++ b/pages/views/examples/popup-transition.njk
@@ -3,7 +3,7 @@ layout: content
 title: Popup transition example
 seoTitle: Popup UI examples based on ESL web components
 name: Popup transition
-tags: [draft]
+tags: [examples, draft]
 icon: examples/popup.svg
 aside:
   components:
@@ -173,4 +173,4 @@ aside:
   </div>
 </section>
 
-{% include '../examples/popup.njk' %}
+{% include 'popup.njk' %}
diff --git a/src/modules/esl-base-element/core/esl-base-element.ts b/src/modules/esl-base-element/core/esl-base-element.ts
index 00b696ace..090cde465 100644
--- a/src/modules/esl-base-element/core/esl-base-element.ts
+++ b/src/modules/esl-base-element/core/esl-base-element.ts
@@ -114,4 +114,10 @@ export abstract class ESLBaseElement extends HTMLElement {
   public static get registered(): Promise<CustomElementConstructor> {
     return customElements.whenDefined(this.is);
   }
+
+  /** Creates an instance of the current custom element */
+  public static create<T extends typeof ESLBaseElement>(this: T): InstanceType<T> {
+    return document.createElement(this.is) as InstanceType<T>;
+  }
+
 }
diff --git a/src/modules/esl-event-listener/README.md b/src/modules/esl-event-listener/README.md
deleted file mode 100644
index 33f2940a2..000000000
--- a/src/modules/esl-event-listener/README.md
+++ /dev/null
@@ -1,178 +0,0 @@
-[//]: # (TODO)
-
-# ESL Event Listener module
-
-<a name="intro"></a>
-
-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.
-
-## EventUtils class
-
-`EventUtils` is a root class for the event listener module, it contains ESL event listeners module API.
-One of the main advantages of `EventUtils` 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.
-
-For example 
-- `EventUtils.unsubscribe($host);` will unsubscribe everything bound to $host
-- `EventUtils.unsubscribe($host, handlerFn);` will unsubscribe everything that is bound to $host and is handled by handlerFn
-- `EventUtils.unsubscribe($host, 'click');` will unsubscribe everything bound to $host and process 'click'
-- `EventUtils.unsubscribe($host, 'click', handlerFn);` will unsubscribe everything that is bound to $host, processes 'click' event and handles handlerFn
-- There can be any number of criteria.
-
-
-Here the `EventUtils` API:
-
-### ⚡ `EventUtils.subscribe`
-The main method to create ESLEventListener and subscribe to DOM events.
-
-```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[];
-```
-
-**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).
-
-Examples:
-- `EventUtils.subscribe($host, handlerFn);` - 
-subscribes decorated `handlerFn` method to the `target`;
-- `EventUtils.subscribe($host, 'click', handlerFn);` - 
-subscribes `handlerFn` function with the passed event type;
-- `EventUtils.subscribe($host, {event: 'scroll', target: window}, handlerFn);` - 
-subscribes `handlerFn` function with the passed additional descriptor data.
-
-
-### ⚡ `EventUtils.unsubscribe`
-Method of the `EventUtils` interface that allows subscribing elements to DOM events.
-
-```typescript
-unsubscribe(
-  host: HTMLElement,
-  ...criteria: ESLListenerCriteria[]
-): ESLEventListener[];
-```
-
-**Parameters**:
-- `host` - An element to unsubsribe;
-- `criteria` - An optional set of criteria to filter listeners to remove.
-
-
-### ⚡ `EventUtils.descriptors`
-Method of the `EventUtils` interface that gathers descriptors from the passed object. See [ESLListenerDescriptorFn](#listenerDescFn).
-
-```typescript
-EventUtils.descriptors(
-  target?: any, 
-  auto: boolean = true
-): ESLListenerDescriptorFn[]
-```
-
-**Parameters**:
-- `target` - An object to get descriptors from;
-- `auto` - A boolean value indicating that the listener should be automatically subscribed within connected callback.
-
-
-### ⚡ `EventUtils.listeners`
-Method of the `EventUtils` interface that gathers listeners currently subscribed to the target.
-
-```typescript
-EventUtils.listeners(
-  target: HTMLElement,
-  ...criteria: ESLListenerCriteria[]
-): ESLEventListener[];
-```
-
-**Parameters**:
-- `host` - An element which listeners to get;
-- `criteria` - An optional set of criteria to filter listeners list.
-
-### ⚡ `EventUtils.dispatch`
-
-Method of the `EventUtils` interface that is used to dispatch custom events.
-The event that is being dispatched is bubbling and cancelable by default.
-
-```typescript
-EventUtils.dispatch(
-  el: EventTarget,
-  eventName: string,
-  eventInit?: CustomEventInit
-): boolean;
-```
-
-**Parameters**:
-- `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.
-
-```typescript
-this.$$on('click', this.onClick);
-this.$$off('click'); // или this.$$off(this.onClick)
-```
-
-### Auto-unbind
-All the inheritors of `ESLBaseElement` and `ESLMixinElement` unsubscribe from events attached via ESL automatically 
-on `disconnectedCallback`.
-
-### Decorator `@listen`
-The `@listen` decorator is now available in ESL, which makes it even easier to work with events
-
-The following short snippet of code describes a listener that will automatically subscribe and unsubscribe 
-on connected/disconnected callback inside `ESLBaseElement` or `ESLMixinElement`:
- ```typescript
-class MyEl extends ESLBaseElement {
-  @listen('click')
-  onClick(e) { 
-    //... 
-  }
-}
- ```
-
-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 {
-  @listen({event: 'click', auto: false}) // Won`t be subscribed automatically
-  onClick(e) { 
-    // ...
-  }
-
-  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
-
-    EventUtils.subscribe($host, this.onClick); // Low-level API support
-  }
-}
- ```
diff --git a/src/modules/esl-event-listener/core.ts b/src/modules/esl-event-listener/core.ts
deleted file mode 100644
index 6b9a07edb..000000000
--- a/src/modules/esl-event-listener/core.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-export * from './core/api';
-export type {
-  ESLListenerHandler,
-  ESLListenerCriteria,
-  ESLListenerEventMap,
-  ESLListenerDescriptor,
-  ESLListenerDescriptorFn,
-  ESLListenerTarget,
-  ESLListenerDefinition
-} from './core/descriptor';
-export type {
-  ESLEventListener,
-} from './core/listener';
diff --git a/src/modules/esl-event-listener/core/descriptor.ts b/src/modules/esl-event-listener/core/descriptor.ts
deleted file mode 100644
index d7e514f73..000000000
--- a/src/modules/esl-event-listener/core/descriptor.ts
+++ /dev/null
@@ -1,63 +0,0 @@
-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 */
-export type ESLListenerTarget = EventTarget | EventTarget[] | string | null;
-
-/** Extended event map with the custom event definition */
-export interface ESLListenerEventMap extends HTMLElementEventMap {
-  /** User custom event or group of events */
-  [e: string]: Event;
-}
-
-/** Descriptor to create {@link ESLEventListener} */
-export type ESLListenerDescriptor<EType extends keyof ESLListenerEventMap = string> = {
-  /** A case-sensitive string (or provider function) representing the event type to listen for */
-  event: EType | PropertyProvider<EType>;
-  /**
-   * A boolean value indicating that events for this listener will be dispatched on the capture phase.
-   * @see AddEventListenerOptions.capture
-   */
-  capture?: boolean;
-  /**
-   * A boolean value that indicates that the function specified by listener will never call preventDefault()
-   * @see AddEventListenerOptions.passive
-   */
-  passive?: boolean;
-
-  /** A string (or provider function) representing CSS selector to check delegated event target (undefined (disabled) by default) */
-  selector?: string | PropertyProvider<string>;
-  /**
-   * An ESLEventTarget (or provider function) to subscribe the event listener to
-   * **Note**: string values are processed by the {@link TraversingQuery} syntax
-   * (e.g. `button` selects all buttons globally, while `::find(button)` selects only buttons inside current element)
-   */
-  target?: ESLListenerTarget | PropertyProvider<ESLListenerTarget>;
-
-  /**
-   * A reference to the component (mixin) that holds the event listener descriptor
-   * Used as a call context for the event listener handler if defined
-   */
-  context?: unknown;
-
-  /** A boolean value indicating that the listener should be automatically subscribed within connected callback */
-  auto?: boolean;
-  /** A boolean value indicating that the listener should be invoked at most once after being added */
-  once?: boolean;
-};
-
-/** Resolved descriptor (definition) to create {@link ESLEventListener} */
-export interface ESLListenerDefinition<EType extends keyof ESLListenerEventMap = string> extends ESLListenerDescriptor<EType> {
-  /** A case-sensitive string (or provider function) representing the event type to listen for */
-  event: EType;
-}
-
-/** Describes callback handler */
-export type ESLListenerHandler<EType extends Event = Event> = (event: EType, listener: ESLEventListener) => void;
-
-/** Condition (criteria) to find {@link ESLListenerDescriptor} */
-export type ESLListenerCriteria = undefined | keyof ESLListenerEventMap | ESLListenerHandler | Partial<ESLListenerDefinition>;
-
-/** Function decorated as {@link ESLListenerDescriptor} */
-export type ESLListenerDescriptorFn<EType extends keyof ESLListenerEventMap = string> =
-  ESLListenerHandler<ESLListenerEventMap[EType]> & ESLListenerDescriptor<EType>;
diff --git a/src/modules/esl-event-listener/core/listener.ts b/src/modules/esl-event-listener/core/listener.ts
deleted file mode 100644
index 8d619f996..000000000
--- a/src/modules/esl-event-listener/core/listener.ts
+++ /dev/null
@@ -1,170 +0,0 @@
-import {wrap} from '../../esl-utils/misc/array';
-import {resolveProperty} from '../../esl-utils/misc/functions';
-import {memoize} from '../../esl-utils/decorators/memoize';
-import {isSimilar} from '../../esl-utils/misc/object/compare';
-import {TraversingQuery} from '../../esl-traversing-query/core';
-import {isPassiveByDefault} from '../../esl-utils/dom/events/misc';
-
-import type {PropertyProvider} from '../../esl-utils/misc/functions';
-import type {
-  ESLListenerTarget,
-  ESLListenerDefinition,
-  ESLListenerDescriptor,
-  ESLListenerHandler,
-  ESLListenerCriteria
-} from './descriptor';
-
-/** Key to store listeners on the host */
-const STORE = '__listeners';
-
-/**
- * Splits and deduplicates event string
- * @returns array of unique events presented in events string
- */
-export const splitEvents = (events: string): string[] => {
-  const terms = (events || '').split(' ').map((term) => term.trim());
-  const deduplicate = new Set<string>();
-  return terms.filter((term) => {
-    if (!term || deduplicate.has(term)) return false;
-    deduplicate.add(term);
-    return true;
-  });
-};
-
-/**
- * `EventListener` instance, used as an 'inner' record to process subscriptions made by `EventUtils`
- * Uses `EventListenerObject` interface to subscribe on event.
- *
- * Use Chrome console `getEventListeners` method to check subscribers details when debugging ESLEventListener subscriptions.
- * */
-export class ESLEventListener implements ESLListenerDefinition, EventListenerObject {
-  public readonly target?: ESLListenerTarget | PropertyProvider<ESLListenerTarget>;
-  public readonly selector?: string;
-  public readonly capture?: boolean;
-
-  public readonly once?: boolean;
-  public readonly auto?: boolean;
-  public readonly passive?: boolean;
-  public readonly context?: unknown;
-
-  protected constructor(
-    public readonly $host: HTMLElement,
-    public readonly event: string,
-    public readonly handler: ESLListenerHandler,
-    desc: ESLListenerDescriptor
-  ) {
-    const defaults = {
-      capture: false,
-      passive: isPassiveByDefault(event)
-    };
-    Object.assign(this, defaults, desc, {event});
-  }
-
-  /** @returns target element to listen */
-  @memoize()
-  public get $targets(): EventTarget[] {
-    const target = resolveProperty(this.target, this.context || this.$host);
-    if (typeof target === 'undefined') return [this.$host];
-    if (typeof target === 'string') return TraversingQuery.all(target, this.$host);
-    if (typeof target === 'object' && target) return wrap(target);
-    return [];
-  }
-
-  /** @returns resolved selector to check event target */
-  public get delegate(): string | undefined {
-    return resolveProperty(this.selector, this.context || this.$host);
-  }
-
-  /**
-   * Checks if the passed criteria matches current event listener
-   * - `string` type criteria checked by the `event` name
-   * - `function` type criteria checked by the `handler` reference
-   * - `object` type criteria checked as descriptor using `isSimilar` comparer
-   * Note: `function` (handler) marcher has a priority over descriptor check, so handler function own properties will be ignored
-   */
-  public matches(desc?: ESLListenerCriteria): boolean {
-    if (typeof desc === 'string') return this.event === desc;
-    if (typeof desc === 'function') return this.handler === desc;
-    if (typeof desc === 'object') return isSimilar(this, desc, false);
-    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.context ?? this.$host, e, this);
-    if (this.once) this.unsubscribe();
-  }
-
-  /** Checks if the passed event can be handled by the current event listener */
-  protected isDelegatedTarget(e: Event): boolean {
-    const target = e.target;
-    const current = e.currentTarget;
-    const delegate = this.delegate;
-    if (typeof delegate !== 'string') return true;
-    if (!(target instanceof HTMLElement) || !(current instanceof HTMLElement)) return false;
-    return current.contains(target.closest(delegate));
-  }
-
-  /**
-   * (Re-)Subscribes event listener instance
-   * @returns if subscription was successful
-   */
-  public subscribe(): boolean {
-    const {passive, capture} = this;
-    this.unsubscribe();
-    memoize.clear(this, '$targets');
-    if (!this.$targets.length) return false;
-    this.$targets.forEach((el: EventTarget) => el.addEventListener(this.event, this, {passive, capture}));
-    ESLEventListener.add(this.$host, this);
-    return true;
-  }
-
-  /** Unsubscribes event listener instance */
-  public unsubscribe(): void {
-    const {capture} = this;
-    if (!memoize.has(this, '$targets')) return;
-    this.$targets.forEach((el: EventTarget) => el.removeEventListener(this.event, this, {capture}));
-    ESLEventListener.remove(this.$host, this);
-  }
-
-  /**
-   * Gets stored listeners array the passed `host` object
-   * Supports additional filtration criteria
-   */
-  public static get(host?: any, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
-    if (!host) return [];
-    const listeners = (host[STORE] || []) 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;
-    if (!Object.hasOwnProperty.call(host, STORE)) host[STORE] = [];
-    host[STORE].push(instance);
-  }
-  /** Removes listener from the listener store of the host object */
-  protected static remove(host: any, instance: ESLEventListener): void {
-    const listeners = ESLEventListener.get(host);
-    const value = listeners.filter((listener) => listener !== instance);
-    Object.defineProperty(host, STORE, {value, configurable: true});
-  }
-
-  /** Creates or resolve existing event listeners by handler and descriptors */
-  public static createOrResolve(host: HTMLElement, handler: ESLListenerHandler, desc: ESLListenerDescriptor): ESLEventListener[] {
-    const eventString = resolveProperty(desc.event, desc.context || host);
-
-    const listeners: ESLEventListener[] = [];
-    for (const event of splitEvents(eventString)) {
-      const subscribed = ESLEventListener.get(host, event, handler, {
-        target: desc.target,
-        selector: desc.selector,
-        capture: !!desc.capture
-      });
-      if (subscribed.length) listeners.push(...subscribed);
-      else listeners.push(new ESLEventListener(host, event, handler, desc));
-    }
-    return listeners;
-  }
-}
diff --git a/src/modules/esl-panel-group/core/esl-panel-group.ts b/src/modules/esl-panel-group/core/esl-panel-group.ts
index 317ae3b03..06700af7f 100644
--- a/src/modules/esl-panel-group/core/esl-panel-group.ts
+++ b/src/modules/esl-panel-group/core/esl-panel-group.ts
@@ -74,10 +74,8 @@ export class ESLPanelGroup extends ESLBaseElement {
   protected _previousHeight: number = 0;
 
   protected connectedCallback(): void {
-    ESLPanel.registered.then(() => {
-      super.connectedCallback();
-      this.refresh();
-    });
+    super.connectedCallback();
+    this.refresh();
   }
 
   protected attributeChangedCallback(attrName: string, oldVal: string, newVal: string): void {
@@ -102,7 +100,7 @@ export class ESLPanelGroup extends ESLBaseElement {
     this.setAttribute('current-mode', currentMode);
 
     this.updateModeCls();
-    this.reset();
+    ESLPanel.registered.then(() => this.reset());
 
     if (prevMode !== currentMode) this.$$fire(this.MODE_CHANGE_EVENT, {detail: {prevMode, currentMode}});
   }
diff --git a/src/modules/esl-utils/dom/events.ts b/src/modules/esl-utils/dom/events.ts
index 9775676aa..928d88997 100644
--- a/src/modules/esl-utils/dom/events.ts
+++ b/src/modules/esl-utils/dom/events.ts
@@ -1,4 +1,4 @@
 export * from './events/target';
 export * from './events/misc';
-
-export * from '../../esl-event-listener/core';
+export * from './events/listener';
+export * from './events/utils';
diff --git a/src/modules/esl-utils/dom/events/listener.ts b/src/modules/esl-utils/dom/events/listener.ts
new file mode 100644
index 000000000..236ed56d7
--- /dev/null
+++ b/src/modules/esl-utils/dom/events/listener.ts
@@ -0,0 +1,185 @@
+import {wrap} from '../../misc/array';
+import {sequentialUID} from '../../misc/uid';
+import {resolveProperty} from '../../misc/functions';
+import {memoize} from '../../decorators/memoize';
+import {isSimilar} from '../../misc/object/compare';
+import {TraversingQuery} from '../../../esl-traversing-query/core';
+import {isPassiveByDefault} from './misc';
+import type {PropertyProvider} from '../../misc/functions';
+
+/** Describes callback handler */
+export type ESLListenerHandler<EType extends Event = Event> = (event: EType, listener?: ESLEventListener) => void;
+
+/** Extended event map with the custom event definition */
+export interface ESLListenerEventMap extends HTMLElementEventMap {
+  /** User custom event or group of events */
+  [e: string]: Event;
+}
+
+/** Descriptor to create {@link ESLEventListener} */
+export type ESLListenerDescriptor<EType extends keyof ESLListenerEventMap = string> = {
+  /** A case-sensitive string (or provider function) representing the event type to listen for */
+  event: EType | PropertyProvider<EType>;
+  /**
+   * A boolean value indicating that events for this listener will be dispatched on the capture phase.
+   * @see AddEventListenerOptions.capture
+   */
+  capture?: boolean;
+  /**
+   * A boolean value that indicates that the function specified by listener will never call preventDefault()
+   * @see AddEventListenerOptions.passive
+   */
+  passive?: boolean;
+
+  /** A string (or provider function) representing CSS selector to check delegated event target (undefined (disabled) by default) */
+  selector?: string | PropertyProvider<string>;
+  /**
+   * A string (or provider function) selector to find the target or {@link EventTarget} object to subscribe the event listener to
+   * **Note**: string values are processed by the {@link TraversingQuery} syntax
+   * (e.g. `button` selects all buttons globally, while `::find(button)` selects only buttons inside current element)
+   */
+  target?: EventTarget | EventTarget[] | string | null | PropertyProvider<EventTarget | EventTarget[] | string | null>;
+
+  /** Identifier of the event listener. Can be used to group and unsubscribe listeners */
+  id?: string;
+  /**
+   * A reference to the component (mixin) that holds the event listener descriptor
+   * Used as a call context for the event listener handler if defined
+   */
+  context?: unknown;
+
+  /** A boolean value indicating that the listener should be automatically subscribed within connected callback */
+  auto?: boolean;
+  /** A boolean value indicating that the listener should be invoked at most once after being added */
+  once?: boolean;
+};
+
+/** Condition (criteria) to find {@link ESLListenerDescriptor} */
+export type ESLListenerCriteria = undefined | keyof ESLListenerEventMap | ESLListenerHandler | Partial<ESLEventListener>;
+
+const STORE = '__listeners';
+
+/**
+ * `EventListener` instance, used as an 'inner' record to process subscriptions made by `EventUtils`
+ * Uses `EventListenerObject` interface to subscribe on event.
+ *
+ * Use Chrome console `getEventListeners` method to check subscribers details when debugging ESLEventListener subscriptions.
+ * */
+export class ESLEventListener implements ESLListenerDescriptor, EventListenerObject {
+  public readonly id: string;
+  public readonly event: string;
+  public readonly once?: boolean;
+  public readonly auto?: boolean;
+  public readonly target?: string | EventTarget | EventTarget[];
+  public readonly capture?: boolean;
+  public readonly passive?: boolean;
+  public readonly selector?: string;
+  public readonly context?: unknown;
+
+  constructor(
+    public readonly $host: HTMLElement,
+    public readonly handler: ESLListenerHandler,
+    desc: ESLListenerDescriptor
+  ) {
+    desc = Object.assign({}, desc);
+    desc.id = desc.id || sequentialUID('esl.event');
+    desc.target = resolveProperty(desc.target, desc.context || $host);
+    desc.selector = resolveProperty(desc.selector, desc.context || $host);
+    Object.assign(this, {
+      capture: false,
+      passive: isPassiveByDefault(this.event)
+    }, desc);
+  }
+
+  /** @returns target element to listen */
+  @memoize()
+  public get $targets(): EventTarget[] {
+    if (typeof this.target === 'string') return TraversingQuery.all(this.target, this.$host);
+    if (typeof this.target === 'undefined') return [this.$host];
+    if (typeof this.target === 'object' && this.target) return wrap(this.target);
+    return [];
+  }
+
+  /**
+   * Checks if the passed criteria matches current event listener
+   * - `string` type criteria checked by the `event` name
+   * - `function` type criteria checked by the `handler` reference
+   * - `object` type criteria checked as descriptor using `isSimilar` comparer
+   * Note: `function` (handler) marcher has a priority over descriptor check, so handler function own properties will be ignored
+   */
+  public matches(desc?: ESLListenerCriteria): boolean {
+    if (typeof desc === 'string') return this.event === desc;
+    if (typeof desc === 'function') return this.handler === desc;
+    if (typeof desc === 'object') return isSimilar(this, desc, false);
+    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.context ?? this.$host, e, this);
+    if (this.once) this.unsubscribe();
+  }
+
+  /** Checks if the passed event can be handled by the curren event listener */
+  protected isDelegatedTarget(e: Event): boolean {
+    if (typeof this.selector !== 'string') return true;
+    const target = e.target;
+    const current = e.currentTarget;
+    if (!(target instanceof HTMLElement) || !(current instanceof HTMLElement)) return false;
+    return current.contains(target.closest(this.selector));
+  }
+
+  /**
+   * (Re-)Subscribes event listener instance
+   * @returns if subscription was successful
+   */
+  public subscribe(): boolean {
+    const {passive, capture} = this;
+    this.unsubscribe();
+    memoize.clear(this, '$targets');
+    if (!this.$targets.length) return false;
+    this.$targets.forEach((el: EventTarget) => el.addEventListener(this.event, this, {passive, capture}));
+    ESLEventListener.get(this.$host).push(this);
+    return true;
+  }
+
+  /** Unsubscribes event listener instance */
+  public unsubscribe(): void {
+    const {capture} = this;
+    this.$targets.forEach((el: EventTarget) => el.removeEventListener(this.event, this, {capture}));
+    const listeners = ESLEventListener.get(this.$host);
+    const value = listeners.filter((listener) => listener !== this);
+    Object.defineProperty(this.$host, STORE, {value, configurable: true});
+  }
+
+  /** Gets stored listeners array the passed `host` object */
+  public static get(host?: any): ESLEventListener[] {
+    if (!host) return [];
+    if (!Object.hasOwnProperty.call(host, STORE)) host[STORE] = [];
+    return host[STORE];
+  }
+
+  /** Creates event listeners by handler and descriptors */
+  public static create(target: HTMLElement, handler: ESLListenerHandler, desc: ESLListenerDescriptor): ESLEventListener[] {
+    const events = resolveProperty(desc.event, desc.context || target);
+    if (!events.length) {
+      console.warn('[ESL]: Can\'t create ESLEventListener with empty event type: %o %o', handler, desc);
+    }
+    return ESLEventListener.splitEventQ(events).map((event) => {
+      const spec: ESLListenerDescriptor = Object.assign({}, desc, {event});
+      return new ESLEventListener(target, handler, spec);
+    });
+  }
+
+  /** Splits and deduplicate event query */
+  protected static splitEventQ(query: string): string[] {
+    const terms = (query || '').split(' ').map((term) => term.trim());
+    const deduplicate = new Set<string>();
+    return terms.filter((term) => {
+      if (!term || deduplicate.has(term)) return false;
+      deduplicate.add(term);
+      return true;
+    });
+  }
+}
diff --git a/src/modules/esl-utils/dom/events/misc.ts b/src/modules/esl-utils/dom/events/misc.ts
index 84aa96d75..218d397c9 100644
--- a/src/modules/esl-utils/dom/events/misc.ts
+++ b/src/modules/esl-utils/dom/events/misc.ts
@@ -42,19 +42,3 @@ export const getOffsetPoint = (el: Element): Point => {
   const x = props.left + window.scrollX;
   return {x, y};
 };
-
-/**
- * Dispatches custom event.
- * Event bubbles and is cancelable by default, use `eventInit` to override that.
- * @param el - EventTarget to dispatch event
- * @param eventName - name of the event to dispatch
- * @param eventInit - object that specifies characteristics of the event. See {@link CustomEventInit}
- */
-export const dispatchCustomEvent = (el: EventTarget, eventName: string, eventInit?: CustomEventInit): boolean => {
-  const init = Object.assign({
-    bubbles: true,
-    composed: true,
-    cancelable: true
-  }, eventInit || {});
-  return el.dispatchEvent(new CustomEvent(eventName, init));
-};
diff --git a/src/modules/esl-event-listener/core/api.ts b/src/modules/esl-utils/dom/events/utils.ts
similarity index 57%
rename from src/modules/esl-event-listener/core/api.ts
rename to src/modules/esl-utils/dom/events/utils.ts
index 2837101f6..93ac25321 100644
--- a/src/modules/esl-event-listener/core/api.ts
+++ b/src/modules/esl-utils/dom/events/utils.ts
@@ -1,14 +1,16 @@
-import {ExportNs} from '../../esl-utils/environment/export-ns';
-import {dispatchCustomEvent} from '../../esl-utils/dom/events/misc';
+import {ExportNs} from '../../environment/export-ns';
 import {ESLEventListener} from './listener';
 
 import type {
   ESLListenerHandler,
   ESLListenerCriteria,
-  ESLListenerDescriptor,
   ESLListenerEventMap,
-  ESLListenerDescriptorFn
-} from './descriptor';
+  ESLListenerDescriptor
+} from './listener';
+
+/** Function decorated as {@link ESLListenerDescriptor} */
+export type ESLListenerDescriptorFn<EType extends keyof ESLListenerEventMap = string> =
+  ESLListenerHandler<ESLListenerEventMap[EType]> & ESLListenerDescriptor<EType>;
 
 /** Type guard to check if the passed function is typeof {@link ESLListenerDescriptorFn} */
 export const isDescriptorFn = (obj: any): obj is ESLListenerDescriptorFn =>
@@ -17,11 +19,19 @@ export const isDescriptorFn = (obj: any): obj is ESLListenerDescriptorFn =>
 @ExportNs('EventUtils')
 export class EventUtils {
   /**
-   * Dispatches custom event. Alias for {@link dispatchCustomEvent}
-   * @see dispatchCustomEvent
+   * Dispatches custom event.
+   * Event bubbles and is cancelable by default, use `eventInit` to override that.
+   * @param el - element target
+   * @param eventName - event name
+   * @param eventInit - custom event init. See {@link CustomEventInit}
    */
   public static dispatch(el: EventTarget, eventName: string, eventInit?: CustomEventInit): boolean {
-    return dispatchCustomEvent(el, eventName, eventInit);
+    const init = Object.assign({
+      bubbles: true,
+      composed: true,
+      cancelable: true
+    }, eventInit || {});
+    return el.dispatchEvent(new CustomEvent(eventName, init));
   }
 
   /** Gets descriptors from the passed object */
@@ -35,50 +45,48 @@ export class EventUtils {
   }
 
   /**
-   * Gets currently subscribed listeners of the host
-   * @param host - host element (listeners context)
+   * Gets currently subscribed listeners of the target
+   * @param target - host element (listeners context)
    * @param criteria - optional set of criteria {@link ESLListenerCriteria} to filter listeners list
    */
-  public static listeners(host: HTMLElement, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
-    return ESLEventListener.get(host, ...criteria);
+  public static listeners(target: HTMLElement, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
+    return ESLEventListener.get(target).filter((listener) => !criteria.length || criteria.every(listener.matches, listener));
   }
 
-  /** Subscribes decorated `handler` method of the `host` */
-  public static subscribe(host: HTMLElement, handler: ESLListenerHandler): ESLEventListener[];
+  /** Subscribes decorated `handler` method of the `target` */
+  public static subscribe(target: HTMLElement, handler: ESLListenerHandler): ESLEventListener[];
   /** Subscribes `handler` function with the passed event type */
   public static subscribe<EType extends keyof ESLListenerEventMap>(
-    host: HTMLElement,
+    target: HTMLElement,
     descriptor: EType | ESLListenerDescriptor<EType>,
     handler: ESLListenerHandler<ESLListenerEventMap[EType]>
   ): ESLEventListener[];
   /** Subscribes `handler` descriptor function with the passed additional descriptor data */
   public static subscribe<EType extends keyof ESLListenerEventMap>(
-    host: HTMLElement,
+    target: HTMLElement,
     descriptor: Partial<ESLListenerDescriptor>,
     handler: ESLListenerDescriptorFn<EType>
   ): ESLEventListener[];
   public static subscribe(
-    host: HTMLElement,
+    target: HTMLElement,
     eventDesc: string | Partial<ESLListenerDescriptor> | ESLListenerHandler,
     handler: ESLListenerHandler = eventDesc as ESLListenerDescriptorFn
   ): ESLEventListener[] {
     if (typeof handler !== 'function') return [];
     if (typeof eventDesc === 'string') eventDesc = {event: eventDesc};
     if (isDescriptorFn(handler) && eventDesc !== handler) eventDesc = Object.assign({}, handler, eventDesc);
-
-    const listeners = ESLEventListener.createOrResolve(host, handler, eventDesc as ESLListenerDescriptor);
-    const subscribed = listeners.filter((listener) => listener.subscribe());
-    if (!subscribed.length) console.warn('[ESL]: Empty subscription %o for %o', eventDesc, handler);
-    return subscribed;
+    const listeners = ESLEventListener.create(target, handler, eventDesc as ESLListenerDescriptor);
+    listeners.forEach((listener) => listener.subscribe());
+    return listeners;
   }
 
   /**
    * Unsubscribes {@link ESLEventListener}(s) from the object
-   * @param host - host element that stores subscriptions (listeners context)
+   * @param target - host element (listeners context)
    * @param criteria - optional set of criteria {@link ESLListenerCriteria} to filter listeners to remove
    */
-  public static unsubscribe(host: HTMLElement, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
-    const listeners = ESLEventListener.get(host, ...criteria);
+  public static unsubscribe(target: HTMLElement, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
+    const listeners = EventUtils.listeners(target, ...criteria);
     listeners.forEach((listener) => listener.unsubscribe());
     return listeners;
   }


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 041aae914..b3c3b0069 100644
--- a/src/modules/esl-base-element/test/element.listener.test.ts
+++ b/src/modules/esl-base-element/test/element.listener.test.ts
@@ -2,43 +2,23 @@ import '../../../polyfills/es5-target-shim';
 
 import {ESLBaseElement, listen} from '../core';
 import {randUID} from '../../esl-utils/misc/uid';
-import {EventUtils} from '../../esl-utils/dom/events';
+import {EventUtils} from '../../esl-utils/dom/events/utils';
 
 describe('ESLBaseElement: listeners', () => {
-  describe('ESLBaseElement auto subscribes to listener declarations', () => {
-    const mockHandler = jest.fn();
+  test('auto-subscribe', () => {
     class TestElement extends ESLBaseElement {
-      static is = 'test-listen-element';
-
       @listen('click')
-      public onClick(...args: any[]) { mockHandler(this, ...args); }
+      public onClick() {}
     }
-    TestElement.register();
-
-    const el = document.createElement(TestElement.is);
-
-    beforeAll(() => document.body.appendChild(el));
-
-    test('ESLBaseElement successfully auto subscribed', () => {
-      expect(EventUtils.listeners(el).length).toBe(1);
-      expect(EventUtils.listeners(el)[0].event).toBe('click');
-    });
-
-    test('ESLBaseElement subscription works correctly', () => {
-      mockHandler.mockReset();
-      el.click();
-      expect(mockHandler).toBeCalled();
-      expect(mockHandler).lastCalledWith(el, expect.any(Event), expect.any(Object));
-    });
-
-    test('ESLBaseElement successfully auto unsubscribed', async () => {
-      document.body.removeChild(el);
-      await Promise.resolve(); // Wait for microtasks completed
-      expect(EventUtils.listeners(el).length).toBe(0);
-      return Promise.resolve();
-    });
-
-    afterAll(async () => el.parentElement && el.remove());
+    TestElement.register('test-' + randUID());
+    const el = new TestElement();
+
+    expect(EventUtils.listeners(el).length).toBe(0);
+    document.body.appendChild(el);
+    expect(EventUtils.listeners(el).length).toBe(1);
+    expect(EventUtils.listeners(el)[0].event).toBe('click');
+    document.body.removeChild(el);
+    expect(EventUtils.listeners(el).length).toBe(0);
   });
 
   describe('event accessors', () => {
diff --git a/src/modules/esl-event-listener/test/split.test.ts b/src/modules/esl-event-listener/test/split.test.ts
deleted file mode 100644
index 7b6ebaeaa..000000000
--- a/src/modules/esl-event-listener/test/split.test.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import {splitEvents} from '../core/listener';
-
-describe('ESLEventListener: splitEvents sub-utility tests', () => {
-  test(
-    'Empty string parsed to an empty array without exceptions',
-    () => expect(splitEvents('')).toEqual([])
-  );
-  test(
-    'Single event string parsed to single event',
-    () => expect(splitEvents('abc')).toEqual(['abc'])
-  );
-  test(
-    'Single event string with extra spaces parsed to single event',
-    () => expect(splitEvents('  abc ')).toEqual(['abc'])
-  );
-  test(
-    'Multiple events string parsed to separate events',
-    () => expect(splitEvents('abc de f')).toEqual(['abc', 'de', 'f'])
-  );
-  test(
-    'Multiple events string with extra spaces parsed to separate events',
-    () => expect(splitEvents(' a b')).toEqual(['a', 'b'])
-  );
-  test(
-    'The same events count ones when parsed (two unique terms)',
-    () => expect(splitEvents(' a a b')).toEqual(['a', 'b'])
-  );
-  test(
-    'The same events count ones when parsed (one unique term)',
-    () => expect(splitEvents('a a a a a')).toEqual(['a'])
-  );
-});
diff --git a/src/modules/esl-event-listener/test/subscribe.test.ts b/src/modules/esl-event-listener/test/subscribe.test.ts
deleted file mode 100644
index 1d7f962ec..000000000
--- a/src/modules/esl-event-listener/test/subscribe.test.ts
+++ /dev/null
@@ -1,309 +0,0 @@
-import {EventUtils} from '../core';
-
-describe('EventUtils:subscribe tests', () => {
-  test('EventUtils.subscribe successfully subscribes listener by descriptor', () => {
-    const $host = document.createElement('div');
-    const handle = jest.fn();
-    EventUtils.subscribe($host, {event: 'click'}, handle);
-    expect(EventUtils.listeners($host).length).toBe(1);
-  });
-  test('EventUtils.subscribe successfully subscribes listener by event name', () => {
-    const $host = document.createElement('div');
-    const handle = jest.fn();
-    EventUtils.subscribe($host, 'click', handle);
-    expect(EventUtils.listeners($host).length).toBe(1);
-  });
-  test('EventUtils.subscribe successfully subscribes listener by event provider', () => {
-    const $host = document.createElement('div');
-    const provider = jest.fn(function () {
-      expect(this).toBe($host);
-      return 'event';
-    });
-    const handle = jest.fn();
-    EventUtils.subscribe($host, {event: provider}, handle);
-    expect(EventUtils.listeners($host).length).toBe(1);
-    expect(provider).toBeCalledWith($host);
-  });
-
-  test('EventUtils.subscribe successfully subscribes listeners by string with multiple events', () => {
-    const $host = document.createElement('div');
-    const handle = jest.fn();
-    EventUtils.subscribe($host, 'click keydown', handle);
-    expect(EventUtils.listeners($host).length).toBe(2);
-    expect(EventUtils.listeners($host, 'keydown').length).toBe(1);
-    expect(EventUtils.listeners($host, 'click').length).toBe(1);
-  });
-
-  test('ESLEventListener observes target events', () => {
-    const $host = document.createElement('div');
-    const handle = jest.fn();
-    EventUtils.subscribe($host, {event: 'click'}, handle);
-
-    $host.click();
-    expect(handle).toBeCalledTimes(1);
-  });
-  test('ESLEventListener correctly observes target with selector (event delegation)', ()  => {
-    const div = document.createElement('div');
-    const btn = document.createElement('button');
-    div.appendChild(btn);
-    const span = document.createElement('span');
-    div.appendChild(span);
-    const handle = jest.fn();
-    EventUtils.subscribe(div, {event: 'click', selector: 'button'}, handle);
-
-    btn.click();
-    expect(handle).toBeCalledTimes(1);
-    span.click();
-    expect(handle).toBeCalledTimes(1);
-  });
-
-  describe('ESlEventListener subscription and delegation', () => {
-    const host = document.createElement('section');
-    host.innerHTML = `
-      <div class="btns">
-        <button class="btn">Test <span>1</span></button>
-        <button id="btn">2</button>
-      </div>
-    `;
-    const btn = host.querySelector('button.btn') as HTMLButtonElement;
-    const btnSpan = btn.querySelector('span') as HTMLSpanElement;
-    const btn2 = host.querySelector('#btn') as HTMLButtonElement;
-
-    afterEach(() => EventUtils.unsubscribe(host));
-
-    test('Simple click subscription catches click', () => {
-      const handler = jest.fn();
-      EventUtils.subscribe(host, 'click', handler);
-      expect(handler).toBeCalledTimes(0);
-      btn.click();
-      expect(handler).toBeCalledTimes(1);
-      btn2.click();
-      expect(handler).toBeCalledTimes(2);
-    });
-
-    test('Subscription with selector catches click exact on selected target', () => {
-      const handler = jest.fn();
-      EventUtils.subscribe(host, {event: 'click', selector: '.btn'}, handler);
-      expect(handler).toBeCalledTimes(0);
-      btn.click();
-      expect(handler).toBeCalledTimes(1);
-      btn2.click();
-      expect(handler).toBeCalledTimes(1);
-    });
-
-    test('Subscription with selector catches click inside selected target', () => {
-      const handler = jest.fn();
-      EventUtils.subscribe(host, {event: 'click', selector: '.btn'}, handler);
-      expect(handler).toBeCalledTimes(0);
-      btnSpan.click();
-      expect(handler).toBeCalledTimes(1);
-    });
-
-    test('Subscription with target provided with query catches click', () => {
-      const handler = jest.fn();
-      EventUtils.subscribe(host, {event: 'click', target: '::find(#btn)'}, handler);
-      expect(handler).toBeCalledTimes(0);
-      btnSpan.click();
-      expect(handler).toBeCalledTimes(0);
-      btn2.click();
-      expect(handler).toBeCalledTimes(1);
-    });
-
-    test('Subscription with target provided by instance', () => {
-      const handler = jest.fn();
-      const el = document.createElement('button');
-      EventUtils.subscribe(host, {event: 'click', target: el}, handler);
-      expect(handler).toBeCalledTimes(0);
-      btn.click();
-      expect(handler).toBeCalledTimes(0);
-      btn2.click();
-      expect(handler).toBeCalledTimes(0);
-      el.click();
-      expect(handler).toBeCalledTimes(1);
-    });
-  });
-
-  describe('ESlEventListener subscription with target or selector defined by provider', () => {
-    const host = document.createElement('section');
-    host.innerHTML = `
-      <div class="btns">
-        <button class="btn1">Test</button>
-        <button class="btn2">2</button>
-      </div>
-    `;
-    const btn1 = host.querySelector('button.btn1') as HTMLButtonElement;
-    const btn2 = host.querySelector('button.btn2') as HTMLButtonElement;
-
-    afterEach(() => EventUtils.unsubscribe(host));
-
-    test('Selector provider receives host in arg and context', () => {
-      const handler = jest.fn();
-      const provider  = jest.fn(function () {
-        expect(this).toBe(host);
-        return 'button';
-      });
-      EventUtils.subscribe(host, {event: 'click', selector: provider}, handler);
-      btn1.click();
-      expect(provider).toHaveBeenCalledWith(host);
-    });
-
-    test('Selector defined with provider applies on fly', () => {
-      const handler = jest.fn();
-      const provider  = jest.fn();
-      EventUtils.subscribe(host, {event: 'click', selector: provider}, handler);
-      expect(handler).toBeCalledTimes(0);
-
-      provider.mockReturnValue('.btn1');
-      btn1.click();
-      expect(handler).toBeCalledTimes(1);
-      btn2.click();
-      expect(handler).toBeCalledTimes(1);
-
-      provider.mockReturnValue('.btn2');
-      btn1.click();
-      expect(handler).toBeCalledTimes(1);
-      btn2.click();
-      expect(handler).toBeCalledTimes(2);
-    });
-
-    test('Target provider receives host in arg and context', () => {
-      const handler = jest.fn();
-      const provider  = jest.fn(function () {
-        expect(this).toBe(host);
-        return '';
-      });
-      EventUtils.subscribe(host, {event: 'click', target: provider}, handler);
-      btn1.click();
-      expect(provider).toHaveBeenCalledWith(host);
-    });
-
-    test('Subscription use target provider value correctly', () => {
-      const handler = jest.fn();
-      const provider  = jest.fn(() => '::find(.btn2)');
-      EventUtils.subscribe(host, {event: 'click', target: provider}, handler);
-      btn1.click();
-      expect(handler).not.toBeCalled();
-      btn2.click();
-      expect(handler).toBeCalled();
-    });
-  });
-
-  test('ESlEventListener does not produce subscription if target provider returns null', () => {
-    jest.spyOn(console, 'warn').mockImplementationOnce(() => undefined); // Skip warn
-    const host = document.createElement('div');
-    const handler = jest.fn();
-    const provider  = jest.fn(() => null);
-
-    const listeners = EventUtils.subscribe(host, {event: 'click', target: provider}, handler);
-    host.click();
-    expect(listeners).toEqual([]);
-    expect(provider).toBeCalled();
-    expect(handler).not.toBeCalled();
-  });
-
-  describe('EventUtils.subscribe resubscribing event', () => {
-    const $host = document.createElement('div');
-    const handle = jest.fn();
-    const targ1 = document.createElement('button');
-    const targ2 = document.createElement('button');
-    const target = jest.fn(() => targ1);
-
-    const listeners1 = EventUtils.subscribe($host, {event: 'click', target}, handle);
-    test('EventUtils.subscribe subscription correctly first time', () => {
-      expect(listeners1.length).toBe(1);
-      expect(EventUtils.listeners($host).length).toBe(1);
-    });
-
-    target.mockImplementation(() => targ2);
-    const listeners2 = EventUtils.subscribe($host, {event: 'click', target}, handle);
-    test('EventUtils.subscribe subscription correctly second time', () => {
-      expect(listeners2.length).toBe(1);
-      expect(EventUtils.listeners($host).length).toBe(1);
-    });
-
-    test('Resubscribed listener does not observe initial target', () => {
-      targ1.click();
-      expect(handle).not.toBeCalled();
-    });
-
-    test('Resubscribed listener observes actual target', () => {
-      targ2.click();
-      expect(handle).toBeCalled();
-    });
-
-    test('EventUtils des not recreate instance', () => expect(listeners1[0]).toBe(listeners2[0]));
-  });
-
-  describe('EventUtils.subscribe subscribes single time', () => {
-    test('subscribe one event with the same handler does not leads to duplicate subscription', () => {
-      const host = document.createElement('div');
-      const fn = jest.fn();
-
-      EventUtils.subscribe(host, 'click', fn);
-      expect(EventUtils.listeners(host).length).toBe(1);
-      EventUtils.subscribe(host, 'click', fn);
-      expect(EventUtils.listeners(host).length).toBe(1);
-    });
-
-    test('subscribe one event with different handlers produces different subscriptions', () => {
-      const host = document.createElement('div');
-      const fn1 = jest.fn();
-      const fn2 = jest.fn();
-
-      EventUtils.subscribe(host, 'click', fn1);
-      expect(EventUtils.listeners(host).length).toBe(1);
-      EventUtils.subscribe(host, 'click', fn2);
-      expect(EventUtils.listeners(host).length).toBe(2);
-    });
-
-    test('subscribe considers descriptors with different selectors as different', () => {
-      const host = document.createElement('div');
-      const fn = jest.fn();
-
-      EventUtils.subscribe(host, {event: 'e', selector: 'a'}, fn);
-      expect(EventUtils.listeners(host).length).toBe(1);
-      EventUtils.subscribe(host, {event: 'e', selector: 'a'}, fn);
-      expect(EventUtils.listeners(host).length).toBe(1);
-      EventUtils.subscribe(host, {event: 'e', selector: 'b'}, fn);
-      expect(EventUtils.listeners(host).length).toBe(2);
-      EventUtils.subscribe(host, {event: 'e'}, fn);
-      expect(EventUtils.listeners(host).length).toBe(3);
-    });
-
-    test('subscribe considers descriptors with different targets as different', () => {
-      const host = document.createElement('div');
-      const target1 = document.createElement('div');
-      const target2 = document.createElement('div');
-      const fn = jest.fn();
-
-      EventUtils.subscribe(host, {event: 'e', target: target1}, fn);
-      expect(EventUtils.listeners(host).length).toBe(1);
-      EventUtils.subscribe(host, {event: 'e', target: target1}, fn);
-      expect(EventUtils.listeners(host).length).toBe(1);
-      EventUtils.subscribe(host, {event: 'e', target: target2}, fn);
-      expect(EventUtils.listeners(host).length).toBe(2);
-      EventUtils.subscribe(host, {event: 'e'}, fn);
-      expect(EventUtils.listeners(host).length).toBe(3);
-    });
-
-    test('subscribe considers descriptors with different capture phases as different', () => {
-      const host = document.createElement('div');
-      const fn = jest.fn();
-
-      EventUtils.subscribe(host, {event: 'e', capture: true}, fn);
-      expect(EventUtils.listeners(host).length).toBe(1);
-      EventUtils.subscribe(host, {event: 'e', capture: true}, fn);
-      expect(EventUtils.listeners(host).length).toBe(1);
-      EventUtils.subscribe(host, {event: 'e', capture: false}, fn);
-      expect(EventUtils.listeners(host).length).toBe(2);
-      EventUtils.subscribe(host, {event: 'e'}, fn);
-      expect(EventUtils.listeners(host).length).toBe(2);
-    });
-  });
-
-  // TODO: tests for `once` marker
-
-  // TODO: tests for find method
-
-  // TODO: Do we need to split it...
-});
diff --git a/src/modules/esl-event-listener/test/unsubscribe.test.ts b/src/modules/esl-event-listener/test/unsubscribe.test.ts
deleted file mode 100644
index 2fba42a9d..000000000
--- a/src/modules/esl-event-listener/test/unsubscribe.test.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import {EventUtils} from '../core';
-
-describe('EventUtils:unsubscribe successfully removes listener', () => {
-  const handle = jest.fn();
-  const div = document.createElement('div');
-
-  beforeEach(() => {
-    EventUtils.subscribe(div, {event: 'click'}, handle);
-    EventUtils.subscribe(div, {event: 'event'}, handle);
-  });
-
-  test('all', ()  => {
-    EventUtils.unsubscribe(div);
-    expect(EventUtils.listeners(div).length).toBe(0);
-  });
-
-  test('by event', ()  => {
-    EventUtils.unsubscribe(div, {event: 'mousedown'});
-    expect(EventUtils.listeners(div).length).toBe(2);
-    EventUtils.unsubscribe(div, {event: 'click'});
-    expect(EventUtils.listeners(div).length).toBe(1);
-    EventUtils.unsubscribe(div, {event: 'event'});
-    expect(EventUtils.listeners(div).length).toBe(0);
-  });
-
-  test('by event(string)', ()  => {
-    EventUtils.unsubscribe(div, 'test');
-    expect(EventUtils.listeners(div).length).toBe(2);
-    EventUtils.unsubscribe(div, 'click');
-    expect(EventUtils.listeners(div).length).toBe(1);
-    EventUtils.unsubscribe(div, 'event');
-    expect(EventUtils.listeners(div).length).toBe(0);
-  });
-
-  test('by handler', ()  => {
-    EventUtils.unsubscribe(div, () => 1);
-    expect(EventUtils.listeners(div).length).toBe(2);
-    EventUtils.unsubscribe(div, handle);
-    expect(EventUtils.listeners(div).length).toBe(0);
-  });
-
-  test('with multiple criteria', ()  => {
-    EventUtils.unsubscribe(div, handle, {event: 'key'});
-    expect(EventUtils.listeners(div).length).toBe(2);
-    EventUtils.unsubscribe(div, handle, {event: 'click'});
-    expect(EventUtils.listeners(div).length).toBe(1);
-  });
-});
diff --git a/src/modules/esl-event-listener/test/utils.test.ts b/src/modules/esl-event-listener/test/utils.test.ts
deleted file mode 100644
index 57b27212f..000000000
--- a/src/modules/esl-event-listener/test/utils.test.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import {EventUtils} from '../core';
-
-describe('dom/events: EventUtils', () => {
-  // TODO: extend tests + possibly move descriptor finding to separate module?
-  describe('descriptors', () => {
-    test('basic test 1', () => {
-      const fn1 = () => undefined;
-      const fn2 = () => undefined;
-      fn1.event = fn2.event = 'test';
-
-      const obj = {onClick: fn1};
-      const proto = {onEvent: fn2};
-      Object.setPrototypeOf(obj, proto);
-
-      const desc = EventUtils.descriptors(obj);
-      expect(Array.isArray(desc)).toBe(true);
-      expect(desc.includes(fn1));
-      expect(desc.includes(fn2));
-    });
-    test('basic test 2', () => {
-      const obj: any = document.createElement('div');
-
-      expect(EventUtils.descriptors(obj)).toEqual([]);
-
-      obj.onEvent = Object.assign(() => undefined, {event: 'event', auto: true});
-
-      expect(EventUtils.descriptors(obj).length).toEqual(1);
-      expect(EventUtils.descriptors(obj)[0]).toEqual(obj.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
deleted file mode 100644
index 9ec565c6a..000000000
--- a/src/modules/esl-mixin-element/test/mixin.listener.test.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import {ESLMixinElement, listen} from '../core';
-import {EventUtils} from '../../esl-utils/dom/events';
-
-describe('ESLMixinElement: listeners', () => {
-  describe('ESLMixinElement auto subscribes to listener declarations', () => {
-    const mockHandler = jest.fn();
-    class TestElement extends ESLMixinElement {
-      static is = 'test-listen-mixin';
-
-      @listen('click')
-      public onClick(...args: any[]) { mockHandler(this, ...args); }
-    }
-    TestElement.register();
-
-    const el = document.createElement('div');
-    el.setAttribute(TestElement.is, '');
-
-    beforeAll(() => document.body.appendChild(el));
-
-    test('ESLMixinElement successfully auto subscribed', () => {
-      expect(EventUtils.listeners(el).length).toBe(1);
-      expect(EventUtils.listeners(el)[0].event).toBe('click');
-    });
-
-    test('ESLMixinElement subscription works correctly', () => {
-      mockHandler.mockReset();
-      el.click();
-      expect(mockHandler).toBeCalled();
-      expect(mockHandler).lastCalledWith(TestElement.get(el), expect.any(Event), expect.any(Object));
-    });
-
-    test('ESLMixinElement successfully auto unsubscribed', async () => {
-      document.body.removeChild(el);
-      await Promise.resolve(); // Wait for microtasks completed
-      expect(EventUtils.listeners(el).length).toBe(0);
-      return Promise.resolve();
-    });
-
-    afterAll(async () => el.parentElement && el.remove());
-  });
-
-  // TODO: $$on, $$off tests
-});
diff --git a/src/modules/esl-trigger/test/esl-trigger.a11y.test.ts b/src/modules/esl-trigger/test/esl-trigger.a11y.test.ts
index 2d964c7e7..e642f0af1 100644
--- a/src/modules/esl-trigger/test/esl-trigger.a11y.test.ts
+++ b/src/modules/esl-trigger/test/esl-trigger.a11y.test.ts
@@ -1,6 +1,6 @@
 import '../../../polyfills/es5-target-shim';
 import {SyntheticEventTarget} from '../../esl-utils/dom/events/target';
-import {EventUtils} from '../../esl-utils/dom/events';
+import {EventUtils} from '../../esl-utils/dom/events/utils';
 import {ESLTrigger} from '../core/esl-trigger';
 import type {ESLToggleable} from '../../esl-toggleable/core/esl-toggleable';
 
@@ -29,7 +29,7 @@ describe('esl-trigger a11y attributes test', () => {
   describe(
     'a11yLabelActive attribute basic tests',
     () => {
-      const trigger = document.createElement(ESLTrigger.is) as ESLTrigger;
+      const trigger = ESLTrigger.create();
 
       beforeAll(() => {
         trigger.a11yLabelActive = 'active';
@@ -65,7 +65,7 @@ describe('esl-trigger a11y attributes test', () => {
   describe(
     'a11yLabelInactive attribute basic tests',
     () => {
-      const trigger = document.createElement(ESLTrigger.is) as ESLTrigger;
+      const trigger = ESLTrigger.create();
 
       beforeAll(() => {
         trigger.a11yLabelInactive = 'inactive';
@@ -102,7 +102,7 @@ describe('esl-trigger a11y attributes test', () => {
   describe(
     'a11yLabelActive and a11yLabelInactive are both present (+ initially active flow)',
     () => {
-      const trigger = document.createElement(ESLTrigger.is) as ESLTrigger;
+      const trigger = ESLTrigger.create();
 
       beforeAll(() => {
         trigger.a11yLabelActive = 'active';
@@ -138,7 +138,7 @@ describe('esl-trigger a11y attributes test', () => {
     'a11yLabelInactive and a11yLabelActive attributes are not specified',
     () => {
       const INIT_VAL = 'test-init-val';
-      const trigger = document.createElement(ESLTrigger.is) as ESLTrigger;
+      const trigger = ESLTrigger.create();
 
       beforeAll(() => {
         trigger.setAttribute(LABEL_ATTR, INIT_VAL);
diff --git a/src/modules/esl-utils/decorators/test/listen.test.ts b/src/modules/esl-utils/decorators/test/listen.test.ts
index 45d259903..d67a0adbd 100644
--- a/src/modules/esl-utils/decorators/test/listen.test.ts
+++ b/src/modules/esl-utils/decorators/test/listen.test.ts
@@ -33,7 +33,7 @@ describe('Decorator: @listen', () => {
     class Test extends HTMLElement {
       @listen({event: 'event1'})
       onEvent1() {}
-      @listen({event: 'event1'})
+      @listen({event: 'event1', id: 'a'})
       onEvent2() {}
     }
     customElements.define('test-listen-2', Test);
diff --git a/src/modules/esl-utils/dom/events/test/integration.test.ts b/src/modules/esl-utils/dom/events/test/integration.test.ts
new file mode 100644
index 000000000..e8b0b2856
--- /dev/null
+++ b/src/modules/esl-utils/dom/events/test/integration.test.ts
@@ -0,0 +1,82 @@
+import {EventUtils} from '../utils';
+
+describe('EventUtils: integration', () => {
+  test('subscribe', ()  => {
+    const div = document.createElement('div');
+    const handle = jest.fn();
+
+    EventUtils.subscribe(div, {event: 'click'}, handle);
+
+    expect(EventUtils.listeners(div).length).toBe(1);
+
+    expect(handle).toBeCalledTimes(0);
+    div.click();
+    expect(handle).toBeCalledTimes(1);
+  });
+
+  test('subscribe delegate', ()  => {
+    const div = document.createElement('div');
+    const btn = document.createElement('button');
+    div.appendChild(btn);
+    const span = document.createElement('span');
+    div.appendChild(span);
+    const handle = jest.fn();
+    EventUtils.subscribe(div, {event: 'click', selector: 'button'}, handle);
+
+    expect(handle).toBeCalledTimes(0);
+    btn.click();
+    expect(handle).toBeCalledTimes(1);
+    span.click();
+    expect(handle).toBeCalledTimes(1);
+  });
+
+  describe('unsubscribe', () => {
+    const handle = jest.fn();
+    const div = document.createElement('div');
+
+    beforeEach(() => {
+      EventUtils.subscribe(div, {id: 'test', event: 'click'}, handle);
+    });
+
+    test('all', ()  => {
+      expect(EventUtils.listeners(div).length).toBe(1);
+      EventUtils.unsubscribe(div);
+      expect(EventUtils.listeners(div).length).toBe(0);
+    });
+
+    test('by id', ()  => {
+      EventUtils.unsubscribe(div, {id: 'testic'});
+      expect(EventUtils.listeners(div).length).toBe(1);
+      EventUtils.unsubscribe(div, {id: 'test'});
+      expect(EventUtils.listeners(div).length).toBe(0);
+    });
+
+    test('by event', ()  => {
+      EventUtils.unsubscribe(div, {event: 'mousedown'});
+      expect(EventUtils.listeners(div).length).toBe(1);
+      EventUtils.unsubscribe(div, {event: 'click'});
+      expect(EventUtils.listeners(div).length).toBe(0);
+    });
+
+    test('by event(string)', ()  => {
+      EventUtils.unsubscribe(div, 'test');
+      expect(EventUtils.listeners(div).length).toBe(1);
+      EventUtils.unsubscribe(div, 'click');
+      expect(EventUtils.listeners(div).length).toBe(0);
+    });
+
+    test('by handler', ()  => {
+      EventUtils.unsubscribe(div, () => 1);
+      expect(EventUtils.listeners(div).length).toBe(1);
+      EventUtils.unsubscribe(div, handle);
+      expect(EventUtils.listeners(div).length).toBe(0);
+    });
+
+    test('with multiple criteria', ()  => {
+      EventUtils.unsubscribe(div, handle, {event: 'key'});
+      expect(EventUtils.listeners(div).length).toBe(1);
+      EventUtils.unsubscribe(div, handle, {event: 'click'});
+      expect(EventUtils.listeners(div).length).toBe(0);
+    });
+  });
+});
diff --git a/src/modules/esl-utils/dom/events/test/listener.test.ts b/src/modules/esl-utils/dom/events/test/listener.test.ts
new file mode 100644
index 000000000..fb3bff730
--- /dev/null
+++ b/src/modules/esl-utils/dom/events/test/listener.test.ts
@@ -0,0 +1,188 @@
+import {ESLEventListener} from '../listener';
+
+describe('dom/events: ESLEventListener', () => {
+  describe('get', () => {
+    test('simple', () => {
+      expect(ESLEventListener.get()).toEqual([]);
+      expect(ESLEventListener.get({})).toEqual([]);
+    });
+    test('singleton', () => {
+      const obj = {};
+      const listeners = ESLEventListener.get(obj);
+      expect(ESLEventListener.get(obj)).toBe(listeners);
+    });
+  });
+
+  describe('create', () => {
+    test('without event type', () => {
+      const consoleSpy = jest.spyOn(console, 'warn').mockImplementation(() => void 0);
+
+      const host = document.createElement('div');
+      const handler = jest.fn();
+      const result = ESLEventListener.create(host, handler, {event: ''});
+
+      expect(result.length).toBe(0);
+      expect(consoleSpy).toBeCalledTimes(1);
+      consoleSpy.mockClear();
+    });
+
+    test('one by string', () => {
+      const host = document.createElement('div');
+      const handler = jest.fn();
+      const result = ESLEventListener.create(host, handler, {event: 'click'});
+
+      expect(result.length).toBe(1);
+      expect(result[0].event).toBe('click');
+      expect(result[0].handler).toBe(handler);
+    });
+
+    test('via event provider', () => {
+      const host = document.createElement('div');
+      const handler = jest.fn();
+      const eventProvider = jest.fn(() => 'click');
+      const result = ESLEventListener.create(host, handler, {event: eventProvider});
+
+      expect(result.length).toBe(1);
+      expect(result[0].event).toBe('click');
+      expect(result[0].handler).toBe(handler);
+      expect(eventProvider).lastCalledWith(host);
+    });
+
+    test('via event provider with context', () => {
+      const context  = {};
+      const host = document.createElement('div');
+      const handler = jest.fn();
+      const eventProvider = jest.fn(() => 'click');
+      ESLEventListener.create(host, handler, {event: eventProvider, context});
+      expect(eventProvider).lastCalledWith(context);
+    });
+
+    test('multiple by string', () => {
+      const host = document.createElement('div');
+      const handler = jest.fn();
+      const result = ESLEventListener.create(host, handler, {event: 'click keydown keypress'});
+
+      expect(result.length).toBe(3);
+      expect(result[0].handler).toBe(handler);
+      expect(result[1].handler).toBe(handler);
+      expect(result[2].handler).toBe(handler);
+      expect(result.map((l: ESLEventListener) => l.event)).toEqual(['click', 'keydown', 'keypress']);
+    });
+
+    test('one by desc', () => {
+      const host = document.createElement('div');
+      const handler = jest.fn();
+      const result = ESLEventListener.create(host, handler, {event: 'keypress', id: 'test'});
+
+      expect(result.length).toBe(1);
+      expect(result[0].handler).toBe(handler);
+      expect(result[0].id).toBe('test');
+      expect(result[0].event).toBe('keypress');
+    });
+
+    test('multiple by desc', () => {
+      const host = document.createElement('div');
+      const handler = jest.fn();
+      const result = ESLEventListener.create(host, handler, {event: 'e1 e2', selector: 'button'});
+
+      expect(result.length).toBe(2);
+      expect(result[0].handler).toBe(handler);
+      expect(result[1].handler).toBe(handler);
+      expect(result[0].event).toBe('e1');
+      expect(result[1].event).toBe('e2');
+      expect(result[0].selector).toBe('button');
+      expect(result[1].selector).toBe('button');
+    });
+
+    test('option providers', () => {
+      const host = document.createElement('div');
+      const handler = jest.fn();
+      const eventProvider = jest.fn(() => 'test');
+      const targetProvider = jest.fn(() => '::not(btn)');
+      const selectorProvider = jest.fn(() => '.btn');
+
+      const [listener] = ESLEventListener.create(host, handler, {
+        event: eventProvider,
+        target: targetProvider,
+        selector: selectorProvider
+      });
+
+      expect(eventProvider).toBeCalledWith(host);
+      expect(targetProvider).toBeCalledWith(host);
+      expect(selectorProvider).toBeCalledWith(host);
+
+      expect(listener.event).toBe(eventProvider());
+      expect(listener.target).toBe(targetProvider());
+      expect(listener.selector).toBe(selectorProvider());
+    });
+
+    test('option providers vs context', () => {
+      const context  = {};
+      const host = document.createElement('div');
+      const handler = jest.fn();
+      const eventProvider = jest.fn(() => 'test');
+      const targetProvider = jest.fn(() => '::not(btn)');
+      const selectorProvider = jest.fn(() => '.btn');
+
+      ESLEventListener.create(host, handler, {
+        event: eventProvider,
+        target: targetProvider,
+        selector: selectorProvider,
+        context
+      });
+
+      expect(eventProvider).toBeCalledWith(context);
+      expect(targetProvider).toBeCalledWith(context);
+      expect(selectorProvider).toBeCalledWith(context);
+    });
+  });
+
+  describe('instance methods', () => {
+    const host = document.createElement('section');
+    host.innerHTML = `
+      <div class="btns">
+        <button class="btn">Test <span>1</span></button>
+        <button id="btn">2</button>
+      </div>
+    `;
+    const btn = host.querySelector('button.btn') as HTMLButtonElement;
+    const btnSpan = btn.querySelector('span') as HTMLSpanElement;
+    const btn2 = host.querySelector('#btn') as HTMLButtonElement;
+
+    test('simple subscribe', () => {
+      const handler = jest.fn();
+      const listener = new ESLEventListener(host, handler, {event: 'click'});
+      listener.subscribe();
+
+      expect(handler).toBeCalledTimes(0);
+      btnSpan.click();
+      expect(handler).toBeCalledTimes(1);
+    });
+
+    test('delegate subscribe', () => {
+      const handler = jest.fn();
+      const listener = new ESLEventListener(host, handler, {event: 'click', selector: '.btn'});
+      listener.subscribe();
+
+      expect(handler).toBeCalledTimes(0);
+      btnSpan.click();
+      expect(handler).toBeCalledTimes(1);
+      btn.click();
+      expect(handler).toBeCalledTimes(2);
+      btn2.click();
+      expect(handler).toBeCalledTimes(2);
+    });
+
+    test('target subscribe', () => {
+      const handler = jest.fn();
+      const listener = new ESLEventListener(host, handler, {event: 'click', target: '::find(#btn)'});
+      listener.subscribe();
+
+      expect(handler).toBeCalledTimes(0);
+      btn.click();
+      expect(handler).toBeCalledTimes(0);
+      btn2.click();
+      expect(handler).toBeCalledTimes(1);
+    });
+  });
+});
diff --git a/src/modules/esl-utils/dom/events/test/misc.test.ts b/src/modules/esl-utils/dom/events/test/misc.test.ts
index 86de3fd70..1c2359402 100644
--- a/src/modules/esl-utils/dom/events/test/misc.test.ts
+++ b/src/modules/esl-utils/dom/events/test/misc.test.ts
@@ -5,8 +5,7 @@ import {
   isPassiveByDefault,
   getTouchPoint,
   getOffsetPoint,
-  getCompositeTarget,
-  dispatchCustomEvent
+  getCompositeTarget
 } from '../misc';
 
 describe('dom/events: misc', () => {
@@ -116,45 +115,4 @@ describe('dom/events: misc', () => {
       });
     });
   });
-
-  describe('dispatchCustomEvent works correctly', () => {
-    const el = document.createElement('div');
-    const mockDispatch = jest.spyOn(el, 'dispatchEvent');
-
-    beforeEach(() => mockDispatch.mockReset());
-
-    test('dispatchCustomEvent dispatches CustomEvent instance on the provided element', () => {
-      const eventName = `click${Math.random()}`;
-      const customEventInit = {detail: Math.random()};
-      dispatchCustomEvent(el, eventName, customEventInit);
-
-      expect(el.dispatchEvent).toHaveBeenCalled();
-
-      const event: CustomEvent = (el.dispatchEvent as jest.Mock).mock.calls[0][0];
-      expect(event.type).toBe(eventName);
-      expect(event.bubbles).toBe(true);
-      expect(event.cancelable).toBe(true);
-      expect((event as any).detail).toBe(customEventInit.detail);
-    });
-
-    test('dispatchCustomEvent dispatches CustomEvent instance with default params', () => {
-      const eventName = `click${Math.random()}`;
-      dispatchCustomEvent(el, eventName);
-
-      expect(el.dispatchEvent).toHaveBeenCalled();
-      const event: CustomEvent = (el.dispatchEvent as jest.Mock).mock.calls[0][0];
-      expect(event.bubbles).toBe(true);
-      expect(event.cancelable).toBe(true);
-    });
-
-    test('dispatchCustomEvent merge custom init before dispatching CustomEvent', () => {
-      const eventName = `click${Math.random()}`;
-      dispatchCustomEvent(el, eventName, {cancelable: false, bubbles: false});
-
-      expect(el.dispatchEvent).toHaveBeenCalled();
-      const event: CustomEvent = (el.dispatchEvent as jest.Mock).mock.calls[0][0];
-      expect(event.bubbles).toBe(false);
-      expect(event.cancelable).toBe(false);
-    });
-  });
 });
diff --git a/src/modules/esl-utils/dom/events/test/utils.test.ts b/src/modules/esl-utils/dom/events/test/utils.test.ts
new file mode 100644
index 000000000..307e352f8
--- /dev/null
+++ b/src/modules/esl-utils/dom/events/test/utils.test.ts
@@ -0,0 +1,163 @@
+import {EventUtils} from '../utils';
+import {ESLEventListener} from '../listener';
+
+describe('dom/events: EventUtils', () => {
+  describe('dispatch', () => {
+    test('dispatches event with custom event init on the provided element', () => {
+      const el = document.createElement('div');
+      jest.spyOn(el, 'dispatchEvent');
+
+      const eventName = `click${Math.random()}`;
+      const customEventInit = {detail: Math.random()};
+      EventUtils.dispatch(el, eventName, customEventInit);
+
+      expect(el.dispatchEvent).toHaveBeenCalled();
+
+      const event: CustomEvent = (el.dispatchEvent as jest.Mock).mock.calls[0][0];
+      expect(event.type).toBe(eventName);
+      expect((event as any).detail).toBe(customEventInit.detail);
+    });
+  });
+
+  describe('descriptors', () => {
+    test('basic test 1', () => {
+      const fn1 = () => undefined;
+      const fn2 = () => undefined;
+      fn1.event = fn2.event = 'test';
+
+      const obj = {onClick: fn1};
+      const proto = {onEvent: fn2};
+      Object.setPrototypeOf(obj, proto);
+
+      const desc = EventUtils.descriptors(obj);
+      expect(Array.isArray(desc)).toBe(true);
+      expect(desc.includes(fn1));
+      expect(desc.includes(fn2));
+    });
+    test('basic test 2', () => {
+      const obj: any = document.createElement('div');
+
+      expect(EventUtils.descriptors(obj)).toEqual([]);
+
+      obj.onEvent = Object.assign(() => undefined, {event: 'event', auto: true});
+
+      expect(EventUtils.descriptors(obj).length).toEqual(1);
+      expect(EventUtils.descriptors(obj)[0]).toEqual(obj.onEvent);
+    });
+  });
+
+  describe('listeners', () => {
+    const list = [
+      {matches: jest.fn()},
+      {matches: jest.fn()}
+    ];
+    const host = {__listeners: list};
+
+    beforeEach(() => list.forEach(({matches}: any) => matches.mockReset()));
+    test('all', () => {
+      expect(EventUtils.listeners(host as any)).toEqual(list);
+    });
+    test('1 criteria', () => {
+      list[0].matches.mockReturnValue(false);
+      list[1].matches.mockReturnValue(true);
+
+      expect(EventUtils.listeners(host as any, 'a')).toEqual(list.slice(1));
+      expect(list[0].matches).lastCalledWith('a', expect.anything(), expect.anything());
+      expect(list[1].matches).lastCalledWith('a', expect.anything(), expect.anything());
+    });
+    test('2 criteria', () => {
+      list[0].matches.mockReturnValue(true);
+      list[1].matches.mockReturnValue(true);
+
+      expect(EventUtils.listeners(host as any, 'b', {})).toEqual(list);
+      expect(list[0].matches).toBeCalledTimes(2);
+      expect(list[1].matches).toBeCalledTimes(2);
+    });
+  });
+
+  describe('subscribe', () => {
+    const listener1 =
+      Object.assign(() => undefined, {auto: false, event: 'e1', subscribe: jest.fn()});
+
+    test('decorated handler', () => {
+      const host = {};
+      const createMock =
+        jest.spyOn(ESLEventListener, 'create').mockImplementation((el, cb, desc) => [desc] as any);
+
+      EventUtils.subscribe(host as any, listener1);
+      expect(listener1.subscribe).toBeCalled();
+      expect(createMock).toBeCalledWith(host, expect.anything(), expect.anything());
+    });
+
+    test('decorated handler with empty override', () => {
+      const host = {};
+      const createMock =
+        jest.spyOn(ESLEventListener, 'create').mockImplementation((el, cb, desc) => [desc] as any);
+
+      EventUtils.subscribe(host as any, {}, listener1);
+      expect(listener1.subscribe).toBeCalled();
+      expect(createMock).toBeCalledWith(host, expect.anything(), expect.anything());
+    });
+
+    test('merge decorated handler', () => {
+      const host = {};
+      const createMock =
+        jest.spyOn(ESLEventListener, 'create').mockImplementation((el, cb, desc) => [desc] as any);
+
+      EventUtils.subscribe(host as any, {event: 'e2'}, listener1);
+      expect(listener1.subscribe).toBeCalled();
+      const expDesc = Object.assign({}, listener1, {event: 'e2'});
+      expect(createMock).toBeCalledWith(host, expect.anything(), expDesc);
+    });
+
+    test('manual descriptor', () => {
+      const host = {};
+      const fn = jest.fn();
+      const desc = {event: 'test'};
+      const listener = {subscribe: jest.fn()};
+      const createMock =
+        jest.spyOn(ESLEventListener, 'create').mockImplementation(() => [listener] as any);
+
+      EventUtils.subscribe(host as any, desc, fn);
+      expect(listener.subscribe).toBeCalled();
+      expect(createMock).toBeCalledWith(host, fn, desc);
+    });
+
+    test('manual string descriptor', () => {
+      const host = {};
+      const fn = jest.fn();
+      const listener = {subscribe: jest.fn()};
+      const createMock =
+        jest.spyOn(ESLEventListener, 'create').mockImplementation(() => [listener] as any);
+
+      EventUtils.subscribe(host as any, 'click', fn);
+      expect(listener.subscribe).toBeCalled();
+      expect(createMock).toBeCalledWith(host, fn, {event: 'click'});
+    });
+  });
+
+  describe('unsubscribe', () => {
+    const list = [
+      {matches: jest.fn(), unsubscribe: jest.fn()},
+      {matches: jest.fn(), unsubscribe: jest.fn()}
+    ];
+    const host = {__listeners: list};
+
+    beforeEach(() => list.forEach(({matches, unsubscribe}: any) => {
+      matches.mockReset();
+      unsubscribe.mockReset();
+    }));
+    test('all', () => {
+      EventUtils.unsubscribe(host as any);
+      expect(list[0].unsubscribe).toBeCalled();
+      expect(list[1].unsubscribe).toBeCalled();
+    });
+    test('criteria', () => {
+      list[0].matches.mockReturnValue(false);
+      list[1].matches.mockReturnValue(true);
+      EventUtils.unsubscribe(host as any, 'test');
+      expect(list[0].unsubscribe).not.toBeCalled();
+      expect(list[1].unsubscribe).toBeCalled();
+    });
+  });
+});