Repository: exadel-inc/esl
Pull request #2145 (2024-01-04 12:56:01)

fix: popup has wrong positioning leads to a broken popup layout
Closes: #2144 

 - replaced deprecated properties `pageXOffset` `pageYOffset`
 - created new `getViewportRect()` helper function
 - replaced using of `getWindowRect()` with `getViewportRect()` to fix the bug with popups


Patch:
diff --git a/.eslintrc.yml b/.eslintrc.yml
index 9b1f5f393..044c7f1f3 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -10,7 +10,7 @@ extends:
   - 'eslint:recommended'
   - 'plugin:import/typescript'
   - 'plugin:@typescript-eslint/recommended'
-  - 'plugin:@exadel/esl/default'
+  - 'plugin:@exadel/esl/default-4'
 
   # Library base set
   - './linting/codestyle.eslintrc.yml'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f03b9cacb..edebf2734 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,104 +1,3 @@
-# [5.0.0-beta.3](https://github.com/exadel-inc/esl/compare/v5.0.0-beta.2...v5.0.0-beta.3) (2023-12-21)
-
-
-### Bug Fixes
-
-* **esl-event-listener:** fix support for any object-like host ([9ca6aa4](https://github.com/exadel-inc/esl/commit/9ca6aa4e5a12971285c4af7d10a2ecf277c83197))
-* **esl-utils:** `createZIndexIframe` retired ([ad678cb](https://github.com/exadel-inc/esl/commit/ad678cb3d20e122c8e7a87cb19c9e92b3f129a0a))
-* **esl-utils:** `SyntheticEventTarget` optimizations ([a4c9b8d](https://github.com/exadel-inc/esl/commit/a4c9b8d4cfd9b9e3ad20b84a16b8eb6ccc5ae4c0))
-* **esl-utils:** clean IE11 micro-optimizations ([7ed8830](https://github.com/exadel-inc/esl/commit/7ed883054b43c8c142409e6c6539aca93ef0048b))
-* **esl-utils:** fix types for `unwrap` array utility ([a4b432a](https://github.com/exadel-inc/esl/commit/a4b432a8680b02cab6edb23a0fb3de85788a2985))
-* **esl-utils:** simplified `flat` array utility ([788a782](https://github.com/exadel-inc/esl/commit/788a782ec03af63d370dd0c2785c40547a0f7624))
-* **esl-utils:** simplified `union` utility ([043fe45](https://github.com/exadel-inc/esl/commit/043fe45054da7dd1c05c61ea16146ff53555c54e))
-* **esl-utils:** simplified `uniq` array utility ([45f282f](https://github.com/exadel-inc/esl/commit/45f282f05ef8d05d0035d1a99d45034d9a4cf790))
-
-
-### Code Refactoring
-
-* Drop Edge old versions (<14) support ([b577fd1](https://github.com/exadel-inc/esl/commit/b577fd1cc085a6a2c5fbdcfa97a5401e0ad7b259))
-* Drop IE11 support ([6d376ee](https://github.com/exadel-inc/esl/commit/6d376ee2a6a77ddff4827cda454b61b60a690d8a))
-* **esl-media:** remove legacy features of `esl-media` ([c19561d](https://github.com/exadel-inc/esl/commit/c19561d8b04e95ab9525d7ca2dee2ced261b94a8))
-* exclude duplicated exports of `esl-utils/decorators` ([f6c84af](https://github.com/exadel-inc/esl/commit/f6c84afe00558346994d4220db69ac102ccba817))
-
-
-### Features
-
-* drop deprecated aliases ([#1505](https://github.com/exadel-inc/esl/issues/1505)) ([df7f5e5](https://github.com/exadel-inc/esl/commit/df7f5e5c6a3fa62ad559bd93f4c56095def64a0c))
-
-
-### BREAKING CHANGES
-
-* **esl-media:** `load-cls-target`, `load-cls-accepted` and `load-cls-declined` use `load-condition-class` and `load-condition-class-target` instead
-* **esl-media:** `disabled` no longer supported use `lazy="manual"` instead
-* `prop`, `attr`, `boolAttr`, `jsonAttr`, `listen` no longer available in `esl-base-element` and `esl-mixin-element` exports
-* **esl-utils:** `createZIndexIframe` and `is-fixes` module no longer available due to drop of IE11 support
-* **esl-utils:** `hasEventListener` no longer accepts min number value use `this.getEventListeners(type).length` to make extended checks
-* **esl-utils:** `dispatchEvent` does not accepts target argument
-* `ESLEventUtils.descriptors` alias of `ESLEventUtils.getAutoDescriptors` is no longer supported
-* `EventUtils` alias of `ESLEventUtils` is no longer supported
-* `esl-media-quey` module no longer supports `addListener` and `removeListener` shorthand
-* `SynteticEventTarget` no longer supports `addListener` and `removeListener` shorthand
-* 'fallback-duration' is no longer in the JSX shape of ESLPanel and ESLPanelGroup
-* `ToggleableActionParams` alias of `ESLToggleableActionParams` is no longer supported
-* `TraversingQuery` alias of `ESLTraversingQuery` is no longer supported
-* `RTLUtils` retired use separate methods instead
-* `TraversingUtils` retired use separate methods instead
-* `deepCompare` alias of `isEqual` is no longer supported
-* `generateUId` alias of `randUID` no longer supported
-* ESL UI site renderer and ESL polyfills no longer support Edge old versions and ES6 polyfils.
-* ESL UI site renderer and ESL polyfills no longer support IE11 and ES5 target.
-
-# [5.0.0-beta.2](https://github.com/exadel-inc/esl/compare/v5.0.0-beta.1...v5.0.0-beta.2) (2023-12-19)
-
-
-### Bug Fixes
-
-* **eslint:** fix peerDependency constraint ([20b5d9d](https://github.com/exadel-inc/esl/commit/20b5d9d2c8a1b1e51288524024dfe6c17b665671))
-
-# [5.0.0-beta.1](https://github.com/exadel-inc/esl/compare/v4.14.1...v5.0.0-beta.1) (2023-12-19)
-
-
-### Bug Fixes
-
-* **esl-event-listener:** fix delegation recognition on non-DOM targets ([4716947](https://github.com/exadel-inc/esl/commit/47169479b46994e4fd1571a8cefe0454c983a89b))
-* **esl-event-listener:** fix missing host in debug information of empty subscription ([4abfa1e](https://github.com/exadel-inc/esl/commit/4abfa1e12346662616cc7ac431bb9e0161fe0bf0)), closes [#2078](https://github.com/exadel-inc/esl/issues/2078)
-* **esl-event-listener:** improve compatibility of decorated event handler type ([de0c37e](https://github.com/exadel-inc/esl/commit/de0c37e2a399c2e88c16b10c2baaec662538e9a8))
-* **esl-panel-group:** `has-opened` attribute inconsistent ([13003e4](https://github.com/exadel-inc/esl/commit/13003e4b8d5bb039b7ae142739ea8a4667af4e2a))
-* **esl-popup:** create optional autofocus property; update flow with optimized rendering flow ([8e71d20](https://github.com/exadel-inc/esl/commit/8e71d208368f9208b167900211399d138ee7eaa6))
-* **esl-share:** fix focus behavior to a loop inside esl-share-popup ([e3393fe](https://github.com/exadel-inc/esl/commit/e3393fe6641497af149a6f9df5c87e98e7d637c4))
-* **esl-share:** fix share() method signature of print action ([ca7091d](https://github.com/exadel-inc/esl/commit/ca7091df6c6f361e4b8f3e2b328b08a9fb51044e))
-* **esl-toggleable:** rework actions pre-checks to fix flow on reopening toggleable ([c023e55](https://github.com/exadel-inc/esl/commit/c023e55da877a03e2378e6685d86839bcecb7dd1))
-* **esl-tooltip:** add constraints to fix DOM position management for `esl-tooltip` ([9c25137](https://github.com/exadel-inc/esl/commit/9c25137217b93e3ef2c22d5898d550eca0160c2f))
-* **esl-trigger:** default target for `esl-trigger` is not logically correct and obvious ([db4ffb8](https://github.com/exadel-inc/esl/commit/db4ffb8de4efb569587e56a978d8a23bb4671b50))
-
-
-### Code Refactoring
-
-* **esl-toggleable:** move `open` state updating to `onShow`/`onHide` callbacks ([b184eb1](https://github.com/exadel-inc/esl/commit/b184eb185c6816412c81a3a085e2cac276968165))
-
-
-### Features
-
-* **esl-event-listener:** `ESLWheelTarget` proxy target created to handle long wheel user actions ([#2031](https://github.com/exadel-inc/esl/issues/2031)) ([4dc4bfc](https://github.com/exadel-inc/esl/commit/4dc4bfcd3f2f16f9695b9fd468d3b5a6c587c8e8)), closes [#2017](https://github.com/exadel-inc/esl/issues/2017)
-* **esl-open-state:** create ESL Open State mixin ([a8327a8](https://github.com/exadel-inc/esl/commit/a8327a8671bde9544c05cb942589cf80415b84d8))
-* **esl-popup:** add support of `PopupActionParams.extraClass` on popup level ([32b6d0a](https://github.com/exadel-inc/esl/commit/32b6d0a62a10b9009ac1352c9f68b2e7128ec5b0))
-* **esl-popup:** add support of `PopupActionParams.extraStyle` ([399685b](https://github.com/exadel-inc/esl/commit/399685ba69e93a517ac9ec1d081c5926f51bd34b))
-* **esl-share:** add ability to update config items (single one or in batch) ([96eac6b](https://github.com/exadel-inc/esl/commit/96eac6b1ad93d50bf16d76c9f0716d570c625bd9))
-* **esl-share:** add OOTB configuration for the Skype share button ([c3a76c4](https://github.com/exadel-inc/esl/commit/c3a76c490288c61fb1f198d8792c407c350055c8))
-* **esl-share:** add OOTB configuration for the Tumblr share button ([bb73186](https://github.com/exadel-inc/esl/commit/bb731864a0448853f2bb8d96f978b5f8e4b6ab65))
-* **esl-share:** add OOTB configuration for the Viber share button ([5dd4ea4](https://github.com/exadel-inc/esl/commit/5dd4ea41a4700722d97cecdcf40193a5b7f01bd2))
-* **esl-share:** add OOTB configuration for the WhatsApp share button ([748117f](https://github.com/exadel-inc/esl/commit/748117f199fcebd596a2079eea03ca5a14f7fe3e))
-* **esl-utils:** improve `rect` utility (it's almost ready to be immutable) ([f82fb1e](https://github.com/exadel-inc/esl/commit/f82fb1eed5ca06bff3718ce1ac1c86c2769fb2f3))
-
-
-### BREAKING CHANGES
-
-* **esl-toggleable:** `this.open` of Toggleable`s does not updating util `super.onShow` / `super.onHide` called.
-Make sure you update `this.open` synchronously or manually notify consumers in case the super call of `onShow`/`onHide` should be postponed.
-* **esl-trigger:** `esl-trigger` does not have target defined to `::next` by default
-* **esl-toggleable:** `onBeforeShow` and `onBeforeHide` have retired. The constraint now inside `shouldShow`/`shouldHide` methods,
-activator change now is the part of main togglable flow
-
 ## [4.14.1](https://github.com/exadel-inc/esl/compare/v4.14.0...v4.14.1) (2023-11-27)
 
 
diff --git a/README.md b/README.md
index 48b10cde0..02a9d9a21 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@ to meet your project needs.
 
 ESL is based solely on Web API and ECMAScript 6 features, which let our web UI components library work 
 well across all modern browsers such as Firefox, Chrome, Safari, Opera, and Edge. 
+ESL can also work on IE11 when the whole list of polyfills is provided (see [Browser support & Polyfills](https://github.com/exadel-inc/esl/blob/HEAD/docs/BROWSER_SUPPORT.md) for more details).
 
 ## Library Structure
 
diff --git a/docs/BROWSER_SUPPORT.md b/docs/BROWSER_SUPPORT.md
index 06e615e40..b1c998b86 100644
--- a/docs/BROWSER_SUPPORT.md
+++ b/docs/BROWSER_SUPPORT.md
@@ -6,11 +6,17 @@ Exadel Smart Library does not have dependencies but uses the following list of n
 
 - ECMAScript 6 features
     - [Array.from](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from) (no Iterable Objects support required)
+      ([ESL polyfill](../src/polyfills/list/es6.array.from.ts) provided)
     - [Array.prototype.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
+      ([ESL polyfill](../src/polyfills/list/es6.array.find.ts) provided)
     - [Array.prototype.findIndex](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex)
+      ([ESL polyfill](../src/polyfills/list/es6.array.find.ts) provided)
     - [Array.prototype.include](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)
+      ([ESL polyfill](../src/polyfills/list/es6.array.includes.ts) provided)
     - [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)
+      ([ESL polyfill](../src/polyfills/list/es6.object.is.ts) provided)
     - [Object.assign](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) (accurate polyfill required)
+      ([ESL polyfill](../src/polyfills/list/es6.object.assign.ts) provided)
     - [ES6 Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)
 - Web API
     - [Event](https://developer.mozilla.org/en-US/docs/Web/API/Event) constructor + `preventDefault` polyfill
@@ -24,6 +30,25 @@ Exadel Smart Library does not have dependencies but uses the following list of n
     - [ResizeObserver](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver)
     - [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements)
 
-All of them are fully supported by modern browsers such as Chrome, Firefox, Safari (>13) or Edge (>43).
+All of them are fully supported by modern browsers such as Chrome, Firefox, Safari or Edge (>43).
 
-In order to make ESL work in older browsers, you can use polyfill (for older versions of Safari).
+In order to make ESL work in older browsers, you can use a "light" polyfills list of IntersectionObserver, ResizeObserver and Custom Elements (for older versions of Edge and Safari).
+
+Or make the library work in IE11 or Edge (<14) by using the "full" polyfills list provided.
+
+See more details on what polyfill approach might look like in the demo pages source code.
+
+Also, ESL has built-in polyfills for some of DOM and ES6 features. They are available under [polyfills](../src/polyfills) directory:
+  - ECMA Script 5: output shim (`HTMLElement` constructor call) - [es5-target-shim.ts](../src/polyfills/es5-target-shim.ts)
+  - ECMA Script 5: IE11, Edge <14 support - [polyfills.es5.ts](../src/polyfills/polyfills.es5.ts)
+    - `Object.is`, `Object.assign` 
+    - `Array.from`, `Array.prototype.find`, `Array.prototype.findIndex`, `Array.prototype.index`
+    - `Event`, `CustomEvent`, `MouseEvent`, `KeyboardEvent`, `FocusEvent` shim
+    - `Event.prototype.preventDefault` shim
+    - `Element.prototype.closest`
+    - `DOMTokenList.prototype.toggle`
+    - *Note: there is no promise polyfill embedded*, use npm:[promise-polyfill](https://www.npmjs.com/package/promise-polyfill) or similar
+  - ECMA Script 6: DOM - [polyfills.es6.ts](../src/polyfills/polyfills.es6.ts)
+    - `Node.isConnected` 
+    - `KeyboardKey.prototype.key` 
+    - `Element.prototype.toggleAttribute` 
diff --git a/eslint/package.json b/eslint/package.json
index 76e6d7f54..bbc042264 100644
--- a/eslint/package.json
+++ b/eslint/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@exadel/eslint-plugin-esl",
-  "version": "5.0.0-beta.3",
+  "version": "4.14.1",
   "keywords": [
     "eslint",
     "eslint-plugin",
@@ -9,10 +9,6 @@
   "license": "MIT",
   "description": "Helper ESLint rules to find and migrate ESL (@exadel/esl) library deprecations",
   "main": "dist/index.js",
-  "types": "dist/index.d.ts",
-  "files": [
-    "dist/**/*"
-  ],
   "scripts": {
     "prepare": "npm run build",
     "test": "jest",
@@ -20,8 +16,12 @@
     "build": "npm run clean && tsc --project tsconfig.build.json"
   },
   "dependencies": {},
+  "files": [
+    "dist/**/*"
+  ],
+  "types": "dist/index.d.ts",
   "peerDependencies": {
-    "@exadel/esl": "*",
+    "@exadel/esl": "^4.0.0",
     "eslint": ">=8.0.0"
   }
 }
diff --git a/eslint/src/index.ts b/eslint/src/index.ts
index de3ed69f3..4619ef794 100644
--- a/eslint/src/index.ts
+++ b/eslint/src/index.ts
@@ -1,10 +1,29 @@
-import DEPRECATED_4_RULES from './rules/4/all.rules';
-import DEPRECATED_5_RULES from './rules/5/all.rules';
+import deprecatedAlertActionParams from './rules/4/deprecated.alert-action-params';
+import deprecatedGenerateUid from './rules/4/deprecated.generate-uid';
+import deprecatedDeepCompare from './rules/4/deprecated.deep-compare';
+import deprecatedEventUtils from './rules/4/deprecated.event-utils';
+import deprecatedPanelActionParams from './rules/4/deprecated.panel-action-params';
+import deprecatedPopupActionParams from './rules/4/deprecated.popup-action-params';
+import deprecatedTraversingQuery from './rules/4/deprecated.traversing-query';
+import deprecatedToggleableActionParams from './rules/4/deprecated.toggleable-action-params';
+import deprecatedTooltipActionParams from './rules/4/deprecated.tooltip-action-params';
 
 import type {Rule} from 'eslint';
 
 export type logLevel = 'warn' | 'error';
 
+const DEPRECATED_4_RULES = {
+  'deprecated-4/alert-action-params': deprecatedAlertActionParams,
+  'deprecated-4/generate-uid': deprecatedGenerateUid,
+  'deprecated-4/deep-compare': deprecatedDeepCompare,
+  'deprecated-4/event-utils': deprecatedEventUtils,
+  'deprecated-4/panel-action-params': deprecatedPanelActionParams,
+  'deprecated-4/popup-action-params': deprecatedPopupActionParams,
+  'deprecated-4/traversing-query': deprecatedTraversingQuery,
+  'deprecated-4/toggleable-action-params': deprecatedToggleableActionParams,
+  'deprecated-4/tooltip-action-params': deprecatedTooltipActionParams
+};
+
 const buildDefault = (definition: Record<string, Rule.RuleModule>, level: logLevel): Record<string, logLevel> => {
   const config: Record<string, logLevel> = {};
   for (const name of Object.keys(definition)) {
@@ -14,7 +33,7 @@ const buildDefault = (definition: Record<string, Rule.RuleModule>, level: logLev
   return config;
 };
 
-export const rules = Object.assign({}, DEPRECATED_4_RULES, DEPRECATED_5_RULES);
+export const rules = Object.assign({}, DEPRECATED_4_RULES);
 
 export const configs = {
   'default-4': {
@@ -24,12 +43,7 @@ export const configs = {
   },
   'default-5': {
     rules: {
-      ...buildDefault(DEPRECATED_5_RULES, 'warn')
-    }
-  },
-  'default': {
-    rules: {
-      ...buildDefault(DEPRECATED_5_RULES, 'warn')
+      ...buildDefault(DEPRECATED_4_RULES, 'error')
     }
   }
 };
diff --git a/eslint/src/rules/4/all.rules.ts b/eslint/src/rules/4/all.rules.ts
deleted file mode 100644
index f9ff509c4..000000000
--- a/eslint/src/rules/4/all.rules.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import deprecatedGenerateUid from './deprecated.generate-uid';
-import deprecatedDeepCompare from './deprecated.deep-compare';
-import deprecatedEventUtils from './deprecated.event-utils';
-import deprecatedTraversingQuery from './deprecated.traversing-query';
-import deprecatedToggleableActionParams from './deprecated.toggleable-action-params';
-
-export default {
-  'deprecated-4/generate-uid': deprecatedGenerateUid,
-  'deprecated-4/deep-compare': deprecatedDeepCompare,
-  'deprecated-4/event-utils': deprecatedEventUtils,
-  'deprecated-4/traversing-query': deprecatedTraversingQuery,
-  'deprecated-4/toggleable-action-params': deprecatedToggleableActionParams
-};
diff --git a/eslint/src/rules/5/deprecated.alert-action-params.ts b/eslint/src/rules/4/deprecated.alert-action-params.ts
similarity index 100%
rename from eslint/src/rules/5/deprecated.alert-action-params.ts
rename to eslint/src/rules/4/deprecated.alert-action-params.ts
diff --git a/eslint/src/rules/5/deprecated.panel-action-params.ts b/eslint/src/rules/4/deprecated.panel-action-params.ts
similarity index 100%
rename from eslint/src/rules/5/deprecated.panel-action-params.ts
rename to eslint/src/rules/4/deprecated.panel-action-params.ts
diff --git a/eslint/src/rules/5/deprecated.popup-action-params.ts b/eslint/src/rules/4/deprecated.popup-action-params.ts
similarity index 100%
rename from eslint/src/rules/5/deprecated.popup-action-params.ts
rename to eslint/src/rules/4/deprecated.popup-action-params.ts
diff --git a/eslint/src/rules/5/deprecated.tooltip-action-params.ts b/eslint/src/rules/4/deprecated.tooltip-action-params.ts
similarity index 100%
rename from eslint/src/rules/5/deprecated.tooltip-action-params.ts
rename to eslint/src/rules/4/deprecated.tooltip-action-params.ts
diff --git a/eslint/src/rules/5/all.rules.ts b/eslint/src/rules/5/all.rules.ts
deleted file mode 100644
index bc1070cf7..000000000
--- a/eslint/src/rules/5/all.rules.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import deprecatedAlertActionParams from './deprecated.alert-action-params';
-import deprecatedPanelActionParams from './deprecated.panel-action-params';
-import deprecatedPopupActionParams from './deprecated.popup-action-params';
-import deprecatedTooltipActionParams from './deprecated.tooltip-action-params';
-
-export default {
-  'deprecated-5/alert-action-params': deprecatedAlertActionParams,
-  'deprecated-5/panel-action-params': deprecatedPanelActionParams,
-  'deprecated-5/popup-action-params': deprecatedPopupActionParams,
-  'deprecated-5/tooltip-action-params': deprecatedTooltipActionParams
-};
diff --git a/package-lock.json b/package-lock.json
index 48d30f7b2..ca12b4cd2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@exadel/esl",
-  "version": "5.0.0-beta.3",
+  "version": "4.14.1",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "@exadel/esl",
-      "version": "5.0.0-beta.3",
+      "version": "4.14.1",
       "license": "MIT",
       "workspaces": [
         "site",
@@ -59,10 +59,10 @@
     },
     "eslint": {
       "name": "@exadel/eslint-plugin-esl",
-      "version": "5.0.0-beta.3",
+      "version": "4.14.1",
       "license": "MIT",
       "peerDependencies": {
-        "@exadel/esl": "*",
+        "@exadel/esl": "^4.0.0",
         "eslint": ">=8.0.0"
       }
     },
@@ -3447,6 +3447,11 @@
         "@xtuc/long": "4.2.2"
       }
     },
+    "node_modules/@webcomponents/custom-elements": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/@webcomponents/custom-elements/-/custom-elements-1.6.0.tgz",
+      "integrity": "sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww=="
+    },
     "node_modules/@webpack-cli/configtest": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
@@ -3818,11 +3823,11 @@
       }
     },
     "node_modules/arrify": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
-      "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+      "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
       "engines": {
-        "node": ">=8"
+        "node": ">=0.10.0"
       }
     },
     "node_modules/asap": {
@@ -8287,6 +8292,11 @@
         "node": ">=10.13.0"
       }
     },
+    "node_modules/intersection-observer": {
+      "version": "0.12.2",
+      "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz",
+      "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg=="
+    },
     "node_modules/into-stream": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz",
@@ -10429,15 +10439,6 @@
         "node": ">=4"
       }
     },
-    "node_modules/load-json-file/node_modules/pify": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
-      "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
-      "dev": true,
-      "engines": {
-        "node": ">=4"
-      }
-    },
     "node_modules/load-json-file/node_modules/strip-bom": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -10898,14 +10899,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/maximatch/node_modules/arrify": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
-      "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/maximatch/node_modules/brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -11074,31 +11067,15 @@
         "node": ">= 6"
       }
     },
-    "node_modules/minimist-options/node_modules/arrify": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
-      "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.10.0"
-      }
-    },
     "node_modules/minipass": {
-      "version": "3.3.6",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
-      "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
-      "dependencies": {
-        "yallist": "^4.0.0"
-      },
+      "version": "7.0.4",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
+      "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
+      "dev": true,
       "engines": {
-        "node": ">=8"
+        "node": ">=16 || 14 >=14.17"
       }
     },
-    "node_modules/minipass/node_modules/yallist": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
-      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
-    },
     "node_modules/mkdirp": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
@@ -11152,6 +11129,14 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/multimatch/node_modules/arrify": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
+      "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/multimatch/node_modules/brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -14846,15 +14831,6 @@
         "node": "14 || >=16.14"
       }
     },
-    "node_modules/path-scurry/node_modules/minipass": {
-      "version": "7.0.4",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
-      "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=16 || 14 >=14.17"
-      }
-    },
     "node_modules/path-to-regexp": {
       "version": "6.2.1",
       "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
@@ -14898,11 +14874,12 @@
       }
     },
     "node_modules/pify": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-      "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+      "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==",
+      "dev": true,
       "engines": {
-        "node": ">=0.10.0"
+        "node": ">=4"
       }
     },
     "node_modules/pirates": {
@@ -15291,6 +15268,11 @@
         "any-promise": "^0.1.0"
       }
     },
+    "node_modules/promise-polyfill": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz",
+      "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg=="
+    },
     "node_modules/prompts": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -15472,14 +15454,6 @@
       "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
       "dev": true
     },
-    "node_modules/queue": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
-      "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
-      "dependencies": {
-        "inherits": "~2.0.3"
-      }
-    },
     "node_modules/queue-microtask": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -15688,6 +15662,14 @@
         "mkdirp": "bin/cmd.js"
       }
     },
+    "node_modules/recursive-copy/node_modules/pify": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+      "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/recursive-copy/node_modules/rimraf": {
       "version": "2.7.1",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
@@ -15947,15 +15929,6 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
-    "node_modules/rimraf/node_modules/minipass": {
-      "version": "7.0.4",
-      "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
-      "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=16 || 14 >=14.17"
-      }
-    },
     "node_modules/run-parallel": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -16823,6 +16796,22 @@
         "node": ">= 8"
       }
     },
+    "node_modules/ssri/node_modules/minipass": {
+      "version": "3.3.6",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+      "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+      "dependencies": {
+        "yallist": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ssri/node_modules/yallist": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+      "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+    },
     "node_modules/stack-utils": {
       "version": "2.0.6",
       "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
@@ -18849,7 +18838,7 @@
     },
     "site": {
       "name": "@exadel/esl-website",
-      "version": "5.0.0-beta.3",
+      "version": "4.14.1",
       "license": "MIT",
       "dependencies": {
         "@11ty/eleventy": "^2.0.1",
@@ -18859,12 +18848,15 @@
         "@juggle/resize-observer": "^3.4.0",
         "@types/prismjs": "^1.26.3",
         "@types/smoothscroll-polyfill": "^0.3.3",
+        "@webcomponents/custom-elements": "1.6.0",
         "clean-css-cli": "^5.6.3",
         "html-minifier-terser": "^7.2.0",
         "image-size": "^1.1.1",
+        "intersection-observer": "^0.12.2",
         "js-yaml": "^4.1.0",
         "out-url": "^1.1.3",
         "prismjs": "^1.29.0",
+        "promise-polyfill": "^8.3.0",
         "smoothscroll-polyfill": "^0.4.4",
         "ts-loader": "^9.5.1",
         "webpack": "^5.89.0",
@@ -18874,8 +18866,7 @@
     },
     "site/node_modules/image-size": {
       "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz",
-      "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==",
+      "license": "MIT",
       "dependencies": {
         "queue": "6.0.2"
       },
diff --git a/package.json b/package.json
index 1e34c2216..ce6f58ba0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@exadel/esl",
-  "version": "5.0.0-beta.3",
+  "version": "4.14.1",
   "private": false,
   "description": "Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components",
   "license": "MIT",
diff --git a/site/package.json b/site/package.json
index 3ed49ea71..79f3cc2cf 100644
--- a/site/package.json
+++ b/site/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@exadel/esl-website",
-  "version": "5.0.0-beta.3",
+  "version": "4.14.1",
   "private": true,
   "description": "ESL Website source",
   "homepage": "https://esl-ui.com/",
@@ -28,12 +28,15 @@
     "@juggle/resize-observer": "^3.4.0",
     "@types/prismjs": "^1.26.3",
     "@types/smoothscroll-polyfill": "^0.3.3",
+    "@webcomponents/custom-elements": "1.6.0",
     "clean-css-cli": "^5.6.3",
     "html-minifier-terser": "^7.2.0",
     "image-size": "^1.1.1",
+    "intersection-observer": "^0.12.2",
     "js-yaml": "^4.1.0",
     "out-url": "^1.1.3",
     "prismjs": "^1.29.0",
+    "promise-polyfill": "^8.3.0",
     "smoothscroll-polyfill": "^0.4.4",
     "ts-loader": "^9.5.1",
     "webpack": "^5.89.0",
diff --git a/site/src/landing/marquee/marquee.ts b/site/src/landing/marquee/marquee.ts
index 2cd4ef640..3c705082f 100644
--- a/site/src/landing/marquee/marquee.ts
+++ b/site/src/landing/marquee/marquee.ts
@@ -1,6 +1,7 @@
 import {ESLBaseElement} from '@exadel/esl/modules/esl-base-element/core';
 import {bind, ready, memoize, attr} from '@exadel/esl/modules/esl-utils/decorators';
 import {range} from '@exadel/esl/modules/esl-utils/misc/array';
+import {isIE} from '@exadel/esl/modules/esl-utils/environment/device-detector';
 
 export class ESLDemoMarquee extends ESLBaseElement {
   static override is = 'esl-d-marquee';
@@ -15,6 +16,7 @@ export class ESLDemoMarquee extends ESLBaseElement {
   @ready
   protected override connectedCallback(): void {
     super.connectedCallback();
+    if (isIE) return;
     this.startAnimation();
   }
   protected override disconnectedCallback(): void {
diff --git a/site/src/localdev.ts b/site/src/localdev.ts
index e54915c20..fc8728f6c 100644
--- a/site/src/localdev.ts
+++ b/site/src/localdev.ts
@@ -1,5 +1,7 @@
 // Support for ES5 bundle target
 import '@exadel/esl/polyfills/es5-target-shim';
+// Builtin polyfills
+import '@exadel/esl/polyfills/polyfills.es6';
 // Validate environment
 import '@exadel/esl/polyfills/polyfills.validate';
 
diff --git a/site/src/polyfill-full.ts b/site/src/polyfill-full.ts
new file mode 100644
index 000000000..91d4245c2
--- /dev/null
+++ b/site/src/polyfill-full.ts
@@ -0,0 +1,8 @@
+/**
+ * Target: IE11, Edge\<14
+ */
+import 'promise-polyfill/dist/polyfill';
+
+import '@exadel/esl/polyfills/polyfills.es5';
+
+import './polyfill-medium';
diff --git a/site/src/polyfill.ts b/site/src/polyfill-light.ts
similarity index 67%
rename from site/src/polyfill.ts
rename to site/src/polyfill-light.ts
index b76c7eac6..22f4bb69d 100644
--- a/site/src/polyfill.ts
+++ b/site/src/polyfill-light.ts
@@ -1,4 +1,9 @@
-/** Target: Safari <13.3 */
+/**
+ * Target: Edge, Safari 9
+ */
+import '@exadel/esl/polyfills/polyfills.es6';
+
+// IE, Safari <13.3
 import {ResizeObserver} from '@juggle/resize-observer';
 window.ResizeObserver = window.ResizeObserver || ResizeObserver;
 
diff --git a/site/src/polyfill-medium.ts b/site/src/polyfill-medium.ts
new file mode 100644
index 000000000..a75b910c6
--- /dev/null
+++ b/site/src/polyfill-medium.ts
@@ -0,0 +1,10 @@
+/**
+ * Target: iOS, Safari 13.0.0
+ */
+// IE, Edge < 18
+import '@webcomponents/custom-elements/custom-elements.min';
+
+// IE, Safari <12, Edge < 14
+import 'intersection-observer/intersection-observer';
+
+import './polyfill-light';
diff --git a/site/views/_includes/polyfill.njk b/site/views/_includes/polyfill.njk
index 6f5a8bb73..c01d5dc6a 100644
--- a/site/views/_includes/polyfill.njk
+++ b/site/views/_includes/polyfill.njk
@@ -1,7 +1,13 @@
 <script>
   (function () {
-    if (!window.scrollBy || !window.ResizeObserver) {
-      document.write('<script type="text/javascript" src="{{ '/bundles/polyfill.js' | url }}"><\/script>');
+    var version = '';
+    if (!window.Promise || !window.CustomEvent){
+      version = 'full';
+    } else if (!('getRootNode' in Element.prototype) || !window.customElements){
+      version = 'medium';
+    } else if (!window.scrollBy || !window.IntersectionObserver || !window.ResizeObserver || !window.ResizeObserver) {
+      version = 'light';
     }
+    (version) && document.write('<script type="text/javascript" src="{{ '/bundles/polyfill-' | url }}' + version + '.js"><\/script>');
   })();
 </script>
diff --git a/site/views/blogs/ESL v5.0.0 Announce.md b/site/views/blogs/ESL v5.0.0 Announce.md
new file mode 100644
index 000000000..2b1681e00
--- /dev/null
+++ b/site/views/blogs/ESL v5.0.0 Announce.md
@@ -0,0 +1,18 @@
+---
+layout: content
+name: ESL v5.0.0 Announce
+title: Our plans for July
+tags: [news, draft]
+date: 2023-07-01
+containerCls: container markdown-container
+---
+
+ESL is going to have a lot of interesting updates soon:
+- `ESLAnimate` will be finally available as a stable module under `4.9.0`
+- `ESLShare` is going to have final updates, `popup` version will be available in stable `4.9.0` and going to be accepted as 
+  a stale module in `5.0.0`. 
+- `ESLCarousel` module is already available under the beta channel and in the process of testing, refactoring, and going
+  to be a stable version soon.
+- `ESLModal` is going to be available for preview in the nearest beta releases
+- First ESL 5.0.0 versions are going to be available on beta branch soon: 
+  IE11 support dropped, event API improvements, styles distribution improvements. 
diff --git a/site/webpack.config.js b/site/webpack.config.js
index 6aa48e4ba..95f32f83f 100644
--- a/site/webpack.config.js
+++ b/site/webpack.config.js
@@ -5,7 +5,9 @@ module.exports = {
   devtool: 'source-map',
   entry: {
     'localdev': './src/localdev.ts',
-    'polyfill': './src/polyfill.ts'
+    'polyfill-full': './src/polyfill-full.ts',
+    'polyfill-medium': './src/polyfill-medium.ts',
+    'polyfill-light': './src/polyfill-light.ts'
   },
   resolve: {
     modules: ['../node_modules'],
diff --git a/src/modules/all.ts b/src/modules/all.ts
index dca9c88c9..adc75b78d 100644
--- a/src/modules/all.ts
+++ b/src/modules/all.ts
@@ -1,7 +1,6 @@
 // Core
-export * from './esl-base-element/core';
-export * from './esl-mixin-element/core';
-
+export {ESLBaseElement, ESLBaseElementShape} from './esl-base-element/core';
+export {ESLMixinElement} from './esl-mixin-element/core';
 export * from './esl-utils/all';
 export * from './esl-media-query/core';
 export * from './esl-traversing-query/core';
diff --git a/src/modules/esl-alert/core/esl-alert.less b/src/modules/esl-alert/core/esl-alert.less
index 36ba5a584..20e51a89e 100644
--- a/src/modules/esl-alert/core/esl-alert.less
+++ b/src/modules/esl-alert/core/esl-alert.less
@@ -1,3 +1,5 @@
+@import '../../esl-utils/fixes/ie-fixes';
+
 esl-alert {
   display: flex;
   justify-content: center;
diff --git a/src/modules/esl-alert/core/esl-alert.ts b/src/modules/esl-alert/core/esl-alert.ts
index 3412e07de..559145b07 100644
--- a/src/modules/esl-alert/core/esl-alert.ts
+++ b/src/modules/esl-alert/core/esl-alert.ts
@@ -2,7 +2,9 @@ import {ExportNs} from '../../esl-utils/environment/export-ns';
 import {attr, jsonAttr, prop, listen} from '../../esl-utils/decorators';
 import {isMatches} from '../../esl-utils/dom/traversing';
 import {ESLToggleable} from '../../esl-toggleable/core';
+import {DeviceDetector} from '../../esl-utils/environment/device-detector';
 import {CSSClassUtils} from '../../esl-utils/dom/class';
+import {createZIndexIframe} from '../../esl-utils/fixes/ie-fixes';
 import {ESLTraversingQuery} from '../../esl-traversing-query/core';
 
 import type {ESLToggleableActionParams, ESLToggleableRequestDetails} from '../../esl-toggleable/core';
@@ -94,6 +96,7 @@ export class ESLAlert extends ESLToggleable {
     this.$content.className = 'esl-alert-content';
     this.innerHTML = '';
     this.appendChild(this.$content);
+    if (DeviceDetector.isIE) this.appendChild(createZIndexIframe());
     if (this.target) {
       this.$target = ESLTraversingQuery.first(this.target, this) as EventTarget;
     }
diff --git a/src/modules/esl-base-element/core.ts b/src/modules/esl-base-element/core.ts
index 4614966ec..dabbc5d7c 100644
--- a/src/modules/esl-base-element/core.ts
+++ b/src/modules/esl-base-element/core.ts
@@ -1,3 +1,10 @@
 export type {ESLBaseElementShape} from './core/esl-base-element.shape';
 
 export * from './core/esl-base-element';
+
+export * from '../esl-utils/decorators/prop';
+export * from '../esl-utils/decorators/attr';
+export * from '../esl-utils/decorators/bool-attr';
+export * from '../esl-utils/decorators/json-attr';
+
+export * from '../esl-utils/decorators/listen';
diff --git a/src/modules/esl-event-listener/README.md b/src/modules/esl-event-listener/README.md
index 826024b7d..a105a798c 100644
--- a/src/modules/esl-event-listener/README.md
+++ b/src/modules/esl-event-listener/README.md
@@ -308,6 +308,12 @@ ESLEventUtils.descriptors(host?: any): ESLListenerDescriptorFn[]
 
 - `host` - object to get auto-collectable descriptors from;
 
+<a name="-esleventutilsdescriptors"></a>
+
+### ⚡ `ESLEventUtils.descriptors`
+
+Deprecated alias for `ESLEventUtils.getAutoDescriptors`
+
 <a name="-esleventutilsinitdescriptor"></a>
 
 ### ⚡ `ESLEventUtils.initDescriptor`
diff --git a/src/modules/esl-event-listener/core/api.ts b/src/modules/esl-event-listener/core/api.ts
index 617b3edc2..963afa600 100644
--- a/src/modules/esl-event-listener/core/api.ts
+++ b/src/modules/esl-event-listener/core/api.ts
@@ -23,6 +23,9 @@ export class ESLEventUtils {
    */
   public static dispatch = dispatchCustomEvent;
 
+  /** @deprecated alias for {@link getAutoDescriptors} */
+  public static descriptors = getAutoDescriptors;
+
   /** Gets {@link ESLListenerDescriptorFn}s of the passed object */
   public static getAutoDescriptors = getAutoDescriptors;
 
@@ -85,7 +88,11 @@ export class ESLEventUtils {
   ): ESLEventListener[] {
     if (arguments.length === 1) {
       const descriptors = getAutoDescriptors(host);
-      return descriptors.flatMap((desc) => ESLEventUtils.subscribe(host, desc));
+      // TODO: flatMap when ES5 will be out of support list
+      return descriptors.reduce(
+        (acc, desc) => acc.concat(ESLEventUtils.subscribe(host, desc)),
+        [] as ESLEventListener[]
+      );
     }
     const desc = typeof eventDesc === 'string' ? {event: eventDesc} : eventDesc as ESLListenerDescriptor;
     if (Object.hasOwnProperty.call(desc, 'condition') && !resolveProperty(desc.condition, host)) return [];
@@ -107,3 +114,6 @@ function emptySubscriptionWarning(host: object, descriptor: ESLListenerDescripto
   const target = resolveProperty(descriptor.target, host);
   console.warn('[ESL]: Empty subscription %o', {host, descriptor, handler, event, target});
 }
+
+/** @deprecated alias for {@link ESLEventUtils} */
+export const EventUtils = ESLEventUtils;
diff --git a/src/modules/esl-event-listener/core/listener.ts b/src/modules/esl-event-listener/core/listener.ts
index fd3f69a39..17bff4ac3 100644
--- a/src/modules/esl-event-listener/core/listener.ts
+++ b/src/modules/esl-event-listener/core/listener.ts
@@ -2,7 +2,7 @@ import {wrap} from '../../esl-utils/misc/array';
 import {isElement} from '../../esl-utils/dom/api';
 import {isPassiveByDefault} from '../../esl-utils/dom/events/misc';
 import {resolveProperty} from '../../esl-utils/misc/functions';
-import {isObject, isObjectLike, isSimilar} from '../../esl-utils/misc/object';
+import {isObject, isSimilar} from '../../esl-utils/misc/object';
 import {resolveDomTarget} from '../../esl-utils/abstract/dom-target';
 import {memoize} from '../../esl-utils/decorators/memoize';
 import {ESLTraversingQuery} from '../../esl-traversing-query/core';
@@ -149,14 +149,14 @@ export class ESLEventListener implements ESLListenerDefinition, EventListenerObj
    * Supports additional filtration criteria
    */
   public static get(host?: object, ...criteria: ESLListenerCriteria[]): ESLEventListener[] {
-    if (!isObjectLike(host)) return [];
+    if (!isObject(host)) return [];
     const listeners = ((host as any)[LISTENERS] || []) as ESLEventListener[];
     if (!criteria.length) return listeners;
     return listeners.filter((listener) => criteria.every(listener.matches, listener));
   }
   /** Adds a listener to the listener store of the host object */
   protected static add(host: object, instance: ESLEventListener): void {
-    if (!isObjectLike(host)) return;
+    if (!isObject(host)) return;
     if (!Object.hasOwnProperty.call(host, LISTENERS)) (host as any)[LISTENERS] = [];
     (host as any)[LISTENERS].push(instance);
   }
@@ -201,7 +201,7 @@ export class ESLEventListener implements ESLListenerDefinition, EventListenerObj
     handler: ESLListenerHandler,
     desc: ESLListenerDescriptor
   ): ESLEventListener[] {
-    if (!isObjectLike(host)) return [];
+    if (!isObject(host)) return [];
     const eventString = resolveProperty(desc.event, host);
     const listeners: ESLEventListener[] = [];
     for (const event of splitEvents(eventString)) {
diff --git a/src/modules/esl-footnotes/core/esl-note.ts b/src/modules/esl-footnotes/core/esl-note.ts
index 819e8418d..36f5c5fe5 100644
--- a/src/modules/esl-footnotes/core/esl-note.ts
+++ b/src/modules/esl-footnotes/core/esl-note.ts
@@ -1,6 +1,6 @@
 import {ExportNs} from '../../esl-utils/environment/export-ns';
-import {ESLBaseElement} from '../../esl-base-element/core';
-import {ready, attr, prop, boolAttr, memoize, listen} from '../../esl-utils/decorators';
+import {ESLBaseElement, prop} from '../../esl-base-element/core';
+import {ready, attr, boolAttr, memoize, listen} from '../../esl-utils/decorators';
 import {ESLTooltip} from '../../esl-tooltip/core';
 import {promisifyTimeout, repeatSequence} from '../../esl-utils/async';
 import {ESLEventUtils} from '../../esl-utils/dom/events';
diff --git a/src/modules/esl-forms/esl-select-list/core/esl-select-wrapper.ts b/src/modules/esl-forms/esl-select-list/core/esl-select-wrapper.ts
index e965f8976..ad71a29f6 100644
--- a/src/modules/esl-forms/esl-select-list/core/esl-select-wrapper.ts
+++ b/src/modules/esl-forms/esl-select-list/core/esl-select-wrapper.ts
@@ -1,5 +1,4 @@
-import {ESLBaseElement} from '../../../esl-base-element/core';
-import {listen} from '../../../esl-utils/decorators/listen';
+import {ESLBaseElement, listen} from '../../../esl-base-element/core';
 import {ESLEventUtils} from '../../../esl-utils/dom/events';
 
 /** Interface for option definition */
diff --git a/src/modules/esl-media-query/core/common/screen-breakpoint.ts b/src/modules/esl-media-query/core/common/screen-breakpoint.ts
index f08c2d818..d6dfd289a 100644
--- a/src/modules/esl-media-query/core/common/screen-breakpoint.ts
+++ b/src/modules/esl-media-query/core/common/screen-breakpoint.ts
@@ -43,7 +43,9 @@ export abstract class ESLScreenBreakpoints {
 
   /** All available breakpoints names */
   public static get names(): string[] {
-    return Array.from(registry.keys());
+    const keys: string[] = [];
+    registry.forEach((value, key) => keys.push(key));
+    return keys;
   }
 
   /** @returns breakpoints shortcut replacement */
diff --git a/src/modules/esl-media-query/core/conditions/media-query-base.ts b/src/modules/esl-media-query/core/conditions/media-query-base.ts
index 6de981e6b..428f24673 100644
--- a/src/modules/esl-media-query/core/conditions/media-query-base.ts
+++ b/src/modules/esl-media-query/core/conditions/media-query-base.ts
@@ -2,11 +2,15 @@ export interface IMediaQueryCondition extends EventTarget {
   /** @returns true if current environment satisfies query */
   matches: boolean;
 
+  /** @deprecated alias for `addEventListener` */
+  addListener(cb: EventListener): void;
   /** Subscribes to media query state change. Shortcut for `addEventListener('change', callback)` */
   addEventListener(callback: EventListener): void;
   /** Subscribes to media query state change. Implements {@link EventTarget} interface */
   addEventListener(type: 'change', callback: EventListener): void;
 
+  /** @deprecated alias for `removeEventListener` */
+  removeListener(cb: EventListener): void;
   /** Unsubscribes from media query state change event. Shortcut for `removeEventListener('change', callback)` */
   removeEventListener(callback: EventListener): void;
   /** Unsubscribes from media query state change event. Implements {@link EventTarget} interface */
diff --git a/src/modules/esl-media-query/core/conditions/media-query-const.ts b/src/modules/esl-media-query/core/conditions/media-query-const.ts
index 2df664684..81f31ae58 100644
--- a/src/modules/esl-media-query/core/conditions/media-query-const.ts
+++ b/src/modules/esl-media-query/core/conditions/media-query-const.ts
@@ -14,8 +14,10 @@ class MediaQueryConstCondition implements IMediaQueryCondition {
     return this._matches;
   }
 
+  public addListener(): void {}
   public addEventListener(): void {}
 
+  public removeListener(): void {}
   public removeEventListener(): void {}
 
   public dispatchEvent(): boolean {
diff --git a/src/modules/esl-media-query/core/esl-media-query.ts b/src/modules/esl-media-query/core/esl-media-query.ts
index 391cca21e..dc1ab961c 100644
--- a/src/modules/esl-media-query/core/esl-media-query.ts
+++ b/src/modules/esl-media-query/core/esl-media-query.ts
@@ -105,6 +105,11 @@ export abstract class ESLMediaQuery implements IMediaQueryCondition {
   public abstract addEventListener(type: 'change', callback: EventListener): void;
   public abstract removeEventListener(callback: EventListener): void;
   public abstract removeEventListener(type: 'change', callback: EventListener): void;
+
+  /** @deprecated alias for `addEventListener` */
+  public abstract addListener(cb: EventListener): void;
+  /** @deprecated alias for `removeEventListener` */
+  public abstract removeListener(cb: EventListener): void;
 }
 
 // Register otb preprocessors
diff --git a/src/modules/esl-media/core/esl-media-registry.ts b/src/modules/esl-media/core/esl-media-registry.ts
index 773661ffc..be595fc6f 100644
--- a/src/modules/esl-media/core/esl-media-registry.ts
+++ b/src/modules/esl-media/core/esl-media-registry.ts
@@ -21,7 +21,9 @@ export class ESLMediaProviderRegistry extends SyntheticEventTarget {
 
   /** List of registered providers */
   public get providers(): ProviderType[] {
-    return Array.from(this.providersMap.values());
+    const list: ProviderType[] = [];
+    this.providersMap.forEach((provider) => list.push(provider));
+    return list;
   }
 
   /** Register provider */
diff --git a/src/modules/esl-media/core/esl-media.shape.ts b/src/modules/esl-media/core/esl-media.shape.ts
index 7cd2c7810..0dbcfc095 100644
--- a/src/modules/esl-media/core/esl-media.shape.ts
+++ b/src/modules/esl-media/core/esl-media.shape.ts
@@ -23,6 +23,11 @@ export type ESLMediaTagShape = ({
   /** Define preferable aspect ratio */
   'aspect-ratio'?: string;
 
+  /**
+   * Define prevent loading marker
+   * @deprecated Use {@link lazy} instead
+   */
+  disabled?: boolean;
   /** Define lazy loading mode */
   lazy?: ESLMediaLazyMode;
   /** Define autoplay marker */
@@ -52,10 +57,12 @@ export type ESLMediaTagShape = ({
 
   /** Define condition {@link ESLMediaQuery} to allow load of media resource */
   'load-condition'?: string;
-  /** Define class / classes to add when load media is accepted. Supports multiple and inverted classes */
-  'load-condition-class'?: string;
-  /** Defines target element {@link ESLTraversingQuery} selector to toggle `load-condition-class` classes */
-  'load-condition-class-target'?: string;
+  /** Define ESL Traversing Query to find target for load-cls-accepted / load-cls-declined */
+  'load-cls-target'?: string;
+  /** Define class to mark player accepted state */
+  'load-cls-accepted'?: string;
+  /** Define class to mark player declined state */
+  'load-cls-declined'?: string;
 
   /** Children are not allowed for ESLMedia */
   children?: never[];
diff --git a/src/modules/esl-media/core/esl-media.ts b/src/modules/esl-media/core/esl-media.ts
index 26a8a5785..55a716e36 100644
--- a/src/modules/esl-media/core/esl-media.ts
+++ b/src/modules/esl-media/core/esl-media.ts
@@ -34,6 +34,7 @@ const parseLazyAttr = (v: string): ESLMediaLazyMode => isLazyAttr(v) ? v : 'auto
 export class ESLMedia extends ESLBaseElement {
   public static override is = 'esl-media';
   public static observedAttributes = [
+    'disabled',
     'load-condition',
     'media-type',
     'media-id',
@@ -75,6 +76,11 @@ export class ESLMedia extends ESLBaseElement {
   @attr() public fillMode: ESLMediaFillMode;
   /** Strict aspect ratio definition */
   @attr() public aspectRatio: string;
+  /**
+   * Disabled marker to prevent rendering
+   * @deprecated replaced with {@link lazy} = "manual" functionality
+   */
+  @boolAttr() public disabled: boolean;
   /** Allows lazy load resource */
   @attr({parser: parseLazyAttr, defaultValue: 'none'}) public lazy: ESLMediaLazyMode;
   /** Autoplay resource marker */
@@ -100,9 +106,25 @@ export class ESLMedia extends ESLBaseElement {
   /** Ready state class/classes target */
   @attr() public readyClassTarget: string;
 
+  /**
+   * Class / classes to add when media is accepted
+   * @deprecated use {@link loadConditionClass} instead (e.g. `load-condition-class="is-accepted"`)
+   */
+  @attr() public loadClsAccepted: string;
+  /**
+   * Class / classes to add when media is declined
+   * @deprecated use {@link loadConditionClass} with negative class param instead (e.g. `load-condition-class="!is-declined"`)
+   */
+  @attr() public loadClsDeclined: string;
+  /**
+   * Target element {@link ESLTraversingQuery} select to add accepted/declined classes
+   * @deprecated used with legacy load condition attributes, consider migration to {@link loadConditionClass}
+   */
+  @attr({defaultValue: '::parent'}) public loadClsTarget: string;
+
   /** Condition {@link ESLMediaQuery} to allow load of media resource. Default: `all` */
   @attr({defaultValue: 'all'}) public loadCondition: string;
-  /** Class / classes to add when load media is accepted. Supports multiple and inverted classes */
+  /** Class / classes to add when load media is accepted */
   @attr() public loadConditionClass: string;
   /** Target element {@link ESLTraversingQuery} select to toggle {@link loadConditionClass} classes */
   @attr({defaultValue: '::parent'}) public loadConditionClassTarget: string;
@@ -151,6 +173,7 @@ export class ESLMedia extends ESLBaseElement {
   protected override attributeChangedCallback(attrName: string, oldVal: string, newVal: string): void {
     if (!this.connected || oldVal === newVal) return;
     switch (attrName) {
+      case 'disabled':
       case 'media-id':
       case 'media-src':
       case 'media-type':
@@ -183,7 +206,8 @@ export class ESLMedia extends ESLBaseElement {
   }
 
   public canActivate(): boolean {
-    return this.lazy === 'none' && this.conditionQuery.matches;
+    if (this.lazy !== 'none' || this.disabled) return false;
+    return this.conditionQuery.matches;
   }
 
   private reinitInstance(): void {
@@ -205,8 +229,15 @@ export class ESLMedia extends ESLBaseElement {
   }
 
   public updateContainerMarkers(): void {
+    const active = this.conditionQuery.matches;
+
     const $target = ESLTraversingQuery.first(this.loadConditionClassTarget, this) as HTMLElement;
-    $target && CSSClassUtils.toggle($target, this.loadConditionClass, this.conditionQuery.matches);
+    $target && CSSClassUtils.toggle($target, this.loadConditionClass, active);
+
+    // Legacy attributes support
+    const targetEl = ESLTraversingQuery.first(this.loadClsTarget, this) as HTMLElement;
+    targetEl && CSSClassUtils.toggle(targetEl, this.loadClsAccepted, active);
+    targetEl && CSSClassUtils.toggle(targetEl, this.loadClsDeclined, !active);
   }
 
   /** Seek to given position of media */
@@ -216,11 +247,12 @@ export class ESLMedia extends ESLBaseElement {
 
   /**
    * Start playing media
-   * @param allowActivate - allows to remove manual lazy loading restrictions
+   * @param allowActivate - allows to remove manual lazy or disabled marker
    */
   public play(allowActivate: boolean = false): Promise<void> | null {
     if (!this.ready && allowActivate) {
       this.lazy = 'none';
+      this.disabled = false;
       this.deferredReinitialize.cancel();
       this.reinitInstance();
     }
diff --git a/src/modules/esl-mixin-element/core.ts b/src/modules/esl-mixin-element/core.ts
index a1520b480..de26e22f8 100644
--- a/src/modules/esl-mixin-element/core.ts
+++ b/src/modules/esl-mixin-element/core.ts
@@ -1,1 +1,8 @@
 export * from './ui/esl-mixin-element';
+
+export * from '../esl-utils/decorators/prop';
+export * from '../esl-utils/decorators/attr';
+export * from '../esl-utils/decorators/bool-attr';
+export * from '../esl-utils/decorators/json-attr';
+
+export * from '../esl-utils/decorators/listen';
diff --git a/src/modules/esl-mixin-element/ui/esl-mixin-registry.ts b/src/modules/esl-mixin-element/ui/esl-mixin-registry.ts
index b233a0a8a..1206ca34b 100644
--- a/src/modules/esl-mixin-element/ui/esl-mixin-registry.ts
+++ b/src/modules/esl-mixin-element/ui/esl-mixin-registry.ts
@@ -136,7 +136,7 @@ export class ESLMixinRegistry {
   /** @returns all mixins initialized on passed host element */
   public static getAll(el: HTMLElement): ESLMixinElement[] {
     const store = (el as any)[STORE] as Record<string, ESLMixinElement> | undefined;
-    return store ? Object.values(store) : [];
+    return store ? Object.keys(store).map((key: string) => store[key]) : [];
   }
 
   /** @returns if the passed mixin exists on the element */
diff --git a/src/modules/esl-panel-group/core/esl-panel-group.shape.ts b/src/modules/esl-panel-group/core/esl-panel-group.shape.ts
index c85378b8c..b41bd2e29 100644
--- a/src/modules/esl-panel-group/core/esl-panel-group.shape.ts
+++ b/src/modules/esl-panel-group/core/esl-panel-group.shape.ts
@@ -39,6 +39,9 @@ export interface ESLPanelGroupTagShape extends ESLBaseElementShape<ESLPanelGroup
 
   /** Allowed children */
   children?: any;
+
+  /** @deprecated fallback time no longer required */
+  'fallback-duration'?: string | number;
 }
 
 declare global {
diff --git a/src/modules/esl-panel/core/esl-panel.shape.ts b/src/modules/esl-panel/core/esl-panel.shape.ts
index 72e6bcf05..d9a7cd82f 100644
--- a/src/modules/esl-panel/core/esl-panel.shape.ts
+++ b/src/modules/esl-panel/core/esl-panel.shape.ts
@@ -15,6 +15,9 @@ export interface ESLPanelTagShape extends ESLToggleableTagShape<ESLPanel> {
 
   /** Define CSS selector of the parent group (default: 'esl-panel-group')*/
   'panel-group-sel'?: string;
+
+  /** @deprecated fallback time is no longer required */
+  'fallback-duration'?: string | number;
 }
 
 declare global {
diff --git a/src/modules/esl-popup/core/esl-popup.ts b/src/modules/esl-popup/core/esl-popup.ts
index a17ef601d..9156852e4 100644
--- a/src/modules/esl-popup/core/esl-popup.ts
+++ b/src/modules/esl-popup/core/esl-popup.ts
@@ -7,7 +7,7 @@ import {ESLToggleable} from '../../esl-toggleable/core';
 import {Rect} from '../../esl-utils/dom/rect';
 import {isRTL} from '../../esl-utils/dom/rtl';
 import {getListScrollParents} from '../../esl-utils/dom/scroll';
-import {getWindowRect} from '../../esl-utils/dom/window';
+import {getViewportRect} from '../../esl-utils/dom/window';
 import {parseBoolean, parseNumber, toBooleanAttribute} from '../../esl-utils/misc/format';
 import {copyDefinedKeys} from '../../esl-utils/misc/object';
 import {ESLIntersectionTarget, ESLIntersectionEvent} from '../../esl-event-listener/core/targets/intersection.target';
@@ -135,8 +135,8 @@ export class ESLPopup extends ESLToggleable {
 
   /** Get the size and position of the container */
   protected get containerRect(): Rect {
-    if (!this.$container) return getWindowRect();
-    return Rect.from(this.$container).shift(window.pageXOffset, window.pageYOffset);
+    if (!this.$container) return getViewportRect();
+    return Rect.from(this.$container).shift(window.scrollX, window.scrollY);
   }
 
   @ready
@@ -396,7 +396,7 @@ export class ESLPopup extends ESLToggleable {
     const triggerRect = this.activator.getBoundingClientRect();
     const popupRect = this.getBoundingClientRect();
     const arrowRect = this.$arrow ? this.$arrow.getBoundingClientRect() : new Rect();
-    const trigger = new Rect(triggerRect.left + window.pageXOffset, triggerRect.top + window.pageYOffset, triggerRect.width, triggerRect.height);
+    const trigger = new Rect(triggerRect.left + window.scrollX, triggerRect.top + window.scrollY, triggerRect.width, triggerRect.height);
     const innerMargin = this._offsetTrigger + arrowRect.width / 2;
     const {containerRect} = this;
 
diff --git a/src/modules/esl-share/core/esl-share.ts b/src/modules/esl-share/core/esl-share.ts
index 06ba23a7e..c23cf45e0 100644
--- a/src/modules/esl-share/core/esl-share.ts
+++ b/src/modules/esl-share/core/esl-share.ts
@@ -1,12 +1,12 @@
 import {ExportNs} from '../../esl-utils/environment/export-ns';
 import {attr, boolAttr, jsonAttr, prop, ready} from '../../esl-utils/decorators';
 import {ESLTraversingQuery} from '../../esl-traversing-query/core';
-import {ESLBaseTrigger} from '../../esl-trigger/core';
-
+import {ESLTrigger} from '../../esl-trigger/core';
 import {ESLSharePopup} from './esl-share-popup';
 
 import type {ESLToggleable} from '../../esl-toggleable/core/esl-toggleable';
 import type {ESLSharePopupActionParams} from './esl-share-popup';
+
 export type {ESLShareTagShape} from './esl-share.shape';
 
 /**
@@ -16,9 +16,9 @@ export type {ESLShareTagShape} from './esl-share.shape';
  * ESLShare is a component that allows triggering {@link ESLSharePopup} instance state changes.
  */
 @ExportNs('Share')
-export class ESLShare extends ESLBaseTrigger {
+export class ESLShare extends ESLTrigger {
   public static override is = 'esl-share';
-  public static observedAttributes = ['list'];
+  public static override observedAttributes = ['list'];
 
   /** Register {@link ESLShare} component and dependent {@link ESLSharePopup} */
   public static override register(): void {
@@ -51,6 +51,7 @@ export class ESLShare extends ESLBaseTrigger {
   public override get $target(): ESLToggleable | null {
     return ESLSharePopup.sharedInstance;
   }
+  public override set $target(value: any) {}
 
   /** Checks that the target is in active state */
   public override get isTargetActive(): boolean {
@@ -77,10 +78,7 @@ export class ESLShare extends ESLBaseTrigger {
   @ready
   protected override connectedCallback(): void {
     super.connectedCallback();
-    if (!this.ready) {
-      this.$$attr('ready', true);
-      this.$$fire(this.SHARE_READY_EVENT, {bubbles: false});
-    }
+    this.onReady();
   }
 
   protected override attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string | null): void {
@@ -94,6 +92,9 @@ export class ESLShare extends ESLBaseTrigger {
     this.$target?.hide();
   }
 
+  /** Updates `$target` Toggleable  from `target` selector */
+  public override updateTargetFromSelector(): void {}
+
   /** Gets attribute value from the closest element with group behavior settings */
   protected getClosestRelatedAttr(attrName: string): string | null {
     const relatedAttrName = `${this.baseTagName}-${attrName}`;
@@ -103,14 +104,22 @@ export class ESLShare extends ESLBaseTrigger {
 
   /** Merges params to pass to the toggleable */
   protected override mergeToggleableParams(this: ESLShare, ...params: ESLSharePopupActionParams[]): ESLSharePopupActionParams {
-    return super.mergeToggleableParams({
+    return Object.assign({
       initiator: 'share',
+      activator: this,
       containerEl: this.$containerEl,
       list: this.list,
       dir: this.currentDir,
       lang: this.currentLang
     }, this.popupParams, ...params);
   }
+
+  /** Actions on complete init and ready component */
+  private onReady(): void {
+    if (this.ready) return;
+    this.$$attr('ready', true);
+    this.$$fire(this.SHARE_READY_EVENT, {bubbles: false});
+  }
 }
 
 declare global {
diff --git a/src/modules/esl-tab/core/esl-tab.ts b/src/modules/esl-tab/core/esl-tab.ts
index 2a9cf62a8..79f79b8d7 100644
--- a/src/modules/esl-tab/core/esl-tab.ts
+++ b/src/modules/esl-tab/core/esl-tab.ts
@@ -14,7 +14,7 @@ import {ESLTrigger} from '../../esl-trigger/core';
 export class ESLTab extends ESLTrigger {
   public static override is = 'esl-tab';
 
-  @attr({defaultValue: 'show'}) public override mode: 'show' | 'toggle' | 'hide';
+  @attr({defaultValue: 'show'}) public override mode: string;
   @attr({defaultValue: 'active'}) public override activeClass: string;
 
   public override initA11y(): void {
diff --git a/src/modules/esl-tab/core/esl-tabs.arrows.less b/src/modules/esl-tab/core/esl-tabs.arrows.less
index 295d61dff..a83eeebb7 100644
--- a/src/modules/esl-tab/core/esl-tabs.arrows.less
+++ b/src/modules/esl-tab/core/esl-tabs.arrows.less
@@ -19,6 +19,8 @@ esl-tabs[scrollable] {
     border: none;
     opacity: 0.5;
     cursor: pointer;
+
+    // TODO: IE11 support
     appearance: none;
   }
   .arrow-next {
diff --git a/src/modules/esl-toggleable/core/esl-toggleable-dispatcher.ts b/src/modules/esl-toggleable/core/esl-toggleable-dispatcher.ts
index fe33bf0b3..7fcf82520 100644
--- a/src/modules/esl-toggleable/core/esl-toggleable-dispatcher.ts
+++ b/src/modules/esl-toggleable/core/esl-toggleable-dispatcher.ts
@@ -1,5 +1,4 @@
-import {ESLBaseElement} from '../../esl-base-element/core';
-import {listen} from '../../esl-utils/decorators/listen';
+import {ESLBaseElement, listen} from '../../esl-base-element/core';
 import {getCompositeTarget} from '../../esl-utils/dom/events';
 import {ExportNs} from '../../esl-utils/environment/export-ns';
 import {ESLToggleable} from './esl-toggleable';
diff --git a/src/modules/esl-toggleable/core/esl-toggleable.ts b/src/modules/esl-toggleable/core/esl-toggleable.ts
index b7b380976..2fffc9d32 100644
--- a/src/modules/esl-toggleable/core/esl-toggleable.ts
+++ b/src/modules/esl-toggleable/core/esl-toggleable.ts
@@ -44,6 +44,9 @@ export interface ESLToggleableRequestDetails extends ESLToggleableActionParams {
   match?: string | ((target: Element) => boolean);
 }
 
+/** @deprecated alias for ESLToggleableActionParams, will be removed in 5.0.0*/
+export type ToggleableActionParams = ESLToggleableActionParams;
+
 const activators: WeakMap<ESLToggleable, HTMLElement | undefined> = new WeakMap();
 
 /**
diff --git a/src/modules/esl-traversing-query/core/esl-traversing-query.ts b/src/modules/esl-traversing-query/core/esl-traversing-query.ts
index 50cb9f7a4..fb40cc609 100644
--- a/src/modules/esl-traversing-query/core/esl-traversing-query.ts
+++ b/src/modules/esl-traversing-query/core/esl-traversing-query.ts
@@ -145,6 +145,9 @@ export class ESLTraversingQuery {
   }
 }
 
+/** @deprecated alias for {@link ESLTraversingQuery} */
+export const TraversingQuery = ESLTraversingQuery;
+
 declare global {
   export interface ESLLibrary {
     TraversingQuery: typeof ESLTraversingQuery;
diff --git a/src/modules/esl-trigger/core.ts b/src/modules/esl-trigger/core.ts
index dc563765d..22eecfc6c 100644
--- a/src/modules/esl-trigger/core.ts
+++ b/src/modules/esl-trigger/core.ts
@@ -1,4 +1,3 @@
 export type {ESLTriggerTagShape} from './core/esl-trigger.shape';
 
-export * from './core/esl-base-trigger';
 export * from './core/esl-trigger';
diff --git a/src/modules/esl-trigger/core/esl-base-trigger.ts b/src/modules/esl-trigger/core/esl-base-trigger.ts
deleted file mode 100644
index 6f4b58a8c..000000000
--- a/src/modules/esl-trigger/core/esl-base-trigger.ts
+++ /dev/null
@@ -1,232 +0,0 @@
-import {ESLBaseElement} from '../../esl-base-element/core';
-import {DeviceDetector} from '../../esl-utils/environment/device-detector';
-import {isElement} from '../../esl-utils/dom/api';
-import {setAttr} from '../../esl-utils/dom/attr';
-import {CSSClassUtils} from '../../esl-utils/dom/class';
-import {ENTER, SPACE, ESC} from '../../esl-utils/dom/keys';
-import {attr, boolAttr, prop, listen} from '../../esl-utils/decorators';
-import {parseBoolean, parseNumber, toBooleanAttribute} from '../../esl-utils/misc/format';
-import {ESLMediaQuery} from '../../esl-media-query/core';
-import {ESLTraversingQuery} from '../../esl-traversing-query/core';
-
-import type {ESLToggleable, ESLToggleableActionParams} from '../../esl-toggleable/core/esl-toggleable';
-
-/** Base class for elements that should trigger {@link ESLToggleable} instance */
-export abstract class ESLBaseTrigger extends ESLBaseElement {
-  /** Event that represents {@link ESLTrigger} state change */
-  @prop('') public CHANGE_EVENT: string;
-  /** Events to observe target {@link ESLToggleable} instance state */
-  @prop('esl:show esl:hide') public OBSERVED_EVENTS: string;
-
-  /** @readonly Observed Toggleable active state marker */
-  @boolAttr({readonly: true}) public active: boolean;
-
-  /** CSS classes to set on active state */
-  @attr({defaultValue: ''}) public activeClass: string;
-  /** Target element {@link ESLTraversingQuery} selector to set `activeClass` */
-  @attr({defaultValue: ''}) public activeClassTarget: string;
-
-  /** Click event tracking media query. Default: `all` */
-  @attr({defaultValue: 'all'}) public trackClick: string;
-  /** Hover event tracking media query. Default: `none` */
-  @attr({defaultValue: 'not all'}) public trackHover: string;
-
-  /** Value of aria-label for active state */
-  @attr({defaultValue: null}) public a11yLabelActive: string | null;
-  /** Value of aria-label for inactive state */
-  @attr({defaultValue: null}) public a11yLabelInactive: string | null;
-
-  /** Show delay value */
-  @attr({defaultValue: 'none'}) public showDelay: string;
-  /** Hide delay value */
-  @attr({defaultValue: 'none'}) public hideDelay: string;
-
-  /**
-   * Alternative show delay value for hover action.
-   * Note: the value should be numeric in order to delay hover action.
-   */
-  @attr({defaultValue: '0'}) public hoverShowDelay: string;
-  /**
-   * Alternative hide delay value for hover action.
-   * Note: the value should be numeric in order to delay hover action.
-   */
-  @attr({defaultValue: '0'}) public hoverHideDelay: string;
-
-  /** Prevent ESC keyboard event handling for target element hiding */
-  @attr({parser: parseBoolean, serializer: toBooleanAttribute}) public ignoreEsc: boolean;
-
-  /** Action to pass to the Toggleable. Supports `show`, `hide` and `toggle` values. `toggle` by default */
-  @prop('toggle') public mode: 'toggle' | 'show' | 'hide';
-
-  /** Target observable Toggleable */
-  public abstract get $target(): ESLToggleable | null;
-
-  /** Element target to setup aria attributes */
-  public get $a11yTarget(): HTMLElement | null {
-    return this;
-  }
-
-  /** Value to setup aria-label */
-  public get a11yLabel(): string | null {
-    if (!this.$target) return null;
-    return (this.isTargetActive ? this.a11yLabelActive : this.a11yLabelInactive) || null;
-  }
-
-  /** Marker to allow track hover */
-  public get allowHover(): boolean {
-    return DeviceDetector.hasHover && ESLMediaQuery.for(this.trackHover).matches;
-  }
-  /** Marker to allow track clicks */
-  public get allowClick(): boolean {
-    return ESLMediaQuery.for(this.trackClick).matches;
-  }
-
-  /** Checks that the target is in active state */
-  public get isTargetActive(): boolean {
-    return !!this.$target?.open;
-  }
-
-  protected override connectedCallback(): void {
-    super.connectedCallback();
-    this.initA11y();
-  }
-
-  /** Check if the event target should be ignored */
-  protected isTargetIgnored(target: EventTarget | null): boolean {
-    return !isElement(target);
-  }
-
-  /** Merge params to pass to the toggleable */
-  protected mergeToggleableParams(this: ESLBaseTrigger, ...params: ESLToggleableActionParams[]): ESLToggleableActionParams {
-    return Object.assign({
-      initiator: 'trigger',
-      activator: this
-    }, ...params);
-  }
-
-  /** Show target toggleable with passed params */
-  public showTarget(params: ESLToggleableActionParams = {}): void {
-    const actionParams = this.mergeToggleableParams({
-      delay: parseNumber(this.showDelay)
-    }, params);
-    if (this.$target && typeof this.$target.show === 'function') {
-      this.$target.show(actionParams);
-    }
-  }
-  /** Hide target toggleable with passed params */
-  public hideTarget(params: ESLToggleableActionParams = {}): void {
-    const actionParams = this.mergeToggleableParams({
-      delay: parseNumber(this.hideDelay)
-    }, params);
-    if (this.$target && typeof this.$target.hide === 'function') {
-      this.$target.hide(actionParams);
-    }
-  }
-  /** Toggles target toggleable with passed params */
-  public toggleTarget(params: ESLToggleableActionParams = {}, state: boolean = !this.active): void {
-    state ? this.showTarget(params) : this.hideTarget(params);
-  }
-
-  /**
-   * Updates trigger state according to toggleable state
-   * Does not produce `esl:change:active` event
-   */
-  public updateState(): boolean {
-    const {active, isTargetActive} = this;
-
-    this.toggleAttribute('active', isTargetActive);
-    const clsTarget = ESLTraversingQuery.first(this.activeClassTarget, this) as HTMLElement;
-    clsTarget && CSSClassUtils.toggle(clsTarget, this.activeClass, isTargetActive);
-
-    this.updateA11y();
-
-    return isTargetActive !== active;
-  }
-
-
-  /** Handles target primary (observed) event */
-  protected _onPrimaryEvent(event: Event): void {
-    switch (this.mode) {
-      case 'show':
-        return this.showTarget({event});
-      case 'hide':
-        return this.hideTarget({event});
-      default:
-        return this.toggleTarget({event});
-    }
-  }
-
-  /** Handles ESLToggleable state change */
-  @listen({
-    event: (that: ESLBaseTrigger) => that.OBSERVED_EVENTS,
-    target: (that: ESLBaseTrigger) => that.$target
-  })
-  protected _onTargetStateChange(originalEvent?: Event): void {
-    if (!this.updateState()) return;
-    const detail = {active: this.active, originalEvent};
-    this.$$fire(this.CHANGE_EVENT, {detail});
-  }
-
-  /** Handles `click` event */
-  @listen('click')
-  protected _onClick(event: MouseEvent): void {
-    if (!this.allowClick || this.isTargetIgnored(event.target)) return;
-    event.preventDefault();
-    this._onPrimaryEvent(event);
-  }
-
-  /** Handles `keydown` event */
-  @listen('keydown')
-  protected _onKeydown(event: KeyboardEvent): void {
-    if (![ENTER, SPACE, ESC].includes(event.key) || this.isTargetIgnored(event.target)) return;
-    event.preventDefault();
-    if (event.key === ESC) {
-      if (this.ignoreEsc) return;
-      this.hideTarget({event});
-    } else {
-      this._onPrimaryEvent(event);
-    }
-  }
-
-  /** Handles hover `mouseenter` event */
-  @listen('mouseenter')
-  protected _onMouseEnter(event: MouseEvent): void {
-    if (!this.allowHover) return;
-    const delay = parseNumber(this.hoverShowDelay);
-    this.toggleTarget({event, delay}, this.mode !== 'hide');
-    event.preventDefault();
-  }
-
-  /** Handles hover `mouseleave` event */
-  @listen('mouseleave')
-  protected _onMouseLeave(event: MouseEvent): void {
-    if (!this.allowHover) return;
-    if (this.mode === 'show' || this.mode === 'hide') return;
-    const delay = parseNumber(this.hoverHideDelay);
-    this.hideTarget({event, delay, trackHover: true});
-    event.preventDefault();
-  }
-
-  /** Set initial a11y attributes. Do nothing if trigger contains actionable element */
-  public initA11y(): void {
-    if (this.$a11yTarget !== this) return;
-    if (!this.hasAttribute('role')) this.setAttribute('role', 'button');
-    if (this.getAttribute('role') === 'button' && !this.hasAttribute('tabindex')) {
-      this.setAttribute('tabindex', '0');
-    }
-  }
-
-  /** Update aria attributes */
-  public updateA11y(): void {
-    const target = this.$a11yTarget;
-    if (!target) return;
-
-    if (this.a11yLabelActive !== null || this.a11yLabelInactive !== null) {
-      setAttr(target, 'aria-label', this.a11yLabel);
-    }
-    setAttr(target, 'aria-expanded', String(this.active));
-    if (this.$target && this.$target.id) {
-      setAttr(target, 'aria-controls', this.$target.id);
-    }
-  }
-}
diff --git a/src/modules/esl-trigger/core/esl-trigger.shape.ts b/src/modules/esl-trigger/core/esl-trigger.shape.ts
index 86bafed5f..d2941ccda 100644
--- a/src/modules/esl-trigger/core/esl-trigger.shape.ts
+++ b/src/modules/esl-trigger/core/esl-trigger.shape.ts
@@ -1,12 +1,11 @@
 import type {ESLBaseElementShape} from '../../esl-base-element/core/esl-base-element.shape';
 import type {ESLTrigger} from './esl-trigger';
-import type {ESLBaseTrigger} from './esl-base-trigger';
 
 /**
  * Tag declaration interface of {@link ESLTrigger} element
  * Used for TSX declaration
  */
-export interface ESLTriggerTagShape<T extends ESLBaseTrigger = ESLTrigger> extends ESLBaseElementShape<T> {
+export interface ESLTriggerTagShape<T extends ESLTrigger = ESLTrigger> extends ESLBaseElementShape<T> {
   /** Define target Toggleable {@link ESLTraversingQuery} selector. `next` by default */
   'target'?: string;
 
diff --git a/src/modules/esl-trigger/core/esl-trigger.ts b/src/modules/esl-trigger/core/esl-trigger.ts
index 3f1896e2d..69cf4e230 100644
--- a/src/modules/esl-trigger/core/esl-trigger.ts
+++ b/src/modules/esl-trigger/core/esl-trigger.ts
@@ -1,21 +1,36 @@
+import {ESLBaseElement} from '../../esl-base-element/core';
 import {ExportNs} from '../../esl-utils/environment/export-ns';
+import {DeviceDetector} from '../../esl-utils/environment/device-detector';
 import {isElement} from '../../esl-utils/dom/api';
-import {attr, prop, ready} from '../../esl-utils/decorators';
+import {setAttr} from '../../esl-utils/dom/attr';
+import {CSSClassUtils} from '../../esl-utils/dom/class';
+import {ENTER, SPACE, ESC} from '../../esl-utils/dom/keys';
+import {attr, boolAttr, prop, listen, ready} from '../../esl-utils/decorators';
+import {parseBoolean, parseNumber, toBooleanAttribute} from '../../esl-utils/misc/format';
+import {ESLMediaQuery} from '../../esl-media-query/core';
 import {ESLTraversingQuery} from '../../esl-traversing-query/core';
-import {ESLToggleablePlaceholder} from '../../esl-toggleable/core';
-
-import {ESLBaseTrigger} from './esl-base-trigger';
 
-import type {ESLToggleable} from '../../esl-toggleable/core/esl-toggleable';
+import {ESLToggleablePlaceholder} from '../../esl-toggleable/core';
 
+import type {ESLToggleable, ESLToggleableActionParams} from '../../esl-toggleable/core/esl-toggleable';
 
 @ExportNs('Trigger')
-export class ESLTrigger extends ESLBaseTrigger {
+export class ESLTrigger extends ESLBaseElement {
   public static override is = 'esl-trigger';
   public static observedAttributes = ['target'];
 
   /** Event that represents {@link ESLTrigger} state change */
-  @prop('esl:change:active') public override CHANGE_EVENT: string;
+  @prop('esl:change:active') public CHANGE_EVENT: string;
+  /** Events to observe target {@link ESLToggleable} instance state */
+  @prop('esl:show esl:hide') public OBSERVED_EVENTS: string;
+
+  /** @readonly Observed Toggleable active state marker */
+  @boolAttr({readonly: true}) public active: boolean;
+
+  /** CSS classes to set on active state */
+  @attr({defaultValue: ''}) public activeClass: string;
+  /** Target element {@link ESLTraversingQuery} selector to set `activeClass` */
+  @attr({defaultValue: ''}) public activeClassTarget: string;
 
   /** Selector for ignored inner elements */
   @attr({defaultValue: 'a[href]'}) public ignore: string;
@@ -23,14 +38,47 @@ export class ESLTrigger extends ESLBaseTrigger {
   /** Target Toggleable {@link ESLTraversingQuery} selector */
   @attr({defaultValue: ''}) public target: string;
   /** Action to pass to the Toggleable. Supports `show`, `hide` and `toggle` values. `toggle` by default */
-  @attr({defaultValue: 'toggle'}) public override mode: 'toggle' | 'show' | 'hide';
+  @attr({defaultValue: 'toggle'}) public mode: string;
+
+  /** Click event tracking media query. Default: `all` */
+  @attr({defaultValue: 'all'}) public trackClick: string;
+  /** Hover event tracking media query. Default: `none` */
+  @attr({defaultValue: 'not all'}) public trackHover: string;
 
   /** Selector of inner target element to place aria attributes. Uses trigger itself if blank */
   @attr({defaultValue: ''}) public a11yTarget: string;
 
+  /** Value of aria-label for active state */
+  @attr({defaultValue: null}) public a11yLabelActive: string | null;
+  /** Value of aria-label for inactive state */
+  @attr({defaultValue: null}) public a11yLabelInactive: string | null;
+
+  /** Show delay value */
+  @attr({defaultValue: 'none'}) public showDelay: string;
+  /** Hide delay value */
+  @attr({defaultValue: 'none'}) public hideDelay: string;
+
+  /**
+   * Show delay value override for hover.
+   * Note: the value should be numeric in order to delay hover action triggers for correct handling on mobile browsers.
+   */
+  @attr({defaultValue: '0'}) public hoverShowDelay: string;
+  /**
+   * Hide delay value override for hover
+   * Note: the value should be numeric in order to delay hover action triggers for correct handling on mobile browsers.
+   */
+  @attr({defaultValue: '0'}) public hoverHideDelay: string;
+
+  /** Prevent ESC keyboard event handling for target element hiding */
+  @attr({parser: parseBoolean, serializer: toBooleanAttribute}) public ignoreEsc: boolean;
 
   protected _$target: ESLToggleable | null;
 
+  protected override attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string | null): void {
+    if (!this.connected) return;
+    if (attrName === 'target') return this.updateTargetFromSelector();
+  }
+
   /** Target observable Toggleable */
   public get $target(): ESLToggleable | null {
     return this._$target;
@@ -43,10 +91,30 @@ export class ESLTrigger extends ESLBaseTrigger {
   }
 
   /** Element target to setup aria attributes */
-  public override get $a11yTarget(): HTMLElement | null {
+  public get $a11yTarget(): HTMLElement | null {
     return this.a11yTarget ? this.querySelector(this.a11yTarget) : this;
   }
 
+  /** Value to setup aria-label */
+  public get a11yLabel(): string | null {
+    if (!this.$target) return null;
+    return (this.isTargetActive ? this.a11yLabelActive : this.a11yLabelInactive) || null;
+  }
+
+  /** Marker to allow track hover */
+  public get allowHover(): boolean {
+    return DeviceDetector.hasHover && ESLMediaQuery.for(this.trackHover).matches;
+  }
+  /** Marker to allow track clicks */
+  public get allowClick(): boolean {
+    return ESLMediaQuery.for(this.trackClick).matches;
+  }
+
+  /** Checks that the target is in active state */
+  public get isTargetActive(): boolean {
+    return !!this.$target?.open;
+  }
+
   @ready
   protected override connectedCallback(): void {
     super.connectedCallback();
@@ -54,11 +122,6 @@ export class ESLTrigger extends ESLBaseTrigger {
     this.initA11y();
   }
 
-  protected override attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string | null): void {
-    if (!this.connected) return;
-    if (attrName === 'target') return this.updateTargetFromSelector();
-  }
-
   /** Update `$target` Toggleable  from `target` selector */
   public updateTargetFromSelector(): void {
     if (!this.target) return;
@@ -71,12 +134,146 @@ export class ESLTrigger extends ESLBaseTrigger {
   }
 
   /** Check if the event target should be ignored */
-  protected override isTargetIgnored(target: EventTarget | null): boolean {
+  protected isTargetIgnored(target: EventTarget | null): boolean {
     if (!target || !isElement(target) || !this.ignore) return false;
     const $ignore = target.closest(this.ignore);
     // Ignore only inner elements (but do not ignore the trigger itself)
     return !!$ignore && $ignore !== this && this.contains($ignore);
   }
+
+  /** Merge params to pass to the toggleable */
+  protected mergeToggleableParams(this: ESLTrigger, ...params: ESLToggleableActionParams[]): ESLToggleableActionParams {
+    return Object.assign({
+      initiator: 'trigger',
+      activator: this
+    }, ...params);
+  }
+
+  /** Show target toggleable with passed params */
+  public showTarget(params: ESLToggleableActionParams = {}): void {
+    const actionParams = this.mergeToggleableParams({
+      delay: parseNumber(this.showDelay)
+    }, params);
+    if (this.$target && typeof this.$target.show === 'function') {
+      this.$target.show(actionParams);
+    }
+  }
+  /** Hide target toggleable with passed params */
+  public hideTarget(params: ESLToggleableActionParams = {}): void {
+    const actionParams = this.mergeToggleableParams({
+      delay: parseNumber(this.hideDelay)
+    }, params);
+    if (this.$target && typeof this.$target.hide === 'function') {
+      this.$target.hide(actionParams);
+    }
+  }
+  /** Toggles target toggleable with passed params */
+  public toggleTarget(params: ESLToggleableActionParams = {}, state: boolean = !this.active): void {
+    state ? this.showTarget(params) : this.hideTarget(params);
+  }
+
+  /**
+   * Updates trigger state according to toggleable state
+   * Does not produce `esl:change:active` event
+   */
+  public updateState(): boolean {
+    const {isTargetActive} = this;
+    const wasActive = this.active;
+
+    this.toggleAttribute('active', isTargetActive);
+    const clsTarget = ESLTraversingQuery.first(this.activeClassTarget, this) as HTMLElement;
+    clsTarget && CSSClassUtils.toggle(clsTarget, this.activeClass, isTargetActive);
+
+    this.updateA11y();
+
+    return isTargetActive !== wasActive;
+  }
+
+  /** Handles ESLToggleable state change */
+  @listen({
+    event: (that: ESLTrigger) => that.OBSERVED_EVENTS,
+    target: (that: ESLTrigger) => that.$target
+  })
+  protected _onTargetStateChange(originalEvent?: Event): void {
+    if (!this.updateState()) return;
+    const detail = {active: this.active, originalEvent};
+    this.$$fire(this.CHANGE_EVENT, {detail});
+  }
+
+  /** Handles `click` event */
+  @listen('click')
+  protected _onClick(event: MouseEvent): void {
+    if (!this.allowClick || this.isTargetIgnored(event.target)) return;
+    event.preventDefault();
+    this._onPrimaryEvent(event);
+  }
+
+  /** Handles `keydown` event */
+  @listen('keydown')
+  protected _onKeydown(event: KeyboardEvent): void {
+    if (![ENTER, SPACE, ESC].includes(event.key) || this.isTargetIgnored(event.target)) return;
+    event.preventDefault();
+    if (event.key === ESC) {
+      if (this.ignoreEsc) return;
+      this.hideTarget({event});
+    } else {
+      this._onPrimaryEvent(event);
+    }
+  }
+
+  /** Handles target primary (observed) event */
+  protected _onPrimaryEvent(event: Event): void {
+    switch (this.mode) {
+      case 'show':
+        return this.showTarget({event});
+      case 'hide':
+        return this.hideTarget({event});
+      default:
+        return this.toggleTarget({event});
+    }
+  }
+
+  /** Handles hover `mouseenter` event */
+  @listen('mouseenter')
+  protected _onMouseEnter(event: MouseEvent): void {
+    if (!this.allowHover) return;
+    const delay = parseNumber(this.hoverShowDelay);
+    this.toggleTarget({event, delay}, this.mode !== 'hide');
+    event.preventDefault();
+  }
+
+  /** Handles hover `mouseleave` event */
+  @listen('mouseleave')
+  protected _onMouseLeave(event: MouseEvent): void {
+    if (!this.allowHover) return;
+    if (this.mode === 'show' || this.mode === 'hide') return;
+    const delay = parseNumber(this.hoverHideDelay);
+    this.hideTarget({event, delay, trackHover: true});
+    event.preventDefault();
+  }
+
+  /** Set initial a11y attributes. Do nothing if trigger contains actionable element */
+  public initA11y(): void {
+    if (this.$a11yTarget !== this) return;
+    if (!this.hasAttribute('role')) this.setAttribute('role', 'button');
+    if (this.getAttribute('role') === 'button' && !this.hasAttribute('tabindex')) {
+      this.setAttribute('tabindex', '0');
+    }
+  }
+
+  /** Update aria attributes */
+  public updateA11y(): void {
+    const target = this.$a11yTarget;
+    if (!target) return;
+
+    if (this.a11yLabelActive !== null || this.a11yLabelInactive !== null) {
+      setAttr(target, 'aria-label', this.a11yLabel);
+    }
+    setAttr(target, 'aria-expanded', String(this.active));
+    if (this.$target && this.$target.id) {
+      setAttr(target, 'aria-controls', this.$target.id);
+    }
+  }
 }
 
 declare global {
diff --git a/src/modules/esl-utils/dom/events/target.ts b/src/modules/esl-utils/dom/events/target.ts
index 45c31b863..ee1a03f53 100644
--- a/src/modules/esl-utils/dom/events/target.ts
+++ b/src/modules/esl-utils/dom/events/target.ts
@@ -1,4 +1,4 @@
-import {uniq} from '../../misc/array';
+import {flat, uniq} from '../../misc/array';
 import {overrideEvent} from './misc';
 
 /** Key to store listeners on the {@link SyntheticEventTarget} instance*/
@@ -10,7 +10,7 @@ const LISTENERS: unique symbol  = (window.Symbol || String)('_listeners') as any
  * Doesn't give explicit access to callback storage
  */
 export class SyntheticEventTarget implements EventTarget {
-  // Event type to use in the shortcut calls
+  // Event type to use in the shortcutted calls
   public static DEFAULT_EVENT = 'change';
 
   private readonly [LISTENERS]: Record<string, EventListenerOrEventListenerObject[]> = {};
@@ -18,13 +18,19 @@ export class SyntheticEventTarget implements EventTarget {
   protected getEventListeners(): EventListenerOrEventListenerObject[];
   protected getEventListeners(type: string): EventListenerOrEventListenerObject[];
   protected getEventListeners(type?: string): EventListenerOrEventListenerObject[] {
-    if (typeof type !== 'string') return uniq(Object.values(this[LISTENERS]).flat(1));
+    if (typeof type !== 'string') return uniq(flat(Object.values(this[LISTENERS])));
     return this[LISTENERS][type] || [];
   }
 
-  public hasEventListener(type?: string): boolean {
-    if (typeof type === 'string') return this.getEventListeners(type).length > 0;
-    return this.hasEventListener((this.constructor as typeof SyntheticEventTarget).DEFAULT_EVENT);
+  public hasEventListener(): boolean;
+  public hasEventListener(type: string): boolean;
+  /** @deprecated alias for `addEventListener` */
+  public hasEventListener(type: string | number): boolean;
+  /** @deprecated alias for `addEventListener` */
+  public hasEventListener(type: string, minCount: number): boolean;
+  public hasEventListener(type: string | number = 'change', minCount: number = 0): boolean {
+    if (typeof type !== 'string') return this.hasEventListener('change', type || 0); // TODO: remove in 5.0.0
+    return this.getEventListeners(type).length > minCount;
   }
 
   public addEventListener(callback: EventListenerOrEventListenerObject): void;
@@ -50,16 +56,29 @@ export class SyntheticEventTarget implements EventTarget {
     this[LISTENERS][type] = listeners.filter((cb) => cb !== callback);
   }
 
-  public dispatchEvent(e: Event): boolean {
+  public dispatchEvent(e: Event): boolean;
+  /** @deprecated use `overrideEvent` to declare `target` */
+  public dispatchEvent(e: Event, target?: EventTarget): boolean;
+  public dispatchEvent(e: Event, target?: EventTarget): boolean {
     overrideEvent(e, 'currentTarget', this);
+    if (target) overrideEvent(e, 'target', target); // TODO: remove in 5.0.0
     if (!e.target) overrideEvent(e, 'target', this);
-    if (!e.srcElement) overrideEvent(e, 'srcElement', e.target);
+    if (!e.srcElement) overrideEvent(e, 'srcElement', e.target); // TODO: remove in 5.0.0
     this.getEventListeners(e.type).forEach((listener) => {
       if (typeof listener === 'function') listener.call(this, e);
       else listener.handleEvent.call(listener, e);
     });
     return e.defaultPrevented;
   }
+
+  /** @deprecated alias for `addEventListener` */
+  public addListener(cb: EventListener): void {
+    this.addEventListener(cb);
+  }
+  /** @deprecated alias for `removeEventListener` */
+  public removeListener(cb: EventListener): void {
+    this.removeEventListener(cb);
+  }
 }
 
 function validateEventListenerType(callback: any): void | never {
diff --git a/src/modules/esl-utils/dom/rtl.ts b/src/modules/esl-utils/dom/rtl.ts
index d6e1cd2d1..ef011694c 100644
--- a/src/modules/esl-utils/dom/rtl.ts
+++ b/src/modules/esl-utils/dom/rtl.ts
@@ -54,3 +54,17 @@ export const normalizeScrollLeft = (el: HTMLElement, value: number | null = null
       return value;
   }
 };
+
+/** @deprecated use separate functions from the module */
+export abstract class RTLUtils {
+  /** @deprecated use {@link isRTL} instead */
+  static readonly isRtl = isRTL;
+
+  /** @deprecated use {@link normalizeScrollLeft} instead */
+  static readonly normalizeScrollLeft = normalizeScrollLeft;
+
+  /** @deprecated use {@link RTLScroll}.type instead */
+  static get scrollType(): ScrollType {
+    return RTLScroll.type;
+  }
+}
diff --git a/src/modules/esl-utils/dom/traversing.ts b/src/modules/esl-utils/dom/traversing.ts
index f9934f1a1..a0c5cd286 100644
--- a/src/modules/esl-utils/dom/traversing.ts
+++ b/src/modules/esl-utils/dom/traversing.ts
@@ -72,3 +72,18 @@ export const findNextLooped =
  */
 export const findPrevLooped =
   createSequenceFinder((el) => el.previousElementSibling || (el.parentElement && el.parentElement.lastElementChild));
+
+/** @deprecated Cumulative traversing utility set */
+export abstract class TraversingUtils {
+  static isRelative = isRelativeNode;
+  static closestBy = findClosestBy;
+  static createSequenceFinder = createSequenceFinder;
+
+  static findNext = findNext;
+  static findPrev = findPrev;
+  static findParent = findParent;
+  static findClosest = findClosest;
+
+  static findAll = findAll;
+  static findChildren = findChildren;
+}
diff --git a/src/modules/esl-utils/dom/visible.ts b/src/modules/esl-utils/dom/visible.ts
index 438e23e98..6f6d49004 100644
--- a/src/modules/esl-utils/dom/visible.ts
+++ b/src/modules/esl-utils/dom/visible.ts
@@ -1,6 +1,6 @@
 import {Rect} from './rect';
 import {isElement} from './api';
-import {getWindowRect} from './window';
+import {getViewportRect} from './window';
 import {getListScrollParents} from './scroll';
 import {findClosestBy, findHost} from './traversing';
 
@@ -36,7 +36,7 @@ export function isVisible(el: Element, options: VisibilityOptions = {visibility:
  * @param el - element to be checked
  */
 export function isInViewport(el: Element): boolean {
-  const wndIntersection = Rect.from(el).intersect(getWindowRect());
+  const wndIntersection = Rect.from(el).intersect(getViewportRect());
   if (wndIntersection.area === 0) return false;
   return !getListScrollParents(el).some(
     (parent: Element) => Rect.from(parent).intersect(wndIntersection).area === 0
diff --git a/src/modules/esl-utils/dom/window.ts b/src/modules/esl-utils/dom/window.ts
index 80411ef7e..ebfc16314 100644
--- a/src/modules/esl-utils/dom/window.ts
+++ b/src/modules/esl-utils/dom/window.ts
@@ -1,7 +1,7 @@
 import {Rect} from './rect';
 
 /**
- * Get the window object associated with a document of the specified element.
+ * Gets the window object associated with a document of the specified element.
  * @param node - element for which to get window
  * */
 export function getWindow(node: Node | Window): Window {
@@ -12,14 +12,27 @@ export function getWindow(node: Node | Window): Window {
 }
 
 /**
- * Get the size and position of the window.
- * @returns
+ * Gets the size and position of the window (size also accounts for the height of
+ * the horizontal scrollbar and width of the vertical scrollbar, if they are visible).
  */
 export function getWindowRect(wnd: Window = window): Rect {
   return Rect.from({
-    x: wnd.scrollX || wnd.pageXOffset,
-    y: wnd.scrollY || wnd.pageYOffset,
-    width: wnd.innerWidth || wnd.document.documentElement.clientWidth,
-    height: wnd.innerHeight || wnd.document.documentElement.clientHeight
+    x: wnd.scrollX,
+    y: wnd.scrollY,
+    width: wnd.innerWidth,
+    height: wnd.innerHeight
+  });
+}
+
+/**
+ * Gets the size and position of the viewport (like window rect but excluding
+ * the size of rendered scrollbars if any)
+ */
+export function getViewportRect(wnd: Window = window): Rect {
+  return Rect.from({
+    x: wnd.scrollX,
+    y: wnd.scrollY,
+    width: wnd.document.documentElement.clientWidth,
+    height: wnd.document.documentElement.clientHeight
   });
 }
diff --git a/src/modules/esl-utils/fixes.ts b/src/modules/esl-utils/fixes.ts
index 6265f18a8..5ea3790a0 100644
--- a/src/modules/esl-utils/fixes.ts
+++ b/src/modules/esl-utils/fixes.ts
@@ -1,1 +1,2 @@
+export * from './fixes/ie-fixes';
 export * from './fixes/viewport';
diff --git a/src/modules/esl-utils/fixes/ie-fixes.less b/src/modules/esl-utils/fixes/ie-fixes.less
new file mode 100644
index 000000000..f1b197cdb
--- /dev/null
+++ b/src/modules/esl-utils/fixes/ie-fixes.less
@@ -0,0 +1,10 @@
+.ie-zindex-fix {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 100%;
+  z-index: -1;
+  border: none;
+  visibility: hidden;
+}
diff --git a/src/modules/esl-utils/fixes/ie-fixes.ts b/src/modules/esl-utils/fixes/ie-fixes.ts
new file mode 100644
index 000000000..8f5980081
--- /dev/null
+++ b/src/modules/esl-utils/fixes/ie-fixes.ts
@@ -0,0 +1,7 @@
+/** Fix IE browser to allow to display alert under iframe */
+export function createZIndexIframe(): HTMLElement {
+  const iframe = document.createElement('iframe');
+  iframe.className = 'ie-zindex-fix';
+  iframe.src = 'about:blank';
+  return iframe;
+}
diff --git a/src/modules/esl-utils/fixes/viewport.ts b/src/modules/esl-utils/fixes/viewport.ts
index 87c8d0cda..b96e0ab29 100644
--- a/src/modules/esl-utils/fixes/viewport.ts
+++ b/src/modules/esl-utils/fixes/viewport.ts
@@ -1,4 +1,5 @@
 import {rafDecorator} from '../async/raf';
+import {DeviceDetector} from '../environment/device-detector';
 
 /**
  * Small utility to provide 100vw and 100vh alternative CSS Variables
@@ -11,6 +12,9 @@ export class ESLVSizeCSSProxy {
 
   /** Inits custom CSS variables for viewport sizes and it's observation */
   public static observe(): void {
+    // IE doesn’t support CSS Variables (hopefully same as 100vh issue :D)
+    if (DeviceDetector.isIE) return;
+
     this.update();
     window.addEventListener('resize', this.updateDebounced);
   }
diff --git a/src/modules/esl-utils/misc/array.ts b/src/modules/esl-utils/misc/array.ts
index 3e1eaa869..4662ed428 100644
--- a/src/modules/esl-utils/misc/array.ts
+++ b/src/modules/esl-utils/misc/array.ts
@@ -10,11 +10,9 @@ export const tuple = <T>(arr: T[]): Tuple<T>[] => arr.reduce((acc: Tuple<T>[], e
   return acc;
 }, []);
 
-/**
- * Flat array - unwraps one level of nested arrays
- * @deprecated use `Array.prototype.flat` instead
- */
-export const flat = <T>(arr: (null | T | T[])[]): T[] => arr.flat(1) as T[];
+/** Flat array - unwraps one level of nested arrays */
+export const flat = <T>(arr: (null | T | T[])[]): T[] =>
+  arr.reduce((acc: T[], el) => el ? acc.concat(el) : acc, []) as T[];
 
 /** Wraps passed object or primitive to array */
 export const wrap = <T>(arr: undefined | null | T | T[]): T[] => {
@@ -38,8 +36,14 @@ export function unwrap(value: any): any {
 }
 
 /** Makes array values unique */
-export const uniq = <T> (arr: T[]): T[] =>
-  arr.length > 1 ? [...new Set<T>(arr)] : arr.slice(0);
+export const uniq = <T> (arr: T[]): T[] => {
+  if (arr.length < 2) return arr.slice(0);
+  const result: T[] = [];
+  const set = new Set<T>();
+  arr.forEach((item) => set.add(item));
+  set.forEach((item) => result.push(item));
+  return result;
+};
 
 /** Create an array filled with the range [0,..,N-1] */
 export function range(n: number): number[];
diff --git a/src/modules/esl-utils/misc/object/compare.ts b/src/modules/esl-utils/misc/object/compare.ts
index 0c7af5d58..86177795d 100644
--- a/src/modules/esl-utils/misc/object/compare.ts
+++ b/src/modules/esl-utils/misc/object/compare.ts
@@ -13,6 +13,9 @@ export function isEqual(obj1: any, obj2: any): boolean {
   return false;
 }
 
+/** @deprecated alias for `isEqual` method */
+export const deepCompare = isEqual;
+
 /** Check if arr and arr mask has intersection */
 function isIntersect(arrObj: any[], arrMask: any[], comparer: (a: any, b: any) => boolean): boolean {
   return arrMask.every((key) => arrObj.some((itm) => comparer(itm, key)));
diff --git a/src/modules/esl-utils/misc/set.ts b/src/modules/esl-utils/misc/set.ts
index b66d85fb1..05ff92074 100644
--- a/src/modules/esl-utils/misc/set.ts
+++ b/src/modules/esl-utils/misc/set.ts
@@ -8,9 +8,11 @@ export function intersection<T>(a: T[], b: T[], ...rest: T[][]): T[] {
 
 /** Create an array with unique values from each of the passed arrays */
 export function union<T>(...rest: T[][]): T[]  {
-  const set = new Set<T>();
+  const set = new Set();
   rest.forEach((item) => item.forEach((i) => set.add(i)));
-  return [...set];
+  const result: any[] = [];
+  set.forEach((value) => result.push(value));
+  return result;
 }
 
 /** Creates an array of unique values from the first array that are not present in the other arrays */
diff --git a/src/modules/esl-utils/misc/uid.ts b/src/modules/esl-utils/misc/uid.ts
index 721ba0de6..d43d1ca53 100644
--- a/src/modules/esl-utils/misc/uid.ts
+++ b/src/modules/esl-utils/misc/uid.ts
@@ -22,3 +22,9 @@ export const randUID = (prefix: string = ''): string => {
   const rand = Math.round(Math.random() * 1024 * 1024).toString(32);
   return prefix + time + '-' + rand;
 };
+
+/**
+ * Generate unique id
+ * @deprecated Alias for {@link randUID}
+ */
+export const generateUId = randUID;
diff --git a/src/polyfills/list/dom.element.closest.ts b/src/polyfills/list/dom.element.closest.ts
new file mode 100644
index 000000000..45ca9868b
--- /dev/null
+++ b/src/polyfills/list/dom.element.closest.ts
@@ -0,0 +1,22 @@
+/**
+ * Group: DOM API shims ~ ES6 shim group
+ * Target Browsers: `IE11`, `Edge < 14`
+ * Element.closest polyfill
+ */
+(function (e: ElementEx): void {
+  e.matches = e.matches || e.msMatchesSelector || e.mozMatchesSelector || e.webkitMatchesSelector;
+  e.closest = e.closest || function (css: string): Element | null {
+    // eslint-disable-next-line @typescript-eslint/no-this-alias
+    let node = this;
+    while (node) {
+      if (node.matches(css)) return node;
+      node = node.parentElement;
+    }
+    return null;
+  };
+})(Element.prototype as ElementEx);
+
+interface ElementEx extends Element {
+  msMatchesSelector?(selectors: string): boolean;
+  mozMatchesSelector?(selectors: string): boolean;
+}
diff --git a/src/polyfills/list/dom.element.toggleAttribute.ts b/src/polyfills/list/dom.element.toggleAttribute.ts
new file mode 100644
index 000000000..fc794a33b
--- /dev/null
+++ b/src/polyfills/list/dom.element.toggleAttribute.ts
@@ -0,0 +1,16 @@
+/**
+ * Group: DOM API shims
+ * Target Browsers: `IE11`, `Edge < 18`, `Safari < 13`
+ * Element.toggleAttribute polyfill
+ */
+if (!Element.prototype.toggleAttribute) {
+  Element.prototype.toggleAttribute = function (name: string, force: boolean | undefined): boolean {
+    const has = this.hasAttribute(name);
+    const state = force === void 0 ? !has : !!force;
+
+    if (has !== state) {
+      (state) ? this.setAttribute(name, '') : this.removeAttribute(name);
+    }
+    return state;
+  };
+}
diff --git a/src/polyfills/list/dom.event.base.ts b/src/polyfills/list/dom.event.base.ts
new file mode 100644
index 000000000..49ad7c8fa
--- /dev/null
+++ b/src/polyfills/list/dom.event.base.ts
@@ -0,0 +1,40 @@
+/**
+ * Group: DOM API shims
+ * Target Browsers: `IE11`, `Edge < 18`
+ * Event constructor & prevent default polyfill
+ */
+try {
+  new window.Event('event', {bubbles: true, cancelable: true});
+} catch (error) {
+  const EventOriginal = window.CustomEvent || window.Event;
+  const preventDefaultOriginal = EventOriginal.prototype.preventDefault;
+  const Event = function (eventName: string, params: EventInit): Event {
+    params = params || {};
+    const event = document.createEvent('Event');
+    event.initEvent(
+      eventName,
+      (params.bubbles === void 0) ? false : params.bubbles,
+      (params.cancelable === void 0) ? false : params.cancelable
+    );
+    return event;
+  };
+  Event.prototype = EventOriginal.prototype;
+  // defaultPrevented is broken in IE.
+  // https://connect.microsoft.com/IE/feedback/details/790389/event-defaultprevented-returns-false-after-preventdefault-was-called
+  Event.prototype.preventDefault = function (): void {
+    if (!this.cancelable) return;
+    preventDefaultOriginal.call(this);
+    Object.defineProperty(this, 'defaultPrevented', {
+      get() {
+        return true;
+      },
+      configurable: true
+    });
+  };
+  Object.defineProperty(window, 'Event', {
+    value: Event,
+    writable: true,
+    configurable: true
+  });
+}
+
diff --git a/src/polyfills/list/dom.event.custom.ts b/src/polyfills/list/dom.event.custom.ts
new file mode 100644
index 000000000..de39ee31a
--- /dev/null
+++ b/src/polyfills/list/dom.event.custom.ts
@@ -0,0 +1,27 @@
+/**
+ * Group: DOM API shims
+ * Target Browsers: `IE11`, `Edge < 18`
+ * CustomEvent polyfill
+ */
+try {
+  new window.CustomEvent('event', {bubbles: true, cancelable: true});
+} catch (error) {
+  const CustomEventOriginal = window.CustomEvent || window.Event;
+  const CustomEvent = function (eventName: string, params: CustomEventInit): CustomEvent {
+    params = params || {};
+    const event = document.createEvent('CustomEvent');
+    event.initCustomEvent(
+      eventName,
+      (params.bubbles === void 0) ? false : params.bubbles,
+      (params.cancelable === void 0) ? false : params.cancelable,
+      (params.detail === void 0) ? {} : params.detail
+    );
+    return event;
+  };
+  CustomEvent.prototype = CustomEventOriginal.prototype;
+  Object.defineProperty(window, 'CustomEvent', {
+    value: CustomEvent,
+    writable: true,
+    configurable: true
+  });
+}
diff --git a/src/polyfills/list/dom.event.focus.ts b/src/polyfills/list/dom.event.focus.ts
new file mode 100644
index 000000000..7c2a28801
--- /dev/null
+++ b/src/polyfills/list/dom.event.focus.ts
@@ -0,0 +1,32 @@
+/**
+ * Group: DOM API shims
+ * Target Browsers: `IE11`, `Edge < 18`
+ * FocusEvent polyfill: https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent
+ */
+try {
+  new window.FocusEvent('event', {bubbles: true, cancelable: true});
+} catch (error) {
+  const FocusEventOriginal = window.FocusEvent || window.Event;
+  const FocusEvent = function (eventName: string, params: FocusEventInit): FocusEvent {
+    params = params || {};
+    const event: any = document.createEvent('FocusEvent');
+
+    // https://msdn.microsoft.com/en-us/library/ff975954(v=vs.85).aspx
+    event.initFocusEvent(
+      eventName,
+      (params.bubbles === void 0) ? false : params.bubbles,
+      (params.cancelable === void 0) ? false : params.cancelable,
+      (params.view === void 0) ? window : params.view,
+      (params.detail === void 0) ? {} : params.detail,
+      (params.relatedTarget === void 0) ? null : params.relatedTarget
+    );
+
+    return event;
+  };
+  FocusEvent.prototype = FocusEventOriginal.prototype;
+  Object.defineProperty(window, 'FocusEvent', {
+    value: FocusEvent,
+    writable: true,
+    configurable: true
+  });
+}
diff --git a/src/polyfills/list/dom.event.keyboard.ts b/src/polyfills/list/dom.event.keyboard.ts
new file mode 100644
index 000000000..ae16d6b55
--- /dev/null
+++ b/src/polyfills/list/dom.event.keyboard.ts
@@ -0,0 +1,43 @@
+/**
+ * Group: DOM API shims
+ * Target Browsers: `IE11`, `Edge < 18`
+ * KeyboardEvent polyfill: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent
+ */
+try {
+  new window.KeyboardEvent('event', {bubbles: true, cancelable: true});
+} catch (error) {
+  const KeyboardEventOriginal = window.KeyboardEvent || window.Event;
+  // eslint-disable-next-line sonarjs/cognitive-complexity
+  const KeyboardEvent = function (eventName: string, params: KeyboardEventInit): KeyboardEvent {
+    params = params || {};
+    const event: any = document.createEvent('KeyboardEvent');
+
+    // https://msdn.microsoft.com/en-us/library/ff975297(v=vs.85).aspx
+    event.initKeyboardEvent(
+      eventName,
+      (params.bubbles === void 0) ? false : params.bubbles,
+      (params.cancelable === void 0) ? false : params.cancelable,
+      (params.view === void 0) ? window : params.view,
+      (params.key === void 0) ? '' : params.key,
+      (params.location === void 0) ? 0 : params.location,
+      params.ctrlKey === true,
+      params.altKey === true,
+      params.shiftKey === true,
+      params.metaKey === true
+    );
+
+    event.keyCode   = (params.keyCode === void 0) ? 0 : params.keyCode;
+    event.code      = (params.code === void 0) ? '' : params.code;
+    event.charCode  = (params.charCode === void 0) ? 0 : params.charCode;
+    event.char      = (params.charCode === void 0) ? '' : params.charCode;
+    event.which     = (params.which === void 0) ? 0 : params.which;
+
+    return event;
+  };
+  KeyboardEvent.prototype = KeyboardEventOriginal.prototype;
+  Object.defineProperty(window, 'KeyboardEvent', {
+    value: KeyboardEvent,
+    writable: true,
+    configurable: true
+  });
+}
diff --git a/src/polyfills/list/dom.event.mouse.ts b/src/polyfills/list/dom.event.mouse.ts
new file mode 100644
index 000000000..fc2c5ec39
--- /dev/null
+++ b/src/polyfills/list/dom.event.mouse.ts
@@ -0,0 +1,45 @@
+/**
+ * Group: DOM API shims
+ * Target Browsers: `IE11`, `Edge < 18`
+ * MouseEvent polyfill: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent
+ */
+try {
+  new window.MouseEvent('event', {bubbles: true, cancelable: true});
+} catch (error) {
+  const MouseEventOriginal = window.MouseEvent || window.Event;
+  // eslint-disable-next-line sonarjs/cognitive-complexity
+  const MouseEvent = function (eventName: string, params: MouseEventInit): MouseEvent {
+    params = params || {};
+    const event: any = document.createEvent('MouseEvent');
+
+    // https://msdn.microsoft.com/en-us/library/ff975292(v=vs.85).aspx
+    event.initMouseEvent(
+      eventName,
+      (params.bubbles === void 0) ? false : params.bubbles,
+      (params.cancelable === void 0) ? false : params.cancelable,
+      (params.view === void 0) ? window : params.view!,
+      (params.detail === void 0) ? 0 : params.detail,
+      (params.screenX === void 0) ? 0 : params.screenX,
+      (params.screenY === void 0) ? 0 : params.screenY,
+      (params.clientX === void 0) ? 0 : params.clientX,
+      (params.clientY === void 0) ? 0 : params.clientY,
+      (params.ctrlKey === void 0) ? false : params.ctrlKey,
+      (params.altKey === void 0) ? false : params.altKey,
+      (params.shiftKey === void 0) ? false : params.shiftKey,
+      (params.metaKey === void 0) ? false : params.metaKey,
+      (params.button === void 0) ? 0 : params.button,
+      (params.relatedTarget === void 0) ? null : params.relatedTarget
+    );
+
+    event.buttons = (params.buttons === void 0) ? 0 : params.buttons;
+    event.region  = ((params as any).region === void 0) ? null : (params as any).region;
+
+    return event;
+  };
+  MouseEvent.prototype = MouseEventOriginal.prototype;
+  Object.defineProperty(window, 'MouseEvent', {
+    value: MouseEvent,
+    writable: true,
+    configurable: true
+  });
+}
diff --git a/src/polyfills/list/dom.keyboard.key.ts b/src/polyfills/list/dom.keyboard.key.ts
new file mode 100644
index 000000000..984846913
--- /dev/null
+++ b/src/polyfills/list/dom.keyboard.key.ts
@@ -0,0 +1,38 @@
+/**
+ * Group: DOM API shims
+ * Target Browsers: `IE11`, `Edge < 18`
+ * KeyboardEvent.prototype.key shim to normalize key values to W3C spec
+ * Based on published shim https://www.npmjs.com/package/shim-keyboard-event-key
+ */
+(function (KeyboardEventProto): void {
+  const desc = Object.getOwnPropertyDescriptor(KeyboardEventProto, 'key');
+  if (!desc) return;
+
+  const keyMap: Record<string, string> = {
+    Win: 'Meta',
+    Scroll: 'ScrollLock',
+    Spacebar: ' ',
+
+    Down: 'ArrowDown',
+    Left: 'ArrowLeft',
+    Right: 'ArrowRight',
+    Up: 'ArrowUp',
+
+    Del: 'Delete',
+    Apps: 'ContextMenu',
+    Esc: 'Escape',
+
+    Multiply: '*',
+    Add: '+',
+    Subtract: '-',
+    Decimal: '.',
+    Divide: '/',
+  };
+
+  Object.defineProperty(KeyboardEventProto, 'key', {
+    get() {
+      const key = desc.get!.call(this);
+      return Object.prototype.hasOwnProperty.call(keyMap, key) ? keyMap[key] : key;
+    },
+  });
+})(KeyboardEvent.prototype);
diff --git a/src/polyfills/list/dom.node.isConnected.ts b/src/polyfills/list/dom.node.isConnected.ts
new file mode 100644
index 000000000..3044301e6
--- /dev/null
+++ b/src/polyfills/list/dom.node.isConnected.ts
@@ -0,0 +1,14 @@
+/**
+ * Group: DOM polyfills
+ * Target browsers: `IE11`, `Edge <= 18`
+ * Node.isConnected polyfill
+ */
+if (!('isConnected' in Node.prototype)) {
+  Object.defineProperty(Node.prototype, 'isConnected', {
+    get() {
+      return !this.ownerDocument ||
+        // eslint-disable-next-line no-bitwise
+        !(this.ownerDocument.compareDocumentPosition(this) & this.DOCUMENT_POSITION_DISCONNECTED);
+    }
+  });
+}
diff --git a/src/polyfills/list/dom.tokenList.toggle.ts b/src/polyfills/list/dom.tokenList.toggle.ts
new file mode 100644
index 000000000..67257dcab
--- /dev/null
+++ b/src/polyfills/list/dom.tokenList.toggle.ts
@@ -0,0 +1,21 @@
+/**
+ * Group: ES6 shims
+ * Target Browsers: `IE11`
+ * DOMTokenList.toggle polyfill to support force option
+ */
+(function (DOMTokenListProto): void {
+  const nativeToggle = DOMTokenListProto.toggle;
+  const testTokenList = window.document.createElement('div').classList;
+
+  if (!DOMTokenListProto.toggle) return;
+  // eslint-disable-next-line
+  if (testTokenList.toggle('a', false) === false) return;
+
+  DOMTokenListProto.toggle = function toggle(val: string, force?: boolean): boolean {
+    if (arguments.length > 1) {
+      this[force ? 'add' : 'remove'](val);
+      return !!force;
+    }
+    return nativeToggle.call(this, val);
+  };
+})(window.DOMTokenList.prototype);
diff --git a/src/polyfills/list/es6.array.find.ts b/src/polyfills/list/es6.array.find.ts
new file mode 100644
index 000000000..62a6d79fc
--- /dev/null
+++ b/src/polyfills/list/es6.array.find.ts
@@ -0,0 +1,32 @@
+type FindPredicate<T> = (T: any, index: number, array: T[]) => boolean;
+
+/**
+ * Group: ES6 shim
+ * Target Browsers: `IE11`, `Edge < 14`
+ * Array.prototype.findIndex && Array.prototype.findIndex
+ */
+if (typeof Array.prototype.findIndex !== 'function') {
+  const findHelper = function <T> (array: T[], predicate: FindPredicate<T>, context: any): {item: T | null, index: number} {
+    if (typeof predicate !== 'function') throw new TypeError('predicate must be a function');
+
+    const list = Object(array);
+    // eslint-disable-next-line no-bitwise
+    const length = list.length >>> 0;
+
+    for (let i = 0; i < length; i++) {
+      if (predicate.call(context, list[i], i, list)) {
+        return {item: list[i], index: i};
+      }
+    }
+    return {item: null, index: -1};
+  };
+
+  Array.prototype.find = function <T> (predicate: FindPredicate<T>, context: any = window): T | null {
+    if (!this) throw new TypeError('Array.prototype.find called on null or undefined');
+    return findHelper(this, predicate, context).item;
+  };
+  Array.prototype.findIndex = function <T> (predicate: FindPredicate<T>, context: any = window): number {
+    if (!this) throw new TypeError('Array.prototype.findIndex called on null or undefined');
+    return findHelper(this, predicate, context).index;
+  };
+}
diff --git a/src/polyfills/list/es6.array.from.ts b/src/polyfills/list/es6.array.from.ts
new file mode 100644
index 000000000..05ac0684c
--- /dev/null
+++ b/src/polyfills/list/es6.array.from.ts
@@ -0,0 +1,11 @@
+/**
+ * Group: ES6 shim
+ * Target Browsers: `IE11`
+ * Array.from polyfill
+ * Note: does not support Iterables, consider polyfill from https://github.com/rousan/symbol-es6
+ */
+if (!Array.from) {
+  Array.from = function <T>(object: ArrayLike<T>): T[] {
+    return [].slice.call(object);
+  };
+}
diff --git a/src/polyfills/list/es6.array.includes.ts b/src/polyfills/list/es6.array.includes.ts
new file mode 100644
index 000000000..1a7475b1e
--- /dev/null
+++ b/src/polyfills/list/es6.array.includes.ts
@@ -0,0 +1,14 @@
+/**
+ * Group: ES6 shim
+ * Target Browsers: `IE11`, `Edge < 14`
+ * Array.prototype.includes polyfill
+ */
+if (!Array.prototype.includes) {
+  Array.prototype.includes = function <T> (item: T, fromIndex?: number): boolean {
+    if (item !== item) {
+      // NaN case
+      return Array.prototype.slice.call(this, fromIndex).some((i: T) => i !== i);
+    }
+    return Array.prototype.indexOf.call(this, item, fromIndex) !== -1;
+  };
+}
diff --git a/src/polyfills/list/es6.object.assign.ts b/src/polyfills/list/es6.object.assign.ts
new file mode 100644
index 000000000..61aaa5576
--- /dev/null
+++ b/src/polyfills/list/es6.object.assign.ts
@@ -0,0 +1,38 @@
+/**
+ * Group: ES6 shim
+ * Target Browsers: `IE11`
+ * Object.assign polyfill
+ */
+function toObject(value: any): any {
+  if (value === undefined || value === null) throw new TypeError('Cannot convert undefined or null to object');
+  if (typeof value === 'boolean') return new Boolean(value);
+  if (typeof value === 'number') return new Number(value);
+  if (typeof value === 'string') return new String(value);
+  if (typeof value === 'symbol') return Object(value);
+  return value;
+}
+
+function copyProperty(from: any, to: any, name: string): void {
+  const desc = Object.getOwnPropertyDescriptor(from, name);
+  if (desc !== undefined && desc.enumerable) to[name] = from[name];
+}
+
+if (!Object.assign) {
+  Object.assign = function assign(target: any, ...sources: any[]): any {
+    if (this instanceof Object.assign) throw new TypeError('Invoked as a constructor');
+    const to = toObject(target);
+    for (let i = 0; i < sources.length; i++) {
+      const nextSource = sources[i];
+      if (nextSource !== undefined && nextSource !== null) {
+        const from = toObject(nextSource);
+        const keys = Object.getOwnPropertyNames(from);
+        keys.forEach(copyProperty.bind(null, from, to));
+        if (Object.getOwnPropertySymbols) {
+          const symbols = Object.getOwnPropertySymbols(from);
+          symbols.forEach(copyProperty.bind(null, from, to));
+        }
+      }
+    }
+    return to;
+  };
+}
diff --git a/src/polyfills/list/es6.object.is.ts b/src/polyfills/list/es6.object.is.ts
new file mode 100644
index 000000000..e34fba24c
--- /dev/null
+++ b/src/polyfills/list/es6.object.is.ts
@@ -0,0 +1,15 @@
+/**
+ * Group: ES6 shims
+ * Target Browsers: `IE11`
+ * Object.is support
+ * Blatantly stolen from ES6 specs. Kept here for reference.
+ * http://wiki.ecmascript.org/doku.php?id=harmony:egal
+ */
+if (!Object.is) {
+  Object.defineProperty(Object, 'is', {
+    value(x: any, y: any) {
+      return (x === y) ? (x !== 0 || 1 / x === 1 / y) : (x !== x && y !== y);
+    },
+    enumerable: false
+  });
+}
diff --git a/src/polyfills/polyfills.es5.ts b/src/polyfills/polyfills.es5.ts
new file mode 100644
index 000000000..8256d1c08
--- /dev/null
+++ b/src/polyfills/polyfills.es5.ts
@@ -0,0 +1,15 @@
+import './list/es6.object.is';
+import './list/es6.object.assign';
+
+import './list/es6.array.from';
+import './list/es6.array.find';
+import './list/es6.array.includes';
+
+import './list/dom.event.base';
+import './list/dom.event.custom';
+import './list/dom.event.mouse';
+import './list/dom.event.keyboard';
+import './list/dom.event.focus';
+
+import './list/dom.element.closest';
+import './list/dom.tokenList.toggle';
diff --git a/src/polyfills/polyfills.es6.ts b/src/polyfills/polyfills.es6.ts
new file mode 100644
index 000000000..075c9002f
--- /dev/null
+++ b/src/polyfills/polyfills.es6.ts
@@ -0,0 +1,3 @@
+import './list/dom.node.isConnected';
+import './list/dom.keyboard.key';
+import './list/dom.element.toggleAttribute';


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 259834c44..3daa05818 100644
--- a/src/modules/esl-base-element/test/element.listener.test.ts
+++ b/src/modules/esl-base-element/test/element.listener.test.ts
@@ -1,7 +1,6 @@
 import '../../../polyfills/es5-target-shim';
 
-import {ESLBaseElement} from '../core';
-import {listen} from '../../esl-utils/decorators/listen';
+import {ESLBaseElement, listen} from '../core';
 import {randUID} from '../../esl-utils/misc/uid';
 import {ESLEventUtils} from '../../esl-utils/dom/events';
 
diff --git a/src/modules/esl-event-listener/test/listener.subscribe.test.ts b/src/modules/esl-event-listener/test/listener.subscribe.test.ts
index e7bdaeec4..bc32bc9da 100644
--- a/src/modules/esl-event-listener/test/listener.subscribe.test.ts
+++ b/src/modules/esl-event-listener/test/listener.subscribe.test.ts
@@ -73,33 +73,4 @@ describe('ESLEventUtils:subscribe tests', () => {
     ESLEventUtils.subscribe(host, {event: 'click'}, handle);
     expect(ESLEventUtils.listeners(host).length).toBe(0);
   });
-
-  describe('ESLEventListener subscribes correctly for any object-like host', () => {
-    test.each([
-      {},
-      Object.create(null),
-      function hostFunction() {},
-      class HostClass {},
-      new (class HostClassInst {})()
-    ])('ESLEventListener subscribes correctly for %o host',  (host) => {
-      const handle = jest.fn();
-      ESLEventUtils.subscribe(host, {event: 'click', target: document.body}, handle);
-      expect(ESLEventUtils.listeners(host).length).toBe(1);
-      ESLEventUtils.unsubscribe(host);
-    });
-
-    test('ESLEventListener can not subscribe for primitive host', () => {
-      jest.spyOn(console, 'warn').mockImplementationOnce(() => {});
-      const handle = jest.fn();
-      const count = ESLEventUtils.subscribe(1 as any, {event: 'click', target: document.body}, handle).length;
-      expect(count).toBe(0);
-    });
-
-    test('ESLEventListener can not subscribe for null host', () => {
-      jest.spyOn(console, 'warn').mockImplementationOnce(() => {});
-      const handle = jest.fn();
-      const count = ESLEventUtils.subscribe(null as any, {event: 'click', target: document.body}, handle).length;
-      expect(count).toBe(0);
-    });
-  });
 });
diff --git a/src/modules/esl-media/test/esl-media.condition.test.ts b/src/modules/esl-media/test/esl-media.condition.test.ts
index f5d4c0dec..769eabc55 100644
--- a/src/modules/esl-media/test/esl-media.condition.test.ts
+++ b/src/modules/esl-media/test/esl-media.condition.test.ts
@@ -44,6 +44,67 @@ describe('esl-media: lazy loading unit tests', () => {
     expect($media.querySelectorAll('*')).not.toEqual(expect.objectContaining({length: 0}));
   });
 
+  describe('ESLMedia legacy load condition classes', () => {
+    test('ESLMedia declining condition leads to "declined" class on the target', async () => {
+      const $media = createMedia({
+        loadCondition: 'not all',
+        loadClsTarget: 'body',
+        loadClsAccepted: 'accepted',
+        loadClsDeclined: 'declined'
+      });
+      document.body.appendChild($media);
+
+      await promisifyTimeout(100);
+      expect(document.body.classList.contains('accepted')).toBe(false);
+      expect(document.body.classList.contains('declined')).toBe(true);
+    });
+
+    test('ESLMedia accepting condition leads to "accepted" class on the target', async () => {
+      const $media = createMedia({
+        loadCondition: 'all',
+        loadClsTarget: 'body',
+        loadClsAccepted: 'accepted',
+        loadClsDeclined: 'declined'
+      });
+      document.body.appendChild($media);
+
+      await promisifyTimeout(100);
+      expect(document.body.classList.contains('accepted')).toBe(true);
+      expect(document.body.classList.contains('declined')).toBe(false);
+    });
+
+    test('ESLMedia load condition does not clock "declined" class on the target', async () => {
+      const $media = createMedia({
+        lazy: 'manual',
+        loadCondition: 'not all',
+        loadClsTarget: 'body',
+        loadClsAccepted: 'accepted',
+        loadClsDeclined: 'declined'
+      });
+      document.body.appendChild($media);
+
+      await promisifyTimeout(100);
+      expect(document.body.classList.contains('accepted')).toBe(false);
+      expect(document.body.classList.contains('declined')).toBe(true);
+    });
+
+    test('ESLMedia load condition does not clock "accepted" class on the target', async () => {
+      const $media = createMedia({
+        lazy: 'manual',
+        loadCondition: 'all',
+        loadClsTarget: 'body',
+        loadClsAccepted: 'accepted',
+        loadClsDeclined: 'declined'
+      });
+      document.body.appendChild($media);
+
+      await promisifyTimeout(100);
+      expect(document.body.classList.contains('accepted')).toBe(true);
+      expect(document.body.classList.contains('declined')).toBe(false);
+    });
+  });
+
+
   describe('ESLMedia load condition classes', () => {
     test('ESLMedia declining condition leads to "declined" class on the target', async () => {
       const $media = createMedia({
diff --git a/src/modules/esl-mixin-element/test/mixin.listener.test.ts b/src/modules/esl-mixin-element/test/mixin.listener.test.ts
index 95792e911..8e197f5a7 100644
--- a/src/modules/esl-mixin-element/test/mixin.listener.test.ts
+++ b/src/modules/esl-mixin-element/test/mixin.listener.test.ts
@@ -1,5 +1,4 @@
-import {ESLMixinElement} from '../core';
-import {listen} from '../../esl-utils/decorators/listen';
+import {ESLMixinElement, listen} from '../core';
 import {ESLEventUtils} from '../../esl-utils/dom/events';
 
 describe('ESLMixinElement: listeners', () => {
diff --git a/src/modules/esl-utils/dom/events/test/target.test.ts b/src/modules/esl-utils/dom/events/test/target.test.ts
index faecbc7eb..918a32ba7 100644
--- a/src/modules/esl-utils/dom/events/test/target.test.ts
+++ b/src/modules/esl-utils/dom/events/test/target.test.ts
@@ -30,6 +30,18 @@ describe('dom/events: SyntheticEventTarget', () => {
         expect(listener).toHaveBeenLastCalledWith(event2);
       });
 
+      test('listener should be stored', () => {
+        expect(et.hasEventListener('change', 0)).toBe(true);
+      });
+
+      test('target shouldn`t have more listeners than it actually has', () => {
+        expect(et.hasEventListener('change', 1)).toBe(false);
+      });
+
+      test('listener shouldn`t be stored for wrong event type', () => {
+        expect(et.hasEventListener('change2', 0)).toBe(false);
+      });
+
       test('listener shouldn`t be called third time', () => {
         et.removeEventListener('change', listener);
         et.dispatchEvent(event3);
@@ -69,6 +81,10 @@ describe('dom/events: SyntheticEventTarget', () => {
         expect(et.hasEventListener('change2')).toBe(false);
       });
 
+      test('target shouldn`t have more listeners than it actually has', () => {
+        expect(et.hasEventListener(1)).toBe(false);
+      });
+
       test('listener shouldn`t be called second time', () => {
         et.removeEventListener(listener);
         et.dispatchEvent(event2);
@@ -78,6 +94,28 @@ describe('dom/events: SyntheticEventTarget', () => {
       afterAll(() => jest.clearAllMocks());
     });
 
+    describe('Legacy functionality', () => {
+      const et = new SyntheticEventTarget();
+
+      const event1 = new CustomEvent('change');
+      const event2 = new CustomEvent('change');
+      const listener = jest.fn();
+      et.addListener(listener);
+
+      test('listener shoudn`t be called', () => expect(listener).toBeCalledTimes(0));
+
+      test('listener should be called once', () => {
+        et.dispatchEvent(event1);
+        expect(listener).toBeCalledWith(event1);
+      });
+
+      test('listener shouldn`t be called second time', () => {
+        et.removeListener(listener);
+        et.dispatchEvent(event2);
+        expect(listener).toBeCalledTimes(1);
+      });
+    });
+
     describe('API restriction', () => {
       const et = new SyntheticEventTarget();
       // @ts-ignore
@@ -170,4 +208,19 @@ describe('dom/events: SyntheticEventTarget', () => {
       });
     });
   });
+
+  describe('Listener duplicate subscription', () => {
+    const et = new SyntheticEventTarget();
+    const listener = jest.fn(() => {});
+
+    test('event should be subscribed', () => {
+      et.addEventListener('change', listener);
+      expect(et.hasEventListener(1)).toBe(false);
+    });
+
+    test('event shouldn`t be subscribed again', () => {
+      et.addEventListener('change', listener);
+      expect(et.hasEventListener(1)).toBe(false);
+    });
+  });
 });
diff --git a/src/modules/esl-utils/dom/test/visible.test.ts b/src/modules/esl-utils/dom/test/visible.test.ts
index b2d2fe927..a382dbd8f 100644
--- a/src/modules/esl-utils/dom/test/visible.test.ts
+++ b/src/modules/esl-utils/dom/test/visible.test.ts
@@ -15,7 +15,7 @@ jest.mock('../scroll', () => ({
 }));
 
 jest.mock('../window', () => ({
-  getWindowRect: () => Rect.from({x: 0, y: 0, width: 2000, height: 2000})
+  getViewportRect: () => Rect.from({x: 0, y: 0, width: 2000, height: 2000})
 }));
 
 const mockClientRects = (el: HTMLElement) => jest.spyOn(el, 'getClientRects').mockReturnValue([{}] as any);
diff --git a/src/modules/esl-utils/misc/test/array.test.ts b/src/modules/esl-utils/misc/test/array.test.ts
index bec218a58..4739782f6 100644
--- a/src/modules/esl-utils/misc/test/array.test.ts
+++ b/src/modules/esl-utils/misc/test/array.test.ts
@@ -13,13 +13,12 @@ describe('misc/array helper tests', () => {
 
   test('flat', () => {
     expect(flat([])).toEqual([]);
-    expect(flat([0])).toEqual([0]);
     expect(flat([1])).toEqual([1]);
     expect(flat([1, 2])).toEqual([1, 2]);
     expect(flat([1, [2, 3]])).toEqual([1, 2, 3]);
     expect(flat([[1, 2], [3, 4]])).toEqual([1, 2, 3, 4]);
     expect(flat([[1], [2, 3, 4], [], [5]])).toEqual([1, 2, 3, 4, 5]);
-    expect(flat([null, 1, 2, 3, [4, 5], null, [6]])).toEqual([null, 1, 2, 3, 4, 5, null, 6]);
+    expect(flat([null, 1, 2, 3, [4, 5], null, [6]])).toEqual([1, 2, 3, 4, 5, 6]);
   });
 
   test('wrap', () => {
@@ -44,7 +43,6 @@ describe('misc/array helper tests', () => {
 
   test('range', () => {
     expect(range(3)).toEqual([0, 1, 2]);
-    // @ts-ignore
     expect(range(9, (x) => x / 8)).toEqual([...Array(9).keys()].map((x) => x / 8));
   });
 
diff --git a/src/polyfills/test/es6.object.assign.test.ts b/src/polyfills/test/es6.object.assign.test.ts
new file mode 100644
index 000000000..58d1aa741
--- /dev/null
+++ b/src/polyfills/test/es6.object.assign.test.ts
@@ -0,0 +1,394 @@
+const originalAssignFn = Object.assign;
+Object.defineProperty(Object, 'assign', {writable: true, value: undefined});
+import '../list/es6.object.assign';
+
+class TestError extends Error {}
+
+afterAll(() => {
+  Object.assign = originalAssignFn;
+});
+
+describe('Object.assign() polyfill', () => {
+  test('object override sameproperty', () => {
+    // Object properties are assigned to target in ascending index order,
+    // i.e. a later assignment to the same property overrides an earlier assignment.
+    const target = {a: 1};
+    const result = Object.assign(target, {a: 2}, {a: 'c'});
+    expect(result.a).toBe('c');
+  });
+
+  test('only one argument', () => {
+    // Should return ToObject(target)
+    const target = 'a';
+    const result = Object.assign(target as any);
+    expect(typeof result).toBe('object');
+    expect(result.valueOf()).toEqual('a');
+  });
+
+  test('override notstringtarget', () => {
+    // Every string from sources will be wrapped to objects, and override from the first letter(result[0]) all the time
+    const target = 12;
+    const result = Object.assign(target, 'aaa', 'bb2b', '1c');
+    expect(Object.getOwnPropertyNames(result).length).toBe(4);
+    expect(result[0]).toBe('1');
+    expect(result[1]).toBe('c');
+    expect(result[2]).toBe('2');
+    expect(result[3]).toBe('b');
+  });
+
+  test('override', () => {
+    const target = {a: 1};
+    /*
+    '1a2c3' have own enumerable properties, so it Should be wrapped to objects;
+    {b:6} is an object,should be assigned to final object.
+    undefined and null should be ignored;
+    125 is a number,it cannot has own enumerable properties;
+    {a:'c'},{a:5} will override property a, the value should be 5.
+    */
+    const result = Object.assign(target, '1a2c3', {a: 'c'}, undefined, {b: 6}, null, 125, {a: 5});
+    expect(Object.getOwnPropertyNames(result).length).toBe(7);
+    expect(result.a).toBe(5);
+    expect(result[0]).toBe('1');
+    expect(result[1]).toBe('a');
+    expect(result[2]).toBe('2');
+    expect(result[3]).toBe('c');
+    expect(result[4]).toBe('3');
+    expect(result.b).toBe(6);
+  });
+
+  test('source is Null Undefined', () => {
+    const target = new Object();
+    // null and undefined source should be ignored, result should be original object.
+    const result = Object.assign(target, undefined, null);
+    expect(result).toEqual(target);
+  });
+
+  test('source is Number Boolean Symbol', () => {
+    const target = new Object();
+    /*
+    Number, Boolean, and Symbol cannot have their own enumerable properties,
+    so cannot be Assigned. Here result should be the original object.
+    */
+    const result = Object.assign(target, 123, true, Symbol('foo'));
+    expect(result).toEqual(target);
+  });
+
+  test('source is String', () => {
+    const target = new Object();
+    // string have own enumerable properties, so it can be wrapped to objects.
+    const result = Object.assign(target, '123');
+    expect(result[0]).toBe('1');
+    expect(result[1]).toBe('2');
+    expect(result[2]).toBe('3');
+  });
+
+  test('target is Boolean', () => {
+    // if target is Boolean,the return value should be a new object whose value is target.
+    const result = Object.assign(true, {a: 2});
+    expect(typeof result).toBe('object');
+    expect(result.valueOf()).toEqual(true);
+  });
+
+  test('target is Null', () => {
+    // if target is null, should throw a TypeError exception.
+    // @ts-ignore
+    expect(() => Object.assign(null, {a: 1})).toThrow(TypeError);
+  });
+
+  test('target is Number', () => {
+    // if target is Number, the return value should be a new object whose value is target.
+    const result = Object.assign(1, {a: 2});
+    expect(typeof result).toBe('object');
+    expect(result.valueOf()).toEqual(1);
+  });
+
+  test('target is Object', () => {
+    const target = {foo: 1};
+    // if target is Object, its properties will be the properties of new object.
+    const result = Object.assign(target, {a: 2});
+    expect(typeof result).toBe('object');
+    expect(result.a).toEqual(2);
+    expect(result.foo).toEqual(1);
+  });
+
+  test('target is String', () => {
+    // if target is String, the return value should be a new object whose value is target.
+    const result = Object.assign('1', {a: 2});
+    expect(typeof result).toBe('object');
+    expect(result.valueOf()).toEqual('1');
+  });
+
+  test('target is Symbol', () => {
+    // if target is Symbol, the return value should be a new Symbol object whose [[SymbolData]] value is target.
+    const target = Symbol('foo');
+    const result = Object.assign(target, {a: 1});
+    expect(typeof result).toBe('object');
+    expect(result.toString()).toEqual('Symbol(foo)');
+  });
+
+  test('target is Undefined', () => {
+    // if target is Undefined, should throw a TypeError exception.
+    // @ts-ignore
+    expect(() => Object.assign(undefined, {a: 1})).toThrow(TypeError);
+  });
+
+  test('assign descriptor', () => {
+    const descriptor = Object.getOwnPropertyDescriptor(Object, 'assign');
+    expect(descriptor).toHaveProperty('writable', true);
+    expect(descriptor).toHaveProperty('enumerable', false);
+    expect(descriptor).toHaveProperty('configurable', true);
+  });
+
+  test('assignment to readonly property of target must throw a typeerror exception', () => {
+    // Assignment to readonly property of target must throw a TypeError exception
+    expect(() => Object.assign('a', [1])).toThrow(TypeError);
+  });
+
+  test('invoked as constructor', () => {
+    /*
+    Built-in function objects that are not identified as constructors do not
+    implement the [[Construct]] internal method unless otherwise specified in
+    the description of a particular function.
+    */
+    expect(() => {
+      // @ts-ignore
+      new Object.assign({});
+    }).toThrow(TypeError);
+  });
+
+  test('assign name', () => {
+    /*
+    ES6 Section 17:
+    Every built-in Function object, including constructors, that is not
+    identified as an anonymous function has a name property whose value is a
+    String. Unless otherwise specified, this value is the name that is given to
+    the function in this specification.
+    [...]
+    Unless otherwise specified, the name property of a built-in Function
+    object, if it exists, has the attributes { [[Writable]]: false,
+    [[Enumerable]]: false, [[Configurable]]: true }.
+    */
+    expect(Object.assign.name).toBe('assign');
+    const descriptor = Object.getOwnPropertyDescriptor(Object.assign, 'name');
+    expect(descriptor).toHaveProperty('writable', false);
+    expect(descriptor).toHaveProperty('enumerable', false);
+    expect(descriptor).toHaveProperty('configurable', true);
+  });
+
+  test('source - get attributes error', () => {
+    // Errors thrown during retrieval of source object attributes
+    const source = {
+      get attr() {throw new TestError();}
+    };
+    expect(() => Object.assign({}, source)).toThrow(TestError);
+  });
+
+  test('source - non-enumerable', () => {
+    // Does not assign non-enumerable source properties
+    const target = {};
+    const source = Object.defineProperty({}, 'attr', {value: 1, enumerable: false});
+    const result = Object.assign(target, source);
+
+    expect(Object.hasOwnProperty.call(target, 'attr')).toBe(false);
+    expect(result).toEqual(target);
+  });
+
+  test('source - own property error', () => {
+    // Invoked with a source whose own property descriptor cannot be retrieved
+    const source = new Proxy({
+      attr: null
+    }, {
+      getOwnPropertyDescriptor: () => {
+        throw new TestError();
+      }
+    });
+    expect(() => Object.assign({}, source)).toThrow(TestError);
+  });
+
+  test('source - own property keys error', () => {
+    // Invoked with a source whose own property keys cannot be retrieved
+    const source = new Proxy({}, {
+      ownKeys: () => { throw new TestError(); }
+    });
+    expect(() => Object.assign({}, source)).toThrow(TestError);
+  });
+
+  test('strings and symbol order', () => {
+    // Symbol-valued properties are copied after String-valued properties.
+    const log: string[] = [];
+    const sym1 = Symbol('x');
+    const sym2 = Symbol('y');
+    const source = {};
+
+    Object.defineProperty(source, sym1, {
+      get: () => log.push('get sym(x)'),
+      enumerable: true, configurable: true,
+    });
+    Object.defineProperty(source, 'a', {
+      get: () => log.push('get a'),
+      enumerable: true, configurable: true,
+    });
+    Object.defineProperty(source, sym2, {
+      get: () => log.push('get sym(y)'),
+      enumerable: true, configurable: true,
+    });
+    Object.defineProperty(source, 'b', {
+      get: () => log.push('get b'),
+      enumerable: true, configurable: true,
+    });
+
+    Object.assign({}, source);
+    expect(log).toEqual(['get a', 'get b', 'get sym(x)', 'get sym(y)']);
+  });
+
+  test('target is frozen accessor property set succeeds', () => {
+    // [[Set]] to accessor property of frozen `target` succeeds.
+    let value1 = 1;
+    const target1 = {
+      set foo(val: any) { value1 = val; },
+    };
+    Object.freeze(target1);
+    Object.assign(target1, {foo: 2});
+    expect(value1).toBe(2);
+
+    const sym = Symbol();
+    let value2 = 1;
+    const target2 = Object.freeze({
+      set [sym](val: any) { value2 = val; },
+    });
+    Object.freeze(target2);
+    Object.assign(target2, {[sym]: 2});
+    expect(value2).toBe(2);
+  });
+
+  test('target is frozen data property set throws', () => {
+    // [[Set]] to data property of frozen `target` fails with TypeError.
+    const sym = Symbol();
+    const target1 = {[sym]: 1};
+    Object.freeze(target1);
+    expect(() => Object.assign(target1, {[sym]: 1})).toThrow(TypeError);
+
+    const target2 = Object.freeze({foo: 1});
+    expect(() => Object.assign(target2, {foo: 1})).toThrow(TypeError);
+  });
+
+  test('target is non extensible existing accessor property', () => {
+    // [[Set]] to existing accessor property of non-extensible `target` is successful.
+    let value1 = 1;
+    const target1 = Object.preventExtensions({
+      set foo(val: any) { value1 = val; },
+    });
+    Object.assign(target1, {foo: 2});
+    expect(value1).toBe(2);
+
+    const sym = Symbol();
+    let value2 = 1;
+    const target2 = {
+      set [sym](val: any) { value2 = val; },
+    };
+    Object.preventExtensions(target2);
+    Object.assign(target2, {[sym]: 2});
+    expect(value2).toBe(2);
+  });
+
+  test('target is non extensible existing data property', () => {
+    // [[Set]] to existing data property of non-extensible `target` is successful.
+    const target1 = Object.preventExtensions({foo: 1});
+    Object.assign(target1, {foo: 2});
+    expect(target1.foo).toBe(2);
+
+    const sym = Symbol();
+    const target2 = {[sym]: 1};
+    Object.preventExtensions(target2);
+    Object.assign(target2, {[sym]: 2});
+    expect(target2[sym]).toBe(2);
+  });
+
+  test('target is non-extensible property creation throws', () => {
+    // [[Set]] to non-existing property of non-extensible `target` fails with TypeError.
+    const target1 = Object.preventExtensions({foo: 1});
+    expect(() => Object.assign(target1, {get bar() {return;}})).toThrow(TypeError);
+
+    const target2 = {};
+    Object.preventExtensions(target2);
+    expect(() => Object.assign(target2, {[Symbol()]: 1})).toThrow(TypeError);
+  });
+
+  test('target is sealed existing accessor property', () => {
+    // [[Set]] to existing accessor property of sealed `target` is successful.
+    let value1 = 1;
+    const target1 = Object.seal({
+      set foo(val: any) { value1 = val; }
+    });
+    Object.assign(target1, {foo: 2});
+    expect(value1).toBe(2);
+
+    const sym = Symbol();
+    let value2 = 1;
+    const target2 = {
+      set [sym](val: any) { value2 = val; }
+    };
+    Object.seal(target2);
+    Object.assign(target2, {[sym]: 2});
+    expect(value2).toBe(2);
+  });
+
+  test('target is sealed existing data property', () => {
+    // [[Set]] to existing data property of sealed `target` is successful.
+    const target1 = Object.seal({foo: 1});
+    Object.assign(target1, {foo: 2});
+    expect(target1.foo).toBe(2);
+
+    const sym = Symbol();
+    const target2 = {[sym]: 1};
+    Object.seal(target2);
+    Object.assign(target2, {[sym]: 2});
+    expect(target2[sym]).toBe(2);
+  });
+
+  test('target is sealed property creation throws', () => {
+    // [[Set]] to non-existing property of sealed `target` fails with TypeError.
+    const target1 = Object.seal({foo: 1});
+    expect(() => Object.assign(target1, {get bar() {return;}})).toThrow(TypeError);
+
+    const target2 = {};
+    Object.seal(target2);
+    expect(() => Object.assign(target2, {[Symbol()]: 1})).toThrow(TypeError);
+  });
+
+  test('target set not-writable', () => {
+    // Errors thrown during definition of target object attributes
+    const target = {};
+    Object.defineProperty(target, 'attr', {writable: false});
+    expect(() => Object.assign(target, {attr: 1})).toThrow(TypeError);
+  });
+
+  test('target set user error', () => {
+    // Errors thrown during definition of target object attributes
+    const target = {};
+    Object.defineProperty(target, 'attr', {
+      set: (_) => { throw new TestError(); }
+    });
+    expect(() => Object.assign(target, {attr: 1})).toThrow(TestError);
+  });
+
+  test('prototype pollution via __proto__', () => {
+    const maliciousPayload = '{"a": 1, "__proto__": {"polluted": true}}';
+    const a: any = {};
+    const result = Object.assign({}, JSON.parse(maliciousPayload));
+
+    expect(({} as any).polluted).not.toBe(true); // safe Plain Old Javascript Object
+    expect(a.polluted).not.toBe(true); // safe a input
+    expect(result.polluted).toBe(true);
+  });
+
+  test('prototype pollution via constructor', () => {
+    const maliciousPayload = '{"a": 1, "constructor": {"prototype": {"polluted": true}}}';
+    const a: any = {};
+    const result = Object.assign({}, JSON.parse(maliciousPayload));
+
+    expect(result.constructor.prototype.polluted).toBe(true);
+    expect(a.constructor.prototype.polluted).toBe(undefined);
+    expect(({} as any).constructor.prototype.polluted).toBe(undefined);
+  });
+});