Skip to content

Commit

Permalink
fix(material-experimental/mdc-form-field): fix outline notch width (#…
Browse files Browse the repository at this point in the history
…23005)

* fix(material-experimental/mdc-form-field): fix outline notch width

This commit fixes a couple of issues that have been introduced
by accident over time:

1. The outline notch width is incorrectly calculated since
   #21676. This happens
   because we manually override the label's typography level to `body1`
   but didn't account for the `font-size` set by the `notched-outline`.
   This resulted in the computed label width being incorrect.

2. #22089 added `line-height:
   0` to fix textarea's accidentally expanding the infix vertically.
   This breaks custom controls and their alignment. Textarea's behave a
   little different compared to a native input, and the actual fix seems
   to be to fix the alignment from `top` to `middle` so that the infix
   does not try to add additional padding (due to the inherited `body1`
   line-height and to satisfy `min-height`). Example:
   https://jsfiddle.net/rpvm4bkL/6/.

3. Typography has been moved with
   #21676 to the
   input control (notice how `font: inherit` is removed too). This
   breaks custom form controls that rely on the typography provided
   by the form-field (these controls would just inherit typography)

4. Not an actual fix, but a cleanup since 21ab17f
  added a class to avoid styles from the form-field leaking into
  standalone inputs. The class has been updated to be more specific that
  it only matches form-control inputs, and not _any_ type of form
  control.

* fix(material-experimental/mdc-form-field): increase specificity of `line-height: normal` on the floating label

Co-authored-by: Miles Malerba <mmalerba@google.com>
(cherry picked from commit 04d2aaa)
  • Loading branch information
devversion authored and andrewseguin committed Sep 14, 2021
1 parent b487323 commit 13b24ab
Show file tree
Hide file tree
Showing 15 changed files with 104 additions and 56 deletions.
19 changes: 19 additions & 0 deletions src/dev-app/mdc-input/mdc-input-demo.html
Expand Up @@ -731,6 +731,25 @@ <h3>&lt;textarea&gt; with bindable autosize </h3>
<mat-label>Label</mat-label>
<input matInput>
</mat-form-field>

<p>
Textarea alignment
</p>

<mat-form-field appearance="outline">
<mat-label>Textarea</mat-label>
<textarea matInput rows="1"></textarea>
</mat-form-field>

<mat-form-field appearance="outline">
<mat-label>Input</mat-label>
<input matInput>
</mat-form-field>

<mat-form-field appearance="outline">
<mat-label>Input</mat-label>
<input matInput>
</mat-form-field>
</mat-card-content>
</mat-card>

Expand Down
2 changes: 1 addition & 1 deletion src/material-experimental/mdc-chips/chip-input.spec.ts
Expand Up @@ -141,7 +141,7 @@ describe('MDC-based MatChipInput', () => {

it('should set input styling classes', () => {
expect(inputNativeElement.classList).toContain('mat-mdc-input-element');
expect(inputNativeElement.classList).toContain('mat-mdc-form-field-control');
expect(inputNativeElement.classList).toContain('mat-mdc-form-field-input-control');
expect(inputNativeElement.classList).toContain('mat-mdc-chip-input');
expect(inputNativeElement.classList).toContain('mdc-text-field__input');
});
Expand Down
2 changes: 1 addition & 1 deletion src/material-experimental/mdc-chips/chip-input.ts
Expand Up @@ -135,7 +135,7 @@ export class MatChipInput implements MatChipTextControl, AfterContentInit, OnCha
this.inputElement = this._elementRef.nativeElement as HTMLInputElement;

if (formField) {
this.inputElement.classList.add('mat-mdc-form-field-control');
this.inputElement.classList.add('mat-mdc-form-field-input-control');
}
}

Expand Down
Expand Up @@ -18,7 +18,7 @@ $mat-form-field-select-horizontal-end-padding: $mat-form-field-select-arrow-widt
// Remove the native select down arrow and ensure that the native appearance
// does not conflict with the form-field. e.g. Focus indication of the native
// select is undesired since we handle focus as part of the form-field.
select.mat-mdc-form-field-control {
select.mat-mdc-form-field-input-control {
-moz-appearance: none;
-webkit-appearance: none;
background-color: transparent;
Expand Down Expand Up @@ -83,7 +83,7 @@ $mat-form-field-select-horizontal-end-padding: $mat-form-field-select-arrow-widt

// Add padding on the end of the native select so that the content does not
// overlap with the Material Design arrow.
.mat-mdc-form-field-control {
.mat-mdc-form-field-input-control {
padding-right: $mat-form-field-select-horizontal-end-padding;
[dir='rtl'] & {
padding-right: 0;
Expand All @@ -105,7 +105,7 @@ $mat-form-field-select-horizontal-end-padding: $mat-form-field-select-arrow-widt
$dropdown-icon-color: rgba(mdc-theme-color.prop-value(on-surface), 0.54);
$disabled-dropdown-icon-color: rgba(mdc-theme-color.prop-value(on-surface), 0.38);

select.mat-mdc-form-field-control {
select.mat-mdc-form-field-input-control {
// On dark themes we set the native `select` color to some shade of white,
// however the color propagates to all of the `option` elements, which are
// always on a white background inside the dropdown, causing them to blend in.
Expand Down
Expand Up @@ -38,7 +38,3 @@ $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;
@@ -1,6 +1,7 @@
@use '@material/textfield' as mdc-textfield;
@use '@material/theme/theme' as mdc-theme;
@use '@material/typography' as mdc-typography;
@use '@material/textfield/variables' as mdc-textfield-variables;
@use 'form-field-sizing';
@use '../mdc-helpers/mdc-helpers';
@use '../../material/core/theming/theming';
Expand All @@ -13,16 +14,13 @@
position: relative;
}

// The horizontal padding between the edge of the text box and the start of the subscript text.
$subscript-horizontal-padding: 16px;

.mat-mdc-form-field-hint-wrapper,
.mat-mdc-form-field-error-wrapper {
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 0 $subscript-horizontal-padding;
padding: 0 mdc-textfield-variables.$padding-horizontal;
}

.mat-mdc-form-field-bottom-align::before {
Expand All @@ -31,14 +29,8 @@
height: 16px;
}

// Scale down icons in the subscript to be the same size as the text.
.mat-mdc-form-field-subscript-wrapper,
.mat-mdc-form-field label {
.mat-icon {
width: 1em;
height: 1em;
font-size: inherit;
}
.mat-mdc-form-field-hint-end {
order: 1;
}

// Clears the floats on the hints. This is necessary for the hint animation to work.
Expand Down
30 changes: 23 additions & 7 deletions src/material-experimental/mdc-form-field/_form-field-theme.scss
Expand Up @@ -3,7 +3,7 @@
@use '@material/notched-outline' as mdc-notched-outline;
@use '@material/line-ripple' as mdc-line-ripple;
@use '@material/theme/theme-color' as mdc-theme-color;
@use '@material/typography' as mdc-typography;
@use '@material/typography/typography' as mdc-typography;
@use '../mdc-helpers/mdc-helpers';
@use '../../material/core/typography/typography';
@use 'form-field-density';
Expand Down Expand Up @@ -72,14 +72,30 @@
@include mdc-line-ripple.core-styles($query: mdc-helpers.$mat-typography-styles-query);
@include form-field-subscript.private-form-field-subscript-typography($config);

// MDC uses the `subtitle1` level for the input label and value, but the spec shows `body1` as
// the correct level.
.mat-mdc-form-field-control,
.mat-mdc-form-field label,
.mat-mdc-form-field-text-prefix,
.mat-mdc-form-field-text-suffix {
// MDC uses `subtitle1` for the input value, placeholder and floating label. The spec
// shows `body1` for text fields though, so we manually override the typography.
// Note: Form controls inherit the typography from the parent form field.
.mat-mdc-form-field,
.mat-mdc-form-field .mdc-floating-label {
@include mdc-typography.typography(body1, $query: mdc-helpers.$mat-typography-styles-query);
}

// Above, we updated the floating label to use the `body1` typography level. The MDC notched
// outline overrides this accidentally (only when the label floats) to a `rem`-based value.
// This results in different label widths when floated/docked and ultimately breaks the notch
// width as it has been measured in the docked state (where `body1` is applied). We try to
// unset these styles set by the `mdc-notched-outline`:
// https://github.com/material-components/material-components-web/blob/master/packages/mdc-notched-outline/_mixins.scss#L272-L292.
.mat-mdc-form-field .mdc-text-field--outlined {
// For the non-upgraded notch label (i.e. when rendered on the server), also
// use the correct `body1` typography level.
.mdc-floating-label--float-above {
font-size: mdc-typography.get-size(body1) * 0.75;
}
.mdc-notched-outline--upgraded .mdc-floating-label--float-above {
font-size: mdc-typography.get-size(body1);
}
}
}
}

Expand Down
@@ -1,20 +1,26 @@
@use 'form-field-sizing';
@use '@material/textfield/variables' as mdc-textfield-variables;

// Mixin that can be included to override the default MDC text-field
// styles to fit our needs. See individual comments for context on why
// certain MDC styles need to be modified.
@mixin private-text-field-structure-overrides() {
// Unset the border set by MDC. We move the border (which serves as the Material Design
// text-field bottom line) into its own element. This is necessary because we want the
// bottom-line to span across the whole form-field (including prefixes and suffixes).
.mat-mdc-form-field-control {
// bottom-line to span across the whole form-field (including prefixes and suffixes). Also
// we ensure that font styles are inherited for input elements. We do this because inputs by
// default have explicit font styles from the user agent, and we set the desired font styles
// in the parent `mat-form-field` element (for better custom form-field control support).
// Note: We increase specificity here because the MDC textfield seems to override this,
// depending on the CSS order, with an affix selector joint with the input.
.mat-mdc-form-field-input-control.mat-mdc-form-field-input-control {
font: inherit;
border: none;
}

// In order to ensure proper alignment of the floating label, we reset its line-height.
// The line-height is not important as the element is absolutely positioned and only has one line
// of text.
.mat-mdc-form-field .mdc-floating-label {
.mat-mdc-form-field .mdc-floating-label.mdc-floating-label {
line-height: normal;
}

Expand All @@ -24,15 +30,16 @@
// not work for us since we support arbitrary form field controls which don't necessarily
// use an `input` element. We organize the vertical spacing on the infix container.
.mdc-text-field--no-label:not(.mdc-text-field--textarea)
.mat-mdc-form-field-control.mdc-text-field__input,
.mat-mdc-text-field-wrapper .mat-mdc-form-field-control {
.mat-mdc-form-field-input-control.mdc-text-field__input,
.mat-mdc-text-field-wrapper .mat-mdc-form-field-input-control {
height: auto;
}

// Color inputs are a special case, because setting their height to
// `auto` will collapse them. The height value is an arbitrary number
// which was extracted from the user agent styles of Chrome and Firefox.
.mat-mdc-text-field-wrapper .mat-mdc-form-field-control.mdc-text-field__input[type='color'] {
.mat-mdc-text-field-wrapper
.mat-mdc-form-field-input-control.mdc-text-field__input[type='color'] {
height: 23px;
}

Expand All @@ -57,8 +64,8 @@
[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;
padding-left: mdc-textfield-variables.$padding-horizontal;
padding-right: mdc-textfield-variables.$padding-horizontal;
}
// ...and apply the correct padding resets for RTL languages.
.mat-mdc-form-field-has-icon-suffix .mat-mdc-text-field-wrapper {
Expand Down
Expand Up @@ -10,7 +10,15 @@
@mixin private-text-field-textarea-overrides() {
// Ensures that textarea elements inside of the form-field have proper vertical spacing
// to account for the floating label. Also ensures that there is no vertical text overflow.
.mat-mdc-textarea-input {
// **Note**: Before changing this selector, make sure the `cdk-textarea-autosize` class is
// still able to override the `resize` property to `none`.
.mat-mdc-form-field-textarea-control {
// Set the vertical alignment for textareas inside form fields to be the middle. This
// ensures that textareas do not stretch the infix container vertically without having
// multiple rows of text. See: https://github.com/angular/components/pull/22089.
vertical-align: middle;
// Textareas by default also allow users to resize the textarea horizontally. This
// causes the textarea to overflow the form-field. We only allow vertical resizing.
resize: vertical;
box-sizing: border-box;
height: auto;
Expand Down
18 changes: 11 additions & 7 deletions src/material-experimental/mdc-form-field/form-field.scss
Expand Up @@ -90,13 +90,21 @@
}
}

// Scale down icons in the subscript and floating label to be the same size
// as the text.
.mat-mdc-form-field-subscript-wrapper,
.mat-mdc-form-field label {
.mat-icon {
width: 1em;
height: 1em;
font-size: inherit;
}
}

// Infix that contains the projected content (usually an input or a textarea). We ensure
// that the projected form-field control and content can stretch as needed, but we also
// apply a default infix width to make the form-field's look natural.
.mat-mdc-form-field-infix {
// Prevent extra height from being added around the textarea, which throws off the overall
// height of the form-field
line-height: 0;
flex: auto;
min-width: 0;
width: form-field-sizing.$mat-form-field-default-infix-width;
Expand All @@ -105,10 +113,6 @@
box-sizing: border-box;
}

.mat-mdc-form-field-hint-end {
order: 1;
}

// In order to make it possible for developers to disable animations for form-fields,
// we only activate the animation styles if animations are not explicitly disabled.
.mat-mdc-form-field:not(.mat-form-field-no-animations) {
Expand Down
18 changes: 12 additions & 6 deletions src/material-experimental/mdc-form-field/form-field.ts
Expand Up @@ -93,6 +93,12 @@ const DEFAULT_FLOAT_LABEL: FloatLabelType = 'auto';
*/
const FLOATING_LABEL_DEFAULT_DOCKED_TRANSFORM = `translateY(-50%)`;

/**
* Horizontal padding in pixels used by the MDC for the wrapper containing infix.
* This value is extracted from MDC's Sass variables. See `$padding-horizontal`.
*/
const WRAPPER_HORIZONTAL_PADDING = 16;

/** Container for form controls that applies Material Design styling and behavior. */
@Component({
selector: 'mat-form-field',
Expand Down Expand Up @@ -691,16 +697,16 @@ export class MatFormField implements AfterViewInit, OnDestroy, AfterContentCheck
}
const iconPrefixContainer = this._iconPrefixContainer?.nativeElement;
const textPrefixContainer = this._textPrefixContainer?.nativeElement;
const iconPrefixContainerWidth = iconPrefixContainer?.getBoundingClientRect().width ?? 0;
const textPrefixContainerWidth = textPrefixContainer?.getBoundingClientRect().width ?? 0;
// 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 ?
// 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)
// If there's an icon prefix, we subtract the default horizontal padding as we
// reset the horizontal padding in CSS too.
(iconPrefixContainer ? iconPrefixContainerWidth - WRAPPER_HORIZONTAL_PADDING : 0) +
textPrefixContainerWidth
);

// Update the transform the floating label to account for the prefix container. Note
Expand Down
4 changes: 2 additions & 2 deletions src/material-experimental/mdc-input/input.spec.ts
Expand Up @@ -858,8 +858,8 @@ describe('MatMdcInput without forms', () => {
const inFormField = fixture.nativeElement.querySelector('.inside');
const outsideFormField = fixture.nativeElement.querySelector('.outside');

expect(inFormField.classList).toContain('mat-mdc-form-field-control');
expect(outsideFormField.classList).not.toContain('mat-mdc-form-field-control');
expect(inFormField.classList).toContain('mat-mdc-form-field-input-control');
expect(outsideFormField.classList).not.toContain('mat-mdc-form-field-input-control');
}));

});
Expand Down
4 changes: 2 additions & 2 deletions src/material-experimental/mdc-input/input.ts
Expand Up @@ -27,8 +27,8 @@ import {MatInput as BaseMatInput} from '@angular/material/input';
'[class.mat-input-element]': 'false',
'[class.mat-form-field-control]': 'false',
'[class.mat-input-server]': '_isServer',
'[class.mat-mdc-textarea-input]': '_isTextarea',
'[class.mat-mdc-form-field-control]': '_isInFormField',
'[class.mat-mdc-form-field-textarea-control]': '_isInFormField && _isTextarea',
'[class.mat-mdc-form-field-input-control]': '_isInFormField',
'[class.mdc-text-field__input]': '_isInFormField',
// Native input properties that are overwritten by Angular inputs need to be synced with
// the native input element. Otherwise property bindings for those don't work.
Expand Down
2 changes: 1 addition & 1 deletion src/material/datepicker/date-range-input.ts
Expand Up @@ -249,7 +249,7 @@ export class MatDateRangeInput<D> implements MatFormFieldControl<DateRange<D>>,
if (_formField?._elementRef.nativeElement.classList.contains('mat-mdc-form-field')) {
const classList = _elementRef.nativeElement.classList;
classList.add('mat-mdc-input-element');
classList.add('mat-mdc-form-field-control');
classList.add('mat-mdc-form-field-input-control');
}

// TODO(crisbeto): remove `as any` after #18206 lands.
Expand Down
Binary file removed src/material/form-field/.form-field.md.swp
Binary file not shown.

0 comments on commit 13b24ab

Please sign in to comment.