UNPKG

quasar

Version:

Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time

316 lines (260 loc) 8.04 kB
import { h, ref, computed, getCurrentInstance } from 'vue' import QChip from '../chip/QChip.js' import useField, { useFieldState, useNonInputFieldProps, useFieldEmits, fieldValueIsFilled } from '../../composables/private.use-field/use-field.js' import { useFormProps, useFormInputNameAttr } from '../../composables/use-form/private.use-form.js' import useFile, { useFileProps, useFileEmits } from '../../composables/private.use-file/use-file.js' import useFileFormDomProps from '../../composables/private.use-file/use-file-dom-props.js' import { createComponent } from '../../utils/private.create/create.js' import { humanStorageSize } from '../../utils/format/format.js' import { prevent } from '../../utils/event/event.js' import { injectProp } from '../../utils/private.inject-obj-prop/inject-obj-prop.js' export default createComponent({ name: 'QFile', inheritAttrs: false, props: { ...useNonInputFieldProps, ...useFormProps, ...useFileProps, /* SSR does not know about File & FileList */ modelValue: __QUASAR_SSR_SERVER__ ? {} : [File, FileList, Array], append: Boolean, useChips: Boolean, displayValue: [String, Number], tabindex: { type: [String, Number], default: 0 }, counterLabel: Function, inputClass: [Array, String, Object], inputStyle: [Array, String, Object] }, emits: [...useFieldEmits, ...useFileEmits], setup(props, { slots, emit, attrs }) { const { proxy } = getCurrentInstance() const state = useFieldState() const inputRef = ref(null) const dnd = ref(false) const nameProp = useFormInputNameAttr(props) const { pickFiles, onDragover, onDragleave, processFiles, getDndNode } = useFile({ editable: state.editable, dnd, getFileInput, addFilesToQueue }) const formDomProps = useFileFormDomProps(props) const innerValue = computed(() => Object(props.modelValue) === props.modelValue ? 'length' in props.modelValue ? Array.from(props.modelValue) : [props.modelValue] : [] ) const hasValue = computed(() => fieldValueIsFilled(innerValue.value)) const selectedString = computed(() => innerValue.value.map(file => file.name).join(', ') ) const totalSize = computed(() => humanStorageSize( innerValue.value.reduce((acc, file) => acc + file.size, 0) ) ) const counterProps = computed(() => ({ totalSize: totalSize.value, filesNumber: innerValue.value.length, maxFiles: props.maxFiles })) const inputAttrs = computed(() => ({ tabindex: -1, type: 'file', title: '', // try to remove default tooltip, accept: props.accept, capture: props.capture, name: nameProp.value, ...attrs, id: state.targetUid.value, disabled: state.editable.value !== true })) const fieldClass = computed( () => 'q-file q-field--auto-height' + (dnd.value === true ? ' q-file--dnd' : '') ) const isAppending = computed( () => props.multiple === true && props.append === true ) function removeAtIndex(index) { const files = innerValue.value.slice() files.splice(index, 1) emitValue(files) } function removeFile(file) { const index = innerValue.value.indexOf(file) if (index !== -1) { removeAtIndex(index) } } function emitValue(files) { emit('update:modelValue', props.multiple === true ? files : files[0]) } function onKeydown(e) { // prevent form submit if ENTER is pressed if (e.keyCode === 13) prevent(e) } function onKeyup(e) { // only on ENTER and SPACE to match native input field if (e.keyCode === 13 || e.keyCode === 32) { pickFiles(e) } } function getFileInput() { return inputRef.value } function addFilesToQueue(e, fileList) { const files = processFiles( e, fileList, innerValue.value, isAppending.value ) const fileInput = getFileInput() if (fileInput !== void 0 && fileInput !== null) { fileInput.value = '' } // if nothing to do... if (files === void 0) return // protect against input @change being called in a loop // like it happens on Safari, so don't emit same thing: if ( props.multiple === true ? props.modelValue && files.every(f => innerValue.value.includes(f)) : props.modelValue === files[0] ) { return } emitValue( isAppending.value === true ? innerValue.value.concat(files) : files ) } function getFiller() { return [ h('input', { class: [props.inputClass, 'q-file__filler'], style: props.inputStyle }) ] } function getSelection() { if (slots.file !== void 0) { return innerValue.value.length === 0 ? getFiller() : innerValue.value.map((file, index) => slots.file({ index, file, ref: this }) ) } if (slots.selected !== void 0) { return innerValue.value.length === 0 ? getFiller() : slots.selected({ files: innerValue.value, ref: this }) } if (props.useChips === true) { return innerValue.value.length === 0 ? getFiller() : innerValue.value.map((file, i) => h( QChip, { key: 'file-' + i, removable: state.editable.value, dense: true, textColor: props.color, tabindex: props.tabindex, onRemove: () => { removeAtIndex(i) } }, () => h('span', { class: 'ellipsis', textContent: file.name }) ) ) } const textContent = props.displayValue !== void 0 ? props.displayValue : selectedString.value return textContent.length !== 0 ? [ h('div', { class: props.inputClass, style: props.inputStyle, textContent }) ] : getFiller() } function getInput() { const data = { ref: inputRef, ...inputAttrs.value, ...formDomProps.value, class: 'q-field__input fit absolute-full cursor-pointer', onChange: addFilesToQueue } if (props.multiple === true) { data.multiple = true } return h('input', data) } Object.assign(state, { fieldClass, emitValue, hasValue, inputRef, innerValue, floatingLabel: computed( () => hasValue.value === true || fieldValueIsFilled(props.displayValue) ), computedCounter: computed(() => { if (props.counterLabel !== void 0) { return props.counterLabel(counterProps.value) } const max = props.maxFiles return `${innerValue.value.length}${max !== void 0 ? ' / ' + max : ''} (${totalSize.value})` }), getControlChild: () => getDndNode('file'), getControl: () => { const data = { ref: state.targetRef, class: 'q-field__native row items-center cursor-pointer', tabindex: props.tabindex } if (state.editable.value === true) { Object.assign(data, { onDragover, onDragleave, onKeydown, onKeyup }) } return h('div', data, [getInput()].concat(getSelection())) } }) // expose public methods Object.assign(proxy, { removeAtIndex, removeFile, getNativeElement: () => inputRef.value // deprecated }) injectProp(proxy, 'nativeEl', () => inputRef.value) return useField(state) } })