UNPKG

@mui/x-data-grid

Version:

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

134 lines (132 loc) 6.2 kB
import * as React from 'react'; import { useRtl } from '@mui/system/RtlProvider'; import { useGridLogger } from "../../utils/useGridLogger.js"; import { gridColumnPositionsSelector, gridVisibleColumnDefinitionsSelector } from "../columns/gridColumnsSelector.js"; import { useGridSelector } from "../../utils/useGridSelector.js"; import { gridPageSelector, gridPageSizeSelector } from "../pagination/gridPaginationSelector.js"; import { gridRowCountSelector } from "../rows/gridRowsSelector.js"; import { gridRowsMetaSelector } from "../rows/gridRowsMetaSelector.js"; import { useGridApiMethod } from "../../utils/useGridApiMethod.js"; import { gridExpandedSortedRowEntriesSelector } from "../filter/gridFilterSelector.js"; import { gridDimensionsSelector } from "../dimensions/index.js"; import { gridListColumnSelector } from "../listView/gridListViewSelectors.js"; // Logic copied from https://www.w3.org/TR/wai-aria-practices/examples/listbox/js/listbox.js // Similar to https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView function scrollIntoView(dimensions) { const { containerSize, scrollPosition, elementSize, elementOffset } = dimensions; const elementEnd = elementOffset + elementSize; // Always scroll to top when cell is higher than viewport to avoid scroll jump // See https://github.com/mui/mui-x/issues/4513 and https://github.com/mui/mui-x/issues/4514 if (elementSize > containerSize) { return elementOffset; } if (elementEnd - containerSize > scrollPosition) { return elementEnd - containerSize; } if (elementOffset < scrollPosition) { return elementOffset; } return undefined; } /** * @requires useGridPagination (state) - can be after, async only * @requires useGridColumns (state) - can be after, async only * @requires useGridRows (state) - can be after, async only * @requires useGridRowsMeta (state) - can be after, async only * @requires useGridFilter (state) * @requires useGridColumnSpanning (method) */ export const useGridScroll = (apiRef, props) => { const isRtl = useRtl(); const logger = useGridLogger(apiRef, 'useGridScroll'); const colRef = apiRef.current.columnHeadersContainerRef; const virtualScrollerRef = apiRef.current.virtualScrollerRef; const visibleSortedRows = useGridSelector(apiRef, gridExpandedSortedRowEntriesSelector); const scrollToIndexes = React.useCallback(params => { const dimensions = gridDimensionsSelector(apiRef); const totalRowCount = gridRowCountSelector(apiRef); const visibleColumns = props.listView ? [gridListColumnSelector(apiRef)] : gridVisibleColumnDefinitionsSelector(apiRef); const scrollToHeader = params.rowIndex == null; if (!scrollToHeader && totalRowCount === 0 || visibleColumns.length === 0) { return false; } logger.debug(`Scrolling to cell at row ${params.rowIndex}, col: ${params.colIndex} `); let scrollCoordinates = {}; if (params.colIndex !== undefined) { const columnPositions = gridColumnPositionsSelector(apiRef); let cellWidth; if (typeof params.rowIndex !== 'undefined') { const rowId = visibleSortedRows[params.rowIndex]?.id; const cellColSpanInfo = apiRef.current.unstable_getCellColSpanInfo(rowId, params.colIndex); if (cellColSpanInfo && !cellColSpanInfo.spannedByColSpan) { cellWidth = cellColSpanInfo.cellProps.width; } } if (typeof cellWidth === 'undefined') { cellWidth = visibleColumns[params.colIndex].computedWidth; } // When using RTL, `scrollLeft` becomes negative, so we must ensure that we only compare values. scrollCoordinates.left = scrollIntoView({ containerSize: dimensions.viewportOuterSize.width, scrollPosition: Math.abs(virtualScrollerRef.current.scrollLeft), elementSize: cellWidth, elementOffset: columnPositions[params.colIndex] }); } if (params.rowIndex !== undefined) { const rowsMeta = gridRowsMetaSelector(apiRef); const page = gridPageSelector(apiRef); const pageSize = gridPageSizeSelector(apiRef); const elementIndex = !props.pagination ? params.rowIndex : params.rowIndex - page * pageSize; const targetOffsetHeight = rowsMeta.positions[elementIndex + 1] ? rowsMeta.positions[elementIndex + 1] - rowsMeta.positions[elementIndex] : rowsMeta.currentPageTotalHeight - rowsMeta.positions[elementIndex]; scrollCoordinates.top = scrollIntoView({ containerSize: dimensions.viewportInnerSize.height, scrollPosition: virtualScrollerRef.current.scrollTop, elementSize: targetOffsetHeight, elementOffset: rowsMeta.positions[elementIndex] }); } scrollCoordinates = apiRef.current.unstable_applyPipeProcessors('scrollToIndexes', scrollCoordinates, params); if (typeof scrollCoordinates.left !== undefined || typeof scrollCoordinates.top !== undefined) { apiRef.current.scroll(scrollCoordinates); return true; } return false; }, [logger, apiRef, virtualScrollerRef, props.pagination, visibleSortedRows, props.listView]); const scroll = React.useCallback(params => { if (virtualScrollerRef.current && params.left !== undefined && colRef.current) { const direction = isRtl ? -1 : 1; colRef.current.scrollLeft = params.left; virtualScrollerRef.current.scrollLeft = direction * params.left; logger.debug(`Scrolling left: ${params.left}`); } if (virtualScrollerRef.current && params.top !== undefined) { virtualScrollerRef.current.scrollTop = params.top; logger.debug(`Scrolling top: ${params.top}`); } logger.debug(`Scrolling, updating container, and viewport`); }, [virtualScrollerRef, isRtl, colRef, logger]); const getScrollPosition = React.useCallback(() => { if (!virtualScrollerRef?.current) { return { top: 0, left: 0 }; } return { top: virtualScrollerRef.current.scrollTop, left: virtualScrollerRef.current.scrollLeft }; }, [virtualScrollerRef]); const scrollApi = { scroll, scrollToIndexes, getScrollPosition }; useGridApiMethod(apiRef, scrollApi, 'public'); };