UNPKG

expo-router

Version:

Expo Router is a file-based router for React Native and web applications.

301 lines (299 loc) • 12 kB
"use strict"; 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