From 6935c07ce6a9efb68be9bcd85a03b31b486677f1 Mon Sep 17 00:00:00 2001 From: lukicenturi Date: Mon, 11 Dec 2023 18:21:20 +0700 Subject: [PATCH] fix(Tabs): highlight correct active tab on route changed --- src/components/chips/Chip.spec.ts | 2 +- src/components/tabs/tabs/Tabs.vue | 109 ++++++++++++++++-------------- tests/setup-files/setup.ts | 4 ++ 3 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/components/chips/Chip.spec.ts b/src/components/chips/Chip.spec.ts index e28b259..257279c 100644 --- a/src/components/chips/Chip.spec.ts +++ b/src/components/chips/Chip.spec.ts @@ -29,7 +29,7 @@ describe('Chips/Chip', () => { expect(wrapper.emitted()).toHaveProperty('click:close'); }); - it("disabled chip can't close close", async () => { + it("disabled chip can't be closed", async () => { const wrapper = createWrapper({ propsData: { closeable: true, diff --git a/src/components/tabs/tabs/Tabs.vue b/src/components/tabs/tabs/Tabs.vue index 96d9f59..434effd 100644 --- a/src/components/tabs/tabs/Tabs.vue +++ b/src/components/tabs/tabs/Tabs.vue @@ -84,6 +84,8 @@ const updateValue = (newValue: string | number) => { set(internalValue, newValue); }; +const route = useRoute(); + const isPathMatch = ( path: string, { @@ -94,7 +96,6 @@ const isPathMatch = ( exact?: boolean; }, ) => { - const route = useRoute(); const currentRoute = route.fullPath; if (exactPath) { @@ -109,10 +110,51 @@ const isPathMatch = ( return currentRoute.startsWith(routeWithoutQueryParams); }; -onMounted(() => { - if (get(value) !== undefined) { - return; - } +const keepActiveTabVisible = () => { + nextTick(() => { + if (!get(showArrows)) { + return; + } + + const elem = get(wrapper); + const barElem = get(bar); + if (elem) { + const activeTab = (elem.querySelector('.active-tab') ?? + elem.querySelector('.active-tab-link')) as HTMLElement; + + if (!activeTab || !barElem) { + return; + } + + const childLeft = activeTab.offsetLeft - elem.offsetLeft; + const childTop = activeTab.offsetTop - elem.offsetTop; + const childWidth = activeTab.offsetWidth; + const childHeight = activeTab.offsetHeight; + const parentScrollLeft = barElem.scrollLeft; + const parentScrollTop = barElem.scrollTop; + const parentWidth = barElem.offsetWidth; + const parentHeight = barElem.offsetHeight; + + const scrollLeft = Math.max( + Math.min(parentScrollLeft, childLeft), + childLeft + childWidth - parentWidth, + ); + + const scrollTop = Math.max( + Math.min(parentScrollTop, childTop), + childTop + childHeight - parentHeight, + ); + + barElem.scrollTo({ + left: scrollLeft, + top: scrollTop, + behavior: 'smooth', + }); + } + }); +}; + +const applyNewValue = (onlyLink = false) => { const enabledChildren = get(children).filter( (child) => !(child.node.componentOptions?.propsData as TabProps).disabled, ); @@ -120,7 +162,7 @@ onMounted(() => { let newValue: string | number = 0; enabledChildren.forEach((child, index) => { const props = child.node.componentOptions?.propsData as TabProps; - if (index === 0 && props.tabValue) { + if (!onlyLink && index === 0 && props.tabValue) { newValue = props.tabValue; } if (props.link !== false && props.to && isPathMatch(props.to, props)) { @@ -130,6 +172,17 @@ onMounted(() => { updateValue(newValue); } keepActiveTabVisible(); +}; + +onMounted(() => { + if (get(value) !== undefined) { + return; + } + applyNewValue(); +}); + +watch(route, () => { + applyNewValue(true); }); const css = useCssModule(); @@ -206,50 +259,6 @@ const onNextSliderClick = () => { } }; -const keepActiveTabVisible = () => { - nextTick(() => { - if (!get(showArrows)) { - return; - } - - const elem = get(wrapper); - const barElem = get(bar); - if (elem) { - const activeTab = (elem.querySelector('.active-tab') ?? - elem.querySelector('.active-tab-link')) as HTMLElement; - - if (!activeTab || !barElem) { - return; - } - - const childLeft = activeTab.offsetLeft - elem.offsetLeft; - const childTop = activeTab.offsetTop - elem.offsetTop; - const childWidth = activeTab.offsetWidth; - const childHeight = activeTab.offsetHeight; - const parentScrollLeft = barElem.scrollLeft; - const parentScrollTop = barElem.scrollTop; - const parentWidth = barElem.offsetWidth; - const parentHeight = barElem.offsetHeight; - - const scrollLeft = Math.max( - Math.min(parentScrollLeft, childLeft), - childLeft + childWidth - parentWidth, - ); - - const scrollTop = Math.max( - Math.min(parentScrollTop, childTop), - childTop + childHeight - parentHeight, - ); - - barElem.scrollTo({ - left: scrollLeft, - top: scrollTop, - behavior: 'smooth', - }); - } - }); -}; - useResizeObserver(bar, () => { keepActiveTabVisible(); }); diff --git a/tests/setup-files/setup.ts b/tests/setup-files/setup.ts index 2f69bff..e74c2ea 100644 --- a/tests/setup-files/setup.ts +++ b/tests/setup-files/setup.ts @@ -41,3 +41,7 @@ vi.mock('vue', async () => { ), }; }); + +vi.mock('vue-router/composables', () => ({ + useRoute: vi.fn(), +}));