{"version":3,"file":"anchor.mjs","sources":["../../../../../../packages/components/anchor/src/anchor.vue"],"sourcesContent":["<template>\n  <div ref=\"anchorRef\" :class=\"cls\">\n    <div\n      v-if=\"!sliderLess\"\n      ref=\"lineSliderRef\"\n      :class=\"`slider-bar`\"\n    ></div>\n    <div :class=\"`anchor-list`\">\n      <slot></slot>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\">\nimport {\n  computed,\n  defineComponent,\n  nextTick,\n  onBeforeUnmount,\n  onMounted,\n  provide,\n  reactive,\n  ref,\n  watch,\n} from 'vue'\nimport { isString } from '@vue/shared'\nimport computeScrollIntoView from 'compute-scroll-into-view'\nimport { isNumber, throttleByRaf } from '@hongluan-ui/utils'\nimport { useNamespace } from '@hongluan-ui/hooks'\nimport { findNode, slide } from './utils'\nimport { anchorInjectionKey } from './types'\n\nimport type { PropType } from 'vue'\nconst BOUNDARY_POSITIONS = ['start', 'end', 'center', 'nearest'] as const\ntype BoundaryPosition = typeof BOUNDARY_POSITIONS[number];\n\nexport default defineComponent({\n  name: 'Anchor',\n  props: {\n    /**\n     * @zh 滚动边界值，设置该值为数字后，将会在距离滚动容器 `boundary` 距离时停止滚动。\n     * @en Scrolling boundary value. After setting the value to a number, it will stop scrolling when the distance is `boundary` from the scrolling container.\n     * @values 'start', 'end', 'center', 'nearest', number\n     */\n    boundary: {\n      type: [Number, String] as PropType<BoundaryPosition | number>,\n      default: 'start',\n      validator: (value: any) => {\n        return isNumber(value) || BOUNDARY_POSITIONS.includes(value)\n      },\n    },\n    /**\n     * @zh 是否显示左侧轴线\n     * @en Whether to show the left axis\n     */\n    sliderLess: {\n      type: Boolean,\n      default: false,\n    },\n    /**\n     * @zh 滚动容器\n     * @en Scroll container\n     */\n    scrollContainer: {\n      type: [String, Object] as PropType<string | HTMLElement | Window>,\n    },\n    /**\n     * @zh 是否改变hash。设置为 `false` 时点击锚点不会改变页面的 hash\n     * @en Whether to change the hash. When set to `false`, clicking on the anchor will not change the hash of the page\n     */\n    changeHash: {\n      type: Boolean,\n      default: true,\n    },\n    /**\n     * @zh 是否使用平滑滚动\n     * @en Whether to use smooth scrolling\n     */\n    smooth: {\n      type: Boolean,\n      default: true,\n    },\n    right: Boolean,\n  },\n  emits: [\n    /**\n     * @zh 用户点击链接时触发\n     * @en Triggered when the user clicks on the link\n     * @property {string} hash\n     * @property {string} preHash\n     */\n    'select',\n    /**\n     * @zh 链接发生改变时触发\n     * @en Triggered when the link changes\n     * @property {string} hash\n     */\n    'change',\n  ],\n  setup(props, { emit }) {\n    const { namespace } = useNamespace('anchor')\n    const anchorRef = ref<HTMLElement | null>(null)\n    const lineSliderRef = ref<HTMLElement | null>(null)\n    const links = reactive<Record<string, HTMLElement>>({})\n    const currentLink = ref('')\n    const isScrolling = ref(false)\n    // throttle(this.handleScroll, 30, { trailing: true })\n    const addLink = (hash: string, node: HTMLElement) => {\n      if (!hash) return\n      links[hash] = node\n    }\n    const removeLink = (hash: string) => {\n      delete links[hash]\n    }\n    const getContainer = (targetContainer?: HTMLElement) => {\n      let scrollContainer: string | HTMLElement | Window = targetContainer\n      if (!scrollContainer) {\n        scrollContainer = props.scrollContainer\n      }\n      if (isString(scrollContainer)) {\n        return findNode(document, scrollContainer) as HTMLElement\n      }\n      return scrollContainer || window\n    }\n    const getContainerElement = (): HTMLElement => {\n      if (isString(props.scrollContainer)) {\n        return findNode(document, props.scrollContainer) as HTMLElement\n      }\n      if (props.scrollContainer && props.scrollContainer !== window) {\n        return props.scrollContainer as HTMLElement\n      }\n      return document.documentElement ?? document.body\n    }\n    const handleClick = (e: MouseEvent, hash?: string) => {\n      // const { onSelect, hash: changeHash } = this.props;\n      if (!props.changeHash) {\n        e.preventDefault()\n      }\n      handleAnchorChange(hash)\n      scrollIntoView(hash)\n      emit('select', hash, currentLink.value)\n    }\n    const scrollIntoView = (hash: string) => {\n      if (!hash) return\n      try {\n        const node = findNode(document, hash)\n        if (!node) return\n        let block = props.boundary as BoundaryPosition\n        let diff = 0\n        if (isNumber(props.boundary)) {\n          block = 'start'\n          diff = props.boundary as number\n        }\n        const actions = computeScrollIntoView(node, { block })\n        if (!actions.length) return\n        const { el, top } = actions[0]\n        const targetTop = top - diff\n        // if (!this.props.animation) {\n        //   return;\n        // }\n        slide(el as HTMLElement, targetTop, () => {\n          isScrolling.value = false\n        })\n        isScrolling.value = true\n      } catch (e) {\n        // eslint-disable-next-line no-console\n        console.error(e)\n      }\n    }\n    const handleScroll = throttleByRaf(() => {\n      if (isScrolling.value) return\n      const element = getFirstInViewportEle()\n      if (element && element.id) {\n        const hash = `#${element.id}`\n        handleAnchorChange(hash)\n      }\n    })\n    const handleAnchorChange = (hash: string) => {\n      if (!hash) return\n      if (!links[hash] && anchorRef.value) {\n        const node = findNode(\n          anchorRef.value,\n          `a[data-href='${hash}']`,\n        ) as HTMLElement\n        if (!node) return\n        links[hash] = node\n      }\n      if (hash !== currentLink.value) {\n        currentLink.value = hash\n        nextTick(() => {\n          emit('change', hash)\n        })\n      }\n    }\n    const getFirstInViewportEle = (): HTMLElement | undefined => {\n      const boundary = isNumber(props.boundary) ? props.boundary as number : 0\n      const container = getContainerElement()\n      const containerRect = container.getBoundingClientRect()\n      const { clientHeight } = document.documentElement\n      const ele = getContainer()\n      for (const hash of Object.keys(links)) {\n        const node = findNode(document, hash)\n        if (node) {\n          const { top } = node.getBoundingClientRect()\n          if (ele === window) {\n            const offsetTop = top - boundary\n            if (offsetTop >= 0 && offsetTop <= clientHeight / 2) {\n              return node as HTMLElement\n            }\n          } else {\n            const offsetTop = top - containerRect.top - boundary\n            if (offsetTop >= 0 && offsetTop <= containerRect.height / 2) {\n              return node as HTMLElement\n            }\n          }\n        }\n      }\n      return undefined\n    }\n    onMounted(() => {\n      handleScroll()\n      bindScrollEvent()\n    })\n    onBeforeUnmount(() => {\n      unbindScrollEvent()\n    })\n    watch(currentLink, () => {\n      const link = links[currentLink.value]\n      if (!props.sliderLess && link && lineSliderRef.value) {\n        lineSliderRef.value.style.top = `${link.offsetTop}px`\n      }\n    })\n    const bindScrollEvent = (scrollContainer?: HTMLElement) => {\n      getContainer(scrollContainer).addEventListener('scroll', handleScroll)\n    }\n    const unbindScrollEvent = (scrollContainer?: HTMLElement) => {\n      getContainer(scrollContainer).removeEventListener('scroll', handleScroll)\n    }\n    provide(\n      anchorInjectionKey,\n      reactive({\n        name: 'Anchor',\n        currentLink,\n        addLink,\n        removeLink,\n        handleClick,\n      }),\n    )\n    const cls = computed(() => [\n      namespace.value,\n      {\n        'slider-less': props.sliderLess,\n        'right': props.right,\n      },\n    ])\n    return {\n      cls,\n      anchorRef,\n      lineSliderRef,\n    }\n  },\n})\n</script>\n"],"names":["_openBlock"],"mappings":"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"}