projects/kalidea/kaligraphi/src/lib/03-layout/kal-tabs/kal-tab-group/kal-tab-group.component.ts
import {
AfterContentInit,
AfterViewInit,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ContentChildren,
EventEmitter,
forwardRef,
HostListener,
Output,
QueryList,
ViewChildren,
ViewEncapsulation
} from '@angular/core';
import { CdkPortalOutlet, TemplatePortal } from '@angular/cdk/portal';
import { ENTER, SPACE } from '@angular/cdk/keycodes';
import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
import { Subscription } from 'rxjs';
import { KalTabComponent } from '../kal-tab/kal-tab.component';
import { KalTabChange } from '../kal-tab-change';
import { KalTabHeaderComponent } from '../kal-tab-header/kal-tab-header.component';
import { buildProviders, FormElementComponent } from '../../../utils/forms/form-element.component';
import { AutoUnsubscribe } from '../../../utils/decorators/auto-unsubscribe';
@Component({
selector: 'kal-tab-group',
templateUrl: './kal-tab-group.component.html',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
providers: buildProviders(KalTabGroupComponent)
})
export class KalTabGroupComponent extends FormElementComponent<any> implements AfterContentInit, AfterViewInit {
/**
* This event is emitted when a tab is selected
*/
@Output() selectedTab = new EventEmitter<KalTabChange>();
/**
* List of kal tab component
*/
@ContentChildren(forwardRef(() => KalTabComponent), {descendants: true}) tabs: QueryList<KalTabComponent>;
/**
* List of kal tab header component
*/
@ViewChildren(KalTabHeaderComponent) headers: QueryList<KalTabHeaderComponent>;
/**
* The index of the selected tab
*/
private selectedTabIndex = 0;
/**
* Manages keyboard events for optionsComponent in the panel
*/
private keyManager: ActiveDescendantKeyManager<KalTabHeaderComponent>;
/**
* Whether or not the select is focus
*/
private isFocused: boolean;
/**
* Tab to select when the content is init
*/
private tabToSelect = null;
@AutoUnsubscribe()
private subscription = Subscription.EMPTY;
constructor(private cdr: ChangeDetectorRef) {
super();
}
/**
* Return index of selected tab
*/
get selectedIndex(): number {
return this.selectedTabIndex;
}
/**
* Mark for check on tab group
*/
markForTabGroupCheck() {
this.cdr.markForCheck();
}
/**
* @inheritDoc
*/
writeValue(value = null) {
this.tabToSelect = value;
if ('' + value && this.tabs) {
const tabIndex = this.getTabIndex(this.tabToSelect);
const selectedTab = this.tabs.find((element, i) => i === tabIndex);
if (selectedTab) {
this.selectTabHeader(selectedTab, tabIndex, {emitEvent: false});
}
}
}
getTabIndex(value): number {
return (typeof value === 'string') ? this.tabs?.toArray().findIndex(e => e.value === value) : value;
}
/**
* Select a tab and emit an event with the index of the selected tab
*/
selectTabHeader(tab: KalTabComponent, tabIndex: number, params = {emitEvent: true}) {
if (!tab.disabled) {
this.selectedTabIndex = tabIndex;
this.keyManager.setActiveItem(this.selectedIndex);
const value = this.tabs.toArray()[tabIndex]?.value;
this.selectedTab.emit(new KalTabChange(tab, value));
if (params.emitEvent) {
this.notifyUpdate(typeof value === 'string' ? value : tabIndex);
}
}
}
/**
* Attach a template portal
*/
attachTemplatePortal(portalOutlet: CdkPortalOutlet, content: TemplatePortal, cdr: ChangeDetectorRef) {
if (content) {
portalOutlet.attachTemplatePortal(content);
cdr.detectChanges();
}
}
/**
* Focus the tab element
*/
@HostListener('focus')
focus(): void {
this.isFocused = true;
this.keyManager.setActiveItem(this.selectedIndex);
}
/**
* Blur the tab element
*/
@HostListener('blur')
blur() {
this.isFocused = false;
this.keyManager.setActiveItem(this.selectedIndex);
}
/**
* Handles all keydown events on the tab
*/
@HostListener('keydown', ['$event'])
handleKeydown(event: KeyboardEvent): void {
const {keyCode} = event;
const isOpenKey = keyCode === ENTER || keyCode === SPACE;
if (!this.isFocused) {
return;
}
if (isOpenKey && this.keyManager.activeItem) {
event.preventDefault();
const tabToSelect = this.tabs.find((item, i) => i === this.keyManager.activeItemIndex);
this.selectTabHeader(tabToSelect, this.keyManager.activeItemIndex);
} else {
this.keyManager.onKeydown(event);
}
}
ngAfterContentInit() {
this.subscription = this.tabs.changes.subscribe(
() => {
this.cdr.markForCheck();
}
);
if (this.tabToSelect) {
this.selectedTabIndex = this.getTabIndex(this.tabToSelect);
} else {
this.tabs.forEach(
(tab, index) => {
if (tab.selected) {
this.selectedTabIndex = index;
return;
}
}
);
}
this.cdr.markForCheck();
}
ngAfterViewInit(): void {
this.keyManager = new ActiveDescendantKeyManager<KalTabHeaderComponent>(this.headers).withHorizontalOrientation('ltr');
}
}