From 4cc46733560acd79bad7f471716f81fbbb2a49f6 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 12 Dec 2023 20:13:41 +0100 Subject: [PATCH] Revert "refactor(material/form-field): tokenize density overrides (#28249)" (#28269) This reverts commit 087cf15c571d1d9c299e44f0a2f12aa9cdbbd9fd. (cherry picked from commit db17910f04d9b5ef3ece0561dfbc4d723bafc77b) --- .../core/tokens/m2/mat/_form-field.scss | 51 +------- src/material/form-field/BUILD.bazel | 3 +- .../form-field/_form-field-density.scss | 118 ++++++++++++++++++ .../form-field/_form-field-sizing.scss | 40 ++++++ .../form-field/_form-field-subscript.scss | 3 +- .../form-field/_form-field-theme.scss | 6 +- .../_mdc-text-field-density-overrides.scss | 60 --------- src/material/form-field/form-field.scss | 18 +-- src/material/paginator/BUILD.bazel | 1 + src/material/paginator/_paginator-theme.scss | 9 +- 10 files changed, 174 insertions(+), 135 deletions(-) create mode 100644 src/material/form-field/_form-field-density.scss create mode 100644 src/material/form-field/_form-field-sizing.scss delete mode 100644 src/material/form-field/_mdc-text-field-density-overrides.scss diff --git a/src/material/core/tokens/m2/mat/_form-field.scss b/src/material/core/tokens/m2/mat/_form-field.scss index c49fbb9826ee..592c828fee99 100644 --- a/src/material/core/tokens/m2/mat/_form-field.scss +++ b/src/material/core/tokens/m2/mat/_form-field.scss @@ -1,10 +1,6 @@ -@use 'sass:math'; @use 'sass:map'; -@use '@material/textfield' as mdc-textfield; -@use '@material/density' as mdc-density; @use '../../token-utils'; @use '../../../style/sass-utils'; -@use '../../../theming/theming'; @use '../../../theming/inspection'; @use '../../../theming/palette'; @@ -90,52 +86,7 @@ $prefix: (mat, form-field); // Tokens that can be configured through Angular Material's density theming API. @function get-density-tokens($theme) { - $density-scale: theming.clamp-density(inspection.get-theme-density($theme), -4); - $height: mdc-density.prop-value( - $density-config: mdc-textfield.$density-config, - $density-scale: inspection.get-theme-density($theme), - $property-name: height, - ); - $hide-label: $height < mdc-textfield.$minimum-height-for-filled-label; - - // We computed the desired height of the form-field using the density configuration. The - // spec only describes vertical spacing/alignment in non-dense mode. This means that we - // cannot update the spacing to explicit numbers based on the density scale. Instead, we - // determine the height reduction and equally subtract it from the default `top` and `bottom` - // padding that is provided by the Material Design specification. - $vertical-deduction: math.div(mdc-textfield.$height - $height, 2); - - // Note: these calculations are trivial enough that we could do them at runtime with `calc` - // and the value of the `height` token. The problem is that because we need to hide the label - // if the container becomes too short, we have to change the padding calculation. This is - // complicated further by the fact that filled form fields without labels have the same - // vertical padding as outlined ones. Alternatives: - // 1. Using container queries to hide the label and change the padding - this doesn't work - // because size container queries require setting the `container-type` property which breaks - // the form field layout. We could use style queries, but they're only supported in Chrome. - // 2. Monitoring the size of the label - we already have a `ResizeObserver` on the label so we - // could reuse it to also check when it becomes `display: none`. This would allows us to remove - // the three padding tokens. We don't do it, because it would require us to always set up - // the resize observer, as opposed to currently where it's only set up for outlined form fields. - // This may lead to performance regressions. - // 3. Conditionally adding `::before` and `::after` to the infix with positive and negative - // margin respectively - this works, but is likely to break a lot of overrides that are targeting - // a specific padding. It also runs the risk of overflowing the container. - // TODO: switch the padding tokens to style-based container queries - // when they become available in all the browsers we support. - $filled-with-label-padding-top: 24px - $vertical-deduction; - $filled-with-label-padding-bottom: 8px - $vertical-deduction; - $vertical-padding: 16px - $vertical-deduction; - - @return ( - container-height: $height, - filled-label-display: if($hide-label, none, block), - container-vertical-padding: $vertical-padding, - filled-with-label-container-padding-top: - if($hide-label, $vertical-padding, $filled-with-label-padding-top), - filled-with-label-container-padding-bottom: - if($hide-label, $vertical-padding, $filled-with-label-padding-bottom), - ); + @return (); } // Combines the tokens generated by the above functions into a single map with placeholder values. diff --git a/src/material/form-field/BUILD.bazel b/src/material/form-field/BUILD.bazel index 2f5dd654738e..9208f937992e 100644 --- a/src/material/form-field/BUILD.bazel +++ b/src/material/form-field/BUILD.bazel @@ -52,11 +52,12 @@ sass_binary( sass_library( name = "form_field_partials", srcs = [ + "_form-field-density.scss", "_form-field-focus-overlay.scss", "_form-field-high-contrast.scss", "_form-field-native-select.scss", + "_form-field-sizing.scss", "_form-field-subscript.scss", - "_mdc-text-field-density-overrides.scss", "_mdc-text-field-structure-overrides.scss", "_mdc-text-field-textarea-overrides.scss", "_user-agent-overrides.scss", diff --git a/src/material/form-field/_form-field-density.scss b/src/material/form-field/_form-field-density.scss new file mode 100644 index 000000000000..e679668c27f0 --- /dev/null +++ b/src/material/form-field/_form-field-density.scss @@ -0,0 +1,118 @@ +@use 'sass:map'; +@use 'sass:math'; +@use '@material/density' as mdc-density; +@use '@material/textfield' as mdc-textfield; +@use '../core/theming/inspection'; + +@use './form-field-sizing'; + +// Mixin that sets the vertical spacing for the infix container of filled form fields. +// We need to apply spacing to the infix container because we removed the input padding +// provided by MDC in order to support arbitrary form-field controls. +@mixin _infix-vertical-spacing-filled($with-label-padding, $no-label-padding) { + .mat-mdc-text-field-wrapper:not(.mdc-text-field--outlined) .mat-mdc-form-field-infix { + padding-top: map.get($with-label-padding, top); + padding-bottom: map.get($with-label-padding, bottom); + } + + .mdc-text-field--no-label:not(.mdc-text-field--outlined):not(.mdc-text-field--textarea) + .mat-mdc-form-field-infix { + padding-top: map.get($no-label-padding, top); + padding-bottom: map.get($no-label-padding, bottom); + } +} + +// Mixin that sets the vertical spacing for the infix container of outlined form fields. +// We need to apply spacing to the infix container because we removed the input padding +// provided by MDC in order to support arbitrary form-field controls. +@mixin _infix-vertical-spacing-outlined($padding) { + .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mat-mdc-form-field-infix { + padding-top: map.get($padding, top); + padding-bottom: map.get($padding, bottom); + } +} + +// Mixin that includes the density styles for form fields. MDC provides their own density +// styles for MDC text-field which we cannot use. MDC relies on input elements to stretch +// vertically when the height is reduced as per density scale. This doesn't work for our +// form field since we support custom form field controls without a fixed height. Instead, we +// provide spacing that makes arbitrary controls align as specified in the Material Design +// specification. In order to support density, we need to adjust the vertical spacing to be +// based on the density scale. +@mixin private-form-field-density($theme) { + // Height of the form field that is based on the current density scale. + $height: mdc-density.prop-value( + $density-config: mdc-textfield.$density-config, + $density-scale: inspection.get-theme-density($theme), + $property-name: height, + ); + + // Whether floating labels for filled form fields should be hidden. MDC hides the label in + // their density styles when the height decreases too much. We match their density styles. + $hide-filled-floating-label: $height < mdc-textfield.$minimum-height-for-filled-label; + // We computed the desired height of the form-field using the density configuration. The + // spec only describes vertical spacing/alignment in non-dense mode. This means that we + // cannot update the spacing to explicit numbers based on the density scale. Instead, we + // determine the height reduction and equally subtract it from the default `top` and `bottom` + // padding that is provided by the Material Design specification. + $vertical-deduction: math.div(mdc-textfield.$height - $height, 2); + // Map that describes the padding for form-fields with label. + $with-label-padding: ( + top: form-field-sizing.$mat-form-field-with-label-input-padding-top - $vertical-deduction, + bottom: form-field-sizing.$mat-form-field-with-label-input-padding-bottom - $vertical-deduction, + ); + // Map that describes the padding for form-fields without label. + $no-label-padding: ( + top: form-field-sizing.$mat-form-field-no-label-padding-top - $vertical-deduction, + bottom: form-field-sizing.$mat-form-field-no-label-padding-bottom - $vertical-deduction, + ); + + // We add a minimum height to the infix container in order to ensure that custom controls have + // the same default vertical space as text-field inputs (with respect to the vertical padding). + .mat-mdc-form-field-infix { + min-height: $height; + } + + // By default, MDC aligns the label using percentage. This will be overwritten based + // on whether a textarea is used. This is not possible in our implementation of the + // form-field because we do not know what type of form-field control is set up. Hence + // we always use a fixed position for the label. This does not have any implications. + .mat-mdc-text-field-wrapper .mat-mdc-form-field-flex .mat-mdc-floating-label { + top: math.div($height, 2); + } + + // For the outline appearance, we re-create the active floating label transform. This is + // necessary because the transform for docked floating labels can be updated to account for + // the width of prefix container. + .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mdc-notched-outline--upgraded + .mdc-floating-label--float-above { + --mat-mdc-form-field-label-transform: translateY( + -#{mdc-textfield.get-outlined-label-position-y($height)}) + scale(var(--mat-mdc-form-field-floating-label-scale, 0.75)); + transform: var(--mat-mdc-form-field-label-transform); + } + + // Add vertical spacing to the infix to ensure that outlined form fields have their controls + // aligned as if there is no label. This is done similarly in MDC and is specified in the + // Material Design specification. Outline form fields position the control as if there is no + // label. This is because the label overflows the form-field and doesn't need space at the top. + @include _infix-vertical-spacing-outlined($no-label-padding); + + // MDC hides labels for filled form fields when the form field height decreases. We match + // this behavior in our custom density styles. + @if $hide-filled-floating-label { + // Update the spacing for filled form fields to account for the hidden floating label. + @include _infix-vertical-spacing-filled( + $no-label-padding, $no-label-padding); + .mat-mdc-text-field-wrapper:not(.mdc-text-field--outlined) .mat-mdc-floating-label { + display: none; + } + } + @else { + // By default, filled form fields align their controls differently based on whether there + // is a label or not. MDC does this too, but we cannot rely on their styles as we support + // arbitrary form field controls and MDC only applies their spacing to the `` elements. + @include _infix-vertical-spacing-filled( + $with-label-padding, $no-label-padding); + } +} diff --git a/src/material/form-field/_form-field-sizing.scss b/src/material/form-field/_form-field-sizing.scss new file mode 100644 index 000000000000..70b78c8d17a5 --- /dev/null +++ b/src/material/form-field/_form-field-sizing.scss @@ -0,0 +1,40 @@ +// Top spacing of the form-field outline. MDC does not have a variable for this +// and just hard-codes it into their styles. +$mat-form-field-outline-top-spacing: 12px; + +// Infix stretches to fit the container, but naturally wants to be this wide. We set +// this in order to have a consistent natural size for the various types of controls +// that can go in a form field. +$mat-form-field-default-infix-width: 180px !default; + +// Minimum amount of space between start and end hints in the subscript. MDC does not +// have built-in support for hints. +$mat-form-field-hint-min-space: 1em !default; + +// Vertical spacing of the text-field if there is no label. MDC hard-codes the spacing +// into their styles, but their spacing variables would not work for our form-field +// structure anyway. This is because MDC's input elements are larger than the text, and +// their padding variables are calculated with respect to the vertical empty space of the +// inputs. We take the explicit numbers provided by the Material Design specification. +// https://material.io/components/text-fields/#specs + +// Vertical spacing of the text-field if there is a label. MDC hard-codes the spacing into +// their styles, but their spacing variables would not work for our form-field structure anyway. +// This is because MDC's alignment depends on the input element to expand to full infix height. +// We allow for arbitrary form controls and support dynamic height, so we manage the control +// infix alignment through padding on the infix that works for any control. We manually measure +// spacing as provided by the Material Design specification. The outlined dimensions in the +// spec section do not match with the text fields shown in the overview or the ones implemented +// by MDC. Note that we need to account for the input box offset. See above for more context. +$mat-form-field-with-label-input-padding-top: 24px; +$mat-form-field-with-label-input-padding-bottom: 8px; + +// Vertical spacing of the text-field if there is no label. We manually measure the +// spacing in the specs. See comment above for padding for text fields with label. The +// same reasoning applies to the padding for text fields without label. +$mat-form-field-no-label-padding-bottom: 16px; +$mat-form-field-no-label-padding-top: 16px; + +// The amount of padding between the icon prefix/suffix and the infix. +// This assumes that the icon will be a 24px square with 12px padding. +$mat-form-field-icon-prefix-infix-padding: 4px; diff --git a/src/material/form-field/_form-field-subscript.scss b/src/material/form-field/_form-field-subscript.scss index d65aa058d89e..ff0795c929ed 100644 --- a/src/material/form-field/_form-field-subscript.scss +++ b/src/material/form-field/_form-field-subscript.scss @@ -3,6 +3,7 @@ @use '../core/tokens/m2/mat/form-field' as tokens-mat-form-field; @use '../core/tokens/token-utils'; +@use './form-field-sizing'; @mixin private-form-field-subscript() { // Wrapper for the hints and error messages. @@ -49,7 +50,7 @@ // Spacer used to make sure start and end hints have enough space between them. .mat-mdc-form-field-hint-spacer { - flex: 1 0 1em; + flex: 1 0 form-field-sizing.$mat-form-field-hint-min-space; } // Single error message displayed beneath the form field underline. diff --git a/src/material/form-field/_form-field-theme.scss b/src/material/form-field/_form-field-theme.scss index 13052c26e177..026d3f2f6b67 100644 --- a/src/material/form-field/_form-field-theme.scss +++ b/src/material/form-field/_form-field-theme.scss @@ -9,6 +9,7 @@ @use '../core/typography/typography'; @use '../core/style/sass-utils'; @use '../core/tokens/token-utils'; +@use './form-field-density'; @mixin base($theme) { @if inspection.get-theme-version($theme) == 1 { @@ -84,10 +85,7 @@ @include _theme-from-tokens(inspection.get-theme-tokens($theme, density)); } @else { - @include sass-utils.current-selector-or-root() { - @include token-utils.create-token-values(tokens-mat-form-field.$prefix, - tokens-mat-form-field.get-density-tokens($theme)); - } + @include form-field-density.private-form-field-density($theme); } } diff --git a/src/material/form-field/_mdc-text-field-density-overrides.scss b/src/material/form-field/_mdc-text-field-density-overrides.scss deleted file mode 100644 index 41ca017072d8..000000000000 --- a/src/material/form-field/_mdc-text-field-density-overrides.scss +++ /dev/null @@ -1,60 +0,0 @@ -@use '@material/textfield' as mdc-textfield; -@use '../core/tokens/m2/mat/form-field' as tokens-mat-form-field; -@use '../core/tokens/token-utils'; - -// Mixin that includes the density styles for form fields. MDC provides their own density -// styles for MDC text-field which we cannot use. MDC relies on input elements to stretch -// vertically when the height is reduced as per density scale. This doesn't work for our -// form field since we support custom form field controls without a fixed height. Instead, we -// provide spacing that makes arbitrary controls align as specified in the Material Design -// specification. In order to support density, we need to adjust the vertical spacing to be -// based on the density scale. -@mixin private-text-field-density-overrides() { - @include token-utils.use-tokens( - tokens-mat-form-field.$prefix, tokens-mat-form-field.get-token-slots()) { - $height: token-utils.get-token-variable(container-height); - - .mat-mdc-form-field-infix { - // We add a minimum height to the infix container to ensure that custom controls have the - // same default vertical space as text-field inputs (with respect to the vertical padding). - min-height: var(#{$height}); - - @include token-utils.create-token-slot(padding-top, - filled-with-label-container-padding-top); - @include token-utils.create-token-slot(padding-bottom, - filled-with-label-container-padding-bottom); - - .mdc-text-field--outlined &, - .mdc-text-field--no-label & { - @include token-utils.create-token-slot(padding-top, container-vertical-padding); - @include token-utils.create-token-slot(padding-bottom, container-vertical-padding); - } - } - - // By default, MDC aligns the label using percentage. This will be overwritten based - // on whether a textarea is used. This is not possible in our implementation of the - // form-field because we do not know what type of form-field control is set up. Hence - // we always use a fixed position for the label. This does not have any implications. - .mat-mdc-text-field-wrapper .mat-mdc-form-field-flex .mat-mdc-floating-label { - top: calc(var(#{$height}) / 2); - } - - // We need to conditionally hide the floating label based on the height of the form field. - .mdc-text-field--filled .mat-mdc-floating-label { - display: var(#{token-utils.get-token-variable(filled-label-display)}, block); - } - - // For the outline appearance, we re-create the active floating label transform. This is - // necessary because the transform for docked floating labels can be updated to account for - // the width of prefix container. - .mat-mdc-text-field-wrapper.mdc-text-field--outlined .mdc-notched-outline--upgraded - .mdc-floating-label--float-above { - // Needs to be in a string form to work around an internal check that incorrectly flags this - // interpolation in `calc` as unnecessary. If we don't have it, Sass won't evaluate it. - $translate: 'calc(#{mdc-textfield.get-outlined-label-position-y(var(#{$height}))} * -1)'; - --mat-mdc-form-field-label-transform: translateY(#{$translate}) - scale(var(--mat-mdc-form-field-floating-label-scale, 0.75)); - transform: var(--mat-mdc-form-field-label-transform); - } - } -} diff --git a/src/material/form-field/form-field.scss b/src/material/form-field/form-field.scss index efb89ee23fdb..d4171f351946 100644 --- a/src/material/form-field/form-field.scss +++ b/src/material/form-field/form-field.scss @@ -15,6 +15,7 @@ @use '../core/tokens/m2/mat/form-field' as tokens-mat-form-field; @use '../core/tokens/m2/mdc/filled-text-field' as tokens-mdc-filled-text-field; @use '../core/tokens/m2/mdc/outlined-text-field' as tokens-mdc-outlined-text-field; +@use './form-field-sizing'; @use './form-field-subscript'; @use './form-field-focus-overlay'; @use './form-field-high-contrast'; @@ -22,7 +23,6 @@ @use './user-agent-overrides'; @use './mdc-text-field-textarea-overrides'; @use './mdc-text-field-structure-overrides'; -@use './mdc-text-field-density-overrides'; // Includes the structural styles of the components that the form field is composed of. @mixin _static-styles($query) { @@ -57,7 +57,6 @@ // MDC text-field overwrites. @include mdc-text-field-textarea-overrides.private-text-field-textarea-overrides(); @include mdc-text-field-structure-overrides.private-text-field-structure-overrides(); -@include mdc-text-field-density-overrides.private-text-field-density-overrides(); // Include the subscript, focus-overlay, native select and high-contrast styles. @include form-field-subscript.private-form-field-subscript(); @@ -66,10 +65,6 @@ @include form-field-high-contrast.private-form-field-high-contrast(); @include user-agent-overrides.private-form-field-user-agent-overrides(); -// The amount of padding between the icon prefix/suffix and the infix. -// This assumes that the icon will be a 24px square with 12px padding. -$_icon-prefix-infix-padding: 4px; - // Host element of the form-field. It contains the mdc-text-field wrapper // and the subscript wrapper. .mat-mdc-form-field { @@ -149,11 +144,11 @@ $_icon-prefix-infix-padding: 4px; // icons, and therefore can't rely on MDC for these styles. .mat-mdc-form-field-icon-prefix, [dir='rtl'] .mat-mdc-form-field-icon-suffix { - padding: 0 $_icon-prefix-infix-padding 0 0; + padding: 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding 0 0; } .mat-mdc-form-field-icon-suffix, [dir='rtl'] .mat-mdc-form-field-icon-prefix { - padding: 0 0 0 $_icon-prefix-infix-padding; + padding: 0 0 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding; } .mat-mdc-form-field-icon-prefix, @@ -183,12 +178,7 @@ $_icon-prefix-infix-padding: 4px; .mat-mdc-form-field-infix { flex: auto; min-width: 0; - - // Infix stretches to fit the container, but naturally wants to be this wide. We set - // this in order to have a consistent natural size for the various types of controls - // that can go in a form field. - width: 180px; - + width: form-field-sizing.$mat-form-field-default-infix-width; // Needed so that the floating label does not overlap with prefixes or suffixes. position: relative; box-sizing: border-box; diff --git a/src/material/paginator/BUILD.bazel b/src/material/paginator/BUILD.bazel index fc010bb7144d..3c5ce18ad133 100644 --- a/src/material/paginator/BUILD.bazel +++ b/src/material/paginator/BUILD.bazel @@ -35,6 +35,7 @@ sass_library( deps = [ "//:mdc_sass_lib", "//src/material/core:core_scss_lib", + "//src/material/form-field:form_field_scss_lib", ], ) diff --git a/src/material/paginator/_paginator-theme.scss b/src/material/paginator/_paginator-theme.scss index 89a7e75928d6..367f95d1a926 100644 --- a/src/material/paginator/_paginator-theme.scss +++ b/src/material/paginator/_paginator-theme.scss @@ -1,12 +1,12 @@ @use 'sass:map'; @use 'sass:meta'; @use '../core/tokens/m2/mat/paginator' as tokens-mat-paginator; -@use '../core/tokens/m2/mat/form-field' as tokens-mat-form-field; @use '../core/style/sass-utils'; @use '../core/typography/typography'; @use '../core/theming/theming'; @use '../core/theming/inspection'; @use '../core/tokens/token-utils'; +@use '../form-field/form-field-density'; @mixin base($theme) { @if inspection.get-theme-version($theme) == 1 { @@ -51,17 +51,16 @@ tokens-mat-paginator.get-density-tokens($theme)); } + // TODO: this should be done through tokens once the form field has been switched over. .mat-mdc-paginator { // We need the form field to be narrower in order to fit into the paginator, // so we set its density to be -4 or denser. @if ((meta.type-of($density-scale) == 'number' and $density-scale >= -4) or $density-scale == maximum) { - @include token-utils.create-token-values(tokens-mat-form-field.$prefix, - tokens-mat-form-field.get-density-tokens((density: -4))); + @include form-field-density.private-form-field-density(-4); } @else { - @include token-utils.create-token-values(tokens-mat-form-field.$prefix, - tokens-mat-form-field.get-density-tokens((density: $density-scale))); + @include form-field-density.private-form-field-density($density-scale); } } }