From 97ee5c79e14bb9f20ad670b6d5148bbaf3c4e039 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Olle=20M=C3=A5nsson?=
<31876997+ollema@users.noreply.github.com>
Date: Thu, 26 Oct 2023 11:32:49 +0200
Subject: [PATCH] refactor mode.ts to use a single persistent store
---
README.md | 2 +-
src/lib/mode-watcher.svelte | 8 +-
src/lib/mode.ts | 141 +++++++++++-------------------------
src/tests/mode.spec.ts | 10 ++-
4 files changed, 55 insertions(+), 106 deletions(-)
diff --git a/README.md b/README.md
index 41c0684..1bb7469 100644
--- a/README.md
+++ b/README.md
@@ -64,7 +64,7 @@ A function that resets the mode to system preferences.
### mode
-A readable store that contains the current mode. It can be `light` or `dark`.
+A readable store that contains the current mode. It can be `light`, `dark` or `system`.
```svelte
@@ -12,5 +12,5 @@
- {@html `<\u{73}cript nonce="%sveltekit.nonce%">(${setInitialClassState.toString()})();`}
+ {@html `<\u{73}cript nonce="%sveltekit.nonce%">(${setInitialMode.toString()})();`}
diff --git a/src/lib/mode.ts b/src/lib/mode.ts
index cc87bb5..1a34101 100644
--- a/src/lib/mode.ts
+++ b/src/lib/mode.ts
@@ -1,145 +1,86 @@
// Modified version of the light switch by: https://skeleton.dev
-import { derived, get } from 'svelte/store';
import { persisted } from 'svelte-persisted-store';
+import { get } from 'svelte/store';
-/**
- * Stores
- * ----
- * TRUE: Light mode | FALSE: Dark mode
- */
-
-const modeOsPrefers = persisted('modeOsPrefers', false);
-const modeUserPrefers = persisted('modeUserPrefers', undefined);
-const modeCurrent = persisted('modeCurrent', false);
-
-/** Derived store with either `"light"` or `"dark"` depending on the current mode */
-export const mode = derived(modeCurrent, ($modeCurrent) => ($modeCurrent ? 'light' : 'dark'));
+/** Persistent store with either `"light"`, `"dark"` or `"system"` depending on the current mode */
+export const mode = persisted<'light' | 'dark' | 'system'>('mode', 'system');
/**
* Getters
*/
-/** Get the OS preference */
-export function getModeOsPrefers(): boolean {
- const prefersLightMode = window.matchMedia('(prefers-color-scheme: light)').matches;
- modeOsPrefers.set(prefersLightMode);
- return prefersLightMode;
+/** Get the current mode */
+function getCurrentMode(): 'light' | 'dark' | 'system' {
+ return get(mode);
}
-/** Get the User preference */
-function getModeUserPrefers(): boolean | undefined {
- return get(modeUserPrefers);
-}
-
-/** Get the automatic preference */
-export function getModeAutoPrefers(): boolean {
- const os = getModeOsPrefers();
- const user = getModeUserPrefers();
- return user !== undefined ? user : os;
+/** Get the operating system preference */
+export function getSystemMode(): 'light' | 'dark' {
+ return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
}
/**
* Setters
*/
-/** Set the user preference */
-function setModeUserPrefers(value: boolean | undefined): void {
- modeUserPrefers.set(value);
-}
-
-/** Set the current mode */
-export function setModeCurrent(value: boolean): void {
+/** Set class & color scheme for the `html` element */
+function setHtmlClassColorScheme(nextMode: 'light' | 'dark' | 'system'): void {
const htmlEl = document.documentElement;
- const classDark = 'dark';
- if (value === true) {
- htmlEl.classList.remove(classDark);
+ const systemMode = getSystemMode();
+
+ if (nextMode === 'light' || (nextMode === 'system' && systemMode === 'light')) {
+ htmlEl.classList.remove('dark');
htmlEl.style.colorScheme = 'light';
- } else {
- htmlEl.classList.add(classDark);
- htmlEl.style.colorScheme = 'dark';
}
- modeCurrent.set(value);
-}
-
-/**
- * Lightswitch Utility
- */
-/** Set the visible light/dark mode on page load */
-export function setInitialClassState() {
- const htmlEl = document.documentElement;
-
- const condLocalStorageUserPrefs = localStorage.getItem('modeUserPrefers') === 'false';
- const condLocalStorageUserPrefsExist = !('modeUserPrefers' in localStorage);
- const condMatchMedia = window.matchMedia('(prefers-color-scheme: dark)').matches;
-
- if (condLocalStorageUserPrefs || (condLocalStorageUserPrefsExist && condMatchMedia)) {
+ if (nextMode === 'dark' || (nextMode === 'system' && systemMode === 'dark')) {
htmlEl.classList.add('dark');
htmlEl.style.colorScheme = 'dark';
- } else {
- htmlEl.classList.remove('dark');
- htmlEl.style.colorScheme = 'light';
}
}
-/**
- * Auto Mode Watcher
- */
+/** Set the current mode */
+export function setCurrentMode(nextMode: 'light' | 'dark' | 'system'): void {
+ mode.set(nextMode);
+ setHtmlClassColorScheme(nextMode);
+}
-/** Automatically set the visible light/dark updates on change */
-export function autoModeWatcher(): void {
- const mql = window.matchMedia('(prefers-color-scheme: dark)');
- function setMode(value: boolean) {
- const htmlEl = document.documentElement;
- const classDark = 'dark';
- if (value === true) {
- htmlEl.classList.remove(classDark);
- htmlEl.style.colorScheme = 'light';
- } else {
- htmlEl.classList.add(classDark);
- htmlEl.style.colorScheme = 'dark';
- }
- }
- setMode(mql.matches);
- mql.onchange = () => {
- setMode(mql.matches);
- };
+/** Set the visible light/dark mode on page load */
+export function setInitialMode(): void {
+ const currentMode = getCurrentMode();
+ setCurrentMode(currentMode);
}
/**
* Toggle between light and dark mode
*/
export function toggleMode(): void {
- modeCurrent.update((curr) => {
- const next = !curr;
- setModeUserPrefers(next);
- setModeCurrent(next);
- return next;
- });
+ const currentMode = getCurrentMode();
+ const systemMode = getSystemMode();
+
+ const nextMode =
+ currentMode === 'system'
+ ? systemMode === 'light'
+ ? 'dark'
+ : 'light'
+ : currentMode === 'light'
+ ? 'dark'
+ : 'light';
+
+ setCurrentMode(nextMode);
}
/**
* Set the mode to light or dark
*/
export function setMode(mode: 'light' | 'dark'): void {
- modeCurrent.update((curr) => {
- const next = mode === 'light';
- if (curr === next) return curr;
- setModeUserPrefers(next);
- setModeCurrent(next);
- return next;
- });
+ setCurrentMode(mode);
}
/**
- * Reset the mode to OS preference
+ * Reset the mode to system preference
*/
export function resetMode(): void {
- modeCurrent.update(() => {
- setModeUserPrefers(undefined);
- const next = getModeOsPrefers();
- setModeCurrent(next);
- return next;
- });
+ setCurrentMode('system');
}
diff --git a/src/tests/mode.spec.ts b/src/tests/mode.spec.ts
index 443a47a..b0d4709 100644
--- a/src/tests/mode.spec.ts
+++ b/src/tests/mode.spec.ts
@@ -85,6 +85,7 @@ it('resets the mode to OS preferences (dark?)', async () => {
const rootEl = container.parentElement;
const light = getByTestId('light');
const reset = getByTestId('reset');
+ const toggle = getByTestId('toggle');
const mode = getByTestId('mode');
const classes = getClasses(rootEl);
const colorScheme = getColorScheme(rootEl);
@@ -104,7 +105,14 @@ it('resets the mode to OS preferences (dark?)', async () => {
const colorScheme3 = getColorScheme(rootEl);
expect(classes3).toContain('dark');
expect(colorScheme3).toBe('dark');
- expect(mode.textContent).toBe('dark');
+ expect(mode.textContent).toBe('system');
+
+ await userEvent.click(toggle);
+ const classes4 = getClasses(rootEl);
+ const colorScheme4 = getColorScheme(rootEl);
+ expect(classes4).not.toContain('dark');
+ expect(colorScheme4).toBe('light');
+ expect(mode.textContent).toBe('light');
});
function getClasses(element: HTMLElement | null): string[] {