@use 'sass:list'; @use 'sass:map'; @use 'sass:math'; @use 'sass:meta'; @use 'sass:color'; // Whether to enable compatibility with legacy methods for accessing theme information. $theme-legacy-inspection-api-compatibility: true !default; // Whether duplication warnings should be disabled. Warnings enabled by default. $theme-ignore-duplication-warnings: false !default; // Whether density should be generated by default. $_generate-default-density: true !default; // Warning that will be printed if duplicated styles are generated by a theme. $_duplicate-warning: 'Read more about how style duplication can be avoided in a dedicated ' + 'guide. https://v18.material.angular.dev/guide/duplicate-theming-styles'; // Warning that will be printed if the legacy theming API is used. $private-legacy-theme-warning: 'Angular Material themes should be created from a map containing ' + 'the keys "color", "typography", and "density". The color value should be a map containing the ' + 'palette values for "primary", "accent", and "warn". ' + 'See https://material.angular.dev/guide/theming for more information.'; // Flag whether to disable theme definitions copying color values to the top-level theme config. // This copy is to preserve backwards compatibility. $_disable-color-backwards-compatibility: false; // These variable are not intended to be overridden externally. They use `!default` to // avoid being reset every time this file is imported. $_emitted-color: () !default; $_emitted-typography: () !default; $_emitted-density: () !default; $_emitted-base: () !default; // // Private APIs // $private-internal-name: _mat-theming-internals-do-not-access; // Checks if configurations that have been declared in the given theme have been generated // before. If so, warnings will be reported. This should notify developers in case duplicate // styles are accidentally generated due to wrong usage of the all-theme mixins. // // Additionally, this mixin controls the default value for the density configuration. By // default, density styles are generated at scale zero. If the same density styles would be // generated a second time though, the default value will change to avoid duplicate styles. // // The mixin keeps track of all configurations in a list that is scoped to the specified // id. This is necessary because a given theme can be passed to multiple disjoint theme mixins // (e.g. `all-component-themes` and `all-legacy-component-themes`) without causing any // style duplication. @mixin private-check-duplicate-theme-styles($theme-or-color-config, $id) { // TODO(mmalerba): use get-theme-version for this check when its moved out of experimental. @if map.get($theme-or-color-config, $private-internal-name, theme-version) == 1 { @include _check-duplicate-theme-styles-v1($theme-or-color-config, $id) { // Optionally, consumers of this mixin can wrap contents inside so that nested // duplicate style checks do not report another warning. e.g. if developers include // the `all-component-themes` mixin twice, only the top-level duplicate styles check // should report a warning. Not all individual components should report a warning too. $orig-mat-theme-ignore-duplication-warnings: $theme-ignore-duplication-warnings; $theme-ignore-duplication-warnings: true !global; @content; $theme-ignore-duplication-warnings: $orig-mat-theme-ignore-duplication-warnings !global; } } @else { @include _check-duplicate-theme-styles-v0($theme-or-color-config, $id) { // Optionally, consumers of this mixin can wrap contents inside so that nested // duplicate style checks do not report another warning. e.g. if developers include // the `all-component-themes` mixin twice, only the top-level duplicate styles check // should report a warning. Not all individual components should report a warning too. $orig-mat-theme-ignore-duplication-warnings: $theme-ignore-duplication-warnings; $theme-ignore-duplication-warnings: true !global; @content; $theme-ignore-duplication-warnings: $orig-mat-theme-ignore-duplication-warnings !global; } } } /// Strip out any settings map entries that have empty values (null or ()). @function _strip-empty-settings($settings) { $result: (); @each $key, $value in $settings { @if $value != null and $value != () { $result: map.set($result, $key, $value); } } @return if($result == (), null, $result); } // Checks for duplicate styles in a `theme-version: 1` style theme. @mixin _check-duplicate-theme-styles-v1($theme-or-color-config, $id) { $color-settings: _strip-empty-settings(( theme-type: map.get($theme-or-color-config, $private-internal-name, theme-type), color-tokens: map.get($theme-or-color-config, $private-internal-name, color-tokens), )); $typography-settings: _strip-empty-settings(( typography-tokens: map.get($theme-or-color-config, $private-internal-name, typography-tokens), )); $density-settings: _strip-empty-settings(( density-scale: map.get($theme-or-color-config, $private-internal-name, density-scale), density-tokens: map.get($theme-or-color-config, $private-internal-name, density-tokens), )); $base-settings: _strip-empty-settings(( base-tokens: map.get($theme-or-color-config, $private-internal-name, base-tokens), )); $previous-color-settings: map.get($_emitted-color, $id) or (); $previous-typography-settings: map.get($_emitted-typography, $id) or (); $previous-density-settings: map.get($_emitted-density, $id) or (); $previous-base-settings: map.get($_emitted-base, $id) or (); // Check if the color configuration has been generated before. @if $color-settings != null { @if list.index($previous-color-settings, $color-settings) != null and not $theme-ignore-duplication-warnings { @warn 'The same color styles are generated multiple times. ' + $_duplicate-warning; } $previous-color-settings: list.append($previous-color-settings, $color-settings); } // Check if the typography configuration has been generated before. @if $typography-settings != null { @if list.index($previous-typography-settings, $typography-settings) != null and not $theme-ignore-duplication-warnings { @warn 'The same typography styles are generated multiple times. ' + $_duplicate-warning; } $previous-typography-settings: list.append($previous-typography-settings, $typography-settings); } // Check if the density configuration has been generated before. @if $density-settings != null { @if list.index($previous-density-settings, $density-settings) != null and not $theme-ignore-duplication-warnings { @warn 'The same density styles are generated multiple times. ' + $_duplicate-warning; } $previous-density-settings: list.append($previous-density-settings, $density-settings); } // Check if the base configuration has been generated before. @if $base-settings != null { @if list.index($previous-base-settings, $base-settings) != null and not $theme-ignore-duplication-warnings { @warn 'The same base theme styles are generated multiple times. ' + $_duplicate-warning; } $previous-base-settings: list.append($previous-base-settings, $base-settings); } $_emitted-color: map.set($_emitted-color, $id, $previous-color-settings) !global; $_emitted-density: map.set($_emitted-density, $id, $previous-density-settings) !global; $_emitted-typography: map.set($_emitted-typography, $id, $previous-typography-settings) !global; $_emitted-base: map.set($_emitted-base, $id, $previous-base-settings) !global; @content; } // Checks for duplicate styles in a `theme-version: 0` style theme. @mixin _check-duplicate-theme-styles-v0($theme-or-color-config, $id) { $theme: private-legacy-get-theme($theme-or-color-config); $color-config: map.get($theme, $private-internal-name, m2-config, color) or private-get-color-config($theme); $density-config: map.get($theme, $private-internal-name, m2-config, density) or private-get-density-config($theme); $typography-config: map.get($theme, $private-internal-name, m2-config, typography) or private-get-typography-config($theme); // Lists of previous `color`, `density` and `typography` configurations. $previous-color: map.get($_emitted-color, $id) or (); $previous-typography: map.get($_emitted-typography, $id) or (); $previous-density: map.get($_emitted-density, $id) or (); // Whether duplicate legacy density styles would be generated. $duplicate-legacy-density: false; // Check if the color configuration has been generated before. @if $color-config != null { @if list.index($previous-color, $color-config) != null and not $theme-ignore-duplication-warnings { @warn 'The same color styles are generated multiple times. ' + $_duplicate-warning; } $previous-color: list.append($previous-color, $color-config); } // Check if the typography configuration has been generated before. @if $typography-config != null { @if list.index($previous-typography, $typography-config) != null and not $theme-ignore-duplication-warnings { @warn 'The same typography styles are generated multiple times. ' + $_duplicate-warning; } $previous-typography: list.append($previous-typography, $typography-config); } // Check if the density configuration has been generated before. @if $density-config != null { @if list.index($previous-density, $density-config) != null { // Only report a warning if density styles would be duplicated for non-legacy theme // definitions. For legacy themes, we have compatibility logic that avoids duplication // of default density styles. We don't want to report a warning in those cases. @if private-is-legacy-constructed-theme($theme) { $duplicate-legacy-density: true; } @else if not $theme-ignore-duplication-warnings { @warn 'The same density styles are generated multiple times. ' + $_duplicate-warning; } } $previous-density: list.append($previous-density, $density-config); } $_emitted-color: map.merge($_emitted-color, ($id: $previous-color)) !global; $_emitted-density: map.merge($_emitted-density, ($id: $previous-density)) !global; $_emitted-typography: map.merge($_emitted-typography, ($id: $previous-typography)) !global; @content; } // Checks whether the given value resolves to a theme object. Theme objects are always // of type `map` and can optionally only specify `color`, `density` or `typography`. @function private-is-theme-object($value) { @return meta.type-of($value) == 'map' and ( map.has-key($value, color) or map.has-key($value, density) or map.has-key($value, typography) or list.length($value) == 0 ); } // Checks whether a given value corresponds to a legacy constructed theme. @function private-is-legacy-constructed-theme($value) { @return meta.type-of($value) == 'map' and map.get($value, '_is-legacy-theme'); } // This is the implementation of the `m2-get-color-config` function. // It's declared here to avoid a circular reference between this file and `m2/_theming.scss`. @function private-get-color-config($theme, $default: null) { // If a configuration has been passed, return the config directly. @if not private-is-theme-object($theme) { @return $theme; } // If the theme has been constructed through the legacy theming API, we use the theme object // as color configuration instead of the dedicated `color` property. We do this because for // backwards compatibility, we copied the color configuration from `$theme.color` to `$theme`. // Hence developers could customize the colors at top-level and want to respect these changes // TODO: Remove when legacy theming API is removed. @if private-is-legacy-constructed-theme($theme) { @return $theme; } @if map.has-key($theme, color) { @return map.get($theme, color); } @return $default; } // This is the implementation of the `m2-get-density-config` function. // It's declared here to avoid a circular reference between this file and `m2/_theming.scss`. @function private-get-density-config($theme-or-config, $default: 0) { // If a configuration has been passed, return the config directly. @if not private-is-theme-object($theme-or-config) { @return $theme-or-config; } // In case a theme has been passed, extract the configuration if present, // or fall back to the default density config. @if map.has-key($theme-or-config, density) { @return map.get($theme-or-config, density); } @return $default; } // This is the implementation of the `m2-get-typography-config` function. // It's declared here to avoid a circular reference between this file and `m2/_theming.scss`. @function private-get-typography-config($theme-or-config, $default: null) { // If a configuration has been passed, return the config directly. @if not private-is-theme-object($theme-or-config) { @return $theme-or-config; } // In case a theme has been passed, extract the configuration if present, // or fall back to the default typography config. @if (map.has-key($theme-or-config, typography)) { @return map.get($theme-or-config, typography); } @return $default; } // Creates a backwards compatible theme. Previously in Angular Material, theme objects // contained the color configuration directly. With the recent refactoring of the theming // system to allow for density and typography configurations, this is no longer the case. // To ensure that constructed themes which will be passed to custom theme mixins do not break, // we copy the color configuration and put its properties at the top-level of the theme object. // Here is an example of a pattern that should still work until it's officially marked as a // breaking change: // // @mixin my-custom-component-theme($theme) { // .my-comp { // background-color: mat.m2-get-color-from-palette(map.get($theme, primary)); // } // } // // Note that the `$theme.primary` key does usually not exist since the color configuration // is stored in `$theme.color` which contains a property for `primary`. This method copies // the map from `$theme.color` to `$theme` for backwards compatibility. @function private-create-backwards-compatibility-theme($theme) { @if ($_disable-color-backwards-compatibility or not map.get($theme, color)) { @return $theme; } $color: map.get($theme, color); @return map.merge($theme, $color); } // Gets the theme from the given value that is either already a theme, or a color configuration. // This handles the legacy case where developers pass a color configuration directly to the // theme mixin. Before we introduced the new pattern for constructing a theme, developers passed // the color configuration directly to the theme mixins. This can be still the case if developers // construct a theme manually and pass it to a theme. We support this for backwards compatibility. // TODO(devversion): remove this in the future. Constructing themes manually is rare, // and the code can be easily updated to the new API. @function private-legacy-get-theme($theme-or-color-config) { @if private-is-theme-object($theme-or-color-config) or map.get($theme-or-color-config, $private-internal-name, theme-version) == 1 { @return $theme-or-color-config; } @warn $private-legacy-theme-warning; @return private-create-backwards-compatibility-theme(( _is-legacy-theme: true, color: $theme-or-color-config )); } // Approximates an rgba color into a solid hex color, given a background color. @function private-rgba-to-hex($color, $background-color) { // We convert the rgba color into a solid one by taking the opacity from the rgba // value and using it to determine the percentage of the background to put // into foreground when mixing the colors together. @return color.mix($background-color, rgba($color, 1), (1 - color.opacity($color)) * 100%); } // Clamps the density scale to a number between the given min and max. // 'minimum' and 'maximum' are converted to the given min or max number respectively. @function clamp-density($density-scale, $min, $max: 0) { @if $density-scale == minimum { @return $min; } @if $density-scale == maximum { @return $max; } @if meta.type-of($density-scale) != 'number' or not math.is-unitless($density-scale) { @return 0; } @if $density-scale < $min { @return $min; } @if $density-scale > $max { @return $max; } @return $density-scale; }