0

Goal:

  1. Programmatic: Call the API function, it will create <input> element and invoke file picker dialog.
  2. 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.

Grant Howard
  • 135
  • 2
  • 9
  • https://stackoverflow.com/questions/34855400/cancel-event-on-input-type-file – Dmitriy Mozgovoy Apr 20 '21 at 11:54
  • @DmitriyMozgovoy Tried solutions from that question. Because the script clicks the input element instead of user, `focus` / `blur` are never fired. (for Chrome 89 in Windows) – Grant Howard Apr 21 '21 at 08:55

0 Answers0