Skip to content

Commit f8dd018

Browse files
authored
fix(material/core): better handling of css variables in theme palettes (#26260)
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.
1 parent 8626a26 commit f8dd018

File tree

7 files changed

+123
-51
lines changed

7 files changed

+123
-51
lines changed

src/material/card/_card-theme.scss

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,35 @@
88
@use '@material/theme/theme-color' as mdc-theme-color;
99
@use 'sass:color';
1010
@use 'sass:map';
11+
@use 'sass:meta';
1112

1213
@mixin color($config-or-theme) {
1314
$config: theming.get-color-config($config-or-theme);
1415
$foreground: map.get($config, foreground);
1516

1617
@include mdc-helpers.using-mdc-theme($config) {
18+
$on-surface: mdc-theme-color.prop-value(on-surface);
19+
$surface: mdc-theme-color.prop-value(surface);
20+
1721
.mat-mdc-card {
1822
// MDC's theme has `container-elevation` and `container-shadow-color` tokens, but we can't
1923
// use them because they output under a `.mdc-card` selector whereas the rest of the theme
2024
// isn't under any selector. Even if the mixin is pulled out of the selector, it throws a
2125
// different error.
2226
@include private.private-theme-elevation(1, $config);
2327
@include mdc-elevated-card-theme.theme((
24-
container-color: mdc-theme-color.prop-value(surface),
28+
container-color: $surface,
2529
));
2630
}
2731

2832
.mat-mdc-card-outlined {
2933
@include private.private-theme-elevation(0, $config);
3034
@include mdc-outlined-card-theme.theme((
31-
outline-color: color.mix(mdc-theme-color.prop-value(on-surface),
32-
mdc-theme-color.prop-value(surface), 12%)
35+
outline-color: if(
36+
meta.type-of($on-surface) == color and meta.type-of($surface) == color,
37+
color.mix($on-surface, $surface, 12%),
38+
$on-surface
39+
)
3340
));
3441
}
3542

src/material/chips/_chips-theme.scss

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@use 'sass:color';
22
@use 'sass:map';
3+
@use 'sass:meta';
34
@use '@material/chips/chip-theme' as mdc-chip-theme;
45
@use '@material/chips/chip-set' as mdc-chip-set;
56
@use '@material/theme/theme-color' as mdc-theme-color;
@@ -42,9 +43,18 @@
4243
$is-dark: map.get($config, is-dark);
4344

4445
@include mdc-helpers.using-mdc-theme($config) {
46+
$on-surface: mdc-theme-color.prop-value(on-surface);
47+
$surface: mdc-theme-color.prop-value(surface);
48+
4549
.mat-mdc-standard-chip {
50+
$standard-background: if(
51+
meta.type-of($on-surface) == color and meta.type-of($surface) == color,
52+
color.mix($on-surface, $surface, 12%),
53+
$on-surface
54+
);
55+
4656
@include _chip-variant(
47-
color.mix(mdc-theme-color.prop-value(on-surface), mdc-theme-color.prop-value(surface), 12%),
57+
$standard-background,
4858
if($is-dark, mdc-color-palette.$grey-50, mdc-color-palette.$grey-900)
4959
);
5060

src/material/core/mdc-helpers/_mdc-helpers.scss

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,29 @@ $mat-typography-mdc-level-mappings: (
9797
);
9898
}
9999

100+
// MDC logs a warning if the `contrast-tone` function is called with a CSS variable.
101+
// This function falls back to determining the tone based on whether the theme is light or dark.
102+
@function _variable-safe-contrast-tone($value, $is-dark) {
103+
@if ($value == 'dark' or $value == 'light' or type-of($value) == 'color') {
104+
@return mdc-theme-color.contrast-tone($value);
105+
}
106+
107+
@return if($is-dark, 'light', 'dark');
108+
}
109+
110+
@function _variable-safe-ink-color-for-fill($text-style, $fill-color, $is-dark) {
111+
$contrast-tone: _variable-safe-contrast-tone($fill-color, $is-dark);
112+
@return map.get(map.get(mdc-theme-color.$text-colors, $contrast-tone), $text-style);
113+
}
114+
100115
// Configures MDC's global variables to reflect the given theme, applies the given styles,
101116
// then resets the global variables to prevent unintended side effects.
102117
@mixin using-mdc-theme($config) {
103118
$primary: theming.get-color-from-palette(map.get($config, primary));
104119
$accent: theming.get-color-from-palette(map.get($config, accent));
105120
$warn: theming.get-color-from-palette(map.get($config, warn));
106121
$background-palette: map.get($config, background);
122+
$is-dark: map.get($config, is-dark);
107123

108124
// Save the original values.
109125
$orig-primary: mdc-theme-color.$primary;
@@ -120,17 +136,17 @@ $mat-typography-mdc-level-mappings: (
120136
// Set new values based on the given Angular Material theme.
121137
mdc-theme-color.$primary: $primary;
122138
mdc-theme-color.$on-primary:
123-
if(mdc-theme-color.contrast-tone(mdc-theme-color.$primary) == 'dark', #000, #fff);
139+
if(_variable-safe-contrast-tone(mdc-theme-color.$primary, $is-dark) == 'dark', #000, #fff);
124140
mdc-theme-color.$secondary: $accent;
125141
mdc-theme-color.$on-secondary:
126-
if(mdc-theme-color.contrast-tone(mdc-theme-color.$secondary) == 'dark', #000, #fff);
142+
if(_variable-safe-contrast-tone(mdc-theme-color.$secondary, $is-dark) == 'dark', #000, #fff);
127143
mdc-theme-color.$background: theming.get-color-from-palette($background-palette, background);
128144
mdc-theme-color.$surface: theming.get-color-from-palette($background-palette, card);
129145
mdc-theme-color.$on-surface:
130-
if(mdc-theme-color.contrast-tone(mdc-theme-color.$surface) == 'dark', #000, #fff);
146+
if(_variable-safe-contrast-tone(mdc-theme-color.$surface, $is-dark) == 'dark', #000, #fff);
131147
mdc-theme-color.$error: $warn;
132148
mdc-theme-color.$on-error:
133-
if(mdc-theme-color.contrast-tone(mdc-theme-color.$error) == 'dark', #000, #fff);
149+
if(_variable-safe-contrast-tone(mdc-theme-color.$error, $is-dark) == 'dark', #000, #fff);
134150
mdc-theme-color.$property-values: (
135151
// Primary
136152
primary: mdc-theme-color.$primary,
@@ -148,27 +164,27 @@ $mat-typography-mdc-level-mappings: (
148164
on-error: mdc-theme-color.$on-error,
149165
// Text-primary on "background" background
150166
text-primary-on-background:
151-
mdc-theme-color.ink-color-for-fill_(primary, mdc-theme-color.$background),
167+
_variable-safe-ink-color-for-fill(primary, mdc-theme-color.$background, $is-dark),
152168
text-secondary-on-background:
153-
mdc-theme-color.ink-color-for-fill_(secondary, mdc-theme-color.$background),
169+
_variable-safe-ink-color-for-fill(secondary, mdc-theme-color.$background, $is-dark),
154170
text-hint-on-background:
155-
mdc-theme-color.ink-color-for-fill_(hint, mdc-theme-color.$background),
171+
_variable-safe-ink-color-for-fill(hint, mdc-theme-color.$background, $is-dark),
156172
text-disabled-on-background:
157-
mdc-theme-color.ink-color-for-fill_(disabled, mdc-theme-color.$background),
173+
_variable-safe-ink-color-for-fill(disabled, mdc-theme-color.$background, $is-dark),
158174
text-icon-on-background:
159-
mdc-theme-color.ink-color-for-fill_(icon, mdc-theme-color.$background),
175+
_variable-safe-ink-color-for-fill(icon, mdc-theme-color.$background, $is-dark),
160176
// Text-primary on "light" background
161-
text-primary-on-light: mdc-theme-color.ink-color-for-fill_(primary, light),
162-
text-secondary-on-light: mdc-theme-color.ink-color-for-fill_(secondary, light),
163-
text-hint-on-light: mdc-theme-color.ink-color-for-fill_(hint, light),
164-
text-disabled-on-light: mdc-theme-color.ink-color-for-fill_(disabled, light),
165-
text-icon-on-light: mdc-theme-color.ink-color-for-fill_(icon, light),
177+
text-primary-on-light: _variable-safe-ink-color-for-fill(primary, light, $is-dark),
178+
text-secondary-on-light: _variable-safe-ink-color-for-fill(secondary, light, $is-dark),
179+
text-hint-on-light: _variable-safe-ink-color-for-fill(hint, light, $is-dark),
180+
text-disabled-on-light: _variable-safe-ink-color-for-fill(disabled, light, $is-dark),
181+
text-icon-on-light: _variable-safe-ink-color-for-fill(icon, light, $is-dark),
166182
// Text-primary on "dark" background
167-
text-primary-on-dark: mdc-theme-color.ink-color-for-fill_(primary, dark),
168-
text-secondary-on-dark: mdc-theme-color.ink-color-for-fill_(secondary, dark),
169-
text-hint-on-dark: mdc-theme-color.ink-color-for-fill_(hint, dark),
170-
text-disabled-on-dark: mdc-theme-color.ink-color-for-fill_(disabled, dark),
171-
text-icon-on-dark: mdc-theme-color.ink-color-for-fill_(icon, dark)
183+
text-primary-on-dark: _variable-safe-ink-color-for-fill(primary, dark, $is-dark),
184+
text-secondary-on-dark: _variable-safe-ink-color-for-fill(secondary, dark, $is-dark),
185+
text-hint-on-dark: _variable-safe-ink-color-for-fill(hint, dark, $is-dark),
186+
text-disabled-on-dark: _variable-safe-ink-color-for-fill(disabled, dark, $is-dark),
187+
text-icon-on-dark: _variable-safe-ink-color-for-fill(icon, dark, $is-dark)
172188
);
173189

174190
// Apply given rules.

src/material/core/theming/tests/test-css-variables-theme.scss

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
@use 'sass:map';
22
@use 'sass:meta';
33
@use '../all-theme';
4+
@use '../../typography/all-typography';
45
@use '../palette';
56
@use '../theming';
67
@use '../../../legacy-core/theming/all-theme' as legacy-all-theme;
78

89
// Recursively replaces all of the values inside a Sass map with a different value.
9-
@function replace-all-values($palette, $replacement) {
10+
@function _replace-all-values($palette, $replacement) {
1011
$output: ();
1112

1213
@each $key, $value in $palette {
1314
@if (meta.type-of($value) == 'map') {
14-
$output: map.merge(($key: replace-all-values($value, $replacement)), $output);
15+
$output: map.merge(($key: _replace-all-values($value, $replacement)), $output);
1516
}
1617
@else {
1718
$output: map.merge(($key: $replacement), $output);
@@ -29,8 +30,10 @@
2930
primary: $palette,
3031
accent: $palette,
3132
warn: $palette
32-
)
33+
),
34+
typography: all-typography.define-typography-config(),
3335
));
34-
$css-var-theme: replace-all-values($theme, var(--test-var));
36+
$css-var-theme: _replace-all-values($theme, var(--test-var));
37+
@include all-theme.all-component-themes($css-var-theme);
3538
@include legacy-all-theme.all-legacy-component-themes($css-var-theme);
3639
}
Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,56 @@
11
@use '@material/textfield' as mdc-textfield;
22
@use '@material/theme/variables' as mdc-theme-variables;
33
@use 'sass:color';
4+
@use 'sass:meta';
45

56
// Mixin that refreshes the MDC text-field theming variables. This mixin should be used when
67
// the base MDC theming variables have been explicitly updated, but the component specific
78
// theming-based variables are still based on the old MDC base theming variables. The mixin
89
// restores the previous values for the variables to avoid unexpected global side effects.
910
@mixin private-text-field-refresh-theme-variables() {
1011
$_disabled-border: mdc-textfield.$disabled-border;
11-
mdc-textfield.$disabled-border: rgba(mdc-theme-variables.prop-value(on-surface), 0.06);
12+
mdc-textfield.$disabled-border:
13+
_variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.06);
1214
$_bottom-line-hover: mdc-textfield.$bottom-line-hover;
13-
mdc-textfield.$bottom-line-hover: rgba(mdc-theme-variables.prop-value(on-surface), 0.87);
15+
mdc-textfield.$bottom-line-hover:
16+
_variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.87);
1417
$_bottom-line-idle: mdc-textfield.$bottom-line-idle;
15-
mdc-textfield.$bottom-line-idle: rgba(mdc-theme-variables.prop-value(on-surface), 0.42);
18+
mdc-textfield.$bottom-line-idle:
19+
_variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.42);
1620
$_label: mdc-textfield.$label;
17-
mdc-textfield.$label: rgba(mdc-theme-variables.prop-value(on-surface), 0.6);
21+
mdc-textfield.$label: _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.6);
1822
$_ink-color: mdc-textfield.$ink-color;
19-
mdc-textfield.$ink-color: rgba(mdc-theme-variables.prop-value(on-surface), 0.87);
23+
mdc-textfield.$ink-color: _variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.87);
2024
$_focused-label-color: mdc-textfield.$focused-label-color;
21-
mdc-textfield.$focused-label-color: rgba(mdc-theme-variables.prop-value(primary), 0.87);
25+
mdc-textfield.$focused-label-color:
26+
_variable-safe-rgba(mdc-theme-variables.prop-value(primary), 0.87);
2227
$_placeholder-ink-color: mdc-textfield.$placeholder-ink-color;
23-
mdc-textfield.$placeholder-ink-color: rgba(mdc-theme-variables.prop-value(on-surface), 0.6);
28+
mdc-textfield.$placeholder-ink-color:
29+
_variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.6);
2430
$_disabled-label-color: mdc-textfield.$disabled-label-color;
25-
mdc-textfield.$disabled-label-color: rgba(mdc-theme-variables.prop-value(on-surface), 0.38);
31+
mdc-textfield.$disabled-label-color:
32+
_variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.38);
2633
$_disabled-ink-color: mdc-textfield.$disabled-ink-color;
27-
mdc-textfield.$disabled-ink-color: rgba(mdc-theme-variables.prop-value(on-surface), 0.38);
34+
mdc-textfield.$disabled-ink-color:
35+
_variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.38);
2836
$_disabled-placeholder-ink-color: mdc-textfield.$disabled-placeholder-ink-color;
2937
mdc-textfield.$disabled-placeholder-ink-color:
30-
rgba(mdc-theme-variables.prop-value(on-surface), 0.38);
38+
_variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.38);
3139
$_background: mdc-textfield.$background;
32-
mdc-textfield.$background: color.mix(mdc-theme-variables.prop-value(on-surface),
40+
mdc-textfield.$background: _variable-safe-mix(mdc-theme-variables.prop-value(on-surface),
3341
mdc-theme-variables.prop-value(surface), 4%);
3442
$_disabled-background: mdc-textfield.$disabled-background;
35-
mdc-textfield.$disabled-background: color.mix(mdc-theme-variables.prop-value(on-surface),
43+
mdc-textfield.$disabled-background: _variable-safe-mix(mdc-theme-variables.prop-value(on-surface),
3644
mdc-theme-variables.prop-value(surface), 2%);
3745
$_outlined-idle-border: mdc-textfield.$outlined-idle-border;
38-
mdc-textfield.$outlined-idle-border: rgba(mdc-theme-variables.prop-value(on-surface), 0.38);
46+
mdc-textfield.$outlined-idle-border:
47+
_variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.38);
3948
$_outlined-disabled-border: mdc-textfield.$outlined-disabled-border;
40-
mdc-textfield.$outlined-disabled-border: rgba(mdc-theme-variables.prop-value(on-surface), 0.06);
49+
mdc-textfield.$outlined-disabled-border:
50+
_variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.06);
4151
$_outlined-hover-border: mdc-textfield.$outlined-hover-border;
42-
mdc-textfield.$outlined-hover-border: rgba(mdc-theme-variables.prop-value(on-surface), 0.87);
52+
mdc-textfield.$outlined-hover-border:
53+
_variable-safe-rgba(mdc-theme-variables.prop-value(on-surface), 0.87);
4354

4455
// The content will be generated with the refreshed MDC text-field theming variables.
4556
@content;
@@ -61,3 +72,17 @@
6172
mdc-textfield.$outlined-disabled-border: $_outlined-disabled-border;
6273
mdc-textfield.$outlined-hover-border: $_outlined-hover-border;
6374
}
75+
76+
@function _variable-safe-rgba($color, $opacity) {
77+
@if (meta.type-of($color) == color) {
78+
@return rgba($color, $opacity);
79+
}
80+
@return $color;
81+
}
82+
83+
@function _variable-safe-mix($first-color, $second-color, $amount) {
84+
@if (meta.type-of($first-color) == color and meta.type-of($second-color) == color) {
85+
@return color.mix($first-color, $second-color, $amount);
86+
}
87+
@return $first-color;
88+
}

src/material/progress-bar/_progress-bar-theme.scss

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,28 @@
33
@use '@material/theme/theme-color' as mdc-theme-color;
44
@use '@material/linear-progress/linear-progress-theme' as mdc-linear-progress-theme;
55
@use 'sass:color';
6+
@use 'sass:meta';
67

78

89
@mixin _palette-styles($color) {
10+
$color-value: mdc-theme-color.prop-value($color);
11+
912
// We can't set the `track-color` using `theme`, because it isn't possible for it to use a CSS
1013
// variable since MDC's buffer animation works by constructing an SVG string from this color.
1114
@include mdc-linear-progress-theme.theme-styles((
1215
// TODO(crisbeto): the buffer color should come from somewhere in MDC, however at the time of
1316
// writing, their buffer color is hardcoded to #e6e6e6 which both doesn't account for theming
1417
// and doesn't match the Material design spec. For now we approximate the buffer background by
1518
// applying an opacity to the color of the bar.
16-
track-color: color.adjust(mdc-theme-color.prop-value($color), $alpha: -0.75),
19+
track-color: if(
20+
meta.type-of($color-value) == color,
21+
color.adjust($color-value, $alpha: -0.75),
22+
$color-value
23+
),
1724
));
1825

1926
@include mdc-linear-progress-theme.theme((
20-
active-indicator-color: mdc-theme-color.prop-value($color),
27+
active-indicator-color: $color-value,
2128
));
2229
}
2330

src/material/snack-bar/_snack-bar-theme.scss

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
@use '@material/snackbar/snackbar-theme' as mdc-snackbar-theme;
77
@use 'sass:color';
88
@use 'sass:map';
9+
@use 'sass:meta';
910

1011

1112
@mixin color($config-or-theme) {
@@ -18,16 +19,19 @@
1819
$button-color:
1920
if($is-dark-theme, currentColor, theming.get-color-from-palette($accent, text));
2021
--mat-mdc-snack-bar-button-color: #{$button-color};
22+
$on-surface: mdc-theme-color.prop-value(on-surface);
23+
$surface: mdc-theme-color.prop-value(surface);
2124

2225
@include mdc-snackbar-theme.theme((
23-
container-color: color.mix(
24-
mdc-theme-color.prop-value(on-surface),
25-
mdc-theme-color.prop-value(surface),
26-
80%
26+
container-color: if(
27+
meta.type-of($on-surface) == color and meta.type-of($surface) == color,
28+
color.mix($on-surface, $surface, 80%),
29+
$on-surface
2730
),
28-
supporting-text-color: rgba(
29-
mdc-theme-color.prop-value(surface),
30-
mdc-theme-color.text-emphasis(high)
31+
supporting-text-color: if(
32+
meta.type-of($surface) == color,
33+
rgba($surface, mdc-theme-color.text-emphasis(high)),
34+
$surface
3135
)
3236
));
3337
}

0 commit comments

Comments
 (0)