{"version":3,"file":"index2.mjs","sources":["../../../../../src/components/ivue-affix/index.vue"],"sourcesContent":["<template>\r\n  <div ref=\"wrapper\">\r\n    <!-- slot -->\r\n    <div ref=\"content\" :class=\"classes\" :style=\"affixStyle\">\r\n      <slot></slot>\r\n    </div>\r\n    <!-- 占位元素 -->\r\n    <div v-show=\"affixStyle\" :style=\"placeholderStyle\"></div>\r\n  </div>\r\n</template>\r\n\r\n<script lang=\"ts\">\r\nimport {\r\n  defineComponent,\r\n  onMounted,\r\n  ref,\r\n  computed,\r\n  unref,\r\n  onUnmounted,\r\n  nextTick,\r\n  watch,\r\n} from 'vue';\r\nimport { useEventListener } from '@vueuse/core';\r\nimport throttle from 'lodash.throttle';\r\n\r\nimport { AffixStatus } from './types/affix';\r\nimport {\r\n  getTargetRect,\r\n  getFixedTop,\r\n  getFixedBottom,\r\n  getDefaultTarget,\r\n} from './utils';\r\n\r\n// type\r\nimport type {\r\n  Props,\r\n  AffixState,\r\n  AffixStyle,\r\n  PlaceholderStyle,\r\n} from './types/affix';\r\n\r\nconst prefixCls = 'ivue-affix';\r\n\r\nexport default defineComponent({\r\n  name: prefixCls,\r\n  // 声明事件\r\n  emits: ['on-change'],\r\n  props: {\r\n    /**\r\n     * 距离窗口顶部达到指定偏移量后触发\r\n     *\r\n     * @type {Number}\r\n     */\r\n    offsetTop: {\r\n      type: Number,\r\n      default: 0,\r\n    },\r\n    /**\r\n     * 距离窗口底部达到指定偏移量后触发\r\n     *\r\n     * @type {Number}\r\n     */\r\n    offsetBottom: {\r\n      type: Number,\r\n    },\r\n    /**\r\n     * addEventListener 原生的 useCapture 选项\r\n     *\r\n     * @type {Boolean}\r\n     */\r\n    useCapture: {\r\n      type: Boolean,\r\n      default: false,\r\n    },\r\n    /**\r\n     * 设置 Affix 需要监听其滚动事件的元素，值为一个返回对应 DOM 元素的函数\r\n     *\r\n     * @type {HTMLElement}\r\n     */\r\n    target: {\r\n      type: HTMLElement,\r\n    },\r\n  },\r\n  // 组合式 API\r\n  setup(props: Props, { emit }) {\r\n    // ref = wrapper\r\n    const wrapper = ref<HTMLDivElement>();\r\n    // ref = content\r\n    const content = ref<HTMLDivElement>();\r\n\r\n    // 可以执行更新位置\r\n    const status = ref<AffixStatus | undefined>(AffixStatus.None);\r\n    // 是否固定\r\n    const fixed = ref<boolean>(false);\r\n    // 初始化固定样式\r\n    const affixStyle = ref<AffixStyle | undefined>(undefined);\r\n    // 初始化占位样式\r\n    const placeholderStyle = ref<PlaceholderStyle | undefined>(undefined);\r\n\r\n    // 是否添加class设置 fixed\r\n    const classes = computed(() => {\r\n      return [\r\n        {\r\n          [`${prefixCls}`]: !!unref(affixStyle),\r\n        },\r\n      ];\r\n    });\r\n\r\n    // 获取滚动顶部固定位置\r\n    const getOffsetTop = computed(() => {\r\n      const { offsetBottom, offsetTop } = props;\r\n\r\n      return offsetBottom === undefined && offsetTop === undefined\r\n        ? 0\r\n        : offsetTop;\r\n    });\r\n\r\n    // method\r\n\r\n    // 初始化数据\r\n    const initData = () => {\r\n      // 可以执行更新位置\r\n      status.value = AffixStatus.Start;\r\n      // 初始化固定样式\r\n      affixStyle.value = undefined;\r\n      // 初始化占位样式\r\n      placeholderStyle.value = undefined;\r\n    };\r\n\r\n    // 获取目标节点默认 window\r\n    const getTarget = () => {\r\n      const { target } = props;\r\n\r\n      // 有设置自定义 target\r\n      if (target !== undefined) {\r\n        return target;\r\n      }\r\n\r\n      // 有外部配置滚动监听容器\r\n      return getDefaultTarget();\r\n    };\r\n\r\n    // 节流更新位置\r\n    const lazyUpdatePosition = throttle(() => {\r\n      const targetNode = getTarget();\r\n\r\n      const _affixStyle = unref(affixStyle);\r\n\r\n      // 在测量之前检查位置变化以使 Safari 流畅\r\n      if (targetNode && _affixStyle) {\r\n        const offsetTop = unref(getOffsetTop);\r\n        const offsetBottom = props.offsetBottom;\r\n\r\n        // 是否有占位节点\r\n        if (targetNode && unref(wrapper)) {\r\n          // 获取目标节点的元素大小及其相对于视口的位置的信息\r\n          const targetRect = getTargetRect(targetNode);\r\n          // 获取占位节点的元素大小及其相对于视口的位置的信息\r\n          const placeholderReact = getTargetRect(unref(wrapper));\r\n          // 固定的顶部\r\n          const fixedTop = getFixedTop(placeholderReact, targetRect, offsetTop);\r\n          // 固定到底部\r\n          const fixedBottom = getFixedBottom(\r\n            placeholderReact,\r\n            targetRect,\r\n            offsetBottom\r\n          );\r\n\r\n          // 有固定到顶部 || 有固定到底部\r\n          if (\r\n            (fixedTop !== undefined && _affixStyle.top === `${fixedTop}px`) ||\r\n            (fixedBottom !== undefined &&\r\n              _affixStyle.bottom === `${fixedBottom}px`)\r\n          ) {\r\n            return;\r\n          }\r\n        }\r\n      }\r\n\r\n      // 初始化数据\r\n      initData();\r\n\r\n      // 计算滚动位置\r\n      calculateScrollPosition();\r\n    });\r\n\r\n    // 计算滚动位置\r\n    const calculateScrollPosition = () => {\r\n      // 获取目标节点\r\n      const targetNode = getTarget();\r\n\r\n      // status !== Start || 没有固定节点 || 没有外层 || 没有目标节点\r\n      if (\r\n        unref(status) !== AffixStatus.Start ||\r\n        !unref(content) ||\r\n        !unref(wrapper) ||\r\n        !targetNode\r\n      ) {\r\n        return;\r\n      }\r\n\r\n      // 获取滚动顶部固定位置\r\n      const offsetTop = getOffsetTop.value;\r\n      // 获取滚动到底部固定位置\r\n      const offsetBottom = props.offsetBottom;\r\n\r\n      // 没有目标元素\r\n      if (!targetNode) {\r\n        return;\r\n      }\r\n\r\n      // 设置为不能执行，等待节流方法执行\r\n      const newState: Partial<AffixState> = {\r\n        status: AffixStatus.None,\r\n      };\r\n\r\n      //  获取目标节点的元素大小及其相对于视口的位置的信息\r\n      const targetRect = getTargetRect(targetNode);\r\n      // 获取占位节点的元素大小及其相对于视口的位置的信息\r\n      const wrapperRect = getTargetRect(unref(wrapper));\r\n\r\n      // 固定的顶部\r\n      const fixedTop = getFixedTop(wrapperRect, targetRect, offsetTop);\r\n      // 固定到底部\r\n      const fixedBottom = getFixedBottom(wrapperRect, targetRect, offsetBottom);\r\n\r\n      // 已经固定了不执行\r\n      if (\r\n        wrapperRect.top === 0 &&\r\n        wrapperRect.left === 0 &&\r\n        wrapperRect.width === 0 &&\r\n        wrapperRect.height === 0\r\n      ) {\r\n        return;\r\n      }\r\n\r\n      // 固定顶部 || 固定底部\r\n      if (fixedTop !== undefined || fixedBottom !== undefined) {\r\n        // 节点固定\r\n        newState.affixStyle = {\r\n          width: `${wrapperRect.width}px`,\r\n          height: `${wrapperRect.height}px`,\r\n        };\r\n\r\n        // 设置占位节点尺寸\r\n        newState.placeholderStyle = {\r\n          width: `${wrapperRect.width}px`,\r\n          height: `${wrapperRect.height}px`,\r\n        };\r\n\r\n        // 有固定到顶部数值\r\n        if (fixedTop !== undefined) {\r\n          newState.affixStyle.top = `${fixedTop}px`;\r\n        }\r\n\r\n        // 有固定到底部数组\r\n        if (fixedBottom !== undefined) {\r\n          newState.affixStyle.bottom = `${fixedBottom}px`;\r\n        }\r\n      }\r\n\r\n      // 有设置元素固定节点\r\n      newState.fixed = !!newState.affixStyle;\r\n\r\n      // 发送监听事件\r\n      if (unref(fixed) !== newState.fixed) {\r\n        emit('on-change', newState.fixed);\r\n      }\r\n\r\n      // 可以执行更新位置\r\n      status.value = newState.status;\r\n      // 初始化固定样式\r\n      affixStyle.value = newState.affixStyle;\r\n      // 初始化占位样式\r\n      placeholderStyle.value = newState.placeholderStyle;\r\n      // 是否固定\r\n      fixed.value = newState.fixed;\r\n    };\r\n\r\n    // watch\r\n\r\n    // 监听距离窗口顶部达到指定偏移量后触发\r\n    watch(\r\n      () => props.offsetTop,\r\n      () => {\r\n        lazyUpdatePosition();\r\n      }\r\n    );\r\n\r\n    // 监听距离窗口底部达到指定偏移量后触发\r\n    watch(\r\n      () => props.offsetBottom,\r\n      () => {\r\n        lazyUpdatePosition();\r\n      }\r\n    );\r\n\r\n    // mounted\r\n    onMounted(async () => {\r\n      await nextTick();\r\n\r\n      // 节流初始化\r\n      lazyUpdatePosition();\r\n\r\n      // 目标节点\r\n      const targetNode = getTarget();\r\n\r\n      // 缩放\r\n      useEventListener(\r\n        targetNode,\r\n        'resize',\r\n        lazyUpdatePosition,\r\n        props.useCapture\r\n      );\r\n\r\n      // 滚动\r\n      useEventListener(\r\n        targetNode,\r\n        'scroll',\r\n        lazyUpdatePosition,\r\n        props.useCapture\r\n      );\r\n    });\r\n\r\n    // onUnmounted\r\n    onUnmounted(() => {\r\n      // 节流更新位置\r\n      lazyUpdatePosition.cancel();\r\n    });\r\n\r\n    return {\r\n      // ref\r\n      wrapper,\r\n      content,\r\n\r\n      // data\r\n      affixStyle,\r\n      placeholderStyle,\r\n\r\n      // computed\r\n      classes,\r\n\r\n      // methods\r\n      lazyUpdatePosition,\r\n    };\r\n  },\r\n});\r\n</script>\r\n"],"names":["_openBlock","_createElementBlock","_createCommentVNode","_createElementVNode","_normalizeClass","_normalizeStyle","_renderSlot","_withDirectives"],"mappings":";;;;;SAEI,YAAa,IAAA,EAAA,MAAA,EAAA,MAAA,EAAA,MAAA,EAAA,OAAA,QAAA,EAAA;SACbA,SAEM,EAAA,EAAAC,kBAAA,CAAA,KAAA,EAAA,UAAA,EAAA;AAAA,IAFYC,mBAAA,QAAA,CAAA;AAAA,IAAAC,mBAAO,KAAE,EAAA;AAAA,MAAU,GAAK,EAAA,SAAA;AAAA,MAAA,KAAA,EAAAC,cAAA,CAAA,IAAA,CAAA,OAAA,CAAA;AAAA,MACxC,KAAA,EAAaC,cAAA,CAAA,IAAA,CAAA,UAAA,CAAA;AAAA,KAAA,EAAA;AAAA,MAEfC,UAAA,CAAa,IAAA,CAAA,MAAA,EAAA,SAAA,CAAA;AAAA,OACb,CAAA,CAAA;AAAA,IAAAJ,mBAA+B,4BAAE,CAAA;AAAA,IAAAK,cAAA,CAAAJ,mBAAA,KAAA,EAAA;AAAA,2BAApB,CAAA,IAAA,CAAU,gBAAA,CAAA;AAAA,KAAA,EAAA,IAAA,EAAA,CAAA,CAAA,EAAA;AAAA;;;;;;;;"}