Socket
Socket
Sign inDemoInstall

react-dropzone

Package Overview
Dependencies
Maintainers
2
Versions
176
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-dropzone - npm Package Compare versions

Comparing version 9.0.0 to 10.0.0

.babelrc.js

1090

dist/es/index.js

@@ -1,573 +0,711 @@

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; }
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest(); }
/* eslint prefer-template: 0 */
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance"); }
import React from 'react';
import { fromEvent } from 'file-selector';
import PropTypes from 'prop-types';
import { isDragDataWithFiles, supportMultiple, fileAccepted, allFilesAccepted, fileMatchSize, onDocumentDragOver, isIeOrEdge, composeEventHandlers, isPropagationStopped, isDefaultPrevented } from './utils';
function _iterableToArrayLimit(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
var Dropzone = function (_React$Component) {
_inherits(Dropzone, _React$Component);
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
function Dropzone() {
var _ref;
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
var _temp, _this, _ret;
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
_classCallCheck(this, Dropzone);
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
/* eslint prefer-template: 0 */
import React, { Fragment, useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { fromEvent } from 'file-selector';
import { allFilesAccepted, composeEventHandlers, fileAccepted, fileMatchSize, isEvtWithFiles, isIeOrEdge, isPropagationStopped, onDocumentDragOver, supportMultiple } from './utils/index';
/**
* Convenience wrapper component for the `useDropzone` hook
*
* ```jsx
* <Dropzone>
* {({getRootProps, getInputProps}) => (
* <div {...getRootProps()}>
* <input {...getInputProps()} />
* <p>Drag 'n' drop some files here, or click to select files</p>
* </div>
* )}
* </Dropzone>
* ```
*/
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Dropzone.__proto__ || Object.getPrototypeOf(Dropzone)).call.apply(_ref, [this].concat(args))), _this), _this.state = {
draggedFiles: [],
acceptedFiles: [],
rejectedFiles: []
}, _this.isFileDialogActive = false, _this.onDocumentDrop = function (evt) {
if (_this.node && _this.node.contains(evt.target)) {
// if we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
return;
}
evt.preventDefault();
_this.dragTargets = [];
}, _this.onDragStart = function (evt) {
evt.persist();
if (_this.props.onDragStart && isDragDataWithFiles(evt)) {
_this.props.onDragStart.call(_this, evt);
}
}, _this.onDragEnter = function (evt) {
evt.preventDefault();
function Dropzone(_ref) {
var children = _ref.children,
params = _objectWithoutProperties(_ref, ["children"]);
// Count the dropzone and any children that are entered.
if (_this.dragTargets.indexOf(evt.target) === -1) {
_this.dragTargets.push(evt.target);
}
var _useDropzone = useDropzone(params),
props = _extends({}, _useDropzone); // TODO: Figure out why react-styleguidist cannot create docs if we don't return a jsx element
evt.persist();
if (isDragDataWithFiles(evt)) {
Promise.resolve(_this.props.getDataTransferItems(evt)).then(function (draggedFiles) {
if (isPropagationStopped(evt)) {
return;
}
return React.createElement(Fragment, null, children(props));
}
_this.setState({
draggedFiles: draggedFiles,
// Do not rely on files for the drag state. It doesn't work in Safari.
isDragActive: true
});
});
Dropzone.propTypes = {
/**
* Render function that exposes the dropzone state and prop getter fns
*
* @param {object} params
* @param {Function} params.getRootProps Returns the props you should apply to the root drop container you render
* @param {Function} params.getInputProps Returns the props you should apply to hidden file input you render
* @param {Function} params.open Open the native file selection dialog
* @param {boolean} params.isFocused Dropzone area is in focus
* @param {boolean} params.isFileDialogActive File dialog is opened
* @param {boolean} params.isDragActive Active drag is in progress
* @param {boolean} params.isDragAccept Dragged files are accepted
* @param {boolean} params.isDragReject Some dragged files are rejected
* @param {File[]} params.draggedFiles Files in active drag
* @param {File[]} params.acceptedFiles Accepted files
* @param {File[]} params.rejectedFiles Rejected files
*/
children: PropTypes.func,
if (_this.props.onDragEnter) {
_this.props.onDragEnter.call(_this, evt);
}
}
}, _this.onDragOver = function (evt) {
// eslint-disable-line class-methods-use-this
evt.preventDefault();
evt.persist();
/**
* Set accepted file types.
* See https://github.com/okonet/attr-accept for more information.
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
* Windows. In some cases there might not be a mime type set at all.
* See: https://github.com/react-dropzone/react-dropzone/issues/276
*/
accept: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
if (evt.dataTransfer) {
evt.dataTransfer.dropEffect = 'copy';
}
/**
* Allow drag 'n' drop (or selection from the file dialog) of multiple files
*/
multiple: PropTypes.bool,
if (_this.props.onDragOver && isDragDataWithFiles(evt)) {
_this.props.onDragOver.call(_this, evt);
}
/**
* If false, allow dropped items to take over the current browser window
*/
preventDropOnDocument: PropTypes.bool,
return false;
}, _this.onDragLeave = function (evt) {
evt.preventDefault();
evt.persist();
/**
* Minimum file size (in bytes)
*/
minSize: PropTypes.number,
// Only deactivate once the dropzone and all children have been left.
_this.dragTargets = _this.dragTargets.filter(function (el) {
return el !== evt.target && _this.node.contains(el);
});
if (_this.dragTargets.length > 0) {
return;
}
/**
* Maximum file size (in bytes)
*/
maxSize: PropTypes.number,
// Clear dragging files state
_this.setState({
isDragActive: false,
draggedFiles: []
});
/**
* Enable/disable the dropzone
*/
disabled: PropTypes.bool,
if (_this.props.onDragLeave && isDragDataWithFiles(evt)) {
_this.props.onDragLeave.call(_this, evt);
}
}, _this.onDrop = function (evt) {
var _this$props = _this.props,
onDrop = _this$props.onDrop,
onDropAccepted = _this$props.onDropAccepted,
onDropRejected = _this$props.onDropRejected,
multiple = _this$props.multiple,
accept = _this$props.accept,
getDataTransferItems = _this$props.getDataTransferItems;
/**
* Use this to provide a custom file aggregator
*
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
getFilesFromEvent: PropTypes.func,
// Stop default browser behavior
/**
* Cb for when closing the file dialog with no selection
*/
onFileDialogCancel: PropTypes.func,
evt.preventDefault();
/**
* Cb for when the `dragenter` event occurs.
*
* @param {DragEvent} event
*/
onDragEnter: PropTypes.func,
// Persist event for later usage
evt.persist();
/**
* Cb for when the `dragleave` event occurs
*
* @param {DragEvent} event
*/
onDragLeave: PropTypes.func,
// Reset the counter along with the drag on a drop.
_this.dragTargets = [];
_this.isFileDialogActive = false;
/**
* Cb for when the `dragover` event occurs
*
* @param {DragEvent} event
*/
onDragOver: PropTypes.func,
// Clear files value
_this.draggedFiles = null;
/**
* Cb for when the `drop` event occurs.
* Note that this callback is invoked after the `getFilesFromEvent` callback is done.
*
* Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
* `accept` must be a valid [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml) according to [input element specification](https://www.w3.org/wiki/HTML/Elements/input/file) or a valid file extension.
* If `multiple` is set to false and additional files are droppped,
* all files besides the first will be rejected.
* Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
*
* Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
* If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
*
* `onDrop` will provide you with an array of [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects which you can then process and send to a server.
* For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
*
* ```js
* function onDrop(acceptedFiles) {
* const req = request.post('/upload')
* acceptedFiles.forEach(file => {
* req.attach(file.name, file)
* })
* req.end(callback)
* }
* ```
*
* @param {File[]} acceptedFiles
* @param {File[]} rejectedFiles
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
onDrop: PropTypes.func,
// Reset drag state
_this.setState({
isDragActive: false,
draggedFiles: []
});
/**
* Cb for when the `drop` event occurs.
* Note that if no files are accepted, this callback is not invoked.
*
* @param {File[]} files
* @param {(DragEvent|Event)} event
*/
onDropAccepted: PropTypes.func,
if (isDragDataWithFiles(evt)) {
Promise.resolve(getDataTransferItems(evt)).then(function (fileList) {
var acceptedFiles = [];
var rejectedFiles = [];
/**
* Cb for when the `drop` event occurs.
* Note that if no files are rejected, this callback is not invoked.
*
* @param {object[]} files
* @param {(DragEvent|Event)} event
*/
onDropRejected: PropTypes.func
};
export default Dropzone;
/**
* A function that is invoked for the `dragenter`,
* `dragover` and `dragleave` events.
* It is not invoked if the items are not files (such as link, text, etc.).
*
* @callback dragCb
* @param {DragEvent} event
*/
if (isPropagationStopped(evt)) {
return;
}
/**
* A function that is invoked for the `drop` or input change event.
* It is not invoked if the items are not files (such as link, text, etc.).
*
* @callback dropCb
* @param {File[]} acceptedFiles List of accepted files
* @param {File[]} rejectedFiles List of rejected files
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
fileList.forEach(function (file) {
if (fileAccepted(file, accept) && fileMatchSize(file, _this.props.maxSize, _this.props.minSize)) {
acceptedFiles.push(file);
} else {
rejectedFiles.push(file);
}
});
/**
* A function that is invoked for the `drop` or input change event.
* It is not invoked if the items are files (such as link, text, etc.).
*
* @callback dropAcceptedCb
* @param {File[]} files List of accepted files that meet the given criteria
* (`accept`, `multiple`, `minSize`, `maxSize`)
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
if (!multiple && acceptedFiles.length > 1) {
// if not in multi mode add any extra accepted files to rejected.
// This will allow end users to easily ignore a multi file drop in "single" mode.
rejectedFiles.push.apply(rejectedFiles, _toConsumableArray(acceptedFiles.splice(0)));
}
/**
* A function that is invoked for the `drop` or input change event.
*
* @callback dropRejectedCb
* @param {File[]} files List of rejected files that do not meet the given criteria
* (`accept`, `multiple`, `minSize`, `maxSize`)
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
// Update `acceptedFiles` and `rejectedFiles` state
// This will make children render functions receive the appropriate
// values
_this.setState({ acceptedFiles: acceptedFiles, rejectedFiles: rejectedFiles }, function () {
if (onDrop) {
onDrop.call(_this, acceptedFiles, rejectedFiles, evt);
}
/**
* A function that is used aggregate files,
* in a asynchronous fashion, from drag or input change events.
*
* @callback getFilesFromEvent
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
* @returns {(File[]|Promise<File[]>)}
*/
if (rejectedFiles.length > 0 && onDropRejected) {
onDropRejected.call(_this, rejectedFiles, evt);
}
/**
* An object with the current dropzone state and some helper functions.
*
* @typedef {object} DropzoneState
* @property {Function} getRootProps Returns the props you should apply to the root drop container you render
* @property {Function} getInputProps Returns the props you should apply to hidden file input you render
* @property {Function} open Open the native file selection dialog
* @property {boolean} isFocused Dropzone area is in focus
* @property {boolean} isFileDialogActive File dialog is opened
* @property {boolean} isDragActive Active drag is in progress
* @property {boolean} isDragAccept Dragged files are accepted
* @property {boolean} isDragReject Some dragged files are rejected
* @property {File[]} draggedFiles Files in active drag
* @property {File[]} acceptedFiles Accepted files
* @property {File[]} rejectedFiles Rejected files
*/
if (acceptedFiles.length > 0 && onDropAccepted) {
onDropAccepted.call(_this, acceptedFiles, evt);
}
});
});
}
}, _this.onClick = function (evt) {
var onClick = _this.props.onClick;
var initialState = {
isFocused: false,
isFileDialogActive: false,
isDragActive: false,
isDragAccept: false,
isDragReject: false,
draggedFiles: [],
acceptedFiles: [],
rejectedFiles: []
/**
* A React hook that creates a drag 'n' drop area.
*
* ```jsx
* function MyDropzone(props) {
* const {getRootProps, getInputProps} = useDropzone({
* onDrop: acceptedFiles => {
* // do something with the File objects, e.g. upload to some server
* }
* });
* return (
* <div {...getRootProps()}>
* <input {...getInputProps()} />
* <p>Drag and drop some files here, or click to select files</p>
* </div>
* )
* }
* ```
*
* @function useDropzone
*
* @param {object} props
* @param {string|string[]} [props.accept] Set accepted file types.
* See https://github.com/okonet/attr-accept for more information.
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
* Windows. In some cases there might not be a mime type set at all.
* See: https://github.com/react-dropzone/react-dropzone/issues/276
* @param {boolean} [props.multiple=true] Allow drag 'n' drop (or selection from the file dialog) of multiple files
* @param {boolean} [props.preventDropOnDocument=true] If false, allow dropped items to take over the current browser window
* @param {number} [props.minSize=0] Minimum file size (in bytes)
* @param {number} [props.maxSize=Infinity] Maximum file size (in bytes)
* @param {boolean} [props.disabled=false] Enable/disable the dropzone
* @param {getFilesFromEvent} [props.getFilesFromEvent] Use this to provide a custom file aggregator
* @param {Function} [props.onFileDialogCancel] Cb for when closing the file dialog with no selection
* @param {dragCb} [props.onDragEnter] Cb for when the `dragenter` event occurs.
* @param {dragCb} [props.onDragLeave] Cb for when the `dragleave` event occurs
* @param {dragCb} [props.onDragOver] Cb for when the `dragover` event occurs
* @param {dropCb} [props.onDrop] Cb for when the `drop` event occurs.
* Note that this callback is invoked after the `getFilesFromEvent` callback is done.
*
* Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
* `accept` must be a valid [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml) according to [input element specification](https://www.w3.org/wiki/HTML/Elements/input/file) or a valid file extension.
* If `multiple` is set to false and additional files are droppped,
* all files besides the first will be rejected.
* Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
*
* Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
* If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
*
* `onDrop` will provide you with an array of [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects which you can then process and send to a server.
* For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
*
* ```js
* function onDrop(acceptedFiles) {
* const req = request.post('/upload')
* acceptedFiles.forEach(file => {
* req.attach(file.name, file)
* })
* req.end(callback)
* }
* ```
* @param {dropAcceptedCb} [props.onDropAccepted]
* @param {dropRejectedCb} [props.onDropRejected]
*
* @returns {DropzoneState}
*/
// if onClick prop is given, run it first
};
export function useDropzone() {
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
accept = _ref2.accept,
_ref2$disabled = _ref2.disabled,
disabled = _ref2$disabled === void 0 ? false : _ref2$disabled,
_ref2$getFilesFromEve = _ref2.getFilesFromEvent,
getFilesFromEvent = _ref2$getFilesFromEve === void 0 ? fromEvent : _ref2$getFilesFromEve,
_ref2$maxSize = _ref2.maxSize,
maxSize = _ref2$maxSize === void 0 ? Infinity : _ref2$maxSize,
_ref2$minSize = _ref2.minSize,
minSize = _ref2$minSize === void 0 ? 0 : _ref2$minSize,
_ref2$multiple = _ref2.multiple,
multiple = _ref2$multiple === void 0 ? true : _ref2$multiple,
onDragEnter = _ref2.onDragEnter,
onDragLeave = _ref2.onDragLeave,
onDragOver = _ref2.onDragOver,
onDrop = _ref2.onDrop,
onDropAccepted = _ref2.onDropAccepted,
onDropRejected = _ref2.onDropRejected,
onFileDialogCancel = _ref2.onFileDialogCancel,
_ref2$preventDropOnDo = _ref2.preventDropOnDocument,
preventDropOnDocument = _ref2$preventDropOnDo === void 0 ? true : _ref2$preventDropOnDo;
if (onClick) {
onClick.call(_this, evt);
}
var rootRef = useRef(null);
var inputRef = useRef(null);
// If the event hasn't been default prevented from within
// the onClick listener, open the file dialog
if (!isDefaultPrevented(evt)) {
evt.stopPropagation();
var _useReducer = useReducer(reducer, initialState),
_useReducer2 = _slicedToArray(_useReducer, 2),
state = _useReducer2[0],
dispatch = _useReducer2[1];
// in IE11/Edge the file-browser dialog is blocking, ensure this is behind setTimeout
// this is so react can handle state changes in the onClick prop above above
// see: https://github.com/react-dropzone/react-dropzone/issues/450
if (isIeOrEdge()) {
setTimeout(_this.open, 0);
} else {
_this.open();
}
}
}, _this.onInputElementClick = function (evt) {
evt.stopPropagation();
}, _this.onFileDialogCancel = function () {
// timeout will not recognize context of this method
var onFileDialogCancel = _this.props.onFileDialogCancel;
// execute the timeout only if the FileDialog is opened in the browser
var isFocused = state.isFocused,
isFileDialogActive = state.isFileDialogActive,
draggedFiles = state.draggedFiles; // Fn for opening the file dialog programmatically
if (_this.isFileDialogActive) {
setTimeout(function () {
if (_this.input != null) {
// Returns an object as FileList
var files = _this.input.files;
var openFileDialog = function openFileDialog() {
if (inputRef.current) {
dispatch({
type: 'openDialog'
});
inputRef.current.value = null;
inputRef.current.click();
}
}; // Update file dialog active state when the window is focused on
if (!files.length) {
_this.isFileDialogActive = false;
var onWindowFocus = function onWindowFocus() {
// Execute the timeout only if the file dialog is opened in the browser
if (isFileDialogActive) {
setTimeout(function () {
if (inputRef.current) {
var files = inputRef.current.files;
if (typeof onFileDialogCancel === 'function') {
onFileDialogCancel();
}
if (!files.length) {
dispatch({
type: 'closeDialog'
});
if (typeof onFileDialogCancel === 'function') {
onFileDialogCancel();
}
}
}, 300);
}
}, _this.onFocus = function (evt) {
var onFocus = _this.props.onFocus;
}
}, 300);
}
};
if (onFocus) {
onFocus.call(_this, evt);
}
if (!isDefaultPrevented(evt)) {
_this.setState({ isFocused: true });
}
}, _this.onBlur = function (evt) {
var onBlur = _this.props.onBlur;
useEffect(function () {
window.addEventListener('focus', onWindowFocus, false);
return function () {
window.removeEventListener('focus', onWindowFocus, false);
};
}, [inputRef, isFileDialogActive, onFileDialogCancel]); // Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
if (onBlur) {
onBlur.call(_this, evt);
}
if (!isDefaultPrevented(evt)) {
_this.setState({ isFocused: false });
}
}, _this.onKeyDown = function (evt) {
var onKeyDown = _this.props.onKeyDown;
var onKeyDownCb = useCallback(function (event) {
// Ignore keyboard events bubbling up the DOM tree
if (!rootRef.current || !rootRef.current.isEqualNode(event.target)) {
return;
}
if (!_this.node.isEqualNode(evt.target)) {
return;
}
if (event.keyCode === 32 || event.keyCode === 13) {
event.preventDefault();
openFileDialog();
}
}, [rootRef, inputRef]); // Update focus state for the dropzone
if (onKeyDown) {
onKeyDown.call(_this, evt);
}
var onFocusCb = useCallback(function () {
dispatch({
type: 'focus'
});
}, []);
var onBlurCb = useCallback(function () {
dispatch({
type: 'blur'
});
}, []); // Cb to open the file dialog when click occurs on the dropzone
if (!isDefaultPrevented(evt) && (evt.keyCode === 32 || evt.keyCode === 13)) {
evt.preventDefault();
_this.open();
}
}, _this.composeHandler = function (handler) {
if (_this.props.disabled) {
return null;
}
return handler;
}, _this.getRootProps = function () {
var _extends2;
var onClickCb = useCallback(function () {
// In IE11/Edge the file-browser dialog is blocking, therefore, use setTimeout()
// to ensure React can handle state changes
// See: https://github.com/react-dropzone/react-dropzone/issues/450
if (isIeOrEdge()) {
setTimeout(openFileDialog, 0);
} else {
openFileDialog();
}
}, [inputRef]);
var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
var _useState = useState([]),
_useState2 = _slicedToArray(_useState, 2),
dragTargets = _useState2[0],
setDragTargets = _useState2[1];
var _ref2$refKey = _ref2.refKey,
refKey = _ref2$refKey === undefined ? 'ref' : _ref2$refKey,
onKeyDown = _ref2.onKeyDown,
onFocus = _ref2.onFocus,
onBlur = _ref2.onBlur,
onClick = _ref2.onClick,
onDragStart = _ref2.onDragStart,
onDragEnter = _ref2.onDragEnter,
onDragOver = _ref2.onDragOver,
onDragLeave = _ref2.onDragLeave,
onDrop = _ref2.onDrop,
rest = _objectWithoutProperties(_ref2, ['refKey', 'onKeyDown', 'onFocus', 'onBlur', 'onClick', 'onDragStart', 'onDragEnter', 'onDragOver', 'onDragLeave', 'onDrop']);
var onDocumentDrop = function onDocumentDrop(event) {
if (rootRef.current && rootRef.current.contains(event.target)) {
// If we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
return;
}
return _extends((_extends2 = {
onKeyDown: _this.composeHandler(onKeyDown ? composeEventHandlers(onKeyDown, _this.onKeyDown) : _this.onKeyDown),
onFocus: _this.composeHandler(onFocus ? composeEventHandlers(onFocus, _this.onFocus) : _this.onFocus),
onBlur: _this.composeHandler(onBlur ? composeEventHandlers(onBlur, _this.onBlur) : _this.onBlur),
onClick: _this.composeHandler(onClick ? composeEventHandlers(onClick, _this.onClick) : _this.onClick),
onDragStart: _this.composeHandler(onDragStart ? composeEventHandlers(onDragStart, _this.onDragStart) : _this.onDragStart),
onDragEnter: _this.composeHandler(onDragEnter ? composeEventHandlers(onDragEnter, _this.onDragEnter) : _this.onDragEnter),
onDragOver: _this.composeHandler(onDragOver ? composeEventHandlers(onDragOver, _this.onDragOver) : _this.onDragOver),
onDragLeave: _this.composeHandler(onDragLeave ? composeEventHandlers(onDragLeave, _this.onDragLeave) : _this.onDragLeave),
onDrop: _this.composeHandler(onDrop ? composeEventHandlers(onDrop, _this.onDrop) : _this.onDrop)
}, _defineProperty(_extends2, refKey, _this.setNodeRef), _defineProperty(_extends2, 'tabIndex', _this.props.disabled ? -1 : 0), _extends2), rest);
}, _this.getInputProps = function () {
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
event.preventDefault();
setDragTargets([]);
};
var _ref3$refKey = _ref3.refKey,
refKey = _ref3$refKey === undefined ? 'ref' : _ref3$refKey,
onChange = _ref3.onChange,
onClick = _ref3.onClick,
rest = _objectWithoutProperties(_ref3, ['refKey', 'onChange', 'onClick']);
useEffect(function () {
if (preventDropOnDocument) {
document.addEventListener('dragover', onDocumentDragOver, false);
document.addEventListener('drop', onDocumentDrop, false);
}
var _this$props2 = _this.props,
accept = _this$props2.accept,
multiple = _this$props2.multiple,
name = _this$props2.name;
var inputProps = _defineProperty({
accept: accept,
type: 'file',
style: { display: 'none' },
multiple: supportMultiple && multiple,
onChange: composeEventHandlers(onChange, _this.onDrop),
onClick: composeEventHandlers(onClick, _this.onInputElementClick),
autoComplete: 'off',
tabIndex: -1
}, refKey, _this.setInputRef);
if (name && name.length) {
inputProps.name = name;
return function () {
if (preventDropOnDocument) {
document.removeEventListener('dragover', onDocumentDragOver);
document.removeEventListener('drop', onDocumentDrop);
}
return _extends({}, inputProps, rest);
}, _this.setNodeRef = function (node) {
_this.node = node;
}, _this.setInputRef = function (input) {
_this.input = input;
}, _this.open = function () {
_this.isFileDialogActive = true;
if (_this.input) {
_this.input.value = null;
_this.input.click();
}
}, _temp), _possibleConstructorReturn(_this, _ret);
}
};
}, [rootRef, preventDropOnDocument]);
var onDragEnterCb = useCallback(function (event) {
event.preventDefault(); // Persist here because we need the event later after getFilesFromEvent() is done
_createClass(Dropzone, [{
key: 'componentDidMount',
value: function componentDidMount() {
var preventDropOnDocument = this.props.preventDropOnDocument;
event.persist(); // Count the dropzone and any children that are entered.
this.dragTargets = [];
if (dragTargets.indexOf(event.target) === -1) {
setDragTargets([].concat(_toConsumableArray(dragTargets), [event.target]));
}
if (preventDropOnDocument) {
document.addEventListener('dragover', onDocumentDragOver, false);
document.addEventListener('drop', this.onDocumentDrop, false);
}
if (isEvtWithFiles(event)) {
Promise.resolve(getFilesFromEvent(event)).then(function (draggedFiles) {
if (isPropagationStopped(event)) {
return;
}
window.addEventListener('focus', this.onFileDialogCancel, false);
dispatch({
draggedFiles: draggedFiles,
type: 'setDraggedFiles'
});
if (onDragEnter) {
onDragEnter(event);
}
});
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
var preventDropOnDocument = this.props.preventDropOnDocument;
}, [dragTargets, getFilesFromEvent, onDragEnter]);
var onDragOverCb = useCallback(function (event) {
event.preventDefault();
event.persist();
if (preventDropOnDocument) {
document.removeEventListener('dragover', onDocumentDragOver);
document.removeEventListener('drop', this.onDocumentDrop);
}
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'copy';
}
window.removeEventListener('focus', this.onFileDialogCancel, false);
if (isEvtWithFiles(event) && onDragOver) {
onDragOver(event);
}
/**
* Open system file upload dialog.
*
* @public
*/
return false;
}, [onDragOver]);
var onDragLeaveCb = useCallback(function (event) {
event.preventDefault();
event.persist(); // Only deactivate once the dropzone and all children have been left
}, {
key: 'render',
value: function render() {
var _props = this.props,
children = _props.children,
multiple = _props.multiple,
disabled = _props.disabled;
var _state = this.state,
isDragActive = _state.isDragActive,
isFocused = _state.isFocused,
draggedFiles = _state.draggedFiles,
acceptedFiles = _state.acceptedFiles,
rejectedFiles = _state.rejectedFiles;
var targets = _toConsumableArray(dragTargets.filter(function (target) {
return target !== event.target && rootRef.current && rootRef.current.contains(target);
}));
setDragTargets(targets);
var filesCount = draggedFiles.length;
var isMultipleAllowed = multiple || filesCount <= 1;
var isDragAccept = filesCount > 0 && allFilesAccepted(draggedFiles, this.props.accept);
var isDragReject = filesCount > 0 && (!isDragAccept || !isMultipleAllowed);
return children({
isDragActive: isDragActive,
isDragAccept: isDragAccept,
isDragReject: isDragReject,
draggedFiles: draggedFiles,
acceptedFiles: acceptedFiles,
rejectedFiles: rejectedFiles,
isFocused: isFocused && !disabled,
getRootProps: this.getRootProps,
getInputProps: this.getInputProps,
open: this.open
});
if (targets.length > 0) {
return;
}
}]);
return Dropzone;
}(React.Component);
dispatch({
type: 'setDraggedFiles',
draggedFiles: []
});
export default Dropzone;
if (isEvtWithFiles(event) && onDragLeave) {
onDragLeave(event);
}
}, [rootRef, dragTargets, onDragLeave]);
var onDropCb = useCallback(function (event) {
event.preventDefault(); // Persist here because we need the event later after getFilesFromEvent() is done
Dropzone.propTypes = {
/**
* Allow specific types of files. See https://github.com/okonet/attr-accept for more information.
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
* Windows. In some cases there might not be a mime type set at all.
* See: https://github.com/react-dropzone/react-dropzone/issues/276
*/
accept: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
event.persist();
setDragTargets([]);
dispatch({
type: 'reset'
});
/**
* Render function that renders the actual component
*
* @param {Object} props
* @param {Function} props.getRootProps Returns the props you should apply to the root drop container you render
* @param {Function} props.getInputProps Returns the props you should apply to hidden file input you render
* @param {Function} props.open Open the native file selection dialog
* @param {Boolean} props.isFocused Dropzone area is in focus
* @param {Boolean} props.isDragActive Active drag is in progress
* @param {Boolean} props.isDragAccept Dragged files are accepted
* @param {Boolean} props.isDragReject Some dragged files are rejected
* @param {Array} props.draggedFiles Files in active drag
* @param {Array} props.acceptedFiles Accepted files
* @param {Array} props.rejectedFiles Rejected files
*/
children: PropTypes.func,
if (isEvtWithFiles(event)) {
Promise.resolve(getFilesFromEvent(event)).then(function (files) {
if (isPropagationStopped(event)) {
return;
}
/**
* Enable/disable the dropzone entirely
*/
disabled: PropTypes.bool,
var acceptedFiles = [];
var rejectedFiles = [];
files.forEach(function (file) {
if (fileAccepted(file, accept) && fileMatchSize(file, maxSize, minSize)) {
acceptedFiles.push(file);
} else {
rejectedFiles.push(file);
}
});
/**
* If false, allow dropped items to take over the current browser window
*/
preventDropOnDocument: PropTypes.bool,
if (!multiple && acceptedFiles.length > 1) {
rejectedFiles.push.apply(rejectedFiles, _toConsumableArray(acceptedFiles.splice(0))); // Reject everything and empty accepted files
}
/**
* Allow dropping multiple files
*/
multiple: PropTypes.bool,
dispatch({
acceptedFiles: acceptedFiles,
rejectedFiles: rejectedFiles,
type: 'setFiles'
});
/**
* `name` attribute for the input tag
*/
name: PropTypes.string,
if (onDrop) {
onDrop(acceptedFiles, rejectedFiles, event);
}
/**
* Maximum file size (in bytes)
*/
maxSize: PropTypes.number,
if (rejectedFiles.length > 0 && onDropRejected) {
onDropRejected(rejectedFiles, event);
}
/**
* Minimum file size (in bytes)
*/
minSize: PropTypes.number,
if (acceptedFiles.length > 0 && onDropAccepted) {
onDropAccepted(acceptedFiles, event);
}
});
}
}, [multiple, accept, minSize, maxSize, getFilesFromEvent, onDrop, onDropAccepted, onDropRejected]);
/**
* getDataTransferItems handler
* @param {Event} event
* @returns {Array} array of File objects
*/
getDataTransferItems: PropTypes.func,
var composeHandler = function composeHandler(fn) {
return disabled ? null : fn;
};
/**
* onClick callback
* @param {Event} event
*/
onClick: PropTypes.func,
var getRootProps = useMemo(function () {
return function () {
var _objectSpread2;
/**
* onFocus callback
*/
onFocus: PropTypes.func,
var _ref3 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref3$refKey = _ref3.refKey,
refKey = _ref3$refKey === void 0 ? 'ref' : _ref3$refKey,
onKeyDown = _ref3.onKeyDown,
onFocus = _ref3.onFocus,
onBlur = _ref3.onBlur,
onClick = _ref3.onClick,
onDragEnter = _ref3.onDragEnter,
onDragOver = _ref3.onDragOver,
onDragLeave = _ref3.onDragLeave,
onDrop = _ref3.onDrop,
rest = _objectWithoutProperties(_ref3, ["refKey", "onKeyDown", "onFocus", "onBlur", "onClick", "onDragEnter", "onDragOver", "onDragLeave", "onDrop"]);
/**
* onBlur callback
*/
onBlur: PropTypes.func,
return _objectSpread((_objectSpread2 = {
onKeyDown: composeHandler(composeEventHandlers(onKeyDown, onKeyDownCb)),
onFocus: composeHandler(composeEventHandlers(onFocus, onFocusCb)),
onBlur: composeHandler(composeEventHandlers(onBlur, onBlurCb)),
onClick: composeHandler(composeEventHandlers(onClick, onClickCb)),
onDragEnter: composeHandler(composeEventHandlers(onDragEnter, onDragEnterCb)),
onDragOver: composeHandler(composeEventHandlers(onDragOver, onDragOverCb)),
onDragLeave: composeHandler(composeEventHandlers(onDragLeave, onDragLeaveCb)),
onDrop: composeHandler(composeEventHandlers(onDrop, onDropCb))
}, _defineProperty(_objectSpread2, refKey, rootRef), _defineProperty(_objectSpread2, "tabIndex", disabled ? -1 : 0), _objectSpread2), rest);
};
}, [rootRef, onKeyDownCb, onFocusCb, onBlurCb, onClickCb, onDragEnterCb, onDragOverCb, onDragLeaveCb, onDropCb, disabled]);
var onInputElementClick = useCallback(function (event) {
event.stopPropagation();
}, []);
var getInputProps = useMemo(function () {
return function () {
var _ref4 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
_ref4$refKey = _ref4.refKey,
refKey = _ref4$refKey === void 0 ? 'ref' : _ref4$refKey,
onChange = _ref4.onChange,
onClick = _ref4.onClick,
rest = _objectWithoutProperties(_ref4, ["refKey", "onChange", "onClick"]);
/**
* onKeyDown callback
*/
onKeyDown: PropTypes.func,
var inputProps = _defineProperty({
accept: accept,
type: 'file',
style: {
display: 'none'
},
multiple: supportMultiple && multiple,
onChange: composeHandler(composeEventHandlers(onChange, onDropCb)),
onClick: composeHandler(composeEventHandlers(onClick, onInputElementClick)),
autoComplete: 'off',
tabIndex: -1
}, refKey, inputRef);
/**
* The `onDrop` method that accepts two arguments.
* The first argument represents the accepted files and the second argument the rejected files.
*
* ```javascript
* function onDrop(acceptedFiles, rejectedFiles) {
* // do stuff with files...
* }
* ```
*
* Files are accepted or rejected based on the `accept` prop.
* This must be a valid [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml) according to [input element specification](https://www.w3.org/wiki/HTML/Elements/input/file) or a valid file extension.
*
* Note that the `onDrop` callback will always be called regardless if the dropped files were accepted or rejected.
* You can use the `onDropAccepted`/`onDropRejected` props if you'd like to react to a specific event instead of the `onDrop` prop.
*
* The `onDrop` callback will provide you with an array of [Files](https://developer.mozilla.org/en-US/docs/Web/API/File) which you can then process and send to a server.
* For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
*
* ```javascript
* function onDrop(acceptedFiles) {
* const req = request.post('/upload')
* acceptedFiles.forEach(file => {
* req.attach(file.name, file)
* })
* req.end(callback)
* }
* ```
*/
onDrop: PropTypes.func,
return _objectSpread({}, inputProps, rest);
};
}, [inputRef, accept, multiple, onDropCb, disabled]);
var fileCount = draggedFiles.length;
var isMultipleAllowed = multiple || fileCount <= 1;
var isDragAccept = fileCount > 0 && allFilesAccepted(draggedFiles, accept);
var isDragReject = fileCount > 0 && (!isDragAccept || !isMultipleAllowed);
return _objectSpread({}, state, {
isDragAccept: isDragAccept,
isDragReject: isDragReject,
isFocused: isFocused && !disabled,
getRootProps: getRootProps,
getInputProps: getInputProps,
rootRef: rootRef,
inputRef: inputRef,
open: composeHandler(openFileDialog)
});
}
/**
* onDropAccepted callback
*/
onDropAccepted: PropTypes.func,
function reducer(state, action) {
/* istanbul ignore next */
switch (action.type) {
case 'focus':
return _objectSpread({}, state, {
isFocused: true
});
/**
* onDropRejected callback
*/
onDropRejected: PropTypes.func,
case 'blur':
return _objectSpread({}, state, {
isFocused: false
});
/**
* onDragStart callback
*/
onDragStart: PropTypes.func,
case 'openDialog':
return _objectSpread({}, state, {
isFileDialogActive: true
});
/**
* onDragEnter callback
*/
onDragEnter: PropTypes.func,
case 'closeDialog':
return _objectSpread({}, state, {
isFileDialogActive: false
});
/**
* onDragOver callback
*/
onDragOver: PropTypes.func,
case 'setDraggedFiles':
/* eslint no-case-declarations: 0 */
var draggedFiles = action.draggedFiles;
return _objectSpread({}, state, {
draggedFiles: draggedFiles,
isDragActive: draggedFiles.length > 0
});
/**
* onDragLeave callback
*/
onDragLeave: PropTypes.func,
case 'setFiles':
return _objectSpread({}, state, {
acceptedFiles: action.acceptedFiles,
rejectedFiles: action.rejectedFiles
});
/**
* Provide a callback on clicking the cancel button of the file dialog
*/
onFileDialogCancel: PropTypes.func
};
case 'reset':
return _objectSpread({}, state, {
isFileDialogActive: false,
isDragActive: false,
draggedFiles: []
});
Dropzone.defaultProps = {
preventDropOnDocument: true,
disabled: false,
multiple: true,
maxSize: Infinity,
minSize: 0,
getDataTransferItems: fromEvent
};
default:
return state;
}
}

@@ -1,17 +0,13 @@

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
import accepts from 'attr-accept';
export var supportMultiple = 'multiple' in document.createElement('input'); // 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 var supportMultiple = typeof document !== 'undefined' && document && document.createElement ? 'multiple' in document.createElement('input') : true;
// 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) {
return file.type === 'application/x-moz-file' || accepts(file, accept);
}
export function fileMatchSize(file, maxSize, minSize) {
return file.size <= maxSize && file.size >= minSize;
}
export function allFilesAccepted(files, accept) {

@@ -21,46 +17,32 @@ return files.every(function (file) {

});
}
} // React's synthetic events has event.isPropagationStopped,
// but to remain compatibility with other libs (Preact) fall back
// to check event.cancelBubble
// React's synthetic events has evt.isPropagationStopped,
// but to remain compatibility with other libs (Preact) fall back
// to check evt.cancelBubble
export function isPropagationStopped(evt) {
if (typeof evt.isPropagationStopped === 'function') {
return evt.isPropagationStopped();
} else if (typeof evt.cancelBubble !== 'undefined') {
return evt.cancelBubble;
export function isPropagationStopped(event) {
if (typeof event.isPropagationStopped === 'function') {
return event.isPropagationStopped();
} else if (typeof event.cancelBubble !== 'undefined') {
return event.cancelBubble;
}
return false;
}
// React's synthetic events has evt.isDefaultPrevented,
// but to remain compatibility with other libs (Preact) first
// check evt.defaultPrevented
export function isDefaultPrevented(evt) {
if (typeof evt.defaultPrevented !== 'undefined') {
return evt.defaultPrevented;
} else if (typeof evt.isDefaultPrevented === 'function') {
return evt.isDefaultPrevented();
}
return false;
}
export function isEvtWithFiles(event) {
if (!event.dataTransfer) {
return !!event.target && !!event.target.files;
} // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#file
export function isDragDataWithFiles(evt) {
if (!evt.dataTransfer) {
return true;
}
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#file
return Array.prototype.some.call(evt.dataTransfer.types, function (type) {
return Array.prototype.some.call(event.dataTransfer.types, function (type) {
return type === 'Files' || type === 'application/x-moz-file';
});
}
export function isKindFile(item) {
return (typeof item === 'undefined' ? 'undefined' : _typeof(item)) === 'object' && item !== null && item.kind === 'file';
}
return _typeof(item) === 'object' && item !== null && item.kind === 'file';
} // allow the entire document to be a drag target
// allow the entire document to be a drag target
export function onDocumentDragOver(evt) {
evt.preventDefault();
export function onDocumentDragOver(event) {
event.preventDefault();
}

@@ -78,15 +60,17 @@

var userAgent = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : window.navigator.userAgent;
return isIe(userAgent) || isEdge(userAgent);
}
/**
* This is intended to be used to compose event handlers
* They are executed in order until one of them calls `event.preventDefault()`.
* Not sure this is the best way to do this, but it seems legit.
* They are executed in order until one of them calls `event.isPropagationStopped()`.
* Note that the check is done on the first invoke too,
* meaning that if propagation was stopped before invoking the fns,
* no handlers will be executed.
*
* @param {Function} fns the event hanlder functions
* @return {Function} the event handler to add to an element
*/
export function composeEventHandlers() {
for (var _len = arguments.length, fns = Array(_len), _key = 0; _key < _len; _key++) {
for (var _len = arguments.length, fns = new Array(_len), _key = 0; _key < _len; _key++) {
fns[_key] = arguments[_key];

@@ -96,3 +80,3 @@ }

return function (event) {
for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];

@@ -102,6 +86,9 @@ }

return fns.some(function (fn) {
fn && fn.apply(undefined, [event].concat(args));
return event.defaultPrevented;
if (!isPropagationStopped(event) && fn) {
fn.apply(void 0, [event].concat(args));
}
return isPropagationStopped(event);
});
};
}

@@ -11,6 +11,7 @@ {

"build:umd": "cross-env NODE_ENV=es rollup -c",
"build:es": "cross-env BABEL_ENV=es babel ./src --out-dir ./dist/es --ignore spec.js",
"build:es": "cross-env BABEL_ENV=es babel ./src --out-dir ./dist/es --ignore '**/*.spec.js'",
"start": "styleguidist server",
"styleguide": "styleguidist build",
"test": "npm run eslint:src && jest --coverage && npm run typescript",
"test": "cross-env NODE_ENV=test npm run eslint:src && jest --coverage && npm run typescript",
"test:watch": "cross-env NODE_ENV=test jest --watch",
"eslint:src": "eslint .",

@@ -50,7 +51,14 @@ "commitmsg": "commitlint -e",

"jest": {
"setupTestFrameworkScriptFile": "<rootDir>/testSetup.js",
"clearMocks": true,
"setupFilesAfterEnv": [
"<rootDir>/testSetup.js"
],
"coveragePathIgnorePatterns": [
"/dist/",
"/node_modules/",
"<rootDir>/testSetup.js"
],
"testPathIgnorePatterns": [
"/node_modules/",
"/dist/"
]

@@ -79,15 +87,28 @@ },

"Tyler Waters <tyler.waters@gmail.com>",
"Rick Markins <rmarkins@gmail.com>"
"Rick Markins <rmarkins@gmail.com>",
"Roland Groza <rolandjitsu@gmail.com>"
],
"license": "MIT",
"peerDependencies": {
"react": ">=0.14.0"
"react": ">= 16.8"
},
"dependencies": {
"attr-accept": "^1.1.3",
"file-selector": "^0.1.8",
"prop-types": "^15.6.2",
"prop-types-extra": "^1.1.0"
"file-selector": "^0.1.10",
"prop-types": "^15.7.2"
},
"devDependencies": {
"@babel/cli": "^7.2.3",
"@babel/core": "^7.3.4",
"@babel/plugin-external-helpers": "^7.2.0",
"@babel/plugin-proposal-export-default-from": "^7.2.0",
"@babel/plugin-proposal-logical-assignment-operators": "^7.2.0",
"@babel/plugin-proposal-optional-chaining": "^7.2.0",
"@babel/plugin-proposal-pipeline-operator": "^7.3.2",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.2.0",
"@babel/plugin-proposal-do-expressions": "^7.2.0",
"@babel/plugin-transform-runtime": "^7.3.4",
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.0.0",
"@commitlint/cli": "^7.0.0",

@@ -97,18 +118,10 @@ "@commitlint/config-angular": "^7.0.1",

"@commitlint/prompt-cli": "^7.0.0",
"@types/react": "^16.4.6",
"@types/react-dom": "^16.0.4",
"babel-cli": "^6.9.0",
"babel-core": "^6.26.3",
"@types/react": "^16.8.3",
"@types/react-dom": "^16.8.1",
"babel-eslint": "*",
"babel-jest": "^23.4.2",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.3.13",
"babel-preset-stage-1": "^6.24.1",
"babel-register": "^6.9.0",
"babel-jest": "^24.1.0",
"babel-plugin-add-module-exports": "^1.0.0",
"babel-plugin-dynamic-import-node": "^2.2.0",
"commitizen": "^2.10.1",
"cross-env": "^5.2.0",
"enzyme": "^3.4.4",
"enzyme-adapter-react-16": "^1.2.0",
"eslint": "4.x",

@@ -123,28 +136,31 @@ "eslint-config-okonet": "^7.0.2",

"eslint-plugin-react": "7.x",
"eslint-plugin-react-hooks": "^1.0.2",
"husky": "^0.14.3",
"imagemin-cli": "^3.0.0",
"imagemin-pngquant": "^6.0.0",
"jest": "^23.5.0",
"jest-enzyme": "^6.0.3",
"jest": "^24.1.0",
"jest-dom": "^3.1.2",
"lint-staged": "^7.2.2",
"markdownlint-cli": "^0.13.0",
"prettier": "*",
"react": "^16.4.2",
"react-dom": "^16.4.2",
"react-styleguidist": "^7.2.3",
"react-test-renderer": "^16.4.2",
"react": "^16.8.2",
"react-dom": "^16.8.2",
"react-hooks-testing-library": "^0.3.4",
"react-styleguidist": "^9.0.1",
"react-test-renderer": "^16.8.2",
"react-testing-library": "^6.0.0",
"rimraf": "^2.5.2",
"rollup": "^0.58.2",
"rollup-plugin-babel": "^3.0.4",
"rollup-plugin-commonjs": "^9.1.3",
"rollup-plugin-node-resolve": "^3.3.0",
"rollup-plugin-uglify": "^3.0.0",
"rollup": "^1.3.0",
"rollup-plugin-babel": "^4.3.2",
"rollup-plugin-commonjs": "^9.2.1",
"rollup-plugin-node-resolve": "^4.0.1",
"rollup-plugin-uglify": "^6.0.2",
"size-limit": "^0.19.2",
"styled-components": "^4.1.2",
"webpack-blocks": "^1.0.0",
"webpack-blocks": "^2.0.0-rc",
"sinon": "^3.2.1",
"style-loader": "^0.18.2",
"tslint": "^5.9.1",
"typescript": "^2.8.1",
"webpack": "^3.5.5"
"typescript": "^3.2.4",
"webpack": "^4.29.5"
},

@@ -157,6 +173,6 @@ "typings": "typings/react-dropzone.d.ts",

},
"version": "9.0.0",
"version": "10.0.0",
"engines": {
"node": ">= 6"
"node": ">= 8"
}
}

@@ -5,13 +5,13 @@ ![react-dropzone logo](https://raw.githubusercontent.com/react-dropzone/react-dropzone/master/logo/logo.png)

[![npm](https://img.shields.io/npm/v/react-dropzone.svg)](https://www.npmjs.com/package/react-dropzone)
[![Build Status](https://travis-ci.org/react-dropzone/react-dropzone.svg?branch=master)](https://travis-ci.org/react-dropzone/react-dropzone)
[![codecov](https://codecov.io/gh/react-dropzone/react-dropzone/branch/master/graph/badge.svg)](https://codecov.io/gh/react-dropzone/react-dropzone)
[![OpenCollective](https://opencollective.com/react-dropzone/backers/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/react-dropzone/sponsors/badge.svg)](#sponsors)
[![npm](https://img.shields.io/npm/v/react-dropzone.svg?style=flat-square)](https://www.npmjs.com/package/react-dropzone)
[![Build Status](https://img.shields.io/travis/react-dropzone/react-dropzone/master.svg?style=flat-square)](https://travis-ci.org/react-dropzone/react-dropzone)
[![codecov](https://img.shields.io/codecov/c/gh/react-dropzone/react-dropzone/master.svg?style=flat-square)](https://codecov.io/gh/react-dropzone/react-dropzone)
[![Open Collective](https://img.shields.io/opencollective/backers/react-dropzone.svg?style=flat-square)](#backers)
[![Open Collective](https://img.shields.io/opencollective/sponsors/react-dropzone.svg?style=flat-square)](#sponsors)
Simple HTML5-compliant drag'n'drop zone for files built with React.js.
Simple React hook to create a HTML5-compliant drag'n'drop zone for files.
Documentation and examples: https://react-dropzone.js.org
Source code: https://github.com/react-dropzone/react-dropzone/
Documentation and examples at https://react-dropzone.js.org. Source code at https://github.com/react-dropzone/react-dropzone/.
## Installation

@@ -29,60 +29,107 @@

## Usage
You can either use the hook:
```javascript static
import React from 'react'
import classNames from 'classnames'
import Dropzone from 'react-dropzone'
```jsx static
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
class MyDropzone extends React.Component {
onDrop = (acceptedFiles, rejectedFiles) => {
// Do something with files
}
function MyDropzone() {
const onDrop = useCallback(acceptedFiles => {
// Do something with the files
}, [])
const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop})
render() {
return (
<Dropzone onDrop={this.onDrop}>
{({getRootProps, getInputProps, isDragActive}) => {
return (
<div
{...getRootProps()}
className={classNames('dropzone', {'dropzone--isActive': isDragActive})}
>
<input {...getInputProps()} />
{
isDragActive ?
<p>Drop files here...</p> :
<p>Try dropping some files here, or click to select files to upload.</p>
}
</div>
)
}}
</Dropzone>
);
}
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
{
isDragActive ?
<p>Drop the files here ...</p> :
<p>Drag 'n' drop some files here, or click to select files</p>
}
</div>
)
}
```
## Render Prop Function
Or the wrapper component for the hook:
```jsx static
import React from 'react'
import Dropzone from 'react-dropzone'
The render property function is what you use to render whatever you want to based on the state of `Dropzone`:
```jsx static
<Dropzone>
{({getRootProps}) => <div {...getRootProps()} />}
<Dropzone onDrop={acceptedFiles => console.log(acceptedFiles)}>
{({getRootProps, getInputProps}) => (
<section>
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
</section>
)}
</Dropzone>
```
### Prop Getters
See https://react-dropzone.netlify.com/#proptypes `{children}` for more info.
These functions are used to apply props to the elements that you render.
**Warning**: On most recent browsers versions, the files given by `onDrop` won't have properties `path` or `fullPath`, see [this SO question](https://stackoverflow.com/a/23005925/2275818) and [this issue](https://github.com/react-dropzone/react-dropzone/issues/477).
This gives you maximum flexibility to render what, when, and wherever you like. You call these on the element in question (for example: `<div {...getRootProps()} />`).
Furthermore, if you want to access file contents you have to use the [FileReader API](https://developer.mozilla.org/en-US/docs/Web/API/FileReader):
You should pass all your props to that function rather than applying them on the element yourself to avoid your props being overridden (or overriding the props returned).
E.g.
```jsx static
import React, {useCallback} from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const onDrop = useCallback(acceptedFiles => {
const reader = new FileReader()
reader.onabort = () => console.log('file reading was aborted')
reader.onerror = () => console.log('file reading has failed')
reader.onload = () => {
// Do whatever you want with the file contents
const binaryStr = reader.result
console.log(binaryStr)
}
acceptedFiles.forEach(file => reader.readAsBinaryString(file))
}, [])
const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop})
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
)
}
```
## Dropzone Props Getters
The dropzone property getters are just two functions that return objects with properties which you need to use to create the drag 'n' drop zone.
The root properties can be applied to whatever element you want, whereas the input properties must be applied to an `<input>`:
```jsx static
import React from 'react'
import {useDropzone} from 'react-dropzone'
function MyDropzone() {
const {getRootProps, getInputProps} = useDropzone()
return (
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
)
}
```
Note that whatever other props you want to add to the element where the props from `getRootProps()` are set, you should always pass them through that function rather than applying them on the element itself.
This is in order to avoid your props being overridden (or overriding the props returned by `getRootProps()`):
```jsx static
<div
{...getRootProps({
onClick: evt => console.log(event)
onClick: event => console.log(event)
})}

@@ -92,78 +139,124 @@ />

### State
See https://react-dropzone.netlify.com/#proptypes `{children}` for more info.
In the example above, the provided `{onClick}` handler will be invoked before the internal one, therefore, internal callbacks can be prevented by simply using [stopPropagation](https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation).
See [Events](https://react-dropzone.js.org#events) for more examples.
### Custom refKey
*Important*: if you ommit rendering an `<input>` and/or binding the props from `getInputProps()`, opening a file dialog will not be possible.
Both `getRootProps` and `getInputProps` accept custom `refKey` (defaulted to `ref`) as one of the attributes passed down in the parameter.
## Refs
```javascript static
const StyledDropArea = styled.div`
// Some styling here
Both `getRootProps` and `getInputProps` accept a custom `refKey` (defaults to `ref`) as one of the attributes passed down in the parameter.
This can be useful when the element you're trying to apply the props from either one of those fns does not expose a reference to the element, e.g.:
```jsx static
import React from 'react'
import {useDropzone} from 'react-dropzone'
import styled from 'styled-components'
const StyledDiv = styled.div`
// Some styling here
`
const Example = () => (
<Dropzone>
{({ getRootProps, getInputProps }) => (
<StyledDropArea {...getRootProps({ refKey: 'innerRef' })}>
<input {...getInputProps()} />
<p>Drop some files here</p>
</StyledDropArea>
)}
</Dropzone>
);
function Example() {
const {getRootProps, getInputProps} = useDropzone()
<StyledDiv {...getRootProps({ refKey: 'innerRef' })}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</StyledDiv>
}
```
**Warning**: On most recent browsers versions, the files given by `onDrop` won't have properties `path` or `fullPath`, see [this SO question](https://stackoverflow.com/a/23005925/2275818) and [this issue](https://github.com/react-dropzone/react-dropzone/issues/477).
If you want to access file content you have to use the [FileReader API](https://developer.mozilla.org/en-US/docs/Web/API/FileReader).
*Important*: do not set the `ref` prop on the elements where `getRootProps()`/`getInputProps()` props are set, instead, get the refs from the hook itself:
```javascript static
onDrop: acceptedFiles => {
acceptedFiles.forEach(file => {
const reader = new FileReader();
reader.onload = () => {
const fileAsBinaryString = reader.result;
// do whatever you want with the file content
};
reader.onabort = () => console.log('file reading was aborted');
reader.onerror = () => console.log('file reading has failed');
```jsx static
import React from 'react'
import {useDropzone} from 'react-dropzone'
reader.readAsBinaryString(file);
});
function Refs() {
const {
getRootProps,
getInputProps,
rootRef, // Ref to the `<div>`
inputRef // Ref to the `<input>`
} = useDropzone()
<div {...getRootProps()}>
<input {...getInputProps()} />
<p>Drag 'n' drop some files here, or click to select files</p>
</div>
}
```
## PropTypes
See https://react-dropzone.netlify.com/#proptypes
## Testing
### Testing
*Important*: `react-dropzone` makes its drag'n'drop callbacks asynchronous to enable promise based getDataTransfer functions. In order to properly test this, you may want to utilize a helper function to run all promises like this:
*Important*: `react-dropzone` makes some of its drag 'n' drop callbacks asynchronous to enable promise based `getFilesFromEvent()` functions. In order to properly test this, you may want to utilize a helper function to run all promises like this:
```js static
const flushPromises = () => new Promise(resolve => setImmediate(resolve));
const flushPromises = () => new Promise(resolve => setImmediate(resolve))
```
Example with enzyme 3:
Example with [react-testing-library](https://github.com/kentcdodds/react-testing-library):
```js static
it('tests drag state', async () => {
const flushPromises = () => new Promise(resolve => setImmediate(resolve));
const DummyChildComponent = () => null
const dropzone = mount(
<Dropzone>{props => <DummyChildComponent {...props} />}</Dropzone>
import React from 'react'
import Dropzone from 'react-dropzone'
import { fireEvent, render } from 'react-testing-library'
test('invoke onDragEnter when dragenter event occurs', async () => {
const file = new File([
JSON.stringify({ping: true})
], 'ping.json', { type: 'application/json' })
const data = mockData([file])
const onDragEnter = jest.fn()
const ui = (
<Dropzone onDragEnter={onDragEnter}>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps()}>
<input {...getInputProps()} />
</div>
)}
</Dropzone>
)
dropzone.simulate('dragEnter', {
dataTransfer: { files: files.concat(images) }
})
await flushPromises(dropzone)
dropzone.update()
const { container } = render(ui)
const dropzone = container.querySelector('div')
const child = dropzone.find(DummyChildComponent)
expect(child).toHaveProp('isDragActive', true)
expect(child).toHaveProp('isDragAccept', false)
expect(child).toHaveProp('isDragReject', true)
dispatchEvt(dropzone, 'dragenter', data)
await flushPromises(ui, container)
expect(onDragEnter).toHaveBeenCalled()
})
function flushPromises(ui, container) {
return new Promise(resolve =>
setImmediate(() => {
render(ui, { container })
resolve(container)
})
)
}
function dispatchEvt(node, type, data) {
const event = new Event(type, { bubbles: true })
Object.assign(event, data)
fireEvent(node, event)
}
function mockData(files) {
return {
dataTransfer: {
files,
items: files.map(file => ({
kind: 'file',
type: file.type,
getAsFile: () => file
})),
types: ['Files']
}
}
}
```
Remember to update your mounted component before asserting any props. A complete example for this can be found in `react-dropzone`s own [test suite](https://github.com/react-dropzone/react-dropzone/blob/master/src/index.spec.js).
*Note*: using [Enzyme](https://airbnb.io/enzyme) for testing is not supported at the moment, see [#2011](https://github.com/airbnb/enzyme/issues/2011).
More examples for this can be found in `react-dropzone`s own [test suites](https://github.com/react-dropzone/react-dropzone/blob/master/src/index.spec.js).
## Support

@@ -240,4 +333,5 @@

## License
MIT
const nodeResolve = require('rollup-plugin-node-resolve')
const commonjs = require('rollup-plugin-commonjs')
const babel = require('rollup-plugin-babel')
const uglify = require('rollup-plugin-uglify')
const { uglify } = require('rollup-plugin-uglify')

@@ -17,5 +17,6 @@ const umdGlobals = {

format: 'umd',
name: 'Dropzone',
name: 'reactDropzone',
globals: umdGlobals,
sourcemap: 'inline'
sourcemap: 'inline',
exports: 'named'
},

@@ -26,3 +27,3 @@ external: Object.keys(umdGlobals),

commonjs({ include: '**/node_modules/**' }),
babel({ exclude: '**/node_modules/**', plugins: ['external-helpers'] }),
babel({ exclude: '**/node_modules/**' }),
uglify()

@@ -29,0 +30,0 @@ ]

/* eslint prefer-template: 0 */
import React from 'react'
import React, {
Fragment,
useCallback,
useEffect,
useMemo,
useReducer,
useRef,
useState
} from 'react'
import PropTypes from 'prop-types'
import { fromEvent } from 'file-selector'
import PropTypes from 'prop-types'
import {
isDragDataWithFiles,
supportMultiple,
allFilesAccepted,
composeEventHandlers,
fileAccepted,
allFilesAccepted,
fileMatchSize,
onDocumentDragOver,
isEvtWithFiles,
isIeOrEdge,
composeEventHandlers,
isPropagationStopped,
isDefaultPrevented
} from './utils'
onDocumentDragOver,
supportMultiple
} from './utils/index'
class Dropzone extends React.Component {
state = {
draggedFiles: [],
acceptedFiles: [],
rejectedFiles: []
}
/**
* Convenience wrapper component for the `useDropzone` hook
*
* ```jsx
* <Dropzone>
* {({getRootProps, getInputProps}) => (
* <div {...getRootProps()}>
* <input {...getInputProps()} />
* <p>Drag 'n' drop some files here, or click to select files</p>
* </div>
* )}
* </Dropzone>
* ```
*/
function Dropzone({ children, ...params }) {
const { ...props } = useDropzone(params)
// TODO: Figure out why react-styleguidist cannot create docs if we don't return a jsx element
return <Fragment>{children(props)}</Fragment>
}
componentDidMount() {
const { preventDropOnDocument } = this.props
this.dragTargets = []
Dropzone.propTypes = {
/**
* Render function that exposes the dropzone state and prop getter fns
*
* @param {object} params
* @param {Function} params.getRootProps Returns the props you should apply to the root drop container you render
* @param {Function} params.getInputProps Returns the props you should apply to hidden file input you render
* @param {Function} params.open Open the native file selection dialog
* @param {boolean} params.isFocused Dropzone area is in focus
* @param {boolean} params.isFileDialogActive File dialog is opened
* @param {boolean} params.isDragActive Active drag is in progress
* @param {boolean} params.isDragAccept Dragged files are accepted
* @param {boolean} params.isDragReject Some dragged files are rejected
* @param {File[]} params.draggedFiles Files in active drag
* @param {File[]} params.acceptedFiles Accepted files
* @param {File[]} params.rejectedFiles Rejected files
*/
children: PropTypes.func,
if (preventDropOnDocument) {
document.addEventListener('dragover', onDocumentDragOver, false)
document.addEventListener('drop', this.onDocumentDrop, false)
}
/**
* Set accepted file types.
* See https://github.com/okonet/attr-accept for more information.
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
* Windows. In some cases there might not be a mime type set at all.
* See: https://github.com/react-dropzone/react-dropzone/issues/276
*/
accept: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
window.addEventListener('focus', this.onFileDialogCancel, false)
}
/**
* Allow drag 'n' drop (or selection from the file dialog) of multiple files
*/
multiple: PropTypes.bool,
componentWillUnmount() {
const { preventDropOnDocument } = this.props
if (preventDropOnDocument) {
document.removeEventListener('dragover', onDocumentDragOver)
document.removeEventListener('drop', this.onDocumentDrop)
}
/**
* If false, allow dropped items to take over the current browser window
*/
preventDropOnDocument: PropTypes.bool,
window.removeEventListener('focus', this.onFileDialogCancel, false)
}
/**
* Minimum file size (in bytes)
*/
minSize: PropTypes.number,
isFileDialogActive = false
/**
* Maximum file size (in bytes)
*/
maxSize: PropTypes.number,
onDocumentDrop = evt => {
if (this.node && this.node.contains(evt.target)) {
// if we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
return
}
evt.preventDefault()
this.dragTargets = []
}
/**
* Enable/disable the dropzone
*/
disabled: PropTypes.bool,
onDragStart = evt => {
evt.persist()
if (this.props.onDragStart && isDragDataWithFiles(evt)) {
this.props.onDragStart.call(this, evt)
}
}
/**
* Use this to provide a custom file aggregator
*
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
getFilesFromEvent: PropTypes.func,
onDragEnter = evt => {
evt.preventDefault()
/**
* Cb for when closing the file dialog with no selection
*/
onFileDialogCancel: PropTypes.func,
// Count the dropzone and any children that are entered.
if (this.dragTargets.indexOf(evt.target) === -1) {
this.dragTargets.push(evt.target)
}
/**
* Cb for when the `dragenter` event occurs.
*
* @param {DragEvent} event
*/
onDragEnter: PropTypes.func,
evt.persist()
/**
* Cb for when the `dragleave` event occurs
*
* @param {DragEvent} event
*/
onDragLeave: PropTypes.func,
if (isDragDataWithFiles(evt)) {
Promise.resolve(this.props.getDataTransferItems(evt)).then(draggedFiles => {
if (isPropagationStopped(evt)) {
return
}
/**
* Cb for when the `dragover` event occurs
*
* @param {DragEvent} event
*/
onDragOver: PropTypes.func,
this.setState({
draggedFiles,
// Do not rely on files for the drag state. It doesn't work in Safari.
isDragActive: true
})
})
/**
* Cb for when the `drop` event occurs.
* Note that this callback is invoked after the `getFilesFromEvent` callback is done.
*
* Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
* `accept` must be a valid [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml) according to [input element specification](https://www.w3.org/wiki/HTML/Elements/input/file) or a valid file extension.
* If `multiple` is set to false and additional files are droppped,
* all files besides the first will be rejected.
* Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
*
* Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
* If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
*
* `onDrop` will provide you with an array of [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects which you can then process and send to a server.
* For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
*
* ```js
* function onDrop(acceptedFiles) {
* const req = request.post('/upload')
* acceptedFiles.forEach(file => {
* req.attach(file.name, file)
* })
* req.end(callback)
* }
* ```
*
* @param {File[]} acceptedFiles
* @param {File[]} rejectedFiles
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
onDrop: PropTypes.func,
if (this.props.onDragEnter) {
this.props.onDragEnter.call(this, evt)
}
}
}
/**
* Cb for when the `drop` event occurs.
* Note that if no files are accepted, this callback is not invoked.
*
* @param {File[]} files
* @param {(DragEvent|Event)} event
*/
onDropAccepted: PropTypes.func,
onDragOver = evt => {
// eslint-disable-line class-methods-use-this
evt.preventDefault()
evt.persist()
/**
* Cb for when the `drop` event occurs.
* Note that if no files are rejected, this callback is not invoked.
*
* @param {object[]} files
* @param {(DragEvent|Event)} event
*/
onDropRejected: PropTypes.func
}
if (evt.dataTransfer) {
evt.dataTransfer.dropEffect = 'copy'
}
export default Dropzone
if (this.props.onDragOver && isDragDataWithFiles(evt)) {
this.props.onDragOver.call(this, evt)
}
/**
* A function that is invoked for the `dragenter`,
* `dragover` and `dragleave` events.
* It is not invoked if the items are not files (such as link, text, etc.).
*
* @callback dragCb
* @param {DragEvent} event
*/
return false
}
/**
* A function that is invoked for the `drop` or input change event.
* It is not invoked if the items are not files (such as link, text, etc.).
*
* @callback dropCb
* @param {File[]} acceptedFiles List of accepted files
* @param {File[]} rejectedFiles List of rejected files
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
onDragLeave = evt => {
evt.preventDefault()
evt.persist()
/**
* A function that is invoked for the `drop` or input change event.
* It is not invoked if the items are files (such as link, text, etc.).
*
* @callback dropAcceptedCb
* @param {File[]} files List of accepted files that meet the given criteria
* (`accept`, `multiple`, `minSize`, `maxSize`)
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
// Only deactivate once the dropzone and all children have been left.
this.dragTargets = this.dragTargets.filter(el => el !== evt.target && this.node.contains(el))
if (this.dragTargets.length > 0) {
return
}
/**
* A function that is invoked for the `drop` or input change event.
*
* @callback dropRejectedCb
* @param {File[]} files List of rejected files that do not meet the given criteria
* (`accept`, `multiple`, `minSize`, `maxSize`)
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
*/
// Clear dragging files state
this.setState({
isDragActive: false,
draggedFiles: []
})
/**
* A function that is used aggregate files,
* in a asynchronous fashion, from drag or input change events.
*
* @callback getFilesFromEvent
* @param {(DragEvent|Event)} event A drag event or input change event (if files were selected via the file dialog)
* @returns {(File[]|Promise<File[]>)}
*/
if (this.props.onDragLeave && isDragDataWithFiles(evt)) {
this.props.onDragLeave.call(this, evt)
}
}
/**
* An object with the current dropzone state and some helper functions.
*
* @typedef {object} DropzoneState
* @property {Function} getRootProps Returns the props you should apply to the root drop container you render
* @property {Function} getInputProps Returns the props you should apply to hidden file input you render
* @property {Function} open Open the native file selection dialog
* @property {boolean} isFocused Dropzone area is in focus
* @property {boolean} isFileDialogActive File dialog is opened
* @property {boolean} isDragActive Active drag is in progress
* @property {boolean} isDragAccept Dragged files are accepted
* @property {boolean} isDragReject Some dragged files are rejected
* @property {File[]} draggedFiles Files in active drag
* @property {File[]} acceptedFiles Accepted files
* @property {File[]} rejectedFiles Rejected files
*/
onDrop = evt => {
const {
onDrop,
onDropAccepted,
onDropRejected,
multiple,
accept,
getDataTransferItems
} = this.props
const initialState = {
isFocused: false,
isFileDialogActive: false,
isDragActive: false,
isDragAccept: false,
isDragReject: false,
draggedFiles: [],
acceptedFiles: [],
rejectedFiles: []
}
// Stop default browser behavior
evt.preventDefault()
/**
* A React hook that creates a drag 'n' drop area.
*
* ```jsx
* function MyDropzone(props) {
* const {getRootProps, getInputProps} = useDropzone({
* onDrop: acceptedFiles => {
* // do something with the File objects, e.g. upload to some server
* }
* });
* return (
* <div {...getRootProps()}>
* <input {...getInputProps()} />
* <p>Drag and drop some files here, or click to select files</p>
* </div>
* )
* }
* ```
*
* @function useDropzone
*
* @param {object} props
* @param {string|string[]} [props.accept] Set accepted file types.
* See https://github.com/okonet/attr-accept for more information.
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
* Windows. In some cases there might not be a mime type set at all.
* See: https://github.com/react-dropzone/react-dropzone/issues/276
* @param {boolean} [props.multiple=true] Allow drag 'n' drop (or selection from the file dialog) of multiple files
* @param {boolean} [props.preventDropOnDocument=true] If false, allow dropped items to take over the current browser window
* @param {number} [props.minSize=0] Minimum file size (in bytes)
* @param {number} [props.maxSize=Infinity] Maximum file size (in bytes)
* @param {boolean} [props.disabled=false] Enable/disable the dropzone
* @param {getFilesFromEvent} [props.getFilesFromEvent] Use this to provide a custom file aggregator
* @param {Function} [props.onFileDialogCancel] Cb for when closing the file dialog with no selection
* @param {dragCb} [props.onDragEnter] Cb for when the `dragenter` event occurs.
* @param {dragCb} [props.onDragLeave] Cb for when the `dragleave` event occurs
* @param {dragCb} [props.onDragOver] Cb for when the `dragover` event occurs
* @param {dropCb} [props.onDrop] Cb for when the `drop` event occurs.
* Note that this callback is invoked after the `getFilesFromEvent` callback is done.
*
* Files are accepted or rejected based on the `accept`, `multiple`, `minSize` and `maxSize` props.
* `accept` must be a valid [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml) according to [input element specification](https://www.w3.org/wiki/HTML/Elements/input/file) or a valid file extension.
* If `multiple` is set to false and additional files are droppped,
* all files besides the first will be rejected.
* Any file which does not have a size in the [`minSize`, `maxSize`] range, will be rejected as well.
*
* Note that the `onDrop` callback will always be invoked regardless if the dropped files were accepted or rejected.
* If you'd like to react to a specific scenario, use the `onDropAccepted`/`onDropRejected` props.
*
* `onDrop` will provide you with an array of [File](https://developer.mozilla.org/en-US/docs/Web/API/File) objects which you can then process and send to a server.
* For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
*
* ```js
* function onDrop(acceptedFiles) {
* const req = request.post('/upload')
* acceptedFiles.forEach(file => {
* req.attach(file.name, file)
* })
* req.end(callback)
* }
* ```
* @param {dropAcceptedCb} [props.onDropAccepted]
* @param {dropRejectedCb} [props.onDropRejected]
*
* @returns {DropzoneState}
*/
export function useDropzone({
accept,
disabled = false,
getFilesFromEvent = fromEvent,
maxSize = Infinity,
minSize = 0,
multiple = true,
onDragEnter,
onDragLeave,
onDragOver,
onDrop,
onDropAccepted,
onDropRejected,
onFileDialogCancel,
preventDropOnDocument = true
} = {}) {
const rootRef = useRef(null)
const inputRef = useRef(null)
// Persist event for later usage
evt.persist()
const [state, dispatch] = useReducer(reducer, initialState)
const { isFocused, isFileDialogActive, draggedFiles } = state
// Reset the counter along with the drag on a drop.
this.dragTargets = []
this.isFileDialogActive = false
// Clear files value
this.draggedFiles = null
// Reset drag state
this.setState({
isDragActive: false,
draggedFiles: []
})
if (isDragDataWithFiles(evt)) {
Promise.resolve(getDataTransferItems(evt)).then(fileList => {
const acceptedFiles = []
const rejectedFiles = []
if (isPropagationStopped(evt)) {
return
}
fileList.forEach(file => {
if (
fileAccepted(file, accept) &&
fileMatchSize(file, this.props.maxSize, this.props.minSize)
) {
acceptedFiles.push(file)
} else {
rejectedFiles.push(file)
}
})
if (!multiple && acceptedFiles.length > 1) {
// if not in multi mode add any extra accepted files to rejected.
// This will allow end users to easily ignore a multi file drop in "single" mode.
rejectedFiles.push(...acceptedFiles.splice(0))
}
// Update `acceptedFiles` and `rejectedFiles` state
// This will make children render functions receive the appropriate
// values
this.setState({ acceptedFiles, rejectedFiles }, () => {
if (onDrop) {
onDrop.call(this, acceptedFiles, rejectedFiles, evt)
}
if (rejectedFiles.length > 0 && onDropRejected) {
onDropRejected.call(this, rejectedFiles, evt)
}
if (acceptedFiles.length > 0 && onDropAccepted) {
onDropAccepted.call(this, acceptedFiles, evt)
}
})
})
// Fn for opening the file dialog programmatically
const openFileDialog = () => {
if (inputRef.current) {
dispatch({ type: 'openDialog' })
inputRef.current.value = null
inputRef.current.click()
}
}
onClick = evt => {
const { onClick } = this.props
// if onClick prop is given, run it first
if (onClick) {
onClick.call(this, evt)
}
// If the event hasn't been default prevented from within
// the onClick listener, open the file dialog
if (!isDefaultPrevented(evt)) {
evt.stopPropagation()
// in IE11/Edge the file-browser dialog is blocking, ensure this is behind setTimeout
// this is so react can handle state changes in the onClick prop above above
// see: https://github.com/react-dropzone/react-dropzone/issues/450
if (isIeOrEdge()) {
setTimeout(this.open, 0)
} else {
this.open()
}
}
}
onInputElementClick = evt => {
evt.stopPropagation()
}
onFileDialogCancel = () => {
// timeout will not recognize context of this method
const { onFileDialogCancel } = this.props
// execute the timeout only if the FileDialog is opened in the browser
if (this.isFileDialogActive) {
// Update file dialog active state when the window is focused on
const onWindowFocus = () => {
// Execute the timeout only if the file dialog is opened in the browser
if (isFileDialogActive) {
setTimeout(() => {
if (this.input != null) {
// Returns an object as FileList
const { files } = this.input
if (inputRef.current) {
const { files } = inputRef.current
if (!files.length) {
this.isFileDialogActive = false
dispatch({ type: 'closeDialog' })

@@ -256,315 +377,333 @@ if (typeof onFileDialogCancel === 'function') {

}
onFocus = evt => {
const { onFocus } = this.props
if (onFocus) {
onFocus.call(this, evt)
useEffect(() => {
window.addEventListener('focus', onWindowFocus, false)
return () => {
window.removeEventListener('focus', onWindowFocus, false)
}
if (!isDefaultPrevented(evt)) {
this.setState({ isFocused: true })
}
}
}, [inputRef, isFileDialogActive, onFileDialogCancel])
onBlur = evt => {
const { onBlur } = this.props
if (onBlur) {
onBlur.call(this, evt)
// Cb to open the file dialog when SPACE/ENTER occurs on the dropzone
const onKeyDownCb = useCallback(
event => {
// Ignore keyboard events bubbling up the DOM tree
if (!rootRef.current || !rootRef.current.isEqualNode(event.target)) {
return
}
if (event.keyCode === 32 || event.keyCode === 13) {
event.preventDefault()
openFileDialog()
}
},
[rootRef, inputRef]
)
// Update focus state for the dropzone
const onFocusCb = useCallback(() => {
dispatch({ type: 'focus' })
}, [])
const onBlurCb = useCallback(() => {
dispatch({ type: 'blur' })
}, [])
// Cb to open the file dialog when click occurs on the dropzone
const onClickCb = useCallback(() => {
// In IE11/Edge the file-browser dialog is blocking, therefore, use setTimeout()
// to ensure React can handle state changes
// See: https://github.com/react-dropzone/react-dropzone/issues/450
if (isIeOrEdge()) {
setTimeout(openFileDialog, 0)
} else {
openFileDialog()
}
if (!isDefaultPrevented(evt)) {
this.setState({ isFocused: false })
}
}
}, [inputRef])
onKeyDown = evt => {
const { onKeyDown } = this.props
if (!this.node.isEqualNode(evt.target)) {
const [dragTargets, setDragTargets] = useState([])
const onDocumentDrop = event => {
if (rootRef.current && rootRef.current.contains(event.target)) {
// If we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
return
}
event.preventDefault()
setDragTargets([])
}
if (onKeyDown) {
onKeyDown.call(this, evt)
useEffect(() => {
if (preventDropOnDocument) {
document.addEventListener('dragover', onDocumentDragOver, false)
document.addEventListener('drop', onDocumentDrop, false)
}
if (!isDefaultPrevented(evt) && (evt.keyCode === 32 || evt.keyCode === 13)) {
evt.preventDefault()
this.open()
return () => {
if (preventDropOnDocument) {
document.removeEventListener('dragover', onDocumentDragOver)
document.removeEventListener('drop', onDocumentDrop)
}
}
}
}, [rootRef, preventDropOnDocument])
composeHandler = handler => {
if (this.props.disabled) {
return null
}
return handler
}
const onDragEnterCb = useCallback(
event => {
event.preventDefault()
// Persist here because we need the event later after getFilesFromEvent() is done
event.persist()
getRootProps = ({
refKey = 'ref',
onKeyDown,
onFocus,
onBlur,
onClick,
onDragStart,
onDragEnter,
onDragOver,
onDragLeave,
onDrop,
...rest
} = {}) => ({
onKeyDown: this.composeHandler(
onKeyDown ? composeEventHandlers(onKeyDown, this.onKeyDown) : this.onKeyDown
),
onFocus: this.composeHandler(
onFocus ? composeEventHandlers(onFocus, this.onFocus) : this.onFocus
),
onBlur: this.composeHandler(onBlur ? composeEventHandlers(onBlur, this.onBlur) : this.onBlur),
onClick: this.composeHandler(
onClick ? composeEventHandlers(onClick, this.onClick) : this.onClick
),
onDragStart: this.composeHandler(
onDragStart ? composeEventHandlers(onDragStart, this.onDragStart) : this.onDragStart
),
onDragEnter: this.composeHandler(
onDragEnter ? composeEventHandlers(onDragEnter, this.onDragEnter) : this.onDragEnter
),
onDragOver: this.composeHandler(
onDragOver ? composeEventHandlers(onDragOver, this.onDragOver) : this.onDragOver
),
onDragLeave: this.composeHandler(
onDragLeave ? composeEventHandlers(onDragLeave, this.onDragLeave) : this.onDragLeave
),
onDrop: this.composeHandler(onDrop ? composeEventHandlers(onDrop, this.onDrop) : this.onDrop),
[refKey]: this.setNodeRef,
tabIndex: this.props.disabled ? -1 : 0,
...rest
})
// Count the dropzone and any children that are entered.
if (dragTargets.indexOf(event.target) === -1) {
setDragTargets([...dragTargets, event.target])
}
getInputProps = ({ refKey = 'ref', onChange, onClick, ...rest } = {}) => {
const { accept, multiple, name } = this.props
const inputProps = {
accept,
type: 'file',
style: { display: 'none' },
multiple: supportMultiple && multiple,
onChange: composeEventHandlers(onChange, this.onDrop),
onClick: composeEventHandlers(onClick, this.onInputElementClick),
autoComplete: 'off',
tabIndex: -1,
[refKey]: this.setInputRef
}
if (name && name.length) {
inputProps.name = name
}
return {
...inputProps,
...rest
}
}
if (isEvtWithFiles(event)) {
Promise.resolve(getFilesFromEvent(event)).then(draggedFiles => {
if (isPropagationStopped(event)) {
return
}
setNodeRef = node => {
this.node = node
}
dispatch({
draggedFiles,
type: 'setDraggedFiles'
})
setInputRef = input => {
this.input = input
}
if (onDragEnter) {
onDragEnter(event)
}
})
}
},
[dragTargets, getFilesFromEvent, onDragEnter]
)
/**
* Open system file upload dialog.
*
* @public
*/
open = () => {
this.isFileDialogActive = true
if (this.input) {
this.input.value = null
this.input.click()
}
}
const onDragOverCb = useCallback(
event => {
event.preventDefault()
event.persist()
render() {
const { children, multiple, disabled } = this.props
const { isDragActive, isFocused, draggedFiles, acceptedFiles, rejectedFiles } = this.state
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'copy'
}
const filesCount = draggedFiles.length
const isMultipleAllowed = multiple || filesCount <= 1
const isDragAccept = filesCount > 0 && allFilesAccepted(draggedFiles, this.props.accept)
const isDragReject = filesCount > 0 && (!isDragAccept || !isMultipleAllowed)
if (isEvtWithFiles(event) && onDragOver) {
onDragOver(event)
}
return children({
isDragActive,
isDragAccept,
isDragReject,
draggedFiles,
acceptedFiles,
rejectedFiles,
isFocused: isFocused && !disabled,
getRootProps: this.getRootProps,
getInputProps: this.getInputProps,
open: this.open
})
}
}
return false
},
[onDragOver]
)
export default Dropzone
const onDragLeaveCb = useCallback(
event => {
event.preventDefault()
event.persist()
Dropzone.propTypes = {
/**
* Allow specific types of files. See https://github.com/okonet/attr-accept for more information.
* Keep in mind that mime type determination is not reliable across platforms. CSV files,
* for example, are reported as text/plain under macOS but as application/vnd.ms-excel under
* Windows. In some cases there might not be a mime type set at all.
* See: https://github.com/react-dropzone/react-dropzone/issues/276
*/
accept: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
// Only deactivate once the dropzone and all children have been left
const targets = [
...dragTargets.filter(
target => target !== event.target && rootRef.current && rootRef.current.contains(target)
)
]
setDragTargets(targets)
if (targets.length > 0) {
return
}
/**
* Render function that renders the actual component
*
* @param {Object} props
* @param {Function} props.getRootProps Returns the props you should apply to the root drop container you render
* @param {Function} props.getInputProps Returns the props you should apply to hidden file input you render
* @param {Function} props.open Open the native file selection dialog
* @param {Boolean} props.isFocused Dropzone area is in focus
* @param {Boolean} props.isDragActive Active drag is in progress
* @param {Boolean} props.isDragAccept Dragged files are accepted
* @param {Boolean} props.isDragReject Some dragged files are rejected
* @param {Array} props.draggedFiles Files in active drag
* @param {Array} props.acceptedFiles Accepted files
* @param {Array} props.rejectedFiles Rejected files
*/
children: PropTypes.func,
dispatch({
type: 'setDraggedFiles',
draggedFiles: []
})
/**
* Enable/disable the dropzone entirely
*/
disabled: PropTypes.bool,
if (isEvtWithFiles(event) && onDragLeave) {
onDragLeave(event)
}
},
[rootRef, dragTargets, onDragLeave]
)
/**
* If false, allow dropped items to take over the current browser window
*/
preventDropOnDocument: PropTypes.bool,
const onDropCb = useCallback(
event => {
event.preventDefault()
// Persist here because we need the event later after getFilesFromEvent() is done
event.persist()
/**
* Allow dropping multiple files
*/
multiple: PropTypes.bool,
setDragTargets([])
dispatch({ type: 'reset' })
/**
* `name` attribute for the input tag
*/
name: PropTypes.string,
if (isEvtWithFiles(event)) {
Promise.resolve(getFilesFromEvent(event)).then(files => {
if (isPropagationStopped(event)) {
return
}
/**
* Maximum file size (in bytes)
*/
maxSize: PropTypes.number,
const acceptedFiles = []
const rejectedFiles = []
/**
* Minimum file size (in bytes)
*/
minSize: PropTypes.number,
files.forEach(file => {
if (fileAccepted(file, accept) && fileMatchSize(file, maxSize, minSize)) {
acceptedFiles.push(file)
} else {
rejectedFiles.push(file)
}
})
/**
* getDataTransferItems handler
* @param {Event} event
* @returns {Array} array of File objects
*/
getDataTransferItems: PropTypes.func,
if (!multiple && acceptedFiles.length > 1) {
rejectedFiles.push(...acceptedFiles.splice(0)) // Reject everything and empty accepted files
}
/**
* onClick callback
* @param {Event} event
*/
onClick: PropTypes.func,
dispatch({
acceptedFiles,
rejectedFiles,
type: 'setFiles'
})
/**
* onFocus callback
*/
onFocus: PropTypes.func,
if (onDrop) {
onDrop(acceptedFiles, rejectedFiles, event)
}
/**
* onBlur callback
*/
onBlur: PropTypes.func,
if (rejectedFiles.length > 0 && onDropRejected) {
onDropRejected(rejectedFiles, event)
}
/**
* onKeyDown callback
*/
onKeyDown: PropTypes.func,
if (acceptedFiles.length > 0 && onDropAccepted) {
onDropAccepted(acceptedFiles, event)
}
})
}
},
[multiple, accept, minSize, maxSize, getFilesFromEvent, onDrop, onDropAccepted, onDropRejected]
)
/**
* The `onDrop` method that accepts two arguments.
* The first argument represents the accepted files and the second argument the rejected files.
*
* ```javascript
* function onDrop(acceptedFiles, rejectedFiles) {
* // do stuff with files...
* }
* ```
*
* Files are accepted or rejected based on the `accept` prop.
* This must be a valid [MIME type](http://www.iana.org/assignments/media-types/media-types.xhtml) according to [input element specification](https://www.w3.org/wiki/HTML/Elements/input/file) or a valid file extension.
*
* Note that the `onDrop` callback will always be called regardless if the dropped files were accepted or rejected.
* You can use the `onDropAccepted`/`onDropRejected` props if you'd like to react to a specific event instead of the `onDrop` prop.
*
* The `onDrop` callback will provide you with an array of [Files](https://developer.mozilla.org/en-US/docs/Web/API/File) which you can then process and send to a server.
* For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
*
* ```javascript
* function onDrop(acceptedFiles) {
* const req = request.post('/upload')
* acceptedFiles.forEach(file => {
* req.attach(file.name, file)
* })
* req.end(callback)
* }
* ```
*/
onDrop: PropTypes.func,
const composeHandler = fn => {
return disabled ? null : fn
}
/**
* onDropAccepted callback
*/
onDropAccepted: PropTypes.func,
const getRootProps = useMemo(
() => ({
refKey = 'ref',
onKeyDown,
onFocus,
onBlur,
onClick,
onDragEnter,
onDragOver,
onDragLeave,
onDrop,
...rest
} = {}) => ({
onKeyDown: composeHandler(composeEventHandlers(onKeyDown, onKeyDownCb)),
onFocus: composeHandler(composeEventHandlers(onFocus, onFocusCb)),
onBlur: composeHandler(composeEventHandlers(onBlur, onBlurCb)),
onClick: composeHandler(composeEventHandlers(onClick, onClickCb)),
onDragEnter: composeHandler(composeEventHandlers(onDragEnter, onDragEnterCb)),
onDragOver: composeHandler(composeEventHandlers(onDragOver, onDragOverCb)),
onDragLeave: composeHandler(composeEventHandlers(onDragLeave, onDragLeaveCb)),
onDrop: composeHandler(composeEventHandlers(onDrop, onDropCb)),
[refKey]: rootRef,
tabIndex: disabled ? -1 : 0,
...rest
}),
[
rootRef,
onKeyDownCb,
onFocusCb,
onBlurCb,
onClickCb,
onDragEnterCb,
onDragOverCb,
onDragLeaveCb,
onDropCb,
disabled
]
)
/**
* onDropRejected callback
*/
onDropRejected: PropTypes.func,
const onInputElementClick = useCallback(event => {
event.stopPropagation()
}, [])
/**
* onDragStart callback
*/
onDragStart: PropTypes.func,
const getInputProps = useMemo(
() => ({ refKey = 'ref', onChange, onClick, ...rest } = {}) => {
const inputProps = {
accept,
type: 'file',
style: { display: 'none' },
multiple: supportMultiple && multiple,
onChange: composeHandler(composeEventHandlers(onChange, onDropCb)),
onClick: composeHandler(composeEventHandlers(onClick, onInputElementClick)),
autoComplete: 'off',
tabIndex: -1,
[refKey]: inputRef
}
/**
* onDragEnter callback
*/
onDragEnter: PropTypes.func,
return {
...inputProps,
...rest
}
},
[inputRef, accept, multiple, onDropCb, disabled]
)
/**
* onDragOver callback
*/
onDragOver: PropTypes.func,
const fileCount = draggedFiles.length
const isMultipleAllowed = multiple || fileCount <= 1
const isDragAccept = fileCount > 0 && allFilesAccepted(draggedFiles, accept)
const isDragReject = fileCount > 0 && (!isDragAccept || !isMultipleAllowed)
/**
* onDragLeave callback
*/
onDragLeave: PropTypes.func,
/**
* Provide a callback on clicking the cancel button of the file dialog
*/
onFileDialogCancel: PropTypes.func
return {
...state,
isDragAccept,
isDragReject,
isFocused: isFocused && !disabled,
getRootProps,
getInputProps,
rootRef,
inputRef,
open: composeHandler(openFileDialog)
}
}
Dropzone.defaultProps = {
preventDropOnDocument: true,
disabled: false,
multiple: true,
maxSize: Infinity,
minSize: 0,
getDataTransferItems: fromEvent
function reducer(state, action) {
/* istanbul ignore next */
switch (action.type) {
case 'focus':
return {
...state,
isFocused: true
}
case 'blur':
return {
...state,
isFocused: false
}
case 'openDialog':
return {
...state,
isFileDialogActive: true
}
case 'closeDialog':
return {
...state,
isFileDialogActive: false
}
case 'setDraggedFiles':
/* eslint no-case-declarations: 0 */
const { draggedFiles } = action
return {
...state,
draggedFiles,
isDragActive: draggedFiles.length > 0
}
case 'setFiles':
return {
...state,
acceptedFiles: action.acceptedFiles,
rejectedFiles: action.rejectedFiles
}
case 'reset':
return {
...state,
isFileDialogActive: false,
isDragActive: false,
draggedFiles: []
}
default:
return state
}
}
import accepts from 'attr-accept'
export const supportMultiple =
typeof document !== 'undefined' && document && document.createElement
? 'multiple' in document.createElement('input')
: true
export const supportMultiple = 'multiple' in document.createElement('input')

@@ -22,10 +19,10 @@ // Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with

// React's synthetic events has evt.isPropagationStopped,
// React's synthetic events has event.isPropagationStopped,
// but to remain compatibility with other libs (Preact) fall back
// to check evt.cancelBubble
export function isPropagationStopped(evt) {
if (typeof evt.isPropagationStopped === 'function') {
return evt.isPropagationStopped()
} else if (typeof evt.cancelBubble !== 'undefined') {
return evt.cancelBubble
// to check event.cancelBubble
export function isPropagationStopped(event) {
if (typeof event.isPropagationStopped === 'function') {
return event.isPropagationStopped()
} else if (typeof event.cancelBubble !== 'undefined') {
return event.cancelBubble
}

@@ -35,22 +32,10 @@ return false

// React's synthetic events has evt.isDefaultPrevented,
// but to remain compatibility with other libs (Preact) first
// check evt.defaultPrevented
export function isDefaultPrevented(evt) {
if (typeof evt.defaultPrevented !== 'undefined') {
return evt.defaultPrevented
} else if (typeof evt.isDefaultPrevented === 'function') {
return evt.isDefaultPrevented()
export function isEvtWithFiles(event) {
if (!event.dataTransfer) {
return !!event.target && !!event.target.files
}
return false
}
export function isDragDataWithFiles(evt) {
if (!evt.dataTransfer) {
return true
}
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/types
// https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Recommended_drag_types#file
return Array.prototype.some.call(
evt.dataTransfer.types,
event.dataTransfer.types,
type => type === 'Files' || type === 'application/x-moz-file'

@@ -65,4 +50,4 @@ )

// allow the entire document to be a drag target
export function onDocumentDragOver(evt) {
evt.preventDefault()
export function onDocumentDragOver(event) {
event.preventDefault()
}

@@ -84,4 +69,7 @@

* This is intended to be used to compose event handlers
* They are executed in order until one of them calls `event.preventDefault()`.
* Not sure this is the best way to do this, but it seems legit.
* They are executed in order until one of them calls `event.isPropagationStopped()`.
* Note that the check is done on the first invoke too,
* meaning that if propagation was stopped before invoking the fns,
* no handlers will be executed.
*
* @param {Function} fns the event hanlder functions

@@ -93,5 +81,7 @@ * @return {Function} the event handler to add to an element

fns.some(fn => {
fn && fn(event, ...args)
return event.defaultPrevented
if (!isPropagationStopped(event) && fn) {
fn(event, ...args)
}
return isPropagationStopped(event)
})
}

@@ -1,11 +0,12 @@

import {
isIeOrEdge,
isKindFile,
isDragDataWithFiles,
composeEventHandlers,
isPropagationStopped,
isDefaultPrevented
} from './'
beforeEach(() => {
jest.resetModules()
})
describe('isIeOrEdge', () => {
let utils
beforeEach(async done => {
utils = await import('./index')
done()
})
it('should return true for IE10', () => {

@@ -15,3 +16,3 @@ const userAgent =

expect(isIeOrEdge(userAgent)).toBe(true)
expect(utils.isIeOrEdge(userAgent)).toBe(true)
})

@@ -22,3 +23,3 @@

'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.0.30729; .NET CLR 3.5.30729; rv:11.0) like Gecko'
expect(isIeOrEdge(userAgent)).toBe(true)
expect(utils.isIeOrEdge(userAgent)).toBe(true)
})

@@ -30,3 +31,3 @@

expect(isIeOrEdge(userAgent)).toBe(true)
expect(utils.isIeOrEdge(userAgent)).toBe(true)
})

@@ -38,3 +39,3 @@

expect(isIeOrEdge(userAgent)).toBe(false)
expect(utils.isIeOrEdge(userAgent)).toBe(false)
})

@@ -44,7 +45,8 @@ })

describe('isKindFile()', () => {
it('should return true for DataTransferItem of kind "file"', () => {
expect(isKindFile({ kind: 'file' })).toBe(true)
expect(isKindFile({ kind: 'text/html' })).toBe(false)
expect(isKindFile({})).toBe(false)
expect(isKindFile(null)).toBe(false)
it('should return true for DataTransferItem of kind "file"', async () => {
const utils = await import('./index')
expect(utils.isKindFile({ kind: 'file' })).toBe(true)
expect(utils.isKindFile({ kind: 'text/html' })).toBe(false)
expect(utils.isKindFile({})).toBe(false)
expect(utils.isKindFile(null)).toBe(false)
})

@@ -56,47 +58,43 @@ })

let utils
beforeEach(async done => {
utils = await import('./index')
done()
})
it('should return result of isPropagationStopped() if isPropagationStopped exists', () => {
expect(isPropagationStopped({ isPropagationStopped: trueFn })).toBe(true)
expect(utils.isPropagationStopped({ isPropagationStopped: trueFn })).toBe(true)
})
it('should return value of cancelBubble if isPropagationStopped doesnt exist and cancelBubble exists', () => {
expect(isPropagationStopped({ cancelBubble: true })).toBe(true)
expect(utils.isPropagationStopped({ cancelBubble: true })).toBe(true)
})
it('should return false if isPropagationStopped and cancelBubble are missing', () => {
expect(isPropagationStopped({})).toBe(false)
expect(utils.isPropagationStopped({})).toBe(false)
})
})
describe('isDefaultPrevented()', () => {
const trueFn = jest.fn(() => true)
it('should return value of defaultPrevented if defaultPrevented exists', () => {
expect(isDefaultPrevented({ defaultPrevented: true })).toBe(true)
describe('isEvtWithFiles()', () => {
let utils
beforeEach(async done => {
utils = await import('./index')
done()
})
it('should return result of isDefaultPrevented() if isDefaultPrevented exists and defaultPrevented is missing', () => {
expect(isDefaultPrevented({ isDefaultPrevented: trueFn })).toBe(true)
})
it('should return false if isDefaultPrevented and defaultPrevented are missing', () => {
expect(isDefaultPrevented({})).toBe(false)
})
})
describe('isDragDataWithFiles()', () => {
it('should return true if every dragged type is a file', () => {
expect(isDragDataWithFiles({ dataTransfer: { types: ['Files'] } })).toBe(true)
expect(isDragDataWithFiles({ dataTransfer: { types: ['application/x-moz-file'] } })).toBe(true)
it('should return true if some dragged types are files', () => {
expect(utils.isEvtWithFiles({ dataTransfer: { types: ['Files'] } })).toBe(true)
expect(utils.isEvtWithFiles({ dataTransfer: { types: ['application/x-moz-file'] } })).toBe(true)
expect(
isDragDataWithFiles({
utils.isEvtWithFiles({
dataTransfer: { types: ['Files', 'application/x-moz-file'] }
})
).toBe(true)
expect(isDragDataWithFiles({ dataTransfer: { types: ['text/plain'] } })).toBe(false)
expect(isDragDataWithFiles({ dataTransfer: { types: ['text/html'] } })).toBe(false)
expect(isDragDataWithFiles({ dataTransfer: { types: ['Files', 'application/test'] } })).toBe(
expect(utils.isEvtWithFiles({ dataTransfer: { types: ['text/plain'] } })).toBe(false)
expect(utils.isEvtWithFiles({ dataTransfer: { types: ['text/html'] } })).toBe(false)
expect(utils.isEvtWithFiles({ dataTransfer: { types: ['Files', 'application/test'] } })).toBe(
true
)
expect(
isDragDataWithFiles({
utils.isEvtWithFiles({
dataTransfer: { types: ['application/x-moz-file', 'application/test'] }

@@ -107,10 +105,20 @@ })

it('should return true if {dataTransfer} is not defined', () => {
expect(isDragDataWithFiles({})).toBe(true)
it('should return true if the event has a target with files', () => {
expect(utils.isEvtWithFiles({ target: { files: [] } })).toBe(true)
})
it('should return false otherwise', () => {
expect(utils.isEvtWithFiles({})).toBe(false)
})
})
describe('composeEventHandlers', () => {
let utils
beforeEach(async done => {
utils = await import('./index')
done()
})
it('returns a fn', () => {
const fn = composeEventHandlers(() => {})
const fn = utils.composeEventHandlers(() => {})
expect(typeof fn).toBe('function')

@@ -122,22 +130,49 @@ })

const fn2 = jest.fn()
const fn = composeEventHandlers(fn1, fn2)
const evt = { type: 'click' }
const fn = utils.composeEventHandlers(fn1, fn2)
const event = { type: 'click' }
const data = { ping: true }
fn(evt, data)
expect(fn1).toHaveBeenCalledWith(evt, data)
expect(fn2).toHaveBeenCalledWith(evt, data)
fn(event, data)
expect(fn1).toHaveBeenCalledWith(event, data)
expect(fn2).toHaveBeenCalledWith(event, data)
})
it('stops after first fn that calls preventDefault()', () => {
const fn1 = jest.fn().mockImplementation(evt => {
Object.defineProperty(evt, 'defaultPrevented', { value: true })
return evt
it('stops after first fn that calls stopPropagation()', () => {
const fn1 = jest.fn().mockImplementation(event => {
Object.defineProperty(event, 'cancelBubble', { value: true })
return event
})
const fn2 = jest.fn()
const fn = composeEventHandlers(fn1, fn2)
const evt = new MouseEvent('click')
fn(evt)
expect(fn1).toHaveBeenCalledWith(evt)
const fn = utils.composeEventHandlers(fn1, fn2)
const event = new MouseEvent('click')
fn(event)
expect(fn1).toHaveBeenCalledWith(event)
expect(fn2).not.toHaveBeenCalled()
})
it('stops before first fn if bubble is already canceled', () => {
const fn1 = jest.fn()
const fn2 = jest.fn()
const fn = utils.composeEventHandlers(fn1, fn2)
const event = new MouseEvent('click')
Object.defineProperty(event, 'cancelBubble', { value: true })
fn(event)
expect(fn1).not.toHaveBeenCalled()
expect(fn2).not.toHaveBeenCalled()
})
})
describe('supportMultiple', () => {
it('returns true if <input> supports the {multiple} attr', async () => {
jest.spyOn(document, 'createElement').mockReturnValueOnce({ multiple: null })
const utils = await import('./index')
expect(utils.supportMultiple).toBe(true)
})
it('returns false if <input> does not support the {multiple} attr', async () => {
jest.spyOn(document, 'createElement').mockReturnValueOnce({})
const utils = await import('./index')
expect(utils.supportMultiple).toBe(false)
})
// TODO: Test if no doc
})

@@ -5,2 +5,3 @@ /* eslint import/no-extraneous-dependencies: 0 */

// https://react-styleguidist.js.org/docs/configuration.html
module.exports = {

@@ -14,5 +15,4 @@ title: 'react-dropzone',

serverPort: 8080,
compilerConfig: {
transforms: { dangerousTaggedTemplateString: true },
objectAssign: 'Object.assign'
moduleAliases: {
'react-dropzone': path.resolve(__dirname, './src')
},

@@ -24,4 +24,6 @@ sections: [

},
// TODO: Figure out how to document the hook
// See https://github.com/reactjs/react-docgen/issues/332
{
name: 'PropTypes',
name: 'Components',
components: './src/index.js'

@@ -31,49 +33,34 @@ },

name: 'Examples',
context: {
Dropzone: './src/index'
},
sections: [
{
name: 'Basic example',
content: 'examples/Basic/Readme.md'
content: 'examples/basic/README.md'
},
{
name: 'Events',
content: 'examples/Events/README.md'
name: 'Event Propagation',
content: 'examples/events/README.md'
},
{
name: 'Styling Dropzone',
content: 'examples/Styling/Readme.md'
content: 'examples/styling/README.md'
},
{
name: 'Accepting specific file types',
content: 'examples/Accept/Readme.md'
content: 'examples/accept/README.md'
},
{
name: 'Previews',
content: 'examples/Previews/Readme.md'
name: 'Opening File Dialog Programmatically',
content: 'examples/file-dialog/README.md'
},
{
name: 'Nested Dropzone',
content: 'examples/Nesting/Readme.md'
name: 'Previews',
content: 'examples/previews/README.md'
},
{
name: 'Opening File Dialog Programmatically',
content: 'examples/FileDialog/Readme.md'
name: 'Class Components',
content: 'examples/class-component/README.md'
},
{
name: 'Full Screen Dropzone',
content: 'examples/Fullscreen/Readme.md'
},
{
name: 'Extending Dropzone',
context: {
Dropzone: './src/index'
},
sections: [
{
name: 'Using third-party plugins',
content: 'examples/PluginArchitecture/Readme.md'
}
]
content: 'examples/plugins/README.md'
}

@@ -80,0 +67,0 @@ ]

@@ -1,8 +0,8 @@

/* eslint prefer-template: 0 */
/* eslint node/no-unpublished-require: 0 */
// https://www.npmjs.com/package/jest-dom
require('jest-dom/extend-expect')
const Enzyme = require('enzyme')
const Adapter = require('enzyme-adapter-react-16')
require('jest-enzyme')
Enzyme.configure({ adapter: new Adapter() })
// TODO: Ignore warnings about act(), it refers to having async side effects updating the state;
// This happens because our async getFilesFromEvent() fn
// See https://github.com/kentcdodds/react-testing-library/issues/281,
// https://github.com/facebook/react/issues/14769
jest.spyOn(console, 'error').mockImplementation(() => jest.fn())
import * as React from "react";
// import {func} from "prop-types";
export default class Dropzone extends React.Component<DropzoneProps> {
open: () => void;
export default function Dropzone(props: DropzoneProps): JSX.Element;
export function useDropzone(options?: DropzoneOptions): DropzoneState;
export interface DropzoneProps extends DropzoneOptions {
children?(state: DropzoneState): JSX.Element;
}
export type DropzoneProps = Pick<React.HTMLProps<HTMLElement>, PropTypes> & {
children?: DropzoneRenderFunction;
getDataTransferItems?(event: React.DragEvent<HTMLElement> | React.ChangeEvent<HTMLInputElement> | DragEvent | Event): Promise<Array<File | DataTransferItem>>;
onFileDialogCancel?(): void;
onDrop?: DropFilesEventHandler;
onDropAccepted?: DropFileEventHandler;
onDropRejected?: DropFileEventHandler;
export type DropzoneOptions = Pick<React.HTMLProps<HTMLElement>, PropTypes> & {
minSize?: number;
maxSize?: number;
minSize?: number;
preventDropOnDocument?: boolean;
disabled?: boolean;
onDrop?(acceptedFiles: File[], rejectedFiles: File[], event: DropEvent): void;
onDropAccepted?(files: File[], event: DropEvent): void;
onDropRejected?(files: File[], event: DropEvent): void;
getFilesFromEvent?(event: DropEvent): Promise<Array<File | DataTransferItem>>;
onFileDialogCancel?(): void;
};
export type DropEvent = React.DragEvent<HTMLElement> | React.ChangeEvent<HTMLInputElement> | DragEvent | Event;
export type DropzoneState = {
isFocused: boolean;
isDragActive: boolean;
isDragAccept: boolean;
isDragReject: boolean;
isFileDialogActive: boolean;
draggedFiles: File[];
acceptedFiles: File[];
rejectedFiles: File[];
rootRef: React.RefObject<HTMLElement>;
inputRef: React.RefObject<HTMLInputElement>;
getRootProps(props?: DropzoneRootProps): DropzoneRootProps;
getInputProps(props?: DropzoneInputProps): DropzoneInputProps;
open(): void;
};
export interface DropzoneRootProps extends React.HTMLAttributes<HTMLElement> {

@@ -30,39 +49,6 @@ refKey?: string;

export type DropzoneRenderFunction = (x: DropzoneRenderArgs) => JSX.Element;
export type GetRootPropsFn = (props?: DropzoneRootProps) => DropzoneRootProps;
export type GetInputPropsFn = (props?: DropzoneInputProps) => DropzoneInputProps;
export type DropFileEventHandler = (
acceptedOrRejected: File[],
event: React.DragEvent<HTMLElement>
) => void;
export type DropFilesEventHandler = (
accepted: File[],
rejected: File[],
event: React.DragEvent<HTMLElement>
) => void;
export type DropzoneRenderArgs = {
draggedFiles: File[];
acceptedFiles: File[];
rejectedFiles: File[];
isDragActive: boolean;
isDragAccept: boolean;
isDragReject: boolean;
getRootProps: GetRootPropsFn;
getInputProps: GetInputPropsFn;
open: () => void;
};
type PropTypes = "accept"
| "multiple"
| "name"
| "onClick"
| "onFocus"
| "onBlur"
| "onKeyDown"
| "onDragStart"
| "onDragEnter"
| "onDragOver"
| "onDragLeave";

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc