216 lines
7.2 KiB
JavaScript
216 lines
7.2 KiB
JavaScript
import { mergeProps as _mergeProps, createElementVNode as _createElementVNode, Fragment as _Fragment, createVNode as _createVNode } from "vue";
|
|
// Styles
|
|
import "./VFileUpload.css";
|
|
|
|
// Components
|
|
import { VFileUploadDropzone, VFileUploadKey } from "./VFileUploadDropzone.js";
|
|
import { VFileUploadList } from "./VFileUploadList.js";
|
|
import { VDefaultsProvider } from "../../components/VDefaultsProvider/VDefaultsProvider.js";
|
|
import { makeVInputProps, VInput } from "../../components/VInput/VInput.js"; // Composables
|
|
import { makeFileFilterProps, useFileFilter } from "../../composables/fileFilter.js";
|
|
import { useFocus } from "../../composables/focus.js";
|
|
import { useForm } from "../../composables/form.js";
|
|
import { forwardRefs } from "../../composables/forwardRefs.js";
|
|
import { IconValue } from "../../composables/icons.js";
|
|
import { useProxiedModel } from "../../composables/proxiedModel.js"; // Utilities
|
|
import { provide, ref, toRef, watch } from 'vue';
|
|
import { filterInputAttrs, genericComponent, omit, propsFactory, useRender, wrapInArray } from "../../util/index.js"; // Types
|
|
export const makeVFileUploadProps = propsFactory({
|
|
browseText: {
|
|
type: String,
|
|
default: '$vuetify.fileUpload.browse'
|
|
},
|
|
dividerText: {
|
|
type: String,
|
|
default: '$vuetify.fileUpload.divider'
|
|
},
|
|
title: {
|
|
type: String,
|
|
default: '$vuetify.fileUpload.title'
|
|
},
|
|
subtitle: String,
|
|
icon: {
|
|
type: IconValue,
|
|
default: '$upload'
|
|
},
|
|
clearable: Boolean,
|
|
insetFileList: Boolean,
|
|
hideBrowse: Boolean,
|
|
multiple: Boolean,
|
|
scrim: {
|
|
type: [Boolean, String],
|
|
default: true
|
|
},
|
|
showSize: Boolean,
|
|
...makeFileFilterProps(),
|
|
...omit(makeVInputProps(), ['direction']),
|
|
modelValue: {
|
|
type: [Array, Object],
|
|
default: null,
|
|
validator: val => {
|
|
return wrapInArray(val).every(v => v != null && typeof v === 'object');
|
|
}
|
|
}
|
|
}, 'VFileUpload');
|
|
export const VFileUpload = genericComponent()({
|
|
name: 'VFileUpload',
|
|
inheritAttrs: false,
|
|
props: makeVFileUploadProps(),
|
|
emits: {
|
|
'update:modelValue': files => true,
|
|
'update:focused': focused => true,
|
|
rejected: files => true
|
|
},
|
|
setup(props, {
|
|
attrs,
|
|
emit,
|
|
slots
|
|
}) {
|
|
const {
|
|
filterAccepted
|
|
} = useFileFilter(props);
|
|
const {
|
|
isFocused
|
|
} = useFocus(props);
|
|
const form = useForm(props);
|
|
const model = useProxiedModel(props, 'modelValue', props.modelValue, val => wrapInArray(val), val => props.multiple || Array.isArray(props.modelValue) ? val : val[0]);
|
|
const vInputRef = ref();
|
|
const vDropzoneRef = ref();
|
|
const inputRef = ref(null);
|
|
const isError = toRef(() => vInputRef.value?.isValid === false);
|
|
provide(VFileUploadKey, {
|
|
files: model,
|
|
disabled: form.isDisabled,
|
|
error: isError,
|
|
onDrop,
|
|
onClickBrowse: onClick,
|
|
onClickRemove
|
|
});
|
|
watch(model, newValue => {
|
|
const hasModelReset = !Array.isArray(newValue) || !newValue.length;
|
|
if (hasModelReset && inputRef.value) {
|
|
inputRef.value.value = '';
|
|
}
|
|
});
|
|
function onDrop(files) {
|
|
selectAccepted(files);
|
|
}
|
|
function onFileSelection(e) {
|
|
if (!e.target || e.repack) return; // prevent loop
|
|
const target = e.target;
|
|
const selectedFiles = [...(target.files ?? [])];
|
|
if (!selectedFiles.length) return;
|
|
if (!props.filterByType) {
|
|
model.value = props.multiple ? [...model.value, ...selectedFiles] : selectedFiles;
|
|
} else {
|
|
selectAccepted(selectedFiles);
|
|
}
|
|
}
|
|
function selectAccepted(files) {
|
|
const dataTransfer = new DataTransfer();
|
|
const {
|
|
accepted,
|
|
rejected
|
|
} = filterAccepted(files);
|
|
if (rejected.length) {
|
|
emit('rejected', rejected);
|
|
}
|
|
for (const file of accepted) {
|
|
dataTransfer.items.add(file);
|
|
}
|
|
inputRef.value.files = dataTransfer.files;
|
|
const newFiles = [...dataTransfer.files];
|
|
model.value = props.multiple ? [...model.value, ...newFiles] : newFiles;
|
|
const event = new Event('change', {
|
|
bubbles: true
|
|
});
|
|
event.repack = true;
|
|
inputRef.value.dispatchEvent(event);
|
|
}
|
|
function onClick() {
|
|
inputRef.value?.click();
|
|
}
|
|
function onClickRemove(index) {
|
|
const newValue = model.value.filter((_, i) => i !== index);
|
|
model.value = newValue;
|
|
if (newValue.length > 0 || !inputRef.value) return;
|
|
inputRef.value.value = '';
|
|
}
|
|
useRender(() => {
|
|
const {
|
|
modelValue: _,
|
|
...inputProps
|
|
} = VInput.filterProps(props);
|
|
const {
|
|
modelValue: __,
|
|
...dropzoneProps
|
|
} = VFileUploadDropzone.filterProps(props);
|
|
const [rootAttrs, inputAttrs] = filterInputAttrs(attrs);
|
|
const expectsDirectory = attrs.webkitdirectory !== undefined && attrs.webkitdirectory !== false;
|
|
const acceptFallback = attrs.accept ? String(attrs.accept) : undefined;
|
|
const inputAccept = expectsDirectory ? undefined : props.filterByType ?? acceptFallback;
|
|
const inputNode = _createElementVNode("input", _mergeProps({
|
|
"ref": inputRef,
|
|
"type": "file",
|
|
"accept": inputAccept,
|
|
"disabled": props.disabled ?? undefined,
|
|
"multiple": props.multiple,
|
|
"name": props.name,
|
|
"onChange": onFileSelection
|
|
}, inputAttrs), null);
|
|
return _createVNode(VInput, _mergeProps({
|
|
"ref": vInputRef,
|
|
"modelValue": props.multiple ? model.value : model.value[0],
|
|
"onUpdate:modelValue": val => {
|
|
if (val == null || Array.isArray(val) && !val.length) {
|
|
model.value = [];
|
|
}
|
|
},
|
|
"class": ['v-file-upload', props.class],
|
|
"style": props.style,
|
|
"focused": isFocused.value
|
|
}, rootAttrs, inputProps), {
|
|
...slots,
|
|
default: () => {
|
|
return _createElementVNode(_Fragment, null, [slots.default ? _createElementVNode(_Fragment, null, [slots.default(), _createElementVNode("input", _mergeProps({
|
|
"ref": inputRef,
|
|
"type": "file",
|
|
"accept": inputAccept,
|
|
"disabled": props.disabled ?? undefined,
|
|
"multiple": props.multiple,
|
|
"name": props.name,
|
|
"style": "display: none;",
|
|
"onChange": onFileSelection
|
|
}, inputAttrs), null)]) : _createVNode(VFileUploadDropzone, _mergeProps({
|
|
"ref": vDropzoneRef
|
|
}, dropzoneProps), {
|
|
browse: slots.browse,
|
|
icon: slots.icon,
|
|
title: slots.title,
|
|
divider: slots.divider,
|
|
single: slots.single,
|
|
item: slots.item,
|
|
input: () => slots.input?.({
|
|
inputNode
|
|
}) ?? inputNode
|
|
}), !slots.default && !props.insetFileList && _createVNode(VDefaultsProvider, {
|
|
"defaults": {
|
|
VFileUploadList: {
|
|
clearable: props.clearable,
|
|
showSize: props.showSize
|
|
}
|
|
}
|
|
}, {
|
|
default: () => [_createVNode(VFileUploadList, null, {
|
|
item: slots.item
|
|
})]
|
|
})]);
|
|
}
|
|
});
|
|
});
|
|
return forwardRefs({
|
|
controlRef: inputRef
|
|
}, vInputRef, vDropzoneRef);
|
|
}
|
|
});
|
|
//# sourceMappingURL=VFileUpload.js.map
|