Skip to content

Commit 5b8c581

Browse files
kseamonjelbourn
authored andcommitted
feat(popover-edit): Adds support for using mat-selection-list for select-like interactions. (#18194)
Includes minor behavior and style updates to PopoverEdit and an updated demo showing off the new interaction.
1 parent 33a61b0 commit 5b8c581

File tree

9 files changed

+195
-48
lines changed

9 files changed

+195
-48
lines changed

src/cdk-experimental/popover-edit/lens-directives.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -175,24 +175,27 @@ export class CdkEditRevert<FormValue> {
175175
}
176176

177177
/** Closes the lens on click. */
178-
@Directive({
179-
selector: 'button[cdkEditClose]',
180-
host: {
181-
'type': 'button', // Prevents accidental form submits.
182-
}
183-
})
178+
@Directive({selector: '[cdkEditClose]'})
184179
export class CdkEditClose<FormValue> {
185-
/** Type of the button. Defaults to `button` to avoid accident form submits. */
186-
@Input() type: string = 'button';
187-
188180
constructor(
189-
protected readonly editRef: EditRef<FormValue>) {}
181+
protected readonly elementRef: ElementRef<HTMLElement>,
182+
protected readonly editRef: EditRef<FormValue>) {
183+
184+
const nativeElement = elementRef.nativeElement;
185+
186+
// Prevent accidental form submits.
187+
if (nativeElement.nodeName === 'BUTTON' && !nativeElement.getAttribute('type')) {
188+
nativeElement.setAttribute('type', 'button');
189+
}
190+
}
190191

191192
// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
192193
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
193194
// can move this back into `host`.
194195
// tslint:disable:no-host-decorator-in-concrete
195196
@HostListener('click')
197+
@HostListener('keyup.enter')
198+
@HostListener('keyup.space')
196199
closeEdit(): void {
197200
// Note that we use `click` here, rather than a keyboard event, because some screen readers
198201
// will emit a fake click event instead of an enter keyboard event on buttons.

src/components-examples/material-experimental/popover-edit/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ ng_module(
1515
"//src/material/button",
1616
"//src/material/icon",
1717
"//src/material/input",
18+
"//src/material/list",
1819
"//src/material/snack-bar",
1920
"//src/material/table",
21+
"@npm//@angular/common",
2022
"@npm//@angular/forms",
2123
],
2224
)

src/components-examples/material-experimental/popover-edit/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {NgModule} from '@angular/core';
2+
import {CommonModule} from '@angular/common';
23
import {FormsModule} from '@angular/forms';
34
import {MatPopoverEditModule} from '@angular/material-experimental/popover-edit';
45
import {MatButtonModule} from '@angular/material/button';
56
import {MatIconModule} from '@angular/material/icon';
67
import {MatInputModule} from '@angular/material/input';
8+
import {MatListModule} from '@angular/material/list';
79
import {MatSnackBarModule} from '@angular/material/snack-bar';
810
import {MatTableModule} from '@angular/material/table';
911
import {
@@ -33,9 +35,11 @@ const EXAMPLES = [
3335

3436
@NgModule({
3537
imports: [
38+
CommonModule,
3639
MatButtonModule,
3740
MatIconModule,
3841
MatInputModule,
42+
MatListModule,
3943
MatPopoverEditModule,
4044
MatSnackBarModule,
4145
MatTableModule,

src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@
88

99
.example-table td,
1010
.example-table th {
11-
width: 25%;
11+
width: 16%;
1212
}

src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.html

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,43 @@ <h2 mat-edit-title>Name</h2>
7171
</td>
7272
</ng-container>
7373

74+
<!-- Type Column -->
75+
<ng-container matColumnDef="type">
76+
<th mat-header-cell *matHeaderCellDef> Type </th>
77+
<td mat-cell *matCellDef="let element"
78+
[matPopoverEdit]="typeEdit">
79+
{{element.type}}
80+
81+
<!-- This edit is defined in the cell and can implicitly access element -->
82+
<ng-template #typeEdit>
83+
<div>
84+
<form #f="ngForm"
85+
matEditLens
86+
matEditClose
87+
(ngSubmit)="onSubmitType(element, f)"
88+
[(matEditLensPreservedFormValue)]="typeValues.for(element).value">
89+
<div mat-edit-fill>
90+
<mat-selection-list [multiple]="false"
91+
name="type"
92+
[ngModel]="[element.type]"
93+
(selectionChange)="f.ngSubmit.emit()"
94+
aria-label="Element type">
95+
<mat-list-option *ngFor="let type of TYPES"
96+
[value]="type">
97+
{{type}}
98+
</mat-list-option>
99+
</mat-selection-list>
100+
</div>
101+
</form>
102+
</div>
103+
</ng-template>
104+
105+
<span *matRowHoverContent>
106+
<button mat-icon-button matEditOpen><mat-icon>arrow_drop_down</mat-icon></button>
107+
</span>
108+
</td>
109+
</ng-container>
110+
74111
<!-- Weight Column -->
75112
<ng-container matColumnDef="weight">
76113
<th mat-header-cell *matHeaderCellDef> Weight </th>
@@ -92,4 +129,43 @@ <h2 mat-edit-title>Name</h2>
92129

93130
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
94131
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
132+
133+
<!-- Fantasy Counterparts Column -->
134+
<ng-container matColumnDef="fantasyCounterpart">
135+
<th mat-header-cell *matHeaderCellDef> Fantasy Counterparts </th>
136+
<td mat-cell *matCellDef="let element"
137+
[matPopoverEdit]="fantasyCounterpartEdit">
138+
{{element.fantasyCounterparts.join(', ')}}
139+
140+
<!-- This edit is defined in the cell and can implicitly access element -->
141+
<ng-template #fantasyCounterpartEdit>
142+
<div>
143+
<form #f="ngForm"
144+
matEditLens
145+
(ngSubmit)="onSubmitFantasyCounterparts(element, f)"
146+
[(matEditLensPreservedFormValue)]="fantasyValues.for(element).value">
147+
<div mat-edit-fill>
148+
<mat-selection-list [ngModel]="element.fantasyCounterparts"
149+
name="fantasyCounterparts"
150+
aria-label="Fantasy Element Counterparts">
151+
<mat-list-option *ngFor="let fantasyElement of FANTASY_ELEMENTS"
152+
[value]="fantasyElement"
153+
checkboxPosition="before">
154+
{{fantasyElement}}
155+
</mat-list-option>
156+
</mat-selection-list>
157+
</div>
158+
<div mat-edit-actions>
159+
<button mat-button type="submit">Confirm</button>
160+
<button mat-button matEditRevert>Revert</button>
161+
</div>
162+
</form>
163+
</div>
164+
</ng-template>
165+
166+
<span *matRowHoverContent>
167+
<button mat-icon-button matEditOpen><mat-icon>arrow_drop_down</mat-icon></button>
168+
</span>
169+
</td>
170+
</ng-container>
95171
</table>

src/components-examples/material-experimental/popover-edit/popover-edit-mat-table/popover-edit-mat-table-example.ts

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,66 @@ import {NgForm} from '@angular/forms';
55
import {MatSnackBar} from '@angular/material/snack-bar';
66
import {BehaviorSubject, Observable} from 'rxjs';
77

8+
export type ElementType = 'Metal' | 'Semimetal' | 'Nonmetal';
9+
10+
export type FantasyElement = 'Earth' | 'Water' | 'Wind' | 'Fire' | 'Light' | 'Dark';
11+
812
export interface PeriodicElement {
913
name: string;
14+
type: ElementType;
1015
position: number;
1116
weight: number;
1217
symbol: string;
18+
fantasyCounterparts: FantasyElement[];
1319
}
1420

1521
const ELEMENT_DATA: PeriodicElement[] = [
16-
{position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
17-
{position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
18-
{position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
19-
{position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
20-
{position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
21-
{position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
22-
{position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
23-
{position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
24-
{position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
25-
{position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
26-
{position: 11, name: 'Sodium', weight: 22.9897, symbol: 'Na'},
27-
{position: 12, name: 'Magnesium', weight: 24.305, symbol: 'Mg'},
28-
{position: 13, name: 'Aluminum', weight: 26.9815, symbol: 'Al'},
29-
{position: 14, name: 'Silicon', weight: 28.0855, symbol: 'Si'},
30-
{position: 15, name: 'Phosphorus', weight: 30.9738, symbol: 'P'},
31-
{position: 16, name: 'Sulfur', weight: 32.065, symbol: 'S'},
32-
{position: 17, name: 'Chlorine', weight: 35.453, symbol: 'Cl'},
33-
{position: 18, name: 'Argon', weight: 39.948, symbol: 'Ar'},
34-
{position: 19, name: 'Potassium', weight: 39.0983, symbol: 'K'},
35-
{position: 20, name: 'Calcium', weight: 40.078, symbol: 'Ca'},
22+
{position: 1, name: 'Hydrogen', type: 'Nonmetal', weight: 1.0079, symbol: 'H',
23+
fantasyCounterparts: ['Fire', 'Wind', 'Light']},
24+
{position: 2, name: 'Helium', type: 'Nonmetal', weight: 4.0026, symbol: 'He',
25+
fantasyCounterparts: ['Wind', 'Light']},
26+
{position: 3, name: 'Lithium', type: 'Metal', weight: 6.941, symbol: 'Li',
27+
fantasyCounterparts: []},
28+
{position: 4, name: 'Beryllium', type: 'Metal', weight: 9.0122, symbol: 'Be',
29+
fantasyCounterparts: []},
30+
{position: 5, name: 'Boron', type: 'Semimetal', weight: 10.811, symbol: 'B',
31+
fantasyCounterparts: []},
32+
{position: 6, name: 'Carbon', type: 'Nonmetal', weight: 12.0107, symbol: 'C',
33+
fantasyCounterparts: ['Earth', 'Dark']},
34+
{position: 7, name: 'Nitrogen', type: 'Nonmetal', weight: 14.0067, symbol: 'N',
35+
fantasyCounterparts: ['Wind']},
36+
{position: 8, name: 'Oxygen', type: 'Nonmetal', weight: 15.9994, symbol: 'O',
37+
fantasyCounterparts: ['Fire', 'Water', 'Wind']},
38+
{position: 9, name: 'Fluorine', type: 'Nonmetal', weight: 18.9984, symbol: 'F',
39+
fantasyCounterparts: []},
40+
{position: 10, name: 'Neon', type: 'Nonmetal', weight: 20.1797, symbol: 'Ne',
41+
fantasyCounterparts: ['Light']},
42+
{position: 11, name: 'Sodium', type: 'Metal', weight: 22.9897, symbol: 'Na',
43+
fantasyCounterparts: ['Earth', 'Water']},
44+
{position: 12, name: 'Magnesium', type: 'Metal', weight: 24.305, symbol: 'Mg',
45+
fantasyCounterparts: []},
46+
{position: 13, name: 'Aluminum', type: 'Metal', weight: 26.9815, symbol: 'Al',
47+
fantasyCounterparts: []},
48+
{position: 14, name: 'Silicon', type: 'Semimetal', weight: 28.0855, symbol: 'Si',
49+
fantasyCounterparts: []},
50+
{position: 15, name: 'Phosphorus', type: 'Nonmetal', weight: 30.9738, symbol: 'P',
51+
fantasyCounterparts: []},
52+
{position: 16, name: 'Sulfur', type: 'Nonmetal', weight: 32.065, symbol: 'S',
53+
fantasyCounterparts: []},
54+
{position: 17, name: 'Chlorine', type: 'Nonmetal', weight: 35.453, symbol: 'Cl',
55+
fantasyCounterparts: []},
56+
{position: 18, name: 'Argon', type: 'Nonmetal', weight: 39.948, symbol: 'Ar',
57+
fantasyCounterparts: []},
58+
{position: 19, name: 'Potassium', type: 'Metal', weight: 39.0983, symbol: 'K',
59+
fantasyCounterparts: []},
60+
{position: 20, name: 'Calcium', type: 'Metal', weight: 40.078, symbol: 'Ca',
61+
fantasyCounterparts: []},
3662
];
3763

64+
const TYPES: readonly ElementType[] = ['Metal', 'Semimetal', 'Nonmetal'];
65+
const FANTASY_ELEMENTS: readonly FantasyElement[] =
66+
['Earth', 'Water', 'Wind', 'Fire', 'Light', 'Dark'];
67+
3868
/**
3969
* @title Material Popover Edit on a Material data-table
4070
*/
@@ -44,11 +74,17 @@ const ELEMENT_DATA: PeriodicElement[] = [
4474
templateUrl: 'popover-edit-mat-table-example.html',
4575
})
4676
export class PopoverEditMatTableExample {
47-
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
77+
displayedColumns: string[] =
78+
['position', 'name', 'type', 'weight', 'symbol', 'fantasyCounterpart'];
4879
dataSource = new ExampleDataSource();
4980

81+
readonly TYPES = TYPES;
82+
readonly FANTASY_ELEMENTS = FANTASY_ELEMENTS;
83+
5084
readonly nameValues = new FormValueContainer<PeriodicElement, any>();
5185
readonly weightValues = new FormValueContainer<PeriodicElement, any>();
86+
readonly typeValues = new FormValueContainer<PeriodicElement, any>();
87+
readonly fantasyValues = new FormValueContainer<PeriodicElement, any>();
5288

5389
constructor(private readonly _snackBar: MatSnackBar) {}
5490

@@ -64,6 +100,18 @@ export class PopoverEditMatTableExample {
64100
element.weight = f.value.weight;
65101
}
66102

103+
onSubmitType(element: PeriodicElement, f: NgForm) {
104+
if (!f.valid) { return; }
105+
106+
element.type = f.value.type[0];
107+
}
108+
109+
onSubmitFantasyCounterparts(element: PeriodicElement, f: NgForm) {
110+
if (!f.valid) { return; }
111+
112+
element.fantasyCounterparts = f.value.fantasyCounterparts;
113+
}
114+
67115
goodJob(element: PeriodicElement) {
68116
this._snackBar.open(`Way to go, ${element.name}!`, undefined, {duration: 2000});
69117
}

src/dev-app/popover-edit/popover-edit-demo.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,25 @@ import {Component} from '@angular/core';
1010

1111
@Component({
1212
template: `
13-
<h3>CDK popover-edit with cdk-table</h3>
13+
<h3 id="cdk-popover-edit">CDK popover-edit with cdk-table</h3>
1414
<cdk-popover-edit-cdk-table-example></cdk-popover-edit-cdk-table-example>
15-
<h3>CDK popover-edit with cdk-table flex</h3>
15+
<h3 id="cdk-popover-edit-flex">CDK popover-edit with cdk-table flex</h3>
1616
<cdk-popover-edit-cdk-table-flex-example></cdk-popover-edit-cdk-table-flex-example>
17-
<h3>CDK popover-edit with vanilla table</h3>
17+
<h3 id="cdk-popover-edit-vanilla-span">CDK popover-edit with vanilla table</h3>
1818
<cdk-popover-edit-cell-span-vanilla-table-example>
1919
</cdk-popover-edit-cell-span-vanilla-table-example>
20-
<h3>CDK popover-edit with vanilla table and tab out</h3>
20+
<h3 id="cdk-popover-edit-vanilla-tabout">CDK popover-edit with vanilla table and tab out</h3>
2121
<cdk-popover-edit-tab-out-vanilla-table-example>
2222
</cdk-popover-edit-tab-out-vanilla-table-example>
23-
<h3>CDK popover-edit with vanilla table</h3>
23+
<h3 id="cdk-popover-edit-vanilla">CDK popover-edit with vanilla table</h3>
2424
<cdk-popover-edit-vanilla-table-example></cdk-popover-edit-vanilla-table-example>
25-
<h3>Material popover-edit with mat-table and cell span</h3>
25+
<h3 id="mat-popover-edit-span">Material popover-edit with mat-table and cell span</h3>
2626
<popover-edit-cell-span-mat-table-example></popover-edit-cell-span-mat-table-example>
27-
<h3>Material popover-edit with mat-table</h3>
27+
<h3 id="mat-popover-edit">Material popover-edit with mat-table</h3>
2828
<popover-edit-mat-table-example></popover-edit-mat-table-example>
29-
<h3>Material popover-edit with mat-table flex</h3>
29+
<h3 id="mat-popover-edit-flex">Material popover-edit with mat-table flex</h3>
3030
<popover-edit-mat-table-flex-example></popover-edit-mat-table-flex-example>
31-
<h3>Material popover-edit with mat</h3>
31+
<h3 id="mat-popover-edit-tabout">Material popover-edit with mat-table and tab out</h3>
3232
<popover-edit-tab-out-mat-table-example></popover-edit-tab-out-mat-table-example>
3333
`,
3434
})

src/material-experimental/popover-edit/_popover-edit.scss

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@
9999
margin: 0;
100100
}
101101

102-
[mat-edit-content] {
102+
[mat-edit-content],
103+
[mat-edit-fill] {
103104
display: block;
104105

105106
mat-form-field {
@@ -111,6 +112,20 @@
111112
padding-top: 0;
112113
}
113114
}
115+
116+
// Make mat-selection-lists inside of the look more like mat-select popups.
117+
mat-selection-list {
118+
max-height: 256px; // Same as mat-select.
119+
overflow-y: auto;
120+
}
121+
}
122+
123+
[mat-edit-fill] {
124+
margin: -16px -24px;
125+
126+
mat-selection-list:first-child {
127+
padding-top: 0;
128+
}
114129
}
115130

116131
[mat-edit-actions] {
@@ -119,6 +134,10 @@
119134
flex-wrap: wrap;
120135
justify-content: flex-end;
121136
margin: 8px -16px -8px;
137+
138+
[mat-edit-fill] + & {
139+
margin-top: 16px;
140+
}
122141
}
123142
}
124143

src/material-experimental/popover-edit/lens-directives.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,6 @@ export class MatEditRevert<FormValue> extends CdkEditRevert<FormValue> {
4848
}
4949

5050
/** Closes the lens on click. */
51-
@Directive({
52-
selector: 'button[matEditClose]',
53-
host: {
54-
'type': 'button', // Prevents accidental form submits.
55-
}
56-
})
51+
@Directive({selector: '[matEditClose]'})
5752
export class MatEditClose<FormValue> extends CdkEditClose<FormValue> {
5853
}

0 commit comments

Comments
 (0)