@mui/x-data-grid-pro
Version:
The Pro plan edition of the MUI X Data Grid components.
345 lines (328 loc) • 13.9 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.useGridRowReorder = exports.rowReorderStateInitializer = void 0;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var React = _interopRequireWildcard(require("react"));
var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"));
var _xDataGrid = require("@mui/x-data-grid");
var _internals = require("@mui/x-data-grid/internals");
var _gridRowReorderColDef = require("./gridRowReorderColDef");
var Direction = /*#__PURE__*/function (Direction) {
Direction[Direction["UP"] = 0] = "UP";
Direction[Direction["DOWN"] = 1] = "DOWN";
return Direction;
}(Direction || {});
const EMPTY_REORDER_STATE = {
previousTargetId: null,
dragDirection: null,
previousDropPosition: null
};
const useUtilityClasses = ownerState => {
const {
classes
} = ownerState;
const slots = {
rowDragging: ['row--dragging'],
rowDropAbove: ['row--dropAbove'],
rowDropBelow: ['row--dropBelow'],
rowBeingDragged: ['row--beingDragged']
};
return (0, _composeClasses.default)(slots, _xDataGrid.getDataGridUtilityClass, classes);
};
const rowReorderStateInitializer = state => (0, _extends2.default)({}, state, {
rowReorder: {
isActive: false
}
});
/**
* Only available in DataGridPro
* @requires useGridRows (method)
*/
exports.rowReorderStateInitializer = rowReorderStateInitializer;
const useGridRowReorder = (apiRef, props) => {
const logger = (0, _xDataGrid.useGridLogger)(apiRef, 'useGridRowReorder');
const sortModel = (0, _xDataGrid.useGridSelector)(apiRef, _xDataGrid.gridSortModelSelector);
const treeDepth = (0, _xDataGrid.useGridSelector)(apiRef, _xDataGrid.gridRowMaximumTreeDepthSelector);
const dragRowNode = React.useRef(null);
const originRowIndex = React.useRef(null);
const removeDnDStylesTimeout = React.useRef(undefined);
const previousDropIndicatorRef = React.useRef(null);
const ownerState = {
classes: props.classes
};
const classes = useUtilityClasses(ownerState);
const [dragRowId, setDragRowId] = React.useState('');
const sortedRowIndexLookup = (0, _xDataGrid.useGridSelector)(apiRef, _internals.gridSortedRowIndexLookupSelector);
const previousReorderState = React.useRef(EMPTY_REORDER_STATE);
const [dropTarget, setDropTarget] = React.useState({
targetRowId: null,
targetRowIndex: null,
dropPosition: null
});
React.useEffect(() => {
return () => {
clearTimeout(removeDnDStylesTimeout.current);
};
}, []);
// TODO: remove sortModel check once row reorder is sorting compatible
// remove treeDepth once row reorder is tree compatible
const isRowReorderDisabled = React.useMemo(() => {
return !props.rowReordering || !!sortModel.length || treeDepth !== 1;
}, [props.rowReordering, sortModel, treeDepth]);
const applyDropIndicator = React.useCallback((targetRowId, position) => {
// Remove existing drop indicator from previous target
if (previousDropIndicatorRef.current) {
previousDropIndicatorRef.current.classList.remove(classes.rowDropAbove, classes.rowDropBelow);
previousDropIndicatorRef.current = null;
}
// Apply new drop indicator
if (targetRowId && position) {
const targetRow = apiRef.current.rootElementRef?.current?.querySelector(`[data-id="${targetRowId}"]`);
if (targetRow) {
targetRow.classList.add(position === 'above' ? classes.rowDropAbove : classes.rowDropBelow);
previousDropIndicatorRef.current = targetRow;
}
}
}, [apiRef, classes]);
const applyDraggedState = React.useCallback((rowId, isDragged) => {
if (rowId) {
const draggedRow = apiRef.current.rootElementRef?.current?.querySelector(`[data-id="${rowId}"]`);
if (draggedRow) {
if (isDragged) {
draggedRow.classList.add(classes.rowBeingDragged);
} else {
draggedRow.classList.remove(classes.rowBeingDragged);
}
}
}
}, [apiRef, classes.rowBeingDragged]);
const applyRowAnimation = React.useCallback(callback => {
const rootElement = apiRef.current.rootElementRef?.current;
if (!rootElement) {
return;
}
const visibleRows = rootElement.querySelectorAll('[data-id]');
if (!visibleRows.length) {
return;
}
const rowsArray = Array.from(visibleRows);
const initialPositions = new Map();
rowsArray.forEach(row => {
const rowId = row.getAttribute('data-id');
if (rowId) {
initialPositions.set(rowId, row.getBoundingClientRect());
}
});
callback();
// Use `requestAnimationFrame` to ensure DOM has updated
requestAnimationFrame(() => {
const newRows = rootElement.querySelectorAll('[data-id]');
const animations = [];
newRows.forEach(row => {
const rowId = row.getAttribute('data-id');
if (!rowId) {
return;
}
const prevRect = initialPositions.get(rowId);
if (!prevRect) {
return;
}
const currentRect = row.getBoundingClientRect();
const deltaY = prevRect.top - currentRect.top;
if (Math.abs(deltaY) > 1) {
const animation = row.animate([{
transform: `translateY(${deltaY}px)`
}, {
transform: 'translateY(0)'
}], {
duration: 200,
easing: 'ease-in-out',
fill: 'forwards'
});
animations.push(animation);
}
});
if (animations.length > 0) {
Promise.allSettled(animations.map(a => a.finished)).then(() => {});
}
});
}, [apiRef]);
const handleDragStart = React.useCallback((params, event) => {
// Call the gridEditRowsStateSelector directly to avoid infnite loop
const editRowsState = (0, _internals.gridEditRowsStateSelector)(apiRef);
if (isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
return;
}
logger.debug(`Start dragging row ${params.id}`);
// Prevent drag events propagation.
// For more information check here https://github.com/mui/mui-x/issues/2680.
event.stopPropagation();
apiRef.current.setRowDragActive(true);
dragRowNode.current = event.currentTarget;
// Apply cell-level dragging class to the drag handle
dragRowNode.current.classList.add(classes.rowDragging);
setDragRowId(params.id);
// Apply the dragged state to the entire row
applyDraggedState(params.id, true);
removeDnDStylesTimeout.current = setTimeout(() => {
dragRowNode.current.classList.remove(classes.rowDragging);
});
originRowIndex.current = sortedRowIndexLookup[params.id];
apiRef.current.setCellFocus(params.id, _gridRowReorderColDef.GRID_REORDER_COL_DEF.field);
}, [apiRef, isRowReorderDisabled, logger, classes.rowDragging, sortedRowIndexLookup, applyDraggedState]);
const handleDragOver = React.useCallback((params, event) => {
if (dragRowId === '') {
return;
}
const rowNode = (0, _xDataGrid.gridRowNodeSelector)(apiRef, params.id);
if (!rowNode || rowNode.type === 'footer' || rowNode.type === 'pinnedRow' || !event.target) {
return;
}
// Find the relative 'y' mouse position based on the event.target
const targetRect = event.target.getBoundingClientRect();
const relativeY = Math.floor(event.clientY - targetRect.top);
const midPoint = Math.floor(targetRect.height / 2);
logger.debug(`Dragging over row ${params.id}`);
event.preventDefault();
// Prevent drag events propagation.
// For more information check here https://github.com/mui/mui-x/issues/2680.
event.stopPropagation();
if (params.id !== dragRowId) {
const targetRowIndex = sortedRowIndexLookup[params.id];
const sourceRowIndex = sortedRowIndexLookup[dragRowId];
// Determine drop position based on relativeY position within the row
const dropPosition = relativeY < midPoint ? 'above' : 'below';
// Check if this drop would result in no actual movement
const wouldResultInNoMovement = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 ||
// dragging to immediately below (above next row)
dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1; // dragging to immediately above (below previous row)
const currentReorderState = {
dragDirection: targetRowIndex < sourceRowIndex ? Direction.UP : Direction.DOWN,
previousTargetId: params.id,
previousDropPosition: dropPosition
};
// Only update visual indicator:
// 1. When dragging over a different row
// 2. When it would result in actual movement
if (previousReorderState.current.previousTargetId !== params.id || previousReorderState.current.previousDropPosition !== dropPosition) {
if (wouldResultInNoMovement) {
// Clear any existing indicators since this wouldn't result in movement
setDropTarget({
targetRowId: null,
targetRowIndex: null,
dropPosition: null
});
applyDropIndicator(null, null);
} else {
setDropTarget({
targetRowId: params.id,
targetRowIndex,
dropPosition
});
applyDropIndicator(params.id, dropPosition);
}
previousReorderState.current = currentReorderState;
}
} else if (previousReorderState.current.previousTargetId !== null) {
setDropTarget({
targetRowId: null,
targetRowIndex: null,
dropPosition: null
});
applyDropIndicator(null, null);
previousReorderState.current = {
previousTargetId: null,
dragDirection: null,
previousDropPosition: null
};
}
}, [dragRowId, apiRef, logger, sortedRowIndexLookup, applyDropIndicator]);
const handleDragEnd = React.useCallback((_, event) => {
// Call the gridEditRowsStateSelector directly to avoid infnite loop
const editRowsState = (0, _internals.gridEditRowsStateSelector)(apiRef);
if (dragRowId === '' || isRowReorderDisabled || Object.keys(editRowsState).length !== 0) {
return;
}
logger.debug('End dragging row');
event.preventDefault();
// Prevent drag events propagation.
// For more information check here https://github.com/mui/mui-x/issues/2680.
event.stopPropagation();
clearTimeout(removeDnDStylesTimeout.current);
dragRowNode.current = null;
const dragDirection = previousReorderState.current.dragDirection;
previousReorderState.current = EMPTY_REORDER_STATE;
// Clear visual indicators and dragged state
applyDropIndicator(null, null);
applyDraggedState(dragRowId, false);
apiRef.current.setRowDragActive(false);
// Check if the row was dropped outside the grid.
if (!event.dataTransfer || event.dataTransfer.dropEffect === 'none') {
// Reset drop target state
setDropTarget({
targetRowId: null,
targetRowIndex: null,
dropPosition: null
});
originRowIndex.current = null;
} else {
if (dropTarget.targetRowIndex !== null && dropTarget.targetRowId !== null) {
const sourceRowIndex = originRowIndex.current;
const targetRowIndex = dropTarget.targetRowIndex;
const dropPosition = dropTarget.dropPosition;
// Calculate the correct target index based on drop position
let finalTargetIndex;
if (dragDirection === Direction.UP) {
finalTargetIndex = dropPosition === 'above' ? targetRowIndex : targetRowIndex + 1;
} else {
finalTargetIndex = dropPosition === 'above' ? targetRowIndex - 1 : targetRowIndex;
}
const isReorderInvalid = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 ||
// dragging to immediately below (above next row)
dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1 ||
// dragging to immediately above (below previous row)
dropTarget.targetRowId === dragRowId; // dragging to the same row
if (!isReorderInvalid) {
applyRowAnimation(() => {
apiRef.current.setRowIndex(dragRowId, finalTargetIndex);
// Emit the rowOrderChange event only once when the reordering stops.
const rowOrderChangeParams = {
row: apiRef.current.getRow(dragRowId),
targetIndex: finalTargetIndex,
oldIndex: sourceRowIndex
};
apiRef.current.publishEvent('rowOrderChange', rowOrderChangeParams);
});
}
}
// Reset drop target state
setDropTarget({
targetRowId: null,
targetRowIndex: null,
dropPosition: null
});
}
setDragRowId('');
}, [apiRef, dragRowId, isRowReorderDisabled, logger, dropTarget, applyDropIndicator, applyDraggedState, applyRowAnimation]);
(0, _xDataGrid.useGridEvent)(apiRef, 'rowDragStart', handleDragStart);
(0, _xDataGrid.useGridEvent)(apiRef, 'rowDragOver', handleDragOver);
(0, _xDataGrid.useGridEvent)(apiRef, 'rowDragEnd', handleDragEnd);
(0, _xDataGrid.useGridEvent)(apiRef, 'cellDragOver', handleDragOver);
(0, _xDataGrid.useGridEventPriority)(apiRef, 'rowOrderChange', props.onRowOrderChange);
const setRowDragActive = React.useCallback(isActive => {
apiRef.current.setState(state => (0, _extends2.default)({}, state, {
rowReorder: (0, _extends2.default)({}, state.rowReorder, {
isActive
})
}));
}, [apiRef]);
(0, _xDataGrid.useGridApiMethod)(apiRef, {
setRowDragActive
}, 'private');
};
exports.useGridRowReorder = useGridRowReorder;