diff --git a/docs/data/data-grid/events/events.json b/docs/data/data-grid/events/events.json index fedd745dd5c45..26bc1b73ed3e6 100644 --- a/docs/data/data-grid/events/events.json +++ b/docs/data/data-grid/events/events.json @@ -306,6 +306,13 @@ "params": "ElementSize", "event": "MuiEvent<{}>" }, + { + "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], + "name": "rootMount", + "description": "Fired when rootElementRef.current becomes available.", + "params": "HTMLElement", + "event": "MuiEvent<{}>" + }, { "projects": ["x-data-grid", "x-data-grid-pro", "x-data-grid-premium"], "name": "rowClick", diff --git a/docs/data/data-grid/filtering-recipes/FilteredRowCount.js b/docs/data/data-grid/filtering-recipes/FilteredRowCount.js index ddfcfdee406c8..7d633e0838567 100644 --- a/docs/data/data-grid/filtering-recipes/FilteredRowCount.js +++ b/docs/data/data-grid/filtering-recipes/FilteredRowCount.js @@ -45,15 +45,14 @@ export default function FilteredRowCount() { const getFilteredRowsCount = React.useCallback( (filterModel) => { + const rowIds = apiRef.current?.getAllRowIds(); const filterState = apiRef.current?.getFilterState(filterModel); - if (!filterState) { + if (!rowIds || !filterState) { return 0; } const { filteredRowsLookup } = filterState; - return Object.keys(filteredRowsLookup).filter( - (rowId) => filteredRowsLookup[rowId] === true, - ).length; + return rowIds.filter((rowId) => filteredRowsLookup[rowId] !== false).length; }, [apiRef], ); diff --git a/docs/data/data-grid/filtering-recipes/FilteredRowCount.tsx b/docs/data/data-grid/filtering-recipes/FilteredRowCount.tsx index 156860ec70fbe..9e80a5c1af8c6 100644 --- a/docs/data/data-grid/filtering-recipes/FilteredRowCount.tsx +++ b/docs/data/data-grid/filtering-recipes/FilteredRowCount.tsx @@ -45,15 +45,14 @@ export default function FilteredRowCount() { const getFilteredRowsCount = React.useCallback( (filterModel: GridFilterModel) => { + const rowIds = apiRef.current?.getAllRowIds(); const filterState = apiRef.current?.getFilterState(filterModel); - if (!filterState) { + if (!rowIds || !filterState) { return 0; } const { filteredRowsLookup } = filterState; - return Object.keys(filteredRowsLookup).filter( - (rowId) => filteredRowsLookup[rowId] === true, - ).length; + return rowIds.filter((rowId) => filteredRowsLookup[rowId] !== false).length; }, [apiRef], ); diff --git a/docs/data/migration/migration-data-grid-v7/migration-data-grid-v7.md b/docs/data/migration/migration-data-grid-v7/migration-data-grid-v7.md index 47f7db8eb4acf..bd7e499f4701a 100644 --- a/docs/data/migration/migration-data-grid-v7/migration-data-grid-v7.md +++ b/docs/data/migration/migration-data-grid-v7/migration-data-grid-v7.md @@ -106,6 +106,23 @@ Below are described the steps you need to make to migrate from v7 to v8. +const output = useGridSelector(apiRef, selector, arguments, equals); ``` +- The `filteredRowsLookup` object of the filter state does not contain `true` values anymore. If the row is filtered out, the value is `false`. Otherwise, the row id is not present in the object. + This change only impacts you if you relied on `filteredRowsLookup` to get ids of filtered rows. In this case,use `gridDataRowIdsSelector` selector to get row ids and check `filteredRowsLookup` for `false` values: + + ```diff + const filteredRowsLookup = gridFilteredRowsLookupSelector(apiRef); + -const filteredRowIds = Object.keys(filteredRowsLookup).filter((rowId) => filteredRowsLookup[rowId] === true); + +const rowIds = gridDataRowIdsSelector(apiRef); + +const filteredRowIds = rowIds.filter((rowId) => filteredRowsLookup[rowId] !== false); + ``` + +- The `visibleRowsLookup` state does not contain `true` values anymore. If the row is not visible, the value is `false`. Otherwise, the row id is not present in the object: + ```diff + const visibleRowsLookup = gridVisibleRowsLookupSelector(apiRef); + -const isRowVisible = visibleRowsLookup[rowId] === true; + +const isRowVisible = visibleRowsLookup[rowId] !== false; + ``` + ### Other exports - `ariaV8` experimental flag is removed. It's now the default behavior. diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 5fa2f15565440..c470e57eebff7 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -29,6 +29,7 @@ const configuration = { }, }; const releaseInfo = getReleaseInfo(); +const watermark = ; let dataGridPremiumPropValidators: PropValidator[]; @@ -47,6 +48,7 @@ const DataGridPremiumRaw = forwardRef(function DataGridPremium - + {watermark} ); diff --git a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx index c11e362499975..0c3780fe00484 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/useDataGridPremiumComponent.tsx @@ -122,7 +122,6 @@ export const useDataGridPremiumComponent = ( /** * Register all state initializers here. */ - useGridInitializeState(dimensionsStateInitializer, apiRef, props); useGridInitializeState(headerFilteringStateInitializer, apiRef, props); useGridInitializeState(rowGroupingStateInitializer, apiRef, props); useGridInitializeState(aggregationStateInitializer, apiRef, props); @@ -143,11 +142,12 @@ export const useDataGridPremiumComponent = ( useGridInitializeState(densityStateInitializer, apiRef, props); useGridInitializeState(columnReorderStateInitializer, apiRef, props); useGridInitializeState(columnResizeStateInitializer, apiRef, props); - useGridInitializeState(rowsMetaStateInitializer, apiRef, props); useGridInitializeState(columnMenuStateInitializer, apiRef, props); useGridInitializeState(columnGroupsStateInitializer, apiRef, props); useGridInitializeState(virtualizationStateInitializer, apiRef, props); useGridInitializeState(dataSourceStateInitializer, apiRef, props); + useGridInitializeState(dimensionsStateInitializer, apiRef, props); + useGridInitializeState(rowsMetaStateInitializer, apiRef, props); useGridInitializeState(listViewStateInitializer, apiRef, props); useGridRowGrouping(apiRef, props); diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts index 13b735821ae17..f387e249b33be 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregation.ts @@ -69,7 +69,6 @@ export const useGridAggregation = ( const currentModel = gridAggregationModelSelector(apiRef); if (currentModel !== model) { apiRef.current.setState(mergeStateWithAggregationModel(model)); - apiRef.current.forceUpdate(); } }, [apiRef], @@ -147,7 +146,6 @@ export const useGridAggregation = ( // Re-apply the column hydration to wrap / unwrap the aggregated columns if (!areAggregationRulesEqual(rulesOnLastColumnHydration, aggregationRules)) { - apiRef.current.caches.aggregation.rulesOnLastColumnHydration = aggregationRules; apiRef.current.requestPipeProcessorsApplication('hydrateColumns'); } }, [ diff --git a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx index 5681f34bf113d..57fc19a1ee4ba 100644 --- a/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx +++ b/packages/x-data-grid-premium/src/hooks/features/aggregation/useGridAggregationPreProcessors.tsx @@ -73,7 +73,6 @@ export const useGridAggregationPreProcessors = ( }); rulesOnLastColumnHydration.current = aggregationRules; - apiRef.current.caches.aggregation.rulesOnLastColumnHydration = aggregationRules; return columnsState; diff --git a/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts b/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts index a81e42578523f..b131c184ac0aa 100644 --- a/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts +++ b/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts @@ -10,7 +10,6 @@ import { isNavigationKey, serializeCellValue, useGridRegisterPipeProcessor, - useGridVisibleRows, } from '@mui/x-data-grid-pro/internals'; import { useGridApiEventHandler, @@ -26,7 +25,6 @@ import { gridFocusCellSelector, GridCellParams, GRID_REORDER_COL_DEF, - useGridSelector, gridSortedRowIdsSelector, gridDimensionsSelector, } from '@mui/x-data-grid-pro'; @@ -64,13 +62,10 @@ export const useGridCellSelection = ( >, ) => { const hasRootReference = apiRef.current.rootElementRef.current !== null; - const visibleRows = useGridVisibleRows(apiRef, props); const cellWithVirtualFocus = React.useRef(null); const lastMouseDownCell = React.useRef(null); const mousePosition = React.useRef<{ x: number; y: number }>(null); const autoScrollRAF = React.useRef(null); - const sortedRowIds = useGridSelector(apiRef, gridSortedRowIdsSelector); - const dimensions = useGridSelector(apiRef, gridDimensionsSelector); const totalHeaderHeight = getTotalHeaderHeight(apiRef, props); const ignoreValueFormatterProp = props.ignoreValueFormatterDuringExport; @@ -145,6 +140,7 @@ export const useGridCellSelection = ( } const visibleColumns = apiRef.current.getVisibleColumns(); + const visibleRows = getVisibleRows(apiRef); const rowsInRange = visibleRows.rows.slice(finalStartRowIndex, finalEndRowIndex + 1); const columnsInRange = visibleColumns.slice(finalStartColumnIndex, finalEndColumnIndex + 1); @@ -161,7 +157,7 @@ export const useGridCellSelection = ( apiRef.current.setCellSelectionModel(newModel); }, - [apiRef, visibleRows.rows], + [apiRef], ); const getSelectedCellsAsArray = React.useCallback< @@ -292,6 +288,8 @@ export const useGridCellSelection = ( return; } + const dimensions = gridDimensionsSelector(apiRef.current.state); + const { x: mouseX, y: mouseY } = mousePosition.current; const { width, height: viewportOuterHeight } = dimensions.viewportOuterSize; const height = viewportOuterHeight - totalHeaderHeight; @@ -331,7 +329,7 @@ export const useGridCellSelection = ( } autoScroll(); - }, [apiRef, dimensions, totalHeaderHeight]); + }, [apiRef, totalHeaderHeight]); const handleCellMouseOver = React.useCallback>( (params, event) => { @@ -354,6 +352,7 @@ export const useGridCellSelection = ( return; } + const dimensions = gridDimensionsSelector(apiRef.current.state); const { x, y } = virtualScrollerRect; const { width, height: viewportOuterHeight } = dimensions.viewportOuterSize; const height = viewportOuterHeight - totalHeaderHeight; @@ -378,7 +377,7 @@ export const useGridCellSelection = ( stopAutoScroll(); } }, - [apiRef, startAutoScroll, stopAutoScroll, totalHeaderHeight, dimensions], + [apiRef, startAutoScroll, stopAutoScroll, totalHeaderHeight], ); const handleCellClick = useEventCallback< @@ -438,6 +437,7 @@ export const useGridCellSelection = ( endColumnIndex -= 1; } + const visibleRows = getVisibleRows(apiRef); if (endRowIndex < 0 || endRowIndex >= visibleRows.rows.length) { return; } @@ -490,6 +490,7 @@ export const useGridCellSelection = ( const addClassesToCells = React.useCallback>( (classes, { id, field }) => { + const visibleRows = getVisibleRows(apiRef); if (!visibleRows.range || !apiRef.current.isCellSelected(id, field)) { return classes; } @@ -538,7 +539,7 @@ export const useGridCellSelection = ( return newClasses; }, - [apiRef, visibleRows.range, visibleRows.rows], + [apiRef], ); const canUpdateFocus = React.useCallback>( @@ -566,6 +567,7 @@ export const useGridCellSelection = ( if (apiRef.current.getSelectedCellsAsArray().length <= 1) { return value; } + const sortedRowIds = gridSortedRowIdsSelector(apiRef.current.state); const cellSelectionModel = apiRef.current.getCellSelectionModel(); const unsortedSelectedRowIds = Object.keys(cellSelectionModel); const sortedSelectedRowIds = sortedRowIds.filter((id) => @@ -594,7 +596,7 @@ export const useGridCellSelection = ( }, ''); return copyData; }, - [apiRef, ignoreValueFormatter, clipboardCopyCellDelimiter, sortedRowIds], + [apiRef, ignoreValueFormatter, clipboardCopyCellDelimiter], ); useGridRegisterPipeProcessor(apiRef, 'isCellSelected', checkIfCellIsSelected); diff --git a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts index 7e1c99b5f4e2f..846aba5dcfa53 100644 --- a/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts +++ b/packages/x-data-grid-premium/src/hooks/features/rowGrouping/gridRowGroupingUtils.ts @@ -1,6 +1,5 @@ import { RefObject } from '@mui/x-internals/types'; import { - GridRowId, GridRowTreeConfig, GridFilterState, GridFilterModel, @@ -80,9 +79,9 @@ export const filterRowTreeFromGroupingColumns = ( params: FilterRowTreeFromTreeDataParams, ): Omit => { const { apiRef, rowTree, isRowMatchingFilters, filterModel } = params; - const filteredRowsLookup: Record = {}; - const filteredChildrenCountLookup: Record = {}; - const filteredDescendantCountLookup: Record = {}; + const filteredRowsLookup: GridFilterState['filteredRowsLookup'] = {}; + const filteredChildrenCountLookup: GridFilterState['filteredChildrenCountLookup'] = {}; + const filteredDescendantCountLookup: GridFilterState['filteredDescendantCountLookup'] = {}; const filterCache = {}; const filterTreeNode = ( @@ -142,7 +141,9 @@ export const filterRowTreeFromGroupingColumns = ( } } - filteredRowsLookup[node.id] = isPassingFiltering; + if (!isPassingFiltering) { + filteredRowsLookup[node.id] = false; + } if (!isPassingFiltering) { return 0; diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 37d8c05460ed9..b5e5db188f596 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -22,6 +22,7 @@ const configuration = { }, }; const releaseInfo = getReleaseInfo(); +const watermark = ; const DataGridProRaw = forwardRef(function DataGridPro( inProps: DataGridProProps, @@ -34,6 +35,7 @@ const DataGridProRaw = forwardRef(function DataGridPro - + {watermark} ); diff --git a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx index 27b1a5bed4d07..929cbbfeec89b 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/useDataGridProComponent.tsx @@ -110,7 +110,6 @@ export const useDataGridProComponent = ( /** * Register all state initializers here. */ - useGridInitializeState(dimensionsStateInitializer, apiRef, props); useGridInitializeState(headerFilteringStateInitializer, apiRef, props); useGridInitializeState(rowSelectionStateInitializer, apiRef, props); useGridInitializeState(detailPanelStateInitializer, apiRef, props); @@ -128,11 +127,12 @@ export const useDataGridProComponent = ( useGridInitializeState(densityStateInitializer, apiRef, props); useGridInitializeState(columnReorderStateInitializer, apiRef, props); useGridInitializeState(columnResizeStateInitializer, apiRef, props); - useGridInitializeState(rowsMetaStateInitializer, apiRef, props); useGridInitializeState(columnMenuStateInitializer, apiRef, props); useGridInitializeState(columnGroupsStateInitializer, apiRef, props); useGridInitializeState(virtualizationStateInitializer, apiRef, props); useGridInitializeState(dataSourceStateInitializer, apiRef, props); + useGridInitializeState(dimensionsStateInitializer, apiRef, props); + useGridInitializeState(rowsMetaStateInitializer, apiRef, props); useGridInitializeState(listViewStateInitializer, apiRef, props); useGridHeaderFiltering(apiRef, props); diff --git a/packages/x-data-grid-pro/src/components/GridPinnedRows.tsx b/packages/x-data-grid-pro/src/components/GridPinnedRows.tsx index 04dfb4ccd17c5..7a7451f6520f9 100644 --- a/packages/x-data-grid-pro/src/components/GridPinnedRows.tsx +++ b/packages/x-data-grid-pro/src/components/GridPinnedRows.tsx @@ -5,7 +5,6 @@ import { getDataGridUtilityClass, gridClasses, useGridSelector } from '@mui/x-da import { GridPinnedRowsProps, gridPinnedRowsSelector, - gridRenderContextSelector, useGridPrivateApiContext, } from '@mui/x-data-grid/internals'; @@ -20,7 +19,6 @@ export function GridPinnedRows({ position, virtualScroller }: GridPinnedRowsProp const classes = useUtilityClasses(); const apiRef = useGridPrivateApiContext(); - const renderContext = useGridSelector(apiRef, gridRenderContextSelector); const pinnedRowsData = useGridSelector(apiRef, gridPinnedRowsSelector); const rows = pinnedRowsData[position]; @@ -28,10 +26,10 @@ export function GridPinnedRows({ position, virtualScroller }: GridPinnedRowsProp () => ({ firstRowIndex: 0, lastRowIndex: rows.length, - firstColumnIndex: renderContext.firstColumnIndex, - lastColumnIndex: renderContext.lastColumnIndex, + firstColumnIndex: -1, + lastColumnIndex: -1, }), - [rows, renderContext.firstColumnIndex, renderContext.lastColumnIndex], + [rows], ); if (rows.length === 0) { diff --git a/packages/x-data-grid-pro/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/x-data-grid-pro/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx index 66b8d90ac05f5..71a016ae27b5b 100644 --- a/packages/x-data-grid-pro/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -6,9 +6,12 @@ import { gridTabIndexColumnHeaderFilterSelector, getDataGridUtilityClass, GridFilterItem, - gridDimensionsSelector, } from '@mui/x-data-grid'; import { + gridColumnsTotalWidthSelector, + gridHasFillerSelector, + gridHeaderFilterHeightSelector, + gridVerticalScrollbarWidthSelector, useGridColumnHeaders as useGridColumnHeadersCommunity, UseGridColumnHeadersProps, GetHeadersParams, @@ -69,9 +72,11 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { const rootProps = useGridRootProps(); const classes = useUtilityClasses(rootProps); const disableHeaderFiltering = !rootProps.headerFilters; - const dimensions = useGridSelector(apiRef, gridDimensionsSelector); const filterModel = useGridSelector(apiRef, gridFilterModelSelector); - const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width; + const columnsTotalWidth = useGridSelector(apiRef, gridColumnsTotalWidthSelector); + const gridHasFiller = useGridSelector(apiRef, gridHasFillerSelector); + const headerFilterHeight = useGridSelector(apiRef, gridHeaderFilterHeightSelector); + const scrollbarWidth = useGridSelector(apiRef, gridVerticalScrollbarWidthSelector); const columnHeaderFilterFocus = useGridSelector(apiRef, gridFocusColumnHeaderFilterSelector); @@ -120,13 +125,12 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { const item = getFilterItem(colDef); const pinnedPosition = params?.position; - const scrollbarWidth = dimensions.hasScrollY ? dimensions.scrollbarSize : 0; const pinnedOffset = getPinnedCellOffset( pinnedPosition, colDef.computedWidth, columnIndex, columnPositions, - dimensions.columnsTotalWidth, + columnsTotalWidth, scrollbarWidth, ); @@ -146,7 +150,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { { + if (!props.getDetailPanelContent) { + return; + } apiRef.current.setState((state) => { return { ...state, @@ -248,16 +245,15 @@ export const useGridDetailPanel = ( }, }; }); - apiRef.current.updateDimensions?.(); apiRef.current.forceUpdate(); }, [apiRef, props.getDetailPanelContent, props.getDetailPanelHeight]); useGridApiEventHandler(apiRef, 'sortedRowsSet', updateCachesAndForceUpdate); const previousGetDetailPanelContentProp = - React.useRef(null); + React.useRef(undefined); const previousGetDetailPanelHeightProp = - React.useRef(null); + React.useRef(undefined); const updateCachesIfNeeded = React.useCallback(() => { if ( @@ -281,7 +277,6 @@ export const useGridDetailPanel = ( }, }; }); - apiRef.current.updateDimensions?.(); previousGetDetailPanelContentProp.current = props.getDetailPanelContent; previousGetDetailPanelHeightProp.current = props.getDetailPanelHeight; @@ -304,7 +299,8 @@ export const useGridDetailPanel = ( [apiRef, expandedRowIds, updateCachesIfNeeded], ); - useGridRegisterPipeProcessor(apiRef, 'rowHeight', addDetailHeight); + const enabled = props.getDetailPanelContent !== undefined; + useGridRegisterPipeProcessor(apiRef, 'rowHeight', addDetailHeight, enabled); const isFirstRender = React.useRef(true); if (isFirstRender.current) { @@ -313,7 +309,6 @@ export const useGridDetailPanel = ( React.useEffect(() => { if (!isFirstRender.current) { updateCachesIfNeeded(); - apiRef.current.hydrateRowsMeta(); } isFirstRender.current = false; }, [apiRef, updateCachesIfNeeded]); diff --git a/packages/x-data-grid-pro/src/hooks/features/infiniteLoader/useGridInfiniteLoader.tsx b/packages/x-data-grid-pro/src/hooks/features/infiniteLoader/useGridInfiniteLoader.tsx index 61a5fabda0729..ae1b6f11d06ea 100644 --- a/packages/x-data-grid-pro/src/hooks/features/infiniteLoader/useGridInfiniteLoader.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/infiniteLoader/useGridInfiniteLoader.tsx @@ -5,12 +5,12 @@ import { useGridApiOptionHandler, gridVisibleColumnDefinitionsSelector, useGridApiMethod, - gridDimensionsSelector, } from '@mui/x-data-grid'; import { useGridVisibleRows, GridInfiniteLoaderPrivateApi, useTimeout, + gridHorizontalScrollbarHeightSelector, } from '@mui/x-data-grid/internals'; import useEventCallback from '@mui/utils/useEventCallback'; import { styled } from '@mui/system'; @@ -64,10 +64,6 @@ export const useGridInfiniteLoader = ( }); const virtualScroller = apiRef.current.virtualScrollerRef.current; - const dimensions = useGridSelector(apiRef, gridDimensionsSelector); - - const marginBottom = - props.scrollEndThreshold - (dimensions.hasScrollX ? dimensions.scrollbarSize : 0); React.useEffect(() => { if (!isEnabled) { @@ -78,6 +74,9 @@ export const useGridInfiniteLoader = ( } observer.current?.disconnect(); + const horizontalScrollbarHeight = gridHorizontalScrollbarHeightSelector(apiRef.current.state); + const marginBottom = props.scrollEndThreshold - horizontalScrollbarHeight; + observer.current = new IntersectionObserver(handleLoadMoreRows, { threshold: 1, root: virtualScroller, @@ -86,7 +85,7 @@ export const useGridInfiniteLoader = ( if (triggerElement.current) { observer.current.observe(triggerElement.current); } - }, [virtualScroller, handleLoadMoreRows, isEnabled, marginBottom]); + }, [apiRef, virtualScroller, handleLoadMoreRows, isEnabled, props.scrollEndThreshold]); const updateTarget = (node: HTMLElement | null) => { if (triggerElement.current !== node) { diff --git a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts index 677f7210c9274..f319a36f07ef6 100644 --- a/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/serverSideTreeData/utils.ts @@ -1,22 +1,19 @@ import { GridRowId, GridRowTreeConfig, GRID_ROOT_GROUP_ID } from '@mui/x-data-grid'; -import { getTreeNodeDescendants } from '@mui/x-data-grid/internals'; +import { defaultGridFilterLookup, getTreeNodeDescendants } from '@mui/x-data-grid/internals'; export function skipFiltering(rowTree: GridRowTreeConfig) { - const filteredRowsLookup: Record = {}; const filteredChildrenCountLookup: Record = {}; - const filteredDescendantCountLookup: Record = {}; const nodes = Object.values(rowTree); for (let i = 0; i < nodes.length; i += 1) { const node: any = nodes[i]; - filteredRowsLookup[node.id] = true; filteredChildrenCountLookup[node.id] = node.serverChildrenCount ?? 0; } return { - filteredRowsLookup, + filteredRowsLookup: defaultGridFilterLookup.filteredRowsLookup, filteredChildrenCountLookup, - filteredDescendantCountLookup, + filteredDescendantCountLookup: defaultGridFilterLookup.filteredDescendantCountLookup, }; } diff --git a/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts b/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts index d3318e53583e5..cbaaee4732b45 100644 --- a/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts +++ b/packages/x-data-grid-pro/src/hooks/features/treeData/gridTreeDataUtils.ts @@ -1,6 +1,5 @@ import { RefObject } from '@mui/x-internals/types'; import { - GridRowId, GridRowTreeConfig, GridTreeNode, GridFilterState, @@ -35,9 +34,9 @@ export const filterRowTreeFromTreeData = ( params: FilterRowTreeFromTreeDataParams, ): Omit => { const { apiRef, rowTree, disableChildrenFiltering, isRowMatchingFilters } = params; - const filteredRowsLookup: Record = {}; - const filteredChildrenCountLookup: Record = {}; - const filteredDescendantCountLookup: Record = {}; + const filteredRowsLookup: GridFilterState['filteredRowsLookup'] = {}; + const filteredChildrenCountLookup: GridFilterState['filteredChildrenCountLookup'] = {}; + const filteredDescendantCountLookup: GridFilterState['filteredDescendantCountLookup'] = {}; const filterCache = {}; const filterResults: GridAggregatedFilterItemApplierResult = { @@ -103,7 +102,9 @@ export const filterRowTreeFromTreeData = ( } } - filteredRowsLookup[node.id] = shouldPassFilters; + if (!shouldPassFilters) { + filteredRowsLookup[node.id] = false; + } if (!shouldPassFilters) { return 0; diff --git a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx index b674feba8ef9e..3ed9018d5dc53 100644 --- a/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/detailPanel.DataGridPro.test.tsx @@ -20,7 +20,7 @@ import { act, reactMajor, } from '@mui/internal-test-utils'; -import { $, $$, grid, getRow, getCell, getColumnValues, microtasks } from 'test/utils/helperFn'; +import { $, $$, grid, getRow, getCell, getColumnValues } from 'test/utils/helperFn'; import { fireUserEvent } from 'test/utils/fireUserEvent'; import { testSkipIf, isJSDOM } from 'test/utils/skipIf'; @@ -83,7 +83,7 @@ describe(' - Detail panel', () => { async () => { const rowHeight = 50; const detailPanelHeight = 100; - render( + const { user } = render( - Detail panel', () => { getDetailPanelHeight={() => 'auto'} />, ); - fireEvent.click(screen.getAllByRole('button', { name: 'Expand' })[0]); - await microtasks(); + await user.click(screen.getAllByRole('button', { name: 'Expand' })[0]); + await waitFor(() => + expect(getRow(0).className).to.include(gridClasses['row--detailPanelExpanded']), + ); const virtualScrollerContent = $('.MuiDataGrid-virtualScrollerContent')!; expect(virtualScrollerContent).toHaveComputedStyle({ @@ -133,7 +135,9 @@ describe(' - Detail panel', () => { const virtualScrollerContent = grid('virtualScrollerContent')!; await user.click(screen.getByRole('button', { name: 'Expand' })); - expect(getRow(0).className).to.include(gridClasses['row--detailPanelExpanded']); + await waitFor(() => + expect(getRow(0).className).to.include(gridClasses['row--detailPanelExpanded']), + ); expect(virtualScrollerContent).toHaveComputedStyle({ height: `${rowHeight + 100}px` }); expect(virtualScrollerContent).toHaveInlineStyle({ width: 'auto' }); diff --git a/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx index 14a6c258a284c..d910de0a61907 100644 --- a/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/events.DataGridPro.test.tsx @@ -331,8 +331,11 @@ describe(' - Events params', () => { // Needs layout testSkipIf(isJSDOM)( - 'publishing GRID_ROWS_SCROLL should call onFetchRows callback when rows lazy loading is enabled', - () => { + 'lazy loaded grid should load the rest of the rows when mounted when virtualization is disabled', + function test() { + if (isJSDOM) { + this.skip(); // Needs layout + } const handleFetchRows = spy(); render( - Events params', () => { rowCount={50} />, ); - act(() => apiRef.current?.publishEvent('scrollPositionChange', { left: 0, top: 3 * 52 })); expect(handleFetchRows.callCount).to.equal(1); + expect(handleFetchRows.lastCall.firstArg).to.contain({ + firstRowToRender: 3, + lastRowToRender: 50, + }); }, ); + it('publishing renderedRowsIntervalChange should call onFetchRows callback when rows lazy loading is enabled', () => { + const handleFetchRows = spy(); + render( + , + ); + // Since rowheight < viewport height, onmount calls fetchRows directly + expect(handleFetchRows.callCount).to.equal(1); + act(() => { + apiRef.current?.publishEvent('renderedRowsIntervalChange', { + firstRowIndex: 3, + lastRowIndex: 10, + firstColumnIndex: 0, + lastColumnIndex: 0, + }); + }); + expect(handleFetchRows.callCount).to.equal(2); + expect(handleFetchRows.lastCall.firstArg).to.contain({ + firstRowToRender: 3, + lastRowToRender: 10, + }); + }); + it('should publish "unmount" event when unmounting the Grid', () => { const onUnmount = spy(); diff --git a/packages/x-data-grid-pro/src/utils/tree/utils.ts b/packages/x-data-grid-pro/src/utils/tree/utils.ts index fba2f4c9bfa23..0e79df66cc14c 100644 --- a/packages/x-data-grid-pro/src/utils/tree/utils.ts +++ b/packages/x-data-grid-pro/src/utils/tree/utils.ts @@ -16,6 +16,7 @@ import { } from '@mui/x-data-grid/internals'; import { RowTreeBuilderGroupingCriterion } from './models'; import { DataGridProProps } from '../../models/dataGridProProps'; +import type { GridStatePro } from '../../models/gridStatePro'; export const getGroupRowIdFromPath = (path: RowTreeBuilderGroupingCriterion[]) => { const pathStr = path @@ -240,10 +241,10 @@ export const getVisibleRowsLookup = ({ return {}; } - const visibleRowsLookup: Record = {}; + const visibleRowsLookup: GridStatePro['visibleRowsLookup'] = {}; const handleTreeNode = (node: GridTreeNode, areAncestorsExpanded: boolean) => { - const isPassingFiltering = filteredRowsLookup[node.id]; + const isPassingFiltering = filteredRowsLookup[node.id] !== false; if (node.type === 'group') { node.children.forEach((childId) => { @@ -252,12 +253,17 @@ export const getVisibleRowsLookup = ({ }); } - visibleRowsLookup[node.id] = isPassingFiltering && areAncestorsExpanded; + const isVisible = isPassingFiltering && areAncestorsExpanded; + if (!isVisible) { + visibleRowsLookup[node.id] = isVisible; + } // TODO rows v6: Should we keep storing the visibility status of footer independently or rely on the group visibility in the selector ? if (node.type === 'group' && node.footerId != null) { - visibleRowsLookup[node.footerId] = - isPassingFiltering && areAncestorsExpanded && !!node.childrenExpanded; + const isFooterVisible = isPassingFiltering && areAncestorsExpanded && !!node.childrenExpanded; + if (!isFooterVisible) { + visibleRowsLookup[node.footerId] = isFooterVisible; + } } }; diff --git a/packages/x-data-grid/package.json b/packages/x-data-grid/package.json index 2ca267257f5d4..96587886b8c67 100644 --- a/packages/x-data-grid/package.json +++ b/packages/x-data-grid/package.json @@ -52,7 +52,8 @@ "@mui/x-internals": "workspace:*", "clsx": "^2.1.1", "prop-types": "^15.8.1", - "reselect": "^5.1.1" + "reselect": "^5.1.1", + "use-sync-external-store": "^1.0.0" }, "peerDependencies": { "@emotion/react": "^11.9.0", @@ -76,6 +77,7 @@ "@mui/system": "^5.16.14", "@mui/types": "^7.2.20", "@types/prop-types": "^15.7.14", + "@types/use-sync-external-store": "^0.0.6", "react": "^19.0.0", "react-dom": "^19.0.0", "rimraf": "^6.0.1" diff --git a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx index 11bd616909589..2c135ec83bcea 100644 --- a/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx +++ b/packages/x-data-grid/src/DataGrid/useDataGridComponent.tsx @@ -81,7 +81,6 @@ export const useDataGridComponent = ( /** * Register all state initializers here. */ - useGridInitializeState(dimensionsStateInitializer, apiRef, props); useGridInitializeState(rowSelectionStateInitializer, apiRef, props); useGridInitializeState(columnsStateInitializer, apiRef, props); useGridInitializeState(paginationStateInitializer, apiRef, props); @@ -94,10 +93,11 @@ export const useDataGridComponent = ( useGridInitializeState(rowSpanningStateInitializer, apiRef, props); useGridInitializeState(densityStateInitializer, apiRef, props); useGridInitializeState(columnResizeStateInitializer, apiRef, props); - useGridInitializeState(rowsMetaStateInitializer, apiRef, props); useGridInitializeState(columnMenuStateInitializer, apiRef, props); useGridInitializeState(columnGroupsStateInitializer, apiRef, props); useGridInitializeState(virtualizationStateInitializer, apiRef, props); + useGridInitializeState(dimensionsStateInitializer, apiRef, props); + useGridInitializeState(rowsMetaStateInitializer, apiRef, props); useGridInitializeState(listViewStateInitializer, apiRef, props); useGridKeyboardNavigation(apiRef, props); diff --git a/packages/x-data-grid/src/components/GridRow.tsx b/packages/x-data-grid/src/components/GridRow.tsx index 4f2730916d9f2..da359f417d686 100644 --- a/packages/x-data-grid/src/components/GridRow.tsx +++ b/packages/x-data-grid/src/components/GridRow.tsx @@ -126,7 +126,10 @@ const GridRow = forwardRef(function GridRow(props, ); const handleRef = useForkRef(ref, refProp); const rowNode = apiRef.current.getRowNode(rowId); - const editing = useGridSelector(apiRef, gridRowIsEditingSelector, rowId); + const editing = useGridSelector(apiRef, gridRowIsEditingSelector, { + rowId, + editMode: rootProps.editMode, + }); const editable = rootProps.editMode === GridEditModes.Row; const hasFocusCell = focusedColumnIndex !== undefined; const hasVirtualFocusCellLeft = @@ -473,7 +476,9 @@ const GridRow = forwardRef(function GridRow(props, {cells}
{rightCells} - {scrollbarWidth !== 0 && 0} />} + {scrollbarWidth !== 0 && ( + 0} borderTop={!isFirstVisible} /> + )}
); }); diff --git a/packages/x-data-grid/src/components/GridScrollArea.tsx b/packages/x-data-grid/src/components/GridScrollArea.tsx index 268a29a0572d1..af98a1bd0c2d3 100644 --- a/packages/x-data-grid/src/components/GridScrollArea.tsx +++ b/packages/x-data-grid/src/components/GridScrollArea.tsx @@ -7,25 +7,30 @@ import { } from '@mui/utils'; import { styled } from '@mui/system'; import { fastMemo } from '@mui/x-internals/fastMemo'; +import { RefObject } from '@mui/x-internals/types'; import { DataGridProcessedProps } from '../models/props/DataGridProps'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { getDataGridUtilityClass, gridClasses } from '../constants'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { useGridApiEventHandler } from '../hooks/utils/useGridApiEventHandler'; import { useGridSelector } from '../hooks/utils/useGridSelector'; -import { gridDimensionsSelector } from '../hooks/features/dimensions/gridDimensionsSelectors'; +import { + gridDimensionsSelector, + gridColumnsTotalWidthSelector, +} from '../hooks/features/dimensions/gridDimensionsSelectors'; import { gridDensityFactorSelector } from '../hooks/features/density/densitySelector'; -import { gridColumnsTotalWidthSelector } from '../hooks/features/columns/gridColumnsSelector'; import { GridScrollParams } from '../models/params/gridScrollParams'; import { GridEventListener } from '../models/events'; import { useTimeout } from '../hooks/utils/useTimeout'; import { getTotalHeaderHeight } from '../hooks/features/columns/gridColumnsUtils'; +import { createSelector } from '../utils/createSelector'; const CLIFF = 1; const SLOP = 1.5; interface ScrollAreaProps { scrollDirection: 'left' | 'right'; + scrollPosition: RefObject; } type OwnerState = DataGridProcessedProps & Pick; @@ -62,21 +67,44 @@ const GridScrollAreaRawRoot = styled('div', { }, })); -function GridScrollAreaRaw(props: ScrollAreaProps) { - const { scrollDirection } = props; +const offsetSelector = createSelector( + gridDimensionsSelector, + (dimensions, direction: ScrollAreaProps['scrollDirection']) => { + if (direction === 'left') { + return dimensions.leftPinnedWidth; + } + if (direction === 'right') { + return dimensions.rightPinnedWidth + (dimensions.hasScrollX ? dimensions.scrollbarSize : 0); + } + return 0; + }, +); + +function GridScrollAreaWrapper(props: ScrollAreaProps) { + const apiRef = useGridApiContext(); + const [dragging, setDragging] = React.useState(false); + + useGridApiEventHandler(apiRef, 'columnHeaderDragStart', () => setDragging(true)); + useGridApiEventHandler(apiRef, 'columnHeaderDragEnd', () => setDragging(false)); + + if (!dragging) { + return null; + } + + return ; +} + +function GridScrollAreaContent(props: ScrollAreaProps) { + const { scrollDirection, scrollPosition } = props; const rootRef = React.useRef(null); const apiRef = useGridApiContext(); const timeout = useTimeout(); const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector); const columnsTotalWidth = useGridSelector(apiRef, gridColumnsTotalWidthSelector); - const dimensions = useGridSelector(apiRef, gridDimensionsSelector); - - const scrollPosition = React.useRef({ - left: 0, - top: 0, - }); + const sideOffset = useGridSelector(apiRef, offsetSelector, scrollDirection); const getCanScrollMore = () => { + const dimensions = gridDimensionsSelector(apiRef.current.state); if (scrollDirection === 'left') { // Only render if the user has not reached yet the start of the list return scrollPosition.current.left > 0; @@ -91,7 +119,6 @@ function GridScrollAreaRaw(props: ScrollAreaProps) { return false; }; - const [dragging, setDragging] = React.useState(false); const [canScrollMore, setCanScrollMore] = React.useState(getCanScrollMore); const rootProps = useGridRootProps(); @@ -106,15 +133,12 @@ function GridScrollAreaRaw(props: ScrollAreaProps) { }; if (scrollDirection === 'left') { - style.left = dimensions.leftPinnedWidth; + style.left = sideOffset; } else if (scrollDirection === 'right') { - style.right = - dimensions.rightPinnedWidth + (dimensions.hasScrollX ? dimensions.scrollbarSize : 0); + style.right = sideOffset; } - const handleScrolling: GridEventListener<'scrollPositionChange'> = (newScrollPosition) => { - scrollPosition.current = newScrollPosition; - + const handleScrolling: GridEventListener<'scrollPositionChange'> = () => { setCanScrollMore(getCanScrollMore); }; @@ -143,19 +167,9 @@ function GridScrollAreaRaw(props: ScrollAreaProps) { }); }); - const handleColumnHeaderDragStart = useEventCallback(() => { - setDragging(true); - }); - - const handleColumnHeaderDragEnd = useEventCallback(() => { - setDragging(false); - }); - useGridApiEventHandler(apiRef, 'scrollPositionChange', handleScrolling); - useGridApiEventHandler(apiRef, 'columnHeaderDragStart', handleColumnHeaderDragStart); - useGridApiEventHandler(apiRef, 'columnHeaderDragEnd', handleColumnHeaderDragEnd); - if (!dragging || !canScrollMore) { + if (!canScrollMore) { return null; } @@ -170,4 +184,4 @@ function GridScrollAreaRaw(props: ScrollAreaProps) { ); } -export const GridScrollArea = fastMemo(GridScrollAreaRaw); +export const GridScrollArea = fastMemo(GridScrollAreaWrapper); diff --git a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx index b17a03a468401..db9651d1dbd95 100644 --- a/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx +++ b/packages/x-data-grid/src/components/GridSkeletonLoadingOverlay.tsx @@ -9,7 +9,6 @@ import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { gridColumnPositionsSelector, - gridColumnsTotalWidthSelector, gridDimensionsSelector, gridVisibleColumnDefinitionsSelector, gridVisiblePinnedColumnDefinitionsSelector, @@ -17,6 +16,7 @@ import { useGridSelector, } from '../hooks'; import { PinnedColumnPosition } from '../internals/constants'; +import { gridColumnsTotalWidthSelector } from '../hooks/features/dimensions/gridDimensionsSelectors'; import { GridEventListener } from '../models'; import { DataGridProcessedProps } from '../models/props/DataGridProps'; import { getDataGridUtilityClass, gridClasses } from '../constants/gridClasses'; diff --git a/packages/x-data-grid/src/components/containers/GridRoot.tsx b/packages/x-data-grid/src/components/containers/GridRoot.tsx index 24fe8d5afe876..42cfbd9d949db 100644 --- a/packages/x-data-grid/src/components/containers/GridRoot.tsx +++ b/packages/x-data-grid/src/components/containers/GridRoot.tsx @@ -3,7 +3,6 @@ import PropTypes from 'prop-types'; import clsx from 'clsx'; import { unstable_useForkRef as useForkRef, - unstable_useEnhancedEffect as useEnhancedEffect, unstable_capitalize as capitalize, unstable_composeClasses as composeClasses, } from '@mui/utils'; @@ -19,6 +18,7 @@ import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { gridDensitySelector } from '../../hooks/features/density/densitySelector'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { GridDensity } from '../../models/gridDensity'; +import { useIsSSR } from '../../hooks/utils/useIsSSR'; import { GridHeader } from '../GridHeader'; import { GridBody, GridFooterPlaceholder } from '../base'; @@ -54,19 +54,26 @@ const GridRoot = forwardRef(function GridRoot(pro const apiRef = useGridPrivateApiContext(); const density = useGridSelector(apiRef, gridDensitySelector); const rootElementRef = apiRef.current.rootElementRef; - const handleRef = useForkRef(rootElementRef, ref); + + const rootMountCallback = React.useCallback( + (node: HTMLElement | null) => { + if (node === null) { + return; + } + apiRef.current.publishEvent('rootMount', node); + }, + [apiRef], + ); + + const handleRef = useForkRef(rootElementRef, ref, rootMountCallback); const ownerState = rootProps; const classes = useUtilityClasses(ownerState, density); - // Our implementation of - const [mountedState, setMountedState] = React.useState(false); - useEnhancedEffect(() => { - setMountedState(true); - }, []); + const isSSR = useIsSSR(); - if (!mountedState) { + if (isSSR) { return null; } diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index c98994048cfee..98249c2f005ea 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -13,7 +13,7 @@ import { gridClasses as c } from '../../constants/gridClasses'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { useGridPrivateApiContext } from '../../hooks/utils/useGridPrivateApiContext'; -import { gridDimensionsSelector } from '../../hooks/features/dimensions/gridDimensionsSelectors'; +import { GridStateCommunity } from '../../models/gridStateCommunity'; export type OwnerState = DataGridProcessedProps; @@ -54,6 +54,10 @@ const separatorIconDragStyles = { const ignoreSsrWarning = '/* emotion-disable-server-rendering-unsafe-selector-warning-please-do-not-use-this-the-warning-exists-for-a-reason */'; +const shouldShowBorderTopRightRadiusSelector = (state: GridStateCommunity) => + state.dimensions.hasScrollX && + (!state.dimensions.hasScrollY || state.dimensions.scrollbarSize === 0); + export const GridRootStyles = styled('div', { name: 'MuiDataGrid', slot: 'Root', @@ -179,7 +183,10 @@ export const GridRootStyles = styled('div', { ], })<{ ownerState: OwnerState }>(({ theme: t }) => { const apiRef = useGridPrivateApiContext(); - const dimensions = useGridSelector(apiRef, gridDimensionsSelector); + const shouldShowBorderTopRightRadius = useGridSelector( + apiRef, + shouldShowBorderTopRightRadiusSelector, + ); const borderColor = getBorderColor(t); const radius = t.shape.borderRadius; @@ -367,10 +374,9 @@ export const GridRootStyles = styled('div', { borderTopLeftRadius: 'calc(var(--unstable_DataGrid-radius) - 1px)', }, [`&.${c['root--noToolbar']} [aria-rowindex="1"] .${c['columnHeader--last']}`]: { - borderTopRightRadius: - dimensions.hasScrollX && (!dimensions.hasScrollY || dimensions.scrollbarSize === 0) - ? 'calc(var(--unstable_DataGrid-radius) - 1px)' - : undefined, + borderTopRightRadius: shouldShowBorderTopRightRadius + ? 'calc(var(--unstable_DataGrid-radius) - 1px)' + : undefined, }, [`& .${c.columnHeaderCheckbox}, & .${c.cellCheckbox}`]: { padding: 0, diff --git a/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx b/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx index be3e3aadabbb8..e09227daa4e17 100644 --- a/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridMainContainer.tsx @@ -4,7 +4,6 @@ import { forwardRef } from '@mui/x-internals/forwardRef'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { useGridConfiguration } from '../../hooks/utils/useGridConfiguration'; -import { GridDimensions } from '../../hooks/features/dimensions'; import { GridLoadingOverlayVariant } from '../GridLoadingOverlay'; const GridPanelAnchor = styled('div')({ @@ -15,7 +14,8 @@ const GridPanelAnchor = styled('div')({ }); type OwnerState = Pick & { - dimensions: GridDimensions; + hasScrollX: boolean; + hasPinnedRight: boolean; loadingOverlayVariant: GridLoadingOverlayVariant | null; }; @@ -26,7 +26,7 @@ const Element = styled('div', { const { ownerState } = props; return [ styles.main, - ownerState.dimensions.rightPinnedWidth > 0 && styles['main--hasPinnedRight'], + ownerState.hasPinnedRight && styles['main--hasPinnedRight'], ownerState.loadingOverlayVariant === 'skeleton' && styles['main--hasSkeletonLoadingOverlay'], ]; }, diff --git a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx index 4063e6bc47ae2..e5f569a880d1c 100644 --- a/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx +++ b/packages/x-data-grid/src/components/virtualization/GridVirtualScroller.tsx @@ -1,13 +1,17 @@ import * as React from 'react'; import { styled } from '@mui/system'; import composeClasses from '@mui/utils/composeClasses'; +import { + gridHasBottomFillerSelector, + gridHasScrollXSelector, + gridHasScrollYSelector, +} from '../../hooks/features/dimensions/gridDimensionsSelectors'; import { GridScrollArea } from '../GridScrollArea'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridSelector } from '../../hooks/utils/useGridSelector'; import { getDataGridUtilityClass } from '../../constants/gridClasses'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; -import { GridDimensions, gridDimensionsSelector } from '../../hooks/features/dimensions'; import { useGridVirtualScroller } from '../../hooks/features/virtualization/useGridVirtualScroller'; import { useGridOverlays } from '../../hooks/features/overlays/useGridOverlays'; import { GridHeaders } from '../GridHeaders'; @@ -19,21 +23,23 @@ import { GridVirtualScrollerFiller as SpaceFiller } from './GridVirtualScrollerF import { GridVirtualScrollerRenderZone as RenderZone } from './GridVirtualScrollerRenderZone'; import { GridVirtualScrollbar as Scrollbar } from './GridVirtualScrollbar'; import { GridLoadingOverlayVariant } from '../GridLoadingOverlay'; +import { GridStateCommunity } from '../../models/gridStateCommunity'; type OwnerState = Pick & { - dimensions: GridDimensions; + hasScrollX: boolean; + hasPinnedRight: boolean; loadingOverlayVariant: GridLoadingOverlayVariant | null; }; const useUtilityClasses = (ownerState: OwnerState) => { - const { classes, dimensions, loadingOverlayVariant } = ownerState; + const { classes, hasScrollX, hasPinnedRight, loadingOverlayVariant } = ownerState; const slots = { root: [ 'main', - dimensions.rightPinnedWidth > 0 && 'main--hasPinnedRight', + hasPinnedRight && 'main--hasPinnedRight', loadingOverlayVariant === 'skeleton' && 'main--hasSkeletonLoadingOverlay', ], - scroller: ['virtualScroller', dimensions.hasScrollX && 'virtualScroller--hasScrollX'], + scroller: ['virtualScroller', hasScrollX && 'virtualScroller--hasScrollX'], }; return composeClasses(slots, getDataGridUtilityClass, classes); @@ -44,10 +50,7 @@ const Scroller = styled('div', { slot: 'VirtualScroller', overridesResolver: (props, styles) => { const { ownerState } = props; - return [ - styles.virtualScroller, - ownerState.dimensions.hasScrollX && styles['virtualScroller--hasScrollX'], - ]; + return [styles.virtualScroller, ownerState.hasScrollX && styles['virtualScroller--hasScrollX']]; }, })<{ ownerState: OwnerState }>({ position: 'relative', @@ -69,6 +72,8 @@ const Scroller = styled('div', { zIndex: 0, }); +const hasPinnedRightSelector = (state: GridStateCommunity) => state.dimensions.rightPinnedWidth > 0; + export interface GridVirtualScrollerProps { children?: React.ReactNode; } @@ -76,11 +81,15 @@ export interface GridVirtualScrollerProps { function GridVirtualScroller(props: GridVirtualScrollerProps) { const apiRef = useGridApiContext(); const rootProps = useGridRootProps(); - const dimensions = useGridSelector(apiRef, gridDimensionsSelector); + const hasScrollY = useGridSelector(apiRef, gridHasScrollYSelector); + const hasScrollX = useGridSelector(apiRef, gridHasScrollXSelector); + const hasPinnedRight = useGridSelector(apiRef, hasPinnedRightSelector); + const hasBottomFiller = useGridSelector(apiRef, gridHasBottomFillerSelector); const { getOverlay, overlaysProps } = useGridOverlays(); const ownerState = { classes: rootProps.classes, - dimensions, + hasScrollX, + hasPinnedRight, loadingOverlayVariant: overlaysProps.loadingOverlayVariant, }; const classes = useUtilityClasses(ownerState); @@ -94,14 +103,15 @@ function GridVirtualScroller(props: GridVirtualScrollerProps) { getScrollbarVerticalProps, getScrollbarHorizontalProps, getRows, + getScrollAreaProps, } = virtualScroller; const rows = getRows(); return ( - - + + {!rootProps.unstable_listView && } @@ -117,16 +127,15 @@ function GridVirtualScroller(props: GridVirtualScrollerProps) { - - + {hasBottomFiller && } - {dimensions.hasScrollX && !rootProps.unstable_listView && ( + {hasScrollX && !rootProps.unstable_listView && ( )} - {dimensions.hasScrollY && } + {hasScrollY && } {props.children} ); diff --git a/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeProcessor.ts b/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeProcessor.ts index d15fa6077945a..ec918959da3cd 100644 --- a/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeProcessor.ts +++ b/packages/x-data-grid/src/hooks/core/pipeProcessing/useGridRegisterPipeProcessor.ts @@ -11,6 +11,7 @@ export const useGridRegisterPipeProcessor = < apiRef: RefObject, group: G, callback: GridPipeProcessor, + enabled: boolean = true, ) => { const cleanup = React.useRef<(() => void) | null>(null); const id = React.useRef(`mui-${Math.round(Math.random() * 1e9)}`); @@ -20,14 +21,16 @@ export const useGridRegisterPipeProcessor = < }, [apiRef, callback, group]); useFirstRender(() => { - registerPreProcessor(); + if (enabled) { + registerPreProcessor(); + } }); const isFirstRender = React.useRef(true); React.useEffect(() => { if (isFirstRender.current) { isFirstRender.current = false; - } else { + } else if (enabled) { registerPreProcessor(); } @@ -37,5 +40,5 @@ export const useGridRegisterPipeProcessor = < cleanup.current = null; } }; - }, [registerPreProcessor]); + }, [registerPreProcessor, enabled]); }; diff --git a/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts b/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts index 63cabbe5aa168..cdf14bbf6c33f 100644 --- a/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts +++ b/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts @@ -12,8 +12,6 @@ export const useGridStateInitialization = > >({}); - const [, rawForceUpdate] = React.useState(); - const registerControlState = React.useCallback< GridStatePrivateApi['registerControlState'] >((controlStateItem) => { @@ -118,7 +116,9 @@ export const useGridStateInitialization = rawForceUpdate(() => apiRef.current.state), [apiRef]); + const forceUpdate = React.useCallback(() => { + // @deprecated - do nothing + }, []); const publicStateApi: Omit, 'state'> = { setState, diff --git a/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx b/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx index 30a0ab2e02c1f..ff382271dd348 100644 --- a/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx +++ b/packages/x-data-grid/src/hooks/features/columnHeaders/useGridColumnHeaders.tsx @@ -9,7 +9,13 @@ import type { GridColumnsRenderContext } from '../../../models/params/gridScroll import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; import { GridEventListener } from '../../../models/events'; import { GridColumnHeaderItem } from '../../../components/columnHeaders/GridColumnHeaderItem'; -import { gridDimensionsSelector } from '../dimensions'; +import { + gridColumnsTotalWidthSelector, + gridGroupHeaderHeightSelector, + gridHasFillerSelector, + gridHeaderHeightSelector, + gridVerticalScrollbarWidthSelector, +} from '../dimensions/gridDimensionsSelectors'; import { gridRenderContextColumnsSelector } from '../virtualization'; import { computeOffsetLeft } from '../virtualization/useGridVirtualScroller'; import { GridColumnGroupHeader } from '../../../components/columnHeaders/GridColumnGroupHeader'; @@ -99,20 +105,17 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { const apiRef = useGridPrivateApiContext(); const rootProps = useGridRootProps(); - const dimensions = useGridSelector(apiRef, gridDimensionsSelector); const columnGroupsModel = useGridSelector(apiRef, gridColumnGroupsUnwrappedModelSelector); const columnPositions = useGridSelector(apiRef, gridColumnPositionsSelector); const renderContext = useGridSelector(apiRef, gridRenderContextColumnsSelector); const pinnedColumns = useGridSelector(apiRef, gridVisiblePinnedColumnDefinitionsSelector); const columnsLookup = useGridSelector(apiRef, gridColumnLookupSelector); const offsetLeft = computeOffsetLeft(columnPositions, renderContext, pinnedColumns.left.length); - const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width; - - React.useEffect(() => { - if (apiRef.current.columnHeadersContainerRef.current) { - apiRef.current.columnHeadersContainerRef.current.scrollLeft = 0; - } - }, [apiRef]); + const columnsTotalWidth = useGridSelector(apiRef, gridColumnsTotalWidthSelector); + const gridHasFiller = useGridSelector(apiRef, gridHasFillerSelector); + const headerHeight = useGridSelector(apiRef, gridHeaderHeightSelector); + const groupHeaderHeight = useGridSelector(apiRef, gridGroupHeaderHeightSelector); + const scrollbarWidth = useGridSelector(apiRef, gridVerticalScrollbarWidthSelector); const handleColumnResizeStart = React.useCallback>( (params) => setResizeCol(params.field), @@ -227,13 +230,13 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { const hasFocus = columnHeaderFocus !== null && columnHeaderFocus.field === colDef.field; const open = columnMenuState.open && columnMenuState.field === colDef.field; const pinnedPosition = params?.position; - const scrollbarWidth = dimensions.hasScrollY ? dimensions.scrollbarSize : 0; + const pinnedOffset = getPinnedCellOffset( pinnedPosition, colDef.computedWidth, columnIndex, columnPositions, - dimensions.columnsTotalWidth, + columnsTotalWidth, scrollbarWidth, ); @@ -268,7 +271,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { filterItemsCounter={ filterColumnLookup[colDef.field] && filterColumnLookup[colDef.field].length } - headerHeight={dimensions.headerHeight} + headerHeight={headerHeight} isDragging={colDef.field === dragCol} colDef={colDef} colIndex={columnIndex} @@ -402,13 +405,12 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { }; const pinnedPosition = params.position; - const scrollbarWidth = dimensions.hasScrollY ? dimensions.scrollbarSize : 0; const pinnedOffset = getPinnedCellOffset( pinnedPosition, headerInfo.width, columnIndex, columnPositions, - dimensions.columnsTotalWidth, + columnsTotalWidth, scrollbarWidth, ); @@ -430,7 +432,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { depth={depth} isLastColumn={index === visibleColumnGroupHeader.length - 1} maxDepth={headerGroupingMaxDepth} - height={dimensions.groupHeaderHeight} + height={groupHeaderHeight} hasFocus={hasFocus} tabIndex={tabIndex} pinnedPosition={pinnedPosition} diff --git a/packages/x-data-grid/src/hooks/features/columns/gridColumnsSelector.ts b/packages/x-data-grid/src/hooks/features/columns/gridColumnsSelector.ts index f252388bc18f0..7c35559f3c62b 100644 --- a/packages/x-data-grid/src/hooks/features/columns/gridColumnsSelector.ts +++ b/packages/x-data-grid/src/hooks/features/columns/gridColumnsSelector.ts @@ -149,22 +149,6 @@ export const gridColumnPositionsSelector = createSelectorMemoized( }, ); -/** - * Get the summed width of all the visible columns. - * @category Visible Columns - */ -export const gridColumnsTotalWidthSelector = createSelector( - gridVisibleColumnDefinitionsSelector, - gridColumnPositionsSelector, - (visibleColumns, positions) => { - const colCount = visibleColumns.length; - if (colCount === 0) { - return 0; - } - return positions[colCount - 1] + visibleColumns[colCount - 1].computedWidth; - }, -); - /** * Get the filterable columns as an array. * @category Columns diff --git a/packages/x-data-grid/src/hooks/features/dimensions/gridDimensionsSelectors.ts b/packages/x-data-grid/src/hooks/features/dimensions/gridDimensionsSelectors.ts index 2365ce546492f..b117c7c677595 100644 --- a/packages/x-data-grid/src/hooks/features/dimensions/gridDimensionsSelectors.ts +++ b/packages/x-data-grid/src/hooks/features/dimensions/gridDimensionsSelectors.ts @@ -1,3 +1,52 @@ import { GridStateCommunity } from '../../../models/gridStateCommunity'; +import { createSelector } from '../../../utils/createSelector'; export const gridDimensionsSelector = (state: GridStateCommunity) => state.dimensions; + +/** + * Get the summed width of all the visible columns. + * @category Visible Columns + */ +export const gridColumnsTotalWidthSelector = createSelector( + gridDimensionsSelector, + (dimensions) => dimensions.columnsTotalWidth, +); + +export const gridRowHeightSelector = (state: GridStateCommunity) => state.dimensions.rowHeight; + +export const gridContentHeightSelector = (state: GridStateCommunity) => + state.dimensions.contentSize.height; + +export const gridHasScrollXSelector = (state: GridStateCommunity) => state.dimensions.hasScrollX; + +export const gridHasScrollYSelector = (state: GridStateCommunity) => state.dimensions.hasScrollY; + +export const gridHasFillerSelector = (state: GridStateCommunity) => + state.dimensions.columnsTotalWidth < state.dimensions.viewportOuterSize.width; + +export const gridHeaderHeightSelector = (state: GridStateCommunity) => + state.dimensions.headerHeight; + +export const gridGroupHeaderHeightSelector = (state: GridStateCommunity) => + state.dimensions.groupHeaderHeight; + +export const gridHeaderFilterHeightSelector = (state: GridStateCommunity) => + state.dimensions.headerFilterHeight; + +export const gridVerticalScrollbarWidthSelector = (state: GridStateCommunity) => + state.dimensions.hasScrollY ? state.dimensions.scrollbarSize : 0; + +export const gridHorizontalScrollbarHeightSelector = (state: GridStateCommunity) => + state.dimensions.hasScrollX ? state.dimensions.scrollbarSize : 0; + +export const gridHasBottomFillerSelector = (state: GridStateCommunity) => { + const height = state.dimensions.hasScrollX ? state.dimensions.scrollbarSize : 0; + const needsLastRowBorder = + state.dimensions.viewportOuterSize.height - state.dimensions.minimumSize.height > 0; + + if (height === 0 && !needsLastRowBorder) { + return false; + } + + return true; +}; diff --git a/packages/x-data-grid/src/hooks/features/dimensions/index.ts b/packages/x-data-grid/src/hooks/features/dimensions/index.ts index bc71eeab845d4..8d50106f7e8b3 100644 --- a/packages/x-data-grid/src/hooks/features/dimensions/index.ts +++ b/packages/x-data-grid/src/hooks/features/dimensions/index.ts @@ -1,3 +1,3 @@ export type { GridDimensionsState } from './useGridDimensions'; -export * from './gridDimensionsSelectors'; +export { gridDimensionsSelector, gridColumnsTotalWidthSelector } from './gridDimensionsSelectors'; export type { GridDimensions, GridDimensionsApi } from './gridDimensionsApi'; diff --git a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts index e37f70936cab9..1843b7bfb1325 100644 --- a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts +++ b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts @@ -9,16 +9,15 @@ import { throttle } from '@mui/x-internals/throttle'; import { GridEventListener } from '../../../models/events'; import { ElementSize } from '../../../models'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; -import { - useGridApiEventHandler, - useGridApiOptionHandler, -} from '../../utils/useGridApiEventHandler'; +import { useGridApiOptionHandler } from '../../utils/useGridApiEventHandler'; import { useGridApiMethod } from '../../utils/useGridApiMethod'; +import { createSelector } from '../../../utils/createSelector'; import { useGridLogger } from '../../utils/useGridLogger'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridDimensions, GridDimensionsApi, GridDimensionsPrivateApi } from './gridDimensionsApi'; import { - gridColumnsTotalWidthSelector, + gridColumnPositionsSelector, + gridVisibleColumnDefinitionsSelector, gridVisiblePinnedColumnDefinitionsSelector, } from '../columns'; import { gridDimensionsSelector } from './gridDimensionsSelectors'; @@ -27,16 +26,13 @@ import { gridRenderContextSelector } from '../virtualization'; import { useGridSelector } from '../../utils'; import { getVisibleRows } from '../../utils/useGridVisibleRows'; import { gridRowsMetaSelector } from '../rows/gridRowsMetaSelector'; -import { - calculatePinnedRowsHeight, - getValidRowHeight, - rowHeightWarning, -} from '../rows/gridRowsUtils'; +import { getValidRowHeight, rowHeightWarning } from '../rows/gridRowsUtils'; import { getTotalHeaderHeight } from '../columns/gridColumnsUtils'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; import { DATA_GRID_PROPS_DEFAULT_VALUES } from '../../../constants/dataGridPropsDefaultValues'; import { roundToDecimalPlaces } from '../../../utils/roundToDecimalPlaces'; import { isJSDOM } from '../../../utils/isJSDOM'; +import { isDeepEqual } from '../../../utils/utils'; type RootProps = Pick< DataGridProcessedProps, @@ -79,58 +75,80 @@ const EMPTY_DIMENSIONS: GridDimensions = { bottomContainerHeight: 0, }; -export const dimensionsStateInitializer: GridStateInitializer = (state) => { +export const dimensionsStateInitializer: GridStateInitializer = ( + state, + props, + apiRef, +) => { const dimensions = EMPTY_DIMENSIONS; + const density = gridDensityFactorSelector(apiRef); + return { ...state, - dimensions, + dimensions: { + ...dimensions, + ...getStaticDimensions( + props, + apiRef, + density, + gridVisiblePinnedColumnDefinitionsSelector(apiRef), + ), + }, }; }; +const columnsTotalWidthSelector = createSelector( + gridVisibleColumnDefinitionsSelector, + gridColumnPositionsSelector, + (visibleColumns, positions) => { + const colCount = visibleColumns.length; + if (colCount === 0) { + return 0; + } + return roundToDecimalPlaces( + positions[colCount - 1] + visibleColumns[colCount - 1].computedWidth, + 1, + ); + }, +); + export function useGridDimensions(apiRef: RefObject, props: RootProps) { const logger = useGridLogger(apiRef, 'useResizeContainer'); const errorShown = React.useRef(false); const rootDimensionsRef = React.useRef(EMPTY_SIZE); - const dimensionsState = useGridSelector(apiRef, gridDimensionsSelector); - const rowsMeta = useGridSelector(apiRef, gridRowsMetaSelector); const pinnedColumns = useGridSelector(apiRef, gridVisiblePinnedColumnDefinitionsSelector); const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector); - const validRowHeight = React.useMemo( - () => - getValidRowHeight( - props.rowHeight, - DATA_GRID_PROPS_DEFAULT_VALUES.rowHeight, - rowHeightWarning, - ), - [props.rowHeight], - ); - const rowHeight = Math.floor(validRowHeight * densityFactor); - const headerHeight = Math.floor(props.columnHeaderHeight * densityFactor); - const groupHeaderHeight = Math.floor( - (props.columnGroupHeaderHeight ?? props.columnHeaderHeight) * densityFactor, - ); - const headerFilterHeight = Math.floor( - (props.headerFilterHeight ?? props.columnHeaderHeight) * densityFactor, - ); - const columnsTotalWidth = roundToDecimalPlaces(gridColumnsTotalWidthSelector(apiRef), 1); - const headersTotalHeight = getTotalHeaderHeight(apiRef, props); + const columnsTotalWidth = useGridSelector(apiRef, columnsTotalWidthSelector); + const isFirstSizing = React.useRef(true); - const leftPinnedWidth = pinnedColumns.left.reduce((w, col) => w + col.computedWidth, 0); - const rightPinnedWidth = pinnedColumns.right.reduce((w, col) => w + col.computedWidth, 0); + const { + rowHeight, + headerHeight, + groupHeaderHeight, + headerFilterHeight, + headersTotalHeight, + leftPinnedWidth, + rightPinnedWidth, + } = getStaticDimensions(props, apiRef, densityFactor, pinnedColumns); - const [savedSize, setSavedSize] = React.useState(); - const debouncedSetSavedSize = React.useMemo( - () => throttle(setSavedSize, props.resizeThrottleMs), - [props.resizeThrottleMs], + const getRootDimensions = React.useCallback( + () => gridDimensionsSelector(apiRef.current.state), + [apiRef], ); - React.useEffect(() => debouncedSetSavedSize.clear, [debouncedSetSavedSize]); - const getRootDimensions = () => apiRef.current.state.dimensions; - - const setDimensions = useEventCallback((dimensions: GridDimensions) => { - apiRef.current.setState((state) => ({ ...state, dimensions })); - }); + const setDimensions = React.useCallback( + (dimensions: GridDimensions) => { + apiRef.current.setState((state) => ({ ...state, dimensions })); + if (apiRef.current.rootElementRef.current) { + setCSSVariables( + apiRef.current.rootElementRef.current, + gridDimensionsSelector(apiRef.current.state), + ); + } + }, + [apiRef], + ); const getViewportPageSize = React.useCallback(() => { const dimensions = gridDimensionsSelector(apiRef.current.state); @@ -138,10 +156,7 @@ export function useGridDimensions(apiRef: RefObject, pr return 0; } - const currentPage = getVisibleRows(apiRef, { - pagination: props.pagination, - paginationMode: props.paginationMode, - }); + const currentPage = getVisibleRows(apiRef); // TODO: Use a combination of scrollTop, dimensions.viewportInnerSize.height and rowsMeta.possitions // to find out the maximum number of rows that can fit in the visible part of the grid @@ -157,19 +172,22 @@ export function useGridDimensions(apiRef: RefObject, pr ); return Math.min(maximumPageSizeWithoutScrollBar, currentPage.rows.length); - }, [apiRef, props.pagination, props.paginationMode, props.getRowHeight, rowHeight]); + }, [apiRef, props.getRowHeight, rowHeight]); const updateDimensions = React.useCallback(() => { + if (isFirstSizing.current) { + return; + } // All the floating point dimensions should be rounded to .1 decimal places to avoid subpixel rendering issues // https://github.com/mui/mui-x/issues/9550#issuecomment-1619020477 // https://github.com/mui/mui-x/issues/15721 const rootElement = apiRef.current.rootElementRef.current; - const pinnedRowsHeight = calculatePinnedRowsHeight(apiRef); - const scrollbarSize = measureScrollbarSize(rootElement, columnsTotalWidth, props.scrollbarSize); + const scrollbarSize = measureScrollbarSize(rootElement, props.scrollbarSize); - const topContainerHeight = headersTotalHeight + pinnedRowsHeight.top; - const bottomContainerHeight = pinnedRowsHeight.bottom; + const rowsMeta = gridRowsMetaSelector(apiRef.current.state); + const topContainerHeight = headersTotalHeight + rowsMeta.pinnedTopRowsTotalHeight; + const bottomContainerHeight = rowsMeta.pinnedBottomRowsTotalHeight; const contentSize = { width: columnsTotalWidth, @@ -261,6 +279,11 @@ export function useGridDimensions(apiRef: RefObject, pr }; const prevDimensions = apiRef.current.state.dimensions; + + if (isDeepEqual(prevDimensions as any, newDimensions)) { + return; + } + setDimensions(newDimensions); if (!areElementSizesEqual(newDimensions.viewportInnerSize, prevDimensions.viewportInnerSize)) { @@ -273,7 +296,6 @@ export function useGridDimensions(apiRef: RefObject, pr setDimensions, props.scrollbarSize, props.autoHeight, - rowsMeta.currentPageTotalHeight, rowHeight, headerHeight, groupHeaderHeight, @@ -284,6 +306,19 @@ export function useGridDimensions(apiRef: RefObject, pr rightPinnedWidth, ]); + const updateDimensionCallback = useEventCallback(updateDimensions); + const debouncedUpdateDimensions = React.useMemo( + () => + props.resizeThrottleMs > 0 + ? throttle(() => { + updateDimensionCallback(); + apiRef.current.publishEvent('debouncedResize', rootDimensionsRef.current!); + }, props.resizeThrottleMs) + : undefined, + [apiRef, props.resizeThrottleMs, updateDimensionCallback], + ); + React.useEffect(() => debouncedUpdateDimensions?.clear, [debouncedUpdateDimensions]); + const apiPublic: GridDimensionsApi = { getRootDimensions, }; @@ -293,38 +328,17 @@ export function useGridDimensions(apiRef: RefObject, pr getViewportPageSize, }; + useEnhancedEffect(updateDimensions, [updateDimensions]); useGridApiMethod(apiRef, apiPublic, 'public'); useGridApiMethod(apiRef, apiPrivate, 'private'); - useEnhancedEffect(() => { - if (savedSize) { - updateDimensions(); - apiRef.current.publishEvent('debouncedResize', rootDimensionsRef.current!); - } - }, [apiRef, savedSize, updateDimensions]); - - const root = apiRef.current.rootElementRef.current; - useEnhancedEffect(() => { - if (!root) { - return; - } - const set = (k: string, v: string) => root.style.setProperty(k, v); - set('--DataGrid-width', `${dimensionsState.viewportOuterSize.width}px`); - set('--DataGrid-hasScrollX', `${Number(dimensionsState.hasScrollX)}`); - set('--DataGrid-hasScrollY', `${Number(dimensionsState.hasScrollY)}`); - set('--DataGrid-scrollbarSize', `${dimensionsState.scrollbarSize}px`); - set('--DataGrid-rowWidth', `${dimensionsState.rowWidth}px`); - set('--DataGrid-columnsTotalWidth', `${dimensionsState.columnsTotalWidth}px`); - set('--DataGrid-leftPinnedWidth', `${dimensionsState.leftPinnedWidth}px`); - set('--DataGrid-rightPinnedWidth', `${dimensionsState.rightPinnedWidth}px`); - set('--DataGrid-headerHeight', `${dimensionsState.headerHeight}px`); - set('--DataGrid-headersTotalHeight', `${dimensionsState.headersTotalHeight}px`); - set('--DataGrid-topContainerHeight', `${dimensionsState.topContainerHeight}px`); - set('--DataGrid-bottomContainerHeight', `${dimensionsState.bottomContainerHeight}px`); - set('--height', `${dimensionsState.rowHeight}px`); - }, [root, dimensionsState]); + const handleRootMount = React.useCallback>( + (root) => { + setCSSVariables(root, gridDimensionsSelector(apiRef.current.state)); + }, + [apiRef], + ); - const isFirstSizing = React.useRef(true); const handleResize = React.useCallback>( (size) => { rootDimensionsRef.current = size; @@ -353,40 +367,82 @@ export function useGridDimensions(apiRef: RefObject, pr errorShown.current = true; } - if (isFirstSizing.current) { + if (isFirstSizing.current || !debouncedUpdateDimensions) { // We want to initialize the grid dimensions as soon as possible to avoid flickering - setSavedSize(size); isFirstSizing.current = false; + updateDimensions(); return; } - debouncedSetSavedSize(size); + debouncedUpdateDimensions(); }, - [props.autoHeight, debouncedSetSavedSize, logger], + [updateDimensions, props.autoHeight, debouncedUpdateDimensions, logger], ); - useEnhancedEffect(updateDimensions, [updateDimensions]); - - useGridApiOptionHandler(apiRef, 'sortedRowsSet', updateDimensions); - useGridApiOptionHandler(apiRef, 'paginationModelChange', updateDimensions); - useGridApiOptionHandler(apiRef, 'columnsChange', updateDimensions); - useGridApiEventHandler(apiRef, 'resize', handleResize); + useGridApiOptionHandler(apiRef, 'rootMount', handleRootMount); + useGridApiOptionHandler(apiRef, 'resize', handleResize); useGridApiOptionHandler(apiRef, 'debouncedResize', props.onResize); } -function measureScrollbarSize( - rootElement: Element | null, - columnsTotalWidth: number, - scrollbarSize: number | undefined, +function setCSSVariables(root: HTMLElement, dimensions: GridDimensions) { + const set = (k: string, v: string) => root.style.setProperty(k, v); + set('--DataGrid-hasScrollX', `${Number(dimensions.hasScrollX)}`); + set('--DataGrid-hasScrollY', `${Number(dimensions.hasScrollY)}`); + set('--DataGrid-scrollbarSize', `${dimensions.scrollbarSize}px`); + set('--DataGrid-rowWidth', `${dimensions.rowWidth}px`); + set('--DataGrid-columnsTotalWidth', `${dimensions.columnsTotalWidth}px`); + set('--DataGrid-leftPinnedWidth', `${dimensions.leftPinnedWidth}px`); + set('--DataGrid-rightPinnedWidth', `${dimensions.rightPinnedWidth}px`); + set('--DataGrid-headerHeight', `${dimensions.headerHeight}px`); + set('--DataGrid-headersTotalHeight', `${dimensions.headersTotalHeight}px`); + set('--DataGrid-topContainerHeight', `${dimensions.topContainerHeight}px`); + set('--DataGrid-bottomContainerHeight', `${dimensions.bottomContainerHeight}px`); + set('--height', `${dimensions.rowHeight}px`); +} + +function getStaticDimensions( + props: RootProps, + apiRef: RefObject, + density: number, + pinnedColumnns: ReturnType, ) { + const validRowHeight = getValidRowHeight( + props.rowHeight, + DATA_GRID_PROPS_DEFAULT_VALUES.rowHeight, + rowHeightWarning, + ); + + return { + rowHeight: Math.floor(validRowHeight * density), + headerHeight: Math.floor(props.columnHeaderHeight * density), + groupHeaderHeight: Math.floor( + (props.columnGroupHeaderHeight ?? props.columnHeaderHeight) * density, + ), + headerFilterHeight: Math.floor( + (props.headerFilterHeight ?? props.columnHeaderHeight) * density, + ), + columnsTotalWidth: columnsTotalWidthSelector(apiRef), + headersTotalHeight: getTotalHeaderHeight(apiRef, props), + leftPinnedWidth: pinnedColumnns.left.reduce((w, col) => w + col.computedWidth, 0), + rightPinnedWidth: pinnedColumnns.right.reduce((w, col) => w + col.computedWidth, 0), + }; +} + +const scrollbarSizeCache = new WeakMap(); +function measureScrollbarSize(rootElement: Element | null, scrollbarSize: number | undefined) { if (scrollbarSize !== undefined) { return scrollbarSize; } - if (rootElement === null || columnsTotalWidth === 0) { + if (rootElement === null) { return 0; } + const cachedSize = scrollbarSizeCache.get(rootElement); + if (cachedSize !== undefined) { + return cachedSize; + } + const doc = ownerDocument(rootElement); const scrollDiv = doc.createElement('div'); scrollDiv.style.width = '99px'; @@ -397,6 +453,9 @@ function measureScrollbarSize( rootElement.appendChild(scrollDiv); const size = scrollDiv.offsetWidth - scrollDiv.clientWidth; rootElement.removeChild(scrollDiv); + + scrollbarSizeCache.set(rootElement, size); + return size; } diff --git a/packages/x-data-grid/src/hooks/features/editing/gridEditingSelectors.ts b/packages/x-data-grid/src/hooks/features/editing/gridEditingSelectors.ts index 988fe7f81fd92..564b5cc920d4e 100644 --- a/packages/x-data-grid/src/hooks/features/editing/gridEditingSelectors.ts +++ b/packages/x-data-grid/src/hooks/features/editing/gridEditingSelectors.ts @@ -1,6 +1,7 @@ import { createSelector } from '../../../utils/createSelector'; import { GridStateCommunity } from '../../../models/gridStateCommunity'; import { GridRowId } from '../../../models/gridRows'; +import { GridEditModes, GridEditMode } from '../../../models/gridEditRowModel'; /** * Select the row editing state. @@ -9,7 +10,8 @@ export const gridEditRowsStateSelector = (state: GridStateCommunity) => state.ed export const gridRowIsEditingSelector = createSelector( gridEditRowsStateSelector, - (editRows, rowId: GridRowId) => Boolean(editRows[rowId]), + (editRows, { rowId, editMode }: { rowId: GridRowId; editMode: GridEditMode }) => + editMode === GridEditModes.Row && Boolean(editRows[rowId]), ); export const gridEditCellStateSelector = createSelector( diff --git a/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts b/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts index 62f53ac3ff5a0..1302c4d1cc9f9 100644 --- a/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts +++ b/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts @@ -328,10 +328,10 @@ export const useGridRowEditing = ( const getRowMode = React.useCallback( (id) => { - if (props.editMode === GridEditModes.Cell) { - return GridRowModes.View; - } - const isEditing = gridRowIsEditingSelector(apiRef.current.state, id); + const isEditing = gridRowIsEditingSelector(apiRef.current.state, { + rowId: id, + editMode: props.editMode, + }); return isEditing ? GridRowModes.Edit : GridRowModes.View; }, [apiRef, props.editMode], diff --git a/packages/x-data-grid/src/hooks/features/filter/gridFilterSelector.ts b/packages/x-data-grid/src/hooks/features/filter/gridFilterSelector.ts index 218bb839348ca..559294471f34c 100644 --- a/packages/x-data-grid/src/hooks/features/filter/gridFilterSelector.ts +++ b/packages/x-data-grid/src/hooks/features/filter/gridFilterSelector.ts @@ -1,3 +1,4 @@ +import { isObjectEmpty } from '@mui/x-internals/isObjectEmpty'; import { createSelector, createSelectorMemoized } from '../../../utils/createSelector'; import { GridRowId } from '../../../models/gridRows'; import { GridFilterItem } from '../../../models/gridFilterItem'; @@ -70,11 +71,8 @@ export const gridFilteredDescendantCountLookupSelector = createSelector( export const gridExpandedSortedRowEntriesSelector = createSelectorMemoized( gridVisibleRowsLookupSelector, gridSortedRowEntriesSelector, - gridRowMaximumTreeDepthSelector, - gridFilterModelSelector, - gridQuickFilterValuesSelector, - (visibleRowsLookup, sortedRows, maxDepth, filterModel, quickFilterValues) => { - if (maxDepth < 2 && !filterModel.items.length && !quickFilterValues?.length) { + (visibleRowsLookup, sortedRows) => { + if (isObjectEmpty(visibleRowsLookup)) { return sortedRows; } return sortedRows.filter((row) => visibleRowsLookup[row.id] !== false); @@ -100,7 +98,9 @@ export const gridFilteredSortedRowEntriesSelector = createSelectorMemoized( gridFilteredRowsLookupSelector, gridSortedRowEntriesSelector, (filteredRowsLookup, sortedRows) => - sortedRows.filter((row) => filteredRowsLookup[row.id] !== false), + isObjectEmpty(filteredRowsLookup) + ? sortedRows + : sortedRows.filter((row) => filteredRowsLookup[row.id] !== false), ); /** diff --git a/packages/x-data-grid/src/hooks/features/filter/gridFilterState.ts b/packages/x-data-grid/src/hooks/features/filter/gridFilterState.ts index c48354461cf09..4a6fb5d892e67 100644 --- a/packages/x-data-grid/src/hooks/features/filter/gridFilterState.ts +++ b/packages/x-data-grid/src/hooks/features/filter/gridFilterState.ts @@ -5,6 +5,12 @@ import { GridRowId, GridValidRowModel } from '../../../models/gridRows'; export type GridFilterItemResult = { [key: Required['id']]: boolean }; export type GridQuickFilterValueResult = { [key: string]: boolean }; +export const defaultGridFilterLookup = { + filteredRowsLookup: {}, + filteredChildrenCountLookup: {}, + filteredDescendantCountLookup: {}, +}; + export const getDefaultGridFilterModel: () => GridFilterModel = () => ({ items: [], logicOperator: GridLogicOperator.And, @@ -17,10 +23,10 @@ export interface GridFilterState { /** * Filtering status for each row. * A row is filtered if it is passing the filters, whether its parents are expanded or not. - * If a row is not registered in this lookup, it is filtered. + * All the rows are filtered except the ones registered in this lookup with `false` values. * This is the equivalent of the `visibleRowsLookup` if all the groups were expanded. */ - filteredRowsLookup: Record; + filteredRowsLookup: Record; /** * Amount of children that are passing the filters or have children that are passing the filter (does not count grand children). * If a row is not registered in this lookup, it is supposed to have no descendant passing the filters. @@ -67,4 +73,4 @@ export type GridFilteringMethodValue = Omit; * A row is visible if it is passing the filters AND if its parents are expanded. * If a row is not registered in this lookup, it is visible. */ -export type GridVisibleRowsLookupState = Record; +export type GridVisibleRowsLookupState = Record; diff --git a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx index e0c5edc2c4a81..c07297f02d806 100644 --- a/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx +++ b/packages/x-data-grid/src/hooks/features/filter/useGridFilter.tsx @@ -15,7 +15,7 @@ import { useGridApiMethod } from '../../utils/useGridApiMethod'; import { useGridLogger } from '../../utils/useGridLogger'; import { gridColumnLookupSelector } from '../columns/gridColumnsSelector'; import { GridPreferencePanelsValue } from '../preferencesPanel/gridPreferencePanelsValue'; -import { getDefaultGridFilterModel } from './gridFilterState'; +import { defaultGridFilterLookup, getDefaultGridFilterModel } from './gridFilterState'; import { gridFilterModelSelector } from './gridFilterSelector'; import { useFirstRender } from '../../utils/useFirstRender'; import { gridRowsLookupSelector } from '../rows'; @@ -46,9 +46,7 @@ export const filterStateInitializer: GridStateInitializer< ...state, filter: { filterModel: sanitizeFilterModel(filterModel, props.disableMultipleColumnsFiltering, apiRef), - filteredRowsLookup: {}, - filteredChildrenCountLookup: {}, - filteredDescendantCountLookup: {}, + ...defaultGridFilterLookup, }, visibleRowsLookup: {}, }; @@ -423,12 +421,12 @@ export const useGridFilter = ( const flatFilteringMethod = React.useCallback>( (params) => { - if (props.filterMode !== 'client' || !params.isRowMatchingFilters) { - return { - filteredRowsLookup: {}, - filteredChildrenCountLookup: {}, - filteredDescendantCountLookup: {}, - }; + if ( + props.filterMode !== 'client' || + !params.isRowMatchingFilters || + (!params.filterModel.items.length && !params.filterModel.quickFilterValues?.length) + ) { + return defaultGridFilterLookup; } const dataRowIdToModelLookup = gridRowsLookupSelector(apiRef); @@ -456,7 +454,9 @@ export const useGridFilter = ( filterCache, ); - filteredRowsLookup[id] = isRowPassing; + if (!isRowPassing) { + filteredRowsLookup[id] = isRowPassing; + } } const footerId = 'auto-generated-group-footer-root'; diff --git a/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts b/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts index 9c6c627a15501..7de8573b9b2ac 100644 --- a/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts +++ b/packages/x-data-grid/src/hooks/features/pagination/useGridPagination.ts @@ -2,7 +2,6 @@ import { RefObject } from '@mui/x-internals/types'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; - import { throwIfPageSizeExceedsTheLimit, getDefaultGridPaginationModel, @@ -31,7 +30,10 @@ export const paginationStateInitializer: GridStateInitializer< throwIfPageSizeExceedsTheLimit(paginationModel.pageSize, props.signature); - const rowCount = props.rowCount ?? props.initialState?.pagination?.rowCount; + const rowCount = + props.rowCount ?? + props.initialState?.pagination?.rowCount ?? + (props.paginationMode === 'client' ? state.rows?.totalRowCount : undefined); const meta = props.paginationMeta ?? props.initialState?.pagination?.meta ?? {}; return { ...state, diff --git a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts index bd3e4270c606a..3f46ce0f78080 100644 --- a/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts +++ b/packages/x-data-grid/src/hooks/features/rowSelection/useGridRowSelection.ts @@ -463,7 +463,7 @@ export const useGridRowSelection = ( if (props.filterMode === 'server') { return !rowsLookup[id]; } - return filteredRowsLookup[id] !== true; + return !rowsLookup[id] || filteredRowsLookup[id] === false; }; let hasChanged = false; diff --git a/packages/x-data-grid/src/hooks/features/rowSelection/utils.ts b/packages/x-data-grid/src/hooks/features/rowSelection/utils.ts index 76dacf2c0cd5b..91e2323e91507 100644 --- a/packages/x-data-grid/src/hooks/features/rowSelection/utils.ts +++ b/packages/x-data-grid/src/hooks/features/rowSelection/utils.ts @@ -145,7 +145,7 @@ const getFilteredRowNodeSiblings = ( const parentNode = tree[parent] as GridGroupNode; - return parentNode.children.filter((childId) => childId !== id && filteredRows[childId]); + return parentNode.children.filter((childId) => childId !== id && filteredRows[childId] !== false); }; export const findRowsToSelect = ( diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsMetaState.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsMetaState.ts index a7d8d440db6c7..0d614814a3ce9 100644 --- a/packages/x-data-grid/src/hooks/features/rows/gridRowsMetaState.ts +++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsMetaState.ts @@ -10,4 +10,12 @@ export interface GridRowsMetaState { * The grid rows positions. */ positions: number[]; + /** + * The total height of the pinned top rows. + */ + pinnedTopRowsTotalHeight: number; + /** + * The total height of the pinned bottom rows. + */ + pinnedBottomRowsTotalHeight: number; } diff --git a/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts b/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts index 2b10527aa0681..09c9d5e5a2bd8 100644 --- a/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts +++ b/packages/x-data-grid/src/hooks/features/rows/gridRowsUtils.ts @@ -23,7 +23,6 @@ import { GridRowIdToModelLookup, GridRowsPartialUpdateAction, } from './gridRowsInterfaces'; -import { gridPinnedRowsSelector } from './gridRowsSelector'; export const GRID_ROOT_GROUP_ID: GridRowId = `auto-generated-group-node-root`; export const GRID_ID_AUTOGENERATED = Symbol('mui.id_autogenerated'); @@ -370,26 +369,6 @@ export const updateCacheWithNewRows = ({ }; }; -export function calculatePinnedRowsHeight(apiRef: RefObject) { - const pinnedRows = gridPinnedRowsSelector(apiRef); - const topPinnedRowsHeight = - pinnedRows?.top?.reduce((acc, value) => { - acc += apiRef.current.unstable_getRowHeight(value.id); - return acc; - }, 0) || 0; - - const bottomPinnedRowsHeight = - pinnedRows?.bottom?.reduce((acc, value) => { - acc += apiRef.current.unstable_getRowHeight(value.id); - return acc; - }, 0) || 0; - - return { - top: topPinnedRowsHeight, - bottom: bottomPinnedRowsHeight, - }; -} - export const minimalContentHeight = 'var(--DataGrid-overlayHeight, calc(var(--height) * 2))'; export function computeRowsUpdates( diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts index b7585843d04eb..01186dd1d7231 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRows.ts @@ -1,6 +1,7 @@ import * as React from 'react'; import { RefObject } from '@mui/x-internals/types'; import useLazyRef from '@mui/utils/useLazyRef'; +import { isObjectEmpty } from '@mui/x-internals/isObjectEmpty'; import { GridEventListener } from '../../../models/events'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; @@ -329,7 +330,9 @@ export const useGridRows = ( if (applyFiltering) { const filteredRowsLookup = gridFilteredRowsLookupSelector(apiRef); - children = children.filter((childId) => filteredRowsLookup[childId] !== false); + children = isObjectEmpty(filteredRowsLookup) + ? children + : children.filter((childId) => filteredRowsLookup[childId] !== false); } return children; diff --git a/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts b/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts index a57c294b2fa9c..ebd1318ea31c2 100644 --- a/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts +++ b/packages/x-data-grid/src/hooks/features/rows/useGridRowsMeta.ts @@ -12,13 +12,14 @@ import { useGridApiMethod } from '../../utils/useGridApiMethod'; import { GridRowEntry } from '../../../models/gridRows'; import { useGridSelector } from '../../utils/useGridSelector'; import { gridDensityFactorSelector } from '../density/densitySelector'; -import { gridFilterModelSelector } from '../filter/gridFilterSelector'; import { gridPaginationSelector } from '../pagination/gridPaginationSelector'; -import { gridSortModelSelector } from '../sorting/gridSortingSelector'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; import { useGridRegisterPipeApplier } from '../../core/pipeProcessing'; -import { gridPinnedRowsSelector } from './gridRowsSelector'; -import { gridDimensionsSelector } from '../dimensions/gridDimensionsSelectors'; +import { gridPinnedRowsSelector, gridRowCountSelector } from './gridRowsSelector'; +import { + gridDimensionsSelector, + gridRowHeightSelector, +} from '../dimensions/gridDimensionsSelectors'; import { getValidRowHeight, getRowHeightWarning } from './gridRowsUtils'; import type { HeightEntry } from './gridRowsMetaInterfaces'; @@ -29,11 +30,21 @@ export const rowsMetaStateInitializer: GridStateInitializer = (state, props, api heights: new Map(), }; + const baseRowHeight = gridRowHeightSelector(apiRef.current.state); + const dataRowCount = gridRowCountSelector(apiRef.current.state); + const pagination = gridPaginationSelector(apiRef.current.state); + const rowCount = Math.min( + pagination.enabled ? pagination.paginationModel.pageSize : dataRowCount, + dataRowCount, + ); + return { ...state, rowsMeta: { - currentPageTotalHeight: 0, - positions: [], + currentPageTotalHeight: rowCount * baseRowHeight, + positions: Array.from({ length: rowCount }, (_, i) => i * baseRowHeight), + pinnedTopRowsTotalHeight: 0, + pinnedBottomRowsTotalHeight: 0, }, }; }; @@ -62,15 +73,9 @@ export const useGridRowsMeta = ( const isHeightMetaValid = React.useRef(false); const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector); - const filterModel = useGridSelector(apiRef, gridFilterModelSelector); - const paginationState = useGridSelector(apiRef, gridPaginationSelector); - const sortModel = useGridSelector(apiRef, gridSortModelSelector); const currentPage = useGridVisibleRows(apiRef, props); const pinnedRows = useGridSelector(apiRef, gridPinnedRowsSelector); - const rowHeight = useGridSelector( - apiRef, - () => gridDimensionsSelector(apiRef.current.state).rowHeight, - ); + const rowHeight = useGridSelector(apiRef, gridRowHeightSelector); const getRowHeightEntry: GridRowsMetaPrivateApi['getRowHeightEntry'] = (rowId) => { let entry = heightCache.get(rowId); @@ -158,8 +163,15 @@ export const useGridRowsMeta = ( const hydrateRowsMeta = React.useCallback(() => { hasRowWithAutoHeight.current = false; - pinnedRows.top.forEach(processHeightEntry); - pinnedRows.bottom.forEach(processHeightEntry); + const pinnedTopRowsTotalHeight = pinnedRows.top.reduce((acc, row) => { + const entry = processHeightEntry(row); + return acc + entry.content + entry.spacingTop + entry.spacingBottom + entry.detail; + }, 0); + + const pinnedBottomRowsTotalHeight = pinnedRows.bottom.reduce((acc, row) => { + const entry = processHeightEntry(row); + return acc + entry.content + entry.spacingTop + entry.spacingBottom + entry.detail; + }, 0); const positions: number[] = []; const currentPageTotalHeight = currentPage.rows.reduce((acc, row) => { @@ -176,16 +188,29 @@ export const useGridRowsMeta = ( lastMeasuredRowIndex.current = Infinity; } + const didHeightsChange = + pinnedTopRowsTotalHeight !== apiRef.current.state.rowsMeta.pinnedTopRowsTotalHeight || + pinnedBottomRowsTotalHeight !== apiRef.current.state.rowsMeta.pinnedBottomRowsTotalHeight || + currentPageTotalHeight !== apiRef.current.state.rowsMeta.currentPageTotalHeight; + + const rowsMeta = { + currentPageTotalHeight, + positions, + pinnedTopRowsTotalHeight, + pinnedBottomRowsTotalHeight, + }; + apiRef.current.setState((state) => { return { ...state, - rowsMeta: { - currentPageTotalHeight, - positions, - }, + rowsMeta, }; }); + if (didHeightsChange) { + apiRef.current.updateDimensions(); + } + isHeightMetaValid.current = true; }, [apiRef, pinnedRows, currentPage.rows, processHeightEntry]); @@ -258,7 +283,7 @@ export const useGridRowsMeta = ( // Because of variable row height this is needed for the virtualization useEnhancedEffect(() => { hydrateRowsMeta(); - }, [filterModel, paginationState, sortModel, hydrateRowsMeta]); + }, [hydrateRowsMeta]); const rowsMetaApi: GridRowsMetaApi = { unstable_getRowHeight: getRowHeight, diff --git a/packages/x-data-grid/src/hooks/features/sorting/gridSortingSelector.ts b/packages/x-data-grid/src/hooks/features/sorting/gridSortingSelector.ts index 1022a5ee6f2c7..2751c5e2a82bd 100644 --- a/packages/x-data-grid/src/hooks/features/sorting/gridSortingSelector.ts +++ b/packages/x-data-grid/src/hooks/features/sorting/gridSortingSelector.ts @@ -33,10 +33,11 @@ export const gridSortedRowEntriesSelector = createSelectorMemoized( const model = idRowsLookup[id]; if (model) { acc.push({ id, model }); - } - const rowNode = rowTree[id]; - if (rowNode && isAutogeneratedRowNode(rowNode)) { - acc.push({ id, model: { [GRID_ID_AUTOGENERATED]: id } }); + } else { + const rowNode = rowTree[id]; + if (rowNode && isAutogeneratedRowNode(rowNode)) { + acc.push({ id, model: { [GRID_ID_AUTOGENERATED]: id } }); + } } return acc; }, [] as GridRowEntry[]), diff --git a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx index 8be34f61cb08c..b87bd89d4b9c3 100644 --- a/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx +++ b/packages/x-data-grid/src/hooks/features/virtualization/useGridVirtualScroller.tsx @@ -9,6 +9,14 @@ import useLazyRef from '@mui/utils/useLazyRef'; import useTimeout from '@mui/utils/useTimeout'; import { useRtl } from '@mui/system/RtlProvider'; import reactMajor from '@mui/x-internals/reactMajor'; +import { + gridDimensionsSelector, + gridColumnsTotalWidthSelector, + gridContentHeightSelector, + gridHasFillerSelector, + gridRowHeightSelector, + gridVerticalScrollbarWidthSelector, +} from '../dimensions/gridDimensionsSelectors'; import type { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; import { useGridPrivateApiContext } from '../../utils/useGridPrivateApiContext'; import { useGridRootProps } from '../../utils/useGridRootProps'; @@ -20,11 +28,10 @@ import { gridColumnPositionsSelector, gridHasColSpanSelector, } from '../columns/gridColumnsSelector'; -import { gridDimensionsSelector } from '../dimensions/gridDimensionsSelectors'; import { gridPinnedRowsSelector } from '../rows/gridRowsSelector'; import { GridPinnedRowsPosition } from '../rows/gridRowsInterfaces'; import { useGridVisibleRows, getVisibleRows } from '../../utils/useGridVisibleRows'; -import { useGridApiEventHandler } from '../../utils'; +import { useGridApiOptionHandler } from '../../utils'; import * as platform from '../../../utils/platform'; import { clamp, range } from '../../../utils/utils'; import { @@ -51,6 +58,7 @@ import { EMPTY_PINNED_COLUMN_FIELDS, GridPinnedColumns } from '../columns'; import { gridFocusedVirtualCellSelector } from './gridFocusedVirtualCellSelector'; import { roundToDecimalPlaces } from '../../../utils/roundToDecimalPlaces'; import { isJSDOM } from '../../../utils/isJSDOM'; +import { GridStateCommunity } from '../../../models/gridStateCommunity'; const MINIMUM_COLUMN_WIDTH = 50; @@ -104,8 +112,7 @@ export const useGridVirtualScroller = () => { const enabledForRows = useGridSelector(apiRef, gridVirtualizationRowEnabledSelector) && !isJSDOM; const enabledForColumns = useGridSelector(apiRef, gridVirtualizationColumnEnabledSelector) && !isJSDOM; - const dimensions = useGridSelector(apiRef, gridDimensionsSelector); - const outerSize = dimensions.viewportOuterSize; + const pinnedRows = useGridSelector(apiRef, gridPinnedRowsSelector); const pinnedColumnDefinitions = gridVisiblePinnedColumnDefinitionsSelector(apiRef); const pinnedColumns = listView @@ -121,11 +128,16 @@ export const useGridVirtualScroller = () => { const scrollerRef = apiRef.current.virtualScrollerRef; const scrollbarVerticalRef = apiRef.current.virtualScrollbarVerticalRef; const scrollbarHorizontalRef = apiRef.current.virtualScrollbarHorizontalRef; - const contentHeight = dimensions.contentSize.height; - const columnsTotalWidth = dimensions.columnsTotalWidth; const hasColSpan = useGridSelector(apiRef, gridHasColSpanSelector); const isRenderContextReady = React.useRef(false); + const rowHeight = useGridSelector(apiRef, gridRowHeightSelector); + const contentHeight = useGridSelector(apiRef, gridContentHeightSelector); + const columnsTotalWidth = useGridSelector(apiRef, gridColumnsTotalWidthSelector); + const needsHorizontalScrollbar = useGridSelector(apiRef, needsHorizontalScrollbarSelector); + const verticalScrollbarWidth = useGridSelector(apiRef, gridVerticalScrollbarWidthSelector); + const gridHasFiller = useGridSelector(apiRef, gridHasFillerSelector); + const previousSize = React.useRef<{ width: number; height: number }>(null); const mainRefCallback = React.useCallback( @@ -217,7 +229,7 @@ export const useGridVirtualScroller = () => { isRtl, rootProps.rowBufferPx, rootProps.columnBufferPx, - dimensions.rowHeight * 15, + rowHeight * 15, MINIMUM_COLUMN_WIDTH * 6, ), ).current; @@ -245,16 +257,17 @@ export const useGridVirtualScroller = () => { }); // The lazy-loading hook is listening to `renderedRowsIntervalChange`, - // but only does something if the dimensions are also available. - // So we wait until we have valid dimensions before publishing the first event. - if (dimensions.isReady && didRowsIntervalChange) { + // but only does something if we already have a render context, because + // otherwise we would call an update directly on mount + const isReady = gridDimensionsSelector(apiRef.current.state).isReady; + if (isReady && didRowsIntervalChange) { previousRowContext.current = nextRenderContext; apiRef.current.publishEvent('renderedRowsIntervalChange', nextRenderContext); } previousContextScrollPosition.current = scrollPosition.current; }, - [apiRef, dimensions.isReady], + [apiRef], ); const triggerUpdateRenderContext = useEventCallback(() => { @@ -263,6 +276,7 @@ export const useGridVirtualScroller = () => { return undefined; } + const dimensions = gridDimensionsSelector(apiRef.current.state); const maxScrollTop = Math.ceil( dimensions.minimumSize.height - dimensions.viewportOuterSize.height, ); @@ -296,8 +310,7 @@ export const useGridVirtualScroller = () => { ); // PERF: use the computed minimum column width instead of a static one - const didCrossThreshold = - rowScroll >= dimensions.rowHeight || columnScroll >= MINIMUM_COLUMN_WIDTH; + const didCrossThreshold = rowScroll >= rowHeight || columnScroll >= MINIMUM_COLUMN_WIDTH; const didChangeDirection = scrollCache.direction !== direction; const shouldUpdate = didCrossThreshold || didChangeDirection; @@ -326,7 +339,7 @@ export const useGridVirtualScroller = () => { direction, rootProps.rowBufferPx, rootProps.columnBufferPx, - dimensions.rowHeight * 15, + rowHeight * 15, MINIMUM_COLUMN_WIDTH * 6, ); @@ -344,6 +357,13 @@ export const useGridVirtualScroller = () => { }); const forceUpdateRenderContext = () => { + // skip update if dimensions are not ready and virtualization is enabled + if ( + !gridDimensionsSelector(apiRef.current.state).isReady && + (enabledForRows || enabledForColumns) + ) { + return; + } const inputs = inputsSelector(apiRef, rootProps, enabledForRows, enabledForColumns); const nextRenderContext = computeRenderContext(inputs, scrollPosition.current, scrollCache); // Reset the frozen context when the render context changes, see the illustration in https://github.com/mui/mui-x/pull/12353 @@ -385,7 +405,13 @@ export const useGridVirtualScroller = () => { return []; } - const baseRenderContext = params.renderContext ?? renderContext; + let baseRenderContext = renderContext; + if (params.renderContext) { + baseRenderContext = params.renderContext as GridRenderContext; + + baseRenderContext.firstColumnIndex = renderContext.firstColumnIndex; + baseRenderContext.lastColumnIndex = renderContext.lastColumnIndex; + } const isLastSection = (!hasBottomPinnedRows && params.position === undefined) || @@ -500,7 +526,6 @@ export const useGridVirtualScroller = () => { let currentRenderContext = baseRenderContext; if ( - !isPinnedSection && frozenContext.current && rowIndexInPage >= frozenContext.current.firstRowIndex && rowIndexInPage < frozenContext.current.lastRowIndex @@ -529,7 +554,7 @@ export const useGridVirtualScroller = () => { index={rowIndex} selected={isSelected} offsetLeft={offsetLeft} - columnsTotalWidth={dimensions.columnsTotalWidth} + columnsTotalWidth={columnsTotalWidth} rowHeight={baseRowHeight} pinnedColumns={pinnedColumns} visibleColumns={visibleColumns} @@ -540,8 +565,8 @@ export const useGridVirtualScroller = () => { isLastVisible={isLastVisible} isNotVisible={isVirtualFocusRow} showBottomBorder={showBottomBorder} - scrollbarWidth={dimensions.hasScrollY ? dimensions.scrollbarSize : 0} - gridHasFiller={dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width} + scrollbarWidth={verticalScrollbarWidth} + gridHasFiller={gridHasFiller} {...rowProps} />, ); @@ -561,8 +586,6 @@ export const useGridVirtualScroller = () => { return rows; }; - const needsHorizontalScrollbar = outerSize.width && columnsTotalWidth > outerSize.width; - const scrollerStyle = React.useMemo( () => ({ @@ -612,16 +635,11 @@ export const useGridVirtualScroller = () => { } }, [listView, scrollerRef]); - useRunOnce(outerSize.width !== 0, () => { - const inputs = inputsSelector(apiRef, rootProps, enabledForRows, enabledForColumns); - - const initialRenderContext = computeRenderContext(inputs, scrollPosition.current, scrollCache); - updateRenderContext(initialRenderContext); - + useRunOnce(renderContext !== EMPTY_RENDER_CONTEXT, () => { apiRef.current.publishEvent('scrollPositionChange', { top: scrollPosition.current.top, left: scrollPosition.current.left, - renderContext: initialRenderContext, + renderContext, }); isRenderContextReady.current = true; @@ -680,9 +698,9 @@ export const useGridVirtualScroller = () => { updateRenderContext: forceUpdateRenderContext, }); - useGridApiEventHandler(apiRef, 'columnsChange', forceUpdateRenderContext); - useGridApiEventHandler(apiRef, 'filteredRowsSet', forceUpdateRenderContext); - useGridApiEventHandler(apiRef, 'rowExpansionChange', forceUpdateRenderContext); + useGridApiOptionHandler(apiRef, 'sortedRowsSet', forceUpdateRenderContext); + useGridApiOptionHandler(apiRef, 'paginationModelChange', forceUpdateRenderContext); + useGridApiOptionHandler(apiRef, 'columnsChange', forceUpdateRenderContext); return { renderContext, @@ -718,6 +736,9 @@ export const useGridVirtualScroller = () => { role: 'presentation', scrollPosition, }), + getScrollAreaProps: () => ({ + scrollPosition, + }), }; }; @@ -745,6 +766,14 @@ type RenderContextInputs = { virtualizeColumnsWithAutoRowHeight: DataGridProcessedProps['virtualizeColumnsWithAutoRowHeight']; }; +// dimension selectors +function needsHorizontalScrollbarSelector(state: GridStateCommunity) { + return ( + state.dimensions.viewportOuterSize.width > 0 && + state.dimensions.columnsTotalWidth > state.dimensions.viewportOuterSize.width + ); +} + function inputsSelector( apiRef: RefObject, rootProps: ReturnType, diff --git a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts index 6680e010ecb00..20962d950fbf2 100644 --- a/packages/x-data-grid/src/hooks/utils/useGridSelector.ts +++ b/packages/x-data-grid/src/hooks/utils/useGridSelector.ts @@ -2,10 +2,10 @@ import * as React from 'react'; import { RefObject } from '@mui/x-internals/types'; import { fastObjectShallowCompare } from '@mui/x-internals/fastObjectShallowCompare'; import { warnOnce } from '@mui/x-internals/warning'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import type { GridApiCommon } from '../../models/api/gridApiCommon'; import type { OutputSelector } from '../../utils/createSelector'; import { useLazyRef } from './useLazyRef'; -import { useOnMount } from './useOnMount'; import type { GridCoreApi } from '../../models/api/gridCoreApi'; function isOutputSelector( @@ -50,7 +50,19 @@ export const argsEqual = (prev: any, curr: any) => { return fn(prev, curr); }; -const createRefs = () => ({ state: null, equals: null, selector: null, args: null }) as any; +const createRefs = () => ({ state: null, equals: null, selector: null, args: undefined }) as any; + +const EMPTY = [] as unknown[]; + +type Refs = { + state: T; + equals: (a: U, b: U) => boolean; + selector: Selector; + args: any; + subscription: undefined | (() => void); +}; + +const emptyGetSnapshot = () => null; export const useGridSelector = ( apiRef: RefObject, @@ -67,15 +79,7 @@ export const useGridSelector = ( } } - const refs = useLazyRef< - { - state: T; - equals: typeof equals; - selector: typeof selector; - args: typeof args; - }, - never - >(createRefs); + const refs = useLazyRef, never>(createRefs); const didInit = refs.current.selector !== null; const [state, setState] = React.useState( @@ -102,20 +106,43 @@ export const useGridSelector = ( } } - useOnMount(() => { - return apiRef.current.store.subscribe(() => { - const newState = applySelector( - apiRef, - refs.current.selector, - refs.current.args, - apiRef.current.instanceId, - ) as T; - if (!refs.current.equals(refs.current.state, newState)) { - refs.current.state = newState; - setState(newState); + const subscribe = React.useCallback( + () => { + if (refs.current.subscription) { + return null; } - }); - }); + + refs.current.subscription = apiRef.current.store.subscribe(() => { + const newState = applySelector( + apiRef, + refs.current.selector, + refs.current.args, + apiRef.current.instanceId, + ) as T; + + if (!refs.current.equals(refs.current.state, newState)) { + refs.current.state = newState; + setState(newState); + } + }); + + return null; + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + EMPTY, + ); + + const unsubscribe = React.useCallback(() => { + return () => { + if (refs.current.subscription) { + refs.current.subscription(); + refs.current.subscription = undefined; + } + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, EMPTY); + + useSyncExternalStore(unsubscribe, subscribe, emptyGetSnapshot); return state; }; diff --git a/packages/x-data-grid/src/hooks/utils/useIsSSR.ts b/packages/x-data-grid/src/hooks/utils/useIsSSR.ts new file mode 100644 index 0000000000000..f40364e068971 --- /dev/null +++ b/packages/x-data-grid/src/hooks/utils/useIsSSR.ts @@ -0,0 +1,7 @@ +import { useSyncExternalStore } from 'use-sync-external-store/shim'; + +const emptySubscribe = () => () => {}; +const clientSnapshot = () => false; +const serverSnapshot = () => true; + +export const useIsSSR = () => useSyncExternalStore(emptySubscribe, clientSnapshot, serverSnapshot); diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index b65b0de05fa23..96eb78a72a688 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -61,6 +61,7 @@ export { useGridDensity, densityStateInitializer } from '../hooks/features/densi export { useGridCsvExport } from '../hooks/features/export/useGridCsvExport'; export { useGridPrintExport } from '../hooks/features/export/useGridPrintExport'; export { useGridFilter, filterStateInitializer } from '../hooks/features/filter/useGridFilter'; +export { defaultGridFilterLookup } from '../hooks/features/filter/gridFilterState'; export { passFilterLogic } from '../hooks/features/filter/gridFilterUtils'; export { gridFilteredChildrenCountLookupSelector, @@ -114,7 +115,6 @@ export { headerFilteringStateInitializer, useGridHeaderFiltering, } from '../hooks/features/headerFiltering/useGridHeaderFiltering'; -export { calculatePinnedRowsHeight } from '../hooks/features/rows/gridRowsUtils'; export { useGridRowSelection, rowSelectionStateInitializer, @@ -129,6 +129,7 @@ export { dimensionsStateInitializer, useGridDimensions, } from '../hooks/features/dimensions/useGridDimensions'; +export * from '../hooks/features/dimensions/gridDimensionsSelectors'; export { useGridStatePersistence } from '../hooks/features/statePersistence/useGridStatePersistence'; export type { GridRestoreStatePreProcessingContext } from '../hooks/features/statePersistence/gridStatePersistenceInterface'; export { diff --git a/packages/x-data-grid/src/models/api/gridStateApi.ts b/packages/x-data-grid/src/models/api/gridStateApi.ts index a1c88c022edec..40a0cc98e7659 100644 --- a/packages/x-data-grid/src/models/api/gridStateApi.ts +++ b/packages/x-data-grid/src/models/api/gridStateApi.ts @@ -9,6 +9,7 @@ export interface GridStateApi { state: State; /** * Forces the grid to rerender. It's often used after a state update. + * @deprecated no longer needed. */ forceUpdate: () => void; /** diff --git a/packages/x-data-grid/src/models/events/gridEventLookup.ts b/packages/x-data-grid/src/models/events/gridEventLookup.ts index 80198a32f8481..9f37ed2ede1d4 100644 --- a/packages/x-data-grid/src/models/events/gridEventLookup.ts +++ b/packages/x-data-grid/src/models/events/gridEventLookup.ts @@ -399,6 +399,10 @@ export interface GridEventLookup GridColumnGroupHeaderEventLookup, GridCellEventLookup, GridControlledStateEventLookup { + /** + * Fired when rootElementRef.current becomes available. + */ + rootMount: { params: HTMLElement }; /** * Fired when the grid is unmounted. */ diff --git a/packages/x-internals/src/useResizeObserver/useResizeObserver.ts b/packages/x-internals/src/useResizeObserver/useResizeObserver.ts index 19e4d5bb89a19..452ece1dbe34a 100644 --- a/packages/x-internals/src/useResizeObserver/useResizeObserver.ts +++ b/packages/x-internals/src/useResizeObserver/useResizeObserver.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils'; -const isDevEnvironment = process.env.NODE_ENV === 'development'; +const isDevEnvironment = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test'; const noop = () => {}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27adae5763e45..6fb03bc5b4103 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1000,6 +1000,9 @@ importers: reselect: specifier: ^5.1.1 version: 5.1.1 + use-sync-external-store: + specifier: ^1.0.0 + version: 1.4.0(react@19.0.0) devDependencies: '@mui/internal-test-utils': specifier: ^1.0.26 @@ -1016,6 +1019,9 @@ importers: '@types/prop-types': specifier: ^15.7.14 version: 15.7.14 + '@types/use-sync-external-store': + specifier: ^0.0.6 + version: 0.0.6 react: specifier: ^19.0.0 version: 19.0.0