react-dropzone 3.13.0 → 3.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.js CHANGED
@@ -1,229 +1,250 @@
1
1
  /* eslint prefer-template: 0 */
2
2
 
3
- import React from 'react';
4
- import PropTypes from 'prop-types';
5
- import accepts from 'attr-accept';
6
- import getDataTransferItems from './getDataTransferItems';
7
-
8
- const supportMultiple = (typeof document !== 'undefined' && document && document.createElement) ?
9
- 'multiple' in document.createElement('input') :
10
- true;
3
+ import React from 'react'
4
+ import PropTypes from 'prop-types'
5
+ import accepts from 'attr-accept'
6
+ import getDataTransferItems from './getDataTransferItems'
7
+
8
+ const supportMultiple = typeof document !== 'undefined' && document && document.createElement
9
+ ? 'multiple' in document.createElement('input')
10
+ : true
11
+
12
+ function fileAccepted(file, accept) {
13
+ // Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with
14
+ // that MIME type will always be accepted
15
+ return file.type === 'application/x-moz-file' || accepts(file, accept)
16
+ }
11
17
 
12
18
  class Dropzone extends React.Component {
13
- static onDocumentDragOver(e) {
19
+ static onDocumentDragOver(evt) {
14
20
  // allow the entire document to be a drag target
15
- e.preventDefault();
21
+ evt.preventDefault()
16
22
  }
17
23
 
18
24
  constructor(props, context) {
19
- super(props, context);
20
- this.onClick = this.onClick.bind(this);
21
- this.onDocumentDrop = this.onDocumentDrop.bind(this);
22
- this.onDragStart = this.onDragStart.bind(this);
23
- this.onDragEnter = this.onDragEnter.bind(this);
24
- this.onDragLeave = this.onDragLeave.bind(this);
25
- this.onDragOver = this.onDragOver.bind(this);
26
- this.onDrop = this.onDrop.bind(this);
27
- this.onFileDialogCancel = this.onFileDialogCancel.bind(this);
28
- this.fileAccepted = this.fileAccepted.bind(this);
29
- this.setRef = this.setRef.bind(this);
30
- this.isFileDialogActive = false;
25
+ super(props, context)
26
+ this.onClick = this.onClick.bind(this)
27
+ this.onDocumentDrop = this.onDocumentDrop.bind(this)
28
+ this.onDragStart = this.onDragStart.bind(this)
29
+ this.onDragEnter = this.onDragEnter.bind(this)
30
+ this.onDragLeave = this.onDragLeave.bind(this)
31
+ this.onDragOver = this.onDragOver.bind(this)
32
+ this.onDrop = this.onDrop.bind(this)
33
+ this.onFileDialogCancel = this.onFileDialogCancel.bind(this)
34
+ this.setRef = this.setRef.bind(this)
35
+ this.setRefs = this.setRefs.bind(this)
36
+ this.onInputElementClick = this.onInputElementClick.bind(this)
37
+ this.isFileDialogActive = false
31
38
  this.state = {
32
- isDragActive: false,
39
+ draggedFiles: [],
33
40
  acceptedFiles: [],
34
41
  rejectedFiles: []
35
- };
42
+ }
36
43
  }
37
44
 
38
45
  componentDidMount() {
39
- const { preventDropOnDocument } = this.props;
40
- this.dragTargets = [];
46
+ const { preventDropOnDocument } = this.props
47
+ this.dragTargets = []
41
48
 
42
49
  if (preventDropOnDocument) {
43
- document.addEventListener('dragover', Dropzone.onDocumentDragOver, false);
44
- document.addEventListener('drop', this.onDocumentDrop, false);
50
+ document.addEventListener('dragover', Dropzone.onDocumentDragOver, false)
51
+ document.addEventListener('drop', this.onDocumentDrop, false)
45
52
  }
53
+ this.fileInputEl.addEventListener('click', this.onInputElementClick, false)
46
54
  // Tried implementing addEventListener, but didn't work out
47
- document.body.onfocus = this.onFileDialogCancel;
55
+ document.body.onfocus = this.onFileDialogCancel
48
56
  }
49
57
 
50
58
  componentWillUnmount() {
51
- const { preventDropOnDocument } = this.props;
59
+ const { preventDropOnDocument } = this.props
52
60
  if (preventDropOnDocument) {
53
- document.removeEventListener('dragover', Dropzone.onDocumentDragOver);
54
- document.removeEventListener('drop', this.onDocumentDrop);
61
+ document.removeEventListener('dragover', Dropzone.onDocumentDragOver)
62
+ document.removeEventListener('drop', this.onDocumentDrop)
55
63
  }
64
+ this.fileInputEl.removeEventListener('click', this.onInputElementClick, false)
56
65
  // Can be replaced with removeEventListener, if addEventListener works
57
- document.body.onfocus = null;
66
+ document.body.onfocus = null
58
67
  }
59
68
 
60
- onDocumentDrop(e) {
61
- if (this.node.contains(e.target)) {
69
+ onDocumentDrop(evt) {
70
+ if (this.node.contains(evt.target)) {
62
71
  // if we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
63
- return;
72
+ return
64
73
  }
65
- e.preventDefault();
66
- this.dragTargets = [];
74
+ evt.preventDefault()
75
+ this.dragTargets = []
67
76
  }
68
77
 
69
- onDragStart(e) {
78
+ onDragStart(evt) {
70
79
  if (this.props.onDragStart) {
71
- this.props.onDragStart.call(this, e);
80
+ this.props.onDragStart.call(this, evt)
72
81
  }
73
82
  }
74
83
 
75
- onDragEnter(e) {
76
- e.preventDefault();
84
+ onDragEnter(evt) {
85
+ evt.preventDefault()
77
86
 
78
87
  // Count the dropzone and any children that are entered.
79
- if (this.dragTargets.indexOf(e.target) === -1) {
80
- this.dragTargets.push(e.target);
88
+ if (this.dragTargets.indexOf(evt.target) === -1) {
89
+ this.dragTargets.push(evt.target)
81
90
  }
82
91
 
83
- const allFilesAccepted = this.allFilesAccepted(getDataTransferItems(e, this.props.multiple));
84
-
85
- this.setState({
86
- isDragActive: allFilesAccepted,
87
- isDragReject: !allFilesAccepted
88
- });
92
+ this.setState({ draggedFiles: getDataTransferItems(evt) })
89
93
 
90
94
  if (this.props.onDragEnter) {
91
- this.props.onDragEnter.call(this, e);
95
+ this.props.onDragEnter.call(this, evt)
92
96
  }
93
97
  }
94
98
 
95
- onDragOver(e) { // eslint-disable-line class-methods-use-this
96
- e.preventDefault();
97
- e.stopPropagation();
99
+ onDragOver(evt) {
100
+ // eslint-disable-line class-methods-use-this
101
+ evt.preventDefault()
102
+ evt.stopPropagation()
98
103
  try {
99
- e.dataTransfer.dropEffect = 'copy'; // eslint-disable-line no-param-reassign
104
+ evt.dataTransfer.dropEffect = 'copy' // eslint-disable-line no-param-reassign
100
105
  } catch (err) {
101
106
  // continue regardless of error
102
107
  }
103
108
 
104
109
  if (this.props.onDragOver) {
105
- this.props.onDragOver.call(this, e);
110
+ this.props.onDragOver.call(this, evt)
106
111
  }
107
- return false;
112
+ return false
108
113
  }
109
114
 
110
- onDragLeave(e) {
111
- e.preventDefault();
115
+ onDragLeave(evt) {
116
+ evt.preventDefault()
112
117
 
113
118
  // Only deactivate once the dropzone and all children have been left.
114
- this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el));
119
+ this.dragTargets = this.dragTargets.filter(el => el !== evt.target && this.node.contains(el))
115
120
  if (this.dragTargets.length > 0) {
116
- return;
121
+ return
117
122
  }
118
123
 
119
- this.setState({
120
- isDragActive: false,
121
- isDragReject: false
122
- });
124
+ // Clear dragging files state
125
+ this.setState({ draggedFiles: [] })
123
126
 
124
127
  if (this.props.onDragLeave) {
125
- this.props.onDragLeave.call(this, e);
128
+ this.props.onDragLeave.call(this, evt)
126
129
  }
127
130
  }
128
131
 
129
- onDrop(e) {
130
- const { onDrop, onDropAccepted, onDropRejected, multiple, disablePreview } = this.props;
131
- const fileList = getDataTransferItems(e, multiple);
132
- const acceptedFiles = [];
133
- const rejectedFiles = [];
132
+ onDrop(evt) {
133
+ const { onDrop, onDropAccepted, onDropRejected, multiple, disablePreview, accept } = this.props
134
+ const fileList = getDataTransferItems(evt)
135
+ const acceptedFiles = []
136
+ const rejectedFiles = []
134
137
 
135
138
  // Stop default browser behavior
136
- e.preventDefault();
139
+ evt.preventDefault()
137
140
 
138
141
  // Reset the counter along with the drag on a drop.
139
- this.dragTargets = [];
140
- this.isFileDialogActive = false;
142
+ this.dragTargets = []
143
+ this.isFileDialogActive = false
141
144
 
142
- fileList.forEach((file) => {
145
+ fileList.forEach(file => {
143
146
  if (!disablePreview) {
144
147
  try {
145
- file.preview = window.URL.createObjectURL(file); // eslint-disable-line no-param-reassign
148
+ file.preview = window.URL.createObjectURL(file) // eslint-disable-line no-param-reassign
146
149
  } catch (err) {
147
150
  if (process.env.NODE_ENV !== 'production') {
148
- console.error('Failed to generate preview for file', file, err); // eslint-disable-line no-console
151
+ console.error('Failed to generate preview for file', file, err) // eslint-disable-line no-console
149
152
  }
150
153
  }
151
154
  }
152
155
 
153
- if (this.fileAccepted(file) && this.fileMatchSize(file)) {
154
- acceptedFiles.push(file);
156
+ if (fileAccepted(file, accept) && this.fileMatchSize(file)) {
157
+ acceptedFiles.push(file)
155
158
  } else {
156
- rejectedFiles.push(file);
159
+ rejectedFiles.push(file)
157
160
  }
158
- });
161
+ })
162
+
163
+ if (!multiple) {
164
+ // if not in multi mode add any extra accepted files to rejected.
165
+ // This will allow end users to easily ignore a multi file drop in "single" mode.
166
+ rejectedFiles.push(...acceptedFiles.splice(1))
167
+ }
159
168
 
160
169
  if (onDrop) {
161
- onDrop.call(this, acceptedFiles, rejectedFiles, e);
170
+ onDrop.call(this, acceptedFiles, rejectedFiles, evt)
162
171
  }
163
172
 
164
173
  if (rejectedFiles.length > 0 && onDropRejected) {
165
- onDropRejected.call(this, rejectedFiles, e);
174
+ onDropRejected.call(this, rejectedFiles, evt)
166
175
  }
167
176
 
168
177
  if (acceptedFiles.length > 0 && onDropAccepted) {
169
- onDropAccepted.call(this, acceptedFiles, e);
178
+ onDropAccepted.call(this, acceptedFiles, evt)
170
179
  }
171
180
 
181
+ // Clear files value
182
+ this.draggedFiles = null
183
+
172
184
  // Reset drag state
173
185
  this.setState({
174
- isDragActive: false,
175
- isDragReject: false,
186
+ draggedFiles: [],
176
187
  acceptedFiles,
177
188
  rejectedFiles
178
- });
189
+ })
179
190
  }
180
191
 
181
- onClick(e) {
182
- const { onClick, disableClick } = this.props;
192
+ onClick(evt) {
193
+ const { onClick, disableClick } = this.props
183
194
  if (!disableClick) {
184
- e.stopPropagation();
185
- this.open();
195
+ evt.stopPropagation()
196
+
186
197
  if (onClick) {
187
- onClick.call(this, e);
198
+ onClick.call(this, evt)
188
199
  }
200
+
201
+ // in IE11/Edge the file-browser dialog is blocking, ensure this is behind setTimeout
202
+ // this is so react can handle state changes in the onClick prop above above
203
+ // see: https://github.com/okonet/react-dropzone/issues/450
204
+ setTimeout(this.open.bind(this), 0)
205
+ }
206
+ }
207
+
208
+ onInputElementClick(evt) {
209
+ evt.stopPropagation()
210
+ if (this.props.inputProps && this.props.inputProps.onClick) {
211
+ this.props.inputProps.onClick()
189
212
  }
190
213
  }
191
214
 
192
215
  onFileDialogCancel() {
193
216
  // timeout will not recognize context of this method
194
- const { onFileDialogCancel } = this.props;
195
- const { fileInputEl } = this;
196
- let { isFileDialogActive } = this;
217
+ const { onFileDialogCancel } = this.props
218
+ const { fileInputEl } = this
219
+ let { isFileDialogActive } = this
197
220
  // execute the timeout only if the onFileDialogCancel is defined and FileDialog
198
221
  // is opened in the browser
199
222
  if (onFileDialogCancel && isFileDialogActive) {
200
223
  setTimeout(() => {
201
224
  // Returns an object as FileList
202
- const FileList = fileInputEl.files;
225
+ const FileList = fileInputEl.files
203
226
  if (!FileList.length) {
204
- isFileDialogActive = false;
205
- onFileDialogCancel();
227
+ isFileDialogActive = false
228
+ onFileDialogCancel()
206
229
  }
207
- }, 300);
230
+ }, 300)
208
231
  }
209
232
  }
210
233
 
211
234
  setRef(ref) {
212
- this.node = ref;
235
+ this.node = ref
213
236
  }
214
237
 
215
- fileAccepted(file) {
216
- // Firefox versions prior to 53 return a bogus MIME type for every file drag, so dragovers with
217
- // that MIME type will always be accepted
218
- return file.type === 'application/x-moz-file' || accepts(file, this.props.accept);
238
+ setRefs(ref) {
239
+ this.fileInputEl = ref
219
240
  }
220
241
 
221
242
  fileMatchSize(file) {
222
- return file.size <= this.props.maxSize && file.size >= this.props.minSize;
243
+ return file.size <= this.props.maxSize && file.size >= this.props.minSize
223
244
  }
224
245
 
225
246
  allFilesAccepted(files) {
226
- return files.every(this.fileAccepted);
247
+ return files.every(file => fileAccepted(file, this.props.accept))
227
248
  }
228
249
 
229
250
  /**
@@ -232,16 +253,16 @@ class Dropzone extends React.Component {
232
253
  * @public
233
254
  */
234
255
  open() {
235
- this.isFileDialogActive = true;
236
- this.fileInputEl.value = null;
237
- this.fileInputEl.click();
256
+ this.isFileDialogActive = true
257
+ this.fileInputEl.value = null
258
+ this.fileInputEl.click()
238
259
  }
239
260
 
240
- renderChildren = (children) => {
261
+ renderChildren = (children, isDragActive, isDragReject) => {
241
262
  if (typeof children === 'function') {
242
- return children(this.state);
263
+ return children({ ...this.state, isDragActive, isDragReject })
243
264
  }
244
- return children;
265
+ return children
245
266
  }
246
267
 
247
268
  render() {
@@ -254,7 +275,7 @@ class Dropzone extends React.Component {
254
275
  rejectClassName,
255
276
  children,
256
277
  ...rest
257
- } = this.props;
278
+ } = this.props
258
279
 
259
280
  let {
260
281
  activeStyle,
@@ -262,17 +283,21 @@ class Dropzone extends React.Component {
262
283
  rejectStyle,
263
284
  style,
264
285
  ...props // eslint-disable-line prefer-const
265
- } = rest;
286
+ } = rest
266
287
 
267
- const { isDragActive, isDragReject } = this.state;
288
+ const { draggedFiles } = this.state
289
+ const filesCount = draggedFiles.length
290
+ const isMultipleAllowed = multiple || filesCount <= 1
291
+ const isDragActive = filesCount > 0 && this.allFilesAccepted(draggedFiles)
292
+ const isDragReject = filesCount > 0 && (!isDragActive || !isMultipleAllowed)
268
293
 
269
- className = className || '';
294
+ className = className || ''
270
295
 
271
296
  if (isDragActive && activeClassName) {
272
- className += ' ' + activeClassName;
297
+ className += ' ' + activeClassName
273
298
  }
274
299
  if (isDragReject && rejectClassName) {
275
- className += ' ' + rejectClassName;
300
+ className += ' ' + rejectClassName
276
301
  }
277
302
 
278
303
  if (!className && !style && !activeStyle && !rejectStyle) {
@@ -283,34 +308,34 @@ class Dropzone extends React.Component {
283
308
  borderColor: '#666',
284
309
  borderStyle: 'dashed',
285
310
  borderRadius: 5
286
- };
311
+ }
287
312
  activeStyle = {
288
313
  borderStyle: 'solid',
289
314
  borderColor: '#6c6',
290
315
  backgroundColor: '#eee'
291
- };
316
+ }
292
317
  rejectStyle = {
293
318
  borderStyle: 'solid',
294
319
  borderColor: '#c66',
295
320
  backgroundColor: '#eee'
296
- };
321
+ }
297
322
  }
298
323
 
299
- let appliedStyle;
324
+ let appliedStyle
300
325
  if (activeStyle && isDragActive) {
301
326
  appliedStyle = {
302
327
  ...style,
303
328
  ...activeStyle
304
- };
329
+ }
305
330
  } else if (rejectStyle && isDragReject) {
306
331
  appliedStyle = {
307
332
  ...style,
308
333
  ...rejectStyle
309
- };
334
+ }
310
335
  } else {
311
336
  appliedStyle = {
312
337
  ...style
313
- };
338
+ }
314
339
  }
315
340
 
316
341
  const inputAttributes = {
@@ -318,12 +343,12 @@ class Dropzone extends React.Component {
318
343
  type: 'file',
319
344
  style: { display: 'none' },
320
345
  multiple: supportMultiple && multiple,
321
- ref: el => this.fileInputEl = el, // eslint-disable-line
346
+ ref: this.setRefs,
322
347
  onChange: this.onDrop
323
- };
348
+ }
324
349
 
325
350
  if (name && name.length) {
326
- inputAttributes.name = name;
351
+ inputAttributes.name = name
327
352
  }
328
353
 
329
354
  // Remove custom properties before passing them to the wrapper div element
@@ -337,15 +362,15 @@ class Dropzone extends React.Component {
337
362
  'onFileDialogCancel',
338
363
  'maxSize',
339
364
  'minSize'
340
- ];
341
- const divProps = { ...props };
342
- customProps.forEach(prop => delete divProps[prop]);
365
+ ]
366
+ const divProps = { ...props }
367
+ customProps.forEach(prop => delete divProps[prop])
343
368
 
344
369
  return (
345
370
  <div
346
371
  className={className}
347
372
  style={appliedStyle}
348
- {...divProps/* expand user provided props first so event handlers are never overridden */}
373
+ {...divProps /* expand user provided props first so event handlers are never overridden */}
349
374
  onClick={this.onClick}
350
375
  onDragStart={this.onDragStart}
351
376
  onDragEnter={this.onDragEnter}
@@ -354,13 +379,13 @@ class Dropzone extends React.Component {
354
379
  onDrop={this.onDrop}
355
380
  ref={this.setRef}
356
381
  >
357
- {this.renderChildren(children)}
382
+ {this.renderChildren(children, isDragActive, isDragReject)}
358
383
  <input
359
- {...inputProps/* expand user provided inputProps first so inputAttributes override them */}
384
+ {...inputProps /* expand user provided inputProps first so inputAttributes override them */}
360
385
  {...inputAttributes}
361
386
  />
362
387
  </div>
363
- );
388
+ )
364
389
  }
365
390
  }
366
391
 
@@ -377,10 +402,7 @@ Dropzone.propTypes = {
377
402
  /**
378
403
  * Contents of the dropzone
379
404
  */
380
- children: PropTypes.oneOfType([
381
- PropTypes.node,
382
- PropTypes.func
383
- ]),
405
+ children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
384
406
 
385
407
  /**
386
408
  * Disallow clicking on the dropzone container to open file dialog
@@ -497,7 +519,7 @@ Dropzone.propTypes = {
497
519
  * Provide a callback on clicking the cancel button of the file dialog
498
520
  */
499
521
  onFileDialogCancel: PropTypes.func
500
- };
522
+ }
501
523
 
502
524
  Dropzone.defaultProps = {
503
525
  preventDropOnDocument: true,
@@ -506,6 +528,6 @@ Dropzone.defaultProps = {
506
528
  multiple: true,
507
529
  maxSize: Infinity,
508
530
  minSize: 0
509
- };
531
+ }
510
532
 
511
- export default Dropzone;
533
+ export default Dropzone