Skip to content

Commit 5df18ab

Browse files
committed
fix(material/input): only apply styling when inside a form field
Currently `mat-form-field` brings in the styles for `MatInput`. but the problem is that they target a class that is applied even to inputs that aren't inside a form field. These changes aim to prevent CSS from bleeding out by only styling inputs inside a `mat-form-field`. Fixes #21871.
1 parent 70ff528 commit 5df18ab

File tree

17 files changed

+120
-34
lines changed

17 files changed

+120
-34
lines changed

src/material-experimental/mdc-chips/chip-input.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ describe('MDC-based MatChipInput', () => {
130130

131131
it('should set input styling classes', () => {
132132
expect(inputNativeElement.classList).toContain('mat-mdc-input-element');
133+
expect(inputNativeElement.classList).toContain('mat-mdc-form-field-control');
133134
expect(inputNativeElement.classList).toContain('mat-mdc-chip-input');
134135
expect(inputNativeElement.classList).toContain('mdc-text-field__input');
135136
});

src/material-experimental/mdc-chips/chip-input.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,17 @@
88

99
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
1010
import {hasModifierKey, TAB} from '@angular/cdk/keycodes';
11-
import {Directive, ElementRef, EventEmitter, Inject, Input, OnChanges, Output} from '@angular/core';
11+
import {
12+
Directive,
13+
ElementRef,
14+
EventEmitter,
15+
Inject,
16+
Input,
17+
OnChanges,
18+
Optional,
19+
Output,
20+
} from '@angular/core';
21+
import {MatFormField, MAT_FORM_FIELD} from '@angular/material-experimental/mdc-form-field';
1222
import {MatChipsDefaultOptions, MAT_CHIPS_DEFAULT_OPTIONS} from './chip-default-options';
1323
import {MatChipGrid} from './chip-grid';
1424
import {MatChipTextControl} from './chip-text-control';
@@ -104,9 +114,14 @@ export class MatChipInput implements MatChipTextControl, OnChanges {
104114

105115
constructor(
106116
protected _elementRef: ElementRef<HTMLInputElement>,
107-
@Inject(MAT_CHIPS_DEFAULT_OPTIONS) private _defaultOptions: MatChipsDefaultOptions) {
108-
this._inputElement = this._elementRef.nativeElement as HTMLInputElement;
109-
}
117+
@Inject(MAT_CHIPS_DEFAULT_OPTIONS) private _defaultOptions: MatChipsDefaultOptions,
118+
@Optional() @Inject(MAT_FORM_FIELD) formField?: MatFormField) {
119+
this._inputElement = this._elementRef.nativeElement as HTMLInputElement;
120+
121+
if (formField) {
122+
this._inputElement.classList.add('mat-form-field-control');
123+
}
124+
}
110125

111126
ngOnChanges() {
112127
this._chipGrid.stateChanges.next();

src/material-experimental/mdc-form-field/_form-field-native-select.scss

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ $mat-form-field-select-horizontal-end-padding: $mat-form-field-select-arrow-widt
1414
// Remove the native select down arrow and ensure that the native appearance
1515
// does not conflict with the form-field. e.g. Focus indication of the native
1616
// select is undesired since we handle focus as part of the form-field.
17-
select.mat-mdc-input-element {
17+
select.mat-mdc-form-field-control {
1818
-moz-appearance: none;
1919
-webkit-appearance: none;
2020
background-color: transparent;
@@ -80,7 +80,7 @@ $mat-form-field-select-horizontal-end-padding: $mat-form-field-select-arrow-widt
8080

8181
// Add padding on the end of the native select so that the content does not
8282
// overlap with the Material Design arrow.
83-
.mat-mdc-input-element {
83+
.mat-mdc-form-field-control {
8484
padding-right: $mat-form-field-select-horizontal-end-padding;
8585
[dir='rtl'] & {
8686
padding-right: 0;
@@ -91,7 +91,7 @@ $mat-form-field-select-horizontal-end-padding: $mat-form-field-select-arrow-widt
9191
}
9292

9393
@mixin mat-mdc-private-form-field-native-select-color($config) {
94-
select.mat-mdc-input-element {
94+
select.mat-mdc-form-field-control {
9595
// On dark themes we set the native `select` color to some shade of white,
9696
// however the color propagates to all of the `option` elements, which are
9797
// always on a white background inside the dropdown, causing them to blend in.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070

7171
// MDC uses the `subtitle1` level for the input label and value, but the spec shows `body1` as
7272
// the correct level.
73-
.mat-mdc-input-element,
73+
.mat-mdc-form-field-control,
7474
.mat-mdc-form-field label,
7575
.mat-mdc-form-field-prefix,
7676
.mat-mdc-form-field-suffix {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// Unset the border set by MDC. We move the border (which serves as the Material Design
1111
// text-field bottom line) into its own element. This is necessary because we want the
1212
// bottom-line to span across the whole form-field (including prefixes and suffixes).
13-
.mat-mdc-input-element {
13+
.mat-mdc-form-field-control {
1414
border: none;
1515
}
1616

@@ -27,8 +27,8 @@
2727
// not work for us since we support arbitrary form field controls which don't necessarily
2828
// use an `input` element. We organize the vertical spacing on the infix container.
2929
.mdc-text-field--no-label:not(.mdc-text-field--textarea)
30-
.mat-mdc-input-element.mdc-text-field__input,
31-
.mat-mdc-text-field-wrapper .mat-mdc-input-element {
30+
.mat-mdc-form-field-control.mdc-text-field__input,
31+
.mat-mdc-text-field-wrapper .mat-mdc-form-field-control {
3232
height: auto;
3333
}
3434

src/material-experimental/mdc-input/input.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,17 @@ describe('MatMdcInput without forms', () => {
889889
expect(formField.classList).toContain('mat-warn');
890890
}));
891891

892+
it('should set a class on the input depending on whether it is in a form field', fakeAsync(() => {
893+
const fixture = createComponent(MatInputInsideOutsideFormField);
894+
fixture.detectChanges();
895+
896+
const inFormField = fixture.nativeElement.querySelector('.inside');
897+
const outsideFormField = fixture.nativeElement.querySelector('.outside');
898+
899+
expect(inFormField.classList).toContain('mat-mdc-form-field-control');
900+
expect(outsideFormField.classList).not.toContain('mat-mdc-form-field-control');
901+
}));
902+
892903
});
893904

894905
describe('MatMdcInput with forms', () => {
@@ -1786,3 +1797,15 @@ class CustomMatInputAccessor {
17861797
class MatInputWithColor {
17871798
color: ThemePalette;
17881799
}
1800+
1801+
1802+
@Component({
1803+
template: `
1804+
<mat-form-field>
1805+
<input class="inside" matNativeControl>
1806+
</mat-form-field>
1807+
1808+
<input class="outside" matNativeControl>
1809+
`
1810+
})
1811+
class MatInputInsideOutsideFormField {}

src/material-experimental/mdc-input/input.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@ import {MatInput as BaseMatInput} from '@angular/material/input';
1919
input[matNativeControl], textarea[matNativeControl]`,
2020
exportAs: 'matInput',
2121
host: {
22-
'class': 'mat-mdc-input-element mdc-text-field__input',
23-
// The BaseMatInput parent class adds `mat-input-element` and `mat-form-field-autofill-control`
24-
// to the CSS classlist, but this should not be added for this MDC equivalent input.
22+
'class': 'mat-mdc-input-element',
23+
// The BaseMatInput parent class adds `mat-input-element`, `mat-form-field-control` and
24+
// `mat-form-field-autofill-control` to the CSS class list, but this should not be added for
25+
// this MDC equivalent input.
2526
'[class.mat-form-field-autofill-control]': 'false',
2627
'[class.mat-input-element]': 'false',
28+
'[class.mat-form-field-control]': 'false',
2729
'[class.mat-input-server]': '_isServer',
2830
'[class.mat-mdc-textarea-input]': '_isTextarea',
31+
'[class.mat-mdc-form-field-control]': '_isInFormField',
32+
'[class.mdc-text-field__input]': '_isInFormField',
2933
// Native input properties that are overwritten by Angular inputs need to be synced with
3034
// the native input element. Otherwise property bindings for those don't work.
3135
'[id]': 'id',

src/material/chips/chip-input.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ describe('MatChipInput', () => {
141141

142142
it('should set input styling classes', () => {
143143
expect(inputNativeElement.classList).toContain('mat-input-element');
144+
expect(inputNativeElement.classList).toContain('mat-form-field-control');
144145
expect(inputNativeElement.classList).toContain('mat-chip-input');
145146
});
146147

src/material/chips/chip-input.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,17 @@
88

99
import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion';
1010
import {hasModifierKey, TAB} from '@angular/cdk/keycodes';
11-
import {Directive, ElementRef, EventEmitter, Inject, Input, OnChanges, Output} from '@angular/core';
11+
import {
12+
Directive,
13+
ElementRef,
14+
EventEmitter,
15+
Inject,
16+
Input,
17+
OnChanges,
18+
Optional,
19+
Output,
20+
} from '@angular/core';
21+
import {MatFormField, MAT_FORM_FIELD} from '@angular/material/form-field';
1222
import {MatChipsDefaultOptions, MAT_CHIPS_DEFAULT_OPTIONS} from './chip-default-options';
1323
import {MatChipList} from './chip-list';
1424
import {MatChipTextControl} from './chip-text-control';
@@ -101,8 +111,13 @@ export class MatChipInput implements MatChipTextControl, OnChanges {
101111

102112
constructor(
103113
protected _elementRef: ElementRef<HTMLInputElement>,
104-
@Inject(MAT_CHIPS_DEFAULT_OPTIONS) private _defaultOptions: MatChipsDefaultOptions) {
114+
@Inject(MAT_CHIPS_DEFAULT_OPTIONS) private _defaultOptions: MatChipsDefaultOptions,
115+
@Optional() @Inject(MAT_FORM_FIELD) formField?: MatFormField) {
105116
this._inputElement = this._elementRef.nativeElement as HTMLInputElement;
117+
118+
if (formField) {
119+
this._inputElement.classList.add('mat-form-field-control');
120+
}
106121
}
107122

108123
ngOnChanges() {

src/material/chips/chips.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ $mat-chip-remove-size: 18px;
194194
align-items: center;
195195
margin: -$mat-chips-chip-margin;
196196

197-
input.mat-input-element,
197+
input.mat-form-field-control,
198198
.mat-standard-chip {
199199
margin: $mat-chips-chip-margin;
200200
}

src/material/form-field/form-field-input.scss

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
@import '../../cdk/a11y/a11y';
44

55
// The Input element proper.
6-
.mat-input-element {
6+
.mat-form-field-control {
77
// Font needs to be inherited, because by default <input> has a system font.
88
font: inherit;
99

@@ -133,7 +133,7 @@
133133
}
134134

135135
// Prevents IE from always adding a scrollbar by default.
136-
textarea.mat-input-element {
136+
textarea.mat-form-field-control {
137137
// Only allow resizing along the Y axis.
138138
resize: vertical;
139139
overflow: auto;
@@ -143,15 +143,15 @@ textarea.mat-input-element {
143143
}
144144
}
145145

146-
textarea.mat-input-element {
146+
textarea.mat-form-field-control {
147147
// The 2px padding prevents scrollbars from appearing on Chrome even when they aren't needed.
148148
// We also add a negative margin to negate the effect of the padding on the layout.
149149
padding: 2px 0;
150150
margin: -2px 0;
151151
}
152152

153153
// Remove the native select down arrow and replace it with material design arrow
154-
select.mat-input-element {
154+
select.mat-form-field-control {
155155
-moz-appearance: none;
156156
-webkit-appearance: none;
157157
position: relative;
@@ -166,7 +166,7 @@ select.mat-input-element {
166166
display: none;
167167
}
168168

169-
// The `outline: none` from `.mat-input-element` works on all browsers, however Firefox also
169+
// The `outline: none` from `.mat-form-field-control` works on all browsers, however Firefox also
170170
// adds a special `focus-inner` which we have to disable explicitly. See:
171171
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#Firefox
172172
&::-moz-focus-inner {
@@ -222,7 +222,7 @@ select.mat-input-element {
222222
}
223223
}
224224

225-
.mat-input-element {
225+
.mat-form-field-control {
226226
// The arrow is 2 * $arrow-size wide and we add one more width for some spacing.
227227
$padding: $arrow-size * 3;
228228
padding-right: $padding;

src/material/input/_input-theme.scss

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,12 @@
1616
color: mat-color($foreground, secondary-text);
1717
}
1818

19-
.mat-input-element:disabled,
19+
.mat-form-field-control:disabled,
2020
.mat-form-field-type-mat-native-select.mat-form-field-disabled .mat-form-field-infix::after {
2121
color: mat-color($foreground, disabled-text);
2222
}
2323

24-
.mat-input-element {
24+
.mat-form-field-control {
2525
caret-color: mat-color($primary, text);
2626

2727
@include input-placeholder {
@@ -44,12 +44,12 @@
4444
}
4545
}
4646

47-
.mat-form-field.mat-accent .mat-input-element {
47+
.mat-form-field.mat-accent .mat-form-field-control {
4848
caret-color: mat-color($accent, text);
4949
}
5050

51-
.mat-form-field.mat-warn .mat-input-element,
52-
.mat-form-field-invalid .mat-input-element {
51+
.mat-form-field.mat-warn .mat-form-field-control,
52+
.mat-form-field-invalid .mat-form-field-control {
5353
caret-color: mat-color($warn, text);
5454
}
5555

@@ -69,7 +69,7 @@
6969

7070
// <input> elements seem to have their height set slightly too large on Safari causing the text to
7171
// be misaligned w.r.t. the placeholder. Adding this margin corrects it.
72-
input.mat-input-element {
72+
input.mat-form-field-control {
7373
margin-top: -$line-spacing * 1em;
7474
}
7575
}

src/material/input/input.spec.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,17 @@ describe('MatInput without forms', () => {
10601060
expect(formField.classList).toContain('mat-warn');
10611061
}));
10621062

1063+
it('should set a class on the input depending on whether it is in a form field', fakeAsync(() => {
1064+
const fixture = createComponent(MatInputInsideOutsideFormField);
1065+
fixture.detectChanges();
1066+
1067+
const inFormField = fixture.nativeElement.querySelector('.inside');
1068+
const outsideFormField = fixture.nativeElement.querySelector('.outside');
1069+
1070+
expect(inFormField.classList).toContain('mat-form-field-control');
1071+
expect(outsideFormField.classList).not.toContain('mat-form-field-control');
1072+
}));
1073+
10631074
});
10641075

10651076
describe('MatInput with forms', () => {
@@ -2381,3 +2392,15 @@ class MatInputWithAnotherNgIf {
23812392
class MatInputWithColor {
23822393
color: ThemePalette;
23832394
}
2395+
2396+
2397+
@Component({
2398+
template: `
2399+
<mat-form-field>
2400+
<input class="inside" matNativeControl>
2401+
</mat-form-field>
2402+
2403+
<input class="outside" matNativeControl>
2404+
`
2405+
})
2406+
class MatInputInsideOutsideFormField {}

src/material/input/input.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ const _MatInputMixinBase: CanUpdateErrorStateCtor & typeof MatInputBase =
7474
*/
7575
'class': 'mat-input-element mat-form-field-autofill-control',
7676
'[class.mat-input-server]': '_isServer',
77+
'[class.mat-form-field-control]': '_isInFormField',
7778
// Native input properties that are overwritten by Angular inputs need to be synced with
7879
// the native input element. Otherwise property bindings for those don't work.
7980
'[attr.id]': 'id',
@@ -107,6 +108,9 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
107108
/** Whether the component is a textarea. */
108109
readonly _isTextarea: boolean;
109110

111+
/** Whether the component is placed inside of a form field. */
112+
readonly _isInFormField: boolean;
113+
110114
/**
111115
* Implemented as part of MatFormFieldControl.
112116
* @docs-private
@@ -242,8 +246,6 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
242246
@Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR) inputValueAccessor: any,
243247
private _autofillMonitor: AutofillMonitor,
244248
ngZone: NgZone,
245-
// TODO: Remove this once the legacy appearance has been removed. We only need
246-
// to inject the form-field for determining whether the placeholder has been promoted.
247249
@Optional() @Inject(MAT_FORM_FIELD) private _formField?: MatFormField) {
248250

249251
super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);
@@ -282,6 +284,7 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
282284
this._isServer = !this._platform.isBrowser;
283285
this._isNativeSelect = nodeName === 'select';
284286
this._isTextarea = nodeName === 'textarea';
287+
this._isInFormField = !!_formField;
285288

286289
if (this._isNativeSelect) {
287290
this.controlType = (element as HTMLSelectElement).multiple ? 'mat-native-select-multiple' :

src/material/toolbar/_toolbar-theme.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
color: inherit;
3535
}
3636

37-
.mat-input-element {
37+
.mat-form-field-control {
3838
caret-color: currentColor;
3939
}
4040
}

tools/public_api_guard/material/chips.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ export declare class MatChipInput implements MatChipTextControl, OnChanges {
8686
id: string;
8787
placeholder: string;
8888
separatorKeyCodes: readonly number[] | ReadonlySet<number>;
89-
constructor(_elementRef: ElementRef<HTMLInputElement>, _defaultOptions: MatChipsDefaultOptions);
89+
constructor(_elementRef: ElementRef<HTMLInputElement>, _defaultOptions: MatChipsDefaultOptions, formField?: MatFormField);
9090
_blur(): void;
9191
_emitChipEnd(event?: KeyboardEvent): void;
9292
_focus(): void;
@@ -97,7 +97,7 @@ export declare class MatChipInput implements MatChipTextControl, OnChanges {
9797
static ngAcceptInputType_addOnBlur: BooleanInput;
9898
static ngAcceptInputType_disabled: BooleanInput;
9999
static ɵdir: i0.ɵɵDirectiveDefWithMeta<MatChipInput, "input[matChipInputFor]", ["matChipInput", "matChipInputFor"], { "chipList": "matChipInputFor"; "addOnBlur": "matChipInputAddOnBlur"; "separatorKeyCodes": "matChipInputSeparatorKeyCodes"; "placeholder": "placeholder"; "id": "id"; "disabled": "disabled"; }, { "chipEnd": "matChipInputTokenEnd"; }, never>;
100-
static ɵfac: i0.ɵɵFactoryDef<MatChipInput, never>;
100+
static ɵfac: i0.ɵɵFactoryDef<MatChipInput, [null, null, { optional: true; }]>;
101101
}
102102

103103
export interface MatChipInputEvent {

0 commit comments

Comments
 (0)