diff --git a/goldens/material/tabs/index.api.md b/goldens/material/tabs/index.api.md index a9ccbc6a6a56..98255e0824cd 100644 --- a/goldens/material/tabs/index.api.md +++ b/goldens/material/tabs/index.api.md @@ -243,15 +243,17 @@ export class MatTabGroup implements AfterViewInit, AfterContentInit, AfterConten alignTabs: string | null; _allTabs: QueryList; readonly animationDone: EventEmitter; - get animationDuration(): string; - set animationDuration(value: string | number); - // (undocumented) - protected _animationsDisabled(): boolean; + get animationDuration(): MatTabGroupAnimationDuration; + set animationDuration(value: MatTabGroupAnimationDuration); ariaLabel: string; ariaLabelledby: string; // @deprecated get backgroundColor(): ThemePalette; set backgroundColor(value: ThemePalette); + // (undocumented) + protected _bodyAnimationDuration: string; + // (undocumented) + protected _bodyAnimationsDisabled(): boolean; protected _bodyCentered(isCenter: boolean): void; color: ThemePalette; get contentTabIndex(): number | null; @@ -271,6 +273,8 @@ export class MatTabGroup implements AfterViewInit, AfterContentInit, AfterConten _getTabIndex(index: number): number; _getTabLabelId(tab: MatTab, index: number): string; _handleClick(tab: MatTab, tabHeader: MatTabGroupBaseHeader, index: number): void; + // (undocumented) + protected _headerAnimationDuration: string; headerPosition: MatTabHeaderPosition; protected _isServer: boolean; // (undocumented) diff --git a/src/material/tabs/_tabs-common.scss b/src/material/tabs/_tabs-common.scss index 7523f6010dfd..35a7ef99d720 100644 --- a/src/material/tabs/_tabs-common.scss +++ b/src/material/tabs/_tabs-common.scss @@ -247,7 +247,7 @@ $mat-tab-animation-duration: 500ms !default; } .mdc-tab-indicator .mdc-tab-indicator__content { - transition-duration: var(--mat-tab-animation-duration, 250ms); + transition-duration: var(--mat-tab-header-animation-duration, 250ms); } .mat-mdc-tab-header-pagination { diff --git a/src/material/tabs/tab-body.scss b/src/material/tabs/tab-body.scss index 89db99c45976..dd3dcf078a5a 100644 --- a/src/material/tabs/tab-body.scss +++ b/src/material/tabs/tab-body.scss @@ -51,7 +51,7 @@ .mat-tab-body-content-can-animate { // Note: there's a 1ms delay so that transition events // still fire even if the duration is set to zero. - transition: transform var(--mat-tab-animation-duration) 1ms cubic-bezier(0.35, 0, 0.25, 1); + transition: transform var(--mat-tab-body-animation-duration) 1ms cubic-bezier(0.35, 0, 0.25, 1); .mat-mdc-tab-body-wrapper._mat-animation-noopable & { transition: none; diff --git a/src/material/tabs/tab-group.html b/src/material/tabs/tab-group.html index 2db906b31bfb..99f49f0cf1cc 100644 --- a/src/material/tabs/tab-group.html +++ b/src/material/tabs/tab-group.html @@ -65,7 +65,7 @@
@for (tab of _tabs; track tab;) { { }); }); + describe('animation duration', () => { + it('should set the body and header animation duration when value is a string', () => { + const fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); + fixture.detectChanges(); + + const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group'); + expect(tabGroup.style.getPropertyValue('--mat-tab-body-animation-duration')).toBe('500ms'); + expect(tabGroup.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('500ms'); + }); + + it('should set the body and header animation duration when value is an object', () => { + const fixture = TestBed.createComponent(TabsWithObjectAnimationDuration); + fixture.detectChanges(); + + const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group'); + expect(tabGroup.style.getPropertyValue('--mat-tab-body-animation-duration')).toBe('100ms'); + expect(tabGroup.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('200ms'); + }); + }); + describe('aria labelling', () => { let fixture: ComponentFixture; let tab: HTMLElement; @@ -1062,7 +1082,8 @@ describe('nested MatTabGroup with enabled animations', () => { tick(); const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group'); - expect(tabGroup.style.getPropertyValue('--mat-tab-animation-duration')).toBe('500ms'); + expect(tabGroup.style.getPropertyValue('--mat-tab-body-animation-duration')).toBe('500ms'); + expect(tabGroup.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('500ms'); })); }); @@ -1473,6 +1494,17 @@ class TabGroupWithIsActiveBinding {} }) class TabsWithCustomAnimationDuration {} +@Component({ + template: ` + + Tab one content + Tab two content + + `, + imports: [MatTabsModule], +}) +class TabsWithObjectAnimationDuration {} + @Component({ template: ` diff --git a/src/material/tabs/tab-group.ts b/src/material/tabs/tab-group.ts index cafdd6a22af4..0fe5c529e8d6 100644 --- a/src/material/tabs/tab-group.ts +++ b/src/material/tabs/tab-group.ts @@ -50,6 +50,12 @@ export interface MatTabGroupBaseHeader { /** Possible positions for the tab header. */ export type MatTabHeaderPosition = 'above' | 'below'; +/** Possible values for the animation duration of a tab group. */ +export type MatTabGroupAnimationDuration = + | string + | number + | {body: string | number; header: string | number}; + /** Boolean constant that determines whether the tab group supports the `backgroundColor` input */ const ENABLE_BACKGROUND_INPUT = true; @@ -79,7 +85,8 @@ const ENABLE_BACKGROUND_INPUT = true; '[class.mat-mdc-tab-group-inverted-header]': 'headerPosition === "below"', '[class.mat-mdc-tab-group-stretch-tabs]': 'stretchTabs', '[attr.mat-align-tabs]': 'alignTabs', - '[style.--mat-tab-animation-duration]': 'animationDuration', + '[style.--mat-tab-body-animation-duration]': '_bodyAnimationDuration', + '[style.--mat-tab-header-animation-duration]': '_headerAnimationDuration', }, imports: [ MatTabHeader, @@ -100,6 +107,8 @@ export class MatTabGroup private _tabLabelSubscription = Subscription.EMPTY; private _tabBodySubscription = Subscription.EMPTY; private _diAnimationsDisabled = _animationsDisabled(); + protected _bodyAnimationDuration!: string; + protected _headerAnimationDuration!: string; /** * All tabs inside the tab group. This includes tabs that belong to groups that are nested @@ -170,14 +179,20 @@ export class MatTabGroup /** Duration for the tab animation. Will be normalized to milliseconds if no units are set. */ @Input() - get animationDuration(): string { + get animationDuration(): MatTabGroupAnimationDuration { return this._animationDuration; } - set animationDuration(value: string | number) { - const stringValue = value + ''; - this._animationDuration = /^\d+$/.test(stringValue) ? value + 'ms' : stringValue; + set animationDuration(value: MatTabGroupAnimationDuration) { + this._animationDuration = value; + + if (value && typeof value === 'object') { + this._bodyAnimationDuration = normalizeDuration(value.body); + this._headerAnimationDuration = normalizeDuration(value.header); + } else { + this._headerAnimationDuration = this._bodyAnimationDuration = normalizeDuration(value); + } } - private _animationDuration!: string; + private _animationDuration!: MatTabGroupAnimationDuration; /** * `tabindex` to be set on the inner element that wraps the tab content. Can be used for improved @@ -577,11 +592,11 @@ export class MatTabGroup } } - protected _animationsDisabled(): boolean { + protected _bodyAnimationsDisabled(): boolean { return ( this._diAnimationsDisabled || - this.animationDuration === '0' || - this.animationDuration === '0ms' + this._bodyAnimationDuration === '0' || + this._bodyAnimationDuration === '0ms' ); } } @@ -593,3 +608,9 @@ export class MatTabChangeEvent { /** Reference to the currently-selected tab. */ tab!: MatTab; } + +/** Normalizes an animation duration value. */ +function normalizeDuration(value: string | number): string { + const stringValue = value + ''; + return /^\d+$/.test(stringValue) ? value + 'ms' : stringValue; +} diff --git a/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts b/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts index 5eba684bff93..be19c5c6e363 100644 --- a/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts +++ b/src/material/tabs/tab-nav-bar/tab-nav-bar.spec.ts @@ -530,7 +530,7 @@ describe('MatTabNavBar with enabled animations', () => { tick(); const tabNavBar = fixture.nativeElement.querySelector('.mat-mdc-tab-nav-bar'); - expect(tabNavBar.style.getPropertyValue('--mat-tab-animation-duration')).toBe('500ms'); + expect(tabNavBar.style.getPropertyValue('--mat-tab-header-animation-duration')).toBe('500ms'); })); }); diff --git a/src/material/tabs/tab-nav-bar/tab-nav-bar.ts b/src/material/tabs/tab-nav-bar/tab-nav-bar.ts index 45287e6f06ba..4809340113eb 100644 --- a/src/material/tabs/tab-nav-bar/tab-nav-bar.ts +++ b/src/material/tabs/tab-nav-bar/tab-nav-bar.ts @@ -64,7 +64,7 @@ import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; '[class.mat-accent]': 'color === "accent"', '[class.mat-warn]': 'color === "warn"', '[class._mat-animation-noopable]': '_animationsDisabled', - '[style.--mat-tab-animation-duration]': 'animationDuration', + '[style.--mat-tab-header-animation-duration]': 'animationDuration', }, encapsulation: ViewEncapsulation.None, // tslint:disable-next-line:validate-decorators