From 94fc0cad0e1d3044648a511fdaf2f66b6ae0c564 Mon Sep 17 00:00:00 2001 From: Siriwat K Date: Tue, 1 Oct 2024 16:48:37 +0700 Subject: [PATCH] [material-ui] Support CSS variables with shadow DOM (#43948) --- .../customization/shadow-dom/shadow-dom.md | 29 +++++++++++++++++++ .../src/styles/createGetSelector.ts | 16 +++++----- .../src/styles/createTheme.spec.ts | 10 +++++++ .../mui-material/src/styles/createTheme.ts | 1 + .../src/styles/createThemeNoVars.d.ts | 1 + .../src/styles/createThemeWithVars.d.ts | 8 +++++ .../src/styles/createThemeWithVars.js | 2 ++ .../src/styles/extendTheme.test.js | 13 +++++++++ .../src/styles/shouldSkipGeneratingVar.ts | 2 +- 9 files changed, 74 insertions(+), 8 deletions(-) diff --git a/docs/data/material/customization/shadow-dom/shadow-dom.md b/docs/data/material/customization/shadow-dom/shadow-dom.md index ba4c564ee6bfb3..dbb2c931f91654 100644 --- a/docs/data/material/customization/shadow-dom/shadow-dom.md +++ b/docs/data/material/customization/shadow-dom/shadow-dom.md @@ -64,6 +64,35 @@ const theme = createTheme({ ; ``` +### 3. CSS theme variables (optional) + +:::info +If you use **TypeScript**, you need to [extend the interface of the theme](/material-ui/customization/css-theme-variables/usage/#typescript) first. +::: + +To use [CSS theme variables](/material-ui/customization/css-theme-variables/overview/) inside of the shadow DOM, you need to set the selectors for generating the CSS variables: + +```diff + const theme = createTheme({ ++ cssVariables: { ++ rootSelector: ':host', ++ colorSchemeSelector: 'class', ++ }, + components: { + // ...same as above steps + } + }) +``` + +Finally, set the `colorSchemeNode` prop using `shadowRootElement`, from step 1, as the value: + +```diff + +``` + ## Demo In the example below you can see that the component outside of the shadow DOM is affected by global styles, while the component inside of the shadow DOM is not: diff --git a/packages/mui-material/src/styles/createGetSelector.ts b/packages/mui-material/src/styles/createGetSelector.ts index 177dc16f27d7a7..7b03a56fd366a6 100644 --- a/packages/mui-material/src/styles/createGetSelector.ts +++ b/packages/mui-material/src/styles/createGetSelector.ts @@ -2,6 +2,7 @@ import excludeVariablesFromRoot from './excludeVariablesFromRoot'; export default < T extends { + rootSelector?: string; colorSchemeSelector?: 'media' | 'class' | 'data' | string; colorSchemes?: Record; defaultColorScheme?: string; @@ -11,6 +12,7 @@ export default < theme: T, ) => (colorScheme: keyof T['colorSchemes'] | undefined, css: Record) => { + const root = theme.rootSelector || ':root'; const selector = theme.colorSchemeSelector; let rule = selector; if (selector === 'class') { @@ -32,28 +34,28 @@ export default < }); if (rule === 'media') { return { - ':root': css, + [root]: css, [`@media (prefers-color-scheme: dark)`]: { - ':root': excludedVariables, + [root]: excludedVariables, }, }; } if (rule) { return { [rule.replace('%s', colorScheme)]: excludedVariables, - [`:root, ${rule.replace('%s', colorScheme)}`]: css, + [`${root}, ${rule.replace('%s', colorScheme)}`]: css, }; } - return { ':root': { ...css, ...excludedVariables } }; + return { [root]: { ...css, ...excludedVariables } }; } if (rule && rule !== 'media') { - return `:root, ${rule.replace('%s', String(colorScheme))}`; + return `${root}, ${rule.replace('%s', String(colorScheme))}`; } } else if (colorScheme) { if (rule === 'media') { return { [`@media (prefers-color-scheme: ${String(colorScheme)})`]: { - ':root': css, + [root]: css, }, }; } @@ -61,5 +63,5 @@ export default < return rule.replace('%s', String(colorScheme)); } } - return ':root'; + return root; }; diff --git a/packages/mui-material/src/styles/createTheme.spec.ts b/packages/mui-material/src/styles/createTheme.spec.ts index 5ab7673a83aba0..e9616276ef22fe 100644 --- a/packages/mui-material/src/styles/createTheme.spec.ts +++ b/packages/mui-material/src/styles/createTheme.spec.ts @@ -243,3 +243,13 @@ const theme = createTheme(); }, }); } + +// CSS variables for shadow DOM +{ + createTheme({ + cssVariables: { + rootSelector: ':host', + colorSchemeSelector: 'class', + }, + }); +} diff --git a/packages/mui-material/src/styles/createTheme.ts b/packages/mui-material/src/styles/createTheme.ts index 36e92f1082edaf..c0a3c8471fe4fd 100644 --- a/packages/mui-material/src/styles/createTheme.ts +++ b/packages/mui-material/src/styles/createTheme.ts @@ -43,6 +43,7 @@ export default function createTheme( | Pick< CssVarsThemeOptions, | 'colorSchemeSelector' + | 'rootSelector' | 'disableCssColorScheme' | 'cssVarPrefix' | 'shouldSkipGeneratingVar' diff --git a/packages/mui-material/src/styles/createThemeNoVars.d.ts b/packages/mui-material/src/styles/createThemeNoVars.d.ts index 1b17234a324b27..72a5afee7ae347 100644 --- a/packages/mui-material/src/styles/createThemeNoVars.d.ts +++ b/packages/mui-material/src/styles/createThemeNoVars.d.ts @@ -63,6 +63,7 @@ type CssVarsProperties = CssThemeVariables extends { enabled: true } | 'applyStyles' | 'colorSchemes' | 'colorSchemeSelector' + | 'rootSelector' | 'cssVarPrefix' | 'defaultColorScheme' | 'getCssVar' diff --git a/packages/mui-material/src/styles/createThemeWithVars.d.ts b/packages/mui-material/src/styles/createThemeWithVars.d.ts index 4cc426dda8fe56..a878cfbffdacdb 100644 --- a/packages/mui-material/src/styles/createThemeWithVars.d.ts +++ b/packages/mui-material/src/styles/createThemeWithVars.d.ts @@ -306,6 +306,13 @@ export interface CssVarsThemeOptions extends Omit>; + rootSelector: string; colorSchemeSelector: 'media' | 'class' | 'data' | string; cssVarPrefix: string; defaultColorScheme: SupportedColorScheme; diff --git a/packages/mui-material/src/styles/createThemeWithVars.js b/packages/mui-material/src/styles/createThemeWithVars.js index 026f1b9e6ede5d..1da4352a2315d1 100644 --- a/packages/mui-material/src/styles/createThemeWithVars.js +++ b/packages/mui-material/src/styles/createThemeWithVars.js @@ -132,6 +132,7 @@ export default function createThemeWithVars(options = {}, ...args) { colorSchemeSelector: selector = colorSchemesInput.light && colorSchemesInput.dark ? 'media' : undefined, + rootSelector = ':root', ...input } = options; const firstColorScheme = Object.keys(colorSchemesInput)[0]; @@ -179,6 +180,7 @@ export default function createThemeWithVars(options = {}, ...args) { ...muiTheme, cssVarPrefix, colorSchemeSelector: selector, + rootSelector, getCssVar, colorSchemes, font: { ...prepareTypographyVars(muiTheme.typography), ...muiTheme.font }, diff --git a/packages/mui-material/src/styles/extendTheme.test.js b/packages/mui-material/src/styles/extendTheme.test.js index f90a8f4c922066..6bffb73787a98f 100644 --- a/packages/mui-material/src/styles/extendTheme.test.js +++ b/packages/mui-material/src/styles/extendTheme.test.js @@ -851,5 +851,18 @@ describe('extendTheme', () => { '.mode-light', ]); }); + + it('should use a custom root selector', () => { + const theme = extendTheme({ + colorSchemes: { light: true, dark: true }, + colorSchemeSelector: 'class', + rootSelector: ':host', + }); + expect(theme.generateStyleSheets().flatMap((sheet) => Object.keys(sheet))).to.deep.equal([ + ':host', + ':host, .light', + '.dark', + ]); + }); }); }); diff --git a/packages/mui-material/src/styles/shouldSkipGeneratingVar.ts b/packages/mui-material/src/styles/shouldSkipGeneratingVar.ts index 0bd931028287cd..8b69b98cda490a 100644 --- a/packages/mui-material/src/styles/shouldSkipGeneratingVar.ts +++ b/packages/mui-material/src/styles/shouldSkipGeneratingVar.ts @@ -1,7 +1,7 @@ export default function shouldSkipGeneratingVar(keys: string[]) { return ( !!keys[0].match( - /(cssVarPrefix|colorSchemeSelector|typography|mixins|breakpoints|direction|transitions)/, + /(cssVarPrefix|colorSchemeSelector|rootSelector|typography|mixins|breakpoints|direction|transitions)/, ) || !!keys[0].match(/sxConfig$/) || // ends with sxConfig (keys[0] === 'palette' && !!keys[1]?.match(/(mode|contrastThreshold|tonalOffset)/))