expo-router
Version:
Expo Router is a file-based router for React Native and web applications.
220 lines • 9.27 kB
JavaScript
'use client';
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.useSortedScreens = useSortedScreens;
exports.getQualifiedRouteComponent = getQualifiedRouteComponent;
exports.screenOptionsFactory = screenOptionsFactory;
exports.routeToScreen = routeToScreen;
exports.getSingularId = getSingularId;
const native_1 = require("@react-navigation/native");
const react_1 = __importDefault(require("react"));
const Route_1 = require("./Route");
const storeContext_1 = require("./global-state/storeContext");
const import_mode_1 = __importDefault(require("./import-mode"));
const primitives_1 = require("./primitives");
const EmptyRoute_1 = require("./views/EmptyRoute");
const SuspenseFallback_1 = require("./views/SuspenseFallback");
const Try_1 = require("./views/Try");
function getSortedChildren(children, order = [], initialRouteName) {
if (!order?.length) {
return children
.sort((0, Route_1.sortRoutesWithInitial)(initialRouteName))
.map((route) => ({ route, props: {} }));
}
const entries = [...children];
const ordered = order
.map(({ name, redirect, initialParams, listeners, options, getId, dangerouslySingular: singular, }) => {
if (!entries.length) {
console.warn(`[Layout children]: Too many screens defined. Route "${name}" is extraneous.`);
return null;
}
const matchIndex = entries.findIndex((child) => child.route === name);
if (matchIndex === -1) {
console.warn(`[Layout children]: No route named "${name}" exists in nested children:`, children.map(({ route }) => route));
return null;
}
else {
// Get match and remove from entries
const match = entries[matchIndex];
entries.splice(matchIndex, 1);
// Ensure to return null after removing from entries.
if (redirect) {
if (typeof redirect === 'string') {
throw new Error(`Redirecting to a specific route is not supported yet.`);
}
return null;
}
if (getId) {
console.warn(`Deprecated: prop 'getId' on screen ${name} is deprecated. Please rename the prop to 'dangerouslySingular'`);
if (singular) {
console.warn(`Screen ${name} cannot use both getId and dangerouslySingular together.`);
}
}
else if (singular) {
// If singular is set, use it as the getId function.
if (typeof singular === 'string') {
getId = () => singular;
}
else if (typeof singular === 'function' && name) {
getId = (options) => singular(name, options.params || {});
}
else if (singular === true && name) {
getId = (options) => getSingularId(name, options);
}
}
return {
route: match,
props: { initialParams, listeners, options, getId },
};
}
})
.filter(Boolean);
// Add any remaining children
ordered.push(...entries.sort((0, Route_1.sortRoutesWithInitial)(initialRouteName)).map((route) => ({ route, props: {} })));
return ordered;
}
/**
* @returns React Navigation screens sorted by the `route` property.
*/
function useSortedScreens(order, protectedScreens) {
const node = (0, Route_1.useRouteNode)();
const sorted = node?.children?.length
? getSortedChildren(node.children, order, node.initialRouteName)
: [];
return react_1.default.useMemo(() => sorted
.filter((item) => !protectedScreens.has(item.route.route))
.map((value) => {
return routeToScreen(value.route, value.props);
}), [sorted, protectedScreens]);
}
function fromImport(value, { ErrorBoundary, ...component }) {
// If possible, add a more helpful display name for the component stack to improve debugging of React errors such as `Text strings must be rendered within a <Text> component.`.
if (component?.default && __DEV__) {
component.default.displayName ??= `${component.default.name ?? 'Route'}(${value.contextKey})`;
}
if (ErrorBoundary) {
const Wrapped = react_1.default.forwardRef((props, ref) => {
const children = react_1.default.createElement(component.default || EmptyRoute_1.EmptyRoute, {
...props,
ref,
});
return <Try_1.Try catch={ErrorBoundary}>{children}</Try_1.Try>;
});
if (__DEV__) {
Wrapped.displayName = `ErrorBoundary(${value.contextKey})`;
}
return {
default: Wrapped,
};
}
if (process.env.NODE_ENV !== 'production') {
if (typeof component.default === 'object' &&
component.default &&
Object.keys(component.default).length === 0) {
return { default: EmptyRoute_1.EmptyRoute };
}
}
return { default: component.default };
}
function fromLoadedRoute(value, res) {
if (!(res instanceof Promise)) {
return fromImport(value, res);
}
return res.then(fromImport.bind(null, value));
}
// TODO: Maybe there's a more React-y way to do this?
// Without this store, the process enters a recursive loop.
const qualifiedStore = new WeakMap();
/** Wrap the component with various enhancements and add access to child routes. */
function getQualifiedRouteComponent(value) {
if (qualifiedStore.has(value)) {
return qualifiedStore.get(value);
}
let ScreenComponent;
// TODO: This ensures sync doesn't use React.lazy, but it's not ideal.
if (import_mode_1.default === 'lazy') {
ScreenComponent = react_1.default.lazy(async () => {
const res = value.loadRoute();
return fromLoadedRoute(value, res);
});
if (__DEV__) {
ScreenComponent.displayName = `AsyncRoute(${value.route})`;
}
}
else {
const res = value.loadRoute();
ScreenComponent = fromImport(value, res).default;
}
function BaseRoute({
// Remove these React Navigation props to
// enforce usage of expo-router hooks (where the query params are correct).
route, navigation,
// Pass all other props to the component
...props }) {
const stateForPath = (0, native_1.useStateForPath)();
const isFocused = (0, native_1.useIsFocused)();
const store = (0, storeContext_1.useExpoRouterStore)();
if (isFocused) {
const state = navigation.getState();
const isLeaf = !('state' in state.routes[state.index]);
if (isLeaf && stateForPath)
store.setFocusedState(stateForPath);
}
return (<Route_1.Route node={value} route={route}>
<react_1.default.Suspense fallback={<SuspenseFallback_1.SuspenseFallback route={value}/>}>
<ScreenComponent {...props}
// Expose the template segment path, e.g. `(home)`, `[foo]`, `index`
// the intention is to make it possible to deduce shared routes.
segment={value.route}/>
</react_1.default.Suspense>
</Route_1.Route>);
}
if (__DEV__) {
BaseRoute.displayName = `Route(${value.route})`;
}
qualifiedStore.set(value, BaseRoute);
return BaseRoute;
}
function screenOptionsFactory(route, options) {
return (args) => {
// Only eager load generated components
const staticOptions = route.generated ? route.loadRoute()?.getNavOptions : null;
const staticResult = typeof staticOptions === 'function' ? staticOptions(args) : staticOptions;
const dynamicResult = typeof options === 'function' ? options?.(args) : options;
const output = {
...staticResult,
...dynamicResult,
};
// Prevent generated screens from showing up in the tab bar.
if (route.generated) {
output.tabBarItemStyle = { display: 'none' };
output.tabBarButton = () => null;
// TODO: React Navigation doesn't provide a way to prevent rendering the drawer item.
output.drawerItemStyle = { height: 0, display: 'none' };
}
return output;
};
}
function routeToScreen(route, { options, getId, ...props } = {}) {
return (<primitives_1.Screen {...props} name={route.route} key={route.route} getId={getId} options={screenOptionsFactory(route, options)} getComponent={() => getQualifiedRouteComponent(route)}/>);
}
function getSingularId(name, options = {}) {
return name
.split('/')
.map((segment) => {
if (segment.startsWith('[...')) {
return options.params?.[segment.slice(4, -1)]?.join('/') || segment;
}
else if (segment.startsWith('[')) {
return options.params?.[segment.slice(1, -1)] || segment;
}
else {
return segment;
}
})
.join('/');
}
//# sourceMappingURL=useScreens.js.map
;