Skip to content

Commit 258e83c

Browse files
henetirikiLouw Swart
authored andcommitted
feat(expansion): add accordion expand/collapse all (#6929)
Add expandAll()/collapseAll() to perform expand/collapse all on multiple expandable accordions. Closes #6929
1 parent 26bbeb2 commit 258e83c

File tree

8 files changed

+163
-6
lines changed

8 files changed

+163
-6
lines changed

src/demo-app/expansion/expansion-demo.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ <h1>matAccordion</h1>
3939
<mat-radio-button value="default">Default</mat-radio-button>
4040
<mat-radio-button value="flat">Flat</mat-radio-button>
4141
</mat-radio-group>
42+
<p>Accordion Actions <sup>('Multi Expansion' mode only)</sup></p>
43+
<div>
44+
<button mat-button (click)="accordion.expandAll()" [disabled]="!multi">Expand All</button>&nbsp;
45+
<button mat-button (click)="accordion.collapseAll()" [disabled]="!multi">Collapse All</button>
46+
</div>
4247
<p>Accordion Panel(s)</p>
4348
<div>
4449
<mat-checkbox [(ngModel)]="panel1.expanded">Panel 1</mat-checkbox>

src/demo-app/expansion/expansion-demo.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
import {Component, ViewEncapsulation} from '@angular/core';
1+
import {Component, ViewChild, ViewEncapsulation} from '@angular/core';
2+
import {MatAccordion} from '@angular/material';
23

34
@Component({
45
moduleId: module.id,
56
selector: 'expansion-demo',
67
styleUrls: ['expansion-demo.css'],
78
templateUrl: 'expansion-demo.html',
89
encapsulation: ViewEncapsulation.None,
9-
preserveWhitespaces: false,
10+
preserveWhitespaces: true,
1011
})
1112
export class ExpansionDemo {
13+
@ViewChild(MatAccordion) accordion: MatAccordion;
14+
1215
displayMode: string = 'default';
1316
multi = false;
1417
hideToggle = false;

src/lib/expansion/accordion.spec.ts

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import {async, TestBed} from '@angular/core/testing';
2-
import {Component} from '@angular/core';
2+
import {Component, ViewChild} from '@angular/core';
33
import {By} from '@angular/platform-browser';
44
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
5-
import {MatExpansionModule} from './index';
6-
5+
import {MatExpansionModule, MatAccordion} from './index';
76

87
describe('CdkAccordion', () => {
98
beforeEach(async(() => {
@@ -45,6 +44,45 @@ describe('CdkAccordion', () => {
4544
expect(panels[0].classes['mat-expanded']).toBeTruthy();
4645
expect(panels[1].classes['mat-expanded']).toBeTruthy();
4746
});
47+
48+
it('should expand or collapse all enabled items', () => {
49+
const fixture = TestBed.createComponent(SetOfItems);
50+
const panels = fixture.debugElement.queryAll(By.css('.mat-expansion-panel'));
51+
52+
fixture.componentInstance.multi = true;
53+
fixture.componentInstance.secondPanelExpanded = true;
54+
fixture.detectChanges();
55+
expect(panels[0].classes['mat-expanded']).toBeFalsy();
56+
expect(panels[1].classes['mat-expanded']).toBeTruthy();
57+
58+
fixture.componentInstance.accordion.expandAll();
59+
fixture.detectChanges();
60+
expect(panels[0].classes['mat-expanded']).toBeTruthy();
61+
expect(panels[1].classes['mat-expanded']).toBeTruthy();
62+
63+
fixture.componentInstance.accordion.collapseAll();
64+
fixture.detectChanges();
65+
expect(panels[0].classes['mat-expanded']).toBeFalsy();
66+
expect(panels[1].classes['mat-expanded']).toBeFalsy();
67+
});
68+
69+
it('should not expand or collapse disabled items', () => {
70+
const fixture = TestBed.createComponent(SetOfItems);
71+
const panels = fixture.debugElement.queryAll(By.css('.mat-expansion-panel'));
72+
73+
fixture.componentInstance.multi = true;
74+
fixture.componentInstance.secondPanelDisabled = true;
75+
fixture.detectChanges();
76+
fixture.componentInstance.accordion.expandAll();
77+
fixture.detectChanges();
78+
expect(panels[0].classes['mat-expanded']).toBeTruthy();
79+
expect(panels[1].classes['mat-expanded']).toBeFalsy();
80+
81+
fixture.componentInstance.accordion.collapseAll();
82+
fixture.detectChanges();
83+
expect(panels[0].classes['mat-expanded']).toBeFalsy();
84+
expect(panels[1].classes['mat-expanded']).toBeFalsy();
85+
});
4886
});
4987

5088

@@ -54,13 +92,16 @@ describe('CdkAccordion', () => {
5492
<mat-expansion-panel-header>Summary</mat-expansion-panel-header>
5593
<p>Content</p>
5694
</mat-expansion-panel>
57-
<mat-expansion-panel [expanded]="secondPanelExpanded">
95+
<mat-expansion-panel [expanded]="secondPanelExpanded" [disabled]="secondPanelDisabled">
5896
<mat-expansion-panel-header>Summary</mat-expansion-panel-header>
5997
<p>Content</p>
6098
</mat-expansion-panel>
6199
</mat-accordion>`})
62100
class SetOfItems {
101+
@ViewChild(MatAccordion) accordion: MatAccordion;
102+
63103
multi: boolean = false;
64104
firstPanelExpanded: boolean = false;
65105
secondPanelExpanded: boolean = false;
106+
secondPanelDisabled: boolean = false;
66107
}

src/lib/expansion/expansion-panel-header.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ import {EXPANSION_PANEL_ANIMATION_TIMING, MatExpansionPanel} from './expansion-p
8383
})
8484
export class MatExpansionPanelHeader implements OnDestroy {
8585
private _parentChangeSubscription = Subscription.EMPTY;
86+
private _expandCollapseAllSubscription = Subscription.EMPTY;
8687

8788
constructor(
8889
renderer: Renderer2,
@@ -101,6 +102,17 @@ export class MatExpansionPanelHeader implements OnDestroy {
101102
.subscribe(() => this._changeDetectorRef.markForCheck());
102103

103104
_focusMonitor.monitor(_element.nativeElement, renderer, false);
105+
106+
// When a panel is hosted in an accordion, subscribe to the expand/collapse subject
107+
if (this.panel.accordion) {
108+
this._expandCollapseAllSubscription =
109+
this.panel.accordion.expandCollapseAllSubject.subscribe((expanded: boolean) => {
110+
// Only toggle if current state is not the intended state
111+
if (!!this._isExpanded() !== expanded) {
112+
this._toggle();
113+
}
114+
});
115+
}
104116
}
105117

106118
/** Height of the header while the panel is expanded. */
@@ -152,6 +164,7 @@ export class MatExpansionPanelHeader implements OnDestroy {
152164

153165
ngOnDestroy() {
154166
this._parentChangeSubscription.unsubscribe();
167+
this._expandCollapseAllSubscription.unsubscribe();
155168
this._focusMonitor.stopMonitoring(this._element.nativeElement);
156169
}
157170
}

src/material-examples/example-module.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import {DialogContentExampleDialog,DialogContentExample} from './dialog-content/
3939
import {DialogDataExampleDialog,DialogDataExample} from './dialog-data/dialog-data-example';
4040
import {DialogElementsExampleDialog,DialogElementsExample} from './dialog-elements/dialog-elements-example';
4141
import {DialogOverviewExampleDialog,DialogOverviewExample} from './dialog-overview/dialog-overview-example';
42+
import {ExpansionExpandCollapseAllExample} from './expansion-expand-collapse-all/expansion-expand-collapse-all-example';
4243
import {ExpansionOverviewExample} from './expansion-overview/expansion-overview-example';
4344
import {ExpansionStepsExample} from './expansion-steps/expansion-steps-example';
4445
import {MyTelInput,FormFieldCustomControlExample} from './form-field-custom-control/form-field-custom-control-example';
@@ -267,6 +268,12 @@ export const EXAMPLE_COMPONENTS = {
267268
additionalFiles: ["dialog-overview-example-dialog.html"],
268269
selectorName: 'DialogOverviewExample, DialogOverviewExampleDialog'
269270
},
271+
'expansion-expand-collapse-all': {
272+
title: 'Accordion with expand/collapse all',
273+
component: ExpansionExpandCollapseAllExample,
274+
additionalFiles: null,
275+
selectorName: null
276+
},
270277
'expansion-overview': {
271278
title: 'Basic expansion panel',
272279
component: ExpansionOverviewExample,
@@ -716,6 +723,7 @@ export const EXAMPLE_LIST = [
716723
DialogDataExampleDialog,DialogDataExample,
717724
DialogElementsExampleDialog,DialogElementsExample,
718725
DialogOverviewExampleDialog,DialogOverviewExample,
726+
ExpansionExpandCollapseAllExample,
719727
ExpansionOverviewExample,
720728
ExpansionStepsExample,
721729
MyTelInput,FormFieldCustomControlExample,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.example-action-buttons {
2+
padding-bottom: 20px;
3+
}
4+
5+
.example-headers-align .mat-expansion-panel-header-title,
6+
.example-headers-align .mat-expansion-panel-header-description {
7+
flex-basis: 0;
8+
}
9+
10+
.example-headers-align .mat-expansion-panel-header-description {
11+
justify-content: space-between;
12+
align-items: center;
13+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<div class="example-action-buttons">
2+
<button mat-button (click)="accordion.expandAll()">Expand All</button>
3+
<button mat-button (click)="accordion.collapseAll()">Collapse All</button>
4+
</div>
5+
<mat-accordion class="example-headers-align" [multi]="true">
6+
<mat-expansion-panel>
7+
<mat-expansion-panel-header>
8+
<mat-panel-title>
9+
Personal data
10+
</mat-panel-title>
11+
<mat-panel-description>
12+
Type your name and age
13+
<mat-icon>account_circle</mat-icon>
14+
</mat-panel-description>
15+
</mat-expansion-panel-header>
16+
17+
<mat-form-field>
18+
<input matInput placeholder="First name">
19+
</mat-form-field>
20+
21+
<mat-form-field>
22+
<input matInput type="number" min="1" placeholder="Age">
23+
</mat-form-field>
24+
25+
</mat-expansion-panel>
26+
27+
<mat-expansion-panel [disabled]="true">
28+
<mat-expansion-panel-header>
29+
<mat-panel-title>
30+
Destination
31+
</mat-panel-title>
32+
<mat-panel-description>
33+
Type the country name
34+
<mat-icon>map</mat-icon>
35+
</mat-panel-description>
36+
</mat-expansion-panel-header>
37+
38+
<mat-form-field>
39+
<input matInput placeholder="Country">
40+
</mat-form-field>
41+
</mat-expansion-panel>
42+
43+
<mat-expansion-panel>
44+
<mat-expansion-panel-header>
45+
<mat-panel-title>
46+
Day of the trip
47+
</mat-panel-title>
48+
<mat-panel-description>
49+
Inform the date you wish to travel
50+
<mat-icon>date_range</mat-icon>
51+
</mat-panel-description>
52+
</mat-expansion-panel-header>
53+
54+
<mat-form-field>
55+
<input matInput placeholder="Date" [matDatepicker]="picker" (focus)="picker.open()" readonly>
56+
</mat-form-field>
57+
<mat-datepicker #picker></mat-datepicker>
58+
</mat-expansion-panel>
59+
60+
</mat-accordion>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {Component, ViewChild} from '@angular/core';
2+
import {MatAccordion} from '@angular/material';
3+
4+
/**
5+
* @title Accordion with expand/collapse all
6+
*/
7+
@Component({
8+
selector: 'expansion-toggle-all-example',
9+
templateUrl: 'expansion-expand-collapse-all-example.html',
10+
styleUrls: ['expansion-expand-collapse-all-example.css']
11+
})
12+
export class ExpansionExpandCollapseAllExample {
13+
@ViewChild(MatAccordion) accordion: MatAccordion;
14+
}

0 commit comments

Comments
 (0)