66 * found in the LICENSE file at https://angular.io/license
77 */
88
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+ */
1013export type StickyDirection = 'top' | 'bottom' | 'left' | 'right' ;
1114
1215/**
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
1718 */
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' ] ;
2420
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+ */
2625export 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 ) { }
2833
2934 /**
3035 * Clears the sticky positioning styles from the row and its cells by resetting the `position`
3136 * 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.
3239 */
33- clearStickyPositioningStyles ( rows : HTMLElement [ ] , stickyDirections : StickyDirection [ ] ) {
34- rows . forEach ( row => {
40+ clearStickyPositioning ( rows : HTMLElement [ ] , stickyDirections : StickyDirection [ ] ) {
41+ for ( let row of rows ) {
3542 this . _removeStickyStyle ( row , stickyDirections ) ;
3643 for ( let i = 0 ; i < row . children . length ; i ++ ) {
3744 const cell = row . children [ i ] as HTMLElement ;
3845 this . _removeStickyStyle ( cell , stickyDirections ) ;
3946 }
40- } ) ;
47+ }
4148 }
4249
4350 /**
4451 * Applies sticky left and right positions to the cells of each row according to the sticky
4552 * 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.
4658 */
4759 updateStickyColumns (
48- rows : HTMLElement [ ] , stickyLeftStates : boolean [ ] , stickyRightStates : boolean [ ] ) {
60+ rows : HTMLElement [ ] , stickyStartStates : boolean [ ] , stickyEndStates : boolean [ ] ) {
4961 const hasStickyColumns =
50- stickyLeftStates . some ( state => state ) || stickyRightStates . some ( state => state ) ;
62+ stickyStartStates . some ( state => state ) || stickyEndStates . some ( state => state ) ;
5163 if ( ! rows . length || ! hasStickyColumns ) {
5264 return ;
5365 }
5466
67+ const numCells = rows [ 0 ] . children . length ;
5568 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 ) ;
5871
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 ++ ) {
6174 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 ] ) ;
6477 }
6578
66- if ( stickyRightStates [ i ] ) {
67- this . _addStickyStyle ( cell , 'right' , rightPositions [ i ] ) ;
79+ if ( stickyEndStates [ i ] ) {
80+ this . _addStickyStyle ( cell , 'right' , endPositions [ i ] ) ;
6881 }
6982 }
70- } ) ;
83+ }
7184 }
7285
7386 /**
7487 * Applies sticky positioning to the row's cells if using the native table layout, and to the
7588 * 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+ *
7696 */
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 ;
83101
84102 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 ;
88106 }
89107
90- if ( this . usesNativeHtmlTable ) {
108+ const row = rows [ rowIndex ] ;
109+ if ( this . isNativeHtmlTable ) {
91110 for ( let j = 0 ; j < row . children . length ; j ++ ) {
92111 const cell = row . children [ j ] as HTMLElement ;
93112 this . _addStickyStyle ( cell , position , stickyHeight ) ;
@@ -99,7 +118,7 @@ export class StickyStyler {
99118 }
100119
101120 stickyHeight += row . getBoundingClientRect ( ) . height ;
102- } ) ;
121+ }
103122 }
104123
105124 /**
@@ -109,7 +128,7 @@ export class StickyStyler {
109128 * the tfoot element.
110129 */
111130 updateStickyFooterContainer ( tableElement : Element , stickyStates : boolean [ ] ) {
112- if ( ! this . usesNativeHtmlTable ) {
131+ if ( ! this . isNativeHtmlTable ) {
113132 return ;
114133 }
115134
@@ -127,15 +146,17 @@ export class StickyStyler {
127146 * sticky position if there are no more directions.
128147 */
129148 _removeStickyStyle ( element : HTMLElement , stickyDirections : StickyDirection [ ] ) {
130- stickyDirections . forEach ( dir => element . style [ dir ] = '' ) ;
149+ for ( let dir of stickyDirections ) {
150+ element . style [ dir ] = '' ;
151+ }
131152 element . style . zIndex = this . _getCalculatedZIndex ( element ) ;
132153
133154 // If the element no longer has any more sticky directions, remove sticky positioning and
134155 // 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 ] ) ;
136157 if ( ! hasDirection ) {
137158 element . style . position = '' ;
138- element . classList . remove ( this . stickyCellCSS ) ;
159+ element . classList . remove ( this . stickCellCSS ) ;
139160 }
140161 }
141162
@@ -145,18 +166,22 @@ export class StickyStyler {
145166 * direction and value.
146167 */
147168 _addStickyStyle ( element : HTMLElement , dir : StickyDirection , dirValue : number ) {
148- element . classList . add ( this . stickyCellCSS ) ;
169+ element . classList . add ( this . stickCellCSS ) ;
149170 element . style [ dir ] = `${ dirValue } px` ;
150171 element . style . cssText += 'position: -webkit-sticky; position: sticky; ' ;
151172 element . style . zIndex = this . _getCalculatedZIndex ( element ) ;
152173 }
153174
154175 /**
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.
160185 */
161186 _getCalculatedZIndex ( element : HTMLElement ) : string {
162187 const zIndexIncrements = {
@@ -167,13 +192,13 @@ export class StickyStyler {
167192 } ;
168193
169194 let zIndex = 0 ;
170- [ 'top' , 'bottom' , 'left' , 'right' ] . forEach ( dir => {
195+ for ( let dir of STICKY_DIRECTIONS ) {
171196 if ( element . style [ dir ] ) {
172197 zIndex += zIndexIncrements [ dir ] ;
173198 }
174- } ) ;
199+ }
175200
176- return String ( zIndex ) ;
201+ return ` ${ zIndex } ` ;
177202 }
178203
179204 /** Gets the widths for each cell in the provided row. */
@@ -193,7 +218,7 @@ export class StickyStyler {
193218 * accumulation of all sticky column cell widths to the left and right, respectively.
194219 * Non-sticky cells do not need to have a value set since their positions will not be applied.
195220 */
196- _getStickyLeftColumnPositions ( widths : number [ ] , stickyStates : boolean [ ] ) : number [ ] {
221+ _getStickyStartColumnPositions ( widths : number [ ] , stickyStates : boolean [ ] ) : number [ ] {
197222 const positions : number [ ] = [ ] ;
198223 let nextPosition = 0 ;
199224
@@ -212,7 +237,7 @@ export class StickyStyler {
212237 * accumulation of all sticky column cell widths to the left and right, respectively.
213238 * Non-sticky cells do not need to have a value set since their positions will not be applied.
214239 */
215- _getStickyRightColumnPositions ( widths : number [ ] , stickyStates : boolean [ ] ) : number [ ] {
240+ _getStickyEndColumnPositions ( widths : number [ ] , stickyStates : boolean [ ] ) : number [ ] {
216241 const positions : number [ ] = [ ] ;
217242 let nextPosition = 0 ;
218243
0 commit comments