Skip to content

Commit f6b1002

Browse files
josephperrottmmalerba
authored andcommitted
feat(expansion): add animation events for expansion panels (#12412)
1 parent 9d882a0 commit f6b1002

File tree

5 files changed

+78
-14
lines changed

5 files changed

+78
-14
lines changed

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<h1>Single Expansion Panel</h1>
22

3-
<mat-expansion-panel class="demo-expansion-width" #myPanel>
3+
<mat-expansion-panel class="demo-expansion-width" #myPanel
4+
(afterExpand)="addEvent('afterExpand')"
5+
(afterCollapse)="addEvent('afterCollapse')">
46
<mat-expansion-panel-header [expandedHeight]="expandedHeight" [collapsedHeight]="collapsedHeight">
57
<mat-panel-description>This is a panel description.</mat-panel-description>
68
<mat-panel-title>Panel Title</mat-panel-title>
@@ -29,6 +31,11 @@ <h1>Single Expansion Panel</h1>
2931
</mat-form-field>
3032
<br>
3133

34+
<p>Expansion Panel Animation Events</p>
35+
<code class="demo-expansion-code">
36+
<pre *ngFor="let event of events">{{event}}</pre>
37+
</code>
38+
3239
<h1>matAccordion</h1>
3340
<div>
3441
<p>Accordion Options</p>
@@ -56,7 +63,7 @@ <h1>matAccordion</h1>
5663
</div>
5764
<br>
5865
<mat-accordion [displayMode]="displayMode" [multi]="multi"
59-
class="demo-expansion-width">
66+
class="demo-expansion-width">
6067
<mat-expansion-panel #panel1 [hideToggle]="hideToggle">
6168
<mat-expansion-panel-header>Section 1</mat-expansion-panel-header>
6269
<p>This is the content text that makes sense here.</p>

src/demo-app/expansion/expansion-demo.scss

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,16 @@
22
width: 600px;
33
display: block;
44
}
5+
6+
.demo-expansion-code {
7+
background: #d3d3d3;
8+
display: block;
9+
height: 10em;
10+
overflow: auto;
11+
padding: 0 5px;
12+
width: 400px;
13+
14+
pre {
15+
margin: 0.25em 0;
16+
}
17+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,9 @@ export class ExpansionDemo {
2626
showPanel3 = true;
2727
expandedHeight: string;
2828
collapsedHeight: string;
29+
events: string[] = [];
30+
31+
addEvent(eventName: string) {
32+
this.events.push(`${eventName} - ${new Date().toISOString()}`);
33+
}
2934
}

src/lib/expansion/expansion-panel.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ import {
1818
Component,
1919
ContentChild,
2020
Directive,
21+
EventEmitter,
2122
Input,
2223
OnChanges,
2324
OnDestroy,
2425
Optional,
26+
Output,
2527
SimpleChanges,
2628
SkipSelf,
2729
ViewContainerRef,
@@ -69,7 +71,7 @@ let uniqueId = 0;
6971
}
7072
})
7173
export class MatExpansionPanel extends CdkAccordionItem
72-
implements AfterContentInit, OnChanges, OnDestroy {
74+
implements AfterContentInit, OnChanges, OnDestroy {
7375
/** Whether the toggle indicator should be hidden. */
7476
@Input()
7577
get hideToggle(): boolean { return this._hideToggle; }
@@ -78,6 +80,12 @@ export class MatExpansionPanel extends CdkAccordionItem
7880
}
7981
private _hideToggle = false;
8082

83+
/** An event emitted after the body's expansion animation happens. */
84+
@Output() afterExpand = new EventEmitter<void>();
85+
86+
/** An event emitted after the body's collapse animation happens. */
87+
@Output() afterCollapse = new EventEmitter<void>();
88+
8189
/** Stream that emits for changes in `@Input` properties. */
8290
readonly _inputChanges = new Subject<SimpleChanges>();
8391

@@ -147,17 +155,25 @@ export class MatExpansionPanel extends CdkAccordionItem
147155
_bodyAnimation(event: AnimationEvent) {
148156
const classList = event.element.classList;
149157
const cssClass = 'mat-expanded';
150-
const {phaseName, toState} = event;
158+
const {phaseName, toState, fromState} = event;
151159

152160
// Toggle the body's `overflow: hidden` class when closing starts or when expansion ends in
153161
// order to prevent the cases where switching too early would cause the animation to jump.
154162
// Note that we do it directly on the DOM element to avoid the slight delay that comes
155163
// with doing it via change detection.
156164
if (phaseName === 'done' && toState === 'expanded') {
157165
classList.add(cssClass);
158-
} else if (phaseName === 'start' && toState === 'collapsed') {
166+
}
167+
if (phaseName === 'start' && toState === 'collapsed') {
159168
classList.remove(cssClass);
160169
}
170+
171+
if (phaseName === 'done' && toState === 'expanded' && fromState !== 'void') {
172+
this.afterExpand.emit();
173+
}
174+
if (phaseName === 'done' && toState === 'collapsed' && fromState !== 'void') {
175+
this.afterCollapse.emit();
176+
}
161177
}
162178
}
163179

src/lib/expansion/expansion.spec.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ describe('MatExpansionPanel', () => {
1111
beforeEach(async(() => {
1212
TestBed.configureTestingModule({
1313
imports: [
14+
MatExpansionModule,
1415
NoopAnimationsModule,
15-
MatExpansionModule
1616
],
1717
declarations: [
1818
PanelWithContent,
@@ -44,8 +44,9 @@ describe('MatExpansionPanel', () => {
4444
}));
4545

4646
it('should be able to render panel content lazily', fakeAsync(() => {
47-
let fixture = TestBed.createComponent(LazyPanelWithContent);
48-
let content = fixture.debugElement.query(By.css('.mat-expansion-panel-content')).nativeElement;
47+
const fixture = TestBed.createComponent(LazyPanelWithContent);
48+
const content = fixture.debugElement.query(
49+
By.css('.mat-expansion-panel-content')).nativeElement;
4950
fixture.detectChanges();
5051

5152
expect(content.textContent.trim()).toBe('', 'Expected content element to be empty.');
@@ -58,8 +59,9 @@ describe('MatExpansionPanel', () => {
5859
}));
5960

6061
it('should render the content for a lazy-loaded panel that is opened on init', fakeAsync(() => {
61-
let fixture = TestBed.createComponent(LazyPanelOpenOnLoad);
62-
let content = fixture.debugElement.query(By.css('.mat-expansion-panel-content')).nativeElement;
62+
const fixture = TestBed.createComponent(LazyPanelOpenOnLoad);
63+
const content = fixture.debugElement.query(
64+
By.css('.mat-expansion-panel-content')).nativeElement;
6365
fixture.detectChanges();
6466

6567
expect(content.textContent.trim())
@@ -161,10 +163,10 @@ describe('MatExpansionPanel', () => {
161163
}));
162164

163165
it('should not override the panel margin if it is not inside an accordion', fakeAsync(() => {
164-
let fixture = TestBed.createComponent(PanelWithCustomMargin);
166+
const fixture = TestBed.createComponent(PanelWithCustomMargin);
165167
fixture.detectChanges();
166168

167-
let panel = fixture.debugElement.query(By.css('mat-expansion-panel'));
169+
const panel = fixture.debugElement.query(By.css('mat-expansion-panel'));
168170
let styles = getComputedStyle(panel.nativeElement);
169171

170172
expect(panel.componentInstance._hasSpacing()).toBe(false);
@@ -221,7 +223,7 @@ describe('MatExpansionPanel', () => {
221223
}));
222224

223225
it('should make sure accordion item runs ngOnDestroy when expansion panel is destroyed', () => {
224-
let fixture = TestBed.createComponent(PanelWithContentInNgIf);
226+
const fixture = TestBed.createComponent(PanelWithContentInNgIf);
225227
fixture.detectChanges();
226228
let destroyedOk = false;
227229
fixture.componentInstance.panel.destroyed.subscribe(() => destroyedOk = true);
@@ -264,6 +266,27 @@ describe('MatExpansionPanel', () => {
264266
'Expected class to be added after the animation has finished');
265267
}));
266268

269+
it('should emit events for body expanding and collapsing animations', fakeAsync(() => {
270+
const fixture = TestBed.createComponent(PanelWithContent);
271+
fixture.detectChanges();
272+
let afterExpand = 0;
273+
let afterCollapse = 0;
274+
fixture.componentInstance.panel.afterExpand.subscribe(() => afterExpand++);
275+
fixture.componentInstance.panel.afterCollapse.subscribe(() => afterCollapse++);
276+
277+
fixture.componentInstance.expanded = true;
278+
fixture.detectChanges();
279+
flush();
280+
expect(afterExpand).toBe(1);
281+
expect(afterCollapse).toBe(0);
282+
283+
fixture.componentInstance.expanded = false;
284+
fixture.detectChanges();
285+
flush();
286+
expect(afterExpand).toBe(1);
287+
expect(afterCollapse).toBe(1);
288+
}));
289+
267290
describe('disabled state', () => {
268291
let fixture: ComponentFixture<PanelWithContent>;
269292
let panel: HTMLElement;
@@ -373,7 +396,7 @@ class PanelWithContentInNgIf {
373396
</mat-expansion-panel>`
374397
})
375398
class PanelWithCustomMargin {
376-
expanded: boolean = false;
399+
expanded = false;
377400
}
378401

379402
@Component({

0 commit comments

Comments
 (0)