Goal:
- Programmatic: Call the API function, it will create
<input>
element and invoke file picker dialog. - Promise-based: If there're files selected, resolve with array of files. If user cancel the dialog, resolve with empty array or reject.
My current implementation:
let input: HTMLInputElement
let lastPromiseResolve: (files: File[]) => void
const defaultConfig = {
accept: '*',
multiple: false,
}
export const pickFile = async (config: Partial<typeof defaultConfig> = defaultConfig) => {
const { accept, multiple } = { ...defaultConfig, ...config }
if (!input) {
input = document.createElement('input')
input.type = 'file'
input.style.display = 'none'
document.body.appendChild(input)
input.addEventListener('change', () => {
lastPromiseResolve?.([...input.files])
lastPromiseResolve = null
})
}
input.accept = accept
input.multiple = multiple
input.value = ''
// resolve with empty array for previous calls
if (lastPromiseResolve) {
lastPromiseResolve([])
}
const files = await new Promise<File[]>(resolve => {
lastPromiseResolve = resolve
input.click()
})
return files
}
The problem is when user cancel the dialog, change
/ input
events are not fired. So the promise is not resolved nor rejected unless I call pickFile
for the second time.
There is also a similar npm package js-pick-file, having the same problem.