sass-references/angular-material/material/core/tokens/_token-utils.scss

306 lines
11 KiB
SCSS
Raw Normal View History

2024-12-06 10:42:08 +08:00
@use '../style/elevation';
@use '../style/sass-utils';
@use './m3-system';
@use 'sass:list';
@use 'sass:map';
@use 'sass:string';
$_tokens: null;
$_component-prefix: null;
$_system-fallbacks: m3-system.create-system-fallbacks();
// Sets the token prefix and map to use when creating token slots.
@mixin use-tokens($prefix, $tokens) {
$_component-prefix: $prefix !global;
$_tokens: $tokens !global;
@content;
$_component-prefix: null !global;
$_tokens: null !global;
}
// Combines a prefix and a string to generate a CSS variable name for a token.
@function _create-var-name($prefix, $token) {
@if $prefix == null or $token == null {
@error 'Must specify both prefix and name when generating token';
}
$string-prefix: '';
// Prefixes are lists so we need to combine them.
@each $part in $prefix {
$string-prefix: if($string-prefix == '', $part, '#{$string-prefix}-#{$part}');
}
@return string.unquote('--#{$string-prefix}-#{$token}');
}
// Creates a CSS variable, including the fallback if provided.
@function _create-var($name, $fallback: null) {
@if ($fallback) {
@return var($name, $fallback);
} @else {
@return var($name);
}
}
// Gets the value of the token given the current global context state.
@function _get-token-value($token, $fallback) {
$var-name: _create-var-name($_component-prefix, $token);
$fallback: _get-token-fallback($token, $fallback);
@return _create-var($var-name, $fallback);
}
// Assertion mixin that throws an error if the global state has not been set up by wrapping
// calls with `use-tokens`.
@function _assert-use-tokens($token) {
@if $_component-prefix == null or $_tokens == null {
@error 'Function was not called within a wrapping call of `use-tokens`';
}
@if not map.has-key($_tokens, $token) {
@error 'Token #{$token} does not exist. Configured tokens are: #{map.keys($_tokens)}';
}
@return true;
}
// Emits a slot for the given token, provided that it has a non-null value in the token map passed
// to `use-tokens`.
// Accepts an optional fallback parameter to include in the CSS variable.
// If $fallback is `true`, then use the tokens map to get the fallback.
// TODO: Remove the use case where we accept "true" and handle any failing client screenshots
@mixin create-token-slot($property, $token, $fallback: null) {
$_assert: _assert-use-tokens($token);
@if map.get($_tokens, $token) != null {
#{$property}: #{_get-token-value($token, $fallback)};
}
}
// Returns the name of a token including the current prefix. Intended to be used in calculations
// involving tokens. `create-token-slot` should be used when outputting tokens.
@function get-token-variable-name($token) {
$_assert: _assert-use-tokens($token);
@return _create-var-name($_component-prefix, $token);
}
// Returns a `var()` reference to a specific token. Intended for declarations
// where the token has to be referenced as a part of a larger expression.
// Accepts an optional fallback parameter to include in the CSS variable.
// If $fallback is `true`, then use the tokens map to get the fallback.
// TODO: Remove the use case where we accept "true" and handle any failing client screenshots
@function get-token-variable($token, $fallback: null) {
$_assert: _assert-use-tokens($token);
@return _get-token-value($token, $fallback);
}
// Gets the token's fallback value. Prefers adding a system-level fallback if one exists, otherwise
// use the provided fallback.
@function _get-token-fallback($token, $fallback: null) {
// If the $fallback is `true`, this is the component's signal to use the current token map value
@if ($fallback == true) {
$fallback: map.get($_tokens, $token);
}
// 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);
@if (not $sys-fallback) {
@return $fallback;
}
@if (sass-utils.is-css-var-name($sys-fallback)) {
@return _create-var($sys-fallback, $fallback);
}
@return $sys-fallback;
}
// Outputs a map of tokens under a specific prefix.
@mixin create-token-values($prefix, $tokens) {
@if $tokens != null {
// TODO: The `&` adds to the file size of theme, but it's necessary for compatibility
// with https://sass-lang.com/documentation/breaking-changes/mixed-decls/. We should
// figure out a better way to do this or move all the concrete styles out of the theme.
& {
@each $key, $value in $tokens {
@if $value != null {
#{_create-var-name($prefix, $key)}: #{$value};
}
}
}
}
}
// MDC doesn't currently handle elevation tokens properly. As a temporary workaround we can combine
// the elevation and shadow-color tokens into a full box-shadow and use it as the value for the
// elevation token.
@function resolve-elevation($tokens, $elevation-token, $shadow-color-token) {
$elevation: map.get($tokens, $elevation-token);
$shadow-color: map.get($tokens, $shadow-color-token);
@return map.merge(
$tokens,
(
$elevation-token: elevation.get-box-shadow($elevation, $shadow-color),
$shadow-color-token: null,
)
);
}
/// Checks whether a list starts wih a given prefix
/// @param {List} $list The list value to check the prefix of.
/// @param {List} $prefix The prefix to check.
/// @return {Boolean} Whether the list starts with the prefix.
@function _is-prefix($list, $prefix) {
@for $i from 1 through list.length($prefix) {
@if list.nth($list, $i) != list.nth($prefix, $i) {
@return false;
}
}
@return true;
}
/// Gets the supported color variants in the given token set for the given prefix.
/// @param {Map} $tokens The full token map.
/// @param {List} $prefix The component prefix to get color variants for.
/// @return {List} The supported color variants.
@function _supported-color-variants($tokens, $prefix) {
$result: ();
@each $namespace in map.keys($tokens) {
@if list.length($prefix) == list.length($namespace) - 1 and _is-prefix($namespace, $prefix) {
$result: list.append($result, list.nth($namespace, list.length($namespace)), comma);
}
}
@return $result;
}
/// Gets the token values for the given components prefix with the given options.
/// @param {Map} $tokens The full token map.
/// @param {List} $prefix The component prefix to get the token values for.
/// @param {ArgList} Any additional options
/// Currently the additional supported options are:
// - $color-variant - The color variant to use for the component
// - $emit-overrides-only - Whether to emit *only* the overrides for the
// specific color variant, or all color styles. Defaults to false.
/// @throws If given options are invalid
/// @return {Map} The token values for the requested component.
@function get-tokens-for($tokens, $prefix, $options...) {
$options: sass-utils.validate-keyword-args($options, (color-variant, emit-overrides-only));
@if $tokens == () {
@return ();
}
$values: map.get($tokens, $prefix);
$color-variant: map.get($options, color-variant);
$emit-overrides-only: map.get($options, emit-overrides-only);
@if $color-variant == null {
@return $values;
}
$overrides: map.get($tokens, list.append($prefix, $color-variant));
@if $overrides == null {
$variants: _supported-color-variants($tokens, $prefix);
$secondary-message: if(
$variants == (),
'Mixin does not support color variants',
'Supported color variants are: #{$variants}'
);
@error 'Invalid color variant: #{$color-variant}. #{$secondary-message}.';
}
@return if($emit-overrides-only, $overrides, map.merge($values, $overrides));
}
/// Emits new token values for the given token overrides.
/// Verifies that the overrides passed in are valid tokens.
/// New token values are emitted under the current selector or root.
@mixin batch-create-token-values($overrides: (), $namespace-configs...) {
@include sass-utils.current-selector-or-root() {
$prefixed-name-data: ();
$unprefixed-name-data: ();
$all-names: ();
@each $config in $namespace-configs {
$namespace: map.get($config, namespace);
$prefix: if(map.has-key($config, prefix), map.get($config, prefix), '');
$tokens: _filter-nulls(map.get($config, tokens));
@each $name, $value in $tokens {
$prefixed-name: $prefix + $name;
$all-names: list.append($all-names, $prefixed-name, $separator: comma);
@if map.has-key($prefixed-name-data, $prefixed-name) {
@error #{
'Error overriding token: Ambiguous token name `'
}#{
$prefixed-name
}#{
'` exists in multiple namespaces: `('
}#{
list.nth(map.get($prefixed-name-data, $prefixed-name), 1)
}#{
')` and `('
}#{
$namespace
}#{
')`'
};
}
$prefixed-name-data: map.set($prefixed-name-data, $prefixed-name, ($namespace, $name));
$unprefixed-data: map.has-key($unprefixed-name-data, $name) and
map.get($unprefixed-name-data, $name) or
();
$unprefixed-data: list.append($unprefixed-data, ($namespace, $prefixed-name));
$unprefixed-name-data: map.set($unprefixed-name-data, $name, $unprefixed-data);
}
}
@each $name, $value in $overrides {
@if map.has-key($prefixed-name-data, $name) {
$data: map.get($prefixed-name-data, $name);
$namespace: list.nth($data, 1);
$name: list.nth($data, 2);
@include create-token-values(
$namespace,
(
$name: $value,
)
);
} @else if (map.has-key($unprefixed-name-data, $name)) {
$datalist: map.get($unprefixed-name-data, $name);
$prefixed-names: ();
@each $data in $datalist {
$namespace: list.nth($data, 1);
$prefixed-names: list.append($prefixed-names, list.nth($data, 2), $separator: comma);
@include create-token-values(
$namespace,
(
$name: $value,
)
);
}
@warn #{
'Token `'
}#{
$name
}#{
'` is deprecated. Please use one of the following alternatives: '
}#{
$prefixed-names
};
} @else {
@error #{'Invalid token name `'}#{$name}#{'`. '}#{'Valid tokens are: '}#{$all-names};
}
}
}
}
/// Filters keys with a null value out of the map.
/// @param {Map} $map The map to filter.
/// @return {Map} The given map with all of the null keys filtered out.
@function _filter-nulls($map) {
$result: ();
@each $key, $val in $map {
@if $val != null {
$result: map.set($result, $key, $val);
}
}
@return $result;
}