UNPKG

element-plus

Version:

A Component Library for Vue3.0

922 lines (902 loc) 29.9 kB
import { defineComponent, ref, resolveComponent, openBlock, createBlock, TransitionGroup, withCtx, Fragment, renderList, withKeys, renderSlot, createCommentVNode, createVNode, createTextVNode, toDisplayString, inject, withModifiers, watch, computed, provide, getCurrentInstance, onBeforeUnmount, h } from 'vue'; import { elFormKey } from '../el-form'; import { t } from '../locale'; import ElProgress from '../el-progress'; /** * Make a map and return a function for checking if a key * is in that map. * IMPORTANT: all calls of this function must be prefixed with * \/\*#\_\_PURE\_\_\*\/ * So that rollup can tree-shake them if necessary. */ const EMPTY_OBJ = (process.env.NODE_ENV !== 'production') ? Object.freeze({}) : {}; const EMPTY_ARR = (process.env.NODE_ENV !== 'production') ? Object.freeze([]) : []; const NOOP = () => { }; function getError(action, option, xhr) { let msg; if (xhr.response) { msg = `${xhr.response.error || xhr.response}`; } else if (xhr.responseText) { msg = `${xhr.responseText}`; } else { msg = `fail to post ${action} ${xhr.status}`; } const err = new Error(msg); err.status = xhr.status; err.method = 'post'; err.url = action; return err; } function getBody(xhr) { const text = xhr.responseText || xhr.response; if (!text) { return text; } try { return JSON.parse(text); } catch (e) { return text; } } function upload(option) { if (typeof XMLHttpRequest === 'undefined') { return; } const xhr = new XMLHttpRequest(); const action = option.action; if (xhr.upload) { xhr.upload.onprogress = function progress(e) { if (e.total > 0) { e.percent = e.loaded / e.total * 100; } option.onProgress(e); }; } const formData = new FormData(); if (option.data) { Object.keys(option.data).forEach(key => { formData.append(key, option.data[key]); }); } formData.append(option.filename, option.file, option.file.name); xhr.onerror = function error() { option.onError(getError(action, option, xhr)); }; xhr.onload = function onload() { if (xhr.status < 200 || xhr.status >= 300) { return option.onError(getError(action, option, xhr)); } option.onSuccess(getBody(xhr)); }; xhr.open('post', action, true); if (option.withCredentials && 'withCredentials' in xhr) { xhr.withCredentials = true; } const headers = option.headers || {}; for (const item in headers) { if (headers.hasOwnProperty(item) && headers[item] !== null) { xhr.setRequestHeader(item, headers[item]); } } xhr.send(formData); return xhr; } var script = defineComponent({ name: 'ElUploadList', components: { ElProgress }, props: { files: { type: Array, default: () => [], }, disabled: { type: Boolean, default: false, }, handlePreview: { type: Function, default: () => NOOP, }, listType: { type: String, default: 'text', }, }, emits: ['remove'], setup(props, { emit }) { const parsePercentage = (val) => { return parseInt(val, 10); }; const handleClick = (file) => { props.handlePreview(file); }; const onFileClicked = (e) => { e.target.focus(); }; const handleRemove = (e, file) => { emit('remove', file); }; return { focusing: ref(false), parsePercentage, handleClick, handleRemove, onFileClicked, t, }; }, }); const _hoisted_1 = /*#__PURE__*/createVNode("i", { class: "el-icon-document" }, null, -1 /* HOISTED */); const _hoisted_2 = { class: "el-upload-list__item-status-label" }; const _hoisted_3 = { key: 2, class: "el-icon-close-tip" }; const _hoisted_4 = { key: 4, class: "el-upload-list__item-actions" }; const _hoisted_5 = /*#__PURE__*/createVNode("i", { class: "el-icon-zoom-in" }, null, -1 /* HOISTED */); const _hoisted_6 = /*#__PURE__*/createVNode("i", { class: "el-icon-delete" }, null, -1 /* HOISTED */); function render(_ctx, _cache, $props, $setup, $data, $options) { const _component_el_progress = resolveComponent("el-progress"); return (openBlock(), createBlock(TransitionGroup, { tag: "ul", class: [ 'el-upload-list', 'el-upload-list--' + _ctx.listType, { 'is-disabled': _ctx.disabled } ], name: "el-list" }, { default: withCtx(() => [ (openBlock(true), createBlock(Fragment, null, renderList(_ctx.files, (file, idx) => { return (openBlock(), createBlock("li", { key: idx, class: ['el-upload-list__item', 'is-' + file.status, _ctx.focusing ? 'focusing' : ''], tabindex: "0", onKeydown: withKeys($event => (!_ctx.disabled && _ctx.handleRemove($event, file)), ["delete"]), onFocus: _cache[1] || (_cache[1] = $event => (_ctx.focusing = true)), onBlur: _cache[2] || (_cache[2] = $event => (_ctx.focusing = false)), onClick: _cache[3] || (_cache[3] = (...args) => (_ctx.onFileClicked && _ctx.onFileClicked(...args))) }, [ renderSlot(_ctx.$slots, "default", { file: file }, () => [ (file.status !== 'uploading' && ['picture-card', 'picture'].includes(_ctx.listType)) ? (openBlock(), createBlock("img", { key: 0, class: "el-upload-list__item-thumbnail", src: file.url, alt: "" }, null, 8 /* PROPS */, ["src"])) : createCommentVNode("v-if", true), createVNode("a", { class: "el-upload-list__item-name", onClick: $event => (_ctx.handleClick(file)) }, [ _hoisted_1, createTextVNode(toDisplayString(file.name), 1 /* TEXT */) ], 8 /* PROPS */, ["onClick"]), createVNode("label", _hoisted_2, [ createVNode("i", { class: { 'el-icon-upload-success': true, 'el-icon-circle-check': _ctx.listType === 'text', 'el-icon-check': ['picture-card', 'picture'].includes(_ctx.listType) } }, null, 2 /* CLASS */) ]), (!_ctx.disabled) ? (openBlock(), createBlock("i", { key: 1, class: "el-icon-close", onClick: $event => (_ctx.handleRemove($event, file)) }, null, 8 /* PROPS */, ["onClick"])) : createCommentVNode("v-if", true), createCommentVNode(" Due to close btn only appears when li gets focused disappears after li gets blurred, thus keyboard navigation can never reach close btn"), createCommentVNode(" This is a bug which needs to be fixed "), createCommentVNode(" TODO: Fix the incorrect navigation interaction "), (!_ctx.disabled) ? (openBlock(), createBlock("i", _hoisted_3, toDisplayString(_ctx.t('el.upload.deleteTip')), 1 /* TEXT */)) : createCommentVNode("v-if", true), (file.status === 'uploading') ? (openBlock(), createBlock(_component_el_progress, { key: 3, type: _ctx.listType === 'picture-card' ? 'circle' : 'line', "stroke-width": _ctx.listType === 'picture-card' ? 6 : 2, percentage: _ctx.parsePercentage(file.percentage) }, null, 8 /* PROPS */, ["type", "stroke-width", "percentage"])) : createCommentVNode("v-if", true), (_ctx.listType === 'picture-card') ? (openBlock(), createBlock("span", _hoisted_4, [ createVNode("span", { class: "el-upload-list__item-preview", onClick: $event => (_ctx.handlePreview(file)) }, [ _hoisted_5 ], 8 /* PROPS */, ["onClick"]), (!_ctx.disabled) ? (openBlock(), createBlock("span", { key: 0, class: "el-upload-list__item-delete", onClick: $event => (_ctx.handleRemove($event, file)) }, [ _hoisted_6 ], 8 /* PROPS */, ["onClick"])) : createCommentVNode("v-if", true) ])) : createCommentVNode("v-if", true) ]) ], 42 /* CLASS, PROPS, HYDRATE_EVENTS */, ["onKeydown"])) }), 128 /* KEYED_FRAGMENT */)) ]), _: 1 /* STABLE */ }, 8 /* PROPS */, ["class"])) } script.render = render; script.__file = "packages/upload/src/upload-list.vue"; var script$1 = defineComponent({ name: 'ElUploadDrag', props: { disabled: { type: Boolean, default: false, }, }, emits: ['file'], setup(props, { emit }) { const uploader = inject('uploader', {}); const dragover = ref(false); function onDrop(e) { if (props.disabled || !uploader) return; const accept = uploader.accept; dragover.value = false; if (!accept) { emit('file', e.dataTransfer.files); return; } emit('file', Array.from(e.dataTransfer.files).filter(file => { const { type, name } = file; const extension = name.indexOf('.') > -1 ? `.${name.split('.').pop()}` : ''; const baseType = type.replace(/\/.*$/, ''); return accept .split(',') .map(type => type.trim()) .filter(type => type) .some(acceptedType => { if (acceptedType.startsWith('.')) { return extension === acceptedType; } if (/\/\*$/.test(acceptedType)) { return baseType === acceptedType.replace(/\/\*$/, ''); } if (/^[^\/]+\/[^\/]+$/.test(acceptedType)) { return type === acceptedType; } return false; }); })); } function onDragover() { if (!props.disabled) dragover.value = true; } return { dragover, onDrop, onDragover, }; }, }); function render$1(_ctx, _cache, $props, $setup, $data, $options) { return (openBlock(), createBlock("div", { class: { 'el-upload-dragger': true, 'is-dragover': _ctx.dragover }, onDrop: _cache[1] || (_cache[1] = withModifiers((...args) => (_ctx.onDrop && _ctx.onDrop(...args)), ["prevent"])), onDragover: _cache[2] || (_cache[2] = withModifiers((...args) => (_ctx.onDragover && _ctx.onDragover(...args)), ["prevent"])), onDragleave: _cache[3] || (_cache[3] = withModifiers($event => (_ctx.dragover = false), ["prevent"])) }, [ renderSlot(_ctx.$slots, "default") ], 34 /* CLASS, HYDRATE_EVENTS */)) } script$1.render = render$1; script$1.__file = "packages/upload/src/upload-dragger.vue"; var script$2 = defineComponent({ components: { UploadDragger: script$1, }, props: { type: { type: String, default: '', }, action: { type: String, required: true, }, name: { type: String, default: 'file', }, data: { type: Object, default: () => null, }, headers: { type: Object, default: () => null, }, withCredentials: { type: Boolean, default: false, }, multiple: { type: Boolean, default: null, }, accept: { type: String, default: '', }, onStart: { type: Function, default: NOOP, }, onProgress: { type: Function, default: NOOP, }, onSuccess: { type: Function, default: NOOP, }, onError: { type: Function, default: NOOP, }, beforeUpload: { type: Function, default: NOOP, }, drag: { type: Boolean, default: false, }, onPreview: { type: Function, default: NOOP, }, onRemove: { type: Function, default: NOOP, }, fileList: { type: Array, default: () => [], }, autoUpload: { type: Boolean, default: true, }, listType: { type: String, default: 'text', }, httpRequest: { type: Function, default: () => upload, }, disabled: Boolean, limit: { type: Number, default: null, }, onExceed: { type: Function, default: NOOP, }, }, setup(props) { const reqs = ref({}); const mouseover = ref(false); const inputRef = ref(null); function uploadFiles(files) { if (props.limit && props.fileList.length + files.length > props.limit) { props.onExceed(files, props.fileList); return; } let postFiles = Array.from(files); if (!props.multiple) { postFiles = postFiles.slice(0, 1); } if (postFiles.length === 0) { return; } postFiles.forEach(rawFile => { props.onStart(rawFile); if (props.autoUpload) upload(rawFile); }); } function upload(rawFile) { inputRef.value.value = null; if (!props.beforeUpload) { return post(rawFile); } const before = props.beforeUpload(rawFile); if (before instanceof Promise) { before .then(processedFile => { const fileType = Object.prototype.toString.call(processedFile); if (fileType === '[object File]' || fileType === '[object Blob]') { if (fileType === '[object Blob]') { processedFile = new File([processedFile], rawFile.name, { type: rawFile.type, }); } for (const p in rawFile) { if (rawFile.hasOwnProperty(p)) { processedFile[p] = rawFile[p]; } } post(processedFile); } else { post(rawFile); } }) .catch(() => { props.onRemove(null, rawFile); }); } else if (before !== false) { post(rawFile); } else { props.onRemove(null, rawFile); } } function abort(file) { const _reqs = reqs.value; if (file) { let uid = file; if (file.uid) uid = file.uid; if (_reqs[uid]) { _reqs[uid].abort(); } } else { Object.keys(_reqs).forEach(uid => { if (_reqs[uid]) _reqs[uid].abort(); delete _reqs[uid]; }); } } function post(rawFile) { const { uid } = rawFile; const options = { headers: props.headers, withCredentials: props.withCredentials, file: rawFile, data: props.data, filename: props.name, action: props.action, onProgress: e => { props.onProgress(e, rawFile); }, onSuccess: res => { props.onSuccess(res, rawFile); delete reqs.value[uid]; }, onError: err => { props.onError(err, rawFile); delete reqs.value[uid]; }, }; const req = props.httpRequest(options); reqs.value[uid] = req; if (req instanceof Promise) { req.then(options.onSuccess, options.onError); } } function handleChange(e) { const files = e.target.files; if (!files) return; uploadFiles(files); } function handleClick() { if (!props.disabled) { inputRef.value.value = null; inputRef.value.click(); } } function handleKeydown() { handleClick(); } return { reqs, mouseover, inputRef, abort, post, handleChange, handleClick, handleKeydown, upload, uploadFiles, }; }, }); function render$2(_ctx, _cache, $props, $setup, $data, $options) { const _component_upload_dragger = resolveComponent("upload-dragger"); return (openBlock(), createBlock("div", { class: ['el-upload', `el-upload--${_ctx.listType}`], tabindex: "0", onClick: _cache[2] || (_cache[2] = (...args) => (_ctx.handleClick && _ctx.handleClick(...args))), onKeydown: _cache[3] || (_cache[3] = withKeys(withModifiers((...args) => (_ctx.handleKeydown && _ctx.handleKeydown(...args)), ["self"]), ["enter","space"])) }, [ (_ctx.drag) ? (openBlock(), createBlock(_component_upload_dragger, { key: 0, disabled: _ctx.disabled, onFile: _ctx.uploadFiles }, { default: withCtx(() => [ renderSlot(_ctx.$slots, "default") ]), _: 3 /* FORWARDED */ }, 8 /* PROPS */, ["disabled", "onFile"])) : renderSlot(_ctx.$slots, "default", { key: 1 }), createVNode("input", { ref: "inputRef", class: "el-upload__input", type: "file", name: _ctx.name, multiple: _ctx.multiple, accept: _ctx.accept, onChange: _cache[1] || (_cache[1] = (...args) => (_ctx.handleChange && _ctx.handleChange(...args))) }, null, 40 /* PROPS, HYDRATE_EVENTS */, ["name", "multiple", "accept"]) ], 34 /* CLASS, HYDRATE_EVENTS */)) } script$2.render = render$2; script$2.__file = "packages/upload/src/upload.vue"; function getFile(rawFile, uploadFiles) { return uploadFiles.find(file => file.uid === rawFile.uid); } function genUid(seed) { return Date.now() + seed; } var useHandlers = (props) => { const uploadFiles = ref([]); const uploadRef = ref(null); let tempIndex = 1; function abort(file) { uploadRef.value.abort(file); } function clearFiles() { uploadFiles.value = []; } function handleError(err, rawFile) { const file = getFile(rawFile, uploadFiles.value); file.status = 'fail'; uploadFiles.value.splice(uploadFiles.value.indexOf(file), 1); props.onError(err, file, uploadFiles.value); props.onChange(file, uploadFiles.value); } function handleProgress(ev, rawFile) { const file = getFile(rawFile, uploadFiles.value); props.onProgress(ev, file, uploadFiles.value); file.status = 'uploading'; file.percentage = ev.percent || 0; } function handleSuccess(res, rawFile) { const file = getFile(rawFile, uploadFiles.value); if (file) { file.status = 'success'; file.response = res; props.onSuccess(res, file, uploadFiles.value); props.onChange(file, uploadFiles.value); } } function handleStart(rawFile) { const uid = genUid(tempIndex++); rawFile.uid = uid; const file = { name: rawFile.name, percentage: 0, status: 'ready', size: rawFile.size, raw: rawFile, uid, }; if (props.listType === 'picture-card' || props.listType === 'picture') { try { file.url = URL.createObjectURL(rawFile); } catch (err) { console.error('[Element Error][Upload]', err); props.onError(err, file, uploadFiles.value); } } uploadFiles.value.push(file); props.onChange(file, uploadFiles.value); } function handleRemove(file, raw) { if (raw) { file = getFile(raw, uploadFiles.value); } const doRemove = () => { abort(file); const fileList = uploadFiles.value; fileList.splice(fileList.indexOf(file), 1); props.onRemove(file, fileList); }; if (!props.beforeRemove) { doRemove(); } else if (typeof props.beforeRemove === 'function') { const before = props.beforeRemove(file, uploadFiles.value); if (before instanceof Promise) { before.then(() => { doRemove(); }).catch(NOOP); } else if (before !== false) { doRemove(); } } } function submit() { uploadFiles.value .filter(file => file.status === 'ready') .forEach(file => { uploadRef.value.upload(file.raw); }); } watch(() => props.listType, (val) => { if (val === 'picture-card' || val === 'picture') { uploadFiles.value = uploadFiles.value.map(file => { if (!file.url && file.raw) { try { file.url = URL.createObjectURL(file.raw); } catch (err) { props.onError(err, file, uploadFiles.value); } } return file; }); } }); watch(() => props.fileList, (fileList) => { uploadFiles.value = fileList.map(file => { file.uid = file.uid || genUid(tempIndex++); file.status = file.status || 'success'; return file; }); }, { immediate: true, deep: true, }); return { clearFiles, handleError, handleProgress, handleStart, handleSuccess, handleRemove, submit, uploadFiles, uploadRef, }; }; var script$3 = defineComponent({ name: 'ElUpload', components: { Upload: script$2, UploadList: script, }, props: { action: { type: String, required: true, }, headers: { type: Object, default: () => ({}), }, data: { type: Object, default: () => ({}), }, multiple: { type: Boolean, default: false, }, name: { type: String, default: 'file', }, drag: { type: Boolean, default: false, }, withCredentials: Boolean, showFileList: { type: Boolean, default: true, }, accept: { type: String, default: '', }, type: { type: String, default: 'select', }, beforeUpload: { type: Function, default: NOOP, }, beforeRemove: { type: Function, default: NOOP, }, onRemove: { type: Function, default: NOOP, }, onChange: { type: Function, default: NOOP, }, onPreview: { type: Function, default: NOOP, }, onSuccess: { type: Function, default: NOOP, }, onProgress: { type: Function, default: NOOP, }, onError: { type: Function, default: NOOP, }, fileList: { type: Array, default: () => { return []; }, }, autoUpload: { type: Boolean, default: true, }, listType: { type: String, default: 'text', }, httpRequest: { type: Function, default: upload, }, disabled: Boolean, limit: { type: Number, default: null, }, onExceed: { type: Function, default: () => NOOP, }, }, setup(props) { const elForm = inject(elFormKey, {}); const uploadDisabled = computed(() => { return props.disabled || elForm.disabled; }); const { clearFiles, handleError, handleProgress, handleStart, handleSuccess, handleRemove, submit, uploadRef, uploadFiles, } = useHandlers(props); provide('uploader', getCurrentInstance()); onBeforeUnmount(() => { uploadFiles.value.forEach(file => { if (file.url && file.url.indexOf('blob:') === 0) { URL.revokeObjectURL(file.url); } }); }); return { dragOver: ref(false), draging: ref(false), handleError, handleProgress, handleRemove, handleStart, handleSuccess, uploadDisabled, uploadFiles, uploadRef, submit, clearFiles, }; }, render() { var _a, _b; let uploadList; if (this.showFileList) { uploadList = h(script, { disabled: this.uploadDisabled, listType: this.listType, files: this.uploadFiles, onRemove: this.handleRemove, handlePreview: this.onPreview, }, this.$slots.file ? { default: (props) => { return this.$slots.file({ file: props.file, }); }, } : null); } else { uploadList = null; } const uploadData = { type: this.type, drag: this.drag, action: this.action, multiple: this.multiple, 'before-upload': this.beforeUpload, 'with-credentials': this.withCredentials, headers: this.headers, name: this.name, data: this.data, accept: this.accept, fileList: this.uploadFiles, autoUpload: this.autoUpload, listType: this.listType, disabled: this.uploadDisabled, limit: this.limit, 'on-exceed': this.onExceed, 'on-start': this.handleStart, 'on-progress': this.handleProgress, 'on-success': this.handleSuccess, 'on-error': this.handleError, 'on-preview': this.onPreview, 'on-remove': this.handleRemove, 'http-request': this.httpRequest, ref: 'uploadRef', }; const trigger = this.$slots.trigger || this.$slots.default; const uploadComponent = h(script$2, uploadData, { default: () => trigger === null || trigger === void 0 ? void 0 : trigger(), }); return h('div', [ this.listType === 'picture-card' ? uploadList : null, this.$slots.trigger ? [uploadComponent, this.$slots.default()] : uploadComponent, (_b = (_a = this.$slots).tip) === null || _b === void 0 ? void 0 : _b.call(_a), this.listType !== 'picture-card' ? uploadList : null, ]); }, }); script$3.__file = "packages/upload/src/index.vue"; script$3.install = (app) => { app.component(script$3.name, script$3); }; const _Upload = script$3; export default _Upload;