@mui/x-data-grid
Version:
The Community plan edition of the Data Grid components (MUI X).
325 lines (322 loc) • 15.4 kB
JavaScript
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;
}
;