Skip to content

Commit

Permalink
[DataGridPremium] Add support for confirmation before clipboard paste (
Browse files Browse the repository at this point in the history
  • Loading branch information
cherniavskii authored Mar 15, 2024
1 parent 6549091 commit 3003314
Show file tree
Hide file tree
Showing 9 changed files with 201 additions and 13 deletions.
71 changes: 71 additions & 0 deletions docs/data/data-grid/clipboard/ClipboardPasteEvents.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import * as React from 'react';
import { DataGridPremium } from '@mui/x-data-grid-premium';
import { useDemoData } from '@mui/x-data-grid-generator';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import Button from '@mui/material/Button';

export default function ClipboardPasteEvents() {
const { data } = useDemoData({
Expand Down Expand Up @@ -28,6 +34,19 @@ export default function ClipboardPasteEvents() {
},
};

const confirm = useConfirm();
const confirmPaste = React.useCallback(() => {
return new Promise((resolve, reject) => {
confirm.open((confirmed) => {
if (confirmed) {
resolve();
} else {
reject();
}
});
});
}, [confirm]);

return (
<div style={{ width: '100%', height: 400 }}>
<DataGridPremium
Expand All @@ -36,10 +55,62 @@ export default function ClipboardPasteEvents() {
initialState={initialState}
cellSelection
processRowUpdate={processRowUpdate}
onBeforeClipboardPasteStart={confirmPaste}
onClipboardPasteStart={() => setLoading(true)}
onClipboardPasteEnd={() => setLoading(false)}
ignoreValueFormatterDuringExport
disableRowSelectionOnClick
/>
<Dialog
open={confirm.isOpen}
onClose={confirm.cancel}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{'Are you sure you want to paste?'}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
This will overwrite the selected cells.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={confirm.cancel}>Cancel</Button>
<Button onClick={confirm.confirm} autoFocus>
Confirm
</Button>
</DialogActions>
</Dialog>
</div>
);
}

const useConfirm = () => {
const [isOpen, setIsOpen] = React.useState(false);
const callbackRef = React.useRef(null);

const open = React.useCallback((callback) => {
setIsOpen(true);
callbackRef.current = callback;
}, []);

const cancel = React.useCallback(() => {
setIsOpen(false);
callbackRef.current?.(false);
callbackRef.current = null;
}, []);

const confirm = React.useCallback(() => {
setIsOpen(false);
callbackRef.current?.(true);
callbackRef.current = null;
}, []);

return {
open,
isOpen,
cancel,
confirm,
};
};
72 changes: 72 additions & 0 deletions docs/data/data-grid/clipboard/ClipboardPasteEvents.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import * as React from 'react';
import { DataGridPremium, DataGridPremiumProps } from '@mui/x-data-grid-premium';
import { useDemoData } from '@mui/x-data-grid-generator';
import Dialog from '@mui/material/Dialog';
import DialogActions from '@mui/material/DialogActions';
import DialogContent from '@mui/material/DialogContent';
import DialogContentText from '@mui/material/DialogContentText';
import DialogTitle from '@mui/material/DialogTitle';
import Button from '@mui/material/Button';

export default function ClipboardPasteEvents() {
const { data } = useDemoData({
Expand Down Expand Up @@ -30,6 +36,19 @@ export default function ClipboardPasteEvents() {
},
};

const confirm = useConfirm();
const confirmPaste = React.useCallback<() => Promise<void>>(() => {
return new Promise((resolve, reject) => {
confirm.open((confirmed) => {
if (confirmed) {
resolve();
} else {
reject();
}
});
});
}, [confirm]);

return (
<div style={{ width: '100%', height: 400 }}>
<DataGridPremium
Expand All @@ -38,10 +57,63 @@ export default function ClipboardPasteEvents() {
initialState={initialState}
cellSelection
processRowUpdate={processRowUpdate}
onBeforeClipboardPasteStart={confirmPaste}
onClipboardPasteStart={() => setLoading(true)}
onClipboardPasteEnd={() => setLoading(false)}
ignoreValueFormatterDuringExport
disableRowSelectionOnClick
/>

<Dialog
open={confirm.isOpen}
onClose={confirm.cancel}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{'Are you sure you want to paste?'}
</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-description">
This will overwrite the selected cells.
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={confirm.cancel}>Cancel</Button>
<Button onClick={confirm.confirm} autoFocus>
Confirm
</Button>
</DialogActions>
</Dialog>
</div>
);
}

const useConfirm = () => {
const [isOpen, setIsOpen] = React.useState(false);
const callbackRef = React.useRef<((confirmed: boolean) => void) | null>(null);

const open = React.useCallback((callback: (confirmed: boolean) => void) => {
setIsOpen(true);
callbackRef.current = callback;
}, []);

const cancel = React.useCallback(() => {
setIsOpen(false);
callbackRef.current?.(false);
callbackRef.current = null;
}, []);

const confirm = React.useCallback(() => {
setIsOpen(false);
callbackRef.current?.(true);
callbackRef.current = null;
}, []);

return {
open,
isOpen,
cancel,
confirm,
};
};
10 changes: 0 additions & 10 deletions docs/data/data-grid/clipboard/ClipboardPasteEvents.tsx.preview

This file was deleted.

17 changes: 16 additions & 1 deletion docs/data/data-grid/clipboard/clipboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,22 @@ For convenience, you can also listen to these events using their respective prop
- `onClipboardPasteStart`
- `onClipboardPasteEnd`

The demo below shows how to use these events to display a loading indicator while the clipboard paste operation is in progress:
Additionally, there is the `onBeforeClipboardPasteStart` prop, which is called before the clipboard paste operation starts
and can be used to cancel or confirm the paste operation:

```tsx
const onBeforeClipboardPasteStart = async () => {
const confirmed = window.confirm('Are you sure you want to paste?');
if (!confirmed) {
throw new Error('Paste operation cancelled');
}
};

<DataGridPremium onBeforeClipboardPasteStart={onBeforeClipboardPasteStart} />;
```

The demo below uses the [`Dialog`](/material-ui/react-dialog/) component for paste confirmation.
If confirmed, the Data Grid displays a loading indicator during the paste operation.

{{"demo": "ClipboardPasteEvents.js", "bg": "inline"}}

Expand Down
4 changes: 4 additions & 0 deletions docs/pages/x/api/data-grid/data-grid-premium.json
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@
"describedArgs": ["model", "details"]
}
},
"onBeforeClipboardPasteStart": {
"type": { "name": "func" },
"signature": { "type": "function(params: object) => void", "describedArgs": ["params"] }
},
"onCellClick": {
"type": { "name": "func" },
"signature": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,10 @@
"details": "Additional details for this callback."
}
},
"onBeforeClipboardPasteStart": {
"description": "Callback fired before the clipboard paste operation starts. Use it to confirm or cancel the paste operation.",
"typeDescriptions": { "params": "Params passed to the callback." }
},
"onCellClick": {
"description": "Callback fired when any cell is clicked.",
"typeDescriptions": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,14 @@ DataGridPremiumRaw.propTypes = {
* @param {GridCallbackDetails} details Additional details for this callback.
*/
onAggregationModelChange: PropTypes.func,
/**
* Callback fired before the clipboard paste operation starts.
* Use it to confirm or cancel the paste operation.
* @param {object} params Params passed to the callback.
* @param {string[][]} params.data The raw pasted data split by rows and cells.
* @returns {Promise<any>} A promise that resolves to confirm the paste operation, and rejects to cancel it.
*/
onBeforeClipboardPasteStart: PropTypes.func,
/**
* Callback fired when any cell is clicked.
* @param {GridCellParams} params With all properties from [[GridCellParams]].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
useGridRegisterPipeProcessor,
getPublicApiRef,
isPasteShortcut,
useGridLogger,
} from '@mui/x-data-grid/internals';
import { GRID_DETAIL_PANEL_TOGGLE_FIELD, GRID_REORDER_COL_DEF } from '@mui/x-data-grid-pro';
import { unstable_debounce as debounce } from '@mui/utils';
Expand Down Expand Up @@ -318,16 +319,20 @@ export const useGridClipboardImport = (
| 'onClipboardPasteEnd'
| 'splitClipboardPastedText'
| 'disableClipboardPaste'
| 'onBeforeClipboardPasteStart'
>,
): void => {
const processRowUpdate = props.processRowUpdate;
const onProcessRowUpdateError = props.onProcessRowUpdateError;
const getRowId = props.getRowId;
const enableClipboardPaste = !props.disableClipboardPaste;
const rootEl = apiRef.current.rootElementRef?.current;
const logger = useGridLogger(apiRef, 'useGridClipboardImport');

const splitClipboardPastedText = props.splitClipboardPastedText;

const { pagination, onBeforeClipboardPasteStart } = props;

const handlePaste = React.useCallback<GridEventListener<'cellKeyDown'>>(
async (params, event) => {
if (!enableClipboardPaste) {
Expand Down Expand Up @@ -360,6 +365,15 @@ export const useGridClipboardImport = (
return;
}

if (onBeforeClipboardPasteStart) {
try {
await onBeforeClipboardPasteStart({ data: pastedData });
} catch (error) {
logger.debug('Clipboard paste operation cancelled');
return;
}
}

const cellUpdater = new CellValueUpdater({
apiRef,
processRowUpdate,
Expand All @@ -377,7 +391,7 @@ export const useGridClipboardImport = (
updateCell: (...args) => {
cellUpdater.updateCell(...args);
},
pagination: props.pagination,
pagination,
});

cellUpdater.applyUpdates();
Expand All @@ -390,7 +404,9 @@ export const useGridClipboardImport = (
enableClipboardPaste,
rootEl,
splitClipboardPastedText,
props.pagination,
pagination,
onBeforeClipboardPasteStart,
logger,
],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ export interface DataGridPremiumPropsWithoutDefaultValue<R extends GridValidRowM
* @param {string} inProgress Indicates if the task is in progress.
*/
onExcelExportStateChange?: (inProgress: 'pending' | 'finished') => void;
/**
* Callback fired before the clipboard paste operation starts.
* Use it to confirm or cancel the paste operation.
* @param {object} params Params passed to the callback.
* @param {string[][]} params.data The raw pasted data split by rows and cells.
* @returns {Promise<any>} A promise that resolves to confirm the paste operation, and rejects to cancel it.
*/
onBeforeClipboardPasteStart?: (params: { data: string[][] }) => Promise<any>;
/**
* Callback fired when the clipboard paste operation starts.
*/
Expand Down

0 comments on commit 3003314

Please sign in to comment.