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 16, 2023
1 parent e5e5810 commit 0d13f56
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 55 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
185 changes: 132 additions & 53 deletions src/react/VGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
SCROLL_IDLE,
} from "../core/store";
import { useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
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 = {
_width: number;
_hide: boolean;
_element: "div";
_fixedCol?: boolean;
_fixedRow?: boolean;
};

const Cell = memo(
Expand All @@ -79,6 +81,8 @@ const Cell = memo(
_width: width,

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

View workflow job for this annotation

GitHub Actions / check

'width' is declared but its value is never read.
_hide: hide,

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

View workflow job for this annotation

GitHub Actions / check

'hide' is declared but its value is never read.
_element: Element,
_fixedCol,
_fixedRow,
}: CellProps): ReactElement => {
const ref = useRef<HTMLDivElement>(null);

Expand All @@ -96,15 +100,34 @@ const Cell = memo(
display: "grid",
margin: 0,
padding: 0,
position: "absolute",
top: top,
[isRTLDocument() ? "right" : "left"]: left,
visibility: hide ? "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, hide])}
}, [
top,
left,
// width,
// height,
colIndex,
rowIndex,
_fixedCol,
_fixedRow,
])}
>
{children}
</Element>
Expand Down Expand Up @@ -173,6 +196,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 @@ -222,9 +253,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 @@ -349,12 +382,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 @@ -383,53 +414,33 @@ export const VGrid = forwardRef<VGridHandle, VGridProps>(
colCount
);

const items: ReactElement[] = [];
for (
let rowIndex = overscanedStartRowIndex;
rowIndex <= overscanedEndRowIndex;
rowIndex++
) {
for (
let colIndex = overscanedStartColIndex;
colIndex <= overscanedEndColIndex;
colIndex++
) {
items.push(
<Cell
key={genKey(rowIndex, colIndex)}
_resizer={resizer}
_rowIndex={rowIndex}
_colIndex={colIndex}
_top={vStore._getItemOffset(rowIndex)}
_left={hStore._getItemOffset(colIndex)}
_height={vStore._getItemSize(rowIndex)}
_width={hStore._getItemSize(colIndex)}
_hide={
vStore._isUnmeasuredItem(rowIndex) ||
hStore._isUnmeasuredItem(colIndex)
}
_element={ItemElement as "div"}
_children={render(rowIndex, colIndex)}
/>
);
let gridTemplateColumns = "";
for (let i = 0; i < colCount; i++) {
if (i !== 0) {
gridTemplateColumns += " ";
}
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 @@ -438,8 +449,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.
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}

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

View workflow job for this annotation

GitHub Actions / check

Type '{ key: string; _resizer: { _observeRoot(root: HTMLElement): () => void; _observeItem(el: HTMLElement, rowIndex: number, colIndex: number): () => void; }; ... 7 more ...; _fixedRow: boolean; }' is not assignable to type 'IntrinsicAttributes & CellProps'.
_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 0d13f56

Please sign in to comment.