mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Adds openAll()/closeAll() to perform expand/collapse all on multiple expandable accordions. Closes #6929
This commit is contained in:
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
+13
@@ -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;
|
||||
}
|
||||
+60
@@ -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>
|
||||
+14
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user