@jsenv/navi 0.16.30 → 0.16.32

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.
@@ -2499,7 +2499,7 @@ const stateSignal = (defaultValue, options = {}) => {
2499
2499
  if (globalSignalRegistry.has(signalIdString)) {
2500
2500
  const conflictInfo = globalSignalRegistry.get(signalIdString);
2501
2501
  throw new Error(
2502
- `Signal ID conflict: A signal with ID "${signalIdString}" already exists (existing default: ${conflictInfo.options.defaultValue})`,
2502
+ `Signal ID conflict: A signal with ID "${signalIdString}" already exists (existing default: ${conflictInfo.options.getDefaultValue()})`,
2503
2503
  );
2504
2504
  }
2505
2505
 
@@ -2509,45 +2509,61 @@ const stateSignal = (defaultValue, options = {}) => {
2509
2509
  persists
2510
2510
  ? valueInLocalStorage(localStorageKey, { type })
2511
2511
  : NO_LOCAL_STORAGE;
2512
- const getDefaultValue = () => {
2513
- if (persists) {
2514
- const valueFromLocalStorage = readFromLocalStorage();
2515
- if (valueFromLocalStorage !== undefined) {
2516
- if (debug) {
2517
- console.debug(
2518
- `[stateSignal:${signalIdString}] using value from localStorage "${localStorageKey}"=${valueFromLocalStorage}`,
2519
- );
2520
- }
2521
- return valueFromLocalStorage;
2522
- }
2523
- }
2512
+ /**
2513
+ * Returns the current default value from code logic only (static or dynamic).
2514
+ * NEVER considers localStorage - used for URL building and route matching.
2515
+ *
2516
+ * @returns {any} The current code default value, undefined if no default
2517
+ */
2518
+ const getDefaultValue = (internalCall) => {
2524
2519
  if (dynamicDefaultSignal) {
2525
2520
  const dynamicValue = dynamicDefaultSignal.peek();
2526
2521
  if (dynamicValue === undefined) {
2527
2522
  if (staticDefaultValue === undefined) {
2528
2523
  return undefined;
2529
2524
  }
2530
- if (debug) {
2525
+ if (debug && internalCall) {
2531
2526
  console.debug(
2532
2527
  `[stateSignal:${signalIdString}] dynamic default is undefined, using static default=${staticDefaultValue}`,
2533
2528
  );
2534
2529
  }
2535
2530
  return staticDefaultValue;
2536
2531
  }
2537
- if (debug) {
2532
+ if (debug && internalCall) {
2538
2533
  console.debug(
2539
2534
  `[stateSignal:${signalIdString}] using value from dynamic default signal=${dynamicValue}`,
2540
2535
  );
2541
2536
  }
2542
2537
  return dynamicValue;
2543
2538
  }
2544
- if (debug) {
2539
+ if (debug && internalCall) {
2545
2540
  console.debug(
2546
2541
  `[stateSignal:${signalIdString}] using static default value=${staticDefaultValue}`,
2547
2542
  );
2548
2543
  }
2549
2544
  return staticDefaultValue;
2550
2545
  };
2546
+
2547
+ /**
2548
+ * Returns fallback value: localStorage first, then code default.
2549
+ * Used for signal initialization and resets.
2550
+ *
2551
+ * @returns {any} The fallback value (localStorage or code default)
2552
+ */
2553
+ const getFallbackValue = () => {
2554
+ if (persists) {
2555
+ const valueFromLocalStorage = readFromLocalStorage();
2556
+ if (valueFromLocalStorage !== undefined) {
2557
+ if (debug) {
2558
+ console.debug(
2559
+ `[stateSignal:${signalIdString}] using value from localStorage "${localStorageKey}"=${valueFromLocalStorage}`,
2560
+ );
2561
+ }
2562
+ return valueFromLocalStorage;
2563
+ }
2564
+ }
2565
+ return getDefaultValue(true);
2566
+ };
2551
2567
  const isCustomValue = (value) => {
2552
2568
  if (value === undefined) {
2553
2569
  return false;
@@ -2563,7 +2579,7 @@ const stateSignal = (defaultValue, options = {}) => {
2563
2579
  };
2564
2580
 
2565
2581
  // Create signal with initial value: use stored value, or undefined to indicate no explicit value
2566
- const advancedSignal = signal(getDefaultValue());
2582
+ const advancedSignal = signal(getFallbackValue());
2567
2583
  const validity = { valid: true };
2568
2584
  advancedSignal.validity = validity;
2569
2585
  advancedSignal.__signalId = signalIdString;
@@ -2581,16 +2597,16 @@ const stateSignal = (defaultValue, options = {}) => {
2581
2597
  if (value !== undefined) {
2582
2598
  return;
2583
2599
  }
2584
- const defaultValue = getDefaultValue();
2585
- if (defaultValue === value) {
2600
+ const fallbackValue = getFallbackValue();
2601
+ if (fallbackValue === value) {
2586
2602
  return;
2587
2603
  }
2588
2604
  if (debug) {
2589
2605
  console.debug(
2590
- `[stateSignal:${signalIdString}] becomes undefined, reset to ${defaultValue}`,
2606
+ `[stateSignal:${signalIdString}] becomes undefined, reset to ${fallbackValue}`,
2591
2607
  );
2592
2608
  }
2593
- advancedSignal.value = defaultValue;
2609
+ advancedSignal.value = fallbackValue;
2594
2610
  });
2595
2611
  }
2596
2612
  dynamic_signal_effect: {
@@ -2630,18 +2646,18 @@ const stateSignal = (defaultValue, options = {}) => {
2630
2646
  }
2631
2647
 
2632
2648
  // Signal was using default value, update to new default
2633
- const newDefaultValue = getDefaultValue();
2634
- if (newDefaultValue === value) {
2649
+ const newFallbackValue = getFallbackValue();
2650
+ if (newFallbackValue === value) {
2635
2651
  dynamicDefaultPreviousValue = dynamicDefaultValue;
2636
2652
  return;
2637
2653
  }
2638
2654
  if (debug) {
2639
2655
  console.debug(
2640
- `[stateSignal:${signalIdString}] dynamic default updated, update to ${newDefaultValue}`,
2656
+ `[stateSignal:${signalIdString}] dynamic default updated, update to ${newFallbackValue}`,
2641
2657
  );
2642
2658
  }
2643
2659
  dynamicDefaultPreviousValue = dynamicDefaultValue;
2644
- advancedSignal.value = newDefaultValue;
2660
+ advancedSignal.value = newFallbackValue;
2645
2661
  });
2646
2662
  }
2647
2663
  persist_in_local_storage: {
@@ -2711,8 +2727,8 @@ const stateSignal = (defaultValue, options = {}) => {
2711
2727
  globalSignalRegistry.set(signalIdString, {
2712
2728
  signal: advancedSignal,
2713
2729
  options: {
2730
+ staticDefaultValue,
2714
2731
  getDefaultValue,
2715
- defaultValue: staticDefaultValue,
2716
2732
  dynamicDefaultSignal,
2717
2733
  isCustomValue,
2718
2734
  type,
@@ -7755,9 +7771,9 @@ const detectSignals = (routePattern) => {
7755
7771
  updatedPattern = updatedPattern.replace(fullMatch, replacement);
7756
7772
 
7757
7773
  signalConnections.push({
7758
- signal,
7759
7774
  paramName,
7760
- options,
7775
+ signal,
7776
+ ...options,
7761
7777
  });
7762
7778
  } else {
7763
7779
  console.warn(
@@ -7780,30 +7796,18 @@ const detectSignals = (routePattern) => {
7780
7796
  const createRoutePattern = (pattern) => {
7781
7797
  // Detect and process signals in the pattern first
7782
7798
  const [cleanPattern, connections] = detectSignals(pattern);
7783
-
7784
- // Build parameter defaults from signal connections
7785
- const parameterDefaults = new Map();
7786
- for (const connection of connections) {
7787
- const { paramName, options } = connection;
7788
- if (options.defaultValue !== undefined) {
7789
- parameterDefaults.set(paramName, options.defaultValue);
7790
- }
7791
- }
7792
-
7793
- const parsedPattern = parsePattern(
7794
- cleanPattern,
7795
- parameterDefaults,
7796
- connections,
7797
- );
7798
-
7799
+ // Build parameter connection map for efficient lookups
7800
+ const connectionMap = new Map();
7799
7801
  // Create signalSet to track all signals this pattern depends on
7800
7802
  const signalSet = new Set();
7801
7803
  for (const connection of connections) {
7802
- if (connection.signal) {
7803
- signalSet.add(connection.signal);
7804
- }
7804
+ connectionMap.set(connection.paramName, connection);
7805
+
7806
+ signalSet.add(connection.signal);
7805
7807
  }
7806
7808
 
7809
+ const parsedPattern = parsePattern(cleanPattern, connectionMap);
7810
+
7807
7811
  if (DEBUG$2) {
7808
7812
  console.debug(`[CustomPattern] Created pattern:`, parsedPattern);
7809
7813
  console.debug(`[CustomPattern] Signal connections:`, connections);
@@ -7831,26 +7835,34 @@ const createRoutePattern = (pattern) => {
7831
7835
  let resolvedParams = { ...providedParams };
7832
7836
 
7833
7837
  // Process all connections for parameter resolution
7834
- for (const connection of connections) {
7835
- const { paramName, signal } = connection;
7836
-
7837
- if (paramName in providedParams) ; else if (signal?.value !== undefined) {
7838
+ for (const [paramName, connection] of connectionMap) {
7839
+ if (paramName in providedParams) {
7840
+ // Parameter was explicitly provided - always respect explicit parameters
7841
+ // Don't check signal value - explicit parameter takes precedence
7842
+ continue;
7843
+ }
7844
+ const signalValue = connection.signal.value;
7845
+ if (signalValue !== undefined) {
7838
7846
  // Parameter was not provided, check signal value
7839
- resolvedParams[paramName] = signal.value;
7847
+ resolvedParams[paramName] = signalValue;
7840
7848
  }
7841
7849
  }
7842
7850
 
7843
- // Add static defaults for parameters that are still missing
7844
- // This handles cases where the URL doesn't contain the parameter and there's no signal value
7845
- for (const [paramName, defaultValue] of parameterDefaults) {
7846
- if (!(paramName in resolvedParams)) {
7847
- resolvedParams[paramName] = defaultValue;
7851
+ // Add defaults for parameters that are still missing
7852
+ // Use current dynamic defaults from signal connections
7853
+ for (const [paramName, connection] of connectionMap) {
7854
+ if (paramName in resolvedParams) {
7855
+ continue;
7856
+ }
7857
+ const currentDefault = connection.getDefaultValue();
7858
+ if (currentDefault !== undefined) {
7859
+ resolvedParams[paramName] = currentDefault;
7848
7860
  }
7849
7861
  }
7850
7862
 
7851
7863
  // Include active non-default parameters from child routes for URL optimization
7852
7864
  // Only include from child routes that would actually match the current parameters
7853
- const childPatternObjs = patternObject.children || [];
7865
+ const childPatternObjs = patternObject.children;
7854
7866
  for (const childPatternObj of childPatternObjs) {
7855
7867
  // Check if this child route would match the current resolved parameters
7856
7868
  // by simulating URL building and seeing if the child segments align
@@ -7876,20 +7888,20 @@ const createRoutePattern = (pattern) => {
7876
7888
  }
7877
7889
 
7878
7890
  if (childWouldMatch) {
7879
- for (const childConnection of childPatternObj.connections) {
7880
- const {
7881
- paramName: childParam,
7882
- signal: childSignal,
7883
- options: childOptions,
7884
- } = childConnection;
7885
-
7891
+ for (const [
7892
+ childParam,
7893
+ childConnection,
7894
+ ] of childPatternObj.connectionMap) {
7895
+ if (childParam in resolvedParams) {
7896
+ continue;
7897
+ }
7898
+ const childSignalValue = childConnection.signal.value;
7886
7899
  // Only include if not already resolved and is non-default
7887
7900
  if (
7888
- !(childParam in resolvedParams) &&
7889
- childSignal?.value !== undefined &&
7890
- childSignal.value !== childOptions.defaultValue
7901
+ childSignalValue !== undefined &&
7902
+ childSignalValue !== childConnection.getDefaultValue()
7891
7903
  ) {
7892
- resolvedParams[childParam] = childSignal.value;
7904
+ resolvedParams[childParam] = childSignalValue;
7893
7905
  }
7894
7906
  }
7895
7907
  }
@@ -7914,18 +7926,21 @@ const createRoutePattern = (pattern) => {
7914
7926
  const removeDefaultValues = (params) => {
7915
7927
  const filtered = { ...params };
7916
7928
 
7917
- for (const connection of connections) {
7918
- const { paramName, signal, options } = connection;
7919
-
7929
+ for (const [paramName, connection] of connectionMap) {
7920
7930
  if (paramName in filtered) {
7921
7931
  // Parameter is explicitly provided - check if we should remove it
7922
- if (!options.isCustomValue?.(filtered[paramName])) {
7923
- // Parameter value is not custom (matches default) - remove it
7932
+ const paramValue = filtered[paramName];
7933
+
7934
+ if (!connection.isCustomValue(paramValue)) {
7924
7935
  delete filtered[paramName];
7925
7936
  }
7926
- } else if (options.isCustomValue?.(signal.value)) {
7927
- // Parameter not provided but signal has custom value - add it
7928
- filtered[paramName] = signal.value;
7937
+ } else {
7938
+ // Parameter not provided but signal has a value
7939
+ const signalValue = connection.signal.value;
7940
+ if (connection.isCustomValue(signalValue)) {
7941
+ // Only include custom values
7942
+ filtered[paramName] = signalValue;
7943
+ }
7929
7944
  }
7930
7945
  }
7931
7946
 
@@ -7938,12 +7953,11 @@ const createRoutePattern = (pattern) => {
7938
7953
  const canReachLiteralValue = (literalValue, params) => {
7939
7954
  // Check parent's own parameters (signals and user params)
7940
7955
  const parentCanProvide = connections.some((conn) => {
7941
- const signalValue = conn.signal?.value;
7956
+ const signalValue = conn.signal.value;
7942
7957
  const userValue = params[conn.paramName];
7943
7958
  const effectiveValue = userValue !== undefined ? userValue : signalValue;
7944
7959
  return (
7945
- effectiveValue === literalValue &&
7946
- conn.options.isCustomValue?.(effectiveValue)
7960
+ effectiveValue === literalValue && conn.isCustomValue(effectiveValue)
7947
7961
  );
7948
7962
  });
7949
7963
 
@@ -7966,7 +7980,7 @@ const createRoutePattern = (pattern) => {
7966
7980
 
7967
7981
  const getDescendantSignals = (pattern) => {
7968
7982
  const signals = [...pattern.connections];
7969
- for (const child of pattern.children || []) {
7983
+ for (const child of pattern.children) {
7970
7984
  signals.push(...getDescendantSignals(child));
7971
7985
  }
7972
7986
  return signals;
@@ -7978,11 +7992,8 @@ const createRoutePattern = (pattern) => {
7978
7992
  ];
7979
7993
 
7980
7994
  const systemCanProvide = allRelevantSignals.some((conn) => {
7981
- const signalValue = conn.signal?.value;
7982
- return (
7983
- signalValue === literalValue &&
7984
- conn.options.isCustomValue?.(signalValue)
7985
- );
7995
+ const signalValue = conn.signal.value;
7996
+ return signalValue === literalValue && conn.isCustomValue(signalValue);
7986
7997
  });
7987
7998
 
7988
7999
  return parentCanProvide || userCanProvide || systemCanProvide;
@@ -8034,10 +8045,8 @@ const createRoutePattern = (pattern) => {
8034
8045
 
8035
8046
  // If not in params, check signals
8036
8047
  if (parentParamValue === undefined) {
8037
- const parentConnection = connections.find(
8038
- (conn) => conn.paramName === paramName,
8039
- );
8040
- if (parentConnection && parentConnection.signal) {
8048
+ const parentConnection = connectionMap.get(paramName);
8049
+ if (parentConnection) {
8041
8050
  parentParamValue = parentConnection.signal.value;
8042
8051
  }
8043
8052
  }
@@ -8122,16 +8131,12 @@ const createRoutePattern = (pattern) => {
8122
8131
  paramName = item.paramName;
8123
8132
  paramValue = item.userValue;
8124
8133
  } else {
8125
- const { paramName: name, signal, options } = item;
8126
- paramName = name;
8134
+ paramName = item.paramName;
8135
+ paramValue = item.signal.value;
8127
8136
  // Only include custom parent signal values (not using defaults)
8128
- if (
8129
- signal?.value === undefined ||
8130
- !options.isCustomValue?.(signal.value)
8131
- ) {
8137
+ if (paramValue === undefined || !item.isCustomValue(paramValue)) {
8132
8138
  return { isCompatible: true, shouldInclude: false };
8133
8139
  }
8134
- paramValue = signal.value;
8135
8140
  }
8136
8141
 
8137
8142
  // Check if parameter value matches a literal segment in child pattern
@@ -8151,9 +8156,7 @@ const createRoutePattern = (pattern) => {
8151
8156
 
8152
8157
  // ROBUST FIX: For path parameters, check semantic compatibility by verifying
8153
8158
  // that parent parameter values can actually produce the child route structure
8154
- const isParentPathParam = connections.some(
8155
- (conn) => conn.paramName === paramName,
8156
- );
8159
+ const isParentPathParam = connectionMap.has(paramName);
8157
8160
  if (isParentPathParam) {
8158
8161
  // Check if parent parameter value matches any child literal where it should
8159
8162
  // The key insight: if parent has a specific parameter value, child route must
@@ -8187,9 +8190,7 @@ const createRoutePattern = (pattern) => {
8187
8190
  // Check for generic parameter-literal conflicts (only for path parameters)
8188
8191
  if (!matchesChildLiteral) {
8189
8192
  // Check if this is a path parameter from parent pattern
8190
- const isParentPathParam = connections.some(
8191
- (conn) => conn.paramName === paramName,
8192
- );
8193
+ const isParentPathParam = connectionMap.has(paramName);
8193
8194
  if (isParentPathParam) {
8194
8195
  // Parameter value (from user or signal) doesn't match this child's literals
8195
8196
  // Check if child has any literal segments that would conflict with this parameter
@@ -8224,49 +8225,41 @@ const createRoutePattern = (pattern) => {
8224
8225
  // CRITICAL: Check if user explicitly passed undefined for parameters that would
8225
8226
  // normally be used to select this child route via sibling route relationships
8226
8227
  for (const [paramName, paramValue] of Object.entries(params)) {
8227
- if (paramValue === undefined) {
8228
- // Look for sibling routes (other children of the same parent) that use this parameter
8229
- const siblingPatternObjs = patternObject.children || [];
8228
+ if (paramValue !== undefined) {
8229
+ continue;
8230
+ }
8230
8231
 
8231
- for (const siblingPatternObj of siblingPatternObjs) {
8232
- if (siblingPatternObj === childPatternObj) continue; // Skip self
8232
+ // Look for sibling routes (other children of the same parent) that use this parameter
8233
+ const siblingPatternObjs = patternObject.children;
8234
+ for (const siblingPatternObj of siblingPatternObjs) {
8235
+ if (siblingPatternObj === childPatternObj) continue; // Skip self
8233
8236
 
8234
- // Check if sibling route uses this parameter
8235
- const siblingUsesParam = siblingPatternObj.connections.some(
8236
- (conn) => conn.paramName === paramName,
8237
+ // Check if sibling route uses this parameter and get the connection
8238
+ const siblingConnection =
8239
+ siblingPatternObj.connectionMap.get(paramName);
8240
+ if (!siblingConnection) {
8241
+ continue;
8242
+ }
8243
+ const siblingSignalValue = siblingConnection.signal.value;
8244
+ if (siblingSignalValue === undefined) {
8245
+ continue;
8246
+ }
8247
+ // Check if this child route has a literal that matches the signal value
8248
+ const signalMatchesThisChildLiteral =
8249
+ childPatternObj.pattern.segments.some(
8250
+ (segment) =>
8251
+ segment.type === "literal" &&
8252
+ segment.value === siblingSignalValue,
8237
8253
  );
8238
-
8239
- if (siblingUsesParam) {
8240
- // Found a sibling that uses this parameter - get the signal value
8241
- const siblingConnection = siblingPatternObj.connections.find(
8242
- (conn) => conn.paramName === paramName,
8254
+ if (signalMatchesThisChildLiteral) {
8255
+ // This child route's literal matches the sibling's signal value
8256
+ // User passed undefined to override that signal - don't use this child route
8257
+ if (DEBUG$2) {
8258
+ console.debug(
8259
+ `[${pattern}] Blocking child route ${childPatternObj.originalPattern} because ${paramName}:undefined overrides sibling signal value "${siblingSignalValue}"`,
8243
8260
  );
8244
-
8245
- if (
8246
- siblingConnection &&
8247
- siblingConnection.signal?.value !== undefined
8248
- ) {
8249
- const signalValue = siblingConnection.signal.value;
8250
-
8251
- // Check if this child route has a literal that matches the signal value
8252
- const signalMatchesThisChildLiteral =
8253
- childPatternObj.pattern.segments.some(
8254
- (segment) =>
8255
- segment.type === "literal" && segment.value === signalValue,
8256
- );
8257
-
8258
- if (signalMatchesThisChildLiteral) {
8259
- // This child route's literal matches the sibling's signal value
8260
- // User passed undefined to override that signal - don't use this child route
8261
- if (DEBUG$2) {
8262
- console.debug(
8263
- `[${pattern}] Blocking child route ${childPatternObj.originalPattern} because ${paramName}:undefined overrides sibling signal value "${signalValue}"`,
8264
- );
8265
- }
8266
- return false;
8267
- }
8268
- }
8269
8261
  }
8262
+ return false;
8270
8263
  }
8271
8264
  }
8272
8265
  }
@@ -8275,9 +8268,7 @@ const createRoutePattern = (pattern) => {
8275
8268
  let hasActiveParams = false;
8276
8269
  const childParams = { ...compatibility.childParams };
8277
8270
 
8278
- for (const connection of childPatternObj.connections) {
8279
- const { paramName, signal, options } = connection;
8280
-
8271
+ for (const [paramName, connection] of childPatternObj.connectionMap) {
8281
8272
  // Check if parameter was explicitly provided by user
8282
8273
  const hasExplicitParam = paramName in params;
8283
8274
  const explicitValue = params[paramName];
@@ -8287,15 +8278,18 @@ const createRoutePattern = (pattern) => {
8287
8278
  childParams[paramName] = explicitValue;
8288
8279
  if (
8289
8280
  explicitValue !== undefined &&
8290
- options.isCustomValue?.(explicitValue)
8281
+ connection.isCustomValue(explicitValue)
8291
8282
  ) {
8292
8283
  hasActiveParams = true;
8293
8284
  }
8294
- } else if (signal?.value !== undefined) {
8295
- // No explicit override - use signal value
8296
- childParams[paramName] = signal.value;
8297
- if (options.isCustomValue?.(signal.value)) {
8298
- hasActiveParams = true;
8285
+ } else {
8286
+ const signalValue = connection.signal.value;
8287
+ if (signalValue !== undefined) {
8288
+ // No explicit override - use signal value
8289
+ childParams[paramName] = signalValue;
8290
+ if (connection.isCustomValue(signalValue)) {
8291
+ hasActiveParams = true;
8292
+ }
8299
8293
  }
8300
8294
  }
8301
8295
  }
@@ -8320,19 +8314,17 @@ const createRoutePattern = (pattern) => {
8320
8314
  if (value === undefined) return false;
8321
8315
 
8322
8316
  // Check if this parameter has a default value in child's connections
8323
- const childConnection = childPatternObj.connections.find(
8324
- (conn) => conn.paramName === paramName,
8325
- );
8317
+ const childConnection = childPatternObj.connectionMap.get(paramName);
8326
8318
  if (childConnection) {
8327
- return value !== childConnection.options.defaultValue;
8319
+ const childDefault = childConnection.getDefaultValue();
8320
+ return value !== childDefault;
8328
8321
  }
8329
8322
 
8330
8323
  // Check if this parameter has a default value in parent's connections (current pattern)
8331
- const parentConnection = connections.find(
8332
- (conn) => conn.paramName === paramName,
8333
- );
8324
+ const parentConnection = connectionMap.get(paramName);
8334
8325
  if (parentConnection) {
8335
- return value !== parentConnection.options.defaultValue;
8326
+ const parentDefault = parentConnection.getDefaultValue();
8327
+ return value !== parentDefault;
8336
8328
  }
8337
8329
 
8338
8330
  return true; // Non-connection parameters are considered non-default
@@ -8373,18 +8365,17 @@ const createRoutePattern = (pattern) => {
8373
8365
 
8374
8366
  // Check if parameters that determine child selection are non-default
8375
8367
  // OR if any descendant parameters indicate explicit navigation
8376
- for (const connection of connections) {
8377
- const { paramName, options } = connection;
8378
- const defaultValue = parameterDefaults.get(paramName);
8368
+ for (const [paramName, connection] of connectionMap) {
8369
+ const currentDefault = connection.getDefaultValue(); // Use current dynamic default
8379
8370
  const resolvedValue = resolvedParams[paramName];
8380
8371
  const userProvidedParam = paramName in params;
8381
8372
 
8382
- if (extraLiterals.includes(defaultValue)) {
8373
+ if (extraLiterals.includes(currentDefault)) {
8383
8374
  // This literal corresponds to a parameter in the parent
8384
8375
  if (
8385
8376
  userProvidedParam ||
8386
8377
  (resolvedValue !== undefined &&
8387
- options.isCustomValue?.(resolvedValue))
8378
+ connection.isCustomValue(resolvedValue))
8388
8379
  ) {
8389
8380
  // Parameter was explicitly provided or has custom value - child is needed
8390
8381
  childSpecificParamsAreDefaults = false;
@@ -8399,7 +8390,7 @@ const createRoutePattern = (pattern) => {
8399
8390
  if (childSpecificParamsAreDefaults) {
8400
8391
  for (const childConnection of childPatternObj.connections) {
8401
8392
  const childParamName = childConnection.paramName;
8402
- const childDefaultValue = childConnection.options?.defaultValue;
8393
+ const childDefaultValue = childConnection.getDefaultValue();
8403
8394
  const childResolvedValue = resolvedParams[childParamName];
8404
8395
 
8405
8396
  // Only consider path parameters, not query parameters
@@ -8427,19 +8418,18 @@ const createRoutePattern = (pattern) => {
8427
8418
  // When structural parameters (those that determine child selection) are defaults,
8428
8419
  // prefer parent route regardless of whether child has other non-default parameters
8429
8420
  if (childSpecificParamsAreDefaults) {
8430
- for (const connection of connections) {
8431
- const { paramName } = connection;
8432
- const defaultValue = parameterDefaults.get(paramName);
8421
+ for (const [paramName, connection] of connectionMap) {
8422
+ const currentDefault = connection.getDefaultValue(); // Use current dynamic default
8433
8423
  const userProvidedParam = paramName in params;
8434
8424
 
8435
- if (extraLiterals.includes(defaultValue) && !userProvidedParam) {
8425
+ if (extraLiterals.includes(currentDefault) && !userProvidedParam) {
8436
8426
  // This child includes a literal that represents a default value
8437
8427
  // AND user didn't explicitly provide this parameter
8438
8428
  // When structural parameters are defaults, prefer parent for cleaner URL
8439
8429
  shouldUse = false;
8440
8430
  if (DEBUG$2) {
8441
8431
  console.debug(
8442
- `[${pattern}] Preferring parent over child - child includes default literal '${defaultValue}' for param '${paramName}' (structural parameter is default)`,
8432
+ `[${pattern}] Preferring parent over child - child includes default literal '${currentDefault}' for param '${paramName}' (structural parameter is default)`,
8443
8433
  );
8444
8434
  }
8445
8435
  break;
@@ -8472,9 +8462,7 @@ const createRoutePattern = (pattern) => {
8472
8462
  ) => {
8473
8463
  // Start with child signal values
8474
8464
  const baseParams = {};
8475
- for (const connection of childPatternObj.connections) {
8476
- const { paramName, signal, options } = connection;
8477
-
8465
+ for (const [paramName, connection] of childPatternObj.connectionMap) {
8478
8466
  // Check if parameter was explicitly provided by user
8479
8467
  const hasExplicitParam = paramName in params;
8480
8468
  const explicitValue = params[paramName];
@@ -8485,12 +8473,15 @@ const createRoutePattern = (pattern) => {
8485
8473
  baseParams[paramName] = explicitValue;
8486
8474
  }
8487
8475
  // If explicitly undefined, don't include it (which means don't use child route)
8488
- } else if (
8489
- signal?.value !== undefined &&
8490
- options.isCustomValue?.(signal.value)
8491
- ) {
8492
- // No explicit override - use signal value if non-default
8493
- baseParams[paramName] = signal.value;
8476
+ } else {
8477
+ const signalValue = connection.signal.value;
8478
+ if (
8479
+ signalValue !== undefined &&
8480
+ connection.isCustomValue(signalValue)
8481
+ ) {
8482
+ // No explicit override - use signal value if non-default
8483
+ baseParams[paramName] = signalValue;
8484
+ }
8494
8485
  }
8495
8486
  }
8496
8487
 
@@ -8504,13 +8495,10 @@ const createRoutePattern = (pattern) => {
8504
8495
 
8505
8496
  // Add parent's signal parameters
8506
8497
  for (const connection of parentPatternObj.connections) {
8507
- const { paramName, signal, options } = connection;
8498
+ const { paramName } = connection;
8508
8499
 
8509
8500
  // Skip if child route already handles this parameter
8510
- const childConnection = childPatternObj.connections.find(
8511
- (conn) => conn.paramName === paramName,
8512
- );
8513
- if (childConnection) {
8501
+ if (childPatternObj.connectionMap.has(paramName)) {
8514
8502
  continue; // Child route handles this parameter directly
8515
8503
  }
8516
8504
 
@@ -8519,18 +8507,19 @@ const createRoutePattern = (pattern) => {
8519
8507
  continue; // Already have this parameter
8520
8508
  }
8521
8509
 
8510
+ const signalValue = connection.signal.value;
8522
8511
  // Only include custom signal values (not using defaults)
8523
8512
  if (
8524
- signal?.value !== undefined &&
8525
- options.isCustomValue?.(signal.value)
8513
+ signalValue !== undefined &&
8514
+ connection.isCustomValue(signalValue)
8526
8515
  ) {
8527
8516
  // Skip if parameter is consumed by child's literal path segments
8528
8517
  const isConsumedByChildPath = childPatternObj.pattern.segments.some(
8529
8518
  (segment) =>
8530
- segment.type === "literal" && segment.value === signal.value,
8519
+ segment.type === "literal" && segment.value === signalValue,
8531
8520
  );
8532
8521
  if (!isConsumedByChildPath) {
8533
- baseParams[paramName] = signal.value;
8522
+ baseParams[paramName] = signalValue;
8534
8523
  }
8535
8524
  }
8536
8525
  }
@@ -8552,10 +8541,7 @@ const createRoutePattern = (pattern) => {
8552
8541
  }
8553
8542
 
8554
8543
  // Skip if child route already handles this parameter
8555
- const childConnection = childPatternObj.connections.find(
8556
- (conn) => conn.paramName === paramName,
8557
- );
8558
- if (childConnection) {
8544
+ if (childPatternObj.connectionMap.has(paramName)) {
8559
8545
  continue; // Child route handles this parameter directly
8560
8546
  }
8561
8547
 
@@ -8569,10 +8555,10 @@ const createRoutePattern = (pattern) => {
8569
8555
  }
8570
8556
 
8571
8557
  // Check if parent parameter is at default value
8572
- const parentConnection = connections.find(
8573
- (conn) => conn.paramName === paramName,
8574
- );
8575
- const parentDefault = parentConnection?.options?.defaultValue;
8558
+ const parentConnection = connectionMap.get(paramName);
8559
+ const parentDefault = parentConnection
8560
+ ? parentConnection.getDefaultValue()
8561
+ : undefined;
8576
8562
  if (parentValue === parentDefault) {
8577
8563
  continue; // Don't inherit default values
8578
8564
  }
@@ -8583,15 +8569,11 @@ const createRoutePattern = (pattern) => {
8583
8569
 
8584
8570
  // Apply user params with filtering logic
8585
8571
  for (const [paramName, userValue] of Object.entries(params)) {
8586
- const childConnection = childPatternObj.connections.find(
8587
- (conn) => conn.paramName === paramName,
8588
- );
8572
+ const childConnection = childPatternObj.connectionMap.get(paramName);
8589
8573
 
8590
8574
  if (childConnection) {
8591
- const { options } = childConnection;
8592
-
8593
8575
  // Only include if it's a custom value (not default)
8594
- if (options.isCustomValue?.(userValue)) {
8576
+ if (childConnection.isCustomValue(userValue)) {
8595
8577
  baseParams[paramName] = userValue;
8596
8578
  } else {
8597
8579
  // User provided the default value - complete omission
@@ -8648,10 +8630,9 @@ const createRoutePattern = (pattern) => {
8648
8630
 
8649
8631
  if (childParent && childParent.originalPattern === pattern) {
8650
8632
  // Check if child has any non-default signal values
8651
- const hasNonDefaultChildParams = (childPatternObj.connections || []).some(
8633
+ const hasNonDefaultChildParams = childPatternObj.connections.some(
8652
8634
  (childConnection) => {
8653
- const { signal, options } = childConnection;
8654
- return options.isCustomValue?.(signal?.value);
8635
+ return childConnection.isCustomValue(childConnection.signal.value);
8655
8636
  },
8656
8637
  );
8657
8638
 
@@ -8867,9 +8848,7 @@ const createRoutePattern = (pattern) => {
8867
8848
  (qp) => qp.name === connection.paramName,
8868
8849
  );
8869
8850
  // Allow non-default query parameters, but not path parameters
8870
- return (
8871
- !isQueryParam && connection.options.isCustomValue?.(resolvedValue)
8872
- );
8851
+ return !isQueryParam && connection.isCustomValue(resolvedValue);
8873
8852
  });
8874
8853
 
8875
8854
  if (hasNonDefaultPathParams) {
@@ -8899,7 +8878,7 @@ const createRoutePattern = (pattern) => {
8899
8878
  // For non-immediate parents, only allow optimization if all resolved parameters have default values
8900
8879
  const hasNonDefaultParameters = connections.some((connection) => {
8901
8880
  const resolvedValue = resolvedParams[connection.paramName];
8902
- return connection.options.isCustomValue?.(resolvedValue);
8881
+ return connection.isCustomValue(resolvedValue);
8903
8882
  });
8904
8883
 
8905
8884
  if (hasNonDefaultParameters) {
@@ -9077,7 +9056,7 @@ const createRoutePattern = (pattern) => {
9077
9056
  const connection = targetAncestor.connections.find(
9078
9057
  (conn) => conn.paramName === param.name,
9079
9058
  );
9080
- if (!connection || connection.options.defaultValue !== segment) {
9059
+ if (!connection || connection.getDefaultValue() !== segment) {
9081
9060
  if (DEBUG$2) {
9082
9061
  console.debug(
9083
9062
  `[${pattern}] tryDirectOptimization: Parameter default mismatch for ${param.name}`,
@@ -9124,10 +9103,8 @@ const createRoutePattern = (pattern) => {
9124
9103
  // Include parameters that target pattern specifically needs
9125
9104
  if (targetQueryParamNames.has(paramName)) {
9126
9105
  // Only include if the value is not the default value
9127
- const connection = targetAncestor.connections.find(
9128
- (conn) => conn.paramName === paramName,
9129
- );
9130
- if (connection && connection.options.defaultValue !== value) {
9106
+ const connection = targetAncestor.connectionMap.get(paramName);
9107
+ if (connection && connection.getDefaultValue() !== value) {
9131
9108
  ancestorParams[paramName] = value;
9132
9109
  if (DEBUG$2) {
9133
9110
  console.debug(
@@ -9142,7 +9119,7 @@ const createRoutePattern = (pattern) => {
9142
9119
  const connection = sourceConnections.find(
9143
9120
  (conn) => conn.paramName === paramName,
9144
9121
  );
9145
- if (connection && connection.options.defaultValue !== value) {
9122
+ if (connection && connection.getDefaultValue() !== value) {
9146
9123
  ancestorParams[paramName] = value;
9147
9124
  if (DEBUG$2) {
9148
9125
  console.debug(
@@ -9167,14 +9144,14 @@ const createRoutePattern = (pattern) => {
9167
9144
 
9168
9145
  // Also check target ancestor's own signal values for parameters not in resolvedParams
9169
9146
  for (const connection of targetAncestor.connections) {
9170
- const { paramName, signal, options } = connection;
9147
+ const { paramName } = connection;
9148
+ if (paramName in ancestorParams) {
9149
+ continue;
9150
+ }
9171
9151
 
9172
9152
  // Only include if not already processed and has custom value (not default)
9173
- if (
9174
- !(paramName in ancestorParams) &&
9175
- signal?.value !== undefined &&
9176
- options.isCustomValue?.(signal.value)
9177
- ) {
9153
+ const signalValue = connection.signal.value;
9154
+ if (signalValue !== undefined && connection.isCustomValue(signalValue)) {
9178
9155
  // Don't include path parameters that correspond to literal segments we're optimizing away
9179
9156
  const targetParam = targetParams.find((p) => p.name === paramName);
9180
9157
  const isPathParam = targetParam !== undefined; // Any param in segments is a path param
@@ -9182,16 +9159,16 @@ const createRoutePattern = (pattern) => {
9182
9159
  // Skip path parameters - we want them to use default values for optimization
9183
9160
  if (DEBUG$2) {
9184
9161
  console.debug(
9185
- `[${pattern}] tryDirectOptimization: Skipping path param ${paramName}=${signal.value} (will use default)`,
9162
+ `[${pattern}] tryDirectOptimization: Skipping path param ${paramName}=${signalValue} (will use default)`,
9186
9163
  );
9187
9164
  }
9188
9165
  continue;
9189
9166
  }
9190
9167
 
9191
- ancestorParams[paramName] = signal.value;
9168
+ ancestorParams[paramName] = signalValue;
9192
9169
  if (DEBUG$2) {
9193
9170
  console.debug(
9194
- `[${pattern}] tryDirectOptimization: Added target signal param ${paramName}=${signal.value}`,
9171
+ `[${pattern}] tryDirectOptimization: Added target signal param ${paramName}=${signalValue}`,
9195
9172
  );
9196
9173
  }
9197
9174
  }
@@ -9202,24 +9179,25 @@ const createRoutePattern = (pattern) => {
9202
9179
 
9203
9180
  while (currentParent) {
9204
9181
  for (const connection of currentParent.connections) {
9205
- const { paramName, signal, options } = connection;
9182
+ const { paramName } = connection;
9183
+ if (paramName in ancestorParams) {
9184
+ continue;
9185
+ }
9206
9186
 
9207
9187
  // Only inherit custom values (not defaults) that we don't already have
9188
+ const signalValue = connection.signal.value;
9208
9189
  if (
9209
- !(paramName in ancestorParams) &&
9210
- signal?.value !== undefined &&
9211
- options.isCustomValue?.(signal.value)
9190
+ signalValue !== undefined &&
9191
+ connection.isCustomValue(signalValue)
9212
9192
  ) {
9213
9193
  // Check if this parameter would be redundant with target ancestor's literal segments
9214
9194
  const isRedundant = isParameterRedundantWithLiteralSegments(
9215
9195
  targetAncestor.pattern,
9216
9196
  currentParent.pattern,
9217
- paramName,
9218
- signal.value,
9219
- );
9197
+ paramName);
9220
9198
 
9221
9199
  if (!isRedundant) {
9222
- ancestorParams[paramName] = signal.value;
9200
+ ancestorParams[paramName] = signalValue;
9223
9201
  }
9224
9202
  }
9225
9203
  }
@@ -9282,24 +9260,25 @@ const createRoutePattern = (pattern) => {
9282
9260
  while (currentParent) {
9283
9261
  // Check parent's signal connections for non-default values to inherit
9284
9262
  for (const parentConnection of currentParent.connections) {
9285
- const { paramName, signal, options } = parentConnection;
9263
+ const { paramName } = parentConnection;
9264
+ if (paramName in finalParams) {
9265
+ continue; // Already have this parameter
9266
+ }
9286
9267
 
9287
9268
  // Only inherit if we don't have this param and parent has custom value (not default)
9269
+ const parentSignalValue = parentConnection.signal.value;
9288
9270
  if (
9289
- !(paramName in finalParams) &&
9290
- signal?.value !== undefined &&
9291
- options.isCustomValue?.(signal.value)
9271
+ parentSignalValue !== undefined &&
9272
+ parentConnection.isCustomValue(parentSignalValue)
9292
9273
  ) {
9293
9274
  // Don't inherit if parameter corresponds to a literal in our path
9294
9275
  const shouldInherit = !isParameterRedundantWithLiteralSegments(
9295
9276
  parsedPattern,
9296
9277
  currentParent.pattern,
9297
- paramName,
9298
- signal.value,
9299
- );
9278
+ paramName);
9300
9279
 
9301
9280
  if (shouldInherit) {
9302
- finalParams[paramName] = signal.value;
9281
+ finalParams[paramName] = parentSignalValue;
9303
9282
  }
9304
9283
  }
9305
9284
  }
@@ -9349,9 +9328,9 @@ const createRoutePattern = (pattern) => {
9349
9328
  urlPatternRaw: pattern,
9350
9329
  cleanPattern,
9351
9330
  connections,
9331
+ connectionMap,
9352
9332
  parsedPattern,
9353
9333
  signalSet,
9354
- parameterDefaults, // Add parameterDefaults for signal clearing logic
9355
9334
  children: [],
9356
9335
  parent: null,
9357
9336
  depth: 0, // Will be calculated after relationships are built
@@ -9419,11 +9398,7 @@ const canParameterReachChildRoute = (
9419
9398
  /**
9420
9399
  * Parse a route pattern string into structured segments
9421
9400
  */
9422
- const parsePattern = (
9423
- pattern,
9424
- parameterDefaults = new Map(),
9425
- connections = [],
9426
- ) => {
9401
+ const parsePattern = (pattern, connectionMap) => {
9427
9402
  // Handle root route
9428
9403
  if (pattern === "/") {
9429
9404
  return {
@@ -9497,18 +9472,18 @@ const parsePattern = (
9497
9472
  // 1. Explicitly marked with ?
9498
9473
  // 2. Has a default value
9499
9474
  // 3. Connected signal has undefined value and no explicit default (allows /map to match /map/:panel)
9500
- let isOptional = seg.endsWith("?") || parameterDefaults.has(paramName);
9475
+ const connection = connectionMap.get(paramName);
9476
+ const hasDefault =
9477
+ connection && connection.getDefaultValue() !== undefined;
9478
+ let isOptional = seg.endsWith("?") || hasDefault;
9501
9479
 
9502
9480
  if (!isOptional) {
9503
9481
  // Check if connected signal has undefined value (making parameter optional for index routes)
9504
- const connection = connections.find(
9505
- (conn) => conn.paramName === paramName,
9506
- );
9507
9482
  if (
9508
9483
  connection &&
9509
9484
  connection.signal &&
9510
9485
  connection.signal.value === undefined &&
9511
- !parameterDefaults.has(paramName)
9486
+ !hasDefault
9512
9487
  ) {
9513
9488
  isOptional = true;
9514
9489
  }
@@ -9551,7 +9526,7 @@ const checkIfLiteralCanBeOptionalWithPatternObj = (
9551
9526
 
9552
9527
  // Check current pattern's connections
9553
9528
  for (const connection of patternObj.connections) {
9554
- if (connection.options.defaultValue === literalValue) {
9529
+ if (connection.getDefaultValue() === literalValue) {
9555
9530
  return true;
9556
9531
  }
9557
9532
  }
@@ -9560,7 +9535,7 @@ const checkIfLiteralCanBeOptionalWithPatternObj = (
9560
9535
  let currentParent = patternObj.parent;
9561
9536
  while (currentParent) {
9562
9537
  for (const connection of currentParent.connections) {
9563
- if (connection.options.defaultValue === literalValue) {
9538
+ if (connection.getDefaultValue() === literalValue) {
9564
9539
  return true;
9565
9540
  }
9566
9541
  }
@@ -9571,7 +9546,7 @@ const checkIfLiteralCanBeOptionalWithPatternObj = (
9571
9546
  const checkChildrenRecursively = (pattern) => {
9572
9547
  for (const child of pattern.children || []) {
9573
9548
  for (const connection of child.connections) {
9574
- if (connection.options.defaultValue === literalValue) {
9549
+ if (connection.getDefaultValue() === literalValue) {
9575
9550
  return true;
9576
9551
  }
9577
9552
  }
@@ -9699,8 +9674,7 @@ const matchUrl = (
9699
9674
  // Patterns with trailing slashes can match additional URL segments (like wildcards)
9700
9675
  // Patterns without trailing slashes should match exactly (unless they're wildcards)
9701
9676
  // BUT: if pattern has children, it can also match additional segments (hierarchical matching)
9702
- const hasChildren =
9703
- patternObj && patternObj.children && patternObj.children.length > 0;
9677
+ const hasChildren = patternObj && patternObj.children.length > 0;
9704
9678
  if (
9705
9679
  !parsedPattern.wildcard &&
9706
9680
  !parsedPattern.trailingSlash &&
@@ -9730,8 +9704,8 @@ const extractSearchParams = (urlObj, connections = []) => {
9730
9704
  // Create a map for quick signal type lookup
9731
9705
  const signalTypes = new Map();
9732
9706
  for (const connection of connections) {
9733
- if (connection.options.type) {
9734
- signalTypes.set(connection.paramName, connection.options.type);
9707
+ if (connection.type) {
9708
+ signalTypes.set(connection.paramName, connection.type);
9735
9709
  }
9736
9710
  }
9737
9711
 
@@ -10176,9 +10150,7 @@ const setupPatterns = (patternDefinitions) => {
10176
10150
  let parentPatternObj = currentPatternObj.parent;
10177
10151
  while (parentPatternObj) {
10178
10152
  for (const connection of parentPatternObj.connections) {
10179
- if (connection.signal) {
10180
- allRelevantSignals.add(connection.signal);
10181
- }
10153
+ allRelevantSignals.add(connection.signal);
10182
10154
  }
10183
10155
  // Move up the parent chain
10184
10156
  parentPatternObj = parentPatternObj.parent;
@@ -10189,9 +10161,7 @@ const setupPatterns = (patternDefinitions) => {
10189
10161
  for (const childPatternObj of patternObj.children || []) {
10190
10162
  // Add child's own signals
10191
10163
  for (const connection of childPatternObj.connections) {
10192
- if (connection.signal) {
10193
- allRelevantSignals.add(connection.signal);
10194
- }
10164
+ allRelevantSignals.add(connection.signal);
10195
10165
  }
10196
10166
  // Recursively add grandchildren signals
10197
10167
  addDescendantSignals(childPatternObj);
@@ -10357,14 +10327,10 @@ const updateRoutes = (
10357
10327
  newMatching,
10358
10328
  } of routeMatchInfoSet) {
10359
10329
  const { routePattern } = routePrivateProperties;
10360
- const { connections } = routePattern;
10330
+ const { connectionMap } = routePattern;
10361
10331
 
10362
- for (const {
10363
- signal: stateSignal,
10364
- paramName,
10365
- options = {},
10366
- } of connections) {
10367
- const { debug } = options;
10332
+ for (const [paramName, connection] of connectionMap) {
10333
+ const { signal: paramSignal, debug } = connection;
10368
10334
  const params = routePrivateProperties.rawParamsSignal.value;
10369
10335
  const urlParamValue = params[paramName];
10370
10336
 
@@ -10445,7 +10411,7 @@ const updateRoutes = (
10445
10411
  // 2. AND no matching route extracts this parameter from URL
10446
10412
  // 3. AND parameter has no default value (making it truly optional)
10447
10413
  if (matchingRouteInSameFamily && !parameterExtractedByMatchingRoute) {
10448
- const defaultValue = routePattern.parameterDefaults?.get(paramName);
10414
+ const defaultValue = connection.getDefaultValue();
10449
10415
  if (defaultValue === undefined) {
10450
10416
  // Parameter is not extracted within same family and has no default - reset it
10451
10417
  if (debug) {
@@ -10453,21 +10419,21 @@ const updateRoutes = (
10453
10419
  `[route] Same family navigation, ${paramName} not extracted and has no default: resetting signal`,
10454
10420
  );
10455
10421
  }
10456
- stateSignal.value = undefined;
10422
+ paramSignal.value = undefined;
10457
10423
  } else if (debug) {
10458
10424
  // Parameter has a default value - preserve current signal value
10459
10425
  console.debug(
10460
- `[route] Parameter ${paramName} has default value ${defaultValue}: preserving signal value: ${stateSignal.value}`,
10426
+ `[route] Parameter ${paramName} has default value ${defaultValue}: preserving signal value: ${paramSignal.value}`,
10461
10427
  );
10462
10428
  }
10463
10429
  } else if (debug) {
10464
10430
  if (!matchingRouteInSameFamily) {
10465
10431
  console.debug(
10466
- `[route] Different route family: preserving ${paramName} signal value: ${stateSignal.value}`,
10432
+ `[route] Different route family: preserving ${paramName} signal value: ${paramSignal.value}`,
10467
10433
  );
10468
10434
  } else {
10469
10435
  console.debug(
10470
- `[route] Parameter ${paramName} extracted by matching route: preserving signal value: ${stateSignal.value}`,
10436
+ `[route] Parameter ${paramName} extracted by matching route: preserving signal value: ${paramSignal.value}`,
10471
10437
  );
10472
10438
  }
10473
10439
  }
@@ -10476,11 +10442,11 @@ const updateRoutes = (
10476
10442
 
10477
10443
  // URL -> Signal sync: When route matches, ensure signal matches URL state
10478
10444
  // URL is the source of truth for explicit parameters
10479
- const value = stateSignal.peek();
10445
+ const value = paramSignal.peek();
10480
10446
  if (urlParamValue === undefined) {
10481
10447
  // No URL parameter - reset signal to its current default value
10482
10448
  // (handles both static fallback and dynamic default cases)
10483
- const defaultValue = options.getDefaultValue();
10449
+ const defaultValue = connection.getDefaultValue();
10484
10450
  if (value === defaultValue) {
10485
10451
  // Signal already has correct default value, no sync needed
10486
10452
  continue;
@@ -10490,7 +10456,7 @@ const updateRoutes = (
10490
10456
  `[route] URL->Signal: ${paramName} not in URL, reset signal to default (${defaultValue})`,
10491
10457
  );
10492
10458
  }
10493
- stateSignal.value = defaultValue;
10459
+ paramSignal.value = defaultValue;
10494
10460
  continue;
10495
10461
  }
10496
10462
  if (urlParamValue === value) {
@@ -10502,7 +10468,7 @@ const updateRoutes = (
10502
10468
  `[route] URL->Signal: ${paramName}=${urlParamValue} in url, sync signal with url`,
10503
10469
  );
10504
10470
  }
10505
- stateSignal.value = urlParamValue;
10471
+ paramSignal.value = urlParamValue;
10506
10472
  continue;
10507
10473
  }
10508
10474
  }
@@ -10607,7 +10573,7 @@ const getRoutePrivateProperties = (route) => {
10607
10573
 
10608
10574
  const registerRoute = (routePattern) => {
10609
10575
  const urlPatternRaw = routePattern.originalPattern;
10610
- const { cleanPattern, connections } = routePattern;
10576
+ const { cleanPattern, connectionMap } = routePattern;
10611
10577
 
10612
10578
  const cleanupCallbackSet = new Set();
10613
10579
  const cleanup = () => {
@@ -10697,22 +10663,19 @@ const registerRoute = (routePattern) => {
10697
10663
  }
10698
10664
  });
10699
10665
 
10700
- for (const { signal: stateSignal, paramName, options = {} } of connections) {
10701
- const { debug } = options;
10666
+ for (const [paramName, connection] of connectionMap) {
10667
+ const { signal: paramSignal, debug } = connection;
10702
10668
 
10703
10669
  if (debug) {
10704
10670
  console.debug(
10705
10671
  `[route] connecting url param "${paramName}" to signal`,
10706
- stateSignal,
10672
+ paramSignal,
10707
10673
  );
10708
10674
  }
10709
-
10710
- // URL -> Signal synchronization now handled in updateRoutes() to eliminate circular dependency
10711
-
10712
10675
  // Signal -> URL sync: When signal changes, update URL to reflect meaningful state
10713
10676
  // Only sync non-default values to keep URLs clean (static fallbacks stay invisible)
10714
10677
  effect(() => {
10715
- const value = stateSignal.value;
10678
+ const value = paramSignal.value;
10716
10679
  const params = rawParamsSignal.value;
10717
10680
  const urlParamValue = params[paramName];
10718
10681
  const matching = matchingSignal.value;
@@ -10723,7 +10686,7 @@ const registerRoute = (routePattern) => {
10723
10686
  }
10724
10687
  if (urlParamValue === undefined) {
10725
10688
  // No URL parameter exists - check if signal has meaningful value to add
10726
- const defaultValue = options.getDefaultValue();
10689
+ const defaultValue = connection.getDefaultValue();
10727
10690
  if (value === defaultValue) {
10728
10691
  // Signal using default value, keep URL clean (no parameter needed)
10729
10692
  return;