// // Copyright 2020 Google Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // @use 'sass:list'; @use 'sass:map'; @use 'sass:meta'; @use 'sass:string'; @use './css'; @use './gss'; /// Configuration for custom properties. /// @see {mixin} configure $_config: ( emit-custom-properties: true, emit-fallback-values: true, emit-fallback-vars: true, varname-prefix: 'mdc', ); /// Configure options for custom properties. The configuration will be applied /// within the scope of the mixin's content and reset when the mixin scope ends. /// /// @example - scss /// @include configure($emit-fallback-values: false) { /// // No fallback values will be emitted within this mixin scope /// @include another-mixin(); /// } /// /// All parameters must be provided as argument lists. /// /// @link https://sass-lang.com/documentation/values/lists#argument-lists /// /// @param {Bool} $emit-custom-properties [true] - Enable or disable all /// custom property emission. /// @param {Bool} $emit-fallback-values [true] - Enable or disable emission of /// fallback CSS static values. This does not include dynamic `var()` /// fallback values. /// @param {Bool} $emit-fallback-vars [true] - Enable or disable emission of /// fallback `var()` values. /// @param {String} $varname-prefix ['mdc'] - Global prefix for all custom properties. @mixin configure($config...) { @if not meta.content-exists() { @error 'content is required for configure()'; } $config: meta.keywords($config); @each $key, $value in $config { @if $value == null { $config: map.remove($config, $key); } } @if list.length($config) == 0 { @content; } @else { $previous: $_config; // Use !global to avoid shadowing // https://sass-lang.com/documentation/variables#shadowing $_config: map.merge($_config, $config) !global; @content; $_config: $previous !global; } } /// Returns true if the parameter is a custom property Map. /// /// @param {*} $value - the value to test. /// @return true if the value is a custom property Map, or false if not. @function is-custom-prop($value) { @return meta.type-of($value) == 'map' and map.has-key($value, 'varname'); } /// Indicates whether or not a value is a custom property var() string. /// /// @example - scss /// $is-prop-string: is-custom-prop-string('var(--foo)'); // true /// /// @param {*} $value - The value to test. /// @return {Bool} True if the value is a custom property var() string, or /// false if not. @function is-custom-prop-string($value) { @return meta.type-of($value) == 'string' and string.slice($value, 1, 4) == 'var('; } /// Returns true if $prop1's varname and fallback values are deeply equal to /// $prop2's varname and fallback values. /// /// @param {Map} $prop1 - the first value to test. /// @param {Map} $prop2 - the second value to test. /// @return true if both properties are deeply equal @function are-equal($prop1, $prop2) { @return create-var($prop1) == create-var($prop2); } /// Creates a custom property Map. /// /// @param {String} $varname - the custom property name. /// @param {String | Number | Map} - the fallback value (may be another custom /// property Map). May be null. /// @return a custom property Map. @function create($varname, $fallback: null) { @if string.slice($varname, 1, 2) != '--' { $varname: create-varname($varname); } @return (varname: $varname, fallback: $fallback); } /// Create a custom property variable name with the global prefix. /// /// @example - scss /// $varname: create-varname(foo); // --mdc-foo /// /// @param {String} $name - The name of the custom property. /// @return {String} The full CSS custom property variable name. @function create-varname($name) { $varname-prefix: map.get($_config, 'varname-prefix'); @return --#{$varname-prefix}-#{$name}; } /// Returns the custom property variable name of a custom property Map. /// /// @param {Map} $custom-prop - a custom property Map. /// @return the custom property variable name defined by the Map. @function get-varname($custom-prop) { @return map.get($custom-prop, 'varname'); } /// Returns the fallback value of a custom property Map. May be null if the /// custom property does not have a fallback. /// /// @param {Map} $custom-prop - a custom property Map. /// @param {Bool} $shallow - if true, return the first fallback value, which /// may be another custom property Map. Defaults to false, which will return /// the deep final fallback value. /// @return the fallback value of a custom property Map. May be null. @function get-fallback($custom-prop, $shallow: false) { $fallback: map.get($custom-prop, 'fallback'); @if is-custom-prop($fallback) and not $shallow { @return get-fallback($fallback); } @return $fallback; } /// Creates a new custom property Map and returns it with the specified new /// fallback value. /// /// @param {Map} $custom-prop - the custom property Map to copy. /// @param {String | Number | Map} $new-fallback - the new fallback value of the /// custom property Map. May be null. /// @param {Bool} $shallow - if true, set the first fallback value. Defaults to /// false, which will set the deep final fallback value. /// @return a new custom property Map with the new fallback value. @function set-fallback($custom-prop, $new-fallback, $shallow: false) { $varname: get-varname($custom-prop); $first-fallback: get-fallback($custom-prop, $shallow: true); @if is-custom-prop($first-fallback) and not $shallow { // The first fallback is a custom property and $shallow is false. Deeply // set the fallback value of the custom property and get the new custom // property Map returned. $new-fallback: set-fallback($first-fallback, $new-fallback); } @return create($varname, $new-fallback); } /// Creates and returns a CSS `var()` function value represented by the provided /// custom property Map. /// /// If custom properties are disabled, this function will return the custom /// property's fallback value instead, which may be `null` and result in an /// empty declaration. /// /// @param {Map} $custom-prop - A custom property Map. /// @return {*} A CSS value, typically a `var()` function, representing the /// custom property. The returned value may change depending on the current /// configuration options for custom properties and whether or not a /// fallback value is present in the custom property Map. @function create-var($custom-prop) { @if not map.get($_config, emit-custom-properties) { // If configured not to emit custom properties and a request is made for a // custom prop's CSS value, return its fallback value. If this is null, it // will result in an empty declaration. @return get-fallback($custom-prop); } $varname: get-varname($custom-prop); $fallback: get-fallback($custom-prop, $shallow: true); $emit-fallback-vars: map.get($_config, emit-fallback-vars); $fallback-is-prop: is-custom-prop($fallback); @if $fallback-is-prop and $emit-fallback-vars { @return var($varname, create-var($fallback)); } $emit-fallback-values: map.get($_config, emit-fallback-values); @if $fallback and not $fallback-is-prop and $emit-fallback-values { @return var($varname, $fallback); } @return var($varname); } /// Retrieves the CSS declaration value for a custom property Map. This is /// typically a `var()` function. /// /// If custom properties are disabled, the custom property's fallback value /// will be returned instead. /// /// @param {Map} $custom-prop - The custom property Map to retrieve a /// declaration value for. /// @return {*} The CSS declaration value. @function get-declaration-value($custom-prop) { // This will return just the fallback value if `emit-custom-properties` is false. @return create-var($custom-prop); } /// Retrieves the CSS fallback declaration value for a custom property Map. /// This is typically a static CSS value if a custom property has a fallback, or /// null if it does not. /// /// This function will always return `null` if custom properties or fallback /// values are disabled. /// /// @param {Map} $custom-prop - The custom property Map to retrieve a fallback /// declaration value for. /// @return {String | null} The CSS fallback declaration value. @function get-declaration-fallback($custom-prop) { $emit-custom-properties: map.get($_config, emit-custom-properties); $emit-fallback-values: map.get($_config, emit-fallback-values); @if not $emit-custom-properties or not $emit-fallback-values { @return null; } @return get-fallback($custom-prop); } /// Emits a CSS declaration for a custom property. A custom property may either /// be provided as the value to a CSS property string to be emitted as a var() /// function, or as a standalone value to be emitted as a custom property /// declaration. /// /// @example - scss /// @include declaration(color, create(--foo, teal)); /// // color: var(--foo, teal); /// @include declaration(create(--foo, teal)); /// // --foo: teal; /// /// Standalone custom properties must have a fallback value to emit as a CSS /// declaration. /// /// The declaration emitted for a custom property may change or be ignored /// based on the current configuration for custom properties. /// /// @see {mixin} css.declaration /// @see {mixin} configuration /// /// @param {String} $property - The CSS property of the declaration or the /// custom property Map to emit. /// @param {Map} $custom-prop - A custom property Map for the property's value. /// Optional if $property is the custom property Map to emit. /// @param {Map} $gss - An optional Map of GSS annotations to add. /// @param {Bool} $important - If true, add `!important` to the declaration. @mixin declaration($property, $custom-prop: null, $gss: (), $important: false) { @if $property { $value: null; $fallback-value: null; @if is-custom-prop($property) { @if map.get($_config, emit-custom-properties) { $custom-prop: $property; $property: get-varname($custom-prop); $value: get-fallback($custom-prop, $shallow: true); @if is-custom-prop($value) { $value: create-var($value); } } } @else { @if not is-custom-prop($custom-prop) { @error "Invalid custom property: #{$custom-prop}. Must be a Map with 'varname' and 'fallback'."; } $value: get-declaration-value($custom-prop); $fallback-value: get-declaration-fallback($custom-prop); } @include css.declaration( $property, $value, $fallback-value: $fallback-value, $gss: $gss, $important: $important ); } }