Skip to content

Commit 15089ee

Browse files
committed
refactor(material/form-field): tokenize density overrides
Switches the custom density implementation of the form field to be based on tokens.
1 parent 997bf75 commit 15089ee

File tree

10 files changed

+136
-174
lines changed

10 files changed

+136
-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,9 @@
1+
@use 'sass:math';
12
@use 'sass:map';
3+
@use '@material/textfield' as mdc-textfield;
24
@use '../../token-utils';
35
@use '../../../style/sass-utils';
6+
@use '../../../theming/theming';
47
@use '../../../theming/inspection';
58
@use '../../../theming/palette';
69

@@ -86,7 +89,53 @@ $prefix: (mat, form-field);
8689

8790
// Tokens that can be configured through Angular Material's density theming API.
8891
@function get-density-tokens($theme) {
89-
@return ();
92+
$density-scale: theming.clamp-density(inspection.get-theme-density($theme), -4);
93+
$size-scale: (
94+
0: 56px,
95+
-1: 52px,
96+
-2: 48px,
97+
-3: 44px,
98+
-4: 36px,
99+
);
100+
$height: map.get($size-scale, $density-scale);
101+
$hide-label: $height < mdc-textfield.$minimum-height-for-filled-label;
102+
103+
// We computed the desired height of the form-field using the density configuration. The
104+
// spec only describes vertical spacing/alignment in non-dense mode. This means that we
105+
// cannot update the spacing to explicit numbers based on the density scale. Instead, we
106+
// determine the height reduction and equally subtract it from the default `top` and `bottom`
107+
// padding that is provided by the Material Design specification.
108+
$vertical-deduction: math.div(mdc-textfield.$height - $height, 2);
109+
110+
// Note: these calculations are trivial enough that we could do them at runtime with `calc`
111+
// and the value of the `height` token. The problem is that because we need to hide the label
112+
// if the container becomes too short, we have to change the padding calculation. This is
113+
// complicated further by the fact that filled form fields without labels have the same
114+
// vertical padding as outlined ones. Alternatives:
115+
// 1. Using container queries to hide the label and change the padding - this doesn't work
116+
// because size container queries require setting the `container-type` property which breaks
117+
// the form field layout. We could use style queries, but they're only supported in Chrome.
118+
// 2. Monitoring the size of the label - we already have a `ResizeObserver` on the label so we
119+
// could reuse it to also check when it becomes `display: none`. This would allows us to remove
120+
// the three padding tokens. We don't do it, because it would require us to always set up
121+
// the resize observer, as opposed to currently where it's only set up for outlined form fields.
122+
// This may lead to performance regressions.
123+
// 3. Conditionally adding `::before` and `::after` to the infix with positive and negative
124+
// margin respectively - this works, but is likely to break a lot of overrides that are targeting
125+
// a specific padding. It also runs the risk of overflowing the container.
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: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
@use 'sass:list';
2+
@use '@material/textfield' as mdc-textfield;
3+
@use '../core/tokens/m2/mat/form-field' as tokens-mat-form-field;
4+
@use '../core/tokens/token-utils';
5+
6+
// Mixin that includes the density styles for form fields. MDC provides their own density
7+
// styles for MDC text-field which we cannot use. MDC relies on input elements to stretch
8+
// vertically when the height is reduced as per density scale. This doesn't work for our
9+
// form field since we support custom form field controls without a fixed height. Instead, we
10+
// provide spacing that makes arbitrary controls align as specified in the Material Design
11+
// specification. In order to support density, we need to adjust the vertical spacing to be
12+
// based on the density scale.
13+
@mixin private-text-field-density-overrides() {
14+
@include token-utils.use-tokens(
15+
tokens-mat-form-field.$prefix, tokens-mat-form-field.get-token-slots()) {
16+
$height: token-utils.get-token-variable(container-height);
17+
18+
.mat-mdc-form-field-infix {
19+
// We add a minimum height to the infix container to ensure that custom controls have the
20+
// same default vertical space as text-field inputs (with respect to the vertical padding).
21+
min-height: var(#{$height});
22+
23+
@include token-utils.create-token-slot(padding-top,
24+
filled-with-label-container-padding-top);
25+
@include token-utils.create-token-slot(padding-bottom,
26+
filled-with-label-container-padding-bottom);
27+
28+
.mdc-text-field--outlined &,
29+
.mdc-text-field--no-label & {
30+
@include token-utils.create-token-slot(padding-top, container-vertical-padding);
31+
@include token-utils.create-token-slot(padding-bottom, container-vertical-padding);
32+
}
33+
}
34+
35+
// By default, MDC aligns the label using percentage. This will be overwritten based
36+
// on whether a textarea is used. This is not possible in our implementation of the
37+
// form-field because we do not know what type of form-field control is set up. Hence
38+
// we always use a fixed position for the label. This does not have any implications.
39+
.mat-mdc-text-field-wrapper .mat-mdc-form-field-flex .mat-mdc-floating-label {
40+
top: calc(var(#{$height}) / 2);
41+
}
42+
43+
// We need to conditionally hide the floating label based on the height of the form field.
44+
.mdc-text-field--filled .mat-mdc-floating-label {
45+
display: var(#{token-utils.get-token-variable(filled-label-display)}, block);
46+
}
47+
48+
// For the outline appearance, we re-create the active floating label transform. This is
49+
// necessary because the transform for docked floating labels can be updated to account for
50+
// the width of prefix container.
51+
.mat-mdc-text-field-wrapper.mdc-text-field--outlined .mdc-notched-outline--upgraded
52+
.mdc-floating-label--float-above {
53+
// Needs to be in a string form to work around an internal check that incorrectly flags this
54+
// interpolation in `calc` as unnecessary. If we don't have it, Sass won't evaluate it.
55+
$translate: 'calc(#{mdc-textfield.get-outlined-label-position-y(var(#{$height}))} * -1)';
56+
--mat-mdc-form-field-label-transform: translateY(#{$translate})
57+
scale(var(--mat-mdc-form-field-floating-label-scale, 0.75));
58+
transform: var(--mat-mdc-form-field-label-transform);
59+
}
60+
}
61+
}

0 commit comments

Comments
 (0)