Skip to content

Commit 05610e6

Browse files
committed
feat(expansion-panel): make header onInteraction cancelable, add iconRef, #8190
1 parent 2b5c0d6 commit 05610e6

File tree

7 files changed

+147
-18
lines changed

7 files changed

+147
-18
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ All notable changes for each version of this project will be documented in this
1717
- **Breaking Change** - Deprecated the `label` property.
1818
- `igxGridActions`
1919
- 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.
20+
- `IgxExpansionPanel`
21+
- `IExpansionPanelEventArgs.panel` - Deprecated. Usе `owner` property to get a reference to the panel.
2022

2123

2224
### New Features
@@ -54,6 +56,9 @@ All notable changes for each version of this project will be documented in this
5456
- The component now utilizes the `IgxOverlayService` to position itself in the DOM.
5557
- An additional input property `outlet` has been added to allow users to specify custom Overlay Outlets using the `IgxOverlayOutletDirective`;
5658
- The `position` property now accepts values of type `IgxToastPosition` that work with strict templates.
59+
- `IgxExpansionPanelHeader`
60+
- `onInteraction` is now cancelable
61+
- 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`.
5762

5863
## 10.1.0
5964

projects/igniteui-angular/src/lib/expansion-panel/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,15 @@ The following inputs are available in the **igx-expansion-panel-header** compone
8181
| `role` | `string` | The `role` attribute of the header |
8282
| `iconPosition` | `string` | The position of the expand/collapse icon of the header |
8383
| `disabled` | `boolean` | Gets/sets whether the panel header is disabled (blocking user interaction) or not |
84+
| `iconRef` | `ElementRef` | Gets the reference to the element being used as expand/collapse indicator. If `iconPosition` is `NONE` - return `null` |
8485

8586

8687
### Outputs
8788
The following outputs are available in the **igx-expansion-panel-header** component:
8889

8990
| Name | Cancelable | Description | Parameters
9091
| :--- | :--- | :--- | :--- |
91-
| `onInteraction` | `false` | Emitted when a user interacts with the header host | `{ event: Event, panel: IgxExpansionPanelComponent }` |
92+
| `onInteraction` | `true` | Emitted when a user interacts with the header host | `{ event: Event, panel: IgxExpansionPanelComponent, cancel: boolean }` |
9293

9394
## IgxExpansionPanelBodyComponent
9495
### Inputs

projects/igniteui-angular/src/lib/expansion-panel/expansion-panel-header.component.ts

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import {
99
EventEmitter,
1010
Output,
1111
ContentChild,
12-
Inject
12+
Inject,
13+
ViewChild
1314
} from '@angular/core';
1415
import { IgxExpansionPanelIconDirective } from './expansion-panel.directives';
15-
import { IExpansionPanelEventArgs, IGX_EXPANSION_PANEL_COMPONENT, IgxExpansionPanelBase } from './expansion-panel.common';
16+
import { IGX_EXPANSION_PANEL_COMPONENT, IgxExpansionPanelBase, IExpansionPanelCancelableEventArgs } from './expansion-panel.common';
1617
import { mkenum } from '../core/utils';
18+
import { IgxIconComponent } from '../icon/public_api';
1719

1820
/**
1921
* @hidden
@@ -42,6 +44,25 @@ export class IgxExpansionPanelHeaderComponent {
4244
*/
4345
public id = '';
4446

47+
/** @hidden @internal */
48+
@ContentChild(IgxExpansionPanelIconDirective, { read: ElementRef })
49+
private customIconRef: ElementRef;
50+
51+
/** @hidden @internal */
52+
@ViewChild(IgxIconComponent, { read: IgxIconComponent })
53+
public defaultIconRef: IgxIconComponent;
54+
55+
/**
56+
* Returns a reference to the `igx-expansion-panel-icon` element;
57+
* If `iconPosition` is `NONE` - return null;
58+
*/
59+
public get iconRef(): ElementRef {
60+
const defaultRef = this.defaultIconRef ? this.defaultIconRef.el : null;
61+
const customRef = this.customIconRef ? this.customIconRef : null;
62+
const renderedTemplate = this.iconTemplate ? customRef : defaultRef;
63+
return this.iconPosition !== ICON_POSITION.NONE ? renderedTemplate : null;
64+
}
65+
4566
/**
4667
* @hidden
4768
*/
@@ -120,7 +141,7 @@ export class IgxExpansionPanelHeaderComponent {
120141
/**
121142
* Emitted whenever a user interacts with the header host
122143
* ```typescript
123-
* handleInteraction(event: IExpansionPanelEventArgs) {
144+
* handleInteraction(event: IExpansionPanelCancelabelEventArgs) {
124145
* ...
125146
* }
126147
* ```
@@ -131,7 +152,7 @@ export class IgxExpansionPanelHeaderComponent {
131152
* ```
132153
*/
133154
@Output()
134-
public onInteraction = new EventEmitter<IExpansionPanelEventArgs>();
155+
public onInteraction = new EventEmitter<IExpansionPanelCancelableEventArgs >();
135156

136157
/**
137158
* @hidden
@@ -185,7 +206,11 @@ export class IgxExpansionPanelHeaderComponent {
185206
evt.stopPropagation();
186207
return;
187208
}
188-
this.onInteraction.emit({ event: evt, panel: this.panel });
209+
const eventArgs: IExpansionPanelCancelableEventArgs = { event: evt, panel: this.panel, owner: this.panel, cancel: false };
210+
this.onInteraction.emit(eventArgs);
211+
if (eventArgs.cancel === true) {
212+
return;
213+
}
189214
this.panel.toggle(evt);
190215
evt.preventDefault();
191216
}
@@ -194,17 +219,25 @@ export class IgxExpansionPanelHeaderComponent {
194219
@HostListener('keydown.Alt.ArrowDown', ['$event'])
195220
public openPanel(event: KeyboardEvent) {
196221
if (event.altKey) {
222+
const eventArgs: IExpansionPanelCancelableEventArgs = { event, panel: this.panel, owner: this.panel, cancel: false };
223+
this.onInteraction.emit(eventArgs);
224+
if (eventArgs.cancel === true) {
225+
return;
226+
}
197227
this.panel.expand(event);
198-
this.onInteraction.emit({ event: event, panel: this.panel });
199228
}
200229
}
201230

202231
/** @hidden @internal */
203232
@HostListener('keydown.Alt.ArrowUp', ['$event'])
204233
public closePanel(event: KeyboardEvent) {
205234
if (event.altKey) {
235+
const eventArgs: IExpansionPanelCancelableEventArgs = { event, panel: this.panel, owner: this.panel, cancel: false };
236+
this.onInteraction.emit(eventArgs);
237+
if (eventArgs.cancel === true) {
238+
return;
239+
}
206240
this.panel.collapse(event);
207-
this.onInteraction.emit({ event: event, panel: this.panel });
208241
}
209242
}
210243

projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.common.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { EventEmitter, InjectionToken } from '@angular/core';
22
import { AnimationReferenceMetadata } from '@angular/animations';
3-
import { IBaseEventArgs } from '../core/utils';
3+
import { CancelableEventArgs, IBaseEventArgs } from '../core/utils';
44

55
export interface IgxExpansionPanelBase {
66
id: string;
@@ -21,5 +21,11 @@ export const IGX_EXPANSION_PANEL_COMPONENT = new InjectionToken<IgxExpansionPane
2121

2222
export interface IExpansionPanelEventArgs extends IBaseEventArgs {
2323
event: Event;
24-
panel: IgxExpansionPanelBase;
24+
/**
25+
* @deprecated
26+
* To get a reference to the panel, use `owner` instead.
27+
*/
28+
panel?: IgxExpansionPanelBase;
2529
}
30+
31+
export interface IExpansionPanelCancelableEventArgs extends IExpansionPanelEventArgs, CancelableEventArgs {}

projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterC
229229
}
230230
this.playCloseAnimation(
231231
() => {
232-
this.onCollapsed.emit({ event: evt, panel: this });
232+
this.onCollapsed.emit({ event: evt, panel: this, owner: this });
233233
this.collapsed = true;
234234
}
235235
);
@@ -253,7 +253,7 @@ export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterC
253253
this.cdr.detectChanges();
254254
this.playOpenAnimation(
255255
() => {
256-
this.onExpanded.emit({ event: evt, panel: this });
256+
this.onExpanded.emit({ event: evt, panel: this, owner: this });
257257
}
258258
);
259259
}

projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.directives.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Directive, HostBinding } from '@angular/core';
22

33
/**
4-
* @hidden
4+
* @hidden @internal
55
*/
66
@Directive({
77
// tslint:disable-next-line:directive-selector
@@ -13,7 +13,7 @@ export class IgxExpansionPanelTitleDirective {
1313
}
1414

1515
/**
16-
* @hidden
16+
* @hidden @internal
1717
*/
1818
@Directive({
1919
// tslint:disable-next-line:directive-selector
@@ -25,7 +25,7 @@ export class IgxExpansionPanelDescriptionDirective {
2525
}
2626

2727
/**
28-
* @hidden
28+
* @hidden @internal
2929
*/
3030
@Directive({
3131
// tslint:disable-next-line:directive-selector

projects/igniteui-angular/src/lib/expansion-panel/expansion-panel.spec.ts

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ describe('igxExpansionPanel', () => {
114114

115115
spyOn(panel.onCollapsed, 'emit');
116116
spyOn(panel.onExpanded, 'emit');
117-
spyOn(header.onInteraction, 'emit');
117+
spyOn(header.onInteraction, 'emit').and.callThrough();
118118
spyOn(panel, 'toggle').and.callThrough();
119119
spyOn(panel, 'expand').and.callThrough();
120120
spyOn(panel, 'collapse').and.callThrough();
@@ -130,7 +130,9 @@ describe('igxExpansionPanel', () => {
130130
expect(panel.expand).toHaveBeenCalledWith(mockEvent);
131131
expect(panel.collapse).toHaveBeenCalledTimes(0);
132132
expect(panel.onExpanded.emit).toHaveBeenCalledTimes(1);
133-
expect(header.onInteraction.emit).toHaveBeenCalledWith({ event: mockEvent, panel: header.panel });
133+
expect(header.onInteraction.emit).toHaveBeenCalledWith({
134+
event: mockEvent, panel: header.panel, owner: header.panel, cancel: false
135+
});
134136

135137
header.onAction(mockEvent);
136138
tick();
@@ -153,6 +155,40 @@ describe('igxExpansionPanel', () => {
153155
expect(panel.onCollapsed.emit).toHaveBeenCalledTimes(1);
154156
expect(header.onInteraction.emit).toHaveBeenCalledTimes(2);
155157
expect(panel.onExpanded.emit).toHaveBeenCalledTimes(1);
158+
159+
// cancel event
160+
header.disabled = false;
161+
const headerSub = header.onInteraction.subscribe((event) => {
162+
event.cancel = true;
163+
});
164+
165+
// currently collapsed
166+
expect(panel.collapsed).toBeTruthy();
167+
header.onAction(mockEvent);
168+
tick();
169+
fixture.detectChanges();
170+
171+
// still collapsed, no additional onExpanded calls
172+
expect(panel.collapsed).toBeTruthy();
173+
expect(header.onInteraction.emit).toHaveBeenCalledTimes(3);
174+
expect(panel.onExpanded.emit).toHaveBeenCalledTimes(1);
175+
176+
// expand via API
177+
panel.expand();
178+
tick();
179+
fixture.detectChanges();
180+
181+
// currently expanded
182+
expect(panel.collapsed).toBeFalsy();
183+
header.onAction(mockEvent);
184+
tick();
185+
fixture.detectChanges();
186+
187+
// still expanded, no additional onCollapsed calls
188+
headerSub.unsubscribe();
189+
expect(panel.collapsed).toBeFalsy();
190+
expect(header.onInteraction.emit).toHaveBeenCalledTimes(4);
191+
expect(panel.onCollapsed.emit).toHaveBeenCalledTimes(1);
156192
}));
157193
});
158194

@@ -447,7 +483,7 @@ describe('igxExpansionPanel', () => {
447483
let timesExpanded = 0;
448484
spyOn(panel.onCollapsed, 'emit').and.callThrough();
449485
spyOn(panel.onExpanded, 'emit').and.callThrough();
450-
spyOn(header.onInteraction, 'emit');
486+
spyOn(header.onInteraction, 'emit').and.callThrough();
451487
verifyPanelExpansionState(true, panel, panelContainer, panelHeader, button, timesCollapsed, timesExpanded);
452488

453489
panelHeader.dispatchEvent(enterEvent);
@@ -511,6 +547,41 @@ describe('igxExpansionPanel', () => {
511547
timesCollapsed++;
512548
verifyPanelExpansionState(true, panel, panelContainer, panelHeader, button, timesCollapsed, timesExpanded);
513549
expect(header.onInteraction.emit).toHaveBeenCalledTimes(8);
550+
551+
// disabled interaction
552+
const headerSub = header.onInteraction.subscribe((event) => {
553+
event.cancel = true;
554+
});
555+
556+
// currently collapsed
557+
expect(panel.collapsed).toEqual(true);
558+
559+
// cancel openening
560+
panelHeader.dispatchEvent(arrowDownEvent);
561+
fixture.detectChanges();
562+
tick();
563+
// do not iterate timesExpanded
564+
verifyPanelExpansionState(true, panel, panelContainer, panelHeader, button, timesCollapsed, timesExpanded);
565+
expect(header.onInteraction.emit).toHaveBeenCalledTimes(9);
566+
567+
// open through API
568+
panel.expand();
569+
timesExpanded++;
570+
tick();
571+
fixture.detectChanges();
572+
573+
// currently expanded
574+
expect(panel.collapsed).toEqual(false);
575+
576+
// cancel closing
577+
panelHeader.dispatchEvent(arrowUpEvent);
578+
fixture.detectChanges();
579+
tick();
580+
// do not iterate timesCollapsed
581+
verifyPanelExpansionState(false, panel, panelContainer, panelHeader, button, timesCollapsed, timesExpanded);
582+
expect(header.onInteraction.emit).toHaveBeenCalledTimes(10);
583+
584+
headerSub.unsubscribe();
514585
}));
515586
it('Should change panel expansion when using different methods', fakeAsync(() => {
516587
const fixture: ComponentFixture<IgxExpansionPanelListComponent> = TestBed.createComponent(IgxExpansionPanelListComponent);
@@ -727,15 +798,28 @@ describe('igxExpansionPanel', () => {
727798
expect(headerButton.children.length).toEqual(2);
728799
expect(iconContainer.firstElementChild.nodeName).toEqual('IGX-ICON');
729800
expect(titleContainer.firstElementChild.nodeName).toEqual('IGX-EXPANSION-PANEL-TITLE');
801+
expect(header.iconRef).not.toBe(null);
802+
expect(header.iconRef.nativeElement).toEqual(iconContainer.firstElementChild);
730803

731804
fixture.componentInstance.customIcon = true;
732805
fixture.detectChanges();
733806

734807
expect(iconContainer.firstElementChild.nodeName).toEqual('IGX-EXPANSION-PANEL-ICON');
735808
expect(titleContainer.firstElementChild.nodeName).toEqual('IGX-EXPANSION-PANEL-TITLE');
809+
expect(header.iconRef).not.toBe(null);
810+
expect(header.iconRef.nativeElement).toEqual(iconContainer.firstElementChild);
811+
812+
fixture.componentInstance.header.iconPosition = ICON_POSITION.NONE;
813+
fixture.detectChanges();
814+
expect(header.iconRef).toEqual(null);
736815

737816
fixture.componentInstance.customIcon = false;
738817
fixture.detectChanges();
818+
expect(header.iconRef).toEqual(null);
819+
820+
fixture.componentInstance.header.iconPosition = ICON_POSITION.LEFT;
821+
fixture.detectChanges();
822+
expect(header.iconRef).not.toBe(null);
739823

740824
expect(iconContainer.firstElementChild.nodeName).toEqual('IGX-ICON');
741825
expect(titleContainer.firstElementChild.nodeName).toEqual('IGX-EXPANSION-PANEL-TITLE');

0 commit comments

Comments
 (0)