diff --git a/examples/angular/table/src/app/app.component.ts b/examples/angular/table/src/app/app.component.ts index a90d2752e..24cb5ab7c 100644 --- a/examples/angular/table/src/app/app.component.ts +++ b/examples/angular/table/src/app/app.component.ts @@ -25,91 +25,107 @@ import { makeData, type Person } from './make-data' imports: [FlexRenderDirective], template: `

- For tables, the basis for the offset of the translate css function is from - the row's initial position itself. Because of this, we need to calculate - the translateY pixel count different and base it off the the index. + Table rows can't be absolutely positioned, so only the visible rows are + rendered, with a spacer row above and below them. The spacers keep the + table as tall as all of its rows, so the sticky header stays in place + while scrolling.


-
- - - @for ( - headerGroup of table.getHeaderGroups(); - track headerGroup.id - ) { - - @for (header of headerGroup.headers; track header.id) { - - } - - } - - - @for ( - virtualRow of virtualizer.getVirtualItems(); - track data[virtualRow.index].id - ) { - - @for ( - cell of rows()[virtualRow.index].getVisibleCells(); - track cell.id - ) { - - } - - } - -
- @if (!header.isPlaceholder) { -
- - {{ headerText }} - {{ getSortIcon(header.column.getIsSorted()) }} - -
- } -
- + @if (!header.isPlaceholder) { +
- {{ cellText }} - -
-
+ + {{ headerText }} + {{ getSortIcon(header.column.getIsSorted()) }} + +
+ } + + } + + } + + + @if (paddingTop() > 0) { + + + + } + @for ( + virtualRow of virtualizer.getVirtualItems(); + track data[virtualRow.index].id + ) { + + @for ( + cell of rows()[virtualRow.index].getVisibleCells(); + track cell.id + ) { + + + {{ cellText }} + + + } + + } + @if (paddingBottom() > 0) { + + + + } + + `, styles: ` .scroll-container { height: 600px; overflow: auto; + overflow-anchor: none; + } + + table { + border-collapse: collapse; + } + + thead { + position: sticky; + top: 0; + background: white; } `, }) @@ -191,4 +207,18 @@ export class AppComponent { estimateSize: () => 34, overscan: 20, })) + + // Height of the spacer rows rendered above and below the visible rows + paddingTop = computed(() => { + const virtualRows = this.virtualizer.getVirtualItems() + return virtualRows.length > 0 ? virtualRows[0].start : 0 + }) + + paddingBottom = computed(() => { + const virtualRows = this.virtualizer.getVirtualItems() + return virtualRows.length > 0 + ? this.virtualizer.getTotalSize() - + virtualRows[virtualRows.length - 1].end + : 0 + }) } diff --git a/examples/react/table/src/index.css b/examples/react/table/src/index.css index c117a3a78..505f669ea 100644 --- a/examples/react/table/src/index.css +++ b/examples/react/table/src/index.css @@ -16,4 +16,15 @@ body { .container { height: 600px; overflow: auto; + overflow-anchor: none; +} + +table { + border-collapse: collapse; +} + +thead { + position: sticky; + top: 0; + background: white; } diff --git a/examples/react/table/src/main.tsx b/examples/react/table/src/main.tsx index 0bdbe7d69..ea7a484d8 100644 --- a/examples/react/table/src/main.tsx +++ b/examples/react/table/src/main.tsx @@ -86,74 +86,87 @@ function ReactTableVirtualized() { overscan: 20, }) + const virtualRows = virtualizer.getVirtualItems() + + // Height of the spacer rows rendered above and below the visible rows + const [paddingTop, paddingBottom] = + virtualRows.length > 0 + ? [ + virtualRows[0].start, + virtualizer.getTotalSize() - virtualRows[virtualRows.length - 1].end, + ] + : [0, 0] + + const columnCount = table.getVisibleLeafColumns().length + return (
-
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { +
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + ) + })} + + ))} + + + {paddingTop > 0 && ( + + + )} + {virtualRows.map((virtualRow) => { + const row = rows[virtualRow.index] + return ( + + {row.getVisibleCells().map((cell) => { return ( - ) })} - ))} - - - {virtualizer.getVirtualItems().map((virtualRow, index) => { - const row = rows[virtualRow.index] - return ( - - {row.getVisibleCells().map((cell) => { - return ( - - ) - })} - - ) - })} - -
+ {header.isPlaceholder ? null : ( +
+ {flexRender( + header.column.columnDef.header, + header.getContext(), + )} + {{ + asc: ' 🔼', + desc: ' 🔽', + }[header.column.getIsSorted() as string] ?? null} +
+ )} +
+
- {header.isPlaceholder ? null : ( -
- {flexRender( - header.column.columnDef.header, - header.getContext(), - )} - {{ - asc: ' 🔼', - desc: ' 🔽', - }[header.column.getIsSorted() as string] ?? null} -
+
+ {flexRender( + cell.column.columnDef.cell, + cell.getContext(), )} - +
- {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} -
-
+ ) + })} + {paddingBottom > 0 && ( + + + + )} + +
) } @@ -162,10 +175,10 @@ function App() { return (

- For tables, the basis for the offset of the translate css function is - from the row's initial position itself. Because of this, we need to - calculate the translateY pixel count differently and base it off the - index. + Table rows can't be absolutely positioned, so only the visible rows are + rendered, with a spacer row above and below them. The spacers keep the + table as tall as all of its rows, so the sticky header stays in place + while scrolling.


diff --git a/examples/svelte/table/src/App.svelte b/examples/svelte/table/src/App.svelte index 427dcea2a..d91358fcd 100644 --- a/examples/svelte/table/src/App.svelte +++ b/examples/svelte/table/src/App.svelte @@ -94,69 +94,89 @@ estimateSize: () => 34, overscan: 20, }) + + $: virtualRows = $virtualizer.getVirtualItems() + + // Height of the spacer rows rendered above and below the visible rows + $: paddingTop = virtualRows.length > 0 ? virtualRows[0].start : 0 + $: paddingBottom = + virtualRows.length > 0 + ? $virtualizer.getTotalSize() - virtualRows[virtualRows.length - 1].end + : 0 + + $: columnCount = $table.getVisibleLeafColumns().length

- For tables, the basis for the offset of the translate css function is from - the row's initial position itself. Because of this, we need to calculate the - translateY pixel count differently and base it off the index. + Table rows can't be absolutely positioned, so only the visible rows are + rendered, with a spacer row above and below them. The spacers keep the table + as tall as all of its rows, so the sticky header stays in place while + scrolling.

-
- - - {#each $table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {/each} - - {/each} - - - {#each $virtualizer.getVirtualItems() as row, idx (row.index)} - - {#each rows[row.index].getVisibleCells() as cell (cell.id)} - - {/each} - - {/each} - -
- {#if !header.isPlaceholder} - - {/if} -
- -
-
+ + + {#each $table.getHeaderGroups() as headerGroup (headerGroup.id)} + + {#each headerGroup.headers as header (header.id)} + + {/each} + + {/each} + + + {#if paddingTop > 0} + + + + {/if} + {#each virtualRows as row (row.index)} + + {#each rows[row.index].getVisibleCells() as cell (cell.id)} + + {/each} + + {/each} + {#if paddingBottom > 0} + + + + {/if} + +
+ {#if !header.isPlaceholder} + + {/if} +
+ +
@@ -176,6 +196,15 @@ height: 600px; width: 100%; overflow: auto; + overflow-anchor: none; + } + table { + border-collapse: collapse; + } + thead { + position: sticky; + top: 0; + background: white; } .sortable-header { cursor: pointer; diff --git a/examples/vue/table/src/App.vue b/examples/vue/table/src/App.vue index ba12ad428..d3ca5a0fc 100644 --- a/examples/vue/table/src/App.vue +++ b/examples/vue/table/src/App.vue @@ -1,71 +1,77 @@ @@ -177,4 +183,17 @@ const rowVirtualizer = useVirtualizer(rowVirtualizerOptions) const virtualRows = computed(() => rowVirtualizer.value.getVirtualItems()) const totalSize = computed(() => rowVirtualizer.value.getTotalSize()) + +// Height of the spacer rows rendered above and below the visible rows +const paddingTop = computed(() => + virtualRows.value.length > 0 ? virtualRows.value[0].start : 0, +) + +const paddingBottom = computed(() => + virtualRows.value.length > 0 + ? totalSize.value - virtualRows.value[virtualRows.value.length - 1].end + : 0, +) + +const columnCount = computed(() => table.getVisibleLeafColumns().length) diff --git a/examples/vue/table/src/style.css b/examples/vue/table/src/style.css index 7f784706f..9a1b9da09 100644 --- a/examples/vue/table/src/style.css +++ b/examples/vue/table/src/style.css @@ -16,6 +16,17 @@ body { .container { height: 600px; overflow: auto; + overflow-anchor: none; +} + +table { + border-collapse: collapse; +} + +thead { + position: sticky; + top: 0; + background: white; } .cursor-pointer {