From f6b1002b286c134950256294478ab9ca15887536 Mon Sep 17 00:00:00 2001 From: Joey Perrott Date: Thu, 16 Aug 2018 14:37:19 -0700 Subject: [PATCH] feat(expansion): add animation events for expansion panels (#12412) --- src/demo-app/expansion/expansion-demo.html | 11 ++++-- src/demo-app/expansion/expansion-demo.scss | 13 +++++++ src/demo-app/expansion/expansion-demo.ts | 5 +++ src/lib/expansion/expansion-panel.ts | 22 ++++++++++-- src/lib/expansion/expansion.spec.ts | 41 +++++++++++++++++----- 5 files changed, 78 insertions(+), 14 deletions(-) diff --git a/src/demo-app/expansion/expansion-demo.html b/src/demo-app/expansion/expansion-demo.html index 29c4cecc995..414078a94e1 100644 --- a/src/demo-app/expansion/expansion-demo.html +++ b/src/demo-app/expansion/expansion-demo.html @@ -1,6 +1,8 @@

Single Expansion Panel

- + This is a panel description. Panel Title @@ -29,6 +31,11 @@
+

Expansion Panel Animation Events

+ +
{{event}}
+
+

matAccordion

Accordion Options

@@ -56,7 +63,7 @@

+ class="demo-expansion-width"> Section 1

This is the content text that makes sense here.

diff --git a/src/demo-app/expansion/expansion-demo.scss b/src/demo-app/expansion/expansion-demo.scss index 089784b09d8..09ccb98e40f 100644 --- a/src/demo-app/expansion/expansion-demo.scss +++ b/src/demo-app/expansion/expansion-demo.scss @@ -2,3 +2,16 @@ width: 600px; display: block; } + +.demo-expansion-code { + background: #d3d3d3; + display: block; + height: 10em; + overflow: auto; + padding: 0 5px; + width: 400px; + + pre { + margin: 0.25em 0; + } +} diff --git a/src/demo-app/expansion/expansion-demo.ts b/src/demo-app/expansion/expansion-demo.ts index 565ab4b1d53..54e6737af38 100644 --- a/src/demo-app/expansion/expansion-demo.ts +++ b/src/demo-app/expansion/expansion-demo.ts @@ -26,4 +26,9 @@ export class ExpansionDemo { showPanel3 = true; expandedHeight: string; collapsedHeight: string; + events: string[] = []; + + addEvent(eventName: string) { + this.events.push(`${eventName} - ${new Date().toISOString()}`); + } } diff --git a/src/lib/expansion/expansion-panel.ts b/src/lib/expansion/expansion-panel.ts index d7348f9da96..e40071b8d40 100644 --- a/src/lib/expansion/expansion-panel.ts +++ b/src/lib/expansion/expansion-panel.ts @@ -18,10 +18,12 @@ import { Component, ContentChild, Directive, + EventEmitter, Input, OnChanges, OnDestroy, Optional, + Output, SimpleChanges, SkipSelf, ViewContainerRef, @@ -69,7 +71,7 @@ let uniqueId = 0; } }) export class MatExpansionPanel extends CdkAccordionItem - implements AfterContentInit, OnChanges, OnDestroy { + implements AfterContentInit, OnChanges, OnDestroy { /** Whether the toggle indicator should be hidden. */ @Input() get hideToggle(): boolean { return this._hideToggle; } @@ -78,6 +80,12 @@ export class MatExpansionPanel extends CdkAccordionItem } private _hideToggle = false; + /** An event emitted after the body's expansion animation happens. */ + @Output() afterExpand = new EventEmitter(); + + /** An event emitted after the body's collapse animation happens. */ + @Output() afterCollapse = new EventEmitter(); + /** Stream that emits for changes in `@Input` properties. */ readonly _inputChanges = new Subject(); @@ -147,7 +155,7 @@ export class MatExpansionPanel extends CdkAccordionItem _bodyAnimation(event: AnimationEvent) { const classList = event.element.classList; const cssClass = 'mat-expanded'; - const {phaseName, toState} = event; + const {phaseName, toState, fromState} = event; // Toggle the body's `overflow: hidden` class when closing starts or when expansion ends in // order to prevent the cases where switching too early would cause the animation to jump. @@ -155,9 +163,17 @@ export class MatExpansionPanel extends CdkAccordionItem // with doing it via change detection. if (phaseName === 'done' && toState === 'expanded') { classList.add(cssClass); - } else if (phaseName === 'start' && toState === 'collapsed') { + } + if (phaseName === 'start' && toState === 'collapsed') { classList.remove(cssClass); } + + if (phaseName === 'done' && toState === 'expanded' && fromState !== 'void') { + this.afterExpand.emit(); + } + if (phaseName === 'done' && toState === 'collapsed' && fromState !== 'void') { + this.afterCollapse.emit(); + } } } diff --git a/src/lib/expansion/expansion.spec.ts b/src/lib/expansion/expansion.spec.ts index 914dd95a2a8..4af9dce54d3 100644 --- a/src/lib/expansion/expansion.spec.ts +++ b/src/lib/expansion/expansion.spec.ts @@ -11,8 +11,8 @@ describe('MatExpansionPanel', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [ + MatExpansionModule, NoopAnimationsModule, - MatExpansionModule ], declarations: [ PanelWithContent, @@ -44,8 +44,9 @@ describe('MatExpansionPanel', () => { })); it('should be able to render panel content lazily', fakeAsync(() => { - let fixture = TestBed.createComponent(LazyPanelWithContent); - let content = fixture.debugElement.query(By.css('.mat-expansion-panel-content')).nativeElement; + const fixture = TestBed.createComponent(LazyPanelWithContent); + const content = fixture.debugElement.query( + By.css('.mat-expansion-panel-content')).nativeElement; fixture.detectChanges(); expect(content.textContent.trim()).toBe('', 'Expected content element to be empty.'); @@ -58,8 +59,9 @@ describe('MatExpansionPanel', () => { })); it('should render the content for a lazy-loaded panel that is opened on init', fakeAsync(() => { - let fixture = TestBed.createComponent(LazyPanelOpenOnLoad); - let content = fixture.debugElement.query(By.css('.mat-expansion-panel-content')).nativeElement; + const fixture = TestBed.createComponent(LazyPanelOpenOnLoad); + const content = fixture.debugElement.query( + By.css('.mat-expansion-panel-content')).nativeElement; fixture.detectChanges(); expect(content.textContent.trim()) @@ -161,10 +163,10 @@ describe('MatExpansionPanel', () => { })); it('should not override the panel margin if it is not inside an accordion', fakeAsync(() => { - let fixture = TestBed.createComponent(PanelWithCustomMargin); + const fixture = TestBed.createComponent(PanelWithCustomMargin); fixture.detectChanges(); - let panel = fixture.debugElement.query(By.css('mat-expansion-panel')); + const panel = fixture.debugElement.query(By.css('mat-expansion-panel')); let styles = getComputedStyle(panel.nativeElement); expect(panel.componentInstance._hasSpacing()).toBe(false); @@ -221,7 +223,7 @@ describe('MatExpansionPanel', () => { })); it('should make sure accordion item runs ngOnDestroy when expansion panel is destroyed', () => { - let fixture = TestBed.createComponent(PanelWithContentInNgIf); + const fixture = TestBed.createComponent(PanelWithContentInNgIf); fixture.detectChanges(); let destroyedOk = false; fixture.componentInstance.panel.destroyed.subscribe(() => destroyedOk = true); @@ -264,6 +266,27 @@ describe('MatExpansionPanel', () => { 'Expected class to be added after the animation has finished'); })); + it('should emit events for body expanding and collapsing animations', fakeAsync(() => { + const fixture = TestBed.createComponent(PanelWithContent); + fixture.detectChanges(); + let afterExpand = 0; + let afterCollapse = 0; + fixture.componentInstance.panel.afterExpand.subscribe(() => afterExpand++); + fixture.componentInstance.panel.afterCollapse.subscribe(() => afterCollapse++); + + fixture.componentInstance.expanded = true; + fixture.detectChanges(); + flush(); + expect(afterExpand).toBe(1); + expect(afterCollapse).toBe(0); + + fixture.componentInstance.expanded = false; + fixture.detectChanges(); + flush(); + expect(afterExpand).toBe(1); + expect(afterCollapse).toBe(1); + })); + describe('disabled state', () => { let fixture: ComponentFixture; let panel: HTMLElement; @@ -373,7 +396,7 @@ class PanelWithContentInNgIf {
` }) class PanelWithCustomMargin { - expanded: boolean = false; + expanded = false; } @Component({