Merges Renovate's kit 3 (PR #149) and adapter-static 4 (PR #148) bumps, plus the migration they need. The mechanical part is `sv migrate sveltekit-3`, run one task at a time and reviewed: - svelte.config.js is gone. Its options move into sveltekit() in vite.config.ts, exported as kitOptions so vitest.config.ts runs the same kit setup, including the $test-utils alias the tests import. - $lib becomes #lib through package.json "imports". There is no src/lib/index, so only the "#lib/*" entry is kept. - tsconfig extends $app/tsconfig. - Peer floors raised to kit 3's requirements: svelte ^5.57.1, vite ^8.0.12, svelte-check ^4.7.5. By hand, from the codemod's list of non-automated tasks: - goto's replaceState option is now replace; keepFocus becomes reset: false. For the search typeahead, reset: false also stops the scroll-to-top, which is wanted while typing. - The test setup mocks drop pushState/replaceState and $app/paths base/assets, which kit 3 removed, and mock refreshAll in place of invalidateAll. - The other flagged files only read page.url or goto internal routes, so they needed no change. TypeScript goes to ^6, not the ^7 Renovate offers: kit 3 declares typescript ^6 as a peer and svelte-check 4.7 accepts ^5 || ^6. Move to 7 once both accept it. With Tailwind 4 and kit 3 in, `npm audit` on the whole tree reports 0, so the web lane now audits every dependency rather than only what ships to browsers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
import * as storage from '#lib/util/safeLocalStorage.js';
|
|
|
|
const STORAGE_KEY = 'minstrel:theme';
|
|
|
|
export type ThemePreference = 'dark' | 'light' | 'system';
|
|
export type ResolvedTheme = 'dark' | 'light';
|
|
|
|
function readPref(): ThemePreference {
|
|
const v = storage.read(STORAGE_KEY);
|
|
return v === 'dark' || v === 'light' || v === 'system' ? v : 'system';
|
|
}
|
|
|
|
function systemResolved(): ResolvedTheme {
|
|
if (typeof window === 'undefined' || !window.matchMedia) return 'dark';
|
|
return window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
|
}
|
|
|
|
function resolve(pref: ThemePreference): ResolvedTheme {
|
|
return pref === 'system' ? systemResolved() : pref;
|
|
}
|
|
|
|
function applyToDOM(resolved: ResolvedTheme): void {
|
|
if (typeof document === 'undefined') return;
|
|
document.documentElement.setAttribute('data-theme', resolved);
|
|
}
|
|
|
|
let _theme = $state<ThemePreference>(readPref());
|
|
// svelte-ignore state_referenced_locally
|
|
// Module-scope state with manual sync — _theme is read once at module
|
|
// init to seed _resolved; subsequent updates flow through setTheme()
|
|
// (which writes both) and the matchMedia listener (which writes
|
|
// _resolved when prefers-color-scheme flips). Not a missed-reactivity
|
|
// case; $derived doesn't apply at module scope here.
|
|
let _resolved = $state<ResolvedTheme>(resolve(_theme));
|
|
|
|
export const theme = {
|
|
get value(): ThemePreference {
|
|
return _theme;
|
|
}
|
|
};
|
|
|
|
export const resolvedTheme = {
|
|
get value(): ResolvedTheme {
|
|
return _resolved;
|
|
}
|
|
};
|
|
|
|
export function setTheme(pref: ThemePreference): void {
|
|
_theme = pref;
|
|
_resolved = resolve(pref);
|
|
storage.write(STORAGE_KEY, pref);
|
|
applyToDOM(_resolved);
|
|
}
|
|
|
|
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
const mq = window.matchMedia('(prefers-color-scheme: light)');
|
|
mq.addEventListener('change', () => {
|
|
if (_theme === 'system') {
|
|
_resolved = systemResolved();
|
|
applyToDOM(_resolved);
|
|
}
|
|
});
|
|
}
|