@ionic/vue-router
Version:
Vue Router integration for @ionic/vue
1,007 lines (1,000 loc) • 42.8 kB
JavaScript
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