Skip to content

Commit c465610

Browse files
author
Panchenko Vyacheslav
committed
feat(tabs): removable tabs
1 parent eec3cb4 commit c465610

File tree

5 files changed

+60
-8
lines changed

5 files changed

+60
-8
lines changed

components/tabs/readme.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ export class Tabset implements OnInit {
3131
export class Tab implements OnInit, OnDestroy, DoCheck {
3232
@Input() public heading:string;
3333
@Input() public disabled:boolean;
34+
@Input() public removable:boolean;
3435

3536
/** tab active state toogle */
3637
@HostBinding('class.active')
3738
@Input() public get active() {}
3839

3940
@Output() public select:EventEmitter<Tab> = new EventEmitter();
4041
@Output() public deselect:EventEmitter<Tab> = new EventEmitter();
42+
@Output() public removed:EventEmitter<Tab> = new EventEmitter();
4143
}
4244

4345
// directive TabHeading
@@ -56,10 +58,12 @@ export const TAB_DIRECTIVES:Array<any> = [Tab, TabHeading, Tabset];
5658
- `heading` (`string`) - tab header text
5759
- `active` (`?boolean=false`) - if tab is active equals true, or set `true` to activate tab
5860
- `disabled` (`?boolean=false`) - if `true` tab can not be activated
61+
- `removable` (`?boolean=false`) - if `true` tab can be removable, additional button will appear
5962

6063
### Tab events
6164
- `select` - fired when `tab` became active, `$event:Tab` equals to selected instance of `Tab` component
6265
- `deselect` - fired when `tab` became inactive, `$event:Tab` equals to deselected instance of `Tab` component
66+
- `removed` - fired before `tab` will be removed
6367

6468
### Tab heading
6569
Should be used to mark `<template>` element as a template for tab heading

components/tabs/tab.directive.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {Tabset} from './tabset.component';
1313
export class Tab implements OnDestroy {
1414
@Input() public heading:string;
1515
@Input() public disabled:boolean;
16+
@Input() public removable:boolean;
1617

1718
/** tab active state toogle */
1819
@HostBinding('class.active')
@@ -22,7 +23,7 @@ export class Tab implements OnDestroy {
2223

2324
@Output() public select:EventEmitter<Tab> = new EventEmitter();
2425
@Output() public deselect:EventEmitter<Tab> = new EventEmitter();
25-
26+
@Output() public removed:EventEmitter<Tab> = new EventEmitter();
2627

2728
public set active(active) {
2829
if (this.disabled && active || !active) {
@@ -52,6 +53,10 @@ export class Tab implements OnDestroy {
5253
this.tabset.addTab(this);
5354
}
5455

56+
ngOnInit() {
57+
this.removable = !!this.removable;
58+
}
59+
5560
ngOnDestroy() {
5661
this.tabset.removeTab(this);
5762
}

components/tabs/tabset.component.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import {Tab} from './tab.directive';
1515
[class.active]="tabz.active" [class.disabled]="tabz.disabled"
1616
(click)="tabz.active = true">
1717
<span [ngTransclude]="tabz.headingRef">{{tabz.heading}}</span>
18+
<span [hidden]="!tabz.removable">
19+
<span (click)="$event.preventDefault(); removeTab(tabz);" class="glyphicon glyphicon-remove-circle"></span>
20+
</span>
1821
</a>
1922
</li>
2023
</ul>
@@ -84,12 +87,45 @@ export class Tabset implements OnInit {
8487
return;
8588
}
8689
// Select a new tab if the tab to be removed is selected and not destroyed
87-
if (tab.active && this.tabs.length > 1) {
88-
// If this is the last tab, select the previous tab. else, the next tab.
89-
let newActiveIndex = index === this.tabs.length - 1 ? index - 1 : index + 1;
90+
if (tab.active && this.hasAvailableTabs(index)) {
91+
let newActiveIndex = this.getClosestTabIndex(index);
9092
this.tabs[newActiveIndex].active = true;
9193
}
9294

93-
this.tabs.slice(index, 1);
95+
tab.removed.emit(tab);
96+
this.tabs.splice(index, 1);
97+
}
98+
99+
private getClosestTabIndex (index:number):number {
100+
let tabsLength = this.tabs.length;
101+
if (!tabsLength) {
102+
return -1;
103+
}
104+
105+
for (let step = 1; step <= tabsLength; step += 1) {
106+
let prevIndex = index - step;
107+
let nextIndex = index + step;
108+
if (this.tabs[prevIndex] && !this.tabs[prevIndex].disabled) {
109+
return prevIndex;
110+
}
111+
if (this.tabs[nextIndex] && !this.tabs[nextIndex].disabled) {
112+
return nextIndex;
113+
}
114+
}
115+
return -1;
116+
}
117+
118+
private hasAvailableTabs (index:number) {
119+
let tabsLength = this.tabs.length;
120+
if (!tabsLength) {
121+
return false;
122+
}
123+
124+
for (let i = 0; i < tabsLength; i += 1) {
125+
if (!this.tabs[i].disabled && i !== index) {
126+
return true;
127+
}
128+
}
129+
return false;
94130
}
95131
}

demo/components/tabs/tabs-demo.html

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
[active]="tabz.active"
1616
(select)="tabz.active = true"
1717
(deselect)="tabz.active = false"
18-
[disabled]="tabz.disabled">
18+
[disabled]="tabz.disabled"
19+
[removable]="tabz.removable"
20+
(removed)="removeTabHandler(tabz)">
1921
{{tabz?.content}}
2022
</tab>
2123
<tab (select)="alertMe()">

demo/components/tabs/tabs-demo.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ let template = require('./tabs-demo.html');
1414
export class TabsDemo {
1515
public tabs:Array<any> = [
1616
{title: 'Dynamic Title 1', content: 'Dynamic content 1'},
17-
{title: 'Dynamic Title 2', content: 'Dynamic content 2', disabled: true}
17+
{title: 'Dynamic Title 2', content: 'Dynamic content 2', disabled: true},
18+
{title: 'Dynamic Title 3', content: 'Dynamic content 3', removable: true}
1819
];
1920

2021
public alertMe() {
@@ -25,5 +26,9 @@ export class TabsDemo {
2526

2627
public setActiveTab(index:number) {
2728
this.tabs[index].active = true;
28-
}
29+
};
30+
31+
public removeTabHandler(tab:any) {
32+
console.log('Remove Tab handler');
33+
};
2934
}

0 commit comments

Comments
 (0)