mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-06 03:57:59 +08:00
refactor(Tabs): refactor with composition api
This commit is contained in:
parent
8ef46764c9
commit
882e3ef5e7
@ -1,61 +0,0 @@
|
||||
import { on } from '../utils/dom/event';
|
||||
|
||||
const MIN_DISTANCE = 10;
|
||||
|
||||
function getDirection(x, y) {
|
||||
if (x > y && x > MIN_DISTANCE) {
|
||||
return 'horizontal';
|
||||
}
|
||||
|
||||
if (y > x && y > MIN_DISTANCE) {
|
||||
return 'vertical';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
export const TouchMixin = {
|
||||
data() {
|
||||
return { direction: '' };
|
||||
},
|
||||
|
||||
methods: {
|
||||
touchStart(event) {
|
||||
this.resetTouchStatus();
|
||||
this.startX = event.touches[0].clientX;
|
||||
this.startY = event.touches[0].clientY;
|
||||
},
|
||||
|
||||
touchMove(event) {
|
||||
const touch = event.touches[0];
|
||||
this.deltaX = touch.clientX - this.startX;
|
||||
this.deltaY = touch.clientY - this.startY;
|
||||
this.offsetX = Math.abs(this.deltaX);
|
||||
this.offsetY = Math.abs(this.deltaY);
|
||||
this.direction =
|
||||
this.direction || getDirection(this.offsetX, this.offsetY);
|
||||
},
|
||||
|
||||
resetTouchStatus() {
|
||||
this.direction = '';
|
||||
this.deltaX = 0;
|
||||
this.deltaY = 0;
|
||||
this.offsetX = 0;
|
||||
this.offsetY = 0;
|
||||
},
|
||||
|
||||
// avoid Vue 2.6 event bubble issues by manually binding events
|
||||
// https://github.com/youzan/vant/issues/3015
|
||||
bindTouchEvent(el) {
|
||||
const { onTouchStart, onTouchMove, onTouchEnd } = this;
|
||||
|
||||
on(el, 'touchstart', onTouchStart);
|
||||
on(el, 'touchmove', onTouchMove);
|
||||
|
||||
if (onTouchEnd) {
|
||||
on(el, 'touchend', onTouchEnd);
|
||||
on(el, 'touchcancel', onTouchEnd);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
127
src/tab/index.js
127
src/tab/index.js
@ -1,12 +1,14 @@
|
||||
import { ref, watch, nextTick } from 'vue';
|
||||
import { createNamespace } from '../utils';
|
||||
import { ChildrenMixin } from '../mixins/relation';
|
||||
import { TABS_KEY } from '../tabs';
|
||||
|
||||
// Composition
|
||||
import { routeProps } from '../composition/use-route';
|
||||
import { useParent } from '../composition/use-relation';
|
||||
|
||||
const [createComponent, bem] = createNamespace('tab');
|
||||
|
||||
export default createComponent({
|
||||
mixins: [ChildrenMixin('vanTabs')],
|
||||
|
||||
props: {
|
||||
...routeProps,
|
||||
dot: Boolean,
|
||||
@ -17,63 +19,74 @@ export default createComponent({
|
||||
disabled: Boolean,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
inited: false,
|
||||
};
|
||||
},
|
||||
setup(props, { slots }) {
|
||||
const root = ref();
|
||||
const inited = ref(false);
|
||||
const { parent, index } = useParent(TABS_KEY, {
|
||||
getRoot: () => root.value,
|
||||
props,
|
||||
slots,
|
||||
});
|
||||
|
||||
computed: {
|
||||
computedName() {
|
||||
return this.name ?? this.index;
|
||||
},
|
||||
|
||||
isActive() {
|
||||
const active = this.computedName === this.parent.currentName;
|
||||
|
||||
if (active) {
|
||||
this.inited = true;
|
||||
}
|
||||
return active;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
title() {
|
||||
this.parent.setLine();
|
||||
},
|
||||
|
||||
inited(val) {
|
||||
if (this.parent.lazyRender && val) {
|
||||
this.$nextTick(() => {
|
||||
this.parent.$emit('rendered', this.computedName, this.title);
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
const { parent, isActive } = this;
|
||||
const shouldRender = this.inited || parent.scrollspy || !parent.lazyRender;
|
||||
const show = parent.scrollspy || isActive;
|
||||
const Content = shouldRender ? this.$slots.default?.() : null;
|
||||
|
||||
if (parent.animated) {
|
||||
return (
|
||||
<div
|
||||
role="tabpanel"
|
||||
aria-hidden={!isActive}
|
||||
class={bem('pane-wrapper', { inactive: !isActive })}
|
||||
>
|
||||
<div class={bem('pane')}>{Content}</div>
|
||||
</div>
|
||||
);
|
||||
if (!parent) {
|
||||
throw new Error('[Vant] Tabs: <van-tab> must be used inside <van-tabs>');
|
||||
}
|
||||
|
||||
return (
|
||||
<div vShow={show} role="tabpanel" class={bem('pane')}>
|
||||
{Content}
|
||||
</div>
|
||||
const getName = () => props.name ?? index.value;
|
||||
|
||||
const init = () => {
|
||||
inited.value = true;
|
||||
|
||||
if (parent.props.lazyRender) {
|
||||
nextTick(() => {
|
||||
parent.emit('rendered', getName(), props.title);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isActive = () => {
|
||||
const active = getName() === parent.currentName.value;
|
||||
|
||||
if (active && !inited.value) {
|
||||
init();
|
||||
}
|
||||
|
||||
return active;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.title,
|
||||
() => {
|
||||
parent.setLine();
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
const { animated, scrollspy, lazyRender } = parent.props;
|
||||
const active = isActive();
|
||||
const show = scrollspy || active;
|
||||
|
||||
const shouldRender = inited.value || scrollspy || !lazyRender;
|
||||
const Content = shouldRender ? slots.default?.() : null;
|
||||
|
||||
if (animated) {
|
||||
return (
|
||||
<div
|
||||
ref={root}
|
||||
role="tabpanel"
|
||||
aria-hidden={!active}
|
||||
class={bem('pane-wrapper', { inactive: !active })}
|
||||
>
|
||||
<div class={bem('pane')}>{Content}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div v-show={show} ref={root} role="tabpanel" class={bem('pane')}>
|
||||
{Content}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -1,23 +1,33 @@
|
||||
import {
|
||||
ref,
|
||||
watch,
|
||||
provide,
|
||||
computed,
|
||||
reactive,
|
||||
nextTick,
|
||||
onMounted,
|
||||
onActivated,
|
||||
} from 'vue';
|
||||
|
||||
// Utils
|
||||
import { createNamespace, isDef, addUnit } from '../utils';
|
||||
import { scrollLeftTo, scrollTopTo } from './utils';
|
||||
import { route } from '../composition/use-route';
|
||||
import { isHidden } from '../utils/dom/style';
|
||||
import { on, off } from '../utils/dom/event';
|
||||
import { unitToPx } from '../utils/format/unit';
|
||||
import { BORDER_TOP_BOTTOM } from '../utils/constant';
|
||||
import { callInterceptor } from '../utils/interceptor';
|
||||
import {
|
||||
getScroller,
|
||||
getVisibleTop,
|
||||
getElementTop,
|
||||
getVisibleHeight,
|
||||
setRootScrollTop,
|
||||
} from '../utils/dom/scroll';
|
||||
|
||||
// Mixins
|
||||
import { ParentMixin } from '../mixins/relation';
|
||||
import { BindEventMixin } from '../mixins/bind-event';
|
||||
// Composition
|
||||
import { useRefs } from '../composition/use-refs';
|
||||
import { useExpose } from '../composition/use-expose';
|
||||
import { useWindowSize, useScrollParent, useEventListener } from '@vant/use';
|
||||
|
||||
// Components
|
||||
import Sticky from '../sticky';
|
||||
@ -26,22 +36,9 @@ import TabsContent from './TabsContent';
|
||||
|
||||
const [createComponent, bem] = createNamespace('tabs');
|
||||
|
||||
export const TABS_KEY = 'vanTabs';
|
||||
|
||||
export default createComponent({
|
||||
mixins: [
|
||||
ParentMixin('vanTabs'),
|
||||
BindEventMixin(function (bind) {
|
||||
if (!this.scroller) {
|
||||
this.scroller = getScroller(this.$el);
|
||||
}
|
||||
|
||||
bind(window, 'resize', this.resize, true);
|
||||
|
||||
if (this.scrollspy) {
|
||||
bind(this.scroller, 'scroll', this.onScroll, true);
|
||||
}
|
||||
}),
|
||||
],
|
||||
|
||||
props: {
|
||||
color: String,
|
||||
border: Boolean,
|
||||
@ -87,139 +84,109 @@ export default createComponent({
|
||||
|
||||
emits: ['click', 'change', 'scroll', 'disabled', 'rendered', 'update:active'],
|
||||
|
||||
data() {
|
||||
this.titleRefs = [];
|
||||
setup(props, { emit, slots }) {
|
||||
let inited;
|
||||
let tabHeight;
|
||||
let lockScroll;
|
||||
let stickyFixed;
|
||||
|
||||
return {
|
||||
const root = ref();
|
||||
const navRef = ref();
|
||||
const wrapRef = ref();
|
||||
|
||||
const windowSize = useWindowSize();
|
||||
const scroller = useScrollParent(root);
|
||||
const [titleRefs, setTitleRefs] = useRefs();
|
||||
|
||||
const children = reactive([]);
|
||||
const state = reactive({
|
||||
position: '',
|
||||
currentIndex: -1,
|
||||
lineStyle: {
|
||||
backgroundColor: this.color,
|
||||
backgroundColor: props.color,
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
computed: {
|
||||
// whether the nav is scrollable
|
||||
scrollable() {
|
||||
return this.children.length > this.swipeThreshold || !this.ellipsis;
|
||||
},
|
||||
const scrollable = computed(
|
||||
() => children.length > props.swipeThreshold || !props.ellipsis
|
||||
);
|
||||
|
||||
navStyle() {
|
||||
return {
|
||||
borderColor: this.color,
|
||||
background: this.background,
|
||||
};
|
||||
},
|
||||
const navStyle = computed(() => ({
|
||||
borderColor: props.color,
|
||||
background: props.background,
|
||||
}));
|
||||
|
||||
currentName() {
|
||||
const activeTab = this.children[this.currentIndex];
|
||||
const getTabName = (tab, index) => tab.props.name ?? index;
|
||||
|
||||
const currentName = computed(() => {
|
||||
const activeTab = children[state.currentIndex];
|
||||
|
||||
if (activeTab) {
|
||||
return activeTab.computedName;
|
||||
return getTabName(activeTab, state.currentIndex);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
offsetTopPx() {
|
||||
return unitToPx(this.offsetTop);
|
||||
},
|
||||
const offsetTopPx = computed(() => unitToPx(props.offsetTop));
|
||||
|
||||
scrollOffset() {
|
||||
if (this.sticky) {
|
||||
return this.offsetTopPx + this.tabHeight;
|
||||
const scrollOffset = computed(() => {
|
||||
if (props.sticky) {
|
||||
return offsetTopPx.value + tabHeight;
|
||||
}
|
||||
return 0;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
watch: {
|
||||
color: 'setLine',
|
||||
// scroll active tab into view
|
||||
const scrollIntoView = (immediate) => {
|
||||
const nav = navRef.value;
|
||||
const titles = titleRefs.value;
|
||||
|
||||
active(name) {
|
||||
if (name !== this.currentName) {
|
||||
this.setCurrentIndexByName(name);
|
||||
if (!scrollable.value || !titles || !titles[state.currentIndex]) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
children() {
|
||||
this.setCurrentIndexByName(this.currentName || this.active);
|
||||
this.setLine();
|
||||
const title = titles[state.currentIndex].$el;
|
||||
const to = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2;
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.scrollIntoView(true);
|
||||
scrollLeftTo(nav, to, immediate ? 0 : +props.duration);
|
||||
};
|
||||
|
||||
const init = () => {
|
||||
nextTick(() => {
|
||||
inited = true;
|
||||
tabHeight = getVisibleHeight(wrapRef.value);
|
||||
scrollIntoView(true);
|
||||
});
|
||||
},
|
||||
|
||||
currentIndex() {
|
||||
this.scrollIntoView();
|
||||
this.setLine();
|
||||
|
||||
// scroll to correct position
|
||||
if (this.stickyFixed && !this.scrollspy) {
|
||||
setRootScrollTop(Math.ceil(getElementTop(this.$el) - this.offsetTopPx));
|
||||
}
|
||||
},
|
||||
|
||||
scrollspy(val) {
|
||||
if (val) {
|
||||
on(this.scroller, 'scroll', this.onScroll, true);
|
||||
} else {
|
||||
off(this.scroller, 'scroll', this.onScroll);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.init();
|
||||
},
|
||||
|
||||
activated() {
|
||||
this.init();
|
||||
this.setLine();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// @exposed-api
|
||||
resize() {
|
||||
this.setLine();
|
||||
},
|
||||
|
||||
init() {
|
||||
this.$nextTick(() => {
|
||||
this.inited = true;
|
||||
this.tabHeight = getVisibleHeight(this.$refs.wrap);
|
||||
this.scrollIntoView(true);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// update nav bar style
|
||||
setLine() {
|
||||
const shouldAnimate = this.inited;
|
||||
const setLine = () => {
|
||||
const shouldAnimate = inited;
|
||||
|
||||
this.$nextTick(() => {
|
||||
const { titleRefs } = this;
|
||||
nextTick(() => {
|
||||
const titles = titleRefs.value;
|
||||
|
||||
if (
|
||||
!titleRefs ||
|
||||
!titleRefs[this.currentIndex] ||
|
||||
this.type !== 'line' ||
|
||||
isHidden(this.$el)
|
||||
!titles ||
|
||||
!titles[state.currentIndex] ||
|
||||
props.type !== 'line' ||
|
||||
isHidden(root.value)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const title = titleRefs[this.currentIndex].$el;
|
||||
const { lineWidth, lineHeight } = this;
|
||||
const title = titles[state.currentIndex].$el;
|
||||
const { lineWidth, lineHeight } = props;
|
||||
const left = title.offsetLeft + title.offsetWidth / 2;
|
||||
|
||||
const lineStyle = {
|
||||
width: addUnit(lineWidth),
|
||||
backgroundColor: this.color,
|
||||
backgroundColor: props.color,
|
||||
transform: `translateX(${left}px) translateX(-50%)`,
|
||||
};
|
||||
|
||||
if (shouldAnimate) {
|
||||
lineStyle.transitionDuration = `${this.duration}s`;
|
||||
lineStyle.transitionDuration = `${props.duration}s`;
|
||||
}
|
||||
|
||||
if (isDef(lineHeight)) {
|
||||
@ -228,207 +195,247 @@ export default createComponent({
|
||||
lineStyle.borderRadius = height;
|
||||
}
|
||||
|
||||
this.lineStyle = lineStyle;
|
||||
state.lineStyle = lineStyle;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
// correct the index of active tab
|
||||
setCurrentIndexByName(name) {
|
||||
const matched = this.children.filter((tab) => tab.computedName === name);
|
||||
const defaultIndex = (this.children[0] || {}).index || 0;
|
||||
this.setCurrentIndex(matched.length ? matched[0].index : defaultIndex);
|
||||
},
|
||||
const findAvailableTab = (index) => {
|
||||
const diff = index < state.currentIndex ? -1 : 1;
|
||||
|
||||
setCurrentIndex(currentIndex) {
|
||||
currentIndex = this.findAvailableTab(currentIndex);
|
||||
|
||||
if (isDef(currentIndex) && currentIndex !== this.currentIndex) {
|
||||
const shouldEmitChange = this.currentIndex !== null;
|
||||
this.currentIndex = currentIndex;
|
||||
this.$emit('update:active', this.currentName);
|
||||
|
||||
if (shouldEmitChange) {
|
||||
this.$emit(
|
||||
'change',
|
||||
this.currentName,
|
||||
this.children[currentIndex].title
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
findAvailableTab(index) {
|
||||
const diff = index < this.currentIndex ? -1 : 1;
|
||||
|
||||
while (index >= 0 && index < this.children.length) {
|
||||
if (!this.children[index].disabled) {
|
||||
while (index >= 0 && index < children.length) {
|
||||
if (!children[index].props.disabled) {
|
||||
return index;
|
||||
}
|
||||
|
||||
index += diff;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const setCurrentIndex = (currentIndex) => {
|
||||
currentIndex = findAvailableTab(currentIndex);
|
||||
|
||||
if (isDef(currentIndex) && currentIndex !== state.currentIndex) {
|
||||
const shouldEmitChange = state.currentIndex !== null;
|
||||
state.currentIndex = currentIndex;
|
||||
emit('update:active', currentName.value);
|
||||
|
||||
if (shouldEmitChange) {
|
||||
emit('change', currentName.value, children[currentIndex].props.title);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// correct the index of active tab
|
||||
const setCurrentIndexByName = (name) => {
|
||||
const matched = children.filter(
|
||||
(tab, index) => getTabName(tab, index) === name
|
||||
);
|
||||
|
||||
const index = matched[0] ? children.indexOf(matched[0]) : 0;
|
||||
setCurrentIndex(index);
|
||||
};
|
||||
|
||||
const scrollToCurrentContent = (immediate = false) => {
|
||||
if (props.scrollspy) {
|
||||
const target = children[state.currentIndex];
|
||||
const el = target?.getRoot();
|
||||
|
||||
if (el) {
|
||||
const to = getElementTop(el, scroller.value) - scrollOffset.value;
|
||||
|
||||
lockScroll = true;
|
||||
scrollTopTo(
|
||||
scroller.value,
|
||||
to,
|
||||
immediate ? 0 : +props.duration,
|
||||
() => {
|
||||
lockScroll = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// emit event when clicked
|
||||
onClick(item, index) {
|
||||
const { title, disabled, computedName } = this.children[index];
|
||||
const onClick = (item, index) => {
|
||||
const { title, disabled } = children[index].props;
|
||||
const name = getTabName(children[index], index);
|
||||
|
||||
if (disabled) {
|
||||
this.$emit('disabled', computedName, title);
|
||||
emit('disabled', name, title);
|
||||
} else {
|
||||
callInterceptor({
|
||||
interceptor: this.beforeChange,
|
||||
args: [computedName],
|
||||
interceptor: props.beforeChange,
|
||||
args: [name],
|
||||
done: () => {
|
||||
this.setCurrentIndex(index);
|
||||
this.scrollToCurrentContent();
|
||||
setCurrentIndex(index);
|
||||
scrollToCurrentContent();
|
||||
},
|
||||
});
|
||||
|
||||
this.$emit('click', computedName, title);
|
||||
emit('click', name, title);
|
||||
route(item.$router, item);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
// scroll active tab into view
|
||||
scrollIntoView(immediate) {
|
||||
const { titleRefs } = this;
|
||||
const onStickyScroll = (params) => {
|
||||
stickyFixed = params.isFixed;
|
||||
emit('scroll', params);
|
||||
};
|
||||
|
||||
if (!this.scrollable || !titleRefs || !titleRefs[this.currentIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { nav } = this.$refs;
|
||||
const title = titleRefs[this.currentIndex].$el;
|
||||
const to = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2;
|
||||
|
||||
scrollLeftTo(nav, to, immediate ? 0 : +this.duration);
|
||||
},
|
||||
|
||||
onSticktScroll(params) {
|
||||
this.stickyFixed = params.isFixed;
|
||||
this.$emit('scroll', params);
|
||||
},
|
||||
|
||||
// @exposed-api
|
||||
scrollTo(name) {
|
||||
this.$nextTick(() => {
|
||||
this.setCurrentIndexByName(name);
|
||||
this.scrollToCurrentContent(true);
|
||||
const scrollTo = (name) => {
|
||||
nextTick(() => {
|
||||
setCurrentIndexByName(name);
|
||||
scrollToCurrentContent(true);
|
||||
});
|
||||
},
|
||||
|
||||
scrollToCurrentContent(immediate = false) {
|
||||
if (this.scrollspy) {
|
||||
const target = this.children[this.currentIndex];
|
||||
const el = target?.$el;
|
||||
|
||||
if (el) {
|
||||
const to = getElementTop(el, this.scroller) - this.scrollOffset;
|
||||
|
||||
this.lockScroll = true;
|
||||
scrollTopTo(this.scroller, to, immediate ? 0 : +this.duration, () => {
|
||||
this.lockScroll = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onScroll() {
|
||||
if (this.scrollspy && !this.lockScroll) {
|
||||
const index = this.getCurrentIndexOnScroll();
|
||||
this.setCurrentIndex(index);
|
||||
}
|
||||
},
|
||||
|
||||
getCurrentIndexOnScroll() {
|
||||
const { children } = this;
|
||||
};
|
||||
|
||||
const getCurrentIndexOnScroll = () => {
|
||||
for (let index = 0; index < children.length; index++) {
|
||||
const top = getVisibleTop(children[index].$el);
|
||||
const top = getVisibleTop(children[index].getRoot());
|
||||
|
||||
if (top > this.scrollOffset) {
|
||||
if (top > scrollOffset.value) {
|
||||
return index === 0 ? 0 : index - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return children.length - 1;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
render() {
|
||||
const { type, animated, scrollable } = this;
|
||||
const onScroll = () => {
|
||||
if (props.scrollspy && !lockScroll) {
|
||||
const index = getCurrentIndexOnScroll();
|
||||
setCurrentIndex(index);
|
||||
}
|
||||
};
|
||||
|
||||
const Nav = this.children.map((item, index) => {
|
||||
return (
|
||||
const renderNav = () =>
|
||||
children.map((item, index) => (
|
||||
<TabsTitle
|
||||
ref={(val) => {
|
||||
this.titleRefs[index] = val;
|
||||
}}
|
||||
dot={item.dot}
|
||||
type={type}
|
||||
badge={item.badge}
|
||||
title={item.title}
|
||||
color={this.color}
|
||||
style={item.titleStyle}
|
||||
isActive={index === this.currentIndex}
|
||||
disabled={item.disabled}
|
||||
scrollable={scrollable}
|
||||
renderTitle={item.$slots.title}
|
||||
activeColor={this.titleActiveColor}
|
||||
inactiveColor={this.titleInactiveColor}
|
||||
ref={setTitleRefs(index)}
|
||||
dot={item.props.dot}
|
||||
type={props.type}
|
||||
badge={item.props.badge}
|
||||
title={item.props.title}
|
||||
color={props.color}
|
||||
style={item.props.titleStyle}
|
||||
isActive={index === state.currentIndex}
|
||||
disabled={item.props.disabled}
|
||||
scrollable={scrollable.value}
|
||||
renderTitle={item.slots.title}
|
||||
activeColor={props.titleActiveColor}
|
||||
inactiveColor={props.titleInactiveColor}
|
||||
onClick={() => {
|
||||
this.onClick(item, index);
|
||||
onClick(item, index);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
));
|
||||
|
||||
const Wrap = (
|
||||
<div
|
||||
ref="wrap"
|
||||
class={[
|
||||
bem('wrap', { scrollable }),
|
||||
{ [BORDER_TOP_BOTTOM]: type === 'line' && this.border },
|
||||
]}
|
||||
>
|
||||
const renderHeader = () => {
|
||||
const { type, border } = props;
|
||||
return (
|
||||
<div
|
||||
ref="nav"
|
||||
role="tablist"
|
||||
class={bem('nav', [type, { complete: this.scrollable }])}
|
||||
style={this.navStyle}
|
||||
ref={wrapRef}
|
||||
class={[
|
||||
bem('wrap', { scrollable: scrollable.value }),
|
||||
{ [BORDER_TOP_BOTTOM]: type === 'line' && border },
|
||||
]}
|
||||
>
|
||||
{this.$slots['nav-left']?.()}
|
||||
{Nav}
|
||||
{type === 'line' && (
|
||||
<div class={bem('line')} style={this.lineStyle} />
|
||||
)}
|
||||
{this.$slots['nav-right']?.()}
|
||||
<div
|
||||
ref={navRef}
|
||||
role="tablist"
|
||||
class={bem('nav', [type, { complete: scrollable.value }])}
|
||||
style={navStyle.value}
|
||||
>
|
||||
{slots['nav-left']?.()}
|
||||
{renderNav()}
|
||||
{type === 'line' && (
|
||||
<div class={bem('line')} style={state.lineStyle} />
|
||||
)}
|
||||
{slots['nav-right']?.()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
watch([() => props.color, windowSize.width], setLine);
|
||||
|
||||
watch(
|
||||
() => props.active,
|
||||
(value) => {
|
||||
if (value !== currentName.value) {
|
||||
setCurrentIndexByName(name);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<div class={bem([type])}>
|
||||
{this.sticky ? (
|
||||
watch(children, () => {
|
||||
setCurrentIndexByName(currentName.value || props.active);
|
||||
setLine();
|
||||
nextTick(() => {
|
||||
scrollIntoView(true);
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
() => state.currentIndex,
|
||||
() => {
|
||||
scrollIntoView();
|
||||
setLine();
|
||||
|
||||
// scroll to correct position
|
||||
if (stickyFixed && !props.scrollspy) {
|
||||
setRootScrollTop(
|
||||
Math.ceil(getElementTop(root.value) - offsetTopPx.value)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(init);
|
||||
|
||||
onActivated(() => {
|
||||
init();
|
||||
setLine();
|
||||
});
|
||||
|
||||
useExpose({
|
||||
resize: setLine,
|
||||
scrollTo,
|
||||
});
|
||||
|
||||
useEventListener('scroll', onScroll, { target: scroller.value });
|
||||
|
||||
provide(TABS_KEY, {
|
||||
emit,
|
||||
props,
|
||||
setLine,
|
||||
children,
|
||||
currentName,
|
||||
});
|
||||
|
||||
return () => (
|
||||
<div ref={root} class={bem([props.type])}>
|
||||
{props.sticky ? (
|
||||
<Sticky
|
||||
container={this.$el}
|
||||
offsetTop={this.offsetTop}
|
||||
onScroll={this.onSticktScroll}
|
||||
container={root.value}
|
||||
offsetTop={offsetTopPx.value}
|
||||
onScroll={onStickyScroll}
|
||||
>
|
||||
{Wrap}
|
||||
{renderHeader()}
|
||||
</Sticky>
|
||||
) : (
|
||||
Wrap
|
||||
renderHeader()
|
||||
)}
|
||||
<TabsContent
|
||||
count={this.children.length}
|
||||
animated={animated}
|
||||
duration={this.duration}
|
||||
swipeable={this.swipeable}
|
||||
currentIndex={this.currentIndex}
|
||||
onChange={this.setCurrentIndex}
|
||||
count={children.length}
|
||||
animated={props.animated}
|
||||
duration={props.duration}
|
||||
swipeable={props.swipeable}
|
||||
currentIndex={state.currentIndex}
|
||||
onChange={setCurrentIndex}
|
||||
>
|
||||
{this.$slots.default?.()}
|
||||
{slots.default?.()}
|
||||
</TabsContent>
|
||||
</div>
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user