UNPKG

hongluan-ui

Version:
225 lines (222 loc) 6.71 kB
import { defineComponent, ref, reactive, nextTick, onMounted, onBeforeUnmount, watch, provide, computed, openBlock, createElementBlock, normalizeClass, createCommentVNode, createElementVNode, renderSlot } from 'vue'; import { isString } from '@vue/shared'; import computeScrollIntoView from 'compute-scroll-into-view'; import '../../../utils/index.mjs'; import '../../../hooks/index.mjs'; import { findNode, slide } from './utils.mjs'; import { anchorInjectionKey } from './types.mjs'; import _export_sfc from '../../../_virtual/plugin-vue_export-helper.mjs'; import { isNumber } from '../../../utils/types.mjs'; import { useNamespace } from '../../../hooks/use-namespace/index.mjs'; import { throttleByRaf } from '../../../utils/raf.mjs'; const BOUNDARY_POSITIONS = ["start", "end", "center", "nearest"]; const _sfc_main = defineComponent({ name: "Anchor", props: { boundary: { type: [Number, String], default: "start", validator: (value) => { return isNumber(value) || BOUNDARY_POSITIONS.includes(value); } }, sliderLess: { type: Boolean, default: false }, scrollContainer: { type: [String, Object] }, changeHash: { type: Boolean, default: true }, smooth: { type: Boolean, default: true }, right: Boolean }, emits: [ "select", "change" ], setup(props, { emit }) { const { namespace } = useNamespace("anchor"); const anchorRef = ref(null); const lineSliderRef = ref(null); const links = reactive({}); const currentLink = ref(""); const isScrolling = ref(false); const addLink = (hash, node) => { if (!hash) return; links[hash] = node; }; const removeLink = (hash) => { delete links[hash]; }; const getContainer = (targetContainer) => { let scrollContainer = targetContainer; if (!scrollContainer) { scrollContainer = props.scrollContainer; } if (isString(scrollContainer)) { return findNode(document, scrollContainer); } return scrollContainer || window; }; const getContainerElement = () => { var _a; if (isString(props.scrollContainer)) { return findNode(document, props.scrollContainer); } if (props.scrollContainer && props.scrollContainer !== window) { return props.scrollContainer; } return (_a = document.documentElement) != null ? _a : document.body; }; const handleClick = (e, hash) => { if (!props.changeHash) { e.preventDefault(); } handleAnchorChange(hash); scrollIntoView(hash); emit("select", hash, currentLink.value); }; const scrollIntoView = (hash) => { if (!hash) return; try { const node = findNode(document, hash); if (!node) return; let block = props.boundary; let diff = 0; if (isNumber(props.boundary)) { block = "start"; diff = props.boundary; } const actions = computeScrollIntoView(node, { block }); if (!actions.length) return; const { el, top } = actions[0]; const targetTop = top - diff; slide(el, targetTop, () => { isScrolling.value = false; }); isScrolling.value = true; } catch (e) { console.error(e); } }; const handleScroll = throttleByRaf(() => { if (isScrolling.value) return; const element = getFirstInViewportEle(); if (element && element.id) { const hash = `#${element.id}`; handleAnchorChange(hash); } }); const handleAnchorChange = (hash) => { if (!hash) return; if (!links[hash] && anchorRef.value) { const node = findNode(anchorRef.value, `a[data-href='${hash}']`); if (!node) return; links[hash] = node; } if (hash !== currentLink.value) { currentLink.value = hash; nextTick(() => { emit("change", hash); }); } }; const getFirstInViewportEle = () => { const boundary = isNumber(props.boundary) ? props.boundary : 0; const container = getContainerElement(); const containerRect = container.getBoundingClientRect(); const { clientHeight } = document.documentElement; const ele = getContainer(); for (const hash of Object.keys(links)) { const node = findNode(document, hash); if (node) { const { top } = node.getBoundingClientRect(); if (ele === window) { const offsetTop = top - boundary; if (offsetTop >= 0 && offsetTop <= clientHeight / 2) { return node; } } else { const offsetTop = top - containerRect.top - boundary; if (offsetTop >= 0 && offsetTop <= containerRect.height / 2) { return node; } } } } return void 0; }; onMounted(() => { handleScroll(); bindScrollEvent(); }); onBeforeUnmount(() => { unbindScrollEvent(); }); watch(currentLink, () => { const link = links[currentLink.value]; if (!props.sliderLess && link && lineSliderRef.value) { lineSliderRef.value.style.top = `${link.offsetTop}px`; } }); const bindScrollEvent = (scrollContainer) => { getContainer(scrollContainer).addEventListener("scroll", handleScroll); }; const unbindScrollEvent = (scrollContainer) => { getContainer(scrollContainer).removeEventListener("scroll", handleScroll); }; provide(anchorInjectionKey, reactive({ name: "Anchor", currentLink, addLink, removeLink, handleClick })); const cls = computed(() => [ namespace.value, { "slider-less": props.sliderLess, "right": props.right } ]); return { cls, anchorRef, lineSliderRef }; } }); function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) { return openBlock(), createElementBlock("div", { ref: "anchorRef", class: normalizeClass(_ctx.cls) }, [ !_ctx.sliderLess ? (openBlock(), createElementBlock("div", { key: 0, ref: "lineSliderRef", class: normalizeClass(`slider-bar`) }, null, 512)) : createCommentVNode("v-if", true), createElementVNode("div", { class: normalizeClass(`anchor-list`) }, [ renderSlot(_ctx.$slots, "default") ]) ], 2); } var Anchor = /* @__PURE__ */ _export_sfc(_sfc_main, [["render", _sfc_render]]); export { Anchor as default }; //# sourceMappingURL=anchor.mjs.map