diff --git a/src/lib/utilities/dark-mode/dark-mode.svelte b/src/lib/utilities/dark-mode/dark-mode.svelte index 5b3ce499e3..c851c600a6 100644 --- a/src/lib/utilities/dark-mode/dark-mode.svelte +++ b/src/lib/utilities/dark-mode/dark-mode.svelte @@ -1,10 +1,14 @@ - + {@render children?.()} diff --git a/src/lib/utilities/dark-mode/dark-mode.test.ts b/src/lib/utilities/dark-mode/dark-mode.test.ts index 951baf8a52..48b81a449f 100644 --- a/src/lib/utilities/dark-mode/dark-mode.test.ts +++ b/src/lib/utilities/dark-mode/dark-mode.test.ts @@ -65,22 +65,73 @@ describe('dark-mode utilities', () => { }); describe('darkMode', () => { - it('should set data-theme to "dark" when dark mode is enabled', async () => { + it('should set data-theme to "dark" when dark mode is enabled', () => { const node = document.createElement('div'); useDarkModePreference.set(true); darkMode(node); - await new Promise((resolve) => setTimeout(resolve, 0)); expect(node.dataset.theme).toBe('dark'); }); - it('should set data-theme to "light" when dark mode is disabled', async () => { + it('should set data-theme to "light" when dark mode is disabled', () => { const node = document.createElement('div'); useDarkModePreference.set(false); darkMode(node); - await new Promise((resolve) => setTimeout(resolve, 0)); + + expect(node.dataset.theme).toBe('light'); + }); + + it('should force "light" via overrideTheme even when dark mode is enabled', () => { + const node = document.createElement('div'); + useDarkModePreference.set(true); + + darkMode(node, 'light'); + + expect(node.dataset.theme).toBe('light'); + }); + + it('should force "dark" via overrideTheme even when dark mode is disabled', () => { + const node = document.createElement('div'); + useDarkModePreference.set(false); + + darkMode(node, 'dark'); + + expect(node.dataset.theme).toBe('dark'); + }); + + it('should re-apply the theme when the override changes via update', () => { + const node = document.createElement('div'); + useDarkModePreference.set(true); + + const action = darkMode(node, 'light'); + expect(node.dataset.theme).toBe('light'); + + action.update('dark'); + expect(node.dataset.theme).toBe('dark'); + }); + + it('should fall back to the store theme when the override is cleared', () => { + const node = document.createElement('div'); + useDarkModePreference.set(true); + + const action = darkMode(node, 'light'); + expect(node.dataset.theme).toBe('light'); + + action.update(undefined); + expect(node.dataset.theme).toBe('dark'); + }); + + it('should stop updating data-theme after destroy', () => { + const node = document.createElement('div'); + useDarkModePreference.set(false); + + const action = darkMode(node); + expect(node.dataset.theme).toBe('light'); + + action.destroy(); + useDarkModePreference.set(true); expect(node.dataset.theme).toBe('light'); }); diff --git a/src/lib/utilities/dark-mode/dark-mode.ts b/src/lib/utilities/dark-mode/dark-mode.ts index 710335fcc8..4b5637276d 100644 --- a/src/lib/utilities/dark-mode/dark-mode.ts +++ b/src/lib/utilities/dark-mode/dark-mode.ts @@ -3,6 +3,7 @@ import { derived } from 'svelte/store'; import { persistStore } from '$lib/stores/persist-store'; export type DarkModePreference = boolean | 'system'; +export type ThemeOverride = 'light' | 'dark'; export const useDarkModePreference = persistStore( 'dark mode', @@ -25,12 +26,30 @@ export const useDarkMode = derived(useDarkModePreference, prefersDarkMode); export const getNextDarkModePreference = (value: DarkModePreference) => value == 'system' ? true : value == true ? false : 'system'; -export const darkMode = (node: HTMLElement) => { - useDarkMode.subscribe((value) => { - if (value) { - node.dataset.theme = 'dark'; - } else { - node.dataset.theme = 'light'; - } +const applyTheme = ( + node: HTMLElement, + prefersDark: boolean, + overrideTheme?: ThemeOverride, +) => { + node.dataset.theme = overrideTheme ?? (prefersDark ? 'dark' : 'light'); +}; + +export const darkMode = (node: HTMLElement, overrideTheme?: ThemeOverride) => { + let override = overrideTheme; + let prefersDark = false; + + const unsubscribe = useDarkMode.subscribe((value) => { + prefersDark = value; + applyTheme(node, prefersDark, override); }); + + return { + update(newOverride?: ThemeOverride) { + override = newOverride; + applyTheme(node, prefersDark, override); + }, + destroy() { + unsubscribe(); + }, + }; }; diff --git a/src/lib/utilities/dark-mode/index.ts b/src/lib/utilities/dark-mode/index.ts index 49e5192a96..2e48089ad4 100644 --- a/src/lib/utilities/dark-mode/index.ts +++ b/src/lib/utilities/dark-mode/index.ts @@ -7,4 +7,4 @@ export { getNextDarkModePreference, } from './dark-mode'; -export type { DarkModePreference } from './dark-mode'; +export type { DarkModePreference, ThemeOverride } from './dark-mode'; diff --git a/src/routes/(login)/+layout.svelte b/src/routes/(login)/+layout.svelte index 2b4a90763d..2b33c58c69 100644 --- a/src/routes/(login)/+layout.svelte +++ b/src/routes/(login)/+layout.svelte @@ -1,6 +1,8 @@ + {@render children()}