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