241 lines
7.9 KiB
JavaScript
241 lines
7.9 KiB
JavaScript
import { mergeProps as _mergeProps, createVNode as _createVNode, Fragment as _Fragment } from "vue";
|
|
// Styles
|
|
import "./VFileUpload.css";
|
|
|
|
// Components
|
|
import { VFileUploadItem } from "./VFileUploadItem.mjs";
|
|
import { VBtn } from "../../components/VBtn/VBtn.mjs";
|
|
import { VDefaultsProvider } from "../../components/VDefaultsProvider/VDefaultsProvider.mjs";
|
|
import { makeVDividerProps, VDivider } from "../../components/VDivider/VDivider.mjs";
|
|
import { VIcon } from "../../components/VIcon/VIcon.mjs";
|
|
import { VOverlay } from "../../components/VOverlay/VOverlay.mjs";
|
|
import { makeVSheetProps, VSheet } from "../../components/VSheet/VSheet.mjs"; // Composables
|
|
import { makeDelayProps } from "../../composables/delay.mjs";
|
|
import { makeDensityProps, useDensity } from "../../composables/density.mjs";
|
|
import { IconValue } from "../../composables/icons.mjs";
|
|
import { useLocale } from "../../composables/locale.mjs";
|
|
import { useProxiedModel } from "../../composables/proxiedModel.mjs"; // Utilities
|
|
import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
|
|
import { filterInputAttrs, genericComponent, pick, propsFactory, useRender, wrapInArray } from "../../util/index.mjs"; // 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'
|
|
},
|
|
modelValue: {
|
|
type: [Array, Object],
|
|
default: null,
|
|
validator: val => {
|
|
return wrapInArray(val).every(v => v != null && typeof v === 'object');
|
|
}
|
|
},
|
|
clearable: Boolean,
|
|
disabled: Boolean,
|
|
hideBrowse: Boolean,
|
|
multiple: Boolean,
|
|
scrim: {
|
|
type: [Boolean, String],
|
|
default: true
|
|
},
|
|
showSize: Boolean,
|
|
name: String,
|
|
...makeDelayProps(),
|
|
...makeDensityProps(),
|
|
...pick(makeVDividerProps({
|
|
length: 150
|
|
}), ['length', 'thickness', 'opacity']),
|
|
...makeVSheetProps()
|
|
}, 'VFileUpload');
|
|
export const VFileUpload = genericComponent()({
|
|
name: 'VFileUpload',
|
|
inheritAttrs: false,
|
|
props: makeVFileUploadProps(),
|
|
emits: {
|
|
'update:modelValue': files => true
|
|
},
|
|
setup(props, _ref) {
|
|
let {
|
|
attrs,
|
|
slots
|
|
} = _ref;
|
|
const {
|
|
t
|
|
} = useLocale();
|
|
const {
|
|
densityClasses
|
|
} = useDensity(props);
|
|
const model = useProxiedModel(props, 'modelValue', props.modelValue, val => wrapInArray(val), val => props.multiple || Array.isArray(props.modelValue) ? val : val[0]);
|
|
const dragOver = shallowRef(false);
|
|
const vSheetRef = ref(null);
|
|
const inputRef = ref(null);
|
|
onMounted(() => {
|
|
vSheetRef.value?.$el.addEventListener('dragover', onDragOver);
|
|
vSheetRef.value?.$el.addEventListener('drop', onDrop);
|
|
});
|
|
onUnmounted(() => {
|
|
vSheetRef.value?.$el.removeEventListener('dragover', onDragOver);
|
|
vSheetRef.value?.$el.removeEventListener('drop', onDrop);
|
|
});
|
|
function onDragOver(e) {
|
|
e.preventDefault();
|
|
e.stopImmediatePropagation();
|
|
dragOver.value = true;
|
|
}
|
|
function onDragLeave(e) {
|
|
e.preventDefault();
|
|
dragOver.value = false;
|
|
}
|
|
function onDrop(e) {
|
|
e.preventDefault();
|
|
e.stopImmediatePropagation();
|
|
dragOver.value = false;
|
|
const files = Array.from(e.dataTransfer?.files ?? []);
|
|
if (!files.length) return;
|
|
if (!props.multiple) {
|
|
model.value = [files[0]];
|
|
return;
|
|
}
|
|
const array = model.value.slice();
|
|
for (const file of files) {
|
|
if (!array.some(f => f.name === file.name)) {
|
|
array.push(file);
|
|
}
|
|
}
|
|
model.value = array;
|
|
}
|
|
function onClick() {
|
|
inputRef.value?.click();
|
|
}
|
|
function onClickRemove(index) {
|
|
model.value = model.value.filter((_, i) => i !== index);
|
|
if (model.value.length > 0 || !inputRef.value) return;
|
|
inputRef.value.value = '';
|
|
}
|
|
useRender(() => {
|
|
const hasTitle = !!(slots.title || props.title);
|
|
const hasIcon = !!(slots.icon || props.icon);
|
|
const hasBrowse = !!(!props.hideBrowse && (slots.browse || props.density === 'default'));
|
|
const cardProps = VSheet.filterProps(props);
|
|
const dividerProps = VDivider.filterProps(props);
|
|
const [rootAttrs, inputAttrs] = filterInputAttrs(attrs);
|
|
const inputNode = _createVNode("input", _mergeProps({
|
|
"ref": inputRef,
|
|
"type": "file",
|
|
"disabled": props.disabled,
|
|
"multiple": props.multiple,
|
|
"name": props.name,
|
|
"onChange": e => {
|
|
if (!e.target) return;
|
|
const target = e.target;
|
|
model.value = [...(target.files ?? [])];
|
|
}
|
|
}, inputAttrs), null);
|
|
return _createVNode(_Fragment, null, [_createVNode(VSheet, _mergeProps({
|
|
"ref": vSheetRef
|
|
}, cardProps, {
|
|
"class": ['v-file-upload', {
|
|
'v-file-upload--clickable': !hasBrowse,
|
|
'v-file-upload--disabled': props.disabled,
|
|
'v-file-upload--dragging': dragOver.value
|
|
}, densityClasses.value],
|
|
"onDragleave": onDragLeave,
|
|
"onDragover": onDragOver,
|
|
"onDrop": onDrop,
|
|
"onClick": !hasBrowse ? onClick : undefined
|
|
}, rootAttrs), {
|
|
default: () => [hasIcon && _createVNode("div", {
|
|
"key": "icon",
|
|
"class": "v-file-upload-icon"
|
|
}, [!slots.icon ? _createVNode(VIcon, {
|
|
"key": "icon-icon",
|
|
"icon": props.icon
|
|
}, null) : _createVNode(VDefaultsProvider, {
|
|
"key": "icon-defaults",
|
|
"defaults": {
|
|
VIcon: {
|
|
icon: props.icon
|
|
}
|
|
}
|
|
}, {
|
|
default: () => [slots.icon()]
|
|
})]), hasTitle && _createVNode("div", {
|
|
"key": "title",
|
|
"class": "v-file-upload-title"
|
|
}, [slots.title?.() ?? t(props.title)]), props.density === 'default' && _createVNode(_Fragment, null, [_createVNode("div", {
|
|
"key": "upload-divider",
|
|
"class": "v-file-upload-divider"
|
|
}, [slots.divider?.() ?? _createVNode(VDivider, dividerProps, {
|
|
default: () => [t(props.dividerText)]
|
|
})]), hasBrowse && _createVNode(_Fragment, null, [!slots.browse ? _createVNode(VBtn, {
|
|
"readonly": props.disabled,
|
|
"size": "large",
|
|
"text": t(props.browseText),
|
|
"variant": "tonal",
|
|
"onClick": onClick
|
|
}, null) : _createVNode(VDefaultsProvider, {
|
|
"defaults": {
|
|
VBtn: {
|
|
readonly: props.disabled,
|
|
size: 'large',
|
|
text: t(props.browseText),
|
|
variant: 'tonal'
|
|
}
|
|
}
|
|
}, {
|
|
default: () => [slots.browse({
|
|
props: {
|
|
onClick
|
|
}
|
|
})]
|
|
})]), props.subtitle && _createVNode("div", {
|
|
"class": "v-file-upload-subtitle"
|
|
}, [props.subtitle])]), _createVNode(VOverlay, {
|
|
"model-value": dragOver.value,
|
|
"contained": true,
|
|
"scrim": props.scrim
|
|
}, null), slots.input?.({
|
|
inputNode
|
|
}) ?? inputNode]
|
|
}), model.value.length > 0 && _createVNode("div", {
|
|
"class": "v-file-upload-items"
|
|
}, [model.value.map((file, i) => {
|
|
const slotProps = {
|
|
file,
|
|
props: {
|
|
'onClick:remove': () => onClickRemove(i)
|
|
}
|
|
};
|
|
return _createVNode(VDefaultsProvider, {
|
|
"key": i,
|
|
"defaults": {
|
|
VFileUploadItem: {
|
|
file,
|
|
clearable: props.clearable,
|
|
disabled: props.disabled,
|
|
showSize: props.showSize
|
|
}
|
|
}
|
|
}, {
|
|
default: () => [slots.item?.(slotProps) ?? _createVNode(VFileUploadItem, {
|
|
"key": i,
|
|
"onClick:remove": () => onClickRemove(i)
|
|
}, slots)]
|
|
});
|
|
})])]);
|
|
});
|
|
}
|
|
});
|
|
//# sourceMappingURL=VFileUpload.mjs.map
|