359 lines
16 KiB
SCSS
359 lines
16 KiB
SCSS
|
|
@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://github.com/angular/components/blob/main/guides/duplicate-theming-styles.md';
|
||
|
|
|
||
|
|
// 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.io/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;
|
||
|
|
}
|