Skip to content

Commit 98a8cf1

Browse files
committed
feat(expansion): add animation events for expansion panels
1 parent c3e3dcb commit 98a8cf1

File tree

5 files changed

+100
-14
lines changed

5 files changed

+100
-14
lines changed

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
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+
(beforeExpanded)="addEvent('beforeExpanded')"
5+
(afterExpanded)="addEvent('afterExpanded')"
6+
(beforeCollapsed)="addEvent('beforeCollapsed')"
7+
(afterCollapsed)="addEvent('afterCollapsed')">
48
<mat-expansion-panel-header [expandedHeight]="expandedHeight" [collapsedHeight]="collapsedHeight">
59
<mat-panel-description>This is a panel description.</mat-panel-description>
610
<mat-panel-title>Panel Title</mat-panel-title>
@@ -29,6 +33,11 @@ <h1>Single Expansion Panel</h1>
2933
</mat-form-field>
3034
<br>
3135

36+
<p>Expansion Panel Animation Events</p>
37+
<code class="demo-expansion-code">
38+
<pre *ngFor="let event of events">{{event}}</pre>
39+
</code>
40+
3241
<h1>matAccordion</h1>
3342
<div>
3443
<p>Accordion Options</p>
@@ -56,7 +65,7 @@ <h1>matAccordion</h1>
5665
</div>
5766
<br>
5867
<mat-accordion [displayMode]="displayMode" [multi]="multi"
59-
class="demo-expansion-width">
68+
class="demo-expansion-width">
6069
<mat-expansion-panel #panel1 [hideToggle]="hideToggle">
6170
<mat-expansion-panel-header>Section 1</mat-expansion-panel-header>
6271
<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: 31 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,18 @@ export class MatExpansionPanel extends CdkAccordionItem
7880
}
7981
private _hideToggle = false;
8082

83+
/** An event emitted before the body's expansion animation happens. */
84+
@Output() beforeExpand = new EventEmitter<void>();
85+
86+
/** An event emitted after the body's expansion animation happens. */
87+
@Output() afterExpand = new EventEmitter<void>();
88+
89+
/** An event emitted before the body's collapse animation happens. */
90+
@Output() beforeCollapse = new EventEmitter<void>();
91+
92+
/** An event emitted after the body's collapse animation happens. */
93+
@Output() afterCollapse = new EventEmitter<void>();
94+
8195
/** Stream that emits for changes in `@Input` properties. */
8296
readonly _inputChanges = new Subject<SimpleChanges>();
8397

@@ -147,17 +161,31 @@ export class MatExpansionPanel extends CdkAccordionItem
147161
_bodyAnimation(event: AnimationEvent) {
148162
const classList = event.element.classList;
149163
const cssClass = 'mat-expanded';
150-
const {phaseName, toState} = event;
164+
const {phaseName, toState, fromState} = event;
151165

152166
// Toggle the body's `overflow: hidden` class when closing starts or when expansion ends in
153167
// order to prevent the cases where switching too early would cause the animation to jump.
154168
// Note that we do it directly on the DOM element to avoid the slight delay that comes
155169
// with doing it via change detection.
156170
if (phaseName === 'done' && toState === 'expanded') {
157171
classList.add(cssClass);
158-
} else if (phaseName === 'start' && toState === 'collapsed') {
172+
}
173+
if (phaseName === 'start' && toState === 'collapsed') {
159174
classList.remove(cssClass);
160175
}
176+
177+
if (phaseName === 'done' && toState === 'expanded' && fromState !== 'void') {
178+
this.afterExpand.emit();
179+
}
180+
if (phaseName === 'done' && toState === 'collapsed' && fromState !== 'void') {
181+
this.afterCollapse.emit();
182+
}
183+
if (phaseName === 'start' && toState === 'expanded' && fromState !== 'void') {
184+
this.beforeExpand.emit();
185+
}
186+
if (phaseName === 'start' && toState === 'collapsed' && fromState !== 'void') {
187+
this.beforeCollapse.emit();
188+
}
161189
}
162190
}
163191

src/lib/expansion/expansion.spec.ts

Lines changed: 40 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,35 @@ 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 beforeExpand = 0;
273+
let afterExpand = 0;
274+
let beforeCollaps = 0;
275+
let afterCollaps = 0;
276+
fixture.componentInstance.panel.beforeExpand.subscribe(() => beforeExpand++);
277+
fixture.componentInstance.panel.afterExpand.subscribe(() => afterExpand++);
278+
fixture.componentInstance.panel.beforeCollapse.subscribe(() => beforeCollaps++);
279+
fixture.componentInstance.panel.afterCollapse.subscribe(() => afterCollaps++);
280+
281+
fixture.componentInstance.expanded = true;
282+
fixture.detectChanges();
283+
flush();
284+
expect(beforeExpand).toBe(1);
285+
expect(afterExpand).toBe(1);
286+
expect(beforeCollaps).toBe(0);
287+
expect(afterCollaps).toBe(0);
288+
289+
fixture.componentInstance.expanded = false;
290+
fixture.detectChanges();
291+
flush();
292+
expect(beforeExpand).toBe(1);
293+
expect(afterExpand).toBe(1);
294+
expect(beforeCollaps).toBe(1);
295+
expect(afterCollaps).toBe(1);
296+
}));
297+
267298
describe('disabled state', () => {
268299
let fixture: ComponentFixture<PanelWithContent>;
269300
let panel: HTMLElement;
@@ -373,7 +404,7 @@ class PanelWithContentInNgIf {
373404
</mat-expansion-panel>`
374405
})
375406
class PanelWithCustomMargin {
376-
expanded: boolean = false;
407+
expanded = false;
377408
}
378409

379410
@Component({

0 commit comments

Comments
 (0)