UNPKG

@mui/x-data-grid

Version:

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

325 lines (322 loc) 15.4 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default; Object.defineProperty(exports, "__esModule", { value: true }); exports.dimensionsStateInitializer = void 0; exports.useGridDimensions = useGridDimensions; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var React = _interopRequireWildcard(require("react")); var _utils = require("@mui/utils"); var _throttle = require("@mui/x-internals/throttle"); var _useGridApiEventHandler = require("../../utils/useGridApiEventHandler"); var _useGridApiMethod = require("../../utils/useGridApiMethod"); var _createSelector = require("../../../utils/createSelector"); var _useGridLogger = require("../../utils/useGridLogger"); var _columns = require("../columns"); var _gridDimensionsSelectors = require("./gridDimensionsSelectors"); var _density = require("../density"); var _virtualization = require("../virtualization"); var _utils2 = require("../../utils"); var _useGridVisibleRows = require("../../utils/useGridVisibleRows"); var _gridRowsMetaSelector = require("../rows/gridRowsMetaSelector"); var _gridRowsUtils = require("../rows/gridRowsUtils"); var _gridColumnsUtils = require("../columns/gridColumnsUtils"); var _dataGridPropsDefaultValues = require("../../../constants/dataGridPropsDefaultValues"); var _roundToDecimalPlaces = require("../../../utils/roundToDecimalPlaces"); var _isJSDOM = require("../../../utils/isJSDOM"); var _utils3 = require("../../../utils/utils"); const EMPTY_SIZE = { width: 0, height: 0 }; const EMPTY_DIMENSIONS = { isReady: false, root: EMPTY_SIZE, viewportOuterSize: EMPTY_SIZE, viewportInnerSize: EMPTY_SIZE, contentSize: EMPTY_SIZE, minimumSize: EMPTY_SIZE, hasScrollX: false, hasScrollY: false, scrollbarSize: 0, headerHeight: 0, groupHeaderHeight: 0, headerFilterHeight: 0, rowWidth: 0, rowHeight: 0, columnsTotalWidth: 0, leftPinnedWidth: 0, rightPinnedWidth: 0, headersTotalHeight: 0, topContainerHeight: 0, bottomContainerHeight: 0 }; const dimensionsStateInitializer = (state, props, apiRef) => { const dimensions = EMPTY_DIMENSIONS; const density = (0, _density.gridDensityFactorSelector)(apiRef); return (0, _extends2.default)({}, state, { dimensions: (0, _extends2.default)({}, dimensions, getStaticDimensions(props, apiRef, density, (0, _columns.gridVisiblePinnedColumnDefinitionsSelector)(apiRef))) }); }; exports.dimensionsStateInitializer = dimensionsStateInitializer; const columnsTotalWidthSelector = (0, _createSelector.createSelector)(_columns.gridVisibleColumnDefinitionsSelector, _columns.gridColumnPositionsSelector, (visibleColumns, positions) => { const colCount = visibleColumns.length; if (colCount === 0) { return 0; } return (0, _roundToDecimalPlaces.roundToDecimalPlaces)(positions[colCount - 1] + visibleColumns[colCount - 1].computedWidth, 1); }); function useGridDimensions(apiRef, props) { const logger = (0, _useGridLogger.useGridLogger)(apiRef, 'useResizeContainer'); const errorShown = React.useRef(false); const rootDimensionsRef = React.useRef(EMPTY_SIZE); const pinnedColumns = (0, _utils2.useGridSelector)(apiRef, _columns.gridVisiblePinnedColumnDefinitionsSelector); const densityFactor = (0, _utils2.useGridSelector)(apiRef, _density.gridDensityFactorSelector); const columnsTotalWidth = (0, _utils2.useGridSelector)(apiRef, columnsTotalWidthSelector); const isFirstSizing = React.useRef(true); const { rowHeight, headerHeight, groupHeaderHeight, headerFilterHeight, headersTotalHeight, leftPinnedWidth, rightPinnedWidth } = getStaticDimensions(props, apiRef, densityFactor, pinnedColumns); const previousSize = React.useRef(undefined); const getRootDimensions = React.useCallback(() => (0, _gridDimensionsSelectors.gridDimensionsSelector)(apiRef.current.state), [apiRef]); const setDimensions = React.useCallback(dimensions => { apiRef.current.setState(state => (0, _extends2.default)({}, state, { dimensions })); if (apiRef.current.rootElementRef.current) { setCSSVariables(apiRef.current.rootElementRef.current, (0, _gridDimensionsSelectors.gridDimensionsSelector)(apiRef.current.state)); } }, [apiRef]); const resize = React.useCallback(() => { const element = apiRef.current.mainElementRef.current; if (!element) { return; } const computedStyle = (0, _utils.unstable_ownerWindow)(element).getComputedStyle(element); const newSize = { width: parseFloat(computedStyle.width) || 0, height: parseFloat(computedStyle.height) || 0 }; if (!previousSize.current || !areElementSizesEqual(previousSize.current, newSize)) { apiRef.current.publishEvent('resize', newSize); previousSize.current = newSize; } }, [apiRef]); const getViewportPageSize = React.useCallback(() => { const dimensions = (0, _gridDimensionsSelectors.gridDimensionsSelector)(apiRef.current.state); if (!dimensions.isReady) { return 0; } const currentPage = (0, _useGridVisibleRows.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 if (props.getRowHeight) { const renderContext = (0, _virtualization.gridRenderContextSelector)(apiRef); 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.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 scrollbarSize = measureScrollbarSize(apiRef.current.mainElementRef.current, props.scrollbarSize); const rowsMeta = (0, _gridRowsMetaSelector.gridRowsMetaSelector)(apiRef.current.state); const topContainerHeight = headersTotalHeight + rowsMeta.pinnedTopRowsTotalHeight; const bottomContainerHeight = rowsMeta.pinnedBottomRowsTotalHeight; const nonPinnedColumnsTotalWidth = columnsTotalWidth - leftPinnedWidth - rightPinnedWidth; const contentSize = { width: nonPinnedColumnsTotalWidth, height: (0, _roundToDecimalPlaces.roundToDecimalPlaces)(rowsMeta.currentPageTotalHeight, 1) }; let viewportOuterSize; let viewportInnerSize; let hasScrollX = false; let hasScrollY = false; if (props.autoHeight) { hasScrollY = false; hasScrollX = Math.round(columnsTotalWidth) > Math.round(rootDimensionsRef.current.width); viewportOuterSize = { width: rootDimensionsRef.current.width, height: topContainerHeight + bottomContainerHeight + contentSize.height }; viewportInnerSize = { width: Math.max(0, viewportOuterSize.width - (hasScrollY ? scrollbarSize : 0)), height: Math.max(0, viewportOuterSize.height - (hasScrollX ? scrollbarSize : 0)) }; } else { viewportOuterSize = { width: rootDimensionsRef.current.width, height: rootDimensionsRef.current.height }; viewportInnerSize = { width: Math.max(0, viewportOuterSize.width - leftPinnedWidth - rightPinnedWidth), height: Math.max(0, viewportOuterSize.height - topContainerHeight - bottomContainerHeight) }; const content = contentSize; const container = viewportInnerSize; const hasScrollXIfNoYScrollBar = content.width > container.width; const hasScrollYIfNoXScrollBar = content.height > container.height; if (hasScrollXIfNoYScrollBar || hasScrollYIfNoXScrollBar) { hasScrollY = hasScrollYIfNoXScrollBar; hasScrollX = content.width + (hasScrollY ? scrollbarSize : 0) > container.width; // We recalculate the scroll y to consider the size of the x scrollbar. if (hasScrollX) { hasScrollY = content.height + scrollbarSize > container.height; } } if (hasScrollY) { viewportInnerSize.width -= scrollbarSize; } if (hasScrollX) { viewportInnerSize.height -= scrollbarSize; } } const rowWidth = Math.max(viewportOuterSize.width, columnsTotalWidth + (hasScrollY ? scrollbarSize : 0)); const minimumSize = { width: columnsTotalWidth, height: topContainerHeight + contentSize.height + bottomContainerHeight }; const newDimensions = { isReady: true, root: rootDimensionsRef.current, viewportOuterSize, viewportInnerSize, contentSize, minimumSize, hasScrollX, hasScrollY, scrollbarSize, headerHeight, groupHeaderHeight, headerFilterHeight, rowWidth, rowHeight, columnsTotalWidth, leftPinnedWidth, rightPinnedWidth, headersTotalHeight, topContainerHeight, bottomContainerHeight }; const prevDimensions = apiRef.current.state.dimensions; if ((0, _utils3.isDeepEqual)(prevDimensions, newDimensions)) { return; } setDimensions(newDimensions); if (!areElementSizesEqual(newDimensions.viewportInnerSize, prevDimensions.viewportInnerSize)) { apiRef.current.publishEvent('viewportInnerSizeChange', newDimensions.viewportInnerSize); } apiRef.current.updateRenderContext?.(); }, [apiRef, setDimensions, props.scrollbarSize, props.autoHeight, rowHeight, headerHeight, groupHeaderHeight, headerFilterHeight, columnsTotalWidth, headersTotalHeight, leftPinnedWidth, rightPinnedWidth]); const updateDimensionCallback = (0, _utils.unstable_useEventCallback)(updateDimensions); const debouncedUpdateDimensions = React.useMemo(() => props.resizeThrottleMs > 0 ? (0, _throttle.throttle)(() => { updateDimensionCallback(); apiRef.current.publishEvent('debouncedResize', rootDimensionsRef.current); }, props.resizeThrottleMs) : undefined, [apiRef, props.resizeThrottleMs, updateDimensionCallback]); React.useEffect(() => debouncedUpdateDimensions?.clear, [debouncedUpdateDimensions]); const apiPublic = { resize, getRootDimensions }; const apiPrivate = { updateDimensions, getViewportPageSize }; (0, _utils.unstable_useEnhancedEffect)(updateDimensions, [updateDimensions]); (0, _useGridApiMethod.useGridApiMethod)(apiRef, apiPublic, 'public'); (0, _useGridApiMethod.useGridApiMethod)(apiRef, apiPrivate, 'private'); const handleRootMount = React.useCallback(root => { setCSSVariables(root, (0, _gridDimensionsSelectors.gridDimensionsSelector)(apiRef.current.state)); }, [apiRef]); const handleResize = React.useCallback(size => { rootDimensionsRef.current = size; if (size.height === 0 && !errorShown.current && !props.autoHeight && !_isJSDOM.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.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 (isFirstSizing.current || !debouncedUpdateDimensions) { // We want to initialize the grid dimensions as soon as possible to avoid flickering isFirstSizing.current = false; updateDimensions(); return; } debouncedUpdateDimensions(); }, [updateDimensions, props.autoHeight, debouncedUpdateDimensions, logger]); (0, _useGridApiEventHandler.useGridApiOptionHandler)(apiRef, 'rootMount', handleRootMount); (0, _useGridApiEventHandler.useGridApiOptionHandler)(apiRef, 'resize', handleResize); (0, _useGridApiEventHandler.useGridApiOptionHandler)(apiRef, 'debouncedResize', props.onResize); } function setCSSVariables(root, dimensions) { const set = (k, v) => 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, apiRef, density, pinnedColumnns) { const validRowHeight = (0, _gridRowsUtils.getValidRowHeight)(props.rowHeight, _dataGridPropsDefaultValues.DATA_GRID_PROPS_DEFAULT_VALUES.rowHeight, _gridRowsUtils.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: (0, _gridColumnsUtils.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(element, scrollbarSize) { if (scrollbarSize !== undefined) { return scrollbarSize; } if (element === null) { return 0; } const cachedSize = scrollbarSizeCache.get(element); if (cachedSize !== undefined) { return cachedSize; } const doc = (0, _utils.unstable_ownerDocument)(element); const scrollDiv = doc.createElement('div'); scrollDiv.style.width = '99px'; scrollDiv.style.height = '99px'; scrollDiv.style.position = 'absolute'; scrollDiv.style.overflow = 'scroll'; scrollDiv.className = 'scrollDiv'; element.appendChild(scrollDiv); const size = scrollDiv.offsetWidth - scrollDiv.clientWidth; element.removeChild(scrollDiv); scrollbarSizeCache.set(element, size); return size; } function areElementSizesEqual(a, b) { return a.width === b.width && a.height === b.height; }