diff --git a/src/demo-app/select/select-demo.html b/src/demo-app/select/select-demo.html index b92727f38e8..9b7061c9d63 100644 --- a/src/demo-app/select/select-demo.html +++ b/src/demo-app/select/select-demo.html @@ -84,6 +84,7 @@ Fill + Volvo Saab Mercedes @@ -93,6 +94,7 @@ Outline + volvo Saab Mercedes diff --git a/src/lib/form-field/form-field.scss b/src/lib/form-field/form-field.scss index ea19795d065..be43e14afff 100644 --- a/src/lib/form-field/form-field.scss +++ b/src/lib/form-field/form-field.scss @@ -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 { diff --git a/src/lib/input/_input-theme.scss b/src/lib/input/_input-theme.scss index 055be25d1ab..ecdd6b0e176 100644 --- a/src/lib/input/_input-theme.scss +++ b/src/lib/input/_input-theme.scss @@ -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) { diff --git a/src/lib/input/input.scss b/src/lib/input/input.scss index c0cbae872f5..ead0f296ee6 100644 --- a/src/lib/input/input.scss +++ b/src/lib/input/input.scss @@ -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; + } +} diff --git a/src/lib/input/input.spec.ts b/src/lib/input/input.spec.ts index 1031e39c527..72b8c5e3aba 100644 --- a/src/lib/input/input.spec.ts +++ b/src/lib/input/input.spec.ts @@ -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', () => { diff --git a/src/lib/input/input.ts b/src/lib/input/input.ts index db94fd81797..59835fafbbc 100644 --- a/src/lib/input/input.ts +++ b/src/lib/input/input.ts @@ -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() {