diff --git a/playground/app/pages/components/file-upload.vue b/playground/app/pages/components/file-upload.vue index 9b37751b..b747e4cc 100644 --- a/playground/app/pages/components/file-upload.vue +++ b/playground/app/pages/components/file-upload.vue @@ -62,6 +62,8 @@ const state = reactive>({ avatar: undefined }) +const value = ref([new File(['foo'], 'file1.txt', { type: 'text/plain' })]) + const upload = useUpload('/api/blob', { method: 'PUT' }) function createObjectUrl(file: File): string { @@ -109,9 +111,11 @@ async function onSubmit(event: FormSubmitEvent) { diff --git a/src/runtime/components/FileUpload.vue b/src/runtime/components/FileUpload.vue index 2fcf82f2..7300e835 100644 --- a/src/runtime/components/FileUpload.vue +++ b/src/runtime/components/FileUpload.vue @@ -2,7 +2,7 @@ import type { AppConfig } from '@nuxt/schema' import type { UseFileDialogReturn } from '@vueuse/core' import theme from '#build/ui/file-upload' -import type { ButtonProps } from '../types' +import type { AvatarProps, ButtonProps } from '../types' import type { ComponentConfig } from '../types/utils' type FileUpload = ComponentConfig @@ -59,18 +59,23 @@ export interface FileUploadProps { export interface FileUploadEmits { (e: 'update:modelValue', value: M extends true ? File[] : File | null): void + (e: 'change', event: Event): void } -export interface FileUploadSlots { - default(props: { +export interface FileUploadSlots { + 'default'(props: { open: UseFileDialogReturn['open'] - reset: UseFileDialogReturn['reset'] }): any - leading(props?: {}): any - label(props?: {}): any - description(props?: {}): any - actions(props?: {}): any - files(props?: {}): any + 'leading'(props?: {}): any + 'label'(props?: {}): any + 'description'(props?: {}): any + 'actions'(props?: {}): any + 'files'(props: { files: M extends true ? File[] : File | null }): any + 'file'(props: { file: File, index: number }): any + 'file-leading'(props: { file: File, index: number }): any + 'file-name'(props: { file: File, index: number }): any + 'file-size'(props: { file: File, index: number }): any + 'file-trailing'(props: { file: File, index: number }): any } @@ -90,10 +95,11 @@ defineOptions({ inheritAttrs: false }) const props = withDefaults(defineProps>(), { accept: '*', multiple: false as never, + reset: false, dropzone: true }) -defineEmits>() -const slots = defineSlots() +const emits = defineEmits>() +const slots = defineSlots>() const modelValue = defineModel<(M extends true ? File[] : File) | null>() @@ -102,14 +108,16 @@ const appConfig = useAppConfig() as FileUpload['AppConfig'] const inputRef = ref() const dropzoneRef = ref() -const { isDragging, open, reset } = useFileUpload({ +const { isDragging, open } = useFileUpload({ accept: props.accept, + reset: props.reset, multiple: props.multiple, dropzone: props.dropzone, dropzoneRef, + inputRef, onUpdate }) -const { emitFormInput, id, name, disabled, ariaAttrs } = useFormField(props, { deferInputValidation: true }) +const { emitFormInput, emitFormChange, id, name, disabled, ariaAttrs } = useFormField(props, { deferInputValidation: true }) const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.fileUpload || {}) })({ dropzone: props.dropzone, @@ -130,11 +138,30 @@ function onUpdate(files: File[]) { modelValue.value = files?.[0] as (M extends true ? File[] : File) | null } + // @ts-expect-error - 'target' does not exist in type 'EventInit' + const event = new Event('change', { target: { value: modelValue.value } }) + emits('change', event) + emitFormChange() emitFormInput() } +function formatFileSize(bytes: number): string { + if (bytes === 0) { + return '0B' + } + + const k = 1024 + const sizes = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + + const size = bytes / Math.pow(k, i) + const formattedSize = i === 0 ? size.toString() : size.toFixed(2) + + return `${formattedSize}${sizes[i]}` +} + function removeFile(index: number) { - if (!props.multiple || !modelValue.value) { + if (!modelValue.value) { return } @@ -154,7 +181,7 @@ defineExpose({