From 775c7cec4be08f1a8fac30119d3ecfa2a1a6c74c Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Thu, 21 Nov 2024 13:11:07 +0100 Subject: [PATCH] fix(material/sort): simplify animations For a long time the sort header's animation was set up by rendering out 4 `div` elements and then arranging them to look like an arrow. This is somewhat complicated to maintain, difficult to customize, in some cases it leads to weird visual bugs and ends up triggering excessive change detections. On top of that, because it depends on `@angular/animations`, it is prone to memory leaks (see https://github.com/angular/angular/issues/54149). These changes aim to simplify the component and make it more robust by using an `svg` icon and dealing with the animations. Fixes #9758. Fixes #9844. Fixes #10088. Fixes #15451. Fixes #19441. Fixes #10242. --- src/material/sort/sort-animations.ts | 2 + src/material/sort/sort-header.html | 21 ++- src/material/sort/sort-header.scss | 148 +++++++++---------- src/material/sort/sort-header.ts | 182 ++++------------------- src/material/sort/sort.spec.ts | 186 +----------------------- tools/public_api_guard/material/sort.md | 21 +-- 6 files changed, 118 insertions(+), 442 deletions(-) diff --git a/src/material/sort/sort-animations.ts b/src/material/sort/sort-animations.ts index 2107cc36b3f0..155a46c7b35c 100644 --- a/src/material/sort/sort-animations.ts +++ b/src/material/sort/sort-animations.ts @@ -24,6 +24,8 @@ const SORT_ANIMATION_TRANSITION = /** * Animations used by MatSort. * @docs-private + * @deprecated No longer being used, to be removed. + * @breaking-change 21.0.0 */ export const matSortAnimations: { readonly indicator: AnimationTriggerMetadata; diff --git a/src/material/sort/sort-header.html b/src/material/sort/sort-header.html index 93cf5d902690..ecfae9ac0294 100644 --- a/src/material/sort/sort-header.html +++ b/src/material/sort/sort-header.html @@ -11,6 +11,11 @@
@@ -26,18 +31,10 @@ @if (_renderArrow()) { -
-
-
-
-
-
-
+
+
}
diff --git a/src/material/sort/sort-header.scss b/src/material/sort/sort-header.scss index 0e1818717f69..575439384539 100644 --- a/src/material/sort/sort-header.scss +++ b/src/material/sort/sort-header.scss @@ -1,16 +1,7 @@ -@use '@angular/cdk'; - @use '../core/tokens/m2/mat/sort' as tokens-mat-sort; @use '../core/tokens/token-utils'; @use '../core/focus-indicators/private'; -$header-arrow-margin: 6px; -$header-arrow-container-size: 12px; -$header-arrow-stem-size: 10px; -$header-arrow-pointer-length: 6px; -$header-arrow-thickness: 2px; -$header-arrow-hint-opacity: 0.38; - .mat-sort-header-container { display: flex; cursor: pointer; @@ -51,93 +42,96 @@ $header-arrow-hint-opacity: 0.38; flex-direction: row-reverse; } +@keyframes _mat-sort-header-recently-cleared-ascending { + from { + transform: translateY(0); + opacity: 1; + } + + to { + transform: translateY(-25%); + opacity: 0; + } +} + +@keyframes _mat-sort-header-recently-cleared-descending { + from { + transform: translateY(0) rotate(180deg); + opacity: 1; + } + + to { + transform: translateY(25%) rotate(180deg); + opacity: 0; + } +} + .mat-sort-header-arrow { - height: $header-arrow-container-size; - width: $header-arrow-container-size; - min-width: $header-arrow-container-size; + $timing: 225ms cubic-bezier(0.4, 0, 0.2, 1); + height: 12px; + width: 12px; position: relative; - display: flex; + transition: transform $timing, opacity $timing; + opacity: 0; + overflow: visible; @include token-utils.use-tokens(tokens-mat-sort.$prefix, tokens-mat-sort.get-token-slots()) { @include token-utils.create-token-slot(color, arrow-color); } - // Start off at 0 since the arrow may become visible while parent are animating. - // This will be overwritten when the arrow animations kick in. See #11819. - opacity: 0; - - &, - [dir='rtl'] .mat-sort-header-position-before & { - margin: 0 0 0 $header-arrow-margin; + .mat-sort-header:hover & { + opacity: 0.54; } - .mat-sort-header-position-before &, - [dir='rtl'] & { - margin: 0 $header-arrow-margin 0 0; + .mat-sort-header .mat-sort-header-sorted & { + opacity: 1; } -} -.mat-sort-header-stem { - background: currentColor; - height: $header-arrow-stem-size; - width: $header-arrow-thickness; - margin: auto; - display: flex; - align-items: center; - - @include cdk.high-contrast { - width: 0; - border-left: solid $header-arrow-thickness; + .mat-sort-header-descending & { + transform: rotate(180deg); } -} -.mat-sort-header-indicator { - width: 100%; - height: $header-arrow-thickness; - display: flex; - align-items: center; - position: absolute; - top: 0; - left: 0; -} + .mat-sort-header-recently-cleared-ascending & { + transform: translateY(-25%); + } -.mat-sort-header-pointer-middle { - margin: auto; - height: $header-arrow-thickness; - width: $header-arrow-thickness; - background: currentColor; - transform: rotate(45deg); + .mat-sort-header-recently-cleared-ascending & { + transition: none; // Without this the animation looks glitchy on Safari. + animation: _mat-sort-header-recently-cleared-ascending $timing forwards; + } - @include cdk.high-contrast { - width: 0; - height: 0; - border-top: solid $header-arrow-thickness; - border-left: solid $header-arrow-thickness; + .mat-sort-header-recently-cleared-descending & { + transition: none; // Without this the animation looks glitchy on Safari. + animation: _mat-sort-header-recently-cleared-descending $timing forwards; } -} -.mat-sort-header-pointer-left, -.mat-sort-header-pointer-right { - background: currentColor; - width: $header-arrow-pointer-length; - height: $header-arrow-thickness; - position: absolute; - top: 0; + // Set the durations to 0, but keep the actual animation, since we still want it to play. + .mat-sort-header-animations-disabled & { + transition-duration: 0ms; + animation-duration: 0ms; + } - @include cdk.high-contrast { - width: 0; - height: 0; - border-left: solid $header-arrow-pointer-length; - border-top: solid $header-arrow-thickness; + svg { + // Even though this is 24x24, the actual `path` inside ends up being 12x12. + width: 24px; + height: 24px; + fill: currentColor; + position: absolute; + top: 50%; + left: 50%; + margin: -12px 0 0 -12px; + + // Without this transform the element twitches at the end of the transition on Safari. + transform: translateZ(0); } -} -.mat-sort-header-pointer-left { - transform-origin: right; - left: 0; -} + &, + [dir='rtl'] .mat-sort-header-position-before & { + margin: 0 0 0 6px; + } -.mat-sort-header-pointer-right { - transform-origin: left; - right: 0; + .mat-sort-header-position-before &, + [dir='rtl'] & { + margin: 0 6px 0 0; + } } diff --git a/src/material/sort/sort-header.ts b/src/material/sort/sort-header.ts index 75fc440a882e..51d529b0fcbd 100644 --- a/src/material/sort/sort-header.ts +++ b/src/material/sort/sort-header.ts @@ -11,7 +11,6 @@ import {ENTER, SPACE} from '@angular/cdk/keycodes'; import { AfterViewInit, ChangeDetectionStrategy, - ChangeDetectorRef, Component, ElementRef, Input, @@ -20,6 +19,9 @@ import { ViewEncapsulation, booleanAttribute, inject, + signal, + ANIMATION_MODULE_TYPE, + ChangeDetectorRef, } from '@angular/core'; import {merge, Subscription} from 'rxjs'; import { @@ -29,7 +31,6 @@ import { MatSortDefaultOptions, SortHeaderArrowPosition, } from './sort'; -import {matSortAnimations} from './sort-animations'; import {SortDirection} from './sort-direction'; import {getSortHeaderNotContainedWithinSortError} from './sort-errors'; import {MatSortHeaderIntl} from './sort-header-intl'; @@ -43,6 +44,8 @@ import {_StructuralStylesLoader} from '@angular/material/core'; * be fully opaque in the center. * * @docs-private + * @deprecated No longer being used, to be removed. + * @breaking-change 21.0.0 */ export type ArrowViewState = SortDirection | 'hint' | 'active'; @@ -50,6 +53,8 @@ export type ArrowViewState = SortDirection | 'hint' | 'active'; * States describing the arrow's animated position (animating fromState to toState). * If the fromState is not defined, there will be no animated transition to the toState. * @docs-private + * @deprecated No longer being used, to be removed. + * @breaking-change 21.0.0 */ export interface ArrowViewStateTransition { fromState?: ArrowViewState; @@ -77,36 +82,33 @@ interface MatSortHeaderColumnDef { styleUrl: 'sort-header.css', host: { 'class': 'mat-sort-header', - '(click)': '_handleClick()', + '(click)': '_toggleOnInteraction()', '(keydown)': '_handleKeydown($event)', - '(mouseenter)': '_setIndicatorHintVisible(true)', - '(mouseleave)': '_setIndicatorHintVisible(false)', + '(mouseleave)': '_recentlyCleared.set(false)', '[attr.aria-sort]': '_getAriaSortAttribute()', '[class.mat-sort-header-disabled]': '_isDisabled()', }, encapsulation: ViewEncapsulation.None, changeDetection: ChangeDetectionStrategy.OnPush, - animations: [ - matSortAnimations.indicator, - matSortAnimations.leftPointer, - matSortAnimations.rightPointer, - matSortAnimations.arrowOpacity, - matSortAnimations.arrowPosition, - matSortAnimations.allowChildren, - ], }) export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewInit { _intl = inject(MatSortHeaderIntl); - private _changeDetectorRef = inject(ChangeDetectorRef); _sort = inject(MatSort, {optional: true})!; _columnDef = inject('MAT_SORT_HEADER_COLUMN_DEF' as any, { optional: true, }); + private _changeDetectorRef = inject(ChangeDetectorRef); private _focusMonitor = inject(FocusMonitor); private _elementRef = inject>(ElementRef); private _ariaDescriber = inject(AriaDescriber, {optional: true}); + private _renderChanges: Subscription | undefined; + protected _animationModule = inject(ANIMATION_MODULE_TYPE, {optional: true}); - private _rerenderSubscription: Subscription; + /** + * Indicates which state was just cleared from the sort header. + * Will be reset on the next interaction. Used for coordinating animations. + */ + protected _recentlyCleared = signal(null); /** * The element with role="button" inside this component's view. We need this @@ -114,27 +116,6 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI */ private _sortButton: HTMLElement; - /** - * Flag set to true when the indicator should be displayed while the sort is not active. Used to - * provide an affordance that the header is sortable by showing on focus and hover. - */ - _showIndicatorHint: boolean = false; - - /** - * The view transition state of the arrow (translation/ opacity) - indicates its `from` and `to` - * position through the animation. If animations are currently disabled, the fromState is removed - * so that there is no animation displayed. - */ - _viewState: ArrowViewStateTransition = {}; - - /** The direction the arrow should be facing according to the current state. */ - _arrowDirection: SortDirection = ''; - - /** - * Whether the view state animation should show the transition between the `from` and `to` states. - */ - _disableViewStateAnimation = false; - /** * ID of this sort header. If used within the context of a CdkColumnDef, this will default to * the column's name. @@ -190,8 +171,6 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI if (defaultOptions?.arrowPosition) { this.arrowPosition = defaultOptions?.arrowPosition; } - - this._handleStateChanges(); } ngOnInit() { @@ -199,14 +178,10 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI this.id = this._columnDef.name; } - // Initialize the direction of the arrow and set the view state to be immediately that state. - this._updateArrowDirection(); - this._setAnimationTransitionState({ - toState: this._isSorted() ? 'active' : this._arrowDirection, - }); - this._sort.register(this); - + this._renderChanges = merge(this._sort._stateChanges, this._sort.sortChange).subscribe(() => + this._changeDetectorRef.markForCheck(), + ); this._sortButton = this._elementRef.nativeElement.querySelector('.mat-sort-header-container')!; this._updateSortActionDescription(this._sortActionDescription); } @@ -214,80 +189,33 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI ngAfterViewInit() { // We use the focus monitor because we also want to style // things differently based on the focus origin. - this._focusMonitor.monitor(this._elementRef, true).subscribe(origin => { - const newState = !!origin; - if (newState !== this._showIndicatorHint) { - this._setIndicatorHintVisible(newState); - this._changeDetectorRef.markForCheck(); - } - }); + this._focusMonitor + .monitor(this._elementRef, true) + .subscribe(() => this._recentlyCleared.set(null)); } ngOnDestroy() { this._focusMonitor.stopMonitoring(this._elementRef); this._sort.deregister(this); - this._rerenderSubscription.unsubscribe(); + this._renderChanges?.unsubscribe(); if (this._sortButton) { this._ariaDescriber?.removeDescription(this._sortButton, this._sortActionDescription); } } - /** - * Sets the "hint" state such that the arrow will be semi-transparently displayed as a hint to the - * user showing what the active sort will become. If set to false, the arrow will fade away. - */ - _setIndicatorHintVisible(visible: boolean) { - // No-op if the sort header is disabled - should not make the hint visible. - if (this._isDisabled() && visible) { - return; - } - - this._showIndicatorHint = visible; - - if (!this._isSorted()) { - this._updateArrowDirection(); - if (this._showIndicatorHint) { - this._setAnimationTransitionState({fromState: this._arrowDirection, toState: 'hint'}); - } else { - this._setAnimationTransitionState({fromState: 'hint', toState: this._arrowDirection}); - } - } - } - - /** - * Sets the animation transition view state for the arrow's position and opacity. If the - * `disableViewStateAnimation` flag is set to true, the `fromState` will be ignored so that - * no animation appears. - */ - _setAnimationTransitionState(viewState: ArrowViewStateTransition) { - this._viewState = viewState || {}; - - // If the animation for arrow position state (opacity/translation) should be disabled, - // remove the fromState so that it jumps right to the toState. - if (this._disableViewStateAnimation) { - this._viewState = {toState: viewState.toState}; - } - } - /** Triggers the sort on this sort header and removes the indicator hint. */ _toggleOnInteraction() { - this._sort.sort(this); - - // Do not show the animation if the header was already shown in the right position. - if (this._viewState.toState === 'hint' || this._viewState.toState === 'active') { - this._disableViewStateAnimation = true; - } - } - - _handleClick() { if (!this._isDisabled()) { + const wasSorted = this._isSorted(); + const prevDirection = this._sort.direction; this._sort.sort(this); + this._recentlyCleared.set(wasSorted && !this._isSorted() ? prevDirection : null); } } _handleKeydown(event: KeyboardEvent) { - if (!this._isDisabled() && (event.keyCode === SPACE || event.keyCode === ENTER)) { + if (event.keyCode === SPACE || event.keyCode === ENTER) { event.preventDefault(); this._toggleOnInteraction(); } @@ -301,31 +229,6 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI ); } - /** Returns the animation state for the arrow direction (indicator and pointers). */ - _getArrowDirectionState() { - return `${this._isSorted() ? 'active-' : ''}${this._arrowDirection}`; - } - - /** Returns the arrow position state (opacity, translation). */ - _getArrowViewState() { - const fromState = this._viewState.fromState; - return (fromState ? `${fromState}-to-` : '') + this._viewState.toState; - } - - /** - * Updates the direction the arrow should be pointing. If it is not sorted, the arrow should be - * facing the start direction. Otherwise if it is sorted, the arrow should point in the currently - * active sorted direction. The reason this is updated through a function is because the direction - * should only be changed at specific times - when deactivated but the hint is displayed and when - * the sort is active and the direction changes. Otherwise the arrow's direction should linger - * in cases such as the sort becoming deactivated but we want to animate the arrow away while - * preserving its direction, even though the next sort direction is actually different and should - * only be changed once the arrow displays again (hint or activation). - */ - _updateArrowDirection() { - this._arrowDirection = this._isSorted() ? this._sort.direction : this.start || this._sort.start; - } - _isDisabled() { return this._sort.disabled || this.disabled; } @@ -365,33 +268,4 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI this._sortActionDescription = newDescription; } - - /** Handles changes in the sorting state. */ - private _handleStateChanges() { - this._rerenderSubscription = merge( - this._sort.sortChange, - this._sort._stateChanges, - this._intl.changes, - ).subscribe(() => { - if (this._isSorted()) { - this._updateArrowDirection(); - - // Do not show the animation if the header was already shown in the right position. - if (this._viewState.toState === 'hint' || this._viewState.toState === 'active') { - this._disableViewStateAnimation = true; - } - - this._setAnimationTransitionState({fromState: this._arrowDirection, toState: 'active'}); - this._showIndicatorHint = false; - } - - // If this header was recently active and now no longer sorted, animate away the arrow. - if (!this._isSorted() && this._viewState && this._viewState.toState === 'active') { - this._disableViewStateAnimation = false; - this._setAnimationTransitionState({fromState: 'active', toState: this._arrowDirection}); - } - - this._changeDetectorRef.markForCheck(); - }); - } } diff --git a/src/material/sort/sort.spec.ts b/src/material/sort/sort.spec.ts index 08bf17a48219..383721086209 100644 --- a/src/material/sort/sort.spec.ts +++ b/src/material/sort/sort.spec.ts @@ -1,11 +1,6 @@ import {CollectionViewer, DataSource} from '@angular/cdk/collections'; import {CdkTableModule} from '@angular/cdk/table'; -import { - createFakeEvent, - createMouseEvent, - dispatchMouseEvent, - wrappedErrorMessage, -} from '@angular/cdk/testing/private'; +import {dispatchMouseEvent, wrappedErrorMessage} from '@angular/cdk/testing/private'; import {Component, ElementRef, ViewChild, inject} from '@angular/core'; import {ComponentFixture, TestBed, fakeAsync, tick, waitForAsync} from '@angular/core/testing'; import {MatTableModule} from '@angular/material/table'; @@ -103,116 +98,6 @@ describe('MatSort', () => { expect(sortables.has('column_c')).toBe(true); }); - describe('checking correct arrow direction and view state for its various states', () => { - let expectedStates: Map; - - beforeEach(() => { - // Starting state for the view and directions - note that overrideStart is reversed to be - // desc - expectedStates = new Map([ - ['defaultA', {viewState: 'asc', arrowDirection: 'asc'}], - ['defaultB', {viewState: 'asc', arrowDirection: 'asc'}], - ['overrideStart', {viewState: 'desc', arrowDirection: 'desc'}], - ['overrideDisableClear', {viewState: 'asc', arrowDirection: 'asc'}], - ]); - component.expectViewAndDirectionStates(expectedStates); - }); - - it('should be correct when mousing over headers and leaving on mouseleave', () => { - // Mousing over the first sort should set the view state to hint (asc) - component.dispatchMouseEvent('defaultA', 'mouseenter'); - expectedStates.set('defaultA', {viewState: 'asc-to-hint', arrowDirection: 'asc'}); - component.expectViewAndDirectionStates(expectedStates); - - // Mousing away from the first sort should hide the arrow - component.dispatchMouseEvent('defaultA', 'mouseleave'); - expectedStates.set('defaultA', {viewState: 'hint-to-asc', arrowDirection: 'asc'}); - component.expectViewAndDirectionStates(expectedStates); - - // Mousing over another sort should set the view state to hint (desc) - component.dispatchMouseEvent('overrideStart', 'mouseenter'); - expectedStates.set('overrideStart', {viewState: 'desc-to-hint', arrowDirection: 'desc'}); - component.expectViewAndDirectionStates(expectedStates); - }); - - it('should be correct when mousing over header and then sorting', () => { - // Mousing over the first sort should set the view state to hint - component.dispatchMouseEvent('defaultA', 'mouseenter'); - expectedStates.set('defaultA', {viewState: 'asc-to-hint', arrowDirection: 'asc'}); - component.expectViewAndDirectionStates(expectedStates); - - // Clicking sort on the header should set it to be active immediately - // (since it was already hinted) - component.dispatchMouseEvent('defaultA', 'click'); - expectedStates.set('defaultA', {viewState: 'active', arrowDirection: 'active-asc'}); - component.expectViewAndDirectionStates(expectedStates); - }); - - it('should be correct when cycling through a default sort header', () => { - // Sort the header to set it to the active start state - component.sort('defaultA'); - expectedStates.set('defaultA', {viewState: 'asc-to-active', arrowDirection: 'active-asc'}); - component.expectViewAndDirectionStates(expectedStates); - - // Sorting again will reverse its direction - component.dispatchMouseEvent('defaultA', 'click'); - expectedStates.set('defaultA', {viewState: 'active', arrowDirection: 'active-desc'}); - component.expectViewAndDirectionStates(expectedStates); - - // Sorting again will remove the sort and animate away the view - component.dispatchMouseEvent('defaultA', 'click'); - expectedStates.set('defaultA', {viewState: 'active-to-desc', arrowDirection: 'desc'}); - component.expectViewAndDirectionStates(expectedStates); - }); - - it('should not enter sort with animations if an animations is disabled', () => { - // Sort the header to set it to the active start state - component.defaultA._disableViewStateAnimation = true; - component.sort('defaultA'); - expectedStates.set('defaultA', {viewState: 'active', arrowDirection: 'active-asc'}); - component.expectViewAndDirectionStates(expectedStates); - - // Sorting again will reverse its direction - component.defaultA._disableViewStateAnimation = true; - component.dispatchMouseEvent('defaultA', 'click'); - expectedStates.set('defaultA', {viewState: 'active', arrowDirection: 'active-desc'}); - component.expectViewAndDirectionStates(expectedStates); - }); - - it('should be correct when sort has changed while a header is active', () => { - // Sort the first header to set up - component.sort('defaultA'); - expectedStates.set('defaultA', {viewState: 'asc-to-active', arrowDirection: 'active-asc'}); - component.expectViewAndDirectionStates(expectedStates); - - // Sort the second header and verify that the first header animated away - component.dispatchMouseEvent('defaultB', 'click'); - expectedStates.set('defaultA', {viewState: 'active-to-asc', arrowDirection: 'asc'}); - expectedStates.set('defaultB', {viewState: 'asc-to-active', arrowDirection: 'active-asc'}); - component.expectViewAndDirectionStates(expectedStates); - }); - - it('should be correct when sort has been disabled', () => { - // Mousing over the first sort should set the view state to hint - component.disabledColumnSort = true; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); - - component.dispatchMouseEvent('defaultA', 'mouseenter'); - component.expectViewAndDirectionStates(expectedStates); - }); - - it('should be correct when sorting programmatically', () => { - component.active = 'defaultB'; - component.direction = 'asc'; - fixture.changeDetectorRef.markForCheck(); - fixture.detectChanges(); - - expectedStates.set('defaultB', {viewState: 'asc-to-active', arrowDirection: 'active-asc'}); - component.expectViewAndDirectionStates(expectedStates); - }); - }); - it('should be able to cycle from asc -> desc from either start point', () => { component.disableClear = true; @@ -328,54 +213,6 @@ describe('MatSort', () => { testSingleColumnSortDirectionSequence(fixture, ['asc', 'desc'], 'overrideDisableClear'); }); - it('should toggle indicator hint on button focus/blur and hide on click', () => { - const header = fixture.componentInstance.defaultA; - const container = fixture.nativeElement.querySelector('#defaultA .mat-sort-header-container'); - const focusEvent = createFakeEvent('focus'); - const blurEvent = createFakeEvent('blur'); - - // Should start without a displayed hint - expect(header._showIndicatorHint).toBeFalsy(); - - // Focusing the button should show the hint, blurring should hide it - container.dispatchEvent(focusEvent); - expect(header._showIndicatorHint).toBeTruthy(); - - container.dispatchEvent(blurEvent); - expect(header._showIndicatorHint).toBeFalsy(); - - // Show the indicator hint. On click the hint should be hidden - container.dispatchEvent(focusEvent); - expect(header._showIndicatorHint).toBeTruthy(); - - header._handleClick(); - expect(header._showIndicatorHint).toBeFalsy(); - }); - - it('should toggle indicator hint on mouseenter/mouseleave and hide on click', () => { - const header = fixture.componentInstance.defaultA; - const headerElement = fixture.nativeElement.querySelector('#defaultA'); - const mouseenterEvent = createMouseEvent('mouseenter'); - const mouseleaveEvent = createMouseEvent('mouseleave'); - - // Should start without a displayed hint - expect(header._showIndicatorHint).toBeFalsy(); - - // Mouse enter should show the hint, blurring should hide it - headerElement.dispatchEvent(mouseenterEvent); - expect(header._showIndicatorHint).toBeTruthy(); - - headerElement.dispatchEvent(mouseleaveEvent); - expect(header._showIndicatorHint).toBeFalsy(); - - // Show the indicator hint. On click the hint should be hidden - headerElement.dispatchEvent(mouseenterEvent); - expect(header._showIndicatorHint).toBeTruthy(); - - header._handleClick(); - expect(header._showIndicatorHint).toBeFalsy(); - }); - it('should apply the aria-sort label to the header when sorted', () => { const sortHeaderElement = fixture.nativeElement.querySelector('#defaultA'); expect(sortHeaderElement.getAttribute('aria-sort')).toBe('none'); @@ -701,27 +538,6 @@ class SimpleMatSortApp { const sortElement = this.elementRef.nativeElement.querySelector(`#${id}`)!; dispatchMouseEvent(sortElement, event); } - - /** - * Checks expectations for each sort header's view state and arrow direction states. Receives a - * map that is keyed by each sort header's ID and contains the expectation for that header's - * states. - */ - expectViewAndDirectionStates( - viewStates: Map, - ) { - const sortHeaders = new Map([ - ['defaultA', this.defaultA], - ['defaultB', this.defaultB], - ['overrideStart', this.overrideStart], - ['overrideDisableClear', this.overrideDisableClear], - ]); - - viewStates.forEach((viewState, id) => { - expect(sortHeaders.get(id)!._getArrowViewState()).toEqual(viewState.viewState); - expect(sortHeaders.get(id)!._getArrowDirectionState()).toEqual(viewState.arrowDirection); - }); - } } class FakeDataSource extends DataSource { diff --git a/tools/public_api_guard/material/sort.md b/tools/public_api_guard/material/sort.md index d1a5a95a515e..78eeabc38186 100644 --- a/tools/public_api_guard/material/sort.md +++ b/tools/public_api_guard/material/sort.md @@ -16,11 +16,12 @@ import { OnDestroy } from '@angular/core'; import { OnInit } from '@angular/core'; import { Optional } from '@angular/core'; import { Subject } from 'rxjs'; +import { WritableSignal } from '@angular/core'; -// @public +// @public @deprecated export type ArrowViewState = SortDirection | 'hint' | 'active'; -// @public +// @public @deprecated export interface ArrowViewStateTransition { // (undocumented) fromState?: ArrowViewState; @@ -81,7 +82,7 @@ export interface MatSortable { start: SortDirection; } -// @public +// @public @deprecated export const matSortAnimations: { readonly indicator: AnimationTriggerMetadata; readonly leftPointer: AnimationTriggerMetadata; @@ -100,18 +101,14 @@ export interface MatSortDefaultOptions { // @public export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewInit { constructor(...args: unknown[]); - _arrowDirection: SortDirection; + // (undocumented) + protected _animationModule: "NoopAnimations" | "BrowserAnimations" | null; arrowPosition: SortHeaderArrowPosition; // (undocumented) _columnDef: MatSortHeaderColumnDef | null; disableClear: boolean; disabled: boolean; - _disableViewStateAnimation: boolean; _getAriaSortAttribute(): "none" | "ascending" | "descending"; - _getArrowDirectionState(): string; - _getArrowViewState(): string; - // (undocumented) - _handleClick(): void; // (undocumented) _handleKeydown(event: KeyboardEvent): void; id: string; @@ -130,18 +127,14 @@ export class MatSortHeader implements MatSortable, OnDestroy, OnInit, AfterViewI ngOnDestroy(): void; // (undocumented) ngOnInit(): void; + protected _recentlyCleared: WritableSignal; _renderArrow(): boolean; - _setAnimationTransitionState(viewState: ArrowViewStateTransition): void; - _setIndicatorHintVisible(visible: boolean): void; - _showIndicatorHint: boolean; // (undocumented) _sort: MatSort; get sortActionDescription(): string; set sortActionDescription(value: string); start: SortDirection; _toggleOnInteraction(): void; - _updateArrowDirection(): void; - _viewState: ArrowViewStateTransition; // (undocumented) static ɵcmp: i0.ɵɵComponentDeclaration; // (undocumented)