mode-watcher
Version:
SSR-friendly light and dark mode for SvelteKit
101 lines (100 loc) • 3.53 kB
JavaScript
import { box } from "svelte-toolbelt";
import { isBrowser, sanitizeClassNames } from "./utils.js";
import { withoutTransition } from "./without-transition.js";
import { systemPrefersMode, userPrefersMode } from "./mode-states.svelte.js";
import { customTheme } from "./theme-state.svelte.js";
/**
* Theme colors for light and dark modes.
*/
export const themeColors = box(undefined);
/**
* Whether to disable transitions when changing the mode.
*/
export const disableTransitions = box(true);
/**
* The classnames to add to the root `html` element when the mode is dark.
*/
export const darkClassNames = box([]);
/**
* The classnames to add to the root `html` element when the mode is light.
*/
export const lightClassNames = box([]);
function createDerivedMode() {
const current = $derived.by(() => {
if (!isBrowser)
return undefined;
const derivedMode = userPrefersMode.current === "system"
? systemPrefersMode.current
: userPrefersMode.current;
const sanitizedDarkClassNames = sanitizeClassNames(darkClassNames.current);
const sanitizedLightClassNames = sanitizeClassNames(lightClassNames.current);
function update() {
const htmlEl = document.documentElement;
const themeColorEl = document.querySelector('meta[name="theme-color"]');
if (derivedMode === "light") {
if (sanitizedDarkClassNames.length)
htmlEl.classList.remove(...sanitizedDarkClassNames);
if (sanitizedLightClassNames.length)
htmlEl.classList.add(...sanitizedLightClassNames);
htmlEl.style.colorScheme = "light";
if (themeColorEl && themeColors.current) {
themeColorEl.setAttribute("content", themeColors.current.light);
}
}
else {
if (sanitizedLightClassNames.length)
htmlEl.classList.remove(...sanitizedLightClassNames);
if (sanitizedDarkClassNames.length)
htmlEl.classList.add(...sanitizedDarkClassNames);
htmlEl.style.colorScheme = "dark";
if (themeColorEl && themeColors.current) {
themeColorEl.setAttribute("content", themeColors.current.dark);
}
}
}
if (disableTransitions.current) {
withoutTransition(update);
}
else {
update();
}
return derivedMode;
});
return {
get current() {
return current;
},
};
}
function createDerivedTheme() {
const current = $derived.by(() => {
customTheme.current;
if (!isBrowser)
return undefined;
function update() {
const htmlEl = document.documentElement;
htmlEl.setAttribute("data-theme", customTheme.current);
}
if (disableTransitions.current) {
withoutTransition(update);
}
else {
update();
}
return customTheme.current;
});
return {
get current() {
return current;
},
};
}
/**
* Derived store that represents the current mode (`"dark"`, `"light"` or `undefined`)
*/
export const derivedMode = createDerivedMode();
/**
* Derived store that represents the current custom theme
*/
export const derivedTheme = createDerivedTheme();
export { derivedMode as mode, derivedTheme as theme };