From 517ed5e5d49c2d621c6ace85e9be9332e9777079 Mon Sep 17 00:00:00 2001 From: damien Date: Thu, 11 Mar 2021 15:06:54 +0100 Subject: [PATCH] [DataGrid] Improve the handling of events (rm capture, add event, add new props) (#1158) --- docs/pages/api-docs/data-grid.md | 23 +- docs/pages/api-docs/x-grid.md | 23 +- .../data-grid/overview/DataGridDemo.tsx | 4 +- .../rendering/ValueFormatterGrid.tsx | 8 +- .../data-grid/rendering/ValueGetterGrid.tsx | 4 +- .../sorting/ComparatorSortingGrid.tsx | 4 +- .../grid/_modules_/grid/GridComponent.tsx | 2 + .../_modules_/grid/components/GridCell.tsx | 98 +++++--- .../grid/components/GridEmptyCell.tsx | 29 +++ .../_modules_/grid/components/GridRow.tsx | 37 ++- .../grid/components/GridRowCells.tsx | 38 +-- .../grid/components/GridViewport.tsx | 9 +- .../columnHeaders/GridColumnHeaderItem.tsx | 44 ++-- .../columnHeaders/GridColumnHeaders.tsx | 6 +- .../components/editCell/EditInputCell.tsx | 6 +- .../grid/_modules_/grid/components/index.ts | 1 + .../grid/constants/eventsConstants.ts | 42 ++-- .../hooks/features/filter/useGridFilter.ts | 11 +- .../grid/hooks/features/rows/index.ts | 1 + .../hooks/features/rows/useGridEditRows.ts | 27 +-- .../hooks/features/rows/useGridParamsApi.ts | 163 +++++++++++++ .../hooks/features/sorting/useGridSorting.ts | 15 +- .../_modules_/grid/hooks/root/useEvents.ts | 180 +++----------- .../grid/hooks/root/useGridApiEventHandler.ts | 2 +- .../grid/hooks/root/useGridApiMethod.ts | 13 +- .../grid/_modules_/grid/models/api/gridApi.ts | 2 + .../grid/models/api/gridEditRowApi.ts | 8 +- .../grid/models/api/gridParamsApi.ts | 48 ++++ .../grid/_modules_/grid/models/api/index.ts | 1 + .../grid/models/colDef/gridColDef.ts | 10 +- .../_modules_/grid/models/gridOptions.tsx | 88 ++++++- .../grid/models/params/gridCellParams.ts | 18 +- .../grid/models/params/gridColParams.ts | 4 + .../grid/models/params/gridRowParams.ts | 10 +- .../grid/_modules_/grid/utils/domUtils.ts | 28 ++- packages/grid/_modules_/grid/utils/index.ts | 1 - .../grid/_modules_/grid/utils/paramsUtils.ts | 95 -------- .../src/tests/layout.DataGrid.test.tsx | 4 +- .../x-grid/src/tests/events.XGrid.test.tsx | 220 ++++++++++++++++++ packages/storybook/.storybook/preview.tsx | 7 +- .../src/stories/grid-columns.stories.tsx | 31 ++- .../src/stories/grid-events.stories.tsx | 36 ++- .../stories/playground/RecipeReviewCard.tsx | 126 ++++++++++ .../stories/playground/customComponents.tsx | 106 +++++++++ .../customize-components.stories.tsx | 123 ++-------- .../playground/data-grid.options.stories.tsx | 64 ----- .../stories/playground/options.stories.tsx | 52 ----- .../playground/real-data-demo.stories.tsx | 196 ++++++++-------- 48 files changed, 1276 insertions(+), 792 deletions(-) create mode 100644 packages/grid/_modules_/grid/components/GridEmptyCell.tsx create mode 100644 packages/grid/_modules_/grid/hooks/features/rows/useGridParamsApi.ts create mode 100644 packages/grid/_modules_/grid/models/api/gridParamsApi.ts delete mode 100644 packages/grid/_modules_/grid/utils/paramsUtils.ts create mode 100644 packages/grid/x-grid/src/tests/events.XGrid.test.tsx create mode 100644 packages/storybook/src/stories/playground/RecipeReviewCard.tsx create mode 100644 packages/storybook/src/stories/playground/customComponents.tsx delete mode 100644 packages/storybook/src/stories/playground/data-grid.options.stories.tsx delete mode 100644 packages/storybook/src/stories/playground/options.stories.tsx diff --git a/docs/pages/api-docs/data-grid.md b/docs/pages/api-docs/data-grid.md index 7be86e278c35d..b38f5e4751fdb 100644 --- a/docs/pages/api-docs/data-grid.md +++ b/docs/pages/api-docs/data-grid.md @@ -42,10 +42,19 @@ import { DataGrid } from '@material-ui/data-grid'; | logger | Logger | null | Pass a custom logger in the components that implements the 'Logger' interface. | | logLevel | string | false | false | Allows to pass the logging level or false to turn off logging. | | nonce | string | | Nonce of the inline styles for [Content Security Policy](https://www.w3.org/TR/2016/REC-CSP2-20161215/#script-src-the-nonce-attribute). | -| onCellClick | (param: GridCellParams) => void | | Callback fired when a click event comes from a cell element. | -| onCellHover | (param: GridCellParams) => void | | Callback fired when a hover event comes from a cell element. | +| onCellClick | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a click event comes from a cell element. | +| onCellDoubleClick | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a double click event comes from a cell element. | +| onCellOver | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a mouse over event comes from a cell element. | +| onCellOut | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a mouse out comes from a cell element. | +| onCellEnter | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a mouse enter event comes from a cell element. | +| onCellLeave | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a mouse leave event comes from a cell element. | | onCellModeChange | (params: GridCellModeChangeParams) => void | | Callback fired when the cell mode changed. | -| onColumnHeaderClick | (param: GridColParams) => void | | Callback fired when a click event comes from a column header element. | +| onColumnHeaderClick | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a click event comes from a column header element. | +| onColumnHeaderDoubleClick | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a double click event comes from a column header element. | +| onColumnHeaderOver | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a mouseover event comes from a column header element. | +| onColumnHeaderOut | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a mouseout event comes from a column header element. | +| onColumnHeaderEnter | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a mouse enter event comes from a column header element. | +| onColumnHeaderLeave | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a mouse leave event comes from a column header element. | | onError | (args: any) => void | | Callback fired when an exception is thrown in the grid, or when the `showError` API method is called. | | onEditCellChange | (params: GridEditCellParams) => void | | Callback fired when the edit cell value changed. | | onEditCellChangeCommitted | (params: GridEditCellParams) => void | | Callback fired when the cell changes are committed. | @@ -53,8 +62,12 @@ import { DataGrid } from '@material-ui/data-grid'; | onFilterModelChange | (params: GridFilterModelParams) => void | | Callback fired when the Filter model changes before the filters are applied. | | onPageChange | (param: GridPageChangeParams) => void | | Callback fired when the current page has changed. | | onPageSizeChange | (param: GridPageChangeParams) => void | | Callback fired when the page size has changed. | -| onRowClick | (param: GridRowParams) => void | | Callback fired when a click event comes from a row container element. | -| onRowHover | (param: GridRowParams) => void | | Callback fired when a hover event comes from a row container element. | +| onRowClick | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a click event comes from a row container element. | +| onRowDoubleClick | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a double click event comes from a row container element. | +| onRowOver | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a mouse over comes from a row container element. | +| onRowOut | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a mouse out comes from a row container element. | +| onRowEnter | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a mouse enter comes from a row container element. | +| onRowLeave | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a mouse leave event comes from a row container element. | | onRowSelected | (param: GridRowSelectedParams) => void | | Callback fired when one row is selected. | | onSelectionModelChange | (param: GridSelectionModelChangeParams) => void | | Callback fired when the selection state of one or multiple rows changes. | | onSortModelChange | (param: GridSortModelParams) => void | | Callback fired when the sort model changes before a column is sorted. | diff --git a/docs/pages/api-docs/x-grid.md b/docs/pages/api-docs/x-grid.md index a105537ee1692..1ec8c66c19683 100644 --- a/docs/pages/api-docs/x-grid.md +++ b/docs/pages/api-docs/x-grid.md @@ -45,10 +45,19 @@ import { XGrid } from '@material-ui/x-grid'; | logger | Logger | null | Pass a custom logger in the components that implements the 'Logger' interface. | | logLevel | string | false | false | Allows to pass the logging level or false to turn off logging. | | nonce | string | | Nonce of the inline styles for [Content Security Policy](https://www.w3.org/TR/2016/REC-CSP2-20161215/#script-src-the-nonce-attribute). | -| onCellClick | (param: GridCellParams) => void | | Callback fired when a click event comes from a cell element. | -| onCellHover | (param: GridCellParams) => void | | Callback fired when a hover event comes from a cell element. | +| onCellClick | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a click event comes from a cell element. | +| onCellDoubleClick | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a double click event comes from a cell element. | +| onCellOver | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a mouse over event comes from a cell element. | +| onCellOut | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a mouse out comes from a cell element. | +| onCellEnter | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a mouse enter event comes from a cell element. | +| onCellLeave | (param: GridCellParams, event: React.MouseEvent) => void | | Callback fired when a mouse leave event comes from a cell element. | | onCellModeChange | (params: GridCellModeChangeParams) => void | | Callback fired when the cell mode changed. | -| onColumnHeaderClick | (param: GridColParams) => void | | Callback fired when a click event comes from a column header element. | +| onColumnHeaderClick | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a click event comes from a column header element. | +| onColumnHeaderDoubleClick | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a double click event comes from a column header element. | +| onColumnHeaderOver | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a mouseover event comes from a column header element. | +| onColumnHeaderOut | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a mouseout event comes from a column header element. | +| onColumnHeaderEnter | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a mouse enter event comes from a column header element. | +| onColumnHeaderLeave | (param: GridColParams, event: React.MouseEvent) => void | | Callback fired when a mouse leave event comes from a column header element. | | onError | (args: any) => void | | Callback fired when an exception is thrown in the grid, or when the `showError` API method is called. | | onEditCellChange | (params: GridEditCellParams) => void | | Callback fired when the edit cell value changed. | | onEditCellChangeCommitted | (params: GridEditCellParams) => void | | Callback fired when the cell changes are committed. | @@ -56,8 +65,12 @@ import { XGrid } from '@material-ui/x-grid'; | onFilterModelChange | (params: GridFilterModelParams) => void | | Callback fired when the Filter model changes before the filters are applied. | | onPageChange | (param: GridPageChangeParams) => void | | Callback fired when the current page has changed. | | onPageSizeChange | (param: GridPageChangeParams) => void | | Callback fired when the page size has changed. | -| onRowClick | (param: GridRowParams) => void | | Callback fired when a click event comes from a row container element. | -| onRowHover | (param: GridRowParams) => void | | Callback fired when a hover event comes from a row container element. | +| onRowClick | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a click event comes from a row container element. | +| onRowDoubleClick | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a double click event comes from a row container element. | +| onRowOver | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a mouse over comes from a row container element. | +| onRowOut | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a mouse out comes from a row container element. | +| onRowEnter | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a mouse enter comes from a row container element. | +| onRowLeave | (param: GridRowParams, event: React.MouseEvent) => void | | Callback fired when a mouse leave event comes from a row container element. | | onRowSelected | (param: GridRowSelectedParams) => void | | Callback fired when one row is selected. | | onSelectionModelChange | (param: GridSelectionModelChangeParams) => void | | Callback fired when the selection state of one or multiple rows changes. | | onSortModelChange | (param: GridSortModelParams) => void | | Callback fired when the sort model changes before a column is sorted. | diff --git a/docs/src/pages/components/data-grid/overview/DataGridDemo.tsx b/docs/src/pages/components/data-grid/overview/DataGridDemo.tsx index dea26492e138f..f4fa10db58e5b 100644 --- a/docs/src/pages/components/data-grid/overview/DataGridDemo.tsx +++ b/docs/src/pages/components/data-grid/overview/DataGridDemo.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { DataGrid, GridColDef, ValueGetterParams } from '@material-ui/data-grid'; +import { DataGrid, GridColDef, GridValueGetterParams } from '@material-ui/data-grid'; const columns: GridColDef[] = [ { field: 'id', headerName: 'ID', width: 70 }, @@ -17,7 +17,7 @@ const columns: GridColDef[] = [ description: 'This column has a value getter and is not sortable.', sortable: false, width: 160, - valueGetter: (params: ValueGetterParams) => + valueGetter: (params: GridValueGetterParams) => `${params.getValue('firstName') || ''} ${params.getValue('lastName') || ''}`, }, ]; diff --git a/docs/src/pages/components/data-grid/rendering/ValueFormatterGrid.tsx b/docs/src/pages/components/data-grid/rendering/ValueFormatterGrid.tsx index 5fb869f12b81e..07d7c704bd91f 100644 --- a/docs/src/pages/components/data-grid/rendering/ValueFormatterGrid.tsx +++ b/docs/src/pages/components/data-grid/rendering/ValueFormatterGrid.tsx @@ -1,11 +1,15 @@ import * as React from 'react'; -import { DataGrid, GridColDef, ValueFormatterParams } from '@material-ui/data-grid'; +import { + DataGrid, + GridColDef, + GridValueFormatterParams, +} from '@material-ui/data-grid'; const columns: GridColDef[] = [ { field: 'date', headerName: 'Year', - valueFormatter: (params: ValueFormatterParams) => + valueFormatter: (params: GridValueFormatterParams) => (params.value as Date).getFullYear(), }, ]; diff --git a/docs/src/pages/components/data-grid/rendering/ValueGetterGrid.tsx b/docs/src/pages/components/data-grid/rendering/ValueGetterGrid.tsx index 6140bb77d9cb9..7e810039f4ea1 100644 --- a/docs/src/pages/components/data-grid/rendering/ValueGetterGrid.tsx +++ b/docs/src/pages/components/data-grid/rendering/ValueGetterGrid.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; -import { DataGrid, GridColDef, ValueGetterParams } from '@material-ui/data-grid'; +import { DataGrid, GridColDef, GridValueGetterParams } from '@material-ui/data-grid'; -function getFullName(params: ValueGetterParams) { +function getFullName(params: GridValueGetterParams) { return `${params.getValue('firstName') || ''} ${ params.getValue('lastName') || '' }`; diff --git a/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.tsx b/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.tsx index 13416b60a4936..77de800d57aae 100644 --- a/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.tsx +++ b/docs/src/pages/components/data-grid/sorting/ComparatorSortingGrid.tsx @@ -4,7 +4,7 @@ import { GridRowsProp, DataGrid, GridSortDirection, - ValueGetterParams, + GridValueGetterParams, } from '@material-ui/data-grid'; import { randomCreatedDate, @@ -16,7 +16,7 @@ const columns: GridColumns = [ { field: 'age', type: 'number' }, { field: 'username', - valueGetter: (params: ValueGetterParams) => + valueGetter: (params: GridValueGetterParams) => `${params.getValue('name') || 'unknown'} - ${params.getValue('age') || 'x'}`, sortComparator: (v1, v2, param1, param2) => param1.row.age - param2.row.age, width: 150, diff --git a/packages/grid/_modules_/grid/GridComponent.tsx b/packages/grid/_modules_/grid/GridComponent.tsx index 15489cdfe0f21..34e9a7a6a1892 100644 --- a/packages/grid/_modules_/grid/GridComponent.tsx +++ b/packages/grid/_modules_/grid/GridComponent.tsx @@ -21,6 +21,7 @@ import { useGridColumns } from './hooks/features/columns/useGridColumns'; import { useGridState } from './hooks/features/core/useGridState'; import { useGridPagination } from './hooks/features/pagination/useGridPagination'; import { useGridPreferencesPanel } from './hooks/features/preferencesPanel/useGridPreferencesPanel'; +import { useGridParamsApi } from './hooks/features/rows/useGridParamsApi'; import { useGridRows } from './hooks/features/rows/useGridRows'; import { useGridEditRows } from './hooks/features/rows/useGridEditRows'; import { useGridSorting } from './hooks/features/sorting/useGridSorting'; @@ -75,6 +76,7 @@ export const GridComponent = React.forwardRef = React.memo((props) => { height, isEditable, rowIndex, + rowId, showRightBorder, tabIndex, value, @@ -40,6 +51,17 @@ export const GridCell: React.FC = React.memo((props) => { const valueToRender = formattedValue || value; const cellRef = React.useRef(null); + const apiRef = React.useContext(GridApiContext); + + const cssClasses = classnames( + GRID_CELL_CSS_CLASS, + cssClass, + `MuiDataGrid-cell${capitalize(align)}`, + { + 'MuiDataGrid-withBorder': showRightBorder, + 'MuiDataGrid-cellEditable': isEditable, + }, + ); React.useEffect(() => { if (hasFocus && cellRef.current) { @@ -47,27 +69,60 @@ export const GridCell: React.FC = React.memo((props) => { } }, [hasFocus]); + const publishClick = React.useCallback( + (eventName: string) => (event: React.MouseEvent) => { + const params = apiRef!.current.getCellParams(rowId, field || ''); + if (params?.colDef.disableClickEventBubbling) { + event.stopPropagation(); + } + apiRef!.current.publishEvent(eventName, params, event); + }, + [apiRef, field, rowId], + ); + + const publish = React.useCallback( + (eventName: string) => (event: React.MouseEvent) => + apiRef!.current.publishEvent( + eventName, + apiRef!.current.getCellParams(rowId!, field || ''), + event, + ), + [apiRef, field, rowId], + ); + + const mouseEventsHandlers = React.useMemo( + () => ({ + onClick: publishClick(GRID_CELL_CLICK), + onDoubleClick: publish(GRID_CELL_DOUBLE_CLICK), + onMouseOver: publish(GRID_CELL_OVER), + onMouseOut: publish(GRID_CELL_OUT), + onMouseEnter: publish(GRID_CELL_ENTER), + onMouseLeave: publish(GRID_CELL_LEAVE), + }), + [publish, publishClick], + ); + + const style = { + minWidth: width, + maxWidth: width, + lineHeight: `${height - 1}px`, + minHeight: height, + maxHeight: height, + }; + return (
{children || valueToRender?.toString()}
@@ -75,18 +130,3 @@ export const GridCell: React.FC = React.memo((props) => { }); GridCell.displayName = 'GridCell'; - -interface EmptyCellProps { - width?: number; - height?: number; -} - -export const GridLeftEmptyCell: React.FC = React.memo(({ width, height }) => - !width || !height ? null : , -); -GridLeftEmptyCell.displayName = 'GridLeftEmptyCell'; - -export const GridRightEmptyCell: React.FC = React.memo(({ width, height }) => - !width || !height ? null : , -); -GridRightEmptyCell.displayName = 'GridRightEmptyCell'; diff --git a/packages/grid/_modules_/grid/components/GridEmptyCell.tsx b/packages/grid/_modules_/grid/components/GridEmptyCell.tsx new file mode 100644 index 0000000000000..02ba236662184 --- /dev/null +++ b/packages/grid/_modules_/grid/components/GridEmptyCell.tsx @@ -0,0 +1,29 @@ +import * as React from 'react'; +import { GRID_CELL_CSS_CLASS } from '../constants/cssClassesConstants'; + +export interface GridEmptyCellProps { + width?: number; + height?: number; +} + +export const GridEmptyCell = React.memo(function GridEmptyCell({ + width, + height, +}: GridEmptyCellProps) { + if (!width || !height) { + return null; + } + + return ( +
+ ); +}); diff --git a/packages/grid/_modules_/grid/components/GridRow.tsx b/packages/grid/_modules_/grid/components/GridRow.tsx index 427ceaf65ddf7..271355647b1a6 100644 --- a/packages/grid/_modules_/grid/components/GridRow.tsx +++ b/packages/grid/_modules_/grid/components/GridRow.tsx @@ -1,4 +1,12 @@ import * as React from 'react'; +import { + GRID_ROW_DOUBLE_CLICK, + GRID_ROW_CLICK, + GRID_ROW_ENTER, + GRID_ROW_LEAVE, + GRID_ROW_OUT, + GRID_ROW_OVER, +} from '../constants/eventsConstants'; import { GridRowId } from '../models'; import { GRID_ROW_CSS_CLASS } from '../constants/cssClassesConstants'; import { classnames } from '../utils'; @@ -18,6 +26,29 @@ export const GridRow: React.FC = ({ selected, id, className, rowIndex, const apiRef = React.useContext(GridApiContext); const rowHeight = useGridSelector(apiRef, gridDensityRowHeightSelector); + const publish = React.useCallback( + (eventName: string) => (event: React.MouseEvent) => + apiRef!.current.publishEvent(eventName, apiRef?.current.getRowParams(id), event), + [apiRef, id], + ); + + const mouseEventsHandlers = React.useMemo( + () => ({ + onClick: publish(GRID_ROW_CLICK), + onDoubleClick: publish(GRID_ROW_DOUBLE_CLICK), + onMouseOver: publish(GRID_ROW_OVER), + onMouseOut: publish(GRID_ROW_OUT), + onMouseEnter: publish(GRID_ROW_ENTER), + onMouseLeave: publish(GRID_ROW_LEAVE), + }), + [publish], + ); + + const style = { + maxHeight: rowHeight, + minHeight: rowHeight, + }; + return (
= ({ selected, id, className, rowIndex, className={classnames(GRID_ROW_CSS_CLASS, className, { 'Mui-selected': selected })} aria-rowindex={ariaRowIndex} aria-selected={selected} - style={{ - maxHeight: rowHeight, - minHeight: rowHeight, - }} + style={style} + {...mouseEventsHandlers} > {children}
diff --git a/packages/grid/_modules_/grid/components/GridRowCells.tsx b/packages/grid/_modules_/grid/components/GridRowCells.tsx index d8af0d65cf69e..4bfa1e3d34adc 100644 --- a/packages/grid/_modules_/grid/components/GridRowCells.tsx +++ b/packages/grid/_modules_/grid/components/GridRowCells.tsx @@ -11,7 +11,6 @@ import { import { GridCell, GridCellProps } from './GridCell'; import { GridApiContext } from './GridApiContext'; import { classnames, isFunction } from '../utils'; -import { buildGridCellParams } from '../utils/paramsUtils'; import { gridDensityRowHeightSelector } from '../hooks/features/density/densitySelector'; import { useGridSelector } from '../hooks/features/core/useGridSelector'; @@ -50,9 +49,9 @@ export const GridRowCells: React.FC = React.memo((props) => { cellFocus, showCellRightBorder, } = props; - const api = React.useContext(GridApiContext); - const rowHeight = useGridSelector(api, gridDensityRowHeightSelector); - const editRowsState = useGridSelector(api, gridEditRowsStateSelector); + 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; @@ -63,15 +62,7 @@ export const GridRowCells: React.FC = React.memo((props) => { ? showCellRightBorder : !removeLastBorderRight && !props.extendRowFullWidth; - let value = row[column.field!]; - const cellParams: GridCellParams = buildGridCellParams({ - rowModel: row, - colDef: column, - rowIndex, - colIndex: colIdx, - value, - api: api!.current!, - }); + const cellParams: GridCellParams = apiRef!.current.getCellParams(row.id, column.field); let cssClassProp = { cssClass: '' }; if (column.cellClassName) { @@ -90,18 +81,6 @@ export const GridRowCells: React.FC = React.memo((props) => { const editCellState = editRowsState[row.id] && editRowsState[row.id][column.field]; let cellComponent: React.ReactElement | null = null; - if (column.valueGetter) { - // Value getter override the original value - value = column.valueGetter(cellParams); - cellParams.value = value; - } - - let formattedValueProp = {}; - if (column.valueFormatter) { - // TODO add formatted value to cellParams? - formattedValueProp = { formattedValue: column.valueFormatter(cellParams) }; - } - if (editCellState == null && column.renderCell) { cellComponent = column.renderCell(cellParams); cssClassProp = { cssClass: `${cssClassProp.cssClass} MuiDataGrid-cellWithRenderer` }; @@ -114,23 +93,24 @@ export const GridRowCells: React.FC = React.memo((props) => { } const cellProps: GridCellProps & { children: any } = { - value, + value: cellParams.value, field: column.field, width, + rowId: row.id, height: rowHeight, showRightBorder, - ...formattedValueProp, + formattedValue: cellParams.formattedValue, align: column.align || 'left', ...cssClassProp, tabIndex: domIndex === 0 && colIdx === 0 ? 0 : -1, rowIndex, - colIndex: colIdx + firstColIdx, + colIndex: cellParams.colIndex, children: cellComponent, isEditable: cellParams.isEditable, hasFocus: cellFocus !== null && cellFocus.rowIndex === rowIndex && - cellFocus.colIndex === colIdx + firstColIdx, + cellFocus.colIndex === cellParams.colIndex, }; return cellProps; diff --git a/packages/grid/_modules_/grid/components/GridViewport.tsx b/packages/grid/_modules_/grid/components/GridViewport.tsx index 3d72a8db63542..8f30cc9ca577b 100644 --- a/packages/grid/_modules_/grid/components/GridViewport.tsx +++ b/packages/grid/_modules_/grid/components/GridViewport.tsx @@ -9,8 +9,8 @@ import { gridSelectionStateSelector } from '../hooks/features/selection/gridSele import { renderStateSelector } from '../hooks/features/virtualization/renderingStateSelector'; import { optionsSelector } from '../hooks/utils/optionsSelector'; import { GridApiContext } from './GridApiContext'; -import { GridLeftEmptyCell, GridRightEmptyCell } from './GridCell'; import { GridDataContainer } from './containers/GridDataContainer'; +import { GridEmptyCell } from './GridEmptyCell'; import { GridRenderingZone } from './GridRenderingZone'; import { GridRow } from './GridRow'; import { GridRowCells } from './GridRowCells'; @@ -56,7 +56,7 @@ export const GridViewport: ViewportType = React.forwardRef( selected={!!selectionState[r.id]} rowIndex={renderState.renderContext!.firstRowIdx! + idx} > - + ( cellFocus={cellFocus} domIndex={idx} /> - + )); }; diff --git a/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaderItem.tsx b/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaderItem.tsx index f74774a9efe6c..a0e01a2c83dc8 100644 --- a/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaderItem.tsx +++ b/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaderItem.tsx @@ -1,8 +1,14 @@ import * as React from 'react'; -import { GRID_COLUMN_HEADER_CLICK } from '../../constants/eventsConstants'; +import { + GRID_COLUMN_HEADER_CLICK, + GRID_COLUMN_HEADER_DOUBLE_CLICK, + GRID_COLUMN_HEADER_ENTER, + GRID_COLUMN_HEADER_LEAVE, + GRID_COLUMN_HEADER_OUT, + GRID_COLUMN_HEADER_OVER, +} from '../../constants/eventsConstants'; import { GridColDef, GRID_NUMBER_COLUMN_TYPE } from '../../models/colDef/index'; import { GridOptions } from '../../models/gridOptions'; -import { GridColParams } from '../../models/params/gridColParams'; import { GridSortDirection } from '../../models/gridSortModel'; import { GridApiContext } from '../GridApiContext'; import { GRID_HEADER_CELL_CSS_CLASS } from '../../constants/cssClassesConstants'; @@ -73,15 +79,28 @@ export const GridColumnHeaderItem = ({ }), [apiRef, column], ); - const onHeaderTitleClick = React.useCallback(() => { - const colHeaderParams: GridColParams = { - field: column.field, - colDef: column, - colIndex, - api: apiRef!.current, - }; - apiRef!.current.publishEvent(GRID_COLUMN_HEADER_CLICK, colHeaderParams); - }, [apiRef, colIndex, column]); + + const publish = React.useCallback( + (eventName: string) => (event: React.MouseEvent) => + apiRef!.current.publishEvent( + eventName, + apiRef!.current.getColumnHeaderParams(column.field), + event, + ), + [apiRef, column.field], + ); + + const mouseEventsHandlers = React.useMemo( + () => ({ + onClick: publish(GRID_COLUMN_HEADER_CLICK), + onDoubleClick: publish(GRID_COLUMN_HEADER_DOUBLE_CLICK), + onMouseOver: publish(GRID_COLUMN_HEADER_OVER), + onMouseOut: publish(GRID_COLUMN_HEADER_OUT), + onMouseEnter: publish(GRID_COLUMN_HEADER_ENTER), + onMouseLeave: publish(GRID_COLUMN_HEADER_LEAVE), + }), + [publish], + ); const cssClasses = classnames( GRID_HEADER_CELL_CSS_CLASS, @@ -125,7 +144,6 @@ export const GridColumnHeaderItem = ({ const columnMenuIconButton = ; return ( - // eslint-disable-next-line jsx-a11y/click-events-have-key-events
{!disableColumnMenu && isColumnNumeric && !column.disableColumnMenu && columnMenuIconButton} diff --git a/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaders.tsx b/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaders.tsx index 94f8569d53739..1eaf265c449cf 100644 --- a/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaders.tsx +++ b/packages/grid/_modules_/grid/components/columnHeaders/GridColumnHeaders.tsx @@ -5,7 +5,7 @@ import { useGridSelector } from '../../hooks/features/core/useGridSelector'; import { renderStateSelector } from '../../hooks/features/virtualization/renderingStateSelector'; import { optionsSelector } from '../../hooks/utils/optionsSelector'; import { GridApiContext } from '../GridApiContext'; -import { GridLeftEmptyCell, GridRightEmptyCell } from '../GridCell'; +import { GridEmptyCell } from '../GridEmptyCell'; import { GridScrollArea } from '../GridScrollArea'; import { gridContainerSizesSelector } from '../GridViewport'; import { GridColumnHeadersItemCollection } from './GridColumnHeadersItemCollection'; @@ -51,9 +51,9 @@ export const GridColumnsHeader = React.forwardRef(function G style={{ minWidth: containerSizes?.totalSizes?.width }} onDragOver={handleDragOver} > - + - +
diff --git a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx index 7f6cbecf4bd12..16cb6306d2777 100644 --- a/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx +++ b/packages/grid/_modules_/grid/components/editCell/EditInputCell.tsx @@ -7,7 +7,9 @@ import { GridEditRowApi } from '../../models/api/gridEditRowApi'; export function EditInputCell(props: GridCellParams & InputBaseProps) { const { + id, value, + formattedValue, api, field, row, @@ -51,7 +53,7 @@ export function EditInputCell(props: GridCellParams & InputBaseProps) { ); const inputType = mapColDefTypeToInputType(colDef.type); - const formattedValue = + const inputFormattedValue = valueState && isDate(valueState) ? formatDateToLocalInputDate({ value: valueState, withTime: colDef.type === 'dateTime' }) : valueState; @@ -66,7 +68,7 @@ export function EditInputCell(props: GridCellParams & InputBaseProps) { fullWidth className="MuiDataGrid-editCellInputBase" onKeyDown={onKeyDown} - value={formattedValue} + value={inputFormattedValue} onChange={onValueChange} type={inputType} {...inputBaseProps} diff --git a/packages/grid/_modules_/grid/components/index.ts b/packages/grid/_modules_/grid/components/index.ts index 35b31972b71a0..f25672967a6ca 100644 --- a/packages/grid/_modules_/grid/components/index.ts +++ b/packages/grid/_modules_/grid/components/index.ts @@ -22,3 +22,4 @@ export * from './GridStickyContainer'; export * from './GridViewport'; export * from './Watermark'; export * from './GridScrollArea'; +export * from './GridEmptyCell'; diff --git a/packages/grid/_modules_/grid/constants/eventsConstants.ts b/packages/grid/_modules_/grid/constants/eventsConstants.ts index 4dab2c660ba8d..82f69f38ffa8d 100644 --- a/packages/grid/_modules_/grid/constants/eventsConstants.ts +++ b/packages/grid/_modules_/grid/constants/eventsConstants.ts @@ -1,8 +1,5 @@ // Web standard events export const GRID_RESIZE = 'resize'; -export const GRID_CLICK = 'click'; -export const GRID_DOUBLE_CLICK = 'dblclick'; -export const GRID_MOUSE_HOVER = 'mouseover'; export const GRID_FOCUS_OUT = 'focusout'; export const GRID_KEYDOWN = 'keydown'; export const GRID_KEYUP = 'keyup'; @@ -10,31 +7,42 @@ export const GRID_SCROLL = 'scroll'; export const GRID_DRAGEND = 'dragend'; // GRID events -export const GRID_CELL_CHANGE = 'cellChange'; -export const GRID_CELL_CHANGE_COMMITTED = 'cellChangeCommitted'; -export const GRID_CELL_MODE_CHANGE = 'cellModeChange'; -export const GRID_EDIT_ROW_MODEL_CHANGE = 'editRowModelChange'; export const GRID_COMPONENT_ERROR = 'componentError'; export const GRID_UNMOUNT = 'unmount'; export const GRID_ELEMENT_FOCUS_OUT = 'gridFocusOut'; + +export const GRID_CELL_CHANGE = 'cellChange'; +export const GRID_CELL_CHANGE_COMMITTED = 'cellChangeCommitted'; +export const GRID_CELL_MODE_CHANGE = 'cellModeChange'; export const GRID_CELL_CLICK = 'cellClick'; -export const GRID_DOUBLE_CELL_CLICK = 'doubleCellClick'; -export const GRID_CELL_HOVER = 'cellHover'; +export const GRID_CELL_DOUBLE_CLICK = 'cellDoubleClick'; +export const GRID_CELL_OVER = 'cellOver'; +export const GRID_CELL_OUT = 'cellOut'; +export const GRID_CELL_ENTER = 'cellEnter'; +export const GRID_CELL_LEAVE = 'cellLeave'; + export const GRID_ROW_CLICK = 'rowClick'; -export const GRID_DOUBLE_ROW_CLICK = 'doubleRowClick'; -export const GRID_ROW_HOVER = 'rowHover'; +export const GRID_ROW_DOUBLE_CLICK = 'rowDoubleClick'; +export const GRID_ROW_OVER = 'rowOver'; +export const GRID_ROW_OUT = 'rowOut'; +export const GRID_ROW_ENTER = 'rowEnter'; +export const GRID_ROW_LEAVE = 'rowLeave'; + +export const GRID_COLUMN_HEADER_CLICK = 'columnHeaderClick'; +export const GRID_COLUMN_HEADER_DOUBLE_CLICK = 'columnHeaderDoubleClick'; +export const GRID_COLUMN_HEADER_OVER = 'columnHeaderOver'; +export const GRID_COLUMN_HEADER_OUT = 'columnHeaderOut'; +export const GRID_COLUMN_HEADER_ENTER = 'columnHeaderEnter'; +export const GRID_COLUMN_HEADER_LEAVE = 'columnHeaderLeave'; + +export const GRID_EDIT_ROW_MODEL_CHANGE = 'editRowModelChange'; export const GRID_ROW_SELECTED = 'rowSelected'; export const GRID_SELECTION_CHANGED = 'selectionChange'; -export const GRID_COLUMN_HEADER_CLICK = 'columnClick'; -export const GRID_COLUMN_HEADER_HOVER = 'columnHeaderHover'; + export const GRID_PAGE_CHANGED = 'pageChange'; export const GRID_PAGESIZE_CHANGED = 'pageSizeChange'; -export const GRID_COLUMN_FILTER_BUTTON_CLICK = 'colFilterButtonClick'; -export const GRID_COLUMN_MENU_BUTTON_CLICK = 'colMenuButtonClick'; -export const GRID_SCROLLING_START = 'scrolling:start'; export const GRID_SCROLLING = 'scrolling'; -export const GRID_SCROLLING_STOP = 'scrolling:stop'; export const GRID_COL_RESIZE_START = 'colResizing:start'; export const GRID_COL_RESIZE_STOP = 'colResizing:stop'; diff --git a/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts b/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts index 43de15831459e..48b9d1bc1b47b 100644 --- a/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts +++ b/packages/grid/_modules_/grid/hooks/features/filter/useGridFilter.ts @@ -11,7 +11,6 @@ import { GridFeatureModeConstant } from '../../../models/gridFeatureMode'; import { GridFilterItem, GridLinkOperator } from '../../../models/gridFilterItem'; import { GridFilterModelParams } from '../../../models/params/gridFilterModelParams'; import { GridRowsProp } from '../../../models/gridRows'; -import { buildGridCellParams } from '../../../utils/paramsUtils'; import { isDeepEqual } from '../../../utils/utils'; import { useGridApiEventHandler } from '../../root/useGridApiEventHandler'; import { useGridApiMethod } from '../../root/useGridApiMethod'; @@ -88,14 +87,8 @@ export const useGridFilter = (apiRef: GridApiRef, rowsProp: GridRowsProp): void // This way we have latest rows on the first rendering const rows = sortedGridRowsSelector(state); - rows.forEach((row, rowIndex) => { - const params = buildGridCellParams({ - rowModel: row, - colDef: column, - rowIndex, - value: row[column.field], - api: apiRef!.current!, - }); + rows.forEach((row) => { + const params = apiRef.current.getCellParams(row.id, filterItem.columnField!); const isShown = applyFilterOnRow(params); if (visibleRowsLookup[row.id] == null) { diff --git a/packages/grid/_modules_/grid/hooks/features/rows/index.ts b/packages/grid/_modules_/grid/hooks/features/rows/index.ts index e2f92894b076c..8eb76004cacb3 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/index.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/index.ts @@ -1,5 +1,6 @@ export * from './gridRowsSelector'; export * from './gridRowsState'; +export * from './useGridParamsApi'; export * from './useGridRows'; export * from './gridEditRowsSelector'; export * from './useGridEditRows'; diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts index ebbc87ad26332..f97034ede3a57 100644 --- a/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridEditRows.ts @@ -17,7 +17,6 @@ import { GridEditCellParams, GridEditRowModelParams, } from '../../../models/params/gridEditCellParams'; -import { buildGridCellParams } from '../../../utils/paramsUtils'; import { useGridApiEventHandler } from '../../root/useGridApiEventHandler'; import { useGridApiMethod } from '../../root/useGridApiMethod'; import { optionsSelector } from '../../utils/optionsSelector'; @@ -28,27 +27,6 @@ export function useGridEditRows(apiRef: GridApiRef) { const [, setGridState, forceUpdate] = useGridState(apiRef); const options = useGridSelector(apiRef, optionsSelector); - const getCellValue = React.useCallback( - (id: GridRowId, field: string) => { - const colDef = apiRef.current.getColumnFromField(field); - const rowModel = apiRef.current.getRowFromId(id); - - if (!colDef || !colDef.valueGetter) { - return rowModel[field]; - } - - return colDef.valueGetter( - buildGridCellParams({ - value: rowModel[field], - colDef, - rowModel, - api: apiRef.current, - }), - ); - }, - [apiRef], - ); - const setCellEditMode = React.useCallback( (id, field) => { setGridState((state) => { @@ -58,7 +36,7 @@ export function useGridEditRows(apiRef: GridApiRef) { const currentCellEditState: GridEditRowsModel = { ...state.editRows }; currentCellEditState[id] = { ...currentCellEditState[id] } || {}; - currentCellEditState[id][field] = { value: getCellValue(id, field) }; + currentCellEditState[id][field] = { value: apiRef.current.getCellValue(id, field) }; const newEditRowsState: GridEditRowsModel = { ...state.editRows, ...currentCellEditState }; @@ -78,7 +56,7 @@ export function useGridEditRows(apiRef: GridApiRef) { }; apiRef.current.publishEvent(GRID_EDIT_ROW_MODEL_CHANGE, editRowParams); }, - [apiRef, forceUpdate, getCellValue, setGridState], + [apiRef, forceUpdate, setGridState], ); const setCellViewMode = React.useCallback( @@ -219,7 +197,6 @@ export function useGridEditRows(apiRef: GridApiRef) { useGridApiMethod( apiRef, { - getCellValue, setCellMode, onEditRowModelChange, onCellModeChange, diff --git a/packages/grid/_modules_/grid/hooks/features/rows/useGridParamsApi.ts b/packages/grid/_modules_/grid/hooks/features/rows/useGridParamsApi.ts new file mode 100644 index 0000000000000..4c533f6d3ff02 --- /dev/null +++ b/packages/grid/_modules_/grid/hooks/features/rows/useGridParamsApi.ts @@ -0,0 +1,163 @@ +import * as React from 'react'; +import { GridApiRef } from '../../../models/api/gridApiRef'; +import { GridParamsApi } from '../../../models/api/gridParamsApi'; +import { GridRowId } from '../../../models/gridRows'; +import { GridCellParams, GridValueGetterParams } from '../../../models/params/gridCellParams'; +import { GridColParams } from '../../../models/params/gridColParams'; +import { GridRowParams } from '../../../models/params/gridRowParams'; +import { + getGridCellElement, + getGridColumnHeaderElement, + getGridRowElement, +} from '../../../utils/domUtils'; +import { useGridApiMethod } from '../../root/useGridApiMethod'; + +let warnedOnce = false; +function warnMissingColumn(field) { + if (!warnedOnce && process.env.NODE_ENV !== 'production') { + console.warn( + [ + `Material-UI: You are calling getValue('${field}') but the column \`${field}\` is not defined.`, + `Instead, you can access the data from \`params.row.${field}\`.`, + ].join('\n'), + ); + warnedOnce = true; + } +} +export function useGridParamsApi(apiRef: GridApiRef) { + const getColumnHeaderParams = React.useCallback( + (field: string): GridColParams => ({ + field, + element: apiRef.current.getColumnHeaderElement(field), + colDef: apiRef.current.getColumnFromField(field), + colIndex: apiRef.current.getColumnIndex(field, true), + api: apiRef!.current, + }), + [apiRef], + ); + + const getRowParams = React.useCallback( + (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, + }; + return params; + }, + [apiRef], + ); + + 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), + rowIndex: apiRef.current.getRowIndexFromId(id), + colIndex: apiRef.current.getColumnIndex(field, true), + api: apiRef.current, + }; + + return params; + }, + [apiRef], + ); + + const getCellParams = React.useCallback( + (id: GridRowId, field: string) => { + const colDef = apiRef.current.getColumnFromField(field); + const element = apiRef.current.getCellElement(id, field); + const value = apiRef.current.getCellValue(id, field); + const baseParams = getBaseCellParams(id, field); + const params: GridCellParams = { + ...baseParams, + value, + getValue: (columnField: string) => apiRef.current.getCellValue(id, columnField), + formattedValue: value, + }; + if (colDef.valueFormatter) { + params.formattedValue = colDef.valueFormatter(params); + } + const isEditableAttr = element && element.getAttribute('data-editable'); + params.isEditable = + isEditableAttr != null + ? isEditableAttr === 'true' + : colDef && apiRef.current.isCellEditable(params); + + return params; + }, + [apiRef, getBaseCellParams], + ); + + const getCellValue = React.useCallback( + (id: GridRowId, field: string) => { + const colDef = apiRef.current.getColumnFromField(field); + const rowModel = apiRef.current.getRowFromId(id); + + if (!colDef) { + warnMissingColumn(field); + } + if (!colDef || !colDef.valueGetter) { + return rowModel[field]; + } + + return colDef.valueGetter(getBaseCellParams(id, field)); + }, + [apiRef, getBaseCellParams], + ); + + const getColumnHeaderElement = React.useCallback( + (field: string): HTMLDivElement | null => { + if (!apiRef.current.rootElementRef!.current) { + return null; + } + return getGridColumnHeaderElement(apiRef.current.rootElementRef!.current!, field); + }, + [apiRef], + ); + const getRowElement = React.useCallback( + (id: GridRowId): HTMLDivElement | null => { + if (!apiRef.current.rootElementRef!.current) { + return null; + } + return getGridRowElement(apiRef.current.rootElementRef!.current!, id); + }, + [apiRef], + ); + + const getCellElement = React.useCallback( + (id: GridRowId, field: string): HTMLDivElement | null => { + if (!apiRef.current.rootElementRef!.current) { + return null; + } + return getGridCellElement(apiRef.current.rootElementRef!.current!, { id, field }); + }, + [apiRef], + ); + + useGridApiMethod( + apiRef, + { + getCellValue, + getCellParams, + getCellElement, + getRowParams, + getRowElement, + getColumnHeaderParams, + getColumnHeaderElement, + }, + 'CellApi', + ); +} diff --git a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts index 4304ab1e91369..5081728c899c7 100644 --- a/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts +++ b/packages/grid/_modules_/grid/hooks/features/sorting/useGridSorting.ts @@ -23,7 +23,6 @@ import { GridSortModel, GridSortDirection, } from '../../../models/gridSortModel'; -import { buildGridCellParams } from '../../../utils/paramsUtils'; import { isDesc, nextGridSortDirection } from '../../../utils/sortingUtils'; import { isDeepEqual } from '../../../utils/utils'; import { useGridApiEventHandler } from '../../root/useGridApiEventHandler'; @@ -104,18 +103,8 @@ export const useGridSorting = (apiRef: GridApiRef, rowsProp: GridRowsProp) => { comparator( row1[field], row2[field], - buildGridCellParams({ - api: apiRef.current, - colDef: apiRef.current.getColumnFromField(field), - rowModel: row1, - value: row1[field], - }), - buildGridCellParams({ - api: apiRef.current, - colDef: apiRef.current.getColumnFromField(field), - rowModel: row2, - value: row2[field], - }), + apiRef.current.getCellParams(row1.id, field), + apiRef.current.getCellParams(row2.id, field), ); return res; }, 0); diff --git a/packages/grid/_modules_/grid/hooks/root/useEvents.ts b/packages/grid/_modules_/grid/hooks/root/useEvents.ts index 2a10d3416583f..375c59b5322ea 100644 --- a/packages/grid/_modules_/grid/hooks/root/useEvents.ts +++ b/packages/grid/_modules_/grid/hooks/root/useEvents.ts @@ -1,14 +1,10 @@ import * as React from 'react'; import { GridApiRef } from '../../models/api/gridApiRef'; -import { GridCellParams } from '../../models/params/gridCellParams'; -import { GridColParams } from '../../models/params/gridColParams'; -import { GridRowParams } from '../../models/params/gridRowParams'; import { useGridSelector } from '../features/core/useGridSelector'; import { optionsSelector } from '../utils/optionsSelector'; import { useLogger } from '../utils/useLogger'; import { GRID_CELL_CLICK, - GRID_CLICK, GRID_COL_RESIZE_START, GRID_COL_RESIZE_STOP, GRID_COLUMN_HEADER_CLICK, @@ -17,23 +13,28 @@ import { GRID_KEYUP, GRID_RESIZE, GRID_ROW_CLICK, - GRID_MOUSE_HOVER, - GRID_CELL_HOVER, - GRID_ROW_HOVER, - GRID_COLUMN_HEADER_HOVER, + GRID_CELL_OVER, + GRID_ROW_OVER, GRID_FOCUS_OUT, GRID_ELEMENT_FOCUS_OUT, GRID_COMPONENT_ERROR, GRID_STATE_CHANGE, - GRID_DOUBLE_CELL_CLICK, - GRID_DOUBLE_ROW_CLICK, - GRID_DOUBLE_CLICK, + GRID_CELL_DOUBLE_CLICK, + GRID_ROW_DOUBLE_CLICK, + GRID_CELL_ENTER, + GRID_CELL_LEAVE, + GRID_CELL_OUT, + GRID_ROW_ENTER, + GRID_ROW_LEAVE, + GRID_ROW_OUT, + GRID_COLUMN_HEADER_LEAVE, + GRID_COLUMN_HEADER_ENTER, + GRID_COLUMN_HEADER_DOUBLE_CLICK, + GRID_COLUMN_HEADER_OVER, + GRID_COLUMN_HEADER_OUT, } from '../../constants/eventsConstants'; -import { GRID_CELL_CSS_CLASS, GRID_ROW_CSS_CLASS } from '../../constants/cssClassesConstants'; -import { findParentElementFromClassName, getIdFromRowElem, isGridCell } from '../../utils/domUtils'; import { useGridApiMethod } from './useGridApiMethod'; import { useGridApiEventHandler } from './useGridApiEventHandler'; -import { buildGridCellParams, buildGridRowParams } from '../../utils/paramsUtils'; import { GridEventsApi } from '../../models/api/gridEventsApi'; export function useEvents(gridRootRef: React.RefObject, apiRef: GridApiRef): void { @@ -47,114 +48,6 @@ export function useEvents(gridRootRef: React.RefObject, apiRef: [apiRef], ); - const getEventParams = React.useCallback( - (event: MouseEvent) => { - if (event.target == null) { - throw new Error( - `Event target null - Target has been removed or component might already be unmounted.`, - ); - } - - const elem = event.target as HTMLElement; - const eventParams: { - cell?: GridCellParams; - row?: GridRowParams; - header?: GridColParams; - } = {}; - - if (isGridCell(elem)) { - const cellEl = findParentElementFromClassName(elem, GRID_CELL_CSS_CLASS)! as HTMLElement; - const rowEl = findParentElementFromClassName(elem, GRID_ROW_CSS_CLASS)! as HTMLElement; - if (rowEl == null) { - return null; - } - const id = getIdFromRowElem(rowEl); - const rowModel = apiRef.current.getRowFromId(id); - const rowIndex = apiRef.current.getRowIndexFromId(id); - const field = cellEl.getAttribute('data-field') as string; - const value = cellEl.getAttribute('data-value'); - const column = apiRef.current.getColumnFromField(field); - if (!column || !column.disableClickEventBubbling) { - const commonParams = { - data: rowModel, - rowIndex, - colDef: column, - rowModel, - api: apiRef.current, - }; - eventParams.cell = buildGridCellParams({ - ...commonParams, - element: cellEl, - value, - }); - eventParams.row = buildGridRowParams({ - ...commonParams, - element: rowEl, - }); - } - } - return eventParams; - }, - [apiRef], - ); - - const onClickHandler = React.useCallback( - (event: MouseEvent) => { - const eventParams = getEventParams(event); - - if (!eventParams) { - return; - } - - if (eventParams.cell) { - apiRef.current.publishEvent(GRID_CELL_CLICK, eventParams.cell); - } - if (eventParams.row) { - apiRef.current.publishEvent(GRID_ROW_CLICK, eventParams.row); - } - }, - [apiRef, getEventParams], - ); - - const onDoubleClickHandler = React.useCallback( - (event: MouseEvent) => { - const eventParams = getEventParams(event); - - if (!eventParams) { - return; - } - - if (eventParams.cell) { - apiRef.current.publishEvent(GRID_DOUBLE_CELL_CLICK, eventParams.cell); - } - if (eventParams.row) { - apiRef.current.publishEvent(GRID_DOUBLE_ROW_CLICK, eventParams.row); - } - }, - [apiRef, getEventParams], - ); - - const onHoverHandler = React.useCallback( - (event: MouseEvent) => { - const eventParams = getEventParams(event); - - if (!eventParams) { - return; - } - - if (eventParams.cell) { - apiRef.current.publishEvent(GRID_CELL_HOVER, eventParams.cell); - } - if (eventParams.row) { - apiRef.current.publishEvent(GRID_ROW_HOVER, eventParams.row); - } - if (eventParams.header) { - apiRef.current.publishEvent(GRID_COLUMN_HEADER_HOVER, eventParams.header); - } - }, - [apiRef, getEventParams], - ); - const onFocusOutHandler = React.useCallback( (event: FocusEvent) => { apiRef.current.publishEvent(GRID_FOCUS_OUT, event); @@ -194,13 +87,30 @@ export function useEvents(gridRootRef: React.RefObject, apiRef: useGridApiEventHandler(apiRef, GRID_COL_RESIZE_STOP, handleResizeStop); useGridApiEventHandler(apiRef, GRID_COLUMN_HEADER_CLICK, options.onColumnHeaderClick); + useGridApiEventHandler( + apiRef, + GRID_COLUMN_HEADER_DOUBLE_CLICK, + options.onColumnHeaderDoubleClick, + ); + useGridApiEventHandler(apiRef, GRID_COLUMN_HEADER_OVER, options.onColumnHeaderOver); + useGridApiEventHandler(apiRef, GRID_COLUMN_HEADER_OUT, options.onColumnHeaderOut); + useGridApiEventHandler(apiRef, GRID_COLUMN_HEADER_ENTER, options.onColumnHeaderEnter); + useGridApiEventHandler(apiRef, GRID_COLUMN_HEADER_LEAVE, options.onColumnHeaderLeave); + useGridApiEventHandler(apiRef, GRID_CELL_CLICK, options.onCellClick); + useGridApiEventHandler(apiRef, GRID_CELL_DOUBLE_CLICK, options.onCellDoubleClick); + useGridApiEventHandler(apiRef, GRID_CELL_OVER, options.onCellOver); + useGridApiEventHandler(apiRef, GRID_CELL_OUT, options.onCellOut); + useGridApiEventHandler(apiRef, GRID_CELL_ENTER, options.onCellEnter); + useGridApiEventHandler(apiRef, GRID_CELL_LEAVE, options.onCellLeave); + + useGridApiEventHandler(apiRef, GRID_ROW_DOUBLE_CLICK, options.onRowDoubleClick); useGridApiEventHandler(apiRef, GRID_ROW_CLICK, options.onRowClick); - useGridApiEventHandler(apiRef, GRID_DOUBLE_CELL_CLICK, options.onCellDoubleClick); - useGridApiEventHandler(apiRef, GRID_DOUBLE_ROW_CLICK, options.onRowDoubleClick); + useGridApiEventHandler(apiRef, GRID_ROW_OVER, options.onRowOver); + useGridApiEventHandler(apiRef, GRID_ROW_OUT, options.onRowOut); + useGridApiEventHandler(apiRef, GRID_ROW_ENTER, options.onRowEnter); + useGridApiEventHandler(apiRef, GRID_ROW_LEAVE, options.onRowLeave); - useGridApiEventHandler(apiRef, GRID_CELL_HOVER, options.onCellHover); - useGridApiEventHandler(apiRef, GRID_ROW_HOVER, options.onRowHover); useGridApiEventHandler(apiRef, GRID_COMPONENT_ERROR, options.onError); useGridApiEventHandler(apiRef, GRID_STATE_CHANGE, options.onStateChange); @@ -211,11 +121,7 @@ export function useEvents(gridRootRef: React.RefObject, apiRef: const keyUpHandler = getHandler(GRID_KEYUP); const gridRootElem = gridRootRef.current; - gridRootElem.addEventListener(GRID_CLICK, onClickHandler, { capture: true }); - gridRootElem.addEventListener(GRID_DOUBLE_CLICK, onDoubleClickHandler, { capture: true }); - gridRootElem.addEventListener(GRID_MOUSE_HOVER, onHoverHandler, { capture: true }); gridRootElem.addEventListener(GRID_FOCUS_OUT, onFocusOutHandler); - gridRootElem.addEventListener(GRID_KEYDOWN, keyDownHandler); gridRootElem.addEventListener(GRID_KEYUP, keyUpHandler); apiRef.current.isInitialised = true; @@ -224,8 +130,6 @@ export function useEvents(gridRootRef: React.RefObject, apiRef: return () => { logger.debug('Clearing all events listeners'); api.publishEvent(GRID_UNMOUNT); - gridRootElem.removeEventListener(GRID_CLICK, onClickHandler, { capture: true }); - gridRootElem.removeEventListener(GRID_MOUSE_HOVER, onHoverHandler, { capture: true }); gridRootElem.removeEventListener(GRID_FOCUS_OUT, onFocusOutHandler); gridRootElem.removeEventListener(GRID_KEYDOWN, keyDownHandler); gridRootElem.removeEventListener(GRID_KEYUP, keyUpHandler); @@ -233,15 +137,5 @@ export function useEvents(gridRootRef: React.RefObject, apiRef: }; } return undefined; - }, [ - gridRootRef, - apiRef.current?.isInitialised, - getHandler, - logger, - onClickHandler, - onDoubleClickHandler, - onHoverHandler, - onFocusOutHandler, - apiRef, - ]); + }, [gridRootRef, apiRef.current?.isInitialised, getHandler, logger, onFocusOutHandler, apiRef]); } diff --git a/packages/grid/_modules_/grid/hooks/root/useGridApiEventHandler.ts b/packages/grid/_modules_/grid/hooks/root/useGridApiEventHandler.ts index 5e88e6d5e1e03..92e611bdbf1e6 100644 --- a/packages/grid/_modules_/grid/hooks/root/useGridApiEventHandler.ts +++ b/packages/grid/_modules_/grid/hooks/root/useGridApiEventHandler.ts @@ -5,7 +5,7 @@ import { useLogger } from '../utils/useLogger'; export function useGridApiEventHandler( apiRef: GridApiRef, eventName: string, - handler?: (args: any) => void, + handler?: (...args: any) => void, ) { const logger = useLogger('useGridApiEventHandler'); diff --git a/packages/grid/_modules_/grid/hooks/root/useGridApiMethod.ts b/packages/grid/_modules_/grid/hooks/root/useGridApiMethod.ts index 26fae50738771..946667a046129 100644 --- a/packages/grid/_modules_/grid/hooks/root/useGridApiMethod.ts +++ b/packages/grid/_modules_/grid/hooks/root/useGridApiMethod.ts @@ -10,23 +10,18 @@ export function useGridApiMethod>( ) { const logger = useLogger('useGridApiMethod'); const apiMethodsRef = React.useRef(apiMethods); + const [apiMethodsNames] = React.useState(Object.keys(apiMethods)); React.useEffect(() => { apiMethodsRef.current = apiMethods; }, [apiMethods]); React.useEffect(() => { - if (!apiRef.current.isInitialised) { - return; - } - - Object.keys(apiMethods).forEach((methodName) => { + apiMethodsNames.forEach((methodName) => { if (!apiRef.current.hasOwnProperty(methodName)) { logger.debug(`Adding ${apiName}.${methodName} to apiRef`); - apiRef.current[methodName] = (...args) => { - return apiMethodsRef.current[methodName](...args); - }; + apiRef.current[methodName] = (...args) => apiMethodsRef.current[methodName](...args); } }); - }, [apiMethods, apiName, apiRef, logger]); + }, [apiMethodsNames, apiName, apiRef, logger]); } diff --git a/packages/grid/_modules_/grid/models/api/gridApi.ts b/packages/grid/_modules_/grid/models/api/gridApi.ts index 9204c3997af60..cfea6b26c03ca 100644 --- a/packages/grid/_modules_/grid/models/api/gridApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridApi.ts @@ -1,5 +1,6 @@ import { ColumnMenuApi } from './columnMenuApi'; import { ColumnResizeApi } from './columnResizeApi'; +import { GridParamsApi } from './gridParamsApi'; import { ComponentsApi } from './gridComponentsApi'; import { FilterApi } from './filterApi'; import { GridEditRowApi } from './gridEditRowApi'; @@ -28,6 +29,7 @@ export type GridApi = GridCoreApi & GridEventsApi & GridRowApi & GridEditRowApi & + GridParamsApi & GridColumnApi & ColumnReorderApi & GridSelectionApi & diff --git a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts index 0ee6b9e709602..9575f249df4ee 100644 --- a/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts +++ b/packages/grid/_modules_/grid/models/api/gridEditRowApi.ts @@ -1,4 +1,4 @@ -import { GridCellMode, GridCellValue } from '../gridCell'; +import { GridCellMode } from '../gridCell'; import { GridEditRowsModel, GridEditRowUpdate } from '../gridEditRowModel'; import { GridRowId } from '../gridRows'; import { GridCellParams } from '../params/gridCellParams'; @@ -36,12 +36,6 @@ export interface GridEditRowApi { * @param update */ commitCellChange: (id: GridRowId, update: GridEditRowUpdate) => void; - /** - * Get the cell value of a row and field. - * @param id - * @param field - */ - getCellValue: (id: GridRowId, field: string) => GridCellValue; /** * Callback fired when the EditRowModel changed. * @param handler diff --git a/packages/grid/_modules_/grid/models/api/gridParamsApi.ts b/packages/grid/_modules_/grid/models/api/gridParamsApi.ts new file mode 100644 index 0000000000000..35095dc3d812c --- /dev/null +++ b/packages/grid/_modules_/grid/models/api/gridParamsApi.ts @@ -0,0 +1,48 @@ +import { GridCellValue } from '../gridCell'; +import { GridRowId } from '../gridRows'; +import { GridCellParams } from '../params/gridCellParams'; +import { GridColParams } from '../params/gridColParams'; +import { GridRowParams } from '../params/gridRowParams'; + +export interface GridParamsApi { + /** + * Get the cell value of a row and field. + * @param id + * @param field + */ + getCellValue: (id: GridRowId, field: string) => GridCellValue; + /** + * Get the cell DOM element. + * @param id + * @param field + */ + getCellElement: (id: GridRowId, field: string) => HTMLDivElement | null; + /** + * Get the cell params that are passed in events. + * @param id + * @param field + */ + getCellParams: (id: GridRowId, field: string) => GridCellParams; + /** + * Get the row params that are passed in events. + * @param id + * @param field + */ + getRowParams: (id: GridRowId) => GridRowParams; + /** + * Get the row DOM element. + * @param id + * @param field + */ + getRowElement: (id: GridRowId) => HTMLDivElement | null; + /** + * Get the column header DOM element. + * @param field + */ + getColumnHeaderElement: (field: string) => HTMLDivElement | null; + /** + * Get the header params that are passed in events. + * @param field + */ + getColumnHeaderParams: (field: string) => GridColParams; +} diff --git a/packages/grid/_modules_/grid/models/api/index.ts b/packages/grid/_modules_/grid/models/api/index.ts index 032328ea976f8..ff15818a3ce10 100644 --- a/packages/grid/_modules_/grid/models/api/index.ts +++ b/packages/grid/_modules_/grid/models/api/index.ts @@ -1,4 +1,5 @@ export * from './gridApiRef'; +export * from './gridParamsApi'; export * from './gridCoreApi'; export * from './gridColumnApi'; export * from './gridComponentsApi'; diff --git a/packages/grid/_modules_/grid/models/colDef/gridColDef.ts b/packages/grid/_modules_/grid/models/colDef/gridColDef.ts index 3bd0888cb2587..d947351afb99c 100644 --- a/packages/grid/_modules_/grid/models/colDef/gridColDef.ts +++ b/packages/grid/_modules_/grid/models/colDef/gridColDef.ts @@ -2,7 +2,11 @@ import * as React from 'react'; import { GridCellValue } from '../gridCell'; import { GridCellClassNamePropType, GridCellClassRules } from '../gridCellClass'; import { GridFilterOperator } from '../gridFilterOperator'; -import { GridCellParams, ValueFormatterParams, ValueGetterParams } from '../params/gridCellParams'; +import { + GridCellParams, + GridValueFormatterParams, + GridValueGetterParams, +} from '../params/gridCellParams'; import { GridColParams } from '../params/gridColParams'; import { GridComparatorFn } from '../gridSortModel'; import { GridColType, GridNativeColTypes } from './gridColType'; @@ -74,12 +78,12 @@ export interface GridColDef { * Function that allows to get a specific data instead of field to render in the cell. * @param params */ - valueGetter?: (params: ValueGetterParams) => GridCellValue; + valueGetter?: (params: GridValueGetterParams) => GridCellValue; /** * Function that allows to apply a formatter before rendering its value. * @param params */ - valueFormatter?: (params: ValueFormatterParams) => GridCellValue; + valueFormatter?: (params: GridValueFormatterParams) => GridCellValue; /** * Class name that will be added in cells for that column. */ diff --git a/packages/grid/_modules_/grid/models/gridOptions.tsx b/packages/grid/_modules_/grid/models/gridOptions.tsx index cf13904586a69..89940fd6636ca 100644 --- a/packages/grid/_modules_/grid/models/gridOptions.tsx +++ b/packages/grid/_modules_/grid/models/gridOptions.tsx @@ -1,3 +1,4 @@ +import * as React from 'react'; import { GRID_DEFAULT_LOCALE_TEXT } from '../constants/localeTextConstants'; import { FilterModel } from '../hooks/features/filter/FilterModelState'; import { Logger } from '../hooks/utils/useLogger'; @@ -225,33 +226,75 @@ export interface GridOptions { /** * Callback fired when a click event comes from a cell element. * @param param With all properties from [[GridCellParams]]. + * @param event [[React.MouseEvent]]. */ - onCellClick?: (param: GridCellParams) => void; + onCellClick?: (param: GridCellParams, event: React.MouseEvent) => void; /** * Callback fired when a double click event comes from a cell element. * @param param With all properties from [[CellParams]]. + * @param event [[React.MouseEvent]]. */ - onCellDoubleClick?: (param: GridCellParams) => void; + onCellDoubleClick?: (param: GridCellParams, event: React.MouseEvent) => void; /** - * Callback fired when a hover event comes from a cell element. + * Callback fired when a mouseover event comes from a cell element. * @param param With all properties from [[GridCellParams]]. + * @param event [[React.MouseEvent]]. */ - onCellHover?: (param: GridCellParams) => void; + onCellOver?: (param: GridCellParams, event: React.MouseEvent) => void; + /** + * Callback fired when a mouseout event comes from a cell element. + * @param param With all properties from [[GridCellParams]]. + * @param event [[React.MouseEvent]]. + */ + onCellOut?: (param: GridCellParams, event: React.MouseEvent) => void; + /** + * Callback fired when a mouse enter event comes from a cell element. + * @param param With all properties from [[GridCellParams]]. + * @param event [[React.MouseEvent]]. + */ + onCellEnter?: (param: GridCellParams, event: React.MouseEvent) => void; + /** + * Callback fired when a mouse leave event comes from a cell element. + * @param param With all properties from [[GridCellParams]]. + * @param event [[React.MouseEvent]]. + */ + onCellLeave?: (param: GridCellParams, event: React.MouseEvent) => void; /** * Callback fired when a click event comes from a row container element. * @param param With all properties from [[GridRowParams]]. + * @param event [[React.MouseEvent]]. */ - onRowClick?: (param: GridRowParams) => void; + onRowClick?: (param: GridRowParams, event: React.MouseEvent) => void; /** - * Callback fired when a click event comes from a row container element. + * Callback fired when a double click event comes from a row container element. * @param param With all properties from [[RowParams]]. + * @param event [[React.MouseEvent]]. + */ + onRowDoubleClick?: (param: GridRowParams, event: React.MouseEvent) => void; + /** + * Callback fired when a mouseover event comes from a row container element. + * @param param With all properties from [[GridRowParams]]. + * @param event [[React.MouseEvent]]. + */ + onRowOver?: (param: GridRowParams, event: React.MouseEvent) => void; + /** + * Callback fired when a mouseout event comes from a row container element. + * @param param With all properties from [[GridRowParams]]. + * @param event [[React.MouseEvent]]. + */ + onRowOut?: (param: GridRowParams, event: React.MouseEvent) => void; + /** + * Callback fired when a mouse enter event comes from a row container element. + * @param param With all properties from [[GridRowParams]]. + * @param event [[React.MouseEvent]]. */ - onRowDoubleClick?: (param: GridRowParams) => void; + onRowEnter?: (param: GridRowParams, event: React.MouseEvent) => void; /** - * Callback fired when a hover event comes from a row container element. + * Callback fired when a mouse leave event comes from a row container element. * @param param With all properties from [[GridRowParams]]. + * @param event [[React.MouseEvent]]. */ - onRowHover?: (param: GridRowParams) => void; + onRowLeave?: (param: GridRowParams, event: React.MouseEvent) => void; /** * Callback fired when one row is selected. * @param param With all properties from [[GridRowSelectedParams]]. @@ -266,7 +309,32 @@ export interface GridOptions { * Callback fired when a click event comes from a column header element. * @param param With all properties from [[GridColParams]]. */ - onColumnHeaderClick?: (param: GridColParams) => void; + onColumnHeaderClick?: (param: GridColParams, event: React.MouseEvent) => void; + /** + * Callback fired when a double click event comes from a column header element. + * @param param With all properties from [[GridColParams]]. + */ + onColumnHeaderDoubleClick?: (param: GridColParams, event: React.MouseEvent) => void; + /** + * Callback fired when a mouseover event comes from a column header element. + * @param param With all properties from [[GridColParams]]. + */ + onColumnHeaderOver?: (param: GridColParams, event: React.MouseEvent) => void; + /** + * Callback fired when a mouseout event comes from a column header element. + * @param param With all properties from [[GridColParams]]. + */ + onColumnHeaderOut?: (param: GridColParams, event: React.MouseEvent) => void; + /** + * Callback fired when a mouse enter event comes from a column header element. + * @param param With all properties from [[GridColParams]]. + */ + onColumnHeaderEnter?: (param: GridColParams, event: React.MouseEvent) => void; + /** + * Callback fired when a mouse leave event comes from a column header element. + * @param param With all properties from [[GridColParams]]. + */ + onColumnHeaderLeave?: (param: GridColParams, event: React.MouseEvent) => void; /** * Callback fired when the sort model changes before a column is sorted. * @param param With all properties from [[GridSortModelParams]]. diff --git a/packages/grid/_modules_/grid/models/params/gridCellParams.ts b/packages/grid/_modules_/grid/models/params/gridCellParams.ts index de00904d825f0..9176c4423d547 100644 --- a/packages/grid/_modules_/grid/models/params/gridCellParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridCellParams.ts @@ -1,14 +1,18 @@ import { GridCellValue } from '../gridCell'; -import { GridRowModel } from '../gridRows'; +import { GridRowId, GridRowModel } from '../gridRows'; /** * Object passed as parameter in the column [[GridColDef]] cell renderer. */ export interface GridCellParams { /** - * The HTMLElement that triggered the event + * The grid row id. */ - element?: HTMLElement; + id: GridRowId; + /** + * The HTMLElement cell element. + */ + element?: HTMLElement | null; /** * The column field of the cell that triggered the event */ @@ -17,6 +21,10 @@ export interface GridCellParams { * The cell value, but if the column has valueGetter, use getValue. */ value: GridCellValue; + /** + * The cell value formatted with the column valueFormatter. + */ + formattedValue: GridCellValue; /** * A function that let you get data from other columns. * @param field @@ -51,9 +59,9 @@ export interface GridCellParams { /** * Alias of GridCellParams. */ -export type ValueGetterParams = GridCellParams; +export type GridValueGetterParams = Omit; /** * Alias of GridCellParams. */ -export type ValueFormatterParams = GridCellParams; +export type GridValueFormatterParams = Omit; diff --git a/packages/grid/_modules_/grid/models/params/gridColParams.ts b/packages/grid/_modules_/grid/models/params/gridColParams.ts index e97bc6906d876..67c1c3e982edd 100644 --- a/packages/grid/_modules_/grid/models/params/gridColParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridColParams.ts @@ -2,6 +2,10 @@ * Object passed as parameter in the column [[GridColDef]] header renderer. */ export interface GridColParams { + /** + * The HTMLElement column header element. + */ + element?: HTMLElement | null; /** * The column field of the column that triggered the event */ diff --git a/packages/grid/_modules_/grid/models/params/gridRowParams.ts b/packages/grid/_modules_/grid/models/params/gridRowParams.ts index 0d9fdec99320c..d733139176038 100644 --- a/packages/grid/_modules_/grid/models/params/gridRowParams.ts +++ b/packages/grid/_modules_/grid/models/params/gridRowParams.ts @@ -1,14 +1,18 @@ import { GridCellValue } from '../gridCell'; -import { GridRowModel } from '../gridRows'; +import { GridRowId, GridRowModel } from '../gridRows'; /** * Object passed as parameter in the column [[GridColDef]] cell renderer. */ export interface GridRowParams { /** - * The HTMLElement that triggered the event + * The grid row id. */ - element?: HTMLElement; + id: GridRowId; + /** + * The HTMLElement row element. + */ + element?: HTMLElement | null; /** * A function that let you get data from other columns. * @param field diff --git a/packages/grid/_modules_/grid/utils/domUtils.ts b/packages/grid/_modules_/grid/utils/domUtils.ts index 1a45e1b3872d0..06361cbface09 100644 --- a/packages/grid/_modules_/grid/utils/domUtils.ts +++ b/packages/grid/_modules_/grid/utils/domUtils.ts @@ -5,6 +5,7 @@ import { GRID_ROW_CSS_CLASS, } from '../constants/cssClassesConstants'; import { GridCellIndexCoordinates } from '../models/gridCell'; +import { GridRowId } from '../models/gridRows'; export function isOverflown(element: Element): boolean { return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth; @@ -25,13 +26,6 @@ export function isGridCellRoot(elem: Element | null): boolean { return elem != null && elem.classList.contains(GRID_CELL_CSS_CLASS); } -export function isGridCell(elem: Element | null): boolean { - return ( - elem != null && - (isGridCellRoot(elem) || findParentElementFromClassName(elem, GRID_CELL_CSS_CLASS) !== null) - ); -} - export function isGridHeaderTitleContainer(elem: Element): boolean { return elem && findParentElementFromClassName(elem, GRID_HEADER_CELL_TITLE_CSS_CLASS) !== null; } @@ -40,10 +34,6 @@ export function getIdFromRowElem(rowEl: Element): string { return rowEl.getAttribute('data-id')!; } -export function getFieldFromCellElem(cellEl: Element): string { - return cellEl.getAttribute('data-field')!; -} - export function getFieldFromHeaderElem(colCellEl: Element): string { return colCellEl.getAttribute('data-field')!; } @@ -86,3 +76,19 @@ export function getGridCellElementFromIndexes( `:scope .${GRID_CELL_CSS_CLASS}[aria-colIndex='${colIndex}'][data-rowIndex='${rowIndex}']`, ) as HTMLDivElement; } + +export function getGridColumnHeaderElement(root: Element, field: string) { + return root.querySelector(`[role='columnheader'][data-field='${field}']`) as HTMLDivElement; +} + +export function getGridRowElement(root: Element, id: GridRowId) { + return root.querySelector(`:scope .${GRID_ROW_CSS_CLASS}[data-id='${id}']`) as HTMLDivElement; +} + +export function getGridCellElement(root: Element, { id, field }: { id: GridRowId; field: string }) { + const row = getGridRowElement(root, id); + if (!row) { + return null; + } + return row.querySelector(`.${GRID_CELL_CSS_CLASS}[data-field='${field}']`) as HTMLDivElement; +} diff --git a/packages/grid/_modules_/grid/utils/index.ts b/packages/grid/_modules_/grid/utils/index.ts index 512fd9dafc275..077ff9ccdf409 100644 --- a/packages/grid/_modules_/grid/utils/index.ts +++ b/packages/grid/_modules_/grid/utils/index.ts @@ -4,7 +4,6 @@ export * from './domUtils'; export * from './classnames'; export * from './keyboardUtils'; export * from './mergeUtils'; -export * from './paramsUtils'; export * from './getGridLocalization'; export * from './material-ui-utils'; export * from './exportAs'; diff --git a/packages/grid/_modules_/grid/utils/paramsUtils.ts b/packages/grid/_modules_/grid/utils/paramsUtils.ts deleted file mode 100644 index f2430026b1e09..0000000000000 --- a/packages/grid/_modules_/grid/utils/paramsUtils.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { GridCellValue } from '../models/gridCell'; -import { GridRowModel } from '../models/gridRows'; -import { GridColDef } from '../models/colDef/gridColDef'; -import { GridApi } from '../models/api/gridApi'; -import { GridCellParams } from '../models/params/gridCellParams'; -import { GridRowParams } from '../models/params/gridRowParams'; - -let warnedOnce = false; - -export function buildGridCellParams({ - element, - value, - rowIndex, - colIndex, - rowModel, - colDef, - api, -}: { - rowModel: GridRowModel; - colDef: GridColDef; - rowIndex?: number; - colIndex?: number; - value: GridCellValue; - api: GridApi; - element?: HTMLElement; -}): GridCellParams { - const params: GridCellParams = { - element, - value, - field: colDef?.field, - getValue: (field: string) => { - // We are getting the value of another column here, field - const col = api.getColumnFromField(field); - - if (process.env.NODE_ENV !== 'production') { - if (!col && !warnedOnce) { - console.warn( - [ - `Material-UI: You are calling getValue('${field}') but the column \`${field}\` is not defined.`, - `Instead, you can access the data from \`params.row.${field}\`.`, - ].join('\n'), - ); - warnedOnce = true; - } - } - - if (!col || !col.valueGetter) { - return rowModel[field]; - } - - return col.valueGetter( - buildGridCellParams({ - value: rowModel[field], - colDef: col, - rowIndex, - element, - rowModel, - api, - }), - ); - }, - row: rowModel, - colDef, - rowIndex, - colIndex: colIndex || (colDef && api.getColumnIndex(colDef.field, true)), - api, - }; - const isEditableAttr = element && element.getAttribute('data-editable'); - params.isEditable = - isEditableAttr != null ? isEditableAttr === 'true' : colDef && api.isCellEditable(params); - - return params; -} - -export function buildGridRowParams({ - element, - rowIndex, - rowModel, - api, -}: { - rowModel: GridRowModel; - colDef: GridColDef; - rowIndex: number; - api: GridApi; - element?: HTMLElement; -}): GridRowParams { - return { - element, - columns: api.getAllColumns(), - getValue: (field: string) => rowModel[field], - row: rowModel, - rowIndex, - api, - }; -} 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 a951b953f8337..4e24305ff7f56 100644 --- a/packages/grid/data-grid/src/tests/layout.DataGrid.test.tsx +++ b/packages/grid/data-grid/src/tests/layout.DataGrid.test.tsx @@ -7,7 +7,7 @@ import { } from 'test/utils'; import { useFakeTimers, stub } from 'sinon'; import { expect } from 'chai'; -import { DataGrid, ValueGetterParams, GridToolbar } from '@material-ui/data-grid'; +import { DataGrid, GridValueGetterParams, GridToolbar } from '@material-ui/data-grid'; import { getColumnValues, raf, sleep } from 'test/utils/helperFn'; describe(' - Layout & Warnings', () => { @@ -170,7 +170,7 @@ describe(' - Layout & Warnings', () => { { field: 'id', hide: true }, { field: 'fullName', - valueGetter: (params: ValueGetterParams) => params.getValue('age'), + valueGetter: (params: GridValueGetterParams) => params.getValue('age'), }, ]; expect(() => { diff --git a/packages/grid/x-grid/src/tests/events.XGrid.test.tsx b/packages/grid/x-grid/src/tests/events.XGrid.test.tsx new file mode 100644 index 0000000000000..d051946d3bd3d --- /dev/null +++ b/packages/grid/x-grid/src/tests/events.XGrid.test.tsx @@ -0,0 +1,220 @@ +import * as React from 'react'; +import { + createClientRenderStrictMode, + // @ts-ignore + fireEvent, +} from 'test/utils'; +import { expect } from 'chai'; +import { + XGrid, + GridColParams, + useGridApiRef, + XGridProps, + GridRowParams, + GridCellParams, + GridRowsProp, + GridColumns, +} from '@material-ui/x-grid'; +import { getCell, getColumnHeaderCell, getRow } from 'test/utils/helperFn'; + +describe(' - Events Params ', () => { + // TODO v5: replace with createClientRender + const render = createClientRenderStrictMode(); + + const baselineProps: { rows: GridRowsProp; columns: GridColumns } = { + rows: [ + { + id: 1, + first: 'Mike', + age: 11, + }, + { + id: 2, + first: 'Jack', + age: 11, + }, + { + id: 3, + first: 'Mike', + age: 20, + }, + ], + columns: [ + { field: 'id' }, + { field: 'first', editable: true }, + { field: 'age' }, + { + field: 'firstAge', + valueGetter: (params) => `${params.row.first}_${params.row.age}`, + valueFormatter: (params) => `${params.value} yrs`, + }, + ], + }; + + before(function beforeHook() { + if (/jsdom/.test(window.navigator.userAgent)) { + // Need layouting + this.skip(); + } + }); + + let apiRef; + const TestEvents = (props: Partial) => { + apiRef = useGridApiRef(); + return ( +
+ +
+ ); + }; + + describe('columnHeaderParams', () => { + it('should include the correct params', () => { + let eventArgs: { params: GridColParams; event: React.MouseEvent } | null = null; + const handleClick = (params, event) => { + eventArgs = { params, event }; + }; + render(); + + const ageColumnElement = getColumnHeaderCell(3); + fireEvent.click(ageColumnElement); + + expect(eventArgs!.params).to.deep.include({ + colDef: apiRef!.current.getColumnFromField('age'), + element: ageColumnElement, + colIndex: 2, + field: 'age', + api: apiRef.current, + }); + }); + }); + + describe('RowsParams', () => { + it('should include the correct params', () => { + let eventArgs: { params: GridRowParams; event: React.MouseEvent } | null = null; + + const handleClick = (params, event) => { + eventArgs = { params, event }; + }; + render(); + + const row1 = getRow(1); + fireEvent.click(row1); + + expect(eventArgs!.params).to.deep.include({ + id: 2, + element: row1, + row: baselineProps.rows[1], + rowIndex: 1, + columns: apiRef!.current.getAllColumns(), + api: apiRef.current, + }); + }); + }); + + describe('CellsParams', () => { + let eventArgs: { params: GridCellParams; event: React.MouseEvent } | null = null; + let cell11; + + beforeEach(() => { + const handleClick = (params, event) => { + eventArgs = { params, event }; + }; + render(); + }); + + it('should include the correct params', () => { + cell11 = getCell(1, 1); + fireEvent.click(cell11); + + expect(eventArgs!.params).to.deep.include({ + id: 2, + value: 'Jack', + formattedValue: 'Jack', + isEditable: true, + element: cell11, + row: baselineProps.rows[1], + rowIndex: 1, + colDef: apiRef!.current.getColumnFromField('first'), + colIndex: 1, + api: apiRef.current, + }); + }); + + it('should consider value getter', () => { + const cellFirstAge = getCell(1, 3); + fireEvent.click(cellFirstAge); + + expect(eventArgs!.params.value).to.equal('Jack_11'); + }); + + it('should consider value formatter', () => { + const cellFirstAge = getCell(1, 3); + fireEvent.click(cellFirstAge); + + expect(eventArgs!.params.formattedValue).to.equal('Jack_11 yrs'); + }); + }); + + describe('onCellClick', () => { + let eventStack: string[] = []; + const push = (name: string) => () => { + eventStack.push(name); + }; + + beforeEach(() => { + eventStack = []; + }); + + it('should bubble to the row', () => { + render(); + + const cell11 = getCell(1, 1); + fireEvent.click(cell11); + expect(eventStack).to.deep.equal(['cellClick', 'rowClick']); + }); + + it('should not bubble to the row if the column has disableEventBubbling', () => { + render( + ({ + ...col, + disableClickEventBubbling: true, + }))} + />, + ); + + const cell11 = getCell(1, 1); + fireEvent.click(cell11); + expect(eventStack).to.deep.equal(['cellClick']); + }); + + it('should allow to stop propagation', () => { + const stopClick = (params, event) => { + event.stopPropagation(); + }; + render(); + + const cell11 = getCell(1, 1); + fireEvent.click(cell11); + expect(eventStack).to.deep.equal([]); + }); + + it('should select a row by default', () => { + render(); + + const cell11 = getCell(1, 1); + fireEvent.click(cell11); + expect(eventStack).to.deep.equal(['rowSelected']); + }); + + it('should not select a row if options.disableSelectionOnClick', () => { + render(); + const cell11 = getCell(1, 1); + fireEvent.click(cell11); + expect(eventStack).to.deep.equal([]); + }); + }); +}); diff --git a/packages/storybook/.storybook/preview.tsx b/packages/storybook/.storybook/preview.tsx index b9f540b2b564f..e6b45512938c4 100644 --- a/packages/storybook/.storybook/preview.tsx +++ b/packages/storybook/.storybook/preview.tsx @@ -14,7 +14,12 @@ configureActions({ }); export const parameters = { - actions: { argTypesRegex: '^on.*' }, + // The storybook action panel is throwing a serialisation error with mouse events + // due to its circular structure + actions: { + argTypesRegex: + '^on((?!CellClick|CellDoubleClick|CellEnter|CellLeave|CellOut|CellOver|ColumnHeaderClick|ColumnHeaderDoubleClick|ColumnHeaderOver|ColumnHeaderOut|ColumnHeaderEnter|ColumnHeaderLeave|StateChange|RowClick|RowDoubleClick|RowEnter|RowLeave|RowOut|RowOver).)*$', + }, options: { /** * display the top-level grouping as a "root" in the sidebar diff --git a/packages/storybook/src/stories/grid-columns.stories.tsx b/packages/storybook/src/stories/grid-columns.stories.tsx index c5324dc2cd972..783eee43028b9 100644 --- a/packages/storybook/src/stories/grid-columns.stories.tsx +++ b/packages/storybook/src/stories/grid-columns.stories.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { GridColDef, XGrid, GridColTypeDef } from '@material-ui/x-grid'; +import { GridColDef, XGrid, GridColTypeDef, GridValueGetterParams } from '@material-ui/x-grid'; import CreateIcon from '@material-ui/icons/Create'; import Button from '@material-ui/core/Button'; import { useData } from '../hooks/useData'; @@ -287,3 +287,32 @@ export const FlexColumnWidth2000 = () => {
); }; + +export const ValueGetterAndFormatter = () => { + const [data] = React.useState({ + rows: [ + { id: 1, first: 'mark', age: 1 }, + { id: 2, first: 'jack', age: 2 }, + ], + columns: [ + { field: 'id', hide: true }, + { + field: 'firstAge', + valueGetter: (params: GridValueGetterParams) => + `${params.getValue('first')}_${params.getValue('age')}`, + }, + { + field: 'firstAgeFormatted', + valueGetter: (params: GridValueGetterParams) => + `${params.getValue('first')}_${params.getValue('age')}`, + valueFormatter: (params) => `${params.value} yrs`, + }, + ], + }); + + return ( +
+ +
+ ); +}; diff --git a/packages/storybook/src/stories/grid-events.stories.tsx b/packages/storybook/src/stories/grid-events.stories.tsx index bc08224a32201..bba60ff57be91 100644 --- a/packages/storybook/src/stories/grid-events.stories.tsx +++ b/packages/storybook/src/stories/grid-events.stories.tsx @@ -20,8 +20,8 @@ export function AllEvents() { const options: GridOptionsProp = { onRowClick: (params) => action('onRowClick')(params), onCellClick: (params) => action('onCellClick')(params), - onCellHover: (params) => action('onCellHover')(params), - onRowHover: (params) => action('onRowHover')(params), + onCellOver: (params) => action('onCellOver')(params), + onRowOver: (params) => action('onRowOver')(params), onColumnHeaderClick: (params) => action('onColumnHeaderClick')(params), onRowSelected: (params) => action('onRowSelected')(params), onSelectionModelChange: (params) => action('onSelectionChange', { depth: 1 })(params), @@ -43,11 +43,20 @@ export const OnRowClick = () => { return ; }; +export const OnRowDoubleClick = () => { + const data = useData(2000, 200); + + const options: GridOptionsProp = { + onRowDoubleClick: (params) => action('row double click')(params), + }; + + return ; +}; export const OnRowHover = () => { const data = useData(2000, 200); const options: GridOptionsProp = { - onRowHover: (params) => action('Row Hover')(params), + onRowOver: (params) => action('Row over')(params), }; return ; @@ -61,11 +70,30 @@ export const OnCellClick = () => { return ; }; +export const OnCellClickNotPropagated = () => { + const data = useData(2000, 200); + const options: GridOptionsProp = { + onCellClick: (params, event) => { + event.stopPropagation(); + action('cell click')(params); + }, + }; + + return ; +}; +export const OnCellDoubleClick = () => { + const data = useData(2000, 200); + const options: GridOptionsProp = { + onCellDoubleClick: (params) => action('Cell double click')(params), + }; + + return ; +}; export const OnCellHover = () => { const data = useData(2000, 200); const options: GridOptionsProp = { - onCellHover: (params) => action('cell Hover')(params), + onCellOver: (params) => action('cell over')(params), }; return ; diff --git a/packages/storybook/src/stories/playground/RecipeReviewCard.tsx b/packages/storybook/src/stories/playground/RecipeReviewCard.tsx new file mode 100644 index 0000000000000..7d5fd56566b8f --- /dev/null +++ b/packages/storybook/src/stories/playground/RecipeReviewCard.tsx @@ -0,0 +1,126 @@ +import * as React from 'react'; +import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; +import clsx from 'clsx'; +import Card from '@material-ui/core/Card'; +import CardHeader from '@material-ui/core/CardHeader'; +import CardMedia from '@material-ui/core/CardMedia'; +import CardContent from '@material-ui/core/CardContent'; +import CardActions from '@material-ui/core/CardActions'; +import Collapse from '@material-ui/core/Collapse'; +import Avatar from '@material-ui/core/Avatar'; +import IconButton from '@material-ui/core/IconButton'; +import Typography from '@material-ui/core/Typography'; +import { red } from '@material-ui/core/colors'; +import FavoriteIcon from '@material-ui/icons/Favorite'; +import ShareIcon from '@material-ui/icons/Share'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + root: { + maxWidth: 345, + }, + media: { + height: 0, + paddingTop: '56.25%', // 16:9 + }, + expand: { + transform: 'rotate(0deg)', + marginLeft: 'auto', + transition: theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + }, + expandOpen: { + transform: 'rotate(180deg)', + }, + avatar: { + backgroundColor: red[500], + }, + }), +); + +export default function RecipeReviewCard() { + const classes = useStyles(); + const [expanded, setExpanded] = React.useState(false); + + const handleExpandClick = () => { + setExpanded(!expanded); + }; + + return ( + + + R + + } + action={ + + + + } + title="Shrimp and Chorizo Paella" + subheader="September 14, 2016" + /> + + + + This impressive paella is a perfect party dish and a fun meal to cook together with your + guests. Add 1 cup of frozen peas along with the mussels, if you like. + + + + + + + + + + + + + + + + Method: + + Heat 1/2 cup of the broth in a pot until simmering, add saffron and set aside for 10 + minutes. + + + Heat oil in a (14- to 16-inch) paella pan or a large, deep skillet over medium-high + heat. Add chicken, shrimp and chorizo, and cook, stirring occasionally until lightly + browned, 6 to 8 minutes. Transfer shrimp to a large plate and set aside, leaving chicken + and chorizo in the pan. Add pimentón, bay leaves, garlic, tomatoes, onion, salt and + pepper, and cook, stirring often until thickened and fragrant, about 10 minutes. Add + saffron broth and remaining 4 1/2 cups chicken broth; bring to a boil. + + + Add rice and stir very gently to distribute. Top with artichokes and peppers, and cook + without stirring, until most of the liquid is absorbed, 15 to 18 minutes. Reduce heat to + medium-low, add reserved shrimp and mussels, tucking them down into the rice, and cook + again without stirring, until mussels have opened and rice is just tender, 5 to 7 + minutes more. (Discard any mussels that don’t open.) + + + Set aside off of the heat to let rest for 10 minutes, and then serve. + + + + + ); +} diff --git a/packages/storybook/src/stories/playground/customComponents.tsx b/packages/storybook/src/stories/playground/customComponents.tsx new file mode 100644 index 0000000000000..ad813f8eea3a7 --- /dev/null +++ b/packages/storybook/src/stories/playground/customComponents.tsx @@ -0,0 +1,106 @@ +import * as React from 'react'; +import LinearProgress from '@material-ui/core/LinearProgress'; +import CodeIcon from '@material-ui/icons/Code'; +import ExpandLessIcon from '@material-ui/icons/ExpandLess'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import Pagination from '@material-ui/lab/Pagination'; +import { + GridFooterContainer, + GridOverlay, + GridColumnMenu, + HideGridColMenuItem, + GridBaseComponentProps, + ColumnMenuProps, +} from '@material-ui/data-grid'; +import RecipeReviewCard from './RecipeReviewCard'; + +export function SortedDescendingIcon() { + return ; +} + +export function SortedAscendingIcon() { + return ; +} + +export function LoadingComponent() { + return ( + +
+ +
+
+ ); +} + +export function NoRowsComponent() { + return ( + + + No Rows + + + ); +} + +export function PaginationComponent(props: GridBaseComponentProps & { color?: 'primary' }) { + const { state, api } = props; + return ( + api.current.setPage(value)} + /> + ); +} + +export function CustomFooter(props) { + const { state, api } = props; + return ( + + + Custom footer is here. + + api.current.setPage(value)} + /> + + ); +} + +export function FooterComponent2(props) { + const { state } = props; + + return ( +
I counted {state.pagination.rowCount} row(s)
+ ); +} + +export function CustomHeader(props) { + return ( +
+ +
+ ); +} + +export function ColumnMenuComponent(props: ColumnMenuProps) { + if (props.api.current.getColumnIndex(props.currentColumn.field) === 1) { + return ; + } + if (props.currentColumn.field === 'id') { + return ; + } + + return ( + + ); +} diff --git a/packages/storybook/src/stories/playground/customize-components.stories.tsx b/packages/storybook/src/stories/playground/customize-components.stories.tsx index 11764d5c309ea..db0bf81337f97 100644 --- a/packages/storybook/src/stories/playground/customize-components.stories.tsx +++ b/packages/storybook/src/stories/playground/customize-components.stories.tsx @@ -4,27 +4,28 @@ import { Story, Meta } from '@storybook/react'; import { GridColDef, XGrid, - GridOverlay, - GridFooterContainer, XGridProps, - HideGridColMenuItem, - ColumnMenuProps, GridBaseComponentProps, - GridColumnMenu, GridPanelProps, GridPreferencesPanel, GridFooter, GridToolbar, } from '@material-ui/x-grid'; -import LinearProgress from '@material-ui/core/LinearProgress'; -import CodeIcon from '@material-ui/icons/Code'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import ExpandLessIcon from '@material-ui/icons/ExpandLess'; -import Pagination from '@material-ui/lab/Pagination'; import DoneIcon from '@material-ui/icons/Done'; import ClearIcon from '@material-ui/icons/Clear'; import CreateIcon from '@material-ui/icons/Create'; import { useData } from '../../hooks/useData'; +import { + CustomHeader, + CustomFooter, + FooterComponent2, + LoadingComponent, + NoRowsComponent, + PaginationComponent, + ColumnMenuComponent, + SortedDescendingIcon, + SortedAscendingIcon, +} from './customComponents'; export default { title: 'X-Grid Demos/Custom-Components', @@ -67,16 +68,6 @@ const Template: Story = (args) => { ); }; -function LoadingComponent() { - return ( - -
- -
-
- ); -} - export const Loading = Template.bind({}); Loading.args = { ...defaultData, @@ -86,16 +77,6 @@ Loading.args = { }, }; -function NoRowsComponent() { - return ( - - - No Rows - - - ); -} - export const NoRows = Template.bind({}); NoRows.args = { rows: [], @@ -105,36 +86,15 @@ NoRows.args = { }, }; -function SortedDescending() { - return ; -} - -function SortedAscending() { - return ; -} - export const Icons = Template.bind({}); Icons.args = { ...defaultData, components: { - ColumnSortedDescendingIcon: SortedDescending, - ColumnSortedAscendingIcon: SortedAscending, + ColumnSortedDescendingIcon: SortedDescendingIcon, + ColumnSortedAscendingIcon: SortedAscendingIcon, }, }; -function PaginationComponent(props: GridBaseComponentProps & { color?: 'primary' }) { - const { state, api } = props; - return ( - api.current.setPage(value)} - /> - ); -} - export const CustomPagination = Template.bind({}); CustomPagination.args = { pagination: true, @@ -147,51 +107,18 @@ CustomPagination.args = { }, }; -function FooterComponent(props) { - const { state, api } = props; - return ( - - - This is my custom footer and pagination here!{' '} - - api.current.setPage(value)} - /> - - ); -} - -export const CustomFooter = Template.bind({}); -CustomFooter.args = { +export const CustomFooterDemo = Template.bind({}); +CustomFooterDemo.args = { pagination: true, pageSize: 33, components: { - Footer: FooterComponent, + Footer: CustomFooter, }, componentsProps: { footer: { color: 'blue' }, }, }; -function FooterComponent2(props) { - const { state } = props; - - return ( -
I counted {state.pagination.rowCount} row(s)
- ); -} - -function CustomHeader(props) { - return ( -
- -
- ); -} - export const HeaderAndFooter = Template.bind({}); HeaderAndFooter.args = { pagination: true, @@ -326,24 +253,6 @@ CustomToolbar.args = { }, }; -function ColumnMenuComponent(props: ColumnMenuProps & { color?: string }) { - if (props.currentColumn.field === 'id') { - return ; - } - if (props.currentColumn.field === 'currencyPair') { - return ( -
This is my currency pair column Menu!
- ); - } - return ( - - ); -} - export const CustomColumnMenu = Template.bind({}); CustomColumnMenu.args = { components: { diff --git a/packages/storybook/src/stories/playground/data-grid.options.stories.tsx b/packages/storybook/src/stories/playground/data-grid.options.stories.tsx deleted file mode 100644 index 3f8766d18831a..0000000000000 --- a/packages/storybook/src/stories/playground/data-grid.options.stories.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import * as React from 'react'; -import { Story, Meta } from '@storybook/react'; -import { DataGrid, DataGridProps, GridSortDirection } from '@material-ui/data-grid'; -import { useData } from '../../hooks/useData'; - -export default { - title: 'Data-Grid Demos/Options Events', - component: DataGrid, - parameters: { - options: { selectedPanel: 'storybook/controls/panel' }, - docs: { - page: null, - }, - }, -} as Meta; - -interface StoryExtraArgs { - resizable: boolean; -} - -const Template: Story & StoryExtraArgs> = ({ - resizable, - sortingOrder, - ...args -}) => { - const { rows, columns } = useData(200, 20); - if (resizable) { - columns.forEach((c) => { - c.resizable = true; - }); - } - return ( - ((value as string) === 'null' ? null : value))} - {...args} - /> - ); -}; - -export const Options = Template.bind({}); -Options.args = { - pageSize: 100, - autoPageSize: false, - rowsPerPageOptions: [25, 50, 100], - hideFooterRowCount: false, - hideFooterPagination: false, - hideFooter: false, - disableExtendRowFullWidth: false, - showCellRightBorder: false, - showColumnRightBorder: false, - checkboxSelection: true, - disableSelectionOnClick: false, - sortingOrder: ['asc', 'desc', 'null' as GridSortDirection], - headerHeight: 56, - rowHeight: 52, -}; - -export const ResizableValidation = Template.bind({}); -ResizableValidation.args = { - ...Options.args, - resizable: true, -}; diff --git a/packages/storybook/src/stories/playground/options.stories.tsx b/packages/storybook/src/stories/playground/options.stories.tsx deleted file mode 100644 index e3df8f9e7d42d..0000000000000 --- a/packages/storybook/src/stories/playground/options.stories.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import * as React from 'react'; -import { Story, Meta } from '@storybook/react'; -import { XGrid, GridSortDirection, GridOptionsProp } from '@material-ui/x-grid'; -import { useData } from '../../hooks/useData'; - -export default { - title: 'X-Grid Demos/Options-Events', - component: XGrid, - parameters: { - options: { selectedPanel: 'storybook/controls/panel' }, - docs: { - page: null, - }, - }, -} as Meta; - -const Template: Story = ({ sortingOrder, ...args }) => { - const data = useData(2000, 200); - return ( - ((value as string) === 'null' ? null : value))} - {...args} - /> - ); -}; - -export const Options = Template.bind({}); -Options.args = { - autoHeight: false, - pagination: true, - pageSize: 100, - autoPageSize: false, - rowsPerPageOptions: [25, 50, 100], - hideFooterRowCount: false, - hideFooterPagination: false, - hideFooter: false, - disableExtendRowFullWidth: false, - disableColumnMenu: false, - disableColumnFilter: false, - disableColumnSelector: false, - showCellRightBorder: false, - showColumnRightBorder: false, - disableMultipleSelection: false, - checkboxSelection: true, - disableSelectionOnClick: false, - disableMultipleColumnsSorting: false, - sortingOrder: ['asc', 'desc', 'null' as GridSortDirection], - headerHeight: 56, - rowHeight: 52, -}; 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 372ef04193381..330f0d72ba988 100644 --- a/packages/storybook/src/stories/playground/real-data-demo.stories.tsx +++ b/packages/storybook/src/stories/playground/real-data-demo.stories.tsx @@ -1,134 +1,107 @@ +import { DataGrid, GridToolbar } from '@material-ui/data-grid'; import * as React from 'react'; import { Story, Meta, DecoratorFn } from '@storybook/react'; -import { XGridProps, GridPreferencePanelsValue, XGrid, GridOptionsProp } from '@material-ui/x-grid'; +import { XGridProps, GridPreferencePanelsValue, XGrid } from '@material-ui/x-grid'; import { useDemoData, DemoDataOptions } from '@material-ui/x-grid-data-generator'; -import Button from '@material-ui/core/Button'; -import { randomInt } from '../../data/random-generator'; +import { useData } from '../../hooks/useData'; +import { + ColumnMenuComponent, + CustomFooter, + LoadingComponent, + NoRowsComponent, + PaginationComponent, + SortedAscendingIcon, + SortedDescendingIcon, +} from './customComponents'; + +export interface PlaygroundProps { + multipleGrid: boolean; + license: 'XGrid' | 'DataGrid'; +} + +const gridContainer: DecoratorFn = (storyFn) =>
{storyFn()}
; export default { title: 'X-Grid Demos/Playground', component: XGrid, + argTypes: { + dataSet: { + defaultValue: 'Commodity', + control: { + type: 'inline-radio', + options: ['Commodity', 'Employee', 'Raw'], + }, + }, + license: { + defaultValue: 'XGrid', + control: { + type: 'inline-radio', + options: ['XGrid', 'DataGrid'], + }, + }, + rowLength: { + defaultValue: 10000, + control: { + type: 'select', + options: [100, 500, 1000, 2000, 5000, 8000, 10000, 50000, 100000, 500000], + }, + }, + multipleGrid: { + defaultValue: false, + control: { + type: 'boolean', + }, + }, + headerHeight: { + control: { type: 'range', min: 20, max: 150, step: 10 }, + }, + rowHeight: { + control: { type: 'range', min: 20, max: 150, step: 10 }, + }, + }, + decorators: [gridContainer], parameters: { options: { selectedPanel: 'storybook/controls/panel' }, - docs: { - page: null, - }, }, } as Meta; -const gridContainer: DecoratorFn = (storyFn) =>
{storyFn()}
; - -export const Commodity: Story = ({ rows, columns, ...args }) => { - const { data, setRowLength, loadNewData } = useDemoData({ dataSet: 'Commodity', rowLength: 100 }); - - return ( - -
- - -
-
- -
-
- ); -}; - -const DemoTemplate: Story = ({ +const DemoTemplate: Story = ({ rows, columns, - rowLength, dataSet, + license, + rowLength, maxColumns, + multipleGrid, ...args }) => { - const { data } = useDemoData({ rowLength, dataSet, maxColumns }); + const isRaw = dataSet.toString() === 'Raw'; + let data: any = useData(isRaw ? rowLength : 0, maxColumns || 20); + const demoData = useDemoData({ rowLength: !isRaw ? rowLength : 0, dataSet, maxColumns }); - return ; -}; - -export const Commodity500 = DemoTemplate.bind({}); -Commodity500.args = { - rowLength: 500, - dataSet: 'Commodity', -}; -Commodity500.decorators = [gridContainer]; - -export const Commodity1000 = DemoTemplate.bind({}); -Commodity1000.args = { - rowLength: 1000, - dataSet: 'Commodity', -}; -Commodity1000.decorators = [gridContainer]; - -export const Commodity10000 = DemoTemplate.bind({}); -Commodity10000.args = { - rowLength: 10000, - dataSet: 'Commodity', -}; -Commodity10000.decorators = [gridContainer]; - -export const Employee100 = DemoTemplate.bind({}); -Employee100.args = { - rowLength: 500, - dataSet: 'Employee', -}; -Employee100.decorators = [gridContainer]; - -export const Employee1000 = DemoTemplate.bind({}); -Employee1000.args = { - rowLength: 1000, - dataSet: 'Employee', -}; -Employee1000.decorators = [gridContainer]; - -export const Employee10000 = DemoTemplate.bind({}); -Employee10000.args = { - rowLength: 10000, - dataSet: 'Commodity', -}; -Employee10000.decorators = [gridContainer]; + if (!isRaw) { + data = demoData.data; + } -export const MultipleEmployee100: Story = (args) => { - const { data } = useDemoData({ dataSet: 'Employee', rowLength: 100 }); + const Grid = license === 'XGrid' ? XGrid : DataGrid; - return ( -
-
- -
-
- -
-
+ return !multipleGrid ? ( + + ) : ( + + + + ); }; -export const XGridDemo = DemoTemplate.bind({}); -XGridDemo.args = { - dataSet: 'Commodity', - rowLength: 100000, - rowHeight: 38, +export const Demo = DemoTemplate.bind({}); +Demo.args = { checkboxSelection: true, }; -XGridDemo.decorators = [gridContainer]; -export const CommodityPreferencesDefaultOpen = DemoTemplate.bind({}); -CommodityPreferencesDefaultOpen.args = { - dataSet: 'Commodity', - rowLength: 500, - state: { preferencePanel: { open: true } }, -}; -CommodityPreferencesDefaultOpen.decorators = [gridContainer]; - -export const CommodityPreferences = DemoTemplate.bind({}); -CommodityPreferences.args = { - dataSet: 'Commodity', - rowLength: 500, +export const ColumnSelector = DemoTemplate.bind({}); +ColumnSelector.args = { state: { preferencePanel: { open: true, @@ -136,4 +109,17 @@ CommodityPreferences.args = { }, }, }; -CommodityPreferences.decorators = [gridContainer]; + +export const Custom = DemoTemplate.bind({}); +Custom.args = { + components: { + Footer: CustomFooter, + ColumnSortedDescendingIcon: SortedDescendingIcon, + ColumnSortedAscendingIcon: SortedAscendingIcon, + NoRowsOverlay: NoRowsComponent, + LoadingOverlay: LoadingComponent, + Pagination: PaginationComponent, + ColumnMenu: ColumnMenuComponent, + Toolbar: GridToolbar, + }, +};