diff --git a/src/material-experimental/theming/_custom-tokens.scss b/src/material-experimental/theming/_custom-tokens.scss index 09f12242935e..1866b62ab16a 100644 --- a/src/material-experimental/theming/_custom-tokens.scss +++ b/src/material-experimental/theming/_custom-tokens.scss @@ -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), @@ -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), @@ -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. @@ -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), diff --git a/src/material-experimental/theming/_m3-density.scss b/src/material-experimental/theming/_m3-density.scss index 51ca09a58597..dd09d22936a9 100644 --- a/src/material-experimental/theming/_m3-density.scss +++ b/src/material-experimental/theming/_m3-density.scss @@ -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), diff --git a/src/material-experimental/theming/_m3-tokens.scss b/src/material-experimental/theming/_m3-tokens.scss index ef5f128cea08..f288634bce55 100644 --- a/src/material-experimental/theming/_m3-tokens.scss +++ b/src/material-experimental/theming/_m3-tokens.scss @@ -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 @@ -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. @@ -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(), ))); } @@ -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); } diff --git a/src/material/button/_button-theme.scss b/src/material/button/_button-theme.scss index 56bcbae5038d..c691c7612f44 100644 --- a/src/material/button/_button-theme.scss +++ b/src/material/button/_button-theme.scss @@ -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() { diff --git a/src/material/core/theming/_inspection.scss b/src/material/core/theming/_inspection.scss index 6dc55b1816bc..30f6afc069c1 100644 --- a/src/material/core/theming/_inspection.scss +++ b/src/material/core/theming/_inspection.scss @@ -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; diff --git a/src/material/core/theming/tests/m3-theme.spec.ts b/src/material/core/theming/tests/m3-theme.spec.ts index fcf151bd4b1b..bfc815c7d7b6 100644 --- a/src/material/core/theming/tests/m3-theme.spec.ts +++ b/src/material/core/theming/tests/m3-theme.spec.ts @@ -44,9 +44,27 @@ function transpile(content: string) { ).css.toString(); } +function union(...sets: Set[]): Set { + return new Set(sets.flatMap(s => [...s])); +} + +function intersection(set: Set, ...sets: Set[]): Set { + 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); @@ -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} = {}; + 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([]); + }); }); diff --git a/src/material/core/theming/tests/theming-inspection-api.spec.ts b/src/material/core/theming/tests/theming-inspection-api.spec.ts index d64000977a33..d6da0856ab2e 100644 --- a/src/material/core/theming/tests/theming-inspection-api.spec.ts +++ b/src/material/core/theming/tests/theming-inspection-api.spec.ts @@ -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. @@ -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(''); + }); }); }); diff --git a/src/material/core/tokens/m2/mat/_paginator.scss b/src/material/core/tokens/m2/mat/_paginator.scss index 257f3b35e1d2..4405b905d8ce 100644 --- a/src/material/core/tokens/m2/mat/_paginator.scss +++ b/src/material/core/tokens/m2/mat/_paginator.scss @@ -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'; @@ -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, ); } diff --git a/src/material/core/typography/_all-typography.scss b/src/material/core/typography/_all-typography.scss index 29a0db750ffe..55ce57568889 100644 --- a/src/material/core/typography/_all-typography.scss +++ b/src/material/core/typography/_all-typography.scss @@ -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); diff --git a/src/material/paginator/_paginator-theme.scss b/src/material/paginator/_paginator-theme.scss index 571af2776afd..12617b09abf7 100644 --- a/src/material/paginator/_paginator-theme.scss +++ b/src/material/paginator/_paginator-theme.scss @@ -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) { diff --git a/src/material/paginator/paginator.scss b/src/material/paginator/paginator.scss index ebfef6ece107..3994a8269bae 100644 --- a/src/material/paginator/paginator.scss +++ b/src/material/paginator/paginator.scss @@ -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); }