123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226 |
- import { createVNode as _createVNode } from "vue";
- import { ref, watch, computed, nextTick, Teleport, onMounted, defineComponent } from "vue";
- import { isDef, isHidden, truthProp, numericProp, getScrollTop, preventDefault, makeNumberProp, createNamespace, getRootScrollTop, setRootScrollTop } from "../utils/index.mjs";
- import { useRect, useChildren, useScrollParent, useEventListener } from "@vant/use";
- import { useTouch } from "../composables/use-touch.mjs";
- import { useExpose } from "../composables/use-expose.mjs";
- function genAlphabet() {
- const charCodeOfA = "A".charCodeAt(0);
- const indexList = Array(26).fill("").map((_, i) => String.fromCharCode(charCodeOfA + i));
- return indexList;
- }
- const [name, bem] = createNamespace("index-bar");
- const indexBarProps = {
- sticky: truthProp,
- zIndex: numericProp,
- teleport: [String, Object],
- highlightColor: String,
- stickyOffsetTop: makeNumberProp(0),
- indexList: {
- type: Array,
- default: genAlphabet
- }
- };
- const INDEX_BAR_KEY = Symbol(name);
- var stdin_default = defineComponent({
- name,
- props: indexBarProps,
- emits: ["select", "change"],
- setup(props, {
- emit,
- slots
- }) {
- const root = ref();
- const sidebar = ref();
- const activeAnchor = ref("");
- const touch = useTouch();
- const scrollParent = useScrollParent(root);
- const {
- children,
- linkChildren
- } = useChildren(INDEX_BAR_KEY);
- let selectActiveIndex;
- linkChildren({
- props
- });
- const sidebarStyle = computed(() => {
- if (isDef(props.zIndex)) {
- return {
- zIndex: +props.zIndex + 1
- };
- }
- });
- const highlightStyle = computed(() => {
- if (props.highlightColor) {
- return {
- color: props.highlightColor
- };
- }
- });
- const getActiveAnchor = (scrollTop, rects) => {
- for (let i = children.length - 1; i >= 0; i--) {
- const prevHeight = i > 0 ? rects[i - 1].height : 0;
- const reachTop = props.sticky ? prevHeight + props.stickyOffsetTop : 0;
- if (scrollTop + reachTop >= rects[i].top) {
- return i;
- }
- }
- return -1;
- };
- const getMatchAnchor = (index) => children.find((item) => String(item.index) === index);
- const onScroll = () => {
- if (isHidden(root)) {
- return;
- }
- const {
- sticky,
- indexList
- } = props;
- const scrollTop = getScrollTop(scrollParent.value);
- const scrollParentRect = useRect(scrollParent);
- const rects = children.map((item) => item.getRect(scrollParent.value, scrollParentRect));
- let active = -1;
- if (selectActiveIndex) {
- const match = getMatchAnchor(selectActiveIndex);
- if (match) {
- const rect = match.getRect(scrollParent.value, scrollParentRect);
- active = getActiveAnchor(rect.top, rects);
- }
- } else {
- active = getActiveAnchor(scrollTop, rects);
- }
- activeAnchor.value = indexList[active];
- if (sticky) {
- children.forEach((item, index) => {
- const {
- state,
- $el
- } = item;
- if (index === active || index === active - 1) {
- const rect = $el.getBoundingClientRect();
- state.left = rect.left;
- state.width = rect.width;
- } else {
- state.left = null;
- state.width = null;
- }
- if (index === active) {
- state.active = true;
- state.top = Math.max(props.stickyOffsetTop, rects[index].top - scrollTop) + scrollParentRect.top;
- } else if (index === active - 1 && selectActiveIndex === "") {
- const activeItemTop = rects[active].top - scrollTop;
- state.active = activeItemTop > 0;
- state.top = activeItemTop + scrollParentRect.top - rects[index].height;
- } else {
- state.active = false;
- }
- });
- }
- selectActiveIndex = "";
- };
- const init = () => {
- nextTick(onScroll);
- };
- useEventListener("scroll", onScroll, {
- target: scrollParent,
- passive: true
- });
- onMounted(init);
- watch(() => props.indexList, init);
- watch(activeAnchor, (value) => {
- if (value) {
- emit("change", value);
- }
- });
- const renderIndexes = () => props.indexList.map((index) => {
- const active = index === activeAnchor.value;
- return _createVNode("span", {
- "class": bem("index", {
- active
- }),
- "style": active ? highlightStyle.value : void 0,
- "data-index": index
- }, [index]);
- });
- const scrollTo = (index) => {
- selectActiveIndex = String(index);
- const match = getMatchAnchor(selectActiveIndex);
- if (match) {
- const scrollTop = getScrollTop(scrollParent.value);
- const scrollParentRect = useRect(scrollParent);
- const {
- offsetHeight
- } = document.documentElement;
- match.$el.scrollIntoView();
- if (scrollTop === offsetHeight - scrollParentRect.height) {
- onScroll();
- return;
- }
- if (props.sticky && props.stickyOffsetTop) {
- setRootScrollTop(getRootScrollTop() - props.stickyOffsetTop);
- }
- emit("select", match.index);
- }
- };
- const scrollToElement = (element) => {
- const {
- index
- } = element.dataset;
- if (index) {
- scrollTo(index);
- }
- };
- const onClickSidebar = (event) => {
- scrollToElement(event.target);
- };
- let touchActiveIndex;
- const onTouchMove = (event) => {
- touch.move(event);
- if (touch.isVertical()) {
- preventDefault(event);
- const {
- clientX,
- clientY
- } = event.touches[0];
- const target = document.elementFromPoint(clientX, clientY);
- if (target) {
- const {
- index
- } = target.dataset;
- if (index && touchActiveIndex !== index) {
- touchActiveIndex = index;
- scrollToElement(target);
- }
- }
- }
- };
- const renderSidebar = () => _createVNode("div", {
- "ref": sidebar,
- "class": bem("sidebar"),
- "style": sidebarStyle.value,
- "onClick": onClickSidebar,
- "onTouchstartPassive": touch.start
- }, [renderIndexes()]);
- useExpose({
- scrollTo
- });
- useEventListener("touchmove", onTouchMove, {
- target: sidebar
- });
- return () => {
- var _a;
- return _createVNode("div", {
- "ref": root,
- "class": bem()
- }, [props.teleport ? _createVNode(Teleport, {
- "to": props.teleport
- }, {
- default: () => [renderSidebar()]
- }) : renderSidebar(), (_a = slots.default) == null ? void 0 : _a.call(slots)]);
- };
- }
- });
- export {
- INDEX_BAR_KEY,
- stdin_default as default
- };
|