UNPKG

bootstrap-vue-next

Version:

Seamless integration of Vue 3, Bootstrap 5, and TypeScript for modern, type-safe UI development

412 lines (411 loc) 14.7 kB
import { d as useDropZone, m as useFileDialog } from "./dist-Dn5blevd.mjs"; import { o as isEmptySlot } from "./dom-AhkaSoh8.mjs"; import { t as useDefaults } from "./useDefaults-BKgBaqOV.mjs"; import { t as useId$1 } from "./useId-BKZFSYm8.mjs"; import { t as useStateClass } from "./useStateClass-DXbp5rFK.mjs"; import { computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, defineComponent, mergeModels, mergeProps, nextTick, normalizeClass, onMounted, openBlock, ref, renderSlot, toDisplayString, unref, useAttrs, useModel, useSlots, useTemplateRef, watch, withModifiers } from "vue"; //#region src/components/BFormFile/BFormFile.vue?vue&type=script&setup=true&lang.ts var _hoisted_1 = ["for"]; var _hoisted_2 = ["aria-disabled"]; var _hoisted_3 = [ "id", "disabled", "aria-label", "aria-labelledby" ]; var _hoisted_4 = { class: "b-form-file-text" }; var _hoisted_5 = { key: 0 }; var _hoisted_6 = { key: 1, class: "text-muted" }; var _hoisted_7 = { key: 0, class: "b-form-file-drag-overlay" }; var _hoisted_8 = { class: "b-form-file-drag-text" }; var _hoisted_9 = [ "name", "form", "multiple", "disabled", "required", "accept", "capture", "directory", "webkitdirectory" ]; var _hoisted_10 = [ "id", "form", "name", "multiple", "disabled", "capture", "accept", "required", "aria-label", "aria-labelledby", "aria-required", "directory", "webkitdirectory" ]; var _hoisted_11 = { key: 3, class: "b-form-file-display mt-2" }; var _hoisted_12 = { key: 0, class: "small text-muted" }; var _hoisted_13 = { key: 1, class: "small text-muted" }; var _hoisted_14 = { key: 4, class: "visually-hidden", "aria-live": "polite", "aria-atomic": "true" }; //#endregion //#region src/components/BFormFile/BFormFile.vue var BFormFile_default = /* @__PURE__ */ defineComponent({ inheritAttrs: false, __name: "BFormFile", props: /* @__PURE__ */ mergeModels({ ariaLabel: { default: void 0 }, ariaLabelledby: { default: void 0 }, accept: { default: "" }, autofocus: { type: Boolean, default: false }, browseText: { default: void 0 }, capture: { default: void 0 }, directory: { type: Boolean, default: false }, disabled: { type: Boolean, default: false }, dropPlaceholder: { default: void 0 }, fileNameFormatter: { type: Function, default: void 0 }, form: { default: void 0 }, id: { default: void 0 }, label: { default: "" }, labelClass: { default: void 0 }, multiple: { type: Boolean, default: false }, name: { default: void 0 }, noButton: { type: Boolean, default: false }, noDrop: { type: Boolean, default: false }, plain: { type: Boolean, default: false }, placeholder: { default: "No file chosen" }, required: { type: Boolean, default: false }, showFileNames: { type: Boolean, default: false }, size: { default: void 0 }, state: { type: [Boolean, null], default: null } }, { "modelValue": { default: null }, "modelModifiers": {} }), emits: /* @__PURE__ */ mergeModels(["change"], ["update:modelValue"]), setup(__props, { expose: __expose, emit: __emit }) { const props = useDefaults(__props, "BFormFile"); const slots = useSlots(); const emit = __emit; const modelValue = useModel(__props, "modelValue"); const attrs = useAttrs(); const processedAttrs = computed(() => { if (props.plain) return { rootAttrs: {}, dropZoneAttrs: {}, inputAttrs: attrs }; const { class: rootClass, style: rootStyle, title: dropZoneTitle, ...inputAttrs } = attrs; const rootAttrs = {}; const dropZoneAttrs = {}; if (rootClass !== void 0) rootAttrs.class = rootClass; if (rootStyle !== void 0) rootAttrs.style = rootStyle; if (dropZoneTitle !== void 0) dropZoneAttrs.title = dropZoneTitle; return { rootAttrs, dropZoneAttrs, inputAttrs }; }); const computedId = useId$1(() => props.id); const stateClass = useStateClass(() => props.state); const rootRef = useTemplateRef("rootRef"); const dropZoneRef = useTemplateRef("dropZoneRef"); const browseButtonRef = useTemplateRef("browseButtonRef"); const plainInputRef = useTemplateRef("plainInputRef"); const customInputRef = useTemplateRef("customInputRef"); const computedAccept = computed(() => typeof props.accept === "string" ? props.accept : props.accept.join(",")); const { open, reset: resetDialog, onChange: onDialogChange } = useFileDialog({ accept: computedAccept.value, multiple: props.multiple || props.directory, directory: props.directory, input: customInputRef }); const { isOverDropZone } = useDropZone(dropZoneRef, { onDrop: (files) => { if (files && !props.noDrop) handleFiles(files); }, multiple: props.multiple || props.directory }); const hasLabelSlot = computed(() => !isEmptySlot(slots.label)); const hasPlaceholderSlot = computed(() => !isEmptySlot(slots.placeholder)); const computedClasses = computed(() => [stateClass.value, { [`form-control-${props.size}`]: props.size !== void 0 }]); const computedPlainClasses = computed(() => [ "form-control", stateClass.value, { [`form-control-${props.size}`]: props.size !== void 0 } ]); const internalFiles = ref([]); const selectedFiles = computed(() => internalFiles.value); const hasFiles = computed(() => selectedFiles.value.length > 0); const fileNames = computed(() => selectedFiles.value.map((file) => file.name)); const formattedFileNames = computed(() => { if (!hasFiles.value) return ""; if (props.fileNameFormatter) return props.fileNameFormatter(selectedFiles.value); const names = fileNames.value; if (names.length === 1) return names[0]; return `${names.length} files selected`; }); const showExternalDisplay = computed(() => !props.plain && props.showFileNames && (hasFiles.value || props.placeholder)); const ariaLiveMessage = computed(() => { if (!hasFiles.value) return ""; const count = selectedFiles.value.length; if (count === 1) return `File selected: ${selectedFiles.value[0]?.name}`; return `${count} files selected`; }); const effectiveBrowseText = computed(() => props.browseText ?? "Browse"); const effectiveDropPlaceholder = computed(() => props.dropPlaceholder ?? "Drop files here..."); const isFileAccepted = (file) => { if (!computedAccept.value) return true; return computedAccept.value.split(",").map((type) => type.trim()).some((acceptType) => { if (acceptType.startsWith(".")) return file.name.toLowerCase().endsWith(acceptType.toLowerCase()); if (!acceptType.includes("*")) return file.type === acceptType; const slashIndex = acceptType.indexOf("/"); if (slashIndex === -1) return false; const category = acceptType.slice(0, slashIndex); if (category === "*") return true; return file.type.startsWith(`${category}/`); }); }; const handleFiles = (files, nativeEvent) => { let fileArray = []; if (nativeEvent) { const input = nativeEvent.target; fileArray = input.files ? Array.from(input.files) : []; } else { fileArray = Array.from(files).filter((file) => isFileAccepted(file)); if (customInputRef.value && typeof DataTransfer !== "undefined") try { const dataTransfer = new DataTransfer(); fileArray.forEach((file) => dataTransfer.items.add(file)); customInputRef.value.files = dataTransfer.files; } catch {} } internalFiles.value = fileArray; if (fileArray.length === 0) modelValue.value = null; else if (props.directory || props.multiple) modelValue.value = fileArray; else { const [firstFile] = fileArray; if (firstFile) modelValue.value = firstFile; } nextTick(() => { if (nativeEvent) emit("change", nativeEvent); else { const changeEvent = new CustomEvent("change", { bubbles: true, cancelable: false, detail: { files: fileArray, target: { files: fileArray } } }); Object.defineProperty(changeEvent, "files", { value: fileArray, enumerable: true }); emit("change", changeEvent); } }); }; const openFileDialog = () => { if (!props.disabled) open({ accept: computedAccept.value, multiple: props.multiple || props.directory, directory: props.directory }); }; const handleControlClick = () => { if (!props.disabled) openFileDialog(); }; const onPlainChange = (e) => { const input = e.target; if (input.files) handleFiles(input.files, e); }; onDialogChange((files) => { if (files) handleFiles(files); }); const reset = () => { internalFiles.value = []; modelValue.value = null; resetDialog(); if (plainInputRef.value) plainInputRef.value.value = ""; }; const focus = () => { if (props.plain) plainInputRef.value?.focus(); else browseButtonRef.value?.focus(); }; const blur = () => { if (props.plain) plainInputRef.value?.blur(); else browseButtonRef.value?.blur(); }; onMounted(() => { if (props.autofocus) nextTick(() => { focus(); }); }); watch(() => props.autofocus, (autofocus) => { if (autofocus) focus(); }); watch(modelValue, (newValue) => { if (newValue === null) { internalFiles.value = []; if (plainInputRef.value) plainInputRef.value.value = ""; } else if (Array.isArray(newValue)) internalFiles.value = newValue; else internalFiles.value = [newValue]; }); __expose({ blur, element: computed(() => props.plain ? plainInputRef.value : browseButtonRef.value), focus, reset }); return (_ctx, _cache) => { return openBlock(), createElementBlock("div", mergeProps({ ref_key: "rootRef", ref: rootRef }, processedAttrs.value.rootAttrs, { class: "b-form-file-root" }), [ hasLabelSlot.value || unref(props).label ? (openBlock(), createElementBlock("label", { key: 0, class: normalizeClass(["form-label", unref(props).labelClass]), for: unref(computedId) }, [renderSlot(_ctx.$slots, "label", {}, () => [createTextVNode(toDisplayString(unref(props).label), 1)])], 10, _hoisted_1)) : createCommentVNode("", true), !unref(props).plain ? (openBlock(), createElementBlock("div", mergeProps({ key: 1, ref_key: "dropZoneRef", ref: dropZoneRef }, processedAttrs.value.dropZoneAttrs, { class: ["b-form-file-wrapper", { "b-form-file-dragging": unref(isOverDropZone) && !unref(props).noDrop, "b-form-file-has-files": hasFiles.value }] }), [ createElementVNode("div", { class: normalizeClass(["b-form-file-control", computedClasses.value]), "aria-disabled": unref(props).disabled, onClick: handleControlClick }, [!unref(props).noButton ? (openBlock(), createElementBlock("button", { key: 0, id: unref(computedId), ref_key: "browseButtonRef", ref: browseButtonRef, type: "button", class: "b-form-file-button", disabled: unref(props).disabled, "aria-label": unref(props).ariaLabel, "aria-labelledby": unref(props).ariaLabelledby, onClick: withModifiers(openFileDialog, ["stop"]) }, toDisplayString(effectiveBrowseText.value), 9, _hoisted_3)) : createCommentVNode("", true), createElementVNode("div", _hoisted_4, [renderSlot(_ctx.$slots, "file-name", { files: selectedFiles.value, names: fileNames.value }, () => [hasFiles.value ? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(formattedFileNames.value), 1)) : hasPlaceholderSlot.value || unref(props).placeholder ? (openBlock(), createElementBlock("span", _hoisted_6, [renderSlot(_ctx.$slots, "placeholder", {}, () => [createTextVNode(toDisplayString(unref(props).placeholder), 1)])])) : createCommentVNode("", true)])])], 10, _hoisted_2), unref(isOverDropZone) && !unref(props).noDrop ? (openBlock(), createElementBlock("div", _hoisted_7, [renderSlot(_ctx.$slots, "drop-placeholder", {}, () => [createElementVNode("div", _hoisted_8, toDisplayString(effectiveDropPlaceholder.value), 1)])])) : createCommentVNode("", true), createElementVNode("input", mergeProps({ ref_key: "customInputRef", ref: customInputRef }, processedAttrs.value.inputAttrs, { type: "file", name: unref(props).name, form: unref(props).form, multiple: unref(props).multiple || unref(props).directory, disabled: unref(props).disabled, required: unref(props).required, accept: computedAccept.value || void 0, capture: unref(props).capture, directory: unref(props).directory || void 0, webkitdirectory: unref(props).directory || void 0, tabindex: "-1", "aria-hidden": "true", style: { "position": "absolute", "z-index": "-5", "width": "0", "height": "0", "opacity": "0", "overflow": "hidden", "pointer-events": "none" } }), null, 16, _hoisted_9) ], 16)) : (openBlock(), createElementBlock("input", mergeProps({ key: 2, id: unref(computedId), ref_key: "plainInputRef", ref: plainInputRef }, processedAttrs.value.inputAttrs, { type: "file", class: computedPlainClasses.value, form: unref(props).form, name: unref(props).name, multiple: unref(props).multiple || unref(props).directory, disabled: unref(props).disabled, capture: unref(props).capture, accept: computedAccept.value || void 0, required: unref(props).required || void 0, "aria-label": unref(props).ariaLabel, "aria-labelledby": unref(props).ariaLabelledby, "aria-required": unref(props).required || void 0, directory: unref(props).directory || void 0, webkitdirectory: unref(props).directory || void 0, onChange: onPlainChange }), null, 16, _hoisted_10)), showExternalDisplay.value ? (openBlock(), createElementBlock("div", _hoisted_11, [renderSlot(_ctx.$slots, "file-name", { files: selectedFiles.value, names: fileNames.value }, () => [hasFiles.value ? (openBlock(), createElementBlock("div", _hoisted_12, toDisplayString(formattedFileNames.value), 1)) : hasPlaceholderSlot.value || unref(props).placeholder ? (openBlock(), createElementBlock("div", _hoisted_13, [renderSlot(_ctx.$slots, "placeholder", {}, () => [createTextVNode(toDisplayString(unref(props).placeholder), 1)])])) : createCommentVNode("", true)])])) : createCommentVNode("", true), !unref(props).plain ? (openBlock(), createElementBlock("div", _hoisted_14, toDisplayString(ariaLiveMessage.value), 1)) : createCommentVNode("", true) ], 16); }; } }); //#endregion export { BFormFile_default as t }; //# sourceMappingURL=BFormFile-DUd50zn5.mjs.map