mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
3 Commits
v8.6.7
...
sp/react-r
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1194f24756 | ||
|
|
f49ce7a835 | ||
|
|
1ab433be07 |
@@ -46,6 +46,10 @@ class IonRouterInner extends React.PureComponent<IonRouteProps, IonRouteState> {
|
|||||||
findLeavingViewItemByRouteInfo: this.viewStack.findLeavingViewItemByRouteInfo,
|
findLeavingViewItemByRouteInfo: this.viewStack.findLeavingViewItemByRouteInfo,
|
||||||
addViewItem: this.viewStack.add,
|
addViewItem: this.viewStack.add,
|
||||||
unMountViewItem: this.viewStack.remove,
|
unMountViewItem: this.viewStack.remove,
|
||||||
|
registerIonPage: this.viewStack.registerIonPage,
|
||||||
|
unmountLeavingViews: this.viewStack.unmountLeavingViews,
|
||||||
|
mountIntermediaryViews: this.viewStack.mountIntermediaryViews,
|
||||||
|
size: this.viewStack.size,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: IonRouteProps) {
|
constructor(props: IonRouteProps) {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import type { RouteInfo, ViewItem } from '@ionic/react';
|
import type { RouteInfo, ViewItem } from '@ionic/react';
|
||||||
import { IonRoute, ViewLifeCycleManager, ViewStacks, generateId } from '@ionic/react';
|
import { IonRoute, ViewLifeCycleManager, ViewStacks, generateId } from '@ionic/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { matchPath } from 'react-router';
|
// import { matchPath } from 'react-router';
|
||||||
|
|
||||||
|
import { matchPath } from './utils/matchPath';
|
||||||
|
|
||||||
export class ReactRouterViewStack extends ViewStacks {
|
export class ReactRouterViewStack extends ViewStacks {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -13,35 +15,30 @@ export class ReactRouterViewStack extends ViewStacks {
|
|||||||
this.findViewItemByPathname = this.findViewItemByPathname.bind(this);
|
this.findViewItemByPathname = this.findViewItemByPathname.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
createViewItem(outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) {
|
createViewItem(
|
||||||
const viewItem: ViewItem = {
|
outletId: string,
|
||||||
|
reactElement: React.ReactElement,
|
||||||
|
routeInfo: RouteInfo,
|
||||||
|
ionPage?: HTMLElement
|
||||||
|
): ViewItem {
|
||||||
|
const ionRoute = reactElement.type === IonRoute;
|
||||||
|
|
||||||
|
return {
|
||||||
id: generateId('viewItem'),
|
id: generateId('viewItem'),
|
||||||
outletId,
|
outletId,
|
||||||
ionPageElement: page,
|
ionPageElement: ionPage,
|
||||||
reactElement,
|
reactElement,
|
||||||
mount: true,
|
ionRoute,
|
||||||
ionRoute: false,
|
disableIonPageManagement: ionRoute && reactElement.props.disableIonPageManagement,
|
||||||
|
mount: false,
|
||||||
|
routeData: {
|
||||||
|
match: matchPath({
|
||||||
|
pathname: routeInfo.pathname,
|
||||||
|
componentProps: reactElement.props,
|
||||||
|
}),
|
||||||
|
childProps: reactElement.props,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const matchProps = {
|
|
||||||
exact: reactElement.props.exact,
|
|
||||||
path: reactElement.props.path || reactElement.props.from,
|
|
||||||
component: reactElement.props.component,
|
|
||||||
};
|
|
||||||
|
|
||||||
const match = matchPath(routeInfo.pathname, matchProps);
|
|
||||||
|
|
||||||
if (reactElement.type === IonRoute) {
|
|
||||||
viewItem.ionRoute = true;
|
|
||||||
viewItem.disableIonPageManagement = reactElement.props.disableIonPageManagement;
|
|
||||||
}
|
|
||||||
|
|
||||||
viewItem.routeData = {
|
|
||||||
match,
|
|
||||||
childProps: reactElement.props,
|
|
||||||
};
|
|
||||||
|
|
||||||
return viewItem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getChildrenToRender(outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo) {
|
getChildrenToRender(outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo) {
|
||||||
@@ -58,7 +55,7 @@ export class ReactRouterViewStack extends ViewStacks {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const children = viewItems.map((viewItem) => {
|
const children = viewItems.map((viewItem) => {
|
||||||
let clonedChild;
|
let clonedChild: React.ReactNode;
|
||||||
if (viewItem.ionRoute && !viewItem.disableIonPageManagement) {
|
if (viewItem.ionRoute && !viewItem.disableIonPageManagement) {
|
||||||
clonedChild = (
|
clonedChild = (
|
||||||
<ViewLifeCycleManager
|
<ViewLifeCycleManager
|
||||||
@@ -96,8 +93,20 @@ export class ReactRouterViewStack extends ViewStacks {
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the `<IonPage>` element reference to
|
||||||
|
* the view item with the matching route info.
|
||||||
|
*/
|
||||||
|
registerIonPage(viewItem: ViewItem, ionPage: HTMLElement) {
|
||||||
|
if (viewItem) {
|
||||||
|
// TODO view doesn't check if it exists
|
||||||
|
viewItem.ionPageElement = ionPage;
|
||||||
|
viewItem.ionRoute = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) {
|
findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) {
|
||||||
const { viewItem, match } = this.findViewItemByPath(routeInfo.pathname, outletId);
|
const { viewItem, match } = this.findViewItemByPath(routeInfo.pathname, outletId, false);
|
||||||
const shouldUpdateMatch = updateMatch === undefined || updateMatch === true;
|
const shouldUpdateMatch = updateMatch === undefined || updateMatch === true;
|
||||||
if (shouldUpdateMatch && viewItem && match) {
|
if (shouldUpdateMatch && viewItem && match) {
|
||||||
viewItem.routeData.match = match;
|
viewItem.routeData.match = match;
|
||||||
@@ -106,31 +115,30 @@ export class ReactRouterViewStack extends ViewStacks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, mustBeIonRoute = true) {
|
findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, mustBeIonRoute = true) {
|
||||||
const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname!, outletId, false, mustBeIonRoute);
|
const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname!, outletId, mustBeIonRoute);
|
||||||
return viewItem;
|
return viewItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
findViewItemByPathname(pathname: string, outletId?: string) {
|
findViewItemByPathname(pathname: string, outletId?: string) {
|
||||||
const { viewItem } = this.findViewItemByPath(pathname, outletId);
|
const { viewItem } = this.findViewItemByPath(pathname, outletId, false);
|
||||||
return viewItem;
|
return viewItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
private findViewItemByPath(pathname: string, outletId?: string, forceExact?: boolean, mustBeIonRoute?: boolean) {
|
private findViewItemByPath(pathname: string, outletId?: string, mustBeIonRoute = false) {
|
||||||
let viewItem: ViewItem | undefined;
|
let viewItem: ViewItem | undefined;
|
||||||
let match: ReturnType<typeof matchPath> | undefined;
|
let match: ReturnType<typeof matchPath> | undefined;
|
||||||
let viewStack: ViewItem[];
|
|
||||||
|
|
||||||
if (outletId) {
|
if (outletId) {
|
||||||
viewStack = this.getViewItemsForOutlet(outletId);
|
const viewStack = this.getViewItemsForOutlet(outletId);
|
||||||
viewStack.some(matchView);
|
viewStack.some(matchView);
|
||||||
if (!viewItem) {
|
if (!viewItem) {
|
||||||
viewStack.some(matchDefaultRoute);
|
viewStack.some(matchDefaultRoute);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const viewItems = this.getAllViewItems();
|
const viewStack = this.getAllViewItems();
|
||||||
viewItems.some(matchView);
|
viewStack.some(matchView);
|
||||||
if (!viewItem) {
|
if (!viewItem) {
|
||||||
viewItems.some(matchDefaultRoute);
|
viewStack.some(matchDefaultRoute);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,15 +148,14 @@ export class ReactRouterViewStack extends ViewStacks {
|
|||||||
if (mustBeIonRoute && !v.ionRoute) {
|
if (mustBeIonRoute && !v.ionRoute) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const matchProps = {
|
|
||||||
exact: forceExact ? true : v.routeData.childProps.exact,
|
match = matchPath({
|
||||||
path: v.routeData.childProps.path || v.routeData.childProps.from,
|
pathname,
|
||||||
component: v.routeData.childProps.component,
|
componentProps: v.routeData.childProps,
|
||||||
};
|
});
|
||||||
const myMatch = matchPath(pathname, matchProps);
|
|
||||||
if (myMatch) {
|
if (match) {
|
||||||
viewItem = v;
|
viewItem = v;
|
||||||
match = myMatch;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -171,13 +178,16 @@ export class ReactRouterViewStack extends ViewStacks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchComponent(node: React.ReactElement, pathname: string, forceExact?: boolean) {
|
function matchComponent(node: React.ReactElement, pathname: string) {
|
||||||
const matchProps = {
|
// const matchProps = {
|
||||||
exact: forceExact ? true : node.props.exact,
|
// exact: forceExact ? true : node.props.exact,
|
||||||
path: node.props.path || node.props.from,
|
// path: node.props.path || node.props.from,
|
||||||
component: node.props.component,
|
// component: node.props.component,
|
||||||
};
|
// };
|
||||||
const match = matchPath(pathname, matchProps);
|
// const match = matchPath(pathname, matchProps);
|
||||||
|
|
||||||
return match;
|
return matchPath({
|
||||||
|
pathname,
|
||||||
|
componentProps: node.props,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
|
import type { AnimationBuilder, RouteInfo, RouterDirection, StackContextState } from '@ionic/react';
|
||||||
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
|
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { matchPath } from 'react-router-dom';
|
import { matchPath } from 'react-router-dom';
|
||||||
|
|
||||||
import { clonePageElement } from './clonePageElement';
|
|
||||||
|
|
||||||
// TODO(FW-2959): types
|
|
||||||
|
|
||||||
interface StackManagerProps {
|
interface StackManagerProps {
|
||||||
routeInfo: RouteInfo;
|
routeInfo: RouteInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
interface StackManagerState {
|
||||||
interface StackManagerState {}
|
components: ReactNode[];
|
||||||
|
}
|
||||||
|
|
||||||
const isViewVisible = (el: HTMLElement) =>
|
const isViewVisible = (enteringEl: HTMLElement) => {
|
||||||
!el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden');
|
return !enteringEl.classList.contains('ion-page-invisible') && !enteringEl.classList.contains('ion-page-hidden');
|
||||||
|
};
|
||||||
|
|
||||||
export class StackManager extends React.PureComponent<StackManagerProps, StackManagerState> {
|
export class StackManager extends React.PureComponent<StackManagerProps, StackManagerState> {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -31,16 +30,18 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
};
|
};
|
||||||
|
|
||||||
private clearOutletTimeout: any;
|
private clearOutletTimeout: any;
|
||||||
private pendingPageTransition = false;
|
|
||||||
|
|
||||||
constructor(props: StackManagerProps) {
|
constructor(props: StackManagerProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.registerIonPage = this.registerIonPage.bind(this);
|
this.registerIonPage = this.registerIonPage.bind(this);
|
||||||
this.transitionPage = this.transitionPage.bind(this);
|
this.transition = this.transition.bind(this);
|
||||||
this.handlePageTransition = this.handlePageTransition.bind(this);
|
this.handlePageTransition = this.handlePageTransition.bind(this);
|
||||||
this.id = generateId('routerOutlet');
|
this.id = generateId('ion-router-outlet');
|
||||||
this.prevProps = undefined;
|
this.prevProps = undefined;
|
||||||
this.skipTransition = false;
|
this.skipTransition = false;
|
||||||
|
this.state = {
|
||||||
|
components: [],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -58,7 +59,7 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
}
|
}
|
||||||
if (this.routerOutletElement) {
|
if (this.routerOutletElement) {
|
||||||
this.setupRouterOutlet(this.routerOutletElement);
|
this.setupRouterOutlet(this.routerOutletElement);
|
||||||
this.handlePageTransition(this.props.routeInfo);
|
this.setupViewItem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,145 +69,362 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
|
|
||||||
if (pathname !== prevPathname) {
|
if (pathname !== prevPathname) {
|
||||||
this.prevProps = prevProps;
|
this.prevProps = prevProps;
|
||||||
this.handlePageTransition(this.props.routeInfo);
|
console.log('pathname changed... setup view item?!');
|
||||||
} else if (this.pendingPageTransition) {
|
this.setupViewItem();
|
||||||
this.handlePageTransition(this.props.routeInfo);
|
|
||||||
this.pendingPageTransition = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (pathname !== prevPathname) {
|
||||||
|
// this.prevProps = prevProps;
|
||||||
|
// console.log('calling page transition because path is different', {
|
||||||
|
// pathname,
|
||||||
|
// prevPathname,
|
||||||
|
// });
|
||||||
|
// this.handlePageTransition(this.props.routeInfo);
|
||||||
|
// } else if (this.pendingPageTransition) {
|
||||||
|
// console.log('calling page transition because pending transition');
|
||||||
|
// this.handlePageTransition(this.props.routeInfo);
|
||||||
|
// this.pendingPageTransition = false;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
/**
|
||||||
|
* Remove stack data for this outlet
|
||||||
|
* when outlet is destroyed otherwise
|
||||||
|
* we will see cached view data.
|
||||||
|
*/
|
||||||
this.clearOutletTimeout = this.context.clearOutlet(this.id);
|
this.clearOutletTimeout = this.context.clearOutlet(this.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePageTransition(routeInfo: RouteInfo) {
|
setupViewItem() {
|
||||||
if (!this.routerOutletElement || !this.routerOutletElement.commit) {
|
// TODO additional checks
|
||||||
|
|
||||||
|
const { id, props } = this;
|
||||||
|
const { routeInfo } = props;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function is responsible for creating the entering view item
|
||||||
|
* and adding it to the view stack.
|
||||||
|
*/
|
||||||
|
const currentRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
|
||||||
|
|
||||||
|
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, id);
|
||||||
|
|
||||||
|
if (!enteringViewItem) {
|
||||||
/**
|
/**
|
||||||
* The route outlet has not mounted yet. We need to wait for it to render
|
* If the entering view item does not exist, this is
|
||||||
* before we can transition the page.
|
* the first time we are rendering this route.
|
||||||
*
|
*
|
||||||
* Set a flag to indicate that we should transition the page after
|
* We need to create a view item instance and add it to the view stack.
|
||||||
* the component has updated.
|
* Later, we will mount the view item and transition the page.
|
||||||
*/
|
*/
|
||||||
this.pendingPageTransition = true;
|
enteringViewItem = this.context.createViewItem(id, currentRoute, routeInfo);
|
||||||
} else {
|
|
||||||
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
|
|
||||||
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
|
|
||||||
|
|
||||||
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
|
this.context.addViewItem(enteringViewItem);
|
||||||
leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if leavingViewItem should be unmounted
|
|
||||||
if (leavingViewItem) {
|
|
||||||
if (routeInfo.routeAction === 'replace') {
|
|
||||||
leavingViewItem.mount = false;
|
|
||||||
} else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) {
|
|
||||||
if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
|
|
||||||
leavingViewItem.mount = false;
|
|
||||||
}
|
|
||||||
} else if (routeInfo.routeOptions?.unmount) {
|
|
||||||
leavingViewItem.mount = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
|
|
||||||
|
|
||||||
if (enteringViewItem) {
|
|
||||||
enteringViewItem.reactElement = enteringRoute;
|
|
||||||
} else if (enteringRoute) {
|
|
||||||
enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
|
|
||||||
this.context.addViewItem(enteringViewItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enteringViewItem && enteringViewItem.ionPageElement) {
|
|
||||||
/**
|
|
||||||
* If the entering view item is the same as the leaving view item,
|
|
||||||
* then we don't need to transition.
|
|
||||||
*/
|
|
||||||
if (enteringViewItem === leavingViewItem) {
|
|
||||||
/**
|
|
||||||
* If the entering view item is the same as the leaving view item,
|
|
||||||
* we are either transitioning using parameterized routes to the same view
|
|
||||||
* or a parent router outlet is re-rendering as a result of React props changing.
|
|
||||||
*
|
|
||||||
* If the route data does not match the current path, the parent router outlet
|
|
||||||
* is attempting to transition and we cancel the operation.
|
|
||||||
*/
|
|
||||||
if (enteringViewItem.routeData.match.url !== routeInfo.pathname) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If there isn't a leaving view item, but the route info indicates
|
|
||||||
* that the user has routed from a previous path, then we need
|
|
||||||
* to find the leaving view item to transition between.
|
|
||||||
*/
|
|
||||||
if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
|
|
||||||
leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If the entering view is already visible and the leaving view is not, the transition does not need to occur.
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
isViewVisible(enteringViewItem.ionPageElement) &&
|
|
||||||
leavingViewItem !== undefined &&
|
|
||||||
!isViewVisible(leavingViewItem.ionPageElement!)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The view should only be transitioned in the following cases:
|
|
||||||
* 1. Performing a replace or pop action, such as a swipe to go back gesture
|
|
||||||
* to animation the leaving view off the screen.
|
|
||||||
*
|
|
||||||
* 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
|
|
||||||
* or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
|
|
||||||
*
|
|
||||||
* 3. The entering view is an ion-router-outlet containing a page
|
|
||||||
* matching the current route and that hasn't already transitioned in.
|
|
||||||
*
|
|
||||||
* This should only happen when navigating directly to a nested router outlet
|
|
||||||
* route or on an initial page load (i.e. refreshing). In cases when loading
|
|
||||||
* /tabs/tab-1, we need to transition the /tabs page element into the view.
|
|
||||||
*/
|
|
||||||
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem);
|
|
||||||
} else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
|
|
||||||
// If we have a leavingView but no entering view/route, we are probably leaving to
|
|
||||||
// another outlet, so hide this leavingView. We do it in a timeout to give time for a
|
|
||||||
// transition to finish.
|
|
||||||
// setTimeout(() => {
|
|
||||||
if (leavingViewItem.ionPageElement) {
|
|
||||||
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
|
||||||
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
|
||||||
}
|
|
||||||
// }, 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.forceUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!enteringViewItem.mount) {
|
||||||
|
/**
|
||||||
|
* If the entering view item is not mounted,
|
||||||
|
* that means it has not been rendered yet.
|
||||||
|
* We need to mount it and then finish the transition
|
||||||
|
* after the React component has mounted.
|
||||||
|
*/
|
||||||
|
enteringViewItem.mount = true;
|
||||||
|
enteringViewItem.registerCallback = () => {
|
||||||
|
this.handlePageTransition();
|
||||||
|
if (enteringViewItem) {
|
||||||
|
enteringViewItem.registerCallback = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* If the entering view item has already mounted,
|
||||||
|
* that means the page reference should already
|
||||||
|
* exist and we can transition immediately.
|
||||||
|
*/
|
||||||
|
this.handlePageTransition();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
components: this.context.getChildrenToRender(id, this.ionRouterOutlet!, routeInfo),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerIonPage(page: HTMLElement, routeInfo: RouteInfo) {
|
async handlePageTransition() {
|
||||||
const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id);
|
const { id, props } = this;
|
||||||
if (foundView) {
|
const { routeInfo } = props;
|
||||||
const oldPageElement = foundView.ionPageElement;
|
const { prevRouteLastPathname, routeDirection, pushedByRoute, routeAnimation, routeAction, delta } = routeInfo;
|
||||||
foundView.ionPageElement = page;
|
|
||||||
foundView.ionRoute = true;
|
const enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, id)!;
|
||||||
|
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, id);
|
||||||
|
const enteringEl = enteringViewItem?.ionPageElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All views that can be transitioned to must have
|
||||||
|
* an `<ion-page>` element for transitions and lifecycle
|
||||||
|
* methods to work properly.
|
||||||
|
*/
|
||||||
|
if (enteringEl === undefined) {
|
||||||
|
console.warn(`[@ionic/react Warning]: The view you are trying to render for path ${routeInfo.pathname} does not have the required <IonPage> component. Transitions and lifecycle methods may not work as expected.
|
||||||
|
|
||||||
|
See https://ionicframework.com/docs/react/navigation#ionpage for more information.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enteringViewItem === leavingViewItem) return;
|
||||||
|
|
||||||
|
if (!leavingViewItem && prevRouteLastPathname) {
|
||||||
|
leavingViewItem = this.context.findViewItemByPathname(prevRouteLastPathname, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the entering view is already
|
||||||
|
* visible, then no transition is needed.
|
||||||
|
* This is most common when navigating
|
||||||
|
* from a tabs page to a non-tabs page
|
||||||
|
* and then back to the tabs page.
|
||||||
|
* Even when the tabs context navigated away,
|
||||||
|
* the inner tabs page was still active.
|
||||||
|
* This also avoids an issue where
|
||||||
|
* the previous tabs page is incorrectly
|
||||||
|
* unmounted since it would automatically
|
||||||
|
* unmount the previous view.
|
||||||
|
*
|
||||||
|
* This should also only apply to entering and
|
||||||
|
* leaving items in the same router outlet (i.e.
|
||||||
|
* Tab1 and Tab2), otherwise this will
|
||||||
|
* return early for swipe to go back when
|
||||||
|
* going from a non-tabs page to a tabs page.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
enteringEl !== undefined &&
|
||||||
|
isViewVisible(enteringEl) &&
|
||||||
|
leavingViewItem?.ionPageElement !== undefined &&
|
||||||
|
!isViewVisible(leavingViewItem.ionPageElement)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vue performs a fireLifeCycle for willEnter here on the enteringViewItem
|
||||||
|
|
||||||
|
if (leavingViewItem?.ionPageElement && enteringViewItem !== leavingViewItem) {
|
||||||
|
const enteringEl = enteringViewItem.ionPageElement;
|
||||||
|
const leavingEl = leavingViewItem.ionPageElement;
|
||||||
|
|
||||||
|
let animationBuilder = routeAnimation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* React 18 will unmount and remount IonPage
|
* If we are going back from a page that
|
||||||
* elements in development mode when using createRoot.
|
* was presented using a custom animation
|
||||||
* This can cause duplicate page transitions to occur.
|
* we should default to using that
|
||||||
|
* unless the developer explicitly
|
||||||
|
* provided another animation.
|
||||||
*/
|
*/
|
||||||
if (oldPageElement === page) {
|
const customAnimation = enteringViewItem!.routerAnimation;
|
||||||
return;
|
if (animationBuilder === undefined && routeDirection === 'back' && customAnimation !== undefined) {
|
||||||
|
animationBuilder = customAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
leavingViewItem.routerAnimation = animationBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The view should only be transitioned in the following cases:
|
||||||
|
* 1. Performing a replace or pop action, such as a swipe to go back gesture
|
||||||
|
* to animation the leaving view off the screen.
|
||||||
|
*
|
||||||
|
* 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
|
||||||
|
* or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
|
||||||
|
*
|
||||||
|
* 3. The entering view is an ion-router-outlet containing a page
|
||||||
|
* matching the current route and that hasn't already transitioned in.
|
||||||
|
*
|
||||||
|
* This should only happen when navigating directly to a nested router outlet
|
||||||
|
* route or on an initial page load (i.e. refreshing). In cases when loading
|
||||||
|
* /tabs/tab-1, we need to transition the /tabs page element into the view.
|
||||||
|
*/
|
||||||
|
this.transition(enteringEl!, leavingEl, routeDirection!, !!pushedByRoute, false, animationBuilder);
|
||||||
|
|
||||||
|
leavingEl.classList.add('ion-page-hidden');
|
||||||
|
leavingEl.setAttribute('aria-hidden', 'true');
|
||||||
|
|
||||||
|
const usingLinearNavigation = this.context.size() === 1;
|
||||||
|
|
||||||
|
if (routeAction === 'replace') {
|
||||||
|
leavingViewItem.mount = false;
|
||||||
|
leavingViewItem.ionPageElement = undefined;
|
||||||
|
leavingViewItem.ionRoute = false;
|
||||||
|
} else if (!(routeAction === 'push' && routeDirection === 'forward')) {
|
||||||
|
const shouldLeavingViewBeRemoved =
|
||||||
|
routeDirection !== 'none' && leavingViewItem && enteringViewItem !== leavingViewItem;
|
||||||
|
if (shouldLeavingViewBeRemoved) {
|
||||||
|
leavingViewItem.mount = false;
|
||||||
|
leavingViewItem.ionPageElement = undefined;
|
||||||
|
leavingViewItem.ionRoute = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usingLinearNavigation) {
|
||||||
|
this.context.unmountLeavingViews(id, enteringViewItem, delta);
|
||||||
|
}
|
||||||
|
} else if (usingLinearNavigation) {
|
||||||
|
this.context.mountIntermediaryViews(id, leavingViewItem, delta);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* If there is no leaving element, just show
|
||||||
|
* the entering element. Wrap it in an raf
|
||||||
|
* in case IonContent's fullscreen callback
|
||||||
|
* is running. Otherwise we'd have a flicker.
|
||||||
|
*/
|
||||||
|
requestAnimationFrame(() => enteringEl?.classList.remove('ion-page-invisible'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// this.forceUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// async handlePageTransition(routeInfo: RouteInfo) {
|
||||||
|
// if (!this.routerOutletElement || !this.routerOutletElement.commit) {
|
||||||
|
// /**
|
||||||
|
// * The route outlet has not mounted yet. We need to wait for it to render
|
||||||
|
// * before we can transition the page.
|
||||||
|
// *
|
||||||
|
// * Set a flag to indicate that we should transition the page after
|
||||||
|
// * the component has updated.
|
||||||
|
// */
|
||||||
|
// this.pendingPageTransition = true;
|
||||||
|
// } else {
|
||||||
|
// console.log('handling page transition...');
|
||||||
|
|
||||||
|
// let enteringViewItem = this.context.findViewItemByPathname(routeInfo.pathname, this.id);
|
||||||
|
// let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
|
||||||
|
|
||||||
|
// if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
|
||||||
|
// leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Check if leavingViewItem should be unmounted
|
||||||
|
// if (leavingViewItem) {
|
||||||
|
// if (routeInfo.routeAction === 'replace') {
|
||||||
|
// leavingViewItem.mount = false;
|
||||||
|
// } else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) {
|
||||||
|
// if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) {
|
||||||
|
// leavingViewItem.mount = false;
|
||||||
|
// }
|
||||||
|
// } else if (routeInfo.routeOptions?.unmount) {
|
||||||
|
// leavingViewItem.mount = false;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
|
||||||
|
|
||||||
|
// if (enteringViewItem) {
|
||||||
|
// enteringViewItem.reactElement = enteringRoute;
|
||||||
|
// } else if (enteringRoute) {
|
||||||
|
// enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
|
||||||
|
// this.context.addViewItem(enteringViewItem);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (enteringViewItem && enteringViewItem.ionPageElement) {
|
||||||
|
// /**
|
||||||
|
// * If the entering view item is the same as the leaving view item,
|
||||||
|
// * then we don't need to transition.
|
||||||
|
// */
|
||||||
|
// if (enteringViewItem === leavingViewItem) {
|
||||||
|
// /**
|
||||||
|
// * If the entering view item is the same as the leaving view item,
|
||||||
|
// * we are either transitioning using parameterized routes to the same view
|
||||||
|
// * or a parent router outlet is re-rendering as a result of React props changing.
|
||||||
|
// *
|
||||||
|
// * If the route data does not match the current path, the parent router outlet
|
||||||
|
// * is attempting to transition and we cancel the operation.
|
||||||
|
// */
|
||||||
|
// if (enteringViewItem.routeData.match.url !== routeInfo.pathname) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * If there isn't a leaving view item, but the route info indicates
|
||||||
|
// * that the user has routed from a previous path, then we need
|
||||||
|
// * to find the leaving view item to transition between.
|
||||||
|
// */
|
||||||
|
// if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
|
||||||
|
// leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * If the entering view is already visible and the leaving view is not, the transition does not need to occur.
|
||||||
|
// */
|
||||||
|
// if (
|
||||||
|
// isViewVisible(enteringViewItem.ionPageElement) &&
|
||||||
|
// leavingViewItem !== undefined &&
|
||||||
|
// !isViewVisible(leavingViewItem.ionPageElement!)
|
||||||
|
// ) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * The view should only be transitioned in the following cases:
|
||||||
|
// * 1. Performing a replace or pop action, such as a swipe to go back gesture
|
||||||
|
// * to animation the leaving view off the screen.
|
||||||
|
// *
|
||||||
|
// * 2. Navigating between top-level router outlets, such as /page-1 to /page-2;
|
||||||
|
// * or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2.
|
||||||
|
// *
|
||||||
|
// * 3. The entering view is an ion-router-outlet containing a page
|
||||||
|
// * matching the current route and that hasn't already transitioned in.
|
||||||
|
// *
|
||||||
|
// * This should only happen when navigating directly to a nested router outlet
|
||||||
|
// * route or on an initial page load (i.e. refreshing). In cases when loading
|
||||||
|
// * /tabs/tab-1, we need to transition the /tabs page element into the view.
|
||||||
|
// */
|
||||||
|
// this.transitionPage(routeInfo, enteringViewItem, leavingViewItem);
|
||||||
|
// } else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
|
||||||
|
// // If we have a leavingView but no entering view/route, we are probably leaving to
|
||||||
|
// // another outlet, so hide this leavingView. We do it in a timeout to give time for a
|
||||||
|
// // transition to finish.
|
||||||
|
// // setTimeout(() => {
|
||||||
|
// if (leavingViewItem.ionPageElement) {
|
||||||
|
// leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
||||||
|
// leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
||||||
|
// }
|
||||||
|
// // }, 250);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// this.forceUpdate();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
registerIonPage(routeInfo: RouteInfo, ionPageEl: HTMLElement) {
|
||||||
|
const { id } = this;
|
||||||
|
const viewItem = this.context.findViewItemByRouteInfo(routeInfo, id)!;
|
||||||
|
const oldIonPageEl = viewItem.ionPageElement;
|
||||||
|
|
||||||
|
this.context.registerIonPage(viewItem, ionPageEl);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If there is a registerCallback,
|
||||||
|
* then this component is being registered
|
||||||
|
* as a result of a navigation change.
|
||||||
|
*/
|
||||||
|
if (viewItem.registerCallback) {
|
||||||
|
/**
|
||||||
|
* Page should be hidden initially
|
||||||
|
* to avoid flickering.
|
||||||
|
*/
|
||||||
|
ionPageEl.classList.add('ion-page-invisible');
|
||||||
|
viewItem.registerCallback();
|
||||||
|
} else if (oldIonPageEl && !oldIonPageEl.classList.contains('ion-page-invisible')) {
|
||||||
|
/**
|
||||||
|
* If there is no registerCallback, then
|
||||||
|
* this component is likely being re-registered
|
||||||
|
* as a result of a hot module replacement.
|
||||||
|
* We need to see if the oldIonPageEl has
|
||||||
|
* .ion-page-invisible. If it does not then we
|
||||||
|
* need to remove it from the new ionPageEl otherwise
|
||||||
|
* the page will be hidden when it is replaced.
|
||||||
|
*/
|
||||||
|
ionPageEl.classList.remove('ion-page-invisible');
|
||||||
}
|
}
|
||||||
this.handlePageTransition(routeInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
|
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
|
||||||
@@ -247,24 +465,52 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onStart = async () => {
|
const onStart = async () => {
|
||||||
|
const { id } = this;
|
||||||
const { routeInfo } = this.props;
|
const { routeInfo } = this.props;
|
||||||
|
let { routeAnimation: animationBuilder } = routeInfo;
|
||||||
|
|
||||||
const propsToUse =
|
// const propsToUse =
|
||||||
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
|
// this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
|
||||||
? this.prevProps.routeInfo
|
// ? this.prevProps.routeInfo
|
||||||
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
|
// : ({ pathname: routeInfo.pushedByRoute || '' } as any);
|
||||||
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
|
|
||||||
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
|
||||||
|
|
||||||
/**
|
const enteringViewItem = this.context.findViewItemByRouteInfo(
|
||||||
* When the gesture starts, kick off
|
{ pathname: routeInfo.pushedByRoute || '' } as RouteInfo,
|
||||||
* a transition that is controlled
|
id,
|
||||||
* via a swipe gesture.
|
false
|
||||||
*/
|
);
|
||||||
if (enteringViewItem && leavingViewItem) {
|
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, id, false);
|
||||||
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
|
|
||||||
|
if (leavingViewItem) {
|
||||||
|
const enteringEl = enteringViewItem?.ionPageElement;
|
||||||
|
const leavingEl = leavingViewItem?.ionPageElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If we are going back from a page that
|
||||||
|
* was presented using a custom animation
|
||||||
|
* we should default to using that
|
||||||
|
* unless the developer explicitly
|
||||||
|
* provided another animation.
|
||||||
|
*/
|
||||||
|
const customAnimation = enteringViewItem!.routerAnimation;
|
||||||
|
if (animationBuilder === undefined && customAnimation !== undefined) {
|
||||||
|
animationBuilder = customAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
|
leavingViewItem.routerAnimation = animationBuilder;
|
||||||
|
|
||||||
|
await this.transition(enteringEl!, leavingEl!, 'back', this.context.canGoBack(), true, animationBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * When the gesture starts, kick off
|
||||||
|
// * a transition that is controlled
|
||||||
|
// * via a swipe gesture.
|
||||||
|
// */
|
||||||
|
// if (enteringViewItem && leavingViewItem) {
|
||||||
|
// await this.transition(enteringViewItem.ionPageElement!, leavingViewItem.ionPageElement!, 'back', true, true);
|
||||||
|
// }
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
const onEnd = (shouldContinue: boolean) => {
|
const onEnd = (shouldContinue: boolean) => {
|
||||||
@@ -311,82 +557,103 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async transitionPage(
|
async transition(
|
||||||
routeInfo: RouteInfo,
|
enteringEl: HTMLElement,
|
||||||
enteringViewItem: ViewItem,
|
leavingEl: HTMLElement,
|
||||||
leavingViewItem?: ViewItem,
|
direction: RouterDirection,
|
||||||
direction?: 'forward' | 'back',
|
showGoBack: boolean,
|
||||||
progressAnimation = false
|
progressAnimation: boolean,
|
||||||
|
animationBuilder?: AnimationBuilder
|
||||||
) {
|
) {
|
||||||
const runCommit = async (enteringEl: HTMLElement, leavingEl?: HTMLElement) => {
|
console.log('transition', {
|
||||||
const skipTransition = this.skipTransition;
|
enteringEl,
|
||||||
|
leavingEl,
|
||||||
|
direction,
|
||||||
|
});
|
||||||
|
const { skipTransition, routerOutletElement } = this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the transition was handled
|
||||||
|
* via the swipe to go back gesture,
|
||||||
|
* then we do not want to perform
|
||||||
|
* another transition.
|
||||||
|
*
|
||||||
|
* We skip adding ion-page or ion-page-invisible
|
||||||
|
* because the entering view already exists in the DOM.
|
||||||
|
* If we added the classes, there would be a flicker where
|
||||||
|
* the view would be briefly hidden.
|
||||||
|
*/
|
||||||
|
if (skipTransition) {
|
||||||
/**
|
/**
|
||||||
* If the transition was handled
|
* We need to reset skipTransition before
|
||||||
* via the swipe to go back gesture,
|
* we call routerOutlet.commit otherwise
|
||||||
* then we do not want to perform
|
* the transition triggered by the swipe
|
||||||
* another transition.
|
* to go back gesture would reset it. In
|
||||||
*
|
* that case you would see a duplicate
|
||||||
* We skip adding ion-page or ion-page-invisible
|
* transition triggered by handlePageTransition
|
||||||
* because the entering view already exists in the DOM.
|
* in componentDidUpdate.
|
||||||
* If we added the classes, there would be a flicker where
|
|
||||||
* the view would be briefly hidden.
|
|
||||||
*/
|
*/
|
||||||
if (skipTransition) {
|
this.skipTransition = false;
|
||||||
/**
|
|
||||||
* We need to reset skipTransition before
|
|
||||||
* we call routerOutlet.commit otherwise
|
|
||||||
* the transition triggered by the swipe
|
|
||||||
* to go back gesture would reset it. In
|
|
||||||
* that case you would see a duplicate
|
|
||||||
* transition triggered by handlePageTransition
|
|
||||||
* in componentDidUpdate.
|
|
||||||
*/
|
|
||||||
this.skipTransition = false;
|
|
||||||
} else {
|
|
||||||
enteringEl.classList.add('ion-page');
|
|
||||||
enteringEl.classList.add('ion-page-invisible');
|
|
||||||
}
|
|
||||||
|
|
||||||
await routerOutlet.commit(enteringEl, leavingEl, {
|
return Promise.resolve(false);
|
||||||
duration: skipTransition || directionToUse === undefined ? 0 : undefined,
|
|
||||||
direction: directionToUse,
|
|
||||||
showGoBack: !!routeInfo.pushedByRoute,
|
|
||||||
progressAnimation,
|
|
||||||
animationBuilder: routeInfo.routeAnimation,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const routerOutlet = this.routerOutletElement!;
|
|
||||||
|
|
||||||
const routeInfoFallbackDirection =
|
|
||||||
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
|
|
||||||
const directionToUse = direction ?? routeInfoFallbackDirection;
|
|
||||||
|
|
||||||
if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
|
|
||||||
if (leavingViewItem && leavingViewItem.ionPageElement && enteringViewItem === leavingViewItem) {
|
|
||||||
// If a page is transitioning to another version of itself
|
|
||||||
// we clone it so we can have an animation to show
|
|
||||||
|
|
||||||
const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname, true);
|
|
||||||
if (match) {
|
|
||||||
const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
|
|
||||||
if (newLeavingElement) {
|
|
||||||
this.routerOutletElement.appendChild(newLeavingElement);
|
|
||||||
await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
|
|
||||||
this.routerOutletElement.removeChild(newLeavingElement);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await runCommit(enteringViewItem.ionPageElement, undefined);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
|
|
||||||
if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
|
|
||||||
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
|
||||||
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enteringEl === leavingEl) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
enteringEl.classList.add('ion-page-invisible');
|
||||||
|
|
||||||
|
const hasRootDirection = direction === undefined || direction === 'root' || direction === 'none';
|
||||||
|
const result = await routerOutletElement!.commit(enteringEl, leavingEl, {
|
||||||
|
/**
|
||||||
|
* replace operations result in a direction of none.
|
||||||
|
* These typically do not have need animations, so we set
|
||||||
|
* the duration to 0. However, if a developer explicitly
|
||||||
|
* passes an animationBuilder, we should assume that
|
||||||
|
* they want an animation to be played even
|
||||||
|
* though it is a replace operation.
|
||||||
|
*/
|
||||||
|
duration: hasRootDirection && animationBuilder === undefined ? 0 : undefined,
|
||||||
|
direction: direction as any, // TODO none isn't a valid direction, investigate
|
||||||
|
showGoBack,
|
||||||
|
progressAnimation,
|
||||||
|
animationBuilder,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const routerOutlet = this.routerOutletElement!;
|
||||||
|
|
||||||
|
// const routeInfoFallbackDirection =
|
||||||
|
// routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
|
||||||
|
// const directionToUse = direction ?? routeInfoFallbackDirection;
|
||||||
|
|
||||||
|
// if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
|
||||||
|
// if (leavingViewItem && leavingViewItem.ionPageElement && enteringViewItem === leavingViewItem) {
|
||||||
|
// // If a page is transitioning to another version of itself
|
||||||
|
// // we clone it so we can have an animation to show
|
||||||
|
|
||||||
|
// const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname, true);
|
||||||
|
// if (match) {
|
||||||
|
// const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
|
||||||
|
// if (newLeavingElement) {
|
||||||
|
// this.routerOutletElement.appendChild(newLeavingElement);
|
||||||
|
// await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
|
||||||
|
// this.routerOutletElement.removeChild(newLeavingElement);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// await runCommit(enteringViewItem.ionPageElement, undefined);
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
|
||||||
|
// if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
|
||||||
|
// leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
||||||
|
// leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@@ -394,10 +661,6 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
|
const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
|
||||||
this.ionRouterOutlet = ionRouterOutlet;
|
this.ionRouterOutlet = ionRouterOutlet;
|
||||||
|
|
||||||
const components = this.context.getChildrenToRender(this.id, this.ionRouterOutlet, this.props.routeInfo, () => {
|
|
||||||
this.forceUpdate();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StackContext.Provider value={this.stackContextValue}>
|
<StackContext.Provider value={this.stackContextValue}>
|
||||||
{React.cloneElement(
|
{React.cloneElement(
|
||||||
@@ -410,14 +673,17 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
if (ionRouterOutlet.props.forwardedRef) {
|
if (ionRouterOutlet.props.forwardedRef) {
|
||||||
ionRouterOutlet.props.forwardedRef.current = node;
|
ionRouterOutlet.props.forwardedRef.current = node;
|
||||||
}
|
}
|
||||||
this.routerOutletElement = node;
|
if (node) {
|
||||||
|
this.routerOutletElement = node;
|
||||||
|
console.log('assigned router outlet element node...', node);
|
||||||
|
}
|
||||||
const { ref } = ionRouterOutlet as any;
|
const { ref } = ionRouterOutlet as any;
|
||||||
if (typeof ref === 'function') {
|
if (typeof ref === 'function') {
|
||||||
ref(node);
|
ref(node);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components
|
this.state.components
|
||||||
)}
|
)}
|
||||||
</StackContext.Provider>
|
</StackContext.Provider>
|
||||||
);
|
);
|
||||||
@@ -458,13 +724,13 @@ function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
|
|||||||
return matchedNode;
|
return matchedNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function matchComponent(node: React.ReactElement, pathname: string, forceExact?: boolean) {
|
// function matchComponent(node: React.ReactElement, pathname: string, forceExact?: boolean) {
|
||||||
const matchProps = {
|
// const matchProps = {
|
||||||
exact: forceExact ? true : node.props.exact,
|
// exact: forceExact ? true : node.props.exact,
|
||||||
path: node.props.path || node.props.from,
|
// path: node.props.path || node.props.from,
|
||||||
component: node.props.component,
|
// component: node.props.component,
|
||||||
};
|
// };
|
||||||
const match = matchPath(pathname, matchProps);
|
// const match = matchPath(pathname, matchProps);
|
||||||
|
|
||||||
return match;
|
// return match;
|
||||||
}
|
// }
|
||||||
|
|||||||
65
packages/react-router/src/ReactRouter/utils/matchPath.ts
Normal file
65
packages/react-router/src/ReactRouter/utils/matchPath.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { matchPath as reactRouterMatchPath } from 'react-router';
|
||||||
|
|
||||||
|
interface MatchPathOptions {
|
||||||
|
/**
|
||||||
|
* The pathname to match against.
|
||||||
|
*/
|
||||||
|
pathname: string;
|
||||||
|
/**
|
||||||
|
* The props to match against, they are identical to the matching props `Route` accepts.
|
||||||
|
*/
|
||||||
|
componentProps: {
|
||||||
|
path?: string;
|
||||||
|
from?: string;
|
||||||
|
component?: any;
|
||||||
|
exact?: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://v5.reactrouter.com/web/api/matchPath
|
||||||
|
*/
|
||||||
|
export const matchPath = ({ pathname, componentProps }: MatchPathOptions): false | ReturnType<typeof reactRouterMatchPath> => {
|
||||||
|
const { exact, component } = componentProps;
|
||||||
|
|
||||||
|
const path = componentProps.path || componentProps.from;
|
||||||
|
/***
|
||||||
|
* The props to match against, they are identical
|
||||||
|
* to the matching props `Route` accepts. It could also be a string
|
||||||
|
* or an array of strings as shortcut for `{ path }`.
|
||||||
|
*/
|
||||||
|
const matchProps = {
|
||||||
|
exact,
|
||||||
|
path,
|
||||||
|
component
|
||||||
|
};
|
||||||
|
|
||||||
|
const match = reactRouterMatchPath(pathname, matchProps);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasParameter = match.path.includes(':');
|
||||||
|
if (hasParameter) {
|
||||||
|
console.log('the match path has a parameter!!', {
|
||||||
|
pathname,
|
||||||
|
url: match.url,
|
||||||
|
match
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (hasParameter && match.url.includes(':')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasParameter && pathname !== match.url) {
|
||||||
|
console.log('discarding the match because it has a path parameter', {
|
||||||
|
pathname,
|
||||||
|
path,
|
||||||
|
match
|
||||||
|
})
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
@@ -8,7 +8,11 @@ export interface RouteInfo<TOptions = any> {
|
|||||||
lastPathname?: string;
|
lastPathname?: string;
|
||||||
prevRouteLastPathname?: string;
|
prevRouteLastPathname?: string;
|
||||||
routeAction?: RouteAction;
|
routeAction?: RouteAction;
|
||||||
|
// In Ionic Vue this is called routerDirection
|
||||||
|
// TODO we should align the naming
|
||||||
routeDirection?: RouterDirection;
|
routeDirection?: RouterDirection;
|
||||||
|
// In Ionic Vue this is called routerAnimation
|
||||||
|
// TODO we should align the naming
|
||||||
routeAnimation?: AnimationBuilder;
|
routeAnimation?: AnimationBuilder;
|
||||||
routeOptions?: TOptions;
|
routeOptions?: TOptions;
|
||||||
params?: { [key: string]: string | string[] };
|
params?: { [key: string]: string | string[] };
|
||||||
@@ -16,4 +20,5 @@ export interface RouteInfo<TOptions = any> {
|
|||||||
pathname: string;
|
pathname: string;
|
||||||
search: string;
|
search: string;
|
||||||
tab?: string;
|
tab?: string;
|
||||||
|
delta?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ export class OutletPageManager extends React.Component<OutletPageManagerProps> {
|
|||||||
if (!this.outletIsReady) {
|
if (!this.outletIsReady) {
|
||||||
componentOnReady(this.ionRouterOutlet, () => {
|
componentOnReady(this.ionRouterOutlet, () => {
|
||||||
this.outletIsReady = true;
|
this.outletIsReady = true;
|
||||||
this.context.registerIonPage(this.ionRouterOutlet!, this.props.routeInfo!);
|
console.log('registerIonPage from OutletPageManager');
|
||||||
|
this.context.registerIonPage(this.props.routeInfo!, this.ionRouterOutlet!);
|
||||||
|
// this.context.registerIonPage(this.ionRouterOutlet!, this.props.routeInfo!);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,11 @@ export class PageManager extends React.PureComponent<PageManagerProps> {
|
|||||||
if (this.context.isInOutlet()) {
|
if (this.context.isInOutlet()) {
|
||||||
this.ionPageElementRef.current.classList.add('ion-page-invisible');
|
this.ionPageElementRef.current.classList.add('ion-page-invisible');
|
||||||
}
|
}
|
||||||
this.context.registerIonPage(this.ionPageElementRef.current, this.props.routeInfo!);
|
console.log('registerIonPage from PageManager');
|
||||||
|
|
||||||
|
this.context.registerIonPage(this.props.routeInfo!, this.ionPageElementRef.current);
|
||||||
|
|
||||||
|
// this.context.registerIonPage(this.ionPageElementRef.current, this.props.routeInfo!);
|
||||||
this.ionPageElementRef.current.addEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
|
this.ionPageElementRef.current.addEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
|
||||||
this.ionPageElementRef.current.addEventListener('ionViewDidEnter', this.ionViewDidEnterHandler.bind(this));
|
this.ionPageElementRef.current.addEventListener('ionViewDidEnter', this.ionViewDidEnterHandler.bind(this));
|
||||||
this.ionPageElementRef.current.addEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler.bind(this));
|
this.ionPageElementRef.current.addEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler.bind(this));
|
||||||
|
|||||||
@@ -14,17 +14,52 @@ export interface RouteManagerContextState {
|
|||||||
routeInfo: RouteInfo,
|
routeInfo: RouteInfo,
|
||||||
page?: HTMLElement
|
page?: HTMLElement
|
||||||
) => ViewItem;
|
) => ViewItem;
|
||||||
findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
|
findViewItemByPathname(pathname: string, outletId?: string, forceExact?: boolean): ViewItem | undefined;
|
||||||
findLeavingViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
|
findLeavingViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
|
||||||
findViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) => ViewItem | undefined;
|
findViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) => ViewItem | undefined;
|
||||||
getChildrenToRender: (
|
getChildrenToRender: (
|
||||||
outletId: string,
|
outletId: string,
|
||||||
ionRouterOutlet: React.ReactElement,
|
ionRouterOutlet: React.ReactElement,
|
||||||
routeInfo: RouteInfo,
|
routeInfo: RouteInfo
|
||||||
reRender: () => void
|
|
||||||
) => React.ReactNode[];
|
) => React.ReactNode[];
|
||||||
goBack: () => void;
|
goBack: () => void;
|
||||||
|
/**
|
||||||
|
* Returns the number of active stacks.
|
||||||
|
* This is useful for determining if an app
|
||||||
|
* is using linear navigation only or non-linear
|
||||||
|
* navigation. Multiple stacks indicate an app
|
||||||
|
* is using non-linear navigation.
|
||||||
|
*/
|
||||||
|
size: () => number;
|
||||||
|
/**
|
||||||
|
* When navigating backwards, we need to clean up and
|
||||||
|
* leaving pages so that they are re-created if
|
||||||
|
* we ever navigate back to them. This is especially
|
||||||
|
* important when using router.go and stepping back
|
||||||
|
* multiple pages at a time.
|
||||||
|
*/
|
||||||
|
unmountLeavingViews: (outletId: string, viewItem: ViewItem, delta?: number) => void;
|
||||||
|
/**
|
||||||
|
* When navigating forward it is possible for
|
||||||
|
* developers to step forward over multiple views.
|
||||||
|
* The intermediary views need to be remounted so that
|
||||||
|
* swipe to go back works properly.
|
||||||
|
* We need to account for the delta value here too because
|
||||||
|
* we do not want to remount an unrelated view.
|
||||||
|
* Example:
|
||||||
|
* /home --> /page2 --> router.back() --> /page3
|
||||||
|
* Going to /page3 would remount /page2 since we do
|
||||||
|
* not prune /page2 from the stack. However, /page2
|
||||||
|
* needs to remain in the stack.
|
||||||
|
* Example:
|
||||||
|
* /home --> /page2 --> /page3 --> router.go(-2) --> router.go(2)
|
||||||
|
* We would end up on /page3, but users need to be able to swipe
|
||||||
|
* to go back to /page2 and /home, so we need both pages mounted
|
||||||
|
* in the DOM.
|
||||||
|
*/
|
||||||
|
mountIntermediaryViews: (outletId: string, viewItem: ViewItem, delta?: number) => void;
|
||||||
unMountViewItem: (viewItem: ViewItem) => void;
|
unMountViewItem: (viewItem: ViewItem) => void;
|
||||||
|
registerIonPage: (viewItem: ViewItem, ionPage: HTMLElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(FW-2959): types
|
// TODO(FW-2959): types
|
||||||
@@ -36,7 +71,11 @@ export const RouteManagerContext = /*@__PURE__*/ React.createContext<RouteManage
|
|||||||
findViewItemByPathname: () => undefined,
|
findViewItemByPathname: () => undefined,
|
||||||
findLeavingViewItemByRouteInfo: () => undefined,
|
findLeavingViewItemByRouteInfo: () => undefined,
|
||||||
findViewItemByRouteInfo: () => undefined,
|
findViewItemByRouteInfo: () => undefined,
|
||||||
getChildrenToRender: () => undefined as any,
|
getChildrenToRender: () => [],
|
||||||
goBack: () => undefined,
|
goBack: () => undefined,
|
||||||
|
size: () => 0,
|
||||||
|
unmountLeavingViews: () => undefined,
|
||||||
|
mountIntermediaryViews: () => undefined,
|
||||||
unMountViewItem: () => undefined,
|
unMountViewItem: () => undefined,
|
||||||
|
registerIonPage: () => undefined
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from 'react';
|
|||||||
import type { RouteInfo } from '../models/RouteInfo';
|
import type { RouteInfo } from '../models/RouteInfo';
|
||||||
|
|
||||||
export interface StackContextState {
|
export interface StackContextState {
|
||||||
registerIonPage: (page: HTMLElement, routeInfo: RouteInfo) => void;
|
registerIonPage: (routeInfo: RouteInfo, page: HTMLElement) => void;
|
||||||
isInOutlet: () => boolean;
|
isInOutlet: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import type { AnimationBuilder } from '@ionic/core';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
|
|
||||||
export interface ViewItem<T = any> {
|
export interface ViewItem<T = any> {
|
||||||
@@ -10,4 +11,9 @@ export interface ViewItem<T = any> {
|
|||||||
transitionHtml?: string;
|
transitionHtml?: string;
|
||||||
outletId: string;
|
outletId: string;
|
||||||
disableIonPageManagement?: boolean;
|
disableIonPageManagement?: boolean;
|
||||||
|
routerAnimation?: AnimationBuilder;
|
||||||
|
/**
|
||||||
|
* Callback function when the view item is registered.
|
||||||
|
*/
|
||||||
|
registerCallback?: () => void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,69 @@ export abstract class ViewStacks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When navigating backwards, we need to clean up and
|
||||||
|
* leaving pages so that they are re-created if
|
||||||
|
* we ever navigate back to them. This is especially
|
||||||
|
* important when using router.go and stepping back
|
||||||
|
* multiple pages at a time.
|
||||||
|
*/
|
||||||
|
unmountLeavingViews(outletId: string, viewItem: ViewItem, delta = 1) {
|
||||||
|
const viewStack = this.viewStacks[outletId];
|
||||||
|
if (!viewStack) return;
|
||||||
|
|
||||||
|
const startIndex = viewStack.findIndex(v => v === viewItem);
|
||||||
|
|
||||||
|
for (let i = startIndex + 1; i < startIndex - delta; i++) {
|
||||||
|
const viewItem = viewStack[1];
|
||||||
|
viewItem.mount = false;
|
||||||
|
viewItem.ionPageElement = undefined;
|
||||||
|
viewItem.ionRoute = false;
|
||||||
|
// This is Vue specific - TODO find if there is a React equivalent needed
|
||||||
|
// viewItem.matchedRoute.instances = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When navigating forward it is possible for
|
||||||
|
* developers to step forward over multiple views.
|
||||||
|
* The intermediary views need to be remounted so that
|
||||||
|
* swipe to go back works properly.
|
||||||
|
* We need to account for the delta value here too because
|
||||||
|
* we do not want to remount an unrelated view.
|
||||||
|
* Example:
|
||||||
|
* /home --> /page2 --> router.back() --> /page3
|
||||||
|
* Going to /page3 would remount /page2 since we do
|
||||||
|
* not prune /page2 from the stack. However, /page2
|
||||||
|
* needs to remain in the stack.
|
||||||
|
* Example:
|
||||||
|
* /home --> /page2 --> /page3 --> router.go(-2) --> router.go(2)
|
||||||
|
* We would end up on /page3, but users need to be able to swipe
|
||||||
|
* to go back to /page2 and /home, so we need both pages mounted
|
||||||
|
* in the DOM.
|
||||||
|
*/
|
||||||
|
mountIntermediaryViews(outletId: string, viewItem: ViewItem, delta = 1) {
|
||||||
|
const viewStack = this.viewStacks[outletId];
|
||||||
|
if (!viewStack) return;
|
||||||
|
|
||||||
|
const startIndex = viewStack.findIndex(v => v === viewItem);
|
||||||
|
|
||||||
|
for (let i = startIndex + 1; i < startIndex + delta; i++) {
|
||||||
|
viewStack[i].mount = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of active stacks.
|
||||||
|
* This is useful for determining if an app
|
||||||
|
* is using linear navigation only or non-linear
|
||||||
|
* navigation. Multiple stacks indicate an app
|
||||||
|
* is using non-linear navigation.
|
||||||
|
*/
|
||||||
|
size() {
|
||||||
|
return Object.keys(this.viewStacks || {}).length;
|
||||||
|
}
|
||||||
|
|
||||||
protected getStackIds() {
|
protected getStackIds() {
|
||||||
return Object.keys(this.viewStacks);
|
return Object.keys(this.viewStacks);
|
||||||
}
|
}
|
||||||
@@ -63,7 +126,7 @@ export abstract class ViewStacks {
|
|||||||
routeInfo: RouteInfo,
|
routeInfo: RouteInfo,
|
||||||
page?: HTMLElement
|
page?: HTMLElement
|
||||||
): ViewItem;
|
): ViewItem;
|
||||||
abstract findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
|
abstract findViewItemByPathname(pathname: string, outletId?: string, forceExact?: boolean): ViewItem | undefined;
|
||||||
abstract findViewItemByRouteInfo(
|
abstract findViewItemByRouteInfo(
|
||||||
routeInfo: RouteInfo,
|
routeInfo: RouteInfo,
|
||||||
outletId?: string,
|
outletId?: string,
|
||||||
@@ -74,7 +137,5 @@ export abstract class ViewStacks {
|
|||||||
outletId: string,
|
outletId: string,
|
||||||
ionRouterOutlet: React.ReactElement,
|
ionRouterOutlet: React.ReactElement,
|
||||||
routeInfo: RouteInfo,
|
routeInfo: RouteInfo,
|
||||||
reRender: () => void,
|
|
||||||
setInTransition: () => void
|
|
||||||
): React.ReactNode[];
|
): React.ReactNode[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const createViewStacks = (router: Router) => {
|
|||||||
* Returns the number of active stacks.
|
* Returns the number of active stacks.
|
||||||
* This is useful for determining if an app
|
* This is useful for determining if an app
|
||||||
* is using linear navigation only or non-linear
|
* is using linear navigation only or non-linear
|
||||||
* navigation. Multiple stacks indiciate an app
|
* navigation. Multiple stacks indicate an app
|
||||||
* is using non-linear navigation.
|
* is using non-linear navigation.
|
||||||
*/
|
*/
|
||||||
const size = () => Object.keys(viewStacks).length;
|
const size = () => Object.keys(viewStacks).length;
|
||||||
|
|||||||
Reference in New Issue
Block a user