sass-references/angular-material/material/tabs/tab-header.spec.ts

792 lines
28 KiB
TypeScript

import {Dir, Direction} from '@angular/cdk/bidi';
import {END, ENTER, HOME, LEFT_ARROW, RIGHT_ARROW, SPACE} from '@angular/cdk/keycodes';
import {MutationObserverFactory, ObserversModule} from '@angular/cdk/observers';
import {SharedResizeObserver} from '@angular/cdk/observers/private';
import {PortalModule} from '@angular/cdk/portal';
import {ScrollingModule, ViewportRuler} from '@angular/cdk/scrolling';
import {
createKeyboardEvent,
createMouseEvent,
dispatchEvent,
dispatchFakeEvent,
dispatchKeyboardEvent,
} from '@angular/cdk/testing/private';
import {ChangeDetectorRef, Component, ViewChild, inject} from '@angular/core';
import {
ComponentFixture,
TestBed,
discardPeriodicTasks,
fakeAsync,
flush,
flushMicrotasks,
tick,
waitForAsync,
} from '@angular/core/testing';
import {MatRippleModule} from '@angular/material/core';
import {By} from '@angular/platform-browser';
import {Subject} from 'rxjs';
import {MatTabHeader} from './tab-header';
import {MatTabLabelWrapper} from './tab-label-wrapper';
describe('MatTabHeader', () => {
let fixture: ComponentFixture<SimpleTabHeaderApp>;
let appComponent: SimpleTabHeaderApp;
let resizeEvents: Subject<ResizeObserverEntry[]>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
PortalModule,
MatRippleModule,
ScrollingModule,
ObserversModule,
MatTabHeader,
MatTabLabelWrapper,
SimpleTabHeaderApp,
],
providers: [ViewportRuler],
});
resizeEvents = new Subject();
spyOn(TestBed.inject(SharedResizeObserver), 'observe').and.returnValue(resizeEvents);
}));
describe('focusing', () => {
let tabListContainer: HTMLElement;
beforeEach(() => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
fixture.detectChanges();
appComponent = fixture.componentInstance;
tabListContainer = appComponent.tabHeader._tabListContainer.nativeElement;
});
it('should initialize to the selected index', () => {
// Recreate the fixture so we can test that it works with a non-default selected index
fixture.destroy();
fixture = TestBed.createComponent(SimpleTabHeaderApp);
fixture.componentInstance.selectedIndex = 1;
fixture.detectChanges();
appComponent = fixture.componentInstance;
tabListContainer = appComponent.tabHeader._tabListContainer.nativeElement;
expect(appComponent.tabHeader.focusIndex).toBe(1);
});
it('should send focus change event', () => {
appComponent.tabHeader.focusIndex = 2;
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(2);
});
it('should be able to focus a disabled tab', () => {
appComponent.tabHeader.focusIndex = 0;
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(0);
appComponent.tabHeader.focusIndex = appComponent.disabledTabIndex;
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(appComponent.disabledTabIndex);
});
it('should move focus right including over disabled tabs', () => {
appComponent.tabHeader.focusIndex = 0;
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(0);
expect(appComponent.disabledTabIndex).toBe(1);
dispatchKeyboardEvent(tabListContainer, 'keydown', RIGHT_ARROW);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(1);
dispatchKeyboardEvent(tabListContainer, 'keydown', RIGHT_ARROW);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(2);
});
it('should move focus left including over disabled tabs', () => {
appComponent.tabHeader.focusIndex = 3;
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(3);
// Move focus left to index 3
dispatchKeyboardEvent(tabListContainer, 'keydown', LEFT_ARROW);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(2);
expect(appComponent.disabledTabIndex).toBe(1);
dispatchKeyboardEvent(tabListContainer, 'keydown', LEFT_ARROW);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(1);
});
it('should support key down events to move and select focus', () => {
appComponent.tabHeader.focusIndex = 0;
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(0);
// Move focus right to 1
dispatchKeyboardEvent(tabListContainer, 'keydown', RIGHT_ARROW);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(1);
// Try to select 1. Should not work since it's disabled.
expect(appComponent.selectedIndex).toBe(0);
const firstEnterEvent = dispatchKeyboardEvent(tabListContainer, 'keydown', ENTER);
fixture.detectChanges();
expect(appComponent.selectedIndex).toBe(0);
expect(firstEnterEvent.defaultPrevented).toBe(false);
// Move focus right to 2
dispatchKeyboardEvent(tabListContainer, 'keydown', RIGHT_ARROW);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(2);
// Select 2 which is enabled.
expect(appComponent.selectedIndex).toBe(0);
const secondEnterEvent = dispatchKeyboardEvent(tabListContainer, 'keydown', ENTER);
fixture.detectChanges();
expect(appComponent.selectedIndex).toBe(2);
expect(secondEnterEvent.defaultPrevented).toBe(true);
// Move focus left to 1
dispatchKeyboardEvent(tabListContainer, 'keydown', LEFT_ARROW);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(1);
// Move again to 0
dispatchKeyboardEvent(tabListContainer, 'keydown', LEFT_ARROW);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(0);
// Select the focused 0 using space.
expect(appComponent.selectedIndex).toBe(2);
const spaceEvent = dispatchKeyboardEvent(tabListContainer, 'keydown', SPACE);
fixture.detectChanges();
expect(appComponent.selectedIndex).toBe(0);
expect(spaceEvent.defaultPrevented).toBe(true);
});
it('should not prevent the default space/enter action if the current is selected', () => {
appComponent.tabHeader.focusIndex = appComponent.tabHeader.selectedIndex = 0;
fixture.detectChanges();
const spaceEvent = dispatchKeyboardEvent(tabListContainer, 'keydown', SPACE);
fixture.detectChanges();
expect(spaceEvent.defaultPrevented).toBe(false);
const enterEvent = dispatchKeyboardEvent(tabListContainer, 'keydown', ENTER);
fixture.detectChanges();
expect(enterEvent.defaultPrevented).toBe(false);
});
it('should move focus to the first tab when pressing HOME', () => {
appComponent.tabHeader.focusIndex = 3;
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(3);
const event = dispatchKeyboardEvent(tabListContainer, 'keydown', HOME);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(0);
expect(event.defaultPrevented).toBe(true);
});
it('should focus disabled items when moving focus using HOME', () => {
appComponent.tabHeader.focusIndex = 3;
appComponent.tabs[0].disabled = true;
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(3);
dispatchKeyboardEvent(tabListContainer, 'keydown', HOME);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(0);
});
it('should move focus to the last tab when pressing END', () => {
appComponent.tabHeader.focusIndex = 0;
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(0);
const event = dispatchKeyboardEvent(tabListContainer, 'keydown', END);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(3);
expect(event.defaultPrevented).toBe(true);
});
it('should focus disabled items when moving focus using END', () => {
appComponent.tabHeader.focusIndex = 0;
appComponent.tabs[3].disabled = true;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(0);
dispatchKeyboardEvent(tabListContainer, 'keydown', END);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(3);
});
it('should not do anything if a modifier key is pressed', () => {
const rightArrowEvent = createKeyboardEvent('keydown', RIGHT_ARROW, undefined, {shift: true});
const enterEvent = createKeyboardEvent('keydown', ENTER, undefined, {shift: true});
appComponent.tabHeader.focusIndex = 0;
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(0);
dispatchEvent(tabListContainer, rightArrowEvent);
fixture.detectChanges();
expect(appComponent.tabHeader.focusIndex).toBe(0);
expect(rightArrowEvent.defaultPrevented).toBe(false);
expect(appComponent.selectedIndex).toBe(0);
dispatchEvent(tabListContainer, enterEvent);
fixture.detectChanges();
expect(appComponent.selectedIndex).toBe(0);
expect(enterEvent.defaultPrevented).toBe(false);
});
});
describe('pagination', () => {
describe('ltr', () => {
beforeEach(() => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
appComponent = fixture.componentInstance;
appComponent.dir = 'ltr';
fixture.detectChanges();
});
it('should show width when tab list width exceeds container', () => {
fixture.detectChanges();
expect(appComponent.tabHeader._showPaginationControls).toBe(false);
// Add enough tabs that it will obviously exceed the width
appComponent.addTabsForScrolling();
fixture.detectChanges();
expect(appComponent.tabHeader._showPaginationControls).toBe(true);
});
it('should scroll to show the focused tab label', () => {
appComponent.addTabsForScrolling();
fixture.detectChanges();
expect(appComponent.tabHeader.scrollDistance).toBe(0);
// Focus on the last tab, expect this to be the maximum scroll distance.
appComponent.tabHeader.focusIndex = appComponent.tabs.length - 1;
fixture.detectChanges();
const {offsetLeft, offsetWidth} = appComponent.getSelectedLabel(
appComponent.tabHeader.focusIndex,
);
const viewLength = appComponent.getViewLength();
expect(appComponent.tabHeader.scrollDistance).toBe(offsetLeft + offsetWidth - viewLength);
// Focus on the first tab, expect this to be the maximum scroll distance.
appComponent.tabHeader.focusIndex = 0;
fixture.detectChanges();
expect(appComponent.tabHeader.scrollDistance).toBe(0);
});
it('should show ripples for pagination buttons', () => {
appComponent.addTabsForScrolling();
fixture.detectChanges();
expect(appComponent.tabHeader._showPaginationControls).toBe(true);
const buttonAfter = fixture.debugElement.query(
By.css('.mat-mdc-tab-header-pagination-after'),
);
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
.withContext('Expected no ripple to show up initially.')
.toBe(0);
dispatchFakeEvent(buttonAfter.nativeElement, 'mousedown');
dispatchFakeEvent(buttonAfter.nativeElement, 'mouseup');
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
.withContext('Expected one ripple to show up after mousedown')
.toBe(1);
});
it('should allow disabling ripples for pagination buttons', () => {
appComponent.addTabsForScrolling();
appComponent.disableRipple = true;
fixture.detectChanges();
expect(appComponent.tabHeader._showPaginationControls).toBe(true);
const buttonAfter = fixture.debugElement.query(
By.css('.mat-mdc-tab-header-pagination-after'),
);
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
.withContext('Expected no ripple to show up initially.')
.toBe(0);
dispatchFakeEvent(buttonAfter.nativeElement, 'mousedown');
dispatchFakeEvent(buttonAfter.nativeElement, 'mouseup');
expect(fixture.nativeElement.querySelectorAll('.mat-ripple-element').length)
.withContext('Expected no ripple to show up after mousedown')
.toBe(0);
});
it('should update the scroll distance if a tab is removed and no tabs are selected', fakeAsync(() => {
appComponent.selectedIndex = 0;
fixture.changeDetectorRef.markForCheck();
appComponent.addTabsForScrolling();
fixture.detectChanges();
// Focus the last tab so the header scrolls to the end.
appComponent.tabHeader.focusIndex = appComponent.tabs.length - 1;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
const {offsetLeft, offsetWidth} = appComponent.getSelectedLabel(
appComponent.tabHeader.focusIndex,
);
const viewLength = appComponent.getViewLength();
expect(appComponent.tabHeader.scrollDistance).toBe(offsetLeft + offsetWidth - viewLength);
// Remove the first two tabs which includes the selected tab.
appComponent.tabs = appComponent.tabs.slice(2);
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
tick();
expect(appComponent.tabHeader.scrollDistance).toBe(
appComponent.tabHeader._getMaxScrollDistance(),
);
}));
});
describe('rtl', () => {
beforeEach(() => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
appComponent = fixture.componentInstance;
appComponent.dir = 'rtl';
fixture.detectChanges();
});
it('should scroll to show the focused tab label', () => {
appComponent.addTabsForScrolling();
fixture.detectChanges();
expect(appComponent.tabHeader.scrollDistance).toBe(0);
// Focus on the last tab, expect this to be the maximum scroll distance.
appComponent.tabHeader.focusIndex = appComponent.tabs.length - 1;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
const {offsetLeft} = appComponent.getSelectedLabel(appComponent.tabHeader.focusIndex);
expect(offsetLeft).toBe(0);
// Focus on the first tab, expect this to be the maximum scroll distance.
appComponent.tabHeader.focusIndex = 0;
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
expect(appComponent.tabHeader.scrollDistance).toBe(0);
});
});
describe('scrolling when holding paginator', () => {
let nextButton: HTMLElement;
let prevButton: HTMLElement;
let header: MatTabHeader;
let headerElement: HTMLElement;
beforeEach(() => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
fixture.componentInstance.disableRipple = true;
fixture.detectChanges();
fixture.componentInstance.addTabsForScrolling(50);
fixture.detectChanges();
nextButton = fixture.nativeElement.querySelector('.mat-mdc-tab-header-pagination-after');
prevButton = fixture.nativeElement.querySelector('.mat-mdc-tab-header-pagination-before');
header = fixture.componentInstance.tabHeader;
headerElement = fixture.nativeElement.querySelector('.mat-mdc-tab-header');
});
it('should scroll towards the end while holding down the next button using a mouse', fakeAsync(() => {
assertNextButtonScrolling('mousedown', 'click');
}));
it('should scroll towards the start while holding down the prev button using a mouse', fakeAsync(() => {
assertPrevButtonScrolling('mousedown', 'click');
}));
it('should scroll towards the end while holding down the next button using touch', fakeAsync(() => {
assertNextButtonScrolling('touchstart', 'touchend');
}));
it('should scroll towards the start while holding down the prev button using touch', fakeAsync(() => {
assertPrevButtonScrolling('touchstart', 'touchend');
}));
it('should not scroll if the sequence is interrupted quickly', fakeAsync(() => {
expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0);
dispatchFakeEvent(nextButton, 'mousedown');
fixture.detectChanges();
tick(100);
dispatchFakeEvent(headerElement, 'mouseleave');
fixture.detectChanges();
tick(3000);
expect(header.scrollDistance)
.withContext('Expected not to have scrolled after a while.')
.toBe(0);
}));
it('should clear the timeouts on destroy', fakeAsync(() => {
dispatchFakeEvent(nextButton, 'mousedown');
fixture.detectChanges();
fixture.destroy();
// No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared.
}));
it('should clear the timeouts on click', fakeAsync(() => {
dispatchFakeEvent(nextButton, 'mousedown');
fixture.detectChanges();
dispatchFakeEvent(nextButton, 'click');
fixture.detectChanges();
// No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared.
}));
it('should clear the timeouts on touchend', fakeAsync(() => {
dispatchFakeEvent(nextButton, 'touchstart');
fixture.detectChanges();
dispatchFakeEvent(nextButton, 'touchend');
fixture.detectChanges();
// No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared.
}));
it('should clear the timeouts when reaching the end', fakeAsync(() => {
dispatchFakeEvent(nextButton, 'mousedown');
fixture.detectChanges();
// Simulate a very long timeout.
tick(60000);
// No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared.
}));
it('should clear the timeouts when reaching the start', fakeAsync(() => {
header.scrollDistance = Infinity;
fixture.detectChanges();
dispatchFakeEvent(prevButton, 'mousedown');
fixture.detectChanges();
// Simulate a very long timeout.
tick(60000);
// No need to assert. If fakeAsync doesn't throw, it means that the timers were cleared.
}));
it('should stop scrolling if the pointer leaves the header', fakeAsync(() => {
expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0);
dispatchFakeEvent(nextButton, 'mousedown');
fixture.detectChanges();
tick(300);
expect(header.scrollDistance)
.withContext('Expected not to scroll after short amount of time.')
.toBe(0);
tick(1000);
expect(header.scrollDistance)
.withContext('Expected to scroll after some time.')
.toBeGreaterThan(0);
let previousDistance = header.scrollDistance;
dispatchFakeEvent(headerElement, 'mouseleave');
fixture.detectChanges();
tick(100);
expect(header.scrollDistance).toBe(previousDistance);
}));
it('should not scroll when pressing the right mouse button', fakeAsync(() => {
expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0);
dispatchEvent(
nextButton,
createMouseEvent('mousedown', undefined, undefined, undefined, undefined, 2),
);
fixture.detectChanges();
tick(3000);
expect(header.scrollDistance)
.withContext('Expected not to have scrolled after a while.')
.toBe(0);
}));
/**
* Asserts that auto scrolling using the next button works.
* @param startEventName Name of the event that is supposed to start the scrolling.
* @param endEventName Name of the event that is supposed to end the scrolling.
*/
function assertNextButtonScrolling(startEventName: string, endEventName: string) {
expect(header.scrollDistance).withContext('Expected to start off not scrolled.').toBe(0);
dispatchFakeEvent(nextButton, startEventName);
fixture.detectChanges();
tick(300);
expect(header.scrollDistance)
.withContext('Expected not to scroll after short amount of time.')
.toBe(0);
tick(1000);
expect(header.scrollDistance)
.withContext('Expected to scroll after some time.')
.toBeGreaterThan(0);
let previousDistance = header.scrollDistance;
tick(100);
expect(header.scrollDistance)
.withContext('Expected to scroll again after some more time.')
.toBeGreaterThan(previousDistance);
dispatchFakeEvent(nextButton, endEventName);
flush();
}
/**
* Asserts that auto scrolling using the previous button works.
* @param startEventName Name of the event that is supposed to start the scrolling.
* @param endEventName Name of the event that is supposed to end the scrolling.
*/
function assertPrevButtonScrolling(startEventName: string, endEventName: string) {
header.scrollDistance = Infinity;
fixture.detectChanges();
let currentScroll = header.scrollDistance;
expect(currentScroll).withContext('Expected to start off scrolled.').toBeGreaterThan(0);
dispatchFakeEvent(prevButton, startEventName);
fixture.detectChanges();
tick(300);
expect(header.scrollDistance)
.withContext('Expected not to scroll after short amount of time.')
.toBe(currentScroll);
tick(1000);
expect(header.scrollDistance)
.withContext('Expected to scroll after some time.')
.toBeLessThan(currentScroll);
currentScroll = header.scrollDistance;
tick(100);
expect(header.scrollDistance)
.withContext('Expected to scroll again after some more time.')
.toBeLessThan(currentScroll);
dispatchFakeEvent(nextButton, endEventName);
flush();
}
});
describe('disabling pagination', () => {
it('should not show the pagination controls if pagination is disabled', () => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
appComponent = fixture.componentInstance;
appComponent.disablePagination = true;
fixture.detectChanges();
expect(appComponent.tabHeader._showPaginationControls).toBe(false);
// Add enough tabs that it will obviously exceed the width
appComponent.addTabsForScrolling();
fixture.detectChanges();
expect(appComponent.tabHeader._showPaginationControls).toBe(false);
});
it('should not change the scroll position if pagination is disabled', () => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
appComponent = fixture.componentInstance;
appComponent.disablePagination = true;
fixture.detectChanges();
appComponent.addTabsForScrolling();
fixture.detectChanges();
expect(appComponent.tabHeader.scrollDistance).toBe(0);
appComponent.tabHeader.focusIndex = appComponent.tabs.length - 1;
fixture.detectChanges();
expect(appComponent.tabHeader.scrollDistance).toBe(0);
appComponent.tabHeader.focusIndex = 0;
fixture.detectChanges();
expect(appComponent.tabHeader.scrollDistance).toBe(0);
});
});
it('should re-align the ink bar when the direction changes', fakeAsync(() => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
fixture.detectChanges();
const inkBar = fixture.componentInstance.tabHeader._inkBar;
spyOn(inkBar, 'alignToElement');
fixture.detectChanges();
fixture.componentInstance.dir = 'rtl';
fixture.changeDetectorRef.markForCheck();
fixture.detectChanges();
tick();
expect(inkBar.alignToElement).toHaveBeenCalled();
}));
it('should re-align the ink bar when the header is resized', fakeAsync(() => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
fixture.detectChanges();
const inkBar = fixture.componentInstance.tabHeader._inkBar;
spyOn(inkBar, 'alignToElement');
resizeEvents.next([]);
fixture.detectChanges();
tick(32);
expect(inkBar.alignToElement).toHaveBeenCalled();
discardPeriodicTasks();
}));
it('should update arrows when the header is resized', fakeAsync(() => {
fixture = TestBed.createComponent(SimpleTabHeaderApp);
const header = fixture.componentInstance.tabHeader;
spyOn(header, '_checkPaginationEnabled');
resizeEvents.next([]);
fixture.detectChanges();
flushMicrotasks();
expect(header._checkPaginationEnabled).toHaveBeenCalled();
discardPeriodicTasks();
}));
it('should update the pagination state if the content of the labels changes', () => {
const mutationCallbacks: Function[] = [];
spyOn(TestBed.inject(MutationObserverFactory), 'create').and.callFake(
(callback: Function) => {
mutationCallbacks.push(callback);
return {observe: () => {}, disconnect: () => {}} as any;
},
);
fixture = TestBed.createComponent(SimpleTabHeaderApp);
fixture.detectChanges();
const tabHeaderElement: HTMLElement =
fixture.nativeElement.querySelector('.mat-mdc-tab-header');
const labels = Array.from<HTMLElement>(
fixture.nativeElement.querySelectorAll('.label-content'),
);
const extraText = new Array(100).fill('w').join();
const enabledClass = 'mat-mdc-tab-header-pagination-controls-enabled';
expect(tabHeaderElement.classList).not.toContain(enabledClass);
labels.forEach(label => {
label.style.width = '';
label.textContent += extraText;
});
mutationCallbacks.forEach(callback => callback([{type: 'fake'}]));
fixture.detectChanges();
expect(tabHeaderElement.classList).toContain(enabledClass);
});
});
});
interface Tab {
label: string;
disabled?: boolean;
}
@Component({
template: `
<div [dir]="dir">
<mat-tab-header [selectedIndex]="selectedIndex" [disableRipple]="disableRipple"
(indexFocused)="focusedIndex = $event"
(selectFocusedIndex)="selectedIndex = $event"
[disablePagination]="disablePagination">
@for (tab of tabs; track tab; let i = $index) {
<div matTabLabelWrapper class="label-content" style="min-width: 30px; width: 30px"
[disabled]="!!tab.disabled"
(click)="selectedIndex = i">{{tab.label}}</div>
}
</mat-tab-header>
</div>
`,
styles: `
:host {
width: 130px;
}
`,
standalone: true,
imports: [Dir, MatTabHeader, MatTabLabelWrapper],
})
class SimpleTabHeaderApp {
disableRipple: boolean = false;
selectedIndex: number = 0;
focusedIndex: number;
disabledTabIndex = 1;
tabs: Tab[] = [{label: 'tab one'}, {label: 'tab one'}, {label: 'tab one'}, {label: 'tab one'}];
dir: Direction = 'ltr';
disablePagination: boolean;
@ViewChild(MatTabHeader, {static: true}) tabHeader: MatTabHeader;
private readonly _changeDetectorRef = inject(ChangeDetectorRef);
constructor() {
this.tabs[this.disabledTabIndex].disabled = true;
}
addTabsForScrolling(amount = 4) {
for (let i = 0; i < amount; i++) {
this.tabs.push({label: 'new'});
}
this._changeDetectorRef.markForCheck();
}
getViewLength() {
return this.tabHeader._tabListContainer.nativeElement.offsetWidth;
}
getSelectedLabel(index: number) {
return this.tabHeader._items.toArray()[this.tabHeader.focusIndex].elementRef.nativeElement;
}
}