UNPKG

bootstrap-vue-next

Version:

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

1 lines 10.3 kB
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/composables/useScrollspy/index.ts"],"sourcesContent":["import {syncRef, useIntersectionObserver, useMutationObserver} from '@vueuse/core'\nimport {\n type ComponentPublicInstance,\n computed,\n getCurrentInstance,\n type MaybeRef,\n type MaybeRefOrGetter,\n nextTick,\n onMounted,\n readonly,\n type Ref,\n ref,\n toRef,\n unref,\n watch,\n} from 'vue'\nimport {getElement} from '../../utils/getElement'\nimport {getSafeDocument} from '../../utils/dom'\n\ntype ScrollspyList = {\n id: string | null\n el: HTMLElement | null\n visible: boolean\n text: string | null\n}[]\n\ninterface ScrollspyReturn {\n current: Readonly<Ref<string | null>>\n list: Readonly<Ref<ScrollspyList>>\n content: Ref<HTMLElement | undefined>\n target: Ref<HTMLElement | undefined>\n scrollIntoView: (event: MouseEvent) => void\n updateList: () => void\n cleanup: () => void\n}\n\ninterface ScrollspyOptions {\n contentQuery: string\n targetQuery: string\n manual: boolean\n root: MaybeRef<string | ComponentPublicInstance | HTMLElement | null>\n rootMargin: string\n threshold: number | number[]\n watchChanges: boolean\n}\n\nexport const useScrollspy = (\n content: MaybeRefOrGetter<string | ComponentPublicInstance | HTMLElement | null>,\n target: MaybeRefOrGetter<string | ComponentPublicInstance | HTMLElement | null>,\n options: Readonly<Partial<ScrollspyOptions>> = {}\n): ScrollspyReturn => {\n const cont = toRef(content)\n const tar = toRef(target)\n\n const resolvedContent = ref(getElement(cont.value))\n const resolvedTarget = ref(getElement(tar.value))\n\n watch([cont, tar], () => {\n updateList()\n })\n const {\n contentQuery = ':scope > [id]',\n targetQuery = '[href]',\n manual = false,\n root,\n rootMargin = '0px 0px -25%',\n threshold = [0.1, 0.5, 1],\n watchChanges = true,\n } = options\n const current = ref<string | null>(null)\n const list = ref<ScrollspyList>([])\n const nodeList = ref<HTMLElement[]>([])\n\n // are we called in directive?\n const ctx = getCurrentInstance()\n if (!ctx) {\n nextTick(() => {\n updateList()\n })\n } else {\n onMounted(() => {\n syncRef(cont, resolvedContent, {\n transform: {\n ltr: (v) => getElement(v),\n },\n direction: 'ltr',\n immediate: true,\n })\n syncRef(tar, resolvedTarget, {\n transform: {\n ltr: (v) => getElement(v),\n },\n direction: 'ltr',\n immediate: true,\n })\n updateList()\n })\n }\n\n const updateList = () => {\n nodeList.value = resolvedContent.value\n ? (Array.from(resolvedContent.value.querySelectorAll(contentQuery)) as HTMLElement[])\n : []\n list.value = nodeList.value.map((el) => ({\n id: el.id,\n el,\n visible: false,\n text: el.textContent,\n }))\n }\n\n let isScrollingDown = true\n let previousScrollTop = 0\n const scrollRoot = computed(() =>\n resolvedContent.value && getComputedStyle(resolvedContent.value).overflowY === 'visible'\n ? null\n : resolvedContent.value\n )\n\n const jobs = useIntersectionObserver(\n nodeList,\n (entries) => {\n const doc = getSafeDocument()\n const scrollTop =\n (scrollRoot.value ? scrollRoot.value : doc !== null ? doc.documentElement : undefined)\n ?.scrollTop || 0\n isScrollingDown = scrollTop > previousScrollTop\n previousScrollTop = scrollTop\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n list.value.forEach((node) => {\n if (node.el === entry.target) {\n node.visible = true\n }\n })\n return\n }\n list.value.forEach((node) => {\n if (node.el === entry.target) {\n node.visible = false\n }\n })\n })\n let newId: string | null = null\n if (isScrollingDown) {\n newId = [...list.value].reverse().find((node) => node.visible)?.id || null\n } else {\n newId = list.value.find((node) => node.visible)?.id || null\n }\n if (newId !== null) {\n current.value = newId\n }\n if (!current.value) {\n current.value = list.value[0]?.id || null\n }\n },\n {\n root: computed(() => (root ? getElement(unref(root)) : scrollRoot.value)),\n rootMargin,\n threshold,\n }\n )\n watch(current, (newId) => {\n if (manual) return\n const nodes = resolvedTarget.value?.querySelectorAll(targetQuery)\n if (nodes === undefined) return\n let foundParent = false\n let activeElement: HTMLElement | null = null\n nodes.forEach((node) => {\n const parentDropdown = node.closest('.dropdown')\n\n if (node.getAttribute('href')?.includes(`#${newId}`)) {\n activeElement = node as HTMLElement\n node.classList.add('active')\n if (parentDropdown) {\n parentDropdown?.querySelector('.dropdown-toggle')?.classList.add('active')\n foundParent = true\n }\n let parentNav = node.closest('.nav')?.previousSibling as HTMLElement\n while (parentNav?.classList?.contains('nav-item')) {\n foundParent = true\n parentNav.querySelector('.nav-link')?.classList.add('active')\n parentNav = parentNav.closest('.nav')?.previousSibling as HTMLElement\n }\n } else {\n node.classList.remove('active')\n if (parentDropdown && !foundParent) {\n parentDropdown?.querySelector('.dropdown-toggle')?.classList.remove('active')\n }\n\n if (!foundParent) {\n let parentNav = node.closest('.nav')?.previousSibling as HTMLElement\n while (parentNav?.classList?.contains('nav-item')) {\n foundParent = true\n if (parentNav.querySelector('.nav-link') !== activeElement) {\n parentNav.querySelector('.nav-link')?.classList.remove('active')\n }\n parentNav = parentNav.closest('.nav')?.previousSibling as HTMLElement\n }\n }\n }\n })\n })\n\n const mobs = !watchChanges\n ? {stop: () => {}}\n : useMutationObserver(\n resolvedContent,\n () => {\n updateList()\n },\n {\n childList: true,\n }\n )\n const scrollIntoView = (event: Readonly<MouseEvent>, smooth: boolean = false) => {\n event.preventDefault()\n const href = (event.target as HTMLElement)?.getAttribute?.('href')\n const doc = getSafeDocument()\n const el: HTMLElement | null = href ? (doc?.querySelector(href) ?? null) : null\n // console.log('scrollIntoView', event, el, content.value.$el)\n if (el && resolvedContent.value) {\n if (resolvedContent.value.scrollTo) {\n resolvedContent.value.scrollTo({top: el.offsetTop, behavior: smooth ? 'smooth' : 'auto'})\n } else {\n resolvedContent.value.scrollTop = el.offsetTop\n }\n }\n }\n const cleanup = () => {\n jobs.stop()\n mobs.stop()\n }\n return {\n current: readonly(current),\n list,\n content: resolvedContent,\n target: resolvedTarget,\n scrollIntoView,\n updateList,\n cleanup,\n }\n}\n"],"mappings":"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"}