import { ref, watch, computed, reactive, nextTick, onActivated, defineComponent, createVNode as _createVNode } from "vue"; import { isDef, addUnit, isHidden, unitToPx, truthProp, numericProp, windowWidth, getElementTop, makeStringProp, callInterceptor, createNamespace, makeNumericProp, setRootScrollTop, BORDER_TOP_BOTTOM } from "../utils/index.mjs"; import { scrollLeftTo, scrollTopTo } from "./utils.mjs"; import { useRect, useChildren, useScrollParent, useEventListener, onMountedOrActivated } from "@vant/use"; import { useId } from "../composables/use-id.mjs"; import { route } from "../composables/use-route.mjs"; import { useRefs } from "../composables/use-refs.mjs"; import { useExpose } from "../composables/use-expose.mjs"; import { onPopupReopen } from "../composables/on-popup-reopen.mjs"; import { useVisibilityChange } from "../composables/use-visibility-change.mjs"; import { Sticky } from "../sticky/index.mjs"; import TabsContent from "./TabsContent.mjs"; const [name, bem] = createNamespace("tabs"); const tabsProps = { type: makeStringProp("line"), color: String, border: Boolean, sticky: Boolean, shrink: Boolean, active: makeNumericProp(0), duration: makeNumericProp(0.3), animated: Boolean, ellipsis: truthProp, swipeable: Boolean, scrollspy: Boolean, offsetTop: makeNumericProp(0), background: String, lazyRender: truthProp, showHeader: truthProp, lineWidth: numericProp, lineHeight: numericProp, beforeChange: Function, swipeThreshold: makeNumericProp(5), titleActiveColor: String, titleInactiveColor: String }; const TABS_KEY = Symbol(name); var stdin_default = defineComponent({ name, props: tabsProps, emits: ["change", "scroll", "rendered", "clickTab", "update:active"], setup(props, { emit, slots }) { let tabHeight; let lockScroll; let stickyFixed; let cancelScrollLeftToRaf; let cancelScrollTopToRaf; const root = ref(); const navRef = ref(); const wrapRef = ref(); const contentRef = ref(); const id = useId(); const scroller = useScrollParent(root); const [titleRefs, setTitleRefs] = useRefs(); const { children, linkChildren } = useChildren(TABS_KEY); const state = reactive({ inited: false, position: "", lineStyle: {}, currentIndex: -1 }); const scrollable = computed(() => children.length > +props.swipeThreshold || !props.ellipsis || props.shrink); const navStyle = computed(() => ({ borderColor: props.color, background: props.background })); const getTabName = (tab, index) => { var _a; return (_a = tab.name) != null ? _a : index; }; const currentName = computed(() => { const activeTab = children[state.currentIndex]; if (activeTab) { return getTabName(activeTab, state.currentIndex); } }); const offsetTopPx = computed(() => unitToPx(props.offsetTop)); const scrollOffset = computed(() => { if (props.sticky) { return offsetTopPx.value + tabHeight; } return 0; }); const scrollIntoView = (immediate) => { const nav = navRef.value; const titles = titleRefs.value; if (!scrollable.value || !nav || !titles || !titles[state.currentIndex]) { return; } const title = titles[state.currentIndex].$el; const to = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2; if (cancelScrollLeftToRaf) cancelScrollLeftToRaf(); cancelScrollLeftToRaf = scrollLeftTo(nav, to, immediate ? 0 : +props.duration); }; const setLine = () => { const shouldAnimate = state.inited; nextTick(() => { const titles = titleRefs.value; if (!titles || !titles[state.currentIndex] || props.type !== "line" || isHidden(root.value)) { return; } const title = titles[state.currentIndex].$el; const { lineWidth, lineHeight } = props; const left = title.offsetLeft + title.offsetWidth / 2; const lineStyle = { width: addUnit(lineWidth), backgroundColor: props.color, transform: `translateX(${left}px) translateX(-50%)` }; if (shouldAnimate) { lineStyle.transitionDuration = `${props.duration}s`; } if (isDef(lineHeight)) { const height = addUnit(lineHeight); lineStyle.height = height; lineStyle.borderRadius = height; } state.lineStyle = lineStyle; }); }; const findAvailableTab = (index) => { const diff = index < state.currentIndex ? -1 : 1; while (index >= 0 && index < children.length) { if (!children[index].disabled) { return index; } index += diff; } }; const setCurrentIndex = (currentIndex, skipScrollIntoView) => { const newIndex = findAvailableTab(currentIndex); if (!isDef(newIndex)) { return; } const newTab = children[newIndex]; const newName = getTabName(newTab, newIndex); const shouldEmitChange = state.currentIndex !== null; if (state.currentIndex !== newIndex) { state.currentIndex = newIndex; if (!skipScrollIntoView) { scrollIntoView(); } setLine(); } if (newName !== props.active) { emit("update:active", newName); if (shouldEmitChange) { emit("change", newName, newTab.title); } } if (stickyFixed && !props.scrollspy) { setRootScrollTop(Math.ceil(getElementTop(root.value) - offsetTopPx.value)); } }; const setCurrentIndexByName = (name2, skipScrollIntoView) => { const matched = children.find((tab, index2) => getTabName(tab, index2) === name2); const index = matched ? children.indexOf(matched) : 0; setCurrentIndex(index, skipScrollIntoView); }; const scrollToCurrentContent = (immediate = false) => { if (props.scrollspy) { const target = children[state.currentIndex].$el; if (target && scroller.value) { const to = getElementTop(target, scroller.value) - scrollOffset.value; lockScroll = true; if (cancelScrollTopToRaf) cancelScrollTopToRaf(); cancelScrollTopToRaf = scrollTopTo(scroller.value, to, immediate ? 0 : +props.duration, () => { lockScroll = false; }); } } }; const onClickTab = (item, index, event) => { const { title, disabled } = children[index]; const name2 = getTabName(children[index], index); if (!disabled) { callInterceptor(props.beforeChange, { args: [name2], done: () => { setCurrentIndex(index); scrollToCurrentContent(); } }); route(item); } emit("clickTab", { name: name2, title, event, disabled }); }; const onStickyScroll = (params) => { stickyFixed = params.isFixed; emit("scroll", params); }; const scrollTo = (name2) => { nextTick(() => { setCurrentIndexByName(name2); scrollToCurrentContent(true); }); }; const getCurrentIndexOnScroll = () => { for (let index = 0; index < children.length; index++) { const { top } = useRect(children[index].$el); if (top > scrollOffset.value) { return index === 0 ? 0 : index - 1; } } return children.length - 1; }; const onScroll = () => { if (props.scrollspy && !lockScroll) { const index = getCurrentIndexOnScroll(); setCurrentIndex(index); } }; const renderLine = () => { if (props.type === "line" && children.length) { return _createVNode("div", { "class": bem("line"), "style": state.lineStyle }, null); } }; const renderHeader = () => { var _a, _b, _c; const { type, border, sticky } = props; const Header = [_createVNode("div", { "ref": sticky ? void 0 : wrapRef, "class": [bem("wrap"), { [BORDER_TOP_BOTTOM]: type === "line" && border }] }, [_createVNode("div", { "ref": navRef, "role": "tablist", "class": bem("nav", [type, { shrink: props.shrink, complete: scrollable.value }]), "style": navStyle.value, "aria-orientation": "horizontal" }, [(_a = slots["nav-left"]) == null ? void 0 : _a.call(slots), children.map((item) => item.renderTitle(onClickTab)), renderLine(), (_b = slots["nav-right"]) == null ? void 0 : _b.call(slots)])]), (_c = slots["nav-bottom"]) == null ? void 0 : _c.call(slots)]; if (sticky) { return _createVNode("div", { "ref": wrapRef }, [Header]); } return Header; }; const resize = () => { setLine(); nextTick(() => { var _a, _b; scrollIntoView(true); (_b = (_a = contentRef.value) == null ? void 0 : _a.swipeRef.value) == null ? void 0 : _b.resize(); }); }; watch(() => [props.color, props.duration, props.lineWidth, props.lineHeight], setLine); watch(windowWidth, resize); watch(() => props.active, (value) => { if (value !== currentName.value) { setCurrentIndexByName(value); } }); watch(() => children.length, () => { if (state.inited) { setCurrentIndexByName(props.active); setLine(); nextTick(() => { scrollIntoView(true); }); } }); const init = () => { setCurrentIndexByName(props.active, true); nextTick(() => { state.inited = true; if (wrapRef.value) { tabHeight = useRect(wrapRef.value).height; } scrollIntoView(true); }); }; const onRendered = (name2, title) => emit("rendered", name2, title); useExpose({ resize, scrollTo }); onActivated(setLine); onPopupReopen(setLine); onMountedOrActivated(init); useVisibilityChange(root, setLine); useEventListener("scroll", onScroll, { target: scroller, passive: true }); linkChildren({ id, props, setLine, scrollable, onRendered, currentName, setTitleRefs, scrollIntoView }); return () => _createVNode("div", { "ref": root, "class": bem([props.type]) }, [props.showHeader ? props.sticky ? _createVNode(Sticky, { "container": root.value, "offsetTop": offsetTopPx.value, "onScroll": onStickyScroll }, { default: () => [renderHeader()] }) : renderHeader() : null, _createVNode(TabsContent, { "ref": contentRef, "count": children.length, "inited": state.inited, "animated": props.animated, "duration": props.duration, "swipeable": props.swipeable, "lazyRender": props.lazyRender, "currentIndex": state.currentIndex, "onChange": setCurrentIndex }, { default: () => { var _a; return [(_a = slots.default) == null ? void 0 : _a.call(slots)]; } })]); } }); export { TABS_KEY, stdin_default as default, tabsProps };