Skip to content

Commit

Permalink
feat(popover-edit): Adds support for using mat-selection-list for sel…
Browse files Browse the repository at this point in the history
…ect-like interactions. (#18194)

Includes minor behavior and style updates to PopoverEdit and an updated demo
showing off the new interaction.
  • Loading branch information
kseamon authored and jelbourn committed Jan 22, 2020
1 parent 33a61b0 commit 5b8c581
Show file tree
Hide file tree
Showing 9 changed files with 195 additions and 48 deletions.
23 changes: 13 additions & 10 deletions src/cdk-experimental/popover-edit/lens-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,24 +175,27 @@ export class CdkEditRevert<FormValue> {
}

/** Closes the lens on click. */
@Directive({
selector: 'button[cdkEditClose]',
host: {
'type': 'button', // Prevents accidental form submits.
}
})
@Directive({selector: '[cdkEditClose]'})
export class CdkEditClose<FormValue> {
/** Type of the button. Defaults to `button` to avoid accident form submits. */
@Input() type: string = 'button';

constructor(
protected readonly editRef: EditRef<FormValue>) {}
protected readonly elementRef: ElementRef<HTMLElement>,
protected readonly editRef: EditRef<FormValue>) {

const nativeElement = elementRef.nativeElement;

// Prevent accidental form submits.
if (nativeElement.nodeName === 'BUTTON' && !nativeElement.getAttribute('type')) {
nativeElement.setAttribute('type', 'button');
}
}

// In Ivy the `host` metadata will be merged, whereas in ViewEngine it is overridden. In order
// to avoid double event listeners, we need to use `HostListener`. Once Ivy is the default, we
// can move this back into `host`.
// tslint:disable:no-host-decorator-in-concrete
@HostListener('click')
@HostListener('keyup.enter')
@HostListener('keyup.space')
closeEdit(): void {
// Note that we use `click` here, rather than a keyboard event, because some screen readers
// will emit a fake click event instead of an enter keyboard event on buttons.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ ng_module(
"//src/material/button",
"//src/material/icon",
"//src/material/input",
"//src/material/list",
"//src/material/snack-bar",
"//src/material/table",
"@npm//@angular/common",
"@npm//@angular/forms",
],
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {MatPopoverEditModule} from '@angular/material-experimental/popover-edit';
import {MatButtonModule} from '@angular/material/button';
import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatListModule} from '@angular/material/list';
import {MatSnackBarModule} from '@angular/material/snack-bar';
import {MatTableModule} from '@angular/material/table';
import {
Expand Down Expand Up @@ -33,9 +35,11 @@ const EXAMPLES = [

@NgModule({
imports: [
CommonModule,
MatButtonModule,
MatIconModule,
MatInputModule,
MatListModule,
MatPopoverEditModule,
MatSnackBarModule,
MatTableModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@

.example-table td,
.example-table th {
width: 25%;
width: 16%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,43 @@ <h2 mat-edit-title>Name</h2>
</td>
</ng-container>

<!-- Type Column -->
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef> Type </th>
<td mat-cell *matCellDef="let element"
[matPopoverEdit]="typeEdit">
{{element.type}}

<!-- This edit is defined in the cell and can implicitly access element -->
<ng-template #typeEdit>
<div>
<form #f="ngForm"
matEditLens
matEditClose
(ngSubmit)="onSubmitType(element, f)"
[(matEditLensPreservedFormValue)]="typeValues.for(element).value">
<div mat-edit-fill>
<mat-selection-list [multiple]="false"
name="type"
[ngModel]="[element.type]"
(selectionChange)="f.ngSubmit.emit()"
aria-label="Element type">
<mat-list-option *ngFor="let type of TYPES"
[value]="type">
{{type}}
</mat-list-option>
</mat-selection-list>
</div>
</form>
</div>
</ng-template>

<span *matRowHoverContent>
<button mat-icon-button matEditOpen><mat-icon>arrow_drop_down</mat-icon></button>
</span>
</td>
</ng-container>

<!-- Weight Column -->
<ng-container matColumnDef="weight">
<th mat-header-cell *matHeaderCellDef> Weight </th>
Expand All @@ -92,4 +129,43 @@ <h2 mat-edit-title>Name</h2>

<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>

<!-- Fantasy Counterparts Column -->
<ng-container matColumnDef="fantasyCounterpart">
<th mat-header-cell *matHeaderCellDef> Fantasy Counterparts </th>
<td mat-cell *matCellDef="let element"
[matPopoverEdit]="fantasyCounterpartEdit">
{{element.fantasyCounterparts.join(', ')}}

<!-- This edit is defined in the cell and can implicitly access element -->
<ng-template #fantasyCounterpartEdit>
<div>
<form #f="ngForm"
matEditLens
(ngSubmit)="onSubmitFantasyCounterparts(element, f)"
[(matEditLensPreservedFormValue)]="fantasyValues.for(element).value">
<div mat-edit-fill>
<mat-selection-list [ngModel]="element.fantasyCounterparts"
name="fantasyCounterparts"
aria-label="Fantasy Element Counterparts">
<mat-list-option *ngFor="let fantasyElement of FANTASY_ELEMENTS"
[value]="fantasyElement"
checkboxPosition="before">
{{fantasyElement}}
</mat-list-option>
</mat-selection-list>
</div>
<div mat-edit-actions>
<button mat-button type="submit">Confirm</button>
<button mat-button matEditRevert>Revert</button>
</div>
</form>
</div>
</ng-template>

<span *matRowHoverContent>
<button mat-icon-button matEditOpen><mat-icon>arrow_drop_down</mat-icon></button>
</span>
</td>
</ng-container>
</table>
Original file line number Diff line number Diff line change
Expand Up @@ -5,36 +5,66 @@ import {NgForm} from '@angular/forms';
import {MatSnackBar} from '@angular/material/snack-bar';
import {BehaviorSubject, Observable} from 'rxjs';

export type ElementType = 'Metal' | 'Semimetal' | 'Nonmetal';

export type FantasyElement = 'Earth' | 'Water' | 'Wind' | 'Fire' | 'Light' | 'Dark';

export interface PeriodicElement {
name: string;
type: ElementType;
position: number;
weight: number;
symbol: string;
fantasyCounterparts: FantasyElement[];
}

const ELEMENT_DATA: PeriodicElement[] = [
{position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
{position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
{position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
{position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
{position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
{position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
{position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
{position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
{position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
{position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
{position: 11, name: 'Sodium', weight: 22.9897, symbol: 'Na'},
{position: 12, name: 'Magnesium', weight: 24.305, symbol: 'Mg'},
{position: 13, name: 'Aluminum', weight: 26.9815, symbol: 'Al'},
{position: 14, name: 'Silicon', weight: 28.0855, symbol: 'Si'},
{position: 15, name: 'Phosphorus', weight: 30.9738, symbol: 'P'},
{position: 16, name: 'Sulfur', weight: 32.065, symbol: 'S'},
{position: 17, name: 'Chlorine', weight: 35.453, symbol: 'Cl'},
{position: 18, name: 'Argon', weight: 39.948, symbol: 'Ar'},
{position: 19, name: 'Potassium', weight: 39.0983, symbol: 'K'},
{position: 20, name: 'Calcium', weight: 40.078, symbol: 'Ca'},
{position: 1, name: 'Hydrogen', type: 'Nonmetal', weight: 1.0079, symbol: 'H',
fantasyCounterparts: ['Fire', 'Wind', 'Light']},
{position: 2, name: 'Helium', type: 'Nonmetal', weight: 4.0026, symbol: 'He',
fantasyCounterparts: ['Wind', 'Light']},
{position: 3, name: 'Lithium', type: 'Metal', weight: 6.941, symbol: 'Li',
fantasyCounterparts: []},
{position: 4, name: 'Beryllium', type: 'Metal', weight: 9.0122, symbol: 'Be',
fantasyCounterparts: []},
{position: 5, name: 'Boron', type: 'Semimetal', weight: 10.811, symbol: 'B',
fantasyCounterparts: []},
{position: 6, name: 'Carbon', type: 'Nonmetal', weight: 12.0107, symbol: 'C',
fantasyCounterparts: ['Earth', 'Dark']},
{position: 7, name: 'Nitrogen', type: 'Nonmetal', weight: 14.0067, symbol: 'N',
fantasyCounterparts: ['Wind']},
{position: 8, name: 'Oxygen', type: 'Nonmetal', weight: 15.9994, symbol: 'O',
fantasyCounterparts: ['Fire', 'Water', 'Wind']},
{position: 9, name: 'Fluorine', type: 'Nonmetal', weight: 18.9984, symbol: 'F',
fantasyCounterparts: []},
{position: 10, name: 'Neon', type: 'Nonmetal', weight: 20.1797, symbol: 'Ne',
fantasyCounterparts: ['Light']},
{position: 11, name: 'Sodium', type: 'Metal', weight: 22.9897, symbol: 'Na',
fantasyCounterparts: ['Earth', 'Water']},
{position: 12, name: 'Magnesium', type: 'Metal', weight: 24.305, symbol: 'Mg',
fantasyCounterparts: []},
{position: 13, name: 'Aluminum', type: 'Metal', weight: 26.9815, symbol: 'Al',
fantasyCounterparts: []},
{position: 14, name: 'Silicon', type: 'Semimetal', weight: 28.0855, symbol: 'Si',
fantasyCounterparts: []},
{position: 15, name: 'Phosphorus', type: 'Nonmetal', weight: 30.9738, symbol: 'P',
fantasyCounterparts: []},
{position: 16, name: 'Sulfur', type: 'Nonmetal', weight: 32.065, symbol: 'S',
fantasyCounterparts: []},
{position: 17, name: 'Chlorine', type: 'Nonmetal', weight: 35.453, symbol: 'Cl',
fantasyCounterparts: []},
{position: 18, name: 'Argon', type: 'Nonmetal', weight: 39.948, symbol: 'Ar',
fantasyCounterparts: []},
{position: 19, name: 'Potassium', type: 'Metal', weight: 39.0983, symbol: 'K',
fantasyCounterparts: []},
{position: 20, name: 'Calcium', type: 'Metal', weight: 40.078, symbol: 'Ca',
fantasyCounterparts: []},
];

const TYPES: readonly ElementType[] = ['Metal', 'Semimetal', 'Nonmetal'];
const FANTASY_ELEMENTS: readonly FantasyElement[] =
['Earth', 'Water', 'Wind', 'Fire', 'Light', 'Dark'];

/**
* @title Material Popover Edit on a Material data-table
*/
Expand All @@ -44,11 +74,17 @@ const ELEMENT_DATA: PeriodicElement[] = [
templateUrl: 'popover-edit-mat-table-example.html',
})
export class PopoverEditMatTableExample {
displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
displayedColumns: string[] =
['position', 'name', 'type', 'weight', 'symbol', 'fantasyCounterpart'];
dataSource = new ExampleDataSource();

readonly TYPES = TYPES;
readonly FANTASY_ELEMENTS = FANTASY_ELEMENTS;

readonly nameValues = new FormValueContainer<PeriodicElement, any>();
readonly weightValues = new FormValueContainer<PeriodicElement, any>();
readonly typeValues = new FormValueContainer<PeriodicElement, any>();
readonly fantasyValues = new FormValueContainer<PeriodicElement, any>();

constructor(private readonly _snackBar: MatSnackBar) {}

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

onSubmitType(element: PeriodicElement, f: NgForm) {
if (!f.valid) { return; }

element.type = f.value.type[0];
}

onSubmitFantasyCounterparts(element: PeriodicElement, f: NgForm) {
if (!f.valid) { return; }

element.fantasyCounterparts = f.value.fantasyCounterparts;
}

goodJob(element: PeriodicElement) {
this._snackBar.open(`Way to go, ${element.name}!`, undefined, {duration: 2000});
}
Expand Down
18 changes: 9 additions & 9 deletions src/dev-app/popover-edit/popover-edit-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@ import {Component} from '@angular/core';

@Component({
template: `
<h3>CDK popover-edit with cdk-table</h3>
<h3 id="cdk-popover-edit">CDK popover-edit with cdk-table</h3>
<cdk-popover-edit-cdk-table-example></cdk-popover-edit-cdk-table-example>
<h3>CDK popover-edit with cdk-table flex</h3>
<h3 id="cdk-popover-edit-flex">CDK popover-edit with cdk-table flex</h3>
<cdk-popover-edit-cdk-table-flex-example></cdk-popover-edit-cdk-table-flex-example>
<h3>CDK popover-edit with vanilla table</h3>
<h3 id="cdk-popover-edit-vanilla-span">CDK popover-edit with vanilla table</h3>
<cdk-popover-edit-cell-span-vanilla-table-example>
</cdk-popover-edit-cell-span-vanilla-table-example>
<h3>CDK popover-edit with vanilla table and tab out</h3>
<h3 id="cdk-popover-edit-vanilla-tabout">CDK popover-edit with vanilla table and tab out</h3>
<cdk-popover-edit-tab-out-vanilla-table-example>
</cdk-popover-edit-tab-out-vanilla-table-example>
<h3>CDK popover-edit with vanilla table</h3>
<h3 id="cdk-popover-edit-vanilla">CDK popover-edit with vanilla table</h3>
<cdk-popover-edit-vanilla-table-example></cdk-popover-edit-vanilla-table-example>
<h3>Material popover-edit with mat-table and cell span</h3>
<h3 id="mat-popover-edit-span">Material popover-edit with mat-table and cell span</h3>
<popover-edit-cell-span-mat-table-example></popover-edit-cell-span-mat-table-example>
<h3>Material popover-edit with mat-table</h3>
<h3 id="mat-popover-edit">Material popover-edit with mat-table</h3>
<popover-edit-mat-table-example></popover-edit-mat-table-example>
<h3>Material popover-edit with mat-table flex</h3>
<h3 id="mat-popover-edit-flex">Material popover-edit with mat-table flex</h3>
<popover-edit-mat-table-flex-example></popover-edit-mat-table-flex-example>
<h3>Material popover-edit with mat</h3>
<h3 id="mat-popover-edit-tabout">Material popover-edit with mat-table and tab out</h3>
<popover-edit-tab-out-mat-table-example></popover-edit-tab-out-mat-table-example>
`,
})
Expand Down
21 changes: 20 additions & 1 deletion src/material-experimental/popover-edit/_popover-edit.scss
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@
margin: 0;
}

[mat-edit-content] {
[mat-edit-content],
[mat-edit-fill] {
display: block;

mat-form-field {
Expand All @@ -111,6 +112,20 @@
padding-top: 0;
}
}

// Make mat-selection-lists inside of the look more like mat-select popups.
mat-selection-list {
max-height: 256px; // Same as mat-select.
overflow-y: auto;
}
}

[mat-edit-fill] {
margin: -16px -24px;

mat-selection-list:first-child {
padding-top: 0;
}
}

[mat-edit-actions] {
Expand All @@ -119,6 +134,10 @@
flex-wrap: wrap;
justify-content: flex-end;
margin: 8px -16px -8px;

[mat-edit-fill] + & {
margin-top: 16px;
}
}
}

Expand Down
7 changes: 1 addition & 6 deletions src/material-experimental/popover-edit/lens-directives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@ export class MatEditRevert<FormValue> extends CdkEditRevert<FormValue> {
}

/** Closes the lens on click. */
@Directive({
selector: 'button[matEditClose]',
host: {
'type': 'button', // Prevents accidental form submits.
}
})
@Directive({selector: '[matEditClose]'})
export class MatEditClose<FormValue> extends CdkEditClose<FormValue> {
}

0 comments on commit 5b8c581

Please sign in to comment.