fix: add colspan and rowspan support#79
Conversation
| ) : ( | ||
| visibleRows.map((row, index) => <DataRow key={row.motifIndex} rowNumberStatic={index + 1} row={row} />) | ||
| visibleRows.map((row, index) => ( | ||
| <DataRow key={row.motifIndex} rowNumberStatic={index + 1} row={row} spannedCellsMap={spannedCellsMap} /> |
There was a problem hiding this comment.
It should be fixed after my fixes, but I'm not sure because I couldn't see the full data content. Can you verify?
aktasmehmet
left a comment
There was a problem hiding this comment.
This is my review v1 :) There are some issues to fix. Then I'll check later.
This PR is so so so important because it directly manipulates the performance related codes of the component. We'll probably need more reviewers here.
| footer?: Footer; | ||
| width?: string; | ||
| filter?: boolean; | ||
| colSpan?: number | ((rowData: object) => number); |
There was a problem hiding this comment.
why need numbertype here? If it is to apply colspan for every cell, then user can easily reduce the number of colums, so I dont see a need here?
There was a problem hiding this comment.
There are two different needs:
- Static span (colSpan: 2) — fixed across all rows, number type is sufficient
- Dynamic span (colSpan: (row) => row.merged ? 2 : 1) — varies per row, function type is required
Modifying the data is incorrect because:
- Data integrity breaks (missing rows become null/undefined)
- Sorting/filtering breaks
- Component dataKey stops working
example
// 4 rows of data, but Department column calculates rowSpan dynamically per group
const data = [
{ department: "Engineering", employee: "John" }, // rowSpan: 3
{ department: "Engineering", employee: "Jane" }, // (skip)
{ department: "Engineering", employee: "Bob" }, // (skip)
{ department: "Sales", employee: "Alice" } // rowSpan: 1
];
// Halving the data makes no sense — grouping is a UI concern, not a data concern
rowSpan: (row) => row.firstInGroup ? groupSizes[row.department] : 1There was a problem hiding this comment.
I dont find useful information in this answer. The example shows the usage of the function, not the number.
I wonder that, what would be the result for the code below:
<Table
data={[
{ name: "Alice", age: 28, group: "A" },
{ name: "Bob", age: 34, group: "B" },
{ name: "Charlie", age: 29, group: "A" },
{ name: "Dave", age: 42, group: "B" },
]}
columns={[
{ title: "Group", dataKey: "group", rowSpan: 2 },
{ title: "Name", dataKey: "name" },
{ title: "Age", dataKey: "age" },
]}
border="cellBorders"
/>
Afaik, this would render only Alice and Charline lines, right? So this will show 1st row and 3rd row and so on... So, what I mean is that, user can filter the data himself without using rowspan? This will not effect the filtering also?
Am I missing something?
There was a problem hiding this comment.
I might be misunderstood.
rowSpan doesn't filter. it only determines how cells are displayed.
If you set rowSpan: 2, the Group cell spans 2 rows. But which rows appear depends on your data.
// Pass this data:
data={[
{ name: "Alice", age: 28, group: "A" },
{ name: "Charlie", age: 29, group: "A" }, // 2 people = rowSpan: 2 works fine
{ name: "Bob", age: 34, group: "B" },
{ name: "Dave", age: 42, group: "B" },
]}
// Result: Group A cell spans 2 rows ✓
If Group A has 3 people, rowSpan: 2 won't be enough. Then use dynamic rowSpan:
rowSpan: (row) => row.firstInGroup ? groupSize[row.group] : 1
It has nothing to do with filtering — rowSpan just decides how to merge cells in the rows that are already shown.
| const cellKey: SpannedCellKey = `${row.motifIndex}-${colIndex}`; | ||
| if (map.has(cellKey)) return; | ||
|
|
||
| const remainingRowsCount = rows.filter(r => r.motifIndex >= row.motifIndex).length; |
There was a problem hiding this comment.
Another and more dangerous problem here is that since it uses original index order, this sill clamp all rowSpan to 1 after any sort!
Just check the ss
here is the data to test:
<Table
data={[
{ basarili: "Evet", nasil: "İyi", sonuc: "Mutlu" },
{ basarili: "Hayır", nasil: "Kötü", sonuc: "Mutsuz" },
{ basarili: "Evet", nasil: "Orta", sonuc: "Kararsız" },
{ basarili: "Belki", nasil: "Eh İşte", sonuc: "Belirsiz" },
{ basarili: "Hayır", nasil: "Çok Kötü", sonuc: "Çok Mutsuz" },
{ basarili: "Evet", nasil: "Mükemmel", sonuc: "Çok Mutlu" },
{ basarili: "Belki", nasil: "Fena Değil", sonuc: "Kararsız" },
{ basarili: "Hayır", nasil: "Berbat", sonuc: "Çok Mutsuz" },
{ basarili: "Evet", nasil: "Harika", sonuc: "Çok Mutlu" },
{ basarili: "Belki", nasil: "Orta", sonuc: "Kararsız" },
]}
columns={[
{ dataKey: "basarili", title: "Başarılı", colSpan: obj => (obj.basarili === "Evet" ? 2 : 1) },
{ dataKey: "nasil", title: "Nasıl" },
{ dataKey: "sonuc", title: "Sonuç", rowSpan: obj => (obj.sonuc.includes("Mutlu") ? 2 : 1), sorting:{} },
]}
/>
There was a problem hiding this comment.
It should be fixed after my fixes. You can review them.
| export type ResolvedCellSpan = { | ||
| colSpan: number; | ||
| rowSpan: number; | ||
| }; | ||
|
|
||
| export type SpannedCellKey = `${number}-${number}`; | ||
| export type SpannedCellsMap = Map<SpannedCellKey, ResolvedCellSpan | undefined>; | ||
|
|
||
| export type RenderableColumn = { | ||
| column: Column; | ||
| index: number; | ||
| colSpan?: number; | ||
| }; |
There was a problem hiding this comment.
There is already a types.ts file, so it would be better to move all these types there and access types from a single file for easy management.
There was a problem hiding this comment.
This has been fixed in the latest commit. You can verify it.
| footer?: Footer; | ||
| width?: string; | ||
| filter?: boolean; | ||
| colSpan?: number | ((rowData: object) => number); |
There was a problem hiding this comment.
I dont find useful information in this answer. The example shows the usage of the function, not the number.
I wonder that, what would be the result for the code below:
<Table
data={[
{ name: "Alice", age: 28, group: "A" },
{ name: "Bob", age: 34, group: "B" },
{ name: "Charlie", age: 29, group: "A" },
{ name: "Dave", age: 42, group: "B" },
]}
columns={[
{ title: "Group", dataKey: "group", rowSpan: 2 },
{ title: "Name", dataKey: "name" },
{ title: "Age", dataKey: "age" },
]}
border="cellBorders"
/>
Afaik, this would render only Alice and Charline lines, right? So this will show 1st row and 3rd row and so on... So, what I mean is that, user can filter the data himself without using rowspan? This will not effect the filtering also?
Am I missing something?
|
|
||
| const ColumnFootArea = ({ background, customFooter }: Props) => { | ||
| const { originalRows, columns, showFixedRowNumbers, selectable, numberOfVisibleColumns } = useContext(TableContext); | ||
| const { usableRows, columns, showFixedRowNumbers, selectable, numberOfVisibleColumns } = useContext(TableContext); |
There was a problem hiding this comment.
switching from originalRowsto usableRows causes the avg footer to be shown as NaN when all rows are filtered out. You may filter with "x" Please check the ss.
Here is the sample test:
<Table
data={[
{ name: "Alice", score: 90 },
{ name: "Bob", score: 80 },
{ name: "Carol", score: 70 },
]}
columns={[
{ title: "Name", dataKey: "name", filter: true },
{ title: "Score", dataKey: "score", footer: { type: "avg" } },
]}
border="cellBorders"
/>
There was a problem hiding this comment.
This should be fixed in the latest commit. You can verify it.
|
|
||
| const ColumnFootArea = ({ background, customFooter }: Props) => { | ||
| const { originalRows, columns, showFixedRowNumbers, selectable, numberOfVisibleColumns } = useContext(TableContext); | ||
| const { usableRows, columns, showFixedRowNumbers, selectable, numberOfVisibleColumns } = useContext(TableContext); |
There was a problem hiding this comment.
Now saw that this also breaks the filtered total. Previously, it was showin the total of the whole table but now, it only shows the filtered ones as total. This is not expected.
Here is the code (Sum should stay as 600):
<Table
data={[
{ name: "Alice", amount: 100 },
{ name: "Bob", amount: 200 },
{ name: "Carol", amount: 300 },
]}
columns={[
{ title: "Name", dataKey: "name", filter: true },
{ title: "Amount", dataKey: "amount", footer: { type: "sum" } },
]}
border="cellBorders"
/>
There was a problem hiding this comment.
This should be fixed in the latest commit. You can verify it.
| ); | ||
| }); | ||
| return ( | ||
| <th key={"foot_" + column.dataKey} {...(actualColSpan > 1 && { colSpan: actualColSpan })}> |
There was a problem hiding this comment.
This change will lead to duplicate key error. Here is the scenario:
When :
selectable=true
AND
showFixedRowNumbers=true
AND
any data column has footer defined
--> columnsConsideringExtraCols prepends {title:''} and {title:'#'}, both with dataKey=undefined. key={'foot_' + column.dataKey} evaluates to 'foot_undefined' for both.
So this will cause the error:
<Table
data={[
{ name: "Alice", amount: 100 },
{ name: "Bob", amount: 200 },
{ name: "Carol", amount: 300 },
]}
columns={[
{ title: "Name", dataKey: "name" },
{ title: "Amount", dataKey: "amount", footer: { type: "sum" } },
]}
selectable
showFixedRowNumbers
border="cellBorders"
/>
There was a problem hiding this comment.
This has been fixed in the latest commit. You can verify it.




Description
Add colspan and rowspan configuration to table columns with proper cell rendering and pagination safety.
Type of Change
Screenshots / Preview
Checklist
How to Test
[#EDKUI-1410]