11
11
* @docs -private
12
12
*/
13
13
import { Direction } from '@angular/cdk/bidi' ;
14
+ import { CoalescedStyleScheduler } from './coalesced-style-scheduler' ;
14
15
15
16
export type StickyDirection = 'top' | 'bottom' | 'left' | 'right' ;
16
17
@@ -37,6 +38,7 @@ export class StickyStyler {
37
38
constructor ( private _isNativeHtmlTable : boolean ,
38
39
private _stickCellCss : string ,
39
40
public direction : Direction ,
41
+ private _coalescedStyleScheduler : CoalescedStyleScheduler ,
40
42
private _isBrowser = true ) { }
41
43
42
44
/**
@@ -46,20 +48,25 @@ export class StickyStyler {
46
48
* @param stickyDirections The directions that should no longer be set as sticky on the rows.
47
49
*/
48
50
clearStickyPositioning ( rows : HTMLElement [ ] , stickyDirections : StickyDirection [ ] ) {
51
+ const elementsToClear : HTMLElement [ ] = [ ] ;
49
52
for ( const row of rows ) {
50
53
// If the row isn't an element (e.g. if it's an `ng-container`),
51
54
// it won't have inline styles or `children` so we skip it.
52
55
if ( row . nodeType !== row . ELEMENT_NODE ) {
53
56
continue ;
54
57
}
55
58
56
- this . _removeStickyStyle ( row , stickyDirections ) ;
57
-
59
+ elementsToClear . push ( row ) ;
58
60
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 ) ;
61
62
}
62
63
}
64
+
65
+ this . _coalescedStyleScheduler . schedule ( ( ) => {
66
+ for ( const element of elementsToClear ) {
67
+ this . _removeStickyStyle ( element , stickyDirections ) ;
68
+ }
69
+ } ) ;
63
70
}
64
71
65
72
/**
@@ -73,9 +80,8 @@ export class StickyStyler {
73
80
*/
74
81
updateStickyColumns (
75
82
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 ) {
83
+ if ( ! rows . length || ! this . _isBrowser || ! ( stickyStartStates . some ( state => state ) ||
84
+ stickyEndStates . some ( state => state ) ) ) {
79
85
return ;
80
86
}
81
87
@@ -85,20 +91,25 @@ export class StickyStyler {
85
91
86
92
const startPositions = this . _getStickyStartColumnPositions ( cellWidths , stickyStartStates ) ;
87
93
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
- }
96
94
97
- if ( stickyEndStates [ i ] ) {
98
- this . _addStickyStyle ( cell , isRtl ? 'left' : 'right' , endPositions [ i ] ) ;
95
+ this . _coalescedStyleScheduler . schedule ( ( ) => {
96
+ const isRtl = this . direction === 'rtl' ;
97
+ const start = isRtl ? 'right' : 'left' ;
98
+ const end = isRtl ? 'left' : 'right' ;
99
+
100
+ for ( const row of rows ) {
101
+ for ( let i = 0 ; i < numCells ; i ++ ) {
102
+ const cell = row . children [ i ] as HTMLElement ;
103
+ if ( stickyStartStates [ i ] ) {
104
+ this . _addStickyStyle ( cell , start , startPositions [ i ] ) ;
105
+ }
106
+
107
+ if ( stickyEndStates [ i ] ) {
108
+ this . _addStickyStyle ( cell , end , endPositions [ i ] ) ;
109
+ }
99
110
}
100
111
}
101
- }
112
+ } ) ;
102
113
}
103
114
104
115
/**
@@ -124,30 +135,37 @@ export class StickyStyler {
124
135
const rows = position === 'bottom' ? rowsToStick . slice ( ) . reverse ( ) : rowsToStick ;
125
136
const states = position === 'bottom' ? stickyStates . slice ( ) . reverse ( ) : stickyStates ;
126
137
127
- let stickyHeight = 0 ;
128
- for ( let rowIndex = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
138
+ // Measure row heights all at once before adding sticky styles to reduce layout thrashing.
139
+ const stickyHeights : number [ ] = [ ] ;
140
+ const elementsToStick : HTMLElement [ ] [ ] = [ ] ;
141
+ for ( let rowIndex = 0 , stickyHeight = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
142
+ stickyHeights [ rowIndex ] = stickyHeight ;
143
+
129
144
if ( ! states [ rowIndex ] ) {
130
145
continue ;
131
146
}
132
147
133
148
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
- }
149
+ elementsToStick [ rowIndex ] = this . _isNativeHtmlTable ?
150
+ Array . from ( row . children ) as HTMLElement [ ] : [ row ] ;
144
151
145
- if ( rowIndex === rows . length - 1 ) {
146
- // prevent unnecessary reflow from getBoundingClientRect()
147
- return ;
152
+ if ( rowIndex !== rows . length - 1 ) {
153
+ stickyHeight += row . getBoundingClientRect ( ) . height ;
148
154
}
149
- stickyHeight += row . getBoundingClientRect ( ) . height ;
150
155
}
156
+
157
+ this . _coalescedStyleScheduler . schedule ( ( ) => {
158
+ for ( let rowIndex = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
159
+ if ( ! states [ rowIndex ] ) {
160
+ continue ;
161
+ }
162
+
163
+ const height = stickyHeights [ rowIndex ] ;
164
+ for ( const element of elementsToStick [ rowIndex ] ) {
165
+ this . _addStickyStyle ( element , position , height ) ;
166
+ }
167
+ }
168
+ } ) ;
151
169
}
152
170
153
171
/**
@@ -162,11 +180,14 @@ export class StickyStyler {
162
180
}
163
181
164
182
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
- }
183
+
184
+ this . _coalescedStyleScheduler . schedule ( ( ) => {
185
+ if ( stickyStates . some ( state => ! state ) ) {
186
+ this . _removeStickyStyle ( tfoot , [ 'bottom' ] ) ;
187
+ } else {
188
+ this . _addStickyStyle ( tfoot , 'bottom' , 0 ) ;
189
+ }
190
+ } ) ;
170
191
}
171
192
172
193
/**
0 commit comments