@remix-run/router 1.11.0 → 1.12.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 +28 -0
- package/dist/router.cjs.js +121 -84
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +2 -0
- package/dist/router.js +119 -84
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +121 -84
- 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/package.json +1 -1
- package/router.ts +149 -87
- package/utils.ts +23 -4
package/package.json
CHANGED
package/router.ts
CHANGED
|
@@ -411,6 +411,7 @@ export interface RouterSubscriber {
|
|
|
411
411
|
opts: {
|
|
412
412
|
deletedFetchers: string[];
|
|
413
413
|
unstable_viewTransitionOpts?: ViewTransitionOpts;
|
|
414
|
+
unstable_flushSync: boolean;
|
|
414
415
|
}
|
|
415
416
|
): void;
|
|
416
417
|
}
|
|
@@ -436,6 +437,7 @@ export type RelativeRoutingType = "route" | "path";
|
|
|
436
437
|
type BaseNavigateOrFetchOptions = {
|
|
437
438
|
preventScrollReset?: boolean;
|
|
438
439
|
relative?: RelativeRoutingType;
|
|
440
|
+
unstable_flushSync?: boolean;
|
|
439
441
|
};
|
|
440
442
|
|
|
441
443
|
// Only allowed for navigations
|
|
@@ -1018,7 +1020,10 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1018
1020
|
// Update our state and notify the calling context of the change
|
|
1019
1021
|
function updateState(
|
|
1020
1022
|
newState: Partial<RouterState>,
|
|
1021
|
-
|
|
1023
|
+
opts: {
|
|
1024
|
+
flushSync?: boolean;
|
|
1025
|
+
viewTransitionOpts?: ViewTransitionOpts;
|
|
1026
|
+
} = {}
|
|
1022
1027
|
): void {
|
|
1023
1028
|
state = {
|
|
1024
1029
|
...state,
|
|
@@ -1045,10 +1050,14 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1045
1050
|
});
|
|
1046
1051
|
}
|
|
1047
1052
|
|
|
1048
|
-
|
|
1053
|
+
// Iterate over a local copy so that if flushSync is used and we end up
|
|
1054
|
+
// removing and adding a new subscriber due to the useCallback dependencies,
|
|
1055
|
+
// we don't get ourselves into a loop calling the new subscriber immediately
|
|
1056
|
+
[...subscribers].forEach((subscriber) =>
|
|
1049
1057
|
subscriber(state, {
|
|
1050
1058
|
deletedFetchers: deletedFetchersKeys,
|
|
1051
|
-
unstable_viewTransitionOpts: viewTransitionOpts,
|
|
1059
|
+
unstable_viewTransitionOpts: opts.viewTransitionOpts,
|
|
1060
|
+
unstable_flushSync: opts.flushSync === true,
|
|
1052
1061
|
})
|
|
1053
1062
|
);
|
|
1054
1063
|
|
|
@@ -1066,7 +1075,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1066
1075
|
// - Can pass any other state in newState
|
|
1067
1076
|
function completeNavigation(
|
|
1068
1077
|
location: Location,
|
|
1069
|
-
newState: Partial<Omit<RouterState, "action" | "location" | "navigation"
|
|
1078
|
+
newState: Partial<Omit<RouterState, "action" | "location" | "navigation">>,
|
|
1079
|
+
{ flushSync }: { flushSync?: boolean } = {}
|
|
1070
1080
|
): void {
|
|
1071
1081
|
// Deduce if we're in a loading/actionReload state:
|
|
1072
1082
|
// - We have committed actionData in the store
|
|
@@ -1188,7 +1198,10 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1188
1198
|
preventScrollReset,
|
|
1189
1199
|
blockers,
|
|
1190
1200
|
},
|
|
1191
|
-
|
|
1201
|
+
{
|
|
1202
|
+
viewTransitionOpts,
|
|
1203
|
+
flushSync: flushSync === true,
|
|
1204
|
+
}
|
|
1192
1205
|
);
|
|
1193
1206
|
|
|
1194
1207
|
// Reset stateful navigation vars
|
|
@@ -1266,6 +1279,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1266
1279
|
? opts.preventScrollReset === true
|
|
1267
1280
|
: undefined;
|
|
1268
1281
|
|
|
1282
|
+
let flushSync = (opts && opts.unstable_flushSync) === true;
|
|
1283
|
+
|
|
1269
1284
|
let blockerKey = shouldBlockNavigation({
|
|
1270
1285
|
currentLocation,
|
|
1271
1286
|
nextLocation,
|
|
@@ -1304,6 +1319,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1304
1319
|
preventScrollReset,
|
|
1305
1320
|
replace: opts && opts.replace,
|
|
1306
1321
|
enableViewTransition: opts && opts.unstable_viewTransition,
|
|
1322
|
+
flushSync,
|
|
1307
1323
|
});
|
|
1308
1324
|
}
|
|
1309
1325
|
|
|
@@ -1355,6 +1371,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1355
1371
|
preventScrollReset?: boolean;
|
|
1356
1372
|
replace?: boolean;
|
|
1357
1373
|
enableViewTransition?: boolean;
|
|
1374
|
+
flushSync?: boolean;
|
|
1358
1375
|
}
|
|
1359
1376
|
): Promise<void> {
|
|
1360
1377
|
// Abort any in-progress navigations and start a new one. Unset any ongoing
|
|
@@ -1376,6 +1393,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1376
1393
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1377
1394
|
let loadingNavigation = opts && opts.overrideNavigation;
|
|
1378
1395
|
let matches = matchRoutes(routesToUse, location, basename);
|
|
1396
|
+
let flushSync = (opts && opts.flushSync) === true;
|
|
1379
1397
|
|
|
1380
1398
|
// Short circuit with a 404 on the root error boundary if we match nothing
|
|
1381
1399
|
if (!matches) {
|
|
@@ -1384,13 +1402,17 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1384
1402
|
getShortCircuitMatches(routesToUse);
|
|
1385
1403
|
// Cancel all pending deferred on 404s since we don't keep any routes
|
|
1386
1404
|
cancelActiveDeferreds();
|
|
1387
|
-
completeNavigation(
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1405
|
+
completeNavigation(
|
|
1406
|
+
location,
|
|
1407
|
+
{
|
|
1408
|
+
matches: notFoundMatches,
|
|
1409
|
+
loaderData: {},
|
|
1410
|
+
errors: {
|
|
1411
|
+
[route.id]: error,
|
|
1412
|
+
},
|
|
1392
1413
|
},
|
|
1393
|
-
|
|
1414
|
+
{ flushSync }
|
|
1415
|
+
);
|
|
1394
1416
|
return;
|
|
1395
1417
|
}
|
|
1396
1418
|
|
|
@@ -1406,7 +1428,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1406
1428
|
isHashChangeOnly(state.location, location) &&
|
|
1407
1429
|
!(opts && opts.submission && isMutationMethod(opts.submission.formMethod))
|
|
1408
1430
|
) {
|
|
1409
|
-
completeNavigation(location, { matches });
|
|
1431
|
+
completeNavigation(location, { matches }, { flushSync });
|
|
1410
1432
|
return;
|
|
1411
1433
|
}
|
|
1412
1434
|
|
|
@@ -1440,7 +1462,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1440
1462
|
location,
|
|
1441
1463
|
opts.submission,
|
|
1442
1464
|
matches,
|
|
1443
|
-
{ replace: opts.replace }
|
|
1465
|
+
{ replace: opts.replace, flushSync }
|
|
1444
1466
|
);
|
|
1445
1467
|
|
|
1446
1468
|
if (actionOutput.shortCircuited) {
|
|
@@ -1450,6 +1472,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1450
1472
|
pendingActionData = actionOutput.pendingActionData;
|
|
1451
1473
|
pendingError = actionOutput.pendingActionError;
|
|
1452
1474
|
loadingNavigation = getLoadingNavigation(location, opts.submission);
|
|
1475
|
+
flushSync = false;
|
|
1453
1476
|
|
|
1454
1477
|
// Create a GET request for the loaders
|
|
1455
1478
|
request = new Request(request.url, { signal: request.signal });
|
|
@@ -1464,6 +1487,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1464
1487
|
opts && opts.submission,
|
|
1465
1488
|
opts && opts.fetcherSubmission,
|
|
1466
1489
|
opts && opts.replace,
|
|
1490
|
+
flushSync,
|
|
1467
1491
|
pendingActionData,
|
|
1468
1492
|
pendingError
|
|
1469
1493
|
);
|
|
@@ -1492,13 +1516,13 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1492
1516
|
location: Location,
|
|
1493
1517
|
submission: Submission,
|
|
1494
1518
|
matches: AgnosticDataRouteMatch[],
|
|
1495
|
-
opts: { replace?: boolean } = {}
|
|
1519
|
+
opts: { replace?: boolean; flushSync?: boolean } = {}
|
|
1496
1520
|
): Promise<HandleActionResult> {
|
|
1497
1521
|
interruptActiveLoads();
|
|
1498
1522
|
|
|
1499
1523
|
// Put us in a submitting state
|
|
1500
1524
|
let navigation = getSubmittingNavigation(location, submission);
|
|
1501
|
-
updateState({ navigation });
|
|
1525
|
+
updateState({ navigation }, { flushSync: opts.flushSync === true });
|
|
1502
1526
|
|
|
1503
1527
|
// Call our action and get the result
|
|
1504
1528
|
let result: DataResult;
|
|
@@ -1583,6 +1607,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1583
1607
|
submission?: Submission,
|
|
1584
1608
|
fetcherSubmission?: Submission,
|
|
1585
1609
|
replace?: boolean,
|
|
1610
|
+
flushSync?: boolean,
|
|
1586
1611
|
pendingActionData?: RouteData,
|
|
1587
1612
|
pendingError?: RouteData
|
|
1588
1613
|
): Promise<HandleLoadersResult> {
|
|
@@ -1629,14 +1654,18 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1629
1654
|
// Short circuit if we have no loaders to run
|
|
1630
1655
|
if (matchesToLoad.length === 0 && revalidatingFetchers.length === 0) {
|
|
1631
1656
|
let updatedFetchers = markFetchRedirectsDone();
|
|
1632
|
-
completeNavigation(
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1657
|
+
completeNavigation(
|
|
1658
|
+
location,
|
|
1659
|
+
{
|
|
1660
|
+
matches,
|
|
1661
|
+
loaderData: {},
|
|
1662
|
+
// Commit pending error if we're short circuiting
|
|
1663
|
+
errors: pendingError || null,
|
|
1664
|
+
...(pendingActionData ? { actionData: pendingActionData } : {}),
|
|
1665
|
+
...(updatedFetchers ? { fetchers: new Map(state.fetchers) } : {}),
|
|
1666
|
+
},
|
|
1667
|
+
{ flushSync }
|
|
1668
|
+
);
|
|
1640
1669
|
return { shortCircuited: true };
|
|
1641
1670
|
}
|
|
1642
1671
|
|
|
@@ -1654,17 +1683,22 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1654
1683
|
state.fetchers.set(rf.key, revalidatingFetcher);
|
|
1655
1684
|
});
|
|
1656
1685
|
let actionData = pendingActionData || state.actionData;
|
|
1657
|
-
updateState(
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
?
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1686
|
+
updateState(
|
|
1687
|
+
{
|
|
1688
|
+
navigation: loadingNavigation,
|
|
1689
|
+
...(actionData
|
|
1690
|
+
? Object.keys(actionData).length === 0
|
|
1691
|
+
? { actionData: null }
|
|
1692
|
+
: { actionData }
|
|
1693
|
+
: {}),
|
|
1694
|
+
...(revalidatingFetchers.length > 0
|
|
1695
|
+
? { fetchers: new Map(state.fetchers) }
|
|
1696
|
+
: {}),
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
flushSync,
|
|
1700
|
+
}
|
|
1701
|
+
);
|
|
1668
1702
|
}
|
|
1669
1703
|
|
|
1670
1704
|
revalidatingFetchers.forEach((rf) => {
|
|
@@ -1764,18 +1798,6 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1764
1798
|
};
|
|
1765
1799
|
}
|
|
1766
1800
|
|
|
1767
|
-
function getFetcher<TData = any>(key: string): Fetcher<TData> {
|
|
1768
|
-
if (future.v7_fetcherPersist) {
|
|
1769
|
-
activeFetchers.set(key, (activeFetchers.get(key) || 0) + 1);
|
|
1770
|
-
// If this fetcher was previously marked for deletion, unmark it since we
|
|
1771
|
-
// have a new instance
|
|
1772
|
-
if (deletedFetchers.has(key)) {
|
|
1773
|
-
deletedFetchers.delete(key);
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
return state.fetchers.get(key) || IDLE_FETCHER;
|
|
1777
|
-
}
|
|
1778
|
-
|
|
1779
1801
|
// Trigger a fetcher load/submit for the given fetcher key
|
|
1780
1802
|
function fetch(
|
|
1781
1803
|
key: string,
|
|
@@ -1792,6 +1814,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1792
1814
|
}
|
|
1793
1815
|
|
|
1794
1816
|
if (fetchControllers.has(key)) abortFetcher(key);
|
|
1817
|
+
let flushSync = (opts && opts.unstable_flushSync) === true;
|
|
1795
1818
|
|
|
1796
1819
|
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1797
1820
|
let normalizedPath = normalizeTo(
|
|
@@ -1809,7 +1832,8 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1809
1832
|
setFetcherError(
|
|
1810
1833
|
key,
|
|
1811
1834
|
routeId,
|
|
1812
|
-
getInternalRouterError(404, { pathname: normalizedPath })
|
|
1835
|
+
getInternalRouterError(404, { pathname: normalizedPath }),
|
|
1836
|
+
{ flushSync }
|
|
1813
1837
|
);
|
|
1814
1838
|
return;
|
|
1815
1839
|
}
|
|
@@ -1822,7 +1846,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1822
1846
|
);
|
|
1823
1847
|
|
|
1824
1848
|
if (error) {
|
|
1825
|
-
setFetcherError(key, routeId, error);
|
|
1849
|
+
setFetcherError(key, routeId, error, { flushSync });
|
|
1826
1850
|
return;
|
|
1827
1851
|
}
|
|
1828
1852
|
|
|
@@ -1831,14 +1855,30 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1831
1855
|
pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
1832
1856
|
|
|
1833
1857
|
if (submission && isMutationMethod(submission.formMethod)) {
|
|
1834
|
-
handleFetcherAction(
|
|
1858
|
+
handleFetcherAction(
|
|
1859
|
+
key,
|
|
1860
|
+
routeId,
|
|
1861
|
+
path,
|
|
1862
|
+
match,
|
|
1863
|
+
matches,
|
|
1864
|
+
flushSync,
|
|
1865
|
+
submission
|
|
1866
|
+
);
|
|
1835
1867
|
return;
|
|
1836
1868
|
}
|
|
1837
1869
|
|
|
1838
1870
|
// Store off the match so we can call it's shouldRevalidate on subsequent
|
|
1839
1871
|
// revalidations
|
|
1840
1872
|
fetchLoadMatches.set(key, { routeId, path });
|
|
1841
|
-
handleFetcherLoader(
|
|
1873
|
+
handleFetcherLoader(
|
|
1874
|
+
key,
|
|
1875
|
+
routeId,
|
|
1876
|
+
path,
|
|
1877
|
+
match,
|
|
1878
|
+
matches,
|
|
1879
|
+
flushSync,
|
|
1880
|
+
submission
|
|
1881
|
+
);
|
|
1842
1882
|
}
|
|
1843
1883
|
|
|
1844
1884
|
// Call the action for the matched fetcher.submit(), and then handle redirects,
|
|
@@ -1849,6 +1889,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1849
1889
|
path: string,
|
|
1850
1890
|
match: AgnosticDataRouteMatch,
|
|
1851
1891
|
requestMatches: AgnosticDataRouteMatch[],
|
|
1892
|
+
flushSync: boolean,
|
|
1852
1893
|
submission: Submission
|
|
1853
1894
|
) {
|
|
1854
1895
|
interruptActiveLoads();
|
|
@@ -1860,15 +1901,15 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1860
1901
|
pathname: path,
|
|
1861
1902
|
routeId: routeId,
|
|
1862
1903
|
});
|
|
1863
|
-
setFetcherError(key, routeId, error);
|
|
1904
|
+
setFetcherError(key, routeId, error, { flushSync });
|
|
1864
1905
|
return;
|
|
1865
1906
|
}
|
|
1866
1907
|
|
|
1867
1908
|
// Put this fetcher into it's submitting state
|
|
1868
1909
|
let existingFetcher = state.fetchers.get(key);
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1910
|
+
updateFetcherState(key, getSubmittingFetcher(submission, existingFetcher), {
|
|
1911
|
+
flushSync,
|
|
1912
|
+
});
|
|
1872
1913
|
|
|
1873
1914
|
// Call the action for the fetcher
|
|
1874
1915
|
let abortController = new AbortController();
|
|
@@ -1901,8 +1942,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1901
1942
|
}
|
|
1902
1943
|
|
|
1903
1944
|
if (deletedFetchers.has(key)) {
|
|
1904
|
-
|
|
1905
|
-
updateState({ fetchers: new Map(state.fetchers) });
|
|
1945
|
+
updateFetcherState(key, getDoneFetcher(undefined));
|
|
1906
1946
|
return;
|
|
1907
1947
|
}
|
|
1908
1948
|
|
|
@@ -1913,16 +1953,11 @@ export function createRouter(init: RouterInit): Router {
|
|
|
1913
1953
|
// should take precedence over this redirect navigation. We already
|
|
1914
1954
|
// set isRevalidationRequired so all loaders for the new route should
|
|
1915
1955
|
// fire unless opted out via shouldRevalidate
|
|
1916
|
-
|
|
1917
|
-
state.fetchers.set(key, doneFetcher);
|
|
1918
|
-
updateState({ fetchers: new Map(state.fetchers) });
|
|
1956
|
+
updateFetcherState(key, getDoneFetcher(undefined));
|
|
1919
1957
|
return;
|
|
1920
1958
|
} else {
|
|
1921
1959
|
fetchRedirectIds.add(key);
|
|
1922
|
-
|
|
1923
|
-
state.fetchers.set(key, loadingFetcher);
|
|
1924
|
-
updateState({ fetchers: new Map(state.fetchers) });
|
|
1925
|
-
|
|
1960
|
+
updateFetcherState(key, getLoadingFetcher(submission));
|
|
1926
1961
|
return startRedirectNavigation(state, actionResult, {
|
|
1927
1962
|
fetcherSubmission: submission,
|
|
1928
1963
|
});
|
|
@@ -2106,16 +2141,18 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2106
2141
|
path: string,
|
|
2107
2142
|
match: AgnosticDataRouteMatch,
|
|
2108
2143
|
matches: AgnosticDataRouteMatch[],
|
|
2144
|
+
flushSync: boolean,
|
|
2109
2145
|
submission?: Submission
|
|
2110
2146
|
) {
|
|
2111
2147
|
let existingFetcher = state.fetchers.get(key);
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2148
|
+
updateFetcherState(
|
|
2149
|
+
key,
|
|
2150
|
+
getLoadingFetcher(
|
|
2151
|
+
submission,
|
|
2152
|
+
existingFetcher ? existingFetcher.data : undefined
|
|
2153
|
+
),
|
|
2154
|
+
{ flushSync }
|
|
2116
2155
|
);
|
|
2117
|
-
state.fetchers.set(key, loadingFetcher);
|
|
2118
|
-
updateState({ fetchers: new Map(state.fetchers) });
|
|
2119
2156
|
|
|
2120
2157
|
// Call the loader for this fetcher route match
|
|
2121
2158
|
let abortController = new AbortController();
|
|
@@ -2158,8 +2195,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2158
2195
|
}
|
|
2159
2196
|
|
|
2160
2197
|
if (deletedFetchers.has(key)) {
|
|
2161
|
-
|
|
2162
|
-
updateState({ fetchers: new Map(state.fetchers) });
|
|
2198
|
+
updateFetcherState(key, getDoneFetcher(undefined));
|
|
2163
2199
|
return;
|
|
2164
2200
|
}
|
|
2165
2201
|
|
|
@@ -2168,9 +2204,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2168
2204
|
if (pendingNavigationLoadId > originatingLoadId) {
|
|
2169
2205
|
// A new navigation was kicked off after our loader started, so that
|
|
2170
2206
|
// should take precedence over this redirect navigation
|
|
2171
|
-
|
|
2172
|
-
state.fetchers.set(key, doneFetcher);
|
|
2173
|
-
updateState({ fetchers: new Map(state.fetchers) });
|
|
2207
|
+
updateFetcherState(key, getDoneFetcher(undefined));
|
|
2174
2208
|
return;
|
|
2175
2209
|
} else {
|
|
2176
2210
|
fetchRedirectIds.add(key);
|
|
@@ -2188,9 +2222,7 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2188
2222
|
invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
|
|
2189
2223
|
|
|
2190
2224
|
// Put the fetcher back into an idle state
|
|
2191
|
-
|
|
2192
|
-
state.fetchers.set(key, doneFetcher);
|
|
2193
|
-
updateState({ fetchers: new Map(state.fetchers) });
|
|
2225
|
+
updateFetcherState(key, getDoneFetcher(result.data));
|
|
2194
2226
|
}
|
|
2195
2227
|
|
|
2196
2228
|
/**
|
|
@@ -2399,15 +2431,47 @@ export function createRouter(init: RouterInit): Router {
|
|
|
2399
2431
|
});
|
|
2400
2432
|
}
|
|
2401
2433
|
|
|
2402
|
-
function
|
|
2434
|
+
function updateFetcherState(
|
|
2435
|
+
key: string,
|
|
2436
|
+
fetcher: Fetcher,
|
|
2437
|
+
opts: { flushSync?: boolean } = {}
|
|
2438
|
+
) {
|
|
2439
|
+
state.fetchers.set(key, fetcher);
|
|
2440
|
+
updateState(
|
|
2441
|
+
{ fetchers: new Map(state.fetchers) },
|
|
2442
|
+
{ flushSync: (opts && opts.flushSync) === true }
|
|
2443
|
+
);
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
function setFetcherError(
|
|
2447
|
+
key: string,
|
|
2448
|
+
routeId: string,
|
|
2449
|
+
error: any,
|
|
2450
|
+
opts: { flushSync?: boolean } = {}
|
|
2451
|
+
) {
|
|
2403
2452
|
let boundaryMatch = findNearestBoundary(state.matches, routeId);
|
|
2404
2453
|
deleteFetcher(key);
|
|
2405
|
-
updateState(
|
|
2406
|
-
|
|
2407
|
-
|
|
2454
|
+
updateState(
|
|
2455
|
+
{
|
|
2456
|
+
errors: {
|
|
2457
|
+
[boundaryMatch.route.id]: error,
|
|
2458
|
+
},
|
|
2459
|
+
fetchers: new Map(state.fetchers),
|
|
2408
2460
|
},
|
|
2409
|
-
|
|
2410
|
-
|
|
2461
|
+
{ flushSync: (opts && opts.flushSync) === true }
|
|
2462
|
+
);
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
function getFetcher<TData = any>(key: string): Fetcher<TData> {
|
|
2466
|
+
if (future.v7_fetcherPersist) {
|
|
2467
|
+
activeFetchers.set(key, (activeFetchers.get(key) || 0) + 1);
|
|
2468
|
+
// If this fetcher was previously marked for deletion, unmark it since we
|
|
2469
|
+
// have a new instance
|
|
2470
|
+
if (deletedFetchers.has(key)) {
|
|
2471
|
+
deletedFetchers.delete(key);
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
return state.fetchers.get(key) || IDLE_FETCHER;
|
|
2411
2475
|
}
|
|
2412
2476
|
|
|
2413
2477
|
function deleteFetcher(key: string): void {
|
|
@@ -3255,11 +3319,9 @@ function normalizeTo(
|
|
|
3255
3319
|
) {
|
|
3256
3320
|
let contextualMatches: AgnosticDataRouteMatch[];
|
|
3257
3321
|
let activeRouteMatch: AgnosticDataRouteMatch | undefined;
|
|
3258
|
-
if (fromRouteId
|
|
3322
|
+
if (fromRouteId) {
|
|
3259
3323
|
// Grab matches up to the calling route so our route-relative logic is
|
|
3260
|
-
// relative to the correct source route
|
|
3261
|
-
// fromRouteId is ignored since that is always relative to the current
|
|
3262
|
-
// location path
|
|
3324
|
+
// relative to the correct source route
|
|
3263
3325
|
contextualMatches = [];
|
|
3264
3326
|
for (let match of matches) {
|
|
3265
3327
|
contextualMatches.push(match);
|
package/utils.ts
CHANGED
|
@@ -1188,17 +1188,36 @@ export function resolveTo(
|
|
|
1188
1188
|
// `to` values that do not provide a pathname. `to` can simply be a search or
|
|
1189
1189
|
// hash string, in which case we should assume that the navigation is relative
|
|
1190
1190
|
// to the current location's pathname and *not* the route pathname.
|
|
1191
|
-
if (
|
|
1191
|
+
if (toPathname == null) {
|
|
1192
1192
|
from = locationPathname;
|
|
1193
|
+
} else if (isPathRelative) {
|
|
1194
|
+
let fromSegments = routePathnames[routePathnames.length - 1]
|
|
1195
|
+
.replace(/^\//, "")
|
|
1196
|
+
.split("/");
|
|
1197
|
+
|
|
1198
|
+
if (toPathname.startsWith("..")) {
|
|
1199
|
+
let toSegments = toPathname.split("/");
|
|
1200
|
+
|
|
1201
|
+
// With relative="path", each leading .. segment means "go up one URL segment"
|
|
1202
|
+
while (toSegments[0] === "..") {
|
|
1203
|
+
toSegments.shift();
|
|
1204
|
+
fromSegments.pop();
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
to.pathname = toSegments.join("/");
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
from = "/" + fromSegments.join("/");
|
|
1193
1211
|
} else {
|
|
1194
1212
|
let routePathnameIndex = routePathnames.length - 1;
|
|
1195
1213
|
|
|
1196
1214
|
if (toPathname.startsWith("..")) {
|
|
1197
1215
|
let toSegments = toPathname.split("/");
|
|
1198
1216
|
|
|
1199
|
-
//
|
|
1200
|
-
// URL segment". This is a key
|
|
1201
|
-
//
|
|
1217
|
+
// With relative="route" (the default), each leading .. segment means
|
|
1218
|
+
// "go up one route" instead of "go up one URL segment". This is a key
|
|
1219
|
+
// difference from how <a href> works and a major reason we call this a
|
|
1220
|
+
// "to" value instead of a "href".
|
|
1202
1221
|
while (toSegments[0] === "..") {
|
|
1203
1222
|
toSegments.shift();
|
|
1204
1223
|
routePathnameIndex -= 1;
|