From 1c89a26abe3491640b9a616c1872128f115de7d0 Mon Sep 17 00:00:00 2001 From: markusingvarsson Date: Sun, 7 May 2023 04:17:56 +0200 Subject: [PATCH] feat(material/tabs): ink bar animation synchronize The animation of the ink bar of the mat tab group and the mat tab nav bar is now synchronized with the animationDuration of the tab body. When the animation duration of the tab body was set to e.g. 0ms the ink bar still had an animation of 500ms. The following feat extends the animationDuration property to not only affect the tab body but also the ink bar of the tab group and the tab nav bar. Fixes angular#25068 --- src/material/tabs/_tabs-common.scss | 4 ++ src/material/tabs/tab-group.spec.ts | 9 +++++ src/material/tabs/tab-group.ts | 1 + .../tabs/tab-nav-bar/tab-nav-bar.spec.ts | 40 +++++++++++++++++++ src/material/tabs/tab-nav-bar/tab-nav-bar.ts | 14 ++++++- tools/public_api_guard/material/tabs.md | 5 ++- 6 files changed, 71 insertions(+), 2 deletions(-) diff --git a/src/material/tabs/_tabs-common.scss b/src/material/tabs/_tabs-common.scss index f22b4580eca2..15c36eb4326e 100644 --- a/src/material/tabs/_tabs-common.scss +++ b/src/material/tabs/_tabs-common.scss @@ -217,6 +217,10 @@ $mat-tab-animation-duration: 500ms !default; tokens-mat-tab-header.$prefix, tokens-mat-tab-header.get-unthemable-tokens()); } + .mdc-tab-indicator .mdc-tab-indicator__content { + transition-duration: var(--mat-tab-animation-duration, 250ms); + } + .mat-mdc-tab-header-pagination { @include vendor-prefixes.user-select(none); position: relative; diff --git a/src/material/tabs/tab-group.spec.ts b/src/material/tabs/tab-group.spec.ts index 371232caf531..0b092d81f4b7 100644 --- a/src/material/tabs/tab-group.spec.ts +++ b/src/material/tabs/tab-group.spec.ts @@ -999,6 +999,15 @@ describe('nested MatTabGroup with enabled animations', () => { tick(); }).not.toThrow(); })); + + it('should set appropiate css variable given a specified animationDuration', fakeAsync(() => { + let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); + fixture.detectChanges(); + tick(); + + const tabGroup = fixture.nativeElement.querySelector('.mat-mdc-tab-group'); + expect(tabGroup.style.getPropertyValue('--mat-tab-animation-duration')).toBe('500ms'); + })); }); describe('MatTabGroup with ink bar fit to content', () => { diff --git a/src/material/tabs/tab-group.ts b/src/material/tabs/tab-group.ts index 4157bf08ab14..fc3e775910a1 100644 --- a/src/material/tabs/tab-group.ts +++ b/src/material/tabs/tab-group.ts @@ -528,6 +528,7 @@ export abstract class _MatTabGroupBase '[class.mat-mdc-tab-group-dynamic-height]': 'dynamicHeight', '[class.mat-mdc-tab-group-inverted-header]': 'headerPosition === "below"', '[class.mat-mdc-tab-group-stretch-tabs]': 'stretchTabs', + '[style.--mat-tab-animation-duration]': 'animationDuration', }, }) export class MatTabGroup extends _MatTabGroupBase { 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 0dbc5dbb894c..d30951ef920f 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 @@ -489,6 +489,34 @@ describe('MatTabNavBar with a default config', () => { }); }); +describe('MatTabNavBar with enabled animations', () => { + beforeEach(fakeAsync(() => { + TestBed.configureTestingModule({ + imports: [MatTabsModule, BrowserAnimationsModule], + declarations: [TabsWithCustomAnimationDuration], + }); + + TestBed.compileComponents(); + })); + + it('should not throw when setting an animationDuration without units', fakeAsync(() => { + expect(() => { + let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); + fixture.detectChanges(); + tick(); + }).not.toThrow(); + })); + + it('should set appropiate css variable given a specified animationDuration', fakeAsync(() => { + let fixture = TestBed.createComponent(TabsWithCustomAnimationDuration); + fixture.detectChanges(); + tick(); + + const tabNavBar = fixture.nativeElement.querySelector('.mat-mdc-tab-nav-bar'); + expect(tabNavBar.style.getPropertyValue('--mat-tab-animation-duration')).toBe('500ms'); + })); +}); + @Component({ selector: 'test-app', template: ` @@ -545,3 +573,15 @@ class TabLinkWithNgIf { class TabBarWithInactiveTabsOnInit { tabs = [0, 1, 2]; } + +@Component({ + template: ` + + , + `, +}) +class TabsWithCustomAnimationDuration { + links = ['First', 'Second', 'Third']; +} 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 f48df15ea368..6ec9ca681b1e 100644 --- a/src/material/tabs/tab-nav-bar/tab-nav-bar.ts +++ b/src/material/tabs/tab-nav-bar/tab-nav-bar.ts @@ -45,7 +45,7 @@ import {Directionality} from '@angular/cdk/bidi'; import {ViewportRuler} from '@angular/cdk/scrolling'; import {Platform} from '@angular/cdk/platform'; import {MatInkBar, MatInkBarItem, mixinInkBarItem} from '../ink-bar'; -import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; +import {BooleanInput, coerceBooleanProperty, NumberInput} from '@angular/cdk/coercion'; import {BehaviorSubject, Subject} from 'rxjs'; import {startWith, takeUntil} from 'rxjs/operators'; import {SPACE} from '@angular/cdk/keycodes'; @@ -319,6 +319,7 @@ const _MatTabLinkBaseWithInkBarItem = mixinInkBarItem(_MatTabLinkBase); '[class.mat-accent]': 'color === "accent"', '[class.mat-warn]': 'color === "warn"', '[class._mat-animation-noopable]': '_animationMode === "NoopAnimations"', + '[style.--mat-tab-animation-duration]': 'animationDuration', }, encapsulation: ViewEncapsulation.None, // tslint:disable-next-line:validate-decorators @@ -346,6 +347,17 @@ export class MatTabNav extends _MatTabNavBase implements AfterContentInit, After } private _stretchTabs = true; + @Input() + get animationDuration(): string { + return this._animationDuration; + } + + set animationDuration(value: NumberInput) { + this._animationDuration = /^\d+$/.test(value + '') ? value + 'ms' : (value as string); + } + + private _animationDuration: string; + @ContentChildren(forwardRef(() => MatTabLink), {descendants: true}) _items: QueryList; @ViewChild('tabListContainer', {static: true}) _tabListContainer: ElementRef; @ViewChild('tabList', {static: true}) _tabList: ElementRef; diff --git a/tools/public_api_guard/material/tabs.md b/tools/public_api_guard/material/tabs.md index 527854e2cb43..f11a43b522c0 100644 --- a/tools/public_api_guard/material/tabs.md +++ b/tools/public_api_guard/material/tabs.md @@ -489,6 +489,9 @@ export class _MatTabLinkBase extends _MatTabLinkMixinBase implements AfterViewIn // @public export class MatTabNav extends _MatTabNavBase implements AfterContentInit, AfterViewInit { constructor(elementRef: ElementRef, dir: Directionality, ngZone: NgZone, changeDetectorRef: ChangeDetectorRef, viewportRuler: ViewportRuler, platform: Platform, animationMode?: string, defaultConfig?: MatTabsConfig); + // (undocumented) + get animationDuration(): string; + set animationDuration(value: NumberInput); get fitInkBarToContent(): boolean; set fitInkBarToContent(v: BooleanInput); // (undocumented) @@ -514,7 +517,7 @@ export class MatTabNav extends _MatTabNavBase implements AfterContentInit, After // (undocumented) _tabListInner: ElementRef; // (undocumented) - static ɵcmp: i0.ɵɵComponentDeclaration; + static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented) static ɵfac: i0.ɵɵFactoryDeclaration; }