@mui/x-data-grid-premium 8.8.0 → 8.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +101 -24
  2. package/DataGridPremium/DataGridPremium.js +1 -1
  3. package/DataGridPremium/useDataGridPremiumComponent.js +19 -7
  4. package/DataGridPremium/useDataGridPremiumProps.js +2 -1
  5. package/esm/DataGridPremium/DataGridPremium.js +1 -1
  6. package/esm/DataGridPremium/useDataGridPremiumComponent.js +21 -8
  7. package/esm/DataGridPremium/useDataGridPremiumProps.js +2 -1
  8. package/esm/hooks/features/aggregation/createAggregationLookup.d.ts +9 -4
  9. package/esm/hooks/features/aggregation/createAggregationLookup.js +78 -41
  10. package/esm/hooks/features/aggregation/gridAggregationFunctions.js +2 -2
  11. package/esm/hooks/features/aggregation/gridAggregationInterfaces.d.ts +13 -1
  12. package/esm/hooks/features/aggregation/gridAggregationUtils.d.ts +2 -1
  13. package/esm/hooks/features/aggregation/gridAggregationUtils.js +2 -1
  14. package/esm/hooks/features/aggregation/useGridAggregation.js +94 -18
  15. package/esm/hooks/features/pivoting/gridPivotingInterfaces.d.ts +12 -2
  16. package/esm/hooks/features/pivoting/useGridPivoting.d.ts +2 -1
  17. package/esm/hooks/features/pivoting/useGridPivoting.js +57 -35
  18. package/esm/hooks/features/pivoting/utils.d.ts +3 -1
  19. package/esm/hooks/features/pivoting/utils.js +22 -14
  20. package/esm/hooks/features/rowGrouping/gridRowGroupingUtils.js +5 -1
  21. package/esm/index.js +3 -3
  22. package/esm/typeOverloads/modules.d.ts +2 -1
  23. package/hooks/features/aggregation/createAggregationLookup.d.ts +9 -4
  24. package/hooks/features/aggregation/createAggregationLookup.js +79 -41
  25. package/hooks/features/aggregation/gridAggregationFunctions.js +2 -2
  26. package/hooks/features/aggregation/gridAggregationInterfaces.d.ts +13 -1
  27. package/hooks/features/aggregation/gridAggregationUtils.d.ts +2 -1
  28. package/hooks/features/aggregation/gridAggregationUtils.js +4 -2
  29. package/hooks/features/aggregation/useGridAggregation.js +92 -16
  30. package/hooks/features/pivoting/gridPivotingInterfaces.d.ts +12 -2
  31. package/hooks/features/pivoting/useGridPivoting.d.ts +2 -1
  32. package/hooks/features/pivoting/useGridPivoting.js +60 -37
  33. package/hooks/features/pivoting/utils.d.ts +3 -1
  34. package/hooks/features/pivoting/utils.js +22 -14
  35. package/hooks/features/rowGrouping/gridRowGroupingUtils.js +5 -1
  36. package/index.js +3 -3
  37. package/package.json +4 -4
  38. package/typeOverloads/modules.d.ts +2 -1
@@ -1,51 +1,67 @@
1
- import { gridColumnLookupSelector, gridFilteredRowsLookupSelector, gridRowTreeSelector, GRID_ROOT_GROUP_ID, gridRowNodeSelector } from '@mui/x-data-grid-pro';
2
- import { getAggregationRules } from "./gridAggregationUtils.js";
3
- import { gridAggregationModelSelector } from "./gridAggregationSelectors.js";
4
- const getGroupAggregatedValue = (groupId, apiRef, aggregationRowsScope, aggregatedFields, aggregationRules, position) => {
1
+ import { gridColumnLookupSelector, gridRowTreeSelector, GRID_ROOT_GROUP_ID, gridRowsLookupSelector } from '@mui/x-data-grid-pro';
2
+ import { getVisibleRows } from '@mui/x-data-grid/internals';
3
+ export const shouldApplySorting = (aggregationRules, aggregatedFields) => {
4
+ return aggregatedFields.some(field => aggregationRules[field].aggregationFunction.applySorting);
5
+ };
6
+ const getGroupAggregatedValue = (groupId, apiRef, aggregationRowsScope, aggregatedFields, aggregationRules, position, applySorting, valueGetters, publicApi, groupAggregatedValuesLookup) => {
5
7
  const groupAggregationLookup = {};
6
8
  const aggregatedValues = [];
9
+ for (let i = 0; i < aggregatedFields.length; i += 1) {
10
+ aggregatedValues[i] = {
11
+ aggregatedField: aggregatedFields[i],
12
+ values: []
13
+ };
14
+ }
15
+ const rowTree = gridRowTreeSelector(apiRef);
16
+ const rowLookup = gridRowsLookupSelector(apiRef);
17
+ const isPivotActive = apiRef.current.state.pivoting.active;
7
18
  const rowIds = apiRef.current.getRowGroupChildren({
8
- groupId
19
+ groupId,
20
+ applySorting,
21
+ directChildrenOnly: true,
22
+ skipAutoGeneratedRows: false,
23
+ applyFiltering: aggregationRowsScope === 'filtered'
9
24
  });
10
- const filteredRowsLookup = gridFilteredRowsLookupSelector(apiRef);
11
- rowIds.forEach(rowId => {
12
- if (aggregationRowsScope === 'filtered' && filteredRowsLookup[rowId] === false) {
13
- return;
14
- }
15
-
16
- // If the row is a group, we want to aggregate based on its children
17
- // For instance in the following tree, we want the aggregated values of A to be based on A.A, A.B.A and A.B.B but not A.B
18
- // A
19
- // A.A
20
- // A.B
21
- // A.B.A
22
- // A.B.B
23
- const rowNode = gridRowNodeSelector(apiRef, rowId);
25
+ for (let i = 0; i < rowIds.length; i += 1) {
26
+ const rowId = rowIds[i];
27
+ const rowNode = rowTree[rowId];
24
28
  if (rowNode.type === 'group') {
25
- return;
29
+ // MERGE EXISTING VALUES FROM THE LOOKUP TABLE
30
+ const childGroupValues = groupAggregatedValuesLookup.get(rowId);
31
+ if (childGroupValues) {
32
+ for (let j = 0; j < aggregatedFields.length; j += 1) {
33
+ aggregatedValues[j].values = aggregatedValues[j].values.concat(childGroupValues[j].values);
34
+ }
35
+ }
36
+ continue;
26
37
  }
27
- const row = apiRef.current.getRow(rowId);
38
+ const row = rowLookup[rowId];
28
39
  for (let j = 0; j < aggregatedFields.length; j += 1) {
29
40
  const aggregatedField = aggregatedFields[j];
30
41
  const columnAggregationRules = aggregationRules[aggregatedField];
31
42
  const aggregationFunction = columnAggregationRules.aggregationFunction;
32
43
  const field = aggregatedField;
33
- if (aggregatedValues[j] === undefined) {
34
- aggregatedValues[j] = {
35
- aggregatedField,
36
- values: []
37
- };
38
- }
44
+ let value;
39
45
  if (typeof aggregationFunction.getCellValue === 'function') {
40
- aggregatedValues[j].values.push(aggregationFunction.getCellValue({
46
+ value = aggregationFunction.getCellValue({
47
+ field,
41
48
  row
42
- }));
49
+ });
50
+ } else if (isPivotActive) {
51
+ // Since we know that pivoted fields are flat, we can use the row directly, and save lots of processing time
52
+ value = row[field];
43
53
  } else {
44
- const colDef = apiRef.current.getColumn(field);
45
- aggregatedValues[j].values.push(apiRef.current.getRowValue(row, colDef));
54
+ if (!row) {
55
+ continue;
56
+ }
57
+ const valueGetter = valueGetters[aggregatedField];
58
+ value = valueGetter(row);
59
+ }
60
+ if (value !== undefined) {
61
+ aggregatedValues[j].values.push(value);
46
62
  }
47
63
  }
48
- });
64
+ }
49
65
  for (let i = 0; i < aggregatedValues.length; i += 1) {
50
66
  const {
51
67
  aggregatedField,
@@ -56,13 +72,16 @@ const getGroupAggregatedValue = (groupId, apiRef, aggregationRowsScope, aggregat
56
72
  values,
57
73
  groupId,
58
74
  field: aggregatedField // Added per user request in https://github.com/mui/mui-x/issues/6995#issuecomment-1327423455
59
- });
75
+ }, publicApi);
60
76
  groupAggregationLookup[aggregatedField] = {
61
77
  position,
62
78
  value
63
79
  };
64
80
  }
65
- return groupAggregationLookup;
81
+ return {
82
+ groupAggregationLookup,
83
+ aggregatedValues
84
+ };
66
85
  };
67
86
  const getGroupAggregatedValueDataSource = (groupId, apiRef, aggregatedFields, position) => {
68
87
  const groupAggregationLookup = {};
@@ -77,21 +96,37 @@ const getGroupAggregatedValueDataSource = (groupId, apiRef, aggregatedFields, po
77
96
  };
78
97
  export const createAggregationLookup = ({
79
98
  apiRef,
80
- aggregationFunctions,
99
+ aggregationRules,
100
+ aggregatedFields,
81
101
  aggregationRowsScope,
82
102
  getAggregationPosition,
83
- isDataSource
103
+ isDataSource,
104
+ applySorting = false
84
105
  }) => {
85
- const aggregationRules = getAggregationRules(gridColumnLookupSelector(apiRef), gridAggregationModelSelector(apiRef), aggregationFunctions, isDataSource);
86
- const aggregatedFields = Object.keys(aggregationRules);
87
106
  if (aggregatedFields.length === 0) {
88
107
  return {};
89
108
  }
109
+ const columnsLookup = gridColumnLookupSelector(apiRef);
110
+ const valueGetters = {};
111
+ for (let i = 0; i < aggregatedFields.length; i += 1) {
112
+ const field = aggregatedFields[i];
113
+ const column = columnsLookup[field];
114
+ const valueGetter = row => apiRef.current.getRowValue(row, column);
115
+ valueGetters[field] = valueGetter;
116
+ }
90
117
  const aggregationLookup = {};
91
118
  const rowTree = gridRowTreeSelector(apiRef);
119
+ const groupAggregatedValuesLookup = new Map();
120
+ const {
121
+ rowIdToIndexMap
122
+ } = getVisibleRows(apiRef);
92
123
  const createGroupAggregationLookup = groupNode => {
93
- for (let i = 0; i < groupNode.children.length; i += 1) {
94
- const childId = groupNode.children[i];
124
+ let children = groupNode.children;
125
+ if (applySorting) {
126
+ children = children.toSorted((a, b) => rowIdToIndexMap.get(a) - rowIdToIndexMap.get(b));
127
+ }
128
+ for (let i = 0; i < children.length; i += 1) {
129
+ const childId = children[i];
95
130
  const childNode = rowTree[childId];
96
131
  if (childNode.type === 'group') {
97
132
  createGroupAggregationLookup(childNode);
@@ -102,7 +137,9 @@ export const createAggregationLookup = ({
102
137
  if (isDataSource) {
103
138
  aggregationLookup[groupNode.id] = getGroupAggregatedValueDataSource(groupNode.id, apiRef, aggregatedFields, position);
104
139
  } else if (groupNode.children.length) {
105
- aggregationLookup[groupNode.id] = getGroupAggregatedValue(groupNode.id, apiRef, aggregationRowsScope, aggregatedFields, aggregationRules, position);
140
+ const result = getGroupAggregatedValue(groupNode.id, apiRef, aggregationRowsScope, aggregatedFields, aggregationRules, position, applySorting, valueGetters, apiRef.current, groupAggregatedValuesLookup);
141
+ aggregationLookup[groupNode.id] = result.groupAggregationLookup;
142
+ groupAggregatedValuesLookup.set(groupNode.id, result.aggregatedValues);
106
143
  }
107
144
  }
108
145
  };
@@ -6,7 +6,7 @@ const sumAgg = {
6
6
  let sum = 0;
7
7
  for (let i = 0; i < values.length; i += 1) {
8
8
  const value = values[i];
9
- if (isNumber(value)) {
9
+ if (typeof value === 'number' && !Number.isNaN(value)) {
10
10
  sum += value;
11
11
  }
12
12
  }
@@ -25,7 +25,7 @@ const avgAgg = {
25
25
  let valuesCount = 0;
26
26
  for (let i = 0; i < values.length; i += 1) {
27
27
  const value = values[i];
28
- if (isNumber(value)) {
28
+ if (typeof value === 'number' && !Number.isNaN(value)) {
29
29
  valuesCount += 1;
30
30
  sum += value;
31
31
  }
@@ -1,4 +1,5 @@
1
1
  import { GridRowId, GridRowModel, GridColDef, GridValueFormatter } from '@mui/x-data-grid-pro';
2
+ import { GridApiPremium } from "../../../models/gridApiPremium.js";
2
3
  export interface GridAggregationState {
3
4
  model: GridAggregationModel;
4
5
  lookup: GridAggregationLookup;
@@ -28,6 +29,10 @@ export interface GridAggregationGetCellValueParams {
28
29
  * The row model of the row that the current cell belongs to.
29
30
  */
30
31
  row: GridRowModel;
32
+ /**
33
+ * The field of the cell that the aggregation function is applied to.
34
+ */
35
+ field: GridColDef['field'];
31
36
  }
32
37
  /**
33
38
  * Grid aggregation function definition interface.
@@ -39,9 +44,10 @@ export interface GridAggregationFunction<V = any, AV = V> {
39
44
  * Function that takes the current cell values and generates the aggregated value.
40
45
  * @template V, AV
41
46
  * @param {GridAggregationParams<V>} params The params of the current aggregated cell.
47
+ * @param {GridApiPremium} api The grid API.
42
48
  * @returns {AV} The aggregated value.
43
49
  */
44
- apply: (params: GridAggregationParams<V>) => AV | null | undefined;
50
+ apply: (params: GridAggregationParams<V>, api: GridApiPremium) => AV | null | undefined;
45
51
  /**
46
52
  * Label of the aggregation function.
47
53
  * Used for adding a label to the footer of the grouping column when this aggregation function is the only one being used.
@@ -72,6 +78,12 @@ export interface GridAggregationFunction<V = any, AV = V> {
72
78
  * @returns {V} The value of the cell that will be passed to the aggregation `apply` function
73
79
  */
74
80
  getCellValue?: (params: GridAggregationGetCellValueParams) => V;
81
+ /**
82
+ * Indicates if the aggregation function depends on rows being in a sorted order.
83
+ * If `true`, the values provided to `apply` will be sorted.
84
+ * @default false
85
+ */
86
+ applySorting?: boolean;
75
87
  }
76
88
  /**
77
89
  * Grid aggregation function data source definition interface.
@@ -1,5 +1,5 @@
1
1
  import { RefObject } from '@mui/x-internals/types';
2
- import { GridColDef, GridRowId } from '@mui/x-data-grid-pro';
2
+ import { GridColDef, GridRowId, GridGroupNode } from '@mui/x-data-grid-pro';
3
3
  import { GridColumnRawLookup, GridHydrateRowsValue } from '@mui/x-data-grid-pro/internals';
4
4
  import { GridAggregationFunction, GridAggregationFunctionDataSource, GridAggregationModel, GridAggregationRule, GridAggregationRules } from "./gridAggregationInterfaces.js";
5
5
  import { GridStatePremium } from "../../../models/gridStatePremium.js";
@@ -59,4 +59,5 @@ export declare const getAggregationFunctionLabel: ({
59
59
  apiRef: RefObject<GridApiPremium>;
60
60
  aggregationRule: GridAggregationRule;
61
61
  }) => string;
62
+ export declare const defaultGetAggregationPosition: (groupNode: GridGroupNode) => "inline" | "footer";
62
63
  export {};
@@ -177,4 +177,5 @@ export const getAggregationFunctionLabel = ({
177
177
  } catch {
178
178
  return aggregationRule.aggregationFunctionName;
179
179
  }
180
- };
180
+ };
181
+ export const defaultGetAggregationPosition = groupNode => groupNode.depth === -1 ? 'footer' : 'inline';
@@ -2,11 +2,11 @@
2
2
 
3
3
  import _extends from "@babel/runtime/helpers/esm/extends";
4
4
  import * as React from 'react';
5
- import { gridColumnLookupSelector, useGridEvent, useGridApiMethod } from '@mui/x-data-grid-pro';
5
+ import { gridColumnLookupSelector, useGridEvent, useGridApiMethod, useRunOncePerLoop, gridRenderContextSelector, gridVisibleColumnFieldsSelector, gridSortModelSelector } from '@mui/x-data-grid-pro';
6
6
  import { useGridRegisterPipeProcessor } from '@mui/x-data-grid-pro/internals';
7
7
  import { gridAggregationModelSelector } from "./gridAggregationSelectors.js";
8
8
  import { getAggregationRules, mergeStateWithAggregationModel, areAggregationRulesEqual } from "./gridAggregationUtils.js";
9
- import { createAggregationLookup } from "./createAggregationLookup.js";
9
+ import { createAggregationLookup, shouldApplySorting } from "./createAggregationLookup.js";
10
10
  export const aggregationStateInitializer = (state, props, apiRef) => {
11
11
  apiRef.current.caches.aggregation = {
12
12
  rulesOnLastColumnHydration: {},
@@ -36,20 +36,95 @@ export const useGridAggregation = (apiRef, props) => {
36
36
  apiRef.current.setState(mergeStateWithAggregationModel(model));
37
37
  }
38
38
  }, [apiRef]);
39
- const applyAggregation = React.useCallback(() => {
40
- const aggregationLookup = createAggregationLookup({
41
- apiRef,
42
- getAggregationPosition: props.getAggregationPosition,
43
- aggregationFunctions: props.aggregationFunctions,
44
- aggregationRowsScope: props.aggregationRowsScope,
45
- isDataSource: !!props.dataSource
46
- });
47
- apiRef.current.setState(state => _extends({}, state, {
48
- aggregation: _extends({}, state.aggregation, {
49
- lookup: aggregationLookup
50
- })
51
- }));
39
+ const abortControllerRef = React.useRef(null);
40
+ const applyAggregation = React.useCallback(reason => {
41
+ // Abort previous if any
42
+ if (abortControllerRef.current) {
43
+ abortControllerRef.current.abort();
44
+ }
45
+ const abortController = new AbortController();
46
+ abortControllerRef.current = abortController;
47
+ const aggregationRules = getAggregationRules(gridColumnLookupSelector(apiRef), gridAggregationModelSelector(apiRef), props.aggregationFunctions, !!props.dataSource);
48
+ const aggregatedFields = Object.keys(aggregationRules);
49
+ const needsSorting = shouldApplySorting(aggregationRules, aggregatedFields);
50
+ if (reason === 'sort' && !needsSorting) {
51
+ // no need to re-apply aggregation on `sortedRowsSet` if sorting is not needed
52
+ return;
53
+ }
54
+ const renderContext = gridRenderContextSelector(apiRef);
55
+ const visibleColumns = gridVisibleColumnFieldsSelector(apiRef);
56
+ const chunks = [];
57
+ const visibleAggregatedFields = visibleColumns.slice(renderContext.firstColumnIndex, renderContext.lastColumnIndex + 1).filter(field => aggregatedFields.includes(field));
58
+ if (visibleAggregatedFields.length > 0) {
59
+ chunks.push(visibleAggregatedFields);
60
+ }
61
+ const otherAggregatedFields = aggregatedFields.filter(field => !visibleAggregatedFields.includes(field));
62
+ const chunkSize = 20; // columns per chunk
63
+ for (let i = 0; i < otherAggregatedFields.length; i += chunkSize) {
64
+ chunks.push(otherAggregatedFields.slice(i, i + chunkSize));
65
+ }
66
+ let chunkIndex = 0;
67
+ const aggregationLookup = {};
68
+ let chunkStartTime = performance.now();
69
+ const timeLimit = 1000 / 120;
70
+ const processChunk = () => {
71
+ if (abortController.signal.aborted) {
72
+ return;
73
+ }
74
+ const currentChunk = chunks[chunkIndex];
75
+ if (!currentChunk) {
76
+ const sortModel = gridSortModelSelector(apiRef).map(s => s.field);
77
+ const hasAggregatedSorting = sortModel.some(field => aggregationRules[field]);
78
+ if (hasAggregatedSorting) {
79
+ apiRef.current.applySorting();
80
+ }
81
+ abortControllerRef.current = null;
82
+ return;
83
+ }
84
+ const applySorting = shouldApplySorting(aggregationRules, currentChunk);
85
+
86
+ // createAggregationLookup now RETURNS new partial lookup
87
+ const partialLookup = createAggregationLookup({
88
+ apiRef,
89
+ getAggregationPosition: props.getAggregationPosition,
90
+ aggregatedFields: currentChunk,
91
+ aggregationRules,
92
+ aggregationRowsScope: props.aggregationRowsScope,
93
+ isDataSource: !!props.dataSource,
94
+ applySorting
95
+ });
96
+ for (const key of Object.keys(partialLookup)) {
97
+ for (const field of Object.keys(partialLookup[key])) {
98
+ aggregationLookup[key] ??= {};
99
+ aggregationLookup[key][field] = partialLookup[key][field];
100
+ }
101
+ }
102
+ apiRef.current.setState(state => _extends({}, state, {
103
+ aggregation: _extends({}, state.aggregation, {
104
+ lookup: _extends({}, aggregationLookup)
105
+ })
106
+ }));
107
+ chunkIndex += 1;
108
+ if (performance.now() - chunkStartTime < timeLimit) {
109
+ processChunk();
110
+ return;
111
+ }
112
+ setTimeout(() => {
113
+ chunkStartTime = performance.now();
114
+ processChunk();
115
+ }, 0);
116
+ };
117
+ processChunk();
52
118
  }, [apiRef, props.getAggregationPosition, props.aggregationFunctions, props.aggregationRowsScope, props.dataSource]);
119
+ React.useEffect(() => {
120
+ return () => {
121
+ if (abortControllerRef.current) {
122
+ abortControllerRef.current.abort();
123
+ abortControllerRef.current = null;
124
+ }
125
+ };
126
+ }, []);
127
+ const deferredApplyAggregation = useRunOncePerLoop(applyAggregation);
53
128
  const aggregationApi = {
54
129
  setAggregationModel
55
130
  };
@@ -78,17 +153,18 @@ export const useGridAggregation = (apiRef, props) => {
78
153
  // Re-apply the row hydration to add / remove the aggregation footers
79
154
  if (!props.dataSource && !areAggregationRulesEqual(rulesOnLastRowHydration, aggregationRules)) {
80
155
  apiRef.current.requestPipeProcessorsApplication('hydrateRows');
81
- applyAggregation();
156
+ deferredApplyAggregation();
82
157
  }
83
158
 
84
159
  // Re-apply the column hydration to wrap / unwrap the aggregated columns
85
160
  if (!areAggregationRulesEqual(rulesOnLastColumnHydration, aggregationRules)) {
86
161
  apiRef.current.requestPipeProcessorsApplication('hydrateColumns');
87
162
  }
88
- }, [apiRef, applyAggregation, props.aggregationFunctions, props.disableAggregation, props.dataSource]);
163
+ }, [apiRef, deferredApplyAggregation, props.aggregationFunctions, props.disableAggregation, props.dataSource]);
89
164
  useGridEvent(apiRef, 'aggregationModelChange', checkAggregationRulesDiff);
90
165
  useGridEvent(apiRef, 'columnsChange', checkAggregationRulesDiff);
91
- useGridEvent(apiRef, 'filteredRowsSet', applyAggregation);
166
+ useGridEvent(apiRef, 'filteredRowsSet', deferredApplyAggregation);
167
+ useGridEvent(apiRef, 'sortedRowsSet', () => deferredApplyAggregation('sort'));
92
168
 
93
169
  /**
94
170
  * EFFECTS
@@ -1,6 +1,8 @@
1
- import type { GridColDef } from '@mui/x-data-grid-pro';
1
+ import type { GridColDef, GridRowModel } from '@mui/x-data-grid-pro';
2
2
  import type { GridPivotingPrivateApiCommunity, GridPivotingStatePartial } from '@mui/x-data-grid/internals';
3
+ import type { RefObject } from '@mui/x-internals/types';
3
4
  import type { DataGridPremiumProcessedProps } from "../../../models/dataGridPremiumProps.js";
5
+ import type { GridInitialStatePremium } from "../../../models/gridStatePremium.js";
4
6
  export type GridPivotingPropsOverrides = {
5
7
  rows: DataGridPremiumProcessedProps['rows'];
6
8
  columns: DataGridPremiumProcessedProps['columns'];
@@ -66,4 +68,12 @@ export interface GridPivotingPrivateApi extends GridPivotingPrivateApiCommunity
66
68
  targetFieldPosition?: DropPosition;
67
69
  }) => void;
68
70
  }
69
- export type GridPivotingColDefOverrides = Pick<GridColDef, 'width' | 'flex' | 'headerName' | 'description' | 'align' | 'headerAlign' | 'cellClassName' | 'headerClassName' | 'display' | 'maxWidth' | 'minWidth' | 'resizable' | 'sortingOrder'>;
71
+ export type GridPivotingColDefOverrides = Pick<GridColDef, 'width' | 'flex' | 'headerName' | 'description' | 'align' | 'headerAlign' | 'cellClassName' | 'headerClassName' | 'display' | 'maxWidth' | 'minWidth' | 'resizable' | 'sortingOrder'>;
72
+ export interface GridPivotingInternalCache {
73
+ nonPivotDataRef: RefObject<{
74
+ rows: GridRowModel[];
75
+ columns: Map<string, GridColDef>;
76
+ originalRowsProp: readonly GridRowModel[];
77
+ } | undefined>;
78
+ exportedStateRef: RefObject<GridInitialStatePremium | null>;
79
+ }
@@ -4,4 +4,5 @@ import { GridStateInitializer } from '@mui/x-data-grid-pro/internals';
4
4
  import type { DataGridPremiumProcessedProps } from "../../../models/dataGridPremiumProps.js";
5
5
  import { GridPrivateApiPremium } from "../../../models/gridApiPremium.js";
6
6
  export declare const pivotingStateInitializer: GridStateInitializer<Pick<DataGridPremiumProcessedProps, 'pivotActive' | 'pivotModel' | 'pivotPanelOpen' | 'initialState' | 'disablePivoting' | 'getPivotDerivedColumns' | 'columns'>>;
7
- export declare const useGridPivoting: (apiRef: RefObject<GridPrivateApiPremium>, props: Pick<DataGridPremiumProcessedProps, "pivotActive" | "onPivotActiveChange" | "pivotModel" | "onPivotModelChange" | "pivotPanelOpen" | "onPivotPanelOpenChange" | "disablePivoting" | "getPivotDerivedColumns" | "pivotingColDef" | "aggregationFunctions">, originalColumnsProp: readonly GridColDef[], originalRowsProp: readonly GridRowModel[]) => void;
7
+ export declare const useGridPivoting: (apiRef: RefObject<GridPrivateApiPremium>, props: Pick<DataGridPremiumProcessedProps, "pivotActive" | "onPivotActiveChange" | "pivotModel" | "onPivotModelChange" | "pivotPanelOpen" | "onPivotPanelOpenChange" | "disablePivoting" | "getPivotDerivedColumns" | "pivotingColDef" | "groupingColDef" | "aggregationFunctions">, originalColumnsProp: readonly GridColDef[], originalRowsProp: readonly GridRowModel[]) => void;
8
+ export declare const useGridPivotingExportState: (apiRef: RefObject<GridPrivateApiPremium>) => void;
@@ -13,6 +13,14 @@ const emptyPivotModel = {
13
13
  values: []
14
14
  };
15
15
  export const pivotingStateInitializer = (state, props, apiRef) => {
16
+ apiRef.current.caches.pivoting = {
17
+ exportedStateRef: {
18
+ current: null
19
+ },
20
+ nonPivotDataRef: {
21
+ current: undefined
22
+ }
23
+ };
16
24
  if (!isPivotingAvailableFn(props)) {
17
25
  return _extends({}, state, {
18
26
  pivoting: {
@@ -34,8 +42,10 @@ export const pivotingStateInitializer = (state, props, apiRef) => {
34
42
  };
35
43
  export const useGridPivoting = (apiRef, props, originalColumnsProp, originalRowsProp) => {
36
44
  const isPivotActive = useGridSelector(apiRef, gridPivotActiveSelector);
37
- const exportedStateRef = React.useRef(null);
38
- const nonPivotDataRef = React.useRef(undefined);
45
+ const {
46
+ exportedStateRef,
47
+ nonPivotDataRef
48
+ } = apiRef.current.caches.pivoting;
39
49
  const isPivotingAvailable = isPivotingAvailableFn(props);
40
50
  apiRef.current.registerControlState({
41
51
  stateId: 'pivotModel',
@@ -68,9 +78,10 @@ export const useGridPivoting = (apiRef, props, originalColumnsProp, originalRows
68
78
  const initialColumns = getInitialColumns(originalColumnsProp, props.getPivotDerivedColumns, apiRef.current.getLocaleText);
69
79
  return {
70
80
  rows,
71
- columns: initialColumns
81
+ columns: initialColumns,
82
+ originalRowsProp
72
83
  };
73
- }, [apiRef, props.getPivotDerivedColumns, originalColumnsProp]);
84
+ }, [apiRef, props.getPivotDerivedColumns, originalColumnsProp, originalRowsProp, exportedStateRef]);
74
85
  const computePivotingState = React.useCallback(({
75
86
  active,
76
87
  model: pivotModel
@@ -91,51 +102,44 @@ export const useGridPivoting = (apiRef, props, originalColumnsProp, originalRows
91
102
  columns,
92
103
  pivotModel,
93
104
  apiRef: apiRef,
94
- pivotingColDef: props.pivotingColDef
105
+ pivotingColDef: props.pivotingColDef,
106
+ groupingColDef: props.groupingColDef
95
107
  })
96
108
  };
97
109
  }
98
110
  return undefined;
99
- }, [apiRef, props.pivotingColDef]);
111
+ }, [apiRef, props.pivotingColDef, props.groupingColDef, nonPivotDataRef]);
100
112
  useOnMount(() => {
101
113
  if (!isPivotingAvailable || !isPivotActive) {
102
114
  return undefined;
103
115
  }
116
+ nonPivotDataRef.current = getInitialData();
104
117
  const isLoading = gridRowsLoadingSelector(apiRef) ?? false;
105
- const runPivoting = () => {
106
- nonPivotDataRef.current = getInitialData();
107
- apiRef.current.setState(state => {
108
- const pivotingState = _extends({}, state.pivoting, computePivotingState(state.pivoting));
109
- return _extends({}, state, {
110
- pivoting: pivotingState
111
- });
112
- });
113
- };
114
- if (!isLoading) {
115
- runPivoting();
118
+ if (isLoading) {
116
119
  return undefined;
117
120
  }
118
- const unsubscribe = apiRef.current?.store.subscribe(() => {
119
- const loading = gridRowsLoadingSelector(apiRef);
120
- if (loading === false) {
121
- unsubscribe();
122
- runPivoting();
123
- }
121
+ apiRef.current.setState(state => {
122
+ const pivotingState = _extends({}, state.pivoting, computePivotingState(state.pivoting));
123
+ return _extends({}, state, {
124
+ pivoting: pivotingState
125
+ });
124
126
  });
125
- return unsubscribe;
127
+ return undefined;
126
128
  });
127
129
  useEnhancedEffect(() => {
128
130
  if (!isPivotingAvailable || !isPivotActive) {
129
- if (exportedStateRef.current) {
130
- apiRef.current.restoreState(exportedStateRef.current);
131
- exportedStateRef.current = null;
132
- }
133
131
  if (nonPivotDataRef.current) {
132
+ // Prevent rows from being resynced from the original rows prop
133
+ apiRef.current.caches.rows.rowsBeforePartialUpdates = nonPivotDataRef.current.originalRowsProp;
134
134
  apiRef.current.setRows(nonPivotDataRef.current.rows);
135
135
  nonPivotDataRef.current = undefined;
136
136
  }
137
+ if (exportedStateRef.current) {
138
+ apiRef.current.restoreState(exportedStateRef.current);
139
+ exportedStateRef.current = null;
140
+ }
137
141
  }
138
- }, [isPivotActive, apiRef, isPivotingAvailable]);
142
+ }, [isPivotActive, apiRef, isPivotingAvailable, nonPivotDataRef, exportedStateRef]);
139
143
  const setPivotModel = React.useCallback(callback => {
140
144
  if (!isPivotingAvailable) {
141
145
  return;
@@ -221,7 +225,6 @@ export const useGridPivoting = (apiRef, props, originalColumnsProp, originalRows
221
225
  if (!isPivotingAvailable) {
222
226
  return;
223
227
  }
224
- apiRef.current.selectRows([], false, true);
225
228
  apiRef.current.setState(state => {
226
229
  const newPivotMode = typeof callback === 'function' ? callback(state.pivoting?.active) : callback;
227
230
  if (state.pivoting?.active === newPivotMode) {
@@ -240,7 +243,8 @@ export const useGridPivoting = (apiRef, props, originalColumnsProp, originalRows
240
243
  });
241
244
  return newState;
242
245
  });
243
- }, [apiRef, computePivotingState, getInitialData, isPivotingAvailable]);
246
+ apiRef.current.selectRows([], false, true);
247
+ }, [apiRef, computePivotingState, getInitialData, isPivotingAvailable, nonPivotDataRef]);
244
248
  const setPivotPanelOpen = React.useCallback(callback => {
245
249
  if (!isPivotingAvailable) {
246
250
  return;
@@ -276,9 +280,9 @@ export const useGridPivoting = (apiRef, props, originalColumnsProp, originalRows
276
280
  })
277
281
  });
278
282
  });
279
- }, [isPivotingAvailable, apiRef, props.getPivotDerivedColumns, computePivotingState]);
283
+ }, [isPivotingAvailable, apiRef, props.getPivotDerivedColumns, computePivotingState, nonPivotDataRef]);
280
284
  const updateNonPivotRows = React.useCallback((rows, keepPreviousRows = true) => {
281
- if (!nonPivotDataRef.current || !rows || rows.length === 0) {
285
+ if (!nonPivotDataRef.current || !isPivotingAvailable || !rows || rows.length === 0) {
282
286
  return;
283
287
  }
284
288
  if (keepPreviousRows) {
@@ -304,7 +308,7 @@ export const useGridPivoting = (apiRef, props, originalColumnsProp, originalRows
304
308
  pivoting: _extends({}, state.pivoting, computePivotingState(state.pivoting))
305
309
  });
306
310
  });
307
- }, [apiRef, computePivotingState]);
311
+ }, [apiRef, computePivotingState, isPivotingAvailable, nonPivotDataRef]);
308
312
  useGridApiMethod(apiRef, {
309
313
  setPivotModel,
310
314
  setPivotActive,
@@ -320,7 +324,10 @@ export const useGridPivoting = (apiRef, props, originalColumnsProp, originalRows
320
324
  }, [originalColumnsProp, apiRef]);
321
325
  useEnhancedEffect(() => {
322
326
  apiRef.current.updateNonPivotRows(originalRowsProp, false);
323
- }, [originalRowsProp, apiRef]);
327
+ if (nonPivotDataRef.current) {
328
+ nonPivotDataRef.current.originalRowsProp = originalRowsProp;
329
+ }
330
+ }, [originalRowsProp, apiRef, nonPivotDataRef]);
324
331
  useEnhancedEffect(() => {
325
332
  if (props.pivotModel !== undefined) {
326
333
  apiRef.current.setPivotModel(props.pivotModel);
@@ -336,4 +343,19 @@ export const useGridPivoting = (apiRef, props, originalColumnsProp, originalRows
336
343
  apiRef.current.setPivotPanelOpen(props.pivotPanelOpen);
337
344
  }
338
345
  }, [apiRef, props.pivotPanelOpen]);
346
+ };
347
+ export const useGridPivotingExportState = apiRef => {
348
+ const stateExportPreProcessing = React.useCallback(state => {
349
+ const isPivotActive = gridPivotActiveSelector(apiRef);
350
+ if (!isPivotActive) {
351
+ return state;
352
+ }
353
+
354
+ // To-do: implement context.exportOnlyDirtyModels
355
+ const newState = _extends({}, state, apiRef.current.caches.pivoting.exportedStateRef.current, {
356
+ sorting: state.sorting
357
+ });
358
+ return newState;
359
+ }, [apiRef]);
360
+ useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing);
339
361
  };
@@ -11,11 +11,13 @@ export declare const getPivotedData: ({
11
11
  columns,
12
12
  pivotModel,
13
13
  apiRef,
14
- pivotingColDef
14
+ pivotingColDef,
15
+ groupingColDef
15
16
  }: {
16
17
  rows: GridRowModel[];
17
18
  columns: Map<string, GridColDef>;
18
19
  pivotModel: GridPivotModel;
19
20
  apiRef: RefObject<GridApiPremium>;
20
21
  pivotingColDef: DataGridPremiumProcessedProps["pivotingColDef"];
22
+ groupingColDef: DataGridPremiumProcessedProps["groupingColDef"];
21
23
  }) => GridPivotingPropsOverrides;