@engie-group/fluid-design-system-angular
Version:
Fluid Design System Angular
199 lines (169 loc) • 4.8 kB
text/typescript
import {CommonModule} from '@angular/common';
import {
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChildren,
EventEmitter,
Input,
OnDestroy,
Output,
QueryList,
ViewEncapsulation
} from '@angular/core';
import {Subject, takeUntil} from 'rxjs';
import {Utils} from '../../utils/utils.util';
import {TabComponent} from '../tab/tab.component';
import {TabsDensity} from './tabs.model';
export class TabsComponent implements AfterViewInit, OnDestroy {
private activeTabIndex;
private readonly tabClass = 'nj-tab';
private unsubscribe: Subject<void> = new Subject<void>();
/**
* @ignore
*/
public tabToRender: TabComponent;
/**
* @ignore
*/
public tabs: TabComponent[];
/**
* Label for the tablist element
*/
label: string;
/**
* Tabs density
*/
density: TabsDensity = 'normal';
/**
* @ignore
*/
tabsList: QueryList<TabComponent>;
/**
* Output that emits selected tab index
*/
selectedTab: EventEmitter<number> = new EventEmitter<number>();
constructor(private cdr: ChangeDetectorRef) {
}
ngAfterViewInit() {
// A render is being done after view init so setTimeout allows us to delay any attribute modification
// in the call stack so it can be taken account on next render only
setTimeout(() => {
this.tabs = this.tabsList?.toArray();
this.initializeClickListener();
this.initializeKeydownListener();
const activeIndex = this.tabs?.findIndex(tab => tab?.isActive);
this.goToTab(activeIndex === -1 ? 0 : activeIndex, false);
});
}
ngOnDestroy() {
this.unsubscribe.next();
this.unsubscribe.complete();
}
/**
* Check if tab is active
* @param index index of tab to check
*/
isActiveTab(index: number): boolean {
return this.activeTabIndex === index;
}
/**
* Allows you to navigate to tab
* @param index index of tab to select
* @param emit emits if set to true
*/
goToTab(index: number, emit = true) {
const validIndex = (Utils.isUndefinedOrNull(index) || index < 0) ? 0 : index;
this.activeTabIndex = validIndex;
this.renderTemplate(validIndex);
if (emit) {
this.selectedTab.emit(validIndex);
}
this.cdr.markForCheck();
}
/**
* @ignore
*/
focusNextFocusableTab() {
const focusableTabs = Array.from(this.tabs).filter(tab => !tab.isDisabled);
const focusedTabIndex = focusableTabs.findIndex(tab => document.activeElement === tab.tab.nativeElement);
const nextFocusableTab = focusableTabs[(focusedTabIndex + 1) % focusableTabs.length];
nextFocusableTab.tab.nativeElement.focus();
}
/**
* @ignore
*/
focusPreviousFocusableTab() {
const focusableTabs = Array.from(this.tabs).filter(tab => !tab.isDisabled);
const focusedTabIndex = focusableTabs.findIndex(tab => document.activeElement === tab.tab.nativeElement);
const previousFocusableTabIndex = focusedTabIndex === 0
? focusableTabs.length - 1
: focusedTabIndex - 1;
const previousFocusableTab = focusableTabs[previousFocusableTabIndex];
previousFocusableTab.tab.nativeElement.focus();
}
/**
* @ignore
*/
handleTabKeydown(event: KeyboardEvent) {
switch (event.key) {
case 'ArrowRight':
event.preventDefault();
this.focusNextFocusableTab();
break;
case 'ArrowLeft':
event.preventDefault();
this.focusPreviousFocusableTab();
break;
default:
}
}
getDensityClass() {
if(!this.density || this.density === 'normal') {
return;
}
return `${this.tabClass}--${this.density}`;
}
private initializeClickListener() {
if (Utils.isUndefinedOrNull(this.tabs)) {
return;
}
this.tabs.forEach((tab, index) => {
tab.tabSelect
.pipe(takeUntil(this.unsubscribe))
.subscribe(_ => {
this.goToTab(index);
});
});
}
private initializeKeydownListener() {
if (Utils.isUndefinedOrNull(this.tabs)) {
return;
}
this.tabs.forEach(tab => {
tab.tabMove.pipe(takeUntil(this.unsubscribe))
.subscribe(event => {
this.handleTabKeydown(event);
});
});
}
private renderTemplate(index: number) {
for (const tab of this.tabs) {
if (tab?.isActive) {
tab.setIsActive(false);
}
}
this.tabToRender = this.tabs?.[index];
this.tabToRender.setIsActive(true);
}
}