From cd95c32a2b95ff5a32d06824160944b70acc5e1c Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 14 Dec 2022 12:30:27 +0100 Subject: [PATCH] fix(material/core): better handling of css variables in theme palettes Technically we don't support theming using CSS variables, but the community has started depending on it anyway since it happened to work. This was broken during the switch to MDC which is blocking users from updating to v15. These changes make a best effort to avoid all the errors and reduce the amount of warnings as much as possible. A couple of disclaimers: * All the places where we were manipulating colors are going to compile now, but they might not look great. This is only going to affect some MDC components though, the legacy components will work as expected. * One warning might be logged from the themes for components based on MDC's list. They will compile and look acceptable, but I couldn't find a good way to avoid the error with our current setup. We will be able to avoid the warning once we switch the components to the token theming APIs. Relates to #25981. --- src/material/card/_card-theme.scss | 13 ++++- src/material/chips/_chips-theme.scss | 12 +++- .../core/mdc-helpers/_mdc-helpers.scss | 54 +++++++++++------- .../tests/test-css-variables-theme.scss | 11 ++-- ...mdc-text-field-theme-variable-refresh.scss | 55 ++++++++++++++----- .../progress-bar/_progress-bar-theme.scss | 11 +++- src/material/snack-bar/_snack-bar-theme.scss | 18 +++--- 7 files changed, 123 insertions(+), 51 deletions(-) diff --git a/src/material/card/_card-theme.scss b/src/material/card/_card-theme.scss index 8b9b320c1b2a..c667b81d881f 100644 --- a/src/material/card/_card-theme.scss +++ b/src/material/card/_card-theme.scss @@ -8,12 +8,16 @@ @use '@material/theme/theme-color' as mdc-theme-color; @use 'sass:color'; @use 'sass:map'; +@use 'sass:meta'; @mixin color($config-or-theme) { $config: theming.get-color-config($config-or-theme); $foreground: map.get($config, foreground); @include mdc-helpers.using-mdc-theme($config) { + $on-surface: mdc-theme-color.prop-value(on-surface); + $surface: mdc-theme-color.prop-value(surface); + .mat-mdc-card { // MDC's theme has `container-elevation` and `container-shadow-color` tokens, but we can't // use them because they output under a `.mdc-card` selector whereas the rest of the theme @@ -21,15 +25,18 @@ // different error. @include private.private-theme-elevation(1, $config); @include mdc-elevated-card-theme.theme(( - container-color: mdc-theme-color.prop-value(surface), + container-color: $surface, )); } .mat-mdc-card-outlined { @include private.private-theme-elevation(0, $config); @include mdc-outlined-card-theme.theme(( - outline-color: color.mix(mdc-theme-color.prop-value(on-surface), - mdc-theme-color.prop-value(surface), 12%) + outline-color: if( + meta.type-of($on-surface) == color and meta.type-of($surface) == color, + color.mix($on-surface, $surface, 12%), + $on-surface + ) )); } diff --git a/src/material/chips/_chips-theme.scss b/src/material/chips/_chips-theme.scss index 65ae18053096..49354e533288 100644 --- a/src/material/chips/_chips-theme.scss +++ b/src/material/chips/_chips-theme.scss @@ -1,5 +1,6 @@ @use 'sass:color'; @use 'sass:map'; +@use 'sass:meta'; @use '@material/chips/chip-theme' as mdc-chip-theme; @use '@material/chips/chip-set' as mdc-chip-set; @use '@material/theme/theme-color' as mdc-theme-color; @@ -42,9 +43,18 @@ $is-dark: map.get($config, is-dark); @include mdc-helpers.using-mdc-theme($config) { + $on-surface: mdc-theme-color.prop-value(on-surface); + $surface: mdc-theme-color.prop-value(surface); + .mat-mdc-standard-chip { + $standard-background: if( + meta.type-of($on-surface) == color and meta.type-of($surface) == color, + color.mix($on-surface, $surface, 12%), + $on-surface + ); + @include _chip-variant( - color.mix(mdc-theme-color.prop-value(on-surface), mdc-theme-color.prop-value(surface), 12%), + $standard-background, if($is-dark, mdc-color-palette.$grey-50, mdc-color-palette.$grey-900) ); diff --git a/src/material/core/mdc-helpers/_mdc-helpers.scss b/src/material/core/mdc-helpers/_mdc-helpers.scss index e6b2320fcc71..eced4eea4d6a 100644 --- a/src/material/core/mdc-helpers/_mdc-helpers.scss +++ b/src/material/core/mdc-helpers/_mdc-helpers.scss @@ -97,6 +97,21 @@ $mat-typography-mdc-level-mappings: ( ); } +// MDC logs a warning if the `contrast-tone` function is called with a CSS variable. +// This function falls back to determining the tone based on whether the theme is light or dark. +@function _variable-safe-contrast-tone($value, $is-dark) { + @if ($value == 'dark' or $value == 'light' or type-of($value) == 'color') { + @return mdc-theme-color.contrast-tone($value); + } + + @return if($is-dark, 'light', 'dark'); +} + +@function _variable-safe-ink-color-for-fill($text-style, $fill-color, $is-dark) { + $contrast-tone: _variable-safe-contrast-tone($fill-color, $is-dark); + @return map.get(map.get(mdc-theme-color.$text-colors, $contrast-tone), $text-style); +} + // Configures MDC's global variables to reflect the given theme, applies the given styles, // then resets the global variables to prevent unintended side effects. @mixin using-mdc-theme($config) { @@ -104,6 +119,7 @@ $mat-typography-mdc-level-mappings: ( $accent: theming.get-color-from-palette(map.get($config, accent)); $warn: theming.get-color-from-palette(map.get($config, warn)); $background-palette: map.get($config, background); + $is-dark: map.get($config, is-dark); // Save the original values. $orig-primary: mdc-theme-color.$primary; @@ -120,17 +136,17 @@ $mat-typography-mdc-level-mappings: ( // Set new values based on the given Angular Material theme. mdc-theme-color.$primary: $primary; mdc-theme-color.$on-primary: - if(mdc-theme-color.contrast-tone(mdc-theme-color.$primary) == 'dark', #000, #fff); + if(_variable-safe-contrast-tone(mdc-theme-color.$primary, $is-dark) == 'dark', #000, #fff); mdc-theme-color.$secondary: $accent; mdc-theme-color.$on-secondary: - if(mdc-theme-color.contrast-tone(mdc-theme-color.$secondary) == 'dark', #000, #fff); + if(_variable-safe-contrast-tone(mdc-theme-color.$secondary, $is-dark) == 'dark', #000, #fff); mdc-theme-color.$background: theming.get-color-from-palette($background-palette, background); mdc-theme-color.$surface: theming.get-color-from-palette($background-palette, card); mdc-theme-color.$on-surface: - if(mdc-theme-color.contrast-tone(mdc-theme-color.$surface) == 'dark', #000, #fff); + if(_variable-safe-contrast-tone(mdc-theme-color.$surface, $is-dark) == 'dark', #000, #fff); mdc-theme-color.$error: $warn; mdc-theme-color.$on-error: - if(mdc-theme-color.contrast-tone(mdc-theme-color.$error) == 'dark', #000, #fff); + if(_variable-safe-contrast-tone(mdc-theme-color.$error, $is-dark) == 'dark', #000, #fff); mdc-theme-color.$property-values: ( // Primary primary: mdc-theme-color.$primary, @@ -148,27 +164,27 @@ $mat-typography-mdc-level-mappings: ( on-error: mdc-theme-color.$on-error, // Text-primary on "background" background text-primary-on-background: - mdc-theme-color.ink-color-for-fill_(primary, mdc-theme-color.$background), + _variable-safe-ink-color-for-fill(primary, mdc-theme-color.$background, $is-dark), text-secondary-on-background: - mdc-theme-color.ink-color-for-fill_(secondary, mdc-theme-color.$background), + _variable-safe-ink-color-for-fill(secondary, mdc-theme-color.$background, $is-dark), text-hint-on-background: - mdc-theme-color.ink-color-for-fill_(hint, mdc-theme-color.$background), + _variable-safe-ink-color-for-fill(hint, mdc-theme-color.$background, $is-dark), text-disabled-on-background: - mdc-theme-color.ink-color-for-fill_(disabled, mdc-theme-color.$background), + _variable-safe-ink-color-for-fill(disabled, mdc-theme-color.$background, $is-dark), text-icon-on-background: - mdc-theme-color.ink-color-for-fill_(icon, mdc-theme-color.$background), + _variable-safe-ink-color-for-fill(icon, mdc-theme-color.$background, $is-dark), // Text-primary on "light" background - text-primary-on-light: mdc-theme-color.ink-color-for-fill_(primary, light), - text-secondary-on-light: mdc-theme-color.ink-color-for-fill_(secondary, light), - text-hint-on-light: mdc-theme-color.ink-color-for-fill_(hint, light), - text-disabled-on-light: mdc-theme-color.ink-color-for-fill_(disabled, light), - text-icon-on-light: mdc-theme-color.ink-color-for-fill_(icon, light), + text-primary-on-light: _variable-safe-ink-color-for-fill(primary, light, $is-dark), + text-secondary-on-light: _variable-safe-ink-color-for-fill(secondary, light, $is-dark), + text-hint-on-light: _variable-safe-ink-color-for-fill(hint, light, $is-dark), + text-disabled-on-light: _variable-safe-ink-color-for-fill(disabled, light, $is-dark), + text-icon-on-light: _variable-safe-ink-color-for-fill(icon, light, $is-dark), // Text-primary on "dark" background - text-primary-on-dark: mdc-theme-color.ink-color-for-fill_(primary, dark), - text-secondary-on-dark: mdc-theme-color.ink-color-for-fill_(secondary, dark), - text-hint-on-dark: mdc-theme-color.ink-color-for-fill_(hint, dark), - text-disabled-on-dark: mdc-theme-color.ink-color-for-fill_(disabled, dark), - text-icon-on-dark: mdc-theme-color.ink-color-for-fill_(icon, dark) + text-primary-on-dark: _variable-safe-ink-color-for-fill(primary, dark, $is-dark), + text-secondary-on-dark: _variable-safe-ink-color-for-fill(secondary, dark, $is-dark), + text-hint-on-dark: _variable-safe-ink-color-for-fill(hint, dark, $is-dark), + text-disabled-on-dark: _variable-safe-ink-color-for-fill(disabled, dark, $is-dark), + text-icon-on-dark: _variable-safe-ink-color-for-fill(icon, dark, $is-dark) ); // Apply given rules. diff --git a/src/material/core/theming/tests/test-css-variables-theme.scss b/src/material/core/theming/tests/test-css-variables-theme.scss index 3598d81dc422..0222b8b55200 100644 --- a/src/material/core/theming/tests/test-css-variables-theme.scss +++ b/src/material/core/theming/tests/test-css-variables-theme.scss @@ -1,17 +1,18 @@ @use 'sass:map'; @use 'sass:meta'; @use '../all-theme'; +@use '../../typography/all-typography'; @use '../palette'; @use '../theming'; @use '../../../legacy-core/theming/all-theme' as legacy-all-theme; // Recursively replaces all of the values inside a Sass map with a different value. -@function replace-all-values($palette, $replacement) { +@function _replace-all-values($palette, $replacement) { $output: (); @each $key, $value in $palette { @if (meta.type-of($value) == 'map') { - $output: map.merge(($key: replace-all-values($value, $replacement)), $output); + $output: map.merge(($key: _replace-all-values($value, $replacement)), $output); } @else { $output: map.merge(($key: $replacement), $output); @@ -29,8 +30,10 @@ primary: $palette, accent: $palette, warn: $palette - ) + ), + typography: all-typography.define-typography-config(), )); - $css-var-theme: replace-all-values($theme, var(--test-var)); + $css-var-theme: _replace-all-values($theme, var(--test-var)); + @include all-theme.all-component-themes($css-var-theme); @include legacy-all-theme.all-legacy-component-themes($css-var-theme); } diff --git a/src/material/form-field/_mdc-text-field-theme-variable-refresh.scss b/src/material/form-field/_mdc-text-field-theme-variable-refresh.scss index bed2445556bf..bb85e7a9a15a 100644 --- a/src/material/form-field/_mdc-text-field-theme-variable-refresh.scss +++ b/src/material/form-field/_mdc-text-field-theme-variable-refresh.scss @@ -1,6 +1,7 @@ @use '@material/textfield' as mdc-textfield; @use '@material/theme/variables' as mdc-theme-variables; @use 'sass:color'; +@use 'sass:meta'; // Mixin that refreshes the MDC text-field theming variables. This mixin should be used when // the base MDC theming variables have been explicitly updated, but the component specific @@ -8,38 +9,48 @@ // restores the previous values for the variables to avoid unexpected global side effects. @mixin private-text-field-refresh-theme-variables() { $_disabled-border: mdc-textfield.$disabled-border; - mdc-textfield.$disabled-border: rgba(mdc-theme-variables.prop-value(on-surface), 0.06); + mdc-textfield.$disabled-border: + _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.06); $_bottom-line-hover: mdc-textfield.$bottom-line-hover; - mdc-textfield.$bottom-line-hover: rgba(mdc-theme-variables.prop-value(on-surface), 0.87); + mdc-textfield.$bottom-line-hover: + _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.87); $_bottom-line-idle: mdc-textfield.$bottom-line-idle; - mdc-textfield.$bottom-line-idle: rgba(mdc-theme-variables.prop-value(on-surface), 0.42); + mdc-textfield.$bottom-line-idle: + _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.42); $_label: mdc-textfield.$label; - mdc-textfield.$label: rgba(mdc-theme-variables.prop-value(on-surface), 0.6); + mdc-textfield.$label: _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.6); $_ink-color: mdc-textfield.$ink-color; - mdc-textfield.$ink-color: rgba(mdc-theme-variables.prop-value(on-surface), 0.87); + mdc-textfield.$ink-color: _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.87); $_focused-label-color: mdc-textfield.$focused-label-color; - mdc-textfield.$focused-label-color: rgba(mdc-theme-variables.prop-value(primary), 0.87); + mdc-textfield.$focused-label-color: + _variable-safe-rgba(mdc-theme-variables.prop-value(primary), 0.87); $_placeholder-ink-color: mdc-textfield.$placeholder-ink-color; - mdc-textfield.$placeholder-ink-color: rgba(mdc-theme-variables.prop-value(on-surface), 0.6); + mdc-textfield.$placeholder-ink-color: + _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.6); $_disabled-label-color: mdc-textfield.$disabled-label-color; - mdc-textfield.$disabled-label-color: rgba(mdc-theme-variables.prop-value(on-surface), 0.38); + mdc-textfield.$disabled-label-color: + _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.38); $_disabled-ink-color: mdc-textfield.$disabled-ink-color; - mdc-textfield.$disabled-ink-color: rgba(mdc-theme-variables.prop-value(on-surface), 0.38); + mdc-textfield.$disabled-ink-color: + _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.38); $_disabled-placeholder-ink-color: mdc-textfield.$disabled-placeholder-ink-color; mdc-textfield.$disabled-placeholder-ink-color: - rgba(mdc-theme-variables.prop-value(on-surface), 0.38); + _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.38); $_background: mdc-textfield.$background; - mdc-textfield.$background: color.mix(mdc-theme-variables.prop-value(on-surface), + mdc-textfield.$background: _variable-safe-mix(mdc-theme-variables.prop-value(on-surface), mdc-theme-variables.prop-value(surface), 4%); $_disabled-background: mdc-textfield.$disabled-background; - mdc-textfield.$disabled-background: color.mix(mdc-theme-variables.prop-value(on-surface), + mdc-textfield.$disabled-background: _variable-safe-mix(mdc-theme-variables.prop-value(on-surface), mdc-theme-variables.prop-value(surface), 2%); $_outlined-idle-border: mdc-textfield.$outlined-idle-border; - mdc-textfield.$outlined-idle-border: rgba(mdc-theme-variables.prop-value(on-surface), 0.38); + mdc-textfield.$outlined-idle-border: + _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.38); $_outlined-disabled-border: mdc-textfield.$outlined-disabled-border; - mdc-textfield.$outlined-disabled-border: rgba(mdc-theme-variables.prop-value(on-surface), 0.06); + mdc-textfield.$outlined-disabled-border: + _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.06); $_outlined-hover-border: mdc-textfield.$outlined-hover-border; - mdc-textfield.$outlined-hover-border: rgba(mdc-theme-variables.prop-value(on-surface), 0.87); + mdc-textfield.$outlined-hover-border: + _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.87); // The content will be generated with the refreshed MDC text-field theming variables. @content; @@ -61,3 +72,17 @@ mdc-textfield.$outlined-disabled-border: $_outlined-disabled-border; mdc-textfield.$outlined-hover-border: $_outlined-hover-border; } + +@function _variable-safe-rgba($color, $opacity) { + @if (meta.type-of($color) == color) { + @return rgba($color, $opacity); + } + @return $color; +} + +@function _variable-safe-mix($first-color, $second-color, $amount) { + @if (meta.type-of($first-color) == color and meta.type-of($second-color) == color) { + @return color.mix($first-color, $second-color, $amount); + } + @return $first-color; +} diff --git a/src/material/progress-bar/_progress-bar-theme.scss b/src/material/progress-bar/_progress-bar-theme.scss index abac1a5011bd..6cd97f617199 100644 --- a/src/material/progress-bar/_progress-bar-theme.scss +++ b/src/material/progress-bar/_progress-bar-theme.scss @@ -3,9 +3,12 @@ @use '@material/theme/theme-color' as mdc-theme-color; @use '@material/linear-progress/linear-progress-theme' as mdc-linear-progress-theme; @use 'sass:color'; +@use 'sass:meta'; @mixin _palette-styles($color) { + $color-value: mdc-theme-color.prop-value($color); + // We can't set the `track-color` using `theme`, because it isn't possible for it to use a CSS // variable since MDC's buffer animation works by constructing an SVG string from this color. @include mdc-linear-progress-theme.theme-styles(( @@ -13,11 +16,15 @@ // writing, their buffer color is hardcoded to #e6e6e6 which both doesn't account for theming // and doesn't match the Material design spec. For now we approximate the buffer background by // applying an opacity to the color of the bar. - track-color: color.adjust(mdc-theme-color.prop-value($color), $alpha: -0.75), + track-color: if( + meta.type-of($color-value) == color, + color.adjust($color-value, $alpha: -0.75), + $color-value + ), )); @include mdc-linear-progress-theme.theme(( - active-indicator-color: mdc-theme-color.prop-value($color), + active-indicator-color: $color-value, )); } diff --git a/src/material/snack-bar/_snack-bar-theme.scss b/src/material/snack-bar/_snack-bar-theme.scss index cb0e2e1a37ee..d54e31a122f6 100644 --- a/src/material/snack-bar/_snack-bar-theme.scss +++ b/src/material/snack-bar/_snack-bar-theme.scss @@ -6,6 +6,7 @@ @use '@material/snackbar/snackbar-theme' as mdc-snackbar-theme; @use 'sass:color'; @use 'sass:map'; +@use 'sass:meta'; @mixin color($config-or-theme) { @@ -18,16 +19,19 @@ $button-color: if($is-dark-theme, currentColor, theming.get-color-from-palette($accent, text)); --mat-mdc-snack-bar-button-color: #{$button-color}; + $on-surface: mdc-theme-color.prop-value(on-surface); + $surface: mdc-theme-color.prop-value(surface); @include mdc-snackbar-theme.theme(( - container-color: color.mix( - mdc-theme-color.prop-value(on-surface), - mdc-theme-color.prop-value(surface), - 80% + container-color: if( + meta.type-of($on-surface) == color and meta.type-of($surface) == color, + color.mix($on-surface, $surface, 80%), + $on-surface ), - supporting-text-color: rgba( - mdc-theme-color.prop-value(surface), - mdc-theme-color.text-emphasis(high) + supporting-text-color: if( + meta.type-of($surface) == color, + rgba($surface, mdc-theme-color.text-emphasis(high)), + $surface ) )); }