sass-references/angular-material/material/core/theming/_theming.scss

359 lines
16 KiB
SCSS
Raw Normal View History

2024-12-06 10:42:08 +08:00
@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;
}