UNPKG

@ionic/react-router

Version:
946 lines (935 loc) • 44 kB
import { __rest } from 'tslib'; import { createBrowserHistory, createHashHistory } from 'history'; import React from 'react'; import { withRouter, Router } from 'react-router-dom'; import { ViewStacks, generateId, IonRoute, ViewLifeCycleManager, StackContext, RouteManagerContext, getConfig, LocationHistory, NavManager } from '@ionic/react'; import { Route, matchPath as matchPath$1, Router as Router$1 } from 'react-router'; class IonRouteInner extends React.PureComponent { render() { return (React.createElement(Route, Object.assign({ path: this.props.path, exact: this.props.exact, render: this.props.render }, (this.props.computedMatch !== undefined ? { computedMatch: this.props.computedMatch, } : {})))); } } /** * @see https://v5.reactrouter.com/web/api/matchPath */ const matchPath = ({ pathname, componentProps, }) => { const { exact, component } = componentProps; const path = componentProps.path || componentProps.from; /*** * The props to match against, they are identical * to the matching props `Route` accepts. It could also be a string * or an array of strings as shortcut for `{ path }`. */ const matchProps = { exact, path, component, }; const match = matchPath$1(pathname, matchProps); if (!match) { return false; } return match; }; class ReactRouterViewStack extends ViewStacks { constructor() { super(); this.createViewItem = this.createViewItem.bind(this); this.findViewItemByRouteInfo = this.findViewItemByRouteInfo.bind(this); this.findLeavingViewItemByRouteInfo = this.findLeavingViewItemByRouteInfo.bind(this); this.getChildrenToRender = this.getChildrenToRender.bind(this); this.findViewItemByPathname = this.findViewItemByPathname.bind(this); } createViewItem(outletId, reactElement, routeInfo, page) { const viewItem = { id: generateId('viewItem'), outletId, ionPageElement: page, reactElement, mount: true, ionRoute: false, }; if (reactElement.type === IonRoute) { viewItem.ionRoute = true; viewItem.disableIonPageManagement = reactElement.props.disableIonPageManagement; } viewItem.routeData = { match: matchPath({ pathname: routeInfo.pathname, componentProps: reactElement.props, }), childProps: reactElement.props, }; return viewItem; } getChildrenToRender(outletId, ionRouterOutlet, routeInfo) { const viewItems = this.getViewItemsForOutlet(outletId); // Sync latest routes with viewItems React.Children.forEach(ionRouterOutlet.props.children, (child) => { const viewItem = viewItems.find((v) => { return matchComponent$1(child, v.routeData.childProps.path || v.routeData.childProps.from); }); if (viewItem) { viewItem.reactElement = child; } }); const children = viewItems.map((viewItem) => { let clonedChild; if (viewItem.ionRoute && !viewItem.disableIonPageManagement) { clonedChild = (React.createElement(ViewLifeCycleManager, { key: `view-${viewItem.id}`, mount: viewItem.mount, removeView: () => this.remove(viewItem) }, React.cloneElement(viewItem.reactElement, { computedMatch: viewItem.routeData.match, }))); } else { const match = matchComponent$1(viewItem.reactElement, routeInfo.pathname); clonedChild = (React.createElement(ViewLifeCycleManager, { key: `view-${viewItem.id}`, mount: viewItem.mount, removeView: () => this.remove(viewItem) }, React.cloneElement(viewItem.reactElement, { computedMatch: viewItem.routeData.match, }))); if (!match && viewItem.routeData.match) { viewItem.routeData.match = undefined; viewItem.mount = false; } } return clonedChild; }); return children; } findViewItemByRouteInfo(routeInfo, outletId, updateMatch) { const { viewItem, match } = this.findViewItemByPath(routeInfo.pathname, outletId); const shouldUpdateMatch = updateMatch === undefined || updateMatch === true; if (shouldUpdateMatch && viewItem && match) { viewItem.routeData.match = match; } return viewItem; } findLeavingViewItemByRouteInfo(routeInfo, outletId, mustBeIonRoute = true) { const { viewItem } = this.findViewItemByPath(routeInfo.lastPathname, outletId, mustBeIonRoute); return viewItem; } findViewItemByPathname(pathname, outletId) { const { viewItem } = this.findViewItemByPath(pathname, outletId); return viewItem; } /** * Returns the matching view item and the match result for a given pathname. */ findViewItemByPath(pathname, outletId, mustBeIonRoute) { let viewItem; let match; let viewStack; if (outletId) { viewStack = this.getViewItemsForOutlet(outletId); viewStack.some(matchView); if (!viewItem) { viewStack.some(matchDefaultRoute); } } else { const viewItems = this.getAllViewItems(); viewItems.some(matchView); if (!viewItem) { viewItems.some(matchDefaultRoute); } } return { viewItem, match }; function matchView(v) { var _a, _b; if (mustBeIonRoute && !v.ionRoute) { return false; } match = matchPath({ pathname, componentProps: v.routeData.childProps, }); if (match) { /** * Even though we have a match from react-router, we do not know if the match * is for this specific view item. * * To validate this, we need to check if the path and url match the view item's route data. */ const hasParameter = match.path.includes(':'); if (!hasParameter || (hasParameter && match.url === ((_b = (_a = v.routeData) === null || _a === void 0 ? void 0 : _a.match) === null || _b === void 0 ? void 0 : _b.url))) { viewItem = v; return true; } } return false; } function matchDefaultRoute(v) { // try to find a route that doesn't have a path or from prop, that will be our default route if (!v.routeData.childProps.path && !v.routeData.childProps.from) { match = { path: pathname, url: pathname, isExact: true, params: {}, }; viewItem = v; return true; } return false; } } } function matchComponent$1(node, pathname) { return matchPath({ pathname, componentProps: node.props, }); } function clonePageElement(leavingViewHtml) { let html; if (typeof leavingViewHtml === 'string') { html = leavingViewHtml; } else { html = leavingViewHtml.outerHTML; } if (document) { const newEl = document.createElement('div'); newEl.innerHTML = html; 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].remove(); } return newEl.firstChild; } return undefined; } const isViewVisible = (el) => !el.classList.contains('ion-page-invisible') && !el.classList.contains('ion-page-hidden'); class StackManager extends React.PureComponent { constructor(props) { super(props); this.stackContextValue = { registerIonPage: this.registerIonPage.bind(this), isInOutlet: () => true, }; this.pendingPageTransition = false; 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. * It uses a timeout to clear the outlet after a transition. * In React v18, components are mounted and unmounted in development mode * 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 (this.routerOutletElement) { this.setupRouterOutlet(this.routerOutletElement); this.handlePageTransition(this.props.routeInfo); } } componentDidUpdate(prevProps) { const { pathname } = this.props.routeInfo; const { pathname: prevPathname } = prevProps.routeInfo; if (pathname !== prevPathname) { this.prevProps = prevProps; this.handlePageTransition(this.props.routeInfo); } else if (this.pendingPageTransition) { this.handlePageTransition(this.props.routeInfo); this.pendingPageTransition = false; } } componentWillUnmount() { this.clearOutletTimeout = this.context.clearOutlet(this.id); } async handlePageTransition(routeInfo) { var _a, _b; if (!this.routerOutletElement || !this.routerOutletElement.commit) { /** * The route outlet has not mounted yet. We need to wait for it to render * before we can transition the page. * * Set a flag to indicate that we should transition the page after * the component has updated. */ this.pendingPageTransition = true; } else { 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 if (leavingViewItem) { if (routeInfo.routeAction === 'replace') { leavingViewItem.mount = false; } else if (!(routeInfo.routeAction === 'push' && routeInfo.routeDirection === 'forward')) { if (routeInfo.routeDirection !== 'none' && enteringViewItem !== leavingViewItem) { leavingViewItem.mount = false; } } else if ((_a = routeInfo.routeOptions) === null || _a === void 0 ? void 0 : _a.unmount) { leavingViewItem.mount = false; } } const enteringRoute = matchRoute((_b = this.ionRouterOutlet) === null || _b === void 0 ? void 0 : _b.props.children, routeInfo); if (enteringViewItem) { enteringViewItem.reactElement = enteringRoute; } else if (enteringRoute) { enteringViewItem = this.context.createViewItem(this.id, enteringRoute, routeInfo); this.context.addViewItem(enteringViewItem); } if (enteringViewItem && enteringViewItem.ionPageElement) { /** * If the entering view item is the same as the leaving view item, * then we don't need to transition. */ if (enteringViewItem === leavingViewItem) { /** * If the entering view item is the same as the leaving view item, * we are either transitioning using parameterized routes to the same view * or a parent router outlet is re-rendering as a result of React props changing. * * If the route data does not match the current path, the parent router outlet * is attempting to transition and we cancel the operation. */ if (enteringViewItem.routeData.match.url !== routeInfo.pathname) { return; } } /** * If there isn't a leaving view item, but the route info indicates * that the user has routed from a previous path, then we need * to find the leaving view item to transition between. */ if (!leavingViewItem && this.props.routeInfo.prevRouteLastPathname) { leavingViewItem = this.context.findViewItemByPathname(this.props.routeInfo.prevRouteLastPathname, this.id); } /** * If the entering view is already visible and the leaving view is not, the transition does not need to occur. */ if (isViewVisible(enteringViewItem.ionPageElement) && leavingViewItem !== undefined && !isViewVisible(leavingViewItem.ionPageElement)) { return; } /** * The view should only be transitioned in the following cases: * 1. Performing a replace or pop action, such as a swipe to go back gesture * to animation the leaving view off the screen. * * 2. Navigating between top-level router outlets, such as /page-1 to /page-2; * or navigating within a nested outlet, such as /tabs/tab-1 to /tabs/tab-2. * * 3. The entering view is an ion-router-outlet containing a page * matching the current route and that hasn't already transitioned in. * * This should only happen when navigating directly to a nested router outlet * 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. */ this.transitionPage(routeInfo, enteringViewItem, leavingViewItem); } else if (leavingViewItem && !enteringRoute && !enteringViewItem) { // 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 // transition to finish. // setTimeout(() => { if (leavingViewItem.ionPageElement) { leavingViewItem.ionPageElement.classList.add('ion-page-hidden'); leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true'); } // }, 250); } this.forceUpdate(); } } registerIonPage(page, routeInfo) { const foundView = this.context.findViewItemByRouteInfo(routeInfo, this.id); 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) { const canStart = () => { const config = getConfig(); const swipeEnabled = config && config.get('swipeBackEnabled', routerOutlet.mode === 'ios'); if (!swipeEnabled) { return false; } const { routeInfo } = this.props; const propsToUse = this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute ? this.prevProps.routeInfo : { pathname: routeInfo.pushedByRoute || '' }; const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false); return (!!enteringViewItem && /** * The root url '/' is treated as * the first view item (but is never mounted), * so we do not want to swipe back to the * root url. */ enteringViewItem.mount && /** * When on the first page (whatever view * you land on after the root url) it * is possible for findViewItemByRouteInfo to * return the exact same view you are currently on. * Make sure that we are not swiping back to the same * instances of a view. */ enteringViewItem.routeData.match.path !== routeInfo.pathname); }; const onStart = async () => { const { routeInfo } = this.props; const propsToUse = this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute ? this.prevProps.routeInfo : { pathname: routeInfo.pushedByRoute || '' }; const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false); const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false); /** * When the gesture starts, kick off * a transition that is controlled * via a swipe gesture. */ if (enteringViewItem && leavingViewItem) { await this.transitionPage(routeInfo, enteringViewItem, leavingViewItem, 'back', true); } return Promise.resolve(); }; const onEnd = (shouldContinue) => { if (shouldContinue) { this.skipTransition = true; this.context.goBack(); } else { /** * In the event that the swipe * gesture was aborted, we should * re-hide the page that was going to enter. */ const { routeInfo } = this.props; const propsToUse = this.prevProps && this.prevProps.routeInfo.pathname === routeInfo.pushedByRoute ? this.prevProps.routeInfo : { pathname: routeInfo.pushedByRoute || '' }; const enteringViewItem = this.context.findViewItemByRouteInfo(propsToUse, this.id, false); const leavingViewItem = this.context.findViewItemByRouteInfo(routeInfo, this.id, false); /** * Ionic React has a design defect where it * a) Unmounts the leaving view item when using parameterized routes * b) Considers the current view to be the entering view when using * parameterized routes * * As a result, we should not hide the view item here * as it will cause the current view to be hidden. */ if (enteringViewItem !== leavingViewItem && (enteringViewItem === null || enteringViewItem === void 0 ? void 0 : enteringViewItem.ionPageElement) !== undefined) { const { ionPageElement } = enteringViewItem; ionPageElement.setAttribute('aria-hidden', 'true'); ionPageElement.classList.add('ion-page-hidden'); } } }; routerOutlet.swipeHandler = { canStart, onStart, onEnd, }; } async transitionPage(routeInfo, enteringViewItem, leavingViewItem, direction, progressAnimation = false) { const runCommit = async (enteringEl, leavingEl) => { const skipTransition = this.skipTransition; /** * If the transition was handled * via the swipe to go back gesture, * then we do not want to perform * another transition. * * We skip adding ion-page or ion-page-invisible * because the entering view already exists in the DOM. * If we added the classes, there would be a flicker where * the view would be briefly hidden. */ if (skipTransition) { /** * We need to reset skipTransition before * we call routerOutlet.commit otherwise * the transition triggered by the swipe * to go back gesture would reset it. In * that case you would see a duplicate * transition triggered by handlePageTransition * in componentDidUpdate. */ this.skipTransition = false; } else { enteringEl.classList.add('ion-page'); enteringEl.classList.add('ion-page-invisible'); } await routerOutlet.commit(enteringEl, leavingEl, { duration: skipTransition || directionToUse === undefined ? 0 : undefined, direction: directionToUse, showGoBack: !!routeInfo.pushedByRoute, progressAnimation, animationBuilder: routeInfo.routeAnimation, }); }; const routerOutlet = this.routerOutletElement; const routeInfoFallbackDirection = routeInfo.routeDirection === 'none' || routeInfo.routeDirection === 'root' ? undefined : routeInfo.routeDirection; const directionToUse = direction !== null && direction !== void 0 ? direction : routeInfoFallbackDirection; if (enteringViewItem && enteringViewItem.ionPageElement && this.routerOutletElement) { if (leavingViewItem && leavingViewItem.ionPageElement && enteringViewItem === leavingViewItem) { // If a page is transitioning to another version of itself // we clone it so we can have an animation to show const match = matchComponent(leavingViewItem.reactElement, routeInfo.pathname, true); if (match) { const newLeavingElement = clonePageElement(leavingViewItem.ionPageElement.outerHTML); if (newLeavingElement) { this.routerOutletElement.appendChild(newLeavingElement); await runCommit(enteringViewItem.ionPageElement, newLeavingElement); this.routerOutletElement.removeChild(newLeavingElement); } } else { await runCommit(enteringViewItem.ionPageElement, undefined); } } else { await runCommit(enteringViewItem.ionPageElement, leavingViewItem === null || leavingViewItem === void 0 ? void 0 : leavingViewItem.ionPageElement); if (leavingViewItem && leavingViewItem.ionPageElement && !progressAnimation) { leavingViewItem.ionPageElement.classList.add('ion-page-hidden'); leavingViewItem.ionPageElement.setAttribute('aria-hidden', 'true'); } } } } render() { const { children } = this.props; const ionRouterOutlet = React.Children.only(children); this.ionRouterOutlet = ionRouterOutlet; const components = this.context.getChildrenToRender(this.id, this.ionRouterOutlet, this.props.routeInfo, () => { this.forceUpdate(); }); return (React.createElement(StackContext.Provider, { value: this.stackContextValue }, React.cloneElement(ionRouterOutlet, { ref: (node) => { if (ionRouterOutlet.props.setRef) { ionRouterOutlet.props.setRef(node); } if (ionRouterOutlet.props.forwardedRef) { ionRouterOutlet.props.forwardedRef.current = node; } this.routerOutletElement = node; const { ref } = ionRouterOutlet; if (typeof ref === 'function') { ref(node); } }, }, components))); } static get contextType() { return RouteManagerContext; } } function matchRoute(node, routeInfo) { let matchedNode; React.Children.forEach(node, (child) => { const match = matchPath({ pathname: routeInfo.pathname, componentProps: child.props, }); if (match) { matchedNode = child; } }); if (matchedNode) { return matchedNode; } // If we haven't found a node // try to find one that doesn't have a path or from prop, that will be our not found route React.Children.forEach(node, (child) => { if (!(child.props.path || child.props.from)) { matchedNode = child; } }); return matchedNode; } function matchComponent(node, pathname, forceExact) { return matchPath({ pathname, componentProps: Object.assign(Object.assign({}, node.props), { exact: forceExact }), }); } class IonRouterInner extends React.PureComponent { constructor(props) { super(props); this.exitViewFromOtherOutletHandlers = []; this.locationHistory = new LocationHistory(); this.viewStack = new ReactRouterViewStack(); this.routeMangerContextState = { canGoBack: () => this.locationHistory.canGoBack(), clearOutlet: this.viewStack.clear, findViewItemByPathname: this.viewStack.findViewItemByPathname, getChildrenToRender: this.viewStack.getChildrenToRender, goBack: () => this.handleNavigateBack(), createViewItem: this.viewStack.createViewItem, findViewItemByRouteInfo: this.viewStack.findViewItemByRouteInfo, findLeavingViewItemByRouteInfo: this.viewStack.findLeavingViewItemByRouteInfo, addViewItem: this.viewStack.add, unMountViewItem: this.viewStack.remove, }; const routeInfo = { id: generateId('routeInfo'), pathname: this.props.location.pathname, search: this.props.location.search, }; this.locationHistory.add(routeInfo); this.handleChangeTab = this.handleChangeTab.bind(this); this.handleResetTab = this.handleResetTab.bind(this); this.handleNativeBack = this.handleNativeBack.bind(this); this.handleNavigate = this.handleNavigate.bind(this); this.handleNavigateBack = this.handleNavigateBack.bind(this); this.props.registerHistoryListener(this.handleHistoryChange.bind(this)); this.handleSetCurrentTab = this.handleSetCurrentTab.bind(this); this.state = { routeInfo, }; } handleChangeTab(tab, path, routeOptions) { if (!path) { return; } const routeInfo = this.locationHistory.getCurrentRouteInfoForTab(tab); const [pathname, search] = path.split('?'); if (routeInfo) { this.incomingRouteParams = Object.assign(Object.assign({}, routeInfo), { routeAction: 'push', routeDirection: 'none' }); if (routeInfo.pathname === pathname) { this.incomingRouteParams.routeOptions = routeOptions; this.props.history.push(routeInfo.pathname + (routeInfo.search || '')); } else { this.incomingRouteParams.pathname = pathname; this.incomingRouteParams.search = search ? '?' + search : undefined; this.incomingRouteParams.routeOptions = routeOptions; this.props.history.push(pathname + (search ? '?' + search : '')); } } else { this.handleNavigate(pathname, 'push', 'none', undefined, routeOptions, tab); } } handleHistoryChange(location, action) { var _a, _b, _c; let leavingLocationInfo; if (this.incomingRouteParams) { if (this.incomingRouteParams.routeAction === 'replace') { leavingLocationInfo = this.locationHistory.previous(); } else { leavingLocationInfo = this.locationHistory.current(); } } else { leavingLocationInfo = this.locationHistory.current(); } const leavingUrl = leavingLocationInfo.pathname + leavingLocationInfo.search; if (leavingUrl !== location.pathname) { if (!this.incomingRouteParams) { if (action === 'REPLACE') { this.incomingRouteParams = { routeAction: 'replace', routeDirection: 'none', tab: this.currentTab, }; } if (action === 'POP') { const currentRoute = this.locationHistory.current(); if (currentRoute && currentRoute.pushedByRoute) { const prevInfo = this.locationHistory.findLastLocation(currentRoute); this.incomingRouteParams = Object.assign(Object.assign({}, prevInfo), { routeAction: 'pop', routeDirection: 'back' }); } else { this.incomingRouteParams = { routeAction: 'pop', routeDirection: 'none', tab: this.currentTab, }; } } if (!this.incomingRouteParams) { this.incomingRouteParams = { routeAction: 'push', routeDirection: ((_a = location.state) === null || _a === void 0 ? void 0 : _a.direction) || 'forward', routeOptions: (_b = location.state) === null || _b === void 0 ? void 0 : _b.routerOptions, tab: this.currentTab, }; } } let routeInfo; if ((_c = this.incomingRouteParams) === null || _c === void 0 ? void 0 : _c.id) { routeInfo = Object.assign(Object.assign({}, this.incomingRouteParams), { lastPathname: leavingLocationInfo.pathname }); this.locationHistory.add(routeInfo); } else { const isPushed = this.incomingRouteParams.routeAction === 'push' && this.incomingRouteParams.routeDirection === 'forward'; routeInfo = Object.assign(Object.assign({ id: generateId('routeInfo') }, this.incomingRouteParams), { lastPathname: leavingLocationInfo.pathname, pathname: location.pathname, search: location.search, params: this.props.match.params, prevRouteLastPathname: leavingLocationInfo.lastPathname }); if (isPushed) { routeInfo.tab = leavingLocationInfo.tab; routeInfo.pushedByRoute = leavingLocationInfo.pathname; } else if (routeInfo.routeAction === 'pop') { const r = this.locationHistory.findLastLocation(routeInfo); routeInfo.pushedByRoute = r === null || r === void 0 ? void 0 : r.pushedByRoute; } else if (routeInfo.routeAction === 'push' && routeInfo.tab !== leavingLocationInfo.tab) { // If we are switching tabs grab the last route info for the tab and use its pushedByRoute const lastRoute = this.locationHistory.getCurrentRouteInfoForTab(routeInfo.tab); routeInfo.pushedByRoute = lastRoute === null || lastRoute === void 0 ? void 0 : lastRoute.pushedByRoute; } else if (routeInfo.routeAction === 'replace') { // Make sure to set the lastPathname, etc.. to the current route so the page transitions out const currentRouteInfo = this.locationHistory.current(); /** * If going from /home to /child, then replacing from * /child to /home, we don't want the route info to * say that /home was pushed by /home which is not correct. */ const currentPushedBy = currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.pushedByRoute; const pushedByRoute = currentPushedBy !== undefined && currentPushedBy !== routeInfo.pathname ? currentPushedBy : routeInfo.pushedByRoute; routeInfo.lastPathname = (currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.pathname) || routeInfo.lastPathname; routeInfo.prevRouteLastPathname = currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.lastPathname; routeInfo.pushedByRoute = pushedByRoute; /** * When replacing routes we should still prefer * any custom direction/animation that the developer * has specified when navigating first instead of relying * on previously used directions/animations. */ routeInfo.routeDirection = routeInfo.routeDirection || (currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.routeDirection); routeInfo.routeAnimation = routeInfo.routeAnimation || (currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.routeAnimation); } this.locationHistory.add(routeInfo); } this.setState({ routeInfo, }); } this.incomingRouteParams = undefined; } /** * history@4.x uses goBack(), history@5.x uses back() * TODO: If support for React Router <=5 is dropped * this logic is no longer needed. We can just * assume back() is available. */ handleNativeBack() { const history = this.props.history; const goBack = history.goBack || history.back; goBack(); } handleNavigate(path, routeAction, routeDirection, routeAnimation, routeOptions, tab) { this.incomingRouteParams = Object.assign(this.incomingRouteParams || {}, { routeAction, routeDirection, routeOptions, routeAnimation, tab, }); if (routeAction === 'push') { this.props.history.push(path); } else { this.props.history.replace(path); } } handleNavigateBack(defaultHref = '/', routeAnimation) { const config = getConfig(); defaultHref = defaultHref ? defaultHref : config && config.get('backButtonDefaultHref'); const routeInfo = this.locationHistory.current(); if (routeInfo && routeInfo.pushedByRoute) { const prevInfo = this.locationHistory.findLastLocation(routeInfo); if (prevInfo) { /** * This needs to be passed to handleNavigate * otherwise incomingRouteParams.routeAnimation * will be overridden. */ const incomingAnimation = routeAnimation || routeInfo.routeAnimation; this.incomingRouteParams = Object.assign(Object.assign({}, prevInfo), { routeAction: 'pop', routeDirection: 'back', routeAnimation: incomingAnimation }); if (routeInfo.lastPathname === routeInfo.pushedByRoute || /** * We need to exclude tab switches/tab * context changes here because tabbed * navigation is not linear, but router.back() * will go back in a linear fashion. */ (prevInfo.pathname === routeInfo.pushedByRoute && routeInfo.tab === '' && prevInfo.tab === '')) { /** * history@4.x uses goBack(), history@5.x uses back() * TODO: If support for React Router <=5 is dropped * this logic is no longer needed. We can just * assume back() is available. */ const history = this.props.history; const goBack = history.goBack || history.back; goBack(); } else { this.handleNavigate(prevInfo.pathname + (prevInfo.search || ''), 'pop', 'back', incomingAnimation); } } else { this.handleNavigate(defaultHref, 'pop', 'back', routeAnimation); } } else { this.handleNavigate(defaultHref, 'pop', 'back', routeAnimation); } } handleResetTab(tab, originalHref, originalRouteOptions) { const routeInfo = this.locationHistory.getFirstRouteInfoForTab(tab); if (routeInfo) { const newRouteInfo = Object.assign({}, routeInfo); newRouteInfo.pathname = originalHref; newRouteInfo.routeOptions = originalRouteOptions; this.incomingRouteParams = Object.assign(Object.assign({}, newRouteInfo), { routeAction: 'pop', routeDirection: 'back' }); this.props.history.push(newRouteInfo.pathname + (newRouteInfo.search || '')); } } handleSetCurrentTab(tab) { this.currentTab = tab; const ri = Object.assign({}, this.locationHistory.current()); if (ri.tab !== tab) { ri.tab = tab; this.locationHistory.update(ri); } } render() { return (React.createElement(RouteManagerContext.Provider, { value: this.routeMangerContextState }, React.createElement(NavManager, { ionRoute: IonRouteInner, ionRedirect: {}, stackManager: StackManager, routeInfo: this.state.routeInfo, onNativeBack: this.handleNativeBack, onNavigateBack: this.handleNavigateBack, onNavigate: this.handleNavigate, onSetCurrentTab: this.handleSetCurrentTab, onChangeTab: this.handleChangeTab, onResetTab: this.handleResetTab, locationHistory: this.locationHistory }, this.props.children))); } } const IonRouter = withRouter(IonRouterInner); IonRouter.displayName = 'IonRouter'; class IonReactRouter extends React.Component { constructor(props) { super(props); const { history } = props, rest = __rest(props, ["history"]); this.history = history || createBrowserHistory(rest); this.history.listen(this.handleHistoryChange.bind(this)); this.registerHistoryListener = this.registerHistoryListener.bind(this); } /** * history@4.x passes separate location and action * params. history@5.x passes location and action * together as a single object. * TODO: If support for React Router <=5 is dropped * this logic is no longer needed. We can just assume * a single object with both location and action. */ handleHistoryChange(location, action) { const locationValue = location.location || location; const actionValue = location.action || action; if (this.historyListenHandler) { this.historyListenHandler(locationValue, actionValue); } } registerHistoryListener(cb) { this.historyListenHandler = cb; } render() { const _a = this.props, { children } = _a, props = __rest(_a, ["children"]); return (React.createElement(Router, Object.assign({ history: this.history }, props), React.createElement(IonRouter, { registerHistoryListener: this.registerHistoryListener }, children))); } } class IonReactMemoryRouter extends React.Component { constructor(props) { super(props); this.history = props.history; this.history.listen(this.handleHistoryChange.bind(this)); this.registerHistoryListener = this.registerHistoryListener.bind(this); } /** * history@4.x passes separate location and action * params. history@5.x passes location and action * together as a single object. * TODO: If support for React Router <=5 is dropped * this logic is no longer needed. We can just assume * a single object with both location and action. */ handleHistoryChange(location, action) { const locationValue = location.location || location; const actionValue = location.action || action; if (this.historyListenHandler) { this.historyListenHandler(locationValue, actionValue); } } registerHistoryListener(cb) { this.historyListenHandler = cb; } render() { const _a = this.props, { children } = _a, props = __rest(_a, ["children"]); return (React.createElement(Router$1, Object.assign({}, props), React.createElement(IonRouter, { registerHistoryListener: this.registerHistoryListener }, children))); } } class IonReactHashRouter extends React.Component { constructor(props) { super(props); const { history } = props, rest = __rest(props, ["history"]); this.history = history || createHashHistory(rest); this.history.listen(this.handleHistoryChange.bind(this)); this.registerHistoryListener = this.registerHistoryListener.bind(this); } /** * history@4.x passes separate location and action * params. history@5.x passes location and action * together as a single object. * TODO: If support for React Router <=5 is dropped * this logic is no longer needed. We can just assume * a single object with both location and action. */ handleHistoryChange(location, action) { const locationValue = location.location || location; const actionValue = location.action || action; if (this.historyListenHandler) { this.historyListenHandler(locationValue, actionValue); } } registerHistoryListener(cb) { this.historyListenHandler = cb; } render() { const _a = this.props, { children } = _a, props = __rest(_a, ["children"]); return (React.createElement(Router, Object.assign({ history: this.history }, props), React.createElement(IonRouter, { registerHistoryListener: this.registerHistoryListener }, children))); } } export { IonReactHashRouter, IonReactMemoryRouter, IonReactRouter }; //# sourceMappingURL=index.js.map