Repository: exadel-inc/esl
Pull request #2067 (2023-11-22 14:13:20)

[ESL Share]: follow up (wave 2)
Closes: #2012 




Patch:
diff --git a/pages/src/esl-share/esl-share.ts b/pages/src/esl-share/esl-share.ts
index b413da462..69bfc6bc1 100644
--- a/pages/src/esl-share/esl-share.ts
+++ b/pages/src/esl-share/esl-share.ts
@@ -1,8 +1,9 @@
 import '@exadel/esl/modules/esl-share/buttons/all';
-import {ESLShare, ESLShareConfig} from '@exadel/esl/modules/esl-share/core';
+import {ESLShare, ESLShareList, ESLShareConfig} from '@exadel/esl/modules/esl-share/core';
 
 ESLShareConfig.append({name: 'site', 'list': 'facebook twitter linkedin telegram copy'});
 ESLShare.register();
+ESLShareList.register();
 
 ESLShareConfig.set(
   fetch('/assets/share/config.json').then((response) => response.json())
diff --git a/pages/views/_includes/navigation/header.njk b/pages/views/_includes/navigation/header.njk
index 6652e6b62..351e1bb9c 100644
--- a/pages/views/_includes/navigation/header.njk
+++ b/pages/views/_includes/navigation/header.njk
@@ -39,7 +39,7 @@
                    default-params="{hideDelay: 500}"
                    close-on-esc
                    close-on-outside-action>
-          <esl-share list="group:site"></esl-share>
+          <esl-share-list list="group:site"></esl-share-list>
           <span class="esl-popup-arrow"></span>
         </esl-popup>
       </li>
diff --git a/pages/views/_includes/navigation/sidebar.njk b/pages/views/_includes/navigation/sidebar.njk
index 235115938..250f08e31 100644
--- a/pages/views/_includes/navigation/sidebar.njk
+++ b/pages/views/_includes/navigation/sidebar.njk
@@ -50,8 +50,8 @@
         <hr class="sidebar-nav-item-divider show-xs"/>
 
         <li class="sidebar-nav-item sidebar-nav-item-share">
-          <esl-share aria-label="Share this page"
-                     list="group:site"></esl-share>
+          <esl-share-list list="group:site"
+                          aria-label="Share this page"></esl-share-list>
         </li>
       </ul>
     </div>
diff --git a/pages/views/draft/share-regression.njk b/pages/views/draft/share-regression.njk
index 8a6128e2b..cd8e23a9b 100644
--- a/pages/views/draft/share-regression.njk
+++ b/pages/views/draft/share-regression.njk
@@ -21,7 +21,7 @@ aside:
 {% macro shareAttrs4() %}share-url="https://esl-ui.com/examples/" share-title="Examples"{% endmacro %}
 
 <style>
-  esl-share-popup-trigger {
+  esl-share {
     margin: 10px;
   }
   .demo-trigger-svg-icon {
@@ -35,27 +35,27 @@ aside:
 
   <h3>Displays list of share buttons without list attribute</h3>
   <p>Shows all available buttons from config.</p>
-  <esl-share></esl-share>
+  <esl-share-list></esl-share-list>
   <hr>
 
   <h3>Displays list of share buttons with list="all"</h3>
   <p>Shows all available buttons from config.</p>
-  <esl-share list="all"></esl-share>
+  <esl-share-list list="all"></esl-share-list>
   <hr>
 
   <h3>Displays list of share buttons with empty list (list="")</h3>
   <p>Shows empty component without buttons.</p>
-  <esl-share list=""></esl-share>
+  <esl-share-list list=""></esl-share-list>
   <hr>
 
   <h3>Displays list of share buttons for nonexistent group</h3>
   <p>Shows empty list when a nonexistent group is specified.</p>
-  <esl-share list="group:foo"></esl-share>
+  <esl-share-list list="group:foo"></esl-share-list>
   <hr>
 
   <h3>Displays list of share buttons only for existent groups or buttons</h3>
   <p>Shows specified buttons. Displays nothing in the case when specifying the wrong button or group name. (should display LinkedIn, Facebook, Twitter, LinkedIn, Wykop and Copy buttons)</p>
-  <esl-share list="linkedin alladin group:alladin group:demo"></esl-share>
+  <esl-share-list list="linkedin alladin group:alladin group:demo"></esl-share-list>
   <hr>
 
 {#  TODO: discuss behaviour #}
@@ -66,68 +66,68 @@ aside:
 
   <h3>Displays list of share buttons via incorrect list</h3>
   <p>Shows buttons only with the correct names. (should display only LinkedIn button)</p>
-  <esl-share list="twittor  copi group:exsampl linkedin rint"></esl-share>
+  <esl-share-list list="twittor  copi group:exsampl linkedin rint"></esl-share-list>
   <hr>
 
   <h3>Trigger madness</h3>
   <p>Displays a row of share triggers related to three variations of the popup. The purpose is to check that popup is consistently following the active trigger.</p>
   <p>Hint: the first number is a variant of the share popup, and the second number is a variant of the share attributes.<p>
 
-  <esl-share mode="popup">
+  <esl-share dir="rtl" lang="he" popup-params="{extraClass: 'foo', inlineStyles: 'max-width:325px; padding:20px'}">
     {{ triggerSVGicon('#00BFFF', '1/1') }}
   </esl-share>
-  <esl-share mode="popup" list="group:demo">
+  <esl-share list="group:demo">
     {{ triggerSVGicon('#00CED1', '2/1') }}
   </esl-share>
-  <esl-share mode="popup" list="group:alternative">
+  <esl-share dir="ltr" lang="fr" popup-params="{extraClass: 'bar', inlineStyles: 'padding:10px'}" list="group:alternative">
     {{ triggerSVGicon('#8FBC8F', '3/1') }}
   </esl-share>
-  <esl-share mode="popup">
+  <esl-share>
     {{ triggerSVGicon('#00BFFF', '1/1') }}
   </esl-share>
-  <esl-share mode="popup" {{ shareAttrs2() }}>
+  <esl-share {{ shareAttrs2() }}>
     {{ triggerSVGicon('#00BFFF', '1/2') }}
   </esl-share>
-  <esl-share mode="popup" list="group:demo">
+  <esl-share list="group:demo">
     {{ triggerSVGicon('#00CED1', '2/1') }}
   </esl-share>
-  <esl-share mode="popup" list="group:demo" {{ shareAttrs2() }}>
+  <esl-share list="group:demo" {{ shareAttrs2() }}>
     {{ triggerSVGicon('#00CED1', '2/2') }}
   </esl-share>
-  <esl-share mode="popup" list="group:demo" {{ shareAttrs3() }}>
+  <esl-share list="group:demo" {{ shareAttrs3() }}>
     {{ triggerSVGicon('#00CED1', '2/3') }}
   </esl-share>
-  <esl-share mode="popup" list="group:demo" {{ shareAttrs4() }}>
+  <esl-share list="group:demo" {{ shareAttrs4() }}>
     {{ triggerSVGicon('#00CED1', '2/4') }}
   </esl-share>
-  <esl-share mode="popup" list="group:demo" {{ shareAttrs3() }}>
+  <esl-share list="group:demo" {{ shareAttrs3() }}>
     {{ triggerSVGicon('#00CED1', '2/3') }}
   </esl-share>
-  <esl-share mode="popup" list="group:demo" {{ shareAttrs4() }}>
+  <esl-share list="group:demo" {{ shareAttrs4() }}>
     {{ triggerSVGicon('#00CED1', '2/4') }}
   </esl-share>
-  <esl-share mode="popup" list="group:demo">
+  <esl-share list="group:demo">
     {{ triggerSVGicon('#00CED1', '2/1') }}
   </esl-share>
-  <esl-share mode="popup" list="group:alternative">
+  <esl-share list="group:alternative">
     {{ triggerSVGicon('#8FBC8F', '3/1') }}
   </esl-share>
-  <esl-share mode="popup" list="group:alternative" {{ shareAttrs2() }}>
+  <esl-share list="group:alternative" {{ shareAttrs2() }}>
     {{ triggerSVGicon('#8FBC8F', '3/2') }}
   </esl-share>
-  <esl-share mode="popup" list="group:alternative" {{ shareAttrs3() }}>
+  <esl-share list="group:alternative" {{ shareAttrs3() }}>
     {{ triggerSVGicon('#8FBC8F', '3/3') }}
   </esl-share>
-  <esl-share mode="popup" list="group:alternative" {{ shareAttrs4() }}>
+  <esl-share list="group:alternative" {{ shareAttrs4() }}>
     {{ triggerSVGicon('#8FBC8F', '3/4') }}
   </esl-share>
-  <esl-share mode="popup" list="group:alternative">
+  <esl-share list="group:alternative">
     {{ triggerSVGicon('#8FBC8F', '3/1') }}
   </esl-share>
-  <esl-share mode="popup">
+  <esl-share>
     {{ triggerSVGicon('#00BFFF', '1/1') }}
   </esl-share>
-  <esl-share mode="popup">
+  <esl-share>
     {{ triggerSVGicon('#00BFFF', '1/1') }}
   </esl-share>
 </section>
diff --git a/pages/views/examples/share.njk b/pages/views/examples/share.njk
index 80882dda4..3f72e5876 100644
--- a/pages/views/examples/share.njk
+++ b/pages/views/examples/share.njk
@@ -10,10 +10,10 @@ aside:
 ---
 
 <style>
-  esl-share-popup-trigger {
+  esl-share {
     margin: 10px;
   }
-  esl-share-popup-trigger svg {
+  esl-share svg {
     width: 40px;
     height: 40px;
   }
@@ -76,11 +76,6 @@ aside:
 
   <h3>Share popup trigger example</h3>
   <p>Displays trigger icon. Hovering or clicking on the icon shows a popup with the list of share icons. The component's configuration uses attributes to specify the URL and title of the page to be shared.</p>
-  <esl-share mode="popup" list="group:demo" share-url="https://esl-ui.com/" share-title="Exadel Smart Library">{% include 'static/assets/examples/share-trigger-icon.svg' %}</esl-share>
-  <hr>
+  <esl-share list="group:demo" share-url="https://esl-ui.com/" share-title="Exadel Smart Library">{% include 'static/assets/examples/share-trigger-icon.svg' %}</esl-share>
 
-  <h3>Share (universal) component example (with both list and popup modes)</h3>
-  <p>Shows share list from a specified group and share popup trigger for the same group.</p>
-  <esl-share mode="list" list="group:demo"></esl-share>
-  <esl-share mode="popup" list="group:demo">{% include 'static/assets/examples/share-trigger-icon.svg' %}</esl-share>
 </section>
diff --git a/src/modules/esl-alert/core/esl-alert.ts b/src/modules/esl-alert/core/esl-alert.ts
index e709f3b45..b60f40e3e 100644
--- a/src/modules/esl-alert/core/esl-alert.ts
+++ b/src/modules/esl-alert/core/esl-alert.ts
@@ -31,8 +31,14 @@ export class ESLAlert extends ESLToggleable {
   public static override is = 'esl-alert';
   public static override observedAttributes = ['target'];
 
+  /**
+   * Legacy default show/hide params for all ESLAlert instances
+   * @deprecated Use {@link ESLAlert.DEFAULT_PARAMS} instead
+   */
+  public static defaultConfig: AlertActionParams = {};
+
   /** Default show/hide params for all ESLAlert instances */
-  public static defaultConfig: AlertActionParams = {
+  public static override DEFAULT_PARAMS: AlertActionParams = {
     hideTime: 300,
     hideDelay: 2500
   };
@@ -70,7 +76,7 @@ export class ESLAlert extends ESLToggleable {
 
   protected override mergeDefaultParams(params?: ESLToggleableActionParams): ESLToggleableActionParams {
     const type = this.constructor as typeof ESLAlert;
-    return Object.assign({}, type.defaultConfig, this.defaultParams || {}, params || {});
+    return Object.assign({}, type.defaultConfig, type.DEFAULT_PARAMS, this.defaultParams || {}, params || {});
   }
 
   protected override attributeChangedCallback(attrName: string, oldVal: string, newVal: string): void {
diff --git a/src/modules/esl-panel/core/esl-panel.ts b/src/modules/esl-panel/core/esl-panel.ts
index 0b5ed79de..c2b354f21 100644
--- a/src/modules/esl-panel/core/esl-panel.ts
+++ b/src/modules/esl-panel/core/esl-panel.ts
@@ -143,8 +143,9 @@ export class ESLPanel extends ESLToggleable {
 
   /** Merge params that are used by panel group for actions */
   protected override mergeDefaultParams(params?: ESLToggleableActionParams): ESLToggleableActionParams {
+    const type = this.constructor as typeof ESLToggleable;
     const stackConfig = this.$group?.panelConfig || {};
-    return Object.assign({}, stackConfig, this.defaultParams, params || {});
+    return Object.assign({}, stackConfig, type.DEFAULT_PARAMS, this.defaultParams, params || {});
   }
 }
 
diff --git a/src/modules/esl-popup/core/esl-popup.ts b/src/modules/esl-popup/core/esl-popup.ts
index fa8b0f295..fe95169a5 100644
--- a/src/modules/esl-popup/core/esl-popup.ts
+++ b/src/modules/esl-popup/core/esl-popup.ts
@@ -17,7 +17,6 @@ import {ESLPopupPlaceholder} from './esl-popup-placeholder';
 import type {ESLToggleableActionParams} from '../../esl-toggleable/core';
 import type {PositionType, IntersectionRatioRect} from './esl-popup-position';
 
-
 const INTERSECTION_LIMIT_FOR_ADJACENT_AXIS = 0.7;
 const DEFAULT_OFFSET_ARROW = 50;
 
@@ -57,6 +56,13 @@ export interface PopupActionParams extends ESLToggleableActionParams {
 export class ESLPopup extends ESLToggleable {
   public static override is = 'esl-popup';
 
+  /** Default params to pass into the popup on show/hide actions */
+  public static override DEFAULT_PARAMS: PopupActionParams = {
+    offsetTrigger: 3,
+    offsetContainer: 15,
+    intersectionMargin: '0px'
+  };
+
   /** Classname of popups arrow element */
   @attr({defaultValue: 'esl-popup-arrow'}) public arrowClass: string;
 
@@ -89,12 +95,8 @@ export class ESLPopup extends ESLToggleable {
   /** Target to container element {@link ESLTraversingQuery} to define bounds of popups visibility (window by default) */
   @attr() public container: string;
 
-  /** Default params to merge into passed action params */
-  @jsonAttr<PopupActionParams>({defaultValue: {
-    offsetTrigger: 3,
-    offsetContainer: 15,
-    intersectionMargin: '0px'
-  }})
+  /** Default show/hide params for current ESLAlert instance */
+  @jsonAttr<PopupActionParams>()
   public override defaultParams: PopupActionParams;
 
   @attr({parser: parseBoolean, serializer: toBooleanAttribute, defaultValue: true})
diff --git a/src/modules/esl-share/core.less b/src/modules/esl-share/core.less
index 28f98e2a2..9a3818bfd 100644
--- a/src/modules/esl-share/core.less
+++ b/src/modules/esl-share/core.less
@@ -1,3 +1,3 @@
 @import './core/esl-share-button.less';
 @import './core/esl-share-list.less';
-@import './core/esl-share-popup-trigger.less';
+@import './core/esl-share.less';
diff --git a/src/modules/esl-share/core.ts b/src/modules/esl-share/core.ts
index 3dd93ce94..90e86719c 100644
--- a/src/modules/esl-share/core.ts
+++ b/src/modules/esl-share/core.ts
@@ -5,4 +5,3 @@ export * from './core/esl-share-button';
 export * from './core/esl-share-config';
 export * from './core/esl-share-list';
 export * from './core/esl-share-popup';
-export * from './core/esl-share-popup-trigger';
diff --git a/src/modules/esl-share/core/esl-share-button.ts b/src/modules/esl-share/core/esl-share-button.ts
index 77582e125..b7f2c1d11 100644
--- a/src/modules/esl-share/core/esl-share-button.ts
+++ b/src/modules/esl-share/core/esl-share-button.ts
@@ -34,7 +34,9 @@ export class ESLShareButton extends ESLBaseElement {
   }
 
   /** Event to dispatch when {@link ESLShareButton} configuration is changed */
-  @prop('esl:share:button:changed') public SHARE_BUTTON_CHANGED_EVENT: string;
+  @prop('esl:share:changed') public SHARE_CHANGED_EVENT: string;
+  /** Event to dispatch on {@link ESLShareButton} ready state */
+  @prop('esl:share:ready') public SHARE_READY_EVENT: string;
 
   /** Name of share action that occurs after button click */
   @attr() public action: string;
@@ -126,7 +128,7 @@ export class ESLShareButton extends ESLBaseElement {
     if (this.defaultIcon) this.initIcon();
     this.initA11y();
     this.updateAction();
-    this.$$attr('ready', true);
+    this.onReady();
   }
 
   /** Sets initial a11y attributes */
@@ -190,7 +192,14 @@ export class ESLShareButton extends ESLBaseElement {
     memoize.clear(this, 'config');
     if (isEqual(this.config, config)) return;
     this.init(true);
-    this.$$fire(this.SHARE_BUTTON_CHANGED_EVENT, {bubbles: false});
+    this.$$fire(this.SHARE_CHANGED_EVENT, {bubbles: false});
+  }
+
+  /** 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});
   }
 }
 
diff --git a/src/modules/esl-share/core/esl-share-config.ts b/src/modules/esl-share/core/esl-share-config.ts
index 7b339a1c8..7a6945a2c 100644
--- a/src/modules/esl-share/core/esl-share-config.ts
+++ b/src/modules/esl-share/core/esl-share-config.ts
@@ -119,8 +119,10 @@ export class ESLShareConfig extends SyntheticEventTarget {
    */
   public get(query: string): ESLShareButtonConfig[] {
     const terms = this.resolve(query);
-    if (terms.includes('all')) return this.buttons;
-    const list = terms.map((name) => this.getButton(name));
+    const list: (ESLShareButtonConfig | undefined)[] = [];
+    terms.forEach((name) => {
+      name === 'all' ? list.push(...this.buttons) : list.push(this.getButton(name));
+    });
     return uniq(list.filter(Boolean) as ESLShareButtonConfig[]);
   }
 
diff --git a/src/modules/esl-share/core/esl-share-list.ts b/src/modules/esl-share/core/esl-share-list.ts
index c2a781648..340482ab2 100644
--- a/src/modules/esl-share/core/esl-share-list.ts
+++ b/src/modules/esl-share/core/esl-share-list.ts
@@ -28,6 +28,8 @@ export class ESLShareList extends ESLBaseElement {
 
   /** Event to dispatch on change of {@link ESLShareList} */
   @prop('esl:share:changed') public SHARE_CHANGED_EVENT: string;
+  /** Event to dispatch on {@link ESLShareList} ready state */
+  @prop('esl:share:ready') public SHARE_READY_EVENT: string;
 
   /**
    * List of social networks or groups of them to display (all by default).
@@ -82,13 +84,15 @@ export class ESLShareList extends ESLBaseElement {
 
   /** Actions on complete init and ready component. */
   private onReady(): void {
+    if (this.ready) return;
     this.$$attr('ready', true);
-    this.$$fire(this.SHARE_CHANGED_EVENT, {bubbles: false});
+    this.$$fire(this.SHARE_READY_EVENT, {bubbles: false});
   }
 
   @listen({event: 'change', target: ESLShareConfig.instance})
   protected _onConfigChange(): void {
     this.update();
+    this.$$fire(this.SHARE_CHANGED_EVENT, {bubbles: false});
   }
 }
 
diff --git a/src/modules/esl-share/core/esl-share-popup-trigger.less b/src/modules/esl-share/core/esl-share-popup-trigger.less
deleted file mode 100644
index cd5d88d10..000000000
--- a/src/modules/esl-share/core/esl-share-popup-trigger.less
+++ /dev/null
@@ -1,4 +0,0 @@
-esl-share-popup-trigger {
-  display: inline-block;
-  cursor: pointer;
-}
diff --git a/src/modules/esl-share/core/esl-share-popup-trigger.shape.ts b/src/modules/esl-share/core/esl-share-popup-trigger.shape.ts
deleted file mode 100644
index 714315626..000000000
--- a/src/modules/esl-share/core/esl-share-popup-trigger.shape.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import type {ESLBaseElementShape} from '../../esl-base-element/core/esl-base-element.shape';
-import type {ESLSharePopupTrigger} from './esl-share-popup-trigger';
-
-/**
- * Tag declaration interface of ESLSharePopupTrigger element
- * Used for TSX declaration
- */
-export interface ESLSharePopupTriggerTagShape extends ESLBaseElementShape<ESLSharePopupTrigger> {
-  /** Define the list of social networks or groups of them to display at popup (all by default) */
-  list?: string;
-  /** Define URL to share (current page URL by default) */
-  'share-url'?: string;
-  /** Define title to share (current document title by default) */
-  'share-title'?: string;
-
-  /** Allowed children */
-  children?: any;
-}
-
-declare global {
-  namespace JSX {
-    export interface IntrinsicElements {
-      /** {@link ESLSharePopupTrigger} custom tag */
-      'esl-share-popup-trigger': ESLSharePopupTriggerTagShape;
-    }
-  }
-}
diff --git a/src/modules/esl-share/core/esl-share-popup-trigger.ts b/src/modules/esl-share/core/esl-share-popup-trigger.ts
deleted file mode 100644
index 9851692fb..000000000
--- a/src/modules/esl-share/core/esl-share-popup-trigger.ts
+++ /dev/null
@@ -1,116 +0,0 @@
-import {ExportNs} from '../../esl-utils/environment/export-ns';
-import {attr, jsonAttr} from '../../esl-utils/decorators';
-import {ESLTraversingQuery} from '../../esl-traversing-query/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 {ESLSharePopupTriggerTagShape} from './esl-share-popup-trigger.shape';
-
-/**
- * ESLSharePopupTrigger component
- * @author Dmytro Shovchko
- *
- * ESLSharePopupTrigger is a component that allows triggering {@link ESLSharePopup} instance state changes.
- */
-@ExportNs('SharePopupTrigger')
-export class ESLSharePopupTrigger extends ESLTrigger {
-  public static override is = 'esl-share-popup-trigger';
-  public static override observedAttributes = ['list'];
-
-  /** Register {@link ESLSharePopupTrigger} component and dependent {@link ESLSharePopup} */
-  public static override register(): void {
-    ESLSharePopup.register();
-    super.register();
-  }
-
-  /**
-   * List of social networks or groups of them to display (all by default).
-   * The value - a string containing the names of the buttons or groups (specified with
-   * the prefix `group:`) separated by spaces.
-   * @example "facebook reddit group:default"
-   * */
-  @attr({defaultValue: 'all'}) public list: string;
-
-  /** Hover event tracking media query. Default: `all` */
-  @attr({defaultValue: 'all'}) public override trackHover: string;
-
-  /** Default initial params to pass into the popup */
-  @jsonAttr<ESLSharePopupActionParams>({defaultValue: {
-    position: 'top',
-    hideDelay: 300
-  }})
-  public popupInitialParams: ESLSharePopupActionParams;
-
-  /** Target observable Toggleable */
-  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 {
-    return !!this.$target?.open && this.$target?.activator === this;
-  }
-
-  /** The text writing directionality of the element */
-  protected get currentDir(): string {
-    return getComputedStyle(this).direction;
-  }
-
-  /** The base language of the element */
-  protected get currentLang(): string {
-    const el = this.closest('[lang]');
-    return (el) ? (el as HTMLElement).lang : '';
-  }
-
-  /** Container element that defines bounds of popups visibility */
-  protected get $containerEl(): HTMLElement | undefined {
-    const container = this.getClosestRelatedAttr('container');
-    return container ? ESLTraversingQuery.first(container, this) as HTMLElement : undefined;
-  }
-
-  protected override attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string | null): void {
-    if (!this.connected || oldValue === newValue) return;
-    this.update();
-  }
-
-  /** Updates the component and related popup */
-  protected update(): void {
-    if (!this.isTargetActive) return;
-    this.$target?.hide();
-  }
-
-  /** Update `$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}`;
-    const $closest = this.closest(`[${relatedAttrName}]`);
-    return $closest ? $closest.getAttribute(relatedAttrName) : null;
-  }
-
-  /** Merges params to pass to the toggleable */
-  protected override mergeToggleableParams(this: ESLSharePopupTrigger, ...params: ESLSharePopupActionParams[]): ESLSharePopupActionParams {
-    return Object.assign({
-      initiator: 'share',
-      activator: this,
-      containerEl: this.$containerEl,
-      list: this.list,
-      dir: this.currentDir,
-      lang: this.currentLang
-    }, this.popupInitialParams, ...params);
-  }
-}
-
-declare global {
-  export interface ESLLibrary {
-    SharePopupTrigger: typeof ESLSharePopupTrigger;
-  }
-  export interface HTMLElementTagNameMap {
-    'esl-share-popup-trigger': ESLSharePopupTrigger;
-  }
-}
diff --git a/src/modules/esl-share/core/esl-share-popup.ts b/src/modules/esl-share/core/esl-share-popup.ts
index d3ae6d448..c652f1725 100644
--- a/src/modules/esl-share/core/esl-share-popup.ts
+++ b/src/modules/esl-share/core/esl-share-popup.ts
@@ -32,6 +32,13 @@ export interface ESLSharePopupActionParams extends TooltipActionParams {
 export class ESLSharePopup extends ESLTooltip {
   static override is = 'esl-share-popup';
 
+  /** Default params to pass into the share popup */
+  static override DEFAULT_PARAMS: ESLSharePopupActionParams = {
+    ...ESLTooltip.DEFAULT_PARAMS,
+    position: 'top',
+    hideDelay: 300
+  };
+
   /** List of attributes to forward from the activator to the {@link ESLSharePopup} */
   public static forwardedAttrs = ['share-title', 'share-url'];
 
diff --git a/src/modules/esl-share/core/esl-share.less b/src/modules/esl-share/core/esl-share.less
new file mode 100644
index 000000000..953f78bbe
--- /dev/null
+++ b/src/modules/esl-share/core/esl-share.less
@@ -0,0 +1,8 @@
+esl-share {
+  display: inline-block;
+  cursor: pointer;
+}
+
+esl-share-popup {
+  max-width: var(--esl-share-popup-max-width, 202px);
+}
diff --git a/src/modules/esl-share/core/esl-share.shape.ts b/src/modules/esl-share/core/esl-share.shape.ts
index dca07882a..4ebf064a1 100644
--- a/src/modules/esl-share/core/esl-share.shape.ts
+++ b/src/modules/esl-share/core/esl-share.shape.ts
@@ -1,22 +1,22 @@
-import type {ESLBaseElementShape} from '../../esl-base-element/core/esl-base-element.shape';
+import type {ESLTriggerTagShape} from '../../esl-trigger/core/esl-trigger.shape';
 import type {ESLShare} from './esl-share';
 
 /**
- * Tag declaration interface of ESL Share element
+ * Tag declaration interface of ESLSharePopupTrigger element
  * Used for TSX declaration
  */
-export interface ESLShareTagShape extends ESLBaseElementShape<ESLShare> {
-  /** Define the list of social networks or groups of them to display (all by default) */
+export interface ESLShareTagShape<T extends ESLShare = ESLShare> extends ESLTriggerTagShape<T> {
+  /** Define the list of social networks or groups of them to display at popup (all by default) */
   list?: string;
-  /** Define share buttons rendering mode */
-  mode?: string;
   /** Define URL to share (current page URL by default) */
   'share-url'?: string;
   /** Define title to share (current document title by default) */
   'share-title'?: string;
+  /** Define JSON of params to pass into the popup */
+  'popup-params'?: string;
 
-  /** Children are not allowed for ESLShare */
-  children?: never[];
+  /** Allowed children */
+  children?: any;
 }
 
 declare global {
diff --git a/src/modules/esl-share/core/esl-share.ts b/src/modules/esl-share/core/esl-share.ts
index 84792581b..227100b77 100644
--- a/src/modules/esl-share/core/esl-share.ts
+++ b/src/modules/esl-share/core/esl-share.ts
@@ -1,10 +1,10 @@
 import {ExportNs} from '../../esl-utils/environment/export-ns';
-import {ESLBaseElement} from '../../esl-base-element/core';
-import {attr, bind, boolAttr, jsonAttr} from '../../esl-utils/decorators';
-import {ESLShareList} from './esl-share-list';
-import {ESLSharePopupTrigger} from './esl-share-popup-trigger';
-import {ESLShareConfig} from './esl-share-config';
+import {attr, boolAttr, jsonAttr, prop, ready} from '../../esl-utils/decorators';
+import {ESLTraversingQuery} from '../../esl-traversing-query/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';
@@ -13,93 +13,112 @@ export type {ESLShareTagShape} from './esl-share.shape';
  * ESLShare
  * @author Dmytro Shovchko
  *
- * ESLShare is a custom element to dynamically draw {@link ESLShareList}
- * or {@link ESLSharePopupTrigger} depending on the specified mode
+ * ESLShare is a component that allows triggering {@link ESLSharePopup} instance state changes.
  */
 @ExportNs('Share')
-export class ESLShare extends ESLBaseElement {
+export class ESLShare extends ESLTrigger {
   public static override is = 'esl-share';
+  public static override observedAttributes = ['list'];
 
-  /** Register {@link ESLShare} component and dependent {@link ESLShareList} and {@link ESLSharePopupTrigger} */
+  /** Register {@link ESLShare} component and dependent {@link ESLSharePopup} */
   public static override register(): void {
-    ESLShareList.register();
-    ESLSharePopupTrigger.register();
+    ESLSharePopup.register();
     super.register();
   }
 
-  /**
-   * Updates the configuration with either a new config object or by using a configuration provider function.
-   * Every button and group specified in the new config will be added to the current configuration.
-   * @returns Promise of the current config
-   * @deprecated alias for ESLShareConfig.set(), will be removed soon
-   */
-  public static readonly config = ESLShareConfig.set;
-
-  /** Default initial params to pass into the popup trigger */
-  @jsonAttr<ESLSharePopupActionParams>({defaultValue: {
-    trackClick: true,
-    trackHover: true
-  }}) public triggerInitialParams: ESLSharePopupTrigger;
+  /** Event to dispatch on {@link ESLShare} ready state */
+  @prop('esl:share:ready') public SHARE_READY_EVENT: string;
 
   /**
    * List of social networks or groups of them to display (all by default).
    * The value - a string containing the names of the buttons or groups (specified with
-   * the prefix group:) separated by spaces.
+   * the prefix `group:`) separated by spaces.
    * @example "facebook reddit group:default"
    * */
   @attr({defaultValue: 'all'}) public list: string;
-  /** URL to share (current page URL by default) */
-  @attr() public shareUrl: string;
-  /** Title to share (current document title by default) */
-  @attr() public shareTitle: string;
-  /** Rendering mode of the share buttons ('list' by default) */
-  @attr() public mode: 'list' | 'popup';
+
+  /** Hover event tracking media query. Default: `all` */
+  @attr({defaultValue: 'all'}) public override trackHover: string;
+
+  /** Action params to pass into the popup */
+  @jsonAttr<ESLSharePopupActionParams>({defaultValue: {}})
+  public popupParams: ESLSharePopupActionParams;
 
   /** @readonly Ready state marker */
   @boolAttr({readonly: true}) public ready: boolean;
 
-  protected _content: string;
+  /** Target observable Toggleable */
+  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 {
+    return !!this.$target?.open && this.$target?.activator === this;
+  }
 
-  public override connectedCallback(): void {
-    super.connectedCallback();
-    this.init();
+  /** The text writing directionality of the element */
+  protected get currentDir(): string {
+    return getComputedStyle(this).direction;
   }
 
-  /** Initializes the component */
-  protected init(force?: boolean): void {
-    if (this.ready && !force) return;
-    if (!this.mode) this.mode = 'list';
-    if (!this._content) this._content = this.innerHTML;
-    this.buildContent();
+  /** The base language of the element */
+  protected get currentLang(): string {
+    const el = this.closest('[lang]');
+    return (el) ? (el as HTMLElement).lang : '';
+  }
+
+  /** Container element that defines bounds of popups visibility */
+  protected get $containerEl(): HTMLElement | undefined {
+    const container = this.getClosestRelatedAttr('container');
+    return container ? ESLTraversingQuery.first(container, this) as HTMLElement : undefined;
+  }
+
+  @ready
+  protected override connectedCallback(): void {
+    super.connectedCallback();
     this.onReady();
   }
 
-  /** Builds component's content. */
-  @bind
-  protected buildContent(): void {
-    this.innerHTML = '';
-    this.mode === 'list' ? this.appendList() : this.appendPopupTrigger();
+  protected override attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string | null): void {
+    if (!this.connected || oldValue === newValue) return;
+    this.update();
   }
 
-  /** Appends share list to the share component. */
-  protected appendList(): void {
-    const $list = ESLShareList.create();
-    $list.list = this.list;
-    this.appendChild($list);
+  /** Updates the component and related popup */
+  protected update(): void {
+    if (!this.isTargetActive) return;
+    this.$target?.hide();
+  }
+
+  /** Update `$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}`;
+    const $closest = this.closest(`[${relatedAttrName}]`);
+    return $closest ? $closest.getAttribute(relatedAttrName) : null;
   }
 
-  /** Appends share popup trigger to the share component. */
-  protected appendPopupTrigger(): void {
-    const {list} = this;
-    const $trigger = ESLSharePopupTrigger.create();
-    Object.assign($trigger, this.triggerInitialParams, {list});
-    $trigger.innerHTML = this._content;
-    this.appendChild($trigger);
+  /** Merges params to pass to the toggleable */
+  protected override mergeToggleableParams(this: ESLShare, ...params: ESLSharePopupActionParams[]): ESLSharePopupActionParams {
+    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. */
+  /** 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});
   }
 }
 
diff --git a/src/modules/esl-toggleable/core/esl-toggleable.ts b/src/modules/esl-toggleable/core/esl-toggleable.ts
index 2cdb5fb7f..44be3628e 100644
--- a/src/modules/esl-toggleable/core/esl-toggleable.ts
+++ b/src/modules/esl-toggleable/core/esl-toggleable.ts
@@ -58,6 +58,9 @@ export class ESLToggleable extends ESLBaseElement {
   public static override is = 'esl-toggleable';
   public static observedAttributes = ['open', 'group'];
 
+  /** Default show/hide params for all ESLToggleable instances */
+  public static DEFAULT_PARAMS: ESLToggleableActionParams = {};
+
   /** Event to dispatch when toggleable is going to be activated */
   @prop('esl:before:show') public BEFORE_SHOW_EVENT: string;
   /** Event to dispatch when toggleable is going to be deactivated */
@@ -189,7 +192,8 @@ export class ESLToggleable extends ESLBaseElement {
 
   /** Function to merge the result action params */
   protected mergeDefaultParams(params?: ESLToggleableActionParams): ESLToggleableActionParams {
-    return Object.assign({}, this.defaultParams, copyDefinedKeys(params));
+    const type = this.constructor as typeof ESLToggleable;
+    return Object.assign({}, type.DEFAULT_PARAMS, this.defaultParams, copyDefinedKeys(params));
   }
 
   /** Toggle the element state */
diff --git a/src/modules/esl-tooltip/core/esl-tooltip.ts b/src/modules/esl-tooltip/core/esl-tooltip.ts
index dbd21e413..a1c3096b4 100644
--- a/src/modules/esl-tooltip/core/esl-tooltip.ts
+++ b/src/modules/esl-tooltip/core/esl-tooltip.ts
@@ -5,6 +5,7 @@ import {TAB} from '../../esl-utils/dom/keys';
 import {getKeyboardFocusableElements} from '../../esl-utils/dom/focus';
 import {CSSClassUtils} from '../../esl-utils/dom/class';
 
+import type {ESLToggleableActionParams} from '../../esl-toggleable/core';
 import type {PopupActionParams} from '../../esl-popup/core';
 import type {PositionType} from '../../esl-popup/core/esl-popup-position';
 
@@ -19,6 +20,10 @@ export interface TooltipActionParams extends PopupActionParams {
   lang?: string;
   /** tooltip without arrow */
   disableArrow?: boolean;
+  /** extra class to add to tooltip */
+  extraClass?: string;
+  /** inline styles to add to tooltip */
+  inlineStyles?: string;
 }
 
 @ExportNs('Tooltip')
@@ -116,6 +121,16 @@ export class ESLTooltip extends ESLPopup {
     }
   }
 
+  /**
+   * Actions to execute before showing of popup.
+   * @returns false if the show task should be canceled
+   */
+  protected override onBeforeShow(params: ESLToggleableActionParams): boolean | void {
+    const result = super.onBeforeShow(params);
+    this.style.cssText = params.inlineStyles || '';
+    return result;
+  }
+
   /**
    * Actions to execute after showing of popup.
    */


Test patch:
diff --git a/src/modules/esl-share/test/esl-share-config.test.ts b/src/modules/esl-share/test/esl-share-config.test.ts
index 78f0be572..92ee879ec 100644
--- a/src/modules/esl-share/test/esl-share-config.test.ts
+++ b/src/modules/esl-share/test/esl-share-config.test.ts
@@ -72,7 +72,7 @@ describe('ESLShareConfig tests', () => {
       ['sn1', ['sn1']],
       ['sn2', ['sn2']],
       ['', []]
-    ])('ESLShareConfig.prototype.get resolves %s query to list %s', (name: string, list: string[]) => {
+    ])('ESLShareConfig.prototype.get resolves "%s" query to list %s', (name: string, list: string[]) => {
       const result = instance.get(name);
       expect(result.map((cfg) => cfg.name)).toEqual(list);
       for (const res of result) {
@@ -84,6 +84,23 @@ describe('ESLShareConfig tests', () => {
         }));
       }
     });
+
+    describe('ESLShareConfig selects buttons from the list with deduplication behavior', () => {
+      test.each([
+        ['sn1 sn1 sn1', ['sn1']],
+        ['sn2 sn1 sn2', ['sn2', 'sn1']],
+        ['sn1 sn2 sn2 sn1', ['sn1', 'sn2']],
+        ['group:group1 group:group1', ['sn1', 'sn2']],
+        ['group:group2 group:group1 group:group2', ['sn2', 'sn3', 'sn4', 'sn1']],
+        ['sn4 group:group2 sn4 group:group2', ['sn4', 'sn2', 'sn3']],
+        ['all all all', ['sn1', 'sn2', 'sn3', 'sn4', 'sn-5']],
+        ['sn3 all', ['sn3', 'sn1', 'sn2', 'sn4', 'sn-5']],
+        ['group:group2 sn-5 all', ['sn2', 'sn3', 'sn4', 'sn-5', 'sn1']]
+      ])('ESLShareConfig.prototype.get resolves "%s" query to list %s', (name: string, list: string[]) => {
+        const result = instance.get(name);
+        expect(result.map((cfg) => cfg.name)).toEqual(list);
+      });
+    });
   });
 
   describe('ESLShareConfig resolve recursive config', () => {
diff --git a/src/modules/esl-share/test/esl-share-popup.test.ts b/src/modules/esl-share/test/esl-share-popup.test.ts
index 3cff409bd..915bb6236 100644
--- a/src/modules/esl-share/test/esl-share-popup.test.ts
+++ b/src/modules/esl-share/test/esl-share-popup.test.ts
@@ -1,5 +1,6 @@
+import {IntersectionObserverMock} from '../../esl-utils/test/intersectionObserver.mock';
 import {ESLSharePopup} from '../core/esl-share-popup';
-import {ESLSharePopupTrigger} from '../core/esl-share-popup-trigger';
+import {ESLShare} from '../core/esl-share';
 import '../buttons/copy';
 
 // @ts-ignore
@@ -7,14 +8,16 @@ import nestedElementsTemplate from './nested-elements-template.html';
 
 import type {ESLShareButton} from '../core/esl-share-button';
 
+
 describe('ESLSharePopup tests', () => {
   beforeAll(() => {
-    ESLSharePopupTrigger.register();
+    IntersectionObserverMock.mock();
+    ESLShare.register();
   });
 
   describe('share-title and share-url attributes forwarding from activator', () => {
     const $lvl: HTMLElement[] = [];
-    let $trigger: ESLSharePopupTrigger;
+    let $trigger: ESLShare;
 
     const setShareAttributes = ($element: HTMLElement, title?: string, url?: string): void => {
       title && $element.setAttribute('share-title', title);
@@ -35,7 +38,7 @@ describe('ESLSharePopup tests', () => {
       document.body.innerHTML = nestedElementsTemplate;
       $lvl.push(document.body);
       ['.lvl-1', '.lvl-2', '.lvl-3'].forEach((selector) => $lvl.push(document.querySelector(selector) as HTMLElement));
-      $trigger = ESLSharePopupTrigger.create();
+      $trigger = ESLShare.create();
       $trigger.list = 'copy';
       $lvl[3].appendChild($trigger);
     });