UNPKG

@engie-group/fluid-design-system-angular

Version:

Fluid Design System Angular

199 lines (169 loc) 4.8 kB
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'; @Component({ selector: 'nj-tabs', templateUrl: './tabs.component.html', styleUrls: ['./tabs.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, standalone: true, imports: [TabComponent, CommonModule] }) 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 */ @Input() label: string; /** * Tabs density */ @Input() density: TabsDensity = 'normal'; /** * @ignore */ @ContentChildren(TabComponent) tabsList: QueryList<TabComponent>; /** * Output that emits selected tab index */ @Output() 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); } }