* Applies sticky positioning to the row's cells if using the native table layout, and to the * row itself otherwise. * @param rowsToStick The list of rows that should be stuck according to their corresponding * sticky state and to the provided top or bottom position. * @param stickyS
(rowsToStick: HTMLElement[], stickyStates: boolean[], position: 'top' | 'bottom')
| 228 | * |
| 229 | */ |
| 230 | stickRows(rowsToStick: HTMLElement[], stickyStates: boolean[], position: 'top' | 'bottom') { |
| 231 | // Since we can't measure the rows on the server, we can't stick the rows properly. |
| 232 | if (!this._isBrowser) { |
| 233 | return; |
| 234 | } |
| 235 | |
| 236 | // If positioning the rows to the bottom, reverse their order when evaluating the sticky |
| 237 | // position such that the last row stuck will be "bottom: 0px" and so on. Note that the |
| 238 | // sticky states need to be reversed as well. |
| 239 | const rows = position === 'bottom' ? rowsToStick.slice().reverse() : rowsToStick; |
| 240 | const states = position === 'bottom' ? stickyStates.slice().reverse() : stickyStates; |
| 241 | |
| 242 | // Measure row heights all at once before adding sticky styles to reduce layout thrashing. |
| 243 | const stickyOffsets: number[] = []; |
| 244 | const stickyCellHeights: (number | undefined)[] = []; |
| 245 | const elementsToStick: HTMLElement[][] = []; |
| 246 | |
| 247 | // Coalesce with other sticky row updates (top/bottom), sticky columns updates |
| 248 | // (and potentially other changes like column resize). |
| 249 | afterNextRender( |
| 250 | { |
| 251 | earlyRead: () => { |
| 252 | for (let rowIndex = 0, stickyOffset = 0; rowIndex < rows.length; rowIndex++) { |
| 253 | if (!states[rowIndex]) { |
| 254 | continue; |
| 255 | } |
| 256 | |
| 257 | stickyOffsets[rowIndex] = stickyOffset; |
| 258 | const row = rows[rowIndex]; |
| 259 | elementsToStick[rowIndex] = this._isNativeHtmlTable |
| 260 | ? (Array.from(row.children) as HTMLElement[]) |
| 261 | : [row]; |
| 262 | |
| 263 | const height = this._retrieveElementSize(row).height; |
| 264 | stickyOffset += height; |
| 265 | stickyCellHeights[rowIndex] = height; |
| 266 | } |
| 267 | }, |
| 268 | write: () => { |
| 269 | const borderedRowIndex = states.lastIndexOf(true); |
| 270 | |
| 271 | for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { |
| 272 | if (!states[rowIndex]) { |
| 273 | continue; |
| 274 | } |
| 275 | |
| 276 | const offset = stickyOffsets[rowIndex]; |
| 277 | const isBorderedRowIndex = rowIndex === borderedRowIndex; |
| 278 | for (const element of elementsToStick[rowIndex]) { |
| 279 | this._addStickyStyle(element, position, offset, isBorderedRowIndex); |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | if (position === 'top') { |
| 284 | this._positionListener?.stickyHeaderRowsUpdated({ |
| 285 | sizes: stickyCellHeights, |
| 286 | offsets: stickyOffsets, |
| 287 | elements: elementsToStick, |
no test coverage detected