@mui/x-data-grid-premium 6.3.1 → 6.4.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 (34) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/DataGridPremium/DataGridPremium.js +41 -1
  3. package/DataGridPremium/useDataGridPremiumComponent.js +4 -2
  4. package/DataGridPremium/useDataGridPremiumProps.js +3 -1
  5. package/hooks/features/cellSelection/useGridCellSelection.d.ts +1 -1
  6. package/hooks/features/cellSelection/useGridCellSelection.js +29 -1
  7. package/hooks/features/clipboard/useGridClipboardImport.d.ts +4 -0
  8. package/hooks/features/clipboard/useGridClipboardImport.js +315 -0
  9. package/index.js +1 -1
  10. package/legacy/DataGridPremium/DataGridPremium.js +41 -1
  11. package/legacy/DataGridPremium/useDataGridPremiumComponent.js +4 -2
  12. package/legacy/DataGridPremium/useDataGridPremiumProps.js +6 -0
  13. package/legacy/hooks/features/cellSelection/useGridCellSelection.js +30 -1
  14. package/legacy/hooks/features/clipboard/useGridClipboardImport.js +395 -0
  15. package/legacy/index.js +1 -1
  16. package/legacy/utils/releaseInfo.js +1 -1
  17. package/models/dataGridPremiumProps.d.ts +30 -6
  18. package/modern/DataGridPremium/DataGridPremium.js +41 -1
  19. package/modern/DataGridPremium/useDataGridPremiumComponent.js +4 -2
  20. package/modern/DataGridPremium/useDataGridPremiumProps.js +3 -1
  21. package/modern/hooks/features/cellSelection/useGridCellSelection.js +29 -1
  22. package/modern/hooks/features/clipboard/useGridClipboardImport.js +313 -0
  23. package/modern/index.js +1 -1
  24. package/modern/utils/releaseInfo.js +1 -1
  25. package/node/DataGridPremium/DataGridPremium.js +41 -1
  26. package/node/DataGridPremium/useDataGridPremiumComponent.js +4 -2
  27. package/node/DataGridPremium/useDataGridPremiumProps.js +3 -1
  28. package/node/hooks/features/cellSelection/useGridCellSelection.js +28 -0
  29. package/node/hooks/features/clipboard/useGridClipboardImport.js +323 -0
  30. package/node/index.js +1 -1
  31. package/node/utils/releaseInfo.js +1 -1
  32. package/package.json +3 -3
  33. package/typeOverloads/modules.d.ts +26 -2
  34. package/utils/releaseInfo.js +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,63 @@
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
+ ## v6.4.0
7
+
8
+ _May 12, 2023_
9
+
10
+ We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨:
11
+
12
+ - 🎁 Introduce clipboard paste support for `DataGridPremium`:
13
+
14
+ https://github.com/mui/mui-x/assets/13808724/abfcb5c6-9db6-4677-9ba7-ae97de441080
15
+
16
+ See [the documentation](https://mui.com/x/react-data-grid/clipboard/#clipboard-paste) for more information
17
+
18
+ - 🌍 Improve French (fr-FR), German (de-DE), Portuguese (pt-BR) and Ukrainian (uk-UA) locales on the data grid
19
+ - 🌍 Add Slovak (sk-SK) locale on the pickers
20
+ - 🐞 Bugfixes
21
+ - 📚 Documentation improvements
22
+
23
+ ### `@mui/x-data-grid@v6.4.0` / `@mui/x-data-grid-pro@v6.4.0` / `@mui/x-data-grid-premium@v6.4.0`
24
+
25
+ #### Changes
26
+
27
+ - [DataGrid] Fix DataGrid rendering in JSDOM (#8968) @cherniavskii
28
+ - [DataGrid] Fix layout when rendered inside a parent with `display: grid` (#8577) @cherniavskii
29
+ - [DataGrid] Add Joy UI icon slots (#8940) @siriwatknp
30
+ - [DataGrid] Add Joy UI pagination slot (#8871) @cherniavskii
31
+ - [DataGrid] Extract `baseChip` slot (#8748) @cherniavskii
32
+ - [DataGridPremium] Implement Clipboard import (#7389) @cherniavskii
33
+ - [l10n] Improve French (fr-FR) locale (#8825) @vallereaugabriel
34
+ - [l10n] Improve German (de-DE) locale (#8898) @marcauberer
35
+ - [l10n] Improve Portuguese (pt-BR) locale (#8960) @Sorriso337
36
+ - [l10n] Improve Ukrainian (uk-UA) locale (#8863) @Neonin
37
+
38
+ ### `@mui/x-date-pickers@v6.4.0` / `@mui/x-date-pickers-pro@v6.4.0`
39
+
40
+ #### Changes
41
+
42
+ - [pickers] Fix trailing zeros inconsistency in `LuxonAdapter` (#8955) @alexfauquette
43
+ - [pickers] Stop using deprecated adapter methods (#8735) @flaviendelangle
44
+ - [pickers] Strictly type the `adapterLocale` prop of `LocalizationProvider` (#8780) @flaviendelangle
45
+ - [l10n] Add Slovak (sk-SK) locale (#8875) @MatejFacko
46
+
47
+ ### Docs
48
+
49
+ - [docs] Fix date pickers typo in the docs (#8939) @richbustos
50
+ - [docs] Fix master detail demo (#8894) @m4theushw
51
+ - [docs] Fix typo in clipboard docs (#8971) @MBilalShafi
52
+ - [docs] Reduce list of dependencies in Codesandbox/Stackblitz demos (#8535) @cherniavskii
53
+
54
+ ### Core
55
+
56
+ - [core] Improve testing of the adapters (#8789) @flaviendelangle
57
+ - [core] Update license key for tests (#8917) @LukasTy
58
+ - [charts] Make introduction docs pages for each chart (#8869) @alexfauquette
59
+ - [charts] Document Tooltip and Highlighs (#8867) @alexfauquette
60
+ - [test] Cover row grouping regression with a unit test (#8870) @cherniavskii
61
+ - [test] Fix flaky regression tests (#8954) @cherniavskii
62
+
6
63
  ## 6.3.1
7
64
 
8
65
  _May 5, 2023_
@@ -106,6 +106,11 @@ process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
106
106
  * Override or extend the styles applied to the component.
107
107
  */
108
108
  classes: PropTypes.object,
109
+ /**
110
+ * The character used to separate cell values when copying to the clipboard.
111
+ * @default '\t'
112
+ */
113
+ clipboardCopyCellDelimiter: PropTypes.string,
109
114
  /**
110
115
  * Number of extra columns to be rendered before/after the visible slice.
111
116
  * @default 3
@@ -171,6 +176,11 @@ process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
171
176
  * @default false
172
177
  */
173
178
  disableChildrenSorting: PropTypes.bool,
179
+ /**
180
+ * If `true`, the clipboard paste is disabled.
181
+ * @default false
182
+ */
183
+ disableClipboardPaste: PropTypes.bool,
174
184
  /**
175
185
  * If `true`, column filters are disabled.
176
186
  * @default false
@@ -246,6 +256,7 @@ process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
246
256
  * For each feature, if the flag is not explicitly set to `true`, then the feature is fully disabled, and neither property nor method calls will have any effect.
247
257
  */
248
258
  experimentalFeatures: PropTypes.shape({
259
+ clipboardPaste: PropTypes.bool,
249
260
  columnGrouping: PropTypes.bool,
250
261
  lazyLoading: PropTypes.bool,
251
262
  warnIfFocusStateIsNotSynced: PropTypes.bool
@@ -481,6 +492,19 @@ process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
481
492
  * @param {GridCallbackDetails} details Additional details for this callback.
482
493
  */
483
494
  onCellModesModelChange: PropTypes.func,
495
+ /**
496
+ * Callback called when the data is copied to the clipboard.
497
+ * @param {string} data The data copied to the clipboard.
498
+ */
499
+ onClipboardCopy: PropTypes.func,
500
+ /**
501
+ * Callback fired when the clipboard paste operation ends.
502
+ */
503
+ onClipboardPasteEnd: PropTypes.func,
504
+ /**
505
+ * Callback fired when the clipboard paste operation starts.
506
+ */
507
+ onClipboardPasteStart: PropTypes.func,
484
508
  /**
485
509
  * Callback fired when a click event comes from a column header element.
486
510
  * @param {GridColumnHeaderParams} params With all properties from [[GridColumnHeaderParams]].
@@ -885,10 +909,26 @@ process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
885
909
  * Set the cell selection model of the grid.
886
910
  */
887
911
  unstable_cellSelectionModel: PropTypes.object,
912
+ /**
913
+ * If `true`, the grid will not use `valueFormatter` when exporting to CSV or copying to clipboard.
914
+ * If an object is provided, you can choose to ignore the `valueFormatter` for CSV export or clipboard export.
915
+ * @default: false
916
+ */
917
+ unstable_ignoreValueFormatterDuringExport: PropTypes.oneOfType([PropTypes.shape({
918
+ clipboardExport: PropTypes.bool,
919
+ csvExport: PropTypes.bool
920
+ }), PropTypes.bool]),
888
921
  /**
889
922
  * Callback fired when the selection state of one or multiple cells changes.
890
923
  * @param {GridCellSelectionModel} cellSelectionModel Object in the shape of [[GridCellSelectionModel]] containing the selected cells.
891
924
  * @param {GridCallbackDetails} details Additional details for this callback.
892
925
  */
893
- unstable_onCellSelectionModelChange: PropTypes.func
926
+ unstable_onCellSelectionModelChange: PropTypes.func,
927
+ /**
928
+ * The function is used to split the pasted text into rows and cells.
929
+ * @param {string} text The text pasted from the clipboard.
930
+ * @returns {string[][] | null} A 2D array of strings. The first dimension is the rows, the second dimension is the columns.
931
+ * @default `(text) => text.split(/\r\n|\n|\r/).map((row) => row.split('\t'))`
932
+ */
933
+ unstable_splitClipboardPastedText: PropTypes.func
894
934
  } : void 0;
@@ -6,6 +6,7 @@ import { useGridRowGrouping, rowGroupingStateInitializer } from '../hooks/featur
6
6
  import { useGridRowGroupingPreProcessors } from '../hooks/features/rowGrouping/useGridRowGroupingPreProcessors';
7
7
  import { useGridExcelExport } from '../hooks/features/export/useGridExcelExport';
8
8
  import { cellSelectionStateInitializer, useGridCellSelection } from '../hooks/features/cellSelection/useGridCellSelection';
9
+ import { useGridClipboardImport } from '../hooks/features/clipboard/useGridClipboardImport';
9
10
  export const useDataGridPremiumComponent = (inputApiRef, props) => {
10
11
  const privateApiRef = useGridInitialization(inputApiRef, props);
11
12
 
@@ -63,6 +64,7 @@ export const useDataGridPremiumComponent = (inputApiRef, props) => {
63
64
  useGridDetailPanel(privateApiRef, props);
64
65
  useGridColumnSpanning(privateApiRef);
65
66
  useGridColumnGrouping(privateApiRef, props);
67
+ useGridClipboardImport(privateApiRef, props);
66
68
  useGridEditing(privateApiRef, props);
67
69
  useGridFocus(privateApiRef, props);
68
70
  useGridPreferencesPanel(privateApiRef, props);
@@ -78,10 +80,10 @@ export const useDataGridPremiumComponent = (inputApiRef, props) => {
78
80
  useGridInfiniteLoader(privateApiRef, props);
79
81
  useGridLazyLoader(privateApiRef, props);
80
82
  useGridColumnMenu(privateApiRef);
81
- useGridCsvExport(privateApiRef);
83
+ useGridCsvExport(privateApiRef, props);
82
84
  useGridPrintExport(privateApiRef, props);
83
85
  useGridExcelExport(privateApiRef, props);
84
- useGridClipboard(privateApiRef);
86
+ useGridClipboard(privateApiRef, props);
85
87
  useGridDimensions(privateApiRef, props);
86
88
  useGridEvents(privateApiRef, props);
87
89
  useGridStatePersistence(privateApiRef);
@@ -18,7 +18,9 @@ export const DATA_GRID_PREMIUM_PROPS_DEFAULT_VALUES = _extends({}, DATA_GRID_PRO
18
18
  rowGroupingColumnMode: 'single',
19
19
  aggregationFunctions: GRID_AGGREGATION_FUNCTIONS,
20
20
  aggregationRowsScope: 'filtered',
21
- getAggregationPosition: groupNode => groupNode.depth === -1 ? 'footer' : 'inline'
21
+ getAggregationPosition: groupNode => groupNode.depth === -1 ? 'footer' : 'inline',
22
+ disableClipboardPaste: false,
23
+ unstable_splitClipboardPastedText: text => text.split(/\r\n|\n|\r/).map(row => row.split('\t'))
22
24
  });
23
25
  const defaultSlots = uncapitalizeObjectKeys(DATA_GRID_PREMIUM_DEFAULT_SLOTS_COMPONENTS);
24
26
  export const useDataGridPremiumProps = inProps => {
@@ -3,4 +3,4 @@ import { GridStateInitializer } from '@mui/x-data-grid-pro/internals';
3
3
  import { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps';
4
4
  import { GridPrivateApiPremium } from '../../../models/gridApiPremium';
5
5
  export declare const cellSelectionStateInitializer: GridStateInitializer<Pick<DataGridPremiumProcessedProps, 'unstable_cellSelectionModel' | 'initialState'>>;
6
- export declare const useGridCellSelection: (apiRef: React.MutableRefObject<GridPrivateApiPremium>, props: Pick<DataGridPremiumProcessedProps, 'unstable_cellSelection' | 'unstable_cellSelectionModel' | 'unstable_onCellSelectionModelChange' | 'pagination' | 'paginationMode'>) => void;
6
+ export declare const useGridCellSelection: (apiRef: React.MutableRefObject<GridPrivateApiPremium>, props: Pick<DataGridPremiumProcessedProps, 'unstable_cellSelection' | 'unstable_cellSelectionModel' | 'unstable_onCellSelectionModelChange' | 'pagination' | 'paginationMode' | 'unstable_ignoreValueFormatterDuringExport' | 'clipboardCopyCellDelimiter'>) => void;
@@ -1,7 +1,7 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import * as React from 'react';
3
3
  import { useEventCallback } from '@mui/material/utils';
4
- import { isNavigationKey, useGridRegisterPipeProcessor, useGridVisibleRows } from '@mui/x-data-grid-pro/internals';
4
+ import { isNavigationKey, serializeCellValue, useGridRegisterPipeProcessor, useGridVisibleRows } from '@mui/x-data-grid-pro/internals';
5
5
  import { useGridApiEventHandler, useGridApiMethod, GRID_ACTIONS_COLUMN_TYPE, GRID_CHECKBOX_SELECTION_COL_DEF, GRID_DETAIL_PANEL_TOGGLE_FIELD, gridRowsDataRowIdToIdLookupSelector, gridClasses, gridFocusCellSelector } from '@mui/x-data-grid-pro';
6
6
  import { gridCellSelectionStateSelector } from './gridCellSelectionSelector';
7
7
  export const cellSelectionStateInitializer = (state, props) => {
@@ -17,6 +17,9 @@ export const useGridCellSelection = (apiRef, props) => {
17
17
  const visibleRows = useGridVisibleRows(apiRef, props);
18
18
  const cellWithVirtualFocus = React.useRef();
19
19
  const lastMouseDownCell = React.useRef();
20
+ const ignoreValueFormatterProp = props.unstable_ignoreValueFormatterDuringExport;
21
+ const ignoreValueFormatter = (typeof ignoreValueFormatterProp === 'object' ? ignoreValueFormatterProp == null ? void 0 : ignoreValueFormatterProp.clipboardExport : ignoreValueFormatterProp) || false;
22
+ const clipboardCopyCellDelimiter = props.clipboardCopyCellDelimiter;
20
23
  apiRef.current.registerControlState({
21
24
  stateId: 'cellSelection',
22
25
  propModel: props.unstable_cellSelectionModel,
@@ -317,7 +320,32 @@ export const useGridCellSelection = (apiRef, props) => {
317
320
  }
318
321
  return initialValue;
319
322
  }, [apiRef, props.unstable_cellSelection, hasClickedValidCellForRangeSelection]);
323
+ const handleClipboardCopy = React.useCallback(value => {
324
+ if (apiRef.current.unstable_getSelectedCellsAsArray().length <= 1) {
325
+ return value;
326
+ }
327
+ const cellSelectionModel = apiRef.current.unstable_getCellSelectionModel();
328
+ const copyData = Object.keys(cellSelectionModel).reduce((acc, rowId) => {
329
+ const fieldsMap = cellSelectionModel[rowId];
330
+ const rowString = Object.keys(fieldsMap).reduce((acc2, field) => {
331
+ let cellData;
332
+ if (fieldsMap[field]) {
333
+ const cellParams = apiRef.current.getCellParams(rowId, field);
334
+ cellData = serializeCellValue(cellParams, {
335
+ delimiterCharacter: clipboardCopyCellDelimiter,
336
+ ignoreValueFormatter
337
+ });
338
+ } else {
339
+ cellData = '';
340
+ }
341
+ return acc2 === '' ? cellData : [acc2, cellData].join(clipboardCopyCellDelimiter);
342
+ }, '');
343
+ return acc === '' ? rowString : [acc, rowString].join('\r\n');
344
+ }, '');
345
+ return copyData;
346
+ }, [apiRef, ignoreValueFormatter, clipboardCopyCellDelimiter]);
320
347
  useGridRegisterPipeProcessor(apiRef, 'isCellSelected', checkIfCellIsSelected);
321
348
  useGridRegisterPipeProcessor(apiRef, 'cellClassName', addClassesToCells);
322
349
  useGridRegisterPipeProcessor(apiRef, 'canUpdateFocus', canUpdateFocus);
350
+ useGridRegisterPipeProcessor(apiRef, 'clipboardCopy', handleClipboardCopy);
323
351
  };
@@ -0,0 +1,4 @@
1
+ import * as React from 'react';
2
+ import { GridPrivateApiPremium } from '../../../models/gridApiPremium';
3
+ import type { DataGridPremiumProcessedProps } from '../../../models/dataGridPremiumProps';
4
+ export declare const useGridClipboardImport: (apiRef: React.MutableRefObject<GridPrivateApiPremium>, props: Pick<DataGridPremiumProcessedProps, 'pagination' | 'paginationMode' | 'processRowUpdate' | 'onProcessRowUpdateError' | 'getRowId' | 'onClipboardPasteStart' | 'onClipboardPasteEnd' | 'experimentalFeatures' | 'unstable_splitClipboardPastedText' | 'disableClipboardPaste'>) => void;
@@ -0,0 +1,315 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ import * as React from 'react';
3
+ import { GRID_CHECKBOX_SELECTION_FIELD, gridFocusCellSelector, gridVisibleColumnFieldsSelector, useGridApiOptionHandler, useGridApiEventHandler, gridPaginatedVisibleSortedGridRowIdsSelector } from '@mui/x-data-grid';
4
+ import { buildWarning, getRowIdFromRowModel, getActiveElement, useGridRegisterPipeProcessor } from '@mui/x-data-grid/internals';
5
+ import { GRID_DETAIL_PANEL_TOGGLE_FIELD, GRID_REORDER_COL_DEF } from '@mui/x-data-grid-pro';
6
+ import { unstable_debounce as debounce } from '@mui/utils';
7
+ const missingOnProcessRowUpdateErrorWarning = buildWarning(['MUI: A call to `processRowUpdate` threw an error which was not handled because `onProcessRowUpdateError` is missing.', 'To handle the error pass a callback to the `onProcessRowUpdateError` prop, e.g. `<DataGrid onProcessRowUpdateError={(error) => ...} />`.', 'For more detail, see http://mui.com/components/data-grid/editing/#persistence.'], 'error');
8
+ const columnFieldsToExcludeFromPaste = [GRID_CHECKBOX_SELECTION_FIELD, GRID_REORDER_COL_DEF.field, GRID_DETAIL_PANEL_TOGGLE_FIELD];
9
+
10
+ // Batches rows that are updated during clipboard paste to reduce `updateRows` calls
11
+ function batchRowUpdates(func, wait) {
12
+ let rows = [];
13
+ const debounced = debounce(() => {
14
+ func(rows);
15
+ rows = [];
16
+ }, wait);
17
+ return row => {
18
+ rows.push(row);
19
+ debounced();
20
+ };
21
+ }
22
+ async function getTextFromClipboard(rootEl) {
23
+ return new Promise(resolve => {
24
+ const focusedCell = getActiveElement(document);
25
+ const el = document.createElement('input');
26
+ el.style.width = '0px';
27
+ el.style.height = '0px';
28
+ el.style.border = 'none';
29
+ el.style.margin = '0';
30
+ el.style.padding = '0';
31
+ el.style.outline = 'none';
32
+ el.style.position = 'absolute';
33
+ el.style.top = '0';
34
+ el.style.left = '0';
35
+ const handlePasteEvent = event => {
36
+ var _event$clipboardData;
37
+ el.removeEventListener('paste', handlePasteEvent);
38
+ const text = (_event$clipboardData = event.clipboardData) == null ? void 0 : _event$clipboardData.getData('text/plain');
39
+ if (focusedCell instanceof HTMLElement) {
40
+ focusedCell.focus({
41
+ preventScroll: true
42
+ });
43
+ }
44
+ el.remove();
45
+ resolve(text || '');
46
+ };
47
+ el.addEventListener('paste', handlePasteEvent);
48
+ rootEl.appendChild(el);
49
+ el.focus({
50
+ preventScroll: true
51
+ });
52
+ });
53
+ }
54
+
55
+ // Keeps track of updated rows during clipboard paste
56
+ class CellValueUpdater {
57
+ constructor(options) {
58
+ this.rowsToUpdate = {};
59
+ this.updateRow = void 0;
60
+ this.options = void 0;
61
+ this.options = options;
62
+ this.updateRow = batchRowUpdates(options.apiRef.current.updateRows, 50);
63
+ }
64
+ updateCell({
65
+ rowId,
66
+ field,
67
+ pastedCellValue
68
+ }) {
69
+ if (pastedCellValue === undefined) {
70
+ return;
71
+ }
72
+ const {
73
+ apiRef,
74
+ getRowId
75
+ } = this.options;
76
+ const colDef = apiRef.current.getColumn(field);
77
+ if (!colDef || !colDef.editable) {
78
+ return;
79
+ }
80
+ const row = this.rowsToUpdate[rowId] || _extends({}, apiRef.current.getRow(rowId));
81
+ if (!row) {
82
+ return;
83
+ }
84
+ const cellParams = apiRef.current.getCellParams(rowId, field);
85
+ let parsedValue = pastedCellValue;
86
+ if (colDef.pastedValueParser) {
87
+ parsedValue = colDef.pastedValueParser(pastedCellValue, cellParams);
88
+ } else if (colDef.valueParser) {
89
+ parsedValue = colDef.valueParser(parsedValue, cellParams);
90
+ }
91
+ if (parsedValue === undefined) {
92
+ return;
93
+ }
94
+ let rowCopy = _extends({}, row);
95
+ if (typeof colDef.valueSetter === 'function') {
96
+ rowCopy = colDef.valueSetter({
97
+ value: parsedValue,
98
+ row: rowCopy
99
+ });
100
+ } else {
101
+ rowCopy[field] = parsedValue;
102
+ }
103
+ const newRowId = getRowIdFromRowModel(rowCopy, getRowId);
104
+ if (String(newRowId) !== String(rowId)) {
105
+ // We cannot update row id, so this cell value update should be ignored
106
+ return;
107
+ }
108
+ this.rowsToUpdate[rowId] = rowCopy;
109
+ }
110
+ applyUpdates() {
111
+ const {
112
+ apiRef,
113
+ processRowUpdate,
114
+ onProcessRowUpdateError
115
+ } = this.options;
116
+ const rowsToUpdate = this.rowsToUpdate;
117
+ const rowIdsToUpdate = Object.keys(rowsToUpdate);
118
+ if (rowIdsToUpdate.length === 0) {
119
+ apiRef.current.publishEvent('clipboardPasteEnd');
120
+ return;
121
+ }
122
+ const handleRowUpdate = async rowId => {
123
+ const newRow = rowsToUpdate[rowId];
124
+ if (typeof processRowUpdate === 'function') {
125
+ const handleError = errorThrown => {
126
+ if (onProcessRowUpdateError) {
127
+ onProcessRowUpdateError(errorThrown);
128
+ } else {
129
+ missingOnProcessRowUpdateErrorWarning();
130
+ }
131
+ };
132
+ try {
133
+ const oldRow = apiRef.current.getRow(rowId);
134
+ const finalRowUpdate = await processRowUpdate(newRow, oldRow);
135
+ this.updateRow(finalRowUpdate);
136
+ } catch (error) {
137
+ handleError(error);
138
+ }
139
+ } else {
140
+ this.updateRow(newRow);
141
+ }
142
+ };
143
+ const promises = rowIdsToUpdate.map(rowId => {
144
+ // Wrap in promise that always resolves to avoid Promise.all from stopping on first error.
145
+ // This is to avoid using `Promise.allSettled` that has worse browser support.
146
+ return new Promise(resolve => {
147
+ handleRowUpdate(rowId).then(resolve).catch(resolve);
148
+ });
149
+ });
150
+ Promise.all(promises).then(() => {
151
+ this.rowsToUpdate = {};
152
+ apiRef.current.publishEvent('clipboardPasteEnd');
153
+ });
154
+ }
155
+ }
156
+ function defaultPasteResolver({
157
+ pastedData,
158
+ apiRef,
159
+ updateCell
160
+ }) {
161
+ const isSingleValuePasted = pastedData.length === 1 && pastedData[0].length === 1;
162
+ const cellSelectionModel = apiRef.current.unstable_getCellSelectionModel();
163
+ if (cellSelectionModel && apiRef.current.unstable_getSelectedCellsAsArray().length > 1) {
164
+ Object.keys(cellSelectionModel).forEach((rowId, rowIndex) => {
165
+ const rowDataArr = pastedData[isSingleValuePasted ? 0 : rowIndex];
166
+ const hasRowData = isSingleValuePasted ? true : rowDataArr !== undefined;
167
+ if (!hasRowData) {
168
+ return;
169
+ }
170
+ Object.keys(cellSelectionModel[rowId]).forEach((field, colIndex) => {
171
+ const cellValue = isSingleValuePasted ? rowDataArr[0] : rowDataArr[colIndex];
172
+ updateCell({
173
+ rowId,
174
+ field,
175
+ pastedCellValue: cellValue
176
+ });
177
+ });
178
+ });
179
+ return;
180
+ }
181
+ const visibleColumnFields = gridVisibleColumnFieldsSelector(apiRef).filter(field => {
182
+ if (columnFieldsToExcludeFromPaste.includes(field)) {
183
+ return false;
184
+ }
185
+ return true;
186
+ });
187
+ const selectedRows = apiRef.current.getSelectedRows();
188
+ if (selectedRows.size > 0 && !isSingleValuePasted) {
189
+ // Multiple values are pasted starting from the first and top-most cell
190
+ const pastedRowsDataCount = pastedData.length;
191
+
192
+ // There's no guarantee that the selected rows are in the same order as the pasted rows
193
+ selectedRows.forEach((row, rowId) => {
194
+ let rowData;
195
+ if (pastedRowsDataCount === 1) {
196
+ // If only one row is pasted - paste it to all selected rows
197
+ rowData = pastedData[0];
198
+ } else {
199
+ rowData = pastedData.shift();
200
+ }
201
+ if (rowData === undefined) {
202
+ return;
203
+ }
204
+ rowData.forEach((newCellValue, cellIndex) => {
205
+ updateCell({
206
+ rowId,
207
+ field: visibleColumnFields[cellIndex],
208
+ pastedCellValue: newCellValue
209
+ });
210
+ });
211
+ });
212
+ return;
213
+ }
214
+ const selectedCell = gridFocusCellSelector(apiRef);
215
+ if (!selectedCell) {
216
+ return;
217
+ }
218
+ if (columnFieldsToExcludeFromPaste.includes(selectedCell.field)) {
219
+ return;
220
+ }
221
+ const selectedRowId = selectedCell.id;
222
+ const selectedRowIndex = apiRef.current.getRowIndexRelativeToVisibleRows(selectedRowId);
223
+ const visibleRowIds = gridPaginatedVisibleSortedGridRowIdsSelector(apiRef);
224
+ const selectedFieldIndex = visibleColumnFields.indexOf(selectedCell.field);
225
+ pastedData.forEach((rowData, index) => {
226
+ const rowId = visibleRowIds[selectedRowIndex + index];
227
+ if (typeof rowId === 'undefined') {
228
+ return;
229
+ }
230
+ for (let i = selectedFieldIndex; i < visibleColumnFields.length; i += 1) {
231
+ const field = visibleColumnFields[i];
232
+ const stringValue = rowData[i - selectedFieldIndex];
233
+ updateCell({
234
+ rowId,
235
+ field,
236
+ pastedCellValue: stringValue
237
+ });
238
+ }
239
+ });
240
+ }
241
+ function isPasteShortcut(event) {
242
+ const isModifierKeyPressed = event.ctrlKey || event.metaKey || event.altKey;
243
+ if (event.code === 'KeyV' && isModifierKeyPressed) {
244
+ return true;
245
+ }
246
+ return false;
247
+ }
248
+ export const useGridClipboardImport = (apiRef, props) => {
249
+ var _ref, _props$experimentalFe, _apiRef$current$rootE;
250
+ const processRowUpdate = props.processRowUpdate;
251
+ const onProcessRowUpdateError = props.onProcessRowUpdateError;
252
+ const getRowId = props.getRowId;
253
+ const enableClipboardPaste = (_ref = !props.disableClipboardPaste && ((_props$experimentalFe = props.experimentalFeatures) == null ? void 0 : _props$experimentalFe.clipboardPaste)) != null ? _ref : false;
254
+ const rootEl = (_apiRef$current$rootE = apiRef.current.rootElementRef) == null ? void 0 : _apiRef$current$rootE.current;
255
+ const splitClipboardPastedText = props.unstable_splitClipboardPastedText;
256
+ const handlePaste = React.useCallback(async (params, event) => {
257
+ if (!enableClipboardPaste) {
258
+ return;
259
+ }
260
+ if (!isPasteShortcut(event)) {
261
+ return;
262
+ }
263
+ const focusedCell = gridFocusCellSelector(apiRef);
264
+ if (focusedCell !== null) {
265
+ const cellMode = apiRef.current.getCellMode(focusedCell.id, focusedCell.field);
266
+ if (cellMode === 'edit') {
267
+ // Do not paste data when the cell is in edit mode
268
+ return;
269
+ }
270
+ }
271
+ if (!rootEl) {
272
+ return;
273
+ }
274
+ const text = await getTextFromClipboard(rootEl);
275
+ if (!text) {
276
+ return;
277
+ }
278
+ const pastedData = splitClipboardPastedText(text);
279
+ if (!pastedData) {
280
+ return;
281
+ }
282
+ const cellUpdater = new CellValueUpdater({
283
+ apiRef,
284
+ processRowUpdate,
285
+ onProcessRowUpdateError,
286
+ getRowId
287
+ });
288
+ apiRef.current.publishEvent('clipboardPasteStart', {
289
+ data: pastedData
290
+ });
291
+ defaultPasteResolver({
292
+ pastedData,
293
+ apiRef: {
294
+ current: apiRef.current.getPublicApi()
295
+ },
296
+ updateCell: (...args) => {
297
+ cellUpdater.updateCell(...args);
298
+ }
299
+ });
300
+ cellUpdater.applyUpdates();
301
+ }, [apiRef, processRowUpdate, onProcessRowUpdateError, getRowId, enableClipboardPaste, rootEl, splitClipboardPastedText]);
302
+ const checkIfCanStartEditing = React.useCallback((initialValue, {
303
+ event
304
+ }) => {
305
+ if (isPasteShortcut(event) && enableClipboardPaste) {
306
+ // Do not enter cell edit mode on paste
307
+ return false;
308
+ }
309
+ return initialValue;
310
+ }, [enableClipboardPaste]);
311
+ useGridApiEventHandler(apiRef, 'cellKeyDown', handlePaste);
312
+ useGridApiOptionHandler(apiRef, 'clipboardPasteStart', props.onClipboardPasteStart);
313
+ useGridApiOptionHandler(apiRef, 'clipboardPasteEnd', props.onClipboardPasteEnd);
314
+ useGridRegisterPipeProcessor(apiRef, 'canStartEditing', checkIfCanStartEditing);
315
+ };
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @mui/x-data-grid-premium v6.3.1
2
+ * @mui/x-data-grid-premium v6.4.0
3
3
  *
4
4
  * @license MUI X Commercial
5
5
  * This source code is licensed under the commercial license found in the
@@ -106,6 +106,11 @@ process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
106
106
  * Override or extend the styles applied to the component.
107
107
  */
108
108
  classes: PropTypes.object,
109
+ /**
110
+ * The character used to separate cell values when copying to the clipboard.
111
+ * @default '\t'
112
+ */
113
+ clipboardCopyCellDelimiter: PropTypes.string,
109
114
  /**
110
115
  * Number of extra columns to be rendered before/after the visible slice.
111
116
  * @default 3
@@ -171,6 +176,11 @@ process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
171
176
  * @default false
172
177
  */
173
178
  disableChildrenSorting: PropTypes.bool,
179
+ /**
180
+ * If `true`, the clipboard paste is disabled.
181
+ * @default false
182
+ */
183
+ disableClipboardPaste: PropTypes.bool,
174
184
  /**
175
185
  * If `true`, column filters are disabled.
176
186
  * @default false
@@ -246,6 +256,7 @@ process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
246
256
  * For each feature, if the flag is not explicitly set to `true`, then the feature is fully disabled, and neither property nor method calls will have any effect.
247
257
  */
248
258
  experimentalFeatures: PropTypes.shape({
259
+ clipboardPaste: PropTypes.bool,
249
260
  columnGrouping: PropTypes.bool,
250
261
  lazyLoading: PropTypes.bool,
251
262
  warnIfFocusStateIsNotSynced: PropTypes.bool
@@ -481,6 +492,19 @@ process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
481
492
  * @param {GridCallbackDetails} details Additional details for this callback.
482
493
  */
483
494
  onCellModesModelChange: PropTypes.func,
495
+ /**
496
+ * Callback called when the data is copied to the clipboard.
497
+ * @param {string} data The data copied to the clipboard.
498
+ */
499
+ onClipboardCopy: PropTypes.func,
500
+ /**
501
+ * Callback fired when the clipboard paste operation ends.
502
+ */
503
+ onClipboardPasteEnd: PropTypes.func,
504
+ /**
505
+ * Callback fired when the clipboard paste operation starts.
506
+ */
507
+ onClipboardPasteStart: PropTypes.func,
484
508
  /**
485
509
  * Callback fired when a click event comes from a column header element.
486
510
  * @param {GridColumnHeaderParams} params With all properties from [[GridColumnHeaderParams]].
@@ -885,10 +909,26 @@ process.env.NODE_ENV !== "production" ? DataGridPremiumRaw.propTypes = {
885
909
  * Set the cell selection model of the grid.
886
910
  */
887
911
  unstable_cellSelectionModel: PropTypes.object,
912
+ /**
913
+ * If `true`, the grid will not use `valueFormatter` when exporting to CSV or copying to clipboard.
914
+ * If an object is provided, you can choose to ignore the `valueFormatter` for CSV export or clipboard export.
915
+ * @default: false
916
+ */
917
+ unstable_ignoreValueFormatterDuringExport: PropTypes.oneOfType([PropTypes.shape({
918
+ clipboardExport: PropTypes.bool,
919
+ csvExport: PropTypes.bool
920
+ }), PropTypes.bool]),
888
921
  /**
889
922
  * Callback fired when the selection state of one or multiple cells changes.
890
923
  * @param {GridCellSelectionModel} cellSelectionModel Object in the shape of [[GridCellSelectionModel]] containing the selected cells.
891
924
  * @param {GridCallbackDetails} details Additional details for this callback.
892
925
  */
893
- unstable_onCellSelectionModelChange: PropTypes.func
926
+ unstable_onCellSelectionModelChange: PropTypes.func,
927
+ /**
928
+ * The function is used to split the pasted text into rows and cells.
929
+ * @param {string} text The text pasted from the clipboard.
930
+ * @returns {string[][] | null} A 2D array of strings. The first dimension is the rows, the second dimension is the columns.
931
+ * @default `(text) => text.split(/\r\n|\n|\r/).map((row) => row.split('\t'))`
932
+ */
933
+ unstable_splitClipboardPastedText: PropTypes.func
894
934
  } : void 0;