From 62d48530996bc3515c5dbc4a450986ef91b1259f 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 --- .../tab-group-animations-example.html | 2 +- src/material/tabs/_tabs-common.scss | 20 +++++++--- src/material/tabs/tab-group.spec.ts | 9 +++++ src/material/tabs/tab-group.ts | 27 +++++++------ .../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 ++- 7 files changed, 95 insertions(+), 22 deletions(-) diff --git a/src/components-examples/material/tabs/tab-group-animations/tab-group-animations-example.html b/src/components-examples/material/tabs/tab-group-animations/tab-group-animations-example.html index 110f1973d653..3f9f87b8049f 100644 --- a/src/components-examples/material/tabs/tab-group-animations/tab-group-animations-example.html +++ b/src/components-examples/material/tabs/tab-group-animations/tab-group-animations-example.html @@ -9,7 +9,7 @@

No animation

Very slow animation

- + Content 1 Content 2 Content 3 diff --git a/src/material/tabs/_tabs-common.scss b/src/material/tabs/_tabs-common.scss index f22b4580eca2..098918e5f697 100644 --- a/src/material/tabs/_tabs-common.scss +++ b/src/material/tabs/_tabs-common.scss @@ -13,7 +13,6 @@ @use '../core/tokens/token-utils'; @use 'sass:map'; - $mat-tab-animation-duration: 500ms !default; // Combines the various structural styles we need for the tab group and tab nav bar. @@ -214,7 +213,13 @@ $mat-tab-animation-duration: 500ms !default; @include mdc-tab-indicator-theme.theme(tokens-mdc-tab-indicator.get-unthemable-tokens()); @include mdc-tab-theme.secondary-navigation-tab-theme(tokens-mdc-tab.get-unthemable-tokens()); @include token-utils.create-token-values( - tokens-mat-tab-header.$prefix, tokens-mat-tab-header.get-unthemable-tokens()); + 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 { @@ -309,7 +314,7 @@ $mat-tab-animation-duration: 500ms !default; // The `span` is in the selector in order to increase the specificity, ensuring // that it's always higher than the selector that declares the transition. ._mat-animation-noopable { - span.mdc-tab-indicator__content, + --mat-tab-animation-duration: 0; span.mdc-tab__text-label { transition: none; } @@ -354,7 +359,8 @@ $mat-tab-animation-duration: 500ms !default; ) { // Note that these selectors target direct descendants so // that the styles don't apply to any nested tab groups. - > #{$header-selector}, > .mat-mdc-tab-header-pagination { + > #{$header-selector}, + > .mat-mdc-tab-header-pagination { // Set background color for the tab group @include token-utils.create-token-slot(background-color, background-color); } @@ -386,13 +392,15 @@ $mat-tab-animation-duration: 500ms !default; } } - > #{$header-selector}, > .mat-mdc-tab-header-pagination { + > #{$header-selector}, + > .mat-mdc-tab-header-pagination { .mat-mdc-tab-header-pagination-chevron, .mat-mdc-focus-indicator::before { @include token-utils.create-token-slot(border-color, foreground-color); } - .mat-ripple-element, .mdc-tab__ripple::before { + .mat-ripple-element, + .mdc-tab__ripple::before { @include token-utils.create-token-slot(background-color, foreground-color); } 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..3a543904249d 100644 --- a/src/material/tabs/tab-group.ts +++ b/src/material/tabs/tab-group.ts @@ -6,6 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ +import {FocusOrigin} from '@angular/cdk/a11y'; +import { + BooleanInput, + NumberInput, + coerceBooleanProperty, + coerceNumberProperty, +} from '@angular/cdk/coercion'; import { AfterContentChecked, AfterContentInit, @@ -25,26 +32,19 @@ import { ViewChild, ViewEncapsulation, } from '@angular/core'; -import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; -import {MAT_TAB_GROUP, MatTab} from './tab'; -import {MatTabHeader} from './tab-header'; -import { - BooleanInput, - coerceBooleanProperty, - coerceNumberProperty, - NumberInput, -} from '@angular/cdk/coercion'; import { CanColor, CanDisableRipple, + ThemePalette, mixinColor, mixinDisableRipple, - ThemePalette, } from '@angular/material/core'; -import {merge, Subscription} from 'rxjs'; -import {MAT_TABS_CONFIG, MatTabsConfig} from './tab-config'; +import {ANIMATION_MODULE_TYPE} from '@angular/platform-browser/animations'; +import {Subscription, merge} from 'rxjs'; import {startWith} from 'rxjs/operators'; -import {FocusOrigin} from '@angular/cdk/a11y'; +import {MAT_TAB_GROUP, MatTab} from './tab'; +import {MAT_TABS_CONFIG, MatTabsConfig} from './tab-config'; +import {MatTabHeader} from './tab-header'; /** Used to generate unique ID's for each tab component */ let nextId = 0; @@ -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; }