Skip to content

Commit

Permalink
feat: add support for folder drag 'n' drop
Browse files Browse the repository at this point in the history
BREAKING CHANGE

Dropzone uses [file-selector](https://github.com/react-dropzone/file-selector) to aggregate files by default.
This means that users that previously had to install the lib and use a custom {getDataTransferItems} prop to get folder drop support, don't have to do it anymore.

Before:
```js
import {fromEvent} from 'file-selector';

<Dropzone getDataTransferItems={fromEvent}>
  {({getRootProps}) => <div {...getRootProps()} />}
</Dropzone>
```

After:
```js
<Dropzone>
  {({getRootProps}) => <div {...getRootProps()} />}
</Dropzone>
```
  • Loading branch information
Roland Groza authored and rolandjitsu committed Feb 21, 2019
1 parent 3655a0d commit 2fc6e06
Show file tree
Hide file tree
Showing 9 changed files with 41 additions and 181 deletions.
4 changes: 3 additions & 1 deletion examples/Basic/Readme.md
@@ -1,4 +1,6 @@
By default, Dropzone just renders provided children without applying any styles.
By default, Dropzone just renders provided children without applying any styles.

Furthermore, Dropzone supports folders drag 'n' drop. See [file-selector](https://github.com/react-dropzone/file-selector) for more info about supported browsers.


```jsx harmony
Expand Down
49 changes: 0 additions & 49 deletions examples/Folders/Readme.md

This file was deleted.

6 changes: 3 additions & 3 deletions package.json
Expand Up @@ -25,11 +25,11 @@
"size-limit": [
{
"path": "dist/index.js",
"limit": "6 KB"
"limit": "7 KB"
},
{
"path": "dist/es/index.js",
"limit": "6 KB"
"limit": "7 KB"
}
],
"lint-staged": {
Expand Down Expand Up @@ -82,6 +82,7 @@
},
"dependencies": {
"attr-accept": "^1.1.3",
"file-selector": "^0.1.8",
"prop-types": "^15.6.2",
"prop-types-extra": "^1.1.0"
},
Expand Down Expand Up @@ -115,7 +116,6 @@
"eslint-plugin-node": "5.x",
"eslint-plugin-prettier": "2.x",
"eslint-plugin-react": "7.x",
"file-selector": "^0.1.3",
"husky": "^0.14.3",
"imagemin-cli": "^3.0.0",
"imagemin-pngquant": "^6.0.0",
Expand Down
4 changes: 2 additions & 2 deletions src/index.js
@@ -1,6 +1,7 @@
/* eslint prefer-template: 0 */

import React from 'react'
import { fromEvent } from 'file-selector'
import PropTypes from 'prop-types'
import deprecated from 'prop-types-extra/lib/deprecated'
import {
Expand All @@ -10,7 +11,6 @@ import {
allFilesAccepted,
fileMatchSize,
onDocumentDragOver,
getDataTransferItems as defaultGetDataTransferItem,
isIeOrEdge,
composeEventHandlers,
isPropagationStopped,
Expand Down Expand Up @@ -577,5 +577,5 @@ Dropzone.defaultProps = {
multiple: true,
maxSize: Infinity,
minSize: 0,
getDataTransferItems: defaultGetDataTransferItem
getDataTransferItems: fromEvent
}
30 changes: 29 additions & 1 deletion src/index.spec.js
Expand Up @@ -30,6 +30,11 @@ const createDtWithFiles = (files = []) => {
return {
dataTransfer: {
files,
items: files.map(file => ({
kind: 'file',
type: file.type,
getAsFile: () => file
})),
types: ['Files']
}
}
Expand Down Expand Up @@ -1127,8 +1132,9 @@ describe('Dropzone', () => {
)

const input = component.find('input')
Object.defineProperty(input, 'files', { value: files })
const evt = {
target: { files },
target: input,
preventDefault() {},
isPropagationStopped: () => false,
persist() {}
Expand Down Expand Up @@ -1202,6 +1208,7 @@ describe('Dropzone', () => {
</Dropzone>
)
await dropzone.simulate('drop', createDtWithFiles(files))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith([], files, expectedEvent)
})

Expand All @@ -1217,6 +1224,7 @@ describe('Dropzone', () => {
)

await dropzone.simulate('drop', createDtWithFiles([images[0]]))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith([images[0]], [], expectedEvent)
})

Expand All @@ -1232,6 +1240,7 @@ describe('Dropzone', () => {
)

await dropzone.simulate('drop', createDtWithFiles(images))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith([], images, expectedEvent)
})

Expand All @@ -1246,6 +1255,7 @@ describe('Dropzone', () => {
</Dropzone>
)
await dropzone.simulate('drop', createDtWithFiles(images))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith(images, [], expectedEvent)
})

Expand Down Expand Up @@ -1276,14 +1286,17 @@ describe('Dropzone', () => {
)

await dropzone.simulate('drop', createDtWithFiles(files))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith([], files, expectedEvent)
onDrop.mockClear()

await dropzone.simulate('drop', createDtWithFiles(images))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith(images, [], expectedEvent)
onDrop.mockClear()

await dropzone.simulate('drop', createDtWithFiles(files.concat(images)))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith(images, files, expectedEvent)
})

Expand All @@ -1299,14 +1312,17 @@ describe('Dropzone', () => {
)

await dropzone.simulate('drop', createDtWithFiles(files))
await flushPromises(dropzone)
expect(onDropAccepted).not.toHaveBeenCalled()
onDropAccepted.mockClear()

await dropzone.simulate('drop', createDtWithFiles(images))
await flushPromises(dropzone)
expect(onDropAccepted).toHaveBeenCalledWith(images, expectedEvent)
onDropAccepted.mockClear()

await dropzone.simulate('drop', createDtWithFiles(files.concat(images)))
await flushPromises(dropzone)
expect(onDropAccepted).toHaveBeenCalledWith(images, expectedEvent)
})

Expand All @@ -1322,14 +1338,17 @@ describe('Dropzone', () => {
)

await dropzone.simulate('drop', createDtWithFiles(images))
await flushPromises(dropzone)
expect(onDropRejected).not.toHaveBeenCalled()
onDropRejected.mockClear()

await dropzone.simulate('drop', createDtWithFiles(files))
await flushPromises(dropzone)
expect(onDropRejected).toHaveBeenCalledWith(files, expectedEvent)
onDropRejected.mockClear()

await dropzone.simulate('drop', createDtWithFiles(files.concat(images)))
await flushPromises(dropzone)
expect(onDropRejected).toHaveBeenCalledWith(files, expectedEvent)
})

Expand All @@ -1349,6 +1368,7 @@ describe('Dropzone', () => {
</Dropzone>
)
await dropzone.simulate('drop', createDtWithFiles(files))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith([], files, expectedEvent)
expect(onDropAccepted).not.toHaveBeenCalled()
expect(onDropRejected).toHaveBeenCalledWith(files, expectedEvent)
Expand All @@ -1371,6 +1391,7 @@ describe('Dropzone', () => {
)

await dropzone.simulate('drop', createDtWithFiles(images))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith(images, [], expectedEvent)
expect(onDropAccepted).toHaveBeenCalledWith(images, expectedEvent)
expect(onDropRejected).not.toHaveBeenCalled()
Expand All @@ -1394,6 +1415,7 @@ describe('Dropzone', () => {
const bogusImages = [createFile('bogus.gif', 1234, 'application/x-moz-file')]

await dropzone.simulate('drop', createDtWithFiles(bogusImages))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith(bogusImages, [], expectedEvent)
expect(onDropAccepted).toHaveBeenCalledWith(bogusImages, expectedEvent)
expect(onDropRejected).not.toHaveBeenCalled()
Expand All @@ -1410,6 +1432,7 @@ describe('Dropzone', () => {
</Dropzone>
)
await dropzone.simulate('drop', createDtWithFiles(files.concat(images)))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith(files.concat(images), [], expectedEvent)
expect(onDropAccepted).toHaveBeenCalledWith(files.concat(images), expectedEvent)
expect(onDropRejected).not.toHaveBeenCalled()
Expand All @@ -1432,6 +1455,7 @@ describe('Dropzone', () => {
)

await dropzone.simulate('drop', createDtWithFiles(files))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith(files, [], expectedEvent)
expect(onDropAccepted).toHaveBeenCalledWith(files, expectedEvent)
expect(onDropRejected).not.toHaveBeenCalled()
Expand All @@ -1453,6 +1477,7 @@ describe('Dropzone', () => {
</Dropzone>
)
await dropzone.simulate('drop', createDtWithFiles(images))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith([], images, expectedEvent)
expect(onDropAccepted).not.toHaveBeenCalled()
expect(onDropRejected).toHaveBeenCalledWith(images, expectedEvent)
Expand All @@ -1474,6 +1499,7 @@ describe('Dropzone', () => {
</Dropzone>
)
await dropzone.simulate('drop', createDtWithFiles(files))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith([], files, expectedEvent)
expect(onDropAccepted).not.toHaveBeenCalled()
expect(onDropRejected).toHaveBeenCalledWith(files, expectedEvent)
Expand All @@ -1495,6 +1521,7 @@ describe('Dropzone', () => {
</Dropzone>
)
await dropzone.simulate('drop', createDtWithFiles(images))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith(images, [], expectedEvent)
expect(onDropAccepted).toHaveBeenCalledWith(images, expectedEvent)
expect(onDropRejected).not.toHaveBeenCalled()
Expand All @@ -1511,6 +1538,7 @@ describe('Dropzone', () => {
</Dropzone>
)
await dropzone.simulate('drop', createDtWithFiles(files.concat(images)))
await flushPromises(dropzone)
expect(onDrop).toHaveBeenCalledWith(files.concat(images), [], expectedEvent)
expect(onDropAccepted).toHaveBeenCalledWith(files.concat(images), expectedEvent)
expect(onDropRejected).not.toHaveBeenCalled()
Expand Down
22 changes: 0 additions & 22 deletions src/utils/index.js
Expand Up @@ -5,28 +5,6 @@ export const supportMultiple =
? 'multiple' in document.createElement('input')
: true

export function getDataTransferItems(event) {
let dataTransferItemsList = []
if (event.dataTransfer) {
const dt = event.dataTransfer

// NOTE: Only the 'drop' event has access to DataTransfer.files,
// otherwise it will always be empty
if (dt.files && dt.files.length) {
dataTransferItemsList = dt.files
} else if (dt.items && dt.items.length) {
// During the drag even the dataTransfer.files is null
// but Chrome implements some drag store, which is accesible via dataTransfer.items
dataTransferItemsList = dt.items
}
} else if (event.target && event.target.files) {
dataTransferItemsList = event.target.files
}

// Convert from DataTransferItemsList to the native Array
return Array.prototype.slice.call(dataTransferItemsList)
}

// Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with
// that MIME type will always be accepted
export function fileAccepted(file, accept) {
Expand Down

0 comments on commit 2fc6e06

Please sign in to comment.