@@ -3,7 +3,7 @@ import type { Ref } from 'vue-demi'
3
3
// eslint-disable-next-line no-restricted-imports
4
4
import { ref , shallowRef , unref } from 'vue-demi'
5
5
import type { MaybeRef , MaybeRefOrGetter } from '@vueuse/shared'
6
- import { isClient , notNullish } from '@vueuse/shared'
6
+ import { isClient } from '@vueuse/shared'
7
7
8
8
import { useEventListener } from '../useEventListener'
9
9
@@ -22,6 +22,14 @@ export interface UseDropZoneOptions {
22
22
onEnter ?: ( files : File [ ] | null , event : DragEvent ) => void
23
23
onLeave ?: ( files : File [ ] | null , event : DragEvent ) => void
24
24
onOver ?: ( files : File [ ] | null , event : DragEvent ) => void
25
+ /**
26
+ * Allow multiple files to be dropped. Defaults to true.
27
+ */
28
+ multiple ?: boolean
29
+ /**
30
+ * Prevent default behavior for unhandled events. Defaults to false.
31
+ */
32
+ preventDefaultForUnhandled ?: boolean
25
33
}
26
34
27
35
export function useDropZone (
@@ -31,59 +39,92 @@ export function useDropZone(
31
39
const isOverDropZone = ref ( false )
32
40
const files = shallowRef < File [ ] | null > ( null )
33
41
let counter = 0
34
- let isDataTypeIncluded = true
42
+ let isValid = true
43
+
35
44
if ( isClient ) {
36
45
const _options = typeof options === 'function' ? { onDrop : options } : options
46
+ const multiple = _options . multiple ?? true
47
+ const preventDefaultForUnhandled = _options . preventDefaultForUnhandled ?? false
48
+
37
49
const getFiles = ( event : DragEvent ) => {
38
50
const list = Array . from ( event . dataTransfer ?. files ?? [ ] )
39
- return ( files . value = list . length === 0 ? null : list )
51
+ return list . length === 0 ? null : ( multiple ? list : [ list [ 0 ] ] )
40
52
}
41
53
42
- useEventListener < DragEvent > ( target , 'dragenter' , ( event ) => {
43
- const types = Array . from ( event ?. dataTransfer ?. items || [ ] )
44
- . map ( i => i . kind === 'file' ? i . type : null )
45
- . filter ( notNullish )
46
-
47
- if ( _options . dataTypes && event . dataTransfer ) {
54
+ const checkDataTypes = ( types : string [ ] ) => {
55
+ if ( _options . dataTypes ) {
48
56
const dataTypes = unref ( _options . dataTypes )
49
- isDataTypeIncluded = typeof dataTypes === 'function'
57
+ return typeof dataTypes === 'function'
50
58
? dataTypes ( types )
51
59
: dataTypes
52
60
? dataTypes . some ( item => types . includes ( item ) )
53
61
: true
54
- if ( ! isDataTypeIncluded )
55
- return
56
62
}
57
- event . preventDefault ( )
58
- counter += 1
59
- isOverDropZone . value = true
60
- const files = getFiles ( event )
61
- _options . onEnter ?.( files , event )
62
- } )
63
- useEventListener < DragEvent > ( target , 'dragover' , ( event ) => {
64
- if ( ! isDataTypeIncluded )
65
- return
66
- event . preventDefault ( )
67
- const files = getFiles ( event )
68
- _options . onOver ?.( files , event )
69
- } )
70
- useEventListener < DragEvent > ( target , 'dragleave' , ( event ) => {
71
- if ( ! isDataTypeIncluded )
63
+ return true
64
+ }
65
+
66
+ const checkValidity = ( event : DragEvent ) => {
67
+ const items = Array . from ( event . dataTransfer ?. items ?? [ ] )
68
+ const types = items
69
+ . filter ( item => item . kind === 'file' )
70
+ . map ( item => item . type )
71
+
72
+ const dataTypesValid = checkDataTypes ( types )
73
+ const multipleFilesValid = multiple || items . filter ( item => item . kind === 'file' ) . length <= 1
74
+
75
+ return dataTypesValid && multipleFilesValid
76
+ }
77
+
78
+ const handleDragEvent = ( event : DragEvent , eventType : 'enter' | 'over' | 'leave' | 'drop' ) => {
79
+ isValid = checkValidity ( event )
80
+
81
+ if ( ! isValid ) {
82
+ if ( preventDefaultForUnhandled ) {
83
+ event . preventDefault ( )
84
+ }
85
+ if ( event . dataTransfer ) {
86
+ event . dataTransfer . dropEffect = 'none'
87
+ }
72
88
return
89
+ }
90
+
73
91
event . preventDefault ( )
74
- counter -= 1
75
- if ( counter === 0 )
76
- isOverDropZone . value = false
77
- const files = getFiles ( event )
78
- _options . onLeave ?.( files , event )
79
- } )
80
- useEventListener < DragEvent > ( target , 'drop' , ( event ) => {
81
- event . preventDefault ( )
82
- counter = 0
83
- isOverDropZone . value = false
84
- const files = getFiles ( event )
85
- _options . onDrop ?.( files , event )
86
- } )
92
+ if ( event . dataTransfer ) {
93
+ event . dataTransfer . dropEffect = 'copy'
94
+ }
95
+
96
+ const currentFiles = getFiles ( event )
97
+
98
+ switch ( eventType ) {
99
+ case 'enter' :
100
+ counter += 1
101
+ isOverDropZone . value = true
102
+ _options . onEnter ?.( null , event )
103
+ break
104
+ case 'over' :
105
+ _options . onOver ?.( null , event )
106
+ break
107
+ case 'leave' :
108
+ counter -= 1
109
+ if ( counter === 0 )
110
+ isOverDropZone . value = false
111
+ _options . onLeave ?.( null , event )
112
+ break
113
+ case 'drop' :
114
+ counter = 0
115
+ isOverDropZone . value = false
116
+ if ( isValid ) {
117
+ files . value = currentFiles
118
+ _options . onDrop ?.( currentFiles , event )
119
+ }
120
+ break
121
+ }
122
+ }
123
+
124
+ useEventListener < DragEvent > ( target , 'dragenter' , event => handleDragEvent ( event , 'enter' ) )
125
+ useEventListener < DragEvent > ( target , 'dragover' , event => handleDragEvent ( event , 'over' ) )
126
+ useEventListener < DragEvent > ( target , 'dragleave' , event => handleDragEvent ( event , 'leave' ) )
127
+ useEventListener < DragEvent > ( target , 'drop' , event => handleDragEvent ( event , 'drop' ) )
87
128
}
88
129
89
130
return {
0 commit comments