@jsenv/navi 0.29.354 → 0.29.355
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/dist/dev/jsenv_navi.js +74 -9
- package/dist/dev/jsenv_navi.js.map +4 -4
- package/dist/jsenv_navi.js +74 -9
- package/dist/jsenv_navi.js.map +4 -4
- package/docs/navigation.md +25 -0
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -13003,10 +13003,12 @@ const createControlValidation = (
|
|
|
13003
13003
|
}
|
|
13004
13004
|
}
|
|
13005
13005
|
|
|
13006
|
-
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13006
|
+
// The value is about to be read and sent: whoever may correct it gets the
|
|
13007
|
+
// last word before the constraints judge it, so a field is never refused
|
|
13008
|
+
// for something navi knows how to put right. Unless the request comes from
|
|
13009
|
+
// typing — an action run as you type (a search, debounced or not) is not
|
|
13010
|
+
// a commit, and correcting there would eat the space before the next word.
|
|
13011
|
+
if (fromRequestAction && !findEvent(event, "input")) {
|
|
13010
13012
|
applyAutoFix(event);
|
|
13011
13013
|
}
|
|
13012
13014
|
|
|
@@ -25659,10 +25661,27 @@ let isUpdatingRoutesFromUrl = false;
|
|
|
25659
25661
|
* route("/legacy/:id", { redirectRoute: GAME_PAGE, redirectRouteParams: ({ id }) => ({ gameId: id }) });
|
|
25660
25662
|
* route("/:gameId/invite", { redirectRoute: HOME_PAGE, redirectRouteParams: null });
|
|
25661
25663
|
* ```
|
|
25664
|
+
* @param {string[]} [options.dropSearchParams]
|
|
25665
|
+
* Search params this route's address never keeps: read by whoever fetched
|
|
25666
|
+
* the address (a link preview crawler, a cache), never by the app. When this
|
|
25667
|
+
* route's own address arrives carrying one, the navigation goes to the same
|
|
25668
|
+
* address without it, at the door, like a redirection: no history entry, no
|
|
25669
|
+
* route action, no signal written. Every other search param stays as written.
|
|
25670
|
+
*
|
|
25671
|
+
* ```js
|
|
25672
|
+
* // ?v= makes WhatsApp fetch a new preview, the address bar never shows it
|
|
25673
|
+
* route(`/games/:gameId=${gamePageIdSignal}`, { dropSearchParams: ["v"] });
|
|
25674
|
+
* ```
|
|
25662
25675
|
*/
|
|
25663
25676
|
const route = (
|
|
25664
25677
|
pattern,
|
|
25665
|
-
{
|
|
25678
|
+
{
|
|
25679
|
+
searchParams,
|
|
25680
|
+
params,
|
|
25681
|
+
redirectRoute,
|
|
25682
|
+
redirectRouteParams,
|
|
25683
|
+
dropSearchParams,
|
|
25684
|
+
} = {},
|
|
25666
25685
|
) => {
|
|
25667
25686
|
const routePattern = createRoutePattern(pattern, { searchParams, params });
|
|
25668
25687
|
const { cleanPattern } = routePattern;
|
|
@@ -25722,6 +25741,7 @@ const route = (
|
|
|
25722
25741
|
routePattern,
|
|
25723
25742
|
redirectRoute,
|
|
25724
25743
|
redirectRouteParams,
|
|
25744
|
+
dropSearchParams,
|
|
25725
25745
|
setup: null,
|
|
25726
25746
|
updateStatus: null,
|
|
25727
25747
|
cleanup: null,
|
|
@@ -26081,6 +26101,7 @@ const route = (
|
|
|
26081
26101
|
const [publishRouteMutations, observeRouteMutations] = createPubSub();
|
|
26082
26102
|
|
|
26083
26103
|
let redirectingRouteSet = null;
|
|
26104
|
+
let droppingRouteSet = null;
|
|
26084
26105
|
/**
|
|
26085
26106
|
* Where does this url really lead?
|
|
26086
26107
|
*
|
|
@@ -26097,14 +26118,21 @@ let redirectingRouteSet = null;
|
|
|
26097
26118
|
* @returns {string|null} The url to go to instead, or null.
|
|
26098
26119
|
*/
|
|
26099
26120
|
const resolveRouteRedirection = (url) => {
|
|
26100
|
-
if (
|
|
26121
|
+
if (
|
|
26122
|
+
!redirectingRouteSet ||
|
|
26123
|
+
(redirectingRouteSet.size === 0 && droppingRouteSet.size === 0)
|
|
26124
|
+
) {
|
|
26101
26125
|
return null;
|
|
26102
26126
|
}
|
|
26103
26127
|
let urlToResolve = url;
|
|
26104
26128
|
let redirectionUrl = null;
|
|
26105
26129
|
const urlChain = [url];
|
|
26106
26130
|
while (true) {
|
|
26107
|
-
|
|
26131
|
+
// A redirection first: the address it lands on may carry a param its own
|
|
26132
|
+
// route drops, and the next turn removes it there.
|
|
26133
|
+
const nextUrl =
|
|
26134
|
+
resolveRedirectionOnce(urlToResolve) ||
|
|
26135
|
+
dropSearchParamsOnce(urlToResolve);
|
|
26108
26136
|
if (!nextUrl || nextUrl === urlToResolve) {
|
|
26109
26137
|
break;
|
|
26110
26138
|
}
|
|
@@ -26145,6 +26173,35 @@ const resolveRedirectionOnce = (url) => {
|
|
|
26145
26173
|
}
|
|
26146
26174
|
return buildRedirectionUrl(redirectingRoute, redirectingRouteParams);
|
|
26147
26175
|
};
|
|
26176
|
+
// The query is edited as written rather than rebuilt through URLSearchParams,
|
|
26177
|
+
// which would rewrite every param kept ("?weather" into "?weather=", commas
|
|
26178
|
+
// encoded — see extractSearchParams).
|
|
26179
|
+
const dropSearchParamsOnce = (url) => {
|
|
26180
|
+
const urlObject = new URL(url);
|
|
26181
|
+
if (!urlObject.search) {
|
|
26182
|
+
return null;
|
|
26183
|
+
}
|
|
26184
|
+
for (const route of droppingRouteSet) {
|
|
26185
|
+
const { routePattern, dropSearchParams } = getRoutePrivateProperties(route);
|
|
26186
|
+
// exact, for the same reason as a redirection: the addresses below a
|
|
26187
|
+
// trailing slash belong to the routes declared for them
|
|
26188
|
+
if (!routePattern.applyOn(url, { exact: true })) {
|
|
26189
|
+
continue;
|
|
26190
|
+
}
|
|
26191
|
+
const pairs = urlObject.search.slice(1).split("&");
|
|
26192
|
+
const pairsKept = pairs.filter((pair) => {
|
|
26193
|
+
const eqIndex = pair.indexOf("=");
|
|
26194
|
+
const key = eqIndex > -1 ? pair.slice(0, eqIndex) : pair;
|
|
26195
|
+
return !dropSearchParams.includes(decodeURIComponent(key));
|
|
26196
|
+
});
|
|
26197
|
+
if (pairsKept.length === pairs.length) {
|
|
26198
|
+
continue;
|
|
26199
|
+
}
|
|
26200
|
+
urlObject.search = pairsKept.length ? `?${pairsKept.join("&")}` : "";
|
|
26201
|
+
return urlObject.href;
|
|
26202
|
+
}
|
|
26203
|
+
return null;
|
|
26204
|
+
};
|
|
26148
26205
|
const buildRedirectionUrl = (route, urlParams) => {
|
|
26149
26206
|
const { redirectRoute, redirectRouteParams } =
|
|
26150
26207
|
getRoutePrivateProperties(route);
|
|
@@ -26248,8 +26305,13 @@ This prevents cross-test pollution and ensures clean state.`,
|
|
|
26248
26305
|
// Checked here rather than at declaration: a route may redirect to one
|
|
26249
26306
|
// declared after it, and reading the target then would forbid that order.
|
|
26250
26307
|
redirectingRouteSet = new Set();
|
|
26308
|
+
droppingRouteSet = new Set();
|
|
26251
26309
|
for (const route of routeSet) {
|
|
26252
|
-
const { redirectRoute } =
|
|
26310
|
+
const { redirectRoute, dropSearchParams } =
|
|
26311
|
+
getRoutePrivateProperties(route);
|
|
26312
|
+
if (dropSearchParams && dropSearchParams.length) {
|
|
26313
|
+
droppingRouteSet.add(route);
|
|
26314
|
+
}
|
|
26253
26315
|
if (!redirectRoute) {
|
|
26254
26316
|
continue;
|
|
26255
26317
|
}
|
|
@@ -26535,6 +26597,7 @@ This prevents cross-test pollution and ensures clean state.`,
|
|
|
26535
26597
|
}
|
|
26536
26598
|
routeSet.clear();
|
|
26537
26599
|
redirectingRouteSet = null;
|
|
26600
|
+
droppingRouteSet = null;
|
|
26538
26601
|
setupRoutesCalled = false;
|
|
26539
26602
|
activeRouteSet = null;
|
|
26540
26603
|
};
|
|
@@ -39748,7 +39811,9 @@ const useControlProps = (props, {
|
|
|
39748
39811
|
requester: control
|
|
39749
39812
|
});
|
|
39750
39813
|
if (dispatched) {
|
|
39751
|
-
|
|
39814
|
+
// Read again: the gate may have corrected the value (see applyAutoFix),
|
|
39815
|
+
// and what the next request is compared with is what went out.
|
|
39816
|
+
lastActionValueRef.current = readControlValue(control);
|
|
39752
39817
|
}
|
|
39753
39818
|
return dispatched;
|
|
39754
39819
|
};
|