diff --git a/docs/data/data-grid/events/events.json b/docs/data/data-grid/events/events.json index d30f1bf4a996..bab28ffa6f12 100644 --- a/docs/data/data-grid/events/events.json +++ b/docs/data/data-grid/events/events.json @@ -61,6 +61,13 @@ "event": "MuiEvent>", "componentProp": "onCellKeyDown" }, + { + "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], + "name": "cellKeyUp", + "description": "Fired when a keyup event happens in a cell.", + "params": "GridCellParams", + "event": "MuiEvent>" + }, { "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], "name": "cellModesModelChange", @@ -75,6 +82,13 @@ "params": "GridCellParams", "event": "MuiEvent>" }, + { + "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], + "name": "cellMouseOver", + "description": "Fired when a mouseover event happens in a cell.", + "params": "GridCellParams", + "event": "MuiEvent>" + }, { "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], "name": "cellMouseUp", @@ -82,6 +96,13 @@ "params": "GridCellParams", "event": "MuiEvent>" }, + { + "projects": ["x-data-grid-premium"], + "name": "cellSelectionChange", + "description": "Fired when the selection state of one or multiple cells change.", + "params": "GridCellSelectionModel", + "event": "MuiEvent<{}>" + }, { "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], "name": "columnGroupHeaderKeyDown", diff --git a/docs/data/data-grid/getting-started/getting-started.md b/docs/data/data-grid/getting-started/getting-started.md index 629c92a8b24a..c54ae104857c 100644 --- a/docs/data/data-grid/getting-started/getting-started.md +++ b/docs/data/data-grid/getting-started/getting-started.md @@ -172,7 +172,7 @@ The enterprise components come in two plans: Pro and Premium. | [Single row selection](/x/react-data-grid/selection/#single-row-selection) | ✅ | ✅ | ✅ | | [Checkbox selection](/x/react-data-grid/selection/#checkbox-selection) | ✅ | ✅ | ✅ | | [Multiple row selection](/x/react-data-grid/selection/#multiple-row-selection) | ❌ | ✅ | ✅ | -| [Cell range selection](/x/react-data-grid/selection/#range-selection) | ❌ | ❌ | 🚧 | +| [Cell range selection](/x/react-data-grid/selection/#cell-selection) | ❌ | ❌ | ✅ | | **Filtering** | | | | | [Quick filter](/x/react-data-grid/filtering/#quick-filter) | ✅ | ✅ | ✅ | | [Column filters](/x/react-data-grid/filtering/#single-and-multi-filtering) | ✅ | ✅ | ✅ | diff --git a/docs/data/data-grid/overview/overview.md b/docs/data/data-grid/overview/overview.md index 26ff8f83e8db..e54ae8e638ba 100644 --- a/docs/data/data-grid/overview/overview.md +++ b/docs/data/data-grid/overview/overview.md @@ -97,7 +97,6 @@ Please see [the Licensing page](/x/introduction/licensing/) for details. While development of the data grid component is moving fast, there are still many additional features that we plan to implement. Some of them: - Headless (hooks only) -- [Range selection](/x/react-data-grid/selection/#range-selection) - [Pivoting](/x/react-data-grid/pivoting/) You can find more details on, the [feature comparison](/x/react-data-grid/getting-started/#feature-comparison), our living quarterly [roadmap](https://github.com/mui/mui-x/projects/1) as well as on the open [GitHub issues](https://github.com/mui/mui-x/issues?q=is%3Aopen+label%3A%22component%3A+DataGrid%22+label%3Aenhancement). diff --git a/docs/data/data-grid/selection/CellSelectionFormulaField.js b/docs/data/data-grid/selection/CellSelectionFormulaField.js new file mode 100644 index 000000000000..fbdb16830c8e --- /dev/null +++ b/docs/data/data-grid/selection/CellSelectionFormulaField.js @@ -0,0 +1,87 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Stack from '@mui/material/Stack'; +import { DataGridPremium, useGridApiRef } from '@mui/x-data-grid-premium'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function CellSelectionFormulaField() { + const apiRef = useGridApiRef(); + const [value, setValue] = React.useState(''); + const [cellSelectionModel, setCellSelectionModel] = React.useState({}); + const [numberOfSelectedCells, setNumberOfSelectedCells] = React.useState(0); + + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 10, + maxColumns: 6, + }); + + const handleCellSelectionModelChange = React.useCallback((newModel) => { + setCellSelectionModel(newModel); + }, []); + + const handleValueChange = React.useCallback((event) => { + setValue(event.target.value); + }, []); + + const updateSelectedCells = React.useCallback(() => { + const updates = []; + + Object.entries(cellSelectionModel).forEach(([id, fields]) => { + const updatedRow = { ...apiRef.current.getRow(id) }; + + Object.entries(fields).forEach(([field, isSelected]) => { + if (isSelected) { + updatedRow[field] = value; + } + }); + + updates.push(updatedRow); + }); + + apiRef.current.updateRows(updates); + }, [apiRef, cellSelectionModel, value]); + + React.useEffect(() => { + const selectedCells = apiRef.current.unstable_getSelectedCellsAsArray(); + setNumberOfSelectedCells(selectedCells.length); + + if (selectedCells.length > 1) { + setValue('(multiple values)'); + } else if (selectedCells.length === 1) { + setValue( + apiRef.current.getCellValue(selectedCells[0].id, selectedCells[0].field), + ); + } else { + setValue(''); + } + }, [apiRef, cellSelectionModel]); + + return ( +
+ + + + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/selection/CellSelectionFormulaField.tsx b/docs/data/data-grid/selection/CellSelectionFormulaField.tsx new file mode 100644 index 000000000000..93a183c49e55 --- /dev/null +++ b/docs/data/data-grid/selection/CellSelectionFormulaField.tsx @@ -0,0 +1,99 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import TextField from '@mui/material/TextField'; +import Stack from '@mui/material/Stack'; +import { + DataGridPremium, + GridCellSelectionModel, + GridRowModelUpdate, + useGridApiRef, +} from '@mui/x-data-grid-premium'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function CellSelectionFormulaField() { + const apiRef = useGridApiRef(); + const [value, setValue] = React.useState(''); + const [cellSelectionModel, setCellSelectionModel] = + React.useState({}); + const [numberOfSelectedCells, setNumberOfSelectedCells] = React.useState(0); + + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 10, + maxColumns: 6, + }); + + const handleCellSelectionModelChange = React.useCallback( + (newModel: GridCellSelectionModel) => { + setCellSelectionModel(newModel); + }, + [], + ); + + const handleValueChange = React.useCallback( + (event: React.ChangeEvent) => { + setValue(event.target.value); + }, + [], + ); + + const updateSelectedCells = React.useCallback(() => { + const updates: GridRowModelUpdate[] = []; + + Object.entries(cellSelectionModel).forEach(([id, fields]) => { + const updatedRow = { ...apiRef.current.getRow(id) }; + + Object.entries(fields).forEach(([field, isSelected]) => { + if (isSelected) { + updatedRow[field] = value; + } + }); + + updates.push(updatedRow); + }); + + apiRef.current.updateRows(updates); + }, [apiRef, cellSelectionModel, value]); + + React.useEffect(() => { + const selectedCells = apiRef.current.unstable_getSelectedCellsAsArray(); + setNumberOfSelectedCells(selectedCells.length); + + if (selectedCells.length > 1) { + setValue('(multiple values)'); + } else if (selectedCells.length === 1) { + setValue( + apiRef.current.getCellValue(selectedCells[0].id, selectedCells[0].field), + ); + } else { + setValue(''); + } + }, [apiRef, cellSelectionModel]); + + return ( +
+ + + + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/selection/CellSelectionGrid.js b/docs/data/data-grid/selection/CellSelectionGrid.js new file mode 100644 index 000000000000..b4f4dd26ae5d --- /dev/null +++ b/docs/data/data-grid/selection/CellSelectionGrid.js @@ -0,0 +1,30 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import { DataGridPremium } from '@mui/x-data-grid-premium'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function CellSelectionGrid() { + const [rowSelection, setRowSelection] = React.useState(false); + + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 10, + maxColumns: 6, + }); + + return ( +
+ +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/selection/CellSelectionGrid.tsx b/docs/data/data-grid/selection/CellSelectionGrid.tsx new file mode 100644 index 000000000000..b4f4dd26ae5d --- /dev/null +++ b/docs/data/data-grid/selection/CellSelectionGrid.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import { DataGridPremium } from '@mui/x-data-grid-premium'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +export default function CellSelectionGrid() { + const [rowSelection, setRowSelection] = React.useState(false); + + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 10, + maxColumns: 6, + }); + + return ( +
+ +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/selection/CellSelectionGrid.tsx.preview b/docs/data/data-grid/selection/CellSelectionGrid.tsx.preview new file mode 100644 index 000000000000..6d909e7baf77 --- /dev/null +++ b/docs/data/data-grid/selection/CellSelectionGrid.tsx.preview @@ -0,0 +1,11 @@ + +
+ +
\ No newline at end of file diff --git a/docs/data/data-grid/selection/CellSelectionRangeStyling.js b/docs/data/data-grid/selection/CellSelectionRangeStyling.js new file mode 100644 index 000000000000..b62307b6342c --- /dev/null +++ b/docs/data/data-grid/selection/CellSelectionRangeStyling.js @@ -0,0 +1,50 @@ +import * as React from 'react'; +import { styled, lighten, darken, alpha } from '@mui/material/styles'; +import { DataGridPremium, gridClasses } from '@mui/x-data-grid-premium'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const StyledDataGridPremium = styled(DataGridPremium)(({ theme }) => { + const borderColor = + theme.palette.mode === 'light' + ? lighten(alpha(theme.palette.divider, 1), 0.88) + : darken(alpha(theme.palette.divider, 1), 0.68); + + const selectedCellBorder = alpha(theme.palette.primary.main, 0.5); + + return { + [`& .${gridClasses.cell}`]: { + border: `1px solid transparent`, + borderRight: `1px solid ${borderColor}`, + borderBottom: `1px solid ${borderColor}`, + }, + [`& .${gridClasses.cell}.Mui-selected`]: { + borderColor: alpha(theme.palette.primary.main, 0.1), + }, + [`& .${gridClasses.cell}.Mui-selected.${gridClasses['cell--rangeTop']}`]: { + borderTopColor: selectedCellBorder, + }, + [`& .${gridClasses.cell}.Mui-selected.${gridClasses['cell--rangeBottom']}`]: { + borderBottomColor: selectedCellBorder, + }, + [`& .${gridClasses.cell}.Mui-selected.${gridClasses['cell--rangeLeft']}`]: { + borderLeftColor: selectedCellBorder, + }, + [`& .${gridClasses.cell}.Mui-selected.${gridClasses['cell--rangeRight']}`]: { + borderRightColor: selectedCellBorder, + }, + }; +}); + +export default function CellSelectionRangeStyling() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 10, + maxColumns: 6, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/selection/CellSelectionRangeStyling.tsx b/docs/data/data-grid/selection/CellSelectionRangeStyling.tsx new file mode 100644 index 000000000000..b62307b6342c --- /dev/null +++ b/docs/data/data-grid/selection/CellSelectionRangeStyling.tsx @@ -0,0 +1,50 @@ +import * as React from 'react'; +import { styled, lighten, darken, alpha } from '@mui/material/styles'; +import { DataGridPremium, gridClasses } from '@mui/x-data-grid-premium'; +import { useDemoData } from '@mui/x-data-grid-generator'; + +const StyledDataGridPremium = styled(DataGridPremium)(({ theme }) => { + const borderColor = + theme.palette.mode === 'light' + ? lighten(alpha(theme.palette.divider, 1), 0.88) + : darken(alpha(theme.palette.divider, 1), 0.68); + + const selectedCellBorder = alpha(theme.palette.primary.main, 0.5); + + return { + [`& .${gridClasses.cell}`]: { + border: `1px solid transparent`, + borderRight: `1px solid ${borderColor}`, + borderBottom: `1px solid ${borderColor}`, + }, + [`& .${gridClasses.cell}.Mui-selected`]: { + borderColor: alpha(theme.palette.primary.main, 0.1), + }, + [`& .${gridClasses.cell}.Mui-selected.${gridClasses['cell--rangeTop']}`]: { + borderTopColor: selectedCellBorder, + }, + [`& .${gridClasses.cell}.Mui-selected.${gridClasses['cell--rangeBottom']}`]: { + borderBottomColor: selectedCellBorder, + }, + [`& .${gridClasses.cell}.Mui-selected.${gridClasses['cell--rangeLeft']}`]: { + borderLeftColor: selectedCellBorder, + }, + [`& .${gridClasses.cell}.Mui-selected.${gridClasses['cell--rangeRight']}`]: { + borderRightColor: selectedCellBorder, + }, + }; +}); + +export default function CellSelectionRangeStyling() { + const { data } = useDemoData({ + dataSet: 'Commodity', + rowLength: 10, + maxColumns: 6, + }); + + return ( +
+ +
+ ); +} diff --git a/docs/data/data-grid/selection/CellSelectionRangeStyling.tsx.preview b/docs/data/data-grid/selection/CellSelectionRangeStyling.tsx.preview new file mode 100644 index 000000000000..ea88d081d68f --- /dev/null +++ b/docs/data/data-grid/selection/CellSelectionRangeStyling.tsx.preview @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/data/data-grid/selection/selection.md b/docs/data/data-grid/selection/selection.md index 0af7d2521850..668a723f2ab0 100644 --- a/docs/data/data-grid/selection/selection.md +++ b/docs/data/data-grid/selection/selection.md @@ -23,6 +23,29 @@ On the `DataGridPro` component, you can select multiple rows in two ways: {{"demo": "MultipleRowSelectionGrid.js", "disableAd": true, "bg": "inline"}} +### Disable row selection on click + +You might have interactive content in the cells and need to disable the selection of the row on click. Use the `disableRowSelectionOnClick` prop in this case. + +{{"demo": "DisableClickSelectionGrid.js", "bg": "inline"}} + +### Disable selection on certain rows + +Use the `isRowSelectable` prop to indicate if a row can be selected. +It's called with a `GridRowParams` object and should return a boolean value. +If not specified, all rows are selectable. + +In the demo below only rows with quantity above 50000 can be selected: + +{{"demo": "DisableRowSelection.js", "bg": "inline"}} + +### Controlled row selection + +Use the `rowSelectionModel` prop to control the selection. +Each time this prop changes, the `onRowSelectionModelChange` callback is called with the new selection value. + +{{"demo": "ControlledSelectionGrid.js", "bg": "inline"}} + ## Checkbox selection To activate checkbox selection set `checkboxSelection={true}`. @@ -44,29 +67,6 @@ Always set the `checkboxSelection` prop to `true` even when providing a custom c Otherwise, the grid might remove your column. ::: -## Disable selection on click - -You might have interactive content in the cells and need to disable the selection of the row on click. Use the `disableRowSelectionOnClick` prop in this case. - -{{"demo": "DisableClickSelectionGrid.js", "bg": "inline"}} - -## Disable selection on certain rows - -Use the `isRowSelectable` prop to indicate if a row can be selected. -It's called with a `GridRowParams` object and should return a boolean value. -If not specified, all rows are selectable. - -In the demo below only rows with quantity above 50000 can be selected: - -{{"demo": "DisableRowSelection.js", "bg": "inline"}} - -## Controlled selection - -Use the `rowSelectionModel` prop to control the selection. -Each time this prop changes, the `onRowSelectionModelChange` callback is called with the new selection value. - -{{"demo": "ControlledSelectionGrid.js", "bg": "inline"}} - ### Usage with server-side pagination Using the controlled selection with `paginationMode="server"` may result in selected rows being lost when the page is changed. @@ -84,25 +84,85 @@ The following demo shows the prop in action: {{"demo": "ControlledSelectionServerPaginationGrid.js", "bg": "inline"}} -## apiRef [](/x/introduction/licensing/#pro-plan) +## Cell selection [](/x/introduction/licensing/#premium-plan) -The grid exposes a set of methods that enables all of these features using the imperative apiRef. +The Data Grid has, by default, the ability to select rows. +On the `DataGridPremium`, you can also enable the ability to select cells with the `unstable_cellSelection` prop. + +```tsx + +``` :::warning -Only use this API as the last option. Give preference to the props to control the grid. +This feature is not stable yet, meaning that its APIs may suffer breaking changes. +While in development, all props and methods related to cell selection must be prefixed with `unstable_`. ::: -{{"demo": "SelectionApiNoSnap.js", "bg": "inline", "hideToolbar": true}} +To select a single cell, click on it or, with the cell focused, press Shift+Space. +Multiple cells can be selected by holding Ctrl while clicking the cells. +A cell can be deselected by also clicking it while Ctrl is pressed. -## 🚧 Range selection [](/x/introduction/licensing/#premium-plan) +To select all cells within a range, you can use one of the interactions available: -:::warning -This feature isn't implemented yet. It's coming. +- **Mouse drag:** Click on a cell, then drag the mouse over another cell and release it +- **Shift + click**: Click on a cell and, while holding Shift, click on another cell—if a third cell is clicked the selection will restart from the last clicked cell +- **Shift + arrow keys**: Using the arrow keys, focus on a cell, then hold Shift and navigate to another cell—if Shift is released and pressed again, the selection will restart from the last focused cell + +The following demo allows to explore the cell selection feature. +It has row selection disabled, but it's possible to set both selections to work in parallel. + +{{"demo": "CellSelectionGrid.js", "bg": "inline"}} + +### Controlled cell selection + +You can control which cells are selected with the `unstable_cellSelectionModel` prop. +This props accepts an object whose keys are the row IDs that contain selected cells. +The value of each key is another object, which in turn has column fields as keys, each with a boolean value to represent their selection state. You can set `true` to select the cell or `false` to deselect a cell. +Removing the field from the object also deselects the cell. + +```tsx +// Selects the cell with field=name from row with id=1 + + +// Unselects the cell with field=name from row with id=1 + +``` + +When a new selection is made, the callback passed to the `unstable_onCellSelectionModelChange` prop is called with the updated model. +Use this value to update the current model. + +The following demo shows how these props can be combined to create an Excel-like formula field. + +{{"demo": "CellSelectionFormulaField.js", "bg": "inline"}} + +### Customize range styling + +When multiple selected cells make a range, specific class names are applied to the cells at the edges of this range. +The class names applied are the following: + +- `MuiDataGrid-cell--rangeTop`: applied to all cells of the first row of the range +- `MuiDataGrid-cell--rangeBottom`: applied to all cells of the last row of the range +- `MuiDataGrid-cell--rangeLeft`: applied to all cells of the first column of the range +- `MuiDataGrid-cell--rangeRight`: applied to all cells of the last column of the range + +You can use these classes to create CSS selectors targeting specific corners of each range. +The demo below shows how to add a border around the range. + +{{"demo": "CellSelectionRangeStyling.js", "bg": "inline"}} -👍 Upvote [issue #208](https://github.com/mui/mui-x/issues/208) if you want to see it land faster. +:::info +If a single cell is selected, all classes above are applied to the same element. ::: -With this feature, you will be able to select ranges of cells across the Grid. +## apiRef [](/x/introduction/licensing/#pro-plan) + +The grid exposes a set of methods that enables all of these features using the imperative apiRef. + +:::warning +Only use this API as the last option. Give preference to the props to control the grid. +::: + +{{"demo": "SelectionApiNoSnap.js", "bg": "inline", "hideToolbar": true}} ## API diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index efc0f001533d..846e69196e48 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -247,7 +247,10 @@ } }, "throttleRowsMs": { "type": { "name": "number" }, "default": "0" }, - "treeData": { "type": { "name": "bool" } } + "treeData": { "type": { "name": "bool" } }, + "unstable_cellSelection": { "type": { "name": "bool" } }, + "unstable_cellSelectionModel": { "type": { "name": "object" } }, + "unstable_onCellSelectionModelChange": { "type": { "name": "func" } } }, "slots": { "BaseButton": { "default": "Button", "type": { "name": "elementType" } }, @@ -341,6 +344,10 @@ "cell--textLeft", "cell--textRight", "cell--withRenderer", + "cell--rangeTop", + "cell--rangeBottom", + "cell--rangeLeft", + "cell--rangeRight", "cell", "cellContent", "cellCheckbox", @@ -423,6 +430,7 @@ "root--densityStandard", "root--densityComfortable", "root--densityCompact", + "root--disableUserSelection", "row--editable", "row--editing", "row--dragging", diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index e50562ab848b..ea9b14600801 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -319,6 +319,10 @@ "cell--textLeft", "cell--textRight", "cell--withRenderer", + "cell--rangeTop", + "cell--rangeBottom", + "cell--rangeLeft", + "cell--rangeRight", "cell", "cellContent", "cellCheckbox", @@ -401,6 +405,7 @@ "root--densityStandard", "root--densityComfortable", "root--densityCompact", + "root--disableUserSelection", "row--editable", "row--editing", "row--dragging", diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index e81b4810f039..261365de292b 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -263,6 +263,10 @@ "cell--textLeft", "cell--textRight", "cell--withRenderer", + "cell--rangeTop", + "cell--rangeBottom", + "cell--rangeLeft", + "cell--rangeRight", "cell", "cellContent", "cellCheckbox", @@ -345,6 +349,7 @@ "root--densityStandard", "root--densityComfortable", "root--densityCompact", + "root--disableUserSelection", "row--editable", "row--editing", "row--dragging", diff --git a/docs/pages/x/api/data-grid/grid-api.md b/docs/pages/x/api/data-grid/grid-api.md index 0aaa3625bfbc..4d720e98e6fb 100644 --- a/docs/pages/x/api/data-grid/grid-api.md +++ b/docs/pages/x/api/data-grid/grid-api.md @@ -12,110 +12,115 @@ import { GridApi } from '@mui/x-data-grid-pro'; ## Properties -| Name | Type | Description | -| :------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| addRowGroupingCriteria [](/x/introduction/licensing/#premium-plan) | (groupingCriteriaField: string, groupingIndex?: number) => void | Adds the field to the row grouping model. | -| applySorting | () => void | Applies the current sort model to the rows. | -| deleteFilterItem | (item: GridFilterItem) => void | Deletes a [GridFilterItem](/x/api/data-grid/grid-filter-item/). | -| exportDataAsCsv | (options?: GridCsvExportOptions) => void | Downloads and exports a CSV of the grid's data. | -| exportDataAsExcel [](/x/introduction/licensing/#premium-plan) | (options?: GridExcelExportOptions) => Promise<void> | Downloads and exports an Excel file of the grid's data. | -| exportDataAsPrint | (options?: GridPrintExportOptions) => void | Print the grid's data. | -| exportState | (params?: GridExportStateParams) => InitialState | Generates a serializable object containing the exportable parts of the DataGrid state.
These values can then be passed to the `initialState` prop or injected using the `restoreState` method. | -| forceUpdate | () => void | Forces the grid to rerender. It's often used after a state update. | -| getAllColumns | () => GridStateColDef[] | Returns an array of [GridColDef](/x/api/data-grid/grid-col-def/) containing all the column definitions. | -| getAllRowIds | () => GridRowId[] | Gets the list of row ids. | -| getCellElement | (id: GridRowId, field: string) => HTMLDivElement \| null | Gets the underlying DOM element for a cell at the given `id` and `field`. | -| getCellMode | (id: GridRowId, field: string) => GridCellMode | Gets the mode of a cell. | -| getCellParams | <V = any, R extends GridValidRowModel = any, F = V, N extends GridTreeNode = GridTreeNode>(id: GridRowId, field: string) => GridCellParams<V, R, F, N> | Gets the [GridCellParams](/x/api/data-grid/grid-cell-params/) object that is passed as argument in events. | -| getCellValue | <V extends any = any>(id: GridRowId, field: string) => V | Gets the value of a cell at the given `id` and `field`. | -| getColumn | (field: string) => GridStateColDef | Returns the [GridColDef](/x/api/data-grid/grid-col-def/) for the given `field`. | -| getColumnHeaderElement | (field: string) => HTMLDivElement \| null | Gets the underlying DOM element for the column header with the given `field`. | -| getColumnHeaderParams | (field: string) => GridColumnHeaderParams | Gets the GridColumnHeaderParams object that is passed as argument in events. | -| getColumnIndex | (field: string, useVisibleColumns?: boolean) => number | Returns the index position of a column. By default, only the visible columns are considered.
Pass `false` to `useVisibleColumns` to consider all columns. | -| getColumnPosition | (field: string) => number | Returns the left-position of a column relative to the inner border of the grid. | -| getDataAsCsv | (options?: GridCsvExportOptions) => string | Returns the grid data as a CSV string.
This method is used internally by `exportDataAsCsv`. | -| getDataAsExcel [](/x/introduction/licensing/#premium-plan) | (options?: GridExcelExportOptions) => Promise<Excel.Workbook> \| null | Returns the grid data as an exceljs workbook.
This method is used internally by `exportDataAsExcel`. | -| getExpandedDetailPanels [](/x/introduction/licensing/#pro-plan) | () => GridRowId[] | Returns the rows whose detail panel is open. | -| getLocaleText | <T extends GridTranslationKeys>(key: T) => GridLocaleText[T] | Returns the translation for the `key`. | -| getPinnedColumns [](/x/introduction/licensing/#pro-plan) | () => GridPinnedColumns | Returns which columns are pinned. | -| getRootDimensions | () => GridDimensions \| null | Returns the dimensions of the grid | -| getRow | <R extends GridValidRowModel = any>(id: GridRowId) => R \| null | Gets the row data with a given id. | -| getRowElement | (id: GridRowId) => HTMLDivElement \| null | Gets the underlying DOM element for a row at the given `id`. | -| getRowGroupChildren | (params: GridRowGroupChildrenGetterParams) => GridRowId[] | Gets the rows of a grouping criteria.
Only contains the rows provided to the grid, not the rows automatically generated by it. | -| getRowIdFromRowIndex | (index: number) => GridRowId | Gets the `GridRowId` of a row at a specific index.
The index is based on the sorted but unfiltered row list. | -| getRowIndexRelativeToVisibleRows | (id: GridRowId) => number | Gets the index of a row relative to the rows that are reachable by scroll. | -| getRowMode | (id: GridRowId) => GridRowMode | Gets the mode of a row. | -| getRowModels | () => Map<GridRowId, GridRowModel> | Gets the full set of rows as Map<GridRowId, GridRowModel>. | -| getRowNode | <N extends GridTreeNode>(id: GridRowId) => N \| null | Gets the row node from the internal tree structure. | -| getRowParams | (id: GridRowId) => GridRowParams | Gets the [GridRowParams](/x/api/data-grid/grid-row-params/) object that is passed as argument in events. | -| getRowsCount | () => number | Gets the total number of rows in the grid. | -| getScrollPosition | () => GridScrollParams | Returns the current scroll position. | -| getSelectedRows | () => Map<GridRowId, GridRowModel> | Returns an array of the selected rows. | -| getSortedRowIds | () => GridRowId[] | Returns all row ids sorted according to the active sort model. | -| getSortedRows | () => GridRowModel[] | Returns all rows sorted according to the active sort model. | -| getSortModel | () => GridSortModel | Returns the sort model currently applied to the grid. | -| getVisibleColumns | () => GridStateColDef[] | Returns the currently visible columns. | -| getVisibleRowModels | () => Map<GridRowId, GridRowModel> | Returns a sorted `Map` containing only the visible rows. | -| hideColumnMenu | () => void | Hides the column menu that is open. | -| hideFilterPanel | () => void | Hides the filter panel. | -| hidePreferences | () => void | Hides the preferences panel. | -| isCellEditable | (params: GridCellParams) => boolean | Controls if a cell is editable. | -| isColumnPinned [](/x/introduction/licensing/#pro-plan) | (field: string) => GridPinnedPosition \| false | Returns which side a column is pinned to. | -| isRowSelectable | (id: GridRowId) => boolean | Determines if a row can be selected or not. | -| isRowSelected | (id: GridRowId) => boolean | Determines if a row is selected or not. | -| pinColumn [](/x/introduction/licensing/#pro-plan) | (field: string, side: GridPinnedPosition) => void | Pins a column to the left or right side of the grid. | -| publishEvent | GridEventPublisher | Emits an event. | -| removeRowGroupingCriteria [](/x/introduction/licensing/#premium-plan) | (groupingCriteriaField: string) => void | Remove the field from the row grouping model. | -| resetRowHeights | () => void | Forces the recalculation of the heights of all rows. | -| resize | () => void | Triggers a resize of the component and recalculation of width and height. | -| restoreState | (stateToRestore: InitialState) => void | Inject the given values into the state of the DataGrid. | -| scroll | (params: Partial<GridScrollParams>) => void | Triggers the viewport to scroll to the given positions (in pixels). | -| scrollToIndexes | (params: Partial<GridCellIndexCoordinates>) => boolean | Triggers the viewport to scroll to the cell at indexes given by `params`.
Returns `true` if the grid had to scroll to reach the target. | -| selectRow | (id: GridRowId, isSelected?: boolean, resetSelection?: boolean) => void | Change the selection state of a row. | -| selectRowRange [](/x/introduction/licensing/#pro-plan) | (range: { startId: GridRowId; endId: GridRowId }, isSelected?: boolean, resetSelection?: boolean) => void | Change the selection state of all the selectable rows in a range. | -| selectRows [](/x/introduction/licensing/#pro-plan) | (ids: GridRowId[], isSelected?: boolean, resetSelection?: boolean) => void | Change the selection state of multiple rows. | -| setAggregationModel [](/x/introduction/licensing/#premium-plan) | (model: GridAggregationModel) => void | Sets the aggregation model to the one given by `model`. | -| setCellFocus | (id: GridRowId, field: string) => void | Sets the focus to the cell at the given `id` and `field`. | -| setColumnHeaderFocus | (field: string, event?: MuiBaseEvent) => void | Sets the focus to the column header at the given `field`. | -| setColumnIndex | (field: string, targetIndexPosition: number) => void | Moves a column from its original position to the position given by `targetIndexPosition`. | -| setColumnVisibility | (field: string, isVisible: boolean) => void | Changes the visibility of the column referred by `field`. | -| setColumnVisibilityModel | (model: GridColumnVisibilityModel) => void | Sets the column visibility model to the one given by `model`. | -| setColumnWidth | (field: string, width: number) => void | Updates the width of a column. | -| setDensity | (density: GridDensity, headerHeight?: number, rowHeight?: number, maxDepth?: number) => void | Sets the density of the grid. | -| setEditCellValue | (params: GridEditCellValueParams, event?: MuiBaseEvent) => Promise<boolean> \| void | Sets the value of the edit cell.
Commonly used inside the edit cell component. | -| setExpandedDetailPanels [](/x/introduction/licensing/#pro-plan) | (ids: GridRowId[]) => void | Changes which rows to expand the detail panel. | -| setFilterLinkOperator | (operator: GridLinkOperator) => void | Changes the GridLinkOperator used to connect the filters. | -| setFilterModel | (model: GridFilterModel, reason?: GridControlledStateReasonLookup['filter']) => void | Sets the filter model to the one given by `model`. | -| setPage | (page: number) => void | Sets the displayed page to the value given by `page`. | -| setPageSize | (pageSize: number) => void | Sets the number of displayed rows to the value given by `pageSize`. | -| setPinnedColumns [](/x/introduction/licensing/#pro-plan) | (pinnedColumns: GridPinnedColumns) => void | Changes the pinned columns. | -| setQuickFilterValues | (values: any[]) => void | Set the quick filter values ot the one given by `values` | -| setRowChildrenExpansion | (id: GridRowId, isExpanded: boolean) => void | Expand or collapse a row children. | -| setRowGroupingCriteriaIndex [](/x/introduction/licensing/#premium-plan) | (groupingCriteriaField: string, groupingIndex: number) => void | Sets the grouping index of a grouping criteria. | -| setRowGroupingModel [](/x/introduction/licensing/#premium-plan) | (model: GridRowGroupingModel) => void | Sets the columns to use as grouping criteria. | -| setRowIndex | (rowId: GridRowId, targetIndex: number) => void | Moves a row from its original position to the position given by `targetIndex`. | -| setRows | (rows: GridRowModel[]) => void | Sets a new set of rows. | -| setRowSelectionModel | (rowIds: GridRowId[]) => void | Updates the selected rows to be those passed to the `rowIds` argument.
Any row already selected will be unselected. | -| setSortModel | (model: GridSortModel) => void | Updates the sort model and triggers the sorting of rows. | -| showColumnMenu | (field: string) => void | Display the column menu under the `field` column. | -| showError | (props: any) => void | Displays the error overlay component. | -| showFilterPanel | (targetColumnField?: string) => void | Shows the filter panel. If `targetColumnField` is given, a filter for this field is also added. | -| showPreferences | (newValue: GridPreferencePanelsValue) => void | Displays the preferences panel. The `newValue` argument controls the content of the panel. | -| sortColumn | (column: GridColDef, direction?: GridSortDirection, allowMultipleSorting?: boolean) => void | Sorts a column. | -| startCellEditMode | (params: GridStartCellEditModeParams) => void | Puts the cell corresponding to the given row id and field into edit mode. | -| startRowEditMode | (params: GridStartRowEditModeParams) => void | Puts the row corresponding to the given id into edit mode. | -| state | State | Property that contains the whole state of the grid. | -| stopCellEditMode | (params: GridStopCellEditModeParams) => void | Puts the cell corresponding to the given row id and field into view mode and updates the original row with the new value stored.
If `params.ignoreModifications` is `false` it will discard the modifications made. | -| stopRowEditMode | (params: GridStopRowEditModeParams) => void | Puts the row corresponding to the given id and into view mode and updates the original row with the new values stored.
If `params.ignoreModifications` is `false` it will discard the modifications made. | -| subscribeEvent | <E extends GridEvents>(event: E, handler: GridEventListener<E>, options?: EventListenerOptions) => () => void | Registers a handler for an event. | -| toggleColumnMenu | (field: string) => void | Toggles the column menu under the `field` column. | -| toggleDetailPanel [](/x/introduction/licensing/#pro-plan) | (id: GridRowId) => void | Expands or collapses the detail panel of a row. | -| unpinColumn [](/x/introduction/licensing/#pro-plan) | (field: string) => void | Unpins a column. | -| unstable_getAllGroupDetails | () => GridColumnGroupLookup | Returns the column group lookup. | -| unstable_getColumnGroupPath | (field: string) => GridColumnGroup['groupId'][] | Returns the id of the groups leading to the requested column.
The array is ordered by increasing depth (the last element is the direct parent of the column). | -| unstable_replaceRows | (firstRowToReplace: number, newRows: GridRowModel[]) => void | Replace a set of rows with new rows. | -| unstable_setPinnedRows [](/x/introduction/licensing/#pro-plan) | (pinnedRows?: GridPinnedRowsProp) => void | Changes the pinned rows. | -| updateColumns | (cols: GridColDef[]) => void | Updates the definition of multiple columns at the same time. | -| updateRows | (updates: GridRowModelUpdate[]) => void | Allows to update, insert and delete rows. | -| upsertFilterItem | (item: GridFilterItem) => void | Updates or inserts a [GridFilterItem](/x/api/data-grid/grid-filter-item/). | -| upsertFilterItems | (items: GridFilterItem[]) => void | Updates or inserts many [GridFilterItem](/x/api/data-grid/grid-filter-item/). | +| Name | Type | Description | +| :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| addRowGroupingCriteria [](/x/introduction/licensing/#premium-plan) | (groupingCriteriaField: string, groupingIndex?: number) => void | Adds the field to the row grouping model. | +| applySorting | () => void | Applies the current sort model to the rows. | +| deleteFilterItem | (item: GridFilterItem) => void | Deletes a [GridFilterItem](/x/api/data-grid/grid-filter-item/). | +| exportDataAsCsv | (options?: GridCsvExportOptions) => void | Downloads and exports a CSV of the grid's data. | +| exportDataAsExcel [](/x/introduction/licensing/#premium-plan) | (options?: GridExcelExportOptions) => Promise<void> | Downloads and exports an Excel file of the grid's data. | +| exportDataAsPrint | (options?: GridPrintExportOptions) => void | Print the grid's data. | +| exportState | (params?: GridExportStateParams) => InitialState | Generates a serializable object containing the exportable parts of the DataGrid state.
These values can then be passed to the `initialState` prop or injected using the `restoreState` method. | +| forceUpdate | () => void | Forces the grid to rerender. It's often used after a state update. | +| getAllColumns | () => GridStateColDef[] | Returns an array of [GridColDef](/x/api/data-grid/grid-col-def/) containing all the column definitions. | +| getAllRowIds | () => GridRowId[] | Gets the list of row ids. | +| getCellElement | (id: GridRowId, field: string) => HTMLDivElement \| null | Gets the underlying DOM element for a cell at the given `id` and `field`. | +| getCellMode | (id: GridRowId, field: string) => GridCellMode | Gets the mode of a cell. | +| getCellParams | <V = any, R extends GridValidRowModel = any, F = V, N extends GridTreeNode = GridTreeNode>(id: GridRowId, field: string) => GridCellParams<V, R, F, N> | Gets the [GridCellParams](/x/api/data-grid/grid-cell-params/) object that is passed as argument in events. | +| getCellValue | <V extends any = any>(id: GridRowId, field: string) => V | Gets the value of a cell at the given `id` and `field`. | +| getColumn | (field: string) => GridStateColDef | Returns the [GridColDef](/x/api/data-grid/grid-col-def/) for the given `field`. | +| getColumnHeaderElement | (field: string) => HTMLDivElement \| null | Gets the underlying DOM element for the column header with the given `field`. | +| getColumnHeaderParams | (field: string) => GridColumnHeaderParams | Gets the GridColumnHeaderParams object that is passed as argument in events. | +| getColumnIndex | (field: string, useVisibleColumns?: boolean) => number | Returns the index position of a column. By default, only the visible columns are considered.
Pass `false` to `useVisibleColumns` to consider all columns. | +| getColumnPosition | (field: string) => number | Returns the left-position of a column relative to the inner border of the grid. | +| getDataAsCsv | (options?: GridCsvExportOptions) => string | Returns the grid data as a CSV string.
This method is used internally by `exportDataAsCsv`. | +| getDataAsExcel [](/x/introduction/licensing/#premium-plan) | (options?: GridExcelExportOptions) => Promise<Excel.Workbook> \| null | Returns the grid data as an exceljs workbook.
This method is used internally by `exportDataAsExcel`. | +| getExpandedDetailPanels [](/x/introduction/licensing/#pro-plan) | () => GridRowId[] | Returns the rows whose detail panel is open. | +| getLocaleText | <T extends GridTranslationKeys>(key: T) => GridLocaleText[T] | Returns the translation for the `key`. | +| getPinnedColumns [](/x/introduction/licensing/#pro-plan) | () => GridPinnedColumns | Returns which columns are pinned. | +| getRootDimensions | () => GridDimensions \| null | Returns the dimensions of the grid | +| getRow | <R extends GridValidRowModel = any>(id: GridRowId) => R \| null | Gets the row data with a given id. | +| getRowElement | (id: GridRowId) => HTMLDivElement \| null | Gets the underlying DOM element for a row at the given `id`. | +| getRowGroupChildren | (params: GridRowGroupChildrenGetterParams) => GridRowId[] | Gets the rows of a grouping criteria.
Only contains the rows provided to the grid, not the rows automatically generated by it. | +| getRowIdFromRowIndex | (index: number) => GridRowId | Gets the `GridRowId` of a row at a specific index.
The index is based on the sorted but unfiltered row list. | +| getRowIndexRelativeToVisibleRows | (id: GridRowId) => number | Gets the index of a row relative to the rows that are reachable by scroll. | +| getRowMode | (id: GridRowId) => GridRowMode | Gets the mode of a row. | +| getRowModels | () => Map<GridRowId, GridRowModel> | Gets the full set of rows as Map<GridRowId, GridRowModel>. | +| getRowNode | <N extends GridTreeNode>(id: GridRowId) => N \| null | Gets the row node from the internal tree structure. | +| getRowParams | (id: GridRowId) => GridRowParams | Gets the [GridRowParams](/x/api/data-grid/grid-row-params/) object that is passed as argument in events. | +| getRowsCount | () => number | Gets the total number of rows in the grid. | +| getScrollPosition | () => GridScrollParams | Returns the current scroll position. | +| getSelectedRows | () => Map<GridRowId, GridRowModel> | Returns an array of the selected rows. | +| getSortedRowIds | () => GridRowId[] | Returns all row ids sorted according to the active sort model. | +| getSortedRows | () => GridRowModel[] | Returns all rows sorted according to the active sort model. | +| getSortModel | () => GridSortModel | Returns the sort model currently applied to the grid. | +| getVisibleColumns | () => GridStateColDef[] | Returns the currently visible columns. | +| getVisibleRowModels | () => Map<GridRowId, GridRowModel> | Returns a sorted `Map` containing only the visible rows. | +| hideColumnMenu | () => void | Hides the column menu that is open. | +| hideFilterPanel | () => void | Hides the filter panel. | +| hidePreferences | () => void | Hides the preferences panel. | +| isCellEditable | (params: GridCellParams) => boolean | Controls if a cell is editable. | +| isColumnPinned [](/x/introduction/licensing/#pro-plan) | (field: string) => GridPinnedPosition \| false | Returns which side a column is pinned to. | +| isRowSelectable | (id: GridRowId) => boolean | Determines if a row can be selected or not. | +| isRowSelected | (id: GridRowId) => boolean | Determines if a row is selected or not. | +| pinColumn [](/x/introduction/licensing/#pro-plan) | (field: string, side: GridPinnedPosition) => void | Pins a column to the left or right side of the grid. | +| publishEvent | GridEventPublisher | Emits an event. | +| removeRowGroupingCriteria [](/x/introduction/licensing/#premium-plan) | (groupingCriteriaField: string) => void | Remove the field from the row grouping model. | +| resetRowHeights | () => void | Forces the recalculation of the heights of all rows. | +| resize | () => void | Triggers a resize of the component and recalculation of width and height. | +| restoreState | (stateToRestore: InitialState) => void | Inject the given values into the state of the DataGrid. | +| scroll | (params: Partial<GridScrollParams>) => void | Triggers the viewport to scroll to the given positions (in pixels). | +| scrollToIndexes | (params: Partial<GridCellIndexCoordinates>) => boolean | Triggers the viewport to scroll to the cell at indexes given by `params`.
Returns `true` if the grid had to scroll to reach the target. | +| selectRow | (id: GridRowId, isSelected?: boolean, resetSelection?: boolean) => void | Change the selection state of a row. | +| selectRowRange [](/x/introduction/licensing/#pro-plan) | (range: { startId: GridRowId; endId: GridRowId }, isSelected?: boolean, resetSelection?: boolean) => void | Change the selection state of all the selectable rows in a range. | +| selectRows [](/x/introduction/licensing/#pro-plan) | (ids: GridRowId[], isSelected?: boolean, resetSelection?: boolean) => void | Change the selection state of multiple rows. | +| setAggregationModel [](/x/introduction/licensing/#premium-plan) | (model: GridAggregationModel) => void | Sets the aggregation model to the one given by `model`. | +| setCellFocus | (id: GridRowId, field: string) => void | Sets the focus to the cell at the given `id` and `field`. | +| setColumnHeaderFocus | (field: string, event?: MuiBaseEvent) => void | Sets the focus to the column header at the given `field`. | +| setColumnIndex | (field: string, targetIndexPosition: number) => void | Moves a column from its original position to the position given by `targetIndexPosition`. | +| setColumnVisibility | (field: string, isVisible: boolean) => void | Changes the visibility of the column referred by `field`. | +| setColumnVisibilityModel | (model: GridColumnVisibilityModel) => void | Sets the column visibility model to the one given by `model`. | +| setColumnWidth | (field: string, width: number) => void | Updates the width of a column. | +| setDensity | (density: GridDensity, headerHeight?: number, rowHeight?: number, maxDepth?: number) => void | Sets the density of the grid. | +| setEditCellValue | (params: GridEditCellValueParams, event?: MuiBaseEvent) => Promise<boolean> \| void | Sets the value of the edit cell.
Commonly used inside the edit cell component. | +| setExpandedDetailPanels [](/x/introduction/licensing/#pro-plan) | (ids: GridRowId[]) => void | Changes which rows to expand the detail panel. | +| setFilterLinkOperator | (operator: GridLinkOperator) => void | Changes the GridLinkOperator used to connect the filters. | +| setFilterModel | (model: GridFilterModel, reason?: GridControlledStateReasonLookup['filter']) => void | Sets the filter model to the one given by `model`. | +| setPage | (page: number) => void | Sets the displayed page to the value given by `page`. | +| setPageSize | (pageSize: number) => void | Sets the number of displayed rows to the value given by `pageSize`. | +| setPinnedColumns [](/x/introduction/licensing/#pro-plan) | (pinnedColumns: GridPinnedColumns) => void | Changes the pinned columns. | +| setQuickFilterValues | (values: any[]) => void | Set the quick filter values ot the one given by `values` | +| setRowChildrenExpansion | (id: GridRowId, isExpanded: boolean) => void | Expand or collapse a row children. | +| setRowGroupingCriteriaIndex [](/x/introduction/licensing/#premium-plan) | (groupingCriteriaField: string, groupingIndex: number) => void | Sets the grouping index of a grouping criteria. | +| setRowGroupingModel [](/x/introduction/licensing/#premium-plan) | (model: GridRowGroupingModel) => void | Sets the columns to use as grouping criteria. | +| setRowIndex | (rowId: GridRowId, targetIndex: number) => void | Moves a row from its original position to the position given by `targetIndex`. | +| setRows | (rows: GridRowModel[]) => void | Sets a new set of rows. | +| setRowSelectionModel | (rowIds: GridRowId[]) => void | Updates the selected rows to be those passed to the `rowIds` argument.
Any row already selected will be unselected. | +| setSortModel | (model: GridSortModel) => void | Updates the sort model and triggers the sorting of rows. | +| showColumnMenu | (field: string) => void | Display the column menu under the `field` column. | +| showError | (props: any) => void | Displays the error overlay component. | +| showFilterPanel | (targetColumnField?: string) => void | Shows the filter panel. If `targetColumnField` is given, a filter for this field is also added. | +| showPreferences | (newValue: GridPreferencePanelsValue) => void | Displays the preferences panel. The `newValue` argument controls the content of the panel. | +| sortColumn | (column: GridColDef, direction?: GridSortDirection, allowMultipleSorting?: boolean) => void | Sorts a column. | +| startCellEditMode | (params: GridStartCellEditModeParams) => void | Puts the cell corresponding to the given row id and field into edit mode. | +| startRowEditMode | (params: GridStartRowEditModeParams) => void | Puts the row corresponding to the given id into edit mode. | +| state | State | Property that contains the whole state of the grid. | +| stopCellEditMode | (params: GridStopCellEditModeParams) => void | Puts the cell corresponding to the given row id and field into view mode and updates the original row with the new value stored.
If `params.ignoreModifications` is `false` it will discard the modifications made. | +| stopRowEditMode | (params: GridStopRowEditModeParams) => void | Puts the row corresponding to the given id and into view mode and updates the original row with the new values stored.
If `params.ignoreModifications` is `false` it will discard the modifications made. | +| subscribeEvent | <E extends GridEvents>(event: E, handler: GridEventListener<E>, options?: EventListenerOptions) => () => void | Registers a handler for an event. | +| toggleColumnMenu | (field: string) => void | Toggles the column menu under the `field` column. | +| toggleDetailPanel [](/x/introduction/licensing/#pro-plan) | (id: GridRowId) => void | Expands or collapses the detail panel of a row. | +| unpinColumn [](/x/introduction/licensing/#pro-plan) | (field: string) => void | Unpins a column. | +| unstable_getAllGroupDetails | () => GridColumnGroupLookup | Returns the column group lookup. | +| unstable_getCellSelectionModel [](/x/introduction/licensing/#premium-plan) | () => GridCellSelectionModel | Returns an object containing the selection state of the cells.
The keys of the object correpond to the row IDs.
The value of each key is another object whose keys are the fields and values are the selection state. | +| unstable_getColumnGroupPath | (field: string) => GridColumnGroup['groupId'][] | Returns the id of the groups leading to the requested column.
The array is ordered by increasing depth (the last element is the direct parent of the column). | +| unstable_getSelectedCellsAsArray [](/x/introduction/licensing/#premium-plan) | () => GridCellCoordinates[] | Returns an array containg only the selected cells.
Each item is an object with the ID and field of the cell. | +| unstable_isCellSelected [](/x/introduction/licensing/#premium-plan) | (id: GridRowId, field: GridColDef['field']) => boolean | Determines if a cell is selected or not. | +| unstable_replaceRows | (firstRowToReplace: number, newRows: GridRowModel[]) => void | Replace a set of rows with new rows. | +| unstable_selectCellRange [](/x/introduction/licensing/#premium-plan) | (start: GridCellCoordinates, end: GridCellCoordinates, keepOtherSelected?: boolean) => void | Selects all cells that are inside the range given by `start` and `end` coordinates. | +| unstable_setCellSelectionModel [](/x/introduction/licensing/#premium-plan) | (newModel: GridCellSelectionModel) => void | Updates the selected cells to be those passed to the `newModel` argument.
Any cell already selected will be unselected. | +| unstable_setPinnedRows [](/x/introduction/licensing/#pro-plan) | (pinnedRows?: GridPinnedRowsProp) => void | Changes the pinned rows. | +| updateColumns | (cols: GridColDef[]) => void | Updates the definition of multiple columns at the same time. | +| updateRows | (updates: GridRowModelUpdate[]) => void | Allows to update, insert and delete rows. | +| upsertFilterItem | (item: GridFilterItem) => void | Updates or inserts a [GridFilterItem](/x/api/data-grid/grid-filter-item/). | +| upsertFilterItems | (items: GridFilterItem[]) => void | Updates or inserts many [GridFilterItem](/x/api/data-grid/grid-filter-item/). | diff --git a/docs/translations/api-docs/data-grid/data-grid-premium-pt.json b/docs/translations/api-docs/data-grid/data-grid-premium-pt.json index 77bb7053124b..3bf89fd4d0a0 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium-pt.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium-pt.json @@ -146,7 +146,10 @@ "sortModel": "Set the sort model of the grid.", "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.", "throttleRowsMs": "If positive, the Grid will throttle updates coming from apiRef.current.updateRows and apiRef.current.setRows. It can be useful if you have a high update rate but do not want to do heavy work like filtering / sorting or rendering on each individual update.", - "treeData": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop." + "treeData": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop.", + "unstable_cellSelection": "If true, the cell selection mode is enabled.", + "unstable_cellSelectionModel": "Set the cell selection model of the grid.", + "unstable_onCellSelectionModelChange": "Callback fired when the selection state of one or multiple cells changes.

Signature:
function(cellSelectionModel: GridCellSelectionModel, details: GridCallbackDetails) => void
cellSelectionModel: Object in the shape of GridCellSelectionModel containg the selected cells.
details: Additional details for this callback." }, "classDescriptions": { "actionsCell": { @@ -217,6 +220,26 @@ "nodeName": "the cell element", "conditions": "the cell has a custom renderer" }, + "cell--rangeTop": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the top edge of a cell selection range" + }, + "cell--rangeBottom": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the bottom edge of a cell selection range" + }, + "cell--rangeLeft": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the left edge of a cell selection range" + }, + "cell--rangeRight": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the right edge of a cell selection range" + }, "cell": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the cell element" }, "cellContent": { "description": "Styles applied to {{nodeName}}.", @@ -550,6 +573,11 @@ "nodeName": "the root element", "conditions": "density is \"compact\"" }, + "root--disableUserSelection": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the root element", + "conditions": "user selection is disabled" + }, "row--editable": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the row element", diff --git a/docs/translations/api-docs/data-grid/data-grid-premium-zh.json b/docs/translations/api-docs/data-grid/data-grid-premium-zh.json index 77bb7053124b..3bf89fd4d0a0 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium-zh.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium-zh.json @@ -146,7 +146,10 @@ "sortModel": "Set the sort model of the grid.", "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.", "throttleRowsMs": "If positive, the Grid will throttle updates coming from apiRef.current.updateRows and apiRef.current.setRows. It can be useful if you have a high update rate but do not want to do heavy work like filtering / sorting or rendering on each individual update.", - "treeData": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop." + "treeData": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop.", + "unstable_cellSelection": "If true, the cell selection mode is enabled.", + "unstable_cellSelectionModel": "Set the cell selection model of the grid.", + "unstable_onCellSelectionModelChange": "Callback fired when the selection state of one or multiple cells changes.

Signature:
function(cellSelectionModel: GridCellSelectionModel, details: GridCallbackDetails) => void
cellSelectionModel: Object in the shape of GridCellSelectionModel containg the selected cells.
details: Additional details for this callback." }, "classDescriptions": { "actionsCell": { @@ -217,6 +220,26 @@ "nodeName": "the cell element", "conditions": "the cell has a custom renderer" }, + "cell--rangeTop": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the top edge of a cell selection range" + }, + "cell--rangeBottom": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the bottom edge of a cell selection range" + }, + "cell--rangeLeft": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the left edge of a cell selection range" + }, + "cell--rangeRight": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the right edge of a cell selection range" + }, "cell": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the cell element" }, "cellContent": { "description": "Styles applied to {{nodeName}}.", @@ -550,6 +573,11 @@ "nodeName": "the root element", "conditions": "density is \"compact\"" }, + "root--disableUserSelection": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the root element", + "conditions": "user selection is disabled" + }, "row--editable": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the row element", diff --git a/docs/translations/api-docs/data-grid/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium.json index 77bb7053124b..3bf89fd4d0a0 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium.json @@ -146,7 +146,10 @@ "sortModel": "Set the sort model of the grid.", "sx": "The system prop that allows defining system overrides as well as additional CSS styles. See the `sx` page for more details.", "throttleRowsMs": "If positive, the Grid will throttle updates coming from apiRef.current.updateRows and apiRef.current.setRows. It can be useful if you have a high update rate but do not want to do heavy work like filtering / sorting or rendering on each individual update.", - "treeData": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop." + "treeData": "If true, the rows will be gathered in a tree structure according to the getTreeDataPath prop.", + "unstable_cellSelection": "If true, the cell selection mode is enabled.", + "unstable_cellSelectionModel": "Set the cell selection model of the grid.", + "unstable_onCellSelectionModelChange": "Callback fired when the selection state of one or multiple cells changes.

Signature:
function(cellSelectionModel: GridCellSelectionModel, details: GridCallbackDetails) => void
cellSelectionModel: Object in the shape of GridCellSelectionModel containg the selected cells.
details: Additional details for this callback." }, "classDescriptions": { "actionsCell": { @@ -217,6 +220,26 @@ "nodeName": "the cell element", "conditions": "the cell has a custom renderer" }, + "cell--rangeTop": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the top edge of a cell selection range" + }, + "cell--rangeBottom": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the bottom edge of a cell selection range" + }, + "cell--rangeLeft": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the left edge of a cell selection range" + }, + "cell--rangeRight": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the right edge of a cell selection range" + }, "cell": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the cell element" }, "cellContent": { "description": "Styles applied to {{nodeName}}.", @@ -550,6 +573,11 @@ "nodeName": "the root element", "conditions": "density is \"compact\"" }, + "root--disableUserSelection": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the root element", + "conditions": "user selection is disabled" + }, "row--editable": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the row element", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro-pt.json b/docs/translations/api-docs/data-grid/data-grid-pro-pt.json index 2e2a847d0a93..70fdc8af274d 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro-pt.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro-pt.json @@ -207,6 +207,26 @@ "nodeName": "the cell element", "conditions": "the cell has a custom renderer" }, + "cell--rangeTop": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the top edge of a cell selection range" + }, + "cell--rangeBottom": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the bottom edge of a cell selection range" + }, + "cell--rangeLeft": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the left edge of a cell selection range" + }, + "cell--rangeRight": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the right edge of a cell selection range" + }, "cell": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the cell element" }, "cellContent": { "description": "Styles applied to {{nodeName}}.", @@ -540,6 +560,11 @@ "nodeName": "the root element", "conditions": "density is \"compact\"" }, + "root--disableUserSelection": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the root element", + "conditions": "user selection is disabled" + }, "row--editable": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the row element", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro-zh.json b/docs/translations/api-docs/data-grid/data-grid-pro-zh.json index 2e2a847d0a93..70fdc8af274d 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro-zh.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro-zh.json @@ -207,6 +207,26 @@ "nodeName": "the cell element", "conditions": "the cell has a custom renderer" }, + "cell--rangeTop": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the top edge of a cell selection range" + }, + "cell--rangeBottom": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the bottom edge of a cell selection range" + }, + "cell--rangeLeft": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the left edge of a cell selection range" + }, + "cell--rangeRight": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the right edge of a cell selection range" + }, "cell": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the cell element" }, "cellContent": { "description": "Styles applied to {{nodeName}}.", @@ -540,6 +560,11 @@ "nodeName": "the root element", "conditions": "density is \"compact\"" }, + "root--disableUserSelection": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the root element", + "conditions": "user selection is disabled" + }, "row--editable": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the row element", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro.json index 2e2a847d0a93..70fdc8af274d 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro.json @@ -207,6 +207,26 @@ "nodeName": "the cell element", "conditions": "the cell has a custom renderer" }, + "cell--rangeTop": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the top edge of a cell selection range" + }, + "cell--rangeBottom": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the bottom edge of a cell selection range" + }, + "cell--rangeLeft": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the left edge of a cell selection range" + }, + "cell--rangeRight": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the right edge of a cell selection range" + }, "cell": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the cell element" }, "cellContent": { "description": "Styles applied to {{nodeName}}.", @@ -540,6 +560,11 @@ "nodeName": "the root element", "conditions": "density is \"compact\"" }, + "root--disableUserSelection": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the root element", + "conditions": "user selection is disabled" + }, "row--editable": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the row element", diff --git a/docs/translations/api-docs/data-grid/data-grid-pt.json b/docs/translations/api-docs/data-grid/data-grid-pt.json index 0bdb70dcaa4c..e588dca34e51 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pt.json +++ b/docs/translations/api-docs/data-grid/data-grid-pt.json @@ -174,6 +174,26 @@ "nodeName": "the cell element", "conditions": "the cell has a custom renderer" }, + "cell--rangeTop": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the top edge of a cell selection range" + }, + "cell--rangeBottom": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the bottom edge of a cell selection range" + }, + "cell--rangeLeft": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the left edge of a cell selection range" + }, + "cell--rangeRight": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the right edge of a cell selection range" + }, "cell": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the cell element" }, "cellContent": { "description": "Styles applied to {{nodeName}}.", @@ -507,6 +527,11 @@ "nodeName": "the root element", "conditions": "density is \"compact\"" }, + "root--disableUserSelection": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the root element", + "conditions": "user selection is disabled" + }, "row--editable": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the row element", diff --git a/docs/translations/api-docs/data-grid/data-grid-zh.json b/docs/translations/api-docs/data-grid/data-grid-zh.json index 0bdb70dcaa4c..e588dca34e51 100644 --- a/docs/translations/api-docs/data-grid/data-grid-zh.json +++ b/docs/translations/api-docs/data-grid/data-grid-zh.json @@ -174,6 +174,26 @@ "nodeName": "the cell element", "conditions": "the cell has a custom renderer" }, + "cell--rangeTop": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the top edge of a cell selection range" + }, + "cell--rangeBottom": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the bottom edge of a cell selection range" + }, + "cell--rangeLeft": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the left edge of a cell selection range" + }, + "cell--rangeRight": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the right edge of a cell selection range" + }, "cell": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the cell element" }, "cellContent": { "description": "Styles applied to {{nodeName}}.", @@ -507,6 +527,11 @@ "nodeName": "the root element", "conditions": "density is \"compact\"" }, + "root--disableUserSelection": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the root element", + "conditions": "user selection is disabled" + }, "row--editable": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the row element", diff --git a/docs/translations/api-docs/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid.json index 0bdb70dcaa4c..e588dca34e51 100644 --- a/docs/translations/api-docs/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid.json @@ -174,6 +174,26 @@ "nodeName": "the cell element", "conditions": "the cell has a custom renderer" }, + "cell--rangeTop": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the top edge of a cell selection range" + }, + "cell--rangeBottom": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the bottom edge of a cell selection range" + }, + "cell--rangeLeft": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the left edge of a cell selection range" + }, + "cell--rangeRight": { + "description": "Styles applied to {{nodeName}} if {{conditions}}.", + "nodeName": "the cell element", + "conditions": "it is at the right edge of a cell selection range" + }, "cell": { "description": "Styles applied to {{nodeName}}.", "nodeName": "the cell element" }, "cellContent": { "description": "Styles applied to {{nodeName}}.", @@ -507,6 +527,11 @@ "nodeName": "the root element", "conditions": "density is \"compact\"" }, + "root--disableUserSelection": { + "description": "Styles applied to {{nodeName}} when {{conditions}}.", + "nodeName": "the root element", + "conditions": "user selection is disabled" + }, "row--editable": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the row element", diff --git a/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 71b9989dbcd2..246e347b3674 100644 --- a/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -950,4 +950,19 @@ DataGridPremiumRaw.propTypes = { * @default false */ treeData: PropTypes.bool, + /** + * If `true`, the cell selection mode is enabled. + * @default false + */ + unstable_cellSelection: PropTypes.bool, + /** + * Set the cell selection model of the grid. + */ + unstable_cellSelectionModel: PropTypes.object, + /** + * Callback fired when the selection state of one or multiple cells changes. + * @param {GridCellSelectionModel} cellSelectionModel Object in the shape of [[GridCellSelectionModel]] containg the selected cells. + * @param {GridCallbackDetails} details Additional details for this callback. + */ + unstable_onCellSelectionModelChange: PropTypes.func, } as any; diff --git a/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index de6e6d98dc4c..39b7cd3cac44 100644 --- a/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -75,6 +75,10 @@ import { } from '../hooks/features/rowGrouping/useGridRowGrouping'; import { useGridRowGroupingPreProcessors } from '../hooks/features/rowGrouping/useGridRowGroupingPreProcessors'; import { useGridExcelExport } from '../hooks/features/export/useGridExcelExport'; +import { + cellSelectionStateInitializer, + useGridCellSelection, +} from '../hooks/features/cellSelection/useGridCellSelection'; export const useDataGridPremiumComponent = ( inputApiRef: React.MutableRefObject | undefined, @@ -107,6 +111,7 @@ export const useDataGridPremiumComponent = ( useGridInitializeState(rowGroupingStateInitializer, privateApiRef, props); useGridInitializeState(aggregationStateInitializer, privateApiRef, props); useGridInitializeState(rowSelectionStateInitializer, privateApiRef, props); + useGridInitializeState(cellSelectionStateInitializer, privateApiRef, props); useGridInitializeState(detailPanelStateInitializer, privateApiRef, props); useGridInitializeState(columnPinningStateInitializer, privateApiRef, props); useGridInitializeState(columnsStateInitializer, privateApiRef, props); @@ -130,6 +135,7 @@ export const useDataGridPremiumComponent = ( useGridAggregation(privateApiRef, props); useGridKeyboardNavigation(privateApiRef, props); useGridRowSelection(privateApiRef, props); + useGridCellSelection(privateApiRef, props); useGridColumnPinning(privateApiRef, props); useGridRowPinning(privateApiRef, props); useGridColumns(privateApiRef, props); diff --git a/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts b/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts index 5c24b04f1f64..0d21c974dc65 100644 --- a/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts +++ b/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumProps.ts @@ -18,6 +18,7 @@ import { GRID_AGGREGATION_FUNCTIONS } from '../hooks/features/aggregation'; */ export const DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES: DataGridPremiumPropsWithDefaultValue = { ...DATA_GRID_PRO_PROPS_DEFAULT_VALUES, + unstable_cellSelection: false, disableAggregation: false, disableRowGrouping: false, rowGroupingColumnMode: 'single', diff --git a/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/gridCellSelectionInterfaces.ts b/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/gridCellSelectionInterfaces.ts new file mode 100644 index 000000000000..c593575aac18 --- /dev/null +++ b/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/gridCellSelectionInterfaces.ts @@ -0,0 +1,46 @@ +import { GridCellCoordinates, GridColDef, GridRowId } from '@mui/x-data-grid-pro'; + +export type GridCellSelectionModel = Record>; + +/** + * The cell selection API interface that is available in the grid [[apiRef]]. + */ +export interface GridCellSelectionApi { + /** + * Determines if a cell is selected or not. + * @param {GridRowId} id The id of the row. + * @param {GridColDef['field']} field The field. + * @returns {boolean} A boolean indicating if the cell is selected. + */ + unstable_isCellSelected: (id: GridRowId, field: GridColDef['field']) => boolean; + /** + * Returns an object containing the selection state of the cells. + * The keys of the object correpond to the row IDs. + * The value of each key is another object whose keys are the fields and values are the selection state. + * @returns {GridCellSelectionModel} Object containing the selection state of the cells + */ + unstable_getCellSelectionModel: () => GridCellSelectionModel; + /** + * Updates the selected cells to be those passed to the `newModel` argument. + * Any cell already selected will be unselected. + * @param {GridCellSelectionModel} newModel The cells to select. + */ + unstable_setCellSelectionModel: (newModel: GridCellSelectionModel) => void; + /** + * Selects all cells that are inside the range given by `start` and `end` coordinates. + * @param {GridCellCoordinates} start Object containg the row ID and field of the first cell to select. + * @param {GridCellCoordinates} end Object containg the row ID and field of the last cell to select. + * @param {boolean} keepOtherSelected Whether to keep current selected cells or discard. Default is false. + */ + unstable_selectCellRange: ( + start: GridCellCoordinates, + end: GridCellCoordinates, + keepOtherSelected?: boolean, + ) => void; + /** + * Returns an array containg only the selected cells. + * Each item is an object with the ID and field of the cell. + * @returns {GridCellCoordinates[]} Array with the selected cells. + */ + unstable_getSelectedCellsAsArray: () => GridCellCoordinates[]; +} diff --git a/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/gridCellSelectionSelector.ts b/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/gridCellSelectionSelector.ts new file mode 100644 index 000000000000..a4d35e75605e --- /dev/null +++ b/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/gridCellSelectionSelector.ts @@ -0,0 +1,3 @@ +import { GridStatePremium } from '../../../models/gridStatePremium'; + +export const gridCellSelectionStateSelector = (state: GridStatePremium) => state.cellSelection; diff --git a/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/index.ts b/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/index.ts new file mode 100644 index 000000000000..6bd3918c94c5 --- /dev/null +++ b/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/index.ts @@ -0,0 +1 @@ +export * from './gridCellSelectionInterfaces'; diff --git a/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts b/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts new file mode 100644 index 000000000000..4f8cec688ffa --- /dev/null +++ b/packages/grid/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts @@ -0,0 +1,379 @@ +import * as React from 'react'; +import { useEventCallback } from '@mui/material/utils'; +import { + GridPipeProcessor, + GridStateInitializer, + isNavigationKey, + useGridRegisterPipeProcessor, + useGridVisibleRows, +} from '@mui/x-data-grid-pro/internals'; +import { + useGridApiEventHandler, + useGridApiMethod, + GridEventListener, + GridEventLookup, + GRID_ACTIONS_COLUMN_TYPE, + GRID_CHECKBOX_SELECTION_COL_DEF, + GRID_DETAIL_PANEL_TOGGLE_FIELD, + GridCellCoordinates, + gridRowsDataRowIdToIdLookupSelector, + GridRowId, + gridClasses, +} from '@mui/x-data-grid-pro'; +import { gridCellSelectionStateSelector } from './gridCellSelectionSelector'; +import { GridCellSelectionApi } from './gridCellSelectionInterfaces'; +import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps'; +import { GridPrivateApiPremium } from '../../../models/gridApiPremium'; + +export const cellSelectionStateInitializer: GridStateInitializer< + Pick +> = (state, props) => ({ + ...state, + cellSelection: { ...(props.unstable_cellSelectionModel ?? props.initialState?.cellSelection) }, +}); + +export const useGridCellSelection = ( + apiRef: React.MutableRefObject, + props: Pick< + DataGridPremiumProcessedProps, + | 'unstable_cellSelection' + | 'unstable_cellSelectionModel' + | 'unstable_onCellSelectionModelChange' + | 'pagination' + | 'paginationMode' + >, +) => { + const visibleRows = useGridVisibleRows(apiRef, props); + const lastClickedCell = React.useRef(); + const lastMouseDownCell = React.useRef(); + const focusedCellWhenShiftWasPressed = React.useRef(null); + + apiRef.current.registerControlState({ + stateId: 'cellSelection', + propModel: props.unstable_cellSelectionModel, + propOnChange: props.unstable_onCellSelectionModelChange, + stateSelector: gridCellSelectionStateSelector, + changeEvent: 'cellSelectionChange', + }); + + const runIfCellSelectionIsEnabled = + (callback: (...args: Args) => void) => + (...args: Args) => { + if (props.unstable_cellSelection) { + callback(...args); + } + }; + + const isCellSelected = React.useCallback( + (id, field) => { + if (!props.unstable_cellSelection) { + return false; + } + const cellSelectionModel = gridCellSelectionStateSelector(apiRef.current.state); + return cellSelectionModel[id] ? !!cellSelectionModel[id][field] : false; + }, + [apiRef, props.unstable_cellSelection], + ); + + const getCellSelectionModel = React.useCallback(() => { + return gridCellSelectionStateSelector(apiRef.current.state); + }, [apiRef]); + + const setCellSelectionModel = React.useCallback< + GridCellSelectionApi['unstable_setCellSelectionModel'] + >( + (newModel) => { + if (!props.unstable_cellSelection) { + return; + } + apiRef.current.setState((prevState) => ({ ...prevState, cellSelection: newModel })); + apiRef.current.forceUpdate(); + }, + [apiRef, props.unstable_cellSelection], + ); + + const selectCellRange = React.useCallback( + (start, end, keepOtherSelected = false) => { + const startRowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(start.id); + const startColumnIndex = apiRef.current.getColumnIndex(start.field); + const endRowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(end.id); + const endColumnIndex = apiRef.current.getColumnIndex(end.field); + + let finalStartRowIndex = startRowIndex; + let finalStartColumnIndex = startColumnIndex; + let finalEndRowIndex = endRowIndex; + let finalEndColumnIndex = endColumnIndex; + + if (finalStartRowIndex > finalEndRowIndex) { + finalStartRowIndex = endRowIndex; + finalEndRowIndex = startRowIndex; + } + + if (finalStartColumnIndex > finalEndColumnIndex) { + finalStartColumnIndex = endColumnIndex; + finalEndColumnIndex = startColumnIndex; + } + + const visibleColumns = apiRef.current.getVisibleColumns(); + const rowsInRange = visibleRows.rows.slice(finalStartRowIndex, finalEndRowIndex + 1); + const columnsInRange = visibleColumns.slice(finalStartColumnIndex, finalEndColumnIndex + 1); + + const newModel = keepOtherSelected ? apiRef.current.unstable_getCellSelectionModel() : {}; + + rowsInRange.forEach((row) => { + if (!newModel[row.id]) { + newModel[row.id] = {}; + } + columnsInRange.forEach((column) => { + newModel[row.id][column.field] = true; + }, {}); + }); + + apiRef.current.unstable_setCellSelectionModel(newModel); + }, + [apiRef, visibleRows.rows], + ); + + const getSelectedCellsAsArray = React.useCallback< + GridCellSelectionApi['unstable_getSelectedCellsAsArray'] + >(() => { + const model = apiRef.current.unstable_getCellSelectionModel(); + const idToIdLookup = gridRowsDataRowIdToIdLookupSelector(apiRef); + + return Object.entries(model).reduce<{ id: GridRowId; field: string }[]>( + (acc, [id, fields]) => [ + ...acc, + ...Object.entries(fields).reduce<{ id: GridRowId; field: string }[]>( + (acc2, [field, isSelected]) => { + return isSelected ? [...acc2, { id: idToIdLookup[id], field }] : acc2; + }, + [], + ), + ], + [], + ); + }, [apiRef]); + + const cellSelectionApi: GridCellSelectionApi = { + unstable_isCellSelected: isCellSelected, + unstable_getCellSelectionModel: getCellSelectionModel, + unstable_setCellSelectionModel: setCellSelectionModel, + unstable_selectCellRange: selectCellRange, + unstable_getSelectedCellsAsArray: getSelectedCellsAsArray, + }; + + useGridApiMethod(apiRef, cellSelectionApi, 'public'); + + const handleCellMouseDown = React.useCallback>( + (params, event) => { + // Skip if the click comes from the right-button or, only on macOS, Ctrl is pressed + // Fix for https://github.com/mui/mui-x/pull/6567#issuecomment-1329155578 + const isMacOs = window.navigator.platform.toUpperCase().indexOf('MAC') >= 0; + if (event.button !== 0 || (event.ctrlKey && isMacOs)) { + return; + } + + lastMouseDownCell.current = { id: params.id, field: params.field }; + apiRef.current.rootElementRef?.current?.classList.add( + gridClasses['root--disableUserSelection'], + ); + }, + [apiRef], + ); + + const handleCellMouseUp = React.useCallback>(() => { + lastMouseDownCell.current = null; + apiRef.current.rootElementRef?.current?.classList.remove( + gridClasses['root--disableUserSelection'], + ); + }, [apiRef]); + + const handleCellMouseOver = React.useCallback>( + (params, event) => { + if (!lastMouseDownCell.current) { + return; + } + + const { id, field } = params; + + apiRef.current.unstable_selectCellRange( + lastMouseDownCell.current, + { id, field }, + event.ctrlKey || event.metaKey, + ); + }, + [apiRef], + ); + + const handleCellClick = useEventCallback< + [GridEventLookup['cellClick']['params'], GridEventLookup['cellClick']['event']], + void + >((params, event) => { + const { id, field } = params; + + if (params.field === GRID_CHECKBOX_SELECTION_COL_DEF.field) { + return; + } + + if (params.field === GRID_DETAIL_PANEL_TOGGLE_FIELD) { + return; + } + + const column = apiRef.current.getColumn(params.field); + if (column.type === GRID_ACTIONS_COLUMN_TYPE) { + return; + } + + if (params.rowNode.type === 'pinnedRow') { + return; + } + + if (event.shiftKey && lastClickedCell.current) { + apiRef.current.unstable_selectCellRange(lastClickedCell.current, { id, field }); + lastClickedCell.current = { id, field }; + return; + } + + lastClickedCell.current = { id, field }; + + if (event.ctrlKey || event.metaKey) { + // Add the clicked cell to the selection + const prevModel = apiRef.current.unstable_getCellSelectionModel(); + apiRef.current.unstable_setCellSelectionModel({ + ...prevModel, + [id]: { ...prevModel[id], [field]: !apiRef.current.unstable_isCellSelected(id, field) }, + }); + } else { + // Clear the selection and keep only the clicked cell selected + apiRef.current.unstable_setCellSelectionModel({ [id]: { [field]: true } }); + } + }); + + const handleCellKeyDown = useEventCallback< + [GridEventLookup['cellKeyDown']['params'], GridEventLookup['cellKeyDown']['event']], + void + >((params, event) => { + const { id, field } = params; + + if (event.key === 'Shift') { + focusedCellWhenShiftWasPressed.current = { id, field }; + return; + } + + if (!focusedCellWhenShiftWasPressed.current || !isNavigationKey(event.key) || !event.shiftKey) { + return; + } + + let endRowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(id); + let endColumnIndex = apiRef.current.getColumnIndex(field); + + if (event.key === 'ArrowDown') { + endRowIndex += 1; + } else if (event.key === 'ArrowUp') { + endRowIndex -= 1; + } else if (event.key === 'ArrowRight') { + endColumnIndex += 1; + } else if (event.key === 'ArrowLeft') { + endColumnIndex -= 1; + } + + if (endRowIndex < 0 || endRowIndex >= visibleRows.rows.length) { + return; + } + + const visibleColumns = apiRef.current.getVisibleColumns(); + if (endColumnIndex < 0 || endColumnIndex >= visibleColumns.length) { + return; + } + + apiRef.current.unstable_selectCellRange(focusedCellWhenShiftWasPressed.current, { + id: visibleRows.rows[endRowIndex].id, + field: visibleColumns[endColumnIndex].field, + }); + }); + + const handleCellKeyUp = useEventCallback< + [GridEventLookup['cellKeyUp']['params'], GridEventLookup['cellKeyUp']['event']], + void + >((params, event) => { + if (event.key === 'Shift') { + focusedCellWhenShiftWasPressed.current = null; + } + }); + + useGridApiEventHandler(apiRef, 'cellClick', runIfCellSelectionIsEnabled(handleCellClick)); + useGridApiEventHandler(apiRef, 'cellKeyDown', runIfCellSelectionIsEnabled(handleCellKeyDown)); + useGridApiEventHandler(apiRef, 'cellKeyUp', runIfCellSelectionIsEnabled(handleCellKeyUp)); + useGridApiEventHandler(apiRef, 'cellMouseDown', runIfCellSelectionIsEnabled(handleCellMouseDown)); + useGridApiEventHandler(apiRef, 'cellMouseUp', runIfCellSelectionIsEnabled(handleCellMouseUp)); + useGridApiEventHandler(apiRef, 'cellMouseOver', runIfCellSelectionIsEnabled(handleCellMouseOver)); + + React.useEffect(() => { + if (props.unstable_cellSelectionModel) { + apiRef.current.unstable_setCellSelectionModel(props.unstable_cellSelectionModel); + } + }, [apiRef, props.unstable_cellSelectionModel]); + + const checkIfCellIsSelected = React.useCallback>( + (isSelected, { id, field }) => { + return apiRef.current.unstable_isCellSelected(id, field); + }, + [apiRef], + ); + + const addClassesToCells = React.useCallback>( + (classes, { id, field }) => { + const newClasses = [...classes]; + + if (!visibleRows.range || !apiRef.current.unstable_isCellSelected(id, field)) { + return classes; + } + + const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(id); + const columnIndex = apiRef.current.getColumnIndex(field); + const visibleColumns = apiRef.current.getVisibleColumns(); + + if (rowIndex > 0) { + const { id: previousRowId } = visibleRows.rows[rowIndex - 1]; + if (!apiRef.current.unstable_isCellSelected(previousRowId, field)) { + newClasses.push(gridClasses['cell--rangeTop']); + } + } else { + newClasses.push(gridClasses['cell--rangeTop']); + } + + if (rowIndex < visibleRows.range.lastRowIndex) { + const { id: nextRowId } = visibleRows.rows[rowIndex + 1]; + if (!apiRef.current.unstable_isCellSelected(nextRowId, field)) { + newClasses.push(gridClasses['cell--rangeBottom']); + } + } else { + newClasses.push(gridClasses['cell--rangeBottom']); + } + + if (columnIndex > 0) { + const { field: previousColumnField } = visibleColumns[columnIndex - 1]; + if (!apiRef.current.unstable_isCellSelected(id, previousColumnField)) { + newClasses.push(gridClasses['cell--rangeLeft']); + } + } else { + newClasses.push(gridClasses['cell--rangeLeft']); + } + + if (columnIndex < visibleColumns.length - 1) { + const { field: nextColumnField } = visibleColumns[columnIndex + 1]; + if (!apiRef.current.unstable_isCellSelected(id, nextColumnField)) { + newClasses.push(gridClasses['cell--rangeRight']); + } + } else { + newClasses.push(gridClasses['cell--rangeRight']); + } + + return newClasses; + }, + [apiRef, visibleRows.range, visibleRows.rows], + ); + + useGridRegisterPipeProcessor(apiRef, 'isCellSelected', checkIfCellIsSelected); + useGridRegisterPipeProcessor(apiRef, 'cellClassName', addClassesToCells); +}; diff --git a/packages/grid/x-data-grid-premium/src/hooks/features/index.ts b/packages/grid/x-data-grid-premium/src/hooks/features/index.ts index 0661b353b530..c35c495dc027 100644 --- a/packages/grid/x-data-grid-premium/src/hooks/features/index.ts +++ b/packages/grid/x-data-grid-premium/src/hooks/features/index.ts @@ -2,3 +2,4 @@ export * from './aggregation'; export * from './rowGrouping'; export * from './export'; +export * from './cellSelection'; diff --git a/packages/grid/x-data-grid-premium/src/models/dataGridPremiumProps.ts b/packages/grid/x-data-grid-premium/src/models/dataGridPremiumProps.ts index 4b633f089ac0..9611cdae67d6 100644 --- a/packages/grid/x-data-grid-premium/src/models/dataGridPremiumProps.ts +++ b/packages/grid/x-data-grid-premium/src/models/dataGridPremiumProps.ts @@ -15,6 +15,7 @@ import type { } from '../hooks/features/aggregation'; import { GridInitialStatePremium } from './gridStatePremium'; import { GridApiPremium } from './gridApiPremium'; +import { GridCellSelectionModel } from '../hooks/features/cellSelection'; export interface GridExperimentalPremiumFeatures extends GridExperimentalProFeatures {} @@ -51,6 +52,11 @@ export type DataGridPremiumForcedPropsKey = 'signature'; * The controlled model do not have a default value at the prop processing level, so they must be defined in `DataGridOtherProps`. */ export interface DataGridPremiumPropsWithDefaultValue extends DataGridProPropsWithDefaultValue { + /** + * If `true`, the cell selection mode is enabled. + * @default false + */ + unstable_cellSelection: boolean; /** * If `true`, aggregation is disabled. * @default false @@ -120,4 +126,17 @@ export interface DataGridPremiumPropsWithoutDefaultValue void; + /** + * Set the cell selection model of the grid. + */ + unstable_cellSelectionModel?: GridCellSelectionModel; + /** + * Callback fired when the selection state of one or multiple cells changes. + * @param {GridCellSelectionModel} cellSelectionModel Object in the shape of [[GridCellSelectionModel]] containg the selected cells. + * @param {GridCallbackDetails} details Additional details for this callback. + */ + unstable_onCellSelectionModelChange?: ( + cellSelectionModel: GridCellSelectionModel, + details: GridCallbackDetails, + ) => void; } diff --git a/packages/grid/x-data-grid-premium/src/models/gridApiPremium.ts b/packages/grid/x-data-grid-premium/src/models/gridApiPremium.ts index 64c1182289d3..8ccfe46b00a0 100644 --- a/packages/grid/x-data-grid-premium/src/models/gridApiPremium.ts +++ b/packages/grid/x-data-grid-premium/src/models/gridApiPremium.ts @@ -9,6 +9,7 @@ import { } from '@mui/x-data-grid-pro'; import { GridInitialStatePremium, GridStatePremium } from './gridStatePremium'; import type { GridRowGroupingApi, GridExcelExportApi, GridAggregationApi } from '../hooks'; +import { GridCellSelectionApi } from '../hooks/features/cellSelection/gridCellSelectionInterfaces'; /** * The api of `DataGridPremium`. @@ -22,6 +23,7 @@ export interface GridApiPremium GridExcelExportApi, GridAggregationApi, GridRowPinningApi, + GridCellSelectionApi, // it's private in Community plan, but public in Pro and Premium plans GridRowMultiSelectionApi {} diff --git a/packages/grid/x-data-grid-premium/src/models/gridStatePremium.ts b/packages/grid/x-data-grid-premium/src/models/gridStatePremium.ts index 610b8b0637dc..c3bee2ce7b09 100644 --- a/packages/grid/x-data-grid-premium/src/models/gridStatePremium.ts +++ b/packages/grid/x-data-grid-premium/src/models/gridStatePremium.ts @@ -7,6 +7,7 @@ import type { GridRowGroupingInitialState, GridAggregationState, GridAggregationInitialState, + GridCellSelectionModel, } from '../hooks'; /** @@ -15,6 +16,7 @@ import type { export interface GridStatePremium extends GridStatePro { rowGrouping: GridRowGroupingState; aggregation: GridAggregationState; + cellSelection: GridCellSelectionModel; } /** @@ -23,4 +25,5 @@ export interface GridStatePremium extends GridStatePro { export interface GridInitialStatePremium extends GridInitialStatePro { rowGrouping?: GridRowGroupingInitialState; aggregation?: GridAggregationInitialState; + cellSelection?: GridCellSelectionModel; } diff --git a/packages/grid/x-data-grid-premium/src/tests/cellSelection.DataGridPremium.test.tsx b/packages/grid/x-data-grid-premium/src/tests/cellSelection.DataGridPremium.test.tsx new file mode 100644 index 000000000000..41e393cdc22d --- /dev/null +++ b/packages/grid/x-data-grid-premium/src/tests/cellSelection.DataGridPremium.test.tsx @@ -0,0 +1,306 @@ +import * as React from 'react'; +import { spy } from 'sinon'; +import { expect } from 'chai'; +import { getCell } from 'test/utils/helperFn'; +// @ts-ignore Remove once the test utils are typed +import { createRenderer, fireEvent, act } from '@mui/monorepo/test/utils'; +import { + DataGridPremium, + DataGridPremiumProps, + GridApi, + useGridApiRef, + gridClasses, +} from '@mui/x-data-grid-premium'; +import { getBasicGridData } from '@mui/x-data-grid-generator'; + +describe(' - Cell Selection', () => { + const { render } = createRenderer(); + + let apiRef: React.MutableRefObject; + + function TestDataGridSelection({ + rowLength = 4, + ...other + }: Omit & + Partial> & { rowLength?: number }) { + apiRef = useGridApiRef(); + + const data = React.useMemo(() => getBasicGridData(rowLength, 3), [rowLength]); + + return ( +
+ +
+ ); + } + + it('should select the cell clicked', () => { + render(); + expect(document.querySelector('.Mui-selected')).to.equal(null); + const cell = getCell(0, 1); + fireEvent.click(cell); + expect(document.querySelector('.Mui-selected')).to.equal(cell); + }); + + it('should unselect already selected cells when selecting a cell', () => { + render(); + const cell01 = getCell(0, 1); + fireEvent.click(cell01); + expect(cell01).to.have.class('Mui-selected'); + const cell11 = getCell(1, 1); + fireEvent.click(cell11); + expect(cell01).not.to.have.class('Mui-selected'); + expect(cell11).to.have.class('Mui-selected'); + }); + + describe('Ctrl + click', () => { + it('should add the clicked cells to the selection', () => { + render(); + expect(document.querySelector('.Mui-selected')).to.equal(null); + const cell11 = getCell(1, 1); + fireEvent.click(cell11); + expect(cell11).to.have.class('Mui-selected'); + const cell21 = getCell(2, 1); + fireEvent.click(cell21, { ctrlKey: true }); + expect(cell21).to.have.class('Mui-selected'); + expect(cell11).to.have.class('Mui-selected'); + }); + + it('should unselect the cell if the cell is already selected', () => { + render(); + expect(document.querySelector('.Mui-selected')).to.equal(null); + const cell = getCell(1, 1); + fireEvent.click(cell); + expect(cell).to.have.class('Mui-selected'); + fireEvent.click(cell, { ctrlKey: true }); + expect(cell).not.to.have.class('Mui-selected'); + }); + }); + + describe('Shift + click', () => { + it('should select all cells between two cells', () => { + render(); + expect(document.querySelector('.Mui-selected')).to.equal(null); + const cell = getCell(0, 0); + cell.focus(); + fireEvent.click(cell); + fireEvent.keyDown(cell, { key: 'Shift' }); + fireEvent.click(getCell(2, 1), { shiftKey: true }); + expect(document.querySelectorAll('.Mui-selected')).to.have.length(3 * 2); // 3 rows with 2 cells each + }); + + it('should consider as the origin for a new range the cell focused when Shift is pressed', () => { + render(); + const cell00 = getCell(0, 0); + cell00.focus(); + fireEvent.click(cell00); + fireEvent.keyDown(cell00, { key: 'Shift' }); + const cell01 = getCell(0, 1); + fireEvent.click(cell01, { shiftKey: true }); + cell01.focus(); + fireEvent.keyDown(cell01, { key: 'Shift' }); + expect(cell00).to.have.class('Mui-selected'); + expect(cell01).to.have.class('Mui-selected'); + const cell11 = getCell(1, 1); + fireEvent.click(cell11, { shiftKey: true }); + expect(cell00).not.to.have.class('Mui-selected'); + expect(cell01).to.have.class('Mui-selected'); + expect(cell11).to.have.class('Mui-selected'); + }); + + it('should call selectCellRange', () => { + render(); + const spiedSelectCellsBetweenRange = spy(apiRef.current, 'unstable_selectCellRange'); + const cell = getCell(0, 0); + cell.focus(); + fireEvent.click(cell); + fireEvent.keyDown(cell, { key: 'Shift' }); + fireEvent.click(getCell(2, 1), { shiftKey: true }); + expect(spiedSelectCellsBetweenRange.lastCall.args[0]).to.deep.equal({ id: 0, field: 'id' }); + expect(spiedSelectCellsBetweenRange.lastCall.args[1]).to.deep.equal({ + id: 2, + field: 'currencyPair', + }); + }); + + it('should add classes to the cells that are at the corners of a range', () => { + render(); + const cell = getCell(0, 0); + cell.focus(); + fireEvent.click(cell); + fireEvent.keyDown(cell, { key: 'Shift' }); + fireEvent.click(getCell(2, 2), { shiftKey: true }); + + expect(getCell(0, 0)).to.have.class(gridClasses['cell--rangeTop']); + expect(getCell(0, 0)).to.have.class(gridClasses['cell--rangeLeft']); + expect(getCell(0, 1)).to.have.class(gridClasses['cell--rangeTop']); + expect(getCell(0, 2)).to.have.class(gridClasses['cell--rangeRight']); + expect(getCell(0, 2)).to.have.class(gridClasses['cell--rangeTop']); + + expect(getCell(1, 0)).to.have.class(gridClasses['cell--rangeLeft']); + expect(getCell(1, 2)).to.have.class(gridClasses['cell--rangeRight']); + + expect(getCell(2, 0)).to.have.class(gridClasses['cell--rangeBottom']); + expect(getCell(2, 0)).to.have.class(gridClasses['cell--rangeLeft']); + expect(getCell(2, 1)).to.have.class(gridClasses['cell--rangeBottom']); + expect(getCell(2, 2)).to.have.class(gridClasses['cell--rangeRight']); + expect(getCell(2, 2)).to.have.class(gridClasses['cell--rangeBottom']); + }); + }); + + describe('Shift + arrow keys', () => { + it('should call selectCellRange when ArrowDown is pressed', () => { + render(); + const spiedSelectCellsBetweenRange = spy(apiRef.current, 'unstable_selectCellRange'); + const cell = getCell(0, 0); + cell.focus(); + fireEvent.click(cell); + fireEvent.keyDown(cell, { key: 'Shift' }); + fireEvent.keyDown(cell, { key: 'ArrowDown', shiftKey: true }); + expect(spiedSelectCellsBetweenRange.lastCall.args[0]).to.deep.equal({ id: 0, field: 'id' }); + expect(spiedSelectCellsBetweenRange.lastCall.args[1]).to.deep.equal({ id: 1, field: 'id' }); + }); + + it('should call selectCellRange when ArrowUp is pressed', () => { + render(); + const spiedSelectCellsBetweenRange = spy(apiRef.current, 'unstable_selectCellRange'); + const cell = getCell(1, 0); + cell.focus(); + fireEvent.click(cell); + fireEvent.keyDown(cell, { key: 'Shift' }); + fireEvent.keyDown(cell, { key: 'ArrowUp', shiftKey: true }); + expect(spiedSelectCellsBetweenRange.lastCall.args[0]).to.deep.equal({ id: 1, field: 'id' }); + expect(spiedSelectCellsBetweenRange.lastCall.args[1]).to.deep.equal({ id: 0, field: 'id' }); + }); + + it('should call selectCellRange when ArrowLeft is pressed', () => { + render(); + const spiedSelectCellsBetweenRange = spy(apiRef.current, 'unstable_selectCellRange'); + const cell = getCell(0, 1); + cell.focus(); + fireEvent.click(cell); + fireEvent.keyDown(cell, { key: 'Shift' }); + fireEvent.keyDown(cell, { key: 'ArrowLeft', shiftKey: true }); + expect(spiedSelectCellsBetweenRange.lastCall.args[0]).to.deep.equal({ + id: 0, + field: 'currencyPair', + }); + expect(spiedSelectCellsBetweenRange.lastCall.args[1]).to.deep.equal({ id: 0, field: 'id' }); + }); + + it('should call selectCellRange when ArrowRight is pressed', () => { + render(); + const spiedSelectCellsBetweenRange = spy(apiRef.current, 'unstable_selectCellRange'); + const cell = getCell(0, 0); + cell.focus(); + fireEvent.click(cell); + fireEvent.keyDown(cell, { key: 'Shift' }); + fireEvent.keyDown(cell, { key: 'ArrowRight', shiftKey: true }); + expect(spiedSelectCellsBetweenRange.lastCall.args[0]).to.deep.equal({ id: 0, field: 'id' }); + expect(spiedSelectCellsBetweenRange.lastCall.args[1]).to.deep.equal({ + id: 0, + field: 'currencyPair', + }); + }); + }); + + describe('apiRef', () => { + describe('unstable_selectCellRange', () => { + it('should select all cells within the given arguments if end > start', () => { + render(); + act(() => + apiRef.current.unstable_selectCellRange( + { id: 0, field: 'id' }, + { id: 2, field: 'price1M' }, + ), + ); + + expect(getCell(0, 0)).to.have.class('Mui-selected'); + expect(getCell(0, 1)).to.have.class('Mui-selected'); + expect(getCell(0, 2)).to.have.class('Mui-selected'); + + expect(getCell(1, 0)).to.have.class('Mui-selected'); + expect(getCell(1, 1)).to.have.class('Mui-selected'); + expect(getCell(1, 2)).to.have.class('Mui-selected'); + + expect(getCell(2, 0)).to.have.class('Mui-selected'); + expect(getCell(2, 1)).to.have.class('Mui-selected'); + expect(getCell(2, 2)).to.have.class('Mui-selected'); + }); + + it('should select all cells within the given arguments if start > end', () => { + render(); + act(() => + apiRef.current.unstable_selectCellRange( + { id: 0, field: 'id' }, + { id: 2, field: 'price1M' }, + ), + ); + + expect(getCell(0, 0)).to.have.class('Mui-selected'); + expect(getCell(0, 1)).to.have.class('Mui-selected'); + expect(getCell(0, 2)).to.have.class('Mui-selected'); + + expect(getCell(1, 0)).to.have.class('Mui-selected'); + expect(getCell(1, 1)).to.have.class('Mui-selected'); + expect(getCell(1, 2)).to.have.class('Mui-selected'); + + expect(getCell(2, 0)).to.have.class('Mui-selected'); + expect(getCell(2, 1)).to.have.class('Mui-selected'); + expect(getCell(2, 2)).to.have.class('Mui-selected'); + }); + + it('should discard previously selected cells and keep only the ones inside the range', () => { + render( + , + ); + + expect(getCell(0, 0)).to.have.class('Mui-selected'); + expect(getCell(0, 1)).to.have.class('Mui-selected'); + expect(getCell(0, 2)).to.have.class('Mui-selected'); + + act(() => + apiRef.current.unstable_selectCellRange( + { id: 1, field: 'id' }, + { id: 2, field: 'price1M' }, + ), + ); + + expect(getCell(0, 0)).not.to.have.class('Mui-selected'); + expect(getCell(0, 1)).not.to.have.class('Mui-selected'); + expect(getCell(0, 2)).not.to.have.class('Mui-selected'); + + expect(getCell(1, 0)).to.have.class('Mui-selected'); + expect(getCell(1, 1)).to.have.class('Mui-selected'); + expect(getCell(1, 2)).to.have.class('Mui-selected'); + + expect(getCell(2, 0)).to.have.class('Mui-selected'); + expect(getCell(2, 1)).to.have.class('Mui-selected'); + expect(getCell(2, 2)).to.have.class('Mui-selected'); + }); + }); + + describe('getSelectedCellsAsArray', () => { + it('should return the selected cells as an array', () => { + render( + , + ); + expect(apiRef.current.unstable_getSelectedCellsAsArray()).to.deep.equal([ + { id: 0, field: 'id' }, + { id: 0, field: 'currencyPair' }, + ]); + }); + }); + }); +}); diff --git a/packages/grid/x-data-grid-premium/src/typeOverloads/modules.ts b/packages/grid/x-data-grid-premium/src/typeOverloads/modules.ts index 639df7d02ef6..f129668d3ff3 100644 --- a/packages/grid/x-data-grid-premium/src/typeOverloads/modules.ts +++ b/packages/grid/x-data-grid-premium/src/typeOverloads/modules.ts @@ -9,6 +9,7 @@ import type { GridAggregationModel, GridAggregationCellMeta, GridAggregationHeaderMeta, + GridCellSelectionModel, } from '../hooks'; import { GridRowGroupingInternalCache } from '../hooks/features/rowGrouping/gridRowGroupingInterfaces'; import { GridAggregationInternalCache } from '../hooks/features/aggregation/gridAggregationInterfaces'; @@ -22,6 +23,10 @@ export interface GridControlledStateEventLookupPremium { * Fired when the row grouping model changes. */ rowGroupingModelChange: { params: GridRowGroupingModel }; + /** + * Fired when the selection state of one or multiple cells change. + */ + cellSelectionChange: { params: GridCellSelectionModel }; } // eslint-disable-next-line @typescript-eslint/no-unused-vars diff --git a/packages/grid/x-data-grid/src/components/GridRow.tsx b/packages/grid/x-data-grid/src/components/GridRow.tsx index 0ce6ab47dc10..2122ee0734f1 100644 --- a/packages/grid/x-data-grid/src/components/GridRow.tsx +++ b/packages/grid/x-data-grid/src/components/GridRow.tsx @@ -273,7 +273,10 @@ const GridRow = React.forwardRef< column.field, ); - const classNames: string[] = []; + const classNames = apiRef.current.unstable_applyPipeProcessors('cellClassName', [], { + id: rowId, + field: column.field, + }); const disableDragEvents = (rootProps.disableColumnReorder && column.disableReorder) || @@ -340,6 +343,11 @@ const GridRow = React.forwardRef< ? 0 : -1; + const isSelected = apiRef.current.unstable_applyPipeProcessors('isCellSelected', false, { + id: rowId, + field: column.field, + }); + return ( { hasFocus?: boolean; height: number | 'auto'; isEditable?: boolean; + isSelected?: boolean; showRightBorder?: boolean; value?: V; width: number; @@ -64,18 +65,19 @@ function doesSupportPreventScroll(): boolean { return cachedSupportsPreventScroll; } -type OwnerState = Pick & { +type OwnerState = Pick & { classes?: DataGridProcessedProps['classes']; }; const useUtilityClasses = (ownerState: OwnerState) => { - const { align, showRightBorder, isEditable, classes } = ownerState; + const { align, showRightBorder, isEditable, isSelected, classes } = ownerState; const slots = { root: [ 'cell', `cell--text${capitalize(align)}`, isEditable && 'cell--editable', + isSelected && 'selected', showRightBorder && 'cell--withRightBorder', 'withBorderColor', ], @@ -99,6 +101,7 @@ function GridCell(props: GridCellProps) { hasFocus, height, isEditable, + isSelected, rowId, tabIndex, value, @@ -113,7 +116,9 @@ function GridCell(props: GridCellProps) { onDoubleClick, onMouseDown, onMouseUp, + onMouseOver, onKeyDown, + onKeyUp, onDragEnter, onDragOver, ...other @@ -125,7 +130,7 @@ function GridCell(props: GridCellProps) { const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); - const ownerState = { align, showRightBorder, isEditable, classes: rootProps.classes }; + const ownerState = { align, showRightBorder, isEditable, classes: rootProps.classes, isSelected }; const classes = useUtilityClasses(ownerState); const publishMouseUp = React.useCallback( @@ -268,9 +273,11 @@ function GridCell(props: GridCellProps) { tabIndex={(cellMode === 'view' || !isEditable) && !managesOwnFocus ? tabIndex : -1} onClick={publish('cellClick', onClick)} onDoubleClick={publish('cellDoubleClick', onDoubleClick)} + onMouseOver={publish('cellMouseOver', onMouseOver)} onMouseDown={publishMouseDown('cellMouseDown')} onMouseUp={publishMouseUp('cellMouseUp')} onKeyDown={publish('cellKeyDown', onKeyDown)} + onKeyUp={publish('cellKeyUp', onKeyUp)} {...draggableEventHandlers} {...other} onFocus={handleFocus} @@ -297,6 +304,7 @@ GridCell.propTypes = { hasFocus: PropTypes.bool, height: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]).isRequired, isEditable: PropTypes.bool, + isSelected: PropTypes.bool, onClick: PropTypes.func, onDoubleClick: PropTypes.func, onDragEnter: PropTypes.func, diff --git a/packages/grid/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/grid/x-data-grid/src/components/containers/GridRootStyles.ts index 8f3d0e799cce..4058c3800338 100644 --- a/packages/grid/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/grid/x-data-grid/src/components/containers/GridRootStyles.ts @@ -31,6 +31,10 @@ export const GridRootStyles = styled('div', { styles['aggregationColumnHeader--alignRight'], }, { [`&.${gridClasses.aggregationColumnHeaderLabel}`]: styles.aggregationColumnHeaderLabel }, + { + [`&.${gridClasses['root--disableUserSelection']} .${gridClasses.cell}`]: + styles['root--disableUserSelection'], + }, { [`& .${gridClasses.editBooleanCell}`]: styles.editBooleanCell }, { [`& .${gridClasses['cell--editing']}`]: styles['cell--editing'] }, { [`& .${gridClasses['cell--textCenter']}`]: styles['cell--textCenter'] }, @@ -39,6 +43,10 @@ export const GridRootStyles = styled('div', { // TODO v6: Remove { [`& .${gridClasses['cell--withRenderer']}`]: styles['cell--withRenderer'] }, { [`& .${gridClasses.cell}`]: styles.cell }, + { [`& .${gridClasses['cell--rangeTop']}`]: styles['cell--rangeTop'] }, + { [`& .${gridClasses['cell--rangeBottom']}`]: styles['cell--rangeBottom'] }, + { [`& .${gridClasses['cell--rangeLeft']}`]: styles['cell--rangeLeft'] }, + { [`& .${gridClasses['cell--rangeRight']}`]: styles['cell--rangeRight'] }, { [`& .${gridClasses.cellContent}`]: styles.cellContent }, { [`& .${gridClasses.cellCheckbox}`]: styles.cellCheckbox }, { [`& .${gridClasses.cellSkeleton}`]: styles.cellSkeleton }, @@ -301,6 +309,30 @@ export const GridRootStyles = styled('div', { display: 'flex', alignItems: 'center', borderBottom: '1px solid', + '&.Mui-selected': { + backgroundColor: theme.vars + ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` + : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), + '&:hover, &.Mui-hovered': { + backgroundColor: theme.vars + ? `rgba(${theme.vars.palette.primary.mainChannel} / ${ + theme.vars.palette.action.selectedOpacity + theme.palette.action.hoverOpacity + })` + : alpha( + theme.palette.primary.main, + theme.palette.action.selectedOpacity + theme.palette.action.hoverOpacity, + ), + // Reset on touch devices, it doesn't add specificity + '@media (hover: none)': { + backgroundColor: theme.vars + ? `rgba(${theme.vars.palette.primary.mainChannel} / ${theme.vars.palette.action.selectedOpacity})` + : alpha(theme.palette.primary.main, theme.palette.action.selectedOpacity), + }, + }, + }, + }, + [`&.${gridClasses['root--disableUserSelection']} .${gridClasses.cell}`]: { + userSelect: 'none', }, [`& .${gridClasses.row}:not(.${gridClasses['row--dynamicHeight']}) > .${gridClasses.cell}`]: { overflow: 'hidden', diff --git a/packages/grid/x-data-grid/src/constants/gridClasses.ts b/packages/grid/x-data-grid/src/constants/gridClasses.ts index 5b600eac2572..d6394c964e96 100644 --- a/packages/grid/x-data-grid/src/constants/gridClasses.ts +++ b/packages/grid/x-data-grid/src/constants/gridClasses.ts @@ -60,6 +60,22 @@ export interface GridClasses { * Styles applied to the cell element if the cell has a custom renderer. */ 'cell--withRenderer': string; + /** + * Styles applied to the cell element if it is at the top edge of a cell selection range. + */ + 'cell--rangeTop': string; + /** + * Styles applied to the cell element if it is at the bottom edge of a cell selection range. + */ + 'cell--rangeBottom': string; + /** + * Styles applied to the cell element if it is at the left edge of a cell selection range. + */ + 'cell--rangeLeft': string; + /** + * Styles applied to the cell element if it is at the right edge of a cell selection range. + */ + 'cell--rangeRight': string; /** * Styles applied to the cell element. */ @@ -388,6 +404,10 @@ export interface GridClasses { * Styles applied to the root element if density is "compact". */ 'root--densityCompact': string; + /** + * Styles applied to the root element when user selection is disabled. + */ + 'root--disableUserSelection': string; /** * Styles applied to the row element if the row is editable. */ @@ -528,6 +548,10 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [ 'cell--textLeft', 'cell--textRight', 'cell--withRenderer', + 'cell--rangeTop', + 'cell--rangeBottom', + 'cell--rangeLeft', + 'cell--rangeRight', 'cell', 'cellContent', 'cellCheckbox', @@ -597,6 +621,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [ 'root--densityStandard', 'root--densityComfortable', 'root--densityCompact', + 'root--disableUserSelection', 'row', 'row--editable', 'row--editing', diff --git a/packages/grid/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts b/packages/grid/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts index 442d2695d952..a940b44cdddb 100644 --- a/packages/grid/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts +++ b/packages/grid/x-data-grid/src/hooks/core/pipeProcessing/gridPipeProcessingApi.ts @@ -1,5 +1,10 @@ import * as React from 'react'; -import { GridCellIndexCoordinates, GridScrollParams, GridColDef } from '../../../models'; +import { + GridCellIndexCoordinates, + GridScrollParams, + GridColDef, + GridCellCoordinates, +} from '../../../models'; import { GridInitialStateCommunity } from '../../../models/gridStateCommunity'; import { GridExportStateParams, @@ -37,6 +42,8 @@ export interface GridPipeProcessingLookup { value: string[]; context: GridRowId; }; + cellClassName: { value: string[]; context: GridCellCoordinates }; + isCellSelected: { value: boolean; context: GridCellCoordinates }; } export type GridPipeProcessor

= ( diff --git a/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts b/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts index 246e8e60bfc0..0e5c9053831a 100644 --- a/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts +++ b/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts @@ -239,6 +239,13 @@ export interface GridCellEventLookup { params: GridCellParams; event: React.MouseEvent; }; + /** + * Fired when a `mouseover` event happens in a cell. + */ + cellMouseOver: { + params: GridCellParams; + event: React.MouseEvent; + }; /** * Fired when a `keydown` event happens in a cell. */ @@ -246,6 +253,13 @@ export interface GridCellEventLookup { params: GridCellParams; event: React.KeyboardEvent; }; + /** + * Fired when a `keyup` event happens in a cell. + */ + cellKeyUp: { + params: GridCellParams; + event: React.KeyboardEvent; + }; /** * Fired when the dragged cell enters a valid drop target. It's mapped to the `dragend` DOM event. * @ignore - do not document. diff --git a/packages/grid/x-data-grid/src/models/gridCell.ts b/packages/grid/x-data-grid/src/models/gridCell.ts index 223c46ed387a..74bf5d2aa3a4 100644 --- a/packages/grid/x-data-grid/src/models/gridCell.ts +++ b/packages/grid/x-data-grid/src/models/gridCell.ts @@ -1,3 +1,6 @@ +import type { GridColDef } from './colDef'; +import { GridRowId } from './gridRows'; + /** * The mode of the cell. */ @@ -16,6 +19,14 @@ export interface GridCellIndexCoordinates { rowIndex: number; } +/** + * The coordinates of a cell represented by their row ID and column field. + */ +export interface GridCellCoordinates { + id: GridRowId; + field: GridColDef['field']; +} + /** * The coordinates of column header represented by their row and column indexes. */ diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index f084c94ef9d6..7cdcf0459baa 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -101,6 +101,7 @@ { "name": "GridCellCheckboxRenderer", "kind": "Variable" }, { "name": "GridCellClassFn", "kind": "TypeAlias" }, { "name": "GridCellClassNamePropType", "kind": "TypeAlias" }, + { "name": "GridCellCoordinates", "kind": "Interface" }, { "name": "GridCellEditStartParams", "kind": "Interface" }, { "name": "GridCellEditStartReasons", "kind": "Enum" }, { "name": "GridCellEditStopParams", "kind": "Interface" }, @@ -113,6 +114,8 @@ { "name": "GridCellModesModel", "kind": "TypeAlias" }, { "name": "GridCellParams", "kind": "Interface" }, { "name": "GridCellProps", "kind": "Interface" }, + { "name": "GridCellSelectionApi", "kind": "Interface" }, + { "name": "GridCellSelectionModel", "kind": "TypeAlias" }, { "name": "GridCheckCircleIcon", "kind": "Variable" }, { "name": "GridCheckIcon", "kind": "Variable" }, { "name": "GridChildrenFromPathLookup", "kind": "TypeAlias" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 25b8e1c43e26..fbc7aa9cff52 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -79,6 +79,7 @@ { "name": "GridCellCheckboxRenderer", "kind": "Variable" }, { "name": "GridCellClassFn", "kind": "TypeAlias" }, { "name": "GridCellClassNamePropType", "kind": "TypeAlias" }, + { "name": "GridCellCoordinates", "kind": "Interface" }, { "name": "GridCellEditStartParams", "kind": "Interface" }, { "name": "GridCellEditStartReasons", "kind": "Enum" }, { "name": "GridCellEditStopParams", "kind": "Interface" }, diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index c3da8bc969fe..2510259a2aea 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -72,6 +72,7 @@ { "name": "GridCellCheckboxRenderer", "kind": "Variable" }, { "name": "GridCellClassFn", "kind": "TypeAlias" }, { "name": "GridCellClassNamePropType", "kind": "TypeAlias" }, + { "name": "GridCellCoordinates", "kind": "Interface" }, { "name": "GridCellEditStartParams", "kind": "Interface" }, { "name": "GridCellEditStartReasons", "kind": "Enum" }, { "name": "GridCellEditStopParams", "kind": "Interface" },