const fileInput = document.createElement('template') fileInput.innerHTML = ` ` customElements.define('file-input', class extends HTMLElement { constructor() { super() this.attachShadow({ mode: 'open' }).append(fileInput.content.cloneNode(true)) this.input = this.shadowRoot.querySelector('input') this.fileInput = this.shadowRoot.querySelector('.file-input') this.filesPreviewWrapper = this.shadowRoot.querySelector('.files-preview-wrapper') this.reflectedAttributes = ['accept', 'multiple', 'capture', 'type'] this.reset = this.reset.bind(this) this.formatBytes = this.formatBytes.bind(this) this.createFilePreview = this.createFilePreview.bind(this) this.handleChange = this.handleChange.bind(this) this.handleKeyDown = this.handleKeyDown.bind(this) } static get observedAttributes() { return ['accept', 'multiple', 'capture', 'type'] } get files() { return this.input.files } set accept(val) { this.setAttribute('accept', val) } set multiple(val) { if (val) { this.setAttribute('multiple', '') } else { this.removeAttribute('multiple') } } set capture(val) { this.setAttribute('capture', val) } set value(val) { this.input.value = val } get isValid() { return this.input.value !== '' } reset() { this.input.value = '' this.filesPreviewWrapper.innerHTML = '' } formatBytes(a, b = 2) { if (0 === a) return "0 Bytes"; const c = 0 > b ? 0 : b, d = Math.floor(Math.log(a) / Math.log(1024)); return parseFloat((a / Math.pow(1024, d)).toFixed(c)) + " " + ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"][d] } createFilePreview(file) { const filePreview = document.createElement('li') const { name, size } = file filePreview.className = 'file-preview' filePreview.innerHTML = `
${name}
${this.formatBytes(size)}
` return filePreview } handleChange(e) { this.filesPreviewWrapper.innerHTML = '' const frag = document.createDocumentFragment() Array.from(e.target.files).forEach(file => { frag.append( this.createFilePreview(file) ) }); this.filesPreviewWrapper.append(frag) } handleKeyDown(e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault() this.input.click() } } connectedCallback() { this.setAttribute('role', 'button') this.setAttribute('aria-label', 'File upload') this.input.addEventListener('change', this.handleChange) this.fileInput.addEventListener('keydown', this.handleKeyDown) } attributeChangedCallback(name) { if (this.reflectedAttributes.includes(name)) { if (this.hasAttribute(name)) { this.input.setAttribute(name, this.getAttribute(name) ? this.getAttribute(name) : '') } else { this.input.removeAttribute(name) } } } disconnectedCallback() { this.input.removeEventListener('change', this.handleChange) this.fileInput.removeEventListener('keydown', this.handleKeyDown) } })