diff --git a/docs/data/data-grid/column-dimensions/ColumnAutosizing.js b/docs/data/data-grid/column-dimensions/ColumnAutosizing.js new file mode 100644 index 0000000000000..1743b754eccc1 --- /dev/null +++ b/docs/data/data-grid/column-dimensions/ColumnAutosizing.js @@ -0,0 +1,129 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Rating from '@mui/material/Rating'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import { useGridApiRef } from '@mui/x-data-grid'; +import { DataGridPro, DEFAULT_GRID_AUTOSIZE_OPTIONS } from '@mui/x-data-grid-pro'; +import { randomRating, randomTraderName } from '@mui/x-data-grid-generator'; + +function renderRating(params) { + return ; +} + +function useData(length) { + return React.useMemo(() => { + const names = [ + 'Nike', + 'Adidas', + 'Puma', + 'Reebok', + 'Fila', + 'Lululemon Athletica Clothing', + 'Varley', + ]; + + const rows = Array.from({ length }).map((_, id) => ({ + id, + brand: names[id % names.length], + rep: randomTraderName(), + rating: randomRating(), + })); + + const columns = [ + { field: 'id', headerName: 'Brand ID' }, + { field: 'brand', headerName: 'Brand name' }, + { field: 'rep', headerName: 'Representative' }, + { field: 'rating', headerName: 'Rating', renderCell: renderRating }, + ]; + + return { rows, columns }; + }, [length]); +} + +export default function ColumnAutosizing() { + const apiRef = useGridApiRef(); + const data = useData(100); + + const [includeHeaders, setIncludeHeaders] = React.useState( + DEFAULT_GRID_AUTOSIZE_OPTIONS.includeHeaders, + ); + const [includeOutliers, setExcludeOutliers] = React.useState( + DEFAULT_GRID_AUTOSIZE_OPTIONS.includeOutliers, + ); + const [outliersFactor, setOutliersFactor] = React.useState( + String(DEFAULT_GRID_AUTOSIZE_OPTIONS.outliersFactor), + ); + const [expand, setExpand] = React.useState(DEFAULT_GRID_AUTOSIZE_OPTIONS.expand); + + const autosizeOptions = { + includeHeaders, + includeOutliers, + outliersFactor: Number.isNaN(parseFloat(outliersFactor)) + ? 1 + : parseFloat(outliersFactor), + expand, + }; + + return ( +
+ + + setIncludeHeaders(ev.target.checked)} + /> + } + label="Include headers" + /> + setExcludeOutliers(event.target.checked)} + /> + } + label="Include outliers" + /> + setOutliersFactor(ev.target.value)} + sx={{ width: '12ch' }} + /> + setExpand(ev.target.checked)} + /> + } + label="Expand" + /> + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/column-dimensions/ColumnAutosizing.tsx b/docs/data/data-grid/column-dimensions/ColumnAutosizing.tsx new file mode 100644 index 0000000000000..066846d3406c2 --- /dev/null +++ b/docs/data/data-grid/column-dimensions/ColumnAutosizing.tsx @@ -0,0 +1,132 @@ +import * as React from 'react'; +import Button from '@mui/material/Button'; +import Checkbox from '@mui/material/Checkbox'; +import FormControlLabel from '@mui/material/FormControlLabel'; +import Rating from '@mui/material/Rating'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import { useGridApiRef } from '@mui/x-data-grid'; +import { + DataGridPro, + GridApiPro, + DEFAULT_GRID_AUTOSIZE_OPTIONS, +} from '@mui/x-data-grid-pro'; +import { randomRating, randomTraderName } from '@mui/x-data-grid-generator'; + +function renderRating(params: any) { + return ; +} + +function useData(length: number) { + return React.useMemo(() => { + const names = [ + 'Nike', + 'Adidas', + 'Puma', + 'Reebok', + 'Fila', + 'Lululemon Athletica Clothing', + 'Varley', + ]; + const rows = Array.from({ length }).map((_, id) => ({ + id, + brand: names[id % names.length], + rep: randomTraderName(), + rating: randomRating(), + })); + + const columns = [ + { field: 'id', headerName: 'Brand ID' }, + { field: 'brand', headerName: 'Brand name' }, + { field: 'rep', headerName: 'Representative' }, + { field: 'rating', headerName: 'Rating', renderCell: renderRating }, + ]; + + return { rows, columns }; + }, [length]); +} + +export default function ColumnAutosizing() { + const apiRef = useGridApiRef(); + const data = useData(100); + + const [includeHeaders, setIncludeHeaders] = React.useState( + DEFAULT_GRID_AUTOSIZE_OPTIONS.includeHeaders, + ); + const [includeOutliers, setExcludeOutliers] = React.useState( + DEFAULT_GRID_AUTOSIZE_OPTIONS.includeOutliers, + ); + const [outliersFactor, setOutliersFactor] = React.useState( + String(DEFAULT_GRID_AUTOSIZE_OPTIONS.outliersFactor), + ); + const [expand, setExpand] = React.useState(DEFAULT_GRID_AUTOSIZE_OPTIONS.expand); + + const autosizeOptions = { + includeHeaders, + includeOutliers, + outliersFactor: Number.isNaN(parseFloat(outliersFactor)) + ? 1 + : parseFloat(outliersFactor), + expand, + }; + + return ( +
+ + + setIncludeHeaders(ev.target.checked)} + /> + } + label="Include headers" + /> + setExcludeOutliers(event.target.checked)} + /> + } + label="Include outliers" + /> + setOutliersFactor(ev.target.value)} + sx={{ width: '12ch' }} + /> + setExpand(ev.target.checked)} + /> + } + label="Expand" + /> + +
+ +
+
+ ); +} diff --git a/docs/data/data-grid/column-dimensions/column-dimensions.md b/docs/data/data-grid/column-dimensions/column-dimensions.md index bded63f3da53f..3a60bf30cbe9d 100644 --- a/docs/data/data-grid/column-dimensions/column-dimensions.md +++ b/docs/data/data-grid/column-dimensions/column-dimensions.md @@ -58,6 +58,45 @@ To capture changes in the width of a column there are two callbacks that are cal - `onColumnResize`: Called while a column is being resized. - `onColumnWidthChange`: Called after the width of a column is changed, but not during resizing. +## Autosizing [](/x/introduction/licensing/#pro-plan 'Pro plan') + +`DataGridPro` allows to autosize the columns' dimensions based on their content. Autosizing is enabled by default. To turn it off, pass the `disableAutosize` prop to the datagrid. + +Autosizing can be used by one of the following methods: + +- Adding the `autosizeOnMount` prop, +- Double-clicking a column header separator on the grid, +- Calling the `apiRef.current.autosizeColumns(options)` API method. + +You can pass options directly to the API method when you call it. To configure autosize for the other two methods, provide the options in the `autosizeOptions` prop. + +Note that for the separator double-click method, the `autosizeOptions.columns` will be replaced by the respective column user double-clicked on. + +In all the cases, the `colDef.minWidth` and `colDef.maxWidth` options will be respected. + +```tsx + +``` + +{{"demo": "ColumnAutosizing.js", "disableAd": true, "bg": "inline"}} + +:::warning +Autosizing has no effect if dynamic row height is enabled. +::: + +:::warning +The data grid can only autosize based on the currently rendered cells. + +DOM access is required to accurately calculate dimensions, so unmounted cells (when [virtualization](/x/react-data-grid/virtualization/) is on) cannot be sized. If you need a bigger row sample, [open an issue](https://github.com/mui/mui-x/issues) to discuss it further. +::: + ## API - [DataGrid](/x/api/data-grid/data-grid/) diff --git a/docs/data/data-grid/getting-started/getting-started.md b/docs/data/data-grid/getting-started/getting-started.md index 09b092da8d60f..1503de859a2df 100644 --- a/docs/data/data-grid/getting-started/getting-started.md +++ b/docs/data/data-grid/getting-started/getting-started.md @@ -178,6 +178,7 @@ The enterprise components come in two plans: Pro and Premium. | [Column groups](/x/react-data-grid/column-groups/) | ✅ | ✅ | ✅ | | [Column spanning](/x/react-data-grid/column-spanning/) | ✅ | ✅ | ✅ | | [Column resizing](/x/react-data-grid/column-dimensions/#resizing) | ❌ | ✅ | ✅ | +| [Column autosizing](/x/react-data-grid/column-dimensions/#autosizing) | ❌ | ✅ | ✅ | | [Column reorder](/x/react-data-grid/column-ordering/) | ❌ | ✅ | ✅ | | [Column pinning](/x/react-data-grid/column-pinning/) | ❌ | ✅ | ✅ | | **Row** | | | | diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index bdc26c3d9d287..214405c70d17f 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -22,6 +22,13 @@ "aria-labelledby": { "type": { "name": "string" } }, "autoHeight": { "type": { "name": "bool" } }, "autoPageSize": { "type": { "name": "bool" } }, + "autosizeOnMount": { "type": { "name": "bool" } }, + "autosizeOptions": { + "type": { + "name": "shape", + "description": "{ columns?: Array<string>, expand?: bool, includeHeaders?: bool, includeOutliers?: bool, outliersFactor?: number }" + } + }, "cellModesModel": { "type": { "name": "object" } }, "checkboxSelection": { "type": { "name": "bool" } }, "checkboxSelectionVisibleOnly": { @@ -56,6 +63,7 @@ "type": { "name": "arrayOf", "description": "Array<number
| string>" } }, "disableAggregation": { "type": { "name": "bool" } }, + "disableAutosize": { "type": { "name": "bool" } }, "disableChildrenFiltering": { "type": { "name": "bool" } }, "disableChildrenSorting": { "type": { "name": "bool" } }, "disableClipboardPaste": { "type": { "name": "bool" } }, diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index 726b11be83007..ae3753d91c47f 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -13,6 +13,13 @@ "aria-labelledby": { "type": { "name": "string" } }, "autoHeight": { "type": { "name": "bool" } }, "autoPageSize": { "type": { "name": "bool" } }, + "autosizeOnMount": { "type": { "name": "bool" } }, + "autosizeOptions": { + "type": { + "name": "shape", + "description": "{ columns?: Array<string>, expand?: bool, includeHeaders?: bool, includeOutliers?: bool, outliersFactor?: number }" + } + }, "cellModesModel": { "type": { "name": "object" } }, "checkboxSelection": { "type": { "name": "bool" } }, "checkboxSelectionVisibleOnly": { @@ -46,6 +53,7 @@ "detailPanelExpandedRowIds": { "type": { "name": "arrayOf", "description": "Array<number
| string>" } }, + "disableAutosize": { "type": { "name": "bool" } }, "disableChildrenFiltering": { "type": { "name": "bool" } }, "disableChildrenSorting": { "type": { "name": "bool" } }, "disableColumnFilter": { "type": { "name": "bool" } }, diff --git a/docs/pages/x/api/data-grid/grid-api.md b/docs/pages/x/api/data-grid/grid-api.md index fd398b891abd1..d7c967eb00f73 100644 --- a/docs/pages/x/api/data-grid/grid-api.md +++ b/docs/pages/x/api/data-grid/grid-api.md @@ -27,6 +27,7 @@ import { GridApi } from '@mui/x-data-grid'; | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | addRowGroupingCriteria [](/x/introduction/licensing/#premium-plan) | (groupingCriteriaField: string, groupingIndex?: number) => void | Adds the field to the row grouping model. | | applySorting | () => void | Applies the current sort model to the rows. | +| autosizeColumns [](/x/introduction/licensing/#pro-plan) | (options?: GridAutosizeOptions) => Promise<void> | Auto-size the columns of the grid based on the cells' content and the space available. | | deleteFilterItem | (item: GridFilterItem) => void | Deletes a [GridFilterItem](/x/api/data-grid/grid-filter-item/). | | exportDataAsCsv | (options?: GridCsvExportOptions) => void | Downloads and exports a CSV of the grid's data. | | exportDataAsExcel [](/x/introduction/licensing/#premium-plan) | (options?: GridExcelExportOptions) => Promise<void> | Downloads and exports an Excel file of the grid's data. | @@ -137,7 +138,9 @@ import { GridApi } from '@mui/x-data-grid'; | unstable_replaceRows | (firstRowToReplace: number, newRows: GridRowModel[]) => void | Replace a set of rows with new rows. | | unstable_selectCellRange [](/x/introduction/licensing/#premium-plan) | (start: GridCellCoordinates, end: GridCellCoordinates, keepOtherSelected?: boolean) => void | Selects all cells that are inside the range given by `start` and `end` coordinates. | | unstable_setCellSelectionModel [](/x/introduction/licensing/#premium-plan) | (newModel: GridCellSelectionModel) => void | Updates the selected cells to be those passed to the `newModel` argument.
Any cell already selected will be unselected. | +| unstable_setColumnVirtualization | (enabled: boolean) => void | Enable/disable column virtualization. | | unstable_setPinnedRows [](/x/introduction/licensing/#pro-plan) | (pinnedRows?: GridPinnedRowsProp) => void | Changes the pinned rows. | +| unstable_setVirtualization | (enabled: boolean) => void | Enable/disable virtualization. | | updateColumns | (cols: GridColDef[]) => void | Updates the definition of multiple columns at the same time. | | updateRows | (updates: GridRowModelUpdate[]) => void | Allows to update, insert and delete rows. | | upsertFilterItem | (item: GridFilterItem) => void | Updates or inserts a [GridFilterItem](/x/api/data-grid/grid-filter-item/). | diff --git a/docs/pages/x/api/data-grid/grid-column-resize-api.json b/docs/pages/x/api/data-grid/grid-column-resize-api.json new file mode 100644 index 0000000000000..50378d4b2d1a4 --- /dev/null +++ b/docs/pages/x/api/data-grid/grid-column-resize-api.json @@ -0,0 +1,11 @@ +{ + "name": "GridColumnResizeApi", + "description": "The Resize API interface that is available in the grid `apiRef`.", + "properties": [ + { + "name": "autosizeColumns", + "description": "Auto-size the columns of the grid based on the cells' content and the space available.", + "type": "(options?: GridAutosizeOptions) => Promise" + } + ] +} diff --git a/docs/pages/x/api/data-grid/grid-virtualization-api.json b/docs/pages/x/api/data-grid/grid-virtualization-api.json new file mode 100644 index 0000000000000..d295357e4397a --- /dev/null +++ b/docs/pages/x/api/data-grid/grid-virtualization-api.json @@ -0,0 +1,16 @@ +{ + "name": "GridVirtualizationApi", + "description": "", + "properties": [ + { + "name": "unstable_setColumnVirtualization", + "description": "Enable/disable column virtualization.", + "type": "(enabled: boolean) => void" + }, + { + "name": "unstable_setVirtualization", + "description": "Enable/disable virtualization.", + "type": "(enabled: boolean) => void" + } + ] +} diff --git a/docs/pages/x/api/data-grid/selectors.json b/docs/pages/x/api/data-grid/selectors.json index 418ad2366f6bc..ed03b0af5172a 100644 --- a/docs/pages/x/api/data-grid/selectors.json +++ b/docs/pages/x/api/data-grid/selectors.json @@ -424,6 +424,27 @@ "description": "", "supportsApiRef": true }, + { + "name": "gridVirtualizationColumnEnabledSelector", + "returnType": "boolean", + "category": "Virtualization", + "description": "Get the enabled state for virtualization", + "supportsApiRef": true + }, + { + "name": "gridVirtualizationEnabledSelector", + "returnType": "boolean", + "category": "Virtualization", + "description": "Get the enabled state for virtualization", + "supportsApiRef": true + }, + { + "name": "gridVirtualizationSelector", + "returnType": "GridVirtualizationState", + "category": "Virtualization", + "description": "Get the columns state", + "supportsApiRef": false + }, { "name": "gridVisibleColumnDefinitionsSelector", "returnType": "GridStateColDef[]", diff --git a/docs/scripts/api/buildInterfacesDocumentation.ts b/docs/scripts/api/buildInterfacesDocumentation.ts index e9345308365e8..48b578dfda536 100644 --- a/docs/scripts/api/buildInterfacesDocumentation.ts +++ b/docs/scripts/api/buildInterfacesDocumentation.ts @@ -40,21 +40,22 @@ interface ParsedProperty { } const GRID_API_INTERFACES_WITH_DEDICATED_PAGES = [ - 'GridRowSelectionApi', - 'GridRowMultiSelectionApi', 'GridCellSelectionApi', - 'GridFilterApi', - 'GridSortApi', - 'GridPaginationApi', - 'GridCsvExportApi', - 'GridScrollApi', - 'GridEditingApi', - 'GridRowGroupingApi', 'GridColumnPinningApi', + 'GridColumnResizeApi', + 'GridCsvExportApi', 'GridDetailPanelApi', - 'GridPrintExportApi', - 'GridDisableVirtualizationApi', + 'GridEditingApi', 'GridExcelExportApi', + 'GridFilterApi', + 'GridPaginationApi', + 'GridPrintExportApi', + 'GridRowGroupingApi', + 'GridRowMultiSelectionApi', + 'GridRowSelectionApi', + 'GridScrollApi', + 'GridSortApi', + 'GridVirtualizationApi', ]; const OTHER_GRID_INTERFACES_WITH_DEDICATED_PAGES = [ diff --git a/docs/translations/api-docs/data-grid/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium.json index 6777cb6c53e5a..56c0232c3a532 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium.json @@ -41,6 +41,16 @@ "deprecated": "", "typeDescriptions": {} }, + "autosizeOnMount": { + "description": "If true, columns are autosized after the datagrid is mounted.", + "deprecated": "", + "typeDescriptions": {} + }, + "autosizeOptions": { + "description": "The options for autosize when user-initiated.", + "deprecated": "", + "typeDescriptions": {} + }, "cellModesModel": { "description": "Controls the modes of the cells.", "deprecated": "", @@ -121,6 +131,11 @@ "deprecated": "", "typeDescriptions": {} }, + "disableAutosize": { + "description": "If true, column autosizing on header separator double-click is disabled.", + "deprecated": "", + "typeDescriptions": {} + }, "disableChildrenFiltering": { "description": "If true, the filtering will only be applied to the top level rows when grouping rows with the treeData prop.", "deprecated": "", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro.json index 141f12c38f150..00bf251a86214 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro.json @@ -26,6 +26,16 @@ "deprecated": "", "typeDescriptions": {} }, + "autosizeOnMount": { + "description": "If true, columns are autosized after the datagrid is mounted.", + "deprecated": "", + "typeDescriptions": {} + }, + "autosizeOptions": { + "description": "The options for autosize when user-initiated.", + "deprecated": "", + "typeDescriptions": {} + }, "cellModesModel": { "description": "Controls the modes of the cells.", "deprecated": "", @@ -101,6 +111,11 @@ "deprecated": "", "typeDescriptions": {} }, + "disableAutosize": { + "description": "If true, column autosizing on header separator double-click is disabled.", + "deprecated": "", + "typeDescriptions": {} + }, "disableChildrenFiltering": { "description": "If true, the filtering will only be applied to the top level rows when grouping rows with the treeData prop.", "deprecated": "", diff --git a/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 0357715bb15ce..95bb964b8d390 100644 --- a/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/grid/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -107,6 +107,21 @@ DataGridPremiumRaw.propTypes = { * @default false */ autoPageSize: PropTypes.bool, + /** + * If `true`, columns are autosized after the datagrid is mounted. + * @default false + */ + autosizeOnMount: PropTypes.bool, + /** + * The options for autosize when user-initiated. + */ + autosizeOptions: PropTypes.shape({ + columns: PropTypes.arrayOf(PropTypes.string), + expand: PropTypes.bool, + includeHeaders: PropTypes.bool, + includeOutliers: PropTypes.bool, + outliersFactor: PropTypes.number, + }), /** * Controls the modes of the cells. */ @@ -195,6 +210,11 @@ DataGridPremiumRaw.propTypes = { * @default false */ disableAggregation: PropTypes.bool, + /** + * If `true`, column autosizing on header separator double-click is disabled. + * @default false + */ + disableAutosize: PropTypes.bool, /** * If `true`, the filtering will only be applied to the top level rows when grouping rows with the `treeData` prop. * @default false diff --git a/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index 4da70394a3142..aaca7bff91acb 100644 --- a/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/grid/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -62,6 +62,8 @@ import { useGridLazyLoaderPreProcessors, headerFilteringStateInitializer, useGridHeaderFiltering, + virtualizationStateInitializer, + useGridVirtualization, } from '@mui/x-data-grid-pro/internals'; import { GridApiPremium, GridPrivateApiPremium } from '../models/gridApiPremium'; import { DataGridPremiumProcessedProps } from '../models/dataGridPremiumProps'; @@ -87,91 +89,90 @@ export const useDataGridPremiumComponent = ( inputApiRef: React.MutableRefObject | undefined, props: DataGridPremiumProcessedProps, ) => { - const privateApiRef = useGridInitialization( - inputApiRef, - props, - ); + const apiRef = useGridInitialization(inputApiRef, props); /** * Register all pre-processors called during state initialization here. */ - useGridRowSelectionPreProcessors(privateApiRef, props); - useGridRowReorderPreProcessors(privateApiRef, props); - useGridRowGroupingPreProcessors(privateApiRef, props); - useGridTreeDataPreProcessors(privateApiRef, props); - useGridLazyLoaderPreProcessors(privateApiRef, props); - useGridRowPinningPreProcessors(privateApiRef); - useGridAggregationPreProcessors(privateApiRef, props); - useGridDetailPanelPreProcessors(privateApiRef, props); + useGridRowSelectionPreProcessors(apiRef, props); + useGridRowReorderPreProcessors(apiRef, props); + useGridRowGroupingPreProcessors(apiRef, props); + useGridTreeDataPreProcessors(apiRef, props); + useGridLazyLoaderPreProcessors(apiRef, props); + useGridRowPinningPreProcessors(apiRef); + useGridAggregationPreProcessors(apiRef, props); + useGridDetailPanelPreProcessors(apiRef, props); // The column pinning `hydrateColumns` pre-processor must be after every other `hydrateColumns` pre-processors // Because it changes the order of the columns. - useGridColumnPinningPreProcessors(privateApiRef, props); - useGridRowsPreProcessors(privateApiRef); + useGridColumnPinningPreProcessors(apiRef, props); + useGridRowsPreProcessors(apiRef); /** * Register all state initializers here. */ - useGridInitializeState(headerFilteringStateInitializer, privateApiRef, props); - useGridInitializeState(rowGroupingStateInitializer, privateApiRef, props); - useGridInitializeState(aggregationStateInitializer, privateApiRef, props); - useGridInitializeState(rowSelectionStateInitializer, privateApiRef, props); - useGridInitializeState(cellSelectionStateInitializer, privateApiRef, props); - useGridInitializeState(detailPanelStateInitializer, privateApiRef, props); - useGridInitializeState(columnPinningStateInitializer, privateApiRef, props); - useGridInitializeState(columnsStateInitializer, privateApiRef, props); - useGridInitializeState(rowPinningStateInitializer, privateApiRef, props); - useGridInitializeState(rowsStateInitializer, privateApiRef, props); - useGridInitializeState(editingStateInitializer, privateApiRef, props); - useGridInitializeState(focusStateInitializer, privateApiRef, props); - useGridInitializeState(sortingStateInitializer, privateApiRef, props); - useGridInitializeState(preferencePanelStateInitializer, privateApiRef, props); - useGridInitializeState(filterStateInitializer, privateApiRef, props); - useGridInitializeState(densityStateInitializer, privateApiRef, props); - useGridInitializeState(columnReorderStateInitializer, privateApiRef, props); - useGridInitializeState(columnResizeStateInitializer, privateApiRef, props); - useGridInitializeState(paginationStateInitializer, privateApiRef, props); - useGridInitializeState(rowsMetaStateInitializer, privateApiRef, props); - useGridInitializeState(columnMenuStateInitializer, privateApiRef, props); - useGridInitializeState(columnGroupsStateInitializer, privateApiRef, props); + useGridInitializeState(headerFilteringStateInitializer, apiRef, props); + useGridInitializeState(rowGroupingStateInitializer, apiRef, props); + useGridInitializeState(aggregationStateInitializer, apiRef, props); + useGridInitializeState(rowSelectionStateInitializer, apiRef, props); + useGridInitializeState(cellSelectionStateInitializer, apiRef, props); + useGridInitializeState(detailPanelStateInitializer, apiRef, props); + useGridInitializeState(columnPinningStateInitializer, apiRef, props); + useGridInitializeState(columnsStateInitializer, apiRef, props); + useGridInitializeState(rowPinningStateInitializer, apiRef, props); + useGridInitializeState(rowsStateInitializer, apiRef, props); + useGridInitializeState(editingStateInitializer, apiRef, props); + useGridInitializeState(focusStateInitializer, apiRef, props); + useGridInitializeState(sortingStateInitializer, apiRef, props); + useGridInitializeState(preferencePanelStateInitializer, apiRef, props); + useGridInitializeState(filterStateInitializer, apiRef, props); + useGridInitializeState(densityStateInitializer, apiRef, props); + useGridInitializeState(columnReorderStateInitializer, apiRef, props); + useGridInitializeState(columnResizeStateInitializer, apiRef, props); + useGridInitializeState(paginationStateInitializer, apiRef, props); + useGridInitializeState(rowsMetaStateInitializer, apiRef, props); + useGridInitializeState(columnMenuStateInitializer, apiRef, props); + useGridInitializeState(columnGroupsStateInitializer, apiRef, props); + useGridInitializeState(virtualizationStateInitializer, apiRef, props); - useGridRowGrouping(privateApiRef, props); - useGridHeaderFiltering(privateApiRef, props); - useGridTreeData(privateApiRef); - useGridAggregation(privateApiRef, props); - useGridKeyboardNavigation(privateApiRef, props); - useGridRowSelection(privateApiRef, props); - useGridCellSelection(privateApiRef, props); - useGridColumnPinning(privateApiRef, props); - useGridRowPinning(privateApiRef, props); - useGridColumns(privateApiRef, props); - useGridRows(privateApiRef, props); - useGridParamsApi(privateApiRef, props); - useGridDetailPanel(privateApiRef, props); - useGridColumnSpanning(privateApiRef); - useGridColumnGrouping(privateApiRef, props); - useGridClipboardImport(privateApiRef, props); - useGridEditing(privateApiRef, props); - useGridFocus(privateApiRef, props); - useGridPreferencesPanel(privateApiRef, props); - useGridFilter(privateApiRef, props); - useGridSorting(privateApiRef, props); - useGridDensity(privateApiRef, props); - useGridColumnReorder(privateApiRef, props); - useGridColumnResize(privateApiRef, props); - useGridPagination(privateApiRef, props); - useGridRowsMeta(privateApiRef, props); - useGridRowReorder(privateApiRef, props); - useGridScroll(privateApiRef, props); - useGridInfiniteLoader(privateApiRef, props); - useGridLazyLoader(privateApiRef, props); - useGridColumnMenu(privateApiRef); - useGridCsvExport(privateApiRef, props); - useGridPrintExport(privateApiRef, props); - useGridExcelExport(privateApiRef, props); - useGridClipboard(privateApiRef, props); - useGridDimensions(privateApiRef, props); - useGridEvents(privateApiRef, props); - useGridStatePersistence(privateApiRef); + useGridRowGrouping(apiRef, props); + useGridHeaderFiltering(apiRef, props); + useGridTreeData(apiRef); + useGridAggregation(apiRef, props); + useGridKeyboardNavigation(apiRef, props); + useGridRowSelection(apiRef, props); + useGridCellSelection(apiRef, props); + useGridColumnPinning(apiRef, props); + useGridRowPinning(apiRef, props); + useGridColumns(apiRef, props); + useGridRows(apiRef, props); + useGridParamsApi(apiRef, props); + useGridDetailPanel(apiRef, props); + useGridColumnSpanning(apiRef); + useGridColumnGrouping(apiRef, props); + useGridClipboardImport(apiRef, props); + useGridEditing(apiRef, props); + useGridFocus(apiRef, props); + useGridPreferencesPanel(apiRef, props); + useGridFilter(apiRef, props); + useGridSorting(apiRef, props); + useGridDensity(apiRef, props); + useGridColumnReorder(apiRef, props); + useGridColumnResize(apiRef, props); + useGridPagination(apiRef, props); + useGridRowsMeta(apiRef, props); + useGridRowReorder(apiRef, props); + useGridScroll(apiRef, props); + useGridInfiniteLoader(apiRef, props); + useGridLazyLoader(apiRef, props); + useGridColumnMenu(apiRef); + useGridCsvExport(apiRef, props); + useGridPrintExport(apiRef, props); + useGridExcelExport(apiRef, props); + useGridClipboard(apiRef, props); + useGridDimensions(apiRef, props); + useGridEvents(apiRef, props); + useGridStatePersistence(apiRef); + useGridVirtualization(apiRef, props); - return privateApiRef; + return apiRef; }; diff --git a/packages/grid/x-data-grid-premium/src/models/gridApiPremium.ts b/packages/grid/x-data-grid-premium/src/models/gridApiPremium.ts index 3577b6ca9c3bd..c28b36e4dd19c 100644 --- a/packages/grid/x-data-grid-premium/src/models/gridApiPremium.ts +++ b/packages/grid/x-data-grid-premium/src/models/gridApiPremium.ts @@ -2,6 +2,7 @@ import { GridPrivateOnlyApiCommon } from '@mui/x-data-grid/internals'; import { GridApiCommon, GridColumnPinningApi, + GridColumnResizeApi, GridDetailPanelApi, GridDetailPanelPrivateApi, GridRowPinningApi, @@ -21,6 +22,7 @@ export interface GridApiPremium extends GridApiCommon, GridRowProApi, GridColumnPinningApi, + GridColumnResizeApi, GridDetailPanelApi, GridRowGroupingApi, GridExcelExportApi, @@ -29,8 +31,7 @@ export interface GridApiPremium GridCellSelectionApi, // APIs that are private in Community plan, but public in Pro and Premium plans GridRowMultiSelectionApi, - GridColumnReorderApi, - GridRowProApi {} + GridColumnReorderApi {} export interface GridPrivateApiPremium extends GridApiPremium, diff --git a/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 0231d8bb4d41c..a4e53c48bdeca 100644 --- a/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/grid/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -90,6 +90,21 @@ DataGridProRaw.propTypes = { * @default false */ autoPageSize: PropTypes.bool, + /** + * If `true`, columns are autosized after the datagrid is mounted. + * @default false + */ + autosizeOnMount: PropTypes.bool, + /** + * The options for autosize when user-initiated. + */ + autosizeOptions: PropTypes.shape({ + columns: PropTypes.arrayOf(PropTypes.string), + expand: PropTypes.bool, + includeHeaders: PropTypes.bool, + includeOutliers: PropTypes.bool, + outliersFactor: PropTypes.number, + }), /** * Controls the modes of the cells. */ @@ -173,6 +188,11 @@ DataGridProRaw.propTypes = { detailPanelExpandedRowIds: PropTypes.arrayOf( PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, ), + /** + * If `true`, column autosizing on header separator double-click is disabled. + * @default false + */ + disableAutosize: PropTypes.bool, /** * If `true`, the filtering will only be applied to the top level rows when grouping rows with the `treeData` prop. * @default false diff --git a/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index e9077e1996b1d..23220818d45aa 100644 --- a/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -42,6 +42,8 @@ import { columnGroupsStateInitializer, headerFilteringStateInitializer, useGridHeaderFiltering, + virtualizationStateInitializer, + useGridVirtualization, } from '@mui/x-data-grid/internals'; import { GridApiPro, GridPrivateApiPro } from '../models/gridApiPro'; import { DataGridProProcessedProps } from '../models/dataGridProProps'; @@ -119,6 +121,7 @@ export const useDataGridProComponent = ( useGridInitializeState(rowsMetaStateInitializer, apiRef, props); useGridInitializeState(columnMenuStateInitializer, apiRef, props); useGridInitializeState(columnGroupsStateInitializer, apiRef, props); + useGridInitializeState(virtualizationStateInitializer, apiRef, props); useGridHeaderFiltering(apiRef, props); useGridTreeData(apiRef); @@ -153,6 +156,7 @@ export const useDataGridProComponent = ( useGridDimensions(apiRef, props); useGridEvents(apiRef, props); useGridStatePersistence(apiRef); + useGridVirtualization(apiRef, props); return apiRef; }; diff --git a/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts b/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts index 9c991e4f24d8d..eb8c0ab505db0 100644 --- a/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts +++ b/packages/grid/x-data-grid-pro/src/DataGridPro/useDataGridProProps.ts @@ -22,6 +22,8 @@ export const DATA_GRID_PRO_PROPS_DEFAULT_VALUES: DataGridProPropsWithDefaultValu scrollEndThreshold: 80, treeData: false, defaultGroupingExpansionDepth: 0, + autosizeOnMount: false, + disableAutosize: false, disableColumnPinning: false, keepColumnPositionIfDraggedOutside: false, disableChildrenFiltering: false, diff --git a/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/gridColumnResizeApi.ts b/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/gridColumnResizeApi.ts new file mode 100644 index 0000000000000..c19883f4368aa --- /dev/null +++ b/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/gridColumnResizeApi.ts @@ -0,0 +1,47 @@ +import { GridColDef } from '@mui/x-data-grid'; + +export const DEFAULT_GRID_AUTOSIZE_OPTIONS = { + includeHeaders: true, + includeOutliers: false, + outliersFactor: 1.5, + expand: false, +}; + +export type GridAutosizeOptions = { + /** + * The columns to autosize. By default, applies to all columns. + */ + columns?: GridColDef['field'][]; + /** + * If true, include the header widths in the calculation. + * @default false + */ + includeHeaders?: boolean; + /** + * If true, width outliers will be ignored. + * @default false + */ + includeOutliers?: boolean; + /** + * The IQR factor range to detect outliers. + * @default 1.5 + */ + outliersFactor?: number; + /** + * If the total width is less than the available width, expand columns to fill it. + * @default false + */ + expand?: boolean; +}; + +/** + * The Resize API interface that is available in the grid `apiRef`. + */ +export interface GridColumnResizeApi { + /** + * Auto-size the columns of the grid based on the cells' content and the space available. + * @param {GridAutosizeOptions} options The autosizing options + * @returns {Promise} A promise that resolves when autosizing is completed + */ + autosizeColumns: (options?: GridAutosizeOptions) => Promise; +} diff --git a/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/index.ts b/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/index.ts index f4105375e68ff..beb30a48691a6 100644 --- a/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/index.ts +++ b/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/index.ts @@ -1,2 +1,3 @@ export * from './columnResizeSelector'; export * from './columnResizeState'; +export * from './gridColumnResizeApi'; diff --git a/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/useGridColumnResize.tsx b/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/useGridColumnResize.tsx index 93161721c049d..fdf34490bd9ae 100644 --- a/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/useGridColumnResize.tsx +++ b/packages/grid/x-data-grid-pro/src/hooks/features/columnResize/useGridColumnResize.tsx @@ -11,14 +11,22 @@ import { GridColumnResizeParams, useGridApiEventHandler, useGridApiOptionHandler, + useGridApiMethod, useGridNativeEventListener, useGridLogger, + useGridSelector, + gridVirtualizationColumnEnabledSelector, } from '@mui/x-data-grid'; import { clamp, findParentElementFromClassName, - GridStateInitializer, + gridColumnsStateSelector, + useOnMount, + useTimeout, + createControllablePromise, + ControllablePromise, GridStateColDef, + GridStateInitializer, } from '@mui/x-data-grid/internals'; import { useTheme, Direction } from '@mui/material/styles'; import { @@ -26,9 +34,18 @@ import { getFieldFromHeaderElem, findHeaderElementFromField, findGroupHeaderElementsFromField, + findGridHeader, + findGridCells, } from '../../../utils/domUtils'; import { GridPrivateApiPro } from '../../../models/gridApiPro'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; +import { + GridAutosizeOptions, + GridColumnResizeApi, + DEFAULT_GRID_AUTOSIZE_OPTIONS, +} from './gridColumnResizeApi'; + +type AutosizeOptionsRequired = Required; type ResizeDirection = keyof typeof GridColumnHeaderSeparatorSides; @@ -119,18 +136,139 @@ function getResizeDirection(element: HTMLElement, direction: Direction) { return side; } +function preventClick(event: MouseEvent) { + event.preventDefault(); + event.stopImmediatePropagation(); +} + +/** + * Checker that returns a promise that resolves when the column virtualization + * is disabled. + */ +function useColumnVirtualizationDisabled(apiRef: React.MutableRefObject) { + const promise = React.useRef(); + const selector = () => gridVirtualizationColumnEnabledSelector(apiRef); + const value = useGridSelector(apiRef, selector); + + React.useEffect(() => { + if (promise.current && value === false) { + promise.current.resolve(); + promise.current = undefined; + } + }); + + const asyncCheck = () => { + if (!promise.current) { + if (selector() === false) { + return Promise.resolve(); + } + promise.current = createControllablePromise(); + } + return promise.current; + }; + + return asyncCheck; +} + +/** + * Basic statistical outlier detection, checks if the value is `F * IQR` away from + * the Q1 and Q3 boundaries. IQR: interquartile range. + */ +function excludeOutliers(inputValues: number[], factor: number) { + if (inputValues.length < 4) { + return inputValues; + } + + const values = inputValues.slice(); + values.sort((a, b) => a - b); + + const q1 = values[Math.floor(values.length * 0.25)]; + const q3 = values[Math.floor(values.length * 0.75) - 1]; + const iqr = q3 - q1; + + // We make a small adjustment if `iqr < 5` for the cases where the IQR is + // very small (e.g. zero) due to very close by values in the input data. + // Otherwise, with an IQR of `0`, anything outside that would be considered + // an outlier, but it makes more sense visually to allow for this 5px variance + // rather than showing a cropped cell. + const deviation = iqr < 5 ? 5 : iqr * factor; + + return values.filter((v) => v > q1 - deviation && v < q3 + deviation); +} + +function extractColumnWidths( + apiRef: React.MutableRefObject, + options: AutosizeOptionsRequired, + columns: GridStateColDef[], +) { + const widthByField = {} as Record; + + columns.forEach((column) => { + const cells = findGridCells(apiRef.current, column.field); + + const widths = cells.map((cell) => { + const id = cell.parentElement!.getAttribute('data-id') ?? ''; + const hasAutoHeight = apiRef.current.rowHasAutoHeight(id); + if (hasAutoHeight) { + return (column as any).computedWidth ?? column.width; + } + const style = window.getComputedStyle(cell, null); + const paddingWidth = parseInt(style.paddingLeft, 10) + parseInt(style.paddingRight, 10); + const contentWidth = (cell.firstElementChild?.scrollWidth ?? -1) + 1; + return paddingWidth + contentWidth; + }); + + const filteredWidths = options.includeOutliers + ? widths + : excludeOutliers(widths, options.outliersFactor); + + if (options.includeHeaders) { + const header = findGridHeader(apiRef.current, column.field); + if (header) { + const title = header.querySelector(`.${gridClasses.columnHeaderTitle}`); + const content = header.querySelector(`.${gridClasses.columnHeaderTitleContainerContent}`)!; + const element = title ?? content; + + const style = window.getComputedStyle(header, null); + const paddingWidth = parseInt(style.paddingLeft, 10) + parseInt(style.paddingRight, 10); + const contentWidth = element.scrollWidth + 1; + const width = paddingWidth + contentWidth; + + filteredWidths.push(width); + } + } + + const hasColumnMin = column.minWidth !== -Infinity && column.minWidth !== undefined; + const hasColumnMax = column.maxWidth !== Infinity && column.maxWidth !== undefined; + + const min = hasColumnMin ? column.minWidth! : 0; + const max = hasColumnMax ? column.maxWidth! : Infinity; + const maxContent = filteredWidths.length === 0 ? 0 : Math.max(...filteredWidths); + + widthByField[column.field] = clamp(maxContent, min, max); + }); + + return widthByField; +} + export const columnResizeStateInitializer: GridStateInitializer = (state) => ({ ...state, columnResize: { resizingColumnField: '' }, }); - /** * @requires useGridColumns (method, event) * TODO: improve experience for last column */ export const useGridColumnResize = ( apiRef: React.MutableRefObject, - props: Pick, + props: Pick< + DataGridProProcessedProps, + | 'autosizeOptions' + | 'autosizeOnMount' + | 'disableAutosize' + | 'onColumnResize' + | 'onColumnWidthChange' + >, ) => { const logger = useGridLogger(apiRef, 'useGridColumnResize'); @@ -147,7 +285,7 @@ export const useGridColumnResize = ( const initialOffsetToSeparator = React.useRef(); const resizeDirection = React.useRef(); - const stopResizeEventTimeout = React.useRef(); + const stopResizeEventTimeout = useTimeout(); const touchId = React.useRef(); const updateWidth = (newWidth: number) => { @@ -201,8 +339,7 @@ export const useGridColumnResize = ( ); } - clearTimeout(stopResizeEventTimeout.current); - stopResizeEventTimeout.current = setTimeout(() => { + stopResizeEventTimeout.start(0, () => { apiRef.current.publishEvent('columnResizeStop', null, nativeEvent); }); }; @@ -234,66 +371,6 @@ export const useGridColumnResize = ( apiRef.current.publishEvent('columnResize', params, nativeEvent); }); - const handleColumnResizeMouseDown: GridEventListener<'columnSeparatorMouseDown'> = - useEventCallback(({ colDef }, event) => { - // Only handle left clicks - if (event.button !== 0) { - return; - } - - // Skip if the column isn't resizable - if (!event.currentTarget.classList.contains(gridClasses['columnSeparator--resizable'])) { - return; - } - - // Avoid text selection - event.preventDefault(); - - logger.debug(`Start Resize on col ${colDef.field}`); - apiRef.current.publishEvent('columnResizeStart', { field: colDef.field }, event); - - colDefRef.current = colDef as GridStateColDef; - colElementRef.current = - apiRef.current.columnHeadersContainerElementRef?.current!.querySelector( - `[data-field="${colDef.field}"]`, - )!; - - const headerFilterRowElement = apiRef.current.headerFiltersElementRef?.current; - - if (headerFilterRowElement) { - headerFilterElementRef.current = headerFilterRowElement.querySelector( - `[data-field="${colDef.field}"]`, - ) as HTMLDivElement; - } - - colGroupingElementRef.current = findGroupHeaderElementsFromField( - apiRef.current.columnHeadersContainerElementRef?.current!, - colDef.field, - ); - - colCellElementsRef.current = findGridCellElementsFromCol( - colElementRef.current, - apiRef.current, - ); - - const doc = ownerDocument(apiRef.current.rootElementRef!.current); - doc.body.style.cursor = 'col-resize'; - - resizeDirection.current = getResizeDirection(event.currentTarget, theme.direction); - - initialOffsetToSeparator.current = computeOffsetToSeparator( - event.clientX, - colElementRef.current!.getBoundingClientRect(), - resizeDirection.current, - ); - - doc.addEventListener('mousemove', handleResizeMouseMove); - doc.addEventListener('mouseup', handleResizeMouseUp); - - // Fixes https://github.com/mui/mui-x/issues/4777 - colElementRef.current.style.pointerEvents = 'none'; - }); - const handleTouchEnd = useEventCallback((nativeEvent: any) => { const finger = trackFinger(nativeEvent, touchId.current); @@ -395,6 +472,11 @@ export const useGridColumnResize = ( doc.removeEventListener('mouseup', handleResizeMouseUp); doc.removeEventListener('touchmove', handleTouchMove); doc.removeEventListener('touchend', handleTouchEnd); + // The click event runs right after the mouseup event, we want to wait until it + // has been canceled before removing our handler. + setTimeout(() => { + doc.removeEventListener('click', preventClick, true); + }, 100); if (colElementRef.current) { colElementRef.current!.style.pointerEvents = 'unset'; } @@ -426,12 +508,172 @@ export const useGridColumnResize = ( apiRef.current.forceUpdate(); }, [apiRef]); - React.useEffect(() => { - return () => { - clearTimeout(stopResizeEventTimeout.current); - stopListening(); - }; - }, [apiRef, handleTouchStart, stopListening]); + const handleColumnResizeMouseDown: GridEventListener<'columnSeparatorMouseDown'> = + useEventCallback(({ colDef }, event) => { + // Only handle left clicks + if (event.button !== 0) { + return; + } + + // Skip if the column isn't resizable + if (!event.currentTarget.classList.contains(gridClasses['columnSeparator--resizable'])) { + return; + } + + // Avoid text selection + event.preventDefault(); + + logger.debug(`Start Resize on col ${colDef.field}`); + apiRef.current.publishEvent('columnResizeStart', { field: colDef.field }, event); + + colDefRef.current = colDef as GridStateColDef; + colElementRef.current = + apiRef.current.columnHeadersContainerElementRef?.current!.querySelector( + `[data-field="${colDef.field}"]`, + )!; + + const headerFilterRowElement = apiRef.current.headerFiltersElementRef?.current; + + if (headerFilterRowElement) { + headerFilterElementRef.current = headerFilterRowElement.querySelector( + `[data-field="${colDef.field}"]`, + ) as HTMLDivElement; + } + + colGroupingElementRef.current = findGroupHeaderElementsFromField( + apiRef.current.columnHeadersContainerElementRef?.current!, + colDef.field, + ); + + colCellElementsRef.current = findGridCellElementsFromCol( + colElementRef.current, + apiRef.current, + ); + + const doc = ownerDocument(apiRef.current.rootElementRef!.current); + doc.body.style.cursor = 'col-resize'; + + resizeDirection.current = getResizeDirection(event.currentTarget, theme.direction); + + initialOffsetToSeparator.current = computeOffsetToSeparator( + event.clientX, + colElementRef.current!.getBoundingClientRect(), + resizeDirection.current, + ); + + doc.addEventListener('mousemove', handleResizeMouseMove); + doc.addEventListener('mouseup', handleResizeMouseUp); + + // Prevent the click event if we have resized the column. + // Fixes https://github.com/mui/mui-x/issues/4777 + doc.addEventListener('click', preventClick, true); + }); + + const handleColumnSeparatorDoubleClick: GridEventListener<'columnSeparatorDoubleClick'> = + useEventCallback((params, event) => { + if (props.disableAutosize) { + return; + } + + // Only handle left clicks + if (event.button !== 0) { + return; + } + + const column = apiRef.current.state.columns.lookup[params.field]; + if (column.resizable === false) { + return; + } + + apiRef.current.autosizeColumns({ + ...props.autosizeOptions, + columns: [column.field], + }); + }); + + /** + * API METHODS + */ + + const columnVirtualizationDisabled = useColumnVirtualizationDisabled(apiRef); + const isAutosizingRef = React.useRef(false); + const autosizeColumns = React.useCallback( + async (userOptions) => { + const root = apiRef.current.rootElementRef?.current; + if (!root) { + return; + } + if (isAutosizingRef.current) { + return; + } + isAutosizingRef.current = true; + + const state = gridColumnsStateSelector(apiRef.current.state); + const options = { + ...DEFAULT_GRID_AUTOSIZE_OPTIONS, + ...userOptions, + columns: userOptions?.columns ?? state.orderedFields, + }; + options.columns = options.columns.filter((c) => state.columnVisibilityModel[c] !== false); + + const columns = options.columns.map((c) => apiRef.current.state.columns.lookup[c]); + + try { + apiRef.current.unstable_setColumnVirtualization(false); + await columnVirtualizationDisabled(); + + const widthByField = extractColumnWidths(apiRef, options, columns); + + const newColumns = columns.map((column) => ({ + ...column, + width: widthByField[column.field], + computedWidth: widthByField[column.field], + })); + + if (options.expand) { + const visibleColumns = state.orderedFields + .map((field) => state.lookup[field]) + .filter((c) => state.columnVisibilityModel[c.field] !== false); + + const totalWidth = visibleColumns.reduce( + (total, column) => + total + (widthByField[column.field] ?? column.computedWidth ?? column.width), + 0, + ); + const availableWidth = apiRef.current.getRootDimensions()?.viewportInnerSize.width ?? 0; + const remainingWidth = availableWidth - totalWidth; + + if (remainingWidth > 0) { + const widthPerColumn = remainingWidth / (newColumns.length || 1); + newColumns.forEach((column) => { + column.width += widthPerColumn; + column.computedWidth += widthPerColumn; + }); + } + } + + apiRef.current.updateColumns(newColumns); + } finally { + apiRef.current.unstable_setColumnVirtualization(true); + isAutosizingRef.current = false; + } + }, + [apiRef, columnVirtualizationDisabled], + ); + + /** + * EFFECTS + */ + + React.useEffect(() => stopListening, [stopListening]); + + useOnMount(() => { + if (props.autosizeOnMount) { + Promise.resolve().then(() => { + apiRef.current.autosizeColumns(props.autosizeOptions); + }); + } + }); useGridNativeEventListener( apiRef, @@ -441,9 +683,18 @@ export const useGridColumnResize = ( { passive: doesSupportTouchActionNone() }, ); - useGridApiEventHandler(apiRef, 'columnSeparatorMouseDown', handleColumnResizeMouseDown); - useGridApiEventHandler(apiRef, 'columnResizeStart', handleResizeStart); + useGridApiMethod( + apiRef, + { + autosizeColumns, + }, + 'public', + ); + useGridApiEventHandler(apiRef, 'columnResizeStop', handleResizeStop); + useGridApiEventHandler(apiRef, 'columnResizeStart', handleResizeStart); + useGridApiEventHandler(apiRef, 'columnSeparatorMouseDown', handleColumnResizeMouseDown); + useGridApiEventHandler(apiRef, 'columnSeparatorDoubleClick', handleColumnSeparatorDoubleClick); useGridApiOptionHandler(apiRef, 'columnResize', props.onColumnResize); useGridApiOptionHandler(apiRef, 'columnWidthChange', props.onColumnWidthChange); diff --git a/packages/grid/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/grid/x-data-grid-pro/src/models/dataGridProProps.ts index 95301282803bb..d602feb7b44b5 100644 --- a/packages/grid/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/grid/x-data-grid-pro/src/models/dataGridProProps.ts @@ -25,6 +25,7 @@ import { import { GridInitialStatePro } from './gridStatePro'; import { GridProSlotsComponent, UncapitalizedGridProSlotsComponent } from './gridProSlotsComponent'; import type { GridProSlotProps } from './gridProSlotProps'; +import { GridAutosizeOptions } from '../hooks'; export interface GridExperimentalProFeatures extends GridExperimentalFeatures { /** @@ -101,6 +102,16 @@ export interface DataGridProPropsWithDefaultValue extends DataGridPropsWithDefau * @returns {boolean} A boolean indicating if the group is expanded. */ isGroupExpandedByDefault?: (node: GridGroupNode) => boolean; + /** + * If `true`, columns are autosized after the datagrid is mounted. + * @default false + */ + autosizeOnMount: boolean; + /** + * If `true`, column autosizing on header separator double-click is disabled. + * @default false + */ + disableAutosize: boolean; /** * If `true`, the column pinning is disabled. * @default false @@ -157,6 +168,10 @@ export interface DataGridProPropsWithoutDefaultValue; + /** + * The options for autosize when user-initiated. + */ + autosizeOptions?: GridAutosizeOptions; /** * The initial state of the DataGridPro. * The data in it will be set in the state on initialization but will not be controlled. diff --git a/packages/grid/x-data-grid-pro/src/models/gridApiPro.ts b/packages/grid/x-data-grid-pro/src/models/gridApiPro.ts index bd5c6dddeccfc..5b541d730f6b1 100644 --- a/packages/grid/x-data-grid-pro/src/models/gridApiPro.ts +++ b/packages/grid/x-data-grid-pro/src/models/gridApiPro.ts @@ -8,6 +8,7 @@ import { GridPrivateOnlyApiCommon } from '@mui/x-data-grid/internals'; import { GridInitialStatePro, GridStatePro } from './gridStatePro'; import type { GridColumnPinningApi, + GridColumnResizeApi, GridDetailPanelApi, GridRowPinningApi, GridDetailPanelPrivateApi, @@ -20,12 +21,12 @@ export interface GridApiPro extends GridApiCommon, GridRowProApi, GridColumnPinningApi, + GridColumnResizeApi, GridDetailPanelApi, GridRowPinningApi, // APIs that are private in Community plan, but public in Pro and Premium plans GridRowMultiSelectionApi, - GridColumnReorderApi, - GridRowProApi {} + GridColumnReorderApi {} export interface GridPrivateApiPro extends GridApiPro, diff --git a/packages/grid/x-data-grid-pro/src/models/index.ts b/packages/grid/x-data-grid-pro/src/models/index.ts index 58cfd0bf2f8eb..36deff5c944b6 100644 --- a/packages/grid/x-data-grid-pro/src/models/index.ts +++ b/packages/grid/x-data-grid-pro/src/models/index.ts @@ -1,3 +1,4 @@ +export * from './gridApiPro'; export * from './gridGroupingColDefOverride'; export * from './gridRowScrollEndParams'; export * from './gridRowOrderChangeParams'; diff --git a/packages/grid/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx b/packages/grid/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx index e56f110f2ff6a..2775ddf2af9ac 100644 --- a/packages/grid/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx +++ b/packages/grid/x-data-grid-pro/src/tests/columns.DataGridPro.test.tsx @@ -10,9 +10,10 @@ import { gridColumnLookupSelector, gridColumnFieldsSelector, GridApi, + GridAutosizeOptions, } from '@mui/x-data-grid-pro'; import { useGridPrivateApiContext } from '@mui/x-data-grid-pro/internals'; -import { getColumnHeaderCell, getCell } from 'test/utils/helperFn'; +import { getColumnHeaderCell, getCell, microtasks } from 'test/utils/helperFn'; const isJSDOM = /jsdom/.test(window.navigator.userAgent); @@ -410,6 +411,91 @@ describe(' - Columns', () => { }); }); + describe('autosizing', () => { + before(function beforeHook() { + if (isJSDOM) { + // Need layouting + this.skip(); + } + }); + + const rows = [ + { + id: 0, + brand: 'Nike', + }, + { + id: 1, + brand: 'Adidas', + }, + { + id: 2, + brand: 'Puma', + }, + { + id: 3, + brand: 'Lululemon Athletica', + }, + ]; + const columns = [ + { field: 'id', headerName: 'This is the ID column' }, + { field: 'brand', headerName: 'This is the brand column' }, + ]; + + const getWidths = () => { + return columns.map((_, i) => parseInt(getColumnHeaderCell(i).style.width, 10)); + }; + + it('should work through the API', async () => { + render(); + await apiRef.current.autosizeColumns(); + await microtasks(); + expect(getWidths()).to.deep.equal([155, 177]); + }); + + it('should work through double-clicking the separator', async () => { + render(); + const separator = document.querySelectorAll( + `.${gridClasses['columnSeparator--resizable']}`, + )[1]; + fireEvent.doubleClick(separator); + await microtasks(); + expect(getWidths()).to.deep.equal([100, 177]); + }); + + it('should work on mount', async () => { + render(); + await microtasks(); /* first effect after render */ + await microtasks(); /* async autosize operation */ + expect(getWidths()).to.deep.equal([155, 177]); + }); + + describe('options', () => { + const autosize = async (options: GridAutosizeOptions | undefined, widths: number[]) => { + render(); + await apiRef.current.autosizeColumns({ includeHeaders: false, ...options }); + await microtasks(); + expect(getWidths()).to.deep.equal(widths); + }; + + it('.columns works', async () => { + await autosize({ columns: [columns[0].field] }, [50, 100]); + }); + it('.includeHeaders works', async () => { + await autosize({ includeHeaders: true }, [155, 177]); + }); + it('.includeOutliers works', async () => { + await autosize({ includeOutliers: true }, [50, 145]); + }); + it('.outliersFactor works', async () => { + await autosize({ outliersFactor: 40 }, [50, 145]); + }); + it('.expand works', async () => { + await autosize({ expand: true }, [134, 149]); + }); + }); + }); + describe('column pipe processing', () => { type GridPrivateApiContextRef = ReturnType; it('should not loose column width when re-applying pipe processing', () => { diff --git a/packages/grid/x-data-grid-pro/src/utils/domUtils.ts b/packages/grid/x-data-grid-pro/src/utils/domUtils.ts index 697c10999dee0..02e1b5ca563f3 100644 --- a/packages/grid/x-data-grid-pro/src/utils/domUtils.ts +++ b/packages/grid/x-data-grid-pro/src/utils/domUtils.ts @@ -56,3 +56,18 @@ export function findGridCellElementsFromCol(col: HTMLElement, api: GridPrivateAp return cells; } + +export function findGridHeader(api: GridPrivateApiPro, field: string) { + const headers = api.columnHeadersContainerElementRef!.current!; + return headers.querySelector(`:scope > div > div > [data-field="${field}"][role="columnheader"]`); +} + +export function findGridCells(api: GridPrivateApiPro, field: string) { + const container = api.virtualScrollerRef!.current!; + const selectorFor = (role: string) => + `:scope > div > div > div > [data-field="${field}"][role="${role}"]`; + // TODO(v7): Keep only the selector for the correct role + return Array.from( + container.querySelectorAll(`${selectorFor('cell')}, ${selectorFor('gridcell')}`), + ); +} diff --git a/packages/grid/x-data-grid/src/DataGrid/useDataGridComponent.tsx b/packages/grid/x-data-grid/src/DataGrid/useDataGridComponent.tsx index 2e0db8b05d205..17d5b9e59327a 100644 --- a/packages/grid/x-data-grid/src/DataGrid/useDataGridComponent.tsx +++ b/packages/grid/x-data-grid/src/DataGrid/useDataGridComponent.tsx @@ -42,12 +42,16 @@ import { useGridColumnGrouping, columnGroupsStateInitializer, } from '../hooks/features/columnGrouping/useGridColumnGrouping'; +import { + useGridVirtualization, + virtualizationStateInitializer, +} from '../hooks/features/virtualization'; export const useDataGridComponent = ( inputApiRef: React.MutableRefObject | undefined, props: DataGridProcessedProps, ) => { - const privateApiRef = useGridInitialization( + const apiRef = useGridInitialization( inputApiRef, props, ); @@ -55,49 +59,51 @@ export const useDataGridComponent = ( /** * Register all pre-processors called during state initialization here. */ - useGridRowSelectionPreProcessors(privateApiRef, props); - useGridRowsPreProcessors(privateApiRef); + useGridRowSelectionPreProcessors(apiRef, props); + useGridRowsPreProcessors(apiRef); /** * Register all state initializers here. */ - useGridInitializeState(rowSelectionStateInitializer, privateApiRef, props); - useGridInitializeState(columnsStateInitializer, privateApiRef, props); - useGridInitializeState(rowsStateInitializer, privateApiRef, props); - useGridInitializeState(editingStateInitializer, privateApiRef, props); - useGridInitializeState(focusStateInitializer, privateApiRef, props); - useGridInitializeState(sortingStateInitializer, privateApiRef, props); - useGridInitializeState(preferencePanelStateInitializer, privateApiRef, props); - useGridInitializeState(filterStateInitializer, privateApiRef, props); - useGridInitializeState(densityStateInitializer, privateApiRef, props); - useGridInitializeState(paginationStateInitializer, privateApiRef, props); - useGridInitializeState(rowsMetaStateInitializer, privateApiRef, props); - useGridInitializeState(columnMenuStateInitializer, privateApiRef, props); - useGridInitializeState(columnGroupsStateInitializer, privateApiRef, props); + useGridInitializeState(rowSelectionStateInitializer, apiRef, props); + useGridInitializeState(columnsStateInitializer, apiRef, props); + useGridInitializeState(rowsStateInitializer, apiRef, props); + useGridInitializeState(editingStateInitializer, apiRef, props); + useGridInitializeState(focusStateInitializer, apiRef, props); + useGridInitializeState(sortingStateInitializer, apiRef, props); + useGridInitializeState(preferencePanelStateInitializer, apiRef, props); + useGridInitializeState(filterStateInitializer, apiRef, props); + useGridInitializeState(densityStateInitializer, apiRef, props); + useGridInitializeState(paginationStateInitializer, apiRef, props); + useGridInitializeState(rowsMetaStateInitializer, apiRef, props); + useGridInitializeState(columnMenuStateInitializer, apiRef, props); + useGridInitializeState(columnGroupsStateInitializer, apiRef, props); + useGridInitializeState(virtualizationStateInitializer, apiRef, props); - useGridKeyboardNavigation(privateApiRef, props); - useGridRowSelection(privateApiRef, props); - useGridColumns(privateApiRef, props); - useGridRows(privateApiRef, props); - useGridParamsApi(privateApiRef, props); - useGridColumnSpanning(privateApiRef); - useGridColumnGrouping(privateApiRef, props); - useGridEditing(privateApiRef, props); - useGridFocus(privateApiRef, props); - useGridPreferencesPanel(privateApiRef, props); - useGridFilter(privateApiRef, props); - useGridSorting(privateApiRef, props); - useGridDensity(privateApiRef, props); - useGridPagination(privateApiRef, props); - useGridRowsMeta(privateApiRef, props); - useGridScroll(privateApiRef, props); - useGridColumnMenu(privateApiRef); - useGridCsvExport(privateApiRef, props); - useGridPrintExport(privateApiRef, props); - useGridClipboard(privateApiRef, props); - useGridDimensions(privateApiRef, props); - useGridEvents(privateApiRef, props); - useGridStatePersistence(privateApiRef); + useGridKeyboardNavigation(apiRef, props); + useGridRowSelection(apiRef, props); + useGridColumns(apiRef, props); + useGridRows(apiRef, props); + useGridParamsApi(apiRef, props); + useGridColumnSpanning(apiRef); + useGridColumnGrouping(apiRef, props); + useGridEditing(apiRef, props); + useGridFocus(apiRef, props); + useGridPreferencesPanel(apiRef, props); + useGridFilter(apiRef, props); + useGridSorting(apiRef, props); + useGridDensity(apiRef, props); + useGridPagination(apiRef, props); + useGridRowsMeta(apiRef, props); + useGridScroll(apiRef, props); + useGridColumnMenu(apiRef); + useGridCsvExport(apiRef, props); + useGridPrintExport(apiRef, props); + useGridClipboard(apiRef, props); + useGridDimensions(apiRef, props); + useGridEvents(apiRef, props); + useGridStatePersistence(apiRef); + useGridVirtualization(apiRef, props); - return privateApiRef; + return apiRef; }; diff --git a/packages/grid/x-data-grid/src/components/DataGridVirtualScroller.tsx b/packages/grid/x-data-grid/src/components/DataGridVirtualScroller.tsx index 2589c5423c7e1..e10e75dd2724f 100644 --- a/packages/grid/x-data-grid/src/components/DataGridVirtualScroller.tsx +++ b/packages/grid/x-data-grid/src/components/DataGridVirtualScroller.tsx @@ -5,30 +5,26 @@ import { GridVirtualScrollerRenderZone } from './virtualization/GridVirtualScrol import { useGridVirtualScroller } from '../hooks/features/virtualization/useGridVirtualScroller'; import { GridOverlays } from './base/GridOverlays'; -interface DataGridVirtualScrollerProps extends React.HTMLAttributes { - disableVirtualization?: boolean; -} +const DataGridVirtualScroller = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(function DataGridVirtualScroller(props, ref) { + const { className, ...other } = props; -const DataGridVirtualScroller = React.forwardRef( - function DataGridVirtualScroller(props, ref) { - const { className, disableVirtualization, ...other } = props; + const { getRootProps, getContentProps, getRenderZoneProps, getRows } = useGridVirtualScroller({ + ref, + }); - const { getRootProps, getContentProps, getRenderZoneProps, getRows } = useGridVirtualScroller({ - ref, - disableVirtualization, - }); - - return ( - - - - - {getRows()} - - - - ); - }, -); + return ( + + + + + {getRows()} + + + + ); +}); export { DataGridVirtualScroller }; diff --git a/packages/grid/x-data-grid/src/components/base/GridBody.tsx b/packages/grid/x-data-grid/src/components/base/GridBody.tsx index 320501ca349ed..8c0ea5762311a 100644 --- a/packages/grid/x-data-grid/src/components/base/GridBody.tsx +++ b/packages/grid/x-data-grid/src/components/base/GridBody.tsx @@ -32,7 +32,6 @@ interface GridBodyProps { VirtualScrollerComponent: React.JSXElementConstructor< React.HTMLAttributes & { ref: React.Ref; - disableVirtualization: boolean; } >; } @@ -76,10 +75,6 @@ function GridBody(props: GridBodyProps) { cellTabIndexState === null ); - const [isVirtualizationDisabled, setIsVirtualizationDisabled] = React.useState( - rootProps.disableVirtualization, - ); - useEnhancedEffect(() => { apiRef.current.computeSizeAndPublishResizeEvent(); @@ -111,27 +106,6 @@ function GridBody(props: GridBodyProps) { }; }, [apiRef]); - const disableVirtualization = React.useCallback(() => { - setIsVirtualizationDisabled(true); - }, []); - - const enableVirtualization = React.useCallback(() => { - setIsVirtualizationDisabled(false); - }, []); - - React.useEffect(() => { - setIsVirtualizationDisabled(rootProps.disableVirtualization); - }, [rootProps.disableVirtualization]); - - // The `useGridApiMethod` hook can't be used here, because it only installs the - // method if it doesn't exist yet. Once installed, it's never updated again. - // This break the methods above, since their closure comes from the first time - // they were installed. Which means that calling `setIsVirtualizationDisabled` - // will trigger a re-render, but it won't update the state. That can be solved - // by migrating the virtualization status to the global state. - apiRef.current.unstable_disableVirtualization = disableVirtualization; - apiRef.current.unstable_enableVirtualization = enableVirtualization; - const columnHeadersRef = React.useRef(null); const columnsContainerRef = React.useRef(null); const virtualScrollerRef = React.useRef(null); @@ -174,7 +148,6 @@ function GridBody(props: GridBodyProps) { // If this event is published while dimensions haven't been computed, // the `onFetchRows` prop won't be called during mount. ref={virtualScrollerRef} - disableVirtualization={isVirtualizationDisabled} /> )} diff --git a/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx b/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx index ba7777efbed80..78592957c681d 100644 --- a/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx +++ b/packages/grid/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx @@ -152,7 +152,10 @@ function GridColumnHeaderItem(props: GridColumnHeaderItemProps) { ); const columnHeaderSeparatorProps = React.useMemo( - () => ({ onMouseDown: publish('columnSeparatorMouseDown') }), + () => ({ + onMouseDown: publish('columnSeparatorMouseDown'), + onDoubleClick: publish('columnSeparatorDoubleClick'), + }), [publish], ); diff --git a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx index e1f9470b15fe1..f10009980f68b 100644 --- a/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx +++ b/packages/grid/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -3,6 +3,7 @@ import * as ReactDOM from 'react-dom'; import { unstable_useForkRef as useForkRef } from '@mui/utils'; import { styled, useTheme } from '@mui/system'; import { defaultMemoize } from 'reselect'; +import { useGridSelector } from '../../utils'; import { useGridPrivateApiContext } from '../../utils/useGridPrivateApiContext'; import { useGridRootProps } from '../../utils/useGridRootProps'; import { GridRenderContext } from '../../../models/params/gridScrollParams'; @@ -15,6 +16,7 @@ import { areRenderContextsEqual, getRenderableIndexes, } from '../virtualization/useGridVirtualScroller'; +import { gridVirtualizationColumnEnabledSelector } from '../virtualization'; import { GridColumnGroupHeader } from '../../../components/columnHeaders/GridColumnGroupHeader'; import { GridColumnGroup } from '../../../models/gridColumnGrouping'; import { GridStateColDef } from '../../../models/colDef/gridColDef'; @@ -100,6 +102,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { const [resizeCol, setResizeCol] = React.useState(''); const apiRef = useGridPrivateApiContext(); + const hasVirtualization = useGridSelector(apiRef, gridVirtualizationColumnEnabledSelector); const rootProps = useGridRootProps(); const innerRef = React.useRef(null); @@ -243,7 +246,6 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { (params) => setDragCol(params.field), [], ); - const handleColumnReorderStop = React.useCallback>( () => setDragCol(''), [], @@ -276,20 +278,21 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { buffer: rootProps.rowBuffer, }); - const firstColumnToRender = getFirstColumnIndexToRenderRef.current({ - firstColumnIndex: nextRenderContext!.firstColumnIndex, - minColumnIndex: minFirstColumn, - columnBuffer: rootProps.columnBuffer, - apiRef, - firstRowToRender, - lastRowToRender, - visibleRows: currentPage.rows, - }); - - const lastColumnToRender = Math.min( - nextRenderContext.lastColumnIndex! + rootProps.columnBuffer, - maxLastColumn, - ); + const firstColumnToRender = !hasVirtualization + ? 0 + : getFirstColumnIndexToRenderRef.current({ + firstColumnIndex: nextRenderContext!.firstColumnIndex, + minColumnIndex: minFirstColumn, + columnBuffer: rootProps.columnBuffer, + apiRef, + firstRowToRender, + lastRowToRender, + visibleRows: currentPage.rows, + }); + + const lastColumnToRender = !hasVirtualization + ? maxLastColumn + : Math.min(nextRenderContext.lastColumnIndex! + rootProps.columnBuffer, maxLastColumn); const renderedColumns = visibleColumns.slice(firstColumnToRender, lastColumnToRender); diff --git a/packages/grid/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx b/packages/grid/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx index 87d108f92a674..b143d906338b3 100644 --- a/packages/grid/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx +++ b/packages/grid/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx @@ -281,7 +281,7 @@ export const useGridPrintExport = ( apiRef.current.setColumnVisibilityModel(previousColumnVisibility.current); } - apiRef.current.unstable_enableVirtualization(); + apiRef.current.unstable_setVirtualization(true); apiRef.current.setRows(previousRows.current); // Clear local state @@ -329,7 +329,7 @@ export const useGridPrintExport = ( updateGridRowsForPrint(options.getRowsToExport); } - apiRef.current.unstable_disableVirtualization(); + apiRef.current.unstable_setVirtualization(false); await raf(); // wait for the state changes to take action const printWindow = buildPrintWindow(options?.fileName); if (process.env.NODE_ENV === 'test') { diff --git a/packages/grid/x-data-grid/src/hooks/features/index.ts b/packages/grid/x-data-grid/src/hooks/features/index.ts index dc565a3269ebb..b5d62c7b80d5b 100644 --- a/packages/grid/x-data-grid/src/hooks/features/index.ts +++ b/packages/grid/x-data-grid/src/hooks/features/index.ts @@ -13,3 +13,4 @@ export * from './sorting'; export * from './dimensions'; export * from './statePersistence'; export * from './headerFiltering'; +export * from './virtualization'; diff --git a/packages/grid/x-data-grid/src/hooks/features/virtualization/gridVirtualizationSelectors.ts b/packages/grid/x-data-grid/src/hooks/features/virtualization/gridVirtualizationSelectors.ts new file mode 100644 index 0000000000000..4fed29c696b7f --- /dev/null +++ b/packages/grid/x-data-grid/src/hooks/features/virtualization/gridVirtualizationSelectors.ts @@ -0,0 +1,26 @@ +import { createSelector } from '../../../utils/createSelector'; +import { GridStateCommunity } from '../../../models/gridStateCommunity'; + +/** + * Get the columns state + * @category Virtualization + */ +export const gridVirtualizationSelector = (state: GridStateCommunity) => state.virtualization; + +/** + * Get the enabled state for virtualization + * @category Virtualization + */ +export const gridVirtualizationEnabledSelector = createSelector( + gridVirtualizationSelector, + (state) => state.enabled, +); + +/** + * Get the enabled state for virtualization + * @category Virtualization + */ +export const gridVirtualizationColumnEnabledSelector = createSelector( + gridVirtualizationSelector, + (state) => state.enabledForColumns, +); diff --git a/packages/grid/x-data-grid/src/hooks/features/virtualization/index.ts b/packages/grid/x-data-grid/src/hooks/features/virtualization/index.ts new file mode 100644 index 0000000000000..19098c42dfda5 --- /dev/null +++ b/packages/grid/x-data-grid/src/hooks/features/virtualization/index.ts @@ -0,0 +1,2 @@ +export * from './useGridVirtualization'; +export * from './gridVirtualizationSelectors'; diff --git a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx index dee0de795da83..145d7e2e64a4e 100644 --- a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx +++ b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx @@ -28,6 +28,10 @@ import { GridStateColDef } from '../../../models/colDef/gridColDef'; import { getFirstNonSpannedColumnToRender } from '../columns/gridColumnsUtils'; import { getMinimalContentHeight } from '../rows/gridRowsUtils'; import { GridRowProps } from '../../../components/GridRow'; +import { + gridVirtualizationEnabledSelector, + gridVirtualizationColumnEnabledSelector, +} from './gridVirtualizationSelectors'; // Uses binary search to avoid looping through all possible positions export function binarySearch( @@ -98,7 +102,6 @@ export const areRenderContextsEqual = ( interface UseGridVirtualScrollerProps { ref: React.Ref; - disableVirtualization?: boolean; renderZoneMinColumnIndex?: number; renderZoneMaxColumnIndex?: number; onRenderZonePositioning?: (params: { top: number; left: number }) => void; @@ -118,10 +121,11 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { const apiRef = useGridPrivateApiContext(); const rootProps = useGridRootProps(); const visibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); + const enabled = useGridSelector(apiRef, gridVirtualizationEnabledSelector); + const enabledForColumns = useGridSelector(apiRef, gridVirtualizationColumnEnabledSelector); const { ref, - disableVirtualization, onRenderZonePositioning, renderZoneMinColumnIndex = 0, renderZoneMaxColumnIndex = visibleColumns.length, @@ -139,7 +143,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { const renderZoneRef = React.useRef(null); const rootRef = React.useRef(null); const handleRef = useForkRef(ref, rootRef); - const [renderContext, setRenderContext] = React.useState(null); + const [renderContext, setRenderContextState] = React.useState(null); const prevRenderContext = React.useRef(renderContext); const scrollPosition = React.useRef({ top: 0, left: 0 }); const [containerDimensions, setContainerDimensions] = React.useState({ @@ -232,7 +236,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { ); const computeRenderContext = React.useCallback(() => { - if (disableVirtualization) { + if (!enabled) { return { firstRowIndex: 0, lastRowIndex: currentPage.rows.length, @@ -251,26 +255,32 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { ? firstRowIndex + currentPage.rows.length : getNearestIndexToRender(top + containerDimensions.height!); - let hasRowWithAutoHeight = false; let firstColumnIndex = 0; let lastColumnIndex = columnPositions.length; - const [firstRowToRender, lastRowToRender] = getRenderableIndexes({ - firstIndex: firstRowIndex, - lastIndex: lastRowIndex, - minFirstIndex: 0, - maxLastIndex: currentPage.rows.length, - buffer: rootProps.rowBuffer, - }); + if (enabledForColumns) { + let hasRowWithAutoHeight = false; - for (let i = firstRowToRender; i < lastRowToRender && !hasRowWithAutoHeight; i += 1) { - const row = currentPage.rows[i]; - hasRowWithAutoHeight = apiRef.current.rowHasAutoHeight(row.id); - } + const [firstRowToRender, lastRowToRender] = getRenderableIndexes({ + firstIndex: firstRowIndex, + lastIndex: lastRowIndex, + minFirstIndex: 0, + maxLastIndex: currentPage.rows.length, + buffer: rootProps.rowBuffer, + }); - if (!hasRowWithAutoHeight) { - firstColumnIndex = binarySearch(Math.abs(left), columnPositions); - lastColumnIndex = binarySearch(Math.abs(left) + containerDimensions.width!, columnPositions); + for (let i = firstRowToRender; i < lastRowToRender && !hasRowWithAutoHeight; i += 1) { + const row = currentPage.rows[i]; + hasRowWithAutoHeight = apiRef.current.rowHasAutoHeight(row.id); + } + + if (!hasRowWithAutoHeight) { + firstColumnIndex = binarySearch(Math.abs(left), columnPositions); + lastColumnIndex = binarySearch( + Math.abs(left) + containerDimensions.width!, + columnPositions, + ); + } } return { @@ -280,7 +290,8 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { lastColumnIndex, }; }, [ - disableVirtualization, + enabled, + enabledForColumns, getNearestIndexToRender, rowsMeta.positions.length, rootProps.autoHeight, @@ -293,14 +304,14 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { ]); useEnhancedEffect(() => { - if (disableVirtualization) { - renderZoneRef.current!.style.transform = `translate3d(0px, 0px, 0px)`; - } else { + if (enabled) { // TODO a scroll reset should not be necessary rootRef.current!.scrollLeft = 0; rootRef.current!.scrollTop = 0; + } else { + renderZoneRef.current!.style.transform = `translate3d(0px, 0px, 0px)`; } - }, [disableVirtualization]); + }, [enabled]); useEnhancedEffect(() => { setContainerDimensions({ @@ -367,7 +378,9 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { ], ); - const updateRenderContext = React.useCallback( + const getRenderContext = React.useCallback(() => prevRenderContext.current!, []); + + const setRenderContext = React.useCallback( (nextRenderContext: GridRenderContext) => { if ( prevRenderContext.current && @@ -376,7 +389,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { updateRenderZonePosition(nextRenderContext); return; } - setRenderContext(nextRenderContext); + setRenderContextState(nextRenderContext); updateRenderZonePosition(nextRenderContext); @@ -397,7 +410,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { }, [ apiRef, - setRenderContext, + setRenderContextState, prevRenderContext, currentPage.rows.length, rootProps.rowBuffer, @@ -411,12 +424,12 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { } const initialRenderContext = computeRenderContext(); - updateRenderContext(initialRenderContext); + setRenderContext(initialRenderContext); const { top, left } = scrollPosition.current!; const params = { top, left, renderContext: initialRenderContext }; apiRef.current.publishEvent('scrollPositionChange', params); - }, [apiRef, computeRenderContext, containerDimensions.width, updateRenderContext]); + }, [apiRef, computeRenderContext, containerDimensions.width, setRenderContext]); const handleScroll = useEventCallback((event: React.UIEvent) => { const { scrollTop, scrollLeft } = event.currentTarget; @@ -439,9 +452,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { } // When virtualization is disabled, the context never changes during scroll - const nextRenderContext = disableVirtualization - ? prevRenderContext.current - : computeRenderContext(); + const nextRenderContext = enabled ? computeRenderContext() : prevRenderContext.current; const topRowsScrolledSincePreviousRender = Math.abs( nextRenderContext.firstRowIndex - prevRenderContext.current.firstRowIndex, @@ -477,7 +488,7 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { if (shouldSetState) { // Prevents batching render context changes ReactDOM.flushSync(() => { - updateRenderContext(nextRenderContext); + setRenderContext(nextRenderContext); }); prevTotalWidth.current = columnsTotalWidth; } @@ -524,8 +535,8 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { return null; } - const rowBuffer = !disableVirtualization ? rootProps.rowBuffer : 0; - const columnBuffer = !disableVirtualization ? rootProps.columnBuffer : 0; + const rowBuffer = enabled ? rootProps.rowBuffer : 0; + const columnBuffer = enabled ? rootProps.columnBuffer : 0; const [firstRowToRender, lastRowToRender] = getRenderableIndexes({ firstIndex: nextRenderContext.firstRowIndex, @@ -769,11 +780,9 @@ export const useGridVirtualScroller = (props: UseGridVirtualScrollerProps) => { return style; }, [needsHorizontalScrollbar, rootProps.autoHeight]); - const getRenderContext = React.useCallback((): GridRenderContext => { - return prevRenderContext.current!; - }, []); - - apiRef.current.register('private', { getRenderContext }); + apiRef.current.register('private', { + getRenderContext, + }); return { renderContext, diff --git a/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx new file mode 100644 index 0000000000000..7634600ac1da8 --- /dev/null +++ b/packages/grid/x-data-grid/src/hooks/features/virtualization/useGridVirtualization.tsx @@ -0,0 +1,70 @@ +import * as React from 'react'; +import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; +import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; +import { useGridApiMethod } from '../../utils/useGridApiMethod'; +import { GridStateInitializer } from '../../utils/useGridInitializeState'; + +type RootProps = Pick; + +export type GridVirtualizationState = { + enabled: boolean; + enabledForColumns: boolean; +}; + +export const virtualizationStateInitializer: GridStateInitializer = (state, props) => { + const virtualization = { + enabled: !props.disableVirtualization, + enabledForColumns: true, + }; + + return { + ...state, + virtualization, + }; +}; + +export function useGridVirtualization( + apiRef: React.MutableRefObject, + props: RootProps, +): void { + /* + * API METHODS + */ + + const setVirtualization = (enabled: boolean) => { + apiRef.current.setState((state) => ({ + ...state, + virtualization: { + ...state.virtualization, + enabled, + }, + })); + }; + + const setColumnVirtualization = (enabled: boolean) => { + apiRef.current.setState((state) => ({ + ...state, + virtualization: { + ...state.virtualization, + enabledForColumns: enabled, + }, + })); + }; + + const api = { + unstable_setVirtualization: setVirtualization, + unstable_setColumnVirtualization: setColumnVirtualization, + }; + + useGridApiMethod(apiRef, api, 'public'); + + /* + * EFFECTS + */ + + /* eslint-disable react-hooks/exhaustive-deps */ + React.useEffect(() => { + setVirtualization(!props.disableVirtualization); + }, [props.disableVirtualization]); + /* eslint-enable react-hooks/exhaustive-deps */ +} diff --git a/packages/grid/x-data-grid/src/internals/index.ts b/packages/grid/x-data-grid/src/internals/index.ts index 40a9d112efdad..282be9d713862 100644 --- a/packages/grid/x-data-grid/src/internals/index.ts +++ b/packages/grid/x-data-grid/src/internals/index.ts @@ -39,6 +39,7 @@ export { export { useGridColumns, columnsStateInitializer } from '../hooks/features/columns/useGridColumns'; export { getTotalHeaderHeight } from '../hooks/features/columns/gridColumnsUtils'; export { useGridColumnSpanning } from '../hooks/features/columns/useGridColumnSpanning'; +export { gridColumnsStateSelector } from '../hooks/features/columns/gridColumnsSelector'; export { useGridColumnGrouping, columnGroupsStateInitializer, @@ -113,6 +114,7 @@ export { useGridVirtualScroller, getRenderableIndexes, } from '../hooks/features/virtualization/useGridVirtualScroller'; +export * from '../hooks/features/virtualization'; export { useTimeout } from '../hooks/utils/useTimeout'; export { useGridVisibleRows, getVisibleRows } from '../hooks/utils/useGridVisibleRows'; @@ -128,6 +130,7 @@ export type { } from '../models/props/DataGridProps'; export { getColumnsToExport, defaultGetRowsToExport } from '../hooks/features/export/utils'; +export * from '../utils/createControllablePromise'; export { createSelector, createSelectorMemoized, @@ -140,6 +143,7 @@ export { buildWarning } from '../utils/warning'; export { exportAs } from '../utils/exportAs'; export type { GridPrivateOnlyApiCommon } from '../models/api/gridApiCommon'; export { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContext'; +export * from '../hooks/utils/useOnMount'; export type { GridApiCommunity } from '../models/api/gridApiCommunity'; export type { GridApiCaches } from '../models/gridApiCaches'; diff --git a/packages/grid/x-data-grid/src/models/api/gridApiCommon.ts b/packages/grid/x-data-grid/src/models/api/gridApiCommon.ts index 17c98ff59b273..79745cb26e03f 100644 --- a/packages/grid/x-data-grid/src/models/api/gridApiCommon.ts +++ b/packages/grid/x-data-grid/src/models/api/gridApiCommon.ts @@ -10,7 +10,6 @@ import { GridLocaleTextApi } from './gridLocaleTextApi'; import type { GridParamsApi } from './gridParamsApi'; import { GridPreferencesPanelApi } from './gridPreferencesPanelApi'; import { GridPrintExportApi } from './gridPrintExportApi'; -import { GridDisableVirtualizationApi } from './gridDisableVirtualizationApi'; import { GridRowApi } from './gridRowApi'; import { GridRowsMetaApi, GridRowsMetaPrivateApi } from './gridRowsMetaApi'; import { GridRowSelectionApi } from './gridRowSelectionApi'; @@ -18,7 +17,7 @@ import { GridSortApi } from './gridSortApi'; import { GridStateApi, GridStatePrivateApi } from './gridStateApi'; import { GridLoggerApi } from './gridLoggerApi'; import { GridScrollApi } from './gridScrollApi'; -import { GridVirtualScrollerApi } from './gridVirtualScrollerApi'; +import { GridVirtualizationApi, GridVirtualizationPrivateApi } from './gridVirtualizationApi'; import type { GridPipeProcessingApi, GridPipeProcessingPrivateApi, @@ -56,7 +55,7 @@ export interface GridApiCommon< GridColumnMenuApi, GridPreferencesPanelApi, GridPrintExportApi, - GridDisableVirtualizationApi, + GridVirtualizationApi, GridLocaleTextApi, GridScrollApi, GridColumnSpanningApi, @@ -75,11 +74,11 @@ export interface GridPrivateOnlyApiCommon< GridColumnSpanningPrivateApi, GridRowsMetaPrivateApi, GridDimensionsPrivateApi, - GridVirtualScrollerApi, GridEditingPrivateApi, GridLoggerApi, GridFocusPrivateApi, - GridHeaderFilteringPrivateApi {} + GridHeaderFilteringPrivateApi, + GridVirtualizationPrivateApi {} export interface GridPrivateApiCommon extends GridApiCommon, diff --git a/packages/grid/x-data-grid/src/models/api/gridDisableVirtualizationApi.ts b/packages/grid/x-data-grid/src/models/api/gridDisableVirtualizationApi.ts deleted file mode 100644 index 1c31999395d87..0000000000000 --- a/packages/grid/x-data-grid/src/models/api/gridDisableVirtualizationApi.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * The API to disable the virtualization that is available in the grid [[apiRef]]. - */ -export interface GridDisableVirtualizationApi { - /** - * Disables grid's virtualization. - * @ignore - do not document. Remove before releasing v5 stable version. - */ - unstable_disableVirtualization: () => void; - /** - * Enables grid's virtualization. - * @ignore - do not document. Remove before releasing v5 stable version. - */ - unstable_enableVirtualization: () => void; -} diff --git a/packages/grid/x-data-grid/src/models/api/gridVirtualScrollerApi.ts b/packages/grid/x-data-grid/src/models/api/gridVirtualScrollerApi.ts deleted file mode 100644 index a91668ed1b3cb..0000000000000 --- a/packages/grid/x-data-grid/src/models/api/gridVirtualScrollerApi.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { GridRenderContext } from '../params/gridScrollParams'; - -export interface GridVirtualScrollerApi { - /** - * Get the current grid rendering context. - * @returns {GridRenderContext} The `GridRenderContext`. - */ - getRenderContext: () => GridRenderContext; -} diff --git a/packages/grid/x-data-grid/src/models/api/gridVirtualizationApi.ts b/packages/grid/x-data-grid/src/models/api/gridVirtualizationApi.ts new file mode 100644 index 0000000000000..6a5d787e72d39 --- /dev/null +++ b/packages/grid/x-data-grid/src/models/api/gridVirtualizationApi.ts @@ -0,0 +1,22 @@ +import { GridRenderContext } from '../params'; + +export interface GridVirtualizationApi { + /** + * Enable/disable virtualization. + * @param {boolean} enabled The enabled value for virtualization + */ + unstable_setVirtualization: (enabled: boolean) => void; + /** + * Enable/disable column virtualization. + * @param {boolean} enabled The enabled value for column virtualization + */ + unstable_setColumnVirtualization: (enabled: boolean) => void; +} + +export interface GridVirtualizationPrivateApi { + /** + * Get the current grid rendering context. + * @returns {GridRenderContext} The `GridRenderContext`. + */ + getRenderContext: () => GridRenderContext; +} diff --git a/packages/grid/x-data-grid/src/models/api/index.ts b/packages/grid/x-data-grid/src/models/api/index.ts index f84f56ee69a17..709885eea44ad 100644 --- a/packages/grid/x-data-grid/src/models/api/index.ts +++ b/packages/grid/x-data-grid/src/models/api/index.ts @@ -16,10 +16,9 @@ export * from './gridFilterApi'; export * from './gridColumnMenuApi'; export * from './gridPreferencesPanelApi'; export * from './gridPrintExportApi'; -export * from './gridDisableVirtualizationApi'; export * from './gridCallbackDetails'; export * from './gridScrollApi'; -export * from './gridVirtualScrollerApi'; +export * from './gridVirtualizationApi'; export type { GridApiCommon } from './gridApiCommon'; export type { GridEditingApi, GridCellModesModel, GridRowModesModel } from './gridEditingApi'; diff --git a/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts b/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts index 294cba7f29c59..919ca4847bcfc 100644 --- a/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts +++ b/packages/grid/x-data-grid/src/models/events/gridEventLookup.ts @@ -175,6 +175,14 @@ export interface GridColumnHeaderEventLookup { params: GridColumnHeaderParams; event: React.DragEvent; }; + /** + * Fired when a `dblclick` DOM event happens in the column header separator. + * @ignore - do not document. + */ + columnSeparatorDoubleClick: { + params: GridColumnHeaderParams; + event: React.MouseEvent; + }; /** * Fired when a `mousedown` DOM event happens in the column header separator. * @ignore - do not document. diff --git a/packages/grid/x-data-grid/src/models/gridStateCommunity.ts b/packages/grid/x-data-grid/src/models/gridStateCommunity.ts index ac5bf733807a1..a0ee71bdf0c2b 100644 --- a/packages/grid/x-data-grid/src/models/gridStateCommunity.ts +++ b/packages/grid/x-data-grid/src/models/gridStateCommunity.ts @@ -15,6 +15,7 @@ import type { GridSortingInitialState, GridSortingState, GridTabIndexState, + GridVirtualizationState, } from '../hooks'; import type { GridRowsMetaState } from '../hooks/features/rows/gridRowsMetaState'; import type { GridEditingState } from './gridEditRowModel'; @@ -42,6 +43,7 @@ export interface GridStateCommunity { filter: GridFilterState; preferencePanel: GridPreferencePanelState; density: GridDensityState; + virtualization: GridVirtualizationState; } /** diff --git a/packages/grid/x-data-grid/src/utils/createControllablePromise.ts b/packages/grid/x-data-grid/src/utils/createControllablePromise.ts new file mode 100644 index 0000000000000..4582b40872fec --- /dev/null +++ b/packages/grid/x-data-grid/src/utils/createControllablePromise.ts @@ -0,0 +1,16 @@ +export type ControllablePromise = Promise & { + resolve: T extends unknown ? (value?: T) => void : (value: T) => void; + reject: (reason?: any) => void; +}; + +export function createControllablePromise() { + let resolve: ControllablePromise['resolve']; + let reject: ControllablePromise['reject']; + const promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }) as ControllablePromise; + promise.resolve = resolve!; + promise.reject = reject!; + return promise; +} diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 577959c886326..0846622cb10f0 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -31,6 +31,7 @@ { "name": "DataGridPremiumProps", "kind": "Interface" }, { "name": "DataGridPro", "kind": "Function" }, { "name": "deDE", "kind": "Variable" }, + { "name": "DEFAULT_GRID_AUTOSIZE_OPTIONS", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, { "name": "ElementSize", "kind": "Interface" }, { "name": "elGR", "kind": "Variable" }, @@ -107,10 +108,12 @@ { "name": "GridApi", "kind": "TypeAlias" }, { "name": "GridApiCommon", "kind": "Interface" }, { "name": "GridApiContext", "kind": "Variable" }, + { "name": "GridApiPro", "kind": "Interface" }, { "name": "GridArrowDownwardIcon", "kind": "Variable" }, { "name": "GridArrowUpwardIcon", "kind": "Variable" }, { "name": "GridAutoGeneratedGroupNode", "kind": "Interface" }, { "name": "GridAutoGeneratedPinnedRowNode", "kind": "Interface" }, + { "name": "GridAutosizeOptions", "kind": "TypeAlias" }, { "name": "GridBasicGroupNode", "kind": "Interface" }, { "name": "GridBody", "kind": "Function" }, { "name": "GridBooleanCell", "kind": "Variable" }, @@ -209,6 +212,7 @@ { "name": "gridColumnReorderDragColSelector", "kind": "Variable" }, { "name": "gridColumnReorderSelector", "kind": "Variable" }, { "name": "GridColumnReorderState", "kind": "Interface" }, + { "name": "GridColumnResizeApi", "kind": "Interface" }, { "name": "GridColumnResizeParams", "kind": "Interface" }, { "name": "gridColumnResizeSelector", "kind": "Variable" }, { "name": "GridColumnResizeState", "kind": "Interface" }, @@ -257,7 +261,6 @@ { "name": "GridDetailPanelToggleCell", "kind": "Function" }, { "name": "GridDimensions", "kind": "Interface" }, { "name": "GridDimensionsApi", "kind": "Interface" }, - { "name": "GridDisableVirtualizationApi", "kind": "Interface" }, { "name": "GridDragIcon", "kind": "Variable" }, { "name": "GridEditBooleanCell", "kind": "Function" }, { "name": "GridEditBooleanCellProps", "kind": "Interface" }, @@ -430,6 +433,7 @@ { "name": "GridPrintExportMenuItemProps", "kind": "TypeAlias" }, { "name": "GridPrintExportOptions", "kind": "Interface" }, { "name": "GridPrintGetRowsToExportParams", "kind": "Interface" }, + { "name": "GridPrivateApiPro", "kind": "Interface" }, { "name": "GridProIconSlotsComponent", "kind": "Interface" }, { "name": "GridProSlotsComponent", "kind": "Interface" }, { "name": "GridPushPinLeftIcon", "kind": "Variable" }, @@ -571,7 +575,12 @@ { "name": "GridViewColumnIcon", "kind": "Variable" }, { "name": "GridViewHeadlineIcon", "kind": "Variable" }, { "name": "GridViewStreamIcon", "kind": "Variable" }, - { "name": "GridVirtualScrollerApi", "kind": "Interface" }, + { "name": "GridVirtualizationApi", "kind": "Interface" }, + { "name": "gridVirtualizationColumnEnabledSelector", "kind": "Variable" }, + { "name": "gridVirtualizationEnabledSelector", "kind": "Variable" }, + { "name": "GridVirtualizationPrivateApi", "kind": "Interface" }, + { "name": "gridVirtualizationSelector", "kind": "Variable" }, + { "name": "GridVirtualizationState", "kind": "TypeAlias" }, { "name": "GridVisibilityOffIcon", "kind": "Variable" }, { "name": "gridVisibleColumnDefinitionsSelector", "kind": "Variable" }, { "name": "gridVisibleColumnFieldsSelector", "kind": "Variable" }, @@ -641,8 +650,10 @@ { "name": "useGridNativeEventListener", "kind": "Variable" }, { "name": "useGridRootProps", "kind": "Variable" }, { "name": "useGridSelector", "kind": "Variable" }, + { "name": "useGridVirtualization", "kind": "Function" }, { "name": "useKeepGroupedColumnsHidden", "kind": "Variable" }, { "name": "ValueOptions", "kind": "TypeAlias" }, + { "name": "virtualizationStateInitializer", "kind": "Variable" }, { "name": "viVN", "kind": "Variable" }, { "name": "zhCN", "kind": "Variable" }, { "name": "zhTW", "kind": "Variable" } diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 819d55f8450c1..6457182e64df7 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -30,6 +30,7 @@ { "name": "DataGridPro", "kind": "Variable" }, { "name": "DataGridProProps", "kind": "Interface" }, { "name": "deDE", "kind": "Variable" }, + { "name": "DEFAULT_GRID_AUTOSIZE_OPTIONS", "kind": "Variable" }, { "name": "DEFAULT_GRID_COL_TYPE_KEY", "kind": "Variable" }, { "name": "ElementSize", "kind": "Interface" }, { "name": "elGR", "kind": "Variable" }, @@ -85,10 +86,12 @@ { "name": "GridApi", "kind": "TypeAlias" }, { "name": "GridApiCommon", "kind": "Interface" }, { "name": "GridApiContext", "kind": "Variable" }, + { "name": "GridApiPro", "kind": "Interface" }, { "name": "GridArrowDownwardIcon", "kind": "Variable" }, { "name": "GridArrowUpwardIcon", "kind": "Variable" }, { "name": "GridAutoGeneratedGroupNode", "kind": "Interface" }, { "name": "GridAutoGeneratedPinnedRowNode", "kind": "Interface" }, + { "name": "GridAutosizeOptions", "kind": "TypeAlias" }, { "name": "GridBasicGroupNode", "kind": "Interface" }, { "name": "GridBody", "kind": "Function" }, { "name": "GridBooleanCell", "kind": "Variable" }, @@ -183,6 +186,7 @@ { "name": "gridColumnReorderDragColSelector", "kind": "Variable" }, { "name": "gridColumnReorderSelector", "kind": "Variable" }, { "name": "GridColumnReorderState", "kind": "Interface" }, + { "name": "GridColumnResizeApi", "kind": "Interface" }, { "name": "GridColumnResizeParams", "kind": "Interface" }, { "name": "gridColumnResizeSelector", "kind": "Variable" }, { "name": "GridColumnResizeState", "kind": "Interface" }, @@ -231,7 +235,6 @@ { "name": "GridDetailPanelToggleCell", "kind": "Function" }, { "name": "GridDimensions", "kind": "Interface" }, { "name": "GridDimensionsApi", "kind": "Interface" }, - { "name": "GridDisableVirtualizationApi", "kind": "Interface" }, { "name": "GridDragIcon", "kind": "Variable" }, { "name": "GridEditBooleanCell", "kind": "Function" }, { "name": "GridEditBooleanCellProps", "kind": "Interface" }, @@ -392,6 +395,7 @@ { "name": "GridPrintExportMenuItemProps", "kind": "TypeAlias" }, { "name": "GridPrintExportOptions", "kind": "Interface" }, { "name": "GridPrintGetRowsToExportParams", "kind": "Interface" }, + { "name": "GridPrivateApiPro", "kind": "Interface" }, { "name": "GridProIconSlotsComponent", "kind": "Interface" }, { "name": "GridProSlotsComponent", "kind": "Interface" }, { "name": "GridPushPinLeftIcon", "kind": "Variable" }, @@ -526,7 +530,12 @@ { "name": "GridViewColumnIcon", "kind": "Variable" }, { "name": "GridViewHeadlineIcon", "kind": "Variable" }, { "name": "GridViewStreamIcon", "kind": "Variable" }, - { "name": "GridVirtualScrollerApi", "kind": "Interface" }, + { "name": "GridVirtualizationApi", "kind": "Interface" }, + { "name": "gridVirtualizationColumnEnabledSelector", "kind": "Variable" }, + { "name": "gridVirtualizationEnabledSelector", "kind": "Variable" }, + { "name": "GridVirtualizationPrivateApi", "kind": "Interface" }, + { "name": "gridVirtualizationSelector", "kind": "Variable" }, + { "name": "GridVirtualizationState", "kind": "TypeAlias" }, { "name": "GridVisibilityOffIcon", "kind": "Variable" }, { "name": "gridVisibleColumnDefinitionsSelector", "kind": "Variable" }, { "name": "gridVisibleColumnFieldsSelector", "kind": "Variable" }, @@ -592,7 +601,9 @@ { "name": "useGridNativeEventListener", "kind": "Variable" }, { "name": "useGridRootProps", "kind": "Variable" }, { "name": "useGridSelector", "kind": "Variable" }, + { "name": "useGridVirtualization", "kind": "Function" }, { "name": "ValueOptions", "kind": "TypeAlias" }, + { "name": "virtualizationStateInitializer", "kind": "Variable" }, { "name": "viVN", "kind": "Variable" }, { "name": "zhCN", "kind": "Variable" }, { "name": "zhTW", "kind": "Variable" } diff --git a/scripts/x-data-grid.exports.json b/scripts/x-data-grid.exports.json index 32e6c5ec47f07..926ef92abb13a 100644 --- a/scripts/x-data-grid.exports.json +++ b/scripts/x-data-grid.exports.json @@ -207,7 +207,6 @@ { "name": "gridDensityValueSelector", "kind": "Variable" }, { "name": "GridDimensions", "kind": "Interface" }, { "name": "GridDimensionsApi", "kind": "Interface" }, - { "name": "GridDisableVirtualizationApi", "kind": "Interface" }, { "name": "GridDragIcon", "kind": "Variable" }, { "name": "GridEditBooleanCell", "kind": "Function" }, { "name": "GridEditBooleanCellProps", "kind": "Interface" }, @@ -481,7 +480,12 @@ { "name": "GridViewColumnIcon", "kind": "Variable" }, { "name": "GridViewHeadlineIcon", "kind": "Variable" }, { "name": "GridViewStreamIcon", "kind": "Variable" }, - { "name": "GridVirtualScrollerApi", "kind": "Interface" }, + { "name": "GridVirtualizationApi", "kind": "Interface" }, + { "name": "gridVirtualizationColumnEnabledSelector", "kind": "Variable" }, + { "name": "gridVirtualizationEnabledSelector", "kind": "Variable" }, + { "name": "GridVirtualizationPrivateApi", "kind": "Interface" }, + { "name": "gridVirtualizationSelector", "kind": "Variable" }, + { "name": "GridVirtualizationState", "kind": "TypeAlias" }, { "name": "GridVisibilityOffIcon", "kind": "Variable" }, { "name": "gridVisibleColumnDefinitionsSelector", "kind": "Variable" }, { "name": "gridVisibleColumnFieldsSelector", "kind": "Variable" }, @@ -545,7 +549,9 @@ { "name": "useGridNativeEventListener", "kind": "Variable" }, { "name": "useGridRootProps", "kind": "Variable" }, { "name": "useGridSelector", "kind": "Variable" }, + { "name": "useGridVirtualization", "kind": "Function" }, { "name": "ValueOptions", "kind": "TypeAlias" }, + { "name": "virtualizationStateInitializer", "kind": "Variable" }, { "name": "viVN", "kind": "Variable" }, { "name": "zhCN", "kind": "Variable" }, { "name": "zhTW", "kind": "Variable" } diff --git a/test/utils/helperFn.ts b/test/utils/helperFn.ts index 7216af55c17b0..c0b808493e253 100644 --- a/test/utils/helperFn.ts +++ b/test/utils/helperFn.ts @@ -12,7 +12,7 @@ export function sleep(duration: number): Promise { } export function microtasks() { - return act(() => Promise.resolve()); + return act(() => Promise.resolve()) as unknown as Promise; } export function spyApi(api: GridApiCommon, methodName: string) {