UNPKG

@mui/x-data-grid-pro

Version:

The Pro plan edition of the Data Grid components (MUI X).

190 lines (187 loc) 8.21 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import * as React from 'react'; import { useGridSelector, useGridApiEventHandler, useGridApiMethod, gridDataRowIdsSelector } from '@mui/x-data-grid'; import { useGridRegisterPipeProcessor } from '@mui/x-data-grid/internals'; import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from './gridDetailPanelToggleColDef'; import { gridDetailPanelExpandedRowIdsSelector, gridDetailPanelExpandedRowsContentCacheSelector, gridDetailPanelExpandedRowsHeightCacheSelector, gridDetailPanelRawHeightCacheSelector } from './gridDetailPanelSelector'; // FIXME: calling `api.updateDimensions()` here triggers a cycle where `updateDimensions` is // called 3 times when opening/closing a panel. export const detailPanelStateInitializer = (state, props) => { return _extends({}, state, { detailPanel: { heightCache: {}, expandedRowIds: props.detailPanelExpandedRowIds ?? props.initialState?.detailPanel?.expandedRowIds ?? [] } }); }; function cacheContentAndHeight(apiRef, getDetailPanelContent, getDetailPanelHeight, previousHeightCache) { if (typeof getDetailPanelContent !== 'function') { return {}; } // 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 params = apiRef.current.getRowParams(id); const height = getDetailPanelHeight(params); const autoHeight = height === 'auto'; acc[id] = { autoHeight, height: autoHeight ? previousHeightCache[id]?.height : height }; return acc; }, {}); return { contentCache, heightCache }; } export const useGridDetailPanel = (apiRef, props) => { const expandedRowIds = useGridSelector(apiRef, gridDetailPanelExpandedRowIdsSelector); const contentCache = useGridSelector(apiRef, gridDetailPanelExpandedRowsContentCacheSelector); const handleCellClick = React.useCallback((params, event) => { if (params.field !== GRID_DETAIL_PANEL_TOGGLE_FIELD || props.getDetailPanelContent == null) { return; } const content = contentCache[params.id]; if (! /*#__PURE__*/React.isValidElement(content)) { return; } // Ignore if the user didn't click specifically in the "i" button if (event.target === event.currentTarget) { return; } apiRef.current.toggleDetailPanel(params.id); }, [apiRef, contentCache, props.getDetailPanelContent]); const handleCellKeyDown = React.useCallback((params, event) => { if (props.getDetailPanelContent == null) { return; } if (params.field === GRID_DETAIL_PANEL_TOGGLE_FIELD && event.key === ' ') { apiRef.current.toggleDetailPanel(params.id); } }, [apiRef, props.getDetailPanelContent]); useGridApiEventHandler(apiRef, 'cellClick', handleCellClick); useGridApiEventHandler(apiRef, 'cellKeyDown', handleCellKeyDown); apiRef.current.registerControlState({ stateId: 'detailPanels', propModel: props.detailPanelExpandedRowIds, propOnChange: props.onDetailPanelExpandedRowIdsChange, stateSelector: gridDetailPanelExpandedRowIdsSelector, changeEvent: 'detailPanelsExpandedRowIdsChange' }); const toggleDetailPanel = React.useCallback(id => { if (props.getDetailPanelContent == null) { return; } const content = contentCache[id]; if (! /*#__PURE__*/React.isValidElement(content)) { return; } const ids = apiRef.current.getExpandedDetailPanels(); apiRef.current.setExpandedDetailPanels(ids.includes(id) ? ids.filter(rowId => rowId !== id) : [...ids, id]); }, [apiRef, contentCache, props.getDetailPanelContent]); const getExpandedDetailPanels = React.useCallback(() => gridDetailPanelExpandedRowIdsSelector(apiRef.current.state), [apiRef]); const setExpandedDetailPanels = React.useCallback(ids => { apiRef.current.setState(state => { return _extends({}, state, { detailPanel: _extends({}, state.detailPanel, { expandedRowIds: ids }) }); }); apiRef.current.updateDimensions(); apiRef.current.forceUpdate(); }, [apiRef]); const storeDetailPanelHeight = React.useCallback((id, height) => { const heightCache = gridDetailPanelRawHeightCacheSelector(apiRef.current.state); if (!heightCache[id] || heightCache[id].height === height) { return; } apiRef.current.setState(state => { return _extends({}, state, { detailPanel: _extends({}, state.detailPanel, { heightCache: _extends({}, heightCache, { [id]: _extends({}, heightCache[id], { height }) }) }) }); }); apiRef.current.updateDimensions(); apiRef.current.requestPipeProcessorsApplication('rowHeight'); }, [apiRef]); const detailPanelHasAutoHeight = React.useCallback(id => { const heightCache = gridDetailPanelRawHeightCacheSelector(apiRef.current.state); return heightCache[id] ? heightCache[id].autoHeight : false; }, [apiRef]); const detailPanelPubicApi = { toggleDetailPanel, getExpandedDetailPanels, setExpandedDetailPanels }; const detailPanelPrivateApi = { storeDetailPanelHeight, detailPanelHasAutoHeight }; useGridApiMethod(apiRef, detailPanelPubicApi, 'public'); useGridApiMethod(apiRef, detailPanelPrivateApi, 'private'); React.useEffect(() => { if (props.detailPanelExpandedRowIds) { const currentModel = gridDetailPanelExpandedRowIdsSelector(apiRef.current.state); if (currentModel !== props.detailPanelExpandedRowIds) { apiRef.current.setExpandedDetailPanels(props.detailPanelExpandedRowIds); } } }, [apiRef, props.detailPanelExpandedRowIds]); const updateCachesAndForceUpdate = React.useCallback(() => { apiRef.current.setState(state => { return _extends({}, state, { detailPanel: _extends({}, state.detailPanel, cacheContentAndHeight(apiRef, props.getDetailPanelContent, props.getDetailPanelHeight, state.detailPanel.heightCache)) }); }); apiRef.current.updateDimensions?.(); apiRef.current.forceUpdate(); }, [apiRef, props.getDetailPanelContent, props.getDetailPanelHeight]); useGridApiEventHandler(apiRef, 'sortedRowsSet', updateCachesAndForceUpdate); const previousGetDetailPanelContentProp = React.useRef(); const previousGetDetailPanelHeightProp = React.useRef(); const updateCachesIfNeeded = React.useCallback(() => { if (props.getDetailPanelContent === previousGetDetailPanelContentProp.current && props.getDetailPanelHeight === previousGetDetailPanelHeightProp.current) { return; } apiRef.current.setState(state => { return _extends({}, state, { detailPanel: _extends({}, state.detailPanel, cacheContentAndHeight(apiRef, props.getDetailPanelContent, props.getDetailPanelHeight, state.detailPanel.heightCache)) }); }); apiRef.current.updateDimensions?.(); previousGetDetailPanelContentProp.current = props.getDetailPanelContent; previousGetDetailPanelHeightProp.current = props.getDetailPanelHeight; }, [apiRef, props.getDetailPanelContent, props.getDetailPanelHeight]); const addDetailHeight = React.useCallback((initialValue, row) => { if (!expandedRowIds || expandedRowIds.length === 0 || !expandedRowIds.includes(row.id)) { initialValue.detail = 0; return initialValue; } updateCachesIfNeeded(); const heightCache = gridDetailPanelExpandedRowsHeightCacheSelector(apiRef); initialValue.detail = heightCache[row.id] ?? 0; // Fallback to zero because the cache might not be ready yet (for example page was changed) return initialValue; }, [apiRef, expandedRowIds, updateCachesIfNeeded]); useGridRegisterPipeProcessor(apiRef, 'rowHeight', addDetailHeight); const isFirstRender = React.useRef(true); if (isFirstRender.current) { isFirstRender.current = false; updateCachesIfNeeded(); } };