Skip to content

refactor(vue): remove support for child routes nested inside of tabs #22919

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions BREAKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ This is a comprehensive list of the breaking changes introduced in the major ver
* [Transition Shadow](#transition-shadow)
- [Angular](#angular)
* [Config Provider](#config-provider)
- [Vue](#vue)
* [Tabs Config](#tabs-config)



Expand Down Expand Up @@ -73,6 +75,88 @@ The `experimentalTransitionShadow` config option has been removed. The transitio
The `Config.set()` method has been removed. See https://ionicframework.com/docs/angular/config for examples on how to set config globally, per-component, and per-platform.


### Vue

#### Tabs Config

Support for child routes nested inside of tabs has been removed to better conform to Vue Router's best practices. Additional routes should be written as sibling routes with the parent tab as the path prefix:

**Old**
```typescript
const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/tabs/tab1'
},
{
path: '/tabs/',
component: Tabs,
children: [
{
path: '',
redirect: 'tab1'
},
{
path: 'tab1',
component: () => import('@/views/Tab1.vue'),
children: {
{
path: 'view',
component: () => import('@/views/Tab1View.vue')
}
}
},
{
path: 'tab2',
component: () => import('@/views/Tab2.vue')
},
{
path: 'tab3',
component: () => import('@/views/Tab3.vue')
}
]
}
]
```

**New**
```typescript
const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/tabs/tab1'
},
{
path: '/tabs/',
component: Tabs,
children: [
{
path: '',
redirect: 'tab1'
},
{
path: 'tab1',
component: () => import('@/views/Tab1.vue')
},
{
path: 'tab1/view',
component: () => import('@/views/Tab1View.vue')
},
{
path: 'tab2',
component: () => import('@/views/Tab2.vue')
},
{
path: 'tab3',
component: () => import('@/views/Tab3.vue')
}
]
}
]
```

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.



## Version 5.x
Expand Down
22 changes: 8 additions & 14 deletions packages/vue-router/src/viewStacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,16 @@ export const createViewStacks = (router: Router) => {
viewItem.ionRoute = true;
}

const findViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number, useDeprecatedRouteSetup: boolean = false) => {
return findViewItemByPath(routeInfo.pathname, outletId, false, useDeprecatedRouteSetup);
const findViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number) => {
return findViewItemByPath(routeInfo.pathname, outletId, false);
}

const findLeavingViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number, mustBeIonRoute: boolean = true, useDeprecatedRouteSetup: boolean = false) => {
return findViewItemByPath(routeInfo.lastPathname, outletId, mustBeIonRoute, useDeprecatedRouteSetup);
const findLeavingViewItemByRouteInfo = (routeInfo: RouteInfo, outletId?: number, mustBeIonRoute: boolean = true) => {
return findViewItemByPath(routeInfo.lastPathname, outletId, mustBeIonRoute);
}

const findViewItemByPathname = (pathname: string, outletId?: number, useDeprecatedRouteSetup: boolean = false) => {
return findViewItemByPath(pathname, outletId, false, useDeprecatedRouteSetup);
const findViewItemByPathname = (pathname: string, outletId?: number) => {
return findViewItemByPath(pathname, outletId, false);
}

const findViewItemInStack = (path: string, stack: ViewItem[]): ViewItem | undefined => {
Expand All @@ -44,7 +44,7 @@ export const createViewStacks = (router: Router) => {
})
}

const findViewItemByPath = (path: string, outletId?: number, mustBeIonRoute: boolean = false, useDeprecatedRouteSetup: boolean = false): ViewItem | undefined => {
const findViewItemByPath = (path: string, outletId?: number, mustBeIonRoute: boolean = false): ViewItem | undefined => {
const matchView = (viewItem: ViewItem) => {
if (
(mustBeIonRoute && !viewItem.ionRoute) ||
Expand All @@ -54,13 +54,7 @@ export const createViewStacks = (router: Router) => {
}

const resolvedPath = router.resolve(path);
let findMatchedRoute;
// TODO: Remove in Ionic Vue v6.0
if (useDeprecatedRouteSetup) {
findMatchedRoute = resolvedPath.matched.find((matchedRoute: RouteLocationMatched) => matchedRoute === viewItem.matchedRoute && (path === viewItem.pathname || matchedRoute.path.includes(':')));
} else {
findMatchedRoute = resolvedPath.matched.find((matchedRoute: RouteLocationMatched) => matchedRoute === viewItem.matchedRoute);
}
const findMatchedRoute = resolvedPath.matched.find((matchedRoute: RouteLocationMatched) => matchedRoute === viewItem.matchedRoute);

if (findMatchedRoute) {
return viewItem;
Expand Down
35 changes: 10 additions & 25 deletions packages/vue/src/components/IonRouterOutlet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,11 @@ import { fireLifecycle, generateId, getConfig } from '../utils';
let viewDepthKey: InjectionKey<0> = Symbol(0);
export const IonRouterOutlet = defineComponent({
name: 'IonRouterOutlet',
setup(_, { attrs }) {
setup() {
const injectedRoute = inject(routeLocationKey)!;
const route = useRoute();
const depth = inject(viewDepthKey, 0);
let usingDeprecatedRouteSetup = false;

// TODO: Remove in Ionic Vue v6.0
if (attrs.tabs && route.matched[depth]?.children?.length > 0) {
console.warn('[@ionic/vue Deprecation]: Your child routes are nested inside of each tab in your routing config. This format will not be supported in Ionic Vue v6.0. Instead, write your child routes as sibling routes. See https://ionicframework.com/docs/vue/navigation#child-routes-within-tabs for more information.');
usingDeprecatedRouteSetup = true;
}
const matchedRouteRef: any = computed(() => {
const matchedRoute = route.matched[depth];

if (matchedRoute && attrs.tabs && route.matched[depth + 1] && usingDeprecatedRouteSetup) {
return route.matched[route.matched.length - 1];
}

return matchedRoute;
});
const matchedRouteRef: any = computed(() => route.matched[depth]);

provide(viewDepthKey, depth + 1)
provide(matchedRouteKey, matchedRouteRef);
Expand Down Expand Up @@ -83,15 +68,15 @@ export const IonRouterOutlet = defineComponent({
* to make sure the view is in the outlet we want.
*/
const routeInfo = ionRouter.getCurrentRouteInfo();
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id, usingDeprecatedRouteSetup);
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);

return !!enteringViewItem;
}
const onStart = async () => {
const routeInfo = ionRouter.getCurrentRouteInfo();
const { routerAnimation } = routeInfo;
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id, usingDeprecatedRouteSetup);
const leavingViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id, usingDeprecatedRouteSetup);
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);
const leavingViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);

if (leavingViewItem) {
let animationBuilder = routerAnimation;
Expand Down Expand Up @@ -146,7 +131,7 @@ export const IonRouterOutlet = defineComponent({
* re-hide the page that was going to enter.
*/
const routeInfo = ionRouter.getCurrentRouteInfo();
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id, usingDeprecatedRouteSetup);
const enteringViewItem = viewStacks.findViewItemByRouteInfo({ pathname: routeInfo.pushedByRoute || '' }, id);
enteringViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
enteringViewItem.ionPageElement.classList.add('ion-page-hidden');
}
Expand Down Expand Up @@ -201,14 +186,14 @@ export const IonRouterOutlet = defineComponent({
const routeInfo = ionRouter.getCurrentRouteInfo();
const { routerDirection, routerAction, routerAnimation, prevRouteLastPathname } = routeInfo;

const enteringViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id, usingDeprecatedRouteSetup);
let leavingViewItem = viewStacks.findLeavingViewItemByRouteInfo(routeInfo, id, true, usingDeprecatedRouteSetup);
const enteringViewItem = viewStacks.findViewItemByRouteInfo(routeInfo, id);
let leavingViewItem = viewStacks.findLeavingViewItemByRouteInfo(routeInfo, id);
const enteringEl = enteringViewItem.ionPageElement;

if (enteringViewItem === leavingViewItem) return;

if (!leavingViewItem && prevRouteLastPathname) {
leavingViewItem = viewStacks.findViewItemByPathname(prevRouteLastPathname, id, usingDeprecatedRouteSetup);
leavingViewItem = viewStacks.findViewItemByPathname(prevRouteLastPathname, id);
}

fireLifecycle(enteringViewItem.vueComponent, enteringViewItem.vueComponentRef, LIFECYCLE_WILL_ENTER);
Expand Down Expand Up @@ -303,7 +288,7 @@ export const IonRouterOutlet = defineComponent({
}

const currentRoute = ionRouter.getCurrentRouteInfo();
let enteringViewItem = viewStacks.findViewItemByRouteInfo(currentRoute, id, usingDeprecatedRouteSetup);
let enteringViewItem = viewStacks.findViewItemByRouteInfo(currentRoute, id);

if (!enteringViewItem) {
enteringViewItem = viewStacks.createViewItem(id, matchedRouteRef.value.components.default, matchedRouteRef.value, currentRoute);
Expand Down
2 changes: 1 addition & 1 deletion packages/vue/src/components/IonTabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const IonTabs = defineComponent({
'contain': 'layout size style'
}
}, [
h(IonRouterOutlet, { tabs: true })
h(IonRouterOutlet)
])
];

Expand Down
35 changes: 0 additions & 35 deletions packages/vue/test-app/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,41 +83,6 @@ const routes: Array<RouteRecordRaw> = [
path: '',
redirect: '/tabs/tab1'
},
{
path: 'tab1',
component: () => import('@/views/Tab1.vue'),
children: [
{
path: 'child-one',
component: () => import('@/views/Tab1ChildOne.vue')
},
{
path: 'child-two',
component: () => import('@/views/Tab1ChildTwo.vue')
}
]
},
{
path: 'tab2',
component: () => import('@/views/Tab2.vue')
},
{
path: 'tab3',
beforeEnter: (to, from, next) => {
next({ path: '/tabs/tab1' });
},
component: () => import('@/views/Tab3.vue')
}
]
},
{
path: '/tabs-new/',
component: () => import('@/views/Tabs.vue'),
children: [
{
path: '',
redirect: '/tabs-new/tab1'
},
{
path: 'tab1',
component: () => import('@/views/Tab1.vue'),
Expand Down
17 changes: 0 additions & 17 deletions packages/vue/test-app/tests/e2e/specs/tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,23 +190,6 @@ describe('Tabs', () => {
cy.ionPageVisible('tab2');
cy.ionPageVisible('tabs');
});

// Verifies 1 of 2 fixes for https://github.com/ionic-team/ionic-framework/issues/22519
it('should not create a new tabs instance when switching between tabbed and non-tabbed contexts - new tabs setup', () => {
cy.visit('http://localhost:8080/tabs-new/tab1');

cy.routerPush('/');
cy.ionPageHidden('tabs');
cy.ionPageVisible('home');

cy.routerPush('/tabs-new/tab2');
cy.ionPageHidden('tab1');

cy.ionPageHidden('home');

cy.ionPageVisible('tab2');
cy.ionPageVisible('tabs');
});
})

describe('Tabs - Swipe to Go Back', () => {
Expand Down
6 changes: 2 additions & 4 deletions packages/vue/test-app/tests/unit/tab-bar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@ describe('ion-tab-bar', () => {
});

const innerHTML = wrapper.find('ion-tabs').html();
// TODO: Remove tabs="true" in Ionic Vue v6.0
expect(innerHTML).toContain(`<div class="tabs-inner" style="position: relative; flex: 1; contain: layout size style;"><ion-router-outlet tabs="true"></ion-router-outlet></div><ion-tab-bar slot="bottom"></ion-tab-bar>`);
expect(innerHTML).toContain(`<div class="tabs-inner" style="position: relative; flex: 1; contain: layout size style;"><ion-router-outlet></ion-router-outlet></div><ion-tab-bar slot="bottom"></ion-tab-bar>`);

});

Expand Down Expand Up @@ -101,8 +100,7 @@ describe('ion-tab-bar', () => {
});

const innerHTML = wrapper.find('ion-tabs').html();
// TODO: Remove tabs="true" in Ionic Vue v6.0
expect(innerHTML).toContain(`<div class="tabs-inner" style="position: relative; flex: 1; contain: layout size style;"><ion-router-outlet tabs="true"></ion-router-outlet></div><ion-tab-bar></ion-tab-bar></ion-tabs>`)
expect(innerHTML).toContain(`<div class="tabs-inner" style="position: relative; flex: 1; contain: layout size style;"><ion-router-outlet></ion-router-outlet></div><ion-tab-bar></ion-tab-bar></ion-tabs>`)
});

// Verifies the fix for https://github.com/ionic-team/ionic-framework/issues/22642
Expand Down