mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-09 12:57:48 +00:00
update
This commit is contained in:
0
global.d.ts → src/global.d.ts
vendored
0
global.d.ts → src/global.d.ts
vendored
@@ -1,15 +1,14 @@
|
|||||||
import type { Theme } from '.';
|
|
||||||
import { MEDIA } from './constants';
|
import { MEDIA } from './constants';
|
||||||
|
|
||||||
export const getTheme = (key: string, fallback?: string): Theme | undefined => {
|
export const getTheme = (key: string, fallback?: string): string | undefined => {
|
||||||
if (typeof window === 'undefined') return undefined;
|
if (typeof window === 'undefined') return undefined;
|
||||||
let theme: Theme | undefined = undefined;
|
let theme: string | undefined = undefined;
|
||||||
try {
|
try {
|
||||||
theme = localStorage.getItem(key) as Theme || undefined;
|
theme = localStorage.getItem(key) as string || undefined;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Unsupported
|
// Unsupported
|
||||||
}
|
}
|
||||||
return theme || fallback as Theme;
|
return theme || fallback as string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const disableAnimation = () => {
|
export const disableAnimation = () => {
|
||||||
@@ -33,11 +32,11 @@ export const disableAnimation = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getSystemTheme = (e?: MediaQueryList): string => {
|
export const getSystemTheme = (e?: MediaQueryList): string => {
|
||||||
if (!e) {
|
if (!e && typeof window !== 'undefined') {
|
||||||
e = window.matchMedia(MEDIA);
|
e = window.matchMedia(MEDIA);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDark = e.matches;
|
const isDark = e?.matches;
|
||||||
const systemTheme = isDark ? 'dark' : 'light';
|
const systemTheme = isDark ? 'dark' : 'light';
|
||||||
return systemTheme;
|
return systemTheme;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { MEDIA } from './constants';
|
import { MEDIA } from './constants';
|
||||||
import { type Theme } from '.';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
forcedTheme?: string;
|
forcedTheme?: string;
|
||||||
storageKey?: string;
|
storageKey?: string;
|
||||||
attribute?: string;
|
attribute?: string;
|
||||||
enableSystem?: boolean;
|
enableSystem?: boolean;
|
||||||
defaultTheme?: Theme;
|
defaultTheme?: string;
|
||||||
value?: { [themeName: string]: string };
|
value?: { [themeName: string]: string };
|
||||||
attrs: string[];
|
attrs: string[];
|
||||||
}
|
}
|
||||||
@@ -22,46 +21,64 @@
|
|||||||
attrs
|
attrs
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
// These are minified via Terser and then updated by hand, don't recommend
|
const getThemeUpdate = (name: string, literal?: boolean) => {
|
||||||
|
const themeName = value?.[name] || name;
|
||||||
const updateDOM = (name: string, literal?: boolean) => {
|
const val = literal ? themeName : `'${themeName}'`;
|
||||||
name = value?.[name] || name;
|
|
||||||
const val = literal ? name : `'${name}'`;
|
|
||||||
|
|
||||||
|
// Set both attribute and color-scheme
|
||||||
if (attribute === 'class') {
|
if (attribute === 'class') {
|
||||||
return `d.add(${val})${`;document.documentElement.style.setProperty('color-scheme', ${val})`}`;
|
return `d.add(${val});document.documentElement.style.setProperty('color-scheme', ${val})`;
|
||||||
}
|
}
|
||||||
|
return `d.setAttribute('${attribute}', ${val});document.documentElement.style.setProperty('color-scheme', ${val})`;
|
||||||
return `d.setAttribute('${attribute}', ${val})${`;document.documentElement.style.setProperty('color-scheme', ${val})`}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let defaultSystem = $derived(defaultTheme === 'system');
|
let defaultSystem = $derived(defaultTheme === 'system');
|
||||||
// Code-golfing the amount of characters in the script
|
let classListPrep = $derived(
|
||||||
let optimization = $derived(
|
|
||||||
attribute === 'class'
|
attribute === 'class'
|
||||||
? `var d=document.documentElement.classList;${`d.remove(${attrs
|
? `var d=document.documentElement.classList;d.remove(${attrs
|
||||||
.map((t: string) => `'${t}'`)
|
.map((t: string) => `'${t}'`)
|
||||||
.join(',')})`};`
|
.join(',')});`
|
||||||
: `var d=document.documentElement;`
|
: `var d=document.documentElement;`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Encapsulate script tag into string to not mess with the compiler
|
// Script implementation varies based on configuration
|
||||||
let themeScript = $derived(
|
let themeScript = $derived(
|
||||||
`<${'script'}>${
|
`<${'script'}>
|
||||||
forcedTheme
|
(function() {
|
||||||
? `!function(){${optimization}${updateDOM(forcedTheme)}}()`
|
${classListPrep}
|
||||||
: enableSystem
|
${
|
||||||
? `!function(){try {${optimization}var e=localStorage.getItem('${storageKey}');${
|
forcedTheme
|
||||||
!defaultSystem ? updateDOM(defaultTheme) + ';' : ''
|
? getThemeUpdate(forcedTheme)
|
||||||
}if("system"===e||(!e&&${defaultSystem})){var t="${MEDIA}",m=window.matchMedia(t);if(m.media!==t||m.matches){${updateDOM(
|
: enableSystem
|
||||||
'dark'
|
? `try {
|
||||||
)}}else{${updateDOM('light')}}}else if(e){ ${
|
var storedTheme = localStorage.getItem('${storageKey}');
|
||||||
value ? `var x=${JSON.stringify(value)};` : ''
|
${!defaultSystem ? `${getThemeUpdate(defaultTheme)};` : ''}
|
||||||
}${updateDOM(value ? 'x[e]' : 'e', true)}}}catch(e){}}()`
|
|
||||||
: `!function(){try{${optimization}var e=localStorage.getItem("${storageKey}");if(e){${
|
if ("system" === storedTheme || (!storedTheme && ${defaultSystem})) {
|
||||||
value ? `var x=${JSON.stringify(value)};` : ''
|
var mediaQuery = "${MEDIA}";
|
||||||
}${updateDOM(value ? 'x[e]' : 'e', true)}}else{${updateDOM(defaultTheme)};}}catch(t){}}();`
|
var mql = window.matchMedia(mediaQuery);
|
||||||
}</${'script'}>`
|
if (mql.media !== mediaQuery || mql.matches) {
|
||||||
|
${getThemeUpdate('dark')}
|
||||||
|
} else {
|
||||||
|
${getThemeUpdate('light')}
|
||||||
|
}
|
||||||
|
} else if (storedTheme) {
|
||||||
|
${value ? `var themeMapping = ${JSON.stringify(value)};` : ''}
|
||||||
|
${getThemeUpdate(value ? 'themeMapping[storedTheme]' : 'storedTheme', true)}
|
||||||
|
}
|
||||||
|
} catch(e) { console.error("Theme initialization error:", e); }`
|
||||||
|
: `try {
|
||||||
|
var storedTheme = localStorage.getItem("${storageKey}");
|
||||||
|
if (storedTheme) {
|
||||||
|
${value ? `var themeMapping = ${JSON.stringify(value)};` : ''}
|
||||||
|
${getThemeUpdate(value ? 'themeMapping[storedTheme]' : 'storedTheme', true)}
|
||||||
|
} else {
|
||||||
|
${getThemeUpdate(defaultTheme)};
|
||||||
|
}
|
||||||
|
} catch(e) { console.error("Theme initialization error:", e); }`
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</${'script'}>`
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { colorSchemes, MEDIA } from './constants';
|
import { colorSchemes, MEDIA } from './constants';
|
||||||
import { disableAnimation, getSystemTheme, getTheme } from './helpers';
|
import { disableAnimation, getSystemTheme, getTheme } from './helpers';
|
||||||
import { themeStore, setTheme } from './index';
|
import { themeStore, setTheme, setResolvedTheme, setSystemTheme, setThemes } from './index';
|
||||||
|
|
||||||
import ThemeScript from './theme-script.svelte';
|
import ThemeScript from './theme-script.svelte';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
|
|
||||||
@@ -32,97 +31,108 @@
|
|||||||
value = undefined
|
value = undefined
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
// Initialize theme state
|
||||||
const initialTheme = getTheme(storageKey, defaultTheme);
|
const initialTheme = getTheme(storageKey, defaultTheme);
|
||||||
|
const systemTheme = enableSystem ? getSystemTheme() : undefined;
|
||||||
|
|
||||||
themeStore.set({
|
themeStore.set({
|
||||||
theme: initialTheme,
|
theme: initialTheme,
|
||||||
forcedTheme,
|
forcedTheme,
|
||||||
resolvedTheme: initialTheme === 'system' ? getTheme(storageKey) : initialTheme,
|
resolvedTheme: initialTheme === 'system' ? systemTheme : initialTheme,
|
||||||
themes: enableSystem ? [...themes, 'system'] : themes,
|
themes: enableSystem ? [...themes, 'system'] : themes,
|
||||||
systemTheme: (enableSystem ? getTheme(storageKey) : undefined) as
|
systemTheme
|
||||||
| 'light'
|
|
||||||
| 'dark'
|
|
||||||
| undefined
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let theme = $derived($themeStore.theme);
|
let theme = $derived($themeStore.theme);
|
||||||
let resolvedTheme = $derived($themeStore.resolvedTheme);
|
let resolvedTheme = $derived($themeStore.resolvedTheme);
|
||||||
|
|
||||||
const attrs = !value ? themes : Object.values(value);
|
const attrs = !value ? themes : Object.values(value);
|
||||||
|
|
||||||
|
// Handle system theme changes
|
||||||
const handleMediaQuery = (e?: MediaQueryList) => {
|
const handleMediaQuery = (e?: MediaQueryList) => {
|
||||||
const systemTheme = getSystemTheme(e) as string;
|
const newSystemTheme = getSystemTheme(e);
|
||||||
$themeStore.resolvedTheme = systemTheme;
|
setSystemTheme(newSystemTheme);
|
||||||
$themeStore.systemTheme = systemTheme;
|
setResolvedTheme(newSystemTheme);
|
||||||
|
|
||||||
if (theme === 'system' && !forcedTheme) changeTheme(systemTheme, false, true);
|
// Only update DOM if currently using system theme
|
||||||
|
if (theme === 'system' && !forcedTheme) {
|
||||||
|
changeTheme(newSystemTheme, false, true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const changeTheme = (theme?: string, updateStorage?: boolean, updateDOM?: boolean) => {
|
// Core theme change function
|
||||||
if (!theme) return;
|
const changeTheme = (newTheme?: string, updateStorage = true, updateDOM = true) => {
|
||||||
let name = value?.[theme] || theme;
|
if (!newTheme) return;
|
||||||
|
|
||||||
const enable = disableTransitionOnChange && updateDOM ? disableAnimation() : null;
|
// Handle animation disabling if needed
|
||||||
|
const enableAnimations = disableTransitionOnChange && updateDOM ? disableAnimation() : null;
|
||||||
|
|
||||||
|
// Update localStorage if needed
|
||||||
if (updateStorage) {
|
if (updateStorage) {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(storageKey, theme);
|
localStorage.setItem(storageKey, newTheme);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Unsupported
|
// Ignore storage errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theme === 'system' && enableSystem) {
|
// Determine the actual theme value to apply
|
||||||
|
let themeName = value?.[newTheme] || newTheme;
|
||||||
|
if (newTheme === 'system' && enableSystem) {
|
||||||
const resolved = getSystemTheme();
|
const resolved = getSystemTheme();
|
||||||
name = value?.[resolved] || resolved;
|
themeName = value?.[resolved] || resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update DOM if needed and in browser context
|
||||||
if (updateDOM && browser) {
|
if (updateDOM && browser) {
|
||||||
const d = document.body;
|
const target = document.body;
|
||||||
|
|
||||||
if (attribute === 'class') {
|
if (attribute === 'class') {
|
||||||
d.classList.remove(...(attrs as string[]));
|
// Remove all possible theme classes then add the current one
|
||||||
d.classList.add(name);
|
target.classList.remove(...attrs);
|
||||||
|
target.classList.add(themeName);
|
||||||
} else {
|
} else {
|
||||||
d.setAttribute(attribute, name);
|
target.setAttribute(attribute, themeName);
|
||||||
}
|
}
|
||||||
enable?.();
|
|
||||||
|
// Re-enable animations if they were disabled
|
||||||
|
enableAnimations?.();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const mediaHandler = (...args: any) => handleMediaQuery(...args);
|
// Event handlers
|
||||||
|
const mediaHandler = (e: MediaQueryList) => handleMediaQuery(e);
|
||||||
|
|
||||||
const storageHandler = (e: StorageEvent) => {
|
const storageHandler = (e: StorageEvent) => {
|
||||||
if (e.key !== storageKey) return;
|
if (e.key !== storageKey) return;
|
||||||
setTheme((e.newValue as string) || (defaultTheme as string));
|
setTheme((e.newValue as string) || defaultTheme);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Setup and teardown for window events
|
||||||
const onWindow = (window: Window) => {
|
const onWindow = (window: Window) => {
|
||||||
const media = window.matchMedia(MEDIA);
|
const media = window.matchMedia(MEDIA);
|
||||||
// Use modern event listener approach
|
media.addEventListener('change', () => mediaHandler(media));
|
||||||
media.addEventListener('change', mediaHandler);
|
|
||||||
mediaHandler(media);
|
mediaHandler(media);
|
||||||
|
|
||||||
window.addEventListener('storage', storageHandler);
|
if (browser) {
|
||||||
|
window.addEventListener('storage', storageHandler);
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
destroy() {
|
destroy() {
|
||||||
window.removeEventListener('storage', storageHandler);
|
window.removeEventListener('storage', storageHandler);
|
||||||
media.removeEventListener('change', mediaHandler);
|
media.removeEventListener('change', () => mediaHandler(media));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Update color-scheme CSS property when theme changes
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (enableColorScheme && browser) {
|
if (enableColorScheme && browser) {
|
||||||
let colorScheme =
|
let colorScheme =
|
||||||
// If theme is forced to light or dark, use that
|
|
||||||
forcedTheme && colorSchemes.includes(forcedTheme)
|
forcedTheme && colorSchemes.includes(forcedTheme)
|
||||||
? forcedTheme
|
? forcedTheme
|
||||||
: // If regular theme is light or dark
|
: theme && colorSchemes.includes(theme)
|
||||||
theme && colorSchemes.includes(theme)
|
|
||||||
? theme
|
? theme
|
||||||
: // If theme is system, use the resolved version
|
: theme === 'system'
|
||||||
theme === 'system'
|
|
||||||
? resolvedTheme || null
|
? resolvedTheme || null
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
@@ -130,12 +140,11 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Apply theme changes when theme store updates
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (forcedTheme) {
|
// If theme is forced, update storage but not DOM (script handles it)
|
||||||
changeTheme(theme, true, false);
|
// Otherwise update both
|
||||||
} else {
|
changeTheme(theme, true, !forcedTheme);
|
||||||
changeTheme(theme, true, true); // Add true for updateDOM parameter
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user