From b80bb42ff8ccaac8b2a304be34266c2accc5cdfb Mon Sep 17 00:00:00 2001 From: Miles Malerba Date: Thu, 4 Mar 2021 14:31:18 -0800 Subject: [PATCH] fix(material-experimental/mdc-form-field): fix prefix/suffix padding --- .../mdc-form-field/_form-field-sizing.scss | 8 +++++ .../_mdc-text-field-structure-overrides.scss | 25 ++++++++++++++ .../mdc-form-field/directives/prefix.ts | 10 ++++-- .../mdc-form-field/directives/suffix.ts | 10 ++++-- .../mdc-form-field/form-field.html | 8 ++--- .../mdc-form-field/form-field.scss | 24 ++++++++++++- .../mdc-form-field/form-field.ts | 34 +++++++++++++++---- 7 files changed, 104 insertions(+), 15 deletions(-) diff --git a/src/material-experimental/mdc-form-field/_form-field-sizing.scss b/src/material-experimental/mdc-form-field/_form-field-sizing.scss index f2b9c97fe761..34dc37fadf63 100644 --- a/src/material-experimental/mdc-form-field/_form-field-sizing.scss +++ b/src/material-experimental/mdc-form-field/_form-field-sizing.scss @@ -36,3 +36,11 @@ $mat-form-field-with-label-input-padding-bottom: 8px; // same reasoning applies to the padding for text fields without label. $mat-form-field-no-label-padding-bottom: 16px; $mat-form-field-no-label-padding-top: 16px; + +// The amount of padding between the icon prefix/suffix and the infix. +// This assumes that the icon will be a 24px square with 12px padding. +$mat-form-field-icon-prefix-infix-padding: 4px; + +// The amount of padding between the end of the form-field and the infix for a form-field with no +// icons. +$mat-form-field-end-padding: 16px; diff --git a/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss b/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss index 8428d0b0729b..d877174cde89 100644 --- a/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss +++ b/src/material-experimental/mdc-form-field/_mdc-text-field-structure-overrides.scss @@ -40,6 +40,31 @@ flex: auto; } + // The icon prefix/suffix is closer to the edge of the form-field than the infix is in a + // form-field with no prefix/suffix. Therefore the standard padding has to be removed when showing + // an icon prefix or suffix. We can't rely on MDC's styles for this because we use a different + // structure for our form-field in order to support arbitrary height input elements. + .mat-mdc-form-field-has-icon-prefix .mat-mdc-text-field-wrapper { + padding-left: 0; + } + .mat-mdc-form-field-has-icon-suffix .mat-mdc-text-field-wrapper { + padding-right: 0; + } + [dir='rtl'] { + // Undo the above padding removals which only apply in LTR languages. + .mat-mdc-text-field-wrapper { + padding-left: form-field-sizing.$mat-form-field-end-padding; + padding-right: form-field-sizing.$mat-form-field-end-padding; + } + // ...and apply the correct padding resets for RTL languages. + .mat-mdc-form-field-has-icon-suffix .mat-mdc-text-field-wrapper { + padding-left: 0; + } + .mat-mdc-form-field-has-icon-prefix .mat-mdc-text-field-wrapper { + padding-right: 0; + } + } + // The default MDC text-field implementation does not support labels which always float. // MDC only renders the placeholder if the input is focused. We extend this to show the // placeholder if the form-field label is set to always float. diff --git a/src/material-experimental/mdc-form-field/directives/prefix.ts b/src/material-experimental/mdc-form-field/directives/prefix.ts index d44b3cd423e3..f60d3a9e9801 100644 --- a/src/material-experimental/mdc-form-field/directives/prefix.ts +++ b/src/material-experimental/mdc-form-field/directives/prefix.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Directive, InjectionToken} from '@angular/core'; +import {Directive, ElementRef, InjectionToken} from '@angular/core'; /** * 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'); selector: '[matPrefix], [matIconPrefix], [matTextPrefix]', providers: [{provide: MAT_PREFIX, useExisting: MatPrefix}], }) -export class MatPrefix {} +export class MatPrefix { + _isText = false; + + constructor(elementRef: ElementRef) { + this._isText = elementRef.nativeElement.hasAttribute('matTextPrefix'); + } +} diff --git a/src/material-experimental/mdc-form-field/directives/suffix.ts b/src/material-experimental/mdc-form-field/directives/suffix.ts index cca32a9996ca..2c0ca14db3ca 100644 --- a/src/material-experimental/mdc-form-field/directives/suffix.ts +++ b/src/material-experimental/mdc-form-field/directives/suffix.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Directive, InjectionToken} from '@angular/core'; +import {Directive, ElementRef, InjectionToken} from '@angular/core'; /** * 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'); selector: '[matSuffix], [matIconSuffix], [matTextSuffix]', providers: [{provide: MAT_SUFFIX, useExisting: MatSuffix}], }) -export class MatSuffix {} +export class MatSuffix { + _isText = false; + + constructor(elementRef: ElementRef) { + this._isText = elementRef.nativeElement.hasAttribute('matTextSuffix'); + } +} diff --git a/src/material-experimental/mdc-form-field/form-field.html b/src/material-experimental/mdc-form-field/form-field.html index 030f5f19450e..8dde556e1776 100644 --- a/src/material-experimental/mdc-form-field/form-field.html +++ b/src/material-experimental/mdc-form-field/form-field.html @@ -44,10 +44,10 @@ -
+
-
+
@@ -59,10 +59,10 @@
-
+
-
+
diff --git a/src/material-experimental/mdc-form-field/form-field.scss b/src/material-experimental/mdc-form-field/form-field.scss index 8cab9b93a80c..9bfb1fc91ab5 100644 --- a/src/material-experimental/mdc-form-field/form-field.scss +++ b/src/material-experimental/mdc-form-field/form-field.scss @@ -7,7 +7,6 @@ @use '../mdc-helpers/mdc-helpers'; @import '@material/textfield/mixins.import'; - // Base styles for MDC text-field, notched-outline, floating label and line-ripple. @include mdc-text-field-without-ripple( $query: mdc-helpers.$mat-base-styles-without-animation-query); @@ -52,9 +51,32 @@ width: 100%; } +// Vertically center icons. .mat-mdc-form-field-icon-prefix, .mat-mdc-form-field-icon-suffix { align-self: center; + // The line-height can cause the prefix/suffix container to be taller than the actual icons, + // breaking the vertical centering. To prevent this we set the line-height to 0. + line-height: 0; +} + +// The prefix/suffix needs a little extra padding between the icon and the infix. Because we need to +// support arbitrary height input elements, we use a different DOM structure for prefix and suffix +// icons, and therefore can't rely on MDC for these styles. +.mat-mdc-form-field-icon-prefix, +[dir='rtl'] .mat-mdc-form-field-icon-suffix { + padding: 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding 0 0; +} +.mat-mdc-form-field-icon-suffix, +[dir='rtl'] .mat-mdc-form-field-icon-prefix { + padding: 0 0 0 form-field-sizing.$mat-form-field-icon-prefix-infix-padding; +} + +.mat-mdc-form-field-icon-prefix, +.mat-mdc-form-field-icon-suffix { + & > .mat-icon { + padding: 12px; + } } // Infix that contains the projected content (usually an input or a textarea). We ensure diff --git a/src/material-experimental/mdc-form-field/form-field.ts b/src/material-experimental/mdc-form-field/form-field.ts index 63ee64665b0d..a91093d163f9 100644 --- a/src/material-experimental/mdc-form-field/form-field.ts +++ b/src/material-experimental/mdc-form-field/form-field.ts @@ -102,6 +102,8 @@ const FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM = `translateY(-50%)`; host: { 'class': 'mat-mdc-form-field', '[class.mat-mdc-form-field-label-always-float]': '_shouldAlwaysFloat()', + '[class.mat-mdc-form-field-has-icon-prefix]': '_hasIconPrefix', + '[class.mat-mdc-form-field-has-icon-suffix]': '_hasIconSuffix', // Note that these classes reuse the same names as the non-MDC version, because they can be // 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 } private _hintLabel = ''; + _hasIconPrefix = false; + _hasTextPrefix = false; + _hasIconSuffix = false; + _hasTextSuffix = false; + // Unique id for the internal form field label. readonly _labelId = `mat-mdc-form-field-label-${nextUniqueId++}`; @@ -438,13 +445,24 @@ export class MatFormField implements AfterViewInit, OnDestroy, AfterContentCheck } } + private _checkPrefixAndSuffixTypes() { + this._hasIconPrefix = !!this._prefixChildren.find(p => !p._isText); + this._hasTextPrefix = !!this._prefixChildren.find(p => p._isText); + this._hasIconSuffix = !!this._suffixChildren.find(s => !s._isText); + this._hasTextSuffix = !!this._suffixChildren.find(s => s._isText); + } + /** Initializes the prefix and suffix containers. */ private _initializePrefixAndSuffix() { + this._checkPrefixAndSuffixTypes(); // Mark the form-field as dirty whenever the prefix or suffix children change. This // is necessary because we conditionally display the prefix/suffix containers based // on whether there is projected content. merge(this._prefixChildren.changes, this._suffixChildren.changes) - .subscribe(() => this._changeDetectorRef.markForCheck()); + .subscribe(() => { + this._checkPrefixAndSuffixTypes(); + this._changeDetectorRef.markForCheck(); + }); } /** @@ -667,15 +685,19 @@ export class MatFormField implements AfterViewInit, OnDestroy, AfterContentCheck this._needsOutlineLabelOffsetUpdateOnStable = true; return; } - const iconPrefixContainer = this._iconPrefixContainer.nativeElement as HTMLElement; - const textPrefixContainer = this._textPrefixContainer.nativeElement as HTMLElement; + const iconPrefixContainer = this._iconPrefixContainer?.nativeElement; + const textPrefixContainer = this._textPrefixContainer?.nativeElement; // If the directionality is RTL, the x-axis transform needs to be inverted. This // is because `transformX` does not change based on the page directionality. const labelHorizontalOffset = (this._dir.value === 'rtl' ? -1 : 1) * ( - iconPrefixContainer.getBoundingClientRect().width + - textPrefixContainer.getBoundingClientRect().width - ); + (iconPrefixContainer ? + // If there's an icon prefix, we disable the default 16px padding, + // so make sure to account for that. + (iconPrefixContainer?.getBoundingClientRect().width ?? 0) - 16 : 0 + ) + + (textPrefixContainer?.getBoundingClientRect().width ?? 0) + ); // Update the transform the floating label to account for the prefix container. Note // that we do not want to overwrite the default transform for docked floating labels.