@mui/x-data-grid-premium 6.19.10 → 6.20.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,82 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## 6.20.0
7
+
8
+ _May 24, 2024_
9
+
10
+ We'd like to offer a big thanks to the 2 contributors who made this release possible. Here are some highlights ✨:
11
+
12
+ - 🐞 Bugfixes
13
+
14
+ ### Data Grid
15
+
16
+ #### `@mui/x-data-grid@6.20.0`
17
+
18
+ - [DataGrid] Escape formulas in CSV and Excel export (#13190) @cherniavskii
19
+
20
+ #### `@mui/x-data-grid-pro@6.20.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
21
+
22
+ Same changes as in `@mui/x-data-grid@6.20.0`.
23
+
24
+ #### `@mui/x-data-grid-premium@6.20.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
25
+
26
+ Same changes as in `@mui/x-data-grid-pro@6.20.0`.
27
+
28
+ ### Date Pickers
29
+
30
+ #### `@mui/x-date-pickers@6.20.0`
31
+
32
+ - [pickers] Fix `disableOpenPicker` prop behavior (#13221) @LukasTy
33
+
34
+ #### `@mui/x-date-pickers-pro@6.20.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
35
+
36
+ Same changes as in `@mui/x-date-pickers@6.20.0`.
37
+
38
+ ## 6.19.12
39
+
40
+ _May 17, 2024_
41
+
42
+ We'd like to offer a big thanks to the 2 contributors who made this release possible. Here are some highlights ✨:
43
+
44
+ - 🐞 Bugfixes
45
+
46
+ ### Date Pickers
47
+
48
+ #### `@mui/x-date-pickers@6.19.12`
49
+
50
+ - [pickers] Fix `AdapterMomentJalaali` regression (#13150) @LukasTy
51
+
52
+ #### `@mui/x-date-pickers-pro@6.19.12` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
53
+
54
+ Same changes as in `@mui/x-date-pickers@6.19.12`.
55
+
56
+ ### Docs
57
+
58
+ - [docs] Use MUI X v6 in Codesandbox and Stackblitz demos (#12838) @cherniavskii
59
+
60
+ ## 6.19.11
61
+
62
+ _Apr 18, 2024_
63
+
64
+ We'd like to offer a big thanks to the 1 contributor who made this release possible. Here are some highlights ✨:
65
+
66
+ - 🐞 Bugfixes
67
+
68
+ ### Data Grid
69
+
70
+ #### `@mui/x-data-grid@6.19.11`
71
+
72
+ - [DataGrid] Fix virtualization memory leak (#12812) @romgrk
73
+
74
+ #### `@mui/x-data-grid-pro@6.19.11` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
75
+
76
+ Same changes as in `@mui/x-data-grid@6.19.11`.
77
+
78
+ #### `@mui/x-data-grid-premium@6.19.11` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
79
+
80
+ Same changes as in `@mui/x-data-grid-pro@6.19.11`.
81
+
6
82
  ## 6.19.10
7
83
 
8
84
  _Apr 12, 2024_
@@ -32,6 +32,7 @@ process.env.NODE_ENV !== "production" ? GridExcelExportMenuItem.propTypes = {
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),
@@ -455,9 +455,12 @@ export const useGridCellSelection = (apiRef, props) => {
455
455
  if (fieldsMap[field]) {
456
456
  const cellParams = apiRef.current.getCellParams(rowId, field);
457
457
  cellData = serializeCellValue(cellParams, {
458
- delimiterCharacter: clipboardCopyCellDelimiter,
459
- ignoreValueFormatter,
460
- shouldAppendQuotes: false
458
+ csvOptions: {
459
+ delimiter: clipboardCopyCellDelimiter,
460
+ shouldAppendQuotes: false,
461
+ escapeFormulas: false
462
+ },
463
+ ignoreValueFormatter
461
464
  });
462
465
  } else {
463
466
  cellData = '';
@@ -1,7 +1,7 @@
1
1
  import type * as Excel from 'exceljs';
2
2
  import { GridRowId, GridColDef } from '@mui/x-data-grid-pro';
3
3
  import { GridStateColDef, GridColumnGroupLookup } from '@mui/x-data-grid/internals';
4
- import { GridExceljsProcessInput, ColumnsStylesInterface, GridExcelExportOptions } from '../gridExcelExportInterface';
4
+ import { ColumnsStylesInterface, GridExcelExportOptions } from '../gridExcelExportInterface';
5
5
  import { GridPrivateApiPremium } from '../../../../models/gridApiPremium';
6
6
  interface SerializedRow {
7
7
  row: Record<string, undefined | number | boolean | string | Date>;
@@ -16,7 +16,7 @@ export declare const serializeRow: (id: GridRowId, columns: GridStateColDef[], a
16
16
  [field: string]: {
17
17
  address: string;
18
18
  };
19
- }) => SerializedRow;
19
+ }, options: Pick<BuildExcelOptions, 'escapeFormulas'>) => SerializedRow;
20
20
  export declare const serializeColumn: (column: GridColDef, columnsStyles: ColumnsStylesInterface) => {
21
21
  key: string;
22
22
  headerText: string;
@@ -42,14 +42,9 @@ type ValueOptionsData = Record<string, {
42
42
  address: string;
43
43
  }>;
44
44
  export declare function getDataForValueOptionsSheet(columns: GridStateColDef[], valueOptionsSheetName: string, api: GridPrivateApiPremium): Promise<ValueOptionsData>;
45
- interface BuildExcelOptions {
45
+ interface BuildExcelOptions extends Pick<GridExcelExportOptions, 'exceljsPreProcess' | 'exceljsPostProcess'>, Pick<Required<GridExcelExportOptions>, 'valueOptionsSheetName' | 'includeHeaders' | 'includeColumnGroupsHeaders' | 'escapeFormulas'> {
46
46
  columns: GridStateColDef[];
47
47
  rowIds: GridRowId[];
48
- includeHeaders: boolean;
49
- includeColumnGroupsHeaders: boolean;
50
- valueOptionsSheetName: string;
51
- exceljsPreProcess?: (processInput: GridExceljsProcessInput) => Promise<void>;
52
- exceljsPostProcess?: (processInput: GridExceljsProcessInput) => Promise<void>;
53
48
  columnsStyles?: ColumnsStylesInterface;
54
49
  }
55
50
  export declare function buildExcel(options: BuildExcelOptions, api: GridPrivateApiPremium): Promise<Excel.Workbook>;
@@ -27,7 +27,7 @@ const getFormattedValueOptions = (colDef, valueOptions, api) => {
27
27
  }
28
28
  return valueOptionsFormatted.map(option => typeof option === 'object' ? option.label : option);
29
29
  };
30
- export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
30
+ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae, options) => {
31
31
  const row = {};
32
32
  const dataValidation = {};
33
33
  const mergedCells = [];
@@ -53,6 +53,7 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
53
53
  });
54
54
  }
55
55
  const cellParams = api.getCellParams(id, column.field);
56
+ let cellValue;
56
57
  switch (cellParams.colDef.type) {
57
58
  case 'singleSelect':
58
59
  {
@@ -96,7 +97,7 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
96
97
  }
97
98
  case 'boolean':
98
99
  case 'number':
99
- row[column.field] = api.getCellParams(id, column.field).value;
100
+ cellValue = api.getCellParams(id, column.field).value;
100
101
  break;
101
102
  case 'date':
102
103
  case 'dateTime':
@@ -116,7 +117,7 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
116
117
  case 'actions':
117
118
  break;
118
119
  default:
119
- row[column.field] = api.getCellParams(id, column.field).formattedValue;
120
+ cellValue = api.getCellParams(id, column.field).formattedValue;
120
121
  if (process.env.NODE_ENV !== 'production') {
121
122
  if (String(cellParams.formattedValue) === '[object Object]') {
122
123
  warnInvalidFormattedValue();
@@ -124,6 +125,15 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
124
125
  }
125
126
  break;
126
127
  }
128
+ if (typeof cellValue === 'string' && options.escapeFormulas) {
129
+ // See https://owasp.org/www-community/attacks/CSV_Injection
130
+ if (['=', '+', '-', '@', '\t', '\r'].includes(cellValue[0])) {
131
+ cellValue = `'${cellValue}`;
132
+ }
133
+ }
134
+ if (typeof cellValue !== 'undefined') {
135
+ row[column.field] = cellValue;
136
+ }
127
137
  });
128
138
  return {
129
139
  row,
@@ -316,7 +326,7 @@ export async function buildExcel(options, api) {
316
326
  const valueOptionsData = await getDataForValueOptionsSheet(columns, valueOptionsSheetName, api);
317
327
  createValueOptionsSheetIfNeeded(valueOptionsData, valueOptionsSheetName, workbook);
318
328
  rowIds.forEach(id => {
319
- const serializedRow = serializeRow(id, columns, api, valueOptionsData);
329
+ const serializedRow = serializeRow(id, columns, api, valueOptionsData, options);
320
330
  addSerializedRowToWorksheet(serializedRow, worksheet);
321
331
  });
322
332
  if (exceljsPostProcess) {
@@ -17,7 +17,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
17
17
  export const useGridExcelExport = (apiRef, props) => {
18
18
  const logger = useGridLogger(apiRef, 'useGridExcelExport');
19
19
  const getDataAsExcel = React.useCallback((options = {}) => {
20
- var _options$getRowsToExp, _options$includeHeade, _options$includeColum;
20
+ var _options$getRowsToExp, _options$includeHeade, _options$includeColum, _options$escapeFormul;
21
21
  logger.debug(`Get data as excel`);
22
22
  const getRowsToExport = (_options$getRowsToExp = options.getRowsToExport) != null ? _options$getRowsToExp : defaultGetRowsToExport;
23
23
  const exportedRowIds = getRowsToExport({
@@ -35,7 +35,8 @@ export const useGridExcelExport = (apiRef, props) => {
35
35
  valueOptionsSheetName: (options == null ? void 0 : options.valueOptionsSheetName) || 'Options',
36
36
  columnsStyles: options == null ? void 0 : options.columnsStyles,
37
37
  exceljsPreProcess: options == null ? void 0 : options.exceljsPreProcess,
38
- exceljsPostProcess: options == null ? void 0 : options.exceljsPostProcess
38
+ exceljsPostProcess: options == null ? void 0 : options.exceljsPostProcess,
39
+ escapeFormulas: (_options$escapeFormul = options.escapeFormulas) != null ? _options$escapeFormul : true
39
40
  }, apiRef.current);
40
41
  }, [logger, apiRef]);
41
42
  const exportDataAsExcel = React.useCallback(async (options = {}) => {
@@ -86,7 +87,12 @@ export const useGridExcelExport = (apiRef, props) => {
86
87
  });
87
88
  const valueOptionsData = await getDataForValueOptionsSheet(exportedColumns, valueOptionsSheetName, apiRef.current);
88
89
  const serializedColumns = serializeColumns(exportedColumns, options.columnsStyles || {});
89
- const serializedRows = exportedRowIds.map(id => serializeRow(id, exportedColumns, apiRef.current, valueOptionsData));
90
+ const serializedRows = exportedRowIds.map(id => {
91
+ var _options$escapeFormul2;
92
+ return serializeRow(id, exportedColumns, apiRef.current, valueOptionsData, {
93
+ escapeFormulas: (_options$escapeFormul2 = options.escapeFormulas) != null ? _options$escapeFormul2 : true
94
+ });
95
+ });
90
96
  const columnGroupPaths = exportedColumns.reduce((acc, column) => {
91
97
  acc[column.field] = apiRef.current.unstable_getColumnGroupPath(column.field);
92
98
  return acc;
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid-premium v6.19.10
2
+ * @mui/x-data-grid-premium v6.20.0
3
3
  *
4
4
  * @license MUI X Commercial
5
5
  * This source code is licensed under the commercial license found in the
@@ -30,6 +30,7 @@ process.env.NODE_ENV !== "production" ? GridExcelExportMenuItem.propTypes = {
30
30
  allColumns: PropTypes.bool,
31
31
  columnsStyles: PropTypes.object,
32
32
  disableToolbarButton: PropTypes.bool,
33
+ escapeFormulas: PropTypes.bool,
33
34
  exceljsPostProcess: PropTypes.func,
34
35
  exceljsPreProcess: PropTypes.func,
35
36
  fields: PropTypes.arrayOf(PropTypes.string),
@@ -441,9 +441,12 @@ export var useGridCellSelection = function useGridCellSelection(apiRef, props) {
441
441
  if (fieldsMap[field]) {
442
442
  var cellParams = apiRef.current.getCellParams(rowId, field);
443
443
  cellData = serializeCellValue(cellParams, {
444
- delimiterCharacter: clipboardCopyCellDelimiter,
445
- ignoreValueFormatter: ignoreValueFormatter,
446
- shouldAppendQuotes: false
444
+ csvOptions: {
445
+ delimiter: clipboardCopyCellDelimiter,
446
+ shouldAppendQuotes: false,
447
+ escapeFormulas: false
448
+ },
449
+ ignoreValueFormatter: ignoreValueFormatter
447
450
  });
448
451
  } else {
449
452
  cellData = '';
@@ -52,7 +52,7 @@ var getFormattedValueOptions = function getFormattedValueOptions(colDef, valueOp
52
52
  return _typeof(option) === 'object' ? option.label : option;
53
53
  });
54
54
  };
55
- export var serializeRow = function serializeRow(id, columns, api, defaultValueOptionsFormulae) {
55
+ export var serializeRow = function serializeRow(id, columns, api, defaultValueOptionsFormulae, options) {
56
56
  var row = {};
57
57
  var dataValidation = {};
58
58
  var mergedCells = [];
@@ -78,6 +78,7 @@ export var serializeRow = function serializeRow(id, columns, api, defaultValueOp
78
78
  });
79
79
  }
80
80
  var cellParams = api.getCellParams(id, column.field);
81
+ var cellValue;
81
82
  switch (cellParams.colDef.type) {
82
83
  case 'singleSelect':
83
84
  {
@@ -123,7 +124,7 @@ export var serializeRow = function serializeRow(id, columns, api, defaultValueOp
123
124
  }
124
125
  case 'boolean':
125
126
  case 'number':
126
- row[column.field] = api.getCellParams(id, column.field).value;
127
+ cellValue = api.getCellParams(id, column.field).value;
127
128
  break;
128
129
  case 'date':
129
130
  case 'dateTime':
@@ -143,7 +144,7 @@ export var serializeRow = function serializeRow(id, columns, api, defaultValueOp
143
144
  case 'actions':
144
145
  break;
145
146
  default:
146
- row[column.field] = api.getCellParams(id, column.field).formattedValue;
147
+ cellValue = api.getCellParams(id, column.field).formattedValue;
147
148
  if (process.env.NODE_ENV !== 'production') {
148
149
  if (String(cellParams.formattedValue) === '[object Object]') {
149
150
  warnInvalidFormattedValue();
@@ -151,6 +152,15 @@ export var serializeRow = function serializeRow(id, columns, api, defaultValueOp
151
152
  }
152
153
  break;
153
154
  }
155
+ if (typeof cellValue === 'string' && options.escapeFormulas) {
156
+ // See https://owasp.org/www-community/attacks/CSV_Injection
157
+ if (['=', '+', '-', '@', '\t', '\r'].includes(cellValue[0])) {
158
+ cellValue = "'".concat(cellValue);
159
+ }
160
+ }
161
+ if (typeof cellValue !== 'undefined') {
162
+ row[column.field] = cellValue;
163
+ }
154
164
  });
155
165
  return {
156
166
  row: row,
@@ -392,7 +402,7 @@ function _buildExcel() {
392
402
  valueOptionsData = _context5.sent;
393
403
  createValueOptionsSheetIfNeeded(valueOptionsData, valueOptionsSheetName, workbook);
394
404
  rowIds.forEach(function (id) {
395
- var serializedRow = serializeRow(id, columns, api, valueOptionsData);
405
+ var serializedRow = serializeRow(id, columns, api, valueOptionsData, options);
396
406
  addSerializedRowToWorksheet(serializedRow, worksheet);
397
407
  });
398
408
  if (!exceljsPostProcess) {
@@ -20,7 +20,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
20
20
  export var useGridExcelExport = function useGridExcelExport(apiRef, props) {
21
21
  var logger = useGridLogger(apiRef, 'useGridExcelExport');
22
22
  var getDataAsExcel = React.useCallback(function () {
23
- var _options$getRowsToExp, _options$includeHeade, _options$includeColum;
23
+ var _options$getRowsToExp, _options$includeHeade, _options$includeColum, _options$escapeFormul;
24
24
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
25
25
  logger.debug("Get data as excel");
26
26
  var getRowsToExport = (_options$getRowsToExp = options.getRowsToExport) != null ? _options$getRowsToExp : defaultGetRowsToExport;
@@ -39,7 +39,8 @@ export var useGridExcelExport = function useGridExcelExport(apiRef, props) {
39
39
  valueOptionsSheetName: (options == null ? void 0 : options.valueOptionsSheetName) || 'Options',
40
40
  columnsStyles: options == null ? void 0 : options.columnsStyles,
41
41
  exceljsPreProcess: options == null ? void 0 : options.exceljsPreProcess,
42
- exceljsPostProcess: options == null ? void 0 : options.exceljsPostProcess
42
+ exceljsPostProcess: options == null ? void 0 : options.exceljsPostProcess,
43
+ escapeFormulas: (_options$escapeFormul = options.escapeFormulas) != null ? _options$escapeFormul : true
43
44
  }, apiRef.current);
44
45
  }, [logger, apiRef]);
45
46
  var exportDataAsExcel = React.useCallback( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
@@ -139,7 +140,10 @@ export var useGridExcelExport = function useGridExcelExport(apiRef, props) {
139
140
  valueOptionsData = _context2.sent;
140
141
  serializedColumns = serializeColumns(exportedColumns, options.columnsStyles || {});
141
142
  serializedRows = exportedRowIds.map(function (id) {
142
- return serializeRow(id, exportedColumns, apiRef.current, valueOptionsData);
143
+ var _options$escapeFormul2;
144
+ return serializeRow(id, exportedColumns, apiRef.current, valueOptionsData, {
145
+ escapeFormulas: (_options$escapeFormul2 = options.escapeFormulas) != null ? _options$escapeFormul2 : true
146
+ });
143
147
  });
144
148
  columnGroupPaths = exportedColumns.reduce(function (acc, column) {
145
149
  acc[column.field] = apiRef.current.unstable_getColumnGroupPath(column.field);
package/legacy/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid-premium v6.19.10
2
+ * @mui/x-data-grid-premium v6.20.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 var getReleaseInfo = function getReleaseInfo() {
3
- var releaseInfo = "MTcxMjg2OTIwMDAwMA==";
3
+ var releaseInfo = "MTcxNjUwMTYwMDAwMA==";
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
@@ -32,6 +32,7 @@ process.env.NODE_ENV !== "production" ? GridExcelExportMenuItem.propTypes = {
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),
@@ -446,9 +446,12 @@ export const useGridCellSelection = (apiRef, props) => {
446
446
  if (fieldsMap[field]) {
447
447
  const cellParams = apiRef.current.getCellParams(rowId, field);
448
448
  cellData = serializeCellValue(cellParams, {
449
- delimiterCharacter: clipboardCopyCellDelimiter,
450
- ignoreValueFormatter,
451
- shouldAppendQuotes: false
449
+ csvOptions: {
450
+ delimiter: clipboardCopyCellDelimiter,
451
+ shouldAppendQuotes: false,
452
+ escapeFormulas: false
453
+ },
454
+ ignoreValueFormatter
452
455
  });
453
456
  } else {
454
457
  cellData = '';
@@ -26,7 +26,7 @@ const getFormattedValueOptions = (colDef, valueOptions, api) => {
26
26
  }
27
27
  return valueOptionsFormatted.map(option => typeof option === 'object' ? option.label : option);
28
28
  };
29
- export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
29
+ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae, options) => {
30
30
  const row = {};
31
31
  const dataValidation = {};
32
32
  const mergedCells = [];
@@ -52,6 +52,7 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
52
52
  });
53
53
  }
54
54
  const cellParams = api.getCellParams(id, column.field);
55
+ let cellValue;
55
56
  switch (cellParams.colDef.type) {
56
57
  case 'singleSelect':
57
58
  {
@@ -95,7 +96,7 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
95
96
  }
96
97
  case 'boolean':
97
98
  case 'number':
98
- row[column.field] = api.getCellParams(id, column.field).value;
99
+ cellValue = api.getCellParams(id, column.field).value;
99
100
  break;
100
101
  case 'date':
101
102
  case 'dateTime':
@@ -115,7 +116,7 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
115
116
  case 'actions':
116
117
  break;
117
118
  default:
118
- row[column.field] = api.getCellParams(id, column.field).formattedValue;
119
+ cellValue = api.getCellParams(id, column.field).formattedValue;
119
120
  if (process.env.NODE_ENV !== 'production') {
120
121
  if (String(cellParams.formattedValue) === '[object Object]') {
121
122
  warnInvalidFormattedValue();
@@ -123,6 +124,15 @@ export const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
123
124
  }
124
125
  break;
125
126
  }
127
+ if (typeof cellValue === 'string' && options.escapeFormulas) {
128
+ // See https://owasp.org/www-community/attacks/CSV_Injection
129
+ if (['=', '+', '-', '@', '\t', '\r'].includes(cellValue[0])) {
130
+ cellValue = `'${cellValue}`;
131
+ }
132
+ }
133
+ if (typeof cellValue !== 'undefined') {
134
+ row[column.field] = cellValue;
135
+ }
126
136
  });
127
137
  return {
128
138
  row,
@@ -304,7 +314,7 @@ export async function buildExcel(options, api) {
304
314
  const valueOptionsData = await getDataForValueOptionsSheet(columns, valueOptionsSheetName, api);
305
315
  createValueOptionsSheetIfNeeded(valueOptionsData, valueOptionsSheetName, workbook);
306
316
  rowIds.forEach(id => {
307
- const serializedRow = serializeRow(id, columns, api, valueOptionsData);
317
+ const serializedRow = serializeRow(id, columns, api, valueOptionsData, options);
308
318
  addSerializedRowToWorksheet(serializedRow, worksheet);
309
319
  });
310
320
  if (exceljsPostProcess) {
@@ -34,7 +34,8 @@ 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
37
+ exceljsPostProcess: options?.exceljsPostProcess,
38
+ escapeFormulas: options.escapeFormulas ?? true
38
39
  }, apiRef.current);
39
40
  }, [logger, apiRef]);
40
41
  const exportDataAsExcel = React.useCallback(async (options = {}) => {
@@ -85,7 +86,9 @@ export const useGridExcelExport = (apiRef, props) => {
85
86
  });
86
87
  const valueOptionsData = await getDataForValueOptionsSheet(exportedColumns, valueOptionsSheetName, apiRef.current);
87
88
  const serializedColumns = serializeColumns(exportedColumns, options.columnsStyles || {});
88
- const serializedRows = exportedRowIds.map(id => serializeRow(id, exportedColumns, apiRef.current, valueOptionsData));
89
+ const serializedRows = exportedRowIds.map(id => serializeRow(id, exportedColumns, apiRef.current, valueOptionsData, {
90
+ escapeFormulas: options.escapeFormulas ?? true
91
+ }));
89
92
  const columnGroupPaths = exportedColumns.reduce((acc, column) => {
90
93
  acc[column.field] = apiRef.current.unstable_getColumnGroupPath(column.field);
91
94
  return acc;
package/modern/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid-premium v6.19.10
2
+ * @mui/x-data-grid-premium v6.20.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 = "MTcxMjg2OTIwMDAwMA==";
3
+ const releaseInfo = "MTcxNjUwMTYwMDAwMA==";
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
@@ -41,6 +41,7 @@ process.env.NODE_ENV !== "production" ? GridExcelExportMenuItem.propTypes = {
41
41
  allColumns: _propTypes.default.bool,
42
42
  columnsStyles: _propTypes.default.object,
43
43
  disableToolbarButton: _propTypes.default.bool,
44
+ escapeFormulas: _propTypes.default.bool,
44
45
  exceljsPostProcess: _propTypes.default.func,
45
46
  exceljsPreProcess: _propTypes.default.func,
46
47
  fields: _propTypes.default.arrayOf(_propTypes.default.string),
@@ -456,9 +456,12 @@ const useGridCellSelection = (apiRef, props) => {
456
456
  if (fieldsMap[field]) {
457
457
  const cellParams = apiRef.current.getCellParams(rowId, field);
458
458
  cellData = (0, _internals.serializeCellValue)(cellParams, {
459
- delimiterCharacter: clipboardCopyCellDelimiter,
460
- ignoreValueFormatter,
461
- shouldAppendQuotes: false
459
+ csvOptions: {
460
+ delimiter: clipboardCopyCellDelimiter,
461
+ shouldAppendQuotes: false,
462
+ escapeFormulas: false
463
+ },
464
+ ignoreValueFormatter
462
465
  });
463
466
  } else {
464
467
  cellData = '';
@@ -40,7 +40,7 @@ const getFormattedValueOptions = (colDef, valueOptions, api) => {
40
40
  }
41
41
  return valueOptionsFormatted.map(option => typeof option === 'object' ? option.label : option);
42
42
  };
43
- const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
43
+ const serializeRow = (id, columns, api, defaultValueOptionsFormulae, options) => {
44
44
  const row = {};
45
45
  const dataValidation = {};
46
46
  const mergedCells = [];
@@ -66,6 +66,7 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
66
66
  });
67
67
  }
68
68
  const cellParams = api.getCellParams(id, column.field);
69
+ let cellValue;
69
70
  switch (cellParams.colDef.type) {
70
71
  case 'singleSelect':
71
72
  {
@@ -109,7 +110,7 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
109
110
  }
110
111
  case 'boolean':
111
112
  case 'number':
112
- row[column.field] = api.getCellParams(id, column.field).value;
113
+ cellValue = api.getCellParams(id, column.field).value;
113
114
  break;
114
115
  case 'date':
115
116
  case 'dateTime':
@@ -129,7 +130,7 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
129
130
  case 'actions':
130
131
  break;
131
132
  default:
132
- row[column.field] = api.getCellParams(id, column.field).formattedValue;
133
+ cellValue = api.getCellParams(id, column.field).formattedValue;
133
134
  if (process.env.NODE_ENV !== 'production') {
134
135
  if (String(cellParams.formattedValue) === '[object Object]') {
135
136
  warnInvalidFormattedValue();
@@ -137,6 +138,15 @@ const serializeRow = (id, columns, api, defaultValueOptionsFormulae) => {
137
138
  }
138
139
  break;
139
140
  }
141
+ if (typeof cellValue === 'string' && options.escapeFormulas) {
142
+ // See https://owasp.org/www-community/attacks/CSV_Injection
143
+ if (['=', '+', '-', '@', '\t', '\r'].includes(cellValue[0])) {
144
+ cellValue = `'${cellValue}`;
145
+ }
146
+ }
147
+ if (typeof cellValue !== 'undefined') {
148
+ row[column.field] = cellValue;
149
+ }
140
150
  });
141
151
  return {
142
152
  row,
@@ -320,7 +330,7 @@ async function buildExcel(options, api) {
320
330
  const valueOptionsData = await getDataForValueOptionsSheet(columns, valueOptionsSheetName, api);
321
331
  createValueOptionsSheetIfNeeded(valueOptionsData, valueOptionsSheetName, workbook);
322
332
  rowIds.forEach(id => {
323
- const serializedRow = serializeRow(id, columns, api, valueOptionsData);
333
+ const serializedRow = serializeRow(id, columns, api, valueOptionsData, options);
324
334
  addSerializedRowToWorksheet(serializedRow, worksheet);
325
335
  });
326
336
  if (exceljsPostProcess) {
@@ -42,7 +42,8 @@ const useGridExcelExport = (apiRef, props) => {
42
42
  valueOptionsSheetName: options?.valueOptionsSheetName || 'Options',
43
43
  columnsStyles: options?.columnsStyles,
44
44
  exceljsPreProcess: options?.exceljsPreProcess,
45
- exceljsPostProcess: options?.exceljsPostProcess
45
+ exceljsPostProcess: options?.exceljsPostProcess,
46
+ escapeFormulas: options.escapeFormulas ?? true
46
47
  }, apiRef.current);
47
48
  }, [logger, apiRef]);
48
49
  const exportDataAsExcel = React.useCallback(async (options = {}) => {
@@ -93,7 +94,9 @@ const useGridExcelExport = (apiRef, props) => {
93
94
  });
94
95
  const valueOptionsData = await (0, _excelSerializer.getDataForValueOptionsSheet)(exportedColumns, valueOptionsSheetName, apiRef.current);
95
96
  const serializedColumns = (0, _excelSerializer.serializeColumns)(exportedColumns, options.columnsStyles || {});
96
- const serializedRows = exportedRowIds.map(id => (0, _excelSerializer.serializeRow)(id, exportedColumns, apiRef.current, valueOptionsData));
97
+ const serializedRows = exportedRowIds.map(id => (0, _excelSerializer.serializeRow)(id, exportedColumns, apiRef.current, valueOptionsData, {
98
+ escapeFormulas: options.escapeFormulas ?? true
99
+ }));
97
100
  const columnGroupPaths = exportedColumns.reduce((acc, column) => {
98
101
  acc[column.field] = apiRef.current.unstable_getColumnGroupPath(column.field);
99
102
  return acc;
package/node/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid-premium v6.19.10
2
+ * @mui/x-data-grid-premium v6.20.0
3
3
  *
4
4
  * @license MUI X Commercial
5
5
  * This source code is licensed under the commercial license found in the
@@ -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 = "MTcxMjg2OTIwMDAwMA==";
9
+ const releaseInfo = "MTcxNjUwMTYwMDAwMA==";
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/x-data-grid-premium",
3
- "version": "6.19.10",
3
+ "version": "6.20.0",
4
4
  "description": "The Premium plan edition of the data grid component (MUI X).",
5
5
  "author": "MUI Team",
6
6
  "main": "./node/index.js",
@@ -33,8 +33,8 @@
33
33
  "dependencies": {
34
34
  "@babel/runtime": "^7.23.2",
35
35
  "@mui/utils": "^5.14.16",
36
- "@mui/x-data-grid": "6.19.10",
37
- "@mui/x-data-grid-pro": "6.19.10",
36
+ "@mui/x-data-grid": "6.20.0",
37
+ "@mui/x-data-grid-pro": "6.20.0",
38
38
  "@mui/x-license-pro": "6.10.2",
39
39
  "@types/format-util": "^1.0.3",
40
40
  "clsx": "^2.0.0",
@@ -1,6 +1,6 @@
1
1
  import { ponyfillGlobal } from '@mui/utils';
2
2
  export const getReleaseInfo = () => {
3
- const releaseInfo = "MTcxMjg2OTIwMDAwMA==";
3
+ const releaseInfo = "MTcxNjUwMTYwMDAwMA==";
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