UNPKG

9.15 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.default = getPathFromState;
7
8var queryString = _interopRequireWildcard(require("query-string"));
9
10var _fromEntries = _interopRequireDefault(require("./fromEntries"));
11
12var _validatePathConfig = _interopRequireDefault(require("./validatePathConfig"));
13
14function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
15
16function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
17
18function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
19
20const getActiveRoute = state => {
21 const route = typeof state.index === 'number' ? state.routes[state.index] : state.routes[state.routes.length - 1];
22
23 if (route.state) {
24 return getActiveRoute(route.state);
25 }
26
27 return route;
28};
29/**
30 * Utility to serialize a navigation state object to a path string.
31 *
32 * @example
33 * ```js
34 * getPathFromState(
35 * {
36 * routes: [
37 * {
38 * name: 'Chat',
39 * params: { author: 'Jane', id: 42 },
40 * },
41 * ],
42 * },
43 * {
44 * screens: {
45 * Chat: {
46 * path: 'chat/:author/:id',
47 * stringify: { author: author => author.toLowerCase() }
48 * }
49 * }
50 * }
51 * )
52 * ```
53 *
54 * @param state Navigation state to serialize.
55 * @param options Extra options to fine-tune how to serialize the path.
56 * @returns Path representing the state, e.g. /foo/bar?count=42.
57 */
58
59
60function getPathFromState(state, options) {
61 if (state == null) {
62 throw Error("Got 'undefined' for the navigation state. You must pass a valid state object.");
63 }
64
65 if (options) {
66 (0, _validatePathConfig.default)(options);
67 } // Create a normalized configs object which will be easier to use
68
69
70 const configs = options !== null && options !== void 0 && options.screens ? createNormalizedConfigs(options === null || options === void 0 ? void 0 : options.screens) : {};
71 let path = '/';
72 let current = state;
73 const allParams = {};
74
75 while (current) {
76 let index = typeof current.index === 'number' ? current.index : 0;
77 let route = current.routes[index];
78 let pattern;
79 let focusedParams;
80 let focusedRoute = getActiveRoute(state);
81 let currentOptions = configs; // Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
82
83 let nestedRouteNames = [];
84 let hasNext = true;
85
86 while (route.name in currentOptions && hasNext) {
87 pattern = currentOptions[route.name].pattern;
88 nestedRouteNames.push(route.name);
89
90 if (route.params) {
91 var _currentOptions$route;
92
93 const stringify = (_currentOptions$route = currentOptions[route.name]) === null || _currentOptions$route === void 0 ? void 0 : _currentOptions$route.stringify;
94 const currentParams = (0, _fromEntries.default)(Object.entries(route.params).map(_ref => {
95 let [key, value] = _ref;
96 return [key, stringify !== null && stringify !== void 0 && stringify[key] ? stringify[key](value) : String(value)];
97 }));
98
99 if (pattern) {
100 Object.assign(allParams, currentParams);
101 }
102
103 if (focusedRoute === route) {
104 var _pattern;
105
106 // If this is the focused route, keep the params for later use
107 // We save it here since it's been stringified already
108 focusedParams = { ...currentParams
109 };
110 (_pattern = pattern) === null || _pattern === void 0 ? void 0 : _pattern.split('/').filter(p => p.startsWith(':')) // eslint-disable-next-line no-loop-func
111 .forEach(p => {
112 const name = getParamName(p); // Remove the params present in the pattern since we'll only use the rest for query string
113
114 if (focusedParams) {
115 // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
116 delete focusedParams[name];
117 }
118 });
119 }
120 } // If there is no `screens` property or no nested state, we return pattern
121
122
123 if (!currentOptions[route.name].screens || route.state === undefined) {
124 hasNext = false;
125 } else {
126 index = typeof route.state.index === 'number' ? route.state.index : route.state.routes.length - 1;
127 const nextRoute = route.state.routes[index];
128 const nestedConfig = currentOptions[route.name].screens; // if there is config for next route name, we go deeper
129
130 if (nestedConfig && nextRoute.name in nestedConfig) {
131 route = nextRoute;
132 currentOptions = nestedConfig;
133 } else {
134 // If not, there is no sense in going deeper in config
135 hasNext = false;
136 }
137 }
138 }
139
140 if (pattern === undefined) {
141 pattern = nestedRouteNames.join('/');
142 }
143
144 if (currentOptions[route.name] !== undefined) {
145 path += pattern.split('/').map(p => {
146 const name = getParamName(p); // We don't know what to show for wildcard patterns
147 // Showing the route name seems ok, though whatever we show here will be incorrect
148 // Since the page doesn't actually exist
149
150 if (p === '*') {
151 return route.name;
152 } // If the path has a pattern for a param, put the param in the path
153
154
155 if (p.startsWith(':')) {
156 const value = allParams[name];
157
158 if (value === undefined && p.endsWith('?')) {
159 // Optional params without value assigned in route.params should be ignored
160 return '';
161 }
162
163 return encodeURIComponent(value);
164 }
165
166 return encodeURIComponent(p);
167 }).join('/');
168 } else {
169 path += encodeURIComponent(route.name);
170 }
171
172 if (!focusedParams) {
173 focusedParams = focusedRoute.params;
174 }
175
176 if (route.state) {
177 path += '/';
178 } else if (focusedParams) {
179 for (let param in focusedParams) {
180 if (focusedParams[param] === 'undefined') {
181 // eslint-disable-next-line @typescript-eslint/no-dynamic-delete
182 delete focusedParams[param];
183 }
184 }
185
186 const query = queryString.stringify(focusedParams, {
187 sort: false
188 });
189
190 if (query) {
191 path += `?${query}`;
192 }
193 }
194
195 current = route.state;
196 } // Remove multiple as well as trailing slashes
197
198
199 path = path.replace(/\/+/g, '/');
200 path = path.length > 1 ? path.replace(/\/$/, '') : path;
201 return path;
202}
203
204const getParamName = pattern => pattern.replace(/^:/, '').replace(/\?$/, '');
205
206const joinPaths = function () {
207 for (var _len = arguments.length, paths = new Array(_len), _key = 0; _key < _len; _key++) {
208 paths[_key] = arguments[_key];
209 }
210
211 return [].concat(...paths.map(p => p.split('/'))).filter(Boolean).join('/');
212};
213
214const createConfigItem = (config, parentPattern) => {
215 var _pattern2;
216
217 if (typeof config === 'string') {
218 // If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
219 const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
220 return {
221 pattern
222 };
223 } // If an object is specified as the value (e.g. Foo: { ... }),
224 // It can have `path` property and `screens` prop which has nested configs
225
226
227 let pattern;
228
229 if (config.exact && config.path === undefined) {
230 throw new Error("A 'path' needs to be specified when specifying 'exact: true'. If you don't want this screen in the URL, specify it as empty string, e.g. `path: ''`.");
231 }
232
233 pattern = config.exact !== true ? joinPaths(parentPattern || '', config.path || '') : config.path || '';
234 const screens = config.screens ? createNormalizedConfigs(config.screens, pattern) : undefined;
235 return {
236 // Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
237 pattern: (_pattern2 = pattern) === null || _pattern2 === void 0 ? void 0 : _pattern2.split('/').filter(Boolean).join('/'),
238 stringify: config.stringify,
239 screens
240 };
241};
242
243const createNormalizedConfigs = (options, pattern) => (0, _fromEntries.default)(Object.entries(options).map(_ref2 => {
244 let [name, c] = _ref2;
245 const result = createConfigItem(c, pattern);
246 return [name, result];
247}));
248//# sourceMappingURL=getPathFromState.js.map
\No newline at end of file