258 lines
11 KiB
JavaScript
258 lines
11 KiB
JavaScript
const STORAGE_KEY = 'vendoo-theme-state-v2';
|
|
const LEGACY_KEY = 'vendoo-theme';
|
|
const BUILTIN = new Set(['light', 'dark', 'contrast', 'system']);
|
|
let activeCustomProperties = [];
|
|
|
|
function mediaDark() {
|
|
return window.matchMedia?.('(prefers-color-scheme: dark)').matches;
|
|
}
|
|
|
|
function mediaReducedMotion() {
|
|
return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
}
|
|
|
|
function clearCustomProperties() {
|
|
for (const property of activeCustomProperties) document.documentElement.style.removeProperty(property);
|
|
activeCustomProperties = [];
|
|
}
|
|
|
|
function effectiveBuiltin(themeId) {
|
|
return themeId === 'system' ? (mediaDark() ? 'dark' : 'light') : themeId;
|
|
}
|
|
|
|
function normalizeState(input = {}) {
|
|
const themeId = String(input.theme_id || input.themeId || 'system');
|
|
return {
|
|
theme_id: themeId,
|
|
density: ['compact', 'comfortable', 'spacious'].includes(input.density) ? input.density : 'comfortable',
|
|
reduced_motion: ['system', 'reduce', 'allow'].includes(input.reduced_motion || input.reducedMotion) ? (input.reduced_motion || input.reducedMotion) : 'system',
|
|
custom_theme: input.custom_theme || input.customTheme || null,
|
|
};
|
|
}
|
|
|
|
function applyState(input, { persist = true, dispatch = true } = {}) {
|
|
const state = normalizeState(input);
|
|
clearCustomProperties();
|
|
const custom = state.custom_theme && state.custom_theme.id === state.theme_id ? state.custom_theme : null;
|
|
const baseTheme = custom ? custom.base_theme : state.theme_id;
|
|
const resolved = effectiveBuiltin(BUILTIN.has(baseTheme) ? baseTheme : 'light');
|
|
document.documentElement.dataset.theme = resolved;
|
|
document.documentElement.dataset.themePreference = state.theme_id;
|
|
document.documentElement.dataset.density = state.density;
|
|
const reduce = state.reduced_motion === 'reduce' || (state.reduced_motion === 'system' && mediaReducedMotion());
|
|
document.documentElement.dataset.reducedMotion = reduce ? 'true' : 'false';
|
|
if (custom?.css_values) {
|
|
for (const [property, value] of Object.entries(custom.css_values)) {
|
|
if (!/^--vd-[a-z0-9-]+$/.test(property)) continue;
|
|
document.documentElement.style.setProperty(property, String(value));
|
|
activeCustomProperties.push(property);
|
|
}
|
|
}
|
|
if (persist) localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
if (dispatch) window.dispatchEvent(new CustomEvent('vendoo:theme-changed', { detail: { ...state, resolved } }));
|
|
return { ...state, resolved };
|
|
}
|
|
|
|
function readStoredState() {
|
|
try {
|
|
const parsed = JSON.parse(localStorage.getItem(STORAGE_KEY) || 'null');
|
|
if (parsed) return normalizeState(parsed);
|
|
} catch {}
|
|
const legacy = localStorage.getItem(LEGACY_KEY);
|
|
return normalizeState({ theme_id: BUILTIN.has(legacy) ? legacy : 'system' });
|
|
}
|
|
|
|
function previewTheme({ base_theme = 'light', css_values = {}, density = 'comfortable', reduced_motion = 'system' } = {}) {
|
|
return applyState({ theme_id: '__preview__', density, reduced_motion, custom_theme: { id: '__preview__', base_theme, css_values } }, { persist: false });
|
|
}
|
|
|
|
function restoreStored() {
|
|
return applyState(readStoredState(), { persist: false });
|
|
}
|
|
|
|
function loadUxFoundation() {
|
|
if (!document.querySelector('link[data-vendoo-ux-foundation]')) {
|
|
const stylesheet = document.createElement('link');
|
|
stylesheet.rel = 'stylesheet';
|
|
stylesheet.href = '/ux-foundation.css?v=1.44.0';
|
|
stylesheet.dataset.vendooUxFoundation = '1.44.0';
|
|
document.head.append(stylesheet);
|
|
}
|
|
const loadBehavior = () => {
|
|
if (document.querySelector('script[data-vendoo-ux-foundation]')) return;
|
|
const script = document.createElement('script');
|
|
script.src = '/ux-foundation.js?v=1.44.0';
|
|
script.dataset.vendooUxFoundation = '1.44.0';
|
|
script.async = false;
|
|
document.body.append(script);
|
|
};
|
|
if (document.readyState === 'complete') loadBehavior();
|
|
else window.addEventListener('load', loadBehavior, { once: true });
|
|
}
|
|
|
|
function loadFluxStudioAssets() {
|
|
if (!document.querySelector('link[data-ux-flux-studio]')) {
|
|
const stylesheet = document.createElement('link');
|
|
stylesheet.rel = 'stylesheet';
|
|
stylesheet.href = '/flux-studio-ux.css?v=1.44.0';
|
|
stylesheet.setAttribute('data-ux-flux-studio', 'style');
|
|
document.head.append(stylesheet);
|
|
}
|
|
const loadBehavior = () => {
|
|
if (document.querySelector('script[data-ux-flux-studio]')) return;
|
|
const script = document.createElement('script');
|
|
script.src = '/flux-studio-ux.js?v=1.44.0';
|
|
script.setAttribute('data-ux-flux-studio', 'script');
|
|
script.async = false;
|
|
document.body.append(script);
|
|
};
|
|
if (document.readyState === 'complete') loadBehavior();
|
|
else window.addEventListener('load', loadBehavior, { once: true });
|
|
}
|
|
|
|
function loadQualityCenterAssets() {
|
|
if (!document.querySelector('link[data-ux-quality-center]')) {
|
|
const stylesheet = document.createElement('link');
|
|
stylesheet.rel = 'stylesheet';
|
|
stylesheet.href = '/quality-center-ux.css?v=1.44.0';
|
|
stylesheet.setAttribute('data-ux-quality-center', 'style');
|
|
document.head.append(stylesheet);
|
|
}
|
|
const loadBehavior = () => {
|
|
if (document.querySelector('script[data-ux-quality-center]')) return;
|
|
const script = document.createElement('script');
|
|
script.src = '/quality-center-ux.js?v=1.44.0';
|
|
script.setAttribute('data-ux-quality-center', 'script');
|
|
script.async = false;
|
|
document.body.append(script);
|
|
};
|
|
if (document.readyState === 'complete') loadBehavior();
|
|
else window.addEventListener('load', loadBehavior, { once: true });
|
|
}
|
|
|
|
function loadInventoryWorkspaceAssets() {
|
|
if (!document.querySelector('link[data-ux-inventory-workspace]')) {
|
|
const stylesheet = document.createElement('link');
|
|
stylesheet.rel = 'stylesheet';
|
|
stylesheet.href = '/inventory-workspace.css?v=1.44.0';
|
|
stylesheet.setAttribute('data-ux-inventory-workspace', 'style');
|
|
document.head.append(stylesheet);
|
|
}
|
|
const loadBehavior = () => {
|
|
if (document.querySelector('script[data-ux-inventory-workspace]')) return;
|
|
const script = document.createElement('script');
|
|
script.src = '/inventory-workspace.js?v=1.44.0';
|
|
script.setAttribute('data-ux-inventory-workspace', 'script');
|
|
script.async = false;
|
|
document.body.append(script);
|
|
};
|
|
if (document.readyState === 'complete') loadBehavior();
|
|
else window.addEventListener('load', loadBehavior, { once: true });
|
|
}
|
|
|
|
function loadTemplateWorkspaceAssets() {
|
|
if (!document.querySelector('link[data-ux-template-workspace]')) {
|
|
const stylesheet = document.createElement('link');
|
|
stylesheet.rel = 'stylesheet';
|
|
stylesheet.href = '/template-workspace.css?v=1.44.0';
|
|
stylesheet.setAttribute('data-ux-template-workspace', 'style');
|
|
document.head.append(stylesheet);
|
|
}
|
|
const loadBehavior = () => {
|
|
if (document.querySelector('script[data-ux-template-workspace]')) return;
|
|
const script = document.createElement('script');
|
|
script.src = '/template-workspace.js?v=1.44.0';
|
|
script.setAttribute('data-ux-template-workspace', 'script');
|
|
script.async = false;
|
|
document.body.append(script);
|
|
};
|
|
if (document.readyState === 'complete') loadBehavior();
|
|
else window.addEventListener('load', loadBehavior, { once: true });
|
|
}
|
|
|
|
function loadTodayWorkspaceAssets() {
|
|
if (!document.querySelector('link[data-ux-today-workspace]')) {
|
|
const stylesheet = document.createElement('link');
|
|
stylesheet.rel = 'stylesheet';
|
|
stylesheet.href = '/today-workspace.css?v=1.44.0';
|
|
stylesheet.setAttribute('data-ux-today-workspace', 'style');
|
|
document.head.append(stylesheet);
|
|
}
|
|
const loadBehavior = () => {
|
|
if (document.querySelector('script[data-ux-today-workspace]')) return;
|
|
const script = document.createElement('script');
|
|
script.src = '/today-workspace.js?v=1.44.0';
|
|
script.setAttribute('data-ux-today-workspace', 'script');
|
|
script.async = false;
|
|
document.body.append(script);
|
|
};
|
|
if (document.readyState === 'complete') loadBehavior();
|
|
else window.addEventListener('load', loadBehavior, { once: true });
|
|
}
|
|
|
|
function loadSettingsControlCenterAssets() {
|
|
if (!document.querySelector('link[data-ux-settings-control-center]')) {
|
|
const stylesheet = document.createElement('link');
|
|
stylesheet.rel = 'stylesheet';
|
|
stylesheet.href = '/settings-control-center.css?v=1.44.0';
|
|
stylesheet.setAttribute('data-ux-settings-control-center', 'style');
|
|
document.head.append(stylesheet);
|
|
}
|
|
const loadBehavior = () => {
|
|
if (document.querySelector('script[data-ux-settings-control-center]')) return;
|
|
const script = document.createElement('script');
|
|
script.src = '/settings-control-center.js?v=1.44.0';
|
|
script.setAttribute('data-ux-settings-control-center', 'script');
|
|
script.async = false;
|
|
document.body.append(script);
|
|
};
|
|
if (document.readyState === 'complete') loadBehavior();
|
|
else window.addEventListener('load', loadBehavior, { once: true });
|
|
}
|
|
|
|
function loadModuleExtensionWorkspaceAssets() {
|
|
if (!document.querySelector('link[data-ux-module-extension-workspace]')) {
|
|
const stylesheet = document.createElement('link');
|
|
stylesheet.rel = 'stylesheet';
|
|
stylesheet.href = '/module-extension-workspace.css?v=1.44.0';
|
|
stylesheet.setAttribute('data-ux-module-extension-workspace', 'style');
|
|
document.head.append(stylesheet);
|
|
}
|
|
const loadBehavior = () => {
|
|
if (document.querySelector('script[data-ux-module-extension-workspace]')) return;
|
|
const script = document.createElement('script');
|
|
script.src = '/module-extension-workspace.js?v=1.44.0';
|
|
script.setAttribute('data-ux-module-extension-workspace', 'script');
|
|
script.async = false;
|
|
document.body.append(script);
|
|
};
|
|
if (document.readyState === 'complete') loadBehavior();
|
|
else window.addEventListener('load', loadBehavior, { once: true });
|
|
}
|
|
|
|
applyState(readStoredState(), { persist: false, dispatch: false });
|
|
loadUxFoundation();
|
|
loadFluxStudioAssets();
|
|
loadQualityCenterAssets();
|
|
loadInventoryWorkspaceAssets();
|
|
loadTemplateWorkspaceAssets();
|
|
loadTodayWorkspaceAssets();
|
|
loadSettingsControlCenterAssets();
|
|
loadModuleExtensionWorkspaceAssets();
|
|
window.matchMedia?.('(prefers-color-scheme: dark)').addEventListener?.('change', () => {
|
|
if (document.documentElement.dataset.themePreference === 'system') restoreStored();
|
|
});
|
|
window.matchMedia?.('(prefers-reduced-motion: reduce)').addEventListener?.('change', () => restoreStored());
|
|
|
|
window.VendooTheme = Object.freeze({
|
|
get preference() { return document.documentElement.dataset.themePreference || 'system'; },
|
|
get resolved() { return document.documentElement.dataset.theme || 'light'; },
|
|
applyState,
|
|
previewTheme,
|
|
restoreStored,
|
|
setTheme(themeId, { persist = true } = {}) {
|
|
const current = readStoredState();
|
|
return applyState({ ...current, theme_id: BUILTIN.has(themeId) ? themeId : 'system', custom_theme: null }, { persist });
|
|
},
|
|
supported: Object.freeze([...BUILTIN]),
|
|
}); |