mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 01:03:03 +08:00
fix(react): swipe to go back gesture works on ios (#25563)
resolves #22342 Co-authored-by: masonicboom <masonicboom@users.noreply.github.com>
This commit is contained in:
@ -107,9 +107,10 @@ export class ReactRouterViewStack extends ViewStacks {
|
|||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
||||||
findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string) {
|
findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) {
|
||||||
const { viewItem, match } = this.findViewItemByPath(routeInfo.pathname, outletId);
|
const { viewItem, match } = this.findViewItemByPath(routeInfo.pathname, outletId);
|
||||||
if (viewItem && match) {
|
const shouldUpdateMatch = updateMatch === undefined || updateMatch === true;
|
||||||
|
if (shouldUpdateMatch && viewItem && match) {
|
||||||
viewItem.routeData.match = match;
|
viewItem.routeData.match = match;
|
||||||
}
|
}
|
||||||
return viewItem;
|
return viewItem;
|
||||||
|
@ -25,6 +25,8 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
context!: React.ContextType<typeof RouteManagerContext>;
|
context!: React.ContextType<typeof RouteManagerContext>;
|
||||||
ionRouterOutlet?: React.ReactElement;
|
ionRouterOutlet?: React.ReactElement;
|
||||||
routerOutletElement: HTMLIonRouterOutletElement | undefined;
|
routerOutletElement: HTMLIonRouterOutletElement | undefined;
|
||||||
|
prevProps?: StackManagerProps;
|
||||||
|
skipTransition: boolean;
|
||||||
|
|
||||||
stackContextValue: StackContextState = {
|
stackContextValue: StackContextState = {
|
||||||
registerIonPage: this.registerIonPage.bind(this),
|
registerIonPage: this.registerIonPage.bind(this),
|
||||||
@ -39,6 +41,8 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
this.transitionPage = this.transitionPage.bind(this);
|
this.transitionPage = this.transitionPage.bind(this);
|
||||||
this.handlePageTransition = this.handlePageTransition.bind(this);
|
this.handlePageTransition = this.handlePageTransition.bind(this);
|
||||||
this.id = generateId('routerOutlet');
|
this.id = generateId('routerOutlet');
|
||||||
|
this.prevProps = undefined;
|
||||||
|
this.skipTransition = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -50,7 +54,13 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps: StackManagerProps) {
|
componentDidUpdate(prevProps: StackManagerProps) {
|
||||||
if (this.props.routeInfo.pathname !== prevProps.routeInfo.pathname || this.pendingPageTransition) {
|
const { pathname } = this.props.routeInfo;
|
||||||
|
const { pathname: prevPathname } = prevProps.routeInfo;
|
||||||
|
|
||||||
|
if (pathname !== prevPathname) {
|
||||||
|
this.prevProps = prevProps;
|
||||||
|
this.handlePageTransition(this.props.routeInfo);
|
||||||
|
} else if (this.pendingPageTransition) {
|
||||||
this.handlePageTransition(this.props.routeInfo);
|
this.handlePageTransition(this.props.routeInfo);
|
||||||
this.pendingPageTransition = false;
|
this.pendingPageTransition = false;
|
||||||
}
|
}
|
||||||
@ -187,34 +197,151 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
const canStart = () => {
|
const canStart = () => {
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
|
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
|
||||||
if (swipeEnabled) {
|
if (!swipeEnabled) { return false; }
|
||||||
return this.context.canGoBack();
|
|
||||||
} else {
|
const { routeInfo } = this.props;
|
||||||
return false;
|
|
||||||
}
|
const propsToUse = (this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute) ? this.prevProps.routeInfo : { pathname: routeInfo.pushedByRoute || '' } as any;
|
||||||
|
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
!!enteringViewItem &&
|
||||||
|
/**
|
||||||
|
* The root url '/' is treated as
|
||||||
|
* the first view item (but is never mounted),
|
||||||
|
* so we do not want to swipe back to the
|
||||||
|
* root url.
|
||||||
|
*/
|
||||||
|
enteringViewItem.mount &&
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When on the first page (whatever view
|
||||||
|
* you land on after the root url) it
|
||||||
|
* is possible for findViewItemByRouteInfo to
|
||||||
|
* return the exact same view you are currently on.
|
||||||
|
* Make sure that we are not swiping back to the same
|
||||||
|
* instances of a view.
|
||||||
|
*/
|
||||||
|
enteringViewItem.routeData.match.path !== routeInfo.pathname
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onStart = () => {
|
const onStart = async () => {
|
||||||
this.context.goBack();
|
const { routeInfo } = this.props;
|
||||||
|
|
||||||
|
const propsToUse = (this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute) ? this.prevProps.routeInfo : { pathname: routeInfo.pushedByRoute || '' } as any;
|
||||||
|
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
|
||||||
|
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the gesture starts, kick off
|
||||||
|
* a transition that is controlled
|
||||||
|
* via a swipe gesture.
|
||||||
|
*/
|
||||||
|
if (enteringViewItem && leavingViewItem) {
|
||||||
|
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
const onEnd = (shouldContinue: boolean) => {
|
||||||
|
if (shouldContinue) {
|
||||||
|
this.skipTransition = true;
|
||||||
|
|
||||||
|
this.context.goBack();
|
||||||
|
} else {
|
||||||
|
/**
|
||||||
|
* In the event that the swipe
|
||||||
|
* gesture was aborted, we should
|
||||||
|
* re-hide the page that was going to enter.
|
||||||
|
*/
|
||||||
|
const { routeInfo } = this.props;
|
||||||
|
|
||||||
|
const propsToUse = (this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute) ? this.prevProps.routeInfo : { pathname: routeInfo.pushedByRoute || '' } as any;
|
||||||
|
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
|
||||||
|
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ionic React has a design defect where it
|
||||||
|
* a) Unmounts the leaving view item when using parameterized routes
|
||||||
|
* b) Considers the current view to be the entering view when using
|
||||||
|
* parameterized routes
|
||||||
|
*
|
||||||
|
* As a result, we should not hide the view item here
|
||||||
|
* as it will cause the current view to be hidden.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
enteringViewItem !== leavingViewItem &&
|
||||||
|
enteringViewItem?.ionPageElement !== undefined
|
||||||
|
) {
|
||||||
|
const { ionPageElement } = enteringViewItem;
|
||||||
|
ionPageElement.setAttribute('aria-hidden', 'true');
|
||||||
|
ionPageElement.classList.add('ion-page-hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
routerOutlet.swipeHandler = {
|
routerOutlet.swipeHandler = {
|
||||||
canStart,
|
canStart,
|
||||||
onStart,
|
onStart,
|
||||||
onEnd: (_shouldContinue) => true,
|
onEnd
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async transitionPage(
|
async transitionPage(
|
||||||
routeInfo: RouteInfo,
|
routeInfo: RouteInfo,
|
||||||
enteringViewItem: ViewItem,
|
enteringViewItem: ViewItem,
|
||||||
leavingViewItem?: ViewItem
|
leavingViewItem?: ViewItem,
|
||||||
|
direction?: 'forward' | 'back',
|
||||||
|
progressAnimation = false
|
||||||
) {
|
) {
|
||||||
|
const runCommit = async (enteringEl: HTMLElement, leavingEl?: HTMLElement) => {
|
||||||
|
const skipTransition = this.skipTransition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
/**
|
||||||
|
* 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, {
|
||||||
|
deepWait: true,
|
||||||
|
duration: skipTransition || directionToUse === undefined ? 0 : undefined,
|
||||||
|
direction: directionToUse,
|
||||||
|
showGoBack: !!routeInfo.pushedByRoute,
|
||||||
|
progressAnimation,
|
||||||
|
animationBuilder: routeInfo.routeAnimation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const routerOutlet = this.routerOutletElement!;
|
const routerOutlet = this.routerOutletElement!;
|
||||||
|
|
||||||
const direction =
|
const routeInfoFallbackDirection =
|
||||||
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root'
|
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root'
|
||||||
? undefined
|
? undefined
|
||||||
: routeInfo.routeDirection;
|
: routeInfo.routeDirection;
|
||||||
|
const directionToUse = direction ?? routeInfoFallbackDirection;
|
||||||
|
|
||||||
if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
|
if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
|
||||||
if (
|
if (
|
||||||
@ -238,26 +365,12 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
|
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
|
||||||
if (leavingViewItem && leavingViewItem.ionPageElement) {
|
if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
|
||||||
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
||||||
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runCommit(enteringEl: HTMLElement, leavingEl?: HTMLElement) {
|
|
||||||
enteringEl.classList.add('ion-page');
|
|
||||||
enteringEl.classList.add('ion-page-invisible');
|
|
||||||
|
|
||||||
await routerOutlet.commit(enteringEl, leavingEl, {
|
|
||||||
deepWait: true,
|
|
||||||
duration: direction === undefined ? 0 : undefined,
|
|
||||||
direction: direction as any,
|
|
||||||
showGoBack: !!routeInfo.pushedByRoute,
|
|
||||||
progressAnimation: false,
|
|
||||||
animationBuilder: routeInfo.routeAnimation,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -5,13 +5,139 @@ describe('Swipe To Go Back', () => {
|
|||||||
This spec tests that swipe to go back works
|
This spec tests that swipe to go back works
|
||||||
*/
|
*/
|
||||||
|
|
||||||
it('/swipe-to-go-back, ', () => {
|
it('should swipe and abort', () => {
|
||||||
cy.visit(`http://localhost:${port}/swipe-to-go-back`);
|
cy.visit(`http://localhost:${port}/swipe-to-go-back`);
|
||||||
cy.ionPageVisible('main');
|
cy.ionPageVisible('main');
|
||||||
|
|
||||||
cy.ionNav('ion-item', 'Details');
|
cy.ionNav('ion-item', 'Details');
|
||||||
cy.ionPageVisible('details');
|
cy.ionPageVisible('details');
|
||||||
cy.ionPageHidden('main');
|
cy.ionPageHidden('main');
|
||||||
cy.ionSwipeToGoBack(true);
|
|
||||||
|
cy.ionSwipeToGoBack(false, 'ion-router-outlet#swipe-to-go-back');
|
||||||
|
cy.ionPageVisible('details');
|
||||||
|
cy.ionPageHidden('main');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should swipe and go back', () => {
|
||||||
|
cy.visit(`http://localhost:${port}/swipe-to-go-back`);
|
||||||
|
cy.ionPageVisible('main');
|
||||||
|
|
||||||
|
cy.ionNav('ion-item', 'Details');
|
||||||
|
cy.ionPageVisible('details');
|
||||||
|
cy.ionPageHidden('main');
|
||||||
|
|
||||||
|
cy.ionSwipeToGoBack(true, 'ion-router-outlet#swipe-to-go-back');
|
||||||
cy.ionPageVisible('main');
|
cy.ionPageVisible('main');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should swipe and abort within a tab', () => {
|
||||||
|
cy.visit(`http://localhost:${port}/tabs/tab1`);
|
||||||
|
cy.ionPageVisible('tab1');
|
||||||
|
|
||||||
|
cy.get('#child-one').click();
|
||||||
|
cy.ionPageHidden('tab1');
|
||||||
|
cy.ionPageVisible('tab1child1');
|
||||||
|
|
||||||
|
cy.ionSwipeToGoBack(false, 'ion-tabs ion-router-outlet');
|
||||||
|
|
||||||
|
cy.ionPageHidden('tab1');
|
||||||
|
cy.ionPageVisible('tab1child1')
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should swipe and go back within a tab', () => {
|
||||||
|
cy.visit(`http://localhost:${port}/tabs/tab1`);
|
||||||
|
cy.ionPageVisible('tab1');
|
||||||
|
|
||||||
|
cy.get('#child-one').click();
|
||||||
|
cy.ionPageHidden('tab1');
|
||||||
|
cy.ionPageVisible('tab1child1');
|
||||||
|
|
||||||
|
cy.ionSwipeToGoBack(true, 'ion-tabs ion-router-outlet');
|
||||||
|
|
||||||
|
cy.ionPageVisible('tab1');
|
||||||
|
cy.ionPageDoesNotExist('tab1child1')
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should swipe and go back to correct tab after switching tabs', () => {
|
||||||
|
cy.visit(`http://localhost:${port}`);
|
||||||
|
cy.ionPageVisible('home');
|
||||||
|
|
||||||
|
cy.get('#go-to-tabs').click();
|
||||||
|
cy.ionPageHidden('home');
|
||||||
|
cy.ionPageVisible('tab1');
|
||||||
|
cy.ionPageVisible('tabs');
|
||||||
|
|
||||||
|
cy.get('#child-one').click();
|
||||||
|
cy.ionPageHidden('tab1');
|
||||||
|
cy.ionPageVisible('tab1child1');
|
||||||
|
|
||||||
|
cy.get('ion-tab-button#tab-button-tab2').click();
|
||||||
|
cy.ionPageVisible('tab2');
|
||||||
|
cy.ionPageHidden('tab1child1');
|
||||||
|
|
||||||
|
cy.get('ion-tab-button#tab-button-tab1').click();
|
||||||
|
cy.ionPageVisible('tab1child1');
|
||||||
|
cy.ionPageHidden('tab2');
|
||||||
|
|
||||||
|
cy.ionSwipeToGoBack(true, 'ion-tabs ion-router-outlet');
|
||||||
|
|
||||||
|
cy.ionPageVisible('tab1');
|
||||||
|
cy.ionPageDoesNotExist('tab1child1');
|
||||||
|
|
||||||
|
cy.ionSwipeToGoBack(true, 'ion-tabs ion-router-outlet');
|
||||||
|
cy.ionPageVisible('home');
|
||||||
|
cy.ionPageDoesNotExist('tabs');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be able to swipe back from child tab page after visiting', () => {
|
||||||
|
cy.visit(`http://localhost:${port}/tabs/tab1`);
|
||||||
|
cy.ionPageVisible('tab1');
|
||||||
|
|
||||||
|
cy.get('#child-one').click();
|
||||||
|
cy.ionPageHidden('tab1');
|
||||||
|
cy.ionPageVisible('tab1child1');
|
||||||
|
|
||||||
|
cy.get('#child-two').click();
|
||||||
|
cy.ionPageHidden('tab1child1');
|
||||||
|
cy.ionPageVisible('tab1child2');
|
||||||
|
|
||||||
|
cy.ionSwipeToGoBack(true, 'ion-tabs ion-router-outlet');
|
||||||
|
|
||||||
|
cy.ionPageDoesNotExist('tab1child2');
|
||||||
|
cy.ionPageVisible('tab1child1');
|
||||||
|
|
||||||
|
cy.ionSwipeToGoBack(true, 'ion-tabs ion-router-outlet');
|
||||||
|
|
||||||
|
cy.ionPageDoesNotExist('tab1child1');
|
||||||
|
cy.ionPageVisible('tab1');
|
||||||
|
|
||||||
|
cy.get('#child-one').click();
|
||||||
|
cy.ionPageHidden('tab1');
|
||||||
|
cy.ionPageVisible('tab1child1');
|
||||||
|
|
||||||
|
cy.ionSwipeToGoBack(true, 'ion-tabs ion-router-outlet');
|
||||||
|
|
||||||
|
cy.ionPageDoesNotExist('tab1child1');
|
||||||
|
cy.ionPageVisible('tab1');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not swipe to go back to the same view you are on', () => {
|
||||||
|
cy.visit(`http://localhost:${port}`);
|
||||||
|
cy.ionPageVisible('home');
|
||||||
|
|
||||||
|
cy.ionSwipeToGoBack(false);
|
||||||
|
cy.ionPageVisible('home');
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not hide a parameterized page when swiping and aborting', () => {
|
||||||
|
cy.visit(`http://localhost:${port}/params/0`);
|
||||||
|
cy.ionPageVisible('params-0');
|
||||||
|
|
||||||
|
cy.get('#next-page').click();
|
||||||
|
cy.ionPageVisible('params-1');
|
||||||
|
|
||||||
|
cy.ionSwipeToGoBack(false);
|
||||||
|
|
||||||
|
cy.ionPageVisible('params-1');
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
@ -102,7 +102,7 @@ Cypress.Commands.add('ionMenuNav', (contains) => {
|
|||||||
|
|
||||||
Cypress.Commands.add('ionTabClick', (tabText) => {
|
Cypress.Commands.add('ionTabClick', (tabText) => {
|
||||||
// TODO: figure out how to get rid of this wait. Switching tabs after a forward nav to a details page needs it
|
// TODO: figure out how to get rid of this wait. Switching tabs after a forward nav to a details page needs it
|
||||||
cy.wait(250);
|
cy.wait(500);
|
||||||
cy.contains('ion-tab-button', tabText).click({ force: true });
|
cy.contains('ion-tab-button', tabText).click({ force: true });
|
||||||
// cy.get('ion-tab-button.tab-selected').contains(tabText)
|
// cy.get('ion-tab-button.tab-selected').contains(tabText)
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IonApp, setupIonicReact } from '@ionic/react';
|
import { IonApp, setupIonicReact, IonRouterOutlet } from '@ionic/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Route } from 'react-router-dom';
|
import { Route } from 'react-router-dom';
|
||||||
|
|
||||||
@ -36,6 +36,7 @@ import Refs from './pages/refs/Refs';
|
|||||||
import DynamicIonpageClassnames from './pages/dynamic-ionpage-classnames/DynamicIonpageClassnames';
|
import DynamicIonpageClassnames from './pages/dynamic-ionpage-classnames/DynamicIonpageClassnames';
|
||||||
import Tabs from './pages/tabs/Tabs';
|
import Tabs from './pages/tabs/Tabs';
|
||||||
import TabsSecondary from './pages/tabs/TabsSecondary';
|
import TabsSecondary from './pages/tabs/TabsSecondary';
|
||||||
|
import Params from './pages/params/Params';
|
||||||
|
|
||||||
setupIonicReact();
|
setupIonicReact();
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ const App: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<IonApp>
|
<IonApp>
|
||||||
<IonReactRouter>
|
<IonReactRouter>
|
||||||
|
<IonRouterOutlet>
|
||||||
<Route path="/" component={Main} exact />
|
<Route path="/" component={Main} exact />
|
||||||
<Route path="/routing" component={Routing} />
|
<Route path="/routing" component={Routing} />
|
||||||
<Route path="/dynamic-routes" component={DynamicRoutes} />
|
<Route path="/dynamic-routes" component={DynamicRoutes} />
|
||||||
@ -58,6 +60,8 @@ const App: React.FC = () => {
|
|||||||
<Route path="/tabs" component={Tabs} />
|
<Route path="/tabs" component={Tabs} />
|
||||||
<Route path="/tabs-secondary" component={TabsSecondary} />
|
<Route path="/tabs-secondary" component={TabsSecondary} />
|
||||||
<Route path="/refs" component={Refs} />
|
<Route path="/refs" component={Refs} />
|
||||||
|
<Route path="/params/:id" component={Params} />
|
||||||
|
</IonRouterOutlet>
|
||||||
</IonReactRouter>
|
</IonReactRouter>
|
||||||
</IonApp>
|
</IonApp>
|
||||||
);
|
);
|
||||||
|
@ -14,7 +14,7 @@ interface MainProps {}
|
|||||||
|
|
||||||
const Main: React.FC<MainProps> = () => {
|
const Main: React.FC<MainProps> = () => {
|
||||||
return (
|
return (
|
||||||
<IonPage>
|
<IonPage data-pageid="home">
|
||||||
<IonHeader>
|
<IonHeader>
|
||||||
<IonToolbar>
|
<IonToolbar>
|
||||||
<IonTitle>Main</IonTitle>
|
<IonTitle>Main</IonTitle>
|
||||||
@ -58,6 +58,12 @@ const Main: React.FC<MainProps> = () => {
|
|||||||
<IonItem routerLink="/Refs">
|
<IonItem routerLink="/Refs">
|
||||||
<IonLabel>Refs</IonLabel>
|
<IonLabel>Refs</IonLabel>
|
||||||
</IonItem>
|
</IonItem>
|
||||||
|
<IonItem routerLink="/tabs" id="go-to-tabs">
|
||||||
|
<IonLabel>Tabs</IonLabel>
|
||||||
|
</IonItem>
|
||||||
|
<IonItem routerLink="/params/0">
|
||||||
|
<IonLabel>Params</IonLabel>
|
||||||
|
</IonItem>
|
||||||
</IonList>
|
</IonList>
|
||||||
</IonContent>
|
</IonContent>
|
||||||
</IonPage>
|
</IonPage>
|
||||||
|
41
packages/react-router/test-app/src/pages/params/Params.tsx
Normal file
41
packages/react-router/test-app/src/pages/params/Params.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
IonButtons,
|
||||||
|
IonBackButton,
|
||||||
|
IonButton,
|
||||||
|
IonContent,
|
||||||
|
IonHeader,
|
||||||
|
IonPage,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
|
import { RouteComponentProps } from 'react-router';
|
||||||
|
|
||||||
|
interface PageProps
|
||||||
|
extends RouteComponentProps<{
|
||||||
|
id: string;
|
||||||
|
}> {}
|
||||||
|
|
||||||
|
|
||||||
|
const Page: React.FC<PageProps> = ({ match }) => {
|
||||||
|
const parseID = parseInt(match.params.id);
|
||||||
|
return (
|
||||||
|
<IonPage data-pageid={'params-' + match.params.id }>
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle>Params { match.params.id }</IonTitle>
|
||||||
|
<IonButtons slot="start">
|
||||||
|
<IonBackButton />
|
||||||
|
</IonButtons>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
<IonContent>
|
||||||
|
<IonButton id="next-page" routerLink={'/params/' + (parseID + 1) } >Go to next param</IonButton>
|
||||||
|
<br />
|
||||||
|
Page ID: { match.params.id }
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
@ -22,11 +22,12 @@ interface TabsProps {}
|
|||||||
|
|
||||||
const Tabs: React.FC<TabsProps> = () => {
|
const Tabs: React.FC<TabsProps> = () => {
|
||||||
return (
|
return (
|
||||||
<IonTabs>
|
<IonTabs data-pageid="tabs">
|
||||||
<IonRouterOutlet id="tabs">
|
<IonRouterOutlet id="tabs">
|
||||||
<Route path="/tabs/tab1" component={Tab1} exact />
|
<Route path="/tabs/tab1" component={Tab1} exact />
|
||||||
<Route path="/tabs/tab2" component={Tab2} exact />
|
<Route path="/tabs/tab2" component={Tab2} exact />
|
||||||
<Route path="/tabs/tab1/child" component={Tab1Child1} exact />
|
<Route path="/tabs/tab1/child" component={Tab1Child1} exact />
|
||||||
|
<Route path="/tabs/tab1/child2" component={Tab1Child2} exact />
|
||||||
<Redirect from="/tabs" to="/tabs/tab1" exact />
|
<Redirect from="/tabs" to="/tabs/tab1" exact />
|
||||||
</IonRouterOutlet>
|
</IonRouterOutlet>
|
||||||
<IonTabBar slot="bottom">
|
<IonTabBar slot="bottom">
|
||||||
@ -71,7 +72,26 @@ const Tab1Child1 = () => {
|
|||||||
</IonToolbar>
|
</IonToolbar>
|
||||||
</IonHeader>
|
</IonHeader>
|
||||||
<IonContent>
|
<IonContent>
|
||||||
|
Tab 1 Child 1
|
||||||
|
<IonButton routerLink="/tabs/tab1/child2" id="child-two">Go to Tab1Child2</IonButton>
|
||||||
|
</IonContent>
|
||||||
|
</IonPage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Tab1Child2 = () => {
|
||||||
|
return (
|
||||||
|
<IonPage data-pageid="tab1child2">
|
||||||
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonButtons slot="start">
|
||||||
|
<IonBackButton />
|
||||||
|
</IonButtons>
|
||||||
|
<IonTitle>Tab1</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
<IonContent>
|
||||||
|
Tab 1 Child 2
|
||||||
</IonContent>
|
</IonContent>
|
||||||
</IonPage>
|
</IonPage>
|
||||||
);
|
);
|
||||||
|
@ -16,7 +16,7 @@ export interface RouteManagerContextState {
|
|||||||
) => ViewItem;
|
) => ViewItem;
|
||||||
findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
|
findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
|
||||||
findLeavingViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
|
findLeavingViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
|
||||||
findViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
|
findViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean) => ViewItem | undefined;
|
||||||
getChildrenToRender: (
|
getChildrenToRender: (
|
||||||
outletId: string,
|
outletId: string,
|
||||||
ionRouterOutlet: React.ReactElement,
|
ionRouterOutlet: React.ReactElement,
|
||||||
|
@ -65,7 +65,7 @@ export abstract class ViewStacks {
|
|||||||
page?: HTMLElement
|
page?: HTMLElement
|
||||||
): ViewItem;
|
): ViewItem;
|
||||||
abstract findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
|
abstract findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
|
||||||
abstract findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string): ViewItem | undefined;
|
abstract findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string, updateMatch?: boolean): ViewItem | undefined;
|
||||||
abstract findLeavingViewItemByRouteInfo(
|
abstract findLeavingViewItemByRouteInfo(
|
||||||
routeInfo: RouteInfo,
|
routeInfo: RouteInfo,
|
||||||
outletId?: string
|
outletId?: string
|
||||||
|
Reference in New Issue
Block a user