// // 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. // // Selector '.mdc-*' should only be used in this project. // stylelint-disable selector-class-pattern -- // NOTE: this is the implementation of the aforementioned classes. @use 'sass:math'; @use 'sass:color'; @use 'sass:list'; @use 'sass:map'; @use 'sass:meta'; @use '@material/tokens/resolvers'; @use '@material/density/functions' as density-functions; @use '@material/density/variables' as density-variables; @use '@material/elevation/elevation-theme'; @use '@material/feature-targeting/feature-targeting'; @use '@material/floating-label/mixins' as floating-label-mixins; @use '@material/floating-label/variables' as floating-label-variables; @use '@material/line-ripple/mixins' as line-ripple-mixins; @use '@material/list/mixins' as list-mixins; @use '@material/list/evolution-mixins' as list-evolution-mixins; @use '@material/menu-surface/mixins' as menu-surface-mixins; @use '@material/notched-outline/mixins' as notched-outline-mixins; @use '@material/notched-outline/variables' as notched-outline-variables; @use '@material/ripple/ripple-theme'; @use '@material/rtl/rtl'; @use '@material/shape/functions' as shape-functions; @use '@material/shape/mixins' as shape-mixins; @use '@material/theme/css'; @use '@material/theme/custom-properties'; @use '@material/theme/gss'; @use '@material/theme/keys'; @use '@material/theme/state'; @use '@material/theme/theme'; @use '@material/theme/variables' as theme-variables; @use '@material/typography/mixins' as typography-mixins; @use '@material/typography/typography'; @use './select-helper-text-theme'; @use './select-icon-theme'; @use './select-shared-theme'; $ripple-target: '.mdc-select__ripple'; @function get-outlined-label-position-y($select-anchor-height) { @if custom-properties.is-custom-prop($select-anchor-height) { $value: custom-properties.get-declaration-value($select-anchor-height); @return calc( calc($value / 2) + math.div(notched-outline-variables.$label-box-height, 2) ); } @else { @return math.div($select-anchor-height, 2) + math.div(notched-outline-variables.$label-box-height, 2); } } $arrow-padding: 52px !default; $label-padding: 16px !default; $height: 56px !default; $minimum-height-for-filled-label: 52px !default; $filled-baseline-top: 40px !default; $selected-text-height: 28px !default; $anchor-padding-left: 16px !default; $anchor-padding-left-with-leading-icon: 0 !default; $anchor-padding-right: 0 !default; $outlined-stroke-width: 2px !default; $default-width: 200px !default; $shape-radius: small !default; $minimum-height: 40px !default; $minimum-height-for-filled-label: 52px !default; $maximum-height: $height !default; $density-scale: density-variables.$default-scale !default; $density-config: ( height: ( default: $height, maximum: $maximum-height, minimum: $minimum-height, ), ) !default; $ink-color: rgba(theme-variables.prop-value(on-surface), 0.87) !default; $dropdown-icon-color: rgba( theme-variables.prop-value(on-surface), 0.54 ) !default; $label-color: rgba(theme-variables.prop-value(on-surface), 0.6) !default; $focused-label-color: rgba(theme-variables.prop-value(primary), 0.87) !default; $bottom-line-idle-color: rgba( theme-variables.prop-value(on-surface), 0.42 ) !default; $bottom-line-hover-color: rgba( theme-variables.prop-value(on-surface), 0.87 ) !default; $fill-color: color.mix( theme-variables.prop-value(on-surface), theme-variables.prop-value(surface), 4% ) !default; // Disabled Styles $disabled-label-color: rgba( theme-variables.prop-value(on-surface), 0.38 ) !default; $disabled-ink-color: rgba( theme-variables.prop-value(on-surface), 0.38 ) !default; $disabled-fill-color: color.mix( theme-variables.prop-value(on-surface), theme-variables.prop-value(surface), 2% ) !default; $disabled-fill-border: rgba( theme-variables.prop-value(on-surface), 0.06 ) !default; $disabled-bottom-line-color: rgba( theme-variables.prop-value(on-surface), 0.06 ) !default; $disabled-dropdown-icon-color: rgba( theme-variables.prop-value(on-surface), 0.38 ) !default; $disabled-outline-color: rgba( theme-variables.prop-value(on-surface), 0.06 ) !default; $outlined-idle-border: rgba( theme-variables.prop-value(on-surface), 0.38 ) !default; $outlined-hover-border: rgba( theme-variables.prop-value(on-surface), 0.87 ) !default; $label-position-y: 106% !default; $outline-label-offset: 16px !default; $outlined-label-position-y: get-outlined-label-position-y($height) !default; $outlined-with-leading-icon-label-position-x: 32px !default; $dropdown-transition-duration: 150ms !default; // Transition durartions to active state $icon-active-fade-out-duration: 0.33 * $dropdown-transition-duration !default; $icon-active-fade-in-duration: 0.67 * $dropdown-transition-duration !default; // Transition durartions to inactive state $icon-inactive-fade-out-duration: 0.5 * $dropdown-transition-duration !default; $icon-inactive-fade-in-duration: 0.5 * $dropdown-transition-duration !default; // Error colors $error-color: error !default; @mixin theme-styles($theme, $resolvers: resolvers.$material) { @include container-fill-color( ( default: map.get($theme, text-field-container-color), disabled: map.get($theme, text-field-disabled-container-color), ) ); @include outline-color( ( default: map.get($theme, text-field-outline-color), hover: map.get($theme, text-field-hover-outline-color), focus: map.get($theme, text-field-focus-outline-color), disabled: map.get($theme, text-field-disabled-outline-color), ) ); @include _error-outline-color( ( default: map.get($theme, text-field-error-outline-color), hover: map.get($theme, text-field-error-hover-outline-color), focus: map.get($theme, text-field-error-focus-outline-color), ) ); @include outline-width( ( default: map.get($theme, text-field-outline-width), hover: map.get($theme, text-field-hover-outline-width), focus: map.get($theme, text-field-focus-outline-width), ) ); @include _menu-container-color(map.get($theme, menu-container-color)); @include _menu-container-elevation(map.get($theme, menu-container-elevation)); @include _menu-container-shape(map.get($theme, menu-container-shape)); @include _menu-container-surface-tint-layer-color( map.get($theme, menu-container-surface-tint-layer-color) ); @include _menu-divider-color(map.get($theme, menu-divider-color)); @include _menu-divider-height(map.get($theme, menu-divider-height)); @include _menu-list-item-container-height( map.get($theme, menu-list-item-container-height) ); @include _menu-list-item-label-text-color( map.get($theme, menu-list-item-label-text-color) ); @include _menu-list-item-label-text-typography( ( font: map.get($theme, menu-list-item-label-text-font), line-height: map.get($theme, menu-list-item-label-text-line-height), size: map.get($theme, menu-list-item-label-text-size), tracking: map.get($theme, menu-list-item-label-text-tracking), weight: map.get($theme, menu-list-item-label-text-weight), ) ); @include _menu-list-item-state-layer( ( focus-state-layer-color: map.get($theme, menu-list-item-focus-state-layer-color), focus-state-layer-opacity: map.get($theme, menu-list-item-focus-state-layer-opacity), hover-state-layer-color: map.get($theme, menu-list-item-hover-state-layer-color), hover-state-layer-opacity: map.get($theme, menu-list-item-hover-state-layer-opacity), pressed-state-layer-color: map.get($theme, menu-list-item-pressed-state-layer-color), pressed-state-layer-opacity: map.get($theme, menu-list-item-pressed-state-layer-opacity), ) ); @include _menu-list-item-selected-container-color( map.get($theme, menu-list-item-selected-container-color) ); @include _menu-list-item-with-leading-icon-leading-icon-color( map.get($theme, menu-list-item-with-leading-icon-leading-icon-color) ); @include _menu-list-item-with-leading-icon-leading-icon-size( map.get($theme, menu-list-item-with-leading-icon-leading-icon-size) ); // TODO: Create new mixin for trailing icon theming when select supports it. @include _menu-list-item-with-leading-icon-leading-icon-color( map.get($theme, menu-list-item-with-trailing-icon-trailing-icon-color) ); @include _menu-list-item-with-leading-icon-leading-icon-size( map.get($theme, menu-list-item-with-trailing-icon-trailing-icon-size) ); @include bottom-line-color( ( default: map.get($theme, text-field-active-indicator-color), hover: map.get($theme, text-field-hover-active-indicator-color), focus: map.get($theme, text-field-focus-active-indicator-color), disabled: map.get($theme, text-field-disabled-active-indicator-color), ) ); @include dropdown-icon-size(map.get($theme, text-field-trailing-icon-size)); @include select-icon-theme.size( map.get($theme, text-field-leading-icon-size) ); @include _text-field-error-active-indicator-color( ( default: map.get($theme, text-field-error-active-indicator-color), hover: map.get($theme, text-field-error-hover-active-indicator-color), focus: map.get($theme, text-field-error-focus-active-indicator-color), ) ); @include _text-field-active-indicator-height( ( default: map.get($theme, text-field-active-indicator-height), focus: map.get($theme, text-field-focus-active-indicator-height), ) ); // TODO(b/259951357): Cleanup branch after fix and tokens upgrade. @if map.has-key($theme, text-field-caret-color) or map.has-key($theme, text-field-error-caret-color) { @include _text-field-trailing-icon-color( ( default: map.get($theme, text-field-caret-color), hover: map.get($theme, text-field-hover-caret-color), focus: map.get($theme, text-field-focus-caret-color), ) ); @include _text-field-error-trailing-icon-color( ( default: map.get($theme, text-field-error-caret-color), hover: map.get($theme, text-field-error-hover-caret-color), focus: map.get($theme, text-field-error-focus-caret-color), ) ); } @else { @include _text-field-trailing-icon-color( ( default: map.get($theme, text-field-trailing-icon-color), hover: map.get($theme, text-field-hover-trailing-icon-color), focus: map.get($theme, text-field-focus-trailing-icon-color), disabled: map.get($theme, text-field-disabled-trailing-icon-color), ) ); @include _text-field-error-trailing-icon-color( ( default: map.get($theme, text-field-error-trailing-icon-color), hover: map.get($theme, text-field-error-hover-trailing-icon-color), focus: map.get($theme, text-field-error-focus-trailing-icon-color), ) ); } @include ink-color( ( default: map.get($theme, text-field-input-text-color), hover: map.get($theme, text-field-hover-input-text-color), focus: map.get($theme, text-field-focus-input-text-color), disabled: map.get($theme, text-field-disabled-input-text-color), ) ); @include _error-input-text-color( ( default: map.get($theme, text-field-error-input-text-color), hover: map.get($theme, text-field-error-hover-input-text-color), focus: map.get($theme, text-field-error-focus-input-text-color), ) ); @include label-color( ( default: map.get($theme, text-field-label-text-color), hover: map.get($theme, text-field-hover-label-text-color), focus: map.get($theme, text-field-focus-label-text-color), disabled: map.get($theme, text-field-disabled-label-text-color), ) ); @include label-floating-color( ( default: map.get($theme, text-field-label-text-color), hover: map.get($theme, text-field-hover-label-text-color), focus: map.get($theme, text-field-focus-label-text-color), disabled: map.get($theme, text-field-disabled-label-text-color), ) ); @include _error-label-text-color( ( default: map.get($theme, text-field-error-label-text-color), hover: map.get($theme, text-field-error-hover-label-text-color), focus: map.get($theme, text-field-error-focus-label-text-color), ) ); @include _text-field-leading-icon-color( ( default: map.get($theme, text-field-leading-icon-color), hover: map.get($theme, text-field-hover-leading-icon-color), focus: map.get($theme, text-field-focus-leading-icon-color), disabled: map.get($theme, text-field-disabled-leading-icon-color), ) ); @include _error-text-field-leading-icon-color( ( default: map.get($theme, text-field-error-leading-icon-color), hover: map.get($theme, text-field-error-hover-leading-icon-color), focus: map.get($theme, text-field-error-focus-leading-icon-color), ) ); @include select-helper-text-theme.helper-text-color( ( default: map.get($theme, text-field-supporting-text-color), disabled: map.get($theme, text-field-disabled-supporting-text-color), hover: map.get($theme, text-field-hover-supporting-text-color), focus: map.get($theme, text-field-focus-supporting-text-color), ) ); @include select-helper-text-theme.helper-text-validation-color( ( default: map.get($theme, text-field-error-supporting-text-color), hover: map.get($theme, text-field-error-hover-supporting-text-color), focus: map.get($theme, text-field-error-focus-supporting-text-color), ) ); @include _text-field-input-text-typography( ( font: map.get($theme, text-field-input-text-font), line-height: map.get($theme, text-field-input-text-line-height), size: map.get($theme, text-field-input-text-size), tracking: map.get($theme, text-field-input-text-tracking), weight: map.get($theme, text-field-input-text-weight), ) ); @include _text-field-label-text-typography( ( font: map.get($theme, text-field-label-text-font), line-height: map.get($theme, text-field-label-text-line-height), size: map.get($theme, text-field-label-text-size), tracking: map.get($theme, text-field-label-text-tracking), weight: map.get($theme, text-field-label-text-weight), ) ); @include _text-field-label-text-populated-typography( ( line-height: map.get($theme, text-field-label-text-populated-line-height), size: map.get($theme, text-field-label-text-populated-size), ) ); @include _text-field-supporting-text-typography( ( font: map.get($theme, text-field-supporting-text-font), line-height: map.get($theme, text-field-supporting-text-line-height), size: map.get($theme, text-field-supporting-text-size), tracking: map.get($theme, text-field-supporting-text-tracking), weight: map.get($theme, text-field-supporting-text-weight), ) ); } @mixin _text-field-supporting-text-typography($typography-theme) { & + .mdc-select-helper-text { @include typography.theme-styles($typography-theme); } } @mixin _text-field-input-text-typography($typography-theme) { .mdc-select__selected-text { @include typography.theme-styles($typography-theme); } } @mixin _text-field-label-text-populated-typography($typography-theme) { .mdc-floating-label--float-above, // Used for CSS specificity to match with selector used in `outlined-height()`. &.mdc-select--with-leading-icon .mdc-select__anchor .mdc-notched-outline .mdc-floating-label.mdc-floating-label--float-above { @include typography.theme-styles($typography-theme); } } @mixin _text-field-label-text-typography($typography-theme) { .mdc-floating-label { @include typography.theme-styles($typography-theme); } } @mixin _error-outline-color($color) { &.mdc-select--invalid { @include outline-color($color); } } @mixin _error-text-field-leading-icon-color($color) { &.mdc-select--invalid { @include _text-field-leading-icon-color($color); } } @mixin _text-field-leading-icon-color($color) { @include _if-enabled { @include _set-text-field-leading-icon-color( state.get-default-state($color) ); &:not(.mdc-select--focused):hover { @include _set-text-field-leading-icon-color( state.get-hover-state($color) ); } @include _if-focused { @include _set-text-field-leading-icon-color( state.get-focus-state($color) ); } } @include _if-disabled { @include _set-text-field-leading-icon-color( state.get-disabled-state($color) ); } } @mixin _set-text-field-leading-icon-color($color) { .mdc-select__anchor .mdc-select__icon { @include theme.property(color, $color); } } @mixin _error-label-text-color($color) { &.mdc-select--invalid { @include label-color($color); } } @mixin _error-input-text-color($color) { &.mdc-select--invalid { @include ink-color($color); } } @mixin _text-field-trailing-icon-color($color) { @include dropdown-icon-color($color); } @mixin _text-field-error-trailing-icon-color($color) { &.mdc-select--invalid { @include dropdown-icon-color($color); } } @mixin _text-field-error-active-indicator-color($color) { &.mdc-select--invalid { @include bottom-line-color($color); } } @mixin _text-field-active-indicator-height($height) { .mdc-line-ripple { @include line-ripple-mixins.height(state.get-default-state($height)); @include line-ripple-mixins.active-height(state.get-focus-state($height)); } } @mixin _menu-list-item-with-leading-icon-leading-icon-color($color) { &:not(.mdc-select--disabled) .mdc-select__option .mdc-select__icon { @include theme.property(color, $color); } } @mixin _menu-list-item-with-leading-icon-leading-icon-size($size) { &:not(.mdc-select--disabled) .mdc-select__option .mdc-select__icon { @include theme.property(font-size, $size); } } @mixin _menu-list-item-selected-container-color($color) { .mdc-select__option { @include list-evolution-mixins.list-item-selected-container-color($color); } } @mixin _menu-list-item-label-text-typography($typography-theme) { .mdc-select__option { @include list-evolution-mixins.list-primary-text-typography( $typography-theme ); } } @mixin _menu-list-item-label-text-color($color) { .mdc-select__option { @include list-evolution-mixins.list-primary-text-ink-color($color); } } @mixin _menu-list-item-container-height($height) { .mdc-select__option { @include list-evolution-mixins.list-item-height($height); } } @mixin _menu-list-item-state-layer($ripple-theme) { .mdc-list-item:not(.mdc-list-item--disabled) { @include ripple-theme.theme-styles( $ripple-theme, $ripple-target: list-evolution-mixins.$ripple-target ); } } @mixin _menu-divider-color($color) { .mdc-select__list { @include list-evolution-mixins.divider-color($color); } } @mixin _menu-divider-height($height) { .mdc-select__list { @include list-evolution-mixins.divider-height($height); } } @mixin _menu-container-color($color) { .mdc-select__menu { @include menu-surface-mixins.fill-color($color); } } @mixin _menu-container-elevation($elevation) { @if $elevation { .mdc-select__menu { @include elevation-theme.shadow(map.get($elevation, shadow)); @include elevation-theme.overlay-opacity( map.get($elevation, overlay-opacity) ); } } } @mixin _menu-container-shape($shape) { .mdc-select__menu { @include menu-surface-mixins.shape-radius($shape); } } @mixin _menu-container-surface-tint-layer-color($color) { .mdc-select__menu { @include elevation-theme.overlay-container-color($color); } } @mixin menu-list-item-secondary-label-text-color($color) { .mdc-select__list { @include list-evolution-mixins.list-secondary-text-ink-color($color); } } @mixin menu-list-item-secondary-label-text-typography($typography-theme) { .mdc-select__list { @include list-evolution-mixins.list-secondary-text-typography( $typography-theme ); } } /// Sets the min-width of the select. /// @param {Number} $min-width - The desired min-width. /// @deprecated using this mixin is no longer required, and clients may set /// the attribute directly @mixin min-width($min-width, $query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); @include feature-targeting.targets($feat-structure) { min-width: $min-width; } } /// Sets the select behavior to change width dynamically based on content. /// @param {Number} $min-width - The min-width of the select, which should be /// large enough to allow the label (if exists) to display in full. @mixin variable-width($min-width, $query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); .mdc-select__anchor { @include feature-targeting.targets($feat-structure) { width: 100%; min-width: $min-width; } } } @mixin ink-color($color-or-map, $query: feature-targeting.all()) { @include _if-enabled { @include _ink-color(state.get-default-state($color-or-map), $query: $query); } @include _if-disabled { @include _ink-color( state.get-disabled-state($color-or-map), $query: $query ); } } @mixin container-fill-color($color-or-map, $query: feature-targeting.all()) { @include _if-enabled { @include _container-fill-color( state.get-default-state($color-or-map), $query: $query ); } @include _if-disabled { @include _container-fill-color( state.get-disabled-state($color-or-map), $query: $query ); } } @mixin dropdown-icon-color($color-or-map, $query: feature-targeting.all()) { @include _if-enabled { @include _dropdown-icon-color( state.get-default-state($color-or-map), $query: $query ); &:not(.mdc-select--focused):hover { @include _dropdown-icon-color( state.get-hover-state($color-or-map), $query: $query ); } @include _if-focused { @include _dropdown-icon-color( state.get-focus-state($color-or-map), $query: $query ); } } @include _if-disabled { @include _dropdown-icon-color( state.get-disabled-state($color-or-map), $query: $query ); } } @mixin label-floating-color($color-or-map, $query: feature-targeting.all()) { @include _if-enabled { @include _label-floating-color( state.get-default-state($color-or-map), $query: $query ); &:not(.mdc-select--focused):hover { @include _label-floating-color( state.get-hover-state($color-or-map), $query: $query ); } } } @mixin bottom-line-color($color-or-map, $query: feature-targeting.all()) { @include _if-enabled { @include _bottom-line-color( state.get-default-state($color-or-map), $query: $query ); // bottom line inactive/active are on different elements, does not need // to check for :not() focused &:hover { @include _bottom-line-color( state.get-hover-state($color-or-map), $query: $query ); } @include _focused-line-ripple-color( state.get-focus-state($color-or-map), $query: $query ); } @include _if-disabled { @include _bottom-line-color( state.get-disabled-state($color-or-map), $query: $query ); } } @mixin label-color($color-or-map, $query: feature-targeting.all()) { @include _if-enabled { @include _label-color( state.get-default-state($color-or-map), $query: $query ); @include _if-focused { @include _label-color( state.get-focus-state($color-or-map), $query: $query ); } &:not(.mdc-select--focused):hover { @include _label-color( state.get-hover-state($color-or-map), $query: $query ); } } @include _if-disabled { @include _label-color( state.get-disabled-state($color-or-map), $query: $query ); } } @mixin outline-color($color-or-map, $query: feature-targeting.all()) { @include _if-enabled { @include _outline-color( state.get-default-state($color-or-map), $query: $query ); &:not(.mdc-select--focused) .mdc-select__anchor:hover { @include _hover-outline-color( state.get-hover-state($color-or-map), $query: $query ); } @include _if-focused { @include _focused-outline-color( state.get-focus-state($color-or-map), $query: $query ); } } @include _if-disabled { @include _outline-color( state.get-disabled-state($color-or-map), $query: $query ); } } @mixin outline-width($width, $query: feature-targeting.all()) { @include _if-enabled { @include _outline-width(state.get-default-state($width), $query: $query); &:not(.mdc-select--focused) .mdc-select__anchor:hover { @include _outline-width(state.get-hover-state($width), $query: $query); } @include _if-focused { .mdc-notched-outline { @include _outline-width(state.get-focus-state($width), $query: $query); } } } } @mixin _outline-width($width, $query: feature-targeting.all()) { @if $width { @include notched-outline-mixins.stroke-width($width, $query: $query); } } /// /// Sets the dropdown icon to the specified size. /// @mixin dropdown-icon-size($size, $query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); .mdc-select__dropdown-icon { @include feature-targeting.targets($feat-structure) { @include theme.property(width, $size); @include theme.property(height, $size); } } } @mixin filled-shape-radius( $radius, $density-scale: $density-scale, $rtl-reflexive: false, $query: feature-targeting.all() ) { $height: density-functions.prop-value( $density-config: $density-config, $density-scale: $density-scale, $property-name: height, ); $masked-radius: if( (meta.type-of($radius) == 'list') and (list.length($radius) > 2), shape-functions.mask-radius($radius, 1 1 1 1), shape-functions.mask-radius($radius, 1 1 0 0) ); $fallback: if( custom-properties.is-custom-prop($radius), custom-properties.get-fallback($radius), null ); @if meta.type-of($fallback) == 'list' { $fallback: css.unpack-value($fallback); $first: list.nth($masked-radius, 1); $second: list.nth($masked-radius, 2); $third: list.nth($masked-radius, 3); $fourth: list.nth($masked-radius, 4); $masked-radius: ( if( custom-properties.is-custom-prop($first), custom-properties.set-fallback($first, list.nth($fallback, 1)), $first ), if( custom-properties.is-custom-prop($second), custom-properties.set-fallback($second, list.nth($fallback, 2)), $second ), if( custom-properties.is-custom-prop($third), custom-properties.set-fallback($third, list.nth($fallback, 3)), $third ), if( custom-properties.is-custom-prop($fourth), custom-properties.set-fallback($fourth, list.nth($fallback, 4)), $fourth ) ); } .mdc-select__anchor { @include shape-mixins.radius( $masked-radius, $rtl-reflexive, $component-height: $height, $query: $query ); } } @mixin outline-shape-radius( $radius, $density-scale: $density-scale, $rtl-reflexive: false, $query: feature-targeting.all(), $height: null ) { $feat-structure: feature-targeting.create-target($query, structure); @if not $height { $height: density-functions.prop-value( $density-config: $density-config, $density-scale: $density-scale, $property-name: height, ); } .mdc-notched-outline { @include notched-outline-mixins.shape-radius( $radius, $rtl-reflexive, $component-height: $height, $query: $query ); } $resolved-radius: shape-functions.resolve-radius( $radius, $component-height: $height ); $unpacked-radius: shape-functions.unpack-radius($resolved-radius); $top-left-radius: list.nth($unpacked-radius, 1); $top-left-is-custom-prop: custom-properties.is-custom-prop($top-left-radius); $top-left-radius-px: $top-left-radius; @if ($top-left-is-custom-prop) { $top-left-radius-px: custom-properties.get-fallback($top-left-radius); } @if ( $top-left-is-custom-prop or $top-left-radius-px > notched-outline-variables.$leading-width ) { .mdc-select__anchor { @include _apply-outline-shape-padding( padding-left, $top-left-radius, $add-label-padding: true, $query: $query ); @include rtl.rtl { @include feature-targeting.targets($feat-structure) { @include rtl.ignore-next-line(); padding-left: 0; } @include _apply-outline-shape-padding( padding-right, $top-left-radius, $add-label-padding: true, $query: $query ); } } + .mdc-select-helper-text { @include _apply-outline-shape-padding( margin-left, $top-left-radius, $add-label-padding: true, $query: $query ); @include rtl.rtl { @include feature-targeting.targets($feat-structure) { @include rtl.ignore-next-line(); margin-left: 0; } @include _apply-outline-shape-padding( margin-right, $top-left-radius, $add-label-padding: true, $query: $query ); } } // Unlike textfield, select does not need to re-apply leading icon padding. // This is because select only has one potential leading class, not two // extra classes like textfield (leading + trailing). Textfield's two extra // classes can cause specificity conflicts, requiring everything to be // re-applied. } } /// /// Sets density scale for filled select variant. /// /// @param {Number | String} $density-scale - Density scale value for component. Supported density scale values `-4`, /// `-3`, `-2`, `-1`, `0`. Default is `0`. /// @param {Number} $minimum-height-for-filled-label Sets the minimum height for /// filled selects at which to allow floating labels. /// @mixin filled-density( $density-scale, $minimum-height-for-filled-label: $minimum-height-for-filled-label, $query: feature-targeting.all() ) { $height: density-functions.prop-value( $density-config: $density-config, $density-scale: $density-scale, $property-name: height, ); @include filled-height( $height, $minimum-height-for-filled-label: $minimum-height-for-filled-label, $query: $query ); @include _list-density($density-scale, $query: $query); } /// /// Sets density scale for filled select variant with leading icon. /// /// @param {Number | String} $density-scale - Density scale value for component. Supported density scale values `-4`, /// `-3`, `-2`, `-1`, `0`. Default is `0`. /// @param {Number} $minimum-height-for-filled-label Sets the minimum height for /// filled selects at which to allow floating labels. /// @mixin filled-with-leading-icon-density( $density-scale, $minimum-height-for-filled-label: $minimum-height-for-filled-label, $query: feature-targeting.all() ) { $height: density-functions.prop-value( $density-config: $density-config, $density-scale: $density-scale, $property-name: height, ); @include filled-with-leading-icon-height( $height, $minimum-height-for-filled-label: $minimum-height-for-filled-label, $query: $query ); @include _list-density($density-scale, $query: $query); } /// /// Sets density scale for outlined select (Excluding outlined select with leading icon). /// /// @param {Number | String} $density-scale - Density scale value for component. Supported density scale values `-4`, /// `-3`, `-2`, `-1`, `0`. Default is `0`. /// @mixin outlined-density($density-scale, $query: feature-targeting.all()) { $height: density-functions.prop-value( $density-config: $density-config, $density-scale: $density-scale, $property-name: height, ); @include outlined-height($height, $query: $query); @include _list-density($density-scale, $query: $query); } /// /// Sets density scale for outlined select with leading icon. /// /// @param {Number | String} $density-scale - Density scale value for component. Supported density scale values `-4`, /// `-3`, `-2`, `-1`, `0`. Default is `0`. /// @mixin outlined-with-leading-icon-density( $density-scale, $query: feature-targeting.all() ) { $height: density-functions.prop-value( $density-config: $density-config, $density-scale: $density-scale, $property-name: height, ); @include outlined-with-leading-icon-height($height, $query: $query); @include _list-density($density-scale, $query: $query); } @mixin _list-density($density-scale, $query) { @include list-mixins.deprecated-single-line-density( $density-scale, $query: $query ); .mdc-select__one-line-option { @include list-evolution-mixins.one-line-item-density( $density-scale, $exclude-variants: true, $query: $query ); } .mdc-select__two-line-option { @include list-evolution-mixins.two-line-item-density( $density-scale, $query: $query ); } } /// /// Sets height of default select variant. /// /// @param {Number} $new-height /// @param {Number} $minimum-height-for-filled-label Sets the minimum height for /// filled selects at which to allow floating labels. /// @param {Number} $filled-baseline-top The baseline from the top of the anchor /// that the input should be aligned to for a filled variant with a label /// @access public /// @mixin filled-height( $new-height, $minimum-height-for-filled-label: $minimum-height-for-filled-label, $filled-baseline-top: $filled-baseline-top, $query: feature-targeting.all() ) { $feat-structure: feature-targeting.create-target($query, structure); .mdc-select__anchor { @include feature-targeting.targets($feat-structure) { @include theme.property(height, $new-height); } // Filled variant is aligned to baseline... @include typography-mixins.baseline( $top: $filled-baseline-top, $display: flex, $query: $query ); // ...unless it is too small to display a label // TODO(b/283457421): Account for dynamic height @if _is-less-than($new-height, $minimum-height-for-filled-label) { @include center-aligned($query: $query); @include feature-targeting.targets($feat-structure) { .mdc-floating-label { display: none; } } } } // No-label variants are always centered &.mdc-select--no-label .mdc-select__anchor { @include center-aligned($query: $query); } // TODO(b/283457421): Account for dynamic height @if _is-less-than($new-height, $height) { @include dropdown-icon-size( select-icon-theme.$dense-icon-size, $query: $query ); &.mdc-select--filled { @include truncate-floating-label-max-width( $leading-icon-size: 0, $dropdown-icon-size: select-icon-theme.$dense-icon-size, $query: $query ); } } } /// /// Sets height of filled select variant with leading icon. /// /// @param {Number} $height /// @param {Number} $minimum-height-for-filled-label Sets the minimum height for /// filled selects at which to allow floating labels. /// @param {Number} $filled-baseline-top The baseline from the top of the anchor /// that the input should be aligned to for a filled variant with a label /// @access public /// @mixin filled-with-leading-icon-height( $new-height, $minimum-height-for-filled-label: $minimum-height-for-filled-label, $filled-baseline-top: $filled-baseline-top, $query: feature-targeting.all() ) { $feat-structure: feature-targeting.create-target($query, structure); @include filled-height( $new-height, $minimum-height-for-filled-label: $minimum-height-for-filled-label, $filled-baseline-top: $filled-baseline-top, $query: $query ); // TODO(b/283457421): Account for dynamic height @if _is-less-than($new-height, $height) { @include select-icon-theme.size( select-icon-theme.$dense-icon-size, $query: $query ); &.mdc-select--filled { @include truncate-floating-label-max-width( $leading-icon-size: select-icon-theme.$dense-icon-size, $dropdown-icon-size: select-icon-theme.$dense-icon-size, $query: $query ); } .mdc-deprecated-list-item__graphic { width: select-icon-theme.$dense-icon-size; height: select-icon-theme.$dense-icon-size; } @include list-evolution-mixins.item-start-size( $width: select-icon-theme.$dense-icon-size + 12px, $height: select-icon-theme.$dense-icon-size, $query: $query ); .mdc-select__anchor { @include leading-icon-floating-label-position( select-icon-theme.$dense-icon-size, $query: $query ); } } } /// /// Sets height of outlined select variant (Excluding outlined select with leading icon). /// /// @param {Number} $new-height /// @param {String} $keyframe-suffix - Optional suffix to use for generated /// floating label keyframes /// @mixin outlined-height( $new-height, $keyframe-suffix: null, $query: feature-targeting.all() ) { $feat-structure: feature-targeting.create-target($query, structure); $positionY: get-outlined-label-position-y($new-height); @if $keyframe-suffix == null { $modifier: $new-height; @if custom-properties.is-custom-prop($modifier) { $modifier: custom-properties.get-fallback($modifier); } $keyframe-suffix: select-outlined-#{$modifier}; } .mdc-select__anchor { // Floating label position @include notched-outline-mixins.floating-label-float-position-absolute( $positionY, $query: $query ); // Floating label animation @include floating-label-mixins.shake-animation( $keyframe-suffix, $query: $query ); @at-root { @include floating-label-mixins.shake-keyframes( $keyframe-suffix, $positionY, $query: $query ); } @include feature-targeting.targets($feat-structure) { @include theme.property(height, $new-height); } } // TODO(b/283457421): Account for dynamic height @if _is-less-than($new-height, $height) { @include dropdown-icon-size( select-icon-theme.$dense-icon-size, $query: $query ); &.mdc-select--outlined { @include truncate-notched-outline-max-width( $leading-icon-size: 0, $dropdown-icon-size: select-icon-theme.$dense-icon-size, $query: $query ); } } } /// /// Sets height of outlined select with leading icon variant. /// /// @param {Number} $new-height /// @param {String} $keyframe-suffix - Optional suffix to use for generated /// floating label keyframes /// @mixin outlined-with-leading-icon-height( $new-height, $keyframe-suffix: null, $query: feature-targeting.all() ) { $feat-structure: feature-targeting.create-target($query, structure); .mdc-select__anchor { // TODO(b/283457421): Account for dynamic height @if _is-less-than($new-height, $height) { @include dropdown-icon-size( select-icon-theme.$dense-icon-size, $query: $query ); @include outlined-with-leading-icon-floating-label-position-animation( $new-height, select-icon-theme.$dense-icon-size, $keyframe-suffix, $query: $query ); } @else { @include outlined-with-leading-icon-floating-label-position-animation( $new-height, select-icon-theme.$icon-size, $keyframe-suffix, $query: $query ); } @include feature-targeting.targets($feat-structure) { @include theme.property(height, $new-height); } } // TODO(b/283457421): Account for dynamic height @if _is-less-than($new-height, $height) { .mdc-deprecated-list-item__graphic { width: select-icon-theme.$dense-icon-size; height: select-icon-theme.$dense-icon-size; } @include list-evolution-mixins.item-start-size( $width: select-icon-theme.$dense-icon-size + 12px, $height: select-icon-theme.$dense-icon-size, $query: $query ); @include select-icon-theme.size( select-icon-theme.$dense-icon-size, $query: $query ); &.mdc-select--outlined { @include truncate-notched-outline-max-width( $leading-icon-size: select-icon-theme.$dense-icon-size, $dropdown-icon-size: select-icon-theme.$dense-icon-size, $query: $query ); } } } // $add-label-padding is copied from textfield's mixin, even though select // always sets it to true. This is to try and keep things aligned between select // and textfield for when these styles are refactored into shared styles. @mixin _apply-outline-shape-padding( $property, $padding, $add-label-padding: false, $query: feature-targeting.all() ) { $feat-structure: feature-targeting.create-target($query, structure); $padding-is-custom-prop: custom-properties.is-custom-prop($padding); $padding-px: $padding; @if ($padding-is-custom-prop) { $padding-px: custom-properties.get-fallback($padding); } @include feature-targeting.targets($feat-structure) { // The shape should only change the padding if the radius becomes greater // than the default padding. That means we need to add more padding. @if ($padding-px > notched-outline-variables.$leading-width) { // Set a px value if it's greater. This is either the only value (if // we're given an exact value), or an IE11 fallback if we're given a // custom property and the fallback value is greater than the padding. $value: $padding-px; @if ($add-label-padding) { // If this is for the top-left leading, add the notched outline padding // to keep it aligned with the label $value: $padding-px + notched-outline-variables.$padding; } @include rtl.ignore-next-line(); #{$property}: $value; @include gss.annotate( ( alternate: $padding-is-custom-prop, ) ); } @if ($padding-is-custom-prop) { // If it's a custom property, always add it since the value may change // to be greater than the padding at runtime, even if the fallback is // not currently greater than the default padding. $value: custom-properties.create-var($padding); @if ($add-label-padding) { $value: calc(#{$value} + #{notched-outline-variables.$padding}); } // Interpolation is a workaround for sass/sass#3259. @supports (top: max(#{0%})) { // A max() function makes this runtime dynamic. The padding will be // whichever is greater: the default horizontal padding, or the // calculated custom property plus extra padding. @include rtl.ignore-next-line(); #{$property}: max(#{$anchor-padding-left}, #{$value}); } } } } // Removes filled baseline alignment @mixin center-aligned($query: feature-targeting.all()) { $feat-structure: feature-targeting.create-target($query, structure); .mdc-select__selected-text { @include typography-mixins.zero-width-prefix($query: $query); } @include feature-targeting.targets($feat-structure) { // In order for a flexbox container to participate in baseline alignment, // it follows these rules to determine where its baseline is: // https://www.w3.org/TR/css-flexbox-1/#flex-baselines // // In order to avoid leading icons "controlling" the baseline (since they // are the first child), flexbox will generate a baseline from any child // flex items that participate in baseline alignment. // // Icons are set to "align-self: center", while all other children are // aligned to baseline. The next problem is deciding which child is // used to determine the baseline. // // According to spec, the item with the largest distance between its // baseline and the edge of the cross axis is placed flush with that edge, // making it the baseline of the container. // https://www.w3.org/TR/css-flexbox-1/#baseline-participation // // For the filled variant, the pseudo ::before strut is the "largest" // child since the input has a height of 28px and the strut is 40px. We // can emulate center alignment and force the baseline to use the input // text by making the input the full height of the container and removing // the baseline strut. // // IE11 does not respect this, and makes the leading icon (if present) // the baseline. .mdc-select__selected-text-container { height: 100%; display: inline-flex; align-items: center; } &::before { display: none; } } } /// /// Sets the position of the floating label for a select with leading icon. /// @param {number} $icon-size - The size of the leading icon. /// @mixin leading-icon-floating-label-position( $icon-size: select-icon-theme.$icon-size, $query: feature-targeting.all() ) { $feat-structure: feature-targeting.create-target($query, structure); $icon-total-width: $icon-size + 2 * select-icon-theme.$icon-horizontal-margin; .mdc-floating-label { @include feature-targeting.targets($feat-structure) { @include rtl.reflexive-position(left, $icon-total-width); } } } /// /// Sets the floating label position and animations for a given height for an /// outlined select with leaing icon. /// @param {number} $icon-size - The size of the leading icon. /// @param {string} $keyframe-suffix - The suffix for the newly generated keyframes. /// @mixin outlined-with-leading-icon-floating-label-position-animation( $height, $icon-size, $keyframe-suffix: select-outlined-leading-icon-#{$height}, $query: feature-targeting.all() ) { $feat-structure: feature-targeting.create-target($query, structure); $icon-total-width: $icon-size + 2 * select-icon-theme.$icon-horizontal-margin; $resting-position-x: $icon-total-width - select-icon-theme.$icon-horizontal-margin; $float-position-y: get-outlined-label-position-y($height); $float-position-x: $icon-size + select-icon-theme.$icon-horizontal-margin - notched-outline-variables.$notch-gutter-size; // Resting label position .mdc-floating-label { @include feature-targeting.targets($feat-structure) { @include rtl.reflexive-position(left, $resting-position-x); } } // Floating label position @include notched-outline-mixins.floating-label-float-position-absolute( $float-position-y, $float-position-x, $query: $query ); // Floating label animation @include floating-label-mixins.shake-animation( $keyframe-suffix, $query: $query ); @at-root { @include floating-label-mixins.shake-keyframes( $keyframe-suffix, $float-position-y, $float-position-x, $query: $query ); } $keyframe-suffix-rtl: #{$keyframe-suffix}-rtl; @include rtl.rtl { @include floating-label-mixins.shake-animation( $keyframe-suffix, $query: $query ); } @at-root { @include floating-label-mixins.shake-keyframes( $keyframe-suffix-rtl, $float-position-y, -($float-position-x), $query: $query ); } } /// /// Truncates the max-width of the floating label according to sizes of the /// leading icon and dropdown icon. /// /// @param {Number} $leading-icon-size - Size of leading icon. /// @param {Number} $dropdown-icon-size - Size of dropdown icon. /// @mixin truncate-floating-label-max-width( $leading-icon-size, $dropdown-icon-size, $query: feature-targeting.all() ) { $truncation: select-icon-theme.$icon-horizontal-margin * 2 + $dropdown-icon-size; @if $leading-icon-size > 0 { $truncation: $truncation + select-icon-theme.$icon-horizontal-margin * 2 + $leading-icon-size; } @else { $truncation: $truncation + $outline-label-offset; } .mdc-floating-label { @include floating-label-mixins.max-width( calc(100% - #{$truncation}), $query: $query ); } .mdc-floating-label--float-above { $scale: floating-label-variables.$float-scale; @include floating-label-mixins.max-width( calc(100% / #{$scale} - #{$truncation} / #{$scale}), $query: $query ); } } /// /// Truncates the max-width of the notched outline according to the sizes of /// the leading icon and dropdown icon. /// /// @param {Number} $leading-icon-size - Size of leading icon. /// @param {Number} $dropdown-icon-size - Size of dropdown icon. /// @mixin truncate-notched-outline-max-width( $leading-icon-size, $dropdown-icon-size, $query: feature-targeting.all() ) { $truncation: select-icon-theme.$icon-horizontal-margin * 2 + $dropdown-icon-size + notched-outline-variables.$leading-width; @if $leading-icon-size > 0 { $truncation: $truncation + select-icon-theme.$icon-horizontal-margin + $leading-icon-size; } .mdc-select__anchor { @include notched-outline-mixins.notch-max-width( calc(100% - #{$truncation}), $query: $query ); } } /// Selector for focused state /// @access private @mixin _if-focused { &.mdc-select--focused { @content; } } /// Selector for enabled state /// @access private @mixin _if-enabled { &:not(.mdc-select--disabled) { @content; } } /// Selector for disabled state /// @access private @mixin _if-disabled { &.mdc-select--disabled { @content; } } @mixin _ink-color($color, $query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); @if $color { .mdc-select__selected-text { @include feature-targeting.targets($feat-color) { @include theme.property(color, $color); } } } } @mixin _container-fill-color($color, $query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); @if $color { .mdc-select__anchor { @include feature-targeting.targets($feat-color) { @include theme.property(background-color, $color); } } } } @mixin _bottom-line-color($color, $query: feature-targeting.all()) { @if $color { .mdc-line-ripple { @include line-ripple-mixins.inactive-color($color, $query: $query); } } } @mixin _focused-line-ripple-color($color, $query: feature-targeting.all()) { @if $color { .mdc-line-ripple { @include line-ripple-mixins.active-color($color, $query: $query); } } } @mixin _outline-color($color, $query: feature-targeting.all()) { @if $color { @include notched-outline-mixins.color($color, $query: $query); } } @mixin _hover-outline-color($color, $query: feature-targeting.all()) { @if $color { .mdc-notched-outline { @include notched-outline-mixins.color($color, $query: $query); } } } @mixin _focused-outline-color($color, $query: feature-targeting.all()) { @if $color { .mdc-notched-outline { @include notched-outline-mixins.color($color, $query: $query); } } } @mixin _label-color($color, $query: feature-targeting.all()) { @if $color { .mdc-floating-label { @include floating-label-mixins.ink-color($color, $query: $query); } } } @mixin _label-floating-color($color, $query: feature-targeting.all()) { @if $color { .mdc-floating-label--float-above { @include floating-label-mixins.ink-color($color, $query: $query); } } } /// /// Sets the dropdown icon to the specified color. /// @access private /// @mixin _dropdown-icon-color($color, $query: feature-targeting.all()) { $feat-color: feature-targeting.create-target($query, color); @if $color { @include feature-targeting.targets($feat-color) { .mdc-select__dropdown-icon { @include theme.property(fill, $color); } } } } /// /// Compare the two inputs even if they're custom-prop objects. /// @access private /// @function _is-less-than($subject, $object) { $fallback-subject: if( custom-properties.is-custom-prop($subject), custom-properties.get-fallback($subject), $subject ); $fallback-object: if( custom-properties.is-custom-prop($object), custom-properties.get-fallback($object), $object ); @return $fallback-subject < $fallback-object; }