File

src/tabs/tab-header-group.component.ts

Extends

BaseTabHeader

Implements

AfterContentInit OnChanges OnInit OnDestroy

Metadata

Index

Properties
Methods
Inputs
Outputs
HostBindings
HostListeners
Accessors

Constructor

constructor(elementRef: ElementRef, changeDetectorRef: ChangeDetectorRef, eventService: EventService, renderer: Renderer2, i18n: I18n)
Parameters :
Name Type Optional
elementRef ElementRef No
changeDetectorRef ChangeDetectorRef No
eventService EventService No
renderer Renderer2 No
i18n I18n No

Inputs

isNavigation
Type : boolean
Default value : false

When true, sets each tab panel tabindex to -1 for navigation-style usage.

translations
Type : any
Default value : this.i18n.get().TABS

i18n strings for overflow controls and the tab list aria-label fallback.

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 true to have Tab items cached and not reloaded on tab switching. Duplicated from cds-tabs to support standalone headers.

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: default (16px) or lg (20px).

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: dark or light.

type
Type : "line" | "contained"
Default value : "line"
Inherited from BaseTabHeader
Defined in BaseTabHeader:51

Visual style of the tab list: line or contained.

Outputs

tabClose
Type : EventEmitter<number>

Emits when a tab close control is used (with dismissable). The emitted value is the tab index.

HostBindings

class.cds--tabs--full-width
Type : boolean
class.cds--tabs--tall
Type : boolean

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

HostListeners

keydown
Arguments : '$event'
keydown(event)

Methods

getSelectedTab
getSelectedTab()
Returns : any
keyboardInput
keyboardInput(event)
Decorators :
@HostListener('keydown', ['$event'])
Parameters :
Name Optional
event No
Returns : void
ngAfterContentInit
ngAfterContentInit()
Returns : void
ngOnChanges
ngOnChanges(changes: SimpleChanges)
Parameters :
Name Type Optional
changes SimpleChanges No
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()
Returns : void
Protected setFirstTab
setFirstTab()

Determines which Tab is initially selected.

Returns : void
handleOverflowNavClick
handleOverflowNavClick(direction: number, numOftabs: number)
Inherited from BaseTabHeader
Defined in BaseTabHeader:156
Parameters :
Name Type Optional Default value
direction number No
numOftabs number No 0
Returns : void
handleOverflowNavMouseDown
handleOverflowNavMouseDown(direction: number)
Inherited from BaseTabHeader
Defined in BaseTabHeader:168
Parameters :
Name Type Optional
direction number No
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

Properties

activeIndex
Type : number | null
Default value : null

Focused tab index when followFocus is false (manual activation).

closeSubscriptionTracker
Default value : new Subscription()
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()
tabHeaderQuery
Type : QueryList<TabHeaderBase>
Decorators :
@ContentChildren(TabHeaderBase)

Projected tab headers (TabHeaderBase: directive or cds-tab-header).

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

Accessors

fullWidthClass
getfullWidthClass()
tallClass
gettallClass()

We use taller rows when any header has a secondary label.

Returns : boolean
hasSecondaryLabelTabs
gethasSecondaryLabelTabs()
distributeWidth
getdistributeWidth()

True when fullWidth applies (contained, fewer than 9 headers).

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();
			}
		});
	}
}
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""