UNPKG

dark-reader-aware-theme-toggle-button

Version:

Dark mode toggle button that detects the Dark Reader extension and steps aside

118 lines (115 loc) 3.89 kB
// src/is-dark-reader-active.ts function isDarkReaderActive(doc = document) { const html = doc.documentElement; return html.hasAttribute("data-darkreader-scheme") || html.hasAttribute("data-darkreader-mode") || doc.querySelector('meta[name="darkreader"]') != null || doc.getElementById("dark-reader-style") != null; } // src/watch-dark-reader.ts function watchDarkReader(onChange, doc = document) { const html = doc.documentElement; const check = () => onChange(isDarkReaderActive(doc)); check(); const htmlObserver = new MutationObserver(check); htmlObserver.observe(html, { attributes: true, attributeFilter: ["data-darkreader-scheme", "data-darkreader-mode"] }); let headObserver = null; if (doc.head) { headObserver = new MutationObserver(check); headObserver.observe(doc.head, { childList: true, subtree: true }); } return () => { htmlObserver.disconnect(); headObserver?.disconnect(); }; } // src/theme.ts function getStorage(doc) { return doc.defaultView?.localStorage ?? null; } function prefersDark(doc) { return doc.defaultView?.matchMedia("(prefers-color-scheme: dark)").matches ?? false; } function getTheme(options = {}, doc = document) { const { storageKey = "theme", respectSystemPreference = false } = options; const saved = getStorage(doc)?.getItem(storageKey); if (saved === "dark" || saved === "light") return saved; if (respectSystemPreference && prefersDark(doc)) return "dark"; return "light"; } function setTheme(theme, options = {}, doc = document) { const { storageKey = "theme", attribute = "data-theme", darkValue = "dark" } = options; const html = doc.documentElement; if (theme === "dark") { html.setAttribute(attribute, darkValue); } else { html.removeAttribute(attribute); } getStorage(doc)?.setItem(storageKey, theme); } function getCurrentTheme(options = {}, doc = document) { const { attribute = "data-theme", darkValue = "dark" } = options; return doc.documentElement.getAttribute(attribute) === darkValue ? "dark" : "light"; } function toggleTheme(options = {}, doc = document) { const next = getCurrentTheme(options, doc) === "dark" ? "light" : "dark"; setTheme(next, options, doc); return next; } function initThemeEarly(options = {}, doc = document) { const theme = getTheme(options, doc); setTheme(theme, options, doc); return theme; } // src/mount-theme-toggle-button.ts function resolveElement(ref, doc) { return typeof ref === "string" ? doc.querySelector(ref) : ref; } function mountThemeToggleButton(options, doc = document) { const button = resolveElement(options.button, doc); if (!button) { throw new Error("mountThemeToggleButton: button element not found"); } const labelEl = options.label ? resolveElement(options.label, doc) : null; const labels = options.labels ?? { light: "Dark", dark: "Light" }; const hideWhenDR = options.hideWhenDarkReaderActive !== false; const updateLabel = (theme2) => { const text = theme2 === "dark" ? labels.dark : labels.light; if (labelEl) { labelEl.textContent = text; } else { button.textContent = text; } }; const theme = getTheme(options, doc); setTheme(theme, options, doc); updateLabel(theme); options.onThemeChange?.(theme); const onClick = () => { const next = toggleTheme(options, doc); updateLabel(next); options.onThemeChange?.(next); }; button.addEventListener("click", onClick); let unwatchDR; if (hideWhenDR) { unwatchDR = watchDarkReader((active) => { button.style.visibility = active ? "hidden" : ""; }, doc); } return () => { button.removeEventListener("click", onClick); unwatchDR?.(); }; } export { getCurrentTheme, getTheme, initThemeEarly, isDarkReaderActive, mountThemeToggleButton, setTheme, toggleTheme, watchDarkReader }; //# sourceMappingURL=index.js.map