GeoLite/assets/js/theme-toggle.js

130 lines
3.8 KiB
JavaScript

/**
* GeoLite Theme Toggle
* Provides light/dark theme switching with persistence
*/
(function () {
const storageKey = 'geolite-theme';
const toggleSelector = '[data-theme-toggle]';
const toggles = new Set();
let initialized = false;
function getMediaMatcher() {
if (typeof window === 'undefined' || !window.matchMedia) return null;
return window.matchMedia('(prefers-color-scheme: dark)');
}
function updateToggleAppearance(button, isDark) {
if (!button) return;
const icon = button.querySelector('.theme-toggle-icon');
const label = button.querySelector('.theme-toggle-label');
if (icon) {
icon.classList.toggle('bi-moon-stars', !isDark);
icon.classList.toggle('bi-brightness-high', isDark);
}
if (label) {
label.textContent = isDark ? 'Light' : 'Dark';
}
button.setAttribute('title', isDark ? 'Switch to light mode' : 'Switch to dark mode');
}
function applyTheme(theme, persist = true) {
if (!document.body) {
// Defer until body exists
document.addEventListener('DOMContentLoaded', () => applyTheme(theme, persist), { once: true });
return;
}
const isDark = theme === 'dark';
document.body.classList.toggle('dark-mode', isDark);
document.documentElement.setAttribute('data-theme', theme);
if (persist) {
try {
localStorage.setItem(storageKey, theme);
} catch (error) {
console.warn('Theme preference could not be saved:', error);
}
}
toggles.forEach((btn) => updateToggleAppearance(btn, isDark));
}
function currentTheme() {
return document.body && document.body.classList.contains('dark-mode') ? 'dark' : 'light';
}
function handleToggleClick(event) {
event.preventDefault();
const nextTheme = currentTheme() === 'dark' ? 'light' : 'dark';
applyTheme(nextTheme);
}
function registerToggle(button) {
if (!button || toggles.has(button)) return;
toggles.add(button);
button.addEventListener('click', handleToggleClick);
updateToggleAppearance(button, currentTheme() === 'dark');
}
function initToggles() {
document.querySelectorAll(toggleSelector).forEach(registerToggle);
}
function initTheme() {
if (initialized) return;
initialized = true;
let savedTheme = null;
try {
savedTheme = localStorage.getItem(storageKey);
} catch (error) {
console.warn('Unable to read saved theme preference:', error);
}
if (!savedTheme) {
const media = getMediaMatcher();
savedTheme = media && media.matches ? 'dark' : 'light';
}
applyTheme(savedTheme, Boolean(savedTheme));
const media = getMediaMatcher();
if (media) {
const handleChange = (event) => {
try {
if (localStorage.getItem(storageKey)) return;
} catch (error) {
// continue
}
applyTheme(event.matches ? 'dark' : 'light', false);
};
if (typeof media.addEventListener === 'function') {
media.addEventListener('change', handleChange);
} else if (typeof media.addListener === 'function') {
media.addListener(handleChange);
}
}
initToggles();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTheme);
} else {
initTheme();
}
window.GeoliteTheme = {
applyTheme,
registerToggle,
refresh: initToggles,
};
})();