From e1e83d107204bd579180c2b3d081f26e5ff6868c Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Sat, 21 Feb 2026 04:29:35 +0000 Subject: [PATCH 1/7] Multiple renames for clarity and consistency --- README.md | 68 ++++++++++--------- src/Cell.tsx | 12 ++-- src/Columns.tsx | 27 +++++--- src/DataGrid.tsx | 11 +-- src/EditCell.tsx | 10 +-- src/Row.tsx | 6 +- src/SummaryCell.tsx | 8 +-- src/TreeDataGrid.tsx | 20 +++--- ...ctCellFormatter.tsx => SelectCheckbox.tsx} | 6 +- src/cellRenderers/index.ts | 2 +- src/cellRenderers/renderToggleGroup.tsx | 6 +- src/cellRenderers/renderValue.tsx | 4 +- src/editors/renderTextEditor.tsx | 4 +- src/hooks/useCalculatedColumns.ts | 2 +- src/hooks/useLatestFunc.ts | 15 ++-- src/index.ts | 16 ++--- src/renderHeaderCell.tsx | 8 +-- src/types.ts | 43 ++++++------ src/utils/eventUtils.ts | 23 ++++--- test/browser/TreeDataGrid.test.tsx | 6 +- test/browser/column/renderCell.test.tsx | 12 ++-- test/browser/keyboardNavigation.test.tsx | 6 +- test/browser/renderers.test.tsx | 6 +- ...ExpanderFormatter.tsx => CellExpander.tsx} | 8 +-- ...ggableRowRenderer.tsx => DraggableRow.tsx} | 6 +- website/components/index.ts | 4 +- website/renderers/renderCoordinates.ts | 4 +- website/routes/CommonFeatures.tsx | 14 ++-- website/routes/CustomizableRenderers.tsx | 4 +- website/routes/HeaderFilters.tsx | 28 ++++---- website/routes/InfiniteScrolling.tsx | 20 +++--- website/routes/MasterDetail.tsx | 4 +- website/routes/NoRows.tsx | 4 +- website/routes/RowGrouping.tsx | 2 +- website/routes/RowsReordering.tsx | 4 +- website/routes/TreeView.tsx | 4 +- website/utils.tsx | 9 +-- 37 files changed, 226 insertions(+), 210 deletions(-) rename src/cellRenderers/{SelectCellFormatter.tsx => SelectCheckbox.tsx} (83%) rename website/components/{CellExpanderFormatter.tsx => CellExpander.tsx} (80%) rename website/components/{DraggableRowRenderer.tsx => DraggableRow.tsx} (93%) diff --git a/README.md b/README.md index 2940542996..dc6fed68fd 100644 --- a/README.md +++ b/README.md @@ -242,7 +242,7 @@ Control column widths using the [`width`](#width-maybenumber--string), [`minWidt ### Custom Renderers -Replace default components with custom implementations using the [`renderers`](#renderers-mayberenderersr-sr) prop. Columns can also have custom renderers using the [`renderCell`](#rendercell-maybeprops-rendercellpropstrow-tsummaryrow--reactnode), [`renderHeaderCell`](#renderheadercell-maybeprops-renderheadercellpropstrow-tsummaryrow--reactnode), [`renderSummaryCell`](#rendersummarycell-maybeprops-rendersummarycellpropstsummaryrow-trow--reactnode), [`renderGroupCell`](#rendergroupcell-maybeprops-rendergroupcellpropstrow-tsummaryrow--reactnode), and [`renderEditCell`](#rendereditcell-maybeprops-rendereditcellpropstrow-tsummaryrow--reactnode) properties. +Replace default components with custom implementations using the [`renderers`](#renderers-mayberenderersr-sr) prop. Columns can also have custom renderers using the [`renderCell`](#rendercell-maybeprops-rendercellcontentpropstrow-tsummaryrow--reactnode), [`renderHeaderCell`](#renderheadercell-maybeprops-renderheadercellcontentpropstrow-tsummaryrow--reactnode), [`renderSummaryCell`](#rendersummarycell-maybeprops-rendersummarycellcontentpropstsummaryrow-trow--reactnode), [`renderGroupCell`](#rendergroupcell-maybeprops-rendergroupcellcontentpropstrow-tsummaryrow--reactnode), and [`renderEditCell`](#rendereditcell-maybeprops-rendereditcellcontentpropstrow-tsummaryrow--reactnode) properties. ## API Reference @@ -869,6 +869,8 @@ function rowGrouper(rows: readonly Row[], columnKey: string): Record` **Required.** A set of group IDs that are currently expanded. Group IDs are generated by `groupIdGetter`. @@ -898,6 +900,8 @@ 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 `__`. +:warning: **Performance:** Define this function outside your component or memoize it with `useCallback` to prevent unnecessary re-renders. + ###### `rowHeight?: Maybe) => number)>` **Note:** Unlike `DataGrid`, the `rowHeight` function receives [`RowHeightArgs`](#rowheightargstrow) which includes a `type` property to distinguish between regular rows and group rows: @@ -927,11 +931,11 @@ The default cell component. Can be wrapped via the `renderers.renderCell` prop. ##### Props -[`CellRendererProps`](#cellrendererpropstrow-tsummaryrow) +[`RenderCellProps`](#rendercellpropstrow-tsummaryrow) -#### `` +#### `` -A formatter component for rendering row selection checkboxes. +A component for rendering row selection checkboxes. ##### Props @@ -1018,7 +1022,7 @@ Hook for managing row selection state. Used within custom cell renderers to impl **Example:** ```tsx -function CustomSelectCell({ row }: RenderCellProps) { +function CustomSelectCell({ row }: RenderCellContentProps) { const { isRowSelectionDisabled, isRowSelected, onRowSelectionChange } = useRowSelection(); return ( @@ -1040,7 +1044,7 @@ function CustomSelectCell({ row }: RenderCellProps) { ### Render Functions -#### `renderHeaderCell(props: RenderHeaderCellProps)` +#### `renderHeaderCell(props: RenderHeaderCellContentProps)` The default header cell renderer. Renders sortable columns with sort indicators. @@ -1059,7 +1063,7 @@ const columns: readonly Column[] = [ ]; ``` -#### `renderTextEditor(props: RenderEditCellProps)` +#### `renderTextEditor(props: RenderEditCellContentProps)` A basic text editor provided for convenience. @@ -1119,13 +1123,13 @@ import { DataGrid, renderCheckbox } from 'react-data-grid'; />; ``` -#### `renderToggleGroup(props: RenderGroupCellProps)` +#### `renderToggleGroup(props: RenderGroupCellContentProps)` The default group cell renderer used by the columns used for grouping (`groupBy` prop). This renders the expand/collapse toggle. ##### Props -[`RenderGroupCellProps`](#rendergroupcellpropstrow-tsummaryrow) +[`RenderGroupCellContentProps`](#rendergroupcellcontentpropstrow-tsummaryrow) **Example:** @@ -1141,7 +1145,7 @@ const columns: readonly Column[] = [ ]; ``` -#### `renderValue(props: RenderCellProps)` +#### `renderValue(props: RenderCellContentProps)` The default cell renderer that renders the value of `row[column.key]`. @@ -1347,23 +1351,23 @@ Class name(s) for the header cell. Class name(s) for summary cells. Can be a string or a function that returns a class name based on the summary row. -##### `renderCell?: Maybe<(props: RenderCellProps) => ReactNode>` +##### `renderCell?: Maybe<(props: RenderCellContentProps) => ReactNode>` Render function to render the content of cells. -##### `renderHeaderCell?: Maybe<(props: RenderHeaderCellProps) => ReactNode>` +##### `renderHeaderCell?: Maybe<(props: RenderHeaderCellContentProps) => ReactNode>` Render function to render the content of the header cell. -##### `renderSummaryCell?: Maybe<(props: RenderSummaryCellProps) => ReactNode>` +##### `renderSummaryCell?: Maybe<(props: RenderSummaryCellContentProps) => ReactNode>` Render function to render the content of summary cells -##### `renderGroupCell?: Maybe<(props: RenderGroupCellProps) => ReactNode>` +##### `renderGroupCell?: Maybe<(props: RenderGroupCellContentProps) => ReactNode>` Render function to render the content of group cells when using `TreeDataGrid`. -##### `renderEditCell?: Maybe<(props: RenderEditCellProps) => ReactNode>` +##### `renderEditCell?: Maybe<(props: RenderEditCellContentProps) => ReactNode>` Render function to render the content of edit cells. When set, the column is automatically set to be editable @@ -1533,12 +1537,12 @@ function getRowHeight(args: RowHeightArgs): number { ``` -#### `RenderCellProps` +#### `RenderCellContentProps` Props passed to custom cell renderers. ```tsx -interface RenderCellProps { +interface RenderCellContentProps { column: CalculatedColumn; row: TRow; rowIdx: number; @@ -1551,9 +1555,9 @@ interface RenderCellProps { **Example:** ```tsx -import type { RenderCellProps } from 'react-data-grid'; +import type { RenderCellContentProps } from 'react-data-grid'; -function renderCell({ row, column, onRowChange }: RenderCellProps) { +function renderCell({ row, column, onRowChange }: RenderCellContentProps) { return (
{row[column.key]} @@ -1563,12 +1567,12 @@ function renderCell({ row, column, onRowChange }: RenderCellProps) { } ``` -#### `RenderHeaderCellProps` +#### `RenderHeaderCellContentProps` Props passed to custom header cell renderers. ```tsx -interface RenderHeaderCellProps { +interface RenderHeaderCellContentProps { column: CalculatedColumn; sortDirection: SortDirection | undefined; priority: number | undefined; @@ -1576,12 +1580,12 @@ interface RenderHeaderCellProps { } ``` -#### `RenderEditCellProps` +#### `RenderEditCellContentProps` Props passed to custom edit cell renderers (editors). ```tsx -interface RenderEditCellProps { +interface RenderEditCellContentProps { column: CalculatedColumn; row: TRow; rowIdx: number; @@ -1593,9 +1597,9 @@ interface RenderEditCellProps { **Example:** ```tsx -import type { RenderEditCellProps } from 'react-data-grid'; +import type { RenderEditCellContentProps } from 'react-data-grid'; -function CustomEditor({ row, column, onRowChange, onClose }: RenderEditCellProps) { +function renderEditor({ row, column, onRowChange, onClose }: RenderEditCellContentProps) { return ( ` +#### `RenderSummaryCellContentProps` Props passed to summary cell renderers. ```tsx -interface RenderSummaryCellProps { +interface RenderSummaryCellContentProps { column: CalculatedColumn; row: TSummaryRow; tabIndex: number; } ``` -#### `RenderGroupCellProps` +#### `RenderGroupCellContentProps` Props passed to group cell renderers when using `TreeDataGrid`. ```tsx -interface RenderGroupCellProps { +interface RenderGroupCellContentProps { groupKey: unknown; column: CalculatedColumn; row: GroupRow; @@ -1650,14 +1654,14 @@ interface RenderRowProps { gridRowStart: number; lastFrozenColumnIndex: number; draggedOverCellIdx: number | undefined; - selectedCellEditor: ReactElement> | undefined; + selectedCellEditor: ReactElement> | undefined; onRowChange: (column: CalculatedColumn, rowIdx: number, newRow: TRow) => void; rowClass: Maybe<(row: TRow, rowIdx: number) => Maybe>; // ... and event handlers } ``` -#### `CellRendererProps` +#### `RenderCellProps` Props passed to the cell renderer when using `renderers.renderCell`. @@ -1669,7 +1673,7 @@ Custom renderer configuration for the grid. ```tsx interface Renderers { - renderCell?: Maybe<(key: Key, props: CellRendererProps) => ReactNode>; + renderCell?: Maybe<(key: Key, props: RenderCellProps) => ReactNode>; renderCheckbox?: Maybe<(props: RenderCheckboxProps) => ReactNode>; renderRow?: Maybe<(key: Key, props: RenderRowProps) => ReactNode>; renderSortStatus?: Maybe<(props: RenderSortStatusProps) => ReactNode>; diff --git a/src/Cell.tsx b/src/Cell.tsx index 253d4bbd1a..02db3852a0 100644 --- a/src/Cell.tsx +++ b/src/Cell.tsx @@ -3,7 +3,7 @@ import { css } from 'ecij'; import { useRovingTabIndex } from './hooks'; import { createCellEvent, getCellClassname, getCellStyle, isCellEditableUtil } from './utils'; -import type { CellMouseEventHandler, CellRendererProps } from './types'; +import type { CellMouseEventHandler, RenderCellProps } from './types'; const cellDraggedOver = css` @layer rdg.Cell { @@ -33,7 +33,7 @@ function Cell({ selectCell, style, ...props -}: CellRendererProps) { +}: RenderCellProps) { const { tabIndex, childTabIndex, onFocus } = useRovingTabIndex(isCellSelected); const { cellClass } = column; @@ -126,10 +126,10 @@ function Cell({ ); } -const CellComponent = memo(Cell) as (props: CellRendererProps) => React.JSX.Element; +const MemoCell = memo(Cell) as (props: RenderCellProps) => React.JSX.Element; -export default CellComponent; +export { MemoCell as Cell }; -export function defaultRenderCell(key: React.Key, props: CellRendererProps) { - return ; +export function defaultRenderCell(key: React.Key, props: RenderCellProps) { + return ; } diff --git a/src/Columns.tsx b/src/Columns.tsx index 19e1533238..484dc2ad3e 100644 --- a/src/Columns.tsx +++ b/src/Columns.tsx @@ -1,14 +1,19 @@ import { useHeaderRowSelection, useRowSelection } from './hooks'; -import type { Column, RenderCellProps, RenderGroupCellProps, RenderHeaderCellProps } from './types'; -import { SelectCellFormatter } from './cellRenderers'; +import type { + Column, + RenderCellContentProps, + RenderGroupCellContentProps, + RenderHeaderCellContentProps +} from './types'; +import { SelectCheckbox } from './cellRenderers'; export const SELECT_COLUMN_KEY = 'rdg-select-column'; -function HeaderRenderer(props: RenderHeaderCellProps) { +function SelectAllCell(props: RenderHeaderCellContentProps) { const { isIndeterminate, isRowSelected, onRowSelectionChange } = useHeaderRowSelection(); return ( - ) { ); } -function SelectFormatter(props: RenderCellProps) { +function RowSelectCell(props: RenderCellContentProps) { const { isRowSelectionDisabled, isRowSelected, onRowSelectionChange } = useRowSelection(); return ( - ) { ); } -function SelectGroupFormatter(props: RenderGroupCellProps) { +function GroupSelectCell(props: RenderGroupCellContentProps) { const { isRowSelected, onRowSelectionChange } = useRowSelection(); return ( - = { sortable: false, frozen: true, renderHeaderCell(props) { - return ; + return ; }, renderCell(props) { - return ; + return ; }, renderGroupCell(props) { - return ; + return ; } }; diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index ebaeb25d86..cc5b30aee7 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -469,7 +469,7 @@ export function DataGrid(props: DataGridPr */ const handleColumnResizeLatest = useLatestFunc(handleColumnResize); const handleColumnResizeEndLatest = useLatestFunc(handleColumnResizeEnd); - const onColumnsReorderLastest = useLatestFunc(onColumnsReorder); + const onColumnsReorderLatest = useLatestFunc(onColumnsReorder); const onSortColumnsChangeLatest = useLatestFunc(onSortColumnsChange); const onCellMouseDownLatest = useLatestFunc(onCellMouseDown); const onCellClickLatest = useLatestFunc(onCellClick); @@ -477,7 +477,7 @@ export function DataGrid(props: DataGridPr const onCellContextMenuLatest = useLatestFunc(onCellContextMenu); const selectHeaderRowLatest = useLatestFunc(selectHeaderRow); const selectRowLatest = useLatestFunc(selectRow); - const handleFormatterRowChangeLatest = useLatestFunc(updateRow); + const updateRowLatest = useLatestFunc(updateRow); const selectCellLatest = useLatestFunc(selectCell); const selectHeaderCellLatest = useLatestFunc(selectHeaderCell); @@ -962,7 +962,8 @@ export function DataGrid(props: DataGridPr const isLastRow = rowIdx === maxRowIdx; const columnWidth = getColumnWidth(column); const colSpan = column.colSpan?.({ type: 'ROW', row: rows[rowIdx] }) ?? 1; - const { insetInlineStart, ...style } = getCellStyle(column, colSpan); + const style = getCellStyle(column, colSpan); + const { insetInlineStart } = style; const marginEnd = 'calc(var(--rdg-drag-handle-size) * -0.5 + 1px)'; const isLastColumn = column.idx + colSpan - 1 === maxColIdx; const dragHandleStyle: React.CSSProperties = { @@ -1127,7 +1128,7 @@ export function DataGrid(props: DataGridPr selectedCellIdx: selectedRowIdx === rowIdx ? selectedIdx : undefined, draggedOverCellIdx: getDraggedOverCellIdx(rowIdx), lastFrozenColumnIndex, - onRowChange: handleFormatterRowChangeLatest, + onRowChange: updateRowLatest, selectCell: selectCellLatest, selectedCellEditor: getCellEditor(rowIdx), isTreeGrid @@ -1222,7 +1223,7 @@ export function DataGrid(props: DataGridPr columns={getRowViewportColumns(mainHeaderRowIdx)} onColumnResize={handleColumnResizeLatest} onColumnResizeEnd={handleColumnResizeEndLatest} - onColumnsReorder={onColumnsReorderLastest} + onColumnsReorder={onColumnsReorderLatest} sortColumns={sortColumns} onSortColumnsChange={onSortColumnsChangeLatest} lastFrozenColumnIndex={lastFrozenColumnIndex} diff --git a/src/EditCell.tsx b/src/EditCell.tsx index 1138192035..f84df40110 100644 --- a/src/EditCell.tsx +++ b/src/EditCell.tsx @@ -4,11 +4,11 @@ import { css } from 'ecij'; import { createCellEvent, getCellClassname, getCellStyle, onEditorNavigation } from './utils'; import type { CellKeyboardEvent, - CellRendererProps, + RenderCellProps, EditCellKeyDownArgs, Maybe, Omit, - RenderEditCellProps + RenderEditCellContentProps } from './types'; declare global { @@ -50,12 +50,12 @@ const cellEditing = css` } `; -type SharedCellRendererProps = Pick, 'colSpan'>; +type SharedRenderCellProps = Pick, 'colSpan'>; interface EditCellProps extends - Omit, 'onRowChange' | 'onClose'>, - SharedCellRendererProps { + Omit, 'onRowChange' | 'onClose'>, + SharedRenderCellProps { rowIdx: number; onRowChange: (row: R, commitChanges: boolean, shouldFocusCell: boolean) => void; closeEditor: (shouldFocusCell: boolean) => void; diff --git a/src/Row.tsx b/src/Row.tsx index d38ea2bc5d..4c27d0a10e 100644 --- a/src/Row.tsx +++ b/src/Row.tsx @@ -102,10 +102,10 @@ function Row({ ); } -const RowComponent = memo(Row) as (props: RenderRowProps) => React.JSX.Element; +const MemoRow = memo(Row) as (props: RenderRowProps) => React.JSX.Element; -export default RowComponent; +export { MemoRow as Row }; export function defaultRenderRow(key: React.Key, props: RenderRowProps) { - return ; + return ; } diff --git a/src/SummaryCell.tsx b/src/SummaryCell.tsx index d44738ed03..4e7010e900 100644 --- a/src/SummaryCell.tsx +++ b/src/SummaryCell.tsx @@ -2,14 +2,14 @@ import { memo } from 'react'; import { useRovingTabIndex } from './hooks'; import { getCellClassname, getCellStyle } from './utils'; -import type { CellRendererProps } from './types'; +import type { RenderCellProps } from './types'; -type SharedCellRendererProps = Pick< - CellRendererProps, +type SharedRenderCellProps = Pick< + RenderCellProps, 'rowIdx' | 'column' | 'colSpan' | 'isCellSelected' | 'selectCell' >; -interface SummaryCellProps extends SharedCellRendererProps { +interface SummaryCellProps extends SharedRenderCellProps { row: SR; } diff --git a/src/TreeDataGrid.tsx b/src/TreeDataGrid.tsx index 9f6845cad5..24f71cda4c 100644 --- a/src/TreeDataGrid.tsx +++ b/src/TreeDataGrid.tsx @@ -115,27 +115,29 @@ export function TreeDataGrid({ const [groupedRows, rowsCount] = useMemo(() => { if (groupBy.length === 0) return [undefined, rawRows.length]; - const groupRows = ( + function groupRows( rows: readonly R[], - [groupByKey, ...remainingGroupByKeys]: readonly string[], + groupByIndex: number, startRowIndex: number - ): [Readonly>, number] => { + ): [Readonly>, number] { let groupRowsCount = 0; + const groupByKey = groupBy[groupByIndex]; + const isLastGroupBy = groupByIndex === groupBy.length - 1; const groups: GroupByDictionary = {}; + for (const [key, childRows] of Object.entries(rowGrouper(rows, groupByKey))) { // Recursively group each parent group - const [childGroups, childRowsCount] = - remainingGroupByKeys.length === 0 - ? [childRows, childRows.length] - : groupRows(childRows, remainingGroupByKeys, startRowIndex + groupRowsCount + 1); // 1 for parent row + const [childGroups, childRowsCount] = isLastGroupBy + ? [childRows, childRows.length] + : groupRows(childRows, groupByIndex + 1, startRowIndex + groupRowsCount + 1); // 1 for parent row groups[key] = { childRows, childGroups, startRowIndex: startRowIndex + groupRowsCount }; groupRowsCount += childRowsCount + 1; // 1 for parent row } return [groups, groupRowsCount]; - }; + } - return groupRows(rawRows, groupBy, 0); + return groupRows(rawRows, 0, 0); }, [groupBy, rowGrouper, rawRows]); const [rows, isGroupRow] = useMemo((): [ diff --git a/src/cellRenderers/SelectCellFormatter.tsx b/src/cellRenderers/SelectCheckbox.tsx similarity index 83% rename from src/cellRenderers/SelectCellFormatter.tsx rename to src/cellRenderers/SelectCheckbox.tsx index e006386fd7..145a6ebe06 100644 --- a/src/cellRenderers/SelectCellFormatter.tsx +++ b/src/cellRenderers/SelectCheckbox.tsx @@ -6,11 +6,11 @@ type SharedInputProps = Pick< 'disabled' | 'tabIndex' | 'aria-label' | 'aria-labelledby' | 'indeterminate' | 'onChange' >; -interface SelectCellFormatterProps extends SharedInputProps { +interface SelectCheckboxProps extends SharedInputProps { value: boolean; } -export function SelectCellFormatter({ +export function SelectCheckbox({ value, tabIndex, indeterminate, @@ -18,7 +18,7 @@ export function SelectCellFormatter({ onChange, 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy -}: SelectCellFormatterProps) { +}: SelectCheckboxProps) { const renderCheckbox = useDefaultRenderers()!.renderCheckbox!; return renderCheckbox({ diff --git a/src/cellRenderers/index.ts b/src/cellRenderers/index.ts index 0eb0747f28..d126dc2f65 100644 --- a/src/cellRenderers/index.ts +++ b/src/cellRenderers/index.ts @@ -1,4 +1,4 @@ export * from './renderCheckbox'; export * from './renderToggleGroup'; export * from './renderValue'; -export * from './SelectCellFormatter'; +export * from './SelectCheckbox'; diff --git a/src/cellRenderers/renderToggleGroup.tsx b/src/cellRenderers/renderToggleGroup.tsx index ba7884dd7e..aacf370344 100644 --- a/src/cellRenderers/renderToggleGroup.tsx +++ b/src/cellRenderers/renderToggleGroup.tsx @@ -1,6 +1,6 @@ import { css } from 'ecij'; -import type { RenderGroupCellProps } from '../types'; +import type { RenderGroupCellContentProps } from '../types'; const groupCellContent = css` @layer rdg.GroupCellContent { @@ -26,7 +26,7 @@ const caret = css` const caretClassname = `rdg-caret ${caret}`; -export function renderToggleGroup(props: RenderGroupCellProps) { +export function renderToggleGroup(props: RenderGroupCellContentProps) { return ; } @@ -35,7 +35,7 @@ export function ToggleGroup({ isExpanded, tabIndex, toggleGroup -}: RenderGroupCellProps) { +}: RenderGroupCellContentProps) { function handleKeyDown({ key }: React.KeyboardEvent) { if (key === 'Enter') { toggleGroup(); diff --git a/src/cellRenderers/renderValue.tsx b/src/cellRenderers/renderValue.tsx index 8ea0bb06da..fa28d992ed 100644 --- a/src/cellRenderers/renderValue.tsx +++ b/src/cellRenderers/renderValue.tsx @@ -1,6 +1,6 @@ -import type { RenderCellProps } from '../types'; +import type { RenderCellContentProps } from '../types'; -export function renderValue(props: RenderCellProps) { +export function renderValue(props: RenderCellContentProps) { try { return props.row[props.column.key as keyof R] as React.ReactNode; } catch { diff --git a/src/editors/renderTextEditor.tsx b/src/editors/renderTextEditor.tsx index cba7b4a115..a53e9a4eca 100644 --- a/src/editors/renderTextEditor.tsx +++ b/src/editors/renderTextEditor.tsx @@ -1,6 +1,6 @@ import { css } from 'ecij'; -import type { RenderEditCellProps } from '../types'; +import type { RenderEditCellContentProps } from '../types'; const textEditorInternalClassname = css` @layer rdg.TextEditor { @@ -43,7 +43,7 @@ export function renderTextEditor({ column, onRowChange, onClose -}: RenderEditCellProps) { +}: RenderEditCellContentProps) { return ( = { -readonly [P in keyof T]: T[P] extends ReadonlyArray ? Mutable[] : T[P]; diff --git a/src/hooks/useLatestFunc.ts b/src/hooks/useLatestFunc.ts index 634694f3bb..c78c78dd05 100644 --- a/src/hooks/useLatestFunc.ts +++ b/src/hooks/useLatestFunc.ts @@ -3,18 +3,25 @@ import { useCallback, useLayoutEffect, useRef } from 'react'; import type { Maybe } from '../types'; // https://reactjs.org/docs/hooks-faq.html#what-can-i-do-if-my-effect-dependencies-change-too-often -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function useLatestFunc any>>(fn: T): T { + +export function useLatestFunc( + fn: (...args: Args) => void +): (...args: Args) => void; + +export function useLatestFunc( + fn: Maybe<(...args: Args) => void> +): Maybe<(...args: Args) => void>; + +export function useLatestFunc(fn: Maybe<(...args: Args) => void>) { const ref = useRef(fn); useLayoutEffect(() => { ref.current = fn; }); - const callbackFn = useCallback((...args: Parameters>) => { + const callbackFn = useCallback((...args: Args): void => { ref.current!(...args); }, []); - // @ts-expect-error return fn ? callbackFn : fn; } diff --git a/src/index.ts b/src/index.ts index ce5f9900fa..e47cb16bcd 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,12 +8,12 @@ export { } from './DataGrid'; export { TreeDataGrid, type TreeDataGridProps } from './TreeDataGrid'; export { DataGridDefaultRenderersContext } from './DataGridDefaultRenderersContext'; -export { default as Row } from './Row'; -export { default as Cell } from './Cell'; +export { Row } from './Row'; +export { Cell } from './Cell'; export * from './Columns'; export * from './cellRenderers'; export { renderTextEditor } from './editors/renderTextEditor'; -export { default as renderHeaderCell } from './renderHeaderCell'; +export { renderHeaderCell } from './renderHeaderCell'; export { renderSortIcon, renderSortPriority } from './sortStatus'; export { useRowSelection, useHeaderRowSelection } from './hooks'; export type { @@ -26,7 +26,6 @@ export type { CellMouseArgs, CellMouseEvent, CellPasteArgs, - CellRendererProps, CellSelectArgs, ColSpanArgs, Column, @@ -36,17 +35,18 @@ export type { ColumnWidths, Direction, FillEvent, + RenderCellContentProps, RenderCellProps, RenderCheckboxProps, - RenderEditCellProps, + RenderEditCellContentProps, Renderers, - RenderGroupCellProps, - RenderHeaderCellProps, + RenderGroupCellContentProps, + RenderHeaderCellContentProps, RenderRowProps, RenderSortIconProps, RenderSortPriorityProps, RenderSortStatusProps, - RenderSummaryCellProps, + RenderSummaryCellContentProps, RowHeightArgs, RowsChangeData, SelectCellOptions, diff --git a/src/renderHeaderCell.tsx b/src/renderHeaderCell.tsx index eb14ee48f4..5336f7e5dc 100644 --- a/src/renderHeaderCell.tsx +++ b/src/renderHeaderCell.tsx @@ -1,6 +1,6 @@ import { css } from 'ecij'; -import type { RenderHeaderCellProps } from './types'; +import type { RenderHeaderCellContentProps } from './types'; import { useDefaultRenderers } from './DataGridDefaultRenderersContext'; const headerSortCellClassname = css` @@ -19,11 +19,11 @@ const headerSortName = css` const headerSortNameClassname = `rdg-header-sort-name ${headerSortName}`; -export default function renderHeaderCell({ +export function renderHeaderCell({ column, sortDirection, priority -}: RenderHeaderCellProps) { +}: RenderHeaderCellContentProps) { if (!column.sortable) return column.name; return ( @@ -34,7 +34,7 @@ export default function renderHeaderCell({ } type SharedHeaderCellProps = Pick< - RenderHeaderCellProps, + RenderHeaderCellContentProps, 'sortDirection' | 'priority' >; diff --git a/src/types.ts b/src/types.ts index 53d4f4b019..59c0741407 100644 --- a/src/types.ts +++ b/src/types.ts @@ -32,17 +32,23 @@ export interface Column { /** Class name(s) for summary cells */ readonly summaryCellClass?: Maybe Maybe)>; /** Render function to render the content of cells */ - readonly renderCell?: Maybe<(props: RenderCellProps) => ReactNode>; + readonly renderCell?: Maybe<(props: RenderCellContentProps) => ReactNode>; /** Render function to render the content of the header cell */ - readonly renderHeaderCell?: Maybe<(props: RenderHeaderCellProps) => ReactNode>; + readonly renderHeaderCell?: Maybe< + (props: RenderHeaderCellContentProps) => ReactNode + >; /** Render function to render the content of summary cells */ readonly renderSummaryCell?: Maybe< - (props: RenderSummaryCellProps) => ReactNode + (props: RenderSummaryCellContentProps) => ReactNode >; /** Render function to render the content of group cells */ - readonly renderGroupCell?: Maybe<(props: RenderGroupCellProps) => ReactNode>; + readonly renderGroupCell?: Maybe< + (props: RenderGroupCellContentProps) => ReactNode + >; /** Render function to render the content of edit cells. When set, the column is automatically set to be editable */ - readonly renderEditCell?: Maybe<(props: RenderEditCellProps) => ReactNode>; + readonly renderEditCell?: Maybe< + (props: RenderEditCellContentProps) => ReactNode + >; /** Enables cell editing. If set and no editor property specified, then a textinput will be used as the cell editor */ readonly editable?: Maybe boolean)>; readonly colSpan?: Maybe<(args: ColSpanArgs) => Maybe>; @@ -89,8 +95,8 @@ export interface CalculatedColumn extends Column) => ReactNode; - readonly renderHeaderCell: (props: RenderHeaderCellProps) => ReactNode; + readonly renderCell: (props: RenderCellContentProps) => ReactNode; + readonly renderHeaderCell: (props: RenderHeaderCellContentProps) => ReactNode; } export interface ColumnGroup { @@ -120,7 +126,7 @@ export interface Position { readonly rowIdx: number; } -export interface RenderCellProps { +export interface RenderCellContentProps { column: CalculatedColumn; row: TRow; rowIdx: number; @@ -129,13 +135,13 @@ export interface RenderCellProps { onRowChange: (row: TRow) => void; } -export interface RenderSummaryCellProps { +export interface RenderSummaryCellContentProps { column: CalculatedColumn; row: TSummaryRow; tabIndex: number; } -export interface RenderGroupCellProps { +export interface RenderGroupCellContentProps { groupKey: unknown; column: CalculatedColumn; row: GroupRow; @@ -145,7 +151,7 @@ export interface RenderGroupCellProps { toggleGroup: () => void; } -export interface RenderEditCellProps { +export interface RenderEditCellContentProps { column: CalculatedColumn; row: TRow; rowIdx: number; @@ -153,14 +159,14 @@ export interface RenderEditCellProps { onClose: (commitChanges?: boolean, shouldFocusCell?: boolean) => void; } -export interface RenderHeaderCellProps { +export interface RenderHeaderCellContentProps { column: CalculatedColumn; sortDirection: SortDirection | undefined; priority: number | undefined; tabIndex: number; } -interface BaseCellRendererProps +interface BaseRenderCellProps extends Omit, 'children'>, Pick< @@ -171,10 +177,7 @@ interface BaseCellRendererProps selectCell: (position: Position, options?: SelectCellOptions) => void; } -export interface CellRendererProps extends BaseCellRendererProps< - TRow, - TSummaryRow -> { +export interface RenderCellProps extends BaseRenderCellProps { column: CalculatedColumn; row: TRow; colSpan: number | undefined; @@ -232,7 +235,7 @@ export type CellMouseEventHandler = Maybe< (args: CellMouseArgs, NoInfer>, event: CellMouseEvent) => void >; -export interface BaseRenderRowProps extends BaseCellRendererProps< +export interface BaseRenderRowProps extends BaseRenderCellProps< TRow, TSummaryRow > { @@ -251,7 +254,7 @@ export interface RenderRowProps extends BaseRenderR row: TRow; lastFrozenColumnIndex: number; draggedOverCellIdx: number | undefined; - selectedCellEditor: ReactElement> | undefined; + selectedCellEditor: ReactElement> | undefined; onRowChange: (column: CalculatedColumn, rowIdx: number, newRow: TRow) => void; rowClass: Maybe<(row: TRow, rowIdx: number) => Maybe>; isTreeGrid: boolean; @@ -334,7 +337,7 @@ export interface RenderCheckboxProps extends Pick< } export interface Renderers { - renderCell?: Maybe<(key: Key, props: CellRendererProps) => ReactNode>; + renderCell?: Maybe<(key: Key, props: RenderCellProps) => ReactNode>; renderCheckbox?: Maybe<(props: RenderCheckboxProps) => ReactNode>; renderRow?: Maybe<(key: Key, props: RenderRowProps) => ReactNode>; renderSortStatus?: Maybe<(props: RenderSortStatusProps) => ReactNode>; diff --git a/src/utils/eventUtils.ts b/src/utils/eventUtils.ts index d8d47a4892..a5eba01a37 100644 --- a/src/utils/eventUtils.ts +++ b/src/utils/eventUtils.ts @@ -4,17 +4,18 @@ export function createCellEvent>( event: E ): CellEvent { let defaultPrevented = false; - const cellEvent = { - ...event, - preventGridDefault() { - defaultPrevented = true; + + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return Object.create(event, { + preventGridDefault: { + value() { + defaultPrevented = true; + } }, - isGridDefaultPrevented() { - return defaultPrevented; + isGridDefaultPrevented: { + value() { + return defaultPrevented; + } } - }; - - Object.setPrototypeOf(cellEvent, Object.getPrototypeOf(event)); - - return cellEvent; + }); } diff --git a/test/browser/TreeDataGrid.test.tsx b/test/browser/TreeDataGrid.test.tsx index b141c6dff6..ea2ef40211 100644 --- a/test/browser/TreeDataGrid.test.tsx +++ b/test/browser/TreeDataGrid.test.tsx @@ -97,9 +97,7 @@ function TestGrid({ }) { const [rows, setRows] = useState(initialRows); const [selectedRows, setSelectedRows] = useState((): ReadonlySet => new Set()); - const [expandedGroupIds, setExpandedGroupIds] = useState( - (): ReadonlySet => new Set([]) - ); + const [expandedGroupIds, setExpandedGroupIds] = useState((): ReadonlySet => new Set([])); return ( { ); }); -test('update row using cell renderer', async () => { +test('update row using cell content renderer', async () => { await setup(['year']); await userEvent.click(page.getCell({ name: '2021' })); await userEvent.click(page.getCell({ name: 'USA' })); diff --git a/test/browser/column/renderCell.test.tsx b/test/browser/column/renderCell.test.tsx index 7abd91f0f3..f44e685543 100644 --- a/test/browser/column/renderCell.test.tsx +++ b/test/browser/column/renderCell.test.tsx @@ -3,7 +3,7 @@ import { page, userEvent } from 'vitest/browser'; import { DataGrid } from '../../../src'; import type { Column } from '../../../src'; -import defaultRenderHeaderCell from '../../../src/renderHeaderCell'; +import { renderHeaderCell } from '../../../src/renderHeaderCell'; import { getCellsAtRowIndex, setup } from '../utils'; const cells = page.getCell(); @@ -33,7 +33,7 @@ describe('renderValue', () => { }); }); -describe('Custom cell renderer', () => { +describe('Custom cell content renderer', () => { const columns: readonly Column[] = [ { key: 'id', @@ -49,7 +49,7 @@ describe('Custom cell renderer', () => { const rows: readonly Row[] = [{ id: 101 }]; - it('should replace the default cell renderer', async () => { + it('should replace the default cell content renderer', async () => { await setup({ columns, rows }); await expect.element(cells.nth(0)).toHaveTextContent('#101'); await expect.element(cells.nth(1)).toHaveTextContent('No name'); @@ -108,7 +108,7 @@ describe('Custom cell renderer', () => { sortable: false, draggable: false, width: 'auto', - renderHeaderCell: defaultRenderHeaderCell + renderHeaderCell }, indexes: [0] }); @@ -167,7 +167,7 @@ test('Focus child if it sets tabIndex', async () => { test('Cell should not steal focus when the focus is outside the grid and cell is recreated', async () => { const columns: readonly Column[] = [{ key: 'id', name: 'ID' }]; - function FormatterTest() { + function Test() { const [rows, setRows] = useState((): readonly Row[] => [{ id: 1 }]); function onClick() { @@ -189,7 +189,7 @@ test('Cell should not steal focus when the focus is outside the grid and cell is ); } - await page.render(); + await page.render(); const cell = getCellsAtRowIndex(0).nth(0); await userEvent.click(cell); diff --git a/test/browser/keyboardNavigation.test.tsx b/test/browser/keyboardNavigation.test.tsx index 91cf9f3a01..e1167c4faa 100644 --- a/test/browser/keyboardNavigation.test.tsx +++ b/test/browser/keyboardNavigation.test.tsx @@ -172,13 +172,13 @@ test('grid enter/exit', async () => { await expect.element(afterButton).toHaveFocus(); }); -test('navigation with focusable cell renderer', async () => { +test('navigation with focusable cell content', async () => { await setup({ columns, rows: Array.from({ length: 1 }), bottomSummaryRows }, true); await tabIntoGrid(); await userEvent.keyboard('{arrowdown}'); await validateCellPosition(0, 1); - // cell should not set tabIndex to 0 if it contains a focusable cell renderer + // cell should not set tabIndex to 0 if it contains a focusable cell content await expect.element(selectedCell).toHaveAttribute('tabIndex', '-1'); const checkbox = selectedCell.getByRole('checkbox'); await expect.element(checkbox).toHaveFocus(); @@ -186,7 +186,7 @@ test('navigation with focusable cell renderer', async () => { await userEvent.tab(); await validateCellPosition(1, 1); - // cell should set tabIndex to 0 if it does not have focusable cell renderer + // cell should set tabIndex to 0 if it does not have focusable cell content await expect.element(selectedCell).toHaveAttribute('tabIndex', '0'); }); diff --git a/test/browser/renderers.test.tsx b/test/browser/renderers.test.tsx index 54059ee126..0b9a38ed7c 100644 --- a/test/browser/renderers.test.tsx +++ b/test/browser/renderers.test.tsx @@ -10,7 +10,7 @@ import { SelectColumn } from '../../src'; import type { - CellRendererProps, + RenderCellProps, Column, DataGridProps, RenderRowProps, @@ -41,11 +41,11 @@ const columns: readonly Column[] = [ } ]; -function renderGlobalCell(key: React.Key, props: CellRendererProps) { +function renderGlobalCell(key: React.Key, props: RenderCellProps) { return ; } -function renderLocalCell(key: React.Key, props: CellRendererProps) { +function renderLocalCell(key: React.Key, props: RenderCellProps) { return ; } diff --git a/website/components/CellExpanderFormatter.tsx b/website/components/CellExpander.tsx similarity index 80% rename from website/components/CellExpanderFormatter.tsx rename to website/components/CellExpander.tsx index 7d27dddfcf..7ef2521b67 100644 --- a/website/components/CellExpanderFormatter.tsx +++ b/website/components/CellExpander.tsx @@ -7,17 +7,13 @@ const cellExpandClassname = css` cursor: pointer; `; -interface CellExpanderFormatterProps { +interface CellExpanderProps { tabIndex: number; expanded: boolean; onCellExpand: () => void; } -export function CellExpanderFormatter({ - tabIndex, - expanded, - onCellExpand -}: CellExpanderFormatterProps) { +export function CellExpander({ tabIndex, expanded, onCellExpand }: CellExpanderProps) { function handleKeyDown(e: React.KeyboardEvent) { if (e.key === ' ' || e.key === 'Enter') { // prevent scrolling diff --git a/website/components/DraggableRowRenderer.tsx b/website/components/DraggableRow.tsx similarity index 93% rename from website/components/DraggableRowRenderer.tsx rename to website/components/DraggableRow.tsx index fe0a58908e..5e93924430 100644 --- a/website/components/DraggableRowRenderer.tsx +++ b/website/components/DraggableRow.tsx @@ -12,16 +12,16 @@ const rowOverClassname = css` background-color: #ececec; `; -interface DraggableRowRenderProps extends RenderRowProps { +interface DraggableRowProps extends RenderRowProps { onRowReorder: (sourceIndex: number, targetIndex: number) => void; } -export function DraggableRowRenderer({ +export function DraggableRow({ rowIdx, className, onRowReorder, ...props -}: DraggableRowRenderProps) { +}: DraggableRowProps) { const [isDragging, setIsDragging] = useState(false); const [isOver, setIsOver] = useState(false); diff --git a/website/components/index.ts b/website/components/index.ts index d8e454b6f5..2683718ec0 100644 --- a/website/components/index.ts +++ b/website/components/index.ts @@ -1,3 +1,3 @@ -export * from './CellExpanderFormatter'; +export * from './CellExpander'; export * from './ChildRowDeleteButton'; -export * from './DraggableRowRenderer'; +export * from './DraggableRow'; diff --git a/website/renderers/renderCoordinates.ts b/website/renderers/renderCoordinates.ts index 3761a3a710..ecaf8abc42 100644 --- a/website/renderers/renderCoordinates.ts +++ b/website/renderers/renderCoordinates.ts @@ -1,6 +1,6 @@ -import type { RenderCellProps } from '../../src'; +import type { RenderCellContentProps } from '../../src'; // eslint-disable-next-line @typescript-eslint/no-explicit-any -export function renderCoordinates(props: RenderCellProps) { +export function renderCoordinates(props: RenderCellContentProps) { return `${props.column.key}×${props.row}`; } diff --git a/website/routes/CommonFeatures.tsx b/website/routes/CommonFeatures.tsx index 0d934a526c..73676df635 100644 --- a/website/routes/CommonFeatures.tsx +++ b/website/routes/CommonFeatures.tsx @@ -6,7 +6,7 @@ import { css } from 'ecij'; import { DataGrid, renderTextEditor, - SelectCellFormatter, + SelectCheckbox, SelectColumn, type Column, type DataGridHandle, @@ -47,11 +47,11 @@ const dialogContainerClassname = css` } `; -const dateFormatter = new Intl.DateTimeFormat(navigator.language); +const dateFormatter = new Intl.DateTimeFormat(navigator.language).format; const currencyFormatter = new Intl.NumberFormat(navigator.language, { style: 'currency', currency: 'eur' -}); +}).format; interface SummaryRow { id: string; @@ -191,21 +191,21 @@ function getColumns( key: 'startTimestamp', name: 'Start date', renderCell(props) { - return dateFormatter.format(props.row.startTimestamp); + return dateFormatter(props.row.startTimestamp); } }, { key: 'endTimestamp', name: 'Deadline', renderCell(props) { - return dateFormatter.format(props.row.endTimestamp); + return dateFormatter(props.row.endTimestamp); } }, { key: 'budget', name: 'Budget', renderCell(props) { - return currencyFormatter.format(props.row.budget); + return currencyFormatter(props.row.budget); } }, { @@ -226,7 +226,7 @@ function getColumns( name: 'Available', renderCell({ row, onRowChange, tabIndex }) { return ( - { onRowChange({ ...row, available: !row.available }); diff --git a/website/routes/CustomizableRenderers.tsx b/website/routes/CustomizableRenderers.tsx index ec4a2aa47d..be54396e9e 100644 --- a/website/routes/CustomizableRenderers.tsx +++ b/website/routes/CustomizableRenderers.tsx @@ -3,7 +3,7 @@ import { css } from 'ecij'; import { Row as BaseRow, Cell, DataGrid, renderTextEditor, SelectColumn } from '../../src'; import type { - CellRendererProps, + RenderCellProps, Column, RenderCheckboxProps, RenderRowProps, @@ -158,7 +158,7 @@ function renderSortStatus({ sortDirection, priority }: RenderSortStatusProps) { const cellStyle: React.CSSProperties = { color: 'red' }; -function renderCell(key: React.Key, props: CellRendererProps) { +function renderCell(key: React.Key, props: RenderCellProps) { const style = props.column.key === 'priority' && props.row.priority === 'Critical' ? cellStyle : undefined; diff --git a/website/routes/HeaderFilters.tsx b/website/routes/HeaderFilters.tsx index f4b26fd95b..dda240ca80 100644 --- a/website/routes/HeaderFilters.tsx +++ b/website/routes/HeaderFilters.tsx @@ -2,7 +2,7 @@ import { createContext, useContext, useMemo, useState } from 'react'; import { faker } from '@faker-js/faker'; import { css } from 'ecij'; -import { DataGrid, type Column, type RenderHeaderCellProps } from '../../src'; +import { DataGrid, type Column, type RenderHeaderCellContentProps } from '../../src'; import type { Omit } from '../../src/types'; import { useDirection } from '../directionContext'; @@ -95,7 +95,7 @@ function HeaderFilters() { const developerOptions = useMemo( () => - Array.from(new Set(rows.map((r) => r.developer))).map((d) => ({ + Array.from(new Set(rows.map((r) => r.developer)), (d) => ({ label: d, value: d })), @@ -114,7 +114,7 @@ function HeaderFilters() { name: 'Title', headerCellClass: filterColumnClassName, renderHeaderCell: (p) => ( - {...p}> + {...p}> {({ filters, ...rest }) => ( )} - + ) }, { @@ -137,7 +137,7 @@ function HeaderFilters() { name: 'Priority', headerCellClass: filterColumnClassName, renderHeaderCell: (p) => ( - {...p}> + {...p}> {({ filters, ...rest }) => ( )} - + ) }, { @@ -166,7 +166,7 @@ function HeaderFilters() { name: 'Issue Type', headerCellClass: filterColumnClassName, renderHeaderCell: (p) => ( - {...p}> + {...p}> {({ filters, ...rest }) => ( )} - + ) }, { @@ -195,7 +195,7 @@ function HeaderFilters() { name: 'Developer', headerCellClass: filterColumnClassName, renderHeaderCell: (p) => ( - {...p}> + {...p}> {({ filters, ...rest }) => ( )} - + ) }, { @@ -219,7 +219,7 @@ function HeaderFilters() { name: '% Complete', headerCellClass: filterColumnClassName, renderHeaderCell: (p) => ( - {...p}> + {...p}> {({ filters, ...rest }) => ( )} - + ) } ]; @@ -309,11 +309,11 @@ function HeaderFilters() { ); } -function FilterRenderer({ +function FilterCell({ tabIndex, column, children -}: RenderHeaderCellProps & { +}: RenderHeaderCellContentProps & { children: (args: { tabIndex: number; filters: Filter }) => React.ReactElement; }) { const filters = useContext(FilterContext)!; diff --git a/website/routes/InfiniteScrolling.tsx b/website/routes/InfiniteScrolling.tsx index 2b7ec28646..9f8cc75e91 100644 --- a/website/routes/InfiniteScrolling.tsx +++ b/website/routes/InfiniteScrolling.tsx @@ -1,4 +1,4 @@ -import { useState } from 'react'; +import { useState, useTransition } from 'react'; import { faker } from '@faker-js/faker'; import { css } from 'ecij'; @@ -95,17 +95,15 @@ function loadMoreRows(newRowsCount: number, length: number): Promise { function InfiniteScrolling() { const direction = useDirection(); const [rows, setRows] = useState(() => createRows(50)); - const [isLoading, setIsLoading] = useState(false); + const [isPending, startTransition] = useTransition(); - async function handleScroll(event: React.UIEvent) { - if (isLoading || !isAtBottom(event)) return; + function handleScroll(event: React.UIEvent) { + if (isPending || !isAtBottom(event)) return; - setIsLoading(true); - - const newRows = await loadMoreRows(50, rows.length); - - setRows([...rows, ...newRows]); - setIsLoading(false); + startTransition(async () => { + const newRows = await loadMoreRows(50, rows.length); + setRows([...rows, ...newRows]); + }); } return ( @@ -121,7 +119,7 @@ function InfiniteScrolling() { className="fill-grid" direction={direction} /> - {isLoading &&
Loading more rows...
} + {isPending &&
Loading more rows...
} ); } diff --git a/website/routes/MasterDetail.tsx b/website/routes/MasterDetail.tsx index b5d41d0945..a4469c5eac 100644 --- a/website/routes/MasterDetail.tsx +++ b/website/routes/MasterDetail.tsx @@ -3,7 +3,7 @@ import { faker } from '@faker-js/faker'; import { css } from 'ecij'; import { DataGrid, type Column, type Direction, type RowsChangeData } from '../../src'; -import { CellExpanderFormatter } from '../components'; +import { CellExpander } from '../components'; import { useDirection } from '../directionContext'; export const Route = createFileRoute({ @@ -93,7 +93,7 @@ function MasterDetail() { } return ( - { diff --git a/website/routes/NoRows.tsx b/website/routes/NoRows.tsx index ccc9c3c61c..eed21b93da 100644 --- a/website/routes/NoRows.tsx +++ b/website/routes/NoRows.tsx @@ -12,7 +12,7 @@ const gridClassname = css` block-size: 300px; `; -function EmptyRowsRenderer() { +function NoRowsFallback() { return (
Nothing to show{' '} @@ -51,7 +51,7 @@ function NoRows() { aria-label="No Rows Example" columns={columns} rows={rows} - renderers={{ noRowsFallback: }} + renderers={{ noRowsFallback: }} selectedRows={selectedRows} onSelectedRowsChange={onSelectedRowsChange} rowKeyGetter={rowKeyGetter} diff --git a/website/routes/RowGrouping.tsx b/website/routes/RowGrouping.tsx index d260902718..bbfcac9941 100644 --- a/website/routes/RowGrouping.tsx +++ b/website/routes/RowGrouping.tsx @@ -153,7 +153,7 @@ function RowGrouping() { ]); const [expandedGroupIds, setExpandedGroupIds] = useState( (): ReadonlySet => - new Set(['United States of America', 'United States of America__2015']) + new Set(['United States of America', 'United States of America__2015']) ); function toggleOption(option: string, enabled: boolean) { diff --git a/website/routes/RowsReordering.tsx b/website/routes/RowsReordering.tsx index 8382faf053..7e63c77a81 100644 --- a/website/routes/RowsReordering.tsx +++ b/website/routes/RowsReordering.tsx @@ -1,7 +1,7 @@ import { useCallback, useState } from 'react'; import { DataGrid, renderTextEditor, type Column, type RenderRowProps } from '../../src'; -import { DraggableRowRenderer } from '../components'; +import { DraggableRow } from '../components'; import { startViewTransition } from '../utils'; import { useDirection } from '../directionContext'; @@ -76,7 +76,7 @@ function RowsReordering() { startViewTransition(reorderRows); } - return key={key} {...props} onRowReorder={onRowReorder} />; + return key={key} {...props} onRowReorder={onRowReorder} />; }, []); return ( diff --git a/website/routes/TreeView.tsx b/website/routes/TreeView.tsx index 47fd20fa4d..516c2432b9 100644 --- a/website/routes/TreeView.tsx +++ b/website/routes/TreeView.tsx @@ -2,7 +2,7 @@ import { useMemo, useReducer, useState } from 'react'; import { css } from 'ecij'; import { DataGrid, type Column } from '../../src'; -import { CellExpanderFormatter, ChildRowDeleteButton } from '../components'; +import { CellExpander, ChildRowDeleteButton } from '../components'; import { useDirection } from '../directionContext'; export const Route = createFileRoute({ @@ -151,7 +151,7 @@ function TreeView() { `} > {hasChildren && ( - dispatch({ id: row.id, type: 'toggleSubRow' })} diff --git a/website/utils.tsx b/website/utils.tsx index 6748007b41..128b2154e2 100644 --- a/website/utils.tsx +++ b/website/utils.tsx @@ -37,11 +37,12 @@ function getGridContent(gridEl: HTMLDivElement) { }; function getRows(selector: string) { - return Array.from(gridEl.querySelectorAll(selector)).map((gridRow) => { - return Array.from(gridRow.querySelectorAll('.rdg-cell')).map( + return Array.from(gridEl.querySelectorAll(selector), (gridRow) => + Array.from( + gridRow.querySelectorAll('.rdg-cell'), (gridCell) => gridCell.innerText - ); - }); + ) + ); } } From 4ba6b57db8ec2e4cccdf597cb982df4463337cc6 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Mon, 23 Feb 2026 22:08:13 +0000 Subject: [PATCH 2/7] fix SelectCellKeyDownArgs type in README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dc6fed68fd..1f5e33cbaf 100644 --- a/README.md +++ b/README.md @@ -1782,9 +1782,9 @@ Arguments passed to the `onCellKeyDown` handler. The shape differs based on whet **SELECT mode:** ```tsx -interface SelectCellKeyDownArgs { +interface SelectCellKeyDownArgs { mode: 'SELECT'; - column: CalculatedColumn; + column: CalculatedColumn | undefined; row: TRow; rowIdx: number; selectCell: (position: Position, options?: SelectCellOptions) => void; @@ -1794,7 +1794,7 @@ interface SelectCellKeyDownArgs { **EDIT mode:** ```tsx -interface EditCellKeyDownArgs { +interface EditCellKeyDownArgs { mode: 'EDIT'; column: CalculatedColumn; row: TRow; From 944babe719989961c581de7d7ea2205bb546c39c Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Mon, 23 Feb 2026 22:16:53 +0000 Subject: [PATCH 3/7] lint --- src/utils/eventUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/eventUtils.ts b/src/utils/eventUtils.ts index a5eba01a37..5806b5af2a 100644 --- a/src/utils/eventUtils.ts +++ b/src/utils/eventUtils.ts @@ -5,7 +5,6 @@ export function createCellEvent>( ): CellEvent { let defaultPrevented = false; - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return Object.create(event, { preventGridDefault: { value() { From 07e3af6c62903d301bf4cbe3a40ad0d7bdae89c4 Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Mon, 23 Feb 2026 22:37:13 +0000 Subject: [PATCH 4/7] tweak SortableHeaderCell --- src/renderHeaderCell.tsx | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/renderHeaderCell.tsx b/src/renderHeaderCell.tsx index 5336f7e5dc..f3248bfd06 100644 --- a/src/renderHeaderCell.tsx +++ b/src/renderHeaderCell.tsx @@ -26,32 +26,24 @@ export function renderHeaderCell({ }: RenderHeaderCellContentProps) { if (!column.sortable) return column.name; - return ( - - {column.name} - - ); + return ; } -type SharedHeaderCellProps = Pick< +type SortableHeaderCellProps = Pick< RenderHeaderCellContentProps, - 'sortDirection' | 'priority' + 'column' | 'sortDirection' | 'priority' >; -interface SortableHeaderCellProps extends SharedHeaderCellProps { - children: React.ReactNode; -} - function SortableHeaderCell({ + column, sortDirection, - priority, - children + priority }: SortableHeaderCellProps) { const renderSortStatus = useDefaultRenderers()!.renderSortStatus!; return ( - {children} + {column.name} {renderSortStatus({ sortDirection, priority })} ); From 548fc22606c5e7509f2038f44c9b9a1a6816071c Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Mon, 23 Feb 2026 22:49:23 +0000 Subject: [PATCH 5/7] tweak EditCell --- src/EditCell.tsx | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/EditCell.tsx b/src/EditCell.tsx index 8ec453ba54..45e8d2f1fb 100644 --- a/src/EditCell.tsx +++ b/src/EditCell.tsx @@ -2,14 +2,7 @@ import { useEffectEvent, useLayoutEffect, useRef } from 'react'; import { css } from 'ecij'; import { createCellEvent, getCellClassname, getCellStyle, onEditorNavigation } from './utils'; -import type { - CellKeyboardEvent, - RenderCellProps, - EditCellKeyDownArgs, - Maybe, - Omit, - RenderEditCellContentProps -} from './types'; +import type { CellKeyboardEvent, RenderCellProps, EditCellKeyDownArgs, Maybe } from './types'; declare global { const scheduler: Scheduler | undefined; @@ -50,13 +43,12 @@ const cellEditing = css` } `; -type SharedRenderCellProps = Pick, 'colSpan'>; +type SharedRenderCellProps = Pick< + RenderCellProps, + 'column' | 'colSpan' | 'row' | 'rowIdx' +>; -interface EditCellProps - extends - Omit, 'onRowChange' | 'onClose'>, - SharedRenderCellProps { - rowIdx: number; +interface EditCellProps extends SharedRenderCellProps { onRowChange: (row: R, commitChanges: boolean, shouldFocusCell: boolean) => void; closeEditor: (shouldFocusCell: boolean) => void; navigate: (event: React.KeyboardEvent) => void; @@ -70,8 +62,8 @@ export default function EditCell({ rowIdx, onRowChange, closeEditor, - onKeyDown, - navigate + navigate, + onKeyDown }: EditCellProps) { const captureEventRef = useRef(undefined); const abortControllerRef = useRef(undefined); From 6cffef157563182d924fce05e092a8ea231e74fa Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Mon, 23 Feb 2026 23:10:38 +0000 Subject: [PATCH 6/7] fix selectedCellEditor type --- README.md | 2 +- src/EditCell.tsx | 14 +------------- src/types.ts | 12 +++++++++++- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 1f5e33cbaf..33a9dbbd93 100644 --- a/README.md +++ b/README.md @@ -1654,7 +1654,7 @@ interface RenderRowProps { gridRowStart: number; lastFrozenColumnIndex: number; draggedOverCellIdx: number | undefined; - selectedCellEditor: ReactElement> | undefined; + selectedCellEditor: ReactElement> | undefined; onRowChange: (column: CalculatedColumn, rowIdx: number, newRow: TRow) => void; rowClass: Maybe<(row: TRow, rowIdx: number) => Maybe>; // ... and event handlers diff --git a/src/EditCell.tsx b/src/EditCell.tsx index 45e8d2f1fb..bdfdcc51a2 100644 --- a/src/EditCell.tsx +++ b/src/EditCell.tsx @@ -2,7 +2,7 @@ import { useEffectEvent, useLayoutEffect, useRef } from 'react'; import { css } from 'ecij'; import { createCellEvent, getCellClassname, getCellStyle, onEditorNavigation } from './utils'; -import type { CellKeyboardEvent, RenderCellProps, EditCellKeyDownArgs, Maybe } from './types'; +import type { EditCellProps } from './types'; declare global { const scheduler: Scheduler | undefined; @@ -43,18 +43,6 @@ const cellEditing = css` } `; -type SharedRenderCellProps = Pick< - RenderCellProps, - 'column' | 'colSpan' | 'row' | 'rowIdx' ->; - -interface EditCellProps extends SharedRenderCellProps { - onRowChange: (row: R, commitChanges: boolean, shouldFocusCell: boolean) => void; - closeEditor: (shouldFocusCell: boolean) => void; - navigate: (event: React.KeyboardEvent) => void; - onKeyDown: Maybe<(args: EditCellKeyDownArgs, event: CellKeyboardEvent) => void>; -} - export default function EditCell({ column, colSpan, diff --git a/src/types.ts b/src/types.ts index 38c4fd144c..36e7f8bd39 100644 --- a/src/types.ts +++ b/src/types.ts @@ -186,6 +186,16 @@ export interface RenderCellProps extends BaseRenderCellProps< onRowChange: (column: CalculatedColumn, newRow: TRow) => void; } +export interface EditCellProps extends Pick< + RenderCellProps, + 'column' | 'colSpan' | 'row' | 'rowIdx' +> { + onRowChange: (row: R, commitChanges: boolean, shouldFocusCell: boolean) => void; + closeEditor: (shouldFocusCell: boolean) => void; + navigate: (event: React.KeyboardEvent) => void; + onKeyDown: Maybe<(args: EditCellKeyDownArgs, event: CellKeyboardEvent) => void>; +} + export type CellEvent> = E & { preventGridDefault: () => void; isGridDefaultPrevented: () => boolean; @@ -254,7 +264,7 @@ export interface RenderRowProps extends BaseRenderR row: TRow; lastFrozenColumnIndex: number; draggedOverCellIdx: number | undefined; - selectedCellEditor: ReactElement> | undefined; + selectedCellEditor: ReactElement> | undefined; onRowChange: (column: CalculatedColumn, rowIdx: number, newRow: TRow) => void; rowClass: Maybe<(row: TRow, rowIdx: number) => Maybe>; isTreeGrid: boolean; From 794687cfdf194dff49ed4a046ae511b0d0e422eb Mon Sep 17 00:00:00 2001 From: Nicolas Stepien Date: Mon, 23 Feb 2026 23:25:18 +0000 Subject: [PATCH 7/7] GroupRowRendererProps -> GroupedRowProps --- src/{GroupRow.tsx => GroupedRow.tsx} | 8 +++----- src/TreeDataGrid.tsx | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) rename src/{GroupRow.tsx => GroupedRow.tsx} (92%) diff --git a/src/GroupRow.tsx b/src/GroupedRow.tsx similarity index 92% rename from src/GroupRow.tsx rename to src/GroupedRow.tsx index 7c2c453c6b..ae5c675c0f 100644 --- a/src/GroupRow.tsx +++ b/src/GroupedRow.tsx @@ -24,7 +24,7 @@ const groupRow = css` const groupRowClassname = `rdg-group-row ${groupRow}`; -interface GroupRowRendererProps extends BaseRenderRowProps { +interface GroupedRowProps extends BaseRenderRowProps { row: GroupRow; groupBy: readonly string[]; toggleGroup: (expandedGroupId: unknown) => void; @@ -43,7 +43,7 @@ function GroupedRow({ toggleGroup, isRowSelectionDisabled: _isRowSelectionDisabled, ...props -}: GroupRowRendererProps) { +}: GroupedRowProps) { const isPositionOnRow = selectedCellIdx === -1; // Select is always the first column const idx = viewportColumns[0].key === SELECT_COLUMN_KEY ? row.level + 1 : row.level; @@ -97,6 +97,4 @@ function GroupedRow({ ); } -export default memo(GroupedRow) as ( - props: GroupRowRendererProps -) => React.JSX.Element; +export default memo(GroupedRow) as (props: GroupedRowProps) => React.JSX.Element; diff --git a/src/TreeDataGrid.tsx b/src/TreeDataGrid.tsx index 87e8ba49fe..5a7a6f9712 100644 --- a/src/TreeDataGrid.tsx +++ b/src/TreeDataGrid.tsx @@ -22,7 +22,7 @@ import { SELECT_COLUMN_KEY } from './Columns'; import { DataGrid } from './DataGrid'; import type { DataGridProps } from './DataGrid'; import { useDefaultRenderers } from './DataGridDefaultRenderersContext'; -import GroupedRow from './GroupRow'; +import GroupedRow from './GroupedRow'; import { defaultRenderRow } from './Row'; export interface TreeDataGridProps extends Omit<