354 lines
16 KiB
JavaScript
354 lines
16 KiB
JavaScript
"use strict";
|
||
'use client';
|
||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||
if (k2 === undefined) k2 = k;
|
||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||
}
|
||
Object.defineProperty(o, k2, desc);
|
||
}) : (function(o, m, k, k2) {
|
||
if (k2 === undefined) k2 = k;
|
||
o[k2] = m[k];
|
||
}));
|
||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||
}) : function(o, v) {
|
||
o["default"] = v;
|
||
});
|
||
var __importStar = (this && this.__importStar) || (function () {
|
||
var ownKeys = function(o) {
|
||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||
var ar = [];
|
||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||
return ar;
|
||
};
|
||
return ownKeys(o);
|
||
};
|
||
return function (mod) {
|
||
if (mod && mod.__esModule) return mod;
|
||
var result = {};
|
||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||
__setModuleDefault(result, mod);
|
||
return result;
|
||
};
|
||
})();
|
||
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 = __importStar(require("react"));
|
||
const Route_1 = require("./Route");
|
||
const storeContext_1 = require("./global-state/storeContext");
|
||
const utils_1 = require("./global-state/utils");
|
||
const import_mode_1 = __importDefault(require("./import-mode"));
|
||
const ZoomTransitionEnabler_1 = require("./link/zoom/ZoomTransitionEnabler");
|
||
const zoom_transition_context_providers_1 = require("./link/zoom/zoom-transition-context-providers");
|
||
const navigationEvents_1 = require("./navigationEvents");
|
||
const utils_2 = require("./navigationEvents/utils");
|
||
const navigationParams_1 = require("./navigationParams");
|
||
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, useOnlyUserDefinedScreens = false) {
|
||
const node = (0, Route_1.useRouteNode)();
|
||
const nodeChildren = node?.children ?? [];
|
||
const children = useOnlyUserDefinedScreens
|
||
? nodeChildren.filter((child) => order.some((userDefinedScreen) => userDefinedScreen.name === child.route))
|
||
: nodeChildren;
|
||
const sorted = children.length ? getSortedChildren(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;
|
||
}
|
||
const WrappedScreenComponent = (props) => {
|
||
(0, utils_1.useColorSchemeChangesIfNeeded)();
|
||
return <ScreenComponent {...props}/>;
|
||
};
|
||
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 = navigation.isFocused();
|
||
const store = (0, storeContext_1.useExpoRouterStore)();
|
||
if (isFocused) {
|
||
const state = navigation.getState();
|
||
const isLeaf = !(state && 'state' in state.routes[state.index]);
|
||
if (isLeaf && stateForPath)
|
||
store.setFocusedState(stateForPath);
|
||
}
|
||
(0, react_1.useEffect)(() => navigation.addListener('focus', () => {
|
||
const state = navigation.getState();
|
||
const isLeaf = !(state && 'state' in state.routes[state.index]);
|
||
// Because setFocusedState caches the route info, this call will only trigger rerenders
|
||
// if the component itself didn’t rerender and the route info changed.
|
||
// Otherwise, the update from the `if` above will handle it,
|
||
// and this won’t cause a redundant second update.
|
||
if (isLeaf && stateForPath)
|
||
store.setFocusedState(stateForPath);
|
||
}), [navigation]);
|
||
(0, react_1.useEffect)(() => {
|
||
return navigation.addListener('transitionEnd', (e) => {
|
||
if (!e?.data?.closing) {
|
||
// When navigating to a screen, remove the no animation param to re-enable animations
|
||
// Otherwise the navigation back would also have no animation
|
||
if ((0, navigationParams_1.hasParam)(route?.params, navigationParams_1.INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME)) {
|
||
navigation.replaceParams((0, navigationParams_1.removeParams)(route?.params, [navigationParams_1.INTERNAL_EXPO_ROUTER_NO_ANIMATION_PARAM_NAME]));
|
||
}
|
||
}
|
||
});
|
||
}, [navigation]);
|
||
const isRouteType = value.type === 'route';
|
||
const hasRouteKey = !!route?.key;
|
||
return (<Route_1.Route node={value} params={route?.params}>
|
||
{navigationEvents_1.unstable_navigationEvents.isEnabled() && isRouteType && hasRouteKey && (<AnalyticsListeners navigation={navigation} screenId={route.key}/>)}
|
||
<zoom_transition_context_providers_1.ZoomTransitionTargetContextProvider route={route}>
|
||
<ZoomTransitionEnabler_1.ZoomTransitionEnabler route={route}/>
|
||
<react_1.default.Suspense fallback={<SuspenseFallback_1.SuspenseFallback route={value}/>}>
|
||
<WrappedScreenComponent {...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>
|
||
</zoom_transition_context_providers_1.ZoomTransitionTargetContextProvider>
|
||
</Route_1.Route>);
|
||
}
|
||
if (__DEV__) {
|
||
BaseRoute.displayName = `Route(${value.route})`;
|
||
}
|
||
qualifiedStore.set(value, BaseRoute);
|
||
return BaseRoute;
|
||
}
|
||
function AnalyticsListeners({ navigation, screenId, }) {
|
||
const stateForPath = (0, native_1.useStateForPath)();
|
||
const isFirstRenderRef = react_1.default.useRef(true);
|
||
const hasBlurredRef = react_1.default.useRef(true);
|
||
const stringUrl = (0, react_1.useMemo)(() => (0, utils_2.generateStringUrlForState)(stateForPath), [stateForPath]);
|
||
if (isFirstRenderRef.current) {
|
||
isFirstRenderRef.current = false;
|
||
if (stringUrl) {
|
||
navigationEvents_1.unstable_navigationEvents.emit('pageWillRender', {
|
||
...(0, utils_2.getPathAndParamsFromStringUrl)(stringUrl),
|
||
screenId,
|
||
});
|
||
}
|
||
}
|
||
(0, react_1.useEffect)(() => {
|
||
if (stringUrl) {
|
||
return () => {
|
||
navigationEvents_1.unstable_navigationEvents.emit('pageRemoved', {
|
||
...(0, utils_2.getPathAndParamsFromStringUrl)(stringUrl),
|
||
screenId,
|
||
});
|
||
};
|
||
}
|
||
return () => { };
|
||
}, [stringUrl, screenId]);
|
||
const isFocused = navigation.isFocused();
|
||
if (isFocused && stringUrl) {
|
||
navigationEvents_1.unstable_navigationEvents.emit('pageFocused', {
|
||
...(0, utils_2.getPathAndParamsFromStringUrl)(stringUrl),
|
||
screenId,
|
||
});
|
||
hasBlurredRef.current = false;
|
||
}
|
||
(0, react_1.useEffect)(() => {
|
||
if (stringUrl) {
|
||
const cleanFocus = navigation.addListener('focus', () => {
|
||
// If the screen was not blurred, don't emit focused again
|
||
// hasBlurredRef will be false when the screen was initially focused
|
||
if (hasBlurredRef.current) {
|
||
navigationEvents_1.unstable_navigationEvents.emit('pageFocused', {
|
||
...(0, utils_2.getPathAndParamsFromStringUrl)(stringUrl),
|
||
screenId,
|
||
});
|
||
hasBlurredRef.current = false;
|
||
}
|
||
});
|
||
const cleanBlur = navigation.addListener('blur', () => {
|
||
navigationEvents_1.unstable_navigationEvents.emit('pageBlurred', {
|
||
...(0, utils_2.getPathAndParamsFromStringUrl)(stringUrl),
|
||
screenId,
|
||
});
|
||
hasBlurredRef.current = true;
|
||
});
|
||
return () => {
|
||
cleanFocus();
|
||
cleanBlur();
|
||
};
|
||
}
|
||
return () => { };
|
||
}, [navigation, stringUrl, screenId]);
|
||
return null;
|
||
}
|
||
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.internal) {
|
||
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('[') && segment.endsWith(']')) {
|
||
return options.params?.[segment.slice(1, -1)] || segment;
|
||
}
|
||
else {
|
||
return segment;
|
||
}
|
||
})
|
||
.join('/');
|
||
}
|
||
//# sourceMappingURL=useScreens.js.map
|