Skip to content

Commit b2f1710

Browse files
committed
fix(material-experimental/theming): Add more tests for M3 theme tokens
Additional tests that help ensure our M3 theme conforms to the following rules: - Does not add any additional selector specificity - Emits only CSS variable definitions - Emits each token for one and only one theme dimension Also fixes several existing bugs that were discovered by the new tests.
1 parent f1e82da commit b2f1710

File tree

11 files changed

+139
-38
lines changed

11 files changed

+139
-38
lines changed

src/material-experimental/theming/_custom-tokens.scss

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@
7070
small-size-container-size: _hardcode(6px, $exclude-hardcoded),
7171
container-padding: _hardcode(0 4px, $exclude-hardcoded),
7272
small-size-container-padding: _hardcode(0, $exclude-hardcoded),
73-
container-offset: -12px 0,
74-
small-size-container-offset: -6px 0,
75-
container-overlap-offset: -12px,
76-
small-size-container-overlap-offset: -6px,
73+
container-offset: _hardcode(-12px 0, $exclude-hardcoded),
74+
small-size-container-offset: _hardcode(-6px 0, $exclude-hardcoded),
75+
container-overlap-offset: _hardcode(-12px, $exclude-hardcoded),
76+
small-size-container-overlap-offset: _hardcode(-6px, $exclude-hardcoded),
7777

7878
// This size isn't in the M3 spec so we emit the same values as for `medium`.
7979
large-size-container-size: _hardcode(16px, $exclude-hardcoded),
80-
large-size-container-offset: -12px 0,
81-
large-size-container-overlap-offset: -12px,
80+
large-size-container-offset: _hardcode(-12px 0, $exclude-hardcoded),
81+
large-size-container-overlap-offset: _hardcode(-12px, $exclude-hardcoded),
8282
large-size-text-size: map.get($systems, md-sys-typescale, label-small-size),
8383
large-size-container-padding: _hardcode(0 4px, $exclude-hardcoded),
8484
legacy-large-size-container-size: _hardcode(unset, $exclude-hardcoded),
@@ -423,7 +423,7 @@
423423
@return (
424424
container-max-width: _hardcode(560px, $exclude-hardcoded),
425425
container-small-max-width: _hardcode(calc(100vw - 32px), $exclude-hardcoded),
426-
container-min-width: _harcode(280px, $exclude-hardcoded),
426+
container-min-width: _hardcode(280px, $exclude-hardcoded),
427427
actions-alignment: _hardcode(flex-end, $exclude-hardcoded),
428428
content-padding: _hardcode(20px 24px, $exclude-hardcoded),
429429
with-actions-content-padding: _hardcode(20px 24px 0, $exclude-hardcoded),
@@ -1160,28 +1160,28 @@
11601160
/// @param {Boolean} $exclude-hardcoded Whether to exclude hardcoded token values
11611161
/// @return {Map} A set of custom tokens for the mat-slide-toggle
11621162
@function switch($systems, $exclude-hardcoded) {
1163-
@return ((
1164-
unselected-handle-size: 16px,
1165-
selected-handle-size: 24px,
1166-
with-icon-handle-size: 24px,
1167-
pressed-handle-size: 28px,
1168-
selected-handle-horizontal-margin: 0 24px,
1169-
selected-with-icon-handle-horizontal-margin: 0 24px,
1170-
selected-pressed-handle-horizontal-margin: 0 22px,
1171-
unselected-handle-horizontal-margin: 0 8px,
1172-
unselected-with-icon-handle-horizontal-margin: 0 4px,
1173-
unselected-pressed-handle-horizontal-margin: 0 2px,
1163+
@return (
1164+
unselected-handle-size: _hardcode(16px, $exclude-hardcoded),
1165+
selected-handle-size: _hardcode(24px, $exclude-hardcoded),
1166+
with-icon-handle-size: _hardcode(24px, $exclude-hardcoded),
1167+
pressed-handle-size: _hardcode(28px, $exclude-hardcoded),
1168+
selected-handle-horizontal-margin: _hardcode(0 24px, $exclude-hardcoded),
1169+
selected-with-icon-handle-horizontal-margin: _hardcode(0 24px, $exclude-hardcoded),
1170+
selected-pressed-handle-horizontal-margin: _hardcode(0 22px, $exclude-hardcoded),
1171+
unselected-handle-horizontal-margin: _hardcode(0 8px, $exclude-hardcoded),
1172+
unselected-with-icon-handle-horizontal-margin: _hardcode(0 4px, $exclude-hardcoded),
1173+
unselected-pressed-handle-horizontal-margin: _hardcode(0 2px, $exclude-hardcoded),
11741174
// The hidden and visible track represent whichever track is visible or
11751175
// hidden in the ui. This could be the .mdc-switch__track :before or
11761176
// :after depending on whether the switch is selected or unselected.
11771177
//
11781178
// The m2 slide-toggle uses transforms to hide & show the tracks while
11791179
// the m3 slide-toggle uses opacity.
1180-
visible-track-opacity: 1,
1181-
hidden-track-opacity: 0,
1182-
visible-track-transition: opacity 75ms,
1183-
hidden-track-transition: opacity 75ms,
1184-
), ());
1180+
visible-track-opacity: _hardcode(1, $exclude-hardcoded),
1181+
hidden-track-opacity: _hardcode(0, $exclude-hardcoded),
1182+
visible-track-transition: _hardcode(opacity 75ms, $exclude-hardcoded),
1183+
hidden-track-transition: _hardcode(opacity 75ms, $exclude-hardcoded),
1184+
);
11851185
}
11861186

11871187
/// Generates custom tokens for the mat-snack-bar.
@@ -1282,7 +1282,7 @@
12821282
_generate-typography-tokens($systems, label-text, title-small),
12831283
(
12841284
divider-color: map.get($systems, md-sys-color, surface-variant),
1285-
divider-height: 1px,
1285+
divider-height: _hardcode(1px, $exclude-hardcoded),
12861286
disabled-ripple-color: null, // TODO(mmalerba): Figure out correct value.
12871287
pagination-icon-color: map.get($systems, md-sys-color, on-surface),
12881288
inactive-label-text-color: map.get($systems, md-sys-color, on-surface),

src/material-experimental/theming/_m3-density.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ $_density-tokens: (
114114
(mat, minimal-pseudo-checkbox): (),
115115
(mat, paginator): (
116116
container-size: (56px, 52px, 48px, 40px),
117+
form-field-container-height: (40px, 40px, 40px, 40px, 40px, 36px),
118+
form-field-container-vertical-padding: (8px, 8px, 8px, 8px, 8px, 6px),
117119
),
118120
(mat, radio): (
119121
touch-target-display: (block, block, none, none),

src/material-experimental/theming/_m3-tokens.scss

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@
446446
headline-weight: subhead-weight,
447447
));
448448

449-
@if (map.has-key($tokens, container-elevation)) {
449+
@if map.get($tokens, container-elevation) != null {
450450
$tokens: map.merge($tokens, (
451451
// The spec has the dialog at an elevation of 3 which is consistent with the current
452452
// version of the tokens (0_161), however both the designs and MDC's implementation
@@ -472,7 +472,10 @@
472472
// However, this interferes with the use case of placing a list on other components. For example,
473473
// the bottom sheet's container color is `md.sys.color.surface-container-low`. Instead, allow the
474474
// list to just display the colors for its background.
475-
@return map.set($tokens, list-item-container-color, transparent);
475+
@if map.get($tokens, list-item-container-color) != null {
476+
$tokens: map.set($tokens, list-item-container-color, transparent);
477+
}
478+
@return $tokens;
476479
}
477480

478481
/// Generates a set of namespaced tokens for all components.
@@ -976,7 +979,13 @@
976979
mdc-tokens.md-sys-color-values-light($ref));
977980
@return _generate-tokens(map.merge($ref, (
978981
md-sys-color: $sys-color,
982+
// Because the elevation values are always combined with color values to create the box shadow,
983+
// elevation needs to be part of the color dimension.
979984
md-sys-elevation: mdc-tokens.md-sys-elevation-values(),
985+
// Because the state values are sometimes combined with color values to create rgba colors,
986+
// state needs to be part of color dimension.
987+
// TODO(mmalerba): If at some point we remove the need for these combined values, we can move
988+
// state to the base dimension.
980989
md-sys-state: mdc-tokens.md-sys-state-values(),
981990
)));
982991
}
@@ -1012,6 +1021,5 @@
10121021
@return _generate-tokens((
10131022
md-sys-motion: mdc-tokens.md-sys-motion-values(),
10141023
md-sys-shape: mdc-tokens.md-sys-shape-values(),
1015-
md-sys-state: mdc-tokens.md-sys-state-values(),
10161024
), $include-non-systemized: true);
10171025
}

src/material/button/_button-theme.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
/// @param {Map} $theme The theme to generate base styles for.
113113
@mixin base($theme) {
114114
@if inspection.get-theme-version($theme) == 1 {
115-
@include theme-from-tokens(inspection.get-theme-tokens($theme, base));
115+
@include _theme-from-tokens(inspection.get-theme-tokens($theme, base));
116116
}
117117
@else {
118118
@include sass-utils.current-selector-or-root() {

src/material/core/theming/_inspection.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac
260260
}
261261
@else if $system == density {
262262
$theme: map.deep-remove($theme, $_internals, density-scale);
263+
$theme: map.deep-remove($theme, $_internals, density-tokens);
263264
}
264265
}
265266
@return $theme;

src/material/core/theming/tests/m3-theme.spec.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,27 @@ function transpile(content: string) {
4444
).css.toString();
4545
}
4646

47+
function union<T>(...sets: Set<T>[]): Set<T> {
48+
return new Set(sets.flatMap(s => [...s]));
49+
}
50+
51+
function intersection<T>(set: Set<T>, ...sets: Set<T>[]): Set<T> {
52+
return new Set([...set].filter(i => sets.every(s => s.has(i))));
53+
}
54+
4755
describe('M3 theme', () => {
4856
it('should emit all styles under the given selector', () => {
49-
const root = parse(transpile(`html { @include mat.all-component-themes($theme); }`));
57+
const root = parse(
58+
transpile(`
59+
html {
60+
@include mat.all-component-themes($theme);
61+
@include mat.all-component-bases($theme);
62+
@include mat.all-component-colors($theme);
63+
@include mat.all-component-typographies($theme);
64+
@include mat.all-component-densities($theme);
65+
}
66+
`),
67+
);
5068
const selectors: string[] = [];
5169
root.walkRules(rule => {
5270
selectors.push(rule.selector);
@@ -64,4 +82,41 @@ describe('M3 theme', () => {
6482
});
6583
expect(nonVarProps).toEqual([]);
6684
});
85+
86+
it('should not have overlapping tokens between theme dimensions', () => {
87+
const css = transpile(`
88+
$theme: matx.define-theme();
89+
base {
90+
@include mat.all-component-bases($theme);
91+
}
92+
color {
93+
@include mat.all-component-colors($theme);
94+
}
95+
typography {
96+
@include mat.all-component-typographies($theme);
97+
}
98+
density {
99+
@include mat.all-component-densities($theme);
100+
}
101+
`);
102+
const root = parse(css);
103+
const propSets: {[key: string]: Set<string>} = {};
104+
root.walkRules(rule => {
105+
rule.walkDecls(decl => {
106+
propSets[rule.selector] = propSets[rule.selector] || new Set();
107+
propSets[rule.selector].add(decl.prop);
108+
});
109+
});
110+
let overlap = new Set();
111+
for (const [dimension1, props1] of Object.entries(propSets)) {
112+
for (const [dimension2, props2] of Object.entries(propSets)) {
113+
if (dimension1 !== dimension2) {
114+
overlap = union(overlap, intersection(props1, props2));
115+
}
116+
}
117+
}
118+
expect([...overlap])
119+
.withContext('Did you forget to wrap these in `_hardcode()`?')
120+
.toEqual([]);
121+
});
67122
});

src/material/core/theming/tests/theming-inspection-api.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as path from 'path';
44

55
import {createLocalAngularPackageImporter} from '../../../../../tools/sass/local-sass-importer';
66
import {pathToFileURL} from 'url';
7+
import {parse} from 'postcss';
78

89
// Note: For Windows compatibility, we need to resolve the directory paths through runfiles
910
// which are guaranteed to reside in the source tree.
@@ -466,5 +467,14 @@ describe('theming inspection api', () => {
466467
`),
467468
).toThrowError(/Density information is not available on this theme/);
468469
});
470+
471+
it('should not emit styles for removed theme dimensions', () => {
472+
const css = transpile(`
473+
$theme: mat.theme-remove(matx.define-theme(), base, color, typography, density);
474+
div {
475+
@include mat.all-component-themes($theme);
476+
}`);
477+
expect(css).toBe('');
478+
});
469479
});
470480
});

src/material/core/tokens/m2/mat/_paginator.scss

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
@use 'sass:math';
12
@use 'sass:map';
3+
@use '@material/textfield' as mdc-textfield;
4+
@use '@material/density' as mdc-density;
25
@use '../../token-utils';
36
@use '../../../theming/theming';
47
@use '../../../theming/inspection';
@@ -39,16 +42,33 @@ $prefix: (mat, paginator);
3942

4043
// Tokens that can be configured through Angular Material's density theming API.
4144
@function get-density-tokens($theme) {
42-
$density-scale: theming.clamp-density(inspection.get-theme-density($theme), -3);
45+
$density-scale: theming.clamp-density(inspection.get-theme-density($theme), -5);
4346
$size-scale: (
4447
0: 56px,
4548
-1: 52px,
4649
-2: 48px,
4750
-3: 40px,
51+
-4: 40px,
52+
-5: 40px,
4853
);
54+
$form-field-density-scale: if($density-scale > -4, -4, $density-scale);
55+
$form-field-height: mdc-density.prop-value(
56+
$density-config: mdc-textfield.$density-config,
57+
$density-scale: $form-field-density-scale,
58+
$property-name: height,
59+
);
60+
// We computed the desired height of the form-field using the density configuration. The
61+
// spec only describes vertical spacing/alignment in non-dense mode. This means that we
62+
// cannot update the spacing to explicit numbers based on the density scale. Instead, we
63+
// determine the height reduction and equally subtract it from the default `top` and `bottom`
64+
// padding that is provided by the Material Design specification.
65+
$form-field-vertical-deduction: math.div(mdc-textfield.$height - $form-field-height, 2);
66+
$form-field-vertical-padding: 16px - $form-field-vertical-deduction;
4967

5068
@return (
51-
container-size: map.get($size-scale, $density-scale)
69+
container-size: map.get($size-scale, $density-scale),
70+
form-field-container-height: $form-field-height,
71+
form-field-container-vertical-padding: $form-field-vertical-padding,
5272
);
5373
}
5474

src/material/core/typography/_all-typography.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@
5151
// mixin that is transitively loaded by the `all-theme` file, imports `all-typography` which
5252
// would then load `all-theme` again. This ultimately results a circular dependency.
5353
@include badge-theme.typography($theme);
54-
@include typography.typography-hierarchy($theme);
54+
// Historically the typography hierarchy styles were included as part of this. We maintain this
55+
// behavior for M2, but from M3 forward this is not included and should be explicitly @included
56+
// by the user if desired.
57+
@if (inspection.get-theme-version($theme) < 1) {
58+
@include typography.typography-hierarchy($theme);
59+
}
5560
@include bottom-sheet-theme.typography($theme);
5661
@include button-toggle-theme.typography($theme);
5762
@include divider-theme.typography($theme);

src/material/paginator/_paginator-theme.scss

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,6 @@
5353
tokens-mat-paginator.get-density-tokens($theme));
5454
}
5555
}
56-
57-
// We need the form field to be narrower in order to fit into the paginator,
58-
// so we set its density to be -4 or denser.
59-
.mat-mdc-paginator {
60-
@include form-field-theme.density((density: $form-field-density));
61-
}
6256
}
6357

6458
@mixin theme($theme) {

src/material/paginator/paginator.scss

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ $button-icon-size: 28px;
2727
@include token-utils.create-token-slot(font-weight, container-text-weight);
2828
@include token-utils.create-token-slot(letter-spacing, container-text-tracking);
2929

30+
// Apply custom form-field density for paginator.
31+
@include token-utils.create-token-slot(
32+
--mat-form-field-container-height, form-field-container-height);
33+
@include token-utils.create-token-slot(
34+
--mat-form-field-container-vertical-padding, form-field-container-vertical-padding);
35+
3036
.mat-mdc-select-value {
3137
@include token-utils.create-token-slot(font-size, select-trigger-text-size);
3238
}

0 commit comments

Comments
 (0)