All files / utils chooseFile.ts

93.94% Statements 31/33
90% Branches 9/10
60% Functions 3/5
93.94% Lines 31/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99                                                                                          4x   4x       4x 4x 4x 4x 4x 4x   4x   4x 4x 4x 4x 4x 4x 4x   4x 7x 7x 7x 7x 7x 4x 4x 4x 4x     4x       4x       4x     4x       4x      
import { toArray } from 'lodash-uni'
import { LiteralUnion } from '../types'
import { bindEvent } from './bindEvent'
import { wait } from './wait'
 
export interface ChooseFileOptions {
  /**
   * 是否多选
   *
   * @default false
   */
  multiple?: boolean
 
  /**
   * 元素准备好后的回调
   */
  afterElementReady?: (payload: { el: HTMLInputElement }) => any
}
 
/**
 * 选择文件。
 *
 * @param accept 接受的文件类型,其中默认的 `image` 表示 `image/*`
 * @param multiple 是否多选
 * @returns 返回选中的文件列表
 */
export function chooseFile(
  accept: LiteralUnion<'image', string>,
  multiple?: boolean,
): Promise<ReadonlyArray<File>>
/**
 * 选择文件。
 *
 * @param accept 接受的文件类型,其中默认的 `image` 表示 `image/*`
 * @param options 选项
 * @returns 返回选中的文件列表
 */
export function chooseFile(
  accept: LiteralUnion<'image', string>,
  options?: ChooseFileOptions,
): Promise<ReadonlyArray<File>>
export function chooseFile(
  accept: LiteralUnion<'image', string>,
  multipleOrOptions?: boolean | ChooseFileOptions,
): Promise<ReadonlyArray<File>> {
  return new Promise(resolve => {
    const options: ChooseFileOptions =
      typeof multipleOrOptions === 'boolean'
        ? { multiple: multipleOrOptions }
        : multipleOrOptions || {}
 
    let input = document.createElement('input')
    input.style.all = 'unset'
    input.style.position = 'fixed'
    input.style.top = '0px'
    input.style.clip = 'rect(0, 0, 0, 0)'
    input.style.webkitUserSelect = 'text'
    // @ts-ignore
    input.style.MozUserSelect = 'text'
    // @ts-ignore
    input.style.msUserSelect = 'text'
    input.style.userSelect = 'text'
    input.type = 'file'
    input.accept = accept === 'image' ? 'image/*' : accept
    input.multiple = !!options.multiple
    options.afterElementReady?.({ el: input })
    document.body.appendChild(input)
 
    const handleChange = () => {
      unbindChange()
      unbindCancel()
      unbindFocus()
      unbindTouchend()
      if (input) {
        const files = input.files || []
        document.body.removeChild(input)
        input = null as any
        resolve(Object.freeze(toArray(files)))
      }
    }
    const unbindChange = bindEvent(input)('change', handleChange)
 
    // 标准取消监听 但有兼容问题
    // https://caniuse.com/?search=HTMLInputElement%20cancel
    const unbindCancel = bindEvent(input)('cancel', handleChange)
 
    // 取消监听 hack
    // https://stackoverflow.com/a/67603015
    const unbindFocus = bindEvent(window)('focus', () =>
      wait(1000).then(handleChange),
    )
    const unbindTouchend = bindEvent(window)('touchend', () =>
      wait(1000).then(handleChange),
    )
 
    input.click()
  })
}