Skip to content

Commit 0e6dee3

Browse files
committed
fix(material/form-field): Don't allow label to grow larger than input (#29673)
Fixes #26558 (cherry picked from commit 77051f8)
1 parent 103ec13 commit 0e6dee3

File tree

4 files changed

+103
-71
lines changed

4 files changed

+103
-71
lines changed

src/material/form-field/_mdc-text-field-structure.scss

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66

77
// Includes the structural styles for the form field inherited from MDC.
88
@mixin private-text-field-structure {
9-
$filled-slots: (tokens-mdc-filled-text-field.$prefix,
10-
tokens-mdc-filled-text-field.get-token-slots());
11-
$outlined-slots: (tokens-mdc-outlined-text-field.$prefix,
12-
tokens-mdc-outlined-text-field.get-token-slots());
9+
$filled-slots: (
10+
tokens-mdc-filled-text-field.$prefix,
11+
tokens-mdc-filled-text-field.get-token-slots()
12+
);
13+
$outlined-slots: (
14+
tokens-mdc-outlined-text-field.$prefix,
15+
tokens-mdc-outlined-text-field.get-token-slots()
16+
);
1317

1418
.mdc-text-field {
1519
display: inline-flex;
@@ -124,19 +128,15 @@
124128
.mdc-text-field--outlined {
125129
height: 56px;
126130
overflow: visible;
127-
padding-left: 16px;
128-
padding-right: 16px;
129131

130-
@include _supports-max {
131-
@include token-utils.use-tokens($outlined-slots...) {
132-
$shape-var: token-utils.get-token-variable(container-shape);
133-
padding-right: max(16px, #{$shape-var});
134-
padding-left: max(16px, calc(#{$shape-var} + 4px));
132+
@include token-utils.use-tokens($outlined-slots...) {
133+
$shape-var: token-utils.get-token-variable(container-shape);
134+
padding-right: max(16px, #{$shape-var});
135+
padding-left: max(16px, calc(#{$shape-var} + 4px));
135136

136-
[dir='rtl'] & {
137-
padding-right: max(16px, calc(#{$shape-var} + 4px));
138-
padding-left: max(16px, #{$shape-var});
139-
}
137+
[dir='rtl'] & {
138+
padding-right: max(16px, calc(#{$shape-var} + 4px));
139+
padding-left: max(16px, #{$shape-var});
140140
}
141141
}
142142
}
@@ -336,17 +336,14 @@
336336
border-right: none;
337337
border-top-right-radius: 0;
338338
border-bottom-right-radius: 0;
339-
width: 12px;
340339

341340
@include token-utils.use-tokens($outlined-slots...) {
342341
@include token-utils.create-token-slot(border-top-left-radius, container-shape);
343342
@include token-utils.create-token-slot(border-bottom-left-radius, container-shape);
344343

345-
@include _supports-max {
346-
.mdc-text-field--outlined .mdc-notched-outline & {
347-
$shape-var: token-utils.get-token-variable(container-shape);
348-
width: max(12px, #{$shape-var});
349-
}
344+
.mdc-text-field--outlined .mdc-notched-outline & {
345+
$shape-var: token-utils.get-token-variable(container-shape);
346+
width: max(12px, #{$shape-var});
350347
}
351348
}
352349

@@ -391,14 +388,14 @@
391388
.mdc-notched-outline__notch {
392389
flex: 0 0 auto;
393390
width: auto;
394-
max-width: calc(100% - 24px);
395391

396392
@include token-utils.use-tokens($outlined-slots...) {
397-
@include _supports-max {
398-
.mdc-text-field--outlined .mdc-notched-outline & {
399-
$shape-var: token-utils.get-token-variable(container-shape);
400-
max-width: calc(100% - max(12px, #{$shape-var}) * 2);
401-
}
393+
.mdc-text-field--outlined .mdc-notched-outline & {
394+
$shape-var: token-utils.get-token-variable(container-shape);
395+
max-width: min(
396+
var(--mat-form-field-notch-max-width, 100%),
397+
calc(100% - max(12px, #{$shape-var}) * 2)
398+
);
402399
}
403400
}
404401

@@ -414,6 +411,7 @@
414411
padding-left: 0;
415412
padding-right: 8px;
416413
border-top: none;
414+
--mat-form-field-notch-max-width: 100%;
417415
}
418416

419417
[dir='rtl'] .mdc-notched-outline--notched & {
@@ -427,7 +425,8 @@
427425
}
428426

429427
.mdc-line-ripple {
430-
&::before, &::after {
428+
&::before,
429+
&::after {
431430
position: absolute;
432431
bottom: 0;
433432
left: 0;
@@ -453,17 +452,21 @@
453452
}
454453

455454
.mdc-text-field--filled.mdc-text-field--disabled & {
456-
@include token-utils.create-token-slot(border-bottom-color,
457-
disabled-active-indicator-color);
455+
@include token-utils.create-token-slot(
456+
border-bottom-color,
457+
disabled-active-indicator-color
458+
);
458459
}
459460

460461
#{$enabled-field}.mdc-text-field--invalid & {
461462
@include token-utils.create-token-slot(border-bottom-color, error-active-indicator-color);
462463
}
463464

464465
#{$enabled-field}.mdc-text-field--invalid:not(.mdc-text-field--focused):hover & {
465-
@include token-utils.create-token-slot(border-bottom-color,
466-
error-hover-active-indicator-color);
466+
@include token-utils.create-token-slot(
467+
border-bottom-color,
468+
error-hover-active-indicator-color
469+
);
467470
}
468471
}
469472
}
@@ -475,17 +478,21 @@
475478

476479
@include token-utils.use-tokens($filled-slots...) {
477480
.mdc-text-field--filled & {
478-
@include token-utils.create-token-slot(border-bottom-width,
479-
focus-active-indicator-height);
481+
@include token-utils.create-token-slot(
482+
border-bottom-width,
483+
focus-active-indicator-height
484+
);
480485
}
481486

482487
.mdc-text-field--filled:not(.mdc-text-field--disabled) & {
483488
@include token-utils.create-token-slot(border-bottom-color, focus-active-indicator-color);
484489
}
485490

486491
.mdc-text-field--filled.mdc-text-field--invalid:not(.mdc-text-field--disabled) & {
487-
@include token-utils.create-token-slot(border-bottom-color,
488-
error-focus-active-indicator-color);
492+
@include token-utils.create-token-slot(
493+
border-bottom-color,
494+
error-focus-active-indicator-color
495+
);
489496
}
490497
}
491498
}
@@ -565,21 +572,14 @@
565572
}
566573
}
567574

568-
// Wraps the content in a `@supports` query targeting the `max` CSS function.
569-
@mixin _supports-max {
570-
// stylelint-disable material/no-prefixes
571-
@supports (top: max(0%)) {
572-
@content;
573-
}
574-
// stylelint-enable
575-
}
576-
577575
// Includes the animation styles for the form field inherited from MDC.
578576
@mixin private-text-field-animations {
579577
$timing-curve: cubic-bezier(0.4, 0, 0.2, 1);
580578

581579
.mdc-floating-label {
582-
transition: transform 150ms $timing-curve, color 150ms $timing-curve;
580+
transition:
581+
transform 150ms $timing-curve,
582+
color 150ms $timing-curve;
583583
}
584584

585585
.mdc-text-field__input {
@@ -605,6 +605,8 @@
605605
}
606606

607607
.mdc-line-ripple::after {
608-
transition: transform 180ms $timing-curve, opacity 180ms $timing-curve;
608+
transition:
609+
transform 180ms $timing-curve,
610+
opacity 180ms $timing-curve;
609611
}
610612
}

src/material/form-field/form-field.html

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,39 @@
1212
simply link the label to the control using the label `for` attribute.
1313
-->
1414
@if (_hasFloatingLabel()) {
15-
<label matFormFieldFloatingLabel
16-
[floating]="_shouldLabelFloat()"
17-
[monitorResize]="_hasOutline()"
18-
[id]="_labelId"
19-
[attr.for]="_control.disableAutomaticLabeling ? null : _control.id">
15+
<label
16+
matFormFieldFloatingLabel
17+
[floating]="_shouldLabelFloat()"
18+
[monitorResize]="_hasOutline()"
19+
[id]="_labelId"
20+
[attr.for]="_control.disableAutomaticLabeling ? null : _control.id"
21+
>
2022
<ng-content select="mat-label"></ng-content>
2123
<!--
2224
We set the required marker as a separate element, in order to make it easier to target if
2325
apps want to override it and to be able to set `aria-hidden` so that screen readers don't
2426
pick it up.
2527
-->
26-
@if (!hideRequiredMarker && _control.required) {
27-
<span
28-
aria-hidden="true"
29-
class="mat-mdc-form-field-required-marker mdc-floating-label--required"></span>
30-
}
28+
@if (!hideRequiredMarker && _control.required) {
29+
<span
30+
aria-hidden="true"
31+
class="mat-mdc-form-field-required-marker mdc-floating-label--required"
32+
></span>
33+
}
3134
</label>
3235
}
3336
</ng-template>
3437

35-
<div class="mat-mdc-text-field-wrapper mdc-text-field" #textField
36-
[class.mdc-text-field--filled]="!_hasOutline()"
37-
[class.mdc-text-field--outlined]="_hasOutline()"
38-
[class.mdc-text-field--no-label]="!_hasFloatingLabel()"
39-
[class.mdc-text-field--disabled]="_control.disabled"
40-
[class.mdc-text-field--invalid]="_control.errorState"
41-
(click)="_control.onContainerClick($event)">
38+
<div
39+
class="mat-mdc-text-field-wrapper mdc-text-field"
40+
#textField
41+
[class.mdc-text-field--filled]="!_hasOutline()"
42+
[class.mdc-text-field--outlined]="_hasOutline()"
43+
[class.mdc-text-field--no-label]="!_hasFloatingLabel()"
44+
[class.mdc-text-field--disabled]="_control.disabled"
45+
[class.mdc-text-field--invalid]="_control.errorState"
46+
(click)="_control.onContainerClick($event)"
47+
>
4248
@if (!_hasOutline() && !_control.disabled) {
4349
<div class="mat-mdc-form-field-focus-overlay"></div>
4450
}
@@ -72,13 +78,13 @@
7278
</div>
7379

7480
@if (_hasTextSuffix) {
75-
<div class="mat-mdc-form-field-text-suffix">
81+
<div class="mat-mdc-form-field-text-suffix" #textSuffixContainer>
7682
<ng-content select="[matTextSuffix]"></ng-content>
7783
</div>
7884
}
7985

8086
@if (_hasIconSuffix) {
81-
<div class="mat-mdc-form-field-icon-suffix">
87+
<div class="mat-mdc-form-field-icon-suffix" #iconSuffixContainer>
8288
<ng-content select="[matSuffix], [matIconSuffix]"></ng-content>
8389
</div>
8490
}
@@ -89,13 +95,16 @@
8995
}
9096
</div>
9197

92-
<div class="mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align"
93-
[class.mat-mdc-form-field-subscript-dynamic-size]="subscriptSizing === 'dynamic'">
94-
98+
<div
99+
class="mat-mdc-form-field-subscript-wrapper mat-mdc-form-field-bottom-align"
100+
[class.mat-mdc-form-field-subscript-dynamic-size]="subscriptSizing === 'dynamic'"
101+
>
95102
@switch (_getDisplayedMessages()) {
96103
@case ('error') {
97-
<div class="mat-mdc-form-field-error-wrapper"
98-
[@transitionMessages]="_subscriptAnimationState">
104+
<div
105+
class="mat-mdc-form-field-error-wrapper"
106+
[@transitionMessages]="_subscriptAnimationState"
107+
>
99108
<ng-content select="mat-error, [matError]"></ng-content>
100109
</div>
101110
}

src/material/form-field/form-field.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ export class MatFormField
194194
@ViewChild('textField') _textField: ElementRef<HTMLElement>;
195195
@ViewChild('iconPrefixContainer') _iconPrefixContainer: ElementRef<HTMLElement>;
196196
@ViewChild('textPrefixContainer') _textPrefixContainer: ElementRef<HTMLElement>;
197+
@ViewChild('iconSuffixContainer') _iconSuffixContainer: ElementRef<HTMLElement>;
198+
@ViewChild('textSuffixContainer') _textSuffixContainer: ElementRef<HTMLElement>;
197199
@ViewChild(MatFormFieldFloatingLabel) _floatingLabel: MatFormFieldFloatingLabel | undefined;
198200
@ViewChild(MatFormFieldNotchedOutline) _notchedOutline: MatFormFieldNotchedOutline | undefined;
199201
@ViewChild(MatFormFieldLineRipple) _lineRipple: MatFormFieldLineRipple | undefined;
@@ -708,8 +710,12 @@ export class MatFormField
708710
}
709711
const iconPrefixContainer = this._iconPrefixContainer?.nativeElement;
710712
const textPrefixContainer = this._textPrefixContainer?.nativeElement;
713+
const iconSuffixContainer = this._iconSuffixContainer?.nativeElement;
714+
const textSuffixContainer = this._textSuffixContainer?.nativeElement;
711715
const iconPrefixContainerWidth = iconPrefixContainer?.getBoundingClientRect().width ?? 0;
712716
const textPrefixContainerWidth = textPrefixContainer?.getBoundingClientRect().width ?? 0;
717+
const iconSuffixContainerWidth = iconSuffixContainer?.getBoundingClientRect().width ?? 0;
718+
const textSuffixContainerWidth = textSuffixContainer?.getBoundingClientRect().width ?? 0;
713719
// If the directionality is RTL, the x-axis transform needs to be inverted. This
714720
// is because `transformX` does not change based on the page directionality.
715721
const negate = this._dir.value === 'rtl' ? '-1' : '1';
@@ -724,6 +730,17 @@ export class MatFormField
724730
--mat-mdc-form-field-label-transform,
725731
${FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM} translateX(${labelHorizontalOffset})
726732
)`;
733+
734+
// Prevent the label from overlapping the suffix when in resting position.
735+
const prefixAndSuffixWidth =
736+
iconPrefixContainerWidth +
737+
textPrefixContainerWidth +
738+
iconSuffixContainerWidth +
739+
textSuffixContainerWidth;
740+
this._elementRef.nativeElement.style.setProperty(
741+
'--mat-form-field-notch-max-width',
742+
`calc(100% - ${prefixAndSuffixWidth}px)`,
743+
);
727744
}
728745

729746
/** Checks whether the form field is attached to the DOM. */

tools/public_api_guard/material/form-field.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ export class MatFormField implements FloatingLabelParent, AfterContentInit, Afte
116116
// (undocumented)
117117
_iconPrefixContainer: ElementRef<HTMLElement>;
118118
// (undocumented)
119+
_iconSuffixContainer: ElementRef<HTMLElement>;
120+
// (undocumented)
119121
readonly _labelId: string;
120122
// (undocumented)
121123
_lineRipple: MatFormFieldLineRipple | undefined;
@@ -146,6 +148,8 @@ export class MatFormField implements FloatingLabelParent, AfterContentInit, Afte
146148
// (undocumented)
147149
_textPrefixContainer: ElementRef<HTMLElement>;
148150
// (undocumented)
151+
_textSuffixContainer: ElementRef<HTMLElement>;
152+
// (undocumented)
149153
static ɵcmp: i0.ɵɵComponentDeclaration<MatFormField, "mat-form-field", ["matFormField"], { "hideRequiredMarker": { "alias": "hideRequiredMarker"; "required": false; }; "color": { "alias": "color"; "required": false; }; "floatLabel": { "alias": "floatLabel"; "required": false; }; "appearance": { "alias": "appearance"; "required": false; }; "subscriptSizing": { "alias": "subscriptSizing"; "required": false; }; "hintLabel": { "alias": "hintLabel"; "required": false; }; }, {}, ["_labelChild", "_formFieldControl", "_prefixChildren", "_suffixChildren", "_errorChildren", "_hintChildren"], ["mat-label", "[matPrefix], [matIconPrefix]", "[matTextPrefix]", "*", "[matTextSuffix]", "[matSuffix], [matIconSuffix]", "mat-error, [matError]", "mat-hint:not([align='end'])", "mat-hint[align='end']"], true, never>;
150154
// (undocumented)
151155
static ɵfac: i0.ɵɵFactoryDeclaration<MatFormField, [null, null, null, null, null, { optional: true; }, { optional: true; }, null]>;

0 commit comments

Comments
 (0)