import { ref, computed, unref, onMounted, watch, reactive, effectScope } from "vue"; import { useFileDialog, useDropZone } from "@vueuse/core"; function isFileAccepted(file, accept) { const types = accept.split(",").map((type) => type.trim().toLowerCase()).filter(Boolean); if (!types.length) { return true; } const name = file.name.toLowerCase(); const mime = file.type.toLowerCase(); return types.some((type) => { if (type === "*" || type === "*/*") { return true; } if (type.startsWith(".")) { return name.endsWith(type); } if (type.endsWith("/*")) { return mime.startsWith(type.slice(0, -1)); } return mime === type; }); } export function useFileUpload(options) { const { accept = "*", reset = false, multiple = false, dropzone = true, onUpdate, onReject } = options; const inputRef = ref(); const dropzoneRef = ref(); const onDrop = (files, fromDropZone = false) => { if (!files || files.length === 0) { return; } if (files instanceof FileList) { files = Array.from(files); } if (fromDropZone) { const rejected = []; files = files.filter((file) => { if (isFileAccepted(file, unref(accept))) { return true; } rejected.push(file); return false; }); if (rejected.length) { onReject?.(rejected); } if (!files.length) { return; } } if (files.length > 1 && !unref(multiple)) { files = [files[0]]; } if (fromDropZone && inputRef.value?.$el) { try { const dt = new DataTransfer(); files.forEach((file) => dt.items.add(file)); inputRef.value.$el.files = dt.files; } catch (e) { console.warn("Could not sync files to input element:", e); } } onUpdate(files); }; const isDragging = ref(false); const fileDialog = reactive({ open: () => { } }); function open() { fileDialog.open(); } onMounted(() => { watch(() => unref(dropzone), (enabled, _, onCleanup) => { if (!enabled) { return; } const scope = effectScope(); scope.run(() => { const { isOverDropZone } = useDropZone(dropzoneRef, { onDrop: (files) => onDrop(files, true) }); watch(isOverDropZone, (value) => { isDragging.value = value; }); }); onCleanup(() => { scope.stop(); isDragging.value = false; }); }, { immediate: true }); const { onChange, open: open2 } = useFileDialog({ accept, multiple, input: computed(() => unref(inputRef)?.$el), reset }); fileDialog.open = open2; onChange((fileList) => onDrop(fileList, false)); }); return { isDragging, open, inputRef, dropzoneRef }; }