bootstrap-vue-next
Version:
Seamless integration of Vue 3, Bootstrap 5, and TypeScript for modern, type-safe UI development
506 lines (505 loc) • 19.6 kB
JavaScript
import { h as modalManagerKey } from "./keys-CQKrwmvN.mjs";
import { P as tryOnScopeDispose, i as getSSRHandler, o as onKeyStroke, s as unrefElement } from "./dist-Dn5blevd.mjs";
import { i as getSafeDocument, o as isEmptySlot, r as getModalZIndex } from "./dom-AhkaSoh8.mjs";
import { t as useDefaults } from "./useDefaults-BKgBaqOV.mjs";
import { t as useId$1 } from "./useId-BKZFSYm8.mjs";
import { t as useShowHide } from "./useShowHide-IPyuuPL0.mjs";
import { t as useColorVariantClasses } from "./useColorVariantClasses-B6Me_Kx6.mjs";
import { t as BButton_default } from "./BButton-DPCH-g3G.mjs";
import { t as BCloseButton_default } from "./BCloseButton-CjgbR1Ec.mjs";
import { t as ConditionalTeleport_default } from "./ConditionalTeleport-BNsziElf.mjs";
import { t as getElement } from "./getElement-BPjh0Qkh.mjs";
import { n as useActivatedFocusTrap, t as useSafeScrollLock } from "./useSafeScrollLock-DJ1UsIFY.mjs";
import { Fragment, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, getCurrentInstance, guardReactiveProps, inject, mergeModels, mergeProps, nextTick, normalizeClass, normalizeProps, normalizeStyle, onMounted, openBlock, ref, renderSlot, resolveDynamicComponent, toDisplayString, toValue, unref, useModel, useSlots, useTemplateRef, vShow, watch, withCtx, withDirectives, withModifiers } from "vue";
//#region src/composables/useModalManager.ts
var modalOpenClassName = "modal-open";
var useSharedModalStack = () => {
const modalManagerPlugin = inject(modalManagerKey, null);
/**
* Removes an item from both the stack and registry
*/
const dispose = (modal) => {
modalManagerPlugin?.removeStack(modal);
modalManagerPlugin?.removeRegistry(modal);
};
const updateHTMLAttrs = getSSRHandler("updateHTMLAttrs", (selector, attribute, value) => {
const el = typeof selector !== "string" ? unrefElement(selector) : selector ? getSafeDocument()?.querySelector(selector) : void 0;
if (!el) return;
if (attribute === "class") el.classList.toggle(modalOpenClassName, value === modalOpenClassName);
else el.setAttribute(attribute, value);
});
tryOnScopeDispose(() => {
if (modalManagerPlugin?.countStack.value === 0) updateHTMLAttrs("body", "class", "");
});
watch(() => modalManagerPlugin?.countStack.value, (newValue) => {
if (newValue === void 0) return;
updateHTMLAttrs("body", "class", newValue > 0 ? modalOpenClassName : "");
});
return {
...modalManagerPlugin,
dispose
};
};
var useModalManager = (modalOpen, initialValue) => {
const { pushRegistry, pushStack, removeStack, stack, dispose, countStack } = useSharedModalStack();
const currentModal = getCurrentInstance();
if (!currentModal || currentModal.type.__name !== "BModal") throw new Error("useModalManager must only use in BModal component");
pushRegistry?.(currentModal);
tryOnScopeDispose(() => {
dispose(currentModal);
});
const setInStack = (newValue, oldValue) => {
if (newValue) pushStack?.(currentModal);
else if (oldValue && !newValue) removeStack?.(currentModal);
};
setInStack(initialValue, initialValue);
watch(modalOpen, setInStack);
return {
activePosition: computed(() => stack?.value.findIndex((el) => toValue(el.exposed?.id) === toValue(currentModal.exposed?.id))),
activeModalCount: countStack,
stackWithoutSelf: computed(() => stack?.value.filter((el) => toValue(el.exposed?.id) !== toValue(currentModal.exposed?.id)) ?? [])
};
};
//#endregion
//#region src/components/BModal/BModal.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1 = [
"id",
"aria-labelledby",
"aria-describedby"
];
var _hoisted_2 = ["id"];
var fallbackClassSelector = "modal-fallback-focus";
//#endregion
//#region src/components/BModal/BModal.vue
var BModal_default = /* @__PURE__ */ defineComponent({
inheritAttrs: false,
__name: "BModal",
props: /* @__PURE__ */ mergeModels({
focus: {
type: [
String,
Boolean,
Object,
null
],
default: void 0
},
backdropFirst: {
type: Boolean,
default: false
},
body: { default: void 0 },
bodyAttrs: { default: void 0 },
bodyBgVariant: { default: null },
bodyClass: { default: null },
bodyScrolling: {
type: Boolean,
default: false
},
bodyTextVariant: { default: null },
bodyVariant: { default: null },
busy: {
type: Boolean,
default: false
},
buttonSize: { default: "md" },
cancelClass: { default: void 0 },
cancelDisabled: {
type: Boolean,
default: false
},
cancelTitle: { default: "Cancel" },
cancelVariant: { default: "secondary" },
centered: {
type: Boolean,
default: false
},
contentClass: { default: void 0 },
dialogClass: { default: void 0 },
footerBgVariant: { default: null },
footerBorderVariant: { default: null },
footerClass: { default: void 0 },
footerTextVariant: { default: null },
footerVariant: { default: null },
fullscreen: {
type: [Boolean, String],
default: false
},
headerAttrs: { default: void 0 },
headerBgVariant: { default: null },
headerBorderVariant: { default: null },
headerClass: { default: void 0 },
headerCloseClass: { default: void 0 },
headerCloseLabel: { default: "Close" },
headerCloseVariant: { default: "secondary" },
headerTextVariant: { default: null },
headerVariant: { default: null },
noBackdrop: {
type: Boolean,
default: false
},
noFooter: {
type: Boolean,
default: false
},
noHeader: {
type: Boolean,
default: false
},
noHeaderClose: {
type: Boolean,
default: false
},
id: { default: void 0 },
modalClass: { default: void 0 },
noCloseOnBackdrop: {
type: Boolean,
default: false
},
noCloseOnEsc: {
type: Boolean,
default: false
},
noTrap: {
type: Boolean,
default: false
},
noStacking: { type: Boolean },
okClass: { default: void 0 },
okDisabled: {
type: Boolean,
default: false
},
okOnly: {
type: Boolean,
default: false
},
okTitle: { default: "OK" },
okVariant: { default: "primary" },
scrollable: {
type: Boolean,
default: false
},
size: { default: "md" },
title: { default: void 0 },
titleClass: { default: void 0 },
titleVisuallyHidden: {
type: Boolean,
default: false
},
titleTag: { default: "h5" },
teleportDisabled: {
type: Boolean,
default: false
},
teleportTo: { default: "body" },
initialAnimation: {
type: Boolean,
default: false
},
noAnimation: { type: Boolean },
noFade: {
type: Boolean,
default: false
},
lazy: {
type: Boolean,
default: false
},
unmountLazy: {
type: Boolean,
default: false
},
show: {
type: Boolean,
default: false
},
transProps: { default: void 0 },
visible: {
type: Boolean,
default: false
}
}, {
"modelValue": {
type: Boolean,
default: false
},
"modelModifiers": {}
}),
emits: /* @__PURE__ */ mergeModels([
"backdrop",
"cancel",
"close",
"esc",
"ok",
"hide",
"hide-prevented",
"hidden",
"show",
"show-prevented",
"shown",
"toggle",
"toggle-prevented"
], ["update:modelValue"]),
setup(__props, { expose: __expose, emit: __emit }) {
const props = useDefaults(__props, "BModal");
const emit = __emit;
const slots = useSlots();
const computedId = useId$1(() => props.id, "modal");
const modelValue = useModel(__props, "modelValue");
const element = useTemplateRef("_element");
const fallbackFocusElement = useTemplateRef("_fallbackFocusElement");
const okButton = useTemplateRef("_okButton");
const cancelButton = useTemplateRef("_cancelButton");
const closeButton = useTemplateRef("_closeButton");
const pickFocusItem = () => {
if (props.focus && typeof props.focus !== "boolean") {
if (props.focus === "ok") return okButton;
else if (props.focus === "close") return closeButton;
else if (props.focus === "cancel") return cancelButton;
return getElement(props.focus, element.value ?? void 0) ?? element.value;
}
return element;
};
let activeElement = null;
const onAfterEnter = () => {
const doc = getSafeDocument();
if (props.noTrap && props.focus !== false && doc) {
activeElement = doc.activeElement;
if (activeElement === element.value) activeElement = null;
const el = unrefElement(pickFocusItem());
if (!el) return;
el?.focus();
if (el.tagName && el.tagName.toLowerCase() === "input" && typeof el.select === "function") el.select();
}
};
const onAfterLeave = () => {
if (props.noTrap && props.focus !== false && activeElement) {
activeElement?.focus();
activeElement = null;
}
};
const { showRef, renderRef, renderBackdropRef, hide, show, toggle, computedNoAnimation, transitionProps, backdropTransitionProps, isLeaving, isVisible, trapActive, contentShowing, backdropReady, backdropVisible } = useShowHide(modelValue, props, emit, element, computedId, { transitionProps: {
onAfterEnter,
onAfterLeave
} });
const { needsFallback } = useActivatedFocusTrap({
element,
isActive: trapActive,
noTrap: () => props.noTrap,
fallbackFocus: {
ref: fallbackFocusElement,
classSelector: fallbackClassSelector
},
focus: () => props.focus === false ? false : unrefElement(pickFocusItem()) ?? void 0
});
onKeyStroke("Escape", () => {
hide("esc");
}, { target: element });
useSafeScrollLock(showRef, () => props.bodyScrolling);
const hasHeaderCloseSlot = computed(() => !isEmptySlot(slots["header-close"]));
const modalDialogClasses = computed(() => [props.dialogClass, {
"modal-fullscreen": props.fullscreen === true,
[`modal-fullscreen-${props.fullscreen}-down`]: typeof props.fullscreen === "string",
[`modal-${props.size}`]: props.size !== "md",
"modal-dialog-centered": props.centered,
"modal-dialog-scrollable": props.scrollable
}]);
const bodyColorClasses = useColorVariantClasses(() => ({
bgVariant: props.bodyBgVariant,
textVariant: props.bodyTextVariant,
variant: props.bodyVariant
}));
const bodyClasses = computed(() => [props.bodyClass, bodyColorClasses.value]);
const headerColorClasses = useColorVariantClasses(() => ({
bgVariant: props.headerBgVariant,
textVariant: props.headerTextVariant,
variant: props.headerVariant,
borderVariant: props.headerBorderVariant
}));
const headerClasses = computed(() => [props.headerClass, headerColorClasses.value]);
const headerCloseAttrs = computed(() => ({
variant: hasHeaderCloseSlot.value ? props.headerCloseVariant : void 0,
class: props.headerCloseClass
}));
const footerColorClasses = useColorVariantClasses(() => ({
bgVariant: props.footerBgVariant,
textVariant: props.footerTextVariant,
variant: props.footerVariant,
borderVariant: props.footerBorderVariant
}));
const footerClasses = computed(() => [props.footerClass, footerColorClasses.value]);
const titleClasses = computed(() => [props.titleClass, { ["visually-hidden"]: props.titleVisuallyHidden }]);
const disableCancel = computed(() => props.cancelDisabled || props.busy);
const disableOk = computed(() => props.okDisabled || props.busy);
const { activePosition, activeModalCount, stackWithoutSelf } = useModalManager(showRef, modelValue.value);
const sharedClasses = computed(() => ({
[`stack-position-${activePosition?.value ?? 0}`]: true,
[`stack-inverse-position-${(activeModalCount?.value ?? 1) - 1 - (activePosition?.value ?? 0)}`]: true
}));
watch(stackWithoutSelf, (newValue, oldValue) => {
if (newValue.length > oldValue.length && showRef.value === true && props.noStacking) hide();
});
const defaultModalDialogZIndex = ref(getModalZIndex(element.value ?? getSafeDocument()?.body));
onMounted(() => {
watch(renderRef, (v) => {
if (!v) return;
nextTick(() => {
if (!element.value) return;
defaultModalDialogZIndex.value = getModalZIndex(element.value);
});
}, { immediate: true });
});
const computedZIndexNumber = computed(() => showRef.value || isLeaving.value ? defaultModalDialogZIndex.value - ((activeModalCount?.value ?? 0) * 2 - (activePosition?.value ?? 0) * 2) : defaultModalDialogZIndex.value);
const computedZIndex = computed(() => ({
"z-index": computedZIndexNumber.value,
"--b-position": activePosition?.value ?? 0,
"--b-inverse-position": (activeModalCount?.value ?? 1) - 1 - (activePosition?.value ?? 0),
"--b-count": activeModalCount?.value ?? 0
}));
const computedZIndexBackdrop = computed(() => ({
"z-index": computedZIndexNumber.value - 1,
"--b-position": activePosition?.value ?? 0,
"--b-inverse-position": (activeModalCount?.value ?? 1) - 1 - (activePosition?.value ?? 0),
"--b-count": activeModalCount?.value ?? 0
}));
const sharedSlots = computed(() => ({
id: computedId.value,
cancel: () => {
hide("cancel");
},
close: () => {
hide("close");
},
hide,
show,
toggle,
ok: () => {
hide("ok");
},
active: showRef.value,
visible: showRef.value
}));
__expose({
hide,
id: computedId,
show,
toggle,
visible: showRef
});
return (_ctx, _cache) => {
return openBlock(), createBlock(ConditionalTeleport_default, {
to: unref(props).teleportTo,
disabled: unref(props).teleportDisabled
}, {
default: withCtx(() => [unref(renderRef) || unref(contentShowing) ? (openBlock(), createBlock(Transition, mergeProps({ key: 0 }, unref(transitionProps), { appear: modelValue.value || unref(props).visible }), {
default: withCtx(() => [withDirectives(createElementVNode("div", mergeProps({
id: unref(computedId),
ref: "_element",
class: ["modal", [unref(props).modalClass, {
fade: !unref(computedNoAnimation),
show: unref(isVisible),
...sharedClasses.value
}]],
role: "dialog",
"aria-labelledby": !unref(props).noHeader ? `${unref(computedId)}-label` : void 0,
"aria-describedby": `${unref(computedId)}-body`,
tabindex: "-1"
}, _ctx.$attrs, {
style: [computedZIndex.value, { "display": "block" }],
onMousedown: _cache[4] || (_cache[4] = withModifiers(($event) => unref(hide)("backdrop"), ["left", "self"]))
}), [createElementVNode("div", { class: normalizeClass(["modal-dialog", modalDialogClasses.value]) }, [unref(contentShowing) ? (openBlock(), createElementBlock("div", {
key: 0,
class: normalizeClass(["modal-content", unref(props).contentClass])
}, [
!unref(props).noHeader ? (openBlock(), createElementBlock("div", mergeProps({
key: 0,
class: ["modal-header", headerClasses.value]
}, unref(props).headerAttrs), [renderSlot(_ctx.$slots, "header", normalizeProps(guardReactiveProps(sharedSlots.value)), () => [(openBlock(), createBlock(resolveDynamicComponent(unref(props).titleTag), {
id: `${unref(computedId)}-label`,
class: normalizeClass(["modal-title", titleClasses.value])
}, {
default: withCtx(() => [renderSlot(_ctx.$slots, "title", normalizeProps(guardReactiveProps(sharedSlots.value)), () => [createTextVNode(toDisplayString(unref(props).title), 1)])]),
_: 3
}, 8, ["id", "class"])), !unref(props).noHeaderClose ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [hasHeaderCloseSlot.value ? (openBlock(), createBlock(BButton_default, mergeProps({
key: 0,
ref: "_closeButton"
}, headerCloseAttrs.value, { onClick: _cache[0] || (_cache[0] = ($event) => unref(hide)("close")) }), {
default: withCtx(() => [renderSlot(_ctx.$slots, "header-close", normalizeProps(guardReactiveProps(sharedSlots.value)))]),
_: 3
}, 16)) : (openBlock(), createBlock(BCloseButton_default, mergeProps({
key: 1,
ref: "_closeButton",
"aria-label": unref(props).headerCloseLabel
}, headerCloseAttrs.value, { onClick: _cache[1] || (_cache[1] = ($event) => unref(hide)("close")) }), null, 16, ["aria-label"]))], 64)) : createCommentVNode("", true)])], 16)) : createCommentVNode("", true),
createElementVNode("div", mergeProps({
id: `${unref(computedId)}-body`,
class: ["modal-body", bodyClasses.value]
}, unref(props).bodyAttrs), [renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(sharedSlots.value)), () => [createTextVNode(toDisplayString(unref(props).body), 1)])], 16, _hoisted_2),
!unref(props).noFooter ? (openBlock(), createElementBlock("div", {
key: 1,
class: normalizeClass(["modal-footer", footerClasses.value])
}, [renderSlot(_ctx.$slots, "footer", normalizeProps(guardReactiveProps(sharedSlots.value)), () => [renderSlot(_ctx.$slots, "cancel", normalizeProps(guardReactiveProps(sharedSlots.value)), () => [!unref(props).okOnly ? (openBlock(), createBlock(BButton_default, {
key: 0,
ref: "_cancelButton",
disabled: disableCancel.value,
size: unref(props).buttonSize,
variant: unref(props).cancelVariant,
class: normalizeClass(unref(props).cancelClass),
onClick: _cache[2] || (_cache[2] = ($event) => unref(hide)("cancel"))
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(props).cancelTitle), 1)]),
_: 1
}, 8, [
"disabled",
"size",
"variant",
"class"
])) : createCommentVNode("", true)]), renderSlot(_ctx.$slots, "ok", normalizeProps(guardReactiveProps(sharedSlots.value)), () => [createVNode(BButton_default, {
ref: "_okButton",
disabled: disableOk.value,
size: unref(props).buttonSize,
variant: unref(props).okVariant,
class: normalizeClass(unref(props).okClass),
onClick: _cache[3] || (_cache[3] = ($event) => unref(hide)("ok"))
}, {
default: withCtx(() => [createTextVNode(toDisplayString(unref(props).okTitle), 1)]),
_: 1
}, 8, [
"disabled",
"size",
"variant",
"class"
])])])], 2)) : createCommentVNode("", true)
], 2)) : createCommentVNode("", true)], 2), unref(needsFallback) ? (openBlock(), createElementBlock("div", {
key: 0,
ref: "_fallbackFocusElement",
class: normalizeClass(fallbackClassSelector),
tabindex: "0",
style: {
"width": "0",
"height": "0",
"overflow": "hidden"
}
}, null, 512)) : createCommentVNode("", true)], 16, _hoisted_1), [[vShow, unref(showRef) && (unref(backdropReady) && unref(props).backdropFirst || !unref(props).backdropFirst)]])]),
_: 3
}, 16, ["appear"])) : createCommentVNode("", true), !unref(props).noBackdrop ? renderSlot(_ctx.$slots, "backdrop", normalizeProps(mergeProps({ key: 1 }, sharedSlots.value)), () => [unref(renderBackdropRef) ? (openBlock(), createBlock(Transition, normalizeProps(mergeProps({ key: 0 }, unref(backdropTransitionProps))), {
default: withCtx(() => [withDirectives(createElementVNode("div", {
class: normalizeClass(["modal-backdrop", {
fade: !unref(computedNoAnimation),
show: unref(backdropVisible) || unref(computedNoAnimation),
...sharedClasses.value
}]),
style: normalizeStyle(computedZIndexBackdrop.value),
onClick: _cache[5] || (_cache[5] = ($event) => unref(hide)("backdrop"))
}, null, 6), [[vShow, unref(showRef) || unref(isLeaving) && unref(props).backdropFirst && !unref(computedNoAnimation)]])]),
_: 1
}, 16)) : createCommentVNode("", true)]) : createCommentVNode("", true)]),
_: 3
}, 8, ["to", "disabled"]);
};
}
});
//#endregion
export { useSharedModalStack as n, BModal_default as t };
//# sourceMappingURL=BModal-DkX-2F1v.mjs.map