1111 * @docs -private
1212 */
1313import { Direction } from '@angular/cdk/bidi' ;
14+ import { _CoalescedStyleScheduler } from './coalesced-style-scheduler' ;
1415
1516export type StickyDirection = 'top' | 'bottom' | 'left' | 'right' ;
1617
@@ -37,6 +38,7 @@ export class StickyStyler {
3738 constructor ( private _isNativeHtmlTable : boolean ,
3839 private _stickCellCss : string ,
3940 public direction : Direction ,
41+ private _coalescedStyleScheduler : _CoalescedStyleScheduler ,
4042 private _isBrowser = true ) { }
4143
4244 /**
@@ -46,20 +48,26 @@ export class StickyStyler {
4648 * @param stickyDirections The directions that should no longer be set as sticky on the rows.
4749 */
4850 clearStickyPositioning ( rows : HTMLElement [ ] , stickyDirections : StickyDirection [ ] ) {
51+ const elementsToClear : HTMLElement [ ] = [ ] ;
4952 for ( const row of rows ) {
5053 // If the row isn't an element (e.g. if it's an `ng-container`),
5154 // it won't have inline styles or `children` so we skip it.
5255 if ( row . nodeType !== row . ELEMENT_NODE ) {
5356 continue ;
5457 }
5558
56- this . _removeStickyStyle ( row , stickyDirections ) ;
57-
59+ elementsToClear . push ( row ) ;
5860 for ( let i = 0 ; i < row . children . length ; i ++ ) {
59- const cell = row . children [ i ] as HTMLElement ;
60- this . _removeStickyStyle ( cell , stickyDirections ) ;
61+ elementsToClear . push ( row . children [ i ] as HTMLElement ) ;
6162 }
6263 }
64+
65+ // Coalesce with sticky row/column updates (and potentially other changes like column resize).
66+ this . _coalescedStyleScheduler . schedule ( ( ) => {
67+ for ( const element of elementsToClear ) {
68+ this . _removeStickyStyle ( element , stickyDirections ) ;
69+ }
70+ } ) ;
6371 }
6472
6573 /**
@@ -73,9 +81,8 @@ export class StickyStyler {
7381 */
7482 updateStickyColumns (
7583 rows : HTMLElement [ ] , stickyStartStates : boolean [ ] , stickyEndStates : boolean [ ] ) {
76- const hasStickyColumns =
77- stickyStartStates . some ( state => state ) || stickyEndStates . some ( state => state ) ;
78- if ( ! rows . length || ! hasStickyColumns || ! this . _isBrowser ) {
84+ if ( ! rows . length || ! this . _isBrowser || ! ( stickyStartStates . some ( state => state ) ||
85+ stickyEndStates . some ( state => state ) ) ) {
7986 return ;
8087 }
8188
@@ -85,20 +92,26 @@ export class StickyStyler {
8592
8693 const startPositions = this . _getStickyStartColumnPositions ( cellWidths , stickyStartStates ) ;
8794 const endPositions = this . _getStickyEndColumnPositions ( cellWidths , stickyEndStates ) ;
88- const isRtl = this . direction === 'rtl' ;
89-
90- for ( const row of rows ) {
91- for ( let i = 0 ; i < numCells ; i ++ ) {
92- const cell = row . children [ i ] as HTMLElement ;
93- if ( stickyStartStates [ i ] ) {
94- this . _addStickyStyle ( cell , isRtl ? 'right' : 'left' , startPositions [ i ] ) ;
95- }
9695
97- if ( stickyEndStates [ i ] ) {
98- this . _addStickyStyle ( cell , isRtl ? 'left' : 'right' , endPositions [ i ] ) ;
96+ // Coalesce with sticky row updates (and potentially other changes like column resize).
97+ this . _coalescedStyleScheduler . schedule ( ( ) => {
98+ const isRtl = this . direction === 'rtl' ;
99+ const start = isRtl ? 'right' : 'left' ;
100+ const end = isRtl ? 'left' : 'right' ;
101+
102+ for ( const row of rows ) {
103+ for ( let i = 0 ; i < numCells ; i ++ ) {
104+ const cell = row . children [ i ] as HTMLElement ;
105+ if ( stickyStartStates [ i ] ) {
106+ this . _addStickyStyle ( cell , start , startPositions [ i ] ) ;
107+ }
108+
109+ if ( stickyEndStates [ i ] ) {
110+ this . _addStickyStyle ( cell , end , endPositions [ i ] ) ;
111+ }
99112 }
100113 }
101- }
114+ } ) ;
102115 }
103116
104117 /**
@@ -124,30 +137,39 @@ export class StickyStyler {
124137 const rows = position === 'bottom' ? rowsToStick . slice ( ) . reverse ( ) : rowsToStick ;
125138 const states = position === 'bottom' ? stickyStates . slice ( ) . reverse ( ) : stickyStates ;
126139
127- let stickyHeight = 0 ;
128- for ( let rowIndex = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
140+ // Measure row heights all at once before adding sticky styles to reduce layout thrashing.
141+ const stickyHeights : number [ ] = [ ] ;
142+ const elementsToStick : HTMLElement [ ] [ ] = [ ] ;
143+ for ( let rowIndex = 0 , stickyHeight = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
144+ stickyHeights [ rowIndex ] = stickyHeight ;
145+
129146 if ( ! states [ rowIndex ] ) {
130147 continue ;
131148 }
132149
133150 const row = rows [ rowIndex ] ;
134- if ( this . _isNativeHtmlTable ) {
135- for ( let j = 0 ; j < row . children . length ; j ++ ) {
136- const cell = row . children [ j ] as HTMLElement ;
137- this . _addStickyStyle ( cell , position , stickyHeight ) ;
138- }
139- } else {
140- // Flex does not respect the stick positioning on the cells, needs to be applied to the row.
141- // If this is applied on a native table, Safari causes the header to fly in wrong direction.
142- this . _addStickyStyle ( row , position , stickyHeight ) ;
143- }
151+ elementsToStick [ rowIndex ] = this . _isNativeHtmlTable ?
152+ Array . from ( row . children ) as HTMLElement [ ] : [ row ] ;
144153
145- if ( rowIndex === rows . length - 1 ) {
146- // prevent unnecessary reflow from getBoundingClientRect()
147- return ;
154+ if ( rowIndex !== rows . length - 1 ) {
155+ stickyHeight += row . getBoundingClientRect ( ) . height ;
148156 }
149- stickyHeight += row . getBoundingClientRect ( ) . height ;
150157 }
158+
159+ // Coalesce with other sticky row updates (top/bottom), sticky columns updates
160+ // (and potentially other changes like column resize).
161+ this . _coalescedStyleScheduler . schedule ( ( ) => {
162+ for ( let rowIndex = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
163+ if ( ! states [ rowIndex ] ) {
164+ continue ;
165+ }
166+
167+ const height = stickyHeights [ rowIndex ] ;
168+ for ( const element of elementsToStick [ rowIndex ] ) {
169+ this . _addStickyStyle ( element , position , height ) ;
170+ }
171+ }
172+ } ) ;
151173 }
152174
153175 /**
@@ -162,11 +184,15 @@ export class StickyStyler {
162184 }
163185
164186 const tfoot = tableElement . querySelector ( 'tfoot' ) ! ;
165- if ( stickyStates . some ( state => ! state ) ) {
166- this . _removeStickyStyle ( tfoot , [ 'bottom' ] ) ;
167- } else {
168- this . _addStickyStyle ( tfoot , 'bottom' , 0 ) ;
169- }
187+
188+ // Coalesce with other sticky updates (and potentially other changes like column resize).
189+ this . _coalescedStyleScheduler . schedule ( ( ) => {
190+ if ( stickyStates . some ( state => ! state ) ) {
191+ this . _removeStickyStyle ( tfoot , [ 'bottom' ] ) ;
192+ } else {
193+ this . _addStickyStyle ( tfoot , 'bottom' , 0 ) ;
194+ }
195+ } ) ;
170196 }
171197
172198 /**
0 commit comments