Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor <ThemeSelect> script to avoid double media query event listener #1734

Merged
merged 4 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/sweet-vans-carry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/starlight': patch
---

Refactors `<ThemeSelect>` custom element logic to improve performance
87 changes: 36 additions & 51 deletions packages/starlight/components/ThemeSelect.astro
Original file line number Diff line number Diff line change
Expand Up @@ -28,65 +28,50 @@ const { labels } = Astro.props;
<script>
type Theme = 'auto' | 'dark' | 'light';

class StarlightThemeSelect extends HTMLElement {
/** Key in `localStorage` to store color theme preference at. */
#key = 'starlight-theme';
/** Key in `localStorage` to store color theme preference at. */
const storageKey = 'starlight-theme';

constructor() {
super();
this.#onThemeChange(this.#loadTheme());
const select = this.querySelector('select');
if (select) {
select.addEventListener('change', (e) => {
if (e.currentTarget instanceof HTMLSelectElement) {
this.#onThemeChange(this.#parseTheme(e.currentTarget.value));
}
});
matchMedia(`(prefers-color-scheme: light)`).addEventListener('change', () => {
if (this.#loadTheme() === 'auto') this.#onThemeChange('auto');
});
}
}
/** Get a typesafe theme string from any JS value (unknown values are coerced to `'auto'`). */
const parseTheme = (theme: unknown): Theme =>
theme === 'auto' || theme === 'dark' || theme === 'light' ? theme : 'auto';

/** Get a typesafe theme string from any JS value (unknown values are coerced to `'auto'`). */
#parseTheme(theme: unknown): Theme {
if (theme === 'auto' || theme === 'dark' || theme === 'light') {
return theme;
} else {
return 'auto';
}
}
/** Load the user’s preference from `localStorage`. */
const loadTheme = (): Theme =>
parseTheme(typeof localStorage !== 'undefined' && localStorage.getItem(storageKey));

/** Get the preferred system color scheme. */
#getPreferredColorScheme(): Theme {
return matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
/** Store the user’s preference in `localStorage`. */
function storeTheme(theme: Theme): void {
if (typeof localStorage !== 'undefined') {
localStorage.setItem(storageKey, theme === 'light' || theme === 'dark' ? theme : '');
}
}

/** Update select menu UI, document theme, and local storage state. */
#onThemeChange(theme: Theme): void {
StarlightThemeProvider.updatePickers(theme);
document.documentElement.dataset.theme =
theme === 'auto' ? this.#getPreferredColorScheme() : theme;
this.#storeTheme(theme);
}
/** Get the preferred system color scheme. */
const getPreferredColorScheme = (): Theme =>
matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';

/** Store the user’s preference in `localStorage`. */
#storeTheme(theme: Theme): void {
if (typeof localStorage !== 'undefined') {
if (theme === 'light' || theme === 'dark') {
localStorage.setItem(this.#key, theme);
} else {
localStorage.removeItem(this.#key);
}
}
}
/** Update select menu UI, document theme, and local storage state. */
function onThemeChange(theme: Theme): void {
StarlightThemeProvider.updatePickers(theme);
document.documentElement.dataset.theme = theme === 'auto' ? getPreferredColorScheme() : theme;
storeTheme(theme);
}

/** Load the user’s preference from `localStorage`. */
#loadTheme(): Theme {
const theme = typeof localStorage !== 'undefined' && localStorage.getItem(this.#key);
return this.#parseTheme(theme);
// React to changes in system color scheme.
matchMedia(`(prefers-color-scheme: light)`).addEventListener('change', () => {
if (loadTheme() === 'auto') onThemeChange('auto');
});

class StarlightThemeSelect extends HTMLElement {
constructor() {
super();
onThemeChange(loadTheme());
this.querySelector('select')?.addEventListener('change', (e) => {
if (e.currentTarget instanceof HTMLSelectElement) {
onThemeChange(parseTheme(e.currentTarget.value));
}
});
}
}

customElements.define('starlight-theme-select', StarlightThemeSelect);
</script>
2 changes: 1 addition & 1 deletion packages/starlight/global.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
declare global {
export declare global {
var StarlightThemeProvider: {
updatePickers(theme?: string): void;
};
Expand Down
Loading