src/tabs/tab-header-group.component.ts
AfterContentInit
OnChanges
OnInit
OnDestroy
| selector | cds-tab-header-group, ibm-tab-header-group |
| template | |
Properties |
|
Methods |
Inputs |
Outputs |
HostBindings |
HostListeners |
Accessors |
constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, eventService: EventService, renderer: Renderer2, i18n: I18n)
|
||||||||||||||||||
|
Defined in src/tabs/tab-header-group.component.ts:167
|
||||||||||||||||||
|
Parameters :
|
| isNavigation | |
Type : boolean
|
|
Default value : false
|
|
|
Defined in src/tabs/tab-header-group.component.ts:142
|
|
|
When |
|
| translations | |
Type : any
|
|
Default value : this.i18n.get().TABS
|
|
|
Defined in src/tabs/tab-header-group.component.ts:137
|
|
|
i18n strings for overflow controls and the tab list |
|
| ariaLabel | |
Type : string
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:33
|
|
|
Sets the aria label on the nav element. |
|
| ariaLabelledby | |
Type : string
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:37
|
|
|
Sets the aria labelledby on the nav element. |
|
| cacheActive | |
Type : boolean
|
|
Default value : false
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:25
|
|
|
Set to |
|
| contentAfter | |
Type : TemplateRef<any>
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:46
|
|
|
Template projected after tab items inside the tab list. |
|
| contentBefore | |
Type : TemplateRef<any>
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:42
|
|
|
Template projected before tab items inside the tab list. |
|
| dismissable | |
Type : boolean
|
|
Default value : false
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:70
|
|
|
Show a close control on each tab. |
|
| followFocus | |
Type : boolean
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:29
|
|
|
Set to 'true' to have tabs automatically activated and have their content displayed when they receive focus. |
|
| fullWidth | |
Type : boolean
|
|
Default value : false
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:65
|
|
|
Contained only: Evenly sized tabs across the row (must have fewer than 9 tabs). |
|
| iconSize | |
Type : "default" | "lg"
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:60
|
|
|
When using icon-only tabs, icon size: |
|
| scrollDebounceWait | |
Type : number
|
|
Default value : 200
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:80
|
|
|
Debounce (ms) for tab list scroll events; affects overflow chevron updates. |
|
| scrollIntoView | |
Type : boolean
|
|
Default value : false
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:75
|
|
|
Scroll the active tab into view on focus/select. |
|
| theme | |
Type : "dark" | "light"
|
|
Default value : "dark"
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:55
|
|
|
Theme for contained tabs: |
|
| type | |
Type : "line" | "contained"
|
|
Default value : "line"
|
|
|
Inherited from
BaseTabHeader
|
|
|
Defined in
BaseTabHeader:51
|
|
|
Visual style of the tab list: |
|
| tabClose | |
Type : EventEmitter<number>
|
|
|
Defined in src/tabs/tab-header-group.component.ts:148
|
|
|
Emits when a tab close control is used (with |
|
| class.cds--tabs--full-width |
Type : boolean
|
|
Defined in src/tabs/tab-header-group.component.ts:102
|
| class.cds--tabs--tall |
Type : boolean
|
|
Defined in src/tabs/tab-header-group.component.ts:109
|
|
We use taller rows when any header has a secondary label. |
| class.cds--layout--size-lg |
Type : boolean
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:98
|
| class.cds--tabs |
Type : boolean
|
Default value : true
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:82
|
| class.cds--tabs__icon--default |
Type : boolean
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:92
|
| class.cds--tabs__icon--lg |
Type : boolean
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:95
|
| class.cds--tabs--contained |
Type : boolean
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:83
|
| class.cds--tabs--dismissable |
Type : boolean
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:89
|
| class.cds--tabs--light |
Type : boolean
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:86
|
| keydown |
Arguments : '$event'
|
keydown(event)
|
|
Defined in src/tabs/tab-header-group.component.ts:180
|
| getSelectedTab |
getSelectedTab()
|
|
Defined in src/tabs/tab-header-group.component.ts:320
|
|
Returns :
any
|
| keyboardInput | ||||
keyboardInput(event)
|
||||
Decorators :
@HostListener('keydown', ['$event'])
|
||||
|
Defined in src/tabs/tab-header-group.component.ts:180
|
||||
|
Parameters :
Returns :
void
|
| ngAfterContentInit |
ngAfterContentInit()
|
|
Defined in src/tabs/tab-header-group.component.ts:252
|
|
Returns :
void
|
| ngOnChanges | ||||||
ngOnChanges(changes: SimpleChanges)
|
||||||
|
Defined in src/tabs/tab-header-group.component.ts:303
|
||||||
|
Parameters :
Returns :
void
|
| ngOnDestroy |
ngOnDestroy()
|
|
Defined in src/tabs/tab-header-group.component.ts:297
|
|
Returns :
void
|
| ngOnInit |
ngOnInit()
|
|
Defined in src/tabs/tab-header-group.component.ts:248
|
|
Returns :
void
|
| Protected setFirstTab |
setFirstTab()
|
|
Defined in src/tabs/tab-header-group.component.ts:334
|
|
Determines which
Returns :
void
|
| handleOverflowNavClick |
handleOverflowNavClick(direction: number, numOftabs: number)
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:156
|
|
Returns :
void
|
| handleOverflowNavMouseDown | ||||||
handleOverflowNavMouseDown(direction: number)
|
||||||
|
Inherited from
BaseTabHeader
|
||||||
|
Defined in
BaseTabHeader:168
|
||||||
|
Parameters :
Returns :
void
|
| handleOverflowNavMouseUp |
handleOverflowNavMouseUp()
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:193
|
|
Clear intervals/Timeout & reset scroll behavior
Returns :
void
|
| handleScroll |
handleScroll()
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:143
|
|
Returns :
void
|
| activeIndex |
Type : number | null
|
Default value : null
|
|
Defined in src/tabs/tab-header-group.component.ts:167
|
|
Focused tab index when |
| closeSubscriptionTracker |
Default value : new Subscription()
|
|
Defined in src/tabs/tab-header-group.component.ts:157
|
| currentSelectedTab |
Type : number
|
Default value : 0
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:162
|
|
Index of the selected tab for keyboard logic. |
| headerContainer |
Decorators :
@ViewChild('tabList', {static: true})
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:155
|
| selectedSubscriptionTracker |
Default value : new Subscription()
|
|
Defined in src/tabs/tab-header-group.component.ts:156
|
| tabHeaderQuery |
Type : QueryList<TabHeaderBase>
|
Decorators :
@ContentChildren(TabHeaderBase)
|
|
Defined in src/tabs/tab-header-group.component.ts:153
|
|
Projected tab headers ( |
| Readonly clickMultiplier |
Type : number
|
Default value : 1.5
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:114
|
| Protected longPressInterval |
Type : null
|
Default value : null
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:116
|
| Readonly longPressMultiplier |
Type : number
|
Default value : 3
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:113
|
| Readonly OVERFLOW_BUTTON_OFFSET |
Type : number
|
Default value : 44
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:112
|
| Protected scrollDebounceTimer |
Type : any
|
Default value : null
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:118
|
| tabsClass |
Default value : true
|
Decorators :
@HostBinding('class.cds--tabs')
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:82
|
| Protected tickInterval |
Type : null
|
Default value : null
|
|
Inherited from
BaseTabHeader
|
|
Defined in
BaseTabHeader:117
|
| fullWidthClass |
getfullWidthClass()
|
|
Defined in src/tabs/tab-header-group.component.ts:102
|
| tallClass |
gettallClass()
|
|
Defined in src/tabs/tab-header-group.component.ts:109
|
|
We use taller rows when any header has a secondary label.
Returns :
boolean
|
| hasSecondaryLabelTabs |
gethasSecondaryLabelTabs()
|
|
Defined in src/tabs/tab-header-group.component.ts:113
|
| distributeWidth |
getdistributeWidth()
|
|
Defined in src/tabs/tab-header-group.component.ts:127
|
|
True when
Returns :
boolean
|
import {
Component,
QueryList,
Input,
Output,
EventEmitter,
HostBinding,
HostListener,
ContentChildren,
AfterContentInit,
ElementRef,
OnChanges,
SimpleChanges,
ChangeDetectorRef,
ViewChild,
OnInit,
OnDestroy,
Renderer2
} from "@angular/core";
import { Subscription } from "rxjs";
import { EventService } from "carbon-components-angular/utils";
import { I18n } from "carbon-components-angular/i18n";
import { TabHeaderBase } from "./tab-header.directive";
import { BaseTabHeader } from "./base-tab-header.component";
@Component({
selector: "cds-tab-header-group, ibm-tab-header-group",
template: `
<button
type="button"
class="cds--tab--overflow-nav-button cds--tab--overflow-nav-button--previous"
[ngClass]="{
'cds--tab--overflow-nav-button--hidden': leftOverflowNavButtonHidden
}"
[attr.aria-hidden]="leftOverflowNavButtonHidden"
[attr.tabindex]="-1"
[attr.aria-label]="translations.BUTTON_ARIA_LEFT"
[attr.title]="translations.BUTTON_ARIA_LEFT"
(click)="handleOverflowNavClick(-1, tabHeaderQuery.length)"
(pointerdown)="handleOverflowNavMouseDown(-1)"
(pointerup)="handleOverflowNavMouseUp()"
(pointerleave)="handleOverflowNavMouseUp()"
(pointerout)="handleOverflowNavMouseUp()"
(pointercancel)="handleOverflowNavMouseUp()">
<svg
focusable="false"
preserveAspectRatio="xMidYMid meet"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width="16"
height="16"
viewBox="0 0 16 16"
aria-hidden="true">
<path d="M5 8L10 3 10.7 3.7 6.4 8 10.7 12.3 10 13z"></path>
</svg>
</button>
<div
class="cds--tab--list"
role="tablist"
[attr.aria-label]="ariaLabel || translations.HEADER_ARIA_LABEL"
[attr.aria-labelledby]="ariaLabelledby || null"
(scroll)="handleScroll()"
#tabList>
<ng-container [ngTemplateOutlet]="contentBefore"></ng-container>
<ng-content></ng-content>
<ng-container [ngTemplateOutlet]="contentAfter"></ng-container>
</div>
<button
type="button"
class="cds--tab--overflow-nav-button cds--tab--overflow-nav-button--next"
[ngClass]="{
'cds--tab--overflow-nav-button--hidden': rightOverflowNavButtonHidden
}"
[attr.aria-hidden]="rightOverflowNavButtonHidden"
[attr.tabindex]="-1"
[attr.aria-label]="translations.BUTTON_ARIA_RIGHT"
[attr.title]="translations.BUTTON_ARIA_RIGHT"
(click)="handleOverflowNavClick(1, tabHeaderQuery.length)"
(pointerdown)="handleOverflowNavMouseDown(1)"
(pointerup)="handleOverflowNavMouseUp()"
(pointerleave)="handleOverflowNavMouseUp()"
(pointerout)="handleOverflowNavMouseUp()"
(pointercancel)="handleOverflowNavMouseUp()">
<svg
focusable="false"
preserveAspectRatio="xMidYMid meet"
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
width="16"
height="16"
viewBox="0 0 16 16"
aria-hidden="true">
<path d="M11 8L6 13 5.3 12.3 9.6 8 5.3 3.7 6 3z"></path>
</svg>
</button>
`
})
export class TabHeaderGroup extends BaseTabHeader implements AfterContentInit, OnChanges, OnInit, OnDestroy {
@HostBinding("class.cds--tabs--full-width") get fullWidthClass() {
return this.distributeWidth;
}
/**
* We use taller rows when any header has a secondary label.
*/
@HostBinding("class.cds--tabs--tall") get tallClass(): boolean {
return this.hasSecondaryLabelTabs;
}
get hasSecondaryLabelTabs(): boolean {
if (!this.tabHeaderQuery || this.type !== "contained") {
return false;
}
return this.tabHeaderQuery.toArray().some(
h =>
h.secondaryLabel != null &&
String(h.secondaryLabel).trim() !== ""
);
}
/**
* True when `fullWidth` applies (contained, fewer than 9 headers).
*/
get distributeWidth(): boolean {
return (
this.fullWidth &&
this.type === "contained" &&
(this.tabHeaderQuery ? this.tabHeaderQuery.length < 9 : false)
);
}
/**
* i18n strings for overflow controls and the tab list `aria-label` fallback.
*/
@Input() translations = this.i18n.get().TABS;
/**
* When `true`, sets each tab panel `tabindex` to `-1` for navigation-style usage.
*/
@Input() isNavigation = false;
/**
* Emits when a tab close control is used (with `dismissable`).
* The emitted value is the tab index.
*/
@Output() tabClose: EventEmitter<number> = new EventEmitter<number>();
/**
* Projected tab headers (`TabHeaderBase`: directive or `cds-tab-header`).
*/
@ContentChildren(TabHeaderBase) tabHeaderQuery: QueryList<TabHeaderBase>;
@ViewChild("tabList", { static: true }) headerContainer;
selectedSubscriptionTracker = new Subscription();
closeSubscriptionTracker = new Subscription();
/**
* Index of the selected tab for keyboard logic.
*/
currentSelectedTab = 0;
/**
* Focused tab index when `followFocus` is false (manual activation).
*/
activeIndex: number | null = null;
constructor(
protected elementRef: ElementRef,
protected changeDetectorRef: ChangeDetectorRef,
protected eventService: EventService,
protected renderer: Renderer2,
protected i18n: I18n
) {
super(elementRef, changeDetectorRef, eventService, renderer);
}
@HostListener("keydown", ["$event"])
keyboardInput(event) {
const tabHeadersArray = this.tabHeaderQuery.toArray();
if (event.key === "ArrowRight") {
if (this.currentSelectedTab < tabHeadersArray.length - 1) {
event.preventDefault();
if (this.followFocus && !tabHeadersArray[this.currentSelectedTab + 1].disabled) {
tabHeadersArray[this.currentSelectedTab + 1].selectTab();
} else {
tabHeadersArray[this.currentSelectedTab + 1].focus();
this.currentSelectedTab++;
}
} else {
event.preventDefault();
if (this.followFocus && !tabHeadersArray[0].disabled) {
tabHeadersArray[0].selectTab();
} else {
tabHeadersArray[0].focus();
this.currentSelectedTab = 0;
}
}
}
if (event.key === "ArrowLeft") {
if (this.currentSelectedTab > 0) {
event.preventDefault();
if (this.followFocus && !tabHeadersArray[this.currentSelectedTab - 1].disabled) {
tabHeadersArray[this.currentSelectedTab - 1].selectTab();
} else {
tabHeadersArray[this.currentSelectedTab - 1].focus();
this.currentSelectedTab--;
}
} else {
event.preventDefault();
if (this.followFocus && !tabHeadersArray[tabHeadersArray.length - 1].disabled) {
tabHeadersArray[tabHeadersArray.length - 1].selectTab();
} else {
tabHeadersArray[tabHeadersArray.length - 1].focus();
this.currentSelectedTab = tabHeadersArray.length - 1;
}
}
}
if (event.key === "Home") {
event.preventDefault();
if (this.followFocus && !tabHeadersArray[0].disabled) {
tabHeadersArray[0].selectTab();
} else {
tabHeadersArray[0].focus();
this.currentSelectedTab = 0;
}
}
if (event.key === "End") {
event.preventDefault();
if (this.followFocus && !tabHeadersArray[tabHeadersArray.length - 1].disabled) {
tabHeadersArray[tabHeadersArray.length - 1].selectTab();
} else {
tabHeadersArray[tabHeadersArray.length - 1].focus();
this.currentSelectedTab = tabHeadersArray.length - 1;
}
}
if ((event.key === " ") && !this.followFocus) {
tabHeadersArray[this.currentSelectedTab].selectTab();
}
}
ngOnInit() {
this.eventService.on(window as any, "resize", () => this.handleScroll());
}
ngAfterContentInit() {
// Reallocate trackers because subscriptions are permanently closed after unsubscribe
this.selectedSubscriptionTracker.unsubscribe();
this.closeSubscriptionTracker.unsubscribe();
this.selectedSubscriptionTracker = new Subscription();
this.closeSubscriptionTracker = new Subscription();
if (this.tabHeaderQuery) {
this.tabHeaderQuery.toArray()
.forEach(tabHeader => {
tabHeader.cacheActive = this.cacheActive;
tabHeader.dismissable = this.dismissable;
tabHeader.paneTabIndex = this.isNavigation ? null : 0;
});
}
const headersArray = this.tabHeaderQuery.toArray();
headersArray.forEach(tabHeader => {
this.selectedSubscriptionTracker.add(
tabHeader.selected.subscribe(() => {
this.currentSelectedTab = this.tabHeaderQuery.toArray().indexOf(tabHeader);
// The Filter takes the current selected tab out, then all other headers are
// deactivated and their associated pane references are also deactivated.
this.tabHeaderQuery.toArray().filter(header => header !== tabHeader)
.forEach(filteredHeader => {
filteredHeader.active = false;
if (filteredHeader.paneReference) {
filteredHeader.paneReference.active = false;
}
});
})
);
this.closeSubscriptionTracker.add(
tabHeader.tabClose.subscribe(() => {
const index = this.tabHeaderQuery.toArray().indexOf(tabHeader);
this.tabClose.emit(index);
})
);
});
this.setFirstTab();
}
ngOnDestroy() {
this.selectedSubscriptionTracker.unsubscribe();
this.closeSubscriptionTracker.unsubscribe();
clearTimeout(this.scrollDebounceTimer);
}
ngOnChanges(changes: SimpleChanges) {
if (this.tabHeaderQuery) {
if (changes.cacheActive) {
this.tabHeaderQuery.toArray().forEach(tabHeader => tabHeader.cacheActive = this.cacheActive);
}
if (changes.dismissable) {
this.tabHeaderQuery.toArray().forEach(tabHeader => tabHeader.dismissable = this.dismissable);
}
if (changes.isNavigation) {
this.tabHeaderQuery.toArray()
.forEach(tabHeader => tabHeader.paneTabIndex = this.isNavigation ? null : 0);
}
}
}
getSelectedTab(): any {
const selected = this.tabHeaderQuery.toArray()[this.currentSelectedTab];
if (selected) {
return selected;
}
return {
headingIsTemplate: false,
heading: ""
};
}
/**
* Determines which `Tab` is initially selected.
*/
protected setFirstTab() {
setTimeout(() => {
const headers = this.tabHeaderQuery.toArray();
let selectedHeader = headers.find(h => h.active || h.paneReference?.active);
if (!selectedHeader && headers.length > 0) {
selectedHeader = headers[0];
}
if (selectedHeader) {
selectedHeader.selectTab();
this.activeIndex = this.currentSelectedTab;
this.changeDetectorRef.markForCheck();
}
});
}
}