From ed47a7f7f6aa311ab1ccaf0131a17f66a83bb91d Mon Sep 17 00:00:00 2001 From: Adam Cooke Date: Wed, 24 Sep 2025 17:00:27 +0100 Subject: [PATCH 1/5] Added ability to specify VirtualizationOptions in enableVirtualization property. --- src/DataGrid.tsx | 71 +++++++---- src/hooks/useCalculatedColumns.ts | 10 +- src/hooks/useViewportRows.ts | 7 +- src/types.ts | 32 +++++ website/routeTree.gen.ts | 200 +++++++++++++++--------------- website/routes/MillionCells.tsx | 1 + 6 files changed, 187 insertions(+), 134 deletions(-) diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 18f99cf024..71e3e38845 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 { + isVirtualizationOptions, + 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 } 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 == false) { + 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..5bba884a32 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) { 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}} /> ); } From af3e87d56b31a5f513fb89fc042ecb622aabef92 Mon Sep 17 00:00:00 2001 From: Adam Cooke Date: Thu, 25 Sep 2025 11:41:05 +0100 Subject: [PATCH 2/5] Refactor virtualization options import and simplify conditional check in DataGrid --- src/DataGrid.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index 71e3e38845..ba44ca4866 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -39,7 +39,6 @@ import { sign } from './utils'; import { - isVirtualizationOptions, type CalculatedColumn, type CellClipboardEvent, type CellCopyArgs, @@ -62,7 +61,8 @@ import { type SelectHeaderRowEvent, type SelectRowEvent, type SortColumn, - type VirtualizationOptions + type VirtualizationOptions, + isVirtualizationOptions } from './types'; import { defaultRenderCell } from './Cell'; import { renderCheckbox as defaultRenderCheckbox } from './cellRenderers'; @@ -328,7 +328,7 @@ export function DataGrid(props: DataGridPr return rawEnableVirtualization; } if(typeof rawEnableVirtualization === 'boolean') { - if(rawEnableVirtualization == false) { + if(!rawEnableVirtualization) { return {rows: false, columns: false, rowsOverscanThreshold: 4}; } } From 7d31c1ac8da5452171adf3ccb71b00edd86009f1 Mon Sep 17 00:00:00 2001 From: Adam Cooke Date: Thu, 25 Sep 2025 12:10:13 +0100 Subject: [PATCH 3/5] Fix for enabledVirtualisation.columns == undefined defaulting to false not true --- .npmrc | 1 + package.json | 11 +++++------ src/hooks/useCalculatedColumns.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.npmrc b/.npmrc index 80bcbed90c..e0cbd8cc2c 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ legacy-peer-deps = true +@QualityAppLtd:registry=https://npm.pkg.github.com \ No newline at end of file diff --git a/package.json b/package.json index 7f310b7418..0474318c92 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "react-data-grid", + "name": "@qualityappltd/react-data-grid", "version": "7.0.0-beta.57", "license": "MIT", "description": "Feature-rich and customizable data grid React component", @@ -9,9 +9,9 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/Comcast/react-data-grid.git" + "url": "git+https://github.com/QualityAppLtd/react-data-grid.git" }, - "bugs": "https://github.com/Comcast/react-data-grid/issues", + "bugs": "https://github.com/QualityAppLtd/react-data-grid/issues", "type": "module", "exports": { "./lib/styles.css": "./lib/styles.css", @@ -44,9 +44,8 @@ "eslint:fix": "node --run eslint -- --fix", "prettier:check": "prettier --check .", "prettier:format": "prettier --write .", - "typecheck": "tsc --build", - "prepublishOnly": "npm install && node --run build", - "postpublish": "git push --follow-tags origin HEAD" + "typecheck": "tsc --build" + }, "dependencies": { "clsx": "^2.0.0" diff --git a/src/hooks/useCalculatedColumns.ts b/src/hooks/useCalculatedColumns.ts index 5bba884a32..a8d8ec5e62 100644 --- a/src/hooks/useCalculatedColumns.ts +++ b/src/hooks/useCalculatedColumns.ts @@ -204,7 +204,7 @@ export function useCalculatedColumns({ }, [getColumnWidth, columns, lastFrozenColumnIndex]); const [colOverscanStartIdx, colOverscanEndIdx] = useMemo((): [number, number] => { - if (!enableVirtualization.columns) { + if (enableVirtualization.columns === false) { return [0, columns.length - 1]; } // get the viewport's left side and right side positions for non-frozen columns From 48219e82836e0801e03998ead5fe0d1cf798b3fc Mon Sep 17 00:00:00 2001 From: Adam Cooke Date: Thu, 25 Sep 2025 12:15:36 +0100 Subject: [PATCH 4/5] Remove unnecessary registry configuration from .npmrc --- .npmrc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.npmrc b/.npmrc index e0cbd8cc2c..ca1e9d98b5 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1 @@ -legacy-peer-deps = true -@QualityAppLtd:registry=https://npm.pkg.github.com \ No newline at end of file +legacy-peer-deps = true \ No newline at end of file From 3d18918cbc846b2375f2512d6dff943e7861379d Mon Sep 17 00:00:00 2001 From: Adam Cooke Date: Thu, 25 Sep 2025 12:16:24 +0100 Subject: [PATCH 5/5] Update package name and repository URLs to reflect correct ownership --- package.json | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0474318c92..7f310b7418 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "@qualityappltd/react-data-grid", + "name": "react-data-grid", "version": "7.0.0-beta.57", "license": "MIT", "description": "Feature-rich and customizable data grid React component", @@ -9,9 +9,9 @@ ], "repository": { "type": "git", - "url": "git+https://github.com/QualityAppLtd/react-data-grid.git" + "url": "git+https://github.com/Comcast/react-data-grid.git" }, - "bugs": "https://github.com/QualityAppLtd/react-data-grid/issues", + "bugs": "https://github.com/Comcast/react-data-grid/issues", "type": "module", "exports": { "./lib/styles.css": "./lib/styles.css", @@ -44,8 +44,9 @@ "eslint:fix": "node --run eslint -- --fix", "prettier:check": "prettier --check .", "prettier:format": "prettier --write .", - "typecheck": "tsc --build" - + "typecheck": "tsc --build", + "prepublishOnly": "npm install && node --run build", + "postpublish": "git push --follow-tags origin HEAD" }, "dependencies": { "clsx": "^2.0.0"