feat(expansion): add accordion expand/collapse all (#6929) (#7461)

Adds openAll()/closeAll() to perform expand/collapse all on multiple expandable accordions.

Closes #6929
This commit is contained in:
Louw Swart
2018-02-10 11:18:18 +13:00
committed by mmalerba
parent 39936c6204
commit 3aceb7361c
9 changed files with 180 additions and 5 deletions
+18
View File
@@ -18,6 +18,7 @@ import {
import {UniqueSelectionDispatcher} from '@angular/cdk/collections';
import {CdkAccordion} from './accordion';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {Subscription} from 'rxjs/Subscription';
/** Used to generate unique ID for each accordion item. */
let nextId = 0;
@@ -31,6 +32,8 @@ let nextId = 0;
exportAs: 'cdkAccordionItem',
})
export class CdkAccordionItem implements OnDestroy {
/** Subscription to openAll/closeAll events. */
private _openCloseAllSubscription = Subscription.EMPTY;
/** Event emitted every time the AccordionItem is closed. */
@Output() closed: EventEmitter<void> = new EventEmitter<void>();
/** Event emitted every time the AccordionItem is opened. */
@@ -97,12 +100,18 @@ export class CdkAccordionItem implements OnDestroy {
this.expanded = false;
}
});
// When an accordion item is hosted in an accordion, subscribe to open/close events.
if (this.accordion) {
this._openCloseAllSubscription = this._subscribeToOpenCloseAllActions();
}
}
/** Emits an event for the accordion item being destroyed. */
ngOnDestroy() {
this.destroyed.emit();
this._removeUniqueSelectionListener();
this._openCloseAllSubscription.unsubscribe();
}
/** Toggles the expanded state of the accordion item. */
@@ -125,4 +134,13 @@ export class CdkAccordionItem implements OnDestroy {
this.expanded = true;
}
}
private _subscribeToOpenCloseAllActions(): Subscription {
return this.accordion._openCloseAllActions.subscribe(expanded => {
// Only change expanded state if item is enabled
if (!this.disabled) {
this.expanded = expanded;
}
});
}
}
+20
View File
@@ -8,6 +8,7 @@
import {Directive, Input} from '@angular/core';
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {Subject} from 'rxjs/Subject';
/** Used to generate unique ID for each accordion. */
let nextId = 0;
@@ -20,6 +21,9 @@ let nextId = 0;
exportAs: 'cdkAccordion',
})
export class CdkAccordion {
/** Stream that emits true/false when openAll/closeAll is triggered. */
readonly _openCloseAllActions: Subject<boolean> = new Subject<boolean>();
/** A readonly id value to use for unique selection coordination. */
readonly id = `cdk-accordion-${nextId++}`;
@@ -28,4 +32,20 @@ export class CdkAccordion {
get multi(): boolean { return this._multi; }
set multi(multi: boolean) { this._multi = coerceBooleanProperty(multi); }
private _multi: boolean = false;
/** Opens all enabled accordion items in an accordion where multi is enabled. */
openAll(): void {
this._openCloseAll(true);
}
/** Closes all enabled accordion items in an accordion where multi is enabled. */
closeAll(): void {
this._openCloseAll(false);
}
private _openCloseAll(expanded: boolean): void {
if (this.multi) {
this._openCloseAllActions.next(expanded);
}
}
}
@@ -43,6 +43,11 @@
<mat-radio-button value="default">Default</mat-radio-button>
<mat-radio-button value="flat">Flat</mat-radio-button>
</mat-radio-group>
<p>Accordion Actions <sup>('Multi Expansion' mode only)</sup></p>
<div>
<button mat-button (click)="accordion.openAll()" [disabled]="!multi">Expand All</button>
<button mat-button (click)="accordion.closeAll()" [disabled]="!multi">Collapse All</button>
</div>
<p>Accordion Panel(s)</p>
<div>
<mat-checkbox [(ngModel)]="panel1.expanded">Panel 1</mat-checkbox>
+4 -1
View File
@@ -6,7 +6,8 @@
* found in the LICENSE file at https://angular.io/license
*/
import {Component, ViewEncapsulation} from '@angular/core';
import {Component, ViewChild, ViewEncapsulation} from '@angular/core';
import {MatAccordion} from '@angular/material';
@Component({
moduleId: module.id,
@@ -17,6 +18,8 @@ import {Component, ViewEncapsulation} from '@angular/core';
preserveWhitespaces: false,
})
export class ExpansionDemo {
@ViewChild(MatAccordion) accordion: MatAccordion;
displayMode: string = 'default';
multi = false;
hideToggle = false;
+45 -3
View File
@@ -1,8 +1,8 @@
import {async, TestBed} from '@angular/core/testing';
import {Component} from '@angular/core';
import {Component, ViewChild} from '@angular/core';
import {By} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {MatExpansionModule} from './index';
import {MatExpansionModule, MatAccordion} from './index';
describe('CdkAccordion', () => {
@@ -45,6 +45,45 @@ describe('CdkAccordion', () => {
expect(panels[0].classes['mat-expanded']).toBeTruthy();
expect(panels[1].classes['mat-expanded']).toBeTruthy();
});
it('should expand or collapse all enabled items', () => {
const fixture = TestBed.createComponent(SetOfItems);
const panels = fixture.debugElement.queryAll(By.css('.mat-expansion-panel'));
fixture.componentInstance.multi = true;
fixture.componentInstance.secondPanelExpanded = true;
fixture.detectChanges();
expect(panels[0].classes['mat-expanded']).toBeFalsy();
expect(panels[1].classes['mat-expanded']).toBeTruthy();
fixture.componentInstance.accordion.openAll();
fixture.detectChanges();
expect(panels[0].classes['mat-expanded']).toBeTruthy();
expect(panels[1].classes['mat-expanded']).toBeTruthy();
fixture.componentInstance.accordion.closeAll();
fixture.detectChanges();
expect(panels[0].classes['mat-expanded']).toBeFalsy();
expect(panels[1].classes['mat-expanded']).toBeFalsy();
});
it('should not expand or collapse disabled items', () => {
const fixture = TestBed.createComponent(SetOfItems);
const panels = fixture.debugElement.queryAll(By.css('.mat-expansion-panel'));
fixture.componentInstance.multi = true;
fixture.componentInstance.secondPanelDisabled = true;
fixture.detectChanges();
fixture.componentInstance.accordion.openAll();
fixture.detectChanges();
expect(panels[0].classes['mat-expanded']).toBeTruthy();
expect(panels[1].classes['mat-expanded']).toBeFalsy();
fixture.componentInstance.accordion.closeAll();
fixture.detectChanges();
expect(panels[0].classes['mat-expanded']).toBeFalsy();
expect(panels[1].classes['mat-expanded']).toBeFalsy();
});
});
@@ -54,13 +93,16 @@ describe('CdkAccordion', () => {
<mat-expansion-panel-header>Summary</mat-expansion-panel-header>
<p>Content</p>
</mat-expansion-panel>
<mat-expansion-panel [expanded]="secondPanelExpanded">
<mat-expansion-panel [expanded]="secondPanelExpanded" [disabled]="secondPanelDisabled">
<mat-expansion-panel-header>Summary</mat-expansion-panel-header>
<p>Content</p>
</mat-expansion-panel>
</mat-accordion>`})
class SetOfItems {
@ViewChild(MatAccordion) accordion: MatAccordion;
multi: boolean = false;
firstPanelExpanded: boolean = false;
secondPanelExpanded: boolean = false;
secondPanelDisabled: boolean = false;
}
+1 -1
View File
@@ -33,7 +33,7 @@ export class MatAccordion extends CdkAccordion {
/**
* The display mode used for all expansion panels in the accordion. Currently two display
* modes exist:
* default - a gutter-like spacing is placed around any expanded panel, placing the expanded
* default - a gutter-like spacing is placed around any expanded panel, placing the expanded
* panel at a different elevation from the reset of the accordion.
* flat - no spacing is placed around expanded panels, showing all panels at the same
* elevation.
@@ -0,0 +1,13 @@
.example-action-buttons {
padding-bottom: 20px;
}
.example-headers-align .mat-expansion-panel-header-title,
.example-headers-align .mat-expansion-panel-header-description {
flex-basis: 0;
}
.example-headers-align .mat-expansion-panel-header-description {
justify-content: space-between;
align-items: center;
}
@@ -0,0 +1,60 @@
<div class="example-action-buttons">
<button mat-button (click)="accordion.openAll()">Expand All</button>
<button mat-button (click)="accordion.closeAll()">Collapse All</button>
</div>
<mat-accordion class="example-headers-align" [multi]="true">
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Personal data
</mat-panel-title>
<mat-panel-description>
Type your name and age
<mat-icon>account_circle</mat-icon>
</mat-panel-description>
</mat-expansion-panel-header>
<mat-form-field>
<input matInput placeholder="First name">
</mat-form-field>
<mat-form-field>
<input matInput type="number" min="1" placeholder="Age">
</mat-form-field>
</mat-expansion-panel>
<mat-expansion-panel [disabled]="true">
<mat-expansion-panel-header>
<mat-panel-title>
Destination
</mat-panel-title>
<mat-panel-description>
Type the country name
<mat-icon>map</mat-icon>
</mat-panel-description>
</mat-expansion-panel-header>
<mat-form-field>
<input matInput placeholder="Country">
</mat-form-field>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title>
Day of the trip
</mat-panel-title>
<mat-panel-description>
Inform the date you wish to travel
<mat-icon>date_range</mat-icon>
</mat-panel-description>
</mat-expansion-panel-header>
<mat-form-field>
<input matInput placeholder="Date" [matDatepicker]="picker" (focus)="picker.open()" readonly>
</mat-form-field>
<mat-datepicker #picker></mat-datepicker>
</mat-expansion-panel>
</mat-accordion>
@@ -0,0 +1,14 @@
import {Component, ViewChild} from '@angular/core';
import {MatAccordion} from '@angular/material';
/**
* @title Accordion with expand/collapse all toggles
*/
@Component({
selector: 'expansion-toggle-all-example',
templateUrl: 'expansion-expand-collapse-all-example.html',
styleUrls: ['expansion-expand-collapse-all-example.css']
})
export class ExpansionExpandCollapseAllExample {
@ViewChild(MatAccordion) accordion: MatAccordion;
}