@mui/x-data-grid-premium 7.5.0 → 7.6.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 (39) hide show
  1. package/CHANGELOG.md +148 -5418
  2. package/DataGridPremium/DataGridPremium.js +2 -2
  3. package/components/GridColumnMenuAggregationItem.js +1 -1
  4. package/components/GridColumnMenuRowGroupItem.d.ts +1 -5
  5. package/components/GridColumnMenuRowGroupItem.js +1 -10
  6. package/components/GridColumnMenuRowUngroupItem.d.ts +1 -5
  7. package/components/GridColumnMenuRowUngroupItem.js +1 -10
  8. package/components/GridExcelExportMenuItem.js +2 -1
  9. package/components/GridPremiumColumnMenu.d.ts +1 -2
  10. package/components/GridPremiumColumnMenu.js +1 -11
  11. package/esm/DataGridPremium/DataGridPremium.js +2 -2
  12. package/esm/components/GridColumnMenuAggregationItem.js +1 -1
  13. package/esm/components/GridColumnMenuRowGroupItem.js +2 -12
  14. package/esm/components/GridColumnMenuRowUngroupItem.js +2 -12
  15. package/esm/components/GridExcelExportMenuItem.js +2 -1
  16. package/esm/components/GridPremiumColumnMenu.js +2 -13
  17. package/esm/hooks/features/cellSelection/useGridCellSelection.js +6 -3
  18. package/esm/hooks/features/export/serializer/excelSerializer.js +43 -23
  19. package/esm/hooks/features/export/useGridExcelExport.js +9 -4
  20. package/esm/utils/releaseInfo.js +1 -1
  21. package/hooks/features/cellSelection/useGridCellSelection.js +6 -3
  22. package/hooks/features/export/serializer/excelSerializer.d.ts +12 -10
  23. package/hooks/features/export/serializer/excelSerializer.js +44 -24
  24. package/hooks/features/export/useGridExcelExport.js +8 -3
  25. package/index.d.ts +1 -0
  26. package/index.js +1 -1
  27. package/modern/DataGridPremium/DataGridPremium.js +2 -2
  28. package/modern/components/GridColumnMenuAggregationItem.js +1 -1
  29. package/modern/components/GridColumnMenuRowGroupItem.js +2 -12
  30. package/modern/components/GridColumnMenuRowUngroupItem.js +2 -12
  31. package/modern/components/GridExcelExportMenuItem.js +2 -1
  32. package/modern/components/GridPremiumColumnMenu.js +2 -13
  33. package/modern/hooks/features/cellSelection/useGridCellSelection.js +6 -3
  34. package/modern/hooks/features/export/serializer/excelSerializer.js +43 -23
  35. package/modern/hooks/features/export/useGridExcelExport.js +9 -4
  36. package/modern/index.js +1 -1
  37. package/modern/utils/releaseInfo.js +1 -1
  38. package/package.json +6 -6
  39. package/utils/releaseInfo.js +1 -1
@@ -8,7 +8,7 @@ exports.buildExcel = buildExcel;
8
8
  exports.getDataForValueOptionsSheet = getDataForValueOptionsSheet;
9
9
  exports.serializeColumn = void 0;
10
10
  exports.serializeColumns = serializeColumns;
11
- exports.serializeRow = void 0;
11
+ exports.serializeRowUnsafe = void 0;
12
12
  exports.setupExcelExportWebWorker = setupExcelExportWebWorker;
13
13
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
14
14
  var _xDataGridPro = require("@mui/x-data-grid-pro");
@@ -37,22 +37,30 @@ const getFormattedValueOptions = (colDef, row, valueOptions, api) => {
37
37
  }
38
38
  return valueOptionsFormatted.map(option => typeof option === 'object' ? option.label : option);
39
39
  };
40
- const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
40
+ /**
41
+ * FIXME: This function mutates the colspan info, but colspan info assumes that the columns
42
+ * passed to it are always consistent. In this case, the exported columns may differ from the
43
+ * actual rendered columns.
44
+ * The caller of this function MUST call `resetColSpan()` before and after usage.
45
+ */
46
+ const serializeRowUnsafe = (id, columns, apiRef, defaultValueOptionsFormulae, options) => {
41
47
  const row = {};
42
48
  const dataValidation = {};
43
49
  const mergedCells = [];
44
- const firstCellParams = api.getCellParams(id, columns[0].field);
50
+ const firstCellParams = apiRef.current.getCellParams(id, columns[0].field);
45
51
  const outlineLevel = firstCellParams.rowNode.depth;
46
-
47
- // `colSpan` is only calculated for rendered rows, so we need to calculate it during export for every row
48
- api.calculateColSpan({
49
- rowId: id,
50
- minFirstColumn: 0,
51
- maxLastColumn: columns.length,
52
- columns
53
- });
52
+ const hasColSpan = (0, _internals.gridHasColSpanSelector)(apiRef);
53
+ if (hasColSpan) {
54
+ // `colSpan` is only calculated for rendered rows, so we need to calculate it during export for every row
55
+ apiRef.current.calculateColSpan({
56
+ rowId: id,
57
+ minFirstColumn: 0,
58
+ maxLastColumn: columns.length,
59
+ columns
60
+ });
61
+ }
54
62
  columns.forEach((column, colIndex) => {
55
- const colSpanInfo = api.unstable_getCellColSpanInfo(id, colIndex);
63
+ const colSpanInfo = hasColSpan ? apiRef.current.unstable_getCellColSpanInfo(id, colIndex) : undefined;
56
64
  if (colSpanInfo && colSpanInfo.spannedByColSpan) {
57
65
  return;
58
66
  }
@@ -62,7 +70,8 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
62
70
  rightIndex: colIndex + colSpanInfo.cellProps.colSpan
63
71
  });
64
72
  }
65
- const cellParams = api.getCellParams(id, column.field);
73
+ const cellParams = apiRef.current.getCellParams(id, column.field);
74
+ let cellValue;
66
75
  switch (cellParams.colDef.type) {
67
76
  case 'singleSelect':
68
77
  {
@@ -75,7 +84,7 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
75
84
  row,
76
85
  field: cellParams.field
77
86
  });
78
- const formattedValueOptions = getFormattedValueOptions(castColumn, row, valueOptions, api);
87
+ const formattedValueOptions = getFormattedValueOptions(castColumn, row, valueOptions, apiRef.current);
79
88
  dataValidation[castColumn.field] = {
80
89
  type: 'list',
81
90
  allowBlank: true,
@@ -91,7 +100,7 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
91
100
  formulae: [address]
92
101
  };
93
102
  }
94
- const formattedValue = api.getCellParams(id, castColumn.field).formattedValue;
103
+ const formattedValue = apiRef.current.getCellParams(id, castColumn.field).formattedValue;
95
104
  if (process.env.NODE_ENV !== 'production') {
96
105
  if (String(cellParams.formattedValue) === '[object Object]') {
97
106
  warnInvalidFormattedValue();
@@ -106,7 +115,7 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
106
115
  }
107
116
  case 'boolean':
108
117
  case 'number':
109
- row[column.field] = api.getCellParams(id, column.field).value;
118
+ cellValue = apiRef.current.getCellParams(id, column.field).value;
110
119
  break;
111
120
  case 'date':
112
121
  case 'dateTime':
@@ -114,7 +123,7 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
114
123
  // Excel does not do any timezone conversion, so we create a date using UTC instead of local timezone
115
124
  // Solution from: https://github.com/exceljs/exceljs/issues/486#issuecomment-432557582
116
125
  // About Date.UTC(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC#exemples
117
- const value = api.getCellParams(id, column.field).value;
126
+ const value = apiRef.current.getCellParams(id, column.field).value;
118
127
  // value may be `undefined` in auto-generated grouping rows
119
128
  if (!value) {
120
129
  break;
@@ -126,7 +135,7 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
126
135
  case 'actions':
127
136
  break;
128
137
  default:
129
- row[column.field] = api.getCellParams(id, column.field).formattedValue;
138
+ cellValue = apiRef.current.getCellParams(id, column.field).formattedValue;
130
139
  if (process.env.NODE_ENV !== 'production') {
131
140
  if (String(cellParams.formattedValue) === '[object Object]') {
132
141
  warnInvalidFormattedValue();
@@ -134,6 +143,15 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
134
143
  }
135
144
  break;
136
145
  }
146
+ if (typeof cellValue === 'string' && options.escapeFormulas) {
147
+ // See https://owasp.org/www-community/attacks/CSV_Injection
148
+ if (['=', '+', '-', '@', '\t', '\r'].includes(cellValue[0])) {
149
+ cellValue = `'${cellValue}`;
150
+ }
151
+ }
152
+ if (typeof cellValue !== 'undefined') {
153
+ row[column.field] = cellValue;
154
+ }
137
155
  });
138
156
  return {
139
157
  row,
@@ -142,7 +160,7 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
142
160
  mergedCells
143
161
  };
144
162
  };
145
- exports.serializeRow = serializeRow;
163
+ exports.serializeRowUnsafe = serializeRowUnsafe;
146
164
  const defaultColumnsStyles = {
147
165
  [_xDataGridPro.GRID_DATE_COL_DEF.type]: {
148
166
  numFmt: 'dd.mm.yyyy'
@@ -282,7 +300,7 @@ async function createValueOptionsSheetIfNeeded(valueOptionsData, sheetName, work
282
300
  valueOptionsWorksheet.getColumn(field).values = values;
283
301
  });
284
302
  }
285
- async function buildExcel(options, api) {
303
+ async function buildExcel(options, apiRef) {
286
304
  const {
287
305
  columns,
288
306
  rowIds,
@@ -306,20 +324,22 @@ async function buildExcel(options, api) {
306
324
  }
307
325
  if (includeColumnGroupsHeaders) {
308
326
  const columnGroupPaths = columns.reduce((acc, column) => {
309
- acc[column.field] = api.getColumnGroupPath(column.field);
327
+ acc[column.field] = apiRef.current.getColumnGroupPath(column.field);
310
328
  return acc;
311
329
  }, {});
312
- addColumnGroupingHeaders(worksheet, serializedColumns, columnGroupPaths, api.getAllGroupDetails());
330
+ addColumnGroupingHeaders(worksheet, serializedColumns, columnGroupPaths, apiRef.current.getAllGroupDetails());
313
331
  }
314
332
  if (includeHeaders) {
315
333
  worksheet.addRow(columns.map(column => column.headerName ?? column.field));
316
334
  }
317
- const valueOptionsData = await getDataForValueOptionsSheet(columns, valueOptionsSheetName, api);
335
+ const valueOptionsData = await getDataForValueOptionsSheet(columns, valueOptionsSheetName, apiRef.current);
318
336
  createValueOptionsSheetIfNeeded(valueOptionsData, valueOptionsSheetName, workbook);
337
+ apiRef.current.resetColSpan();
319
338
  rowIds.forEach(id => {
320
- const serializedRow = serializeRow(id, columns, api, valueOptionsData);
339
+ const serializedRow = serializeRowUnsafe(id, columns, apiRef, valueOptionsData, options);
321
340
  addSerializedRowToWorksheet(serializedRow, worksheet);
322
341
  });
342
+ apiRef.current.resetColSpan();
323
343
  if (exceljsPostProcess) {
324
344
  await exceljsPostProcess({
325
345
  workbook,
@@ -42,8 +42,9 @@ const useGridExcelExport = (apiRef, props) => {
42
42
  valueOptionsSheetName: options?.valueOptionsSheetName || 'Options',
43
43
  columnsStyles: options?.columnsStyles,
44
44
  exceljsPreProcess: options?.exceljsPreProcess,
45
- exceljsPostProcess: options?.exceljsPostProcess
46
- }, apiRef.current);
45
+ exceljsPostProcess: options?.exceljsPostProcess,
46
+ escapeFormulas: options.escapeFormulas ?? true
47
+ }, apiRef);
47
48
  }, [logger, apiRef]);
48
49
  const exportDataAsExcel = React.useCallback(async (options = {}) => {
49
50
  const {
@@ -95,7 +96,11 @@ const useGridExcelExport = (apiRef, props) => {
95
96
  });
96
97
  const valueOptionsData = await (0, _excelSerializer.getDataForValueOptionsSheet)(exportedColumns, valueOptionsSheetName, apiRef.current);
97
98
  const serializedColumns = (0, _excelSerializer.serializeColumns)(exportedColumns, options.columnsStyles || {});
98
- const serializedRows = exportedRowIds.map(id => (0, _excelSerializer.serializeRow)(id, exportedColumns, apiRef.current, valueOptionsData));
99
+ apiRef.current.resetColSpan();
100
+ const serializedRows = exportedRowIds.map(id => (0, _excelSerializer.serializeRowUnsafe)(id, exportedColumns, apiRef, valueOptionsData, {
101
+ escapeFormulas: options.escapeFormulas ?? true
102
+ }));
103
+ apiRef.current.resetColSpan();
99
104
  const columnGroupPaths = exportedColumns.reduce((acc, column) => {
100
105
  acc[column.field] = apiRef.current.getColumnGroupPath(column.field);
101
106
  return acc;
package/index.d.ts CHANGED
@@ -22,6 +22,7 @@ export * from './hooks';
22
22
  export * from './models';
23
23
  export * from './components';
24
24
  export { GridColumnHeaders } from '@mui/x-data-grid-pro';
25
+ export type { GridColumnHeadersProps } from '@mui/x-data-grid-pro';
25
26
  export type { DataGridPremiumProps, GridExperimentalPremiumFeatures, } from './models/dataGridPremiumProps';
26
27
  export { useGridApiContext, useGridApiRef, useGridRootProps } from './typeOverloads/reexports';
27
28
  export type { GridApi, GridInitialState, GridState } from './typeOverloads/reexports';
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid-premium v7.5.0
2
+ * @mui/x-data-grid-premium v7.6.0
3
3
  *
4
4
  * @license MUI X Commercial
5
5
  * This source code is licensed under the commercial license found in the
@@ -51,7 +51,7 @@ export const DataGridPremium = /*#__PURE__*/React.memo(DataGridPremiumRaw);
51
51
  process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
52
52
  // ----------------------------- Warning --------------------------------
53
53
  // | These PropTypes are generated from the TypeScript type definitions |
54
- // | To update them edit the TypeScript types and run "yarn proptypes" |
54
+ // | To update them edit the TypeScript types and run "pnpm proptypes" |
55
55
  // ----------------------------------------------------------------------
56
56
  /**
57
57
  * Aggregation functions available on the grid.
@@ -84,7 +84,7 @@ process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
84
84
  */
85
85
  'aria-labelledby': PropTypes.string,
86
86
  /**
87
- * If `true`, the Data Grid height is dynamic and follow the number of rows in the Data Grid.
87
+ * If `true`, the Data Grid height is dynamic and follows the number of rows in the Data Grid.
88
88
  * @default false
89
89
  */
90
90
  autoHeight: PropTypes.bool,
@@ -100,7 +100,7 @@ function GridColumnMenuAggregationItem(props) {
100
100
  process.env.NODE_ENV !== "production" ? GridColumnMenuAggregationItem.propTypes = {
101
101
  // ----------------------------- Warning --------------------------------
102
102
  // | These PropTypes are generated from the TypeScript type definitions |
103
- // | To update them edit the TypeScript types and run "yarn proptypes" |
103
+ // | To update them edit the TypeScript types and run "pnpm proptypes" |
104
104
  // ----------------------------------------------------------------------
105
105
  colDef: PropTypes.object.isRequired,
106
106
  onClick: PropTypes.func.isRequired
@@ -1,5 +1,4 @@
1
1
  import * as React from 'react';
2
- import PropTypes from 'prop-types';
3
2
  import MenuItem from '@mui/material/MenuItem';
4
3
  import ListItemIcon from '@mui/material/ListItemIcon';
5
4
  import ListItemText from '@mui/material/ListItemText';
@@ -9,7 +8,7 @@ import { gridRowGroupingSanitizedModelSelector } from '../hooks/features/rowGrou
9
8
  import { getRowGroupingCriteriaFromGroupingField, GRID_ROW_GROUPING_SINGLE_GROUPING_FIELD, isGroupingColumn } from '../hooks/features/rowGrouping/gridRowGroupingUtils';
10
9
  import { useGridRootProps } from '../hooks/utils/useGridRootProps';
11
10
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
- function GridColumnMenuRowGroupItem(props) {
11
+ export function GridColumnMenuRowGroupItem(props) {
13
12
  const {
14
13
  colDef,
15
14
  onClick
@@ -46,13 +45,4 @@ function GridColumnMenuRowGroupItem(props) {
46
45
  });
47
46
  }
48
47
  return renderUnGroupingMenuItem(getRowGroupingCriteriaFromGroupingField(colDef.field));
49
- }
50
- process.env.NODE_ENV !== "production" ? GridColumnMenuRowGroupItem.propTypes = {
51
- // ----------------------------- Warning --------------------------------
52
- // | These PropTypes are generated from the TypeScript type definitions |
53
- // | To update them edit the TypeScript types and run "yarn proptypes" |
54
- // ----------------------------------------------------------------------
55
- colDef: PropTypes.object.isRequired,
56
- onClick: PropTypes.func.isRequired
57
- } : void 0;
58
- export { GridColumnMenuRowGroupItem };
48
+ }
@@ -1,5 +1,4 @@
1
1
  import * as React from 'react';
2
- import PropTypes from 'prop-types';
3
2
  import MenuItem from '@mui/material/MenuItem';
4
3
  import ListItemIcon from '@mui/material/ListItemIcon';
5
4
  import ListItemText from '@mui/material/ListItemText';
@@ -8,7 +7,7 @@ import { useGridApiContext } from '../hooks/utils/useGridApiContext';
8
7
  import { gridRowGroupingSanitizedModelSelector } from '../hooks/features/rowGrouping/gridRowGroupingSelector';
9
8
  import { useGridRootProps } from '../hooks/utils/useGridRootProps';
10
9
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
- function GridColumnMenuRowUngroupItem(props) {
10
+ export function GridColumnMenuRowUngroupItem(props) {
12
11
  const {
13
12
  colDef,
14
13
  onClick
@@ -51,13 +50,4 @@ function GridColumnMenuRowUngroupItem(props) {
51
50
  children: apiRef.current.getLocaleText('groupColumn')(name)
52
51
  })]
53
52
  });
54
- }
55
- process.env.NODE_ENV !== "production" ? GridColumnMenuRowUngroupItem.propTypes = {
56
- // ----------------------------- Warning --------------------------------
57
- // | These PropTypes are generated from the TypeScript type definitions |
58
- // | To update them edit the TypeScript types and run "yarn proptypes" |
59
- // ----------------------------------------------------------------------
60
- colDef: PropTypes.object.isRequired,
61
- onClick: PropTypes.func.isRequired
62
- } : void 0;
63
- export { GridColumnMenuRowUngroupItem };
53
+ }
@@ -25,13 +25,14 @@ function GridExcelExportMenuItem(props) {
25
25
  process.env.NODE_ENV !== "production" ? GridExcelExportMenuItem.propTypes = {
26
26
  // ----------------------------- Warning --------------------------------
27
27
  // | These PropTypes are generated from the TypeScript type definitions |
28
- // | To update them edit the TypeScript types and run "yarn proptypes" |
28
+ // | To update them edit the TypeScript types and run "pnpm proptypes" |
29
29
  // ----------------------------------------------------------------------
30
30
  hideMenu: PropTypes.func,
31
31
  options: PropTypes.shape({
32
32
  allColumns: PropTypes.bool,
33
33
  columnsStyles: PropTypes.object,
34
34
  disableToolbarButton: PropTypes.bool,
35
+ escapeFormulas: PropTypes.bool,
35
36
  exceljsPostProcess: PropTypes.func,
36
37
  exceljsPreProcess: PropTypes.func,
37
38
  fields: PropTypes.arrayOf(PropTypes.string),
@@ -1,6 +1,5 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
- import PropTypes from 'prop-types';
4
3
  import { GridGenericColumnMenu, GRID_COLUMN_MENU_SLOTS, GRID_COLUMN_MENU_SLOT_PROPS } from '@mui/x-data-grid-pro';
5
4
  import { GridColumnMenuAggregationItem } from './GridColumnMenuAggregationItem';
6
5
  import { isGroupingColumn } from '../hooks/features/rowGrouping';
@@ -28,21 +27,11 @@ export const GRID_COLUMN_MENU_SLOT_PROPS_PREMIUM = _extends({}, GRID_COLUMN_MENU
28
27
  displayOrder: 27
29
28
  }
30
29
  });
31
- const GridPremiumColumnMenu = /*#__PURE__*/React.forwardRef(function GridPremiumColumnMenuSimple(props, ref) {
30
+ export const GridPremiumColumnMenu = /*#__PURE__*/React.forwardRef(function GridPremiumColumnMenuSimple(props, ref) {
32
31
  return /*#__PURE__*/_jsx(GridGenericColumnMenu, _extends({
33
32
  ref: ref
34
33
  }, props, {
35
34
  defaultSlots: GRID_COLUMN_MENU_SLOTS_PREMIUM,
36
35
  defaultSlotProps: GRID_COLUMN_MENU_SLOT_PROPS_PREMIUM
37
36
  }));
38
- });
39
- process.env.NODE_ENV !== "production" ? GridPremiumColumnMenu.propTypes = {
40
- // ----------------------------- Warning --------------------------------
41
- // | These PropTypes are generated from the TypeScript type definitions |
42
- // | To update them edit the TypeScript types and run "yarn proptypes" |
43
- // ----------------------------------------------------------------------
44
- colDef: PropTypes.object.isRequired,
45
- hideMenu: PropTypes.func.isRequired,
46
- open: PropTypes.bool.isRequired
47
- } : void 0;
48
- export { GridPremiumColumnMenu };
37
+ });
@@ -445,9 +445,12 @@ export const useGridCellSelection = (apiRef, props) => {
445
445
  if (fieldsMap[field]) {
446
446
  const cellParams = apiRef.current.getCellParams(rowId, field);
447
447
  cellData = serializeCellValue(cellParams, {
448
- delimiterCharacter: clipboardCopyCellDelimiter,
449
- ignoreValueFormatter,
450
- shouldAppendQuotes: false
448
+ csvOptions: {
449
+ delimiter: clipboardCopyCellDelimiter,
450
+ shouldAppendQuotes: false,
451
+ escapeFormulas: false
452
+ },
453
+ ignoreValueFormatter
451
454
  });
452
455
  } else {
453
456
  cellData = '';
@@ -1,6 +1,6 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import { GRID_DATE_COL_DEF, GRID_DATETIME_COL_DEF } from '@mui/x-data-grid-pro';
3
- import { buildWarning, isObject, isSingleSelectColDef } from '@mui/x-data-grid/internals';
3
+ import { buildWarning, isObject, isSingleSelectColDef, gridHasColSpanSelector } from '@mui/x-data-grid/internals';
4
4
  const getExcelJs = async () => {
5
5
  const excelJsModule = await import('exceljs');
6
6
  return excelJsModule.default ?? excelJsModule;
@@ -23,22 +23,30 @@ const getFormattedValueOptions = (colDef, row, valueOptions, api) => {
23
23
  }
24
24
  return valueOptionsFormatted.map(option => typeof option === 'object' ? option.label : option);
25
25
  };
26
- export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
26
+ /**
27
+ * FIXME: This function mutates the colspan info, but colspan info assumes that the columns
28
+ * passed to it are always consistent. In this case, the exported columns may differ from the
29
+ * actual rendered columns.
30
+ * The caller of this function MUST call `resetColSpan()` before and after usage.
31
+ */
32
+ export const serializeRowUnsafe = (id, columns, apiRef, defaultValueOptionsFormulae, options) => {
27
33
  const row = {};
28
34
  const dataValidation = {};
29
35
  const mergedCells = [];
30
- const firstCellParams = api.getCellParams(id, columns[0].field);
36
+ const firstCellParams = apiRef.current.getCellParams(id, columns[0].field);
31
37
  const outlineLevel = firstCellParams.rowNode.depth;
32
-
33
- // `colSpan` is only calculated for rendered rows, so we need to calculate it during export for every row
34
- api.calculateColSpan({
35
- rowId: id,
36
- minFirstColumn: 0,
37
- maxLastColumn: columns.length,
38
- columns
39
- });
38
+ const hasColSpan = gridHasColSpanSelector(apiRef);
39
+ if (hasColSpan) {
40
+ // `colSpan` is only calculated for rendered rows, so we need to calculate it during export for every row
41
+ apiRef.current.calculateColSpan({
42
+ rowId: id,
43
+ minFirstColumn: 0,
44
+ maxLastColumn: columns.length,
45
+ columns
46
+ });
47
+ }
40
48
  columns.forEach((column, colIndex) => {
41
- const colSpanInfo = api.unstable_getCellColSpanInfo(id, colIndex);
49
+ const colSpanInfo = hasColSpan ? apiRef.current.unstable_getCellColSpanInfo(id, colIndex) : undefined;
42
50
  if (colSpanInfo && colSpanInfo.spannedByColSpan) {
43
51
  return;
44
52
  }
@@ -48,7 +56,8 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
48
56
  rightIndex: colIndex + colSpanInfo.cellProps.colSpan
49
57
  });
50
58
  }
51
- const cellParams = api.getCellParams(id, column.field);
59
+ const cellParams = apiRef.current.getCellParams(id, column.field);
60
+ let cellValue;
52
61
  switch (cellParams.colDef.type) {
53
62
  case 'singleSelect':
54
63
  {
@@ -61,7 +70,7 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
61
70
  row,
62
71
  field: cellParams.field
63
72
  });
64
- const formattedValueOptions = getFormattedValueOptions(castColumn, row, valueOptions, api);
73
+ const formattedValueOptions = getFormattedValueOptions(castColumn, row, valueOptions, apiRef.current);
65
74
  dataValidation[castColumn.field] = {
66
75
  type: 'list',
67
76
  allowBlank: true,
@@ -77,7 +86,7 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
77
86
  formulae: [address]
78
87
  };
79
88
  }
80
- const formattedValue = api.getCellParams(id, castColumn.field).formattedValue;
89
+ const formattedValue = apiRef.current.getCellParams(id, castColumn.field).formattedValue;
81
90
  if (process.env.NODE_ENV !== 'production') {
82
91
  if (String(cellParams.formattedValue) === '[object Object]') {
83
92
  warnInvalidFormattedValue();
@@ -92,7 +101,7 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
92
101
  }
93
102
  case 'boolean':
94
103
  case 'number':
95
- row[column.field] = api.getCellParams(id, column.field).value;
104
+ cellValue = apiRef.current.getCellParams(id, column.field).value;
96
105
  break;
97
106
  case 'date':
98
107
  case 'dateTime':
@@ -100,7 +109,7 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
100
109
  // Excel does not do any timezone conversion, so we create a date using UTC instead of local timezone
101
110
  // Solution from: https://github.com/exceljs/exceljs/issues/486#issuecomment-432557582
102
111
  // About Date.UTC(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC#exemples
103
- const value = api.getCellParams(id, column.field).value;
112
+ const value = apiRef.current.getCellParams(id, column.field).value;
104
113
  // value may be `undefined` in auto-generated grouping rows
105
114
  if (!value) {
106
115
  break;
@@ -112,7 +121,7 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
112
121
  case 'actions':
113
122
  break;
114
123
  default:
115
- row[column.field] = api.getCellParams(id, column.field).formattedValue;
124
+ cellValue = apiRef.current.getCellParams(id, column.field).formattedValue;
116
125
  if (process.env.NODE_ENV !== 'production') {
117
126
  if (String(cellParams.formattedValue) === '[object Object]') {
118
127
  warnInvalidFormattedValue();
@@ -120,6 +129,15 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
120
129
  }
121
130
  break;
122
131
  }
132
+ if (typeof cellValue === 'string' && options.escapeFormulas) {
133
+ // See https://owasp.org/www-community/attacks/CSV_Injection
134
+ if (['=', '+', '-', '@', '\t', '\r'].includes(cellValue[0])) {
135
+ cellValue = `'${cellValue}`;
136
+ }
137
+ }
138
+ if (typeof cellValue !== 'undefined') {
139
+ row[column.field] = cellValue;
140
+ }
123
141
  });
124
142
  return {
125
143
  row,
@@ -266,7 +284,7 @@ async function createValueOptionsSheetIfNeeded(valueOptionsData, sheetName, work
266
284
  valueOptionsWorksheet.getColumn(field).values = values;
267
285
  });
268
286
  }
269
- export async function buildExcel(options, api) {
287
+ export async function buildExcel(options, apiRef) {
270
288
  const {
271
289
  columns,
272
290
  rowIds,
@@ -290,20 +308,22 @@ export async function buildExcel(options, api) {
290
308
  }
291
309
  if (includeColumnGroupsHeaders) {
292
310
  const columnGroupPaths = columns.reduce((acc, column) => {
293
- acc[column.field] = api.getColumnGroupPath(column.field);
311
+ acc[column.field] = apiRef.current.getColumnGroupPath(column.field);
294
312
  return acc;
295
313
  }, {});
296
- addColumnGroupingHeaders(worksheet, serializedColumns, columnGroupPaths, api.getAllGroupDetails());
314
+ addColumnGroupingHeaders(worksheet, serializedColumns, columnGroupPaths, apiRef.current.getAllGroupDetails());
297
315
  }
298
316
  if (includeHeaders) {
299
317
  worksheet.addRow(columns.map(column => column.headerName ?? column.field));
300
318
  }
301
- const valueOptionsData = await getDataForValueOptionsSheet(columns, valueOptionsSheetName, api);
319
+ const valueOptionsData = await getDataForValueOptionsSheet(columns, valueOptionsSheetName, apiRef.current);
302
320
  createValueOptionsSheetIfNeeded(valueOptionsData, valueOptionsSheetName, workbook);
321
+ apiRef.current.resetColSpan();
303
322
  rowIds.forEach(id => {
304
- const serializedRow = serializeRow(id, columns, api, valueOptionsData);
323
+ const serializedRow = serializeRowUnsafe(id, columns, apiRef, valueOptionsData, options);
305
324
  addSerializedRowToWorksheet(serializedRow, worksheet);
306
325
  });
326
+ apiRef.current.resetColSpan();
307
327
  if (exceljsPostProcess) {
308
328
  await exceljsPostProcess({
309
329
  workbook,
@@ -3,7 +3,7 @@ const _excluded = ["worker", "exceljsPostProcess", "exceljsPreProcess", "columns
3
3
  import * as React from 'react';
4
4
  import { useGridApiMethod, useGridLogger, useGridApiOptionHandler } from '@mui/x-data-grid';
5
5
  import { useGridRegisterPipeProcessor, exportAs, getColumnsToExport, defaultGetRowsToExport } from '@mui/x-data-grid/internals';
6
- import { buildExcel, getDataForValueOptionsSheet, serializeColumns, serializeRow } from './serializer/excelSerializer';
6
+ import { buildExcel, getDataForValueOptionsSheet, serializeColumns, serializeRowUnsafe } from './serializer/excelSerializer';
7
7
  import { GridExcelExportMenuItem } from '../../../components';
8
8
 
9
9
  /**
@@ -34,8 +34,9 @@ export const useGridExcelExport = (apiRef, props) => {
34
34
  valueOptionsSheetName: options?.valueOptionsSheetName || 'Options',
35
35
  columnsStyles: options?.columnsStyles,
36
36
  exceljsPreProcess: options?.exceljsPreProcess,
37
- exceljsPostProcess: options?.exceljsPostProcess
38
- }, apiRef.current);
37
+ exceljsPostProcess: options?.exceljsPostProcess,
38
+ escapeFormulas: options.escapeFormulas ?? true
39
+ }, apiRef);
39
40
  }, [logger, apiRef]);
40
41
  const exportDataAsExcel = React.useCallback(async (options = {}) => {
41
42
  const {
@@ -87,7 +88,11 @@ export const useGridExcelExport = (apiRef, props) => {
87
88
  });
88
89
  const valueOptionsData = await getDataForValueOptionsSheet(exportedColumns, valueOptionsSheetName, apiRef.current);
89
90
  const serializedColumns = serializeColumns(exportedColumns, options.columnsStyles || {});
90
- const serializedRows = exportedRowIds.map(id => serializeRow(id, exportedColumns, apiRef.current, valueOptionsData));
91
+ apiRef.current.resetColSpan();
92
+ const serializedRows = exportedRowIds.map(id => serializeRowUnsafe(id, exportedColumns, apiRef, valueOptionsData, {
93
+ escapeFormulas: options.escapeFormulas ?? true
94
+ }));
95
+ apiRef.current.resetColSpan();
91
96
  const columnGroupPaths = exportedColumns.reduce((acc, column) => {
92
97
  acc[column.field] = apiRef.current.getColumnGroupPath(column.field);
93
98
  return acc;
package/modern/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid-premium v7.5.0
2
+ * @mui/x-data-grid-premium v7.6.0
3
3
  *
4
4
  * @license MUI X Commercial
5
5
  * This source code is licensed under the commercial license found in the
@@ -1,6 +1,6 @@
1
1
  import { ponyfillGlobal } from '@mui/utils';
2
2
  export const getReleaseInfo = () => {
3
- const releaseInfo = "MTcxNTg5NjgwMDAwMA==";
3
+ const releaseInfo = "MTcxNzAxNjQwMDAwMA==";
4
4
  if (process.env.NODE_ENV !== 'production') {
5
5
  // A simple hack to set the value in the test environment (has no build step).
6
6
  // eslint-disable-next-line no-useless-concat
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/x-data-grid-premium",
3
- "version": "7.5.0",
3
+ "version": "7.6.0",
4
4
  "description": "The Premium plan edition of the Data Grid Components (MUI X).",
5
5
  "author": "MUI Team",
6
6
  "main": "./index.js",
@@ -33,17 +33,17 @@
33
33
  "directory": "packages/x-data-grid-premium"
34
34
  },
35
35
  "dependencies": {
36
- "@babel/runtime": "^7.24.5",
37
- "@mui/system": "^5.15.14",
36
+ "@babel/runtime": "^7.24.6",
37
+ "@mui/system": "^5.15.15",
38
38
  "@mui/utils": "^5.15.14",
39
39
  "@types/format-util": "^1.0.4",
40
40
  "clsx": "^2.1.1",
41
41
  "exceljs": "^4.4.0",
42
42
  "prop-types": "^15.8.1",
43
43
  "reselect": "^4.1.8",
44
- "@mui/x-data-grid": "7.5.0",
45
- "@mui/x-data-grid-pro": "7.5.0",
46
- "@mui/x-license": "7.2.0"
44
+ "@mui/x-data-grid-pro": "7.6.0",
45
+ "@mui/x-data-grid": "7.6.0",
46
+ "@mui/x-license": "7.6.0"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@mui/material": "^5.15.14",
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  exports.getReleaseInfo = void 0;
7
7
  var _utils = require("@mui/utils");
8
8
  const getReleaseInfo = () => {
9
- const releaseInfo = "MTcxNTg5NjgwMDAwMA==";
9
+ const releaseInfo = "MTcxNzAxNjQwMDAwMA==";
10
10
  if (process.env.NODE_ENV !== 'production') {
11
11
  // A simple hack to set the value in the test environment (has no build step).
12
12
  // eslint-disable-next-line no-useless-concat