Skip to content

Commit 6241113

Browse files
author
Vivian Hu
committed
feat(input): add native select to be form input.
1 parent 08569b6 commit 6241113

File tree

17 files changed

+455
-27
lines changed

17 files changed

+455
-27
lines changed

src/demo-app/select/select-demo.html

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,126 @@
1+
2+
3+
<mat-card class="demo-card demo-basic">
4+
<mat-toolbar color="primary">Native Select</mat-toolbar>
5+
<mat-card-content>
6+
<form>
7+
<h4>Basic</h4>
8+
<mat-form-field class="demo-full-width">
9+
<mat-label>Select your car</mat-label>
10+
<select matInput id="mySelectId">
11+
<option value="" disabled selected></option>
12+
<option value="volvo">Volvo</option>
13+
<option value="saab" disabled>Saab</option>
14+
<option value="mercedes">Mercedes</option>
15+
<option value="audi">Audi</option>
16+
</select>
17+
</mat-form-field>
18+
<h4>Disabled and required</h4>
19+
<mat-form-field class="demo-full-width">
20+
<mat-label>Select your car (disabled)</mat-label>
21+
<select matInput disabled required>
22+
<option value="volvo">Volvo</option>
23+
<option value="saab">Saab</option>
24+
<option value="mercedes">Mercedes</option>
25+
<option value="audi">Audi</option>
26+
</select>
27+
</mat-form-field>
28+
<h4>Looks</h4>
29+
<mat-form-field appearance="legacy">
30+
<mat-label>Legacy</mat-label>
31+
<select matInput required>
32+
<option value="volvo">Volvo</option>
33+
<option value="saab">Saab</option>
34+
<option value="mercedes">Mercedes</option>
35+
<option value="audi">Audi</option>
36+
</select>
37+
</mat-form-field>
38+
<mat-form-field appearance="standard">
39+
<mat-label>Standard</mat-label>
40+
<select matInput required>
41+
<option value="volvo">Volvo</option>
42+
<option value="saab">Saab</option>
43+
<option value="mercedes">Mercedes</option>
44+
<option value="audi">Audi</option>
45+
</select>
46+
</mat-form-field>
47+
<mat-form-field appearance="fill">
48+
<mat-label>Fill</mat-label>
49+
<select matInput required>
50+
<option value="volvo">Volvo</option>
51+
<option value="saab">Saab</option>
52+
<option value="mercedes">Mercedes</option>
53+
<option value="audi">Audi</option>
54+
</select>
55+
</mat-form-field>
56+
<mat-form-field appearance="outline">
57+
<mat-label>Outline</mat-label>
58+
<select matInput>
59+
<option value="volvo">volvo</option>
60+
<option value="saab">Saab</option>
61+
<option value="mercedes">Mercedes</option>
62+
<option value="audi">Audi</option>
63+
</select>
64+
</mat-form-field>
65+
<h4>Option group</h4>
66+
<mat-form-field>
67+
<select matInput>
68+
<optgroup label="Swedish Cars">
69+
<option value="volvo">volvo</option>
70+
<option value="saab">Saab</option>
71+
</optgroup>
72+
<optgroup label="German Cars">
73+
<option value="mercedes">Mercedes</option>
74+
<option value="audi">Audi</option>
75+
</optgroup>
76+
</select>
77+
</mat-form-field>
78+
<h4>Place holder</h4>
79+
<mat-form-field class="demo-full-width">
80+
<select matInput placeholder="place holder">
81+
<option value="" disabled selected></option>
82+
<option value="volvo">Volvo</option>
83+
<option value="saab" disabled>Saab</option>
84+
<option value="mercedes">Mercedes</option>
85+
<option value="audi">Audi</option>
86+
</select>
87+
</mat-form-field>
88+
<h4>Error message, hint, form sumbit</h4>
89+
<mat-form-field class="demo-full-width">
90+
<mat-label>Select your car (required)</mat-label>
91+
<select matInput required [formControl]="selectFormControl">
92+
<option label="--select something --"></option>
93+
<option value="saab">Saab</option>
94+
<option value="mercedes">Mercedes</option>
95+
<option value="audi">Audi</option>
96+
</select>
97+
<mat-error *ngIf="selectFormControl.hasError('required')">
98+
This field is required
99+
</mat-error>
100+
<mat-hint>You can pick up your favorite car here</mat-hint>
101+
</mat-form-field>
102+
103+
<h4>Error message with errorStateMatcher</h4>
104+
<mat-form-field class="demo-full-width">
105+
<mat-label>Select your car</mat-label>
106+
<select matInput required [formControl]="selectFormControl" [errorStateMatcher]="matcher">
107+
<option label="--select something --"></option>
108+
<option value="saab">Saab</option>
109+
<option value="mercedes">Mercedes</option>
110+
<option value="audi">Audi</option>
111+
</select>
112+
<mat-error *ngIf="selectFormControl.hasError('required')">
113+
This field is required
114+
</mat-error>
115+
<mat-hint>You can pick up your favorite car here</mat-hint>
116+
</mat-form-field>
117+
<button color="primary" mat-raised-button>Submit</button>
118+
</form>
119+
</mat-card-content>
120+
</mat-card>
121+
122+
123+
<mat-toolbar color="primary">mat-select</mat-toolbar>
1124
Space above cards: <input type="number" [formControl]="topHeightCtrl">
2125
<button mat-button (click)="showSelect=!showSelect">SHOW SELECT</button>
3126
<div [style.height.px]="topHeightCtrl.value"></div>
@@ -185,3 +308,4 @@
185308

186309
</div>
187310
<div style="height: 500px">This div is for testing scrolled selects.</div>
311+

src/demo-app/select/select-demo.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,18 @@
77
*/
88

99
import {Component} from '@angular/core';
10-
import {FormControl} from '@angular/forms';
11-
import {MatSelectChange} from '@angular/material';
10+
import {FormControl, Validators} from '@angular/forms';
11+
import {ErrorStateMatcher, MatSelectChange} from '@angular/material';
1212

13+
/** Error any time control is invalid */
14+
export class MyErrorStateMatcher implements ErrorStateMatcher {
15+
isErrorState(control: FormControl | null): boolean {
16+
if (control) {
17+
return control.invalid;
18+
}
19+
return false;
20+
}
21+
}
1322

1423
@Component({
1524
moduleId: module.id,
@@ -36,6 +45,7 @@ export class SelectDemo {
3645
drinksTheme = 'primary';
3746
pokemonTheme = 'primary';
3847
compareByValue = true;
48+
selectFormControl = new FormControl('', Validators.required);
3949

4050
foods = [
4151
{value: null, viewValue: 'None'},
@@ -135,4 +145,6 @@ export class SelectDemo {
135145
compareByReference(o1: any, o2: any) {
136146
return o1 === o2;
137147
}
148+
149+
matcher = new MyErrorStateMatcher();
138150
}

src/lib/form-field/form-field.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ In this document, "form field" refers to the wrapper component `<mat-form-field>
88

99
The following Angular Material components are designed to work inside a `<mat-form-field>`:
1010
* [`<input matInput>` &amp; `<textarea matInput>`](https://material.angular.io/components/input/overview)
11+
* [`<select matInput>`](https://material.angular.io/components/select/overview)
1112
* [`<mat-select>`](https://material.angular.io/components/select/overview)
1213
* [`<mat-chip-list>`](https://material.angular.io/components/chips/overview)
1314

@@ -41,7 +42,8 @@ want a floating label, add a `<mat-label>` to the `mat-form-field`.
4142
### Floating label
4243

4344
The floating label is a text label displayed on top of the form field control when
44-
the control does not contain any text. By default, when text is present the floating label
45+
the control does not contain any text or when `<select matInput>` does not show any option text.
46+
By default, when text is present the floating label
4547
floats above the form field control. The label for a form field can be specified by adding a
4648
`mat-label` element.
4749

src/lib/input/input.scss

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,21 @@ textarea.mat-input-element {
121121
padding: 2px 0;
122122
margin: -2px 0;
123123
}
124+
125+
// Remove select button from IE11
126+
select::-ms-expand {
127+
display: none;
128+
}
129+
130+
select[matInput] {
131+
-moz-appearance: none;
132+
-webkit-appearance: none;
133+
position: relative;
134+
background-color: transparent;
135+
background-image: url('data:image/svg+xml;charset=utf8,%3Csvg%20width%3D%2210%22%20height%3D%225%22%20viewBox%3D%227%2010%2010%205%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%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');
136+
background-repeat: no-repeat;
137+
display: inline-flex;
138+
box-sizing: border-box;
139+
background-position: right center;
140+
}
141+

src/lib/input/input.spec.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,26 @@ describe('MatInput without forms', () => {
438438
expect(inputEl.disabled).toBe(true);
439439
}));
440440

441+
it('supports the disabled attribute as binding for select', fakeAsync(() => {
442+
const fixture = createComponent(MatInputSelect);
443+
fixture.detectChanges();
444+
445+
const formFieldEl =
446+
fixture.debugElement.query(By.css('.mat-form-field')).nativeElement;
447+
const inputEl = fixture.debugElement.query(By.css('select')).nativeElement;
448+
449+
expect(formFieldEl.classList.contains('mat-form-field-disabled'))
450+
.toBe(false, `Expected form field not to start out disabled.`);
451+
expect(inputEl.disabled).toBe(false);
452+
453+
fixture.componentInstance.disabled = true;
454+
fixture.detectChanges();
455+
456+
expect(formFieldEl.classList.contains('mat-form-field-disabled'))
457+
.toBe(true, `Expected form field to look disabled after property is set.`);
458+
expect(inputEl.disabled).toBe(true);
459+
}));
460+
441461
it('supports the required attribute as binding', fakeAsync(() => {
442462
let fixture = createComponent(MatInputWithRequired);
443463
fixture.detectChanges();
@@ -452,6 +472,20 @@ describe('MatInput without forms', () => {
452472
expect(inputEl.required).toBe(true);
453473
}));
454474

475+
it('supports the required attribute as binding for select', fakeAsync(() => {
476+
const fixture = createComponent(MatInputSelect);
477+
fixture.detectChanges();
478+
479+
const inputEl = fixture.debugElement.query(By.css('select')).nativeElement;
480+
481+
expect(inputEl.required).toBe(false);
482+
483+
fixture.componentInstance.required = true;
484+
fixture.detectChanges();
485+
486+
expect(inputEl.required).toBe(true);
487+
}));
488+
455489
it('supports the type attribute as binding', fakeAsync(() => {
456490
let fixture = createComponent(MatInputWithType);
457491
fixture.detectChanges();
@@ -474,6 +508,14 @@ describe('MatInput without forms', () => {
474508
expect(textarea).not.toBeNull();
475509
}));
476510

511+
it('supports select', fakeAsync(() => {
512+
const fixture = createComponent(MatInputSelect);
513+
fixture.detectChanges();
514+
515+
const nativeSelect: HTMLTextAreaElement = fixture.nativeElement.querySelector('select');
516+
expect(nativeSelect).not.toBeNull();
517+
}));
518+
477519
it('sets the aria-describedby when a hintLabel is set', fakeAsync(() => {
478520
let fixture = createComponent(MatInputHintLabelTestController);
479521

@@ -572,6 +614,41 @@ describe('MatInput without forms', () => {
572614
expect(formFieldEl.classList).toContain('mat-form-field-should-float');
573615
}));
574616

617+
it('should floating labels when select has value', fakeAsync(() => {
618+
const fixture = createComponent(MatInputSelect);
619+
fixture.detectChanges();
620+
621+
const formFieldEl = fixture.debugElement.query(By.css('.mat-form-field')).nativeElement;
622+
expect(formFieldEl.classList).toContain('mat-form-field-should-float');
623+
}));
624+
625+
it('should not floating labels when select has no value, no option label, ' +
626+
'no option innerHtml', fakeAsync(() => {
627+
const fixture = createComponent(MatInputSelectWithNoLabelNoValue);
628+
fixture.detectChanges();
629+
630+
const formFieldEl = fixture.debugElement.query(By.css('.mat-form-field')).nativeElement;
631+
expect(formFieldEl.classList).not.toContain('mat-form-field-should-float');
632+
}));
633+
634+
it('should floating labels when select has no value but has option label',
635+
fakeAsync(() => {
636+
const fixture = createComponent(MatInputSelectWithLabel);
637+
fixture.detectChanges();
638+
639+
const formFieldEl = fixture.debugElement.query(By.css('.mat-form-field')).nativeElement;
640+
expect(formFieldEl.classList).toContain('mat-form-field-should-float');
641+
}));
642+
643+
it('should floating labels when select has no value but has option innerHTML',
644+
fakeAsync(() => {
645+
const fixture = createComponent(MatInputSelectWithInnerHtml);
646+
fixture.detectChanges();
647+
648+
const formFieldEl = fixture.debugElement.query(By.css('.mat-form-field'))
649+
.nativeElement;
650+
expect(formFieldEl.classList).toContain('mat-form-field-should-float');
651+
}));
575652

576653
it('should never float the label when floatLabel is set to false', fakeAsync(() => {
577654
let fixture = createComponent(MatInputWithDynamicLabel);
@@ -1662,3 +1739,58 @@ class AutosizeTextareaInATab {}
16621739
`
16631740
})
16641741
class AutosizeTextareaInAStep {}
1742+
1743+
@Component({
1744+
template: `
1745+
<mat-form-field>
1746+
<select matInput id="test-id" [disabled]="disabled" [required]="required">
1747+
<option value="volvo">Volvo</option>
1748+
<option value="saab">Saab</option>
1749+
<option value="mercedes">Mercedes</option>
1750+
<option value="audi">Audi</option>
1751+
</select>
1752+
</mat-form-field>`
1753+
})
1754+
class MatInputSelect {
1755+
disabled: boolean;
1756+
required: boolean;
1757+
}
1758+
1759+
@Component({
1760+
template: `
1761+
<mat-form-field>
1762+
<select matInput>
1763+
<option value="" disabled selected></option>
1764+
<option value="saab">Saab</option>
1765+
<option value="mercedes">Mercedes</option>
1766+
<option value="audi">Audi</option>
1767+
</select>
1768+
</mat-form-field>`
1769+
})
1770+
class MatInputSelectWithNoLabelNoValue {}
1771+
1772+
@Component({
1773+
template: `
1774+
<mat-form-field>
1775+
<select matInput>
1776+
<option value="" label="select a car"></option>
1777+
<option value="saab">Saab</option>
1778+
<option value="mercedes">Mercedes</option>
1779+
<option value="audi">Audi</option>
1780+
</select>
1781+
</mat-form-field>`
1782+
})
1783+
class MatInputSelectWithLabel {};
1784+
1785+
@Component({
1786+
template: `
1787+
<mat-form-field>
1788+
<select matInput>
1789+
<option value="">select a car</option>
1790+
<option value="saab">Saab</option>
1791+
<option value="mercedes">Mercedes</option>
1792+
<option value="audi">Audi</option>
1793+
</select>
1794+
</mat-form-field>`
1795+
})
1796+
class MatInputSelectWithInnerHtml {};

0 commit comments

Comments
 (0)