Skip to content

refactor(material/form-field): tokenize density overrides #28249

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion src/material/core/tokens/m2/mat/_form-field.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
@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';

Expand Down Expand Up @@ -86,7 +90,52 @@ $prefix: (mat, form-field);

// Tokens that can be configured through Angular Material's density theming API.
@function get-density-tokens($theme) {
@return ();
$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),
);
}

// Combines the tokens generated by the above functions into a single map with placeholder values.
Expand Down
3 changes: 1 addition & 2 deletions src/material/form-field/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,11 @@ 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",
Expand Down
118 changes: 0 additions & 118 deletions src/material/form-field/_form-field-density.scss

This file was deleted.

40 changes: 0 additions & 40 deletions src/material/form-field/_form-field-sizing.scss

This file was deleted.

3 changes: 1 addition & 2 deletions src/material/form-field/_form-field-subscript.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

@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.
Expand Down Expand Up @@ -50,7 +49,7 @@

// Spacer used to make sure start and end hints have enough space between them.
.mat-mdc-form-field-hint-spacer {
flex: 1 0 form-field-sizing.$mat-form-field-hint-min-space;
flex: 1 0 1em;
}

// Single error message displayed beneath the form field underline.
Expand Down
6 changes: 4 additions & 2 deletions src/material/form-field/_form-field-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
@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 {
Expand Down Expand Up @@ -85,7 +84,10 @@
@include _theme-from-tokens(inspection.get-theme-tokens($theme, density));
}
@else {
@include form-field-density.private-form-field-density($theme);
@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));
}
}
}

Expand Down
60 changes: 60 additions & 0 deletions src/material/form-field/_mdc-text-field-density-overrides.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
@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);
}
}
}
18 changes: 14 additions & 4 deletions src/material/form-field/form-field.scss
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
@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';
@use './form-field-native-select';
@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) {
Expand Down Expand Up @@ -57,6 +57,7 @@
// 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();
Expand All @@ -65,6 +66,10 @@
@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 {
Expand Down Expand Up @@ -144,11 +149,11 @@
// 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 form-field-sizing.$mat-form-field-icon-prefix-infix-padding 0 0;
padding: 0 $_icon-prefix-infix-padding 0 0;
}
.mat-mdc-form-field-icon-suffix,
[dir='rtl'] .mat-mdc-form-field-icon-prefix {
padding: 0 0 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding;
padding: 0 0 0 $_icon-prefix-infix-padding;
}

.mat-mdc-form-field-icon-prefix,
Expand Down Expand Up @@ -178,7 +183,12 @@
.mat-mdc-form-field-infix {
flex: auto;
min-width: 0;
width: form-field-sizing.$mat-form-field-default-infix-width;

// 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;

// Needed so that the floating label does not overlap with prefixes or suffixes.
position: relative;
box-sizing: border-box;
Expand Down
Loading