@use 'sass:list'; @use 'sass:map'; @use 'sass:meta'; @use 'sass:color'; @use '../theming/theming'; @use './palette'; /// Extracts a color from a palette or throws an error if it doesn't exist. /// @param {Map} $palette The palette from which to extract a color. /// @param {String | Number} $hue The hue for which to get the color. @function _get-color-from-palette($palette, $hue) { @if map.has-key($palette, $hue) { @return map.get($palette, $hue); } @error 'Hue "' + $hue + '" does not exist in palette. Available hues are: ' + map.keys($palette); } /// For a given hue in a palette, return the contrast color from the map of contrast palettes. /// @param {Map} $palette The palette from which to extract a color. /// @param {String | Number} $hue The hue for which to get a contrast color. /// @returns {Color} The contrast color for the given palette and hue. @function get-contrast-color-from-palette($palette, $hue) { @return map.get(map.get($palette, contrast), $hue); } /// Creates a map of hues to colors for a theme. This is used to define a theme palette in terms /// of the Material Design hues. /// @param {Map} $base-palette Map of hue keys to color values for the basis for this palette. /// @param {String | Number} $default Default hue for this palette. /// @param {String | Number} $lighter "lighter" hue for this palette. /// @param {String | Number} $darker "darker" hue for this palette. /// @param {String | Number} $text "text" hue for this palette. /// @returns {Map} A complete Angular Material theming palette. @function define-palette( $base-palette, $default: 500, $lighter: 100, $darker: 700, $text: $default ) { $result: map.merge( $base-palette, ( default: _get-color-from-palette($base-palette, $default), lighter: _get-color-from-palette($base-palette, $lighter), darker: _get-color-from-palette($base-palette, $darker), text: _get-color-from-palette($base-palette, $text), default-contrast: get-contrast-color-from-palette($base-palette, $default), lighter-contrast: get-contrast-color-from-palette($base-palette, $lighter), darker-contrast: get-contrast-color-from-palette($base-palette, $darker), ) ); // For each hue in the palette, add a "-contrast" color to the map. @each $hue, $color in $base-palette { $result: map.merge( $result, ( '#{$hue}-contrast': get-contrast-color-from-palette($base-palette, $hue), ) ); } @return $result; } /// Gets a color from a theme palette (the output of mat-palette). /// The hue can be one of the standard values (500, A400, etc.), one of the three preconfigured /// hues (default, lighter, darker), or any of the aforementioned suffixed with "-contrast". /// /// @param {Map} $palette The palette from which to extract a color. /// @param {String | Number} $hue The hue from the palette to use. If this is a value between 0 // and 1, it will be treated as opacity. /// @param {Number} $opacity The alpha channel value for the color. /// @returns {Color} The color for the given palette, hue, and opacity. @function get-color-from-palette($palette, $hue: default, $opacity: null) { // If hueKey is a number between zero and one, then it actually contains an // opacity value, so recall this function with the default hue and that given opacity. @if meta.type-of($hue) == number and $hue >= 0 and $hue <= 1 { @return get-color-from-palette($palette, default, $hue); } // We cast the $hue to a string, because some hues starting with a number, like `700-contrast`, // might be inferred as numbers by Sass. Casting them to string fixes the map lookup. $color: if(map.has-key($palette, $hue), map.get($palette, $hue), map.get($palette, $hue + '')); @if (meta.type-of($color) != color) { // If the $color resolved to something different from a color (e.g. a CSS variable), // we can't apply the opacity anyway so we return the value as is, otherwise Sass can // throw an error or output something invalid. @return $color; } @return rgba($color, if($opacity == null, color.opacity($color), $opacity)); } // Validates the specified theme by ensuring that the optional color config defines // a primary, accent and warn palette. Returns the theme if no failures were found. @function _mat-validate-theme($theme) { @if map.get($theme, color) { $color: map.get($theme, color); @if not map.get($color, primary) { @error 'Theme does not define a valid "primary" palette.'; } @else if not map.get($color, accent) { @error 'Theme does not define a valid "accent" palette.'; } @else if not map.get($color, warn) { @error 'Theme does not define a valid "warn" palette.'; } } @return $theme; } // Creates a light-themed color configuration from the specified // primary, accent and warn palettes. @function _mat-create-light-color-config($primary, $accent, $warn: null) { @return ( primary: $primary, accent: $accent, warn: if($warn != null, $warn, define-palette(palette.$red-palette)), is-dark: false, foreground: palette.$light-theme-foreground-palette, background: palette.$light-theme-background-palette ); } // Creates a dark-themed color configuration from the specified // primary, accent and warn palettes. @function _mat-create-dark-color-config($primary, $accent, $warn: null) { @return ( primary: $primary, accent: $accent, warn: if($warn != null, $warn, define-palette(palette.$red-palette)), is-dark: true, foreground: palette.$dark-theme-foreground-palette, background: palette.$dark-theme-background-palette ); } // TODO: Remove legacy API and rename `$primary` below to `$config`. Currently it cannot be renamed // as it would break existing apps that set the parameter by name. /// Creates a container object for a light theme to be given to individual component theme mixins. /// @param {Map} $primary The theme configuration object. /// @returns {Map} A complete Angular Material theme map. @function define-light-theme($primary, $accent: null, $warn: define-palette(palette.$red-palette)) { // This function creates a container object for the individual component theme mixins. Consumers // can construct such an object by calling this function, or by building the object manually. // There are two possible ways to invoke this function in order to create such an object: // // (1) Passing in a map that holds optional configurations for individual parts of the // theming system. For `color` configurations, the function only expects the palettes // for `primary` and `accent` (and optionally `warn`). The function will expand the // shorthand into an actual configuration that can be consumed in `-color` mixins. // (2) Legacy pattern: Passing in the palettes as parameters. This is not as flexible // as passing in a configuration map because only the `color` system can be configured. // // If the legacy pattern is used, we generate a container object only with a light-themed // configuration for the `color` theming part. @if $accent != null { @warn theming.$private-legacy-theme-warning; $theme: _mat-validate-theme( ( _is-legacy-theme: true, color: _mat-create-light-color-config($primary, $accent, $warn), ) ); @return _internalize-theme(theming.private-create-backwards-compatibility-theme($theme)); } // If the map pattern is used (1), we just pass-through the configurations for individual // parts of the theming system, but update the `color` configuration if set. As explained // above, the color shorthand will be expanded to an actual light-themed color configuration. $result: $primary; @if map.get($primary, color) { $color-settings: map.get($primary, color); $primary: map.get($color-settings, primary); $accent: map.get($color-settings, accent); $warn: map.get($color-settings, warn); $result: map.merge( $result, ( color: _mat-create-light-color-config($primary, $accent, $warn), ) ); } @return _internalize-theme( theming.private-create-backwards-compatibility-theme(_mat-validate-theme($result)) ); } // TODO: Remove legacy API and rename below `$primary` to `$config`. Currently it cannot be renamed // as it would break existing apps that set the parameter by name. /// Creates a container object for a dark theme to be given to individual component theme mixins. /// @param {Map} $primary The theme configuration object. /// @returns {Map} A complete Angular Material theme map. @function define-dark-theme($primary, $accent: null, $warn: define-palette(palette.$red-palette)) { // This function creates a container object for the individual component theme mixins. Consumers // can construct such an object by calling this function, or by building the object manually. // There are two possible ways to invoke this function in order to create such an object: // // (1) Passing in a map that holds optional configurations for individual parts of the // theming system. For `color` configurations, the function only expects the palettes // for `primary` and `accent` (and optionally `warn`). The function will expand the // shorthand into an actual configuration that can be consumed in `-color` mixins. // (2) Legacy pattern: Passing in the palettes as parameters. This is not as flexible // as passing in a configuration map because only the `color` system can be configured. // // If the legacy pattern is used, we generate a container object only with a dark-themed // configuration for the `color` theming part. @if $accent != null { @warn theming.$private-legacy-theme-warning; $theme: _mat-validate-theme( ( _is-legacy-theme: true, color: _mat-create-dark-color-config($primary, $accent, $warn), ) ); @return _internalize-theme(theming.private-create-backwards-compatibility-theme($theme)); } // If the map pattern is used (1), we just pass-through the configurations for individual // parts of the theming system, but update the `color` configuration if set. As explained // above, the color shorthand will be expanded to an actual dark-themed color configuration. $result: $primary; @if map.get($primary, color) { $color-settings: map.get($primary, color); $primary: map.get($color-settings, primary); $accent: map.get($color-settings, accent); $warn: map.get($color-settings, warn); $result: map.merge( $result, ( color: _mat-create-dark-color-config($primary, $accent, $warn), ) ); } @return _internalize-theme( theming.private-create-backwards-compatibility-theme(_mat-validate-theme($result)) ); } /// Gets the color configuration from the given theme or configuration. /// @param {Map} $theme The theme map returned from `define-light-theme` or `define-dark-theme`. /// @param {Map} $default The default value returned if the given `$theme` does not include a /// `color` configuration. /// @returns {Map} Color configuration for a theme. @function get-color-config($theme, $default: null) { @return theming.private-get-color-config($theme, $default); } /// Gets the density configuration from the given theme or configuration. /// @param {Map|string|number} $theme-or-config The theme map returned from `define-light-theme` or /// `define-dark-theme`. /// @param {string|number} $default The default value returned if the given `$theme` does not /// include a `density` configuration. /// @returns {string|number} Density configuration for a theme. @function get-density-config($theme-or-config, $default: 0) { @return theming.private-get-density-config($theme-or-config, $default); } /// Gets the typography configuration from the given theme or configuration. /// For backwards compatibility, typography is not included by default. /// @param {Map} $theme-or-config The theme map returned from `define-light-theme` or /// `define-dark-theme`. /// @param {Map} $default The default value returned if the given `$theme` does not include a /// `typography` configuration. /// @returns {Map} Typography configuration for a theme. @function get-typography-config($theme-or-config, $default: null) { @return theming.private-get-typography-config($theme-or-config, $default); } /// Copies the given theme object and nests it within itself under a secret key and replaces the /// original map keys with error values. This allows the inspection API which is aware of the secret /// key to access the real values, but attempts to directly access the map will result in errors. /// @param {Map} $theme The theme map. @function _internalize-theme($theme) { @if map.has-key($theme, theming.$private-internal-name) { @return $theme; } $internalized-theme: ( theming.$private-internal-name: ( theme-version: 0, m2-config: $theme, ), ); @if (theming.$theme-legacy-inspection-api-compatibility) { @return map.merge($theme, $internalized-theme); } $error-theme: _replace-values-with-errors( $theme, 'Theme may only be accessed via theme inspection API' ); @return map.merge($error-theme, $internalized-theme); } /// Replaces concrete CSS values with errors in a theme object. /// Errors are represented as a map `(ERROR: )`. Because maps are not valid CSS values, /// the Sass will not compile if the user tries to use any of the error theme values in their CSS. /// Users will see a message about `(ERROR: )` not being a valid CSS value. Using the /// message, that winds up getting shown, we can help explain to users why they're getting the /// error. /// @param {*} $value The theme value to replace with errors. /// @param {String} $message The error message to sow users. /// @return {Map} A version of $value where concrete CSS values have been replaced with errors @function _replace-values-with-errors($value, $message) { $value-type: meta.type-of($value); @if $value-type == 'map' { @each $k, $v in $value { $value: map.set($value, $k, _replace-values-with-errors($v, $message)); } @return $value; } @else if $value-type == 'list' and list.length($value) > 0 { @for $i from 1 through list.length() { $value: list.set-nth($value, $i, _replace-values-with-errors(list.nth($value, $i), $message)); } @return $value; } @return (ERROR: $message); }