merge release-4.11.6

This commit is contained in:
Ely Lucas
2019-12-11 10:07:10 -07:00
committed by GitHub
22 changed files with 391 additions and 218 deletions

View File

@ -1,3 +1,19 @@
## [4.11.6](https://github.com/ionic-team/ionic/compare/v4.11.5...v4.11.6) (2019-12-11)
### Bug Fixes
* **react:** don't show back button when not appropriate ([684293d](https://github.com/ionic-team/ionic/commit/684293ddbf1ad4edce590d56f7ff66fcd6c817a5))
* **react:** first render performance improvements ([1c7d1e5](https://github.com/ionic-team/ionic/commit/1c7d1e5cf1ad7e53ebbee2566e8fa89f567f7fb5))
* **react:** fix refs for controllers, overlays, ionpage, and ionrouteroutlet, fixes [#19924](https://github.com/ionic-team/ionic/issues/19924) ([#20012](https://github.com/ionic-team/ionic/issues/20012)) ([eef55bb](https://github.com/ionic-team/ionic/commit/eef55bb0072a9e54b1fd7d1c8c69e7fd43b2a5c5))
* **react:** support for 'root' router direction, fixes [#19982](https://github.com/ionic-team/ionic/issues/19982) ([#20052](https://github.com/ionic-team/ionic/issues/20052)) ([e116712](https://github.com/ionic-team/ionic/commit/e1167122758b23221935e897bcd65839b75c59aa))
* **react:** support navigating to same page and route updates in IonRouterOutlet, fixes [#19891](https://github.com/ionic-team/ionic/issues/19891), [#19892](https://github.com/ionic-team/ionic/issues/19892), [#19986](https://github.com/ionic-team/ionic/issues/19986) ([f9bf8db](https://github.com/ionic-team/ionic/commit/f9bf8dbe6f952ee53b6b213a4c0d043d25f49b93))
### Upgrade Note
If you run into a "Property 'translate' is missing in type" error building after updating to 4.11.6, update your React Typings library to the latest:
npm i @types/react@latest @types/react-dom@latest
# [5.0.0-beta.1](https://github.com/ionic-team/ionic/compare/v5.0.0-beta.0...v5.0.0-beta.1) (2019-11-20) # [5.0.0-beta.1](https://github.com/ionic-team/ionic/compare/v5.0.0-beta.0...v5.0.0-beta.1) (2019-11-20)

View File

@ -50,7 +50,7 @@
"@ionic/core": "5.0.0-beta.1", "@ionic/core": "5.0.0-beta.1",
"@ionic/react": "5.0.0-beta.1", "@ionic/react": "5.0.0-beta.1",
"@types/jest": "^23.3.9", "@types/jest": "^23.3.9",
"@types/node": "12.6.9", "@types/node": "^12.12.14",
"@types/react": "^16.9.2", "@types/react": "^16.9.2",
"@types/react-dom": "^16.9.0", "@types/react-dom": "^16.9.0",
"@types/react-router": "^5.0.3", "@types/react-router": "^5.0.3",
@ -70,7 +70,7 @@
"tslint": "^5.20.0", "tslint": "^5.20.0",
"tslint-ionic-rules": "0.0.21", "tslint-ionic-rules": "0.0.21",
"tslint-react": "^4.1.0", "tslint-react": "^4.1.0",
"typescript": "3.5.3" "typescript": "^3.7.2"
}, },
"jest": { "jest": {
"preset": "ts-jest", "preset": "ts-jest",

View File

@ -0,0 +1 @@
export type IonRouteAction = 'push' | 'replace' | 'pop';

View File

@ -4,11 +4,12 @@ import { Location as HistoryLocation, UnregisterCallback } from 'history';
import React from 'react'; import React from 'react';
import { RouteComponentProps } from 'react-router-dom'; import { RouteComponentProps } from 'react-router-dom';
import { IonRouteAction } from './IonRouteAction';
import { StackManager } from './StackManager'; import { StackManager } from './StackManager';
interface NavManagerProps extends RouteComponentProps { interface NavManagerProps extends RouteComponentProps {
onNavigateBack: (defaultHref?: string) => void; onNavigateBack: (defaultHref?: string) => void;
onNavigate: (type: 'push' | 'replace', path: string, state?: any) => void; onNavigate: (ionRouteAction: IonRouteAction, path: string, state?: any) => void;
} }
export class NavManager extends React.Component<NavManagerProps, NavContextState> { export class NavManager extends React.Component<NavManagerProps, NavContextState> {
@ -24,8 +25,7 @@ export class NavManager extends React.Component<NavManagerProps, NavContextState
getStackManager: this.getStackManager.bind(this), getStackManager: this.getStackManager.bind(this),
getPageManager: this.getPageManager.bind(this), getPageManager: this.getPageManager.bind(this),
currentPath: this.props.location.pathname, currentPath: this.props.location.pathname,
registerIonPage: () => { return; }, // overridden in View for each IonPage registerIonPage: () => { return; } // overridden in View for each IonPage
tabNavigate: this.tabNavigate.bind(this)
}; };
this.listenUnregisterCallback = this.props.history.listen((location: HistoryLocation) => { this.listenUnregisterCallback = this.props.history.listen((location: HistoryLocation) => {
@ -53,12 +53,8 @@ export class NavManager extends React.Component<NavManagerProps, NavContextState
this.props.onNavigateBack(defaultHref); this.props.onNavigateBack(defaultHref);
} }
navigate(path: string, direction?: RouterDirection | 'none') { navigate(path: string, direction?: RouterDirection | 'none', ionRouteAction: IonRouteAction = 'push') {
this.props.onNavigate('push', path, direction); this.props.onNavigate(ionRouteAction, path, direction);
}
tabNavigate(path: string) {
this.props.onNavigate('replace', path, 'back');
} }
getPageManager() { getPageManager() {

View File

@ -4,18 +4,22 @@ import { ViewStacks } from './ViewStacks';
export interface RouteManagerContextState { export interface RouteManagerContextState {
syncView: (page: HTMLElement, viewId: string) => void; syncView: (page: HTMLElement, viewId: string) => void;
syncRoute: (id: string, route: any) => void;
hideView: (viewId: string) => void; hideView: (viewId: string) => void;
viewStacks: ViewStacks; viewStacks: ViewStacks;
setupIonRouter: (id: string, children: ReactNode, routerOutlet: HTMLIonRouterOutletElement) => void; setupIonRouter: (id: string, children: ReactNode, routerOutlet: HTMLIonRouterOutletElement) => void;
removeViewStack: (stack: string) => void; removeViewStack: (stack: string) => void;
getRoute: (id: string) => any;
} }
export const RouteManagerContext = /*@__PURE__*/React.createContext<RouteManagerContextState>({ export const RouteManagerContext = /*@__PURE__*/React.createContext<RouteManagerContextState>({
viewStacks: new ViewStacks(), viewStacks: new ViewStacks(),
syncView: () => { navContextNotFoundError(); }, syncView: () => { navContextNotFoundError(); },
syncRoute: () => { navContextNotFoundError(); },
hideView: () => { navContextNotFoundError(); }, hideView: () => { navContextNotFoundError(); },
setupIonRouter: () => Promise.reject(navContextNotFoundError()), setupIonRouter: () => Promise.reject(navContextNotFoundError()),
removeViewStack: () => { navContextNotFoundError(); } removeViewStack: () => { navContextNotFoundError(); },
getRoute: () => { navContextNotFoundError(); }
}); });
function navContextNotFoundError() { function navContextNotFoundError() {

View File

@ -7,6 +7,7 @@ import { RouteComponentProps, matchPath, withRouter } from 'react-router-dom';
import { generateId, isDevMode } from '../utils'; import { generateId, isDevMode } from '../utils';
import { LocationHistory } from '../utils/LocationHistory'; import { LocationHistory } from '../utils/LocationHistory';
import { IonRouteAction } from './IonRouteAction';
import { IonRouteData } from './IonRouteData'; import { IonRouteData } from './IonRouteData';
import { NavManager } from './NavManager'; import { NavManager } from './NavManager';
import { RouteManagerContext, RouteManagerContextState } from './RouteManagerContext'; import { RouteManagerContext, RouteManagerContextState } from './RouteManagerContext';
@ -15,14 +16,19 @@ import { ViewStack, ViewStacks } from './ViewStacks';
interface RouteManagerState extends RouteManagerContextState { interface RouteManagerState extends RouteManagerContextState {
location?: HistoryLocation; location?: HistoryLocation;
action?: HistoryAction; action?: IonRouteAction;
} }
class RouteManager extends React.Component<RouteComponentProps, RouteManagerState> { class RouteManager extends React.Component<RouteComponentProps, RouteManagerState> {
listenUnregisterCallback: UnregisterCallback | undefined; listenUnregisterCallback: UnregisterCallback | undefined;
activeIonPageId?: string; activeIonPageId?: string;
currentDirection?: RouterDirection; currentIonRouteAction?: IonRouteAction;
currentRouteDirection?: RouterDirection;
locationHistory = new LocationHistory(); locationHistory = new LocationHistory();
routes: { [key: string]: React.ReactElement<any>; } = {};
ionPageElements: { [key: string]: HTMLElement; } = {};
routerOutlets: { [key: string]: HTMLIonRouterOutletElement; } = {};
firstRender = true;
constructor(props: RouteComponentProps) { constructor(props: RouteComponentProps) {
super(props); super(props);
@ -34,7 +40,9 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
hideView: this.hideView.bind(this), hideView: this.hideView.bind(this),
setupIonRouter: this.setupIonRouter.bind(this), setupIonRouter: this.setupIonRouter.bind(this),
removeViewStack: this.removeViewStack.bind(this), removeViewStack: this.removeViewStack.bind(this),
syncView: this.syncView.bind(this) syncView: this.syncView.bind(this),
syncRoute: this.syncRoute.bind(this),
getRoute: this.getRoute.bind(this)
}; };
this.locationHistory.add({ this.locationHistory.add({
@ -49,7 +57,8 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
componentDidUpdate(_prevProps: RouteComponentProps, prevState: RouteManagerState) { componentDidUpdate(_prevProps: RouteComponentProps, prevState: RouteManagerState) {
// Trigger a page change if the location or action is different // Trigger a page change if the location or action is different
if (this.state.location && prevState.location !== this.state.location || prevState.action !== this.state.action) { if (this.state.location && prevState.location !== this.state.location || prevState.action !== this.state.action) {
this.setActiveView(this.state.location!, this.state.action!); const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
this.setActiveView(this.state.location!, this.state.action!, viewStacks);
} }
} }
@ -59,15 +68,19 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
} }
} }
getRoute(id: string) {
return this.routes[id];
}
hideView(viewId: string) { hideView(viewId: string) {
const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks); const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
const { view } = viewStacks.findViewInfoById(viewId); const { view } = viewStacks.findViewInfoById(viewId);
if (view) { if (view) {
view.show = false; view.show = false;
view.ionPageElement = undefined;
view.isIonRoute = false; view.isIonRoute = false;
view.prevId = undefined; view.prevId = undefined;
view.key = generateId(); view.key = generateId();
delete this.ionPageElements[view.id];
this.setState({ this.setState({
viewStacks viewStacks
}); });
@ -75,64 +88,74 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
} }
historyChange(location: HistoryLocation, action: HistoryAction) { historyChange(location: HistoryLocation, action: HistoryAction) {
location.state = location.state || { direction: this.currentDirection }; const ionRouteAction = this.currentIonRouteAction === 'pop' ? 'pop' : action.toLowerCase() as IonRouteAction;
this.currentDirection = undefined; let direction = this.currentRouteDirection;
if (action === 'PUSH') {
if (ionRouteAction === 'push') {
this.locationHistory.add(location); this.locationHistory.add(location);
} else if ((action === 'REPLACE' && location.state.direction === 'back') || action === 'POP') { } else if (ionRouteAction === 'pop') {
this.locationHistory.pop(); this.locationHistory.pop();
} else { direction = direction || 'back';
} else if (ionRouteAction === 'replace') {
this.locationHistory.replace(location); this.locationHistory.replace(location);
} direction = 'none';
this.setState({
location,
action
});
} }
setActiveView(location: HistoryLocation, action: HistoryAction) { if (direction === 'root') {
const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks); this.locationHistory.clear();
this.locationHistory.add(location);
}
location.state = location.state || { direction };
this.setState({
location,
action: ionRouteAction as IonRouteAction
});
this.currentRouteDirection = undefined;
this.currentIonRouteAction = undefined;
}
setActiveView(location: HistoryLocation, action: IonRouteAction, viewStacks: ViewStacks) {
let direction: RouterDirection | undefined = (location.state && location.state.direction) || 'forward'; let direction: RouterDirection | undefined = (location.state && location.state.direction) || 'forward';
let leavingView: ViewItem | undefined; let leavingView: ViewItem | undefined;
const viewStackKeys = viewStacks.getKeys(); const viewStackKeys = viewStacks.getKeys();
let shouldTransitionPage = false;
let leavingViewHtml: string | undefined;
viewStackKeys.forEach(key => { viewStackKeys.forEach(key => {
const { view: enteringView, viewStack: enteringViewStack, match } = viewStacks.findViewInfoByLocation(location, key); const { view: enteringView, viewStack: enteringViewStack, match } = viewStacks.findViewInfoByLocation(location, key);
if (!enteringView || !enteringViewStack) { if (!enteringView || !enteringViewStack) {
return; return;
} }
leavingView = viewStacks.findViewInfoById(this.activeIonPageId).view; leavingView = viewStacks.findViewInfoById(this.activeIonPageId).view;
if (enteringView.isIonRoute) { if (enteringView.isIonRoute) {
enteringView.show = true; enteringView.show = true;
enteringView.mount = true; enteringView.mount = true;
enteringView.routeData.match = match!; enteringView.routeData.match = match!;
shouldTransitionPage = true;
this.activeIonPageId = enteringView.id; this.activeIonPageId = enteringView.id;
if (leavingView) { if (leavingView) {
if (direction === 'forward') { if (action === 'push' && direction === 'forward') {
if (action === 'PUSH') {
/** /**
* If the page is being pushed into the stack by another view, * If the page is being pushed into the stack by another view,
* record the view that originally directed to the new view for back button purposes. * record the view that originally directed to the new view for back button purposes.
*/ */
enteringView.prevId = leavingView.id; enteringView.prevId = leavingView.id;
} else if (action === 'POP') { } else if (action === 'pop' || action === 'replace') {
direction = leavingView.prevId === enteringView.id ? 'back' : 'none';
} else {
direction = direction || 'back';
leavingView.mount = false;
}
}
if (direction === 'back' || action === 'REPLACE') {
leavingView.mount = false; leavingView.mount = false;
this.removeOrphanedViews(enteringView, enteringViewStack); this.removeOrphanedViews(enteringView, enteringViewStack);
} }
leavingViewHtml = enteringView.id === leavingView.id ? this.ionPageElements[leavingView.id].outerHTML : undefined;
} else { } else {
// If there is not a leavingView, then we shouldn't provide a direction // If there is not a leavingView, then we shouldn't provide a direction
direction = undefined; direction = undefined;
} }
} else { } else {
enteringView.show = true; enteringView.show = true;
enteringView.mount = true; enteringView.mount = true;
@ -151,28 +174,39 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
this.setState({ this.setState({
viewStacks viewStacks
}, () => { }, () => {
if (shouldTransitionPage) {
const { view: enteringView, viewStack } = this.state.viewStacks.findViewInfoById(this.activeIonPageId); const { view: enteringView, viewStack } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
if (enteringView && viewStack) { if (enteringView && viewStack) {
const enteringEl = enteringView.ionPageElement ? enteringView.ionPageElement : undefined; const enteringEl = this.ionPageElements[enteringView.id];
const leavingEl = leavingView && leavingView.ionPageElement ? leavingView.ionPageElement : undefined; const leavingEl = leavingView && this.ionPageElements[leavingView.id];
if (enteringEl) { if (enteringEl) {
let navDirection: NavDirection | undefined;
if (leavingEl && leavingEl.innerHTML === '') {
// Don't animate from an empty view // Don't animate from an empty view
const navDirection = leavingEl && leavingEl.innerHTML === '' ? undefined : direction === 'none' ? undefined : direction; navDirection = undefined;
const shouldGoBack = !!enteringView.prevId || !!this.locationHistory.previous(); } else if (direction === 'none' || direction === 'root') {
navDirection = undefined;
} else {
navDirection = direction;
}
const shouldGoBack = !!enteringView.prevId;
const routerOutlet = this.routerOutlets[viewStack.id];
this.commitView( this.commitView(
enteringEl!, enteringEl!,
leavingEl!, leavingEl!,
viewStack.routerOutlet, routerOutlet,
navDirection, navDirection,
shouldGoBack); shouldGoBack,
leavingViewHtml);
} else if (leavingEl) { } else if (leavingEl) {
leavingEl.classList.add('ion-page-hidden'); leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true'); leavingEl.setAttribute('aria-hidden', 'true');
} }
}
// Warn if an IonPage was not eventually rendered in Dev Mode // Warn if an IonPage was not eventually rendered in Dev Mode
if (isDevMode()) { if (isDevMode()) {
if (enteringView.routeData.match!.url !== location.pathname) { if (enteringView && enteringView.routeData.match!.url !== location.pathname) {
setTimeout(() => { setTimeout(() => {
const { view } = this.state.viewStacks.findViewInfoById(this.activeIonPageId); const { view } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
if (view!.routeData.match!.url !== location.pathname) { if (view!.routeData.match!.url !== location.pathname) {
@ -195,12 +229,13 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
this.removeOrphanedViews(v, viewStack); this.removeOrphanedViews(v, viewStack);
// If view is not currently visible, go ahead and remove it from DOM // If view is not currently visible, go ahead and remove it from DOM
if (v.ionPageElement!.classList.contains('ion-page-hidden')) { const page = this.ionPageElements[v.id];
if (page.classList.contains('ion-page-hidden')) {
v.show = false; v.show = false;
v.ionPageElement = undefined;
v.isIonRoute = false; v.isIonRoute = false;
v.prevId = undefined; v.prevId = undefined;
v.key = generateId(); v.key = generateId();
delete this.ionPageElements[v.id];
} }
v.mount = false; v.mount = false;
} }
@ -212,15 +247,18 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
let activeId: string | undefined; let activeId: string | undefined;
const ionRouterOutlet = React.Children.only(children) as React.ReactElement; const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => { React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
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); this.registerViewStack(id, activeId, views, routerOutlet, this.props.location);
function createViewItem(child: React.ReactElement<any>, location: HistoryLocation) { function createViewItem(child: React.ReactElement<any>, routeId: string, location: HistoryLocation) {
const viewId = generateId(); const viewId = generateId();
const key = generateId(); const key = generateId();
const route = child;
// const route = child;
const matchProps = { const matchProps = {
exact: child.props.exact, exact: child.props.exact,
path: child.props.path || child.props.from, path: child.props.path || child.props.from,
@ -234,7 +272,7 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
match, match,
childProps: child.props childProps: child.props
}, },
route, routeId,
mount: true, mount: true,
show: !!match, show: !!match,
isIonRoute: false isIonRoute: false
@ -251,9 +289,9 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
const prevViewStacks = Object.assign(new ViewStacks(), prevState.viewStacks); const prevViewStacks = Object.assign(new ViewStacks(), prevState.viewStacks);
const newStack: ViewStack = { const newStack: ViewStack = {
id: stack, id: stack,
views: stackItems, views: stackItems
routerOutlet
}; };
this.routerOutlets[stack] = routerOutlet;
if (activeId) { if (activeId) {
this.activeIonPageId = activeId; this.activeIonPageId = activeId;
} }
@ -267,18 +305,6 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
} }
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) { async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
const waitUntilReady = async () => {
if (routerOutlet.componentOnReady) {
routerOutlet.dispatchEvent(new Event('routerOutletReady'));
return;
} else {
setTimeout(() => {
waitUntilReady();
}, 0);
}
};
await waitUntilReady();
const canStart = () => { const canStart = () => {
const config = getConfig(); const config = getConfig();
@ -310,70 +336,115 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
} }
syncView(page: HTMLElement, viewId: string) { syncView(page: HTMLElement, viewId: string) {
this.setState(state => { const viewStacks = Object.assign(new ViewStacks(), this.state.viewStacks);
const viewStacks = Object.assign(new ViewStacks(), state.viewStacks);
const { view } = viewStacks.findViewInfoById(viewId); const { view } = viewStacks.findViewInfoById(viewId);
if (view) {
view.isIonRoute = true;
this.ionPageElements[view.id] = page;
this.setActiveView(this.state.location || this.props.location, this.state.action!, viewStacks);
}
}
view!.ionPageElement = page; syncRoute(_id: string, routerOutlet: any) {
view!.isIonRoute = true; const ionRouterOutlet = React.Children.only(routerOutlet) as React.ReactElement;
return { React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
viewStacks for (const routeKey in this.routes) {
}; const route = this.routes[routeKey];
if (route.props.path === child.props.path) {
}, () => { this.routes[routeKey] = child;
this.setActiveView(this.state.location || this.props.location, this.state.action!); }
}
}); });
} }
private async commitView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOuter: HTMLIonRouterOutletElement, direction?: NavDirection, showGoBack?: boolean) { private async commitView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet: HTMLIonRouterOutletElement, direction?: NavDirection, showGoBack?: boolean, leavingViewHtml?: string) {
if (!this.firstRender) {
if (enteringEl === leavingEl) { if (!('componentOnReady' in ionRouterOutlet)) {
return; await waitUntilRouterOutletReady(ionRouterOutlet);
} }
await ionRouterOuter.commit(enteringEl, leavingEl, { 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, deepWait: true,
duration: direction === undefined ? 0 : undefined, duration: direction === undefined ? 0 : undefined,
direction, direction,
showGoBack, showGoBack,
progressAnimation: false 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)) { if (leavingEl && (enteringEl !== leavingEl)) {
/** add hidden attributes */ /** add hidden attributes */
leavingEl.classList.add('ion-page-hidden'); leavingEl.classList.add('ion-page-hidden');
leavingEl.setAttribute('aria-hidden', 'true'); leavingEl.setAttribute('aria-hidden', 'true');
} }
} else {
enteringEl.classList.remove('ion-page-invisible');
enteringEl.style.zIndex = '101';
this.firstRender = false;
}
} }
handleNavigate(type: 'push' | 'replace', path: string, direction?: RouterDirection) { handleNavigate(ionRouteAction: IonRouteAction, path: string, direction?: RouterDirection) {
this.currentDirection = direction; this.currentIonRouteAction = ionRouteAction;
if (type === 'push') { switch (ionRouteAction) {
case 'push':
this.currentRouteDirection = direction;
this.props.history.push(path); this.props.history.push(path);
} else { break;
case 'pop':
this.currentRouteDirection = direction || 'back';
this.props.history.replace(path); this.props.history.replace(path);
break;
case 'replace':
this.currentRouteDirection = 'none';
this.props.history.replace(path);
break;
} }
} }
navigateBack(defaultHref?: string) { navigateBack(defaultHref?: string) {
const { view: activeIonPage } = this.state.viewStacks.findViewInfoById(this.activeIonPageId); const { view: leavingView } = this.state.viewStacks.findViewInfoById(this.activeIonPageId);
if (activeIonPage) { if (leavingView) {
const { view: enteringView } = this.state.viewStacks.findViewInfoById(activeIonPage.prevId); if (leavingView.id === leavingView.prevId) {
const previousLocation = this.locationHistory.previous();
if (previousLocation) {
this.handleNavigate('pop', previousLocation.pathname + previousLocation.search);
} else {
defaultHref && this.handleNavigate('pop', defaultHref);
}
} else {
const { view: enteringView } = this.state.viewStacks.findViewInfoById(leavingView.prevId);
if (enteringView) { if (enteringView) {
const lastLocation = this.locationHistory.findLastLocationByUrl(enteringView.routeData.match!.url); const lastLocation = this.locationHistory.findLastLocationByUrl(enteringView.routeData.match!.url);
if (lastLocation) { if (lastLocation) {
this.handleNavigate('replace', lastLocation.pathname + lastLocation.search, 'back'); this.handleNavigate('pop', lastLocation.pathname + lastLocation.search);
} else { } else {
this.handleNavigate('replace', enteringView.routeData.match!.url, 'back'); this.handleNavigate('pop', enteringView.routeData.match!.url);
} }
} else { } else {
const currentLocation = this.locationHistory.previous(); const currentLocation = this.locationHistory.previous();
if (currentLocation) { if (currentLocation) {
this.handleNavigate('replace', currentLocation.pathname + currentLocation.search, 'back'); this.handleNavigate('pop', currentLocation.pathname + currentLocation.search);
} else { } else {
if (defaultHref) { if (defaultHref) {
this.handleNavigate('replace', defaultHref, 'back'); this.handleNavigate('pop', defaultHref);
}
} }
} }
} }
@ -399,5 +470,28 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
} }
} }
function clonePageElement(leavingViewHtml: string) {
const newEl = document.createElement('div');
newEl.innerHTML = leavingViewHtml;
newEl.classList.add('ion-page-hidden');
newEl.style.zIndex = '';
// Remove an existing back button so the new element doesn't get two of them
const ionBackButton = newEl.getElementsByTagName('ion-back-button');
if (ionBackButton[0]) {
ionBackButton[0].innerHTML = '';
}
return newEl.firstChild as HTMLElement;
}
async function waitUntilRouterOutletReady(ionRouterOutlet: HTMLIonRouterElement) {
if ('componentOnReady' in ionRouterOutlet) {
return;
} else {
setTimeout(() => {
waitUntilRouterOutletReady(ionRouterOutlet);
}, 0);
}
}
export const RouteManagerWithRouter = withRouter(RouteManager); export const RouteManagerWithRouter = withRouter(RouteManager);
RouteManagerWithRouter.displayName = 'RouteManager'; RouteManagerWithRouter.displayName = 'RouteManager';

View File

@ -2,22 +2,21 @@ import React from 'react';
import { generateId, isDevMode } from '../utils'; import { generateId, isDevMode } from '../utils';
import { RouteManagerContext } from './RouteManagerContext'; import { RouteManagerContext, RouteManagerContextState } from './RouteManagerContext';
import { View } from './View'; import { View } from './View';
import { ViewItem } from './ViewItem'; import { ViewItem } from './ViewItem';
import { ViewTransitionManager } from './ViewTransitionManager'; import { ViewTransitionManager } from './ViewTransitionManager';
interface StackManagerProps { interface StackManagerProps {
id?: string; id?: string;
routeManager: RouteManagerContextState;
children?: React.ReactNode;
} }
interface StackManagerState { interface StackManagerState { }
routerOutletReady: boolean;
}
export class StackManager extends React.Component<StackManagerProps, StackManagerState> { class StackManagerInner extends React.Component<StackManagerProps, StackManagerState> {
routerOutletEl: React.RefObject<HTMLIonRouterOutletElement> = React.createRef(); routerOutletEl: React.RefObject<HTMLIonRouterOutletElement> = React.createRef();
context!: React.ContextType<typeof RouteManagerContext>;
id: string; id: string;
constructor(props: StackManagerProps) { constructor(props: StackManagerProps) {
@ -25,47 +24,44 @@ export class StackManager extends React.Component<StackManagerProps, StackManage
this.id = this.props.id || generateId(); this.id = this.props.id || generateId();
this.handleViewSync = this.handleViewSync.bind(this); this.handleViewSync = this.handleViewSync.bind(this);
this.handleHideView = this.handleHideView.bind(this); this.handleHideView = this.handleHideView.bind(this);
this.state = { this.state = {};
routerOutletReady: false
};
} }
componentDidMount() { componentDidMount() {
this.context.setupIonRouter(this.id, this.props.children, this.routerOutletEl.current!); this.props.routeManager.setupIonRouter(this.id, this.props.children, this.routerOutletEl.current!);
this.routerOutletEl.current!.addEventListener('routerOutletReady', () => { }
this.setState({
routerOutletReady: true static getDerivedStateFromProps(props: StackManagerProps, state: StackManagerState) {
}); props.routeManager.syncRoute('', props.children);
}); return state;
} }
componentWillUnmount() { componentWillUnmount() {
this.context.removeViewStack(this.id); this.props.routeManager.removeViewStack(this.id);
} }
handleViewSync(page: HTMLElement, viewId: string) { handleViewSync(page: HTMLElement, viewId: string) {
this.context.syncView(page, viewId); this.props.routeManager.syncView(page, viewId);
} }
handleHideView(viewId: string) { handleHideView(viewId: string) {
this.context.hideView(viewId); this.props.routeManager.hideView(viewId);
} }
renderChild(item: ViewItem) { renderChild(item: ViewItem, route: any) {
const component = React.cloneElement(item.route, { const component = React.cloneElement(route, {
computedMatch: item.routeData.match computedMatch: item.routeData.match
}); });
return component; return component;
} }
render() { render() {
const context = this.context; const routeManager = this.props.routeManager;
const viewStack = context.viewStacks.get(this.id); const viewStack = routeManager.viewStacks.get(this.id);
const views = (viewStack || { views: [] }).views.filter(x => x.show); const views = (viewStack || { views: [] }).views.filter(x => x.show);
const ionRouterOutlet = React.Children.only(this.props.children) as React.ReactElement; const ionRouterOutlet = React.Children.only(this.props.children) as React.ReactElement;
const { routerOutletReady } = this.state; const childElements = views.map(view => {
const route = routeManager.getRoute(view.routeId);
const childElements = routerOutletReady ? views.map(view => {
return ( return (
<ViewTransitionManager <ViewTransitionManager
id={view.id} id={view.id}
@ -76,17 +72,22 @@ export class StackManager extends React.Component<StackManagerProps, StackManage
onViewSync={this.handleViewSync} onViewSync={this.handleViewSync}
onHideView={this.handleHideView} onHideView={this.handleHideView}
view={view} view={view}
route={route}
> >
{this.renderChild(view)} {this.renderChild(view, route)}
</View> </View>
</ViewTransitionManager> </ViewTransitionManager>
); );
}) : <div></div>; });
const elementProps: any = { const elementProps: any = {
ref: this.routerOutletEl ref: this.routerOutletEl
}; };
if (ionRouterOutlet.props.forwardedRef) {
ionRouterOutlet.props.forwardedRef.current = this.routerOutletEl;
}
if (isDevMode()) { if (isDevMode()) {
elementProps['data-stack-id'] = this.id; elementProps['data-stack-id'] = this.id;
} }
@ -95,8 +96,14 @@ export class StackManager extends React.Component<StackManagerProps, StackManage
return routerOutletChild; return routerOutletChild;
} }
}
static get contextType() { const withContext = (Component: any) => {
return RouteManagerContext; return (props: any) => (
} <RouteManagerContext.Consumer>
} {context => <Component {...props} routeManager={context} />}
</RouteManagerContext.Consumer>
);
};
export const StackManager = withContext(StackManagerInner);

View File

@ -1,6 +1,5 @@
import { IonLifeCycleContext, NavContext } from '@ionic/react'; import { IonLifeCycleContext, NavContext } from '@ionic/react';
import React from 'react'; import React from 'react';
import { Redirect, Route } from 'react-router-dom';
import { isDevMode } from '../utils'; import { isDevMode } from '../utils';
@ -10,6 +9,7 @@ interface ViewProps extends React.HTMLAttributes<HTMLElement> {
onViewSync: (page: HTMLElement, viewId: string) => void; onViewSync: (page: HTMLElement, viewId: string) => void;
onHideView: (viewId: string) => void; onHideView: (viewId: string) => void;
view: ViewItem; view: ViewItem;
route: any;
} }
/** /**
@ -19,20 +19,6 @@ export class View extends React.Component<ViewProps, {}> {
context!: React.ContextType<typeof IonLifeCycleContext>; context!: React.ContextType<typeof IonLifeCycleContext>;
ionPage?: HTMLElement; ionPage?: HTMLElement;
componentDidMount() {
/**
* 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) {
this.props.onHideView(view.id);
} else if (view.route.type === Route && view.route.props.render) {
if (view.route.props.render().type === Redirect) {
this.props.onHideView(view.id);
}
}
}
componentWillUnmount() { componentWillUnmount() {
if (this.ionPage) { if (this.ionPage) {
this.ionPage.removeEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this)); this.ionPage.removeEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));

View File

@ -3,10 +3,9 @@ export interface ViewItem<RouteData = any> {
id: string; 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. */ /** 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; key: string;
/** The <Route /> or <Redirect /> component associated with the view */
route: React.ReactElement<any>; routeId: string;
/** The reference to the <IonPage /> element. */
ionPageElement?: HTMLElement;
/** The routeData for the view. */ /** The routeData for the view. */
routeData: RouteData; routeData: RouteData;
/** Used to track which page pushed the page into view. Used for back button purposes. */ /** Used to track which page pushed the page into view. Used for back button purposes. */
@ -23,4 +22,9 @@ export interface ViewItem<RouteData = any> {
* An IonRoute is a Route that contains an IonPage. Only IonPages participate in transition and lifecycle events. * An IonRoute is a Route that contains an IonPage. Only IonPages participate in transition and lifecycle events.
*/ */
isIonRoute: boolean; isIonRoute: boolean;
/**
* location of the view
*/
location?: string;
} }

View File

@ -6,7 +6,6 @@ import { ViewItem } from './ViewItem';
export interface ViewStack { export interface ViewStack {
id: string; id: string;
routerOutlet: HTMLIonRouterOutletElement;
views: ViewItem[]; views: ViewItem[];
} }
@ -58,10 +57,11 @@ export class ViewStacks {
path: v.routeData.childProps.path || v.routeData.childProps.from, path: v.routeData.childProps.path || v.routeData.childProps.from,
component: v.routeData.childProps.component component: v.routeData.childProps.component
}; };
match = matchPath(location.pathname, matchProps); const myMatch: IonRouteData['match'] | null | undefined = matchPath(location.pathname, matchProps);
if (match) { if (myMatch) {
view = v; view = v;
return true; match = myMatch;
return view.location === location.pathname;
} }
return false; return false;
} }

View File

@ -3,7 +3,7 @@ import { Location as HistoryLocation } from 'history';
const RESTRICT_SIZE = 25; const RESTRICT_SIZE = 25;
export class LocationHistory { export class LocationHistory {
locationHistory: HistoryLocation[] = []; private locationHistory: HistoryLocation[] = [];
add(location: HistoryLocation) { add(location: HistoryLocation) {
this.locationHistory.push(location); this.locationHistory.push(location);
@ -21,6 +21,10 @@ export class LocationHistory {
this.locationHistory.push(location); this.locationHistory.push(location);
} }
clear() {
this.locationHistory = [];
}
findLastLocationByUrl(url: string) { findLastLocationByUrl(url: string) {
for (let i = this.locationHistory.length - 1; i >= 0; i--) { for (let i = this.locationHistory.length - 1; i >= 0; i--) {
const location = this.locationHistory[i]; const location = this.locationHistory[i];

View File

@ -27,6 +27,11 @@
"jsx-no-bind": false, "jsx-no-bind": false,
"jsx-no-lambda": false, "jsx-no-lambda": false,
"jsx-no-multiline-js": false, "jsx-no-multiline-js": false,
"jsx-wrap-multiline": false "jsx-wrap-multiline": false,
"forin": false,
"strict-type-predicates": false,
"no-unused-expression": false,
"no-constant-condition": false,
"no-empty-interface": false
} }
} }

View File

@ -49,7 +49,7 @@
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^23.3.9", "@types/jest": "^23.3.9",
"@types/node": "10.12.9", "@types/node": "^12.12.14",
"@types/react": "^16.9.2", "@types/react": "^16.9.2",
"@types/react-dom": "^16.9.0", "@types/react-dom": "^16.9.0",
"fs-extra": "^8.1.0", "fs-extra": "^8.1.0",
@ -67,7 +67,7 @@
"tslint": "^5.18.0", "tslint": "^5.18.0",
"tslint-ionic-rules": "0.0.21", "tslint-ionic-rules": "0.0.21",
"tslint-react": "^4.0.0", "tslint-react": "^4.0.0",
"typescript": "3.5.3" "typescript": "^3.7.2"
}, },
"jest": { "jest": {
"preset": "ts-jest", "preset": "ts-jest",

View File

@ -3,13 +3,26 @@ import React from 'react';
import { NavContext } from '../contexts/NavContext'; import { NavContext } from '../contexts/NavContext';
import { IonicReactProps } from './IonicReactProps'; import { IonicReactProps } from './IonicReactProps';
import { createForwardRef } from './utils';
export const IonPage = /*@__PURE__*/(() => class IonPageInternal extends React.Component<React.HTMLAttributes<HTMLElement> & IonicReactProps> { interface IonPageProps extends IonicReactProps {
}
interface IonPageInternalProps extends IonPageProps {
forwardedRef?: React.RefObject<HTMLDivElement>;
}
class IonPageInternal extends React.Component<IonPageInternalProps> {
context!: React.ContextType<typeof NavContext>; context!: React.ContextType<typeof NavContext>;
ref = React.createRef<HTMLDivElement>(); ref: React.RefObject<HTMLDivElement>;
constructor(props: IonPageInternalProps) {
super(props);
this.ref = this.props.forwardedRef || React.createRef();
}
componentDidMount() { componentDidMount() {
if (this.context && this.ref.current) { if (this.context && this.ref && this.ref.current) {
if (this.context.hasIonicRouter()) { if (this.context.hasIonicRouter()) {
this.context.registerIonPage(this.ref.current); this.context.registerIonPage(this.ref.current);
} }
@ -17,7 +30,7 @@ export const IonPage = /*@__PURE__*/(() => class IonPageInternal extends React.C
} }
render() { render() {
const { className, children, ...props } = this.props; const { className, children, forwardedRef, ...props } = this.props;
return ( return (
<div className={className ? `ion-page ${className}` : 'ion-page'} ref={this.ref} {...props}> <div className={className ? `ion-page ${className}` : 'ion-page'} ref={this.ref} {...props}>
@ -33,4 +46,6 @@ export const IonPage = /*@__PURE__*/(() => class IonPageInternal extends React.C
static get contextType() { static get contextType() {
return NavContext; return NavContext;
} }
})(); }
export const IonPage = createForwardRef(IonPageInternal, 'IonPage');

View File

@ -13,7 +13,7 @@ type Props = LocalJSX.IonRouterOutlet & {
}; };
type InternalProps = Props & { type InternalProps = Props & {
forwardedRef: any; forwardedRef?: React.RefObject<HTMLIonRouterOutletElement>;
}; };
const IonRouterOutletContainer = /*@__PURE__*/(() => class extends React.Component<InternalProps> { const IonRouterOutletContainer = /*@__PURE__*/(() => class extends React.Component<InternalProps> {

View File

@ -1,5 +1,6 @@
export interface IonicReactProps { export interface IonicReactProps {
class?: string; class?: string;
className?: string;
style?: {[key: string]: any }; style?: {[key: string]: any };
} }

View File

@ -15,18 +15,21 @@ export interface ReactControllerProps {
export const createControllerComponent = <OptionsType extends object, OverlayType extends OverlayBase>( export const createControllerComponent = <OptionsType extends object, OverlayType extends OverlayBase>(
displayName: string, displayName: string,
controller: { create: (options: OptionsType) => Promise<OverlayType> } controller: { create: (options: OptionsType) => Promise<OverlayType>; }
) => { ) => {
const dismissEventName = `on${displayName}DidDismiss`; const dismissEventName = `on${displayName}DidDismiss`;
type Props = OptionsType & ReactControllerProps; type Props = OptionsType & ReactControllerProps & {
forwardedRef?: React.RefObject<OverlayType>;
};
return class extends React.Component<Props> { class Overlay extends React.Component<Props> {
overlay?: OverlayType; overlay?: OverlayType;
isUnmounted = false; isUnmounted = false;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.handleDismiss = this.handleDismiss.bind(this);
} }
static get displayName() { static get displayName() {
@ -54,17 +57,29 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
} }
} }
handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
if (this.props.onDidDismiss) {
this.props.onDidDismiss(event);
}
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = undefined;
}
}
async present(prevProps?: Props) { async present(prevProps?: Props) {
const { isOpen, onDidDismiss, ...cProps } = this.props; const { isOpen, onDidDismiss, ...cProps } = this.props;
this.overlay = await controller.create({ this.overlay = await controller.create({
...cProps as any ...cProps as any
}); });
attachProps(this.overlay, { attachProps(this.overlay, {
[dismissEventName]: onDidDismiss [dismissEventName]: this.handleDismiss
}, prevProps); }, prevProps);
// Check isOpen again since the value could have changed during the async call to controller.create // Check isOpen again since the value could have changed during the async call to controller.create
// It's also possible for the component to have become unmounted. // It's also possible for the component to have become unmounted.
if (this.props.isOpen === true && this.isUnmounted === false) { if (this.props.isOpen === true && this.isUnmounted === false) {
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = this.overlay;
}
await this.overlay.present(); await this.overlay.present();
} }
} }
@ -72,5 +87,9 @@ export const createControllerComponent = <OptionsType extends object, OverlayTyp
render(): null { render(): null {
return null; return null;
} }
}; }
return React.forwardRef<OverlayType, Props>((props, ref) => {
return <Overlay {...props} forwardedRef={ref} />;
});
}; };

View File

@ -15,21 +15,24 @@ export interface ReactOverlayProps {
onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void; onDidDismiss?: (event: CustomEvent<OverlayEventDetail>) => void;
} }
export const createOverlayComponent = <T extends object, OverlayType extends OverlayElement>( export const createOverlayComponent = <OverlayComponent extends object, OverlayType extends OverlayElement>(
displayName: string, displayName: string,
controller: { create: (options: any) => Promise<OverlayType> } controller: { create: (options: any) => Promise<OverlayType>; }
) => { ) => {
const dismissEventName = `on${displayName}DidDismiss`; const dismissEventName = `on${displayName}DidDismiss`;
type Props = T & ReactOverlayProps; type Props = OverlayComponent & ReactOverlayProps & {
forwardedRef?: React.RefObject<OverlayType>;
};
return class extends React.Component<Props> { class Overlay extends React.Component<Props> {
overlay?: OverlayType; overlay?: OverlayType;
el: HTMLDivElement; el: HTMLDivElement;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.el = document.createElement('div'); this.el = document.createElement('div');
this.handleDismiss = this.handleDismiss.bind(this);
} }
static get displayName() { static get displayName() {
@ -46,6 +49,15 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
if (this.overlay) { this.overlay.dismiss(); } if (this.overlay) { this.overlay.dismiss(); }
} }
handleDismiss(event: CustomEvent<OverlayEventDetail<any>>) {
if (this.props.onDidDismiss) {
this.props.onDidDismiss(event);
}
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = undefined;
}
}
async componentDidUpdate(prevProps: Props) { async componentDidUpdate(prevProps: Props) {
if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) { if (prevProps.isOpen !== this.props.isOpen && this.props.isOpen === true) {
this.present(prevProps); this.present(prevProps);
@ -56,10 +68,11 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
} }
async present(prevProps?: Props) { async present(prevProps?: Props) {
const { children, isOpen, onDidDismiss = () => { return; }, ...cProps } = this.props; const { children, isOpen, onDidDismiss, ...cProps } = this.props;
const elementProps = { const elementProps = {
...cProps, ...cProps,
[dismissEventName]: onDidDismiss ref: this.props.forwardedRef,
[dismissEventName]: this.handleDismiss
}; };
const overlay = this.overlay = await controller.create({ const overlay = this.overlay = await controller.create({
@ -68,6 +81,10 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
componentProps: {} componentProps: {}
}); });
if (this.props.forwardedRef) {
(this.props.forwardedRef as any).current = overlay;
}
attachProps(overlay, elementProps, prevProps); attachProps(overlay, elementProps, prevProps);
await overlay.present(); await overlay.present();
@ -76,8 +93,12 @@ export const createOverlayComponent = <T extends object, OverlayType extends Ove
render() { render() {
return ReactDOM.createPortal( return ReactDOM.createPortal(
this.props.children, this.props.children,
this.el, this.el
); );
} }
}; }
return React.forwardRef<OverlayType, Props>((props, ref) => {
return <Overlay {...props} forwardedRef={ref} />;
});
}; };

View File

@ -1,4 +1,4 @@
export declare type RouterDirection = 'forward' | 'back' | 'none'; export declare type RouterDirection = 'forward' | 'back' | 'root' | 'none';
export type HrefProps<T> = Omit<T, 'routerDirection'> & { export type HrefProps<T> = Omit<T, 'routerDirection'> & {
routerLink?: string; routerLink?: string;

View File

@ -69,12 +69,13 @@ const IonTabBarUnwrapped = /*@__PURE__*/(() => class extends React.Component<Pro
} }
private onTabButtonClick = (e: CustomEvent<{ href: string, selected: boolean, tab: string }>) => { private onTabButtonClick = (e: CustomEvent<{ href: string, selected: boolean, tab: string }>) => {
if (this.state.activeTab === e.detail.tab) {
const originalHref = this.state.tabs[e.detail.tab].originalHref; const originalHref = this.state.tabs[e.detail.tab].originalHref;
if (this.context.hasIonicRouter()) { const currentHref = this.state.tabs[e.detail.tab].currentHref;
this.context.tabNavigate(originalHref); if (this.state.activeTab === e.detail.tab) {
if (originalHref === currentHref) {
this.context.navigate(originalHref, 'none');
} else { } else {
this.context.navigate(originalHref, 'back'); this.context.navigate(originalHref, 'back', 'pop');
} }
} else { } else {
if (this.props.onIonTabsWillChange) { if (this.props.onIonTabsWillChange) {
@ -83,7 +84,7 @@ const IonTabBarUnwrapped = /*@__PURE__*/(() => class extends React.Component<Pro
if (this.props.onIonTabsDidChange) { if (this.props.onIonTabsDidChange) {
this.props.onIonTabsDidChange(new CustomEvent('ionTabDidChange', { detail: { tab: e.detail.tab } })); this.props.onIonTabsDidChange(new CustomEvent('ionTabDidChange', { detail: { tab: e.detail.tab } }));
} }
this.context.navigate(this.state.tabs[e.detail.tab].currentHref, 'none'); this.context.navigate(currentHref, 'none');
} }
} }

View File

@ -5,10 +5,9 @@ export interface NavContextState {
getPageManager: () => any; getPageManager: () => any;
getStackManager: () => any; getStackManager: () => any;
goBack: (defaultHref?: string) => void; goBack: (defaultHref?: string) => void;
navigate: (path: string, direction?: RouterDirection | 'none') => void; navigate: (path: string, direction?: RouterDirection | 'none', ionRouteAction?: 'push' | 'replace' | 'pop') => void;
hasIonicRouter: () => boolean; hasIonicRouter: () => boolean;
registerIonPage: (page: HTMLElement) => void; registerIonPage: (page: HTMLElement) => void;
tabNavigate: (url: string) => void;
currentPath: string | undefined; currentPath: string | undefined;
} }
@ -23,7 +22,6 @@ export const NavContext = /*@__PURE__*/React.createContext<NavContextState>({
} }
}, },
navigate: (path: string) => { window.location.pathname = path; }, navigate: (path: string) => { window.location.pathname = path; },
tabNavigate: () => undefined,
hasIonicRouter: () => false, hasIonicRouter: () => false,
registerIonPage: () => undefined, registerIonPage: () => undefined,
currentPath: undefined currentPath: undefined

View File

@ -14,7 +14,6 @@
"trailing-comma": false, "trailing-comma": false,
"no-null-keyword": false, "no-null-keyword": false,
"no-console": false, "no-console": false,
"no-unbound-method": true,
"no-floating-promises": false, "no-floating-promises": false,
"no-invalid-template-strings": true, "no-invalid-template-strings": true,
"ban-export-const-enum": true, "ban-export-const-enum": true,
@ -27,6 +26,8 @@
"jsx-no-bind": false, "jsx-no-bind": false,
"jsx-no-lambda": false, "jsx-no-lambda": false,
"jsx-no-multiline-js": false, "jsx-no-multiline-js": false,
"jsx-wrap-multiline": false "jsx-wrap-multiline": false,
"no-empty-interface": false,
"no-unbound-method": false
} }
} }