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,26 @@ 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
+ // 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
+ } ) ;
63
71
}
64
72
65
73
/**
@@ -73,9 +81,8 @@ export class StickyStyler {
73
81
*/
74
82
updateStickyColumns (
75
83
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 ) ) ) {
79
86
return ;
80
87
}
81
88
@@ -85,20 +92,26 @@ export class StickyStyler {
85
92
86
93
const startPositions = this . _getStickyStartColumnPositions ( cellWidths , stickyStartStates ) ;
87
94
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
95
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
+ }
99
112
}
100
113
}
101
- }
114
+ } ) ;
102
115
}
103
116
104
117
/**
@@ -124,30 +137,39 @@ export class StickyStyler {
124
137
const rows = position === 'bottom' ? rowsToStick . slice ( ) . reverse ( ) : rowsToStick ;
125
138
const states = position === 'bottom' ? stickyStates . slice ( ) . reverse ( ) : stickyStates ;
126
139
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
+
129
146
if ( ! states [ rowIndex ] ) {
130
147
continue ;
131
148
}
132
149
133
150
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 ] ;
144
153
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 ;
148
156
}
149
- stickyHeight += row . getBoundingClientRect ( ) . height ;
150
157
}
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
+ } ) ;
151
173
}
152
174
153
175
/**
@@ -162,11 +184,15 @@ export class StickyStyler {
162
184
}
163
185
164
186
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
+ } ) ;
170
196
}
171
197
172
198
/**
0 commit comments