expo-router
Version:
Expo Router is a file-based router for React Native and web applications.
268 lines (266 loc) • 10.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
0 && (module.exports = {
applyRouterHistoryAction: null,
createBrowserHistory: null,
restoreNavigationFromBrowser: null,
updateCurrentHistoryEntry: null
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: Object.getOwnPropertyDescriptor(all, name).get
});
}
_export(exports, {
get applyRouterHistoryAction () {
return applyRouterHistoryAction;
},
get createBrowserHistory () {
return createBrowserHistory;
},
get restoreNavigationFromBrowser () {
return restoreNavigationFromBrowser;
},
get updateCurrentHistoryEntry () {
return updateCurrentHistoryEntry;
}
});
const _stateKeys = require("../react-navigation/routers/stateKeys");
const _createSeededNavigationState = require("./createSeededNavigationState");
const _getPathForState = require("./getPathForState");
const _getRouteInfoFromState = require("./getRouteInfoFromState");
const _utils = require("./utils");
function createBrowserHistory(state, config) {
const id = `${config.browserHistoryIdPrefix}:0`;
const path = (0, _getPathForState.getPathForState)(state, config.linking);
return {
history: {
entries: [
{
id,
path,
state
}
],
index: 0,
idPrefix: config.browserHistoryIdPrefix,
entrySeq: 1
},
events: [
createReplaceEvent(id, path)
]
};
}
function createReplaceEvent(id, path) {
return {
type: 'browser-history',
op: 'replace',
entryId: id,
path
};
}
function applyRouterHistoryAction(history, nextState, config, action) {
const path = (0, _getPathForState.getPathForState)(nextState, config.linking);
if (action?.type === 'push') {
return addHistoryEntry(history, nextState, path, 'push');
}
if (action?.type === 'pop') {
const index = findBackDestinationIndex(history, action);
return selectAndUpdateHistoryEntry(history, index, nextState, path);
}
// Push and pop change which tracked entry is current, so they need special handling above.
// `replace` and no instruction both refresh this entry. An explicit `replace` only matters
// while nested routers compose their instructions, where it can override a child push.
return selectAndUpdateHistoryEntry(history, history.index, nextState, path);
}
/** Adds a snapshot and assigns its ID to a new or already-created browser entry. */ function addHistoryEntry(history, state, path, browserOperation) {
const id = `${history.idPrefix}:${history.entrySeq}`;
// Navigating after Back discards the forward branch: A -> B, Back, then C becomes A -> C.
const entries = [
...history.entries.slice(0, history.index + 1),
{
id,
path,
state
}
];
return {
history: {
...history,
entries,
index: entries.length - 1,
entrySeq: history.entrySeq + 1
},
events: [
{
type: 'browser-history',
op: browserOperation,
entryId: id,
path
}
]
};
}
/** Finds the browser entry for the pop target, falling back to the router's pop count. */ function findBackDestinationIndex(history, action) {
const fallbackIndex = Math.max(0, history.index - action.count);
const { target } = action;
if (!target) {
return fallbackIndex;
}
// A single parent pop can remove several entries created by a nested stack.
const targetIndex = history.entries.findLastIndex((entry, index)=>index < history.index && isTargetRouteFocused(entry.state, target));
return targetIndex === -1 ? fallbackIndex : targetIndex;
}
/** Checks whether the target is visible by following only the state's focused route branch. */ function isTargetRouteFocused(state, target) {
let focusedState = state;
while(focusedState && focusedState.key !== target.navigatorKey){
focusedState = focusedState.routes[focusedState.index]?.state;
}
return focusedState?.routes[focusedState.index]?.key === target.routeKey;
}
/** Updates the selected snapshot and queues a browser move, if needed, followed by replacement. */ function selectAndUpdateHistoryEntry(history, index, state, path) {
const updatedHistory = updateHistoryEntry(history, index, state, path);
const events = [];
if (index !== history.index) {
// A router pop selected an earlier entry. Move the browser there before updating its URL;
// replacing the current entry alone would leave the browser's Back/Forward position unchanged.
events.push({
type: 'browser-history',
op: 'go',
delta: index - history.index
});
}
events.push(createReplaceEvent(updatedHistory.entries[index].id, path));
return {
history: updatedHistory,
events
};
}
/** Replaces a saved snapshot and selects it in memory without emitting browser commands. */ function updateHistoryEntry(history, index, state, path) {
if (!Number.isInteger(index) || index < 0 || index >= history.entries.length) {
throw new Error(`Invalid browser history index ${index} for ${history.entries.length} entries.`);
}
const entries = [
...history.entries
];
entries[index] = {
...entries[index],
path,
state
};
return {
...history,
entries,
index
};
}
function updateCurrentHistoryEntry(history, nextState, config) {
return applyRouterHistoryAction(history, nextState, config);
}
function restoreNavigationFromBrowser(history, result, change, config, reduce) {
const trackedEntry = history.entries.find((entry)=>entry.id === change.id);
if (trackedEntry) {
return restoreTrackedEntry(history, trackedEntry, result, change, config, reduce);
}
return restoreUntrackedEntry(history, result, change, config, reduce);
}
function restoreTrackedEntry(history, trackedEntry, result, change, config, reduce) {
const trackedIndex = history.entries.indexOf(trackedEntry);
const reduced = trackedEntry.path === change.path ? reduce(result, createResetIntent(trackedEntry.state)) : navigateToBrowserPath(history, trackedEntry.path, result, change.path, config, reduce);
if (reduced.state === result.state) {
// The restore was prevented, so move the browser back to the entry that matches the UI.
return {
result: reduced,
history,
events: trackedIndex === history.index ? [] : [
{
type: 'browser-history',
op: 'go',
delta: history.index - trackedIndex
}
]
};
}
const path = (0, _getPathForState.getPathForState)(reduced.state, config.linking);
return {
result: reduced,
history: updateHistoryEntry(history, trackedIndex, reduced.state, path),
events: path === change.path ? [] : [
createReplaceEvent(trackedEntry.id, path)
]
};
}
function createResetIntent(state) {
return {
type: 'ACTION',
payload: {
action: {
type: 'RESET',
payload: state,
target: state.key
}
}
};
}
/** Navigates to a browser URL, preserving route state for hash-only changes. */ function navigateToBrowserPath(history, basePath, result, path, config, reduce) {
// Only the hash changed, so the route keeps its state and gets the hash as a param.
if (stripHash(path) === stripHash(basePath)) {
return reduce(result, {
type: 'NAVIGATE_TO_HREF',
payload: {
href: path,
options: {
event: 'NAVIGATE'
}
}
});
}
const state = parseNavigationStateFromPath(path, result.state, config);
// A path outside the app falls back to the first entry.
return reduce(result, createResetIntent(state ?? history.entries[0].state));
}
function stripHash(path) {
const hashIndex = path.indexOf('#');
return hashIndex === -1 ? path : path.slice(0, hashIndex);
}
/** Parses the URL and fills in route keys and initial routes required by the navigation tree. */ function parseNavigationStateFromPath(path, currentState, config) {
const parsed = (0, _getPathForState.resolvePathLinking)(config.linking).getStateFromPath(path, config.linking?.config, (0, _getRouteInfoFromState.getRouteSegmentsFromState)(currentState));
const routeNames = (0, _utils.getRootStackRouteNames)();
if (!parsed || !parsed.routes.every((route)=>routeNames.includes(route.name))) {
return undefined;
}
return config.routeNode ? (0, _createSeededNavigationState.createSeededRootState)(parsed, config.routeNode) : (0, _createSeededNavigationState.completeParsedState)(parsed, _stateKeys.ROOT_CHAIN);
}
function restoreUntrackedEntry(history, result, change, config, reduce) {
const reduced = navigateToBrowserPath(history, history.entries[history.index].path, result, change.path, config, reduce);
const path = (0, _getPathForState.getPathForState)(reduced.state, config.linking);
if (change.id === null) {
// The browser created this entry (e.g. a hash link). Assign an ID without pushing it again.
return {
result: reduced,
...addHistoryEntry(history, reduced.state, path, 'replace')
};
}
// An ID from a previous page load has no saved snapshot. Start tracking from this entry.
return {
result: reduced,
history: {
...history,
entries: [
{
id: change.id,
path,
state: reduced.state
}
],
index: 0
},
events: [
createReplaceEvent(change.id, path)
]
};
}
//# sourceMappingURL=browserHistory.js.map