ngx-bootstrap
Version:
Angular Bootstrap
215 lines • 8.06 kB
JavaScript
import { Component, HostBinding, Input, Renderer2, ElementRef } from '@angular/core';
import { TabsetConfig } from './tabset.config';
// todo: add active event to tab
// todo: fix? mixing static and dynamic tabs position tabs in order of creation
export class TabsetComponent {
constructor(config, renderer, elementRef) {
this.renderer = renderer;
this.elementRef = elementRef;
this.clazz = true;
this.tabs = [];
this.classMap = {};
/** aria label for tab list */
this.ariaLabel = 'Tabs';
this.isDestroyed = false;
this._vertical = false;
this._justified = false;
this._type = 'tabs';
this._isKeysAllowed = true;
Object.assign(this, config);
}
/** if true tabs will be placed vertically */
get vertical() {
return this._vertical;
}
set vertical(value) {
this._vertical = value;
this.setClassMap();
}
/** if true tabs fill the container and have a consistent width */
get justified() {
return this._justified;
}
set justified(value) {
this._justified = value;
this.setClassMap();
}
/** navigation context class: 'tabs' or 'pills' */
get type() {
return this._type;
}
set type(value) {
this._type = value;
this.setClassMap();
}
get isKeysAllowed() {
return this._isKeysAllowed;
}
set isKeysAllowed(value) {
this._isKeysAllowed = value;
}
ngOnDestroy() {
this.isDestroyed = true;
}
addTab(tab) {
this.tabs.push(tab);
tab.active = this.tabs.length === 1 && !tab.active;
}
removeTab(tab, options = { reselect: true, emit: true }) {
const index = this.tabs.indexOf(tab);
if (index === -1 || this.isDestroyed) {
return;
}
// Select a new tab if the tab to be removed is selected and not destroyed
if (options.reselect && tab.active && this.hasAvailableTabs(index)) {
const newActiveIndex = this.getClosestTabIndex(index);
this.tabs[newActiveIndex].active = true;
}
if (options.emit) {
tab.removed.emit(tab);
}
this.tabs.splice(index, 1);
if (tab.elementRef.nativeElement.parentNode) {
this.renderer.removeChild(tab.elementRef.nativeElement.parentNode, tab.elementRef.nativeElement);
}
}
keyNavActions(event, index) {
if (!this.isKeysAllowed) {
return;
}
const list = Array.from(this.elementRef.nativeElement.querySelectorAll('.nav-link'));
// const activeElList = list.filter((el: HTMLElement) => !el.classList.contains('disabled'));
if (event.keyCode === 13 || event.key === 'Enter' || event.keyCode === 32 || event.key === 'Space') {
event.preventDefault();
const currentTab = list[(index) % list.length];
currentTab.click();
return;
}
if (event.keyCode === 39 || event.key === 'RightArrow') {
let nextTab;
let shift = 1;
do {
nextTab = list[(index + shift) % list.length];
shift++;
} while (nextTab.classList.contains('disabled'));
nextTab.focus();
return;
}
if (event.keyCode === 37 || event.key === 'LeftArrow') {
let previousTab;
let shift = 1;
let i = index;
do {
if ((i - shift) < 0) {
i = list.length - 1;
previousTab = list[i];
shift = 0;
}
else {
previousTab = list[i - shift];
}
shift++;
} while (previousTab.classList.contains('disabled'));
previousTab.focus();
return;
}
if (event.keyCode === 36 || event.key === 'Home') {
event.preventDefault();
let firstTab;
let shift = 0;
do {
firstTab = list[shift % list.length];
shift++;
} while (firstTab.classList.contains('disabled'));
firstTab.focus();
return;
}
if (event.keyCode === 35 || event.key === 'End') {
event.preventDefault();
let lastTab;
let shift = 1;
let i = index;
do {
if ((i - shift) < 0) {
i = list.length - 1;
lastTab = list[i];
shift = 0;
}
else {
lastTab = list[i - shift];
}
shift++;
} while (lastTab.classList.contains('disabled'));
lastTab.focus();
return;
}
if (event.keyCode === 46 || event.key === 'Delete') {
if (this.tabs[index].removable) {
this.removeTab(this.tabs[index]);
if (list[index + 1]) {
list[(index + 1) % list.length].focus();
return;
}
if (list[list.length - 1]) {
list[0].focus();
}
}
}
}
getClosestTabIndex(index) {
const tabsLength = this.tabs.length;
if (!tabsLength) {
return -1;
}
for (let step = 1; step <= tabsLength; step += 1) {
const prevIndex = index - step;
const nextIndex = index + step;
if (this.tabs[prevIndex] && !this.tabs[prevIndex].disabled) {
return prevIndex;
}
if (this.tabs[nextIndex] && !this.tabs[nextIndex].disabled) {
return nextIndex;
}
}
return -1;
}
hasAvailableTabs(index) {
const tabsLength = this.tabs.length;
if (!tabsLength) {
return false;
}
for (let i = 0; i < tabsLength; i += 1) {
if (!this.tabs[i].disabled && i !== index) {
return true;
}
}
return false;
}
setClassMap() {
this.classMap = {
'nav-stacked': this.vertical,
'flex-column': this.vertical,
'nav-justified': this.justified,
[`nav-${this.type}`]: true
};
}
}
TabsetComponent.decorators = [
{ type: Component, args: [{
selector: 'tabset',
template: "<ul class=\"nav\" [ngClass]=\"classMap\"\n (click)=\"$event.preventDefault()\"\n [attr.aria-label]=\"ariaLabel\"\n role=\"tablist\">\n <li *ngFor=\"let tabz of tabs; let i = index\" [ngClass]=\"['nav-item', tabz.customClass || '']\"\n [class.active]=\"tabz.active\" [class.disabled]=\"tabz.disabled\" (keydown)=\"keyNavActions($event, i)\">\n <a href=\"javascript:void(0);\" class=\"nav-link\" role=\"tab\"\n [attr.aria-controls]=\"tabz.id ? tabz.id : ''\"\n [attr.aria-selected]=\"!!tabz.active\"\n [attr.id]=\"tabz.id ? tabz.id + '-link' : ''\"\n [class.active]=\"tabz.active\" [class.disabled]=\"tabz.disabled\"\n (click)=\"tabz.active = true\">\n <span [ngTransclude]=\"tabz.headingRef\">{{ tabz.heading }}</span>\n <span *ngIf=\"tabz.removable\" (click)=\"$event.preventDefault(); removeTab(tabz);\" class=\"bs-remove-tab\"> ❌</span>\n </a>\n </li>\n</ul>\n<div class=\"tab-content\">\n <ng-content></ng-content>\n</div>\n",
styles: [":host .nav-tabs .nav-item.disabled a.disabled{cursor:default}"]
},] }
];
TabsetComponent.ctorParameters = () => [
{ type: TabsetConfig },
{ type: Renderer2 },
{ type: ElementRef }
];
TabsetComponent.propDecorators = {
vertical: [{ type: Input }],
justified: [{ type: Input }],
type: [{ type: Input }],
clazz: [{ type: HostBinding, args: ['class.tab-container',] }]
};
//# sourceMappingURL=tabset.component.js.map