import * as React from 'react'; import { unstable_debounce as debounce, unstable_ownerDocument as ownerDocument, unstable_useEnhancedEffect as useEnhancedEffect, unstable_ownerWindow as ownerWindow } from '@mui/utils'; import { useGridApiEventHandler, useGridApiOptionHandler } from '../../utils/useGridApiEventHandler'; import { useGridApiMethod } from '../../utils/useGridApiMethod'; import { useGridLogger } from '../../utils/useGridLogger'; import { gridColumnsTotalWidthSelector } from '../columns'; import { gridDensityFactorSelector } from '../density'; import { useGridSelector } from '../../utils'; import { getVisibleRows } from '../../utils/useGridVisibleRows'; import { gridRowsMetaSelector } from '../rows/gridRowsMetaSelector'; import { calculatePinnedRowsHeight } from '../rows/gridRowsUtils'; import { getTotalHeaderHeight } from '../columns/gridColumnsUtils'; const isTestEnvironment = process.env.NODE_ENV === 'test'; const hasScroll = ({ content, container, scrollBarSize }) => { const hasScrollXIfNoYScrollBar = content.width > container.width; const hasScrollYIfNoXScrollBar = content.height > container.height; let hasScrollX = false; let hasScrollY = false; if (hasScrollXIfNoYScrollBar || hasScrollYIfNoXScrollBar) { hasScrollX = hasScrollXIfNoYScrollBar; hasScrollY = content.height + (hasScrollX ? scrollBarSize : 0) > container.height; // We recalculate the scroll x to consider the size of the y scrollbar. if (hasScrollY) { hasScrollX = content.width + scrollBarSize > container.width; } } return { hasScrollX, hasScrollY }; }; export function useGridDimensions(apiRef, props) { const logger = useGridLogger(apiRef, 'useResizeContainer'); const errorShown = React.useRef(false); const rootDimensionsRef = React.useRef(null); const fullDimensionsRef = React.useRef(null); const rowsMeta = useGridSelector(apiRef, gridRowsMetaSelector); const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector); const rowHeight = Math.floor(props.rowHeight * densityFactor); const totalHeaderHeight = getTotalHeaderHeight(apiRef, props.columnHeaderHeight); const updateGridDimensionsRef = React.useCallback(() => { var _apiRef$current$rootE; const rootElement = (_apiRef$current$rootE = apiRef.current.rootElementRef) == null ? void 0 : _apiRef$current$rootE.current; const columnsTotalWidth = gridColumnsTotalWidthSelector(apiRef); const pinnedRowsHeight = calculatePinnedRowsHeight(apiRef); if (!rootDimensionsRef.current) { return; } let scrollBarSize; if (props.scrollbarSize != null) { scrollBarSize = props.scrollbarSize; } else if (!columnsTotalWidth || !rootElement) { scrollBarSize = 0; } else { const doc = ownerDocument(rootElement); const scrollDiv = doc.createElement('div'); scrollDiv.style.width = '99px'; scrollDiv.style.height = '99px'; scrollDiv.style.position = 'absolute'; scrollDiv.style.overflow = 'scroll'; scrollDiv.className = 'scrollDiv'; rootElement.appendChild(scrollDiv); scrollBarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth; rootElement.removeChild(scrollDiv); } let viewportOuterSize; let hasScrollX; let hasScrollY; if (props.autoHeight) { hasScrollY = false; hasScrollX = Math.round(columnsTotalWidth) > Math.round(rootDimensionsRef.current.width); viewportOuterSize = { width: rootDimensionsRef.current.width, height: rowsMeta.currentPageTotalHeight + (hasScrollX ? scrollBarSize : 0) }; } else { viewportOuterSize = { width: rootDimensionsRef.current.width, height: Math.max(rootDimensionsRef.current.height - totalHeaderHeight, 0) }; const scrollInformation = hasScroll({ content: { width: Math.round(columnsTotalWidth), height: rowsMeta.currentPageTotalHeight }, container: { width: Math.round(viewportOuterSize.width), height: viewportOuterSize.height - pinnedRowsHeight.top - pinnedRowsHeight.bottom }, scrollBarSize }); hasScrollY = scrollInformation.hasScrollY; hasScrollX = scrollInformation.hasScrollX; } const viewportInnerSize = { width: viewportOuterSize.width - (hasScrollY ? scrollBarSize : 0), height: viewportOuterSize.height - (hasScrollX ? scrollBarSize : 0) }; const newFullDimensions = { viewportOuterSize, viewportInnerSize, hasScrollX, hasScrollY, scrollBarSize }; const prevDimensions = fullDimensionsRef.current; fullDimensionsRef.current = newFullDimensions; if (newFullDimensions.viewportInnerSize.width !== (prevDimensions == null ? void 0 : prevDimensions.viewportInnerSize.width) || newFullDimensions.viewportInnerSize.height !== (prevDimensions == null ? void 0 : prevDimensions.viewportInnerSize.height)) { apiRef.current.publishEvent('viewportInnerSizeChange', newFullDimensions.viewportInnerSize); } }, [apiRef, props.scrollbarSize, props.autoHeight, rowsMeta.currentPageTotalHeight, totalHeaderHeight]); const [savedSize, setSavedSize] = React.useState(); const debouncedSetSavedSize = React.useMemo(() => debounce(setSavedSize, 60), []); const previousSize = React.useRef(); useEnhancedEffect(() => { if (savedSize) { updateGridDimensionsRef(); apiRef.current.publishEvent('debouncedResize', rootDimensionsRef.current); } }, [apiRef, savedSize, updateGridDimensionsRef]); // This is the function called by apiRef.current.resize() const resize = React.useCallback(() => { apiRef.current.computeSizeAndPublishResizeEvent(); }, [apiRef]); const getRootDimensions = React.useCallback(() => fullDimensionsRef.current, []); const getViewportPageSize = React.useCallback(() => { const dimensions = apiRef.current.getRootDimensions(); if (!dimensions) { return 0; } const currentPage = getVisibleRows(apiRef, { pagination: props.pagination, paginationMode: props.paginationMode }); // 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 if (props.getRowHeight) { const renderContext = apiRef.current.getRenderContext(); const viewportPageSize = renderContext.lastRowIndex - renderContext.firstRowIndex; return Math.min(viewportPageSize - 1, currentPage.rows.length); } const maximumPageSizeWithoutScrollBar = Math.floor(dimensions.viewportInnerSize.height / rowHeight); return Math.min(maximumPageSizeWithoutScrollBar, currentPage.rows.length); }, [apiRef, props.pagination, props.paginationMode, props.getRowHeight, rowHeight]); const computeSizeAndPublishResizeEvent = React.useCallback(() => { var _apiRef$current$mainE, _previousSize$current, _previousSize$current2; const mainEl = (_apiRef$current$mainE = apiRef.current.mainElementRef) == null ? void 0 : _apiRef$current$mainE.current; if (!mainEl) { return; } const win = ownerWindow(mainEl); const computedStyle = win.getComputedStyle(mainEl); const height = parseFloat(computedStyle.height) || 0; const width = parseFloat(computedStyle.width) || 0; const hasHeightChanged = height !== ((_previousSize$current = previousSize.current) == null ? void 0 : _previousSize$current.height); const hasWidthChanged = width !== ((_previousSize$current2 = previousSize.current) == null ? void 0 : _previousSize$current2.width); if (!previousSize.current || hasHeightChanged || hasWidthChanged) { const size = { width, height }; apiRef.current.publishEvent('resize', size); previousSize.current = size; } }, [apiRef]); const dimensionsApi = { resize, getRootDimensions }; const dimensionsPrivateApi = { getViewportPageSize, updateGridDimensionsRef, computeSizeAndPublishResizeEvent }; useGridApiMethod(apiRef, dimensionsApi, 'public'); useGridApiMethod(apiRef, dimensionsPrivateApi, 'private'); const isFirstSizing = React.useRef(true); const handleResize = React.useCallback(size => { rootDimensionsRef.current = size; // jsdom has no layout capabilities const isJSDOM = /jsdom/.test(window.navigator.userAgent); if (size.height === 0 && !errorShown.current && !props.autoHeight && !isJSDOM) { logger.error(['The parent DOM element of the data grid has an empty height.', 'Please make sure that this element has an intrinsic height.', 'The grid displays with a height of 0px.', '', 'More details: https://mui.com/r/x-data-grid-no-dimensions.'].join('\n')); errorShown.current = true; } if (size.width === 0 && !errorShown.current && !isJSDOM) { logger.error(['The parent DOM element of the data grid has an empty width.', 'Please make sure that this element has an intrinsic width.', 'The grid displays with a width of 0px.', '', 'More details: https://mui.com/r/x-data-grid-no-dimensions.'].join('\n')); errorShown.current = true; } if (isTestEnvironment) { // We don't need to debounce the resize for tests. setSavedSize(size); isFirstSizing.current = false; return; } if (isFirstSizing.current) { // We want to initialize the grid dimensions as soon as possible to avoid flickering setSavedSize(size); isFirstSizing.current = false; return; } debouncedSetSavedSize(size); }, [props.autoHeight, debouncedSetSavedSize, logger]); useEnhancedEffect(() => updateGridDimensionsRef(), [updateGridDimensionsRef]); useGridApiOptionHandler(apiRef, 'sortedRowsSet', updateGridDimensionsRef); useGridApiOptionHandler(apiRef, 'paginationModelChange', updateGridDimensionsRef); useGridApiOptionHandler(apiRef, 'columnsChange', updateGridDimensionsRef); useGridApiEventHandler(apiRef, 'resize', handleResize); useGridApiOptionHandler(apiRef, 'debouncedResize', props.onResize); }