From 128b2e0aba88ef76d35f8f92131db8d739a889aa Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Mon, 12 May 2025 21:29:11 -0600 Subject: [PATCH] refactor: remove badge component token values from theme --- src/material/badge/_badge-theme.scss | 41 +++-- src/material/badge/_m3-badge.scss | 106 +++++-------- src/material/badge/badge.scss | 9 +- .../button-toggle/_m3-button-toggle.scss | 2 +- src/material/button/_button-base.scss | 10 +- src/material/button/fab.scss | 3 +- src/material/core/color/_all-color.scss | 2 +- src/material/core/theming/_all-theme.scss | 41 +++++ .../_color-api-backwards-compatibility.scss | 9 +- src/material/core/theming/_definition.scss | 128 ++++++++-------- src/material/core/theming/_inspection.scss | 46 +++--- .../tests/theming-definition-api.spec.ts | 69 ++------- .../tests/theming-inspection-api.spec.ts | 81 +--------- src/material/core/tokens/_m3-system.scss | 141 ++++++++++++------ src/material/core/tokens/_m3-tokens.scss | 10 +- src/material/core/tokens/_m3-utils.scss | 35 +++++ src/material/core/tokens/_token-utils.scss | 23 ++- src/material/form-field/form-field.ts | 77 ++++++---- tools/extract-tokens/extract-tokens.ts | 2 +- 19 files changed, 434 insertions(+), 401 deletions(-) diff --git a/src/material/badge/_badge-theme.scss b/src/material/badge/_badge-theme.scss index 5b99eca35535..ced78f1ffddd 100644 --- a/src/material/badge/_badge-theme.scss +++ b/src/material/badge/_badge-theme.scss @@ -1,18 +1,22 @@ -@use 'sass:color'; -@use '../core/theming/theming'; +@use '../core/style/sass-utils'; @use '../core/theming/inspection'; +@use '../core/theming/theming'; @use '../core/theming/validation'; +@use '../core/tokens/token-utils'; @use '../core/typography/typography'; @use './m2-badge'; -@use '../core/tokens/token-utils'; -@use '../core/style/sass-utils'; +@use './m3-badge'; +@use 'sass:color'; +@use 'sass:map'; /// Outputs base theme styles (styles not dependent on the color, typography, or density settings) /// for the mat-badge. /// @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 token-utils.create-token-values( + m3-badge.$prefix, + map.get(m3-badge.get-tokens($theme), base)); } @else { @include sass-utils.current-selector-or-root() { @include token-utils.create-token-values-mixed( @@ -25,12 +29,14 @@ /// Outputs color theme styles for the mat-badge. /// @param {Map} $theme The theme to generate color styles for. -/// @param {ArgList} Additional optional arguments (only supported for M3 themes): -/// $color-variant: The color variant to use for the badge: primary, secondary, tertiary, -/// or error (If not specified, default error color will be used). -@mixin color($theme, $options...) { +/// @param {String} $color-variant The color variant to use for +/// the badge: primary, secondary, tertiary, or error (If not specified, +/// default error color will be used). +@mixin color($theme, $color-variant: null) { @if inspection.get-theme-version($theme) == 1 { - @include _theme-from-tokens(inspection.get-theme-tokens($theme, color), $options...); + @include token-utils.create-token-values( + m3-badge.$prefix, + map.get(m3-badge.get-tokens($theme, $color-variant), color)); } @else { @include sass-utils.current-selector-or-root() { @include token-utils.create-token-values-mixed( @@ -59,7 +65,9 @@ /// @param {Map} $theme The theme to generate typography styles for. @mixin typography($theme) { @if inspection.get-theme-version($theme) == 1 { - @include _theme-from-tokens(inspection.get-theme-tokens($theme, typography)); + @include token-utils.create-token-values( + m3-badge.$prefix, + map.get(m3-badge.get-tokens($theme), typography)); } @else { @include sass-utils.current-selector-or-root() { @include token-utils.create-token-values-mixed( @@ -74,7 +82,9 @@ /// @param {Map} $theme The theme to generate density styles for. @mixin density($theme) { @if inspection.get-theme-version($theme) == 1 { - // There are no M3 density tokens for this component + @include token-utils.create-token-values( + m3-badge.$prefix, + map.get(m3-badge.get-tokens($theme), density)); } @else { } } @@ -100,10 +110,13 @@ /// @param {ArgList} Additional optional arguments (only supported for M3 themes): /// $color-variant: The color variant to use for the badge: primary, secondary, tertiary, /// or error (If not specified, default error color will be used). -@mixin theme($theme, $options...) { +@mixin theme($theme, $color-variant: null) { @include theming.private-check-duplicate-theme-styles($theme, 'mat-badge') { @if inspection.get-theme-version($theme) == 1 { - @include _theme-from-tokens(inspection.get-theme-tokens($theme), $options...); + @include base($theme); + @include color($theme, $color-variant); + @include density($theme); + @include typography($theme); } @else { @include base($theme); @if inspection.theme-has($theme, color) { diff --git a/src/material/badge/_m3-badge.scss b/src/material/badge/_m3-badge.scss index 108795797d3d..f41305a74e91 100644 --- a/src/material/badge/_m3-badge.scss +++ b/src/material/badge/_m3-badge.scss @@ -1,81 +1,53 @@ @use 'sass:map'; -@use '../core/style/sass-utils'; @use '../core/tokens/m3-utils'; // The prefix used to generate the fully qualified name for tokens in this file. $prefix: (mat, badge); /// Generates custom tokens for the mat-badge. -/// @param {Map} $systems The MDC system tokens -/// @param {Boolean} $exclude-hardcoded Whether to exclude hardcoded token values -/// @param {Map} $token-slots Possible token slots -/// @return {Map} A set of custom tokens for the mat-badge -@function get-tokens($systems, $exclude-hardcoded, $token-slots) { - $tokens: ( - background-color: map.get($systems, md-sys-color, error), - text-color: map.get($systems, md-sys-color, on-error), - disabled-state-background-color: sass-utils.safe-color-change( - map.get($systems, md-sys-color, error), - $alpha: 0.38), - disabled-state-text-color: map.get($systems, md-sys-color, on-error), - text-font: map.get($systems, md-sys-typescale, label-small-font), - text-size: map.get($systems, md-sys-typescale, label-small-size), - text-weight: map.get($systems, md-sys-typescale, label-small-weight), - small-size-text-size: m3-utils.hardcode(0, $exclude-hardcoded), - container-shape: map.get($systems, md-sys-shape, corner-full), - container-size: m3-utils.hardcode(16px, $exclude-hardcoded), - line-height: m3-utils.hardcode(16px, $exclude-hardcoded), - legacy-container-size: m3-utils.hardcode(unset, $exclude-hardcoded), - legacy-small-size-container-size: m3-utils.hardcode(unset, $exclude-hardcoded), - small-size-container-size: m3-utils.hardcode(6px, $exclude-hardcoded), - small-size-line-height: m3-utils.hardcode(6px, $exclude-hardcoded), - container-padding: m3-utils.hardcode(0 4px, $exclude-hardcoded), - small-size-container-padding: m3-utils.hardcode(0, $exclude-hardcoded), - container-offset: m3-utils.hardcode(-12px 0, $exclude-hardcoded), - small-size-container-offset: m3-utils.hardcode(-6px 0, $exclude-hardcoded), - container-overlap-offset: m3-utils.hardcode(-12px, $exclude-hardcoded), - small-size-container-overlap-offset: m3-utils.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: m3-utils.hardcode(16px, $exclude-hardcoded), - large-size-line-height: m3-utils.hardcode(16px, $exclude-hardcoded), - large-size-container-offset: m3-utils.hardcode(-12px 0, $exclude-hardcoded), - large-size-container-overlap-offset: m3-utils.hardcode(-12px, $exclude-hardcoded), - large-size-text-size: map.get($systems, md-sys-typescale, label-small-size), - large-size-container-padding: m3-utils.hardcode(0 4px, $exclude-hardcoded), - legacy-large-size-container-size: m3-utils.hardcode(unset, $exclude-hardcoded), - ); +@function get-tokens($theme, $color-variant: null) { + $system: m3-utils.get-system($theme); + @if $color-variant { + $system: m3-utils.replace-colors-with-variant($system, error, $color-variant); + } - $variant-tokens: ( - primary: ( - background-color: map.get($systems, md-sys-color, primary), - text-color: map.get($systems, md-sys-color, on-primary), - disabled-state-background-color: sass-utils.safe-color-change( - map.get($systems, md-sys-color, primary), - $alpha: 0.38, - ), - disabled-state-text-color: map.get($systems, md-sys-color, on-primary), + $tokens: ( + base: ( + container-shape: map.get($system, corner-full), + container-size: 16px, + legacy-container-size: unset, + legacy-small-size-container-size: unset, + small-size-container-size: 6px, + container-padding: 0 4px, + small-size-container-padding: 0, + container-offset: -12px 0, + small-size-container-offset: -6px 0, + container-overlap-offset: -12px, + small-size-container-overlap-offset: -6px, + large-size-container-size: 16px, + large-size-container-offset: -12px 0, + large-size-container-overlap-offset: -12px, + large-size-container-padding: 0 4px, + legacy-large-size-container-size: unset, ), - secondary: ( - background-color: map.get($systems, md-sys-color, secondary), - text-color: map.get($systems, md-sys-color, on-secondary), - disabled-state-background-color: sass-utils.safe-color-change( - map.get($systems, md-sys-color, secondary), - $alpha: 0.38, - ), - disabled-state-text-color: map.get($systems, md-sys-color, on-secondary), + color: ( + background-color: map.get($system, error), + text-color: map.get($system, on-error), + disabled-state-background-color: m3-utils.color-with-opacity(map.get($system, error), 38%), + disabled-state-text-color: map.get($system, on-error), ), - tertiary: ( - background-color: map.get($systems, md-sys-color, tertiary), - text-color: map.get($systems, md-sys-color, on-tertiary), - disabled-state-background-color: sass-utils.safe-color-change( - map.get($systems, md-sys-color, tertiary), - $alpha: 0.38, - ), - disabled-state-text-color: map.get($systems, md-sys-color, on-tertiary), + typography: ( + text-font: map.get($system, label-small-font), + text-size: map.get($system, label-small-size), + text-weight: map.get($system, label-small-weight), + large-size-text-size: map.get($system, label-small-size), + small-size-text-size: 0, + line-height: 16px, + small-size-line-height: 6px, + large-size-line-height: 16px, ), - error: () // Default, no overrides needed + density: (), ); - @return m3-utils.namespace($prefix, ($tokens, $variant-tokens), $token-slots); + @return $tokens; } diff --git a/src/material/badge/badge.scss b/src/material/badge/badge.scss index a05d5245bcb9..2dd684f6fd79 100644 --- a/src/material/badge/badge.scss +++ b/src/material/badge/badge.scss @@ -1,7 +1,9 @@ @use 'sass:color'; @use '@angular/cdk'; @use './m2-badge'; +@use './m3-badge'; @use '../core/tokens/token-utils'; +@use '../core/tokens/m3-system'; $default-size: 22px !default; $small-size: $default-size - 6; @@ -9,9 +11,10 @@ $large-size: $default-size + 6; $token-prefix: m2-badge.$prefix; $token-slots: m2-badge.get-token-slots(); +$fallbacks: m3-badge.get-tokens(m3-system.$theme-with-system-vars); @mixin _badge-size($size) { - @include token-utils.use-tokens($token-prefix, $token-slots) { + @include token-utils.use-tokens($token-prefix, $token-slots, $fallbacks) { $prefix: if($size == 'medium', '', $size + '-size-'); $legacy-size-var-name: 'legacy-#{$prefix}container-size'; $size-var-name: '#{$prefix}container-size'; @@ -65,7 +68,7 @@ $token-slots: m2-badge.get-token-slots(); box-sizing: border-box; pointer-events: none; - @include token-utils.use-tokens($token-prefix, $token-slots) { + @include token-utils.use-tokens($token-prefix, $token-slots, $fallbacks) { background-color: token-utils.slot(background-color); color: token-utils.slot(text-color); font-family: token-utils.slot(text-font); @@ -106,7 +109,7 @@ $token-slots: m2-badge.get-token-slots(); } .mat-badge-disabled .mat-badge-content { - @include token-utils.use-tokens($token-prefix, $token-slots) { + @include token-utils.use-tokens($token-prefix, $token-slots, $fallbacks) { background-color: token-utils.slot(disabled-state-background-color); color: token-utils.slot(disabled-state-text-color); } diff --git a/src/material/button-toggle/_m3-button-toggle.scss b/src/material/button-toggle/_m3-button-toggle.scss index 935455ef4abb..6eea960a27fd 100644 --- a/src/material/button-toggle/_m3-button-toggle.scss +++ b/src/material/button-toggle/_m3-button-toggle.scss @@ -17,7 +17,7 @@ $prefix: (mat, button-toggle); $tokens: sass-utils.merge-all( m3-utils.generate-typography-tokens($systems, label-text, label-large), ( - shape: map.get($systems, md-sys-shape, corner-full), + shape: map.get($systems, md-sys-shape, corner-extra-large), hover-state-layer-opacity: map.get($systems, md-sys-state, hover-state-layer-opacity), focus-state-layer-opacity: map.get($systems, md-sys-state, focus-state-layer-opacity), text-color: map.get($systems, md-sys-color, on-surface), diff --git a/src/material/button/_button-base.scss b/src/material/button/_button-base.scss index bd452c589ad7..33d25abf3bde 100644 --- a/src/material/button/_button-base.scss +++ b/src/material/button/_button-base.scss @@ -6,7 +6,7 @@ // color and opacity for states like hover, active, and focus. Additionally, adds styles to the // ripple and state container so that they fill the button, match the border radius, and avoid // pointer events. -@mixin mat-private-button-interactive() { +@mixin mat-private-button-interactive($focus-indicator-inherits-shape: true) { -webkit-tap-highlight-color: transparent; // The ripple container should match the bounds of the entire button. @@ -50,10 +50,18 @@ // The focus indicator should match the bounds of the entire button. .mat-focus-indicator { @include layout-common.fill(); + + @if ($focus-indicator-inherits-shape) { + border-radius: inherit; + } } &:focus > .mat-focus-indicator::before { content: ''; + + @if ($focus-indicator-inherits-shape) { + border-radius: inherit; + } } } diff --git a/src/material/button/fab.scss b/src/material/button/fab.scss index 8da19ef2230e..327532994e5e 100644 --- a/src/material/button/fab.scss +++ b/src/material/button/fab.scss @@ -26,7 +26,8 @@ transform 270ms 0ms cubic-bezier(0, 0, 0.2, 1); flex-shrink: 0; // Prevent the button from shrinking since it's always supposed to be a circle. - @include button-base.mat-private-button-interactive(); + // Due to the shape of the FAB, inheriting the shape looks off. Disable it explicitly. + @include button-base.mat-private-button-interactive($focus-indicator-inherits-shape: false); @include style-private.private-animation-noop(); &::before { diff --git a/src/material/core/color/_all-color.scss b/src/material/core/color/_all-color.scss index 79241610ea54..ce75b859c505 100644 --- a/src/material/core/color/_all-color.scss +++ b/src/material/core/color/_all-color.scss @@ -7,7 +7,7 @@ @error 'No color configuration specified.'; } - @include all-theme.all-component-themes( + @include all-theme.all-component-colors( inspection.theme-remove($theme, base, typography, density)); } diff --git a/src/material/core/theming/_all-theme.scss b/src/material/core/theming/_all-theme.scss index 66823927319b..e3536aaf087a 100644 --- a/src/material/core/theming/_all-theme.scss +++ b/src/material/core/theming/_all-theme.scss @@ -125,6 +125,47 @@ @include timepicker-theme.base($theme); } +@mixin all-component-colors($theme) { + @include core-theme.color($theme); + @include card-theme.color($theme); + @include progress-bar-theme.color($theme); + @include tooltip-theme.color($theme); + @include form-field-theme.color($theme); + @include input-theme.color($theme); + @include select-theme.color($theme); + @include autocomplete-theme.color($theme); + @include dialog-theme.color($theme); + @include chips-theme.color($theme); + @include slide-toggle-theme.color($theme); + @include radio-theme.color($theme); + @include slider-theme.color($theme); + @include menu-theme.color($theme); + @include list-theme.color($theme); + @include paginator-theme.color($theme); + @include tabs-theme.color($theme); + @include checkbox-theme.color($theme); + @include button-theme.color($theme); + @include icon-button-theme.color($theme); + @include fab-theme.color($theme); + @include snack-bar-theme.color($theme); + @include table-theme.color($theme); + @include progress-spinner-theme.color($theme); + @include badge-theme.color($theme); + @include bottom-sheet-theme.color($theme); + @include button-toggle-theme.color($theme); + @include datepicker-theme.color($theme); + @include divider-theme.color($theme); + @include expansion-theme.color($theme); + @include grid-list-theme.color($theme); + @include icon-theme.color($theme); + @include sidenav-theme.color($theme); + @include stepper-theme.color($theme); + @include sort-theme.color($theme); + @include toolbar-theme.color($theme); + @include tree-theme.color($theme); + @include timepicker-theme.color($theme); +} + // @deprecated Use `all-component-themes`. @mixin angular-material-theme($theme) { @include all-component-themes($theme); diff --git a/src/material/core/theming/_color-api-backwards-compatibility.scss b/src/material/core/theming/_color-api-backwards-compatibility.scss index b48c2972c27e..84f449bc61fc 100644 --- a/src/material/core/theming/_color-api-backwards-compatibility.scss +++ b/src/material/core/theming/_color-api-backwards-compatibility.scss @@ -119,23 +119,20 @@ $_overrides-only: true; @include _color-variant-styles($theme, primary); } .mat-badge { - @include badge-theme.color($theme, $color-variant: primary, - $emit-overrides-only: $_overrides-only); + @include badge-theme.color($theme, $color-variant: primary); } .mat-accent { @include _color-variant-styles($theme, tertiary); } .mat-badge-accent { - @include badge-theme.color($theme, $color-variant: tertiary, - $emit-overrides-only: $_overrides-only); + @include badge-theme.color($theme, $color-variant: tertiary); } .mat-warn { @include _color-variant-styles($theme, error); } .mat-badge-warn { - @include badge-theme.color($theme, $color-variant: error, - $emit-overrides-only: $_overrides-only); + @include badge-theme.color($theme, $color-variant: error); } } diff --git a/src/material/core/theming/_definition.scss b/src/material/core/theming/_definition.scss index 847ac0e8a64c..27ddffbf1117 100644 --- a/src/material/core/theming/_definition.scss +++ b/src/material/core/theming/_definition.scss @@ -5,6 +5,7 @@ @use './palettes'; @use '../tokens/m3-tokens'; @use './config-validation'; +@use '../tokens/m3'; // Prefix used for component token fallback variables, e.g. // `color: var(--mat-text-button-label-text-color, var(--mat-sys-primary));` @@ -18,6 +19,7 @@ $system-level-prefix: mat-sys; /// Map key used to store internals of theme config. $internals: _mat-theming-internals-do-not-access; + /// The theme version of generated themes. $theme-version: 1; @@ -30,28 +32,17 @@ $theme-version: 1; @error $err; } - @return sass-utils.deep-merge-all( - define-colors(map.get($config, color) or ()), - define-typography(map.get($config, typography) or ()), - define-density(map.get($config, density) or ()), - ($internals: (base-tokens: m3-tokens.generate-base-tokens())), - ); -} + $color-config: map.get($config, color) or (); + $typography-config: map.get($config, typography) or (); + $density-config: map.get($config, density) or (); -/// Defines an Angular Material theme object with color settings. -/// @param {Map} $config The color configuration -/// @return {Map} A theme object -@function define-colors($config: ()) { - $err: config-validation.validate-color-config($config); - @if $err { - @error $err; - } - - $type: map.get($config, theme-type) or light; - $primary: map.get($config, primary) or palettes.$violet-palette; - $tertiary: map.get($config, tertiary) or $primary; - $system-variables-prefix: map.get($config, system-variables-prefix) or $system-level-prefix; - sass-utils.$use-system-color-variables: map.get($config, use-system-variables) or false; + // colors + $type: map.get($color-config, theme-type) or light; + $primary: map.get($color-config, primary) or palettes.$violet-palette; + $tertiary: map.get($color-config, tertiary) or $primary; + $color-system-variables-prefix: + map.get($color-config, system-variables-prefix) or $system-level-prefix; + sass-utils.$use-system-color-variables: map.get($color-config, use-system-variables) or false; $palettes: ( primary: map.remove($primary, neutral, neutral-variant, secondary), @@ -62,34 +53,18 @@ $theme-version: 1; error: map.get($primary, error), ); - @return ( - $internals: ( - theme-version: $theme-version, - theme-type: $type, - palettes: $palettes, - color-system-variables-prefix: $system-variables-prefix, - color-tokens: m3-tokens.generate-color-tokens($type, $palettes, $system-variables-prefix) - ) - ); -} - -/// Defines an Angular Material theme object with typography settings. -/// @param {Map} $config The typography configuration -/// @return {Map} A theme object -@function define-typography($config: ()) { - $err: config-validation.validate-typography-config($config); - @if $err { - @error $err; - } - - $plain: map.get($config, plain-family) or (Roboto, sans-serif); - $brand: map.get($config, brand-family) or $plain; - $bold: map.get($config, bold-weight) or 700; - $medium: map.get($config, medium-weight) or 500; - $regular: map.get($config, regular-weight) or 400; - $system-variables-prefix: map.get($config, system-variables-prefix) or $system-level-prefix; - sass-utils.$use-system-typography-variables: map.get($config, use-system-variables) or false; - + // typography + $default-plain: (Roboto, sans-serif); + $default-brand: $default-plain; + $plain: map.get($typography-config, plain-family) or $default-plain; + $brand: map.get($typography-config, brand-family) or $default-brand; + $bold: map.get($typography-config, bold-weight) or 700; + $medium: map.get($typography-config, medium-weight) or 500; + $regular: map.get($typography-config, regular-weight) or 400; + $typography-system-variables-prefix: map.get($typography-config, system-variables-prefix) or + $system-level-prefix; + sass-utils.$use-system-typography-variables: + map.get($typography-config, use-system-variables) or false; $typography: ( plain: $plain, brand: $brand, @@ -98,31 +73,54 @@ $theme-version: 1; regular: $regular, ); + // density + $density-scale: map.get($density-config, scale) or 0; + @return ( $internals: ( - theme-version: $theme-version, + base-tokens: m3-tokens.generate-base-tokens(), + color-system-variables-prefix: $color-system-variables-prefix, + color-tokens: + m3-tokens.generate-color-tokens($type, $palettes, $color-system-variables-prefix), + density-scale: $density-scale, font-definition: $typography, - typography-system-variables-prefix: $system-variables-prefix, - typography-tokens: m3-tokens.generate-typography-tokens($typography, $system-variables-prefix) + md-sys-color: m3-tokens.get-sys-color($type, $palettes, $color-system-variables-prefix), + md-sys-elevation: m3.md-sys-elevation-values(), + md-sys-motion: m3.md-sys-motion-values(), + md-sys-shape: m3.md-sys-shape-values(), + md-sys-state: m3.md-sys-state-values(), + md-sys-typescale: + m3-tokens.get-sys-typeface($typography, $typography-system-variables-prefix), + palettes: $palettes, + theme-type: $type, + theme-version: $theme-version, + typography-system-variables-prefix: $typography-system-variables-prefix, + typography-tokens: + m3-tokens.generate-typography-tokens($typography, $typography-system-variables-prefix), ) ); } +/// Defines an Angular Material theme object with color settings. +/// @param {Map} $config The color configuration +/// @return {Map} A theme object +/// @deprecated Use define-theme with a map using the "color" key +@function define-colors($config: ()) { + @return define-theme((color: $config)); +} + +/// Defines an Angular Material theme object with typography settings. +/// @param {Map} $config The typography configuration +/// @return {Map} A theme object +/// @deprecated Use define-theme with a map using the "typography" key +@function define-typography($config: ()) { + @return define-theme((typography: $config)); +} + /// Defines an Angular Material theme object with density settings. /// @param {Map} $config The density configuration /// @return {Map} A theme object +/// @deprecated Use define-theme with a map using the "density" key @function define-density($config: ()) { - $err: config-validation.validate-density-config($config); - @if $err { - @error $err; - } - - $density-scale: map.get($config, scale) or 0; - - @return ( - $internals: ( - theme-version: $theme-version, - density-scale: $density-scale, - ) - ); + @return define-theme((density: $config)); } diff --git a/src/material/core/theming/_inspection.scss b/src/material/core/theming/_inspection.scss index 900ba77f8e06..ae86be3523f7 100644 --- a/src/material/core/theming/_inspection.scss +++ b/src/material/core/theming/_inspection.scss @@ -3,7 +3,7 @@ @use '../style/validation'; @use './m2-inspection'; -$_internals: _mat-theming-internals-do-not-access; +$internals: _mat-theming-internals-do-not-access; $_m3-typescales: ( display-large, @@ -30,7 +30,7 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac /// @return {Boolean|Null} true if the theme has errors, else null. @function _validate-theme-object($theme) { $err: validation.validate-type($theme, 'map') or - map.get($theme, $_internals, theme-version) == null; + map.get($theme, $internals, theme-version) == null; @return if($err, true, null); } @@ -40,7 +40,7 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac /// @return {Number} The version number of the theme (0 if unknown). @function get-theme-version($theme) { $err: _validate-theme-object($theme); - @return if($err, 0, map.get($theme, $_internals, theme-version) or 0); + @return if($err, 0, map.get($theme, $internals, theme-version) or 0); } /// Gets the type of theme represented by a theme object (light or dark). @@ -55,13 +55,15 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac @if not theme-has($theme, color) { @error 'Color information is not available on this theme.'; } - @return map.get($theme, $_internals, theme-type) or light; + @return map.get($theme, $internals, theme-type) or light; } @else { @error #{'Unrecognized theme version:'} $version; } } + + /// Gets a color from a theme object. This function take a different amount of arguments depending /// on if it's working with an M2 or M3 theme: /// - With an M3 theme it accepts either 2 or 3 arguments. If 2 arguments are passed, the second @@ -119,7 +121,7 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac @if not theme-has($theme, color) { @error 'Color information is not available on this theme.'; } - $color-roles: map.get($theme, $_internals, color-tokens, (mat, theme)); + $color-roles: map.get($theme, $internals, color-tokens, (mat, theme)); $result: map.get($color-roles, $color-role-name); @if not $result { @error #{'Valid color roles are: #{map.keys($color-roles)}. Got:'} $color-role-name; @@ -141,7 +143,7 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac @if not theme-has($theme, color) { @error 'Color information is not available on this theme.'; } - $palettes: map.get($theme, $_internals, palettes); + $palettes: map.get($theme, $internals, palettes); $palette: map.get($palettes, $palette-name); @if not $palette { $supported-palettes: map.keys($palettes); @@ -185,7 +187,7 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac font-weight: '-weight' ), $property); $token-name: '#{$typescale}#{$property-key}'; - @return map.get($theme, $_internals, typography-tokens, (mat, typography), $token-name); + @return map.get($theme, $internals, typography-tokens, (mat, typography), $token-name); } @else { @error #{'Unrecognized theme version:'} $version; @@ -204,7 +206,7 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac @if not theme-has($theme, density) { @error 'Density information is not available on this theme.'; } - @return map.get($theme, $_internals, density-scale); + @return map.get($theme, $internals, density-scale); } @else { @error #{'Unrecognized theme version:'} $version; @@ -222,18 +224,18 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac } @else if $version == 1 { @if $system == base { - @return map.get($theme, $_internals, base-tokens) != null; + @return map.get($theme, $internals, base-tokens) != null; } @if $system == color { - @return map.get($theme, $_internals, color-tokens) != null and - map.get($theme, $_internals, theme-type) != null and - map.get($theme, $_internals, palettes) != null; + @return map.get($theme, $internals, color-tokens) != null and + map.get($theme, $internals, theme-type) != null and + map.get($theme, $internals, palettes) != null; } @if $system == typography { - @return map.get($theme, $_internals, typography-tokens) != null; + @return map.get($theme, $internals, typography-tokens) != null; } @if $system == density { - @return map.get($theme, $_internals, density-scale) != null; + @return map.get($theme, $internals, density-scale) != null; } @error 'Valid systems are: base, color, typography, density. Got:' $system; } @@ -259,19 +261,19 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac @else if $version == 1 { @each $system in $systems { @if $system == base { - $theme: map.deep-remove($theme, $_internals, base-tokens); + $theme: map.deep-remove($theme, $internals, base-tokens); } @else if $system == color { - $theme: map.deep-remove($theme, $_internals, color-tokens); - $theme: map.deep-remove($theme, $_internals, theme-type); - $theme: map.deep-remove($theme, $_internals, palettes); + $theme: map.deep-remove($theme, $internals, color-tokens); + $theme: map.deep-remove($theme, $internals, theme-type); + $theme: map.deep-remove($theme, $internals, palettes); } @else if $system == typography { - $theme: map.deep-remove($theme, $_internals, typography-tokens); + $theme: map.deep-remove($theme, $internals, typography-tokens); } @else if $system == density { - $theme: map.deep-remove($theme, $_internals, density-scale); - $theme: map.deep-remove($theme, $_internals, density-tokens); + $theme: map.deep-remove($theme, $internals, density-scale); + $theme: map.deep-remove($theme, $internals, density-tokens); } } @return $theme; @@ -297,7 +299,7 @@ $_typography-properties: (font, font-family, line-height, font-size, letter-spac } $result: (); @each $system in $systems { - $result: map.deep-merge($result, map.get($theme, $_internals, '#{$system}-tokens') or ()); + $result: map.deep-merge($result, map.get($theme, $internals, '#{$system}-tokens') or ()); } @return $result; diff --git a/src/material/core/theming/tests/theming-definition-api.spec.ts b/src/material/core/theming/tests/theming-definition-api.spec.ts index 512fa7f8655f..746128266659 100644 --- a/src/material/core/theming/tests/theming-definition-api.spec.ts +++ b/src/material/core/theming/tests/theming-definition-api.spec.ts @@ -69,16 +69,22 @@ describe('theming definition api', () => { `); const vars = getRootVars(css); expect(vars['keys'].split(', ')).toEqual([ - 'theme-version', - 'theme-type', - 'palettes', + 'base-tokens', 'color-system-variables-prefix', 'color-tokens', + 'density-scale', 'font-definition', + 'md-sys-color', + 'md-sys-elevation', + 'md-sys-motion', + 'md-sys-shape', + 'md-sys-state', + 'md-sys-typescale', + 'palettes', + 'theme-type', + 'theme-version', 'typography-system-variables-prefix', 'typography-tokens', - 'density-scale', - 'base-tokens', ]); expect(vars['version']).toBe('1'); expect(vars['type']).toBe('light'); @@ -243,57 +249,4 @@ describe('theming definition api', () => { ); }); }); - - describe('define-colors', () => { - it('should omit non-color info', () => { - const css = transpile(` - $theme: mat.define-colors(); - $data: map.get($theme, $internals); - :root { - --keys: #{map.keys($data)}; - } - `); - const vars = getRootVars(css); - expect(vars['keys'].split(', ')).toEqual([ - 'theme-version', - 'theme-type', - 'palettes', - 'color-system-variables-prefix', - 'color-tokens', - ]); - }); - }); - - describe('define-typography', () => { - it('should omit non-typography info', () => { - const css = transpile(` - $theme: mat.define-typography(); - $data: map.get($theme, $internals); - :root { - --keys: #{map.keys($data)}; - } - `); - const vars = getRootVars(css); - expect(vars['keys'].split(', ')).toEqual([ - 'theme-version', - 'font-definition', - 'typography-system-variables-prefix', - 'typography-tokens', - ]); - }); - }); - - describe('define-density', () => { - it('should omit non-density info', () => { - const css = transpile(` - $theme: mat.define-density(); - $data: map.get($theme, $internals); - :root { - --keys: #{map.keys($data)}; - } - `); - const vars = getRootVars(css); - expect(vars['keys'].split(', ')).toEqual(['theme-version', 'density-scale']); - }); - }); }); 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 0da3e6853a0c..e3ce2cdef6cd 100644 --- a/src/material/core/theming/tests/theming-inspection-api.spec.ts +++ b/src/material/core/theming/tests/theming-inspection-api.spec.ts @@ -371,96 +371,19 @@ describe('theming inspection api', () => { ).toMatch('--density-scale: 0;'); }); - it('should check what information the theme has', () => { + it('should check that the theme has all the information', () => { const css = transpile(` $theme: mat.define-theme(); - $color-only: mat.define-colors(); - $typography-only: mat.define-typography(); - $density-only: mat.define-density(); div { --base: #{( mat.theme-has($theme, base), - mat.theme-has($color-only, base), - mat.theme-has($typography-only, base), - mat.theme-has($density-only, base), - )}; - --color: #{( mat.theme-has($theme, color), - mat.theme-has($color-only, color), - mat.theme-has($typography-only, color), - mat.theme-has($density-only, color), - )}; - --typography: #{( mat.theme-has($theme, typography), - mat.theme-has($color-only, typography), - mat.theme-has($typography-only, typography), - mat.theme-has($density-only, typography), - )}; - --density: #{( mat.theme-has($theme, density), - mat.theme-has($color-only, density), - mat.theme-has($typography-only, density), - mat.theme-has($density-only, density), )}; } `); - expect(css).toMatch(/--base: true, false, false, false;/); - expect(css).toMatch(/--color: true, true, false, false;/); - expect(css).toMatch(/--typography: true, false, true, false;/); - expect(css).toMatch(/--density: true, false, false, true;/); - }); - - it('should error when reading theme type from a theme with no color information', () => { - expect(() => - transpile(` - $theme: mat.define-density(); - div { - color: mat.get-theme-type($theme); - } - `), - ).toThrowError(/Color information is not available on this theme/); - }); - - it('should error when reading color from a theme with no color information', () => { - expect(() => - transpile(` - $theme: mat.define-density(); - div { - color: mat.get-theme-color($theme, primary); - } - `), - ).toThrowError(/Color information is not available on this theme/); - }); - - it('should error when reading typography from a theme with no typography information', () => { - expect(() => - transpile(` - $theme: mat.define-density(); - div { - font: mat.get-theme-typography($theme, body-small); - } - `), - ).toThrowError(/Typography information is not available on this theme/); - }); - - it('should error when reading density from a theme with no density information', () => { - expect(() => - transpile(` - $theme: mat.define-colors(); - div { - --density: #{mat.get-theme-density($theme)}; - } - `), - ).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(mat.define-theme(), base, color, typography, density); - div { - @include mat.all-component-themes($theme); - }`); - expect(css.trim()).toBe(''); + expect(css).toMatch(/--base: true, true, true, true;/); }); }); }); diff --git a/src/material/core/tokens/_m3-system.scss b/src/material/core/tokens/_m3-system.scss index 19c9e47a0a16..8958b456d491 100644 --- a/src/material/core/tokens/_m3-system.scss +++ b/src/material/core/tokens/_m3-system.scss @@ -64,9 +64,40 @@ $color: map.set($color, theme-type, color-scheme); } - $color-config: if($is-palette, - definition.define-colors((primary: $color, theme-type: color-scheme)), - definition.define-colors($color)); + $color-config: $color; + @if ($is-palette) { + $color: map.set($color, tertiary, $color); + $color-config: ( + definition.$internals: ( + palettes: ( + primary: map.remove($color, neutral, neutral-variant, secondary), + secondary: map.get($color, secondary), + tertiary: map.remove($color, neutral, neutral-variant, secondary, error), + neutral: map.get($color, neutral), + neutral-variant: map.get($color, neutral-variant), + error: map.get($color, error), + ), + theme-type: color-scheme, + ) + ); + } @else { + $primary: map.get($color, primary) or palettes.$violet-palette; + $tertiary: map.get($color, tertiary) or $primary; + $color-config: ( + definition.$internals: ( + palettes: ( + primary: map.remove($primary, neutral, neutral-variant, secondary), + secondary: map.get($primary, secondary), + tertiary: map.remove($tertiary, neutral, neutral-variant, secondary, error), + neutral: map.get($primary, neutral), + neutral-variant: map.get($primary, neutral-variant), + error: map.get($primary, error), + ), + theme-type: map.get($color, theme-type), + ) + ); + } + @include system-level-colors($color-config, $overrides, definition.$system-fallback-prefix); @include system-level-elevation($color-config, $overrides, definition.$system-fallback-prefix); } @@ -74,9 +105,31 @@ $typography: map.get($config, typography); $typography-config: null; @if ($typography) { - $typography-config: if(meta.type-of($typography) == 'map', - definition.define-typography($typography), - definition.define-typography((plain-family: $typography))); + $plain: (Roboto, sans-serif); + $brand: $plain; + $bold: 700; + $medium: 500; + $regular: 400; + @if (meta.type-of($typography) == map) { + $plain: map.get($typography, plain-family); + $brand: map.get($typography, brand-family) or $plain; + $bold: map.get($typography, bold-weight) or $bold; + $medium: map.get($typography, medium-weight) or $medium; + $regular: map.get($typography, regular-weight) or $regular; + } @else { + $plain: $typography; + } + $typography-config: ( + definition.$internals: ( + font-definition: ( + plain: $plain, + brand: $brand, + bold: $bold, + medium: $medium, + regular: $regular, + ) + ) + ); @include system-level-typography( $typography-config, $overrides, definition.$system-fallback-prefix); } @@ -84,10 +137,12 @@ $density: map.get($config, density); $density-config: null; @if ($density) { - $density-config: if(meta.type-of($density) == 'map', - definition.define-density($density), - definition.define-density((scale: $density))); - $scale: map.get($density-config, _mat-theming-internals-do-not-access, density-scale); + $scale: 0; + @if (meta.type-of($density) == map) { + $scale: map.get($density, scale); + } @else { + $scale: $density; + } @if ($scale != 0) { // Emit component-level density tokens if the scale is lower than 0. The density tokens // do not fallback to any system-level values and must be defined if the scale is different. @@ -153,11 +208,11 @@ } @mixin system-level-colors($theme, $overrides: (), $prefix: null) { - $palettes: map.get($theme, _mat-theming-internals-do-not-access, palettes); - $type: map.get($theme, _mat-theming-internals-do-not-access, theme-type); + $palettes: map.get($theme, definition.$internals, palettes); + $type: map.get($theme, definition.$internals, theme-type); @if (not $prefix) { - $prefix: map.get($theme, _mat-theming-internals-do-not-access, + $prefix: map.get($theme, definition.$internals, color-system-variables-prefix) or definition.$system-level-prefix; } @@ -201,10 +256,10 @@ } @mixin system-level-typography($theme, $overrides: (), $prefix: null) { - $font-definition: map.get($theme, _mat-theming-internals-do-not-access, font-definition); + $font-definition: map.get($theme, definition.$internals, font-definition); @if (not $prefix) { - $prefix: map.get($theme, _mat-theming-internals-do-not-access, + $prefix: map.get($theme, definition.$internals, typography-system-variables-prefix) or definition.$system-level-prefix; } @@ -217,7 +272,7 @@ @mixin system-level-elevation($theme, $overrides: (), $prefix: definition.$system-level-prefix) { $shadow-color: map.get( - $theme, _mat-theming-internals-do-not-access, color-tokens, (mat, theme), shadow); + $theme, definition.$internals, palettes, neutral, 0); @each $name, $value in m3.md-sys-elevation-values() { $level: map.get($overrides, $name) or $value; @@ -259,35 +314,6 @@ // system fallback variables referencing Material's system keys. // Includes density token fallbacks where density is 0. @function create-system-fallbacks() { - $palettes: m3.md-sys-color-values-light(palettes.$blue-palette); - $palettes: map.set($palettes, primary, palettes.$blue-palette); - $app-vars: ( - 'md-sys-color': - _create-system-app-vars-map(m3.md-sys-color-values-light($palettes)), - 'md-sys-typescale': - _create-system-app-vars-map(m3.md-sys-typescale-values(( - brand: (Roboto), - plain: (Roboto), - bold: 700, - medium: 500, - regular: 400 - ))), - 'md-sys-elevation': - _create-system-app-vars-map(m3.md-sys-elevation-values()), - 'md-sys-state': - _create-system-app-vars-map(m3.md-sys-state-values()), - 'md-sys-shape': - _create-system-app-vars-map(m3.md-sys-shape-values()), - // Add a subset of palette-specific colors used by components instead of system values - 'md-ref-palette': - _create-system-app-vars-map( - ( - neutral10: '', // Form field native select option text color - neutral-variant20: '', // Sidenav scrim (container background shadow when opened), - ) - ), - ); - @return sass-utils.deep-merge-all( m3-tokens.generate-tokens($app-vars, true, true), get-density-tokens(0), @@ -302,3 +328,28 @@ } @return $result; } + +$placeholder-palettes: m3.md-sys-color-values-light(palettes.$blue-palette); +$placeholder-palettes: map.set($placeholder-palettes, primary, palettes.$blue-palette); +$app-vars: ( + 'md-sys-color': _create-system-app-vars-map(m3.md-sys-color-values-light($placeholder-palettes)), + 'md-sys-typescale': _create-system-app-vars-map(m3.md-sys-typescale-values(( + brand: (Roboto), + plain: (Roboto), + bold: 700, + medium: 500, + regular: 400 + ))), + 'md-sys-elevation': _create-system-app-vars-map(m3.md-sys-elevation-values()), + 'md-sys-state': _create-system-app-vars-map(m3.md-sys-state-values()), + 'md-sys-shape': _create-system-app-vars-map(m3.md-sys-shape-values()), + // Add a subset of palette-specific colors used by components instead of system values + 'md-ref-palette': _create-system-app-vars-map( + ( + neutral10: '', // Form field native select option text color + neutral-variant20: '', // Sidenav scrim (container background shadow when opened), + ) + ), +); + +$theme-with-system-vars: (definition.$internals: $app-vars); diff --git a/src/material/core/tokens/_m3-tokens.scss b/src/material/core/tokens/_m3-tokens.scss index aaf13937685d..4ae58eecaab4 100644 --- a/src/material/core/tokens/_m3-tokens.scss +++ b/src/material/core/tokens/_m3-tokens.scss @@ -1,5 +1,4 @@ @use '../../autocomplete/m3-autocomplete'; -@use '../../badge/m3-badge'; @use '../../bottom-sheet/m3-bottom-sheet'; @use '../../button-toggle/m3-button-toggle'; @use '../../button/m3-button'; @@ -110,7 +109,7 @@ $_cached-token-slots: null; @return $result; } -@function _get-sys-color($type, $palettes, $prefix) { +@function get-sys-color($type, $palettes, $prefix) { $sys-color: if($type == dark, m3.md-sys-color-values-dark($palettes), m3.md-sys-color-values-light($palettes)); @@ -127,7 +126,7 @@ $_cached-token-slots: null; @return $sys-color; } -@function _get-sys-typeface($typography, $prefix) { +@function get-sys-typeface($typography, $prefix) { $sys-typography: m3.md-sys-typescale-values($typography); @if (sass-utils.$use-system-typography-variables) { $var-values: (); @@ -148,7 +147,7 @@ $_cached-token-slots: null; /// @param {String} $system-variables-prefix The prefix of system tokens /// @return {Map} A map of namespaced color tokens @function generate-color-tokens($type, $palettes, $system-variables-prefix) { - $sys-color: _get-sys-color($type, $palettes, $system-variables-prefix); + $sys-color: get-sys-color($type, $palettes, $system-variables-prefix); @return generate-tokens(( md-sys-color: $sys-color, @@ -176,7 +175,7 @@ $_cached-token-slots: null; /// @param {String} $system-variables-prefix The prefix of system tokens /// @return {Map} A map of namespaced typography tokens @function generate-typography-tokens($typography, $system-variables-prefix) { - $sys-typeface: _get-sys-typeface($typography, $system-variables-prefix); + $sys-typeface: get-sys-typeface($typography, $system-variables-prefix); @return generate-tokens(( md-sys-typescale: $sys-typeface )); @@ -202,7 +201,6 @@ $_cached-token-slots: null; $tokens-list: ( m3-app.get-tokens($systems, $exclude-hardcoded, $token-slots), m3-autocomplete.get-tokens($systems, $exclude-hardcoded, $token-slots), - m3-badge.get-tokens($systems, $exclude-hardcoded, $token-slots), m3-bottom-sheet.get-tokens($systems, $exclude-hardcoded, $token-slots), m3-button-toggle.get-tokens($systems, $exclude-hardcoded, $token-slots), m3-button.get-tokens($systems, $exclude-hardcoded, $token-slots), diff --git a/src/material/core/tokens/_m3-utils.scss b/src/material/core/tokens/_m3-utils.scss index 9efd6c5cab76..e22c9bb17a01 100644 --- a/src/material/core/tokens/_m3-utils.scss +++ b/src/material/core/tokens/_m3-utils.scss @@ -1,6 +1,7 @@ @use 'sass:map'; @use 'sass:list'; @use 'sass:meta'; +@use 'sass:string'; /// Gets the MDC tokens for the given prefix, M3 token values, and supported token slots. /// @param {List} $prefix The token prefix for the given tokens. @@ -83,3 +84,37 @@ } @return $result; } + +// Replaces color tokens in the map with those defined as the variant color. +@function replace-colors-with-variant($system, $color, $variant) { + $system: map.set($system, on-#{$color}, map.get($system, on-#{$variant})); + $system: map.set($system, on-#{$color}-container, map.get($system, on-#{$variant}-container)); + $system: map.set($system, #{$color}, map.get($system, #{$variant})); + $system: map.set($system, #{$color}-container, map.get($system, #{$variant}-container)); + @return $system; +} + +// Gets the theme's system values as a flat map. +@function get-system($theme) { + $system: (); + $system: map.merge($system, + map.get($theme, _mat-theming-internals-do-not-access, md-sys-color)); + $system: map.merge($system, + map.get($theme, _mat-theming-internals-do-not-access, md-sys-elevation)); + $system: map.merge($system, + map.get($theme, _mat-theming-internals-do-not-access, md-sys-shape)); + $system: map.merge($system, + map.get($theme, _mat-theming-internals-do-not-access, md-sys-state)); + $system: map.merge($system, + map.get($theme, _mat-theming-internals-do-not-access, md-sys-typescale)); + @return $system; +} + +// Returns the color with an opacity value using color-mix. If the color is a variable name, it +// will wrap it with `var()`. +@function color-with-opacity($color, $opacity) { + @if (meta.type-of($color) == string and string.index($color, '--') == 1) { + $color: var($color); + } + @return color-mix(in srgb, #{$color} #{$opacity}, transparent); +} diff --git a/src/material/core/tokens/_token-utils.scss b/src/material/core/tokens/_token-utils.scss index f18ddc7123bf..b1e1e966b863 100644 --- a/src/material/core/tokens/_token-utils.scss +++ b/src/material/core/tokens/_token-utils.scss @@ -9,16 +9,29 @@ $_tokens: null; $_component-prefix: null; $_system-fallbacks: m3-system.create-system-fallbacks(); +$_direct-system-fallbacks: (); // Sets the token prefix and map to use when creating token slots. -@mixin use-tokens($prefix, $tokens) { +@mixin use-tokens($prefix, $tokens, $direct-system-fallbacks: null) { $_component-prefix: $prefix !global; $_tokens: $tokens !global; + // Direct system fallbacks are a map of base, color, typography, and density tokens. To simplify + // lookup, flatten these token groups into a single map. + @if $direct-system-fallbacks { + $_direct-system-fallbacks: () !global; + @each $tokens in map.values($direct-system-fallbacks) { + @each $token, $value in $tokens { + $_direct-system-fallbacks: map.set($_direct-system-fallbacks, $token, $value) !global; + } + } + } + @content; $_component-prefix: null !global; $_tokens: null !global; + $_direct-system-fallbacks: () !global; } // Combines a prefix and a string to generate a CSS variable name for a token. @@ -90,6 +103,14 @@ $_system-fallbacks: m3-system.create-system-fallbacks(); $fallback: map.get($_tokens, $token); } + $direct-sys-fallback: map.get($_direct-system-fallbacks, $token); + @if ($direct-sys-fallback) { + @if (sass-utils.is-css-var-name($direct-sys-fallback)) { + @return _create-var($direct-sys-fallback, $fallback); + } + @return $direct-sys-fallback; + } + // Check whether there's a system-level fallback. If not, return the optional // provided fallback (otherwise null). $sys-fallback: map.get($_system-fallbacks, $_component-prefix, $token); diff --git a/src/material/form-field/form-field.ts b/src/material/form-field/form-field.ts index c3c81b054553..c737e5e2eed9 100644 --- a/src/material/form-field/form-field.ts +++ b/src/material/form-field/form-field.ts @@ -106,6 +106,11 @@ export const MAT_FORM_FIELD_DEFAULT_OPTIONS = new InjectionToken { - if (this._appearanceSignal() === 'outline') { - this._updateOutlineLabelOffset(); - if (!globalThis.ResizeObserver) { - return; + afterRenderEffect({ + earlyRead: () => { + if (this._appearanceSignal() !== 'outline') { + this._outlineLabelOffsetResizeObserver?.disconnect(); + return null; } // Setup a resize observer to monitor changes to the size of the prefix / suffix and // readjust the label offset. - this._outlineLabelOffsetResizeObserver ||= new globalThis.ResizeObserver(() => - this._updateOutlineLabelOffset(), - ); - for (const el of this._prefixSuffixContainers()) { - this._outlineLabelOffsetResizeObserver.observe(el, {box: 'border-box'}); + if (globalThis.ResizeObserver) { + this._outlineLabelOffsetResizeObserver ||= new globalThis.ResizeObserver(() => { + this._writeOutlinedLabelStyles(this._getOutlinedLabelOffset()); + }); + for (const el of this._prefixSuffixContainers()) { + this._outlineLabelOffsetResizeObserver.observe(el, {box: 'border-box'}); + } } - } else { - this._outlineLabelOffsetResizeObserver?.disconnect(); - } + + return this._getOutlinedLabelOffset(); + }, + write: labelStyles => this._writeOutlinedLabelStyles(labelStyles()), }); } @@ -740,7 +745,7 @@ export class MatFormField } /** - * Updates the horizontal offset of the label in the outline appearance. In the outline + * Calculates the horizontal offset of the label in the outline appearance. In the outline * appearance, the notched-outline and label are not relative to the infix container because * the outline intends to surround prefixes, suffixes and the infix. This means that the * floating label by default overlaps prefixes in the docked state. To avoid this, we need to @@ -748,22 +753,20 @@ export class MatFormField * not need to do this because they use a fixed width for prefixes. Hence, they can simply * incorporate the horizontal offset into their default text-field styles. */ - private _updateOutlineLabelOffset() { + private _getOutlinedLabelOffset(): OutlinedLabelStyles { const dir = this._dir.valueSignal(); if (!this._hasOutline() || !this._floatingLabel) { - return; + return null; } - const floatingLabel = this._floatingLabel.element; // If no prefix is displayed, reset the outline label offset from potential // previous label offset updates. - if (!(this._iconPrefixContainer || this._textPrefixContainer)) { - floatingLabel.style.transform = ''; - return; + if (!this._iconPrefixContainer && !this._textPrefixContainer) { + return ['', null]; } // If the form field is not attached to the DOM yet (e.g. in a tab), we defer // the label offset update until the zone stabilizes. if (!this._isAttachedToDom()) { - return; + return null; } const iconPrefixContainer = this._iconPrefixContainer?.nativeElement; const textPrefixContainer = this._textPrefixContainer?.nativeElement; @@ -783,19 +786,33 @@ export class MatFormField // Update the translateX of the floating label to account for the prefix container, // but allow the CSS to override this setting via a CSS variable when the label is // floating. - floatingLabel.style.transform = `var( - --mat-mdc-form-field-label-transform, - ${FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM} translateX(${labelHorizontalOffset}) - )`; + const floatingLabelTransform = + 'var(--mat-mdc-form-field-label-transform, ' + + `${FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM} translateX(${labelHorizontalOffset}))`; // Prevent the label from overlapping the suffix when in resting position. - const prefixAndSuffixWidth = + const notchedOutlineWidth = iconPrefixContainerWidth + textPrefixContainerWidth + iconSuffixContainerWidth + textSuffixContainerWidth; - this._notchedOutline?._setMaxWidth(prefixAndSuffixWidth); + return [floatingLabelTransform, notchedOutlineWidth]; + } + + /** Writes the styles produced by `_getOutlineLabelOffset` synchronously to the DOM. */ + private _writeOutlinedLabelStyles(styles: OutlinedLabelStyles): void { + if (styles !== null) { + const [floatingLabelTransform, notchedOutlineWidth] = styles; + + if (this._floatingLabel) { + this._floatingLabel.element.style.transform = floatingLabelTransform; + } + + if (notchedOutlineWidth !== null) { + this._notchedOutline?._setMaxWidth(notchedOutlineWidth); + } + } } /** Checks whether the form field is attached to the DOM. */ diff --git a/tools/extract-tokens/extract-tokens.ts b/tools/extract-tokens/extract-tokens.ts index dcd0715048b5..178a7b664677 100644 --- a/tools/extract-tokens/extract-tokens.ts +++ b/tools/extract-tokens/extract-tokens.ts @@ -323,7 +323,7 @@ function getTokenExtractionCode( $fallback-type: ${inferTokenType}($name, $resolved-value); @if ($fallback-type == null) { - @error 'Cannot determine type of token "#{$name}". Token extraction script needs to be updated.'; + $fallback-type: base; } $type: $fallback-type;