@remix-run/router 1.3.2-pre.0 → 1.3.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 +10 -2
- package/dist/index.d.ts +2 -1
- package/dist/router.cjs.js +106 -45
- package/dist/router.cjs.js.map +1 -1
- package/dist/router.d.ts +8 -0
- package/dist/router.js +106 -45
- package/dist/router.js.map +1 -1
- package/dist/router.umd.js +106 -45
- 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/index.ts +2 -1
- package/package.json +1 -1
- package/router.ts +129 -54
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
# `@remix-run/router`
|
|
2
2
|
|
|
3
|
-
## 1.3.
|
|
3
|
+
## 1.3.3-pre.0
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- Change `invariant` to an `UNSAFE_` export since it's only intended for internal use ([#10066](https://github.com/remix-run/react-router/pull/10066))
|
|
8
|
+
- Ensure status code and headers are maintained for `defer` loader responses in `createStaticHandler`'s `query()` method ([#10077](https://github.com/remix-run/react-router/pull/10077))
|
|
9
|
+
- Add internal API for custom HMR implementations ([#9996](https://github.com/remix-run/react-router/pull/9996))
|
|
10
|
+
|
|
11
|
+
## 1.3.2
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Remove inaccurate console warning for POP navigations and update active blocker logic ([#10030](https://github.com/remix-run/react-router/pull/10030))
|
|
8
16
|
- Only check for differing origin on absolute URL redirects ([#10033](https://github.com/remix-run/react-router/pull/10033))
|
|
9
17
|
|
|
10
18
|
## 1.3.1
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export type { ActionFunction, ActionFunctionArgs, AgnosticDataIndexRouteObject, AgnosticDataNonIndexRouteObject, AgnosticDataRouteMatch, AgnosticDataRouteObject, AgnosticIndexRouteObject, AgnosticNonIndexRouteObject, AgnosticRouteMatch, AgnosticRouteObject, TrackedPromise, FormEncType, FormMethod, JsonFunction, LoaderFunction, LoaderFunctionArgs, ParamParseKey, Params, PathMatch, PathPattern, RedirectFunction, ShouldRevalidateFunction, Submission, } from "./utils";
|
|
2
2
|
export { AbortedDeferredError, ErrorResponse, defer, generatePath, getToPathname, isRouteErrorResponse, joinPaths, json, matchPath, matchRoutes, normalizePathname, redirect, resolvePath, resolveTo, stripBasename, warning, } from "./utils";
|
|
3
3
|
export type { BrowserHistory, BrowserHistoryOptions, HashHistory, HashHistoryOptions, History, InitialEntry, Location, MemoryHistory, MemoryHistoryOptions, Path, To, } from "./history";
|
|
4
|
-
export { Action, createBrowserHistory, createPath, createHashHistory, createMemoryHistory,
|
|
4
|
+
export { Action, createBrowserHistory, createPath, createHashHistory, createMemoryHistory, parsePath, } from "./history";
|
|
5
5
|
export * from "./router";
|
|
6
6
|
/** @internal */
|
|
7
7
|
export { DeferredData as UNSAFE_DeferredData, convertRoutesToDataRoutes as UNSAFE_convertRoutesToDataRoutes, getPathContributingMatches as UNSAFE_getPathContributingMatches, } from "./utils";
|
|
8
|
+
export { invariant as UNSAFE_invariant } from "./history";
|
package/dist/router.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @remix-run/router v1.3.
|
|
2
|
+
* @remix-run/router v1.3.3-pre.0
|
|
3
3
|
*
|
|
4
4
|
* Copyright (c) Remix Software Inc.
|
|
5
5
|
*
|
|
@@ -1489,7 +1489,8 @@ const isServer = !isBrowser; //#endregion
|
|
|
1489
1489
|
|
|
1490
1490
|
function createRouter(init) {
|
|
1491
1491
|
invariant(init.routes.length > 0, "You must provide a non-empty routes array to createRouter");
|
|
1492
|
-
let dataRoutes = convertRoutesToDataRoutes(init.routes);
|
|
1492
|
+
let dataRoutes = convertRoutesToDataRoutes(init.routes);
|
|
1493
|
+
let inFlightDataRoutes; // Cleanup function for history
|
|
1493
1494
|
|
|
1494
1495
|
let unlistenHistory = null; // Externally-provided functions to call on all state changes
|
|
1495
1496
|
|
|
@@ -1725,6 +1726,12 @@ function createRouter(init) {
|
|
|
1725
1726
|
|
|
1726
1727
|
|
|
1727
1728
|
let preventScrollReset = pendingPreventScrollReset === true || state.navigation.formMethod != null && isMutationMethod(state.navigation.formMethod) && ((_location$state2 = location.state) == null ? void 0 : _location$state2._isRedirect) !== true;
|
|
1729
|
+
|
|
1730
|
+
if (inFlightDataRoutes) {
|
|
1731
|
+
dataRoutes = inFlightDataRoutes;
|
|
1732
|
+
inFlightDataRoutes = undefined;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1728
1735
|
updateState(_extends({}, newState, {
|
|
1729
1736
|
// matches, errors, fetchers go through as-is
|
|
1730
1737
|
actionData,
|
|
@@ -1880,8 +1887,9 @@ function createRouter(init) {
|
|
|
1880
1887
|
|
|
1881
1888
|
saveScrollPosition(state.location, state.matches);
|
|
1882
1889
|
pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
|
|
1890
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
1883
1891
|
let loadingNavigation = opts && opts.overrideNavigation;
|
|
1884
|
-
let matches = matchRoutes(
|
|
1892
|
+
let matches = matchRoutes(routesToUse, location, init.basename); // Short circuit with a 404 on the root error boundary if we match nothing
|
|
1885
1893
|
|
|
1886
1894
|
if (!matches) {
|
|
1887
1895
|
let error = getInternalRouterError(404, {
|
|
@@ -1890,7 +1898,7 @@ function createRouter(init) {
|
|
|
1890
1898
|
let {
|
|
1891
1899
|
matches: notFoundMatches,
|
|
1892
1900
|
route
|
|
1893
|
-
} = getShortCircuitMatches(
|
|
1901
|
+
} = getShortCircuitMatches(routesToUse); // Cancel all pending deferred on 404s since we don't keep any routes
|
|
1894
1902
|
|
|
1895
1903
|
cancelActiveDeferreds();
|
|
1896
1904
|
completeNavigation(location, {
|
|
@@ -2095,7 +2103,8 @@ function createRouter(init) {
|
|
|
2095
2103
|
formData: loadingNavigation.formData,
|
|
2096
2104
|
formEncType: loadingNavigation.formEncType
|
|
2097
2105
|
} : undefined;
|
|
2098
|
-
let
|
|
2106
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2107
|
+
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, activeSubmission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, init.basename, pendingActionData, pendingError); // Cancel pending deferreds for no-longer-matched routes or routes we're
|
|
2099
2108
|
// about to reload. Note that if this is an action reload we would have
|
|
2100
2109
|
// already cancelled all pending deferreds so this would be a no-op
|
|
2101
2110
|
|
|
@@ -2212,7 +2221,8 @@ function createRouter(init) {
|
|
|
2212
2221
|
}
|
|
2213
2222
|
|
|
2214
2223
|
if (fetchControllers.has(key)) abortFetcher(key);
|
|
2215
|
-
let
|
|
2224
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2225
|
+
let matches = matchRoutes(routesToUse, href, init.basename);
|
|
2216
2226
|
|
|
2217
2227
|
if (!matches) {
|
|
2218
2228
|
setFetcherError(key, routeId, getInternalRouterError(404, {
|
|
@@ -2237,9 +2247,7 @@ function createRouter(init) {
|
|
|
2237
2247
|
|
|
2238
2248
|
fetchLoadMatches.set(key, {
|
|
2239
2249
|
routeId,
|
|
2240
|
-
path
|
|
2241
|
-
match,
|
|
2242
|
-
matches
|
|
2250
|
+
path
|
|
2243
2251
|
});
|
|
2244
2252
|
handleFetcherLoader(key, routeId, path, match, matches, submission);
|
|
2245
2253
|
} // Call the action for the matched fetcher.submit(), and then handle redirects,
|
|
@@ -2326,7 +2334,8 @@ function createRouter(init) {
|
|
|
2326
2334
|
|
|
2327
2335
|
let nextLocation = state.navigation.location || state.location;
|
|
2328
2336
|
let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);
|
|
2329
|
-
let
|
|
2337
|
+
let routesToUse = inFlightDataRoutes || dataRoutes;
|
|
2338
|
+
let matches = state.navigation.state !== "idle" ? matchRoutes(routesToUse, state.navigation.location, init.basename) : state.matches;
|
|
2330
2339
|
invariant(matches, "Didn't find any matches after fetcher action");
|
|
2331
2340
|
let loadId = ++incrementingLoadId;
|
|
2332
2341
|
fetchReloadIds.set(key, loadId);
|
|
@@ -2339,10 +2348,10 @@ function createRouter(init) {
|
|
|
2339
2348
|
});
|
|
2340
2349
|
|
|
2341
2350
|
state.fetchers.set(key, loadFetcher);
|
|
2342
|
-
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, {
|
|
2351
|
+
let [matchesToLoad, revalidatingFetchers] = getMatchesToLoad(init.history, state, matches, submission, nextLocation, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, init.basename, {
|
|
2343
2352
|
[match.route.id]: actionResult.data
|
|
2344
|
-
}, undefined
|
|
2345
|
-
|
|
2353
|
+
}, undefined // No need to send through errors since we short circuit above
|
|
2354
|
+
); // Put all revalidating fetchers into the loading state, except for the
|
|
2346
2355
|
// current fetcher which we want to keep in it's current loading state which
|
|
2347
2356
|
// contains it's action submission info + action data
|
|
2348
2357
|
|
|
@@ -2616,7 +2625,19 @@ function createRouter(init) {
|
|
|
2616
2625
|
// Call all navigation loaders and revalidating fetcher loaders in parallel,
|
|
2617
2626
|
// then slice off the results into separate arrays so we can handle them
|
|
2618
2627
|
// accordingly
|
|
2619
|
-
let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f =>
|
|
2628
|
+
let results = await Promise.all([...matchesToLoad.map(match => callLoaderOrAction("loader", request, match, matches, router.basename)), ...fetchersToLoad.map(f => {
|
|
2629
|
+
if (f.matches && f.match) {
|
|
2630
|
+
return callLoaderOrAction("loader", createClientSideRequest(init.history, f.path, request.signal), f.match, f.matches, router.basename);
|
|
2631
|
+
} else {
|
|
2632
|
+
let error = {
|
|
2633
|
+
type: ResultType.error,
|
|
2634
|
+
error: getInternalRouterError(404, {
|
|
2635
|
+
pathname: f.path
|
|
2636
|
+
})
|
|
2637
|
+
};
|
|
2638
|
+
return error;
|
|
2639
|
+
}
|
|
2640
|
+
})]);
|
|
2620
2641
|
let loaderResults = results.slice(0, matchesToLoad.length);
|
|
2621
2642
|
let fetcherResults = results.slice(matchesToLoad.length);
|
|
2622
2643
|
await Promise.all([resolveDeferredResults(currentMatches, matchesToLoad, loaderResults, request.signal, false, state.loaderData), resolveDeferredResults(currentMatches, fetchersToLoad.map(f => f.match), fetcherResults, request.signal, true)]);
|
|
@@ -2851,6 +2872,10 @@ function createRouter(init) {
|
|
|
2851
2872
|
return null;
|
|
2852
2873
|
}
|
|
2853
2874
|
|
|
2875
|
+
function _internalSetRoutes(newRoutes) {
|
|
2876
|
+
inFlightDataRoutes = newRoutes;
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2854
2879
|
router = {
|
|
2855
2880
|
get basename() {
|
|
2856
2881
|
return init.basename;
|
|
@@ -2880,7 +2905,10 @@ function createRouter(init) {
|
|
|
2880
2905
|
getBlocker,
|
|
2881
2906
|
deleteBlocker,
|
|
2882
2907
|
_internalFetchControllers: fetchControllers,
|
|
2883
|
-
_internalActiveDeferreds: activeDeferreds
|
|
2908
|
+
_internalActiveDeferreds: activeDeferreds,
|
|
2909
|
+
// TODO: Remove setRoutes, it's temporary to avoid dealing with
|
|
2910
|
+
// updating the tree while validating the update algorithm.
|
|
2911
|
+
_internalSetRoutes
|
|
2884
2912
|
};
|
|
2885
2913
|
return router;
|
|
2886
2914
|
} //#endregion
|
|
@@ -3381,7 +3409,7 @@ function getLoaderMatchesUntilBoundary(matches, boundaryId) {
|
|
|
3381
3409
|
return boundaryMatches;
|
|
3382
3410
|
}
|
|
3383
3411
|
|
|
3384
|
-
function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, pendingActionData, pendingError
|
|
3412
|
+
function getMatchesToLoad(history, state, matches, submission, location, isRevalidationRequired, cancelledDeferredRoutes, cancelledFetcherLoads, fetchLoadMatches, routesToUse, basename, pendingActionData, pendingError) {
|
|
3385
3413
|
let actionResult = pendingError ? Object.values(pendingError)[0] : pendingActionData ? Object.values(pendingActionData)[0] : undefined;
|
|
3386
3414
|
let currentUrl = history.createURL(state.location);
|
|
3387
3415
|
let nextUrl = history.createURL(location);
|
|
@@ -3420,36 +3448,56 @@ function getMatchesToLoad(history, state, matches, submission, location, isReval
|
|
|
3420
3448
|
}); // Pick fetcher.loads that need to be revalidated
|
|
3421
3449
|
|
|
3422
3450
|
let revalidatingFetchers = [];
|
|
3423
|
-
fetchLoadMatches
|
|
3451
|
+
fetchLoadMatches.forEach((f, key) => {
|
|
3452
|
+
// Don't revalidate if fetcher won't be present in the subsequent render
|
|
3424
3453
|
if (!matches.some(m => m.route.id === f.routeId)) {
|
|
3425
|
-
// This fetcher is not going to be present in the subsequent render so
|
|
3426
|
-
// there's no need to revalidate it
|
|
3427
3454
|
return;
|
|
3428
|
-
}
|
|
3429
|
-
|
|
3455
|
+
}
|
|
3456
|
+
|
|
3457
|
+
let fetcherMatches = matchRoutes(routesToUse, f.path, basename); // If the fetcher path no longer matches, push it in with null matches so
|
|
3458
|
+
// we can trigger a 404 in callLoadersAndMaybeResolveData
|
|
3459
|
+
|
|
3460
|
+
if (!fetcherMatches) {
|
|
3430
3461
|
revalidatingFetchers.push(_extends({
|
|
3431
3462
|
key
|
|
3432
|
-
}, f
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
// hit a static href, so they _always_ check shouldRevalidate and the
|
|
3436
|
-
// default is strictly if a revalidation is explicitly required (action
|
|
3437
|
-
// submissions, useRevalidator, X-Remix-Revalidate).
|
|
3438
|
-
let shouldRevalidate = shouldRevalidateLoader(f.match, _extends({
|
|
3439
|
-
currentUrl,
|
|
3440
|
-
currentParams: state.matches[state.matches.length - 1].params,
|
|
3441
|
-
nextUrl,
|
|
3442
|
-
nextParams: matches[matches.length - 1].params
|
|
3443
|
-
}, submission, {
|
|
3444
|
-
actionResult,
|
|
3445
|
-
defaultShouldRevalidate
|
|
3463
|
+
}, f, {
|
|
3464
|
+
matches: null,
|
|
3465
|
+
match: null
|
|
3446
3466
|
}));
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3447
3469
|
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3470
|
+
let fetcherMatch = getTargetMatch(fetcherMatches, f.path);
|
|
3471
|
+
|
|
3472
|
+
if (cancelledFetcherLoads.includes(key)) {
|
|
3473
|
+
revalidatingFetchers.push(_extends({
|
|
3474
|
+
key,
|
|
3475
|
+
matches: fetcherMatches,
|
|
3476
|
+
match: fetcherMatch
|
|
3477
|
+
}, f));
|
|
3478
|
+
return;
|
|
3479
|
+
} // Revalidating fetchers are decoupled from the route matches since they
|
|
3480
|
+
// hit a static href, so they _always_ check shouldRevalidate and the
|
|
3481
|
+
// default is strictly if a revalidation is explicitly required (action
|
|
3482
|
+
// submissions, useRevalidator, X-Remix-Revalidate).
|
|
3483
|
+
|
|
3484
|
+
|
|
3485
|
+
let shouldRevalidate = shouldRevalidateLoader(fetcherMatch, _extends({
|
|
3486
|
+
currentUrl,
|
|
3487
|
+
currentParams: state.matches[state.matches.length - 1].params,
|
|
3488
|
+
nextUrl,
|
|
3489
|
+
nextParams: matches[matches.length - 1].params
|
|
3490
|
+
}, submission, {
|
|
3491
|
+
actionResult,
|
|
3492
|
+
defaultShouldRevalidate
|
|
3493
|
+
}));
|
|
3494
|
+
|
|
3495
|
+
if (shouldRevalidate) {
|
|
3496
|
+
revalidatingFetchers.push(_extends({
|
|
3497
|
+
key,
|
|
3498
|
+
matches: fetcherMatches,
|
|
3499
|
+
match: fetcherMatch
|
|
3500
|
+
}, f));
|
|
3453
3501
|
}
|
|
3454
3502
|
});
|
|
3455
3503
|
return [navigationMatches, revalidatingFetchers];
|
|
@@ -3619,9 +3667,13 @@ async function callLoaderOrAction(type, request, match, matches, basename, isSta
|
|
|
3619
3667
|
}
|
|
3620
3668
|
|
|
3621
3669
|
if (result instanceof DeferredData) {
|
|
3670
|
+
var _result$init, _result$init2;
|
|
3671
|
+
|
|
3622
3672
|
return {
|
|
3623
3673
|
type: ResultType.deferred,
|
|
3624
|
-
deferredData: result
|
|
3674
|
+
deferredData: result,
|
|
3675
|
+
statusCode: (_result$init = result.init) == null ? void 0 : _result$init.status,
|
|
3676
|
+
headers: ((_result$init2 = result.init) == null ? void 0 : _result$init2.headers) && new Headers(result.init.headers)
|
|
3625
3677
|
};
|
|
3626
3678
|
}
|
|
3627
3679
|
|
|
@@ -3758,7 +3810,7 @@ function processLoaderData(state, matches, matchesToLoad, results, pendingError,
|
|
|
3758
3810
|
let result = fetcherResults[index]; // Process fetcher non-redirect errors
|
|
3759
3811
|
|
|
3760
3812
|
if (isErrorResult(result)) {
|
|
3761
|
-
let boundaryMatch = findNearestBoundary(state.matches, match.route.id);
|
|
3813
|
+
let boundaryMatch = findNearestBoundary(state.matches, match == null ? void 0 : match.route.id);
|
|
3762
3814
|
|
|
3763
3815
|
if (!(errors && errors[boundaryMatch.route.id])) {
|
|
3764
3816
|
errors = _extends({}, errors, {
|
|
@@ -3805,7 +3857,9 @@ function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
|
|
|
3805
3857
|
if (newLoaderData[id] !== undefined) {
|
|
3806
3858
|
mergedLoaderData[id] = newLoaderData[id];
|
|
3807
3859
|
}
|
|
3808
|
-
} else if (loaderData[id] !== undefined) {
|
|
3860
|
+
} else if (loaderData[id] !== undefined && match.route.loader) {
|
|
3861
|
+
// Preserve existing keys not included in newLoaderData and where a loader
|
|
3862
|
+
// wasn't removed by HMR
|
|
3809
3863
|
mergedLoaderData[id] = loaderData[id];
|
|
3810
3864
|
}
|
|
3811
3865
|
|
|
@@ -3942,7 +3996,14 @@ function isMutationMethod(method) {
|
|
|
3942
3996
|
async function resolveDeferredResults(currentMatches, matchesToLoad, results, signal, isFetcher, currentLoaderData) {
|
|
3943
3997
|
for (let index = 0; index < results.length; index++) {
|
|
3944
3998
|
let result = results[index];
|
|
3945
|
-
let match = matchesToLoad[index];
|
|
3999
|
+
let match = matchesToLoad[index]; // If we don't have a match, then we can have a deferred result to do
|
|
4000
|
+
// anything with. This is for revalidating fetchers where the route was
|
|
4001
|
+
// removed during HMR
|
|
4002
|
+
|
|
4003
|
+
if (!match) {
|
|
4004
|
+
continue;
|
|
4005
|
+
}
|
|
4006
|
+
|
|
3946
4007
|
let currentMatch = currentMatches.find(m => m.route.id === match.route.id);
|
|
3947
4008
|
let isRevalidatingLoader = currentMatch != null && !isNewRouteInstance(currentMatch, match) && (currentLoaderData && currentLoaderData[match.route.id]) !== undefined;
|
|
3948
4009
|
|
|
@@ -4035,6 +4096,7 @@ exports.UNSAFE_DEFERRED_SYMBOL = UNSAFE_DEFERRED_SYMBOL;
|
|
|
4035
4096
|
exports.UNSAFE_DeferredData = DeferredData;
|
|
4036
4097
|
exports.UNSAFE_convertRoutesToDataRoutes = convertRoutesToDataRoutes;
|
|
4037
4098
|
exports.UNSAFE_getPathContributingMatches = getPathContributingMatches;
|
|
4099
|
+
exports.UNSAFE_invariant = invariant;
|
|
4038
4100
|
exports.createBrowserHistory = createBrowserHistory;
|
|
4039
4101
|
exports.createHashHistory = createHashHistory;
|
|
4040
4102
|
exports.createMemoryHistory = createMemoryHistory;
|
|
@@ -4045,7 +4107,6 @@ exports.defer = defer;
|
|
|
4045
4107
|
exports.generatePath = generatePath;
|
|
4046
4108
|
exports.getStaticContextFromError = getStaticContextFromError;
|
|
4047
4109
|
exports.getToPathname = getToPathname;
|
|
4048
|
-
exports.invariant = invariant;
|
|
4049
4110
|
exports.isRouteErrorResponse = isRouteErrorResponse;
|
|
4050
4111
|
exports.joinPaths = joinPaths;
|
|
4051
4112
|
exports.json = json;
|