From 58db2ef169eedab2335536cb11e254937bfa58d6 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 19 Mar 2025 13:29:25 +0100 Subject: [PATCH 1/2] refactor(multiple): consolidate logic for disabling animations Adds a common function we can use to determine whether animations are disabled globally. This reduces duplication and makes it easier to change the logic. --- goldens/material/checkbox/index.api.md | 2 - goldens/material/core/index.api.md | 5 ++- goldens/material/progress-bar/index.api.md | 2 - goldens/material/sort/index.api.md | 2 +- goldens/material/tabs/index.api.md | 4 +- src/dev-app/checkbox/checkbox-demo.html | 40 ------------------- src/dev-app/checkbox/checkbox-demo.ts | 9 +---- src/material/autocomplete/autocomplete.ts | 5 +-- src/material/badge/badge.ts | 9 ++--- .../bottom-sheet/bottom-sheet-container.ts | 5 +-- src/material/button-toggle/button-toggle.ts | 7 ++-- src/material/button/button-base.ts | 7 ++-- src/material/checkbox/checkbox.ts | 14 ++++--- src/material/chips/chip.ts | 6 +-- src/material/core/animation/animation.ts | 10 +++++ src/material/core/private/ripple-loader.ts | 20 +++------- src/material/core/ripple/ripple.ts | 6 +-- .../pseudo-checkbox/pseudo-checkbox.ts | 14 ++----- src/material/datepicker/datepicker-base.ts | 6 +-- src/material/dialog/dialog-container.ts | 7 +--- src/material/expansion/expansion-panel.ts | 5 +-- src/material/form-field/form-field.ts | 7 +--- src/material/list/list-base.ts | 7 +--- src/material/menu/menu.ts | 5 +-- src/material/progress-bar/progress-bar.ts | 8 +--- .../progress-spinner/progress-spinner.ts | 7 +--- src/material/radio/radio.ts | 13 +++--- src/material/select/select.ts | 5 +-- src/material/sidenav/drawer.ts | 6 +-- src/material/slide-toggle/slide-toggle.ts | 12 +++--- src/material/slider/slider.ts | 6 +-- src/material/snack-bar/snack-bar-container.ts | 5 +-- src/material/sort/sort-header.html | 2 +- src/material/sort/sort-header.ts | 5 +-- src/material/stepper/stepper.ts | 9 ++--- src/material/tabs/paginated-tab-header.ts | 4 +- src/material/tabs/tab-body.ts | 6 +-- src/material/tabs/tab-group.html | 2 +- src/material/tabs/tab-group.ts | 5 +-- src/material/tabs/tab-header.html | 2 +- src/material/tabs/tab-nav-bar/tab-nav-bar.ts | 22 ++-------- src/material/timepicker/timepicker.ts | 5 +-- src/material/tooltip/tooltip.ts | 9 ++--- 43 files changed, 121 insertions(+), 216 deletions(-) diff --git a/goldens/material/checkbox/index.api.md b/goldens/material/checkbox/index.api.md index c491e5f99703..06ca6f619947 100644 --- a/goldens/material/checkbox/index.api.md +++ b/goldens/material/checkbox/index.api.md @@ -35,8 +35,6 @@ export class MatCheckbox implements AfterViewInit, OnChanges, ControlValueAccess indeterminateToChecked: string; indeterminateToUnchecked: string; }; - // (undocumented) - _animationMode?: "NoopAnimations" | "BrowserAnimations" | null | undefined; ariaControls: string; ariaDescribedby: string; ariaExpanded: boolean; diff --git a/goldens/material/core/index.api.md b/goldens/material/core/index.api.md index 8fb3b531e825..a94fe4237e4f 100644 --- a/goldens/material/core/index.api.md +++ b/goldens/material/core/index.api.md @@ -51,6 +51,9 @@ export class AnimationDurations { static EXITING: string; } +// @public +export function _animationsDisabled(): boolean; + // @public export function _countGroupLabelsBeforeOption(optionIndex: number, options: QueryList, optionGroups: QueryList): number; @@ -318,7 +321,7 @@ export class MatOptionSelectionChange { export class MatPseudoCheckbox { constructor(...args: unknown[]); // (undocumented) - _animationMode?: "NoopAnimations" | "BrowserAnimations" | null | undefined; + _animationsDisabled: boolean; appearance: 'minimal' | 'full'; disabled: boolean; state: MatPseudoCheckboxState; diff --git a/goldens/material/progress-bar/index.api.md b/goldens/material/progress-bar/index.api.md index 264e59c4dafb..7fe9eb4907cd 100644 --- a/goldens/material/progress-bar/index.api.md +++ b/goldens/material/progress-bar/index.api.md @@ -25,8 +25,6 @@ export function MAT_PROGRESS_BAR_LOCATION_FACTORY(): MatProgressBarLocation; export class MatProgressBar implements AfterViewInit, OnDestroy { constructor(...args: unknown[]); readonly animationEnd: EventEmitter; - // (undocumented) - _animationMode?: "NoopAnimations" | "BrowserAnimations" | null | undefined; get bufferValue(): number; set bufferValue(v: number); get color(): string | null | undefined; diff --git a/goldens/material/sort/index.api.md b/goldens/material/sort/index.api.md index 208bf37ca6a8..46cae4a290ba 100644 --- a/goldens/material/sort/index.api.md +++ b/goldens/material/sort/index.api.md @@ -100,7 +100,7 @@ export interface MatSortDefaultOptions { export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewInit { constructor(...args: unknown[]); // (undocumented) - protected _animationModule: "NoopAnimations" | "BrowserAnimations" | null; + protected _animationsDisabled: boolean; arrowPosition: SortHeaderArrowPosition; // (undocumented) _columnDef: MatSortHeaderColumnDef | null; diff --git a/goldens/material/tabs/index.api.md b/goldens/material/tabs/index.api.md index f8856853384f..512b07eb4b8a 100644 --- a/goldens/material/tabs/index.api.md +++ b/goldens/material/tabs/index.api.md @@ -70,7 +70,7 @@ export abstract class MatPaginatedTabHeader implements AfterContentChecked, Afte constructor(...args: unknown[]); _alignInkBarToSelectedTab(): void; // (undocumented) - _animationMode: "NoopAnimations" | "BrowserAnimations" | null; + _animationsDisabled: boolean; // (undocumented) protected _changeDetectorRef: ChangeDetectorRef; _checkPaginationEnabled(): void; @@ -246,7 +246,7 @@ export class MatTabGroup implements AfterViewInit, AfterContentInit, AfterConten get animationDuration(): string; set animationDuration(value: string | number); // (undocumented) - _animationMode: "NoopAnimations" | "BrowserAnimations" | null; + _animationsDisabled: boolean; ariaLabel: string; ariaLabelledby: string; // @deprecated diff --git a/src/dev-app/checkbox/checkbox-demo.html b/src/dev-app/checkbox/checkbox-demo.html index 1c65e67460bc..759955b74165 100644 --- a/src/dev-app/checkbox/checkbox-demo.html +++ b/src/dev-app/checkbox/checkbox-demo.html @@ -255,46 +255,6 @@
Click action: noop
} -
-
No animations
- - @if (!demoHideLabel) { - Checkbox w/ [checked] & (change) - } - - - @if (!demoHideLabel) { - Checkbox w/ [(ngModel)] - } - -

diff --git a/src/dev-app/checkbox/checkbox-demo.ts b/src/dev-app/checkbox/checkbox-demo.ts index 057ef0a35967..e6eff0bbf99d 100644 --- a/src/dev-app/checkbox/checkbox-demo.ts +++ b/src/dev-app/checkbox/checkbox-demo.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ANIMATION_MODULE_TYPE, ChangeDetectionStrategy, Component, Directive} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Directive} from '@angular/core'; import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {MAT_CHECKBOX_DEFAULT_OPTIONS, MatCheckboxModule} from '@angular/material/checkbox'; import {MatPseudoCheckboxModule, ThemePalette} from '@angular/material/core'; @@ -32,12 +32,6 @@ export class ClickActionNoop {} }) export class ClickActionCheck {} -@Directive({ - selector: '[animationsNoop]', - providers: [{provide: ANIMATION_MODULE_TYPE, useValue: 'NoopAnimations'}], -}) -export class AnimationsNoop {} - @Component({ selector: 'mat-checkbox-demo-nested-checklist', styles: ` @@ -107,7 +101,6 @@ export class MatCheckboxDemoNestedChecklist { MatCheckboxDemoNestedChecklist, ClickActionNoop, ClickActionCheck, - AnimationsNoop, MatTooltip, ], changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/material/autocomplete/autocomplete.ts b/src/material/autocomplete/autocomplete.ts index 89993cdf0003..475023b30661 100644 --- a/src/material/autocomplete/autocomplete.ts +++ b/src/material/autocomplete/autocomplete.ts @@ -7,7 +7,6 @@ */ import { - ANIMATION_MODULE_TYPE, AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, @@ -27,6 +26,7 @@ import { inject, } from '@angular/core'; import { + _animationsDisabled, MAT_OPTGROUP, MAT_OPTION_PARENT_COMPONENT, MatOptgroup, @@ -124,8 +124,7 @@ export class MatAutocomplete implements AfterContentInit, OnDestroy { private _changeDetectorRef = inject(ChangeDetectorRef); private _elementRef = inject>(ElementRef); protected _defaults = inject(MAT_AUTOCOMPLETE_DEFAULT_OPTIONS); - protected _animationsDisabled = - inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations'; + protected _animationsDisabled = _animationsDisabled(); private _activeOptionChanges = Subscription.EMPTY; /** Manages active item in option list based on key events. */ diff --git a/src/material/badge/badge.ts b/src/material/badge/badge.ts index a5aef2248693..2cbb6e18da03 100644 --- a/src/material/badge/badge.ts +++ b/src/material/badge/badge.ts @@ -21,9 +21,8 @@ import { OnInit, Renderer2, ViewEncapsulation, - ANIMATION_MODULE_TYPE, } from '@angular/core'; -import {ThemePalette} from '../core'; +import {_animationsDisabled, ThemePalette} from '../core'; import {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '@angular/cdk/private'; /** Allowed position options for matBadgePosition */ @@ -76,7 +75,7 @@ export class MatBadge implements OnInit, OnDestroy { private _elementRef = inject>(ElementRef); private _ariaDescriber = inject(AriaDescriber); private _renderer = inject(Renderer2); - private _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); + private _animationsDisabled = _animationsDisabled(); private _idGenerator = inject(_IdGenerator); /** @@ -240,14 +239,14 @@ export class MatBadge implements OnInit, OnDestroy { badgeElement.setAttribute('aria-hidden', 'true'); badgeElement.classList.add(BADGE_CONTENT_CLASS); - if (this._animationMode === 'NoopAnimations') { + if (this._animationsDisabled) { badgeElement.classList.add('_mat-animation-noopable'); } this._elementRef.nativeElement.appendChild(badgeElement); // animate in after insertion - if (typeof requestAnimationFrame === 'function' && this._animationMode !== 'NoopAnimations') { + if (typeof requestAnimationFrame === 'function' && !this._animationsDisabled) { this._ngZone.runOutsideAngular(() => { requestAnimationFrame(() => { badgeElement.classList.add(activeClass); diff --git a/src/material/bottom-sheet/bottom-sheet-container.ts b/src/material/bottom-sheet/bottom-sheet-container.ts index d7beb99f7d9e..7c3cdb0c014c 100644 --- a/src/material/bottom-sheet/bottom-sheet-container.ts +++ b/src/material/bottom-sheet/bottom-sheet-container.ts @@ -9,7 +9,6 @@ import {CdkDialogContainer} from '@angular/cdk/dialog'; import {BreakpointObserver, Breakpoints} from '@angular/cdk/layout'; import { - ANIMATION_MODULE_TYPE, ChangeDetectionStrategy, Component, EventEmitter, @@ -19,6 +18,7 @@ import { } from '@angular/core'; import {Subscription} from 'rxjs'; import {CdkPortalOutlet} from '@angular/cdk/portal'; +import {_animationsDisabled} from '../core'; const ENTER_ANIMATION = '_mat-bottom-sheet-enter'; const EXIT_ANIMATION = '_mat-bottom-sheet-exit'; @@ -54,8 +54,7 @@ const EXIT_ANIMATION = '_mat-bottom-sheet-exit'; }) export class MatBottomSheetContainer extends CdkDialogContainer implements OnDestroy { private _breakpointSubscription: Subscription; - protected _animationsDisabled = - inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations'; + protected _animationsDisabled = _animationsDisabled(); /** The state of the bottom sheet animations. */ _animationState: 'void' | 'visible' | 'hidden' = 'void'; diff --git a/src/material/button-toggle/button-toggle.ts b/src/material/button-toggle/button-toggle.ts index 3f5d0ac30f75..609007629293 100644 --- a/src/material/button-toggle/button-toggle.ts +++ b/src/material/button-toggle/button-toggle.ts @@ -14,7 +14,6 @@ import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; import { AfterContentInit, AfterViewInit, - ANIMATION_MODULE_TYPE, booleanAttribute, ChangeDetectionStrategy, ChangeDetectorRef, @@ -36,7 +35,7 @@ import { ViewEncapsulation, } from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; -import {_StructuralStylesLoader, MatPseudoCheckbox, MatRipple} from '../core'; +import {_animationsDisabled, _StructuralStylesLoader, MatPseudoCheckbox, MatRipple} from '../core'; /** * @deprecated No longer used. @@ -577,7 +576,7 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy { private _elementRef = inject>(ElementRef); private _focusMonitor = inject(FocusMonitor); private _idGenerator = inject(_IdGenerator); - private _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); + private _animationDisabled = _animationsDisabled(); private _checked = false; /** @@ -721,7 +720,7 @@ export class MatButtonToggle implements OnInit, AfterViewInit, OnDestroy { // 1. We don't want the animation to fire on the first render for pre-checked toggles so we // delay adding the class until the view is rendered. // 2. We don't want animation if the `NoopAnimationsModule` is provided. - if (this._animationMode !== 'NoopAnimations') { + if (!this._animationDisabled) { this._elementRef.nativeElement.classList.add('mat-button-toggle-animations-enabled'); } diff --git a/src/material/button/button-base.ts b/src/material/button/button-base.ts index 271044546c36..20abb826c425 100644 --- a/src/material/button/button-base.ts +++ b/src/material/button/button-base.ts @@ -9,7 +9,6 @@ import {FocusMonitor, FocusOrigin} from '@angular/cdk/a11y'; import { AfterViewInit, - ANIMATION_MODULE_TYPE, booleanAttribute, Directive, ElementRef, @@ -21,7 +20,7 @@ import { OnDestroy, Renderer2, } from '@angular/core'; -import {_StructuralStylesLoader, MatRippleLoader, ThemePalette} from '../core'; +import {_animationsDisabled, _StructuralStylesLoader, MatRippleLoader, ThemePalette} from '../core'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; /** @@ -62,13 +61,13 @@ function transformTabIndex(value: unknown): number | undefined { '[class.mat-mdc-button-disabled]': 'disabled', '[class.mat-mdc-button-disabled-interactive]': 'disabledInteractive', '[class.mat-unthemed]': '!color', - '[class._mat-animation-noopable]': '_animationMode === "NoopAnimations"', + '[class._mat-animation-noopable]': '_animationsDisabled', }, }) export class MatButtonBase implements AfterViewInit, OnDestroy { _elementRef = inject>(ElementRef); protected _ngZone = inject(NgZone); - protected _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); + protected _animationsDisabled = _animationsDisabled(); protected readonly _config = inject(MAT_BUTTON_CONFIG, {optional: true}); private readonly _focusMonitor = inject(FocusMonitor); diff --git a/src/material/checkbox/checkbox.ts b/src/material/checkbox/checkbox.ts index b95d4ed88cc4..9c991e193e52 100644 --- a/src/material/checkbox/checkbox.ts +++ b/src/material/checkbox/checkbox.ts @@ -8,7 +8,6 @@ import {_IdGenerator, FocusableOption} from '@angular/cdk/a11y'; import { - ANIMATION_MODULE_TYPE, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, @@ -36,7 +35,12 @@ import { ValidationErrors, Validator, } from '@angular/forms'; -import {MatRipple, _MatInternalFormField, _StructuralStylesLoader} from '../core'; +import { + MatRipple, + _MatInternalFormField, + _StructuralStylesLoader, + _animationsDisabled, +} from '../core'; import { MAT_CHECKBOX_DEFAULT_OPTIONS, MAT_CHECKBOX_DEFAULT_OPTIONS_FACTORY, @@ -79,7 +83,7 @@ const defaults = MAT_CHECKBOX_DEFAULT_OPTIONS_FACTORY(); '[attr.tabindex]': 'null', '[attr.aria-label]': 'null', '[attr.aria-labelledby]': 'null', - '[class._mat-animation-noopable]': `_animationMode === 'NoopAnimations'`, + '[class._mat-animation-noopable]': '_animationsDisabled', '[class.mdc-checkbox--disabled]': 'disabled', '[id]': 'id', // Add classes that users can use to more easily target disabled or checked checkboxes. @@ -111,7 +115,7 @@ export class MatCheckbox _elementRef = inject>(ElementRef); private _changeDetectorRef = inject(ChangeDetectorRef); private _ngZone = inject(NgZone); - _animationMode? = inject(ANIMATION_MODULE_TYPE, {optional: true}); + private _animationsDisabled = _animationsDisabled(); private _options = inject(MAT_CHECKBOX_DEFAULT_OPTIONS, { optional: true, }); @@ -464,7 +468,7 @@ export class MatCheckbox newState: TransitionCheckState, ): string { // Don't transition if animations are disabled. - if (this._animationMode === 'NoopAnimations') { + if (this._animationsDisabled) { return ''; } diff --git a/src/material/chips/chip.ts b/src/material/chips/chip.ts index 3860dd4c6b78..883e6f280fca 100644 --- a/src/material/chips/chip.ts +++ b/src/material/chips/chip.ts @@ -11,7 +11,6 @@ import {BACKSPACE, DELETE} from '@angular/cdk/keycodes'; import {_CdkPrivateStyleLoader, _VisuallyHiddenLoader} from '@angular/cdk/private'; import {DOCUMENT} from '@angular/common'; import { - ANIMATION_MODULE_TYPE, AfterContentInit, AfterViewInit, ChangeDetectionStrategy, @@ -39,6 +38,7 @@ import { MatRippleLoader, RippleGlobalOptions, _StructuralStylesLoader, + _animationsDisabled, } from '../core'; import {Subject, Subscription, merge} from 'rxjs'; import {MatChipAction} from './chip-action'; @@ -120,7 +120,7 @@ export class MatChip implements OnInit, AfterViewInit, AfterContentInit, DoCheck private _actionChanges: Subscription | undefined; /** Whether animations for the chip are enabled. */ - _animationsDisabled: boolean; + _animationsDisabled = _animationsDisabled(); /** All avatars present in the chip. */ @ContentChildren(MAT_CHIP_AVATAR, {descendants: true}) @@ -245,8 +245,6 @@ export class MatChip implements OnInit, AfterViewInit, AfterContentInit, DoCheck const styleLoader = inject(_CdkPrivateStyleLoader); styleLoader.load(_StructuralStylesLoader); styleLoader.load(_VisuallyHiddenLoader); - const animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); - this._animationsDisabled = animationMode === 'NoopAnimations'; this._monitorFocus(); this._rippleLoader?.configureRipple(this._elementRef.nativeElement, { diff --git a/src/material/core/animation/animation.ts b/src/material/core/animation/animation.ts index a8b9720596c0..02c15a20e06c 100644 --- a/src/material/core/animation/animation.ts +++ b/src/material/core/animation/animation.ts @@ -6,6 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ +import {ANIMATION_MODULE_TYPE, inject} from '@angular/core'; + /** * @deprecated No longer used, will be removed. * @breaking-change 21.0.0 @@ -28,3 +30,11 @@ export class AnimationDurations { static ENTERING = '225ms'; static EXITING = '195ms'; } + +/** + * Returns whether animations have been disabled by DI. Must be called in a DI context. + * @docs-private + */ +export function _animationsDisabled(): boolean { + return inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations'; +} diff --git a/src/material/core/private/ripple-loader.ts b/src/material/core/private/ripple-loader.ts index 0b7c1d5ae4d4..6cddbd8c3dfa 100644 --- a/src/material/core/private/ripple-loader.ts +++ b/src/material/core/private/ripple-loader.ts @@ -7,15 +7,7 @@ */ import {DOCUMENT} from '@angular/common'; -import { - ANIMATION_MODULE_TYPE, - Injectable, - Injector, - NgZone, - OnDestroy, - RendererFactory2, - inject, -} from '@angular/core'; +import {Injectable, Injector, NgZone, OnDestroy, RendererFactory2, inject} from '@angular/core'; import { MAT_RIPPLE_GLOBAL_OPTIONS, RippleRenderer, @@ -24,6 +16,7 @@ import { } from '../ripple'; import {Platform, _bindEventWithOptions, _getEventTarget} from '@angular/cdk/platform'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; +import {_animationsDisabled} from '../animation/animation'; /** The options for the MatRippleLoader's event listeners. */ const eventListenerOptions = {capture: true}; @@ -58,7 +51,7 @@ const matRippleDisabled = 'mat-ripple-loader-disabled'; @Injectable({providedIn: 'root'}) export class MatRippleLoader implements OnDestroy { private _document = inject(DOCUMENT); - private _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); + private _animationsDisabled = _animationsDisabled(); private _globalRippleOptions = inject(MAT_RIPPLE_GLOBAL_OPTIONS, {optional: true}); private _platform = inject(Platform); private _ngZone = inject(NgZone); @@ -179,17 +172,16 @@ export class MatRippleLoader implements OnDestroy { rippleEl.classList.add('mat-ripple', host.getAttribute(matRippleClassName)!); host.append(rippleEl); - const isNoopAnimations = this._animationMode === 'NoopAnimations'; const globalOptions = this._globalRippleOptions; - const enterDuration = isNoopAnimations + const enterDuration = this._animationsDisabled ? 0 : globalOptions?.animation?.enterDuration ?? defaultRippleAnimationConfig.enterDuration; - const exitDuration = isNoopAnimations + const exitDuration = this._animationsDisabled ? 0 : globalOptions?.animation?.exitDuration ?? defaultRippleAnimationConfig.exitDuration; const target: RippleTarget = { rippleDisabled: - isNoopAnimations || globalOptions?.disabled || host.hasAttribute(matRippleDisabled), + this._animationsDisabled || globalOptions?.disabled || host.hasAttribute(matRippleDisabled), rippleConfig: { centered: host.hasAttribute(matRippleCentered), terminateOnPointerUp: globalOptions?.terminateOnPointerUp, diff --git a/src/material/core/ripple/ripple.ts b/src/material/core/ripple/ripple.ts index 3b4593a0f199..c5e6c603c2fe 100644 --- a/src/material/core/ripple/ripple.ts +++ b/src/material/core/ripple/ripple.ts @@ -15,13 +15,13 @@ import { NgZone, OnDestroy, OnInit, - ANIMATION_MODULE_TYPE, Injector, inject, } from '@angular/core'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; import {RippleAnimationConfig, RippleConfig, RippleRef} from './ripple-ref'; import {RippleRenderer, RippleTarget} from './ripple-renderer'; +import {_animationsDisabled} from '../animation/animation'; /** Configurable options for `matRipple`. */ export interface RippleGlobalOptions { @@ -65,7 +65,7 @@ export const MAT_RIPPLE_GLOBAL_OPTIONS = new InjectionToken }) export class MatRipple implements OnInit, OnDestroy, RippleTarget { private _elementRef = inject>(ElementRef); - private _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); + private _animationsDisabled = _animationsDisabled(); /** Custom color for all ripples. */ @Input('matRippleColor') color: string; @@ -177,7 +177,7 @@ export class MatRipple implements OnInit, OnDestroy, RippleTarget { color: this.color, animation: { ...this._globalOptions.animation, - ...(this._animationMode === 'NoopAnimations' ? {enterDuration: 0, exitDuration: 0} : {}), + ...(this._animationsDisabled ? {enterDuration: 0, exitDuration: 0} : {}), ...this.animation, }, terminateOnPointerUp: this._globalOptions.terminateOnPointerUp, diff --git a/src/material/core/selection/pseudo-checkbox/pseudo-checkbox.ts b/src/material/core/selection/pseudo-checkbox/pseudo-checkbox.ts index 78ea18e78899..79962f379ef6 100644 --- a/src/material/core/selection/pseudo-checkbox/pseudo-checkbox.ts +++ b/src/material/core/selection/pseudo-checkbox/pseudo-checkbox.ts @@ -6,14 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ -import { - Component, - ViewEncapsulation, - Input, - ChangeDetectionStrategy, - ANIMATION_MODULE_TYPE, - inject, -} from '@angular/core'; +import {Component, ViewEncapsulation, Input, ChangeDetectionStrategy} from '@angular/core'; +import {_animationsDisabled} from '../../animation/animation'; /** * Possible states for a pseudo checkbox. @@ -47,11 +41,11 @@ export type MatPseudoCheckboxState = 'unchecked' | 'checked' | 'indeterminate'; '[class.mat-pseudo-checkbox-disabled]': 'disabled', '[class.mat-pseudo-checkbox-minimal]': 'appearance === "minimal"', '[class.mat-pseudo-checkbox-full]': 'appearance === "full"', - '[class._mat-animation-noopable]': '_animationMode === "NoopAnimations"', + '[class._mat-animation-noopable]': '_animationsDisabled', }, }) export class MatPseudoCheckbox { - _animationMode? = inject(ANIMATION_MODULE_TYPE, {optional: true}); + _animationsDisabled = _animationsDisabled(); /** Display state of the checkbox. */ @Input() state: MatPseudoCheckboxState = 'unchecked'; diff --git a/src/material/datepicker/datepicker-base.ts b/src/material/datepicker/datepicker-base.ts index d821d7d7efe9..8b98d13d5544 100644 --- a/src/material/datepicker/datepicker-base.ts +++ b/src/material/datepicker/datepicker-base.ts @@ -33,7 +33,6 @@ import {DOCUMENT} from '@angular/common'; import { afterNextRender, AfterViewInit, - ANIMATION_MODULE_TYPE, booleanAttribute, ChangeDetectionStrategy, ChangeDetectorRef, @@ -57,7 +56,7 @@ import { ViewEncapsulation, } from '@angular/core'; import {MatButton} from '../button'; -import {DateAdapter, ThemePalette} from '../core'; +import {_animationsDisabled, DateAdapter, ThemePalette} from '../core'; import {merge, Observable, Subject, Subscription} from 'rxjs'; import {filter, take} from 'rxjs/operators'; import {MatCalendar, MatCalendarView} from './calendar'; @@ -140,8 +139,7 @@ export class MatDatepickerContent> implements AfterViewInit, OnDestroy { protected _elementRef = inject>(ElementRef); - protected _animationsDisabled = - inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations'; + protected _animationsDisabled = _animationsDisabled(); private _changeDetectorRef = inject(ChangeDetectorRef); private _globalModel = inject>(MatDateSelectionModel); private _dateAdapter = inject>(DateAdapter)!; diff --git a/src/material/dialog/dialog-container.ts b/src/material/dialog/dialog-container.ts index 6db50d85250a..625d148db3ac 100644 --- a/src/material/dialog/dialog-container.ts +++ b/src/material/dialog/dialog-container.ts @@ -13,13 +13,12 @@ import { EventEmitter, OnDestroy, ViewEncapsulation, - ANIMATION_MODULE_TYPE, - inject, } from '@angular/core'; import {MatDialogConfig} from './dialog-config'; import {CdkDialogContainer} from '@angular/cdk/dialog'; import {coerceNumberProperty} from '@angular/cdk/coercion'; import {CdkPortalOutlet, ComponentPortal} from '@angular/cdk/portal'; +import {_animationsDisabled} from '../core/animation/animation'; /** Event that captures the state of dialog container animations. */ interface LegacyDialogAnimationEvent { @@ -65,13 +64,11 @@ export const CLOSE_ANIMATION_DURATION = 75; }, }) export class MatDialogContainer extends CdkDialogContainer implements OnDestroy { - private _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); - /** Emits when an animation state changes. */ _animationStateChanged = new EventEmitter(); /** Whether animations are enabled. */ - _animationsEnabled: boolean = this._animationMode !== 'NoopAnimations'; + _animationsEnabled = !_animationsDisabled(); /** Number of actions projected in the dialog. */ protected _actionSectionCount = 0; diff --git a/src/material/expansion/expansion-panel.ts b/src/material/expansion/expansion-panel.ts index 04dfdf0e0b02..1565aa72ada5 100644 --- a/src/material/expansion/expansion-panel.ts +++ b/src/material/expansion/expansion-panel.ts @@ -28,7 +28,6 @@ import { ViewContainerRef, ViewEncapsulation, booleanAttribute, - ANIMATION_MODULE_TYPE, inject, NgZone, Renderer2, @@ -39,6 +38,7 @@ import {filter, startWith, take} from 'rxjs/operators'; import {MatAccordionBase, MatAccordionTogglePosition, MAT_ACCORDION} from './accordion-base'; import {MAT_EXPANSION_PANEL} from './expansion-panel-base'; import {MatExpansionPanelContent} from './expansion-panel-content'; +import {_animationsDisabled} from '../core'; /** MatExpansionPanel's states. */ export type MatExpansionPanelState = 'expanded' | 'collapsed'; @@ -94,8 +94,7 @@ export class MatExpansionPanel implements AfterContentInit, OnChanges, OnDestroy { private _viewContainerRef = inject(ViewContainerRef); - private readonly _animationsDisabled = - inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations'; + private readonly _animationsDisabled = _animationsDisabled(); private _document = inject(DOCUMENT); private _ngZone = inject(NgZone); private _elementRef = inject>(ElementRef); diff --git a/src/material/form-field/form-field.ts b/src/material/form-field/form-field.ts index 6bdcbf2a745c..3cb238a35f07 100644 --- a/src/material/form-field/form-field.ts +++ b/src/material/form-field/form-field.ts @@ -10,7 +10,6 @@ import {BooleanInput, coerceBooleanProperty} from '@angular/cdk/coercion'; import {Platform} from '@angular/cdk/platform'; import {NgTemplateOutlet} from '@angular/common'; import { - ANIMATION_MODULE_TYPE, AfterContentChecked, AfterContentInit, AfterViewInit, @@ -34,7 +33,7 @@ import { inject, } from '@angular/core'; import {AbstractControlDirective, ValidatorFn} from '@angular/forms'; -import {ThemePalette} from '../core'; +import {_animationsDisabled, ThemePalette} from '../core'; import {_IdGenerator} from '@angular/cdk/a11y'; import {Subject, Subscription, merge} from 'rxjs'; import {map, pairwise, takeUntil, filter, startWith} from 'rxjs/operators'; @@ -326,7 +325,7 @@ export class MatFormField private _stateChanges: Subscription | undefined; private _valueChanges: Subscription | undefined; private _describedByChanges: Subscription | undefined; - protected readonly _animationsDisabled: boolean; + protected readonly _animationsDisabled = _animationsDisabled(); constructor(...args: unknown[]); @@ -342,8 +341,6 @@ export class MatFormField this.color = defaults.color; } } - - this._animationsDisabled = inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations'; } ngAfterViewInit() { diff --git a/src/material/list/list-base.ts b/src/material/list/list-base.ts index 24d5e93b478f..eaa942ed0043 100644 --- a/src/material/list/list-base.ts +++ b/src/material/list/list-base.ts @@ -18,10 +18,10 @@ import { NgZone, OnDestroy, QueryList, - ANIMATION_MODULE_TYPE, Injector, } from '@angular/core'; import { + _animationsDisabled, _StructuralStylesLoader, MAT_RIPPLE_GLOBAL_OPTIONS, RippleConfig, @@ -109,7 +109,7 @@ export abstract class MatListItemBase implements AfterViewInit, OnDestroy, Rippl _isButtonElement: boolean; /** Whether animations are disabled. */ - _noopAnimations: boolean; + _noopAnimations = _animationsDisabled(); @ContentChildren(MatListItemAvatar, {descendants: false}) _avatars: QueryList; @ContentChildren(MatListItemIcon, {descendants: false}) _icons: QueryList; @@ -183,12 +183,9 @@ export abstract class MatListItemBase implements AfterViewInit, OnDestroy, Rippl const globalRippleOptions = inject(MAT_RIPPLE_GLOBAL_OPTIONS, { optional: true, }); - const animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); - this.rippleConfig = globalRippleOptions || {}; this._hostElement = this._elementRef.nativeElement; this._isButtonElement = this._hostElement.nodeName.toLowerCase() === 'button'; - this._noopAnimations = animationMode === 'NoopAnimations'; if (this._listBase && !this._listBase._isNonInteractive) { this._initInteractiveListItem(); diff --git a/src/material/menu/menu.ts b/src/material/menu/menu.ts index 94ac23c85498..cffc817720fc 100644 --- a/src/material/menu/menu.ts +++ b/src/material/menu/menu.ts @@ -29,7 +29,6 @@ import { AfterRenderRef, inject, Injector, - ANIMATION_MODULE_TYPE, } from '@angular/core'; import {_IdGenerator, FocusKeyManager, FocusOrigin} from '@angular/cdk/a11y'; import {Direction} from '@angular/cdk/bidi'; @@ -48,6 +47,7 @@ import {MatMenuPanel, MAT_MENU_PANEL} from './menu-panel'; import {MenuPositionX, MenuPositionY} from './menu-positions'; import {throwMatMenuInvalidPositionX, throwMatMenuInvalidPositionY} from './menu-errors'; import {MatMenuContent, MAT_MENU_CONTENT} from './menu-content'; +import {_animationsDisabled} from '../core'; /** Reason why the menu was closed. */ export type MenuCloseReason = void | 'click' | 'keydown' | 'tab'; @@ -128,7 +128,7 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnI private _exitFallbackTimeout: ReturnType | undefined; /** Whether animations are currently disabled. */ - protected _animationsDisabled: boolean; + protected _animationsDisabled = _animationsDisabled(); /** All items inside the menu. Includes items nested inside another menu. */ @ContentChildren(MatMenuItem, {descendants: true}) _allItems: QueryList; @@ -290,7 +290,6 @@ export class MatMenu implements AfterContentInit, MatMenuPanel, OnI this.backdropClass = defaultOptions.backdropClass; this.overlapTrigger = defaultOptions.overlapTrigger; this.hasBackdrop = defaultOptions.hasBackdrop; - this._animationsDisabled = inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations'; } ngOnInit() { diff --git a/src/material/progress-bar/progress-bar.ts b/src/material/progress-bar/progress-bar.ts index 2ca673c8f08a..76ef1b15c78c 100644 --- a/src/material/progress-bar/progress-bar.ts +++ b/src/material/progress-bar/progress-bar.ts @@ -21,11 +21,10 @@ import { InjectionToken, inject, numberAttribute, - ANIMATION_MODULE_TYPE, Renderer2, } from '@angular/core'; import {DOCUMENT} from '@angular/common'; -import {ThemePalette} from '../core'; +import {_animationsDisabled, ThemePalette} from '../core'; /** Last animation end data. */ export interface ProgressAnimationEnd { @@ -117,7 +116,6 @@ export class MatProgressBar implements AfterViewInit, OnDestroy { private _changeDetectorRef = inject(ChangeDetectorRef); private _renderer = inject(Renderer2); private _cleanupTransitionEnd: (() => void) | undefined; - _animationMode? = inject(ANIMATION_MODULE_TYPE, {optional: true}); constructor(...args: unknown[]); @@ -126,8 +124,6 @@ export class MatProgressBar implements AfterViewInit, OnDestroy { optional: true, }); - this._isNoopAnimation = this._animationMode === 'NoopAnimations'; - if (defaults) { if (defaults.color) { this.color = this._defaultColor = defaults.color; @@ -138,7 +134,7 @@ export class MatProgressBar implements AfterViewInit, OnDestroy { } /** Flag that indicates whether NoopAnimations mode is set to true. */ - _isNoopAnimation = false; + _isNoopAnimation = _animationsDisabled(); // TODO: should be typed as `ThemePalette` but internal apps pass in arbitrary strings. /** diff --git a/src/material/progress-spinner/progress-spinner.ts b/src/material/progress-spinner/progress-spinner.ts index b0a6ff8da14b..c517d3e11c55 100644 --- a/src/material/progress-spinner/progress-spinner.ts +++ b/src/material/progress-spinner/progress-spinner.ts @@ -15,10 +15,9 @@ import { ViewChild, ViewEncapsulation, numberAttribute, - ANIMATION_MODULE_TYPE, inject, } from '@angular/core'; -import {ThemePalette} from '../core'; +import {_animationsDisabled, ThemePalette} from '../core'; import {NgTemplateOutlet} from '@angular/common'; /** Possible mode for a progress spinner. */ @@ -128,11 +127,9 @@ export class MatProgressSpinner { constructor(...args: unknown[]); constructor() { - const animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); const defaults = inject(MAT_PROGRESS_SPINNER_DEFAULT_OPTIONS); - this._noopAnimations = - animationMode === 'NoopAnimations' && !!defaults && !defaults._forceAnimations; + this._noopAnimations = _animationsDisabled() && !!defaults && !defaults._forceAnimations; this.mode = this._elementRef.nativeElement.nodeName.toLowerCase() === 'mat-spinner' ? 'indeterminate' diff --git a/src/material/radio/radio.ts b/src/material/radio/radio.ts index 0d4115897a65..18f71768b6a0 100644 --- a/src/material/radio/radio.ts +++ b/src/material/radio/radio.ts @@ -9,7 +9,6 @@ import {_IdGenerator, FocusMonitor, FocusOrigin} from '@angular/cdk/a11y'; import {UniqueSelectionDispatcher} from '@angular/cdk/collections'; import { - ANIMATION_MODULE_TYPE, AfterContentInit, AfterViewInit, ChangeDetectionStrategy, @@ -39,7 +38,13 @@ import { Renderer2, } from '@angular/core'; import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms'; -import {MatRipple, ThemePalette, _MatInternalFormField, _StructuralStylesLoader} from '../core'; +import { + MatRipple, + ThemePalette, + _MatInternalFormField, + _StructuralStylesLoader, + _animationsDisabled, +} from '../core'; import {Subscription} from 'rxjs'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; @@ -597,7 +602,7 @@ export class MatRadioButton implements OnInit, AfterViewInit, DoCheck, OnDestroy _rippleTrigger: ElementRef; /** Whether animations are disabled. */ - _noopAnimations: boolean; + _noopAnimations = _animationsDisabled(); private _injector = inject(Injector); @@ -606,13 +611,11 @@ export class MatRadioButton implements OnInit, AfterViewInit, DoCheck, OnDestroy constructor() { inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader); const radioGroup = inject(MAT_RADIO_GROUP, {optional: true})!; - const animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); const tabIndex = inject(new HostAttributeToken('tabindex'), {optional: true}); // Assertions. Ideally these should be stripped out by the compiler. // TODO(jelbourn): Assert that there's no name binding AND a parent radio group. this.radioGroup = radioGroup; - this._noopAnimations = animationMode === 'NoopAnimations'; this._disabledInteractive = this._defaultOptions?.disabledInteractive ?? false; if (tabIndex) { diff --git a/src/material/select/select.ts b/src/material/select/select.ts index 3f7f21824d55..005312acbe1b 100644 --- a/src/material/select/select.ts +++ b/src/material/select/select.ts @@ -59,7 +59,6 @@ import { ViewChild, ViewEncapsulation, HostAttributeToken, - ANIMATION_MODULE_TYPE, Renderer2, } from '@angular/core'; import { @@ -71,6 +70,7 @@ import { Validators, } from '@angular/forms'; import { + _animationsDisabled, _countGroupLabelsBeforeOption, _ErrorStateTracker, _getOptionScrollPosition, @@ -227,8 +227,7 @@ export class MatSelect ngControl = inject(NgControl, {self: true, optional: true})!; private _liveAnnouncer = inject(LiveAnnouncer); protected _defaultOptions = inject(MAT_SELECT_CONFIG, {optional: true}); - protected _animationsDisabled = - inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations'; + protected _animationsDisabled = _animationsDisabled(); private _initialized = new Subject(); private _cleanupDetach: (() => void) | undefined; diff --git a/src/material/sidenav/drawer.ts b/src/material/sidenav/drawer.ts index 77c3f691fc99..efbb55507e75 100644 --- a/src/material/sidenav/drawer.ts +++ b/src/material/sidenav/drawer.ts @@ -22,7 +22,6 @@ import { AfterContentInit, afterNextRender, AfterViewInit, - ANIMATION_MODULE_TYPE, ChangeDetectionStrategy, ChangeDetectorRef, Component, @@ -45,6 +44,7 @@ import { } from '@angular/core'; import {fromEvent, merge, Observable, Subject} from 'rxjs'; import {debounceTime, filter, map, mapTo, startWith, take, takeUntil} from 'rxjs/operators'; +import {_animationsDisabled} from '../core'; /** * Throws an exception when two MatDrawer are matching the same position. @@ -703,7 +703,7 @@ export class MatDrawerContainer implements AfterContentInit, DoCheck, OnDestroy private _element = inject>(ElementRef); private _ngZone = inject(NgZone); private _changeDetectorRef = inject(ChangeDetectorRef); - private _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); + private _animationDisabled = _animationsDisabled(); _transitionsEnabled = false; /** All drawers in the container. Includes drawers from inside nested containers. */ @@ -819,7 +819,7 @@ export class MatDrawerContainer implements AfterContentInit, DoCheck, OnDestroy .pipe(takeUntil(this._destroyed)) .subscribe(() => this.updateContentMargins()); - if (this._animationMode !== 'NoopAnimations' && platform.isBrowser) { + if (!this._animationDisabled && platform.isBrowser) { this._ngZone.runOutsideAngular(() => { // Enable the animations after a delay in order to skip // the initial transition if a drawer is open by default. diff --git a/src/material/slide-toggle/slide-toggle.ts b/src/material/slide-toggle/slide-toggle.ts index b6ff2d5e7eac..c606a5025ea0 100644 --- a/src/material/slide-toggle/slide-toggle.ts +++ b/src/material/slide-toggle/slide-toggle.ts @@ -23,7 +23,6 @@ import { SimpleChanges, ViewChild, ViewEncapsulation, - ANIMATION_MODULE_TYPE, inject, HostAttributeToken, } from '@angular/core'; @@ -40,7 +39,12 @@ import { MAT_SLIDE_TOGGLE_DEFAULT_OPTIONS, MatSlideToggleDefaultOptions, } from './slide-toggle-config'; -import {_MatInternalFormField, _StructuralStylesLoader, MatRipple} from '../core'; +import { + _animationsDisabled, + _MatInternalFormField, + _StructuralStylesLoader, + MatRipple, +} from '../core'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; /** Change event object emitted by a slide toggle. */ @@ -122,7 +126,7 @@ export class MatSlideToggle this._switchElement.nativeElement.focus(); } /** Whether noop animations are enabled. */ - _noopAnimations: boolean; + _noopAnimations = _animationsDisabled(); /** Whether the slide toggle is currently focused. */ _focused: boolean; @@ -206,11 +210,9 @@ export class MatSlideToggle inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader); const tabIndex = inject(new HostAttributeToken('tabindex'), {optional: true}); const defaults = this.defaults; - const animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); this.tabIndex = tabIndex == null ? 0 : parseInt(tabIndex) || 0; this.color = defaults.color || 'accent'; - this._noopAnimations = animationMode === 'NoopAnimations'; this.id = this._uniqueId = inject(_IdGenerator).getId('mat-mdc-slide-toggle-'); this.hideIcon = defaults.hideIcon ?? false; this.disabledInteractive = defaults.disabledInteractive ?? false; diff --git a/src/material/slider/slider.ts b/src/material/slider/slider.ts index 423267910983..7ce70cdc12aa 100644 --- a/src/material/slider/slider.ts +++ b/src/material/slider/slider.ts @@ -26,9 +26,9 @@ import { ViewChild, ViewChildren, ViewEncapsulation, - ANIMATION_MODULE_TYPE, } from '@angular/core'; import { + _animationsDisabled, _StructuralStylesLoader, MAT_RIPPLE_GLOBAL_OPTIONS, RippleGlobalOptions, @@ -359,7 +359,7 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider { _tickMarks: _MatTickMark[]; /** Whether animations have been disabled. */ - _noopAnimations: boolean; + _noopAnimations = _animationsDisabled(); /** Subscription to changes to the directionality (LTR / RTL) context for the application. */ private _dirChangeSubscription: Subscription; @@ -410,8 +410,6 @@ export class MatSlider implements AfterViewInit, OnDestroy, _MatSlider { constructor() { inject(_CdkPrivateStyleLoader).load(_StructuralStylesLoader); - const animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); - this._noopAnimations = animationMode === 'NoopAnimations'; if (this._dir) { this._dirChangeSubscription = this._dir.change.subscribe(() => this._onDirChange()); diff --git a/src/material/snack-bar/snack-bar-container.ts b/src/material/snack-bar/snack-bar-container.ts index 846d4366927e..3e43a7de36fe 100644 --- a/src/material/snack-bar/snack-bar-container.ts +++ b/src/material/snack-bar/snack-bar-container.ts @@ -9,7 +9,6 @@ import { afterRender, AfterRenderRef, - ANIMATION_MODULE_TYPE, ChangeDetectionStrategy, ChangeDetectorRef, Component, @@ -35,6 +34,7 @@ import {_IdGenerator, AriaLivePoliteness} from '@angular/cdk/a11y'; import {Platform} from '@angular/cdk/platform'; import {MatSnackBarConfig} from './snack-bar-config'; import {take} from 'rxjs/operators'; +import {_animationsDisabled} from '../core'; const ENTER_ANIMATION = '_mat-snack-bar-enter'; const EXIT_ANIMATION = '_mat-snack-bar-exit'; @@ -69,8 +69,7 @@ export class MatSnackBarContainer extends BasePortalOutlet implements OnDestroy private _changeDetectorRef = inject(ChangeDetectorRef); private _platform = inject(Platform); private _rendersRef: AfterRenderRef; - protected _animationsDisabled = - inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations'; + protected _animationsDisabled = _animationsDisabled(); snackBarConfig = inject(MatSnackBarConfig); private _document = inject(DOCUMENT); diff --git a/src/material/sort/sort-header.html b/src/material/sort/sort-header.html index ecfae9ac0294..6f119af50223 100644 --- a/src/material/sort/sort-header.html +++ b/src/material/sort/sort-header.html @@ -15,7 +15,7 @@ [class.mat-sort-header-ascending]="this._sort.direction === 'asc'" [class.mat-sort-header-recently-cleared-ascending]="_recentlyCleared() === 'asc'" [class.mat-sort-header-recently-cleared-descending]="_recentlyCleared() === 'desc'" - [class.mat-sort-header-animations-disabled]="_animationModule === 'NoopAnimations'" + [class.mat-sort-header-animations-disabled]="_animationsDisabled" [attr.tabindex]="_isDisabled() ? null : 0" [attr.role]="_isDisabled() ? null : 'button'"> diff --git a/src/material/sort/sort-header.ts b/src/material/sort/sort-header.ts index f8ca0b57276c..f6b8961f245f 100644 --- a/src/material/sort/sort-header.ts +++ b/src/material/sort/sort-header.ts @@ -20,7 +20,6 @@ import { booleanAttribute, inject, signal, - ANIMATION_MODULE_TYPE, ChangeDetectorRef, } from '@angular/core'; import {merge, Subscription} from 'rxjs'; @@ -35,7 +34,7 @@ import {SortDirection} from './sort-direction'; import {getSortHeaderNotContainedWithinSortError} from './sort-errors'; import {MatSortHeaderIntl} from './sort-header-intl'; import {_CdkPrivateStyleLoader} from '@angular/cdk/private'; -import {_StructuralStylesLoader} from '../core'; +import {_animationsDisabled, _StructuralStylesLoader} from '../core'; /** * Valid positions for the arrow to be in for its opacity and translation. If the state is a @@ -102,7 +101,7 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI private _elementRef = inject>(ElementRef); private _ariaDescriber = inject(AriaDescriber, {optional: true}); private _renderChanges: Subscription | undefined; - protected _animationModule = inject(ANIMATION_MODULE_TYPE, {optional: true}); + protected _animationsDisabled = _animationsDisabled(); /** * Indicates which state was just cleared from the sort header. diff --git a/src/material/stepper/stepper.ts b/src/material/stepper/stepper.ts index 29bbb4dd86c5..fe5834bde0a0 100644 --- a/src/material/stepper/stepper.ts +++ b/src/material/stepper/stepper.ts @@ -10,7 +10,6 @@ import {CdkStep, CdkStepper} from '@angular/cdk/stepper'; import { AfterContentInit, AfterViewInit, - ANIMATION_MODULE_TYPE, ChangeDetectionStrategy, Component, ContentChild, @@ -32,7 +31,7 @@ import { } from '@angular/core'; import {NgTemplateOutlet} from '@angular/common'; import {AbstractControl, FormGroupDirective, NgForm} from '@angular/forms'; -import {ErrorStateMatcher, ThemePalette} from '../core'; +import {_animationsDisabled, ErrorStateMatcher, ThemePalette} from '../core'; import {Platform} from '@angular/cdk/platform'; import {CdkPortalOutlet, TemplatePortal} from '@angular/cdk/portal'; import {Subscription} from 'rxjs'; @@ -142,7 +141,7 @@ export class MatStep extends CdkStep implements ErrorStateMatcher, AfterContentI export class MatStepper extends CdkStepper implements AfterViewInit, AfterContentInit, OnDestroy { private _ngZone = inject(NgZone); private _renderer = inject(Renderer2); - private _animationsModule = inject(ANIMATION_MODULE_TYPE, {optional: true}); + private _animationsDisabled = _animationsDisabled(); private _cleanupTransition: (() => void) | undefined; protected _isAnimating = signal(false); @@ -234,7 +233,7 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten }); this._ngZone.runOutsideAngular(() => { - if (this._animationsModule !== 'NoopAnimations') { + if (!this._animationsDisabled) { setTimeout(() => { // Delay enabling the animations so we don't animate the initial state. this._elementRef.nativeElement.classList.add('mat-stepper-animations-enabled'); @@ -289,7 +288,7 @@ export class MatStepper extends CdkStepper implements AfterViewInit, AfterConten } _getAnimationDuration() { - if (this._animationsModule === 'NoopAnimations') { + if (this._animationsDisabled) { return '0ms'; } diff --git a/src/material/tabs/paginated-tab-header.ts b/src/material/tabs/paginated-tab-header.ts index afa004cbb830..c43f349c55e7 100644 --- a/src/material/tabs/paginated-tab-header.ts +++ b/src/material/tabs/paginated-tab-header.ts @@ -13,7 +13,6 @@ import {SharedResizeObserver} from '@angular/cdk/observers/private'; import {Platform, _bindEventWithOptions} from '@angular/cdk/platform'; import {ViewportRuler} from '@angular/cdk/scrolling'; import { - ANIMATION_MODULE_TYPE, AfterContentChecked, AfterContentInit, AfterViewInit, @@ -35,6 +34,7 @@ import { } from '@angular/core'; import {EMPTY, Observable, Observer, Subject, merge, of as observableOf, timer} from 'rxjs'; import {debounceTime, filter, skip, startWith, switchMap, takeUntil} from 'rxjs/operators'; +import {_animationsDisabled} from '../core'; /** Config used to bind passive event listeners */ const passiveEventListenerOptions = { @@ -80,7 +80,7 @@ export abstract class MatPaginatedTabHeader private _sharedResizeObserver = inject(SharedResizeObserver); private _injector = inject(Injector); private _renderer = inject(Renderer2); - _animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); + _animationsDisabled = _animationsDisabled(); private _eventCleanups: (() => void)[]; abstract _items: QueryList; diff --git a/src/material/tabs/tab-body.ts b/src/material/tabs/tab-body.ts index f1dc0ccb0abc..b1efb0801da8 100644 --- a/src/material/tabs/tab-body.ts +++ b/src/material/tabs/tab-body.ts @@ -10,7 +10,6 @@ import {Direction, Directionality} from '@angular/cdk/bidi'; import {CdkPortalOutlet, TemplatePortal} from '@angular/cdk/portal'; import {CdkScrollable} from '@angular/cdk/scrolling'; import { - ANIMATION_MODULE_TYPE, ChangeDetectionStrategy, ChangeDetectorRef, Component, @@ -31,6 +30,7 @@ import { } from '@angular/core'; import {Subscription} from 'rxjs'; import {startWith} from 'rxjs/operators'; +import {_animationsDisabled} from '../core'; /** * The portal host directive for the contents of the tab. @@ -131,7 +131,7 @@ export class MatTabBody implements OnInit, OnDestroy { private _ngZone = inject(NgZone); private _injector = inject(Injector); private _renderer = inject(Renderer2); - private _animationsModule = inject(ANIMATION_MODULE_TYPE, {optional: true}); + private _diAnimationsDisabled = _animationsDisabled(); private _eventCleanups?: (() => void)[]; private _initialized: boolean; private _fallbackTimer: ReturnType; @@ -317,7 +317,7 @@ export class MatTabBody implements OnInit, OnDestroy { /** Whether animations are disabled for the tab group. */ private _animationsDisabled() { return ( - this._animationsModule === 'NoopAnimations' || + this._diAnimationsDisabled || this.animationDuration === '0ms' || this.animationDuration === '0s' ); diff --git a/src/material/tabs/tab-group.html b/src/material/tabs/tab-group.html index acd621732dfd..626fa00c7355 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;) { + [class._mat-animation-noopable]="_animationsDisabled">
(MAT_TABS_CONFIG, {optional: true}); - super(elementRef, changeDetectorRef, viewportRuler, dir, ngZone, platform, animationMode); + super(); this.disablePagination = defaultConfig && defaultConfig.disablePagination != null ? defaultConfig.disablePagination @@ -332,12 +320,10 @@ export class MatTabLink optional: true, }); const tabIndex = inject(new HostAttributeToken('tabindex'), {optional: true}); - const animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); - this.rippleConfig = globalRippleOptions || {}; this.tabIndex = tabIndex == null ? 0 : parseInt(tabIndex) || 0; - if (animationMode === 'NoopAnimations') { + if (_animationsDisabled()) { this.rippleConfig.animation = {enterDuration: 0, exitDuration: 0}; } diff --git a/src/material/timepicker/timepicker.ts b/src/material/timepicker/timepicker.ts index 7da38240c4f6..483b48e1b99f 100644 --- a/src/material/timepicker/timepicker.ts +++ b/src/material/timepicker/timepicker.ts @@ -9,7 +9,6 @@ import { afterNextRender, AfterRenderRef, - ANIMATION_MODULE_TYPE, booleanAttribute, ChangeDetectionStrategy, Component, @@ -35,6 +34,7 @@ import { ViewEncapsulation, } from '@angular/core'; import { + _animationsDisabled, DateAdapter, MAT_DATE_FORMATS, MAT_OPTION_PARENT_COMPONENT, @@ -103,8 +103,7 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent { private _dateAdapter = inject>(DateAdapter, {optional: true})!; private _dateFormats = inject(MAT_DATE_FORMATS, {optional: true})!; private _scrollStrategyFactory = inject(MAT_TIMEPICKER_SCROLL_STRATEGY); - protected _animationsDisabled = - inject(ANIMATION_MODULE_TYPE, {optional: true}) === 'NoopAnimations'; + protected _animationsDisabled = _animationsDisabled(); private _isOpen = signal(false); private _activeDescendant = signal(null); diff --git a/src/material/tooltip/tooltip.ts b/src/material/tooltip/tooltip.ts index 49d45af9d9d5..82d5770956f2 100644 --- a/src/material/tooltip/tooltip.ts +++ b/src/material/tooltip/tooltip.ts @@ -28,7 +28,6 @@ import { ViewContainerRef, ViewEncapsulation, inject, - ANIMATION_MODULE_TYPE, afterNextRender, Injector, } from '@angular/core'; @@ -51,6 +50,7 @@ import { } from '@angular/cdk/overlay'; import {ComponentPortal} from '@angular/cdk/portal'; import {Observable, Subject} from 'rxjs'; +import {_animationsDisabled} from '../core'; /** Possible positions for a tooltip. */ export type TooltipPosition = 'left' | 'right' | 'above' | 'below' | 'before' | 'after'; @@ -998,7 +998,7 @@ export class TooltipComponent implements OnDestroy { _mouseLeaveHideDelay: number; /** Whether animations are currently disabled. */ - private _animationsDisabled: boolean; + private _animationsDisabled = _animationsDisabled(); /** Reference to the internal tooltip element. */ @ViewChild('tooltip', { @@ -1025,10 +1025,7 @@ export class TooltipComponent implements OnDestroy { constructor(...args: unknown[]); - constructor() { - const animationMode = inject(ANIMATION_MODULE_TYPE, {optional: true}); - this._animationsDisabled = animationMode === 'NoopAnimations'; - } + constructor() {} /** * Shows the tooltip with an animation originating from the provided origin From 371be9e207dd09d2c8274f68063087c1e2a7c0cf Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Wed, 19 Mar 2025 14:03:30 +0100 Subject: [PATCH 2/2] refactor(cdk/overlay): add config option for disabling animations Currently the CDK overlay uses `ANIMATION_MODULE_TYPE` to implicitly disable the backdrop animation, however we want to introduce a Material-specific way of disabling animations. These changes add a config option that we can use to do so. --- goldens/cdk/dialog/index.api.md | 1 + goldens/cdk/overlay/index.api.md | 1 + src/cdk/dialog/dialog-config.ts | 5 +++++ src/cdk/dialog/dialog.ts | 1 + src/cdk/overlay/overlay-config.ts | 3 +++ src/cdk/overlay/overlay.ts | 2 +- src/material/autocomplete/autocomplete-trigger.ts | 3 +++ src/material/bottom-sheet/bottom-sheet.ts | 3 +++ src/material/core/private/ripple-loader.ts | 4 ++-- src/material/datepicker/datepicker-base.ts | 2 ++ src/material/dialog/dialog-container.ts | 4 ++-- src/material/dialog/dialog.ts | 6 ++++++ src/material/menu/menu-trigger.ts | 3 +++ src/material/snack-bar/snack-bar.ts | 3 +++ src/material/timepicker/timepicker.ts | 1 + src/material/tooltip/tooltip.ts | 2 ++ 16 files changed, 39 insertions(+), 5 deletions(-) diff --git a/goldens/cdk/dialog/index.api.md b/goldens/cdk/dialog/index.api.md index 156b68e79511..cbcca84d68f1 100644 --- a/goldens/cdk/dialog/index.api.md +++ b/goldens/cdk/dialog/index.api.md @@ -127,6 +127,7 @@ export class DialogConfig( MAT_AUTOCOMPLETE_DEFAULT_OPTIONS, {optional: true}, @@ -903,6 +905,7 @@ export class MatAutocompleteTrigger hasBackdrop: this._defaults?.hasBackdrop, backdropClass: this._defaults?.backdropClass, panelClass: this._defaults?.overlayPanelClass, + disableAnimations: this._animationsDisabled, }); } diff --git a/src/material/bottom-sheet/bottom-sheet.ts b/src/material/bottom-sheet/bottom-sheet.ts index e46edf55d2e7..ce8e459e5f0d 100644 --- a/src/material/bottom-sheet/bottom-sheet.ts +++ b/src/material/bottom-sheet/bottom-sheet.ts @@ -13,6 +13,7 @@ import {Injectable, TemplateRef, InjectionToken, OnDestroy, inject} from '@angul import {MAT_BOTTOM_SHEET_DATA, MatBottomSheetConfig} from './bottom-sheet-config'; import {MatBottomSheetContainer} from './bottom-sheet-container'; import {MatBottomSheetRef} from './bottom-sheet-ref'; +import {_animationsDisabled} from '../core'; /** Injection token that can be used to specify default bottom sheet options. */ export const MAT_BOTTOM_SHEET_DEFAULT_OPTIONS = new InjectionToken( @@ -26,6 +27,7 @@ export const MAT_BOTTOM_SHEET_DEFAULT_OPTIONS = new InjectionToken(MAT_BOTTOM_SHEET_DEFAULT_OPTIONS, { optional: true, }); @@ -89,6 +91,7 @@ export class MatBottomSheet implements OnDestroy { container: MatBottomSheetContainer, scrollStrategy: _config.scrollStrategy || this._overlay.scrollStrategies.block(), positionStrategy: this._overlay.position().global().centerHorizontally().bottom('0'), + disableAnimations: this._animationsDisabled, templateContext: () => ({bottomSheetRef: ref}), providers: (cdkRef, _cdkConfig, container) => { ref = new MatBottomSheetRef(cdkRef, _config, container as MatBottomSheetContainer); diff --git a/src/material/core/private/ripple-loader.ts b/src/material/core/private/ripple-loader.ts index 6cddbd8c3dfa..c098a7e523a6 100644 --- a/src/material/core/private/ripple-loader.ts +++ b/src/material/core/private/ripple-loader.ts @@ -175,10 +175,10 @@ export class MatRippleLoader implements OnDestroy { const globalOptions = this._globalRippleOptions; const enterDuration = this._animationsDisabled ? 0 - : globalOptions?.animation?.enterDuration ?? defaultRippleAnimationConfig.enterDuration; + : (globalOptions?.animation?.enterDuration ?? defaultRippleAnimationConfig.enterDuration); const exitDuration = this._animationsDisabled ? 0 - : globalOptions?.animation?.exitDuration ?? defaultRippleAnimationConfig.exitDuration; + : (globalOptions?.animation?.exitDuration ?? defaultRippleAnimationConfig.exitDuration); const target: RippleTarget = { rippleDisabled: this._animationsDisabled || globalOptions?.disabled || host.hasAttribute(matRippleDisabled), diff --git a/src/material/datepicker/datepicker-base.ts b/src/material/datepicker/datepicker-base.ts index 8b98d13d5544..2fea31ae0b18 100644 --- a/src/material/datepicker/datepicker-base.ts +++ b/src/material/datepicker/datepicker-base.ts @@ -394,6 +394,7 @@ export abstract class MatDatepickerBase< private _dateAdapter = inject>(DateAdapter, {optional: true})!; private _dir = inject(Directionality, {optional: true}); private _model = inject>(MatDateSelectionModel); + private _animationsDisabled = _animationsDisabled(); private _scrollStrategy = inject(MAT_DATEPICKER_SCROLL_STRATEGY); private _inputStateChanges = Subscription.EMPTY; @@ -769,6 +770,7 @@ export abstract class MatDatepickerBase< direction: this._dir || 'ltr', scrollStrategy: isDialog ? this._overlay.scrollStrategies.block() : this._scrollStrategy(), panelClass: `mat-datepicker-${isDialog ? 'dialog' : 'popup'}`, + disableAnimations: this._animationsDisabled, }), )); diff --git a/src/material/dialog/dialog-container.ts b/src/material/dialog/dialog-container.ts index 625d148db3ac..c217c6aca5d7 100644 --- a/src/material/dialog/dialog-container.ts +++ b/src/material/dialog/dialog-container.ts @@ -77,11 +77,11 @@ export class MatDialogContainer extends CdkDialogContainer impl private _hostElement: HTMLElement = this._elementRef.nativeElement; /** Duration of the dialog open animation. */ private _enterAnimationDuration = this._animationsEnabled - ? parseCssTime(this._config.enterAnimationDuration) ?? OPEN_ANIMATION_DURATION + ? (parseCssTime(this._config.enterAnimationDuration) ?? OPEN_ANIMATION_DURATION) : 0; /** Duration of the dialog close animation. */ private _exitAnimationDuration = this._animationsEnabled - ? parseCssTime(this._config.exitAnimationDuration) ?? CLOSE_ANIMATION_DURATION + ? (parseCssTime(this._config.exitAnimationDuration) ?? CLOSE_ANIMATION_DURATION) : 0; /** Current timer for dialog animations. */ private _animationTimer: ReturnType | null = null; diff --git a/src/material/dialog/dialog.ts b/src/material/dialog/dialog.ts index fa75e3f50aee..d4083b695dd9 100644 --- a/src/material/dialog/dialog.ts +++ b/src/material/dialog/dialog.ts @@ -23,6 +23,7 @@ import {defer, Observable, Subject} from 'rxjs'; import {Dialog, DialogConfig} from '@angular/cdk/dialog'; import {startWith} from 'rxjs/operators'; import {_IdGenerator} from '@angular/cdk/a11y'; +import {_animationsDisabled} from '../core'; /** Injection token that can be used to access the data that was passed in to a dialog. */ export const MAT_DIALOG_DATA = new InjectionToken('MatMdcDialogData'); @@ -55,6 +56,7 @@ export class MatDialog implements OnDestroy { private _parentDialog = inject(MatDialog, {optional: true, skipSelf: true}); private _idGenerator = inject(_IdGenerator); protected _dialog = inject(Dialog); + private _animationsDisabled = _animationsDisabled(); private readonly _openDialogsAtThisLevel: MatDialogRef[] = []; private readonly _afterAllClosedAtThisLevel = new Subject(); @@ -146,6 +148,10 @@ export class MatDialog implements OnDestroy { // Disable closing on detachments so that we can sync up the animation. // The Material dialog ref handles this manually. closeOnOverlayDetachments: false, + disableAnimations: + this._animationsDisabled || + config.enterAnimationDuration?.toLocaleString() === '0' || + config.exitAnimationDuration?.toString() === '0', container: { type: this._dialogContainerType, providers: () => [ diff --git a/src/material/menu/menu-trigger.ts b/src/material/menu/menu-trigger.ts index a19f1bf79393..6dcf5aefeb8b 100644 --- a/src/material/menu/menu-trigger.ts +++ b/src/material/menu/menu-trigger.ts @@ -47,6 +47,7 @@ import {throwMatMenuRecursiveError} from './menu-errors'; import {MatMenuItem} from './menu-item'; import {MAT_MENU_PANEL, MatMenuPanel} from './menu-panel'; import {MenuPositionX, MenuPositionY} from './menu-positions'; +import {_animationsDisabled} from '../core'; /** Injection token that determines the scroll handling while the menu is open. */ export const MAT_MENU_SCROLL_STRATEGY = new InjectionToken<() => ScrollStrategy>( @@ -117,6 +118,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { private _ngZone = inject(NgZone); private _scrollStrategy = inject(MAT_MENU_SCROLL_STRATEGY); private _changeDetectorRef = inject(ChangeDetectorRef); + private _animationsDisabled = _animationsDisabled(); private _cleanupTouchstart: () => void; private _portal: TemplatePortal; @@ -447,6 +449,7 @@ export class MatMenuTrigger implements AfterContentInit, OnDestroy { panelClass: menu.overlayPanelClass, scrollStrategy: this._scrollStrategy(), direction: this._dir || 'ltr', + disableAnimations: this._animationsDisabled, }); } diff --git a/src/material/snack-bar/snack-bar.ts b/src/material/snack-bar/snack-bar.ts index b7c2dd348de0..c7e65e3e2b83 100644 --- a/src/material/snack-bar/snack-bar.ts +++ b/src/material/snack-bar/snack-bar.ts @@ -25,6 +25,7 @@ import {MAT_SNACK_BAR_DATA, MatSnackBarConfig} from './snack-bar-config'; import {MatSnackBarRef} from './snack-bar-ref'; import {ComponentPortal, TemplatePortal} from '@angular/cdk/portal'; import {takeUntil} from 'rxjs/operators'; +import {_animationsDisabled} from '../core'; /** * @docs-private @@ -55,6 +56,7 @@ export class MatSnackBar implements OnDestroy { private _breakpointObserver = inject(BreakpointObserver); private _parentSnackBar = inject(MatSnackBar, {optional: true, skipSelf: true}); private _defaultConfig = inject(MAT_SNACK_BAR_DEFAULT_OPTIONS); + private _animationsDisabled = _animationsDisabled(); /** * Reference to the current snack bar in the view *at this level* (in the Angular injector tree). @@ -295,6 +297,7 @@ export class MatSnackBar implements OnDestroy { } overlayConfig.positionStrategy = positionStrategy; + overlayConfig.disableAnimations = this._animationsDisabled; return this._overlay.create(overlayConfig); } diff --git a/src/material/timepicker/timepicker.ts b/src/material/timepicker/timepicker.ts index 483b48e1b99f..7fc2352618e6 100644 --- a/src/material/timepicker/timepicker.ts +++ b/src/material/timepicker/timepicker.ts @@ -343,6 +343,7 @@ export class MatTimepicker implements OnDestroy, MatOptionParentComponent { scrollStrategy: this._scrollStrategyFactory(), direction: this._dir || 'ltr', hasBackdrop: false, + disableAnimations: this._animationsDisabled, }); this._overlayRef.detachments().subscribe(() => this.close()); diff --git a/src/material/tooltip/tooltip.ts b/src/material/tooltip/tooltip.ts index 82d5770956f2..ccd75286af37 100644 --- a/src/material/tooltip/tooltip.ts +++ b/src/material/tooltip/tooltip.ts @@ -207,6 +207,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { protected _dir = inject(Directionality); private _injector = inject(Injector); private _viewContainerRef = inject(ViewContainerRef); + private _animationsDisabled = _animationsDisabled(); private _defaultOptions = inject(MAT_TOOLTIP_DEFAULT_OPTIONS, { optional: true, }); @@ -550,6 +551,7 @@ export class MatTooltip implements OnDestroy, AfterViewInit { positionStrategy: strategy, panelClass: `${this._cssClassPrefix}-${PANEL_CLASS}`, scrollStrategy: this._injector.get(MAT_TOOLTIP_SCROLL_STRATEGY)(), + disableAnimations: this._animationsDisabled, }); this._updatePosition(this._overlayRef);