UNPKG

element-plus

Version:

A Component Library for Vue 3

199 lines (196 loc) 6.58 kB
import { defineComponent, ref, computed, onMounted, watch, provide, openBlock, createElementBlock, normalizeClass, unref, normalizeStyle, createCommentVNode, createElementVNode, renderSlot } from 'vue'; import { useEventListener } from '@vueuse/core'; import { anchorProps, anchorEmits } from './anchor.mjs'; import { anchorKey } from './constants.mjs'; import _export_sfc from '../../../_virtual/plugin-vue_export-helper.mjs'; import { getElement } from '../../../utils/dom/element.mjs'; import { throttleByRaf } from '../../../utils/throttleByRaf.mjs'; import { isWindow, isUndefined } from '../../../utils/types.mjs'; import { getScrollElement, animateScrollTo, getScrollTop } from '../../../utils/dom/scroll.mjs'; import { getOffsetTopDistance } from '../../../utils/dom/position.mjs'; import { useNamespace } from '../../../hooks/use-namespace/index.mjs'; import { CHANGE_EVENT } from '../../../constants/event.mjs'; const __default__ = defineComponent({ name: "ElAnchor" }); const _sfc_main = /* @__PURE__ */ defineComponent({ ...__default__, props: anchorProps, emits: anchorEmits, setup(__props, { expose, emit }) { const props = __props; const currentAnchor = ref(""); const anchorRef = ref(null); const markerRef = ref(null); const containerEl = ref(); const links = {}; let isScrolling = false; let currentScrollTop = 0; const ns = useNamespace("anchor"); const cls = computed(() => [ ns.b(), props.type === "underline" ? ns.m("underline") : "", ns.m(props.direction) ]); const addLink = (state) => { links[state.href] = state.el; }; const removeLink = (href) => { delete links[href]; }; const setCurrentAnchor = (href) => { const activeHref = currentAnchor.value; if (activeHref !== href) { currentAnchor.value = href; emit(CHANGE_EVENT, href); } }; let clearAnimate = null; const scrollToAnchor = (href) => { if (!containerEl.value) return; const target = getElement(href); if (!target) return; if (clearAnimate) clearAnimate(); isScrolling = true; const scrollEle = getScrollElement(target, containerEl.value); const distance = getOffsetTopDistance(target, scrollEle); const max = scrollEle.scrollHeight - scrollEle.clientHeight; const to = Math.min(distance - props.offset, max); clearAnimate = animateScrollTo(containerEl.value, currentScrollTop, to, props.duration, () => { setTimeout(() => { isScrolling = false; }, 20); }); }; const scrollTo = (href) => { if (href) { setCurrentAnchor(href); scrollToAnchor(href); } }; const handleClick = (e, href) => { emit("click", e, href); scrollTo(href); }; const handleScroll = throttleByRaf(() => { if (containerEl.value) { currentScrollTop = getScrollTop(containerEl.value); } const currentHref = getCurrentHref(); if (isScrolling || isUndefined(currentHref)) return; setCurrentAnchor(currentHref); }); const getCurrentHref = () => { if (!containerEl.value) return; const scrollTop = getScrollTop(containerEl.value); const anchorTopList = []; for (const href of Object.keys(links)) { const target = getElement(href); if (!target) continue; const scrollEle = getScrollElement(target, containerEl.value); const distance = getOffsetTopDistance(target, scrollEle); anchorTopList.push({ top: distance - props.offset - props.bound, href }); } anchorTopList.sort((prev, next) => prev.top - next.top); for (let i = 0; i < anchorTopList.length; i++) { const item = anchorTopList[i]; const next = anchorTopList[i + 1]; if (i === 0 && scrollTop === 0) { return props.selectScrollTop ? item.href : ""; } if (item.top <= scrollTop && (!next || next.top > scrollTop)) { return item.href; } } }; const getContainer = () => { const el = getElement(props.container); if (!el || isWindow(el)) { containerEl.value = window; } else { containerEl.value = el; } }; useEventListener(containerEl, "scroll", handleScroll); const markerStyle = computed(() => { if (!anchorRef.value || !markerRef.value || !currentAnchor.value) return {}; const currentLinkEl = links[currentAnchor.value]; if (!currentLinkEl) return {}; const anchorRect = anchorRef.value.getBoundingClientRect(); const markerRect = markerRef.value.getBoundingClientRect(); const linkRect = currentLinkEl.getBoundingClientRect(); if (props.direction === "horizontal") { const left = linkRect.left - anchorRect.left; return { left: `${left}px`, width: `${linkRect.width}px`, opacity: 1 }; } else { const top = linkRect.top - anchorRect.top + (linkRect.height - markerRect.height) / 2; return { top: `${top}px`, opacity: 1 }; } }); onMounted(() => { getContainer(); const hash = decodeURIComponent(window.location.hash); const target = getElement(hash); if (target) { scrollTo(hash); } else { handleScroll(); } }); watch(() => props.container, () => { getContainer(); }); provide(anchorKey, { ns, direction: props.direction, currentAnchor, addLink, removeLink, handleClick }); expose({ scrollTo }); return (_ctx, _cache) => { return openBlock(), createElementBlock("div", { ref_key: "anchorRef", ref: anchorRef, class: normalizeClass(unref(cls)) }, [ _ctx.marker ? (openBlock(), createElementBlock("div", { key: 0, ref_key: "markerRef", ref: markerRef, class: normalizeClass(unref(ns).e("marker")), style: normalizeStyle(unref(markerStyle)) }, null, 6)) : createCommentVNode("v-if", true), createElementVNode("div", { class: normalizeClass(unref(ns).e("list")) }, [ renderSlot(_ctx.$slots, "default") ], 2) ], 2); }; } }); var Anchor = /* @__PURE__ */ _export_sfc(_sfc_main, [["__file", "anchor.vue"]]); export { Anchor as default }; //# sourceMappingURL=anchor2.mjs.map