@redshank/native-router
Version:
@redshank/native-router is a file-based router for React Native CLI
1,666 lines (1,482 loc) • 108 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var native = require('@react-navigation/native');
var reactNative = require('react-native');
var React = require('react');
var jsxDevRuntime = require('react/jsx-dev-runtime');
require('react/jsx-runtime');
var URL = require('url-parse');
var nativeStack = require('@react-navigation/native-stack');
var bottomTabs = require('@react-navigation/bottom-tabs');
var drawer = require('@react-navigation/drawer');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
function _objectWithoutPropertiesLoose(r, e) {
if (null == r) return {};
var t = {};
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
if (-1 !== e.indexOf(n)) continue;
t[n] = r[n];
}
return t;
}
var _jsxFileName$6 = "/Users/rivaslive/Documentos/npm/redshank/packages/native-router/src/components/PageNotFound.tsx";
const PageNotFound = ({}) => {
return /*#__PURE__*/jsxDevRuntime.jsxDEV(reactNative.SafeAreaView, {
style: styles$1.container,
children: /*#__PURE__*/jsxDevRuntime.jsxDEV(reactNative.Text, {
style: styles$1.title,
children: "Page Not Found"
}, void 0, false, {
fileName: _jsxFileName$6,
lineNumber: 7,
columnNumber: 7
}, undefined)
}, void 0, false, {
fileName: _jsxFileName$6,
lineNumber: 6,
columnNumber: 5
}, undefined);
};
const styles$1 = reactNative.StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 16
},
title: {
fontSize: 24,
marginBottom: 16
},
subtitle: {
textAlign: 'center'
},
dirText: {
fontWeight: 'bold'
}
});
/** Match `[page]` -> `page` */
function matchDynamicName(name) {
var _name$match;
// Don't match `...` or `[` or `]` inside the brackets
// eslint-disable-next-line no-useless-escape
return (_name$match = name.match(/^\[([^[\](?:\.\.\.)]+?)\]$/)) == null ? void 0 : _name$match[1];
}
/** Match `[...page]` -> `page` */
function matchDeepDynamicRouteName(name) {
var _name$match2;
return (_name$match2 = name.match(/^\[\.\.\.([^/]+?)\]$/)) == null ? void 0 : _name$match2[1];
}
/** Match `(page)` -> `page` */
function matchGroupName(name) {
var _name$match3;
return (_name$match3 = name.match(/^(?:[^\\(\\)])*?\(([^\\/]+)\).*?$/)) == null ? void 0 : _name$match3[1];
}
function getNameFromFilePath(name) {
return removeSupportedExtensions(removeFileSystemDots(name));
}
function getContextKey(name) {
// The root path is `` (empty string) so always prepend `/` to ensure
// there is some value.
const normal = '/' + getNameFromFilePath(name);
if (!normal.endsWith('_layout')) {
return normal;
}
return normal.replace(/\/?_layout$/, '');
}
/** Remove `.js`, `.ts`, `.jsx`, `.tsx` */
function removeSupportedExtensions(name) {
return name.replace(/\.[jt]sx?$/g, '');
}
// Remove any amount of `./` and `../` from the start of the string
function removeFileSystemDots(filePath) {
return filePath.replace(/^(?:\.\.?\/)+/g, '');
}
function stripGroupSegmentsFromPath(path) {
return path.split('/').reduce((acc, v) => {
if (matchGroupName(v) == null) {
acc.push(v);
}
return acc;
}, []).join('/');
}
function stripInvisibleSegmentsFromPath(path) {
return stripGroupSegmentsFromPath(path).replace(/\/?index$/, '');
}
var _jsxFileName$5 = "/Users/rivaslive/Documentos/npm/redshank/packages/native-router/src/Route.tsx";
const CurrentRouteContext = /*#__PURE__*/React.createContext(null);
/** Return the RouteNode at the current contextual boundary. */
function useRouteNode() {
return React.useContext(CurrentRouteContext);
}
function useContextKey() {
const node = useRouteNode();
if (node == null) {
throw new Error('No filename found');
}
return getContextKey(node.contextKey);
}
/** Provides the matching routes and filename to the children. */
function Route({
children,
node
}) {
return /*#__PURE__*/jsxDevRuntime.jsxDEV(CurrentRouteContext.Provider, {
value: node,
children: children
}, void 0, false, {
fileName: _jsxFileName$5,
lineNumber: 62,
columnNumber: 5
}, this);
}
var _jsxFileName$4 = "/Users/rivaslive/Documentos/npm/redshank/packages/native-router/src/components/ErrorBoundary.tsx";
const ErrorBoundary = ({
children,
name
}) => {
const [hasError, setHasError] = React.useState(false);
if (hasError) {
return /*#__PURE__*/jsxDevRuntime.jsxDEV(reactNative.SafeAreaView, {
style: styles.container,
children: /*#__PURE__*/jsxDevRuntime.jsxDEV(reactNative.Text, {
style: styles.text,
children: `Failed to load ${name}`
}, void 0, false, {
fileName: _jsxFileName$4,
lineNumber: 15,
columnNumber: 9
}, undefined)
}, void 0, false, {
fileName: _jsxFileName$4,
lineNumber: 14,
columnNumber: 7
}, undefined);
}
try {
return /*#__PURE__*/jsxDevRuntime.jsxDEV(jsxDevRuntime.Fragment, {
children: children
}, void 0, false);
} catch (error) {
console.log(error);
setHasError(true);
return /*#__PURE__*/jsxDevRuntime.jsxDEV(reactNative.SafeAreaView, {
style: styles.container,
children: /*#__PURE__*/jsxDevRuntime.jsxDEV(reactNative.Text, {
style: styles.text,
children: `Failed to load ${name}`
}, void 0, false, {
fileName: _jsxFileName$4,
lineNumber: 27,
columnNumber: 9
}, undefined)
}, void 0, false, {
fileName: _jsxFileName$4,
lineNumber: 26,
columnNumber: 7
}, undefined);
}
};
const styles = reactNative.StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
text: {
fontSize: 24,
textAlign: 'center'
}
});
var _jsxFileName$3 = "/Users/rivaslive/Documentos/npm/redshank/packages/native-router/src/routeBuilder.tsx";
const importModule = route => {
const component = route.loadRoute();
if (component instanceof Promise) {
return component;
}
const response = {
default: component.default || PageNotFound
};
return new Promise(resolve => resolve(response));
};
const routeStored = new WeakMap();
function buildRouter(value, fallback) {
if (routeStored.has(value)) {
return routeStored.get(value);
}
let getLoadable;
if (process.env.IMPORT_MODE === 'lazy') {
const AsyncComponent = /*#__PURE__*/React.lazy(() => importModule(value));
getLoadable = (props, ref) => {
return /*#__PURE__*/jsxDevRuntime.jsxDEV(ErrorBoundary, {
name: value.contextKey,
children: /*#__PURE__*/jsxDevRuntime.jsxDEV(React.Suspense, {
fallback: /*#__PURE__*/jsxDevRuntime.jsxDEV(reactNative.View, {}, void 0, false, {
fileName: _jsxFileName$3,
lineNumber: 32,
columnNumber: 49
}, this),
children: /*#__PURE__*/jsxDevRuntime.jsxDEV(AsyncComponent, _extends({}, _extends({}, props, {
ref,
segment: value.route
})), void 0, false, {
fileName: _jsxFileName$3,
lineNumber: 33,
columnNumber: 13
}, this)
}, void 0, false, {
fileName: _jsxFileName$3,
lineNumber: 32,
columnNumber: 11
}, this)
}, void 0, false, {
fileName: _jsxFileName$3,
lineNumber: 31,
columnNumber: 9
}, this);
};
} else {
const SyncComponent = /*#__PURE__*/React.forwardRef((props, ref) => {
const Component = value.loadRoute().default;
if (Component) {
return /*#__PURE__*/jsxDevRuntime.jsxDEV(Component, _extends({}, props, {
ref: ref
}), void 0, false, {
fileName: _jsxFileName$3,
lineNumber: 48,
columnNumber: 16
}, this);
}
return /*#__PURE__*/jsxDevRuntime.jsxDEV(PageNotFound, {}, void 0, false, {
fileName: _jsxFileName$3,
lineNumber: 50,
columnNumber: 14
}, this);
});
getLoadable = (props, ref) => /*#__PURE__*/jsxDevRuntime.jsxDEV(SyncComponent, _extends({}, _extends({}, props, {
ref,
segment: value.route
})), void 0, false, {
fileName: _jsxFileName$3,
lineNumber: 54,
columnNumber: 7
}, this);
}
const RouteWithRef = /*#__PURE__*/React.forwardRef((props, ref) => {
return /*#__PURE__*/jsxDevRuntime.jsxDEV(Route, {
node: value,
children: getLoadable(props, ref)
}, void 0, false, {
fileName: _jsxFileName$3,
lineNumber: 65,
columnNumber: 12
}, this);
});
RouteWithRef.displayName = `Route(${value.route})`;
routeStored.set(value, RouteWithRef);
return RouteWithRef;
}
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var useIsomorphicLayoutEffect$1 = {};
Object.defineProperty(useIsomorphicLayoutEffect$1, "__esModule", { value: true });
var react_1 = React;
/**
* Use `useEffect` during SSR and `useLayoutEffect` in the browser to avoid warnings.
*/
var useIsomorphicLayoutEffect = typeof document !== 'undefined' ? react_1.useLayoutEffect : react_1.useEffect;
useIsomorphicLayoutEffect$1.default = useIsomorphicLayoutEffect;
const ThemeContext = /*#__PURE__*/React__namespace.createContext(undefined);
ThemeContext.displayName = 'ThemeContext';
var queryString = {};
var strictUriEncode = str => encodeURIComponent(str).replace(/[!'()*]/g, x => `%${x.charCodeAt(0).toString(16).toUpperCase()}`);
var token = '%[a-f0-9]{2}';
var singleMatcher = new RegExp('(' + token + ')|([^%]+?)', 'gi');
var multiMatcher = new RegExp('(' + token + ')+', 'gi');
function decodeComponents(components, split) {
try {
// Try to decode the entire string first
return [decodeURIComponent(components.join(''))];
} catch (err) {
// Do nothing
}
if (components.length === 1) {
return components;
}
split = split || 1;
// Split the array in 2 parts
var left = components.slice(0, split);
var right = components.slice(split);
return Array.prototype.concat.call([], decodeComponents(left), decodeComponents(right));
}
function decode(input) {
try {
return decodeURIComponent(input);
} catch (err) {
var tokens = input.match(singleMatcher) || [];
for (var i = 1; i < tokens.length; i++) {
input = decodeComponents(tokens, i).join('');
tokens = input.match(singleMatcher) || [];
}
return input;
}
}
function customDecodeURIComponent(input) {
// Keep track of all the replacements and prefill the map with the `BOM`
var replaceMap = {
'%FE%FF': '\uFFFD\uFFFD',
'%FF%FE': '\uFFFD\uFFFD'
};
var match = multiMatcher.exec(input);
while (match) {
try {
// Decode as big chunks as possible
replaceMap[match[0]] = decodeURIComponent(match[0]);
} catch (err) {
var result = decode(match[0]);
if (result !== match[0]) {
replaceMap[match[0]] = result;
}
}
match = multiMatcher.exec(input);
}
// Add `%C2` at the end of the map to make sure it does not replace the combinator before everything else
replaceMap['%C2'] = '\uFFFD';
var entries = Object.keys(replaceMap);
for (var i = 0; i < entries.length; i++) {
// Replace all decoded components
var key = entries[i];
input = input.replace(new RegExp(key, 'g'), replaceMap[key]);
}
return input;
}
var decodeUriComponent = function (encodedURI) {
if (typeof encodedURI !== 'string') {
throw new TypeError('Expected `encodedURI` to be of type `string`, got `' + typeof encodedURI + '`');
}
try {
encodedURI = encodedURI.replace(/\+/g, ' ');
// Try the built in decoder first
return decodeURIComponent(encodedURI);
} catch (err) {
// Fallback to a more advanced decoder
return customDecodeURIComponent(encodedURI);
}
};
var splitOnFirst = (string, separator) => {
if (!(typeof string === 'string' && typeof separator === 'string')) {
throw new TypeError('Expected the arguments to be of type `string`');
}
if (separator === '') {
return [string];
}
const separatorIndex = string.indexOf(separator);
if (separatorIndex === -1) {
return [string];
}
return [
string.slice(0, separatorIndex),
string.slice(separatorIndex + separator.length)
];
};
var filterObj = function (obj, predicate) {
var ret = {};
var keys = Object.keys(obj);
var isArr = Array.isArray(predicate);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var val = obj[key];
if (isArr ? predicate.indexOf(key) !== -1 : predicate(key, val, obj)) {
ret[key] = val;
}
}
return ret;
};
(function (exports) {
const strictUriEncode$1 = strictUriEncode;
const decodeComponent = decodeUriComponent;
const splitOnFirst$1 = splitOnFirst;
const filterObject = filterObj;
const isNullOrUndefined = value => value === null || value === undefined;
const encodeFragmentIdentifier = Symbol('encodeFragmentIdentifier');
function encoderForArrayFormat(options) {
switch (options.arrayFormat) {
case 'index':
return key => (result, value) => {
const index = result.length;
if (
value === undefined ||
(options.skipNull && value === null) ||
(options.skipEmptyString && value === '')
) {
return result;
}
if (value === null) {
return [...result, [encode(key, options), '[', index, ']'].join('')];
}
return [
...result,
[encode(key, options), '[', encode(index, options), ']=', encode(value, options)].join('')
];
};
case 'bracket':
return key => (result, value) => {
if (
value === undefined ||
(options.skipNull && value === null) ||
(options.skipEmptyString && value === '')
) {
return result;
}
if (value === null) {
return [...result, [encode(key, options), '[]'].join('')];
}
return [...result, [encode(key, options), '[]=', encode(value, options)].join('')];
};
case 'colon-list-separator':
return key => (result, value) => {
if (
value === undefined ||
(options.skipNull && value === null) ||
(options.skipEmptyString && value === '')
) {
return result;
}
if (value === null) {
return [...result, [encode(key, options), ':list='].join('')];
}
return [...result, [encode(key, options), ':list=', encode(value, options)].join('')];
};
case 'comma':
case 'separator':
case 'bracket-separator': {
const keyValueSep = options.arrayFormat === 'bracket-separator' ?
'[]=' :
'=';
return key => (result, value) => {
if (
value === undefined ||
(options.skipNull && value === null) ||
(options.skipEmptyString && value === '')
) {
return result;
}
// Translate null to an empty string so that it doesn't serialize as 'null'
value = value === null ? '' : value;
if (result.length === 0) {
return [[encode(key, options), keyValueSep, encode(value, options)].join('')];
}
return [[result, encode(value, options)].join(options.arrayFormatSeparator)];
};
}
default:
return key => (result, value) => {
if (
value === undefined ||
(options.skipNull && value === null) ||
(options.skipEmptyString && value === '')
) {
return result;
}
if (value === null) {
return [...result, encode(key, options)];
}
return [...result, [encode(key, options), '=', encode(value, options)].join('')];
};
}
}
function parserForArrayFormat(options) {
let result;
switch (options.arrayFormat) {
case 'index':
return (key, value, accumulator) => {
result = /\[(\d*)\]$/.exec(key);
key = key.replace(/\[\d*\]$/, '');
if (!result) {
accumulator[key] = value;
return;
}
if (accumulator[key] === undefined) {
accumulator[key] = {};
}
accumulator[key][result[1]] = value;
};
case 'bracket':
return (key, value, accumulator) => {
result = /(\[\])$/.exec(key);
key = key.replace(/\[\]$/, '');
if (!result) {
accumulator[key] = value;
return;
}
if (accumulator[key] === undefined) {
accumulator[key] = [value];
return;
}
accumulator[key] = [].concat(accumulator[key], value);
};
case 'colon-list-separator':
return (key, value, accumulator) => {
result = /(:list)$/.exec(key);
key = key.replace(/:list$/, '');
if (!result) {
accumulator[key] = value;
return;
}
if (accumulator[key] === undefined) {
accumulator[key] = [value];
return;
}
accumulator[key] = [].concat(accumulator[key], value);
};
case 'comma':
case 'separator':
return (key, value, accumulator) => {
const isArray = typeof value === 'string' && value.includes(options.arrayFormatSeparator);
const isEncodedArray = (typeof value === 'string' && !isArray && decode(value, options).includes(options.arrayFormatSeparator));
value = isEncodedArray ? decode(value, options) : value;
const newValue = isArray || isEncodedArray ? value.split(options.arrayFormatSeparator).map(item => decode(item, options)) : value === null ? value : decode(value, options);
accumulator[key] = newValue;
};
case 'bracket-separator':
return (key, value, accumulator) => {
const isArray = /(\[\])$/.test(key);
key = key.replace(/\[\]$/, '');
if (!isArray) {
accumulator[key] = value ? decode(value, options) : value;
return;
}
const arrayValue = value === null ?
[] :
value.split(options.arrayFormatSeparator).map(item => decode(item, options));
if (accumulator[key] === undefined) {
accumulator[key] = arrayValue;
return;
}
accumulator[key] = [].concat(accumulator[key], arrayValue);
};
default:
return (key, value, accumulator) => {
if (accumulator[key] === undefined) {
accumulator[key] = value;
return;
}
accumulator[key] = [].concat(accumulator[key], value);
};
}
}
function validateArrayFormatSeparator(value) {
if (typeof value !== 'string' || value.length !== 1) {
throw new TypeError('arrayFormatSeparator must be single character string');
}
}
function encode(value, options) {
if (options.encode) {
return options.strict ? strictUriEncode$1(value) : encodeURIComponent(value);
}
return value;
}
function decode(value, options) {
if (options.decode) {
return decodeComponent(value);
}
return value;
}
function keysSorter(input) {
if (Array.isArray(input)) {
return input.sort();
}
if (typeof input === 'object') {
return keysSorter(Object.keys(input))
.sort((a, b) => Number(a) - Number(b))
.map(key => input[key]);
}
return input;
}
function removeHash(input) {
const hashStart = input.indexOf('#');
if (hashStart !== -1) {
input = input.slice(0, hashStart);
}
return input;
}
function getHash(url) {
let hash = '';
const hashStart = url.indexOf('#');
if (hashStart !== -1) {
hash = url.slice(hashStart);
}
return hash;
}
function extract(input) {
input = removeHash(input);
const queryStart = input.indexOf('?');
if (queryStart === -1) {
return '';
}
return input.slice(queryStart + 1);
}
function parseValue(value, options) {
if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) {
value = Number(value);
} else if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) {
value = value.toLowerCase() === 'true';
}
return value;
}
function parse(query, options) {
options = Object.assign({
decode: true,
sort: true,
arrayFormat: 'none',
arrayFormatSeparator: ',',
parseNumbers: false,
parseBooleans: false
}, options);
validateArrayFormatSeparator(options.arrayFormatSeparator);
const formatter = parserForArrayFormat(options);
// Create an object with no prototype
const ret = Object.create(null);
if (typeof query !== 'string') {
return ret;
}
query = query.trim().replace(/^[?#&]/, '');
if (!query) {
return ret;
}
for (const param of query.split('&')) {
if (param === '') {
continue;
}
let [key, value] = splitOnFirst$1(options.decode ? param.replace(/\+/g, ' ') : param, '=');
// Missing `=` should be `null`:
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
value = value === undefined ? null : ['comma', 'separator', 'bracket-separator'].includes(options.arrayFormat) ? value : decode(value, options);
formatter(decode(key, options), value, ret);
}
for (const key of Object.keys(ret)) {
const value = ret[key];
if (typeof value === 'object' && value !== null) {
for (const k of Object.keys(value)) {
value[k] = parseValue(value[k], options);
}
} else {
ret[key] = parseValue(value, options);
}
}
if (options.sort === false) {
return ret;
}
return (options.sort === true ? Object.keys(ret).sort() : Object.keys(ret).sort(options.sort)).reduce((result, key) => {
const value = ret[key];
if (Boolean(value) && typeof value === 'object' && !Array.isArray(value)) {
// Sort object keys, not values
result[key] = keysSorter(value);
} else {
result[key] = value;
}
return result;
}, Object.create(null));
}
exports.extract = extract;
exports.parse = parse;
exports.stringify = (object, options) => {
if (!object) {
return '';
}
options = Object.assign({
encode: true,
strict: true,
arrayFormat: 'none',
arrayFormatSeparator: ','
}, options);
validateArrayFormatSeparator(options.arrayFormatSeparator);
const shouldFilter = key => (
(options.skipNull && isNullOrUndefined(object[key])) ||
(options.skipEmptyString && object[key] === '')
);
const formatter = encoderForArrayFormat(options);
const objectCopy = {};
for (const key of Object.keys(object)) {
if (!shouldFilter(key)) {
objectCopy[key] = object[key];
}
}
const keys = Object.keys(objectCopy);
if (options.sort !== false) {
keys.sort(options.sort);
}
return keys.map(key => {
const value = object[key];
if (value === undefined) {
return '';
}
if (value === null) {
return encode(key, options);
}
if (Array.isArray(value)) {
if (value.length === 0 && options.arrayFormat === 'bracket-separator') {
return encode(key, options) + '[]';
}
return value
.reduce(formatter(key), [])
.join('&');
}
return encode(key, options) + '=' + encode(value, options);
}).filter(x => x.length > 0).join('&');
};
exports.parseUrl = (url, options) => {
options = Object.assign({
decode: true
}, options);
const [url_, hash] = splitOnFirst$1(url, '#');
return Object.assign(
{
url: url_.split('?')[0] || '',
query: parse(extract(url), options)
},
options && options.parseFragmentIdentifier && hash ? {fragmentIdentifier: decode(hash, options)} : {}
);
};
exports.stringifyUrl = (object, options) => {
options = Object.assign({
encode: true,
strict: true,
[encodeFragmentIdentifier]: true
}, options);
const url = removeHash(object.url).split('?')[0] || '';
const queryFromUrl = exports.extract(object.url);
const parsedQueryFromUrl = exports.parse(queryFromUrl, {sort: false});
const query = Object.assign(parsedQueryFromUrl, object.query);
let queryString = exports.stringify(query, options);
if (queryString) {
queryString = `?${queryString}`;
}
let hash = getHash(object.url);
if (object.fragmentIdentifier) {
hash = `#${options[encodeFragmentIdentifier] ? encode(object.fragmentIdentifier, options) : object.fragmentIdentifier}`;
}
return `${url}${queryString}${hash}`;
};
exports.pick = (input, filter, options) => {
options = Object.assign({
parseFragmentIdentifier: true,
[encodeFragmentIdentifier]: false
}, options);
const {url, query, fragmentIdentifier} = exports.parseUrl(input, options);
return exports.stringifyUrl({
url,
query: filterObject(query, filter),
fragmentIdentifier
}, options);
};
exports.exclude = (input, filter, options) => {
const exclusionFilter = Array.isArray(filter) ? key => !filter.includes(key) : (key, value) => !filter(key, value);
return exports.pick(input, exclusionFilter, options);
};
} (queryString));
const formatToList$1 = items => Object.entries(items).map(([key, value]) => `- ${key} (${value})`).join('\n');
function validatePathConfig$1(config, root = true) {
const validation = {
path: 'string',
initialRouteName: 'string',
screens: 'object',
...(root ? null : {
alias: 'array',
exact: 'boolean',
stringify: 'object',
parse: 'object'
})
};
if (typeof config !== 'object' || config === null) {
throw new Error(`Expected the configuration to be an object, but got ${JSON.stringify(config)}.`);
}
const validationErrors = Object.fromEntries(Object.keys(config).map(key => {
if (key in validation) {
const type = validation[key];
// @ts-expect-error: we know the key exists
const value = config[key];
if (value !== undefined) {
if (type === 'array') {
if (!Array.isArray(value)) {
return [key, `expected 'Array', got '${typeof value}'`];
}
} else if (typeof value !== type) {
return [key, `expected '${type}', got '${typeof value}'`];
}
}
} else {
return [key, 'extraneous'];
}
return null;
}).filter(Boolean));
if (Object.keys(validationErrors).length) {
throw new Error(`Found invalid properties in the configuration:\n${formatToList$1(validationErrors)}\n\nYou can only specify the following properties:\n${formatToList$1(validation)}\n\nIf you want to specify configuration for screens, you need to specify them under a 'screens' property.\n\nSee https://reactnavigation.org/docs/configuring-links for more details on how to specify a linking configuration.`);
}
if (root && 'path' in config && typeof config.path === 'string' && config.path.includes(':')) {
throw new Error(`Found invalid path '${config.path}'. The 'path' in the top-level configuration cannot contain patterns for params.`);
}
if ('screens' in config && config.screens) {
Object.entries(config.screens).forEach(([_, value]) => {
if (typeof value !== 'string') {
validatePathConfig$1(value, false);
}
});
}
}
var escapeStringRegexp = string => {
if (typeof string !== 'string') {
throw new TypeError('Expected a string');
}
// Escape characters with special meaning either inside or outside character sets.
// Use a simple backslash escape when it’s always valid, and a \unnnn escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.
return string
.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
.replace(/-/g, '\\x2d');
};
var escape = /*@__PURE__*/getDefaultExportFromCjs(escapeStringRegexp);
/**
* Context which holds the route prop for a screen.
*/
const NavigationRouteContext = /*#__PURE__*/React__namespace.createContext(undefined);
/**
* Hook to access the route prop of the parent screen anywhere.
*
* @returns Route prop of the parent screen.
*/
function useRoute() {
const route = React__namespace.useContext(NavigationRouteContext);
if (route === undefined) {
throw new Error("Couldn't find a route object. Is your component inside a screen in a navigator?");
}
return route;
}
const MemoizedScreen = /*#__PURE__*/React__namespace.memo(({
component
}) => {
const route = useRoute();
const children = /*#__PURE__*/React__namespace.createElement(component, {
route
});
return children;
});
MemoizedScreen.displayName = 'Memo(Screen)';
var useSyncExternalStoreWithSelector_production = {};
/**
* @license React
* use-sync-external-store-with-selector.production.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredUseSyncExternalStoreWithSelector_production;
function requireUseSyncExternalStoreWithSelector_production () {
if (hasRequiredUseSyncExternalStoreWithSelector_production) return useSyncExternalStoreWithSelector_production;
hasRequiredUseSyncExternalStoreWithSelector_production = 1;
var React$1 = React;
function is(x, y) {
return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y);
}
var objectIs = "function" === typeof Object.is ? Object.is : is,
useSyncExternalStore = React$1.useSyncExternalStore,
useRef = React$1.useRef,
useEffect = React$1.useEffect,
useMemo = React$1.useMemo,
useDebugValue = React$1.useDebugValue;
useSyncExternalStoreWithSelector_production.useSyncExternalStoreWithSelector = function (
subscribe,
getSnapshot,
getServerSnapshot,
selector,
isEqual
) {
var instRef = useRef(null);
if (null === instRef.current) {
var inst = { hasValue: false, value: null };
instRef.current = inst;
} else inst = instRef.current;
instRef = useMemo(
function () {
function memoizedSelector(nextSnapshot) {
if (!hasMemo) {
hasMemo = true;
memoizedSnapshot = nextSnapshot;
nextSnapshot = selector(nextSnapshot);
if (void 0 !== isEqual && inst.hasValue) {
var currentSelection = inst.value;
if (isEqual(currentSelection, nextSnapshot))
return (memoizedSelection = currentSelection);
}
return (memoizedSelection = nextSnapshot);
}
currentSelection = memoizedSelection;
if (objectIs(memoizedSnapshot, nextSnapshot)) return currentSelection;
var nextSelection = selector(nextSnapshot);
if (void 0 !== isEqual && isEqual(currentSelection, nextSelection))
return (memoizedSnapshot = nextSnapshot), currentSelection;
memoizedSnapshot = nextSnapshot;
return (memoizedSelection = nextSelection);
}
var hasMemo = false,
memoizedSnapshot,
memoizedSelection,
maybeGetServerSnapshot =
void 0 === getServerSnapshot ? null : getServerSnapshot;
return [
function () {
return memoizedSelector(getSnapshot());
},
null === maybeGetServerSnapshot
? void 0
: function () {
return memoizedSelector(maybeGetServerSnapshot());
}
];
},
[getSnapshot, getServerSnapshot, selector, isEqual]
);
var value = useSyncExternalStore(subscribe, instRef[0], instRef[1]);
useEffect(
function () {
inst.hasValue = true;
inst.value = value;
},
[value]
);
useDebugValue(value);
return value;
};
return useSyncExternalStoreWithSelector_production;
}
var useSyncExternalStoreWithSelector_development = {};
/**
* @license React
* use-sync-external-store-with-selector.development.js
*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
var hasRequiredUseSyncExternalStoreWithSelector_development;
function requireUseSyncExternalStoreWithSelector_development () {
if (hasRequiredUseSyncExternalStoreWithSelector_development) return useSyncExternalStoreWithSelector_development;
hasRequiredUseSyncExternalStoreWithSelector_development = 1;
"production" !== process.env.NODE_ENV &&
(function () {
function is(x, y) {
return (x === y && (0 !== x || 1 / x === 1 / y)) || (x !== x && y !== y);
}
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart &&
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(Error());
var React$1 = React,
objectIs = "function" === typeof Object.is ? Object.is : is,
useSyncExternalStore = React$1.useSyncExternalStore,
useRef = React$1.useRef,
useEffect = React$1.useEffect,
useMemo = React$1.useMemo,
useDebugValue = React$1.useDebugValue;
useSyncExternalStoreWithSelector_development.useSyncExternalStoreWithSelector = function (
subscribe,
getSnapshot,
getServerSnapshot,
selector,
isEqual
) {
var instRef = useRef(null);
if (null === instRef.current) {
var inst = { hasValue: false, value: null };
instRef.current = inst;
} else inst = instRef.current;
instRef = useMemo(
function () {
function memoizedSelector(nextSnapshot) {
if (!hasMemo) {
hasMemo = true;
memoizedSnapshot = nextSnapshot;
nextSnapshot = selector(nextSnapshot);
if (void 0 !== isEqual && inst.hasValue) {
var currentSelection = inst.value;
if (isEqual(currentSelection, nextSnapshot))
return (memoizedSelection = currentSelection);
}
return (memoizedSelection = nextSnapshot);
}
currentSelection = memoizedSelection;
if (objectIs(memoizedSnapshot, nextSnapshot))
return currentSelection;
var nextSelection = selector(nextSnapshot);
if (void 0 !== isEqual && isEqual(currentSelection, nextSelection))
return (memoizedSnapshot = nextSnapshot), currentSelection;
memoizedSnapshot = nextSnapshot;
return (memoizedSelection = nextSelection);
}
var hasMemo = false,
memoizedSnapshot,
memoizedSelection,
maybeGetServerSnapshot =
void 0 === getServerSnapshot ? null : getServerSnapshot;
return [
function () {
return memoizedSelector(getSnapshot());
},
null === maybeGetServerSnapshot
? void 0
: function () {
return memoizedSelector(maybeGetServerSnapshot());
}
];
},
[getSnapshot, getServerSnapshot, selector, isEqual]
);
var value = useSyncExternalStore(subscribe, instRef[0], instRef[1]);
useEffect(
function () {
inst.hasValue = true;
inst.value = value;
},
[value]
);
useDebugValue(value);
return value;
};
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop(Error());
})();
return useSyncExternalStoreWithSelector_development;
}
if (process.env.NODE_ENV === 'production') {
requireUseSyncExternalStoreWithSelector_production();
} else {
requireUseSyncExternalStoreWithSelector_development();
}
const _excluded$2 = ["preserveGroups", "preserveDynamicRoutes"];
const DEFAULT_SCREENS = {};
const getActiveRoute = state => {
const route = typeof state.index === 'number' ? state.routes[state.index] : state.routes[state.routes.length - 1];
if (route.state) {
return getActiveRoute(route.state);
}
if (route && isInvalidParams(route.params)) {
return getActiveRoute(createFakeState(route.params));
}
return route;
};
function createFakeState(params) {
return {
stale: false,
type: 'UNKNOWN',
key: 'UNKNOWN',
index: 0,
routeNames: [],
routes: [{
key: 'UNKNOWN',
name: params.screen,
params: params.params,
path: params.path
}]
};
}
function segmentMatchesConvention(segment) {
return segment === 'index' || matchDynamicName(segment) != null || matchGroupName(segment) != null || matchDeepDynamicRouteName(segment) != null;
}
function encodeURIComponentPreservingBrackets(str) {
return encodeURIComponent(str).replace(/%5B/g, '[').replace(/%5D/g, ']');
}
/**
* Utility to serialize a navigation state object to a path string.
*
* @example
* ```js
* getPathFromState(
* {
* routes: [
* {
* name: 'Chat',
* params: { author: 'Jane', id: 42 },
* },
* ],
* },
* {
* screens: {
* Chat: {
* path: 'chat/:author/:id',
* stringify: { author: author => author.toLowerCase() }
* }
* }
* }
* )
* ```
*
* @param state Navigation state to serialize.
* @param options Extra options to fine-tune how to serialize the path.
* @returns Path representing the state, e.g. /foo/bar?count=42.
*/
function getPathFromState(state, _options) {
return getPathDataFromState(state, _options).path;
}
function getPathDataFromState(state, _options = {
screens: DEFAULT_SCREENS
}) {
if (state == null) {
throw Error("Got 'undefined' for the navigation state. You must pass a valid state object.");
}
const {
preserveGroups,
preserveDynamicRoutes
} = _options,
options = _objectWithoutPropertiesLoose(_options, _excluded$2);
validatePathConfig$1(options);
// Expo Router disallows usage without a linking config.
if (Object.is(options.screens, DEFAULT_SCREENS)) {
throw Error("You must pass a 'screens' object to 'getPathFromState' to generate a path.");
}
return getPathFromResolvedState(JSON.parse(JSON.stringify(state)),
// Create a normalized configs object which will be easier to use
createNormalizedConfigs$1(options.screens), {
preserveGroups,
preserveDynamicRoutes
});
}
function processParamsWithUserSettings(configItem, params) {
const stringify = configItem == null ? void 0 : configItem.stringify;
return Object.fromEntries(Object.entries(params).map(([key, value]) => [key,
// TODO: Strip nullish values here.
stringify != null && stringify[key] ? stringify[key](value) :
// Preserve rest params
Array.isArray(value) ? value : String(value)]));
}
function deepEqual(a, b) {
if (a === b) {
return true;
}
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (!deepEqual(a[i], b[i])) {
return false;
}
}
return true;
}
if (typeof a === 'object' && typeof b === 'object') {
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) {
return false;
}
for (const key of keysA) {
if (!deepEqual(a[key], b[key])) {
return false;
}
}
return true;
}
return false;
}
function walkConfigItems(route, focusedRoute, configs, {
preserveDynamicRoutes
}) {
// NOTE(EvanBacon): Fill in current route using state that was passed as params.
if (!route.state && isInvalidParams(route.params)) {
route.state = createFakeState(route.params);
}
let pattern = null;
let focusedParams;
const collectedParams = {};
while (route.name in configs) {
var _route$state$index;
const configItem = configs[route.name];
const inputPattern = configItem.pattern;
if (inputPattern == null) {
// This should never happen in Expo Router.
throw new Error('Unexpected: No pattern found for route ' + route.name);
}
pattern = inputPattern;
if (route.params) {
const params = processParamsWithUserSettings(configItem, route.params);
// TODO: Does this need to be a null check?
if (pattern) {
Object.assign(collectedParams, params);
}
if (deepEqual(focusedRoute, route)) {
if (preserveDynamicRoutes) {
focusedParams = params;
} else {
// If this is the focused route, keep the params for later use
// We save it here since it's been stringified already
focusedParams = getParamsWithConventionsCollapsed({
params,
pattern,
routeName: route.name
});
}
}
}
if (!route.state && isInvalidParams(route.params)) {
route.state = createFakeState(route.params);
}
// If there is no `screens` property or no nested state, we return pattern
if (!configItem.screens || route.state === undefined) {
var _configItem$screens$c;
if (configItem.initialRouteName && configItem.screens && configItem.initialRouteName in configItem.screens && (_configItem$screens$c = configItem.screens[configItem.initialRouteName]) != null && _configItem$screens$c.pattern) {
const initialRouteConfig = configItem.screens[configItem.initialRouteName];
// NOTE(EvanBacon): Big hack to support initial route changes in tab bars.
pattern = initialRouteConfig.pattern;
if (focusedParams) {
if (!preserveDynamicRoutes) {
// If this is the focused route, keep the params for later use
// We save it here since it's been stringified already
focusedParams = getParamsWithConventionsCollapsed({
params: focusedParams,
pattern,
routeName: route.name
});
}
}
}
break;
}
const index = (_route$state$index = route.state.index) != null ? _route$state$index : route.state.routes.length - 1;
const nextRoute = route.state.routes[index];
const nestedScreens = configItem.screens;
// if there is config for next route name, we go deeper
if (nestedScreens && nextRoute.name in nestedScreens) {
route = nextRoute;
configs = nestedScreens;
} else {
// If not, there is no sense in going deeper in config
break;
}
}
if (pattern == null) {
throw new Error(`No pattern found for route "${route.name}". Options are: ${Object.keys(configs).join(', ')}.`);
}
if (pattern && !focusedParams && focusedRoute.params) {
if (preserveDynamicRoutes) {
focusedParams = focusedRoute.params;
} else {
// If this is the focused route, keep the params for later use
// We save it here since it's been stringified already
focusedParams = getParamsWithConventionsCollapsed({
params: focusedRoute.params,
pattern,
routeName: route.name
});
}
Object.assign(focusedParams, collectedParams);
}
return {
pattern,
nextRoute: route,
focusedParams,
params: collectedParams
};
}
function getPathFromResolvedState(state, configs, {
preserveGroups,
preserveDynamicRoutes
}) {
let path = '';
let current = state;
const allParams = {};
while (current) {
var _current$index, _configs$nextRoute$na, _nextRoute$state$rout, _nextRoute$state$inde, _nextRoute$state;
path += '/';
const route = _extends({}, current.routes[(_current$index = current.index) != null ? _current$index : 0]);
// NOTE(EvanBacon): Fill in current route using state that was passed as params.
// if (isInvalidParams(route.params)) {
if (!route.state && isInvalidParams(route.params)) {
route.state = createFakeState(route.params);
}
const {
pattern,
params,
nextRoute,
focusedParams
} = walkConfigItems(route, getActiveRoute(current), _extends({}, configs), {
preserveDynamicRoutes
});
Object.assign(allParams, params);
path += getPathWithConventionsCollapsed({
pattern,
routePath: nextRoute.path,
params: allParams,
initialRouteName: (_configs$nextRoute$na = configs[nextRoute.name]) == null ? void 0 : _configs$nextRoute$na.initialRouteName,
preserveGroups,
preserveDynamicRoutes
});
if (nextRoute.state &&
// NOTE(EvanBacon): The upstream implementation allows for sending in synthetic states (states that weren't generated by `getStateFromPath`)
// and any invalid routes will simply be ignored.
// Because of this, we need to check if the next route is valid before continuing, otherwise our more strict
// implementation will throw an error.
configs[(_nextRoute$state$rout = nextRoute.state.routes) == null || (_nextRoute$state$rout = _nextRoute$state$rout[(_nextRoute$state$inde = (_nextRoute$state = nextRoute.state) == null ? void 0 : _nextRoute$state.index) != null ? _nextRoute$state$inde : 0]) == null ? void 0 : _nextRoute$state$rout.name]) {
// Continue looping with the next state if available.
current = nextRoute.state;
} else {
// Finished crawling state.
// Check for query params before exiting.
if (focusedParams) {
for (const param in focusedParams) {
if (focusedParams[param] === 'undefined') {
delete focusedParams[param];
}
}
const query = queryString.stringify(focusedParams, {
sort: false
});
if (query) {
path += `?${query}`;
}
}
break;
}
}
return {
path: basicSanitizePath(path),
params: decodeParams(allParams)
};
}
function decodeParams(params) {
const parsed = {};
for (const [key, value] of Object.entries(params)) {
parsed[key] = decodeURIComponent(value);
}
return parsed;
}
function getPathWithConventionsCollapsed({
pattern,
routePath,
params,
preserveGroups,
preserveDynamicRoutes,
initialRouteName
}) {
const segments = pattern.split('/');
return segments.map((p, i) => {
const name = getParamName(p);
// We don't know what to show for wildcard patterns
// Showing the route name seems ok, though whatever we show here will be incorrect
// Since the page doesn't actually exist
if (p.startsWith('*')) {
if (preserveDynamicRoutes) {
return `[...${name}]`;
}
if (params[name]) {
return params[name].join('/');
}
if (i === 0) {
// This can occur when a wildcard matches all routes and the given path was `/`.
return routePath;
}
// remove existing segments from route.path and return it
// this is used for nested wildcard routes. Without this, the path would add
// all nested segments to the beginning of the wildcard route.
return routePath == null ? void 0 : routePath.split('/').slice(i + 1).join('/');
}
// If the path has a pattern for a param, put the param in the path
if (p.startsWith(':')) {
if (preserveDynamicRoutes) {
return `[${name}]`;
}
// Optional params without value assigned in route.params should be ignored
return params[name];
}
if (!preserveGroups && matchGroupName(p) != null) {
// When the last part is a group it could be a shared URL
// if the route has an initialRouteName defined, then we should
// use that as the component path as we can assume it will be shown.
if (segments.length - 1 === i) {
if (initialRouteName) {
// Return an empty string if the init route is ambiguous.
if (segmentMatchesConvention(initialRouteName)) {
return '';
}
return encodeURIComponentPreservingBrackets(initialRouteName);
}
}
return '';
}
// Preserve dynamic syntax for rehydration
return encodeURIComponentPreservingBrackets(p);
}).map(v => v != null ? v : '').join('/');
}
/** Given a set of query params and a pattern with possible conventions, collapse the conventions and return the remaining params. */
function getParamsWithConventionsCollapsed({
pattern,
routeName,
params
}) {
const processedParams = _extends({}, params);
// Remove the params present in the pattern since we'll only use the rest for query string
const segments = pattern.split('/');
// Dynamic Routes
segments.filter(segment => segment.startsWith(':')).forEach(segment => {
const name = getParamName(segment);
delete processedParams[name];
});
// Deep Dynamic Routes
if (segments.some(segment => segment.startsWith('*'))) {
var _matchDeepDynamicRout;
// NOTE(EvanBacon): Drop the param name matching the wildcard route name -- this is specific to Expo Router.
const name = (_matchDeepDynamicRout = matchDeepDynamicRouteName(routeName)) != null ? _matchDeepDynamicRout : routeName;
delete processedParams[name];
}
return processedParams;
}
// Remove multiple as well as trailing slashes
function basicSanitizePath(path) {
// Remove duplicate slashes like `foo//bar` -> `foo/bar`
const simplifiedPath = path.replace(/\/+/g, '/');
if (simplifiedPath.length <= 1) {
return simplifiedPath;
}
// Remove trailing slash like `foo/bar/` -> `foo/bar`
return simplifiedPath.replace(/\/$/, '');
}
// TODO: Make StackRouter not do this...
// Detect if the params came from StackRouter using `params` to pass around internal state.
function isInvalidParams(params) {
if (!params) {
return false;
}
if ('params' in params && typeof params.params === 'object' && !!params.params) {
return true;
}
return 'initial' in params && typeof params.initial === 'boolean' &&
// "path" in params