Skip to content

Commit 5dbc718

Browse files
committed
wip
1 parent b2db331 commit 5dbc718

7 files changed

Lines changed: 150 additions & 102 deletions

File tree

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import {
2+
InfiniteTable,
3+
DataSource,
4+
type InfiniteTablePropColumns,
5+
} from '@infinite-table/infinite-react';
6+
7+
import * as React from 'react';
8+
import { useState } from 'react';
9+
import { Developer, dataSource } from './horiz-layout-data';
10+
11+
const columns: InfiniteTablePropColumns<Developer> = {
12+
id: {
13+
field: 'id',
14+
type: 'number',
15+
/*xdefaultWidth: 80,*/ renderValue: ({ value }) => value - 1,
16+
style: (_options) => {
17+
return {
18+
// background : options.rowInfo.
19+
};
20+
},
21+
},
22+
preferredLanguage: { field: 'preferredLanguage' /*xdefaultWidth: 110 */ },
23+
// age: { field: 'age' /*xdefaultWidth: 70 */ },
24+
// salary: {
25+
// field: 'salary',
26+
// type: 'number',
27+
// /*xdefaultWidth: 100,*/
28+
// },
29+
};
30+
31+
export function getRandomInt(min: number, max: number) {
32+
return Math.floor(Math.random() * (max - min + 1) + min);
33+
}
34+
35+
const domProps = { style: { height: '30vh', width: '90vw' } };
36+
// const domProps = { style: { height: '30vh', width: 300 } };
37+
38+
// dataSource.length = 12;
39+
40+
export default function App() {
41+
const [wrapRowsHorizontally, setWrapRowsHorizontally] = useState(true);
42+
return (
43+
<>
44+
<button
45+
onClick={() => {
46+
setWrapRowsHorizontally(!wrapRowsHorizontally);
47+
}}
48+
>
49+
toggle
50+
</button>
51+
<DataSource<Developer>
52+
primaryKey="id"
53+
data={dataSource}
54+
key={`${wrapRowsHorizontally}`}
55+
>
56+
<InfiniteTable<Developer>
57+
wrapRowsHorizontally={wrapRowsHorizontally}
58+
rowHeight={50}
59+
domProps={domProps}
60+
columns={columns}
61+
columnDefaultWidth={150}
62+
onCellClick={({ rowIndex, colIndex }) => {
63+
console.log('clicked', rowIndex, colIndex);
64+
}}
65+
/>
66+
</DataSource>
67+
</>
68+
);
69+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,24 @@ const columns: InfiniteTablePropColumns<Developer> = {
2828
field: 'id',
2929
type: 'number',
3030
defaultWidth: 100,
31+
style: {
32+
background: 'rgba(255, 99, 71, 0.4)',
33+
},
3134
},
3235
canDesign: {
3336
field: 'canDesign',
3437
defaultWidth: 200,
38+
style: {
39+
background: 'rgba(211, 119, 171, 0.3)',
40+
},
3541
},
3642
salary: {
3743
field: 'salary',
3844
type: 'number',
3945
defaultWidth: 300,
46+
style: {
47+
background: 'rgba(55, 99, 171, 0.3)',
48+
},
4049
},
4150
// firstName: {
4251
// field: 'firstName',

source/src/components/HeadlessTable/GridCellInterface.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@ import { Renderable } from '../types/Renderable';
22

33
export interface GridCellInterface<T_ADDITIONAL_CELL_INFO = any> {
44
debugId: string;
5-
update(
6-
content: Renderable,
7-
additionalInfo?: T_ADDITIONAL_CELL_INFO,
8-
scrollingObjectParam?: {
9-
scrolling: boolean;
10-
},
11-
): void;
5+
update(content: Renderable, additionalInfo?: T_ADDITIONAL_CELL_INFO): void;
126
getElement(): HTMLElement | null;
137
getNode(): Renderable;
148
destroy(): void;
@@ -18,4 +12,5 @@ export interface GridCellInterface<T_ADDITIONAL_CELL_INFO = any> {
1812
getAdditionalInfo(): T_ADDITIONAL_CELL_INFO | undefined;
1913
isMounted(): boolean;
2014
ref: React.RefCallback<HTMLElement | undefined>;
15+
setPendingAfterCommitWork(fn: (() => void) | null): void;
2116
}

source/src/components/HeadlessTable/GridCellManager.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,6 @@ export class GridCellManager<T_ADDITIONAL_CELL_INFO> extends Logger {
185185
cell: GridCellInterface<T_ADDITIONAL_CELL_INFO>,
186186
cellPos: CellPos,
187187
additionalInfo?: T_ADDITIONAL_CELL_INFO,
188-
scrollingObjectParam?: {
189-
scrolling: boolean;
190-
isHorizontalLayout: boolean;
191-
},
192188
) {
193189
const currentCellAtPos = this.getCellAt(cellPos);
194190

@@ -203,7 +199,7 @@ export class GridCellManager<T_ADDITIONAL_CELL_INFO> extends Logger {
203199

204200
this.setCellPositionInMatrix(cell, cellPos);
205201

206-
cell.update(node, additionalInfo, scrollingObjectParam);
202+
cell.update(node, additionalInfo);
207203

208204
return cell;
209205
}

source/src/components/HeadlessTable/GridCellPoolForReact.tsx

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class GridCellForReact<T_ADDITIONAL_CELL_INFO = any>
2626

2727
private mounted: boolean = false;
2828

29-
private IS_UPDATED_WHILE_SCROLLING_IN_HORIZONTAL_LAYOUT = false;
29+
private pendingAfterCommitWork: (() => void) | null = null;
3030

3131
private mountSubscription =
3232
buildSubscriptionCallback<GridCellInterface<T_ADDITIONAL_CELL_INFO>>();
@@ -46,7 +46,7 @@ class GridCellForReact<T_ADDITIONAL_CELL_INFO = any>
4646
key={key}
4747
name={key}
4848
updater={this.updater}
49-
shouldFlushSync={this.isUpdatedWhileScrolling}
49+
afterCommit={this.afterCommit}
5050
/>
5151
);
5252

@@ -61,8 +61,16 @@ class GridCellForReact<T_ADDITIONAL_CELL_INFO = any>
6161
};
6262
}
6363

64-
isUpdatedWhileScrolling = () => {
65-
return this.IS_UPDATED_WHILE_SCROLLING_IN_HORIZONTAL_LAYOUT;
64+
setPendingAfterCommitWork(fn: (() => void) | null) {
65+
this.pendingAfterCommitWork = fn;
66+
}
67+
68+
private afterCommit = () => {
69+
if (this.pendingAfterCommitWork) {
70+
const work = this.pendingAfterCommitWork;
71+
this.pendingAfterCommitWork = null;
72+
work();
73+
}
6674
};
6775

6876
isMounted() {
@@ -83,6 +91,7 @@ class GridCellForReact<T_ADDITIONAL_CELL_INFO = any>
8391
this.mountSubscription.destroy();
8492
this.updater.destroy();
8593

94+
this.pendingAfterCommitWork = null;
8695
this.ref = emptyFn;
8796
this.element = null;
8897
this.node = null;
@@ -92,18 +101,7 @@ class GridCellForReact<T_ADDITIONAL_CELL_INFO = any>
92101
return this.node;
93102
}
94103

95-
update(
96-
content: Renderable,
97-
additionalInfo?: T_ADDITIONAL_CELL_INFO,
98-
scrollingObjectParam?: {
99-
scrolling: boolean;
100-
isHorizontalLayout: boolean;
101-
},
102-
): void {
103-
this.IS_UPDATED_WHILE_SCROLLING_IN_HORIZONTAL_LAYOUT = scrollingObjectParam
104-
? scrollingObjectParam.scrolling &&
105-
scrollingObjectParam.isHorizontalLayout
106-
: false;
104+
update(content: Renderable, additionalInfo?: T_ADDITIONAL_CELL_INFO): void {
107105
this.updater(content);
108106
this.cellInfo = additionalInfo;
109107
}

source/src/components/HeadlessTable/ReactHeadlessTableRenderer.tsx

Lines changed: 43 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,6 @@ export const columnOffsetAtIndexWhileReordering = stripVar(
4242
InternalVars.columnOffsetAtIndexWhileReordering,
4343
);
4444

45-
const SCROLLING_OBJECT_PARAM_FLYWEIGHT = {
46-
scrolling: false,
47-
isHorizontalLayout: false,
48-
};
49-
5045
export class GridRenderer extends Logger {
5146
protected brain: MatrixBrain;
5247

@@ -1381,15 +1376,11 @@ export class GridRenderer extends Logger {
13811376

13821377
return;
13831378
}
1384-
SCROLLING_OBJECT_PARAM_FLYWEIGHT.scrolling = this.scrolling;
1385-
SCROLLING_OBJECT_PARAM_FLYWEIGHT.isHorizontalLayout = isHorizontalLayout;
1386-
13871379
this.cellManager.renderNodeAtCell(
13881380
renderedNode,
13891381
cell,
13901382
[rowIndex, colIndex],
13911383
cellAdditionalInfo,
1392-
SCROLLING_OBJECT_PARAM_FLYWEIGHT,
13931384
);
13941385

13951386
this.updateElementPosition(cell, { hidden, rowspan, colspan });
@@ -1501,45 +1492,53 @@ export class GridRenderer extends Logger {
15011492
const { x, y } = itemPosition;
15021493

15031494
if (itemElement) {
1504-
this.updateHoverClassNamesForRow(rowIndex);
1505-
1506-
// itemElement.style.gridColumn = `${colIndex} / span 1`;
1507-
// itemElement.style.gridRow = `${rowIndex} / span 1`;
1508-
1509-
// (itemElement.dataset as any).elementIndex = elementIndex;
1510-
const realCoords = this.getCellRealCoordinates(rowIndex, colIndex);
1511-
(itemElement.dataset as any).rowIndex = realCoords.rowIndex;
1495+
const applyPosition = () => {
1496+
this.updateHoverClassNamesForRow(rowIndex);
1497+
// itemElement.style.gridColumn = `${colIndex} / span 1`;
1498+
// itemElement.style.gridRow = `${rowIndex} / span 1`;
1499+
1500+
// (itemElement.dataset as any).elementIndex = elementIndex;
1501+
const realCoords = this.getCellRealCoordinates(rowIndex, colIndex);
1502+
(itemElement.dataset as any).rowIndex = realCoords.rowIndex;
1503+
1504+
(itemElement.dataset as any).colIndex = realCoords.colIndex;
1505+
1506+
if (ITEM_POSITION_WITH_TRANSFORM) {
1507+
this.setTransform(itemElement, rowIndex, colIndex, { x, y }, null);
1508+
1509+
itemElement.style.willChange = 'transform';
1510+
itemElement.style.backfaceVisibility = 'hidden';
1511+
// need to set it to auto
1512+
// in case some fixed cells are reused
1513+
// as the fixed cells had a zIndex
1514+
const hidden = options
1515+
? options.hidden
1516+
: !!this.isCellCovered(rowIndex, colIndex);
1517+
1518+
const zIndex = hidden
1519+
? '-1'
1520+
: // #updatezindex - we need to allow elements use their own zIndex, so we
1521+
// resort to allowing them to have it as a data-z-index attribute
1522+
itemElement.dataset.zIndex || 'auto';
15121523

1513-
(itemElement.dataset as any).colIndex = realCoords.colIndex;
1514-
1515-
if (ITEM_POSITION_WITH_TRANSFORM) {
1516-
this.setTransform(itemElement, rowIndex, colIndex, { x, y }, null);
1517-
1518-
itemElement.style.willChange = 'transform';
1519-
itemElement.style.backfaceVisibility = 'hidden';
1520-
// need to set it to auto
1521-
// in case some fixed cells are reused
1522-
// as the fixed cells had a zIndex
1523-
const hidden = options
1524-
? options.hidden
1525-
: !!this.isCellCovered(rowIndex, colIndex);
1526-
1527-
const zIndex = hidden
1528-
? '-1'
1529-
: // #updatezindex - we need to allow elements use their own zIndex, so we
1530-
// resort to allowing them to have it as a data-z-index attribute
1531-
itemElement.dataset.zIndex || 'auto';
1532-
1533-
//@ts-ignore
1534-
if (itemElement.__zIndex !== zIndex) {
15351524
//@ts-ignore
1536-
itemElement.__zIndex = zIndex;
1537-
itemElement.style.zIndex = zIndex;
1525+
if (itemElement.__zIndex !== zIndex) {
1526+
//@ts-ignore
1527+
itemElement.__zIndex = zIndex;
1528+
itemElement.style.zIndex = zIndex;
1529+
}
1530+
} else {
1531+
itemElement.style.display = '';
1532+
itemElement.style.left = `${x}px`;
1533+
itemElement.style.top = `${y}px`;
15381534
}
1535+
};
1536+
1537+
// if (this.scrolling && this.brain.isHorizontalLayoutBrain) {
1538+
if (this.scrolling && this.brain.isHorizontalLayoutBrain) {
1539+
cell.setPendingAfterCommitWork(applyPosition);
15391540
} else {
1540-
itemElement.style.display = '';
1541-
itemElement.style.left = `${x}px`;
1542-
itemElement.style.top = `${y}px`;
1541+
applyPosition();
15431542
}
15441543
}
15451544
};

source/src/components/RawList/AvoidReactDiff.tsx

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as React from 'react';
2-
import { flushSync } from 'react-dom';
32
import { useLayoutEffect, useRef, useState } from 'react';
43

54
import type { Renderable } from '../types/Renderable';
@@ -9,18 +8,16 @@ export type AvoidReactDiffProps = {
98
name?: string;
109
useraf?: boolean;
1110
updater: SubscriptionCallback<Renderable>;
12-
shouldFlushSync?: () => boolean;
11+
afterCommit?: () => void;
1312
};
1413

15-
const SHOULD_FLUSH_SYNC = () => false;
16-
1714
function AvoidReactDiffFn(props: AvoidReactDiffProps) {
1815
const [children, setChildren] = useState<Renderable>(props.updater.get);
1916

2017
const rafId = useRef<any>(null);
2118

22-
// const shouldFlushSync = SHOULD_FLUSH_SYNC;
23-
const shouldFlushSync = props.shouldFlushSync ?? SHOULD_FLUSH_SYNC;
19+
const afterCommitRef = useRef(props.afterCommit);
20+
afterCommitRef.current = props.afterCommit;
2421

2522
// prev react 19 we had useEffect here
2623
// but there are situations when the updater would be called
@@ -29,36 +26,15 @@ function AvoidReactDiffFn(props: AvoidReactDiffProps) {
2926
// so we use useLayoutEffect here to ensure we pick up those changes
3027
useLayoutEffect(() => {
3128
function onChange(children: Renderable) {
32-
// so when updater triggers a change
33-
// we can re-render and set the children
34-
35-
let FLUSH_SYNC = shouldFlushSync();
36-
3729
if (props.useraf) {
3830
if (rafId.current != null) {
3931
cancelAnimationFrame(rafId.current);
4032
}
4133
rafId.current = requestAnimationFrame(() => {
42-
if (FLUSH_SYNC) {
43-
queueMicrotask(() => {
44-
flushSync(() => {
45-
setChildren(children);
46-
});
47-
});
48-
} else {
49-
setChildren(children);
50-
}
34+
setChildren(children);
5135
});
5236
} else {
53-
if (FLUSH_SYNC) {
54-
queueMicrotask(() => {
55-
flushSync(() => {
56-
setChildren(children);
57-
});
58-
});
59-
} else {
60-
setChildren(children);
61-
}
37+
setChildren(children);
6238
}
6339
}
6440
const remove = props.updater.onChange(onChange);
@@ -69,7 +45,13 @@ function AvoidReactDiffFn(props: AvoidReactDiffProps) {
6945
}
7046
remove();
7147
};
72-
}, [props.updater, props.useraf, shouldFlushSync]);
48+
}, [props.updater, props.useraf]);
49+
50+
// Fires synchronously after React commits DOM changes but before browser paint.
51+
// Used to sync direct DOM work (like transforms) with React's commit cycle.
52+
useLayoutEffect(() => {
53+
afterCommitRef.current?.();
54+
});
7355

7456
return (children as React.ReactNode) ?? null;
7557
}

0 commit comments

Comments
 (0)