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