Skip to content

Commit a44bdc2

Browse files
hunterwilhelmantfu
andauthored
feat(useFileDialog): add MaybRef to multiple, accept, capture, reset, and directory (#4813)
Co-authored-by: Anthony Fu <anthonyfu117@hotmail.com> Co-authored-by: Anthony Fu <github@antfu.me>
1 parent 3a2df2e commit a44bdc2

File tree

2 files changed

+190
-41
lines changed

2 files changed

+190
-41
lines changed

packages/core/useFileDialog/index.test.ts

Lines changed: 143 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it, vi } from 'vitest'
2-
import { shallowRef } from 'vue'
2+
import { nextTick, shallowRef } from 'vue'
33
import { useFileDialog } from './index'
44

55
class DataTransferMock {
@@ -59,17 +59,29 @@ describe('useFileDialog', () => {
5959
expect(input.click).toBeCalled()
6060
})
6161

62-
it('should work with input element passed as template ref', () => {
63-
const inputEl = document.createElement('input')
64-
inputEl.click = vi.fn()
62+
it('should work with input element passed as template ref', async () => {
63+
const inputEl1 = document.createElement('input')
64+
inputEl1.click = vi.fn()
65+
const input = shallowRef<HTMLInputElement>(inputEl1)
66+
const { open } = useFileDialog({ input })
67+
68+
expect(inputEl1.click).toHaveBeenCalledTimes(0)
69+
open()
70+
expect(inputEl1.type).toBe('file')
71+
expect(inputEl1.click).toHaveBeenCalledTimes(1)
6572

66-
const inputRef = shallowRef<HTMLInputElement>(inputEl)
73+
const inputEl2 = document.createElement('input')
74+
inputEl2.click = vi.fn()
6775

68-
const { open } = useFileDialog({ input: inputRef })
76+
input.value = inputEl2
77+
await nextTick()
78+
79+
expect(inputEl2.type).toBe('file')
80+
expect(inputEl2.click).toHaveBeenCalledTimes(0)
6981

7082
open()
71-
expect(inputEl.type).toBe('file')
72-
expect(inputEl.click).toHaveBeenCalled()
83+
84+
expect(inputEl2.click).toHaveBeenCalledTimes(1)
7385
})
7486

7587
it('should trigger onchange and update files when file is selected', async () => {
@@ -93,4 +105,127 @@ describe('useFileDialog', () => {
93105
expect(files.value?.[0]).toEqual(file)
94106
expect(changeHandler).toHaveBeenCalledWith(files.value)
95107
})
108+
109+
it('should work with ref value for multiple option', async () => {
110+
const input = document.createElement('input')
111+
input.click = vi.fn()
112+
113+
const multipleRef = shallowRef(true)
114+
115+
const { open } = useFileDialog({
116+
input,
117+
multiple: multipleRef,
118+
})
119+
120+
expect(input.multiple).toBe(true)
121+
122+
open()
123+
124+
expect(input.multiple).toBe(true)
125+
126+
multipleRef.value = false
127+
await nextTick()
128+
129+
expect(input.multiple).toBe(false)
130+
131+
open()
132+
133+
expect(input.multiple).toBe(false)
134+
})
135+
136+
it('should work with ref value for accept option', async () => {
137+
const input = document.createElement('input')
138+
input.click = vi.fn()
139+
140+
const acceptRef = shallowRef('image/*')
141+
142+
const { open } = useFileDialog({
143+
input,
144+
accept: acceptRef,
145+
})
146+
147+
expect(input.accept).toBe('image/*')
148+
149+
open()
150+
151+
expect(input.accept).toBe('image/*')
152+
153+
acceptRef.value = 'video/*'
154+
await nextTick()
155+
156+
expect(input.accept).toBe('video/*')
157+
158+
open()
159+
160+
expect(input.accept).toBe('video/*')
161+
})
162+
163+
it('should work with ref value for directory option', async () => {
164+
const input = document.createElement('input')
165+
input.click = vi.fn()
166+
167+
const directoryRef = shallowRef(true)
168+
169+
const { open } = useFileDialog({
170+
input,
171+
directory: directoryRef,
172+
})
173+
174+
expect(input.webkitdirectory).toBe(true)
175+
176+
open()
177+
178+
expect(input.webkitdirectory).toBe(true)
179+
180+
directoryRef.value = false
181+
await nextTick()
182+
183+
expect(input.webkitdirectory).toBe(false)
184+
185+
open()
186+
187+
expect(input.webkitdirectory).toBe(false)
188+
})
189+
190+
it('should work with ref value for reset option', () => {
191+
const input = document.createElement('input')
192+
input.click = vi.fn()
193+
194+
const resetRef = shallowRef(true)
195+
196+
const { open } = useFileDialog({
197+
input,
198+
reset: resetRef,
199+
})
200+
open()
201+
202+
expect(input.click).toHaveBeenCalled() // Assuming reset does not change input attributes
203+
})
204+
205+
it('should work with ref value for capture option', async () => {
206+
const input = document.createElement('input')
207+
input.click = vi.fn()
208+
209+
const captureRef = shallowRef('user')
210+
211+
const { open } = useFileDialog({
212+
input,
213+
capture: captureRef,
214+
})
215+
216+
expect(input.capture).toBe('user')
217+
218+
open()
219+
220+
expect(input.capture).toBe('user')
221+
222+
captureRef.value = 'environment'
223+
await nextTick()
224+
225+
expect(input.capture).toBe('environment')
226+
227+
open()
228+
229+
expect(input.capture).toBe('environment')
230+
})
96231
})

packages/core/useFileDialog/index.ts

Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,37 @@
11
import type { EventHookOn } from '@vueuse/shared'
2-
import type { Ref } from 'vue'
2+
import type { MaybeRef, Ref } from 'vue'
33
import type { ConfigurableDocument } from '../_configurable'
44
import type { MaybeElementRef } from '../unrefElement'
55
import { createEventHook, hasOwn } from '@vueuse/shared'
6-
import { ref as deepRef, readonly } from 'vue'
6+
import { computed, ref as deepRef, readonly, toValue, watchEffect } from 'vue'
77
import { defaultDocument } from '../_configurable'
88
import { unrefElement } from '../unrefElement'
99

1010
export interface UseFileDialogOptions extends ConfigurableDocument {
1111
/**
1212
* @default true
1313
*/
14-
multiple?: boolean
14+
multiple?: MaybeRef<boolean>
1515
/**
1616
* @default '*'
1717
*/
18-
accept?: string
18+
accept?: MaybeRef<string>
1919
/**
2020
* Select the input source for the capture file.
2121
* @see [HTMLInputElement Capture](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/capture)
2222
*/
23-
capture?: string
23+
capture?: MaybeRef<string>
2424
/**
2525
* Reset when open file dialog.
2626
* @default false
2727
*/
28-
reset?: boolean
28+
reset?: MaybeRef<boolean>
2929
/**
3030
* Select directories instead of files.
3131
* @see [HTMLInputElement webkitdirectory](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory)
3232
* @default false
3333
*/
34-
directory?: boolean
34+
directory?: MaybeRef<boolean>
3535

3636
/**
3737
* Initial files to set.
@@ -90,49 +90,63 @@ export function useFileDialog(options: UseFileDialogOptions = {}): UseFileDialog
9090
const files = deepRef<FileList | null>(prepareInitialFiles(options.initialFiles))
9191
const { on: onChange, trigger: changeTrigger } = createEventHook()
9292
const { on: onCancel, trigger: cancelTrigger } = createEventHook()
93-
let input: HTMLInputElement | undefined
94-
if (document) {
95-
input = unrefElement(options.input) || document.createElement('input')
96-
input.type = 'file'
97-
98-
input.onchange = (event: Event) => {
99-
const result = event.target as HTMLInputElement
100-
files.value = result.files
101-
changeTrigger(files.value)
93+
const inputRef = computed(() => {
94+
const input = unrefElement(options.input) ?? (document ? document.createElement('input') : undefined)
95+
if (input) {
96+
input.type = 'file'
97+
98+
input.onchange = (event: Event) => {
99+
const result = event.target as HTMLInputElement
100+
files.value = result.files
101+
changeTrigger(files.value)
102+
}
103+
104+
input.oncancel = () => {
105+
cancelTrigger()
106+
}
102107
}
103-
104-
input.oncancel = () => {
105-
cancelTrigger()
106-
}
107-
}
108+
return input
109+
})
108110

109111
const reset = () => {
110112
files.value = null
111-
if (input && input.value) {
112-
input.value = ''
113+
if (inputRef.value && inputRef.value.value) {
114+
inputRef.value.value = ''
113115
changeTrigger(null)
114116
}
115117
}
116118

119+
const applyOptions = (options: UseFileDialogOptions) => {
120+
const el = inputRef.value
121+
if (!el)
122+
return
123+
el.multiple = toValue(options.multiple)!
124+
el.accept = toValue(options.accept)!
125+
// webkitdirectory key is not stabled, maybe replaced in the future.
126+
el.webkitdirectory = toValue(options.directory)!
127+
if (hasOwn(options, 'capture'))
128+
el.capture = toValue(options.capture)!
129+
}
130+
117131
const open = (localOptions?: Partial<UseFileDialogOptions>) => {
118-
if (!input)
132+
const el = inputRef.value
133+
if (!el)
119134
return
120-
const _options = {
135+
const mergedOptions = {
121136
...DEFAULT_OPTIONS,
122137
...options,
123138
...localOptions,
124139
}
125-
input.multiple = _options.multiple!
126-
input.accept = _options.accept!
127-
// webkitdirectory key is not stabled, maybe replaced in the future.
128-
input.webkitdirectory = _options.directory!
129-
if (hasOwn(_options, 'capture'))
130-
input.capture = _options.capture!
131-
if (_options.reset)
140+
applyOptions(mergedOptions)
141+
if (toValue(mergedOptions.reset))
132142
reset()
133-
input.click()
143+
el.click()
134144
}
135145

146+
watchEffect(() => {
147+
applyOptions(options)
148+
})
149+
136150
return {
137151
files: readonly(files),
138152
open,

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy