diff --git a/docs/demo/paste.md b/docs/demo/paste.md
new file mode 100644
index 0000000..51574df
--- /dev/null
+++ b/docs/demo/paste.md
@@ -0,0 +1,8 @@
+---
+title: paste
+nav:
+ title: Demo
+ path: /demo
+---
+
+
diff --git a/docs/examples/paste.tsx b/docs/examples/paste.tsx
new file mode 100644
index 0000000..05e41f0
--- /dev/null
+++ b/docs/examples/paste.tsx
@@ -0,0 +1,43 @@
+/* eslint no-console:0 */
+import React from 'react';
+import Upload from 'rc-upload';
+
+const props = {
+ action: '/upload.do',
+ type: 'drag',
+ accept: '.png',
+ beforeUpload(file) {
+ console.log('beforeUpload', file.name);
+ },
+ onStart: file => {
+ console.log('onStart', file.name);
+ },
+ onSuccess(file) {
+ console.log('onSuccess', file);
+ },
+ onProgress(step, file) {
+ console.log('onProgress', Math.round(step.percent), file.name);
+ },
+ onError(err) {
+ console.log('onError', err);
+ },
+ style: { display: 'inline-block', width: 200, height: 200, background: '#eee' },
+};
+
+const Test = () => {
+ return (
+
+ );
+};
+
+export default Test;
diff --git a/src/AjaxUploader.tsx b/src/AjaxUploader.tsx
index e88c291..155384f 100644
--- a/src/AjaxUploader.tsx
+++ b/src/AjaxUploader.tsx
@@ -28,6 +28,8 @@ class AjaxUploader extends Component {
private fileInput: HTMLInputElement;
+ private isMouseEnter: boolean;
+
private _isMounted: boolean;
onChange = (e: React.ChangeEvent) => {
@@ -66,41 +68,57 @@ class AjaxUploader extends Component {
}
};
- onFileDrop = (e: React.DragEvent) => {
- const { multiple } = this.props;
-
+ onFileDropOrPaste = (e: React.DragEvent | ClipboardEvent) => {
e.preventDefault();
if (e.type === 'dragover') {
return;
}
- if (this.props.directory) {
- traverseFileTree(
- Array.prototype.slice.call(e.dataTransfer.items),
- this.uploadFiles,
- (_file: RcFile) => attrAccept(_file, this.props.accept),
+ const { multiple, accept, directory } = this.props;
+ let items: DataTransferItem[] = [];
+ let files: File[] = [];
+
+ if (e.type === 'drop') {
+ const dataTransfer = (e as React.DragEvent).dataTransfer;
+ items = [...(dataTransfer.items || [])];
+ files = [...(dataTransfer.files || [])];
+ } else if (e.type === 'paste') {
+ const clipboardData = (e as ClipboardEvent).clipboardData;
+ items = [...(clipboardData.items || [])];
+ files = [...(clipboardData.files || [])];
+ }
+
+ if (directory) {
+ traverseFileTree(Array.prototype.slice.call(items), this.uploadFiles, (_file: RcFile) =>
+ attrAccept(_file, accept),
);
} else {
- let files = [...e.dataTransfer.files].filter((file: RcFile) =>
- attrAccept(file, this.props.accept),
- );
+ let acceptFiles = [...files].filter((file: RcFile) => attrAccept(file, accept));
if (multiple === false) {
- files = files.slice(0, 1);
+ acceptFiles = files.slice(0, 1);
}
- this.uploadFiles(files);
+ this.uploadFiles(acceptFiles);
}
};
+ onPrePaste(e: ClipboardEvent) {
+ if (this.isMouseEnter) {
+ this.onFileDropOrPaste(e);
+ }
+ }
+
componentDidMount() {
this._isMounted = true;
+ document.addEventListener('paste', this.onPrePaste.bind(this));
}
componentWillUnmount() {
this._isMounted = false;
this.abort();
+ document.removeEventListener('paste', this.onPrePaste.bind(this));
}
uploadFiles = (files: File[]) => {
@@ -261,6 +279,18 @@ class AjaxUploader extends Component {
this.fileInput = node;
};
+ handleMouseEnter = (e: React.MouseEvent) => {
+ this.isMouseEnter = true;
+
+ this.props.onMouseEnter?.(e);
+ };
+
+ handleMouseLeave = (e: React.MouseEvent) => {
+ this.isMouseEnter = false;
+
+ this.props.onMouseLeave?.(e);
+ };
+
render() {
const {
component: Tag,
@@ -277,8 +307,6 @@ class AjaxUploader extends Component {
children,
directory,
openFileDialogOnClick,
- onMouseEnter,
- onMouseLeave,
hasControlInside,
...otherProps
} = this.props;
@@ -296,10 +324,10 @@ class AjaxUploader extends Component {
: {
onClick: openFileDialogOnClick ? this.onClick : () => {},
onKeyDown: openFileDialogOnClick ? this.onKeyDown : () => {},
- onMouseEnter,
- onMouseLeave,
- onDrop: this.onFileDrop,
- onDragOver: this.onFileDrop,
+ onMouseEnter: this.handleMouseEnter,
+ onMouseLeave: this.handleMouseLeave,
+ onDrop: this.onFileDropOrPaste,
+ onDragOver: this.onFileDropOrPaste,
tabIndex: hasControlInside ? undefined : '0',
};
return (
diff --git a/tests/uploader.spec.tsx b/tests/uploader.spec.tsx
index b7e060c..4ac119f 100644
--- a/tests/uploader.spec.tsx
+++ b/tests/uploader.spec.tsx
@@ -306,6 +306,106 @@ describe('uploader', () => {
}, 100);
});
+ it('paste to upload', done => {
+ const rcUpload = uploader.container.querySelector('.rc-upload')!;
+ const input = uploader.container.querySelector('input')!;
+
+ const files = [
+ {
+ name: 'success.png',
+ toString() {
+ return this.name;
+ },
+ },
+ ];
+ (files as any).item = (i: number) => files[i];
+
+ handlers.onSuccess = (ret, file) => {
+ expect(ret[1]).toEqual(file.name);
+ expect(file).toHaveProperty('uid');
+ done();
+ };
+
+ handlers.onError = err => {
+ done(err);
+ };
+
+ fireEvent.mouseEnter(rcUpload);
+ fireEvent.paste(input, {
+ clipboardData: { files },
+ });
+
+ setTimeout(() => {
+ requests[0].respond(200, {}, `["","${files[0].name}"]`);
+ }, 100);
+ });
+
+ it('paste unaccepted type files to upload will not trigger onStart', done => {
+ const input = uploader.container.querySelector('input')!;
+ const files = [
+ {
+ name: 'success.jpg',
+ toString() {
+ return this.name;
+ },
+ },
+ ];
+ (files as any).item = (i: number) => files[i];
+
+ fireEvent.paste(input, {
+ clipboardData: { files },
+ });
+ const mockStart = jest.fn();
+ handlers.onStart = mockStart;
+ setTimeout(() => {
+ expect(mockStart.mock.calls.length).toBe(0);
+ done();
+ }, 100);
+ });
+
+ it('paste files with multiple false', done => {
+ const { container } = render();
+ const rcUpload = container.querySelector('.rc-upload')!;
+ const input = container.querySelector('input')!;
+ const files = [
+ new File([''], 'success.png', { type: 'image/png' }),
+ new File([''], 'filtered.png', { type: 'image/png' }),
+ ];
+ Object.defineProperty(files, 'item', {
+ value: i => files[i],
+ });
+
+ // Only can trigger once
+ let triggerTimes = 0;
+ handlers.onStart = () => {
+ triggerTimes += 1;
+ };
+ handlers.onSuccess = (ret, file) => {
+ try {
+ expect(ret[1]).toEqual(file.name);
+ expect(file).toHaveProperty('uid');
+ expect(triggerTimes).toEqual(1);
+ done();
+ } catch (error) {
+ done(error);
+ }
+ };
+ handlers.onError = error => {
+ done(error);
+ };
+
+ Object.defineProperty(input, 'files', {
+ value: files,
+ });
+
+ fireEvent.mouseEnter(rcUpload);
+ fireEvent.paste(input, { clipboardData: { files } });
+
+ setTimeout(() => {
+ handlers.onSuccess!(['', files[0].name] as any, files[0] as any, null!);
+ }, 100);
+ });
+
it('support action and data is function returns Promise', async () => {
const action: any = () => {
return new Promise(resolve => {
@@ -332,6 +432,21 @@ describe('uploader', () => {
await new Promise(resolve => setTimeout(resolve, 100));
await new Promise(resolve => setTimeout(resolve, 2000));
});
+
+ it('support onMouseEnter and onMouseLeave', async () => {
+ const onMouseEnter = jest.fn();
+ const onMouseLeave = jest.fn();
+
+ const { container } = render(
+ ,
+ );
+ const rcUpload = container.querySelector('.rc-upload')!;
+
+ fireEvent.mouseEnter(rcUpload);
+ fireEvent.mouseLeave(rcUpload);
+ expect(onMouseEnter).toHaveBeenCalled();
+ expect(onMouseLeave).toHaveBeenCalled();
+ });
});
describe('directory uploader', () => {