From 2a99d9c227cbe9c621d93ae077c0d90719af1729 Mon Sep 17 00:00:00 2001 From: Lauri Date: Fri, 24 Jan 2025 23:44:01 +0100 Subject: [PATCH] [DataGrid] Refactor row state propagation (#15627) Co-authored-by: Rom Grk Co-authored-by: Andrew Cherniavskii --- .../master-detail/FullWidthDetailPanel.js | 6 +- .../master-detail/FullWidthDetailPanel.tsx | 6 +- docs/pages/x/api/data-grid/selectors.json | 14 ++- .../api-docs/data-grid/grid-api.json | 2 +- .../useGridAggregationPreProcessors.tsx | 2 + ...SourceAggregation.DataGridPremium.test.tsx | 6 + .../components/GridDetailPanelToggleCell.tsx | 25 +++- .../columnPinning/useGridColumnPinning.tsx | 2 +- .../useGridColumnPinningPreProcessors.ts | 14 +-- .../useGridDataSourceLazyLoader.ts | 8 +- .../x-data-grid/src/components/GridRow.tsx | 50 +++++--- .../src/components/cell/GridCell.tsx | 108 ++++++++---------- .../columnResize/useGridColumnResize.tsx | 3 +- .../hooks/features/columns/useGridColumns.tsx | 16 ++- .../features/dimensions/useGridDimensions.ts | 20 ++-- .../features/editing/gridEditingSelectors.ts | 21 ++++ .../features/editing/useGridCellEditing.ts | 2 +- .../features/editing/useGridRowEditing.ts | 7 +- .../src/hooks/features/focus/useGridFocus.ts | 6 +- .../useGridKeyboardNavigation.ts | 51 ++++----- .../features/keyboardNavigation/utils.ts | 12 +- .../features/listView/useGridListView.tsx | 3 +- .../pagination/gridPaginationSelector.ts | 14 +-- .../rowSelection/useGridRowSelection.ts | 6 +- .../hooks/features/rows/useGridParamsApi.ts | 65 +++++++---- .../hooks/features/rows/useGridRowSpanning.ts | 41 +++++-- .../src/hooks/features/rows/useGridRows.ts | 18 ++- .../hooks/features/rows/useGridRowsMeta.ts | 5 +- .../gridFocusedVirtualCellSelector.ts | 25 +--- .../virtualization/useGridVirtualScroller.tsx | 39 ++++--- .../src/models/api/gridApiCommon.ts | 5 +- .../src/models/api/gridParamsApi.ts | 49 +++++++- packages/x-data-grid/src/models/api/index.ts | 2 +- .../src/tests/rowSpanning.DataGrid.test.tsx | 10 +- .../src/utils/roundToDecimalPlaces.ts | 3 + .../x-internals/src/isObjectEmpty/index.ts | 1 + .../src/isObjectEmpty/isObjectEmpty.ts | 7 ++ scripts/x-data-grid-premium.exports.json | 2 + scripts/x-data-grid-pro.exports.json | 2 + scripts/x-data-grid.exports.json | 2 + 40 files changed, 398 insertions(+), 282 deletions(-) create mode 100644 packages/x-data-grid/src/utils/roundToDecimalPlaces.ts create mode 100644 packages/x-internals/src/isObjectEmpty/index.ts create mode 100644 packages/x-internals/src/isObjectEmpty/isObjectEmpty.ts diff --git a/docs/data/data-grid/master-detail/FullWidthDetailPanel.js b/docs/data/data-grid/master-detail/FullWidthDetailPanel.js index 83a4aee6f6d12..c967d7d09ab84 100644 --- a/docs/data/data-grid/master-detail/FullWidthDetailPanel.js +++ b/docs/data/data-grid/master-detail/FullWidthDetailPanel.js @@ -21,11 +21,7 @@ import { } from '@mui/x-data-grid-generator'; const getDetailPanelWidth = (gridDimensions) => { - return ( - gridDimensions.viewportInnerSize.width + - gridDimensions.leftPinnedWidth + - gridDimensions.rightPinnedWidth - ); + return gridDimensions.viewportInnerSize.width; }; function DetailPanelContent({ row: rowProp }) { diff --git a/docs/data/data-grid/master-detail/FullWidthDetailPanel.tsx b/docs/data/data-grid/master-detail/FullWidthDetailPanel.tsx index aa30837bb4ac9..646447ac85b05 100644 --- a/docs/data/data-grid/master-detail/FullWidthDetailPanel.tsx +++ b/docs/data/data-grid/master-detail/FullWidthDetailPanel.tsx @@ -24,11 +24,7 @@ import { } from '@mui/x-data-grid-generator'; const getDetailPanelWidth = (gridDimensions: GridDimensions) => { - return ( - gridDimensions.viewportInnerSize.width + - gridDimensions.leftPinnedWidth + - gridDimensions.rightPinnedWidth - ); + return gridDimensions.viewportInnerSize.width; }; function DetailPanelContent({ row: rowProp }: { row: Customer }) { diff --git a/docs/pages/x/api/data-grid/selectors.json b/docs/pages/x/api/data-grid/selectors.json index d3ffa8cabc06d..b76378ef69684 100644 --- a/docs/pages/x/api/data-grid/selectors.json +++ b/docs/pages/x/api/data-grid/selectors.json @@ -146,6 +146,12 @@ "description": "", "supportsApiRef": false }, + { + "name": "gridEditCellStateSelector", + "returnType": "GridEditCellProps", + "description": "", + "supportsApiRef": true + }, { "name": "gridEditRowsStateSelector", "returnType": "GridEditingState", @@ -403,6 +409,12 @@ "description": "", "supportsApiRef": true }, + { + "name": "gridRowIsEditingSelector", + "returnType": "boolean", + "description": "", + "supportsApiRef": true + }, { "name": "gridRowMaximumTreeDepthSelector", "returnType": "number", @@ -554,7 +566,7 @@ }, { "name": "gridVisibleRowsSelector", - "returnType": "{ rows: GridRowEntry[]; range: { firstRowIndex: number; lastRowIndex: number } | null; rowToIndexMap: Map }", + "returnType": "{ rows: GridRowEntry[]; range: { firstRowIndex: number; lastRowIndex: number } | null; rowIdToIndexMap: Map }", "category": "Pagination", "description": "Get the rows, range and rowIndex lookup map after filtering and sorting.\nDoes not contain the collapsed children.", "supportsApiRef": true diff --git a/docs/translations/api-docs/data-grid/grid-api.json b/docs/translations/api-docs/data-grid/grid-api.json index 1c9c515175209..1bc85f2528eb8 100644 --- a/docs/translations/api-docs/data-grid/grid-api.json +++ b/docs/translations/api-docs/data-grid/grid-api.json @@ -26,7 +26,7 @@ "getAllGroupDetails": { "description": "Returns the column group lookup." }, "getAllRowIds": { "description": "Gets the list of row ids." }, "getCellElement": { - "description": "Gets the underlying DOM element for a cell at the given id and field." + "description": "Gets the GridCellParams object that is passed as argument in events." }, "getCellMode": { "description": "Gets the mode of a cell." }, "getCellParams": { diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx index 0469a72d59ebd..5681f34bf113d 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx @@ -74,6 +74,8 @@ export const useGridAggregationPreProcessors = ( rulesOnLastColumnHydration.current = aggregationRules; + apiRef.current.caches.aggregation.rulesOnLastColumnHydration = aggregationRules; + return columnsState; }, [apiRef, props.aggregationFunctions, props.disableAggregation, props.unstable_dataSource], diff --git a/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx b/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx index f7a7e2e626092..f7065916f2363 100644 --- a/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx +++ b/packages/x-data-grid-premium/src/tests/dataSourceAggregation.DataGridPremium.test.tsx @@ -99,12 +99,18 @@ describe(' - Data source aggregation', () => { it('should show aggregation option in the column menu', async () => { const { user } = render(); + await waitFor(() => { + expect(getRowsSpy.callCount).to.be.greaterThan(0); + }); await user.click(within(getColumnHeaderCell(0)).getByLabelText('Menu')); expect(screen.queryByLabelText('Aggregation')).not.to.equal(null); }); it('should not show aggregation option in the column menu when no aggregation function is defined', async () => { const { user } = render(); + await waitFor(() => { + expect(getRowsSpy.callCount).to.be.greaterThan(0); + }); await user.click(within(getColumnHeaderCell(0)).getByLabelText('Menu')); expect(screen.queryByLabelText('Aggregation')).to.equal(null); }); diff --git a/packages/x-data-grid-pro/src/components/GridDetailPanelToggleCell.tsx b/packages/x-data-grid-pro/src/components/GridDetailPanelToggleCell.tsx index 0df3ae4e9dbb4..359d2b6bd3092 100644 --- a/packages/x-data-grid-pro/src/components/GridDetailPanelToggleCell.tsx +++ b/packages/x-data-grid-pro/src/components/GridDetailPanelToggleCell.tsx @@ -1,11 +1,21 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import composeClasses from '@mui/utils/composeClasses'; -import { getDataGridUtilityClass, useGridSelector, GridRenderCellParams } from '@mui/x-data-grid'; +import { + getDataGridUtilityClass, + useGridSelector, + GridRenderCellParams, + GridRowId, +} from '@mui/x-data-grid'; +import { createSelector } from '@mui/x-data-grid/internals'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { DataGridProProcessedProps } from '../models/dataGridProProps'; -import { gridDetailPanelExpandedRowsContentCacheSelector } from '../hooks/features/detailPanel/gridDetailPanelSelector'; +import { + gridDetailPanelExpandedRowIdsSelector, + gridDetailPanelExpandedRowsContentCacheSelector, +} from '../hooks/features/detailPanel/gridDetailPanelSelector'; +import { GridApiPro } from '../models'; type OwnerState = { classes: DataGridProProcessedProps['classes']; isExpanded: boolean }; @@ -19,8 +29,17 @@ const useUtilityClasses = (ownerState: OwnerState) => { return composeClasses(slots, getDataGridUtilityClass, classes); }; +const isExpandedSelector = createSelector( + gridDetailPanelExpandedRowIdsSelector, + (expandedRowIds, rowId: GridRowId) => { + return expandedRowIds.has(rowId); + }, +); + function GridDetailPanelToggleCell(props: GridRenderCellParams) { - const { id, value: isExpanded } = props; + const { id, row, api } = props; + const rowId = api.getRowId(row); + const isExpanded = useGridSelector({ current: api as GridApiPro }, isExpandedSelector, rowId); const rootProps = useGridRootProps(); const apiRef = useGridApiContext(); diff --git a/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinning.tsx b/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinning.tsx index caa058c643e97..66e3bdb130775 100644 --- a/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinning.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinning.tsx @@ -234,7 +234,7 @@ export const useGridColumnPinning = ( const setPinnedColumns = React.useCallback( (newPinnedColumns) => { setState(apiRef, newPinnedColumns); - apiRef.current.forceUpdate(); + apiRef.current.requestPipeProcessorsApplication('hydrateColumns'); }, [apiRef], ); diff --git a/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinningPreProcessors.ts b/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinningPreProcessors.ts index 99ed8c41b9700..fd677688d6e66 100644 --- a/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinningPreProcessors.ts +++ b/packages/x-data-grid-pro/src/hooks/features/columnPinning/useGridColumnPinningPreProcessors.ts @@ -1,11 +1,8 @@ import * as React from 'react'; import { RefObject } from '@mui/x-internals/types'; import { - GridPinnedColumnFields, GridPipeProcessor, - gridPinnedColumnsSelector, useGridRegisterPipeProcessor, - eslintUseValue, gridVisiblePinnedColumnDefinitionsSelector, } from '@mui/x-data-grid/internals'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; @@ -17,19 +14,10 @@ export const useGridColumnPinningPreProcessors = ( ) => { const { disableColumnPinning } = props; - let pinnedColumns: GridPinnedColumnFields | null; - if (apiRef.current.state.columns) { - pinnedColumns = gridPinnedColumnsSelector(apiRef.current.state); - } else { - pinnedColumns = null; - } - const prevAllPinnedColumns = React.useRef([]); const reorderPinnedColumns = React.useCallback>( (columnsState) => { - eslintUseValue(pinnedColumns); - if (columnsState.orderedFields.length === 0 || disableColumnPinning) { return columnsState; } @@ -132,7 +120,7 @@ export const useGridColumnPinningPreProcessors = ( orderedFields: [...leftPinnedColumns, ...centerColumns, ...rightPinnedColumns], }; }, - [apiRef, disableColumnPinning, pinnedColumns], + [apiRef, disableColumnPinning], ); useGridRegisterPipeProcessor(apiRef, 'hydrateColumns', reorderPinnedColumns); diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts index d616732a2ed80..5e14c182e0b33 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideLazyLoader/useGridDataSourceLazyLoader.ts @@ -235,13 +235,13 @@ export const useGridDataSourceLazyLoader = ( const newLoadingTrigger = rowCount === -1 ? LoadingTrigger.SCROLL_END : LoadingTrigger.VIEWPORT; - if (loadingTrigger.current !== newLoadingTrigger) { - loadingTrigger.current = newLoadingTrigger; - } - if (loadingTrigger.current !== null) { ensureValidRowCount(loadingTrigger.current, newLoadingTrigger); } + + if (loadingTrigger.current !== newLoadingTrigger) { + loadingTrigger.current = newLoadingTrigger; + } }, [ensureValidRowCount], ); diff --git a/packages/x-data-grid/src/components/GridRow.tsx b/packages/x-data-grid/src/components/GridRow.tsx index 2258692bcb7ff..7e63bbd4814b9 100644 --- a/packages/x-data-grid/src/components/GridRow.tsx +++ b/packages/x-data-grid/src/components/GridRow.tsx @@ -5,9 +5,10 @@ import clsx from 'clsx'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; import { fastMemo } from '@mui/x-internals/fastMemo'; import { forwardRef } from '@mui/x-internals/forwardRef'; +import { isObjectEmpty } from '@mui/x-internals/isObjectEmpty'; import { GridRowEventLookup } from '../models/events'; import { GridRowId, GridRowModel } from '../models/gridRows'; -import { GridEditModes, GridRowModes, GridCellModes } from '../models/gridEditRowModel'; +import { GridEditModes, GridCellModes } from '../models/gridEditRowModel'; import { gridClasses } from '../constants/gridClasses'; import { composeGridClasses } from '../utils/composeGridClasses'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; @@ -24,11 +25,26 @@ import { GRID_ACTIONS_COLUMN_TYPE } from '../colDef/gridActionsColDef'; import { GRID_DETAIL_PANEL_TOGGLE_FIELD, PinnedColumnPosition } from '../internals/constants'; import { gridSortModelSelector } from '../hooks/features/sorting/gridSortingSelector'; import { gridRowMaximumTreeDepthSelector } from '../hooks/features/rows/gridRowsSelector'; -import { gridEditRowsStateSelector } from '../hooks/features/editing/gridEditingSelectors'; +import { + gridEditRowsStateSelector, + gridRowIsEditingSelector, +} from '../hooks/features/editing/gridEditingSelectors'; import { GridScrollbarFillerCell as ScrollbarFiller } from './GridScrollbarFillerCell'; import { getPinnedCellOffset } from '../internals/utils/getPinnedCellOffset'; import { useGridConfiguration } from '../hooks/utils/useGridConfiguration'; import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext'; +import { createSelector } from '../utils/createSelector'; + +const isRowReorderingEnabledSelector = createSelector( + gridEditRowsStateSelector, + (editRows, rowReordering: boolean) => { + if (!rowReordering) { + return false; + } + const isEditingRows = !isObjectEmpty(editRows); + return !isEditingRows; + }, +); export interface GridRowProps extends React.HTMLAttributes { row: GridRowModel; @@ -104,10 +120,15 @@ const GridRow = forwardRef(function GridRow(props, const sortModel = useGridSelector(apiRef, gridSortModelSelector); const treeDepth = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector); const columnPositions = useGridSelector(apiRef, gridColumnPositionsSelector); - const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); + const rowReordering = (rootProps as any).rowReordering as boolean; + const isRowReorderingEnabled = useGridSelector( + apiRef, + isRowReorderingEnabledSelector, + rowReordering, + ); const handleRef = useForkRef(ref, refProp); const rowNode = apiRef.current.getRowNode(rowId); - const editing = apiRef.current.getRowMode(rowId) === GridRowModes.Edit; + const editing = useGridSelector(apiRef, gridRowIsEditingSelector, rowId); const editable = rootProps.editMode === GridEditModes.Row; const hasFocusCell = focusedColumnIndex !== undefined; const hasVirtualFocusCellLeft = @@ -216,8 +237,6 @@ const GridRow = forwardRef(function GridRow(props, const { slots, slotProps, disableColumnReorder } = rootProps; - const rowReordering = (rootProps as any).rowReordering as boolean; - const heightEntry = useGridSelector( apiRef, () => ({ ...apiRef.current.getRowHeightEntry(rowId) }), @@ -275,6 +294,11 @@ const GridRow = forwardRef(function GridRow(props, rowClassNames.push(rootProps.getRowClassName(rowParams)); } + /* Start of rendering */ + if (!rowNode) { + return null; + } + const getCell = ( column: GridStateColDef, indexInSection: number, @@ -316,15 +340,12 @@ const GridRow = forwardRef(function GridRow(props, ); } - const editCellState = editRowsState[rowId]?.[column.field] ?? null; - // when the cell is a reorder cell we are not allowing to reorder the col // fixes https://github.com/mui/mui-x/issues/11126 const isReorderCell = column.field === '__reorder__'; - const isEditingRows = Object.keys(editRowsState).length > 0; const canReorderColumn = !(disableColumnReorder || column.disableReorder); - const canReorderRow = rowReordering && !sortModel.length && treeDepth <= 1 && !isEditingRows; + const canReorderRow = isRowReorderingEnabled && !sortModel.length && treeDepth <= 1; const disableDragEvents = !(canReorderColumn || (isReorderCell && canReorderRow)); @@ -349,23 +370,18 @@ const GridRow = forwardRef(function GridRow(props, colIndex={indexRelativeToAllColumns} colSpan={colSpan} disableDragEvents={disableDragEvents} - editCellState={editCellState} isNotVisible={cellIsNotVisible} pinnedOffset={pinnedOffset} pinnedPosition={pinnedPosition} showLeftBorder={showLeftBorder} showRightBorder={showRightBorder} + row={row} + rowNode={rowNode} {...slotProps?.cell} /> ); }; - /* Start of rendering */ - - if (!rowNode) { - return null; - } - const leftCells = pinnedColumns.left.map((column, i) => { const indexRelativeToAllColumns = i; return getCell( diff --git a/packages/x-data-grid/src/components/cell/GridCell.tsx b/packages/x-data-grid/src/components/cell/GridCell.tsx index ec9e97550c75b..fa8f9be1e0969 100644 --- a/packages/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridCell.tsx @@ -25,12 +25,14 @@ import { FocusElement, GridCellParams, } from '../../models/params/gridCellParams'; -import { GridColDef, GridAlignment } from '../../models/colDef/gridColDef'; -import { GridTreeNodeWithRender } from '../../models/gridRows'; -import { useGridSelector, objectShallowCompare } from '../../hooks/utils/useGridSelector'; -import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; +import { GridAlignment, GridStateColDef } from '../../models/colDef/gridColDef'; +import { GridRowModel, GridTreeNode, GridTreeNodeWithRender } from '../../models/gridRows'; +import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; -import { gridFocusCellSelector } from '../../hooks/features/focus/gridFocusStateSelector'; +import { + gridFocusCellSelector, + gridTabIndexCellSelector, +} from '../../hooks/features/focus/gridFocusStateSelector'; import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { GridPinnedColumnPosition } from '../../hooks/features/columns/gridColumnsInterfaces'; import { PinnedColumnPosition } from '../../internals/constants'; @@ -38,6 +40,8 @@ import { gridRowSpanningHiddenCellsSelector, gridRowSpanningSpannedCellsSelector, } from '../../hooks/features/rows/gridRowSpanningSelectors'; +import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiContext'; +import { gridEditCellStateSelector } from '../../hooks/features/editing/gridEditingSelectors'; import { attachPinnedStyle } from '../../internals/utils'; export const gridPinnedColumnPositionLookup = { @@ -51,13 +55,14 @@ export type GridCellProps = React.HTMLAttributes & { align: GridAlignment; className?: string; colIndex: number; - column: GridColDef; + column: GridStateColDef; + row: GridRowModel; rowId: GridRowId; + rowNode: GridTreeNode; width: number; colSpan?: number; disableDragEvents?: boolean; isNotVisible: boolean; - editCellState: GridEditCellProps | null; pinnedOffset?: number; pinnedPosition: PinnedColumnPosition; showRightBorder: boolean; @@ -79,31 +84,6 @@ export type GridCellProps = React.HTMLAttributes & { [x: `data-${string}`]: string; }; -const EMPTY_CELL_PARAMS: GridCellParams = { - id: -1, - field: '__unset__', - row: {}, - rowNode: { - id: -1, - depth: 0, - type: 'leaf', - parent: -1, - groupingKey: null, - }, - colDef: { - type: 'string', - field: '__unset__', - computedWidth: 0, - }, - cellMode: GridCellModes.View, - hasFocus: false, - tabIndex: -1, - value: null, - formattedValue: '__unset__', - isEditable: false, - api: {} as any, -}; - type OwnerState = Pick & { showLeftBorder: boolean; showRightBorder: boolean; @@ -149,8 +129,9 @@ let warnedOnce = false; const GridCell = forwardRef(function GridCell(props, ref) { const { column, + row, rowId, - editCellState, + rowNode, align, children: childrenProp, colIndex, @@ -176,34 +157,43 @@ const GridCell = forwardRef(function GridCell(pro ...other } = props; - const apiRef = useGridApiContext(); + const apiRef = useGridPrivateApiContext(); const rootProps = useGridRootProps(); const isRtl = useRtl(); const field = column.field; - const cellParams = useGridSelector( + const editCellState: GridEditCellProps | null = useGridSelector( apiRef, - () => { - // This is required because `.getCellParams` tries to get the `state.rows.tree` entry - // associated with `rowId`/`fieldId`, but this selector runs after the state has been - // updated, while `rowId`/`fieldId` reference an entry in the old state. - const row = apiRef.current.getRow(rowId); - if (!row) { - return EMPTY_CELL_PARAMS; - } - - const result = apiRef.current.getCellParams( - rowId, - field, - ); - result.api = apiRef.current; - return result; + gridEditCellStateSelector, + { + rowId, + field, }, - undefined, - objectShallowCompare, ); + const cellMode: GridCellModes = editCellState ? GridCellModes.Edit : GridCellModes.View; + + const cellParams: GridCellParams = apiRef.current.getCellParamsForRow< + any, + any, + any, + GridTreeNodeWithRender + >(rowId, field, row, { + colDef: column, + cellMode, + rowNode: rowNode as GridTreeNodeWithRender, + tabIndex: useGridSelector(apiRef, () => { + const cellTabIndex = gridTabIndexCellSelector(apiRef); + return cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === rowId ? 0 : -1; + }), + hasFocus: useGridSelector(apiRef, () => { + const focus = gridFocusCellSelector(apiRef); + return focus?.id === rowId && focus.field === field; + }), + }); + cellParams.api = apiRef.current; + const isSelected = useGridSelector(apiRef, () => apiRef.current.unstable_applyPipeProcessors('isCellSelected', false, { id: rowId, @@ -214,7 +204,7 @@ const GridCell = forwardRef(function GridCell(pro const hiddenCells = useGridSelector(apiRef, gridRowSpanningHiddenCellsSelector); const spannedCells = useGridSelector(apiRef, gridRowSpanningSpannedCellsSelector); - const { cellMode, hasFocus, isEditable = false, value } = cellParams; + const { hasFocus, isEditable = false, value } = cellParams; const canManageOwnFocus = column.type === 'actions' && @@ -385,10 +375,6 @@ const GridCell = forwardRef(function GridCell(pro ); } - if (cellParams === EMPTY_CELL_PARAMS) { - return null; - } - let handleFocus: any = other.onFocus; if ( @@ -504,16 +490,12 @@ GridCell.propTypes = { colSpan: PropTypes.number, column: PropTypes.object.isRequired, disableDragEvents: PropTypes.bool, - editCellState: PropTypes.shape({ - changeReason: PropTypes.oneOf(['debouncedSetEditCellValue', 'setEditCellValue']), - isProcessingProps: PropTypes.bool, - isValidating: PropTypes.bool, - value: PropTypes.any, - }), isNotVisible: PropTypes.bool.isRequired, pinnedOffset: PropTypes.number, pinnedPosition: PropTypes.oneOf([0, 1, 2, 3]).isRequired, + row: PropTypes.object.isRequired, rowId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, + rowNode: PropTypes.object.isRequired, showLeftBorder: PropTypes.bool.isRequired, showRightBorder: PropTypes.bool.isRequired, width: PropTypes.number.isRequired, diff --git a/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx b/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx index db6720d95661b..fed758b7d21aa 100644 --- a/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx +++ b/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx @@ -746,7 +746,8 @@ export const useGridColumnResize = ( total + (widthByField[column.field] ?? column.computedWidth ?? column.width), 0, ); - const availableWidth = apiRef.current.getRootDimensions().viewportInnerSize.width; + const dimensions = apiRef.current.getRootDimensions(); + const availableWidth = dimensions.viewportInnerSize.width; const remainingWidth = availableWidth - totalWidth; if (remainingWidth > 0) { diff --git a/packages/x-data-grid/src/hooks/features/columns/useGridColumns.tsx b/packages/x-data-grid/src/hooks/features/columns/useGridColumns.tsx index 713e99204b5f7..ae65ff306aac3 100644 --- a/packages/x-data-grid/src/hooks/features/columns/useGridColumns.tsx +++ b/packages/x-data-grid/src/hooks/features/columns/useGridColumns.tsx @@ -392,11 +392,17 @@ export function useGridColumns( */ const prevInnerWidth = React.useRef(null); - const handleGridSizeChange: GridEventListener<'viewportInnerSizeChange'> = ( - viewportInnerSize, - ) => { - if (prevInnerWidth.current !== viewportInnerSize.width) { - prevInnerWidth.current = viewportInnerSize.width; + const handleGridSizeChange: GridEventListener<'viewportInnerSizeChange'> = (size) => { + if (prevInnerWidth.current !== size.width) { + prevInnerWidth.current = size.width; + + const hasFlexColumns = gridVisibleColumnDefinitionsSelector(apiRef).some( + (col) => col.flex && col.flex > 0, + ); + if (!hasFlexColumns) { + return; + } + setGridColumnsState( hydrateColumnsWidth( gridColumnsStateSelector(apiRef.current.state), diff --git a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts index 166c55c01ae2c..e37f70936cab9 100644 --- a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts +++ b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts @@ -35,6 +35,7 @@ import { import { getTotalHeaderHeight } from '../columns/gridColumnsUtils'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; import { DATA_GRID_PROPS_DEFAULT_VALUES } from '../../../constants/dataGridPropsDefaultValues'; +import { roundToDecimalPlaces } from '../../../utils/roundToDecimalPlaces'; import { isJSDOM } from '../../../utils/isJSDOM'; type RootProps = Pick< @@ -112,7 +113,7 @@ export function useGridDimensions(apiRef: RefObject, pr const headerFilterHeight = Math.floor( (props.headerFilterHeight ?? props.columnHeaderHeight) * densityFactor, ); - const columnsTotalWidth = roundToDecimalPlaces(gridColumnsTotalWidthSelector(apiRef), 6); + const columnsTotalWidth = roundToDecimalPlaces(gridColumnsTotalWidthSelector(apiRef), 1); const headersTotalHeight = getTotalHeaderHeight(apiRef, props); const leftPinnedWidth = pinnedColumns.left.reduce((w, col) => w + col.computedWidth, 0); @@ -159,6 +160,9 @@ export function useGridDimensions(apiRef: RefObject, pr }, [apiRef, props.pagination, props.paginationMode, props.getRowHeight, rowHeight]); const updateDimensions = React.useCallback(() => { + // All the floating point dimensions should be rounded to .1 decimal places to avoid subpixel rendering issues + // https://github.com/mui/mui-x/issues/9550#issuecomment-1619020477 + // https://github.com/mui/mui-x/issues/15721 const rootElement = apiRef.current.rootElementRef.current; const pinnedRowsHeight = calculatePinnedRowsHeight(apiRef); @@ -167,11 +171,9 @@ export function useGridDimensions(apiRef: RefObject, pr const topContainerHeight = headersTotalHeight + pinnedRowsHeight.top; const bottomContainerHeight = pinnedRowsHeight.bottom; - const nonPinnedColumnsTotalWidth = columnsTotalWidth - leftPinnedWidth - rightPinnedWidth; - const contentSize = { - width: nonPinnedColumnsTotalWidth, - height: rowsMeta.currentPageTotalHeight, + width: columnsTotalWidth, + height: roundToDecimalPlaces(rowsMeta.currentPageTotalHeight, 1), }; let viewportOuterSize: ElementSize; @@ -197,7 +199,7 @@ export function useGridDimensions(apiRef: RefObject, pr height: rootDimensionsRef.current.height, }; viewportInnerSize = { - width: Math.max(0, viewportOuterSize.width - leftPinnedWidth - rightPinnedWidth), + width: Math.max(0, viewportOuterSize.width), height: Math.max(0, viewportOuterSize.height - topContainerHeight - bottomContainerHeight), }; @@ -398,12 +400,6 @@ function measureScrollbarSize( return size; } -// Get rid of floating point imprecision errors -// https://github.com/mui/mui-x/issues/9550#issuecomment-1619020477 -function roundToDecimalPlaces(value: number, decimals: number) { - return Math.round(value * 10 ** decimals) / 10 ** decimals; -} - function areElementSizesEqual(a: ElementSize, b: ElementSize) { return a.width === b.width && a.height === b.height; } diff --git a/packages/x-data-grid/src/hooks/features/editing/gridEditingSelectors.ts b/packages/x-data-grid/src/hooks/features/editing/gridEditingSelectors.ts index 5ee7bfac20574..988fe7f81fd92 100644 --- a/packages/x-data-grid/src/hooks/features/editing/gridEditingSelectors.ts +++ b/packages/x-data-grid/src/hooks/features/editing/gridEditingSelectors.ts @@ -1,6 +1,27 @@ +import { createSelector } from '../../../utils/createSelector'; import { GridStateCommunity } from '../../../models/gridStateCommunity'; +import { GridRowId } from '../../../models/gridRows'; /** * Select the row editing state. */ export const gridEditRowsStateSelector = (state: GridStateCommunity) => state.editRows; + +export const gridRowIsEditingSelector = createSelector( + gridEditRowsStateSelector, + (editRows, rowId: GridRowId) => Boolean(editRows[rowId]), +); + +export const gridEditCellStateSelector = createSelector( + gridEditRowsStateSelector, + ( + editRows, + { + rowId, + field, + }: { + rowId: GridRowId; + field: string; + }, + ) => editRows[rowId]?.[field] ?? null, +); diff --git a/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts b/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts index 054d1cf818d59..b796e32df9a14 100644 --- a/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts +++ b/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts @@ -570,7 +570,7 @@ export const useGridCellEditing = ( Object.entries(cellModesModel).forEach(([id, fields]) => { Object.entries(fields).forEach(([field, params]) => { const prevMode = copyOfPrevCellModes[id]?.[field]?.mode || GridCellModes.View; - const originalId = apiRef.current.getRowId(rowsLookup[id]) ?? id; + const originalId = rowsLookup[id] ? apiRef.current.getRowId(rowsLookup[id]) : id; if (params.mode === GridCellModes.Edit && prevMode === GridCellModes.View) { updateStateToStartCellEditMode({ id: originalId, field, ...params }); } else if (params.mode === GridCellModes.View && prevMode === GridCellModes.Edit) { diff --git a/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts b/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts index f06f11c1dbe44..62f53ac3ff5a0 100644 --- a/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts +++ b/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts @@ -30,7 +30,7 @@ import { GridEditingSharedPrivateApi, } from '../../../models/api/gridEditingApi'; import { useGridApiMethod } from '../../utils/useGridApiMethod'; -import { gridEditRowsStateSelector } from './gridEditingSelectors'; +import { gridEditRowsStateSelector, gridRowIsEditingSelector } from './gridEditingSelectors'; import { GridRowId } from '../../../models/gridRows'; import { isPrintableKey, isPasteShortcut } from '../../../utils/keyboardUtils'; import { @@ -331,8 +331,7 @@ export const useGridRowEditing = ( if (props.editMode === GridEditModes.Cell) { return GridRowModes.View; } - const editingState = gridEditRowsStateSelector(apiRef.current.state); - const isEditing = editingState[id] && Object.keys(editingState[id]).length > 0; + const isEditing = gridRowIsEditingSelector(apiRef.current.state, id); return isEditing ? GridRowModes.Edit : GridRowModes.View; }, [apiRef, props.editMode], @@ -754,7 +753,7 @@ export const useGridRowEditing = ( Array.from(ids).forEach((id) => { const params = rowModesModel[id] ?? { mode: GridRowModes.View }; const prevMode = copyOfPrevRowModesModel[id]?.mode || GridRowModes.View; - const originalId = apiRef.current.getRowId(rowsLookup[id]) ?? id; + const originalId = rowsLookup[id] ? apiRef.current.getRowId(rowsLookup[id]) : id; if (params.mode === GridRowModes.Edit && prevMode === GridRowModes.View) { updateStateToStartRowEditMode({ id: originalId, ...params }); } else if (params.mode === GridRowModes.View && prevMode === GridRowModes.Edit) { diff --git a/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts b/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts index 55e5f75194082..a4866af957642 100644 --- a/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts +++ b/packages/x-data-grid/src/hooks/features/focus/useGridFocus.ts @@ -427,12 +427,12 @@ export const useGridFocus = ( // If the focused cell is in a row which does not exist anymore, // focus previous row or remove the focus if (cell && !apiRef.current.getRow(cell.id)) { - const lastFocusedRowId = gridFocusCellSelector(apiRef)?.id; + const lastFocusedRowId = cell.id; let nextRowId: GridRowId | null = null; if (typeof lastFocusedRowId !== 'undefined') { - const lastFocusedRowIndex = - apiRef.current.getRowIndexRelativeToVisibleRows(lastFocusedRowId); + const rowEl = apiRef.current.getRowElement(lastFocusedRowId); + const lastFocusedRowIndex = rowEl?.dataset.rowindex ? Number(rowEl?.dataset.rowindex) : 0; const currentPage = getVisibleRows(apiRef, { pagination: props.pagination, paginationMode: props.paginationMode, diff --git a/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts b/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts index ea4d89bfdc84f..6811cfec50300 100644 --- a/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts +++ b/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts @@ -17,7 +17,6 @@ import { useGridLogger } from '../../utils/useGridLogger'; import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { gridExpandedSortedRowEntriesSelector } from '../filter/gridFilterSelector'; -import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../../../colDef/gridCheckboxSelectionColDef'; import { gridClasses } from '../../../constants/gridClasses'; import { GridCellModes } from '../../../models/gridEditRowModel'; @@ -31,13 +30,19 @@ import { } from '../headerFiltering/gridHeaderFilteringSelectors'; import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; import { isEventTargetInPortal } from '../../../utils/domUtils'; -import { - enrichPageRowsWithPinnedRows, - getLeftColumnIndex, - getRightColumnIndex, - findNonRowSpannedCell, -} from './utils'; +import { getLeftColumnIndex, getRightColumnIndex, findNonRowSpannedCell } from './utils'; import { gridListColumnSelector } from '../listView/gridListViewSelectors'; +import { createSelectorMemoized } from '../../../utils/createSelector'; +import { gridVisibleRowsSelector } from '../pagination'; +import { gridPinnedRowsSelector } from '../rows/gridRowsSelector'; + +const gridVisibleRowsWithPinnedRowsSelector = createSelectorMemoized( + gridVisibleRowsSelector, + gridPinnedRowsSelector, + (visibleRows, pinnedRows) => { + return (pinnedRows.top || []).concat(visibleRows.rows, pinnedRows.bottom || []); + }, +); /** * @requires useGridSorting (method) - can be after @@ -62,14 +67,12 @@ export const useGridKeyboardNavigation = ( >, ): void => { const logger = useGridLogger(apiRef, 'useGridKeyboardNavigation'); - const initialCurrentPageRows = useGridVisibleRows(apiRef, props).rows; const isRtl = useRtl(); const listView = props.unstable_listView; - const currentPageRows = React.useMemo( - () => enrichPageRowsWithPinnedRows(apiRef, initialCurrentPageRows), - [apiRef, initialCurrentPageRows], - ); + const getCurrentPageRows = React.useCallback(() => { + return gridVisibleRowsWithPinnedRowsSelector(apiRef); + }, [apiRef]); const headerFilteringEnabled = props.signature !== 'DataGrid' && props.headerFilters; @@ -147,9 +150,9 @@ export const useGridKeyboardNavigation = ( const getRowIdFromIndex = React.useCallback( (rowIndex: number) => { - return currentPageRows[rowIndex]?.id; + return getCurrentPageRows()[rowIndex]?.id; }, - [currentPageRows], + [getCurrentPageRows], ); const handleColumnHeaderKeyDown = React.useCallback>( @@ -166,6 +169,7 @@ export const useGridKeyboardNavigation = ( return; } + const currentPageRows = getCurrentPageRows(); const viewportPageSize = apiRef.current.getViewportPageSize(); const colIndexBefore = params.field ? apiRef.current.getColumnIndex(params.field) : 0; const firstRowIndexInPage = currentPageRows.length > 0 ? 0 : null; @@ -267,7 +271,7 @@ export const useGridKeyboardNavigation = ( }, [ apiRef, - currentPageRows.length, + getCurrentPageRows, headerFilteringEnabled, goToHeaderFilter, goToCell, @@ -287,6 +291,7 @@ export const useGridKeyboardNavigation = ( return; } + const currentPageRows = getCurrentPageRows(); const viewportPageSize = apiRef.current.getViewportPageSize(); const colIndexBefore = params.field ? apiRef.current.getColumnIndex(params.field) : 0; const firstRowIndexInPage = 0; @@ -375,15 +380,7 @@ export const useGridKeyboardNavigation = ( event.preventDefault(); } }, - [ - apiRef, - currentPageRows.length, - goToHeaderFilter, - isRtl, - goToHeader, - goToCell, - getRowIdFromIndex, - ], + [apiRef, getCurrentPageRows, goToHeaderFilter, isRtl, goToHeader, goToCell, getRowIdFromIndex], ); const handleColumnGroupHeaderKeyDown = React.useCallback< @@ -398,6 +395,7 @@ export const useGridKeyboardNavigation = ( const { fields, depth, maxDepth } = params; + const currentPageRows = getCurrentPageRows(); const viewportPageSize = apiRef.current.getViewportPageSize(); const currentColIndex = apiRef.current.getColumnIndex(currentField); const colIndexBefore = currentField ? apiRef.current.getColumnIndex(currentField) : 0; @@ -477,7 +475,7 @@ export const useGridKeyboardNavigation = ( event.preventDefault(); } }, - [apiRef, currentPageRows.length, goToHeader, goToGroupHeader, goToCell, getRowIdFromIndex], + [apiRef, getCurrentPageRows, goToHeader, goToGroupHeader, goToCell, getRowIdFromIndex], ); const handleCellKeyDown = React.useCallback>( @@ -503,6 +501,7 @@ export const useGridKeyboardNavigation = ( return; } + const currentPageRows = getCurrentPageRows(); if (currentPageRows.length === 0) { return; } @@ -655,7 +654,7 @@ export const useGridKeyboardNavigation = ( }, [ apiRef, - currentPageRows, + getCurrentPageRows, isRtl, goToCell, getRowIdFromIndex, diff --git a/packages/x-data-grid/src/hooks/features/keyboardNavigation/utils.ts b/packages/x-data-grid/src/hooks/features/keyboardNavigation/utils.ts index 543ba8d83249d..5191ecac97fae 100644 --- a/packages/x-data-grid/src/hooks/features/keyboardNavigation/utils.ts +++ b/packages/x-data-grid/src/hooks/features/keyboardNavigation/utils.ts @@ -1,18 +1,8 @@ import { RefObject } from '@mui/x-internals/types'; import { gridFilteredSortedRowIdsSelector } from '../filter/gridFilterSelector'; -import { GridColDef, GridRowEntry, GridRowId } from '../../../models'; +import { GridColDef, GridRowId } from '../../../models'; import { gridRowSpanningHiddenCellsSelector } from '../rows/gridRowSpanningSelectors'; import { GridApiCommunity } from '../../../models/api/gridApiCommunity'; -import { gridPinnedRowsSelector } from '../rows/gridRowsSelector'; - -export function enrichPageRowsWithPinnedRows( - apiRef: RefObject, - rows: GridRowEntry[], -) { - const pinnedRows = gridPinnedRowsSelector(apiRef) || {}; - - return [...(pinnedRows.top || []), ...rows, ...(pinnedRows.bottom || [])]; -} export const getLeftColumnIndex = ({ currentColIndex, diff --git a/packages/x-data-grid/src/hooks/features/listView/useGridListView.tsx b/packages/x-data-grid/src/hooks/features/listView/useGridListView.tsx index a10db552401d1..8f48ca31e87bb 100644 --- a/packages/x-data-grid/src/hooks/features/listView/useGridListView.tsx +++ b/packages/x-data-grid/src/hooks/features/listView/useGridListView.tsx @@ -1,4 +1,5 @@ import * as React from 'react'; +import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import { RefObject } from '@mui/x-internals/types'; import { warnOnce } from '@mui/x-internals/warning'; import type { GridListColDef } from '../../../models/colDef/gridColDef'; @@ -58,7 +59,7 @@ export function useGridListView( /* * EFFECTS */ - React.useEffect(() => { + useEnhancedEffect(() => { const listColumn = props.unstable_listColumn; if (listColumn) { apiRef.current.setState((state) => { diff --git a/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts b/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts index c1ef44002ec7b..fa04d26aae4a9 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/gridPaginationSelector.ts @@ -7,7 +7,7 @@ import { } from '../filter/gridFilterSelector'; import { gridRowMaximumTreeDepthSelector, gridRowTreeSelector } from '../rows/gridRowsSelector'; import { getPageCount } from './gridPaginationUtils'; -import { GridValidRowModel } from '../../../models/gridRows'; +import { GridRowId } from '../../../models/gridRows'; const ALL_RESULTS_PAGE_VALUE = -1; @@ -213,10 +213,10 @@ export const gridVisibleRowsSelector = createSelectorMemoized( return { rows: paginationRows, range: paginationRowRange, - rowToIndexMap: paginationRows.reduce((lookup, row, index) => { - lookup.set(row.model, index); + rowIdToIndexMap: paginationRows.reduce((lookup, row, index) => { + lookup.set(row.id, index); return lookup; - }, new Map()), + }, new Map()), }; } @@ -229,10 +229,10 @@ export const gridVisibleRowsSelector = createSelectorMemoized( firstRowIndex: 0, lastRowIndex: expandedSortedRowEntries.length - 1, }, - rowToIndexMap: expandedSortedRowEntries.reduce((lookup, row, index) => { - lookup.set(row.model, index); + rowIdToIndexMap: expandedSortedRowEntries.reduce((lookup, row, index) => { + lookup.set(row.id, index); return lookup; - }, new Map()), + }, new Map()), }; }, ); diff --git a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts index 6f2a156b2800b..bd3e4270c606a 100644 --- a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts +++ b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts @@ -31,7 +31,7 @@ import { import { GRID_CHECKBOX_SELECTION_COL_DEF, GRID_ACTIONS_COLUMN_TYPE } from '../../../colDef'; import { GridCellModes } from '../../../models/gridEditRowModel'; import { isKeyboardEvent, isNavigationKey } from '../../../utils/keyboardUtils'; -import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; +import { getVisibleRows } from '../../utils/useGridVisibleRows'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; import { GridRowSelectionModel } from '../../../models'; import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../../../internals/constants'; @@ -134,7 +134,6 @@ export const useGridRowSelection = ( } = props; const canHaveMultipleSelection = isMultipleRowSelectionEnabled(props); - const visibleRows = useGridVisibleRows(apiRef, props); const tree = useGridSelector(apiRef, gridRowTreeSelector); const isNestedData = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector) > 1; @@ -685,6 +684,7 @@ export const useGridRowSelection = ( } } + const visibleRows = getVisibleRows(apiRef); const rowsBetweenStartAndEnd = visibleRows.rows .slice(start, end + 1) .map((row) => row.id); @@ -704,7 +704,7 @@ export const useGridRowSelection = ( selectRows(apiRef.current.getAllRowIds(), true); } }, - [apiRef, handleSingleRowSelection, selectRows, visibleRows.rows, canHaveMultipleSelection], + [apiRef, handleSingleRowSelection, selectRows, canHaveMultipleSelection], ); useGridApiEventHandler( diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts b/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts index 9f535cf93af68..e9628d76b0523 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridParamsApi.ts @@ -1,10 +1,9 @@ import * as React from 'react'; import { RefObject } from '@mui/x-internals/types'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; -import { GridParamsApi } from '../../../models/api/gridParamsApi'; +import { GridParamsApi, GridParamsPrivateApi } from '../../../models/api/gridParamsApi'; import { GridCellParams } from '../../../models/params/gridCellParams'; import { GridRowParams } from '../../../models/params/gridRowParams'; -import { GridStateColDef } from '../../../models/colDef/gridColDef'; import { getGridCellElement, getGridColumnHeaderElement, @@ -27,7 +26,7 @@ export class MissingRowIdError extends Error {} */ export function useGridParamsApi( apiRef: RefObject, - props: Pick, + props: DataGridProcessedProps, ) { const getColumnHeaderParams = React.useCallback( (field) => ({ @@ -55,26 +54,12 @@ export function useGridParamsApi( [apiRef], ); - const getCellParams = React.useCallback( - (id, field) => { - const colDef = ( - props.unstable_listView - ? gridListColumnSelector(apiRef.current.state) - : apiRef.current.getColumn(field) - ) as GridStateColDef; - const row = apiRef.current.getRow(id); - const rowNode = apiRef.current.getRowNode(id); - - if (!row || !rowNode) { - throw new MissingRowIdError(`No row with id #${id} found`); - } - + const getCellParamsForRow = React.useCallback( + (id, field, row, { cellMode, colDef, hasFocus, rowNode, tabIndex }) => { const rawValue = row[field]; const value = colDef?.valueGetter ? colDef.valueGetter(rawValue as never, row, colDef, apiRef) : rawValue; - const cellFocus = gridFocusCellSelector(apiRef); - const cellTabIndex = gridTabIndexCellSelector(apiRef); const params: GridCellParams = { id, @@ -82,13 +67,13 @@ export function useGridParamsApi( row, rowNode, colDef, - cellMode: apiRef.current.getCellMode(id, field), - hasFocus: cellFocus !== null && cellFocus.field === field && cellFocus.id === id, - tabIndex: cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === id ? 0 : -1, + cellMode, + hasFocus, + tabIndex, value, formattedValue: value, isEditable: false, - api: {} as any, + api: null as any, }; if (colDef && colDef.valueFormatter) { params.formattedValue = colDef.valueFormatter(value as never, row, colDef, apiRef); @@ -97,7 +82,34 @@ export function useGridParamsApi( return params; }, - [apiRef, props.unstable_listView], + [apiRef], + ); + + const getCellParams = React.useCallback( + (id, field) => { + const row = apiRef.current.getRow(id); + const rowNode = apiRef.current.getRowNode(id); + + if (!row || !rowNode) { + throw new MissingRowIdError(`No row with id #${id} found`); + } + + const cellFocus = gridFocusCellSelector(apiRef); + const cellTabIndex = gridTabIndexCellSelector(apiRef); + const cellMode = apiRef.current.getCellMode(id, field); + + return apiRef.current.getCellParamsForRow(id, field, row, { + colDef: + props.unstable_listView && props.unstable_listColumn?.field === field + ? gridListColumnSelector(apiRef.current.state)! + : apiRef.current.getColumn(field), + rowNode, + hasFocus: cellFocus !== null && cellFocus.field === field && cellFocus.id === id, + tabIndex: cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === id ? 0 : -1, + cellMode, + }); + }, + [apiRef, props.unstable_listView, props.unstable_listColumn?.field], ); const getCellValue = React.useCallback( @@ -186,5 +198,10 @@ export function useGridParamsApi( getColumnHeaderElement, }; + const paramsPrivateApi: GridParamsPrivateApi = { + getCellParamsForRow, + }; + useGridApiMethod(apiRef, paramsApi, 'public'); + useGridApiMethod(apiRef, paramsPrivateApi, 'private'); } diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts index e211e9e0136f1..b7fc5793dbd16 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowSpanning.ts @@ -15,6 +15,8 @@ import { getUnprocessedRange, isRowContextInitialized, getCellValue } from './gr import { GRID_CHECKBOX_SELECTION_FIELD } from '../../../colDef/gridCheckboxSelectionColDef'; import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; import { runIf } from '../../../utils/utils'; +import { gridPageSizeSelector } from '../pagination'; +import { gridDataRowIdsSelector } from './gridRowsSelector'; export interface GridRowSpanningState { spannedCells: Record>; @@ -159,6 +161,30 @@ const computeRowSpanningState = ( return { spannedCells, hiddenCells, hiddenCellOriginMap, processedRange }; }; +const getInitialRangeToProcess = ( + props: Pick, + apiRef: React.RefObject, +) => { + const rowCount = gridDataRowIdsSelector(apiRef).length; + + if (props.pagination) { + const pageSize = gridPageSizeSelector(apiRef); + let paginationLastRowIndex = DEFAULT_ROWS_TO_PROCESS; + if (pageSize > 0) { + paginationLastRowIndex = pageSize - 1; + } + return { + firstRowIndex: 0, + lastRowIndex: Math.min(paginationLastRowIndex, rowCount), + }; + } + + return { + firstRowIndex: 0, + lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS, rowCount), + }; +}; + /** * @requires columnsStateInitializer (method) - should be initialized before * @requires rowsStateInitializer (method) - should be initialized before @@ -192,10 +218,7 @@ export const rowSpanningStateInitializer: GridStateInitializer = (state, props, rowSpanning: EMPTY_STATE, }; } - const rangeToProcess = { - firstRowIndex: 0, - lastRowIndex: Math.min(DEFAULT_ROWS_TO_PROCESS, Math.max(rowIds.length, 0)), - }; + const rangeToProcess = getInitialRangeToProcess(props, apiRef); const rows = rowIds.map((id) => ({ id, model: dataRowIdToModelLookup[id!], @@ -226,14 +249,8 @@ export const useGridRowSpanning = ( props: Pick, ): void => { const processedRange = useLazyRef(() => { - return Object.keys(apiRef.current.state.rowSpanning.spannedCells).length > 0 - ? { - firstRowIndex: 0, - lastRowIndex: Math.min( - DEFAULT_ROWS_TO_PROCESS, - Math.max(apiRef.current.state.rows.dataRowIds.length, 0), - ), - } + return apiRef.current.state.rowSpanning !== EMPTY_STATE + ? getInitialRangeToProcess(props, apiRef) : EMPTY_RANGE; }); diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts index 95c2d9bd610eb..b7585843d04eb 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -88,7 +88,6 @@ export const useGridRows = ( } const logger = useGridLogger(apiRef, 'useGridRows'); - const currentPage = getVisibleRows(apiRef); const lastUpdateMs = React.useRef(Date.now()); const lastRowCount = React.useRef(props.rowCount); @@ -126,15 +125,6 @@ export const useGridRows = ( [getRowIdProp], ); - const lookup = React.useMemo( - () => - currentPage.rows.reduce>((acc, { id }, index) => { - acc[id] = index; - return acc; - }, {}), - [currentPage.rows], - ); - const throttledRowsChange = React.useCallback( ({ cache, throttle }: { cache: GridRowsInternalCache; throttle: boolean }) => { const run = () => { @@ -268,7 +258,13 @@ export const useGridRows = ( const getRowIndexRelativeToVisibleRows = React.useCallback< GridRowApi['getRowIndexRelativeToVisibleRows'] - >((id) => lookup[id], [lookup]); + >( + (id) => { + const { rowIdToIndexMap } = getVisibleRows(apiRef); + return rowIdToIndexMap.get(id)!; + }, + [apiRef], + ); const setRowChildrenExpansion = React.useCallback( (id, isExpanded) => { diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts index 17e567bc5b97f..a57c294b2fa9c 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts @@ -1,6 +1,7 @@ import * as React from 'react'; import { RefObject } from '@mui/x-internals/types'; import useLazyRef from '@mui/utils/useLazyRef'; +import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils'; import { ResizeObserver } from '../../../utils/ResizeObserver'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; import { GridRowsMetaApi, GridRowsMetaPrivateApi } from '../../../models/api/gridRowsMetaApi'; @@ -145,7 +146,7 @@ export const useGridRowsMeta = ( }, [ apiRef, - currentPage.rows.length, + currentPage.rows, getRowHeightProp, getEstimatedRowHeight, rowHeight, @@ -255,7 +256,7 @@ export const useGridRowsMeta = ( // The effect is used to build the rows meta data - currentPageTotalHeight and positions. // Because of variable row height this is needed for the virtualization - React.useEffect(() => { + useEnhancedEffect(() => { hydrateRowsMeta(); }, [filterModel, paginationState, sortModel, hydrateRowsMeta]); diff --git a/packages/x-data-grid/src/hooks/features/virtualization/gridFocusedVirtualCellSelector.ts b/packages/x-data-grid/src/hooks/features/virtualization/gridFocusedVirtualCellSelector.ts index c1931ff5dc43d..17d0bc9e58b29 100644 --- a/packages/x-data-grid/src/hooks/features/virtualization/gridFocusedVirtualCellSelector.ts +++ b/packages/x-data-grid/src/hooks/features/virtualization/gridFocusedVirtualCellSelector.ts @@ -4,25 +4,18 @@ import { gridVisibleColumnDefinitionsSelector } from '../columns/gridColumnsSele import { gridRenderContextSelector } from './gridVirtualizationSelectors'; import { gridFocusCellSelector } from '../focus'; import { gridVisibleRowsSelector } from '../pagination'; -import { gridRowsLookupSelector } from '../rows'; -const gridIsFocusedCellOutOfContex = createSelector( +const gridIsFocusedCellOutOfContext = createSelector( gridFocusCellSelector, gridRenderContextSelector, gridVisibleRowsSelector, gridVisibleColumnDefinitionsSelector, - gridRowsLookupSelector, - (focusedCell, renderContext, currentPage, visibleColumns, rows) => { + (focusedCell, renderContext, currentPage, visibleColumns) => { if (!focusedCell) { return false; } - const row = rows[focusedCell.id]; - if (!row) { - return false; - } - - const rowIndex = currentPage.rowToIndexMap.get(row); + const rowIndex = currentPage.rowIdToIndexMap.get(focusedCell.id); const columnIndex = visibleColumns .slice(renderContext.firstColumnIndex, renderContext.lastColumnIndex) .findIndex((column) => column.field === focusedCell.field); @@ -38,22 +31,16 @@ const gridIsFocusedCellOutOfContex = createSelector( ); export const gridFocusedVirtualCellSelector = createSelectorMemoized( - gridIsFocusedCellOutOfContex, + gridIsFocusedCellOutOfContext, gridVisibleColumnDefinitionsSelector, gridVisibleRowsSelector, - gridRowsLookupSelector, gridFocusCellSelector, - (isFocusedCellOutOfRenderContext, visibleColumns, currentPage, rows, focusedCell) => { + (isFocusedCellOutOfRenderContext, visibleColumns, currentPage, focusedCell) => { if (!isFocusedCellOutOfRenderContext) { return null; } - const row = rows[focusedCell!.id]; - if (!row) { - return null; - } - - const rowIndex = currentPage.rowToIndexMap.get(row); + const rowIndex = currentPage.rowIdToIndexMap.get(focusedCell!.id); if (rowIndex === undefined) { return null; diff --git a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx index f82e462587b81..aaf3d2bfe6dc2 100644 --- a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx +++ b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx @@ -47,7 +47,9 @@ import { EMPTY_RENDER_CONTEXT } from './useGridVirtualization'; import { gridRowSpanningHiddenCellsOriginMapSelector } from '../rows/gridRowSpanningSelectors'; import { gridListColumnSelector } from '../listView/gridListViewSelectors'; import { minimalContentHeight } from '../rows/gridRowsUtils'; +import { EMPTY_PINNED_COLUMN_FIELDS, GridPinnedColumns } from '../columns'; import { gridFocusedVirtualCellSelector } from './gridFocusedVirtualCellSelector'; +import { roundToDecimalPlaces } from '../../../utils/roundToDecimalPlaces'; import { isJSDOM } from '../../../utils/isJSDOM'; const MINIMUM_COLUMN_WIDTH = 50; @@ -105,18 +107,17 @@ export const useGridVirtualScroller = () => { const dimensions = useGridSelector(apiRef, gridDimensionsSelector); const outerSize = dimensions.viewportOuterSize; const pinnedRows = useGridSelector(apiRef, gridPinnedRowsSelector); - const pinnedColumnDefinitions = useGridSelector( - apiRef, - gridVisiblePinnedColumnDefinitionsSelector, - ); - const pinnedColumns = listView ? { left: [], right: [] } : pinnedColumnDefinitions; + const pinnedColumnDefinitions = gridVisiblePinnedColumnDefinitionsSelector(apiRef); + const pinnedColumns = listView + ? (EMPTY_PINNED_COLUMN_FIELDS as unknown as GridPinnedColumns) + : pinnedColumnDefinitions; const hasBottomPinnedRows = pinnedRows.bottom.length > 0; const [panels, setPanels] = React.useState(EMPTY_DETAIL_PANELS); const isRtl = useRtl(); const rowsMeta = useGridSelector(apiRef, gridRowsMetaSelector); const selectedRowsLookup = useGridSelector(apiRef, selectedIdsLookupSelector); - const currentPage = useGridVisibleRows(apiRef, rootProps); + const currentPage = useGridVisibleRows(apiRef); const mainRef = apiRef.current.mainElementRef; const scrollerRef = apiRef.current.virtualScrollerRef; const scrollbarVerticalRef = apiRef.current.virtualScrollbarVerticalRef; @@ -137,7 +138,10 @@ export const useGridVirtualScroller = () => { } const initialRect = node.getBoundingClientRect(); - let lastSize = roundDimensions(initialRect); + let lastSize = { + width: roundToDecimalPlaces(initialRect.width, 1), + height: roundToDecimalPlaces(initialRect.height, 1), + }; if ( !previousSize.current || @@ -158,7 +162,10 @@ export const useGridVirtualScroller = () => { return; } - const newSize = roundDimensions(entry.contentRect); + const newSize = { + width: roundToDecimalPlaces(entry.contentRect.width, 1), + height: roundToDecimalPlaces(entry.contentRect.height, 1), + }; if (newSize.width === lastSize.width && newSize.height === lastSize.height) { return; @@ -790,6 +797,13 @@ function computeRenderContext( lastColumnIndex: inputs.visibleColumns.length, }; + if (inputs.listView) { + return { + ...renderContext, + lastColumnIndex: 1, + }; + } + const { top, left } = scrollPosition; const realLeft = Math.abs(left) + inputs.leftPinnedWidth; @@ -1154,12 +1168,3 @@ function bufferForDirection( throw new Error('unreachable'); } } - -// Round to avoid issues with subpixel rendering -// https://github.com/mui/mui-x/issues/15721 -function roundDimensions(dimensions: { width: number; height: number }) { - return { - width: Math.round(dimensions.width * 10) / 10, - height: Math.round(dimensions.height * 10) / 10, - }; -} diff --git a/packages/x-data-grid/src/models/api/gridApiCommon.ts b/packages/x-data-grid/src/models/api/gridApiCommon.ts index f5ba4a40faf79..2b0281798609d 100644 --- a/packages/x-data-grid/src/models/api/gridApiCommon.ts +++ b/packages/x-data-grid/src/models/api/gridApiCommon.ts @@ -7,7 +7,7 @@ import { GridEditingApi, GridEditingPrivateApi } from './gridEditingApi'; import type { GridFilterApi } from './gridFilterApi'; import { GridFocusApi, GridFocusPrivateApi } from './gridFocusApi'; import type { GridLocaleTextApi } from './gridLocaleTextApi'; -import type { GridParamsApi } from './gridParamsApi'; +import type { GridParamsApi, GridParamsPrivateApi } from './gridParamsApi'; import { GridPreferencesPanelApi } from './gridPreferencesPanelApi'; import { GridPrintExportApi } from './gridPrintExportApi'; import { GridRowApi, GridRowProPrivateApi } from './gridRowApi'; @@ -83,7 +83,8 @@ export interface GridPrivateOnlyApiCommon< GridFocusPrivateApi, GridHeaderFilteringPrivateApi, GridVirtualizationPrivateApi, - GridRowProPrivateApi {} + GridRowProPrivateApi, + GridParamsPrivateApi {} export interface GridPrivateApiCommon extends GridApiCommon, diff --git a/packages/x-data-grid/src/models/api/gridParamsApi.ts b/packages/x-data-grid/src/models/api/gridParamsApi.ts index 39a220de36e09..264e1a492af63 100644 --- a/packages/x-data-grid/src/models/api/gridParamsApi.ts +++ b/packages/x-data-grid/src/models/api/gridParamsApi.ts @@ -1,4 +1,6 @@ import { GridColDef } from '../colDef'; +import { GridStateColDef } from '../colDef/gridColDef'; +import { GridCellMode } from '../gridCell'; import { GridValidRowModel, GridRowId, GridTreeNode, GridRowModel } from '../gridRows'; import { GridCellParams } from '../params/gridCellParams'; import { GridColumnHeaderParams } from '../params/gridColumnHeaderParams'; @@ -34,10 +36,10 @@ export interface GridParamsApi { */ getRowFormattedValue: (row: GridRowModel, colDef: GridColDef) => V; /** - * Gets the underlying DOM element for a cell at the given `id` and `field`. + * Gets the [[GridCellParams]] object that is passed as argument in events. * @param {GridRowId} id The id of the row. * @param {string} field The column field. - * @returns {HTMLDivElement | null} The DOM element or `null`. + * @returns {GridCellParams} The cell params. */ getCellElement: (id: GridRowId, field: string) => HTMLDivElement | null; /** @@ -81,3 +83,46 @@ export interface GridParamsApi { */ getColumnHeaderParams: (field: string) => GridColumnHeaderParams; } + +export interface GridParamsPrivateApi { + /** + * @typedef {Object} CellParamsOverrides + * @property {GridCellMode} cellMode - The mode of the cell. + * @property {GridStateColDef} colDef - The column definition. + * @property {boolean} hasFocus - Indicates if the cell is in focus. + * @property {GridTreeNode} rowNode - The node of the row that the current cell belongs to. + * @property {0|-1} tabIndex - The tabIndex value. + */ + + /** + * Used internally to render the cell based on existing row data provided by the GridRow. + * @param {GridRowId} id The id of the row. + * @param {string} field The column field. + * @param {GridValidRowModel} row The row model. + * @param {CellParamsOverrides} cellParams The cell params. + * @returns {GridCellParams} The cell params. + */ + getCellParamsForRow: < + R extends GridValidRowModel = any, + V = unknown, + F = V, + N extends GridTreeNode = GridTreeNode, + >( + id: GridRowId, + field: string, + row: R, + { + cellMode, + colDef, + hasFocus, + rowNode, + tabIndex, + }: { + cellMode: GridCellMode; + colDef: GridStateColDef; + hasFocus: boolean; + rowNode: N; + tabIndex: 0 | -1; + }, + ) => GridCellParams; +} diff --git a/packages/x-data-grid/src/models/api/index.ts b/packages/x-data-grid/src/models/api/index.ts index 38a051fc1f055..8c8793845c9eb 100644 --- a/packages/x-data-grid/src/models/api/index.ts +++ b/packages/x-data-grid/src/models/api/index.ts @@ -1,6 +1,6 @@ import type { GridEditingApi } from './gridEditingApi'; -export * from './gridParamsApi'; +export type { GridParamsApi } from './gridParamsApi'; export type { GridCoreApi } from './gridCoreApi'; export * from './gridColumnApi'; export * from './gridDensityApi'; diff --git a/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx b/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx index 790cdaf5d9732..64e4f5a736e76 100644 --- a/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx +++ b/packages/x-data-grid/src/tests/rowSpanning.DataGrid.test.tsx @@ -117,7 +117,7 @@ describe(' - Row spanning', () => { render(); const rowsWithSpannedCells = Object.keys(apiRef.current.state.rowSpanning.spannedCells); expect(rowsWithSpannedCells.length).to.equal(1); - const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows('4'); + const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(4); expect(rowIndex).to.equal(3); const spanValue = apiRef.current.state.rowSpanning.spannedCells['4']; expect(spanValue).to.deep.equal({ code: 3, totalPrice: 3 }); @@ -134,7 +134,7 @@ describe(' - Row spanning', () => { ); const rowsWithSpannedCells = Object.keys(apiRef.current.state.rowSpanning.spannedCells); expect(rowsWithSpannedCells.length).to.equal(1); - const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows('4'); + const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(4); expect(rowIndex).to.equal(1); const spanValue = apiRef.current.state.rowSpanning.spannedCells['4']; expect(spanValue).to.deep.equal({ code: 3, totalPrice: 3 }); @@ -146,7 +146,7 @@ describe(' - Row spanning', () => { render(); const rowsWithSpannedCells = Object.keys(apiRef.current.state.rowSpanning.spannedCells); expect(rowsWithSpannedCells.length).to.equal(1); - const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows('4'); + const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(4); expect(rowIndex).to.equal(1); const spanValue = apiRef.current.state.rowSpanning.spannedCells['4']; expect(spanValue).to.deep.equal({ code: 3, totalPrice: 3 }); @@ -170,7 +170,7 @@ describe(' - Row spanning', () => { ); const rowsWithSpannedCells = Object.keys(apiRef.current.state.rowSpanning.spannedCells); expect(rowsWithSpannedCells.length).to.equal(1); - const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows('5'); + const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(5); expect(rowIndex).to.equal(0); const spanValue = apiRef.current.state.rowSpanning.spannedCells['5']; expect(spanValue).to.deep.equal({ code: 2, totalPrice: 2 }); @@ -188,7 +188,7 @@ describe(' - Row spanning', () => { ); const rowsWithSpannedCells = Object.keys(apiRef.current.state.rowSpanning.spannedCells); expect(rowsWithSpannedCells.length).to.equal(1); - const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows('5'); + const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(5); expect(rowIndex).to.equal(0); const spanValue = apiRef.current.state.rowSpanning.spannedCells['5']; expect(spanValue).to.deep.equal({ code: 2, totalPrice: 2 }); diff --git a/packages/x-data-grid/src/utils/roundToDecimalPlaces.ts b/packages/x-data-grid/src/utils/roundToDecimalPlaces.ts new file mode 100644 index 0000000000000..fcd6e126d7b7b --- /dev/null +++ b/packages/x-data-grid/src/utils/roundToDecimalPlaces.ts @@ -0,0 +1,3 @@ +export function roundToDecimalPlaces(value: number, decimals: number) { + return Math.round(value * 10 ** decimals) / 10 ** decimals; +} diff --git a/packages/x-internals/src/isObjectEmpty/index.ts b/packages/x-internals/src/isObjectEmpty/index.ts new file mode 100644 index 0000000000000..8463da317abca --- /dev/null +++ b/packages/x-internals/src/isObjectEmpty/index.ts @@ -0,0 +1 @@ +export { isObjectEmpty } from './isObjectEmpty'; diff --git a/packages/x-internals/src/isObjectEmpty/isObjectEmpty.ts b/packages/x-internals/src/isObjectEmpty/isObjectEmpty.ts new file mode 100644 index 0000000000000..5080fd6fc7697 --- /dev/null +++ b/packages/x-internals/src/isObjectEmpty/isObjectEmpty.ts @@ -0,0 +1,7 @@ +export function isObjectEmpty(object: any): boolean { + // eslint-disable-next-line + for (const _ in object) { + return false; + } + return true; +} diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 694eea68b32d5..5a603b8186802 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -288,6 +288,7 @@ { "name": "GridEditBooleanCell", "kind": "Function" }, { "name": "GridEditBooleanCellProps", "kind": "Interface" }, { "name": "GridEditCellProps", "kind": "Interface" }, + { "name": "gridEditCellStateSelector", "kind": "Variable" }, { "name": "GridEditCellValueParams", "kind": "Interface" }, { "name": "GridEditDateCell", "kind": "Function" }, { "name": "GridEditDateCellProps", "kind": "Interface" }, @@ -525,6 +526,7 @@ { "name": "GridRowId", "kind": "TypeAlias" }, { "name": "GridRowIdGetter", "kind": "TypeAlias" }, { "name": "GridRowIdToModelLookup", "kind": "TypeAlias" }, + { "name": "gridRowIsEditingSelector", "kind": "Variable" }, { "name": "gridRowMaximumTreeDepthSelector", "kind": "Variable" }, { "name": "GridRowMode", "kind": "TypeAlias" }, { "name": "GridRowModel", "kind": "TypeAlias" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 804bc87dbee3c..b28b78a60bff6 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -260,6 +260,7 @@ { "name": "GridEditBooleanCell", "kind": "Function" }, { "name": "GridEditBooleanCellProps", "kind": "Interface" }, { "name": "GridEditCellProps", "kind": "Interface" }, + { "name": "gridEditCellStateSelector", "kind": "Variable" }, { "name": "GridEditCellValueParams", "kind": "Interface" }, { "name": "GridEditDateCell", "kind": "Function" }, { "name": "GridEditDateCellProps", "kind": "Interface" }, @@ -476,6 +477,7 @@ { "name": "GridRowId", "kind": "TypeAlias" }, { "name": "GridRowIdGetter", "kind": "TypeAlias" }, { "name": "GridRowIdToModelLookup", "kind": "TypeAlias" }, + { "name": "gridRowIsEditingSelector", "kind": "Variable" }, { "name": "gridRowMaximumTreeDepthSelector", "kind": "Variable" }, { "name": "GridRowMode", "kind": "TypeAlias" }, { "name": "GridRowModel", "kind": "TypeAlias" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 3e8d8d9d7fde3..b12b38362f888 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -232,6 +232,7 @@ { "name": "GridEditBooleanCell", "kind": "Function" }, { "name": "GridEditBooleanCellProps", "kind": "Interface" }, { "name": "GridEditCellProps", "kind": "Interface" }, + { "name": "gridEditCellStateSelector", "kind": "Variable" }, { "name": "GridEditCellValueParams", "kind": "Interface" }, { "name": "GridEditDateCell", "kind": "Function" }, { "name": "GridEditDateCellProps", "kind": "Interface" }, @@ -432,6 +433,7 @@ { "name": "GridRowId", "kind": "TypeAlias" }, { "name": "GridRowIdGetter", "kind": "TypeAlias" }, { "name": "GridRowIdToModelLookup", "kind": "TypeAlias" }, + { "name": "gridRowIsEditingSelector", "kind": "Variable" }, { "name": "gridRowMaximumTreeDepthSelector", "kind": "Variable" }, { "name": "GridRowMode", "kind": "TypeAlias" }, { "name": "GridRowModel", "kind": "TypeAlias" },