diff --git a/src/material-experimental/mdc-tabs/tab-header.spec.ts b/src/material-experimental/mdc-tabs/tab-header.spec.ts index 5922f5c8c679..7f710c019d1e 100644 --- a/src/material-experimental/mdc-tabs/tab-header.spec.ts +++ b/src/material-experimental/mdc-tabs/tab-header.spec.ts @@ -318,6 +318,28 @@ describe('MDC-based MatTabHeader', () => { .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; + appComponent.addTabsForScrolling(); + fixture.detectChanges(); + + // Focus the last tab so the header scrolls to the end. + appComponent.tabHeader.focusIndex = appComponent.tabs.length - 1; + fixture.detectChanges(); + expect(appComponent.tabHeader.scrollDistance).toBe( + appComponent.tabHeader._getMaxScrollDistance(), + ); + + // Remove the first two tabs which includes the selected tab. + appComponent.tabs = appComponent.tabs.slice(2); + fixture.detectChanges(); + tick(); + + expect(appComponent.tabHeader.scrollDistance).toBe( + appComponent.tabHeader._getMaxScrollDistance(), + ); + })); }); describe('rtl', () => { diff --git a/src/material/tabs/paginated-tab-header.ts b/src/material/tabs/paginated-tab-header.ts index 184e4162e7aa..fdbf2f4d332e 100644 --- a/src/material/tabs/paginated-tab-header.ts +++ b/src/material/tabs/paginated-tab-header.ts @@ -211,7 +211,16 @@ export abstract class MatPaginatedTabHeader // We need to defer this to give the browser some time to recalculate // the element dimensions. The call has to be wrapped in `NgZone.run`, // because the viewport change handler runs outside of Angular. - this._ngZone.run(() => Promise.resolve().then(realign)); + this._ngZone.run(() => { + Promise.resolve().then(() => { + // Clamp the scroll distance, because it can change with the number of tabs. + this._scrollDistance = Math.max( + 0, + Math.min(this._getMaxScrollDistance(), this._scrollDistance), + ); + realign(); + }); + }); this._keyManager.withHorizontalOrientation(this._getLayoutDirection()); }); diff --git a/src/material/tabs/tab-header.spec.ts b/src/material/tabs/tab-header.spec.ts index 41749fd0adee..b891cc23ce81 100644 --- a/src/material/tabs/tab-header.spec.ts +++ b/src/material/tabs/tab-header.spec.ts @@ -315,6 +315,28 @@ describe('MatTabHeader', () => { .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; + appComponent.addTabsForScrolling(); + fixture.detectChanges(); + + // Focus the last tab so the header scrolls to the end. + appComponent.tabHeader.focusIndex = appComponent.tabs.length - 1; + fixture.detectChanges(); + expect(appComponent.tabHeader.scrollDistance).toBe( + appComponent.tabHeader._getMaxScrollDistance(), + ); + + // Remove the first two tabs which includes the selected tab. + appComponent.tabs = appComponent.tabs.slice(2); + fixture.detectChanges(); + tick(); + + expect(appComponent.tabHeader.scrollDistance).toBe( + appComponent.tabHeader._getMaxScrollDistance(), + ); + })); }); describe('rtl', () => {