chore: complete M1.1 runtime QA and release packaging
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user