diff --git a/.npmrc b/.npmrc index 80bcbed90c..ca1e9d98b5 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1 @@ -legacy-peer-deps = true +legacy-peer-deps = true \ No newline at end of file diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 18f99cf024..ba44ca4866 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -38,29 +38,31 @@ import { scrollIntoView, sign } from './utils'; -import type { - CalculatedColumn, - CellClipboardEvent, - CellCopyArgs, - CellKeyboardEvent, - CellKeyDownArgs, - CellMouseEventHandler, - CellNavigationMode, - CellPasteArgs, - CellSelectArgs, - Column, - ColumnOrColumnGroup, - ColumnWidths, - Direction, - FillEvent, - Maybe, - Position, - Renderers, - RowsChangeData, - SelectCellOptions, - SelectHeaderRowEvent, - SelectRowEvent, - SortColumn +import { + type CalculatedColumn, + type CellClipboardEvent, + type CellCopyArgs, + type CellKeyboardEvent, + type CellKeyDownArgs, + type CellMouseEventHandler, + type CellNavigationMode, + type CellPasteArgs, + type CellSelectArgs, + type Column, + type ColumnOrColumnGroup, + type ColumnWidths, + type Direction, + type FillEvent, + type Maybe, + type Position, + type Renderers, + type RowsChangeData, + type SelectCellOptions, + type SelectHeaderRowEvent, + type SelectRowEvent, + type SortColumn, + type VirtualizationOptions, + isVirtualizationOptions } from './types'; import { defaultRenderCell } from './Cell'; import { renderCheckbox as defaultRenderCheckbox } from './cellRenderers'; @@ -219,7 +221,9 @@ export interface DataGridProps extends Sha * Toggles and modes */ /** @default true */ - enableVirtualization?: Maybe; + enableVirtualization?: Maybe; + + /** * Miscellaneous @@ -317,7 +321,22 @@ export function DataGrid(props: DataGridPr const renderCheckbox = renderers?.renderCheckbox ?? defaultRenderers?.renderCheckbox ?? defaultRenderCheckbox; const noRowsFallback = renderers?.noRowsFallback ?? defaultRenderers?.noRowsFallback; - const enableVirtualization = rawEnableVirtualization ?? true; + + const enableVirtualization = useMemo(() => { + + if(isVirtualizationOptions(rawEnableVirtualization)) { + return rawEnableVirtualization; + } + if(typeof rawEnableVirtualization === 'boolean') { + if(!rawEnableVirtualization) { + return {rows: false, columns: false, rowsOverscanThreshold: 4}; + } + } + + return {rows: true, columns: true, rowsOverscanThreshold: 4}; + + }, [rawEnableVirtualization]); + const direction = rawDirection ?? 'ltr'; /** @@ -447,7 +466,7 @@ export function DataGrid(props: DataGridPr rowHeight, clientHeight, scrollTop, - enableVirtualization + enableVirtualization }); const viewportColumns = useViewportColumns({ diff --git a/src/hooks/useCalculatedColumns.ts b/src/hooks/useCalculatedColumns.ts index 0bbc33d0c9..a8d8ec5e62 100644 --- a/src/hooks/useCalculatedColumns.ts +++ b/src/hooks/useCalculatedColumns.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react'; import { clampColumnWidth, max, min } from '../utils'; -import type { CalculatedColumn, CalculatedColumnParent, ColumnOrColumnGroup, Omit } from '../types'; +import { isVirtualizationOptions, type CalculatedColumn, type CalculatedColumnParent, type ColumnOrColumnGroup, type Omit, type VirtualizationOptions } from '../types'; import { renderValue } from '../cellRenderers'; import { SELECT_COLUMN_KEY } from '../Columns'; import type { DataGridProps } from '../DataGrid'; @@ -34,7 +34,7 @@ interface CalculatedColumnsArgs { viewportWidth: number; scrollLeft: number; getColumnWidth: (column: CalculatedColumn) => string | number; - enableVirtualization: boolean; + enableVirtualization: VirtualizationOptions; } export function useCalculatedColumns({ @@ -203,14 +203,14 @@ export function useCalculatedColumns({ return { templateColumns, layoutCssVars, totalFrozenColumnWidth, columnMetrics }; }, [getColumnWidth, columns, lastFrozenColumnIndex]); - const [colOverscanStartIdx, colOverscanEndIdx] = useMemo((): [number, number] => { - if (!enableVirtualization) { + const [colOverscanStartIdx, colOverscanEndIdx] = useMemo((): [number, number] => { + if (enableVirtualization.columns === false) { return [0, columns.length - 1]; } // get the viewport's left side and right side positions for non-frozen columns const viewportLeft = scrollLeft + totalFrozenColumnWidth; const viewportRight = scrollLeft + viewportWidth; - // get first and last non-frozen column indexes + // get first and last non-frozen column indexes const lastColIdx = columns.length - 1; const firstUnfrozenColumnIdx = min(lastFrozenColumnIndex + 1, lastColIdx); diff --git a/src/hooks/useViewportRows.ts b/src/hooks/useViewportRows.ts index 13b8502302..766a6b3717 100644 --- a/src/hooks/useViewportRows.ts +++ b/src/hooks/useViewportRows.ts @@ -1,13 +1,14 @@ import { useMemo } from 'react'; import { floor, max, min } from '../utils'; +import type { VirtualizationOptions } from '../types'; interface ViewportRowsArgs { rows: readonly R[]; rowHeight: number | ((row: R) => number); clientHeight: number; scrollTop: number; - enableVirtualization: boolean; + enableVirtualization: VirtualizationOptions } export function useViewportRows({ @@ -107,8 +108,8 @@ export function useViewportRows({ let rowOverscanStartIdx = 0; let rowOverscanEndIdx = rows.length - 1; - if (enableVirtualization) { - const overscanThreshold = 4; + if (enableVirtualization.rows !== false) { + const overscanThreshold = (enableVirtualization.rows === true || enableVirtualization.rows === undefined) ? 4 : enableVirtualization.rows.overscanThreshold; const rowVisibleStartIdx = findRowIdx(scrollTop); const rowVisibleEndIdx = findRowIdx(scrollTop + clientHeight); rowOverscanStartIdx = max(0, rowVisibleStartIdx - overscanThreshold); diff --git a/src/types.ts b/src/types.ts index 96d0013147..04cf1ff086 100644 --- a/src/types.ts +++ b/src/types.ts @@ -349,3 +349,35 @@ export type ColumnWidths = ReadonlyMap; export type Direction = 'ltr' | 'rtl'; export type ResizedWidth = number | 'max-content'; + + +export interface VirtualizationOptions { + /** Enable row virtualization */ + /** @default 4 */ + rows?: boolean | {overscanThreshold: number}; + columns?: boolean; +} +export function isVirtualizationOptions(obj: unknown): obj is VirtualizationOptions { + if (typeof obj !== 'object' || obj === null) return false; + const o = obj as VirtualizationOptions; + + if ('rows' in o && typeof o.rows !== 'undefined') { + if (typeof o.rows !== 'boolean') { + if ( + typeof o.rows !== 'object' || + o.rows === null || + !('overscanThreshold' in o.rows) || + typeof (o.rows as any).overscanThreshold !== 'number' + ) { + return false; + } + } + } + + if ('columns' in o && typeof o.columns !== 'undefined' && typeof o.columns !== 'boolean') { + return false; + } + + return true; +} + diff --git a/website/routeTree.gen.ts b/website/routeTree.gen.ts index cf9eb65c5b..673c81a1d2 100644 --- a/website/routeTree.gen.ts +++ b/website/routeTree.gen.ts @@ -307,74 +307,74 @@ export interface RootRouteChildren { declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/': { - id: '/' - path: '/' - fullPath: '/' - preLoaderRoute: typeof IndexRouteImport + '/VariableRowHeight': { + id: '/VariableRowHeight' + path: '/VariableRowHeight' + fullPath: '/VariableRowHeight' + preLoaderRoute: typeof VariableRowHeightRouteImport parentRoute: typeof rootRouteImport } - '/AllFeatures': { - id: '/AllFeatures' - path: '/AllFeatures' - fullPath: '/AllFeatures' - preLoaderRoute: typeof AllFeaturesRouteImport + '/TreeView': { + id: '/TreeView' + path: '/TreeView' + fullPath: '/TreeView' + preLoaderRoute: typeof TreeViewRouteImport parentRoute: typeof rootRouteImport } - '/Animation': { - id: '/Animation' - path: '/Animation' - fullPath: '/Animation' - preLoaderRoute: typeof AnimationRouteImport + '/ScrollToCell': { + id: '/ScrollToCell' + path: '/ScrollToCell' + fullPath: '/ScrollToCell' + preLoaderRoute: typeof ScrollToCellRouteImport parentRoute: typeof rootRouteImport } - '/CellNavigation': { - id: '/CellNavigation' - path: '/CellNavigation' - fullPath: '/CellNavigation' - preLoaderRoute: typeof CellNavigationRouteImport + '/RowsReordering': { + id: '/RowsReordering' + path: '/RowsReordering' + fullPath: '/RowsReordering' + preLoaderRoute: typeof RowsReorderingRouteImport parentRoute: typeof rootRouteImport } - '/ColumnGrouping': { - id: '/ColumnGrouping' - path: '/ColumnGrouping' - fullPath: '/ColumnGrouping' - preLoaderRoute: typeof ColumnGroupingRouteImport + '/RowGrouping': { + id: '/RowGrouping' + path: '/RowGrouping' + fullPath: '/RowGrouping' + preLoaderRoute: typeof RowGroupingRouteImport parentRoute: typeof rootRouteImport } - '/ColumnSpanning': { - id: '/ColumnSpanning' - path: '/ColumnSpanning' - fullPath: '/ColumnSpanning' - preLoaderRoute: typeof ColumnSpanningRouteImport + '/ResizableGrid': { + id: '/ResizableGrid' + path: '/ResizableGrid' + fullPath: '/ResizableGrid' + preLoaderRoute: typeof ResizableGridRouteImport parentRoute: typeof rootRouteImport } - '/ColumnsReordering': { - id: '/ColumnsReordering' - path: '/ColumnsReordering' - fullPath: '/ColumnsReordering' - preLoaderRoute: typeof ColumnsReorderingRouteImport + '/NoRows': { + id: '/NoRows' + path: '/NoRows' + fullPath: '/NoRows' + preLoaderRoute: typeof NoRowsRouteImport parentRoute: typeof rootRouteImport } - '/CommonFeatures': { - id: '/CommonFeatures' - path: '/CommonFeatures' - fullPath: '/CommonFeatures' - preLoaderRoute: typeof CommonFeaturesRouteImport + '/MillionCells': { + id: '/MillionCells' + path: '/MillionCells' + fullPath: '/MillionCells' + preLoaderRoute: typeof MillionCellsRouteImport parentRoute: typeof rootRouteImport } - '/ContextMenu': { - id: '/ContextMenu' - path: '/ContextMenu' - fullPath: '/ContextMenu' - preLoaderRoute: typeof ContextMenuRouteImport + '/MasterDetail': { + id: '/MasterDetail' + path: '/MasterDetail' + fullPath: '/MasterDetail' + preLoaderRoute: typeof MasterDetailRouteImport parentRoute: typeof rootRouteImport } - '/CustomizableRenderers': { - id: '/CustomizableRenderers' - path: '/CustomizableRenderers' - fullPath: '/CustomizableRenderers' - preLoaderRoute: typeof CustomizableRenderersRouteImport + '/InfiniteScrolling': { + id: '/InfiniteScrolling' + path: '/InfiniteScrolling' + fullPath: '/InfiniteScrolling' + preLoaderRoute: typeof InfiniteScrollingRouteImport parentRoute: typeof rootRouteImport } '/HeaderFilters': { @@ -384,74 +384,74 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof HeaderFiltersRouteImport parentRoute: typeof rootRouteImport } - '/InfiniteScrolling': { - id: '/InfiniteScrolling' - path: '/InfiniteScrolling' - fullPath: '/InfiniteScrolling' - preLoaderRoute: typeof InfiniteScrollingRouteImport + '/CustomizableRenderers': { + id: '/CustomizableRenderers' + path: '/CustomizableRenderers' + fullPath: '/CustomizableRenderers' + preLoaderRoute: typeof CustomizableRenderersRouteImport parentRoute: typeof rootRouteImport } - '/MasterDetail': { - id: '/MasterDetail' - path: '/MasterDetail' - fullPath: '/MasterDetail' - preLoaderRoute: typeof MasterDetailRouteImport + '/ContextMenu': { + id: '/ContextMenu' + path: '/ContextMenu' + fullPath: '/ContextMenu' + preLoaderRoute: typeof ContextMenuRouteImport parentRoute: typeof rootRouteImport } - '/MillionCells': { - id: '/MillionCells' - path: '/MillionCells' - fullPath: '/MillionCells' - preLoaderRoute: typeof MillionCellsRouteImport + '/CommonFeatures': { + id: '/CommonFeatures' + path: '/CommonFeatures' + fullPath: '/CommonFeatures' + preLoaderRoute: typeof CommonFeaturesRouteImport parentRoute: typeof rootRouteImport } - '/NoRows': { - id: '/NoRows' - path: '/NoRows' - fullPath: '/NoRows' - preLoaderRoute: typeof NoRowsRouteImport + '/ColumnsReordering': { + id: '/ColumnsReordering' + path: '/ColumnsReordering' + fullPath: '/ColumnsReordering' + preLoaderRoute: typeof ColumnsReorderingRouteImport parentRoute: typeof rootRouteImport } - '/ResizableGrid': { - id: '/ResizableGrid' - path: '/ResizableGrid' - fullPath: '/ResizableGrid' - preLoaderRoute: typeof ResizableGridRouteImport + '/ColumnSpanning': { + id: '/ColumnSpanning' + path: '/ColumnSpanning' + fullPath: '/ColumnSpanning' + preLoaderRoute: typeof ColumnSpanningRouteImport parentRoute: typeof rootRouteImport } - '/RowGrouping': { - id: '/RowGrouping' - path: '/RowGrouping' - fullPath: '/RowGrouping' - preLoaderRoute: typeof RowGroupingRouteImport + '/ColumnGrouping': { + id: '/ColumnGrouping' + path: '/ColumnGrouping' + fullPath: '/ColumnGrouping' + preLoaderRoute: typeof ColumnGroupingRouteImport parentRoute: typeof rootRouteImport } - '/RowsReordering': { - id: '/RowsReordering' - path: '/RowsReordering' - fullPath: '/RowsReordering' - preLoaderRoute: typeof RowsReorderingRouteImport + '/CellNavigation': { + id: '/CellNavigation' + path: '/CellNavigation' + fullPath: '/CellNavigation' + preLoaderRoute: typeof CellNavigationRouteImport parentRoute: typeof rootRouteImport } - '/ScrollToCell': { - id: '/ScrollToCell' - path: '/ScrollToCell' - fullPath: '/ScrollToCell' - preLoaderRoute: typeof ScrollToCellRouteImport + '/Animation': { + id: '/Animation' + path: '/Animation' + fullPath: '/Animation' + preLoaderRoute: typeof AnimationRouteImport parentRoute: typeof rootRouteImport } - '/TreeView': { - id: '/TreeView' - path: '/TreeView' - fullPath: '/TreeView' - preLoaderRoute: typeof TreeViewRouteImport + '/AllFeatures': { + id: '/AllFeatures' + path: '/AllFeatures' + fullPath: '/AllFeatures' + preLoaderRoute: typeof AllFeaturesRouteImport parentRoute: typeof rootRouteImport } - '/VariableRowHeight': { - id: '/VariableRowHeight' - path: '/VariableRowHeight' - fullPath: '/VariableRowHeight' - preLoaderRoute: typeof VariableRowHeightRouteImport + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport parentRoute: typeof rootRouteImport } } diff --git a/website/routes/MillionCells.tsx b/website/routes/MillionCells.tsx index 06bdec2df0..5757574246 100644 --- a/website/routes/MillionCells.tsx +++ b/website/routes/MillionCells.tsx @@ -35,6 +35,7 @@ function MillionCells() { rowHeight={22} className="fill-grid" direction={direction} + enableVirtualization={{rows: {overscanThreshold: 4}, columns: true}} /> ); }