chunk-001-core.js

I’ll build this as a safe, advanced “core chunk” for RX Theme: performance helpers, DOM utilities, accessibility, lazy loading, fetch helpers, analytics-safe events, WordPress-friendly hooks, and error protection in one file.

Yes friend. Create this file:

assets/static/static-chunks-js/chunk-001-core.js

This is an advanced RX Theme core JavaScript chunk. It is safe, modular, WordPress-friendly, performance-focused, and can be used as your first core JS file.

/*!
 * RX Theme Core Chunk
 * File: assets/static/static-chunks-js/chunk-001-core.js
 * Theme: RX Theme
 * Author: RxHarun
 * Purpose: Core browser helpers, performance, accessibility, lazy loading,
 *          navigation, UI state, WordPress compatibility, and safe utilities.
 */

(function () {
  "use strict";

  /**
   * ============================================================
   * RX GLOBAL NAMESPACE
   * ============================================================
   */

  const RX = (window.RXTheme = window.RXTheme || {});

  RX.version = RX.version || "1.0.0";
  RX.name = RX.name || "RX Theme";
  RX.debug = Boolean(window.rxThemeDebug || false);

  RX.config = Object.assign(
    {
      selectors: {
        html: "html",
        body: "body",
        header: "[data-rx-header]",
        navToggle: "[data-rx-nav-toggle]",
        navMenu: "[data-rx-nav-menu]",
        dropdownToggle: "[data-rx-dropdown-toggle]",
        backToTop: "[data-rx-back-to-top]",
        lazyImage: "img[data-src], img[data-rx-src]",
        lazyBackground: "[data-rx-bg]",
        accordion: "[data-rx-accordion]",
        accordionToggle: "[data-rx-accordion-toggle]",
        tabs: "[data-rx-tabs]",
        tabButton: "[data-rx-tab-button]",
        tabPanel: "[data-rx-tab-panel]",
        modal: "[data-rx-modal]",
        modalOpen: "[data-rx-modal-open]",
        modalClose: "[data-rx-modal-close]",
        searchToggle: "[data-rx-search-toggle]",
        searchForm: "[data-rx-search-form]",
        readingProgress: "[data-rx-reading-progress]",
        tocLinks: "[data-rx-toc] a",
        copyButton: "[data-rx-copy]",
        externalLinks: "a[href^='http']",
      },

      classes: {
        ready: "rx-js-ready",
        loaded: "rx-page-loaded",
        scrolled: "rx-is-scrolled",
        navOpen: "rx-nav-open",
        dropdownOpen: "rx-dropdown-open",
        modalOpen: "rx-modal-open",
        active: "rx-is-active",
        hidden: "rx-is-hidden",
        focusVisible: "rx-focus-visible",
        reducedMotion: "rx-reduced-motion",
        lazyLoaded: "rx-lazy-loaded",
        lazyError: "rx-lazy-error",
      },

      breakpoints: {
        sm: 576,
        md: 768,
        lg: 992,
        xl: 1200,
        xxl: 1400,
      },

      scrollOffset: 80,
      backToTopOffset: 500,
      readingProgress: true,
      smoothScroll: true,
      lazyLoad: true,
      externalLinkSecurity: true,
    },
    window.rxThemeConfig || {}
  );

  /**
   * ============================================================
   * LOGGER
   * ============================================================
   */

  RX.log = function () {
    if (!RX.debug || !window.console) return;
    console.log.apply(console, ["[RXTheme]"].concat(Array.prototype.slice.call(arguments)));
  };

  RX.warn = function () {
    if (!RX.debug || !window.console) return;
    console.warn.apply(console, ["[RXTheme]"].concat(Array.prototype.slice.call(arguments)));
  };

  RX.error = function () {
    if (!RX.debug || !window.console) return;
    console.error.apply(console, ["[RXTheme]"].concat(Array.prototype.slice.call(arguments)));
  };

  /**
   * ============================================================
   * BASIC DOM UTILITIES
   * ============================================================
   */

  RX.$ = function (selector, context) {
    if (!selector) return null;
    return (context || document).querySelector(selector);
  };

  RX.$$ = function (selector, context) {
    if (!selector) return [];
    return Array.prototype.slice.call((context || document).querySelectorAll(selector));
  };

  RX.exists = function (selector, context) {
    return Boolean(RX.$(selector, context));
  };

  RX.on = function (element, event, handler, options) {
    if (!element || !event || typeof handler !== "function") return null;
    element.addEventListener(event, handler, options || false);

    return function removeListener() {
      element.removeEventListener(event, handler, options || false);
    };
  };

  RX.off = function (element, event, handler, options) {
    if (!element || !event || typeof handler !== "function") return;
    element.removeEventListener(event, handler, options || false);
  };

  RX.delegate = function (parent, event, selector, handler, options) {
    if (!parent || !event || !selector || typeof handler !== "function") return null;

    const listener = function (e) {
      const target = e.target.closest(selector);
      if (!target || !parent.contains(target)) return;
      handler.call(target, e, target);
    };

    parent.addEventListener(event, listener, options || false);

    return function removeDelegatedListener() {
      parent.removeEventListener(event, listener, options || false);
    };
  };

  RX.ready = function (callback) {
    if (typeof callback !== "function") return;

    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", callback, { once: true });
    } else {
      callback();
    }
  };

  RX.load = function (callback) {
    if (typeof callback !== "function") return;

    if (document.readyState === "complete") {
      callback();
    } else {
      window.addEventListener("load", callback, { once: true });
    }
  };

  RX.create = function (tag, attributes, children) {
    const element = document.createElement(tag);

    if (attributes && typeof attributes === "object") {
      Object.keys(attributes).forEach(function (key) {
        if (key === "class") {
          element.className = attributes[key];
        } else if (key === "text") {
          element.textContent = attributes[key];
        } else if (key === "html") {
          element.innerHTML = attributes[key];
        } else if (key.startsWith("data-")) {
          element.setAttribute(key, attributes[key]);
        } else {
          element[key] = attributes[key];
        }
      });
    }

    if (Array.isArray(children)) {
      children.forEach(function (child) {
        if (typeof child === "string") {
          element.appendChild(document.createTextNode(child));
        } else if (child instanceof Node) {
          element.appendChild(child);
        }
      });
    }

    return element;
  };

  /**
   * ============================================================
   * CLASS AND ATTRIBUTE HELPERS
   * ============================================================
   */

  RX.addClass = function (element, className) {
    if (element && className) element.classList.add(className);
  };

  RX.removeClass = function (element, className) {
    if (element && className) element.classList.remove(className);
  };

  RX.toggleClass = function (element, className, force) {
    if (!element || !className) return false;
    return element.classList.toggle(className, force);
  };

  RX.hasClass = function (element, className) {
    if (!element || !className) return false;
    return element.classList.contains(className);
  };

  RX.attr = function (element, name, value) {
    if (!element || !name) return null;

    if (typeof value === "undefined") {
      return element.getAttribute(name);
    }

    if (value === null) {
      element.removeAttribute(name);
      return null;
    }

    element.setAttribute(name, value);
    return value;
  };

  /**
   * ============================================================
   * TYPE AND VALUE HELPERS
   * ============================================================
   */

  RX.isObject = function (value) {
    return value !== null && typeof value === "object" && !Array.isArray(value);
  };

  RX.isString = function (value) {
    return typeof value === "string";
  };

  RX.isNumber = function (value) {
    return typeof value === "number" && !Number.isNaN(value);
  };

  RX.toNumber = function (value, fallback) {
    const number = Number(value);
    return Number.isFinite(number) ? number : fallback || 0;
  };

  RX.clamp = function (value, min, max) {
    return Math.min(Math.max(value, min), max);
  };

  RX.uuid = function (prefix) {
    return (prefix || "rx") + "-" + Math.random().toString(36).slice(2, 10);
  };

  /**
   * ============================================================
   * PERFORMANCE HELPERS
   * ============================================================
   */

  RX.debounce = function (callback, delay) {
    let timer = null;

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

      clearTimeout(timer);

      timer = setTimeout(function () {
        callback.apply(context, args);
      }, delay || 150);
    };
  };

  RX.throttle = function (callback, limit) {
    let waiting = false;

    return function throttled() {
      if (waiting) return;

      const context = this;
      const args = arguments;

      waiting = true;

      window.setTimeout(function () {
        callback.apply(context, args);
        waiting = false;
      }, limit || 100);
    };
  };

  RX.raf = function (callback) {
    return window.requestAnimationFrame
      ? window.requestAnimationFrame(callback)
      : window.setTimeout(callback, 16);
  };

  RX.idle = function (callback, timeout) {
    if ("requestIdleCallback" in window) {
      return window.requestIdleCallback(callback, { timeout: timeout || 1500 });
    }

    return window.setTimeout(callback, 1);
  };

  /**
   * ============================================================
   * DEVICE AND BROWSER HELPERS
   * ============================================================
   */

  RX.browser = {
    supportsPassive: false,
    supportsIntersectionObserver: "IntersectionObserver" in window,
    supportsLocalStorage: false,
    supportsSessionStorage: false,
    supportsWebp: false,
  };

  try {
    const passiveTest = Object.defineProperty({}, "passive", {
      get: function () {
        RX.browser.supportsPassive = true;
        return true;
      },
    });

    window.addEventListener("rx-passive-test", null, passiveTest);
    window.removeEventListener("rx-passive-test", null, passiveTest);
  } catch (e) {}

  try {
    const testKey = "__rx_storage_test__";
    window.localStorage.setItem(testKey, "1");
    window.localStorage.removeItem(testKey);
    RX.browser.supportsLocalStorage = true;
  } catch (e) {}

  try {
    const testKey = "__rx_session_test__";
    window.sessionStorage.setItem(testKey, "1");
    window.sessionStorage.removeItem(testKey);
    RX.browser.supportsSessionStorage = true;
  } catch (e) {}

  RX.viewport = {
    width: function () {
      return Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0);
    },

    height: function () {
      return Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
    },

    isBelow: function (breakpoint) {
      const value = RX.config.breakpoints[breakpoint] || RX.toNumber(breakpoint, 0);
      return RX.viewport.width() < value;
    },

    isAbove: function (breakpoint) {
      const value = RX.config.breakpoints[breakpoint] || RX.toNumber(breakpoint, 0);
      return RX.viewport.width() >= value;
    },
  };

  /**
   * ============================================================
   * STORAGE HELPERS
   * ============================================================
   */

  RX.storage = {
    get: function (key, fallback) {
      if (!RX.browser.supportsLocalStorage) return fallback;

      try {
        const item = window.localStorage.getItem(key);
        return item === null ? fallback : JSON.parse(item);
      } catch (e) {
        return fallback;
      }
    },

    set: function (key, value) {
      if (!RX.browser.supportsLocalStorage) return false;

      try {
        window.localStorage.setItem(key, JSON.stringify(value));
        return true;
      } catch (e) {
        return false;
      }
    },

    remove: function (key) {
      if (!RX.browser.supportsLocalStorage) return false;

      try {
        window.localStorage.removeItem(key);
        return true;
      } catch (e) {
        return false;
      }
    },
  };

  /**
   * ============================================================
   * CUSTOM EVENT SYSTEM
   * ============================================================
   */

  RX.events = {
    emit: function (name, detail, target) {
      if (!name) return;

      const event = new CustomEvent(name, {
        bubbles: true,
        cancelable: true,
        detail: detail || {},
      });

      (target || document).dispatchEvent(event);
    },

    on: function (name, handler, target) {
      if (!name || typeof handler !== "function") return null;
      return RX.on(target || document, name, handler);
    },

    once: function (name, handler, target) {
      if (!name || typeof handler !== "function") return null;
      return RX.on(target || document, name, handler, { once: true });
    },
  };

  /**
   * ============================================================
   * SAFE FETCH HELPER
   * ============================================================
   */

  RX.fetch = function (url, options) {
    const settings = Object.assign(
      {
        method: "GET",
        credentials: "same-origin",
        headers: {
          Accept: "application/json, text/plain, */*",
        },
      },
      options || {}
    );

    return fetch(url, settings).then(function (response) {
      const contentType = response.headers.get("content-type") || "";

      if (!response.ok) {
        throw new Error("RX fetch failed with status " + response.status);
      }

      if (contentType.includes("application/json")) {
        return response.json();
      }

      return response.text();
    });
  };

  /**
   * ============================================================
   * WORDPRESS AJAX HELPER
   * ============================================================
   */

  RX.wpAjax = function (action, data) {
    const ajaxUrl =
      window.rxThemeAjaxUrl ||
      (window.rxThemeData && window.rxThemeData.ajaxUrl) ||
      window.ajaxurl;

    if (!ajaxUrl) {
      RX.warn("WordPress AJAX URL missing.");
      return Promise.reject(new Error("AJAX URL missing."));
    }

    const formData = new FormData();
    formData.append("action", action);

    if (window.rxThemeNonce) {
      formData.append("nonce", window.rxThemeNonce);
    }

    if (data && typeof data === "object") {
      Object.keys(data).forEach(function (key) {
        formData.append(key, data[key]);
      });
    }

    return RX.fetch(ajaxUrl, {
      method: "POST",
      body: formData,
    });
  };

  /**
   * ============================================================
   * ACCESSIBILITY HELPERS
   * ============================================================
   */

  RX.a11y = {
    focusableSelector:
      'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])',

    getFocusable: function (container) {
      if (!container) return [];
      return RX.$$(RX.a11y.focusableSelector, container).filter(function (element) {
        return element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement;
      });
    },

    trapFocus: function (container) {
      if (!container) return null;

      const focusable = RX.a11y.getFocusable(container);
      const first = focusable[0];
      const last = focusable[focusable.length - 1];

      if (first) first.focus();

      const handler = function (e) {
        if (e.key !== "Tab") return;

        if (focusable.length === 0) {
          e.preventDefault();
          return;
        }

        if (e.shiftKey && document.activeElement === first) {
          e.preventDefault();
          last.focus();
        } else if (!e.shiftKey && document.activeElement === last) {
          e.preventDefault();
          first.focus();
        }
      };

      container.addEventListener("keydown", handler);

      return function removeTrap() {
        container.removeEventListener("keydown", handler);
      };
    },

    announce: function (message) {
      if (!message) return;

      let liveRegion = RX.$("#rx-live-region");

      if (!liveRegion) {
        liveRegion = RX.create("div", {
          id: "rx-live-region",
          class: "screen-reader-text",
        });

        liveRegion.setAttribute("aria-live", "polite");
        liveRegion.setAttribute("aria-atomic", "true");
        document.body.appendChild(liveRegion);
      }

      liveRegion.textContent = "";

      window.setTimeout(function () {
        liveRegion.textContent = message;
      }, 50);
    },
  };

  /**
   * ============================================================
   * REDUCED MOTION
   * ============================================================
   */

  RX.motion = {
    prefersReduced: function () {
      return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
    },

    init: function () {
      if (RX.motion.prefersReduced()) {
        RX.addClass(document.documentElement, RX.config.classes.reducedMotion);
      }
    },
  };

  /**
   * ============================================================
   * SCROLL HELPERS
   * ============================================================
   */

  RX.scroll = {
    y: function () {
      return window.pageYOffset || document.documentElement.scrollTop || 0;
    },

    to: function (target, offset) {
      let top = 0;

      if (typeof target === "number") {
        top = target;
      } else if (typeof target === "string") {
        const element = RX.$(target);
        if (!element) return;
        top = element.getBoundingClientRect().top + RX.scroll.y();
      } else if (target instanceof Element) {
        top = target.getBoundingClientRect().top + RX.scroll.y();
      }

      top = Math.max(top - (offset || RX.config.scrollOffset), 0);

      window.scrollTo({
        top: top,
        behavior: RX.motion.prefersReduced() ? "auto" : "smooth",
      });
    },

    lock: function () {
      const scrollY = RX.scroll.y();
      document.body.dataset.rxScrollY = String(scrollY);
      document.body.style.position = "fixed";
      document.body.style.top = "-" + scrollY + "px";
      document.body.style.left = "0";
      document.body.style.right = "0";
      document.body.style.width = "100%";
    },

    unlock: function () {
      const scrollY = RX.toNumber(document.body.dataset.rxScrollY, 0);

      document.body.style.position = "";
      document.body.style.top = "";
      document.body.style.left = "";
      document.body.style.right = "";
      document.body.style.width = "";

      delete document.body.dataset.rxScrollY;

      window.scrollTo(0, scrollY);
    },
  };

  /**
   * ============================================================
   * HEADER SCROLL STATE
   * ============================================================
   */

  RX.header = {
    init: function () {
      const html = document.documentElement;

      const update = RX.throttle(function () {
        const isScrolled = RX.scroll.y() > 10;
        RX.toggleClass(html, RX.config.classes.scrolled, isScrolled);
      }, 80);

      update();
      window.addEventListener("scroll", update, RX.browser.supportsPassive ? { passive: true } : false);
    },
  };

  /**
   * ============================================================
   * MOBILE NAVIGATION
   * ============================================================
   */

  RX.navigation = {
    isOpen: false,

    init: function () {
      const toggle = RX.$(RX.config.selectors.navToggle);
      const menu = RX.$(RX.config.selectors.navMenu);

      if (!toggle || !menu) return;

      toggle.setAttribute("aria-expanded", "false");

      RX.on(toggle, "click", function () {
        RX.navigation.toggle(toggle, menu);
      });

      RX.on(document, "keydown", function (e) {
        if (e.key === "Escape" && RX.navigation.isOpen) {
          RX.navigation.close(toggle, menu);
        }
      });

      RX.on(window, "resize", RX.debounce(function () {
        if (RX.viewport.isAbove("lg") && RX.navigation.isOpen) {
          RX.navigation.close(toggle, menu);
        }
      }, 150));
    },

    open: function (toggle, menu) {
      RX.navigation.isOpen = true;
      toggle.setAttribute("aria-expanded", "true");
      RX.addClass(document.documentElement, RX.config.classes.navOpen);
      RX.addClass(menu, RX.config.classes.active);
      RX.events.emit("rx:navigation:open");
    },

    close: function (toggle, menu) {
      RX.navigation.isOpen = false;
      toggle.setAttribute("aria-expanded", "false");
      RX.removeClass(document.documentElement, RX.config.classes.navOpen);
      RX.removeClass(menu, RX.config.classes.active);
      RX.events.emit("rx:navigation:close");
    },

    toggle: function (toggle, menu) {
      if (RX.navigation.isOpen) {
        RX.navigation.close(toggle, menu);
      } else {
        RX.navigation.open(toggle, menu);
      }
    },
  };

  /**
   * ============================================================
   * DROPDOWN MENU
   * ============================================================
   */

  RX.dropdowns = {
    init: function () {
      RX.delegate(document, "click", RX.config.selectors.dropdownToggle, function (e, toggle) {
        e.preventDefault();

        const parent = toggle.closest("[data-rx-dropdown]");
        if (!parent) return;

        const isOpen = RX.hasClass(parent, RX.config.classes.dropdownOpen);

        RX.dropdowns.closeAll(parent);

        RX.toggleClass(parent, RX.config.classes.dropdownOpen, !isOpen);
        toggle.setAttribute("aria-expanded", String(!isOpen));
      });

      RX.on(document, "click", function (e) {
        if (!e.target.closest("[data-rx-dropdown]")) {
          RX.dropdowns.closeAll();
        }
      });

      RX.on(document, "keydown", function (e) {
        if (e.key === "Escape") {
          RX.dropdowns.closeAll();
        }
      });
    },

    closeAll: function (except) {
      RX.$$("[data-rx-dropdown]").forEach(function (dropdown) {
        if (except && dropdown === except) return;

        RX.removeClass(dropdown, RX.config.classes.dropdownOpen);

        const toggle = RX.$(RX.config.selectors.dropdownToggle, dropdown);
        if (toggle) toggle.setAttribute("aria-expanded", "false");
      });
    },
  };

  /**
   * ============================================================
   * LAZY LOADING IMAGE AND BACKGROUND
   * ============================================================
   */

  RX.lazy = {
    init: function () {
      if (!RX.config.lazyLoad) return;

      const lazyImages = RX.$$(RX.config.selectors.lazyImage);
      const lazyBackgrounds = RX.$$(RX.config.selectors.lazyBackground);

      const items = lazyImages.concat(lazyBackgrounds);

      if (!items.length) return;

      if (RX.browser.supportsIntersectionObserver) {
        const observer = new IntersectionObserver(
          function (entries) {
            entries.forEach(function (entry) {
              if (!entry.isIntersecting) return;

              RX.lazy.load(entry.target);
              observer.unobserve(entry.target);
            });
          },
          {
            rootMargin: "300px 0px",
            threshold: 0.01,
          }
        );

        items.forEach(function (item) {
          observer.observe(item);
        });
      } else {
        items.forEach(function (item) {
          RX.lazy.load(item);
        });
      }
    },

    load: function (element) {
      if (!element) return;

      if (element.tagName === "IMG") {
        const src = element.dataset.src || element.dataset.rxSrc;
        const srcset = element.dataset.srcset || element.dataset.rxSrcset;

        if (srcset) element.setAttribute("srcset", srcset);
        if (src) element.setAttribute("src", src);

        element.addEventListener(
          "load",
          function () {
            RX.addClass(element, RX.config.classes.lazyLoaded);
            RX.events.emit("rx:lazy:loaded", { element: element }, element);
          },
          { once: true }
        );

        element.addEventListener(
          "error",
          function () {
            RX.addClass(element, RX.config.classes.lazyError);
            RX.events.emit("rx:lazy:error", { element: element }, element);
          },
          { once: true }
        );
      } else {
        const bg = element.dataset.rxBg;
        if (bg) {
          element.style.backgroundImage = "url('" + bg.replace(/'/g, "\\'") + "')";
          RX.addClass(element, RX.config.classes.lazyLoaded);
        }
      }
    },
  };

  /**
   * ============================================================
   * SMOOTH SCROLL ANCHORS
   * ============================================================
   */

  RX.smoothScroll = {
    init: function () {
      if (!RX.config.smoothScroll) return;

      RX.delegate(document, "click", 'a[href^="#"]:not([href="#"])', function (e, link) {
        const hash = link.getAttribute("href");
        if (!hash || hash.length < 2) return;

        const target = RX.$(decodeURIComponent(hash));
        if (!target) return;

        e.preventDefault();

        RX.scroll.to(target);

        if (history.pushState) {
          history.pushState(null, "", hash);
        } else {
          window.location.hash = hash;
        }
      });
    },
  };

  /**
   * ============================================================
   * BACK TO TOP BUTTON
   * ============================================================
   */

  RX.backToTop = {
    init: function () {
      const button = RX.$(RX.config.selectors.backToTop);
      if (!button) return;

      const update = RX.throttle(function () {
        const visible = RX.scroll.y() > RX.config.backToTopOffset;
        RX.toggleClass(button, RX.config.classes.active, visible);
        button.setAttribute("aria-hidden", String(!visible));
      }, 100);

      RX.on(button, "click", function (e) {
        e.preventDefault();
        RX.scroll.to(0, 0);
      });

      update();
      window.addEventListener("scroll", update, RX.browser.supportsPassive ? { passive: true } : false);
    },
  };

  /**
   * ============================================================
   * READING PROGRESS BAR
   * ============================================================
   */

  RX.readingProgress = {
    init: function () {
      if (!RX.config.readingProgress) return;

      const bar = RX.$(RX.config.selectors.readingProgress);
      if (!bar) return;

      const update = RX.throttle(function () {
        const scrollTop = RX.scroll.y();
        const docHeight = document.documentElement.scrollHeight - window.innerHeight;
        const progress = docHeight > 0 ? RX.clamp((scrollTop / docHeight) * 100, 0, 100) : 0;

        bar.style.width = progress + "%";
        bar.setAttribute("aria-valuenow", String(Math.round(progress)));
      }, 50);

      update();
      window.addEventListener("scroll", update, RX.browser.supportsPassive ? { passive: true } : false);
      window.addEventListener("resize", RX.debounce(update, 150));
    },
  };

  /**
   * ============================================================
   * ACCORDION
   * ============================================================
   */

  RX.accordion = {
    init: function () {
      RX.$$(RX.config.selectors.accordion).forEach(function (accordion) {
        RX.accordion.setup(accordion);
      });
    },

    setup: function (accordion) {
      const toggles = RX.$$(RX.config.selectors.accordionToggle, accordion);

      toggles.forEach(function (toggle) {
        const panelId = toggle.getAttribute("aria-controls");
        const panel = panelId ? document.getElementById(panelId) : toggle.nextElementSibling;

        if (!panel) return;

        if (!panel.id) panel.id = RX.uuid("rx-accordion-panel");

        toggle.setAttribute("aria-controls", panel.id);
        toggle.setAttribute("aria-expanded", toggle.getAttribute("aria-expanded") || "false");

        RX.on(toggle, "click", function () {
          const isOpen = toggle.getAttribute("aria-expanded") === "true";
          const single = accordion.dataset.rxAccordion === "single";

          if (single) {
            toggles.forEach(function (otherToggle) {
              if (otherToggle === toggle) return;

              const otherPanelId = otherToggle.getAttribute("aria-controls");
              const otherPanel = otherPanelId ? document.getElementById(otherPanelId) : null;

              otherToggle.setAttribute("aria-expanded", "false");

              if (otherPanel) {
                otherPanel.hidden = true;
                RX.removeClass(otherPanel, RX.config.classes.active);
              }
            });
          }

          toggle.setAttribute("aria-expanded", String(!isOpen));
          panel.hidden = isOpen;
          RX.toggleClass(panel, RX.config.classes.active, !isOpen);
        });
      });
    },
  };

  /**
   * ============================================================
   * TABS
   * ============================================================
   */

  RX.tabs = {
    init: function () {
      RX.$$(RX.config.selectors.tabs).forEach(function (tabs) {
        RX.tabs.setup(tabs);
      });
    },

    setup: function (tabs) {
      const buttons = RX.$$(RX.config.selectors.tabButton, tabs);
      const panels = RX.$$(RX.config.selectors.tabPanel, tabs);

      if (!buttons.length || !panels.length) return;

      buttons.forEach(function (button, index) {
        const panel = panels[index];

        if (!button.id) button.id = RX.uuid("rx-tab");
        if (!panel.id) panel.id = RX.uuid("rx-panel");

        button.setAttribute("role", "tab");
        button.setAttribute("aria-controls", panel.id);

        panel.setAttribute("role", "tabpanel");
        panel.setAttribute("aria-labelledby", button.id);

        RX.on(button, "click", function () {
          RX.tabs.activate(button, buttons, panels);
        });

        RX.on(button, "keydown", function (e) {
          const currentIndex = buttons.indexOf(button);
          let nextIndex = currentIndex;

          if (e.key === "ArrowRight") nextIndex = (currentIndex + 1) % buttons.length;
          if (e.key === "ArrowLeft") nextIndex = (currentIndex - 1 + buttons.length) % buttons.length;
          if (e.key === "Home") nextIndex = 0;
          if (e.key === "End") nextIndex = buttons.length - 1;

          if (nextIndex !== currentIndex) {
            e.preventDefault();
            buttons[nextIndex].focus();
            RX.tabs.activate(buttons[nextIndex], buttons, panels);
          }
        });
      });

      RX.tabs.activate(buttons[0], buttons, panels);
    },

    activate: function (activeButton, buttons, panels) {
      buttons.forEach(function (button, index) {
        const isActive = button === activeButton;
        const panel = panels[index];

        button.setAttribute("aria-selected", String(isActive));
        button.setAttribute("tabindex", isActive ? "0" : "-1");
        RX.toggleClass(button, RX.config.classes.active, isActive);

        if (panel) {
          panel.hidden = !isActive;
          RX.toggleClass(panel, RX.config.classes.active, isActive);
        }
      });
    },
  };

  /**
   * ============================================================
   * MODAL SYSTEM
   * ============================================================
   */

  RX.modal = {
    active: null,
    removeTrap: null,
    previousFocus: null,

    init: function () {
      RX.delegate(document, "click", RX.config.selectors.modalOpen, function (e, opener) {
        e.preventDefault();

        const target = opener.dataset.rxModalOpen;
        const modal = target ? RX.$(target) : null;

        if (modal) RX.modal.open(modal, opener);
      });

      RX.delegate(document, "click", RX.config.selectors.modalClose, function (e, closeButton) {
        e.preventDefault();

        const modal = closeButton.closest(RX.config.selectors.modal);
        if (modal) RX.modal.close(modal);
      });

      RX.on(document, "keydown", function (e) {
        if (e.key === "Escape" && RX.modal.active) {
          RX.modal.close(RX.modal.active);
        }
      });

      RX.delegate(document, "click", RX.config.selectors.modal, function (e, modal) {
        if (e.target === modal && modal.dataset.rxModalBackdrop !== "false") {
          RX.modal.close(modal);
        }
      });
    },

    open: function (modal, opener) {
      RX.modal.previousFocus = opener || document.activeElement;
      RX.modal.active = modal;

      modal.hidden = false;
      modal.setAttribute("aria-hidden", "false");

      RX.addClass(document.documentElement, RX.config.classes.modalOpen);
      RX.addClass(modal, RX.config.classes.active);

      RX.scroll.lock();
      RX.modal.removeTrap = RX.a11y.trapFocus(modal);

      RX.events.emit("rx:modal:open", { modal: modal });
    },

    close: function (modal) {
      modal = modal || RX.modal.active;
      if (!modal) return;

      modal.hidden = true;
      modal.setAttribute("aria-hidden", "true");

      RX.removeClass(document.documentElement, RX.config.classes.modalOpen);
      RX.removeClass(modal, RX.config.classes.active);

      RX.scroll.unlock();

      if (typeof RX.modal.removeTrap === "function") {
        RX.modal.removeTrap();
      }

      if (RX.modal.previousFocus && typeof RX.modal.previousFocus.focus === "function") {
        RX.modal.previousFocus.focus();
      }

      RX.events.emit("rx:modal:close", { modal: modal });

      RX.modal.active = null;
      RX.modal.removeTrap = null;
      RX.modal.previousFocus = null;
    },
  };

  /**
   * ============================================================
   * SEARCH TOGGLE
   * ============================================================
   */

  RX.search = {
    init: function () {
      const toggle = RX.$(RX.config.selectors.searchToggle);
      const form = RX.$(RX.config.selectors.searchForm);

      if (!toggle || !form) return;

      toggle.setAttribute("aria-expanded", "false");

      RX.on(toggle, "click", function (e) {
        e.preventDefault();

        const isOpen = RX.hasClass(form, RX.config.classes.active);

        RX.toggleClass(form, RX.config.classes.active, !isOpen);
        toggle.setAttribute("aria-expanded", String(!isOpen));

        if (!isOpen) {
          const input = RX.$('input[type="search"], input[name="s"]', form);
          if (input) input.focus();
        }
      });

      RX.on(document, "keydown", function (e) {
        if (e.key === "Escape" && RX.hasClass(form, RX.config.classes.active)) {
          RX.removeClass(form, RX.config.classes.active);
          toggle.setAttribute("aria-expanded", "false");
          toggle.focus();
        }
      });
    },
  };

  /**
   * ============================================================
   * COPY TO CLIPBOARD
   * ============================================================
   */

  RX.copy = {
    init: function () {
      RX.delegate(document, "click", RX.config.selectors.copyButton, function (e, button) {
        e.preventDefault();

        const targetSelector = button.dataset.rxCopy;
        const target = targetSelector ? RX.$(targetSelector) : null;
        const text = target ? target.textContent : button.dataset.rxCopyText;

        if (!text) return;

        RX.copy.text(text).then(function () {
          const oldText = button.textContent;
          const successText = button.dataset.rxCopySuccess || "Copied";

          button.textContent = successText;
          RX.a11y.announce(successText);

          window.setTimeout(function () {
            button.textContent = oldText;
          }, 1500);
        });
      });
    },

    text: function (text) {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        return navigator.clipboard.writeText(text);
      }

      return new Promise(function (resolve, reject) {
        const textarea = RX.create("textarea", {
          value: text,
        });

        textarea.style.position = "fixed";
        textarea.style.left = "-9999px";

        document.body.appendChild(textarea);
        textarea.select();

        try {
          document.execCommand("copy");
          document.body.removeChild(textarea);
          resolve();
        } catch (e) {
          document.body.removeChild(textarea);
          reject(e);
        }
      });
    },
  };

  /**
   * ============================================================
   * EXTERNAL LINK SECURITY
   * ============================================================
   */

  RX.externalLinks = {
    init: function () {
      if (!RX.config.externalLinkSecurity) return;

      const siteHost = window.location.hostname;

      RX.$$(RX.config.selectors.externalLinks).forEach(function (link) {
        try {
          const linkHost = new URL(link.href).hostname;

          if (linkHost !== siteHost) {
            link.setAttribute("rel", RX.externalLinks.mergeRel(link.getAttribute("rel")));
            link.setAttribute("target", link.getAttribute("target") || "_blank");
          }
        } catch (e) {}
      });
    },

    mergeRel: function (rel) {
      const values = new Set((rel || "").split(/\s+/).filter(Boolean));
      values.add("noopener");
      values.add("noreferrer");
      return Array.from(values).join(" ");
    },
  };

  /**
   * ============================================================
   * TABLE OF CONTENTS ACTIVE LINK
   * ============================================================
   */

  RX.toc = {
    init: function () {
      const links = RX.$$(RX.config.selectors.tocLinks);
      if (!links.length || !RX.browser.supportsIntersectionObserver) return;

      const headingMap = new Map();

      links.forEach(function (link) {
        const href = link.getAttribute("href");
        if (!href || !href.startsWith("#")) return;

        const heading = RX.$(decodeURIComponent(href));
        if (heading) headingMap.set(heading, link);
      });

      if (!headingMap.size) return;

      const observer = new IntersectionObserver(
        function (entries) {
          entries.forEach(function (entry) {
            const link = headingMap.get(entry.target);
            if (!link) return;

            if (entry.isIntersecting) {
              links.forEach(function (item) {
                RX.removeClass(item, RX.config.classes.active);
              });

              RX.addClass(link, RX.config.classes.active);
            }
          });
        },
        {
          rootMargin: "-20% 0px -70% 0px",
          threshold: 0,
        }
      );

      headingMap.forEach(function (link, heading) {
        observer.observe(heading);
      });
    },
  };

  /**
   * ============================================================
   * FORM ENHANCEMENT
   * ============================================================
   */

  RX.forms = {
    init: function () {
      RX.$$("form").forEach(function (form) {
        RX.forms.setup(form);
      });
    },

    setup: function (form) {
      RX.$$("input, textarea, select", form).forEach(function (field) {
        RX.forms.updateFieldState(field);

        RX.on(field, "focus", function () {
          RX.addClass(field.closest(".form-field, .rx-form-field") || field, "rx-is-focused");
        });

        RX.on(field, "blur", function () {
          RX.removeClass(field.closest(".form-field, .rx-form-field") || field, "rx-is-focused");
          RX.forms.updateFieldState(field);
        });

        RX.on(field, "input", function () {
          RX.forms.updateFieldState(field);
        });
      });
    },

    updateFieldState: function (field) {
      const wrapper = field.closest(".form-field, .rx-form-field") || field;
      const hasValue = Boolean(field.value && field.value.trim() !== "");

      RX.toggleClass(wrapper, "rx-has-value", hasValue);
      RX.toggleClass(wrapper, "rx-is-required", Boolean(field.required));
      RX.toggleClass(wrapper, "rx-is-invalid", Boolean(field.matches(":invalid") && field.value));
    },
  };

  /**
   * ============================================================
   * IMAGE ASPECT RATIO HELPER
   * ============================================================
   */

  RX.images = {
    init: function () {
      RX.$$("img[width][height]").forEach(function (img) {
        if (img.style.aspectRatio) return;

        const width = RX.toNumber(img.getAttribute("width"), 0);
        const height = RX.toNumber(img.getAttribute("height"), 0);

        if (width > 0 && height > 0) {
          img.style.aspectRatio = width + " / " + height;
        }
      });
    },
  };

  /**
   * ============================================================
   * CSS VARIABLE VIEWPORT HEIGHT FIX
   * ============================================================
   */

  RX.cssVars = {
    init: function () {
      RX.cssVars.update();

      window.addEventListener(
        "resize",
        RX.debounce(function () {
          RX.cssVars.update();
        }, 150)
      );
    },

    update: function () {
      const vh = window.innerHeight * 0.01;
      document.documentElement.style.setProperty("--rx-vh", vh + "px");
      document.documentElement.style.setProperty("--rx-scroll-y", RX.scroll.y() + "px");
    },
  };

  /**
   * ============================================================
   * PRINT HELPER
   * ============================================================
   */

  RX.print = {
    init: function () {
      RX.delegate(document, "click", "[data-rx-print]", function (e) {
        e.preventDefault();
        window.print();
      });
    },
  };

  /**
   * ============================================================
   * THEME MODE HELPER
   * ============================================================
   */

  RX.themeMode = {
    key: "rx-theme-mode",

    init: function () {
      const saved = RX.storage.get(RX.themeMode.key, null);

      if (saved) {
        RX.themeMode.apply(saved);
      }

      RX.delegate(document, "click", "[data-rx-theme-mode]", function (e, button) {
        e.preventDefault();

        const mode = button.dataset.rxThemeMode;

        if (mode === "toggle") {
          RX.themeMode.toggle();
        } else {
          RX.themeMode.apply(mode);
        }
      });
    },

    apply: function (mode) {
      if (!mode) return;

      document.documentElement.dataset.rxThemeMode = mode;
      RX.storage.set(RX.themeMode.key, mode);

      RX.events.emit("rx:theme-mode:change", { mode: mode });
    },

    toggle: function () {
      const current = document.documentElement.dataset.rxThemeMode || "light";
      RX.themeMode.apply(current === "dark" ? "light" : "dark");
    },
  };

  /**
   * ============================================================
   * ERROR GUARD
   * ============================================================
   */

  RX.guard = function (name, callback) {
    try {
      if (typeof callback === "function") callback();
    } catch (e) {
      RX.error("Module failed:", name, e);
      RX.events.emit("rx:error", {
        module: name,
        error: e,
      });
    }
  };

  /**
   * ============================================================
   * CORE INIT
   * ============================================================
   */

  RX.init = function () {
    RX.guard("motion", RX.motion.init);
    RX.guard("cssVars", RX.cssVars.init);
    RX.guard("header", RX.header.init);
    RX.guard("navigation", RX.navigation.init);
    RX.guard("dropdowns", RX.dropdowns.init);
    RX.guard("lazy", RX.lazy.init);
    RX.guard("smoothScroll", RX.smoothScroll.init);
    RX.guard("backToTop", RX.backToTop.init);
    RX.guard("readingProgress", RX.readingProgress.init);
    RX.guard("accordion", RX.accordion.init);
    RX.guard("tabs", RX.tabs.init);
    RX.guard("modal", RX.modal.init);
    RX.guard("search", RX.search.init);
    RX.guard("copy", RX.copy.init);
    RX.guard("externalLinks", RX.externalLinks.init);
    RX.guard("toc", RX.toc.init);
    RX.guard("forms", RX.forms.init);
    RX.guard("images", RX.images.init);
    RX.guard("print", RX.print.init);
    RX.guard("themeMode", RX.themeMode.init);

    RX.addClass(document.documentElement, RX.config.classes.ready);

    RX.events.emit("rx:ready", {
      version: RX.version,
      name: RX.name,
    });

    RX.log("RX Theme core initialized.");
  };

  RX.ready(function () {
    RX.init();
  });

  RX.load(function () {
    RX.addClass(document.documentElement, RX.config.classes.loaded);

    RX.events.emit("rx:loaded", {
      version: RX.version,
      name: RX.name,
    });
  });
})();

Enqueue this file in WordPress

Add this in your theme enqueue file, for example:

inc/core/enqueue.php

<?php
/**
 * RX Theme scripts and styles.
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

function rx_theme_enqueue_assets() {

	$theme_version = wp_get_theme()->get( 'Version' );

	wp_enqueue_script(
		'rx-theme-core',
		get_template_directory_uri() . '/assets/static/static-chunks-js/chunk-001-core.js',
		array(),
		$theme_version,
		true
	);

	wp_script_add_data(
		'rx-theme-core',
		'defer',
		true
	);

	wp_localize_script(
		'rx-theme-core',
		'rxThemeData',
		array(
			'ajaxUrl' => admin_url( 'admin-ajax.php' ),
			'homeUrl' => home_url( '/' ),
			'themeUrl' => get_template_directory_uri(),
			'nonce'   => wp_create_nonce( 'rx_theme_nonce' ),
		)
	);
}
add_action( 'wp_enqueue_scripts', 'rx_theme_enqueue_assets' );

Useful HTML examples

Mobile menu:

<button data-rx-nav-toggle aria-controls="site-navigation">
	Menu
</button>

<nav id="site-navigation" data-rx-nav-menu>
	<ul>
		<li><a href="/">Home</a></li>
		<li><a href="/health/">Health</a></li>
	</ul>
</nav>

Back to top:

<button data-rx-back-to-top aria-label="Back to top"></button>

Reading progress:

<div data-rx-reading-progress role="progressbar" aria-valuemin="0" aria-valuemax="100"></div>

Lazy image:

<img
	data-src="image.jpg"
	alt="Medical article image"
	width="800"
	height="500"
/>

Accordion:

<div data-rx-accordion="single">
	<button data-rx-accordion-toggle aria-expanded="false">
		What is RX Theme?
	</button>
	<div hidden>
		RX Theme is a performance-first WordPress theme.
	</div>
</div>

Modal:

<button data-rx-modal-open="#rx-modal-example">
	Open Modal
</button>

<div id="rx-modal-example" data-rx-modal hidden aria-hidden="true">
	<div>
		<button data-rx-modal-close>Close</button>
		<p>Hello from RX Theme modal.</p>
	</div>
</div>

This file can be your first core JS chunk. Later you can create:

chunk-002-ui.js
chunk-003-navigation.js
chunk-004-search.js
chunk-005-medical-article.js
chunk-006-performance.js
chunk-007-analytics-safe.js
chunk-008-accessibility.js

Leave a Reply

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