mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
Compare commits
1 Commits
sp/next
...
sp/refacto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f56f069e5 |
@@ -1,6 +1,7 @@
|
|||||||
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
|
import type { RouteInfo, StackContextState, ViewItem } from '@ionic/react';
|
||||||
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
|
import { RouteManagerContext, StackContext, generateId, getConfig } from '@ionic/react';
|
||||||
import React from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
|
import React, { cloneElement, useContext, useEffect, useMemo, useReducer, useRef, useState } from 'react';
|
||||||
import { matchPath } from 'react-router-dom';
|
import { matchPath } from 'react-router-dom';
|
||||||
|
|
||||||
import { clonePageElement } from './clonePageElement';
|
import { clonePageElement } from './clonePageElement';
|
||||||
@@ -11,76 +12,93 @@ interface StackManagerProps {
|
|||||||
routeInfo: RouteInfo;
|
routeInfo: RouteInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
|
||||||
interface StackManagerState {}
|
|
||||||
|
|
||||||
const isViewVisible = (el: HTMLElement) =>
|
const isViewVisible = (el: HTMLElement) =>
|
||||||
!el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden');
|
!el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden');
|
||||||
|
|
||||||
export class StackManager extends React.PureComponent<StackManagerProps, StackManagerState> {
|
export const StackManager = ({ children, ...props }: PropsWithChildren<StackManagerProps>) => {
|
||||||
id: string;
|
const { routeInfo } = props;
|
||||||
context!: React.ContextType<typeof RouteManagerContext>;
|
const {
|
||||||
ionRouterOutlet?: React.ReactElement;
|
findViewItemByRouteInfo,
|
||||||
routerOutletElement: HTMLIonRouterOutletElement | undefined;
|
findLeavingViewItemByRouteInfo,
|
||||||
prevProps?: StackManagerProps;
|
findViewItemByPathname,
|
||||||
skipTransition: boolean;
|
createViewItem,
|
||||||
|
addViewItem,
|
||||||
|
goBack,
|
||||||
|
getChildrenToRender,
|
||||||
|
clearOutlet,
|
||||||
|
} = useContext(RouteManagerContext);
|
||||||
|
|
||||||
stackContextValue: StackContextState = {
|
const routerOutletRef = useRef<HTMLIonRouterOutletElement>();
|
||||||
registerIonPage: this.registerIonPage.bind(this),
|
const ionRouterOutletRef = useRef<React.ReactElement>();
|
||||||
|
const skipTransitionRef = useRef(false);
|
||||||
|
const clearOutletTimeout = useRef(null);
|
||||||
|
const pendingPageTransitionRef = useRef(false);
|
||||||
|
const prevProps = useRef<{ routeInfo: RouteInfo }>();
|
||||||
|
|
||||||
|
const forceUpdate = useReducer((x) => x + 1, 0)[1];
|
||||||
|
|
||||||
|
const [id] = useState(generateId('routerOutlet'));
|
||||||
|
|
||||||
|
const stackContextValue: StackContextState = useMemo(
|
||||||
|
() => ({
|
||||||
isInOutlet: () => true,
|
isInOutlet: () => true,
|
||||||
};
|
registerIonPage: (page: HTMLElement, routeInfo: RouteInfo) => {
|
||||||
|
const foundView = findViewItemByRouteInfo(routeInfo, id);
|
||||||
|
if (foundView) {
|
||||||
|
const oldPageElement = foundView.ionPageElement;
|
||||||
|
foundView.ionPageElement = page;
|
||||||
|
foundView.ionRoute = true;
|
||||||
|
|
||||||
private clearOutletTimeout: any;
|
|
||||||
private pendingPageTransition = false;
|
|
||||||
|
|
||||||
constructor(props: StackManagerProps) {
|
|
||||||
super(props);
|
|
||||||
this.registerIonPage = this.registerIonPage.bind(this);
|
|
||||||
this.transitionPage = this.transitionPage.bind(this);
|
|
||||||
this.handlePageTransition = this.handlePageTransition.bind(this);
|
|
||||||
this.id = generateId('routerOutlet');
|
|
||||||
this.prevProps = undefined;
|
|
||||||
this.skipTransition = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
if (this.clearOutletTimeout) {
|
|
||||||
/**
|
/**
|
||||||
* The clearOutlet integration with React Router is a bit hacky.
|
* React 18 will unmount and remount IonPage
|
||||||
* It uses a timeout to clear the outlet after a transition.
|
* elements in development mode when using createRoot.
|
||||||
* In React v18, components are mounted and unmounted in development mode
|
* This can cause duplicate page transitions to occur.
|
||||||
* to check for side effects.
|
|
||||||
*
|
|
||||||
* This clearTimeout prevents the outlet from being cleared when the component is re-mounted,
|
|
||||||
* which should only happen in development mode and as a result of a hot reload.
|
|
||||||
*/
|
*/
|
||||||
clearTimeout(this.clearOutletTimeout);
|
if (oldPageElement === page) {
|
||||||
}
|
return;
|
||||||
if (this.routerOutletElement) {
|
|
||||||
this.setupRouterOutlet(this.routerOutletElement);
|
|
||||||
this.handlePageTransition(this.props.routeInfo);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
handlePageTransition(routeInfo);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[routerOutletRef.current]
|
||||||
|
);
|
||||||
|
|
||||||
componentDidUpdate(prevProps: StackManagerProps) {
|
useEffect(() => {
|
||||||
const { pathname } = this.props.routeInfo;
|
const routerOutletElement = routerOutletRef.current;
|
||||||
const { pathname: prevPathname } = prevProps.routeInfo;
|
if (routerOutletElement) {
|
||||||
|
// Mount behavior for the initial route
|
||||||
|
setupRouterOutlet(routerOutletElement);
|
||||||
|
|
||||||
if (pathname !== prevPathname) {
|
handlePageTransition(routeInfo);
|
||||||
this.prevProps = prevProps;
|
|
||||||
this.handlePageTransition(this.props.routeInfo);
|
|
||||||
} else if (this.pendingPageTransition) {
|
|
||||||
this.handlePageTransition(this.props.routeInfo);
|
|
||||||
this.pendingPageTransition = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
componentWillUnmount() {
|
useEffect(() => {
|
||||||
this.clearOutletTimeout = this.context.clearOutlet(this.id);
|
const { pathname } = routeInfo;
|
||||||
}
|
|
||||||
|
|
||||||
async handlePageTransition(routeInfo: RouteInfo) {
|
if (pathname !== prevProps.current?.routeInfo.pathname) {
|
||||||
if (!this.routerOutletElement || !this.routerOutletElement.commit) {
|
prevProps.current = props;
|
||||||
|
handlePageTransition(routeInfo);
|
||||||
|
} else if (pendingPageTransitionRef.current) {
|
||||||
|
handlePageTransition(routeInfo);
|
||||||
|
pendingPageTransitionRef.current = false;
|
||||||
|
}
|
||||||
|
}, [routeInfo]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
clearOutlet(id);
|
||||||
|
if (clearOutletTimeout.current) {
|
||||||
|
clearTimeout(clearOutletTimeout.current);
|
||||||
|
clearOutletTimeout.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handlePageTransition = (routeInfo: RouteInfo) => {
|
||||||
|
const routerOutletElement = routerOutletRef.current;
|
||||||
|
if (!routerOutletElement || !routerOutletElement.commit) {
|
||||||
/**
|
/**
|
||||||
* The route outlet has not mounted yet. We need to wait for it to render
|
* The route outlet has not mounted yet. We need to wait for it to render
|
||||||
* before we can transition the page.
|
* before we can transition the page.
|
||||||
@@ -88,16 +106,18 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
* Set a flag to indicate that we should transition the page after
|
* Set a flag to indicate that we should transition the page after
|
||||||
* the component has updated.
|
* the component has updated.
|
||||||
*/
|
*/
|
||||||
this.pendingPageTransition = true;
|
pendingPageTransitionRef.current = true;
|
||||||
} else {
|
return;
|
||||||
let enteringViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id);
|
|
||||||
let leavingViewItem = this.context.findLeavingViewItemByRouteInfo(routeInfo, this.id);
|
|
||||||
|
|
||||||
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
|
|
||||||
leavingViewItem = this.context.findViewItemByPathname(routeInfo.prevRouteLastPathname, this.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if leavingViewItem should be unmounted
|
let enteringViewItem = findViewItemByRouteInfo(routeInfo, id);
|
||||||
|
let leavingViewItem = findLeavingViewItemByRouteInfo(routeInfo, id);
|
||||||
|
|
||||||
|
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
|
||||||
|
leavingViewItem = findViewItemByPathname(routeInfo.prevRouteLastPathname, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the leavingViewItem should be unmounted
|
||||||
if (leavingViewItem) {
|
if (leavingViewItem) {
|
||||||
if (routeInfo.routeAction === 'replace') {
|
if (routeInfo.routeAction === 'replace') {
|
||||||
leavingViewItem.mount = false;
|
leavingViewItem.mount = false;
|
||||||
@@ -110,13 +130,15 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const enteringRoute = matchRoute(this.ionRouterOutlet?.props.children, routeInfo) as React.ReactElement;
|
const enteringRoute = matchRoute(ionRouterOutletRef.current?.props.children, routeInfo) as React.ReactElement;
|
||||||
|
|
||||||
if (enteringViewItem) {
|
if (enteringViewItem) {
|
||||||
|
// If the entering view is already in the stack, then we need to clone it
|
||||||
enteringViewItem.reactElement = enteringRoute;
|
enteringViewItem.reactElement = enteringRoute;
|
||||||
} else if (enteringRoute) {
|
} else if (enteringRoute) {
|
||||||
enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo);
|
// Otherwise we need to create a new view item
|
||||||
this.context.addViewItem(enteringViewItem);
|
enteringViewItem = createViewItem(id, enteringRoute, routeInfo);
|
||||||
|
addViewItem(enteringViewItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enteringViewItem && enteringViewItem.ionPageElement) {
|
if (enteringViewItem && enteringViewItem.ionPageElement) {
|
||||||
@@ -143,8 +165,8 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
* that the user has routed from a previous path, then we need
|
* that the user has routed from a previous path, then we need
|
||||||
* to find the leaving view item to transition between.
|
* to find the leaving view item to transition between.
|
||||||
*/
|
*/
|
||||||
if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) {
|
if (!leavingViewItem && routeInfo.prevRouteLastPathname) {
|
||||||
leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id);
|
leavingViewItem = findViewItemByPathname(routeInfo.prevRouteLastPathname, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -173,57 +195,37 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
* route or on an initial page load (i.e. refreshing). In cases when loading
|
* route or on an initial page load (i.e. refreshing). In cases when loading
|
||||||
* /tabs/tab-1, we need to transition the /tabs page element into the view.
|
* /tabs/tab-1, we need to transition the /tabs page element into the view.
|
||||||
*/
|
*/
|
||||||
this.transitionPage(routeInfo, enteringViewItem, leavingViewItem);
|
transitionPage(routeInfo, enteringViewItem, leavingViewItem!);
|
||||||
} else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
|
} else if (leavingViewItem && !enteringRoute && !enteringViewItem) {
|
||||||
// If we have a leavingView but no entering view/route, we are probably leaving to
|
// If we have a leavingView but no entering view/route, we are probably leaving to
|
||||||
// another outlet, so hide this leavingView. We do it in a timeout to give time for a
|
// another outlet, so hide this leavingView.
|
||||||
// transition to finish.
|
|
||||||
// setTimeout(() => {
|
|
||||||
if (leavingViewItem.ionPageElement) {
|
if (leavingViewItem.ionPageElement) {
|
||||||
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
||||||
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
||||||
}
|
}
|
||||||
// }, 250);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.forceUpdate();
|
// This causes the router outlet to re-render with the updated view items.
|
||||||
}
|
// Without it, a push navigation will remove the previous route's view item,
|
||||||
}
|
// but will not render the new route's view item.
|
||||||
|
forceUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
registerIonPage(page: HTMLElement, routeInfo: RouteInfo) {
|
const setupRouterOutlet = (routerOutlet: HTMLIonRouterOutletElement) => {
|
||||||
const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id);
|
const canStart = (): boolean => {
|
||||||
if (foundView) {
|
|
||||||
const oldPageElement = foundView.ionPageElement;
|
|
||||||
foundView.ionPageElement = page;
|
|
||||||
foundView.ionRoute = true;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* React 18 will unmount and remount IonPage
|
|
||||||
* elements in development mode when using createRoot.
|
|
||||||
* This can cause duplicate page transitions to occur.
|
|
||||||
*/
|
|
||||||
if (oldPageElement === page) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.handlePageTransition(routeInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
async setupRouterOutlet(routerOutlet: HTMLIonRouterOutletElement) {
|
|
||||||
const canStart = () => {
|
|
||||||
const config = getConfig();
|
const config = getConfig();
|
||||||
const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios');
|
const swipeEnabled = config?.getBoolean('swipeBackEnabled', routerOutlet.mode === 'ios');
|
||||||
|
|
||||||
if (!swipeEnabled) {
|
if (!swipeEnabled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { routeInfo } = this.props;
|
|
||||||
|
|
||||||
const propsToUse =
|
const propsToUse =
|
||||||
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
|
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
|
||||||
? this.prevProps.routeInfo
|
? prevProps.current!.routeInfo
|
||||||
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
|
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
|
||||||
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
|
|
||||||
|
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!!enteringViewItem &&
|
!!enteringViewItem &&
|
||||||
@@ -247,14 +249,12 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onStart = async () => {
|
const onStart = async () => {
|
||||||
const { routeInfo } = this.props;
|
|
||||||
|
|
||||||
const propsToUse =
|
const propsToUse =
|
||||||
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
|
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
|
||||||
? this.prevProps.routeInfo
|
? prevProps.current!.routeInfo
|
||||||
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
|
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
|
||||||
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
|
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
|
||||||
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When the gesture starts, kick off
|
* When the gesture starts, kick off
|
||||||
@@ -262,30 +262,28 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
* via a swipe gesture.
|
* via a swipe gesture.
|
||||||
*/
|
*/
|
||||||
if (enteringViewItem && leavingViewItem) {
|
if (enteringViewItem && leavingViewItem) {
|
||||||
await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
|
await transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onEnd = (shouldContinue: boolean) => {
|
const onEnd = (shouldContinue: boolean) => {
|
||||||
if (shouldContinue) {
|
if (shouldContinue) {
|
||||||
this.skipTransition = true;
|
skipTransitionRef.current = true;
|
||||||
|
goBack();
|
||||||
this.context.goBack();
|
|
||||||
} else {
|
} else {
|
||||||
/**
|
/**
|
||||||
* In the event that the swipe
|
* In the event that the swipe
|
||||||
* gesture was aborted, we should
|
* gesture was aborted, we should
|
||||||
* re-hide the page that was going to enter.
|
* re-hide the page that was going to enter.
|
||||||
*/
|
*/
|
||||||
const { routeInfo } = this.props;
|
|
||||||
|
|
||||||
const propsToUse =
|
const propsToUse =
|
||||||
this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute
|
prevProps.current?.routeInfo.pathname === routeInfo.pushedByRoute
|
||||||
? this.prevProps.routeInfo
|
? prevProps.current!.routeInfo
|
||||||
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
|
: ({ pathname: routeInfo.pushedByRoute || '' } as any);
|
||||||
const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false);
|
const enteringViewItem = findViewItemByRouteInfo(propsToUse, id, false);
|
||||||
const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false);
|
const leavingViewItem = findViewItemByRouteInfo(routeInfo, id, false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ionic React has a design defect where it
|
* Ionic React has a design defect where it
|
||||||
@@ -309,17 +307,17 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
onStart,
|
onStart,
|
||||||
onEnd,
|
onEnd,
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
async transitionPage(
|
const transitionPage = async (
|
||||||
routeInfo: RouteInfo,
|
routeInfo: RouteInfo,
|
||||||
enteringViewItem: ViewItem,
|
enteringViewItem: ViewItem,
|
||||||
leavingViewItem?: ViewItem,
|
leavingViewItem: ViewItem,
|
||||||
direction?: 'forward' | 'back',
|
direction?: 'forward' | 'back',
|
||||||
progressAnimation = false
|
progressAnimation = false
|
||||||
) {
|
) => {
|
||||||
const runCommit = async (enteringEl: HTMLElement, leavingEl?: HTMLElement) => {
|
const runCommit = async (enteringEl: HTMLElement, leavingEl?: HTMLElement) => {
|
||||||
const skipTransition = this.skipTransition;
|
const skipTransition = skipTransitionRef.current;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the transition was handled
|
* If the transition was handled
|
||||||
@@ -342,67 +340,65 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
* transition triggered by handlePageTransition
|
* transition triggered by handlePageTransition
|
||||||
* in componentDidUpdate.
|
* in componentDidUpdate.
|
||||||
*/
|
*/
|
||||||
this.skipTransition = false;
|
skipTransitionRef.current = false;
|
||||||
} else {
|
} else {
|
||||||
enteringEl.classList.add('ion-page');
|
enteringEl.classList.add('ion-page', 'ion-page-invisible');
|
||||||
enteringEl.classList.add('ion-page-invisible');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await routerOutlet.commit(enteringEl, leavingEl, {
|
const routerOutletElement = routerOutletRef.current;
|
||||||
duration: skipTransition || directionToUse === undefined ? 0 : undefined,
|
|
||||||
|
if (routerOutletElement) {
|
||||||
|
await routerOutletElement.commit(enteringEl, leavingEl, {
|
||||||
|
duration: skipTransitionRef.current || directionToUse === undefined ? 0 : undefined,
|
||||||
direction: directionToUse,
|
direction: directionToUse,
|
||||||
showGoBack: !!routeInfo.pushedByRoute,
|
showGoBack: !!routeInfo.pushedByRoute,
|
||||||
progressAnimation,
|
progressAnimation,
|
||||||
animationBuilder: routeInfo.routeAnimation,
|
animationBuilder: routeInfo.routeAnimation,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const routerOutlet = this.routerOutletElement!;
|
const routerInfoFallbackDirection =
|
||||||
|
|
||||||
const routeInfoFallbackDirection =
|
|
||||||
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
|
routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection;
|
||||||
const directionToUse = direction ?? routeInfoFallbackDirection;
|
const directionToUse = direction ?? routerInfoFallbackDirection;
|
||||||
|
|
||||||
if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) {
|
const routerOutletElement = routerOutletRef.current;
|
||||||
if (leavingViewItem && leavingViewItem.ionPageElement && enteringViewItem === leavingViewItem) {
|
|
||||||
|
if (enteringViewItem?.ionPageElement && routerOutletElement) {
|
||||||
|
if (leavingViewItem?.ionPageElement && enteringViewItem === leavingViewItem) {
|
||||||
// If a page is transitioning to another version of itself
|
// If a page is transitioning to another version of itself
|
||||||
// we clone it so we can have an animation to show
|
// we clone it so we can have an animation to show
|
||||||
|
|
||||||
const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname, true);
|
const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname, true);
|
||||||
if (match) {
|
if (match) {
|
||||||
const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
|
const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML);
|
||||||
if (newLeavingElement) {
|
if (newLeavingElement) {
|
||||||
this.routerOutletElement.appendChild(newLeavingElement);
|
routerOutletElement.appendChild(newLeavingElement);
|
||||||
await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
|
await runCommit(enteringViewItem.ionPageElement, newLeavingElement);
|
||||||
this.routerOutletElement.removeChild(newLeavingElement);
|
routerOutletElement.removeChild(newLeavingElement);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await runCommit(enteringViewItem.ionPageElement, undefined);
|
await runCommit(enteringViewItem.ionPageElement, undefined);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
|
await runCommit(enteringViewItem.ionPageElement, leavingViewItem?.ionPageElement);
|
||||||
if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) {
|
if (leavingViewItem?.ionPageElement && !progressAnimation) {
|
||||||
leavingViewItem.ionPageElement.classList.add('ion-page-hidden');
|
const { ionPageElement } = leavingViewItem;
|
||||||
leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true');
|
ionPageElement.setAttribute('aria-hidden', 'true');
|
||||||
}
|
ionPageElement.classList.add('ion-page-hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
const renderComponents = () => {
|
||||||
const { children } = this.props;
|
const ionRouterOutlet = React.Children.only<React.ReactElement>(children as React.ReactElement);
|
||||||
const ionRouterOutlet = React.Children.only(children) as React.ReactElement;
|
ionRouterOutletRef.current = ionRouterOutlet;
|
||||||
this.ionRouterOutlet = ionRouterOutlet;
|
|
||||||
|
|
||||||
const components = this.context.getChildrenToRender(this.id, this.ionRouterOutlet, this.props.routeInfo, () => {
|
const components = getChildrenToRender(id, ionRouterOutlet, routeInfo, () => {
|
||||||
this.forceUpdate();
|
forceUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return cloneElement(ionRouterOutlet, {
|
||||||
<StackContext.Provider value={this.stackContextValue}>
|
|
||||||
{React.cloneElement(
|
|
||||||
ionRouterOutlet as any,
|
|
||||||
{
|
|
||||||
ref: (node: HTMLIonRouterOutletElement) => {
|
ref: (node: HTMLIonRouterOutletElement) => {
|
||||||
if (ionRouterOutlet.props.setRef) {
|
if (ionRouterOutlet.props.setRef) {
|
||||||
ionRouterOutlet.props.setRef(node);
|
ionRouterOutlet.props.setRef(node);
|
||||||
@@ -410,34 +406,33 @@ export class StackManager extends React.PureComponent<StackManagerProps, StackMa
|
|||||||
if (ionRouterOutlet.props.forwardedRef) {
|
if (ionRouterOutlet.props.forwardedRef) {
|
||||||
ionRouterOutlet.props.forwardedRef.current = node;
|
ionRouterOutlet.props.forwardedRef.current = node;
|
||||||
}
|
}
|
||||||
this.routerOutletElement = node;
|
|
||||||
|
routerOutletRef.current = node;
|
||||||
|
|
||||||
const { ref } = ionRouterOutlet as any;
|
const { ref } = ionRouterOutlet as any;
|
||||||
if (typeof ref === 'function') {
|
if (typeof ref === 'function') {
|
||||||
ref(node);
|
ref(node);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
components,
|
||||||
components
|
});
|
||||||
)}
|
};
|
||||||
</StackContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static get contextType() {
|
return <StackContext.Provider value={stackContextValue}>{renderComponents()}</StackContext.Provider>;
|
||||||
return RouteManagerContext;
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default StackManager;
|
export default StackManager;
|
||||||
|
|
||||||
function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
|
function matchRoute(node: React.ReactNode, routeInfo: RouteInfo) {
|
||||||
let matchedNode: React.ReactNode;
|
let matchedNode: React.ReactNode;
|
||||||
|
|
||||||
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
|
React.Children.forEach(node as React.ReactElement, (child: React.ReactElement) => {
|
||||||
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,
|
||||||
component: child.props.component,
|
component: child.props.component,
|
||||||
};
|
};
|
||||||
|
// In React Router v6 the prop arguments are in a different order
|
||||||
const match = matchPath(routeInfo.pathname, matchProps);
|
const match = matchPath(routeInfo.pathname, matchProps);
|
||||||
if (match) {
|
if (match) {
|
||||||
matchedNode = child;
|
matchedNode = child;
|
||||||
@@ -464,6 +459,7 @@ function matchComponent(node: React.ReactElement, pathname: string, forceExact?:
|
|||||||
path: node.props.path || node.props.from,
|
path: node.props.path || node.props.from,
|
||||||
component: node.props.component,
|
component: node.props.component,
|
||||||
};
|
};
|
||||||
|
// In React Router v6 the prop arguments are in a different order
|
||||||
const match = matchPath(pathname, matchProps);
|
const match = matchPath(pathname, matchProps);
|
||||||
|
|
||||||
return match;
|
return match;
|
||||||
|
|||||||
Reference in New Issue
Block a user