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
JavaScript
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