Skip to content

Commit

Permalink
fix(material-experimental/theming): Add more tests for M3 theme tokens
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mmalerba committed Feb 8, 2024
1 parent f1e82da commit b2f1710
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 38 deletions.
48 changes: 24 additions & 24 deletions src/material-experimental/theming/_custom-tokens.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@
small-size-container-size: _hardcode(6px, $exclude-hardcoded),
container-padding: _hardcode(0 4px, $exclude-hardcoded),
small-size-container-padding: _hardcode(0, $exclude-hardcoded),
container-offset: -12px 0,
small-size-container-offset: -6px 0,
container-overlap-offset: -12px,
small-size-container-overlap-offset: -6px,
container-offset: _hardcode(-12px 0, $exclude-hardcoded),
small-size-container-offset: _hardcode(-6px 0, $exclude-hardcoded),
container-overlap-offset: _hardcode(-12px, $exclude-hardcoded),
small-size-container-overlap-offset: _hardcode(-6px, $exclude-hardcoded),

// This size isn't in the M3 spec so we emit the same values as for `medium`.
large-size-container-size: _hardcode(16px, $exclude-hardcoded),
large-size-container-offset: -12px 0,
large-size-container-overlap-offset: -12px,
large-size-container-offset: _hardcode(-12px 0, $exclude-hardcoded),
large-size-container-overlap-offset: _hardcode(-12px, $exclude-hardcoded),
large-size-text-size: map.get($systems, md-sys-typescale, label-small-size),
large-size-container-padding: _hardcode(0 4px, $exclude-hardcoded),
legacy-large-size-container-size: _hardcode(unset, $exclude-hardcoded),
Expand Down Expand Up @@ -423,7 +423,7 @@
@return (
container-max-width: _hardcode(560px, $exclude-hardcoded),
container-small-max-width: _hardcode(calc(100vw - 32px), $exclude-hardcoded),
container-min-width: _harcode(280px, $exclude-hardcoded),
container-min-width: _hardcode(280px, $exclude-hardcoded),
actions-alignment: _hardcode(flex-end, $exclude-hardcoded),
content-padding: _hardcode(20px 24px, $exclude-hardcoded),
with-actions-content-padding: _hardcode(20px 24px 0, $exclude-hardcoded),
Expand Down Expand Up @@ -1160,28 +1160,28 @@
/// @param {Boolean} $exclude-hardcoded Whether to exclude hardcoded token values
/// @return {Map} A set of custom tokens for the mat-slide-toggle
@function switch($systems, $exclude-hardcoded) {
@return ((
unselected-handle-size: 16px,
selected-handle-size: 24px,
with-icon-handle-size: 24px,
pressed-handle-size: 28px,
selected-handle-horizontal-margin: 0 24px,
selected-with-icon-handle-horizontal-margin: 0 24px,
selected-pressed-handle-horizontal-margin: 0 22px,
unselected-handle-horizontal-margin: 0 8px,
unselected-with-icon-handle-horizontal-margin: 0 4px,
unselected-pressed-handle-horizontal-margin: 0 2px,
@return (
unselected-handle-size: _hardcode(16px, $exclude-hardcoded),
selected-handle-size: _hardcode(24px, $exclude-hardcoded),
with-icon-handle-size: _hardcode(24px, $exclude-hardcoded),
pressed-handle-size: _hardcode(28px, $exclude-hardcoded),
selected-handle-horizontal-margin: _hardcode(0 24px, $exclude-hardcoded),
selected-with-icon-handle-horizontal-margin: _hardcode(0 24px, $exclude-hardcoded),
selected-pressed-handle-horizontal-margin: _hardcode(0 22px, $exclude-hardcoded),
unselected-handle-horizontal-margin: _hardcode(0 8px, $exclude-hardcoded),
unselected-with-icon-handle-horizontal-margin: _hardcode(0 4px, $exclude-hardcoded),
unselected-pressed-handle-horizontal-margin: _hardcode(0 2px, $exclude-hardcoded),
// The hidden and visible track represent whichever track is visible or
// hidden in the ui. This could be the .mdc-switch__track :before or
// :after depending on whether the switch is selected or unselected.
//
// The m2 slide-toggle uses transforms to hide & show the tracks while
// the m3 slide-toggle uses opacity.
visible-track-opacity: 1,
hidden-track-opacity: 0,
visible-track-transition: opacity 75ms,
hidden-track-transition: opacity 75ms,
), ());
visible-track-opacity: _hardcode(1, $exclude-hardcoded),
hidden-track-opacity: _hardcode(0, $exclude-hardcoded),
visible-track-transition: _hardcode(opacity 75ms, $exclude-hardcoded),
hidden-track-transition: _hardcode(opacity 75ms, $exclude-hardcoded),
);
}

/// Generates custom tokens for the mat-snack-bar.
Expand Down Expand Up @@ -1282,7 +1282,7 @@
_generate-typography-tokens($systems, label-text, title-small),
(
divider-color: map.get($systems, md-sys-color, surface-variant),
divider-height: 1px,
divider-height: _hardcode(1px, $exclude-hardcoded),
disabled-ripple-color: null, // TODO(mmalerba): Figure out correct value.
pagination-icon-color: map.get($systems, md-sys-color, on-surface),
inactive-label-text-color: map.get($systems, md-sys-color, on-surface),
Expand Down
2 changes: 2 additions & 0 deletions src/material-experimental/theming/_m3-density.scss
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ $_density-tokens: (
(mat, minimal-pseudo-checkbox): (),
(mat, paginator): (
container-size: (56px, 52px, 48px, 40px),
form-field-container-height: (40px, 40px, 40px, 40px, 40px, 36px),
form-field-container-vertical-padding: (8px, 8px, 8px, 8px, 8px, 6px),
),
(mat, radio): (
touch-target-display: (block, block, none, none),
Expand Down
14 changes: 11 additions & 3 deletions src/material-experimental/theming/_m3-tokens.scss
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@
headline-weight: subhead-weight,
));

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

/// Generates a set of namespaced tokens for all components.
Expand Down Expand Up @@ -976,7 +979,13 @@
mdc-tokens.md-sys-color-values-light($ref));
@return _generate-tokens(map.merge($ref, (
md-sys-color: $sys-color,
// Because the elevation values are always combined with color values to create the box shadow,
// elevation needs to be part of the color dimension.
md-sys-elevation: mdc-tokens.md-sys-elevation-values(),
// Because the state values are sometimes combined with color values to create rgba colors,
// state needs to be part of color dimension.
// TODO(mmalerba): If at some point we remove the need for these combined values, we can move
// state to the base dimension.
md-sys-state: mdc-tokens.md-sys-state-values(),
)));
}
Expand Down Expand Up @@ -1012,6 +1021,5 @@
@return _generate-tokens((
md-sys-motion: mdc-tokens.md-sys-motion-values(),
md-sys-shape: mdc-tokens.md-sys-shape-values(),
md-sys-state: mdc-tokens.md-sys-state-values(),
), $include-non-systemized: true);
}
2 changes: 1 addition & 1 deletion src/material/button/_button-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@
/// @param {Map} $theme The theme to generate base styles for.
@mixin base($theme) {
@if inspection.get-theme-version($theme) == 1 {
@include theme-from-tokens(inspection.get-theme-tokens($theme, base));
@include _theme-from-tokens(inspection.get-theme-tokens($theme, base));
}
@else {
@include sass-utils.current-selector-or-root() {
Expand Down
1 change: 1 addition & 0 deletions src/material/core/theming/_inspection.scss
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac
}
@else if $system == density {
$theme: map.deep-remove($theme, $_internals, density-scale);
$theme: map.deep-remove($theme, $_internals, density-tokens);
}
}
@return $theme;
Expand Down
57 changes: 56 additions & 1 deletion src/material/core/theming/tests/m3-theme.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,27 @@ function transpile(content: string) {
).css.toString();
}

function union<T>(...sets: Set<T>[]): Set<T> {
return new Set(sets.flatMap(s => [...s]));
}

function intersection<T>(set: Set<T>, ...sets: Set<T>[]): Set<T> {
return new Set([...set].filter(i => sets.every(s => s.has(i))));
}

describe('M3 theme', () => {
it('should emit all styles under the given selector', () => {
const root = parse(transpile(`html { @include mat.all-component-themes($theme); }`));
const root = parse(
transpile(`
html {
@include mat.all-component-themes($theme);
@include mat.all-component-bases($theme);
@include mat.all-component-colors($theme);
@include mat.all-component-typographies($theme);
@include mat.all-component-densities($theme);
}
`),
);
const selectors: string[] = [];
root.walkRules(rule => {
selectors.push(rule.selector);
Expand All @@ -64,4 +82,41 @@ describe('M3 theme', () => {
});
expect(nonVarProps).toEqual([]);
});

it('should not have overlapping tokens between theme dimensions', () => {
const css = transpile(`
$theme: matx.define-theme();
base {
@include mat.all-component-bases($theme);
}
color {
@include mat.all-component-colors($theme);
}
typography {
@include mat.all-component-typographies($theme);
}
density {
@include mat.all-component-densities($theme);
}
`);
const root = parse(css);
const propSets: {[key: string]: Set<string>} = {};
root.walkRules(rule => {
rule.walkDecls(decl => {
propSets[rule.selector] = propSets[rule.selector] || new Set();
propSets[rule.selector].add(decl.prop);
});
});
let overlap = new Set();
for (const [dimension1, props1] of Object.entries(propSets)) {
for (const [dimension2, props2] of Object.entries(propSets)) {
if (dimension1 !== dimension2) {
overlap = union(overlap, intersection(props1, props2));
}
}
}
expect([...overlap])
.withContext('Did you forget to wrap these in `_hardcode()`?')
.toEqual([]);
});
});
10 changes: 10 additions & 0 deletions src/material/core/theming/tests/theming-inspection-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as path from 'path';

import {createLocalAngularPackageImporter} from '../../../../../tools/sass/local-sass-importer';
import {pathToFileURL} from 'url';
import {parse} from 'postcss';

// Note: For Windows compatibility, we need to resolve the directory paths through runfiles
// which are guaranteed to reside in the source tree.
Expand Down Expand Up @@ -466,5 +467,14 @@ describe('theming inspection api', () => {
`),
).toThrowError(/Density information is not available on this theme/);
});

it('should not emit styles for removed theme dimensions', () => {
const css = transpile(`
$theme: mat.theme-remove(matx.define-theme(), base, color, typography, density);
div {
@include mat.all-component-themes($theme);
}`);
expect(css).toBe('');
});
});
});
24 changes: 22 additions & 2 deletions src/material/core/tokens/m2/mat/_paginator.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
@use 'sass:math';
@use 'sass:map';
@use '@material/textfield' as mdc-textfield;
@use '@material/density' as mdc-density;
@use '../../token-utils';
@use '../../../theming/theming';
@use '../../../theming/inspection';
Expand Down Expand Up @@ -39,16 +42,33 @@ $prefix: (mat, paginator);

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

@return (
container-size: map.get($size-scale, $density-scale)
container-size: map.get($size-scale, $density-scale),
form-field-container-height: $form-field-height,
form-field-container-vertical-padding: $form-field-vertical-padding,
);
}

Expand Down
7 changes: 6 additions & 1 deletion src/material/core/typography/_all-typography.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@
// mixin that is transitively loaded by the `all-theme` file, imports `all-typography` which
// would then load `all-theme` again. This ultimately results a circular dependency.
@include badge-theme.typography($theme);
@include typography.typography-hierarchy($theme);
// Historically the typography hierarchy styles were included as part of this. We maintain this
// behavior for M2, but from M3 forward this is not included and should be explicitly @included
// by the user if desired.
@if (inspection.get-theme-version($theme) < 1) {
@include typography.typography-hierarchy($theme);
}
@include bottom-sheet-theme.typography($theme);
@include button-toggle-theme.typography($theme);
@include divider-theme.typography($theme);
Expand Down
6 changes: 0 additions & 6 deletions src/material/paginator/_paginator-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,6 @@
tokens-mat-paginator.get-density-tokens($theme));
}
}

// We need the form field to be narrower in order to fit into the paginator,
// so we set its density to be -4 or denser.
.mat-mdc-paginator {
@include form-field-theme.density((density: $form-field-density));
}
}

@mixin theme($theme) {
Expand Down
6 changes: 6 additions & 0 deletions src/material/paginator/paginator.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ $button-icon-size: 28px;
@include token-utils.create-token-slot(font-weight, container-text-weight);
@include token-utils.create-token-slot(letter-spacing, container-text-tracking);

// Apply custom form-field density for paginator.
@include token-utils.create-token-slot(
--mat-form-field-container-height, form-field-container-height);
@include token-utils.create-token-slot(
--mat-form-field-container-vertical-padding, form-field-container-vertical-padding);

.mat-mdc-select-value {
@include token-utils.create-token-slot(font-size, select-trigger-text-size);
}
Expand Down

0 comments on commit b2f1710

Please sign in to comment.