From 45e01cf68561c5b98272c18fdd5fff3d5410c1dc Mon Sep 17 00:00:00 2001 From: Andrew Cherniavskii Date: Mon, 16 Dec 2024 13:55:55 +0100 Subject: [PATCH] [DataGridPro] Use `Set` for `detailPanelExpandedRowIds` (#15835) --- .../master-detail/ControlMasterDetail.js | 2 +- .../master-detail/ControlMasterDetail.tsx | 18 +++--- .../master-detail/LazyLoadingDetailPanel.js | 14 ++-- .../master-detail/LazyLoadingDetailPanel.tsx | 24 +++---- .../data-grid/master-detail/master-detail.md | 4 +- .../DetailPanelExpandCollapseAll.js | 24 ++++--- .../DetailPanelExpandCollapseAll.tsx | 24 ++++--- .../row-recipes/DetailPanelOneExpandedRow.js | 12 ++-- .../row-recipes/DetailPanelOneExpandedRow.tsx | 25 ++++---- .../x/api/data-grid/data-grid-premium.json | 4 +- docs/pages/x/api/data-grid/data-grid-pro.json | 4 +- docs/pages/x/api/data-grid/grid-api.json | 4 +- .../api/data-grid/grid-detail-panel-api.json | 4 +- docs/pages/x/api/data-grid/selectors.json | 8 +-- .../src/DataGridPremium/DataGridPremium.tsx | 4 +- .../src/tests/DataGridPremium.spec.tsx | 2 - .../src/DataGridPro/DataGridPro.tsx | 4 +- .../src/components/GridDetailPanels.tsx | 23 +++---- .../detailPanel/gridDetailPanelInterface.ts | 12 +--- .../detailPanel/gridDetailPanelSelector.ts | 17 ++--- .../gridDetailPanelToggleColDef.tsx | 2 +- .../src/hooks/features/detailPanel/index.ts | 1 - .../detailPanel/useGridDetailPanel.ts | 64 +++++++++---------- .../useGridDetailPanelPreProcessors.ts | 2 +- .../src/models/dataGridProProps.ts | 4 +- .../src/tests/DataGridPro.spec.tsx | 2 - .../tests/detailPanel.DataGridPro.test.tsx | 47 ++++++++------ .../src/typeOverloads/modules.ts | 2 +- .../x-data-grid/src/tests/DataGrid.spec.tsx | 2 - scripts/x-data-grid-premium.exports.json | 1 - scripts/x-data-grid-pro.exports.json | 1 - 31 files changed, 169 insertions(+), 192 deletions(-) diff --git a/docs/data/data-grid/master-detail/ControlMasterDetail.js b/docs/data/data-grid/master-detail/ControlMasterDetail.js index bebc52b7b177..056ce7dac09f 100644 --- a/docs/data/data-grid/master-detail/ControlMasterDetail.js +++ b/docs/data/data-grid/master-detail/ControlMasterDetail.js @@ -11,7 +11,7 @@ import Alert from '@mui/material/Alert'; export default function ControlMasterDetail() { const [detailPanelExpandedRowIds, setDetailPanelExpandedRowIds] = React.useState( - [], + () => new Set(), ); const handleDetailPanelExpandedRowIdsChange = React.useCallback((newIds) => { diff --git a/docs/data/data-grid/master-detail/ControlMasterDetail.tsx b/docs/data/data-grid/master-detail/ControlMasterDetail.tsx index 611e8f8dd434..967619c55552 100644 --- a/docs/data/data-grid/master-detail/ControlMasterDetail.tsx +++ b/docs/data/data-grid/master-detail/ControlMasterDetail.tsx @@ -5,6 +5,7 @@ import { GridColDef, GridRowsProp, GridRowId, + DataGridProProps, } from '@mui/x-data-grid-pro'; import { randomCreatedDate, @@ -15,17 +16,16 @@ import { import Alert from '@mui/material/Alert'; export default function ControlMasterDetail() { - const [detailPanelExpandedRowIds, setDetailPanelExpandedRowIds] = React.useState< - GridRowId[] - >([]); - - const handleDetailPanelExpandedRowIdsChange = React.useCallback( - (newIds: GridRowId[]) => { - setDetailPanelExpandedRowIds(newIds); - }, - [], + const [detailPanelExpandedRowIds, setDetailPanelExpandedRowIds] = React.useState( + () => new Set(), ); + const handleDetailPanelExpandedRowIdsChange = React.useCallback< + NonNullable + >((newIds) => { + setDetailPanelExpandedRowIds(newIds); + }, []); + return ( diff --git a/docs/data/data-grid/master-detail/LazyLoadingDetailPanel.js b/docs/data/data-grid/master-detail/LazyLoadingDetailPanel.js index 7e136821589c..b09e70d776ab 100644 --- a/docs/data/data-grid/master-detail/LazyLoadingDetailPanel.js +++ b/docs/data/data-grid/master-detail/LazyLoadingDetailPanel.js @@ -45,6 +45,7 @@ function DetailPanelContent({ row: rowProp }) { // Store the data in cache so that when detail panel unmounts due to virtualization, the data is not lost detailPanelDataCache.set(rowProp.id, response); } + const result = detailPanelDataCache.get(rowProp.id); if (!isMounted) { @@ -125,14 +126,11 @@ export default function LazyLoadingDetailPanel() { const handleDetailPanelExpansionChange = React.useCallback( (newExpandedRowIds) => { // Only keep cached data for detail panels that are still expanded - const preservedEntries = newExpandedRowIds.map((id) => [ - id, - detailPanelDataCache.get(id), - ]); - detailPanelDataCache.clear(); - preservedEntries.forEach( - ([id, value]) => value && detailPanelDataCache.set(id, value), - ); + for (const [id] of detailPanelDataCache) { + if (!newExpandedRowIds.has(id)) { + detailPanelDataCache.delete(id); + } + } }, [detailPanelDataCache], ); diff --git a/docs/data/data-grid/master-detail/LazyLoadingDetailPanel.tsx b/docs/data/data-grid/master-detail/LazyLoadingDetailPanel.tsx index 2fbc032ed70f..53c9dbb7c5a1 100644 --- a/docs/data/data-grid/master-detail/LazyLoadingDetailPanel.tsx +++ b/docs/data/data-grid/master-detail/LazyLoadingDetailPanel.tsx @@ -12,9 +12,11 @@ import { randomTraderName, randomId, } from '@mui/x-data-grid-generator'; -import { DataGridProps } from '@mui/x-data-grid'; +import { DataGridProps, GridRowId } from '@mui/x-data-grid'; -const DetailPanelDataCache = React.createContext>(new Map()); +type Products = Awaited>; + +const DetailPanelDataCache = React.createContext(new Map()); async function getProducts(orderId: Customer['id']) { await new Promise((resolve) => { @@ -48,7 +50,8 @@ function DetailPanelContent({ row: rowProp }: { row: Customer }) { // Store the data in cache so that when detail panel unmounts due to virtualization, the data is not lost detailPanelDataCache.set(rowProp.id, response); } - const result = detailPanelDataCache.get(rowProp.id); + + const result = detailPanelDataCache.get(rowProp.id)!; if (!isMounted) { return; @@ -127,21 +130,18 @@ const getDetailPanelContent: DataGridProps['getDetailPanelContent'] = (params) = const getDetailPanelHeight = () => 240; export default function LazyLoadingDetailPanel() { - const detailPanelDataCache = React.useRef(new Map()).current; + const detailPanelDataCache = React.useRef(new Map()).current; const handleDetailPanelExpansionChange = React.useCallback< NonNullable >( (newExpandedRowIds) => { // Only keep cached data for detail panels that are still expanded - const preservedEntries = newExpandedRowIds.map((id) => [ - id, - detailPanelDataCache.get(id), - ]); - detailPanelDataCache.clear(); - preservedEntries.forEach( - ([id, value]) => value && detailPanelDataCache.set(id, value), - ); + for (const [id] of detailPanelDataCache) { + if (!newExpandedRowIds.has(id)) { + detailPanelDataCache.delete(id); + } + } }, [detailPanelDataCache], ); diff --git a/docs/data/data-grid/master-detail/master-detail.md b/docs/data/data-grid/master-detail/master-detail.md index d2366d51cdc0..495fa8d62601 100644 --- a/docs/data/data-grid/master-detail/master-detail.md +++ b/docs/data/data-grid/master-detail/master-detail.md @@ -66,13 +66,13 @@ The following example demonstrates this option in action: ## Controlling expanded detail panels -To control which rows are expanded, pass a list of row IDs to the `detailPanelExpandedRowIds` prop. +To control which rows are expanded, pass a set of row IDs to the `detailPanelExpandedRowIds` prop. Passing a callback to the `onDetailPanelExpandedRowIds` prop can be used to detect when a row gets expanded or collapsed. On the other hand, if you only want to initialize the Data Grid with some rows already expanded, use the `initialState` prop as follows: ```tsx - + ``` {{"demo": "ControlMasterDetail.js", "bg": "inline", "defaultCodeOpen": false}} diff --git a/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.js b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.js index cd48306cf1ba..678cc14df5af 100644 --- a/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.js +++ b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.js @@ -51,17 +51,23 @@ function CustomDetailPanelHeader() { gridDetailPanelExpandedRowsContentCacheSelector, ); - const noDetailPanelsOpen = expandedRowIds.length === 0; + const noDetailPanelsOpen = expandedRowIds.size === 0; const expandOrCollapseAll = () => { - const dataRowIdToModelLookup = gridRowsLookupSelector(apiRef); - const allRowIdsWithDetailPanels = Object.keys(rowsWithDetailPanels).map((key) => - apiRef.current.getRowId(dataRowIdToModelLookup[key]), - ); - - apiRef.current.setExpandedDetailPanels( - noDetailPanelsOpen ? allRowIdsWithDetailPanels : [], - ); + if (noDetailPanelsOpen) { + const dataRowIdToModelLookup = gridRowsLookupSelector(apiRef); + const allRowIdsWithDetailPanels = new Set(); + for (const key in rowsWithDetailPanels) { + if (rowsWithDetailPanels.hasOwnProperty(key)) { + allRowIdsWithDetailPanels.add( + apiRef.current.getRowId(dataRowIdToModelLookup[key]), + ); + } + } + apiRef.current.setExpandedDetailPanels(allRowIdsWithDetailPanels); + } else { + apiRef.current.setExpandedDetailPanels(new Set()); + } }; const Icon = noDetailPanelsOpen ? UnfoldMoreIcon : UnfoldLessIcon; diff --git a/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx index a85385042f8c..44b17c56ca3e 100644 --- a/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx +++ b/docs/data/data-grid/row-recipes/DetailPanelExpandCollapseAll.tsx @@ -55,17 +55,23 @@ function CustomDetailPanelHeader() { gridDetailPanelExpandedRowsContentCacheSelector, ); - const noDetailPanelsOpen = expandedRowIds.length === 0; + const noDetailPanelsOpen = expandedRowIds.size === 0; const expandOrCollapseAll = () => { - const dataRowIdToModelLookup = gridRowsLookupSelector(apiRef); - const allRowIdsWithDetailPanels: GridRowId[] = Object.keys( - rowsWithDetailPanels, - ).map((key) => apiRef.current.getRowId(dataRowIdToModelLookup[key])); - - apiRef.current.setExpandedDetailPanels( - noDetailPanelsOpen ? allRowIdsWithDetailPanels : [], - ); + if (noDetailPanelsOpen) { + const dataRowIdToModelLookup = gridRowsLookupSelector(apiRef); + const allRowIdsWithDetailPanels = new Set(); + for (const key in rowsWithDetailPanels) { + if (rowsWithDetailPanels.hasOwnProperty(key)) { + allRowIdsWithDetailPanels.add( + apiRef.current.getRowId(dataRowIdToModelLookup[key]), + ); + } + } + apiRef.current.setExpandedDetailPanels(allRowIdsWithDetailPanels); + } else { + apiRef.current.setExpandedDetailPanels(new Set()); + } }; const Icon = noDetailPanelsOpen ? UnfoldMoreIcon : UnfoldLessIcon; diff --git a/docs/data/data-grid/row-recipes/DetailPanelOneExpandedRow.js b/docs/data/data-grid/row-recipes/DetailPanelOneExpandedRow.js index 22070963b4fc..af4c0f548d71 100644 --- a/docs/data/data-grid/row-recipes/DetailPanelOneExpandedRow.js +++ b/docs/data/data-grid/row-recipes/DetailPanelOneExpandedRow.js @@ -15,13 +15,17 @@ const getDetailPanelHeight = () => 50; export default function DetailPanelOneExpandedRow() { const [detailPanelExpandedRowIds, setDetailPanelExpandedRowIds] = React.useState( - [], + () => new Set(), ); const handleDetailPanelExpandedRowIdsChange = React.useCallback((newIds) => { - setDetailPanelExpandedRowIds( - newIds.length > 1 ? [newIds[newIds.length - 1]] : newIds, - ); + if (newIds.size > 1) { + const newSet = new Set(); + const newIdsArray = Array.from(newIds); + newSet.add(newIdsArray[newIdsArray.length - 1]); + } else { + setDetailPanelExpandedRowIds(newIds); + } }, []); return ( diff --git a/docs/data/data-grid/row-recipes/DetailPanelOneExpandedRow.tsx b/docs/data/data-grid/row-recipes/DetailPanelOneExpandedRow.tsx index 79f15166d5bd..384b6f1b8dff 100644 --- a/docs/data/data-grid/row-recipes/DetailPanelOneExpandedRow.tsx +++ b/docs/data/data-grid/row-recipes/DetailPanelOneExpandedRow.tsx @@ -20,19 +20,22 @@ const getDetailPanelContent: DataGridProProps['getDetailPanelContent'] = ({ const getDetailPanelHeight: DataGridProProps['getDetailPanelHeight'] = () => 50; export default function DetailPanelOneExpandedRow() { - const [detailPanelExpandedRowIds, setDetailPanelExpandedRowIds] = React.useState< - GridRowId[] - >([]); - - const handleDetailPanelExpandedRowIdsChange = React.useCallback( - (newIds: GridRowId[]) => { - setDetailPanelExpandedRowIds( - newIds.length > 1 ? [newIds[newIds.length - 1]] : newIds, - ); - }, - [], + const [detailPanelExpandedRowIds, setDetailPanelExpandedRowIds] = React.useState( + () => new Set(), ); + const handleDetailPanelExpandedRowIdsChange = React.useCallback< + NonNullable + >((newIds) => { + if (newIds.size > 1) { + const newSet = new Set(); + const newIdsArray = Array.from(newIds); + newSet.add(newIdsArray[newIdsArray.length - 1]); + } else { + setDetailPanelExpandedRowIds(newIds); + } + }, []); + return ( | string>" } - }, + "detailPanelExpandedRowIds": { "type": { "name": "instanceOf", "description": "Set" } }, "disableAggregation": { "type": { "name": "bool" }, "default": "false" }, "disableAutosize": { "type": { "name": "bool" }, "default": "false" }, "disableChildrenFiltering": { "type": { "name": "bool" }, "default": "false" }, diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index 1dd61eee2593..25e5f5e23247 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -38,9 +38,7 @@ }, "default": "\"standard\"" }, - "detailPanelExpandedRowIds": { - "type": { "name": "arrayOf", "description": "Array<number
| string>" } - }, + "detailPanelExpandedRowIds": { "type": { "name": "instanceOf", "description": "Set" } }, "disableAutosize": { "type": { "name": "bool" }, "default": "false" }, "disableChildrenFiltering": { "type": { "name": "bool" }, "default": "false" }, "disableChildrenSorting": { "type": { "name": "bool" }, "default": "false" }, diff --git a/docs/pages/x/api/data-grid/grid-api.json b/docs/pages/x/api/data-grid/grid-api.json index 8e11d0b8cc80..923ef0914ff1 100644 --- a/docs/pages/x/api/data-grid/grid-api.json +++ b/docs/pages/x/api/data-grid/grid-api.json @@ -112,7 +112,7 @@ "isPremiumPlan": true }, "getExpandedDetailPanels": { - "type": { "description": "() => GridRowId[]" }, + "type": { "description": "() => Set<GridRowId>" }, "required": true, "isProPlan": true }, @@ -335,7 +335,7 @@ "required": true }, "setExpandedDetailPanels": { - "type": { "description": "(ids: GridRowId[]) => void" }, + "type": { "description": "(ids: Set<GridRowId>) => void" }, "required": true, "isProPlan": true }, diff --git a/docs/pages/x/api/data-grid/grid-detail-panel-api.json b/docs/pages/x/api/data-grid/grid-detail-panel-api.json index 56633cb86b2d..9ae90268bda8 100644 --- a/docs/pages/x/api/data-grid/grid-detail-panel-api.json +++ b/docs/pages/x/api/data-grid/grid-detail-panel-api.json @@ -5,12 +5,12 @@ { "name": "getExpandedDetailPanels", "description": "Returns the rows whose detail panel is open.", - "type": "() => GridRowId[]" + "type": "() => Set" }, { "name": "setExpandedDetailPanels", "description": "Changes which rows to expand the detail panel.", - "type": "(ids: GridRowId[]) => void" + "type": "(ids: Set) => void" }, { "name": "toggleDetailPanel", diff --git a/docs/pages/x/api/data-grid/selectors.json b/docs/pages/x/api/data-grid/selectors.json index 689dce958592..9ca1f13812d2 100644 --- a/docs/pages/x/api/data-grid/selectors.json +++ b/docs/pages/x/api/data-grid/selectors.json @@ -130,7 +130,7 @@ }, { "name": "gridDetailPanelExpandedRowIdsSelector", - "returnType": "GridRowId[]", + "returnType": "Set", "description": "", "supportsApiRef": false }, @@ -140,12 +140,6 @@ "description": "", "supportsApiRef": false }, - { - "name": "gridDetailPanelExpandedRowsHeightCacheSelector", - "returnType": "Record", - "description": "", - "supportsApiRef": true - }, { "name": "gridDimensionsSelector", "returnType": "GridDimensions", diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index aa6044260b24..1cf6e1ce97c2 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -209,9 +209,7 @@ DataGridPremiumRaw.propTypes = { /** * The row ids to show the detail panel. */ - detailPanelExpandedRowIds: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - ), + detailPanelExpandedRowIds: PropTypes /* @typescript-to-proptypes-ignore */.instanceOf(Set), /** * If `true`, aggregation is disabled. * @default false diff --git a/packages/x-data-grid-premium/src/tests/DataGridPremium.spec.tsx b/packages/x-data-grid-premium/src/tests/DataGridPremium.spec.tsx index 8f76012552ef..e3b99769ec9b 100644 --- a/packages/x-data-grid-premium/src/tests/DataGridPremium.spec.tsx +++ b/packages/x-data-grid-premium/src/tests/DataGridPremium.spec.tsx @@ -78,8 +78,6 @@ function ApiRefPrivateMethods() { apiRef.current.applyStrategyProcessor; // @ts-expect-error Property 'storeDetailPanelHeight' does not exist on type 'GridApiPremium' apiRef.current.storeDetailPanelHeight; - // @ts-expect-error Property 'detailPanelHasAutoHeight' does not exist on type 'GridApiPremium' - apiRef.current.detailPanelHasAutoHeight; // @ts-expect-error Property 'calculateColSpan' does not exist on type 'GridApiPremium' apiRef.current.calculateColSpan; // @ts-expect-error Property 'rowHasAutoHeight' does not exist on type 'GridApiPremium' diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index e8d57f00d5c7..bddd8a86313f 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -187,9 +187,7 @@ DataGridProRaw.propTypes = { /** * The row ids to show the detail panel. */ - detailPanelExpandedRowIds: PropTypes.arrayOf( - PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, - ), + detailPanelExpandedRowIds: PropTypes /* @typescript-to-proptypes-ignore */.instanceOf(Set), /** * If `true`, column autosizing on header separator double-click is disabled. * @default false diff --git a/packages/x-data-grid-pro/src/components/GridDetailPanels.tsx b/packages/x-data-grid-pro/src/components/GridDetailPanels.tsx index a1bdd1cf006b..88f81f32c6e7 100644 --- a/packages/x-data-grid-pro/src/components/GridDetailPanels.tsx +++ b/packages/x-data-grid-pro/src/components/GridDetailPanels.tsx @@ -6,10 +6,10 @@ import { useGridPrivateApiContext } from '../hooks/utils/useGridPrivateApiContex import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { gridDetailPanelExpandedRowsContentCacheSelector, - gridDetailPanelExpandedRowsHeightCacheSelector, gridDetailPanelExpandedRowIdsSelector, } from '../hooks/features/detailPanel'; import { GridDetailPanel } from './GridDetailPanel'; +import { gridDetailPanelRawHeightCacheSelector } from '../hooks/features/detailPanel/gridDetailPanelSelector'; const useUtilityClasses = () => { const slots = { @@ -36,10 +36,7 @@ function GridDetailPanelsImpl({ virtualScroller }: GridDetailPanelsProps) { apiRef, gridDetailPanelExpandedRowsContentCacheSelector, ); - const detailPanelsHeights = useGridSelector( - apiRef, - gridDetailPanelExpandedRowsHeightCacheSelector, - ); + const detailPanelsHeights = useGridSelector(apiRef, gridDetailPanelRawHeightCacheSelector); const getDetailPanel = React.useCallback( (rowId: GridRowId): React.ReactNode => { @@ -53,8 +50,8 @@ function GridDetailPanelsImpl({ virtualScroller }: GridDetailPanelsProps) { return null; } - const hasAutoHeight = apiRef.current.detailPanelHasAutoHeight(rowId); - const height = hasAutoHeight ? 'auto' : detailPanelsHeights[rowId]; + const heightCache = detailPanelsHeights[rowId]; + const height = heightCache.autoHeight ? 'auto' : heightCache.height; return ( { - if (expandedRowIds.length === 0) { + if (expandedRowIds.size === 0) { setPanels(EMPTY_DETAIL_PANELS); } else { - setPanels( - new Map( - expandedRowIds.map((rowId) => [rowId, getDetailPanel(rowId)]), - ), - ); + const map = new Map(); + for (const rowId of expandedRowIds) { + map.set(rowId, getDetailPanel(rowId)); + } + setPanels(map); } }, [expandedRowIds, setPanels, getDetailPanel]); diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelInterface.ts b/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelInterface.ts index 63840ecada20..29f5ea37c5e7 100644 --- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelInterface.ts +++ b/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelInterface.ts @@ -16,12 +16,12 @@ export interface GridDetailPanelApi { * Returns the rows whose detail panel is open. * @returns {GridRowId[]} An array of row ids. */ - getExpandedDetailPanels: () => GridRowId[]; + getExpandedDetailPanels: () => Set; /** * Changes which rows to expand the detail panel. * @param {GridRowId[]} ids The ids of the rows to open the detail panel. */ - setExpandedDetailPanels: (ids: GridRowId[]) => void; + setExpandedDetailPanels: (ids: Set) => void; } export interface GridDetailPanelPrivateApi { @@ -31,16 +31,10 @@ export interface GridDetailPanelPrivateApi { * @param {number} height The new height. */ storeDetailPanelHeight: (id: GridRowId, height: number) => void; - /** - * Determines if the height of a detail panel is "auto". - * @param {GridRowId} id The id of the row. - * @returns {boolean} `true` if the detail panel height is "auto". - */ - detailPanelHasAutoHeight: (id: GridRowId) => boolean; } export interface GridDetailPanelState { - expandedRowIds: GridRowId[]; + expandedRowIds: Set; contentCache: Record; heightCache: DetailPanelHeightCache; } diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelSelector.ts b/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelSelector.ts index c8b7d63a3940..8be957531485 100644 --- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelSelector.ts +++ b/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelSelector.ts @@ -1,22 +1,15 @@ -import { GridRowId } from '@mui/x-data-grid'; import { createSelectorMemoized } from '@mui/x-data-grid/internals'; import { GridStatePro } from '../../../models/gridStatePro'; +const gridDetailPanelStateSelector = (state: GridStatePro) => state.detailPanel; + export const gridDetailPanelExpandedRowIdsSelector = (state: GridStatePro) => state.detailPanel.expandedRowIds; export const gridDetailPanelExpandedRowsContentCacheSelector = (state: GridStatePro) => state.detailPanel.contentCache; -export const gridDetailPanelRawHeightCacheSelector = (state: GridStatePro) => - state.detailPanel.heightCache; - -// TODO v6: Make this selector return the full object, including the autoHeight flag -export const gridDetailPanelExpandedRowsHeightCacheSelector = createSelectorMemoized( - gridDetailPanelRawHeightCacheSelector, - (heightCache) => - Object.entries(heightCache).reduce>((acc, [id, { height }]) => { - acc[id] = height || 0; - return acc; - }, {}), +export const gridDetailPanelRawHeightCacheSelector = createSelectorMemoized( + gridDetailPanelStateSelector, + (detailPanelState) => detailPanelState.heightCache, ); diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx b/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx index 16998e55615f..b5d22107c0dd 100644 --- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx +++ b/packages/x-data-grid-pro/src/hooks/features/detailPanel/gridDetailPanelToggleColDef.tsx @@ -27,7 +27,7 @@ export const GRID_DETAIL_PANEL_TOGGLE_COL_DEF: GridColDef = { const expandedRowIds = gridDetailPanelExpandedRowIdsSelector( (apiRef.current as GridApiPro).state, ); - return expandedRowIds.includes(rowId); + return expandedRowIds.has(rowId); }, renderCell: (params) => , renderHeader: ({ colDef }) =>
, diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/index.ts b/packages/x-data-grid-pro/src/hooks/features/detailPanel/index.ts index 9499c717b5c4..8628f27b0b72 100644 --- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/index.ts +++ b/packages/x-data-grid-pro/src/hooks/features/detailPanel/index.ts @@ -2,6 +2,5 @@ export * from './gridDetailPanelToggleColDef'; export { gridDetailPanelExpandedRowIdsSelector, gridDetailPanelExpandedRowsContentCacheSelector, - gridDetailPanelExpandedRowsHeightCacheSelector, } from './gridDetailPanelSelector'; export * from './gridDetailPanelInterface'; diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts b/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts index 2efc3fbe9250..aed2941bc56a 100644 --- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts +++ b/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanel.ts @@ -18,7 +18,6 @@ import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from './gridDetailPanelToggleColDef'; import { gridDetailPanelExpandedRowIdsSelector, gridDetailPanelExpandedRowsContentCacheSelector, - gridDetailPanelExpandedRowsHeightCacheSelector, gridDetailPanelRawHeightCacheSelector, } from './gridDetailPanelSelector'; import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; @@ -31,6 +30,8 @@ import { // FIXME: calling `api.updateDimensions()` here triggers a cycle where `updateDimensions` is // called 3 times when opening/closing a panel. +const emptySet = new Set(); + export const detailPanelStateInitializer: GridStateInitializer< Pick > = (state, props) => { @@ -39,7 +40,9 @@ export const detailPanelStateInitializer: GridStateInitializer< detailPanel: { heightCache: {}, expandedRowIds: - props.detailPanelExpandedRowIds ?? props.initialState?.detailPanel?.expandedRowIds ?? [], + props.detailPanelExpandedRowIds ?? + props.initialState?.detailPanel?.expandedRowIds ?? + emptySet, }, }; }; @@ -57,25 +60,23 @@ function cacheContentAndHeight( // TODO change to lazy approach using a Proxy // only call getDetailPanelContent when asked for an id const rowIds = gridDataRowIdsSelector(apiRef); - const contentCache = rowIds.reduce>>( - (acc, id) => { - const params = apiRef.current.getRowParams(id); - acc[id] = getDetailPanelContent(params); - return acc; - }, - {}, - ); - const heightCache = rowIds.reduce((acc, id) => { - if (contentCache[id] == null) { - return acc; - } + const contentCache: Record> = {}; + const heightCache: GridDetailPanelState['heightCache'] = {}; + + for (let i = 0; i < rowIds.length; i += 1) { + const id = rowIds[i]; const params = apiRef.current.getRowParams(id); + const content = getDetailPanelContent(params); + contentCache[id] = content; + + if (content == null) { + continue; + } const height = getDetailPanelHeight(params); const autoHeight = height === 'auto'; - acc[id] = { autoHeight, height: autoHeight ? previousHeightCache[id]?.height : height }; - return acc; - }, {}); + heightCache[id] = { autoHeight, height: autoHeight ? previousHeightCache[id]?.height : height }; + } return { contentCache, heightCache }; } @@ -150,9 +151,13 @@ export const useGridDetailPanel = ( } const ids = apiRef.current.getExpandedDetailPanels(); - apiRef.current.setExpandedDetailPanels( - ids.includes(id) ? ids.filter((rowId) => rowId !== id) : [...ids, id], - ); + const newIds = new Set(ids); + if (ids.has(id)) { + newIds.delete(id); + } else { + newIds.add(id); + } + apiRef.current.setExpandedDetailPanels(newIds); }, [apiRef, contentCache, props.getDetailPanelContent], ); @@ -183,7 +188,7 @@ export const useGridDetailPanel = ( GridDetailPanelPrivateApi['storeDetailPanelHeight'] >( (id, height) => { - const heightCache = gridDetailPanelRawHeightCacheSelector(apiRef.current.state); + const heightCache = gridDetailPanelRawHeightCacheSelector(apiRef); if (!heightCache[id] || heightCache[id].height === height) { return; @@ -205,16 +210,6 @@ export const useGridDetailPanel = ( [apiRef], ); - const detailPanelHasAutoHeight = React.useCallback< - GridDetailPanelPrivateApi['detailPanelHasAutoHeight'] - >( - (id) => { - const heightCache = gridDetailPanelRawHeightCacheSelector(apiRef.current.state); - return heightCache[id] ? heightCache[id].autoHeight : false; - }, - [apiRef], - ); - const detailPanelPubicApi: GridDetailPanelApi = { toggleDetailPanel, getExpandedDetailPanels, @@ -223,7 +218,6 @@ export const useGridDetailPanel = ( const detailPanelPrivateApi: GridDetailPanelPrivateApi = { storeDetailPanelHeight, - detailPanelHasAutoHeight, }; useGridApiMethod(apiRef, detailPanelPubicApi, 'public'); @@ -294,16 +288,16 @@ export const useGridDetailPanel = ( const addDetailHeight = React.useCallback>( (initialValue, row) => { - if (!expandedRowIds || expandedRowIds.length === 0 || !expandedRowIds.includes(row.id)) { + if (!expandedRowIds || expandedRowIds.size === 0 || !expandedRowIds.has(row.id)) { initialValue.detail = 0; return initialValue; } updateCachesIfNeeded(); - const heightCache = gridDetailPanelExpandedRowsHeightCacheSelector(apiRef); + const heightCache = gridDetailPanelRawHeightCacheSelector(apiRef); - initialValue.detail = heightCache[row.id] ?? 0; // Fallback to zero because the cache might not be ready yet (for example page was changed) + initialValue.detail = heightCache[row.id].height ?? 0; // Fallback to zero because the cache might not be ready yet (for example page was changed) return initialValue; }, [apiRef, expandedRowIds, updateCachesIfNeeded], diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanelPreProcessors.ts b/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanelPreProcessors.ts index 4fb594b672b8..35f67561fbde 100644 --- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanelPreProcessors.ts +++ b/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanelPreProcessors.ts @@ -50,7 +50,7 @@ export const useGridDetailPanelPreProcessors = ( } const expandedRowIds = gridDetailPanelExpandedRowIdsSelector(privateApiRef.current.state); - if (!expandedRowIds.includes(id)) { + if (!expandedRowIds.has(id)) { return classes; } diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index af091b4e9340..a81cd6c6d702 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -229,13 +229,13 @@ export interface DataGridProPropsWithoutDefaultValue; /** * Callback fired when the detail panel of a row is opened or closed. * @param {GridRowId[]} ids The ids of the rows which have the detail panel open. * @param {GridCallbackDetails} details Additional details for this callback. */ - onDetailPanelExpandedRowIdsChange?: (ids: GridRowId[], details: GridCallbackDetails) => void; + onDetailPanelExpandedRowIdsChange?: (ids: Set, details: GridCallbackDetails) => void; /** * Function that returns the element to render in row detail. * @param {GridRowParams} params With all properties from [[GridRowParams]]. diff --git a/packages/x-data-grid-pro/src/tests/DataGridPro.spec.tsx b/packages/x-data-grid-pro/src/tests/DataGridPro.spec.tsx index 381641537a0e..593ae07a0fbc 100644 --- a/packages/x-data-grid-pro/src/tests/DataGridPro.spec.tsx +++ b/packages/x-data-grid-pro/src/tests/DataGridPro.spec.tsx @@ -82,8 +82,6 @@ function ApiRefPrivateMethods() { apiRef.current.applyStrategyProcessor; // @ts-expect-error Property 'storeDetailPanelHeight' does not exist on type 'GridApiPro' apiRef.current.storeDetailPanelHeight; - // @ts-expect-error Property 'detailPanelHasAutoHeight' does not exist on type 'GridApiPro' - apiRef.current.detailPanelHasAutoHeight; // @ts-expect-error Property 'calculateColSpan' does not exist on type 'GridApiPro' apiRef.current.calculateColSpan; // @ts-expect-error Property 'rowHasAutoHeight' does not exist on type 'GridApiPro' 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 115a7b69feeb..13a12b420276 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 @@ -63,7 +63,7 @@ describe(' - Detail panel', () => { rowBufferPx={0} initialState={{ detailPanel: { - expandedRowIds: [0, 1], + expandedRowIds: new Set([0, 1]), }, }} />, @@ -173,7 +173,7 @@ describe(' - Detail panel', () => { rowHeight={rowHeight} initialState={{ detailPanel: { - expandedRowIds: [0, 1], + expandedRowIds: new Set([0, 1]), }, }} />, @@ -194,7 +194,7 @@ describe(' - Detail panel', () => { pagination pageSizeOptions={[1]} initialState={{ - detailPanel: { expandedRowIds: [0] }, + detailPanel: { expandedRowIds: new Set([0]) }, pagination: { paginationModel: { pageSize: 1 } }, }} />, @@ -218,7 +218,7 @@ describe(' - Detail panel', () => { columnHeaderHeight={columnHeaderHeight} initialState={{ detailPanel: { - expandedRowIds: [0], + expandedRowIds: new Set([0]), }, }} hideFooter @@ -244,7 +244,7 @@ describe(' - Detail panel', () => { getDetailPanelContent={() =>
} initialState={{ detailPanel: { - expandedRowIds: [0], + expandedRowIds: new Set([0]), }, }} hideFooter @@ -508,10 +508,13 @@ describe(' - Detail panel', () => { return
{counter}
; } const { setProps } = render( - } detailPanelExpandedRowIds={[0]} />, + } + detailPanelExpandedRowIds={new Set([0])} + />, ); expect(screen.getByTestId(`detail-panel-content`).textContent).to.equal(`${counter}`); - setProps({ detailPanelExpandedRowIds: [1] }); + setProps({ detailPanelExpandedRowIds: new Set([1]) }); expect(screen.getByTestId(`detail-panel-content`).textContent).to.equal(`${counter}`); }); @@ -548,13 +551,15 @@ describe(' - Detail panel', () => { />, ); fireEvent.click(screen.getAllByRole('button', { name: 'Expand' })[0]); // Expand the 1st row - expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal([0]); + expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal(new Set([0])); fireEvent.click(screen.getAllByRole('button', { name: 'Expand' })[0]); // Expand the 2nd row - expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal([0, 1]); + expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal( + new Set([0, 1]), + ); fireEvent.click(screen.getAllByRole('button', { name: 'Collapse' })[0]); // Close the 1st row - expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal([1]); + expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal(new Set([1])); fireEvent.click(screen.getAllByRole('button', { name: 'Collapse' })[0]); // Close the 2nd row - expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal([]); + expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal(new Set([])); }); it('should not change the open detail panels when called while detailPanelsExpandedRowIds is the same', () => { @@ -562,13 +567,13 @@ describe(' - Detail panel', () => { render(
Row {id}
} - detailPanelExpandedRowIds={[0]} + detailPanelExpandedRowIds={new Set([0])} onDetailPanelExpandedRowIdsChange={handleDetailPanelsExpandedRowIdsChange} />, ); expect(screen.getByText('Row 0')).not.to.equal(null); fireEvent.click(screen.getByRole('button', { name: 'Collapse' })); - expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal([]); + expect(handleDetailPanelsExpandedRowIdsChange.lastCall.args[0]).to.deep.equal(new Set([])); expect(screen.getByText('Row 0')).not.to.equal(null); }); }); @@ -578,7 +583,7 @@ describe(' - Detail panel', () => { render(
Row {id}
} - detailPanelExpandedRowIds={[0, 1]} + detailPanelExpandedRowIds={new Set([0, 1])} />, ); expect(screen.queryByText('Row 0')).not.to.equal(null); @@ -590,7 +595,7 @@ describe(' - Detail panel', () => { render(
Row {id}
} - detailPanelExpandedRowIds={[0]} + detailPanelExpandedRowIds={new Set([0])} />, ); expect(screen.queryByText('Row 0')).not.to.equal(null); @@ -604,7 +609,7 @@ describe(' - Detail panel', () => { render(
Row {id}
} - detailPanelExpandedRowIds={[0, 0]} + detailPanelExpandedRowIds={new Set([0, 0])} />, ); expect(screen.queryAllByText('Row 0').length).to.equal(1); @@ -645,18 +650,18 @@ describe(' - Detail panel', () => { }); describe('getExpandedDetailPanels', () => { - it('should return an array of ids', () => { + it('should return a set of ids', () => { render(
Detail
} initialState={{ detailPanel: { - expandedRowIds: [0, 1], + expandedRowIds: new Set([0, 1]), }, }} />, ); - act(() => expect(apiRef.current.getExpandedDetailPanels()).to.deep.equal([0, 1])); + act(() => expect(apiRef.current.getExpandedDetailPanels()).to.deep.equal(new Set([0, 1]))); }); }); @@ -667,7 +672,7 @@ describe(' - Detail panel', () => { getDetailPanelContent={({ id }) =>
Row {id}
} initialState={{ detailPanel: { - expandedRowIds: [0], + expandedRowIds: new Set([0]), }, }} />, @@ -675,7 +680,7 @@ describe(' - Detail panel', () => { expect(screen.queryByText('Row 0')).not.to.equal(null); expect(screen.queryByText('Row 1')).to.equal(null); expect(screen.queryByText('Row 2')).to.equal(null); - act(() => apiRef.current.setExpandedDetailPanels([1, 2])); + act(() => apiRef.current.setExpandedDetailPanels(new Set([1, 2]))); expect(screen.queryByText('Row 0')).to.equal(null); expect(screen.queryByText('Row 1')).not.to.equal(null); expect(screen.queryByText('Row 2')).not.to.equal(null); diff --git a/packages/x-data-grid-pro/src/typeOverloads/modules.ts b/packages/x-data-grid-pro/src/typeOverloads/modules.ts index 430f21d777f4..c297b07a8f16 100644 --- a/packages/x-data-grid-pro/src/typeOverloads/modules.ts +++ b/packages/x-data-grid-pro/src/typeOverloads/modules.ts @@ -23,7 +23,7 @@ export interface GridControlledStateEventLookupPro { * Fired when the open detail panels are changed. * @ignore - do not document. */ - detailPanelsExpandedRowIdsChange: { params: GridRowId[] }; + detailPanelsExpandedRowIdsChange: { params: Set }; /** * Fired when the pinned columns is changed. * @ignore - do not document. diff --git a/packages/x-data-grid/src/tests/DataGrid.spec.tsx b/packages/x-data-grid/src/tests/DataGrid.spec.tsx index 5eff934863a0..a4622c5e2a16 100644 --- a/packages/x-data-grid/src/tests/DataGrid.spec.tsx +++ b/packages/x-data-grid/src/tests/DataGrid.spec.tsx @@ -219,8 +219,6 @@ function ApiRefPrivateMethods() { apiRef.current.applyStrategyProcessor; // @ts-expect-error Property 'storeDetailPanelHeight' does not exist on type 'GridApiCommunity' apiRef.current.storeDetailPanelHeight; - // @ts-expect-error Property 'detailPanelHasAutoHeight' does not exist on type 'GridApiCommunity' - apiRef.current.detailPanelHasAutoHeight; // @ts-expect-error Property 'calculateColSpan' does not exist on type 'GridApiCommunity' apiRef.current.calculateColSpan; // @ts-expect-error Property 'rowHasAutoHeight' does not exist on type 'GridApiCommunity' diff --git a/scripts/x-data-grid-premium.exports.json b/scripts/x-data-grid-premium.exports.json index 99ca439203b8..9fbabee08cbf 100644 --- a/scripts/x-data-grid-premium.exports.json +++ b/scripts/x-data-grid-premium.exports.json @@ -272,7 +272,6 @@ { "name": "GridDetailPanelApi", "kind": "Interface" }, { "name": "gridDetailPanelExpandedRowIdsSelector", "kind": "Variable" }, { "name": "gridDetailPanelExpandedRowsContentCacheSelector", "kind": "Variable" }, - { "name": "gridDetailPanelExpandedRowsHeightCacheSelector", "kind": "Variable" }, { "name": "GridDetailPanelInitialState", "kind": "TypeAlias" }, { "name": "GridDetailPanelPrivateApi", "kind": "Interface" }, { "name": "GridDetailPanelState", "kind": "Interface" }, diff --git a/scripts/x-data-grid-pro.exports.json b/scripts/x-data-grid-pro.exports.json index 71add8c10f94..285e6817f6e2 100644 --- a/scripts/x-data-grid-pro.exports.json +++ b/scripts/x-data-grid-pro.exports.json @@ -245,7 +245,6 @@ { "name": "GridDetailPanelApi", "kind": "Interface" }, { "name": "gridDetailPanelExpandedRowIdsSelector", "kind": "Variable" }, { "name": "gridDetailPanelExpandedRowsContentCacheSelector", "kind": "Variable" }, - { "name": "gridDetailPanelExpandedRowsHeightCacheSelector", "kind": "Variable" }, { "name": "GridDetailPanelInitialState", "kind": "TypeAlias" }, { "name": "GridDetailPanelPrivateApi", "kind": "Interface" }, { "name": "GridDetailPanelState", "kind": "Interface" },