wip:milestone 0 fixes
Some checks failed
CI/CD Pipeline / unit-tests (push) Failing after 1m16s
CI/CD Pipeline / integration-tests (push) Failing after 2m32s
CI/CD Pipeline / lint (push) Successful in 5m22s
CI/CD Pipeline / e2e-tests (push) Has been skipped
CI/CD Pipeline / build (push) Has been skipped

This commit is contained in:
2026-03-15 12:35:42 +02:00
parent 6708cf28a7
commit cffdf8af86
61266 changed files with 4511646 additions and 1938 deletions

View File

@@ -0,0 +1 @@
export type { GridPipeProcessingLookup } from './pipeProcessing';

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,126 @@
import * as React from 'react';
import { GridCellIndexCoordinates, GridScrollParams, GridColDef, GridCellCoordinates, GridCellParams, GridEditMode } from '../../../models';
import { GridInitialStateCommunity } from '../../../models/gridStateCommunity';
import { GridExportStateParams, GridRestoreStatePreProcessingContext, GridRestoreStatePreProcessingValue } from '../../features/statePersistence/gridStatePersistenceInterface';
import { GridHydrateColumnsValue } from '../../features/columns/gridColumnsInterfaces';
import { GridRowEntry, GridRowId } from '../../../models/gridRows';
import { GridHydrateRowsValue } from '../../features/rows/gridRowsInterfaces';
import { GridPreferencePanelsValue } from '../../features/preferencesPanel';
export type GridPipeProcessorGroup = keyof GridPipeProcessingLookup;
export interface GridPipeProcessingLookup {
columnMenu: {
value: Array<string>;
context: GridColDef;
};
exportState: {
value: GridInitialStateCommunity;
context: GridExportStateParams;
};
hydrateColumns: {
value: GridHydrateColumnsValue;
};
hydrateRows: {
value: GridHydrateRowsValue;
};
exportMenu: {
value: {
component: React.ReactElement;
componentName: string;
}[];
context: any;
};
preferencePanel: {
value: React.ReactNode;
context: GridPreferencePanelsValue;
};
restoreState: {
value: GridRestoreStatePreProcessingValue;
context: GridRestoreStatePreProcessingContext<GridInitialStateCommunity>;
};
rowHeight: {
value: Record<string, number>;
context: GridRowEntry;
};
scrollToIndexes: {
value: Partial<GridScrollParams>;
context: Partial<GridCellIndexCoordinates>;
};
rowClassName: {
value: string[];
context: GridRowId;
};
cellClassName: {
value: string[];
context: GridCellCoordinates;
};
isCellSelected: {
value: boolean;
context: GridCellCoordinates;
};
canUpdateFocus: {
value: boolean;
context: {
event: MouseEvent | React.KeyboardEvent;
cell: GridCellParams | null;
};
};
clipboardCopy: {
value: string;
};
canStartEditing: {
value: boolean;
context: {
event: React.KeyboardEvent;
cellParams: GridCellParams;
editMode: GridEditMode;
};
};
}
export type GridPipeProcessor<P extends GridPipeProcessorGroup> = (value: GridPipeProcessingLookup[P]['value'], context: GridPipeProcessingLookup[P] extends {
context: any;
} ? GridPipeProcessingLookup[P]['context'] : undefined) => GridPipeProcessingLookup[P]['value'];
type GridPipeProcessorsApplierArgs<P extends GridPipeProcessorGroup, T extends {
value: any;
}> = T extends {
context: any;
} ? [P, T['value'], T['context']] : [P, T['value']];
type GridPipeProcessorsApplier = <P extends GridPipeProcessorGroup>(...params: GridPipeProcessorsApplierArgs<P, GridPipeProcessingLookup[P]>) => GridPipeProcessingLookup[P]['value'];
export interface GridPipeProcessingApi {
/**
* Run all the processors registered for the given group.
* @template T
* @param {GridPipeProcessorGroup} group The group from which we want to apply the processors.
* @param {T['value']} value The initial value to pass to the first processor.
* @param {T['context]} context Context object that will be passed to each processor.
* @returns {T['value]} The value after passing through all pre-processors.
* @ignore - do not document.
*/
unstable_applyPipeProcessors: GridPipeProcessorsApplier;
}
export interface GridPipeProcessingPrivateApi {
/**
* Register a processor and run all the appliers of the group.
* @param {GridPipeProcessorGroup} group The group on which this processor should be applied.
* @param {string} id An unique and static identifier of the processor.
* @param {GridPipeProcessor} processor The processor to register.
* @returns {() => void} A function to unregister the processor.
*/
registerPipeProcessor: <G extends GridPipeProcessorGroup>(group: GridPipeProcessorGroup, id: string, processor: GridPipeProcessor<G>) => () => void;
/**
* Register an applier.
* @param {GridPipeProcessorGroup} group The group of this applier
* @param {string} id An unique and static identifier of the applier.
* @param {() => void} applier The applier to register.
* @returns {() => void} A function to unregister the applier.
*/
registerPipeApplier: (group: GridPipeProcessorGroup, id: string, applier: () => void) => () => void;
/**
* Imperatively run all the appliers of a group.
* Most of the time, the applier should run because a processor is re-registered,
* but sometimes we want to re-apply the processing even if the processor deps have not changed.
* This may occur when the change requires a `isDeepEqual` check.
* @param {GridPipeProcessorGroup} group The group to apply.
*/
requestPipeProcessorsApplication: (group: GridPipeProcessorGroup) => void;
}
export {};

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,4 @@
export * from './gridPipeProcessingApi';
export * from './useGridPipeProcessing';
export * from './useGridRegisterPipeProcessor';
export * from './useGridRegisterPipeApplier';

View File

@@ -0,0 +1,4 @@
export * from './gridPipeProcessingApi';
export * from './useGridPipeProcessing';
export * from './useGridRegisterPipeProcessor';
export * from './useGridRegisterPipeApplier';

View File

@@ -0,0 +1,32 @@
import * as React from 'react';
import { GridPrivateApiCommon } from '../../../models/api/gridApiCommon';
/**
* Implement the Pipeline Pattern
*
* More information and detailed example in (TODO add link to technical doc when ready)
*
* Some plugins contains custom logic to enrich data provided by other plugins or components.
* For instance, the row grouping plugin needs to add / remove the grouping columns when the grid columns are updated.
*
* =====================================================================================================================
*
* The plugin containing the custom logic must use:
*
* - `useGridRegisterPipeProcessor` to register their processor.
*
* - `apiRef.current.requestPipeProcessorsApplication` to imperatively re-apply a group.
* This method should be used in last resort.
* Most of the time, the application should be triggered by an update on the deps of the processor.
*
* =====================================================================================================================
*
* The plugin or component that needs to enrich its data must use:
*
* - `apiRef.current.unstable_applyPipeProcessors` to run in chain all the processors of a given group.
*
* - `useGridRegisterPipeApplier` to re-apply the whole pipe when requested.
* The applier will be called when:
* * a processor is registered.
* * `apiRef.current.requestPipeProcessorsApplication` is called for the given group.
*/
export declare const useGridPipeProcessing: (apiRef: React.MutableRefObject<GridPrivateApiCommon>) => void;

View File

@@ -0,0 +1,105 @@
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import _toPropertyKey from "@babel/runtime/helpers/esm/toPropertyKey";
import * as React from 'react';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
/**
* Implement the Pipeline Pattern
*
* More information and detailed example in (TODO add link to technical doc when ready)
*
* Some plugins contains custom logic to enrich data provided by other plugins or components.
* For instance, the row grouping plugin needs to add / remove the grouping columns when the grid columns are updated.
*
* =====================================================================================================================
*
* The plugin containing the custom logic must use:
*
* - `useGridRegisterPipeProcessor` to register their processor.
*
* - `apiRef.current.requestPipeProcessorsApplication` to imperatively re-apply a group.
* This method should be used in last resort.
* Most of the time, the application should be triggered by an update on the deps of the processor.
*
* =====================================================================================================================
*
* The plugin or component that needs to enrich its data must use:
*
* - `apiRef.current.unstable_applyPipeProcessors` to run in chain all the processors of a given group.
*
* - `useGridRegisterPipeApplier` to re-apply the whole pipe when requested.
* The applier will be called when:
* * a processor is registered.
* * `apiRef.current.requestPipeProcessorsApplication` is called for the given group.
*/
export const useGridPipeProcessing = apiRef => {
const processorsCache = React.useRef({});
const isRunning = React.useRef(false);
const runAppliers = React.useCallback(groupCache => {
if (isRunning.current || !groupCache) {
return;
}
isRunning.current = true;
Object.values(groupCache.appliers).forEach(callback => {
callback();
});
isRunning.current = false;
}, []);
const registerPipeProcessor = React.useCallback((group, id, processor) => {
if (!processorsCache.current[group]) {
processorsCache.current[group] = {
processors: new Map(),
appliers: {}
};
}
const groupCache = processorsCache.current[group];
const oldProcessor = groupCache.processors.get(id);
if (oldProcessor !== processor) {
groupCache.processors.set(id, processor);
runAppliers(groupCache);
}
return () => {
processorsCache.current[group].processors.set(id, null);
};
}, [runAppliers]);
const registerPipeApplier = React.useCallback((group, id, applier) => {
if (!processorsCache.current[group]) {
processorsCache.current[group] = {
processors: new Map(),
appliers: {}
};
}
processorsCache.current[group].appliers[id] = applier;
return () => {
const _appliers = processorsCache.current[group].appliers,
otherAppliers = _objectWithoutPropertiesLoose(_appliers, [id].map(_toPropertyKey));
processorsCache.current[group].appliers = otherAppliers;
};
}, []);
const requestPipeProcessorsApplication = React.useCallback(group => {
const groupCache = processorsCache.current[group];
runAppliers(groupCache);
}, [runAppliers]);
const applyPipeProcessors = React.useCallback((...args) => {
const [group, value, context] = args;
if (!processorsCache.current[group]) {
return value;
}
const preProcessors = Array.from(processorsCache.current[group].processors.values());
return preProcessors.reduce((acc, preProcessor) => {
if (!preProcessor) {
return acc;
}
return preProcessor(acc, context);
}, value);
}, []);
const preProcessingPrivateApi = {
registerPipeProcessor,
registerPipeApplier,
requestPipeProcessorsApplication
};
const preProcessingPublicApi = {
unstable_applyPipeProcessors: applyPipeProcessors
};
useGridApiMethod(apiRef, preProcessingPrivateApi, 'private');
useGridApiMethod(apiRef, preProcessingPublicApi, 'public');
};

View File

@@ -0,0 +1,3 @@
import * as React from 'react';
import { GridPrivateApiCommon } from '../../../models/api/gridApiCommon';
export declare const useGridRegisterPipeApplier: <PrivateApi extends GridPrivateApiCommon, G extends keyof import("./gridPipeProcessingApi").GridPipeProcessingLookup>(apiRef: React.MutableRefObject<PrivateApi>, group: G, callback: () => void) => void;

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
import { useFirstRender } from '../../utils/useFirstRender';
export const useGridRegisterPipeApplier = (apiRef, group, callback) => {
const cleanup = React.useRef();
const id = React.useRef(`mui-${Math.round(Math.random() * 1e9)}`);
const registerPreProcessor = React.useCallback(() => {
cleanup.current = apiRef.current.registerPipeApplier(group, id.current, callback);
}, [apiRef, callback, group]);
useFirstRender(() => {
registerPreProcessor();
});
const isFirstRender = React.useRef(true);
React.useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
registerPreProcessor();
}
return () => {
if (cleanup.current) {
cleanup.current();
cleanup.current = null;
}
};
}, [registerPreProcessor]);
};

View File

@@ -0,0 +1,4 @@
import * as React from 'react';
import { GridPrivateApiCommon } from '../../../models/api/gridApiCommon';
import { GridPipeProcessor } from './gridPipeProcessingApi';
export declare const useGridRegisterPipeProcessor: <PrivateApi extends GridPrivateApiCommon, G extends keyof import("./gridPipeProcessingApi").GridPipeProcessingLookup>(apiRef: React.MutableRefObject<PrivateApi>, group: G, callback: GridPipeProcessor<G>) => void;

View File

@@ -0,0 +1,26 @@
import * as React from 'react';
import { useFirstRender } from '../../utils/useFirstRender';
export const useGridRegisterPipeProcessor = (apiRef, group, callback) => {
const cleanup = React.useRef();
const id = React.useRef(`mui-${Math.round(Math.random() * 1e9)}`);
const registerPreProcessor = React.useCallback(() => {
cleanup.current = apiRef.current.registerPipeProcessor(group, id.current, callback);
}, [apiRef, callback, group]);
useFirstRender(() => {
registerPreProcessor();
});
const isFirstRender = React.useRef(true);
React.useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
registerPreProcessor();
}
return () => {
if (cleanup.current) {
cleanup.current();
cleanup.current = null;
}
};
}, [registerPreProcessor]);
};

View File

@@ -0,0 +1,63 @@
import { GridRowTreeCreationParams, GridRowTreeCreationValue, GridRowsState } from '../../features/rows/gridRowsInterfaces';
import { GridFilteringMethodParams, GridFilteringMethodValue, GridFilterState, GridVisibleRowsLookupState } from '../../features/filter/gridFilterState';
import { GridSortingMethodParams, GridSortingMethodValue } from '../../features/sorting/gridSortingState';
export type GridStrategyProcessorName = keyof GridStrategyProcessingLookup;
export type GridStrategyGroup = GridStrategyProcessingLookup[keyof GridStrategyProcessingLookup]['group'];
export interface GridStrategyProcessingLookup {
rowTreeCreation: {
group: 'rowTree';
params: GridRowTreeCreationParams;
value: GridRowTreeCreationValue;
};
filtering: {
group: 'rowTree';
params: GridFilteringMethodParams;
value: GridFilteringMethodValue;
};
sorting: {
group: 'rowTree';
params: GridSortingMethodParams;
value: GridSortingMethodValue;
};
visibleRowsLookupCreation: {
group: 'rowTree';
params: {
tree: GridRowsState['tree'];
filteredRowsLookup: GridFilterState['filteredRowsLookup'];
};
value: GridVisibleRowsLookupState;
};
}
export type GridStrategyProcessor<P extends GridStrategyProcessorName> = (params: GridStrategyProcessingLookup[P]['params']) => GridStrategyProcessingLookup[P]['value'];
export interface GridStrategyProcessingApi {
/**
* Registers a strategy processor.
* If the strategy is active, it emits an event to notify the agents to re-apply the processor.
* @template P
* @param {string} strategyName The name of the strategy on which this processor should be applied.
* @param {GridStrategyProcessorName} processorName The name of the processor.
* @param {GridStrategyProcessor<P>} processor The processor to register.
* @returns {() => void} A function to unregister the processor.
*/
registerStrategyProcessor: <P extends GridStrategyProcessorName>(strategyName: string, processorName: P, processor: GridStrategyProcessor<P>) => () => void;
/**
* Set a callback to know if a strategy is available.
* @param {GridStrategyGroup} strategyGroup The group for which we set strategy availability.
* @param {string} strategyName The name of the strategy.
* @param {boolean} callback A callback to know if this strategy is available.
*/
setStrategyAvailability: (strategyGroup: GridStrategyGroup, strategyName: string, callback: () => boolean) => void;
/**
* Returns the name of the active strategy of a given strategy group
* @param {GridStrategyGroup} strategyGroup The group from which we want the active strategy.
* @returns {string} The name of the active strategy.
*/
getActiveStrategy: (strategyGroup: GridStrategyGroup) => string;
/**
* Run the processor registered for the active strategy.
* @param {GridStrategyProcessorName} processorName The name of the processor to run.
* @param {GridStrategyProcessingLookup[P]['params']} params Additional params to pass to the processor.
* @returns {GridStrategyProcessingLookup[P]['value']} The value returned by the processor.
*/
applyStrategyProcessor: <P extends GridStrategyProcessorName>(processorName: P, params: GridStrategyProcessingLookup[P]['params']) => GridStrategyProcessingLookup[P]['value'];
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,3 @@
export * from './gridStrategyProcessingApi';
export * from './useGridRegisterStrategyProcessor';
export * from './useGridStrategyProcessing';

View File

@@ -0,0 +1,3 @@
export * from './gridStrategyProcessingApi';
export * from './useGridRegisterStrategyProcessor';
export * from './useGridStrategyProcessing';

View File

@@ -0,0 +1,4 @@
import * as React from 'react';
import { GridPrivateApiCommon } from '../../../models/api/gridApiCommon';
import { GridStrategyProcessor } from './gridStrategyProcessingApi';
export declare const useGridRegisterStrategyProcessor: <Api extends GridPrivateApiCommon, G extends keyof import("./gridStrategyProcessingApi").GridStrategyProcessingLookup>(apiRef: React.MutableRefObject<Api>, strategyName: string, group: G, processor: GridStrategyProcessor<G>) => void;

View File

@@ -0,0 +1,18 @@
import * as React from 'react';
import { useFirstRender } from '../../utils/useFirstRender';
export const useGridRegisterStrategyProcessor = (apiRef, strategyName, group, processor) => {
const registerPreProcessor = React.useCallback(() => {
apiRef.current.registerStrategyProcessor(strategyName, group, processor);
}, [apiRef, processor, group, strategyName]);
useFirstRender(() => {
registerPreProcessor();
});
const isFirstRender = React.useRef(true);
React.useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
registerPreProcessor();
}
}, [registerPreProcessor]);
};

View File

@@ -0,0 +1,48 @@
import * as React from 'react';
import { GridPrivateApiCommon } from '../../../models/api/gridApiCommon';
import { GridStrategyProcessorName, GridStrategyProcessingLookup } from './gridStrategyProcessingApi';
export declare const GRID_DEFAULT_STRATEGY = "none";
export declare const GRID_STRATEGIES_PROCESSORS: {
[P in GridStrategyProcessorName]: GridStrategyProcessingLookup[P]['group'];
};
/**
* Implements a variant of the Strategy Pattern (see https://en.wikipedia.org/wiki/Strategy_pattern)
*
* More information and detailed example in (TODO add link to technical doc when ready)
*
* Some plugins contains custom logic that must only be applied if the right strategy is active.
* For instance, the row grouping plugin has a custom filtering algorithm.
* This algorithm must be applied by the filtering plugin if the row grouping is the current way of grouping rows,
* but not if the tree data is the current way of grouping rows.
*
* =====================================================================================================================
*
* The plugin containing the custom logic must use:
*
* - `useGridRegisterStrategyProcessor` to register their processor.
* When the processor of the active strategy changes, it will fire `"activeStrategyProcessorChange"` to re-apply the processor.
*
* - `apiRef.current.setStrategyAvailability` to tell if their strategy can be used.
*
* =====================================================================================================================
*
* The plugin or component that needs to apply the custom logic of the current strategy must use:
*
* - `apiRef.current.applyStrategyProcessor` to run the processor of the active strategy for a given processor name.
*
* - the "strategyAvailabilityChange" event to update something when the active strategy changes.
* Warning: Be careful not to apply the processor several times.
* For instance "rowsSet" is fired by `useGridRows` whenever the active strategy changes.
* So listening to both would most likely run your logic twice.
*
* - The "activeStrategyProcessorChange" event to update something when the processor of the active strategy changes.
*
* =====================================================================================================================
*
* Each processor name is part of a strategy group which can only have one active strategy at the time.
* For now, there is only one strategy group named `rowTree` which customize
* - row tree creation algorithm.
* - sorting algorithm.
* - filtering algorithm.
*/
export declare const useGridStrategyProcessing: (apiRef: React.MutableRefObject<GridPrivateApiCommon>) => void;

View File

@@ -0,0 +1,112 @@
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import _toPropertyKey from "@babel/runtime/helpers/esm/toPropertyKey";
import * as React from 'react';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
export const GRID_DEFAULT_STRATEGY = 'none';
export const GRID_STRATEGIES_PROCESSORS = {
rowTreeCreation: 'rowTree',
filtering: 'rowTree',
sorting: 'rowTree',
visibleRowsLookupCreation: 'rowTree'
};
/**
* Implements a variant of the Strategy Pattern (see https://en.wikipedia.org/wiki/Strategy_pattern)
*
* More information and detailed example in (TODO add link to technical doc when ready)
*
* Some plugins contains custom logic that must only be applied if the right strategy is active.
* For instance, the row grouping plugin has a custom filtering algorithm.
* This algorithm must be applied by the filtering plugin if the row grouping is the current way of grouping rows,
* but not if the tree data is the current way of grouping rows.
*
* =====================================================================================================================
*
* The plugin containing the custom logic must use:
*
* - `useGridRegisterStrategyProcessor` to register their processor.
* When the processor of the active strategy changes, it will fire `"activeStrategyProcessorChange"` to re-apply the processor.
*
* - `apiRef.current.setStrategyAvailability` to tell if their strategy can be used.
*
* =====================================================================================================================
*
* The plugin or component that needs to apply the custom logic of the current strategy must use:
*
* - `apiRef.current.applyStrategyProcessor` to run the processor of the active strategy for a given processor name.
*
* - the "strategyAvailabilityChange" event to update something when the active strategy changes.
* Warning: Be careful not to apply the processor several times.
* For instance "rowsSet" is fired by `useGridRows` whenever the active strategy changes.
* So listening to both would most likely run your logic twice.
*
* - The "activeStrategyProcessorChange" event to update something when the processor of the active strategy changes.
*
* =====================================================================================================================
*
* Each processor name is part of a strategy group which can only have one active strategy at the time.
* For now, there is only one strategy group named `rowTree` which customize
* - row tree creation algorithm.
* - sorting algorithm.
* - filtering algorithm.
*/
export const useGridStrategyProcessing = apiRef => {
const availableStrategies = React.useRef(new Map());
const strategiesCache = React.useRef({});
const registerStrategyProcessor = React.useCallback((strategyName, processorName, processor) => {
const cleanup = () => {
const _ref = strategiesCache.current[processorName],
otherProcessors = _objectWithoutPropertiesLoose(_ref, [strategyName].map(_toPropertyKey));
strategiesCache.current[processorName] = otherProcessors;
};
if (!strategiesCache.current[processorName]) {
strategiesCache.current[processorName] = {};
}
const groupPreProcessors = strategiesCache.current[processorName];
const previousProcessor = groupPreProcessors[strategyName];
groupPreProcessors[strategyName] = processor;
if (!previousProcessor || previousProcessor === processor) {
return cleanup;
}
if (strategyName === apiRef.current.getActiveStrategy(GRID_STRATEGIES_PROCESSORS[processorName])) {
apiRef.current.publishEvent('activeStrategyProcessorChange', processorName);
}
return cleanup;
}, [apiRef]);
const applyStrategyProcessor = React.useCallback((processorName, params) => {
const activeStrategy = apiRef.current.getActiveStrategy(GRID_STRATEGIES_PROCESSORS[processorName]);
if (activeStrategy == null) {
throw new Error("Can't apply a strategy processor before defining an active strategy");
}
const groupCache = strategiesCache.current[processorName];
if (!groupCache || !groupCache[activeStrategy]) {
throw new Error(`No processor found for processor "${processorName}" on strategy "${activeStrategy}"`);
}
const processor = groupCache[activeStrategy];
return processor(params);
}, [apiRef]);
const getActiveStrategy = React.useCallback(strategyGroup => {
var _availableStrategyEnt;
const strategyEntries = Array.from(availableStrategies.current.entries());
const availableStrategyEntry = strategyEntries.find(([, strategy]) => {
if (strategy.group !== strategyGroup) {
return false;
}
return strategy.isAvailable();
});
return (_availableStrategyEnt = availableStrategyEntry == null ? void 0 : availableStrategyEntry[0]) != null ? _availableStrategyEnt : GRID_DEFAULT_STRATEGY;
}, []);
const setStrategyAvailability = React.useCallback((strategyGroup, strategyName, isAvailable) => {
availableStrategies.current.set(strategyName, {
group: strategyGroup,
isAvailable
});
apiRef.current.publishEvent('strategyAvailabilityChange');
}, [apiRef]);
const strategyProcessingApi = {
registerStrategyProcessor,
applyStrategyProcessor,
getActiveStrategy,
setStrategyAvailability
};
useGridApiMethod(apiRef, strategyProcessingApi, 'private');
};

View File

@@ -0,0 +1,5 @@
import * as React from 'react';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
import type { GridApiCommon, GridPrivateApiCommon } from '../../models/api/gridApiCommon';
export declare function unwrapPrivateAPI<PrivateApi extends GridPrivateApiCommon, Api extends GridApiCommon>(publicApi: Api): PrivateApi;
export declare function useGridApiInitialization<PrivateApi extends GridPrivateApiCommon, Api extends GridApiCommon>(inputApiRef: React.MutableRefObject<Api> | undefined, props: Pick<DataGridProcessedProps, 'signature'>): React.MutableRefObject<PrivateApi>;

View File

@@ -0,0 +1,110 @@
import * as React from 'react';
import { Store } from '../../utils/Store';
import { useGridApiMethod } from '../utils/useGridApiMethod';
import { GridSignature } from '../utils/useGridApiEventHandler';
import { EventManager } from '../../utils/EventManager';
const SYMBOL_API_PRIVATE = Symbol('mui.api_private');
const isSyntheticEvent = event => {
return event.isPropagationStopped !== undefined;
};
export function unwrapPrivateAPI(publicApi) {
return publicApi[SYMBOL_API_PRIVATE];
}
let globalId = 0;
function createPrivateAPI(publicApiRef) {
var _publicApiRef$current;
const existingPrivateApi = (_publicApiRef$current = publicApiRef.current) == null ? void 0 : _publicApiRef$current[SYMBOL_API_PRIVATE];
if (existingPrivateApi) {
return existingPrivateApi;
}
const state = {};
const privateApi = {
state,
store: Store.create(state),
instanceId: {
id: globalId
}
};
globalId += 1;
privateApi.getPublicApi = () => publicApiRef.current;
privateApi.register = (visibility, methods) => {
Object.keys(methods).forEach(methodName => {
const method = methods[methodName];
const currentPrivateMethod = privateApi[methodName];
if ((currentPrivateMethod == null ? void 0 : currentPrivateMethod.spying) === true) {
currentPrivateMethod.target = method;
} else {
privateApi[methodName] = method;
}
if (visibility === 'public') {
const publicApi = publicApiRef.current;
const currentPublicMethod = publicApi[methodName];
if ((currentPublicMethod == null ? void 0 : currentPublicMethod.spying) === true) {
currentPublicMethod.target = method;
} else {
publicApi[methodName] = method;
}
}
});
};
privateApi.register('private', {
caches: {},
eventManager: new EventManager()
});
return privateApi;
}
function createPublicAPI(privateApiRef) {
const publicApi = {
get state() {
return privateApiRef.current.state;
},
get store() {
return privateApiRef.current.store;
},
get instanceId() {
return privateApiRef.current.instanceId;
},
[SYMBOL_API_PRIVATE]: privateApiRef.current
};
return publicApi;
}
export function useGridApiInitialization(inputApiRef, props) {
const publicApiRef = React.useRef();
const privateApiRef = React.useRef();
if (!privateApiRef.current) {
privateApiRef.current = createPrivateAPI(publicApiRef);
}
if (!publicApiRef.current) {
publicApiRef.current = createPublicAPI(privateApiRef);
}
const publishEvent = React.useCallback((...args) => {
const [name, params, event = {}] = args;
event.defaultMuiPrevented = false;
if (isSyntheticEvent(event) && event.isPropagationStopped()) {
return;
}
const details = props.signature === GridSignature.DataGridPro ? {
api: privateApiRef.current.getPublicApi()
} : {};
privateApiRef.current.eventManager.emit(name, params, event, details);
}, [privateApiRef, props.signature]);
const subscribeEvent = React.useCallback((event, handler, options) => {
privateApiRef.current.eventManager.on(event, handler, options);
const api = privateApiRef.current;
return () => {
api.eventManager.removeListener(event, handler);
};
}, [privateApiRef]);
useGridApiMethod(privateApiRef, {
subscribeEvent,
publishEvent
}, 'public');
React.useImperativeHandle(inputApiRef, () => publicApiRef.current, [publicApiRef]);
React.useEffect(() => {
const api = privateApiRef.current;
return () => {
api.publishEvent('unmount');
};
}, [privateApiRef]);
return privateApiRef;
}

View File

@@ -0,0 +1,7 @@
import * as React from 'react';
import type { GridApiCommon, GridPrivateApiCommon } from '../../models/api/gridApiCommon';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
/**
* Initialize the technical pieces of the DataGrid (logger, state, ...) that any DataGrid implementation needs
*/
export declare const useGridInitialization: <PrivateApi extends GridPrivateApiCommon, Api extends GridApiCommon<any, any>>(inputApiRef: React.MutableRefObject<Api> | undefined, props: DataGridProcessedProps) => React.MutableRefObject<PrivateApi>;

View File

@@ -0,0 +1,22 @@
import { useGridLoggerFactory } from './useGridLoggerFactory';
import { useGridApiInitialization } from './useGridApiInitialization';
import { useGridLocaleText } from './useGridLocaleText';
import { useGridPipeProcessing } from './pipeProcessing';
import { useGridStrategyProcessing } from './strategyProcessing';
import { useGridStateInitialization } from './useGridStateInitialization';
/**
* Initialize the technical pieces of the DataGrid (logger, state, ...) that any DataGrid implementation needs
*/
export const useGridInitialization = (inputApiRef, props) => {
const privateApiRef = useGridApiInitialization(inputApiRef, props);
useGridLoggerFactory(privateApiRef, props);
useGridStateInitialization(privateApiRef, props);
useGridPipeProcessing(privateApiRef);
useGridStrategyProcessing(privateApiRef);
useGridLocaleText(privateApiRef, props);
privateApiRef.current.register('private', {
rootProps: props
});
return privateApiRef;
};

View File

@@ -0,0 +1,4 @@
import * as React from 'react';
import { GridPrivateApiCommon } from '../../models/api/gridApiCommon';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
export declare const useGridLocaleText: (apiRef: React.MutableRefObject<GridPrivateApiCommon>, props: Pick<DataGridProcessedProps, 'localeText'>) => void;

View File

@@ -0,0 +1,12 @@
import * as React from 'react';
export const useGridLocaleText = (apiRef, props) => {
const getLocaleText = React.useCallback(key => {
if (props.localeText[key] == null) {
throw new Error(`Missing translation for key ${key}.`);
}
return props.localeText[key];
}, [props.localeText]);
apiRef.current.register('public', {
getLocaleText
});
};

View File

@@ -0,0 +1,4 @@
import * as React from 'react';
import { GridPrivateApiCommon } from '../../models/api/gridApiCommon';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
export declare const useGridLoggerFactory: (apiRef: React.MutableRefObject<GridPrivateApiCommon>, props: Pick<DataGridProcessedProps, 'logger' | 'logLevel'>) => void;

View File

@@ -0,0 +1,44 @@
import * as React from 'react';
import { localStorageAvailable } from '../../utils/utils';
import { useGridApiMethod } from '../utils';
const forceDebug = localStorageAvailable() && window.localStorage.getItem('DEBUG') != null;
const noop = () => {};
const noopLogger = {
debug: noop,
info: noop,
warn: noop,
error: noop
};
const LOG_LEVELS = ['debug', 'info', 'warn', 'error'];
function getAppender(name, logLevel, appender = console) {
const minLogLevelIdx = LOG_LEVELS.indexOf(logLevel);
if (minLogLevelIdx === -1) {
throw new Error(`MUI: Log level ${logLevel} not recognized.`);
}
const logger = LOG_LEVELS.reduce((loggerObj, method, idx) => {
if (idx >= minLogLevelIdx) {
loggerObj[method] = (...args) => {
const [message, ...other] = args;
appender[method](`MUI: ${name} - ${message}`, ...other);
};
} else {
loggerObj[method] = noop;
}
return loggerObj;
}, {});
return logger;
}
export const useGridLoggerFactory = (apiRef, props) => {
const getLogger = React.useCallback(name => {
if (forceDebug) {
return getAppender(name, 'debug', props.logger);
}
if (!props.logLevel) {
return noopLogger;
}
return getAppender(name, props.logLevel.toString(), props.logger);
}, [props.logLevel, props.logger]);
useGridApiMethod(apiRef, {
getLogger
}, 'private');
};

View File

@@ -0,0 +1,4 @@
import * as React from 'react';
import { DataGridProcessedProps } from '../../models/props/DataGridProps';
import type { GridPrivateApiCommon } from '../../models/api/gridApiCommon';
export declare const useGridStateInitialization: <PrivateApi extends GridPrivateApiCommon>(apiRef: React.MutableRefObject<PrivateApi>, props: Pick<DataGridProcessedProps, 'signature'>) => void;

View File

@@ -0,0 +1,99 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { GridSignature } from '../utils/useGridApiEventHandler';
import { useGridApiMethod } from '../utils';
import { isFunction } from '../../utils/utils';
export const useGridStateInitialization = (apiRef, props) => {
const controlStateMapRef = React.useRef({});
const [, rawForceUpdate] = React.useState();
const registerControlState = React.useCallback(controlStateItem => {
controlStateMapRef.current[controlStateItem.stateId] = controlStateItem;
}, []);
const setState = React.useCallback((state, reason) => {
let newState;
if (isFunction(state)) {
newState = state(apiRef.current.state);
} else {
newState = state;
}
if (apiRef.current.state === newState) {
return false;
}
let ignoreSetState = false;
// Apply the control state constraints
const updatedControlStateIds = [];
Object.keys(controlStateMapRef.current).forEach(stateId => {
const controlState = controlStateMapRef.current[stateId];
const oldSubState = controlState.stateSelector(apiRef.current.state, apiRef.current.instanceId);
const newSubState = controlState.stateSelector(newState, apiRef.current.instanceId);
if (newSubState === oldSubState) {
return;
}
updatedControlStateIds.push({
stateId: controlState.stateId,
hasPropChanged: newSubState !== controlState.propModel
});
// The state is controlled, the prop should always win
if (controlState.propModel !== undefined && newSubState !== controlState.propModel) {
ignoreSetState = true;
}
});
if (updatedControlStateIds.length > 1) {
// Each hook modify its own state, and it should not leak
// Events are here to forward to other hooks and apply changes.
// You are trying to update several states in a no isolated way.
throw new Error(`You're not allowed to update several sub-state in one transaction. You already updated ${updatedControlStateIds[0].stateId}, therefore, you're not allowed to update ${updatedControlStateIds.map(el => el.stateId).join(', ')} in the same transaction.`);
}
if (!ignoreSetState) {
// We always assign it as we mutate rows for perf reason.
apiRef.current.state = newState;
if (apiRef.current.publishEvent) {
apiRef.current.publishEvent('stateChange', newState);
}
apiRef.current.store.update(newState);
}
if (updatedControlStateIds.length === 1) {
const {
stateId,
hasPropChanged
} = updatedControlStateIds[0];
const controlState = controlStateMapRef.current[stateId];
const model = controlState.stateSelector(newState, apiRef.current.instanceId);
if (controlState.propOnChange && hasPropChanged) {
const details = props.signature === GridSignature.DataGridPro ? {
api: apiRef.current,
reason
} : {
reason
};
controlState.propOnChange(model, details);
}
if (!ignoreSetState) {
apiRef.current.publishEvent(controlState.changeEvent, model, {
reason
});
}
}
return !ignoreSetState;
}, [apiRef, props.signature]);
const updateControlState = React.useCallback((key, state, reason) => {
return apiRef.current.setState(previousState => {
return _extends({}, previousState, {
[key]: state(previousState[key])
});
}, reason);
}, [apiRef]);
const forceUpdate = React.useCallback(() => rawForceUpdate(() => apiRef.current.state), [apiRef]);
const publicStateApi = {
setState,
forceUpdate
};
const privateStateApi = {
updateControlState,
registerControlState
};
useGridApiMethod(apiRef, publicStateApi, 'public');
useGridApiMethod(apiRef, privateStateApi, 'private');
};

View File

@@ -0,0 +1,8 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import type { DataGridProcessedProps } from '../../../models/props/DataGridProps';
/**
* @requires useGridCsvExport (method)
* @requires useGridSelection (method)
*/
export declare const useGridClipboard: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'unstable_ignoreValueFormatterDuringExport' | 'onClipboardCopy' | 'clipboardCopyCellDelimiter'>) => void;

View File

@@ -0,0 +1,96 @@
import * as React from 'react';
import { useGridApiOptionHandler, useGridNativeEventListener } from '../../utils';
import { gridFocusCellSelector } from '../focus/gridFocusStateSelector';
import { serializeCellValue } from '../export/serializers/csvSerializer';
function writeToClipboardPolyfill(data) {
const span = document.createElement('span');
span.style.whiteSpace = 'pre';
span.style.userSelect = 'all';
span.style.opacity = '0px';
span.textContent = data;
document.body.appendChild(span);
const range = document.createRange();
range.selectNode(span);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
try {
document.execCommand('copy');
} finally {
document.body.removeChild(span);
}
}
function copyToClipboard(data) {
if (navigator.clipboard) {
navigator.clipboard.writeText(data).catch(() => {
writeToClipboardPolyfill(data);
});
} else {
writeToClipboardPolyfill(data);
}
}
function hasNativeSelection(element) {
var _window$getSelection;
// When getSelection is called on an <iframe> that is not displayed Firefox will return null.
if ((_window$getSelection = window.getSelection()) != null && _window$getSelection.toString()) {
return true;
}
// window.getSelection() returns an empty string in Firefox for selections inside a form element.
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=85686.
// Instead, we can use element.selectionStart that is only defined on form elements.
if (element && (element.selectionEnd || 0) - (element.selectionStart || 0) > 0) {
return true;
}
return false;
}
/**
* @requires useGridCsvExport (method)
* @requires useGridSelection (method)
*/
export const useGridClipboard = (apiRef, props) => {
const ignoreValueFormatterProp = props.unstable_ignoreValueFormatterDuringExport;
const ignoreValueFormatter = (typeof ignoreValueFormatterProp === 'object' ? ignoreValueFormatterProp == null ? void 0 : ignoreValueFormatterProp.clipboardExport : ignoreValueFormatterProp) || false;
const clipboardCopyCellDelimiter = props.clipboardCopyCellDelimiter;
const handleCopy = React.useCallback(event => {
if (!((event.ctrlKey || event.metaKey) && event.key === 'c')) {
return;
}
// Do nothing if there's a native selection
if (hasNativeSelection(event.target)) {
return;
}
let textToCopy = '';
const selectedRows = apiRef.current.getSelectedRows();
if (selectedRows.size > 0) {
textToCopy = apiRef.current.getDataAsCsv({
includeHeaders: false,
delimiter: clipboardCopyCellDelimiter,
shouldAppendQuotes: false,
escapeFormulas: false
});
} else {
const focusedCell = gridFocusCellSelector(apiRef);
if (focusedCell) {
const cellParams = apiRef.current.getCellParams(focusedCell.id, focusedCell.field);
textToCopy = serializeCellValue(cellParams, {
csvOptions: {
delimiter: clipboardCopyCellDelimiter,
shouldAppendQuotes: false,
escapeFormulas: false
},
ignoreValueFormatter
});
}
}
textToCopy = apiRef.current.unstable_applyPipeProcessors('clipboardCopy', textToCopy);
if (textToCopy) {
copyToClipboard(textToCopy);
apiRef.current.publishEvent('clipboardCopy', textToCopy);
}
}, [apiRef, ignoreValueFormatter, clipboardCopyCellDelimiter]);
useGridNativeEventListener(apiRef, apiRef.current.rootElementRef, 'keydown', handleCopy);
useGridApiOptionHandler(apiRef, 'clipboardCopy', props.onClipboardCopy);
};

View File

@@ -0,0 +1,16 @@
import { GridColumnGroup } from '../../../models/gridColumnGrouping';
export type GridColumnGroupLookup = {
[groupId: string]: Omit<GridColumnGroup, 'children'>;
};
export type GridGroupingStructure = {
groupId: null | string;
columnFields: string[];
};
export interface GridColumnsGroupingState {
lookup: GridColumnGroupLookup;
headerStructure: GridGroupingStructure[][];
unwrappedGroupingModel: {
[columnField: string]: GridColumnGroup['groupId'][];
};
maxDepth: number;
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,12 @@
import { GridStateCommunity } from '../../../models/gridStateCommunity';
/**
* @category ColumnGrouping
* @ignore - do not document.
*/
export declare const gridColumnGroupingSelector: (state: GridStateCommunity) => import("./gridColumnGroupsInterfaces").GridColumnsGroupingState;
export declare const gridColumnGroupsUnwrappedModelSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, {
[columnField: string]: string[];
}>;
export declare const gridColumnGroupsLookupSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, import("./gridColumnGroupsInterfaces").GridColumnGroupLookup>;
export declare const gridColumnGroupsHeaderStructureSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, import("./gridColumnGroupsInterfaces").GridGroupingStructure[][]>;
export declare const gridColumnGroupsHeaderMaxDepthSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, number>;

View File

@@ -0,0 +1,22 @@
import { createSelector, createSelectorMemoized } from '../../../utils/createSelector';
/**
* @category ColumnGrouping
* @ignore - do not document.
*/
export const gridColumnGroupingSelector = state => state.columnGrouping;
export const gridColumnGroupsUnwrappedModelSelector = createSelectorMemoized(gridColumnGroupingSelector, columnGrouping => {
var _columnGrouping$unwra;
return (_columnGrouping$unwra = columnGrouping == null ? void 0 : columnGrouping.unwrappedGroupingModel) != null ? _columnGrouping$unwra : {};
});
export const gridColumnGroupsLookupSelector = createSelectorMemoized(gridColumnGroupingSelector, columnGrouping => {
var _columnGrouping$looku;
return (_columnGrouping$looku = columnGrouping == null ? void 0 : columnGrouping.lookup) != null ? _columnGrouping$looku : {};
});
export const gridColumnGroupsHeaderStructureSelector = createSelectorMemoized(gridColumnGroupingSelector, columnGrouping => {
var _columnGrouping$heade;
return (_columnGrouping$heade = columnGrouping == null ? void 0 : columnGrouping.headerStructure) != null ? _columnGrouping$heade : [];
});
export const gridColumnGroupsHeaderMaxDepthSelector = createSelector(gridColumnGroupingSelector, columnGrouping => {
var _columnGrouping$maxDe;
return (_columnGrouping$maxDe = columnGrouping == null ? void 0 : columnGrouping.maxDepth) != null ? _columnGrouping$maxDe : 0;
});

View File

@@ -0,0 +1,18 @@
import { GridColumnGroupingModel, GridColumnGroup } from '../../../models/gridColumnGrouping';
import { GridColDef } from '../../../models/colDef';
import { GridGroupingStructure } from './gridColumnGroupsInterfaces';
type UnwrappedGroupingModel = {
[key: GridColDef['field']]: GridColumnGroup['groupId'][];
};
/**
* This is a function that provide for each column the array of its parents.
* Parents are ordered from the root to the leaf.
* @param columnGroupingModel The model such as provided in DataGrid props
* @returns An object `{[field]: groupIds}` where `groupIds` is the parents of the column `field`
*/
export declare const unwrapGroupingColumnModel: (columnGroupingModel?: GridColumnGroupingModel) => UnwrappedGroupingModel;
export declare const getColumnGroupsHeaderStructure: (orderedColumns: string[], unwrappedGroupingModel: UnwrappedGroupingModel, pinnedFields: {
right?: string[];
left?: string[];
}) => GridGroupingStructure[][];
export {};

View File

@@ -0,0 +1,86 @@
import { isLeaf } from '../../../models/gridColumnGrouping';
import { isDeepEqual } from '../../../utils/utils';
// This is the recurrence function that help writing `unwrapGroupingColumnModel()`
const recurrentUnwrapGroupingColumnModel = (columnGroupNode, parents, unwrappedGroupingModelToComplete) => {
if (isLeaf(columnGroupNode)) {
if (unwrappedGroupingModelToComplete[columnGroupNode.field] !== undefined) {
throw new Error([`MUI: columnGroupingModel contains duplicated field`, `column field ${columnGroupNode.field} occurs two times in the grouping model:`, `- ${unwrappedGroupingModelToComplete[columnGroupNode.field].join(' > ')}`, `- ${parents.join(' > ')}`].join('\n'));
}
unwrappedGroupingModelToComplete[columnGroupNode.field] = parents;
return;
}
const {
groupId,
children
} = columnGroupNode;
children.forEach(child => {
recurrentUnwrapGroupingColumnModel(child, [...parents, groupId], unwrappedGroupingModelToComplete);
});
};
/**
* This is a function that provide for each column the array of its parents.
* Parents are ordered from the root to the leaf.
* @param columnGroupingModel The model such as provided in DataGrid props
* @returns An object `{[field]: groupIds}` where `groupIds` is the parents of the column `field`
*/
export const unwrapGroupingColumnModel = columnGroupingModel => {
if (!columnGroupingModel) {
return {};
}
const unwrappedSubTree = {};
columnGroupingModel.forEach(columnGroupNode => {
recurrentUnwrapGroupingColumnModel(columnGroupNode, [], unwrappedSubTree);
});
return unwrappedSubTree;
};
export const getColumnGroupsHeaderStructure = (orderedColumns, unwrappedGroupingModel, pinnedFields) => {
const getParents = field => {
var _unwrappedGroupingMod;
return (_unwrappedGroupingMod = unwrappedGroupingModel[field]) != null ? _unwrappedGroupingMod : [];
};
const groupingHeaderStructure = [];
const maxDepth = Math.max(...orderedColumns.map(field => getParents(field).length));
const haveSameParents = (field1, field2, depth) => isDeepEqual(getParents(field1).slice(0, depth + 1), getParents(field2).slice(0, depth + 1));
const haveDifferentContainers = (field1, field2) => {
if (pinnedFields != null && pinnedFields.left && pinnedFields.left.includes(field1) && !pinnedFields.left.includes(field2)) {
return true;
}
if (pinnedFields != null && pinnedFields.right && !pinnedFields.right.includes(field1) && pinnedFields.right.includes(field2)) {
return true;
}
return false;
};
for (let depth = 0; depth < maxDepth; depth += 1) {
const depthStructure = orderedColumns.reduce((structure, newField) => {
var _getParents$depth;
const groupId = (_getParents$depth = getParents(newField)[depth]) != null ? _getParents$depth : null;
if (structure.length === 0) {
return [{
columnFields: [newField],
groupId
}];
}
const lastGroup = structure[structure.length - 1];
const prevField = lastGroup.columnFields[lastGroup.columnFields.length - 1];
const prevGroupId = lastGroup.groupId;
if (prevGroupId !== groupId || !haveSameParents(prevField, newField, depth) ||
// Fix for https://github.com/mui/mui-x/issues/7041
haveDifferentContainers(prevField, newField)) {
// It's a new group
return [...structure, {
columnFields: [newField],
groupId
}];
}
// It extends the previous group
return [...structure.slice(0, structure.length - 1), {
columnFields: [...lastGroup.columnFields, newField],
groupId
}];
}, []);
groupingHeaderStructure.push(depthStructure);
}
return groupingHeaderStructure;
};

View File

@@ -0,0 +1,2 @@
export * from './gridColumnGroupsSelector';
export type { GridColumnsGroupingState } from './gridColumnGroupsInterfaces';

View File

@@ -0,0 +1,2 @@
export * from './gridColumnGroupsSelector';
export {};

View File

@@ -0,0 +1,10 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
import { GridStateInitializer } from '../../utils/useGridInitializeState';
export declare const columnGroupsStateInitializer: GridStateInitializer<Pick<DataGridProcessedProps, 'columnGroupingModel' | 'experimentalFeatures'>>;
/**
* @requires useGridColumns (method, event)
* @requires useGridParamsApi (method)
*/
export declare const useGridColumnGrouping: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'columnGroupingModel' | 'experimentalFeatures'>) => void;

View File

@@ -0,0 +1,147 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
const _excluded = ["groupId", "children"];
import * as React from 'react';
import { isLeaf } from '../../../models/gridColumnGrouping';
import { gridColumnGroupsLookupSelector, gridColumnGroupsUnwrappedModelSelector } from './gridColumnGroupsSelector';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { getColumnGroupsHeaderStructure, unwrapGroupingColumnModel } from './gridColumnGroupsUtils';
import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
import { gridColumnFieldsSelector, gridVisibleColumnFieldsSelector } from '../columns';
const createGroupLookup = columnGroupingModel => {
let groupLookup = {};
columnGroupingModel.forEach(node => {
if (isLeaf(node)) {
return;
}
const {
groupId,
children
} = node,
other = _objectWithoutPropertiesLoose(node, _excluded);
if (!groupId) {
throw new Error('MUI: An element of the columnGroupingModel does not have either `field` or `groupId`.');
}
if (!children) {
console.warn(`MUI: group groupId=${groupId} has no children.`);
}
const groupParam = _extends({}, other, {
groupId
});
const subTreeLookup = createGroupLookup(children);
if (subTreeLookup[groupId] !== undefined || groupLookup[groupId] !== undefined) {
throw new Error(`MUI: The groupId ${groupId} is used multiple times in the columnGroupingModel.`);
}
groupLookup = _extends({}, groupLookup, subTreeLookup, {
[groupId]: groupParam
});
});
return _extends({}, groupLookup);
};
export const columnGroupsStateInitializer = (state, props, apiRef) => {
var _props$experimentalFe, _props$columnGrouping, _props$columnGrouping2, _apiRef$current$state;
if (!((_props$experimentalFe = props.experimentalFeatures) != null && _props$experimentalFe.columnGrouping)) {
return state;
}
const columnFields = gridColumnFieldsSelector(apiRef);
const visibleColumnFields = gridVisibleColumnFieldsSelector(apiRef);
const groupLookup = createGroupLookup((_props$columnGrouping = props.columnGroupingModel) != null ? _props$columnGrouping : []);
const unwrappedGroupingModel = unwrapGroupingColumnModel((_props$columnGrouping2 = props.columnGroupingModel) != null ? _props$columnGrouping2 : []);
const columnGroupsHeaderStructure = getColumnGroupsHeaderStructure(columnFields, unwrappedGroupingModel, // @ts-expect-error Move this part to `Pro` package
(_apiRef$current$state = apiRef.current.state.pinnedColumns) != null ? _apiRef$current$state : {});
const maxDepth = visibleColumnFields.length === 0 ? 0 : Math.max(...visibleColumnFields.map(field => {
var _unwrappedGroupingMod, _unwrappedGroupingMod2;
return (_unwrappedGroupingMod = (_unwrappedGroupingMod2 = unwrappedGroupingModel[field]) == null ? void 0 : _unwrappedGroupingMod2.length) != null ? _unwrappedGroupingMod : 0;
}));
return _extends({}, state, {
columnGrouping: {
lookup: groupLookup,
unwrappedGroupingModel,
headerStructure: columnGroupsHeaderStructure,
maxDepth
}
});
};
/**
* @requires useGridColumns (method, event)
* @requires useGridParamsApi (method)
*/
export const useGridColumnGrouping = (apiRef, props) => {
var _props$experimentalFe3;
/**
* API METHODS
*/
const getColumnGroupPath = React.useCallback(field => {
var _unwrappedGroupingMod3;
const unwrappedGroupingModel = gridColumnGroupsUnwrappedModelSelector(apiRef);
return (_unwrappedGroupingMod3 = unwrappedGroupingModel[field]) != null ? _unwrappedGroupingMod3 : [];
}, [apiRef]);
const getAllGroupDetails = React.useCallback(() => {
const columnGroupLookup = gridColumnGroupsLookupSelector(apiRef);
return columnGroupLookup;
}, [apiRef]);
const columnGroupingApi = {
unstable_getColumnGroupPath: getColumnGroupPath,
unstable_getAllGroupDetails: getAllGroupDetails
};
useGridApiMethod(apiRef, columnGroupingApi, 'public');
const handleColumnIndexChange = React.useCallback(() => {
var _props$columnGrouping3;
const unwrappedGroupingModel = unwrapGroupingColumnModel((_props$columnGrouping3 = props.columnGroupingModel) != null ? _props$columnGrouping3 : []);
apiRef.current.setState(state => {
var _state$columns$ordere, _state$columns, _state$pinnedColumns;
const orderedFields = (_state$columns$ordere = (_state$columns = state.columns) == null ? void 0 : _state$columns.orderedFields) != null ? _state$columns$ordere : [];
// @ts-expect-error Move this logic to `Pro` package
const pinnedColumns = (_state$pinnedColumns = state.pinnedColumns) != null ? _state$pinnedColumns : {};
const columnGroupsHeaderStructure = getColumnGroupsHeaderStructure(orderedFields, unwrappedGroupingModel, pinnedColumns);
return _extends({}, state, {
columnGrouping: _extends({}, state.columnGrouping, {
headerStructure: columnGroupsHeaderStructure
})
});
});
}, [apiRef, props.columnGroupingModel]);
const updateColumnGroupingState = React.useCallback(columnGroupingModel => {
var _props$experimentalFe2, _apiRef$current$getPi, _apiRef$current$getPi2, _apiRef$current;
if (!((_props$experimentalFe2 = props.experimentalFeatures) != null && _props$experimentalFe2.columnGrouping)) {
return;
}
// @ts-expect-error Move this logic to `Pro` package
const pinnedColumns = (_apiRef$current$getPi = (_apiRef$current$getPi2 = (_apiRef$current = apiRef.current).getPinnedColumns) == null ? void 0 : _apiRef$current$getPi2.call(_apiRef$current)) != null ? _apiRef$current$getPi : {};
const columnFields = gridColumnFieldsSelector(apiRef);
const visibleColumnFields = gridVisibleColumnFieldsSelector(apiRef);
const groupLookup = createGroupLookup(columnGroupingModel != null ? columnGroupingModel : []);
const unwrappedGroupingModel = unwrapGroupingColumnModel(columnGroupingModel != null ? columnGroupingModel : []);
const columnGroupsHeaderStructure = getColumnGroupsHeaderStructure(columnFields, unwrappedGroupingModel, pinnedColumns);
const maxDepth = visibleColumnFields.length === 0 ? 0 : Math.max(...visibleColumnFields.map(field => {
var _unwrappedGroupingMod4, _unwrappedGroupingMod5;
return (_unwrappedGroupingMod4 = (_unwrappedGroupingMod5 = unwrappedGroupingModel[field]) == null ? void 0 : _unwrappedGroupingMod5.length) != null ? _unwrappedGroupingMod4 : 0;
}));
apiRef.current.setState(state => {
return _extends({}, state, {
columnGrouping: {
lookup: groupLookup,
unwrappedGroupingModel,
headerStructure: columnGroupsHeaderStructure,
maxDepth
}
});
});
}, [apiRef, (_props$experimentalFe3 = props.experimentalFeatures) == null ? void 0 : _props$experimentalFe3.columnGrouping]);
useGridApiEventHandler(apiRef, 'columnIndexChange', handleColumnIndexChange);
useGridApiEventHandler(apiRef, 'columnsChange', () => {
updateColumnGroupingState(props.columnGroupingModel);
});
useGridApiEventHandler(apiRef, 'columnVisibilityModelChange', () => {
updateColumnGroupingState(props.columnGroupingModel);
});
/**
* EFFECTS
*/
React.useEffect(() => {
updateColumnGroupingState(props.columnGroupingModel);
}, [updateColumnGroupingState, props.columnGroupingModel]);
};

View File

@@ -0,0 +1,57 @@
import * as React from 'react';
import { GridRenderContext } from '../../../models/params/gridScrollParams';
import { GridStateColDef } from '../../../models/colDef/gridColDef';
import { GridSortColumnLookup } from '../sorting';
import { GridFilterActiveItemsLookup } from '../filter';
import { GridColumnGroupIdentifier, GridColumnIdentifier } from '../focus';
import { GridColumnMenuState } from '../columnMenu';
import { GridColumnVisibilityModel } from '../columns';
import { GridGroupingStructure } from '../columnGrouping/gridColumnGroupsInterfaces';
export interface UseGridColumnHeadersProps {
innerRef?: React.Ref<HTMLDivElement>;
minColumnIndex?: number;
visibleColumns: GridStateColDef[];
sortColumnLookup: GridSortColumnLookup;
filterColumnLookup: GridFilterActiveItemsLookup;
columnPositions: number[];
columnHeaderTabIndexState: GridColumnIdentifier | null;
columnGroupHeaderTabIndexState: GridColumnGroupIdentifier | null;
columnHeaderFocus: GridColumnIdentifier | null;
columnGroupHeaderFocus: GridColumnGroupIdentifier | null;
densityFactor: number;
headerGroupingMaxDepth: number;
columnMenuState: GridColumnMenuState;
columnVisibility: GridColumnVisibilityModel;
columnGroupsHeaderStructure: GridGroupingStructure[][];
hasOtherElementInTabSequence: boolean;
}
export interface GetHeadersParams {
renderContext: GridRenderContext | null;
minFirstColumn?: number;
maxLastColumn?: number;
}
export declare const useGridColumnHeaders: (props: UseGridColumnHeadersProps) => {
renderContext: GridRenderContext | null;
getColumnHeaders: (params?: GetHeadersParams, other?: {}) => React.JSX.Element | null;
getColumnsToRender: (params?: GetHeadersParams) => {
renderedColumns: GridStateColDef[];
firstColumnToRender: number;
lastColumnToRender: number;
minFirstColumn: number;
maxLastColumn: number;
} | null;
getColumnGroupHeaders: (params?: GetHeadersParams) => React.JSX.Element[] | null;
isDragging: boolean;
getRootProps: (other?: {}) => {
style: {
minHeight: number;
maxHeight: number;
lineHeight: string;
};
};
getInnerProps: () => {
ref: ((instance: HTMLDivElement | null) => void) | null;
role: string;
};
headerHeight: number;
};

View File

@@ -0,0 +1,346 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { unstable_useForkRef as useForkRef } from '@mui/utils';
import { styled, useTheme } from '@mui/material/styles';
import { defaultMemoize } from 'reselect';
import { useGridSelector } from '../../utils';
import { useGridPrivateApiContext } from '../../utils/useGridPrivateApiContext';
import { useGridRootProps } from '../../utils/useGridRootProps';
import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
import { GridColumnHeaderItem } from '../../../components/columnHeaders/GridColumnHeaderItem';
import { getFirstColumnIndexToRender, getTotalHeaderHeight } from '../columns/gridColumnsUtils';
import { useGridVisibleRows } from '../../utils/useGridVisibleRows';
import { areRenderContextsEqual, getRenderableIndexes } from '../virtualization/useGridVirtualScroller';
import { gridVirtualizationColumnEnabledSelector } from '../virtualization';
import { GridColumnGroupHeader } from '../../../components/columnHeaders/GridColumnGroupHeader';
import { jsx as _jsx } from "react/jsx-runtime";
const GridColumnHeaderRow = styled('div', {
name: 'MuiDataGrid',
slot: 'ColumnHeaderRow',
overridesResolver: (props, styles) => styles.columnHeaderRow
})(() => ({
display: 'flex'
}));
function isUIEvent(event) {
return !!event.target;
}
export const useGridColumnHeaders = props => {
const {
innerRef: innerRefProp,
minColumnIndex = 0,
visibleColumns,
sortColumnLookup,
filterColumnLookup,
columnPositions,
columnHeaderTabIndexState,
columnGroupHeaderTabIndexState,
columnHeaderFocus,
columnGroupHeaderFocus,
densityFactor,
headerGroupingMaxDepth,
columnMenuState,
columnVisibility,
columnGroupsHeaderStructure,
hasOtherElementInTabSequence
} = props;
const theme = useTheme();
const [dragCol, setDragCol] = React.useState('');
const [resizeCol, setResizeCol] = React.useState('');
const apiRef = useGridPrivateApiContext();
const hasVirtualization = useGridSelector(apiRef, gridVirtualizationColumnEnabledSelector);
const rootProps = useGridRootProps();
const innerRef = React.useRef(null);
const handleInnerRef = useForkRef(innerRefProp, innerRef);
const [renderContext, setRenderContextRaw] = React.useState(null);
const prevRenderContext = React.useRef(renderContext);
const prevScrollLeft = React.useRef(0);
const currentPage = useGridVisibleRows(apiRef, rootProps);
const totalHeaderHeight = getTotalHeaderHeight(apiRef, rootProps.columnHeaderHeight);
const headerHeight = Math.floor(rootProps.columnHeaderHeight * densityFactor);
const setRenderContext = React.useCallback(nextRenderContext => {
if (renderContext && nextRenderContext && areRenderContextsEqual(renderContext, nextRenderContext)) {
return;
}
setRenderContextRaw(nextRenderContext);
}, [renderContext]);
React.useEffect(() => {
var _apiRef$current$colum;
if ((_apiRef$current$colum = apiRef.current.columnHeadersContainerElementRef) != null && _apiRef$current$colum.current) {
apiRef.current.columnHeadersContainerElementRef.current.scrollLeft = 0;
}
}, [apiRef]);
// memoize `getFirstColumnIndexToRender`, since it's called on scroll
const getFirstColumnIndexToRenderRef = React.useRef(defaultMemoize(getFirstColumnIndexToRender, {
equalityCheck: (a, b) => ['firstColumnIndex', 'minColumnIndex', 'columnBuffer'].every(key => a[key] === b[key])
}));
const updateInnerPosition = React.useCallback(nextRenderContext => {
const [firstRowToRender, lastRowToRender] = getRenderableIndexes({
firstIndex: nextRenderContext.firstRowIndex,
lastIndex: nextRenderContext.lastRowIndex,
minFirstIndex: 0,
maxLastIndex: currentPage.rows.length,
buffer: rootProps.rowBuffer
});
const firstColumnToRender = getFirstColumnIndexToRenderRef.current({
firstColumnIndex: nextRenderContext.firstColumnIndex,
minColumnIndex,
columnBuffer: rootProps.columnBuffer,
firstRowToRender,
lastRowToRender,
apiRef,
visibleRows: currentPage.rows
});
const direction = theme.direction === 'ltr' ? 1 : -1;
const offset = firstColumnToRender > 0 ? prevScrollLeft.current - direction * columnPositions[firstColumnToRender] : prevScrollLeft.current;
innerRef.current.style.transform = `translate3d(${-offset}px, 0px, 0px)`;
}, [columnPositions, minColumnIndex, rootProps.columnBuffer, apiRef, currentPage.rows, rootProps.rowBuffer, theme.direction]);
React.useLayoutEffect(() => {
if (renderContext) {
updateInnerPosition(renderContext);
}
}, [renderContext, updateInnerPosition]);
const handleScroll = React.useCallback(({
left,
renderContext: nextRenderContext = null
}, event) => {
var _prevRenderContext$cu, _prevRenderContext$cu2;
if (!innerRef.current) {
return;
}
// Ignore vertical scroll.
// Excepts the first event which sets the previous render context.
if (prevScrollLeft.current === left && ((_prevRenderContext$cu = prevRenderContext.current) == null ? void 0 : _prevRenderContext$cu.firstColumnIndex) === (nextRenderContext == null ? void 0 : nextRenderContext.firstColumnIndex) && ((_prevRenderContext$cu2 = prevRenderContext.current) == null ? void 0 : _prevRenderContext$cu2.lastColumnIndex) === (nextRenderContext == null ? void 0 : nextRenderContext.lastColumnIndex)) {
return;
}
prevScrollLeft.current = left;
// We can only update the position when we guarantee that the render context has been
// rendered. This is achieved using ReactDOM.flushSync or when the context doesn't change.
let canUpdateInnerPosition = false;
if (nextRenderContext !== prevRenderContext.current || !prevRenderContext.current) {
// ReactDOM.flushSync cannot be called on `scroll` events fired inside effects
if (isUIEvent(event)) {
// To prevent flickering, the inner position can only be updated after the new context has
// been rendered. ReactDOM.flushSync ensures that the state changes will happen before
// updating the position.
ReactDOM.flushSync(() => {
setRenderContext(nextRenderContext);
});
canUpdateInnerPosition = true;
} else {
setRenderContext(nextRenderContext);
}
prevRenderContext.current = nextRenderContext;
} else {
canUpdateInnerPosition = true;
}
// Pass directly the render context to avoid waiting for the next render
if (nextRenderContext && canUpdateInnerPosition) {
updateInnerPosition(nextRenderContext);
}
}, [updateInnerPosition, setRenderContext]);
const handleColumnResizeStart = React.useCallback(params => setResizeCol(params.field), []);
const handleColumnResizeStop = React.useCallback(() => setResizeCol(''), []);
const handleColumnReorderStart = React.useCallback(params => setDragCol(params.field), []);
const handleColumnReorderStop = React.useCallback(() => setDragCol(''), []);
useGridApiEventHandler(apiRef, 'columnResizeStart', handleColumnResizeStart);
useGridApiEventHandler(apiRef, 'columnResizeStop', handleColumnResizeStop);
useGridApiEventHandler(apiRef, 'columnHeaderDragStart', handleColumnReorderStart);
useGridApiEventHandler(apiRef, 'columnHeaderDragEnd', handleColumnReorderStop);
useGridApiEventHandler(apiRef, 'scrollPositionChange', handleScroll);
// Helper for computation common between getColumnHeaders and getColumnGroupHeaders
const getColumnsToRender = params => {
const {
renderContext: nextRenderContext = renderContext,
minFirstColumn = minColumnIndex,
maxLastColumn = visibleColumns.length
} = params || {};
if (!nextRenderContext) {
return null;
}
const [firstRowToRender, lastRowToRender] = getRenderableIndexes({
firstIndex: nextRenderContext.firstRowIndex,
lastIndex: nextRenderContext.lastRowIndex,
minFirstIndex: 0,
maxLastIndex: currentPage.rows.length,
buffer: rootProps.rowBuffer
});
const firstColumnToRender = !hasVirtualization ? 0 : getFirstColumnIndexToRenderRef.current({
firstColumnIndex: nextRenderContext.firstColumnIndex,
minColumnIndex: minFirstColumn,
columnBuffer: rootProps.columnBuffer,
apiRef,
firstRowToRender,
lastRowToRender,
visibleRows: currentPage.rows
});
const lastColumnToRender = !hasVirtualization ? maxLastColumn : Math.min(nextRenderContext.lastColumnIndex + rootProps.columnBuffer, maxLastColumn);
const renderedColumns = visibleColumns.slice(firstColumnToRender, lastColumnToRender);
return {
renderedColumns,
firstColumnToRender,
lastColumnToRender,
minFirstColumn,
maxLastColumn
};
};
const getColumnHeaders = (params, other = {}) => {
const columnsToRender = getColumnsToRender(params);
if (columnsToRender == null) {
return null;
}
const {
renderedColumns,
firstColumnToRender
} = columnsToRender;
const columns = [];
for (let i = 0; i < renderedColumns.length; i += 1) {
const colDef = renderedColumns[i];
const columnIndex = firstColumnToRender + i;
const isFirstColumn = columnIndex === 0;
const tabIndex = columnHeaderTabIndexState !== null && columnHeaderTabIndexState.field === colDef.field || isFirstColumn && !hasOtherElementInTabSequence ? 0 : -1;
const hasFocus = columnHeaderFocus !== null && columnHeaderFocus.field === colDef.field;
const open = columnMenuState.open && columnMenuState.field === colDef.field;
columns.push( /*#__PURE__*/_jsx(GridColumnHeaderItem, _extends({}, sortColumnLookup[colDef.field], {
columnMenuOpen: open,
filterItemsCounter: filterColumnLookup[colDef.field] && filterColumnLookup[colDef.field].length,
headerHeight: headerHeight,
isDragging: colDef.field === dragCol,
colDef: colDef,
colIndex: columnIndex,
isResizing: resizeCol === colDef.field,
hasFocus: hasFocus,
tabIndex: tabIndex
}, other), colDef.field));
}
return /*#__PURE__*/_jsx(GridColumnHeaderRow, {
role: "row",
"aria-rowindex": headerGroupingMaxDepth + 1,
ownerState: rootProps,
children: columns
});
};
const getColumnGroupHeaders = params => {
if (headerGroupingMaxDepth === 0) {
return null;
}
const columnsToRender = getColumnsToRender(params);
if (columnsToRender == null || columnsToRender.renderedColumns.length === 0) {
return null;
}
const {
firstColumnToRender,
lastColumnToRender
} = columnsToRender;
const columns = [];
const headerToRender = [];
for (let depth = 0; depth < headerGroupingMaxDepth; depth += 1) {
var _apiRef$current$unsta, _apiRef$current$unsta2;
const rowStructure = columnGroupsHeaderStructure[depth];
const firstColumnFieldToRender = visibleColumns[firstColumnToRender].field;
const firstGroupToRender = (_apiRef$current$unsta = apiRef.current.unstable_getColumnGroupPath(firstColumnFieldToRender)[depth]) != null ? _apiRef$current$unsta : null;
const firstGroupIndex = rowStructure.findIndex(({
groupId,
columnFields
}) => groupId === firstGroupToRender && columnFields.includes(firstColumnFieldToRender));
const lastColumnFieldToRender = visibleColumns[lastColumnToRender - 1].field;
const lastGroupToRender = (_apiRef$current$unsta2 = apiRef.current.unstable_getColumnGroupPath(lastColumnFieldToRender)[depth]) != null ? _apiRef$current$unsta2 : null;
const lastGroupIndex = rowStructure.findIndex(({
groupId,
columnFields
}) => groupId === lastGroupToRender && columnFields.includes(lastColumnFieldToRender));
const visibleColumnGroupHeader = rowStructure.slice(firstGroupIndex, lastGroupIndex + 1).map(groupStructure => {
return _extends({}, groupStructure, {
columnFields: groupStructure.columnFields.filter(field => columnVisibility[field] !== false)
});
}).filter(groupStructure => groupStructure.columnFields.length > 0);
const firstVisibleColumnIndex = visibleColumnGroupHeader[0].columnFields.indexOf(firstColumnFieldToRender);
const hiddenGroupColumns = visibleColumnGroupHeader[0].columnFields.slice(0, firstVisibleColumnIndex);
const leftOverflow = hiddenGroupColumns.reduce((acc, field) => {
var _column$computedWidth;
const column = apiRef.current.getColumn(field);
return acc + ((_column$computedWidth = column.computedWidth) != null ? _column$computedWidth : 0);
}, 0);
let columnIndex = firstColumnToRender;
const elements = visibleColumnGroupHeader.map(({
groupId,
columnFields
}) => {
const hasFocus = columnGroupHeaderFocus !== null && columnGroupHeaderFocus.depth === depth && columnFields.includes(columnGroupHeaderFocus.field);
const tabIndex = columnGroupHeaderTabIndexState !== null && columnGroupHeaderTabIndexState.depth === depth && columnFields.includes(columnGroupHeaderTabIndexState.field) ? 0 : -1;
const headerInfo = {
groupId,
width: columnFields.reduce((acc, field) => acc + apiRef.current.getColumn(field).computedWidth, 0),
fields: columnFields,
colIndex: columnIndex,
hasFocus,
tabIndex
};
columnIndex += columnFields.length;
return headerInfo;
});
headerToRender.push({
leftOverflow,
elements
});
}
headerToRender.forEach((depthInfo, depthIndex) => {
columns.push( /*#__PURE__*/_jsx(GridColumnHeaderRow, {
style: {
height: `${headerHeight}px`,
transform: `translateX(-${depthInfo.leftOverflow}px)`
},
role: "row",
"aria-rowindex": depthIndex + 1,
ownerState: rootProps,
children: depthInfo.elements.map(({
groupId,
width,
fields,
colIndex,
hasFocus,
tabIndex
}, groupIndex) => {
return /*#__PURE__*/_jsx(GridColumnGroupHeader, {
groupId: groupId,
width: width,
fields: fields,
colIndex: colIndex,
depth: depthIndex,
isLastColumn: colIndex === visibleColumns.length - fields.length,
maxDepth: headerToRender.length,
height: headerHeight,
hasFocus: hasFocus,
tabIndex: tabIndex
}, groupIndex);
})
}, depthIndex));
});
return columns;
};
const rootStyle = {
minHeight: totalHeaderHeight,
maxHeight: totalHeaderHeight,
lineHeight: `${headerHeight}px`
};
return {
renderContext,
getColumnHeaders,
getColumnsToRender,
getColumnGroupHeaders,
isDragging: !!dragCol,
getRootProps: (other = {}) => _extends({
style: rootStyle
}, other),
getInnerProps: () => ({
ref: handleInnerRef,
role: 'rowgroup'
}),
headerHeight
};
};

View File

@@ -0,0 +1,47 @@
import * as React from 'react';
export interface GridColumnMenuState {
open: boolean;
field?: string;
}
export interface GridColumnMenuSlotProps {
/**
* Every item has a `displayOrder` based which it will be placed before or after other
* items in the column menu, `array.prototype.sort` is applied to sort the items.
* Note: If same `displayOrder` is applied to two or more items they will be sorted
* based on the definition order
* @default 100
*/
displayOrder?: number;
[key: string]: any;
}
export interface GridColumnMenuRootProps {
/**
* Initial `slots` - it is internal, to be overrriden by Pro or Premium packages
* @ignore - do not document.
*/
defaultSlots: {
[key: string]: React.JSXElementConstructor<any>;
};
/**
* Initial `slotProps` - it is internal, to be overrriden by Pro or Premium packages
* @ignore - do not document.
*/
defaultSlotProps: {
[key: string]: GridColumnMenuSlotProps;
};
/**
* `slots` could be used to add new and (or) override default column menu items
* If you register a nee component you must pass it's `displayOrder` in `slotProps`
* or it will be placed in the end of the list
*/
slots?: {
[key: string]: React.JSXElementConstructor<any> | null;
};
/**
* Could be used to pass new props or override props specific to a column menu component
* e.g. `displayOrder`
*/
slotProps?: {
[key: string]: GridColumnMenuSlotProps;
};
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,2 @@
import { GridStateCommunity } from '../../../models/gridStateCommunity';
export declare const gridColumnMenuSelector: (state: GridStateCommunity) => import("./columnMenuInterfaces").GridColumnMenuState;

View File

@@ -0,0 +1 @@
export const gridColumnMenuSelector = state => state.columnMenu;

View File

@@ -0,0 +1,2 @@
export * from './columnMenuInterfaces';
export * from './columnMenuSelector';

View File

@@ -0,0 +1,2 @@
export * from './columnMenuInterfaces';
export * from './columnMenuSelector';

View File

@@ -0,0 +1,9 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { GridStateInitializer } from '../../utils/useGridInitializeState';
export declare const columnMenuStateInitializer: GridStateInitializer;
/**
* @requires useGridColumnResize (event)
* @requires useGridInfiniteLoader (event)
*/
export declare const useGridColumnMenu: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>) => void;

View File

@@ -0,0 +1,101 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { useGridLogger, useGridApiMethod, useGridApiEventHandler } from '../../utils';
import { gridColumnMenuSelector } from './columnMenuSelector';
import { gridColumnLookupSelector, gridColumnVisibilityModelSelector, gridColumnFieldsSelector } from '../columns/gridColumnsSelector';
export const columnMenuStateInitializer = state => _extends({}, state, {
columnMenu: {
open: false
}
});
/**
* @requires useGridColumnResize (event)
* @requires useGridInfiniteLoader (event)
*/
export const useGridColumnMenu = apiRef => {
const logger = useGridLogger(apiRef, 'useGridColumnMenu');
/**
* API METHODS
*/
const showColumnMenu = React.useCallback(field => {
const shouldUpdate = apiRef.current.setState(state => {
if (state.columnMenu.open && state.columnMenu.field === field) {
return state;
}
logger.debug('Opening Column Menu');
return _extends({}, state, {
columnMenu: {
open: true,
field
}
});
});
if (shouldUpdate) {
apiRef.current.hidePreferences();
apiRef.current.forceUpdate();
}
}, [apiRef, logger]);
const hideColumnMenu = React.useCallback(() => {
const columnMenuState = gridColumnMenuSelector(apiRef.current.state);
if (columnMenuState.field) {
const columnLookup = gridColumnLookupSelector(apiRef);
const columnVisibilityModel = gridColumnVisibilityModelSelector(apiRef);
const orderedFields = gridColumnFieldsSelector(apiRef);
let fieldToFocus = columnMenuState.field;
// If the column was removed from the grid, we need to find the closest visible field
if (!columnLookup[fieldToFocus]) {
fieldToFocus = orderedFields[0];
}
// If the field to focus is hidden, we need to find the closest visible field
if (columnVisibilityModel[fieldToFocus] === false) {
// contains visible column fields + the field that was just hidden
const visibleOrderedFields = orderedFields.filter(field => {
if (field === fieldToFocus) {
return true;
}
return columnVisibilityModel[field] !== false;
});
const fieldIndex = visibleOrderedFields.indexOf(fieldToFocus);
fieldToFocus = visibleOrderedFields[fieldIndex + 1] || visibleOrderedFields[fieldIndex - 1];
}
apiRef.current.setColumnHeaderFocus(fieldToFocus);
}
const shouldUpdate = apiRef.current.setState(state => {
if (!state.columnMenu.open && state.columnMenu.field === undefined) {
return state;
}
logger.debug('Hiding Column Menu');
return _extends({}, state, {
columnMenu: _extends({}, state.columnMenu, {
open: false,
field: undefined
})
});
});
if (shouldUpdate) {
apiRef.current.forceUpdate();
}
}, [apiRef, logger]);
const toggleColumnMenu = React.useCallback(field => {
logger.debug('Toggle Column Menu');
const columnMenu = gridColumnMenuSelector(apiRef.current.state);
if (!columnMenu.open || columnMenu.field !== field) {
showColumnMenu(field);
} else {
hideColumnMenu();
}
}, [apiRef, logger, showColumnMenu, hideColumnMenu]);
const columnMenuApi = {
showColumnMenu,
hideColumnMenu,
toggleColumnMenu
};
useGridApiMethod(apiRef, columnMenuApi, 'public');
useGridApiEventHandler(apiRef, 'columnResizeStart', hideColumnMenu);
useGridApiEventHandler(apiRef, 'virtualScrollerWheel', apiRef.current.hideColumnMenu);
useGridApiEventHandler(apiRef, 'virtualScrollerTouchMove', apiRef.current.hideColumnMenu);
};

View File

@@ -0,0 +1,16 @@
import * as React from 'react';
import { GridColumnMenuRootProps } from './columnMenuInterfaces';
import { GridColDef } from '../../../models/colDef/gridColDef';
interface UseGridColumnMenuSlotsProps extends GridColumnMenuRootProps {
colDef: GridColDef;
hideMenu: (event: React.SyntheticEvent) => void;
addDividers?: boolean;
}
type UseGridColumnMenuSlotsResponse = Array<[
React.JSXElementConstructor<any>,
{
[key: string]: any;
}
]>;
declare const useGridColumnMenuSlots: (props: UseGridColumnMenuSlotsProps) => UseGridColumnMenuSlotsResponse;
export { useGridColumnMenuSlots };

View File

@@ -0,0 +1,58 @@
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import _extends from "@babel/runtime/helpers/esm/extends";
const _excluded = ["displayOrder"];
import * as React from 'react';
import Divider from '@mui/material/Divider';
import { useGridPrivateApiContext } from '../../utils/useGridPrivateApiContext';
const useGridColumnMenuSlots = props => {
const apiRef = useGridPrivateApiContext();
const {
defaultSlots,
defaultSlotProps,
slots = {},
slotProps = {},
hideMenu,
colDef,
addDividers = true
} = props;
const processedComponents = React.useMemo(() => _extends({}, defaultSlots, slots), [defaultSlots, slots]);
const processedSlotProps = React.useMemo(() => {
if (!slotProps || Object.keys(slotProps).length === 0) {
return defaultSlotProps;
}
const mergedProps = _extends({}, slotProps);
Object.entries(defaultSlotProps).forEach(([key, currentSlotProps]) => {
mergedProps[key] = _extends({}, currentSlotProps, slotProps[key] || {});
});
return mergedProps;
}, [defaultSlotProps, slotProps]);
const defaultItems = apiRef.current.unstable_applyPipeProcessors('columnMenu', [], props.colDef);
const userItems = React.useMemo(() => {
const defaultComponentKeys = Object.keys(defaultSlots);
return Object.keys(slots).filter(key => !defaultComponentKeys.includes(key));
}, [slots, defaultSlots]);
return React.useMemo(() => {
const uniqueItems = Array.from(new Set([...defaultItems, ...userItems]));
const cleansedItems = uniqueItems.filter(key => processedComponents[key] != null);
const sorted = cleansedItems.sort((a, b) => {
const leftItemProps = processedSlotProps[a];
const rightItemProps = processedSlotProps[b];
const leftDisplayOrder = Number.isFinite(leftItemProps == null ? void 0 : leftItemProps.displayOrder) ? leftItemProps.displayOrder : 100;
const rightDisplayOrder = Number.isFinite(rightItemProps == null ? void 0 : rightItemProps.displayOrder) ? rightItemProps.displayOrder : 100;
return leftDisplayOrder - rightDisplayOrder;
});
return sorted.reduce((acc, key, index) => {
let itemProps = {
colDef,
onClick: hideMenu
};
const processedComponentProps = processedSlotProps[key];
if (processedComponentProps) {
const customProps = _objectWithoutPropertiesLoose(processedComponentProps, _excluded);
itemProps = _extends({}, itemProps, customProps);
}
return addDividers && index !== sorted.length - 1 ? [...acc, [processedComponents[key], itemProps], [Divider, {}]] : [...acc, [processedComponents[key], itemProps]];
}, []);
}, [addDividers, colDef, defaultItems, hideMenu, processedComponents, processedSlotProps, userItems]);
};
export { useGridColumnMenuSlots };

View File

@@ -0,0 +1,26 @@
import { GridColDef, GridStateColDef } from '../../../models/colDef/gridColDef';
import type { GridColumnDimensionProperties } from './gridColumnsUtils';
export type GridColumnLookup = {
[field: string]: GridStateColDef;
};
export type GridColumnRawLookup = {
[field: string]: GridColDef | GridStateColDef;
};
export interface GridColumnsState {
orderedFields: string[];
lookup: GridColumnLookup;
columnVisibilityModel: GridColumnVisibilityModel;
}
export type GridColumnDimensions = {
[key in GridColumnDimensionProperties]?: number;
};
export interface GridColumnsInitialState {
columnVisibilityModel?: GridColumnVisibilityModel;
orderedFields?: string[];
dimensions?: Record<string, GridColumnDimensions>;
}
export type GridColumnsRawState = Omit<GridColumnsState, 'lookup'> & {
lookup: GridColumnRawLookup;
};
export type GridHydrateColumnsValue = GridColumnsRawState;
export type GridColumnVisibilityModel = Record<GridColDef['field'], boolean>;

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,58 @@
import { GridStateCommunity } from '../../../models/gridStateCommunity';
import { GridColumnLookup } from './gridColumnsInterfaces';
/**
* Get the columns state
* @category Columns
*/
export declare const gridColumnsStateSelector: (state: GridStateCommunity) => import("./gridColumnsInterfaces").GridColumnsState;
/**
* Get an array of column fields in the order rendered on screen.
* @category Columns
*/
export declare const gridColumnFieldsSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, string[]>;
/**
* Get the columns as a lookup (an object containing the field for keys and the definition for values).
* @category Columns
*/
export declare const gridColumnLookupSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, GridColumnLookup>;
/**
* Get an array of column definitions in the order rendered on screen..
* @category Columns
*/
export declare const gridColumnDefinitionsSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, import("../../../internals").GridStateColDef[]>;
/**
* Get the column visibility model, containing the visibility status of each column.
* If a column is not registered in the model, it is visible.
* @category Visible Columns
*/
export declare const gridColumnVisibilityModelSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, import("./gridColumnsInterfaces").GridColumnVisibilityModel>;
/**
* Get the visible columns as a lookup (an object containing the field for keys and the definition for values).
* @category Visible Columns
*/
export declare const gridVisibleColumnDefinitionsSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, import("../../../internals").GridStateColDef[]>;
/**
* Get the field of each visible column.
* @category Visible Columns
*/
export declare const gridVisibleColumnFieldsSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, string[]>;
/**
* Get the left position in pixel of each visible columns relative to the left of the first column.
* @category Visible Columns
*/
export declare const gridColumnPositionsSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, number[]>;
/**
* Get the summed width of all the visible columns.
* @category Visible Columns
*/
export declare const gridColumnsTotalWidthSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, number>;
/**
* Get the filterable columns as an array.
* @category Columns
*/
export declare const gridFilterableColumnDefinitionsSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, import("../../../internals").GridStateColDef[]>;
/**
* Get the filterable columns as a lookup (an object containing the field for keys and the definition for values).
* @category Columns
*/
export declare const gridFilterableColumnLookupSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, GridColumnLookup>;

View File

@@ -0,0 +1,86 @@
import { createSelector, createSelectorMemoized } from '../../../utils/createSelector';
/**
* Get the columns state
* @category Columns
*/
export const gridColumnsStateSelector = state => state.columns;
/**
* Get an array of column fields in the order rendered on screen.
* @category Columns
*/
export const gridColumnFieldsSelector = createSelector(gridColumnsStateSelector, columnsState => columnsState.orderedFields);
/**
* Get the columns as a lookup (an object containing the field for keys and the definition for values).
* @category Columns
*/
export const gridColumnLookupSelector = createSelector(gridColumnsStateSelector, columnsState => columnsState.lookup);
/**
* Get an array of column definitions in the order rendered on screen..
* @category Columns
*/
export const gridColumnDefinitionsSelector = createSelectorMemoized(gridColumnFieldsSelector, gridColumnLookupSelector, (allFields, lookup) => allFields.map(field => lookup[field]));
/**
* Get the column visibility model, containing the visibility status of each column.
* If a column is not registered in the model, it is visible.
* @category Visible Columns
*/
export const gridColumnVisibilityModelSelector = createSelector(gridColumnsStateSelector, columnsState => columnsState.columnVisibilityModel);
/**
* Get the visible columns as a lookup (an object containing the field for keys and the definition for values).
* @category Visible Columns
*/
export const gridVisibleColumnDefinitionsSelector = createSelectorMemoized(gridColumnDefinitionsSelector, gridColumnVisibilityModelSelector, (columns, columnVisibilityModel) => columns.filter(column => columnVisibilityModel[column.field] !== false));
/**
* Get the field of each visible column.
* @category Visible Columns
*/
export const gridVisibleColumnFieldsSelector = createSelectorMemoized(gridVisibleColumnDefinitionsSelector, visibleColumns => visibleColumns.map(column => column.field));
/**
* Get the left position in pixel of each visible columns relative to the left of the first column.
* @category Visible Columns
*/
export const gridColumnPositionsSelector = createSelectorMemoized(gridVisibleColumnDefinitionsSelector, visibleColumns => {
const positions = [];
let currentPosition = 0;
for (let i = 0; i < visibleColumns.length; i += 1) {
positions.push(currentPosition);
currentPosition += visibleColumns[i].computedWidth;
}
return positions;
});
/**
* Get the summed width of all the visible columns.
* @category Visible Columns
*/
export const gridColumnsTotalWidthSelector = createSelector(gridVisibleColumnDefinitionsSelector, gridColumnPositionsSelector, (visibleColumns, positions) => {
const colCount = visibleColumns.length;
if (colCount === 0) {
return 0;
}
return positions[colCount - 1] + visibleColumns[colCount - 1].computedWidth;
});
/**
* Get the filterable columns as an array.
* @category Columns
*/
export const gridFilterableColumnDefinitionsSelector = createSelectorMemoized(gridColumnDefinitionsSelector, columns => columns.filter(col => col.filterable));
/**
* Get the filterable columns as a lookup (an object containing the field for keys and the definition for values).
* @category Columns
*/
export const gridFilterableColumnLookupSelector = createSelectorMemoized(gridColumnDefinitionsSelector, columns => columns.reduce((acc, col) => {
if (col.filterable) {
acc[col.field] = col;
}
return acc;
}, {}));

View File

@@ -0,0 +1,67 @@
import * as React from 'react';
import { GridColumnsState, GridColumnsRawState, GridColumnVisibilityModel, GridColumnsInitialState } from './gridColumnsInterfaces';
import { GridColumnTypesRecord } from '../../../models';
import { GridStateCommunity } from '../../../models/gridStateCommunity';
import { GridApiCommunity } from '../../../models/api/gridApiCommunity';
import { GridColDef } from '../../../models/colDef/gridColDef';
import { GridApiCommon } from '../../../models/api/gridApiCommon';
import { GridRowEntry } from '../../../models/gridRows';
export declare const COLUMNS_DIMENSION_PROPERTIES: readonly ["maxWidth", "minWidth", "width", "flex"];
export type GridColumnDimensionProperties = (typeof COLUMNS_DIMENSION_PROPERTIES)[number];
/**
* Computes width for flex columns.
* Based on CSS Flexbox specification:
* https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths
*/
export declare function computeFlexColumnsWidth({ initialFreeSpace, totalFlexUnits, flexColumns, }: {
initialFreeSpace: number;
totalFlexUnits: number;
flexColumns: {
field: GridColDef['field'];
flex?: number | null;
minWidth?: number;
maxWidth?: number;
}[];
}): Record<string, {
flex: number;
computedWidth: number;
frozen: boolean;
}>;
/**
* Compute the `computedWidth` (ie: the width the column should have during rendering) based on the `width` / `flex` / `minWidth` / `maxWidth` properties of `GridColDef`.
* The columns already have been merged with there `type` default values for `minWidth`, `maxWidth` and `width`, thus the `!` for those properties below.
* TODO: Unit test this function in depth and only keep basic cases for the whole grid testing.
* TODO: Improve the `GridColDef` typing to reflect the fact that `minWidth` / `maxWidth` and `width` can't be null after the merge with the `type` default values.
*/
export declare const hydrateColumnsWidth: (rawState: GridColumnsRawState, viewportInnerWidth: number) => GridColumnsState;
/**
* Apply the order and the dimensions of the initial state.
* The columns not registered in `orderedFields` will be placed after the imported columns.
*/
export declare const applyInitialState: (columnsState: GridColumnsRawState, initialState: GridColumnsInitialState | undefined) => GridColumnsRawState;
export declare const createColumnsState: ({ apiRef, columnsToUpsert, initialState, columnTypes, columnVisibilityModel, keepOnlyColumnsToUpsert, }: {
columnsToUpsert: readonly GridColDef[];
initialState: GridColumnsInitialState | undefined;
columnTypes: GridColumnTypesRecord;
columnVisibilityModel?: GridColumnVisibilityModel | undefined;
keepOnlyColumnsToUpsert: boolean;
apiRef: React.MutableRefObject<GridApiCommunity>;
}) => GridColumnsState;
export declare const mergeColumnsState: (columnsState: GridColumnsState) => (state: GridStateCommunity) => GridStateCommunity;
export declare function getFirstNonSpannedColumnToRender({ firstColumnToRender, apiRef, firstRowToRender, lastRowToRender, visibleRows, }: {
firstColumnToRender: number;
apiRef: React.MutableRefObject<GridApiCommon>;
firstRowToRender: number;
lastRowToRender: number;
visibleRows: GridRowEntry[];
}): number;
export declare function getFirstColumnIndexToRender({ firstColumnIndex, minColumnIndex, columnBuffer, firstRowToRender, lastRowToRender, apiRef, visibleRows, }: {
firstColumnIndex: number;
minColumnIndex: number;
columnBuffer: number;
apiRef: React.MutableRefObject<GridApiCommon>;
firstRowToRender: number;
lastRowToRender: number;
visibleRows: GridRowEntry[];
}): number;
export declare function getTotalHeaderHeight(apiRef: React.MutableRefObject<GridApiCommunity>, headerHeight: number): number;

View File

@@ -0,0 +1,332 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import { DEFAULT_GRID_COL_TYPE_KEY, GRID_STRING_COL_DEF } from '../../../colDef';
import { gridColumnsStateSelector, gridColumnVisibilityModelSelector } from './gridColumnsSelector';
import { clamp } from '../../../utils/utils';
import { gridDensityFactorSelector } from '../density/densitySelector';
import { gridColumnGroupsHeaderMaxDepthSelector } from '../columnGrouping/gridColumnGroupsSelector';
export const COLUMNS_DIMENSION_PROPERTIES = ['maxWidth', 'minWidth', 'width', 'flex'];
/**
* Computes width for flex columns.
* Based on CSS Flexbox specification:
* https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths
*/
export function computeFlexColumnsWidth({
initialFreeSpace,
totalFlexUnits,
flexColumns
}) {
const uniqueFlexColumns = new Set(flexColumns.map(col => col.field));
const flexColumnsLookup = {
all: {},
frozenFields: [],
freeze: field => {
const value = flexColumnsLookup.all[field];
if (value && value.frozen !== true) {
flexColumnsLookup.all[field].frozen = true;
flexColumnsLookup.frozenFields.push(field);
}
}
};
// Step 5 of https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths
function loopOverFlexItems() {
// 5a: If all the flex items on the line are frozen, free space has been distributed.
if (flexColumnsLookup.frozenFields.length === uniqueFlexColumns.size) {
return;
}
const violationsLookup = {
min: {},
max: {}
};
let remainingFreeSpace = initialFreeSpace;
let flexUnits = totalFlexUnits;
let totalViolation = 0;
// 5b: Calculate the remaining free space
flexColumnsLookup.frozenFields.forEach(field => {
remainingFreeSpace -= flexColumnsLookup.all[field].computedWidth;
flexUnits -= flexColumnsLookup.all[field].flex;
});
for (let i = 0; i < flexColumns.length; i += 1) {
const column = flexColumns[i];
if (flexColumnsLookup.all[column.field] && flexColumnsLookup.all[column.field].frozen === true) {
continue;
}
// 5c: Distribute remaining free space proportional to the flex factors
const widthPerFlexUnit = remainingFreeSpace / flexUnits;
let computedWidth = widthPerFlexUnit * column.flex;
// 5d: Fix min/max violations
if (computedWidth < column.minWidth) {
totalViolation += column.minWidth - computedWidth;
computedWidth = column.minWidth;
violationsLookup.min[column.field] = true;
} else if (computedWidth > column.maxWidth) {
totalViolation += column.maxWidth - computedWidth;
computedWidth = column.maxWidth;
violationsLookup.max[column.field] = true;
}
flexColumnsLookup.all[column.field] = {
frozen: false,
computedWidth,
flex: column.flex
};
}
// 5e: Freeze over-flexed items
if (totalViolation < 0) {
// Freeze all the items with max violations
Object.keys(violationsLookup.max).forEach(field => {
flexColumnsLookup.freeze(field);
});
} else if (totalViolation > 0) {
// Freeze all the items with min violations
Object.keys(violationsLookup.min).forEach(field => {
flexColumnsLookup.freeze(field);
});
} else {
// Freeze all items
flexColumns.forEach(({
field
}) => {
flexColumnsLookup.freeze(field);
});
}
// 5f: Return to the start of this loop
loopOverFlexItems();
}
loopOverFlexItems();
return flexColumnsLookup.all;
}
/**
* Compute the `computedWidth` (ie: the width the column should have during rendering) based on the `width` / `flex` / `minWidth` / `maxWidth` properties of `GridColDef`.
* The columns already have been merged with there `type` default values for `minWidth`, `maxWidth` and `width`, thus the `!` for those properties below.
* TODO: Unit test this function in depth and only keep basic cases for the whole grid testing.
* TODO: Improve the `GridColDef` typing to reflect the fact that `minWidth` / `maxWidth` and `width` can't be null after the merge with the `type` default values.
*/
export const hydrateColumnsWidth = (rawState, viewportInnerWidth) => {
const columnsLookup = {};
let totalFlexUnits = 0;
let widthAllocatedBeforeFlex = 0;
const flexColumns = [];
// For the non-flex columns, compute their width
// For the flex columns, compute there minimum width and how much width must be allocated during the flex allocation
rawState.orderedFields.forEach(columnField => {
const newColumn = _extends({}, rawState.lookup[columnField]);
if (rawState.columnVisibilityModel[columnField] === false) {
newColumn.computedWidth = 0;
} else {
let computedWidth;
if (newColumn.flex && newColumn.flex > 0) {
totalFlexUnits += newColumn.flex;
computedWidth = 0;
flexColumns.push(newColumn);
} else {
computedWidth = clamp(newColumn.width || GRID_STRING_COL_DEF.width, newColumn.minWidth || GRID_STRING_COL_DEF.minWidth, newColumn.maxWidth || GRID_STRING_COL_DEF.maxWidth);
}
widthAllocatedBeforeFlex += computedWidth;
newColumn.computedWidth = computedWidth;
}
columnsLookup[columnField] = newColumn;
});
const initialFreeSpace = Math.max(viewportInnerWidth - widthAllocatedBeforeFlex, 0);
// Allocate the remaining space to the flex columns
if (totalFlexUnits > 0 && viewportInnerWidth > 0) {
const computedColumnWidths = computeFlexColumnsWidth({
initialFreeSpace,
totalFlexUnits,
flexColumns
});
Object.keys(computedColumnWidths).forEach(field => {
columnsLookup[field].computedWidth = computedColumnWidths[field].computedWidth;
});
}
return _extends({}, rawState, {
lookup: columnsLookup
});
};
/**
* Apply the order and the dimensions of the initial state.
* The columns not registered in `orderedFields` will be placed after the imported columns.
*/
export const applyInitialState = (columnsState, initialState) => {
if (!initialState) {
return columnsState;
}
const {
orderedFields = [],
dimensions = {}
} = initialState;
const columnsWithUpdatedDimensions = Object.keys(dimensions);
if (columnsWithUpdatedDimensions.length === 0 && orderedFields.length === 0) {
return columnsState;
}
const orderedFieldsLookup = {};
const cleanOrderedFields = [];
for (let i = 0; i < orderedFields.length; i += 1) {
const field = orderedFields[i];
// Ignores the fields in the initialState that matches no field on the current column state
if (columnsState.lookup[field]) {
orderedFieldsLookup[field] = true;
cleanOrderedFields.push(field);
}
}
const newOrderedFields = cleanOrderedFields.length === 0 ? columnsState.orderedFields : [...cleanOrderedFields, ...columnsState.orderedFields.filter(field => !orderedFieldsLookup[field])];
const newColumnLookup = _extends({}, columnsState.lookup);
for (let i = 0; i < columnsWithUpdatedDimensions.length; i += 1) {
const field = columnsWithUpdatedDimensions[i];
const newColDef = _extends({}, newColumnLookup[field], {
hasBeenResized: true
});
Object.entries(dimensions[field]).forEach(([key, value]) => {
newColDef[key] = value === -1 ? Infinity : value;
});
newColumnLookup[field] = newColDef;
}
const newColumnsState = _extends({}, columnsState, {
orderedFields: newOrderedFields,
lookup: newColumnLookup
});
return newColumnsState;
};
function getDefaultColTypeDef(columnTypes, type) {
let colDef = columnTypes[DEFAULT_GRID_COL_TYPE_KEY];
if (type && columnTypes[type]) {
colDef = columnTypes[type];
}
return colDef;
}
export const createColumnsState = ({
apiRef,
columnsToUpsert,
initialState,
columnTypes,
columnVisibilityModel = gridColumnVisibilityModelSelector(apiRef),
keepOnlyColumnsToUpsert = false
}) => {
var _apiRef$current$getRo, _apiRef$current$getRo2, _apiRef$current;
const isInsideStateInitializer = !apiRef.current.state.columns;
let columnsState;
if (isInsideStateInitializer) {
columnsState = {
orderedFields: [],
lookup: {},
columnVisibilityModel
};
} else {
const currentState = gridColumnsStateSelector(apiRef.current.state);
columnsState = {
orderedFields: keepOnlyColumnsToUpsert ? [] : [...currentState.orderedFields],
lookup: _extends({}, currentState.lookup),
// Will be cleaned later if keepOnlyColumnsToUpsert=true
columnVisibilityModel
};
}
let columnsToKeep = {};
if (keepOnlyColumnsToUpsert && !isInsideStateInitializer) {
columnsToKeep = Object.keys(columnsState.lookup).reduce((acc, key) => _extends({}, acc, {
[key]: false
}), {});
}
const columnsToUpsertLookup = {};
columnsToUpsert.forEach(newColumn => {
const {
field
} = newColumn;
columnsToUpsertLookup[field] = true;
columnsToKeep[field] = true;
let existingState = columnsState.lookup[field];
if (existingState == null) {
existingState = _extends({}, getDefaultColTypeDef(columnTypes, newColumn.type), {
field,
hasBeenResized: false
});
columnsState.orderedFields.push(field);
} else if (keepOnlyColumnsToUpsert) {
columnsState.orderedFields.push(field);
}
// If the column type has changed - merge the existing state with the default column type definition
if (existingState && existingState.type !== newColumn.type) {
existingState = _extends({}, getDefaultColTypeDef(columnTypes, newColumn.type), {
field
});
}
let hasBeenResized = existingState.hasBeenResized;
COLUMNS_DIMENSION_PROPERTIES.forEach(key => {
if (newColumn[key] !== undefined) {
hasBeenResized = true;
if (newColumn[key] === -1) {
newColumn[key] = Infinity;
}
}
});
columnsState.lookup[field] = _extends({}, existingState, newColumn, {
hasBeenResized
});
});
if (keepOnlyColumnsToUpsert && !isInsideStateInitializer) {
Object.keys(columnsState.lookup).forEach(field => {
if (!columnsToKeep[field]) {
delete columnsState.lookup[field];
}
});
}
const columnsStateWithPreProcessing = apiRef.current.unstable_applyPipeProcessors('hydrateColumns', columnsState);
const columnsStateWithPortableColumns = applyInitialState(columnsStateWithPreProcessing, initialState);
return hydrateColumnsWidth(columnsStateWithPortableColumns, (_apiRef$current$getRo = (_apiRef$current$getRo2 = (_apiRef$current = apiRef.current).getRootDimensions) == null || (_apiRef$current$getRo2 = _apiRef$current$getRo2.call(_apiRef$current)) == null ? void 0 : _apiRef$current$getRo2.viewportInnerSize.width) != null ? _apiRef$current$getRo : 0);
};
export const mergeColumnsState = columnsState => state => _extends({}, state, {
columns: columnsState
});
export function getFirstNonSpannedColumnToRender({
firstColumnToRender,
apiRef,
firstRowToRender,
lastRowToRender,
visibleRows
}) {
let firstNonSpannedColumnToRender = firstColumnToRender;
for (let i = firstRowToRender; i < lastRowToRender; i += 1) {
const row = visibleRows[i];
if (row) {
const rowId = visibleRows[i].id;
const cellColSpanInfo = apiRef.current.unstable_getCellColSpanInfo(rowId, firstColumnToRender);
if (cellColSpanInfo && cellColSpanInfo.spannedByColSpan) {
firstNonSpannedColumnToRender = cellColSpanInfo.leftVisibleCellIndex;
}
}
}
return firstNonSpannedColumnToRender;
}
export function getFirstColumnIndexToRender({
firstColumnIndex,
minColumnIndex,
columnBuffer,
firstRowToRender,
lastRowToRender,
apiRef,
visibleRows
}) {
const initialFirstColumnToRender = Math.max(firstColumnIndex - columnBuffer, minColumnIndex);
const firstColumnToRender = getFirstNonSpannedColumnToRender({
firstColumnToRender: initialFirstColumnToRender,
apiRef,
firstRowToRender,
lastRowToRender,
visibleRows
});
return firstColumnToRender;
}
export function getTotalHeaderHeight(apiRef, headerHeight) {
const densityFactor = gridDensityFactorSelector(apiRef);
const maxDepth = gridColumnGroupsHeaderMaxDepthSelector(apiRef);
return Math.floor(headerHeight * densityFactor) * ((maxDepth != null ? maxDepth : 0) + 1);
}

View File

@@ -0,0 +1,2 @@
export { gridColumnFieldsSelector, gridColumnLookupSelector, gridColumnDefinitionsSelector, gridColumnVisibilityModelSelector, gridVisibleColumnDefinitionsSelector, gridVisibleColumnFieldsSelector, gridColumnPositionsSelector, gridColumnsTotalWidthSelector, gridFilterableColumnDefinitionsSelector, gridFilterableColumnLookupSelector, } from './gridColumnsSelector';
export type { GridColumnLookup, GridColumnsState, GridColumnsInitialState, GridColumnVisibilityModel, } from './gridColumnsInterfaces';

View File

@@ -0,0 +1 @@
export { gridColumnFieldsSelector, gridColumnLookupSelector, gridColumnDefinitionsSelector, gridColumnVisibilityModelSelector, gridVisibleColumnDefinitionsSelector, gridVisibleColumnFieldsSelector, gridColumnPositionsSelector, gridColumnsTotalWidthSelector, gridFilterableColumnDefinitionsSelector, gridFilterableColumnLookupSelector } from './gridColumnsSelector';

View File

@@ -0,0 +1,7 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
/**
* @requires useGridColumns (method, event)
* @requires useGridParamsApi (method)
*/
export declare const useGridColumnSpanning: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>) => void;

View File

@@ -0,0 +1,105 @@
import * as React from 'react';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
/**
* @requires useGridColumns (method, event)
* @requires useGridParamsApi (method)
*/
export const useGridColumnSpanning = apiRef => {
const lookup = React.useRef({});
const setCellColSpanInfo = React.useCallback((rowId, columnIndex, cellColSpanInfo) => {
const sizes = lookup.current;
if (!sizes[rowId]) {
sizes[rowId] = {};
}
sizes[rowId][columnIndex] = cellColSpanInfo;
}, []);
const getCellColSpanInfo = React.useCallback((rowId, columnIndex) => {
var _lookup$current$rowId;
return (_lookup$current$rowId = lookup.current[rowId]) == null ? void 0 : _lookup$current$rowId[columnIndex];
}, []);
// Calculate `colSpan` for the cell.
const calculateCellColSpan = React.useCallback(params => {
const {
columnIndex,
rowId,
minFirstColumnIndex,
maxLastColumnIndex,
columns
} = params;
const columnsLength = columns.length;
const column = columns[columnIndex];
const colSpan = typeof column.colSpan === 'function' ? column.colSpan(apiRef.current.getCellParams(rowId, column.field)) : column.colSpan;
if (!colSpan || colSpan === 1) {
setCellColSpanInfo(rowId, columnIndex, {
spannedByColSpan: false,
cellProps: {
colSpan: 1,
width: column.computedWidth
}
});
return {
colSpan: 1
};
}
let width = column.computedWidth;
for (let j = 1; j < colSpan; j += 1) {
const nextColumnIndex = columnIndex + j;
// Cells should be spanned only within their column section (left-pinned, right-pinned and unpinned).
if (nextColumnIndex >= minFirstColumnIndex && nextColumnIndex < maxLastColumnIndex) {
const nextColumn = columns[nextColumnIndex];
width += nextColumn.computedWidth;
setCellColSpanInfo(rowId, columnIndex + j, {
spannedByColSpan: true,
rightVisibleCellIndex: Math.min(columnIndex + colSpan, columnsLength - 1),
leftVisibleCellIndex: columnIndex
});
}
setCellColSpanInfo(rowId, columnIndex, {
spannedByColSpan: false,
cellProps: {
colSpan,
width
}
});
}
return {
colSpan
};
}, [apiRef, setCellColSpanInfo]);
// Calculate `colSpan` for each cell in the row
const calculateColSpan = React.useCallback(({
rowId,
minFirstColumn,
maxLastColumn,
columns
}) => {
for (let i = minFirstColumn; i < maxLastColumn; i += 1) {
const cellProps = calculateCellColSpan({
columnIndex: i,
rowId,
minFirstColumnIndex: minFirstColumn,
maxLastColumnIndex: maxLastColumn,
columns
});
if (cellProps.colSpan > 1) {
i += cellProps.colSpan - 1;
}
}
}, [calculateCellColSpan]);
const columnSpanningPublicApi = {
unstable_getCellColSpanInfo: getCellColSpanInfo
};
const columnSpanningPrivateApi = {
calculateColSpan
};
useGridApiMethod(apiRef, columnSpanningPublicApi, 'public');
useGridApiMethod(apiRef, columnSpanningPrivateApi, 'private');
const handleColumnReorderChange = React.useCallback(() => {
// `colSpan` needs to be recalculated after column reordering
lookup.current = {};
}, []);
useGridApiEventHandler(apiRef, 'columnOrderChange', handleColumnReorderChange);
};

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
import { GridStateInitializer } from '../../utils/useGridInitializeState';
export declare const columnsStateInitializer: GridStateInitializer<Pick<DataGridProcessedProps, 'columnVisibilityModel' | 'initialState' | 'columns'>>;
/**
* @requires useGridParamsApi (method)
* @requires useGridDimensions (method, event) - can be after
* TODO: Impossible priority - useGridParamsApi also needs to be after useGridColumns
*/
export declare function useGridColumns(apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'initialState' | 'columns' | 'columnVisibilityModel' | 'onColumnVisibilityModelChange' | 'slots' | 'slotProps' | 'disableColumnSelector' | 'signature'>): void;

View File

@@ -0,0 +1,309 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { useGridLogger } from '../../utils/useGridLogger';
import { gridColumnFieldsSelector, gridColumnDefinitionsSelector, gridColumnLookupSelector, gridColumnsStateSelector, gridColumnVisibilityModelSelector, gridVisibleColumnDefinitionsSelector, gridColumnPositionsSelector } from './gridColumnsSelector';
import { GridSignature, useGridApiEventHandler } from '../../utils/useGridApiEventHandler';
import { useGridRegisterPipeProcessor, useGridRegisterPipeApplier } from '../../core/pipeProcessing';
import { hydrateColumnsWidth, createColumnsState, mergeColumnsState, COLUMNS_DIMENSION_PROPERTIES } from './gridColumnsUtils';
import { GridPreferencePanelsValue } from '../preferencesPanel';
import { getGridDefaultColumnTypes } from '../../../colDef';
import { jsx as _jsx } from "react/jsx-runtime";
const defaultColumnTypes = getGridDefaultColumnTypes();
export const columnsStateInitializer = (state, props, apiRef) => {
var _props$initialState, _ref, _props$columnVisibili, _props$initialState2;
const columnsState = createColumnsState({
apiRef,
columnTypes: defaultColumnTypes,
columnsToUpsert: props.columns,
initialState: (_props$initialState = props.initialState) == null ? void 0 : _props$initialState.columns,
columnVisibilityModel: (_ref = (_props$columnVisibili = props.columnVisibilityModel) != null ? _props$columnVisibili : (_props$initialState2 = props.initialState) == null || (_props$initialState2 = _props$initialState2.columns) == null ? void 0 : _props$initialState2.columnVisibilityModel) != null ? _ref : {},
keepOnlyColumnsToUpsert: true
});
return _extends({}, state, {
columns: columnsState
});
};
/**
* @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) {
var _props$initialState4, _props$slotProps2;
const logger = useGridLogger(apiRef, 'useGridColumns');
const columnTypes = defaultColumnTypes;
const previousColumnsProp = React.useRef(props.columns);
const previousColumnTypesProp = React.useRef(columnTypes);
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.forceUpdate();
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,
columnTypes,
columnsToUpsert: [],
initialState: undefined,
columnVisibilityModel: model,
keepOnlyColumnsToUpsert: false
})
}));
apiRef.current.forceUpdate();
}
}, [apiRef, columnTypes]);
const updateColumns = React.useCallback(columns => {
const columnsState = createColumnsState({
apiRef,
columnTypes,
columnsToUpsert: columns,
initialState: undefined,
keepOnlyColumnsToUpsert: false
});
setGridColumnsState(columnsState);
}, [apiRef, setGridColumnsState, columnTypes]);
const setColumnVisibility = React.useCallback((field, isVisible) => {
var _columnVisibilityMode;
const columnVisibilityModel = gridColumnVisibilityModelSelector(apiRef);
const isCurrentlyVisible = (_columnVisibilityMode = columnVisibilityModel[field]) != null ? _columnVisibilityMode : 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.current.state), {
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) => {
var _apiRef$current$getRo, _apiRef$current$getRo2;
logger.debug(`Updating column ${field} width to ${width}`);
const columnsState = gridColumnsStateSelector(apiRef.current.state);
const column = columnsState.lookup[field];
const newColumn = _extends({}, column, {
width,
hasBeenResized: true
});
setGridColumnsState(hydrateColumnsWidth(_extends({}, columnsState, {
lookup: _extends({}, columnsState.lookup, {
[field]: newColumn
})
}), (_apiRef$current$getRo = (_apiRef$current$getRo2 = apiRef.current.getRootDimensions()) == null ? void 0 : _apiRef$current$getRo2.viewportInnerSize.width) != null ? _apiRef$current$getRo : 0));
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) => {
var _props$initialState$c, _props$initialState3;
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$c = (_props$initialState3 = props.initialState) == null || (_props$initialState3 = _props$initialState3.columns) == null ? void 0 : _props$initialState3.columnVisibilityModel) != null ? _props$initialState$c : {}).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$initialState4 = props.initialState) == null ? void 0 : _props$initialState4.columns]);
const stateRestorePreProcessing = React.useCallback((params, context) => {
var _context$stateToResto;
const columnVisibilityModelToImport = (_context$stateToResto = context.stateToRestore.columns) == null ? void 0 : _context$stateToResto.columnVisibilityModel;
const initialState = context.stateToRestore.columns;
if (columnVisibilityModelToImport == null && initialState == null) {
return params;
}
const columnsState = createColumnsState({
apiRef,
columnTypes,
columnsToUpsert: [],
initialState,
columnVisibilityModel: columnVisibilityModelToImport,
keepOnlyColumnsToUpsert: false
});
apiRef.current.setState(mergeColumnsState(columnsState));
if (initialState != null) {
apiRef.current.publishEvent('columnsChange', columnsState.orderedFields);
}
return params;
}, [apiRef, columnTypes]);
const preferencePanelPreProcessing = React.useCallback((initialValue, value) => {
if (value === GridPreferencePanelsValue.columns) {
var _props$slotProps;
const ColumnsPanel = props.slots.columnsPanel;
return /*#__PURE__*/_jsx(ColumnsPanel, _extends({}, (_props$slotProps = props.slotProps) == null ? void 0 : _props$slotProps.columnsPanel));
}
return initialValue;
}, [props.slots.columnsPanel, (_props$slotProps2 = props.slotProps) == null ? void 0 : _props$slotProps2.columnsPanel]);
const addColumnMenuItems = React.useCallback(columnMenuItems => {
if (props.disableColumnSelector) {
return columnMenuItems;
}
return [...columnMenuItems, 'columnMenuColumnsItem'];
}, [props.disableColumnSelector]);
useGridRegisterPipeProcessor(apiRef, 'columnMenu', addColumnMenuItems);
useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing);
useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing);
useGridRegisterPipeProcessor(apiRef, 'preferencePanel', preferencePanelPreProcessing);
/**
* EVENTS
*/
const prevInnerWidth = React.useRef(null);
const handleGridSizeChange = viewportInnerSize => {
if (prevInnerWidth.current !== viewportInnerSize.width) {
prevInnerWidth.current = viewportInnerSize.width;
setGridColumnsState(hydrateColumnsWidth(gridColumnsStateSelector(apiRef.current.state), viewportInnerSize.width));
}
};
useGridApiEventHandler(apiRef, 'viewportInnerSizeChange', handleGridSizeChange);
/**
* APPLIERS
*/
const hydrateColumns = React.useCallback(() => {
logger.info(`Columns pipe processing have changed, regenerating the columns`);
const columnsState = createColumnsState({
apiRef,
columnTypes,
columnsToUpsert: [],
initialState: undefined,
keepOnlyColumnsToUpsert: false
});
setGridColumnsState(columnsState);
}, [apiRef, logger, setGridColumnsState, columnTypes]);
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 && previousColumnTypesProp.current === columnTypes) {
return;
}
const columnsState = createColumnsState({
apiRef,
columnTypes,
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
});
previousColumnsProp.current = props.columns;
previousColumnTypesProp.current = columnTypes;
setGridColumnsState(columnsState);
}, [logger, apiRef, setGridColumnsState, props.columns, columnTypes]);
React.useEffect(() => {
if (props.columnVisibilityModel !== undefined) {
apiRef.current.setColumnVisibilityModel(props.columnVisibilityModel);
}
}, [apiRef, logger, props.columnVisibilityModel]);
}

View File

@@ -0,0 +1,4 @@
import { GridStateCommunity } from '../../../models/gridStateCommunity';
export declare const gridDensitySelector: (state: GridStateCommunity) => import("./densityState").GridDensityState;
export declare const gridDensityValueSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, import("../../..").GridDensity>;
export declare const gridDensityFactorSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, number>;

View File

@@ -0,0 +1,4 @@
import { createSelector } from '../../../utils/createSelector';
export const gridDensitySelector = state => state.density;
export const gridDensityValueSelector = createSelector(gridDensitySelector, density => density.value);
export const gridDensityFactorSelector = createSelector(gridDensitySelector, density => density.factor);

View File

@@ -0,0 +1,5 @@
import { GridDensity } from '../../../models/gridDensity';
export interface GridDensityState {
value: GridDensity;
factor: number;
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,2 @@
export * from './densityState';
export * from './densitySelector';

View File

@@ -0,0 +1,2 @@
export * from './densityState';
export * from './densitySelector';

View File

@@ -0,0 +1,8 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
import { GridStateInitializer } from '../../utils/useGridInitializeState';
export declare const COMPACT_DENSITY_FACTOR = 0.7;
export declare const COMFORTABLE_DENSITY_FACTOR = 1.3;
export declare const densityStateInitializer: GridStateInitializer<Pick<DataGridProcessedProps, 'density'>>;
export declare const useGridDensity: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'density'>) => void;

View File

@@ -0,0 +1,46 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { useGridLogger } from '../../utils/useGridLogger';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { gridDensitySelector } from './densitySelector';
import { isDeepEqual } from '../../../utils/utils';
export const COMPACT_DENSITY_FACTOR = 0.7;
export const COMFORTABLE_DENSITY_FACTOR = 1.3;
const DENSITY_FACTORS = {
compact: COMPACT_DENSITY_FACTOR,
comfortable: COMFORTABLE_DENSITY_FACTOR,
standard: 1
};
export const densityStateInitializer = (state, props) => _extends({}, state, {
density: {
value: props.density,
factor: DENSITY_FACTORS[props.density]
}
});
export const useGridDensity = (apiRef, props) => {
const logger = useGridLogger(apiRef, 'useDensity');
const setDensity = React.useCallback(newDensity => {
logger.debug(`Set grid density to ${newDensity}`);
apiRef.current.setState(state => {
const currentDensityState = gridDensitySelector(state);
const newDensityState = {
value: newDensity,
factor: DENSITY_FACTORS[newDensity]
};
if (isDeepEqual(currentDensityState, newDensityState)) {
return state;
}
return _extends({}, state, {
density: newDensityState
});
});
apiRef.current.forceUpdate();
}, [logger, apiRef]);
React.useEffect(() => {
apiRef.current.setDensity(props.density);
}, [apiRef, props.density]);
const densityApi = {
setDensity
};
useGridApiMethod(apiRef, densityApi, 'public');
};

View File

@@ -0,0 +1,50 @@
import type { ElementSize } from '../../../models/elementSize';
export interface GridDimensions {
/**
* The viewport size including scrollbars.
*/
viewportOuterSize: ElementSize;
/**
* The viewport size not including scrollbars.
*/
viewportInnerSize: ElementSize;
/**
* Indicates if a scroll is currently needed to go from the beginning of the first column to the end of the last column.
*/
hasScrollX: boolean;
/**
* Indicates if a scroll is currently needed to go from the beginning of the first row to the end of the last row.
*/
hasScrollY: boolean;
/**
* Size of the scrollbar used to scroll the rows in pixel.
* It is defined even when the scrollbar is currently not needed.
*/
scrollBarSize: number;
}
export interface GridDimensionsApi {
/**
* Triggers a resize of the component and recalculation of width and height.
*/
resize: () => void;
/**
* Returns the dimensions of the grid
* @returns {GridDimensions | null} The dimension information of the grid. If `null`, the grid is not ready yet.
*/
getRootDimensions: () => GridDimensions | null;
}
export interface GridDimensionsPrivateApi {
/**
* Returns the amount of rows that are currently visible in the viewport
* @returns {number} The amount of rows visible in the viewport
*/
getViewportPageSize: () => number;
/**
* Forces a recalculation of all dimensions.
*/
updateGridDimensionsRef: () => void;
/**
* Computes the size and publishes a `resize` event with the new value.
*/
computeSizeAndPublishResizeEvent: () => void;
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1 @@
export type { GridDimensions, GridDimensionsApi } from './gridDimensionsApi';

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,4 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
export declare function useGridDimensions(apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'onResize' | 'scrollbarSize' | 'pagination' | 'paginationMode' | 'autoHeight' | 'getRowHeight' | 'rowHeight' | 'columnHeaderHeight'>): void;

View File

@@ -0,0 +1,218 @@
import * as React from 'react';
import { unstable_debounce as debounce, unstable_ownerDocument as ownerDocument, unstable_useEnhancedEffect as useEnhancedEffect, unstable_ownerWindow as ownerWindow } from '@mui/utils';
import { useGridApiEventHandler, useGridApiOptionHandler } from '../../utils/useGridApiEventHandler';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { useGridLogger } from '../../utils/useGridLogger';
import { gridColumnsTotalWidthSelector } from '../columns';
import { gridDensityFactorSelector } from '../density';
import { useGridSelector } from '../../utils';
import { getVisibleRows } from '../../utils/useGridVisibleRows';
import { gridRowsMetaSelector } from '../rows/gridRowsMetaSelector';
import { calculatePinnedRowsHeight } from '../rows/gridRowsUtils';
import { getTotalHeaderHeight } from '../columns/gridColumnsUtils';
const isTestEnvironment = process.env.NODE_ENV === 'test';
const hasScroll = ({
content,
container,
scrollBarSize
}) => {
const hasScrollXIfNoYScrollBar = content.width > container.width;
const hasScrollYIfNoXScrollBar = content.height > container.height;
let hasScrollX = false;
let hasScrollY = false;
if (hasScrollXIfNoYScrollBar || hasScrollYIfNoXScrollBar) {
hasScrollX = hasScrollXIfNoYScrollBar;
hasScrollY = content.height + (hasScrollX ? scrollBarSize : 0) > container.height;
// We recalculate the scroll x to consider the size of the y scrollbar.
if (hasScrollY) {
hasScrollX = content.width + scrollBarSize > container.width;
}
}
return {
hasScrollX,
hasScrollY
};
};
export function useGridDimensions(apiRef, props) {
const logger = useGridLogger(apiRef, 'useResizeContainer');
const errorShown = React.useRef(false);
const rootDimensionsRef = React.useRef(null);
const fullDimensionsRef = React.useRef(null);
const rowsMeta = useGridSelector(apiRef, gridRowsMetaSelector);
const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector);
const rowHeight = Math.floor(props.rowHeight * densityFactor);
const totalHeaderHeight = getTotalHeaderHeight(apiRef, props.columnHeaderHeight);
const updateGridDimensionsRef = React.useCallback(() => {
var _apiRef$current$rootE;
const rootElement = (_apiRef$current$rootE = apiRef.current.rootElementRef) == null ? void 0 : _apiRef$current$rootE.current;
const columnsTotalWidth = gridColumnsTotalWidthSelector(apiRef);
const pinnedRowsHeight = calculatePinnedRowsHeight(apiRef);
if (!rootDimensionsRef.current) {
return;
}
let scrollBarSize;
if (props.scrollbarSize != null) {
scrollBarSize = props.scrollbarSize;
} else if (!columnsTotalWidth || !rootElement) {
scrollBarSize = 0;
} else {
const doc = ownerDocument(rootElement);
const scrollDiv = doc.createElement('div');
scrollDiv.style.width = '99px';
scrollDiv.style.height = '99px';
scrollDiv.style.position = 'absolute';
scrollDiv.style.overflow = 'scroll';
scrollDiv.className = 'scrollDiv';
rootElement.appendChild(scrollDiv);
scrollBarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth;
rootElement.removeChild(scrollDiv);
}
let viewportOuterSize;
let hasScrollX;
let hasScrollY;
if (props.autoHeight) {
hasScrollY = false;
hasScrollX = Math.round(columnsTotalWidth) > Math.round(rootDimensionsRef.current.width);
viewportOuterSize = {
width: rootDimensionsRef.current.width,
height: rowsMeta.currentPageTotalHeight + (hasScrollX ? scrollBarSize : 0)
};
} else {
viewportOuterSize = {
width: rootDimensionsRef.current.width,
height: Math.max(rootDimensionsRef.current.height - totalHeaderHeight, 0)
};
const scrollInformation = hasScroll({
content: {
width: Math.round(columnsTotalWidth),
height: rowsMeta.currentPageTotalHeight
},
container: {
width: Math.round(viewportOuterSize.width),
height: viewportOuterSize.height - pinnedRowsHeight.top - pinnedRowsHeight.bottom
},
scrollBarSize
});
hasScrollY = scrollInformation.hasScrollY;
hasScrollX = scrollInformation.hasScrollX;
}
const viewportInnerSize = {
width: viewportOuterSize.width - (hasScrollY ? scrollBarSize : 0),
height: viewportOuterSize.height - (hasScrollX ? scrollBarSize : 0)
};
const newFullDimensions = {
viewportOuterSize,
viewportInnerSize,
hasScrollX,
hasScrollY,
scrollBarSize
};
const prevDimensions = fullDimensionsRef.current;
fullDimensionsRef.current = newFullDimensions;
if (newFullDimensions.viewportInnerSize.width !== (prevDimensions == null ? void 0 : prevDimensions.viewportInnerSize.width) || newFullDimensions.viewportInnerSize.height !== (prevDimensions == null ? void 0 : prevDimensions.viewportInnerSize.height)) {
apiRef.current.publishEvent('viewportInnerSizeChange', newFullDimensions.viewportInnerSize);
}
}, [apiRef, props.scrollbarSize, props.autoHeight, rowsMeta.currentPageTotalHeight, totalHeaderHeight]);
const [savedSize, setSavedSize] = React.useState();
const debouncedSetSavedSize = React.useMemo(() => debounce(setSavedSize, 60), []);
const previousSize = React.useRef();
useEnhancedEffect(() => {
if (savedSize) {
updateGridDimensionsRef();
apiRef.current.publishEvent('debouncedResize', rootDimensionsRef.current);
}
}, [apiRef, savedSize, updateGridDimensionsRef]);
// This is the function called by apiRef.current.resize()
const resize = React.useCallback(() => {
apiRef.current.computeSizeAndPublishResizeEvent();
}, [apiRef]);
const getRootDimensions = React.useCallback(() => fullDimensionsRef.current, []);
const getViewportPageSize = React.useCallback(() => {
const dimensions = apiRef.current.getRootDimensions();
if (!dimensions) {
return 0;
}
const currentPage = getVisibleRows(apiRef, {
pagination: props.pagination,
paginationMode: props.paginationMode
});
// TODO: Use a combination of scrollTop, dimensions.viewportInnerSize.height and rowsMeta.possitions
// to find out the maximum number of rows that can fit in the visible part of the grid
if (props.getRowHeight) {
const renderContext = apiRef.current.getRenderContext();
const viewportPageSize = renderContext.lastRowIndex - renderContext.firstRowIndex;
return Math.min(viewportPageSize - 1, currentPage.rows.length);
}
const maximumPageSizeWithoutScrollBar = Math.floor(dimensions.viewportInnerSize.height / rowHeight);
return Math.min(maximumPageSizeWithoutScrollBar, currentPage.rows.length);
}, [apiRef, props.pagination, props.paginationMode, props.getRowHeight, rowHeight]);
const computeSizeAndPublishResizeEvent = React.useCallback(() => {
var _apiRef$current$mainE, _previousSize$current, _previousSize$current2;
const mainEl = (_apiRef$current$mainE = apiRef.current.mainElementRef) == null ? void 0 : _apiRef$current$mainE.current;
if (!mainEl) {
return;
}
const win = ownerWindow(mainEl);
const computedStyle = win.getComputedStyle(mainEl);
const height = parseFloat(computedStyle.height) || 0;
const width = parseFloat(computedStyle.width) || 0;
const hasHeightChanged = height !== ((_previousSize$current = previousSize.current) == null ? void 0 : _previousSize$current.height);
const hasWidthChanged = width !== ((_previousSize$current2 = previousSize.current) == null ? void 0 : _previousSize$current2.width);
if (!previousSize.current || hasHeightChanged || hasWidthChanged) {
const size = {
width,
height
};
apiRef.current.publishEvent('resize', size);
previousSize.current = size;
}
}, [apiRef]);
const dimensionsApi = {
resize,
getRootDimensions
};
const dimensionsPrivateApi = {
getViewportPageSize,
updateGridDimensionsRef,
computeSizeAndPublishResizeEvent
};
useGridApiMethod(apiRef, dimensionsApi, 'public');
useGridApiMethod(apiRef, dimensionsPrivateApi, 'private');
const isFirstSizing = React.useRef(true);
const handleResize = React.useCallback(size => {
rootDimensionsRef.current = size;
// jsdom has no layout capabilities
const isJSDOM = /jsdom/.test(window.navigator.userAgent);
if (size.height === 0 && !errorShown.current && !props.autoHeight && !isJSDOM) {
logger.error(['The parent DOM element of the data grid has an empty height.', 'Please make sure that this element has an intrinsic height.', 'The grid displays with a height of 0px.', '', 'More details: https://mui.com/r/x-data-grid-no-dimensions.'].join('\n'));
errorShown.current = true;
}
if (size.width === 0 && !errorShown.current && !isJSDOM) {
logger.error(['The parent DOM element of the data grid has an empty width.', 'Please make sure that this element has an intrinsic width.', 'The grid displays with a width of 0px.', '', 'More details: https://mui.com/r/x-data-grid-no-dimensions.'].join('\n'));
errorShown.current = true;
}
if (isTestEnvironment) {
// We don't need to debounce the resize for tests.
setSavedSize(size);
isFirstSizing.current = false;
return;
}
if (isFirstSizing.current) {
// We want to initialize the grid dimensions as soon as possible to avoid flickering
setSavedSize(size);
isFirstSizing.current = false;
return;
}
debouncedSetSavedSize(size);
}, [props.autoHeight, debouncedSetSavedSize, logger]);
useEnhancedEffect(() => updateGridDimensionsRef(), [updateGridDimensionsRef]);
useGridApiOptionHandler(apiRef, 'sortedRowsSet', updateGridDimensionsRef);
useGridApiOptionHandler(apiRef, 'paginationModelChange', updateGridDimensionsRef);
useGridApiOptionHandler(apiRef, 'columnsChange', updateGridDimensionsRef);
useGridApiEventHandler(apiRef, 'resize', handleResize);
useGridApiOptionHandler(apiRef, 'debouncedResize', props.onResize);
}

View File

@@ -0,0 +1,2 @@
import { GridStateCommunity } from '../../../models/gridStateCommunity';
export declare const gridEditRowsStateSelector: (state: GridStateCommunity) => import("../../..").GridEditingState;

View File

@@ -0,0 +1,2 @@
// TODO v6: rename to gridEditingStateSelector
export const gridEditRowsStateSelector = state => state.editRows;

View File

@@ -0,0 +1,4 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
export declare const useGridCellEditing: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'editMode' | 'processRowUpdate' | 'onCellEditStart' | 'onCellEditStop' | 'cellModesModel' | 'onCellModesModelChange' | 'onProcessRowUpdateError' | 'signature'>) => void;

View File

@@ -0,0 +1,437 @@
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import _toPropertyKey from "@babel/runtime/helpers/esm/toPropertyKey";
import _extends from "@babel/runtime/helpers/esm/extends";
const _excluded = ["id", "field"],
_excluded2 = ["id", "field"];
import * as React from 'react';
import { unstable_useEventCallback as useEventCallback, unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils';
import { useGridApiEventHandler, useGridApiOptionHandler } from '../../utils/useGridApiEventHandler';
import { GridEditModes, GridCellModes } from '../../../models/gridEditRowModel';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { gridEditRowsStateSelector } from './gridEditingSelectors';
import { isPrintableKey } from '../../../utils/keyboardUtils';
import { buildWarning } from '../../../utils/warning';
import { gridRowsDataRowIdToIdLookupSelector } from '../rows/gridRowsSelector';
import { deepClone } from '../../../utils/utils';
import { GridCellEditStartReasons, GridCellEditStopReasons } from '../../../models/params/gridEditCellParams';
const missingOnProcessRowUpdateErrorWarning = buildWarning(['MUI: A call to `processRowUpdate` threw an error which was not handled because `onProcessRowUpdateError` is missing.', 'To handle the error pass a callback to the `onProcessRowUpdateError` prop, e.g. `<DataGrid onProcessRowUpdateError={(error) => ...} />`.', 'For more detail, see http://mui.com/components/data-grid/editing/#server-side-persistence.'], 'error');
export const useGridCellEditing = (apiRef, props) => {
const [cellModesModel, setCellModesModel] = React.useState({});
const cellModesModelRef = React.useRef(cellModesModel);
const prevCellModesModel = React.useRef({});
const {
processRowUpdate,
onProcessRowUpdateError,
cellModesModel: cellModesModelProp,
onCellModesModelChange
} = props;
const runIfEditModeIsCell = callback => (...args) => {
if (props.editMode === GridEditModes.Cell) {
callback(...args);
}
};
const throwIfNotEditable = React.useCallback((id, field) => {
const params = apiRef.current.getCellParams(id, field);
if (!apiRef.current.isCellEditable(params)) {
throw new Error(`MUI: The cell with id=${id} and field=${field} is not editable.`);
}
}, [apiRef]);
const throwIfNotInMode = React.useCallback((id, field, mode) => {
if (apiRef.current.getCellMode(id, field) !== mode) {
throw new Error(`MUI: The cell with id=${id} and field=${field} is not in ${mode} mode.`);
}
}, [apiRef]);
const handleCellDoubleClick = React.useCallback((params, event) => {
if (!params.isEditable) {
return;
}
if (params.cellMode === GridCellModes.Edit) {
return;
}
const newParams = _extends({}, params, {
reason: GridCellEditStartReasons.cellDoubleClick
});
apiRef.current.publishEvent('cellEditStart', newParams, event);
}, [apiRef]);
const handleCellFocusOut = React.useCallback((params, event) => {
if (params.cellMode === GridCellModes.View) {
return;
}
if (apiRef.current.getCellMode(params.id, params.field) === GridCellModes.View) {
return;
}
const newParams = _extends({}, params, {
reason: GridCellEditStopReasons.cellFocusOut
});
apiRef.current.publishEvent('cellEditStop', newParams, event);
}, [apiRef]);
const handleCellKeyDown = React.useCallback((params, event) => {
if (params.cellMode === GridCellModes.Edit) {
// Wait until IME is settled for Asian languages like Japanese and Chinese
// TODO: `event.which` is deprecated but this is a temporary workaround
if (event.which === 229) {
return;
}
let reason;
if (event.key === 'Escape') {
reason = GridCellEditStopReasons.escapeKeyDown;
} else if (event.key === 'Enter') {
reason = GridCellEditStopReasons.enterKeyDown;
} else if (event.key === 'Tab') {
reason = event.shiftKey ? GridCellEditStopReasons.shiftTabKeyDown : GridCellEditStopReasons.tabKeyDown;
event.preventDefault(); // Prevent going to the next element in the tab sequence
}
if (reason) {
const newParams = _extends({}, params, {
reason
});
apiRef.current.publishEvent('cellEditStop', newParams, event);
}
} else if (params.isEditable) {
let reason;
const canStartEditing = apiRef.current.unstable_applyPipeProcessors('canStartEditing', true, {
event,
cellParams: params,
editMode: 'cell'
});
if (!canStartEditing) {
return;
}
if (isPrintableKey(event)) {
reason = GridCellEditStartReasons.printableKeyDown;
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
reason = GridCellEditStartReasons.pasteKeyDown;
} else if (event.key === 'Enter') {
reason = GridCellEditStartReasons.enterKeyDown;
} else if (event.key === 'Delete' || event.key === 'Backspace') {
// Delete on Windows, Backspace on macOS
reason = GridCellEditStartReasons.deleteKeyDown;
}
if (reason) {
const newParams = _extends({}, params, {
reason,
key: event.key
});
apiRef.current.publishEvent('cellEditStart', newParams, event);
}
}
}, [apiRef]);
const handleCellEditStart = React.useCallback(params => {
const {
id,
field,
reason
} = params;
const startCellEditModeParams = {
id,
field
};
if (reason === GridCellEditStartReasons.printableKeyDown || reason === GridCellEditStartReasons.deleteKeyDown || reason === GridCellEditStartReasons.pasteKeyDown) {
startCellEditModeParams.deleteValue = true;
}
apiRef.current.startCellEditMode(startCellEditModeParams);
}, [apiRef]);
const handleCellEditStop = React.useCallback(params => {
const {
id,
field,
reason
} = params;
apiRef.current.runPendingEditCellValueMutation(id, field);
let cellToFocusAfter;
if (reason === GridCellEditStopReasons.enterKeyDown) {
cellToFocusAfter = 'below';
} else if (reason === GridCellEditStopReasons.tabKeyDown) {
cellToFocusAfter = 'right';
} else if (reason === GridCellEditStopReasons.shiftTabKeyDown) {
cellToFocusAfter = 'left';
}
const ignoreModifications = reason === 'escapeKeyDown';
apiRef.current.stopCellEditMode({
id,
field,
ignoreModifications,
cellToFocusAfter
});
}, [apiRef]);
useGridApiEventHandler(apiRef, 'cellDoubleClick', runIfEditModeIsCell(handleCellDoubleClick));
useGridApiEventHandler(apiRef, 'cellFocusOut', runIfEditModeIsCell(handleCellFocusOut));
useGridApiEventHandler(apiRef, 'cellKeyDown', runIfEditModeIsCell(handleCellKeyDown));
useGridApiEventHandler(apiRef, 'cellEditStart', runIfEditModeIsCell(handleCellEditStart));
useGridApiEventHandler(apiRef, 'cellEditStop', runIfEditModeIsCell(handleCellEditStop));
useGridApiOptionHandler(apiRef, 'cellEditStart', props.onCellEditStart);
useGridApiOptionHandler(apiRef, 'cellEditStop', props.onCellEditStop);
const getCellMode = React.useCallback((id, field) => {
const editingState = gridEditRowsStateSelector(apiRef.current.state);
const isEditing = editingState[id] && editingState[id][field];
return isEditing ? GridCellModes.Edit : GridCellModes.View;
}, [apiRef]);
const updateCellModesModel = useEventCallback(newModel => {
const isNewModelDifferentFromProp = newModel !== props.cellModesModel;
if (onCellModesModelChange && isNewModelDifferentFromProp) {
onCellModesModelChange(newModel, {});
}
if (props.cellModesModel && isNewModelDifferentFromProp) {
return; // The prop always win
}
setCellModesModel(newModel);
cellModesModelRef.current = newModel;
apiRef.current.publishEvent('cellModesModelChange', newModel);
});
const updateFieldInCellModesModel = React.useCallback((id, field, newProps) => {
// We use the ref because it always contain the up-to-date value, different from the state
// that needs a rerender to reflect the new value
const newModel = _extends({}, cellModesModelRef.current);
if (newProps !== null) {
newModel[id] = _extends({}, newModel[id], {
[field]: _extends({}, newProps)
});
} else {
const _newModel$id = newModel[id],
otherFields = _objectWithoutPropertiesLoose(_newModel$id, [field].map(_toPropertyKey)); // Ensure that we have a new object, not a reference
newModel[id] = otherFields;
if (Object.keys(newModel[id]).length === 0) {
delete newModel[id];
}
}
updateCellModesModel(newModel);
}, [updateCellModesModel]);
const updateOrDeleteFieldState = React.useCallback((id, field, newProps) => {
apiRef.current.setState(state => {
const newEditingState = _extends({}, state.editRows);
if (newProps !== null) {
newEditingState[id] = _extends({}, newEditingState[id], {
[field]: _extends({}, newProps)
});
} else {
delete newEditingState[id][field];
if (Object.keys(newEditingState[id]).length === 0) {
delete newEditingState[id];
}
}
return _extends({}, state, {
editRows: newEditingState
});
});
apiRef.current.forceUpdate();
}, [apiRef]);
const startCellEditMode = React.useCallback(params => {
const {
id,
field
} = params,
other = _objectWithoutPropertiesLoose(params, _excluded);
throwIfNotEditable(id, field);
throwIfNotInMode(id, field, GridCellModes.View);
updateFieldInCellModesModel(id, field, _extends({
mode: GridCellModes.Edit
}, other));
}, [throwIfNotEditable, throwIfNotInMode, updateFieldInCellModesModel]);
const updateStateToStartCellEditMode = useEventCallback(params => {
const {
id,
field,
deleteValue,
initialValue
} = params;
let newValue = apiRef.current.getCellValue(id, field);
if (deleteValue || initialValue) {
newValue = deleteValue ? '' : initialValue;
}
const newProps = {
value: newValue,
error: false,
isProcessingProps: false
};
updateOrDeleteFieldState(id, field, newProps);
apiRef.current.setCellFocus(id, field);
});
const stopCellEditMode = React.useCallback(params => {
const {
id,
field
} = params,
other = _objectWithoutPropertiesLoose(params, _excluded2);
throwIfNotInMode(id, field, GridCellModes.Edit);
updateFieldInCellModesModel(id, field, _extends({
mode: GridCellModes.View
}, other));
}, [throwIfNotInMode, updateFieldInCellModesModel]);
const updateStateToStopCellEditMode = useEventCallback(async params => {
const {
id,
field,
ignoreModifications,
cellToFocusAfter = 'none'
} = params;
throwIfNotInMode(id, field, GridCellModes.Edit);
apiRef.current.runPendingEditCellValueMutation(id, field);
const finishCellEditMode = () => {
updateOrDeleteFieldState(id, field, null);
updateFieldInCellModesModel(id, field, null);
if (cellToFocusAfter !== 'none') {
apiRef.current.moveFocusToRelativeCell(id, field, cellToFocusAfter);
}
};
if (ignoreModifications) {
finishCellEditMode();
return;
}
const editingState = gridEditRowsStateSelector(apiRef.current.state);
const {
error,
isProcessingProps
} = editingState[id][field];
if (error || isProcessingProps) {
// Attempt to change cell mode to "view" was not successful
// Update previous mode to allow another attempt
prevCellModesModel.current[id][field].mode = GridCellModes.Edit;
// Revert the mode in the cellModesModel prop back to "edit"
updateFieldInCellModesModel(id, field, {
mode: GridCellModes.Edit
});
return;
}
const rowUpdate = apiRef.current.getRowWithUpdatedValuesFromCellEditing(id, field);
if (processRowUpdate) {
const handleError = errorThrown => {
prevCellModesModel.current[id][field].mode = GridCellModes.Edit;
// Revert the mode in the cellModesModel prop back to "edit"
updateFieldInCellModesModel(id, field, {
mode: GridCellModes.Edit
});
if (onProcessRowUpdateError) {
onProcessRowUpdateError(errorThrown);
} else {
missingOnProcessRowUpdateErrorWarning();
}
};
try {
const row = apiRef.current.getRow(id);
Promise.resolve(processRowUpdate(rowUpdate, row)).then(finalRowUpdate => {
apiRef.current.updateRows([finalRowUpdate]);
finishCellEditMode();
}).catch(handleError);
} catch (errorThrown) {
handleError(errorThrown);
}
} else {
apiRef.current.updateRows([rowUpdate]);
finishCellEditMode();
}
});
const setCellEditingEditCellValue = React.useCallback(async params => {
var _editingState$id;
const {
id,
field,
value,
debounceMs,
unstable_skipValueParser: skipValueParser
} = params;
throwIfNotEditable(id, field);
throwIfNotInMode(id, field, GridCellModes.Edit);
const column = apiRef.current.getColumn(field);
const row = apiRef.current.getRow(id);
let parsedValue = value;
if (column.valueParser && !skipValueParser) {
parsedValue = column.valueParser(value, apiRef.current.getCellParams(id, field));
}
let editingState = gridEditRowsStateSelector(apiRef.current.state);
let newProps = _extends({}, editingState[id][field], {
value: parsedValue,
changeReason: debounceMs ? 'debouncedSetEditCellValue' : 'setEditCellValue'
});
if (column.preProcessEditCellProps) {
const hasChanged = value !== editingState[id][field].value;
newProps = _extends({}, newProps, {
isProcessingProps: true
});
updateOrDeleteFieldState(id, field, newProps);
newProps = await Promise.resolve(column.preProcessEditCellProps({
id,
row,
props: newProps,
hasChanged
}));
}
// Check again if the cell is in edit mode because the user may have
// discarded the changes while the props were being processed.
if (apiRef.current.getCellMode(id, field) === GridCellModes.View) {
return false;
}
editingState = gridEditRowsStateSelector(apiRef.current.state);
newProps = _extends({}, newProps, {
isProcessingProps: false
});
// We don't update the value with the one coming from the props pre-processing
// because when the promise resolves it may be already outdated. The only
// exception to this rule is when there's no pre-processing.
newProps.value = column.preProcessEditCellProps ? editingState[id][field].value : parsedValue;
updateOrDeleteFieldState(id, field, newProps);
editingState = gridEditRowsStateSelector(apiRef.current.state);
return !((_editingState$id = editingState[id]) != null && (_editingState$id = _editingState$id[field]) != null && _editingState$id.error);
}, [apiRef, throwIfNotEditable, throwIfNotInMode, updateOrDeleteFieldState]);
const getRowWithUpdatedValuesFromCellEditing = React.useCallback((id, field) => {
const column = apiRef.current.getColumn(field);
const editingState = gridEditRowsStateSelector(apiRef.current.state);
const row = apiRef.current.getRow(id);
if (!editingState[id] || !editingState[id][field]) {
return apiRef.current.getRow(id);
}
const {
value
} = editingState[id][field];
return column.valueSetter ? column.valueSetter({
value,
row
}) : _extends({}, row, {
[field]: value
});
}, [apiRef]);
const editingApi = {
getCellMode,
startCellEditMode,
stopCellEditMode
};
const editingPrivateApi = {
setCellEditingEditCellValue,
getRowWithUpdatedValuesFromCellEditing
};
useGridApiMethod(apiRef, editingApi, 'public');
useGridApiMethod(apiRef, editingPrivateApi, 'private');
React.useEffect(() => {
if (cellModesModelProp) {
updateCellModesModel(cellModesModelProp);
}
}, [cellModesModelProp, updateCellModesModel]);
// Run this effect synchronously so that the keyboard event can impact the yet-to-be-rendered input.
useEnhancedEffect(() => {
const idToIdLookup = gridRowsDataRowIdToIdLookupSelector(apiRef);
// Update the ref here because updateStateToStopCellEditMode may change it later
const copyOfPrevCellModes = prevCellModesModel.current;
prevCellModesModel.current = deepClone(cellModesModel); // Do a deep-clone because the attributes might be changed later
Object.entries(cellModesModel).forEach(([id, fields]) => {
Object.entries(fields).forEach(([field, params]) => {
var _copyOfPrevCellModes$, _idToIdLookup$id;
const prevMode = ((_copyOfPrevCellModes$ = copyOfPrevCellModes[id]) == null || (_copyOfPrevCellModes$ = _copyOfPrevCellModes$[field]) == null ? void 0 : _copyOfPrevCellModes$.mode) || GridCellModes.View;
const originalId = (_idToIdLookup$id = idToIdLookup[id]) != null ? _idToIdLookup$id : id;
if (params.mode === GridCellModes.Edit && prevMode === GridCellModes.View) {
updateStateToStartCellEditMode(_extends({
id: originalId,
field
}, params));
} else if (params.mode === GridCellModes.View && prevMode === GridCellModes.Edit) {
updateStateToStopCellEditMode(_extends({
id: originalId,
field
}, params));
}
});
});
}, [apiRef, cellModesModel, updateStateToStartCellEditMode, updateStateToStopCellEditMode]);
};

View File

@@ -0,0 +1,6 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
import { GridStateInitializer } from '../../utils/useGridInitializeState';
export declare const editingStateInitializer: GridStateInitializer;
export declare const useGridEditing: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'isCellEditable' | 'editMode' | 'processRowUpdate'>) => void;

View File

@@ -0,0 +1,124 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { useGridCellEditing } from './useGridCellEditing';
import { GridCellModes, GridEditModes } from '../../../models/gridEditRowModel';
import { useGridRowEditing } from './useGridRowEditing';
import { gridEditRowsStateSelector } from './gridEditingSelectors';
import { isAutoGeneratedRow } from '../rows/gridRowsUtils';
export const editingStateInitializer = state => _extends({}, state, {
editRows: {}
});
export const useGridEditing = (apiRef, props) => {
useGridCellEditing(apiRef, props);
useGridRowEditing(apiRef, props);
const debounceMap = React.useRef({});
const {
isCellEditable: isCellEditableProp
} = props;
const isCellEditable = React.useCallback(params => {
if (isAutoGeneratedRow(params.rowNode)) {
return false;
}
if (!params.colDef.editable) {
return false;
}
if (!params.colDef.renderEditCell) {
return false;
}
if (isCellEditableProp) {
return isCellEditableProp(params);
}
return true;
}, [isCellEditableProp]);
const maybeDebounce = (id, field, debounceMs, callback) => {
if (!debounceMs) {
callback();
return;
}
if (!debounceMap.current[id]) {
debounceMap.current[id] = {};
}
if (debounceMap.current[id][field]) {
const [timeout] = debounceMap.current[id][field];
clearTimeout(timeout);
}
// To run the callback immediately without waiting the timeout
const runImmediately = () => {
const [timeout] = debounceMap.current[id][field];
clearTimeout(timeout);
callback();
delete debounceMap.current[id][field];
};
const timeout = setTimeout(() => {
callback();
delete debounceMap.current[id][field];
}, debounceMs);
debounceMap.current[id][field] = [timeout, runImmediately];
};
React.useEffect(() => {
const debounces = debounceMap.current;
return () => {
Object.entries(debounces).forEach(([id, fields]) => {
Object.keys(fields).forEach(field => {
const [timeout] = debounces[id][field];
clearTimeout(timeout);
delete debounces[id][field];
});
});
};
}, []);
const runPendingEditCellValueMutation = React.useCallback((id, field) => {
if (!debounceMap.current[id]) {
return;
}
if (!field) {
Object.keys(debounceMap.current[id]).forEach(debouncedField => {
const [, runCallback] = debounceMap.current[id][debouncedField];
runCallback();
});
} else if (debounceMap.current[id][field]) {
const [, runCallback] = debounceMap.current[id][field];
runCallback();
}
}, []);
const setEditCellValue = React.useCallback(params => {
const {
id,
field,
debounceMs
} = params;
return new Promise(resolve => {
maybeDebounce(id, field, debounceMs, async () => {
const setEditCellValueToCall = props.editMode === GridEditModes.Row ? apiRef.current.setRowEditingEditCellValue : apiRef.current.setCellEditingEditCellValue;
// Check if the cell is in edit mode
// By the time this callback runs the user may have cancelled the editing
if (apiRef.current.getCellMode(id, field) === GridCellModes.Edit) {
const result = await setEditCellValueToCall(params);
resolve(result);
}
});
});
}, [apiRef, props.editMode]);
const getRowWithUpdatedValues = React.useCallback((id, field) => {
return props.editMode === GridEditModes.Cell ? apiRef.current.getRowWithUpdatedValuesFromCellEditing(id, field) : apiRef.current.getRowWithUpdatedValuesFromRowEditing(id);
}, [apiRef, props.editMode]);
const getEditCellMeta = React.useCallback((id, field) => {
var _editingState$id$fiel, _editingState$id;
const editingState = gridEditRowsStateSelector(apiRef.current.state);
return (_editingState$id$fiel = (_editingState$id = editingState[id]) == null ? void 0 : _editingState$id[field]) != null ? _editingState$id$fiel : null;
}, [apiRef]);
const editingSharedApi = {
isCellEditable,
setEditCellValue,
getRowWithUpdatedValues,
unstable_getEditCellMeta: getEditCellMeta
};
const editingSharedPrivateApi = {
runPendingEditCellValueMutation
};
useGridApiMethod(apiRef, editingSharedApi, 'public');
useGridApiMethod(apiRef, editingSharedPrivateApi, 'private');
};

View File

@@ -0,0 +1,4 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
export declare const useGridRowEditing: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'editMode' | 'processRowUpdate' | 'onRowEditStart' | 'onRowEditStop' | 'onProcessRowUpdateError' | 'rowModesModel' | 'onRowModesModelChange' | 'signature'>) => void;

View File

@@ -0,0 +1,563 @@
import _toPropertyKey from "@babel/runtime/helpers/esm/toPropertyKey";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
import _extends from "@babel/runtime/helpers/esm/extends";
const _excluded = ["id"],
_excluded2 = ["id"];
import * as React from 'react';
import { unstable_useEventCallback as useEventCallback, unstable_useEnhancedEffect as useEnhancedEffect } from '@mui/utils';
import { useGridApiEventHandler, useGridApiOptionHandler } from '../../utils/useGridApiEventHandler';
import { GridEditModes, GridRowModes } from '../../../models/gridEditRowModel';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { gridEditRowsStateSelector } from './gridEditingSelectors';
import { isPrintableKey } from '../../../utils/keyboardUtils';
import { gridColumnFieldsSelector, gridVisibleColumnFieldsSelector } from '../columns/gridColumnsSelector';
import { buildWarning } from '../../../utils/warning';
import { gridRowsDataRowIdToIdLookupSelector } from '../rows/gridRowsSelector';
import { deepClone } from '../../../utils/utils';
import { GridRowEditStopReasons, GridRowEditStartReasons } from '../../../models/params/gridRowParams';
import { GRID_ACTIONS_COLUMN_TYPE } from '../../../colDef';
const missingOnProcessRowUpdateErrorWarning = buildWarning(['MUI: A call to `processRowUpdate` threw an error which was not handled because `onProcessRowUpdateError` is missing.', 'To handle the error pass a callback to the `onProcessRowUpdateError` prop, e.g. `<DataGrid onProcessRowUpdateError={(error) => ...} />`.', 'For more detail, see http://mui.com/components/data-grid/editing/#server-side-persistence.'], 'error');
export const useGridRowEditing = (apiRef, props) => {
const [rowModesModel, setRowModesModel] = React.useState({});
const rowModesModelRef = React.useRef(rowModesModel);
const prevRowModesModel = React.useRef({});
const focusTimeout = React.useRef(null);
const nextFocusedCell = React.useRef(null);
const {
processRowUpdate,
onProcessRowUpdateError,
rowModesModel: rowModesModelProp,
onRowModesModelChange
} = props;
const runIfEditModeIsRow = callback => (...args) => {
if (props.editMode === GridEditModes.Row) {
callback(...args);
}
};
const throwIfNotEditable = React.useCallback((id, field) => {
const params = apiRef.current.getCellParams(id, field);
if (!apiRef.current.isCellEditable(params)) {
throw new Error(`MUI: The cell with id=${id} and field=${field} is not editable.`);
}
}, [apiRef]);
const throwIfNotInMode = React.useCallback((id, mode) => {
if (apiRef.current.getRowMode(id) !== mode) {
throw new Error(`MUI: The row with id=${id} is not in ${mode} mode.`);
}
}, [apiRef]);
const handleCellDoubleClick = React.useCallback((params, event) => {
if (!params.isEditable) {
return;
}
if (apiRef.current.getRowMode(params.id) === GridRowModes.Edit) {
return;
}
const rowParams = apiRef.current.getRowParams(params.id);
const newParams = _extends({}, rowParams, {
field: params.field,
reason: GridRowEditStartReasons.cellDoubleClick
});
apiRef.current.publishEvent('rowEditStart', newParams, event);
}, [apiRef]);
const handleCellFocusIn = React.useCallback(params => {
nextFocusedCell.current = params;
}, []);
const handleCellFocusOut = React.useCallback((params, event) => {
if (!params.isEditable) {
return;
}
if (apiRef.current.getRowMode(params.id) === GridRowModes.View) {
return;
}
// The mechanism to detect if we can stop editing a row is different from
// the cell editing. Instead of triggering it when clicking outside a cell,
// we must check if another cell in the same row was not clicked. To achieve
// that, first we keep track of all cells that gained focus. When a cell loses
// focus we check if the next cell that received focus is from a different row.
nextFocusedCell.current = null;
focusTimeout.current = setTimeout(() => {
var _nextFocusedCell$curr;
focusTimeout.current = null;
if (((_nextFocusedCell$curr = nextFocusedCell.current) == null ? void 0 : _nextFocusedCell$curr.id) !== params.id) {
// The row might have been deleted during the click
if (!apiRef.current.getRow(params.id)) {
return;
}
// The row may already changed its mode
if (apiRef.current.getRowMode(params.id) === GridRowModes.View) {
return;
}
const rowParams = apiRef.current.getRowParams(params.id);
const newParams = _extends({}, rowParams, {
field: params.field,
reason: GridRowEditStopReasons.rowFocusOut
});
apiRef.current.publishEvent('rowEditStop', newParams, event);
}
});
}, [apiRef]);
React.useEffect(() => {
return () => {
clearTimeout(focusTimeout.current);
};
}, []);
const handleCellKeyDown = React.useCallback((params, event) => {
if (params.cellMode === GridRowModes.Edit) {
// Wait until IME is settled for Asian languages like Japanese and Chinese
// TODO: `event.which` is deprecated but this is a temporary workaround
if (event.which === 229) {
return;
}
let reason;
if (event.key === 'Escape') {
reason = GridRowEditStopReasons.escapeKeyDown;
} else if (event.key === 'Enter') {
reason = GridRowEditStopReasons.enterKeyDown;
} else if (event.key === 'Tab') {
const columnFields = gridVisibleColumnFieldsSelector(apiRef).filter(field => {
const column = apiRef.current.getColumn(field);
if (column.type === GRID_ACTIONS_COLUMN_TYPE) {
return true;
}
return apiRef.current.isCellEditable(apiRef.current.getCellParams(params.id, field));
});
if (event.shiftKey) {
if (params.field === columnFields[0]) {
// Exit if user pressed Shift+Tab on the first field
reason = GridRowEditStopReasons.shiftTabKeyDown;
}
} else if (params.field === columnFields[columnFields.length - 1]) {
// Exit if user pressed Tab on the last field
reason = GridRowEditStopReasons.tabKeyDown;
}
// Always prevent going to the next element in the tab sequence because the focus is
// handled manually to support edit components rendered inside Portals
event.preventDefault();
if (!reason) {
const index = columnFields.findIndex(field => field === params.field);
const nextFieldToFocus = columnFields[event.shiftKey ? index - 1 : index + 1];
apiRef.current.setCellFocus(params.id, nextFieldToFocus);
}
}
if (reason) {
const newParams = _extends({}, apiRef.current.getRowParams(params.id), {
reason,
field: params.field
});
apiRef.current.publishEvent('rowEditStop', newParams, event);
}
} else if (params.isEditable) {
let reason;
const canStartEditing = apiRef.current.unstable_applyPipeProcessors('canStartEditing', true, {
event,
cellParams: params,
editMode: 'row'
});
if (!canStartEditing) {
return;
}
if (isPrintableKey(event)) {
reason = GridRowEditStartReasons.printableKeyDown;
} else if ((event.ctrlKey || event.metaKey) && event.key === 'v') {
reason = GridRowEditStartReasons.printableKeyDown;
} else if (event.key === 'Enter') {
reason = GridRowEditStartReasons.enterKeyDown;
} else if (event.key === 'Delete' || event.key === 'Backspace') {
// Delete on Windows, Backspace on macOS
reason = GridRowEditStartReasons.deleteKeyDown;
}
if (reason) {
const rowParams = apiRef.current.getRowParams(params.id);
const newParams = _extends({}, rowParams, {
field: params.field,
reason
});
apiRef.current.publishEvent('rowEditStart', newParams, event);
}
}
}, [apiRef]);
const handleRowEditStart = React.useCallback(params => {
const {
id,
field,
reason
} = params;
const startRowEditModeParams = {
id,
fieldToFocus: field
};
if (reason === GridRowEditStartReasons.printableKeyDown || reason === GridRowEditStartReasons.deleteKeyDown) {
startRowEditModeParams.deleteValue = !!field;
}
apiRef.current.startRowEditMode(startRowEditModeParams);
}, [apiRef]);
const handleRowEditStop = React.useCallback(params => {
const {
id,
reason,
field
} = params;
apiRef.current.runPendingEditCellValueMutation(id);
let cellToFocusAfter;
if (reason === GridRowEditStopReasons.enterKeyDown) {
cellToFocusAfter = 'below';
} else if (reason === GridRowEditStopReasons.tabKeyDown) {
cellToFocusAfter = 'right';
} else if (reason === GridRowEditStopReasons.shiftTabKeyDown) {
cellToFocusAfter = 'left';
}
const ignoreModifications = reason === 'escapeKeyDown';
apiRef.current.stopRowEditMode({
id,
ignoreModifications,
field,
cellToFocusAfter
});
}, [apiRef]);
useGridApiEventHandler(apiRef, 'cellDoubleClick', runIfEditModeIsRow(handleCellDoubleClick));
useGridApiEventHandler(apiRef, 'cellFocusIn', runIfEditModeIsRow(handleCellFocusIn));
useGridApiEventHandler(apiRef, 'cellFocusOut', runIfEditModeIsRow(handleCellFocusOut));
useGridApiEventHandler(apiRef, 'cellKeyDown', runIfEditModeIsRow(handleCellKeyDown));
useGridApiEventHandler(apiRef, 'rowEditStart', runIfEditModeIsRow(handleRowEditStart));
useGridApiEventHandler(apiRef, 'rowEditStop', runIfEditModeIsRow(handleRowEditStop));
useGridApiOptionHandler(apiRef, 'rowEditStart', props.onRowEditStart);
useGridApiOptionHandler(apiRef, 'rowEditStop', props.onRowEditStop);
const getRowMode = React.useCallback(id => {
if (props.editMode === GridEditModes.Cell) {
return GridRowModes.View;
}
const editingState = gridEditRowsStateSelector(apiRef.current.state);
const isEditing = editingState[id] && Object.keys(editingState[id]).length > 0;
return isEditing ? GridRowModes.Edit : GridRowModes.View;
}, [apiRef, props.editMode]);
const updateRowModesModel = useEventCallback(newModel => {
const isNewModelDifferentFromProp = newModel !== props.rowModesModel;
if (onRowModesModelChange && isNewModelDifferentFromProp) {
onRowModesModelChange(newModel, {});
}
if (props.rowModesModel && isNewModelDifferentFromProp) {
return; // The prop always win
}
setRowModesModel(newModel);
rowModesModelRef.current = newModel;
apiRef.current.publishEvent('rowModesModelChange', newModel);
});
const updateRowInRowModesModel = React.useCallback((id, newProps) => {
const newModel = _extends({}, rowModesModelRef.current);
if (newProps !== null) {
newModel[id] = _extends({}, newProps);
} else {
delete newModel[id];
}
updateRowModesModel(newModel);
}, [updateRowModesModel]);
const updateOrDeleteRowState = React.useCallback((id, newProps) => {
apiRef.current.setState(state => {
const newEditingState = _extends({}, state.editRows);
if (newProps !== null) {
newEditingState[id] = newProps;
} else {
delete newEditingState[id];
}
return _extends({}, state, {
editRows: newEditingState
});
});
apiRef.current.forceUpdate();
}, [apiRef]);
const updateOrDeleteFieldState = React.useCallback((id, field, newProps) => {
apiRef.current.setState(state => {
const newEditingState = _extends({}, state.editRows);
if (newProps !== null) {
newEditingState[id] = _extends({}, newEditingState[id], {
[field]: _extends({}, newProps)
});
} else {
delete newEditingState[id][field];
if (Object.keys(newEditingState[id]).length === 0) {
delete newEditingState[id];
}
}
return _extends({}, state, {
editRows: newEditingState
});
});
apiRef.current.forceUpdate();
}, [apiRef]);
const startRowEditMode = React.useCallback(params => {
const {
id
} = params,
other = _objectWithoutPropertiesLoose(params, _excluded);
throwIfNotInMode(id, GridRowModes.View);
updateRowInRowModesModel(id, _extends({
mode: GridRowModes.Edit
}, other));
}, [throwIfNotInMode, updateRowInRowModesModel]);
const updateStateToStartRowEditMode = useEventCallback(params => {
const {
id,
fieldToFocus,
deleteValue,
initialValue
} = params;
const columnFields = gridColumnFieldsSelector(apiRef);
const newProps = columnFields.reduce((acc, field) => {
const cellParams = apiRef.current.getCellParams(id, field);
if (!cellParams.isEditable) {
return acc;
}
let newValue = apiRef.current.getCellValue(id, field);
if (fieldToFocus === field && (deleteValue || initialValue)) {
newValue = deleteValue ? '' : initialValue;
}
acc[field] = {
value: newValue,
error: false,
isProcessingProps: false
};
return acc;
}, {});
updateOrDeleteRowState(id, newProps);
if (fieldToFocus) {
apiRef.current.setCellFocus(id, fieldToFocus);
}
});
const stopRowEditMode = React.useCallback(params => {
const {
id
} = params,
other = _objectWithoutPropertiesLoose(params, _excluded2);
throwIfNotInMode(id, GridRowModes.Edit);
updateRowInRowModesModel(id, _extends({
mode: GridRowModes.View
}, other));
}, [throwIfNotInMode, updateRowInRowModesModel]);
const updateStateToStopRowEditMode = useEventCallback(params => {
const {
id,
ignoreModifications,
field: focusedField,
cellToFocusAfter = 'none'
} = params;
apiRef.current.runPendingEditCellValueMutation(id);
const finishRowEditMode = () => {
if (cellToFocusAfter !== 'none' && focusedField) {
apiRef.current.moveFocusToRelativeCell(id, focusedField, cellToFocusAfter);
}
updateOrDeleteRowState(id, null);
updateRowInRowModesModel(id, null);
};
if (ignoreModifications) {
finishRowEditMode();
return;
}
const editingState = gridEditRowsStateSelector(apiRef.current.state);
const row = apiRef.current.getRow(id);
const isSomeFieldProcessingProps = Object.values(editingState[id]).some(fieldProps => fieldProps.isProcessingProps);
if (isSomeFieldProcessingProps) {
prevRowModesModel.current[id].mode = GridRowModes.Edit;
return;
}
const hasSomeFieldWithError = Object.values(editingState[id]).some(fieldProps => fieldProps.error);
if (hasSomeFieldWithError) {
prevRowModesModel.current[id].mode = GridRowModes.Edit;
// Revert the mode in the rowModesModel prop back to "edit"
updateRowInRowModesModel(id, {
mode: GridRowModes.Edit
});
return;
}
const rowUpdate = apiRef.current.getRowWithUpdatedValuesFromRowEditing(id);
if (processRowUpdate) {
const handleError = errorThrown => {
prevRowModesModel.current[id].mode = GridRowModes.Edit;
// Revert the mode in the rowModesModel prop back to "edit"
updateRowInRowModesModel(id, {
mode: GridRowModes.Edit
});
if (onProcessRowUpdateError) {
onProcessRowUpdateError(errorThrown);
} else {
missingOnProcessRowUpdateErrorWarning();
}
};
try {
Promise.resolve(processRowUpdate(rowUpdate, row)).then(finalRowUpdate => {
apiRef.current.updateRows([finalRowUpdate]);
finishRowEditMode();
}).catch(handleError);
} catch (errorThrown) {
handleError(errorThrown);
}
} else {
apiRef.current.updateRows([rowUpdate]);
finishRowEditMode();
}
});
const setRowEditingEditCellValue = React.useCallback(params => {
const {
id,
field,
value,
debounceMs,
unstable_skipValueParser: skipValueParser
} = params;
throwIfNotEditable(id, field);
const column = apiRef.current.getColumn(field);
const row = apiRef.current.getRow(id);
let parsedValue = value;
if (column.valueParser && !skipValueParser) {
parsedValue = column.valueParser(value, apiRef.current.getCellParams(id, field));
}
let editingState = gridEditRowsStateSelector(apiRef.current.state);
let newProps = _extends({}, editingState[id][field], {
value: parsedValue,
changeReason: debounceMs ? 'debouncedSetEditCellValue' : 'setEditCellValue'
});
if (!column.preProcessEditCellProps) {
updateOrDeleteFieldState(id, field, newProps);
}
return new Promise(resolve => {
const promises = [];
if (column.preProcessEditCellProps) {
const hasChanged = newProps.value !== editingState[id][field].value;
newProps = _extends({}, newProps, {
isProcessingProps: true
});
updateOrDeleteFieldState(id, field, newProps);
const _editingState$id = editingState[id],
otherFieldsProps = _objectWithoutPropertiesLoose(_editingState$id, [field].map(_toPropertyKey));
const promise = Promise.resolve(column.preProcessEditCellProps({
id,
row,
props: newProps,
hasChanged,
otherFieldsProps
})).then(processedProps => {
// Check again if the row is in edit mode because the user may have
// discarded the changes while the props were being processed.
if (apiRef.current.getRowMode(id) === GridRowModes.View) {
resolve(false);
return;
}
editingState = gridEditRowsStateSelector(apiRef.current.state);
processedProps = _extends({}, processedProps, {
isProcessingProps: false
});
// We don't reuse the value from the props pre-processing because when the
// promise resolves it may be already outdated. The only exception to this rule
// is when there's no pre-processing.
processedProps.value = column.preProcessEditCellProps ? editingState[id][field].value : parsedValue;
updateOrDeleteFieldState(id, field, processedProps);
});
promises.push(promise);
}
Object.entries(editingState[id]).forEach(([thisField, fieldProps]) => {
if (thisField === field) {
return;
}
const fieldColumn = apiRef.current.getColumn(thisField);
if (!fieldColumn.preProcessEditCellProps) {
return;
}
fieldProps = _extends({}, fieldProps, {
isProcessingProps: true
});
updateOrDeleteFieldState(id, thisField, fieldProps);
editingState = gridEditRowsStateSelector(apiRef.current.state);
const _editingState$id2 = editingState[id],
otherFieldsProps = _objectWithoutPropertiesLoose(_editingState$id2, [thisField].map(_toPropertyKey));
const promise = Promise.resolve(fieldColumn.preProcessEditCellProps({
id,
row,
props: fieldProps,
hasChanged: false,
otherFieldsProps
})).then(processedProps => {
// Check again if the row is in edit mode because the user may have
// discarded the changes while the props were being processed.
if (apiRef.current.getRowMode(id) === GridRowModes.View) {
resolve(false);
return;
}
processedProps = _extends({}, processedProps, {
isProcessingProps: false
});
updateOrDeleteFieldState(id, thisField, processedProps);
});
promises.push(promise);
});
Promise.all(promises).then(() => {
if (apiRef.current.getRowMode(id) === GridRowModes.Edit) {
editingState = gridEditRowsStateSelector(apiRef.current.state);
resolve(!editingState[id][field].error);
} else {
resolve(false);
}
});
});
}, [apiRef, throwIfNotEditable, updateOrDeleteFieldState]);
const getRowWithUpdatedValuesFromRowEditing = React.useCallback(id => {
const editingState = gridEditRowsStateSelector(apiRef.current.state);
const row = apiRef.current.getRow(id);
if (!editingState[id]) {
return apiRef.current.getRow(id);
}
let rowUpdate = _extends({}, row);
Object.entries(editingState[id]).forEach(([field, fieldProps]) => {
const column = apiRef.current.getColumn(field);
if (column.valueSetter) {
rowUpdate = column.valueSetter({
value: fieldProps.value,
row: rowUpdate
});
} else {
rowUpdate[field] = fieldProps.value;
}
});
return rowUpdate;
}, [apiRef]);
const editingApi = {
getRowMode,
startRowEditMode,
stopRowEditMode
};
const editingPrivateApi = {
setRowEditingEditCellValue,
getRowWithUpdatedValuesFromRowEditing
};
useGridApiMethod(apiRef, editingApi, 'public');
useGridApiMethod(apiRef, editingPrivateApi, 'private');
React.useEffect(() => {
if (rowModesModelProp) {
updateRowModesModel(rowModesModelProp);
}
}, [rowModesModelProp, updateRowModesModel]);
// Run this effect synchronously so that the keyboard event can impact the yet-to-be-rendered input.
useEnhancedEffect(() => {
const idToIdLookup = gridRowsDataRowIdToIdLookupSelector(apiRef);
// Update the ref here because updateStateToStopRowEditMode may change it later
const copyOfPrevRowModesModel = prevRowModesModel.current;
prevRowModesModel.current = deepClone(rowModesModel); // Do a deep-clone because the attributes might be changed later
Object.entries(rowModesModel).forEach(([id, params]) => {
var _copyOfPrevRowModesMo, _idToIdLookup$id;
const prevMode = ((_copyOfPrevRowModesMo = copyOfPrevRowModesModel[id]) == null ? void 0 : _copyOfPrevRowModesMo.mode) || GridRowModes.View;
const originalId = (_idToIdLookup$id = idToIdLookup[id]) != null ? _idToIdLookup$id : id;
if (params.mode === GridRowModes.Edit && prevMode === GridRowModes.View) {
updateStateToStartRowEditMode(_extends({
id: originalId
}, params));
} else if (params.mode === GridRowModes.View && prevMode === GridRowModes.Edit) {
updateStateToStopRowEditMode(_extends({
id: originalId
}, params));
}
});
}, [apiRef, rowModesModel, updateStateToStartRowEditMode, updateStateToStopRowEditMode]);
};

View File

@@ -0,0 +1,8 @@
import * as React from 'react';
import { GridApiCommunity } from '../../../models/api/gridApiCommunity';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
/**
* @requires useGridFocus (event) - can be after, async only
* @requires useGridColumns (event) - can be after, async only
*/
export declare function useGridEvents(apiRef: React.MutableRefObject<GridApiCommunity>, props: Pick<DataGridProcessedProps, 'onColumnHeaderClick' | 'onColumnHeaderDoubleClick' | 'onColumnHeaderOver' | 'onColumnHeaderOut' | 'onColumnHeaderEnter' | 'onColumnHeaderLeave' | 'onCellClick' | 'onCellDoubleClick' | 'onCellKeyDown' | 'onPreferencePanelClose' | 'onPreferencePanelOpen' | 'onRowDoubleClick' | 'onRowClick' | 'onStateChange' | 'onMenuOpen' | 'onMenuClose'>): void;

View File

@@ -0,0 +1,23 @@
import { useGridApiOptionHandler } from '../../utils/useGridApiEventHandler';
/**
* @requires useGridFocus (event) - can be after, async only
* @requires useGridColumns (event) - can be after, async only
*/
export function useGridEvents(apiRef, props) {
useGridApiOptionHandler(apiRef, 'columnHeaderClick', props.onColumnHeaderClick);
useGridApiOptionHandler(apiRef, 'columnHeaderDoubleClick', props.onColumnHeaderDoubleClick);
useGridApiOptionHandler(apiRef, 'columnHeaderOver', props.onColumnHeaderOver);
useGridApiOptionHandler(apiRef, 'columnHeaderOut', props.onColumnHeaderOut);
useGridApiOptionHandler(apiRef, 'columnHeaderEnter', props.onColumnHeaderEnter);
useGridApiOptionHandler(apiRef, 'columnHeaderLeave', props.onColumnHeaderLeave);
useGridApiOptionHandler(apiRef, 'cellClick', props.onCellClick);
useGridApiOptionHandler(apiRef, 'cellDoubleClick', props.onCellDoubleClick);
useGridApiOptionHandler(apiRef, 'cellKeyDown', props.onCellKeyDown);
useGridApiOptionHandler(apiRef, 'preferencePanelClose', props.onPreferencePanelClose);
useGridApiOptionHandler(apiRef, 'preferencePanelOpen', props.onPreferencePanelOpen);
useGridApiOptionHandler(apiRef, 'menuOpen', props.onMenuOpen);
useGridApiOptionHandler(apiRef, 'menuClose', props.onMenuClose);
useGridApiOptionHandler(apiRef, 'rowDoubleClick', props.onRowDoubleClick);
useGridApiOptionHandler(apiRef, 'rowClick', props.onRowClick);
useGridApiOptionHandler(apiRef, 'stateChange', props.onStateChange);
}

View File

@@ -0,0 +1,19 @@
/// <reference types="react" />
import type { GridCsvExportOptions, GridRowId } from '../../../../models';
import type { GridCellParams } from '../../../../models/params/gridCellParams';
import type { GridStateColDef } from '../../../../models/colDef/gridColDef';
import type { GridApiCommunity } from '../../../../models/api/gridApiCommunity';
export declare const serializeCellValue: (cellParams: GridCellParams, options: {
csvOptions: CSVOptions;
ignoreValueFormatter: boolean;
}) => any;
type CSVOptions = Required<Pick<GridCsvExportOptions, 'delimiter' | 'shouldAppendQuotes' | 'escapeFormulas'>>;
interface BuildCSVOptions {
columns: GridStateColDef[];
rowIds: GridRowId[];
csvOptions: Required<Pick<GridCsvExportOptions, 'delimiter' | 'includeColumnGroupsHeaders' | 'includeHeaders' | 'shouldAppendQuotes' | 'escapeFormulas'>>;
ignoreValueFormatter: boolean;
apiRef: React.MutableRefObject<GridApiCommunity>;
}
export declare function buildCSV(options: BuildCSVOptions): string;
export {};

View File

@@ -0,0 +1,148 @@
import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../../../../colDef';
import { buildWarning } from '../../../../utils/warning';
function sanitizeCellValue(value, csvOptions) {
if (typeof value === 'string') {
if (csvOptions.shouldAppendQuotes || csvOptions.escapeFormulas) {
const escapedValue = value.replace(/"/g, '""');
// Make sure value containing delimiter or line break won't be split into multiple cells
if ([csvOptions.delimiter, '\n', '\r', '"'].some(delimiter => value.includes(delimiter))) {
return `"${escapedValue}"`;
}
if (csvOptions.escapeFormulas) {
// See https://owasp.org/www-community/attacks/CSV_Injection
if (['=', '+', '-', '@', '\t', '\r'].includes(escapedValue[0])) {
return `'${escapedValue}`;
}
}
return escapedValue;
}
return value;
}
return value;
}
export const serializeCellValue = (cellParams, options) => {
const {
csvOptions,
ignoreValueFormatter
} = options;
let value;
if (ignoreValueFormatter) {
var _cellParams$value2;
const columnType = cellParams.colDef.type;
if (columnType === 'number') {
value = String(cellParams.value);
} else if (columnType === 'date' || columnType === 'dateTime') {
var _cellParams$value;
value = (_cellParams$value = cellParams.value) == null ? void 0 : _cellParams$value.toISOString();
} else if (typeof ((_cellParams$value2 = cellParams.value) == null ? void 0 : _cellParams$value2.toString) === 'function') {
value = cellParams.value.toString();
} else {
value = cellParams.value;
}
} else {
value = cellParams.formattedValue;
}
return sanitizeCellValue(value, csvOptions);
};
const objectFormattedValueWarning = buildWarning(['MUI: When the value of a field is an object or a `renderCell` is provided, the CSV export might not display the value correctly.', 'You can provide a `valueFormatter` with a string representation to be used.']);
class CSVRow {
constructor(options) {
this.options = void 0;
this.rowString = '';
this.isEmpty = true;
this.options = options;
}
addValue(value) {
if (!this.isEmpty) {
this.rowString += this.options.csvOptions.delimiter;
}
if (value === null || value === undefined) {
this.rowString += '';
} else if (typeof this.options.sanitizeCellValue === 'function') {
this.rowString += this.options.sanitizeCellValue(value, this.options.csvOptions);
} else {
this.rowString += value;
}
this.isEmpty = false;
}
getRowString() {
return this.rowString;
}
}
const serializeRow = ({
id,
columns,
getCellParams,
csvOptions,
ignoreValueFormatter
}) => {
const row = new CSVRow({
csvOptions
});
columns.forEach(column => {
const cellParams = getCellParams(id, column.field);
if (process.env.NODE_ENV !== 'production') {
if (String(cellParams.formattedValue) === '[object Object]') {
objectFormattedValueWarning();
}
}
row.addValue(serializeCellValue(cellParams, {
ignoreValueFormatter,
csvOptions
}));
});
return row.getRowString();
};
export function buildCSV(options) {
const {
columns,
rowIds,
csvOptions,
ignoreValueFormatter,
apiRef
} = options;
const CSVBody = rowIds.reduce((acc, id) => `${acc}${serializeRow({
id,
columns,
getCellParams: apiRef.current.getCellParams,
ignoreValueFormatter,
csvOptions
})}\r\n`, '').trim();
if (!csvOptions.includeHeaders) {
return CSVBody;
}
const filteredColumns = columns.filter(column => column.field !== GRID_CHECKBOX_SELECTION_COL_DEF.field);
const headerRows = [];
if (csvOptions.includeColumnGroupsHeaders) {
const columnGroupLookup = apiRef.current.unstable_getAllGroupDetails();
let maxColumnGroupsDepth = 0;
const columnGroupPathsLookup = filteredColumns.reduce((acc, column) => {
const columnGroupPath = apiRef.current.unstable_getColumnGroupPath(column.field);
acc[column.field] = columnGroupPath;
maxColumnGroupsDepth = Math.max(maxColumnGroupsDepth, columnGroupPath.length);
return acc;
}, {});
for (let i = 0; i < maxColumnGroupsDepth; i += 1) {
const headerGroupRow = new CSVRow({
csvOptions,
sanitizeCellValue
});
headerRows.push(headerGroupRow);
filteredColumns.forEach(column => {
const columnGroupId = (columnGroupPathsLookup[column.field] || [])[i];
const columnGroup = columnGroupLookup[columnGroupId];
headerGroupRow.addValue(columnGroup ? columnGroup.headerName || columnGroup.groupId : '');
});
}
}
const mainHeaderRow = new CSVRow({
csvOptions,
sanitizeCellValue
});
filteredColumns.forEach(column => {
mainHeaderRow.addValue(column.headerName || column.field);
});
headerRows.push(mainHeaderRow);
const CSVHead = `${headerRows.map(row => row.getRowString()).join('\r\n')}\r\n`;
return `${CSVHead}${CSVBody}`.trim();
}

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import type { DataGridProcessedProps } from '../../../models/props/DataGridProps';
/**
* @requires useGridColumns (state)
* @requires useGridFilter (state)
* @requires useGridSorting (state)
* @requires useGridSelection (state)
* @requires useGridParamsApi (method)
*/
export declare const useGridCsvExport: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'unstable_ignoreValueFormatterDuringExport'>) => void;

View File

@@ -0,0 +1,76 @@
import * as React from 'react';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { useGridLogger } from '../../utils/useGridLogger';
import { exportAs } from '../../../utils/exportAs';
import { buildCSV } from './serializers/csvSerializer';
import { getColumnsToExport, defaultGetRowsToExport } from './utils';
import { useGridRegisterPipeProcessor } from '../../core/pipeProcessing';
import { GridCsvExportMenuItem } from '../../../components/toolbar/GridToolbarExport';
import { jsx as _jsx } from "react/jsx-runtime";
/**
* @requires useGridColumns (state)
* @requires useGridFilter (state)
* @requires useGridSorting (state)
* @requires useGridSelection (state)
* @requires useGridParamsApi (method)
*/
export const useGridCsvExport = (apiRef, props) => {
const logger = useGridLogger(apiRef, 'useGridCsvExport');
const ignoreValueFormatterProp = props.unstable_ignoreValueFormatterDuringExport;
const ignoreValueFormatter = (typeof ignoreValueFormatterProp === 'object' ? ignoreValueFormatterProp == null ? void 0 : ignoreValueFormatterProp.csvExport : ignoreValueFormatterProp) || false;
const getDataAsCsv = React.useCallback((options = {}) => {
var _options$getRowsToExp, _options$shouldAppend, _options$includeHeade, _options$includeColum, _options$escapeFormul;
logger.debug(`Get data as CSV`);
const exportedColumns = getColumnsToExport({
apiRef,
options
});
const getRowsToExport = (_options$getRowsToExp = options.getRowsToExport) != null ? _options$getRowsToExp : defaultGetRowsToExport;
const exportedRowIds = getRowsToExport({
apiRef
});
return buildCSV({
columns: exportedColumns,
rowIds: exportedRowIds,
csvOptions: {
delimiter: options.delimiter || ',',
shouldAppendQuotes: (_options$shouldAppend = options.shouldAppendQuotes) != null ? _options$shouldAppend : true,
includeHeaders: (_options$includeHeade = options.includeHeaders) != null ? _options$includeHeade : true,
includeColumnGroupsHeaders: (_options$includeColum = options.includeColumnGroupsHeaders) != null ? _options$includeColum : true,
escapeFormulas: (_options$escapeFormul = options.escapeFormulas) != null ? _options$escapeFormul : true
},
ignoreValueFormatter,
apiRef
});
}, [logger, apiRef, ignoreValueFormatter]);
const exportDataAsCsv = React.useCallback(options => {
logger.debug(`Export data as CSV`);
const csv = getDataAsCsv(options);
const blob = new Blob([options != null && options.utf8WithBom ? new Uint8Array([0xef, 0xbb, 0xbf]) : '', csv], {
type: 'text/csv'
});
exportAs(blob, 'csv', options == null ? void 0 : options.fileName);
}, [logger, getDataAsCsv]);
const csvExportApi = {
getDataAsCsv,
exportDataAsCsv
};
useGridApiMethod(apiRef, csvExportApi, 'public');
/**
* PRE-PROCESSING
*/
const addExportMenuButtons = React.useCallback((initialValue, options) => {
var _options$csvOptions;
if ((_options$csvOptions = options.csvOptions) != null && _options$csvOptions.disableToolbarButton) {
return initialValue;
}
return [...initialValue, {
component: /*#__PURE__*/_jsx(GridCsvExportMenuItem, {
options: options.csvOptions
}),
componentName: 'csvExport'
}];
}, []);
useGridRegisterPipeProcessor(apiRef, 'exportMenu', addExportMenuButtons);
};

View File

@@ -0,0 +1,10 @@
import * as React from 'react';
import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity';
import { DataGridProcessedProps } from '../../../models/props/DataGridProps';
/**
* @requires useGridColumns (state)
* @requires useGridFilter (state)
* @requires useGridSorting (state)
* @requires useGridParamsApi (method)
*/
export declare const useGridPrintExport: (apiRef: React.MutableRefObject<GridPrivateApiCommunity>, props: Pick<DataGridProcessedProps, 'pagination' | 'columnHeaderHeight'>) => void;

View File

@@ -0,0 +1,285 @@
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { unstable_ownerDocument as ownerDocument } from '@mui/utils';
import { useGridLogger } from '../../utils/useGridLogger';
import { gridExpandedRowCountSelector } from '../filter/gridFilterSelector';
import { gridColumnDefinitionsSelector, gridColumnVisibilityModelSelector } from '../columns/gridColumnsSelector';
import { gridClasses } from '../../../constants/gridClasses';
import { useGridApiMethod } from '../../utils/useGridApiMethod';
import { gridRowsMetaSelector } from '../rows/gridRowsMetaSelector';
import { getColumnsToExport } from './utils';
import { getDerivedPaginationModel } from '../pagination/useGridPaginationModel';
import { useGridRegisterPipeProcessor } from '../../core/pipeProcessing';
import { GridPrintExportMenuItem } from '../../../components/toolbar/GridToolbarExport';
import { getTotalHeaderHeight } from '../columns/gridColumnsUtils';
import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../../../colDef/gridCheckboxSelectionColDef';
import { gridDataRowIdsSelector, gridRowsLookupSelector } from '../rows/gridRowsSelector';
import { jsx as _jsx } from "react/jsx-runtime";
function raf() {
return new Promise(resolve => {
requestAnimationFrame(() => {
resolve();
});
});
}
function buildPrintWindow(title) {
const iframeEl = document.createElement('iframe');
iframeEl.style.position = 'absolute';
iframeEl.style.width = '0px';
iframeEl.style.height = '0px';
iframeEl.title = title || document.title;
return iframeEl;
}
/**
* @requires useGridColumns (state)
* @requires useGridFilter (state)
* @requires useGridSorting (state)
* @requires useGridParamsApi (method)
*/
export const useGridPrintExport = (apiRef, props) => {
const logger = useGridLogger(apiRef, 'useGridPrintExport');
const doc = React.useRef(null);
const previousGridState = React.useRef(null);
const previousColumnVisibility = React.useRef({});
const previousRows = React.useRef([]);
React.useEffect(() => {
doc.current = ownerDocument(apiRef.current.rootElementRef.current);
}, [apiRef]);
// Returns a promise because updateColumns triggers state update and
// the new state needs to be in place before the grid can be sized correctly
const updateGridColumnsForPrint = React.useCallback((fields, allColumns, includeCheckboxes) => new Promise(resolve => {
const exportedColumnFields = getColumnsToExport({
apiRef,
options: {
fields,
allColumns
}
}).map(column => column.field);
const columns = gridColumnDefinitionsSelector(apiRef);
const newColumnVisibilityModel = {};
columns.forEach(column => {
newColumnVisibilityModel[column.field] = exportedColumnFields.includes(column.field);
});
if (includeCheckboxes) {
newColumnVisibilityModel[GRID_CHECKBOX_SELECTION_COL_DEF.field] = true;
}
apiRef.current.setColumnVisibilityModel(newColumnVisibilityModel);
resolve();
}), [apiRef]);
const updateGridRowsForPrint = React.useCallback(getRowsToExport => {
const rowsToExportIds = getRowsToExport({
apiRef
});
const newRows = rowsToExportIds.map(id => apiRef.current.getRow(id));
apiRef.current.setRows(newRows);
}, [apiRef]);
const handlePrintWindowLoad = React.useCallback((printWindow, options) => {
var _querySelector, _querySelector2;
const normalizeOptions = _extends({
copyStyles: true,
hideToolbar: false,
hideFooter: false,
includeCheckboxes: false
}, options);
const printDoc = printWindow.contentDocument;
if (!printDoc) {
return;
}
const rowsMeta = gridRowsMetaSelector(apiRef.current.state);
const gridRootElement = apiRef.current.rootElementRef.current;
const gridClone = gridRootElement.cloneNode(true);
// Allow to overflow to not hide the border of the last row
const gridMain = gridClone.querySelector(`.${gridClasses.main}`);
gridMain.style.overflow = 'visible';
// See https://support.google.com/chrome/thread/191619088?hl=en&msgid=193009642
gridClone.style.contain = 'size';
const columnHeaders = gridClone.querySelector(`.${gridClasses.columnHeaders}`);
const columnHeadersInner = columnHeaders.querySelector(`.${gridClasses.columnHeadersInner}`);
columnHeadersInner.style.width = '100%';
let gridToolbarElementHeight = ((_querySelector = gridRootElement.querySelector(`.${gridClasses.toolbarContainer}`)) == null ? void 0 : _querySelector.offsetHeight) || 0;
let gridFooterElementHeight = ((_querySelector2 = gridRootElement.querySelector(`.${gridClasses.footerContainer}`)) == null ? void 0 : _querySelector2.offsetHeight) || 0;
if (normalizeOptions.hideToolbar) {
var _gridClone$querySelec;
(_gridClone$querySelec = gridClone.querySelector(`.${gridClasses.toolbarContainer}`)) == null || _gridClone$querySelec.remove();
gridToolbarElementHeight = 0;
}
if (normalizeOptions.hideFooter) {
var _gridClone$querySelec2;
(_gridClone$querySelec2 = gridClone.querySelector(`.${gridClasses.footerContainer}`)) == null || _gridClone$querySelec2.remove();
gridFooterElementHeight = 0;
}
// Expand container height to accommodate all rows
const computedTotalHeight = rowsMeta.currentPageTotalHeight + getTotalHeaderHeight(apiRef, props.columnHeaderHeight) + gridToolbarElementHeight + gridFooterElementHeight;
gridClone.style.height = `${computedTotalHeight}px`;
// The height above does not include grid border width, so we need to exclude it
gridClone.style.boxSizing = 'content-box';
// the footer is always being placed at the bottom of the page as if all rows are exported
// so if getRowsToExport is being used to only export a subset of rows then we need to
// adjust the footer position to be correctly placed at the bottom of the grid
if (options != null && options.getRowsToExport) {
const gridFooterElement = gridClone.querySelector(`.${gridClasses.footerContainer}`);
gridFooterElement.style.position = 'absolute';
gridFooterElement.style.width = '100%';
gridFooterElement.style.top = `${computedTotalHeight - gridFooterElementHeight}px`;
}
// printDoc.body.appendChild(gridClone); should be enough but a clone isolation bug in Safari
// prevents us to do it
const container = document.createElement('div');
container.appendChild(gridClone);
printDoc.body.innerHTML = container.innerHTML;
const defaultPageStyle = typeof normalizeOptions.pageStyle === 'function' ? normalizeOptions.pageStyle() : normalizeOptions.pageStyle;
if (typeof defaultPageStyle === 'string') {
// TODO custom styles should always win
const styleElement = printDoc.createElement('style');
styleElement.appendChild(printDoc.createTextNode(defaultPageStyle));
printDoc.head.appendChild(styleElement);
}
if (normalizeOptions.bodyClassName) {
printDoc.body.classList.add(...normalizeOptions.bodyClassName.split(' '));
}
const stylesheetLoadPromises = [];
if (normalizeOptions.copyStyles) {
const rootCandidate = gridRootElement.getRootNode();
const root = rootCandidate.constructor.name === 'ShadowRoot' ? rootCandidate : doc.current;
const headStyleElements = root.querySelectorAll("style, link[rel='stylesheet']");
for (let i = 0; i < headStyleElements.length; i += 1) {
const node = headStyleElements[i];
if (node.tagName === 'STYLE') {
const newHeadStyleElements = printDoc.createElement(node.tagName);
const sheet = node.sheet;
if (sheet) {
let styleCSS = '';
// NOTE: for-of is not supported by IE
for (let j = 0; j < sheet.cssRules.length; j += 1) {
if (typeof sheet.cssRules[j].cssText === 'string') {
styleCSS += `${sheet.cssRules[j].cssText}\r\n`;
}
}
newHeadStyleElements.appendChild(printDoc.createTextNode(styleCSS));
printDoc.head.appendChild(newHeadStyleElements);
}
} else if (node.getAttribute('href')) {
// If `href` tag is empty, avoid loading these links
const newHeadStyleElements = printDoc.createElement(node.tagName);
for (let j = 0; j < node.attributes.length; j += 1) {
const attr = node.attributes[j];
if (attr) {
newHeadStyleElements.setAttribute(attr.nodeName, attr.nodeValue || '');
}
}
stylesheetLoadPromises.push(new Promise(resolve => {
newHeadStyleElements.addEventListener('load', () => resolve());
}));
printDoc.head.appendChild(newHeadStyleElements);
}
}
}
// Trigger print
if (process.env.NODE_ENV !== 'test') {
// wait for remote stylesheets to load
Promise.all(stylesheetLoadPromises).then(() => {
printWindow.contentWindow.print();
});
}
}, [apiRef, doc, props.columnHeaderHeight]);
const handlePrintWindowAfterPrint = React.useCallback(printWindow => {
var _previousGridState$cu;
// Remove the print iframe
doc.current.body.removeChild(printWindow);
// Revert grid to previous state
apiRef.current.restoreState(previousGridState.current || {});
if (!((_previousGridState$cu = previousGridState.current) != null && (_previousGridState$cu = _previousGridState$cu.columns) != null && _previousGridState$cu.columnVisibilityModel)) {
// if the apiRef.current.exportState(); did not exported the column visibility, we update it
apiRef.current.setColumnVisibilityModel(previousColumnVisibility.current);
}
apiRef.current.unstable_setVirtualization(true);
apiRef.current.setRows(previousRows.current);
// Clear local state
previousGridState.current = null;
previousColumnVisibility.current = {};
previousRows.current = [];
}, [apiRef]);
const exportDataAsPrint = React.useCallback(async options => {
logger.debug(`Export data as Print`);
if (!apiRef.current.rootElementRef.current) {
throw new Error('MUI: No grid root element available.');
}
previousGridState.current = apiRef.current.exportState();
// It appends that the visibility model is not exported, especially if columnVisibility is not controlled
previousColumnVisibility.current = gridColumnVisibilityModelSelector(apiRef);
const gridRowsLookup = gridRowsLookupSelector(apiRef);
previousRows.current = gridDataRowIdsSelector(apiRef).map(rowId => gridRowsLookup[rowId]);
if (props.pagination) {
const visibleRowCount = gridExpandedRowCountSelector(apiRef);
const paginationModel = {
page: 0,
pageSize: visibleRowCount
};
apiRef.current.setState(state => _extends({}, state, {
pagination: _extends({}, state.pagination, {
paginationModel: getDerivedPaginationModel(state.pagination,
// Using signature `DataGridPro` to allow more than 100 rows in the print export
'DataGridPro', paginationModel)
})
}));
apiRef.current.forceUpdate();
}
await updateGridColumnsForPrint(options == null ? void 0 : options.fields, options == null ? void 0 : options.allColumns, options == null ? void 0 : options.includeCheckboxes);
if (options != null && options.getRowsToExport) {
updateGridRowsForPrint(options.getRowsToExport);
}
apiRef.current.unstable_setVirtualization(false);
await raf(); // wait for the state changes to take action
const printWindow = buildPrintWindow(options == null ? void 0 : options.fileName);
if (process.env.NODE_ENV === 'test') {
doc.current.body.appendChild(printWindow);
// In test env, run the all pipeline without waiting for loading
handlePrintWindowLoad(printWindow, options);
handlePrintWindowAfterPrint(printWindow);
} else {
printWindow.onload = () => {
handlePrintWindowLoad(printWindow, options);
const mediaQueryList = printWindow.contentWindow.matchMedia('print');
mediaQueryList.addEventListener('change', mql => {
const isAfterPrint = mql.matches === false;
if (isAfterPrint) {
handlePrintWindowAfterPrint(printWindow);
}
});
};
doc.current.body.appendChild(printWindow);
}
}, [props, logger, apiRef, handlePrintWindowLoad, handlePrintWindowAfterPrint, updateGridColumnsForPrint, updateGridRowsForPrint]);
const printExportApi = {
exportDataAsPrint
};
useGridApiMethod(apiRef, printExportApi, 'public');
/**
* PRE-PROCESSING
*/
const addExportMenuButtons = React.useCallback((initialValue, options) => {
var _options$printOptions;
if ((_options$printOptions = options.printOptions) != null && _options$printOptions.disableToolbarButton) {
return initialValue;
}
return [...initialValue, {
component: /*#__PURE__*/_jsx(GridPrintExportMenuItem, {
options: options.printOptions
}),
componentName: 'printExport'
}];
}, []);
useGridRegisterPipeProcessor(apiRef, 'exportMenu', addExportMenuButtons);
};

View File

@@ -0,0 +1,15 @@
import * as React from 'react';
import { GridApiCommunity } from '../../../models/api/gridApiCommunity';
import { GridExportOptions, GridCsvGetRowsToExportParams } from '../../../models/gridExport';
import { GridStateColDef } from '../../../models/colDef/gridColDef';
import { GridRowId } from '../../../models';
interface GridGetColumnsToExportParams {
/**
* The API of the grid.
*/
apiRef: React.MutableRefObject<GridApiCommunity>;
options: GridExportOptions;
}
export declare const getColumnsToExport: ({ apiRef, options, }: GridGetColumnsToExportParams) => GridStateColDef[];
export declare const defaultGetRowsToExport: ({ apiRef }: GridCsvGetRowsToExportParams) => GridRowId[];
export {};

View File

@@ -0,0 +1,38 @@
import { gridColumnDefinitionsSelector, gridVisibleColumnDefinitionsSelector } from '../columns';
import { gridFilteredSortedRowIdsSelector } from '../filter';
import { gridPinnedRowsSelector, gridRowTreeSelector } from '../rows/gridRowsSelector';
export const getColumnsToExport = ({
apiRef,
options
}) => {
const columns = gridColumnDefinitionsSelector(apiRef);
if (options.fields) {
return options.fields.reduce((currentColumns, field) => {
const column = columns.find(col => col.field === field);
if (column) {
currentColumns.push(column);
}
return currentColumns;
}, []);
}
const validColumns = options.allColumns ? columns : gridVisibleColumnDefinitionsSelector(apiRef);
return validColumns.filter(column => !column.disableExport);
};
export const defaultGetRowsToExport = ({
apiRef
}) => {
var _pinnedRows$top, _pinnedRows$bottom;
const filteredSortedRowIds = gridFilteredSortedRowIdsSelector(apiRef);
const rowTree = gridRowTreeSelector(apiRef);
const selectedRows = apiRef.current.getSelectedRows();
const bodyRows = filteredSortedRowIds.filter(id => rowTree[id].type !== 'footer');
const pinnedRows = gridPinnedRowsSelector(apiRef);
const topPinnedRowsIds = (pinnedRows == null || (_pinnedRows$top = pinnedRows.top) == null ? void 0 : _pinnedRows$top.map(row => row.id)) || [];
const bottomPinnedRowsIds = (pinnedRows == null || (_pinnedRows$bottom = pinnedRows.bottom) == null ? void 0 : _pinnedRows$bottom.map(row => row.id)) || [];
bodyRows.unshift(...topPinnedRowsIds);
bodyRows.push(...bottomPinnedRowsIds);
if (selectedRows.size > 0) {
return bodyRows.filter(id => selectedRows.has(id));
}
return bodyRows;
};

View File

@@ -0,0 +1,88 @@
import { GridFilterItem } from '../../../models/gridFilterItem';
import { GridStateCommunity } from '../../../models/gridStateCommunity';
/**
* Get the current filter model.
* @category Filtering
*/
export declare const gridFilterModelSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, import("../../..").GridFilterModel>;
/**
* Get the current quick filter values.
* @category Filtering
*/
export declare const gridQuickFilterValuesSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, any[] | undefined>;
/**
* @category Visible rows
* @ignore - do not document.
*/
export declare const gridVisibleRowsLookupSelector: (state: GridStateCommunity) => import("./gridFilterState").GridVisibleRowsLookupState;
/**
* @category Filtering
* @ignore - do not document.
*/
export declare const gridFilteredRowsLookupSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, Record<import("../../..").GridRowId, boolean>>;
/**
* @category Filtering
* @ignore - do not document.
*/
export declare const gridFilteredDescendantCountLookupSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, Record<import("../../..").GridRowId, number>>;
/**
* Get the id and the model of the rows accessible after the filtering process.
* Does not contain the collapsed children.
* @category Filtering
*/
export declare const gridExpandedSortedRowEntriesSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, {
id: import("../../..").GridRowId;
model: import("../../..").GridValidRowModel;
}[]>;
/**
* Get the id of the rows accessible after the filtering process.
* Does not contain the collapsed children.
* @category Filtering
*/
export declare const gridExpandedSortedRowIdsSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, import("../../..").GridRowId[]>;
/**
* Get the id and the model of the rows accessible after the filtering process.
* Contains the collapsed children.
* @category Filtering
*/
export declare const gridFilteredSortedRowEntriesSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, {
id: import("../../..").GridRowId;
model: import("../../..").GridValidRowModel;
}[]>;
/**
* Get the id of the rows accessible after the filtering process.
* Contains the collapsed children.
* @category Filtering
*/
export declare const gridFilteredSortedRowIdsSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, import("../../..").GridRowId[]>;
/**
* Get the id and the model of the top level rows accessible after the filtering process.
* @category Filtering
*/
export declare const gridFilteredSortedTopLevelRowEntriesSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, {
id: import("../../..").GridRowId;
model: import("../../..").GridValidRowModel;
}[]>;
/**
* Get the amount of rows accessible after the filtering process.
* @category Filtering
*/
export declare const gridExpandedRowCountSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, number>;
/**
* Get the amount of top level rows accessible after the filtering process.
* @category Filtering
*/
export declare const gridFilteredTopLevelRowCountSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, number>;
/**
* @category Filtering
* @ignore - do not document.
*/
export declare const gridFilterActiveItemsSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, GridFilterItem[]>;
export type GridFilterActiveItemsLookup = {
[field: string]: GridFilterItem[];
};
/**
* @category Filtering
* @ignore - do not document.
*/
export declare const gridFilterActiveItemsLookupSelector: import("../../../utils/createSelector").OutputSelector<GridStateCommunity, GridFilterActiveItemsLookup>;

View File

@@ -0,0 +1,131 @@
import { createSelector, createSelectorMemoized } from '../../../utils/createSelector';
import { gridSortedRowEntriesSelector } from '../sorting/gridSortingSelector';
import { gridColumnLookupSelector } from '../columns/gridColumnsSelector';
import { gridRowMaximumTreeDepthSelector, gridRowTreeSelector } from '../rows/gridRowsSelector';
/**
* @category Filtering
*/
const gridFilterStateSelector = state => state.filter;
/**
* Get the current filter model.
* @category Filtering
*/
export const gridFilterModelSelector = createSelector(gridFilterStateSelector, filterState => filterState.filterModel);
/**
* Get the current quick filter values.
* @category Filtering
*/
export const gridQuickFilterValuesSelector = createSelector(gridFilterModelSelector, filterModel => filterModel.quickFilterValues);
/**
* @category Visible rows
* @ignore - do not document.
*/
export const gridVisibleRowsLookupSelector = state => state.visibleRowsLookup;
/**
* @category Filtering
* @ignore - do not document.
*/
export const gridFilteredRowsLookupSelector = createSelector(gridFilterStateSelector, filterState => filterState.filteredRowsLookup);
/**
* @category Filtering
* @ignore - do not document.
*/
export const gridFilteredDescendantCountLookupSelector = createSelector(gridFilterStateSelector, filterState => filterState.filteredDescendantCountLookup);
/**
* Get the id and the model of the rows accessible after the filtering process.
* Does not contain the collapsed children.
* @category Filtering
*/
export const gridExpandedSortedRowEntriesSelector = createSelectorMemoized(gridVisibleRowsLookupSelector, gridSortedRowEntriesSelector, (visibleRowsLookup, sortedRows) => sortedRows.filter(row => visibleRowsLookup[row.id] !== false));
/**
* Get the id of the rows accessible after the filtering process.
* Does not contain the collapsed children.
* @category Filtering
*/
export const gridExpandedSortedRowIdsSelector = createSelectorMemoized(gridExpandedSortedRowEntriesSelector, visibleSortedRowEntries => visibleSortedRowEntries.map(row => row.id));
/**
* Get the id and the model of the rows accessible after the filtering process.
* Contains the collapsed children.
* @category Filtering
*/
export const gridFilteredSortedRowEntriesSelector = createSelectorMemoized(gridFilteredRowsLookupSelector, gridSortedRowEntriesSelector, (filteredRowsLookup, sortedRows) => sortedRows.filter(row => filteredRowsLookup[row.id] !== false));
/**
* Get the id of the rows accessible after the filtering process.
* Contains the collapsed children.
* @category Filtering
*/
export const gridFilteredSortedRowIdsSelector = createSelectorMemoized(gridFilteredSortedRowEntriesSelector, filteredSortedRowEntries => filteredSortedRowEntries.map(row => row.id));
/**
* Get the id and the model of the top level rows accessible after the filtering process.
* @category Filtering
*/
export const gridFilteredSortedTopLevelRowEntriesSelector = createSelectorMemoized(gridExpandedSortedRowEntriesSelector, gridRowTreeSelector, gridRowMaximumTreeDepthSelector, (visibleSortedRows, rowTree, rowTreeDepth) => {
if (rowTreeDepth < 2) {
return visibleSortedRows;
}
return visibleSortedRows.filter(row => {
var _rowTree$row$id;
return ((_rowTree$row$id = rowTree[row.id]) == null ? void 0 : _rowTree$row$id.depth) === 0;
});
});
/**
* Get the amount of rows accessible after the filtering process.
* @category Filtering
*/
export const gridExpandedRowCountSelector = createSelector(gridExpandedSortedRowEntriesSelector, visibleSortedRows => visibleSortedRows.length);
/**
* Get the amount of top level rows accessible after the filtering process.
* @category Filtering
*/
export const gridFilteredTopLevelRowCountSelector = createSelector(gridFilteredSortedTopLevelRowEntriesSelector, visibleSortedTopLevelRows => visibleSortedTopLevelRows.length);
/**
* @category Filtering
* @ignore - do not document.
*/
export const gridFilterActiveItemsSelector = createSelectorMemoized(gridFilterModelSelector, gridColumnLookupSelector, (filterModel, columnLookup) => {
var _filterModel$items;
return (_filterModel$items = filterModel.items) == null ? void 0 : _filterModel$items.filter(item => {
var _column$filterOperato, _item$value;
if (!item.field) {
return false;
}
const column = columnLookup[item.field];
if (!(column != null && column.filterOperators) || (column == null || (_column$filterOperato = column.filterOperators) == null ? void 0 : _column$filterOperato.length) === 0) {
return false;
}
const filterOperator = column.filterOperators.find(operator => operator.value === item.operator);
if (!filterOperator) {
return false;
}
return !filterOperator.InputComponent || item.value != null && ((_item$value = item.value) == null ? void 0 : _item$value.toString()) !== '';
});
});
/**
* @category Filtering
* @ignore - do not document.
*/
export const gridFilterActiveItemsLookupSelector = createSelectorMemoized(gridFilterActiveItemsSelector, activeFilters => {
const result = activeFilters.reduce((res, filterItem) => {
if (!res[filterItem.field]) {
res[filterItem.field] = [filterItem];
} else {
res[filterItem.field].push(filterItem);
}
return res;
}, {});
return result;
});

Some files were not shown because too many files have changed in this diff Show More