Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 103 additions & 73 deletions examples/angular/table/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,91 +25,107 @@ import { makeData, type Person } from './make-data'
imports: [FlexRenderDirective],
template: `
<p>
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.
</p>
<br />

<div #scrollElement class="list scroll-container">
<div [style.height.px]="virtualizer.getTotalSize()">
<table>
<thead>
@for (
headerGroup of table.getHeaderGroups();
track headerGroup.id
) {
<tr>
@for (header of headerGroup.headers; track header.id) {
<th
[attr.colspan]="header.colSpan"
[style.width.px]="header.getSize()"
>
@if (!header.isPlaceholder) {
<div
[class.cursor-pointer]="header.column.getCanSort()"
[class.select-none]="header.column.getCanSort()"
(click)="
header.column.getToggleSortingHandler()?.($event)
"
>
<ng-container
*flexRender="
header.column.columnDef.header;
props: header.getContext();
let headerText
"
>
<span>{{ headerText }}</span>
{{ getSortIcon(header.column.getIsSorted()) }}
</ng-container>
</div>
}
</th>
}
</tr>
}
</thead>
<tbody>
@for (
virtualRow of virtualizer.getVirtualItems();
track data[virtualRow.index].id
) {
<tr
[style.height.px]="virtualRow.size"
[style.transform]="
'translateY(' +
(virtualRow.start - $index * virtualRow.size) +
'px)'
"
>
@for (
cell of rows()[virtualRow.index].getVisibleCells();
track cell.id
) {
<td>
<ng-container
*flexRender="
cell.column.columnDef.cell;
props: cell.getContext();
let cellText
<table>
<thead>
@for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
<tr>
@for (header of headerGroup.headers; track header.id) {
<th
[attr.colspan]="header.colSpan"
[style.width.px]="header.getSize()"
>
@if (!header.isPlaceholder) {
<div
[class.cursor-pointer]="header.column.getCanSort()"
[class.select-none]="header.column.getCanSort()"
(click)="
header.column.getToggleSortingHandler()?.($event)
"
>
<span>{{ cellText }}</span>
</ng-container>
</td>
}
</tr>
}
</tbody>
</table>
</div>
<ng-container
*flexRender="
header.column.columnDef.header;
props: header.getContext();
let headerText
"
>
<span>{{ headerText }}</span>
{{ getSortIcon(header.column.getIsSorted()) }}
</ng-container>
</div>
}
</th>
}
</tr>
}
</thead>
<tbody>
@if (paddingTop() > 0) {
<tr>
<td
[attr.colspan]="table.getVisibleLeafColumns().length"
[style.height.px]="paddingTop()"
></td>
</tr>
}
@for (
virtualRow of virtualizer.getVirtualItems();
track data[virtualRow.index].id
) {
<tr [style.height.px]="virtualRow.size">
@for (
cell of rows()[virtualRow.index].getVisibleCells();
track cell.id
) {
<td>
<ng-container
*flexRender="
cell.column.columnDef.cell;
props: cell.getContext();
let cellText
"
>
<span>{{ cellText }}</span>
</ng-container>
</td>
}
</tr>
}
@if (paddingBottom() > 0) {
<tr>
<td
[attr.colspan]="table.getVisibleLeafColumns().length"
[style.height.px]="paddingBottom()"
></td>
</tr>
}
</tbody>
</table>
</div>
`,
styles: `
.scroll-container {
height: 600px;
overflow: auto;
overflow-anchor: none;
}

table {
border-collapse: collapse;
}

thead {
position: sticky;
top: 0;
background: white;
}
`,
})
Expand Down Expand Up @@ -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
})
}
11 changes: 11 additions & 0 deletions examples/react/table/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,15 @@ body {
.container {
height: 600px;
overflow: auto;
overflow-anchor: none;
}

table {
border-collapse: collapse;
}

thead {
position: sticky;
top: 0;
background: white;
}
143 changes: 78 additions & 65 deletions examples/react/table/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div ref={parentRef} className="container">
<div style={{ height: `${virtualizer.getTotalSize()}px` }}>
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
<table>
<thead>
{table.getHeaderGroups().map((headerGroup) => (
<tr key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<th
key={header.id}
colSpan={header.colSpan}
style={{ width: header.getSize() }}
>
{header.isPlaceholder ? null : (
<div
{...{
className: header.column.getCanSort()
? 'cursor-pointer select-none'
: '',
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{{
asc: ' 🔼',
desc: ' 🔽',
}[header.column.getIsSorted() as string] ?? null}
</div>
)}
</th>
)
})}
</tr>
))}
</thead>
<tbody>
{paddingTop > 0 && (
<tr>
<td colSpan={columnCount} style={{ height: paddingTop }} />
</tr>
)}
{virtualRows.map((virtualRow) => {
const row = rows[virtualRow.index]
return (
<tr key={row.id} style={{ height: `${virtualRow.size}px` }}>
{row.getVisibleCells().map((cell) => {
return (
<th
key={header.id}
colSpan={header.colSpan}
style={{ width: header.getSize() }}
>
{header.isPlaceholder ? null : (
<div
{...{
className: header.column.getCanSort()
? 'cursor-pointer select-none'
: '',
onClick: header.column.getToggleSortingHandler(),
}}
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{{
asc: ' 🔼',
desc: ' 🔽',
}[header.column.getIsSorted() as string] ?? null}
</div>
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</th>
</td>
)
})}
</tr>
))}
</thead>
<tbody>
{virtualizer.getVirtualItems().map((virtualRow, index) => {
const row = rows[virtualRow.index]
return (
<tr
key={row.id}
style={{
height: `${virtualRow.size}px`,
transform: `translateY(${
virtualRow.start - index * virtualRow.size
}px)`,
}}
>
{row.getVisibleCells().map((cell) => {
return (
<td key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
</div>
)
})}
{paddingBottom > 0 && (
<tr>
<td colSpan={columnCount} style={{ height: paddingBottom }} />
</tr>
)}
</tbody>
</table>
</div>
)
}
Expand All @@ -162,10 +175,10 @@ function App() {
return (
<div>
<p>
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.
</p>
<ReactTableVirtualized />
<br />
Expand Down
Loading
Loading