expo-router
Version:
Expo Router is a file-based router for React Native and web applications.
301 lines (299 loc) • 12 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "resolveNavigationDestination", {
enumerable: true,
get: function() {
return resolveNavigationDestination;
}
});
function _reactfastcompare() {
const data = /*#__PURE__*/ _interop_require_default(require("react-fast-compare"));
_reactfastcompare = function() {
return data;
};
return data;
}
const _constants = require("../constants");
const _matchers = require("../matchers");
const _navigationParams = require("../navigationParams");
const _createInitialState = require("../react-navigation/core/createInitialState");
const _stateKeys = require("../react-navigation/routers/stateKeys");
const _utils = require("../react-navigation/routers/utils");
const _routeNode = require("../routeNode");
const _sortRoutes = require("../sortRoutes");
const _getSingularId = require("../utils/getSingularId");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
const supportedActionTypes = new Set([
'NAVIGATE',
'PUSH',
'REPLACE',
'POP_TO',
'PRELOAD',
'JUMP_TO'
]);
function resolveNavigationDestination({ targetState, navigationState, routeNode, registry, action, withAnchor = false, internalParams = {} }) {
if (!supportedActionTypes.has(action.type)) {
throw new Error(`Unsupported destination action type: ${action.type}`);
}
let target = targetState;
let current = navigationState;
let node = routeNode;
let descended = false;
while(true){
const targetRoute = getFocusedRoute(target);
const currentRoute = current.type === 'tab' && action.type === 'PRELOAD' ? current.routes.find((route)=>route.name === targetRoute?.name) : current.routes[current.index];
const childNode = findDestinationNode(node, targetRoute?.name);
const childTarget = targetRoute?.state;
const childState = currentRoute?.state;
if (!targetRoute || !childNode || !currentRoute || !routesMatch(targetRoute, currentRoute) || !childTarget || childState?.stale !== false || !registry.has(childState.key)) {
break;
}
const childRoute = getFocusedRoute(childTarget);
if (!childRoute) {
break;
}
const probeAction = {
type: 'NAVIGATE',
payload: {
name: childRoute.name,
params: (0, _navigationParams.appendInternalExpoRouterParams)(childRoute.params, internalParams)
}
};
if (getSelection(registry.get(childState.key)?.reduce(childState, probeAction)) === undefined) {
break;
}
target = childTarget;
current = childState;
node = childNode;
descended = true;
}
const resolvedAction = {
...action,
target: current.key
};
if (descended) {
delete resolvedAction.source;
}
return createResolvedAction({
targetState: target,
navigationState: current,
routeNode: node,
rootRouteNode: routeNode,
registry,
action: resolvedAction,
withAnchor,
internalParams
});
}
function createResolvedAction({ targetState, navigationState, routeNode, rootRouteNode, registry, action, withAnchor = false, internalParams = {} }) {
const targetRoute = getFocusedRoute(targetState);
if (!targetRoute) {
return action;
}
const params = (0, _navigationParams.appendInternalExpoRouterParams)(targetRoute.params, internalParams) ?? {};
const childTarget = targetRoute.state;
const childNode = findDestinationNode(routeNode, targetRoute.name);
const { pop, ...actionPayload } = action.payload;
const shouldPop = pop === true && shouldPopTo({
targetRoute,
navigationState,
routeNode,
rootRouteNode,
childNode
});
const payload = {
...actionPayload,
...shouldPop ? {
pop
} : undefined,
name: targetRoute.name,
params,
...targetRoute.path !== undefined ? {
path: targetRoute.path
} : undefined
};
const baseAction = {
...action,
payload
};
const result = registry.get(navigationState.key)?.reduce(navigationState, baseAction);
const selectedRoute = getSelection(result)?.route;
if (!childTarget || !childNode) {
return baseAction;
}
// Resolution and dispatch reduce the same state, so speculative minting produces the same key.
const parentChain = (0, _stateKeys.getChainFromRouteKey)(selectedRoute?.key ?? (0, _stateKeys.createRouteKeyMinter)(navigationState).mint(targetRoute.name));
const childState = resolveState({
targetState: childTarget,
navigationState: selectedRoute?.state,
routeNode: childNode,
rootRouteNode,
registry,
withAnchor,
internalParams,
parentChain,
previewKey: action.payload.__internal__PreviewKey,
// Navigators below the `POP_TO` target get `NAVIGATE`, which pushes unless `pop` is set.
pop: pop === true || action.type === 'POP_TO'
});
return {
...baseAction,
payload: {
...payload,
state: markState(childState)
}
};
}
function resolveState({ targetState, navigationState, routeNode, rootRouteNode, registry, withAnchor, internalParams, parentChain, previewKey, pop }) {
if (navigationState?.stale !== false || !registry.has(navigationState.key)) {
return createDestinationState(targetState, routeNode, withAnchor, internalParams, parentChain);
}
const targetRoute = getFocusedRoute(targetState);
if (!targetRoute) {
return navigationState;
}
const action = createResolvedAction({
targetState: targetState,
navigationState,
routeNode,
rootRouteNode,
registry,
action: {
type: 'NAVIGATE',
payload: {
__internal__PreviewKey: previewKey,
...pop ? {
pop
} : undefined
}
},
withAnchor,
internalParams
});
const result = registry.get(navigationState.key)?.reduce(navigationState, action);
const selection = getSelection(result);
if (!selection) {
return createDestinationState(targetState, routeNode, withAnchor, internalParams, parentChain);
}
return (0, _reactfastcompare().default)(selection.state, navigationState) ? navigationState : selection.state;
}
function createDestinationState(targetState, routeNode, withAnchor, internalParams, parentChain) {
const targetRoute = getFocusedRoute(targetState);
const initialRouteName = (0, _routeNode.getValidInitialRouteName)(routeNode);
// Sort like a mounted navigator does, so the route names match on mount and no
// ROUTE_NAMES_CHANGED action is queued.
const routeNames = [
...routeNode.children
].sort((0, _sortRoutes.sortRoutesWithInitial)(initialRouteName)).map((child)=>child.route);
if (!targetRoute) {
return markState((0, _createInitialState.createInitialState)({
routeNames,
initialRouteName,
parentChain
}));
}
if (!routeNames.includes(targetRoute.name)) {
console.warn(`The navigation destination contains the unknown route "${targetRoute.name}". The route is not registered by its navigator, so Expo Router will use the navigator's initial route instead. Check that your linking configuration only returns registered routes.`);
return markState((0, _createInitialState.createInitialState)({
routeNames,
initialRouteName,
parentChain
}));
}
const hasAnchor = withAnchor && initialRouteName && initialRouteName !== targetRoute.name;
const destination = (0, _createInitialState.createInitialState)({
routeNames,
initialRouteName: hasAnchor ? initialRouteName : targetRoute.name,
parentChain
});
const minter = (0, _stateKeys.createRouteKeyMinter)(destination);
const destinationRouteKey = hasAnchor ? minter.mint(targetRoute.name) : destination.routes[0].key;
const childNode = (0, _routeNode.findRouteNodeByName)(routeNode, targetRoute.name);
const childState = targetRoute.state && childNode ? createDestinationState(targetRoute.state, childNode, withAnchor, internalParams, (0, _stateKeys.getChainFromRouteKey)(destinationRouteKey)) : undefined;
const destinationRoute = {
...destination.routes[0],
key: destinationRouteKey,
name: targetRoute.name,
...targetRoute.path !== undefined ? {
path: targetRoute.path
} : undefined,
params: (0, _navigationParams.appendInternalExpoRouterParams)(targetRoute.params, internalParams) ?? {},
...childState !== undefined ? {
state: childState
} : undefined
};
if (hasAnchor) {
return markState({
...destination,
routeKeySeq: minter.routeKeySeq,
index: 1,
routes: [
destination.routes[0],
destinationRoute
]
});
}
return markState({
...destination,
routes: [
destinationRoute
]
});
}
function getFocusedRoute(state) {
return state.routes[state.index ?? state.routes.length - 1];
}
function routesMatch(targetRoute, currentRoute) {
if (targetRoute.name !== currentRoute.name) {
return false;
}
const dynamic = (0, _matchers.matchDynamicName)(targetRoute.name);
return !dynamic || targetRoute.params?.[dynamic.name] === currentRoute.params?.[dynamic.name];
}
/**
* Whether a `pop` request should apply to the target in this navigator. This follows React
* Navigation's `getActionFromState`, which incoming links used before: the root layout's navigator
* pops only to a route that holds a navigator, and every navigator below it pops to the target
* screen too. Stacks pop to the last route with the target's name, so for a dynamic route that
* route must also have the target's params; otherwise the link pushes a new instance instead of
* taking over another one.
*/ function shouldPopTo({ targetRoute, navigationState, routeNode, rootRouteNode, childNode }) {
const isRootLayoutNavigator = routeNode === rootRouteNode && targetRoute.name !== _constants.INTERNAL_SLOT_NAME;
const holdsNavigator = targetRoute.state !== undefined || (childNode?.children.length ?? 0) > 0;
if (isRootLayoutNavigator && !holdsNavigator) {
return false;
}
const popTarget = navigationState.routes.findLast((route)=>route.name === targetRoute.name);
return !popTarget || (0, _getSingularId.getSingularId)(targetRoute.name, {
params: targetRoute.params
}) === (0, _getSingularId.getSingularId)(popTarget.name, {
params: popTarget.params
});
}
function findDestinationNode(routeNode, routeName) {
return routeName === _constants.INTERNAL_SLOT_NAME ? routeNode : (0, _routeNode.findRouteNodeByName)(routeNode, routeName);
}
function getSelection(result) {
if (result === undefined || !(0, _utils.isActionHandled)(result) || result.state.stale !== false || result.affectedRouteKey === undefined) {
return undefined;
}
const route = result.state.routes.find((route)=>route.key === result.affectedRouteKey);
return route && {
state: result.state,
route
};
}
function markState(state) {
return {
...state,
__internal__routerActionState: true
};
} // TODO: Move removal-protection registrations into a global registry so resolved trees can run
// checks for route keys they remove before dispatch.
//# sourceMappingURL=resolveNavigationDestination.js.map