@use '../style/elevation'; @use '../style/sass-utils'; @use '../theming/config-validation'; @use '../theming/definition'; @use './m3/definitions'; @use 'sass:map'; @use 'sass:meta'; @use 'sass:list'; @use './m3-tokens'; /// Emits necessary CSS variables for Material's system level values for the values defined in the /// config map. The config map can have values color, typography, and/or density. /// /// If the config map's color value is an Angular Material color palette, it will be used as the /// primary and tertiary colors with a `color-scheme` theme type. Otherwise if the color value is a /// map, it must have a `primary` value containing an Angular Material color palette, and /// optionally a different `tertiary` palette (defaults to primary palette) and `theme-type` that /// is either `light`, `dark`, or 'color-scheme` (defaults to `color-scheme`). Color variable /// definitions will not be emitted if there are no color values in the config. /// /// If the config map's typography value is a font family string, it will be used as the /// plain and brand font family with default bold, medium, and regular weights of 700, 500, and 400, /// respectfully. Otherwise if the typography value is a map, it must have a `plain-family` font /// family value, and optionally a different `brand-family` font family (defaults to the plain /// value) and weights for `bold-weight` (default: 700), `bold-weight` (default: 500), and /// `bold-weight` (default: 400). Typography variable definitions will not be emitted if there are /// no typography values in the config. /// /// If the config map's density value is a number, it will be used as the density scale. Otherwise /// if the density value is a map, it must have a `scale` value. Density variable definitions will /// not be emitted if there are no density values in the config. /// /// The application variables emitted use the namespace prefix "--mat-sys". /// e.g. --mat-sys-surface: #E5E5E5 /// /// @param {Map} $config The color configuration with optional keys color, typography, or density. @mixin theme($config, $overrides: ()) { $color: map.get($config, color); $color-config: null; @if ($color) { // validate-palette returns null if it is a valid M3 palette $is-palette: config-validation.validate-palette($color) == null; // Default to "color-scheme" theme type if the config's color does not provide one. @if (not $is-palette and not map.has-key($color, theme-type)) { $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)); @include system-level-colors($color-config, $overrides, definition.$system-fallback-prefix); @include system-level-elevation($color-config, $overrides, definition.$system-fallback-prefix); } $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))); @include system-level-typography( $typography-config, $overrides, definition.$system-fallback-prefix); } $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); @if ($scale != 0) { $all-tokens: m3-tokens.generate-density-tokens($scale); @each $component-tokens in $all-tokens { $namespace: list.nth($component-tokens, 1); @each $tokens in list.nth($component-tokens, 2) { --#{list.nth($namespace, 1)}-#{list.nth($namespace, 2)}-#{ list.nth($tokens, 1)}: #{list.nth($tokens, 2)}; } } } } @include system-level-shape($overrides: $overrides, $prefix: definition.$system-fallback-prefix); @include system-level-state($overrides: $overrides, $prefix: definition.$system-fallback-prefix); } /// Emits the system-level CSS variables for each of the provided override values. E.g. to /// change the primary color to red, use `mat.theme-overrides((primary: red));` @mixin theme-overrides($overrides, $prefix: definition.$system-fallback-prefix) { $sys-names: map-merge-all( definitions.md-sys-color-values-light(), definitions.md-sys-typescale-values(), definitions.md-sys-elevation-values(), definitions.md-sys-shape-values(), definitions.md-sys-state-values()); & { @each $name, $value in $overrides { @if (map.has-key($sys-names, $name)) { --#{$prefix}-#{$name}: #{map.get($overrides, $name)}; } } } } @mixin system-level-colors($theme, $overrides: (), $prefix: null) { $palettes: map.get($theme, _mat-theming-internals-do-not-access, palettes); $base-palettes: ( neutral: map.get($palettes, neutral), neutral-variant: map.get($palettes, neutral-variant), secondary: map.get($palettes, secondary), error: map.get($palettes, error), ); $type: map.get($theme, _mat-theming-internals-do-not-access, theme-type); $primary: map.merge(map.get($palettes, primary), $base-palettes); $tertiary: map.merge(map.get($palettes, tertiary), $base-palettes); $error: map.get($palettes, error); @if (not $prefix) { $prefix: map.get($theme, _mat-theming-internals-do-not-access, color-system-variables-prefix) or definition.$system-level-prefix; } $ref: ( md-ref-palette: m3-tokens.generate-ref-palette-tokens($primary, $tertiary, $error) ); $sys-colors: _generate-sys-colors($ref, $type); // Manually insert a subset of palette values that are used directly by components // instead of system variables. $sys-colors: map.set($sys-colors, 'neutral-variant20', map.get($ref, md-ref-palette, neutral-variant20)); $sys-colors: map.set($sys-colors, 'neutral10', map.get($ref, md-ref-palette, neutral10)); & { @each $name, $value in $sys-colors { --#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value}; } } } @function _generate-sys-colors($ref, $type) { $light-sys-colors: definitions.md-sys-color-values-light($ref); @if ($type == light) { @return $light-sys-colors; } $dark-sys-colors: definitions.md-sys-color-values-dark($ref); @if ($type == dark) { @return $dark-sys-colors; } @if ($type == color-scheme) { $light-dark-sys-colors: (); @each $name, $light-value in $light-sys-colors { $dark-value: map.get($dark-sys-colors, $name); $light-dark-sys-colors: map.set($light-dark-sys-colors, $name, light-dark($light-value, $dark-value)); } @return $light-dark-sys-colors; } @error 'Unknown theme-type provided: #{$type}'; } @mixin system-level-typography($theme, $overrides: (), $prefix: null) { $font-definition: map.get($theme, _mat-theming-internals-do-not-access, font-definition); $brand: map.get($font-definition, brand); $plain: map.get($font-definition, plain); $bold: map.get($font-definition, bold); $medium: map.get($font-definition, medium); $regular: map.get($font-definition, regular); $ref: (md-ref-typeface: m3-tokens.generate-ref-typeface-tokens($brand, $plain, $bold, $medium, $regular) ); @if (not $prefix) { $prefix: map.get($theme, _mat-theming-internals-do-not-access, typography-system-variables-prefix) or definition.$system-level-prefix; } & { @each $name, $value in definitions.md-sys-typescale-values($ref) { --#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value}; } } } @mixin system-level-elevation($theme, $overrides: (), $prefix: definition.$system-level-prefix) { $shadow-color: map.get( $theme, _mat-theming-internals-do-not-access, color-tokens, (mdc, theme), shadow); @each $name, $value in definitions.md-sys-elevation-values() { $level: map.get($overrides, $name) or $value; $value: elevation.get-box-shadow($level, $shadow-color); & { --#{$prefix}-#{$name}: #{$value}; } } } @mixin system-level-shape($theme: (), $overrides: (), $prefix: definition.$system-level-prefix) { & { @each $name, $value in definitions.md-sys-shape-values() { --#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value}; } } } @mixin system-level-state($theme: (), $overrides: (), $prefix: definition.$system-level-prefix) { & { @each $name, $value in definitions.md-sys-state-values() { --#{$prefix}-#{$name}: #{map.get($overrides, $name) or $value}; } } } // Return a new map where the values are the same as the provided map's // keys, prefixed with "--mat-sys-". For example: // (key1: '', key2: '') --> (key1: --mat-sys-key1, key2: --mat-sys-key2) @function _create-system-app-vars-map($map) { $new-map: (); @each $key, $value in $map { $new-map: map.set($new-map, $key, --#{definition.$system-fallback-prefix}-#{$key}); } @return $new-map; } // Create a components tokens map where values are based on // system fallback variables referencing Material's system keys. // Includes density token fallbacks where density is 0. @function create-system-fallbacks() { $app-vars: ( 'md-sys-color': _create-system-app-vars-map(definitions.md-sys-color-values-light()), 'md-sys-typescale': _create-system-app-vars-map(definitions.md-sys-typescale-values()), 'md-sys-elevation': _create-system-app-vars-map(definitions.md-sys-elevation-values()), 'md-sys-state': _create-system-app-vars-map(definitions.md-sys-state-values()), 'md-sys-shape': _create-system-app-vars-map(definitions.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), m3-tokens.generate-density-tokens(0) ); } /// Creates a single merged map from the provided variable-length map arguments @function map-merge-all($maps...) { $result: (); @each $map in $maps { $result: map.merge($result, $map); } @return $result; }