@mui/x-data-grid
Version:
The Community plan edition of the Data Grid components (MUI X).
252 lines (246 loc) • 10.3 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils';
import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { useGridLogger } from '../../utils/useGridLogger';
import { gridColumnLookupSelector } from '../columns/gridColumnsSelector';
import { gridSortedRowEntriesSelector, gridSortedRowIdsSelector, gridSortModelSelector } from './gridSortingSelector';
import { GRID_ROOT_GROUP_ID, gridRowTreeSelector } from '../rows';
import { useFirstRender } from '../../utils/useFirstRender';
import { useGridRegisterStrategyProcessor, GRID_DEFAULT_STRATEGY } from '../../core/strategyProcessing';
import { buildAggregatedSortingApplier, mergeStateWithSortModel, getNextGridSortDirection, sanitizeSortModel } from './gridSortingUtils';
import { useGridRegisterPipeProcessor } from '../../core/pipeProcessing';
import { getTreeNodeDescendants } from '../rows/gridRowsUtils';
export const sortingStateInitializer = (state, props) => {
const sortModel = props.sortModel ?? props.initialState?.sorting?.sortModel ?? [];
return _extends({}, state, {
sorting: {
sortModel: sanitizeSortModel(sortModel, props.disableMultipleColumnsSorting),
sortedRows: []
}
});
};
/**
* @requires useGridRows (event)
* @requires useGridColumns (event)
*/
export const useGridSorting = (apiRef, props) => {
const logger = useGridLogger(apiRef, 'useGridSorting');
apiRef.current.registerControlState({
stateId: 'sortModel',
propModel: props.sortModel,
propOnChange: props.onSortModelChange,
stateSelector: gridSortModelSelector,
changeEvent: 'sortModelChange'
});
const upsertSortModel = React.useCallback((field, sortItem) => {
const sortModel = gridSortModelSelector(apiRef);
const existingIdx = sortModel.findIndex(c => c.field === field);
let newSortModel = [...sortModel];
if (existingIdx > -1) {
if (sortItem?.sort == null) {
newSortModel.splice(existingIdx, 1);
} else {
newSortModel.splice(existingIdx, 1, sortItem);
}
} else {
newSortModel = [...sortModel, sortItem];
}
return newSortModel;
}, [apiRef]);
const createSortItem = React.useCallback((col, directionOverride) => {
const sortModel = gridSortModelSelector(apiRef);
const existing = sortModel.find(c => c.field === col.field);
if (existing) {
const nextSort = directionOverride === undefined ? getNextGridSortDirection(col.sortingOrder ?? props.sortingOrder, existing.sort) : directionOverride;
return nextSort === undefined ? undefined : _extends({}, existing, {
sort: nextSort
});
}
return {
field: col.field,
sort: directionOverride === undefined ? getNextGridSortDirection(col.sortingOrder ?? props.sortingOrder) : directionOverride
};
}, [apiRef, props.sortingOrder]);
const addColumnMenuItem = React.useCallback((columnMenuItems, colDef) => {
if (colDef == null || colDef.sortable === false || props.disableColumnSorting) {
return columnMenuItems;
}
const sortingOrder = colDef.sortingOrder || props.sortingOrder;
if (sortingOrder.some(item => !!item)) {
return [...columnMenuItems, 'columnMenuSortItem'];
}
return columnMenuItems;
}, [props.sortingOrder, props.disableColumnSorting]);
/**
* API METHODS
*/
const applySorting = React.useCallback(() => {
apiRef.current.setState(state => {
if (props.sortingMode === 'server') {
logger.debug('Skipping sorting rows as sortingMode = server');
return _extends({}, state, {
sorting: _extends({}, state.sorting, {
sortedRows: getTreeNodeDescendants(gridRowTreeSelector(apiRef), GRID_ROOT_GROUP_ID, false)
})
});
}
const sortModel = gridSortModelSelector(state, apiRef.current.instanceId);
const sortRowList = buildAggregatedSortingApplier(sortModel, apiRef);
const sortedRows = apiRef.current.applyStrategyProcessor('sorting', {
sortRowList
});
return _extends({}, state, {
sorting: _extends({}, state.sorting, {
sortedRows
})
});
});
apiRef.current.publishEvent('sortedRowsSet');
apiRef.current.forceUpdate();
}, [apiRef, logger, props.sortingMode]);
const setSortModel = React.useCallback(model => {
const currentModel = gridSortModelSelector(apiRef);
if (currentModel !== model) {
logger.debug(`Setting sort model`);
apiRef.current.setState(mergeStateWithSortModel(model, props.disableMultipleColumnsSorting));
apiRef.current.forceUpdate();
apiRef.current.applySorting();
}
}, [apiRef, logger, props.disableMultipleColumnsSorting]);
const sortColumn = React.useCallback((field, direction, allowMultipleSorting) => {
const column = apiRef.current.getColumn(field);
const sortItem = createSortItem(column, direction);
let sortModel;
if (!allowMultipleSorting || props.disableMultipleColumnsSorting) {
sortModel = sortItem?.sort == null ? [] : [sortItem];
} else {
sortModel = upsertSortModel(column.field, sortItem);
}
apiRef.current.setSortModel(sortModel);
}, [apiRef, upsertSortModel, createSortItem, props.disableMultipleColumnsSorting]);
const getSortModel = React.useCallback(() => gridSortModelSelector(apiRef), [apiRef]);
const getSortedRows = React.useCallback(() => {
const sortedRows = gridSortedRowEntriesSelector(apiRef);
return sortedRows.map(row => row.model);
}, [apiRef]);
const getSortedRowIds = React.useCallback(() => gridSortedRowIdsSelector(apiRef), [apiRef]);
const getRowIdFromRowIndex = React.useCallback(index => apiRef.current.getSortedRowIds()[index], [apiRef]);
const sortApi = {
getSortModel,
getSortedRows,
getSortedRowIds,
getRowIdFromRowIndex,
setSortModel,
sortColumn,
applySorting
};
useGridApiMethod(apiRef, sortApi, 'public');
/**
* PRE-PROCESSING
*/
const stateExportPreProcessing = React.useCallback((prevState, context) => {
const sortModelToExport = gridSortModelSelector(apiRef);
const shouldExportSortModel =
// Always export if the `exportOnlyDirtyModels` property is not activated
!context.exportOnlyDirtyModels ||
// Always export if the model is controlled
props.sortModel != null ||
// Always export if the model has been initialized
props.initialState?.sorting?.sortModel != null ||
// Export if the model is not empty
sortModelToExport.length > 0;
if (!shouldExportSortModel) {
return prevState;
}
return _extends({}, prevState, {
sorting: {
sortModel: sortModelToExport
}
});
}, [apiRef, props.sortModel, props.initialState?.sorting?.sortModel]);
const stateRestorePreProcessing = React.useCallback((params, context) => {
const sortModel = context.stateToRestore.sorting?.sortModel;
if (sortModel == null) {
return params;
}
apiRef.current.setState(mergeStateWithSortModel(sortModel, props.disableMultipleColumnsSorting));
return _extends({}, params, {
callbacks: [...params.callbacks, apiRef.current.applySorting]
});
}, [apiRef, props.disableMultipleColumnsSorting]);
const flatSortingMethod = React.useCallback(params => {
const rowTree = gridRowTreeSelector(apiRef);
const rootGroupNode = rowTree[GRID_ROOT_GROUP_ID];
const sortedChildren = params.sortRowList ? params.sortRowList(rootGroupNode.children.map(childId => rowTree[childId])) : [...rootGroupNode.children];
if (rootGroupNode.footerId != null) {
sortedChildren.push(rootGroupNode.footerId);
}
return sortedChildren;
}, [apiRef]);
useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing);
useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
useGridRegisterStrategyProcessor(apiRef, GRID_DEFAULT_STRATEGY, 'sorting', flatSortingMethod);
/**
* EVENTS
*/
const handleColumnHeaderClick = React.useCallback(({
field,
colDef
}, event) => {
if (!colDef.sortable || props.disableColumnSorting) {
return;
}
const allowMultipleSorting = event.shiftKey || event.metaKey || event.ctrlKey;
sortColumn(field, undefined, allowMultipleSorting);
}, [sortColumn, props.disableColumnSorting]);
const handleColumnHeaderKeyDown = React.useCallback(({
field,
colDef
}, event) => {
if (!colDef.sortable || props.disableColumnSorting) {
return;
}
// Ctrl + Enter opens the column menu
if (event.key === 'Enter' && !event.ctrlKey && !event.metaKey) {
sortColumn(field, undefined, event.shiftKey);
}
}, [sortColumn, props.disableColumnSorting]);
const handleColumnsChange = React.useCallback(() => {
// When the columns change we check that the sorted columns are still part of the dataset
const sortModel = gridSortModelSelector(apiRef);
const latestColumns = gridColumnLookupSelector(apiRef);
if (sortModel.length > 0) {
const newModel = sortModel.filter(sortItem => latestColumns[sortItem.field]);
if (newModel.length < sortModel.length) {
apiRef.current.setSortModel(newModel);
}
}
}, [apiRef]);
const handleStrategyProcessorChange = React.useCallback(methodName => {
if (methodName === 'sorting') {
apiRef.current.applySorting();
}
}, [apiRef]);
useGridRegisterPipeProcessor(apiRef, 'columnMenu', addColumnMenuItem);
useGridApiEventHandler(apiRef, 'columnHeaderClick', handleColumnHeaderClick);
useGridApiEventHandler(apiRef, 'columnHeaderKeyDown', handleColumnHeaderKeyDown);
useGridApiEventHandler(apiRef, 'rowsSet', apiRef.current.applySorting);
useGridApiEventHandler(apiRef, 'columnsChange', handleColumnsChange);
useGridApiEventHandler(apiRef, 'activeStrategyProcessorChange', handleStrategyProcessorChange);
/**
* 1ST RENDER
*/
useFirstRender(() => {
apiRef.current.applySorting();
});
/**
* EFFECTS
*/
useEnhancedEffect(() => {
if (props.sortModel !== undefined) {
apiRef.current.setSortModel(props.sortModel);
}
}, [apiRef, props.sortModel]);
};