UNPKG

@mui/x-data-grid

Version:

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

265 lines (258 loc) 12.6 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import * as React from 'react'; import useLazyRef from '@mui/utils/useLazyRef'; import useEventCallback from '@mui/utils/useEventCallback'; import { useRtl } from '@mui/system/RtlProvider'; import { roundToDecimalPlaces } from '@mui/x-internals/math'; import { lruMemoize } from '@mui/x-internals/lruMemoize'; import { useStoreEffect } from '@mui/x-internals/store'; import { useVirtualizer, Dimensions, LayoutDataGridLegacy, Virtualization, EMPTY_RENDER_CONTEXT } from '@mui/x-virtualizer'; import { useFirstRender } from "../utils/useFirstRender.js"; import { createSelector } from "../../utils/createSelector.js"; import { useGridSelector } from "../utils/useGridSelector.js"; import { gridHasFillerSelector, gridVerticalScrollbarWidthSelector } from "../features/dimensions/gridDimensionsSelectors.js"; import { gridDensityFactorSelector } from "../features/density/index.js"; import { gridVisibleColumnDefinitionsSelector, gridVisiblePinnedColumnDefinitionsSelector, gridColumnPositionsSelector, gridHasColSpanSelector } from "../features/columns/gridColumnsSelector.js"; import { gridPinnedRowsSelector, gridRowCountSelector } from "../features/rows/gridRowsSelector.js"; import { useGridVisibleRows } from "../utils/useGridVisibleRows.js"; import { gridPaginationSelector } from "../features/pagination/index.js"; import { gridFocusedVirtualCellSelector } from "../features/virtualization/gridFocusedVirtualCellSelector.js"; import { gridRowSelectionManagerSelector } from "../features/rowSelection/index.js"; import { DATA_GRID_PROPS_DEFAULT_VALUES } from "../../constants/dataGridPropsDefaultValues.js"; import { getValidRowHeight, minimalContentHeight, rowHeightWarning } from "../features/rows/gridRowsUtils.js"; import { getTotalHeaderHeight } from "../features/columns/gridColumnsUtils.js"; import { useGridOverlays } from "../features/overlays/useGridOverlays.js"; import { useGridRootProps } from "../utils/useGridRootProps.js"; import { useGridPrivateApiContext } from "../utils/useGridPrivateApiContext.js"; import { useGridRowsMeta } from "../features/rows/useGridRowsMeta.js"; import { eslintUseValue } from "../../utils/utils.js"; import { jsx as _jsx } from "react/jsx-runtime"; const columnsTotalWidthSelector = createSelector(gridVisibleColumnDefinitionsSelector, gridColumnPositionsSelector, (visibleColumns, positions) => { const colCount = visibleColumns.length; if (colCount === 0) { return 0; } return roundToDecimalPlaces(positions[colCount - 1] + visibleColumns[colCount - 1].computedWidth, 1); }); /** Translates virtualizer state to grid state */ const addGridDimensionsCreator = () => lruMemoize((dimensions, headerHeight, groupHeaderHeight, headerFilterHeight, headersTotalHeight) => { return _extends({}, dimensions, { headerHeight, groupHeaderHeight, headerFilterHeight, headersTotalHeight }); }, { maxSize: 1 }); /** * Virtualizer setup */ export function useGridVirtualizer() { const isRtl = useRtl(); const rootProps = useGridRootProps(); const apiRef = useGridPrivateApiContext(); const { listView } = rootProps; const visibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector); const pinnedRows = useGridSelector(apiRef, gridPinnedRowsSelector); const pinnedColumns = gridVisiblePinnedColumnDefinitionsSelector(apiRef); const rowSelectionManager = useGridSelector(apiRef, gridRowSelectionManagerSelector); const isRowSelected = React.useCallback(id => rowSelectionManager.has(id) && apiRef.current.isRowSelectable(id), [rowSelectionManager, apiRef]); const currentPage = useGridVisibleRows(apiRef); const hasColSpan = useGridSelector(apiRef, gridHasColSpanSelector); const verticalScrollbarWidth = useGridSelector(apiRef, gridVerticalScrollbarWidthSelector); const hasFiller = useGridSelector(apiRef, gridHasFillerSelector); const { autoHeight } = rootProps; const scrollReset = listView; // <DIMENSIONS> const density = useGridSelector(apiRef, gridDensityFactorSelector); const baseRowHeight = getValidRowHeight(rootProps.rowHeight, DATA_GRID_PROPS_DEFAULT_VALUES.rowHeight, rowHeightWarning); const rowHeight = Math.floor(baseRowHeight * density); const headerHeight = Math.floor(rootProps.columnHeaderHeight * density); const groupHeaderHeight = Math.floor((rootProps.columnGroupHeaderHeight ?? rootProps.columnHeaderHeight) * density); const headerFilterHeight = Math.floor((rootProps.headerFilterHeight ?? rootProps.columnHeaderHeight) * density); const columnsTotalWidth = useGridSelector(apiRef, columnsTotalWidthSelector); const headersTotalHeight = getTotalHeaderHeight(apiRef, rootProps); const leftPinnedWidth = pinnedColumns.left.reduce((w, col) => w + col.computedWidth, 0); const rightPinnedWidth = pinnedColumns.right.reduce((w, col) => w + col.computedWidth, 0); const overlayState = useGridOverlays(apiRef, rootProps); const dimensionsParams = { rowHeight, headerHeight, columnsTotalWidth, leftPinnedWidth, rightPinnedWidth, topPinnedHeight: headersTotalHeight, bottomPinnedHeight: 0, autoHeight, minimalContentHeight, scrollbarSize: rootProps.scrollbarSize }; const addGridDimensions = useLazyRef(addGridDimensionsCreator).current; // </DIMENSIONS> // <ROWS_META> const dataRowCount = useGridSelector(apiRef, gridRowCountSelector); const pagination = useGridSelector(apiRef, gridPaginationSelector); const rowCount = Math.min(pagination.enabled ? pagination.paginationModel.pageSize : dataRowCount, dataRowCount); const { getRowHeight, getEstimatedRowHeight, getRowSpacing } = rootProps; // </ROWS_META> const RowSlot = rootProps.slots.row; const rowSlotProps = rootProps.slotProps?.row; const focusedVirtualCell = useGridSelector(apiRef, gridFocusedVirtualCellSelector); // We need it to trigger a new render, but rowsMeta needs access to the latest value, hence we cannot pass it to the focusedVirtualCell callback in the virtualizer params eslintUseValue(focusedVirtualCell); const layout = useLazyRef(() => new LayoutDataGridLegacy({ container: apiRef.current.mainElementRef, scroller: apiRef.current.virtualScrollerRef, scrollbarVertical: apiRef.current.virtualScrollbarVerticalRef, scrollbarHorizontal: apiRef.current.virtualScrollbarHorizontalRef })).current; const virtualizer = useVirtualizer({ layout, dimensions: dimensionsParams, virtualization: { isRtl, rowBufferPx: rootProps.rowBufferPx, columnBufferPx: rootProps.columnBufferPx }, colspan: { enabled: hasColSpan, getColspan: React.useCallback((rowId, column) => { if (typeof column.colSpan === 'function') { const row = apiRef.current.getRow(rowId); const value = apiRef.current.getRowValue(row, column); return column.colSpan(value, row, column, apiRef) ?? 0; } return column.colSpan ?? 1; }, [apiRef]) }, initialState: { scroll: rootProps.initialState?.scroll, rowSpanning: apiRef.current.state.rowSpanning, virtualization: apiRef.current.state.virtualization }, rows: currentPage.rows, range: currentPage.range, rowCount, columns: visibleColumns, pinnedRows, pinnedColumns, disableHorizontalScroll: listView, disableVerticalScroll: overlayState.overlayType === 'noColumnsOverlay' || overlayState.loadingOverlayVariant === 'skeleton', getRowHeight: React.useMemo(() => { if (!getRowHeight) { return undefined; } return rowEntry => getRowHeight(_extends({}, rowEntry, { densityFactor: density })); }, [getRowHeight, density]), getEstimatedRowHeight: React.useMemo(() => getEstimatedRowHeight ? rowEntry => getEstimatedRowHeight(_extends({}, rowEntry, { densityFactor: density })) : undefined, [getEstimatedRowHeight, density]), getRowSpacing: React.useMemo(() => getRowSpacing ? rowEntry => { const indexRelativeToCurrentPage = currentPage.rowIdToIndexMap.get(rowEntry.id) ?? -1; const visibility = { isFirstVisible: indexRelativeToCurrentPage === 0, isLastVisible: indexRelativeToCurrentPage === currentPage.rows.length - 1, indexRelativeToCurrentPage }; return getRowSpacing(_extends({}, rowEntry, visibility, { indexRelativeToCurrentPage: apiRef.current.getRowIndexRelativeToVisibleRows(rowEntry.id) })); } : undefined, [apiRef, getRowSpacing, currentPage.rows, currentPage.rowIdToIndexMap]), applyRowHeight: useEventCallback((entry, row) => apiRef.current.unstable_applyPipeProcessors('rowHeight', entry, row)), virtualizeColumnsWithAutoRowHeight: rootProps.virtualizeColumnsWithAutoRowHeight, focusedVirtualCell: useEventCallback(() => gridFocusedVirtualCellSelector(apiRef)), resizeThrottleMs: rootProps.resizeThrottleMs, onResize: useEventCallback(size => apiRef.current.publishEvent('resize', size)), onWheel: useEventCallback(event => { apiRef.current.publishEvent('virtualScrollerWheel', {}, event); }), onTouchMove: useEventCallback(event => { apiRef.current.publishEvent('virtualScrollerTouchMove', {}, event); }), onRenderContextChange: useEventCallback(nextRenderContext => { apiRef.current.publishEvent('renderedRowsIntervalChange', nextRenderContext); }), onScrollChange: React.useCallback((scrollPosition, nextRenderContext) => { apiRef.current.publishEvent('scrollPositionChange', { top: scrollPosition.top, left: scrollPosition.left, renderContext: nextRenderContext }); }, [apiRef]), scrollReset, renderRow: React.useCallback(params => /*#__PURE__*/_jsx(RowSlot, _extends({ row: params.model, rowId: params.id, index: params.rowIndex, selected: isRowSelected(params.id), offsetLeft: params.offsetLeft, columnsTotalWidth: columnsTotalWidth, rowHeight: params.baseRowHeight, pinnedColumns: pinnedColumns, visibleColumns: visibleColumns, firstColumnIndex: params.firstColumnIndex, lastColumnIndex: params.lastColumnIndex, focusedColumnIndex: params.focusedColumnIndex, isFirstVisible: params.isFirstVisible, isLastVisible: params.isLastVisible, isNotVisible: params.isVirtualFocusRow, showBottomBorder: params.showBottomBorder, scrollbarWidth: verticalScrollbarWidth, gridHasFiller: hasFiller }, rowSlotProps), params.id), [columnsTotalWidth, hasFiller, isRowSelected, pinnedColumns, RowSlot, rowSlotProps, verticalScrollbarWidth, visibleColumns]), renderInfiniteLoadingTrigger: React.useCallback(id => apiRef.current.getInfiniteLoadingTriggerElement?.({ lastRowId: id }), [apiRef]) }); // HACK: Keep the grid's store in sync with the virtualizer store. We set up the // subscription in the render phase rather than in an effect because other grid // initialization code runs between those two moments. // // TODO(v9): Remove this useFirstRender(() => { apiRef.current.store.state.dimensions = addGridDimensions(virtualizer.store.state.dimensions, headerHeight, groupHeaderHeight, headerFilterHeight, headersTotalHeight); apiRef.current.store.state.rowsMeta = virtualizer.store.state.rowsMeta; apiRef.current.store.state.virtualization = virtualizer.store.state.virtualization; }); useStoreEffect(virtualizer.store, Dimensions.selectors.dimensions, (_, dimensions) => { if (!dimensions.isReady) { return; } apiRef.current.setState(gridState => _extends({}, gridState, { dimensions: addGridDimensions(dimensions, headerHeight, groupHeaderHeight, headerFilterHeight, headersTotalHeight) })); }); useStoreEffect(virtualizer.store, Dimensions.selectors.rowsMeta, (_, rowsMeta) => { if (rowsMeta !== apiRef.current.state.rowsMeta) { apiRef.current.setState(gridState => _extends({}, gridState, { rowsMeta })); } }); useStoreEffect(virtualizer.store, Virtualization.selectors.store, (_, virtualization) => { if (virtualization.renderContext === EMPTY_RENDER_CONTEXT) { return; } if (virtualization !== apiRef.current.state.virtualization) { apiRef.current.setState(gridState => _extends({}, gridState, { virtualization })); } }); apiRef.current.register('private', { virtualizer }); useGridRowsMeta(apiRef, rootProps); return virtualizer; }