diff --git a/docs/src/pages/components/data-grid/columns/ValueGetterGrid.js b/docs/src/pages/components/data-grid/columns/ValueGetterGrid.js index f93d8d163f799..49c1462b0866f 100644 --- a/docs/src/pages/components/data-grid/columns/ValueGetterGrid.js +++ b/docs/src/pages/components/data-grid/columns/ValueGetterGrid.js @@ -2,8 +2,8 @@ import * as React from 'react'; import { DataGrid } from '@material-ui/data-grid'; function getFullName(params) { - return `${params.getValue('firstName') || ''} ${ - params.getValue('lastName') || '' + return `${params.getValue(params.id, 'firstName') || ''} ${ + params.getValue(params.id, 'lastName') || '' }`; } @@ -15,8 +15,7 @@ const columns = [ headerName: 'Full name', width: 160, valueGetter: getFullName, - sortComparator: (v1, v2, cellParams1, cellParams2) => - getFullName(cellParams1).localeCompare(getFullName(cellParams2)), + sortComparator: (v1, v2) => v1.toString().localeCompare(v2.toString()), }, ]; diff --git a/docs/src/pages/components/data-grid/columns/ValueGetterGrid.tsx b/docs/src/pages/components/data-grid/columns/ValueGetterGrid.tsx index 2eb1b973de312..83953ccdd2855 100644 --- a/docs/src/pages/components/data-grid/columns/ValueGetterGrid.tsx +++ b/docs/src/pages/components/data-grid/columns/ValueGetterGrid.tsx @@ -1,9 +1,9 @@ import * as React from 'react'; -import { DataGrid, GridColDef, GridSortCellParams } from '@material-ui/data-grid'; +import { DataGrid, GridColDef, GridValueGetterParams } from '@material-ui/data-grid'; -function getFullName(params: GridSortCellParams) { - return `${params.getValue('firstName') || ''} ${ - params.getValue('lastName') || '' +function getFullName(params: GridValueGetterParams) { + return `${params.getValue(params.id, 'firstName') || ''} ${ + params.getValue(params.id, 'lastName') || '' }`; } @@ -15,8 +15,7 @@ const columns: GridColDef[] = [ headerName: 'Full name', width: 160, valueGetter: getFullName, - sortComparator: (v1, v2, cellParams1, cellParams2) => - getFullName(cellParams1).localeCompare(getFullName(cellParams2)), + sortComparator: (v1, v2) => v1!.toString().localeCompare(v2!.toString()), }, ]; diff --git a/docs/src/pages/components/data-grid/editing/CatchEditingEventsGrid.js b/docs/src/pages/components/data-grid/editing/CatchEditingEventsGrid.js index 447045d274820..4f7089c12ce29 100644 --- a/docs/src/pages/components/data-grid/editing/CatchEditingEventsGrid.js +++ b/docs/src/pages/components/data-grid/editing/CatchEditingEventsGrid.js @@ -20,7 +20,7 @@ export default function CatchEditingEventsGrid() { React.useEffect(() => { return apiRef.current.subscribeEvent(GRID_CELL_EDIT_ENTER, (params, event) => { setMessage( - `Editing cell with value: ${params.value} at row: ${params.rowIndex}, column: ${params.field}, triggered by ${event.type}.`, + `Editing cell with value: ${params.value} and row id: ${params.id}, column: ${params.field}, triggered by ${event.type}.`, ); }); }, [apiRef]); diff --git a/docs/src/pages/components/data-grid/editing/CatchEditingEventsGrid.tsx b/docs/src/pages/components/data-grid/editing/CatchEditingEventsGrid.tsx index e78846854065e..c4a6419f51dce 100644 --- a/docs/src/pages/components/data-grid/editing/CatchEditingEventsGrid.tsx +++ b/docs/src/pages/components/data-grid/editing/CatchEditingEventsGrid.tsx @@ -25,8 +25,8 @@ export default function CatchEditingEventsGrid() { GRID_CELL_EDIT_ENTER, (params: GridCellParams, event?: React.SyntheticEvent) => { setMessage( - `Editing cell with value: ${params.value} at row: ${ - params.rowIndex + `Editing cell with value: ${params.value} and row id: ${ + params.id }, column: ${params.field}, triggered by ${event!.type}.`, ); }, diff --git a/docs/src/pages/components/data-grid/editing/ValueGetterGrid.js b/docs/src/pages/components/data-grid/editing/ValueGetterGrid.js index a06d2c26d0c56..6fc4c08b6d865 100644 --- a/docs/src/pages/components/data-grid/editing/ValueGetterGrid.js +++ b/docs/src/pages/components/data-grid/editing/ValueGetterGrid.js @@ -3,8 +3,8 @@ import * as React from 'react'; import { DataGrid } from '@material-ui/data-grid'; function getFullName(params) { - return `${params.getValue('firstName') || ''} ${ - params.getValue('lastName') || '' + return `${params.getValue(params.id, 'firstName') || ''} ${ + params.getValue(params.id, 'lastName') || '' }`; } @@ -48,8 +48,7 @@ const columns = [ width: 160, editable: true, valueGetter: getFullName, - sortComparator: (v1, v2, cellParams1, cellParams2) => - getFullName(cellParams1).localeCompare(getFullName(cellParams2)), + sortComparator: (v1, v2) => v1.toString().localeCompare(v2.toString()), }, ]; diff --git a/docs/src/pages/components/data-grid/editing/ValueGetterGrid.tsx b/docs/src/pages/components/data-grid/editing/ValueGetterGrid.tsx index d2fbc8d917b47..61fe98bf2f434 100644 --- a/docs/src/pages/components/data-grid/editing/ValueGetterGrid.tsx +++ b/docs/src/pages/components/data-grid/editing/ValueGetterGrid.tsx @@ -4,12 +4,12 @@ import { DataGrid, GridColDef, GridEditCellPropsParams, - GridSortCellParams, + GridValueGetterParams, } from '@material-ui/data-grid'; -function getFullName(params: GridSortCellParams) { - return `${params.getValue('firstName') || ''} ${ - params.getValue('lastName') || '' +function getFullName(params: GridValueGetterParams) { + return `${params.getValue(params.id, 'firstName') || ''} ${ + params.getValue(params.id, 'lastName') || '' }`; } @@ -52,8 +52,7 @@ const columns: GridColDef[] = [ width: 160, editable: true, valueGetter: getFullName, - sortComparator: (v1, v2, cellParams1, cellParams2) => - getFullName(cellParams1).localeCompare(getFullName(cellParams2)), + sortComparator: (v1, v2) => v1!.toString().localeCompare(v2!.toString()), }, ]; diff --git a/docs/src/pages/components/data-grid/overview/DataGridDemo.js b/docs/src/pages/components/data-grid/overview/DataGridDemo.js index bd7016c6d1deb..b120e4792794b 100644 --- a/docs/src/pages/components/data-grid/overview/DataGridDemo.js +++ b/docs/src/pages/components/data-grid/overview/DataGridDemo.js @@ -18,7 +18,9 @@ const columns = [ sortable: false, width: 160, valueGetter: (params) => - `${params.getValue('firstName') || ''} ${params.getValue('lastName') || ''}`, + `${params.getValue(params.id, 'firstName') || ''} ${ + params.getValue(params.id, 'lastName') || '' + }`, }, ]; diff --git a/docs/src/pages/components/data-grid/overview/DataGridDemo.tsx b/docs/src/pages/components/data-grid/overview/DataGridDemo.tsx index f4fa10db58e5b..b775918b5f790 100644 --- a/docs/src/pages/components/data-grid/overview/DataGridDemo.tsx +++ b/docs/src/pages/components/data-grid/overview/DataGridDemo.tsx @@ -18,7 +18,9 @@ const columns: GridColDef[] = [ sortable: false, width: 160, valueGetter: (params: GridValueGetterParams) => - `${params.getValue('firstName') || ''} ${params.getValue('lastName') || ''}`, + `${params.getValue(params.id, 'firstName') || ''} ${ + params.getValue(params.id, 'lastName') || '' + }`, }, ]; diff --git a/docs/src/pages/components/data-grid/rows/StylingRowsGrid.js b/docs/src/pages/components/data-grid/rows/StylingRowsGrid.js index 1578f397b6eb4..775cf218b8c9e 100644 --- a/docs/src/pages/components/data-grid/rows/StylingRowsGrid.js +++ b/docs/src/pages/components/data-grid/rows/StylingRowsGrid.js @@ -56,7 +56,9 @@ export default function StylingRowsGrid() {
`super-app-theme--${params.getValue('status')}`} + getRowClassName={(params) => + `super-app-theme--${params.getValue(params.id, 'status')}` + } />
); diff --git a/docs/src/pages/components/data-grid/rows/StylingRowsGrid.tsx b/docs/src/pages/components/data-grid/rows/StylingRowsGrid.tsx index 1578f397b6eb4..775cf218b8c9e 100644 --- a/docs/src/pages/components/data-grid/rows/StylingRowsGrid.tsx +++ b/docs/src/pages/components/data-grid/rows/StylingRowsGrid.tsx @@ -56,7 +56,9 @@ export default function StylingRowsGrid() {
`super-app-theme--${params.getValue('status')}`} + getRowClassName={(params) => + `super-app-theme--${params.getValue(params.id, 'status')}` + } />
); diff --git a/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.js b/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.js index da1f16b3b11b5..eb36240339cc0 100644 --- a/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.js +++ b/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.js @@ -11,8 +11,12 @@ const columns = [ { field: 'username', valueGetter: (params) => - `${params.getValue('name') || 'unknown'} - ${params.getValue('age') || 'x'}`, - sortComparator: (v1, v2, param1, param2) => param1.row.age - param2.row.age, + `${params.getValue(params.id, 'name') || 'unknown'} - ${ + params.getValue(params.id, 'age') || 'x' + }`, + sortComparator: (v1, v2, param1, param2) => + param1.api.getCellValue(param1.id, 'age') - + param2.api.getCellValue(param2.id, 'age'), width: 150, }, { field: 'dateCreated', type: 'date', width: 180 }, diff --git a/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.tsx b/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.tsx index 77de800d57aae..ae0ad2891ad25 100644 --- a/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.tsx +++ b/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.tsx @@ -17,8 +17,12 @@ const columns: GridColumns = [ { field: 'username', valueGetter: (params: GridValueGetterParams) => - `${params.getValue('name') || 'unknown'} - ${params.getValue('age') || 'x'}`, - sortComparator: (v1, v2, param1, param2) => param1.row.age - param2.row.age, + `${params.getValue(params.id, 'name') || 'unknown'} - ${ + params.getValue(params.id, 'age') || 'x' + }`, + sortComparator: (v1, v2, param1, param2) => + param1.api.getCellValue(param1.id, 'age') - + param2.api.getCellValue(param2.id, 'age'), width: 150, }, { field: 'dateCreated', type: 'date', width: 180 }, diff --git a/packages/grid/_modules_/grid/components/GridViewport.tsx b/packages/grid/_modules_/grid/components/GridViewport.tsx index b2308beb731ae..cc9864154deff 100644 --- a/packages/grid/_modules_/grid/components/GridViewport.tsx +++ b/packages/grid/_modules_/grid/components/GridViewport.tsx @@ -7,6 +7,7 @@ import { gridFocusCellSelector, gridTabIndexCellSelector, } from '../hooks/features/focus/gridFocusStateSelector'; +import { gridEditRowsStateSelector } from '../hooks/features/rows/gridEditRowsSelector'; import { gridSelectionStateSelector } from '../hooks/features/selection/gridSelectionSelector'; import { renderStateSelector } from '../hooks/features/virtualization/renderingStateSelector'; import { optionsSelector } from '../hooks/utils/optionsSelector'; @@ -40,6 +41,7 @@ export const GridViewport: ViewportType = React.forwardRef( const selectionState = useGridSelector(apiRef, gridSelectionStateSelector); const rows = useGridSelector(apiRef, visibleSortedGridRowsAsArraySelector); const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector); + const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); const getRowsElements = () => { if (renderState.renderContext == null) { @@ -67,12 +69,15 @@ export const GridViewport: ViewportType = React.forwardRef( id={id} firstColIdx={renderState.renderContext!.firstColIdx!} lastColIdx={renderState.renderContext!.lastColIdx!} - hasScroll={{ y: scrollBarState!.hasScrollY, x: scrollBarState.hasScrollX }} + hasScrollX={scrollBarState.hasScrollX} + hasScrollY={scrollBarState.hasScrollY} showCellRightBorder={!!options.showCellRightBorder} extendRowFullWidth={!options.disableExtendRowFullWidth} rowIndex={renderState.renderContext!.firstRowIdx! + idx} cellFocus={cellFocus} cellTabIndex={cellTabIndex} + isSelected={selectionState[id] !== undefined} + editRowState={editRowsState[id]} /> diff --git a/packages/grid/_modules_/grid/components/cell/GridBooleanCell.tsx b/packages/grid/_modules_/grid/components/cell/GridBooleanCell.tsx index b49b9627367c4..f6caca5c0931c 100644 --- a/packages/grid/_modules_/grid/components/cell/GridBooleanCell.tsx +++ b/packages/grid/_modules_/grid/components/cell/GridBooleanCell.tsx @@ -2,25 +2,27 @@ import * as React from 'react'; import { SvgIconProps } from '@material-ui/core/SvgIcon'; import { GridCellParams } from '../../models/params/gridCellParams'; -export function GridBooleanCell(props: GridCellParams & SvgIconProps) { +export const GridBooleanCell = React.memo((props: GridCellParams & SvgIconProps) => { const { id, value, - element, formattedValue, api, field, row, colDef, cellMode, - getValue, - rowIndex, - colIndex, isEditable, + hasFocus, + tabIndex, + getValue, ...other } = props; - const Icon = value ? api.components.BooleanCellTrueIcon : api.components.BooleanCellFalseIcon; + const Icon = React.useMemo( + () => (value ? api.components.BooleanCellTrueIcon : api.components.BooleanCellFalseIcon), + [api.components.BooleanCellFalseIcon, api.components.BooleanCellTrueIcon, value], + ); return ( ); -} +}); export const renderBooleanCell = (params) => ; diff --git a/packages/grid/_modules_/grid/components/cell/GridCell.tsx b/packages/grid/_modules_/grid/components/cell/GridCell.tsx index 0bc95dc367a56..839f4fd1ef10b 100644 --- a/packages/grid/_modules_/grid/components/cell/GridCell.tsx +++ b/packages/grid/_modules_/grid/components/cell/GridCell.tsx @@ -30,6 +30,7 @@ export interface GridCellProps { hasFocus?: boolean; height: number; isEditable?: boolean; + isSelected?: boolean; rowIndex: number; showRightBorder?: boolean; value?: GridCellValue; @@ -51,6 +52,7 @@ export const GridCell = React.memo((props: GridCellProps) => { hasFocus, height, isEditable, + isSelected, rowIndex, rowId, showRightBorder, @@ -149,7 +151,7 @@ export const GridCell = React.memo((props: GridCellProps) => { cellRef.current && (!document.activeElement || !cellRef.current!.contains(document.activeElement)) ) { - const focusableElement = cellRef.current.querySelector('[tabindex="0"]') as HTMLElement; + const focusableElement = cellRef.current!.querySelector('[tabindex="0"]') as HTMLElement; if (focusableElement) { focusableElement!.focus(); } else { @@ -166,6 +168,7 @@ export const GridCell = React.memo((props: GridCellProps) => { data-value={value} data-field={field} data-rowindex={rowIndex} + data-rowselected={isSelected} data-editable={isEditable} data-mode={cellMode} aria-colindex={colIndex} diff --git a/packages/grid/_modules_/grid/components/cell/GridEditBooleanCell.tsx b/packages/grid/_modules_/grid/components/cell/GridEditBooleanCell.tsx index fa3fe47045031..774e26e104fed 100644 --- a/packages/grid/_modules_/grid/components/cell/GridEditBooleanCell.tsx +++ b/packages/grid/_modules_/grid/components/cell/GridEditBooleanCell.tsx @@ -11,18 +11,15 @@ export function GridEditBooleanCell( const { id: idProp, value, - element, formattedValue, api, field, row, colDef, cellMode, - getValue, - rowIndex, - colIndex, isEditable, className, + getValue, ...other } = props; diff --git a/packages/grid/_modules_/grid/components/cell/GridEditInputCell.tsx b/packages/grid/_modules_/grid/components/cell/GridEditInputCell.tsx index d02430f90b79c..60fd03e8d9868 100644 --- a/packages/grid/_modules_/grid/components/cell/GridEditInputCell.tsx +++ b/packages/grid/_modules_/grid/components/cell/GridEditInputCell.tsx @@ -14,10 +14,9 @@ export function GridEditInputCell(props: GridCellParams & InputBaseProps) { row, colDef, cellMode, - getValue, - rowIndex, - colIndex, isEditable, + hasFocus, + getValue, ...other } = props; diff --git a/packages/grid/_modules_/grid/components/cell/GridRowCells.tsx b/packages/grid/_modules_/grid/components/cell/GridRowCells.tsx index 566472f34e2a4..9e4acc40e562f 100644 --- a/packages/grid/_modules_/grid/components/cell/GridRowCells.tsx +++ b/packages/grid/_modules_/grid/components/cell/GridRowCells.tsx @@ -1,13 +1,13 @@ import * as React from 'react'; -import { gridEditRowsStateSelector } from '../../hooks/features/rows/gridEditRowsSelector'; +import { GridCellIdentifier } from '../../hooks/features/focus/gridFocusState'; import { GridCellClassParams, GridColumns, GridRowModel, GridCellClassRules, GridCellParams, - GridCellIndexCoordinates, GridRowId, + GridEditRowProps, } from '../../models/index'; import { GridCell, GridCellProps } from './GridCell'; import { GridApiContext } from '../GridApiContext'; @@ -28,34 +28,40 @@ interface RowCellsProps { extendRowFullWidth: boolean; firstColIdx: number; id: GridRowId; - hasScroll: { y: boolean; x: boolean }; + hasScrollX: boolean; + hasScrollY: boolean; lastColIdx: number; row: GridRowModel; rowIndex: number; showCellRightBorder: boolean; - cellFocus: GridCellIndexCoordinates | null; - cellTabIndex: GridCellIndexCoordinates | null; + cellFocus: GridCellIdentifier | null; + cellTabIndex: GridCellIdentifier | null; + isSelected: boolean; + editRowState?: GridEditRowProps; } export const GridRowCells = React.memo((props: RowCellsProps) => { const { columns, firstColIdx, - hasScroll, + hasScrollX, + hasScrollY, id, lastColIdx, rowIndex, cellFocus, cellTabIndex, showCellRightBorder, + isSelected, + editRowState, } = props; const apiRef = React.useContext(GridApiContext); const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector); - const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); const cellsProps = columns.slice(firstColIdx, lastColIdx + 1).map((column, colIdx) => { - const isLastColumn = firstColIdx + colIdx === columns.length - 1; - const removeLastBorderRight = isLastColumn && hasScroll.x && !hasScroll.y; + const colIndex = firstColIdx + colIdx; + const isLastColumn = colIndex === columns.length - 1; + const removeLastBorderRight = isLastColumn && hasScrollX && !hasScrollY; const showRightBorder = !isLastColumn ? showCellRightBorder : !removeLastBorderRight && !props.extendRowFullWidth; @@ -76,7 +82,7 @@ export const GridRowCells = React.memo((props: RowCellsProps) => { cssClassProp = { cssClass: `${cssClassProp.cssClass} ${cssClass}` }; } - const editCellState = editRowsState[id] && editRowsState[id][column.field]; + const editCellState = editRowState && editRowState[column.field]; let cellComponent: React.ReactElement | null = null; if (editCellState == null && column.renderCell) { @@ -102,17 +108,13 @@ export const GridRowCells = React.memo((props: RowCellsProps) => { ...cssClassProp, rowIndex, cellMode: cellParams.cellMode, - colIndex: cellParams.colIndex, + colIndex, children: cellComponent, isEditable: cellParams.isEditable, - hasFocus: - cellFocus !== null && - cellFocus.rowIndex === rowIndex && - cellFocus.colIndex === cellParams.colIndex, + isSelected, + hasFocus: cellFocus !== null && cellFocus.id === id && cellFocus.field === column.field, tabIndex: - cellTabIndex !== null && - cellTabIndex.rowIndex === rowIndex && - cellTabIndex.colIndex === cellParams.colIndex + cellTabIndex !== null && cellTabIndex.id === id && cellTabIndex.field === column.field ? 0 : -1, }; diff --git a/packages/grid/_modules_/grid/components/columnHeaders/ColumnHeaderFilterIcon.tsx b/packages/grid/_modules_/grid/components/columnHeaders/ColumnHeaderFilterIcon.tsx index 40a53cb3d00f0..8ad9a78981148 100644 --- a/packages/grid/_modules_/grid/components/columnHeaders/ColumnHeaderFilterIcon.tsx +++ b/packages/grid/_modules_/grid/components/columnHeaders/ColumnHeaderFilterIcon.tsx @@ -47,7 +47,7 @@ export function ColumnHeaderFilterIcon(props: ColumnHeaderFilterIconProps) { size="small" tabIndex={-1} > - + ); diff --git a/packages/grid/_modules_/grid/components/columnHeaders/ColumnHeaderMenuIcon.tsx b/packages/grid/_modules_/grid/components/columnHeaders/ColumnHeaderMenuIcon.tsx index b0e0643de789c..a6b8007fef35c 100644 --- a/packages/grid/_modules_/grid/components/columnHeaders/ColumnHeaderMenuIcon.tsx +++ b/packages/grid/_modules_/grid/components/columnHeaders/ColumnHeaderMenuIcon.tsx @@ -11,8 +11,7 @@ import { GridColDef } from '../../models/colDef/gridColDef'; export interface ColumnHeaderFilterIconProps { column: GridColDef; } - -export function ColumnHeaderMenuIcon(props: ColumnHeaderFilterIconProps) { +export const ColumnHeaderMenuIcon = React.memo((props: ColumnHeaderFilterIconProps) => { const { column } = props; const apiRef = React.useContext(GridApiContext); const columnMenuState = useGridSelector(apiRef, gridColumnMenuStateSelector); @@ -48,4 +47,4 @@ export function ColumnHeaderMenuIcon(props: ColumnHeaderFilterIconProps) { ); -} +}); diff --git a/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaderItem.tsx b/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaderItem.tsx index 0d69ebfe499c5..85291b9f33b37 100644 --- a/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaderItem.tsx +++ b/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaderItem.tsx @@ -69,8 +69,8 @@ export const GridColumnHeaderItem = React.memo( const isColumnNumeric = column.type === GRID_NUMBER_COLUMN_TYPE; let headerComponent: React.ReactElement | null = null; - if (column.renderHeader) { - headerComponent = column.renderHeader(apiRef!.current.getColumnHeaderParams(column.field)!); + if (column.renderHeader && apiRef!.current) { + headerComponent = column.renderHeader(apiRef!.current.getColumnHeaderParams(column.field)); } const publish = React.useCallback( diff --git a/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeadersItemCollection.tsx b/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeadersItemCollection.tsx index 957079bf9d23b..417fbf657e4d1 100644 --- a/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeadersItemCollection.tsx +++ b/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeadersItemCollection.tsx @@ -45,11 +45,11 @@ export function GridColumnHeadersItemCollection(props: GridColumnHeadersItemColl const isFirstColumn = colIndex === 0; const hasTabbableElement = !(tabIndexState === null && cellTabIndexState === null); const tabIndex = - (tabIndexState !== null && tabIndexState.colIndex === colIndex) || + (tabIndexState !== null && tabIndexState.field === col.field) || (isFirstColumn && !hasTabbableElement) ? 0 : -1; - const hasFocus = columnHeaderFocus !== null && columnHeaderFocus.colIndex === colIndex; + const hasFocus = columnHeaderFocus !== null && columnHeaderFocus.field === col.field; return ( ( +export const GridCellCheckboxForwardRef = React.forwardRef( function GridCellCheckboxRenderer(props, ref) { - const { getValue, field, id, rowIndex, colIndex, element } = props; + const { field, id, value, tabIndex, hasFocus } = props; const apiRef = React.useContext(GridApiContext); - const tabIndexState = useGridSelector(apiRef, gridTabIndexCellSelector); + const checkboxElement = React.useRef(null); + + const handleRef = useForkRef(checkboxElement, ref); + const element = props.api.getCellElement(id, field); const handleChange = (event: React.ChangeEvent, checked: boolean) => { apiRef!.current.selectRow(id, checked, true); }; - const tabIndex = - tabIndexState !== null && - tabIndexState.rowIndex === rowIndex && - tabIndexState.colIndex === colIndex - ? 0 - : -1; React.useLayoutEffect(() => { if (tabIndex === 0 && element) { @@ -28,6 +24,13 @@ const GridCellCheckboxForwardRef = React.forwardRef { + if (hasFocus && checkboxElement.current) { + const input = checkboxElement.current.querySelector('input')!; + input!.focus(); + } + }, [hasFocus]); + const handleKeyDown = React.useCallback( (event) => { if (isSpaceKey(event.key)) { @@ -44,9 +47,9 @@ const GridCellCheckboxForwardRef = React.forwardRef( function GridHeaderCheckbox(props, ref) { - const { colIndex, element } = props; + const [, forceUpdate] = React.useState(false); const apiRef = React.useContext(GridApiContext); const visibleRowIds = useGridSelector(apiRef, visibleSortedGridRowIdsSelector); const tabIndexState = useGridSelector(apiRef, gridTabIndexColumnHeaderSelector); + const element = apiRef!.current.getColumnHeaderElement(props.field); const totalSelectedRows = useGridSelector(apiRef, selectedGridRowsCountSelector); const totalRows = useGridSelector(apiRef, gridRowCountSelector); @@ -38,7 +42,7 @@ export const GridHeaderCheckbox = React.forwardRef { if (tabIndex === 0 && element) { element!.tabIndex = -1; @@ -57,6 +61,14 @@ export const GridHeaderCheckbox = React.forwardRef { + forceUpdate((p) => !p); + }, []); + + React.useEffect(() => { + return apiRef?.current.subscribeEvent(GRID_SELECTION_CHANGED, handleSelectionChange); + }, [apiRef, handleSelectionChange]); + const CheckboxComponent = apiRef?.current.components.Checkbox!; return ( diff --git a/packages/grid/_modules_/grid/constants/eventsConstants.ts b/packages/grid/_modules_/grid/constants/eventsConstants.ts index 74b089287727f..9f314d8ce73f7 100644 --- a/packages/grid/_modules_/grid/constants/eventsConstants.ts +++ b/packages/grid/_modules_/grid/constants/eventsConstants.ts @@ -28,7 +28,7 @@ export const GRID_CELL_VALUE_CHANGE = 'cellValueChange'; export const GRID_CELL_EDIT_ENTER = 'cellEditEnter'; export const GRID_CELL_EDIT_EXIT = 'cellEditExit'; export const GRID_CELL_NAVIGATION_KEYDOWN = 'cellNavigationKeyDown'; -export const GRID_CELL_FOCUS = 'cellCellFocus'; +export const GRID_CELL_FOCUS = 'cellFocus'; export const GRID_CELL_DRAG_START = 'cellDragStart'; export const GRID_CELL_DRAG_ENTER = 'cellDragEnter'; export const GRID_CELL_DRAG_OVER = 'cellDragOver'; diff --git a/packages/grid/_modules_/grid/hooks/features/focus/gridFocusState.ts b/packages/grid/_modules_/grid/hooks/features/focus/gridFocusState.ts index b33ff52ad190a..3f48a81200300 100644 --- a/packages/grid/_modules_/grid/hooks/features/focus/gridFocusState.ts +++ b/packages/grid/_modules_/grid/hooks/features/focus/gridFocusState.ts @@ -1,14 +1,14 @@ -import { - GridCellIndexCoordinates, - GridColumnHeaderIndexCoordinates, -} from '../../../models/gridCell'; +import { GridRowId } from '../../../models/gridRows'; + +export type GridCellIdentifier = { id: GridRowId; field: string }; +export type GridColumnIdentifier = { field: string }; export interface GridFocusState { - cell: GridCellIndexCoordinates | null; - columnHeader: GridColumnHeaderIndexCoordinates | null; + cell: GridCellIdentifier | null; + columnHeader: GridColumnIdentifier | null; } export interface GridTabIndexState { - cell: GridCellIndexCoordinates | null; - columnHeader: GridColumnHeaderIndexCoordinates | null; + cell: GridCellIdentifier | null; + columnHeader: GridColumnIdentifier | null; } diff --git a/packages/grid/_modules_/grid/hooks/features/focus/gridFocusStateSelector.ts b/packages/grid/_modules_/grid/hooks/features/focus/gridFocusStateSelector.ts index 572faed212ff3..a5a6380391fb9 100644 --- a/packages/grid/_modules_/grid/hooks/features/focus/gridFocusStateSelector.ts +++ b/packages/grid/_modules_/grid/hooks/features/focus/gridFocusStateSelector.ts @@ -1,23 +1,24 @@ import { createSelector } from 'reselect'; -import { - GridCellIndexCoordinates, - GridColumnHeaderIndexCoordinates, -} from '../../../models/gridCell'; import { GridState } from '../core/gridState'; -import { GridFocusState, GridTabIndexState } from './gridFocusState'; +import { + GridCellIdentifier, + GridColumnIdentifier, + GridFocusState, + GridTabIndexState, +} from './gridFocusState'; export const gridFocusStateSelector = (state: GridState) => state.focus; export const gridFocusCellSelector = createSelector< GridState, GridFocusState, - GridCellIndexCoordinates | null + GridCellIdentifier | null >(gridFocusStateSelector, (focusState: GridFocusState) => focusState.cell); export const gridFocusColumnHeaderSelector = createSelector< GridState, GridFocusState, - GridColumnHeaderIndexCoordinates | null + GridColumnIdentifier | null >(gridFocusStateSelector, (focusState: GridFocusState) => focusState.columnHeader); export const gridTabIndexStateSelector = (state: GridState) => state.tabIndex; @@ -25,11 +26,11 @@ export const gridTabIndexStateSelector = (state: GridState) => state.tabIndex; export const gridTabIndexCellSelector = createSelector< GridState, GridTabIndexState, - GridCellIndexCoordinates | null + GridCellIdentifier | null >(gridTabIndexStateSelector, (state: GridTabIndexState) => state.cell); export const gridTabIndexColumnHeaderSelector = createSelector< GridState, GridTabIndexState, - GridColumnHeaderIndexCoordinates | null + GridColumnIdentifier | null >(gridTabIndexStateSelector, (state: GridTabIndexState) => state.columnHeader); diff --git a/packages/grid/_modules_/grid/hooks/features/focus/useGridFocus.ts b/packages/grid/_modules_/grid/hooks/features/focus/useGridFocus.ts index 46861d389e554..ebe64ec1a762e 100644 --- a/packages/grid/_modules_/grid/hooks/features/focus/useGridFocus.ts +++ b/packages/grid/_modules_/grid/hooks/features/focus/useGridFocus.ts @@ -7,10 +7,7 @@ import { } from '../../../constants/eventsConstants'; import { GridApiRef } from '../../../models/api/gridApiRef'; import { GridFocusApi } from '../../../models/api/gridFocusApi'; -import { - GridCellIndexCoordinates, - GridColumnHeaderIndexCoordinates, -} from '../../../models/gridCell'; +import { GridRowId } from '../../../models/gridRows'; import { GridCellParams } from '../../../models/params/gridCellParams'; import { useGridApiMethod } from '../../root/useGridApiMethod'; import { useGridState } from '../core/useGridState'; @@ -22,56 +19,55 @@ export const useGridFocus = (apiRef: GridApiRef): void => { const [, setGridState, forceUpdate] = useGridState(apiRef); const setCellFocus = React.useCallback( - (nextCellIndexes: GridCellIndexCoordinates) => { + (id: GridRowId, field: string) => { setGridState((state) => { - const { rowIndex, colIndex } = nextCellIndexes; - logger.debug(`Focusing on cell with rowIndex=${rowIndex} and colIndex=${colIndex}`); + logger.debug(`Focusing on cell with id=${id} and field=${field}`); return { ...state, - tabIndex: { cell: { rowIndex, colIndex }, columnHeader: null }, - focus: { cell: { rowIndex, colIndex }, columnHeader: null }, + tabIndex: { cell: { id, field }, columnHeader: null }, + focus: { cell: { id, field }, columnHeader: null }, }; }); + apiRef.current.publishEvent('cellFocusChange'); forceUpdate(); }, - [forceUpdate, logger, setGridState], + [apiRef, forceUpdate, logger, setGridState], ); const setColumnHeaderFocus = React.useCallback( - (nextColumnHeaderIndexes: GridColumnHeaderIndexCoordinates) => { + (field: string) => { setGridState((state) => { - const { colIndex } = nextColumnHeaderIndexes; - logger.debug(`Focusing on column header with colIndex=${colIndex}`); + logger.debug(`Focusing on column header with colIndex=${field}`); return { ...state, - tabIndex: { columnHeader: { colIndex }, cell: null }, - focus: { columnHeader: { colIndex }, cell: null }, + tabIndex: { columnHeader: { field }, cell: null }, + focus: { columnHeader: { field }, cell: null }, }; }); + apiRef.current.publishEvent('cellFocusChange'); + forceUpdate(); }, - [forceUpdate, logger, setGridState], + [apiRef, forceUpdate, logger, setGridState], ); const handleCellFocus = React.useCallback( - (cellParams: GridCellParams, event?: React.SyntheticEvent) => { + ({ id, field }: GridCellParams, event?: React.SyntheticEvent) => { if (event?.target !== event?.currentTarget) { return; } - - apiRef.current.setCellFocus(cellParams); + apiRef.current.setCellFocus(id, field); }, [apiRef], ); const handleColumnHeaderFocus = React.useCallback( - (params: GridCellParams, event?: React.SyntheticEvent) => { + ({ field }: GridCellParams, event?: React.SyntheticEvent) => { if (event?.target !== event?.currentTarget) { return; } - - apiRef.current.setColumnHeaderFocus(params); + apiRef.current.setColumnHeaderFocus(field); }, [apiRef], ); diff --git a/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboard.ts b/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboard.ts index c74b0dfe6d6f8..100c00a84311a 100644 --- a/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboard.ts +++ b/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboard.ts @@ -76,7 +76,7 @@ export const useGridKeyboard = ( // TODO Refactor here to not use api call const selectedRowsIds = [...apiRef.current.getSelectedRows().keys()]; if (selectedRowsIds.length > 0) { - const selectedRowsIndex = selectedRowsIds.map((id) => apiRef.current.getRowIndexFromId(id)); + const selectedRowsIndex = selectedRowsIds.map((id) => apiRef.current.getRowIndex(id)); const diffWithCurrentIndex: number[] = selectedRowsIndex.map((idx) => Math.abs(currentRowIndex - idx), @@ -87,12 +87,11 @@ export const useGridKeyboard = ( apiRef.current.publishEvent(GRID_CELL_NAVIGATION_KEYDOWN, params, event); - const nextCellIndexes = apiRef.current.getState().focus.cell!; + const focusCell = apiRef.current.getState().focus.cell!; + const rowIndex = apiRef.current.getRowIndex(focusCell.id); // We select the rows in between - const rowIds = Array(Math.abs(nextCellIndexes.rowIndex - selectionFromRowIndex) + 1).fill( - nextCellIndexes.rowIndex > selectionFromRowIndex - ? selectionFromRowIndex - : nextCellIndexes.rowIndex, + const rowIds = Array(Math.abs(rowIndex - selectionFromRowIndex) + 1).fill( + rowIndex > selectionFromRowIndex ? selectionFromRowIndex : rowIndex, ); logger.debug('Selecting rows '); diff --git a/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboardNavigation.ts b/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboardNavigation.ts index 9d53c6f80eca3..d8206d9e18db9 100644 --- a/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboardNavigation.ts +++ b/packages/grid/_modules_/grid/hooks/features/keyboard/useGridKeyboardNavigation.ts @@ -9,6 +9,7 @@ import { GridColumnHeaderIndexCoordinates, } from '../../../models/gridCell'; import { GridCellParams } from '../../../models/params/gridCellParams'; +import { GridColumnHeaderParams } from '../../../models/params/gridColumnHeaderParams'; import { isArrowKeys, isEnterKey, @@ -86,7 +87,9 @@ export const useGridKeyboardNavigation = ( const navigateCells = React.useCallback( (params: GridCellParams, event: React.KeyboardEvent) => { event.preventDefault(); - const { colIndex, rowIndex } = params; + const colIndex = apiRef.current.getColumnIndex(params.field); + const rowIndex = apiRef.current.getRowIndex(params.id); + const key = mapKey(event); const isCtrlPressed = event.ctrlKey || event.metaKey || event.shiftKey; let rowCount = totalRowCount; @@ -129,7 +132,8 @@ export const useGridKeyboardNavigation = ( } if (nextCellIndexes.rowIndex < 0) { - apiRef.current.setColumnHeaderFocus({ colIndex: nextCellIndexes.colIndex }); + const field = apiRef.current.getVisibleColumns()[nextCellIndexes.colIndex].field; + apiRef.current.setColumnHeaderFocus(field); return; } @@ -144,7 +148,9 @@ export const useGridKeyboardNavigation = ( `Navigating to next cell row ${nextCellIndexes.rowIndex}, col ${nextCellIndexes.colIndex}`, ); apiRef.current.scrollToIndexes(nextCellIndexes); - apiRef.current.setCellFocus(nextCellIndexes); + const field = apiRef.current.getVisibleColumns()[nextCellIndexes.colIndex].field; + const id = apiRef.current.getRowIdFromRowIndex(nextCellIndexes.rowIndex); + apiRef.current.setCellFocus(id, field); }, [ totalRowCount, @@ -159,10 +165,10 @@ export const useGridKeyboardNavigation = ( ); const navigateColumnHeaders = React.useCallback( - (params: GridCellParams, event: React.KeyboardEvent) => { + (params: GridColumnHeaderParams, event: React.KeyboardEvent) => { event.preventDefault(); let nextColumnHeaderIndexes: GridColumnHeaderIndexCoordinates | null; - const { colIndex } = params; + const colIndex = apiRef.current.getColumnIndex(params.field); const key = mapKey(event); if (isArrowKeys(key)) { @@ -176,10 +182,10 @@ export const useGridKeyboardNavigation = ( } else if (isPageKeys(key)) { // Handle only Page Down key, Page Up should keep the current possition if (key.indexOf('Down') > -1) { - apiRef.current.setCellFocus({ - colIndex: params.colIndex, - rowIndex: containerSizes!.viewportPageSize - 1, - }); + const field = apiRef.current.getVisibleColumns()[colIndex].field; + const id = apiRef.current.getRowIdFromRowIndex(containerSizes!.viewportPageSize - 1); + + apiRef.current.setCellFocus(id, field); } return; } else { @@ -187,7 +193,9 @@ export const useGridKeyboardNavigation = ( } if (!nextColumnHeaderIndexes) { - apiRef.current.setCellFocus({ colIndex: params.colIndex, rowIndex: 0 }); + const field = apiRef.current.getVisibleColumns()[colIndex].field; + const id = apiRef.current.getRowIdFromRowIndex(0); + apiRef.current.setCellFocus(id, field); return; } @@ -199,7 +207,8 @@ export const useGridKeyboardNavigation = ( logger.debug(`Navigating to next column row ${nextColumnHeaderIndexes.colIndex}`); apiRef.current.scrollToIndexes(nextColumnHeaderIndexes); - apiRef.current.setColumnHeaderFocus(nextColumnHeaderIndexes); + const field = apiRef.current.getVisibleColumns()[nextColumnHeaderIndexes.colIndex].field; + apiRef.current.setColumnHeaderFocus(field); }, [apiRef, colCount, containerSizes, logger], ); diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index 179f57ed22bc0..87b598e05f11a 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -219,7 +219,7 @@ export function useGridEditRows(apiRef: GridApiRef) { return; } if (isEscapeKey(event.key) || isDeleteKeys(event.key)) { - apiRef.current.setCellFocus(params); + apiRef.current.setCellFocus(params.id, params.field); } }, [apiRef, setCellMode], diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridParamsApi.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridParamsApi.ts index 96a891c252151..468d725ec1939 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridParamsApi.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridParamsApi.ts @@ -11,6 +11,8 @@ import { getGridRowElement, } from '../../../utils/domUtils'; import { useGridApiMethod } from '../../root/useGridApiMethod'; +import { useGridSelector } from '../core/useGridSelector'; +import { gridFocusCellSelector, gridTabIndexCellSelector } from '../focus/gridFocusStateSelector'; let warnedOnce = false; function warnMissingColumn(field) { @@ -24,12 +26,13 @@ function warnMissingColumn(field) { } export function useGridParamsApi(apiRef: GridApiRef) { + const cellFocus = useGridSelector(apiRef, gridFocusCellSelector); + const cellTabIndex = useGridSelector(apiRef, gridTabIndexCellSelector); + const getColumnHeaderParams = React.useCallback( (field: string): GridColumnHeaderParams => ({ field, - element: apiRef.current.getColumnHeaderElement(field), colDef: apiRef.current.getColumnFromField(field), - colIndex: apiRef.current.getColumnIndex(field, true), api: apiRef!.current, }), [apiRef], @@ -39,12 +42,10 @@ export function useGridParamsApi(apiRef: GridApiRef) { (id: GridRowId) => { const params: GridRowParams = { id, - element: apiRef.current.getRowElement(id), columns: apiRef.current.getAllColumns(), - getValue: (columnField: string) => apiRef.current.getCellValue(id, columnField), row: apiRef.current.getRowFromId(id), - rowIndex: apiRef.current.getRowIndexFromId(id), api: apiRef.current, + getValue: apiRef.current.getCellValue, }; return params; }, @@ -53,26 +54,24 @@ export function useGridParamsApi(apiRef: GridApiRef) { const getBaseCellParams = React.useCallback( (id: GridRowId, field: string) => { - const element = apiRef.current.getCellElement(id, field); const row = apiRef.current.getRowFromId(id); const params: GridValueGetterParams = { - element, id, field, row, value: row[field], - getValue: (columnField: string) => apiRef.current.getCellValue(id, columnField), colDef: apiRef.current.getColumnFromField(field), cellMode: apiRef.current.getCellMode(id, field), - rowIndex: apiRef.current.getRowIndexFromId(id), - colIndex: apiRef.current.getColumnIndex(field, true), + getValue: apiRef.current.getCellValue, api: apiRef.current, + hasFocus: cellFocus !== null && cellFocus.field === field && cellFocus.id === id, + tabIndex: cellTabIndex && cellTabIndex.field === field && cellTabIndex.id === id ? 0 : -1, }; return params; }, - [apiRef], + [apiRef, cellFocus, cellTabIndex], ); const getCellParams = React.useCallback( @@ -83,7 +82,6 @@ export function useGridParamsApi(apiRef: GridApiRef) { const params: GridCellParams = { ...baseParams, value, - getValue: (columnField: string) => apiRef.current.getCellValue(id, columnField), formattedValue: value, }; if (colDef.valueFormatter) { @@ -156,6 +154,6 @@ export function useGridParamsApi(apiRef: GridApiRef) { getColumnHeaderParams, getColumnHeaderElement, }, - 'CellApi', + 'GridParamsApi', ); } diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts index 27f819d31828b..12d11f5f6a7d1 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridRows.ts @@ -214,7 +214,7 @@ export const useGridRows = ( const getAllRowIds = React.useCallback(() => apiRef.current.state.rows.allRows, [apiRef]); const rowApi: GridRowApi = { - getRowIndexFromId, + getRowIndex: getRowIndexFromId, getRowIdFromRowIndex, getRowFromId, getRowModels, diff --git a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts index 1c9dae34bc891..a993bda486a09 100644 --- a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts +++ b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts @@ -38,7 +38,6 @@ import { sortedGridRowIdsSelector, sortedGridRowsSelector } from './gridSortingS export const useGridSorting = (apiRef: GridApiRef, rowsProp: GridRowsProp) => { const logger = useLogger('useGridSorting'); - const comparatorList = React.useRef([]); const [gridState, setGridState, forceUpdate] = useGridState(apiRef); const options = useGridSelector(apiRef, optionsSelector); @@ -100,9 +99,7 @@ export const useGridSorting = (apiRef: GridApiRef, rowsProp: GridRowsProp) => { const params: GridSortCellParams = { id, field, - row: apiRef.current.getRowFromId(id), value: apiRef.current.getCellValue(id, field), - getValue: (columnField: string) => apiRef.current.getCellValue(id, columnField), api: apiRef.current, }; @@ -112,24 +109,28 @@ export const useGridSorting = (apiRef: GridApiRef, rowsProp: GridRowsProp) => { ); const comparatorListAggregate = React.useCallback( - (id1: GridRowId, id2: GridRowId) => { - const result = comparatorList.current.reduce((res, colComparator) => { - const { field, comparator } = colComparator; - const sortCellParams1 = getSortCellParams(id1, field); - const sortCellParams2 = getSortCellParams(id2, field); - res = - res || - comparator( - sortCellParams1.value, - sortCellParams2.value, - sortCellParams1, - sortCellParams2, - ); + (comparatorList: GridFieldComparatorList) => ( + row1: GridSortCellParams[], + row2: GridSortCellParams[], + ) => { + return comparatorList.reduce((res, colComparator, index) => { + if (res !== 0) { + return res; + } + + const { comparator } = colComparator; + const sortCellParams1 = row1[index]; + const sortCellParams2 = row2[index]; + res = comparator( + sortCellParams1.value, + sortCellParams2.value, + sortCellParams1, + sortCellParams2, + ); return res; }, 0); - return result; }, - [getSortCellParams], + [], ); const buildComparatorList = React.useCallback( @@ -169,11 +170,18 @@ export const useGridSorting = (apiRef: GridApiRef, rowsProp: GridRowsProp) => { } const sortModel = apiRef.current.getState().sorting.sortModel; - logger.debug('Sorting rows with ', sortModel); - const sorted = [...rowIds]; + let sorted = rowIds; if (sortModel.length > 0) { - comparatorList.current = buildComparatorList(sortModel); - sorted.sort(comparatorListAggregate); + const comparatorList = buildComparatorList(sortModel); + logger.debug('Sorting rows with ', sortModel); + sorted = rowIds + .map((id) => { + return comparatorList.map((colComparator) => { + return getSortCellParams(id, colComparator.field); + }); + }) + .sort(comparatorListAggregate(comparatorList)) + .map((field) => field[0].id); } setGridState((oldState) => { @@ -186,6 +194,7 @@ export const useGridSorting = (apiRef: GridApiRef, rowsProp: GridRowsProp) => { }, [ apiRef, logger, + getSortCellParams, setGridState, forceUpdate, buildComparatorList, diff --git a/packages/grid/_modules_/grid/models/api/gridFocusApi.ts b/packages/grid/_modules_/grid/models/api/gridFocusApi.ts index d27fa2dac6f23..81434915d4ed7 100644 --- a/packages/grid/_modules_/grid/models/api/gridFocusApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridFocusApi.ts @@ -1,14 +1,15 @@ -import { GridCellIndexCoordinates, GridColumnHeaderIndexCoordinates } from '../gridCell'; +import { GridRowId } from '../gridRows'; export interface GridFocusApi { /** * Set the active element to the cell with the indexes. - * @param indexes + * @param id + * @param field */ - setCellFocus: (indexes: GridCellIndexCoordinates) => void; + setCellFocus: (id: GridRowId, field: string) => void; /** * Set the active element to the column header with the indexes. - * @param indexes + * @param field */ - setColumnHeaderFocus: (indexes: GridColumnHeaderIndexCoordinates) => void; + setColumnHeaderFocus: (field: string) => void; } diff --git a/packages/grid/_modules_/grid/models/api/gridRowApi.ts b/packages/grid/_modules_/grid/models/api/gridRowApi.ts index 7bb1a5e3a265a..93cce25d8889e 100644 --- a/packages/grid/_modules_/grid/models/api/gridRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridRowApi.ts @@ -36,7 +36,7 @@ export interface GridRowApi { * Get the row index of a row with a given id. * @param id */ - getRowIndexFromId: (id: GridRowId) => number; + getRowIndex: (id: GridRowId) => number; /** * Get the [[GridRowModel]] of a given rowId. * @param id diff --git a/packages/grid/_modules_/grid/models/gridSortModel.ts b/packages/grid/_modules_/grid/models/gridSortModel.ts index 801ef2900bfcb..31ba340989f9e 100644 --- a/packages/grid/_modules_/grid/models/gridSortModel.ts +++ b/packages/grid/_modules_/grid/models/gridSortModel.ts @@ -1,5 +1,5 @@ import { GridCellValue } from './gridCell'; -import { GridRowData, GridRowId } from './gridRows'; +import { GridRowId } from './gridRows'; export type GridSortDirection = 'asc' | 'desc' | null | undefined; @@ -8,9 +8,7 @@ export type GridFieldComparatorList = { field: string; comparator: GridComparato export interface GridSortCellParams { id: GridRowId; field: string; - row: GridRowData; value: GridCellValue; - getValue: (columnField: string) => GridCellValue; api: any; } diff --git a/packages/grid/_modules_/grid/models/params/gridCellParams.ts b/packages/grid/_modules_/grid/models/params/gridCellParams.ts index 8c966b89f1bcb..fb7c59a44b7f7 100644 --- a/packages/grid/_modules_/grid/models/params/gridCellParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridCellParams.ts @@ -9,10 +9,6 @@ export interface GridCellParams { * The grid row id. */ id: GridRowId; - /** - * The HTMLElement cell element. - */ - element?: HTMLElement | null; /** * The column field of the cell that triggered the event */ @@ -25,11 +21,6 @@ export interface GridCellParams { * The cell value formatted with the column valueFormatter. */ formattedValue: GridCellValue; - /** - * A function that let you get data from other columns. - * @param field - */ - getValue: (field: string) => GridCellValue; /** * The row model of the row that the current cell belongs to. */ @@ -38,14 +29,6 @@ export interface GridCellParams { * The column of the row that the current cell belongs to. */ colDef: any; - /** - * The row index of the row that the current cell belongs to. - */ - rowIndex: number; - /** - * The column index that the current cell belongs to. - */ - colIndex: number; /** * GridApi that let you manipulate the grid. */ @@ -58,6 +41,20 @@ export interface GridCellParams { * The mode of the cell. */ cellMode: GridCellMode; + /** + * If true, the cell is the active element. + */ + hasFocus: boolean; + /** + * the tabIndex value. + */ + tabIndex: 0 | -1; + /** + * Get the cell value of a row and field. + * @param id + * @param field + */ + getValue: (id: GridRowId, field: string) => GridCellValue; } /** diff --git a/packages/grid/_modules_/grid/models/params/gridColumnHeaderParams.ts b/packages/grid/_modules_/grid/models/params/gridColumnHeaderParams.ts index a002458ada4f4..9f0362e138fa0 100644 --- a/packages/grid/_modules_/grid/models/params/gridColumnHeaderParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridColumnHeaderParams.ts @@ -2,10 +2,6 @@ * Object passed as parameter in the column [[GridColDef]] header renderer. */ export interface GridColumnHeaderParams { - /** - * The HTMLElement column header element. - */ - element?: HTMLElement | null; /** * The column field of the column that triggered the event */ @@ -14,10 +10,6 @@ export interface GridColumnHeaderParams { * The column of the current header component. */ colDef: any; - /** - * The column index of the current header component. - */ - colIndex: number; /** * API ref that let you manipulate the grid. */ diff --git a/packages/grid/_modules_/grid/models/params/gridRowParams.ts b/packages/grid/_modules_/grid/models/params/gridRowParams.ts index d733139176038..f6c6555c1a302 100644 --- a/packages/grid/_modules_/grid/models/params/gridRowParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridRowParams.ts @@ -9,15 +9,6 @@ export interface GridRowParams { * The grid row id. */ id: GridRowId; - /** - * The HTMLElement row element. - */ - element?: HTMLElement | null; - /** - * A function that let you get data from other columns. - * @param field - */ - getValue: (field: string) => GridCellValue; /** * The row model of the row that the current cell belongs to. */ @@ -26,12 +17,14 @@ export interface GridRowParams { * All grid columns. */ columns: any; - /** - * The row index of the row that the current cell belongs to. - */ - rowIndex: number; /** * GridApiRef that let you manipulate the grid. */ api: any; + /** + * Get the cell value of a row and field. + * @param id + * @param field + */ + getValue: (id: GridRowId, field: string) => GridCellValue; } diff --git a/packages/grid/_modules_/grid/utils/sortingUtils.ts b/packages/grid/_modules_/grid/utils/sortingUtils.ts index abef6e3b884ac..516fad6f46f13 100644 --- a/packages/grid/_modules_/grid/utils/sortingUtils.ts +++ b/packages/grid/_modules_/grid/utils/sortingUtils.ts @@ -24,14 +24,9 @@ export const gridNillComparer = (v1: GridCellValue, v2: GridCellValue): number | }; export const gridStringNumberComparer: GridComparatorFn = ( - v1: GridCellValue, - v2: GridCellValue, - cellParams1, - cellParams2, + value1: GridCellValue, + value2: GridCellValue, ) => { - const value1 = cellParams1.getValue(cellParams1.field); - const value2 = cellParams2.getValue(cellParams2.field); - const nillResult = gridNillComparer(value1, value2); if (nillResult !== null) { return nillResult; @@ -44,14 +39,9 @@ export const gridStringNumberComparer: GridComparatorFn = ( }; export const gridNumberComparer: GridComparatorFn = ( - v1: GridCellValue, - v2: GridCellValue, - cellParams1, - cellParams2, + value1: GridCellValue, + value2: GridCellValue, ) => { - const value1 = cellParams1.getValue(cellParams1.field); - const value2 = cellParams2.getValue(cellParams2.field); - const nillResult = gridNillComparer(value1, value2); if (nillResult !== null) { return nillResult; @@ -60,15 +50,7 @@ export const gridNumberComparer: GridComparatorFn = ( return Number(value1) - Number(value2); }; -export const gridDateComparer = ( - v1: GridCellValue, - v2: GridCellValue, - cellParams1, - cellParams2, -): number => { - const value1 = cellParams1.getValue(cellParams1.field); - const value2 = cellParams2.getValue(cellParams2.field); - +export const gridDateComparer = (value1: GridCellValue, value2: GridCellValue): number => { const nillResult = gridNillComparer(value1, value2); if (nillResult !== null) { return nillResult; diff --git a/packages/grid/data-grid/src/tests/layout.DataGrid.test.tsx b/packages/grid/data-grid/src/tests/layout.DataGrid.test.tsx index 984ec63bea151..9e85814eda0fd 100644 --- a/packages/grid/data-grid/src/tests/layout.DataGrid.test.tsx +++ b/packages/grid/data-grid/src/tests/layout.DataGrid.test.tsx @@ -117,7 +117,9 @@ describe(' - Layout & Warnings', () => { { field: 'fullName', valueGetter: (params) => - `${params.getValue('firstName') || ''} ${params.getValue('lastName') || ''}`, + `${params.getValue(params.id, 'firstName') || ''} ${ + params.getValue(params.id, 'lastName') || '' + }`, }, ]; @@ -186,7 +188,7 @@ describe(' - Layout & Warnings', () => { { field: 'id', hide: true }, { field: 'fullName', - valueGetter: (params: GridValueGetterParams) => params.getValue('age'), + valueGetter: (params: GridValueGetterParams) => params.getValue(params.id, 'age'), }, ]; expect(() => { diff --git a/packages/grid/data-grid/src/tests/rows.DataGrid.test.tsx b/packages/grid/data-grid/src/tests/rows.DataGrid.test.tsx index 8facc016c3e11..3653b7f010e24 100644 --- a/packages/grid/data-grid/src/tests/rows.DataGrid.test.tsx +++ b/packages/grid/data-grid/src/tests/rows.DataGrid.test.tsx @@ -85,7 +85,7 @@ describe(' - Rows', () => { it('should apply the CSS class returned by getRowClassName', () => { const getRowId = (row) => `${row.clientId}`; - const getRowClassName = (params) => (params.getValue('age') < 20 ? 'under-age' : ''); + const getRowClassName = (params) => (params.getValue(params.id, 'age') < 20 ? 'under-age' : ''); render(
diff --git a/packages/grid/x-grid/src/tests/events.XGrid.test.tsx b/packages/grid/x-grid/src/tests/events.XGrid.test.tsx index 41c0389a99c43..eb14124c24f55 100644 --- a/packages/grid/x-grid/src/tests/events.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/events.XGrid.test.tsx @@ -85,8 +85,6 @@ describe(' - Events Params', () => { expect(eventArgs!.params).to.deep.include({ colDef: apiRef!.current.getColumnFromField('age'), - element: ageColumnElement, - colIndex: 2, field: 'age', api: apiRef.current, }); @@ -107,11 +105,10 @@ describe(' - Events Params', () => { expect(eventArgs!.params).to.deep.include({ id: 2, - element: row1, row: baselineProps.rows[1], - rowIndex: 1, columns: apiRef!.current.getAllColumns(), api: apiRef.current, + getValue: apiRef.current.getCellValue, }); }); }); @@ -133,12 +130,12 @@ describe(' - Events Params', () => { value: 'Jack', formattedValue: 'Jack', isEditable: true, - element: cell11, row: baselineProps.rows[1], - rowIndex: 1, colDef: apiRef!.current.getColumnFromField('first'), - colIndex: 1, api: apiRef.current, + hasFocus: false, + tabIndex: -1, + getValue: apiRef.current.getCellValue, }); }); @@ -160,12 +157,12 @@ describe(' - Events Params', () => { value: 'Jack', formattedValue: 'Jack', isEditable: true, - element: cell01, row: baselineProps.rows[1], - rowIndex: 0, colDef: apiRef!.current.getColumnFromField('first'), - colIndex: 1, api: apiRef.current, + hasFocus: false, + tabIndex: -1, + getValue: apiRef.current.getCellValue, }); }); diff --git a/packages/grid/x-grid/src/tests/filtering.XGrid.test.tsx b/packages/grid/x-grid/src/tests/filtering.XGrid.test.tsx index c00b775be5f25..ef7df70a1ebc7 100644 --- a/packages/grid/x-grid/src/tests/filtering.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/filtering.XGrid.test.tsx @@ -20,8 +20,11 @@ import { createClientRenderStrictMode, // @ts-expect-error need to migrate helpers to TypeScript fireEvent, + // @ts-expect-error need to migrate helpers to TypeScript + waitFor, } from 'test/utils'; import { getColumnHeaderCell, getColumnValues } from 'test/utils/helperFn'; +import { useData } from 'packages/storybook/src/hooks/useData'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); @@ -257,6 +260,50 @@ describe(' - Filter', () => { expect(apiRef.current.getVisibleRowModels().get(1)).to.deep.equal({ id: 1, brand: 'Adidas' }); }); + describe('performance', () => { + beforeEach(() => { + clock.restore(); + }); + + it('should filter 5,000 rows in less than 100 ms', async function test() { + // It's simpler to only run the performance test in a single controlled environment. + if (!/HeadlessChrome/.test(window.navigator.userAgent)) { + this.skip(); + return; + } + + const TestCasePerf = () => { + const data = useData(5000, 10); + apiRef = useGridApiRef(); + return ( +
+ +
+ ); + }; + + render(); + const newModel = { + items: [ + { + columnField: 'currencyPair', + value: 'usd', + operatorValue: 'startsWith', + }, + ], + }; + const t0 = performance.now(); + apiRef.current.setFilterModel(newModel); + + await waitFor(() => + expect(document.querySelector('.MuiDataGrid-filterIcon')).to.not.equal(null), + ); + const t1 = performance.now(); + const time = Math.round(t1 - t0); + expect(time).to.be.lessThan(100); + }); + }); + describe('Server', () => { it('should refresh the filter panel when adding filters', () => { function loadServerRows(commodityFilterValue) { diff --git a/packages/grid/x-grid/src/tests/sorting.XGrid.test.tsx b/packages/grid/x-grid/src/tests/sorting.XGrid.test.tsx index 374e6f695c3c4..b40ee441e5c99 100644 --- a/packages/grid/x-grid/src/tests/sorting.XGrid.test.tsx +++ b/packages/grid/x-grid/src/tests/sorting.XGrid.test.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { GridApiRef, GridSortModel, useGridApiRef } from '@material-ui/data-grid'; +import { GridApiRef, GridColDef, GridSortModel, useGridApiRef } from '@material-ui/data-grid'; import { XGrid } from '@material-ui/x-grid'; import { expect } from 'chai'; import { useFakeTimers } from 'sinon'; @@ -216,10 +216,56 @@ describe(' - Sorting', () => { const t0 = performance.now(); fireEvent.click(header); - await waitFor(() => expect(document.querySelector('.MuiDataGrid-sortIcon')).to.not.be.null); + await waitFor(() => + expect(document.querySelector('.MuiDataGrid-sortIcon')).to.not.equal(null), + ); const t1 = performance.now(); const time = Math.round(t1 - t0); expect(time).to.be.lessThan(300); }); + + it('should render maximum twice', async function test() { + let renderCellCount = 0; + let renderHeaderCount = 0; + const TestCasePerf = () => { + const [cols, setCols] = React.useState([]); + const data = useData(10, 10); + + React.useEffect(() => { + if (data.columns.length) { + const newColumns = [...data.columns]; + newColumns[1].renderHeader = (params) => { + renderHeaderCount += 1; + + return {params.field}; + }; + newColumns[1].renderCell = (params) => { + if (params.id === 0) { + renderCellCount += 1; + } + return {params.value}; + }; + setCols(newColumns); + } + }, [data.columns]); + + return ( +
+ +
+ ); + }; + + render(); + const header = getColumnHeaderCell(2); + renderHeaderCount = 0; + renderCellCount = 0; + fireEvent.click(header); + await waitFor(() => + expect(document.querySelector('.MuiDataGrid-sortIcon')).to.not.equal(null), + ); + expect(renderHeaderCount).to.equal(2); + expect(renderCellCount).to.equal(0); + }); }); }); diff --git a/packages/storybook/src/stories/grid-columns.stories.tsx b/packages/storybook/src/stories/grid-columns.stories.tsx index 783eee43028b9..feed9714c8ddf 100644 --- a/packages/storybook/src/stories/grid-columns.stories.tsx +++ b/packages/storybook/src/stories/grid-columns.stories.tsx @@ -299,12 +299,12 @@ export const ValueGetterAndFormatter = () => { { field: 'firstAge', valueGetter: (params: GridValueGetterParams) => - `${params.getValue('first')}_${params.getValue('age')}`, + `${params.getValue(params.id, 'first')}_${params.getValue(params.id, 'age')}`, }, { field: 'firstAgeFormatted', valueGetter: (params: GridValueGetterParams) => - `${params.getValue('first')}_${params.getValue('age')}`, + `${params.getValue(params.id, 'first')}_${params.getValue(params.id, 'age')}`, valueFormatter: (params) => `${params.value} yrs`, }, ], diff --git a/packages/storybook/src/stories/grid-error.stories.tsx b/packages/storybook/src/stories/grid-error.stories.tsx index a7eb54a1934f6..1c50ed7f9542a 100644 --- a/packages/storybook/src/stories/grid-error.stories.tsx +++ b/packages/storybook/src/stories/grid-error.stories.tsx @@ -26,7 +26,9 @@ const getColumns: () => GridColDef[] = () => [ description: 'this column has a value getter and is not sortable', sortable: false, valueGetter: (params) => - `${params.getValue('firstName') || ''} ${params.getValue('lastName') || ''}`, + `${params.getValue(params.id, 'firstName') || ''} ${ + params.getValue(params.id, 'lastName') || '' + }`, }, { field: 'isRegistered', diff --git a/packages/storybook/src/stories/grid-rows.stories.tsx b/packages/storybook/src/stories/grid-rows.stories.tsx index 27ee55b9d085d..409753bfc4848 100644 --- a/packages/storybook/src/stories/grid-rows.stories.tsx +++ b/packages/storybook/src/stories/grid-rows.stories.tsx @@ -853,7 +853,7 @@ export function EditCellWithMessageGrid() { return apiRef.current.subscribeEvent( GRID_CELL_EDIT_ENTER, (params: GridCellParams, event?: React.SyntheticEvent) => { - setMessage(`Editing cell with value: ${params.value} at row: ${params.rowIndex}, column: ${ + setMessage(`Editing cell with value: ${params.value} at row: ${params.id}, column: ${ params.field }, triggered by ${event!.type} diff --git a/packages/storybook/src/stories/grid-sorting.stories.tsx b/packages/storybook/src/stories/grid-sorting.stories.tsx index 57d1cf7c4e0a4..007c4cf57406f 100644 --- a/packages/storybook/src/stories/grid-sorting.stories.tsx +++ b/packages/storybook/src/stories/grid-sorting.stories.tsx @@ -234,7 +234,9 @@ export const UnsortableLastCol = () => { field: 'username', sortable: false, valueGetter: (params) => - `${params.getValue('name') || 'unknown'}_${params.getValue('age') || 'x'}`, + `${params.getValue(params.id, 'name') || 'unknown'}_${ + params.getValue(params.id, 'age') || 'x' + }`, width: 150, }; @@ -250,8 +252,12 @@ export const CustomComparator = () => { columns[columns.length] = { field: 'username', valueGetter: (params) => - `${params.getValue('name') || 'unknown'}_${params.getValue('age') || 'x'}`, - sortComparator: (v1, v2, cellParams1, cellParams2) => cellParams1.row.age - cellParams2.row.age, + `${params.getValue(params.id, 'name') || 'unknown'}_${ + params.getValue(params.id, 'age') || 'x' + }`, + sortComparator: (v1, v2, cellParams1, cellParams2) => + cellParams1.api.getCellValue(cellParams1.id, 'age') - + cellParams2.api.getCellValue(cellParams2.id, 'age'), width: 150, }; diff --git a/packages/storybook/src/stories/grid-style.stories.tsx b/packages/storybook/src/stories/grid-style.stories.tsx index 7cec9b0b27c69..4374ade8521ae 100644 --- a/packages/storybook/src/stories/grid-style.stories.tsx +++ b/packages/storybook/src/stories/grid-style.stories.tsx @@ -61,7 +61,9 @@ const getColumns: () => GridColDef[] = () => [ description: 'this column has a value getter and is not sortable', sortable: false, valueGetter: (params) => - `${params.getValue('firstName') || ''} ${params.getValue('lastName') || ''}`.trim(), + `${params.getValue(params.id, 'firstName') || ''} ${ + params.getValue(params.id, 'lastName') || '' + }`.trim(), }, { field: 'isRegistered', diff --git a/packages/storybook/src/stories/playground/customize-components.stories.tsx b/packages/storybook/src/stories/playground/customize-components.stories.tsx index f2219bf6c9e50..2087fac527fc2 100644 --- a/packages/storybook/src/stories/playground/customize-components.stories.tsx +++ b/packages/storybook/src/stories/playground/customize-components.stories.tsx @@ -159,7 +159,9 @@ StyledColumns.args = { headerClassName: 'highlight', sortable: false, valueGetter: (params) => - `${params.getValue('firstName') || ''} ${params.getValue('lastName') || ''}`, + `${params.getValue(params.id, 'firstName') || ''} ${ + params.getValue(params.id, 'lastName') || '' + }`, cellClassRules: { common: (params) => params.row.lastName === 'Smith', unknown: (params) => !params.row.lastName, diff --git a/packages/storybook/src/stories/playground/real-data-demo.stories.tsx b/packages/storybook/src/stories/playground/real-data-demo.stories.tsx index f8a91659190ba..0e1c47044e453 100644 --- a/packages/storybook/src/stories/playground/real-data-demo.stories.tsx +++ b/packages/storybook/src/stories/playground/real-data-demo.stories.tsx @@ -40,10 +40,10 @@ export default { }, }, rowLength: { - defaultValue: 2000, + defaultValue: 500, control: { type: 'select', - options: [10, 50, 100, 500, 1000, 2000, 5000, 8000, 10000, 50000, 100000, 500000], + options: [1, 9, 10, 50, 100, 500, 1000, 2000, 5000, 8000, 10000, 50000, 100000, 500000], }, }, multipleGrid: {