diff --git a/packages/react-router/src/ReactRouter/NavManager.tsx b/packages/react-router/src/ReactRouter/NavManager.tsx index a01bc7873b..e73cd756eb 100644 --- a/packages/react-router/src/ReactRouter/NavManager.tsx +++ b/packages/react-router/src/ReactRouter/NavManager.tsx @@ -3,7 +3,6 @@ import { NavContext, NavContextState } from '@ionic/react'; import { Location as HistoryLocation, UnregisterCallback } from 'history'; import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; - import { StackManager } from './StackManager'; interface NavManagerProps extends RouteComponentProps { @@ -25,7 +24,6 @@ export class NavManager extends React.Component { return; }, // overridden in View for each IonPage - tabNavigate: this.tabNavigate.bind(this) }; this.listenUnregisterCallback = this.props.history.listen((location: HistoryLocation) => { @@ -53,12 +51,8 @@ export class NavManager extends React.Component void; + syncRoute: (id: string, route: any) => void; hideView: (viewId: string) => void; viewStacks: ViewStacks; setupIonRouter: (id: string, children: ReactNode, routerOutlet: HTMLIonRouterOutletElement) => void; removeViewStack: (stack: string) => void; + getRoute: (id: string) => any; } export const RouteManagerContext = /*@__PURE__*/React.createContext({ viewStacks: new ViewStacks(), syncView: () => { navContextNotFoundError(); }, + syncRoute: () => { navContextNotFoundError(); }, hideView: () => { navContextNotFoundError(); }, setupIonRouter: () => Promise.reject(navContextNotFoundError()), - removeViewStack: () => { navContextNotFoundError(); } + removeViewStack: () => { navContextNotFoundError(); }, + getRoute: () => { navContextNotFoundError(); } }); function navContextNotFoundError() { diff --git a/packages/react-router/src/ReactRouter/Router.tsx b/packages/react-router/src/ReactRouter/Router.tsx index 2da67dc729..50bfdb5f61 100644 --- a/packages/react-router/src/ReactRouter/Router.tsx +++ b/packages/react-router/src/ReactRouter/Router.tsx @@ -2,7 +2,7 @@ import { NavDirection } from '@ionic/core'; import { RouterDirection, getConfig } from '@ionic/react'; import { Action as HistoryAction, Location as HistoryLocation, UnregisterCallback } from 'history'; import React from 'react'; -import { RouteComponentProps, matchPath, withRouter } from 'react-router-dom'; +import { RouteComponentProps, withRouter, matchPath } from 'react-router-dom'; import { generateId, isDevMode } from '../utils'; import { LocationHistory } from '../utils/LocationHistory'; @@ -23,6 +23,7 @@ class RouteManager extends React.Component { const { view: enteringView, viewStack: enteringViewStack, match } = viewStacks.findViewInfoByLocation(location, key); if (!enteringView || !enteringViewStack) { return; } + leavingView = viewStacks.findViewInfoById(this.activeIonPageId).view; if (enteringView.isIonRoute) { enteringView.show = true; enteringView.mount = true; enteringView.routeData.match = match!; + shouldTransitionPage = true; this.activeIonPageId = enteringView.id; @@ -129,10 +144,12 @@ class RouteManager extends React.Component { - const { view: enteringView, viewStack } = this.state.viewStacks.findViewInfoById(this.activeIonPageId); - if (enteringView && viewStack) { - const enteringEl = enteringView.ionPageElement ? enteringView.ionPageElement : undefined; - const leavingEl = leavingView && leavingView.ionPageElement ? leavingView.ionPageElement : undefined; - if (enteringEl) { - // Don't animate from an empty view - const navDirection = leavingEl && leavingEl.innerHTML === '' ? undefined : direction === 'none' ? undefined : direction; - const shouldGoBack = !!enteringView.prevId || !!this.locationHistory.previous(); - this.commitView( - enteringEl!, - leavingEl!, - viewStack.routerOutlet, - navDirection, - shouldGoBack); - } else if (leavingEl) { - leavingEl.classList.add('ion-page-hidden'); - leavingEl.setAttribute('aria-hidden', 'true'); + if (shouldTransitionPage) { + const { view: enteringView, viewStack } = this.state.viewStacks.findViewInfoById(this.activeIonPageId); + if (enteringView && viewStack) { + const enteringEl = enteringView.ionPageElement ? enteringView.ionPageElement : undefined; + const leavingEl = leavingView && leavingView.ionPageElement ? leavingView.ionPageElement : undefined; + if (enteringEl) { + // Don't animate from an empty view + const navDirection = leavingEl && leavingEl.innerHTML === '' ? undefined : direction === 'none' ? undefined : direction; + const shouldGoBack = !!enteringView.prevId || !!this.locationHistory.previous(); + this.commitView( + enteringEl!, + leavingEl!, + viewStack.routerOutlet, + navDirection, + shouldGoBack, + leavingViewHtml); + } else if (leavingEl) { + leavingEl.classList.add('ion-page-hidden'); + leavingEl.setAttribute('aria-hidden', 'true'); + } } // Warn if an IonPage was not eventually rendered in Dev Mode if (isDevMode()) { - if (enteringView.routeData.match!.url !== location.pathname) { + if (enteringView && enteringView.routeData.match!.url !== location.pathname) { setTimeout(() => { const { view } = this.state.viewStacks.findViewInfoById(this.activeIonPageId); if (view!.routeData.match!.url !== location.pathname) { @@ -212,15 +232,18 @@ class RouteManager extends React.Component { - views.push(createViewItem(child, this.props.history.location)); + const routeId = generateId(); + this.routes[routeId] = child; + views.push(createViewItem(child, routeId, this.props.history.location)); }); this.registerViewStack(id, activeId, views, routerOutlet, this.props.location); - function createViewItem(child: React.ReactElement, location: HistoryLocation) { + function createViewItem(child: React.ReactElement, routeId: string, location: HistoryLocation) { const viewId = generateId(); const key = generateId(); - const route = child; + + // const route = child; const matchProps = { exact: child.props.exact, path: child.props.path || child.props.from, @@ -234,7 +257,7 @@ class RouteManager extends React.Component { + for (let routeKey in this.routes) { + const route = this.routes[routeKey]; + if (route.props.path == child.props.path) { + this.routes[routeKey] = child; + } + } }); + } + + private async commitView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet: HTMLIonRouterOutletElement, direction?: NavDirection, showGoBack?: boolean, leavingViewHtml?: string) { + + if ((enteringEl === leavingEl) && direction && leavingViewHtml) { + // If a page is transitioning to another version of itself + // we clone it so we can have an animation to show + const newLeavingElement = clonePageElement(leavingViewHtml); + ionRouterOutlet.appendChild(newLeavingElement); + await ionRouterOutlet.commit(enteringEl, newLeavingElement, { + deepWait: true, + duration: direction === undefined ? 0 : undefined, + direction, + showGoBack, + progressAnimation: false + }); + ionRouterOutlet.removeChild(newLeavingElement); + } else { + await ionRouterOutlet.commit(enteringEl, leavingEl, { + deepWait: true, + duration: direction === undefined ? 0 : undefined, + direction, + showGoBack, + progressAnimation: false + }); + } if (leavingEl && (enteringEl !== leavingEl)) { /** add hidden attributes */ @@ -357,23 +404,32 @@ class RouteManager extends React.Component { +class StackManagerInner extends React.Component { routerOutletEl: React.RefObject = React.createRef(); - context!: React.ContextType; + id: string; constructor(props: StackManagerProps) { @@ -31,7 +33,7 @@ export class StackManager extends React.Component { this.setState({ routerOutletReady: true @@ -39,33 +41,39 @@ export class StackManager extends React.Component x.show); const ionRouterOutlet = React.Children.only(this.props.children) as React.ReactElement; const { routerOutletReady } = this.state; const childElements = routerOutletReady ? views.map(view => { + const route = routeManager.getRoute(view.routeId); return ( - {this.renderChild(view)} + {this.renderChild(view, route)} ); @@ -99,8 +108,14 @@ export class StackManager extends React.Component { + return (props: any) => ( + + {context => } + + ) +} + +export const StackManager = withContext(StackManagerInner); diff --git a/packages/react-router/src/ReactRouter/View.tsx b/packages/react-router/src/ReactRouter/View.tsx index 95043e0858..9ce1561b1d 100644 --- a/packages/react-router/src/ReactRouter/View.tsx +++ b/packages/react-router/src/ReactRouter/View.tsx @@ -10,6 +10,7 @@ interface ViewProps extends React.HTMLAttributes { onViewSync: (page: HTMLElement, viewId: string) => void; onHideView: (viewId: string) => void; view: ViewItem; + route: any; } /** @@ -23,11 +24,11 @@ export class View extends React.Component { /** * If we can tell if view is a redirect, hide it so it will work again in future */ - const { view } = this.props; - if (view.route.type === Redirect) { + const { view, route } = this.props; + if (route.type === Redirect) { this.props.onHideView(view.id); - } else if (view.route.type === Route && view.route.props.render) { - if (view.route.props.render().type === Redirect) { + } else if (route.type === Route && route.props.render) { + if (route.props.render().type === Redirect) { this.props.onHideView(view.id); } } diff --git a/packages/react-router/src/ReactRouter/ViewItem.ts b/packages/react-router/src/ReactRouter/ViewItem.ts index 22b7e597e9..f9bc4932a6 100644 --- a/packages/react-router/src/ReactRouter/ViewItem.ts +++ b/packages/react-router/src/ReactRouter/ViewItem.ts @@ -3,8 +3,10 @@ export interface ViewItem { id: string; /** The key used by React. A new key is generated each time the view comes into the DOM so React thinks its a completely new element. */ key: string; + + routeId: string; /** The or component associated with the view */ - route: React.ReactElement; + // route: React.ReactElement; /** The reference to the element. */ ionPageElement?: HTMLElement; /** The routeData for the view. */ @@ -23,4 +25,9 @@ export interface ViewItem { * An IonRoute is a Route that contains an IonPage. Only IonPages participate in transition and lifecycle events. */ isIonRoute: boolean; + + /** + * location of the view + */ + location?: string; } diff --git a/packages/react-router/src/ReactRouter/ViewStacks.ts b/packages/react-router/src/ReactRouter/ViewStacks.ts index 660baa87c5..dd37a895cb 100644 --- a/packages/react-router/src/ReactRouter/ViewStacks.ts +++ b/packages/react-router/src/ReactRouter/ViewStacks.ts @@ -58,10 +58,11 @@ export class ViewStacks { path: v.routeData.childProps.path || v.routeData.childProps.from, component: v.routeData.childProps.component }; - match = matchPath(location.pathname, matchProps); - if (match) { + const myMatch: IonRouteData['match'] | null | undefined = matchPath(location.pathname, matchProps); + if (myMatch) { view = v; - return true; + match = myMatch; + return view.location === location.pathname; } return false; } diff --git a/packages/react/src/components/navigation/IonTabBar.tsx b/packages/react/src/components/navigation/IonTabBar.tsx index fb656acfd0..c22c32257d 100644 --- a/packages/react/src/components/navigation/IonTabBar.tsx +++ b/packages/react/src/components/navigation/IonTabBar.tsx @@ -69,12 +69,13 @@ const IonTabBarUnwrapped = /*@__PURE__*/(() => class extends React.Component) => { + const originalHref = this.state.tabs[e.detail.tab].originalHref; + const currentHref = this.state.tabs[e.detail.tab].currentHref; if (this.state.activeTab === e.detail.tab) { - const originalHref = this.state.tabs[e.detail.tab].originalHref; - if (this.context.hasIonicRouter()) { - this.context.tabNavigate(originalHref); + if (originalHref === currentHref) { + this.context.navigate(originalHref, 'none'); } else { - this.context.navigate(originalHref, 'back'); + this.context.navigate(originalHref, 'back', 'replace'); } } else { if (this.props.onIonTabsWillChange) { @@ -83,7 +84,7 @@ const IonTabBarUnwrapped = /*@__PURE__*/(() => class extends React.Component any; getStackManager: () => any; goBack: (defaultHref?: string) => void; - navigate: (path: string, direction?: RouterDirection | 'none') => void; + navigate: (path: string, direction?: RouterDirection | 'none', type?: 'push' | 'replace') => void; hasIonicRouter: () => boolean; registerIonPage: (page: HTMLElement) => void; - tabNavigate: (url: string) => void; currentPath: string | undefined; } @@ -23,7 +22,6 @@ export const NavContext = /*@__PURE__*/React.createContext({ } }, navigate: (path: string) => { window.location.pathname = path; }, - tabNavigate: () => undefined, hasIonicRouter: () => false, registerIonPage: () => undefined, currentPath: undefined