UNPKG

@mui/x-data-grid-pro

Version:

The Pro plan edition of the MUI X Data Grid components.

345 lines (328 loc) 13.9 kB
"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;