mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 10:41:13 +08:00
fix(vue): dynamic tabs are now correct recognized (#23212)
resolves #22847
This commit is contained in:
@ -11,20 +11,42 @@ interface Tab {
|
|||||||
ref: VNode
|
ref: VNode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isTabButton = (child: any) => child.type?.name === 'IonTabButton';
|
||||||
|
|
||||||
|
const getTabs = (nodes: VNode[]) => {
|
||||||
|
let tabs: VNode[] = [];
|
||||||
|
nodes.forEach((node: VNode) => {
|
||||||
|
if (isTabButton(node)) {
|
||||||
|
tabs.push(node);
|
||||||
|
} else if (Array.isArray(node.children) && node.children.length > 1) {
|
||||||
|
const childTabs = getTabs(node.children as VNode[]);
|
||||||
|
tabs = [...tabs, ...childTabs];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return tabs;
|
||||||
|
}
|
||||||
|
|
||||||
export const IonTabBar = defineComponent({
|
export const IonTabBar = defineComponent({
|
||||||
name: 'IonTabBar',
|
name: 'IonTabBar',
|
||||||
props: {
|
props: {
|
||||||
_tabsWillChange: { type: Function, default: () => {} },
|
_tabsWillChange: { type: Function, default: () => {} },
|
||||||
_tabsDidChange: { type: Function, default: () => {} }
|
_tabsDidChange: { type: Function, default: () => {} }
|
||||||
},
|
},
|
||||||
mounted() {
|
data() {
|
||||||
const ionRouter: any = inject('navManager');
|
return {
|
||||||
const tabState: TabState = {
|
tabState: {
|
||||||
activeTab: undefined,
|
activeTab: undefined,
|
||||||
tabs: {}
|
tabs: {}
|
||||||
};
|
},
|
||||||
const currentInstance = getCurrentInstance();
|
tabVnodes: []
|
||||||
const isTabButton = (child: any) => child.type?.name === 'IonTabButton';
|
}
|
||||||
|
},
|
||||||
|
updated() {
|
||||||
|
this.setupTabState(inject('navManager'));
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setupTabState(ionRouter: any) {
|
||||||
/**
|
/**
|
||||||
* For each tab, we need to keep track of its
|
* For each tab, we need to keep track of its
|
||||||
* base href as well as any child page that
|
* base href as well as any child page that
|
||||||
@ -32,9 +54,10 @@ export const IonTabBar = defineComponent({
|
|||||||
* to a tab from another tab, we can correctly
|
* to a tab from another tab, we can correctly
|
||||||
* show any child pages if necessary.
|
* show any child pages if necessary.
|
||||||
*/
|
*/
|
||||||
const children = (currentInstance.subTree.children || []) as VNode[];
|
const tabState: TabState = this.$data.tabState;
|
||||||
children.forEach((child: VNode) => {
|
const currentInstance = getCurrentInstance();
|
||||||
if (isTabButton(child)) {
|
const tabs = this.$data.tabVnodes = getTabs((currentInstance.subTree.children || []) as VNode[]);
|
||||||
|
tabs.forEach(child => {
|
||||||
tabState.tabs[child.props.tab] = {
|
tabState.tabs[child.props.tab] = {
|
||||||
originalHref: child.props.href,
|
originalHref: child.props.href,
|
||||||
currentHref: child.props.href,
|
currentHref: child.props.href,
|
||||||
@ -47,12 +70,15 @@ export const IonTabBar = defineComponent({
|
|||||||
* ion-tab-bar is managing for it.
|
* ion-tab-bar is managing for it.
|
||||||
*/
|
*/
|
||||||
child.component.props._getTabState = () => tabState;
|
child.component.props._getTabState = () => tabState;
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkActiveTab = (currentRoute: any) => {
|
this.checkActiveTab(ionRouter);
|
||||||
const childNodes = (currentInstance.subTree.children || []) as VNode[];
|
},
|
||||||
const { tabs, activeTab: prevActiveTab } = tabState;
|
checkActiveTab(ionRouter: any) {
|
||||||
|
const currentRoute = ionRouter.getCurrentRouteInfo();
|
||||||
|
const childNodes = this.$data.tabVnodes;
|
||||||
|
const { tabs, activeTab: prevActiveTab } = this.$data.tabState;
|
||||||
|
const tabState = this.$data.tabState;
|
||||||
const tabKeys = Object.keys(tabs);
|
const tabKeys = Object.keys(tabs);
|
||||||
const activeTab = tabKeys
|
const activeTab = tabKeys
|
||||||
.find(key => {
|
.find(key => {
|
||||||
@ -66,7 +92,6 @@ export const IonTabBar = defineComponent({
|
|||||||
* it in the tabs state.
|
* it in the tabs state.
|
||||||
*/
|
*/
|
||||||
childNodes.forEach((child: VNode) => {
|
childNodes.forEach((child: VNode) => {
|
||||||
if (isTabButton(child)) {
|
|
||||||
const tab = tabs[child.props.tab];
|
const tab = tabs[child.props.tab];
|
||||||
if (!tab || (tab.originalHref !== child.props.href)) {
|
if (!tab || (tab.originalHref !== child.props.href)) {
|
||||||
|
|
||||||
@ -76,11 +101,10 @@ export const IonTabBar = defineComponent({
|
|||||||
ref: child
|
ref: child
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (activeTab && prevActiveTab) {
|
if (activeTab && prevActiveTab) {
|
||||||
const prevHref = tabState.tabs[prevActiveTab].currentHref;
|
const prevHref = this.$data.tabState.tabs[prevActiveTab].currentHref;
|
||||||
/**
|
/**
|
||||||
* If the tabs change or the url changes,
|
* If the tabs change or the url changes,
|
||||||
* update the currentHref for the active tab.
|
* update the currentHref for the active tab.
|
||||||
@ -128,10 +152,14 @@ export const IonTabBar = defineComponent({
|
|||||||
tabBar.selectedTab = tabState.activeTab = '';
|
tabBar.selectedTab = tabState.activeTab = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const ionRouter: any = inject('navManager');
|
||||||
|
|
||||||
ionRouter.registerHistoryChangeListener(checkActiveTab.bind(this));
|
this.setupTabState(ionRouter);
|
||||||
checkActiveTab(ionRouter.getCurrentRouteInfo());
|
|
||||||
|
ionRouter.registerHistoryChangeListener(() => this.checkActiveTab(ionRouter));
|
||||||
},
|
},
|
||||||
setup(_, { slots }) {
|
setup(_, { slots }) {
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -3,20 +3,17 @@
|
|||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-tabs id="tabs">
|
<ion-tabs id="tabs">
|
||||||
<ion-tab-bar slot="bottom">
|
<ion-tab-bar slot="bottom">
|
||||||
<ion-tab-button tab="tab1" href="/tabs/tab1">
|
<ion-tab-button
|
||||||
<ion-icon :icon="triangle" />
|
v-for="tab in tabs"
|
||||||
<ion-label>Tab 1</ion-label>
|
:tab="'tab' + tab.id"
|
||||||
|
:href="'/tabs/tab' + tab.id"
|
||||||
|
:key="tab.id"
|
||||||
|
>
|
||||||
|
<ion-icon :icon="tab.icon" />
|
||||||
|
<ion-label>Tab {{ tab.id }}</ion-label>
|
||||||
</ion-tab-button>
|
</ion-tab-button>
|
||||||
|
|
||||||
<ion-tab-button tab="tab2" href="/tabs/tab2">
|
<ion-button id="add-tab" @click="addTab()">Add Tab</ion-button>
|
||||||
<ion-icon :icon="ellipse" />
|
|
||||||
<ion-label>Tab 2</ion-label>
|
|
||||||
</ion-tab-button>
|
|
||||||
|
|
||||||
<ion-tab-button tab="tab3" href="/tabs/tab3">
|
|
||||||
<ion-icon :icon="square" />
|
|
||||||
<ion-label>Tab 3</ion-label>
|
|
||||||
</ion-tab-button>
|
|
||||||
</ion-tab-bar>
|
</ion-tab-bar>
|
||||||
</ion-tabs>
|
</ion-tabs>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
@ -24,18 +21,33 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { IonTabBar, IonTabButton, IonTabs, IonContent, IonLabel, IonIcon, IonPage } from '@ionic/vue';
|
import { IonButton, IonTabBar, IonTabButton, IonTabs, IonContent, IonLabel, IonIcon, IonPage } from '@ionic/vue';
|
||||||
import { ellipse, square, triangle } from 'ionicons/icons';
|
import { ellipse, square, triangle, shield } from 'ionicons/icons';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { ref, defineComponent } from 'vue';
|
||||||
|
|
||||||
export default {
|
export default defineComponent({
|
||||||
name: 'Tabs',
|
name: 'Tabs',
|
||||||
components: { IonContent, IonLabel, IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage },
|
components: { IonButton, IonContent, IonLabel, IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage },
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
const tabs = ref([
|
||||||
ellipse,
|
{ id: 1, icon: triangle },
|
||||||
square,
|
{ id: 2, icon: ellipse },
|
||||||
triangle,
|
{ id: 3, icon: square }
|
||||||
|
])
|
||||||
|
const router = useRouter();
|
||||||
|
const addTab = () => {
|
||||||
|
router.addRoute({ path: '/tabs/tab4', component: () => import('@/views/Tab4.vue') });
|
||||||
|
tabs.value = [
|
||||||
|
...tabs.value,
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
icon: shield
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return { tabs, addTab }
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -264,6 +264,27 @@ describe('Tabs', () => {
|
|||||||
|
|
||||||
cy.url().should('include', '/tabs/tab1');
|
cy.url().should('include', '/tabs/tab1');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22847
|
||||||
|
it('should support dynamic tabs', () => {
|
||||||
|
cy.visit('http://localhost:8080/tabs/tab1');
|
||||||
|
|
||||||
|
cy.ionPageVisible('tab1');
|
||||||
|
|
||||||
|
cy.get('ion-tab-button').its('length').should('equal', 3);
|
||||||
|
cy.get('ion-tab-button#tab-button-tab1').should('have.class', 'tab-selected');
|
||||||
|
|
||||||
|
cy.get('#add-tab').click();
|
||||||
|
|
||||||
|
cy.get('ion-tab-button').its('length').should('equal', 4);
|
||||||
|
|
||||||
|
cy.get('ion-tab-button#tab-button-tab4').click();
|
||||||
|
cy.ionPageVisible('tab4');
|
||||||
|
cy.ionPageHidden('tab1');
|
||||||
|
|
||||||
|
cy.get('ion-tab-button#tab-button-tab1').should('not.have.class', 'tab-selected');
|
||||||
|
cy.get('ion-tab-button#tab-button-tab4').should('have.class', 'tab-selected');
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Tabs - Swipe to Go Back', () => {
|
describe('Tabs - Swipe to Go Back', () => {
|
||||||
|
Reference in New Issue
Block a user