@mui/x-data-grid 6.19.11 → 6.20.1
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 +86 -0
- package/hooks/features/clipboard/useGridClipboard.js +8 -5
- package/hooks/features/export/serializers/csvSerializer.d.ts +3 -6
- package/hooks/features/export/serializers/csvSerializer.js +28 -32
- package/hooks/features/export/useGridCsvExport.js +9 -6
- package/index.js +1 -1
- package/legacy/hooks/features/clipboard/useGridClipboard.js +8 -5
- package/legacy/hooks/features/export/serializers/csvSerializer.js +28 -32
- package/legacy/hooks/features/export/useGridCsvExport.js +9 -6
- package/legacy/index.js +1 -1
- package/models/gridExport.d.ts +7 -0
- package/modern/hooks/features/clipboard/useGridClipboard.js +8 -5
- package/modern/hooks/features/export/serializers/csvSerializer.js +28 -32
- package/modern/hooks/features/export/useGridCsvExport.js +8 -5
- package/modern/index.js +1 -1
- package/node/hooks/features/clipboard/useGridClipboard.js +8 -5
- package/node/hooks/features/export/serializers/csvSerializer.js +28 -32
- package/node/hooks/features/export/useGridCsvExport.js +8 -5
- package/node/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,92 @@
|
|
|
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.1
|
|
7
|
+
|
|
8
|
+
_Jun 6, 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-premium@6.20.1` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
17
|
+
|
|
18
|
+
- [DataGridPremium] Fix clipboard paste not working when cell loses focus (#12737) @cherniavskii
|
|
19
|
+
|
|
20
|
+
### Date Pickers
|
|
21
|
+
|
|
22
|
+
#### `@mui/x-date-pickers@6.20.1`
|
|
23
|
+
|
|
24
|
+
- [pickers] Fix `AdapterDayjs` timezone behavior (#13373) @LukasTy
|
|
25
|
+
|
|
26
|
+
#### `@mui/x-data-grid-pro@6.20.1` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
27
|
+
|
|
28
|
+
Same changes as in `@mui/x-date-pickers-pro@7.20.1`.
|
|
29
|
+
|
|
30
|
+
### Docs
|
|
31
|
+
|
|
32
|
+
- [docs] Fix Pickers FAQ callout (#13243) @LukasTy
|
|
33
|
+
|
|
34
|
+
### Core
|
|
35
|
+
|
|
36
|
+
- [core] Stop publishing v6 under latest tag (#13269) @cherniavskii
|
|
37
|
+
|
|
38
|
+
## 6.20.0
|
|
39
|
+
|
|
40
|
+
_May 24, 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
|
+
### Data Grid
|
|
47
|
+
|
|
48
|
+
#### `@mui/x-data-grid@6.20.0`
|
|
49
|
+
|
|
50
|
+
- [DataGrid] Escape formulas in CSV and Excel export (#13190) @cherniavskii
|
|
51
|
+
|
|
52
|
+
#### `@mui/x-data-grid-pro@6.20.0` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
53
|
+
|
|
54
|
+
Same changes as in `@mui/x-data-grid@6.20.0`.
|
|
55
|
+
|
|
56
|
+
#### `@mui/x-data-grid-premium@6.20.0` [](https://mui.com/r/x-premium-svg-link 'Premium plan')
|
|
57
|
+
|
|
58
|
+
Same changes as in `@mui/x-data-grid-pro@6.20.0`.
|
|
59
|
+
|
|
60
|
+
### Date Pickers
|
|
61
|
+
|
|
62
|
+
#### `@mui/x-date-pickers@6.20.0`
|
|
63
|
+
|
|
64
|
+
- [pickers] Fix `disableOpenPicker` prop behavior (#13221) @LukasTy
|
|
65
|
+
|
|
66
|
+
#### `@mui/x-date-pickers-pro@6.20.0` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
67
|
+
|
|
68
|
+
Same changes as in `@mui/x-date-pickers@6.20.0`.
|
|
69
|
+
|
|
70
|
+
## 6.19.12
|
|
71
|
+
|
|
72
|
+
_May 17, 2024_
|
|
73
|
+
|
|
74
|
+
We'd like to offer a big thanks to the 2 contributors who made this release possible. Here are some highlights ✨:
|
|
75
|
+
|
|
76
|
+
- 🐞 Bugfixes
|
|
77
|
+
|
|
78
|
+
### Date Pickers
|
|
79
|
+
|
|
80
|
+
#### `@mui/x-date-pickers@6.19.12`
|
|
81
|
+
|
|
82
|
+
- [pickers] Fix `AdapterMomentJalaali` regression (#13150) @LukasTy
|
|
83
|
+
|
|
84
|
+
#### `@mui/x-date-pickers-pro@6.19.12` [](https://mui.com/r/x-pro-svg-link 'Pro plan')
|
|
85
|
+
|
|
86
|
+
Same changes as in `@mui/x-date-pickers@6.19.12`.
|
|
87
|
+
|
|
88
|
+
### Docs
|
|
89
|
+
|
|
90
|
+
- [docs] Use MUI X v6 in Codesandbox and Stackblitz demos (#12838) @cherniavskii
|
|
91
|
+
|
|
6
92
|
## 6.19.11
|
|
7
93
|
|
|
8
94
|
_Apr 18, 2024_
|
|
@@ -67,18 +67,21 @@ export const useGridClipboard = (apiRef, props) => {
|
|
|
67
67
|
if (selectedRows.size > 0) {
|
|
68
68
|
textToCopy = apiRef.current.getDataAsCsv({
|
|
69
69
|
includeHeaders: false,
|
|
70
|
-
// TODO: make it configurable
|
|
71
70
|
delimiter: clipboardCopyCellDelimiter,
|
|
72
|
-
shouldAppendQuotes: false
|
|
71
|
+
shouldAppendQuotes: false,
|
|
72
|
+
escapeFormulas: false
|
|
73
73
|
});
|
|
74
74
|
} else {
|
|
75
75
|
const focusedCell = gridFocusCellSelector(apiRef);
|
|
76
76
|
if (focusedCell) {
|
|
77
77
|
const cellParams = apiRef.current.getCellParams(focusedCell.id, focusedCell.field);
|
|
78
78
|
textToCopy = serializeCellValue(cellParams, {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
csvOptions: {
|
|
80
|
+
delimiter: clipboardCopyCellDelimiter,
|
|
81
|
+
shouldAppendQuotes: false,
|
|
82
|
+
escapeFormulas: false
|
|
83
|
+
},
|
|
84
|
+
ignoreValueFormatter
|
|
82
85
|
});
|
|
83
86
|
}
|
|
84
87
|
}
|
|
@@ -4,19 +4,16 @@ import type { GridCellParams } from '../../../../models/params/gridCellParams';
|
|
|
4
4
|
import type { GridStateColDef } from '../../../../models/colDef/gridColDef';
|
|
5
5
|
import type { GridApiCommunity } from '../../../../models/api/gridApiCommunity';
|
|
6
6
|
export declare const serializeCellValue: (cellParams: GridCellParams, options: {
|
|
7
|
-
|
|
7
|
+
csvOptions: CSVOptions;
|
|
8
8
|
ignoreValueFormatter: boolean;
|
|
9
|
-
shouldAppendQuotes: boolean;
|
|
10
9
|
}) => any;
|
|
10
|
+
type CSVOptions = Required<Pick<GridCsvExportOptions, 'delimiter' | 'shouldAppendQuotes' | 'escapeFormulas'>>;
|
|
11
11
|
interface BuildCSVOptions {
|
|
12
12
|
columns: GridStateColDef[];
|
|
13
13
|
rowIds: GridRowId[];
|
|
14
|
-
|
|
15
|
-
includeHeaders: NonNullable<GridCsvExportOptions['includeHeaders']>;
|
|
16
|
-
includeColumnGroupsHeaders: NonNullable<GridCsvExportOptions['includeColumnGroupsHeaders']>;
|
|
14
|
+
csvOptions: Required<Pick<GridCsvExportOptions, 'delimiter' | 'includeColumnGroupsHeaders' | 'includeHeaders' | 'shouldAppendQuotes' | 'escapeFormulas'>>;
|
|
17
15
|
ignoreValueFormatter: boolean;
|
|
18
16
|
apiRef: React.MutableRefObject<GridApiCommunity>;
|
|
19
|
-
shouldAppendQuotes: boolean;
|
|
20
17
|
}
|
|
21
18
|
export declare function buildCSV(options: BuildCSVOptions): string;
|
|
22
19
|
export {};
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../../../../colDef';
|
|
2
2
|
import { buildWarning } from '../../../../utils/warning';
|
|
3
|
-
function sanitizeCellValue(value,
|
|
3
|
+
function sanitizeCellValue(value, csvOptions) {
|
|
4
4
|
if (typeof value === 'string') {
|
|
5
|
-
if (shouldAppendQuotes) {
|
|
5
|
+
if (csvOptions.shouldAppendQuotes || csvOptions.escapeFormulas) {
|
|
6
6
|
const escapedValue = value.replace(/"/g, '""');
|
|
7
|
-
// Make sure value containing delimiter or line break won't be split into multiple
|
|
8
|
-
if ([
|
|
7
|
+
// Make sure value containing delimiter or line break won't be split into multiple cells
|
|
8
|
+
if ([csvOptions.delimiter, '\n', '\r', '"'].some(delimiter => value.includes(delimiter))) {
|
|
9
9
|
return `"${escapedValue}"`;
|
|
10
10
|
}
|
|
11
|
+
if (csvOptions.escapeFormulas) {
|
|
12
|
+
// See https://owasp.org/www-community/attacks/CSV_Injection
|
|
13
|
+
if (['=', '+', '-', '@', '\t', '\r'].includes(escapedValue[0])) {
|
|
14
|
+
return `'${escapedValue}`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
11
17
|
return escapedValue;
|
|
12
18
|
}
|
|
13
19
|
return value;
|
|
@@ -16,9 +22,8 @@ function sanitizeCellValue(value, delimiterCharacter, shouldAppendQuotes) {
|
|
|
16
22
|
}
|
|
17
23
|
export const serializeCellValue = (cellParams, options) => {
|
|
18
24
|
const {
|
|
19
|
-
|
|
20
|
-
ignoreValueFormatter
|
|
21
|
-
shouldAppendQuotes
|
|
25
|
+
csvOptions,
|
|
26
|
+
ignoreValueFormatter
|
|
22
27
|
} = options;
|
|
23
28
|
let value;
|
|
24
29
|
if (ignoreValueFormatter) {
|
|
@@ -37,7 +42,7 @@ export const serializeCellValue = (cellParams, options) => {
|
|
|
37
42
|
} else {
|
|
38
43
|
value = cellParams.formattedValue;
|
|
39
44
|
}
|
|
40
|
-
return sanitizeCellValue(value,
|
|
45
|
+
return sanitizeCellValue(value, csvOptions);
|
|
41
46
|
};
|
|
42
47
|
const objectFormattedValueWarning = buildWarning(['MUI: When the value of a field is an object or a `renderCell` is provided, the CSV export might not display the value correctly.', 'You can provide a `valueFormatter` with a string representation to be used.']);
|
|
43
48
|
class CSVRow {
|
|
@@ -49,12 +54,12 @@ class CSVRow {
|
|
|
49
54
|
}
|
|
50
55
|
addValue(value) {
|
|
51
56
|
if (!this.isEmpty) {
|
|
52
|
-
this.rowString += this.options.
|
|
57
|
+
this.rowString += this.options.csvOptions.delimiter;
|
|
53
58
|
}
|
|
54
59
|
if (value === null || value === undefined) {
|
|
55
60
|
this.rowString += '';
|
|
56
61
|
} else if (typeof this.options.sanitizeCellValue === 'function') {
|
|
57
|
-
this.rowString += this.options.sanitizeCellValue(value, this.options.
|
|
62
|
+
this.rowString += this.options.sanitizeCellValue(value, this.options.csvOptions);
|
|
58
63
|
} else {
|
|
59
64
|
this.rowString += value;
|
|
60
65
|
}
|
|
@@ -68,13 +73,11 @@ const serializeRow = ({
|
|
|
68
73
|
id,
|
|
69
74
|
columns,
|
|
70
75
|
getCellParams,
|
|
71
|
-
|
|
72
|
-
ignoreValueFormatter
|
|
73
|
-
shouldAppendQuotes
|
|
76
|
+
csvOptions,
|
|
77
|
+
ignoreValueFormatter
|
|
74
78
|
}) => {
|
|
75
79
|
const row = new CSVRow({
|
|
76
|
-
|
|
77
|
-
shouldAppendQuotes
|
|
80
|
+
csvOptions
|
|
78
81
|
});
|
|
79
82
|
columns.forEach(column => {
|
|
80
83
|
const cellParams = getCellParams(id, column.field);
|
|
@@ -84,9 +87,8 @@ const serializeRow = ({
|
|
|
84
87
|
}
|
|
85
88
|
}
|
|
86
89
|
row.addValue(serializeCellValue(cellParams, {
|
|
87
|
-
delimiterCharacter,
|
|
88
90
|
ignoreValueFormatter,
|
|
89
|
-
|
|
91
|
+
csvOptions
|
|
90
92
|
}));
|
|
91
93
|
});
|
|
92
94
|
return row.getRowString();
|
|
@@ -95,27 +97,23 @@ export function buildCSV(options) {
|
|
|
95
97
|
const {
|
|
96
98
|
columns,
|
|
97
99
|
rowIds,
|
|
98
|
-
|
|
99
|
-
includeHeaders,
|
|
100
|
-
includeColumnGroupsHeaders,
|
|
100
|
+
csvOptions,
|
|
101
101
|
ignoreValueFormatter,
|
|
102
|
-
apiRef
|
|
103
|
-
shouldAppendQuotes
|
|
102
|
+
apiRef
|
|
104
103
|
} = options;
|
|
105
104
|
const CSVBody = rowIds.reduce((acc, id) => `${acc}${serializeRow({
|
|
106
105
|
id,
|
|
107
106
|
columns,
|
|
108
107
|
getCellParams: apiRef.current.getCellParams,
|
|
109
|
-
delimiterCharacter,
|
|
110
108
|
ignoreValueFormatter,
|
|
111
|
-
|
|
109
|
+
csvOptions
|
|
112
110
|
})}\r\n`, '').trim();
|
|
113
|
-
if (!includeHeaders) {
|
|
111
|
+
if (!csvOptions.includeHeaders) {
|
|
114
112
|
return CSVBody;
|
|
115
113
|
}
|
|
116
114
|
const filteredColumns = columns.filter(column => column.field !== GRID_CHECKBOX_SELECTION_COL_DEF.field);
|
|
117
115
|
const headerRows = [];
|
|
118
|
-
if (includeColumnGroupsHeaders) {
|
|
116
|
+
if (csvOptions.includeColumnGroupsHeaders) {
|
|
119
117
|
const columnGroupLookup = apiRef.current.unstable_getAllGroupDetails();
|
|
120
118
|
let maxColumnGroupsDepth = 0;
|
|
121
119
|
const columnGroupPathsLookup = filteredColumns.reduce((acc, column) => {
|
|
@@ -126,9 +124,8 @@ export function buildCSV(options) {
|
|
|
126
124
|
}, {});
|
|
127
125
|
for (let i = 0; i < maxColumnGroupsDepth; i += 1) {
|
|
128
126
|
const headerGroupRow = new CSVRow({
|
|
129
|
-
|
|
130
|
-
sanitizeCellValue
|
|
131
|
-
shouldAppendQuotes
|
|
127
|
+
csvOptions,
|
|
128
|
+
sanitizeCellValue
|
|
132
129
|
});
|
|
133
130
|
headerRows.push(headerGroupRow);
|
|
134
131
|
filteredColumns.forEach(column => {
|
|
@@ -139,9 +136,8 @@ export function buildCSV(options) {
|
|
|
139
136
|
}
|
|
140
137
|
}
|
|
141
138
|
const mainHeaderRow = new CSVRow({
|
|
142
|
-
|
|
143
|
-
sanitizeCellValue
|
|
144
|
-
shouldAppendQuotes
|
|
139
|
+
csvOptions,
|
|
140
|
+
sanitizeCellValue
|
|
145
141
|
});
|
|
146
142
|
filteredColumns.forEach(column => {
|
|
147
143
|
mainHeaderRow.addValue(column.headerName || column.field);
|
|
@@ -19,7 +19,7 @@ export const useGridCsvExport = (apiRef, props) => {
|
|
|
19
19
|
const ignoreValueFormatterProp = props.unstable_ignoreValueFormatterDuringExport;
|
|
20
20
|
const ignoreValueFormatter = (typeof ignoreValueFormatterProp === 'object' ? ignoreValueFormatterProp == null ? void 0 : ignoreValueFormatterProp.csvExport : ignoreValueFormatterProp) || false;
|
|
21
21
|
const getDataAsCsv = React.useCallback((options = {}) => {
|
|
22
|
-
var _options$getRowsToExp, _options$includeHeade, _options$includeColum, _options$
|
|
22
|
+
var _options$getRowsToExp, _options$shouldAppend, _options$includeHeade, _options$includeColum, _options$escapeFormul;
|
|
23
23
|
logger.debug(`Get data as CSV`);
|
|
24
24
|
const exportedColumns = getColumnsToExport({
|
|
25
25
|
apiRef,
|
|
@@ -32,12 +32,15 @@ export const useGridCsvExport = (apiRef, props) => {
|
|
|
32
32
|
return buildCSV({
|
|
33
33
|
columns: exportedColumns,
|
|
34
34
|
rowIds: exportedRowIds,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
csvOptions: {
|
|
36
|
+
delimiter: options.delimiter || ',',
|
|
37
|
+
shouldAppendQuotes: (_options$shouldAppend = options.shouldAppendQuotes) != null ? _options$shouldAppend : true,
|
|
38
|
+
includeHeaders: (_options$includeHeade = options.includeHeaders) != null ? _options$includeHeade : true,
|
|
39
|
+
includeColumnGroupsHeaders: (_options$includeColum = options.includeColumnGroupsHeaders) != null ? _options$includeColum : true,
|
|
40
|
+
escapeFormulas: (_options$escapeFormul = options.escapeFormulas) != null ? _options$escapeFormul : true
|
|
41
|
+
},
|
|
38
42
|
ignoreValueFormatter,
|
|
39
|
-
apiRef
|
|
40
|
-
shouldAppendQuotes: (_options$shouldAppend = options.shouldAppendQuotes) != null ? _options$shouldAppend : true
|
|
43
|
+
apiRef
|
|
41
44
|
});
|
|
42
45
|
}, [logger, apiRef, ignoreValueFormatter]);
|
|
43
46
|
const exportDataAsCsv = React.useCallback(options => {
|
package/index.js
CHANGED
|
@@ -68,18 +68,21 @@ export var useGridClipboard = function useGridClipboard(apiRef, props) {
|
|
|
68
68
|
if (selectedRows.size > 0) {
|
|
69
69
|
textToCopy = apiRef.current.getDataAsCsv({
|
|
70
70
|
includeHeaders: false,
|
|
71
|
-
// TODO: make it configurable
|
|
72
71
|
delimiter: clipboardCopyCellDelimiter,
|
|
73
|
-
shouldAppendQuotes: false
|
|
72
|
+
shouldAppendQuotes: false,
|
|
73
|
+
escapeFormulas: false
|
|
74
74
|
});
|
|
75
75
|
} else {
|
|
76
76
|
var focusedCell = gridFocusCellSelector(apiRef);
|
|
77
77
|
if (focusedCell) {
|
|
78
78
|
var cellParams = apiRef.current.getCellParams(focusedCell.id, focusedCell.field);
|
|
79
79
|
textToCopy = serializeCellValue(cellParams, {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
csvOptions: {
|
|
81
|
+
delimiter: clipboardCopyCellDelimiter,
|
|
82
|
+
shouldAppendQuotes: false,
|
|
83
|
+
escapeFormulas: false
|
|
84
|
+
},
|
|
85
|
+
ignoreValueFormatter: ignoreValueFormatter
|
|
83
86
|
});
|
|
84
87
|
}
|
|
85
88
|
}
|
|
@@ -2,16 +2,22 @@ import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
|
|
|
2
2
|
import _createClass from "@babel/runtime/helpers/esm/createClass";
|
|
3
3
|
import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../../../../colDef';
|
|
4
4
|
import { buildWarning } from '../../../../utils/warning';
|
|
5
|
-
function sanitizeCellValue(value,
|
|
5
|
+
function sanitizeCellValue(value, csvOptions) {
|
|
6
6
|
if (typeof value === 'string') {
|
|
7
|
-
if (shouldAppendQuotes) {
|
|
7
|
+
if (csvOptions.shouldAppendQuotes || csvOptions.escapeFormulas) {
|
|
8
8
|
var escapedValue = value.replace(/"/g, '""');
|
|
9
|
-
// Make sure value containing delimiter or line break won't be split into multiple
|
|
10
|
-
if ([
|
|
9
|
+
// Make sure value containing delimiter or line break won't be split into multiple cells
|
|
10
|
+
if ([csvOptions.delimiter, '\n', '\r', '"'].some(function (delimiter) {
|
|
11
11
|
return value.includes(delimiter);
|
|
12
12
|
})) {
|
|
13
13
|
return "\"".concat(escapedValue, "\"");
|
|
14
14
|
}
|
|
15
|
+
if (csvOptions.escapeFormulas) {
|
|
16
|
+
// See https://owasp.org/www-community/attacks/CSV_Injection
|
|
17
|
+
if (['=', '+', '-', '@', '\t', '\r'].includes(escapedValue[0])) {
|
|
18
|
+
return "'".concat(escapedValue);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
15
21
|
return escapedValue;
|
|
16
22
|
}
|
|
17
23
|
return value;
|
|
@@ -19,9 +25,8 @@ function sanitizeCellValue(value, delimiterCharacter, shouldAppendQuotes) {
|
|
|
19
25
|
return value;
|
|
20
26
|
}
|
|
21
27
|
export var serializeCellValue = function serializeCellValue(cellParams, options) {
|
|
22
|
-
var
|
|
23
|
-
ignoreValueFormatter = options.ignoreValueFormatter
|
|
24
|
-
shouldAppendQuotes = options.shouldAppendQuotes;
|
|
28
|
+
var csvOptions = options.csvOptions,
|
|
29
|
+
ignoreValueFormatter = options.ignoreValueFormatter;
|
|
25
30
|
var value;
|
|
26
31
|
if (ignoreValueFormatter) {
|
|
27
32
|
var _cellParams$value2;
|
|
@@ -39,7 +44,7 @@ export var serializeCellValue = function serializeCellValue(cellParams, options)
|
|
|
39
44
|
} else {
|
|
40
45
|
value = cellParams.formattedValue;
|
|
41
46
|
}
|
|
42
|
-
return sanitizeCellValue(value,
|
|
47
|
+
return sanitizeCellValue(value, csvOptions);
|
|
43
48
|
};
|
|
44
49
|
var objectFormattedValueWarning = buildWarning(['MUI: When the value of a field is an object or a `renderCell` is provided, the CSV export might not display the value correctly.', 'You can provide a `valueFormatter` with a string representation to be used.']);
|
|
45
50
|
var CSVRow = /*#__PURE__*/function () {
|
|
@@ -54,12 +59,12 @@ var CSVRow = /*#__PURE__*/function () {
|
|
|
54
59
|
key: "addValue",
|
|
55
60
|
value: function addValue(value) {
|
|
56
61
|
if (!this.isEmpty) {
|
|
57
|
-
this.rowString += this.options.
|
|
62
|
+
this.rowString += this.options.csvOptions.delimiter;
|
|
58
63
|
}
|
|
59
64
|
if (value === null || value === undefined) {
|
|
60
65
|
this.rowString += '';
|
|
61
66
|
} else if (typeof this.options.sanitizeCellValue === 'function') {
|
|
62
|
-
this.rowString += this.options.sanitizeCellValue(value, this.options.
|
|
67
|
+
this.rowString += this.options.sanitizeCellValue(value, this.options.csvOptions);
|
|
63
68
|
} else {
|
|
64
69
|
this.rowString += value;
|
|
65
70
|
}
|
|
@@ -77,12 +82,10 @@ var serializeRow = function serializeRow(_ref) {
|
|
|
77
82
|
var id = _ref.id,
|
|
78
83
|
columns = _ref.columns,
|
|
79
84
|
getCellParams = _ref.getCellParams,
|
|
80
|
-
|
|
81
|
-
ignoreValueFormatter = _ref.ignoreValueFormatter
|
|
82
|
-
shouldAppendQuotes = _ref.shouldAppendQuotes;
|
|
85
|
+
csvOptions = _ref.csvOptions,
|
|
86
|
+
ignoreValueFormatter = _ref.ignoreValueFormatter;
|
|
83
87
|
var row = new CSVRow({
|
|
84
|
-
|
|
85
|
-
shouldAppendQuotes: shouldAppendQuotes
|
|
88
|
+
csvOptions: csvOptions
|
|
86
89
|
});
|
|
87
90
|
columns.forEach(function (column) {
|
|
88
91
|
var cellParams = getCellParams(id, column.field);
|
|
@@ -92,9 +95,8 @@ var serializeRow = function serializeRow(_ref) {
|
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
row.addValue(serializeCellValue(cellParams, {
|
|
95
|
-
delimiterCharacter: delimiterCharacter,
|
|
96
98
|
ignoreValueFormatter: ignoreValueFormatter,
|
|
97
|
-
|
|
99
|
+
csvOptions: csvOptions
|
|
98
100
|
}));
|
|
99
101
|
});
|
|
100
102
|
return row.getRowString();
|
|
@@ -102,30 +104,26 @@ var serializeRow = function serializeRow(_ref) {
|
|
|
102
104
|
export function buildCSV(options) {
|
|
103
105
|
var columns = options.columns,
|
|
104
106
|
rowIds = options.rowIds,
|
|
105
|
-
|
|
106
|
-
includeHeaders = options.includeHeaders,
|
|
107
|
-
includeColumnGroupsHeaders = options.includeColumnGroupsHeaders,
|
|
107
|
+
csvOptions = options.csvOptions,
|
|
108
108
|
ignoreValueFormatter = options.ignoreValueFormatter,
|
|
109
|
-
apiRef = options.apiRef
|
|
110
|
-
shouldAppendQuotes = options.shouldAppendQuotes;
|
|
109
|
+
apiRef = options.apiRef;
|
|
111
110
|
var CSVBody = rowIds.reduce(function (acc, id) {
|
|
112
111
|
return "".concat(acc).concat(serializeRow({
|
|
113
112
|
id: id,
|
|
114
113
|
columns: columns,
|
|
115
114
|
getCellParams: apiRef.current.getCellParams,
|
|
116
|
-
delimiterCharacter: delimiterCharacter,
|
|
117
115
|
ignoreValueFormatter: ignoreValueFormatter,
|
|
118
|
-
|
|
116
|
+
csvOptions: csvOptions
|
|
119
117
|
}), "\r\n");
|
|
120
118
|
}, '').trim();
|
|
121
|
-
if (!includeHeaders) {
|
|
119
|
+
if (!csvOptions.includeHeaders) {
|
|
122
120
|
return CSVBody;
|
|
123
121
|
}
|
|
124
122
|
var filteredColumns = columns.filter(function (column) {
|
|
125
123
|
return column.field !== GRID_CHECKBOX_SELECTION_COL_DEF.field;
|
|
126
124
|
});
|
|
127
125
|
var headerRows = [];
|
|
128
|
-
if (includeColumnGroupsHeaders) {
|
|
126
|
+
if (csvOptions.includeColumnGroupsHeaders) {
|
|
129
127
|
var columnGroupLookup = apiRef.current.unstable_getAllGroupDetails();
|
|
130
128
|
var maxColumnGroupsDepth = 0;
|
|
131
129
|
var columnGroupPathsLookup = filteredColumns.reduce(function (acc, column) {
|
|
@@ -136,9 +134,8 @@ export function buildCSV(options) {
|
|
|
136
134
|
}, {});
|
|
137
135
|
var _loop = function _loop(i) {
|
|
138
136
|
var headerGroupRow = new CSVRow({
|
|
139
|
-
|
|
140
|
-
sanitizeCellValue: sanitizeCellValue
|
|
141
|
-
shouldAppendQuotes: shouldAppendQuotes
|
|
137
|
+
csvOptions: csvOptions,
|
|
138
|
+
sanitizeCellValue: sanitizeCellValue
|
|
142
139
|
});
|
|
143
140
|
headerRows.push(headerGroupRow);
|
|
144
141
|
filteredColumns.forEach(function (column) {
|
|
@@ -152,9 +149,8 @@ export function buildCSV(options) {
|
|
|
152
149
|
}
|
|
153
150
|
}
|
|
154
151
|
var mainHeaderRow = new CSVRow({
|
|
155
|
-
|
|
156
|
-
sanitizeCellValue: sanitizeCellValue
|
|
157
|
-
shouldAppendQuotes: shouldAppendQuotes
|
|
152
|
+
csvOptions: csvOptions,
|
|
153
|
+
sanitizeCellValue: sanitizeCellValue
|
|
158
154
|
});
|
|
159
155
|
filteredColumns.forEach(function (column) {
|
|
160
156
|
mainHeaderRow.addValue(column.headerName || column.field);
|
|
@@ -21,7 +21,7 @@ export var useGridCsvExport = function useGridCsvExport(apiRef, props) {
|
|
|
21
21
|
var ignoreValueFormatterProp = props.unstable_ignoreValueFormatterDuringExport;
|
|
22
22
|
var ignoreValueFormatter = (_typeof(ignoreValueFormatterProp) === 'object' ? ignoreValueFormatterProp == null ? void 0 : ignoreValueFormatterProp.csvExport : ignoreValueFormatterProp) || false;
|
|
23
23
|
var getDataAsCsv = React.useCallback(function () {
|
|
24
|
-
var _options$getRowsToExp, _options$includeHeade, _options$includeColum, _options$
|
|
24
|
+
var _options$getRowsToExp, _options$shouldAppend, _options$includeHeade, _options$includeColum, _options$escapeFormul;
|
|
25
25
|
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
26
26
|
logger.debug("Get data as CSV");
|
|
27
27
|
var exportedColumns = getColumnsToExport({
|
|
@@ -35,12 +35,15 @@ export var useGridCsvExport = function useGridCsvExport(apiRef, props) {
|
|
|
35
35
|
return buildCSV({
|
|
36
36
|
columns: exportedColumns,
|
|
37
37
|
rowIds: exportedRowIds,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
csvOptions: {
|
|
39
|
+
delimiter: options.delimiter || ',',
|
|
40
|
+
shouldAppendQuotes: (_options$shouldAppend = options.shouldAppendQuotes) != null ? _options$shouldAppend : true,
|
|
41
|
+
includeHeaders: (_options$includeHeade = options.includeHeaders) != null ? _options$includeHeade : true,
|
|
42
|
+
includeColumnGroupsHeaders: (_options$includeColum = options.includeColumnGroupsHeaders) != null ? _options$includeColum : true,
|
|
43
|
+
escapeFormulas: (_options$escapeFormul = options.escapeFormulas) != null ? _options$escapeFormul : true
|
|
44
|
+
},
|
|
41
45
|
ignoreValueFormatter: ignoreValueFormatter,
|
|
42
|
-
apiRef: apiRef
|
|
43
|
-
shouldAppendQuotes: (_options$shouldAppend = options.shouldAppendQuotes) != null ? _options$shouldAppend : true
|
|
46
|
+
apiRef: apiRef
|
|
44
47
|
});
|
|
45
48
|
}, [logger, apiRef, ignoreValueFormatter]);
|
|
46
49
|
var exportDataAsCsv = React.useCallback(function (options) {
|
package/legacy/index.js
CHANGED
package/models/gridExport.d.ts
CHANGED
|
@@ -37,6 +37,13 @@ export interface GridFileExportOptions<Api extends GridApiCommon = GridApiCommun
|
|
|
37
37
|
* @returns {GridRowId[]} The list of row ids to export.
|
|
38
38
|
*/
|
|
39
39
|
getRowsToExport?: (params: GridGetRowsToExportParams<Api>) => GridRowId[];
|
|
40
|
+
/**
|
|
41
|
+
* If `false`, the formulas in the cells will not be escaped.
|
|
42
|
+
* It is not recommended to disable this option as it exposes the user to potential CSV injection attacks.
|
|
43
|
+
* See https://owasp.org/www-community/attacks/CSV_Injection for more information.
|
|
44
|
+
* @default true
|
|
45
|
+
*/
|
|
46
|
+
escapeFormulas?: boolean;
|
|
40
47
|
}
|
|
41
48
|
export interface GridGetRowsToExportParams<Api extends GridApiCommon = GridApiCommunity> {
|
|
42
49
|
/**
|
|
@@ -66,18 +66,21 @@ export const useGridClipboard = (apiRef, props) => {
|
|
|
66
66
|
if (selectedRows.size > 0) {
|
|
67
67
|
textToCopy = apiRef.current.getDataAsCsv({
|
|
68
68
|
includeHeaders: false,
|
|
69
|
-
// TODO: make it configurable
|
|
70
69
|
delimiter: clipboardCopyCellDelimiter,
|
|
71
|
-
shouldAppendQuotes: false
|
|
70
|
+
shouldAppendQuotes: false,
|
|
71
|
+
escapeFormulas: false
|
|
72
72
|
});
|
|
73
73
|
} else {
|
|
74
74
|
const focusedCell = gridFocusCellSelector(apiRef);
|
|
75
75
|
if (focusedCell) {
|
|
76
76
|
const cellParams = apiRef.current.getCellParams(focusedCell.id, focusedCell.field);
|
|
77
77
|
textToCopy = serializeCellValue(cellParams, {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
csvOptions: {
|
|
79
|
+
delimiter: clipboardCopyCellDelimiter,
|
|
80
|
+
shouldAppendQuotes: false,
|
|
81
|
+
escapeFormulas: false
|
|
82
|
+
},
|
|
83
|
+
ignoreValueFormatter
|
|
81
84
|
});
|
|
82
85
|
}
|
|
83
86
|
}
|
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../../../../colDef';
|
|
2
2
|
import { buildWarning } from '../../../../utils/warning';
|
|
3
|
-
function sanitizeCellValue(value,
|
|
3
|
+
function sanitizeCellValue(value, csvOptions) {
|
|
4
4
|
if (typeof value === 'string') {
|
|
5
|
-
if (shouldAppendQuotes) {
|
|
5
|
+
if (csvOptions.shouldAppendQuotes || csvOptions.escapeFormulas) {
|
|
6
6
|
const escapedValue = value.replace(/"/g, '""');
|
|
7
|
-
// Make sure value containing delimiter or line break won't be split into multiple
|
|
8
|
-
if ([
|
|
7
|
+
// Make sure value containing delimiter or line break won't be split into multiple cells
|
|
8
|
+
if ([csvOptions.delimiter, '\n', '\r', '"'].some(delimiter => value.includes(delimiter))) {
|
|
9
9
|
return `"${escapedValue}"`;
|
|
10
10
|
}
|
|
11
|
+
if (csvOptions.escapeFormulas) {
|
|
12
|
+
// See https://owasp.org/www-community/attacks/CSV_Injection
|
|
13
|
+
if (['=', '+', '-', '@', '\t', '\r'].includes(escapedValue[0])) {
|
|
14
|
+
return `'${escapedValue}`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
11
17
|
return escapedValue;
|
|
12
18
|
}
|
|
13
19
|
return value;
|
|
@@ -16,9 +22,8 @@ function sanitizeCellValue(value, delimiterCharacter, shouldAppendQuotes) {
|
|
|
16
22
|
}
|
|
17
23
|
export const serializeCellValue = (cellParams, options) => {
|
|
18
24
|
const {
|
|
19
|
-
|
|
20
|
-
ignoreValueFormatter
|
|
21
|
-
shouldAppendQuotes
|
|
25
|
+
csvOptions,
|
|
26
|
+
ignoreValueFormatter
|
|
22
27
|
} = options;
|
|
23
28
|
let value;
|
|
24
29
|
if (ignoreValueFormatter) {
|
|
@@ -35,7 +40,7 @@ export const serializeCellValue = (cellParams, options) => {
|
|
|
35
40
|
} else {
|
|
36
41
|
value = cellParams.formattedValue;
|
|
37
42
|
}
|
|
38
|
-
return sanitizeCellValue(value,
|
|
43
|
+
return sanitizeCellValue(value, csvOptions);
|
|
39
44
|
};
|
|
40
45
|
const objectFormattedValueWarning = buildWarning(['MUI: When the value of a field is an object or a `renderCell` is provided, the CSV export might not display the value correctly.', 'You can provide a `valueFormatter` with a string representation to be used.']);
|
|
41
46
|
class CSVRow {
|
|
@@ -47,12 +52,12 @@ class CSVRow {
|
|
|
47
52
|
}
|
|
48
53
|
addValue(value) {
|
|
49
54
|
if (!this.isEmpty) {
|
|
50
|
-
this.rowString += this.options.
|
|
55
|
+
this.rowString += this.options.csvOptions.delimiter;
|
|
51
56
|
}
|
|
52
57
|
if (value === null || value === undefined) {
|
|
53
58
|
this.rowString += '';
|
|
54
59
|
} else if (typeof this.options.sanitizeCellValue === 'function') {
|
|
55
|
-
this.rowString += this.options.sanitizeCellValue(value, this.options.
|
|
60
|
+
this.rowString += this.options.sanitizeCellValue(value, this.options.csvOptions);
|
|
56
61
|
} else {
|
|
57
62
|
this.rowString += value;
|
|
58
63
|
}
|
|
@@ -66,13 +71,11 @@ const serializeRow = ({
|
|
|
66
71
|
id,
|
|
67
72
|
columns,
|
|
68
73
|
getCellParams,
|
|
69
|
-
|
|
70
|
-
ignoreValueFormatter
|
|
71
|
-
shouldAppendQuotes
|
|
74
|
+
csvOptions,
|
|
75
|
+
ignoreValueFormatter
|
|
72
76
|
}) => {
|
|
73
77
|
const row = new CSVRow({
|
|
74
|
-
|
|
75
|
-
shouldAppendQuotes
|
|
78
|
+
csvOptions
|
|
76
79
|
});
|
|
77
80
|
columns.forEach(column => {
|
|
78
81
|
const cellParams = getCellParams(id, column.field);
|
|
@@ -82,9 +85,8 @@ const serializeRow = ({
|
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
row.addValue(serializeCellValue(cellParams, {
|
|
85
|
-
delimiterCharacter,
|
|
86
88
|
ignoreValueFormatter,
|
|
87
|
-
|
|
89
|
+
csvOptions
|
|
88
90
|
}));
|
|
89
91
|
});
|
|
90
92
|
return row.getRowString();
|
|
@@ -93,27 +95,23 @@ export function buildCSV(options) {
|
|
|
93
95
|
const {
|
|
94
96
|
columns,
|
|
95
97
|
rowIds,
|
|
96
|
-
|
|
97
|
-
includeHeaders,
|
|
98
|
-
includeColumnGroupsHeaders,
|
|
98
|
+
csvOptions,
|
|
99
99
|
ignoreValueFormatter,
|
|
100
|
-
apiRef
|
|
101
|
-
shouldAppendQuotes
|
|
100
|
+
apiRef
|
|
102
101
|
} = options;
|
|
103
102
|
const CSVBody = rowIds.reduce((acc, id) => `${acc}${serializeRow({
|
|
104
103
|
id,
|
|
105
104
|
columns,
|
|
106
105
|
getCellParams: apiRef.current.getCellParams,
|
|
107
|
-
delimiterCharacter,
|
|
108
106
|
ignoreValueFormatter,
|
|
109
|
-
|
|
107
|
+
csvOptions
|
|
110
108
|
})}\r\n`, '').trim();
|
|
111
|
-
if (!includeHeaders) {
|
|
109
|
+
if (!csvOptions.includeHeaders) {
|
|
112
110
|
return CSVBody;
|
|
113
111
|
}
|
|
114
112
|
const filteredColumns = columns.filter(column => column.field !== GRID_CHECKBOX_SELECTION_COL_DEF.field);
|
|
115
113
|
const headerRows = [];
|
|
116
|
-
if (includeColumnGroupsHeaders) {
|
|
114
|
+
if (csvOptions.includeColumnGroupsHeaders) {
|
|
117
115
|
const columnGroupLookup = apiRef.current.unstable_getAllGroupDetails();
|
|
118
116
|
let maxColumnGroupsDepth = 0;
|
|
119
117
|
const columnGroupPathsLookup = filteredColumns.reduce((acc, column) => {
|
|
@@ -124,9 +122,8 @@ export function buildCSV(options) {
|
|
|
124
122
|
}, {});
|
|
125
123
|
for (let i = 0; i < maxColumnGroupsDepth; i += 1) {
|
|
126
124
|
const headerGroupRow = new CSVRow({
|
|
127
|
-
|
|
128
|
-
sanitizeCellValue
|
|
129
|
-
shouldAppendQuotes
|
|
125
|
+
csvOptions,
|
|
126
|
+
sanitizeCellValue
|
|
130
127
|
});
|
|
131
128
|
headerRows.push(headerGroupRow);
|
|
132
129
|
filteredColumns.forEach(column => {
|
|
@@ -137,9 +134,8 @@ export function buildCSV(options) {
|
|
|
137
134
|
}
|
|
138
135
|
}
|
|
139
136
|
const mainHeaderRow = new CSVRow({
|
|
140
|
-
|
|
141
|
-
sanitizeCellValue
|
|
142
|
-
shouldAppendQuotes
|
|
137
|
+
csvOptions,
|
|
138
|
+
sanitizeCellValue
|
|
143
139
|
});
|
|
144
140
|
filteredColumns.forEach(column => {
|
|
145
141
|
mainHeaderRow.addValue(column.headerName || column.field);
|
|
@@ -31,12 +31,15 @@ export const useGridCsvExport = (apiRef, props) => {
|
|
|
31
31
|
return buildCSV({
|
|
32
32
|
columns: exportedColumns,
|
|
33
33
|
rowIds: exportedRowIds,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
csvOptions: {
|
|
35
|
+
delimiter: options.delimiter || ',',
|
|
36
|
+
shouldAppendQuotes: options.shouldAppendQuotes ?? true,
|
|
37
|
+
includeHeaders: options.includeHeaders ?? true,
|
|
38
|
+
includeColumnGroupsHeaders: options.includeColumnGroupsHeaders ?? true,
|
|
39
|
+
escapeFormulas: options.escapeFormulas ?? true
|
|
40
|
+
},
|
|
37
41
|
ignoreValueFormatter,
|
|
38
|
-
apiRef
|
|
39
|
-
shouldAppendQuotes: options.shouldAppendQuotes ?? true
|
|
42
|
+
apiRef
|
|
40
43
|
});
|
|
41
44
|
}, [logger, apiRef, ignoreValueFormatter]);
|
|
42
45
|
const exportDataAsCsv = React.useCallback(options => {
|
package/modern/index.js
CHANGED
|
@@ -74,18 +74,21 @@ const useGridClipboard = (apiRef, props) => {
|
|
|
74
74
|
if (selectedRows.size > 0) {
|
|
75
75
|
textToCopy = apiRef.current.getDataAsCsv({
|
|
76
76
|
includeHeaders: false,
|
|
77
|
-
// TODO: make it configurable
|
|
78
77
|
delimiter: clipboardCopyCellDelimiter,
|
|
79
|
-
shouldAppendQuotes: false
|
|
78
|
+
shouldAppendQuotes: false,
|
|
79
|
+
escapeFormulas: false
|
|
80
80
|
});
|
|
81
81
|
} else {
|
|
82
82
|
const focusedCell = (0, _gridFocusStateSelector.gridFocusCellSelector)(apiRef);
|
|
83
83
|
if (focusedCell) {
|
|
84
84
|
const cellParams = apiRef.current.getCellParams(focusedCell.id, focusedCell.field);
|
|
85
85
|
textToCopy = (0, _csvSerializer.serializeCellValue)(cellParams, {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
csvOptions: {
|
|
87
|
+
delimiter: clipboardCopyCellDelimiter,
|
|
88
|
+
shouldAppendQuotes: false,
|
|
89
|
+
escapeFormulas: false
|
|
90
|
+
},
|
|
91
|
+
ignoreValueFormatter
|
|
89
92
|
});
|
|
90
93
|
}
|
|
91
94
|
}
|
|
@@ -7,14 +7,20 @@ exports.buildCSV = buildCSV;
|
|
|
7
7
|
exports.serializeCellValue = void 0;
|
|
8
8
|
var _colDef = require("../../../../colDef");
|
|
9
9
|
var _warning = require("../../../../utils/warning");
|
|
10
|
-
function sanitizeCellValue(value,
|
|
10
|
+
function sanitizeCellValue(value, csvOptions) {
|
|
11
11
|
if (typeof value === 'string') {
|
|
12
|
-
if (shouldAppendQuotes) {
|
|
12
|
+
if (csvOptions.shouldAppendQuotes || csvOptions.escapeFormulas) {
|
|
13
13
|
const escapedValue = value.replace(/"/g, '""');
|
|
14
|
-
// Make sure value containing delimiter or line break won't be split into multiple
|
|
15
|
-
if ([
|
|
14
|
+
// Make sure value containing delimiter or line break won't be split into multiple cells
|
|
15
|
+
if ([csvOptions.delimiter, '\n', '\r', '"'].some(delimiter => value.includes(delimiter))) {
|
|
16
16
|
return `"${escapedValue}"`;
|
|
17
17
|
}
|
|
18
|
+
if (csvOptions.escapeFormulas) {
|
|
19
|
+
// See https://owasp.org/www-community/attacks/CSV_Injection
|
|
20
|
+
if (['=', '+', '-', '@', '\t', '\r'].includes(escapedValue[0])) {
|
|
21
|
+
return `'${escapedValue}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
18
24
|
return escapedValue;
|
|
19
25
|
}
|
|
20
26
|
return value;
|
|
@@ -23,9 +29,8 @@ function sanitizeCellValue(value, delimiterCharacter, shouldAppendQuotes) {
|
|
|
23
29
|
}
|
|
24
30
|
const serializeCellValue = (cellParams, options) => {
|
|
25
31
|
const {
|
|
26
|
-
|
|
27
|
-
ignoreValueFormatter
|
|
28
|
-
shouldAppendQuotes
|
|
32
|
+
csvOptions,
|
|
33
|
+
ignoreValueFormatter
|
|
29
34
|
} = options;
|
|
30
35
|
let value;
|
|
31
36
|
if (ignoreValueFormatter) {
|
|
@@ -42,7 +47,7 @@ const serializeCellValue = (cellParams, options) => {
|
|
|
42
47
|
} else {
|
|
43
48
|
value = cellParams.formattedValue;
|
|
44
49
|
}
|
|
45
|
-
return sanitizeCellValue(value,
|
|
50
|
+
return sanitizeCellValue(value, csvOptions);
|
|
46
51
|
};
|
|
47
52
|
exports.serializeCellValue = serializeCellValue;
|
|
48
53
|
const objectFormattedValueWarning = (0, _warning.buildWarning)(['MUI: When the value of a field is an object or a `renderCell` is provided, the CSV export might not display the value correctly.', 'You can provide a `valueFormatter` with a string representation to be used.']);
|
|
@@ -55,12 +60,12 @@ class CSVRow {
|
|
|
55
60
|
}
|
|
56
61
|
addValue(value) {
|
|
57
62
|
if (!this.isEmpty) {
|
|
58
|
-
this.rowString += this.options.
|
|
63
|
+
this.rowString += this.options.csvOptions.delimiter;
|
|
59
64
|
}
|
|
60
65
|
if (value === null || value === undefined) {
|
|
61
66
|
this.rowString += '';
|
|
62
67
|
} else if (typeof this.options.sanitizeCellValue === 'function') {
|
|
63
|
-
this.rowString += this.options.sanitizeCellValue(value, this.options.
|
|
68
|
+
this.rowString += this.options.sanitizeCellValue(value, this.options.csvOptions);
|
|
64
69
|
} else {
|
|
65
70
|
this.rowString += value;
|
|
66
71
|
}
|
|
@@ -74,13 +79,11 @@ const serializeRow = ({
|
|
|
74
79
|
id,
|
|
75
80
|
columns,
|
|
76
81
|
getCellParams,
|
|
77
|
-
|
|
78
|
-
ignoreValueFormatter
|
|
79
|
-
shouldAppendQuotes
|
|
82
|
+
csvOptions,
|
|
83
|
+
ignoreValueFormatter
|
|
80
84
|
}) => {
|
|
81
85
|
const row = new CSVRow({
|
|
82
|
-
|
|
83
|
-
shouldAppendQuotes
|
|
86
|
+
csvOptions
|
|
84
87
|
});
|
|
85
88
|
columns.forEach(column => {
|
|
86
89
|
const cellParams = getCellParams(id, column.field);
|
|
@@ -90,9 +93,8 @@ const serializeRow = ({
|
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
row.addValue(serializeCellValue(cellParams, {
|
|
93
|
-
delimiterCharacter,
|
|
94
96
|
ignoreValueFormatter,
|
|
95
|
-
|
|
97
|
+
csvOptions
|
|
96
98
|
}));
|
|
97
99
|
});
|
|
98
100
|
return row.getRowString();
|
|
@@ -101,27 +103,23 @@ function buildCSV(options) {
|
|
|
101
103
|
const {
|
|
102
104
|
columns,
|
|
103
105
|
rowIds,
|
|
104
|
-
|
|
105
|
-
includeHeaders,
|
|
106
|
-
includeColumnGroupsHeaders,
|
|
106
|
+
csvOptions,
|
|
107
107
|
ignoreValueFormatter,
|
|
108
|
-
apiRef
|
|
109
|
-
shouldAppendQuotes
|
|
108
|
+
apiRef
|
|
110
109
|
} = options;
|
|
111
110
|
const CSVBody = rowIds.reduce((acc, id) => `${acc}${serializeRow({
|
|
112
111
|
id,
|
|
113
112
|
columns,
|
|
114
113
|
getCellParams: apiRef.current.getCellParams,
|
|
115
|
-
delimiterCharacter,
|
|
116
114
|
ignoreValueFormatter,
|
|
117
|
-
|
|
115
|
+
csvOptions
|
|
118
116
|
})}\r\n`, '').trim();
|
|
119
|
-
if (!includeHeaders) {
|
|
117
|
+
if (!csvOptions.includeHeaders) {
|
|
120
118
|
return CSVBody;
|
|
121
119
|
}
|
|
122
120
|
const filteredColumns = columns.filter(column => column.field !== _colDef.GRID_CHECKBOX_SELECTION_COL_DEF.field);
|
|
123
121
|
const headerRows = [];
|
|
124
|
-
if (includeColumnGroupsHeaders) {
|
|
122
|
+
if (csvOptions.includeColumnGroupsHeaders) {
|
|
125
123
|
const columnGroupLookup = apiRef.current.unstable_getAllGroupDetails();
|
|
126
124
|
let maxColumnGroupsDepth = 0;
|
|
127
125
|
const columnGroupPathsLookup = filteredColumns.reduce((acc, column) => {
|
|
@@ -132,9 +130,8 @@ function buildCSV(options) {
|
|
|
132
130
|
}, {});
|
|
133
131
|
for (let i = 0; i < maxColumnGroupsDepth; i += 1) {
|
|
134
132
|
const headerGroupRow = new CSVRow({
|
|
135
|
-
|
|
136
|
-
sanitizeCellValue
|
|
137
|
-
shouldAppendQuotes
|
|
133
|
+
csvOptions,
|
|
134
|
+
sanitizeCellValue
|
|
138
135
|
});
|
|
139
136
|
headerRows.push(headerGroupRow);
|
|
140
137
|
filteredColumns.forEach(column => {
|
|
@@ -145,9 +142,8 @@ function buildCSV(options) {
|
|
|
145
142
|
}
|
|
146
143
|
}
|
|
147
144
|
const mainHeaderRow = new CSVRow({
|
|
148
|
-
|
|
149
|
-
sanitizeCellValue
|
|
150
|
-
shouldAppendQuotes
|
|
145
|
+
csvOptions,
|
|
146
|
+
sanitizeCellValue
|
|
151
147
|
});
|
|
152
148
|
filteredColumns.forEach(column => {
|
|
153
149
|
mainHeaderRow.addValue(column.headerName || column.field);
|
|
@@ -39,12 +39,15 @@ const useGridCsvExport = (apiRef, props) => {
|
|
|
39
39
|
return (0, _csvSerializer.buildCSV)({
|
|
40
40
|
columns: exportedColumns,
|
|
41
41
|
rowIds: exportedRowIds,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
csvOptions: {
|
|
43
|
+
delimiter: options.delimiter || ',',
|
|
44
|
+
shouldAppendQuotes: options.shouldAppendQuotes ?? true,
|
|
45
|
+
includeHeaders: options.includeHeaders ?? true,
|
|
46
|
+
includeColumnGroupsHeaders: options.includeColumnGroupsHeaders ?? true,
|
|
47
|
+
escapeFormulas: options.escapeFormulas ?? true
|
|
48
|
+
},
|
|
45
49
|
ignoreValueFormatter,
|
|
46
|
-
apiRef
|
|
47
|
-
shouldAppendQuotes: options.shouldAppendQuotes ?? true
|
|
50
|
+
apiRef
|
|
48
51
|
});
|
|
49
52
|
}, [logger, apiRef, ignoreValueFormatter]);
|
|
50
53
|
const exportDataAsCsv = React.useCallback(options => {
|
package/node/index.js
CHANGED