mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-18 19:21:34 +08:00
feat(react): React Router Enhancements (#21693)
This commit is contained in:
166
packages/react/src/routing/LocationHistory.ts
Normal file
166
packages/react/src/routing/LocationHistory.ts
Normal file
@ -0,0 +1,166 @@
|
||||
import { RouteInfo } from '../models/RouteInfo';
|
||||
|
||||
// const RESTRICT_SIZE = 100;
|
||||
|
||||
export class LocationHistory {
|
||||
private locationHistory: RouteInfo[] = [];
|
||||
private tabHistory: {
|
||||
[key: string]: RouteInfo[];
|
||||
} = {};
|
||||
|
||||
add(routeInfo: RouteInfo) {
|
||||
|
||||
if (routeInfo.routeAction === 'push' || routeInfo.routeAction == null) {
|
||||
this._add(routeInfo);
|
||||
} else if (routeInfo.routeAction === 'pop') {
|
||||
this._pop(routeInfo);
|
||||
} else if (routeInfo.routeAction === 'replace') {
|
||||
this._replace(routeInfo);
|
||||
}
|
||||
|
||||
if (routeInfo.routeDirection === 'root') {
|
||||
this._clear();
|
||||
this._add(routeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
clearTabStack(tab: string) {
|
||||
const routeInfos = this._getRouteInfosByKey(tab);
|
||||
if (routeInfos) {
|
||||
routeInfos.forEach(ri => {
|
||||
this.locationHistory = this.locationHistory.filter(x => x.id !== ri.id);
|
||||
});
|
||||
this.tabHistory[tab] = [];
|
||||
}
|
||||
}
|
||||
|
||||
update(routeInfo: RouteInfo) {
|
||||
const locationIndex = this.locationHistory.findIndex(x => x.id === routeInfo.id);
|
||||
if (locationIndex > -1) {
|
||||
this.locationHistory.splice(locationIndex, 1, routeInfo);
|
||||
}
|
||||
const tabArray = this.tabHistory[routeInfo.tab || ''];
|
||||
if (tabArray) {
|
||||
const tabIndex = tabArray.findIndex(x => x.id === routeInfo.id);
|
||||
if (tabIndex > -1) {
|
||||
tabArray.splice(tabIndex, 1, routeInfo);
|
||||
} else {
|
||||
tabArray.push(routeInfo);
|
||||
}
|
||||
} else if (routeInfo.tab) {
|
||||
this.tabHistory[routeInfo.tab] = [routeInfo];
|
||||
}
|
||||
}
|
||||
|
||||
private _add(routeInfo: RouteInfo) {
|
||||
const routeInfos = this._getRouteInfosByKey(routeInfo.tab);
|
||||
if (routeInfos) {
|
||||
// If the latest routeInfo is the same (going back and forth between tabs), replace it
|
||||
if (routeInfos[routeInfos.length - 1]?.id === routeInfo.id) {
|
||||
routeInfos.pop();
|
||||
}
|
||||
routeInfos.push(routeInfo);
|
||||
}
|
||||
this.locationHistory.push(routeInfo);
|
||||
}
|
||||
|
||||
private _pop(routeInfo: RouteInfo) {
|
||||
const routeInfos = this._getRouteInfosByKey(routeInfo.tab);
|
||||
let ri: RouteInfo;
|
||||
if (routeInfos) {
|
||||
// Pop all routes until we are back
|
||||
ri = routeInfos[routeInfos.length - 1];
|
||||
while (ri && ri.id !== routeInfo.id) {
|
||||
routeInfos.pop();
|
||||
ri = routeInfos[routeInfos.length - 1];
|
||||
}
|
||||
// Replace with updated route
|
||||
routeInfos.pop();
|
||||
routeInfos.push(routeInfo);
|
||||
}
|
||||
|
||||
ri = this.locationHistory[this.locationHistory.length - 1];
|
||||
while (ri && ri.id !== routeInfo.id) {
|
||||
this.locationHistory.pop();
|
||||
ri = this.locationHistory[this.locationHistory.length - 1];
|
||||
}
|
||||
// Replace with updated route
|
||||
this.locationHistory.pop();
|
||||
this.locationHistory.push(routeInfo);
|
||||
}
|
||||
|
||||
private _replace(routeInfo: RouteInfo) {
|
||||
const routeInfos = this._getRouteInfosByKey(routeInfo.tab);
|
||||
routeInfos && routeInfos.pop();
|
||||
this.locationHistory.pop();
|
||||
this._add(routeInfo);
|
||||
}
|
||||
|
||||
private _clear() {
|
||||
const keys = Object.keys(this.tabHistory);
|
||||
keys.forEach(k => this.tabHistory[k] = []);
|
||||
this.locationHistory = [];
|
||||
}
|
||||
|
||||
private _getRouteInfosByKey(key?: string) {
|
||||
let routeInfos: RouteInfo[] | undefined;
|
||||
if (key) {
|
||||
routeInfos = this.tabHistory[key];
|
||||
if (!routeInfos) {
|
||||
routeInfos = this.tabHistory[key] = [];
|
||||
}
|
||||
}
|
||||
return routeInfos;
|
||||
}
|
||||
|
||||
getFirstRouteInfoForTab(tab: string) {
|
||||
const routeInfos = this._getRouteInfosByKey(tab);
|
||||
if (routeInfos) {
|
||||
return routeInfos[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getCurrentRouteInfoForTab(tab?: string) {
|
||||
const routeInfos = this._getRouteInfosByKey(tab);
|
||||
if (routeInfos) {
|
||||
return routeInfos[routeInfos.length - 1];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
findLastLocation(routeInfo: RouteInfo) {
|
||||
const routeInfos = this._getRouteInfosByKey(routeInfo.tab);
|
||||
if (routeInfos) {
|
||||
for (let i = routeInfos.length - 2; i >= 0; i--) {
|
||||
const ri = routeInfos[i];
|
||||
if (ri) {
|
||||
if (ri.pathname === routeInfo.pushedByRoute) {
|
||||
return ri;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = this.locationHistory.length - 2; i >= 0; i--) {
|
||||
const ri = this.locationHistory[i];
|
||||
if (ri) {
|
||||
if (ri.pathname === routeInfo.pushedByRoute) {
|
||||
return ri;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
previous() {
|
||||
return this.locationHistory[this.locationHistory.length - 2] || this.locationHistory[this.locationHistory.length - 1];
|
||||
}
|
||||
|
||||
current() {
|
||||
return this.locationHistory[this.locationHistory.length - 1];
|
||||
}
|
||||
|
||||
canGoBack() {
|
||||
return this.locationHistory.length > 1;
|
||||
}
|
||||
}
|
101
packages/react/src/routing/NavManager.tsx
Normal file
101
packages/react/src/routing/NavManager.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
import { AnimationBuilder } from '@ionic/core';
|
||||
import React from 'react';
|
||||
|
||||
import { IonRouterContext, IonRouterContextState } from '../components/IonRouterContext';
|
||||
import { NavContext, NavContextState } from '../contexts/NavContext';
|
||||
import { RouteAction } from '../models/RouteAction';
|
||||
import { RouteInfo } from '../models/RouteInfo';
|
||||
import { RouterDirection } from '../models/RouterDirection';
|
||||
import { RouterOptions } from '../models/RouterOptions';
|
||||
|
||||
import { LocationHistory } from './LocationHistory';
|
||||
import PageManager from './PageManager';
|
||||
|
||||
interface NavManagerProps {
|
||||
routeInfo: RouteInfo;
|
||||
onNavigateBack: (route?: string | RouteInfo, animationBuilder?: AnimationBuilder) => void;
|
||||
onNavigate: (path: string, action: RouteAction, direction?: RouterDirection, animationBuilder?: AnimationBuilder, options?: any, tab?: string) => void;
|
||||
onSetCurrentTab: (tab: string, routeInfo: RouteInfo) => void;
|
||||
onChangeTab: (tab: string, path: string, routeOptions?: any) => void;
|
||||
onResetTab: (tab: string, path: string, routeOptions?: any) => void;
|
||||
ionRedirect: any;
|
||||
ionRoute: any;
|
||||
stackManager: any;
|
||||
locationHistory: LocationHistory;
|
||||
}
|
||||
|
||||
export class NavManager extends React.Component<NavManagerProps, NavContextState> {
|
||||
|
||||
ionRouterContextValue: IonRouterContextState = {
|
||||
push: (pathname: string, routerDirection?: RouterDirection, routeAction?: RouteAction, routerOptions?: RouterOptions, animationBuilder?: AnimationBuilder) => {
|
||||
this.navigate(pathname, routerDirection, routeAction, animationBuilder, routerOptions);
|
||||
},
|
||||
back: (animationBuilder?: AnimationBuilder) => {
|
||||
this.goBack(undefined, animationBuilder);
|
||||
},
|
||||
canGoBack: () => this.props.locationHistory.canGoBack(),
|
||||
routeInfo: this.props.routeInfo
|
||||
};
|
||||
|
||||
constructor(props: NavManagerProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
goBack: this.goBack.bind(this),
|
||||
hasIonicRouter: () => true,
|
||||
navigate: this.navigate.bind(this),
|
||||
getIonRedirect: this.getIonRedirect.bind(this),
|
||||
getIonRoute: this.getIonRoute.bind(this),
|
||||
getStackManager: this.getStackManager.bind(this),
|
||||
getPageManager: this.getPageManager.bind(this),
|
||||
routeInfo: this.props.routeInfo,
|
||||
setCurrentTab: this.props.onSetCurrentTab,
|
||||
changeTab: this.props.onChangeTab,
|
||||
resetTab: this.props.onResetTab,
|
||||
};
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('ionBackButton', (e: any) => {
|
||||
e.detail.register(0, (processNextHandler: () => void) => {
|
||||
this.goBack();
|
||||
processNextHandler();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
goBack(route?: string | RouteInfo, animationBuilder?: AnimationBuilder) {
|
||||
this.props.onNavigateBack(route, animationBuilder);
|
||||
}
|
||||
|
||||
navigate(path: string, direction: RouterDirection = 'forward', action: RouteAction = 'push', animationBuilder?: AnimationBuilder, options?: any, tab?: string) {
|
||||
this.props.onNavigate(path, action, direction, animationBuilder, options, tab);
|
||||
}
|
||||
|
||||
getPageManager() {
|
||||
return PageManager;
|
||||
}
|
||||
|
||||
getIonRedirect() {
|
||||
return this.props.ionRedirect;
|
||||
}
|
||||
|
||||
getIonRoute() {
|
||||
return this.props.ionRoute;
|
||||
}
|
||||
|
||||
getStackManager() {
|
||||
return this.props.stackManager;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<NavContext.Provider value={{ ...this.state, routeInfo: this.props.routeInfo }}>
|
||||
<IonRouterContext.Provider value={{ ...this.ionRouterContextValue, routeInfo: this.props.routeInfo }}>
|
||||
{this.props.children}
|
||||
</IonRouterContext.Provider>
|
||||
</NavContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
86
packages/react/src/routing/OutletPageManager.tsx
Normal file
86
packages/react/src/routing/OutletPageManager.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { IonRouterOutletInner } from '../components/inner-proxies';
|
||||
import { IonLifeCycleContext } from '../contexts/IonLifeCycleContext';
|
||||
import { RouteInfo } from '../models';
|
||||
|
||||
import { StackContext } from './StackContext';
|
||||
|
||||
interface OutletPageManagerProps {
|
||||
className?: string;
|
||||
forwardedRef?: React.RefObject<HTMLDivElement>;
|
||||
routeInfo?: RouteInfo;
|
||||
StackManager: any;
|
||||
}
|
||||
|
||||
export class OutletPageManager extends React.Component<OutletPageManagerProps> {
|
||||
ionLifeCycleContext!: React.ContextType<typeof IonLifeCycleContext>;
|
||||
context!: React.ContextType<typeof StackContext>;
|
||||
ionRouterOutlet: HTMLIonRouterOutletElement | undefined;
|
||||
|
||||
constructor(props: OutletPageManagerProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.ionRouterOutlet) {
|
||||
setTimeout(() => {
|
||||
this.context.registerIonPage(this.ionRouterOutlet!, this.props.routeInfo!);
|
||||
}, 25);
|
||||
|
||||
this.ionRouterOutlet.addEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
|
||||
this.ionRouterOutlet.addEventListener('ionViewDidEnter', this.ionViewDidEnterHandler.bind(this));
|
||||
this.ionRouterOutlet.addEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler.bind(this));
|
||||
this.ionRouterOutlet.addEventListener('ionViewDidLeave', this.ionViewDidLeaveHandler.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.ionRouterOutlet) {
|
||||
this.ionRouterOutlet.removeEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
|
||||
this.ionRouterOutlet.removeEventListener('ionViewDidEnter', this.ionViewDidEnterHandler.bind(this));
|
||||
this.ionRouterOutlet.removeEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler.bind(this));
|
||||
this.ionRouterOutlet.removeEventListener('ionViewDidLeave', this.ionViewDidLeaveHandler.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
ionViewWillEnterHandler() {
|
||||
this.ionLifeCycleContext.ionViewWillEnter();
|
||||
}
|
||||
|
||||
ionViewDidEnterHandler() {
|
||||
this.ionLifeCycleContext.ionViewDidEnter();
|
||||
}
|
||||
|
||||
ionViewWillLeaveHandler() {
|
||||
this.ionLifeCycleContext.ionViewWillLeave();
|
||||
}
|
||||
|
||||
ionViewDidLeaveHandler() {
|
||||
this.ionLifeCycleContext.ionViewDidLeave();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { StackManager, children, routeInfo, ...props } = this.props;
|
||||
return (
|
||||
<IonLifeCycleContext.Consumer>
|
||||
{context => {
|
||||
this.ionLifeCycleContext = context;
|
||||
return (
|
||||
<StackManager routeInfo={routeInfo}>
|
||||
<IonRouterOutletInner setRef={(val: HTMLIonRouterOutletElement) => this.ionRouterOutlet = val} {...props}>
|
||||
{children}
|
||||
</IonRouterOutletInner>
|
||||
</StackManager>
|
||||
);
|
||||
}}
|
||||
</IonLifeCycleContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
static get contextType() {
|
||||
return StackContext;
|
||||
}
|
||||
}
|
||||
export default OutletPageManager;
|
86
packages/react/src/routing/PageManager.tsx
Normal file
86
packages/react/src/routing/PageManager.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { IonLifeCycleContext } from '../contexts/IonLifeCycleContext';
|
||||
import { RouteInfo } from '../models';
|
||||
|
||||
import { StackContext } from './StackContext';
|
||||
|
||||
interface PageManagerProps {
|
||||
className?: string;
|
||||
forwardedRef?: React.RefObject<HTMLDivElement>;
|
||||
routeInfo?: RouteInfo;
|
||||
}
|
||||
|
||||
export class PageManager extends React.PureComponent<PageManagerProps> {
|
||||
ionLifeCycleContext!: React.ContextType<typeof IonLifeCycleContext>;
|
||||
context!: React.ContextType<typeof StackContext>;
|
||||
ionPageElementRef: React.RefObject<HTMLDivElement>;
|
||||
|
||||
constructor(props: PageManagerProps) {
|
||||
super(props);
|
||||
this.ionPageElementRef = this.props.forwardedRef || React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.ionPageElementRef.current) {
|
||||
this.context.registerIonPage(this.ionPageElementRef.current, this.props.routeInfo!);
|
||||
this.ionPageElementRef.current.addEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
|
||||
this.ionPageElementRef.current.addEventListener('ionViewDidEnter', this.ionViewDidEnterHandler.bind(this));
|
||||
this.ionPageElementRef.current.addEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler.bind(this));
|
||||
this.ionPageElementRef.current.addEventListener('ionViewDidLeave', this.ionViewDidLeaveHandler.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.ionPageElementRef.current) {
|
||||
this.ionPageElementRef.current.removeEventListener('ionViewWillEnter', this.ionViewWillEnterHandler.bind(this));
|
||||
this.ionPageElementRef.current.removeEventListener('ionViewDidEnter', this.ionViewDidEnterHandler.bind(this));
|
||||
this.ionPageElementRef.current.removeEventListener('ionViewWillLeave', this.ionViewWillLeaveHandler.bind(this));
|
||||
this.ionPageElementRef.current.removeEventListener('ionViewDidLeave', this.ionViewDidLeaveHandler.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
ionViewWillEnterHandler() {
|
||||
this.ionLifeCycleContext.ionViewWillEnter();
|
||||
}
|
||||
|
||||
ionViewDidEnterHandler() {
|
||||
this.ionLifeCycleContext.ionViewDidEnter();
|
||||
}
|
||||
|
||||
ionViewWillLeaveHandler() {
|
||||
this.ionLifeCycleContext.ionViewWillLeave();
|
||||
}
|
||||
|
||||
ionViewDidLeaveHandler() {
|
||||
this.ionLifeCycleContext.ionViewDidLeave();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, children, routeInfo, forwardedRef, ...props } = this.props;
|
||||
|
||||
return (
|
||||
<IonLifeCycleContext.Consumer>
|
||||
{context => {
|
||||
this.ionLifeCycleContext = context;
|
||||
const hidePageClass = this.context.isInOutlet() ? 'ion-page-invisible' : '';
|
||||
return (
|
||||
<div
|
||||
className={className ? `${className} ion-page ${hidePageClass}` : `ion-page ${hidePageClass}`}
|
||||
ref={this.ionPageElementRef}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</IonLifeCycleContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
static get contextType() {
|
||||
return StackContext;
|
||||
}
|
||||
}
|
||||
export default PageManager;
|
29
packages/react/src/routing/RouteManagerContext.ts
Normal file
29
packages/react/src/routing/RouteManagerContext.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
|
||||
import { RouteInfo } from '../models/RouteInfo';
|
||||
|
||||
import { ViewItem } from './ViewItem';
|
||||
|
||||
export interface RouteManagerContextState {
|
||||
addViewItem: (viewItem: ViewItem) => void;
|
||||
clearOutlet: (outletId: string) => void;
|
||||
createViewItem: (outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement) => ViewItem;
|
||||
findLeavingViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
|
||||
// findViewItemByPathname: (pathname: string, outletId?: string) => ViewItem | undefined;
|
||||
findViewItemByRouteInfo: (routeInfo: RouteInfo, outletId?: string) => ViewItem | undefined;
|
||||
getChildrenToRender: (outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo, reRender: () => void) => React.ReactNode[];
|
||||
getViewItemForTransition: (pathname: string) => ViewItem | undefined;
|
||||
unMountViewItem: (viewItem: ViewItem) => void;
|
||||
}
|
||||
|
||||
export const RouteManagerContext = /*@__PURE__*/React.createContext<RouteManagerContextState>({
|
||||
addViewItem: () => undefined,
|
||||
clearOutlet: () => undefined,
|
||||
createViewItem: () => undefined as any,
|
||||
findLeavingViewItemByRouteInfo: () => undefined,
|
||||
// findViewItemByPathname: () => undefined,
|
||||
findViewItemByRouteInfo: () => undefined,
|
||||
getChildrenToRender: () => undefined as any,
|
||||
getViewItemForTransition: () => undefined,
|
||||
unMountViewItem: () => undefined,
|
||||
});
|
13
packages/react/src/routing/StackContext.tsx
Normal file
13
packages/react/src/routing/StackContext.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
|
||||
import { RouteInfo } from '../models/RouteInfo';
|
||||
|
||||
export interface StackContextState {
|
||||
registerIonPage: (page: HTMLElement, routeInfo: RouteInfo) => void;
|
||||
isInOutlet: () => boolean;
|
||||
}
|
||||
|
||||
export const StackContext = React.createContext<StackContextState>({
|
||||
registerIonPage: () => undefined,
|
||||
isInOutlet: () => false
|
||||
});
|
13
packages/react/src/routing/ViewItem.ts
Normal file
13
packages/react/src/routing/ViewItem.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
export interface ViewItem<T = any> {
|
||||
id: string;
|
||||
reactElement: ReactElement;
|
||||
ionPageElement?: HTMLElement | undefined;
|
||||
ionRoute?: boolean;
|
||||
mount: boolean;
|
||||
routeData?: T;
|
||||
transitionHtml?: string;
|
||||
outletId: string;
|
||||
disableIonPageManagement?: boolean;
|
||||
}
|
53
packages/react/src/routing/ViewLifeCycleManager.tsx
Normal file
53
packages/react/src/routing/ViewLifeCycleManager.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { DefaultIonLifeCycleContext, IonLifeCycleContext } from '../contexts/IonLifeCycleContext';
|
||||
|
||||
interface ViewTransitionManagerProps {
|
||||
removeView: () => void;
|
||||
mount: boolean;
|
||||
}
|
||||
|
||||
interface ViewTransitionManagerState {
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
export class ViewLifeCycleManager extends React.Component<ViewTransitionManagerProps, ViewTransitionManagerState> {
|
||||
ionLifeCycleContext = new DefaultIonLifeCycleContext();
|
||||
private _isMounted = false;
|
||||
|
||||
constructor(props: ViewTransitionManagerProps) {
|
||||
super(props);
|
||||
|
||||
this.ionLifeCycleContext.onComponentCanBeDestroyed(() => {
|
||||
if (!this.props.mount) {
|
||||
if (this._isMounted) {
|
||||
this.setState({
|
||||
show: false
|
||||
}, () => this.props.removeView());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.state = {
|
||||
show: true
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._isMounted = true;
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._isMounted = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { show } = this.state;
|
||||
return (
|
||||
<IonLifeCycleContext.Provider value={this.ionLifeCycleContext}>
|
||||
{show && this.props.children}
|
||||
</IonLifeCycleContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
67
packages/react/src/routing/ViewStacks.ts
Normal file
67
packages/react/src/routing/ViewStacks.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { RouteInfo } from '../models/RouteInfo';
|
||||
|
||||
import { ViewItem } from './ViewItem';
|
||||
|
||||
export abstract class ViewStacks {
|
||||
private viewStacks: { [key: string]: ViewItem[]; } = {};
|
||||
|
||||
constructor() {
|
||||
this.add = this.add.bind(this);
|
||||
this.clear = this.clear.bind(this);
|
||||
this.getViewItemsForOutlet = this.getViewItemsForOutlet.bind(this);
|
||||
this.remove = this.remove.bind(this);
|
||||
}
|
||||
|
||||
add(viewItem: ViewItem) {
|
||||
const { outletId } = viewItem;
|
||||
if (!this.viewStacks[outletId]) {
|
||||
this.viewStacks[outletId] = [viewItem];
|
||||
} else {
|
||||
this.viewStacks[outletId].push(viewItem);
|
||||
}
|
||||
}
|
||||
|
||||
clear(outletId: string) {
|
||||
// Give some time for the leaving views to transition before removing
|
||||
setTimeout(() => {
|
||||
// console.log('Removing viewstack for outletID ' + outletId);
|
||||
delete this.viewStacks[outletId];
|
||||
}, 500);
|
||||
}
|
||||
|
||||
getViewItemsForOutlet(outletId: string) {
|
||||
return (this.viewStacks[outletId] || []);
|
||||
}
|
||||
|
||||
remove(viewItem: ViewItem) {
|
||||
const { outletId } = viewItem;
|
||||
const viewStack = this.viewStacks[outletId];
|
||||
if (viewStack) {
|
||||
const viewItemToRemove = viewStack.find(x => x.id === viewItem.id);
|
||||
if (viewItemToRemove) {
|
||||
viewItemToRemove.mount = false;
|
||||
this.viewStacks[outletId] = viewStack.filter(x => x.id !== viewItemToRemove.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected getStackIds() {
|
||||
return Object.keys(this.viewStacks);
|
||||
}
|
||||
|
||||
protected getAllViewItems() {
|
||||
const keys = this.getStackIds();
|
||||
const viewItems: ViewItem[] = [];
|
||||
keys.forEach(k => {
|
||||
viewItems.push(...this.viewStacks[k]);
|
||||
});
|
||||
return viewItems;
|
||||
}
|
||||
|
||||
abstract createViewItem(outletId: string, reactElement: React.ReactElement, routeInfo: RouteInfo, page?: HTMLElement): ViewItem;
|
||||
// abstract findViewItemByPathname(pathname: string, outletId?: string): ViewItem | undefined;
|
||||
abstract findViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string): ViewItem | undefined;
|
||||
abstract findLeavingViewItemByRouteInfo(routeInfo: RouteInfo, outletId?: string): ViewItem | undefined;
|
||||
abstract getChildrenToRender(outletId: string, ionRouterOutlet: React.ReactElement, routeInfo: RouteInfo, reRender: () => void, setInTransition: () => void): React.ReactNode[];
|
||||
abstract getViewItemForTransition(pathname: string): ViewItem | undefined;
|
||||
}
|
7
packages/react/src/routing/index.ts
Normal file
7
packages/react/src/routing/index.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export * from './RouteManagerContext';
|
||||
export * from './ViewLifeCycleManager';
|
||||
export * from './LocationHistory';
|
||||
export * from './NavManager';
|
||||
export * from './ViewItem';
|
||||
export * from './StackContext';
|
||||
export * from './ViewStacks';
|
8
packages/react/src/routing/package.json
Normal file
8
packages/react/src/routing/package.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "@ionic/react/routing",
|
||||
"version": "5.0.1",
|
||||
"module": "index.mjs",
|
||||
"main": "index.js",
|
||||
"typings": "../dist/types/routing/index.d.ts",
|
||||
"private": true
|
||||
}
|
Reference in New Issue
Block a user