-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[DataGridPremium] Add support for cell selection #6567
Changes from 9 commits
6561c62
7a71703
d0c09e7
d4898de
7342d4d
f1b0923
fe8f09f
77d4b74
de126a9
6ca04ac
c3d45b0
94b09ff
4ab03f8
5c99cdb
4aea302
fe3166c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div style={{ width: '100%' }}> | ||
<Stack sx={{ mb: 1 }} direction="row" spacing={2}> | ||
<TextField | ||
label="Selected cell value" | ||
disabled={numberOfSelectedCells === 0} | ||
value={value} | ||
onChange={handleValueChange} | ||
fullWidth | ||
/> | ||
<Button disabled={numberOfSelectedCells === 0} onClick={updateSelectedCells}> | ||
Update selected cells | ||
</Button> | ||
</Stack> | ||
<div style={{ height: 400 }}> | ||
<DataGridPremium | ||
apiRef={apiRef} | ||
rowSelection={false} | ||
unstable_cellSelectionModel={cellSelectionModel} | ||
unstable_onCellSelectionModelChange={handleCellSelectionModelChange} | ||
unstable_cellSelection | ||
{...data} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<GridCellSelectionModel>({}); | ||
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<HTMLInputElement>) => { | ||
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 ( | ||
<div style={{ width: '100%' }}> | ||
<Stack sx={{ mb: 1 }} direction="row" spacing={2}> | ||
<TextField | ||
label="Selected cell value" | ||
disabled={numberOfSelectedCells === 0} | ||
value={value} | ||
onChange={handleValueChange} | ||
fullWidth | ||
/> | ||
<Button disabled={numberOfSelectedCells === 0} onClick={updateSelectedCells}> | ||
Update selected cells | ||
</Button> | ||
</Stack> | ||
<div style={{ height: 400 }}> | ||
<DataGridPremium | ||
apiRef={apiRef} | ||
rowSelection={false} | ||
unstable_cellSelectionModel={cellSelectionModel} | ||
unstable_onCellSelectionModelChange={handleCellSelectionModelChange} | ||
unstable_cellSelection | ||
{...data} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div style={{ width: '100%' }}> | ||
<Button sx={{ mb: 2 }} onClick={() => setRowSelection(!rowSelection)}> | ||
Toggle row selection | ||
</Button> | ||
<div style={{ height: 400 }}> | ||
<DataGridPremium | ||
rowSelection={rowSelection} | ||
checkboxSelection={rowSelection} | ||
unstable_cellSelection | ||
{...data} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the Shift + Click range selection, is it possible to "remember" the first cell that started the range? Screen.Recording.2022-11-09.at.20.24.24.movCurrently, the grid creates new range selection started from the last click cell: Screen.Recording.2022-11-09.at.20.20.49.movThere was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the current focus logic, no. The cell with the border is the cell currently focused. When you click the first cell, then the last cell, the focused cell becomes the last clicked one. We would need to split the concept of focused cell between cell that has the focus and cell that has the border. If you create a selection by dragging, not clicking the last cell, then it behaves more like Google Spreadsheet, because the last cell was not clicked and the focus remains in the first cell. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We agreed on keeping the current behavior for now, as it requires some changes in how we manage the focused/outlined cell state. |
||
<div style={{ width: '100%' }}> | ||
<Button sx={{ mb: 2 }} onClick={() => setRowSelection(!rowSelection)}> | ||
m4theushw marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Toggle row selection | ||
</Button> | ||
<div style={{ height: 400 }}> | ||
<DataGridPremium | ||
rowSelection={rowSelection} | ||
checkboxSelection={rowSelection} | ||
unstable_cellSelection | ||
{...data} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<Button sx={{ mb: 2 }} onClick={() => setRowSelection(!rowSelection)}> | ||
Toggle row selection | ||
</Button> | ||
<div style={{ height: 400 }}> | ||
<DataGridPremium | ||
rowSelection={rowSelection} | ||
checkboxSelection={rowSelection} | ||
unstable_cellSelection | ||
{...data} | ||
/> | ||
</div> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<div style={{ height: 400, width: '100%' }}> | ||
<StyledDataGridPremium rowSelection={false} unstable_cellSelection {...data} /> | ||
</div> | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to scroll automatically when selecting cells range?
Currently, using a mouse you can only select cell range within visible cells
Screen.Recording.2022-11-09.at.20.16.22.mov
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In terms of UX, from the sources that I could benchmark:
At least, my perspective as a user, I would be curious about @gerdadesign take on this UX.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can implement this later but it's possible. It's similar to what we have for the column headers (although it doesn't work).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, there's an open issue for it #6236