bootstrap-vue-next
Version:
Seamless integration of Vue 3, Bootstrap 5, and TypeScript for modern, type-safe UI development
142 lines (141 loc) • 5.02 kB
JavaScript
import { N as syncRef, g as useIntersectionObserver, y as useMutationObserver } from "../../../dist-B10a-gZ8.mjs";
import { i as getSafeDocument } from "../../../dom-AhkaSoh8.mjs";
import { t as getElement } from "../../../getElement-0_htvrFw.mjs";
import { computed, getCurrentInstance, nextTick, onMounted, readonly, ref, toRef, unref, watch } from "vue";
//#region src/composables/useScrollspy/index.ts
var useScrollspy = (content, target, options = {}) => {
const cont = toRef(content);
const tar = toRef(target);
const resolvedContent = ref(getElement(cont.value));
const resolvedTarget = ref(getElement(tar.value));
watch([cont, tar], () => {
updateList();
});
const { contentQuery = ":scope > [id]", targetQuery = "[href]", manual = false, root, rootMargin = "0px 0px -25%", threshold = [
.1,
.5,
1
], watchChanges = true } = options;
const current = ref(null);
const list = ref([]);
const nodeList = ref([]);
if (!getCurrentInstance()) nextTick(() => {
updateList();
});
else onMounted(() => {
syncRef(cont, resolvedContent, {
transform: { ltr: (v) => getElement(v) },
direction: "ltr",
immediate: true
});
syncRef(tar, resolvedTarget, {
transform: { ltr: (v) => getElement(v) },
direction: "ltr",
immediate: true
});
updateList();
});
const updateList = () => {
nodeList.value = resolvedContent.value ? Array.from(resolvedContent.value.querySelectorAll(contentQuery)) : [];
list.value = nodeList.value.map((el) => ({
id: el.id,
el,
visible: false,
text: el.textContent
}));
};
let isScrollingDown = true;
let previousScrollTop = 0;
const scrollRoot = computed(() => resolvedContent.value && getComputedStyle(resolvedContent.value).overflowY === "visible" ? null : resolvedContent.value);
const jobs = useIntersectionObserver(nodeList, (entries) => {
const doc = getSafeDocument();
const scrollTop = (scrollRoot.value ? scrollRoot.value : doc !== null ? doc.documentElement : void 0)?.scrollTop || 0;
isScrollingDown = scrollTop > previousScrollTop;
previousScrollTop = scrollTop;
entries.forEach((entry) => {
if (entry.isIntersecting) {
list.value.forEach((node) => {
if (node.el === entry.target) node.visible = true;
});
return;
}
list.value.forEach((node) => {
if (node.el === entry.target) node.visible = false;
});
});
let newId = null;
if (isScrollingDown) newId = [...list.value].reverse().find((node) => node.visible)?.id || null;
else newId = list.value.find((node) => node.visible)?.id || null;
if (newId !== null) current.value = newId;
if (!current.value) current.value = list.value[0]?.id || null;
}, {
root: computed(() => root ? getElement(unref(root)) : scrollRoot.value),
rootMargin,
threshold
});
watch(current, (newId) => {
if (manual) return;
const nodes = resolvedTarget.value?.querySelectorAll(targetQuery);
if (nodes === void 0) return;
let foundParent = false;
let activeElement = null;
nodes.forEach((node) => {
const parentDropdown = node.closest(".dropdown");
if (node.getAttribute("href")?.includes(`#${newId}`)) {
activeElement = node;
node.classList.add("active");
if (parentDropdown) {
parentDropdown?.querySelector(".dropdown-toggle")?.classList.add("active");
foundParent = true;
}
let parentNav = node.closest(".nav")?.previousSibling;
while (parentNav?.classList?.contains("nav-item")) {
foundParent = true;
parentNav.querySelector(".nav-link")?.classList.add("active");
parentNav = parentNav.closest(".nav")?.previousSibling;
}
} else {
node.classList.remove("active");
if (parentDropdown && !foundParent) parentDropdown?.querySelector(".dropdown-toggle")?.classList.remove("active");
if (!foundParent) {
let parentNav = node.closest(".nav")?.previousSibling;
while (parentNav?.classList?.contains("nav-item")) {
foundParent = true;
if (parentNav.querySelector(".nav-link") !== activeElement) parentNav.querySelector(".nav-link")?.classList.remove("active");
parentNav = parentNav.closest(".nav")?.previousSibling;
}
}
}
});
});
const mobs = !watchChanges ? { stop: () => {} } : useMutationObserver(resolvedContent, () => {
updateList();
}, { childList: true });
const scrollIntoView = (event, smooth = false) => {
event.preventDefault();
const href = event.target?.getAttribute?.("href");
const doc = getSafeDocument();
const el = href ? doc?.querySelector(href) ?? null : null;
if (el && resolvedContent.value) if (resolvedContent.value.scrollTo) resolvedContent.value.scrollTo({
top: el.offsetTop,
behavior: smooth ? "smooth" : "auto"
});
else resolvedContent.value.scrollTop = el.offsetTop;
};
const cleanup = () => {
jobs.stop();
mobs.stop();
};
return {
current: readonly(current),
list,
content: resolvedContent,
target: resolvedTarget,
scrollIntoView,
updateList,
cleanup
};
};
//#endregion
export { useScrollspy };
//# sourceMappingURL=index.mjs.map