10
10
* Directions that can be used when setting sticky positioning.
11
11
* @docs -private
12
12
*/
13
+ import { afterNextRender , Injector } from '@angular/core' ;
13
14
import { Direction } from '@angular/cdk/bidi' ;
14
- import { _CoalescedStyleScheduler } from './coalesced-style-scheduler' ;
15
15
import { StickyPositioningListener } from './sticky-position-listener' ;
16
16
17
17
export type StickyDirection = 'top' | 'bottom' | 'left' | 'right' ;
@@ -41,6 +41,7 @@ export class StickyStyler {
41
41
private _stickyColumnsReplayTimeout : number | null = null ;
42
42
private _cachedCellWidths : number [ ] = [ ] ;
43
43
private readonly _borderCellCss : Readonly < { [ d in StickyDirection ] : string } > ;
44
+ private _destroyed = false ;
44
45
45
46
/**
46
47
* @param _isNativeHtmlTable Whether the sticky logic should be based on a table
@@ -60,10 +61,10 @@ export class StickyStyler {
60
61
private _isNativeHtmlTable : boolean ,
61
62
private _stickCellCss : string ,
62
63
public direction : Direction ,
63
- private _coalescedStyleScheduler : _CoalescedStyleScheduler ,
64
64
private _isBrowser = true ,
65
65
private readonly _needsPositionStickyOnElement = true ,
66
66
private readonly _positionListener ?: StickyPositioningListener ,
67
+ private readonly _tableInjector ?: Injector ,
67
68
) {
68
69
this . _borderCellCss = {
69
70
'top' : `${ _stickCellCss } -border-elem-top` ,
@@ -92,18 +93,20 @@ export class StickyStyler {
92
93
continue ;
93
94
}
94
95
95
- elementsToClear . push ( row ) ;
96
- for ( let i = 0 ; i < row . children . length ; i ++ ) {
97
- elementsToClear . push ( row . children [ i ] as HTMLElement ) ;
98
- }
96
+ elementsToClear . push ( row , ...( Array . from ( row . children ) as HTMLElement [ ] ) ) ;
99
97
}
100
98
101
99
// Coalesce with sticky row/column updates (and potentially other changes like column resize).
102
- this . _coalescedStyleScheduler . schedule ( ( ) => {
103
- for ( const element of elementsToClear ) {
104
- this . _removeStickyStyle ( element , stickyDirections ) ;
105
- }
106
- } ) ;
100
+ afterNextRender (
101
+ {
102
+ write : ( ) => {
103
+ for ( const element of elementsToClear ) {
104
+ this . _removeStickyStyle ( element , stickyDirections ) ;
105
+ }
106
+ } ,
107
+ } ,
108
+ { injector : this . _tableInjector } ,
109
+ ) ;
107
110
}
108
111
109
112
/**
@@ -147,54 +150,67 @@ export class StickyStyler {
147
150
}
148
151
149
152
// Coalesce with sticky row updates (and potentially other changes like column resize).
150
- this . _coalescedStyleScheduler . schedule ( ( ) => {
151
- const firstRow = rows [ 0 ] ;
152
- const numCells = firstRow . children . length ;
153
- const cellWidths : number [ ] = this . _getCellWidths ( firstRow , recalculateCellWidths ) ;
154
-
155
- const startPositions = this . _getStickyStartColumnPositions ( cellWidths , stickyStartStates ) ;
156
- const endPositions = this . _getStickyEndColumnPositions ( cellWidths , stickyEndStates ) ;
157
-
158
- const lastStickyStart = stickyStartStates . lastIndexOf ( true ) ;
159
- const firstStickyEnd = stickyEndStates . indexOf ( true ) ;
160
-
161
- const isRtl = this . direction === 'rtl' ;
162
- const start = isRtl ? 'right' : 'left' ;
163
- const end = isRtl ? 'left' : 'right' ;
164
-
165
- for ( const row of rows ) {
166
- for ( let i = 0 ; i < numCells ; i ++ ) {
167
- const cell = row . children [ i ] as HTMLElement ;
168
- if ( stickyStartStates [ i ] ) {
169
- this . _addStickyStyle ( cell , start , startPositions [ i ] , i === lastStickyStart ) ;
153
+ const firstRow = rows [ 0 ] ;
154
+ const numCells = firstRow . children . length ;
155
+
156
+ const isRtl = this . direction === 'rtl' ;
157
+ const start = isRtl ? 'right' : 'left' ;
158
+ const end = isRtl ? 'left' : 'right' ;
159
+
160
+ const lastStickyStart = stickyStartStates . lastIndexOf ( true ) ;
161
+ const firstStickyEnd = stickyEndStates . indexOf ( true ) ;
162
+
163
+ let cellWidths : number [ ] ;
164
+ let startPositions : number [ ] ;
165
+ let endPositions : number [ ] ;
166
+
167
+ afterNextRender (
168
+ {
169
+ earlyRead : ( ) => {
170
+ cellWidths = this . _getCellWidths ( firstRow , recalculateCellWidths ) ;
171
+
172
+ startPositions = this . _getStickyStartColumnPositions ( cellWidths , stickyStartStates ) ;
173
+ endPositions = this . _getStickyEndColumnPositions ( cellWidths , stickyEndStates ) ;
174
+ } ,
175
+ write : ( ) => {
176
+ for ( const row of rows ) {
177
+ for ( let i = 0 ; i < numCells ; i ++ ) {
178
+ const cell = row . children [ i ] as HTMLElement ;
179
+ if ( stickyStartStates [ i ] ) {
180
+ this . _addStickyStyle ( cell , start , startPositions [ i ] , i === lastStickyStart ) ;
181
+ }
182
+
183
+ if ( stickyEndStates [ i ] ) {
184
+ this . _addStickyStyle ( cell , end , endPositions [ i ] , i === firstStickyEnd ) ;
185
+ }
186
+ }
170
187
}
171
188
172
- if ( stickyEndStates [ i ] ) {
173
- this . _addStickyStyle ( cell , end , endPositions [ i ] , i === firstStickyEnd ) ;
189
+ if ( this . _positionListener ) {
190
+ this . _positionListener . stickyColumnsUpdated ( {
191
+ sizes :
192
+ lastStickyStart === - 1
193
+ ? [ ]
194
+ : cellWidths
195
+ . slice ( 0 , lastStickyStart + 1 )
196
+ . map ( ( width , index ) => ( stickyStartStates [ index ] ? width : null ) ) ,
197
+ } ) ;
198
+ this . _positionListener . stickyEndColumnsUpdated ( {
199
+ sizes :
200
+ firstStickyEnd === - 1
201
+ ? [ ]
202
+ : cellWidths
203
+ . slice ( firstStickyEnd )
204
+ . map ( ( width , index ) =>
205
+ stickyEndStates [ index + firstStickyEnd ] ? width : null ,
206
+ )
207
+ . reverse ( ) ,
208
+ } ) ;
174
209
}
175
- }
176
- }
177
-
178
- if ( this . _positionListener ) {
179
- this . _positionListener . stickyColumnsUpdated ( {
180
- sizes :
181
- lastStickyStart === - 1
182
- ? [ ]
183
- : cellWidths
184
- . slice ( 0 , lastStickyStart + 1 )
185
- . map ( ( width , index ) => ( stickyStartStates [ index ] ? width : null ) ) ,
186
- } ) ;
187
- this . _positionListener . stickyEndColumnsUpdated ( {
188
- sizes :
189
- firstStickyEnd === - 1
190
- ? [ ]
191
- : cellWidths
192
- . slice ( firstStickyEnd )
193
- . map ( ( width , index ) => ( stickyEndStates [ index + firstStickyEnd ] ? width : null ) )
194
- . reverse ( ) ,
195
- } ) ;
196
- }
197
- } ) ;
210
+ } ,
211
+ } ,
212
+ { injector : this . _tableInjector } ,
213
+ ) ;
198
214
}
199
215
200
216
/**
@@ -214,64 +230,70 @@ export class StickyStyler {
214
230
return ;
215
231
}
216
232
217
- // Coalesce with other sticky row updates (top/bottom), sticky columns updates
218
- // (and potentially other changes like column resize).
219
- this . _coalescedStyleScheduler . schedule ( ( ) => {
220
- // If positioning the rows to the bottom, reverse their order when evaluating the sticky
221
- // position such that the last row stuck will be "bottom: 0px" and so on. Note that the
222
- // sticky states need to be reversed as well.
223
- const rows = position === 'bottom' ? rowsToStick . slice ( ) . reverse ( ) : rowsToStick ;
224
- const states = position === 'bottom' ? stickyStates . slice ( ) . reverse ( ) : stickyStates ;
225
-
226
- // Measure row heights all at once before adding sticky styles to reduce layout thrashing.
227
- const stickyOffsets : number [ ] = [ ] ;
228
- const stickyCellHeights : ( number | undefined ) [ ] = [ ] ;
229
- const elementsToStick : HTMLElement [ ] [ ] = [ ] ;
230
-
231
- for ( let rowIndex = 0 , stickyOffset = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
232
- if ( ! states [ rowIndex ] ) {
233
- continue ;
234
- }
233
+ // If positioning the rows to the bottom, reverse their order when evaluating the sticky
234
+ // position such that the last row stuck will be "bottom: 0px" and so on. Note that the
235
+ // sticky states need to be reversed as well.
236
+ const rows = position === 'bottom' ? rowsToStick . slice ( ) . reverse ( ) : rowsToStick ;
237
+ const states = position === 'bottom' ? stickyStates . slice ( ) . reverse ( ) : stickyStates ;
235
238
236
- stickyOffsets [ rowIndex ] = stickyOffset ;
237
- const row = rows [ rowIndex ] ;
238
- elementsToStick [ rowIndex ] = this . _isNativeHtmlTable
239
- ? ( Array . from ( row . children ) as HTMLElement [ ] )
240
- : [ row ] ;
241
-
242
- const height = this . _retrieveElementSize ( row ) . height ;
243
- stickyOffset += height ;
244
- stickyCellHeights [ rowIndex ] = height ;
245
- }
239
+ // Measure row heights all at once before adding sticky styles to reduce layout thrashing.
240
+ const stickyOffsets : number [ ] = [ ] ;
241
+ const stickyCellHeights : ( number | undefined ) [ ] = [ ] ;
242
+ const elementsToStick : HTMLElement [ ] [ ] = [ ] ;
246
243
247
- const borderedRowIndex = states . lastIndexOf ( true ) ;
248
-
249
- for ( let rowIndex = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
250
- if ( ! states [ rowIndex ] ) {
251
- continue ;
252
- }
253
-
254
- const offset = stickyOffsets [ rowIndex ] ;
255
- const isBorderedRowIndex = rowIndex === borderedRowIndex ;
256
- for ( const element of elementsToStick [ rowIndex ] ) {
257
- this . _addStickyStyle ( element , position , offset , isBorderedRowIndex ) ;
258
- }
259
- }
244
+ // Coalesce with other sticky row updates (top/bottom), sticky columns updates
245
+ // (and potentially other changes like column resize).
246
+ afterNextRender (
247
+ {
248
+ earlyRead : ( ) => {
249
+ for ( let rowIndex = 0 , stickyOffset = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
250
+ if ( ! states [ rowIndex ] ) {
251
+ continue ;
252
+ }
253
+
254
+ stickyOffsets [ rowIndex ] = stickyOffset ;
255
+ const row = rows [ rowIndex ] ;
256
+ elementsToStick [ rowIndex ] = this . _isNativeHtmlTable
257
+ ? ( Array . from ( row . children ) as HTMLElement [ ] )
258
+ : [ row ] ;
259
+
260
+ const height = this . _retrieveElementSize ( row ) . height ;
261
+ stickyOffset += height ;
262
+ stickyCellHeights [ rowIndex ] = height ;
263
+ }
264
+ } ,
265
+ write : ( ) => {
266
+ const borderedRowIndex = states . lastIndexOf ( true ) ;
267
+
268
+ for ( let rowIndex = 0 ; rowIndex < rows . length ; rowIndex ++ ) {
269
+ if ( ! states [ rowIndex ] ) {
270
+ continue ;
271
+ }
272
+
273
+ const offset = stickyOffsets [ rowIndex ] ;
274
+ const isBorderedRowIndex = rowIndex === borderedRowIndex ;
275
+ for ( const element of elementsToStick [ rowIndex ] ) {
276
+ this . _addStickyStyle ( element , position , offset , isBorderedRowIndex ) ;
277
+ }
278
+ }
260
279
261
- if ( position === 'top' ) {
262
- this . _positionListener ?. stickyHeaderRowsUpdated ( {
263
- sizes : stickyCellHeights ,
264
- offsets : stickyOffsets ,
265
- elements : elementsToStick ,
266
- } ) ;
267
- } else {
268
- this . _positionListener ?. stickyFooterRowsUpdated ( {
269
- sizes : stickyCellHeights ,
270
- offsets : stickyOffsets ,
271
- elements : elementsToStick ,
272
- } ) ;
273
- }
274
- } ) ;
280
+ if ( position === 'top' ) {
281
+ this . _positionListener ?. stickyHeaderRowsUpdated ( {
282
+ sizes : stickyCellHeights ,
283
+ offsets : stickyOffsets ,
284
+ elements : elementsToStick ,
285
+ } ) ;
286
+ } else {
287
+ this . _positionListener ?. stickyFooterRowsUpdated ( {
288
+ sizes : stickyCellHeights ,
289
+ offsets : stickyOffsets ,
290
+ elements : elementsToStick ,
291
+ } ) ;
292
+ }
293
+ } ,
294
+ } ,
295
+ { injector : this . _tableInjector } ,
296
+ ) ;
275
297
}
276
298
277
299
/**
@@ -286,17 +308,30 @@ export class StickyStyler {
286
308
}
287
309
288
310
// Coalesce with other sticky updates (and potentially other changes like column resize).
289
- this . _coalescedStyleScheduler . schedule ( ( ) => {
290
- const tfoot = tableElement . querySelector ( 'tfoot' ) ! ;
291
-
292
- if ( tfoot ) {
293
- if ( stickyStates . some ( state => ! state ) ) {
294
- this . _removeStickyStyle ( tfoot , [ 'bottom' ] ) ;
295
- } else {
296
- this . _addStickyStyle ( tfoot , 'bottom' , 0 , false ) ;
297
- }
298
- }
299
- } ) ;
311
+ afterNextRender (
312
+ {
313
+ write : ( ) => {
314
+ const tfoot = tableElement . querySelector ( 'tfoot' ) ! ;
315
+
316
+ if ( tfoot ) {
317
+ if ( stickyStates . some ( state => ! state ) ) {
318
+ this . _removeStickyStyle ( tfoot , [ 'bottom' ] ) ;
319
+ } else {
320
+ this . _addStickyStyle ( tfoot , 'bottom' , 0 , false ) ;
321
+ }
322
+ }
323
+ } ,
324
+ } ,
325
+ { injector : this . _tableInjector } ,
326
+ ) ;
327
+ }
328
+
329
+ destroy ( ) {
330
+ if ( this . _stickyColumnsReplayTimeout ) {
331
+ clearTimeout ( this . _stickyColumnsReplayTimeout ) ;
332
+ }
333
+
334
+ this . _destroyed = true ;
300
335
}
301
336
302
337
/**
@@ -516,6 +551,10 @@ export class StickyStyler {
516
551
}
517
552
518
553
this . _stickyColumnsReplayTimeout = setTimeout ( ( ) => {
554
+ if ( this . _destroyed ) {
555
+ return ;
556
+ }
557
+
519
558
for ( const update of this . _updatedStickyColumnsParamsToReplay ) {
520
559
this . updateStickyColumns (
521
560
update . rows ,
0 commit comments