Skip to content

Commit 77051f8

Browse files
authored
fix(material/form-field): Don't allow label to grow larger than input (#29673)
Fixes #26558
1 parent f4cb9f1 commit 77051f8

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;
@@ -130,19 +134,15 @@
130134
.mdc-text-field--outlined {
131135
height: 56px;
132136
overflow: visible;
133-
padding-left: 16px;
134-
padding-right: 16px;
135137

136-
@include _supports-max {
137-
@include token-utils.use-tokens($outlined-slots...) {
138-
$shape-var: token-utils.get-token-variable(container-shape);
139-
padding-right: max(16px, #{$shape-var});
140-
padding-left: max(16px, calc(#{$shape-var} + 4px));
138+
@include token-utils.use-tokens($outlined-slots...) {
139+
$shape-var: token-utils.get-token-variable(container-shape);
140+
padding-right: max(16px, #{$shape-var});
141+
padding-left: max(16px, calc(#{$shape-var} + 4px));
141142

142-
[dir='rtl'] & {
143-
padding-right: max(16px, calc(#{$shape-var} + 4px));
144-
padding-left: max(16px, #{$shape-var});
145-
}
143+
[dir='rtl'] & {
144+
padding-right: max(16px, calc(#{$shape-var} + 4px));
145+
padding-left: max(16px, #{$shape-var});
146146
}
147147
}
148148
}
@@ -342,17 +342,14 @@
342342
border-right: none;
343343
border-top-right-radius: 0;
344344
border-bottom-right-radius: 0;
345-
width: 12px;
346345

347346
@include token-utils.use-tokens($outlined-slots...) {
348347
@include token-utils.create-token-slot(border-top-left-radius, container-shape);
349348
@include token-utils.create-token-slot(border-bottom-left-radius, container-shape);
350349

351-
@include _supports-max {
352-
.mdc-text-field--outlined .mdc-notched-outline & {
353-
$shape-var: token-utils.get-token-variable(container-shape);
354-
width: max(12px, #{$shape-var});
355-
}
350+
.mdc-text-field--outlined .mdc-notched-outline & {
351+
$shape-var: token-utils.get-token-variable(container-shape);
352+
width: max(12px, #{$shape-var});
356353
}
357354
}
358355

@@ -397,14 +394,14 @@
397394
.mdc-notched-outline__notch {
398395
flex: 0 0 auto;
399396
width: auto;
400-
max-width: calc(100% - 24px);
401397

402398
@include token-utils.use-tokens($outlined-slots...) {
403-
@include _supports-max {
404-
.mdc-text-field--outlined .mdc-notched-outline & {
405-
$shape-var: token-utils.get-token-variable(container-shape);
406-
max-width: calc(100% - max(12px, #{$shape-var}) * 2);
407-
}
399+
.mdc-text-field--outlined .mdc-notched-outline & {
400+
$shape-var: token-utils.get-token-variable(container-shape);
401+
max-width: min(
402+
var(--mat-form-field-notch-max-width, 100%),
403+
calc(100% - max(12px, #{$shape-var}) * 2)
404+
);
408405
}
409406
}
410407

@@ -420,6 +417,7 @@
420417
padding-left: 0;
421418
padding-right: 8px;
422419
border-top: none;
420+
--mat-form-field-notch-max-width: 100%;
423421
}
424422

425423
[dir='rtl'] .mdc-notched-outline--notched & {
@@ -433,7 +431,8 @@
433431
}
434432

435433
.mdc-line-ripple {
436-
&::before, &::after {
434+
&::before,
435+
&::after {
437436
position: absolute;
438437
bottom: 0;
439438
left: 0;
@@ -459,17 +458,21 @@
459458
}
460459

461460
.mdc-text-field--filled.mdc-text-field--disabled & {
462-
@include token-utils.create-token-slot(border-bottom-color,
463-
disabled-active-indicator-color);
461+
@include token-utils.create-token-slot(
462+
border-bottom-color,
463+
disabled-active-indicator-color
464+
);
464465
}
465466

466467
#{$enabled-field}.mdc-text-field--invalid & {
467468
@include token-utils.create-token-slot(border-bottom-color, error-active-indicator-color);
468469
}
469470

470471
#{$enabled-field}.mdc-text-field--invalid:not(.mdc-text-field--focused):hover & {
471-
@include token-utils.create-token-slot(border-bottom-color,
472-
error-hover-active-indicator-color);
472+
@include token-utils.create-token-slot(
473+
border-bottom-color,
474+
error-hover-active-indicator-color
475+
);
473476
}
474477
}
475478
}
@@ -481,17 +484,21 @@
481484

482485
@include token-utils.use-tokens($filled-slots...) {
483486
.mdc-text-field--filled & {
484-
@include token-utils.create-token-slot(border-bottom-width,
485-
focus-active-indicator-height);
487+
@include token-utils.create-token-slot(
488+
border-bottom-width,
489+
focus-active-indicator-height
490+
);
486491
}
487492

488493
.mdc-text-field--filled:not(.mdc-text-field--disabled) & {
489494
@include token-utils.create-token-slot(border-bottom-color, focus-active-indicator-color);
490495
}
491496

492497
.mdc-text-field--filled.mdc-text-field--invalid:not(.mdc-text-field--disabled) & {
493-
@include token-utils.create-token-slot(border-bottom-color,
494-
error-focus-active-indicator-color);
498+
@include token-utils.create-token-slot(
499+
border-bottom-color,
500+
error-focus-active-indicator-color
501+
);
495502
}
496503
}
497504
}
@@ -571,21 +578,14 @@
571578
}
572579
}
573580

574-
// Wraps the content in a `@supports` query targeting the `max` CSS function.
575-
@mixin _supports-max {
576-
// stylelint-disable material/no-prefixes
577-
@supports (top: max(0%)) {
578-
@content;
579-
}
580-
// stylelint-enable
581-
}
582-
583581
// Includes the animation styles for the form field inherited from MDC.
584582
@mixin private-text-field-animations {
585583
$timing-curve: cubic-bezier(0.4, 0, 0.2, 1);
586584

587585
.mdc-floating-label {
588-
transition: transform 150ms $timing-curve, color 150ms $timing-curve;
586+
transition:
587+
transform 150ms $timing-curve,
588+
color 150ms $timing-curve;
589589
}
590590

591591
.mdc-text-field__input {
@@ -611,6 +611,8 @@
611611
}
612612

613613
.mdc-line-ripple::after {
614-
transition: transform 180ms $timing-curve, opacity 180ms $timing-curve;
614+
transition:
615+
transform 180ms $timing-curve,
616+
opacity 180ms $timing-curve;
615617
}
616618
}

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)