UNPKG

@mui/x-data-grid-pro

Version:

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

384 lines (366 loc) 15.6 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 _useTimeout = _interopRequireDefault(require("@mui/utils/useTimeout")); 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"); 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 } }); /** * Hook for row reordering (Pro package) * @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 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.gridExpandedSortedRowIndexLookupSelector); const timeoutRowId = React.useRef(''); const timeout = (0, _useTimeout.default)(); const previousReorderState = React.useRef(EMPTY_REORDER_STATE); const dropTarget = React.useRef({ targetRowId: null, targetRowIndex: null, dropPosition: null }); React.useEffect(() => { return () => { clearTimeout(removeDnDStylesTimeout.current); }; }, []); // TODO: remove sortModel check once row reorder is sorting compatible // remove treeData check once row reorder is treeData compatible const isRowReorderDisabled = React.useMemo(() => { return !props.rowReordering || !!sortModel.length || props.treeData; }, [props.rowReordering, sortModel, props.treeData]); 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 !== undefined && position !== null) { 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); event.dataTransfer.effectAllowed = 'copy'; if (isRowReorderDisabled || Object.keys(editRowsState).length !== 0) { return; } if (timeoutRowId.current) { timeout.clear(); timeoutRowId.current = ''; } 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, applyDraggedState, sortedRowIndexLookup, timeout]); const handleDragOver = React.useCallback((params, event) => { if (dragRowId === '') { return; } const targetNode = (0, _xDataGrid.gridRowNodeSelector)(apiRef, params.id); const sourceNode = (0, _xDataGrid.gridRowNodeSelector)(apiRef, dragRowId); if (!sourceNode || !targetNode || targetNode.type === 'footer' || targetNode.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 (timeoutRowId.current && timeoutRowId.current !== params.id) { timeout.clear(); timeoutRowId.current = ''; } if (targetNode.type === 'group' && targetNode.depth < sourceNode.depth && !targetNode.childrenExpanded && !timeoutRowId.current) { timeout.start(500, () => { const rowNode = (0, _xDataGrid.gridRowNodeSelector)(apiRef, params.id); // TODO: Handle `dataSource` case with https://github.com/mui/mui-x/issues/18947 apiRef.current.setRowChildrenExpansion(params.id, !rowNode.childrenExpanded); }); timeoutRowId.current = params.id; return; } 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'; const currentReorderState = { dragDirection: targetRowIndex < sourceRowIndex ? 'up' : 'down', previousTargetId: params.id, previousDropPosition: dropPosition }; // Update visual indicator when dragging over a different row or position if (previousReorderState.current.previousTargetId !== params.id || previousReorderState.current.previousDropPosition !== dropPosition) { const isSameNode = targetRowIndex === sourceRowIndex; // Check if this is an adjacent position const isAdjacentPosition = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 || dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1; const validatedIndex = apiRef.current.unstable_applyPipeProcessors('getRowReorderTargetIndex', -1, { sourceRowId: dragRowId, targetRowId: params.id, dropPosition, dragDirection: currentReorderState.dragDirection }); // Show drop indicator for valid drops OR adjacent positions OR same node if (validatedIndex !== -1 || isAdjacentPosition || isSameNode) { dropTarget.current = { targetRowId: params.id, targetRowIndex, dropPosition }; applyDropIndicator(params.id, dropPosition); } else { // Clear indicators for invalid drops dropTarget.current = { targetRowId: null, targetRowIndex: null, dropPosition: null }; applyDropIndicator(null, null); } previousReorderState.current = currentReorderState; } // Render the native 'copy' cursor for additional visual feedback if (dropTarget.current.targetRowId === null) { event.dataTransfer.dropEffect = 'none'; } else { event.dataTransfer.dropEffect = 'copy'; } }, [dragRowId, apiRef, logger, timeout, 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; } if (timeoutRowId.current) { timeout.clear(); timeoutRowId.current = ''; } 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 dropTarget.current = { targetRowId: null, targetRowIndex: null, dropPosition: null }; originRowIndex.current = null; setDragRowId(''); return; } if (dropTarget.current.targetRowIndex !== null && dropTarget.current.targetRowId !== null) { const sourceRowIndex = originRowIndex.current; const targetRowIndex = dropTarget.current.targetRowIndex; const validatedIndex = apiRef.current.unstable_applyPipeProcessors('getRowReorderTargetIndex', targetRowIndex, { sourceRowId: dragRowId, targetRowId: dropTarget.current.targetRowId, dropPosition: dropTarget.current.dropPosition, dragDirection: dragDirection }); if (validatedIndex !== -1) { applyRowAnimation(() => { apiRef.current.setRowIndex(dragRowId, validatedIndex); // Emit the rowOrderChange event only once when the reordering stops. const rowOrderChangeParams = { row: apiRef.current.getRow(dragRowId), targetIndex: validatedIndex, oldIndex: sourceRowIndex }; apiRef.current.publishEvent('rowOrderChange', rowOrderChangeParams); }); } } // Reset drop target state dropTarget.current = { targetRowId: null, targetRowIndex: null, dropPosition: null }; setDragRowId(''); }, [apiRef, dragRowId, isRowReorderDisabled, logger, applyDropIndicator, applyDraggedState, timeout, applyRowAnimation]); const getRowReorderTargetIndex = React.useCallback((initialValue, { sourceRowId, targetRowId, dropPosition, dragDirection }) => { if ((0, _xDataGrid.gridRowMaximumTreeDepthSelector)(apiRef) > 1) { return initialValue; } const targetRowIndex = sortedRowIndexLookup[targetRowId]; const sourceRowIndex = sortedRowIndexLookup[sourceRowId]; // Check if this drop would result in no actual movement const isAdjacentNode = dropPosition === 'above' && targetRowIndex === sourceRowIndex + 1 || // dragging to immediately below (above next row) dropPosition === 'below' && targetRowIndex === sourceRowIndex - 1; // dragging to immediately above (below previous row) if (isAdjacentNode || sourceRowIndex === targetRowIndex) { // Return -1 to prevent actual movement (indicators handled separately) return -1; } let finalTargetIndex; if (dragDirection === 'up') { finalTargetIndex = dropPosition === 'above' ? targetRowIndex : targetRowIndex + 1; } else { finalTargetIndex = dropPosition === 'above' ? targetRowIndex - 1 : targetRowIndex; } return finalTargetIndex; }, [apiRef, sortedRowIndexLookup]); (0, _internals.useGridRegisterPipeProcessor)(apiRef, 'getRowReorderTargetIndex', getRowReorderTargetIndex); (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;