mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 03:00:58 +08:00
refactor(vue): remove auto-generated router outlet inside of ion-tabs (#23479)
This commit is contained in:
42
BREAKING.md
42
BREAKING.md
@ -27,6 +27,7 @@ This is a comprehensive list of the breaking changes introduced in the major ver
|
|||||||
* [Config Provider](#config-provider)
|
* [Config Provider](#config-provider)
|
||||||
- [Vue](#vue)
|
- [Vue](#vue)
|
||||||
* [Tabs Config](#tabs-config)
|
* [Tabs Config](#tabs-config)
|
||||||
|
* [Tabs Router Outlet](#tabs-router-outlet)
|
||||||
* [Overlay Events](#overlay-events)
|
* [Overlay Events](#overlay-events)
|
||||||
- [Browser and Platform Support](#browser-and-platform-support)
|
- [Browser and Platform Support](#browser-and-platform-support)
|
||||||
|
|
||||||
@ -220,6 +221,47 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
|
|
||||||
In the example above `tabs/tab1/view` has been rewritten has a sibling route to `tabs/tab1`. The `path` field now includes the `tab1` prefix.
|
In the example above `tabs/tab1/view` has been rewritten has a sibling route to `tabs/tab1`. The `path` field now includes the `tab1` prefix.
|
||||||
|
|
||||||
|
#### Tabs Router Outlet
|
||||||
|
|
||||||
|
Developers must now provide an `ion-router-outlet` inside of `ion-tabs`. Previously one was generated automatically, but this made it difficult for developers to access the properties on the generated `ion-router-outlet`.
|
||||||
|
|
||||||
|
**Old**
|
||||||
|
```html
|
||||||
|
<ion-tabs>
|
||||||
|
<ion-tab-bar slot="bottom">
|
||||||
|
...
|
||||||
|
</ion-tab-bar>
|
||||||
|
</ion-tabs>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { IonTabs, IonTabBar } from '@ionic/vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { IonTabs, IonTabBar }
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
**New**
|
||||||
|
```html
|
||||||
|
<ion-tabs>
|
||||||
|
<ion-router-outlet></ion-router-outlet>
|
||||||
|
<ion-tab-bar slot="bottom">
|
||||||
|
...
|
||||||
|
</ion-tab-bar>
|
||||||
|
</ion-tabs>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { IonTabs, IonTabBar, IonRouterOutlet } from '@ionic/vue';
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { IonTabs, IonTabBar, IonRouterOutlet }
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
#### Overlay Events
|
#### Overlay Events
|
||||||
|
|
||||||
Overlay events `onWillPresent`, `onDidPresent`, `onWillDismiss`, and `onDidDismiss` have been removed in favor of `willPresent`, `didPresent`, `willDismiss`, and `didDismiss`.
|
Overlay events `onWillPresent`, `onDidPresent`, `onWillDismiss`, and `onDidDismiss` have been removed in favor of `willPresent`, `didPresent`, `willDismiss`, and `didDismiss`.
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { h, defineComponent, VNode } from 'vue';
|
import { h, defineComponent, VNode } from 'vue';
|
||||||
import { IonRouterOutlet } from './IonRouterOutlet';
|
|
||||||
|
|
||||||
const WILL_CHANGE = 'ionTabsWillChange';
|
const WILL_CHANGE = 'ionTabsWillChange';
|
||||||
const DID_CHANGE = 'ionTabsDidChange';
|
const DID_CHANGE = 'ionTabsDidChange';
|
||||||
@ -7,20 +6,21 @@ const DID_CHANGE = 'ionTabsDidChange';
|
|||||||
export const IonTabs = /*@__PURE__*/ defineComponent({
|
export const IonTabs = /*@__PURE__*/ defineComponent({
|
||||||
name: 'IonTabs',
|
name: 'IonTabs',
|
||||||
emits: [WILL_CHANGE, DID_CHANGE],
|
emits: [WILL_CHANGE, DID_CHANGE],
|
||||||
data() {
|
|
||||||
return { didWarn: false }
|
|
||||||
},
|
|
||||||
render() {
|
render() {
|
||||||
const { $slots: slots, $emit, $data } = this;
|
const { $slots: slots, $emit } = this;
|
||||||
const slottedContent = slots.default && slots.default();
|
const slottedContent = slots.default && slots.default();
|
||||||
let userProvidedRouterOutlet;
|
let routerOutlet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Developers must pass an ion-router-outlet
|
||||||
|
* inside of ion-tabs.
|
||||||
|
*/
|
||||||
if (slottedContent && slottedContent.length > 0) {
|
if (slottedContent && slottedContent.length > 0) {
|
||||||
/**
|
routerOutlet = slottedContent.find((child: VNode) => child.type && (child.type as any).name === 'IonRouterOutlet');
|
||||||
* If developer passed in their own ion-router-outlet
|
}
|
||||||
* instance, then we should not init a default one
|
|
||||||
*/
|
if (!routerOutlet) {
|
||||||
userProvidedRouterOutlet = slottedContent.find((child: VNode) => child.type && (child.type as any).name === 'IonRouterOutlet');
|
throw new Error('IonTabs must contain an IonRouterOutlet. See https://ionicframework.com/docs/vue/navigation#working-with-tabs for more information.');
|
||||||
}
|
}
|
||||||
|
|
||||||
let childrenToRender = [
|
let childrenToRender = [
|
||||||
@ -31,42 +31,15 @@ export const IonTabs = /*@__PURE__*/ defineComponent({
|
|||||||
'flex': '1',
|
'flex': '1',
|
||||||
'contain': 'layout size style'
|
'contain': 'layout size style'
|
||||||
}
|
}
|
||||||
}, (userProvidedRouterOutlet) ? userProvidedRouterOutlet : [h(IonRouterOutlet)])
|
}, routerOutlet)
|
||||||
];
|
];
|
||||||
|
|
||||||
if (userProvidedRouterOutlet && !$data.didWarn) {
|
|
||||||
console.warn(`[@ionic/vue Deprecation] Starting in Ionic Vue v6.0, developers must add an 'ion-router-outlet' instance inside of 'ion-tabs'.
|
|
||||||
|
|
||||||
Before:
|
|
||||||
|
|
||||||
<ion-tabs>
|
|
||||||
<ion-tab-bar slot="bottom">
|
|
||||||
...
|
|
||||||
</ion-tab-bar>
|
|
||||||
</ion-tabs>
|
|
||||||
|
|
||||||
After:
|
|
||||||
|
|
||||||
<ion-tabs>
|
|
||||||
<ion-router-outlet></ion-router-outlet>
|
|
||||||
<ion-tab-bar slot="bottom">
|
|
||||||
...
|
|
||||||
</ion-tab-bar>
|
|
||||||
</ion-tabs>
|
|
||||||
|
|
||||||
Be sure to import 'IonRouterOutlet' from '@ionic/vue' and provide that import to your Vue component. See https://ionicframework.com/docs/vue/navigation#working-with-tabs for more information.
|
|
||||||
`);
|
|
||||||
|
|
||||||
$data.didWarn = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If ion-tab-bar has slot="top" it needs to be
|
* If ion-tab-bar has slot="top" it needs to be
|
||||||
* rendered before `.tabs-inner` otherwise it will
|
* rendered before `.tabs-inner` otherwise it will
|
||||||
* not show above the tab content.
|
* not show above the tab content.
|
||||||
*/
|
*/
|
||||||
if (slottedContent && slottedContent.length > 0) {
|
if (slottedContent && slottedContent.length > 0) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render all content except for router outlet
|
* Render all content except for router outlet
|
||||||
* since that needs to be inside of `.tabs-inner`.
|
* since that needs to be inside of `.tabs-inner`.
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
<ion-page data-pageid="tabs">
|
<ion-page data-pageid="tabs">
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-tabs id="tabs">
|
<ion-tabs id="tabs">
|
||||||
|
<ion-router-outlet></ion-router-outlet>
|
||||||
<ion-tab-bar slot="bottom">
|
<ion-tab-bar slot="bottom">
|
||||||
<ion-tab-button
|
<ion-tab-button
|
||||||
v-for="tab in tabs"
|
v-for="tab in tabs"
|
||||||
@ -21,14 +22,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { IonButton, IonTabBar, IonTabButton, IonTabs, IonContent, IonLabel, IonIcon, IonPage } from '@ionic/vue';
|
import { IonButton, IonTabBar, IonTabButton, IonTabs, IonContent, IonLabel, IonIcon, IonPage, IonRouterOutlet } from '@ionic/vue';
|
||||||
import { ellipse, square, triangle, shield } from 'ionicons/icons';
|
import { ellipse, square, triangle, shield } from 'ionicons/icons';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { ref, defineComponent } from 'vue';
|
import { ref, defineComponent } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Tabs',
|
name: 'Tabs',
|
||||||
components: { IonButton, IonContent, IonLabel, IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage },
|
components: { IonButton, IonContent, IonLabel, IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage, IonRouterOutlet },
|
||||||
setup() {
|
setup() {
|
||||||
const tabs = ref([
|
const tabs = ref([
|
||||||
{ id: 1, icon: triangle },
|
{ id: 1, icon: triangle },
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
<template>
|
|
||||||
<ion-page data-pageid="tabs">
|
|
||||||
<ion-content>
|
|
||||||
<ion-tabs id="tabs">
|
|
||||||
<ion-router-outlet></ion-router-outlet>
|
|
||||||
<ion-tab-bar slot="bottom">
|
|
||||||
<ion-tab-button
|
|
||||||
v-for="tab in tabs"
|
|
||||||
:tab="'tab' + tab.id"
|
|
||||||
:href="'/tabs-new/tab' + tab.id"
|
|
||||||
:key="tab.id"
|
|
||||||
>
|
|
||||||
<ion-icon :icon="tab.icon" />
|
|
||||||
<ion-label>Tab {{ tab.id }}</ion-label>
|
|
||||||
</ion-tab-button>
|
|
||||||
|
|
||||||
<ion-button id="add-tab" @click="addTab()">Add Tab</ion-button>
|
|
||||||
</ion-tab-bar>
|
|
||||||
</ion-tabs>
|
|
||||||
</ion-content>
|
|
||||||
</ion-page>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { IonButton, IonTabBar, IonTabButton, IonTabs, IonContent, IonLabel, IonIcon, IonPage, IonRouterOutlet } from '@ionic/vue';
|
|
||||||
import { ellipse, square, triangle, shield } from 'ionicons/icons';
|
|
||||||
import { useRouter } from 'vue-router';
|
|
||||||
import { ref, defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
name: 'Tabs',
|
|
||||||
components: { IonButton, IonContent, IonLabel, IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage, IonRouterOutlet },
|
|
||||||
setup() {
|
|
||||||
const tabs = ref([
|
|
||||||
{ id: 1, icon: triangle },
|
|
||||||
{ id: 2, icon: ellipse },
|
|
||||||
{ 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>
|
|
@ -2,6 +2,7 @@
|
|||||||
<ion-page data-pageid="tabs-secondary">
|
<ion-page data-pageid="tabs-secondary">
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<ion-tabs id="tabs">
|
<ion-tabs id="tabs">
|
||||||
|
<ion-router-outlet></ion-router-outlet>
|
||||||
<ion-tab-bar slot="top">
|
<ion-tab-bar slot="top">
|
||||||
<ion-tab-button tab="tab1-secondary" href="/tabs-secondary/tab1">
|
<ion-tab-button tab="tab1-secondary" href="/tabs-secondary/tab1">
|
||||||
<ion-icon :icon="triangle" />
|
<ion-icon :icon="triangle" />
|
||||||
@ -24,12 +25,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { IonTabBar, IonTabButton, IonTabs, IonContent, IonLabel, IonIcon, IonPage } from '@ionic/vue';
|
import { IonTabBar, IonTabButton, IonTabs, IonContent, IonLabel, IonIcon, IonPage, IonRouterOutlet } from '@ionic/vue';
|
||||||
import { ellipse, square, triangle } from 'ionicons/icons';
|
import { ellipse, square, triangle } from 'ionicons/icons';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Tabs',
|
name: 'Tabs',
|
||||||
components: { IonContent, IonLabel, IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage },
|
components: { IonContent, IonLabel, IonTabs, IonTabBar, IonTabButton, IonIcon, IonPage, IonRouterOutlet },
|
||||||
setup() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
ellipse,
|
ellipse,
|
||||||
|
@ -191,10 +191,11 @@ describe('Routing', () => {
|
|||||||
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22492
|
// Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22492
|
||||||
it('should show correct view when replacing', async () => {
|
it('should show correct view when replacing', async () => {
|
||||||
const Tabs = {
|
const Tabs = {
|
||||||
components: { IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel },
|
components: { IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel, IonRouterOutlet },
|
||||||
template: `
|
template: `
|
||||||
<ion-page>
|
<ion-page>
|
||||||
<ion-tabs>
|
<ion-tabs>
|
||||||
|
<ion-router-outlet></ion-router-outlet>
|
||||||
<ion-tab-bar slot="top">
|
<ion-tab-bar slot="top">
|
||||||
<ion-tab-button tab="tab1" href="/tabs/tab1">
|
<ion-tab-button tab="tab1" href="/tabs/tab1">
|
||||||
<ion-label>Tab 1</ion-label>
|
<ion-label>Tab 1</ion-label>
|
||||||
|
@ -10,10 +10,11 @@ const App = {
|
|||||||
describe('ion-tab-bar', () => {
|
describe('ion-tab-bar', () => {
|
||||||
it('should render in the top slot', async () => {
|
it('should render in the top slot', async () => {
|
||||||
const Tabs = {
|
const Tabs = {
|
||||||
components: { IonPage, IonTabs, IonTabBar },
|
components: { IonPage, IonTabs, IonTabBar, IonRouterOutlet },
|
||||||
template: `
|
template: `
|
||||||
<ion-page>
|
<ion-page>
|
||||||
<ion-tabs>
|
<ion-tabs>
|
||||||
|
<ion-router-outlet></ion-router-outlet>
|
||||||
<ion-tab-bar slot="top"></ion-tab-bar>
|
<ion-tab-bar slot="top"></ion-tab-bar>
|
||||||
</ion-tabs>
|
</ion-tabs>
|
||||||
</ion-page>
|
</ion-page>
|
||||||
@ -42,10 +43,11 @@ describe('ion-tab-bar', () => {
|
|||||||
|
|
||||||
it('should render in the bottom slot', async () => {
|
it('should render in the bottom slot', async () => {
|
||||||
const Tabs = {
|
const Tabs = {
|
||||||
components: { IonPage, IonTabs, IonTabBar },
|
components: { IonPage, IonTabs, IonTabBar, IonRouterOutlet },
|
||||||
template: `
|
template: `
|
||||||
<ion-page>
|
<ion-page>
|
||||||
<ion-tabs>
|
<ion-tabs>
|
||||||
|
<ion-router-outlet></ion-router-outlet>
|
||||||
<ion-tab-bar slot="bottom"></ion-tab-bar>
|
<ion-tab-bar slot="bottom"></ion-tab-bar>
|
||||||
</ion-tabs>
|
</ion-tabs>
|
||||||
</ion-page>
|
</ion-page>
|
||||||
@ -74,10 +76,11 @@ describe('ion-tab-bar', () => {
|
|||||||
|
|
||||||
it('should render in the default slot', async () => {
|
it('should render in the default slot', async () => {
|
||||||
const Tabs = {
|
const Tabs = {
|
||||||
components: { IonPage, IonTabs, IonTabBar },
|
components: { IonPage, IonTabs, IonTabBar, IonRouterOutlet },
|
||||||
template: `
|
template: `
|
||||||
<ion-page>
|
<ion-page>
|
||||||
<ion-tabs>
|
<ion-tabs>
|
||||||
|
<ion-router-outlet></ion-router-outlet>
|
||||||
<ion-tab-bar></ion-tab-bar>
|
<ion-tab-bar></ion-tab-bar>
|
||||||
</ion-tabs>
|
</ion-tabs>
|
||||||
</ion-page>
|
</ion-page>
|
||||||
@ -106,10 +109,11 @@ describe('ion-tab-bar', () => {
|
|||||||
// Verifies the fix for https://github.com/ionic-team/ionic-framework/issues/22642
|
// Verifies the fix for https://github.com/ionic-team/ionic-framework/issues/22642
|
||||||
it('should not fail on non tab button elements', async () => {
|
it('should not fail on non tab button elements', async () => {
|
||||||
const Tabs = {
|
const Tabs = {
|
||||||
components: { IonPage, IonTabs, IonTabBar },
|
components: { IonPage, IonTabs, IonTabBar, IonRouterOutlet },
|
||||||
template: `
|
template: `
|
||||||
<ion-page>
|
<ion-page>
|
||||||
<ion-tabs>
|
<ion-tabs>
|
||||||
|
<ion-router-outlet></ion-router-outlet>
|
||||||
<ion-tab-bar>
|
<ion-tab-bar>
|
||||||
<!-- my comment -->
|
<!-- my comment -->
|
||||||
</ion-tab-bar>
|
</ion-tab-bar>
|
||||||
|
@ -9,10 +9,11 @@ const App = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Tabs = {
|
const Tabs = {
|
||||||
components: { IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel },
|
components: { IonPage, IonTabs, IonTabBar, IonTabButton, IonLabel, IonRouterOutlet },
|
||||||
template: `
|
template: `
|
||||||
<ion-page>
|
<ion-page>
|
||||||
<ion-tabs>
|
<ion-tabs>
|
||||||
|
<ion-router-outlet></ion-router-outlet>
|
||||||
<ion-tab-bar slot="top">
|
<ion-tab-bar slot="top">
|
||||||
<ion-tab-button tab="tab1" href="/tab1">
|
<ion-tab-button tab="tab1" href="/tab1">
|
||||||
<ion-label>Tab 1</ion-label>
|
<ion-label>Tab 1</ion-label>
|
||||||
|
Reference in New Issue
Block a user