Skip to content

Commit 54c9679

Browse files
committed
stabilized virtualization algo for horizontal layout
1 parent 186b124 commit 54c9679

File tree

13 files changed

+324
-33
lines changed

13 files changed

+324
-33
lines changed

examples/src/pages/tests/horizontal-layout/default.page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ const columns: InfiniteTablePropColumns<Developer> = {
1313
field: 'id',
1414
type: 'number',
1515
/*xdefaultWidth: 80,*/ renderValue: ({ value }) => value - 1,
16+
style: (options) => {
17+
return {
18+
// background : options.rowInfo.
19+
};
20+
},
1621
},
1722
preferredLanguage: { field: 'preferredLanguage' /*xdefaultWidth: 110 */ },
1823
// age: { field: 'age' /*xdefaultWidth: 70 */ },
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import * as React from 'react';
2+
3+
import {
4+
InfiniteTable,
5+
InfiniteTablePropColumns,
6+
} from '@infinite-table/infinite-react';
7+
import { DataSource } from '@infinite-table/infinite-react';
8+
9+
type Developer = {
10+
id: number;
11+
12+
firstName: string;
13+
lastName: string;
14+
15+
currency: string;
16+
country: string;
17+
preferredLanguage: string;
18+
stack: string;
19+
canDesign: 'yes' | 'no';
20+
21+
age: number;
22+
salary: number;
23+
};
24+
25+
const style = () => {
26+
return {
27+
background: `rgb(255 0 0 / 12%)`,
28+
};
29+
};
30+
31+
const columns: InfiniteTablePropColumns<Developer> = {
32+
id: {
33+
field: 'id',
34+
type: 'number',
35+
style,
36+
},
37+
canDesign: {
38+
field: 'canDesign',
39+
},
40+
salary: {
41+
field: 'salary',
42+
type: 'number',
43+
// style,
44+
},
45+
firstName: {
46+
field: 'firstName',
47+
},
48+
age: {
49+
field: 'age',
50+
type: 'number',
51+
// style,
52+
},
53+
54+
stack: { field: 'stack', renderMenuIcon: false },
55+
currency: { field: 'currency' },
56+
country: { field: 'country' },
57+
};
58+
59+
export default () => {
60+
const dataSource = React.useCallback(() => {
61+
return fetch(process.env.NEXT_PUBLIC_BASE_URL + '/developers100')
62+
.then((r) => r.json())
63+
.then((data) => {
64+
return data;
65+
// return new Promise((resolve) => {
66+
// setTimeout(() => {
67+
// resolve(data);
68+
// }, 10);
69+
// });
70+
});
71+
}, []);
72+
return (
73+
<>
74+
<React.StrictMode>
75+
<DataSource<Developer>
76+
data={dataSource}
77+
primaryKey="id"
78+
defaultGroupBy={[
79+
{
80+
field: 'currency',
81+
},
82+
{
83+
field: 'stack',
84+
},
85+
]}
86+
>
87+
<InfiniteTable<Developer>
88+
columns={columns}
89+
wrapRowsHorizontally={true}
90+
columnDefaultWidth={120}
91+
domProps={{
92+
style: {
93+
minHeight: '70vh',
94+
},
95+
}}
96+
/>
97+
</DataSource>
98+
</React.StrictMode>
99+
</>
100+
);
101+
};

examples/src/pages/tests/horizontal-layout/test.page.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,51 @@ import { useState } from 'react';
99

1010
type Developer = {
1111
id: number;
12+
13+
firstName: string;
14+
lastName: string;
15+
16+
currency: string;
17+
country: string;
1218
preferredLanguage: string;
19+
stack: string;
20+
canDesign: 'yes' | 'no';
21+
1322
age: number;
1423
salary: number;
1524
};
25+
1626
const columns: InfiniteTablePropColumns<Developer> = {
1727
id: {
1828
field: 'id',
1929
type: 'number',
2030
},
21-
preferredLanguage: { field: 'preferredLanguage' },
22-
// age: { field: 'age' },
23-
// salary: { field: 'salary' },
31+
canDesign: {
32+
field: 'canDesign',
33+
},
34+
salary: {
35+
field: 'salary',
36+
type: 'number',
37+
},
38+
firstName: {
39+
field: 'firstName',
40+
},
41+
age: {
42+
field: 'age',
43+
type: 'number',
44+
},
45+
46+
stack: { field: 'stack', renderMenuIcon: false },
47+
currency: { field: 'currency' },
48+
country: { field: 'country' },
2449
};
2550

2651
const domProps = {
2752
// style: { height: 420 /*30px header, 420 body*/, width: 230 },
28-
style: { height: '50vh' /*30px header, 420 body*/, width: '30vw' },
53+
style: { height: '50vh' /*30px header, 420 body*/, width: '80vw' },
2954
};
3055

31-
const data = Array.from({ length: 10 }, (_, i) => ({
56+
const data = Array.from({ length: 1000 }, (_, i) => ({
3257
id: i,
3358
preferredLanguage: `Lang ${i}`,
3459
age: i * 10,

source/src/components/HeadlessTable/HorizontalLayoutTableRenderer.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,37 @@ export class HorizontalLayoutTableRenderer extends ReactHeadlessTableRenderer {
2424
});
2525
}
2626

27+
isCellRenderedAndMappedCorrectly(row: number, col: number) {
28+
const rendered = this.mappedCells.isCellRendered(row, col);
29+
30+
if (!rendered) {
31+
return {
32+
rendered,
33+
mapped: false,
34+
};
35+
}
36+
37+
const cellAdditionalInfo = this.mappedCells.getCellAdditionalInfo(row, col);
38+
39+
if (!cellAdditionalInfo) {
40+
return {
41+
rendered,
42+
mapped: false,
43+
};
44+
}
45+
46+
const info = this.getCellRealCoordinates(row, col);
47+
48+
const mapped =
49+
info.colIndex === cellAdditionalInfo!.renderColIndex &&
50+
info.rowIndex === cellAdditionalInfo!.renderRowIndex;
51+
52+
return {
53+
rendered,
54+
mapped,
55+
};
56+
}
57+
2758
setTransform = (
2859
element: HTMLElement,
2960
rowIndex: number,

source/src/components/HeadlessTable/MappedCells.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { TableRenderRange } from '../VirtualBrain/MatrixBrain';
1414
* This class has tests - see tests/mapped-cells.spec.ts
1515
*/
1616

17-
export class MappedCells extends Logger {
17+
export class MappedCells<T_ADDITIONAL_CELL_INFO = any> extends Logger {
1818
/**
1919
* This is the mapping from element index to cell info.
2020
* The index in the array is the element index while the value at the position is an array where
@@ -29,14 +29,21 @@ export class MappedCells extends Logger {
2929
*/
3030
private cellToElementIndex!: DeepMap<number, number>;
3131

32+
private cellAdditionalInfo!: DeepMap<number, T_ADDITIONAL_CELL_INFO>;
33+
3234
/**
3335
* Keeps the JSX of rendered elements in memory, so we can possibly reuse it later.
3436
*/
3537
private renderedElements!: Renderable[];
3638

37-
constructor() {
39+
private withCellAdditionalInfo: boolean = false;
40+
41+
constructor(opts?: { withCellAdditionalInfo: boolean }) {
3842
super(`MappedCells`);
3943
this.init();
44+
if (opts?.withCellAdditionalInfo) {
45+
this.withCellAdditionalInfo = opts.withCellAdditionalInfo;
46+
}
4047

4148
// if (__DEV__) {
4249
// (globalThis as any).mappedCells = this;
@@ -69,6 +76,7 @@ export class MappedCells extends Logger {
6976
init() {
7077
this.elementIndexToCell = [];
7178
this.cellToElementIndex = new DeepMap();
79+
this.cellAdditionalInfo = new DeepMap();
7280
this.renderedElements = [];
7381
}
7482

@@ -79,6 +87,7 @@ export class MappedCells extends Logger {
7987
destroy() {
8088
this.elementIndexToCell = [];
8189
this.cellToElementIndex.clear();
90+
this.cellAdditionalInfo.clear();
8291
this.renderedElements = [];
8392
}
8493

@@ -129,6 +138,13 @@ export class MappedCells extends Logger {
129138
return this.cellToElementIndex.has([rowIndex, columnIndex]);
130139
};
131140

141+
getCellAdditionalInfo = (
142+
rowIndex: number,
143+
columnIndex: number,
144+
): T_ADDITIONAL_CELL_INFO | undefined => {
145+
return this.cellAdditionalInfo.get([rowIndex, columnIndex]);
146+
};
147+
132148
isElementRendered = (elementIndex: number): boolean => {
133149
return !!this.elementIndexToCell[elementIndex];
134150
};
@@ -172,7 +188,8 @@ export class MappedCells extends Logger {
172188
rowIndex: number,
173189
colIndex: number,
174190
elementIndex: number,
175-
renderNode?: Renderable,
191+
renderNode: Renderable | undefined,
192+
cellAdditionalInfo?: T_ADDITIONAL_CELL_INFO,
176193
) => {
177194
if (__DEV__) {
178195
this.debug(
@@ -184,14 +201,21 @@ export class MappedCells extends Logger {
184201

185202
const currentCell = this.elementIndexToCell[elementIndex];
186203
if (currentCell) {
187-
this.cellToElementIndex.delete([currentCell[0], currentCell[1]]);
204+
const currentCellKey = [currentCell[0], currentCell[1]];
205+
this.cellToElementIndex.delete(currentCellKey);
206+
if (this.withCellAdditionalInfo) {
207+
this.cellAdditionalInfo.delete(currentCellKey);
208+
}
188209
}
189210
if (renderNode) {
190211
this.renderedElements[elementIndex] = renderNode;
191212
}
192213

193214
this.elementIndexToCell[elementIndex] = [rowIndex, colIndex];
194215
this.cellToElementIndex.set(key, elementIndex);
216+
if (this.withCellAdditionalInfo && cellAdditionalInfo !== undefined) {
217+
this.cellAdditionalInfo.set(key, cellAdditionalInfo);
218+
}
195219
};
196220

197221
discardCell = (rowIndex: number, colIndex: number) => {
@@ -202,6 +226,9 @@ export class MappedCells extends Logger {
202226
this.renderedElements[elementIndex] = null;
203227
this.elementIndexToCell[elementIndex] = null;
204228
this.cellToElementIndex.delete(key);
229+
if (this.withCellAdditionalInfo) {
230+
this.cellAdditionalInfo.delete(key);
231+
}
205232
}
206233
};
207234

@@ -213,6 +240,9 @@ export class MappedCells extends Logger {
213240
this.renderedElements[elementIndex] = null;
214241
this.elementIndexToCell[elementIndex] = null;
215242
this.cellToElementIndex.delete(key);
243+
if (this.withCellAdditionalInfo) {
244+
this.cellAdditionalInfo.delete(key);
245+
}
216246

217247
return cell;
218248
}

0 commit comments

Comments
 (0)