6
6
* found in the LICENSE file at https://angular.io/license
7
7
*/
8
8
9
- /** Directions that can be used when setting sticky positioning. */
9
+ /**
10
+ * Directions that can be used when setting sticky positioning.
11
+ * @docs -private
12
+ */
10
13
export type StickyDirection = 'top' | 'bottom' | 'left' | 'right' ;
11
14
12
15
/**
13
- * Z-index values that should be used when sticking row cells to the top and bottom. If one of those
14
- * cells are also stuck to the left or right, their z-index should be incremented by one. In doing
15
- * this, it is guaranteed that header cells will always cover footer cells, and both will always
16
- * cover data rows.
16
+ * List of all possible directions that can be used for sticky positioning.
17
+ * @docs -private
17
18
*/
18
- export enum StickyRowZIndex {
19
- Top = 100 ,
20
- Bottom = 10 ,
21
- Left = 1 ,
22
- Right = 1 ,
23
- }
19
+ export const STICKY_DIRECTIONS : StickyDirection [ ] = [ 'top' , 'bottom' , 'left' , 'right' ] ;
24
20
25
- /** Applies and removes sticky positioning styles to the `CdkTable` rows and columns cells. */
21
+ /**
22
+ * Applies and removes sticky positioning styles to the `CdkTable` rows and columns cells.
23
+ * @docs -private
24
+ */
26
25
export class StickyStyler {
27
- constructor ( private usesNativeHtmlTable : boolean , private stickyCellCSS : string ) { }
26
+ /**
27
+ * @param isNativeHtmlTable Whether the sticky logic should be based on a table
28
+ * that uses the native `<table>` element.
29
+ * @param stickCellCSS The CSS class that will be applied to every row/cell that has
30
+ * sticky positioning applied.
31
+ */
32
+ constructor ( private isNativeHtmlTable : boolean , private stickCellCSS : string ) { }
28
33
29
34
/**
30
35
* Clears the sticky positioning styles from the row and its cells by resetting the `position`
31
36
* style, setting the zIndex to 0, and unsetting each provided sticky direction.
37
+ * @param rows The list of rows that should be cleared from sticking in the provided directions
38
+ * @param stickyDirections The directions that should no longer be set as sticky on the rows.
32
39
*/
33
- clearStickyPositioningStyles ( rows : HTMLElement [ ] , stickyDirections : StickyDirection [ ] ) {
34
- rows . forEach ( row => {
40
+ clearStickyPositioning ( rows : HTMLElement [ ] , stickyDirections : StickyDirection [ ] ) {
41
+ for ( let row of rows ) {
35
42
this . _removeStickyStyle ( row , stickyDirections ) ;
36
43
for ( let i = 0 ; i < row . children . length ; i ++ ) {
37
44
const cell = row . children [ i ] as HTMLElement ;
38
45
this . _removeStickyStyle ( cell , stickyDirections ) ;
39
46
}
40
- } ) ;
47
+ }
41
48
}
42
49
43
50
/**
44
51
* Applies sticky left and right positions to the cells of each row according to the sticky
45
52
* states of the rendered column definitions.
53
+ * @param rows The rows that should have its set of cells stuck according to the sticky states.
54
+ * @param stickyStartStates A list of boolean states where each state represents whether the cell
55
+ * in this index position should be stuck to the start of the row.
56
+ * @param stickyEndStates A list of boolean states where each state represents whether the cell
57
+ * in this index position should be stuck to the end of the row.
46
58
*/
47
59
updateStickyColumns (
48
- rows : HTMLElement [ ] , stickyLeftStates : boolean [ ] , stickyRightStates : boolean [ ] ) {
60
+ rows : HTMLElement [ ] , stickyStartStates : boolean [ ] , stickyEndStates : boolean [ ] ) {
49
61
const hasStickyColumns =
50
- stickyLeftStates . some ( state => state ) || stickyRightStates . some ( state => state ) ;
62
+ stickyStartStates . some ( state => state ) || stickyEndStates . some ( state => state ) ;
51
63
if ( ! rows . length || ! hasStickyColumns ) {
52
64
return ;
53
65
}
54
66
67
+ const numCells = rows [ 0 ] . children . length ;
55
68
const cellWidths : number [ ] = this . _getCellWidths ( rows [ 0 ] ) ;
56
- const leftPositions = this . _getStickyLeftColumnPositions ( cellWidths , stickyLeftStates ) ;
57
- const rightPositions = this . _getStickyRightColumnPositions ( cellWidths , stickyRightStates ) ;
69
+ const startPositions = this . _getStickyStartColumnPositions ( cellWidths , stickyStartStates ) ;
70
+ const endPositions = this . _getStickyEndColumnPositions ( cellWidths , stickyEndStates ) ;
58
71
59
- rows . forEach ( row => {
60
- for ( let i = 0 ; i < row . children . length ; i ++ ) {
72
+ for ( let row of rows ) {
73
+ for ( let i = 0 ; i < numCells ; i ++ ) {
61
74
const cell = row . children [ i ] as HTMLElement ;
62
- if ( stickyLeftStates [ i ] ) {
63
- this . _addStickyStyle ( cell , 'left' , leftPositions [ i ] ) ;
75
+ if ( stickyStartStates [ i ] ) {
76
+ this . _addStickyStyle ( cell , 'left' , startPositions [ i ] ) ;
64
77
}
65
78
66
- if ( stickyRightStates [ i ] ) {
67
- this . _addStickyStyle ( cell , 'right' , rightPositions [ i ] ) ;
79
+ if ( stickyEndStates [ i ] ) {
80
+ this . _addStickyStyle ( cell , 'right' , endPositions [ i ] ) ;
68
81
}
69
82
}
70
- } ) ;
83
+ }
71
84
}
72
85
73
86
/**
74
87
* Applies sticky positioning to the row's cells if using the native table layout, and to the
75
88
* row itself otherwise.
89
+ * @param rowsToStick The list of rows that should be stuck according to their corresponding
90
+ * sticky state and to the provided top or bottom position.
91
+ * @param stickyStates A list of boolean states where each state represents whether the row
92
+ * should be stuck in the particular top or bottom position.
93
+ * @param position The position direction in which the row should be stuck if that row should be
94
+ * sticky.
95
+ *
76
96
*/
77
- stickRows ( rows : HTMLElement [ ] , stickyStates : boolean [ ] , position : 'top' | 'bottom' ) {
78
- // Bottom-positions rows should stick in reverse order
79
- // (e.g. last stuck item will be bottom: 0px)
80
- if ( position === 'bottom' ) {
81
- rows = rows . reverse ( ) ;
82
- }
97
+ stickRows ( rowsToStick : HTMLElement [ ] , stickyStates : boolean [ ] , position : 'top' | 'bottom' ) {
98
+ // If positioning the rows to the bottom, reverse their order when evaluating the sticky
99
+ // position such that the last row stuck will be "bottom: 0px" and so on.
100
+ const rows = position === 'bottom' ? rowsToStick . reverse ( ) : rowsToStick ;
83
101
84
102
let stickyHeight = 0 ;
85
- rows . forEach ( ( row , i ) => {
86
- if ( ! stickyStates [ i ] ) {
87
- return ;
103
+ for ( let rowIndex = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
104
+ if ( ! stickyStates [ rowIndex ] ) {
105
+ continue ;
88
106
}
89
107
90
- if ( this . usesNativeHtmlTable ) {
108
+ const row = rows [ rowIndex ] ;
109
+ if ( this . isNativeHtmlTable ) {
91
110
for ( let j = 0 ; j < row . children . length ; j ++ ) {
92
111
const cell = row . children [ j ] as HTMLElement ;
93
112
this . _addStickyStyle ( cell , position , stickyHeight ) ;
@@ -99,7 +118,7 @@ export class StickyStyler {
99
118
}
100
119
101
120
stickyHeight += row . getBoundingClientRect ( ) . height ;
102
- } ) ;
121
+ }
103
122
}
104
123
105
124
/**
@@ -109,7 +128,7 @@ export class StickyStyler {
109
128
* the tfoot element.
110
129
*/
111
130
updateStickyFooterContainer ( tableElement : Element , stickyStates : boolean [ ] ) {
112
- if ( ! this . usesNativeHtmlTable ) {
131
+ if ( ! this . isNativeHtmlTable ) {
113
132
return ;
114
133
}
115
134
@@ -127,15 +146,17 @@ export class StickyStyler {
127
146
* sticky position if there are no more directions.
128
147
*/
129
148
_removeStickyStyle ( element : HTMLElement , stickyDirections : StickyDirection [ ] ) {
130
- stickyDirections . forEach ( dir => element . style [ dir ] = '' ) ;
149
+ for ( let dir of stickyDirections ) {
150
+ element . style [ dir ] = '' ;
151
+ }
131
152
element . style . zIndex = this . _getCalculatedZIndex ( element ) ;
132
153
133
154
// If the element no longer has any more sticky directions, remove sticky positioning and
134
155
// the sticky CSS class.
135
- const hasDirection = [ 'top' , 'bottom' , 'left' , 'right' ] . some ( dir => element . style [ dir ] ) ;
156
+ const hasDirection = STICKY_DIRECTIONS . some ( dir => ! ! element . style [ dir ] ) ;
136
157
if ( ! hasDirection ) {
137
158
element . style . position = '' ;
138
- element . classList . remove ( this . stickyCellCSS ) ;
159
+ element . classList . remove ( this . stickCellCSS ) ;
139
160
}
140
161
}
141
162
@@ -145,18 +166,22 @@ export class StickyStyler {
145
166
* direction and value.
146
167
*/
147
168
_addStickyStyle ( element : HTMLElement , dir : StickyDirection , dirValue : number ) {
148
- element . classList . add ( this . stickyCellCSS ) ;
169
+ element . classList . add ( this . stickCellCSS ) ;
149
170
element . style [ dir ] = `${ dirValue } px` ;
150
171
element . style . cssText += 'position: -webkit-sticky; position: sticky; ' ;
151
172
element . style . zIndex = this . _getCalculatedZIndex ( element ) ;
152
173
}
153
174
154
175
/**
155
- * Calculate what the z-index should be for the element depending on the sticky directions styles
156
- * that it has set. It should be the case that elements with a top direction should always be at
157
- * the forefront, followed by bottom direction elements. Finally, anything with left or right
158
- * direction should come behind those. All else should be the lowest and not have any increased
159
- * z-index.
176
+ * Calculate what the z-index should be for the element, depending on what directions (top,
177
+ * bottom, left, right) have been set. It should be true that elements with a top direction
178
+ * should have the highest index since these are elements like a table header. If any of those
179
+ * elements are also sticky in another direction, then they should appear above other elements
180
+ * that are only sticky top (e.g. a sticky column on a sticky header). Bottom-sticky elements
181
+ * (e.g. footer rows) should then be next in the ordering such that they are below the header
182
+ * but above any non-sticky elements. Finally, left/right sticky elements (e.g. sticky columns)
183
+ * should minimally increment so that they are above non-sticky elements but below top and bottom
184
+ * elements.
160
185
*/
161
186
_getCalculatedZIndex ( element : HTMLElement ) : string {
162
187
const zIndexIncrements = {
@@ -167,13 +192,13 @@ export class StickyStyler {
167
192
} ;
168
193
169
194
let zIndex = 0 ;
170
- [ 'top' , 'bottom' , 'left' , 'right' ] . forEach ( dir => {
195
+ for ( let dir of STICKY_DIRECTIONS ) {
171
196
if ( element . style [ dir ] ) {
172
197
zIndex += zIndexIncrements [ dir ] ;
173
198
}
174
- } ) ;
199
+ }
175
200
176
- return String ( zIndex ) ;
201
+ return ` ${ zIndex } ` ;
177
202
}
178
203
179
204
/** Gets the widths for each cell in the provided row. */
@@ -193,7 +218,7 @@ export class StickyStyler {
193
218
* accumulation of all sticky column cell widths to the left and right, respectively.
194
219
* Non-sticky cells do not need to have a value set since their positions will not be applied.
195
220
*/
196
- _getStickyLeftColumnPositions ( widths : number [ ] , stickyStates : boolean [ ] ) : number [ ] {
221
+ _getStickyStartColumnPositions ( widths : number [ ] , stickyStates : boolean [ ] ) : number [ ] {
197
222
const positions : number [ ] = [ ] ;
198
223
let nextPosition = 0 ;
199
224
@@ -212,7 +237,7 @@ export class StickyStyler {
212
237
* accumulation of all sticky column cell widths to the left and right, respectively.
213
238
* Non-sticky cells do not need to have a value set since their positions will not be applied.
214
239
*/
215
- _getStickyRightColumnPositions ( widths : number [ ] , stickyStates : boolean [ ] ) : number [ ] {
240
+ _getStickyEndColumnPositions ( widths : number [ ] , stickyStates : boolean [ ] ) : number [ ] {
216
241
const positions : number [ ] = [ ] ;
217
242
let nextPosition = 0 ;
218
243
0 commit comments