UNPKG

@mui/x-data-grid

Version:

The Community plan edition of the Data Grid components (MUI X).

313 lines (306 loc) 13.4 kB
import _extends from "@babel/runtime/helpers/esm/extends"; import * as React from 'react'; import { useGridApiMethod } from "../../utils/useGridApiMethod.js"; import { useGridLogger } from "../../utils/useGridLogger.js"; import { gridColumnFieldsSelector, gridColumnDefinitionsSelector, gridColumnLookupSelector, gridColumnsStateSelector, gridColumnVisibilityModelSelector, gridVisibleColumnDefinitionsSelector, gridColumnPositionsSelector } from "./gridColumnsSelector.js"; import { GridSignature } from "../../../constants/signature.js"; import { useGridEvent } from "../../utils/useGridEvent.js"; import { useGridRegisterPipeProcessor, useGridRegisterPipeApplier } from "../../core/pipeProcessing/index.js"; import { EMPTY_PINNED_COLUMN_FIELDS } from "./gridColumnsInterfaces.js"; import { hydrateColumnsWidth, createColumnsState, COLUMNS_DIMENSION_PROPERTIES } from "./gridColumnsUtils.js"; import { GridPreferencePanelsValue } from "../preferencesPanel/index.js"; import { gridPivotActiveSelector } from "../pivoting/index.js"; import { jsx as _jsx } from "react/jsx-runtime"; export const columnsStateInitializer = (state, props, apiRef) => { const columnsState = createColumnsState({ apiRef, columnsToUpsert: props.columns, initialState: props.initialState?.columns, columnVisibilityModel: props.columnVisibilityModel ?? props.initialState?.columns?.columnVisibilityModel ?? {}, keepOnlyColumnsToUpsert: true }); return _extends({}, state, { columns: columnsState, // In pro/premium, this part of the state is defined. We give it an empty but defined value // for the community version. pinnedColumns: state.pinnedColumns ?? EMPTY_PINNED_COLUMN_FIELDS }); }; /** * @requires useGridParamsApi (method) * @requires useGridDimensions (method, event) - can be after * TODO: Impossible priority - useGridParamsApi also needs to be after useGridColumns */ export function useGridColumns(apiRef, props) { const logger = useGridLogger(apiRef, 'useGridColumns'); const previousColumnsProp = React.useRef(props.columns); apiRef.current.registerControlState({ stateId: 'visibleColumns', propModel: props.columnVisibilityModel, propOnChange: props.onColumnVisibilityModelChange, stateSelector: gridColumnVisibilityModelSelector, changeEvent: 'columnVisibilityModelChange' }); const setGridColumnsState = React.useCallback(columnsState => { logger.debug('Updating columns state.'); apiRef.current.setState(mergeColumnsState(columnsState)); apiRef.current.publishEvent('columnsChange', columnsState.orderedFields); }, [logger, apiRef]); /** * API METHODS */ const getColumn = React.useCallback(field => gridColumnLookupSelector(apiRef)[field], [apiRef]); const getAllColumns = React.useCallback(() => gridColumnDefinitionsSelector(apiRef), [apiRef]); const getVisibleColumns = React.useCallback(() => gridVisibleColumnDefinitionsSelector(apiRef), [apiRef]); const getColumnIndex = React.useCallback((field, useVisibleColumns = true) => { const columns = useVisibleColumns ? gridVisibleColumnDefinitionsSelector(apiRef) : gridColumnDefinitionsSelector(apiRef); return columns.findIndex(col => col.field === field); }, [apiRef]); const getColumnPosition = React.useCallback(field => { const index = getColumnIndex(field); return gridColumnPositionsSelector(apiRef)[index]; }, [apiRef, getColumnIndex]); const setColumnVisibilityModel = React.useCallback(model => { const currentModel = gridColumnVisibilityModelSelector(apiRef); if (currentModel !== model) { apiRef.current.setState(state => _extends({}, state, { columns: createColumnsState({ apiRef, columnsToUpsert: [], initialState: undefined, columnVisibilityModel: model, keepOnlyColumnsToUpsert: false }) })); apiRef.current.updateRenderContext?.(); } }, [apiRef]); const updateColumns = React.useCallback(columns => { if (gridPivotActiveSelector(apiRef)) { apiRef.current.updateNonPivotColumns(columns); return; } const columnsState = createColumnsState({ apiRef, columnsToUpsert: columns, initialState: undefined, keepOnlyColumnsToUpsert: false, updateInitialVisibilityModel: true }); setGridColumnsState(columnsState); }, [apiRef, setGridColumnsState]); const setColumnVisibility = React.useCallback((field, isVisible) => { const columnVisibilityModel = gridColumnVisibilityModelSelector(apiRef); const isCurrentlyVisible = columnVisibilityModel[field] ?? true; if (isVisible !== isCurrentlyVisible) { const newModel = _extends({}, columnVisibilityModel, { [field]: isVisible }); apiRef.current.setColumnVisibilityModel(newModel); } }, [apiRef]); const getColumnIndexRelativeToVisibleColumns = React.useCallback(field => { const allColumns = gridColumnFieldsSelector(apiRef); return allColumns.findIndex(col => col === field); }, [apiRef]); const setColumnIndex = React.useCallback((field, targetIndexPosition) => { const allColumns = gridColumnFieldsSelector(apiRef); const oldIndexPosition = getColumnIndexRelativeToVisibleColumns(field); if (oldIndexPosition === targetIndexPosition) { return; } logger.debug(`Moving column ${field} to index ${targetIndexPosition}`); const updatedColumns = [...allColumns]; const fieldRemoved = updatedColumns.splice(oldIndexPosition, 1)[0]; updatedColumns.splice(targetIndexPosition, 0, fieldRemoved); setGridColumnsState(_extends({}, gridColumnsStateSelector(apiRef), { orderedFields: updatedColumns })); const params = { column: apiRef.current.getColumn(field), targetIndex: apiRef.current.getColumnIndexRelativeToVisibleColumns(field), oldIndex: oldIndexPosition }; apiRef.current.publishEvent('columnIndexChange', params); }, [apiRef, logger, setGridColumnsState, getColumnIndexRelativeToVisibleColumns]); const setColumnWidth = React.useCallback((field, width) => { logger.debug(`Updating column ${field} width to ${width}`); const columnsState = gridColumnsStateSelector(apiRef); const column = columnsState.lookup[field]; const newColumn = _extends({}, column, { width, hasBeenResized: true }); setGridColumnsState(hydrateColumnsWidth(_extends({}, columnsState, { lookup: _extends({}, columnsState.lookup, { [field]: newColumn }) }), apiRef.current.getRootDimensions())); apiRef.current.publishEvent('columnWidthChange', { element: apiRef.current.getColumnHeaderElement(field), colDef: newColumn, width }); }, [apiRef, logger, setGridColumnsState]); const columnApi = { getColumn, getAllColumns, getColumnIndex, getColumnPosition, getVisibleColumns, getColumnIndexRelativeToVisibleColumns, updateColumns, setColumnVisibilityModel, setColumnVisibility, setColumnWidth }; const columnReorderApi = { setColumnIndex }; useGridApiMethod(apiRef, columnApi, 'public'); useGridApiMethod(apiRef, columnReorderApi, props.signature === GridSignature.DataGrid ? 'private' : 'public'); /** * PRE-PROCESSING */ const stateExportPreProcessing = React.useCallback((prevState, context) => { const columnsStateToExport = {}; const columnVisibilityModelToExport = gridColumnVisibilityModelSelector(apiRef); const shouldExportColumnVisibilityModel = // Always export if the `exportOnlyDirtyModels` property is not activated !context.exportOnlyDirtyModels || // Always export if the model is controlled props.columnVisibilityModel != null || // Always export if the model has been initialized // TODO v6 Do a nullish check instead to export even if the initial model equals "{}" Object.keys(props.initialState?.columns?.columnVisibilityModel ?? {}).length > 0 || // Always export if the model is not empty Object.keys(columnVisibilityModelToExport).length > 0; if (shouldExportColumnVisibilityModel) { columnsStateToExport.columnVisibilityModel = columnVisibilityModelToExport; } columnsStateToExport.orderedFields = gridColumnFieldsSelector(apiRef); const columns = gridColumnDefinitionsSelector(apiRef); const dimensions = {}; columns.forEach(colDef => { if (colDef.hasBeenResized) { const colDefDimensions = {}; COLUMNS_DIMENSION_PROPERTIES.forEach(propertyName => { let propertyValue = colDef[propertyName]; if (propertyValue === Infinity) { propertyValue = -1; } colDefDimensions[propertyName] = propertyValue; }); dimensions[colDef.field] = colDefDimensions; } }); if (Object.keys(dimensions).length > 0) { columnsStateToExport.dimensions = dimensions; } return _extends({}, prevState, { columns: columnsStateToExport }); }, [apiRef, props.columnVisibilityModel, props.initialState?.columns]); const stateRestorePreProcessing = React.useCallback((params, context) => { const columnVisibilityModelToImport = context.stateToRestore.columns?.columnVisibilityModel; const initialState = context.stateToRestore.columns; if (columnVisibilityModelToImport == null && initialState == null) { return params; } const columnsState = createColumnsState({ apiRef, columnsToUpsert: [], initialState, columnVisibilityModel: columnVisibilityModelToImport, keepOnlyColumnsToUpsert: false }); apiRef.current.setState(mergeColumnsState(columnsState)); if (initialState != null) { apiRef.current.publishEvent('columnsChange', columnsState.orderedFields); } return params; }, [apiRef]); const preferencePanelPreProcessing = React.useCallback((initialValue, value) => { if (value === GridPreferencePanelsValue.columns) { const ColumnsPanel = props.slots.columnsPanel; return /*#__PURE__*/_jsx(ColumnsPanel, _extends({}, props.slotProps?.columnsPanel)); } return initialValue; }, [props.slots.columnsPanel, props.slotProps?.columnsPanel]); const addColumnMenuItems = React.useCallback(columnMenuItems => { const isPivotActive = gridPivotActiveSelector(apiRef); if (props.disableColumnSelector || isPivotActive) { return columnMenuItems; } return [...columnMenuItems, 'columnMenuColumnsItem']; }, [props.disableColumnSelector, apiRef]); useGridRegisterPipeProcessor(apiRef, 'columnMenu', addColumnMenuItems); useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing); useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing); useGridRegisterPipeProcessor(apiRef, 'preferencePanel', preferencePanelPreProcessing); /* * EVENTS */ const prevInnerWidth = React.useRef(null); const handleGridSizeChange = size => { if (prevInnerWidth.current !== size.width) { prevInnerWidth.current = size.width; const hasFlexColumns = gridVisibleColumnDefinitionsSelector(apiRef).some(col => col.flex && col.flex > 0); if (!hasFlexColumns) { return; } setGridColumnsState(hydrateColumnsWidth(gridColumnsStateSelector(apiRef), apiRef.current.getRootDimensions())); } }; useGridEvent(apiRef, 'viewportInnerSizeChange', handleGridSizeChange); /** * APPLIERS */ const hydrateColumns = React.useCallback(() => { logger.info(`Columns pipe processing have changed, regenerating the columns`); const columnsState = createColumnsState({ apiRef, columnsToUpsert: [], initialState: undefined, keepOnlyColumnsToUpsert: false }); setGridColumnsState(columnsState); }, [apiRef, logger, setGridColumnsState]); useGridRegisterPipeApplier(apiRef, 'hydrateColumns', hydrateColumns); /* * EFFECTS */ // The effect do not track any value defined synchronously during the 1st render by hooks called after `useGridColumns` // As a consequence, the state generated by the 1st run of this useEffect will always be equal to the initialization one const isFirstRender = React.useRef(true); React.useEffect(() => { if (isFirstRender.current) { isFirstRender.current = false; return; } logger.info(`GridColumns have changed, new length ${props.columns.length}`); if (previousColumnsProp.current === props.columns) { return; } const columnsState = createColumnsState({ apiRef, initialState: undefined, // If the user provides a model, we don't want to set it in the state here because it has it's dedicated `useEffect` which calls `setColumnVisibilityModel` columnsToUpsert: props.columns, keepOnlyColumnsToUpsert: true, updateInitialVisibilityModel: true }); previousColumnsProp.current = props.columns; setGridColumnsState(columnsState); }, [logger, apiRef, setGridColumnsState, props.columns]); React.useEffect(() => { if (props.columnVisibilityModel !== undefined) { apiRef.current.setColumnVisibilityModel(props.columnVisibilityModel); } }, [apiRef, logger, props.columnVisibilityModel]); } function mergeColumnsState(columnsState) { return state => _extends({}, state, { columns: columnsState }); }