UNPKG

@studiocms/ui

Version:

The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS.

114 lines (113 loc) 3.76 kB
class ThemeHelper { themeManagerElement; observer; themeChangeCallbacks = []; /** * A helper to toggle, set and get the current StudioCMS UI theme. * @param themeProvider The element that should carry the data-theme attribute (replaces the document root) */ constructor(themeProvider) { this.themeManagerElement = themeProvider || document.documentElement; this.themeManagerElement.dataset.theme = this.getTheme(true); } /** * Get the current theme. * @param {boolean} resolveSystemTheme Whether to resolve the `system` theme to the actual theme (`dark` or `light`) * @returns {Theme} The current theme. */ getTheme = (resolveSystemTheme) => { const theme = this.themeManagerElement.dataset.theme || "system"; if (!resolveSystemTheme) { return theme; } if ((this.themeManagerElement.dataset.theme ?? "system") !== "system") { return this.themeManagerElement.dataset.theme; } if (window.matchMedia("(prefers-color-scheme: dark)").matches) { return "dark"; } if (window.matchMedia("(prefers-color-scheme: light)").matches) { return "light"; } throw new Error( "Unable to resolve theme. (Most likely cause: window.matchMedia is not supported by the browser)" ); }; /** * Sets the current theme. * @param theme The new theme. One of `dark`, `light` or `system`. */ setTheme = (theme) => { this.themeManagerElement.dataset.theme = theme; if (typeof localStorage.getItem("starlight-theme") === "string") { localStorage.setItem("starlight-theme", theme === "system" ? "" : theme); } }; /** * Toggles the current theme. * * If the theme is set to `system` (or no theme is set via the root element), * the theme is set depending on the user's color scheme preference (set in the browser). */ toggleTheme = () => { const theme = this.getTheme(); if (theme && theme === "dark") { this.setTheme("light"); return; } if (theme && theme === "light") { this.setTheme("dark"); return; } if (window.matchMedia("(prefers-color-scheme: dark)").matches) { this.setTheme("light"); return; } if (window.matchMedia("(prefers-color-scheme: light)").matches) { this.setTheme("dark"); return; } }; /** * Register an element to act as a toggle! When clicked, it will toggle the theme. * @param toggle The HTML element that should act as the toggle */ registerToggle = (toggle) => { if (!toggle) { console.error("Element passed to toggle registration does not exist."); return; } if (!toggle.dataset.suiThemeToggle) { toggle.addEventListener("click", this.toggleTheme); toggle.dataset.suiThemeToggle = "true"; } }; /** * Allows for adding a callback that gets called whenever the theme changes. * @param callback The callback to be executed */ onThemeChange = (callback) => { if (!this.observer) { this.observer = new MutationObserver(this.themeManagerMutationHandler); this.observer.observe(this.themeManagerElement, { attributes: true, attributeOldValue: true, attributeFilter: ["data-theme"] }); } this.themeChangeCallbacks.push(callback); }; /** * Simply gets the first mutation and calls all registered callbacks. * @param mutations The mutations array from the observer. Due to the specified options, this will always be a 1-length array, */ themeManagerMutationHandler = (mutations) => { if (!mutations[0]) return; for (const callback of this.themeChangeCallbacks) { callback(this.getTheme(), mutations[0].oldValue || "system"); } }; } export { ThemeHelper };