UNPKG

@react-navigation/core

Version:

Core utilities for building navigators

258 lines (249 loc) 9.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createComponentForStaticNavigation = createComponentForStaticNavigation; exports.createPathConfigForStaticNavigation = createPathConfigForStaticNavigation; var _react = _interopRequireWildcard(require("react")); var React = _react; var _reactIs = require("react-is"); var _useRoute = require("./useRoute.js"); var _jsxRuntime = require("react/jsx-runtime"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } /** * Flatten a type to remove all type alias names, unions etc. * This will show a plain object when hovering over the type. */ /** * keyof T doesn't work for union types. We can use distributive conditional types instead. * https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types */ /** * We get a union type when using keyof, but we want an intersection instead. * https://stackoverflow.com/a/50375286/1665026 */ /** * Props for a screen component which is rendered by a static navigator. * Takes the route params as a generic argument. */ /** * Infer the param list from the static navigation config. */ const MemoizedScreen = /*#__PURE__*/React.memo(({ component }) => { const route = (0, _useRoute.useRoute)(); const children = /*#__PURE__*/React.createElement(component, { route }); return children; }); MemoizedScreen.displayName = 'Memo(Screen)'; const getItemsFromScreens = (Screen, screens) => { return Object.entries(screens).map(([name, item]) => { let component; let props = {}; let useIf; let isNavigator = false; if ('screen' in item) { const { screen, if: _if, ...rest } = item; useIf = _if; props = rest; if ((0, _reactIs.isValidElementType)(screen)) { component = screen; } else if ('config' in screen) { isNavigator = true; component = createComponentForStaticNavigation(screen, `${name}Navigator`); } } else if ((0, _reactIs.isValidElementType)(item)) { component = item; } else if ('config' in item) { isNavigator = true; component = createComponentForStaticNavigation(item, `${name}Navigator`); } if (component == null) { throw new Error(`Couldn't find a 'screen' property for the screen '${name}'. This can happen if you passed 'undefined'. You likely forgot to export your component from the file it's defined in, or mixed up default import and named import when importing.`); } const element = isNavigator ? (/*#__PURE__*/React.createElement(component, {})) : /*#__PURE__*/(0, _jsxRuntime.jsx)(MemoizedScreen, { component: component }); return () => { const shouldRender = useIf == null || useIf(); if (!shouldRender) { return null; } return /*#__PURE__*/(0, _jsxRuntime.jsx)(Screen, { name: name, ...props, children: () => element }, name); }; }); }; /** * Create a component that renders a navigator based on the static configuration. * * @param tree Static navigation config. * @param displayName Name of the component to be displayed in React DevTools. * @returns A component which renders the navigator. */ function createComponentForStaticNavigation(tree, displayName) { const { Navigator, Group, Screen, config } = tree; const { screens, groups, ...rest } = config; if (screens == null && groups == null) { throw new Error("Couldn't find a 'screens' or 'groups' property. Make sure to define your screens under a 'screens' property in the configuration."); } const items = []; // Loop through the config to find screens and groups // So we add the screens and groups in the same order as they are defined for (const key in config) { if (key === 'screens' && screens) { items.push(...getItemsFromScreens(Screen, screens)); } if (key === 'groups' && groups) { items.push(...Object.entries(groups).map(([key, { if: useIf, ...group }]) => { const groupItems = getItemsFromScreens(Screen, group.screens); return () => { // Call unconditionally since screen configs may contain `useIf` hooks const children = groupItems.map(item => item()); const shouldRender = useIf == null || useIf(); if (!shouldRender) { return null; } return /*#__PURE__*/(0, _react.createElement)(Group, { navigationKey: key, ...group, key: key }, children); }; })); } } const NavigatorComponent = () => { const children = items.map(item => item()); return /*#__PURE__*/(0, _jsxRuntime.jsx)(Navigator, { ...rest, children: children }); }; NavigatorComponent.displayName = displayName; return NavigatorComponent; } /** * Create a path config object from a static navigation config for deep linking. * * @param tree Static navigation config. * @param options Additional options from `linking.config`. * @param auto Whether to automatically generate paths for leaf screens. * @returns Path config object to use in linking config. * * @example * ```js * const config = { * screens: { * Home: { * screens: createPathConfigForStaticNavigation(HomeTabs), * }, * }, * }; * ``` */ function createPathConfigForStaticNavigation(tree, options, auto) { let initialScreenConfig; const createPathConfigForTree = (t, o, skipInitialDetection) => { const createPathConfigForScreens = (screens, initialRouteName) => { return Object.fromEntries(Object.entries(screens) // Re-order to move the initial route to the front // This way we can detect the initial route correctly .sort(([a], [b]) => { if (a === initialRouteName) { return -1; } if (b === initialRouteName) { return 1; } return 0; }).map(([key, item]) => { const screenConfig = {}; if ('linking' in item) { if (typeof item.linking === 'string') { screenConfig.path = item.linking; } else { Object.assign(screenConfig, item.linking); } if (typeof screenConfig.path === 'string') { screenConfig.path = screenConfig.path.replace(/^\//, '') // Remove extra leading slash .replace(/\/$/, ''); // Remove extra trailing slash } } let screens; const skipInitialDetectionInChild = skipInitialDetection || screenConfig.path != null && screenConfig.path !== ''; if ('config' in item) { screens = createPathConfigForTree(item, undefined, skipInitialDetectionInChild); } else if ('screen' in item && 'config' in item.screen && (item.screen.config.screens || item.screen.config.groups)) { screens = createPathConfigForTree(item.screen, undefined, skipInitialDetectionInChild); } if (screens) { screenConfig.screens = screens; } if (auto && !screenConfig.screens && // Skip generating path for screens that specify linking config as `undefined` or `null` explicitly !('linking' in item && item.linking == null)) { if (screenConfig.path != null) { if (!skipInitialDetection && screenConfig.path === '') { // We encounter a leaf screen with empty path, // Clear the initial screen config as it's not needed anymore initialScreenConfig = undefined; } } else { if (!skipInitialDetection && initialScreenConfig == null) { initialScreenConfig = screenConfig; } screenConfig.path = key.replace(/([A-Z]+)/g, '-$1').replace(/^-/, '').toLowerCase(); } } return [key, screenConfig]; }).filter(([, screen]) => Object.keys(screen).length > 0)); }; const screens = {}; // Loop through the config to find screens and groups // So we add the screens and groups in the same order as they are defined for (const key in t.config) { if (key === 'screens' && t.config.screens) { Object.assign(screens, createPathConfigForScreens(t.config.screens, o?.initialRouteName ?? t.config.initialRouteName)); } if (key === 'groups' && t.config.groups) { Object.entries(t.config.groups).forEach(([, group]) => { Object.assign(screens, createPathConfigForScreens(group.screens, o?.initialRouteName ?? t.config.initialRouteName)); }); } } if (Object.keys(screens).length === 0) { return undefined; } return screens; }; const screens = createPathConfigForTree(tree, options, false); if (auto && initialScreenConfig) { initialScreenConfig.path = ''; } return screens; } //# sourceMappingURL=StaticNavigation.js.map