// // 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:color'; @use 'sass:list'; @use 'sass:map'; @use 'sass:math'; @use 'sass:meta'; @use 'sass:string'; @use './custom-properties'; @use './keys'; @function _linear-channel-value($channel-value) { $normalized-channel-value: math.div($channel-value, 255); @if $normalized-channel-value < 0.03928 { @return math.div($normalized-channel-value, 12.92); } @return math.pow(math.div($normalized-channel-value + 0.055, 1.055), 2.4); } // Calculate the luminance for a color. // See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests @function luminance($color) { $red: _linear-channel-value(color.red($color)); $green: _linear-channel-value(color.green($color)); $blue: _linear-channel-value(color.blue($color)); @return 0.2126 * $red + 0.7152 * $green + 0.0722 * $blue; } // Calculate the contrast ratio between two colors. // See https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-tests @function contrast($back, $front) { $backLum: luminance($back) + 0.05; $foreLum: luminance($front) + 0.05; @return math.div(math.max($backLum, $foreLum), math.min($backLum, $foreLum)); } // Determine whether the color is 'light' or 'dark'. @function tone($color) { @if $color == 'dark' or $color == 'light' { @return $color; } @if meta.type-of($color) != 'color' { @warn '#{$color} is not a color. Falling back to "dark" tone.'; @return 'dark'; } $minimumContrast: 3.1; $lightContrast: contrast($color, white); $darkContrast: contrast($color, rgba(black, 0.87)); @if ($lightContrast < $minimumContrast) and ($darkContrast > $lightContrast) { @return 'light'; } @else { @return 'dark'; } } // Determine whether to use dark or light text on top of given color to meet accessibility standards for contrast. // Returns 'dark' if the given color is light and 'light' if the given color is dark. @function contrast-tone($color) { @return if(tone($color) == 'dark', 'light', 'dark'); } /// /// @param $color Target color in any color format. /// @return Returns hash in string format that uniquely represents /// any given color format. Useful for generating unique keyframe names. /// @example /// `color-hash(#6200ee)` => "6200ee" /// `color-hash(rgb(255, 112, 112))` => "ff7070" /// `color-hash((varname: --my-fancy-color, fallback: teal))` => 'teal' /// `color-hash((varname: --my-fancy-color, fallback: null))` => '--my-fancy-color' /// @function color-hash($color) { @if custom-properties.is-custom-prop($color) { $color-value: custom-properties.get-fallback($color); @if (custom-properties.is-custom-prop-string($color-value)) { $varEndIndex: if( string.index($color-value, ', '), string.index($color-value, ', ') - 1, -2 ); @return string.slice($color-value, 5, $varEndIndex); } @if (meta.type-of($color-value) == 'color') { @return _get-hex-string($color-value); } @return custom-properties.get-varname($color); } @if meta.type-of($color) == 'string' { @return $color; } @return _get-hex-string($color); } @function _get-hex-string($color) { @return string.slice(color.ie-hex-str($color), 2); // Index starts at 1 } // // Main theme colors for your brand. // // If you're a user customizing your color scheme in SASS, these are probably the only variables you need to change. // $primary: #6200ee !default; // baseline purple, 500 tone $on-primary: if(contrast-tone($primary) == 'dark', #000, #fff) !default; // The $mdc-theme-accent variable is DEPRECATED - it exists purely for backward compatibility. // The $mdc-theme-secondary* variables should be used for all new projects. /// @deprecated - use $secondary $accent: #018786 !default; // baseline teal, 600 tone $secondary: $accent !default; $on-secondary: if(contrast-tone($secondary) == 'dark', #000, #fff) !default; $background: #fff !default; // White $surface: #fff !default; $on-surface: if(contrast-tone($surface) == 'dark', #000, #fff) !default; $error: #b00020 !default; $on-error: if(contrast-tone($error) == 'dark', #000, #fff) !default; // // Text colors according to light vs dark and text type. // $text-colors: ( dark: ( primary: rgba(black, 0.87), secondary: rgba(black, 0.54), hint: rgba(black, 0.38), disabled: rgba(black, 0.38), icon: rgba(black, 0.38), ), light: ( primary: white, secondary: rgba(white, 0.7), hint: rgba(white, 0.5), disabled: rgba(white, 0.5), icon: rgba(white, 0.5), ), ) !default; $text-emphasis: ( high: 0.87, medium: 0.6, disabled: 0.38, ) !default; @function ink-color-for-fill_($text-style, $fill-color) { $contrast-tone: contrast-tone($fill-color); @return map.get(map.get($text-colors, $contrast-tone), $text-style); } // // Primary text colors for each of the theme colors. // /// @deprecated Use individual variables (`$primary`, `$secondary`). Do not /// override this Map of variables. $property-values: ( primary: $primary, secondary: $secondary, background: $background, surface: $surface, error: $error, on-primary: $on-primary, on-secondary: $on-secondary, on-surface: $on-surface, on-error: $on-error, text-primary-on-background: ink-color-for-fill_(primary, $background), text-secondary-on-background: ink-color-for-fill_(secondary, $background), text-hint-on-background: ink-color-for-fill_(hint, $background), text-disabled-on-background: ink-color-for-fill_(disabled, $background), text-icon-on-background: ink-color-for-fill_(icon, $background), text-primary-on-light: ink-color-for-fill_(primary, light), text-secondary-on-light: ink-color-for-fill_(secondary, light), text-hint-on-light: ink-color-for-fill_(hint, light), text-disabled-on-light: ink-color-for-fill_(disabled, light), text-icon-on-light: ink-color-for-fill_(icon, light), text-primary-on-dark: ink-color-for-fill_(primary, dark), text-secondary-on-dark: ink-color-for-fill_(secondary, dark), text-hint-on-dark: ink-color-for-fill_(hint, dark), text-disabled-on-dark: ink-color-for-fill_(disabled, dark), text-icon-on-dark: ink-color-for-fill_(icon, dark), ) !default; @include keys.set-values( $property-values, $options: (custom-property-prefix: theme) ); // A copy of the property values Map that is used to detect compile-time changes // for Angular support. $_property-values-copy: $property-values; /// Checks if the global $mdc-theme-property-values was dynamically changed at /// compile time. Typically, $property-values is configured once, but a Sass /// hack allows the variable to be changed multiple times and effectively /// support dynamic values. /// /// Angular uses this in their dynamic theming. This function checks if this /// scenario has occurred and returns the current global value that should be /// used instead of the key store value. /// /// @deprecated The function should not be used externally. It will be removed /// when $mdc-theme-property-values is fully deprecated and removed. @function deprecated-get-global-theme-key-value-if-changed($key) { // Determine if we need to use a compile-time updated value to support // Angular. $current-global-value: map.get($property-values, $key); $configured-global-value: map.get($_property-values-copy, $key); @if $current-global-value != $configured-global-value { // $mdc-theme-property-values was changed at compile time. Return the new // compile-time value. @return (value: $current-global-value, changed: true); } @return (changed: false); } // @deprecated use theme.property(). If you need to ensure the value is not a // custom property, use custom-properties.is-custom-prop() to check if the value // is a custom prop, then custom-properties.get-fallback() to get its value. // If `$style` is a color (a literal color value, `currentColor`, or a CSS custom property), it is returned verbatim. // Otherwise, `$style` is treated as a theme property name, and the corresponding value from // `$mdc-theme-property-values` is returned. If this also fails, an error is thrown. // // This is mainly useful in situations where `mdc-theme-prop` cannot be used directly (e.g., `box-shadow`). // // Examples: // // 1. mdc-theme-prop-value(primary) => "#6200ee" // 2. mdc-theme-prop-value(blue) => 'blue' // // NOTE: This function must be defined in _variables.scss instead of _functions.scss to avoid circular imports. @function prop-value($style) { @if custom-properties.is-custom-prop($style) { @return custom-properties.get-fallback($style); } @if is-valid-theme-prop-value_($style) { @return $style; } @if is-theme-key($style) { // Determine if we need to use a compile-time updated value to support // Angular. $result: deprecated-get-global-theme-key-value-if-changed($style); @if map.get($result, changed) { @return map.get($result, value); } } @return keys.resolve($style); } // NOTE: This function must be defined in _variables.scss instead of _functions.scss to avoid circular imports. @function accessible-ink-color($fill-color, $text-style: primary) { $fill-color-value: prop-value($fill-color); $color-map-for-tone: map.get($text-colors, contrast-tone($fill-color-value)); @if not map.has-key($color-map-for-tone, $text-style) { @error "Invalid $text-style: '#{$text-style}'. Choose one of: #{map.keys($color-map-for-tone)}"; } @return map.get($color-map-for-tone, $text-style); } // NOTE: This function is depended upon by mdc-theme-prop-value (above) and thus must be defined in this file. @function is-valid-theme-prop-value_($style) { @if $style == null { @return false; } @return meta.type-of($style) == 'color' or $style == 'currentColor' or str_slice($style, 1, 4) == 'var(' or $style == 'inherit' or $style == 'transparent' or // NOTE: `GrayText` is deprecated, but is the only feasible way to convey the // correct high-contrast mode colors in alignment with Windows system colors. $style == 'GrayText'; } @function text-emphasis($emphasis) { @return map.get($text-emphasis, $emphasis); } @function is-theme-key($style) { @return map.has-key($property-values, $style); } @function get-theme-keys() { @return map.keys($property-values); } /// /// @param {Color|String} Color property key name (i.e., `primary`, `secondary`, /// etc). /// @return Returns custom property map containing CSS custom property and /// fallback value (i.e., (varname: ..., fallback: ...). Returns color if /// valid color value is provided. Throws error otherwise. /// @examples /// 1. get-custom-property(primary) /// => (varname: --mdc-theme-primary, fallback: #6200ee) /// /// 2. get-custom-property(#fff) /// => #fff /// @function get-custom-property($color) { $is-tokens-custom-prop: meta.type-of($color) == 'string' and string.index($color, '--') != null; @if custom-properties.is-custom-prop($color) or $is-tokens-custom-prop or is-valid-theme-prop-value_($color) { @return $color; } @else if is-theme-key($color) { $custom-prop: keys.create-custom-property($color); // Determine if we need to use a compile-time updated value to support // Angular. $result: deprecated-get-global-theme-key-value-if-changed($color); @if map.get($result, changed) { $custom-prop: custom-properties.set-fallback( $custom-prop, map.get($result, value) ); } @return $custom-prop; } @else { @error "Invalid theme property: '#{$color}'. Choose one of: #{get-theme-keys()}"; } }