diff --git a/src/cdk/accordion/accordion-item.ts b/src/cdk/accordion/accordion-item.ts index cdca028603a5..082f0615f146 100644 --- a/src/cdk/accordion/accordion-item.ts +++ b/src/cdk/accordion/accordion-item.ts @@ -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 = new EventEmitter(); /** 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; + } + }); + } } diff --git a/src/cdk/accordion/accordion.ts b/src/cdk/accordion/accordion.ts index eb586aa0325b..b596a2efb7b8 100644 --- a/src/cdk/accordion/accordion.ts +++ b/src/cdk/accordion/accordion.ts @@ -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 = new Subject(); + /** 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); + } + } } diff --git a/src/demo-app/expansion/expansion-demo.html b/src/demo-app/expansion/expansion-demo.html index 9e4ecb03f322..46da36d1f2d1 100644 --- a/src/demo-app/expansion/expansion-demo.html +++ b/src/demo-app/expansion/expansion-demo.html @@ -41,6 +41,11 @@

matAccordion

Default Flat +

Accordion Actions ('Multi Expansion' mode only)

+
+ + +

Accordion Panel(s)

Panel 1 diff --git a/src/demo-app/expansion/expansion-demo.ts b/src/demo-app/expansion/expansion-demo.ts index c82169438bbc..f3fab72d743a 100644 --- a/src/demo-app/expansion/expansion-demo.ts +++ b/src/demo-app/expansion/expansion-demo.ts @@ -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; diff --git a/src/lib/expansion/accordion.spec.ts b/src/lib/expansion/accordion.spec.ts index f1f1e6047788..6c12e2db42a4 100644 --- a/src/lib/expansion/accordion.spec.ts +++ b/src/lib/expansion/accordion.spec.ts @@ -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', () => { Summary

Content

- + Summary

Content

`}) class SetOfItems { + @ViewChild(MatAccordion) accordion: MatAccordion; + multi: boolean = false; firstPanelExpanded: boolean = false; secondPanelExpanded: boolean = false; + secondPanelDisabled: boolean = false; } diff --git a/src/lib/expansion/accordion.ts b/src/lib/expansion/accordion.ts index 28499d088f66..962179cd751d 100644 --- a/src/lib/expansion/accordion.ts +++ b/src/lib/expansion/accordion.ts @@ -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. diff --git a/src/material-examples/expansion-expand-collapse-all/expansion-expand-collapse-all-example.css b/src/material-examples/expansion-expand-collapse-all/expansion-expand-collapse-all-example.css new file mode 100644 index 000000000000..8536de1b1880 --- /dev/null +++ b/src/material-examples/expansion-expand-collapse-all/expansion-expand-collapse-all-example.css @@ -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; +} diff --git a/src/material-examples/expansion-expand-collapse-all/expansion-expand-collapse-all-example.html b/src/material-examples/expansion-expand-collapse-all/expansion-expand-collapse-all-example.html new file mode 100644 index 000000000000..497675ccd94f --- /dev/null +++ b/src/material-examples/expansion-expand-collapse-all/expansion-expand-collapse-all-example.html @@ -0,0 +1,60 @@ +
+ + +
+ + + + + Personal data + + + Type your name and age + account_circle + + + + + + + + + + + + + + + + + Destination + + + Type the country name + map + + + + + + + + + + + + Day of the trip + + + Inform the date you wish to travel + date_range + + + + + + + + + + diff --git a/src/material-examples/expansion-expand-collapse-all/expansion-expand-collapse-all-example.ts b/src/material-examples/expansion-expand-collapse-all/expansion-expand-collapse-all-example.ts new file mode 100644 index 000000000000..580430b52c08 --- /dev/null +++ b/src/material-examples/expansion-expand-collapse-all/expansion-expand-collapse-all-example.ts @@ -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; +}