Skip to content

Commit 087cf15

Browse files
authored
refactor(material/form-field): tokenize density overrides (#28249)
Switches the custom density implementation of the form field to be based on tokens.
1 parent f23d8c1 commit 087cf15

File tree

10 files changed

+135
-174
lines changed

10 files changed

+135
-174
lines changed

src/material/core/tokens/m2/mat/_form-field.scss

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
@use 'sass:math';
12
@use 'sass:map';
3+
@use '@material/textfield' as mdc-textfield;
4+
@use '@material/density' as mdc-density;
25
@use '../../token-utils';
36
@use '../../../style/sass-utils';
7+
@use '../../../theming/theming';
48
@use '../../../theming/inspection';
59
@use '../../../theming/palette';
610

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

8791
// Tokens that can be configured through Angular Material's density theming API.
8892
@function get-density-tokens($theme) {
89-
@return ();
93+
$density-scale: theming.clamp-density(inspection.get-theme-density($theme), -4);
94+
$height: mdc-density.prop-value(
95+
$density-config: mdc-textfield.$density-config,
96+
$density-scale: inspection.get-theme-density($theme),
97+
$property-name: height,
98+
);
99+
$hide-label: $height < mdc-textfield.$minimum-height-for-filled-label;
100+
101+
// We computed the desired height of the form-field using the density configuration. The
102+
// spec only describes vertical spacing/alignment in non-dense mode. This means that we
103+
// cannot update the spacing to explicit numbers based on the density scale. Instead, we
104+
// determine the height reduction and equally subtract it from the default `top` and `bottom`
105+
// padding that is provided by the Material Design specification.
106+
$vertical-deduction: math.div(mdc-textfield.$height - $height, 2);
107+
108+
// Note: these calculations are trivial enough that we could do them at runtime with `calc`
109+
// and the value of the `height` token. The problem is that because we need to hide the label
110+
// if the container becomes too short, we have to change the padding calculation. This is
111+
// complicated further by the fact that filled form fields without labels have the same
112+
// vertical padding as outlined ones. Alternatives:
113+
// 1. Using container queries to hide the label and change the padding - this doesn't work
114+
// because size container queries require setting the `container-type` property which breaks
115+
// the form field layout. We could use style queries, but they're only supported in Chrome.
116+
// 2. Monitoring the size of the label - we already have a `ResizeObserver` on the label so we
117+
// could reuse it to also check when it becomes `display: none`. This would allows us to remove
118+
// the three padding tokens. We don't do it, because it would require us to always set up
119+
// the resize observer, as opposed to currently where it's only set up for outlined form fields.
120+
// This may lead to performance regressions.
121+
// 3. Conditionally adding `::before` and `::after` to the infix with positive and negative
122+
// margin respectively - this works, but is likely to break a lot of overrides that are targeting
123+
// a specific padding. It also runs the risk of overflowing the container.
124+
// TODO: switch the padding tokens to style-based container queries
125+
// when they become available in all the browsers we support.
126+
$filled-with-label-padding-top: 24px - $vertical-deduction;
127+
$filled-with-label-padding-bottom: 8px - $vertical-deduction;
128+
$vertical-padding: 16px - $vertical-deduction;
129+
130+
@return (
131+
container-height: $height,
132+
filled-label-display: if($hide-label, none, block),
133+
container-vertical-padding: $vertical-padding,
134+
filled-with-label-container-padding-top:
135+
if($hide-label, $vertical-padding, $filled-with-label-padding-top),
136+
filled-with-label-container-padding-bottom:
137+
if($hide-label, $vertical-padding, $filled-with-label-padding-bottom),
138+
);
90139
}
91140

92141
// Combines the tokens generated by the above functions into a single map with placeholder values.

src/material/form-field/BUILD.bazel

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,11 @@ sass_binary(
5252
sass_library(
5353
name = "form_field_partials",
5454
srcs = [
55-
"_form-field-density.scss",
5655
"_form-field-focus-overlay.scss",
5756
"_form-field-high-contrast.scss",
5857
"_form-field-native-select.scss",
59-
"_form-field-sizing.scss",
6058
"_form-field-subscript.scss",
59+
"_mdc-text-field-density-overrides.scss",
6160
"_mdc-text-field-structure-overrides.scss",
6261
"_mdc-text-field-textarea-overrides.scss",
6362
"_user-agent-overrides.scss",

src/material/form-field/_form-field-density.scss

Lines changed: 0 additions & 118 deletions
This file was deleted.

src/material/form-field/_form-field-sizing.scss

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/material/form-field/_form-field-subscript.scss

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
@use '../core/tokens/m2/mat/form-field' as tokens-mat-form-field;
55
@use '../core/tokens/token-utils';
6-
@use './form-field-sizing';
76

87
@mixin private-form-field-subscript() {
98
// Wrapper for the hints and error messages.
@@ -50,7 +49,7 @@
5049

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

5655
// Single error message displayed beneath the form field underline.

src/material/form-field/_form-field-theme.scss

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
@use '../core/typography/typography';
1010
@use '../core/style/sass-utils';
1111
@use '../core/tokens/token-utils';
12-
@use './form-field-density';
1312

1413
@mixin base($theme) {
1514
@if inspection.get-theme-version($theme) == 1 {
@@ -85,7 +84,10 @@
8584
@include _theme-from-tokens(inspection.get-theme-tokens($theme, density));
8685
}
8786
@else {
88-
@include form-field-density.private-form-field-density($theme);
87+
@include sass-utils.current-selector-or-root() {
88+
@include token-utils.create-token-values(tokens-mat-form-field.$prefix,
89+
tokens-mat-form-field.get-density-tokens($theme));
90+
}
8991
}
9092
}
9193

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
@use '@material/textfield' as mdc-textfield;
2+
@use '../core/tokens/m2/mat/form-field' as tokens-mat-form-field;
3+
@use '../core/tokens/token-utils';
4+
5+
// Mixin that includes the density styles for form fields. MDC provides their own density
6+
// styles for MDC text-field which we cannot use. MDC relies on input elements to stretch
7+
// vertically when the height is reduced as per density scale. This doesn't work for our
8+
// form field since we support custom form field controls without a fixed height. Instead, we
9+
// provide spacing that makes arbitrary controls align as specified in the Material Design
10+
// specification. In order to support density, we need to adjust the vertical spacing to be
11+
// based on the density scale.
12+
@mixin private-text-field-density-overrides() {
13+
@include token-utils.use-tokens(
14+
tokens-mat-form-field.$prefix, tokens-mat-form-field.get-token-slots()) {
15+
$height: token-utils.get-token-variable(container-height);
16+
17+
.mat-mdc-form-field-infix {
18+
// We add a minimum height to the infix container to ensure that custom controls have the
19+
// same default vertical space as text-field inputs (with respect to the vertical padding).
20+
min-height: var(#{$height});
21+
22+
@include token-utils.create-token-slot(padding-top,
23+
filled-with-label-container-padding-top);
24+
@include token-utils.create-token-slot(padding-bottom,
25+
filled-with-label-container-padding-bottom);
26+
27+
.mdc-text-field--outlined &,
28+
.mdc-text-field--no-label & {
29+
@include token-utils.create-token-slot(padding-top, container-vertical-padding);
30+
@include token-utils.create-token-slot(padding-bottom, container-vertical-padding);
31+
}
32+
}
33+
34+
// By default, MDC aligns the label using percentage. This will be overwritten based
35+
// on whether a textarea is used. This is not possible in our implementation of the
36+
// form-field because we do not know what type of form-field control is set up. Hence
37+
// we always use a fixed position for the label. This does not have any implications.
38+
.mat-mdc-text-field-wrapper .mat-mdc-form-field-flex .mat-mdc-floating-label {
39+
top: calc(var(#{$height}) / 2);
40+
}
41+
42+
// We need to conditionally hide the floating label based on the height of the form field.
43+
.mdc-text-field--filled .mat-mdc-floating-label {
44+
display: var(#{token-utils.get-token-variable(filled-label-display)}, block);
45+
}
46+
47+
// For the outline appearance, we re-create the active floating label transform. This is
48+
// necessary because the transform for docked floating labels can be updated to account for
49+
// the width of prefix container.
50+
.mat-mdc-text-field-wrapper.mdc-text-field--outlined .mdc-notched-outline--upgraded
51+
.mdc-floating-label--float-above {
52+
// Needs to be in a string form to work around an internal check that incorrectly flags this
53+
// interpolation in `calc` as unnecessary. If we don't have it, Sass won't evaluate it.
54+
$translate: 'calc(#{mdc-textfield.get-outlined-label-position-y(var(#{$height}))} * -1)';
55+
--mat-mdc-form-field-label-transform: translateY(#{$translate})
56+
scale(var(--mat-mdc-form-field-floating-label-scale, 0.75));
57+
transform: var(--mat-mdc-form-field-label-transform);
58+
}
59+
}
60+
}

src/material/form-field/form-field.scss

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
@use '../core/tokens/m2/mat/form-field' as tokens-mat-form-field;
1616
@use '../core/tokens/m2/mdc/filled-text-field' as tokens-mdc-filled-text-field;
1717
@use '../core/tokens/m2/mdc/outlined-text-field' as tokens-mdc-outlined-text-field;
18-
@use './form-field-sizing';
1918
@use './form-field-subscript';
2019
@use './form-field-focus-overlay';
2120
@use './form-field-high-contrast';
2221
@use './form-field-native-select';
2322
@use './user-agent-overrides';
2423
@use './mdc-text-field-textarea-overrides';
2524
@use './mdc-text-field-structure-overrides';
25+
@use './mdc-text-field-density-overrides';
2626

2727
// Includes the structural styles of the components that the form field is composed of.
2828
@mixin _static-styles($query) {
@@ -57,6 +57,7 @@
5757
// MDC text-field overwrites.
5858
@include mdc-text-field-textarea-overrides.private-text-field-textarea-overrides();
5959
@include mdc-text-field-structure-overrides.private-text-field-structure-overrides();
60+
@include mdc-text-field-density-overrides.private-text-field-density-overrides();
6061

6162
// Include the subscript, focus-overlay, native select and high-contrast styles.
6263
@include form-field-subscript.private-form-field-subscript();
@@ -65,6 +66,10 @@
6566
@include form-field-high-contrast.private-form-field-high-contrast();
6667
@include user-agent-overrides.private-form-field-user-agent-overrides();
6768

69+
// The amount of padding between the icon prefix/suffix and the infix.
70+
// This assumes that the icon will be a 24px square with 12px padding.
71+
$_icon-prefix-infix-padding: 4px;
72+
6873
// Host element of the form-field. It contains the mdc-text-field wrapper
6974
// and the subscript wrapper.
7075
.mat-mdc-form-field {
@@ -144,11 +149,11 @@
144149
// icons, and therefore can't rely on MDC for these styles.
145150
.mat-mdc-form-field-icon-prefix,
146151
[dir='rtl'] .mat-mdc-form-field-icon-suffix {
147-
padding: 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding 0 0;
152+
padding: 0 $_icon-prefix-infix-padding 0 0;
148153
}
149154
.mat-mdc-form-field-icon-suffix,
150155
[dir='rtl'] .mat-mdc-form-field-icon-prefix {
151-
padding: 0 0 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding;
156+
padding: 0 0 0 $_icon-prefix-infix-padding;
152157
}
153158

154159
.mat-mdc-form-field-icon-prefix,
@@ -178,7 +183,12 @@
178183
.mat-mdc-form-field-infix {
179184
flex: auto;
180185
min-width: 0;
181-
width: form-field-sizing.$mat-form-field-default-infix-width;
186+
187+
// Infix stretches to fit the container, but naturally wants to be this wide. We set
188+
// this in order to have a consistent natural size for the various types of controls
189+
// that can go in a form field.
190+
width: 180px;
191+
182192
// Needed so that the floating label does not overlap with prefixes or suffixes.
183193
position: relative;
184194
box-sizing: border-box;

0 commit comments

Comments
 (0)