react-dropzone 7.0.1 → 8.0.3

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
@@ -10,35 +10,15 @@ import {
10
10
  fileMatchSize,
11
11
  onDocumentDragOver,
12
12
  getDataTransferItems as defaultGetDataTransferItem,
13
- isIeOrEdge
13
+ isIeOrEdge,
14
+ composeEventHandlers
14
15
  } from './utils'
15
- import styles from './utils/styles'
16
16
 
17
17
  class Dropzone extends React.Component {
18
- constructor(props, context) {
19
- super(props, context)
20
- this.composeHandlers = this.composeHandlers.bind(this)
21
- this.onClick = this.onClick.bind(this)
22
- this.onDocumentDrop = this.onDocumentDrop.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.onDragStart = this.onDragStart.bind(this)
27
- this.onDrop = this.onDrop.bind(this)
28
- this.onFileDialogCancel = this.onFileDialogCancel.bind(this)
29
- this.onInputElementClick = this.onInputElementClick.bind(this)
30
- this.open = this.open.bind(this)
31
-
32
- this.setRef = this.setRef.bind(this)
33
- this.setRefs = this.setRefs.bind(this)
34
-
35
- this.isFileDialogActive = false
36
-
37
- this.state = {
38
- draggedFiles: [],
39
- acceptedFiles: [],
40
- rejectedFiles: []
41
- }
18
+ state = {
19
+ draggedFiles: [],
20
+ acceptedFiles: [],
21
+ rejectedFiles: []
42
22
  }
43
23
 
44
24
  componentDidMount() {
@@ -49,9 +29,7 @@ class Dropzone extends React.Component {
49
29
  document.addEventListener('dragover', onDocumentDragOver, false)
50
30
  document.addEventListener('drop', this.onDocumentDrop, false)
51
31
  }
52
- if (this.fileInputEl != null) {
53
- this.fileInputEl.addEventListener('click', this.onInputElementClick, false)
54
- }
32
+
55
33
  window.addEventListener('focus', this.onFileDialogCancel, false)
56
34
  }
57
35
 
@@ -61,21 +39,13 @@ class Dropzone extends React.Component {
61
39
  document.removeEventListener('dragover', onDocumentDragOver)
62
40
  document.removeEventListener('drop', this.onDocumentDrop)
63
41
  }
64
- if (this.fileInputEl != null) {
65
- this.fileInputEl.removeEventListener('click', this.onInputElementClick, false)
66
- }
42
+
67
43
  window.removeEventListener('focus', this.onFileDialogCancel, false)
68
44
  }
69
45
 
70
- composeHandlers(handler) {
71
- if (this.props.disabled) {
72
- return null
73
- }
74
-
75
- return handler
76
- }
46
+ isFileDialogActive = false
77
47
 
78
- onDocumentDrop(evt) {
48
+ onDocumentDrop = evt => {
79
49
  if (this.node && this.node.contains(evt.target)) {
80
50
  // if we intercepted an event for our instance, let it propagate down to the instance's onDrop handler
81
51
  return
@@ -84,14 +54,14 @@ class Dropzone extends React.Component {
84
54
  this.dragTargets = []
85
55
  }
86
56
 
87
- onDragStart(evt) {
57
+ onDragStart = evt => {
88
58
  evt.persist()
89
59
  if (this.props.onDragStart && isDragDataWithFiles(evt)) {
90
60
  this.props.onDragStart.call(this, evt)
91
61
  }
92
62
  }
93
63
 
94
- onDragEnter(evt) {
64
+ onDragEnter = evt => {
95
65
  evt.preventDefault()
96
66
 
97
67
  // Count the dropzone and any children that are entered.
@@ -120,20 +90,11 @@ class Dropzone extends React.Component {
120
90
  }
121
91
  }
122
92
 
123
- onDragOver(evt) {
93
+ onDragOver = evt => {
124
94
  // eslint-disable-line class-methods-use-this
125
95
  evt.preventDefault()
126
96
  evt.persist()
127
97
 
128
- try {
129
- // The file dialog on Chrome allows users to drag files from the dialog onto
130
- // the dropzone, causing the browser the crash when the file dialog is closed.
131
- // A drop effect of 'none' prevents the file from being dropped
132
- evt.dataTransfer.dropEffect = this.isFileDialogActive ? 'none' : 'copy' // eslint-disable-line no-param-reassign
133
- } catch (err) {
134
- // continue regardless of error
135
- }
136
-
137
98
  if (this.props.onDragOver && isDragDataWithFiles(evt)) {
138
99
  this.props.onDragOver.call(this, evt)
139
100
  }
@@ -141,7 +102,7 @@ class Dropzone extends React.Component {
141
102
  return false
142
103
  }
143
104
 
144
- onDragLeave(evt) {
105
+ onDragLeave = evt => {
145
106
  evt.preventDefault()
146
107
  evt.persist()
147
108
 
@@ -162,7 +123,7 @@ class Dropzone extends React.Component {
162
123
  }
163
124
  }
164
125
 
165
- onDrop(evt) {
126
+ onDrop = evt => {
166
127
  const {
167
128
  onDrop,
168
129
  onDropAccepted,
@@ -237,7 +198,7 @@ class Dropzone extends React.Component {
237
198
  }
238
199
  }
239
200
 
240
- onClick(evt) {
201
+ onClick = evt => {
241
202
  const { onClick, disableClick } = this.props
242
203
 
243
204
  // if onClick prop is given, run it first
@@ -261,22 +222,19 @@ class Dropzone extends React.Component {
261
222
  }
262
223
  }
263
224
 
264
- onInputElementClick(evt) {
225
+ onInputElementClick = evt => {
265
226
  evt.stopPropagation()
266
- if (this.props.inputProps && this.props.inputProps.onClick) {
267
- this.props.inputProps.onClick(evt)
268
- }
269
227
  }
270
228
 
271
- onFileDialogCancel() {
229
+ onFileDialogCancel = () => {
272
230
  // timeout will not recognize context of this method
273
231
  const { onFileDialogCancel } = this.props
274
232
  // execute the timeout only if the FileDialog is opened in the browser
275
233
  if (this.isFileDialogActive) {
276
234
  setTimeout(() => {
277
- if (this.fileInputEl != null) {
235
+ if (this.input != null) {
278
236
  // Returns an object as FileList
279
- const { files } = this.fileInputEl
237
+ const { files } = this.input
280
238
 
281
239
  if (!files.length) {
282
240
  this.isFileDialogActive = false
@@ -290,184 +248,154 @@ class Dropzone extends React.Component {
290
248
  }
291
249
  }
292
250
 
293
- setRef(ref) {
294
- this.node = ref
295
- }
296
-
297
- setRefs(ref) {
298
- this.fileInputEl = ref
299
- }
300
- /**
301
- * Open system file upload dialog.
302
- *
303
- * @public
304
- */
305
- open() {
306
- this.isFileDialogActive = true
307
- this.fileInputEl.value = null
308
- this.fileInputEl.click()
309
- }
310
-
311
- renderChildren = (children, isDragActive, isDragAccept, isDragReject) => {
312
- if (typeof children === 'function') {
313
- return children({
314
- ...this.state,
315
- isDragActive,
316
- isDragAccept,
317
- isDragReject,
318
- open: this.open
319
- })
251
+ onFocus = evt => {
252
+ const { onFocus } = this.props
253
+ if (onFocus) {
254
+ onFocus.call(this, evt)
255
+ }
256
+ if (!evt.isDefaultPrevented()) {
257
+ this.setState({ isFocused: true })
320
258
  }
321
- return children
322
259
  }
323
260
 
324
- render() {
325
- const {
326
- accept,
327
- acceptClassName,
328
- activeClassName,
329
- children,
330
- disabled,
331
- disabledClassName,
332
- inputProps,
333
- multiple,
334
- name,
335
- rejectClassName,
336
- ...rest
337
- } = this.props
338
-
339
- let {
340
- acceptStyle,
341
- activeStyle,
342
- className = '',
343
- disabledStyle,
344
- rejectStyle,
345
- style,
346
- ...props // eslint-disable-line prefer-const
347
- } = rest
348
-
349
- const { isDragActive, draggedFiles } = this.state
350
- const filesCount = draggedFiles.length
351
- const isMultipleAllowed = multiple || filesCount <= 1
352
- const isDragAccept = filesCount > 0 && allFilesAccepted(draggedFiles, this.props.accept)
353
- const isDragReject = filesCount > 0 && (!isDragAccept || !isMultipleAllowed)
354
- const noStyles =
355
- !className && !style && !activeStyle && !acceptStyle && !rejectStyle && !disabledStyle
356
-
357
- if (isDragActive && activeClassName) {
358
- className += ' ' + activeClassName
261
+ onBlur = evt => {
262
+ const { onBlur } = this.props
263
+ if (onBlur) {
264
+ onBlur.call(this, evt)
359
265
  }
360
- if (isDragAccept && acceptClassName) {
361
- className += ' ' + acceptClassName
362
- }
363
- if (isDragReject && rejectClassName) {
364
- className += ' ' + rejectClassName
365
- }
366
- if (disabled && disabledClassName) {
367
- className += ' ' + disabledClassName
266
+ if (!evt.isDefaultPrevented()) {
267
+ this.setState({ isFocused: false })
368
268
  }
269
+ }
369
270
 
370
- if (noStyles) {
371
- style = styles.default
372
- activeStyle = styles.active
373
- acceptStyle = styles.accepted
374
- rejectStyle = styles.rejected
375
- disabledStyle = styles.disabled
271
+ onKeyDown = evt => {
272
+ const { onKeyDown } = this.props
273
+ if (!this.node.isEqualNode(evt.target)) {
274
+ return
376
275
  }
377
276
 
378
- let appliedStyle = { position: 'relative', ...style }
379
- if (activeStyle && isDragActive) {
380
- appliedStyle = {
381
- ...appliedStyle,
382
- ...activeStyle
383
- }
384
- }
385
- if (acceptStyle && isDragAccept) {
386
- appliedStyle = {
387
- ...appliedStyle,
388
- ...acceptStyle
389
- }
277
+ if (onKeyDown) {
278
+ onKeyDown.call(this, evt)
390
279
  }
391
- if (rejectStyle && isDragReject) {
392
- appliedStyle = {
393
- ...appliedStyle,
394
- ...rejectStyle
395
- }
280
+
281
+ if (!evt.isDefaultPrevented() && (evt.keyCode === 32 || evt.keyCode === 13)) {
282
+ evt.preventDefault()
283
+ this.open()
396
284
  }
397
- if (disabledStyle && disabled) {
398
- appliedStyle = {
399
- ...appliedStyle,
400
- ...disabledStyle
401
- }
285
+ }
286
+
287
+ composeHandler = handler => {
288
+ if (this.props.disabled) {
289
+ return null
402
290
  }
291
+ return handler
292
+ }
403
293
 
404
- const inputAttributes = {
294
+ getRootProps = ({
295
+ refKey = 'ref',
296
+ onKeyDown,
297
+ onFocus,
298
+ onBlur,
299
+ onClick,
300
+ onDragStart,
301
+ onDragEnter,
302
+ onDragOver,
303
+ onDragLeave,
304
+ onDrop,
305
+ ...rest
306
+ } = {}) => ({
307
+ onKeyDown: this.composeHandler(
308
+ onKeyDown ? composeEventHandlers(onKeyDown, this.onKeyDown) : this.onKeyDown
309
+ ),
310
+ onFocus: this.composeHandler(
311
+ onFocus ? composeEventHandlers(onFocus, this.onFocus) : this.onFocus
312
+ ),
313
+ onBlur: this.composeHandler(onBlur ? composeEventHandlers(onBlur, this.onBlur) : this.onBlur),
314
+ onClick: this.composeHandler(
315
+ onClick ? composeEventHandlers(onClick, this.onClick) : this.onClick
316
+ ),
317
+ onDragStart: this.composeHandler(
318
+ onDragStart ? composeEventHandlers(onDragStart, this.onDragStart) : this.onDragStart
319
+ ),
320
+ onDragEnter: this.composeHandler(
321
+ onDragEnter ? composeEventHandlers(onDragEnter, this.onDragEnter) : this.onDragEnter
322
+ ),
323
+ onDragOver: this.composeHandler(
324
+ onDragOver ? composeEventHandlers(onDragOver, this.onDragOver) : this.onDragOver
325
+ ),
326
+ onDragLeave: this.composeHandler(
327
+ onDragLeave ? composeEventHandlers(onDragLeave, this.onDragLeave) : this.onDragLeave
328
+ ),
329
+ onDrop: this.composeHandler(onDrop ? composeEventHandlers(onDrop, this.onDrop) : this.onDrop),
330
+ [refKey]: this.setNodeRef,
331
+ tabIndex: this.props.disabled ? -1 : 0,
332
+ ...rest
333
+ })
334
+
335
+ getInputProps = ({ refKey = 'ref', onChange, onClick, ...rest } = {}) => {
336
+ const { accept, multiple, name } = this.props
337
+ const inputProps = {
405
338
  accept,
406
- disabled,
407
339
  type: 'file',
408
- style: {
409
- position: 'absolute',
410
- top: 0,
411
- right: 0,
412
- bottom: 0,
413
- left: 0,
414
- opacity: 0.00001,
415
- pointerEvents: 'none',
416
- ...inputProps.style
417
- },
340
+ style: { display: 'none' },
418
341
  multiple: supportMultiple && multiple,
419
- ref: this.setRefs,
420
- onChange: this.onDrop,
421
- autoComplete: 'off'
342
+ onChange: composeEventHandlers(onChange, this.onDrop),
343
+ onClick: composeEventHandlers(onClick, this.onInputElementClick),
344
+ autoComplete: 'off',
345
+ tabIndex: -1,
346
+ [refKey]: this.setInputRef
422
347
  }
423
-
424
348
  if (name && name.length) {
425
- inputAttributes.name = name
349
+ inputProps.name = name
350
+ }
351
+ return {
352
+ ...inputProps,
353
+ ...rest
426
354
  }
355
+ }
427
356
 
428
- // Destructure custom props away from props used for the div element
429
- /* eslint-disable no-unused-vars */
430
- const {
357
+ setNodeRef = node => {
358
+ this.node = node
359
+ }
360
+
361
+ setInputRef = input => {
362
+ this.input = input
363
+ }
364
+
365
+ /**
366
+ * Open system file upload dialog.
367
+ *
368
+ * @public
369
+ */
370
+ open = () => {
371
+ this.isFileDialogActive = true
372
+ if (this.input) {
373
+ this.input.value = null
374
+ this.input.click()
375
+ }
376
+ }
377
+
378
+ render() {
379
+ const { children, multiple, disabled } = this.props
380
+ const { isDragActive, isFocused, draggedFiles, acceptedFiles, rejectedFiles } = this.state
381
+
382
+ const filesCount = draggedFiles.length
383
+ const isMultipleAllowed = multiple || filesCount <= 1
384
+ const isDragAccept = filesCount > 0 && allFilesAccepted(draggedFiles, this.props.accept)
385
+ const isDragReject = filesCount > 0 && (!isDragAccept || !isMultipleAllowed)
386
+
387
+ return children({
388
+ isDragActive,
389
+ isDragAccept,
390
+ isDragReject,
391
+ draggedFiles,
431
392
  acceptedFiles,
432
- preventDropOnDocument,
433
- disableClick,
434
- onDropAccepted,
435
- onDropRejected,
436
- onFileDialogCancel,
437
- maxSize,
438
- minSize,
439
- getDataTransferItems,
440
- ...divProps
441
- } = props
442
- /* eslint-enable no-unused-vars */
443
-
444
- /* eslint-disable jsx-a11y/no-static-element-interactions */
445
- /* eslint-disable jsx-a11y/click-events-have-key-events */
446
- return (
447
- <div
448
- className={className}
449
- style={appliedStyle}
450
- {
451
- ...divProps /* expand user provided props first so event handlers are never overridden */
452
- }
453
- onClick={this.composeHandlers(this.onClick)}
454
- onDragStart={this.composeHandlers(this.onDragStart)}
455
- onDragEnter={this.composeHandlers(this.onDragEnter)}
456
- onDragOver={this.composeHandlers(this.onDragOver)}
457
- onDragLeave={this.composeHandlers(this.onDragLeave)}
458
- onDrop={this.composeHandlers(this.onDrop)}
459
- ref={this.setRef}
460
- aria-disabled={disabled}
461
- >
462
- {this.renderChildren(children, isDragActive, isDragAccept, isDragReject)}
463
- <input
464
- {
465
- ...inputProps /* expand user provided inputProps first so inputAttributes override them */
466
- }
467
- {...inputAttributes}
468
- />
469
- </div>
470
- )
393
+ rejectedFiles,
394
+ isFocused: isFocused && !disabled,
395
+ getRootProps: this.getRootProps,
396
+ getInputProps: this.getInputProps,
397
+ open: this.open
398
+ })
471
399
  }
472
400
  }
473
401
 
@@ -484,9 +412,21 @@ Dropzone.propTypes = {
484
412
  accept: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
485
413
 
486
414
  /**
487
- * Contents of the dropzone
415
+ * Render function that renders the actual component
416
+ *
417
+ * @param {Object} props
418
+ * @param {Function} props.getRootProps Returns the props you should apply to the root drop container you render
419
+ * @param {Function} props.getInputProps Returns the props you should apply to hidden file input you render
420
+ * @param {Function} props.open Open the native file selection dialog
421
+ * @param {Boolean} props.isFocused Dropzone area is in focus
422
+ * @param {Boolean} props.isDragActive Active drag is in progress
423
+ * @param {Boolean} props.isDragAccept Dragged files are accepted
424
+ * @param {Boolean} props.isDragReject Some dragged files are rejected
425
+ * @param {Array} props.draggedFiles Files in active drag
426
+ * @param {Array} props.acceptedFiles Accepted files
427
+ * @param {Array} props.rejectedFiles Rejected files
488
428
  */
489
- children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
429
+ children: PropTypes.func,
490
430
 
491
431
  /**
492
432
  * Disallow clicking on the dropzone container to open file dialog
@@ -503,11 +443,6 @@ Dropzone.propTypes = {
503
443
  */
504
444
  preventDropOnDocument: PropTypes.bool,
505
445
 
506
- /**
507
- * Pass additional attributes to the `<input type="file"/>` tag
508
- */
509
- inputProps: PropTypes.object,
510
-
511
446
  /**
512
447
  * Allow dropping multiple files
513
448
  */
@@ -529,70 +464,61 @@ Dropzone.propTypes = {
529
464
  minSize: PropTypes.number,
530
465
 
531
466
  /**
532
- * className
533
- */
534
- className: PropTypes.string,
535
-
536
- /**
537
- * className to apply when drag is active
538
- */
539
- activeClassName: PropTypes.string,
540
-
541
- /**
542
- * className to apply when drop will be accepted
543
- */
544
- acceptClassName: PropTypes.string,
545
-
546
- /**
547
- * className to apply when drop will be rejected
548
- */
549
- rejectClassName: PropTypes.string,
550
-
551
- /**
552
- * className to apply when dropzone is disabled
553
- */
554
- disabledClassName: PropTypes.string,
555
-
556
- /**
557
- * CSS styles to apply
558
- */
559
- style: PropTypes.object,
560
-
561
- /**
562
- * CSS styles to apply when drag is active
563
- */
564
- activeStyle: PropTypes.object,
565
-
566
- /**
567
- * CSS styles to apply when drop will be accepted
467
+ * getDataTransferItems handler
468
+ * @param {Event} event
469
+ * @returns {Array} array of File objects
568
470
  */
569
- acceptStyle: PropTypes.object,
471
+ getDataTransferItems: PropTypes.func,
570
472
 
571
473
  /**
572
- * CSS styles to apply when drop will be rejected
474
+ * onClick callback
475
+ * @param {Event} event
573
476
  */
574
- rejectStyle: PropTypes.object,
477
+ onClick: PropTypes.func,
575
478
 
576
479
  /**
577
- * CSS styles to apply when dropzone is disabled
480
+ * onFocus callback
578
481
  */
579
- disabledStyle: PropTypes.object,
482
+ onFocus: PropTypes.func,
580
483
 
581
484
  /**
582
- * getDataTransferItems handler
583
- * @param {Event} event
584
- * @returns {Array} array of File objects
485
+ * onBlur callback
585
486
  */
586
- getDataTransferItems: PropTypes.func,
487
+ onBlur: PropTypes.func,
587
488
 
588
489
  /**
589
- * onClick callback
590
- * @param {Event} event
490
+ * onKeyDown callback
591
491
  */
592
- onClick: PropTypes.func,
492
+ onKeyDown: PropTypes.func,
593
493
 
594
494
  /**
595
- * onDrop callback
495
+ * The `onDrop` method that accepts two arguments.
496
+ * The first argument represents the accepted files and the second argument the rejected files.
497
+ *
498
+ * ```javascript
499
+ * function onDrop(acceptedFiles, rejectedFiles) {
500
+ * // do stuff with files...
501
+ * }
502
+ * ```
503
+ *
504
+ * Files are accepted or rejected based on the `accept` prop.
505
+ * 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.
506
+ *
507
+ * Note that the `onDrop` callback will always be called regardless if the dropped files were accepted or rejected.
508
+ * You can use the `onDropAccepted`/`onDropRejected` props if you'd like to react to a specific event instead of the `onDrop` prop.
509
+ *
510
+ * 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.
511
+ * For example, with [SuperAgent](https://github.com/visionmedia/superagent) as a http/ajax library:
512
+ *
513
+ * ```javascript
514
+ * function onDrop(acceptedFiles) {
515
+ * const req = request.post('/upload')
516
+ * acceptedFiles.forEach(file => {
517
+ * req.attach(file.name, file)
518
+ * })
519
+ * req.end(callback)
520
+ * }
521
+ * ```
596
522
  */
597
523
  onDrop: PropTypes.func,
598
524
 
@@ -636,7 +562,6 @@ Dropzone.defaultProps = {
636
562
  preventDropOnDocument: true,
637
563
  disabled: false,
638
564
  disableClick: false,
639
- inputProps: {},
640
565
  multiple: true,
641
566
  maxSize: Infinity,
642
567
  minSize: 0,