diff --git a/src/material/core/style/BUILD.bazel b/src/material/core/style/BUILD.bazel index 959dc5a7c22f..b861bec22eef 100644 --- a/src/material/core/style/BUILD.bazel +++ b/src/material/core/style/BUILD.bazel @@ -19,6 +19,7 @@ sass_library( deps = [ ":sass_utils", ":variables", + "//src/material/core/tokens:m3_utils", ], ) diff --git a/src/material/core/style/_elevation.scss b/src/material/core/style/_elevation.scss index 5a3d8c98b0ea..bf934a6ea7d7 100644 --- a/src/material/core/style/_elevation.scss +++ b/src/material/core/style/_elevation.scss @@ -4,6 +4,7 @@ @use 'sass:string'; @use './variables'; @use './sass-utils'; +@use '../tokens/m3-utils'; $_umbra-opacity: 0.2; $_penumbra-opacity: 0.14; @@ -199,10 +200,20 @@ $prefix: 'mat-elevation-z'; } @function _compute-color-opacity($color, $opacity) { - @if meta.type-of($color) == color and $opacity != null { + @if $opacity == null { + @return $color; + } + + @if meta.type-of($color) == color { @return rgba($color, $opacity); } - @else { - @return $color; + + // The color can also be the name of a CSS variable (e.g. `--mat-sys-shadow`) whose value isn't + // known at build time. Wrap it in `var()` and apply the opacity using `color-mix`, otherwise + // we'd interpolate the bare variable name into the shadow and produce invalid CSS. + @if sass-utils.is-css-var-name($color) { + @return m3-utils.color-with-opacity($color, $opacity); } + + @return $color; } diff --git a/src/material/core/theming/tests/m3-theme.spec.ts b/src/material/core/theming/tests/m3-theme.spec.ts index eaa524c78f85..b1e2d53e1a74 100644 --- a/src/material/core/theming/tests/m3-theme.spec.ts +++ b/src/material/core/theming/tests/m3-theme.spec.ts @@ -91,6 +91,31 @@ describe('M3 theme', () => { expect(nonVarProps).toEqual([]); }); + it('should emit valid elevation classes when used with the system-level theme', () => { + const root = parse( + transpile(` + html { + @include mat.theme(( + color: mat.$violet-palette, + typography: Roboto, + density: 0, + )); + } + @include mat.elevation-classes(); + `), + ); + const invalidValues: string[] = []; + root.walkDecls('box-shadow', decl => { + // Any reference to a token has to be wrapped in `var()`. A bare token name (e.g. + // `0px 2px 1px -1px --mat-sys-shadow`) is invalid CSS which makes the browser drop the + // entire declaration, resulting in no shadow at all. + if (/(^|[\s,(])--/.test(decl.value.replace(/var\(\s*--/g, 'var('))) { + invalidValues.push(decl.value); + } + }); + expect(invalidValues).toEqual([]); + }); + it('should not have overlapping tokens between theme dimensions', () => { const css = transpile(` $theme: mat.define-theme();