From 06ed54c2510661cc08ba00fb001ea46ee1cd5ec2 Mon Sep 17 00:00:00 2001 From: Boyi Li Date: Wed, 15 Mar 2023 02:33:16 -0700 Subject: [PATCH] fix(material/tabs): update MatTab _scrollToLabel function to always display a label from its start (#26736) (#26737) (cherry picked from commit 946cc6743c4d0f46c943fca7fdb2ed07148535d9) --- src/material/legacy-tabs/tab-header.spec.ts | 25 +++++++++++++++------ src/material/tabs/paginated-tab-header.ts | 13 +++++------ src/material/tabs/tab-header.spec.ts | 25 +++++++++++++++------ 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/material/legacy-tabs/tab-header.spec.ts b/src/material/legacy-tabs/tab-header.spec.ts index c5380f792952..d46e42ebd1b1 100644 --- a/src/material/legacy-tabs/tab-header.spec.ts +++ b/src/material/legacy-tabs/tab-header.spec.ts @@ -272,9 +272,11 @@ describe('MatTabHeader', () => { // Focus on the last tab, expect this to be the maximum scroll distance. appComponent.tabHeader.focusIndex = appComponent.tabs.length - 1; fixture.detectChanges(); - expect(appComponent.tabHeader.scrollDistance).toBe( - appComponent.tabHeader._getMaxScrollDistance(), + 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; @@ -331,9 +333,11 @@ describe('MatTabHeader', () => { // 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(), + 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); @@ -362,9 +366,8 @@ describe('MatTabHeader', () => { // Focus on the last tab, expect this to be the maximum scroll distance. appComponent.tabHeader.focusIndex = appComponent.tabs.length - 1; fixture.detectChanges(); - expect(appComponent.tabHeader.scrollDistance).toBe( - appComponent.tabHeader._getMaxScrollDistance(), - ); + 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; @@ -757,4 +760,12 @@ class SimpleTabHeaderApp { this.tabs.push({label: 'new'}); } } + + getViewLength() { + return this.tabHeader._tabListContainer.nativeElement.offsetWidth; + } + + getSelectedLabel(index: number) { + return this.tabHeader._items.toArray()[this.tabHeader.focusIndex].elementRef.nativeElement; + } } diff --git a/src/material/tabs/paginated-tab-header.ts b/src/material/tabs/paginated-tab-header.ts index 266190b47d82..934d3668028b 100644 --- a/src/material/tabs/paginated-tab-header.ts +++ b/src/material/tabs/paginated-tab-header.ts @@ -57,12 +57,6 @@ const passiveEventListenerOptions = normalizePassiveListenerOptions({ */ export type ScrollDirection = 'after' | 'before'; -/** - * The distance in pixels that will be overshot when scrolling a tab label into view. This helps - * provide a small affordance to the label next to it. - */ -const EXAGGERATED_OVERSCROLL = 60; - /** * Amount of milliseconds to wait before starting to scroll the header automatically. * Set a little conservatively in order to handle fake events dispatched on touch devices. @@ -524,10 +518,13 @@ export abstract class MatPaginatedTabHeader if (labelBeforePos < beforeVisiblePos) { // Scroll header to move label to the before direction - this.scrollDistance -= beforeVisiblePos - labelBeforePos + EXAGGERATED_OVERSCROLL; + this.scrollDistance -= beforeVisiblePos - labelBeforePos; } else if (labelAfterPos > afterVisiblePos) { // Scroll header to move label to the after direction - this.scrollDistance += labelAfterPos - afterVisiblePos + EXAGGERATED_OVERSCROLL; + this.scrollDistance += Math.min( + labelAfterPos - afterVisiblePos, + labelBeforePos - beforeVisiblePos, + ); } } diff --git a/src/material/tabs/tab-header.spec.ts b/src/material/tabs/tab-header.spec.ts index e58f96bf9369..fda6f442ab51 100644 --- a/src/material/tabs/tab-header.spec.ts +++ b/src/material/tabs/tab-header.spec.ts @@ -266,9 +266,11 @@ describe('MDC-based MatTabHeader', () => { // Focus on the last tab, expect this to be the maximum scroll distance. appComponent.tabHeader.focusIndex = appComponent.tabs.length - 1; fixture.detectChanges(); - expect(appComponent.tabHeader.scrollDistance).toBe( - appComponent.tabHeader._getMaxScrollDistance(), + 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; @@ -329,9 +331,11 @@ describe('MDC-based MatTabHeader', () => { // 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(), + 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); @@ -360,9 +364,8 @@ describe('MDC-based MatTabHeader', () => { // Focus on the last tab, expect this to be the maximum scroll distance. appComponent.tabHeader.focusIndex = appComponent.tabs.length - 1; fixture.detectChanges(); - expect(appComponent.tabHeader.scrollDistance).toBe( - appComponent.tabHeader._getMaxScrollDistance(), - ); + 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; @@ -757,4 +760,12 @@ class SimpleTabHeaderApp { this.tabs.push({label: 'new'}); } } + + getViewLength() { + return this.tabHeader._tabListContainer.nativeElement.offsetWidth; + } + + getSelectedLabel(index: number) { + return this.tabHeader._items.toArray()[this.tabHeader.focusIndex].elementRef.nativeElement; + } }