Skip to content

Commit

Permalink
feat(useDropZone): add dataTypes option (#3471)
Browse files Browse the repository at this point in the history
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com>
  • Loading branch information
rtugeek and antfu committed Nov 9, 2023
1 parent 75ca2bb commit 3f3f153
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 19 deletions.
89 changes: 76 additions & 13 deletions packages/core/useDropZone/demo.vue
@@ -1,8 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue'
import { useDropZone } from '@vueuse/core'
import { useDropZone, useEventListener } from '@vueuse/core'
const filesData = ref<{ name: string; size: number; type: string; lastModified: number }[]>([])
const imageFilesData = ref<{ name: string; size: number; type: string; lastModified: number }[]>([])
function onDrop(files: File[] | null) {
filesData.value = []
if (files) {
Expand All @@ -15,25 +17,86 @@ function onDrop(files: File[] | null) {
}
}
function onImageDrop(files: File[] | null) {
imageFilesData.value = []
if (files) {
imageFilesData.value = files.map(file => ({
name: file.name,
size: file.size,
type: file.type,
lastModified: file.lastModified,
}))
}
}
const dropZoneRef = ref<HTMLElement>()
const imageDropZoneRef = ref<HTMLElement>()
const pngRef = ref()
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
useEventListener(pngRef, 'dragstart', (event) => {
event.dataTransfer?.setData('image/png', '/vue.png')
})
const { isOverDropZone: isOverImageDropZone } = useDropZone(imageDropZoneRef, { dataTypes: ['image/png'], onDrop: onImageDrop })
</script>

<template>
<div class="flex">
<div class="flex flex-col gap-2">
<div class="w-full h-auto relative">
<p>Drop files into dropZone</p>
<img src="/favicon.ico" alt="Drop me">

<div ref="dropZoneRef" class="flex flex-col w-full min-h-200px h-auto bg-gray-400/10 justify-center items-center mt-6">
<div> isOverDropZone: <BooleanDisplay :value="isOverDropZone" /></div>
<div class="flex flex-wrap justify-center items-center">
<div v-for="(file, index) in filesData" :key="index" class="w-200px bg-black-200/10 ma-2 pa-6">
<p>Name: {{ file.name }}</p>
<p>Size: {{ file.size }}</p>
<p>Type: {{ file.type }}</p>
<p>Last modified: {{ file.lastModified }}</p>
<p>Drop files on to drop zones</p>

<div class="flex gap-6">
<div class="flex flex-col items-center">
<img ref="pngRef" src="/vue.png" alt="Drag me" h-10>
<span>PNG</span>
</div>
<div class="flex flex-col items-center">
<img src="/favicon.svg" alt="Drag me" h-10>
<span>SVG</span>
</div>
</div>

<div grid="~ cols-2 gap-2">
<div
ref="dropZoneRef"
class="flex flex-col w-full min-h-200px h-auto bg-gray-400/10 justify-center items-center mt-6 rounded"
>
<div font-bold mb2>
General DropZone
</div>
<div>
isOverDropZone:
<BooleanDisplay :value="isOverDropZone" />
</div>
<div class="flex flex-wrap justify-center items-center">
<div v-for="(file, index) in filesData" :key="index" class="w-200px bg-black-200/10 ma-2 pa-6">
<p>Name: {{ file.name }}</p>
<p>Size: {{ file.size }}</p>
<p>Type: {{ file.type }}</p>
<p>Last modified: {{ file.lastModified }}</p>
</div>
</div>
</div>
<div
ref="imageDropZoneRef"
class="flex flex-col w-full min-h-200px h-auto bg-gray-400/10 justify-center items-center mt-6 rounded"
>
<div font-bold mb2>
Image DropZone
</div>
<div>
isOverDropZone:
<BooleanDisplay :value="isOverImageDropZone" />
</div>
<div class="flex flex-wrap justify-center items-center">
<div v-for="(file, index) in imageFilesData" :key="index" class="w-200px bg-black-200/10 ma-2 pa-6">
<p>Name: {{ file.name }}</p>
<p>Size: {{ file.size }}</p>
<p>Type: {{ file.type }}</p>
<p>Last modified: {{ file.lastModified }}</p>
</div>
</div>
</div>
</div>
Expand Down
6 changes: 5 additions & 1 deletion packages/core/useDropZone/index.md
Expand Up @@ -18,7 +18,11 @@ function onDrop(files: File[] | null) {
// called when files are dropped on zone
}
const { isOverDropZone } = useDropZone(dropZoneRef, onDrop)
const { isOverDropZone } = useDropZone(dropZoneRef, {
onDrop,
// specify the types of data to be received.
dataTypes: ['image/jpeg']
})
</script>

<template>
Expand Down
33 changes: 28 additions & 5 deletions packages/core/useDropZone/index.ts
@@ -1,15 +1,24 @@
import { ref, shallowRef } from 'vue-demi'
import type { Ref } from 'vue-demi'
import type { MaybeRef, Ref } from 'vue-demi'

// eslint-disable-next-line no-restricted-imports
import { ref, shallowRef, unref } from 'vue-demi'
import type { MaybeRefOrGetter } from '@vueuse/shared'
import { isClient } from '@vueuse/shared'
import { useEventListener } from '../useEventListener'

// eslint-disable-next-line no-restricted-imports
import { useEventListener } from '@vueuse/core'

export interface UseDropZoneReturn {
files: Ref<File[] | null>
isOverDropZone: Ref<boolean>
}

export interface UseDropZoneOptions {
/**
* Allowed data types, if not set, all data types are allowed.
* Also can be a function to check the data types.
*/
dataTypes?: MaybeRef<string[]> | ((types: readonly string[]) => boolean)
onDrop?: (files: File[] | null, event: DragEvent) => void
onEnter?: (files: File[] | null, event: DragEvent) => void
onLeave?: (files: File[] | null, event: DragEvent) => void
Expand All @@ -23,25 +32,39 @@ export function useDropZone(
const isOverDropZone = ref(false)
const files = shallowRef<File[] | null>(null)
let counter = 0

let isDataTypeIncluded = true
if (isClient) {
const _options = typeof options === 'function' ? { onDrop: options } : options
const getFiles = (event: DragEvent) => {
const list = Array.from(event.dataTransfer?.files ?? [])
return files.value = list.length === 0 ? null : list
return (files.value = list.length === 0 ? null : list)
}

useEventListener<DragEvent>(target, 'dragenter', (event) => {
if (_options.dataTypes && event.dataTransfer) {
const dataTypes = unref(_options.dataTypes)
isDataTypeIncluded = typeof dataTypes === 'function'
? dataTypes(event.dataTransfer!.types)
: dataTypes
? dataTypes.some(item => event.dataTransfer!.types.includes(item))
: true
if (!isDataTypeIncluded)
return
}
event.preventDefault()
counter += 1
isOverDropZone.value = true
_options.onEnter?.(getFiles(event), event)
})
useEventListener<DragEvent>(target, 'dragover', (event) => {
if (!isDataTypeIncluded)
return
event.preventDefault()
_options.onOver?.(getFiles(event), event)
})
useEventListener<DragEvent>(target, 'dragleave', (event) => {
if (!isDataTypeIncluded)
return
event.preventDefault()
counter -= 1
if (counter === 0)
Expand Down

0 comments on commit 3f3f153

Please sign in to comment.