Skip to content

Commit

Permalink
Reimplement VGrid with css grid
Browse files Browse the repository at this point in the history
  • Loading branch information
inokawa committed Oct 8, 2023
1 parent 7812b79 commit 6a230b9
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 64 deletions.
9 changes: 9 additions & 0 deletions src/core/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const abs = Math.abs;
export const now = Date.now;
export const values = Object.values;
export const isArray = Array.isArray;
export const arrayFrom = Array.from;
export const timeout = setTimeout;

export const clamp = (
Expand All @@ -14,6 +15,14 @@ export const clamp = (

export const exists = <T>(v: T): v is Exclude<T, null | undefined> => v != null;

export const indexes = (start: number, end: number): number[] => {
const array: number[] = [];
for (let i = start; i <= end; i++) {
array.push(i);
}
return array;
};

export const median = (arr: number[]): number => {
const s = [...arr].sort((a, b) => a - b);
const mid = (arr.length / 2) | 0;
Expand Down
195 changes: 133 additions & 62 deletions src/react/VGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
} from "../core/store";
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
import { useSelector } from "./useSelector";
import { values } from "../core/utils";
import { arrayFrom, indexes, values } from "../core/utils";
import { createScroller } from "../core/scroller";
import {
clampEndIndex,
Expand Down Expand Up @@ -65,6 +65,8 @@ type CellProps = {
_rowIndex: number;
_colIndex: number;
_element: "div";
_fixedCol?: boolean;
_fixedRow?: boolean;
};

const Cell = memo(
Expand All @@ -76,6 +78,8 @@ const Cell = memo(
_rowIndex: rowIndex,
_colIndex: colIndex,
_element: Element,
_fixedCol,
_fixedRow,
}: CellProps): ReactElement => {
const ref = useRef<HTMLDivElement>(null);

Expand All @@ -91,18 +95,6 @@ const Cell = memo(
UPDATE_SIZE,
true
);
const vHide = useSelector(
vStore,
() => vStore._isUnmeasuredItem(rowIndex),
UPDATE_SIZE,
true
);
const hHide = useSelector(
hStore,
() => hStore._isUnmeasuredItem(colIndex),
UPDATE_SIZE,
true
);
const height = useSelector(

Check failure on line 98 in src/react/VGrid.tsx

View workflow job for this annotation

GitHub Actions / check

'height' is declared but its value is never read.
vStore,
() => vStore._getItemSize(rowIndex),
Expand Down Expand Up @@ -130,15 +122,34 @@ const Cell = memo(
display: "grid",
margin: 0,
padding: 0,
position: "absolute",
top: top,
[isRtlDocument() ? "right" : "left"]: left,
visibility: vHide || hHide ? "hidden" : "visible",
minHeight: height,
minWidth: width,
gridColumn: `${colIndex + 1} / ${colIndex + 1}`,
gridRow: `${rowIndex + 1} / ${rowIndex + 1}`,
visibility: "visible",
// textOverflow: "ellipsis",
// minHeight: height,
// minWidth: width,
};
if (_fixedCol) {
style.position = "sticky";
style[isRtlDocument() ? "right" : "left"] = left;
style.zIndex = 1;
}
if (_fixedRow) {
style.position = "sticky";
style.top = top;
style.zIndex = ((style.zIndex as number) || 0) + 1;
}
return style;
}, [top, left, width, height, vHide, hHide])}
}, [
top,
left,
// width,
// height,
colIndex,
rowIndex,
_fixedCol,
_fixedRow,
])}
>
{children}
</Element>
Expand Down Expand Up @@ -207,6 +218,14 @@ export interface VGridProps extends ViewportComponentAttributes {
* Total column length of grid.
*/
col: number;
/**
*
*/
fixedRows?: number;
/**
*
*/
fixedCols?: number;
/**
* Cell height hint for unmeasured items. It's recommended to specify this prop if item sizes are fixed and known, or much larger than the defaultValue. It will help to reduce scroll jump when items are measured.
* @defaultValue 40
Expand Down Expand Up @@ -256,9 +275,11 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
children,
row: rowCount,
col: colCount,
fixedCols = 0,
fixedRows = 0,
cellHeight = 40,
cellWidth = 100,
overscan = 2,
overscan = 4,
initialRowCount,
initialColCount,
components: {
Expand Down Expand Up @@ -400,12 +421,10 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
const render = useMemo(() => {
const cache = new Map<string, ReactNode>();
return (rowIndex: number, colIndex: number) => {
let e: ReactNode | undefined = cache.get(genKey(rowIndex, colIndex));
const key = genKey(rowIndex, colIndex);
let e: ReactNode | undefined = cache.get(key);
if (!e) {
cache.set(
genKey(rowIndex, colIndex),
(e = children({ rowIndex, colIndex }))
);
cache.set(key, (e = children({ rowIndex, colIndex })));
}
return e;
};
Expand Down Expand Up @@ -434,49 +453,33 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
colCount
);

const items = useMemo(() => {
const res: ReactElement[] = [];
for (let i = overscanedStartRowIndex; i <= overscanedEndRowIndex; i++) {
for (let j = overscanedStartColIndex; j <= overscanedEndColIndex; j++) {
res.push(
<Cell
key={genKey(i, j)}
_resizer={resizer}
_vStore={vStore}
_hStore={hStore}
_rowIndex={i}
_colIndex={j}
_element={ItemElement as "div"}
_children={render(i, j)}
/>
);
}
let gridTemplateColumns = "";
for (let i = 0; i < colCount; i++) {
if (i !== 0) {
gridTemplateColumns += " ";
}

return res;
}, [
render,
overscanedStartRowIndex,
overscanedEndRowIndex,
overscanedStartColIndex,
overscanedEndColIndex,
]);
gridTemplateColumns += `min(${hStore._getItemSize(i)}px)`;
}
let gridTemplateRows = "";
for (let i = 0; i < rowCount; i++) {
if (i !== 0) {
gridTemplateRows += " ";
}
// gridTemplateRows += `min(${vStore._getItemSize(i)}px)`;
}

return (
<Viewport
<div
ref={rootRef}
width={width}
height={height}
scrolling={vScrolling || hScrolling}
attrs={useMemo(
// width={width}
// height={height}
// scrolling={vScrolling || hScrolling}
{...useMemo(
() => ({
...viewportAttrs,
style: {
overflow: "auto",
contain: "strict",
// transform: "translate3d(0px, 0px, 0px)",
// willChange: "scroll-position",
// backfaceVisibility: "hidden",
width: "100%",
height: "100%",
...viewportAttrs.style,
Expand All @@ -485,8 +488,76 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
values(viewportAttrs)
)}
>
{items}
</Viewport>
<div
style={{
display: "grid",
gridTemplateColumns: `repeat(${colCount}, 1fr)`,
// gridTemplateColumns,
// gridTemplateRows,
// gridTemplateRows: "auto",
// gridTemplateRows: "fit-content",
// TODO fallback with observerd size. 画面外に出たらrowが消えちゃうので。。。。。
gridTemplateRows: `repeat(${rowCount}, min-content)`,
// gridTemplateRows: "min-content",
// gridAutoColumns: "max-content",
// gridAutoRows: "max-content",
// gridAutoRows: `minmax(100px, auto)`,
width,
height,
visibility: "hidden",
pointerEvents: vScrolling || hScrolling ? "none" : "auto",
}}
>
{useMemo(() => {
const elements: ReactElement[] = [];

const fixedRowsSet = new Set(indexes(0, fixedRows - 1));
const fixedColsSet = new Set(indexes(0, fixedCols - 1));

const renderableRows = arrayFrom(
new Set([
...arrayFrom(fixedRowsSet),
...indexes(overscanedStartRowIndex, overscanedEndRowIndex),
])
);
const renderableCols = arrayFrom(
new Set([
...arrayFrom(fixedColsSet),
...indexes(overscanedStartColIndex, overscanedEndColIndex),
])
);

for (const rowIndex of renderableRows) {
for (const colIndex of renderableCols) {
elements.push(
<Cell
key={genKey(rowIndex, colIndex)}
_resizer={resizer}
_vStore={vStore}
_hStore={hStore}
_rowIndex={rowIndex}
_colIndex={colIndex}
_element={ItemElement as "div"}
_children={render(rowIndex, colIndex)}
_fixedCol={fixedColsSet.has(colIndex)}
_fixedRow={fixedRowsSet.has(rowIndex)}
/>
);
}
}

return elements;
}, [
render,
overscanedStartRowIndex,
overscanedEndRowIndex,
overscanedStartColIndex,
overscanedEndColIndex,
fixedCols,
fixedRows,
])}
</div>
</div>
);
}
);
38 changes: 36 additions & 2 deletions stories/basics/VGrid.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ export const DynamicHeight: StoryObj = {
<div>
{rowIndex} / {colIndex}
</div>
{Array.from({ length: (rowIndex % 8) + 1 }, () => (
<div>Hello world!</div>
{Array.from({ length: (rowIndex % 8) + 1 }, (_, i) => (
<div key={i}>Hello world!</div>
))}
</div>
)}
Expand All @@ -74,6 +74,40 @@ export const DynamicHeight: StoryObj = {
},
};

export const Stiky: StoryObj = {
render: () => {
const fixedCols = 2;
const fixedRows = 1;
return (
<VGrid
style={{ height: "100vh" }}
row={1000}
col={500}
fixedCols={fixedCols}
fixedRows={fixedRows}
>
{({ rowIndex, colIndex }) => (
<div
style={{
border: "solid 1px gray",
background:
rowIndex < fixedRows
? "darkgray"
: colIndex < fixedCols
? "lightgray"
: "white",
color: rowIndex < fixedRows ? "white" : undefined,
padding: 4,
}}
>
{rowIndex} / {colIndex}
</div>
)}
</VGrid>
);
},
};

export const DynamicWidth: StoryObj = {
render: () => {
return (
Expand Down

0 comments on commit 6a230b9

Please sign in to comment.