mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-17 02:31:34 +08:00
301 lines
9.6 KiB
TypeScript
301 lines
9.6 KiB
TypeScript
import {
|
|
Router,
|
|
RouteLocationNormalized,
|
|
NavigationFailure
|
|
} from 'vue-router';
|
|
import { createLocationHistory } from './locationHistory';
|
|
import { generateId } from './utils';
|
|
import {
|
|
ExternalNavigationOptions,
|
|
RouteInfo,
|
|
RouteParams,
|
|
RouteAction,
|
|
RouteDirection,
|
|
IonicVueRouterOptions,
|
|
NavigationInformation
|
|
} from './types';
|
|
import { AnimationBuilder } from '@ionic/core';
|
|
|
|
export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) => {
|
|
let currentNavigationInfo: NavigationInformation = { direction: undefined, action: 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: RouteLocationNormalized, _: RouteLocationNormalized, failure?: NavigationFailure) => {
|
|
if (failure) return;
|
|
|
|
const { direction, action } = 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.
|
|
*/
|
|
const replaceAction = history.state.replaced ? 'replace' : undefined;
|
|
handleHistoryChange(to, action || replaceAction, direction);
|
|
|
|
currentNavigationInfo = { direction: undefined, action: undefined };
|
|
});
|
|
|
|
const locationHistory = createLocationHistory();
|
|
let currentRouteInfo: RouteInfo;
|
|
let incomingRouteParams: RouteParams;
|
|
let currentTab: string | undefined;
|
|
|
|
// TODO types
|
|
let historyChangeListeners: any[] = [];
|
|
|
|
if (typeof (document as any) !== 'undefined') {
|
|
document.addEventListener('ionBackButton', (ev: Event) => {
|
|
(ev as any).detail.register(0, (processNextHandler: () => void) => {
|
|
opts.history.go(-1);
|
|
processNextHandler();
|
|
});
|
|
})
|
|
}
|
|
|
|
opts.history.listen((_: any, _x: any, info: any) => {
|
|
/**
|
|
* 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 = {
|
|
action: info.type,
|
|
direction: info.direction === '' ? 'forward' : info.direction
|
|
};
|
|
});
|
|
|
|
const handleNavigateBack = (defaultHref?: string, routerAnimation?: AnimationBuilder) => {
|
|
// todo grab default back button href from config
|
|
const routeInfo = locationHistory.current();
|
|
if (routeInfo && routeInfo.pushedByRoute) {
|
|
const prevInfo = locationHistory.findLastLocation(routeInfo);
|
|
if (prevInfo) {
|
|
incomingRouteParams = { ...prevInfo, routerAction: 'pop', routerDirection: 'back', routerAnimation: routerAnimation || routeInfo.routerAnimation };
|
|
if (routeInfo.lastPathname === routeInfo.pushedByRoute) {
|
|
router.back();
|
|
} else {
|
|
router.replace(prevInfo.pathname + (prevInfo.search || ''));
|
|
}
|
|
} else {
|
|
handleNavigate(defaultHref, 'pop', 'back');
|
|
}
|
|
} else {
|
|
handleNavigate(defaultHref, 'pop', 'back');
|
|
}
|
|
}
|
|
|
|
const handleNavigate = (path: string, routerAction?: RouteAction, routerDirection?: RouteDirection, routerAnimation?: AnimationBuilder, tab?: string) => {
|
|
incomingRouteParams = {
|
|
routerAction,
|
|
routerDirection,
|
|
routerAnimation,
|
|
tab
|
|
}
|
|
|
|
if (routerAction === 'push') {
|
|
router.push(path);
|
|
} else {
|
|
router.replace(path);
|
|
}
|
|
}
|
|
|
|
// TODO RouteLocationNormalized
|
|
const handleHistoryChange = (location: any, action?: RouteAction, direction?: RouteDirection) => {
|
|
let leavingLocationInfo: RouteInfo;
|
|
if (incomingRouteParams) {
|
|
if (incomingRouteParams.routerAction === 'replace') {
|
|
leavingLocationInfo = locationHistory.previous();
|
|
} else {
|
|
leavingLocationInfo = locationHistory.current();
|
|
}
|
|
} else {
|
|
leavingLocationInfo = locationHistory.current();
|
|
}
|
|
|
|
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',
|
|
tab: currentTab
|
|
}
|
|
} else if (action === 'pop') {
|
|
const routeInfo = locationHistory.current();
|
|
if (routeInfo && routeInfo.pushedByRoute) {
|
|
const prevRouteInfo = locationHistory.findLastLocation(routeInfo);
|
|
incomingRouteParams = {
|
|
...prevRouteInfo,
|
|
routerAction: 'pop',
|
|
routerDirection: 'back'
|
|
};
|
|
} else {
|
|
incomingRouteParams = {
|
|
routerAction: 'pop',
|
|
routerDirection: 'none',
|
|
tab: currentTab
|
|
}
|
|
}
|
|
}
|
|
if (!incomingRouteParams) {
|
|
incomingRouteParams = {
|
|
routerAction: 'push',
|
|
routerDirection: direction || 'forward',
|
|
tab: currentTab
|
|
}
|
|
}
|
|
}
|
|
|
|
let routeInfo: RouteInfo;
|
|
if (incomingRouteParams?.id) {
|
|
routeInfo = {
|
|
...incomingRouteParams,
|
|
lastPathname: leavingLocationInfo.pathname
|
|
}
|
|
locationHistory.add(routeInfo);
|
|
|
|
} else {
|
|
const isPushed = incomingRouteParams.routerAction === 'push' && incomingRouteParams.routerDirection === 'forward';
|
|
routeInfo = {
|
|
id: generateId('routeInfo'),
|
|
...incomingRouteParams,
|
|
lastPathname: leavingLocationInfo.pathname,
|
|
pathname: location.path,
|
|
search: location.fullPath && location.fullPath.split('?')[1] || '',
|
|
params: location.params && location.params,
|
|
}
|
|
|
|
if (isPushed) {
|
|
routeInfo.tab = leavingLocationInfo.tab;
|
|
routeInfo.pushedByRoute = (leavingLocationInfo.pathname !== '') ? leavingLocationInfo.pathname : undefined;
|
|
} else if (routeInfo.routerAction === 'pop') {
|
|
const route = locationHistory.findLastLocation(routeInfo);
|
|
routeInfo.pushedByRoute = route?.pushedByRoute;
|
|
} else if (routeInfo.routerAction === 'push' && routeInfo.tab !== leavingLocationInfo.tab) {
|
|
const lastRoute = locationHistory.getCurrentRouteInfoForTab(routeInfo.tab);
|
|
routeInfo.pushedByRoute = lastRoute?.pushedByRoute;
|
|
} else if (routeInfo.routerAction === 'replace') {
|
|
const currentRouteInfo = locationHistory.current();
|
|
routeInfo.lastPathname = currentRouteInfo?.pathname || routeInfo.lastPathname;
|
|
routeInfo.pushedByRoute = currentRouteInfo?.pushedByRoute || routeInfo.pushedByRoute;
|
|
routeInfo.routerDirection = currentRouteInfo?.routerDirection || routeInfo.routerDirection;
|
|
routeInfo.routerAnimation = currentRouteInfo?.routerAnimation || routeInfo.routerAnimation;
|
|
}
|
|
|
|
locationHistory.add(routeInfo);
|
|
}
|
|
currentRouteInfo = routeInfo;
|
|
}
|
|
incomingRouteParams = undefined;
|
|
historyChangeListeners.forEach(cb => cb(currentRouteInfo));
|
|
}
|
|
|
|
const getCurrentRouteInfo = () => currentRouteInfo;
|
|
|
|
const canGoBack = (deep: number = 1) => locationHistory.canGoBack(deep);
|
|
|
|
const navigate = (navigationOptions: ExternalNavigationOptions) => {
|
|
const { routerAnimation, routerDirection, routerLink } = navigationOptions;
|
|
|
|
incomingRouteParams = {
|
|
routerAnimation,
|
|
routerDirection: routerDirection || 'forward',
|
|
routerAction: 'push'
|
|
}
|
|
|
|
router.push(routerLink);
|
|
}
|
|
|
|
const resetTab = (tab: string, originalHref: string) => {
|
|
const routeInfo = locationHistory.getFirstRouteInfoForTab(tab);
|
|
if (routeInfo) {
|
|
const newRouteInfo = { ...routeInfo };
|
|
newRouteInfo.pathname = originalHref;
|
|
incomingRouteParams = { ...newRouteInfo, routerAction: 'pop', routerDirection: 'back' };
|
|
router.push(newRouteInfo.pathname + (newRouteInfo.search || ''));
|
|
}
|
|
}
|
|
|
|
const changeTab = (tab: string, path: string) => {
|
|
const routeInfo = locationHistory.getCurrentRouteInfoForTab(tab);
|
|
const [pathname] = path.split('?');
|
|
|
|
if (routeInfo) {
|
|
const search = (routeInfo.search) ? `?${routeInfo.search}` : '';
|
|
|
|
incomingRouteParams = {
|
|
...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(routeInfo.pathname + search);
|
|
} else {
|
|
router.push(pathname + search);
|
|
}
|
|
}
|
|
else {
|
|
handleNavigate(pathname, 'push', 'none', undefined, tab);
|
|
}
|
|
}
|
|
|
|
const handleSetCurrentTab = (tab: string) => {
|
|
currentTab = tab;
|
|
|
|
const ri = { ...locationHistory.current() };
|
|
if (ri.tab !== tab) {
|
|
ri.tab = tab;
|
|
locationHistory.update(ri);
|
|
}
|
|
}
|
|
|
|
// TODO types
|
|
const registerHistoryChangeListener = (cb: any) => {
|
|
historyChangeListeners.push(cb);
|
|
}
|
|
|
|
return {
|
|
handleNavigateBack,
|
|
handleSetCurrentTab,
|
|
getCurrentRouteInfo,
|
|
canGoBack,
|
|
navigate,
|
|
resetTab,
|
|
changeTab,
|
|
registerHistoryChangeListener
|
|
}
|
|
}
|