fix(form-field): incorrect arrow color for native select (#13046)

Currently the arrow for the form field's native select is done through a URL-encoded SVG set as a background image. This is inflexible, because it doesn't allow us to easily change its color, which means that we're locked into one color that doesn't account for dark themes, disabled state or whether the form field has validation errors. These changes rework the approach to use a pseudo element to render the arrow instead and to use the proper colors for the other states.
This commit is contained in:
Kristiyan Kostadinov
2018-09-25 16:10:44 +02:00
committed by Jeremy Elbourn
parent 18f46cfbbd
commit 10b83534bb
6 changed files with 113 additions and 61 deletions
+2
View File
@@ -84,6 +84,7 @@
<mat-form-field appearance="fill">
<mat-label>Fill</mat-label>
<select matNativeControl required>
<option value="" selected></option>
<option value="volvo">Volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
@@ -93,6 +94,7 @@
<mat-form-field appearance="outline">
<mat-label>Outline</mat-label>
<select matNativeControl>
<option value="" selected></option>
<option value="volvo">volvo</option>
<option value="saab">Saab</option>
<option value="mercedes">Mercedes</option>
+5
View File
@@ -217,6 +217,11 @@ $mat-form-field-default-infix-width: 180px !default;
display: block;
}
// Element that can used to reliably align content in relation to the form field control.
.mat-form-field-control-wrapper {
position: relative;
}
.mat-form-field._mat-animation-noopable {
.mat-form-field-label,
.mat-form-field-ripple {
+10 -1
View File
@@ -11,7 +11,12 @@
$warn: map-get($theme, warn);
$foreground: map-get($theme, foreground);
.mat-input-element:disabled {
.mat-form-field-type-mat-native-select .mat-form-field-infix::after {
color: mat-color($foreground, secondary-text);
}
.mat-input-element:disabled,
.mat-form-field-type-mat-native-select.mat-form-field-disabled .mat-form-field-infix::after {
color: mat-color($foreground, disabled-text);
}
@@ -31,6 +36,10 @@
.mat-form-field-invalid .mat-input-element {
caret-color: mat-color($warn);
}
.mat-form-field-type-mat-native-select.mat-form-field-invalid .mat-form-field-infix::after {
color: mat-color($warn);
}
}
@mixin mat-input-typography($config) {
+29 -19
View File
@@ -122,29 +122,14 @@ textarea.mat-input-element {
margin: -2px 0;
}
// URL-encoded Material Design select arrow SVG.
$mat-native-select-arrow-svg: '' +
'data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%2210%22%20height%3D%225%22%20' +
'viewBox%3D%227%2010%2010%205%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fs' +
'vg%22%3E%3Cpath%20fill%3D%22%230%22%20fill-rule%3D%22evenodd%22%20opacity%3D%22' +
'.54%22%20d%3D%22M7%2010l5%205%205-5z%22%2F%3E%3C%2Fsvg%3E';
// Remove the native select down arrow and replace it with material design arrow
select.mat-input-element {
-moz-appearance: none;
-webkit-appearance: none;
position: relative;
background-color: transparent;
background-repeat: no-repeat;
display: inline-flex;
box-sizing: border-box;
background-position: right center;
// Native multi-selects are rendered inline which
// means that they shouldn't have a dropdown arrow.
&:not([multiple]) {
background-image: url($mat-native-select-arrow-svg);
}
&::-ms-expand {
display: none;
@@ -156,9 +141,34 @@ select.mat-input-element {
&::-moz-focus-inner {
border: 0;
}
[dir='rtl'] & {
background-position: left center;
}
}
.mat-form-field-type-mat-native-select {
$arrow-size: 5px;
.mat-form-field-infix::after {
content: '';
width: 0;
height: 0;
border-left: $arrow-size solid transparent;
border-right: $arrow-size solid transparent;
border-top: $arrow-size solid;
position: absolute;
top: 50%;
right: 0;
margin-top: -$arrow-size / 2;
[dir='rtl'] & {
right: auto;
left: 0;
}
}
&.mat-form-field-appearance-outline .mat-form-field-infix::after {
margin-top: -$arrow-size;
}
&.mat-form-field-appearance-fill .mat-form-field-infix::after {
margin-top: -$arrow-size * 2;
}
}
+56 -39
View File
@@ -218,51 +218,51 @@ describe('MatInput without forms', () => {
const labelElement: HTMLInputElement =
fixture.debugElement.query(By.css('label')).nativeElement;
expect(inputElement.id).toBe('test-id');
expect(labelElement.getAttribute('for')).toBe('test-id');
}));
expect(inputElement.id).toBe('test-id');
expect(labelElement.getAttribute('for')).toBe('test-id');
}));
it('validates there\'s only one hint label per side', fakeAsync(() => {
let fixture = createComponent(MatInputInvalidHintTestController);
it('validates there\'s only one hint label per side', fakeAsync(() => {
let fixture = createComponent(MatInputInvalidHintTestController);
expect(() => {
try {
fixture.detectChanges();
flush();
} catch {
flush();
}
}).toThrowError(
wrappedErrorMessage(getMatFormFieldDuplicatedHintError('start')));
}));
expect(() => {
try {
fixture.detectChanges();
flush();
} catch {
flush();
}
}).toThrowError(
wrappedErrorMessage(getMatFormFieldDuplicatedHintError('start')));
}));
it('validates there\'s only one hint label per side (attribute)', fakeAsync(() => {
let fixture = createComponent(MatInputInvalidHint2TestController);
it('validates there\'s only one hint label per side (attribute)', fakeAsync(() => {
let fixture = createComponent(MatInputInvalidHint2TestController);
expect(() => {
try {
fixture.detectChanges();
flush();
} catch {
flush();
}
}).toThrowError(
wrappedErrorMessage(getMatFormFieldDuplicatedHintError('start')));
}));
expect(() => {
try {
fixture.detectChanges();
flush();
} catch {
flush();
}
}).toThrowError(
wrappedErrorMessage(getMatFormFieldDuplicatedHintError('start')));
}));
it('validates there\'s only one placeholder', fakeAsync(() => {
let fixture = createComponent(MatInputInvalidPlaceholderTestController);
it('validates there\'s only one placeholder', fakeAsync(() => {
let fixture = createComponent(MatInputInvalidPlaceholderTestController);
expect(() => {
try {
fixture.detectChanges();
flush();
} catch {
flush();
}
}).toThrowError(
wrappedErrorMessage(getMatFormFieldPlaceholderConflictError()));
}));
expect(() => {
try {
fixture.detectChanges();
flush();
} catch {
flush();
}
}).toThrowError(
wrappedErrorMessage(getMatFormFieldPlaceholderConflictError()));
}));
it('validates that matInput child is present', fakeAsync(() => {
let fixture = createComponent(MatInputMissingMatInputTestController);
@@ -463,6 +463,15 @@ describe('MatInput without forms', () => {
expect(selectEl.disabled).toBe(true);
}));
it('should add a class to the form field if it has a native select', fakeAsync(() => {
const fixture = createComponent(MatInputSelect);
fixture.detectChanges();
const formField = fixture.debugElement.query(By.css('.mat-form-field')).nativeElement;
expect(formField.classList).toContain('mat-form-field-type-mat-native-select');
}));
it('supports the required attribute as binding', fakeAsync(() => {
let fixture = createComponent(MatInputWithRequired);
fixture.detectChanges();
@@ -872,6 +881,14 @@ describe('MatInput without forms', () => {
expect(container.classList).not.toContain('mat-form-field-should-float');
});
it('should not add the native select class if the control is not a native select', () => {
const fixture = createComponent(MatInputWithId);
fixture.detectChanges();
const formField = fixture.debugElement.query(By.css('mat-form-field')).nativeElement;
expect(formField.classList).not.toContain('mat-form-field-type-mat-native-select');
});
});
describe('MatInput with forms', () => {
+11 -2
View File
@@ -232,10 +232,14 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
@Optional() @Self() @Inject(MAT_INPUT_VALUE_ACCESSOR) inputValueAccessor: any,
private _autofillMonitor: AutofillMonitor,
ngZone: NgZone) {
super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);
const element = this._elementRef.nativeElement;
// If no input value accessor was explicitly specified, use the element as the input value
// accessor.
this._inputValueAccessor = inputValueAccessor || this._elementRef.nativeElement;
this._inputValueAccessor = inputValueAccessor || element;
this._previousNativeValue = this.value;
@@ -262,7 +266,12 @@ export class MatInput extends _MatInputMixinBase implements MatFormFieldControl<
}
this._isServer = !this._platform.isBrowser;
this._isNativeSelect = this._elementRef.nativeElement.nodeName.toLowerCase() === 'select';
this._isNativeSelect = element.nodeName.toLowerCase() === 'select';
if (this._isNativeSelect) {
this.controlType = (element as HTMLSelectElement).multiple ? 'mat-native-select-multiple' :
'mat-native-select';
}
}
ngOnInit() {