Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(material-experimental/mdc-autocomplete): implement MDC-based mat…
…-autocomplete (#20247) * Moves all of the autocomplete logic into base classes so that it can be reused between the standard and MDC components. * Re-implements `mat-autocomplete` using the logic from the existing one and the styling from MDC. The MDC-based autocomplete behaves identically to the existing one, with the only minor difference being that MDC one fixes a long-standing issue where we expect a hardcoded height for each of the options. It was easier to fix the bug and add logic to support arbitrary option heights than to add more logic to account for MDC's styles.
- Loading branch information
Showing
34 changed files
with
3,988 additions
and
194 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
load("//tools:defaults.bzl", "ng_module", "sass_binary") | ||
|
||
package(default_visibility = ["//visibility:public"]) | ||
|
||
ng_module( | ||
name = "mdc-autocomplete", | ||
srcs = glob(["**/*.ts"]), | ||
assets = [ | ||
"mdc-autocomplete-demo.html", | ||
":mdc_autocomplete_demo_scss", | ||
], | ||
deps = [ | ||
"//src/material-experimental/mdc-autocomplete", | ||
"//src/material-experimental/mdc-button", | ||
"//src/material-experimental/mdc-card", | ||
"//src/material-experimental/mdc-form-field", | ||
"//src/material-experimental/mdc-input", | ||
"@npm//@angular/forms", | ||
"@npm//@angular/router", | ||
], | ||
) | ||
|
||
sass_binary( | ||
name = "mdc_autocomplete_demo_scss", | ||
src = "mdc-autocomplete-demo.scss", | ||
) |
35 changes: 35 additions & 0 deletions
35
src/dev-app/mdc-autocomplete/mdc-autocomplete-demo-module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {CommonModule} from '@angular/common'; | ||
import {NgModule} from '@angular/core'; | ||
import {FormsModule, ReactiveFormsModule} from '@angular/forms'; | ||
import {MatAutocompleteModule} from '@angular/material-experimental/mdc-autocomplete'; | ||
import {MatButtonModule} from '@angular/material-experimental/mdc-button'; | ||
import {MatCardModule} from '@angular/material-experimental/mdc-card'; | ||
import {MatFormFieldModule} from '@angular/material-experimental/mdc-form-field'; | ||
import {MatInputModule} from '@angular/material-experimental/mdc-input'; | ||
import {RouterModule} from '@angular/router'; | ||
import {MdcAutocompleteDemo} from './mdc-autocomplete-demo'; | ||
|
||
@NgModule({ | ||
imports: [ | ||
CommonModule, | ||
FormsModule, | ||
MatAutocompleteModule, | ||
MatButtonModule, | ||
MatCardModule, | ||
MatFormFieldModule, | ||
MatInputModule, | ||
ReactiveFormsModule, | ||
RouterModule.forChild([{path: '', component: MdcAutocompleteDemo}]), | ||
], | ||
declarations: [MdcAutocompleteDemo], | ||
}) | ||
export class MdcAutocompleteDemoModule { | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
Space above cards: <input type="number" [formControl]="topHeightCtrl"> | ||
<div [style.height.px]="topHeightCtrl.value"></div> | ||
<div class="demo-autocomplete"> | ||
<mat-card *ngIf="(reactiveStates | async) as tempStates"> | ||
Reactive length: {{ tempStates?.length }} | ||
<div>Reactive value: {{ stateCtrl.value | json }}</div> | ||
<div>Reactive dirty: {{ stateCtrl.dirty }}</div> | ||
|
||
<mat-form-field> | ||
<mat-label>State</mat-label> | ||
<input matInput [matAutocomplete]="reactiveAuto" [formControl]="stateCtrl"> | ||
<mat-autocomplete #reactiveAuto="matAutocomplete" [displayWith]="displayFn"> | ||
<mat-option *ngFor="let state of tempStates" [value]="state"> | ||
<span>{{ state.name }}</span> | ||
<span class="demo-secondary-text"> ({{ state.code }}) </span> | ||
</mat-option> | ||
</mat-autocomplete> | ||
</mat-form-field> | ||
|
||
<mat-card-actions> | ||
<button mat-button (click)="stateCtrl.reset()">RESET</button> | ||
<button mat-button (click)="stateCtrl.setValue(states[10])">SET VALUE</button> | ||
<button mat-button (click)="stateCtrl.enabled ? stateCtrl.disable() : stateCtrl.enable()"> | ||
TOGGLE DISABLED | ||
</button> | ||
</mat-card-actions> | ||
|
||
</mat-card> | ||
|
||
<mat-card> | ||
|
||
<div>Template-driven value (currentState): {{ currentState }}</div> | ||
<div>Template-driven dirty: {{ modelDir ? modelDir.dirty : false }}</div> | ||
|
||
<!-- Added an ngIf below to test that autocomplete works with ngIf --> | ||
<mat-form-field *ngIf="true"> | ||
<mat-label>State</mat-label> | ||
<input matInput [matAutocomplete]="tdAuto" [(ngModel)]="currentState" | ||
(ngModelChange)="tdStates = filterStates(currentState)" [disabled]="tdDisabled"> | ||
<mat-autocomplete #tdAuto="matAutocomplete"> | ||
<mat-option *ngFor="let state of tdStates" [value]="state.name"> | ||
<span>{{ state.name }}</span> | ||
</mat-option> | ||
</mat-autocomplete> | ||
</mat-form-field> | ||
|
||
<mat-card-actions> | ||
<button mat-button (click)="modelDir.reset()">RESET</button> | ||
<button mat-button (click)="currentState='California'">SET VALUE</button> | ||
<button mat-button (click)="tdDisabled=!tdDisabled"> | ||
TOGGLE DISABLED | ||
</button> | ||
</mat-card-actions> | ||
|
||
</mat-card> | ||
|
||
<mat-card> | ||
<div>Option groups (currentGroupedState): {{ currentGroupedState }}</div> | ||
|
||
<mat-form-field> | ||
<mat-label>State</mat-label> | ||
<input | ||
matInput | ||
[matAutocomplete]="groupedAuto" | ||
[(ngModel)]="currentGroupedState" | ||
(ngModelChange)="filteredGroupedStates = filterStateGroups(currentGroupedState)"> | ||
</mat-form-field> | ||
</mat-card> | ||
</div> | ||
|
||
<mat-autocomplete #groupedAuto="matAutocomplete"> | ||
<mat-optgroup *ngFor="let group of filteredGroupedStates" | ||
[label]="'States starting with ' + group.letter"> | ||
<mat-option *ngFor="let state of group.states" [value]="state.name">{{ state.name }}</mat-option> | ||
</mat-optgroup> | ||
</mat-autocomplete> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
.demo-autocomplete { | ||
display: flex; | ||
flex-flow: row wrap; | ||
|
||
.mat-mdc-card { | ||
width: 400px; | ||
margin: 24px; | ||
padding: 16px; | ||
} | ||
|
||
.mat-mdc-form-field { | ||
margin-top: 16px; | ||
min-width: 200px; | ||
max-width: 100%; | ||
} | ||
} | ||
|
||
.demo-secondary-text { | ||
color: rgba(0, 0, 0, 0.54); | ||
margin-left: 8px; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/** | ||
* @license | ||
* Copyright Google LLC All Rights Reserved. | ||
* | ||
* Use of this source code is governed by an MIT-style license that can be | ||
* found in the LICENSE file at https://angular.io/license | ||
*/ | ||
|
||
import {Component, ViewChild} from '@angular/core'; | ||
import {FormControl, NgModel} from '@angular/forms'; | ||
import {Observable} from 'rxjs'; | ||
import {map, startWith} from 'rxjs/operators'; | ||
|
||
|
||
export interface State { | ||
code: string; | ||
name: string; | ||
} | ||
|
||
export interface StateGroup { | ||
letter: string; | ||
states: State[]; | ||
} | ||
|
||
@Component({ | ||
selector: 'mdc-autocomplete-demo', | ||
templateUrl: 'mdc-autocomplete-demo.html', | ||
styleUrls: ['mdc-autocomplete-demo.css'] | ||
}) | ||
export class MdcAutocompleteDemo { | ||
stateCtrl: FormControl; | ||
currentState = ''; | ||
currentGroupedState = ''; | ||
topHeightCtrl = new FormControl(0); | ||
|
||
reactiveStates: Observable<State[]>; | ||
tdStates: State[]; | ||
|
||
tdDisabled = false; | ||
|
||
@ViewChild(NgModel) modelDir: NgModel; | ||
|
||
groupedStates: StateGroup[]; | ||
filteredGroupedStates: StateGroup[]; | ||
states: State[] = [ | ||
{code: 'AL', name: 'Alabama'}, | ||
{code: 'AK', name: 'Alaska'}, | ||
{code: 'AZ', name: 'Arizona'}, | ||
{code: 'AR', name: 'Arkansas'}, | ||
{code: 'CA', name: 'California'}, | ||
{code: 'CO', name: 'Colorado'}, | ||
{code: 'CT', name: 'Connecticut'}, | ||
{code: 'DE', name: 'Delaware'}, | ||
{code: 'FL', name: 'Florida'}, | ||
{code: 'GA', name: 'Georgia'}, | ||
{code: 'HI', name: 'Hawaii'}, | ||
{code: 'ID', name: 'Idaho'}, | ||
{code: 'IL', name: 'Illinois'}, | ||
{code: 'IN', name: 'Indiana'}, | ||
{code: 'IA', name: 'Iowa'}, | ||
{code: 'KS', name: 'Kansas'}, | ||
{code: 'KY', name: 'Kentucky'}, | ||
{code: 'LA', name: 'Louisiana'}, | ||
{code: 'ME', name: 'Maine'}, | ||
{code: 'MD', name: 'Maryland'}, | ||
{code: 'MA', name: 'Massachusetts'}, | ||
{code: 'MI', name: 'Michigan'}, | ||
{code: 'MN', name: 'Minnesota'}, | ||
{code: 'MS', name: 'Mississippi'}, | ||
{code: 'MO', name: 'Missouri'}, | ||
{code: 'MT', name: 'Montana'}, | ||
{code: 'NE', name: 'Nebraska'}, | ||
{code: 'NV', name: 'Nevada'}, | ||
{code: 'NH', name: 'New Hampshire'}, | ||
{code: 'NJ', name: 'New Jersey'}, | ||
{code: 'NM', name: 'New Mexico'}, | ||
{code: 'NY', name: 'New York'}, | ||
{code: 'NC', name: 'North Carolina'}, | ||
{code: 'ND', name: 'North Dakota'}, | ||
{code: 'OH', name: 'Ohio'}, | ||
{code: 'OK', name: 'Oklahoma'}, | ||
{code: 'OR', name: 'Oregon'}, | ||
{code: 'PA', name: 'Pennsylvania'}, | ||
{code: 'RI', name: 'Rhode Island'}, | ||
{code: 'SC', name: 'South Carolina'}, | ||
{code: 'SD', name: 'South Dakota'}, | ||
{code: 'TN', name: 'Tennessee'}, | ||
{code: 'TX', name: 'Texas'}, | ||
{code: 'UT', name: 'Utah'}, | ||
{code: 'VT', name: 'Vermont'}, | ||
{code: 'VA', name: 'Virginia'}, | ||
{code: 'WA', name: 'Washington'}, | ||
{code: 'WV', name: 'West Virginia'}, | ||
{code: 'WI', name: 'Wisconsin'}, | ||
{code: 'WY', name: 'Wyoming'}, | ||
]; | ||
|
||
constructor() { | ||
this.tdStates = this.states; | ||
this.stateCtrl = new FormControl({code: 'CA', name: 'California'}); | ||
this.reactiveStates = this.stateCtrl.valueChanges | ||
.pipe( | ||
startWith(this.stateCtrl.value), | ||
map(val => this.displayFn(val)), | ||
map(name => this.filterStates(name)) | ||
); | ||
|
||
this.filteredGroupedStates = this.groupedStates = | ||
this.states.reduce<StateGroup[]>((groups, state) => { | ||
let group = groups.find(g => g.letter === state.name[0]); | ||
|
||
if (!group) { | ||
group = { letter: state.name[0], states: [] }; | ||
groups.push(group); | ||
} | ||
|
||
group.states.push({ code: state.code, name: state.name }); | ||
|
||
return groups; | ||
}, []); | ||
} | ||
|
||
displayFn(value: any): string { | ||
return value && typeof value === 'object' ? value.name : value; | ||
} | ||
|
||
filterStates(val: string) { | ||
return val ? this._filter(this.states, val) : this.states; | ||
} | ||
|
||
filterStateGroups(val: string) { | ||
if (val) { | ||
return this.groupedStates | ||
.map(group => ({ letter: group.letter, states: this._filter(group.states, val) })) | ||
.filter(group => group.states.length > 0); | ||
} | ||
|
||
return this.groupedStates; | ||
} | ||
|
||
private _filter(states: State[], val: string) { | ||
const filterValue = val.toLowerCase(); | ||
return states.filter(state => state.name.toLowerCase().startsWith(filterValue)); | ||
} | ||
} |
Oops, something went wrong.