UNPKG

@mui/x-data-grid

Version:

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

433 lines (423 loc) 18.9 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose"; const _excluded = ["selected", "rowId", "row", "index", "style", "rowHeight", "className", "visibleColumns", "pinnedColumns", "offsetTop", "offsetLeft", "dimensions", "renderContext", "focusedColumnIndex", "isFirstVisible", "isLastVisible", "isNotVisible", "focusedCell", "tabbableCell", "onClick", "onDoubleClick", "onMouseEnter", "onMouseLeave", "onMouseOut", "onMouseOver"]; import * as React from 'react'; import PropTypes from 'prop-types'; import clsx from 'clsx'; import { unstable_composeClasses as composeClasses, unstable_useForkRef as useForkRef } from '@mui/utils'; import { fastMemo } from '../utils/fastMemo'; import { GridEditModes, GridRowModes, GridCellModes } from '../models/gridEditRowModel'; import { useGridApiContext } from '../hooks/utils/useGridApiContext'; import { getDataGridUtilityClass, gridClasses } from '../constants/gridClasses'; import { useGridRootProps } from '../hooks/utils/useGridRootProps'; import { gridColumnPositionsSelector } from '../hooks/features/columns/gridColumnsSelector'; import { useGridSelector, objectShallowCompare } from '../hooks/utils/useGridSelector'; import { useGridVisibleRows } from '../hooks/utils/useGridVisibleRows'; import { findParentElementFromClassName, isEventTargetInPortal } from '../utils/domUtils'; import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../colDef/gridCheckboxSelectionColDef'; import { GRID_ACTIONS_COLUMN_TYPE } from '../colDef/gridActionsColDef'; import { GRID_DETAIL_PANEL_TOGGLE_FIELD } from '../constants/gridDetailPanelToggleField'; import { gridSortModelSelector } from '../hooks/features/sorting/gridSortingSelector'; import { gridRowMaximumTreeDepthSelector } from '../hooks/features/rows/gridRowsSelector'; import { gridColumnGroupsHeaderMaxDepthSelector } from '../hooks/features/columnGrouping/gridColumnGroupsSelector'; import { gridEditRowsStateSelector } from '../hooks/features/editing/gridEditingSelectors'; import { PinnedPosition, gridPinnedColumnPositionLookup } from './cell/GridCell'; import { GridScrollbarFillerCell as ScrollbarFiller } from './GridScrollbarFillerCell'; import { getPinnedCellOffset } from '../internals/utils/getPinnedCellOffset'; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const useUtilityClasses = ownerState => { const { editable, editing, selected, isFirstVisible, isLastVisible, rowHeight, classes } = ownerState; const slots = { root: ['row', selected && 'selected', editable && 'row--editable', editing && 'row--editing', isFirstVisible && 'row--firstVisible', isLastVisible && 'row--lastVisible', rowHeight === 'auto' && 'row--dynamicHeight'] }; return composeClasses(slots, getDataGridUtilityClass, classes); }; function EmptyCell({ width }) { if (!width) { return null; } return /*#__PURE__*/_jsx("div", { role: "presentation", className: clsx(gridClasses.cell, gridClasses.cellEmpty), style: { '--width': `${width}px` } }); } const GridRow = /*#__PURE__*/React.forwardRef(function GridRow(props, refProp) { const { selected, rowId, row, index, style: styleProp, rowHeight, className, visibleColumns, pinnedColumns, offsetLeft, dimensions, renderContext, focusedColumnIndex, isFirstVisible, isLastVisible, isNotVisible, onClick, onDoubleClick, onMouseEnter, onMouseLeave, onMouseOut, onMouseOver } = props, other = _objectWithoutPropertiesLoose(props, _excluded); const apiRef = useGridApiContext(); const ref = React.useRef(null); const rootProps = useGridRootProps(); const currentPage = useGridVisibleRows(apiRef, rootProps); const sortModel = useGridSelector(apiRef, gridSortModelSelector); const treeDepth = useGridSelector(apiRef, gridRowMaximumTreeDepthSelector); const headerGroupingMaxDepth = useGridSelector(apiRef, gridColumnGroupsHeaderMaxDepthSelector); const columnPositions = useGridSelector(apiRef, gridColumnPositionsSelector); const editRowsState = useGridSelector(apiRef, gridEditRowsStateSelector); const handleRef = useForkRef(ref, refProp); const rowNode = apiRef.current.getRowNode(rowId); const scrollbarWidth = dimensions.hasScrollY ? dimensions.scrollbarSize : 0; const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width; const hasFocusCell = focusedColumnIndex !== undefined; const hasVirtualFocusCellLeft = hasFocusCell && focusedColumnIndex >= pinnedColumns.left.length && focusedColumnIndex < renderContext.firstColumnIndex; const hasVirtualFocusCellRight = hasFocusCell && focusedColumnIndex < visibleColumns.length - pinnedColumns.right.length && focusedColumnIndex >= renderContext.lastColumnIndex; const ariaRowIndex = index + headerGroupingMaxDepth + 2; // 1 for the header row and 1 as it's 1-based const ownerState = { selected, isFirstVisible, isLastVisible, classes: rootProps.classes, editing: apiRef.current.getRowMode(rowId) === GridRowModes.Edit, editable: rootProps.editMode === GridEditModes.Row, rowHeight }; const classes = useUtilityClasses(ownerState); React.useLayoutEffect(() => { if (rowHeight === 'auto' && ref.current && typeof ResizeObserver === 'undefined') { // Fallback for IE apiRef.current.unstable_storeRowHeightMeasurement(rowId, ref.current.clientHeight); } }, [apiRef, rowHeight, rowId]); React.useLayoutEffect(() => { if (currentPage.range) { // The index prop is relative to the rows from all pages. As example, the index prop of the // first row is 5 if `paginationModel.pageSize=5` and `paginationModel.page=1`. However, the index used by the virtualization // doesn't care about pagination and considers the rows from the current page only, so the // first row always has index=0. We need to subtract the index of the first row to make it // compatible with the index used by the virtualization. const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(rowId); // pinned rows are not part of the visible rows if (rowIndex != null) { apiRef.current.unstable_setLastMeasuredRowIndex(rowIndex); } } const rootElement = ref.current; const hasFixedHeight = rowHeight !== 'auto'; if (!rootElement || hasFixedHeight || typeof ResizeObserver === 'undefined') { return undefined; } const resizeObserver = new ResizeObserver(entries => { const [entry] = entries; const height = entry.borderBoxSize && entry.borderBoxSize.length > 0 ? entry.borderBoxSize[0].blockSize : entry.contentRect.height; apiRef.current.unstable_storeRowHeightMeasurement(rowId, height); }); resizeObserver.observe(rootElement); return () => resizeObserver.disconnect(); }, [apiRef, currentPage.range, index, rowHeight, rowId]); const publish = React.useCallback((eventName, propHandler) => event => { // Ignore portal if (isEventTargetInPortal(event)) { return; } // The row might have been deleted if (!apiRef.current.getRow(rowId)) { return; } apiRef.current.publishEvent(eventName, apiRef.current.getRowParams(rowId), event); if (propHandler) { propHandler(event); } }, [apiRef, rowId]); const publishClick = React.useCallback(event => { const cell = findParentElementFromClassName(event.target, gridClasses.cell); const field = cell?.getAttribute('data-field'); // Check if the field is available because the cell that fills the empty // space of the row has no field. if (field) { // User clicked in the checkbox added by checkboxSelection if (field === GRID_CHECKBOX_SELECTION_COL_DEF.field) { return; } // User opened a detail panel if (field === GRID_DETAIL_PANEL_TOGGLE_FIELD) { return; } // User reorders a row if (field === '__reorder__') { return; } // User is editing a cell if (apiRef.current.getCellMode(rowId, field) === GridCellModes.Edit) { return; } // User clicked a button from the "actions" column type const column = apiRef.current.getColumn(field); if (column?.type === GRID_ACTIONS_COLUMN_TYPE) { return; } } publish('rowClick', onClick)(event); }, [apiRef, onClick, publish, rowId]); const { slots, slotProps, disableColumnReorder } = rootProps; const rowReordering = rootProps.rowReordering; const sizes = useGridSelector(apiRef, () => _extends({}, apiRef.current.unstable_getRowInternalSizes(rowId)), objectShallowCompare); let minHeight = rowHeight; if (minHeight === 'auto' && sizes) { const numberOfBaseSizes = 1; const maximumSize = sizes.baseCenter ?? 0; if (maximumSize > 0 && numberOfBaseSizes > 1) { minHeight = maximumSize; } } const style = React.useMemo(() => { if (isNotVisible) { return { opacity: 0, width: 0, height: 0 }; } const rowStyle = _extends({}, styleProp, { maxHeight: rowHeight === 'auto' ? 'none' : rowHeight, // max-height doesn't support "auto" minHeight, '--height': typeof rowHeight === 'number' ? `${rowHeight}px` : rowHeight }); if (sizes?.spacingTop) { const property = rootProps.rowSpacingType === 'border' ? 'borderTopWidth' : 'marginTop'; rowStyle[property] = sizes.spacingTop; } if (sizes?.spacingBottom) { const property = rootProps.rowSpacingType === 'border' ? 'borderBottomWidth' : 'marginBottom'; let propertyValue = rowStyle[property]; // avoid overriding existing value if (typeof propertyValue !== 'number') { propertyValue = parseInt(propertyValue || '0', 10); } propertyValue += sizes.spacingBottom; rowStyle[property] = propertyValue; } return rowStyle; }, [isNotVisible, rowHeight, styleProp, minHeight, sizes, rootProps.rowSpacingType]); const rowClassNames = apiRef.current.unstable_applyPipeProcessors('rowClassName', [], rowId); if (typeof rootProps.getRowClassName === 'function') { const indexRelativeToCurrentPage = index - (currentPage.range?.firstRowIndex || 0); const rowParams = _extends({}, apiRef.current.getRowParams(rowId), { isFirstVisible: indexRelativeToCurrentPage === 0, isLastVisible: indexRelativeToCurrentPage === currentPage.rows.length - 1, indexRelativeToCurrentPage }); rowClassNames.push(rootProps.getRowClassName(rowParams)); } const getCell = (column, indexInSection, indexRelativeToAllColumns, sectionLength, pinnedPosition = PinnedPosition.NONE) => { const cellColSpanInfo = apiRef.current.unstable_getCellColSpanInfo(rowId, indexRelativeToAllColumns); if (cellColSpanInfo?.spannedByColSpan) { return null; } const width = cellColSpanInfo?.cellProps.width ?? column.computedWidth; const colSpan = cellColSpanInfo?.cellProps.colSpan ?? 1; const pinnedOffset = getPinnedCellOffset(gridPinnedColumnPositionLookup[pinnedPosition], column.computedWidth, indexRelativeToAllColumns, columnPositions, dimensions); if (rowNode?.type === 'skeletonRow') { return /*#__PURE__*/_jsx(slots.skeletonCell, { width: width, height: rowHeight, field: column.field, align: column.align ?? 'left' }, column.field); } const editCellState = editRowsState[rowId]?.[column.field] ?? null; // when the cell is a reorder cell we are not allowing to reorder the col // fixes https://github.com/mui/mui-x/issues/11126 const isReorderCell = column.field === '__reorder__'; const isEditingRows = Object.keys(editRowsState).length > 0; const canReorderColumn = !(disableColumnReorder || column.disableReorder); const canReorderRow = rowReordering && !sortModel.length && treeDepth <= 1 && !isEditingRows; const disableDragEvents = !(canReorderColumn || isReorderCell && canReorderRow); const cellIsNotVisible = pinnedPosition === PinnedPosition.VIRTUAL; return /*#__PURE__*/_jsx(slots.cell, _extends({ column: column, width: width, rowId: rowId, align: column.align || 'left', colIndex: indexRelativeToAllColumns, colSpan: colSpan, disableDragEvents: disableDragEvents, editCellState: editCellState, isNotVisible: cellIsNotVisible, pinnedOffset: pinnedOffset, pinnedPosition: pinnedPosition, sectionIndex: indexInSection, sectionLength: sectionLength, gridHasFiller: gridHasFiller }, slotProps?.cell), column.field); }; /* Start of rendering */ if (!rowNode) { return null; } const leftCells = pinnedColumns.left.map((column, i) => { const indexRelativeToAllColumns = i; return getCell(column, i, indexRelativeToAllColumns, pinnedColumns.left.length, PinnedPosition.LEFT); }); const rightCells = pinnedColumns.right.map((column, i) => { const indexRelativeToAllColumns = visibleColumns.length - pinnedColumns.right.length + i; return getCell(column, i, indexRelativeToAllColumns, pinnedColumns.right.length, PinnedPosition.RIGHT); }); const middleColumnsLength = visibleColumns.length - pinnedColumns.left.length - pinnedColumns.right.length; const cells = []; if (hasVirtualFocusCellLeft) { cells.push(getCell(visibleColumns[focusedColumnIndex], focusedColumnIndex - pinnedColumns.left.length, focusedColumnIndex, middleColumnsLength, PinnedPosition.VIRTUAL)); } for (let i = renderContext.firstColumnIndex; i < renderContext.lastColumnIndex; i += 1) { const column = visibleColumns[i]; const indexInSection = i - pinnedColumns.left.length; cells.push(getCell(column, indexInSection, i, middleColumnsLength)); } if (hasVirtualFocusCellRight) { cells.push(getCell(visibleColumns[focusedColumnIndex], focusedColumnIndex - pinnedColumns.left.length, focusedColumnIndex, middleColumnsLength, PinnedPosition.VIRTUAL)); } const eventHandlers = row ? { onClick: publishClick, onDoubleClick: publish('rowDoubleClick', onDoubleClick), onMouseEnter: publish('rowMouseEnter', onMouseEnter), onMouseLeave: publish('rowMouseLeave', onMouseLeave), onMouseOut: publish('rowMouseOut', onMouseOut), onMouseOver: publish('rowMouseOver', onMouseOver) } : null; const expandedWidth = dimensions.viewportOuterSize.width - dimensions.columnsTotalWidth - scrollbarWidth; const emptyCellWidth = Math.max(0, expandedWidth); return /*#__PURE__*/_jsxs("div", _extends({ ref: handleRef, "data-id": rowId, "data-rowindex": index, role: "row", className: clsx(...rowClassNames, classes.root, className), "aria-rowindex": ariaRowIndex, "aria-selected": selected, style: style }, eventHandlers, other, { children: [leftCells, /*#__PURE__*/_jsx("div", { role: "presentation", className: gridClasses.cellOffsetLeft, style: { width: offsetLeft } }), cells, emptyCellWidth > 0 && /*#__PURE__*/_jsx(EmptyCell, { width: emptyCellWidth }), rightCells.length > 0 && /*#__PURE__*/_jsx("div", { role: "presentation", className: gridClasses.filler }), rightCells, scrollbarWidth !== 0 && /*#__PURE__*/_jsx(ScrollbarFiller, { pinnedRight: pinnedColumns.right.length > 0 })] })); }); process.env.NODE_ENV !== "production" ? GridRow.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "yarn proptypes" | // ---------------------------------------------------------------------- dimensions: PropTypes.shape({ bottomContainerHeight: PropTypes.number.isRequired, columnsTotalWidth: PropTypes.number.isRequired, contentSize: PropTypes.shape({ height: PropTypes.number.isRequired, width: PropTypes.number.isRequired }).isRequired, hasScrollX: PropTypes.bool.isRequired, hasScrollY: PropTypes.bool.isRequired, headerFilterHeight: PropTypes.number.isRequired, headerHeight: PropTypes.number.isRequired, headersTotalHeight: PropTypes.number.isRequired, isReady: PropTypes.bool.isRequired, leftPinnedWidth: PropTypes.number.isRequired, minimumSize: PropTypes.shape({ height: PropTypes.number.isRequired, width: PropTypes.number.isRequired }).isRequired, rightPinnedWidth: PropTypes.number.isRequired, root: PropTypes.shape({ height: PropTypes.number.isRequired, width: PropTypes.number.isRequired }).isRequired, rowHeight: PropTypes.number.isRequired, rowWidth: PropTypes.number.isRequired, scrollbarSize: PropTypes.number.isRequired, topContainerHeight: PropTypes.number.isRequired, viewportInnerSize: PropTypes.shape({ height: PropTypes.number.isRequired, width: PropTypes.number.isRequired }).isRequired, viewportOuterSize: PropTypes.shape({ height: PropTypes.number.isRequired, width: PropTypes.number.isRequired }).isRequired }).isRequired, /** * Determines which cell has focus. * If `null`, no cell in this row has focus. */ focusedColumnIndex: PropTypes.number, /** * Index of the row in the whole sorted and filtered dataset. * If some rows above have expanded children, this index also take those children into account. */ index: PropTypes.number.isRequired, isFirstVisible: PropTypes.bool.isRequired, isLastVisible: PropTypes.bool.isRequired, isNotVisible: PropTypes.bool.isRequired, offsetLeft: PropTypes.number.isRequired, offsetTop: PropTypes.number, onClick: PropTypes.func, onDoubleClick: PropTypes.func, onMouseEnter: PropTypes.func, onMouseLeave: PropTypes.func, pinnedColumns: PropTypes.object.isRequired, renderContext: PropTypes.shape({ firstColumnIndex: PropTypes.number.isRequired, firstRowIndex: PropTypes.number.isRequired, lastColumnIndex: PropTypes.number.isRequired, lastRowIndex: PropTypes.number.isRequired }).isRequired, row: PropTypes.object.isRequired, rowHeight: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number]).isRequired, rowId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, selected: PropTypes.bool.isRequired, /** * Determines which cell should be tabbable by having tabIndex=0. * If `null`, no cell in this row is in the tab sequence. */ tabbableCell: PropTypes.string, visibleColumns: PropTypes.arrayOf(PropTypes.object).isRequired } : void 0; const MemoizedGridRow = fastMemo(GridRow); export { MemoizedGridRow as GridRow };