83 lines
3.2 KiB
JavaScript
83 lines
3.2 KiB
JavaScript
/**
|
||
* XShop theme bootstrap – vanilla, no jQuery.
|
||
* Feature modules are dynamically imported when DOM markers are present.
|
||
*/
|
||
(function () {
|
||
'use strict';
|
||
|
||
// Dark mode toggle via [data-theme] if enabled.
|
||
function initThemeMode() {
|
||
var data = window.xshopData;
|
||
if (!data) return;
|
||
// Theme mode is server-rendered via data-theme on <html>; this handles toggle clicks.
|
||
document.addEventListener('click', function (e) {
|
||
var btn = e.target.closest('[data-xshop-theme-toggle]');
|
||
if (!btn) return;
|
||
var html = document.documentElement;
|
||
var next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
|
||
html.setAttribute('data-theme', next);
|
||
try { localStorage.setItem('xshop_theme', next); } catch (_) {}
|
||
// Cookie for server-side rendering on next request.
|
||
document.cookie = 'xshop_theme=' + next + '; path=/; max-age=2592000; SameSite=Lax';
|
||
});
|
||
}
|
||
|
||
// Minimal focus trap helper for drawers/modals (used by later modules).
|
||
window.xshopTrapFocus = function (container) {
|
||
var sel = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
||
var nodes = container.querySelectorAll(sel);
|
||
if (!nodes.length) return function () {};
|
||
var first = nodes[0], last = nodes[nodes.length - 1];
|
||
function onKey(e) {
|
||
if (e.key !== 'Tab') 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', onKey);
|
||
return function () { container.removeEventListener('keydown', onKey); };
|
||
};
|
||
|
||
function initMobileNav() {
|
||
var toggle = document.querySelector('[data-xshop-mobile-toggle]');
|
||
var nav = document.getElementById('xshop-mobile-nav');
|
||
if (!toggle || !nav) return;
|
||
var releaseTrap = null;
|
||
function setOpen(open) {
|
||
toggle.setAttribute('aria-expanded', open ? 'true' : 'false');
|
||
nav.classList.toggle('is-open', open);
|
||
nav.hidden = !open;
|
||
if (open) {
|
||
// Move focus into nav.
|
||
var firstLink = nav.querySelector('a, button');
|
||
if (firstLink) firstLink.focus();
|
||
if (window.xshopTrapFocus) releaseTrap = window.xshopTrapFocus(nav);
|
||
document.addEventListener('keydown', onDocKey);
|
||
} else {
|
||
if (releaseTrap) { releaseTrap(); releaseTrap = null; }
|
||
document.removeEventListener('keydown', onDocKey);
|
||
toggle.focus();
|
||
}
|
||
}
|
||
function onDocKey(e) {
|
||
if (e.key === 'Escape') setOpen(false);
|
||
}
|
||
toggle.addEventListener('click', function () {
|
||
var open = toggle.getAttribute('aria-expanded') === 'true';
|
||
setOpen(!open);
|
||
});
|
||
// Close when clicking outside.
|
||
document.addEventListener('click', function (e) {
|
||
if (nav.hidden) return;
|
||
if (nav.contains(e.target) || toggle.contains(e.target)) return;
|
||
setOpen(false);
|
||
});
|
||
}
|
||
|
||
initThemeMode();
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', initMobileNav);
|
||
} else {
|
||
initMobileNav();
|
||
}
|
||
})();
|