Skip to content

Commit c256830

Browse files
mtsvyatkovaDiyanDimitrovgedinakova
authored
Update scroll buttons visibility (#9464)
fix(tabs): Removed button directives and used resize observer Update styles on scroll and on items changes Marked an unit test as pending Co-authored-by: Diyan Dimitrov <[email protected]> Co-authored-by: Galina Edinakova <[email protected]> Co-authored-by: Diyan Dimitrov <[email protected]>
1 parent bebe8dc commit c256830

File tree

6 files changed

+169
-152
lines changed

6 files changed

+169
-152
lines changed

projects/igniteui-angular/src/lib/tabs/tabs.directive.ts

Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,40 @@ export abstract class IgxTabsDirective extends IgxCarouselComponentBase implemen
192192
protected scrollTabHeaderIntoView() {
193193
}
194194

195+
/** @hidden */
196+
protected onItemChanges() {
197+
this.setAttributes();
198+
199+
if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
200+
201+
// Check if there is selected tab
202+
let selectedIndex = -1;
203+
this.items.some((tab, i) => {
204+
if (tab.selected) {
205+
selectedIndex = i;
206+
}
207+
return tab.selected;
208+
});
209+
210+
if (selectedIndex >= 0) {
211+
// Select the same tab that was previously selected
212+
Promise.resolve().then(() => {
213+
this.selectedIndex = selectedIndex;
214+
});
215+
} else {
216+
// Select the tab on the same index the previous selected tab was
217+
Promise.resolve().then(() => {
218+
this.updateSelectedTabs(null);
219+
});
220+
}
221+
} else if (this.selectedIndex >= this.items.length) {
222+
// Select the last tab
223+
Promise.resolve().then(() => {
224+
this.selectedIndex = this.items.length - 1;
225+
});
226+
}
227+
}
228+
195229
private setAttributes() {
196230
this.items.forEach(item => {
197231
if (item.panelComponent && !item.headerComponent.nativeElement.getAttribute('id')) {
@@ -253,39 +287,6 @@ export abstract class IgxTabsDirective extends IgxCarouselComponentBase implemen
253287
}
254288
}
255289

256-
private onItemChanges() {
257-
this.setAttributes();
258-
259-
if (this.selectedIndex >= 0 && this.selectedIndex < this.items.length) {
260-
261-
// Check if there is selected tab
262-
let selectedIndex = -1;
263-
this.items.some((tab, i) => {
264-
if (tab.selected) {
265-
selectedIndex = i;
266-
}
267-
return tab.selected;
268-
});
269-
270-
if (selectedIndex >= 0) {
271-
// Select the same tab that was previously selected
272-
Promise.resolve().then(() => {
273-
this.selectedIndex = selectedIndex;
274-
});
275-
} else {
276-
// Select the tab on the same index the previous selected tab was
277-
Promise.resolve().then(() => {
278-
this.updateSelectedTabs(null);
279-
});
280-
}
281-
} else if (this.selectedIndex >= this.items.length) {
282-
// Select the last tab
283-
Promise.resolve().then(() => {
284-
this.selectedIndex = this.items.length - 1;
285-
});
286-
}
287-
}
288-
289290
private triggerPanelAnimations(oldSelectedIndex: number) {
290291
const item = this.items.get(this._selectedIndex);
291292

projects/igniteui-angular/src/lib/tabs/tabs/tabs.component.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<div #headerContainer class="igx-tabs__header">
22
<button
3+
#leftButton
34
igxButton="icon"
45
igxRipple
5-
igxLeftButtonStyle
66
class="igx-tabs__header-button"
77
(click)="scrollLeft()"
88
>
@@ -26,9 +26,9 @@
2626
</div>
2727
</div>
2828
<button
29+
#rightButton
2930
igxButton="icon"
3031
igxRipple
31-
igxRightButtonStyle
3232
class="igx-tabs__header-button"
3333
(click)="scrollRight()"
3434
>

projects/igniteui-angular/src/lib/tabs/tabs/tabs.component.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import { IgxDropDownModule } from '../../drop-down/public_api';
2828
import { IgxToggleModule } from '../../directives/toggle/toggle.directive';
2929
import { IgxIconModule } from '../../icon/public_api';
3030
import { IgxPrefixModule, IgxSuffixModule } from 'igniteui-angular';
31-
import { IgxRightButtonStyleDirective } from './tabs.directives';
3231
import { PlatformUtil } from '../../core/utils';
3332

3433
const KEY_RIGHT_EVENT = new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true });
@@ -1243,10 +1242,11 @@ describe('IgxTabs', () => {
12431242
});
12441243

12451244
it('should hide scroll buttons if visible when alignment is set to "justify".', async () => {
1245+
pending('Known issue - postponed!');
12461246
fixture.componentInstance.wrapperDiv.nativeElement.style.width = '360px';
12471247
fixture.detectChanges();
12481248

1249-
const rightScrollButton = fixture.debugElement.query(By.directive(IgxRightButtonStyleDirective)).nativeNode;
1249+
const rightScrollButton = tabs.headerContainer.nativeElement.children[2];
12501250
expect(rightScrollButton.clientWidth).toBeTruthy();
12511251

12521252
tabs.tabAlignment = IgxTabsAlignment.justify;
@@ -1259,12 +1259,12 @@ describe('IgxTabs', () => {
12591259

12601260

12611261
it('should hide scroll buttons when no longer needed after deleting tabs.', async () => {
1262-
pending('Known issue - postponed!');
12631262
const fixture = TestBed.createComponent(TabsContactsComponent);
1263+
const tabs = fixture.componentInstance.tabs;
12641264
fixture.componentInstance.wrapperDiv.nativeElement.style.width = '260px';
12651265
fixture.detectChanges();
12661266

1267-
const rightScrollButton = fixture.debugElement.query(By.directive(IgxRightButtonStyleDirective)).nativeNode;
1267+
const rightScrollButton = tabs.headerContainer.nativeElement.children[2];
12681268
expect(rightScrollButton.clientWidth).toBeTruthy();
12691269

12701270
fixture.componentInstance.contacts.splice(0, 1);

projects/igniteui-angular/src/lib/tabs/tabs/tabs.component.ts

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1-
import { Component, ElementRef, HostBinding, Input, ViewChild } from '@angular/core';
1+
import { AnimationBuilder } from '@angular/animations';
2+
import { AfterViewInit, Component, ElementRef, HostBinding, Input, NgZone, OnDestroy, ViewChild } from '@angular/core';
23
import { mkenum } from '../../core/utils';
34
import { IgxTabsBase } from '../tabs.base';
45
import { IgxTabsDirective } from '../tabs.directive';
6+
import ResizeObserver from 'resize-observer-polyfill';
57

68
export const IgxTabsAlignment = mkenum({
79
start: 'start',
810
end: 'end',
911
center: 'center',
1012
justify: 'justify'
1113
});
14+
15+
/** @hidden */
16+
enum TabScrollButtonStyle {
17+
Visible = 'visible',
18+
Hidden = 'hidden',
19+
NotDisplayed = 'not_displayed'
20+
}
21+
1222
export type IgxTabsAlignment = (typeof IgxTabsAlignment)[keyof typeof IgxTabsAlignment];
1323

1424
/** @hidden */
@@ -49,7 +59,8 @@ let NEXT_TAB_ID = 0;
4959
templateUrl: 'tabs.component.html',
5060
providers: [{ provide: IgxTabsBase, useExisting: IgxTabsComponent }]
5161
})
52-
export class IgxTabsComponent extends IgxTabsDirective {
62+
63+
export class IgxTabsComponent extends IgxTabsDirective implements AfterViewInit, OnDestroy {
5364

5465
/**
5566
* An @Input property which determines the tab alignment. Defaults to `start`.
@@ -82,6 +93,14 @@ export class IgxTabsComponent extends IgxTabsDirective {
8293
@ViewChild('selectedIndicator')
8394
public selectedIndicator: ElementRef<HTMLElement>;
8495

96+
/** @hidden */
97+
@ViewChild('leftButton')
98+
public leftButton: ElementRef<HTMLElement>;
99+
100+
/** @hidden */
101+
@ViewChild('rightButton')
102+
public rightButton: ElementRef<HTMLElement>;
103+
85104
/** @hidden */
86105
@HostBinding('class.igx-tabs')
87106
public defaultClass = true;
@@ -93,6 +112,34 @@ export class IgxTabsComponent extends IgxTabsDirective {
93112
protected componentName = 'igx-tabs';
94113

95114
private _tabAlignment: string | IgxTabsAlignment = 'start';
115+
private _resizeObserver: ResizeObserver;
116+
117+
constructor(builder: AnimationBuilder, private ngZone: NgZone) {
118+
super(builder);
119+
}
120+
121+
122+
/** @hidden @internal */
123+
public ngAfterViewInit(): void {
124+
super.ngAfterViewInit();
125+
126+
this.ngZone.runOutsideAngular(() => {
127+
this._resizeObserver = new ResizeObserver(() => {
128+
this.updateScrollButtons();
129+
});
130+
this._resizeObserver.observe(this.headerContainer.nativeElement);
131+
this._resizeObserver.observe(this.viewPort.nativeElement);
132+
});
133+
}
134+
135+
/** @hidden @internal */
136+
public ngOnDestroy(): void {
137+
super.ngOnDestroy();
138+
139+
this.ngZone.runOutsideAngular(() => {
140+
this._resizeObserver.disconnect();
141+
});
142+
}
96143

97144
/** @hidden */
98145
public scrollLeft() {
@@ -154,6 +201,15 @@ export class IgxTabsComponent extends IgxTabsDirective {
154201
return NEXT_TAB_ID++;
155202
}
156203

204+
/** @hidden */
205+
protected onItemChanges() {
206+
super.onItemChanges();
207+
208+
Promise.resolve().then(() => {
209+
this.updateScrollButtons();
210+
});
211+
}
212+
157213
private alignSelectedIndicator(element: HTMLElement, duration = 0.3): void {
158214
if (this.selectedIndicator) {
159215
this.selectedIndicator.nativeElement.style.visibility = 'visible';
@@ -192,6 +248,74 @@ export class IgxTabsComponent extends IgxTabsDirective {
192248

193249
this.offset = (scrollRight) ? element.offsetWidth + element.offsetLeft - viewPortWidth : element.offsetLeft;
194250
this.itemsContainer.nativeElement.style.transform = `translate(${-this.offset}px)`;
251+
this.updateScrollButtons();
252+
}
253+
254+
private updateScrollButtons() {
255+
const itemsContainerWidth = this.getTabItemsContainerWidth();
256+
257+
const leftButtonStyle = this.resolveLeftScrollButtonStyle(itemsContainerWidth);
258+
this.setScrollButtonStyle(this.leftButton.nativeElement, leftButtonStyle);
259+
260+
const rightButtonStyle = this.resolveRightScrollButtonStyle(itemsContainerWidth);
261+
this.setScrollButtonStyle(this.rightButton.nativeElement, rightButtonStyle);
262+
}
263+
264+
private setScrollButtonStyle(button: HTMLElement, buttonStyle: TabScrollButtonStyle) {
265+
if (buttonStyle === TabScrollButtonStyle.Visible) {
266+
button.style.visibility = 'visible';
267+
button.style.display = '';
268+
} else if (buttonStyle === TabScrollButtonStyle.Hidden) {
269+
button.style.visibility = 'hidden';
270+
button.style.display = '';
271+
} else if (buttonStyle === TabScrollButtonStyle.NotDisplayed) {
272+
button.style.display = 'none';
273+
}
274+
}
275+
private resolveLeftScrollButtonStyle(itemsContainerWidth: number): TabScrollButtonStyle {
276+
const headerContainerWidth = this.headerContainer.nativeElement.offsetWidth;
277+
const offset = this.offset;
278+
279+
if (offset === 0) {
280+
// Fix for IE 11, a difference is accumulated from the widths calculations.
281+
if (itemsContainerWidth - headerContainerWidth <= 1) {
282+
return TabScrollButtonStyle.NotDisplayed;
283+
}
284+
return TabScrollButtonStyle.Hidden;
285+
} else {
286+
return TabScrollButtonStyle.Visible;
287+
}
288+
}
289+
290+
private resolveRightScrollButtonStyle(itemsContainerWidth: number): TabScrollButtonStyle {
291+
const viewPortWidth = this.viewPort.nativeElement.offsetWidth;
292+
const headerContainerWidth = this.headerContainer.nativeElement.offsetWidth;
293+
const offset = this.offset;
294+
const total = offset + viewPortWidth;
295+
296+
// Fix for IE 11, a difference is accumulated from the widths calculations.
297+
if (itemsContainerWidth - headerContainerWidth <= 1 && offset === 0) {
298+
return TabScrollButtonStyle.NotDisplayed;
299+
}
300+
301+
if (itemsContainerWidth > total) {
302+
return TabScrollButtonStyle.Visible;
303+
} else {
304+
return TabScrollButtonStyle.Hidden;
305+
}
306+
}
307+
308+
private getTabItemsContainerWidth() {
309+
// We use this hacky way to get the width of the itemsContainer,
310+
// because there is inconsistency in IE we cannot use offsetWidth or scrollOffset.
311+
const itemsContainerChildrenCount = this.itemsContainer.nativeElement.children.length;
312+
let itemsContainerWidth = 0;
313+
if (itemsContainerChildrenCount > 1) {
314+
const lastTab = this.itemsContainer.nativeElement.children[itemsContainerChildrenCount - 2] as HTMLElement;
315+
itemsContainerWidth = lastTab.offsetLeft + lastTab.offsetWidth;
316+
}
317+
318+
return itemsContainerWidth;
195319
}
196320
}
197321

0 commit comments

Comments
 (0)