2024-08-16 14:20:18 +00:00
|
|
|
|
import type { TabsProps } from './types';
|
2024-08-15 15:30:07 +00:00
|
|
|
|
|
2024-08-16 14:20:18 +00:00
|
|
|
|
import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
2024-08-15 15:30:07 +00:00
|
|
|
|
|
2024-08-16 14:20:18 +00:00
|
|
|
|
import { VbenScrollbar } from '@vben-core/shadcn-ui';
|
|
|
|
|
|
|
|
|
|
import { useDebounceFn } from '@vueuse/core';
|
|
|
|
|
|
|
|
|
|
type DomElement = Element | null | undefined;
|
|
|
|
|
|
|
|
|
|
export function useTabsViewScroll(props: TabsProps) {
|
|
|
|
|
let resizeObserver: null | ResizeObserver = null;
|
|
|
|
|
let mutationObserver: MutationObserver | null = null;
|
|
|
|
|
let tabItemCount = 0;
|
|
|
|
|
const scrollbarRef = ref<InstanceType<typeof VbenScrollbar> | null>(null);
|
|
|
|
|
const scrollViewportEl = ref<DomElement>(null);
|
|
|
|
|
const showScrollButton = ref(false);
|
|
|
|
|
const scrollIsAtLeft = ref(true);
|
|
|
|
|
const scrollIsAtRight = ref(false);
|
2024-08-15 15:30:07 +00:00
|
|
|
|
|
|
|
|
|
function getScrollClientWidth() {
|
2024-08-16 14:20:18 +00:00
|
|
|
|
const scrollbarEl = scrollbarRef.value?.$el;
|
|
|
|
|
if (!scrollbarEl || !scrollViewportEl.value) return {};
|
2024-08-15 15:30:07 +00:00
|
|
|
|
|
2024-08-16 14:20:18 +00:00
|
|
|
|
const scrollbarWidth = scrollbarEl.clientWidth;
|
2024-08-15 15:30:07 +00:00
|
|
|
|
const scrollViewWidth = scrollViewportEl.value.clientWidth;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
scrollbarWidth,
|
|
|
|
|
scrollViewWidth,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function scrollDirection(
|
|
|
|
|
direction: 'left' | 'right',
|
2024-08-16 14:20:18 +00:00
|
|
|
|
distance: number = 150,
|
2024-08-15 15:30:07 +00:00
|
|
|
|
) {
|
|
|
|
|
const { scrollbarWidth, scrollViewWidth } = getScrollClientWidth();
|
|
|
|
|
|
|
|
|
|
if (!scrollbarWidth || !scrollViewWidth) return;
|
|
|
|
|
|
|
|
|
|
if (scrollbarWidth > scrollViewWidth) return;
|
|
|
|
|
|
|
|
|
|
scrollViewportEl.value?.scrollBy({
|
|
|
|
|
behavior: 'smooth',
|
|
|
|
|
left:
|
|
|
|
|
direction === 'left'
|
|
|
|
|
? -(scrollbarWidth - distance)
|
|
|
|
|
: +(scrollbarWidth - distance),
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function initScrollbar() {
|
|
|
|
|
await nextTick();
|
|
|
|
|
|
2024-08-16 14:20:18 +00:00
|
|
|
|
const scrollbarEl = scrollbarRef.value?.$el;
|
|
|
|
|
if (!scrollbarEl) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const viewportEl = scrollbarEl?.querySelector(
|
2024-08-15 15:30:07 +00:00
|
|
|
|
'div[data-radix-scroll-area-viewport]',
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
scrollViewportEl.value = viewportEl;
|
2024-08-16 14:20:18 +00:00
|
|
|
|
calcShowScrollbarButton();
|
|
|
|
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
scrollToActiveIntoView();
|
|
|
|
|
|
|
|
|
|
// 监听大小变化
|
|
|
|
|
resizeObserver?.disconnect();
|
|
|
|
|
resizeObserver = new ResizeObserver(
|
|
|
|
|
useDebounceFn((_entries: ResizeObserverEntry[]) => {
|
|
|
|
|
calcShowScrollbarButton();
|
|
|
|
|
}, 100),
|
|
|
|
|
);
|
|
|
|
|
resizeObserver.observe(viewportEl);
|
2024-08-15 15:30:07 +00:00
|
|
|
|
|
2024-08-16 14:20:18 +00:00
|
|
|
|
tabItemCount = props.tabs?.length || 0;
|
|
|
|
|
mutationObserver?.disconnect();
|
|
|
|
|
// 使用 MutationObserver 仅监听子节点数量变化
|
|
|
|
|
mutationObserver = new MutationObserver(() => {
|
|
|
|
|
const count = viewportEl.querySelectorAll(
|
|
|
|
|
`div[data-tab-item="true"]`,
|
|
|
|
|
).length;
|
|
|
|
|
|
|
|
|
|
if (count > tabItemCount) {
|
|
|
|
|
scrollToActiveIntoView();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (count !== tabItemCount) {
|
|
|
|
|
calcShowScrollbarButton();
|
|
|
|
|
tabItemCount = count;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 配置为仅监听子节点的添加和移除
|
|
|
|
|
mutationObserver.observe(viewportEl, {
|
|
|
|
|
attributes: false,
|
|
|
|
|
childList: true,
|
|
|
|
|
subtree: true,
|
|
|
|
|
});
|
2024-08-15 15:30:07 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-08-16 14:20:18 +00:00
|
|
|
|
async function scrollToActiveIntoView() {
|
|
|
|
|
if (!scrollViewportEl.value) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
await nextTick();
|
|
|
|
|
const viewportEl = scrollViewportEl.value;
|
|
|
|
|
const { scrollbarWidth } = getScrollClientWidth();
|
|
|
|
|
const { scrollWidth } = viewportEl;
|
|
|
|
|
|
|
|
|
|
if (scrollbarWidth >= scrollWidth) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
const activeItem = viewportEl?.querySelector('.is-active');
|
|
|
|
|
activeItem?.scrollIntoView({ behavior: 'smooth', inline: 'start' });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 计算tabs 宽度,用于判断是否显示左右滚动按钮
|
|
|
|
|
*/
|
|
|
|
|
async function calcShowScrollbarButton() {
|
|
|
|
|
if (!scrollViewportEl.value) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { scrollbarWidth } = getScrollClientWidth();
|
|
|
|
|
|
|
|
|
|
showScrollButton.value =
|
|
|
|
|
scrollViewportEl.value.scrollWidth > scrollbarWidth;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const handleScrollAt = useDebounceFn(({ left, right }) => {
|
|
|
|
|
scrollIsAtLeft.value = left;
|
|
|
|
|
scrollIsAtRight.value = right;
|
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
() => props.active,
|
|
|
|
|
async () => {
|
|
|
|
|
// 200为了等待 tab 切换动画完成
|
|
|
|
|
// setTimeout(() => {
|
|
|
|
|
scrollToActiveIntoView();
|
|
|
|
|
// }, 300);
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
flush: 'post',
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// watch(
|
|
|
|
|
// () => props.tabs?.length,
|
|
|
|
|
// async () => {
|
|
|
|
|
// await nextTick();
|
|
|
|
|
// calcShowScrollbarButton();
|
|
|
|
|
// },
|
|
|
|
|
// {
|
|
|
|
|
// flush: 'post',
|
|
|
|
|
// },
|
|
|
|
|
// );
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
() => props.styleType,
|
|
|
|
|
() => {
|
|
|
|
|
initScrollbar();
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
onMounted(initScrollbar);
|
|
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
resizeObserver?.disconnect();
|
|
|
|
|
mutationObserver?.disconnect();
|
|
|
|
|
resizeObserver = null;
|
|
|
|
|
mutationObserver = null;
|
|
|
|
|
});
|
|
|
|
|
|
2024-08-15 15:30:07 +00:00
|
|
|
|
return {
|
2024-08-16 14:20:18 +00:00
|
|
|
|
handleScrollAt,
|
2024-08-15 15:30:07 +00:00
|
|
|
|
initScrollbar,
|
2024-08-16 14:20:18 +00:00
|
|
|
|
scrollbarRef,
|
2024-08-15 15:30:07 +00:00
|
|
|
|
scrollDirection,
|
2024-08-16 14:20:18 +00:00
|
|
|
|
scrollIsAtLeft,
|
|
|
|
|
scrollIsAtRight,
|
|
|
|
|
showScrollButton,
|
2024-08-15 15:30:07 +00:00
|
|
|
|
};
|
|
|
|
|
}
|