Skip to content

Commit 409b3e2

Browse files
committed
feat(tree): add IgxTree, initial commit, #7475
1 parent 4becc57 commit 409b3e2

23 files changed

+858
-56
lines changed

Diff for: projects/igniteui-angular/src/lib/banner/banner.component.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Component, NgModule, EventEmitter, Output, Input, ViewChild, ElementRef,
22
ContentChild, HostBinding } from '@angular/core';
33
import { IgxExpansionPanelModule } from '../expansion-panel/expansion-panel.module';
4-
import { AnimationSettings } from '../expansion-panel/expansion-panel.component';
54
import { IgxExpansionPanelComponent } from '../expansion-panel/public_api';
65
import { IgxIconModule, IgxIconComponent } from '../icon/public_api';
76
import { IToggleView } from '../core/navigation';
@@ -10,6 +9,7 @@ import { IgxRippleModule } from '../directives/ripple/ripple.directive';
109
import { IgxBannerActionsDirective } from './banner.directives';
1110
import { CommonModule } from '@angular/common';
1211
import { CancelableEventArgs, IBaseEventArgs } from '../core/utils';
12+
import { ToggleAnimationSettings } from '../expansion-panel/toggle-animation-component';
1313

1414
export interface BannerEventArgs extends IBaseEventArgs {
1515
banner: IgxBannerComponent;
@@ -111,11 +111,11 @@ export class IgxBannerComponent implements IToggleView {
111111
/**
112112
* Get the animation settings used by the banner open/close methods
113113
* ```typescript
114-
* let currentAnimations: AnimationSettings = banner.animationSettings
114+
* let currentAnimations: ToggleAnimationSettings = banner.animationSettings
115115
* ```
116116
*/
117117
@Input()
118-
public get animationSettings(): AnimationSettings {
118+
public get animationSettings(): ToggleAnimationSettings {
119119
return this._animationSettings ? this._animationSettings : this._expansionPanel.animationSettings;
120120
}
121121

@@ -124,10 +124,10 @@ export class IgxBannerComponent implements IToggleView {
124124
* ```typescript
125125
* import { slideInLeft, slideOutRight } from 'igniteui-angular';
126126
* ...
127-
* banner.animationSettings: AnimationSettings = { openAnimation: slideInLeft, closeAnimation: slideOutRight };
127+
* banner.animationSettings: ToggleAnimationSettings = { openAnimation: slideInLeft, closeAnimation: slideOutRight };
128128
* ```
129129
*/
130-
public set animationSettings(settings: AnimationSettings) {
130+
public set animationSettings(settings: ToggleAnimationSettings) {
131131
this._animationSettings = settings;
132132
}
133133
/**
@@ -166,7 +166,7 @@ export class IgxBannerComponent implements IToggleView {
166166
private _bannerActionTemplate: IgxBannerActionsDirective;
167167

168168
private _bannerEvent: BannerEventArgs;
169-
private _animationSettings: AnimationSettings;
169+
private _animationSettings: ToggleAnimationSettings;
170170

171171
constructor(public elementRef: ElementRef) { }
172172

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

+21-49
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,20 @@ import {
88
ContentChild,
99
AfterContentInit
1010
} from '@angular/core';
11-
import { AnimationBuilder, AnimationReferenceMetadata, useAnimation } from '@angular/animations';
12-
import { growVerOut, growVerIn } from '../animations/main';
11+
import { AnimationBuilder } from '@angular/animations';
1312
import { IgxExpansionPanelBodyComponent } from './expansion-panel-body.component';
1413
import { IgxExpansionPanelHeaderComponent } from './expansion-panel-header.component';
1514
import { IGX_EXPANSION_PANEL_COMPONENT, IgxExpansionPanelBase, IExpansionPanelEventArgs } from './expansion-panel.common';
15+
import { ToggleAnimationPlayer, ToggleAnimationSettings } from './toggle-animation-component';
1616

1717
let NEXT_ID = 0;
1818

19-
export interface AnimationSettings {
20-
openAnimation: AnimationReferenceMetadata;
21-
closeAnimation: AnimationReferenceMetadata;
22-
}
2319
@Component({
2420
selector: 'igx-expansion-panel',
2521
templateUrl: 'expansion-panel.component.html',
2622
providers: [{ provide: IGX_EXPANSION_PANEL_COMPONENT, useExisting: IgxExpansionPanelComponent }]
2723
})
28-
export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterContentInit {
24+
export class IgxExpansionPanelComponent extends ToggleAnimationPlayer implements IgxExpansionPanelBase, AfterContentInit {
2925
/**
3026
* Sets/gets the animation settings of the expansion panel component
3127
* Open and Close animation should be passed
@@ -58,10 +54,12 @@ export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterC
5854
* ```
5955
*/
6056
@Input()
61-
public animationSettings: AnimationSettings = {
62-
openAnimation: growVerIn,
63-
closeAnimation: growVerOut
64-
};
57+
public get animationSettings(): ToggleAnimationSettings {
58+
return this._animationSettings;
59+
}
60+
public set animationSettings(value: ToggleAnimationSettings) {
61+
this._animationSettings = value;
62+
}
6563

6664
/**
6765
* Sets/gets the `id` of the expansion panel component.
@@ -165,10 +163,12 @@ export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterC
165163

166164
private _collapsed = true;
167165

168-
constructor(private cdr: ChangeDetectorRef, private builder: AnimationBuilder) { }
166+
constructor(private cdr: ChangeDetectorRef, protected builder: AnimationBuilder) {
167+
super(builder);
168+
}
169169

170170
/** @hidden */
171-
ngAfterContentInit(): void {
171+
public ngAfterContentInit(): void {
172172
if (this.body && this.header) {
173173
// schedule at end of turn:
174174
Promise.resolve().then(() => {
@@ -188,11 +188,12 @@ export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterC
188188
* <button (click)="myPanel.collapse($event)">Collpase Panel</button>
189189
* ```
190190
*/
191-
collapse(evt?: Event) {
191+
public collapse(evt?: Event) {
192192
if (this.collapsed) { // If expansion panel is already collapsed, do nothing
193193
return;
194194
}
195195
this.playCloseAnimation(
196+
this.body?.element,
196197
() => {
197198
this.onCollapsed.emit({ event: evt, panel: this, owner: this });
198199
this.collapsed = true;
@@ -211,13 +212,14 @@ export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterC
211212
* <button (click)="myPanel.expand($event)">Expand Panel</button>
212213
* ```
213214
*/
214-
expand(evt?: Event) {
215+
public expand(evt?: Event) {
215216
if (!this.collapsed) { // If the panel is already opened, do nothing
216217
return;
217218
}
218219
this.collapsed = false;
219220
this.cdr.detectChanges();
220221
this.playOpenAnimation(
222+
this.body?.element,
221223
() => {
222224
this.onExpanded.emit({ event: evt, panel: this, owner: this });
223225
}
@@ -234,49 +236,19 @@ export class IgxExpansionPanelComponent implements IgxExpansionPanelBase, AfterC
234236
* <button (click)="myPanel.toggle($event)">Expand Panel</button>
235237
* ```
236238
*/
237-
toggle(evt?: Event) {
239+
public toggle(evt?: Event) {
238240
if (this.collapsed) {
239241
this.open(evt);
240242
} else {
241243
this.close(evt);
242244
}
243245
}
244246

245-
open(evt?: Event) {
247+
public open(evt?: Event) {
246248
this.expand(evt);
247249
}
248-
close(evt?: Event) {
249-
this.collapse(evt);
250-
}
251-
252-
private playOpenAnimation(cb: () => void) {
253-
if (!this.body) { // if not body element is passed, there is nothing to animate
254-
return;
255-
}
256-
const animation = useAnimation(this.animationSettings.openAnimation);
257-
const animationBuilder = this.builder.build(animation);
258-
const openAnimationPlayer = animationBuilder.create(this.body.element.nativeElement);
259-
260-
openAnimationPlayer.onDone(() => {
261-
cb();
262-
openAnimationPlayer.reset();
263-
});
264-
265-
openAnimationPlayer.play();
266-
}
267250

268-
private playCloseAnimation(cb: () => void) {
269-
if (!this.body) { // if not body element is passed, there is nothing to animate
270-
return;
271-
}
272-
const animation = useAnimation(this.animationSettings.closeAnimation);
273-
const animationBuilder = this.builder.build(animation);
274-
const closeAnimationPlayer = animationBuilder.create(this.body.element.nativeElement);
275-
closeAnimationPlayer.onDone(() => {
276-
cb();
277-
closeAnimationPlayer.reset();
278-
});
279-
280-
closeAnimationPlayer.play();
251+
public close(evt?: Event) {
252+
this.collapse(evt);
281253
}
282254
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import { AnimationBuilder, AnimationPlayer, AnimationReferenceMetadata, useAnimation } from '@angular/animations';
2+
import { Directive, ElementRef, EventEmitter, OnDestroy } from '@angular/core';
3+
import { noop, Subject } from 'rxjs';
4+
import { growVerIn, growVerOut } from '../animations/grow';
5+
6+
/**@hidden @internal */
7+
export interface ToggleAnimationSettings {
8+
openAnimation: AnimationReferenceMetadata;
9+
closeAnimation: AnimationReferenceMetadata;
10+
}
11+
12+
export interface ToggleAnimationOwner {
13+
animationSettings: ToggleAnimationSettings;
14+
openAnimationStart: EventEmitter<void>;
15+
openAnimationDone: EventEmitter<void>;
16+
closeAnimationStart: EventEmitter<void>;
17+
closeAnimationDone: EventEmitter<void>;
18+
openAnimationPlayer: AnimationPlayer;
19+
closeAnimationPlayer: AnimationPlayer;
20+
playOpenAnimation(element: ElementRef, onDone: () => void): void;
21+
playCloseAnimation(element: ElementRef, onDone: () => void): void;
22+
}
23+
24+
enum ANIMATION_TYPE {
25+
OPEN = 'open',
26+
CLOSE = 'close',
27+
}
28+
29+
/**@hidden @internal */
30+
@Directive()
31+
// eslint-disable-next-line @angular-eslint/directive-class-suffix
32+
export abstract class ToggleAnimationPlayer implements ToggleAnimationOwner, OnDestroy {
33+
34+
35+
/** @hidden @internal */
36+
public openAnimationDone: EventEmitter<void> = new EventEmitter();
37+
/** @hidden @internal */
38+
public closeAnimationDone: EventEmitter<void> = new EventEmitter();
39+
/** @hidden @internal */
40+
public openAnimationStart: EventEmitter<void> = new EventEmitter();
41+
/** @hidden @internal */
42+
public closeAnimationStart: EventEmitter<void> = new EventEmitter();
43+
44+
public get animationSettings(): ToggleAnimationSettings {
45+
return this._animationSettings;
46+
}
47+
public set animationSettings(value: ToggleAnimationSettings) {
48+
this._animationSettings = value;
49+
}
50+
51+
/** @hidden @internal */
52+
public openAnimationPlayer: AnimationPlayer = null;
53+
54+
/** @hidden @internal */
55+
public closeAnimationPlayer: AnimationPlayer = null;
56+
57+
58+
59+
protected destroy$: Subject<void> = new Subject();
60+
protected players: Map<string, AnimationPlayer> = new Map();
61+
protected _animationSettings: ToggleAnimationSettings = {
62+
openAnimation: growVerIn,
63+
closeAnimation: growVerOut
64+
};
65+
66+
private _defaultClosedCallback = noop;
67+
private _defaultOpenedCallback = noop;
68+
private onClosedCallback: () => any = this._defaultClosedCallback;
69+
private onOpenedCallback: () => any = this._defaultOpenedCallback;
70+
71+
constructor(protected builder: AnimationBuilder) {
72+
}
73+
74+
/** @hidden @internal */
75+
public playOpenAnimation(targetElement: ElementRef, onDone?: () => void): void {
76+
this.startPlayer(ANIMATION_TYPE.OPEN, targetElement, onDone || this._defaultOpenedCallback);
77+
}
78+
79+
/** @hidden @internal */
80+
public playCloseAnimation(targetElement: ElementRef, onDone?: () => void): void {
81+
this.startPlayer(ANIMATION_TYPE.CLOSE, targetElement, onDone || this._defaultClosedCallback);
82+
}
83+
public ngOnDestroy() {
84+
this.destroy$.next();
85+
this.destroy$.complete();
86+
}
87+
88+
private startPlayer(type: ANIMATION_TYPE, targetElement: ElementRef, callback: () => void): void {
89+
if (!targetElement) { // if no element is passed, there is nothing to animate
90+
return;
91+
}
92+
93+
let target = this.getPlayer(type);
94+
if (!target) {
95+
target = this.initializePlayer(type, targetElement, callback);
96+
}
97+
98+
if (target.hasStarted()) {
99+
return;
100+
}
101+
102+
const targetEmitter = type === ANIMATION_TYPE.OPEN ? this.openAnimationStart : this.closeAnimationStart;
103+
targetEmitter.emit();
104+
target.play();
105+
}
106+
107+
private initializePlayer(type: ANIMATION_TYPE, targetElement: ElementRef, callback: () => void): AnimationPlayer {
108+
const oppositeType = type === ANIMATION_TYPE.OPEN ? ANIMATION_TYPE.CLOSE : ANIMATION_TYPE.OPEN;
109+
const animationSettings = type === ANIMATION_TYPE.OPEN ?
110+
this.animationSettings.openAnimation : this.animationSettings.closeAnimation;
111+
const animation = useAnimation(animationSettings);
112+
const animationBuilder = this.builder.build(animation);
113+
const opposite = this.getPlayer(oppositeType);
114+
let oppositePosition = 1;
115+
if (opposite) {
116+
if (opposite.hasStarted()) {
117+
// .getPosition() still returns 0 sometimes, regardless of the fix for https://github.com/angular/angular/issues/18891;
118+
oppositePosition = (opposite as any)._renderer.engine.players[0].getPosition();
119+
}
120+
this.cleanUpPlayer(oppositeType);
121+
}
122+
if (type === ANIMATION_TYPE.OPEN) {
123+
this.openAnimationPlayer = animationBuilder.create(targetElement.nativeElement);
124+
} else if (type === ANIMATION_TYPE.CLOSE) {
125+
this.closeAnimationPlayer = animationBuilder.create(targetElement.nativeElement);
126+
}
127+
const target = this.getPlayer(type);
128+
target.init();
129+
this.getPlayer(type).setPosition(1 - oppositePosition);
130+
if (type === ANIMATION_TYPE.OPEN) {
131+
this.onOpenedCallback = callback;
132+
} else if (type === ANIMATION_TYPE.CLOSE) {
133+
this.onClosedCallback = callback;
134+
}
135+
const targetCallback = type === ANIMATION_TYPE.OPEN ? this.onOpenedCallback : this.onClosedCallback;
136+
const targetEmitter = type === ANIMATION_TYPE.OPEN ? this.openAnimationDone : this.closeAnimationDone;
137+
target.onDone(() => {
138+
targetCallback();
139+
targetEmitter.emit();
140+
this.cleanUpPlayer(type);
141+
});
142+
return target;
143+
}
144+
145+
146+
private cleanUpPlayer(target: ANIMATION_TYPE) {
147+
switch (target) {
148+
case ANIMATION_TYPE.CLOSE:
149+
if (this.closeAnimationPlayer != null) {
150+
this.closeAnimationPlayer.reset();
151+
this.closeAnimationPlayer.destroy();
152+
this.closeAnimationPlayer = null;
153+
}
154+
this.onClosedCallback = this._defaultClosedCallback;
155+
break;
156+
case ANIMATION_TYPE.OPEN:
157+
if (this.openAnimationPlayer != null) {
158+
this.openAnimationPlayer.reset();
159+
this.openAnimationPlayer.destroy();
160+
this.openAnimationPlayer = null;
161+
}
162+
this.onOpenedCallback = this._defaultOpenedCallback;
163+
break;
164+
default:
165+
break;
166+
}
167+
}
168+
169+
private getPlayer(type: ANIMATION_TYPE): AnimationPlayer {
170+
switch (type) {
171+
case ANIMATION_TYPE.OPEN:
172+
return this.openAnimationPlayer;
173+
case ANIMATION_TYPE.CLOSE:
174+
return this.closeAnimationPlayer;
175+
default:
176+
return null;
177+
}
178+
}
179+
}

Diff for: projects/igniteui-angular/src/lib/services/overlay/overlay.ts

+1
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,7 @@ export class IgxOverlayService implements OnDestroy {
785785
// is done, 0.75 if 3/4 of the animation is done. As we need to start next animation from where
786786
// the previous has finished we need the amount up to 1, therefore we are subtracting what
787787
// getPosition() returns from one
788+
// TODO: This assumes opening and closing animations are mirrored.
788789
const position = 1 - info.openAnimationInnerPlayer.getPosition();
789790
info.openAnimationPlayer.reset();
790791
info.openAnimationPlayer = null;

Diff for: projects/igniteui-angular/src/lib/tree/README.md

Whitespace-only changes.

0 commit comments

Comments
 (0)