UNPKG

svelte-os-themes

Version:

[![Live Demo](https://img.shields.io/badge/Live%20Demo-Open-0ea5e9)](https://svelte-os-themes.vercel.app)

139 lines (133 loc) 4.35 kB
import { parseTheme } from './parseTheme.js'; const defaultOptions = { fallback: 'system', attribute: 'class', storageKey: 'theme', system: false, colorScheme: true, }; export function createTheme(options) { const options_ = $derived.by(() => { const userOptions = typeof options === 'function' ? options() : options; return { ...defaultOptions, ...userOptions, }; }); let theme = $derived(options_.fallback); $effect.pre(() => { theme = parseTheme(window.localStorage.getItem(options_.storageKey), options_.fallback); }); $effect(() => { const html = document.documentElement; const head = document.head; const style = document.createElement('style'); if (options_.nonce) { style.setAttribute('nonce', options_.nonce); } style.appendChild(document.createTextNode(`*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}`)); head.appendChild(style); const originalTheme = theme; const resolvedTheme = originalTheme === 'system' ? window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' : originalTheme; if (options_.attribute === 'class') { const removeClass = resolvedTheme === 'dark' ? 'light' : 'dark'; html.classList.remove(removeClass); html.classList.add(resolvedTheme); } else { html.setAttribute(options_.attribute, resolvedTheme); } if (options_.colorScheme) { html.style.colorScheme = resolvedTheme; } window.localStorage.setItem(options_.storageKey, originalTheme); setTimeout(() => { head.removeChild(style); }, 1); }); $effect(() => { if (!options_.system) return function noop() { }; const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); const handleChange = (e) => { theme = e.matches ? 'dark' : 'light'; }; mediaQuery.addEventListener('change', handleChange); return () => { mediaQuery.removeEventListener('change', handleChange); }; }); $effect(() => { const handleChange = (e) => { if (e.key === options_.storageKey) { theme = parseTheme(e.newValue, options_.fallback); } }; window.addEventListener('storage', handleChange); return () => { window.removeEventListener('storage', handleChange); }; }); return { get current() { return theme; }, set current(value) { if (value) { theme = value; } else { theme = options_.fallback; } }, }; } createTheme.script = (props) => { const options_ = $derived.by(() => { const userOptions = typeof props === 'function' ? props() : props; return { ...defaultOptions, ...userOptions, }; }); const value = $derived(` <script ${options_.nonce ? `nonce="${options_.nonce}"` : ''}>(function(k, a, f, c) { const h = document.documentElement; const q = window.matchMedia('(prefers-color-scheme: dark)'); const s = window.localStorage.getItem(k)?.toLowerCase().trim(); const l = [ 'dark', 'light', 'system' ]; const v = l.includes(s) ? s : f; const t = v === 'system' ? q.matches ? 'dark' : 'light' : v; if (a === 'class') { h.classList.remove(t === 'dark' ? 'light' : 'dark'); h.classList.add(t); } else { h.setAttribute(a, t); } window.localStorage.setItem(k, v); if (c) h.style.colorScheme = t; })( '${options_.storageKey}', '${options_.attribute}', '${options_.fallback}', ${options_.colorScheme} ); </script> ` .replace(/\n+/g, '') .replace(/\s+/g, ' ') .trim()); return { get current() { return value; }, }; };