@material/textfield
Version:
The Material Components for the web text field component
2,614 lines (2,265 loc) • 80.2 kB
Plain Text
//
// 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.
//
// Selector '.mdc-*' should only be used in this project.
// stylelint-disable selector-class-pattern --
// TODO: document why this disable is neccessary
'sass:math';
'sass:list';
'sass:meta';
'/animation/animation';
'/density/functions' as density-functions;
'/dom/dom';
'/floating-label/mixins' as floating-label-mixins;
'/floating-label/variables' as floating-label-variables;
'/line-ripple/mixins' as line-ripple-mixins;
'/notched-outline/mixins' as notched-outline-mixins;
'/notched-outline/variables' as notched-outline-variables;
'/ripple/ripple';
'/ripple/ripple-theme';
'/theme/custom-properties';
'/theme/theme';
'/shape/mixins' as shape-mixins;
'/shape/functions' as shape-functions;
'/feature-targeting/feature-targeting';
'/typography/typography';
'helper-text/mixins' as helper-text-mixins;
'character-counter/mixins' as character-counter-mixins;
'icon/mixins' as icon-mixins;
'icon/variables' as icon-variables;
'./variables';
'/rtl/rtl';
core-styles($query: feature-targeting.all()) {
ripple($query);
without-ripple($query);
helper-text-mixins.helper-text-core-styles($query);
character-counter-mixins.character-counter-core-styles($query);
icon-mixins.icon-core-styles($query);
}
without-ripple($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
// Baseline
// postcss-bem-linter: define text-field
.mdc-text-field {
_base($query);
}
.mdc-text-field__input {
_input($query);
placeholder-selector_ {
_input-placeholder($query);
}
// Always show placeholder for text fields that has no
// label and show only on focused state when label is present.
.mdc-text-field--no-label &,
.mdc-text-field--focused & {
placeholder-selector_ {
_input-placeholder-visible($query);
}
}
}
.mdc-text-field__affix {
_affix($query: $query);
.mdc-text-field--label-floating &,
.mdc-text-field--no-label & {
_affix-visible($query: $query);
}
// Safari only
(-webkit-hyphens: none) {
.mdc-text-field--outlined & {
_centered-affix-safari-support($query: $query);
}
}
}
.mdc-text-field__affix--prefix {
_prefix($query: $query);
.mdc-text-field--end-aligned & {
_prefix-end-aligned($query: $query);
}
}
.mdc-text-field__affix--suffix {
_suffix($query: $query);
.mdc-text-field--end-aligned & {
_suffix-end-aligned($query: $query);
}
}
// Variants
.mdc-text-field--filled {
_filled($query);
&.mdc-text-field--no-label {
_filled-no-label($query);
}
}
.mdc-text-field--outlined {
outlined_($query);
.mdc-notched-outline {
_outlined-notched-outline($query);
}
}
// Other Variations
.mdc-text-field--textarea {
textarea_($query);
.mdc-text-field__input {
_textarea-input($query);
}
&.mdc-text-field--filled {
_textarea-filled($query);
.mdc-text-field__input {
_textarea-filled-input($query);
}
&.mdc-text-field--no-label {
.mdc-text-field__input {
_textarea-filled-no-label-input($query);
}
}
}
&.mdc-text-field--outlined {
_textarea-outlined($query);
.mdc-text-field__input {
_textarea-outlined-input($query);
}
.mdc-floating-label {
_textarea-outlined-floating-label($query);
}
}
&.mdc-text-field--with-internal-counter {
.mdc-text-field__input {
_textarea-input-with-internal-counter($query);
}
.mdc-text-field-character-counter {
_textarea-internal-counter($query);
}
}
}
// Resizer element does not need to be under mdc-text-field--textarea, that
// just adds specificity
.mdc-text-field__resizer {
_textarea-resizer($query);
.mdc-text-field--filled & {
_textarea-filled-resizer($query);
.mdc-text-field__input,
.mdc-text-field-character-counter {
_textarea-filled-resizer-children($query);
}
}
.mdc-text-field--outlined & {
_textarea-outlined-resizer($query);
.mdc-text-field__input,
.mdc-text-field-character-counter {
_textarea-outlined-resizer-children($query);
}
}
}
.mdc-text-field--with-leading-icon {
_padding-horizontal-with-leading-icon($query);
&.mdc-text-field--filled {
with-leading-icon_($query);
}
&.mdc-text-field--outlined {
outlined-with-leading-icon_($query);
}
}
.mdc-text-field--with-trailing-icon {
_padding-horizontal-with-trailing-icon($query);
&.mdc-text-field--filled {
_with-trailing-icon($query);
}
&.mdc-text-field--outlined {
_outlined-with-trailing-icon($query);
}
}
.mdc-text-field--with-leading-icon.mdc-text-field--with-trailing-icon {
_padding-horizontal-with-both-icons($query);
&.mdc-text-field--filled {
_with-leading-and-trailing-icon($query);
}
}
// postcss-bem-linter: define text-field-helper-text
// stylelint-disable plugin/selector-bem-pattern --
// TODO: document why this disable is neccessary
.mdc-text-field-helper-line {
feature-targeting.targets($feat-structure) {
display: flex;
justify-content: space-between;
box-sizing: border-box;
}
.mdc-text-field + & {
feature-targeting.targets($feat-structure) {
padding-right: variables.$helper-line-padding;
padding-left: variables.$helper-line-padding;
}
}
}
// stylelint-enable plugin/selector-bem-pattern
// postcss-bem-linter: end
// mdc-form-field tweaks to align text field label correctly
// stylelint-disable selector-max-type --
// TODO: document why this disable is neccessary
.mdc-form-field > .mdc-text-field + label {
feature-targeting.targets($feat-structure) {
align-self: flex-start;
}
}
// stylelint-enable selector-max-type
// States
.mdc-text-field--focused {
focused_($query);
&.mdc-text-field--outlined {
_focused-outlined($query);
&.mdc-text-field--textarea {
_focused-outlined-textarea($query);
}
}
}
.mdc-text-field--invalid {
invalid_($query);
}
.mdc-text-field--disabled {
disabled_($query);
&.mdc-text-field--filled {
_disabled-filled($query);
}
.mdc-text-field__input {
_disabled-input($query);
}
}
.mdc-text-field--end-aligned {
end-aligned_($query);
}
.mdc-text-field--ltr-text {
_ltr-text($query);
&.mdc-text-field--end-aligned {
_ltr-text-end-aligned($query);
}
}
}
// This API is intended for use by frameworks that may want to separate the ripple-related styles
// from the other text field styles. It is recommended that most users use `mdc-text-field-core-styles` instead.
ripple($query: feature-targeting.all()) {
ripple.common($query); // COPYBARA_COMMENT_THIS_LINE
.mdc-text-field--filled {
ripple.surface(
$query: $query,
$ripple-target: variables.$ripple-target
);
ripple.radius-bounded(
$query: $query,
$ripple-target: variables.$ripple-target
);
}
#{variables.$ripple-target} {
ripple.target-common($query: $query);
}
}
///
/// Sets density scale for default text field 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 textfields at which to allow floating labels.
///
density(
$density-scale,
$minimum-height-for-filled-label: variables.$minimum-height-for-filled-label,
$query: feature-targeting.all()
) {
$height: density-functions.prop-value(
$density-config: variables.$density-config,
$density-scale: $density-scale,
$property-name: height,
);
height(
$height,
$minimum-height-for-filled-label: $minimum-height-for-filled-label,
$query: $query
);
// TODO(b/151839219): resize icons and adjust label position
// @if $density-scale < 0 {
// @include icon-mixins.size(icon-variables.$dense-icon-size);
// }
}
///
/// Sets density scale for outlined text field (Excluding outlined text field 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`.
///
outlined-density($density-scale, $query: feature-targeting.all()) {
$height: density-functions.prop-value(
$density-config: variables.$density-config,
$density-scale: $density-scale,
$property-name: height,
);
outlined-height($height, $query: $query);
// TODO(b/151839219): resize icons and adjust label position
// @if $density-scale < 0 {
// @include icon-mixins.size(icon-variables.$dense-icon-size);
// }
}
///
/// Sets density scale for outlined text field 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`.
///
outlined-with-leading-icon-density(
$density-scale,
$query: feature-targeting.all()
) {
$height: density-functions.prop-value(
$density-config: variables.$density-config,
$density-scale: $density-scale,
$property-name: height,
);
outlined-with-leading-icon-height($height, $query: $query);
// TODO(b/151839219): resize icons and adjust label position
// @if $density-scale < 0 {
// @include icon-mixins.size(icon-variables.$dense-icon-size);
// }
}
///
/// Sets density scale for filled textarea.
///
/// @param {Number | String} $density-scale - Density scale value for component. Supported density scale values `-4`,
/// `-3`, `-2`, `-1`, `0`. Default is `0`.
///
filled-textarea-density(
$density-scale,
$query: feature-targeting.all()
) {
$feat-structure: feature-targeting.create-target($query, structure);
$textfield-height: density-functions.prop-value(
$density-config: variables.$density-config,
$density-scale: $density-scale,
$property-name: height,
);
$no-label-margin-top: density-functions.prop-value(
$density-config: variables.$textarea-filled-no-label-density-config,
$density-scale: math.div($density-scale, 2),
$property-name: margin-top,
);
$no-label-margin-bottom: density-functions.prop-value(
$density-config: variables.$textarea-filled-no-label-density-config,
$density-scale: math.div($density-scale, 2),
$property-name: margin-bottom,
);
// Textarea mixins require two modifier classes since two are used internally
// for styles (textarea and filled). An extra class is added for the public
// mixin so that only a single public class is needed for specificity.
&.mdc-text-field--filled {
.mdc-text-field__resizer {
feature-targeting.targets($feat-structure) {
min-height: $textfield-height;
}
}
$density-scale >= -1 {
$keyframe-suffix: text-field-filled-#{$density-scale};
$label-top: density-functions.prop-value(
$density-config: variables.$textarea-filled-label-density-config,
$density-scale: math.div($density-scale, 2),
$property-name: top,
);
// Adjust the floating position and animation/keyframes of the floating
// label by the new position of the resting label
$label-top-difference: variables.$textarea-outlined-label-top -
$label-top;
// Floating label position
floating-label-mixins.float-position(
variables.$textarea-filled-label-position-y - $label-top-difference,
$query: $query
);
// Floating label animation
floating-label-mixins.shake-animation(
$keyframe-suffix,
$query: $query
);
-root {
floating-label-mixins.shake-keyframes(
$keyframe-suffix,
variables.$textarea-filled-label-position-y - $label-top-difference,
0%,
$query: $query
);
}
// Resting label position
.mdc-floating-label {
feature-targeting.targets($feat-structure) {
top: $label-top;
}
}
$margin-bottom: density-functions.prop-value(
$density-config: variables.$textarea-filled-density-config,
$density-scale: $density-scale,
$property-name: margin-bottom,
);
.mdc-text-field__input {
feature-targeting.targets($feat-structure) {
margin-bottom: $margin-bottom;
}
}
} {
// The textarea is too dense to show a floating label
.mdc-floating-label {
feature-targeting.targets($feat-structure) {
display: none;
}
}
.mdc-text-field__input {
feature-targeting.targets($feat-structure) {
margin-top: $no-label-margin-top;
margin-bottom: $no-label-margin-bottom;
}
}
}
&.mdc-text-field--no-label {
.mdc-text-field__input {
feature-targeting.targets($feat-structure) {
margin-top: $no-label-margin-top;
margin-bottom: $no-label-margin-bottom;
}
}
}
&.mdc-text-field--with-internal-counter {
.mdc-text-field__input {
// Space between textarea and internal counter should not be affected
_textarea-input-with-internal-counter($query);
}
}
}
}
///
/// Sets density scale for outlined textarea.
///
/// @param {Number | String} $density-scale - Density scale value for component. Supported density scale values `-4`,
/// `-3`, `-2`, `-1`, `0`. Default is `0`.
///
outlined-textarea-density(
$density-scale,
$query: feature-targeting.all()
) {
$feat-structure: feature-targeting.create-target($query, structure);
$keyframe-suffix: text-field-outlined-#{$density-scale};
$label-top: density-functions.prop-value(
$density-config: variables.$textarea-outlined-label-density-config,
$density-scale: math.div($density-scale, 2),
$property-name: top,
);
$textfield-height: density-functions.prop-value(
$density-config: variables.$density-config,
$density-scale: $density-scale,
$property-name: height,
);
$margin-top: density-functions.prop-value(
$density-config: variables.$textarea-outlined-density-config,
$density-scale: math.div($density-scale, 2),
$property-name: margin-top,
);
$margin-bottom: density-functions.prop-value(
$density-config: variables.$textarea-outlined-density-config,
$density-scale: math.div($density-scale, 2),
$property-name: margin-bottom,
);
// Textarea mixins require two modifier classes since two are used internally
// for styles (textarea and outlined). An extra class is added for the public
// mixin so that only a single public class is needed for specificity.
&.mdc-text-field--outlined {
// Adjust the floating position and animation/keyframes of the floating
// label by the new position of the resting label
$label-top-difference: variables.$textarea-outlined-label-top - $label-top;
// Floating label position
notched-outline-mixins.floating-label-float-position-absolute(
variables.$textarea-outlined-label-position-y - $label-top-difference,
$query: $query
);
// Floating label animation
floating-label-mixins.shake-animation(
$keyframe-suffix,
$query: $query
);
-root {
floating-label-mixins.shake-keyframes(
$keyframe-suffix,
variables.$textarea-outlined-label-position-y - $label-top-difference,
0%,
$query: $query
);
}
// Resting label position
.mdc-floating-label {
feature-targeting.targets($feat-structure) {
top: $label-top;
}
}
.mdc-text-field__resizer {
feature-targeting.targets($feat-structure) {
min-height: $textfield-height;
}
}
.mdc-text-field__input {
feature-targeting.targets($feat-structure) {
margin-top: $margin-top;
margin-bottom: $margin-bottom;
}
}
&.mdc-text-field--with-internal-counter {
.mdc-text-field__input {
// Space between textarea and internal counter should not be affected
_textarea-input-with-internal-counter($query);
}
}
}
}
///
/// Sets the minimum number of rows for a textarea a textarea may be resized to.
///
/// For IE11 this mixin can be used instead of the rows attribute.
///
/// @param {Number} $rows - The minimum number of rows for a textarea.
/// @param {Number} $line-height - The line-height of the textarea.
///
textarea-min-rows(
$rows,
$line-height: variables.$textarea-line-height,
$query: feature-targeting.all()
) {
$feat-structure: feature-targeting.create-target($query, structure);
.mdc-text-field__input {
feature-targeting.targets($feat-structure) {
min-height: $rows * $line-height;
}
}
}
///
/// Sets height of default text field variant.
///
/// @param {Number} $height
/// @param {Number} $minimum-height-for-filled-label Sets the minimum height for
/// filled textfields at which to allow floating labels.
/// @access public
///
height(
$height,
$minimum-height-for-filled-label: variables.$minimum-height-for-filled-label,
$query: feature-targeting.all()
) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
height: $height;
}
$height < $minimum-height-for-filled-label {
_filled-no-label($query: $query);
}
}
///
/// Sets height of outlined text field variant (Excluding outlined text field with leading icon).
///
/// @param {Number} $height
/// @param {String} $keyframe-suffix - Optional suffix to use for generated
/// floating label keyframes
/// @access public
///
outlined-height(
$height,
$keyframe-suffix: text-field-outlined-#{$height},
$query: feature-targeting.all()
) {
$feat-structure: feature-targeting.create-target($query, structure);
$positionY: variables.get-outlined-label-position-y($height);
// Floating label position
notched-outline-mixins.floating-label-float-position-absolute(
$positionY,
$query: $query
);
// Floating label animation
floating-label-mixins.shake-animation(
$keyframe-suffix,
$query: $query
);
-root {
floating-label-mixins.shake-keyframes(
$keyframe-suffix,
$positionY,
$query: $query
);
}
feature-targeting.targets($feat-structure) {
height: $height;
}
}
///
/// Sets height of outlined text field with leading icon variant.
///
/// @param {Number} $height
/// @param {String} $keyframe-suffix - Optional suffix to use for generated
/// floating label keyframes
/// @access public
///
outlined-with-leading-icon-height(
$height,
$keyframe-suffix: null,
$query: feature-targeting.all()
) {
$feat-structure: feature-targeting.create-target($query, structure);
// This extra specificity is needed because textfield applies the below mixin
// already to two selectors (outlined + with-leading-icon). To override
// them with a new label position and animation, another selector is needed.
&.mdc-text-field--outlined {
_outlined-with-leading-icon-floating-label-position-animation(
$height,
$keyframe-suffix,
$query
);
}
feature-targeting.targets($feat-structure) {
height: $height;
}
}
// Mixin that sets the floating label position and animations for a given height.
// This mixin is separate to allow outlined-with-leading-icon-height() to
// provide greater specificity over the default mixin that adds styles for
// outlined with leading icons.
_outlined-with-leading-icon-floating-label-position-animation(
$height,
$keyframe-suffix: text-field-outlined-with-leading-icon-#{$height},
$query: feature-targeting.all()
) {
$positionY: variables.get-outlined-label-position-y($height);
// Floating label position
notched-outline-mixins.floating-label-float-position-absolute(
$positionY,
variables.$outlined-with-leading-icon-label-position-x,
$query: $query
);
// Floating label animation
floating-label-mixins.shake-animation(
$keyframe-suffix,
$query: $query
);
-root {
floating-label-mixins.shake-keyframes(
$keyframe-suffix,
$positionY,
variables.$outlined-with-leading-icon-label-position-x,
$query: $query
);
}
$keyframe-suffix-rtl: #{$keyframe-suffix}-rtl;
rtl.rtl {
floating-label-mixins.shake-animation(
$keyframe-suffix,
$query: $query
);
}
-root {
floating-label-mixins.shake-keyframes(
$keyframe-suffix-rtl,
$positionY,
-(variables.$outlined-with-leading-icon-label-position-x),
$query: $query
);
}
}
///
/// Sets shape radius of default text field variant.
///
/// @param {Number} $radius Shape radius value in `px` or in percentage.
/// @param {Number} $text-field-height Height of default text field variant. Required only when `$radius` is in
/// percentage unit and if text field has custom height. Defaults to `variables.$height`.
/// @param {Boolean} $rtl-reflexive Set to true to flip shape radius in RTL context. Defaults to `false`.
///
shape-radius(
$radius,
$density-scale: variables.$density-scale,
$rtl-reflexive: false,
$query: feature-targeting.all()
) {
meta.type-of($radius) == 'list' and list.length($radius) > 2 {
"mdc-textfield: Invalid radius #{$radius}. Only top-left and top-right corners may be customized.";
}
$height: density-functions.prop-value(
$density-config: variables.$density-config,
$density-scale: $density-scale,
$property-name: height,
);
$masked-radius: shape-functions.mask-radius($radius, 1 1 0 0);
shape-mixins.radius(
$masked-radius,
$rtl-reflexive,
$component-height: $height,
$query: $query
);
}
textarea-shape-radius(
$radius,
$rtl-reflexive: false,
$query: feature-targeting.all()
) {
notched-outline-mixins.shape-radius(
$radius,
$rtl-reflexive,
$query: $query
);
}
///
/// Customizes the color of the text entered into an enabled text field.
/// @param {Color} $color - The desired input text color.
///
ink-color($color, $query: feature-targeting.all()) {
if-enabled_ {
ink-color_($color, $query: $query);
}
}
///
/// Customizes the color of the entered text in a disabled text field.
/// @param {Color} $color - The desired input text color.
///
disabled-ink-color($color, $query: feature-targeting.all()) {
if-disabled_ {
ink-color_($color, $query: $query);
}
}
///
/// Customizes the color of the placeholder in an enabled text field.
/// @param {Color} $color - The desired placeholder text color.
///
placeholder-color($color, $query: feature-targeting.all()) {
if-enabled_ {
placeholder-color_($color, $query: $query);
}
}
///
/// Customizes the color of the placeholder in a disabled text field.
/// @param {Color} $color - The desired placeholder text color.
///
disabled-placeholder-color($color, $query: feature-targeting.all()) {
if-disabled_ {
placeholder-color_($color, $query: $query);
}
}
///
/// Customizes the background color of the text field or textarea when enabled.
/// @param {Color} $color - The desired background color.
///
fill-color($color, $query: feature-targeting.all()) {
if-enabled_ {
fill-color_($color, $query: $query);
}
}
///
/// Customizes the background color of the text field or textarea when disabled.
/// @param {Color} $color - The desired background color.
///
disabled-fill-color($color, $query: feature-targeting.all()) {
if-disabled_ {
fill-color_($color, $query: $query);
}
}
///
/// Customizes the text field bottom line color for the filled variant.
/// @param {Color} $color - The desired bottom line color.
///
bottom-line-color($color, $query: feature-targeting.all()) {
if-enabled_ {
bottom-line-color_($color, $query: $query);
}
}
///
/// Customizes the disabled text field bottom line color for the filled variant.
/// @param {Color} $color - The desired bottom line color.
///
disabled-bottom-line-color($color, $query: feature-targeting.all()) {
if-disabled_ {
bottom-line-color_($color, $query: $query);
}
}
///
/// Customizes the hover text field bottom line color for the filled variant.
/// @param {Color} $color - The desired bottom line color.
///
hover-bottom-line-color($color, $query: feature-targeting.all()) {
if-enabled_ {
hover-bottom-line-color_($color, $query: $query);
}
}
///
/// Customizes the color of the default line ripple of the text field.
/// @param {Color} $color - The desired line ripple color.
///
line-ripple-color($color, $query: feature-targeting.all()) {
if-enabled_ {
line-ripple-color_($color, $query: $query);
}
}
///
/// Customizes the text color of the label in an enabled text field.
/// @param {Color} $color - The desired label text color.
///
label-color($color, $query: feature-targeting.all()) {
if-enabled_ {
label-ink-color_($color, $query: $query);
}
}
///
/// Customizes the text color of the label in a disabled text field.
/// @param {Color} $color - The desired label text color.
///
disabled-label-color($color, $query: feature-targeting.all()) {
if-disabled_ {
label-ink-color_($color, $query: $query);
}
}
///
/// Customizes the border color of the outlined text field or textarea.
/// @param {Color} $color - The desired outline border color.
///
outline-color($color, $query: feature-targeting.all()) {
if-enabled_ {
notched-outline-mixins.color($color, $query: $query);
}
}
///
/// Customizes the outline border color when the text field or textarea is hovered.
/// @param {Color} $color - The desired outline border color.
///
hover-outline-color($color, $query: feature-targeting.all()) {
if-enabled_ {
hover-outline-color_($color, $query: $query);
}
}
///
/// Customizes the outline border color when the text field or textarea is focused.
/// @param {Color} $color - The desired outline border color.
///
focused-outline-color($color, $query: feature-targeting.all()) {
if-enabled_ {
focused-outline-color_($color, $query: $query);
}
}
///
/// Customizes the outline border color when the text field or textarea is disabled.
/// @param {Color} $color - The desired outline border color.
///
disabled-outline-color($color, $query: feature-targeting.all()) {
if-disabled_ {
notched-outline-mixins.color($color, $query: $query);
}
}
///
/// Customizes the caret color of the text field or textarea.
/// @param {Color} $color - The desired caret color.
///
caret-color($color, $query: feature-targeting.all()) {
$feat-color: feature-targeting.create-target($query, color);
.mdc-text-field__input {
feature-targeting.targets($feat-color) {
theme.prop(caret-color, $color);
}
}
}
///
/// Customizes the color of the prefix text for an enabled text field.
/// @param {Color} $color - The desired prefix text color.
///
prefix-color($color, $query: feature-targeting.all()) {
if-enabled_ {
_prefix-color($color, $query: $query);
}
}
///
/// Customizes the color of the prefix text for a disabled text field.
/// @param {Color} $color - The desired prefix text color.
///
disabled-prefix-color($color, $query: feature-targeting.all()) {
if-disabled_ {
_prefix-color($color, $query: $query);
}
}
///
/// Customizes the color of the suffix text for an enabled text field.
/// @param {Color} $color - The desired suffix text color.
///
suffix-color($color, $query: feature-targeting.all()) {
if-enabled_ {
_suffix-color($color, $query: $query);
}
}
///
/// Customizes the color of the suffix text for a disabled text field.
/// @param {Color} $color - The desired suffix text color.
///
disabled-suffix-color($color, $query: feature-targeting.all()) {
if-disabled_ {
_suffix-color($color, $query: $query);
}
}
///
/// Sets shape radius of outlined text field variant.
///
/// @param {Number} $radius Shape radius value in `px` or in percentage.
/// @param {Number} $text-field-height Height of outlined text field variant. Required only when `$radius` is in
/// percentage unit and if text field has custom height. Defaults to `variables.$height`.
/// @param {Boolean} $rtl-reflexive Set to true to flip shape radius in RTL context. Defaults to `false`.
///
outline-shape-radius(
$radius,
$density-scale: variables.$density-scale,
$rtl-reflexive: false,
$query: feature-targeting.all()
) {
$feat-structure: feature-targeting.create-target($query, structure);
$height: density-functions.prop-value(
$density-config: variables.$density-config,
$density-scale: $density-scale,
$property-name: height,
);
.mdc-notched-outline {
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;
($top-left-is-custom-prop) {
$top-left-radius-px: custom-properties.get-fallback($top-left-radius);
}
$top-right-radius: list.nth($unpacked-radius, 2);
$top-right-is-custom-prop: custom-properties.is-custom-prop(
$top-right-radius
);
(
$top-left-is-custom-prop or
$top-right-is-custom-prop or
$top-left-radius-px >
notched-outline-variables.$leading-width
) {
// The horizontal padding only needs to be overriden from the base padding
// if the radius is a custom property, or if the top-left radius is a value
// that is large than that default notched outline's leading width.
_outline-shape-radius-horizontal-padding(
$top-left-radius,
$top-right-radius,
$query: $query
);
+ .mdc-text-field-helper-line {
_outline-shape-radius-horizontal-padding(
$top-left-radius,
$top-right-radius,
$query: $query
);
}
// Ensure that leading/trailing icon padding is overriden. Even if the
// top left/right isn't a custom property or the leading isn't larger, we
// still need to override. The above left/right padding rules have more
// specificty than the original leading/trailing icon rules, so we need to
// re-apply them.
// Additionally, if the top left/right radii _are_ custom properties, we
// should use those instead.
&.mdc-text-field--with-leading-icon {
($top-right-is-custom-prop) {
feature-targeting.targets($feat-structure) {
rtl.ignore-next-line();
padding-left: 0;
}
_apply-outline-shape-padding(
padding-right,
$top-right-radius,
$query: $query
);
rtl.rtl {
_apply-outline-shape-padding(
padding-left,
$top-right-radius,
$query: $query
);
feature-targeting.targets($feat-structure) {
rtl.ignore-next-line();
padding-right: 0;
}
}
} {
_padding-horizontal-with-leading-icon($query);
}
}
&.mdc-text-field--with-trailing-icon {
(
$top-left-is-custom-prop or
$top-left-radius-px >
notched-outline-variables.$leading-width
) {
_apply-outline-shape-padding(
padding-left,
$top-left-radius,
$add-label-padding: true,
$query: $query
);
feature-targeting.targets($feat-structure) {
rtl.ignore-next-line();
padding-right: 0;
}
rtl.rtl {
feature-targeting.targets($feat-structure) {
rtl.ignore-next-line();
padding-left: 0;
}
_apply-outline-shape-padding(
padding-right,
$top-left-radius,
$add-label-padding: true,
$query: $query
);
}
} {
_padding-horizontal-with-trailing-icon($query);
}
}
&.mdc-text-field--with-leading-icon.mdc-text-field--with-trailing-icon {
_padding-horizontal-with-both-icons($query);
}
}
}
_outline-shape-radius-horizontal-padding(
$top-left-radius,
$top-right-radius,
$query: feature-targeting.all()
) {
_apply-outline-shape-padding(
padding-left,
$top-left-radius,
$add-label-padding: true,
$query: $query
);
_apply-outline-shape-padding(
padding-right,
$top-right-radius,
$query: $query
);
$top-left-is-custom-prop: custom-properties.is-custom-prop($top-left-radius);
$top-left-radius-px: $top-left-radius;
($top-left-is-custom-prop) {
$top-left-radius-px: custom-properties.get-fallback($top-left-radius);
}
$top-right-is-custom-prop: custom-properties.is-custom-prop(
$top-right-radius
);
$top-right-radius-px: $top-right-radius;
($top-right-is-custom-prop) {
$top-right-radius-px: custom-properties.get-fallback($top-right-radius);
}
(
(
$top-left-is-custom-prop and
$top-right-is-custom-prop and not
custom-properties.are-equal($top-left-radius, $top-right-radius)
) or
$top-left-radius-px !=
$top-right-radius-px
) {
// Normally base horizontal padding doesn't need RTL, but if the values
// are different or they are two different custom properties, they need to
// be reversed.
rtl.rtl {
_apply-outline-shape-padding(
padding-right,
$top-left-radius,
$add-label-padding: true,
$query: $query
);
_apply-outline-shape-padding(
padding-left,
$top-right-radius,
$query: $query
);
}
}
}
_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;
($padding-is-custom-prop) {
$padding-px: custom-properties.get-fallback($padding);
}
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.
($padding-px > variables.$padding-horizontal) {
// 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;
($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;
}
rtl.ignore-next-line();
#{$property}: $value;
($padding-is-custom-prop) {
// Add an alternate GSS tag b/c this was an IE11 fallback and we're
// going to add another property with the var() value
/* @alternate */
}
}
($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);
($add-label-padding) {
$value: calc(#{$value} + #{notched-outline-variables.$padding});
}
// Interpolation is a workaround for sass/sass#3259.
(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.
rtl.ignore-next-line();
#{$property}: max(#{variables.$padding-horizontal}, #{$value});
}
}
}
}
///
/// Sets the CSS transition for the floating label's 'float' animation.
///
/// @param {Number} $duration-ms - Duration (in ms) of the animation.
/// @param {String} $timing-function - Optionally overrides the default animation timing function.
///
floating-label-float-transition(
$duration-ms,
$timing-function: null,
$query: feature-targeting.all()
) {
.mdc-floating-label {
floating-label-mixins.float-transition(
$duration-ms,
$timing-function,
$query: $query
);
}
}
///
/// Sets custom font size of the input.
///
/// @param {number} $font-size - Overrides the font size.
///
input-font-size($font-size, $query: feature-targeting.all()) {
$feat-typography: feature-targeting.create-target($query, typography);
.mdc-text-field__input {
feature-targeting.targets($feat-typography) {
font-size: $font-size;
}
}
}
///
/// Sets custom font family of the input.
///
/// @param {String} $font-family - Selected font family.
///
input-font-family($font-family, $query: feature-targeting.all()) {
$feat-typography: feature-targeting.create-target($query, typography);
.mdc-text-field__input {
feature-targeting.targets($feat-typography) {
font-family: $font-family;
}
}
}
// Private mixins
// Base shared styles
_base($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
// Shape
shape-radius(variables.$shape-radius, $query: $query);
// Colors
label-color(variables.$label, $query: $query);
ink-color(variables.$ink-color, $query: $query);
placeholder-color(variables.$placeholder-ink-color, $query: $query);
caret-color(primary, $query: $query);
helper-text-mixins.helper-text-color(
variables.$helper-text-color,
$query: $query
);
character-counter-mixins.character-counter-color(
variables.$helper-text-color,
$query: $query
);
icon-mixins.leading-icon-color(
variables.$icon-color,
$query: $query
);
icon-mixins.trailing-icon-color(
variables.$icon-color,
$query: $query
);
prefix-color(variables.$affix-color, $query: $query);
suffix-color(variables.$affix-color, $query: $query);
// Floating Label
floating-label_($query);
feature-targeting.targets($feat-structure) {
// display and align-items are necessary to make the text field participate
// in baseline alignment, even though some variants are 'centered'. Those
// variants should use the _baseline-center-aligned() mixin
display: inline-flex;
align-items: baseline;
padding: 0 variables.$padding-horizontal;
position: relative;
box-sizing: border-box;
overflow: hidden;
/* @alternate */
will-change: opacity, transform, color;
}
}
// This mixin adds styles to visually center the text within the text field.
// Sibling text will align to the baseline and appear centered next to the
// text field.
_baseline-center-aligned($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
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. This is a gap with IE11 that we have accepted.
.mdc-text-field__input {
height: 100%;
}
}
}
_padding-horizontal-with-leading-icon($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
rtl.reflexive-property(padding, 0, variables.$padding-horizontal);
}
}
_padding-horizontal-with-trailing-icon($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
rtl.reflexive-property(padding, variables.$padding-horizontal, 0);
}
}
_padding-horizontal-with-both-icons($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
rtl.ignore-next-line();
padding-left: 0;
rtl.ignore-next-line();
padding-right: 0;
}
}
floating-label_($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
.mdc-floating-label {
feature-targeting.targets($feat-structure) {
top: 50%;
transform: translateY(-50%);
pointer-events: none;
}
}
}
// Filled
_filled($query: feature-targeting.all()) {
// Text Field intentionally omits press ripple, so each state needs to be specified individually.
ripple-theme.states-base-color(
variables.$ink-color,
$query: $query,
$ripple-target: variables.$ripple-target
);
ripple-theme.states-hover-opacity(
ripple-theme.states-opacity(variables.$ink-color, hover),
$query: $query,
$ripple-target: variables.$ripple-target
);
ripple-theme.states-focus-opacity(
ripple-theme.states-opacity(variables.$ink-color, focus),
$query: $query,
$ripple-target: variables.$ripple-target
);
height(variables.$height, $query: $query);
typography.baseline-top(
variables.$filled-baseline-top,
$query: $query
);
fill-color(variables.$background, $query: $query);
bottom-line-color(variables.$bottom-line-idle, $query: $query);
hover-bottom-line-color(
variables.$bottom-line-hover,
$query: $query
);
line-ripple-color_(primary, $query: $query);
_filled-floating-label($query);
}
_filled-floating-label($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
.mdc-floating-label {
feature-targeting.targets($feat-structure) {
rtl.reflexive-position(left, variables.$label-offset);
}
}
floating-label-mixins.float-position(
variables.$label-position-y,
$query: $query
);
}
// Filled variant with no label. This variant centers the text elements and
// hides the label and is used with there is explicitly no label provided or
// when the height of the text field is too small for a label to be allowed.
_filled-no-label($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
_baseline-center-aligned($query);
feature-targeting.targets($feat-structure) {
.mdc-floating-label {
display: none;
}
&::before {
// Remove baseline-top strut
display: none;
}
}
// Safari only
(-webkit-hyphens: none) {
.mdc-text-field__affix {
_centered-affix-safari-support($query: $query);
}
}
}
// Outlined
outlined_($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
outlined-height(
$height: variables.$height,
$keyframe-suffix: text-field-outlined,
$query: $query
);
_baseline-center-aligned($query: $query);
outline-color(variables.$outlined-idle-border, $query: $query);
hover-outline-color(
variables.$outlined-hover-border,
$query: $query
);
focused-outline-color(primary, $query: $query);
outline-shape-radius(variables.$shape-radius, $query: $query);
notched-outline-mixins.notch-offset(
notched-outline-variables.$border-width,
$query: $query
);
ripple-theme.states-base-color(
transparent,
$query: $query,
$ripple-target: variables.$ripple-target
);
_outlined-floating-label($query);
feature-targeting.targets($feat-structure) {
overflow: visible;
}
.mdc-text-field__input {
feature-targeting.targets($feat-structure) {
// TODO(b/154349735): Investigate the neccessity of these styles
display: flex;
border: none !important; // FF adds unwanted border in HC mode on windows.
background-color: transparent;
}
}
}
_outlined-floating-label($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
.mdc-floating-label {
feature-targeting.targets($feat-structure) {
rtl.reflexive-position(left, notched-outline-variables.$padding);
}
}
}
_outlined-notched-outline($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
// Force the outline to appear "above" the textfield elements, even though
// it is absolutely positioned and comes before the input in the DOM. This
// is primarily for the textarea scrollbar and resize elements, which may
// clip with with outline border.
z-index: 1;
}
}
// States
disabled_($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
ink-color_(variables.$disabled-ink-color, $query: $query);
placeholder-color_(
variables.$disabled-placeholder-ink-color,
$query: $query
);
label-ink-color_(variables.$disabled-label-color, $query: $query);
helper-text-mixins.helper-text-color_(
variables.$disabled-helper-text-color,
$query: $query
);
character-counter-mixins.character-counter-color_(
variables.$disabled-helper-text-color,
$query: $query
);
icon-mixins.leading-icon-color_(
variables.$disabled-icon,
$query: $query
);
icon-mixins.trailing-icon-color_(
variables.$disabled-icon,
$query: $query
);
_prefix-color(variables.$disabled-affix-color, $query: $query);
_suffix-color(variables.$disabled-affix-color, $query: $query);
// Mixins that are ok to include since they target variant-specific elements
bottom-line-color_(variables.$disabled-border, $query: $query);
notched-outline-mixins.color(
variables.$outlined-disabled-border,
$query: $query
);
dom.forced-colors-mode {
placeholder-color_(GrayText, $query: $query);
label-ink-color_(GrayText, $query: $query);
helper-text-mixins.helper-text-color_(GrayText, $query: $query);
character-counter-mixins.character-counter-color_(
GrayText,
$query: $query
);
icon-mixins.leading-icon-color_(GrayText, $query: $query);
icon-mixins.trailing-icon-color_(GrayText, $query: $query);
_prefix-color(GrayText, $query: $query);
_suffix-color(GrayText, $query: $query);
// Mixins that are ok to include since they target variant-specific elements
bottom-line-color_(GrayText, $query: $query);
notched-outline-mixins.color(GrayText, $query: $query);
}
dom.forced-colors-mode($exclude-ie11: true) {
.mdc-text-field__input {
feature-targeting.targets($feat-structure) {
background-color: Window;
}
}
.mdc-floating-label {
feature-targeting.targets($feat-structure) {
z-index: 1;
}
}
}
feature-targeting.targets($feat-structure) {
pointer-events: none;
}
.mdc-floating-label {
feature-targeting.targets($feat-structure) {
cursor: default;
}
}
}
_disabled-input($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
// disabled inputs should still allow users to interact with them to select
// text and scroll for textareas
pointer-events: auto;
}
}
_disabled-filled($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
fill-color_(variables.$disabled-background, $query: $query);
#{variables.$ripple-target} {
feature-targeting.targets($feat-structure) {
// prevent ripple from displaying on hover when some interactible
// elements like input and resize handles are hovered
display: none;
}
}
}
invalid_($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
hover-bottom-line-color(variables.$error, $query: $query);
line-ripple-color(variables.$error, $query: $query);
label-color(variables.$error, $query: $query);
helper-text-mixins.helper-text-validation-color(
variables.$error,
$query: $query
);
caret-color(variables.$error, $query: $query);
icon-mixins.trailing-icon-color(variables.$error, $query: $query);
// Mixins that are ok to include since they target variant-specific elements
bottom-line-color(variables.$error, $query: $query);
outline-color(variables.$error, $query: $query);
hover-outline-color(variables.$error, $query: $query);
focused-outline-color(variables.$error, $query: $query);
+ .mdc-text-field-helper-line .mdc-text-field-helper-text--validation-msg {
feature-targeting.targets($feat-structure) {
opacity: 1;
}
}
}
focused_($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
label-color(variables.$focused-label-color, $query: $query);
// Mixins that are ok to include since they target variant-specific elements
notched-outline-mixins.stroke-width(
variables.$outlined-stroke-width,
$query: $query
);
+ .mdc-text-field-helper-line
.mdc-text-field-helper-text:not(.mdc-text-field-helper-text--validation-msg) {
feature-targeting.targets($feat-structure) {
opacity: 1;
}
}
}
_focused-outlined($query: feature-targeting.all()) {
notched-outline-mixins.notch-offset(
variables.$outlined-stroke-width,
$query: $query
);
}
_focused-outlined-textarea($query: feature-targeting.all()) {
notched-outline-mixins.notch-offset(0, $query: $query);
}
// Icons
with-leading-icon_($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
$icon-padding: icon-variables.$leading-icon-padding-left +
icon-variables.$icon-size + icon-variables.$leading-icon-padding-right;
.mdc-floating-label {
_truncate-floating-label-max-width($icon-padding, $query: $query);
feature-targeting.targets($feat-structure) {
rtl.reflexive-position(left, $icon-padding);
}
}
$truncation: $icon-padding + variables.$padding-horizontal;
.mdc-floating-label--float-above {
_truncate-floating-label-floated-max-width(
$truncation,
$query: $query
);
}
}
_with-trailing-icon($query: feature-targeting.all()) {
$truncation: icon-variables.$trailing-icon-padding-left +
icon-variables.$icon-size + icon-variables.$trailing-icon-padding-right +
variables.$label-offset;
.mdc-floating-label {
_truncate-floating-label-max-width($truncation, $query: $query);
}
.mdc-floating-label--float-above {
_truncate-floating-label-floated-max-width(
$truncation,
$query: $query
);
}
}
_with-leading-and-trailing-icon($query: feature-targeting.all()) {
$leading-icon: icon-variables.$leading-icon-padding-left +
icon-variables.$icon-size + icon-variables.$leading-icon-padding-right;
$trailing-icon: icon-variables.$trailing-icon-padding-left +
icon-variables.$icon-size + icon-variables.$trailing-icon-padding-right;
$truncation: $leading-icon + $trailing-icon;
.mdc-floating-label {
_truncate-floating-label-max-width($truncation, $query: $query);
}
.mdc-floating-label--float-above {
_truncate-floating-label-floated-max-width(
$truncation,
$query: $query
);
}
}
outlined-with-leading-icon_($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
// Resting label position
$icon-padding: icon-variables.$leading-icon-padding-left +
icon-variables.$icon-size + icon-variables.$leading-icon-padding-right;
$left-spacing: $icon-padding - notched-outline-variables.$leading-width;
.mdc-floating-label {
feature-targeting.targets($feat-structure) {
rtl.reflexive-position(left, $left-spacing);
}
}
// Notch width
$notch-truncation: $icon-padding + notched-outline-variables.$leading-width;
_truncate-notched-outline-max-width(
$notch-truncation,
$query: $query
);
// Floating label position and animation
_outlined-with-leading-icon-floating-label-position-animation(
$height: variables.$height,
$keyframe-suffix: text-field-outlined-leading-icon,
$query: $query
);
}
///
/// Applied to the outlined text field with a trailing icon
///
_outlined-with-trailing-icon($query: feature-targeting.all()) {
// Resting label position
$icon-padding: icon-variables.$trailing-icon-padding-left +
icon-variables.$icon-size + icon-variables.$trailing-icon-padding-right;
// Notch width
$notch-truncation: $icon-padding + notched-outline-variables.$leading-width;
_truncate-notched-outline-max-width(
$notch-truncation,
$query: $query
);
}
///
/// Truncates the max-width of the notched outline by the given amount
///
/// @param {Number} $truncation - Amount to truncate the notched outline max-width
///
_truncate-notched-outline-max-width(
$truncation,
$query: feature-targeting.all()
) {
notched-outline-mixins.notch-max-width(
calc(100% - #{$truncation}),
$query: $query
);
}
///
/// Truncates the max-width of the floating label by the given amount
///
/// @param {Number} $truncation - Amount to truncate the floating label max-width
///
_truncate-floating-label-max-width(
$truncation,
$query: feature-targeting.all()
) {
floating-label-mixins.max-width(
calc(100% - #{$truncation}),
$query: $query
);
}
///
/// Truncates the max-width of the floating label by the given amount while scaling by the given scale value
///
/// @param {Number} $truncation - Amount to truncate the floating label max-width
///
_truncate-floating-label-floated-max-width(
$truncation,
$query: feature-targeting.all()
) {
$scale: floating-label-variables.$float-scale;
floating-label-mixins.max-width(
calc(100% / #{$scale} - #{$truncation} / #{$scale}),
$query: $query
);
}
// Textarea
textarea_($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
$feat-animation: feature-targeting.create-target($query, animation);
_textarea-floating-label($query);
feature-targeting.targets($feat-structure) {
flex-direction: column;
align-items: center;
width: auto;
height: auto;
padding: 0; // see below for explanation
}
feature-targeting.targets($feat-animation) {
transition: none;
}
}
_textarea-resizer($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
align-self: stretch;
display: inline-flex;
flex-direction: column;
flex-grow: 1;
max-height: 100%;
max-width: 100%;
min-height: variables.$height;
// stylelint-disable declaration-block-no-duplicate-properties --
// TODO: document why this disable is neccessary
// 'stretch' is the preferred rule here. It will allow the textarea to grow
// to the min/max width of the container, but if an explicit width is set,
// it cannot be resized horizontally.
// Stretch is still a working draft. Chrome and Firefox have it implemented
// with 'available' prefixes. fit-content is another good target for
// Safari since it works in almost all use cases except when an explicit
// width is set (the user can make the textarea smaller than the container).
// None of this matters for IE11, which doesn't support resize.
min-width: fit-content;
/* @alternate */
min-width: -moz-available;
/* @alternate */
min-width: -webkit-fill-available;
// stylelint-enable declaration-block-no-duplicate-properties
overflow: hidden;
resize: both;
}
}
_textarea-filled-resizer($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
// Shift the resizer element up by a margin amount to make space for the
// resize handle. For filled elements, the resize handle directly touches
// the bottom line and is hard to see.
// Using a margin affects the width and positioning of the overall component
// and underlying textarea, which is why a transform is used instead.
$y: -1 * variables.$textarea-input-handle-margin;
feature-targeting.targets($feat-structure) {
transform: translateY($y);
}
}
_textarea-filled-resizer-children($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
// See above. After shifting the resize wrapper element, all of its children
// should be shifted in the opposite direction (down) to compensate.
$y: variables.$textarea-input-handle-margin;
feature-targeting.targets($feat-structure) {
transform: translateY($y);
}
}
_textarea-outlined-resizer($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
// Shift the resizer element left/up by a margin amount to make space for the
// resize handle. For outlined elements, the resize handle directly touches
// the outline and is hard to see.
// Using a margin affects the width and positioning of the overall component
// and underlying textarea, which is why a transform is used instead.
$x: -1 * variables.$textarea-input-handle-margin;
$y: -1 * variables.$textarea-input-handle-margin;
feature-targeting.targets($feat-structure) {
rtl.ignore-next-line();
transform: translateX($x) translateY($y);
rtl.rtl {
// Flip the horizontal shifting direction for RTL
rtl.ignore-next-line();
transform: translateX(-1 * $x) translateY($y);
}
}
}
_textarea-outlined-resizer-children($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
// See above. After shifting the resize wrapper element, all of its children
// should be shifted in the opposite direction (right and down) to compensate.
$x: variables.$textarea-input-handle-margin;
$y: variables.$textarea-input-handle-margin;
feature-targeting.targets($feat-structure) {
rtl.ignore-next-line();
transform: translateX($x) translateY($y);
rtl.rtl {
// Flip the horizontal shifting direction for RTL
rtl.ignore-next-line();
transform: translateX(-1 * $x) translateY($y);
}
}
}
_textarea-floating-label($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
// Resting label position
.mdc-floating-label {
feature-targeting.targets($feat-structure) {
top: variables.$textarea-label-top;
}
// Resets center aligning the floating label.
&:not(.mdc-floating-label--float-above) {
feature-targeting.targets($feat-structure) {
transform: none;
}
}
}
}
_textarea-input($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
$feat-typography: feature-targeting.create-target($query, typography);
feature-targeting.targets($feat-structure) {
flex-grow: 1;
height: auto;
min-height: variables.$textarea-line-height;
overflow-x: hidden; // https://bugzilla.mozilla.org/show_bug.cgi?id=33654
overflow-y: auto;
box-sizing: border-box;
resize: none;
// Textarea has horizontal padding instead of the container. This allows the
// resize handle to extend to the edge of the container.
padding: 0 variables.$padding-horizontal;
}
feature-targeting.targets($feat-typography) {
line-height: variables.$textarea-line-height;
}
}
_textarea-internal-counter($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
typography.baseline-bottom(
variables.$textarea-internal-counter-baseline-bottom,
$query: $query
);
feature-targeting.targets($feat-structure) {
align-self: flex-end;
// Needed since padding is on the textarea and not the container
padding: 0 variables.$padding-horizontal;
&::before {
// Remove baseline-top
display: none;
}
}
}
_textarea-input-with-internal-counter($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
margin-bottom: variables.$textarea-internal-counter-input-margin-bottom;
}
}
_textarea-filled($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
&::before {
// <textarea> does not align to baseline when it does not have a value,
// unlike <input>, so we have to use padding to fake it instead
display: none;
}
}
// Floating label position
floating-label-mixins.float-position(
variables.$textarea-filled-label-position-y,
$query: $query
);
// Floating label animation
floating-label-mixins.shake-animation(
textarea-filled,
$query: $query
);
-root {
floating-label-mixins.shake-keyframes(
textarea-filled,
variables.$textarea-filled-label-position-y,
0%,
$query: $query
);
}
}
_textarea-filled-input($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
margin-top: variables.$textarea-filled-input-margin-top;
margin-bottom: variables.$textarea-filled-input-margin-bottom;
}
}
_textarea-filled-no-label-input($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
margin-top: variables.$textarea-filled-no-label-input-margin-top;
margin-bottom: variables.$textarea-filled-no-label-input-margin-bottom;
}
}
_textarea-outlined($query: feature-targeting.all()) {
notched-outline-mixins.notch-offset(0, $query: $query);
// Floating label position
notched-outline-mixins.floating-label-float-position-absolute(
variables.$textarea-outlined-label-position-y,
$query: $query
);
// Floating label animation
floating-label-mixins.shake-animation(
textarea-outlined,
$query: $query
);
-root {
floating-label-mixins.shake-keyframes(
textarea-outlined,
variables.$textarea-outlined-label-position-y,
0%,
$query: $query
);
}
}
_textarea-outlined-floating-label($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
top: variables.$textarea-outlined-label-top;
}
}
_textarea-outlined-input($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
margin-top: variables.$textarea-outlined-input-margin-top;
margin-bottom: variables.$textarea-outlined-input-margin-bottom;
}
}
// Text, Prefix and Suffix
// Common styles for the text of the text field, including the prefix, suffix,
// and input.
_text($query: feature-targeting.all()) {
$feat-animation: feature-targeting.create-target($query, animation);
$feat-structure: feature-targeting.create-target($query, structure);
// Exclude setting line-height to keep caret (text cursor) same height as the input text in iOS browser.
typography.typography(
subtitle1,
$exclude-props: (line-height),
$query: $query
);
feature-targeting.targets($feat-structure) {
height: variables.$input-height;
}
feature-targeting.targets($feat-animation) {
transition: animation.standard(opacity, 150ms);
}
}
_input($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
_text($query: $query);
feature-targeting.targets($feat-structure) {
width: 100%;
min-width: 0; // Fixes flex issues on Firefox
border: none;
border-radius: 0;
background: none;
appearance: none;
padding: 0;
// Remove built-in trailing clear icon on IE11. IE vendor prefixes cannot
// be combined with other vendor prefixes like the webkit one below.
&::-ms-clear {
display: none;
}
// Remove built-in datepicker icon on Chrome
&::-webkit-calendar-picker-indicator {
display: none;
}
&:focus {
outline: none;
}
// Remove red outline on firefox
&:invalid {
box-shadow: none;
}
}
}
_input-placeholder($query: feature-targeting.all()) {
$feat-animation: feature-targeting.create-target($query, animation);
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-animation) {
transition: animation.standard(opacity, 67ms);
}
feature-targeting.targets($feat-structure) {
opacity: 0;
}
}
_input-placeholder-visible($query: feature-targeting.all()) {
$feat-animation: feature-targeting.create-target($query, animation);
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-animation) {
transition-delay: 40ms;
transition-duration: 110ms;
}
feature-targeting.targets($feat-structure) {
opacity: 1;
}
}
_affix($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
_text($query: $query);
feature-targeting.targets($feat-structure) {
opacity: 0;
white-space: nowrap;
}
}
// TODO(b/155467610): Remove when Safari supports baseline alignment
// https://github.com/material-components/material-components-web/issues/5879
_centered-affix-safari-support($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
align-items: center;
align-self: center;
display: inline-flex;
height: 100%;
}
}
_affix-visible($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
opacity: 1;
}
}
_prefix($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
rtl.reflexive-box(padding, right, variables.$prefix-padding);
}
}
_prefix-end-aligned($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
rtl.reflexive-box(
padding,
right,
variables.$prefix-end-aligned-padding
);
}
}
_suffix($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
rtl.reflexive-box(padding, left, variables.$suffix-padding);
}
}
_suffix-end-aligned($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
rtl.reflexive-box(
padding,
left,
variables.$suffix-end-aligned-padding
);
}
}
// End aligned
end-aligned_($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
.mdc-text-field__input {
feature-targeting.targets($feat-structure) {
// IE11 does not support text-align: end
rtl.ignore-next-line();
text-align: right;
}
rtl.rtl {
feature-targeting.targets($feat-structure) {
rtl.ignore-next-line();
text-align: left;
}
}
}
}
// Forces input, prefix, and suffix to be LTR when in an RTL environment. Other
// elements such as labels and icons will remain RTL.
_ltr-text($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
rtl.rtl {
.mdc-text-field__input,
.mdc-text-field__affix {
rtl.ignore-next-line();
direction: ltr;
}
.mdc-text-field__affix--prefix {
rtl.ignore-next-line();
padding-left: 0;
rtl.ignore-next-line();
padding-right: variables.$prefix-padding;
}
.mdc-text-field__affix--suffix {
rtl.ignore-next-line();
padding-left: variables.$suffix-padding;
rtl.ignore-next-line();
padding-right: 0;
}
// Need to specify an order for all elements since icons maintain their
// original positions. We can't just reverse the container.
.mdc-text-field__icon--leading {
order: 1;
}
.mdc-text-field__affix--suffix {
order: 2;
}
.mdc-text-field__input {
order: 3;
}
.mdc-text-field__affix--prefix {
order: 4;
}
.mdc-text-field__icon--trailing {
order: 5;
}
}
}
}
// Forces input, prefix, and suffix that are already forced to LTR to also be
// end-aligned. This mixin should be used alongside the styles provided in
// _ltr-text().
_ltr-text-end-aligned($query: feature-targeting.all()) {
$feat-structure: feature-targeting.create-target($query, structure);
feature-targeting.targets($feat-structure) {
rtl.rtl {
.mdc-text-field__input {
// IE11 does not support text-align: end, so we need to duplicate
// the LTR end-aligned style here.
rtl.ignore-next-line();
text-align: right;
}
.mdc-text-field__affix--prefix {
// padding-left: 0 provided by _ltr-text mixin
rtl.ignore-next-line();
padding-right: variables.$prefix-end-aligned-padding;
}
.mdc-text-field__affix--suffix {
rtl.ignore-next-line();
padding-left: variables.$suffix-end-aligned-padding;
// padding-right: 0 provided by _ltr-text mixin
}
}
}
}
// Customization
ink-color_($color, $query: feature-targeting.all()) {
$feat-color: feature-targeting.create-target($query, color);
.mdc-text-field__input {
feature-targeting.targets($feat-color) {
theme.prop(color, $color);
}
}
}
placeholder-color_($color, $query: feature-targeting.all()) {
$feat-color: feature-targeting.create-target($query, color);
feature-targeting.targets($feat-color) {
.mdc-text-field__input {
placeholder-selector_ {
theme.prop(color, $color);
}
}
}
}
fill-color_(
$color,
$query: feature-targeting.all(),
$addAlternate: false
) {
$feat-color: feature-targeting.create-target($query, color);
feature-targeting.targets($feat-color) {
($addAlternate) {
/* @alternate */
}
theme.prop(background-color, $color);
}
}
bottom-line-color_($color, $query: feature-targeting.all()) {
.mdc-line-ripple {
line-ripple-mixins.inactive-color($color, $query: $query);
}
}
hover-bottom-line-color_($color, $query: feature-targeting.all()) {
$feat-color: feature-targeting.create-target($query, color);
&:hover .mdc-line-ripple {
line-ripple-mixins.inactive-color($color, $query: $query);
}
}
line-ripple-color_($color, $query: feature-targeting.all()) {
.mdc-line-ripple {
line-ripple-mixins.active-color($color, $query: $query);
}
}
hover-outline-color_($color, $query: feature-targeting.all()) {
&:not(.mdc-text-field--focused):hover {
.mdc-notched-outline {
notched-outline-mixins.color($color, $query: $query);
}
}
}
focused-outline-color_($color, $query: feature-targeting.all()) {
&.mdc-text-field--focused {
notched-outline-mixins.color($color, $query: $query);
}
}
label-ink-color_($color, $query: feature-targeting.all()) {
.mdc-floating-label {
floating-label-mixins.ink-color($color, $query: $query);
}
}
_prefix-color($color, $query: feature-targeting.all()) {
$feat-color: feature-targeting.create-target($query, color);
feature-targeting.targets($feat-color) {
.mdc-text-field__affix--prefix {
theme.prop(color, $color);
}
}
}
_suffix-color($color, $query: feature-targeting.all()) {
$feat-color: feature-targeting.create-target($query, color);
feature-targeting.targets($feat-color) {
.mdc-text-field__affix--suffix {
theme.prop(color, $color);
}
}
}
// Selectors
placeholder-selector_ {
// GSS will combine selectors with the same content, and some browsers have a
// CSS quirk where it drops a rule if it does not recognize one of the
// selectors.
// To avoid GSS combining the ::placeholder and :-ms-input-placeholder
// selectors, we wrap them in `@media all`.
// TODO(b/142329051)
all {
// ::placeholder needs to be wrapped because IE11 will drop other selectors
// with the same content
&::placeholder {
;
}
}
all {
// :-ms-input-placeholder needs to be wrapped because Firefox will drop
// other selectors with the same content
&:-ms-input-placeholder {
;
}
}
}
// State qualifiers
///
/// Helps style the text-field only when it's enabled.
/// @access private
///
if-enabled_ {
&:not(.mdc-text-field--disabled) {
;
}
}
///
/// Helps style the text-field only when it's disabled.
/// @access private
///
if-disabled_ {
&.mdc-text-field--disabled {
;
}
}