232 lines
5.6 KiB
JavaScript
232 lines
5.6 KiB
JavaScript
|
/* eslint-disable no-multi-spaces */
|
||
|
// Extensions
|
||
|
import { Service } from '../service'; // Utilities
|
||
|
|
||
|
import * as ThemeUtils from './utils'; // Types
|
||
|
|
||
|
import Vue from 'vue';
|
||
|
export class Theme extends Service {
|
||
|
constructor(options = {}) {
|
||
|
super();
|
||
|
this.disabled = false;
|
||
|
this.themes = {
|
||
|
light: {
|
||
|
primary: '#1976D2',
|
||
|
secondary: '#424242',
|
||
|
accent: '#82B1FF',
|
||
|
error: '#FF5252',
|
||
|
info: '#2196F3',
|
||
|
success: '#4CAF50',
|
||
|
warning: '#FB8C00'
|
||
|
},
|
||
|
dark: {
|
||
|
primary: '#2196F3',
|
||
|
secondary: '#424242',
|
||
|
accent: '#FF4081',
|
||
|
error: '#FF5252',
|
||
|
info: '#2196F3',
|
||
|
success: '#4CAF50',
|
||
|
warning: '#FB8C00'
|
||
|
}
|
||
|
};
|
||
|
this.defaults = this.themes;
|
||
|
this.isDark = null;
|
||
|
this.vueInstance = null;
|
||
|
|
||
|
if (options.disable) {
|
||
|
this.disabled = true;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
this.options = { ...this.options,
|
||
|
...options.options
|
||
|
};
|
||
|
this.dark = Boolean(options.dark);
|
||
|
const themes = options.themes || {};
|
||
|
this.themes = {
|
||
|
dark: this.fillVariant(themes.dark, true),
|
||
|
light: this.fillVariant(themes.light, false)
|
||
|
};
|
||
|
} // When setting css, check for element
|
||
|
// and apply new values
|
||
|
|
||
|
|
||
|
set css(val) {
|
||
|
this.checkOrCreateStyleElement() && (this.styleEl.innerHTML = val);
|
||
|
}
|
||
|
|
||
|
set dark(val) {
|
||
|
const oldDark = this.isDark;
|
||
|
this.isDark = val; // Only apply theme after dark
|
||
|
// has already been set before
|
||
|
|
||
|
oldDark != null && this.applyTheme();
|
||
|
}
|
||
|
|
||
|
get dark() {
|
||
|
return Boolean(this.isDark);
|
||
|
} // Apply current theme default
|
||
|
// only called on client side
|
||
|
|
||
|
|
||
|
applyTheme() {
|
||
|
if (this.disabled) return this.clearCss();
|
||
|
this.css = this.generatedStyles;
|
||
|
}
|
||
|
|
||
|
clearCss() {
|
||
|
this.css = '';
|
||
|
} // Initialize theme for SSR and SPA
|
||
|
// Attach to ssrContext head or
|
||
|
// apply new theme to document
|
||
|
|
||
|
|
||
|
init(root, ssrContext) {
|
||
|
if (this.disabled) return;
|
||
|
const meta = Boolean(root.$meta); // TODO: don't import public types from /src
|
||
|
|
||
|
const ssr = Boolean(ssrContext);
|
||
|
/* istanbul ignore else */
|
||
|
|
||
|
if (meta) {
|
||
|
this.initNuxt(root);
|
||
|
} else if (ssr) {
|
||
|
this.initSSR(ssrContext);
|
||
|
}
|
||
|
|
||
|
this.initTheme();
|
||
|
} // Allows for you to set target theme
|
||
|
|
||
|
|
||
|
setTheme(theme, value) {
|
||
|
this.themes[theme] = Object.assign(this.themes[theme], value);
|
||
|
this.applyTheme();
|
||
|
} // Reset theme defaults
|
||
|
|
||
|
|
||
|
resetThemes() {
|
||
|
this.themes.light = Object.assign({}, this.defaults.light);
|
||
|
this.themes.dark = Object.assign({}, this.defaults.dark);
|
||
|
this.applyTheme();
|
||
|
} // Check for existence of style element
|
||
|
|
||
|
|
||
|
checkOrCreateStyleElement() {
|
||
|
this.styleEl = document.getElementById('vuetify-theme-stylesheet');
|
||
|
/* istanbul ignore next */
|
||
|
|
||
|
if (this.styleEl) return true;
|
||
|
this.genStyleElement(); // If doesn't have it, create it
|
||
|
|
||
|
return Boolean(this.styleEl);
|
||
|
}
|
||
|
|
||
|
fillVariant(theme = {}, dark) {
|
||
|
const defaultTheme = this.themes[dark ? 'dark' : 'light'];
|
||
|
return Object.assign({}, defaultTheme, theme);
|
||
|
} // Generate the style element
|
||
|
// if applicable
|
||
|
|
||
|
|
||
|
genStyleElement() {
|
||
|
if (typeof document === 'undefined') return;
|
||
|
/* istanbul ignore next */
|
||
|
|
||
|
const options = this.options || {};
|
||
|
this.styleEl = document.createElement('style');
|
||
|
this.styleEl.type = 'text/css';
|
||
|
this.styleEl.id = 'vuetify-theme-stylesheet';
|
||
|
|
||
|
if (options.cspNonce) {
|
||
|
this.styleEl.setAttribute('nonce', options.cspNonce);
|
||
|
}
|
||
|
|
||
|
document.head.appendChild(this.styleEl);
|
||
|
}
|
||
|
|
||
|
initNuxt(root) {
|
||
|
const options = this.options || {};
|
||
|
root.$children.push(new Vue({
|
||
|
head: {
|
||
|
style: [{
|
||
|
cssText: this.generatedStyles,
|
||
|
type: 'text/css',
|
||
|
id: 'vuetify-theme-stylesheet',
|
||
|
nonce: options.cspNonce
|
||
|
}]
|
||
|
}
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
initSSR(ssrContext) {
|
||
|
const options = this.options || {}; // SSR
|
||
|
|
||
|
const nonce = options.cspNonce ? ` nonce="${options.cspNonce}"` : '';
|
||
|
ssrContext.head = ssrContext.head || '';
|
||
|
ssrContext.head += `<style type="text/css" id="vuetify-theme-stylesheet"${nonce}>${this.generatedStyles}</style>`;
|
||
|
}
|
||
|
|
||
|
initTheme() {
|
||
|
// Only watch for reactivity on client side
|
||
|
if (typeof document === 'undefined') return; // If we get here somehow, ensure
|
||
|
// existing instance is removed
|
||
|
|
||
|
if (this.vueInstance) this.vueInstance.$destroy(); // Use Vue instance to track reactivity
|
||
|
// TODO: Update to use RFC if merged
|
||
|
// https://github.com/vuejs/rfcs/blob/advanced-reactivity-api/active-rfcs/0000-advanced-reactivity-api.md
|
||
|
|
||
|
this.vueInstance = new Vue({
|
||
|
data: {
|
||
|
themes: this.themes
|
||
|
},
|
||
|
watch: {
|
||
|
themes: {
|
||
|
immediate: true,
|
||
|
deep: true,
|
||
|
handler: () => this.applyTheme()
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
get currentTheme() {
|
||
|
const target = this.dark ? 'dark' : 'light';
|
||
|
return this.themes[target];
|
||
|
}
|
||
|
|
||
|
get generatedStyles() {
|
||
|
const theme = this.parsedTheme;
|
||
|
/* istanbul ignore next */
|
||
|
|
||
|
const options = this.options || {};
|
||
|
let css;
|
||
|
|
||
|
if (options.themeCache != null) {
|
||
|
css = options.themeCache.get(theme);
|
||
|
/* istanbul ignore if */
|
||
|
|
||
|
if (css != null) return css;
|
||
|
}
|
||
|
|
||
|
css = ThemeUtils.genStyles(theme, options.customProperties);
|
||
|
|
||
|
if (options.minifyTheme != null) {
|
||
|
css = options.minifyTheme(css);
|
||
|
}
|
||
|
|
||
|
if (options.themeCache != null) {
|
||
|
options.themeCache.set(theme, css);
|
||
|
}
|
||
|
|
||
|
return css;
|
||
|
}
|
||
|
|
||
|
get parsedTheme() {
|
||
|
/* istanbul ignore next */
|
||
|
const theme = this.currentTheme || {};
|
||
|
return ThemeUtils.parse(theme);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
Theme.property = 'theme';
|
||
|
//# sourceMappingURL=index.js.map
|