From 6a230b99798196730d9490ec86cebbfbd63d45a9 Mon Sep 17 00:00:00 2001 From: inokawa <48897392+inokawa@users.noreply.github.com> Date: Wed, 27 Sep 2023 23:24:37 +0900 Subject: [PATCH] Reimplement VGrid with css grid --- src/core/utils.ts | 9 ++ src/react/VGrid.tsx | 195 +++++++++++++++++++++---------- stories/basics/VGrid.stories.tsx | 38 +++++- 3 files changed, 178 insertions(+), 64 deletions(-) diff --git a/src/core/utils.ts b/src/core/utils.ts index 8fe017212..35c8980f3 100644 --- a/src/core/utils.ts +++ b/src/core/utils.ts @@ -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 = ( @@ -14,6 +15,14 @@ export const clamp = ( export const exists = (v: T): v is Exclude => 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; diff --git a/src/react/VGrid.tsx b/src/react/VGrid.tsx index c967c8196..72d71e154 100644 --- a/src/react/VGrid.tsx +++ b/src/react/VGrid.tsx @@ -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, @@ -65,6 +65,8 @@ type CellProps = { _rowIndex: number; _colIndex: number; _element: "div"; + _fixedCol?: boolean; + _fixedRow?: boolean; }; const Cell = memo( @@ -76,6 +78,8 @@ const Cell = memo( _rowIndex: rowIndex, _colIndex: colIndex, _element: Element, + _fixedCol, + _fixedRow, }: CellProps): ReactElement => { const ref = useRef(null); @@ -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( vStore, () => vStore._getItemSize(rowIndex), @@ -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} @@ -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 @@ -256,9 +275,11 @@ export const VGrid = forwardRef( children, row: rowCount, col: colCount, + fixedCols = 0, + fixedRows = 0, cellHeight = 40, cellWidth = 100, - overscan = 2, + overscan = 4, initialRowCount, initialColCount, components: { @@ -400,12 +421,10 @@ export const VGrid = forwardRef( const render = useMemo(() => { const cache = new Map(); 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; }; @@ -434,49 +453,33 @@ export const VGrid = forwardRef( colCount ); - const items = useMemo(() => { - const res: ReactElement[] = []; - for (let i = overscanedStartRowIndex; i <= overscanedEndRowIndex; i++) { - for (let j = overscanedStartColIndex; j <= overscanedEndColIndex; j++) { - res.push( - - ); - } + 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 ( - ({ ...viewportAttrs, style: { overflow: "auto", contain: "strict", - // transform: "translate3d(0px, 0px, 0px)", - // willChange: "scroll-position", - // backfaceVisibility: "hidden", width: "100%", height: "100%", ...viewportAttrs.style, @@ -485,8 +488,76 @@ export const VGrid = forwardRef( values(viewportAttrs) )} > - {items} - +
+ {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( + + ); + } + } + + return elements; + }, [ + render, + overscanedStartRowIndex, + overscanedEndRowIndex, + overscanedStartColIndex, + overscanedEndColIndex, + fixedCols, + fixedRows, + ])} +
+ ); } ); diff --git a/stories/basics/VGrid.stories.tsx b/stories/basics/VGrid.stories.tsx index 102ce1ebb..88d6de922 100644 --- a/stories/basics/VGrid.stories.tsx +++ b/stories/basics/VGrid.stories.tsx @@ -64,8 +64,8 @@ export const DynamicHeight: StoryObj = {
{rowIndex} / {colIndex}
- {Array.from({ length: (rowIndex % 8) + 1 }, () => ( -
Hello world!
+ {Array.from({ length: (rowIndex % 8) + 1 }, (_, i) => ( +
Hello world!
))} )} @@ -74,6 +74,40 @@ export const DynamicHeight: StoryObj = { }, }; +export const Stiky: StoryObj = { + render: () => { + const fixedCols = 2; + const fixedRows = 1; + return ( + + {({ rowIndex, colIndex }) => ( +
+ {rowIndex} / {colIndex} +
+ )} +
+ ); + }, +}; + export const DynamicWidth: StoryObj = { render: () => { return (