chunk-014-share.js

I’ll build this as a safe, standalone advanced share module for RX Theme: native Web Share, copy link, social share URLs, floating bar, shortcode/button support, analytics hooks, accessibility, and no jQuery dependency.

Friend, create this file:

assets/static-js/static-chunks-js/chunk-014-share.js

Paste this full advanced code:

/**
 * RX Theme - Advanced Share Chunk
 * File: assets/static-js/static-chunks-js/chunk-014-share.js
 *
 * Features:
 * - Native Web Share API support
 * - Copy current page link
 * - Facebook, X/Twitter, LinkedIn, WhatsApp, Telegram, Email share
 * - Floating share bar
 * - Inline share buttons
 * - Reading-position aware floating bar
 * - Accessible aria-label support
 * - Keyboard friendly
 * - Auto button creation
 * - Data attribute customization
 * - Lightweight analytics event hooks
 * - No jQuery dependency
 */

(function () {
  'use strict';

  const RXShare = {
    config: {
      selectors: {
        inlineContainer: '[data-rx-share]',
        floatingContainer: '[data-rx-floating-share]',
        shareButton: '[data-rx-share-button]',
        copyButton: '[data-rx-copy-link]',
        nativeButton: '[data-rx-native-share]',
        article: 'article, .entry-content, .rx-post-content, main',
      },

      networks: {
        facebook: {
          label: 'Facebook',
          icon: 'f',
          url: 'https://www.facebook.com/sharer/sharer.php?u={url}',
        },
        x: {
          label: 'X',
          icon: '𝕏',
          url: 'https://twitter.com/intent/tweet?url={url}&text={title}',
        },
        linkedin: {
          label: 'LinkedIn',
          icon: 'in',
          url: 'https://www.linkedin.com/sharing/share-offsite/?url={url}',
        },
        whatsapp: {
          label: 'WhatsApp',
          icon: 'wa',
          url: 'https://api.whatsapp.com/send?text={title}%20{url}',
        },
        telegram: {
          label: 'Telegram',
          icon: 'tg',
          url: 'https://t.me/share/url?url={url}&text={title}',
        },
        email: {
          label: 'Email',
          icon: '@',
          url: 'mailto:?subject={title}&body={title}%0A%0A{url}',
        },
      },

      defaultNetworks: ['facebook', 'x', 'linkedin', 'whatsapp', 'telegram', 'email'],

      popup: {
        width: 680,
        height: 520,
      },

      floating: {
        enabled: true,
        showAfterScroll: 250,
        hideNearFooter: true,
      },

      copy: {
        successText: 'Link copied!',
        errorText: 'Copy failed',
        resetDelay: 1800,
      },

      classes: {
        wrapper: 'rx-share',
        list: 'rx-share__list',
        item: 'rx-share__item',
        button: 'rx-share__button',
        nativeButton: 'rx-share__button--native',
        copyButton: 'rx-share__button--copy',
        floating: 'rx-share--floating',
        visible: 'is-visible',
        copied: 'is-copied',
        hidden: 'is-hidden',
      },
    },

    state: {
      initialized: false,
      pageData: null,
      floatingElement: null,
    },

    init() {
      if (this.state.initialized) return;

      this.state.pageData = this.getPageData();

      this.injectBaseStyles();
      this.renderInlineContainers();
      this.renderFloatingShare();
      this.bindExistingButtons();
      this.bindGlobalEvents();

      this.state.initialized = true;

      this.dispatchEvent('rxShareReady', {
        page: this.state.pageData,
      });
    },

    getPageData(sourceElement) {
      const canonical = document.querySelector('link[rel="canonical"]');
      const ogTitle = document.querySelector('meta[property="og:title"]');
      const ogDescription = document.querySelector('meta[property="og:description"]');
      const metaDescription = document.querySelector('meta[name="description"]');

      const url =
        sourceElement?.dataset?.rxShareUrl ||
        canonical?.href ||
        window.location.href;

      const title =
        sourceElement?.dataset?.rxShareTitle ||
        ogTitle?.content ||
        document.title ||
        '';

      const description =
        sourceElement?.dataset?.rxShareDescription ||
        ogDescription?.content ||
        metaDescription?.content ||
        '';

      return {
        url: this.cleanUrl(url),
        title: this.cleanText(title),
        description: this.cleanText(description),
      };
    },

    cleanUrl(url) {
      try {
        const parsed = new URL(url, window.location.origin);

        parsed.hash = '';

        return parsed.toString();
      } catch (error) {
        return window.location.href.split('#')[0];
      }
    },

    cleanText(text) {
      return String(text || '')
        .replace(/\s+/g, ' ')
        .trim();
    },

    encode(value) {
      return encodeURIComponent(value || '');
    },

    buildShareUrl(networkKey, pageData) {
      const network = this.config.networks[networkKey];

      if (!network) return '';

      return network.url
        .replaceAll('{url}', this.encode(pageData.url))
        .replaceAll('{title}', this.encode(pageData.title))
        .replaceAll('{description}', this.encode(pageData.description));
    },

    getNetworksFromElement(element) {
      const raw = element?.dataset?.rxShareNetworks;

      if (!raw) {
        return this.config.defaultNetworks;
      }

      return raw
        .split(',')
        .map((item) => item.trim())
        .filter((item) => this.config.networks[item]);
    },

    renderInlineContainers() {
      const containers = document.querySelectorAll(this.config.selectors.inlineContainer);

      containers.forEach((container) => {
        if (container.dataset.rxShareRendered === 'true') return;

        const pageData = this.getPageData(container);
        const networks = this.getNetworksFromElement(container);
        const showNative = container.dataset.rxShareNative !== 'false';
        const showCopy = container.dataset.rxShareCopy !== 'false';

        const shareElement = this.createShareElement({
          pageData,
          networks,
          showNative,
          showCopy,
          floating: false,
        });

        container.appendChild(shareElement);
        container.dataset.rxShareRendered = 'true';
      });
    },

    renderFloatingShare() {
      if (!this.config.floating.enabled) return;

      const existing = document.querySelector(this.config.selectors.floatingContainer);

      if (existing) {
        if (existing.dataset.rxFloatingRendered === 'true') return;

        const networks = this.getNetworksFromElement(existing);
        const pageData = this.getPageData(existing);

        const shareElement = this.createShareElement({
          pageData,
          networks,
          showNative: existing.dataset.rxShareNative !== 'false',
          showCopy: existing.dataset.rxShareCopy !== 'false',
          floating: true,
        });

        existing.appendChild(shareElement);
        existing.dataset.rxFloatingRendered = 'true';

        this.state.floatingElement = existing;
        this.updateFloatingVisibility();

        return;
      }

      const article = document.querySelector(this.config.selectors.article);

      if (!article || document.body.dataset.rxFloatingShare === 'false') {
        return;
      }

      const floatingContainer = document.createElement('div');
      floatingContainer.setAttribute('data-rx-floating-share', 'true');
      floatingContainer.className = 'rx-floating-share-container';

      const shareElement = this.createShareElement({
        pageData: this.state.pageData,
        networks: this.config.defaultNetworks,
        showNative: true,
        showCopy: true,
        floating: true,
      });

      floatingContainer.appendChild(shareElement);
      document.body.appendChild(floatingContainer);

      this.state.floatingElement = floatingContainer;
      this.updateFloatingVisibility();
    },

    createShareElement(options) {
      const {
        pageData,
        networks,
        showNative,
        showCopy,
        floating,
      } = options;

      const wrapper = document.createElement('div');

      wrapper.className = [
        this.config.classes.wrapper,
        floating ? this.config.classes.floating : '',
      ]
        .filter(Boolean)
        .join(' ');

      wrapper.setAttribute('role', 'navigation');
      wrapper.setAttribute('aria-label', 'Share this page');

      const list = document.createElement('div');
      list.className = this.config.classes.list;

      if (showNative && this.canUseNativeShare(pageData)) {
        const nativeButton = this.createNativeShareButton(pageData);
        list.appendChild(nativeButton);
      }

      networks.forEach((networkKey) => {
        const button = this.createNetworkButton(networkKey, pageData);
        if (button) list.appendChild(button);
      });

      if (showCopy) {
        const copyButton = this.createCopyButton(pageData);
        list.appendChild(copyButton);
      }

      wrapper.appendChild(list);

      return wrapper;
    },

    createNetworkButton(networkKey, pageData) {
      const network = this.config.networks[networkKey];

      if (!network) return null;

      const button = document.createElement('button');

      button.type = 'button';
      button.className = `${this.config.classes.button} rx-share__button--${networkKey}`;
      button.dataset.rxShareButton = networkKey;
      button.dataset.rxShareUrl = pageData.url;
      button.dataset.rxShareTitle = pageData.title;
      button.setAttribute('aria-label', `Share on ${network.label}`);
      button.setAttribute('title', `Share on ${network.label}`);

      button.innerHTML = `
        <span class="rx-share__icon" aria-hidden="true">${network.icon}</span>
        <span class="rx-share__text">${network.label}</span>
      `;

      button.addEventListener('click', () => {
        this.shareToNetwork(networkKey, pageData);
      });

      return button;
    },

    createNativeShareButton(pageData) {
      const button = document.createElement('button');

      button.type = 'button';
      button.className = `${this.config.classes.button} ${this.config.classes.nativeButton}`;
      button.dataset.rxNativeShare = 'true';
      button.setAttribute('aria-label', 'Share this page');
      button.setAttribute('title', 'Share this page');

      button.innerHTML = `
        <span class="rx-share__icon" aria-hidden="true">↗</span>
        <span class="rx-share__text">Share</span>
      `;

      button.addEventListener('click', () => {
        this.nativeShare(pageData);
      });

      return button;
    },

    createCopyButton(pageData) {
      const button = document.createElement('button');

      button.type = 'button';
      button.className = `${this.config.classes.button} ${this.config.classes.copyButton}`;
      button.dataset.rxCopyLink = 'true';
      button.dataset.rxCopyValue = pageData.url;
      button.setAttribute('aria-label', 'Copy page link');
      button.setAttribute('title', 'Copy page link');

      button.innerHTML = `
        <span class="rx-share__icon" aria-hidden="true">⛓</span>
        <span class="rx-share__text">Copy</span>
      `;

      button.addEventListener('click', () => {
        this.copyLink(pageData.url, button);
      });

      return button;
    },

    bindExistingButtons() {
      const shareButtons = document.querySelectorAll(this.config.selectors.shareButton);

      shareButtons.forEach((button) => {
        if (button.dataset.rxShareBound === 'true') return;

        button.addEventListener('click', () => {
          const network = button.dataset.rxShareButton;
          const pageData = this.getPageData(button);

          if (network) {
            this.shareToNetwork(network, pageData);
          }
        });

        button.dataset.rxShareBound = 'true';
      });

      const copyButtons = document.querySelectorAll(this.config.selectors.copyButton);

      copyButtons.forEach((button) => {
        if (button.dataset.rxCopyBound === 'true') return;

        button.addEventListener('click', () => {
          const value =
            button.dataset.rxCopyValue ||
            button.dataset.rxShareUrl ||
            this.state.pageData.url;

          this.copyLink(value, button);
        });

        button.dataset.rxCopyBound = 'true';
      });

      const nativeButtons = document.querySelectorAll(this.config.selectors.nativeButton);

      nativeButtons.forEach((button) => {
        if (button.dataset.rxNativeBound === 'true') return;

        button.addEventListener('click', () => {
          const pageData = this.getPageData(button);
          this.nativeShare(pageData);
        });

        button.dataset.rxNativeBound = 'true';
      });
    },

    bindGlobalEvents() {
      let ticking = false;

      window.addEventListener(
        'scroll',
        () => {
          if (ticking) return;

          ticking = true;

          window.requestAnimationFrame(() => {
            this.updateFloatingVisibility();
            ticking = false;
          });
        },
        { passive: true }
      );

      window.addEventListener(
        'resize',
        this.debounce(() => {
          this.updateFloatingVisibility();
        }, 150),
        { passive: true }
      );

      document.addEventListener('keydown', (event) => {
        if (event.key === 'Escape') {
          this.closeAllPopupsSignal();
        }
      });
    },

    canUseNativeShare(pageData) {
      if (!navigator.share) return false;

      try {
        return navigator.canShare
          ? navigator.canShare({
              title: pageData.title,
              text: pageData.description,
              url: pageData.url,
            })
          : true;
      } catch (error) {
        return true;
      }
    },

    async nativeShare(pageData) {
      if (!navigator.share) {
        this.copyLink(pageData.url);
        return;
      }

      try {
        await navigator.share({
          title: pageData.title,
          text: pageData.description,
          url: pageData.url,
        });

        this.track('native_share_success', {
          network: 'native',
          url: pageData.url,
          title: pageData.title,
        });
      } catch (error) {
        if (error && error.name === 'AbortError') {
          this.track('native_share_cancelled', {
            network: 'native',
            url: pageData.url,
          });
          return;
        }

        this.copyLink(pageData.url);

        this.track('native_share_failed', {
          network: 'native',
          url: pageData.url,
          error: error?.message || 'Unknown error',
        });
      }
    },

    shareToNetwork(networkKey, pageData) {
      const shareUrl = this.buildShareUrl(networkKey, pageData);

      if (!shareUrl) return;

      if (networkKey === 'email') {
        window.location.href = shareUrl;

        this.track('share_click', {
          network: networkKey,
          url: pageData.url,
          title: pageData.title,
        });

        return;
      }

      this.openPopup(shareUrl, `rx-share-${networkKey}`);

      this.track('share_click', {
        network: networkKey,
        url: pageData.url,
        title: pageData.title,
      });
    },

    openPopup(url, name) {
      const width = this.config.popup.width;
      const height = this.config.popup.height;

      const left = Math.max(0, window.screenX + (window.outerWidth - width) / 2);
      const top = Math.max(0, window.screenY + (window.outerHeight - height) / 2);

      const features = [
        `width=${width}`,
        `height=${height}`,
        `left=${left}`,
        `top=${top}`,
        'menubar=no',
        'toolbar=no',
        'location=no',
        'status=no',
        'scrollbars=yes',
        'resizable=yes',
      ].join(',');

      const popup = window.open(url, name, features);

      if (popup && popup.focus) {
        popup.focus();
      } else {
        window.location.href = url;
      }
    },

    async copyLink(value, button) {
      const text = String(value || this.state.pageData.url);

      let copied = false;

      try {
        if (navigator.clipboard && window.isSecureContext) {
          await navigator.clipboard.writeText(text);
          copied = true;
        } else {
          copied = this.fallbackCopy(text);
        }
      } catch (error) {
        copied = this.fallbackCopy(text);
      }

      if (button) {
        this.updateCopyButton(button, copied);
      }

      this.showToast(
        copied ? this.config.copy.successText : this.config.copy.errorText,
        copied ? 'success' : 'error'
      );

      this.track(copied ? 'copy_link_success' : 'copy_link_failed', {
        url: text,
      });

      return copied;
    },

    fallbackCopy(text) {
      const textarea = document.createElement('textarea');

      textarea.value = text;
      textarea.setAttribute('readonly', '');
      textarea.style.position = 'fixed';
      textarea.style.top = '-9999px';
      textarea.style.left = '-9999px';

      document.body.appendChild(textarea);
      textarea.select();
      textarea.setSelectionRange(0, textarea.value.length);

      let success = false;

      try {
        success = document.execCommand('copy');
      } catch (error) {
        success = false;
      }

      document.body.removeChild(textarea);

      return success;
    },

    updateCopyButton(button, copied) {
      const textElement = button.querySelector('.rx-share__text');
      const originalText = button.dataset.rxOriginalText || textElement?.textContent || 'Copy';

      button.dataset.rxOriginalText = originalText;

      if (copied) {
        button.classList.add(this.config.classes.copied);
        if (textElement) textElement.textContent = 'Copied';
      } else {
        if (textElement) textElement.textContent = 'Failed';
      }

      window.setTimeout(() => {
        button.classList.remove(this.config.classes.copied);
        if (textElement) textElement.textContent = originalText;
      }, this.config.copy.resetDelay);
    },

    showToast(message, type) {
      let toast = document.querySelector('.rx-share-toast');

      if (!toast) {
        toast = document.createElement('div');
        toast.className = 'rx-share-toast';
        toast.setAttribute('role', 'status');
        toast.setAttribute('aria-live', 'polite');
        document.body.appendChild(toast);
      }

      toast.textContent = message;
      toast.dataset.type = type || 'info';
      toast.classList.add('is-visible');

      clearTimeout(toast.rxTimer);

      toast.rxTimer = window.setTimeout(() => {
        toast.classList.remove('is-visible');
      }, 1800);
    },

    updateFloatingVisibility() {
      const floating = this.state.floatingElement;

      if (!floating) return;

      const scrollY = window.scrollY || document.documentElement.scrollTop;
      const shouldShow = scrollY > this.config.floating.showAfterScroll;

      let nearFooter = false;

      if (this.config.floating.hideNearFooter) {
        const footer = document.querySelector('footer, .site-footer, #footer');

        if (footer) {
          const footerRect = footer.getBoundingClientRect();
          nearFooter = footerRect.top < window.innerHeight - 80;
        }
      }

      floating.classList.toggle(this.config.classes.visible, shouldShow && !nearFooter);
      floating.classList.toggle(this.config.classes.hidden, !shouldShow || nearFooter);
    },

    closeAllPopupsSignal() {
      this.dispatchEvent('rxShareEscape', {
        source: 'keyboard',
      });
    },

    track(eventName, detail) {
      const payload = {
        event: eventName,
        detail: detail || {},
        timestamp: Date.now(),
      };

      this.dispatchEvent('rxShareTrack', payload);

      if (window.dataLayer && Array.isArray(window.dataLayer)) {
        window.dataLayer.push({
          event: `rx_${eventName}`,
          rx_share: detail || {},
        });
      }

      if (typeof window.gtag === 'function') {
        window.gtag('event', eventName, {
          event_category: 'RX Share',
          event_label: detail?.network || detail?.url || 'share',
          page_location: detail?.url || window.location.href,
        });
      }
    },

    dispatchEvent(name, detail) {
      document.dispatchEvent(
        new CustomEvent(name, {
          bubbles: true,
          detail,
        })
      );
    },

    debounce(callback, delay) {
      let timer;

      return function debounced() {
        const args = arguments;
        const context = this;

        clearTimeout(timer);

        timer = setTimeout(() => {
          callback.apply(context, args);
        }, delay);
      };
    },

    injectBaseStyles() {
      if (document.getElementById('rx-share-base-style')) return;

      const style = document.createElement('style');

      style.id = 'rx-share-base-style';

      style.textContent = `
        .rx-share {
          --rx-share-gap: 8px;
          --rx-share-size: 42px;
          --rx-share-radius: 999px;
          --rx-share-bg: #ffffff;
          --rx-share-text: #1f2937;
          --rx-share-border: rgba(15, 23, 42, 0.12);
          --rx-share-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
          --rx-share-hover-bg: #f8fafc;
          --rx-share-focus: #2563eb;
        }

        .rx-share__list {
          display: flex;
          flex-wrap: wrap;
          align-items: center;
          gap: var(--rx-share-gap);
        }

        .rx-share__button {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          gap: 7px;
          min-width: var(--rx-share-size);
          min-height: var(--rx-share-size);
          padding: 0 14px;
          border: 1px solid var(--rx-share-border);
          border-radius: var(--rx-share-radius);
          background: var(--rx-share-bg);
          color: var(--rx-share-text);
          font: inherit;
          font-size: 14px;
          line-height: 1;
          cursor: pointer;
          box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);
          transition:
            transform 160ms ease,
            background-color 160ms ease,
            border-color 160ms ease,
            box-shadow 160ms ease;
        }

        .rx-share__button:hover {
          background: var(--rx-share-hover-bg);
          transform: translateY(-1px);
          box-shadow: var(--rx-share-shadow);
        }

        .rx-share__button:focus-visible {
          outline: 3px solid color-mix(in srgb, var(--rx-share-focus), transparent 70%);
          outline-offset: 2px;
        }

        .rx-share__icon {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          min-width: 18px;
          font-weight: 700;
          text-transform: uppercase;
        }

        .rx-share__text {
          white-space: nowrap;
        }

        .rx-share--floating .rx-share__list {
          flex-direction: column;
        }

        .rx-floating-share-container {
          position: fixed;
          z-index: 900;
          left: 16px;
          top: 50%;
          transform: translate3d(-120%, -50%, 0);
          opacity: 0;
          pointer-events: none;
          transition:
            transform 220ms ease,
            opacity 220ms ease;
        }

        .rx-floating-share-container.is-visible {
          transform: translate3d(0, -50%, 0);
          opacity: 1;
          pointer-events: auto;
        }

        .rx-floating-share-container .rx-share__button {
          width: var(--rx-share-size);
          height: var(--rx-share-size);
          padding: 0;
        }

        .rx-floating-share-container .rx-share__text {
          position: absolute;
          width: 1px;
          height: 1px;
          overflow: hidden;
          clip: rect(0 0 0 0);
          white-space: nowrap;
        }

        .rx-share-toast {
          position: fixed;
          z-index: 1000;
          left: 50%;
          bottom: 24px;
          transform: translate3d(-50%, 20px, 0);
          opacity: 0;
          pointer-events: none;
          padding: 10px 14px;
          border-radius: 999px;
          background: #111827;
          color: #ffffff;
          font-size: 14px;
          line-height: 1.3;
          box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18);
          transition:
            transform 180ms ease,
            opacity 180ms ease;
        }

        .rx-share-toast.is-visible {
          transform: translate3d(-50%, 0, 0);
          opacity: 1;
        }

        @media (max-width: 991px) {
          .rx-floating-share-container {
            left: 50%;
            top: auto;
            bottom: 16px;
            transform: translate3d(-50%, 140%, 0);
          }

          .rx-floating-share-container.is-visible {
            transform: translate3d(-50%, 0, 0);
          }

          .rx-share--floating .rx-share__list {
            flex-direction: row;
            padding: 8px;
            border: 1px solid var(--rx-share-border);
            border-radius: 999px;
            background: rgba(255, 255, 255, 0.94);
            box-shadow: var(--rx-share-shadow);
            backdrop-filter: blur(12px);
          }
        }

        @media (max-width: 520px) {
          .rx-share__button {
            min-width: 38px;
            min-height: 38px;
            padding: 0 11px;
            font-size: 13px;
          }

          .rx-floating-share-container .rx-share__button {
            width: 38px;
            height: 38px;
          }
        }

        @media (prefers-reduced-motion: reduce) {
          .rx-share__button,
          .rx-floating-share-container,
          .rx-share-toast {
            transition: none;
          }

          .rx-share__button:hover {
            transform: none;
          }
        }
      `;

      document.head.appendChild(style);
    },
  };

  window.RXShare = RXShare;

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => RXShare.init());
  } else {
    RXShare.init();
  }
})();

Use it in your theme HTML like this:

<div data-rx-share></div>

Custom networks:

<div data-rx-share data-rx-share-networks="facebook,x,linkedin,whatsapp,telegram,email"></div>

Floating share bar will auto-create after scrolling. To disable floating share on a page:

<body data-rx-floating-share="false">

For WordPress enqueue, add this in your theme enqueue file:

wp_enqueue_script(
    'rx-theme-share-chunk',
    get_template_directory_uri() . '/assets/static-js/static-chunks-js/chunk-014-share.js',
    array(),
    wp_get_theme()->get('Version'),
    true
);

Best loading style for this file:

wp_script_add_data('rx-theme-share-chunk', 'defer', true);

This file is safe as a static chunk because it waits for DOM ready, does not depend on jQuery, and will not break the page if no share container exists.

Leave a Reply

Your email address will not be published. Required fields are marked *