@mui/x-data-grid-premium
Version:
The Premium plan edition of the MUI X Data Grid Components.
501 lines (498 loc) • 20.3 kB
JavaScript
"use strict";
'use client';
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useGridCellSelection = exports.cellSelectionStateInitializer = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _ownerDocument = _interopRequireDefault(require("@mui/utils/ownerDocument"));
var _useEventCallback = _interopRequireDefault(require("@mui/utils/useEventCallback"));
var _internals = require("@mui/x-data-grid-pro/internals");
var _xDataGridPro = require("@mui/x-data-grid-pro");
var _gridCellSelectionSelector = require("./gridCellSelectionSelector");
const cellSelectionStateInitializer = (state, props) => (0, _extends2.default)({}, state, {
cellSelection: (0, _extends2.default)({}, props.cellSelectionModel ?? props.initialState?.cellSelection)
});
exports.cellSelectionStateInitializer = cellSelectionStateInitializer;
function isKeyboardEvent(event) {
return !!event.key;
}
const AUTO_SCROLL_SENSITIVITY = 50; // The distance from the edge to start scrolling
const AUTO_SCROLL_SPEED = 20; // The speed to scroll once the mouse enters the sensitivity area
const useGridCellSelection = (apiRef, props) => {
const hasRootReference = apiRef.current.rootElementRef.current !== null;
const cellWithVirtualFocus = React.useRef(null);
const lastMouseDownCell = React.useRef(null);
const mousePosition = React.useRef(null);
const autoScrollRAF = React.useRef(null);
const totalHeaderHeight = (0, _internals.getTotalHeaderHeight)(apiRef, props);
const ignoreValueFormatterProp = props.ignoreValueFormatterDuringExport;
const ignoreValueFormatter = (typeof ignoreValueFormatterProp === 'object' ? ignoreValueFormatterProp?.clipboardExport : ignoreValueFormatterProp) || false;
const clipboardCopyCellDelimiter = props.clipboardCopyCellDelimiter;
apiRef.current.registerControlState({
stateId: 'cellSelection',
propModel: props.cellSelectionModel,
propOnChange: props.onCellSelectionModelChange,
stateSelector: _gridCellSelectionSelector.gridCellSelectionStateSelector,
changeEvent: 'cellSelectionChange'
});
const runIfCellSelectionIsEnabled = callback => (...args) => {
if (props.cellSelection) {
callback(...args);
}
};
const isCellSelected = React.useCallback((id, field) => {
if (!props.cellSelection) {
return false;
}
const cellSelectionModel = (0, _gridCellSelectionSelector.gridCellSelectionStateSelector)(apiRef);
return cellSelectionModel[id] ? !!cellSelectionModel[id][field] : false;
}, [apiRef, props.cellSelection]);
const getCellSelectionModel = React.useCallback(() => {
return (0, _gridCellSelectionSelector.gridCellSelectionStateSelector)(apiRef);
}, [apiRef]);
const setCellSelectionModel = React.useCallback(newModel => {
if (!props.cellSelection) {
return;
}
apiRef.current.setState(prevState => (0, _extends2.default)({}, prevState, {
cellSelection: newModel
}));
}, [apiRef, props.cellSelection]);
const selectCellRange = React.useCallback((start, end, keepOtherSelected = false) => {
const startRowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(start.id);
const startColumnIndex = apiRef.current.getColumnIndex(start.field);
const endRowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(end.id);
const endColumnIndex = apiRef.current.getColumnIndex(end.field);
let finalStartRowIndex = startRowIndex;
let finalStartColumnIndex = startColumnIndex;
let finalEndRowIndex = endRowIndex;
let finalEndColumnIndex = endColumnIndex;
if (finalStartRowIndex > finalEndRowIndex) {
finalStartRowIndex = endRowIndex;
finalEndRowIndex = startRowIndex;
}
if (finalStartColumnIndex > finalEndColumnIndex) {
finalStartColumnIndex = endColumnIndex;
finalEndColumnIndex = startColumnIndex;
}
const visibleColumns = apiRef.current.getVisibleColumns();
const visibleRows = (0, _internals.getVisibleRows)(apiRef);
const rowsInRange = visibleRows.rows.slice(finalStartRowIndex, finalEndRowIndex + 1);
const columnsInRange = visibleColumns.slice(finalStartColumnIndex, finalEndColumnIndex + 1);
const newModel = keepOtherSelected ? (0, _extends2.default)({}, apiRef.current.getCellSelectionModel()) : {};
rowsInRange.forEach(row => {
if (!newModel[row.id]) {
newModel[row.id] = {};
}
columnsInRange.forEach(column => {
newModel[row.id][column.field] = true;
}, {});
});
apiRef.current.setCellSelectionModel(newModel);
}, [apiRef]);
const getSelectedCellsAsArray = React.useCallback(() => {
const selectionModel = apiRef.current.getCellSelectionModel();
const currentVisibleRows = (0, _internals.getVisibleRows)(apiRef, props);
const sortedEntries = currentVisibleRows.rows.reduce((result, row) => {
if (row.id in selectionModel) {
result.push([row.id, selectionModel[row.id]]);
}
return result;
}, []);
return sortedEntries.reduce((selectedCells, [id, fields]) => {
selectedCells.push(...Object.entries(fields).reduce((selectedFields, [field, isSelected]) => {
if (isSelected) {
selectedFields.push({
id,
field
});
}
return selectedFields;
}, []));
return selectedCells;
}, []);
}, [apiRef, props]);
const cellSelectionApi = {
isCellSelected,
getCellSelectionModel,
setCellSelectionModel,
selectCellRange,
getSelectedCellsAsArray
};
(0, _xDataGridPro.useGridApiMethod)(apiRef, cellSelectionApi, 'public');
const hasClickedValidCellForRangeSelection = React.useCallback(params => {
if (params.field === _xDataGridPro.GRID_CHECKBOX_SELECTION_COL_DEF.field) {
return false;
}
if (params.field === _xDataGridPro.GRID_DETAIL_PANEL_TOGGLE_FIELD) {
return false;
}
const column = apiRef.current.getColumn(params.field);
if (column.type === _xDataGridPro.GRID_ACTIONS_COLUMN_TYPE) {
return false;
}
return params.rowNode.type !== 'pinnedRow';
}, [apiRef]);
const handleMouseUp = (0, _useEventCallback.default)(() => {
lastMouseDownCell.current = null;
apiRef.current.rootElementRef?.current?.classList.remove(_xDataGridPro.gridClasses['root--disableUserSelection']);
// eslint-disable-next-line @typescript-eslint/no-use-before-define
stopAutoScroll();
});
const handleCellMouseDown = React.useCallback((params, event) => {
// Skip if the click comes from the right-button or, only on macOS, Ctrl is pressed
// Fix for https://github.com/mui/mui-x/pull/6567#issuecomment-1329155578
const isMacOs = window.navigator.platform.toUpperCase().indexOf('MAC') >= 0;
if (event.button !== 0 || event.ctrlKey && isMacOs) {
return;
}
if (params.field === _xDataGridPro.GRID_REORDER_COL_DEF.field) {
return;
}
const focusedCell = (0, _xDataGridPro.gridFocusCellSelector)(apiRef);
if (hasClickedValidCellForRangeSelection(params) && event.shiftKey && focusedCell) {
event.preventDefault();
}
lastMouseDownCell.current = {
id: params.id,
field: params.field
};
apiRef.current.rootElementRef?.current?.classList.add(_xDataGridPro.gridClasses['root--disableUserSelection']);
const document = (0, _ownerDocument.default)(apiRef.current.rootElementRef?.current);
document.addEventListener('mouseup', handleMouseUp, {
once: true
});
}, [apiRef, handleMouseUp, hasClickedValidCellForRangeSelection]);
const stopAutoScroll = React.useCallback(() => {
if (autoScrollRAF.current) {
cancelAnimationFrame(autoScrollRAF.current);
autoScrollRAF.current = null;
}
}, []);
const handleCellFocusIn = React.useCallback(params => {
cellWithVirtualFocus.current = {
id: params.id,
field: params.field
};
}, []);
const startAutoScroll = React.useCallback(() => {
if (autoScrollRAF.current) {
return;
}
if (!apiRef.current.virtualScrollerRef?.current) {
return;
}
function autoScroll() {
if (!mousePosition.current || !apiRef.current.virtualScrollerRef?.current) {
return;
}
const dimensions = (0, _xDataGridPro.gridDimensionsSelector)(apiRef);
const {
x: mouseX,
y: mouseY
} = mousePosition.current;
const {
width,
height: viewportOuterHeight
} = dimensions.viewportOuterSize;
const height = viewportOuterHeight - totalHeaderHeight;
let deltaX = 0;
let deltaY = 0;
let factor = 0;
if (mouseY <= AUTO_SCROLL_SENSITIVITY && dimensions.hasScrollY) {
// When scrolling up, the multiplier increases going closer to the top edge
factor = (AUTO_SCROLL_SENSITIVITY - mouseY) / -AUTO_SCROLL_SENSITIVITY;
deltaY = AUTO_SCROLL_SPEED;
} else if (mouseY >= height - AUTO_SCROLL_SENSITIVITY && dimensions.hasScrollY) {
// When scrolling down, the multiplier increases going closer to the bottom edge
factor = (mouseY - (height - AUTO_SCROLL_SENSITIVITY)) / AUTO_SCROLL_SENSITIVITY;
deltaY = AUTO_SCROLL_SPEED;
} else if (mouseX <= AUTO_SCROLL_SENSITIVITY && dimensions.hasScrollX) {
// When scrolling left, the multiplier increases going closer to the left edge
factor = (AUTO_SCROLL_SENSITIVITY - mouseX) / -AUTO_SCROLL_SENSITIVITY;
deltaX = AUTO_SCROLL_SPEED;
} else if (mouseX >= width - AUTO_SCROLL_SENSITIVITY && dimensions.hasScrollX) {
// When scrolling right, the multiplier increases going closer to the right edge
factor = (mouseX - (width - AUTO_SCROLL_SENSITIVITY)) / AUTO_SCROLL_SENSITIVITY;
deltaX = AUTO_SCROLL_SPEED;
}
if (deltaX !== 0 || deltaY !== 0) {
const {
scrollLeft,
scrollTop
} = apiRef.current.virtualScrollerRef.current;
apiRef.current.scroll({
top: scrollTop + deltaY * factor,
left: scrollLeft + deltaX * factor
});
}
autoScrollRAF.current = requestAnimationFrame(autoScroll);
}
autoScroll();
}, [apiRef, totalHeaderHeight]);
const handleCellMouseOver = React.useCallback((params, event) => {
if (!lastMouseDownCell.current) {
return;
}
const {
id,
field
} = params;
apiRef.current.selectCellRange(lastMouseDownCell.current, {
id,
field
}, event.ctrlKey || event.metaKey);
const virtualScrollerRect = apiRef.current.virtualScrollerRef?.current?.getBoundingClientRect();
if (!virtualScrollerRect) {
return;
}
const dimensions = (0, _xDataGridPro.gridDimensionsSelector)(apiRef);
const {
x,
y
} = virtualScrollerRect;
const {
width,
height: viewportOuterHeight
} = dimensions.viewportOuterSize;
const height = viewportOuterHeight - totalHeaderHeight;
const mouseX = event.clientX - x;
const mouseY = event.clientY - y - totalHeaderHeight;
mousePosition.current = {
x: mouseX,
y: mouseY
};
const hasEnteredVerticalSensitivityArea = mouseY <= AUTO_SCROLL_SENSITIVITY || mouseY >= height - AUTO_SCROLL_SENSITIVITY;
const hasEnteredHorizontalSensitivityArea = mouseX <= AUTO_SCROLL_SENSITIVITY || mouseX >= width - AUTO_SCROLL_SENSITIVITY;
const hasEnteredSensitivityArea = hasEnteredVerticalSensitivityArea || hasEnteredHorizontalSensitivityArea;
if (hasEnteredSensitivityArea) {
// Mouse has entered the sensitity area for the first time
startAutoScroll();
} else {
// Mouse has left the sensitivity area while auto scroll is on
stopAutoScroll();
}
}, [apiRef, startAutoScroll, stopAutoScroll, totalHeaderHeight]);
const handleCellClick = (0, _useEventCallback.default)((params, event) => {
const {
id,
field
} = params;
if (!hasClickedValidCellForRangeSelection(params)) {
return;
}
const focusedCell = (0, _xDataGridPro.gridFocusCellSelector)(apiRef);
if (event.shiftKey && focusedCell) {
apiRef.current.selectCellRange(focusedCell, {
id,
field
});
cellWithVirtualFocus.current = {
id,
field
};
return;
}
if (event.ctrlKey || event.metaKey) {
// Add the clicked cell to the selection
const prevModel = apiRef.current.getCellSelectionModel();
apiRef.current.setCellSelectionModel((0, _extends2.default)({}, prevModel, {
[id]: (0, _extends2.default)({}, prevModel[id], {
[field]: !apiRef.current.isCellSelected(id, field)
})
}));
} else {
// Clear the selection and keep only the clicked cell selected
apiRef.current.setCellSelectionModel({
[id]: {
[field]: true
}
});
}
});
const handleCellKeyDown = (0, _useEventCallback.default)((params, event) => {
if (!(0, _internals.isNavigationKey)(event.key) || !cellWithVirtualFocus.current) {
return;
}
if (event.key === ' ' && params.cellMode === _xDataGridPro.GridCellModes.Edit) {
return;
}
if (!event.shiftKey) {
apiRef.current.setCellSelectionModel({});
return;
}
const {
current: otherCell
} = cellWithVirtualFocus;
let endRowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(otherCell.id);
let endColumnIndex = apiRef.current.getColumnIndex(otherCell.field);
if (event.key === 'ArrowDown') {
endRowIndex += 1;
} else if (event.key === 'ArrowUp') {
endRowIndex -= 1;
} else if (event.key === 'ArrowRight') {
endColumnIndex += 1;
} else if (event.key === 'ArrowLeft') {
endColumnIndex -= 1;
}
const visibleRows = (0, _internals.getVisibleRows)(apiRef);
if (endRowIndex < 0 || endRowIndex >= visibleRows.rows.length) {
return;
}
const visibleColumns = apiRef.current.getVisibleColumns();
if (endColumnIndex < 0 || endColumnIndex >= visibleColumns.length) {
return;
}
cellWithVirtualFocus.current = {
id: visibleRows.rows[endRowIndex].id,
field: visibleColumns[endColumnIndex].field
};
apiRef.current.scrollToIndexes({
rowIndex: endRowIndex,
colIndex: endColumnIndex
});
const {
id,
field
} = params;
apiRef.current.selectCellRange({
id,
field
}, cellWithVirtualFocus.current);
});
(0, _xDataGridPro.useGridEvent)(apiRef, 'cellClick', runIfCellSelectionIsEnabled(handleCellClick));
(0, _xDataGridPro.useGridEvent)(apiRef, 'cellFocusIn', runIfCellSelectionIsEnabled(handleCellFocusIn));
(0, _xDataGridPro.useGridEvent)(apiRef, 'cellKeyDown', runIfCellSelectionIsEnabled(handleCellKeyDown));
(0, _xDataGridPro.useGridEvent)(apiRef, 'cellMouseDown', runIfCellSelectionIsEnabled(handleCellMouseDown));
(0, _xDataGridPro.useGridEvent)(apiRef, 'cellMouseOver', runIfCellSelectionIsEnabled(handleCellMouseOver));
React.useEffect(() => {
if (props.cellSelectionModel) {
apiRef.current.setCellSelectionModel(props.cellSelectionModel);
}
}, [apiRef, props.cellSelectionModel]);
React.useEffect(() => {
const rootRef = apiRef.current.rootElementRef?.current;
return () => {
stopAutoScroll();
const document = (0, _ownerDocument.default)(rootRef);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [apiRef, hasRootReference, handleMouseUp, stopAutoScroll]);
const checkIfCellIsSelected = React.useCallback((isSelected, {
id,
field
}) => {
return apiRef.current.isCellSelected(id, field);
}, [apiRef]);
const addClassesToCells = React.useCallback((classes, {
id,
field
}) => {
const visibleRows = (0, _internals.getVisibleRows)(apiRef);
if (!visibleRows.range || !apiRef.current.isCellSelected(id, field)) {
return classes;
}
const newClasses = [...classes];
const rowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(id);
const columnIndex = apiRef.current.getColumnIndex(field);
const visibleColumns = apiRef.current.getVisibleColumns();
if (rowIndex > 0) {
const {
id: previousRowId
} = visibleRows.rows[rowIndex - 1];
if (!apiRef.current.isCellSelected(previousRowId, field)) {
newClasses.push(_xDataGridPro.gridClasses['cell--rangeTop']);
}
} else {
newClasses.push(_xDataGridPro.gridClasses['cell--rangeTop']);
}
if (rowIndex + visibleRows.range.firstRowIndex < visibleRows.range.lastRowIndex) {
const {
id: nextRowId
} = visibleRows.rows[rowIndex + 1];
if (!apiRef.current.isCellSelected(nextRowId, field)) {
newClasses.push(_xDataGridPro.gridClasses['cell--rangeBottom']);
}
} else {
newClasses.push(_xDataGridPro.gridClasses['cell--rangeBottom']);
}
if (columnIndex > 0) {
const {
field: previousColumnField
} = visibleColumns[columnIndex - 1];
if (!apiRef.current.isCellSelected(id, previousColumnField)) {
newClasses.push(_xDataGridPro.gridClasses['cell--rangeLeft']);
}
} else {
newClasses.push(_xDataGridPro.gridClasses['cell--rangeLeft']);
}
if (columnIndex < visibleColumns.length - 1) {
const {
field: nextColumnField
} = visibleColumns[columnIndex + 1];
if (!apiRef.current.isCellSelected(id, nextColumnField)) {
newClasses.push(_xDataGridPro.gridClasses['cell--rangeRight']);
}
} else {
newClasses.push(_xDataGridPro.gridClasses['cell--rangeRight']);
}
return newClasses;
}, [apiRef]);
const canUpdateFocus = React.useCallback((initialValue, {
event,
cell
}) => {
if (!cell || !props.cellSelection || !event.shiftKey) {
return initialValue;
}
if (isKeyboardEvent(event)) {
return (0, _internals.isNavigationKey)(event.key) ? false : initialValue;
}
const focusedCell = (0, _xDataGridPro.gridFocusCellSelector)(apiRef);
if (hasClickedValidCellForRangeSelection(cell) && focusedCell) {
return false;
}
return initialValue;
}, [apiRef, props.cellSelection, hasClickedValidCellForRangeSelection]);
const handleClipboardCopy = React.useCallback(value => {
if (apiRef.current.getSelectedCellsAsArray().length <= 1) {
return value;
}
const sortedRowIds = (0, _xDataGridPro.gridSortedRowIdsSelector)(apiRef);
const cellSelectionModel = apiRef.current.getCellSelectionModel();
const unsortedSelectedRowIds = Object.keys(cellSelectionModel);
const sortedSelectedRowIds = sortedRowIds.filter(id => unsortedSelectedRowIds.includes(`${id}`));
const copyData = sortedSelectedRowIds.reduce((acc, rowId) => {
const fieldsMap = cellSelectionModel[rowId];
const rowValues = Object.keys(fieldsMap).map(field => {
let cellData;
if (fieldsMap[field]) {
const cellParams = apiRef.current.getCellParams(rowId, field);
cellData = (0, _internals.serializeCellValue)(cellParams, {
csvOptions: {
delimiter: clipboardCopyCellDelimiter,
shouldAppendQuotes: false,
escapeFormulas: false
},
ignoreValueFormatter
});
} else {
cellData = '';
}
return cellData;
}, '');
const rowString = rowValues.join(clipboardCopyCellDelimiter);
return acc === '' ? rowString : [acc, rowString].join('\r\n');
}, '');
return copyData;
}, [apiRef, ignoreValueFormatter, clipboardCopyCellDelimiter]);
(0, _internals.useGridRegisterPipeProcessor)(apiRef, 'isCellSelected', checkIfCellIsSelected);
(0, _internals.useGridRegisterPipeProcessor)(apiRef, 'cellClassName', addClassesToCells);
(0, _internals.useGridRegisterPipeProcessor)(apiRef, 'canUpdateFocus', canUpdateFocus);
(0, _internals.useGridRegisterPipeProcessor)(apiRef, 'clipboardCopy', handleClipboardCopy);
};
exports.useGridCellSelection = useGridCellSelection;