Skip to content

Commit

Permalink
feat: implement custom stylesheet id and theme scope
Browse files Browse the repository at this point in the history
  • Loading branch information
DerYeger committed Dec 19, 2024
1 parent 8cb5dab commit c2f7652
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 27 deletions.
40 changes: 24 additions & 16 deletions packages/docs/src/pages/en/features/theme.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ Customize your application's default text colors, surfaces, and more. Easily mod

## API

| Feature | Description |
| - | - |
| [useTheme](/api/use-theme/) | The theme composable allows you to get information about, and modify the current theme |
| [v-theme-provider](/api/v-theme-provider/) | The theme provider component modifies the theme of all its children |
| Feature | Description |
| ------------------------------------------ | -------------------------------------------------------------------------------------- |
| [useTheme](/api/use-theme/) | The theme composable allows you to get information about, and modify the current theme |
| [v-theme-provider](/api/v-theme-provider/) | The theme provider component modifies the theme of all its children |

<ApiInline hide-links />

Expand All @@ -42,8 +42,8 @@ import { createVuetify } from 'vuetify'

export default createVuetify({
theme: {
defaultTheme: 'dark'
}
defaultTheme: 'dark',
},
})
```

Expand Down Expand Up @@ -76,7 +76,7 @@ const myCustomLightTheme = {
'border-color': '#000000',
'border-opacity': 0.12,
'high-emphasis-opacity': 0.87,
'medium-emphasis-opacity': 0.60,
'medium-emphasis-opacity': 0.6,
'disabled-opacity': 0.38,
'idle-opacity': 0.04,
'hover-opacity': 0.04,
Expand All @@ -89,7 +89,7 @@ const myCustomLightTheme = {
'theme-on-kbd': '#FFFFFF',
'theme-code': '#F5F5F5',
'theme-on-code': '#000000',
}
},
}

export default createVuetify({
Expand Down Expand Up @@ -162,13 +162,13 @@ This is used when you need to change the theme during runtime
</template>

<script setup>
import { useTheme } from 'vuetify'
import { useTheme } from 'vuetify'
const theme = useTheme()
const theme = useTheme()
function toggleTheme () {
theme.global.name.value = theme.global.current.value.dark ? 'light' : 'dark'
}
function toggleTheme() {
theme.global.name.value = theme.global.current.value.dark ? 'light' : 'dark'
}
</script>
```

Expand Down Expand Up @@ -230,7 +230,9 @@ Custom properties for colors are a list of `red, green, blue`, so the `rgb()` or

```html
<template>
<div class="bg-something on-something">background color with appropriate text color contrast</div>
<div class="bg-something on-something">
background color with appropriate text color contrast
</div>

<div class="text-something">text color</div>

Expand Down Expand Up @@ -339,7 +341,7 @@ Content-Security-Policy: style-src 'self' 'nonce-dQw4w9WgXcQ'
```ts
// src/plugins/vuetify.js

import {createVuetify} from 'vuetify'
import { createVuetify } from 'vuetify'

export const vuetify = createVuetify({
theme: {
Expand All @@ -350,4 +352,10 @@ export const vuetify = createVuetify({

## Implementation

Vuetify generates theme styles at runtime according to the given configuration. The generated styles are injected into the `<head>` section of the DOM in a `<style>` tag with an **id** of `vuetify-theme-stylesheet`.
Vuetify generates theme styles at runtime according to the given configuration. The generated styles are injected into the `<head>` section of the DOM in a `<style>` tag with a default **id** of `vuetify-theme-stylesheet`.

### Microfrontends

An application using microfrontends with multiple instances of Vuetify may need to define unique **theme.stylesheetId** values for each microfrontend in order to prevent conflicts between their generated stylesheets.
Further, such a scenario might require styles to be scoped to a specific microfrontend, which can be achieved by setting the **theme.scope** property.
For example, a microfrontend mounted in an element `#my-app` can define a **theme.scope** of `#my-app` to scope its styles to that element and its children instead of `:root` and global classes.
27 changes: 27 additions & 0 deletions packages/vuetify/src/composables/__tests__/theme.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,33 @@ describe('createTheme', () => {
expect(theme.computedThemes.value.light.colors).toHaveProperty('color2-lighten-1')
})

it('should allow for customization of the stylesheet id', () => {
const customStylesheetId = 'custom-vuetify-stylesheet-id'
const theme = createTheme({
stylesheetId: customStylesheetId,
})

theme.install(app)

expect(document.getElementById(customStylesheetId)).toBeDefined()
})

it('should allow for themes to be scoped', () => {
const scope = '#my-app'
const theme = createTheme({
scope,
})

theme.install(app)

const scopedStyles = document.getElementById('vuetify-theme-stylesheet')!.innerHTML
const selectors = scopedStyles.split('\n').filter(line => line.includes('{')).map(line => line.trim())
selectors.forEach(selector => {
expect(selector.startsWith(`:where(${scope})`)).toBe(true)
expect(selector).not.toContain(':root')
})
})

// it('should use [email protected] functionality', () => {
// const theme = createTheme()
// const set = jest.fn()
Expand Down
38 changes: 27 additions & 11 deletions packages/vuetify/src/composables/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export type ThemeOptions = false | {
defaultTheme?: string
variations?: false | VariationsOptions
themes?: Record<string, ThemeDefinition>
stylesheetId?: string
scope?: string
}
export type ThemeDefinition = DeepPartial<InternalThemeDefinition>

Expand All @@ -42,6 +44,8 @@ interface InternalThemeOptions {
defaultTheme: string
variations: false | VariationsOptions
themes: Record<string, InternalThemeDefinition>
stylesheetId: string
scope?: string
}

interface VariationsOptions {
Expand Down Expand Up @@ -185,6 +189,7 @@ function genDefaults () {
},
},
},
stylesheetId: 'vuetify-theme-stylesheet',
}
}

Expand Down Expand Up @@ -252,6 +257,25 @@ export function createTheme (options?: ThemeOptions): ThemeInstance & { install:
})
const current = computed(() => computedThemes.value[name.value])

function createCssClass (lines: string[], selector: string, content: string[]) {
lines.push(
`${getScopedSelector(selector)} {\n`,
...content.map(line => ` ${line};\n`),
'}\n',
)
}

function getScopedSelector (selector: string) {
if (!parsedOptions.scope) {
return selector
}
const scopeSelector = `:where(${parsedOptions.scope})`
if (selector === ':root') {
return scopeSelector
}
return `${scopeSelector} ${selector}`
}

const styles = computed(() => {
const lines: string[] = []

Expand Down Expand Up @@ -295,7 +319,7 @@ export function createTheme (options?: ThemeOptions): ThemeInstance & { install:
return {
style: [{
children: styles.value,
id: 'vuetify-theme-stylesheet',
id: parsedOptions.stylesheetId,
nonce: parsedOptions.cspNonce || false as never,
}],
}
Expand All @@ -321,7 +345,7 @@ export function createTheme (options?: ThemeOptions): ThemeInstance & { install:
}
} else {
let styleEl = IN_BROWSER
? document.getElementById('vuetify-theme-stylesheet')
? document.getElementById(parsedOptions.stylesheetId)
: null

if (IN_BROWSER) {
Expand All @@ -334,7 +358,7 @@ export function createTheme (options?: ThemeOptions): ThemeInstance & { install:
if (typeof document !== 'undefined' && !styleEl) {
const el = document.createElement('style')
el.type = 'text/css'
el.id = 'vuetify-theme-stylesheet'
el.id = parsedOptions.stylesheetId
if (parsedOptions.cspNonce) el.setAttribute('nonce', parsedOptions.cspNonce)

styleEl = el
Expand Down Expand Up @@ -400,14 +424,6 @@ export function useTheme () {
return theme
}

function createCssClass (lines: string[], selector: string, content: string[]) {
lines.push(
`${selector} {\n`,
...content.map(line => ` ${line};\n`),
'}\n',
)
}

function genCssVariables (theme: InternalThemeDefinition) {
const lightOverlay = theme.dark ? 2 : 1
const darkOverlay = theme.dark ? 1 : 2
Expand Down

0 comments on commit c2f7652

Please sign in to comment.