Skip to content

Commit 23a0248

Browse files
authored
fix(material-experimental/mdc-form-field): fix prefix/suffix padding (#22090)
1 parent a219a4a commit 23a0248

File tree

7 files changed

+104
-15
lines changed

7 files changed

+104
-15
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,11 @@ $mat-form-field-with-label-input-padding-bottom: 8px;
3636
// same reasoning applies to the padding for text fields without label.
3737
$mat-form-field-no-label-padding-bottom: 16px;
3838
$mat-form-field-no-label-padding-top: 16px;
39+
40+
// The amount of padding between the icon prefix/suffix and the infix.
41+
// This assumes that the icon will be a 24px square with 12px padding.
42+
$mat-form-field-icon-prefix-infix-padding: 4px;
43+
44+
// The amount of padding between the end of the form-field and the infix for a form-field with no
45+
// icons.
46+
$mat-form-field-end-padding: 16px;

src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,31 @@
4040
flex: auto;
4141
}
4242

43+
// The icon prefix/suffix is closer to the edge of the form-field than the infix is in a
44+
// form-field with no prefix/suffix. Therefore the standard padding has to be removed when showing
45+
// an icon prefix or suffix. We can't rely on MDC's styles for this because we use a different
46+
// structure for our form-field in order to support arbitrary height input elements.
47+
.mat-mdc-form-field-has-icon-prefix .mat-mdc-text-field-wrapper {
48+
padding-left: 0;
49+
}
50+
.mat-mdc-form-field-has-icon-suffix .mat-mdc-text-field-wrapper {
51+
padding-right: 0;
52+
}
53+
[dir='rtl'] {
54+
// Undo the above padding removals which only apply in LTR languages.
55+
.mat-mdc-text-field-wrapper {
56+
padding-left: form-field-sizing.$mat-form-field-end-padding;
57+
padding-right: form-field-sizing.$mat-form-field-end-padding;
58+
}
59+
// ...and apply the correct padding resets for RTL languages.
60+
.mat-mdc-form-field-has-icon-suffix .mat-mdc-text-field-wrapper {
61+
padding-left: 0;
62+
}
63+
.mat-mdc-form-field-has-icon-prefix .mat-mdc-text-field-wrapper {
64+
padding-right: 0;
65+
}
66+
}
67+
4368
// The default MDC text-field implementation does not support labels which always float.
4469
// MDC only renders the placeholder if the input is focused. We extend this to show the
4570
// placeholder if the form-field label is set to always float.

src/material-experimental/mdc-form-field/directives/prefix.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Directive, InjectionToken} from '@angular/core';
9+
import {Directive, ElementRef, InjectionToken} from '@angular/core';
1010

1111
/**
1212
* Injection token that can be used to reference instances of `MatPrefix`. It serves as
@@ -20,4 +20,10 @@ export const MAT_PREFIX = new InjectionToken<MatPrefix>('MatPrefix');
2020
selector: '[matPrefix], [matIconPrefix], [matTextPrefix]',
2121
providers: [{provide: MAT_PREFIX, useExisting: MatPrefix}],
2222
})
23-
export class MatPrefix {}
23+
export class MatPrefix {
24+
_isText = false;
25+
26+
constructor(elementRef: ElementRef) {
27+
this._isText = elementRef.nativeElement.hasAttribute('matTextPrefix');
28+
}
29+
}

src/material-experimental/mdc-form-field/directives/suffix.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Directive, InjectionToken} from '@angular/core';
9+
import {Directive, ElementRef, InjectionToken} from '@angular/core';
1010

1111
/**
1212
* Injection token that can be used to reference instances of `MatSuffix`. It serves as
@@ -20,4 +20,10 @@ export const MAT_SUFFIX = new InjectionToken<MatSuffix>('MatSuffix');
2020
selector: '[matSuffix], [matIconSuffix], [matTextSuffix]',
2121
providers: [{provide: MAT_SUFFIX, useExisting: MatSuffix}],
2222
})
23-
export class MatSuffix {}
23+
export class MatSuffix {
24+
_isText = false;
25+
26+
constructor(elementRef: ElementRef) {
27+
this._isText = elementRef.nativeElement.hasAttribute('matTextSuffix');
28+
}
29+
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@
4444
</ng-template>
4545
</div>
4646

47-
<div class="mat-mdc-form-field-icon-prefix" *ngIf="_prefixChildren.length" #iconPrefixContainer>
47+
<div class="mat-mdc-form-field-icon-prefix" *ngIf="_hasIconPrefix" #iconPrefixContainer>
4848
<ng-content select="[matPrefix], [matIconPrefix]"></ng-content>
4949
</div>
50-
<div class="mat-mdc-form-field-text-prefix" *ngIf="_prefixChildren.length" #textPrefixContainer>
50+
<div class="mat-mdc-form-field-text-prefix" *ngIf="_hasTextPrefix" #textPrefixContainer>
5151
<ng-content select="[matTextPrefix]"></ng-content>
5252
</div>
5353

@@ -59,10 +59,10 @@
5959
<ng-content></ng-content>
6060
</div>
6161

62-
<div class="mat-mdc-form-field-text-suffix" *ngIf="_suffixChildren.length">
62+
<div class="mat-mdc-form-field-text-suffix" *ngIf="_hasTextSuffix">
6363
<ng-content select="[matTextSuffix]"></ng-content>
6464
</div>
65-
<div class="mat-mdc-form-field-icon-suffix" *ngIf="_suffixChildren.length">
65+
<div class="mat-mdc-form-field-icon-suffix" *ngIf="_hasIconSuffix">
6666
<ng-content select="[matSuffix], [matIconSuffix]"></ng-content>
6767
</div>
6868
</div>

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

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
@use '../mdc-helpers/mdc-helpers';
88
@import '@material/textfield/mixins.import';
99

10-
1110
// Base styles for MDC text-field, notched-outline, floating label and line-ripple.
1211
@include mdc-text-field-without-ripple(
1312
$query: mdc-helpers.$mat-base-styles-without-animation-query);
@@ -52,9 +51,32 @@
5251
width: 100%;
5352
}
5453

54+
// Vertically center icons.
5555
.mat-mdc-form-field-icon-prefix,
5656
.mat-mdc-form-field-icon-suffix {
5757
align-self: center;
58+
// The line-height can cause the prefix/suffix container to be taller than the actual icons,
59+
// breaking the vertical centering. To prevent this we set the line-height to 0.
60+
line-height: 0;
61+
}
62+
63+
// The prefix/suffix needs a little extra padding between the icon and the infix. Because we need to
64+
// support arbitrary height input elements, we use a different DOM structure for prefix and suffix
65+
// icons, and therefore can't rely on MDC for these styles.
66+
.mat-mdc-form-field-icon-prefix,
67+
[dir='rtl'] .mat-mdc-form-field-icon-suffix {
68+
padding: 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding 0 0;
69+
}
70+
.mat-mdc-form-field-icon-suffix,
71+
[dir='rtl'] .mat-mdc-form-field-icon-prefix {
72+
padding: 0 0 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding;
73+
}
74+
75+
.mat-mdc-form-field-icon-prefix,
76+
.mat-mdc-form-field-icon-suffix {
77+
& > .mat-icon {
78+
padding: 12px;
79+
}
5880
}
5981

6082
// Infix that contains the projected content (usually an input or a textarea). We ensure

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

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ const FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM = `translateY(-50%)`;
102102
host: {
103103
'class': 'mat-mdc-form-field',
104104
'[class.mat-mdc-form-field-label-always-float]': '_shouldAlwaysFloat()',
105+
'[class.mat-mdc-form-field-has-icon-prefix]': '_hasIconPrefix',
106+
'[class.mat-mdc-form-field-has-icon-suffix]': '_hasIconSuffix',
105107

106108
// Note that these classes reuse the same names as the non-MDC version, because they can be
107109
// considered a public API since custom form controls may use them to style themselves.
@@ -197,6 +199,11 @@ export class MatFormField implements AfterViewInit, OnDestroy, AfterContentCheck
197199
}
198200
private _hintLabel = '';
199201

202+
_hasIconPrefix = false;
203+
_hasTextPrefix = false;
204+
_hasIconSuffix = false;
205+
_hasTextSuffix = false;
206+
200207
// Unique id for the internal form field label.
201208
readonly _labelId = `mat-mdc-form-field-label-${nextUniqueId++}`;
202209

@@ -438,13 +445,24 @@ export class MatFormField implements AfterViewInit, OnDestroy, AfterContentCheck
438445
}
439446
}
440447

448+
private _checkPrefixAndSuffixTypes() {
449+
this._hasIconPrefix = !!this._prefixChildren.find(p => !p._isText);
450+
this._hasTextPrefix = !!this._prefixChildren.find(p => p._isText);
451+
this._hasIconSuffix = !!this._suffixChildren.find(s => !s._isText);
452+
this._hasTextSuffix = !!this._suffixChildren.find(s => s._isText);
453+
}
454+
441455
/** Initializes the prefix and suffix containers. */
442456
private _initializePrefixAndSuffix() {
457+
this._checkPrefixAndSuffixTypes();
443458
// Mark the form-field as dirty whenever the prefix or suffix children change. This
444459
// is necessary because we conditionally display the prefix/suffix containers based
445460
// on whether there is projected content.
446461
merge(this._prefixChildren.changes, this._suffixChildren.changes)
447-
.subscribe(() => this._changeDetectorRef.markForCheck());
462+
.subscribe(() => {
463+
this._checkPrefixAndSuffixTypes();
464+
this._changeDetectorRef.markForCheck();
465+
});
448466
}
449467

450468
/**
@@ -667,15 +685,19 @@ export class MatFormField implements AfterViewInit, OnDestroy, AfterContentCheck
667685
this._needsOutlineLabelOffsetUpdateOnStable = true;
668686
return;
669687
}
670-
const iconPrefixContainer = this._iconPrefixContainer.nativeElement as HTMLElement;
671-
const textPrefixContainer = this._textPrefixContainer.nativeElement as HTMLElement;
688+
const iconPrefixContainer = this._iconPrefixContainer?.nativeElement;
689+
const textPrefixContainer = this._textPrefixContainer?.nativeElement;
672690
// If the directionality is RTL, the x-axis transform needs to be inverted. This
673691
// is because `transformX` does not change based on the page directionality.
674692
const labelHorizontalOffset =
675693
(this._dir.value === 'rtl' ? -1 : 1) * (
676-
iconPrefixContainer.getBoundingClientRect().width +
677-
textPrefixContainer.getBoundingClientRect().width
678-
);
694+
(iconPrefixContainer ?
695+
// If there's an icon prefix, we disable the default 16px padding,
696+
// so make sure to account for that.
697+
(iconPrefixContainer?.getBoundingClientRect().width ?? 0) - 16 : 0
698+
) +
699+
(textPrefixContainer?.getBoundingClientRect().width ?? 0)
700+
);
679701

680702
// Update the transform the floating label to account for the prefix container. Note
681703
// that we do not want to overwrite the default transform for docked floating labels.

0 commit comments

Comments
 (0)