VFileUpload.mjs 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import { resolveDirective as _resolveDirective, Fragment as _Fragment, createVNode as _createVNode, mergeProps as _mergeProps } from "vue";
  2. // Styles
  3. import "./VFileUpload.css";
  4. // Components
  5. import { VFileUploadItem } from "./VFileUploadItem.mjs";
  6. import { VBtn } from "../../components/VBtn/VBtn.mjs";
  7. import { VDefaultsProvider } from "../../components/VDefaultsProvider/VDefaultsProvider.mjs";
  8. import { makeVDividerProps, VDivider } from "../../components/VDivider/VDivider.mjs";
  9. import { VIcon } from "../../components/VIcon/VIcon.mjs";
  10. import { VOverlay } from "../../components/VOverlay/VOverlay.mjs";
  11. import { makeVSheetProps, VSheet } from "../../components/VSheet/VSheet.mjs"; // Composables
  12. import { makeDelayProps } from "../../composables/delay.mjs";
  13. import { makeDensityProps, useDensity } from "../../composables/density.mjs";
  14. import { IconValue } from "../../composables/icons.mjs";
  15. import { useLocale } from "../../composables/locale.mjs";
  16. import { useProxiedModel } from "../../composables/proxiedModel.mjs"; // Utilities
  17. import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
  18. import { filterInputAttrs, genericComponent, only, propsFactory, useRender, wrapInArray } from "../../util/index.mjs"; // Types
  19. export const makeVFileUploadProps = propsFactory({
  20. browseText: {
  21. type: String,
  22. default: '$vuetify.fileUpload.browse'
  23. },
  24. dividerText: {
  25. type: String,
  26. default: '$vuetify.fileUpload.divider'
  27. },
  28. title: {
  29. type: String,
  30. default: '$vuetify.fileUpload.title'
  31. },
  32. subtitle: String,
  33. icon: {
  34. type: IconValue,
  35. default: '$upload'
  36. },
  37. modelValue: {
  38. type: [Array, Object],
  39. default: null,
  40. validator: val => {
  41. return wrapInArray(val).every(v => v != null && typeof v === 'object');
  42. }
  43. },
  44. clearable: Boolean,
  45. disabled: Boolean,
  46. hideBrowse: Boolean,
  47. multiple: Boolean,
  48. scrim: {
  49. type: [Boolean, String],
  50. default: true
  51. },
  52. showSize: Boolean,
  53. name: String,
  54. ...makeDelayProps(),
  55. ...makeDensityProps(),
  56. ...only(makeVDividerProps({
  57. length: 150
  58. }), ['length', 'thickness', 'opacity']),
  59. ...makeVSheetProps()
  60. }, 'VFileUpload');
  61. export const VFileUpload = genericComponent()({
  62. name: 'VFileUpload',
  63. inheritAttrs: false,
  64. props: makeVFileUploadProps(),
  65. emits: {
  66. 'update:modelValue': files => true
  67. },
  68. setup(props, _ref) {
  69. let {
  70. attrs,
  71. slots
  72. } = _ref;
  73. const {
  74. t
  75. } = useLocale();
  76. const {
  77. densityClasses
  78. } = useDensity(props);
  79. const model = useProxiedModel(props, 'modelValue', props.modelValue, val => wrapInArray(val), val => props.multiple || Array.isArray(props.modelValue) ? val : val[0]);
  80. const dragOver = shallowRef(false);
  81. const vSheetRef = ref(null);
  82. const inputRef = ref(null);
  83. onMounted(() => {
  84. vSheetRef.value?.$el.addEventListener('dragover', onDragOver);
  85. vSheetRef.value?.$el.addEventListener('drop', onDrop);
  86. });
  87. onUnmounted(() => {
  88. vSheetRef.value?.$el.removeEventListener('dragover', onDragOver);
  89. vSheetRef.value?.$el.removeEventListener('drop', onDrop);
  90. });
  91. function onDragOver(e) {
  92. e.preventDefault();
  93. e.stopImmediatePropagation();
  94. dragOver.value = true;
  95. }
  96. function onDragLeave(e) {
  97. e.preventDefault();
  98. dragOver.value = false;
  99. }
  100. function onDrop(e) {
  101. e.preventDefault();
  102. e.stopImmediatePropagation();
  103. dragOver.value = false;
  104. const files = Array.from(e.dataTransfer?.files ?? []);
  105. if (!files.length) return;
  106. if (!props.multiple) {
  107. model.value = [files[0]];
  108. return;
  109. }
  110. const array = model.value.slice();
  111. for (const file of files) {
  112. if (!array.some(f => f.name === file.name)) {
  113. array.push(file);
  114. }
  115. }
  116. model.value = array;
  117. }
  118. function onClick() {
  119. inputRef.value?.click();
  120. }
  121. function onClickRemove(index) {
  122. model.value = model.value.filter((_, i) => i !== index);
  123. if (model.value.length > 0 || !inputRef.value) return;
  124. inputRef.value.value = '';
  125. }
  126. useRender(() => {
  127. const hasTitle = !!(slots.title || props.title);
  128. const hasIcon = !!(slots.icon || props.icon);
  129. const hasBrowse = !!(!props.hideBrowse && (slots.browse || props.density === 'default'));
  130. const cardProps = VSheet.filterProps(props);
  131. const dividerProps = VDivider.filterProps(props);
  132. const [rootAttrs, inputAttrs] = filterInputAttrs(attrs);
  133. const inputNode = _createVNode("input", _mergeProps({
  134. "ref": inputRef,
  135. "type": "file",
  136. "disabled": props.disabled,
  137. "multiple": props.multiple,
  138. "name": props.name,
  139. "onChange": e => {
  140. if (!e.target) return;
  141. const target = e.target;
  142. model.value = [...(target.files ?? [])];
  143. }
  144. }, inputAttrs), null);
  145. return _createVNode(_Fragment, null, [_createVNode(VSheet, _mergeProps({
  146. "ref": vSheetRef
  147. }, cardProps, {
  148. "class": ['v-file-upload', {
  149. 'v-file-upload--clickable': !hasBrowse,
  150. 'v-file-upload--disabled': props.disabled,
  151. 'v-file-upload--dragging': dragOver.value
  152. }, densityClasses.value],
  153. "onDragleave": onDragLeave,
  154. "onDragover": onDragOver,
  155. "onDrop": onDrop,
  156. "onClick": !hasBrowse ? onClick : undefined
  157. }, rootAttrs), {
  158. default: () => [hasIcon && _createVNode("div", {
  159. "key": "icon",
  160. "class": "v-file-upload-icon"
  161. }, [!slots.icon ? _createVNode(VIcon, {
  162. "key": "icon-icon",
  163. "icon": props.icon
  164. }, null) : _createVNode(VDefaultsProvider, {
  165. "key": "icon-defaults",
  166. "defaults": {
  167. VIcon: {
  168. icon: props.icon
  169. }
  170. }
  171. }, {
  172. default: () => [slots.icon()]
  173. })]), hasTitle && _createVNode("div", {
  174. "key": "title",
  175. "class": "v-file-upload-title"
  176. }, [slots.title?.() ?? t(props.title)]), props.density === 'default' && _createVNode(_Fragment, null, [_createVNode("div", {
  177. "key": "upload-divider",
  178. "class": "v-file-upload-divider"
  179. }, [slots.divider?.() ?? _createVNode(VDivider, dividerProps, {
  180. default: () => [t(props.dividerText)]
  181. })]), hasBrowse && _createVNode(_Fragment, null, [!slots.browse ? _createVNode(VBtn, {
  182. "readonly": props.disabled,
  183. "size": "large",
  184. "text": t(props.browseText),
  185. "variant": "tonal",
  186. "onClick": onClick
  187. }, null) : _createVNode(VDefaultsProvider, {
  188. "defaults": {
  189. VBtn: {
  190. readonly: props.disabled,
  191. size: 'large',
  192. text: t(props.browseText),
  193. variant: 'tonal'
  194. }
  195. }
  196. }, {
  197. default: () => [slots.browse({
  198. props: {
  199. onClick
  200. }
  201. })]
  202. })]), props.subtitle && _createVNode("div", {
  203. "class": "v-file-upload-subtitle"
  204. }, [props.subtitle])]), _createVNode(VOverlay, {
  205. "model-value": dragOver.value,
  206. "contained": true,
  207. "scrim": props.scrim
  208. }, null), slots.input?.({
  209. inputNode
  210. }) ?? inputNode]
  211. }), model.value.length > 0 && _createVNode("div", {
  212. "class": "v-file-upload-items"
  213. }, [model.value.map((file, i) => {
  214. const slotProps = {
  215. file,
  216. props: {
  217. 'onClick:remove': () => onClickRemove(i)
  218. }
  219. };
  220. return _createVNode(VDefaultsProvider, {
  221. "key": i,
  222. "defaults": {
  223. VFileUploadItem: {
  224. file,
  225. clearable: props.clearable,
  226. disabled: props.disabled,
  227. showSize: props.showSize
  228. }
  229. }
  230. }, {
  231. default: () => [slots.item?.(slotProps) ?? _createVNode(VFileUploadItem, {
  232. "key": i,
  233. "onClick:remove": () => onClickRemove(i)
  234. }, slots)]
  235. });
  236. })])]);
  237. });
  238. }
  239. });
  240. //# sourceMappingURL=VFileUpload.mjs.map