import { ThemeEmptyStorage } from './ThemeEmptyStorage.js';
/**
* @module
*/
/** @private **/
let storage = null;
/**
* @private
* @type {string}
*/
const dark = 'dark';
/**
* @private
* @type {string}
*/
const light = 'light';
/**
* Auto-detects stored decision for the theme and
* falls back to system level preference.
* Otherwise, uses light theme.
* @function
* @param options {object}
* @param options.storage {ThemeStorage}
* @param options.useStorage {boolean}
* @param options.usePreferred {boolean}
* @return {Promise<{from: string, name: string}>} the name of the decision used for the theme.
*/
const init = async (options) => {
storage = options.storage ? options.storage : new ThemeEmptyStorage();
// phase 1: fetch the preferred theme from storage
if (options.useStorage) {
let storedTheme = null;
if (
storage &&
(await storage.isDefined()) &&
(storedTheme = await storage.value()) !== null
) {
DOM.add(storedTheme);
return { from: 'storage', name: storedTheme };
}
}
// phase 2: use the preferred theme from OS-level preferences
// but to not store the decision as it's not an active user decision
if (options.usePreferred !== false) {
let osPreferred = DOM.getPreferred([dark, light]);
if (osPreferred) {
DOM.add(osPreferred);
return { from: 'preferred', name: osPreferred };
}
}
// phase 2: fall back to light theme as default
// but to not store the decision as it's not an active user decision
DOM.add(light);
return { from: 'fallback', name: light };
};
/**
* Check if current applied theme (in DOM)
* is the theme of the given name
* @function
* @param name {string}
* @return {boolean}
*/
const is = (name) => name === DOM.current;
/**
* Get the name of the current applied theme in DOM
* @function
* @return {null|string}
*/
const current = () => DOM.current;
/**
* Updates the current theme by given value,
* if supported (light, dark)
* @function
* @async
* @param name {string}
* @return {Promise<*>}
*/
const update = async (name) => {
DOM.add(name);
return storage.update(name);
};
/**
* Removes the current theme from DOM
* @function
* @async
* @return {Promise<*>}
*/
const remove = async () => {
DOM.remove(DOM.current);
return storage.remove();
};
/**
* Internal handler to update the DOM manually,
* pimarily on the html root
* @private
*/
const DOM = {};
DOM.current = null;
DOM.getPreferred = (names) => names.find((name) => DOM.isPreferred(name));
DOM.isPreferred = (name) => {
const pattern = `(prefers-color-scheme: ${name})`;
return window.matchMedia && window.matchMedia(pattern).matches;
};
DOM.add = (name) => {
if (DOM.current && name !== DOM.current) {
DOM.remove(DOM.current);
}
DOM.current = name;
document.documentElement.classList.add(name);
};
DOM.remove = (name) => {
DOM.current = null;
document.documentElement.classList.remove(name);
};
/**
* A facade to the underlying theming system that
* supports different storage implementations.
* @constant
* @default
*/
export const Theme = {
DARK: dark,
LIGHT: light,
init,
is,
current,
update,
remove,
};