@remix-run/router 1.6.2 → 1.6.3-pre.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/router.cjs.js +754 -854
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +1 -0
- package/dist/router.js +440 -946
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +754 -854
- package/dist/router.umd.js.map +1 -1
- package/dist/router.umd.min.js +2 -2
- package/dist/router.umd.min.js.map +1 -1
- package/history.ts +7 -0
- package/package.json +1 -1
- package/router.ts +49 -31
package/dist/router.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.6.
|
|
2
|
+
* @remix-run/router v1.6.3-pre.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -16,14 +16,12 @@ function _extends() {
|
|
|
16
16
|
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
17
17
|
for (var i = 1; i < arguments.length; i++) {
|
|
18
18
|
var source = arguments[i];
|
|
19
|
-
|
|
20
19
|
for (var key in source) {
|
|
21
20
|
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
22
21
|
target[key] = source[key];
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
}
|
|
26
|
-
|
|
27
25
|
return target;
|
|
28
26
|
};
|
|
29
27
|
return _extends.apply(this, arguments);
|
|
@@ -37,17 +35,41 @@ function _extends() {
|
|
|
37
35
|
* Actions represent the type of change to a location value.
|
|
38
36
|
*/
|
|
39
37
|
exports.Action = void 0;
|
|
38
|
+
|
|
40
39
|
/**
|
|
41
40
|
* The pathname, search, and hash values of a URL.
|
|
42
41
|
*/
|
|
43
|
-
|
|
44
42
|
(function (Action) {
|
|
45
43
|
Action["Pop"] = "POP";
|
|
46
44
|
Action["Push"] = "PUSH";
|
|
47
45
|
Action["Replace"] = "REPLACE";
|
|
48
46
|
})(exports.Action || (exports.Action = {}));
|
|
47
|
+
/**
|
|
48
|
+
* An entry in a history stack. A location contains information about the
|
|
49
|
+
* URL path, as well as possibly some arbitrary state and a key.
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* A change to the current location.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* A function that receives notifications about location changes.
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* Describes a location that is the destination of some navigation, either via
|
|
59
|
+
* `history.push` or `history.replace`. May be either a URL or the pieces of a
|
|
60
|
+
* URL path.
|
|
61
|
+
*/
|
|
62
|
+
/**
|
|
63
|
+
* A history is an interface to the navigation stack. The history serves as the
|
|
64
|
+
* source of truth for the current location, as well as provides a set of
|
|
65
|
+
* methods that may be used to change it.
|
|
66
|
+
*
|
|
67
|
+
* It is similar to the DOM's `window.history` object, but with a smaller, more
|
|
68
|
+
* focused API.
|
|
69
|
+
*/
|
|
70
|
+
const PopStateEventType = "popstate";
|
|
71
|
+
//#endregion
|
|
49
72
|
|
|
50
|
-
const PopStateEventType = "popstate"; //#endregion
|
|
51
73
|
////////////////////////////////////////////////////////////////////////////////
|
|
52
74
|
//#region Memory History
|
|
53
75
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -56,7 +78,11 @@ const PopStateEventType = "popstate"; //#endregion
|
|
|
56
78
|
* A user-supplied object that describes a location. Used when providing
|
|
57
79
|
* entries to `createMemoryHistory` via its `initialEntries` option.
|
|
58
80
|
*/
|
|
59
|
-
|
|
81
|
+
/**
|
|
82
|
+
* A memory history stores locations in memory. This is useful in stateful
|
|
83
|
+
* environments where there is no web browser, such as node tests or React
|
|
84
|
+
* Native.
|
|
85
|
+
*/
|
|
60
86
|
/**
|
|
61
87
|
* Memory history stores the current location in memory. It is designed for use
|
|
62
88
|
* in stateful non-browser environments like tests and React Native.
|
|
@@ -65,60 +91,47 @@ function createMemoryHistory(options) {
|
|
|
65
91
|
if (options === void 0) {
|
|
66
92
|
options = {};
|
|
67
93
|
}
|
|
68
|
-
|
|
69
94
|
let {
|
|
70
95
|
initialEntries = ["/"],
|
|
71
96
|
initialIndex,
|
|
72
97
|
v5Compat = false
|
|
73
98
|
} = options;
|
|
74
99
|
let entries; // Declare so we can access from createMemoryLocation
|
|
75
|
-
|
|
76
100
|
entries = initialEntries.map((entry, index) => createMemoryLocation(entry, typeof entry === "string" ? null : entry.state, index === 0 ? "default" : undefined));
|
|
77
101
|
let index = clampIndex(initialIndex == null ? entries.length - 1 : initialIndex);
|
|
78
102
|
let action = exports.Action.Pop;
|
|
79
103
|
let listener = null;
|
|
80
|
-
|
|
81
104
|
function clampIndex(n) {
|
|
82
105
|
return Math.min(Math.max(n, 0), entries.length - 1);
|
|
83
106
|
}
|
|
84
|
-
|
|
85
107
|
function getCurrentLocation() {
|
|
86
108
|
return entries[index];
|
|
87
109
|
}
|
|
88
|
-
|
|
89
110
|
function createMemoryLocation(to, state, key) {
|
|
90
111
|
if (state === void 0) {
|
|
91
112
|
state = null;
|
|
92
113
|
}
|
|
93
|
-
|
|
94
114
|
let location = createLocation(entries ? getCurrentLocation().pathname : "/", to, state, key);
|
|
95
115
|
warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in memory history: " + JSON.stringify(to));
|
|
96
116
|
return location;
|
|
97
117
|
}
|
|
98
|
-
|
|
99
118
|
function createHref(to) {
|
|
100
119
|
return typeof to === "string" ? to : createPath(to);
|
|
101
120
|
}
|
|
102
|
-
|
|
103
121
|
let history = {
|
|
104
122
|
get index() {
|
|
105
123
|
return index;
|
|
106
124
|
},
|
|
107
|
-
|
|
108
125
|
get action() {
|
|
109
126
|
return action;
|
|
110
127
|
},
|
|
111
|
-
|
|
112
128
|
get location() {
|
|
113
129
|
return getCurrentLocation();
|
|
114
130
|
},
|
|
115
|
-
|
|
116
131
|
createHref,
|
|
117
|
-
|
|
118
132
|
createURL(to) {
|
|
119
133
|
return new URL(createHref(to), "http://localhost");
|
|
120
134
|
},
|
|
121
|
-
|
|
122
135
|
encodeLocation(to) {
|
|
123
136
|
let path = typeof to === "string" ? parsePath(to) : to;
|
|
124
137
|
return {
|
|
@@ -127,13 +140,11 @@ function createMemoryHistory(options) {
|
|
|
127
140
|
hash: path.hash || ""
|
|
128
141
|
};
|
|
129
142
|
},
|
|
130
|
-
|
|
131
143
|
push(to, state) {
|
|
132
144
|
action = exports.Action.Push;
|
|
133
145
|
let nextLocation = createMemoryLocation(to, state);
|
|
134
146
|
index += 1;
|
|
135
147
|
entries.splice(index, entries.length, nextLocation);
|
|
136
|
-
|
|
137
148
|
if (v5Compat && listener) {
|
|
138
149
|
listener({
|
|
139
150
|
action,
|
|
@@ -142,12 +153,10 @@ function createMemoryHistory(options) {
|
|
|
142
153
|
});
|
|
143
154
|
}
|
|
144
155
|
},
|
|
145
|
-
|
|
146
156
|
replace(to, state) {
|
|
147
157
|
action = exports.Action.Replace;
|
|
148
158
|
let nextLocation = createMemoryLocation(to, state);
|
|
149
159
|
entries[index] = nextLocation;
|
|
150
|
-
|
|
151
160
|
if (v5Compat && listener) {
|
|
152
161
|
listener({
|
|
153
162
|
action,
|
|
@@ -156,13 +165,11 @@ function createMemoryHistory(options) {
|
|
|
156
165
|
});
|
|
157
166
|
}
|
|
158
167
|
},
|
|
159
|
-
|
|
160
168
|
go(delta) {
|
|
161
169
|
action = exports.Action.Pop;
|
|
162
170
|
let nextIndex = clampIndex(index + delta);
|
|
163
171
|
let nextLocation = entries[nextIndex];
|
|
164
172
|
index = nextIndex;
|
|
165
|
-
|
|
166
173
|
if (listener) {
|
|
167
174
|
listener({
|
|
168
175
|
action,
|
|
@@ -171,17 +178,17 @@ function createMemoryHistory(options) {
|
|
|
171
178
|
});
|
|
172
179
|
}
|
|
173
180
|
},
|
|
174
|
-
|
|
175
181
|
listen(fn) {
|
|
176
182
|
listener = fn;
|
|
177
183
|
return () => {
|
|
178
184
|
listener = null;
|
|
179
185
|
};
|
|
180
186
|
}
|
|
181
|
-
|
|
182
187
|
};
|
|
183
188
|
return history;
|
|
184
|
-
}
|
|
189
|
+
}
|
|
190
|
+
//#endregion
|
|
191
|
+
|
|
185
192
|
////////////////////////////////////////////////////////////////////////////////
|
|
186
193
|
//#region Browser History
|
|
187
194
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -193,7 +200,6 @@ function createMemoryHistory(options) {
|
|
|
193
200
|
*
|
|
194
201
|
* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#browserhistory
|
|
195
202
|
*/
|
|
196
|
-
|
|
197
203
|
/**
|
|
198
204
|
* Browser history stores the location in regular URLs. This is the standard for
|
|
199
205
|
* most web apps, but it requires some configuration on the server to ensure you
|
|
@@ -205,7 +211,6 @@ function createBrowserHistory(options) {
|
|
|
205
211
|
if (options === void 0) {
|
|
206
212
|
options = {};
|
|
207
213
|
}
|
|
208
|
-
|
|
209
214
|
function createBrowserLocation(window, globalHistory) {
|
|
210
215
|
let {
|
|
211
216
|
pathname,
|
|
@@ -216,16 +221,17 @@ function createBrowserHistory(options) {
|
|
|
216
221
|
pathname,
|
|
217
222
|
search,
|
|
218
223
|
hash
|
|
219
|
-
},
|
|
224
|
+
},
|
|
225
|
+
// state defaults to `null` because `window.history.state` does
|
|
220
226
|
globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
|
|
221
227
|
}
|
|
222
|
-
|
|
223
228
|
function createBrowserHref(window, to) {
|
|
224
229
|
return typeof to === "string" ? to : createPath(to);
|
|
225
230
|
}
|
|
226
|
-
|
|
227
231
|
return getUrlBasedHistory(createBrowserLocation, createBrowserHref, null, options);
|
|
228
|
-
}
|
|
232
|
+
}
|
|
233
|
+
//#endregion
|
|
234
|
+
|
|
229
235
|
////////////////////////////////////////////////////////////////////////////////
|
|
230
236
|
//#region Hash History
|
|
231
237
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -241,7 +247,6 @@ function createBrowserHistory(options) {
|
|
|
241
247
|
*
|
|
242
248
|
* @see https://github.com/remix-run/history/tree/main/docs/api-reference.md#hashhistory
|
|
243
249
|
*/
|
|
244
|
-
|
|
245
250
|
/**
|
|
246
251
|
* Hash history stores the location in window.location.hash. This makes it ideal
|
|
247
252
|
* for situations where you don't want to send the location to the server for
|
|
@@ -254,7 +259,6 @@ function createHashHistory(options) {
|
|
|
254
259
|
if (options === void 0) {
|
|
255
260
|
options = {};
|
|
256
261
|
}
|
|
257
|
-
|
|
258
262
|
function createHashLocation(window, globalHistory) {
|
|
259
263
|
let {
|
|
260
264
|
pathname = "/",
|
|
@@ -265,29 +269,27 @@ function createHashHistory(options) {
|
|
|
265
269
|
pathname,
|
|
266
270
|
search,
|
|
267
271
|
hash
|
|
268
|
-
},
|
|
272
|
+
},
|
|
273
|
+
// state defaults to `null` because `window.history.state` does
|
|
269
274
|
globalHistory.state && globalHistory.state.usr || null, globalHistory.state && globalHistory.state.key || "default");
|
|
270
275
|
}
|
|
271
|
-
|
|
272
276
|
function createHashHref(window, to) {
|
|
273
277
|
let base = window.document.querySelector("base");
|
|
274
278
|
let href = "";
|
|
275
|
-
|
|
276
279
|
if (base && base.getAttribute("href")) {
|
|
277
280
|
let url = window.location.href;
|
|
278
281
|
let hashIndex = url.indexOf("#");
|
|
279
282
|
href = hashIndex === -1 ? url : url.slice(0, hashIndex);
|
|
280
283
|
}
|
|
281
|
-
|
|
282
284
|
return href + "#" + (typeof to === "string" ? to : createPath(to));
|
|
283
285
|
}
|
|
284
|
-
|
|
285
286
|
function validateHashLocation(location, to) {
|
|
286
287
|
warning(location.pathname.charAt(0) === "/", "relative pathnames are not supported in hash history.push(" + JSON.stringify(to) + ")");
|
|
287
288
|
}
|
|
288
|
-
|
|
289
289
|
return getUrlBasedHistory(createHashLocation, createHashHref, validateHashLocation, options);
|
|
290
|
-
}
|
|
290
|
+
}
|
|
291
|
+
//#endregion
|
|
292
|
+
|
|
291
293
|
////////////////////////////////////////////////////////////////////////////////
|
|
292
294
|
//#region UTILS
|
|
293
295
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -295,7 +297,6 @@ function createHashHistory(options) {
|
|
|
295
297
|
/**
|
|
296
298
|
* @private
|
|
297
299
|
*/
|
|
298
|
-
|
|
299
300
|
function invariant(value, message) {
|
|
300
301
|
if (value === false || value === null || typeof value === "undefined") {
|
|
301
302
|
throw new Error(message);
|
|
@@ -305,26 +306,24 @@ function warning(cond, message) {
|
|
|
305
306
|
if (!cond) {
|
|
306
307
|
// eslint-disable-next-line no-console
|
|
307
308
|
if (typeof console !== "undefined") console.warn(message);
|
|
308
|
-
|
|
309
309
|
try {
|
|
310
310
|
// Welcome to debugging history!
|
|
311
311
|
//
|
|
312
312
|
// This error is thrown as a convenience so you can more easily
|
|
313
313
|
// find the source for a warning that appears in the console by
|
|
314
314
|
// enabling "pause on exceptions" in your JavaScript debugger.
|
|
315
|
-
throw new Error(message);
|
|
315
|
+
throw new Error(message);
|
|
316
|
+
// eslint-disable-next-line no-empty
|
|
316
317
|
} catch (e) {}
|
|
317
318
|
}
|
|
318
319
|
}
|
|
319
|
-
|
|
320
320
|
function createKey() {
|
|
321
321
|
return Math.random().toString(36).substr(2, 8);
|
|
322
322
|
}
|
|
323
|
+
|
|
323
324
|
/**
|
|
324
325
|
* For browser-based histories, we combine the state and key into an object
|
|
325
326
|
*/
|
|
326
|
-
|
|
327
|
-
|
|
328
327
|
function getHistoryState(location, index) {
|
|
329
328
|
return {
|
|
330
329
|
usr: location.state,
|
|
@@ -332,16 +331,14 @@ function getHistoryState(location, index) {
|
|
|
332
331
|
idx: index
|
|
333
332
|
};
|
|
334
333
|
}
|
|
334
|
+
|
|
335
335
|
/**
|
|
336
336
|
* Creates a Location object with a unique key from the given Path
|
|
337
337
|
*/
|
|
338
|
-
|
|
339
|
-
|
|
340
338
|
function createLocation(current, to, state, key) {
|
|
341
339
|
if (state === void 0) {
|
|
342
340
|
state = null;
|
|
343
341
|
}
|
|
344
|
-
|
|
345
342
|
let location = _extends({
|
|
346
343
|
pathname: typeof current === "string" ? current : current.pathname,
|
|
347
344
|
search: "",
|
|
@@ -354,13 +351,12 @@ function createLocation(current, to, state, key) {
|
|
|
354
351
|
// keep as is for the time being and just let any incoming keys take precedence
|
|
355
352
|
key: to && to.key || key || createKey()
|
|
356
353
|
});
|
|
357
|
-
|
|
358
354
|
return location;
|
|
359
355
|
}
|
|
356
|
+
|
|
360
357
|
/**
|
|
361
358
|
* Creates a string URL path from the given pathname, search, and hash components.
|
|
362
359
|
*/
|
|
363
|
-
|
|
364
360
|
function createPath(_ref) {
|
|
365
361
|
let {
|
|
366
362
|
pathname = "/",
|
|
@@ -371,41 +367,33 @@ function createPath(_ref) {
|
|
|
371
367
|
if (hash && hash !== "#") pathname += hash.charAt(0) === "#" ? hash : "#" + hash;
|
|
372
368
|
return pathname;
|
|
373
369
|
}
|
|
370
|
+
|
|
374
371
|
/**
|
|
375
372
|
* Parses a string URL path into its separate pathname, search, and hash components.
|
|
376
373
|
*/
|
|
377
|
-
|
|
378
374
|
function parsePath(path) {
|
|
379
375
|
let parsedPath = {};
|
|
380
|
-
|
|
381
376
|
if (path) {
|
|
382
377
|
let hashIndex = path.indexOf("#");
|
|
383
|
-
|
|
384
378
|
if (hashIndex >= 0) {
|
|
385
379
|
parsedPath.hash = path.substr(hashIndex);
|
|
386
380
|
path = path.substr(0, hashIndex);
|
|
387
381
|
}
|
|
388
|
-
|
|
389
382
|
let searchIndex = path.indexOf("?");
|
|
390
|
-
|
|
391
383
|
if (searchIndex >= 0) {
|
|
392
384
|
parsedPath.search = path.substr(searchIndex);
|
|
393
385
|
path = path.substr(0, searchIndex);
|
|
394
386
|
}
|
|
395
|
-
|
|
396
387
|
if (path) {
|
|
397
388
|
parsedPath.pathname = path;
|
|
398
389
|
}
|
|
399
390
|
}
|
|
400
|
-
|
|
401
391
|
return parsedPath;
|
|
402
392
|
}
|
|
403
|
-
|
|
404
393
|
function getUrlBasedHistory(getLocation, createHref, validateLocation, options) {
|
|
405
394
|
if (options === void 0) {
|
|
406
395
|
options = {};
|
|
407
396
|
}
|
|
408
|
-
|
|
409
397
|
let {
|
|
410
398
|
window = document.defaultView,
|
|
411
399
|
v5Compat = false
|
|
@@ -413,30 +401,27 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
|
|
|
413
401
|
let globalHistory = window.history;
|
|
414
402
|
let action = exports.Action.Pop;
|
|
415
403
|
let listener = null;
|
|
416
|
-
let index = getIndex();
|
|
404
|
+
let index = getIndex();
|
|
405
|
+
// Index should only be null when we initialize. If not, it's because the
|
|
417
406
|
// user called history.pushState or history.replaceState directly, in which
|
|
418
407
|
// case we should log a warning as it will result in bugs.
|
|
419
|
-
|
|
420
408
|
if (index == null) {
|
|
421
409
|
index = 0;
|
|
422
410
|
globalHistory.replaceState(_extends({}, globalHistory.state, {
|
|
423
411
|
idx: index
|
|
424
412
|
}), "");
|
|
425
413
|
}
|
|
426
|
-
|
|
427
414
|
function getIndex() {
|
|
428
415
|
let state = globalHistory.state || {
|
|
429
416
|
idx: null
|
|
430
417
|
};
|
|
431
418
|
return state.idx;
|
|
432
419
|
}
|
|
433
|
-
|
|
434
420
|
function handlePop() {
|
|
435
421
|
action = exports.Action.Pop;
|
|
436
422
|
let nextIndex = getIndex();
|
|
437
423
|
let delta = nextIndex == null ? null : nextIndex - index;
|
|
438
424
|
index = nextIndex;
|
|
439
|
-
|
|
440
425
|
if (listener) {
|
|
441
426
|
listener({
|
|
442
427
|
action,
|
|
@@ -445,23 +430,29 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
|
|
|
445
430
|
});
|
|
446
431
|
}
|
|
447
432
|
}
|
|
448
|
-
|
|
449
433
|
function push(to, state) {
|
|
450
434
|
action = exports.Action.Push;
|
|
451
435
|
let location = createLocation(history.location, to, state);
|
|
452
436
|
if (validateLocation) validateLocation(location, to);
|
|
453
437
|
index = getIndex() + 1;
|
|
454
438
|
let historyState = getHistoryState(location, index);
|
|
455
|
-
let url = history.createHref(location);
|
|
439
|
+
let url = history.createHref(location);
|
|
456
440
|
|
|
441
|
+
// try...catch because iOS limits us to 100 pushState calls :/
|
|
457
442
|
try {
|
|
458
443
|
globalHistory.pushState(historyState, "", url);
|
|
459
444
|
} catch (error) {
|
|
445
|
+
// If the exception is because `state` can't be serialized, let that throw
|
|
446
|
+
// outwards just like a replace call would so the dev knows the cause
|
|
447
|
+
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#shared-history-push/replace-state-steps
|
|
448
|
+
// https://html.spec.whatwg.org/multipage/structured-data.html#structuredserializeinternal
|
|
449
|
+
if (error instanceof DOMException && error.name === "DataCloneError") {
|
|
450
|
+
throw error;
|
|
451
|
+
}
|
|
460
452
|
// They are going to lose state here, but there is no real
|
|
461
453
|
// way to warn them about it since the page will refresh...
|
|
462
454
|
window.location.assign(url);
|
|
463
455
|
}
|
|
464
|
-
|
|
465
456
|
if (v5Compat && listener) {
|
|
466
457
|
listener({
|
|
467
458
|
action,
|
|
@@ -470,7 +461,6 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
|
|
|
470
461
|
});
|
|
471
462
|
}
|
|
472
463
|
}
|
|
473
|
-
|
|
474
464
|
function replace(to, state) {
|
|
475
465
|
action = exports.Action.Replace;
|
|
476
466
|
let location = createLocation(history.location, to, state);
|
|
@@ -479,7 +469,6 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
|
|
|
479
469
|
let historyState = getHistoryState(location, index);
|
|
480
470
|
let url = history.createHref(location);
|
|
481
471
|
globalHistory.replaceState(historyState, "", url);
|
|
482
|
-
|
|
483
472
|
if (v5Compat && listener) {
|
|
484
473
|
listener({
|
|
485
474
|
action,
|
|
@@ -488,7 +477,6 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
|
|
|
488
477
|
});
|
|
489
478
|
}
|
|
490
479
|
}
|
|
491
|
-
|
|
492
480
|
function createURL(to) {
|
|
493
481
|
// window.location.origin is "null" (the literal string value) in Firefox
|
|
494
482
|
// under certain conditions, notably when serving from a local HTML file
|
|
@@ -498,21 +486,17 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
|
|
|
498
486
|
invariant(base, "No window.location.(origin|href) available to create URL for href: " + href);
|
|
499
487
|
return new URL(href, base);
|
|
500
488
|
}
|
|
501
|
-
|
|
502
489
|
let history = {
|
|
503
490
|
get action() {
|
|
504
491
|
return action;
|
|
505
492
|
},
|
|
506
|
-
|
|
507
493
|
get location() {
|
|
508
494
|
return getLocation(window, globalHistory);
|
|
509
495
|
},
|
|
510
|
-
|
|
511
496
|
listen(fn) {
|
|
512
497
|
if (listener) {
|
|
513
498
|
throw new Error("A history only accepts one active listener");
|
|
514
499
|
}
|
|
515
|
-
|
|
516
500
|
window.addEventListener(PopStateEventType, handlePop);
|
|
517
501
|
listener = fn;
|
|
518
502
|
return () => {
|
|
@@ -520,13 +504,10 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
|
|
|
520
504
|
listener = null;
|
|
521
505
|
};
|
|
522
506
|
},
|
|
523
|
-
|
|
524
507
|
createHref(to) {
|
|
525
508
|
return createHref(window, to);
|
|
526
509
|
},
|
|
527
|
-
|
|
528
510
|
createURL,
|
|
529
|
-
|
|
530
511
|
encodeLocation(to) {
|
|
531
512
|
// Encode a Location the same way window.location would
|
|
532
513
|
let url = createURL(to);
|
|
@@ -536,66 +517,176 @@ function getUrlBasedHistory(getLocation, createHref, validateLocation, options)
|
|
|
536
517
|
hash: url.hash
|
|
537
518
|
};
|
|
538
519
|
},
|
|
539
|
-
|
|
540
520
|
push,
|
|
541
521
|
replace,
|
|
542
|
-
|
|
543
522
|
go(n) {
|
|
544
523
|
return globalHistory.go(n);
|
|
545
524
|
}
|
|
546
|
-
|
|
547
525
|
};
|
|
548
526
|
return history;
|
|
549
|
-
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
//#endregion
|
|
550
530
|
|
|
551
531
|
/**
|
|
552
532
|
* Map of routeId -> data returned from a loader/action/error
|
|
553
533
|
*/
|
|
554
534
|
|
|
555
535
|
let ResultType;
|
|
536
|
+
|
|
556
537
|
/**
|
|
557
538
|
* Successful result from a loader or action
|
|
558
539
|
*/
|
|
559
|
-
|
|
560
540
|
(function (ResultType) {
|
|
561
541
|
ResultType["data"] = "data";
|
|
562
542
|
ResultType["deferred"] = "deferred";
|
|
563
543
|
ResultType["redirect"] = "redirect";
|
|
564
544
|
ResultType["error"] = "error";
|
|
565
545
|
})(ResultType || (ResultType = {}));
|
|
566
|
-
|
|
546
|
+
/**
|
|
547
|
+
* Successful defer() result from a loader or action
|
|
548
|
+
*/
|
|
549
|
+
/**
|
|
550
|
+
* Redirect result from a loader or action
|
|
551
|
+
*/
|
|
552
|
+
/**
|
|
553
|
+
* Unsuccessful result from a loader or action
|
|
554
|
+
*/
|
|
555
|
+
/**
|
|
556
|
+
* Result from a loader or action - potentially successful or unsuccessful
|
|
557
|
+
*/
|
|
558
|
+
/**
|
|
559
|
+
* Users can specify either lowercase or uppercase form methods on <Form>,
|
|
560
|
+
* useSubmit(), <fetcher.Form>, etc.
|
|
561
|
+
*/
|
|
562
|
+
/**
|
|
563
|
+
* Active navigation/fetcher form methods are exposed in lowercase on the
|
|
564
|
+
* RouterState
|
|
565
|
+
*/
|
|
566
|
+
/**
|
|
567
|
+
* In v7, active navigation/fetcher form methods are exposed in uppercase on the
|
|
568
|
+
* RouterState. This is to align with the normalization done via fetch().
|
|
569
|
+
*/
|
|
570
|
+
/**
|
|
571
|
+
* @private
|
|
572
|
+
* Internal interface to pass around for action submissions, not intended for
|
|
573
|
+
* external consumption
|
|
574
|
+
*/
|
|
575
|
+
/**
|
|
576
|
+
* @private
|
|
577
|
+
* Arguments passed to route loader/action functions. Same for now but we keep
|
|
578
|
+
* this as a private implementation detail in case they diverge in the future.
|
|
579
|
+
*/
|
|
580
|
+
/**
|
|
581
|
+
* Arguments passed to loader functions
|
|
582
|
+
*/
|
|
583
|
+
/**
|
|
584
|
+
* Arguments passed to action functions
|
|
585
|
+
*/
|
|
586
|
+
/**
|
|
587
|
+
* Loaders and actions can return anything except `undefined` (`null` is a
|
|
588
|
+
* valid return value if there is no data to return). Responses are preferred
|
|
589
|
+
* and will ease any future migration to Remix
|
|
590
|
+
*/
|
|
591
|
+
/**
|
|
592
|
+
* Route loader function signature
|
|
593
|
+
*/
|
|
594
|
+
/**
|
|
595
|
+
* Route action function signature
|
|
596
|
+
*/
|
|
597
|
+
/**
|
|
598
|
+
* Route shouldRevalidate function signature. This runs after any submission
|
|
599
|
+
* (navigation or fetcher), so we flatten the navigation/fetcher submission
|
|
600
|
+
* onto the arguments. It shouldn't matter whether it came from a navigation
|
|
601
|
+
* or a fetcher, what really matters is the URLs and the formData since loaders
|
|
602
|
+
* have to re-run based on the data models that were potentially mutated.
|
|
603
|
+
*/
|
|
604
|
+
/**
|
|
605
|
+
* Function provided by the framework-aware layers to set `hasErrorBoundary`
|
|
606
|
+
* from the framework-aware `errorElement` prop
|
|
607
|
+
*
|
|
608
|
+
* @deprecated Use `mapRouteProperties` instead
|
|
609
|
+
*/
|
|
610
|
+
/**
|
|
611
|
+
* Function provided by the framework-aware layers to set any framework-specific
|
|
612
|
+
* properties from framework-agnostic properties
|
|
613
|
+
*/
|
|
614
|
+
/**
|
|
615
|
+
* Keys we cannot change from within a lazy() function. We spread all other keys
|
|
616
|
+
* onto the route. Either they're meaningful to the router, or they'll get
|
|
617
|
+
* ignored.
|
|
618
|
+
*/
|
|
567
619
|
const immutableRouteKeys = new Set(["lazy", "caseSensitive", "path", "id", "index", "children"]);
|
|
620
|
+
|
|
568
621
|
/**
|
|
569
622
|
* lazy() function to load a route definition, which can add non-matching
|
|
570
623
|
* related properties to a route
|
|
571
624
|
*/
|
|
572
625
|
|
|
626
|
+
/**
|
|
627
|
+
* Base RouteObject with common props shared by all types of routes
|
|
628
|
+
*/
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Index routes must not have children
|
|
632
|
+
*/
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Non-index routes may have children, but cannot have index
|
|
636
|
+
*/
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* A route object represents a logical route, with (optionally) its child
|
|
640
|
+
* routes organized in a tree-like structure.
|
|
641
|
+
*/
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* A data route object, which is just a RouteObject with a required unique ID
|
|
645
|
+
*/
|
|
646
|
+
|
|
647
|
+
// Recursive helper for finding path parameters in the absence of wildcards
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Examples:
|
|
651
|
+
* "/a/b/*" -> "*"
|
|
652
|
+
* ":a" -> "a"
|
|
653
|
+
* "/a/:b" -> "b"
|
|
654
|
+
* "/a/blahblahblah:b" -> "b"
|
|
655
|
+
* "/:a/:b" -> "a" | "b"
|
|
656
|
+
* "/:a/b/:c/*" -> "a" | "c" | "*"
|
|
657
|
+
*/
|
|
658
|
+
|
|
659
|
+
// Attempt to parse the given string segment. If it fails, then just return the
|
|
660
|
+
// plain string type as a default fallback. Otherwise return the union of the
|
|
661
|
+
// parsed string literals that were referenced as dynamic segments in the route.
|
|
662
|
+
/**
|
|
663
|
+
* The parameters that were parsed from the URL path.
|
|
664
|
+
*/
|
|
665
|
+
/**
|
|
666
|
+
* A RouteMatch contains info about how a route matched a URL.
|
|
667
|
+
*/
|
|
573
668
|
function isIndexRoute(route) {
|
|
574
669
|
return route.index === true;
|
|
575
|
-
}
|
|
576
|
-
// solely with AgnosticDataRouteObject's within the Router
|
|
577
|
-
|
|
670
|
+
}
|
|
578
671
|
|
|
672
|
+
// Walk the route tree generating unique IDs where necessary so we are working
|
|
673
|
+
// solely with AgnosticDataRouteObject's within the Router
|
|
579
674
|
function convertRoutesToDataRoutes(routes, mapRouteProperties, parentPath, manifest) {
|
|
580
675
|
if (parentPath === void 0) {
|
|
581
676
|
parentPath = [];
|
|
582
677
|
}
|
|
583
|
-
|
|
584
678
|
if (manifest === void 0) {
|
|
585
679
|
manifest = {};
|
|
586
680
|
}
|
|
587
|
-
|
|
588
681
|
return routes.map((route, index) => {
|
|
589
682
|
let treePath = [...parentPath, index];
|
|
590
683
|
let id = typeof route.id === "string" ? route.id : treePath.join("-");
|
|
591
684
|
invariant(route.index !== true || !route.children, "Cannot specify children on an index route");
|
|
592
685
|
invariant(!manifest[id], "Found a route id collision on id \"" + id + "\". Route " + "id's must be globally unique within Data Router usages");
|
|
593
|
-
|
|
594
686
|
if (isIndexRoute(route)) {
|
|
595
687
|
let indexRoute = _extends({}, route, mapRouteProperties(route), {
|
|
596
688
|
id
|
|
597
689
|
});
|
|
598
|
-
|
|
599
690
|
manifest[id] = indexRoute;
|
|
600
691
|
return indexRoute;
|
|
601
692
|
} else {
|
|
@@ -603,41 +694,35 @@ function convertRoutesToDataRoutes(routes, mapRouteProperties, parentPath, manif
|
|
|
603
694
|
id,
|
|
604
695
|
children: undefined
|
|
605
696
|
});
|
|
606
|
-
|
|
607
697
|
manifest[id] = pathOrLayoutRoute;
|
|
608
|
-
|
|
609
698
|
if (route.children) {
|
|
610
699
|
pathOrLayoutRoute.children = convertRoutesToDataRoutes(route.children, mapRouteProperties, treePath, manifest);
|
|
611
700
|
}
|
|
612
|
-
|
|
613
701
|
return pathOrLayoutRoute;
|
|
614
702
|
}
|
|
615
703
|
});
|
|
616
704
|
}
|
|
705
|
+
|
|
617
706
|
/**
|
|
618
707
|
* Matches the given routes to a location and returns the match data.
|
|
619
708
|
*
|
|
620
709
|
* @see https://reactrouter.com/utils/match-routes
|
|
621
710
|
*/
|
|
622
|
-
|
|
623
711
|
function matchRoutes(routes, locationArg, basename) {
|
|
624
712
|
if (basename === void 0) {
|
|
625
713
|
basename = "/";
|
|
626
714
|
}
|
|
627
|
-
|
|
628
715
|
let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
|
|
629
716
|
let pathname = stripBasename(location.pathname || "/", basename);
|
|
630
|
-
|
|
631
717
|
if (pathname == null) {
|
|
632
718
|
return null;
|
|
633
719
|
}
|
|
634
|
-
|
|
635
720
|
let branches = flattenRoutes(routes);
|
|
636
721
|
rankRouteBranches(branches);
|
|
637
722
|
let matches = null;
|
|
638
|
-
|
|
639
723
|
for (let i = 0; matches == null && i < branches.length; ++i) {
|
|
640
|
-
matches = matchRouteBranch(branches[i],
|
|
724
|
+
matches = matchRouteBranch(branches[i],
|
|
725
|
+
// Incoming pathnames are generally encoded from either window.location
|
|
641
726
|
// or from router.navigate, but we want to match against the unencoded
|
|
642
727
|
// paths in the route definitions. Memory router locations won't be
|
|
643
728
|
// encoded here but there also shouldn't be anything to decode so this
|
|
@@ -645,23 +730,18 @@ function matchRoutes(routes, locationArg, basename) {
|
|
|
645
730
|
// history-aware.
|
|
646
731
|
safelyDecodeURI(pathname));
|
|
647
732
|
}
|
|
648
|
-
|
|
649
733
|
return matches;
|
|
650
734
|
}
|
|
651
|
-
|
|
652
735
|
function flattenRoutes(routes, branches, parentsMeta, parentPath) {
|
|
653
736
|
if (branches === void 0) {
|
|
654
737
|
branches = [];
|
|
655
738
|
}
|
|
656
|
-
|
|
657
739
|
if (parentsMeta === void 0) {
|
|
658
740
|
parentsMeta = [];
|
|
659
741
|
}
|
|
660
|
-
|
|
661
742
|
if (parentPath === void 0) {
|
|
662
743
|
parentPath = "";
|
|
663
744
|
}
|
|
664
|
-
|
|
665
745
|
let flattenRoute = (route, index, relativePath) => {
|
|
666
746
|
let meta = {
|
|
667
747
|
relativePath: relativePath === undefined ? route.path || "" : relativePath,
|
|
@@ -669,40 +749,37 @@ function flattenRoutes(routes, branches, parentsMeta, parentPath) {
|
|
|
669
749
|
childrenIndex: index,
|
|
670
750
|
route
|
|
671
751
|
};
|
|
672
|
-
|
|
673
752
|
if (meta.relativePath.startsWith("/")) {
|
|
674
753
|
invariant(meta.relativePath.startsWith(parentPath), "Absolute route path \"" + meta.relativePath + "\" nested under path " + ("\"" + parentPath + "\" is not valid. An absolute child route path ") + "must start with the combined path of all its parent routes.");
|
|
675
754
|
meta.relativePath = meta.relativePath.slice(parentPath.length);
|
|
676
755
|
}
|
|
677
|
-
|
|
678
756
|
let path = joinPaths([parentPath, meta.relativePath]);
|
|
679
|
-
let routesMeta = parentsMeta.concat(meta);
|
|
757
|
+
let routesMeta = parentsMeta.concat(meta);
|
|
758
|
+
|
|
759
|
+
// Add the children before adding this route to the array so we traverse the
|
|
680
760
|
// route tree depth-first and child routes appear before their parents in
|
|
681
761
|
// the "flattened" version.
|
|
682
|
-
|
|
683
762
|
if (route.children && route.children.length > 0) {
|
|
684
|
-
invariant(
|
|
763
|
+
invariant(
|
|
764
|
+
// Our types know better, but runtime JS may not!
|
|
685
765
|
// @ts-expect-error
|
|
686
766
|
route.index !== true, "Index routes must not have child routes. Please remove " + ("all child routes from route path \"" + path + "\"."));
|
|
687
767
|
flattenRoutes(route.children, branches, routesMeta, path);
|
|
688
|
-
}
|
|
689
|
-
// index routes, so don't add them to the list of possible branches.
|
|
690
|
-
|
|
768
|
+
}
|
|
691
769
|
|
|
770
|
+
// Routes without a path shouldn't ever match by themselves unless they are
|
|
771
|
+
// index routes, so don't add them to the list of possible branches.
|
|
692
772
|
if (route.path == null && !route.index) {
|
|
693
773
|
return;
|
|
694
774
|
}
|
|
695
|
-
|
|
696
775
|
branches.push({
|
|
697
776
|
path,
|
|
698
777
|
score: computeScore(path, route.index),
|
|
699
778
|
routesMeta
|
|
700
779
|
});
|
|
701
780
|
};
|
|
702
|
-
|
|
703
781
|
routes.forEach((route, index) => {
|
|
704
782
|
var _route$path;
|
|
705
|
-
|
|
706
783
|
// coarse-grain check for optional params
|
|
707
784
|
if (route.path === "" || !((_route$path = route.path) != null && _route$path.includes("?"))) {
|
|
708
785
|
flattenRoute(route, index);
|
|
@@ -714,6 +791,7 @@ function flattenRoutes(routes, branches, parentsMeta, parentPath) {
|
|
|
714
791
|
});
|
|
715
792
|
return branches;
|
|
716
793
|
}
|
|
794
|
+
|
|
717
795
|
/**
|
|
718
796
|
* Computes all combinations of optional path segments for a given path,
|
|
719
797
|
* excluding combinations that are ambiguous and of lower priority.
|
|
@@ -728,82 +806,74 @@ function flattenRoutes(routes, branches, parentsMeta, parentPath) {
|
|
|
728
806
|
* - `/one/three/:four/:five`
|
|
729
807
|
* - `/one/:two/three/:four/:five`
|
|
730
808
|
*/
|
|
731
|
-
|
|
732
|
-
|
|
733
809
|
function explodeOptionalSegments(path) {
|
|
734
810
|
let segments = path.split("/");
|
|
735
811
|
if (segments.length === 0) return [];
|
|
736
|
-
let [first, ...rest] = segments;
|
|
737
|
-
|
|
738
|
-
let isOptional = first.endsWith("?"); // Compute the corresponding required segment: `foo?` -> `foo`
|
|
812
|
+
let [first, ...rest] = segments;
|
|
739
813
|
|
|
814
|
+
// Optional path segments are denoted by a trailing `?`
|
|
815
|
+
let isOptional = first.endsWith("?");
|
|
816
|
+
// Compute the corresponding required segment: `foo?` -> `foo`
|
|
740
817
|
let required = first.replace(/\?$/, "");
|
|
741
|
-
|
|
742
818
|
if (rest.length === 0) {
|
|
743
819
|
// Intepret empty string as omitting an optional segment
|
|
744
820
|
// `["one", "", "three"]` corresponds to omitting `:two` from `/one/:two?/three` -> `/one/three`
|
|
745
821
|
return isOptional ? [required, ""] : [required];
|
|
746
822
|
}
|
|
747
|
-
|
|
748
823
|
let restExploded = explodeOptionalSegments(rest.join("/"));
|
|
749
|
-
let result = [];
|
|
824
|
+
let result = [];
|
|
825
|
+
|
|
826
|
+
// All child paths with the prefix. Do this for all children before the
|
|
750
827
|
// optional version for all children so we get consistent ordering where the
|
|
751
828
|
// parent optional aspect is preferred as required. Otherwise, we can get
|
|
752
829
|
// child sections interspersed where deeper optional segments are higher than
|
|
753
830
|
// parent optional segments, where for example, /:two would explodes _earlier_
|
|
754
831
|
// then /:one. By always including the parent as required _for all children_
|
|
755
832
|
// first, we avoid this issue
|
|
833
|
+
result.push(...restExploded.map(subpath => subpath === "" ? required : [required, subpath].join("/")));
|
|
756
834
|
|
|
757
|
-
|
|
758
|
-
|
|
835
|
+
// Then if this is an optional value, add all child versions without
|
|
759
836
|
if (isOptional) {
|
|
760
837
|
result.push(...restExploded);
|
|
761
|
-
}
|
|
762
|
-
|
|
838
|
+
}
|
|
763
839
|
|
|
840
|
+
// for absolute paths, ensure `/` instead of empty segment
|
|
764
841
|
return result.map(exploded => path.startsWith("/") && exploded === "" ? "/" : exploded);
|
|
765
842
|
}
|
|
766
|
-
|
|
767
843
|
function rankRouteBranches(branches) {
|
|
768
844
|
branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
|
|
769
845
|
: compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
|
|
770
846
|
}
|
|
771
|
-
|
|
772
847
|
const paramRe = /^:\w+$/;
|
|
773
848
|
const dynamicSegmentValue = 3;
|
|
774
849
|
const indexRouteValue = 2;
|
|
775
850
|
const emptySegmentValue = 1;
|
|
776
851
|
const staticSegmentValue = 10;
|
|
777
852
|
const splatPenalty = -2;
|
|
778
|
-
|
|
779
853
|
const isSplat = s => s === "*";
|
|
780
|
-
|
|
781
854
|
function computeScore(path, index) {
|
|
782
855
|
let segments = path.split("/");
|
|
783
856
|
let initialScore = segments.length;
|
|
784
|
-
|
|
785
857
|
if (segments.some(isSplat)) {
|
|
786
858
|
initialScore += splatPenalty;
|
|
787
859
|
}
|
|
788
|
-
|
|
789
860
|
if (index) {
|
|
790
861
|
initialScore += indexRouteValue;
|
|
791
862
|
}
|
|
792
|
-
|
|
793
863
|
return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
|
|
794
864
|
}
|
|
795
|
-
|
|
796
865
|
function compareIndexes(a, b) {
|
|
797
866
|
let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
|
|
798
|
-
return siblings ?
|
|
867
|
+
return siblings ?
|
|
868
|
+
// If two routes are siblings, we should try to match the earlier sibling
|
|
799
869
|
// first. This allows people to have fine-grained control over the matching
|
|
800
870
|
// behavior by simply putting routes with identical paths in the order they
|
|
801
871
|
// want them tried.
|
|
802
|
-
a[a.length - 1] - b[b.length - 1] :
|
|
872
|
+
a[a.length - 1] - b[b.length - 1] :
|
|
873
|
+
// Otherwise, it doesn't really make sense to rank non-siblings by index,
|
|
803
874
|
// so they sort equally.
|
|
804
875
|
0;
|
|
805
876
|
}
|
|
806
|
-
|
|
807
877
|
function matchRouteBranch(branch, pathname) {
|
|
808
878
|
let {
|
|
809
879
|
routesMeta
|
|
@@ -811,7 +881,6 @@ function matchRouteBranch(branch, pathname) {
|
|
|
811
881
|
let matchedParams = {};
|
|
812
882
|
let matchedPathname = "/";
|
|
813
883
|
let matches = [];
|
|
814
|
-
|
|
815
884
|
for (let i = 0; i < routesMeta.length; ++i) {
|
|
816
885
|
let meta = routesMeta[i];
|
|
817
886
|
let end = i === routesMeta.length - 1;
|
|
@@ -831,72 +900,70 @@ function matchRouteBranch(branch, pathname) {
|
|
|
831
900
|
pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
|
|
832
901
|
route
|
|
833
902
|
});
|
|
834
|
-
|
|
835
903
|
if (match.pathnameBase !== "/") {
|
|
836
904
|
matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
|
|
837
905
|
}
|
|
838
906
|
}
|
|
839
|
-
|
|
840
907
|
return matches;
|
|
841
908
|
}
|
|
909
|
+
|
|
842
910
|
/**
|
|
843
911
|
* Returns a path with params interpolated.
|
|
844
912
|
*
|
|
845
913
|
* @see https://reactrouter.com/utils/generate-path
|
|
846
914
|
*/
|
|
847
|
-
|
|
848
|
-
|
|
849
915
|
function generatePath(originalPath, params) {
|
|
850
916
|
if (params === void 0) {
|
|
851
917
|
params = {};
|
|
852
918
|
}
|
|
853
|
-
|
|
854
919
|
let path = originalPath;
|
|
855
|
-
|
|
856
920
|
if (path.endsWith("*") && path !== "*" && !path.endsWith("/*")) {
|
|
857
921
|
warning(false, "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
|
|
858
922
|
path = path.replace(/\*$/, "/*");
|
|
859
|
-
}
|
|
860
|
-
|
|
923
|
+
}
|
|
861
924
|
|
|
925
|
+
// ensure `/` is added at the beginning if the path is absolute
|
|
862
926
|
const prefix = path.startsWith("/") ? "/" : "";
|
|
863
927
|
const segments = path.split(/\/+/).map((segment, index, array) => {
|
|
864
|
-
const isLastSegment = index === array.length - 1;
|
|
928
|
+
const isLastSegment = index === array.length - 1;
|
|
865
929
|
|
|
930
|
+
// only apply the splat if it's the last segment
|
|
866
931
|
if (isLastSegment && segment === "*") {
|
|
867
932
|
const star = "*";
|
|
868
|
-
const starParam = params[star];
|
|
933
|
+
const starParam = params[star];
|
|
869
934
|
|
|
935
|
+
// Apply the splat
|
|
870
936
|
return starParam;
|
|
871
937
|
}
|
|
872
|
-
|
|
873
938
|
const keyMatch = segment.match(/^:(\w+)(\??)$/);
|
|
874
|
-
|
|
875
939
|
if (keyMatch) {
|
|
876
940
|
const [, key, optional] = keyMatch;
|
|
877
941
|
let param = params[key];
|
|
878
|
-
|
|
879
942
|
if (optional === "?") {
|
|
880
943
|
return param == null ? "" : param;
|
|
881
944
|
}
|
|
882
|
-
|
|
883
945
|
if (param == null) {
|
|
884
946
|
invariant(false, "Missing \":" + key + "\" param");
|
|
885
947
|
}
|
|
886
|
-
|
|
887
948
|
return param;
|
|
888
|
-
}
|
|
889
|
-
|
|
949
|
+
}
|
|
890
950
|
|
|
951
|
+
// Remove any optional markers from optional static segments
|
|
891
952
|
return segment.replace(/\?$/g, "");
|
|
892
|
-
})
|
|
953
|
+
})
|
|
954
|
+
// Remove empty segments
|
|
893
955
|
.filter(segment => !!segment);
|
|
894
956
|
return prefix + segments.join("/");
|
|
895
957
|
}
|
|
958
|
+
|
|
896
959
|
/**
|
|
897
960
|
* A PathPattern is used to match on some portion of a URL pathname.
|
|
898
961
|
*/
|
|
899
962
|
|
|
963
|
+
/**
|
|
964
|
+
* A PathMatch contains info about how a PathPattern matched on a URL pathname.
|
|
965
|
+
*/
|
|
966
|
+
|
|
900
967
|
/**
|
|
901
968
|
* Performs pattern matching on a URL pathname and returns information about
|
|
902
969
|
* the match.
|
|
@@ -911,7 +978,6 @@ function matchPath(pattern, pathname) {
|
|
|
911
978
|
end: true
|
|
912
979
|
};
|
|
913
980
|
}
|
|
914
|
-
|
|
915
981
|
let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
|
|
916
982
|
let match = pathname.match(matcher);
|
|
917
983
|
if (!match) return null;
|
|
@@ -925,7 +991,6 @@ function matchPath(pattern, pathname) {
|
|
|
925
991
|
let splatValue = captureGroups[index] || "";
|
|
926
992
|
pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
|
|
927
993
|
}
|
|
928
|
-
|
|
929
994
|
memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
|
|
930
995
|
return memo;
|
|
931
996
|
}, {});
|
|
@@ -936,16 +1001,13 @@ function matchPath(pattern, pathname) {
|
|
|
936
1001
|
pattern
|
|
937
1002
|
};
|
|
938
1003
|
}
|
|
939
|
-
|
|
940
1004
|
function compilePath(path, caseSensitive, end) {
|
|
941
1005
|
if (caseSensitive === void 0) {
|
|
942
1006
|
caseSensitive = false;
|
|
943
1007
|
}
|
|
944
|
-
|
|
945
1008
|
if (end === void 0) {
|
|
946
1009
|
end = true;
|
|
947
1010
|
}
|
|
948
|
-
|
|
949
1011
|
warning(path === "*" || !path.endsWith("*") || path.endsWith("/*"), "Route path \"" + path + "\" will be treated as if it were " + ("\"" + path.replace(/\*$/, "/*") + "\" because the `*` character must ") + "always follow a `/` in the pattern. To get rid of this warning, " + ("please change the route path to \"" + path.replace(/\*$/, "/*") + "\"."));
|
|
950
1012
|
let paramNames = [];
|
|
951
1013
|
let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
|
|
@@ -955,7 +1017,6 @@ function compilePath(path, caseSensitive, end) {
|
|
|
955
1017
|
paramNames.push(paramName);
|
|
956
1018
|
return "/([^\\/]+)";
|
|
957
1019
|
});
|
|
958
|
-
|
|
959
1020
|
if (path.endsWith("*")) {
|
|
960
1021
|
paramNames.push("*");
|
|
961
1022
|
regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
|
|
@@ -973,11 +1034,9 @@ function compilePath(path, caseSensitive, end) {
|
|
|
973
1034
|
// /user-preferences since `-` counts as a word boundary.
|
|
974
1035
|
regexpSource += "(?:(?=\\/|$))";
|
|
975
1036
|
} else ;
|
|
976
|
-
|
|
977
1037
|
let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
|
|
978
1038
|
return [matcher, paramNames];
|
|
979
1039
|
}
|
|
980
|
-
|
|
981
1040
|
function safelyDecodeURI(value) {
|
|
982
1041
|
try {
|
|
983
1042
|
return decodeURI(value);
|
|
@@ -986,7 +1045,6 @@ function safelyDecodeURI(value) {
|
|
|
986
1045
|
return value;
|
|
987
1046
|
}
|
|
988
1047
|
}
|
|
989
|
-
|
|
990
1048
|
function safelyDecodeURIComponent(value, paramName) {
|
|
991
1049
|
try {
|
|
992
1050
|
return decodeURIComponent(value);
|
|
@@ -995,41 +1053,36 @@ function safelyDecodeURIComponent(value, paramName) {
|
|
|
995
1053
|
return value;
|
|
996
1054
|
}
|
|
997
1055
|
}
|
|
1056
|
+
|
|
998
1057
|
/**
|
|
999
1058
|
* @private
|
|
1000
1059
|
*/
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
1060
|
function stripBasename(pathname, basename) {
|
|
1004
1061
|
if (basename === "/") return pathname;
|
|
1005
|
-
|
|
1006
1062
|
if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
|
|
1007
1063
|
return null;
|
|
1008
|
-
}
|
|
1009
|
-
// specify a basename with a trailing slash, we should support it
|
|
1010
|
-
|
|
1064
|
+
}
|
|
1011
1065
|
|
|
1066
|
+
// We want to leave trailing slash behavior in the user's control, so if they
|
|
1067
|
+
// specify a basename with a trailing slash, we should support it
|
|
1012
1068
|
let startIndex = basename.endsWith("/") ? basename.length - 1 : basename.length;
|
|
1013
1069
|
let nextChar = pathname.charAt(startIndex);
|
|
1014
|
-
|
|
1015
1070
|
if (nextChar && nextChar !== "/") {
|
|
1016
1071
|
// pathname does not start with basename/
|
|
1017
1072
|
return null;
|
|
1018
1073
|
}
|
|
1019
|
-
|
|
1020
1074
|
return pathname.slice(startIndex) || "/";
|
|
1021
1075
|
}
|
|
1076
|
+
|
|
1022
1077
|
/**
|
|
1023
1078
|
* Returns a resolved path object relative to the given pathname.
|
|
1024
1079
|
*
|
|
1025
1080
|
* @see https://reactrouter.com/utils/resolve-path
|
|
1026
1081
|
*/
|
|
1027
|
-
|
|
1028
1082
|
function resolvePath(to, fromPathname) {
|
|
1029
1083
|
if (fromPathname === void 0) {
|
|
1030
1084
|
fromPathname = "/";
|
|
1031
1085
|
}
|
|
1032
|
-
|
|
1033
1086
|
let {
|
|
1034
1087
|
pathname: toPathname,
|
|
1035
1088
|
search = "",
|
|
@@ -1042,7 +1095,6 @@ function resolvePath(to, fromPathname) {
|
|
|
1042
1095
|
hash: normalizeHash(hash)
|
|
1043
1096
|
};
|
|
1044
1097
|
}
|
|
1045
|
-
|
|
1046
1098
|
function resolvePathname(relativePath, fromPathname) {
|
|
1047
1099
|
let segments = fromPathname.replace(/\/+$/, "").split("/");
|
|
1048
1100
|
let relativeSegments = relativePath.split("/");
|
|
@@ -1056,10 +1108,10 @@ function resolvePathname(relativePath, fromPathname) {
|
|
|
1056
1108
|
});
|
|
1057
1109
|
return segments.length > 1 ? segments.join("/") : "/";
|
|
1058
1110
|
}
|
|
1059
|
-
|
|
1060
1111
|
function getInvalidPathError(char, field, dest, path) {
|
|
1061
1112
|
return "Cannot include a '" + char + "' character in a manually specified " + ("`to." + field + "` field [" + JSON.stringify(path) + "]. Please separate it out to the ") + ("`to." + dest + "` field. Alternatively you may provide the full path as ") + "a string in <Link to=\"...\"> and the router will parse it for you.";
|
|
1062
1113
|
}
|
|
1114
|
+
|
|
1063
1115
|
/**
|
|
1064
1116
|
* @private
|
|
1065
1117
|
*
|
|
@@ -1083,22 +1135,18 @@ function getInvalidPathError(char, field, dest, path) {
|
|
|
1083
1135
|
* </Route>
|
|
1084
1136
|
* </Route>
|
|
1085
1137
|
*/
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
1138
|
function getPathContributingMatches(matches) {
|
|
1089
1139
|
return matches.filter((match, index) => index === 0 || match.route.path && match.route.path.length > 0);
|
|
1090
1140
|
}
|
|
1141
|
+
|
|
1091
1142
|
/**
|
|
1092
1143
|
* @private
|
|
1093
1144
|
*/
|
|
1094
|
-
|
|
1095
1145
|
function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
|
|
1096
1146
|
if (isPathRelative === void 0) {
|
|
1097
1147
|
isPathRelative = false;
|
|
1098
1148
|
}
|
|
1099
|
-
|
|
1100
1149
|
let to;
|
|
1101
|
-
|
|
1102
1150
|
if (typeof toArg === "string") {
|
|
1103
1151
|
to = parsePath(toArg);
|
|
1104
1152
|
} else {
|
|
@@ -1107,10 +1155,11 @@ function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
|
|
|
1107
1155
|
invariant(!to.pathname || !to.pathname.includes("#"), getInvalidPathError("#", "pathname", "hash", to));
|
|
1108
1156
|
invariant(!to.search || !to.search.includes("#"), getInvalidPathError("#", "search", "hash", to));
|
|
1109
1157
|
}
|
|
1110
|
-
|
|
1111
1158
|
let isEmptyPath = toArg === "" || to.pathname === "";
|
|
1112
1159
|
let toPathname = isEmptyPath ? "/" : to.pathname;
|
|
1113
|
-
let from;
|
|
1160
|
+
let from;
|
|
1161
|
+
|
|
1162
|
+
// Routing is relative to the current pathname if explicitly requested.
|
|
1114
1163
|
//
|
|
1115
1164
|
// If a pathname is explicitly provided in `to`, it should be relative to the
|
|
1116
1165
|
// route context. This is explained in `Note on `<Link to>` values` in our
|
|
@@ -1119,71 +1168,66 @@ function resolveTo(toArg, routePathnames, locationPathname, isPathRelative) {
|
|
|
1119
1168
|
// `to` values that do not provide a pathname. `to` can simply be a search or
|
|
1120
1169
|
// hash string, in which case we should assume that the navigation is relative
|
|
1121
1170
|
// to the current location's pathname and *not* the route pathname.
|
|
1122
|
-
|
|
1123
1171
|
if (isPathRelative || toPathname == null) {
|
|
1124
1172
|
from = locationPathname;
|
|
1125
1173
|
} else {
|
|
1126
1174
|
let routePathnameIndex = routePathnames.length - 1;
|
|
1127
|
-
|
|
1128
1175
|
if (toPathname.startsWith("..")) {
|
|
1129
|
-
let toSegments = toPathname.split("/");
|
|
1176
|
+
let toSegments = toPathname.split("/");
|
|
1177
|
+
|
|
1178
|
+
// Each leading .. segment means "go up one route" instead of "go up one
|
|
1130
1179
|
// URL segment". This is a key difference from how <a href> works and a
|
|
1131
1180
|
// major reason we call this a "to" value instead of a "href".
|
|
1132
|
-
|
|
1133
1181
|
while (toSegments[0] === "..") {
|
|
1134
1182
|
toSegments.shift();
|
|
1135
1183
|
routePathnameIndex -= 1;
|
|
1136
1184
|
}
|
|
1137
|
-
|
|
1138
1185
|
to.pathname = toSegments.join("/");
|
|
1139
|
-
}
|
|
1140
|
-
// the root / URL.
|
|
1141
|
-
|
|
1186
|
+
}
|
|
1142
1187
|
|
|
1188
|
+
// If there are more ".." segments than parent routes, resolve relative to
|
|
1189
|
+
// the root / URL.
|
|
1143
1190
|
from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
|
|
1144
1191
|
}
|
|
1192
|
+
let path = resolvePath(to, from);
|
|
1145
1193
|
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1194
|
+
// Ensure the pathname has a trailing slash if the original "to" had one
|
|
1195
|
+
let hasExplicitTrailingSlash = toPathname && toPathname !== "/" && toPathname.endsWith("/");
|
|
1196
|
+
// Or if this was a link to the current path which has a trailing slash
|
|
1150
1197
|
let hasCurrentTrailingSlash = (isEmptyPath || toPathname === ".") && locationPathname.endsWith("/");
|
|
1151
|
-
|
|
1152
1198
|
if (!path.pathname.endsWith("/") && (hasExplicitTrailingSlash || hasCurrentTrailingSlash)) {
|
|
1153
1199
|
path.pathname += "/";
|
|
1154
1200
|
}
|
|
1155
|
-
|
|
1156
1201
|
return path;
|
|
1157
1202
|
}
|
|
1203
|
+
|
|
1158
1204
|
/**
|
|
1159
1205
|
* @private
|
|
1160
1206
|
*/
|
|
1161
|
-
|
|
1162
1207
|
function getToPathname(to) {
|
|
1163
1208
|
// Empty strings should be treated the same as / paths
|
|
1164
1209
|
return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
|
|
1165
1210
|
}
|
|
1211
|
+
|
|
1166
1212
|
/**
|
|
1167
1213
|
* @private
|
|
1168
1214
|
*/
|
|
1169
|
-
|
|
1170
1215
|
const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
|
|
1216
|
+
|
|
1171
1217
|
/**
|
|
1172
1218
|
* @private
|
|
1173
1219
|
*/
|
|
1174
|
-
|
|
1175
1220
|
const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
|
|
1221
|
+
|
|
1176
1222
|
/**
|
|
1177
1223
|
* @private
|
|
1178
1224
|
*/
|
|
1179
|
-
|
|
1180
1225
|
const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
|
|
1226
|
+
|
|
1181
1227
|
/**
|
|
1182
1228
|
* @private
|
|
1183
1229
|
*/
|
|
1184
|
-
|
|
1185
1230
|
const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
|
|
1186
|
-
|
|
1187
1231
|
/**
|
|
1188
1232
|
* This is a shortcut for creating `application/json` responses. Converts `data`
|
|
1189
1233
|
* to JSON and sets the `Content-Type` header.
|
|
@@ -1192,16 +1236,13 @@ const json = function json(data, init) {
|
|
|
1192
1236
|
if (init === void 0) {
|
|
1193
1237
|
init = {};
|
|
1194
1238
|
}
|
|
1195
|
-
|
|
1196
1239
|
let responseInit = typeof init === "number" ? {
|
|
1197
1240
|
status: init
|
|
1198
1241
|
} : init;
|
|
1199
1242
|
let headers = new Headers(responseInit.headers);
|
|
1200
|
-
|
|
1201
1243
|
if (!headers.has("Content-Type")) {
|
|
1202
1244
|
headers.set("Content-Type", "application/json; charset=utf-8");
|
|
1203
1245
|
}
|
|
1204
|
-
|
|
1205
1246
|
return new Response(JSON.stringify(data), _extends({}, responseInit, {
|
|
1206
1247
|
headers
|
|
1207
1248
|
}));
|
|
@@ -1212,17 +1253,15 @@ class DeferredData {
|
|
|
1212
1253
|
this.pendingKeysSet = new Set();
|
|
1213
1254
|
this.subscribers = new Set();
|
|
1214
1255
|
this.deferredKeys = [];
|
|
1215
|
-
invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects");
|
|
1216
|
-
// cancellation
|
|
1256
|
+
invariant(data && typeof data === "object" && !Array.isArray(data), "defer() only accepts plain objects");
|
|
1217
1257
|
|
|
1258
|
+
// Set up an AbortController + Promise we can race against to exit early
|
|
1259
|
+
// cancellation
|
|
1218
1260
|
let reject;
|
|
1219
1261
|
this.abortPromise = new Promise((_, r) => reject = r);
|
|
1220
1262
|
this.controller = new AbortController();
|
|
1221
|
-
|
|
1222
1263
|
let onAbort = () => reject(new AbortedDeferredError("Deferred data aborted"));
|
|
1223
|
-
|
|
1224
1264
|
this.unlistenAbortSignal = () => this.controller.signal.removeEventListener("abort", onAbort);
|
|
1225
|
-
|
|
1226
1265
|
this.controller.signal.addEventListener("abort", onAbort);
|
|
1227
1266
|
this.data = Object.entries(data).reduce((acc, _ref) => {
|
|
1228
1267
|
let [key, value] = _ref;
|
|
@@ -1230,34 +1269,31 @@ class DeferredData {
|
|
|
1230
1269
|
[key]: this.trackPromise(key, value)
|
|
1231
1270
|
});
|
|
1232
1271
|
}, {});
|
|
1233
|
-
|
|
1234
1272
|
if (this.done) {
|
|
1235
1273
|
// All incoming values were resolved
|
|
1236
1274
|
this.unlistenAbortSignal();
|
|
1237
1275
|
}
|
|
1238
|
-
|
|
1239
1276
|
this.init = responseInit;
|
|
1240
1277
|
}
|
|
1241
|
-
|
|
1242
1278
|
trackPromise(key, value) {
|
|
1243
1279
|
if (!(value instanceof Promise)) {
|
|
1244
1280
|
return value;
|
|
1245
1281
|
}
|
|
1246
|
-
|
|
1247
1282
|
this.deferredKeys.push(key);
|
|
1248
|
-
this.pendingKeysSet.add(key);
|
|
1283
|
+
this.pendingKeysSet.add(key);
|
|
1284
|
+
|
|
1285
|
+
// We store a little wrapper promise that will be extended with
|
|
1249
1286
|
// _data/_error props upon resolve/reject
|
|
1287
|
+
let promise = Promise.race([value, this.abortPromise]).then(data => this.onSettle(promise, key, null, data), error => this.onSettle(promise, key, error));
|
|
1250
1288
|
|
|
1251
|
-
|
|
1289
|
+
// Register rejection listeners to avoid uncaught promise rejections on
|
|
1252
1290
|
// errors or aborted deferred values
|
|
1253
|
-
|
|
1254
1291
|
promise.catch(() => {});
|
|
1255
1292
|
Object.defineProperty(promise, "_tracked", {
|
|
1256
1293
|
get: () => true
|
|
1257
1294
|
});
|
|
1258
1295
|
return promise;
|
|
1259
1296
|
}
|
|
1260
|
-
|
|
1261
1297
|
onSettle(promise, key, error, data) {
|
|
1262
1298
|
if (this.controller.signal.aborted && error instanceof AbortedDeferredError) {
|
|
1263
1299
|
this.unlistenAbortSignal();
|
|
@@ -1266,14 +1302,11 @@ class DeferredData {
|
|
|
1266
1302
|
});
|
|
1267
1303
|
return Promise.reject(error);
|
|
1268
1304
|
}
|
|
1269
|
-
|
|
1270
1305
|
this.pendingKeysSet.delete(key);
|
|
1271
|
-
|
|
1272
1306
|
if (this.done) {
|
|
1273
1307
|
// Nothing left to abort!
|
|
1274
1308
|
this.unlistenAbortSignal();
|
|
1275
1309
|
}
|
|
1276
|
-
|
|
1277
1310
|
if (error) {
|
|
1278
1311
|
Object.defineProperty(promise, "_error", {
|
|
1279
1312
|
get: () => error
|
|
@@ -1281,54 +1314,43 @@ class DeferredData {
|
|
|
1281
1314
|
this.emit(false, key);
|
|
1282
1315
|
return Promise.reject(error);
|
|
1283
1316
|
}
|
|
1284
|
-
|
|
1285
1317
|
Object.defineProperty(promise, "_data", {
|
|
1286
1318
|
get: () => data
|
|
1287
1319
|
});
|
|
1288
1320
|
this.emit(false, key);
|
|
1289
1321
|
return data;
|
|
1290
1322
|
}
|
|
1291
|
-
|
|
1292
1323
|
emit(aborted, settledKey) {
|
|
1293
1324
|
this.subscribers.forEach(subscriber => subscriber(aborted, settledKey));
|
|
1294
1325
|
}
|
|
1295
|
-
|
|
1296
1326
|
subscribe(fn) {
|
|
1297
1327
|
this.subscribers.add(fn);
|
|
1298
1328
|
return () => this.subscribers.delete(fn);
|
|
1299
1329
|
}
|
|
1300
|
-
|
|
1301
1330
|
cancel() {
|
|
1302
1331
|
this.controller.abort();
|
|
1303
1332
|
this.pendingKeysSet.forEach((v, k) => this.pendingKeysSet.delete(k));
|
|
1304
1333
|
this.emit(true);
|
|
1305
1334
|
}
|
|
1306
|
-
|
|
1307
1335
|
async resolveData(signal) {
|
|
1308
1336
|
let aborted = false;
|
|
1309
|
-
|
|
1310
1337
|
if (!this.done) {
|
|
1311
1338
|
let onAbort = () => this.cancel();
|
|
1312
|
-
|
|
1313
1339
|
signal.addEventListener("abort", onAbort);
|
|
1314
1340
|
aborted = await new Promise(resolve => {
|
|
1315
1341
|
this.subscribe(aborted => {
|
|
1316
1342
|
signal.removeEventListener("abort", onAbort);
|
|
1317
|
-
|
|
1318
1343
|
if (aborted || this.done) {
|
|
1319
1344
|
resolve(aborted);
|
|
1320
1345
|
}
|
|
1321
1346
|
});
|
|
1322
1347
|
});
|
|
1323
1348
|
}
|
|
1324
|
-
|
|
1325
1349
|
return aborted;
|
|
1326
1350
|
}
|
|
1327
|
-
|
|
1328
1351
|
get done() {
|
|
1329
1352
|
return this.pendingKeysSet.size === 0;
|
|
1330
1353
|
}
|
|
1331
|
-
|
|
1332
1354
|
get unwrappedData() {
|
|
1333
1355
|
invariant(this.data !== null && this.done, "Can only unwrap data on initialized and settled deferreds");
|
|
1334
1356
|
return Object.entries(this.data).reduce((acc, _ref2) => {
|
|
@@ -1338,40 +1360,31 @@ class DeferredData {
|
|
|
1338
1360
|
});
|
|
1339
1361
|
}, {});
|
|
1340
1362
|
}
|
|
1341
|
-
|
|
1342
1363
|
get pendingKeys() {
|
|
1343
1364
|
return Array.from(this.pendingKeysSet);
|
|
1344
1365
|
}
|
|
1345
|
-
|
|
1346
1366
|
}
|
|
1347
|
-
|
|
1348
1367
|
function isTrackedPromise(value) {
|
|
1349
1368
|
return value instanceof Promise && value._tracked === true;
|
|
1350
1369
|
}
|
|
1351
|
-
|
|
1352
1370
|
function unwrapTrackedPromise(value) {
|
|
1353
1371
|
if (!isTrackedPromise(value)) {
|
|
1354
1372
|
return value;
|
|
1355
1373
|
}
|
|
1356
|
-
|
|
1357
1374
|
if (value._error) {
|
|
1358
1375
|
throw value._error;
|
|
1359
1376
|
}
|
|
1360
|
-
|
|
1361
1377
|
return value._data;
|
|
1362
1378
|
}
|
|
1363
|
-
|
|
1364
1379
|
const defer = function defer(data, init) {
|
|
1365
1380
|
if (init === void 0) {
|
|
1366
1381
|
init = {};
|
|
1367
1382
|
}
|
|
1368
|
-
|
|
1369
1383
|
let responseInit = typeof init === "number" ? {
|
|
1370
1384
|
status: init
|
|
1371
1385
|
} : init;
|
|
1372
1386
|
return new DeferredData(data, responseInit);
|
|
1373
1387
|
};
|
|
1374
|
-
|
|
1375
1388
|
/**
|
|
1376
1389
|
* A redirect response. Sets the status code and the `Location` header.
|
|
1377
1390
|
* Defaults to "302 Found".
|
|
@@ -1380,9 +1393,7 @@ const redirect = function redirect(url, init) {
|
|
|
1380
1393
|
if (init === void 0) {
|
|
1381
1394
|
init = 302;
|
|
1382
1395
|
}
|
|
1383
|
-
|
|
1384
1396
|
let responseInit = init;
|
|
1385
|
-
|
|
1386
1397
|
if (typeof responseInit === "number") {
|
|
1387
1398
|
responseInit = {
|
|
1388
1399
|
status: responseInit
|
|
@@ -1390,28 +1401,25 @@ const redirect = function redirect(url, init) {
|
|
|
1390
1401
|
} else if (typeof responseInit.status === "undefined") {
|
|
1391
1402
|
responseInit.status = 302;
|
|
1392
1403
|
}
|
|
1393
|
-
|
|
1394
1404
|
let headers = new Headers(responseInit.headers);
|
|
1395
1405
|
headers.set("Location", url);
|
|
1396
1406
|
return new Response(null, _extends({}, responseInit, {
|
|
1397
1407
|
headers
|
|
1398
1408
|
}));
|
|
1399
1409
|
};
|
|
1410
|
+
|
|
1400
1411
|
/**
|
|
1401
1412
|
* @private
|
|
1402
1413
|
* Utility class we use to hold auto-unwrapped 4xx/5xx Response bodies
|
|
1403
1414
|
*/
|
|
1404
|
-
|
|
1405
1415
|
class ErrorResponse {
|
|
1406
1416
|
constructor(status, statusText, data, internal) {
|
|
1407
1417
|
if (internal === void 0) {
|
|
1408
1418
|
internal = false;
|
|
1409
1419
|
}
|
|
1410
|
-
|
|
1411
1420
|
this.status = status;
|
|
1412
1421
|
this.statusText = statusText || "";
|
|
1413
1422
|
this.internal = internal;
|
|
1414
|
-
|
|
1415
1423
|
if (data instanceof Error) {
|
|
1416
1424
|
this.data = data.toString();
|
|
1417
1425
|
this.error = data;
|
|
@@ -1419,24 +1427,82 @@ class ErrorResponse {
|
|
|
1419
1427
|
this.data = data;
|
|
1420
1428
|
}
|
|
1421
1429
|
}
|
|
1422
|
-
|
|
1423
1430
|
}
|
|
1431
|
+
|
|
1424
1432
|
/**
|
|
1425
1433
|
* Check if the given error is an ErrorResponse generated from a 4xx/5xx
|
|
1426
1434
|
* Response thrown from an action/loader
|
|
1427
1435
|
*/
|
|
1428
|
-
|
|
1429
1436
|
function isRouteErrorResponse(error) {
|
|
1430
1437
|
return error != null && typeof error.status === "number" && typeof error.statusText === "string" && typeof error.internal === "boolean" && "data" in error;
|
|
1431
1438
|
}
|
|
1432
1439
|
|
|
1440
|
+
////////////////////////////////////////////////////////////////////////////////
|
|
1433
1441
|
//#region Types and Constants
|
|
1434
1442
|
////////////////////////////////////////////////////////////////////////////////
|
|
1435
1443
|
|
|
1436
1444
|
/**
|
|
1437
1445
|
* A Router instance manages all navigation and data loading/mutations
|
|
1438
1446
|
*/
|
|
1439
|
-
|
|
1447
|
+
/**
|
|
1448
|
+
* State maintained internally by the router. During a navigation, all states
|
|
1449
|
+
* reflect the the "old" location unless otherwise noted.
|
|
1450
|
+
*/
|
|
1451
|
+
/**
|
|
1452
|
+
* Data that can be passed into hydrate a Router from SSR
|
|
1453
|
+
*/
|
|
1454
|
+
/**
|
|
1455
|
+
* Future flags to toggle new feature behavior
|
|
1456
|
+
*/
|
|
1457
|
+
/**
|
|
1458
|
+
* Initialization options for createRouter
|
|
1459
|
+
*/
|
|
1460
|
+
/**
|
|
1461
|
+
* State returned from a server-side query() call
|
|
1462
|
+
*/
|
|
1463
|
+
/**
|
|
1464
|
+
* A StaticHandler instance manages a singular SSR navigation/fetch event
|
|
1465
|
+
*/
|
|
1466
|
+
/**
|
|
1467
|
+
* Subscriber function signature for changes to router state
|
|
1468
|
+
*/
|
|
1469
|
+
/**
|
|
1470
|
+
* Function signature for determining the key to be used in scroll restoration
|
|
1471
|
+
* for a given location
|
|
1472
|
+
*/
|
|
1473
|
+
/**
|
|
1474
|
+
* Function signature for determining the current scroll position
|
|
1475
|
+
*/
|
|
1476
|
+
/**
|
|
1477
|
+
* Options for a navigate() call for a Link navigation
|
|
1478
|
+
*/
|
|
1479
|
+
/**
|
|
1480
|
+
* Options for a navigate() call for a Form navigation
|
|
1481
|
+
*/
|
|
1482
|
+
/**
|
|
1483
|
+
* Options to pass to navigate() for either a Link or Form navigation
|
|
1484
|
+
*/
|
|
1485
|
+
/**
|
|
1486
|
+
* Options to pass to fetch()
|
|
1487
|
+
*/
|
|
1488
|
+
/**
|
|
1489
|
+
* Potential states for state.navigation
|
|
1490
|
+
*/
|
|
1491
|
+
/**
|
|
1492
|
+
* Potential states for fetchers
|
|
1493
|
+
*/
|
|
1494
|
+
/**
|
|
1495
|
+
* Cached info for active fetcher.load() instances so they can participate
|
|
1496
|
+
* in revalidation
|
|
1497
|
+
*/
|
|
1498
|
+
/**
|
|
1499
|
+
* Identified fetcher.load() calls that need to be revalidated
|
|
1500
|
+
*/
|
|
1501
|
+
/**
|
|
1502
|
+
* Wrapper object to allow us to throw any response out from callLoaderOrAction
|
|
1503
|
+
* for queryRouter while preserving whether or not it was thrown or returned
|
|
1504
|
+
* from the loader/action
|
|
1505
|
+
*/
|
|
1440
1506
|
const validMutationMethodsArr = ["post", "put", "patch", "delete"];
|
|
1441
1507
|
const validMutationMethods = new Set(validMutationMethodsArr);
|
|
1442
1508
|
const validRequestMethodsArr = ["get", ...validMutationMethodsArr];
|
|
@@ -1466,12 +1532,12 @@ const IDLE_BLOCKER = {
|
|
|
1466
1532
|
location: undefined
|
|
1467
1533
|
};
|
|
1468
1534
|
const ABSOLUTE_URL_REGEX = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
|
|
1469
|
-
const isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined" && typeof window.document.createElement !== "undefined";
|
|
1470
|
-
const isServer = !isBrowser;
|
|
1471
|
-
|
|
1472
1535
|
const defaultMapRouteProperties = route => ({
|
|
1473
1536
|
hasErrorBoundary: Boolean(route.hasErrorBoundary)
|
|
1474
|
-
});
|
|
1537
|
+
});
|
|
1538
|
+
|
|
1539
|
+
//#endregion
|
|
1540
|
+
|
|
1475
1541
|
////////////////////////////////////////////////////////////////////////////////
|
|
1476
1542
|
//#region createRouter
|
|
1477
1543
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -1479,57 +1545,54 @@ const defaultMapRouteProperties = route => ({
|
|
|
1479
1545
|
/**
|
|
1480
1546
|
* Create a router and listen to history POP navigations
|
|
1481
1547
|
*/
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
1548
|
function createRouter(init) {
|
|
1549
|
+
const routerWindow = init.window ? init.window : typeof window !== "undefined" ? window : undefined;
|
|
1550
|
+
const isBrowser = typeof routerWindow !== "undefined" && typeof routerWindow.document !== "undefined" && typeof routerWindow.document.createElement !== "undefined";
|
|
1551
|
+
const isServer = !isBrowser;
|
|
1485
1552
|
invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
|
|
1486
1553
|
let mapRouteProperties;
|
|
1487
|
-
|
|
1488
1554
|
if (init.mapRouteProperties) {
|
|
1489
1555
|
mapRouteProperties = init.mapRouteProperties;
|
|
1490
1556
|
} else if (init.detectErrorBoundary) {
|
|
1491
1557
|
// If they are still using the deprecated version, wrap it with the new API
|
|
1492
1558
|
let detectErrorBoundary = init.detectErrorBoundary;
|
|
1493
|
-
|
|
1494
1559
|
mapRouteProperties = route => ({
|
|
1495
1560
|
hasErrorBoundary: detectErrorBoundary(route)
|
|
1496
1561
|
});
|
|
1497
1562
|
} else {
|
|
1498
1563
|
mapRouteProperties = defaultMapRouteProperties;
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
let manifest = {}; // Routes in tree format for matching
|
|
1564
|
+
}
|
|
1503
1565
|
|
|
1566
|
+
// Routes keyed by ID
|
|
1567
|
+
let manifest = {};
|
|
1568
|
+
// Routes in tree format for matching
|
|
1504
1569
|
let dataRoutes = convertRoutesToDataRoutes(init.routes, mapRouteProperties, undefined, manifest);
|
|
1505
1570
|
let inFlightDataRoutes;
|
|
1506
|
-
let basename = init.basename || "/";
|
|
1507
|
-
|
|
1571
|
+
let basename = init.basename || "/";
|
|
1572
|
+
// Config driven behavior flags
|
|
1508
1573
|
let future = _extends({
|
|
1509
1574
|
v7_normalizeFormMethod: false,
|
|
1510
1575
|
v7_prependBasename: false
|
|
1511
|
-
}, init.future);
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1576
|
+
}, init.future);
|
|
1577
|
+
// Cleanup function for history
|
|
1578
|
+
let unlistenHistory = null;
|
|
1579
|
+
// Externally-provided functions to call on all state changes
|
|
1580
|
+
let subscribers = new Set();
|
|
1581
|
+
// Externally-provided object to hold scroll restoration locations during routing
|
|
1582
|
+
let savedScrollPositions = null;
|
|
1583
|
+
// Externally-provided function to get scroll restoration keys
|
|
1584
|
+
let getScrollRestorationKey = null;
|
|
1585
|
+
// Externally-provided function to get current scroll position
|
|
1586
|
+
let getScrollPosition = null;
|
|
1587
|
+
// One-time flag to control the initial hydration scroll restoration. Because
|
|
1523
1588
|
// we don't get the saved positions from <ScrollRestoration /> until _after_
|
|
1524
1589
|
// the initial render, we need to manually trigger a separate updateState to
|
|
1525
1590
|
// send along the restoreScrollPosition
|
|
1526
1591
|
// Set to true if we have `hydrationData` since we assume we were SSR'd and that
|
|
1527
1592
|
// SSR did the initial scroll restoration.
|
|
1528
|
-
|
|
1529
1593
|
let initialScrollRestored = init.hydrationData != null;
|
|
1530
1594
|
let initialMatches = matchRoutes(dataRoutes, init.history.location, basename);
|
|
1531
1595
|
let initialErrors = null;
|
|
1532
|
-
|
|
1533
1596
|
if (initialMatches == null) {
|
|
1534
1597
|
// If we do not match a user-provided-route, fall back to the root
|
|
1535
1598
|
// to allow the error boundary to take over
|
|
@@ -1545,10 +1608,11 @@ function createRouter(init) {
|
|
|
1545
1608
|
[route.id]: error
|
|
1546
1609
|
};
|
|
1547
1610
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1611
|
+
let initialized =
|
|
1612
|
+
// All initialMatches need to be loaded before we're ready. If we have lazy
|
|
1550
1613
|
// functions around still then we'll need to run them in initialize()
|
|
1551
|
-
!initialMatches.some(m => m.route.lazy) && (
|
|
1614
|
+
!initialMatches.some(m => m.route.lazy) && (
|
|
1615
|
+
// And we have to either have no loaders or have been provided hydrationData
|
|
1552
1616
|
!initialMatches.some(m => m.route.loader) || init.hydrationData != null);
|
|
1553
1617
|
let router;
|
|
1554
1618
|
let state = {
|
|
@@ -1566,57 +1630,74 @@ function createRouter(init) {
|
|
|
1566
1630
|
errors: init.hydrationData && init.hydrationData.errors || initialErrors,
|
|
1567
1631
|
fetchers: new Map(),
|
|
1568
1632
|
blockers: new Map()
|
|
1569
|
-
};
|
|
1633
|
+
};
|
|
1634
|
+
|
|
1635
|
+
// -- Stateful internal variables to manage navigations --
|
|
1570
1636
|
// Current navigation in progress (to be committed in completeNavigation)
|
|
1637
|
+
let pendingAction = exports.Action.Pop;
|
|
1571
1638
|
|
|
1572
|
-
|
|
1639
|
+
// Should the current navigation prevent the scroll reset if scroll cannot
|
|
1573
1640
|
// be restored?
|
|
1641
|
+
let pendingPreventScrollReset = false;
|
|
1574
1642
|
|
|
1575
|
-
|
|
1643
|
+
// AbortController for the active navigation
|
|
1644
|
+
let pendingNavigationController;
|
|
1576
1645
|
|
|
1577
|
-
|
|
1646
|
+
// We use this to avoid touching history in completeNavigation if a
|
|
1578
1647
|
// revalidation is entirely uninterrupted
|
|
1648
|
+
let isUninterruptedRevalidation = false;
|
|
1579
1649
|
|
|
1580
|
-
|
|
1650
|
+
// Use this internal flag to force revalidation of all loaders:
|
|
1581
1651
|
// - submissions (completed or interrupted)
|
|
1582
1652
|
// - useRevalidator()
|
|
1583
1653
|
// - X-Remix-Revalidate (from redirect)
|
|
1654
|
+
let isRevalidationRequired = false;
|
|
1584
1655
|
|
|
1585
|
-
|
|
1656
|
+
// Use this internal array to capture routes that require revalidation due
|
|
1586
1657
|
// to a cancelled deferred on action submission
|
|
1658
|
+
let cancelledDeferredRoutes = [];
|
|
1587
1659
|
|
|
1588
|
-
|
|
1660
|
+
// Use this internal array to capture fetcher loads that were cancelled by an
|
|
1589
1661
|
// action navigation and require revalidation
|
|
1662
|
+
let cancelledFetcherLoads = [];
|
|
1590
1663
|
|
|
1591
|
-
|
|
1664
|
+
// AbortControllers for any in-flight fetchers
|
|
1665
|
+
let fetchControllers = new Map();
|
|
1592
1666
|
|
|
1593
|
-
|
|
1667
|
+
// Track loads based on the order in which they started
|
|
1668
|
+
let incrementingLoadId = 0;
|
|
1594
1669
|
|
|
1595
|
-
|
|
1670
|
+
// Track the outstanding pending navigation data load to be compared against
|
|
1596
1671
|
// the globally incrementing load when a fetcher load lands after a completed
|
|
1597
1672
|
// navigation
|
|
1673
|
+
let pendingNavigationLoadId = -1;
|
|
1598
1674
|
|
|
1599
|
-
|
|
1675
|
+
// Fetchers that triggered data reloads as a result of their actions
|
|
1676
|
+
let fetchReloadIds = new Map();
|
|
1600
1677
|
|
|
1601
|
-
|
|
1678
|
+
// Fetchers that triggered redirect navigations
|
|
1679
|
+
let fetchRedirectIds = new Set();
|
|
1602
1680
|
|
|
1603
|
-
|
|
1681
|
+
// Most recent href/match for fetcher.load calls for fetchers
|
|
1682
|
+
let fetchLoadMatches = new Map();
|
|
1604
1683
|
|
|
1605
|
-
|
|
1684
|
+
// Store DeferredData instances for active route matches. When a
|
|
1606
1685
|
// route loader returns defer() we stick one in here. Then, when a nested
|
|
1607
1686
|
// promise resolves we update loaderData. If a new navigation starts we
|
|
1608
1687
|
// cancel active deferreds for eliminated routes.
|
|
1688
|
+
let activeDeferreds = new Map();
|
|
1609
1689
|
|
|
1610
|
-
|
|
1690
|
+
// Store blocker functions in a separate Map outside of router state since
|
|
1611
1691
|
// we don't need to update UI state if they change
|
|
1692
|
+
let blockerFunctions = new Map();
|
|
1612
1693
|
|
|
1613
|
-
|
|
1694
|
+
// Flag to ignore the next history update, so we can revert the URL change on
|
|
1614
1695
|
// a POP navigation that was blocked by the user without touching router state
|
|
1696
|
+
let ignoreNextHistoryUpdate = false;
|
|
1615
1697
|
|
|
1616
|
-
|
|
1698
|
+
// Initialize the router, all side effects should be kicked off from here.
|
|
1617
1699
|
// Implemented as a Fluent API for ease of:
|
|
1618
1700
|
// let router = createRouter(init).initialize();
|
|
1619
|
-
|
|
1620
1701
|
function initialize() {
|
|
1621
1702
|
// If history informs us of a POP navigation, start the navigation but do not update
|
|
1622
1703
|
// state. We'll update our own state once the navigation completes
|
|
@@ -1626,98 +1707,90 @@ function createRouter(init) {
|
|
|
1626
1707
|
location,
|
|
1627
1708
|
delta
|
|
1628
1709
|
} = _ref;
|
|
1629
|
-
|
|
1630
1710
|
// Ignore this event if it was just us resetting the URL from a
|
|
1631
1711
|
// blocked POP navigation
|
|
1632
1712
|
if (ignoreNextHistoryUpdate) {
|
|
1633
1713
|
ignoreNextHistoryUpdate = false;
|
|
1634
1714
|
return;
|
|
1635
1715
|
}
|
|
1636
|
-
|
|
1637
1716
|
warning(blockerFunctions.size === 0 || delta != null, "You are trying to use a blocker on a POP navigation to a location " + "that was not created by @remix-run/router. This will fail silently in " + "production. This can happen if you are navigating outside the router " + "via `window.history.pushState`/`window.location.hash` instead of using " + "router navigation APIs. This can also happen if you are using " + "createHashRouter and the user manually changes the URL.");
|
|
1638
1717
|
let blockerKey = shouldBlockNavigation({
|
|
1639
1718
|
currentLocation: state.location,
|
|
1640
1719
|
nextLocation: location,
|
|
1641
1720
|
historyAction
|
|
1642
1721
|
});
|
|
1643
|
-
|
|
1644
1722
|
if (blockerKey && delta != null) {
|
|
1645
1723
|
// Restore the URL to match the current UI, but don't update router state
|
|
1646
1724
|
ignoreNextHistoryUpdate = true;
|
|
1647
|
-
init.history.go(delta * -1);
|
|
1725
|
+
init.history.go(delta * -1);
|
|
1648
1726
|
|
|
1727
|
+
// Put the blocker into a blocked state
|
|
1649
1728
|
updateBlocker(blockerKey, {
|
|
1650
1729
|
state: "blocked",
|
|
1651
1730
|
location,
|
|
1652
|
-
|
|
1653
1731
|
proceed() {
|
|
1654
1732
|
updateBlocker(blockerKey, {
|
|
1655
1733
|
state: "proceeding",
|
|
1656
1734
|
proceed: undefined,
|
|
1657
1735
|
reset: undefined,
|
|
1658
1736
|
location
|
|
1659
|
-
});
|
|
1660
|
-
|
|
1737
|
+
});
|
|
1738
|
+
// Re-do the same POP navigation we just blocked
|
|
1661
1739
|
init.history.go(delta);
|
|
1662
1740
|
},
|
|
1663
|
-
|
|
1664
1741
|
reset() {
|
|
1665
1742
|
deleteBlocker(blockerKey);
|
|
1666
1743
|
updateState({
|
|
1667
1744
|
blockers: new Map(router.state.blockers)
|
|
1668
1745
|
});
|
|
1669
1746
|
}
|
|
1670
|
-
|
|
1671
1747
|
});
|
|
1672
1748
|
return;
|
|
1673
1749
|
}
|
|
1674
|
-
|
|
1675
1750
|
return startNavigation(historyAction, location);
|
|
1676
|
-
});
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
// Kick off initial data load if needed. Use Pop to avoid modifying history
|
|
1677
1754
|
// Note we don't do any handling of lazy here. For SPA's it'll get handled
|
|
1678
1755
|
// in the normal navigation flow. For SSR it's expected that lazy modules are
|
|
1679
1756
|
// resolved prior to router creation since we can't go into a fallbackElement
|
|
1680
1757
|
// UI for SSR'd apps
|
|
1681
|
-
|
|
1682
1758
|
if (!state.initialized) {
|
|
1683
1759
|
startNavigation(exports.Action.Pop, state.location);
|
|
1684
1760
|
}
|
|
1685
|
-
|
|
1686
1761
|
return router;
|
|
1687
|
-
}
|
|
1688
|
-
|
|
1762
|
+
}
|
|
1689
1763
|
|
|
1764
|
+
// Clean up a router and it's side effects
|
|
1690
1765
|
function dispose() {
|
|
1691
1766
|
if (unlistenHistory) {
|
|
1692
1767
|
unlistenHistory();
|
|
1693
1768
|
}
|
|
1694
|
-
|
|
1695
1769
|
subscribers.clear();
|
|
1696
1770
|
pendingNavigationController && pendingNavigationController.abort();
|
|
1697
1771
|
state.fetchers.forEach((_, key) => deleteFetcher(key));
|
|
1698
1772
|
state.blockers.forEach((_, key) => deleteBlocker(key));
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1773
|
+
}
|
|
1701
1774
|
|
|
1775
|
+
// Subscribe to state updates for the router
|
|
1702
1776
|
function subscribe(fn) {
|
|
1703
1777
|
subscribers.add(fn);
|
|
1704
1778
|
return () => subscribers.delete(fn);
|
|
1705
|
-
}
|
|
1706
|
-
|
|
1779
|
+
}
|
|
1707
1780
|
|
|
1781
|
+
// Update our state and notify the calling context of the change
|
|
1708
1782
|
function updateState(newState) {
|
|
1709
1783
|
state = _extends({}, state, newState);
|
|
1710
1784
|
subscribers.forEach(subscriber => subscriber(state));
|
|
1711
|
-
}
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
// Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
|
|
1712
1788
|
// and setting state.[historyAction/location/matches] to the new route.
|
|
1713
1789
|
// - Location is a required param
|
|
1714
1790
|
// - Navigation will always be set to IDLE_NAVIGATION
|
|
1715
1791
|
// - Can pass any other state in newState
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
1792
|
function completeNavigation(location, newState) {
|
|
1719
1793
|
var _location$state, _location$state2;
|
|
1720
|
-
|
|
1721
1794
|
// Deduce if we're in a loading/actionReload state:
|
|
1722
1795
|
// - We have committed actionData in the store
|
|
1723
1796
|
// - The current navigation was a mutation submission
|
|
@@ -1725,7 +1798,6 @@ function createRouter(init) {
|
|
|
1725
1798
|
// - The location being loaded is not the result of a redirect
|
|
1726
1799
|
let isActionReload = state.actionData != null && state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && state.navigation.state === "loading" && ((_location$state = location.state) == null ? void 0 : _location$state._isRedirect) !== true;
|
|
1727
1800
|
let actionData;
|
|
1728
|
-
|
|
1729
1801
|
if (newState.actionData) {
|
|
1730
1802
|
if (Object.keys(newState.actionData).length > 0) {
|
|
1731
1803
|
actionData = newState.actionData;
|
|
@@ -1739,25 +1811,24 @@ function createRouter(init) {
|
|
|
1739
1811
|
} else {
|
|
1740
1812
|
// Clear actionData on any other completed navigations
|
|
1741
1813
|
actionData = null;
|
|
1742
|
-
}
|
|
1814
|
+
}
|
|
1743
1815
|
|
|
1816
|
+
// Always preserve any existing loaderData from re-used routes
|
|
1817
|
+
let loaderData = newState.loaderData ? mergeLoaderData(state.loaderData, newState.loaderData, newState.matches || [], newState.errors) : state.loaderData;
|
|
1744
1818
|
|
|
1745
|
-
|
|
1819
|
+
// On a successful navigation we can assume we got through all blockers
|
|
1746
1820
|
// so we can start fresh
|
|
1747
|
-
|
|
1748
1821
|
for (let [key] of blockerFunctions) {
|
|
1749
1822
|
deleteBlocker(key);
|
|
1750
|
-
}
|
|
1751
|
-
// submission navigations unless they redirect
|
|
1752
|
-
|
|
1823
|
+
}
|
|
1753
1824
|
|
|
1825
|
+
// Always respect the user flag. Otherwise don't reset on mutation
|
|
1826
|
+
// submission navigations unless they redirect
|
|
1754
1827
|
let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && ((_location$state2 = location.state) == null ? void 0 : _location$state2._isRedirect) !== true;
|
|
1755
|
-
|
|
1756
1828
|
if (inFlightDataRoutes) {
|
|
1757
1829
|
dataRoutes = inFlightDataRoutes;
|
|
1758
1830
|
inFlightDataRoutes = undefined;
|
|
1759
1831
|
}
|
|
1760
|
-
|
|
1761
1832
|
updateState(_extends({}, newState, {
|
|
1762
1833
|
// matches, errors, fetchers go through as-is
|
|
1763
1834
|
actionData,
|
|
@@ -1771,30 +1842,28 @@ function createRouter(init) {
|
|
|
1771
1842
|
preventScrollReset,
|
|
1772
1843
|
blockers: new Map(state.blockers)
|
|
1773
1844
|
}));
|
|
1774
|
-
|
|
1775
1845
|
if (isUninterruptedRevalidation) ; else if (pendingAction === exports.Action.Pop) ; else if (pendingAction === exports.Action.Push) {
|
|
1776
1846
|
init.history.push(location, location.state);
|
|
1777
1847
|
} else if (pendingAction === exports.Action.Replace) {
|
|
1778
1848
|
init.history.replace(location, location.state);
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1849
|
+
}
|
|
1781
1850
|
|
|
1851
|
+
// Reset stateful navigation vars
|
|
1782
1852
|
pendingAction = exports.Action.Pop;
|
|
1783
1853
|
pendingPreventScrollReset = false;
|
|
1784
1854
|
isUninterruptedRevalidation = false;
|
|
1785
1855
|
isRevalidationRequired = false;
|
|
1786
1856
|
cancelledDeferredRoutes = [];
|
|
1787
1857
|
cancelledFetcherLoads = [];
|
|
1788
|
-
}
|
|
1789
|
-
// replace with an optional submission
|
|
1790
|
-
|
|
1858
|
+
}
|
|
1791
1859
|
|
|
1860
|
+
// Trigger a navigation event, which can either be a numerical POP or a PUSH
|
|
1861
|
+
// replace with an optional submission
|
|
1792
1862
|
async function navigate(to, opts) {
|
|
1793
1863
|
if (typeof to === "number") {
|
|
1794
1864
|
init.history.go(to);
|
|
1795
1865
|
return;
|
|
1796
1866
|
}
|
|
1797
|
-
|
|
1798
1867
|
let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, to, opts == null ? void 0 : opts.fromRouteId, opts == null ? void 0 : opts.relative);
|
|
1799
1868
|
let {
|
|
1800
1869
|
path,
|
|
@@ -1802,16 +1871,16 @@ function createRouter(init) {
|
|
|
1802
1871
|
error
|
|
1803
1872
|
} = normalizeNavigateOptions(future.v7_normalizeFormMethod, false, normalizedPath, opts);
|
|
1804
1873
|
let currentLocation = state.location;
|
|
1805
|
-
let nextLocation = createLocation(state.location, path, opts && opts.state);
|
|
1874
|
+
let nextLocation = createLocation(state.location, path, opts && opts.state);
|
|
1875
|
+
|
|
1876
|
+
// When using navigate as a PUSH/REPLACE we aren't reading an already-encoded
|
|
1806
1877
|
// URL from window.location, so we need to encode it here so the behavior
|
|
1807
1878
|
// remains the same as POP and non-data-router usages. new URL() does all
|
|
1808
1879
|
// the same encoding we'd get from a history.pushState/window.location read
|
|
1809
1880
|
// without having to touch history
|
|
1810
|
-
|
|
1811
1881
|
nextLocation = _extends({}, nextLocation, init.history.encodeLocation(nextLocation));
|
|
1812
1882
|
let userReplace = opts && opts.replace != null ? opts.replace : undefined;
|
|
1813
1883
|
let historyAction = exports.Action.Push;
|
|
1814
|
-
|
|
1815
1884
|
if (userReplace === true) {
|
|
1816
1885
|
historyAction = exports.Action.Replace;
|
|
1817
1886
|
} else if (userReplace === false) ; else if (submission != null && isMutationMethod(submission.formMethod) && submission.formAction === state.location.pathname + state.location.search) {
|
|
@@ -1821,42 +1890,36 @@ function createRouter(init) {
|
|
|
1821
1890
|
// action/loader this will be ignored and the redirect will be a PUSH
|
|
1822
1891
|
historyAction = exports.Action.Replace;
|
|
1823
1892
|
}
|
|
1824
|
-
|
|
1825
1893
|
let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
|
|
1826
1894
|
let blockerKey = shouldBlockNavigation({
|
|
1827
1895
|
currentLocation,
|
|
1828
1896
|
nextLocation,
|
|
1829
1897
|
historyAction
|
|
1830
1898
|
});
|
|
1831
|
-
|
|
1832
1899
|
if (blockerKey) {
|
|
1833
1900
|
// Put the blocker into a blocked state
|
|
1834
1901
|
updateBlocker(blockerKey, {
|
|
1835
1902
|
state: "blocked",
|
|
1836
1903
|
location: nextLocation,
|
|
1837
|
-
|
|
1838
1904
|
proceed() {
|
|
1839
1905
|
updateBlocker(blockerKey, {
|
|
1840
1906
|
state: "proceeding",
|
|
1841
1907
|
proceed: undefined,
|
|
1842
1908
|
reset: undefined,
|
|
1843
1909
|
location: nextLocation
|
|
1844
|
-
});
|
|
1845
|
-
|
|
1910
|
+
});
|
|
1911
|
+
// Send the same navigation through
|
|
1846
1912
|
navigate(to, opts);
|
|
1847
1913
|
},
|
|
1848
|
-
|
|
1849
1914
|
reset() {
|
|
1850
1915
|
deleteBlocker(blockerKey);
|
|
1851
1916
|
updateState({
|
|
1852
1917
|
blockers: new Map(state.blockers)
|
|
1853
1918
|
});
|
|
1854
1919
|
}
|
|
1855
|
-
|
|
1856
1920
|
});
|
|
1857
1921
|
return;
|
|
1858
1922
|
}
|
|
1859
|
-
|
|
1860
1923
|
return await startNavigation(historyAction, nextLocation, {
|
|
1861
1924
|
submission,
|
|
1862
1925
|
// Send through the formData serialization error if we have one so we can
|
|
@@ -1865,43 +1928,44 @@ function createRouter(init) {
|
|
|
1865
1928
|
preventScrollReset,
|
|
1866
1929
|
replace: opts && opts.replace
|
|
1867
1930
|
});
|
|
1868
|
-
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// Revalidate all current loaders. If a navigation is in progress or if this
|
|
1869
1934
|
// is interrupted by a navigation, allow this to "succeed" by calling all
|
|
1870
1935
|
// loaders during the next loader round
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
1936
|
function revalidate() {
|
|
1874
1937
|
interruptActiveLoads();
|
|
1875
1938
|
updateState({
|
|
1876
1939
|
revalidation: "loading"
|
|
1877
|
-
});
|
|
1878
|
-
// navigation, we'll just let the follow up loader execution call all loaders
|
|
1940
|
+
});
|
|
1879
1941
|
|
|
1942
|
+
// If we're currently submitting an action, we don't need to start a new
|
|
1943
|
+
// navigation, we'll just let the follow up loader execution call all loaders
|
|
1880
1944
|
if (state.navigation.state === "submitting") {
|
|
1881
1945
|
return;
|
|
1882
|
-
}
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// If we're currently in an idle state, start a new navigation for the current
|
|
1883
1949
|
// action/location and mark it as uninterrupted, which will skip the history
|
|
1884
1950
|
// update in completeNavigation
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
1951
|
if (state.navigation.state === "idle") {
|
|
1888
1952
|
startNavigation(state.historyAction, state.location, {
|
|
1889
1953
|
startUninterruptedRevalidation: true
|
|
1890
1954
|
});
|
|
1891
1955
|
return;
|
|
1892
|
-
}
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
// Otherwise, if we're currently in a loading state, just start a new
|
|
1893
1959
|
// navigation to the navigation.location but do not trigger an uninterrupted
|
|
1894
1960
|
// revalidation so that history correctly updates once the navigation completes
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
1961
|
startNavigation(pendingAction || state.historyAction, state.navigation.location, {
|
|
1898
1962
|
overrideNavigation: state.navigation
|
|
1899
1963
|
});
|
|
1900
|
-
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// Start a navigation to the given action/location. Can optionally provide a
|
|
1901
1967
|
// overrideNavigation which will override the normalLoad in the case of a redirect
|
|
1902
1968
|
// navigation
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
1969
|
async function startNavigation(historyAction, location, opts) {
|
|
1906
1970
|
// Abort any in-progress navigations and start a new one. Unset any ongoing
|
|
1907
1971
|
// uninterrupted revalidations unless told otherwise, since we want this
|
|
@@ -1909,15 +1973,17 @@ function createRouter(init) {
|
|
|
1909
1973
|
pendingNavigationController && pendingNavigationController.abort();
|
|
1910
1974
|
pendingNavigationController = null;
|
|
1911
1975
|
pendingAction = historyAction;
|
|
1912
|
-
isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true;
|
|
1913
|
-
// and track whether we should reset scroll on completion
|
|
1976
|
+
isUninterruptedRevalidation = (opts && opts.startUninterruptedRevalidation) === true;
|
|
1914
1977
|
|
|
1978
|
+
// Save the current scroll position every time we start a new navigation,
|
|
1979
|
+
// and track whether we should reset scroll on completion
|
|
1915
1980
|
saveScrollPosition(state.location, state.matches);
|
|
1916
1981
|
pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
1917
1982
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1918
1983
|
let loadingNavigation = opts && opts.overrideNavigation;
|
|
1919
|
-
let matches = matchRoutes(routesToUse, location, basename);
|
|
1984
|
+
let matches = matchRoutes(routesToUse, location, basename);
|
|
1920
1985
|
|
|
1986
|
+
// Short circuit with a 404 on the root error boundary if we match nothing
|
|
1921
1987
|
if (!matches) {
|
|
1922
1988
|
let error = getInternalRouterError(404, {
|
|
1923
1989
|
pathname: location.pathname
|
|
@@ -1925,8 +1991,8 @@ function createRouter(init) {
|
|
|
1925
1991
|
let {
|
|
1926
1992
|
matches: notFoundMatches,
|
|
1927
1993
|
route
|
|
1928
|
-
} = getShortCircuitMatches(routesToUse);
|
|
1929
|
-
|
|
1994
|
+
} = getShortCircuitMatches(routesToUse);
|
|
1995
|
+
// Cancel all pending deferred on 404s since we don't keep any routes
|
|
1930
1996
|
cancelActiveDeferreds();
|
|
1931
1997
|
completeNavigation(location, {
|
|
1932
1998
|
matches: notFoundMatches,
|
|
@@ -1936,26 +2002,26 @@ function createRouter(init) {
|
|
|
1936
2002
|
}
|
|
1937
2003
|
});
|
|
1938
2004
|
return;
|
|
1939
|
-
}
|
|
1940
|
-
// Ignore on initial page loads because since the initial load will always
|
|
1941
|
-
// be "same hash".
|
|
1942
|
-
// For example, on /page#hash and submit a <Form method="post"> which will
|
|
1943
|
-
// default to a navigation to /page
|
|
1944
|
-
|
|
2005
|
+
}
|
|
1945
2006
|
|
|
1946
|
-
if
|
|
2007
|
+
// Short circuit if it's only a hash change and not a revalidation or
|
|
2008
|
+
// mutation submission.
|
|
2009
|
+
//
|
|
2010
|
+
// Ignore on initial page loads because since the initial load will always
|
|
2011
|
+
// be "same hash". For example, on /page#hash and submit a <Form method="post">
|
|
2012
|
+
// which will default to a navigation to /page
|
|
2013
|
+
if (state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
|
|
1947
2014
|
completeNavigation(location, {
|
|
1948
2015
|
matches
|
|
1949
2016
|
});
|
|
1950
2017
|
return;
|
|
1951
|
-
}
|
|
1952
|
-
|
|
2018
|
+
}
|
|
1953
2019
|
|
|
2020
|
+
// Create a controller/Request for this navigation
|
|
1954
2021
|
pendingNavigationController = new AbortController();
|
|
1955
2022
|
let request = createClientSideRequest(init.history, location, pendingNavigationController.signal, opts && opts.submission);
|
|
1956
2023
|
let pendingActionData;
|
|
1957
2024
|
let pendingError;
|
|
1958
|
-
|
|
1959
2025
|
if (opts && opts.pendingError) {
|
|
1960
2026
|
// If we have a pendingError, it means the user attempted a GET submission
|
|
1961
2027
|
// with binary FormData so assign here and skip to handleLoaders. That
|
|
@@ -1969,40 +2035,36 @@ function createRouter(init) {
|
|
|
1969
2035
|
let actionOutput = await handleAction(request, location, opts.submission, matches, {
|
|
1970
2036
|
replace: opts.replace
|
|
1971
2037
|
});
|
|
1972
|
-
|
|
1973
2038
|
if (actionOutput.shortCircuited) {
|
|
1974
2039
|
return;
|
|
1975
2040
|
}
|
|
1976
|
-
|
|
1977
2041
|
pendingActionData = actionOutput.pendingActionData;
|
|
1978
2042
|
pendingError = actionOutput.pendingActionError;
|
|
1979
|
-
|
|
1980
2043
|
let navigation = _extends({
|
|
1981
2044
|
state: "loading",
|
|
1982
2045
|
location
|
|
1983
2046
|
}, opts.submission);
|
|
2047
|
+
loadingNavigation = navigation;
|
|
1984
2048
|
|
|
1985
|
-
|
|
1986
|
-
|
|
2049
|
+
// Create a GET request for the loaders
|
|
1987
2050
|
request = new Request(request.url, {
|
|
1988
2051
|
signal: request.signal
|
|
1989
2052
|
});
|
|
1990
|
-
}
|
|
1991
|
-
|
|
2053
|
+
}
|
|
1992
2054
|
|
|
2055
|
+
// Call loaders
|
|
1993
2056
|
let {
|
|
1994
2057
|
shortCircuited,
|
|
1995
2058
|
loaderData,
|
|
1996
2059
|
errors
|
|
1997
2060
|
} = await handleLoaders(request, location, matches, loadingNavigation, opts && opts.submission, opts && opts.fetcherSubmission, opts && opts.replace, pendingActionData, pendingError);
|
|
1998
|
-
|
|
1999
2061
|
if (shortCircuited) {
|
|
2000
2062
|
return;
|
|
2001
|
-
}
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
// Clean up now that the action/loaders have completed. Don't clean up if
|
|
2002
2066
|
// we short circuited because pendingNavigationController will have already
|
|
2003
2067
|
// been assigned to a new controller for the next navigation
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
2068
|
pendingNavigationController = null;
|
|
2007
2069
|
completeNavigation(location, _extends({
|
|
2008
2070
|
matches
|
|
@@ -2012,25 +2074,25 @@ function createRouter(init) {
|
|
|
2012
2074
|
loaderData,
|
|
2013
2075
|
errors
|
|
2014
2076
|
}));
|
|
2015
|
-
}
|
|
2016
|
-
// redirects/errors
|
|
2017
|
-
|
|
2077
|
+
}
|
|
2018
2078
|
|
|
2079
|
+
// Call the action matched by the leaf route for this navigation and handle
|
|
2080
|
+
// redirects/errors
|
|
2019
2081
|
async function handleAction(request, location, submission, matches, opts) {
|
|
2020
|
-
interruptActiveLoads();
|
|
2082
|
+
interruptActiveLoads();
|
|
2021
2083
|
|
|
2084
|
+
// Put us in a submitting state
|
|
2022
2085
|
let navigation = _extends({
|
|
2023
2086
|
state: "submitting",
|
|
2024
2087
|
location
|
|
2025
2088
|
}, submission);
|
|
2026
|
-
|
|
2027
2089
|
updateState({
|
|
2028
2090
|
navigation
|
|
2029
|
-
});
|
|
2091
|
+
});
|
|
2030
2092
|
|
|
2093
|
+
// Call our action and get the result
|
|
2031
2094
|
let result;
|
|
2032
2095
|
let actionMatch = getTargetMatch(matches, location);
|
|
2033
|
-
|
|
2034
2096
|
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
2035
2097
|
result = {
|
|
2036
2098
|
type: ResultType.error,
|
|
@@ -2042,17 +2104,14 @@ function createRouter(init) {
|
|
|
2042
2104
|
};
|
|
2043
2105
|
} else {
|
|
2044
2106
|
result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename);
|
|
2045
|
-
|
|
2046
2107
|
if (request.signal.aborted) {
|
|
2047
2108
|
return {
|
|
2048
2109
|
shortCircuited: true
|
|
2049
2110
|
};
|
|
2050
2111
|
}
|
|
2051
2112
|
}
|
|
2052
|
-
|
|
2053
2113
|
if (isRedirectResult(result)) {
|
|
2054
2114
|
let replace;
|
|
2055
|
-
|
|
2056
2115
|
if (opts && opts.replace != null) {
|
|
2057
2116
|
replace = opts.replace;
|
|
2058
2117
|
} else {
|
|
@@ -2061,7 +2120,6 @@ function createRouter(init) {
|
|
|
2061
2120
|
// double back-buttons
|
|
2062
2121
|
replace = result.location === state.location.pathname + state.location.search;
|
|
2063
2122
|
}
|
|
2064
|
-
|
|
2065
2123
|
await startRedirectNavigation(state, result, {
|
|
2066
2124
|
submission,
|
|
2067
2125
|
replace
|
|
@@ -2070,19 +2128,18 @@ function createRouter(init) {
|
|
|
2070
2128
|
shortCircuited: true
|
|
2071
2129
|
};
|
|
2072
2130
|
}
|
|
2073
|
-
|
|
2074
2131
|
if (isErrorResult(result)) {
|
|
2075
2132
|
// Store off the pending error - we use it to determine which loaders
|
|
2076
2133
|
// to call and will commit it when we complete the navigation
|
|
2077
|
-
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
2134
|
+
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
2135
|
+
|
|
2136
|
+
// By default, all submissions are REPLACE navigations, but if the
|
|
2078
2137
|
// action threw an error that'll be rendered in an errorElement, we fall
|
|
2079
2138
|
// back to PUSH so that the user can use the back button to get back to
|
|
2080
2139
|
// the pre-submission form location to try again
|
|
2081
|
-
|
|
2082
2140
|
if ((opts && opts.replace) !== true) {
|
|
2083
2141
|
pendingAction = exports.Action.Push;
|
|
2084
2142
|
}
|
|
2085
|
-
|
|
2086
2143
|
return {
|
|
2087
2144
|
// Send back an empty object we can use to clear out any prior actionData
|
|
2088
2145
|
pendingActionData: {},
|
|
@@ -2091,26 +2148,23 @@ function createRouter(init) {
|
|
|
2091
2148
|
}
|
|
2092
2149
|
};
|
|
2093
2150
|
}
|
|
2094
|
-
|
|
2095
2151
|
if (isDeferredResult(result)) {
|
|
2096
2152
|
throw getInternalRouterError(400, {
|
|
2097
2153
|
type: "defer-action"
|
|
2098
2154
|
});
|
|
2099
2155
|
}
|
|
2100
|
-
|
|
2101
2156
|
return {
|
|
2102
2157
|
pendingActionData: {
|
|
2103
2158
|
[actionMatch.route.id]: result.data
|
|
2104
2159
|
}
|
|
2105
2160
|
};
|
|
2106
|
-
}
|
|
2107
|
-
// errors, etc.
|
|
2108
|
-
|
|
2161
|
+
}
|
|
2109
2162
|
|
|
2163
|
+
// Call all applicable loaders for the given matches, handling redirects,
|
|
2164
|
+
// errors, etc.
|
|
2110
2165
|
async function handleLoaders(request, location, matches, overrideNavigation, submission, fetcherSubmission, replace, pendingActionData, pendingError) {
|
|
2111
2166
|
// Figure out the right navigation we want to use for data loading
|
|
2112
2167
|
let loadingNavigation = overrideNavigation;
|
|
2113
|
-
|
|
2114
2168
|
if (!loadingNavigation) {
|
|
2115
2169
|
let navigation = _extends({
|
|
2116
2170
|
state: "loading",
|
|
@@ -2120,12 +2174,11 @@ function createRouter(init) {
|
|
|
2120
2174
|
formEncType: undefined,
|
|
2121
2175
|
formData: undefined
|
|
2122
2176
|
}, submission);
|
|
2123
|
-
|
|
2124
2177
|
loadingNavigation = navigation;
|
|
2125
|
-
}
|
|
2126
|
-
// we have it on the loading navigation so use that if available
|
|
2127
|
-
|
|
2178
|
+
}
|
|
2128
2179
|
|
|
2180
|
+
// If this was a redirect from an action we don't have a "submission" but
|
|
2181
|
+
// we have it on the loading navigation so use that if available
|
|
2129
2182
|
let activeSubmission = submission || fetcherSubmission ? submission || fetcherSubmission : loadingNavigation.formMethod && loadingNavigation.formAction && loadingNavigation.formData && loadingNavigation.formEncType ? {
|
|
2130
2183
|
formMethod: loadingNavigation.formMethod,
|
|
2131
2184
|
formAction: loadingNavigation.formAction,
|
|
@@ -2133,12 +2186,14 @@ function createRouter(init) {
|
|
|
2133
2186
|
formEncType: loadingNavigation.formEncType
|
|
2134
2187
|
} : undefined;
|
|
2135
2188
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2136
|
-
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, pendingActionData, pendingError);
|
|
2189
|
+
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, pendingActionData, pendingError);
|
|
2190
|
+
|
|
2191
|
+
// Cancel pending deferreds for no-longer-matched routes or routes we're
|
|
2137
2192
|
// about to reload. Note that if this is an action reload we would have
|
|
2138
2193
|
// already cancelled all pending deferreds so this would be a no-op
|
|
2194
|
+
cancelActiveDeferreds(routeId => !(matches && matches.some(m => m.route.id === routeId)) || matchesToLoad && matchesToLoad.some(m => m.route.id === routeId));
|
|
2139
2195
|
|
|
2140
|
-
|
|
2141
|
-
|
|
2196
|
+
// Short circuit if we have no loaders to run
|
|
2142
2197
|
if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
|
|
2143
2198
|
let updatedFetchers = markFetchRedirectsDone();
|
|
2144
2199
|
completeNavigation(location, _extends({
|
|
@@ -2154,12 +2209,12 @@ function createRouter(init) {
|
|
|
2154
2209
|
return {
|
|
2155
2210
|
shortCircuited: true
|
|
2156
2211
|
};
|
|
2157
|
-
}
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
// If this is an uninterrupted revalidation, we remain in our current idle
|
|
2158
2215
|
// state. If not, we need to switch to our loading state and load data,
|
|
2159
2216
|
// preserving any new action data or existing action data (in the case of
|
|
2160
2217
|
// a revalidation interrupting an actionReload)
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
2218
|
if (!isUninterruptedRevalidation) {
|
|
2164
2219
|
revalidatingFetchers.forEach(rf => {
|
|
2165
2220
|
let fetcher = state.fetchers.get(rf.key);
|
|
@@ -2185,7 +2240,6 @@ function createRouter(init) {
|
|
|
2185
2240
|
fetchers: new Map(state.fetchers)
|
|
2186
2241
|
} : {}));
|
|
2187
2242
|
}
|
|
2188
|
-
|
|
2189
2243
|
pendingNavigationLoadId = ++incrementingLoadId;
|
|
2190
2244
|
revalidatingFetchers.forEach(rf => {
|
|
2191
2245
|
if (rf.controller) {
|
|
@@ -2194,37 +2248,34 @@ function createRouter(init) {
|
|
|
2194
2248
|
// triggered the revalidation
|
|
2195
2249
|
fetchControllers.set(rf.key, rf.controller);
|
|
2196
2250
|
}
|
|
2197
|
-
});
|
|
2251
|
+
});
|
|
2198
2252
|
|
|
2253
|
+
// Proxy navigation abort through to revalidation fetchers
|
|
2199
2254
|
let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(f => abortFetcher(f.key));
|
|
2200
|
-
|
|
2201
2255
|
if (pendingNavigationController) {
|
|
2202
2256
|
pendingNavigationController.signal.addEventListener("abort", abortPendingFetchRevalidations);
|
|
2203
2257
|
}
|
|
2204
|
-
|
|
2205
2258
|
let {
|
|
2206
2259
|
results,
|
|
2207
2260
|
loaderResults,
|
|
2208
2261
|
fetcherResults
|
|
2209
2262
|
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, request);
|
|
2210
|
-
|
|
2211
2263
|
if (request.signal.aborted) {
|
|
2212
2264
|
return {
|
|
2213
2265
|
shortCircuited: true
|
|
2214
2266
|
};
|
|
2215
|
-
}
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
// Clean up _after_ loaders have completed. Don't clean up if we short
|
|
2216
2270
|
// circuited because fetchControllers would have been aborted and
|
|
2217
2271
|
// reassigned to new controllers for the next navigation
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
2272
|
if (pendingNavigationController) {
|
|
2221
2273
|
pendingNavigationController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
|
|
2222
2274
|
}
|
|
2275
|
+
revalidatingFetchers.forEach(rf => fetchControllers.delete(rf.key));
|
|
2223
2276
|
|
|
2224
|
-
|
|
2225
|
-
|
|
2277
|
+
// If any loaders returned a redirect Response, start a new REPLACE navigation
|
|
2226
2278
|
let redirect = findRedirect(results);
|
|
2227
|
-
|
|
2228
2279
|
if (redirect) {
|
|
2229
2280
|
await startRedirectNavigation(state, redirect, {
|
|
2230
2281
|
replace
|
|
@@ -2232,14 +2283,15 @@ function createRouter(init) {
|
|
|
2232
2283
|
return {
|
|
2233
2284
|
shortCircuited: true
|
|
2234
2285
|
};
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2286
|
+
}
|
|
2237
2287
|
|
|
2288
|
+
// Process and commit output from loaders
|
|
2238
2289
|
let {
|
|
2239
2290
|
loaderData,
|
|
2240
2291
|
errors
|
|
2241
|
-
} = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds);
|
|
2292
|
+
} = processLoaderData(state, matches, matchesToLoad, loaderResults, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds);
|
|
2242
2293
|
|
|
2294
|
+
// Wire up subscribers to update loaderData as promises settle
|
|
2243
2295
|
activeDeferreds.forEach((deferredData, routeId) => {
|
|
2244
2296
|
deferredData.subscribe(aborted => {
|
|
2245
2297
|
// Note: No need to updateState here since the TrackedPromise on
|
|
@@ -2260,56 +2312,50 @@ function createRouter(init) {
|
|
|
2260
2312
|
fetchers: new Map(state.fetchers)
|
|
2261
2313
|
} : {});
|
|
2262
2314
|
}
|
|
2263
|
-
|
|
2264
2315
|
function getFetcher(key) {
|
|
2265
2316
|
return state.fetchers.get(key) || IDLE_FETCHER;
|
|
2266
|
-
}
|
|
2267
|
-
|
|
2317
|
+
}
|
|
2268
2318
|
|
|
2319
|
+
// Trigger a fetcher load/submit for the given fetcher key
|
|
2269
2320
|
function fetch(key, routeId, href, opts) {
|
|
2270
2321
|
if (isServer) {
|
|
2271
2322
|
throw new Error("router.fetch() was called during the server render, but it shouldn't be. " + "You are likely calling a useFetcher() method in the body of your component. " + "Try moving it to a useEffect or a callback.");
|
|
2272
2323
|
}
|
|
2273
|
-
|
|
2274
2324
|
if (fetchControllers.has(key)) abortFetcher(key);
|
|
2275
2325
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2276
2326
|
let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, routeId, opts == null ? void 0 : opts.relative);
|
|
2277
2327
|
let matches = matchRoutes(routesToUse, normalizedPath, basename);
|
|
2278
|
-
|
|
2279
2328
|
if (!matches) {
|
|
2280
2329
|
setFetcherError(key, routeId, getInternalRouterError(404, {
|
|
2281
2330
|
pathname: normalizedPath
|
|
2282
2331
|
}));
|
|
2283
2332
|
return;
|
|
2284
2333
|
}
|
|
2285
|
-
|
|
2286
2334
|
let {
|
|
2287
2335
|
path,
|
|
2288
2336
|
submission
|
|
2289
2337
|
} = normalizeNavigateOptions(future.v7_normalizeFormMethod, true, normalizedPath, opts);
|
|
2290
2338
|
let match = getTargetMatch(matches, path);
|
|
2291
2339
|
pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
2292
|
-
|
|
2293
2340
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
2294
2341
|
handleFetcherAction(key, routeId, path, match, matches, submission);
|
|
2295
2342
|
return;
|
|
2296
|
-
}
|
|
2297
|
-
// revalidations
|
|
2298
|
-
|
|
2343
|
+
}
|
|
2299
2344
|
|
|
2345
|
+
// Store off the match so we can call it's shouldRevalidate on subsequent
|
|
2346
|
+
// revalidations
|
|
2300
2347
|
fetchLoadMatches.set(key, {
|
|
2301
2348
|
routeId,
|
|
2302
2349
|
path
|
|
2303
2350
|
});
|
|
2304
2351
|
handleFetcherLoader(key, routeId, path, match, matches, submission);
|
|
2305
|
-
}
|
|
2306
|
-
// errors, and revalidation
|
|
2307
|
-
|
|
2352
|
+
}
|
|
2308
2353
|
|
|
2354
|
+
// Call the action for the matched fetcher.submit(), and then handle redirects,
|
|
2355
|
+
// errors, and revalidation
|
|
2309
2356
|
async function handleFetcherAction(key, routeId, path, match, requestMatches, submission) {
|
|
2310
2357
|
interruptActiveLoads();
|
|
2311
2358
|
fetchLoadMatches.delete(key);
|
|
2312
|
-
|
|
2313
2359
|
if (!match.route.action && !match.route.lazy) {
|
|
2314
2360
|
let error = getInternalRouterError(405, {
|
|
2315
2361
|
method: submission.formMethod,
|
|
@@ -2318,49 +2364,43 @@ function createRouter(init) {
|
|
|
2318
2364
|
});
|
|
2319
2365
|
setFetcherError(key, routeId, error);
|
|
2320
2366
|
return;
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2367
|
+
}
|
|
2323
2368
|
|
|
2369
|
+
// Put this fetcher into it's submitting state
|
|
2324
2370
|
let existingFetcher = state.fetchers.get(key);
|
|
2325
|
-
|
|
2326
2371
|
let fetcher = _extends({
|
|
2327
2372
|
state: "submitting"
|
|
2328
2373
|
}, submission, {
|
|
2329
2374
|
data: existingFetcher && existingFetcher.data,
|
|
2330
2375
|
" _hasFetcherDoneAnything ": true
|
|
2331
2376
|
});
|
|
2332
|
-
|
|
2333
2377
|
state.fetchers.set(key, fetcher);
|
|
2334
2378
|
updateState({
|
|
2335
2379
|
fetchers: new Map(state.fetchers)
|
|
2336
|
-
});
|
|
2380
|
+
});
|
|
2337
2381
|
|
|
2382
|
+
// Call the action for the fetcher
|
|
2338
2383
|
let abortController = new AbortController();
|
|
2339
2384
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal, submission);
|
|
2340
2385
|
fetchControllers.set(key, abortController);
|
|
2341
2386
|
let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
|
|
2342
|
-
|
|
2343
2387
|
if (fetchRequest.signal.aborted) {
|
|
2344
2388
|
// We can delete this so long as we weren't aborted by ou our own fetcher
|
|
2345
2389
|
// re-submit which would have put _new_ controller is in fetchControllers
|
|
2346
2390
|
if (fetchControllers.get(key) === abortController) {
|
|
2347
2391
|
fetchControllers.delete(key);
|
|
2348
2392
|
}
|
|
2349
|
-
|
|
2350
2393
|
return;
|
|
2351
2394
|
}
|
|
2352
|
-
|
|
2353
2395
|
if (isRedirectResult(actionResult)) {
|
|
2354
2396
|
fetchControllers.delete(key);
|
|
2355
2397
|
fetchRedirectIds.add(key);
|
|
2356
|
-
|
|
2357
2398
|
let loadingFetcher = _extends({
|
|
2358
2399
|
state: "loading"
|
|
2359
2400
|
}, submission, {
|
|
2360
2401
|
data: undefined,
|
|
2361
2402
|
" _hasFetcherDoneAnything ": true
|
|
2362
2403
|
});
|
|
2363
|
-
|
|
2364
2404
|
state.fetchers.set(key, loadingFetcher);
|
|
2365
2405
|
updateState({
|
|
2366
2406
|
fetchers: new Map(state.fetchers)
|
|
@@ -2369,22 +2409,21 @@ function createRouter(init) {
|
|
|
2369
2409
|
submission,
|
|
2370
2410
|
isFetchActionRedirect: true
|
|
2371
2411
|
});
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2412
|
+
}
|
|
2374
2413
|
|
|
2414
|
+
// Process any non-redirect errors thrown
|
|
2375
2415
|
if (isErrorResult(actionResult)) {
|
|
2376
2416
|
setFetcherError(key, routeId, actionResult.error);
|
|
2377
2417
|
return;
|
|
2378
2418
|
}
|
|
2379
|
-
|
|
2380
2419
|
if (isDeferredResult(actionResult)) {
|
|
2381
2420
|
throw getInternalRouterError(400, {
|
|
2382
2421
|
type: "defer-action"
|
|
2383
2422
|
});
|
|
2384
|
-
}
|
|
2385
|
-
// in the middle of a navigation
|
|
2386
|
-
|
|
2423
|
+
}
|
|
2387
2424
|
|
|
2425
|
+
// Start the data load for current matches, or the next location if we're
|
|
2426
|
+
// in the middle of a navigation
|
|
2388
2427
|
let nextLocation = state.navigation.location || state.location;
|
|
2389
2428
|
let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);
|
|
2390
2429
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
@@ -2392,22 +2431,21 @@ function createRouter(init) {
|
|
|
2392
2431
|
invariant(matches, "Didn't find any matches after fetcher action");
|
|
2393
2432
|
let loadId = ++incrementingLoadId;
|
|
2394
2433
|
fetchReloadIds.set(key, loadId);
|
|
2395
|
-
|
|
2396
2434
|
let loadFetcher = _extends({
|
|
2397
2435
|
state: "loading",
|
|
2398
2436
|
data: actionResult.data
|
|
2399
2437
|
}, submission, {
|
|
2400
2438
|
" _hasFetcherDoneAnything ": true
|
|
2401
2439
|
});
|
|
2402
|
-
|
|
2403
2440
|
state.fetchers.set(key, loadFetcher);
|
|
2404
2441
|
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, {
|
|
2405
2442
|
[match.route.id]: actionResult.data
|
|
2406
2443
|
}, undefined // No need to send through errors since we short circuit above
|
|
2407
|
-
);
|
|
2444
|
+
);
|
|
2445
|
+
|
|
2446
|
+
// Put all revalidating fetchers into the loading state, except for the
|
|
2408
2447
|
// current fetcher which we want to keep in it's current loading state which
|
|
2409
2448
|
// contains it's action submission info + action data
|
|
2410
|
-
|
|
2411
2449
|
revalidatingFetchers.filter(rf => rf.key !== key).forEach(rf => {
|
|
2412
2450
|
let staleKey = rf.key;
|
|
2413
2451
|
let existingFetcher = state.fetchers.get(staleKey);
|
|
@@ -2421,7 +2459,6 @@ function createRouter(init) {
|
|
|
2421
2459
|
" _hasFetcherDoneAnything ": true
|
|
2422
2460
|
};
|
|
2423
2461
|
state.fetchers.set(staleKey, revalidatingFetcher);
|
|
2424
|
-
|
|
2425
2462
|
if (rf.controller) {
|
|
2426
2463
|
fetchControllers.set(staleKey, rf.controller);
|
|
2427
2464
|
}
|
|
@@ -2429,49 +2466,50 @@ function createRouter(init) {
|
|
|
2429
2466
|
updateState({
|
|
2430
2467
|
fetchers: new Map(state.fetchers)
|
|
2431
2468
|
});
|
|
2432
|
-
|
|
2433
2469
|
let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
|
|
2434
|
-
|
|
2435
2470
|
abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
|
|
2436
2471
|
let {
|
|
2437
2472
|
results,
|
|
2438
2473
|
loaderResults,
|
|
2439
2474
|
fetcherResults
|
|
2440
2475
|
} = await callLoadersAndMaybeResolveData(state.matches, matches, matchesToLoad, revalidatingFetchers, revalidationRequest);
|
|
2441
|
-
|
|
2442
2476
|
if (abortController.signal.aborted) {
|
|
2443
2477
|
return;
|
|
2444
2478
|
}
|
|
2445
|
-
|
|
2446
2479
|
abortController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
|
|
2447
2480
|
fetchReloadIds.delete(key);
|
|
2448
2481
|
fetchControllers.delete(key);
|
|
2449
2482
|
revalidatingFetchers.forEach(r => fetchControllers.delete(r.key));
|
|
2450
2483
|
let redirect = findRedirect(results);
|
|
2451
|
-
|
|
2452
2484
|
if (redirect) {
|
|
2453
2485
|
return startRedirectNavigation(state, redirect);
|
|
2454
|
-
}
|
|
2455
|
-
|
|
2486
|
+
}
|
|
2456
2487
|
|
|
2488
|
+
// Process and commit output from loaders
|
|
2457
2489
|
let {
|
|
2458
2490
|
loaderData,
|
|
2459
2491
|
errors
|
|
2460
2492
|
} = processLoaderData(state, state.matches, matchesToLoad, loaderResults, undefined, revalidatingFetchers, fetcherResults, activeDeferreds);
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2493
|
+
|
|
2494
|
+
// Since we let revalidations complete even if the submitting fetcher was
|
|
2495
|
+
// deleted, only put it back to idle if it hasn't been deleted
|
|
2496
|
+
if (state.fetchers.has(key)) {
|
|
2497
|
+
let doneFetcher = {
|
|
2498
|
+
state: "idle",
|
|
2499
|
+
data: actionResult.data,
|
|
2500
|
+
formMethod: undefined,
|
|
2501
|
+
formAction: undefined,
|
|
2502
|
+
formEncType: undefined,
|
|
2503
|
+
formData: undefined,
|
|
2504
|
+
" _hasFetcherDoneAnything ": true
|
|
2505
|
+
};
|
|
2506
|
+
state.fetchers.set(key, doneFetcher);
|
|
2507
|
+
}
|
|
2508
|
+
let didAbortFetchLoads = abortStaleFetchLoads(loadId);
|
|
2509
|
+
|
|
2510
|
+
// If we are currently in a navigation loading state and this fetcher is
|
|
2472
2511
|
// more recent than the navigation, we want the newer data so abort the
|
|
2473
2512
|
// navigation and complete it with the fetcher data
|
|
2474
|
-
|
|
2475
2513
|
if (state.navigation.state === "loading" && loadId > pendingNavigationLoadId) {
|
|
2476
2514
|
invariant(pendingAction, "Expected pending action");
|
|
2477
2515
|
pendingNavigationController && pendingNavigationController.abort();
|
|
@@ -2488,17 +2526,17 @@ function createRouter(init) {
|
|
|
2488
2526
|
updateState(_extends({
|
|
2489
2527
|
errors,
|
|
2490
2528
|
loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors)
|
|
2491
|
-
}, didAbortFetchLoads ? {
|
|
2529
|
+
}, didAbortFetchLoads || revalidatingFetchers.length > 0 ? {
|
|
2492
2530
|
fetchers: new Map(state.fetchers)
|
|
2493
2531
|
} : {}));
|
|
2494
2532
|
isRevalidationRequired = false;
|
|
2495
2533
|
}
|
|
2496
|
-
}
|
|
2497
|
-
|
|
2534
|
+
}
|
|
2498
2535
|
|
|
2536
|
+
// Call the matched loader for fetcher.load(), handling redirects, errors, etc.
|
|
2499
2537
|
async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
|
|
2500
|
-
let existingFetcher = state.fetchers.get(key);
|
|
2501
|
-
|
|
2538
|
+
let existingFetcher = state.fetchers.get(key);
|
|
2539
|
+
// Put this fetcher into it's loading state
|
|
2502
2540
|
let loadingFetcher = _extends({
|
|
2503
2541
|
state: "loading",
|
|
2504
2542
|
formMethod: undefined,
|
|
@@ -2509,48 +2547,48 @@ function createRouter(init) {
|
|
|
2509
2547
|
data: existingFetcher && existingFetcher.data,
|
|
2510
2548
|
" _hasFetcherDoneAnything ": true
|
|
2511
2549
|
});
|
|
2512
|
-
|
|
2513
2550
|
state.fetchers.set(key, loadingFetcher);
|
|
2514
2551
|
updateState({
|
|
2515
2552
|
fetchers: new Map(state.fetchers)
|
|
2516
|
-
});
|
|
2553
|
+
});
|
|
2517
2554
|
|
|
2555
|
+
// Call the loader for this fetcher route match
|
|
2518
2556
|
let abortController = new AbortController();
|
|
2519
2557
|
let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
|
|
2520
2558
|
fetchControllers.set(key, abortController);
|
|
2521
|
-
let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename);
|
|
2559
|
+
let result = await callLoaderOrAction("loader", fetchRequest, match, matches, manifest, mapRouteProperties, basename);
|
|
2560
|
+
|
|
2561
|
+
// Deferred isn't supported for fetcher loads, await everything and treat it
|
|
2522
2562
|
// as a normal load. resolveDeferredData will return undefined if this
|
|
2523
2563
|
// fetcher gets aborted, so we just leave result untouched and short circuit
|
|
2524
2564
|
// below if that happens
|
|
2525
|
-
|
|
2526
2565
|
if (isDeferredResult(result)) {
|
|
2527
2566
|
result = (await resolveDeferredData(result, fetchRequest.signal, true)) || result;
|
|
2528
|
-
}
|
|
2529
|
-
// re-load which would have put _new_ controller is in fetchControllers
|
|
2530
|
-
|
|
2567
|
+
}
|
|
2531
2568
|
|
|
2569
|
+
// We can delete this so long as we weren't aborted by our our own fetcher
|
|
2570
|
+
// re-load which would have put _new_ controller is in fetchControllers
|
|
2532
2571
|
if (fetchControllers.get(key) === abortController) {
|
|
2533
2572
|
fetchControllers.delete(key);
|
|
2534
2573
|
}
|
|
2535
|
-
|
|
2536
2574
|
if (fetchRequest.signal.aborted) {
|
|
2537
2575
|
return;
|
|
2538
|
-
}
|
|
2539
|
-
|
|
2576
|
+
}
|
|
2540
2577
|
|
|
2578
|
+
// If the loader threw a redirect Response, start a new REPLACE navigation
|
|
2541
2579
|
if (isRedirectResult(result)) {
|
|
2542
2580
|
fetchRedirectIds.add(key);
|
|
2543
2581
|
await startRedirectNavigation(state, result);
|
|
2544
2582
|
return;
|
|
2545
|
-
}
|
|
2546
|
-
|
|
2583
|
+
}
|
|
2547
2584
|
|
|
2585
|
+
// Process any non-redirect errors thrown
|
|
2548
2586
|
if (isErrorResult(result)) {
|
|
2549
2587
|
let boundaryMatch = findNearestBoundary(state.matches, routeId);
|
|
2550
|
-
state.fetchers.delete(key);
|
|
2588
|
+
state.fetchers.delete(key);
|
|
2589
|
+
// TODO: In remix, this would reset to IDLE_NAVIGATION if it was a catch -
|
|
2551
2590
|
// do we need to behave any differently with our non-redirect errors?
|
|
2552
2591
|
// What if it was a non-redirect Response?
|
|
2553
|
-
|
|
2554
2592
|
updateState({
|
|
2555
2593
|
fetchers: new Map(state.fetchers),
|
|
2556
2594
|
errors: {
|
|
@@ -2559,9 +2597,9 @@ function createRouter(init) {
|
|
|
2559
2597
|
});
|
|
2560
2598
|
return;
|
|
2561
2599
|
}
|
|
2600
|
+
invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
|
|
2562
2601
|
|
|
2563
|
-
|
|
2564
|
-
|
|
2602
|
+
// Put the fetcher back into an idle state
|
|
2565
2603
|
let doneFetcher = {
|
|
2566
2604
|
state: "idle",
|
|
2567
2605
|
data: result.data,
|
|
@@ -2576,6 +2614,7 @@ function createRouter(init) {
|
|
|
2576
2614
|
fetchers: new Map(state.fetchers)
|
|
2577
2615
|
});
|
|
2578
2616
|
}
|
|
2617
|
+
|
|
2579
2618
|
/**
|
|
2580
2619
|
* Utility function to handle redirects returned from an action or loader.
|
|
2581
2620
|
* Normally, a redirect "replaces" the navigation that triggered it. So, for
|
|
@@ -2595,57 +2634,49 @@ function createRouter(init) {
|
|
|
2595
2634
|
* actually touch history until we've processed redirects, so we just use
|
|
2596
2635
|
* the history action from the original navigation (PUSH or REPLACE).
|
|
2597
2636
|
*/
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
2637
|
async function startRedirectNavigation(state, redirect, _temp) {
|
|
2601
|
-
var _window;
|
|
2602
|
-
|
|
2603
2638
|
let {
|
|
2604
2639
|
submission,
|
|
2605
2640
|
replace,
|
|
2606
2641
|
isFetchActionRedirect
|
|
2607
2642
|
} = _temp === void 0 ? {} : _temp;
|
|
2608
|
-
|
|
2609
2643
|
if (redirect.revalidate) {
|
|
2610
2644
|
isRevalidationRequired = true;
|
|
2611
2645
|
}
|
|
2612
|
-
|
|
2613
2646
|
let redirectLocation = createLocation(state.location, redirect.location, // TODO: This can be removed once we get rid of useTransition in Remix v2
|
|
2614
2647
|
_extends({
|
|
2615
2648
|
_isRedirect: true
|
|
2616
2649
|
}, isFetchActionRedirect ? {
|
|
2617
2650
|
_isFetchActionRedirect: true
|
|
2618
2651
|
} : {}));
|
|
2619
|
-
invariant(redirectLocation, "Expected a location on the redirect navigation");
|
|
2620
|
-
|
|
2621
|
-
if (ABSOLUTE_URL_REGEX.test(redirect.location) && isBrowser
|
|
2652
|
+
invariant(redirectLocation, "Expected a location on the redirect navigation");
|
|
2653
|
+
// Check if this an absolute external redirect that goes to a new origin
|
|
2654
|
+
if (ABSOLUTE_URL_REGEX.test(redirect.location) && isBrowser) {
|
|
2622
2655
|
let url = init.history.createURL(redirect.location);
|
|
2623
2656
|
let isDifferentBasename = stripBasename(url.pathname, basename) == null;
|
|
2624
|
-
|
|
2625
|
-
if (window.location.origin !== url.origin || isDifferentBasename) {
|
|
2657
|
+
if (routerWindow.location.origin !== url.origin || isDifferentBasename) {
|
|
2626
2658
|
if (replace) {
|
|
2627
|
-
|
|
2659
|
+
routerWindow.location.replace(redirect.location);
|
|
2628
2660
|
} else {
|
|
2629
|
-
|
|
2661
|
+
routerWindow.location.assign(redirect.location);
|
|
2630
2662
|
}
|
|
2631
|
-
|
|
2632
2663
|
return;
|
|
2633
2664
|
}
|
|
2634
|
-
}
|
|
2635
|
-
// redirect until the action/loaders have settled
|
|
2636
|
-
|
|
2665
|
+
}
|
|
2637
2666
|
|
|
2667
|
+
// There's no need to abort on redirects, since we don't detect the
|
|
2668
|
+
// redirect until the action/loaders have settled
|
|
2638
2669
|
pendingNavigationController = null;
|
|
2639
|
-
let redirectHistoryAction = replace === true ? exports.Action.Replace : exports.Action.Push;
|
|
2640
|
-
// state.navigation
|
|
2670
|
+
let redirectHistoryAction = replace === true ? exports.Action.Replace : exports.Action.Push;
|
|
2641
2671
|
|
|
2672
|
+
// Use the incoming submission if provided, fallback on the active one in
|
|
2673
|
+
// state.navigation
|
|
2642
2674
|
let {
|
|
2643
2675
|
formMethod,
|
|
2644
2676
|
formAction,
|
|
2645
2677
|
formEncType,
|
|
2646
2678
|
formData
|
|
2647
2679
|
} = state.navigation;
|
|
2648
|
-
|
|
2649
2680
|
if (!submission && formMethod && formAction && formData && formEncType) {
|
|
2650
2681
|
submission = {
|
|
2651
2682
|
formMethod,
|
|
@@ -2653,11 +2684,11 @@ function createRouter(init) {
|
|
|
2653
2684
|
formEncType,
|
|
2654
2685
|
formData
|
|
2655
2686
|
};
|
|
2656
|
-
}
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
// If this was a 307/308 submission we want to preserve the HTTP method and
|
|
2657
2690
|
// re-submit the GET/POST/PUT/PATCH/DELETE as a submission navigation to the
|
|
2658
2691
|
// redirected location
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
2692
|
if (redirectPreserveMethodStatusCodes.has(redirect.status) && submission && isMutationMethod(submission.formMethod)) {
|
|
2662
2693
|
await startNavigation(redirectHistoryAction, redirectLocation, {
|
|
2663
2694
|
submission: _extends({}, submission, {
|
|
@@ -2699,7 +2730,6 @@ function createRouter(init) {
|
|
|
2699
2730
|
});
|
|
2700
2731
|
}
|
|
2701
2732
|
}
|
|
2702
|
-
|
|
2703
2733
|
async function callLoadersAndMaybeResolveData(currentMatches, matches, matchesToLoad, fetchersToLoad, request) {
|
|
2704
2734
|
// Call all navigation loaders and revalidating fetcher loaders in parallel,
|
|
2705
2735
|
// then slice off the results into separate arrays so we can handle them
|
|
@@ -2726,14 +2756,15 @@ function createRouter(init) {
|
|
|
2726
2756
|
fetcherResults
|
|
2727
2757
|
};
|
|
2728
2758
|
}
|
|
2729
|
-
|
|
2730
2759
|
function interruptActiveLoads() {
|
|
2731
2760
|
// Every interruption triggers a revalidation
|
|
2732
|
-
isRevalidationRequired = true;
|
|
2733
|
-
// revalidation
|
|
2761
|
+
isRevalidationRequired = true;
|
|
2734
2762
|
|
|
2735
|
-
|
|
2763
|
+
// Cancel pending route-level deferreds and mark cancelled routes for
|
|
2764
|
+
// revalidation
|
|
2765
|
+
cancelledDeferredRoutes.push(...cancelActiveDeferreds());
|
|
2736
2766
|
|
|
2767
|
+
// Abort in-flight fetcher loads
|
|
2737
2768
|
fetchLoadMatches.forEach((_, key) => {
|
|
2738
2769
|
if (fetchControllers.has(key)) {
|
|
2739
2770
|
cancelledFetcherLoads.push(key);
|
|
@@ -2741,7 +2772,6 @@ function createRouter(init) {
|
|
|
2741
2772
|
}
|
|
2742
2773
|
});
|
|
2743
2774
|
}
|
|
2744
|
-
|
|
2745
2775
|
function setFetcherError(key, routeId, error) {
|
|
2746
2776
|
let boundaryMatch = findNearestBoundary(state.matches, routeId);
|
|
2747
2777
|
deleteFetcher(key);
|
|
@@ -2752,22 +2782,25 @@ function createRouter(init) {
|
|
|
2752
2782
|
fetchers: new Map(state.fetchers)
|
|
2753
2783
|
});
|
|
2754
2784
|
}
|
|
2755
|
-
|
|
2756
2785
|
function deleteFetcher(key) {
|
|
2757
|
-
|
|
2786
|
+
let fetcher = state.fetchers.get(key);
|
|
2787
|
+
// Don't abort the controller if this is a deletion of a fetcher.submit()
|
|
2788
|
+
// in it's loading phase since - we don't want to abort the corresponding
|
|
2789
|
+
// revalidation and want them to complete and land
|
|
2790
|
+
if (fetchControllers.has(key) && !(fetcher && fetcher.state === "loading" && fetchReloadIds.has(key))) {
|
|
2791
|
+
abortFetcher(key);
|
|
2792
|
+
}
|
|
2758
2793
|
fetchLoadMatches.delete(key);
|
|
2759
2794
|
fetchReloadIds.delete(key);
|
|
2760
2795
|
fetchRedirectIds.delete(key);
|
|
2761
2796
|
state.fetchers.delete(key);
|
|
2762
2797
|
}
|
|
2763
|
-
|
|
2764
2798
|
function abortFetcher(key) {
|
|
2765
2799
|
let controller = fetchControllers.get(key);
|
|
2766
2800
|
invariant(controller, "Expected fetch controller: " + key);
|
|
2767
2801
|
controller.abort();
|
|
2768
2802
|
fetchControllers.delete(key);
|
|
2769
2803
|
}
|
|
2770
|
-
|
|
2771
2804
|
function markFetchersDone(keys) {
|
|
2772
2805
|
for (let key of keys) {
|
|
2773
2806
|
let fetcher = getFetcher(key);
|
|
@@ -2783,34 +2816,27 @@ function createRouter(init) {
|
|
|
2783
2816
|
state.fetchers.set(key, doneFetcher);
|
|
2784
2817
|
}
|
|
2785
2818
|
}
|
|
2786
|
-
|
|
2787
2819
|
function markFetchRedirectsDone() {
|
|
2788
2820
|
let doneKeys = [];
|
|
2789
2821
|
let updatedFetchers = false;
|
|
2790
|
-
|
|
2791
2822
|
for (let key of fetchRedirectIds) {
|
|
2792
2823
|
let fetcher = state.fetchers.get(key);
|
|
2793
2824
|
invariant(fetcher, "Expected fetcher: " + key);
|
|
2794
|
-
|
|
2795
2825
|
if (fetcher.state === "loading") {
|
|
2796
2826
|
fetchRedirectIds.delete(key);
|
|
2797
2827
|
doneKeys.push(key);
|
|
2798
2828
|
updatedFetchers = true;
|
|
2799
2829
|
}
|
|
2800
2830
|
}
|
|
2801
|
-
|
|
2802
2831
|
markFetchersDone(doneKeys);
|
|
2803
2832
|
return updatedFetchers;
|
|
2804
2833
|
}
|
|
2805
|
-
|
|
2806
2834
|
function abortStaleFetchLoads(landedId) {
|
|
2807
2835
|
let yeetedKeys = [];
|
|
2808
|
-
|
|
2809
2836
|
for (let [key, id] of fetchReloadIds) {
|
|
2810
2837
|
if (id < landedId) {
|
|
2811
2838
|
let fetcher = state.fetchers.get(key);
|
|
2812
2839
|
invariant(fetcher, "Expected fetcher: " + key);
|
|
2813
|
-
|
|
2814
2840
|
if (fetcher.state === "loading") {
|
|
2815
2841
|
abortFetcher(key);
|
|
2816
2842
|
fetchReloadIds.delete(key);
|
|
@@ -2818,67 +2844,59 @@ function createRouter(init) {
|
|
|
2818
2844
|
}
|
|
2819
2845
|
}
|
|
2820
2846
|
}
|
|
2821
|
-
|
|
2822
2847
|
markFetchersDone(yeetedKeys);
|
|
2823
2848
|
return yeetedKeys.length > 0;
|
|
2824
2849
|
}
|
|
2825
|
-
|
|
2826
2850
|
function getBlocker(key, fn) {
|
|
2827
2851
|
let blocker = state.blockers.get(key) || IDLE_BLOCKER;
|
|
2828
|
-
|
|
2829
2852
|
if (blockerFunctions.get(key) !== fn) {
|
|
2830
2853
|
blockerFunctions.set(key, fn);
|
|
2831
2854
|
}
|
|
2832
|
-
|
|
2833
2855
|
return blocker;
|
|
2834
2856
|
}
|
|
2835
|
-
|
|
2836
2857
|
function deleteBlocker(key) {
|
|
2837
2858
|
state.blockers.delete(key);
|
|
2838
2859
|
blockerFunctions.delete(key);
|
|
2839
|
-
}
|
|
2840
|
-
|
|
2860
|
+
}
|
|
2841
2861
|
|
|
2862
|
+
// Utility function to update blockers, ensuring valid state transitions
|
|
2842
2863
|
function updateBlocker(key, newBlocker) {
|
|
2843
|
-
let blocker = state.blockers.get(key) || IDLE_BLOCKER;
|
|
2844
|
-
// https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
|
|
2864
|
+
let blocker = state.blockers.get(key) || IDLE_BLOCKER;
|
|
2845
2865
|
|
|
2866
|
+
// Poor mans state machine :)
|
|
2867
|
+
// https://mermaid.live/edit#pako:eNqVkc9OwzAMxl8l8nnjAYrEtDIOHEBIgwvKJTReGy3_lDpIqO27k6awMG0XcrLlnz87nwdonESogKXXBuE79rq75XZO3-yHds0RJVuv70YrPlUrCEe2HfrORS3rubqZfuhtpg5C9wk5tZ4VKcRUq88q9Z8RS0-48cE1iHJkL0ugbHuFLus9L6spZy8nX9MP2CNdomVaposqu3fGayT8T8-jJQwhepo_UtpgBQaDEUom04dZhAN1aJBDlUKJBxE1ceB2Smj0Mln-IBW5AFU2dwUiktt_2Qaq2dBfaKdEup85UV7Yd-dKjlnkabl2Pvr0DTkTreM
|
|
2846
2868
|
invariant(blocker.state === "unblocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "blocked" || blocker.state === "blocked" && newBlocker.state === "proceeding" || blocker.state === "blocked" && newBlocker.state === "unblocked" || blocker.state === "proceeding" && newBlocker.state === "unblocked", "Invalid blocker state transition: " + blocker.state + " -> " + newBlocker.state);
|
|
2847
2869
|
state.blockers.set(key, newBlocker);
|
|
2848
2870
|
updateState({
|
|
2849
2871
|
blockers: new Map(state.blockers)
|
|
2850
2872
|
});
|
|
2851
2873
|
}
|
|
2852
|
-
|
|
2853
2874
|
function shouldBlockNavigation(_ref2) {
|
|
2854
2875
|
let {
|
|
2855
2876
|
currentLocation,
|
|
2856
2877
|
nextLocation,
|
|
2857
2878
|
historyAction
|
|
2858
2879
|
} = _ref2;
|
|
2859
|
-
|
|
2860
2880
|
if (blockerFunctions.size === 0) {
|
|
2861
2881
|
return;
|
|
2862
|
-
}
|
|
2863
|
-
// any compelling use cases for multi-blocker yet
|
|
2864
|
-
|
|
2882
|
+
}
|
|
2865
2883
|
|
|
2884
|
+
// We ony support a single active blocker at the moment since we don't have
|
|
2885
|
+
// any compelling use cases for multi-blocker yet
|
|
2866
2886
|
if (blockerFunctions.size > 1) {
|
|
2867
2887
|
warning(false, "A router only supports one blocker at a time");
|
|
2868
2888
|
}
|
|
2869
|
-
|
|
2870
2889
|
let entries = Array.from(blockerFunctions.entries());
|
|
2871
2890
|
let [blockerKey, blockerFunction] = entries[entries.length - 1];
|
|
2872
2891
|
let blocker = state.blockers.get(blockerKey);
|
|
2873
|
-
|
|
2874
2892
|
if (blocker && blocker.state === "proceeding") {
|
|
2875
2893
|
// If the blocker is currently proceeding, we don't need to re-check
|
|
2876
2894
|
// it and can let this navigation continue
|
|
2877
2895
|
return;
|
|
2878
|
-
}
|
|
2879
|
-
// user-provided blocker function
|
|
2880
|
-
|
|
2896
|
+
}
|
|
2881
2897
|
|
|
2898
|
+
// At this point, we know we're unblocked/blocked so we need to check the
|
|
2899
|
+
// user-provided blocker function
|
|
2882
2900
|
if (blockerFunction({
|
|
2883
2901
|
currentLocation,
|
|
2884
2902
|
nextLocation,
|
|
@@ -2887,7 +2905,6 @@ function createRouter(init) {
|
|
|
2887
2905
|
return blockerKey;
|
|
2888
2906
|
}
|
|
2889
2907
|
}
|
|
2890
|
-
|
|
2891
2908
|
function cancelActiveDeferreds(predicate) {
|
|
2892
2909
|
let cancelledRouteIds = [];
|
|
2893
2910
|
activeDeferreds.forEach((dfd, routeId) => {
|
|
@@ -2901,37 +2918,33 @@ function createRouter(init) {
|
|
|
2901
2918
|
}
|
|
2902
2919
|
});
|
|
2903
2920
|
return cancelledRouteIds;
|
|
2904
|
-
}
|
|
2905
|
-
// used by the <ScrollRestoration> component
|
|
2906
|
-
|
|
2921
|
+
}
|
|
2907
2922
|
|
|
2923
|
+
// Opt in to capturing and reporting scroll positions during navigations,
|
|
2924
|
+
// used by the <ScrollRestoration> component
|
|
2908
2925
|
function enableScrollRestoration(positions, getPosition, getKey) {
|
|
2909
2926
|
savedScrollPositions = positions;
|
|
2910
2927
|
getScrollPosition = getPosition;
|
|
2928
|
+
getScrollRestorationKey = getKey || (location => location.key);
|
|
2911
2929
|
|
|
2912
|
-
|
|
2930
|
+
// Perform initial hydration scroll restoration, since we miss the boat on
|
|
2913
2931
|
// the initial updateState() because we've not yet rendered <ScrollRestoration/>
|
|
2914
2932
|
// and therefore have no savedScrollPositions available
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
2933
|
if (!initialScrollRestored && state.navigation === IDLE_NAVIGATION) {
|
|
2918
2934
|
initialScrollRestored = true;
|
|
2919
2935
|
let y = getSavedScrollPosition(state.location, state.matches);
|
|
2920
|
-
|
|
2921
2936
|
if (y != null) {
|
|
2922
2937
|
updateState({
|
|
2923
2938
|
restoreScrollPosition: y
|
|
2924
2939
|
});
|
|
2925
2940
|
}
|
|
2926
2941
|
}
|
|
2927
|
-
|
|
2928
2942
|
return () => {
|
|
2929
2943
|
savedScrollPositions = null;
|
|
2930
2944
|
getScrollPosition = null;
|
|
2931
2945
|
getScrollRestorationKey = null;
|
|
2932
2946
|
};
|
|
2933
2947
|
}
|
|
2934
|
-
|
|
2935
2948
|
function saveScrollPosition(location, matches) {
|
|
2936
2949
|
if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
|
|
2937
2950
|
let userMatches = matches.map(m => createUseMatchesMatch(m, state.loaderData));
|
|
@@ -2939,39 +2952,31 @@ function createRouter(init) {
|
|
|
2939
2952
|
savedScrollPositions[key] = getScrollPosition();
|
|
2940
2953
|
}
|
|
2941
2954
|
}
|
|
2942
|
-
|
|
2943
2955
|
function getSavedScrollPosition(location, matches) {
|
|
2944
2956
|
if (savedScrollPositions && getScrollRestorationKey && getScrollPosition) {
|
|
2945
2957
|
let userMatches = matches.map(m => createUseMatchesMatch(m, state.loaderData));
|
|
2946
2958
|
let key = getScrollRestorationKey(location, userMatches) || location.key;
|
|
2947
2959
|
let y = savedScrollPositions[key];
|
|
2948
|
-
|
|
2949
2960
|
if (typeof y === "number") {
|
|
2950
2961
|
return y;
|
|
2951
2962
|
}
|
|
2952
2963
|
}
|
|
2953
|
-
|
|
2954
2964
|
return null;
|
|
2955
2965
|
}
|
|
2956
|
-
|
|
2957
2966
|
function _internalSetRoutes(newRoutes) {
|
|
2958
2967
|
manifest = {};
|
|
2959
2968
|
inFlightDataRoutes = convertRoutesToDataRoutes(newRoutes, mapRouteProperties, undefined, manifest);
|
|
2960
2969
|
}
|
|
2961
|
-
|
|
2962
2970
|
router = {
|
|
2963
2971
|
get basename() {
|
|
2964
2972
|
return basename;
|
|
2965
2973
|
},
|
|
2966
|
-
|
|
2967
2974
|
get state() {
|
|
2968
2975
|
return state;
|
|
2969
2976
|
},
|
|
2970
|
-
|
|
2971
2977
|
get routes() {
|
|
2972
2978
|
return dataRoutes;
|
|
2973
2979
|
},
|
|
2974
|
-
|
|
2975
2980
|
initialize,
|
|
2976
2981
|
subscribe,
|
|
2977
2982
|
enableScrollRestoration,
|
|
@@ -2994,7 +2999,9 @@ function createRouter(init) {
|
|
|
2994
2999
|
_internalSetRoutes
|
|
2995
3000
|
};
|
|
2996
3001
|
return router;
|
|
2997
|
-
}
|
|
3002
|
+
}
|
|
3003
|
+
//#endregion
|
|
3004
|
+
|
|
2998
3005
|
////////////////////////////////////////////////////////////////////////////////
|
|
2999
3006
|
//#region createStaticHandler
|
|
3000
3007
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -3005,21 +3012,19 @@ function createStaticHandler(routes, opts) {
|
|
|
3005
3012
|
let manifest = {};
|
|
3006
3013
|
let basename = (opts ? opts.basename : null) || "/";
|
|
3007
3014
|
let mapRouteProperties;
|
|
3008
|
-
|
|
3009
3015
|
if (opts != null && opts.mapRouteProperties) {
|
|
3010
3016
|
mapRouteProperties = opts.mapRouteProperties;
|
|
3011
3017
|
} else if (opts != null && opts.detectErrorBoundary) {
|
|
3012
3018
|
// If they are still using the deprecated version, wrap it with the new API
|
|
3013
3019
|
let detectErrorBoundary = opts.detectErrorBoundary;
|
|
3014
|
-
|
|
3015
3020
|
mapRouteProperties = route => ({
|
|
3016
3021
|
hasErrorBoundary: detectErrorBoundary(route)
|
|
3017
3022
|
});
|
|
3018
3023
|
} else {
|
|
3019
3024
|
mapRouteProperties = defaultMapRouteProperties;
|
|
3020
3025
|
}
|
|
3021
|
-
|
|
3022
3026
|
let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, undefined, manifest);
|
|
3027
|
+
|
|
3023
3028
|
/**
|
|
3024
3029
|
* The query() method is intended for document requests, in which we want to
|
|
3025
3030
|
* call an optional action and potentially multiple loaders for all nested
|
|
@@ -3039,7 +3044,6 @@ function createStaticHandler(routes, opts) {
|
|
|
3039
3044
|
* propagate that out and return the raw Response so the HTTP server can
|
|
3040
3045
|
* return it directly.
|
|
3041
3046
|
*/
|
|
3042
|
-
|
|
3043
3047
|
async function query(request, _temp2) {
|
|
3044
3048
|
let {
|
|
3045
3049
|
requestContext
|
|
@@ -3047,8 +3051,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3047
3051
|
let url = new URL(request.url);
|
|
3048
3052
|
let method = request.method;
|
|
3049
3053
|
let location = createLocation("", createPath(url), null, "default");
|
|
3050
|
-
let matches = matchRoutes(dataRoutes, location, basename);
|
|
3054
|
+
let matches = matchRoutes(dataRoutes, location, basename);
|
|
3051
3055
|
|
|
3056
|
+
// SSR supports HEAD requests while SPA doesn't
|
|
3052
3057
|
if (!isValidMethod(method) && method !== "HEAD") {
|
|
3053
3058
|
let error = getInternalRouterError(405, {
|
|
3054
3059
|
method
|
|
@@ -3094,21 +3099,20 @@ function createStaticHandler(routes, opts) {
|
|
|
3094
3099
|
activeDeferreds: null
|
|
3095
3100
|
};
|
|
3096
3101
|
}
|
|
3097
|
-
|
|
3098
3102
|
let result = await queryImpl(request, location, matches, requestContext);
|
|
3099
|
-
|
|
3100
3103
|
if (isResponse(result)) {
|
|
3101
3104
|
return result;
|
|
3102
|
-
}
|
|
3105
|
+
}
|
|
3106
|
+
|
|
3107
|
+
// When returning StaticHandlerContext, we patch back in the location here
|
|
3103
3108
|
// since we need it for React Context. But this helps keep our submit and
|
|
3104
3109
|
// loadRouteData operating on a Request instead of a Location
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
3110
|
return _extends({
|
|
3108
3111
|
location,
|
|
3109
3112
|
basename
|
|
3110
3113
|
}, result);
|
|
3111
3114
|
}
|
|
3115
|
+
|
|
3112
3116
|
/**
|
|
3113
3117
|
* The queryRoute() method is intended for targeted route requests, either
|
|
3114
3118
|
* for fetch ?_data requests or resource route requests. In this case, we
|
|
@@ -3129,8 +3133,6 @@ function createStaticHandler(routes, opts) {
|
|
|
3129
3133
|
* code. Examples here are 404 and 405 errors that occur prior to reaching
|
|
3130
3134
|
* any user-defined loaders.
|
|
3131
3135
|
*/
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
3136
|
async function queryRoute(request, _temp3) {
|
|
3135
3137
|
let {
|
|
3136
3138
|
routeId,
|
|
@@ -3139,8 +3141,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3139
3141
|
let url = new URL(request.url);
|
|
3140
3142
|
let method = request.method;
|
|
3141
3143
|
let location = createLocation("", createPath(url), null, "default");
|
|
3142
|
-
let matches = matchRoutes(dataRoutes, location, basename);
|
|
3144
|
+
let matches = matchRoutes(dataRoutes, location, basename);
|
|
3143
3145
|
|
|
3146
|
+
// SSR supports HEAD requests while SPA doesn't
|
|
3144
3147
|
if (!isValidMethod(method) && method !== "HEAD" && method !== "OPTIONS") {
|
|
3145
3148
|
throw getInternalRouterError(405, {
|
|
3146
3149
|
method
|
|
@@ -3150,9 +3153,7 @@ function createStaticHandler(routes, opts) {
|
|
|
3150
3153
|
pathname: location.pathname
|
|
3151
3154
|
});
|
|
3152
3155
|
}
|
|
3153
|
-
|
|
3154
3156
|
let match = routeId ? matches.find(m => m.route.id === routeId) : getTargetMatch(matches, location);
|
|
3155
|
-
|
|
3156
3157
|
if (routeId && !match) {
|
|
3157
3158
|
throw getInternalRouterError(403, {
|
|
3158
3159
|
pathname: location.pathname,
|
|
@@ -3164,52 +3165,40 @@ function createStaticHandler(routes, opts) {
|
|
|
3164
3165
|
pathname: location.pathname
|
|
3165
3166
|
});
|
|
3166
3167
|
}
|
|
3167
|
-
|
|
3168
3168
|
let result = await queryImpl(request, location, matches, requestContext, match);
|
|
3169
|
-
|
|
3170
3169
|
if (isResponse(result)) {
|
|
3171
3170
|
return result;
|
|
3172
3171
|
}
|
|
3173
|
-
|
|
3174
3172
|
let error = result.errors ? Object.values(result.errors)[0] : undefined;
|
|
3175
|
-
|
|
3176
3173
|
if (error !== undefined) {
|
|
3177
3174
|
// If we got back result.errors, that means the loader/action threw
|
|
3178
3175
|
// _something_ that wasn't a Response, but it's not guaranteed/required
|
|
3179
3176
|
// to be an `instanceof Error` either, so we have to use throw here to
|
|
3180
3177
|
// preserve the "error" state outside of queryImpl.
|
|
3181
3178
|
throw error;
|
|
3182
|
-
}
|
|
3183
|
-
|
|
3179
|
+
}
|
|
3184
3180
|
|
|
3181
|
+
// Pick off the right state value to return
|
|
3185
3182
|
if (result.actionData) {
|
|
3186
3183
|
return Object.values(result.actionData)[0];
|
|
3187
3184
|
}
|
|
3188
|
-
|
|
3189
3185
|
if (result.loaderData) {
|
|
3190
3186
|
var _result$activeDeferre;
|
|
3191
|
-
|
|
3192
3187
|
let data = Object.values(result.loaderData)[0];
|
|
3193
|
-
|
|
3194
3188
|
if ((_result$activeDeferre = result.activeDeferreds) != null && _result$activeDeferre[match.route.id]) {
|
|
3195
3189
|
data[UNSAFE_DEFERRED_SYMBOL] = result.activeDeferreds[match.route.id];
|
|
3196
3190
|
}
|
|
3197
|
-
|
|
3198
3191
|
return data;
|
|
3199
3192
|
}
|
|
3200
|
-
|
|
3201
3193
|
return undefined;
|
|
3202
3194
|
}
|
|
3203
|
-
|
|
3204
3195
|
async function queryImpl(request, location, matches, requestContext, routeMatch) {
|
|
3205
3196
|
invariant(request.signal, "query()/queryRoute() requests must contain an AbortController signal");
|
|
3206
|
-
|
|
3207
3197
|
try {
|
|
3208
3198
|
if (isMutationMethod(request.method.toLowerCase())) {
|
|
3209
3199
|
let result = await submit(request, matches, routeMatch || getTargetMatch(matches, location), requestContext, routeMatch != null);
|
|
3210
3200
|
return result;
|
|
3211
3201
|
}
|
|
3212
|
-
|
|
3213
3202
|
let result = await loadRouteData(request, matches, requestContext, routeMatch);
|
|
3214
3203
|
return isResponse(result) ? result : _extends({}, result, {
|
|
3215
3204
|
actionData: null,
|
|
@@ -3223,47 +3212,38 @@ function createStaticHandler(routes, opts) {
|
|
|
3223
3212
|
if (e.type === ResultType.error && !isRedirectResponse(e.response)) {
|
|
3224
3213
|
throw e.response;
|
|
3225
3214
|
}
|
|
3226
|
-
|
|
3227
3215
|
return e.response;
|
|
3228
|
-
}
|
|
3216
|
+
}
|
|
3217
|
+
// Redirects are always returned since they don't propagate to catch
|
|
3229
3218
|
// boundaries
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
3219
|
if (isRedirectResponse(e)) {
|
|
3233
3220
|
return e;
|
|
3234
3221
|
}
|
|
3235
|
-
|
|
3236
3222
|
throw e;
|
|
3237
3223
|
}
|
|
3238
3224
|
}
|
|
3239
|
-
|
|
3240
3225
|
async function submit(request, matches, actionMatch, requestContext, isRouteRequest) {
|
|
3241
3226
|
let result;
|
|
3242
|
-
|
|
3243
3227
|
if (!actionMatch.route.action && !actionMatch.route.lazy) {
|
|
3244
3228
|
let error = getInternalRouterError(405, {
|
|
3245
3229
|
method: request.method,
|
|
3246
3230
|
pathname: new URL(request.url).pathname,
|
|
3247
3231
|
routeId: actionMatch.route.id
|
|
3248
3232
|
});
|
|
3249
|
-
|
|
3250
3233
|
if (isRouteRequest) {
|
|
3251
3234
|
throw error;
|
|
3252
3235
|
}
|
|
3253
|
-
|
|
3254
3236
|
result = {
|
|
3255
3237
|
type: ResultType.error,
|
|
3256
3238
|
error
|
|
3257
3239
|
};
|
|
3258
3240
|
} else {
|
|
3259
3241
|
result = await callLoaderOrAction("action", request, actionMatch, matches, manifest, mapRouteProperties, basename, true, isRouteRequest, requestContext);
|
|
3260
|
-
|
|
3261
3242
|
if (request.signal.aborted) {
|
|
3262
3243
|
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3263
3244
|
throw new Error(method + "() call aborted");
|
|
3264
3245
|
}
|
|
3265
3246
|
}
|
|
3266
|
-
|
|
3267
3247
|
if (isRedirectResult(result)) {
|
|
3268
3248
|
// Uhhhh - this should never happen, we should always throw these from
|
|
3269
3249
|
// callLoaderOrAction, but the type narrowing here keeps TS happy and we
|
|
@@ -3276,29 +3256,24 @@ function createStaticHandler(routes, opts) {
|
|
|
3276
3256
|
}
|
|
3277
3257
|
});
|
|
3278
3258
|
}
|
|
3279
|
-
|
|
3280
3259
|
if (isDeferredResult(result)) {
|
|
3281
3260
|
let error = getInternalRouterError(400, {
|
|
3282
3261
|
type: "defer-action"
|
|
3283
3262
|
});
|
|
3284
|
-
|
|
3285
3263
|
if (isRouteRequest) {
|
|
3286
3264
|
throw error;
|
|
3287
3265
|
}
|
|
3288
|
-
|
|
3289
3266
|
result = {
|
|
3290
3267
|
type: ResultType.error,
|
|
3291
3268
|
error
|
|
3292
3269
|
};
|
|
3293
3270
|
}
|
|
3294
|
-
|
|
3295
3271
|
if (isRouteRequest) {
|
|
3296
3272
|
// Note: This should only be non-Response values if we get here, since
|
|
3297
3273
|
// isRouteRequest should throw any Response received in callLoaderOrAction
|
|
3298
3274
|
if (isErrorResult(result)) {
|
|
3299
3275
|
throw result.error;
|
|
3300
3276
|
}
|
|
3301
|
-
|
|
3302
3277
|
return {
|
|
3303
3278
|
matches: [actionMatch],
|
|
3304
3279
|
loaderData: {},
|
|
@@ -3314,15 +3289,15 @@ function createStaticHandler(routes, opts) {
|
|
|
3314
3289
|
activeDeferreds: null
|
|
3315
3290
|
};
|
|
3316
3291
|
}
|
|
3317
|
-
|
|
3318
3292
|
if (isErrorResult(result)) {
|
|
3319
3293
|
// Store off the pending error - we use it to determine which loaders
|
|
3320
3294
|
// to call and will commit it when we complete the navigation
|
|
3321
3295
|
let boundaryMatch = findNearestBoundary(matches, actionMatch.route.id);
|
|
3322
3296
|
let context = await loadRouteData(request, matches, requestContext, undefined, {
|
|
3323
3297
|
[boundaryMatch.route.id]: result.error
|
|
3324
|
-
});
|
|
3298
|
+
});
|
|
3325
3299
|
|
|
3300
|
+
// action status codes take precedence over loader status codes
|
|
3326
3301
|
return _extends({}, context, {
|
|
3327
3302
|
statusCode: isRouteErrorResponse(result.error) ? result.error.status : 500,
|
|
3328
3303
|
actionData: null,
|
|
@@ -3330,9 +3305,9 @@ function createStaticHandler(routes, opts) {
|
|
|
3330
3305
|
[actionMatch.route.id]: result.headers
|
|
3331
3306
|
} : {})
|
|
3332
3307
|
});
|
|
3333
|
-
}
|
|
3334
|
-
|
|
3308
|
+
}
|
|
3335
3309
|
|
|
3310
|
+
// Create a GET request for the loaders
|
|
3336
3311
|
let loaderRequest = new Request(request.url, {
|
|
3337
3312
|
headers: request.headers,
|
|
3338
3313
|
redirect: request.redirect,
|
|
@@ -3350,10 +3325,10 @@ function createStaticHandler(routes, opts) {
|
|
|
3350
3325
|
} : {})
|
|
3351
3326
|
});
|
|
3352
3327
|
}
|
|
3353
|
-
|
|
3354
3328
|
async function loadRouteData(request, matches, requestContext, routeMatch, pendingActionError) {
|
|
3355
|
-
let isRouteRequest = routeMatch != null;
|
|
3329
|
+
let isRouteRequest = routeMatch != null;
|
|
3356
3330
|
|
|
3331
|
+
// Short circuit if we have no loaders to run (queryRoute())
|
|
3357
3332
|
if (isRouteRequest && !(routeMatch != null && routeMatch.route.loader) && !(routeMatch != null && routeMatch.route.lazy)) {
|
|
3358
3333
|
throw getInternalRouterError(400, {
|
|
3359
3334
|
method: request.method,
|
|
@@ -3361,10 +3336,10 @@ function createStaticHandler(routes, opts) {
|
|
|
3361
3336
|
routeId: routeMatch == null ? void 0 : routeMatch.route.id
|
|
3362
3337
|
});
|
|
3363
3338
|
}
|
|
3364
|
-
|
|
3365
3339
|
let requestMatches = routeMatch ? [routeMatch] : getLoaderMatchesUntilBoundary(matches, Object.keys(pendingActionError || {})[0]);
|
|
3366
|
-
let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
|
|
3340
|
+
let matchesToLoad = requestMatches.filter(m => m.route.loader || m.route.lazy);
|
|
3367
3341
|
|
|
3342
|
+
// Short circuit if we have no loaders to run (query())
|
|
3368
3343
|
if (matchesToLoad.length === 0) {
|
|
3369
3344
|
return {
|
|
3370
3345
|
matches,
|
|
@@ -3378,18 +3353,17 @@ function createStaticHandler(routes, opts) {
|
|
|
3378
3353
|
activeDeferreds: null
|
|
3379
3354
|
};
|
|
3380
3355
|
}
|
|
3381
|
-
|
|
3382
3356
|
let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, manifest, mapRouteProperties, basename, true, isRouteRequest, requestContext))]);
|
|
3383
|
-
|
|
3384
3357
|
if (request.signal.aborted) {
|
|
3385
3358
|
let method = isRouteRequest ? "queryRoute" : "query";
|
|
3386
3359
|
throw new Error(method + "() call aborted");
|
|
3387
|
-
}
|
|
3388
|
-
|
|
3360
|
+
}
|
|
3389
3361
|
|
|
3362
|
+
// Process and commit output from loaders
|
|
3390
3363
|
let activeDeferreds = new Map();
|
|
3391
|
-
let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionError, activeDeferreds);
|
|
3364
|
+
let context = processRouteLoaderData(matches, matchesToLoad, results, pendingActionError, activeDeferreds);
|
|
3392
3365
|
|
|
3366
|
+
// Add a null for any non-loader matches for proper revalidation on the client
|
|
3393
3367
|
let executedLoaders = new Set(matchesToLoad.map(match => match.route.id));
|
|
3394
3368
|
matches.forEach(match => {
|
|
3395
3369
|
if (!executedLoaders.has(match.route.id)) {
|
|
@@ -3401,13 +3375,15 @@ function createStaticHandler(routes, opts) {
|
|
|
3401
3375
|
activeDeferreds: activeDeferreds.size > 0 ? Object.fromEntries(activeDeferreds.entries()) : null
|
|
3402
3376
|
});
|
|
3403
3377
|
}
|
|
3404
|
-
|
|
3405
3378
|
return {
|
|
3406
3379
|
dataRoutes,
|
|
3407
3380
|
query,
|
|
3408
3381
|
queryRoute
|
|
3409
3382
|
};
|
|
3410
|
-
}
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
//#endregion
|
|
3386
|
+
|
|
3411
3387
|
////////////////////////////////////////////////////////////////////////////////
|
|
3412
3388
|
//#region Helpers
|
|
3413
3389
|
////////////////////////////////////////////////////////////////////////////////
|
|
@@ -3416,7 +3392,6 @@ function createStaticHandler(routes, opts) {
|
|
|
3416
3392
|
* Given an existing StaticHandlerContext and an error thrown at render time,
|
|
3417
3393
|
* provide an updated StaticHandlerContext suitable for a second SSR render
|
|
3418
3394
|
*/
|
|
3419
|
-
|
|
3420
3395
|
function getStaticContextFromError(routes, context, error) {
|
|
3421
3396
|
let newContext = _extends({}, context, {
|
|
3422
3397
|
statusCode: 500,
|
|
@@ -3424,28 +3399,22 @@ function getStaticContextFromError(routes, context, error) {
|
|
|
3424
3399
|
[context._deepestRenderedBoundaryId || routes[0].id]: error
|
|
3425
3400
|
}
|
|
3426
3401
|
});
|
|
3427
|
-
|
|
3428
3402
|
return newContext;
|
|
3429
3403
|
}
|
|
3430
|
-
|
|
3431
3404
|
function isSubmissionNavigation(opts) {
|
|
3432
3405
|
return opts != null && "formData" in opts;
|
|
3433
3406
|
}
|
|
3434
|
-
|
|
3435
3407
|
function normalizeTo(location, matches, basename, prependBasename, to, fromRouteId, relative) {
|
|
3436
3408
|
let contextualMatches;
|
|
3437
3409
|
let activeRouteMatch;
|
|
3438
|
-
|
|
3439
3410
|
if (fromRouteId != null && relative !== "path") {
|
|
3440
3411
|
// Grab matches up to the calling route so our route-relative logic is
|
|
3441
3412
|
// relative to the correct source route. When using relative:path,
|
|
3442
3413
|
// fromRouteId is ignored since that is always relative to the current
|
|
3443
3414
|
// location path
|
|
3444
3415
|
contextualMatches = [];
|
|
3445
|
-
|
|
3446
3416
|
for (let match of matches) {
|
|
3447
3417
|
contextualMatches.push(match);
|
|
3448
|
-
|
|
3449
3418
|
if (match.route.id === fromRouteId) {
|
|
3450
3419
|
activeRouteMatch = match;
|
|
3451
3420
|
break;
|
|
@@ -3454,36 +3423,36 @@ function normalizeTo(location, matches, basename, prependBasename, to, fromRoute
|
|
|
3454
3423
|
} else {
|
|
3455
3424
|
contextualMatches = matches;
|
|
3456
3425
|
activeRouteMatch = matches[matches.length - 1];
|
|
3457
|
-
}
|
|
3426
|
+
}
|
|
3458
3427
|
|
|
3428
|
+
// Resolve the relative path
|
|
3429
|
+
let path = resolveTo(to ? to : ".", getPathContributingMatches(contextualMatches).map(m => m.pathnameBase), stripBasename(location.pathname, basename) || location.pathname, relative === "path");
|
|
3459
3430
|
|
|
3460
|
-
|
|
3431
|
+
// When `to` is not specified we inherit search/hash from the current
|
|
3461
3432
|
// location, unlike when to="." and we just inherit the path.
|
|
3462
3433
|
// See https://github.com/remix-run/remix/issues/927
|
|
3463
|
-
|
|
3464
3434
|
if (to == null) {
|
|
3465
3435
|
path.search = location.search;
|
|
3466
3436
|
path.hash = location.hash;
|
|
3467
|
-
}
|
|
3468
|
-
|
|
3437
|
+
}
|
|
3469
3438
|
|
|
3439
|
+
// Add an ?index param for matched index routes if we don't already have one
|
|
3470
3440
|
if ((to == null || to === "" || to === ".") && activeRouteMatch && activeRouteMatch.route.index && !hasNakedIndexQuery(path.search)) {
|
|
3471
3441
|
path.search = path.search ? path.search.replace(/^\?/, "?index&") : "?index";
|
|
3472
|
-
}
|
|
3442
|
+
}
|
|
3443
|
+
|
|
3444
|
+
// If we're operating within a basename, prepend it to the pathname. If
|
|
3473
3445
|
// this is a root navigation, then just use the raw basename which allows
|
|
3474
3446
|
// the basename to have full control over the presence of a trailing slash
|
|
3475
3447
|
// on root actions
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
3448
|
if (prependBasename && basename !== "/") {
|
|
3479
3449
|
path.pathname = path.pathname === "/" ? basename : joinPaths([basename, path.pathname]);
|
|
3480
3450
|
}
|
|
3481
|
-
|
|
3482
3451
|
return createPath(path);
|
|
3483
|
-
}
|
|
3484
|
-
// URLSearchParams so they behave identically to links with query params
|
|
3485
|
-
|
|
3452
|
+
}
|
|
3486
3453
|
|
|
3454
|
+
// Normalize navigation options by converting formMethod=GET formData objects to
|
|
3455
|
+
// URLSearchParams so they behave identically to links with query params
|
|
3487
3456
|
function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
|
|
3488
3457
|
// Return location verbatim on non-submission navigations
|
|
3489
3458
|
if (!opts || !isSubmissionNavigation(opts)) {
|
|
@@ -3491,7 +3460,6 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
|
|
|
3491
3460
|
path
|
|
3492
3461
|
};
|
|
3493
3462
|
}
|
|
3494
|
-
|
|
3495
3463
|
if (opts.formMethod && !isValidMethod(opts.formMethod)) {
|
|
3496
3464
|
return {
|
|
3497
3465
|
path,
|
|
@@ -3499,11 +3467,10 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
|
|
|
3499
3467
|
method: opts.formMethod
|
|
3500
3468
|
})
|
|
3501
3469
|
};
|
|
3502
|
-
}
|
|
3503
|
-
|
|
3470
|
+
}
|
|
3504
3471
|
|
|
3472
|
+
// Create a Submission on non-GET navigations
|
|
3505
3473
|
let submission;
|
|
3506
|
-
|
|
3507
3474
|
if (opts.formData) {
|
|
3508
3475
|
let formMethod = opts.formMethod || "get";
|
|
3509
3476
|
submission = {
|
|
@@ -3512,53 +3479,48 @@ function normalizeNavigateOptions(normalizeFormMethod, isFetcher, path, opts) {
|
|
|
3512
3479
|
formEncType: opts && opts.formEncType || "application/x-www-form-urlencoded",
|
|
3513
3480
|
formData: opts.formData
|
|
3514
3481
|
};
|
|
3515
|
-
|
|
3516
3482
|
if (isMutationMethod(submission.formMethod)) {
|
|
3517
3483
|
return {
|
|
3518
3484
|
path,
|
|
3519
3485
|
submission
|
|
3520
3486
|
};
|
|
3521
3487
|
}
|
|
3522
|
-
}
|
|
3523
|
-
|
|
3488
|
+
}
|
|
3524
3489
|
|
|
3490
|
+
// Flatten submission onto URLSearchParams for GET submissions
|
|
3525
3491
|
let parsedPath = parsePath(path);
|
|
3526
|
-
let searchParams = convertFormDataToSearchParams(opts.formData);
|
|
3492
|
+
let searchParams = convertFormDataToSearchParams(opts.formData);
|
|
3493
|
+
// On GET navigation submissions we can drop the ?index param from the
|
|
3527
3494
|
// resulting location since all loaders will run. But fetcher GET submissions
|
|
3528
3495
|
// only run a single loader so we need to preserve any incoming ?index params
|
|
3529
|
-
|
|
3530
3496
|
if (isFetcher && parsedPath.search && hasNakedIndexQuery(parsedPath.search)) {
|
|
3531
3497
|
searchParams.append("index", "");
|
|
3532
3498
|
}
|
|
3533
|
-
|
|
3534
3499
|
parsedPath.search = "?" + searchParams;
|
|
3535
3500
|
return {
|
|
3536
3501
|
path: createPath(parsedPath),
|
|
3537
3502
|
submission
|
|
3538
3503
|
};
|
|
3539
|
-
}
|
|
3540
|
-
// render so we don't need to load them
|
|
3541
|
-
|
|
3504
|
+
}
|
|
3542
3505
|
|
|
3506
|
+
// Filter out all routes below any caught error as they aren't going to
|
|
3507
|
+
// render so we don't need to load them
|
|
3543
3508
|
function getLoaderMatchesUntilBoundary(matches, boundaryId) {
|
|
3544
3509
|
let boundaryMatches = matches;
|
|
3545
|
-
|
|
3546
3510
|
if (boundaryId) {
|
|
3547
3511
|
let index = matches.findIndex(m => m.route.id === boundaryId);
|
|
3548
|
-
|
|
3549
3512
|
if (index >= 0) {
|
|
3550
3513
|
boundaryMatches = matches.slice(0, index);
|
|
3551
3514
|
}
|
|
3552
3515
|
}
|
|
3553
|
-
|
|
3554
3516
|
return boundaryMatches;
|
|
3555
3517
|
}
|
|
3556
|
-
|
|
3557
3518
|
function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, pendingActionData, pendingError) {
|
|
3558
3519
|
let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
|
|
3559
3520
|
let currentUrl = history.createURL(state.location);
|
|
3560
|
-
let nextUrl = history.createURL(location);
|
|
3521
|
+
let nextUrl = history.createURL(location);
|
|
3561
3522
|
|
|
3523
|
+
// Pick navigation matches that are net-new or qualify for revalidation
|
|
3562
3524
|
let boundaryId = pendingError ? Object.keys(pendingError)[0] : undefined;
|
|
3563
3525
|
let boundaryMatches = getLoaderMatchesUntilBoundary(matches, boundaryId);
|
|
3564
3526
|
let navigationMatches = boundaryMatches.filter((match, index) => {
|
|
@@ -3566,20 +3528,19 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
|
|
|
3566
3528
|
// We haven't loaded this route yet so we don't know if it's got a loader!
|
|
3567
3529
|
return true;
|
|
3568
3530
|
}
|
|
3569
|
-
|
|
3570
3531
|
if (match.route.loader == null) {
|
|
3571
3532
|
return false;
|
|
3572
|
-
}
|
|
3573
|
-
|
|
3533
|
+
}
|
|
3574
3534
|
|
|
3535
|
+
// Always call the loader on new route instances and pending defer cancellations
|
|
3575
3536
|
if (isNewLoader(state.loaderData, state.matches[index], match) || cancelledDeferredRoutes.some(id => id === match.route.id)) {
|
|
3576
3537
|
return true;
|
|
3577
|
-
}
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
// This is the default implementation for when we revalidate. If the route
|
|
3578
3541
|
// provides it's own implementation, then we give them full control but
|
|
3579
3542
|
// provide this value so they can leverage it if needed after they check
|
|
3580
3543
|
// their own specific use cases
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
3544
|
let currentRouteMatch = state.matches[index];
|
|
3584
3545
|
let nextRouteMatch = match;
|
|
3585
3546
|
return shouldRevalidateLoader(match, _extends({
|
|
@@ -3589,23 +3550,27 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
|
|
|
3589
3550
|
nextParams: nextRouteMatch.params
|
|
3590
3551
|
}, submission, {
|
|
3591
3552
|
actionResult,
|
|
3592
|
-
defaultShouldRevalidate:
|
|
3593
|
-
|
|
3594
|
-
|
|
3553
|
+
defaultShouldRevalidate:
|
|
3554
|
+
// Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
3555
|
+
isRevalidationRequired ||
|
|
3556
|
+
// Clicked the same link, resubmitted a GET form
|
|
3557
|
+
currentUrl.pathname + currentUrl.search === nextUrl.pathname + nextUrl.search ||
|
|
3558
|
+
// Search params affect all loaders
|
|
3595
3559
|
currentUrl.search !== nextUrl.search || isNewRouteInstance(currentRouteMatch, nextRouteMatch)
|
|
3596
3560
|
}));
|
|
3597
|
-
});
|
|
3561
|
+
});
|
|
3598
3562
|
|
|
3563
|
+
// Pick fetcher.loads that need to be revalidated
|
|
3599
3564
|
let revalidatingFetchers = [];
|
|
3600
3565
|
fetchLoadMatches.forEach((f, key) => {
|
|
3601
3566
|
// Don't revalidate if fetcher won't be present in the subsequent render
|
|
3602
3567
|
if (!matches.some(m => m.route.id === f.routeId)) {
|
|
3603
3568
|
return;
|
|
3604
3569
|
}
|
|
3570
|
+
let fetcherMatches = matchRoutes(routesToUse, f.path, basename);
|
|
3605
3571
|
|
|
3606
|
-
|
|
3572
|
+
// If the fetcher path no longer matches, push it in with null matches so
|
|
3607
3573
|
// we can trigger a 404 in callLoadersAndMaybeResolveData
|
|
3608
|
-
|
|
3609
3574
|
if (!fetcherMatches) {
|
|
3610
3575
|
revalidatingFetchers.push({
|
|
3611
3576
|
key,
|
|
@@ -3617,9 +3582,7 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
|
|
|
3617
3582
|
});
|
|
3618
3583
|
return;
|
|
3619
3584
|
}
|
|
3620
|
-
|
|
3621
3585
|
let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
|
|
3622
|
-
|
|
3623
3586
|
if (cancelledFetcherLoads.includes(key)) {
|
|
3624
3587
|
revalidatingFetchers.push({
|
|
3625
3588
|
key,
|
|
@@ -3630,12 +3593,12 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
|
|
|
3630
3593
|
controller: new AbortController()
|
|
3631
3594
|
});
|
|
3632
3595
|
return;
|
|
3633
|
-
}
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
// Revalidating fetchers are decoupled from the route matches since they
|
|
3634
3599
|
// hit a static href, so they _always_ check shouldRevalidate and the
|
|
3635
3600
|
// default is strictly if a revalidation is explicitly required (action
|
|
3636
3601
|
// submissions, useRevalidator, X-Remix-Revalidate).
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
3602
|
let shouldRevalidate = shouldRevalidateLoader(fetcherMatch, _extends({
|
|
3640
3603
|
currentUrl,
|
|
3641
3604
|
currentParams: state.matches[state.matches.length - 1].params,
|
|
@@ -3646,7 +3609,6 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
|
|
|
3646
3609
|
// Forced revalidation due to submission, useRevalidator, or X-Remix-Revalidate
|
|
3647
3610
|
defaultShouldRevalidate: isRevalidationRequired
|
|
3648
3611
|
}));
|
|
3649
|
-
|
|
3650
3612
|
if (shouldRevalidate) {
|
|
3651
3613
|
revalidatingFetchers.push({
|
|
3652
3614
|
key,
|
|
@@ -3660,60 +3622,61 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
|
|
|
3660
3622
|
});
|
|
3661
3623
|
return [navigationMatches, revalidatingFetchers];
|
|
3662
3624
|
}
|
|
3663
|
-
|
|
3664
3625
|
function isNewLoader(currentLoaderData, currentMatch, match) {
|
|
3665
|
-
let isNew =
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
//
|
|
3626
|
+
let isNew =
|
|
3627
|
+
// [a] -> [a, b]
|
|
3628
|
+
!currentMatch ||
|
|
3629
|
+
// [a, b] -> [a, c]
|
|
3630
|
+
match.route.id !== currentMatch.route.id;
|
|
3669
3631
|
|
|
3670
|
-
|
|
3632
|
+
// Handle the case that we don't have data for a re-used route, potentially
|
|
3633
|
+
// from a prior error or from a cancelled pending deferred
|
|
3634
|
+
let isMissingData = currentLoaderData[match.route.id] === undefined;
|
|
3671
3635
|
|
|
3636
|
+
// Always load if this is a net-new route or we don't yet have data
|
|
3672
3637
|
return isNew || isMissingData;
|
|
3673
3638
|
}
|
|
3674
|
-
|
|
3675
3639
|
function isNewRouteInstance(currentMatch, match) {
|
|
3676
3640
|
let currentPath = currentMatch.route.path;
|
|
3677
|
-
return (
|
|
3678
|
-
|
|
3641
|
+
return (
|
|
3642
|
+
// param change for this match, /users/123 -> /users/456
|
|
3643
|
+
currentMatch.pathname !== match.pathname ||
|
|
3644
|
+
// splat param changed, which is not present in match.path
|
|
3679
3645
|
// e.g. /files/images/avatar.jpg -> files/finances.xls
|
|
3680
3646
|
currentPath != null && currentPath.endsWith("*") && currentMatch.params["*"] !== match.params["*"]
|
|
3681
3647
|
);
|
|
3682
3648
|
}
|
|
3683
|
-
|
|
3684
3649
|
function shouldRevalidateLoader(loaderMatch, arg) {
|
|
3685
3650
|
if (loaderMatch.route.shouldRevalidate) {
|
|
3686
3651
|
let routeChoice = loaderMatch.route.shouldRevalidate(arg);
|
|
3687
|
-
|
|
3688
3652
|
if (typeof routeChoice === "boolean") {
|
|
3689
3653
|
return routeChoice;
|
|
3690
3654
|
}
|
|
3691
3655
|
}
|
|
3692
|
-
|
|
3693
3656
|
return arg.defaultShouldRevalidate;
|
|
3694
3657
|
}
|
|
3658
|
+
|
|
3695
3659
|
/**
|
|
3696
3660
|
* Execute route.lazy() methods to lazily load route modules (loader, action,
|
|
3697
3661
|
* shouldRevalidate) and update the routeManifest in place which shares objects
|
|
3698
3662
|
* with dataRoutes so those get updated as well.
|
|
3699
3663
|
*/
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
3664
|
async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
|
|
3703
3665
|
if (!route.lazy) {
|
|
3704
3666
|
return;
|
|
3705
3667
|
}
|
|
3668
|
+
let lazyRoute = await route.lazy();
|
|
3706
3669
|
|
|
3707
|
-
|
|
3670
|
+
// If the lazy route function was executed and removed by another parallel
|
|
3708
3671
|
// call then we can return - first lazy() to finish wins because the return
|
|
3709
3672
|
// value of lazy is expected to be static
|
|
3710
|
-
|
|
3711
3673
|
if (!route.lazy) {
|
|
3712
3674
|
return;
|
|
3713
3675
|
}
|
|
3714
|
-
|
|
3715
3676
|
let routeToUpdate = manifest[route.id];
|
|
3716
|
-
invariant(routeToUpdate, "No route found in manifest");
|
|
3677
|
+
invariant(routeToUpdate, "No route found in manifest");
|
|
3678
|
+
|
|
3679
|
+
// Update the route in place. This should be safe because there's no way
|
|
3717
3680
|
// we could yet be sitting on this route as we can't get there without
|
|
3718
3681
|
// resolving lazy() first.
|
|
3719
3682
|
//
|
|
@@ -3721,52 +3684,45 @@ async function loadLazyRouteModule(route, mapRouteProperties, manifest) {
|
|
|
3721
3684
|
// on the route being updated. The main concern boils down to "does this
|
|
3722
3685
|
// mutation affect any ongoing navigations or any current state.matches
|
|
3723
3686
|
// values?". If not, it should be safe to update in place.
|
|
3724
|
-
|
|
3725
3687
|
let routeUpdates = {};
|
|
3726
|
-
|
|
3727
3688
|
for (let lazyRouteProperty in lazyRoute) {
|
|
3728
3689
|
let staticRouteValue = routeToUpdate[lazyRouteProperty];
|
|
3729
|
-
let isPropertyStaticallyDefined = staticRouteValue !== undefined &&
|
|
3690
|
+
let isPropertyStaticallyDefined = staticRouteValue !== undefined &&
|
|
3691
|
+
// This property isn't static since it should always be updated based
|
|
3730
3692
|
// on the route updates
|
|
3731
3693
|
lazyRouteProperty !== "hasErrorBoundary";
|
|
3732
3694
|
warning(!isPropertyStaticallyDefined, "Route \"" + routeToUpdate.id + "\" has a static property \"" + lazyRouteProperty + "\" " + "defined but its lazy function is also returning a value for this property. " + ("The lazy route property \"" + lazyRouteProperty + "\" will be ignored."));
|
|
3733
|
-
|
|
3734
3695
|
if (!isPropertyStaticallyDefined && !immutableRouteKeys.has(lazyRouteProperty)) {
|
|
3735
3696
|
routeUpdates[lazyRouteProperty] = lazyRoute[lazyRouteProperty];
|
|
3736
3697
|
}
|
|
3737
|
-
}
|
|
3738
|
-
// the updated version to mapRouteProperties
|
|
3698
|
+
}
|
|
3739
3699
|
|
|
3700
|
+
// Mutate the route with the provided updates. Do this first so we pass
|
|
3701
|
+
// the updated version to mapRouteProperties
|
|
3702
|
+
Object.assign(routeToUpdate, routeUpdates);
|
|
3740
3703
|
|
|
3741
|
-
|
|
3704
|
+
// Mutate the `hasErrorBoundary` property on the route based on the route
|
|
3742
3705
|
// updates and remove the `lazy` function so we don't resolve the lazy
|
|
3743
3706
|
// route again.
|
|
3744
|
-
|
|
3745
3707
|
Object.assign(routeToUpdate, _extends({}, mapRouteProperties(routeToUpdate), {
|
|
3746
3708
|
lazy: undefined
|
|
3747
3709
|
}));
|
|
3748
3710
|
}
|
|
3749
|
-
|
|
3750
3711
|
async function callLoaderOrAction(type, request, match, matches, manifest, mapRouteProperties, basename, isStaticRequest, isRouteRequest, requestContext) {
|
|
3751
3712
|
if (isStaticRequest === void 0) {
|
|
3752
3713
|
isStaticRequest = false;
|
|
3753
3714
|
}
|
|
3754
|
-
|
|
3755
3715
|
if (isRouteRequest === void 0) {
|
|
3756
3716
|
isRouteRequest = false;
|
|
3757
3717
|
}
|
|
3758
|
-
|
|
3759
3718
|
let resultType;
|
|
3760
3719
|
let result;
|
|
3761
3720
|
let onReject;
|
|
3762
|
-
|
|
3763
3721
|
let runHandler = handler => {
|
|
3764
3722
|
// Setup a promise we can race against so that abort signals short circuit
|
|
3765
3723
|
let reject;
|
|
3766
3724
|
let abortPromise = new Promise((_, r) => reject = r);
|
|
3767
|
-
|
|
3768
3725
|
onReject = () => reject();
|
|
3769
|
-
|
|
3770
3726
|
request.signal.addEventListener("abort", onReject);
|
|
3771
3727
|
return Promise.race([handler({
|
|
3772
3728
|
request,
|
|
@@ -3774,10 +3730,8 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3774
3730
|
context: requestContext
|
|
3775
3731
|
}), abortPromise]);
|
|
3776
3732
|
};
|
|
3777
|
-
|
|
3778
3733
|
try {
|
|
3779
3734
|
let handler = match.route[type];
|
|
3780
|
-
|
|
3781
3735
|
if (match.route.lazy) {
|
|
3782
3736
|
if (handler) {
|
|
3783
3737
|
// Run statically defined handler in parallel with lazy()
|
|
@@ -3787,7 +3741,6 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3787
3741
|
// Load lazy route module, then run any returned handler
|
|
3788
3742
|
await loadLazyRouteModule(match.route, mapRouteProperties, manifest);
|
|
3789
3743
|
handler = match.route[type];
|
|
3790
|
-
|
|
3791
3744
|
if (handler) {
|
|
3792
3745
|
// Handler still run even if we got interrupted to maintain consistency
|
|
3793
3746
|
// with un-abortable behavior of handler execution on non-lazy or
|
|
@@ -3819,7 +3772,6 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3819
3772
|
} else {
|
|
3820
3773
|
result = await runHandler(handler);
|
|
3821
3774
|
}
|
|
3822
|
-
|
|
3823
3775
|
invariant(result !== undefined, "You defined " + (type === "action" ? "an action" : "a loader") + " for route " + ("\"" + match.route.id + "\" but didn't return anything from your `" + type + "` ") + "function. Please return a value or `null`.");
|
|
3824
3776
|
} catch (e) {
|
|
3825
3777
|
resultType = ResultType.error;
|
|
@@ -3829,14 +3781,15 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3829
3781
|
request.signal.removeEventListener("abort", onReject);
|
|
3830
3782
|
}
|
|
3831
3783
|
}
|
|
3832
|
-
|
|
3833
3784
|
if (isResponse(result)) {
|
|
3834
|
-
let status = result.status;
|
|
3785
|
+
let status = result.status;
|
|
3835
3786
|
|
|
3787
|
+
// Process redirects
|
|
3836
3788
|
if (redirectStatusCodes.has(status)) {
|
|
3837
3789
|
let location = result.headers.get("Location");
|
|
3838
|
-
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
3790
|
+
invariant(location, "Redirects returned/thrown from loaders/actions must have a Location header");
|
|
3839
3791
|
|
|
3792
|
+
// Support relative routing in internal redirects
|
|
3840
3793
|
if (!ABSOLUTE_URL_REGEX.test(location)) {
|
|
3841
3794
|
location = normalizeTo(new URL(request.url), matches.slice(0, matches.indexOf(match) + 1), basename, true, location);
|
|
3842
3795
|
} else if (!isStaticRequest) {
|
|
@@ -3846,32 +3799,30 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3846
3799
|
let currentUrl = new URL(request.url);
|
|
3847
3800
|
let url = location.startsWith("//") ? new URL(currentUrl.protocol + location) : new URL(location);
|
|
3848
3801
|
let isSameBasename = stripBasename(url.pathname, basename) != null;
|
|
3849
|
-
|
|
3850
3802
|
if (url.origin === currentUrl.origin && isSameBasename) {
|
|
3851
3803
|
location = url.pathname + url.search + url.hash;
|
|
3852
3804
|
}
|
|
3853
|
-
}
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
// Don't process redirects in the router during static requests requests.
|
|
3854
3808
|
// Instead, throw the Response and let the server handle it with an HTTP
|
|
3855
3809
|
// redirect. We also update the Location header in place in this flow so
|
|
3856
3810
|
// basename and relative routing is taken into account
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
3811
|
if (isStaticRequest) {
|
|
3860
3812
|
result.headers.set("Location", location);
|
|
3861
3813
|
throw result;
|
|
3862
3814
|
}
|
|
3863
|
-
|
|
3864
3815
|
return {
|
|
3865
3816
|
type: ResultType.redirect,
|
|
3866
3817
|
status,
|
|
3867
3818
|
location,
|
|
3868
3819
|
revalidate: result.headers.get("X-Remix-Revalidate") !== null
|
|
3869
3820
|
};
|
|
3870
|
-
}
|
|
3821
|
+
}
|
|
3822
|
+
|
|
3823
|
+
// For SSR single-route requests, we want to hand Responses back directly
|
|
3871
3824
|
// without unwrapping. We do this with the QueryRouteResponse wrapper
|
|
3872
3825
|
// interface so we can know whether it was returned or thrown
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
3826
|
if (isRouteRequest) {
|
|
3876
3827
|
// eslint-disable-next-line no-throw-literal
|
|
3877
3828
|
throw {
|
|
@@ -3879,17 +3830,15 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3879
3830
|
response: result
|
|
3880
3831
|
};
|
|
3881
3832
|
}
|
|
3882
|
-
|
|
3883
3833
|
let data;
|
|
3884
|
-
let contentType = result.headers.get("Content-Type");
|
|
3834
|
+
let contentType = result.headers.get("Content-Type");
|
|
3835
|
+
// Check between word boundaries instead of startsWith() due to the last
|
|
3885
3836
|
// paragraph of https://httpwg.org/specs/rfc9110.html#field.content-type
|
|
3886
|
-
|
|
3887
3837
|
if (contentType && /\bapplication\/json\b/.test(contentType)) {
|
|
3888
3838
|
data = await result.json();
|
|
3889
3839
|
} else {
|
|
3890
3840
|
data = await result.text();
|
|
3891
3841
|
}
|
|
3892
|
-
|
|
3893
3842
|
if (resultType === ResultType.error) {
|
|
3894
3843
|
return {
|
|
3895
3844
|
type: resultType,
|
|
@@ -3897,7 +3846,6 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3897
3846
|
headers: result.headers
|
|
3898
3847
|
};
|
|
3899
3848
|
}
|
|
3900
|
-
|
|
3901
3849
|
return {
|
|
3902
3850
|
type: ResultType.data,
|
|
3903
3851
|
data,
|
|
@@ -3905,17 +3853,14 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3905
3853
|
headers: result.headers
|
|
3906
3854
|
};
|
|
3907
3855
|
}
|
|
3908
|
-
|
|
3909
3856
|
if (resultType === ResultType.error) {
|
|
3910
3857
|
return {
|
|
3911
3858
|
type: resultType,
|
|
3912
3859
|
error: result
|
|
3913
3860
|
};
|
|
3914
3861
|
}
|
|
3915
|
-
|
|
3916
3862
|
if (isDeferredData(result)) {
|
|
3917
3863
|
var _result$init, _result$init2;
|
|
3918
|
-
|
|
3919
3864
|
return {
|
|
3920
3865
|
type: ResultType.deferred,
|
|
3921
3866
|
deferredData: result,
|
|
@@ -3923,90 +3868,84 @@ async function callLoaderOrAction(type, request, match, matches, manifest, mapRo
|
|
|
3923
3868
|
headers: ((_result$init2 = result.init) == null ? void 0 : _result$init2.headers) && new Headers(result.init.headers)
|
|
3924
3869
|
};
|
|
3925
3870
|
}
|
|
3926
|
-
|
|
3927
3871
|
return {
|
|
3928
3872
|
type: ResultType.data,
|
|
3929
3873
|
data: result
|
|
3930
3874
|
};
|
|
3931
|
-
}
|
|
3875
|
+
}
|
|
3876
|
+
|
|
3877
|
+
// Utility method for creating the Request instances for loaders/actions during
|
|
3932
3878
|
// client-side navigations and fetches. During SSR we will always have a
|
|
3933
3879
|
// Request instance from the static handler (query/queryRoute)
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
3880
|
function createClientSideRequest(history, location, signal, submission) {
|
|
3937
3881
|
let url = history.createURL(stripHashFromPath(location)).toString();
|
|
3938
3882
|
let init = {
|
|
3939
3883
|
signal
|
|
3940
3884
|
};
|
|
3941
|
-
|
|
3942
3885
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
3943
3886
|
let {
|
|
3944
3887
|
formMethod,
|
|
3945
3888
|
formEncType,
|
|
3946
3889
|
formData
|
|
3947
|
-
} = submission;
|
|
3890
|
+
} = submission;
|
|
3891
|
+
// Didn't think we needed this but it turns out unlike other methods, patch
|
|
3948
3892
|
// won't be properly normalized to uppercase and results in a 405 error.
|
|
3949
3893
|
// See: https://fetch.spec.whatwg.org/#concept-method
|
|
3950
|
-
|
|
3951
3894
|
init.method = formMethod.toUpperCase();
|
|
3952
3895
|
init.body = formEncType === "application/x-www-form-urlencoded" ? convertFormDataToSearchParams(formData) : formData;
|
|
3953
|
-
}
|
|
3954
|
-
|
|
3896
|
+
}
|
|
3955
3897
|
|
|
3898
|
+
// Content-Type is inferred (https://fetch.spec.whatwg.org/#dom-request)
|
|
3956
3899
|
return new Request(url, init);
|
|
3957
3900
|
}
|
|
3958
|
-
|
|
3959
3901
|
function convertFormDataToSearchParams(formData) {
|
|
3960
3902
|
let searchParams = new URLSearchParams();
|
|
3961
|
-
|
|
3962
3903
|
for (let [key, value] of formData.entries()) {
|
|
3963
3904
|
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#converting-an-entry-list-to-a-list-of-name-value-pairs
|
|
3964
3905
|
searchParams.append(key, value instanceof File ? value.name : value);
|
|
3965
3906
|
}
|
|
3966
|
-
|
|
3967
3907
|
return searchParams;
|
|
3968
3908
|
}
|
|
3969
|
-
|
|
3970
3909
|
function processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds) {
|
|
3971
3910
|
// Fill in loaderData/errors from our loaders
|
|
3972
3911
|
let loaderData = {};
|
|
3973
3912
|
let errors = null;
|
|
3974
3913
|
let statusCode;
|
|
3975
3914
|
let foundError = false;
|
|
3976
|
-
let loaderHeaders = {};
|
|
3915
|
+
let loaderHeaders = {};
|
|
3977
3916
|
|
|
3917
|
+
// Process loader results into state.loaderData/state.errors
|
|
3978
3918
|
results.forEach((result, index) => {
|
|
3979
3919
|
let id = matchesToLoad[index].route.id;
|
|
3980
3920
|
invariant(!isRedirectResult(result), "Cannot handle redirect results in processLoaderData");
|
|
3981
|
-
|
|
3982
3921
|
if (isErrorResult(result)) {
|
|
3983
3922
|
// Look upwards from the matched route for the closest ancestor
|
|
3984
3923
|
// error boundary, defaulting to the root match
|
|
3985
3924
|
let boundaryMatch = findNearestBoundary(matches, id);
|
|
3986
|
-
let error = result.error;
|
|
3925
|
+
let error = result.error;
|
|
3926
|
+
// If we have a pending action error, we report it at the highest-route
|
|
3987
3927
|
// that throws a loader error, and then clear it out to indicate that
|
|
3988
3928
|
// it was consumed
|
|
3989
|
-
|
|
3990
3929
|
if (pendingError) {
|
|
3991
3930
|
error = Object.values(pendingError)[0];
|
|
3992
3931
|
pendingError = undefined;
|
|
3993
3932
|
}
|
|
3933
|
+
errors = errors || {};
|
|
3994
3934
|
|
|
3995
|
-
|
|
3996
|
-
|
|
3935
|
+
// Prefer higher error values if lower errors bubble to the same boundary
|
|
3997
3936
|
if (errors[boundaryMatch.route.id] == null) {
|
|
3998
3937
|
errors[boundaryMatch.route.id] = error;
|
|
3999
|
-
}
|
|
3938
|
+
}
|
|
4000
3939
|
|
|
3940
|
+
// Clear our any prior loaderData for the throwing route
|
|
3941
|
+
loaderData[id] = undefined;
|
|
4001
3942
|
|
|
4002
|
-
|
|
3943
|
+
// Once we find our first (highest) error, we set the status code and
|
|
4003
3944
|
// prevent deeper status codes from overriding
|
|
4004
|
-
|
|
4005
3945
|
if (!foundError) {
|
|
4006
3946
|
foundError = true;
|
|
4007
3947
|
statusCode = isRouteErrorResponse(result.error) ? result.error.status : 500;
|
|
4008
3948
|
}
|
|
4009
|
-
|
|
4010
3949
|
if (result.headers) {
|
|
4011
3950
|
loaderHeaders[id] = result.headers;
|
|
4012
3951
|
}
|
|
@@ -4016,27 +3955,26 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
4016
3955
|
loaderData[id] = result.deferredData.data;
|
|
4017
3956
|
} else {
|
|
4018
3957
|
loaderData[id] = result.data;
|
|
4019
|
-
}
|
|
4020
|
-
// loaders are successful we take the deepest status code.
|
|
4021
|
-
|
|
3958
|
+
}
|
|
4022
3959
|
|
|
3960
|
+
// Error status codes always override success status codes, but if all
|
|
3961
|
+
// loaders are successful we take the deepest status code.
|
|
4023
3962
|
if (result.statusCode != null && result.statusCode !== 200 && !foundError) {
|
|
4024
3963
|
statusCode = result.statusCode;
|
|
4025
3964
|
}
|
|
4026
|
-
|
|
4027
3965
|
if (result.headers) {
|
|
4028
3966
|
loaderHeaders[id] = result.headers;
|
|
4029
3967
|
}
|
|
4030
3968
|
}
|
|
4031
|
-
});
|
|
3969
|
+
});
|
|
3970
|
+
|
|
3971
|
+
// If we didn't consume the pending action error (i.e., all loaders
|
|
4032
3972
|
// resolved), then consume it here. Also clear out any loaderData for the
|
|
4033
3973
|
// throwing route
|
|
4034
|
-
|
|
4035
3974
|
if (pendingError) {
|
|
4036
3975
|
errors = pendingError;
|
|
4037
3976
|
loaderData[Object.keys(pendingError)[0]] = undefined;
|
|
4038
3977
|
}
|
|
4039
|
-
|
|
4040
3978
|
return {
|
|
4041
3979
|
loaderData,
|
|
4042
3980
|
errors,
|
|
@@ -4044,13 +3982,13 @@ function processRouteLoaderData(matches, matchesToLoad, results, pendingError, a
|
|
|
4044
3982
|
loaderHeaders
|
|
4045
3983
|
};
|
|
4046
3984
|
}
|
|
4047
|
-
|
|
4048
3985
|
function processLoaderData(state, matches, matchesToLoad, results, pendingError, revalidatingFetchers, fetcherResults, activeDeferreds) {
|
|
4049
3986
|
let {
|
|
4050
3987
|
loaderData,
|
|
4051
3988
|
errors
|
|
4052
|
-
} = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds);
|
|
3989
|
+
} = processRouteLoaderData(matches, matchesToLoad, results, pendingError, activeDeferreds);
|
|
4053
3990
|
|
|
3991
|
+
// Process results from our revalidating fetchers
|
|
4054
3992
|
for (let index = 0; index < revalidatingFetchers.length; index++) {
|
|
4055
3993
|
let {
|
|
4056
3994
|
key,
|
|
@@ -4058,20 +3996,19 @@ function processLoaderData(state, matches, matchesToLoad, results, pendingError,
|
|
|
4058
3996
|
controller
|
|
4059
3997
|
} = revalidatingFetchers[index];
|
|
4060
3998
|
invariant(fetcherResults !== undefined && fetcherResults[index] !== undefined, "Did not find corresponding fetcher result");
|
|
4061
|
-
let result = fetcherResults[index];
|
|
3999
|
+
let result = fetcherResults[index];
|
|
4062
4000
|
|
|
4001
|
+
// Process fetcher non-redirect errors
|
|
4063
4002
|
if (controller && controller.signal.aborted) {
|
|
4064
4003
|
// Nothing to do for aborted fetchers
|
|
4065
4004
|
continue;
|
|
4066
4005
|
} else if (isErrorResult(result)) {
|
|
4067
4006
|
let boundaryMatch = findNearestBoundary(state.matches, match == null ? void 0 : match.route.id);
|
|
4068
|
-
|
|
4069
4007
|
if (!(errors && errors[boundaryMatch.route.id])) {
|
|
4070
4008
|
errors = _extends({}, errors, {
|
|
4071
4009
|
[boundaryMatch.route.id]: result.error
|
|
4072
4010
|
});
|
|
4073
4011
|
}
|
|
4074
|
-
|
|
4075
4012
|
state.fetchers.delete(key);
|
|
4076
4013
|
} else if (isRedirectResult(result)) {
|
|
4077
4014
|
// Should never get here, redirects should get processed above, but we
|
|
@@ -4094,19 +4031,15 @@ function processLoaderData(state, matches, matchesToLoad, results, pendingError,
|
|
|
4094
4031
|
state.fetchers.set(key, doneFetcher);
|
|
4095
4032
|
}
|
|
4096
4033
|
}
|
|
4097
|
-
|
|
4098
4034
|
return {
|
|
4099
4035
|
loaderData,
|
|
4100
4036
|
errors
|
|
4101
4037
|
};
|
|
4102
4038
|
}
|
|
4103
|
-
|
|
4104
4039
|
function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
|
|
4105
4040
|
let mergedLoaderData = _extends({}, newLoaderData);
|
|
4106
|
-
|
|
4107
4041
|
for (let match of matches) {
|
|
4108
4042
|
let id = match.route.id;
|
|
4109
|
-
|
|
4110
4043
|
if (newLoaderData.hasOwnProperty(id)) {
|
|
4111
4044
|
if (newLoaderData[id] !== undefined) {
|
|
4112
4045
|
mergedLoaderData[id] = newLoaderData[id];
|
|
@@ -4116,24 +4049,21 @@ function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
|
|
|
4116
4049
|
// wasn't removed by HMR
|
|
4117
4050
|
mergedLoaderData[id] = loaderData[id];
|
|
4118
4051
|
}
|
|
4119
|
-
|
|
4120
4052
|
if (errors && errors.hasOwnProperty(id)) {
|
|
4121
4053
|
// Don't keep any loader data below the boundary
|
|
4122
4054
|
break;
|
|
4123
4055
|
}
|
|
4124
4056
|
}
|
|
4125
|
-
|
|
4126
4057
|
return mergedLoaderData;
|
|
4127
|
-
}
|
|
4058
|
+
}
|
|
4059
|
+
|
|
4060
|
+
// Find the nearest error boundary, looking upwards from the leaf route (or the
|
|
4128
4061
|
// route specified by routeId) for the closest ancestor error boundary,
|
|
4129
4062
|
// defaulting to the root match
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
4063
|
function findNearestBoundary(matches, routeId) {
|
|
4133
4064
|
let eligibleMatches = routeId ? matches.slice(0, matches.findIndex(m => m.route.id === routeId) + 1) : [...matches];
|
|
4134
4065
|
return eligibleMatches.reverse().find(m => m.route.hasErrorBoundary === true) || matches[0];
|
|
4135
4066
|
}
|
|
4136
|
-
|
|
4137
4067
|
function getShortCircuitMatches(routes) {
|
|
4138
4068
|
// Prefer a root layout route if present, otherwise shim in a route object
|
|
4139
4069
|
let route = routes.find(r => r.index || !r.path || r.path === "/") || {
|
|
@@ -4149,7 +4079,6 @@ function getShortCircuitMatches(routes) {
|
|
|
4149
4079
|
route
|
|
4150
4080
|
};
|
|
4151
4081
|
}
|
|
4152
|
-
|
|
4153
4082
|
function getInternalRouterError(status, _temp4) {
|
|
4154
4083
|
let {
|
|
4155
4084
|
pathname,
|
|
@@ -4159,10 +4088,8 @@ function getInternalRouterError(status, _temp4) {
|
|
|
4159
4088
|
} = _temp4 === void 0 ? {} : _temp4;
|
|
4160
4089
|
let statusText = "Unknown Server Error";
|
|
4161
4090
|
let errorMessage = "Unknown @remix-run/router error";
|
|
4162
|
-
|
|
4163
4091
|
if (status === 400) {
|
|
4164
4092
|
statusText = "Bad Request";
|
|
4165
|
-
|
|
4166
4093
|
if (method && pathname && routeId) {
|
|
4167
4094
|
errorMessage = "You made a " + method + " request to \"" + pathname + "\" but " + ("did not provide a `loader` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
|
|
4168
4095
|
} else if (type === "defer-action") {
|
|
@@ -4176,40 +4103,34 @@ function getInternalRouterError(status, _temp4) {
|
|
|
4176
4103
|
errorMessage = "No route matches URL \"" + pathname + "\"";
|
|
4177
4104
|
} else if (status === 405) {
|
|
4178
4105
|
statusText = "Method Not Allowed";
|
|
4179
|
-
|
|
4180
4106
|
if (method && pathname && routeId) {
|
|
4181
4107
|
errorMessage = "You made a " + method.toUpperCase() + " request to \"" + pathname + "\" but " + ("did not provide an `action` for route \"" + routeId + "\", ") + "so there is no way to handle the request.";
|
|
4182
4108
|
} else if (method) {
|
|
4183
4109
|
errorMessage = "Invalid request method \"" + method.toUpperCase() + "\"";
|
|
4184
4110
|
}
|
|
4185
4111
|
}
|
|
4186
|
-
|
|
4187
4112
|
return new ErrorResponse(status || 500, statusText, new Error(errorMessage), true);
|
|
4188
|
-
}
|
|
4189
|
-
|
|
4113
|
+
}
|
|
4190
4114
|
|
|
4115
|
+
// Find any returned redirect errors, starting from the lowest match
|
|
4191
4116
|
function findRedirect(results) {
|
|
4192
4117
|
for (let i = results.length - 1; i >= 0; i--) {
|
|
4193
4118
|
let result = results[i];
|
|
4194
|
-
|
|
4195
4119
|
if (isRedirectResult(result)) {
|
|
4196
4120
|
return result;
|
|
4197
4121
|
}
|
|
4198
4122
|
}
|
|
4199
4123
|
}
|
|
4200
|
-
|
|
4201
4124
|
function stripHashFromPath(path) {
|
|
4202
4125
|
let parsedPath = typeof path === "string" ? parsePath(path) : path;
|
|
4203
4126
|
return createPath(_extends({}, parsedPath, {
|
|
4204
4127
|
hash: ""
|
|
4205
4128
|
}));
|
|
4206
4129
|
}
|
|
4207
|
-
|
|
4208
4130
|
function isHashChangeOnly(a, b) {
|
|
4209
4131
|
if (a.pathname !== b.pathname || a.search !== b.search) {
|
|
4210
4132
|
return false;
|
|
4211
4133
|
}
|
|
4212
|
-
|
|
4213
4134
|
if (a.hash === "") {
|
|
4214
4135
|
// /page -> /page#hash
|
|
4215
4136
|
return b.hash !== "";
|
|
@@ -4219,70 +4140,57 @@ function isHashChangeOnly(a, b) {
|
|
|
4219
4140
|
} else if (b.hash !== "") {
|
|
4220
4141
|
// /page#hash -> /page#other
|
|
4221
4142
|
return true;
|
|
4222
|
-
}
|
|
4223
|
-
// /page#hash -> /page
|
|
4224
|
-
|
|
4143
|
+
}
|
|
4225
4144
|
|
|
4145
|
+
// If the hash is removed the browser will re-perform a request to the server
|
|
4146
|
+
// /page#hash -> /page
|
|
4226
4147
|
return false;
|
|
4227
4148
|
}
|
|
4228
|
-
|
|
4229
4149
|
function isDeferredResult(result) {
|
|
4230
4150
|
return result.type === ResultType.deferred;
|
|
4231
4151
|
}
|
|
4232
|
-
|
|
4233
4152
|
function isErrorResult(result) {
|
|
4234
4153
|
return result.type === ResultType.error;
|
|
4235
4154
|
}
|
|
4236
|
-
|
|
4237
4155
|
function isRedirectResult(result) {
|
|
4238
4156
|
return (result && result.type) === ResultType.redirect;
|
|
4239
4157
|
}
|
|
4240
|
-
|
|
4241
4158
|
function isDeferredData(value) {
|
|
4242
4159
|
let deferred = value;
|
|
4243
4160
|
return deferred && typeof deferred === "object" && typeof deferred.data === "object" && typeof deferred.subscribe === "function" && typeof deferred.cancel === "function" && typeof deferred.resolveData === "function";
|
|
4244
4161
|
}
|
|
4245
|
-
|
|
4246
4162
|
function isResponse(value) {
|
|
4247
4163
|
return value != null && typeof value.status === "number" && typeof value.statusText === "string" && typeof value.headers === "object" && typeof value.body !== "undefined";
|
|
4248
4164
|
}
|
|
4249
|
-
|
|
4250
4165
|
function isRedirectResponse(result) {
|
|
4251
4166
|
if (!isResponse(result)) {
|
|
4252
4167
|
return false;
|
|
4253
4168
|
}
|
|
4254
|
-
|
|
4255
4169
|
let status = result.status;
|
|
4256
4170
|
let location = result.headers.get("Location");
|
|
4257
4171
|
return status >= 300 && status <= 399 && location != null;
|
|
4258
4172
|
}
|
|
4259
|
-
|
|
4260
4173
|
function isQueryRouteResponse(obj) {
|
|
4261
4174
|
return obj && isResponse(obj.response) && (obj.type === ResultType.data || ResultType.error);
|
|
4262
4175
|
}
|
|
4263
|
-
|
|
4264
4176
|
function isValidMethod(method) {
|
|
4265
4177
|
return validRequestMethods.has(method.toLowerCase());
|
|
4266
4178
|
}
|
|
4267
|
-
|
|
4268
4179
|
function isMutationMethod(method) {
|
|
4269
4180
|
return validMutationMethods.has(method.toLowerCase());
|
|
4270
4181
|
}
|
|
4271
|
-
|
|
4272
4182
|
async function resolveDeferredResults(currentMatches, matchesToLoad, results, signals, isFetcher, currentLoaderData) {
|
|
4273
4183
|
for (let index = 0; index < results.length; index++) {
|
|
4274
4184
|
let result = results[index];
|
|
4275
|
-
let match = matchesToLoad[index];
|
|
4185
|
+
let match = matchesToLoad[index];
|
|
4186
|
+
// If we don't have a match, then we can have a deferred result to do
|
|
4276
4187
|
// anything with. This is for revalidating fetchers where the route was
|
|
4277
4188
|
// removed during HMR
|
|
4278
|
-
|
|
4279
4189
|
if (!match) {
|
|
4280
4190
|
continue;
|
|
4281
4191
|
}
|
|
4282
|
-
|
|
4283
4192
|
let currentMatch = currentMatches.find(m => m.route.id === match.route.id);
|
|
4284
4193
|
let isRevalidatingLoader = currentMatch != null && !isNewRouteInstance(currentMatch, match) && (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;
|
|
4285
|
-
|
|
4286
4194
|
if (isDeferredResult(result) && (isFetcher || isRevalidatingLoader)) {
|
|
4287
4195
|
// Note: we do not have to touch activeDeferreds here since we race them
|
|
4288
4196
|
// against the signal in resolveDeferredData and they'll get aborted
|
|
@@ -4297,18 +4205,14 @@ async function resolveDeferredResults(currentMatches, matchesToLoad, results, si
|
|
|
4297
4205
|
}
|
|
4298
4206
|
}
|
|
4299
4207
|
}
|
|
4300
|
-
|
|
4301
4208
|
async function resolveDeferredData(result, signal, unwrap) {
|
|
4302
4209
|
if (unwrap === void 0) {
|
|
4303
4210
|
unwrap = false;
|
|
4304
4211
|
}
|
|
4305
|
-
|
|
4306
4212
|
let aborted = await result.deferredData.resolveData(signal);
|
|
4307
|
-
|
|
4308
4213
|
if (aborted) {
|
|
4309
4214
|
return;
|
|
4310
4215
|
}
|
|
4311
|
-
|
|
4312
4216
|
if (unwrap) {
|
|
4313
4217
|
try {
|
|
4314
4218
|
return {
|
|
@@ -4323,19 +4227,17 @@ async function resolveDeferredData(result, signal, unwrap) {
|
|
|
4323
4227
|
};
|
|
4324
4228
|
}
|
|
4325
4229
|
}
|
|
4326
|
-
|
|
4327
4230
|
return {
|
|
4328
4231
|
type: ResultType.data,
|
|
4329
4232
|
data: result.deferredData.data
|
|
4330
4233
|
};
|
|
4331
4234
|
}
|
|
4332
|
-
|
|
4333
4235
|
function hasNakedIndexQuery(search) {
|
|
4334
4236
|
return new URLSearchParams(search).getAll("index").some(v => v === "");
|
|
4335
|
-
}
|
|
4336
|
-
// this please also change that :) Eventually we'll DRY this up
|
|
4337
|
-
|
|
4237
|
+
}
|
|
4338
4238
|
|
|
4239
|
+
// Note: This should match the format exported by useMatches, so if you change
|
|
4240
|
+
// this please also change that :) Eventually we'll DRY this up
|
|
4339
4241
|
function createUseMatchesMatch(match, loaderData) {
|
|
4340
4242
|
let {
|
|
4341
4243
|
route,
|
|
@@ -4350,20 +4252,18 @@ function createUseMatchesMatch(match, loaderData) {
|
|
|
4350
4252
|
handle: route.handle
|
|
4351
4253
|
};
|
|
4352
4254
|
}
|
|
4353
|
-
|
|
4354
4255
|
function getTargetMatch(matches, location) {
|
|
4355
4256
|
let search = typeof location === "string" ? parsePath(location).search : location.search;
|
|
4356
|
-
|
|
4357
4257
|
if (matches[matches.length - 1].route.index && hasNakedIndexQuery(search || "")) {
|
|
4358
4258
|
// Return the leaf index route when index is present
|
|
4359
4259
|
return matches[matches.length - 1];
|
|
4360
|
-
}
|
|
4260
|
+
}
|
|
4261
|
+
// Otherwise grab the deepest "path contributing" match (ignoring index and
|
|
4361
4262
|
// pathless layout routes)
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
4263
|
let pathMatches = getPathContributingMatches(matches);
|
|
4365
4264
|
return pathMatches[pathMatches.length - 1];
|
|
4366
|
-
}
|
|
4265
|
+
}
|
|
4266
|
+
//#endregion
|
|
4367
4267
|
|
|
4368
4268
|
exports.AbortedDeferredError = AbortedDeferredError;
|
|
4369
4269
|
exports.ErrorResponse = ErrorResponse;
|