mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-20 12:29:55 +08:00
fix(react): support navigating to same page and route updates in IonRouterOutlet, fixes #19891, #19892, #19986
This commit is contained in:
@ -3,7 +3,6 @@ import { NavContext, NavContextState } from '@ionic/react';
|
|||||||
import { Location as HistoryLocation, UnregisterCallback } from 'history';
|
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 { StackManager } from './StackManager';
|
import { StackManager } from './StackManager';
|
||||||
|
|
||||||
interface NavManagerProps extends RouteComponentProps {
|
interface NavManagerProps extends RouteComponentProps {
|
||||||
@ -25,7 +24,6 @@ export class NavManager extends React.Component<NavManagerProps, NavContextState
|
|||||||
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 +51,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', type: 'push' | 'replace' = 'push') {
|
||||||
this.props.onNavigate('push', path, direction);
|
this.props.onNavigate(type, path, direction);
|
||||||
}
|
|
||||||
|
|
||||||
tabNavigate(path: string) {
|
|
||||||
this.props.onNavigate('replace', path, 'back');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getPageManager() {
|
getPageManager() {
|
||||||
|
@ -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() {
|
||||||
|
@ -2,7 +2,7 @@ import { NavDirection } from '@ionic/core';
|
|||||||
import { RouterDirection, getConfig } from '@ionic/react';
|
import { RouterDirection, getConfig } from '@ionic/react';
|
||||||
import { Action as HistoryAction, Location as HistoryLocation, UnregisterCallback } from 'history';
|
import { Action as HistoryAction, Location as HistoryLocation, UnregisterCallback } from 'history';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { RouteComponentProps, matchPath, withRouter } from 'react-router-dom';
|
import { RouteComponentProps, withRouter, matchPath } from 'react-router-dom';
|
||||||
|
|
||||||
import { generateId, isDevMode } from '../utils';
|
import { generateId, isDevMode } from '../utils';
|
||||||
import { LocationHistory } from '../utils/LocationHistory';
|
import { LocationHistory } from '../utils/LocationHistory';
|
||||||
@ -23,6 +23,7 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
|
|||||||
activeIonPageId?: string;
|
activeIonPageId?: string;
|
||||||
currentDirection?: RouterDirection;
|
currentDirection?: RouterDirection;
|
||||||
locationHistory = new LocationHistory();
|
locationHistory = new LocationHistory();
|
||||||
|
routes: { [key: string]: any } = {};
|
||||||
|
|
||||||
constructor(props: RouteComponentProps) {
|
constructor(props: RouteComponentProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@ -34,7 +35,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({
|
||||||
@ -46,6 +49,10 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// componentDidMount() {
|
||||||
|
// this.setActiveView(this.props.location, this.state.action!);
|
||||||
|
// }
|
||||||
|
|
||||||
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) {
|
||||||
@ -59,6 +66,10 @@ 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);
|
||||||
@ -95,18 +106,22 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
|
|||||||
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;
|
||||||
|
|
||||||
@ -129,10 +144,12 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
|
|||||||
leavingView.mount = false;
|
leavingView.mount = false;
|
||||||
this.removeOrphanedViews(enteringView, enteringViewStack);
|
this.removeOrphanedViews(enteringView, enteringViewStack);
|
||||||
}
|
}
|
||||||
|
leavingViewHtml = enteringView.id === leavingView.id ? leavingView.ionPageElement!.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,6 +168,7 @@ 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 = enteringView.ionPageElement ? enteringView.ionPageElement : undefined;
|
||||||
@ -164,15 +182,17 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
|
|||||||
leavingEl!,
|
leavingEl!,
|
||||||
viewStack.routerOutlet,
|
viewStack.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) {
|
||||||
@ -212,15 +232,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 +257,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
|
||||||
@ -326,19 +349,43 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async commitView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOuter: HTMLIonRouterOutletElement, direction?: NavDirection, showGoBack?: boolean) {
|
syncRoute(_id: string, routerOutlet: any) {
|
||||||
|
const ionRouterOutlet = React.Children.only(routerOutlet) as React.ReactElement;
|
||||||
|
|
||||||
if (enteringEl === leavingEl) {
|
React.Children.forEach(ionRouterOutlet.props.children, (child: React.ReactElement) => {
|
||||||
return;
|
for (let routeKey in this.routes) {
|
||||||
|
const route = this.routes[routeKey];
|
||||||
|
if (route.props.path == child.props.path) {
|
||||||
|
this.routes[routeKey] = child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await ionRouterOuter.commit(enteringEl, leavingEl, {
|
private async commitView(enteringEl: HTMLElement, leavingEl: HTMLElement, ionRouterOutlet: HTMLIonRouterOutletElement, direction?: NavDirection, showGoBack?: boolean, leavingViewHtml?: string) {
|
||||||
|
|
||||||
|
if ((enteringEl === leavingEl) && direction && leavingViewHtml) {
|
||||||
|
// If a page is transitioning to another version of itself
|
||||||
|
// we clone it so we can have an animation to show
|
||||||
|
const newLeavingElement = clonePageElement(leavingViewHtml);
|
||||||
|
ionRouterOutlet.appendChild(newLeavingElement);
|
||||||
|
await ionRouterOutlet.commit(enteringEl, newLeavingElement, {
|
||||||
deepWait: true,
|
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 */
|
||||||
@ -357,9 +404,17 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
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('replace', previousLocation.pathname + previousLocation.search, 'back');
|
||||||
|
} else {
|
||||||
|
defaultHref && this.handleNavigate('replace', defaultHref, 'back');
|
||||||
|
}
|
||||||
|
} 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) {
|
||||||
@ -377,6 +432,7 @@ class RouteManager extends React.Component<RouteComponentProps, RouteManagerStat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (defaultHref) {
|
if (defaultHref) {
|
||||||
this.handleNavigate('replace', defaultHref, 'back');
|
this.handleNavigate('replace', defaultHref, 'back');
|
||||||
@ -399,5 +455,18 @@ 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 = null;
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
export const RouteManagerWithRouter = withRouter(RouteManager);
|
export const RouteManagerWithRouter = withRouter(RouteManager);
|
||||||
RouteManagerWithRouter.displayName = 'RouteManager';
|
RouteManagerWithRouter.displayName = 'RouteManager';
|
||||||
|
@ -2,22 +2,24 @@ 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;
|
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) {
|
||||||
@ -31,7 +33,7 @@ export class StackManager extends React.Component<StackManagerProps, StackManage
|
|||||||
}
|
}
|
||||||
|
|
||||||
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.routerOutletEl.current!.addEventListener('routerOutletReady', () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
routerOutletReady: true
|
routerOutletReady: true
|
||||||
@ -39,33 +41,39 @@ export class StackManager extends React.Component<StackManagerProps, StackManage
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 { routerOutletReady } = this.state;
|
||||||
|
|
||||||
const childElements = routerOutletReady ? views.map(view => {
|
const childElements = routerOutletReady ? views.map(view => {
|
||||||
|
const route = routeManager.getRoute(view.routeId);
|
||||||
return (
|
return (
|
||||||
<ViewTransitionManager
|
<ViewTransitionManager
|
||||||
id={view.id}
|
id={view.id}
|
||||||
@ -76,8 +84,9 @@ 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>
|
||||||
);
|
);
|
||||||
@ -99,8 +108,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);
|
||||||
|
@ -10,6 +10,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,11 +24,11 @@ export class View extends React.Component<ViewProps, {}> {
|
|||||||
/**
|
/**
|
||||||
* If we can tell if view is a redirect, hide it so it will work again in future
|
* If we can tell if view is a redirect, hide it so it will work again in future
|
||||||
*/
|
*/
|
||||||
const { view } = this.props;
|
const { view, route } = this.props;
|
||||||
if (view.route.type === Redirect) {
|
if (route.type === Redirect) {
|
||||||
this.props.onHideView(view.id);
|
this.props.onHideView(view.id);
|
||||||
} else if (view.route.type === Route && view.route.props.render) {
|
} else if (route.type === Route && route.props.render) {
|
||||||
if (view.route.props.render().type === Redirect) {
|
if (route.props.render().type === Redirect) {
|
||||||
this.props.onHideView(view.id);
|
this.props.onHideView(view.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,10 @@ 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;
|
||||||
|
|
||||||
|
routeId: string;
|
||||||
/** The <Route /> or <Redirect /> component associated with the view */
|
/** The <Route /> or <Redirect /> component associated with the view */
|
||||||
route: React.ReactElement<any>;
|
// route: React.ReactElement<any>;
|
||||||
/** The reference to the <IonPage /> element. */
|
/** The reference to the <IonPage /> element. */
|
||||||
ionPageElement?: HTMLElement;
|
ionPageElement?: HTMLElement;
|
||||||
/** The routeData for the view. */
|
/** The routeData for the view. */
|
||||||
@ -23,4 +25,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;
|
||||||
}
|
}
|
||||||
|
@ -58,10 +58,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;
|
||||||
}
|
}
|
||||||
|
@ -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', 'replace');
|
||||||
}
|
}
|
||||||
} 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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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', type?: 'push' | 'replace') => 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
|
||||||
|
Reference in New Issue
Block a user