@@ -3,7 +3,7 @@ import type { Ref } from 'vue-demi'
33// eslint-disable-next-line no-restricted-imports
44import { ref , shallowRef , unref } from 'vue-demi'
55import type { MaybeRef , MaybeRefOrGetter } from '@vueuse/shared'
6- import { isClient , notNullish } from '@vueuse/shared'
6+ import { isClient } from '@vueuse/shared'
77
88import { useEventListener } from '../useEventListener'
99
@@ -22,6 +22,14 @@ export interface UseDropZoneOptions {
2222 onEnter ?: ( files : File [ ] | null , event : DragEvent ) => void
2323 onLeave ?: ( files : File [ ] | null , event : DragEvent ) => void
2424 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
2533}
2634
2735export function useDropZone (
@@ -31,59 +39,92 @@ export function useDropZone(
3139 const isOverDropZone = ref ( false )
3240 const files = shallowRef < File [ ] | null > ( null )
3341 let counter = 0
34- let isDataTypeIncluded = true
42+ let isValid = true
43+
3544 if ( isClient ) {
3645 const _options = typeof options === 'function' ? { onDrop : options } : options
46+ const multiple = _options . multiple ?? true
47+ const preventDefaultForUnhandled = _options . preventDefaultForUnhandled ?? false
48+
3749 const getFiles = ( event : DragEvent ) => {
3850 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 ] ] )
4052 }
4153
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 ) {
4856 const dataTypes = unref ( _options . dataTypes )
49- isDataTypeIncluded = typeof dataTypes === 'function'
57+ return typeof dataTypes === 'function'
5058 ? dataTypes ( types )
5159 : dataTypes
5260 ? dataTypes . some ( item => types . includes ( item ) )
5361 : true
54- if ( ! isDataTypeIncluded )
55- return
5662 }
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+ }
7288 return
89+ }
90+
7391 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' ) )
87128 }
88129
89130 return {
0 commit comments