Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGridPremium] Add support for confirmation before clipboard paste #12225

Merged
merged 9 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading