UNPKG

@ionic/vue-router

Version:
1,007 lines (1,000 loc) • 42.8 kB
import { parseQuery, createRouter as createRouter$1, createWebHistory as createWebHistory$1, createWebHashHistory as createWebHashHistory$1, createMemoryHistory as createMemoryHistory$1 } from 'vue-router'; import { shallowRef } from 'vue'; const createLocationHistory = () => { const locationHistory = []; const tabsHistory = {}; const add = (routeInfo) => { switch (routeInfo.routerAction) { case "pop": pop(routeInfo); break; default: addRoute(routeInfo); break; } if (routeInfo.routerDirection === "root") { clearHistory(); addRoute(routeInfo); } }; const update = (routeInfo) => { const locationIndex = locationHistory.findIndex((x) => x.id === routeInfo.id); if (locationIndex > -1) { locationHistory.splice(locationIndex, 1, routeInfo); } const tabArray = tabsHistory[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) { tabsHistory[routeInfo.tab] = [routeInfo]; } }; const pop = (routeInfo) => { const tabHistory = getTabsHistory(routeInfo.tab); let ri; if (tabHistory) { // Pop all routes until we are back ri = tabHistory[tabHistory.length - 1]; while (ri && ri.id !== routeInfo.id) { tabHistory.pop(); ri = tabHistory[tabHistory.length - 1]; } // Replace with updated route tabHistory.pop(); tabHistory.push(routeInfo); } ri = locationHistory[locationHistory.length - 1]; while (ri && ri.id !== routeInfo.id) { locationHistory.pop(); ri = locationHistory[locationHistory.length - 1]; } // Replace with updated route locationHistory.pop(); locationHistory.push(routeInfo); }; const addRoute = (routeInfo) => { const tabHistory = getTabsHistory(routeInfo.tab); if (tabHistory) { // If the latest routeInfo is the same (going back and forth between tabs), replace it if (tabHistory[tabHistory.length - 1] && tabHistory[tabHistory.length - 1].id === routeInfo.id) { tabHistory.pop(); } tabHistory.push(routeInfo); } locationHistory.push(routeInfo); }; /** * Wipes the location history arrays. * You can optionally provide a routeInfo * object which will wipe that entry * and every entry that appears after it. */ const clearHistory = (routeInfo) => { if (routeInfo) { const { position, tab } = routeInfo; /** * If there is no route index in locationHistory * then there will not be any route index in * tabs either. */ const existingRouteIndex = locationHistory.findIndex((r) => r.position === position); if (existingRouteIndex === -1) return; locationHistory.splice(existingRouteIndex); const clearTabHistory = (tab) => { const existingTabRouteIndex = tabsHistory[tab].findIndex((r) => r.position === position); if (existingTabRouteIndex === -1) return; tabsHistory[tab].splice(existingTabRouteIndex); }; /** * We also need to search the current tab * to correctly reset the individual tab * stack. We should not clear the entire * tab stack as that means we will lose * a reference to the root tab route. */ const tabHistory = tabsHistory[tab]; if (tab && tabHistory) { clearTabHistory(tab); /** * If we are not clearing items after * a tabs page, it is still possible * that there are future tabs pages to clear. * As a result, we need to search through * all the tab stacks and remove views that appear * after the given routeInfo. * * Example: /non-tabs-page --> /tabs/tab1 --> /non-tabs-page * (via router.go(-1)) --> /tabs/tab2. The /tabs/tab1 history * has been overwritten with /tabs/tab2. As a result, * the /tabs/tab1 route info in the Tab 1 stack should be removed. */ } else { for (const tab in tabsHistory) { clearTabHistory(tab); } } } else { for (const tab in tabsHistory) { tabsHistory[tab] = []; } locationHistory.length = 0; } }; const getTabsHistory = (tab) => { let history; if (tab) { history = tabsHistory[tab]; if (!history) { history = tabsHistory[tab] = []; } } return history; }; const size = () => locationHistory.length; /** * Finds and returns the location history item * given the state of browser's history API. * This is useful when jumping around in browser * history using router.go. */ const current = (initialHistory, currentHistory) => { /** * initialHistory does not always start at 0 if users navigated * to app from another website, so doing this math lets us * find the correct index in our locationHistory array. */ const index = currentHistory - initialHistory; return locationHistory[index] || last(); }; const last = () => locationHistory[locationHistory.length - 1]; /** * With the introduction of router.go support, we no longer remove * items from locationHistory as they may be needed again in the future. * As a result, we need to look at the current position in location history * to see if users can navigate back n pages. Previously we were checking * the length of locationHistory, but that only worked since we were pruning * the array. */ const canGoBack = (deep = 1, initialHistory, currentHistory) => { return currentHistory - deep >= initialHistory; }; const getFirstRouteInfoForTab = (tab) => { const tabHistory = getTabsHistory(tab); if (tabHistory) { return tabHistory[0]; } return undefined; }; const getCurrentRouteInfoForTab = (tab) => { const tabHistory = getTabsHistory(tab); if (tabHistory) { return tabHistory[tabHistory.length - 1]; } return undefined; }; /** * Finds and returns the previous view based upon * what originally pushed it (pushedByRoute). * When `delta` < -1 then we should just index into * to array because the previous view that we want is not * necessarily the view that pushed our current view. * Additionally, when jumping around in history, we * do not modify the locationHistory stack so we would * not update pushedByRoute anyways. */ const findLastLocation = (routeInfo, delta = -1) => { const routeInfos = getTabsHistory(routeInfo.tab); if (routeInfos) { if (delta < -1) { return routeInfos[routeInfos.length - 1 + delta]; } else { 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 = locationHistory.length - 2; i >= 0; i--) { const ri = locationHistory[i]; if (ri) { if (ri.pathname === routeInfo.pushedByRoute) { return locationHistory[i + 1 + delta]; } } } return undefined; }; return { current, size, last, add, canGoBack, update, getFirstRouteInfoForTab, getCurrentRouteInfoForTab, findLastLocation, clearHistory, }; }; const ids = { main: 0 }; const generateId = (type = "main") => { var _a; const id = ((_a = ids[type]) !== null && _a !== void 0 ? _a : 0) + 1; ids[type] = id; return id.toString(); }; // TODO(FW-2969): types const createIonRouter = (opts, router) => { let currentNavigationInfo = { direction: undefined, action: undefined, delta: undefined, }; /** * Ionic Vue should only react to navigation * changes once they have been confirmed and should * never affect the outcome of navigation (with the * exception of going back or selecting a tab). * As a result, we should do our work in afterEach * which is fired once navigation is confirmed * and any user guards have run. */ router.afterEach((to, _, failure) => { if (failure) return; const { direction, action, delta } = currentNavigationInfo; /** * When calling router.replace, we are not informed * about the replace action in opts.history.listen * but we can check to see if the latest routing action * was a replace action by looking at the history state. * We need to use opts.history rather than window.history * because window.history will be undefined when using SSR. */ currentHistoryPosition = opts.history.state.position; const replaceAction = opts.history.state.replaced ? "replace" : undefined; handleHistoryChange(to, action || replaceAction, direction, delta); currentNavigationInfo = { direction: undefined, action: undefined, delta: undefined, }; }); const locationHistory = createLocationHistory(); /** * Keeping track of the history position * allows us to determine if a user is pushing * new pages or updating history via the forward * and back browser buttons. */ let initialHistoryPosition = opts.history.state.position; let currentHistoryPosition = opts.history.state.position; let currentRouteInfo; let incomingRouteParams; const historyChangeListeners = []; if (typeof document !== "undefined") { document.addEventListener("ionBackButton", (ev) => { ev.detail.register(0, (processNextHandler) => { opts.history.go(-1); processNextHandler(); }); }); } opts.history.listen((_, _x, info) => { /** * history.listen only fires on certain * event such as when the user clicks the * browser back button. It also gives us * additional information as to the type * of navigation (forward, backward, etc). * * We can use this to better handle the * `handleHistoryChange` call in * router.beforeEach */ currentNavigationInfo = { delta: info.delta, /** * Both the browser forward and backward actions * are considered "pop" actions, but when going forward * we want to make sure the forward animation is used. */ action: info.type === "pop" && info.delta >= 1 ? "push" : info.type, direction: info.direction === "" ? "forward" : info.direction, }; }); const handleNavigateBack = (defaultHref, routerAnimation) => { const routeInfo = locationHistory.current(initialHistoryPosition, currentHistoryPosition); if (routeInfo && routeInfo.pushedByRoute) { const prevInfo = locationHistory.findLastLocation(routeInfo); if (prevInfo) { incomingRouteParams = Object.assign(Object.assign({}, prevInfo), { routerAction: "pop", routerDirection: "back", routerAnimation: routerAnimation || routeInfo.routerAnimation }); 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 && /** * Tab info can be undefined or '' (empty string) * both are false-y values, so we can just use !. */ !routeInfo.tab && !prevInfo.tab)) { router.back(); } else { /** * When going back to a child page of a tab * after being on another tab, we need to use * router.go() here instead of pushing or replacing. * Consider the following example: * /tabs/tab1 --> /tabs/tab1/child1 --> /tabs/tab1/child2 * --> /tabs/tab2 (via Tab 2 button) --> /tabs/tab1/child2 (via Tab 1 button) * * Pressing the ion-back-button on /tabs/tab1/child2 should take * us back to /tabs/tab1/child1 not /tabs/tab2 because each tab * is its own stack. * * If we called pressed the ion-back-button and this code called * router.replace, then the state of /tabs/tab1/child2 would * be replaced with /tabs/tab1/child1. However, this means that * there would be two /tabs/tab1/child1 entries in the location * history as the original /tabs/tab1/child1 entry is still there. * As a result, clicking the ion-back-button on /tabs/tab1/child1 does * nothing because this code would try to route to the same page * we are currently on. * * If we called router.push instead then we would push a * new /tabs/tab1/child1 entry to the location history. This * is not good because we would have two /tabs/tab1/child1 entries * separated by a /tabs/tab1/child2 entry. */ router.go(prevInfo.position - routeInfo.position); } } else { handleNavigate(defaultHref, "pop", "back", routerAnimation); } } else { handleNavigate(defaultHref, "pop", "back", routerAnimation); } }; const handleNavigate = (path, routerAction, routerDirection, routerAnimation, tab) => { setIncomingRouteParams(routerAction, routerDirection, routerAnimation, tab); if (routerAction === "push") { router.push(path); } else { router.replace(path); } }; // TODO RouteLocationNormalized const handleHistoryChange = (location, action, direction, delta) => { let leavingLocationInfo; if (incomingRouteParams) { /** * If we are replacing the state of a route * with another route, the "leaving" route * is at the same position in location history * as where the replaced route will exist. */ if (incomingRouteParams.routerAction === "replace") { leavingLocationInfo = locationHistory.current(initialHistoryPosition, currentHistoryPosition); } else if (incomingRouteParams.routerAction === "pop") { leavingLocationInfo = locationHistory.current(initialHistoryPosition, currentHistoryPosition + 1); /** * If the Ionic Router action was "pop" * and the browser history action was "replace", then * it is the case that the user clicked an IonBackButton * that is trying to go back to the route specified * by the defaultHref property. * * The problem is that this route currently does * not exist in the browser history, and we cannot * prepend an item in the browser's history stack. * To work around this, we replace the state of * the current item instead. * Given this scenario: * /page2 --> /page3 --> (back) /page2 --> (defaultHref) /page1 * We would replace the state of /page2 with the state of /page1. * * When doing this, we are essentially re-writing past * history which makes the future history no longer relevant. * As a result, we clear out the location history so that users * can begin pushing new routes to the stack. * * This pattern is aligned with how the browser handles * pushing new routes after going back as well as how * other stack based operations such as undo/redo work. * For example, if you do tasks A, B, C, undo B and C, and * then do task D, you cannot "redo" B and C because you * rewrote the stack's past history. * * With browser history, it is a similar concept. * Going /page1 --> /page2 --> /page3 and then doing * router.go(-2) will bring you back to /page1. * If you then push /page4, you have rewritten * the past history and you can no longer go * forward to /page2 or /page3. */ if (action === "replace") { locationHistory.clearHistory(); } } else { /** * If the routerDirection was specified as "root", then * we are replacing the initial state of location history * with this incoming route. As a result, the leaving * history info is stored at the same location as * where the incoming history location will be stored. * * Otherwise, we can assume this is just another route * that will be pushed onto the end of location history, * so we can grab the previous item in history relative * to where the history state currently is. */ const position = incomingRouteParams.routerDirection === "root" ? currentHistoryPosition : currentHistoryPosition - 1; leavingLocationInfo = locationHistory.current(initialHistoryPosition, position); } } else { leavingLocationInfo = currentRouteInfo; } if (!leavingLocationInfo) { leavingLocationInfo = { pathname: "", search: "", }; } const leavingUrl = leavingLocationInfo.pathname + leavingLocationInfo.search; if (leavingUrl !== location.fullPath) { if (!incomingRouteParams) { if (action === "replace") { incomingRouteParams = { routerAction: "replace", routerDirection: "none", }; } else if (action === "pop") { const routeInfo = locationHistory.current(initialHistoryPosition, currentHistoryPosition - delta); if (routeInfo && routeInfo.pushedByRoute) { const prevRouteInfo = locationHistory.findLastLocation(routeInfo, delta); incomingRouteParams = Object.assign(Object.assign({}, prevRouteInfo), { routerAction: "pop", routerDirection: "back" }); } else { incomingRouteParams = { routerAction: "pop", routerDirection: "none", }; } } if (!incomingRouteParams) { incomingRouteParams = { routerAction: "push", routerDirection: direction || "forward", }; } } let routeInfo; if (incomingRouteParams === null || incomingRouteParams === void 0 ? void 0 : incomingRouteParams.id) { routeInfo = Object.assign(Object.assign({}, incomingRouteParams), { lastPathname: leavingLocationInfo.pathname }); } else { const isPushed = incomingRouteParams.routerAction === "push" && incomingRouteParams.routerDirection === "forward"; routeInfo = Object.assign(Object.assign({ id: generateId("routeInfo") }, incomingRouteParams), { lastPathname: leavingLocationInfo.pathname, pathname: location.path, search: (location.fullPath && location.fullPath.split("?")[1]) || "", params: location.params && location.params, prevRouteLastPathname: leavingLocationInfo.lastPathname }); if (isPushed) { routeInfo.pushedByRoute = leavingLocationInfo.pathname !== "" ? leavingLocationInfo.pathname : undefined; } else if (routeInfo.routerAction === "pop") { const route = locationHistory.findLastLocation(routeInfo); routeInfo.pushedByRoute = route === null || route === void 0 ? void 0 : route.pushedByRoute; } else if (routeInfo.routerAction === "push" && routeInfo.tab !== leavingLocationInfo.tab) { const lastRoute = locationHistory.getCurrentRouteInfoForTab(routeInfo.tab); routeInfo.pushedByRoute = lastRoute === null || lastRoute === void 0 ? void 0 : lastRoute.pushedByRoute; } else if (routeInfo.routerAction === "replace") { /** * When replacing a route, we want to make sure we select the current route * that we are on, not the last route in the stack. The last route in the stack * is not always the current route. * Example: * Given the following history: /page1 --> /page2 * Doing router.go(-1) would bring you to /page1. * If you then did router.replace('/page3'), /page1 should * be replaced with /page3 even though /page2 is the last * item in the stack/ */ const currentRouteInfo = locationHistory.current(initialHistoryPosition, currentHistoryPosition); /** * 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.pushedByRoute = pushedByRoute; routeInfo.routerDirection = (currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.routerDirection) || routeInfo.routerDirection; routeInfo.routerAnimation = (currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.routerAnimation) || routeInfo.routerAnimation; routeInfo.prevRouteLastPathname = currentRouteInfo === null || currentRouteInfo === void 0 ? void 0 : currentRouteInfo.lastPathname; } } routeInfo.position = currentHistoryPosition; routeInfo.delta = delta; const historySize = locationHistory.size(); const historyDiff = currentHistoryPosition - initialHistoryPosition; /** * If the size of location history is greater * than the difference between the current history * position and the initial history position * then we are guaranteed to already have a history * item for this route. In other words, a user * is navigating within the history without pushing * new items within the stack. * * If the historySize === historyDiff, * then we are still re-writing history * by replacing the current route state * with a new route state. The initial * action when loading an app is * going to be replace operation, so * we want to make sure we exclude that * action by ensuring historySize > 0. */ const isReplacing = historySize === historyDiff && historySize > 0 && action === "replace"; if (historySize > historyDiff || isReplacing) { /** * When navigating back through the history, * if users then push a new route the future * history stack is no longer relevant. As * a result, we need to clear out all entries * that appear after the current routeInfo * so that we can then append the new history. * * This does not apply when using router.go * as that is traversing through the history, * not altering it. * * Previously we had only updated the existing route * and then left the future history alone. That * worked for some use cases but was not sufficient * in other scenarios. */ if ((routeInfo.routerAction === "push" || routeInfo.routerAction === "replace") && delta === undefined) { locationHistory.clearHistory(routeInfo); locationHistory.add(routeInfo); } } else { locationHistory.add(routeInfo); } /** * If we recently reset the location history * then we also need to update the initial * history position. */ if (locationHistory.size() === 1) { initialHistoryPosition = routeInfo.position; } currentRouteInfo = routeInfo; } incomingRouteParams = undefined; historyChangeListeners.forEach((cb) => cb(currentRouteInfo)); }; const getCurrentRouteInfo = () => currentRouteInfo; const canGoBack = (deep = 1) => locationHistory.canGoBack(deep, initialHistoryPosition, currentHistoryPosition); const navigate = (navigationOptions) => { const { routerAnimation, routerDirection, routerLink } = navigationOptions; setIncomingRouteParams("push", routerDirection, routerAnimation); router.push(routerLink); }; const resetTab = (tab) => { /** * Resetting the tab should go back * to the initial view in the tab stack. * It should not push a new instance of the * root tab page onto the stack. * * To do this, we get the initial view in the * tab stack and subtract the position of that * entry from our current position. From there * we call router.go() to move us back the * appropriate number of positions. */ const routeInfo = locationHistory.getFirstRouteInfoForTab(tab); if (routeInfo) { router.go(routeInfo.position - currentHistoryPosition); } }; const changeTab = (tab, path) => { if (!path) return; const routeInfo = locationHistory.getCurrentRouteInfoForTab(tab); const [pathname] = path.split("?"); if (routeInfo) { incomingRouteParams = Object.assign(Object.assign({}, incomingRouteParams), { routerAction: "push", routerDirection: "none", tab }); /** * When going back to a tab * you just left, it's possible * for the route info to be incorrect * as the tab you want is not the * tab you are on. */ if (routeInfo.pathname === pathname) { router.push({ path: routeInfo.pathname, query: parseQuery(routeInfo.search), }); } else { router.push({ path: pathname, query: parseQuery(routeInfo.search) }); } } else { handleNavigate(pathname, "push", "none", undefined, tab); } }; /** * This method is invoked by the IonTabs component * during a history change callback. It is responsible * for ensuring that tabbed routes have the correct * "tab" field in its routeInfo object. * * IonTabs will determine if the current route * is in tabs and assign it the correct tab. * If the current route is not in tabs, * then IonTabs will not invoke this. */ const handleSetCurrentTab = (tab) => { /** * Note that the current page that we * are on is not necessarily the last item * in the locationHistory stack. As a result, * we cannot use locationHistory.last() here. */ const ri = Object.assign({}, locationHistory.current(initialHistoryPosition, currentHistoryPosition)); /** * handleHistoryChange is tabs-agnostic by design. * One side effect of this is that certain tabs * routes have extraneous/incorrect information * that we need to remove. To not tightly couple * handleHistoryChange with tabs, we let the * handleSetCurrentTab function. This function is * only called by IonTabs. */ if (ri.tab !== tab) { ri.tab = tab; locationHistory.update(ri); } /** * lastPathname typically equals pushedByRoute * when navigating in a linear manner. When switching between * tabs, this is almost never the case. * * Example: /tabs/tabs1 --> /tabs/tab2 --> /tabs/tab1 * The latest Tab 1 route would have the following information * lastPathname: '/tabs/tab2' * pushedByRoute: '/tabs/tab2' * * A tab cannot push another tab, so we need to set * pushedByRoute to `undefined`. Alternative way of thinking * about this: You cannot swipe to go back from Tab 1 to Tab 2. * * However, there are some instances where we do want to keep * the pushedByRoute. As a result, we need to ensure that * we only wipe the pushedByRoute state when the both of the * following conditions are met: * 1. pushedByRoute is different from lastPathname * 2. The tab for the pushedByRoute info is different * from the current route tab. * * Example of when we would not want to clear pushedByRoute: * /tabs/tab1 --> /tabs/tab1/child --> /tabs/tab2 --> /tabs/tab1/child * The latest Tab 1 Child route would have the following information: * lastPathname: '/tabs/tab2' * pushedByRoute: '/tabs/tab1 * * In this case, /tabs/tab1/child should be able to swipe to go back * to /tabs/tab1 so we want to keep the pushedByRoute. */ const pushedByRoute = locationHistory.findLastLocation(ri); if (ri.pushedByRoute !== ri.lastPathname && (pushedByRoute === null || pushedByRoute === void 0 ? void 0 : pushedByRoute.tab) !== tab) { ri.pushedByRoute = undefined; locationHistory.update(ri); } }; const registerHistoryChangeListener = (cb) => { historyChangeListeners.push(cb); }; const setIncomingRouteParams = (routerAction = "push", routerDirection = "forward", routerAnimation, tab) => { incomingRouteParams = { routerAction, routerDirection, routerAnimation, tab, }; }; const goBack = (routerAnimation) => { setIncomingRouteParams("pop", "back", routerAnimation); router.back(); }; const goForward = (routerAnimation) => { setIncomingRouteParams("push", "forward", routerAnimation); router.forward(); }; const getLeavingRouteInfo = () => { return locationHistory.current(initialHistoryPosition, currentHistoryPosition); }; return { handleNavigate, getLeavingRouteInfo, handleNavigateBack, handleSetCurrentTab, getCurrentRouteInfo, canGoBack, navigate, resetTab, changeTab, registerHistoryChangeListener, goBack, goForward, }; }; const createViewStacks = (router) => { const viewStacks = {}; /** * Returns the number of active stacks. * This is useful for determining if an app * is using linear navigation only or non-linear * navigation. Multiple stacks indiciate an app * is using non-linear navigation. */ const size = () => Object.keys(viewStacks).length; const clear = (outletId) => { delete viewStacks[outletId]; }; const getViewStack = (outletId) => { return viewStacks[outletId]; }; const registerIonPage = (viewItem, ionPage) => { viewItem.ionPageElement = ionPage; viewItem.ionRoute = true; /** * This is needed otherwise Vue Router * will not consider this component mounted * and will not run route guards that * are written in the component. */ viewItem.matchedRoute.instances = { default: viewItem.vueComponentRef.value, }; }; const findViewItemByRouteInfo = (routeInfo, outletId) => { return findViewItemByPath(routeInfo.pathname, outletId, false); }; const findLeavingViewItemByRouteInfo = (routeInfo, outletId, mustBeIonRoute = true) => { return findViewItemByPath(routeInfo.lastPathname, outletId, mustBeIonRoute); }; const findViewItemByPathname = (pathname, outletId) => { return findViewItemByPath(pathname, outletId, false); }; const findViewItemInStack = (path, stack) => { return stack.find((viewItem) => { if (viewItem.pathname === path) { return viewItem; } return undefined; }); }; const findViewItemByPath = (path, outletId, mustBeIonRoute = false) => { const matchView = (viewItem) => { if ((mustBeIonRoute && !viewItem.ionRoute) || path === "") { return false; } const resolvedPath = router.resolve(path); const findMatchedRoute = resolvedPath.matched.find((matchedRoute) => matchedRoute === viewItem.matchedRoute); if (findMatchedRoute) { /** * /page/1 and /page/2 should not match * to the same view item otherwise there will * be not page transition and we will need to * explicitly clear out parameters from page 1 * so the page 2 params are properly passed * to the developer's app. */ const hasParameter = findMatchedRoute.path.includes(":"); if (hasParameter && path !== viewItem.pathname) { return false; } return viewItem; } return undefined; }; if (outletId) { const stack = viewStacks[outletId]; if (!stack) return undefined; const match = router ? stack.find(matchView) : findViewItemInStack(path, stack); if (match) return match; } else { for (const outletId in viewStacks) { const stack = viewStacks[outletId]; const viewItem = findViewItemInStack(path, stack); if (viewItem) { return viewItem; } } } return undefined; }; // TODO(FW-2969): type const createViewItem = (outletId, vueComponent, matchedRoute, routeInfo, ionPage) => { return { id: generateId("viewItem"), pathname: routeInfo.pathname, outletId, matchedRoute, ionPageElement: ionPage, vueComponent, vueComponentRef: shallowRef(), ionRoute: false, mount: false, exact: routeInfo.pathname === matchedRoute.path, params: routeInfo.params, vueComponentData: {}, }; }; const add = (viewItem) => { const { outletId } = viewItem; if (!viewStacks[outletId]) { viewStacks[outletId] = [viewItem]; } else { viewStacks[outletId].push(viewItem); } }; const remove = (viewItem, outletId) => { if (!outletId) { throw Error("outletId required"); } const viewStack = viewStacks[outletId]; if (viewStack) { viewStacks[outletId] = viewStack.filter((item) => item.id !== viewItem.id); } }; const getChildrenToRender = (outletId) => { const viewStack = viewStacks[outletId]; if (viewStack) { const components = viewStacks[outletId].filter((v) => v.mount); return components; } return []; }; /** * When navigating backwards, we need to clean up and * leaving pages so that they are re-created if * we ever navigate back to them. This is especially * important when using router.go and stepping back * multiple pages at a time. */ const unmountLeavingViews = (outletId, viewItem, delta = 1) => { const viewStack = viewStacks[outletId]; if (!viewStack) return; const startIndex = viewStack.findIndex((v) => v === viewItem); for (let i = startIndex + 1; i < startIndex - delta; i++) { const viewItem = viewStack[i]; viewItem.mount = false; viewItem.ionPageElement = undefined; viewItem.ionRoute = false; viewItem.matchedRoute.instances = {}; } }; /** * When navigating forward it is possible for * developers to step forward over multiple views. * The intermediary views need to be remounted so that * swipe to go back works properly. * We need to account for the delta value here too because * we do not want to remount an unrelated view. * Example: * /home --> /page2 --> router.back() --> /page3 * Going to /page3 would remount /page2 since we do * not prune /page2 from the stack. However, /page2 * needs to remain in the stack. * Example: * /home --> /page2 --> /page3 --> router.go(-2) --> router.go(2) * We would end up on /page3, but users need to be able to swipe * to go back to /page2 and /home, so we need both pages mounted * in the DOM. */ const mountIntermediaryViews = (outletId, viewItem, delta = 1) => { const viewStack = viewStacks[outletId]; if (!viewStack) return; const startIndex = viewStack.findIndex((v) => v === viewItem); for (let i = startIndex + 1; i < startIndex + delta; i++) { viewStack[i].mount = true; } }; return { unmountLeavingViews, mountIntermediaryViews, clear, findViewItemByRouteInfo, findLeavingViewItemByRouteInfo, findViewItemByPathname, createViewItem, getChildrenToRender, add, remove, registerIonPage, getViewStack, size, }; }; const createRouter = (opts) => { const routerOptions = Object.assign({}, opts); delete routerOptions.tabsPrefix; const router = createRouter$1(routerOptions); const ionRouter = createIonRouter(opts, router); const viewStacks = createViewStacks(router); const oldInstall = router.install.bind(router); router.install = (app) => { app.provide("navManager", ionRouter); app.provide("viewStacks", viewStacks); oldInstall(app); }; const oldIsReady = router.isReady.bind(router); router.isReady = () => oldIsReady(); return router; }; const createWebHistory = (base) => createWebHistory$1(base); const createWebHashHistory = (base) => createWebHashHistory$1(base); const createMemoryHistory = (base) => createMemoryHistory$1(base); export { createMemoryHistory, createRouter, createWebHashHistory, createWebHistory }; //# sourceMappingURL=index.js.map