diff --git a/src/DataGrid.tsx b/src/DataGrid.tsx index f75c57e6c9..d91cc95627 100644 --- a/src/DataGrid.tsx +++ b/src/DataGrid.tsx @@ -103,78 +103,78 @@ export interface DataGridProps * Rows to be pinned at the bottom of the rows view for summary, the vertical scroll bar will not scroll these rows. * Bottom horizontal scroll bar can move the row left / right. Or a customized row renderer can be used to disabled the scrolling support. */ - summaryRows?: readonly SR[]; + summaryRows?: readonly SR[] | null; /** The getter should return a unique key for each row */ - rowKeyGetter?: (row: R) => K; - onRowsChange?: (rows: R[], data: RowsChangeData) => void; + rowKeyGetter?: ((row: R) => K) | null; + onRowsChange?: ((rows: R[], data: RowsChangeData) => void) | null; /** * Dimensions props */ /** The height of each row in pixels */ - rowHeight?: number | ((args: RowHeightArgs) => number); + rowHeight?: number | ((args: RowHeightArgs) => number) | null; /** The height of the header row in pixels */ - headerRowHeight?: number; + headerRowHeight?: number | null; /** The height of the header filter row in pixels */ - headerFiltersHeight?: number; + headerFiltersHeight?: number | null; /** The height of each summary row in pixels */ - summaryRowHeight?: number; + summaryRowHeight?: number | null; /** * Feature props */ /** Set of selected row keys */ - selectedRows?: ReadonlySet; + selectedRows?: ReadonlySet | null; /** Function called whenever row selection is changed */ - onSelectedRowsChange?: (selectedRows: Set) => void; + onSelectedRowsChange?: ((selectedRows: Set) => void) | null; /** The key of the column which is currently being sorted */ - sortColumn?: string; + sortColumn?: string | null; /** The direction to sort the sortColumn*/ - sortDirection?: SortDirection; + sortDirection?: SortDirection | null; /** Function called whenever grid is sorted*/ - onSort?: (columnKey: string, direction: SortDirection) => void; - filters?: Readonly; - onFiltersChange?: (filters: Filters) => void; - defaultColumnOptions?: DefaultColumnOptions; - groupBy?: readonly string[]; - rowGrouper?: (rows: readonly R[], columnKey: string) => Record; - expandedGroupIds?: ReadonlySet; - onExpandedGroupIdsChange?: (expandedGroupIds: Set) => void; - onFill?: (event: FillEvent) => R[]; - onPaste?: (event: PasteEvent) => R; + onSort?: ((columnKey: string, direction: SortDirection) => void) | null; + filters?: Readonly | null; + onFiltersChange?: ((filters: Filters) => void) | null; + defaultColumnOptions?: DefaultColumnOptions | null; + groupBy?: readonly string[] | null; + rowGrouper?: ((rows: readonly R[], columnKey: string) => Record) | null; + expandedGroupIds?: ReadonlySet | null; + onExpandedGroupIdsChange?: ((expandedGroupIds: Set) => void) | null; + onFill?: ((event: FillEvent) => R[]) | null; + onPaste?: ((event: PasteEvent) => R) | null; /** * Custom renderers */ - rowRenderer?: React.ComponentType>; - emptyRowsRenderer?: React.ComponentType; + rowRenderer?: React.ComponentType> | null; + emptyRowsRenderer?: React.ComponentType | null; /** * Event props */ /** Function called whenever a row is clicked */ - onRowClick?: (rowIdx: number, row: R, column: CalculatedColumn) => void; + onRowClick?: ((rowIdx: number, row: R, column: CalculatedColumn) => void) | null; /** Called when the grid is scrolled */ - onScroll?: (event: React.UIEvent) => void; + onScroll?: ((event: React.UIEvent) => void) | null; /** Called when a column is resized */ - onColumnResize?: (idx: number, width: number) => void; + onColumnResize?: ((idx: number, width: number) => void) | null; /** Function called whenever selected cell is changed */ - onSelectedCellChange?: (position: Position) => void; + onSelectedCellChange?: ((position: Position) => void) | null; /** * Toggles and modes */ /** Toggles whether filters row is displayed or not */ - enableFilterRow?: boolean; - cellNavigationMode?: CellNavigationMode; - enableVirtualization?: boolean; + enableFilterRow?: boolean | null; + cellNavigationMode?: CellNavigationMode | null; + enableVirtualization?: boolean | null; /** * Miscellaneous */ /** The node where the editor portal should mount. */ - editorPortalTarget?: Element; - rowClass?: (row: R) => string | undefined; + editorPortalTarget?: Element | null; + rowClass?: ((row: R) => string | undefined | null) | null; } /** @@ -184,7 +184,7 @@ export interface DataGridProps * * */ -function DataGrid( +function DataGrid( { // Grid and data Props columns: rawColumns, @@ -193,10 +193,10 @@ function DataGrid( rowKeyGetter, onRowsChange, // Dimensions props - rowHeight = 35, - headerRowHeight = typeof rowHeight === 'number' ? rowHeight : 35, - headerFiltersHeight = 45, - summaryRowHeight = typeof rowHeight === 'number' ? rowHeight : 35, + rowHeight, + headerRowHeight, + headerFiltersHeight, + summaryRowHeight: rawSummaryRowHeight, // Feature props selectedRows, onSelectedRowsChange, @@ -211,7 +211,7 @@ function DataGrid( expandedGroupIds, onExpandedGroupIdsChange, // Custom renderers - rowRenderer: RowRenderer = Row, + rowRenderer, emptyRowsRenderer: EmptyRowsRenderer, // Event props onRowClick, @@ -221,11 +221,11 @@ function DataGrid( onFill, onPaste, // Toggles and modes - enableFilterRow = false, - cellNavigationMode = 'NONE', - enableVirtualization = true, + enableFilterRow, + cellNavigationMode: rawCellNavigationMode, + enableVirtualization, // Miscellaneous - editorPortalTarget = body, + editorPortalTarget: rawEditorPortalTarget, className, style, rowClass, @@ -233,9 +233,22 @@ function DataGrid( 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, 'aria-describedby': ariaDescribedBy - }: DataGridProps, + }: DataGridProps, ref: React.Ref ) { + /** + * defaults + */ + rowHeight ??= 35; + headerRowHeight ??= typeof rowHeight === 'number' ? rowHeight : 35; + headerFiltersHeight ??= 45; + const summaryRowHeight = rawSummaryRowHeight ?? (typeof rowHeight === 'number' ? rowHeight : 35); + const RowRenderer = rowRenderer ?? Row; + enableFilterRow ??= false; + const cellNavigationMode = rawCellNavigationMode ?? 'NONE'; + enableVirtualization ??= true; + const editorPortalTarget = rawEditorPortalTarget ?? body; + /** * states */ @@ -273,7 +286,7 @@ function DataGrid( const summaryRowsCount = summaryRows?.length ?? 0; const totalHeaderHeight = headerRowHeight + (enableFilterRow ? headerFiltersHeight : 0); const clientHeight = gridHeight - totalHeaderHeight - summaryRowsCount * summaryRowHeight; - const isSelectable = selectedRows !== undefined && onSelectedRowsChange !== undefined; + const isSelectable = selectedRows != null && onSelectedRowsChange != null; const { columns, @@ -335,7 +348,7 @@ function DataGrid( const minColIdx = hasGroups ? -1 : 0; // Cell drag is not supported on a treegrid - const enableCellDragAndDrop = hasGroups ? false : onFill !== undefined; + const enableCellDragAndDrop = hasGroups ? false : onFill != null; /** * effects @@ -401,7 +414,7 @@ function DataGrid( function selectRow({ rowIdx, checked, isShiftClick }: SelectRowEvent) { if (!onSelectedRowsChange) return; - assertIsValidKeyGetter(rowKeyGetter); + assertIsValidKeyGetter(rowKeyGetter); const newSelectedRows = new Set(selectedRows); const row = rows[rowIdx]; if (isGroupRow(row)) { @@ -973,11 +986,13 @@ function DataGrid( } startRowIndex++; - let key: React.Key = hasGroups ? startRowIndex : rowIdx; + let key; let isRowSelected = false; if (typeof rowKeyGetter === 'function') { key = rowKeyGetter(row); isRowSelected = selectedRows?.has(key) ?? false; + } else { + key = hasGroups ? startRowIndex : rowIdx; } rowElements.push( @@ -1048,7 +1063,7 @@ function DataGrid( ref={gridRef} onScroll={handleScroll} > - + rowKeyGetter={rowKeyGetter} rows={rawRows} columns={viewportColumns} @@ -1117,6 +1132,6 @@ function DataGrid( ); } -export default forwardRef(DataGrid) as ( - props: DataGridProps & RefAttributes +export default forwardRef(DataGrid) as ( + props: DataGridProps & RefAttributes ) => JSX.Element; diff --git a/src/HeaderCell.tsx b/src/HeaderCell.tsx index 61f7d38472..c18036b9aa 100644 --- a/src/HeaderCell.tsx +++ b/src/HeaderCell.tsx @@ -19,7 +19,7 @@ const cellResizable = css` const cellResizableClassname = `rdg-cell-resizable ${cellResizable}`; -function getAriaSort(sortDirection: SortDirection | undefined) { +function getAriaSort(sortDirection: SortDirection | undefined | null) { switch (sortDirection) { case 'ASC': return 'ascending'; @@ -31,7 +31,7 @@ function getAriaSort(sortDirection: SortDirection | undefined) { } type SharedHeaderRowProps = Pick< - HeaderRowProps, + HeaderRowProps, 'sortColumn' | 'sortDirection' | 'onSort' | 'allRowsSelected' >; diff --git a/src/HeaderRow.tsx b/src/HeaderRow.tsx index 90cde6bd1d..bd8b88d2ba 100644 --- a/src/HeaderRow.tsx +++ b/src/HeaderRow.tsx @@ -6,19 +6,19 @@ import { assertIsValidKeyGetter, getColSpan } from './utils'; import type { DataGridProps } from './DataGrid'; import { headerRowClassname } from './style'; -type SharedDataGridProps = Pick< - DataGridProps, +type SharedDataGridProps = Pick< + DataGridProps, 'rows' | 'onSelectedRowsChange' | 'sortColumn' | 'sortDirection' | 'onSort' | 'rowKeyGetter' >; -export interface HeaderRowProps extends SharedDataGridProps { +export interface HeaderRowProps extends SharedDataGridProps { columns: readonly CalculatedColumn[]; allRowsSelected: boolean; onColumnResize: (column: CalculatedColumn, width: number) => void; lastFrozenColumnIndex: number; } -function HeaderRow({ +function HeaderRow({ columns, rows, rowKeyGetter, @@ -29,14 +29,14 @@ function HeaderRow({ sortDirection, onSort, lastFrozenColumnIndex -}: HeaderRowProps) { +}: HeaderRowProps) { const handleAllRowsSelectionChange = useCallback( (checked: boolean) => { if (!onSelectedRowsChange) return; - assertIsValidKeyGetter(rowKeyGetter); + assertIsValidKeyGetter(rowKeyGetter); - const newSelectedRows = new Set(checked ? rows.map(rowKeyGetter) : undefined); + const newSelectedRows = new Set(checked ? rows.map(rowKeyGetter) : undefined); onSelectedRowsChange(newSelectedRows); }, [onSelectedRowsChange, rows, rowKeyGetter] @@ -76,4 +76,6 @@ function HeaderRow({ ); } -export default memo(HeaderRow) as (props: HeaderRowProps) => JSX.Element; +export default memo(HeaderRow) as ( + props: HeaderRowProps +) => JSX.Element; diff --git a/src/hooks/useCalculatedColumns.ts b/src/hooks/useCalculatedColumns.ts index ab4a9cbef3..7d8a911542 100644 --- a/src/hooks/useCalculatedColumns.ts +++ b/src/hooks/useCalculatedColumns.ts @@ -8,7 +8,7 @@ import { floor, max, min } from '../utils'; interface CalculatedColumnsArgs extends Pick, 'defaultColumnOptions'> { rawColumns: readonly Column[]; - rawGroupBy: readonly string[] | undefined; + rawGroupBy: readonly string[] | undefined | null; viewportWidth: number; scrollLeft: number; columnWidths: ReadonlyMap; diff --git a/src/hooks/useViewportColumns.ts b/src/hooks/useViewportColumns.ts index d1aa47871a..51da6316a9 100644 --- a/src/hooks/useViewportColumns.ts +++ b/src/hooks/useViewportColumns.ts @@ -7,7 +7,7 @@ interface ViewportColumnsArgs { columns: readonly CalculatedColumn[]; colSpanColumns: readonly CalculatedColumn[]; rows: readonly (R | GroupRow)[]; - summaryRows: readonly SR[] | undefined; + summaryRows: readonly SR[] | undefined | null; colOverscanStartIdx: number; colOverscanEndIdx: number; lastFrozenColumnIndex: number; @@ -72,7 +72,7 @@ export function useViewportColumns({ } // check summary rows - if (summaryRows !== undefined) { + if (summaryRows != null) { for (const row of summaryRows) { if ( updateStartIdx( diff --git a/src/hooks/useViewportRows.ts b/src/hooks/useViewportRows.ts index f9bccd9070..76a9ca8fab 100644 --- a/src/hooks/useViewportRows.ts +++ b/src/hooks/useViewportRows.ts @@ -10,8 +10,11 @@ interface ViewportRowsArgs { clientHeight: number; scrollTop: number; groupBy: readonly string[]; - rowGrouper: ((rows: readonly R[], columnKey: string) => Record) | undefined; - expandedGroupIds: ReadonlySet | undefined; + rowGrouper: + | ((rows: readonly R[], columnKey: string) => Record) + | undefined + | null; + expandedGroupIds: ReadonlySet | undefined | null; enableVirtualization: boolean; } @@ -31,7 +34,7 @@ export function useViewportRows({ enableVirtualization }: ViewportRowsArgs) { const [groupedRows, rowsCount] = useMemo(() => { - if (groupBy.length === 0 || !rowGrouper) return [undefined, rawRows.length]; + if (groupBy.length === 0 || rowGrouper == null) return [undefined, rawRows.length]; const groupRows = ( rows: readonly R[], diff --git a/src/types.ts b/src/types.ts index 05c31f10cf..37e9875a43 100644 --- a/src/types.ts +++ b/src/types.ts @@ -116,9 +116,9 @@ export interface EditorProps extends SharedEditorPr export interface HeaderRendererProps { column: CalculatedColumn; - sortColumn: string | undefined; - sortDirection: SortDirection | undefined; - onSort: ((columnKey: string, direction: SortDirection) => void) | undefined; + sortColumn: string | undefined | null; + sortDirection: SortDirection | undefined | null; + onSort: ((columnKey: string, direction: SortDirection) => void) | undefined | null; allRowsSelected: boolean; onAllRowsSelectionChange: (checked: boolean) => void; } @@ -156,7 +156,8 @@ export interface CellRendererProps onRowChange: (rowIdx: number, newRow: TRow) => void; onRowClick: | ((rowIdx: number, row: TRow, column: CalculatedColumn) => void) - | undefined; + | undefined + | null; selectCell: (position: Position, enableEditor?: boolean) => void; } @@ -176,8 +177,9 @@ export interface RowRendererProps onRowChange: (rowIdx: number, row: TRow) => void; onRowClick: | ((rowIdx: number, row: TRow, column: CalculatedColumn) => void) - | undefined; - rowClass: ((row: TRow) => string | undefined) | undefined; + | undefined + | null; + rowClass: ((row: TRow) => string | undefined | null) | undefined | null; setDraggedOverRowIdx: ((overRowIdx: number) => void) | undefined; selectCell: (position: Position, enableEditor?: boolean) => void; } diff --git a/src/utils/index.ts b/src/utils/index.ts index 761d53d38e..ac1cf16af9 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -10,9 +10,9 @@ export * from './selectedCellUtils'; export const { min, max, floor, ceil, sign } = Math; -export function assertIsValidKeyGetter( +export function assertIsValidKeyGetter( keyGetter: unknown -): asserts keyGetter is (row: R) => React.Key { +): asserts keyGetter is (row: R) => K { if (typeof keyGetter !== 'function') { throw new Error('Please specify the rowKeyGetter prop to use selection'); } diff --git a/stories/demos/AllFeatures.tsx b/stories/demos/AllFeatures.tsx index 62e7ffda74..3494cdbc16 100644 --- a/stories/demos/AllFeatures.tsx +++ b/stories/demos/AllFeatures.tsx @@ -203,7 +203,7 @@ function loadMoreRows(newRowsCount: number, length: number): Promise { export function AllFeatures() { const [rows, setRows] = useState(() => createRows(2000)); - const [selectedRows, setSelectedRows] = useState(() => new Set()); + const [selectedRows, setSelectedRows] = useState>(() => new Set()); const [isLoading, setIsLoading] = useState(false); function handleFill({ columnKey, sourceRow, targetRows }: FillEvent): Row[] { diff --git a/stories/demos/CommonFeatures.tsx b/stories/demos/CommonFeatures.tsx index 6fce3fde93..042bb14994 100644 --- a/stories/demos/CommonFeatures.tsx +++ b/stories/demos/CommonFeatures.tsx @@ -221,7 +221,7 @@ function createRows(): readonly Row[] { export function CommonFeatures() { const [rows, setRows] = useState(createRows); const [[sortColumn, sortDirection], setSort] = useState<[string, SortDirection]>(['id', 'NONE']); - const [selectedRows, setSelectedRows] = useState(() => new Set()); + const [selectedRows, setSelectedRows] = useState>(() => new Set()); const countries = useMemo(() => { return [...new Set(rows.map((r) => r.country))].sort(new Intl.Collator().compare); diff --git a/stories/demos/Grouping.tsx b/stories/demos/Grouping.tsx index 0ddfe81368..c3abee9d8f 100644 --- a/stories/demos/Grouping.tsx +++ b/stories/demos/Grouping.tsx @@ -162,7 +162,7 @@ const options: OptionsType