@mui/x-data-grid
Version:
The community edition of the data grid component (MUI X).
350 lines (344 loc) • 16.1 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useGridColumnHeaders = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var ReactDOM = _interopRequireWildcard(require("react-dom"));
var _utils = require("@mui/utils");
var _styles = require("@mui/material/styles");
var _reselect = require("reselect");
var _utils2 = require("../../utils");
var _useGridPrivateApiContext = require("../../utils/useGridPrivateApiContext");
var _useGridRootProps = require("../../utils/useGridRootProps");
var _useGridApiEventHandler = require("../../utils/useGridApiEventHandler");
var _GridColumnHeaderItem = require("../../../components/columnHeaders/GridColumnHeaderItem");
var _gridColumnsUtils = require("../columns/gridColumnsUtils");
var _useGridVisibleRows = require("../../utils/useGridVisibleRows");
var _useGridVirtualScroller = require("../virtualization/useGridVirtualScroller");
var _virtualization = require("../virtualization");
var _GridColumnGroupHeader = require("../../../components/columnHeaders/GridColumnGroupHeader");
var _jsxRuntime = require("react/jsx-runtime");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
const GridColumnHeaderRow = (0, _styles.styled)('div', {
name: 'MuiDataGrid',
slot: 'ColumnHeaderRow',
overridesResolver: (props, styles) => styles.columnHeaderRow
})(() => ({
display: 'flex'
}));
function isUIEvent(event) {
return !!event.target;
}
const useGridColumnHeaders = props => {
const {
innerRef: innerRefProp,
minColumnIndex = 0,
visibleColumns,
sortColumnLookup,
filterColumnLookup,
columnPositions,
columnHeaderTabIndexState,
columnGroupHeaderTabIndexState,
columnHeaderFocus,
columnGroupHeaderFocus,
densityFactor,
headerGroupingMaxDepth,
columnMenuState,
columnVisibility,
columnGroupsHeaderStructure,
hasOtherElementInTabSequence
} = props;
const theme = (0, _styles.useTheme)();
const [dragCol, setDragCol] = React.useState('');
const [resizeCol, setResizeCol] = React.useState('');
const apiRef = (0, _useGridPrivateApiContext.useGridPrivateApiContext)();
const hasVirtualization = (0, _utils2.useGridSelector)(apiRef, _virtualization.gridVirtualizationColumnEnabledSelector);
const rootProps = (0, _useGridRootProps.useGridRootProps)();
const innerRef = React.useRef(null);
const handleInnerRef = (0, _utils.unstable_useForkRef)(innerRefProp, innerRef);
const [renderContext, setRenderContextRaw] = React.useState(null);
const prevRenderContext = React.useRef(renderContext);
const prevScrollLeft = React.useRef(0);
const currentPage = (0, _useGridVisibleRows.useGridVisibleRows)(apiRef, rootProps);
const totalHeaderHeight = (0, _gridColumnsUtils.getTotalHeaderHeight)(apiRef, rootProps.columnHeaderHeight);
const headerHeight = Math.floor(rootProps.columnHeaderHeight * densityFactor);
const setRenderContext = React.useCallback(nextRenderContext => {
if (renderContext && nextRenderContext && (0, _useGridVirtualScroller.areRenderContextsEqual)(renderContext, nextRenderContext)) {
return;
}
setRenderContextRaw(nextRenderContext);
}, [renderContext]);
React.useEffect(() => {
apiRef.current.columnHeadersContainerElementRef.current.scrollLeft = 0;
}, [apiRef]);
// memoize `getFirstColumnIndexToRender`, since it's called on scroll
const getFirstColumnIndexToRenderRef = React.useRef((0, _reselect.defaultMemoize)(_gridColumnsUtils.getFirstColumnIndexToRender, {
equalityCheck: (a, b) => ['firstColumnIndex', 'minColumnIndex', 'columnBuffer'].every(key => a[key] === b[key])
}));
const updateInnerPosition = React.useCallback(nextRenderContext => {
const [firstRowToRender, lastRowToRender] = (0, _useGridVirtualScroller.getRenderableIndexes)({
firstIndex: nextRenderContext.firstRowIndex,
lastIndex: nextRenderContext.lastRowIndex,
minFirstIndex: 0,
maxLastIndex: currentPage.rows.length,
buffer: rootProps.rowBuffer
});
const firstColumnToRender = getFirstColumnIndexToRenderRef.current({
firstColumnIndex: nextRenderContext.firstColumnIndex,
minColumnIndex,
columnBuffer: rootProps.columnBuffer,
firstRowToRender,
lastRowToRender,
apiRef,
visibleRows: currentPage.rows
});
const direction = theme.direction === 'ltr' ? 1 : -1;
const offset = firstColumnToRender > 0 ? prevScrollLeft.current - direction * columnPositions[firstColumnToRender] : prevScrollLeft.current;
innerRef.current.style.transform = `translate3d(${-offset}px, 0px, 0px)`;
}, [columnPositions, minColumnIndex, rootProps.columnBuffer, apiRef, currentPage.rows, rootProps.rowBuffer, theme.direction]);
React.useLayoutEffect(() => {
if (renderContext) {
updateInnerPosition(renderContext);
}
}, [renderContext, updateInnerPosition]);
const handleScroll = React.useCallback(({
left,
renderContext: nextRenderContext = null
}, event) => {
if (!innerRef.current) {
return;
}
// Ignore vertical scroll.
// Excepts the first event which sets the previous render context.
if (prevScrollLeft.current === left && prevRenderContext.current?.firstColumnIndex === nextRenderContext?.firstColumnIndex && prevRenderContext.current?.lastColumnIndex === nextRenderContext?.lastColumnIndex) {
return;
}
prevScrollLeft.current = left;
// We can only update the position when we guarantee that the render context has been
// rendered. This is achieved using ReactDOM.flushSync or when the context doesn't change.
let canUpdateInnerPosition = false;
if (nextRenderContext !== prevRenderContext.current || !prevRenderContext.current) {
// ReactDOM.flushSync cannot be called on `scroll` events fired inside effects
if (isUIEvent(event)) {
// To prevent flickering, the inner position can only be updated after the new context has
// been rendered. ReactDOM.flushSync ensures that the state changes will happen before
// updating the position.
ReactDOM.flushSync(() => {
setRenderContext(nextRenderContext);
});
canUpdateInnerPosition = true;
} else {
setRenderContext(nextRenderContext);
}
prevRenderContext.current = nextRenderContext;
} else {
canUpdateInnerPosition = true;
}
// Pass directly the render context to avoid waiting for the next render
if (nextRenderContext && canUpdateInnerPosition) {
updateInnerPosition(nextRenderContext);
}
}, [updateInnerPosition, setRenderContext]);
const handleColumnResizeStart = React.useCallback(params => setResizeCol(params.field), []);
const handleColumnResizeStop = React.useCallback(() => setResizeCol(''), []);
const handleColumnReorderStart = React.useCallback(params => setDragCol(params.field), []);
const handleColumnReorderStop = React.useCallback(() => setDragCol(''), []);
(0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'columnResizeStart', handleColumnResizeStart);
(0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'columnResizeStop', handleColumnResizeStop);
(0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'columnHeaderDragStart', handleColumnReorderStart);
(0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'columnHeaderDragEnd', handleColumnReorderStop);
(0, _useGridApiEventHandler.useGridApiEventHandler)(apiRef, 'scrollPositionChange', handleScroll);
// Helper for computation common between getColumnHeaders and getColumnGroupHeaders
const getColumnsToRender = params => {
const {
renderContext: nextRenderContext = renderContext,
minFirstColumn = minColumnIndex,
maxLastColumn = visibleColumns.length
} = params || {};
if (!nextRenderContext) {
return null;
}
const [firstRowToRender, lastRowToRender] = (0, _useGridVirtualScroller.getRenderableIndexes)({
firstIndex: nextRenderContext.firstRowIndex,
lastIndex: nextRenderContext.lastRowIndex,
minFirstIndex: 0,
maxLastIndex: currentPage.rows.length,
buffer: rootProps.rowBuffer
});
const firstColumnToRender = !hasVirtualization ? 0 : getFirstColumnIndexToRenderRef.current({
firstColumnIndex: nextRenderContext.firstColumnIndex,
minColumnIndex: minFirstColumn,
columnBuffer: rootProps.columnBuffer,
apiRef,
firstRowToRender,
lastRowToRender,
visibleRows: currentPage.rows
});
const lastColumnToRender = !hasVirtualization ? maxLastColumn : Math.min(nextRenderContext.lastColumnIndex + rootProps.columnBuffer, maxLastColumn);
const renderedColumns = visibleColumns.slice(firstColumnToRender, lastColumnToRender);
return {
renderedColumns,
firstColumnToRender,
lastColumnToRender,
minFirstColumn,
maxLastColumn
};
};
const getColumnHeaders = (params, other = {}) => {
const columnsToRender = getColumnsToRender(params);
if (columnsToRender == null) {
return null;
}
const {
renderedColumns,
firstColumnToRender
} = columnsToRender;
const columns = [];
for (let i = 0; i < renderedColumns.length; i += 1) {
const colDef = renderedColumns[i];
const columnIndex = firstColumnToRender + i;
const isFirstColumn = columnIndex === 0;
const tabIndex = columnHeaderTabIndexState !== null && columnHeaderTabIndexState.field === colDef.field || isFirstColumn && !hasOtherElementInTabSequence ? 0 : -1;
const hasFocus = columnHeaderFocus !== null && columnHeaderFocus.field === colDef.field;
const open = columnMenuState.open && columnMenuState.field === colDef.field;
columns.push( /*#__PURE__*/(0, _jsxRuntime.jsx)(_GridColumnHeaderItem.GridColumnHeaderItem, (0, _extends2.default)({}, sortColumnLookup[colDef.field], {
columnMenuOpen: open,
filterItemsCounter: filterColumnLookup[colDef.field] && filterColumnLookup[colDef.field].length,
headerHeight: headerHeight,
isDragging: colDef.field === dragCol,
colDef: colDef,
colIndex: columnIndex,
isResizing: resizeCol === colDef.field,
hasFocus: hasFocus,
tabIndex: tabIndex
}, other), colDef.field));
}
return /*#__PURE__*/(0, _jsxRuntime.jsx)(GridColumnHeaderRow, {
role: "row",
"aria-rowindex": headerGroupingMaxDepth + 1,
ownerState: rootProps,
children: columns
});
};
const getColumnGroupHeaders = params => {
if (headerGroupingMaxDepth === 0) {
return null;
}
const columnsToRender = getColumnsToRender(params);
if (columnsToRender == null || columnsToRender.renderedColumns.length === 0) {
return null;
}
const {
firstColumnToRender,
lastColumnToRender
} = columnsToRender;
const columns = [];
const headerToRender = [];
for (let depth = 0; depth < headerGroupingMaxDepth; depth += 1) {
const rowStructure = columnGroupsHeaderStructure[depth];
const firstColumnFieldToRender = visibleColumns[firstColumnToRender].field;
const firstGroupToRender = apiRef.current.unstable_getColumnGroupPath(firstColumnFieldToRender)[depth] ?? null;
const firstGroupIndex = rowStructure.findIndex(({
groupId,
columnFields
}) => groupId === firstGroupToRender && columnFields.includes(firstColumnFieldToRender));
const lastColumnFieldToRender = visibleColumns[lastColumnToRender - 1].field;
const lastGroupToRender = apiRef.current.unstable_getColumnGroupPath(lastColumnFieldToRender)[depth] ?? null;
const lastGroupIndex = rowStructure.findIndex(({
groupId,
columnFields
}) => groupId === lastGroupToRender && columnFields.includes(lastColumnFieldToRender));
const visibleColumnGroupHeader = rowStructure.slice(firstGroupIndex, lastGroupIndex + 1).map(groupStructure => {
return (0, _extends2.default)({}, groupStructure, {
columnFields: groupStructure.columnFields.filter(field => columnVisibility[field] !== false)
});
}).filter(groupStructure => groupStructure.columnFields.length > 0);
const firstVisibleColumnIndex = visibleColumnGroupHeader[0].columnFields.indexOf(firstColumnFieldToRender);
const hiddenGroupColumns = visibleColumnGroupHeader[0].columnFields.slice(0, firstVisibleColumnIndex);
const leftOverflow = hiddenGroupColumns.reduce((acc, field) => {
const column = apiRef.current.getColumn(field);
return acc + (column.computedWidth ?? 0);
}, 0);
let columnIndex = firstColumnToRender;
const elements = visibleColumnGroupHeader.map(({
groupId,
columnFields
}) => {
const hasFocus = columnGroupHeaderFocus !== null && columnGroupHeaderFocus.depth === depth && columnFields.includes(columnGroupHeaderFocus.field);
const tabIndex = columnGroupHeaderTabIndexState !== null && columnGroupHeaderTabIndexState.depth === depth && columnFields.includes(columnGroupHeaderTabIndexState.field) ? 0 : -1;
const headerInfo = {
groupId,
width: columnFields.reduce((acc, field) => acc + apiRef.current.getColumn(field).computedWidth, 0),
fields: columnFields,
colIndex: columnIndex,
hasFocus,
tabIndex
};
columnIndex += columnFields.length;
return headerInfo;
});
headerToRender.push({
leftOverflow,
elements
});
}
headerToRender.forEach((depthInfo, depthIndex) => {
columns.push( /*#__PURE__*/(0, _jsxRuntime.jsx)(GridColumnHeaderRow, {
style: {
height: `${headerHeight}px`,
transform: `translateX(-${depthInfo.leftOverflow}px)`
},
role: "row",
"aria-rowindex": depthIndex + 1,
ownerState: rootProps,
children: depthInfo.elements.map(({
groupId,
width,
fields,
colIndex,
hasFocus,
tabIndex
}, groupIndex) => {
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_GridColumnGroupHeader.GridColumnGroupHeader, {
groupId: groupId,
width: width,
fields: fields,
colIndex: colIndex,
depth: depthIndex,
isLastColumn: colIndex === visibleColumns.length - fields.length,
maxDepth: headerToRender.length,
height: headerHeight,
hasFocus: hasFocus,
tabIndex: tabIndex
}, groupIndex);
})
}, depthIndex));
});
return columns;
};
const rootStyle = {
minHeight: totalHeaderHeight,
maxHeight: totalHeaderHeight,
lineHeight: `${headerHeight}px`
};
return {
renderContext,
getColumnHeaders,
getColumnsToRender,
getColumnGroupHeaders,
isDragging: !!dragCol,
getRootProps: (other = {}) => (0, _extends2.default)({
style: rootStyle
}, other),
getInnerProps: () => ({
ref: handleInnerRef,
role: 'rowgroup'
}),
headerHeight
};
};
exports.useGridColumnHeaders = useGridColumnHeaders;