@@ -15,25 +15,35 @@ import {
1515 ViewEncapsulation
1616} from '@angular/core' ;
1717import { coerceBooleanProperty } from '@angular/cdk/coercion' ;
18- import {
19- trigger ,
20- state ,
21- style ,
22- animate ,
23- transition ,
24- keyframes ,
25- } from '@angular/animations' ;
18+ import { animate , keyframes , state , style , transition , trigger } from '@angular/animations' ;
2619import { CdkColumnDef } from '@angular/cdk/table' ;
2720import { Subscription } from 'rxjs/Subscription' ;
2821import { merge } from 'rxjs/observable/merge' ;
2922import { MatSort , MatSortable } from './sort' ;
23+ import { SortDirection } from './sort-direction' ;
3024import { MatSortHeaderIntl } from './sort-header-intl' ;
3125import { getSortHeaderNotContainedWithinSortError } from './sort-errors' ;
3226import { AnimationCurves , AnimationDurations } from '@angular/material/core' ;
3327
3428const SORT_ANIMATION_TRANSITION =
3529 AnimationDurations . ENTERING + ' ' + AnimationCurves . STANDARD_CURVE ;
3630
31+ /**
32+ * Valid positions for the arrow to be in for its opacity and translation.
33+ * @docs -private
34+ */
35+ export type ArrowViewState = SortDirection | 'peek' | 'active' ;
36+
37+ /**
38+ * States describing the arrow's animated position (animating fromState -> toState).
39+ * If the fromState is not defined, there will be no animated transition to the toState.
40+ * @docs -private
41+ */
42+ export interface ArrowViewStateTransition {
43+ fromState ?: ArrowViewState ;
44+ toState : ArrowViewState ;
45+ }
46+
3747/**
3848 * Applies sorting behavior (click to change sort) and styles to an element, including an
3949 * arrow to display the current sort direction.
@@ -50,52 +60,135 @@ const SORT_ANIMATION_TRANSITION =
5060 templateUrl : 'sort-header.html' ,
5161 styleUrls : [ 'sort-header.css' ] ,
5262 host : {
53- '(click)' : '_sort.sort(this)' ,
54- '[class.mat-sort-header-sorted]' : '_isSorted()' ,
63+ '(click)' : '_handleClick()' ,
64+ '(mouseenter)' : 'showIndicatorHint = true' ,
65+ '(longpress)' : 'showIndicatorHint = true' ,
66+ '(mouseleave)' : 'showIndicatorHint = false' ,
5567 } ,
5668 encapsulation : ViewEncapsulation . None ,
5769 preserveWhitespaces : false ,
5870 changeDetection : ChangeDetectionStrategy . OnPush ,
5971 animations : [
72+ // Individual arrow parts that show direction
6073 trigger ( 'indicator' , [
61- state ( 'asc' , style ( { transform : 'translateY(0px)' } ) ) ,
74+ state ( 'active-asc, asc' , style ( { transform : 'translateY(0px)' } ) ) ,
6275 // 10px is the height of the sort indicator, minus the width of the pointers
63- state ( 'desc' , style ( { transform : 'translateY(10px)' } ) ) ,
64- transition ( 'asc <=> desc' , animate ( SORT_ANIMATION_TRANSITION ) )
76+ state ( 'active-desc, desc' , style ( { transform : 'translateY(10px)' } ) ) ,
77+ transition ( 'active- asc <=> active- desc' , animate ( SORT_ANIMATION_TRANSITION ) )
6578 ] ) ,
6679 trigger ( 'leftPointer' , [
67- state ( 'asc' , style ( { transform : 'rotate(-45deg)' } ) ) ,
68- state ( 'desc' , style ( { transform : 'rotate(45deg)' } ) ) ,
69- transition ( 'asc <=> desc' , animate ( SORT_ANIMATION_TRANSITION ) )
80+ state ( 'active-asc, asc' , style ( { transform : 'rotate(-45deg)' } ) ) ,
81+ state ( 'active-desc, desc' , style ( { transform : 'rotate(45deg)' } ) ) ,
82+ transition ( 'active- asc <=> active- desc' , animate ( SORT_ANIMATION_TRANSITION ) )
7083 ] ) ,
7184 trigger ( 'rightPointer' , [
72- state ( 'asc' , style ( { transform : 'rotate(45deg)' } ) ) ,
73- state ( 'desc' , style ( { transform : 'rotate(-45deg)' } ) ) ,
74- transition ( 'asc <=> desc' , animate ( SORT_ANIMATION_TRANSITION ) )
85+ state ( 'active-asc, asc' , style ( { transform : 'rotate(45deg)' } ) ) ,
86+ state ( 'active-desc, desc' , style ( { transform : 'rotate(-45deg)' } ) ) ,
87+ transition ( 'active-asc <=> active-desc' , animate ( SORT_ANIMATION_TRANSITION ) )
88+ ] ) ,
89+
90+ // Arrow opacity
91+ trigger ( 'arrowOpacity' , [
92+ state ( 'desc-to-active, asc-to-active, active' ,
93+ style ( { opacity : 1 } ) ) ,
94+ state ( 'desc-to-peek, asc-to-peek, peek' ,
95+ style ( { opacity : .54 } ) ) ,
96+ state ( 'peek-to-desc, active-to-desc, desc, peek-to-asc, active-to-asc, asc' ,
97+ style ( { opacity : 0 } ) ) ,
98+ // Transition between all states except for immediate transitions
99+ transition ( '* => asc, * => desc, * => active, * => peek' , animate ( '0ms' ) ) ,
100+ transition ( '* <=> *' , animate ( SORT_ANIMATION_TRANSITION ) )
75101 ] ) ,
76- trigger ( 'indicatorToggle' , [
77- transition ( 'void => asc' , animate ( SORT_ANIMATION_TRANSITION , keyframes ( [
78- style ( { transform : 'translateY(25%)' , opacity : 0 } ) ,
79- style ( { transform : 'none' , opacity : 1 } )
102+
103+ // Translation of the arrow as a whole. States are separated into two groups: ones with
104+ // animations and others that are immediate. Immediate states are asc, desc, peek, and active.
105+ // The other states define a specific animation (source-to-destination) and are determined as
106+ // a function of their prev user-perceived state and what the next state should be.
107+ trigger ( 'arrowPosition' , [
108+ // Hidden Above => Peek Center
109+ transition ( '* => desc-to-peek, * => desc-to-active' ,
110+ animate ( SORT_ANIMATION_TRANSITION , keyframes ( [
111+ style ( { transform : 'translateY(-25%)' } ) ,
112+ style ( { transform : 'translateY(0)' } )
80113 ] ) ) ) ,
81- transition ( 'asc => void' , animate ( SORT_ANIMATION_TRANSITION , keyframes ( [
82- style ( { transform : 'none' , opacity : 1 } ) ,
83- style ( { transform : 'translateY(-25%)' , opacity : 0 } )
114+ // Peek Center => Hidden Below
115+ transition ( '* => peek-to-desc, * => active-to-desc' ,
116+ animate ( SORT_ANIMATION_TRANSITION , keyframes ( [
117+ style ( { transform : 'translateY(0)' } ) ,
118+ style ( { transform : 'translateY(25%)' } )
84119 ] ) ) ) ,
85- transition ( 'void => desc' , animate ( SORT_ANIMATION_TRANSITION , keyframes ( [
86- style ( { transform : 'translateY(-25%)' , opacity : 0 } ) ,
87- style ( { transform : 'none' , opacity : 1 } )
120+ // Hidden Below => Peek Center
121+ transition ( '* => asc-to-peek, * => asc-to-active' ,
122+ animate ( SORT_ANIMATION_TRANSITION , keyframes ( [
123+ style ( { transform : 'translateY(25%)' } ) ,
124+ style ( { transform : 'translateY(0)' } )
88125 ] ) ) ) ,
89- transition ( 'desc => void' , animate ( SORT_ANIMATION_TRANSITION , keyframes ( [
90- style ( { transform : 'none' , opacity : 1 } ) ,
91- style ( { transform : 'translateY(25%)' , opacity : 0 } )
126+ // Peek Center => Hidden Above
127+ transition ( '* => peek-to-asc, * => active-to-asc' ,
128+ animate ( SORT_ANIMATION_TRANSITION , keyframes ( [
129+ style ( { transform : 'translateY(0)' } ) ,
130+ style ( { transform : 'translateY(-25%)' } )
92131 ] ) ) ) ,
132+ state ( 'desc-to-peek, asc-to-peek, peek, desc-to-active, asc-to-active, active' ,
133+ style ( { transform : 'translateY(0)' } ) ) ,
134+ state ( 'peek-to-desc, active-to-desc, desc' ,
135+ style ( { transform : 'translateY(-25%)' } ) ) ,
136+ state ( 'peek-to-asc, active-to-asc, asc' ,
137+ style ( { transform : 'translateY(25%)' } ) ) ,
93138 ] )
94139 ]
95140} )
96141export class MatSortHeader implements MatSortable {
97142 private _rerenderSubscription : Subscription ;
98143
144+ /**
145+ * Flag set to true when the indicator should be displayed while the sort is not active. Used to
146+ * provide an affordance that the header is sortable by showing on focus and hover.
147+ * @docs -private
148+ */
149+ set showIndicatorHint ( showIndicatorHint : boolean ) {
150+ this . _showIndicatorHint = showIndicatorHint ;
151+
152+ if ( ! this . _isSorted ( ) ) {
153+ this . _updateArrowDirection ( ) ;
154+ if ( this . showIndicatorHint ) {
155+ this . viewState = { fromState : this . _arrowDirection , toState : 'peek' } ;
156+ } else {
157+ this . viewState = { fromState : 'peek' , toState : this . _arrowDirection } ;
158+ }
159+ }
160+ }
161+ get showIndicatorHint ( ) : boolean { return this . _showIndicatorHint ; }
162+ _showIndicatorHint : boolean = false ;
163+
164+ /**
165+ * The view transition state of the arrow (translation/ opacity) - indicates its `from` and `to`
166+ * position through the animation. If animations are currently disabled, the fromState is removed
167+ * so that there is no animation displayed.
168+ * @docs -private
169+ */
170+ set viewState ( arrowPosition : ArrowViewStateTransition ) {
171+ this . _viewState = arrowPosition ;
172+
173+ // If the animation for arrow position state (opacity/translation) should be disabled,
174+ // remove the fromState so that it jumps right to the toState.
175+ if ( this . _disableViewStateAnimation ) {
176+ this . _viewState = { toState : arrowPosition . toState } ;
177+ }
178+ }
179+ get viewState ( ) : ArrowViewStateTransition {
180+ return this . _viewState ;
181+ }
182+ _viewState : ArrowViewStateTransition ;
183+
184+ /** The direction the arrow should be facing according to the current state. */
185+ _arrowDirection : SortDirection = '' ;
186+
187+ /**
188+ * Whether the view state animation should show the transition between the `from` and `to` states.
189+ */
190+ _disableViewStateAnimation = false ;
191+
99192 /**
100193 * ID of this sort header. If used within the context of a CdkColumnDef, this will default to
101194 * the column's name.
@@ -123,6 +216,16 @@ export class MatSortHeader implements MatSortable {
123216 }
124217
125218 this . _rerenderSubscription = merge ( _sort . sortChange , _intl . changes ) . subscribe ( ( ) => {
219+ if ( this . _isSorted ( ) ) {
220+ this . _updateArrowDirection ( ) ;
221+ }
222+
223+ // If this header was recently active and now no longer sorted, animate away the arrow.
224+ if ( ! this . _isSorted ( ) && this . viewState . toState === 'active' ) {
225+ this . _disableViewStateAnimation = false ;
226+ this . viewState = { fromState : 'active' , toState : this . _arrowDirection } ;
227+ }
228+
126229 changeDetectorRef . markForCheck ( ) ;
127230 } ) ;
128231 }
@@ -132,6 +235,10 @@ export class MatSortHeader implements MatSortable {
132235 this . id = this . _cdkColumnDef . name ;
133236 }
134237
238+ // Initialize the direction of the arrow and set the view state to be immediately that state.
239+ this . _updateArrowDirection ( ) ;
240+ this . viewState = { toState : this . _arrowDirection } ;
241+
135242 this . _sort . register ( this ) ;
136243 }
137244
@@ -140,9 +247,49 @@ export class MatSortHeader implements MatSortable {
140247 this . _rerenderSubscription . unsubscribe ( ) ;
141248 }
142249
250+ /** Triggers the sort on this sort header and removes the indicator hint. */
251+ _handleClick ( ) {
252+ this . _sort . sort ( this ) ;
253+
254+ // Do not show the animation if the header was already shown in the right position.
255+ if ( this . viewState . toState === 'peek' || this . viewState . toState === 'active' ) {
256+ this . _disableViewStateAnimation = true ;
257+ }
258+
259+ // If the arrow is now sorted, animate the arrow into place. Otherwise, animate it away into
260+ // the direction it is facing.
261+ this . viewState = this . _isSorted ( ) ?
262+ { fromState : this . _arrowDirection , toState : 'active' } :
263+ { fromState : 'active' , toState : this . _arrowDirection } ;
264+
265+ this . _showIndicatorHint = false ;
266+ }
267+
143268 /** Whether this MatSortHeader is currently sorted in either ascending or descending order. */
144269 _isSorted ( ) {
145270 return this . _sort . active == this . id &&
146271 ( this . _sort . direction === 'asc' || this . _sort . direction === 'desc' ) ;
147272 }
273+
274+ /** Returns the animation state for the arrow direction (indicator and pointers). */
275+ _getArrowDirectionState ( ) {
276+ return `${ this . _isSorted ( ) ? 'active-' : '' } ${ this . _arrowDirection } ` ;
277+ }
278+
279+ /** Returns the arrow position state (opacity, translation). */
280+ _getArrowViewState ( ) {
281+ const fromState = this . viewState . fromState ;
282+ return ( fromState ? `${ fromState } -to-` : '' ) + this . viewState . toState ;
283+ }
284+
285+ /**
286+ * Updates the direction the arrow should be pointing. If it is not sorted, the arrow should be
287+ * facing the start direction. Otherwise if it is sorted, the arrow should point in the currently
288+ * active sorted direction.
289+ */
290+ _updateArrowDirection ( ) {
291+ this . _arrowDirection = this . _isSorted ( ) ?
292+ this . _sort . direction :
293+ ( this . start || this . _sort . start ) ;
294+ }
148295}
0 commit comments