@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.
@@ -17320,6 +17320,11 @@ const generateSignalId = () => {
17320
17320
  * `?level=3,4` becomes `[3, 4]` instead of `["3", "4"]`. Without it items stay strings.
17321
17321
  * @param {number} [options.step] - For number type: step size for precision. Values will be rounded to nearest multiple of step.
17322
17322
  * @param {Array} [options.oneOf] - Array of valid values for validation. Signal will be marked invalid if value is not in this array
17323
+ * @param {boolean} [options.autoFix=false] - Hold the repaired value instead of the refused one: a number
17324
+ * clamped to `min`/`max` and rounded to `step`, a value outside `oneOf` replaced by the signal's
17325
+ * default value (an enumeration has no nearest value, and its order says nothing). Without it the
17326
+ * refused value is held as-is, marked invalid, and `validSignal` reads `undefined`. When the value
17327
+ * came from the address, the address is rewritten with the repaired one (a replace).
17323
17328
  * @param {boolean} [options.weak=false] - The param qualifies one visit, not the screen: it is written into a
17324
17329
  * url only when explicitly named (`routeParams={{ edit: id }}`), never inherited from the signal's current
17325
17330
  * value, and it goes back to the default value when the route stops matching. Use it for params like an
@@ -17354,11 +17359,11 @@ const generateSignalId = () => {
17354
17359
  * });
17355
17360
  *
17356
17361
  * @example
17357
- * // Signal with validation and auto-fix
17362
+ * // Signal with validation and auto-fix: an unknown tab falls back to "overview"
17358
17363
  * const tab = stateSignal("overview", {
17359
17364
  * id: "current-tab",
17360
17365
  * oneOf: ["overview", "details", "settings"],
17361
- * autoFix: () => "overview",
17366
+ * autoFix: true,
17362
17367
  * persists: true
17363
17368
  * });
17364
17369
  *
@@ -17455,6 +17460,10 @@ const stateSignal = (defaultValue, options = {}) => {
17455
17460
  oneOf,
17456
17461
  localStorageRepresentation,
17457
17462
  autoFix,
17463
+ // What a value no rule can repair falls back to: the default is what the
17464
+ // state is when nothing says otherwise, read at repair time (from
17465
+ // processValue, below) so a dynamic default is read where it stands.
17466
+ fallback: () => getDefaultValue(false),
17458
17467
  });
17459
17468
  const readFromLocalStorage = persists
17460
17469
  ? () => {
@@ -26433,6 +26442,14 @@ This prevents cross-test pollution and ensures clean state.`,
26433
26442
  // Apply all signal updates in a batch
26434
26443
  const matchingRouteSet = new Set();
26435
26444
  const routeUpdateSet = new Set();
26445
+ // Params the address gave and the state could not hold as written
26446
+ // (autoFix): the address is rewritten with what the state holds, once
26447
+ // the sync is over, so the repaired value is not left in the url to be
26448
+ // replayed by a reload or a copied link.
26449
+ const repairedParamsByRoute = new Map();
26450
+ // A query connection is inherited by the child routes of the one
26451
+ // declaring it: the first route to record a param answers for all.
26452
+ const repairedParamNameSet = new Set();
26436
26453
  batch(() => {
26437
26454
  for (const {
26438
26455
  route,
@@ -26615,6 +26632,29 @@ This prevents cross-test pollution and ensures clean state.`,
26615
26632
  );
26616
26633
  }
26617
26634
  paramSignal.value = urlParamValue;
26635
+ if (
26636
+ paramSignal.validity?.autoFixed &&
26637
+ !repairedParamNameSet.has(paramName)
26638
+ ) {
26639
+ repairedParamNameSet.add(paramName);
26640
+ const repairedValue = paramSignal.peek();
26641
+ if (debug) {
26642
+ console.debug(
26643
+ `[route] URL->Signal: ${paramName}=${urlParamValue} repaired to ${repairedValue}, the url will be rewritten`,
26644
+ );
26645
+ }
26646
+ let repairedParams = repairedParamsByRoute.get(route);
26647
+ if (!repairedParams) {
26648
+ repairedParams = {};
26649
+ repairedParamsByRoute.set(route, repairedParams);
26650
+ }
26651
+ // A default is not written in an address (see the signal -> url sync)
26652
+ repairedParams[paramName] = connection.isDefaultValue(
26653
+ repairedValue,
26654
+ )
26655
+ ? undefined
26656
+ : repairedValue;
26657
+ }
26618
26658
  continue;
26619
26659
  }
26620
26660
  }
@@ -26625,6 +26665,12 @@ This prevents cross-test pollution and ensures clean state.`,
26625
26665
  // Reset flag after URL -> Signal synchronization is complete
26626
26666
  isUpdatingRoutesFromUrl = false;
26627
26667
  Object.assign(returnValue, { matchingRouteSet });
26668
+ for (const [route, repairedParams] of repairedParamsByRoute) {
26669
+ route.replaceParams(repairedParams, {
26670
+ callReason: `url params repaired on ${route}: ${Object.keys(repairedParams).join(", ")}`,
26671
+ history: "replace",
26672
+ });
26673
+ }
26628
26674
  }
26629
26675
 
26630
26676
  return returnValue;