@jsenv/navi 0.29.382 → 0.29.383

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.
@@ -17917,6 +17917,11 @@ const generateSignalId = () => {
17917
17917
  * `?level=3,4` becomes `[3, 4]` instead of `["3", "4"]`. Without it items stay strings.
17918
17918
  * @param {number} [options.step] - For number type: step size for precision. Values will be rounded to nearest multiple of step.
17919
17919
  * @param {Array} [options.oneOf] - Array of valid values for validation. Signal will be marked invalid if value is not in this array
17920
+ * @param {boolean} [options.autoFix=false] - Hold the repaired value instead of the refused one: a number
17921
+ * clamped to `min`/`max` and rounded to `step`, a value outside `oneOf` replaced by the signal's
17922
+ * default value (an enumeration has no nearest value, and its order says nothing). Without it the
17923
+ * refused value is held as-is, marked invalid, and `validSignal` reads `undefined`. When the value
17924
+ * came from the address, the address is rewritten with the repaired one (a replace).
17920
17925
  * @param {boolean} [options.weak=false] - The param qualifies one visit, not the screen: it is written into a
17921
17926
  * url only when explicitly named (`routeParams={{ edit: id }}`), never inherited from the signal's current
17922
17927
  * value, and it goes back to the default value when the route stops matching. Use it for params like an
@@ -17951,11 +17956,11 @@ const generateSignalId = () => {
17951
17956
  * });
17952
17957
  *
17953
17958
  * @example
17954
- * // Signal with validation and auto-fix
17959
+ * // Signal with validation and auto-fix: an unknown tab falls back to "overview"
17955
17960
  * const tab = stateSignal("overview", {
17956
17961
  * id: "current-tab",
17957
17962
  * oneOf: ["overview", "details", "settings"],
17958
- * autoFix: () => "overview",
17963
+ * autoFix: true,
17959
17964
  * persists: true
17960
17965
  * });
17961
17966
  *
@@ -18052,6 +18057,10 @@ const stateSignal = (defaultValue, options = {}) => {
18052
18057
  oneOf,
18053
18058
  localStorageRepresentation,
18054
18059
  autoFix,
18060
+ // What a value no rule can repair falls back to: the default is what the
18061
+ // state is when nothing says otherwise, read at repair time (from
18062
+ // processValue, below) so a dynamic default is read where it stands.
18063
+ fallback: () => getDefaultValue(false),
18055
18064
  });
18056
18065
  const readFromLocalStorage = persists
18057
18066
  ? () => {
@@ -27169,6 +27178,14 @@ This prevents cross-test pollution and ensures clean state.`,
27169
27178
  // Apply all signal updates in a batch
27170
27179
  const matchingRouteSet = new Set();
27171
27180
  const routeUpdateSet = new Set();
27181
+ // Params the address gave and the state could not hold as written
27182
+ // (autoFix): the address is rewritten with what the state holds, once
27183
+ // the sync is over, so the repaired value is not left in the url to be
27184
+ // replayed by a reload or a copied link.
27185
+ const repairedParamsByRoute = new Map();
27186
+ // A query connection is inherited by the child routes of the one
27187
+ // declaring it: the first route to record a param answers for all.
27188
+ const repairedParamNameSet = new Set();
27172
27189
  batch(() => {
27173
27190
  for (const {
27174
27191
  route,
@@ -27351,6 +27368,29 @@ This prevents cross-test pollution and ensures clean state.`,
27351
27368
  );
27352
27369
  }
27353
27370
  paramSignal.value = urlParamValue;
27371
+ if (
27372
+ paramSignal.validity?.autoFixed &&
27373
+ !repairedParamNameSet.has(paramName)
27374
+ ) {
27375
+ repairedParamNameSet.add(paramName);
27376
+ const repairedValue = paramSignal.peek();
27377
+ if (debug) {
27378
+ console.debug(
27379
+ `[route] URL->Signal: ${paramName}=${urlParamValue} repaired to ${repairedValue}, the url will be rewritten`,
27380
+ );
27381
+ }
27382
+ let repairedParams = repairedParamsByRoute.get(route);
27383
+ if (!repairedParams) {
27384
+ repairedParams = {};
27385
+ repairedParamsByRoute.set(route, repairedParams);
27386
+ }
27387
+ // A default is not written in an address (see the signal -> url sync)
27388
+ repairedParams[paramName] = connection.isDefaultValue(
27389
+ repairedValue,
27390
+ )
27391
+ ? undefined
27392
+ : repairedValue;
27393
+ }
27354
27394
  continue;
27355
27395
  }
27356
27396
  }
@@ -27361,6 +27401,12 @@ This prevents cross-test pollution and ensures clean state.`,
27361
27401
  // Reset flag after URL -> Signal synchronization is complete
27362
27402
  isUpdatingRoutesFromUrl = false;
27363
27403
  Object.assign(returnValue, { matchingRouteSet });
27404
+ for (const [route, repairedParams] of repairedParamsByRoute) {
27405
+ route.replaceParams(repairedParams, {
27406
+ callReason: `url params repaired on ${route}: ${Object.keys(repairedParams).join(", ")}`,
27407
+ history: "replace",
27408
+ });
27409
+ }
27364
27410
  }
27365
27411
 
27366
27412
  return returnValue;