From 05610e6194216177bccbd13505126ae69d28e6d9 Mon Sep 17 00:00:00 2001 From: ViktorSlavov Date: Sun, 4 Oct 2020 19:39:02 +0300 Subject: [PATCH 1/2] feat(expansion-panel): make header onInteraction cancelable, add iconRef, #8190 --- CHANGELOG.md | 5 ++ .../src/lib/expansion-panel/README.md | 3 +- .../expansion-panel-header.component.ts | 47 ++++++++-- .../expansion-panel/expansion-panel.common.ts | 10 ++- .../expansion-panel.component.ts | 4 +- .../expansion-panel.directives.ts | 6 +- .../expansion-panel/expansion-panel.spec.ts | 90 ++++++++++++++++++- 7 files changed, 147 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ad0554b5b9..4bd19c2ddc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ All notable changes for each version of this project will be documented in this - **Breaking Change** - Deprecated the `label` property. - `igxGridActions` - Added `asMenuItems` Input for grid actions - `igx-grid-editing-actions`, `igx-grid-pinning-actions`. When set to true will render the related action buttons as separate menu items with button and label. +- `IgxExpansionPanel` + - `IExpansionPanelEventArgs.panel` - Deprecated. Usе `owner` property to get a reference to the panel. ### New Features @@ -54,6 +56,9 @@ All notable changes for each version of this project will be documented in this - The component now utilizes the `IgxOverlayService` to position itself in the DOM. - An additional input property `outlet` has been added to allow users to specify custom Overlay Outlets using the `IgxOverlayOutletDirective`; - The `position` property now accepts values of type `IgxToastPosition` that work with strict templates. +- `IgxExpansionPanelHeader` + - `onInteraction` is now cancelable + - Added `iconRef` property. This can be used to get a reference to the displayed expand/collapsed indicator. Returns `null` if `iconPosition` is set to `NONE`. ## 10.1.0 diff --git a/projects/igniteui-angular/src/lib/expansion-panel/README.md b/projects/igniteui-angular/src/lib/expansion-panel/README.md index 706e9b92490..05c29965e4c 100644 --- a/projects/igniteui-angular/src/lib/expansion-panel/README.md +++ b/projects/igniteui-angular/src/lib/expansion-panel/README.md @@ -81,6 +81,7 @@ The following inputs are available in the **igx-expansion-panel-header** compone | `role` | `string` | The `role` attribute of the header | | `iconPosition` | `string` | The position of the expand/collapse icon of the header | | `disabled` | `boolean` | Gets/sets whether the panel header is disabled (blocking user interaction) or not | +| `iconRef` | `ElementRef` | Gets the reference to the element being used as expand/collapse indicator. If `iconPosition` is `NONE` - return `null` | ### Outputs @@ -88,7 +89,7 @@ The following outputs are available in the **igx-expansion-panel-header** compon | Name | Cancelable | Description | Parameters | :--- | :--- | :--- | :--- | -| `onInteraction` | `false` | Emitted when a user interacts with the header host | `{ event: Event, panel: IgxExpansionPanelComponent }` | +| `onInteraction` | `true` | Emitted when a user interacts with the header host | `{ event: Event, panel: IgxExpansionPanelComponent, cancel: boolean }` | ## IgxExpansionPanelBodyComponent ### Inputs diff --git a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.ts b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.ts index dd65ce63912..09819bfc53d 100644 --- a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.ts +++ b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.ts @@ -9,11 +9,13 @@ import { EventEmitter, Output, ContentChild, - Inject + Inject, + ViewChild } from '@angular/core'; import { IgxExpansionPanelIconDirective } from './expansion-panel.directives'; -import { IExpansionPanelEventArgs, IGX_EXPANSION_PANEL_COMPONENT, IgxExpansionPanelBase } from './expansion-panel.common'; +import { IGX_EXPANSION_PANEL_COMPONENT, IgxExpansionPanelBase, IExpansionPanelCancelableEventArgs } from './expansion-panel.common'; import { mkenum } from '../core/utils'; +import { IgxIconComponent } from '../icon/public_api'; /** * @hidden @@ -42,6 +44,25 @@ export class IgxExpansionPanelHeaderComponent { */ public id = ''; + /** @hidden @internal */ + @ContentChild(IgxExpansionPanelIconDirective, { read: ElementRef }) + private customIconRef: ElementRef; + + /** @hidden @internal */ + @ViewChild(IgxIconComponent, { read: IgxIconComponent }) + public defaultIconRef: IgxIconComponent; + + /** + * Returns a reference to the `igx-expansion-panel-icon` element; + * If `iconPosition` is `NONE` - return null; + */ + public get iconRef(): ElementRef { + const defaultRef = this.defaultIconRef ? this.defaultIconRef.el : null; + const customRef = this.customIconRef ? this.customIconRef : null; + const renderedTemplate = this.iconTemplate ? customRef : defaultRef; + return this.iconPosition !== ICON_POSITION.NONE ? renderedTemplate : null; + } + /** * @hidden */ @@ -120,7 +141,7 @@ export class IgxExpansionPanelHeaderComponent { /** * Emitted whenever a user interacts with the header host * ```typescript - * handleInteraction(event: IExpansionPanelEventArgs) { + * handleInteraction(event: IExpansionPanelCancelabelEventArgs) { * ... * } * ``` @@ -131,7 +152,7 @@ export class IgxExpansionPanelHeaderComponent { * ``` */ @Output() - public onInteraction = new EventEmitter(); + public onInteraction = new EventEmitter(); /** * @hidden @@ -185,7 +206,11 @@ export class IgxExpansionPanelHeaderComponent { evt.stopPropagation(); return; } - this.onInteraction.emit({ event: evt, panel: this.panel }); + const eventArgs: IExpansionPanelCancelableEventArgs = { event: evt, panel: this.panel, owner: this.panel, cancel: false }; + this.onInteraction.emit(eventArgs); + if (eventArgs.cancel === true) { + return; + } this.panel.toggle(evt); evt.preventDefault(); } @@ -194,8 +219,12 @@ export class IgxExpansionPanelHeaderComponent { @HostListener('keydown.Alt.ArrowDown', ['$event']) public openPanel(event: KeyboardEvent) { if (event.altKey) { + const eventArgs: IExpansionPanelCancelableEventArgs = { event, panel: this.panel, owner: this.panel, cancel: false }; + this.onInteraction.emit(eventArgs); + if (eventArgs.cancel === true) { + return; + } this.panel.expand(event); - this.onInteraction.emit({ event: event, panel: this.panel }); } } @@ -203,8 +232,12 @@ export class IgxExpansionPanelHeaderComponent { @HostListener('keydown.Alt.ArrowUp', ['$event']) public closePanel(event: KeyboardEvent) { if (event.altKey) { + const eventArgs: IExpansionPanelCancelableEventArgs = { event, panel: this.panel, owner: this.panel, cancel: false }; + this.onInteraction.emit(eventArgs); + if (eventArgs.cancel === true) { + return; + } this.panel.collapse(event); - this.onInteraction.emit({ event: event, panel: this.panel }); } } diff --git a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.common.ts b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.common.ts index 9388cc471b0..db7e5b074e7 100644 --- a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.common.ts +++ b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.common.ts @@ -1,6 +1,6 @@ import { EventEmitter, InjectionToken } from '@angular/core'; import { AnimationReferenceMetadata } from '@angular/animations'; -import { IBaseEventArgs } from '../core/utils'; +import { CancelableEventArgs, IBaseEventArgs } from '../core/utils'; export interface IgxExpansionPanelBase { id: string; @@ -21,5 +21,11 @@ export const IGX_EXPANSION_PANEL_COMPONENT = new InjectionToken { - this.onCollapsed.emit({ event: evt, panel: this }); + this.onCollapsed.emit({ event: evt, panel: this, owner: this }); this.collapsed = true; } ); @@ -253,7 +253,7 @@ export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterC this.cdr.detectChanges(); this.playOpenAnimation( () => { - this.onExpanded.emit({ event: evt, panel: this }); + this.onExpanded.emit({ event: evt, panel: this, owner: this }); } ); } diff --git a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.directives.ts b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.directives.ts index 6b7fa415964..8865d32e6ff 100644 --- a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.directives.ts +++ b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.directives.ts @@ -1,7 +1,7 @@ import { Directive, HostBinding } from '@angular/core'; /** - * @hidden + * @hidden @internal */ @Directive({ // tslint:disable-next-line:directive-selector @@ -13,7 +13,7 @@ export class IgxExpansionPanelTitleDirective { } /** - * @hidden + * @hidden @internal */ @Directive({ // tslint:disable-next-line:directive-selector @@ -25,7 +25,7 @@ export class IgxExpansionPanelDescriptionDirective { } /** - * @hidden + * @hidden @internal */ @Directive({ // tslint:disable-next-line:directive-selector diff --git a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.spec.ts b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.spec.ts index 5b0c520c92f..99879a0f073 100644 --- a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.spec.ts +++ b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.spec.ts @@ -114,7 +114,7 @@ describe('igxExpansionPanel', () => { spyOn(panel.onCollapsed, 'emit'); spyOn(panel.onExpanded, 'emit'); - spyOn(header.onInteraction, 'emit'); + spyOn(header.onInteraction, 'emit').and.callThrough(); spyOn(panel, 'toggle').and.callThrough(); spyOn(panel, 'expand').and.callThrough(); spyOn(panel, 'collapse').and.callThrough(); @@ -130,7 +130,9 @@ describe('igxExpansionPanel', () => { expect(panel.expand).toHaveBeenCalledWith(mockEvent); expect(panel.collapse).toHaveBeenCalledTimes(0); expect(panel.onExpanded.emit).toHaveBeenCalledTimes(1); - expect(header.onInteraction.emit).toHaveBeenCalledWith({ event: mockEvent, panel: header.panel }); + expect(header.onInteraction.emit).toHaveBeenCalledWith({ + event: mockEvent, panel: header.panel, owner: header.panel, cancel: false + }); header.onAction(mockEvent); tick(); @@ -153,6 +155,40 @@ describe('igxExpansionPanel', () => { expect(panel.onCollapsed.emit).toHaveBeenCalledTimes(1); expect(header.onInteraction.emit).toHaveBeenCalledTimes(2); expect(panel.onExpanded.emit).toHaveBeenCalledTimes(1); + + // cancel event + header.disabled = false; + const headerSub = header.onInteraction.subscribe((event) => { + event.cancel = true; + }); + + // currently collapsed + expect(panel.collapsed).toBeTruthy(); + header.onAction(mockEvent); + tick(); + fixture.detectChanges(); + + // still collapsed, no additional onExpanded calls + expect(panel.collapsed).toBeTruthy(); + expect(header.onInteraction.emit).toHaveBeenCalledTimes(3); + expect(panel.onExpanded.emit).toHaveBeenCalledTimes(1); + + // expand via API + panel.expand(); + tick(); + fixture.detectChanges(); + + // currently expanded + expect(panel.collapsed).toBeFalsy(); + header.onAction(mockEvent); + tick(); + fixture.detectChanges(); + + // still expanded, no additional onCollapsed calls + headerSub.unsubscribe(); + expect(panel.collapsed).toBeFalsy(); + expect(header.onInteraction.emit).toHaveBeenCalledTimes(4); + expect(panel.onCollapsed.emit).toHaveBeenCalledTimes(1); })); }); @@ -447,7 +483,7 @@ describe('igxExpansionPanel', () => { let timesExpanded = 0; spyOn(panel.onCollapsed, 'emit').and.callThrough(); spyOn(panel.onExpanded, 'emit').and.callThrough(); - spyOn(header.onInteraction, 'emit'); + spyOn(header.onInteraction, 'emit').and.callThrough(); verifyPanelExpansionState(true, panel, panelContainer, panelHeader, button, timesCollapsed, timesExpanded); panelHeader.dispatchEvent(enterEvent); @@ -511,6 +547,41 @@ describe('igxExpansionPanel', () => { timesCollapsed++; verifyPanelExpansionState(true, panel, panelContainer, panelHeader, button, timesCollapsed, timesExpanded); expect(header.onInteraction.emit).toHaveBeenCalledTimes(8); + + // disabled interaction + const headerSub = header.onInteraction.subscribe((event) => { + event.cancel = true; + }); + + // currently collapsed + expect(panel.collapsed).toEqual(true); + + // cancel openening + panelHeader.dispatchEvent(arrowDownEvent); + fixture.detectChanges(); + tick(); + // do not iterate timesExpanded + verifyPanelExpansionState(true, panel, panelContainer, panelHeader, button, timesCollapsed, timesExpanded); + expect(header.onInteraction.emit).toHaveBeenCalledTimes(9); + + // open through API + panel.expand(); + timesExpanded++; + tick(); + fixture.detectChanges(); + + // currently expanded + expect(panel.collapsed).toEqual(false); + + // cancel closing + panelHeader.dispatchEvent(arrowUpEvent); + fixture.detectChanges(); + tick(); + // do not iterate timesCollapsed + verifyPanelExpansionState(false, panel, panelContainer, panelHeader, button, timesCollapsed, timesExpanded); + expect(header.onInteraction.emit).toHaveBeenCalledTimes(10); + + headerSub.unsubscribe(); })); it('Should change panel expansion when using different methods', fakeAsync(() => { const fixture: ComponentFixture = TestBed.createComponent(IgxExpansionPanelListComponent); @@ -727,15 +798,28 @@ describe('igxExpansionPanel', () => { expect(headerButton.children.length).toEqual(2); expect(iconContainer.firstElementChild.nodeName).toEqual('IGX-ICON'); expect(titleContainer.firstElementChild.nodeName).toEqual('IGX-EXPANSION-PANEL-TITLE'); + expect(header.iconRef).not.toBe(null); + expect(header.iconRef.nativeElement).toEqual(iconContainer.firstElementChild); fixture.componentInstance.customIcon = true; fixture.detectChanges(); expect(iconContainer.firstElementChild.nodeName).toEqual('IGX-EXPANSION-PANEL-ICON'); expect(titleContainer.firstElementChild.nodeName).toEqual('IGX-EXPANSION-PANEL-TITLE'); + expect(header.iconRef).not.toBe(null); + expect(header.iconRef.nativeElement).toEqual(iconContainer.firstElementChild); + + fixture.componentInstance.header.iconPosition = ICON_POSITION.NONE; + fixture.detectChanges(); + expect(header.iconRef).toEqual(null); fixture.componentInstance.customIcon = false; fixture.detectChanges(); + expect(header.iconRef).toEqual(null); + + fixture.componentInstance.header.iconPosition = ICON_POSITION.LEFT; + fixture.detectChanges(); + expect(header.iconRef).not.toBe(null); expect(iconContainer.firstElementChild.nodeName).toEqual('IGX-ICON'); expect(titleContainer.firstElementChild.nodeName).toEqual('IGX-EXPANSION-PANEL-TITLE'); From 92e84078d8c98e7699bfe5610b16ec879a0254db Mon Sep 17 00:00:00 2001 From: ViktorSlavov Date: Tue, 6 Oct 2020 16:08:32 +0300 Subject: [PATCH 2/2] chore(expansion-panel): address review comments --- .../igniteui-angular/src/lib/expansion-panel/README.md | 6 +++--- .../expansion-panel-header.component.ts | 10 ++++------ .../lib/expansion-panel/expansion-panel.component.ts | 10 ++-------- src/app/expansion-panel/expansion-panel-sample.html | 2 +- src/app/expansion-panel/expansion-panel-sample.ts | 5 +++-- 5 files changed, 13 insertions(+), 20 deletions(-) diff --git a/projects/igniteui-angular/src/lib/expansion-panel/README.md b/projects/igniteui-angular/src/lib/expansion-panel/README.md index 05c29965e4c..e2135a7e1aa 100644 --- a/projects/igniteui-angular/src/lib/expansion-panel/README.md +++ b/projects/igniteui-angular/src/lib/expansion-panel/README.md @@ -56,8 +56,8 @@ The following outputs are available in the **igx-expansion-panel** component: | Name | Cancelable | Description | Parameters | :--- | :--- | :--- | :--- | -| `onCollapsed` | `false` | Emitted when the panel is collapsed | `{ event: Event, panel: IgxExpansionPanelComponent }` | -| `onExpanded` | `false` | Emitted when the panel is expanded | `{ event: Event, panel: IgxExpansionPanelComponent }` | +| `onCollapsed` | `false` | Emitted when the panel is collapsed | `IExpansionPanelEventArgs` | +| `onExpanded` | `false` | Emitted when the panel is expanded | `IExpansionPanelEventArgs` | ### Methods @@ -89,7 +89,7 @@ The following outputs are available in the **igx-expansion-panel-header** compon | Name | Cancelable | Description | Parameters | :--- | :--- | :--- | :--- | -| `onInteraction` | `true` | Emitted when a user interacts with the header host | `{ event: Event, panel: IgxExpansionPanelComponent, cancel: boolean }` | +| `onInteraction` | `true` | Emitted when a user interacts with the header host | `IExpansionPanelCancelableEventArgs` | ## IgxExpansionPanelBodyComponent ### Inputs diff --git a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.ts b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.ts index 09819bfc53d..7a4f8966b34 100644 --- a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.ts +++ b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.ts @@ -49,17 +49,15 @@ export class IgxExpansionPanelHeaderComponent { private customIconRef: ElementRef; /** @hidden @internal */ - @ViewChild(IgxIconComponent, { read: IgxIconComponent }) - public defaultIconRef: IgxIconComponent; + @ViewChild(IgxIconComponent, { read: ElementRef }) + public defaultIconRef: ElementRef; /** * Returns a reference to the `igx-expansion-panel-icon` element; * If `iconPosition` is `NONE` - return null; */ public get iconRef(): ElementRef { - const defaultRef = this.defaultIconRef ? this.defaultIconRef.el : null; - const customRef = this.customIconRef ? this.customIconRef : null; - const renderedTemplate = this.iconTemplate ? customRef : defaultRef; + const renderedTemplate = this.customIconRef ?? this.defaultIconRef; return this.iconPosition !== ICON_POSITION.NONE ? renderedTemplate : null; } @@ -141,7 +139,7 @@ export class IgxExpansionPanelHeaderComponent { /** * Emitted whenever a user interacts with the header host * ```typescript - * handleInteraction(event: IExpansionPanelCancelabelEventArgs) { + * handleInteraction(event: IExpansionPanelCancelableEventArgs) { * ... * } * ``` diff --git a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.component.ts b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.component.ts index 17de418129d..136992fc09f 100644 --- a/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.component.ts +++ b/projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.component.ts @@ -119,10 +119,7 @@ export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterC /** * Emitted when the expansion panel finishes collapsing * ```typescript - * handleCollapsed(event: { - * panel: IgxExpansionPanelComponent, - * event: Event - * }) + * handleCollapsed(event: IExpansionPanelEventArgs) * ``` * ```html * @@ -136,10 +133,7 @@ export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterC /** * Emitted when the expansion panel finishes expanding * ```typescript - * handleExpanded(event: { - * panel: IgxExpansionPanelComponent, - * event: Event - * }) + * handleExpanded(event: IExpansionPanelEventArgs) * ``` * ```html * diff --git a/src/app/expansion-panel/expansion-panel-sample.html b/src/app/expansion-panel/expansion-panel-sample.html index 0fd387ec3e4..f14cfa9dc7a 100644 --- a/src/app/expansion-panel/expansion-panel-sample.html +++ b/src/app/expansion-panel/expansion-panel-sample.html @@ -7,7 +7,7 @@
- Current Winning Player: {{ getWinningPlayer }} diff --git a/src/app/expansion-panel/expansion-panel-sample.ts b/src/app/expansion-panel/expansion-panel-sample.ts index b78ed6b9959..5fa9b21eecf 100644 --- a/src/app/expansion-panel/expansion-panel-sample.ts +++ b/src/app/expansion-panel/expansion-panel-sample.ts @@ -1,4 +1,4 @@ -import { IgxExpansionPanelComponent, growVerIn, growVerOut, scaleInVerTop } from 'igniteui-angular'; +import { IgxExpansionPanelComponent, growVerIn, growVerOut, scaleInVerTop, IExpansionPanelEventArgs } from 'igniteui-angular'; import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; import { AnimationReferenceMetadata, useAnimation } from '@angular/animations'; @@ -83,7 +83,8 @@ export class ExpansionPanelSampleComponent implements OnInit { handleExpanded(event) { console.log(`I'm expanding!`, event); } - onInterraction(event) { + onInteraction(event: IExpansionPanelEventArgs) { + console.log(event.owner); console.log(`Header's touched!`, event); }