From 0ba8fe31818a3b1772b824e00bdb61ea3ddab7d0 Mon Sep 17 00:00:00 2001 From: Georgi Anastasov Date: Tue, 22 Oct 2024 16:37:12 +0300 Subject: [PATCH 1/4] feat(banner): add collapsed input property to the banner --- CHANGELOG.md | 3 + .../src/lib/banner/banner.component.spec.ts | 59 ++++++++++++++++++- .../src/lib/banner/banner.component.ts | 44 +++++++++++--- 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c158c80fb0a..b6a97ef02a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,9 @@ All notable changes for each version of this project will be documented in this - `IgxGridState` - When possible the state directive nows reuses the column that already exists on the grid when restoring the state, instead of creating new column instances every time. This removes the need to set any complex objects manually back on the column on `columnInit`. The only instance where this is still necessary is when the column (or its children in case of column groups) have no `field` property so there's no way to uniquely identify the matching column. - Added support for persisting Multi-Row Layout. +- `IgxBanner` + - Added a new `collapsed` input property, enabling control over the initial state of the banner, allowing it to be either collapsed (hidden) or expanded (visible) upon rendering. + ### Themes - `Palettes` - All palette colors have been migrated to the [CSS relative colors syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_colors/Relative_colors). This means that color consumed as CSS variables no longer need to be wrapped in an `hsl` function. diff --git a/projects/igniteui-angular/src/lib/banner/banner.component.spec.ts b/projects/igniteui-angular/src/lib/banner/banner.component.spec.ts index 66113e4cbb4..bde176cae95 100644 --- a/projects/igniteui-angular/src/lib/banner/banner.component.spec.ts +++ b/projects/igniteui-angular/src/lib/banner/banner.component.spec.ts @@ -33,7 +33,8 @@ describe('igxBanner', () => { IgxBannerOneButtonComponent, IgxBannerSampleComponent, IgxBannerCustomTemplateComponent, - SimpleBannerEventsComponent + SimpleBannerEventsComponent, + IgxBannerInitializedOpenComponent ] }).compileComponents(); })); @@ -395,6 +396,36 @@ describe('igxBanner', () => { expect(banner.closing.emit).toHaveBeenCalledTimes(2); expect(banner.closed.emit).toHaveBeenCalledTimes(1); })); + + it('Should toggle banner state when collapsed property changes', fakeAsync(() => { + const fixture = TestBed.createComponent(IgxBannerInitializedOpenComponent); + fixture.detectChanges(); + const banner = fixture.componentInstance.banner; + + banner.collapsed = true; + tick(); + fixture.detectChanges(); + + expect(banner.collapsed).toBeTrue(); + + banner.collapsed = false; + tick(); + fixture.detectChanges(); + expect(banner.collapsed).toBeFalse(); + expect(banner.elementRef.nativeElement.style.display).toEqual('block'); + + banner.collapsed = true; + tick(); + fixture.detectChanges(); + expect(banner.collapsed).toBeTrue(); + expect(banner.elementRef.nativeElement.style.display).toEqual(''); + + banner.collapsed = false; + tick(); + fixture.detectChanges(); + expect(banner.collapsed).toBeFalse(); + expect(banner.elementRef.nativeElement.style.display).toEqual('block'); + })); }); describe('Rendering tests: ', () => { @@ -485,6 +516,16 @@ describe('igxBanner', () => { expect(panel.attributes.getNamedItem('role').nodeValue).toEqual('status'); expect(panel.attributes.getNamedItem('aria-live').nodeValue).toEqual('polite'); })); + + it('Should initialize banner as open when collapsed is set to false', fakeAsync(() => { + const fixture = TestBed.createComponent(IgxBannerInitializedOpenComponent); + fixture.detectChanges(); + const banner = fixture.componentInstance.banner; + + expect(banner.collapsed).toBeFalse(); + expect(banner.elementRef.nativeElement.style.display).toEqual('block'); + expect(banner.elementRef.nativeElement.querySelector('.' + CSS_CLASS_BANNER)).not.toBeNull(); + })); }); const getBaseClassElements = (fixture: ComponentFixture) => { @@ -606,3 +647,19 @@ export class SimpleBannerEventsComponent { event.cancel = this.cancelFlag; } } + +@Component({ + template: ` +
+ + Banner initialized as open. + +
+ `, + standalone: true, + imports: [IgxBannerComponent] +}) +export class IgxBannerInitializedOpenComponent { + @ViewChild(IgxBannerComponent, { static: true }) + public banner: IgxBannerComponent; +} diff --git a/projects/igniteui-angular/src/lib/banner/banner.component.ts b/projects/igniteui-angular/src/lib/banner/banner.component.ts index dea300b1895..5bdbea17264 100644 --- a/projects/igniteui-angular/src/lib/banner/banner.component.ts +++ b/projects/igniteui-angular/src/lib/banner/banner.component.ts @@ -158,15 +158,34 @@ export class IgxBannerComponent implements IToggleView { public get resourceStrings(): IBannerResourceStrings { return this._resourceStrings; } + /** - * Gets whether banner is collapsed + * Gets or sets whether the banner is collapsed. + * + * - When `collapsed` is set to `true`, the banner is hidden (collapsed). + * - When `collapsed` is set to `false`, the banner is visible (expanded). * * ```typescript + * // Get * const isCollapsed: boolean = banner.collapsed; + * + * // Set + * // Expand the banner + * banner.collapsed = false; + * + * // Collapse the banner + * banner.collapsed = true; * ``` */ - public get collapsed() { - return this._expansionPanel.collapsed; + @Input() + public get collapsed(): boolean { + return this._collapsed; + } + + public set collapsed(value: boolean) { + if (this._collapsed !== value) { + this._collapsed = value; + } } /** @@ -199,6 +218,7 @@ export class IgxBannerComponent implements IToggleView { private _bannerEvent: BannerEventArgs; private _animationSettings: ToggleAnimationSettings; private _resourceStrings = getCurrentResourceStrings(BannerResourceStringsEN); + private _collapsed: boolean = true; constructor(public elementRef: ElementRef) { } @@ -217,6 +237,9 @@ export class IgxBannerComponent implements IToggleView { * ``` */ public open(event?: Event) { + if (!this.collapsed) { + return; + } this._bannerEvent = { owner: this, event}; const openingArgs: BannerCancelEventArgs = { owner: this, @@ -224,10 +247,10 @@ export class IgxBannerComponent implements IToggleView { cancel: false }; this.opening.emit(openingArgs); - if (openingArgs.cancel) { - return; + if (!openingArgs.cancel) { + this._expansionPanel.open(event); + this._collapsed = false; } - this._expansionPanel.open(event); } /** @@ -245,6 +268,9 @@ export class IgxBannerComponent implements IToggleView { * ``` */ public close(event?: Event) { + if (this.collapsed) { + return; + } this._bannerEvent = { owner: this, event}; const closingArgs: BannerCancelEventArgs = { owner: this, @@ -252,10 +278,10 @@ export class IgxBannerComponent implements IToggleView { cancel: false }; this.closing.emit(closingArgs); - if (closingArgs.cancel) { - return; + if (!closingArgs.cancel) { + this._expansionPanel.close(event); + this._collapsed = true; } - this._expansionPanel.close(event); } /** From a3b67cbf4262d2e026e1025a17e7478a1bd014e6 Mon Sep 17 00:00:00 2001 From: Georgi Anastasov Date: Tue, 26 Nov 2024 16:33:57 +0200 Subject: [PATCH 2/4] feat(banner): implement expanded property to the banner --- CHANGELOG.md | 6 +- .../src/lib/banner/banner.component.spec.ts | 24 +++--- .../src/lib/banner/banner.component.ts | 86 ++++++++++++------- 3 files changed, 71 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75ad14c9667..210a5617ea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Ignite UI for Angular Change Log All notable changes for each version of this project will be documented in this file. +## 19.1.0 +### New Features +- `IgxBanner` + - Introduced a new `expanded` input property, enabling dynamic control over the banner's state. This property allows the banner to be programmatically set as expanded (visible) or collapsed (hidden) both initially and during runtime. ## 19.0.0 ### General @@ -50,8 +54,6 @@ All notable changes for each version of this project will be documented in this - `IgxGridState` - When possible the state directive nows reuses the column that already exists on the grid when restoring the state, instead of creating new column instances every time. This removes the need to set any complex objects manually back on the column on `columnInit`. The only instance where this is still necessary is when the column (or its children in case of column groups) have no `field` property so there's no way to uniquely identify the matching column. - Added support for persisting Multi-Row Layout. -- `IgxBanner` - - Added a new `collapsed` input property, enabling control over the initial state of the banner, allowing it to be either collapsed (hidden) or expanded (visible) upon rendering. ### Themes - **Breaking Change** `Palettes` diff --git a/projects/igniteui-angular/src/lib/banner/banner.component.spec.ts b/projects/igniteui-angular/src/lib/banner/banner.component.spec.ts index bde176cae95..0d3e060ee34 100644 --- a/projects/igniteui-angular/src/lib/banner/banner.component.spec.ts +++ b/projects/igniteui-angular/src/lib/banner/banner.component.spec.ts @@ -397,33 +397,33 @@ describe('igxBanner', () => { expect(banner.closed.emit).toHaveBeenCalledTimes(1); })); - it('Should toggle banner state when collapsed property changes', fakeAsync(() => { + it('Should toggle banner state when expanded property changes', fakeAsync(() => { const fixture = TestBed.createComponent(IgxBannerInitializedOpenComponent); fixture.detectChanges(); const banner = fixture.componentInstance.banner; - banner.collapsed = true; + banner.expanded = false; tick(); fixture.detectChanges(); - expect(banner.collapsed).toBeTrue(); + expect(banner.expanded).toBeFalse(); - banner.collapsed = false; + banner.expanded = true; tick(); fixture.detectChanges(); - expect(banner.collapsed).toBeFalse(); + expect(banner.expanded).toBeTrue(); expect(banner.elementRef.nativeElement.style.display).toEqual('block'); - banner.collapsed = true; + banner.expanded = false; tick(); fixture.detectChanges(); - expect(banner.collapsed).toBeTrue(); + expect(banner.expanded).toBeFalse(); expect(banner.elementRef.nativeElement.style.display).toEqual(''); - banner.collapsed = false; + banner.expanded = true; tick(); fixture.detectChanges(); - expect(banner.collapsed).toBeFalse(); + expect(banner.expanded).toBeTrue(); expect(banner.elementRef.nativeElement.style.display).toEqual('block'); })); }); @@ -517,12 +517,12 @@ describe('igxBanner', () => { expect(panel.attributes.getNamedItem('aria-live').nodeValue).toEqual('polite'); })); - it('Should initialize banner as open when collapsed is set to false', fakeAsync(() => { + it('Should initialize banner as open when expanded is set to true', fakeAsync(() => { const fixture = TestBed.createComponent(IgxBannerInitializedOpenComponent); fixture.detectChanges(); const banner = fixture.componentInstance.banner; - expect(banner.collapsed).toBeFalse(); + expect(banner.expanded).toBeTrue(); expect(banner.elementRef.nativeElement.style.display).toEqual('block'); expect(banner.elementRef.nativeElement.querySelector('.' + CSS_CLASS_BANNER)).not.toBeNull(); })); @@ -651,7 +651,7 @@ export class SimpleBannerEventsComponent { @Component({ template: `
- + Banner initialized as open.
diff --git a/projects/igniteui-angular/src/lib/banner/banner.component.ts b/projects/igniteui-angular/src/lib/banner/banner.component.ts index 5bdbea17264..f26ab9ab4fa 100644 --- a/projects/igniteui-angular/src/lib/banner/banner.component.ts +++ b/projects/igniteui-angular/src/lib/banner/banner.component.ts @@ -160,34 +160,53 @@ export class IgxBannerComponent implements IToggleView { } /** - * Gets or sets whether the banner is collapsed. + * Gets/Sets whether the banner is expanded (visible) or collapsed (hidden). + * Defaults to `false`. + * Setting to `true` opens the banner, while `false` closes it. * - * - When `collapsed` is set to `true`, the banner is hidden (collapsed). - * - When `collapsed` is set to `false`, the banner is visible (expanded). - * - * ```typescript - * // Get - * const isCollapsed: boolean = banner.collapsed; - * - * // Set + * @example * // Expand the banner - * banner.collapsed = false; + * banner.expanded = true; * + * @example * // Collapse the banner - * banner.collapsed = true; - * ``` + * banner.expanded = false; + * + * @example + * // Check if the banner is expanded + * const isExpanded = banner.expanded; */ @Input() - public get collapsed(): boolean { - return this._collapsed; + public get expanded(): boolean { + return this._expanded; } - public set collapsed(value: boolean) { - if (this._collapsed !== value) { - this._collapsed = value; + public set expanded(value: boolean) { + if (value === this._expanded) { + return; + } + + this._expanded = value; + this._isProgrammaticExpanded = true; + + if (value) { + this._expansionPanel.open(); + } else { + this._expansionPanel.close(); } } + /** + * Gets whether the banner is collapsed. + * + * ```typescript + * const isCollapsed: boolean = banner.collapsed; + * ``` + */ + public get collapsed(): boolean { + return this._expansionPanel.collapsed; + } + /** * Returns the native element of the banner component * ```typescript @@ -215,10 +234,11 @@ export class IgxBannerComponent implements IToggleView { @ContentChild(IgxBannerActionsDirective) private _bannerActionTemplate: IgxBannerActionsDirective; + private _expanded: boolean = false; + private _isProgrammaticExpanded = false; private _bannerEvent: BannerEventArgs; private _animationSettings: ToggleAnimationSettings; private _resourceStrings = getCurrentResourceStrings(BannerResourceStringsEN); - private _collapsed: boolean = true; constructor(public elementRef: ElementRef) { } @@ -237,20 +257,19 @@ export class IgxBannerComponent implements IToggleView { * ``` */ public open(event?: Event) { - if (!this.collapsed) { - return; - } - this._bannerEvent = { owner: this, event}; + this._bannerEvent = { owner: this, event }; const openingArgs: BannerCancelEventArgs = { owner: this, event, cancel: false }; this.opening.emit(openingArgs); - if (!openingArgs.cancel) { - this._expansionPanel.open(event); - this._collapsed = false; + if (openingArgs.cancel) { + return; } + this._expansionPanel.open(event); + this._expanded = true; + this._isProgrammaticExpanded = false; } /** @@ -268,9 +287,6 @@ export class IgxBannerComponent implements IToggleView { * ``` */ public close(event?: Event) { - if (this.collapsed) { - return; - } this._bannerEvent = { owner: this, event}; const closingArgs: BannerCancelEventArgs = { owner: this, @@ -278,10 +294,12 @@ export class IgxBannerComponent implements IToggleView { cancel: false }; this.closing.emit(closingArgs); - if (!closingArgs.cancel) { - this._expansionPanel.close(event); - this._collapsed = true; + if (closingArgs.cancel) { + return; } + this._expansionPanel.close(event); + this._expanded = false; + this._isProgrammaticExpanded = false; } /** @@ -308,11 +326,17 @@ export class IgxBannerComponent implements IToggleView { /** @hidden */ public onExpansionPanelOpen() { + if (this._isProgrammaticExpanded) { + return; + } this.opened.emit(this._bannerEvent); } /** @hidden */ public onExpansionPanelClose() { + if (this._isProgrammaticExpanded) { + return; + } this.closed.emit(this._bannerEvent); } } From 6f170da8dbc6bc87db5c0179ed17ebc92e6f5afa Mon Sep 17 00:00:00 2001 From: Georgi Anastasov Date: Tue, 14 Jan 2025 14:18:50 +0200 Subject: [PATCH 3/4] fix(banner): handle expanded=true correctly during close animation --- CHANGELOG.md | 10 +++++----- .../src/lib/banner/banner.component.ts | 17 ++++++++--------- .../expansion-panel.component.ts | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e473a3e230d..4a6c25d8dba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,16 @@ # Ignite UI for Angular Change Log All notable changes for each version of this project will be documented in this file. -## 19.1.0 -### New Features -- `IgxBanner` - - Introduced a new `expanded` input property, enabling dynamic control over the banner's state. This property allows the banner to be programmatically set as expanded (visible) or collapsed (hidden) both initially and during runtime. - ## 19.1.0 ### General - `IgxCarousel` - **Behavioral Changes** - the `maximumIndicatorsCount` input property now defaults to `10`. - **Deprecation** - `CarouselIndicatorsOrientation` enum members `top` and `bottom` have been deprecated and will be removed in a future version. Use `start` and `end` instead. +### New Features +- `IgxBanner` + - Introduced a new `expanded` input property, enabling dynamic control over the banner's state. The banner can now be programmatically set to expanded (visible) or collapsed (hidden) both initially and at runtime. Animations will trigger during runtime updates — the **open animation** plays when `expanded` is set to `true`, and the **close animation** plays when set to `false`. However, no animations will trigger when the property is set initially. + - The banner's event lifecycle (`opening`, `opened`, `closing`, `closed`) only triggers through **user interactions** (e.g., clicking to open/close). Programmatic updates using the `expanded` property will not fire any events. + - If the `expanded` property changes during an ongoing animation, the current animation will **cancel immediately**, applying the new state without additional animations or events. ## 19.0.0 ### General diff --git a/projects/igniteui-angular/src/lib/banner/banner.component.ts b/projects/igniteui-angular/src/lib/banner/banner.component.ts index dcc542e2d7c..08fc743b568 100644 --- a/projects/igniteui-angular/src/lib/banner/banner.component.ts +++ b/projects/igniteui-angular/src/lib/banner/banner.component.ts @@ -142,13 +142,12 @@ export class IgxBannerComponent implements IToggleView { return this._animationSettings ? this._animationSettings : this._expansionPanel.animationSettings; } - /** + /** * Gets/Sets the resource strings. * * @remarks * By default it uses EN resources. */ - @Input() public set resourceStrings(value: IBannerResourceStrings) { this._resourceStrings = Object.assign({}, this._resourceStrings, value); @@ -186,7 +185,7 @@ export class IgxBannerComponent implements IToggleView { } this._expanded = value; - this._isProgrammaticExpanded = true; + this._shouldFireEvent = true; if (value) { this._expansionPanel.open(); @@ -201,7 +200,7 @@ export class IgxBannerComponent implements IToggleView { * ```typescript * const isCollapsed: boolean = banner.collapsed; * ``` - */ + */ public get collapsed(): boolean { return this._expansionPanel.collapsed; } @@ -234,7 +233,7 @@ export class IgxBannerComponent implements IToggleView { private _bannerActionTemplate: IgxBannerActionsDirective; private _expanded: boolean = false; - private _isProgrammaticExpanded = false; + private _shouldFireEvent: boolean = false; private _bannerEvent: BannerEventArgs; private _animationSettings: ToggleAnimationSettings; private _resourceStrings = getCurrentResourceStrings(BannerResourceStringsEN); @@ -268,7 +267,7 @@ export class IgxBannerComponent implements IToggleView { } this._expansionPanel.open(event); this._expanded = true; - this._isProgrammaticExpanded = false; + this._shouldFireEvent = false; } /** @@ -298,7 +297,7 @@ export class IgxBannerComponent implements IToggleView { } this._expansionPanel.close(event); this._expanded = false; - this._isProgrammaticExpanded = false; + this._shouldFireEvent = false; } /** @@ -325,7 +324,7 @@ export class IgxBannerComponent implements IToggleView { /** @hidden */ public onExpansionPanelOpen() { - if (this._isProgrammaticExpanded) { + if (this._shouldFireEvent) { return; } this.opened.emit(this._bannerEvent); @@ -333,7 +332,7 @@ export class IgxBannerComponent implements IToggleView { /** @hidden */ public onExpansionPanelClose() { - if (this._isProgrammaticExpanded) { + if (this._shouldFireEvent) { return; } this.closed.emit(this._bannerEvent); 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 ffda084ea10..f2f643adb21 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 @@ -276,7 +276,7 @@ export class IgxExpansionPanelComponent extends ToggleAnimationPlayer implements * ``` */ public expand(evt?: Event) { - if (!this.collapsed) { // If the panel is already opened, do nothing + if (!this.collapsed && !this.closeAnimationPlayer) { // Check if the panel is currently collapsing or already expanded return; } const args = { event: evt, panel: this, owner: this, cancel: false }; From 505c45d2ede15dd24981d0011a5d2a59da4e2b62 Mon Sep 17 00:00:00 2001 From: Georgi Anastasov Date: Thu, 16 Jan 2025 15:41:00 +0200 Subject: [PATCH 4/4] fix(changelog): update description to actual behavior of expanded property --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a6c25d8dba..1b02a8488ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ All notable changes for each version of this project will be documented in this - `IgxBanner` - Introduced a new `expanded` input property, enabling dynamic control over the banner's state. The banner can now be programmatically set to expanded (visible) or collapsed (hidden) both initially and at runtime. Animations will trigger during runtime updates — the **open animation** plays when `expanded` is set to `true`, and the **close animation** plays when set to `false`. However, no animations will trigger when the property is set initially. - The banner's event lifecycle (`opening`, `opened`, `closing`, `closed`) only triggers through **user interactions** (e.g., clicking to open/close). Programmatic updates using the `expanded` property will not fire any events. - - If the `expanded` property changes during an ongoing animation, the current animation will **cancel immediately**, applying the new state without additional animations or events. + - If the `expanded` property changes during an ongoing animation, the current animation will **stop** and the opposite animation will begin from the **point where the previous animation left off**. For instance, if the open animation (10 seconds) is interrupted at 6 seconds and `expanded` is set to `false`, the close animation (5 seconds) will start from its 3rd second. ## 19.0.0 ### General