// // Copyright 2017 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 '@material/feature-targeting/feature-targeting'; @use './css'; @use './custom-properties'; @use './gss'; @use './keys'; @use './replace'; @use './theme-color'; @mixin core-styles($query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); :root { @include feature-targeting.targets($feat-color) { @each $style in theme-color.get-theme-keys() { @include custom-properties.declaration( keys.create-custom-property($style) ); } } } @each $style in theme-color.get-theme-keys() { @if $style != 'background' and $style != 'surface' { .mdc-theme--#{$style} { @include feature-targeting.targets($feat-color) { @include property(color, $style); } } } @else { .mdc-theme--#{$style} { @include feature-targeting.targets($feat-color) { @include property(background-color, $style); } } } } // CSS rules for using primary and secondary (plus light/dark variants) as background colors. @each $style in ('primary', 'secondary') { .mdc-theme--#{$style}-bg { @include feature-targeting.targets($feat-color) { @include property(background-color, $style); } } } } /// Applies a dynamic value to the specified property. This mixin should be used /// in theme style mixins when setting properties. /// /// The value may be any of the following: /// - a standard CSS value /// - a custom property Map, e.g. (varname: --mdc-foo, fallback: blue) /// - a Material theme key String, e.g. 'primary', 'on-primary' /// /// @example /// @include theme.property(color, teal); /// @include theme.property(color, custom-properties.create(foo, blue)); /// @include theme.property(color, primary); /// /// A `$replace` Map parameter may be provided to replace key/value pairs for /// string values. This can be used to substitute parameters in complex string /// values such as `calc()` with custom properties. /// /// @example /// @include theme.property( /// width, /// calc(foo + bar), /// $replace: (foo: custom-properties.create(foo), bar: 8px) /// ); /// /// Note: Material theme key Strings (e.g. `primary`) are not supported as /// replacement values. /// /// A CSS custom property declaration may be emitted by providing a custom /// property Map to `$property`. The fallback value (or `$value` if provided) /// will be used as the declaration value. /// /// @example - scss /// .foo { /// @include theme.property(custom-properties.create(foo, teal)); /// @include theme.property(custom-properties.create(bar, teal), blue); /// } /// /// @example - css /// .foo { /// --mdc-foo: teal; /// --mdc-bar: blue; /// } /// /// @param {String | Map} $property - The name of the CSS property. May also be /// a custom property Map to emit a custom propery declaration. /// @param {String | Number | Color | List | Map} $value - The property's value. /// This parameter may be omitted if `$property` is a custom property Map. /// @param {Map} $gss - Optional Map of GSS annotations to set. /// @param {Map} $replace - An optional Map of replacement key/value pairs if /// the `$value` is a string. /// @param {Bool} $important - Set to true to add an `!important` rule. Defaults /// to false. @mixin property( $property, $value: null, $gss: (), $replace: null, $important: false ) { @if custom-properties.is-custom-prop($property) { // $property is a custom property Map // --mdc-foo: value; @if $value { $property: custom-properties.set-fallback( $property, $value, $shallow: true ); } @include custom-properties.declaration( $property, $gss: $gss, $important: $important ); } @else if custom-properties.is-custom-prop($value) { // $value is a custom property Map // property: var(--mdc-foo, fallback); @include custom-properties.declaration( $property, $value, $gss: $gss, $important: $important ); } @else if keys.is-key($value) { // $value is a key String // property: key; $custom-prop: keys.create-custom-property($value); @if theme-color.is-theme-key($value) { // Determine if we need to use a compile-time updated value to support // Angular. $key: $value; // (changed: Bool, value: *) $result: theme-color.deprecated-get-global-theme-key-value-if-changed( $key ); @if map.get($result, changed) { // $mdc-theme-property-values was changed at compile time. Use the // global value instead. Otherwise if it was not changed, continue // using the key store normally. $custom-prop: keys.create-custom-property($key); $custom-prop: custom-properties.set-fallback( $custom-prop, map.get($result, value) ); } } @include custom-properties.declaration( $property, $custom-prop, $gss: $gss, $important: $important ); } @else { // $value is a standard CSS value // property: value; $fallback: null; @if $replace { // If any replacements are null, treat the entire value as null (do not // emit anything). @each $name, $replacement in $replace { @if $replacement == null { $value: null; } } } @if $replace and $value { @if meta.type-of($replace) != 'map' { @error 'mdc-theme: Invalid replacement #{$replace}. Must be a Map.'; } $replace-map-fallback: (); $replace-map-value: (); $needs-fallback: false; @each $name, $replacement in $replace { @if custom-properties.is-custom-prop($replacement) { $replace-value: custom-properties.get-declaration-value($replacement); $replace-fallback: custom-properties.get-declaration-fallback( $replacement ); @if $replace-fallback { $needs-fallback: true; } $replace-map-value: map.set( $replace-map-value, $name, $replace-value ); $replace-map-fallback: map.set( $replace-map-fallback, $name, $replace-fallback ); } @else { $replace-map-value: map.set($replace-map-value, $name, $replacement); $replace-map-fallback: map.set( $replace-map-fallback, $name, $replacement ); } } @if meta.type-of($value) == 'string' { @if $needs-fallback { $fallback: replace.replace-string($value, $replace-map-fallback); } $value: replace.replace-string($value, $replace-map-value); } @else if meta.type-of($value) == 'list' { @if $needs-fallback { $fallback: replace.replace-list($value, $replace-map-fallback); } $value: replace.replace-list($value, $replace-map-value); } @else { @error 'mdc-theme: Invalid replacement value #{$value}. $replace may only be used with string or list values.'; } } @include css.declaration( $property, $value, $fallback-value: $fallback, $gss: $gss, $important: $important ); } } // @deprecated use the `property()` mixin instead @mixin prop($property, $style) { @include property($property, $style); } /// Validates theme configuration keys by comparing it with original theme /// configuration, also validates theme values to see if it has any unsupported /// value formats. /// /// Use this in internal `theme()` mixins to validate library-provided /// `$theme` maps and ensure that all tokens are correct and present. /// /// @example /// @mixin theme($theme) { /// @include theme.validate-theme($light-theme, $theme); /// ... /// } /// /// @see validate-theme-styles to validate only theme keys. /// /// @param {Map} $origin-theme - Original theme configuration in Sass map format /// that has all supported keys. /// @param {Map} $custom-theme - Provided theme configuration in Sass map format /// that should be validated against `$origin-theme`. @mixin validate-theme($origin-theme, $custom-theme, $test-only: false) { @include validate-theme-styles( $origin-theme, $custom-theme, $test-only: $test-only ); @include _validate-theme-values($custom-theme, $test-only: $test-only); } /// Validates theme configuration keys by comparing it with original theme /// configuration. /// /// Use this in internal `theme-styles()` mixins to validate library-provided /// `$theme` maps and ensure that all tokens are correct and present. /// /// @example /// @mixin theme-styles($theme) { /// @include theme.validate-theme-styles($light-theme, $theme); /// ... /// } /// /// @see validate-theme to validate both theme keys and theme values. /// /// @param {Map} $origin-theme - Original theme configuration in Sass map format /// that has all supported keys. /// @param {Map} $custom-theme - Provided theme configuration in Sass map format /// that should be validated against `$origin-theme`. @mixin validate-theme-styles($origin-theme, $custom-theme, $test-only: false) { $origin-keys: map.keys($origin-theme); $unsupported-keys: (); @each $key, $value in $custom-theme { @if (not list.index($origin-keys, $key)) { $unsupported-keys: list.append( $unsupported-keys, $key, $separator: comma ); } } @if list.length($unsupported-keys) > 0 { $error-message: 'Unsupported keys found: #{$unsupported-keys}. Expected one of: #{$origin-keys}.'; @if $test-only { content: $error-message; } @else { @error $error-message; } } } /// Validates theme configuration values to see if it has any unsupported value /// formats. /// @see Use `validate-theme()` to validate both theme keys and theme values. /// @param {Map} $custom-theme - Provided theme configuration in Sass map format /// that needs to be validated. @mixin _validate-theme-values($custom-theme, $test-only: false) { $unsupported-custom-prop-keys: (); @each $key, $value in $custom-theme { @if custom-properties.is-custom-prop($value) { $unsupported-custom-prop-keys: list.append( $unsupported-custom-prop-keys, $key, $separator: comma ); } } @if list.length($unsupported-custom-prop-keys) > 0 { $error-message: 'Custom properties are not supported for theme map keys: #{$unsupported-custom-prop-keys}'; @if $test-only { content: $error-message; } @else { @error $error-message; } } }