bootstrap-vue-next
Version:
Seamless integration of Vue 3, Bootstrap 5, and TypeScript for modern, type-safe UI development
567 lines (566 loc) • 21.3 kB
JavaScript
import { C as tabsInjectionKey } from "./keys-CQKrwmvN.mjs";
import { n as createReusableTemplate } from "./dist-Dn5blevd.mjs";
import { c as sortSlotElementsByPosition, i as getSafeDocument } from "./dom-AhkaSoh8.mjs";
import { t as useId$1 } from "./useId-CCwnEmGh.mjs";
import { t as useDefaults } from "./useDefaults-CCWS15M8.mjs";
import { t as flattenFragments } from "./flattenFragments-B1W_x5L2.mjs";
import { n as BvEvent } from "./classes-D6JE1Dex.mjs";
import { t as useAlignment } from "./useAlignment-CwuLc9mf.mjs";
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, inject, mergeModels, mergeProps, nextTick, normalizeClass, onMounted, onUnmounted, openBlock, provide, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, toRef, unref, useAttrs, useModel, useSlots, useTemplateRef, watch, withCtx, withKeys, withModifiers } from "vue";
//#endregion
//#region src/components/BTabs/BTab.vue
var BTab_default = /* @__PURE__ */ defineComponent({
inheritAttrs: false,
__name: "BTab",
props: /* @__PURE__ */ mergeModels({
buttonId: { default: void 0 },
disabled: {
type: Boolean,
default: false
},
id: { default: void 0 },
lazy: {
type: Boolean,
default: void 0
},
unmountLazy: {
type: Boolean,
default: void 0
},
noBody: {
type: Boolean,
default: false
},
tag: { default: "div" },
title: { default: void 0 },
titleItemClass: { default: void 0 },
titleLinkAttrs: { default: void 0 },
titleLinkClass: { default: void 0 }
}, {
"active": {
type: Boolean,
default: false
},
"activeModifiers": {}
}),
emits: ["update:active"],
setup(__props, { expose: __expose }) {
const props = useDefaults(__props, "BTab");
const slots = useSlots();
const attrs = useAttrs();
const activeModel = useModel(__props, "active");
const parentData = inject(tabsInjectionKey, null);
const localId = ref(props.id);
const internalId = useId$1("", "tabpane");
const computedId = computed(() => props.id ?? localId.value ?? internalId.value);
const computedButtonId = useId$1(() => props.buttonId, "tab");
const lazyRenderCompleted = ref(false);
const el = useTemplateRef("_el");
const processedAttrs = computed(() => {
const { onClick: _, ...tabAttrs } = attrs;
return tabAttrs;
});
function updateTab() {
if (!parentData) return;
const newId = parentData.registerTab(computed(() => ({
internalId: internalId.value,
id: computedId.value,
active: activeModel.value,
buttonId: computedButtonId.value,
disabled: props.disabled,
title: props.title,
titleComponent: slots.title,
titleItemClass: props.titleItemClass,
titleLinkAttrs: props.titleLinkAttrs,
titleLinkClass: props.titleLinkClass,
onClick: attrs.onClick,
el
})));
if (newId !== localId.value) localId.value = newId;
}
if (parentData) {
updateTab();
if (activeModel.value) parentData.activateTab(internalId.value);
}
onUnmounted(() => {
if (!parentData) return;
parentData.unregisterTab(internalId.value);
});
const isActive = computed(() => parentData?.activeId.value === computedId.value);
const show = ref(isActive.value);
const computedLazy = computed(() => !!(parentData?.lazy.value || props.lazy));
const computedActive = computed(() => isActive.value && !props.disabled);
const showSlot = computed(() => computedActive.value || !computedLazy.value || computedLazy.value && !props.unmountLazy && lazyRenderCompleted.value);
watch(showSlot, (shown) => {
if (shown && !lazyRenderCompleted.value) lazyRenderCompleted.value = true;
});
watch(isActive, (active) => {
if (active) {
activeModel.value = true;
setTimeout(() => {
show.value = true;
}, 0);
return;
}
show.value = false;
activeModel.value = false;
});
watch(activeModel, (active) => {
if (props.disabled) {
activeModel.value = false;
return;
}
if (!parentData) return;
if (!active) {
if (isActive.value) parentData.activateTab(void 0);
return;
}
if (!isActive.value) parentData.activateTab(internalId.value);
});
const computedClasses = computed(() => [
{
"active": isActive.value,
"show": show.value,
"card-body": parentData?.card.value && props.noBody === false,
"fade": !parentData?.noFade.value
},
show.value ? parentData?.activeTabClass.value : parentData?.inactiveTabClass.value,
parentData?.tabClass.value
]);
__expose({
activate: () => {
activeModel.value = true;
},
deactivate: () => {
activeModel.value = false;
}
});
return (_ctx, _cache) => {
return openBlock(), createBlock(resolveDynamicComponent(unref(props).tag), mergeProps({
id: computedId.value,
ref: "_el",
class: ["tab-pane", computedClasses.value],
role: "tabpanel",
"aria-labelledby": unref(computedButtonId)
}, processedAttrs.value), {
default: withCtx(() => [showSlot.value ? renderSlot(_ctx.$slots, "default", { key: 0 }) : createCommentVNode("", true)]),
_: 3
}, 16, [
"id",
"class",
"aria-labelledby"
]);
};
}
});
//#endregion
//#region src/components/BTabs/BTabs.vue?vue&type=script&setup=true&lang.ts
var _hoisted_1 = ["aria-orientation"];
var _hoisted_2 = [
"id",
"aria-controls",
"aria-selected",
"disabled",
"tabindex",
"onClick"
];
//#endregion
//#region src/components/BTabs/BTabs.vue
var BTabs_default = /* @__PURE__ */ defineComponent({
__name: "BTabs",
props: /* @__PURE__ */ mergeModels({
activeNavItemClass: { default: void 0 },
activeTabClass: { default: void 0 },
align: { default: void 0 },
card: {
type: Boolean,
default: false
},
contentClass: { default: void 0 },
end: {
type: Boolean,
default: false
},
fill: {
type: Boolean,
default: false
},
id: { default: void 0 },
index: {},
inactiveNavItemClass: { default: void 0 },
inactiveTabClass: { default: void 0 },
justified: {
type: Boolean,
default: false
},
lazy: {
type: Boolean,
default: false
},
navClass: { default: void 0 },
navItemClass: { default: void 0 },
navWrapperClass: { default: void 0 },
noFade: {
type: Boolean,
default: false
},
noKeyNav: {
type: Boolean,
default: false
},
noNavStyle: {
type: Boolean,
default: false
},
pills: {
type: Boolean,
default: false
},
small: {
type: Boolean,
default: false
},
tag: { default: "div" },
tabClass: { default: void 0 },
underline: {
type: Boolean,
default: false
},
vertical: {
type: Boolean,
default: false
}
}, {
"index": { default: -1 },
"indexModifiers": {},
"modelValue": { default: void 0 },
"modelModifiers": {}
}),
emits: /* @__PURE__ */ mergeModels(["activate-tab"], ["update:index", "update:modelValue"]),
setup(__props, { emit: __emit }) {
const props = useDefaults(__props, "BTabs");
const emit = __emit;
const slots = useSlots();
const activeIndex = useModel(__props, "index");
const activeId = useModel(__props, "modelValue");
const ReusableEmptyTab = createReusableTemplate();
const tabsInternal = ref([]);
const tabElementsArray = ref([]);
const isChildActive = ref(false);
const initialIds = ref([]);
const hasExplicitIds = ref(false);
const updateTabElementsArray = () => {
const tabElements = flattenFragments(slots.default?.({}) ?? []);
tabElementsArray.value = (Array.isArray(tabElements) ? tabElements : [tabElements]).filter((tab) => tab.type === BTab_default);
if (initialIds.value.length === 0) {
initialIds.value = tabElementsArray.value.map((tab) => unref(useId$1(() => tab.props?.id, "tabpane")));
hasExplicitIds.value = tabElementsArray.value.some((tab) => tab.props?.id !== void 0);
}
isChildActive.value = tabElementsArray.value.some((tab) => tab.props?.active !== void 0 && tab.props?.active !== false);
};
updateTabElementsArray();
watch(() => slots.default?.({}), () => {
updateTabElementsArray();
nextTick(() => {
sortTabs();
});
});
const tabs = computed(() => {
if (tabsInternal.value.length === 0) {
const _activeIndex = tabElementsArray.value.findIndex((tab) => tab.props?.active !== void 0 && (tab.props.disabled === false || tab.props.disabled === void 0) || activeId.value && tab.props?.id === activeId.value);
return tabElementsArray.value.map((tab, index) => {
const active = _activeIndex !== -1 ? index === _activeIndex : activeIndex.value > -1 ? index === activeIndex.value : index === 0;
return {
id: tab.props?.id ?? initialIds.value[index],
internalId: `premount-${index}`,
buttonId: tab.props?.buttonId,
disabled: tab.props?.disabled,
title: tab.props?.title,
titleComponent: tab.children?.title,
titleItemClass: tab.props?.titleItemClass,
titleLinkAttrs: tab.props?.titleLinkAttrs,
titleLinkClass: tab.props?.titleLinkClass,
onClick: tab.props?.onClick,
active,
navItemClasses: [
{
active,
disabled: !(tab.props?.disabled === false || tab.props?.disabled === void 0)
},
active ? props.activeNavItemClass : props.inactiveNavItemClass,
props.navItemClass
]
};
});
}
return tabsInternal.value.map((_tab) => {
const tab = unref(_tab);
const active = tab.id === activeId.value;
return {
...tab,
active,
navItemClasses: [
{
active,
disabled: tab.disabled
},
active ? props.activeNavItemClass : props.inactiveNavItemClass,
props.navItemClass
]
};
});
});
let initialized = false;
let updateInitialActiveIndex = false;
let updateInitialActiveId = false;
let delayedTabSelection = false;
if (activeIndex.value > -1 && !activeId.value && !hasExplicitIds.value && tabElementsArray.value.length > 0) {
delayedTabSelection = true;
updateInitialActiveId = true;
} else if (activeIndex.value === -1 && activeId.value) if (tabs.value.findIndex((t) => t.id === activeId.value) !== -1) activeIndex.value = tabs.value.findIndex((t) => t.id === activeId.value);
else updateInitialActiveIndex = true;
else if (activeIndex.value > -1 && !activeId.value) if (tabs.value[activeIndex.value]?.id) activeId.value = tabs.value[activeIndex.value]?.id;
else updateInitialActiveId = true;
else if (activeIndex.value === -1 && !activeId.value && !isChildActive.value) {
activeIndex.value = tabs.value.findIndex((t) => t.disabled === void 0 || t.disabled === false);
activeId.value = tabs.value[activeIndex.value]?.id;
} else if (activeIndex.value === -1 && !activeId.value && isChildActive.value) {
activeIndex.value = tabs.value.findIndex((t) => t.active !== void 0 && t.active !== false && (t.disabled === void 0 || t.disabled === false));
activeId.value = tabs.value[activeIndex.value]?.id;
}
function updateInitialIndexAndId() {
if (updateInitialActiveIndex) {
const index = tabs.value.findIndex((t) => t.id === activeId.value);
if (index !== -1) nextTick(() => {
activeIndex.value = index;
updateInitialActiveIndex = false;
});
}
if (updateInitialActiveId) {
if (delayedTabSelection && tabsInternal.value.length === 0) return;
if (activeIndex.value > -1 && tabs.value[activeIndex.value]?.id) nextTick(() => {
activeId.value = tabs.value[activeIndex.value]?.id;
updateInitialActiveId = false;
delayedTabSelection = false;
});
}
}
updateInitialIndexAndId();
const showEmpty = computed(() => !(tabs?.value && tabs.value.length > 0));
const computedClasses = computed(() => ({
"d-flex": props.vertical,
"align-items-start": props.vertical
}));
const alignment = useAlignment(() => props.align);
const navTabsClasses = computed(() => ({
"nav-pills": props.pills,
"nav-underline": props.underline,
"flex-column me-3": props.vertical,
[alignment.value]: props.align !== void 0,
"nav-fill": props.fill,
"card-header-tabs": props.card && !props.pills && !props.underline,
"card-header-pills": props.card && props.pills,
"nav-justified": props.justified,
"nav-tabs": !props.noNavStyle && !props.pills && !props.underline,
"small": props.small
}));
const handleClick = (event, index) => {
if (index >= 0 && !tabs.value[index]?.disabled && tabs.value[index]?.onClick && typeof tabs.value[index].onClick === "function") {
tabs.value[index].onClick?.(event);
if (event.defaultPrevented) {
getSafeDocument()?.getElementById(tabs.value[index].buttonId)?.blur();
return;
}
}
activeIndex.value = index;
};
const keynav = (e, direction) => {
if (tabs.value.length <= 0 || props.noKeyNav) return;
e.preventDefault();
e.stopPropagation();
activeIndex.value = nextIndex(activeIndex.value + direction, direction);
nextTick(() => {
if (activeIndex.value >= 0) getSafeDocument()?.getElementById(tabs.value[activeIndex.value]?.buttonId)?.focus();
});
};
const nextIndex = (start, direction) => {
let index = start;
let minIdx = -1;
let maxIdx = -1;
for (let i = 0; i < tabs.value.length; i++) if (!tabs.value[i]?.disabled) {
if (minIdx === -1) minIdx = i;
maxIdx = i;
}
while (index >= minIdx && index <= maxIdx && tabs.value[index]?.disabled) index += direction;
if (index < minIdx) index = minIdx;
if (index > maxIdx) index = maxIdx;
return index;
};
let previousIndex;
let isReverting = false;
watch(activeIndex, (newValue, oldValue) => {
if (tabs.value.length <= 0 || tabs.value.filter((t) => !t.disabled).length <= 0) return;
if (isReverting) {
isReverting = false;
return;
}
const index = nextIndex(newValue, newValue > oldValue ? 1 : -1);
if (index !== newValue) {
previousIndex = oldValue;
activeIndex.value = index;
return;
}
const tabEvent = new BvEvent("activate-tab", { cancelable: true });
emit("activate-tab", {
newTabId: tabs.value[index]?.id,
prevTabId: tabs.value[previousIndex ?? oldValue]?.id,
newTabIndex: index,
prevTabIndex: previousIndex ?? oldValue,
event: tabEvent
});
if (tabEvent.defaultPrevented) {
isReverting = true;
const prev = previousIndex ?? oldValue ?? nextIndex(0, 1);
previousIndex = void 0;
if (activeId.value !== tabs.value[prev]?.id) activeId.value = tabs.value[prev]?.id;
nextTick(() => {
if (prev >= 0) getSafeDocument()?.getElementById(tabs.value[prev]?.buttonId)?.focus();
});
return;
}
if (activeId.value !== tabs.value[index]?.id) activeId.value = tabs.value[index]?.id;
previousIndex = void 0;
});
watch(activeId, (newValue, oldValue) => {
if (tabs.value.length <= 0 || tabs.value.filter((t) => !t.disabled).length <= 0) return;
const index = tabs.value.findIndex((t) => t.id === newValue);
if (index === activeIndex.value) return;
const oldIndex = tabs.value.findIndex((t) => t.id === oldValue);
if (tabs.value[index]?.disabled) {
activeIndex.value = nextIndex(index, index > oldIndex ? 1 : -1);
return;
}
if (index === -1) {
activeIndex.value = nextIndex(0, 1);
nextTick(() => {
activeId.value = tabs.value[activeIndex.value]?.id;
});
return;
}
activeIndex.value = index;
});
const registerTab = (tab) => {
const idx = tabsInternal.value.findIndex((t) => t.value.internalId === tab.value.internalId);
if (idx === -1) {
tabsInternal.value.push(tab);
if (initialized) nextTick(() => {
sortTabs();
});
else if (delayedTabSelection) nextTick(() => {
updateInitialIndexAndId();
});
} else {
tabsInternal.value[idx] = tab;
if (initialized) sortTabs();
}
const idx2 = tabsInternal.value.findIndex((t) => t.value.internalId === tab.value.internalId);
return tab.value.id ?? (!initialized ? initialIds.value[idx2] : tab.value.internalId);
};
onMounted(() => {
updateInitialIndexAndId();
sortTabs();
initialized = true;
});
const sortTabs = () => {
tabsInternal.value.sort((a, b) => sortSlotElementsByPosition(a.value.el.value, b.value.el.value));
if (activeId.value && activeIndex.value !== tabs.value.findIndex((t) => t.id === activeId.value)) activeIndex.value = tabs.value.findIndex((t) => t.id === activeId.value);
};
const unregisterTab = (id) => {
tabsInternal.value = tabsInternal.value.filter((t) => t.value.internalId !== id);
};
provide(tabsInjectionKey, {
lazy: toRef(() => props.lazy),
card: toRef(() => props.card),
noFade: toRef(() => props.noFade),
activeTabClass: toRef(() => props.activeTabClass),
inactiveTabClass: toRef(() => props.inactiveTabClass),
tabClass: toRef(() => props.tabClass),
registerTab,
unregisterTab,
activeId,
activateTab: (internalId) => {
const idx = tabs.value.findIndex((t) => t.internalId === internalId);
if (internalId === void 0 || idx === -1) {
activeIndex.value = nextIndex(0, 1);
return;
}
activeIndex.value = idx;
}
});
return (_ctx, _cache) => {
return openBlock(), createBlock(resolveDynamicComponent(unref(props).tag), {
id: unref(props).id,
class: normalizeClass(["tabs", computedClasses.value])
}, {
default: withCtx(() => [
createVNode(unref(ReusableEmptyTab).define, null, {
default: withCtx(() => [createElementVNode("div", { class: normalizeClass(["tab-content", unref(props).contentClass]) }, [renderSlot(_ctx.$slots, "default"), showEmpty.value ? (openBlock(), createElementBlock("div", {
key: "bv-empty-tab",
class: normalizeClass(["tab-pane active", { "card-body": unref(props).card }])
}, [renderSlot(_ctx.$slots, "empty")], 2)) : createCommentVNode("", true)], 2)]),
_: 3
}),
unref(props).end ? (openBlock(), createBlock(unref(ReusableEmptyTab).reuse, { key: 0 })) : createCommentVNode("", true),
createElementVNode("div", { class: normalizeClass([unref(props).navWrapperClass, {
"card-header": unref(props).card,
"ms-auto": __props.vertical && unref(props).end
}]) }, [createElementVNode("ul", {
class: normalizeClass(["nav", [navTabsClasses.value, unref(props).navClass]]),
role: "tablist",
"aria-orientation": unref(props).vertical ? "vertical" : "horizontal"
}, [
renderSlot(_ctx.$slots, "tabs-start"),
(openBlock(true), createElementBlock(Fragment, null, renderList(tabs.value, (tab, idx) => {
return openBlock(), createElementBlock("li", {
key: tab.id ?? tab.internalId,
class: normalizeClass(["nav-item", tab.titleItemClass]),
role: "presentation"
}, [createElementVNode("button", mergeProps({
id: tab.buttonId,
class: ["nav-link", [tab.navItemClasses, tab.titleLinkClass]],
role: "tab",
"aria-controls": tab.id,
"aria-selected": tab.active,
disabled: tab.disabled,
tabindex: unref(props).noKeyNav ? void 0 : tab.active ? void 0 : -1,
type: "button"
}, { ref_for: true }, tab.titleLinkAttrs, {
onKeydown: [
_cache[0] || (_cache[0] = withKeys(withModifiers(($event) => !unref(props).vertical && keynav($event, -1), ["exact"]), ["left"])),
_cache[1] || (_cache[1] = withKeys(withModifiers(($event) => !unref(props).vertical && keynav($event, -999), ["shift"]), ["left"])),
_cache[2] || (_cache[2] = withKeys(withModifiers(($event) => unref(props).vertical && keynav($event, -1), ["exact"]), ["up"])),
_cache[3] || (_cache[3] = withKeys(withModifiers(($event) => unref(props).vertical && keynav($event, -999), ["shift"]), ["up"])),
_cache[4] || (_cache[4] = withKeys(withModifiers(($event) => !unref(props).vertical && keynav($event, 1), ["exact"]), ["right"])),
_cache[5] || (_cache[5] = withKeys(withModifiers(($event) => !unref(props).vertical && keynav($event, 999), ["shift"]), ["right"])),
_cache[6] || (_cache[6] = withKeys(withModifiers(($event) => unref(props).vertical && keynav($event, 1), ["exact"]), ["down"])),
_cache[7] || (_cache[7] = withKeys(withModifiers(($event) => unref(props).vertical && keynav($event, 999), ["shift"]), ["down"])),
_cache[8] || (_cache[8] = withKeys(($event) => keynav($event, -999), ["page-up"])),
_cache[9] || (_cache[9] = withKeys(($event) => keynav($event, 999), ["page-down"])),
_cache[10] || (_cache[10] = withKeys(($event) => keynav($event, -999), ["home"])),
_cache[11] || (_cache[11] = withKeys(($event) => keynav($event, 999), ["end"]))
],
onClick: withModifiers((e) => handleClick(e, idx), ["stop"])
}), [tab.titleComponent ? (openBlock(), createBlock(resolveDynamicComponent(tab.titleComponent), { key: 0 })) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createTextVNode(toDisplayString(tab.title), 1)], 64))], 16, _hoisted_2)], 2);
}), 128)),
renderSlot(_ctx.$slots, "tabs-end")
], 10, _hoisted_1)], 2),
!unref(props).end ? (openBlock(), createBlock(unref(ReusableEmptyTab).reuse, { key: 1 })) : createCommentVNode("", true)
]),
_: 3
}, 8, ["id", "class"]);
};
}
});
//#endregion
export { BTab_default as n, BTabs_default as t };
//# sourceMappingURL=BTabs-7IT0dhVr.mjs.map