Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ function MyGrid() {

Function to generate unique IDs for group rows. If not provided, a default implementation is used that concatenates parent and group keys with `__`.

###### `rowHeight?: Maybe<number | ((args: RowHeightArgs<R>) => number)>`
###### `rowHeight?: Maybe<number | string | ((args: RowHeightArgs<R>) => number)>`

**Note:** Unlike `DataGrid`, the `rowHeight` function receives [`RowHeightArgs<R>`](#rowheightargstrow) which includes a `type` property to distinguish between regular rows and group rows:

Expand Down
54 changes: 39 additions & 15 deletions src/DataGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
import type { Key, KeyboardEvent } from 'react';
import { useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
import { flushSync } from 'react-dom';

import {
type ActivePosition,
HeaderRowSelectionChangeContext,
HeaderRowSelectionContext,
type HeaderRowSelectionContextValue,
type PartialPosition,
RowSelectionChangeContext,
useActivePosition,
useCalculatedColumns,
Expand All @@ -14,10 +17,7 @@ import {
useScrollState,
useScrollToPosition,
useViewportColumns,
useViewportRows,
type ActivePosition,
type HeaderRowSelectionContextValue,
type PartialPosition
useViewportRows
} from './hooks';
import {
assertIsValidKeyGetter,
Expand Down Expand Up @@ -45,19 +45,19 @@ import type {
CellMouseEventHandler,
CellNavigationMode,
CellPasteArgs,
PositionChangeArgs,
Column,
ColumnOrColumnGroup,
ColumnWidths,
Direction,
FillEvent,
Maybe,
Position,
PositionChangeArgs,
Renderers,
RowsChangeData,
SetActivePositionOptions,
SelectHeaderRowEvent,
SelectRowEvent,
SetActivePositionOptions,
SortColumn
} from './types';
import { defaultRenderCell } from './Cell';
Expand Down Expand Up @@ -138,7 +138,7 @@ export interface DataGridProps<R, SR = unknown, K extends Key = Key> extends Sha
* Height of each row in pixels
* @default 35
*/
rowHeight?: Maybe<number | ((row: NoInfer<R>) => number)>;
rowHeight?: Maybe<number | string | ((row: NoInfer<R>) => number)>;
/**
* Height of the header row in pixels
* @default 35
Expand Down Expand Up @@ -303,9 +303,13 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const renderCheckbox =
renderers?.renderCheckbox ?? defaultRenderers?.renderCheckbox ?? defaultRenderCheckbox;
const noRowsFallback = renderers?.noRowsFallback ?? defaultRenderers?.noRowsFallback;
const enableVirtualization = rawEnableVirtualization ?? true;
const enableVirtualization = rawEnableVirtualization ?? typeof rawRowHeight !== 'string';
const direction = rawDirection ?? 'ltr';

if (enableVirtualization && typeof rowHeight === 'string') {
throw new Error('`rowHeight` cannot be a string when `enableVirtualization` is true.');
}

/**
* ref
*/
Expand Down Expand Up @@ -413,7 +417,9 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
maxRowIdx,
setDraggedOverRowIdx
});
const { setScrollToPosition, scrollToPositionElement } = useScrollToPosition({ gridRef });
const { setScrollToPosition, scrollToPositionElement } = useScrollToPosition({
gridRef
});

const defaultGridComponents = useMemo(
() => ({
Expand Down Expand Up @@ -457,10 +463,16 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
findRowIdx
} = useViewportRows({
rows,
rowHeight,
clientHeight,
scrollTop,
enableVirtualization
enableVirtualization,
...(typeof rowHeight === 'string'
? {
rowHeight,
gridRef,
gridHeight
}
: { rowHeight })
});

const {
Expand Down Expand Up @@ -690,7 +702,11 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
if (isSelectable && shiftKey && key === ' ') {
assertIsValidKeyGetter<R, K>(rowKeyGetter);
const rowKey = rowKeyGetter(row);
selectRow({ row, checked: !selectedRows.has(rowKey), isShiftClick: false });
selectRow({
row,
checked: !selectedRows.has(rowKey),
isShiftClick: false
});
// prevent scrolling
event.preventDefault();
return;
Expand Down Expand Up @@ -776,7 +792,11 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
const indexes: number[] = [];
for (let i = startRowIdx; i < endRowIdx; i++) {
if (isCellEditable({ rowIdx: i, idx })) {
const updatedRow = onFill!({ columnKey: column.key, sourceRow, targetRow: rows[i] });
const updatedRow = onFill!({
columnKey: column.key,
sourceRow,
targetRow: rows[i]
});
if (updatedRow !== rows[i]) {
updatedRows[i] = updatedRow;
indexes.push(i);
Expand Down Expand Up @@ -1048,7 +1068,11 @@ export function DataGrid<R, SR = unknown, K extends Key = Key>(props: DataGridPr
});

function closeEditor(shouldFocus: boolean) {
const newPosition: ActivePosition = { idx: activePosition.idx, rowIdx, mode: 'ACTIVE' };
const newPosition: ActivePosition = {
idx: activePosition.idx,
rowIdx,
mode: 'ACTIVE'
};
setActivePosition(newPosition);
if (shouldFocus) {
setPositionToFocus(newPosition);
Expand Down
93 changes: 88 additions & 5 deletions src/hooks/useViewportRows.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,37 @@
import { useMemo } from 'react';
import { useMemo, type RefObject } from 'react';

import { floor, max, min } from '../utils';

interface ViewportRowsArgs<R> {
interface ViewportRowsBaseArgs<R> {
rows: readonly R[];
rowHeight: number | ((row: R) => number);
clientHeight: number;
scrollTop: number;
enableVirtualization: boolean;
gridHeight?: number;
}

interface ViewportRowsArgsStringHeight {
rowHeight: string;
gridRef: RefObject<HTMLDivElement | null>;
gridHeight: number;
}

interface ViewportRowsArgsRegularHeight<R> {
rowHeight: number | ((row: R) => number);
}

type ViewportRowsArgs<R> = ViewportRowsBaseArgs<R> &
(ViewportRowsArgsStringHeight | ViewportRowsArgsRegularHeight<R>);

export function useViewportRows<R>({
rows,
rowHeight,
clientHeight,
scrollTop,
enableVirtualization
enableVirtualization,
...rest
}: ViewportRowsArgs<R>) {
const { gridRef, gridHeight } = rest as Partial<ViewportRowsArgsStringHeight>;
const { totalRowHeight, gridTemplateRows, getRowTop, getRowHeight, findRowIdx } = useMemo(() => {
if (typeof rowHeight === 'number') {
return {
Expand All @@ -28,6 +43,68 @@ export function useViewportRows<R>({
};
}

if (typeof rowHeight === 'string') {
if (!gridHeight) {
throw new Error(
'props.gridHeight is required when rowHeight is a string. This is needed to calculate the total height of the rows.'
);
}

const getRowElementFirstCell = (element: Element, rowIdx: number): Element | null => {
const nth = element.querySelector('.rdg-header-row') ? rowIdx + 2 : rowIdx + 1;
return element.querySelector(`[role="row"][aria-rowindex="${nth}"] > [role="gridcell"]`);
};

const getRowYTop = (element: Element, rowIdx: number) => {
const cell = getRowElementFirstCell(element, rowIdx);
if (!cell) return -1;
return cell.getBoundingClientRect().top + element.scrollTop;
};

return {
totalRowHeight: gridHeight,
gridTemplateRows: ` repeat(${rows.length}, ${rowHeight})`,
getRowTop(rowIdx: number) {
const element = gridRef?.current;
if (!element) return -1;
const cell = getRowElementFirstCell(element, rowIdx);
if (!cell) return -1;
return cell.getBoundingClientRect().top + element.scrollTop;
},
getRowHeight(rowIdx: number) {
const element = gridRef?.current;
if (!element) return -1;
const cell = getRowElementFirstCell(element, rowIdx);
if (!cell) return -1;
return cell.clientHeight;
},
findRowIdx(offset: number) {
const element = gridRef?.current;
if (!element) return -1;
let start = 0;
let end = rows.length - 1;

while (start <= end) {
const middle = start + floor((end - start) / 2);
const currentScrollTop = getRowYTop(element, middle);
const prevScrollTop = getRowYTop(element, middle - 1);

if (currentScrollTop >= offset && prevScrollTop < offset) return middle;

if (currentScrollTop < offset) {
start = middle + 1;
} else if (currentScrollTop > offset) {
end = middle - 1;
}

if (start > end) return end;
}

return -1;
}
};
}

// Calcule the height of all the rows upfront. This can cause performance issues
// and we can consider using a similar approach as react-window
// https://github.com/bvaughn/react-window/blob/b0a470cc264e9100afcaa1b78ed59d88f7914ad4/src/VariableSizeList.js#L68
Expand Down Expand Up @@ -102,15 +179,21 @@ export function useViewportRows<R>({
return 0;
}
};
}, [rowHeight, rows]);
}, [rowHeight, rows, gridRef, gridHeight]);

let rowOverscanStartIdx = 0;
let rowOverscanEndIdx = rows.length - 1;

if (enableVirtualization) {
const overscanThreshold = 4;
// `findRowIdx` only reads `gridRef.current` in the string-rowHeight branch,
// which is unreachable here because `enableVirtualization` is forced off when
// `rowHeight` is a string (see DataGrid.tsx).
/* eslint-disable react-hooks/refs */
const rowVisibleStartIdx = findRowIdx(scrollTop);
const rowVisibleEndIdx = findRowIdx(scrollTop + clientHeight);
/* eslint-enable react-hooks/refs */

rowOverscanStartIdx = max(0, rowVisibleStartIdx - overscanThreshold);
rowOverscanEndIdx = min(rows.length - 1, rowVisibleEndIdx + overscanThreshold);
}
Expand Down
104 changes: 0 additions & 104 deletions test/browser/rowHeight.test.ts

This file was deleted.

Loading