/* eslint-disable no-multi-spaces */ // Extensions import { Service } from '../service'; // Utilities import * as ThemeUtils from './utils'; import { getNestedValue } from '../../util/helpers'; // Types import Vue from 'vue'; export class Theme extends Service { constructor(preset) { super(); this.disabled = false; this.isDark = null; this.vueInstance = null; this.vueMeta = null; const { dark, disable, options, themes } = preset[Theme.property]; this.dark = Boolean(dark); this.defaults = this.themes = themes; this.options = options; if (disable) { this.disabled = true; return; } 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) { if (this.vueMeta) { if (this.isVueMeta23) { this.applyVueMeta23(); } return; } 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; /* istanbul ignore else */ if (root.$meta) { this.initVueMeta(root); } else if (ssrContext) { 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() { /* istanbul ignore if */ if (typeof document === 'undefined') return; /* istanbul ignore next */ this.styleEl = document.createElement('style'); this.styleEl.type = 'text/css'; this.styleEl.id = 'vuetify-theme-stylesheet'; if (this.options.cspNonce) { this.styleEl.setAttribute('nonce', this.options.cspNonce); } document.head.appendChild(this.styleEl); } initVueMeta(root) { this.vueMeta = root.$meta(); if (this.isVueMeta23) { // vue-meta needs to apply after mounted() root.$nextTick(() => { this.applyVueMeta23(); }); return; } const metaKeyName = typeof this.vueMeta.getOptions === 'function' ? this.vueMeta.getOptions().keyName : 'metaInfo'; const metaInfo = root.$options[metaKeyName] || {}; root.$options[metaKeyName] = () => { metaInfo.style = metaInfo.style || []; const vuetifyStylesheet = metaInfo.style.find(s => s.id === 'vuetify-theme-stylesheet'); if (!vuetifyStylesheet) { metaInfo.style.push({ cssText: this.generatedStyles, type: 'text/css', id: 'vuetify-theme-stylesheet', nonce: (this.options || {}).cspNonce }); } else { vuetifyStylesheet.cssText = this.generatedStyles; } return metaInfo; }; } applyVueMeta23() { const { set } = this.vueMeta.addApp('vuetify'); set({ style: [{ cssText: this.generatedStyles, type: 'text/css', id: 'vuetify-theme-stylesheet', nonce: this.options.cspNonce }] }); } initSSR(ssrContext) { // SSR const nonce = this.options.cspNonce ? ` nonce="${this.options.cspNonce}"` : ''; ssrContext.head = ssrContext.head || ''; ssrContext.head += ``; } 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() { return ThemeUtils.parse(this.currentTheme || {}, undefined, getNestedValue(this.options, ['variations'], true)); } // Is using v2.3 of vue-meta // https://github.com/nuxt/vue-meta/releases/tag/v2.3.0 get isVueMeta23() { return typeof this.vueMeta.addApp === 'function'; } } Theme.property = 'theme'; //# sourceMappingURL=index.js.map