Skip to content

Commit

Permalink
fix(material/core): better handling of css variables in theme palettes
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
crisbeto committed Dec 14, 2022
1 parent 49749fd commit 58f0066
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 51 deletions.
13 changes: 10 additions & 3 deletions src/material/card/_card-theme.scss
Expand Up @@ -8,28 +8,35 @@
@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
// isn't under any selector. Even if the mixin is pulled out of the selector, it throws a
// 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
)
));
}

Expand Down
12 changes: 11 additions & 1 deletion 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;
Expand Down Expand Up @@ -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)
);

Expand Down
54 changes: 35 additions & 19 deletions src/material/core/mdc-helpers/_mdc-helpers.scss
Expand Up @@ -97,13 +97,29 @@ $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 (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) {
$primary: theming.get-color-from-palette(map.get($config, primary));
$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;
Expand All @@ -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,
Expand All @@ -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.
Expand Down
11 changes: 7 additions & 4 deletions 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);
Expand All @@ -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);
}
55 changes: 40 additions & 15 deletions src/material/form-field/_mdc-text-field-theme-variable-refresh.scss
@@ -1,45 +1,56 @@
@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
// theming-based variables are still based on the old MDC base theming variables. The mixin
// 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;
Expand All @@ -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;
}
11 changes: 9 additions & 2 deletions src/material/progress-bar/_progress-bar-theme.scss
Expand Up @@ -3,21 +3,28 @@
@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((
// TODO(crisbeto): the buffer color should come from somewhere in MDC, however at the time of
// 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,
));
}

Expand Down
18 changes: 11 additions & 7 deletions src/material/snack-bar/_snack-bar-theme.scss
Expand Up @@ -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) {
Expand All @@ -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
)
));
}
Expand Down

0 comments on commit 58f0066

Please sign in to comment.