@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.
@@ -13003,10 +13003,12 @@ const createControlValidation = (
13003
13003
  }
13004
13004
  }
13005
13005
 
13006
- if (fromRequestAction) {
13007
- // The value is about to be read and sent: whoever may correct it gets
13008
- // the last word before the constraints judge it, so a field is never
13009
- // refused for something navi knows how to put right.
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
- { searchParams, params, redirectRoute, redirectRouteParams } = {},
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 (!redirectingRouteSet || redirectingRouteSet.size === 0) {
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
- const nextUrl = resolveRedirectionOnce(urlToResolve);
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 } = getRoutePrivateProperties(route);
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
- lastActionValueRef.current = currentValue;
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
  };