@studiocms/ui
Version:
The UI library for StudioCMS. Includes the layouts & components we use to build StudioCMS.
114 lines (113 loc) • 3.76 kB
JavaScript
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
};