Skip to content

Commit eb491e1

Browse files
fix(FileUpload): ensure native validation works with required (#5358)
1 parent 240897e commit eb491e1

File tree

4 files changed

+139
-124
lines changed

4 files changed

+139
-124
lines changed

src/runtime/components/FileUpload.vue

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export interface FileUploadSlots<M extends boolean = false> {
125125

126126
<script setup lang="ts" generic="M extends boolean = false">
127127
import { computed, watch } from 'vue'
128-
import { Primitive } from 'reka-ui'
128+
import { Primitive, VisuallyHidden } from 'reka-ui'
129129
import { createReusableTemplate } from '@vueuse/core'
130130
import { useAppConfig, useLocale } from '#imports'
131131
import { useFormField } from '../composables/useFormField'
@@ -252,15 +252,17 @@ function removeFile(index?: number) {
252252
}
253253
254254
watch(modelValue, (newValue) => {
255-
const hasModelReset = !Array.isArray(newValue) || !newValue.length
255+
const hasModelReset = props.multiple ? !(newValue as File[])?.length : !newValue
256256
257-
if (hasModelReset && inputRef.value) {
258-
inputRef.value.value = ''
257+
if (hasModelReset && inputRef.value?.$el) {
258+
inputRef.value.$el.value = ''
259259
}
260260
})
261261
262262
defineExpose({
263-
inputRef,
263+
get inputRef() {
264+
return inputRef.value?.$el as HTMLInputElement
265+
},
264266
dropzoneRef
265267
})
266268
</script>
@@ -365,18 +367,18 @@ defineExpose({
365367
<ReuseFilesTemplate v-if="position === 'outside'" />
366368
</slot>
367369

368-
<input
370+
<VisuallyHidden
369371
:id="id"
370372
ref="inputRef"
373+
as="input"
371374
type="file"
375+
feature="fully-hidden"
372376
:name="name"
373377
:accept="accept"
374378
:multiple="(multiple as boolean)"
375379
:required="required"
376380
:disabled="disabled"
377381
v-bind="{ ...$attrs, ...ariaAttrs }"
378-
class="sr-only"
379-
tabindex="-1"
380-
>
382+
/>
381383
</Primitive>
382384
</template>

src/runtime/composables/useFileUpload.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ref, computed, unref, onMounted, watch, reactive } from 'vue'
2+
import type { VisuallyHidden } from 'reka-ui'
23
import { useFileDialog, useDropZone } from '@vueuse/core'
34
import type { MaybeRef } from '@vueuse/core'
45

@@ -45,12 +46,12 @@ export function useFileUpload(options: UseFileUploadOptions) {
4546
dropzone = true,
4647
onUpdate
4748
} = options
48-
const inputRef = ref<HTMLInputElement>()
49+
const inputRef = ref<InstanceType<typeof VisuallyHidden>>()
4950
const dropzoneRef = ref<HTMLDivElement>()
5051

5152
const dataTypes = computed(() => parseAcceptToDataTypes(unref(accept)))
5253

53-
const onDrop = (files: FileList | File[] | null) => {
54+
const onDrop = (files: FileList | File[] | null, fromDropZone = false) => {
5455
if (!files || files.length === 0) {
5556
return
5657
}
@@ -60,6 +61,18 @@ export function useFileUpload(options: UseFileUploadOptions) {
6061
if (files.length > 1 && !multiple) {
6162
files = [files[0]!]
6263
}
64+
65+
// Sync dropped files to the input element for proper native validation
66+
if (fromDropZone && inputRef.value?.$el) {
67+
try {
68+
const dt = new DataTransfer()
69+
files.forEach(file => dt.items.add(file))
70+
inputRef.value.$el.files = dt.files
71+
} catch (e) {
72+
console.warn('Could not sync files to input element:', e)
73+
}
74+
}
75+
6376
onUpdate(files)
6477
}
6578

@@ -75,7 +88,7 @@ export function useFileUpload(options: UseFileUploadOptions) {
7588

7689
onMounted(() => {
7790
const { isOverDropZone } = dropzone
78-
? useDropZone(dropzoneRef, { dataTypes: dataTypes.value, onDrop })
91+
? useDropZone(dropzoneRef, { dataTypes: dataTypes.value, onDrop: files => onDrop(files, true) })
7992
: { isOverDropZone: ref(false) }
8093

8194
watch(isOverDropZone, (value) => {
@@ -85,13 +98,13 @@ export function useFileUpload(options: UseFileUploadOptions) {
8598
const { onChange, open } = useFileDialog({
8699
accept: unref(accept),
87100
multiple,
88-
input: unref(inputRef),
101+
input: unref(inputRef)?.$el,
89102
reset
90103
})
91104

92105
fileDialog.open = open
93106

94-
onChange(fileList => onDrop(fileList))
107+
onChange(fileList => onDrop(fileList, false))
95108
})
96109

97110
return {

0 commit comments

Comments
 (0)