@jsenv/navi 0.16.29 → 0.16.31

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,18 +2509,13 @@ const stateSignal = (defaultValue, options = {}) => {
2509
2509
  persists
2510
2510
  ? valueInLocalStorage(localStorageKey, { type })
2511
2511
  : NO_LOCAL_STORAGE;
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
+ */
2512
2518
  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
- }
2524
2519
  if (dynamicDefaultSignal) {
2525
2520
  const dynamicValue = dynamicDefaultSignal.peek();
2526
2521
  if (dynamicValue === undefined) {
@@ -2548,6 +2543,27 @@ const stateSignal = (defaultValue, options = {}) => {
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();
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,7 +2597,7 @@ const stateSignal = (defaultValue, options = {}) => {
2581
2597
  if (value !== undefined) {
2582
2598
  return;
2583
2599
  }
2584
- const defaultValue = getDefaultValue();
2600
+ const defaultValue = getFallbackValue();
2585
2601
  if (defaultValue === value) {
2586
2602
  return;
2587
2603
  }
@@ -2630,7 +2646,7 @@ const stateSignal = (defaultValue, options = {}) => {
2630
2646
  }
2631
2647
 
2632
2648
  // Signal was using default value, update to new default
2633
- const newDefaultValue = getDefaultValue();
2649
+ const newDefaultValue = getFallbackValue();
2634
2650
  if (newDefaultValue === value) {
2635
2651
  dynamicDefaultPreviousValue = dynamicDefaultValue;
2636
2652
  return;
@@ -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);
@@ -7812,7 +7816,6 @@ const createRoutePattern = (pattern) => {
7812
7816
 
7813
7817
  const applyOn = (url) => {
7814
7818
  const result = matchUrl(parsedPattern, url, {
7815
- parameterDefaults,
7816
7819
  baseUrl,
7817
7820
  connections,
7818
7821
  patternObj: patternObject,
@@ -7832,18 +7835,34 @@ const createRoutePattern = (pattern) => {
7832
7835
  let resolvedParams = { ...providedParams };
7833
7836
 
7834
7837
  // Process all connections for parameter resolution
7835
- for (const connection of connections) {
7836
- const { paramName, signal } = connection;
7837
-
7838
- 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) {
7839
7846
  // Parameter was not provided, check signal value
7840
- resolvedParams[paramName] = signal.value;
7847
+ resolvedParams[paramName] = signalValue;
7848
+ }
7849
+ }
7850
+
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;
7841
7860
  }
7842
7861
  }
7843
7862
 
7844
7863
  // Include active non-default parameters from child routes for URL optimization
7845
7864
  // Only include from child routes that would actually match the current parameters
7846
- const childPatternObjs = patternObject.children || [];
7865
+ const childPatternObjs = patternObject.children;
7847
7866
  for (const childPatternObj of childPatternObjs) {
7848
7867
  // Check if this child route would match the current resolved parameters
7849
7868
  // by simulating URL building and seeing if the child segments align
@@ -7869,20 +7888,20 @@ const createRoutePattern = (pattern) => {
7869
7888
  }
7870
7889
 
7871
7890
  if (childWouldMatch) {
7872
- for (const childConnection of childPatternObj.connections) {
7873
- const {
7874
- paramName: childParam,
7875
- signal: childSignal,
7876
- options: childOptions,
7877
- } = childConnection;
7878
-
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;
7879
7899
  // Only include if not already resolved and is non-default
7880
7900
  if (
7881
- !(childParam in resolvedParams) &&
7882
- childSignal?.value !== undefined &&
7883
- childSignal.value !== childOptions.defaultValue
7901
+ childSignalValue !== undefined &&
7902
+ childSignalValue !== childConnection.getDefaultValue()
7884
7903
  ) {
7885
- resolvedParams[childParam] = childSignal.value;
7904
+ resolvedParams[childParam] = childSignalValue;
7886
7905
  }
7887
7906
  }
7888
7907
  }
@@ -7907,18 +7926,21 @@ const createRoutePattern = (pattern) => {
7907
7926
  const removeDefaultValues = (params) => {
7908
7927
  const filtered = { ...params };
7909
7928
 
7910
- for (const connection of connections) {
7911
- const { paramName, signal, options } = connection;
7912
-
7929
+ for (const [paramName, connection] of connectionMap) {
7913
7930
  if (paramName in filtered) {
7914
7931
  // Parameter is explicitly provided - check if we should remove it
7915
- if (!options.isCustomValue?.(filtered[paramName])) {
7916
- // Parameter value is not custom (matches default) - remove it
7932
+ const paramValue = filtered[paramName];
7933
+
7934
+ if (!connection.isCustomValue(paramValue)) {
7917
7935
  delete filtered[paramName];
7918
7936
  }
7919
- } else if (options.isCustomValue?.(signal.value)) {
7920
- // Parameter not provided but signal has custom value - add it
7921
- 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
+ }
7922
7944
  }
7923
7945
  }
7924
7946
 
@@ -7931,12 +7953,11 @@ const createRoutePattern = (pattern) => {
7931
7953
  const canReachLiteralValue = (literalValue, params) => {
7932
7954
  // Check parent's own parameters (signals and user params)
7933
7955
  const parentCanProvide = connections.some((conn) => {
7934
- const signalValue = conn.signal?.value;
7956
+ const signalValue = conn.signal.value;
7935
7957
  const userValue = params[conn.paramName];
7936
7958
  const effectiveValue = userValue !== undefined ? userValue : signalValue;
7937
7959
  return (
7938
- effectiveValue === literalValue &&
7939
- conn.options.isCustomValue?.(effectiveValue)
7960
+ effectiveValue === literalValue && conn.isCustomValue(effectiveValue)
7940
7961
  );
7941
7962
  });
7942
7963
 
@@ -7959,7 +7980,7 @@ const createRoutePattern = (pattern) => {
7959
7980
 
7960
7981
  const getDescendantSignals = (pattern) => {
7961
7982
  const signals = [...pattern.connections];
7962
- for (const child of pattern.children || []) {
7983
+ for (const child of pattern.children) {
7963
7984
  signals.push(...getDescendantSignals(child));
7964
7985
  }
7965
7986
  return signals;
@@ -7971,11 +7992,8 @@ const createRoutePattern = (pattern) => {
7971
7992
  ];
7972
7993
 
7973
7994
  const systemCanProvide = allRelevantSignals.some((conn) => {
7974
- const signalValue = conn.signal?.value;
7975
- return (
7976
- signalValue === literalValue &&
7977
- conn.options.isCustomValue?.(signalValue)
7978
- );
7995
+ const signalValue = conn.signal.value;
7996
+ return signalValue === literalValue && conn.isCustomValue(signalValue);
7979
7997
  });
7980
7998
 
7981
7999
  return parentCanProvide || userCanProvide || systemCanProvide;
@@ -8027,10 +8045,8 @@ const createRoutePattern = (pattern) => {
8027
8045
 
8028
8046
  // If not in params, check signals
8029
8047
  if (parentParamValue === undefined) {
8030
- const parentConnection = connections.find(
8031
- (conn) => conn.paramName === paramName,
8032
- );
8033
- if (parentConnection && parentConnection.signal) {
8048
+ const parentConnection = connectionMap.get(paramName);
8049
+ if (parentConnection) {
8034
8050
  parentParamValue = parentConnection.signal.value;
8035
8051
  }
8036
8052
  }
@@ -8115,16 +8131,12 @@ const createRoutePattern = (pattern) => {
8115
8131
  paramName = item.paramName;
8116
8132
  paramValue = item.userValue;
8117
8133
  } else {
8118
- const { paramName: name, signal, options } = item;
8119
- paramName = name;
8134
+ paramName = item.paramName;
8135
+ paramValue = item.signal.value;
8120
8136
  // Only include custom parent signal values (not using defaults)
8121
- if (
8122
- signal?.value === undefined ||
8123
- !options.isCustomValue?.(signal.value)
8124
- ) {
8137
+ if (paramValue === undefined || !item.isCustomValue(paramValue)) {
8125
8138
  return { isCompatible: true, shouldInclude: false };
8126
8139
  }
8127
- paramValue = signal.value;
8128
8140
  }
8129
8141
 
8130
8142
  // Check if parameter value matches a literal segment in child pattern
@@ -8144,9 +8156,7 @@ const createRoutePattern = (pattern) => {
8144
8156
 
8145
8157
  // ROBUST FIX: For path parameters, check semantic compatibility by verifying
8146
8158
  // that parent parameter values can actually produce the child route structure
8147
- const isParentPathParam = connections.some(
8148
- (conn) => conn.paramName === paramName,
8149
- );
8159
+ const isParentPathParam = connectionMap.has(paramName);
8150
8160
  if (isParentPathParam) {
8151
8161
  // Check if parent parameter value matches any child literal where it should
8152
8162
  // The key insight: if parent has a specific parameter value, child route must
@@ -8180,9 +8190,7 @@ const createRoutePattern = (pattern) => {
8180
8190
  // Check for generic parameter-literal conflicts (only for path parameters)
8181
8191
  if (!matchesChildLiteral) {
8182
8192
  // Check if this is a path parameter from parent pattern
8183
- const isParentPathParam = connections.some(
8184
- (conn) => conn.paramName === paramName,
8185
- );
8193
+ const isParentPathParam = connectionMap.has(paramName);
8186
8194
  if (isParentPathParam) {
8187
8195
  // Parameter value (from user or signal) doesn't match this child's literals
8188
8196
  // Check if child has any literal segments that would conflict with this parameter
@@ -8217,49 +8225,41 @@ const createRoutePattern = (pattern) => {
8217
8225
  // CRITICAL: Check if user explicitly passed undefined for parameters that would
8218
8226
  // normally be used to select this child route via sibling route relationships
8219
8227
  for (const [paramName, paramValue] of Object.entries(params)) {
8220
- if (paramValue === undefined) {
8221
- // Look for sibling routes (other children of the same parent) that use this parameter
8222
- const siblingPatternObjs = patternObject.children || [];
8228
+ if (paramValue !== undefined) {
8229
+ continue;
8230
+ }
8223
8231
 
8224
- for (const siblingPatternObj of siblingPatternObjs) {
8225
- 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
8226
8236
 
8227
- // Check if sibling route uses this parameter
8228
- const siblingUsesParam = siblingPatternObj.connections.some(
8229
- (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,
8230
8253
  );
8231
-
8232
- if (siblingUsesParam) {
8233
- // Found a sibling that uses this parameter - get the signal value
8234
- const siblingConnection = siblingPatternObj.connections.find(
8235
- (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}"`,
8236
8260
  );
8237
-
8238
- if (
8239
- siblingConnection &&
8240
- siblingConnection.signal?.value !== undefined
8241
- ) {
8242
- const signalValue = siblingConnection.signal.value;
8243
-
8244
- // Check if this child route has a literal that matches the signal value
8245
- const signalMatchesThisChildLiteral =
8246
- childPatternObj.pattern.segments.some(
8247
- (segment) =>
8248
- segment.type === "literal" && segment.value === signalValue,
8249
- );
8250
-
8251
- if (signalMatchesThisChildLiteral) {
8252
- // This child route's literal matches the sibling's signal value
8253
- // User passed undefined to override that signal - don't use this child route
8254
- if (DEBUG$2) {
8255
- console.debug(
8256
- `[${pattern}] Blocking child route ${childPatternObj.originalPattern} because ${paramName}:undefined overrides sibling signal value "${signalValue}"`,
8257
- );
8258
- }
8259
- return false;
8260
- }
8261
- }
8262
8261
  }
8262
+ return false;
8263
8263
  }
8264
8264
  }
8265
8265
  }
@@ -8268,9 +8268,7 @@ const createRoutePattern = (pattern) => {
8268
8268
  let hasActiveParams = false;
8269
8269
  const childParams = { ...compatibility.childParams };
8270
8270
 
8271
- for (const connection of childPatternObj.connections) {
8272
- const { paramName, signal, options } = connection;
8273
-
8271
+ for (const [paramName, connection] of childPatternObj.connectionMap) {
8274
8272
  // Check if parameter was explicitly provided by user
8275
8273
  const hasExplicitParam = paramName in params;
8276
8274
  const explicitValue = params[paramName];
@@ -8280,15 +8278,18 @@ const createRoutePattern = (pattern) => {
8280
8278
  childParams[paramName] = explicitValue;
8281
8279
  if (
8282
8280
  explicitValue !== undefined &&
8283
- options.isCustomValue?.(explicitValue)
8281
+ connection.isCustomValue(explicitValue)
8284
8282
  ) {
8285
8283
  hasActiveParams = true;
8286
8284
  }
8287
- } else if (signal?.value !== undefined) {
8288
- // No explicit override - use signal value
8289
- childParams[paramName] = signal.value;
8290
- if (options.isCustomValue?.(signal.value)) {
8291
- 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
+ }
8292
8293
  }
8293
8294
  }
8294
8295
  }
@@ -8313,19 +8314,17 @@ const createRoutePattern = (pattern) => {
8313
8314
  if (value === undefined) return false;
8314
8315
 
8315
8316
  // Check if this parameter has a default value in child's connections
8316
- const childConnection = childPatternObj.connections.find(
8317
- (conn) => conn.paramName === paramName,
8318
- );
8317
+ const childConnection = childPatternObj.connectionMap.get(paramName);
8319
8318
  if (childConnection) {
8320
- return value !== childConnection.options.defaultValue;
8319
+ const childDefault = childConnection.getDefaultValue();
8320
+ return value !== childDefault;
8321
8321
  }
8322
8322
 
8323
8323
  // Check if this parameter has a default value in parent's connections (current pattern)
8324
- const parentConnection = connections.find(
8325
- (conn) => conn.paramName === paramName,
8326
- );
8324
+ const parentConnection = connectionMap.get(paramName);
8327
8325
  if (parentConnection) {
8328
- return value !== parentConnection.options.defaultValue;
8326
+ const parentDefault = parentConnection.getDefaultValue();
8327
+ return value !== parentDefault;
8329
8328
  }
8330
8329
 
8331
8330
  return true; // Non-connection parameters are considered non-default
@@ -8366,18 +8365,17 @@ const createRoutePattern = (pattern) => {
8366
8365
 
8367
8366
  // Check if parameters that determine child selection are non-default
8368
8367
  // OR if any descendant parameters indicate explicit navigation
8369
- for (const connection of connections) {
8370
- const { paramName, options } = connection;
8371
- const defaultValue = parameterDefaults.get(paramName);
8368
+ for (const [paramName, connection] of connectionMap) {
8369
+ const currentDefault = connection.getDefaultValue(); // Use current dynamic default
8372
8370
  const resolvedValue = resolvedParams[paramName];
8373
8371
  const userProvidedParam = paramName in params;
8374
8372
 
8375
- if (extraLiterals.includes(defaultValue)) {
8373
+ if (extraLiterals.includes(currentDefault)) {
8376
8374
  // This literal corresponds to a parameter in the parent
8377
8375
  if (
8378
8376
  userProvidedParam ||
8379
8377
  (resolvedValue !== undefined &&
8380
- options.isCustomValue?.(resolvedValue))
8378
+ connection.isCustomValue(resolvedValue))
8381
8379
  ) {
8382
8380
  // Parameter was explicitly provided or has custom value - child is needed
8383
8381
  childSpecificParamsAreDefaults = false;
@@ -8392,7 +8390,7 @@ const createRoutePattern = (pattern) => {
8392
8390
  if (childSpecificParamsAreDefaults) {
8393
8391
  for (const childConnection of childPatternObj.connections) {
8394
8392
  const childParamName = childConnection.paramName;
8395
- const childDefaultValue = childConnection.options?.defaultValue;
8393
+ const childDefaultValue = childConnection.getDefaultValue();
8396
8394
  const childResolvedValue = resolvedParams[childParamName];
8397
8395
 
8398
8396
  // Only consider path parameters, not query parameters
@@ -8420,19 +8418,18 @@ const createRoutePattern = (pattern) => {
8420
8418
  // When structural parameters (those that determine child selection) are defaults,
8421
8419
  // prefer parent route regardless of whether child has other non-default parameters
8422
8420
  if (childSpecificParamsAreDefaults) {
8423
- for (const connection of connections) {
8424
- const { paramName } = connection;
8425
- const defaultValue = parameterDefaults.get(paramName);
8421
+ for (const [paramName, connection] of connectionMap) {
8422
+ const currentDefault = connection.getDefaultValue(); // Use current dynamic default
8426
8423
  const userProvidedParam = paramName in params;
8427
8424
 
8428
- if (extraLiterals.includes(defaultValue) && !userProvidedParam) {
8425
+ if (extraLiterals.includes(currentDefault) && !userProvidedParam) {
8429
8426
  // This child includes a literal that represents a default value
8430
8427
  // AND user didn't explicitly provide this parameter
8431
8428
  // When structural parameters are defaults, prefer parent for cleaner URL
8432
8429
  shouldUse = false;
8433
8430
  if (DEBUG$2) {
8434
8431
  console.debug(
8435
- `[${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)`,
8436
8433
  );
8437
8434
  }
8438
8435
  break;
@@ -8465,9 +8462,7 @@ const createRoutePattern = (pattern) => {
8465
8462
  ) => {
8466
8463
  // Start with child signal values
8467
8464
  const baseParams = {};
8468
- for (const connection of childPatternObj.connections) {
8469
- const { paramName, signal, options } = connection;
8470
-
8465
+ for (const [paramName, connection] of childPatternObj.connectionMap) {
8471
8466
  // Check if parameter was explicitly provided by user
8472
8467
  const hasExplicitParam = paramName in params;
8473
8468
  const explicitValue = params[paramName];
@@ -8478,12 +8473,15 @@ const createRoutePattern = (pattern) => {
8478
8473
  baseParams[paramName] = explicitValue;
8479
8474
  }
8480
8475
  // If explicitly undefined, don't include it (which means don't use child route)
8481
- } else if (
8482
- signal?.value !== undefined &&
8483
- options.isCustomValue?.(signal.value)
8484
- ) {
8485
- // No explicit override - use signal value if non-default
8486
- 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
+ }
8487
8485
  }
8488
8486
  }
8489
8487
 
@@ -8497,13 +8495,10 @@ const createRoutePattern = (pattern) => {
8497
8495
 
8498
8496
  // Add parent's signal parameters
8499
8497
  for (const connection of parentPatternObj.connections) {
8500
- const { paramName, signal, options } = connection;
8498
+ const { paramName } = connection;
8501
8499
 
8502
8500
  // Skip if child route already handles this parameter
8503
- const childConnection = childPatternObj.connections.find(
8504
- (conn) => conn.paramName === paramName,
8505
- );
8506
- if (childConnection) {
8501
+ if (childPatternObj.connectionMap.has(paramName)) {
8507
8502
  continue; // Child route handles this parameter directly
8508
8503
  }
8509
8504
 
@@ -8512,18 +8507,19 @@ const createRoutePattern = (pattern) => {
8512
8507
  continue; // Already have this parameter
8513
8508
  }
8514
8509
 
8510
+ const signalValue = connection.signal.value;
8515
8511
  // Only include custom signal values (not using defaults)
8516
8512
  if (
8517
- signal?.value !== undefined &&
8518
- options.isCustomValue?.(signal.value)
8513
+ signalValue !== undefined &&
8514
+ connection.isCustomValue(signalValue)
8519
8515
  ) {
8520
8516
  // Skip if parameter is consumed by child's literal path segments
8521
8517
  const isConsumedByChildPath = childPatternObj.pattern.segments.some(
8522
8518
  (segment) =>
8523
- segment.type === "literal" && segment.value === signal.value,
8519
+ segment.type === "literal" && segment.value === signalValue,
8524
8520
  );
8525
8521
  if (!isConsumedByChildPath) {
8526
- baseParams[paramName] = signal.value;
8522
+ baseParams[paramName] = signalValue;
8527
8523
  }
8528
8524
  }
8529
8525
  }
@@ -8545,10 +8541,7 @@ const createRoutePattern = (pattern) => {
8545
8541
  }
8546
8542
 
8547
8543
  // Skip if child route already handles this parameter
8548
- const childConnection = childPatternObj.connections.find(
8549
- (conn) => conn.paramName === paramName,
8550
- );
8551
- if (childConnection) {
8544
+ if (childPatternObj.connectionMap.has(paramName)) {
8552
8545
  continue; // Child route handles this parameter directly
8553
8546
  }
8554
8547
 
@@ -8562,10 +8555,10 @@ const createRoutePattern = (pattern) => {
8562
8555
  }
8563
8556
 
8564
8557
  // Check if parent parameter is at default value
8565
- const parentConnection = connections.find(
8566
- (conn) => conn.paramName === paramName,
8567
- );
8568
- const parentDefault = parentConnection?.options?.defaultValue;
8558
+ const parentConnection = connectionMap.get(paramName);
8559
+ const parentDefault = parentConnection
8560
+ ? parentConnection.getDefaultValue()
8561
+ : undefined;
8569
8562
  if (parentValue === parentDefault) {
8570
8563
  continue; // Don't inherit default values
8571
8564
  }
@@ -8576,15 +8569,11 @@ const createRoutePattern = (pattern) => {
8576
8569
 
8577
8570
  // Apply user params with filtering logic
8578
8571
  for (const [paramName, userValue] of Object.entries(params)) {
8579
- const childConnection = childPatternObj.connections.find(
8580
- (conn) => conn.paramName === paramName,
8581
- );
8572
+ const childConnection = childPatternObj.connectionMap.get(paramName);
8582
8573
 
8583
8574
  if (childConnection) {
8584
- const { options } = childConnection;
8585
-
8586
8575
  // Only include if it's a custom value (not default)
8587
- if (options.isCustomValue?.(userValue)) {
8576
+ if (childConnection.isCustomValue(userValue)) {
8588
8577
  baseParams[paramName] = userValue;
8589
8578
  } else {
8590
8579
  // User provided the default value - complete omission
@@ -8641,10 +8630,9 @@ const createRoutePattern = (pattern) => {
8641
8630
 
8642
8631
  if (childParent && childParent.originalPattern === pattern) {
8643
8632
  // Check if child has any non-default signal values
8644
- const hasNonDefaultChildParams = (childPatternObj.connections || []).some(
8633
+ const hasNonDefaultChildParams = childPatternObj.connections.some(
8645
8634
  (childConnection) => {
8646
- const { signal, options } = childConnection;
8647
- return options.isCustomValue?.(signal?.value);
8635
+ return childConnection.isCustomValue(childConnection.signal.value);
8648
8636
  },
8649
8637
  );
8650
8638
 
@@ -8860,9 +8848,7 @@ const createRoutePattern = (pattern) => {
8860
8848
  (qp) => qp.name === connection.paramName,
8861
8849
  );
8862
8850
  // Allow non-default query parameters, but not path parameters
8863
- return (
8864
- !isQueryParam && connection.options.isCustomValue?.(resolvedValue)
8865
- );
8851
+ return !isQueryParam && connection.isCustomValue(resolvedValue);
8866
8852
  });
8867
8853
 
8868
8854
  if (hasNonDefaultPathParams) {
@@ -8892,7 +8878,7 @@ const createRoutePattern = (pattern) => {
8892
8878
  // For non-immediate parents, only allow optimization if all resolved parameters have default values
8893
8879
  const hasNonDefaultParameters = connections.some((connection) => {
8894
8880
  const resolvedValue = resolvedParams[connection.paramName];
8895
- return connection.options.isCustomValue?.(resolvedValue);
8881
+ return connection.isCustomValue(resolvedValue);
8896
8882
  });
8897
8883
 
8898
8884
  if (hasNonDefaultParameters) {
@@ -9070,7 +9056,7 @@ const createRoutePattern = (pattern) => {
9070
9056
  const connection = targetAncestor.connections.find(
9071
9057
  (conn) => conn.paramName === param.name,
9072
9058
  );
9073
- if (!connection || connection.options.defaultValue !== segment) {
9059
+ if (!connection || connection.getDefaultValue() !== segment) {
9074
9060
  if (DEBUG$2) {
9075
9061
  console.debug(
9076
9062
  `[${pattern}] tryDirectOptimization: Parameter default mismatch for ${param.name}`,
@@ -9117,10 +9103,8 @@ const createRoutePattern = (pattern) => {
9117
9103
  // Include parameters that target pattern specifically needs
9118
9104
  if (targetQueryParamNames.has(paramName)) {
9119
9105
  // Only include if the value is not the default value
9120
- const connection = targetAncestor.connections.find(
9121
- (conn) => conn.paramName === paramName,
9122
- );
9123
- if (connection && connection.options.defaultValue !== value) {
9106
+ const connection = targetAncestor.connectionMap.get(paramName);
9107
+ if (connection && connection.getDefaultValue() !== value) {
9124
9108
  ancestorParams[paramName] = value;
9125
9109
  if (DEBUG$2) {
9126
9110
  console.debug(
@@ -9135,7 +9119,7 @@ const createRoutePattern = (pattern) => {
9135
9119
  const connection = sourceConnections.find(
9136
9120
  (conn) => conn.paramName === paramName,
9137
9121
  );
9138
- if (connection && connection.options.defaultValue !== value) {
9122
+ if (connection && connection.getDefaultValue() !== value) {
9139
9123
  ancestorParams[paramName] = value;
9140
9124
  if (DEBUG$2) {
9141
9125
  console.debug(
@@ -9160,14 +9144,14 @@ const createRoutePattern = (pattern) => {
9160
9144
 
9161
9145
  // Also check target ancestor's own signal values for parameters not in resolvedParams
9162
9146
  for (const connection of targetAncestor.connections) {
9163
- const { paramName, signal, options } = connection;
9147
+ const { paramName } = connection;
9148
+ if (paramName in ancestorParams) {
9149
+ continue;
9150
+ }
9164
9151
 
9165
9152
  // Only include if not already processed and has custom value (not default)
9166
- if (
9167
- !(paramName in ancestorParams) &&
9168
- signal?.value !== undefined &&
9169
- options.isCustomValue?.(signal.value)
9170
- ) {
9153
+ const signalValue = connection.signal.value;
9154
+ if (signalValue !== undefined && connection.isCustomValue(signalValue)) {
9171
9155
  // Don't include path parameters that correspond to literal segments we're optimizing away
9172
9156
  const targetParam = targetParams.find((p) => p.name === paramName);
9173
9157
  const isPathParam = targetParam !== undefined; // Any param in segments is a path param
@@ -9175,16 +9159,16 @@ const createRoutePattern = (pattern) => {
9175
9159
  // Skip path parameters - we want them to use default values for optimization
9176
9160
  if (DEBUG$2) {
9177
9161
  console.debug(
9178
- `[${pattern}] tryDirectOptimization: Skipping path param ${paramName}=${signal.value} (will use default)`,
9162
+ `[${pattern}] tryDirectOptimization: Skipping path param ${paramName}=${signalValue} (will use default)`,
9179
9163
  );
9180
9164
  }
9181
9165
  continue;
9182
9166
  }
9183
9167
 
9184
- ancestorParams[paramName] = signal.value;
9168
+ ancestorParams[paramName] = signalValue;
9185
9169
  if (DEBUG$2) {
9186
9170
  console.debug(
9187
- `[${pattern}] tryDirectOptimization: Added target signal param ${paramName}=${signal.value}`,
9171
+ `[${pattern}] tryDirectOptimization: Added target signal param ${paramName}=${signalValue}`,
9188
9172
  );
9189
9173
  }
9190
9174
  }
@@ -9195,24 +9179,25 @@ const createRoutePattern = (pattern) => {
9195
9179
 
9196
9180
  while (currentParent) {
9197
9181
  for (const connection of currentParent.connections) {
9198
- const { paramName, signal, options } = connection;
9182
+ const { paramName } = connection;
9183
+ if (paramName in ancestorParams) {
9184
+ continue;
9185
+ }
9199
9186
 
9200
9187
  // Only inherit custom values (not defaults) that we don't already have
9188
+ const signalValue = connection.signal.value;
9201
9189
  if (
9202
- !(paramName in ancestorParams) &&
9203
- signal?.value !== undefined &&
9204
- options.isCustomValue?.(signal.value)
9190
+ signalValue !== undefined &&
9191
+ connection.isCustomValue(signalValue)
9205
9192
  ) {
9206
9193
  // Check if this parameter would be redundant with target ancestor's literal segments
9207
9194
  const isRedundant = isParameterRedundantWithLiteralSegments(
9208
9195
  targetAncestor.pattern,
9209
9196
  currentParent.pattern,
9210
- paramName,
9211
- signal.value,
9212
- );
9197
+ paramName);
9213
9198
 
9214
9199
  if (!isRedundant) {
9215
- ancestorParams[paramName] = signal.value;
9200
+ ancestorParams[paramName] = signalValue;
9216
9201
  }
9217
9202
  }
9218
9203
  }
@@ -9275,24 +9260,25 @@ const createRoutePattern = (pattern) => {
9275
9260
  while (currentParent) {
9276
9261
  // Check parent's signal connections for non-default values to inherit
9277
9262
  for (const parentConnection of currentParent.connections) {
9278
- const { paramName, signal, options } = parentConnection;
9263
+ const { paramName } = parentConnection;
9264
+ if (paramName in finalParams) {
9265
+ continue; // Already have this parameter
9266
+ }
9279
9267
 
9280
9268
  // Only inherit if we don't have this param and parent has custom value (not default)
9269
+ const parentSignalValue = parentConnection.signal.value;
9281
9270
  if (
9282
- !(paramName in finalParams) &&
9283
- signal?.value !== undefined &&
9284
- options.isCustomValue?.(signal.value)
9271
+ parentSignalValue !== undefined &&
9272
+ parentConnection.isCustomValue(parentSignalValue)
9285
9273
  ) {
9286
9274
  // Don't inherit if parameter corresponds to a literal in our path
9287
9275
  const shouldInherit = !isParameterRedundantWithLiteralSegments(
9288
9276
  parsedPattern,
9289
9277
  currentParent.pattern,
9290
- paramName,
9291
- signal.value,
9292
- );
9278
+ paramName);
9293
9279
 
9294
9280
  if (shouldInherit) {
9295
- finalParams[paramName] = signal.value;
9281
+ finalParams[paramName] = parentSignalValue;
9296
9282
  }
9297
9283
  }
9298
9284
  }
@@ -9342,9 +9328,9 @@ const createRoutePattern = (pattern) => {
9342
9328
  urlPatternRaw: pattern,
9343
9329
  cleanPattern,
9344
9330
  connections,
9331
+ connectionMap,
9345
9332
  parsedPattern,
9346
9333
  signalSet,
9347
- parameterDefaults, // Add parameterDefaults for signal clearing logic
9348
9334
  children: [],
9349
9335
  parent: null,
9350
9336
  depth: 0, // Will be calculated after relationships are built
@@ -9412,11 +9398,7 @@ const canParameterReachChildRoute = (
9412
9398
  /**
9413
9399
  * Parse a route pattern string into structured segments
9414
9400
  */
9415
- const parsePattern = (
9416
- pattern,
9417
- parameterDefaults = new Map(),
9418
- connections = [],
9419
- ) => {
9401
+ const parsePattern = (pattern, connectionMap) => {
9420
9402
  // Handle root route
9421
9403
  if (pattern === "/") {
9422
9404
  return {
@@ -9490,18 +9472,18 @@ const parsePattern = (
9490
9472
  // 1. Explicitly marked with ?
9491
9473
  // 2. Has a default value
9492
9474
  // 3. Connected signal has undefined value and no explicit default (allows /map to match /map/:panel)
9493
- 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;
9494
9479
 
9495
9480
  if (!isOptional) {
9496
9481
  // Check if connected signal has undefined value (making parameter optional for index routes)
9497
- const connection = connections.find(
9498
- (conn) => conn.paramName === paramName,
9499
- );
9500
9482
  if (
9501
9483
  connection &&
9502
9484
  connection.signal &&
9503
9485
  connection.signal.value === undefined &&
9504
- !parameterDefaults.has(paramName)
9486
+ !hasDefault
9505
9487
  ) {
9506
9488
  isOptional = true;
9507
9489
  }
@@ -9544,7 +9526,7 @@ const checkIfLiteralCanBeOptionalWithPatternObj = (
9544
9526
 
9545
9527
  // Check current pattern's connections
9546
9528
  for (const connection of patternObj.connections) {
9547
- if (connection.options.defaultValue === literalValue) {
9529
+ if (connection.getDefaultValue() === literalValue) {
9548
9530
  return true;
9549
9531
  }
9550
9532
  }
@@ -9553,7 +9535,7 @@ const checkIfLiteralCanBeOptionalWithPatternObj = (
9553
9535
  let currentParent = patternObj.parent;
9554
9536
  while (currentParent) {
9555
9537
  for (const connection of currentParent.connections) {
9556
- if (connection.options.defaultValue === literalValue) {
9538
+ if (connection.getDefaultValue() === literalValue) {
9557
9539
  return true;
9558
9540
  }
9559
9541
  }
@@ -9564,7 +9546,7 @@ const checkIfLiteralCanBeOptionalWithPatternObj = (
9564
9546
  const checkChildrenRecursively = (pattern) => {
9565
9547
  for (const child of pattern.children || []) {
9566
9548
  for (const connection of child.connections) {
9567
- if (connection.options.defaultValue === literalValue) {
9549
+ if (connection.getDefaultValue() === literalValue) {
9568
9550
  return true;
9569
9551
  }
9570
9552
  }
@@ -9584,7 +9566,7 @@ const checkIfLiteralCanBeOptionalWithPatternObj = (
9584
9566
  const matchUrl = (
9585
9567
  parsedPattern,
9586
9568
  url,
9587
- { parameterDefaults, baseUrl, connections = [], patternObj = null },
9569
+ { baseUrl, connections = [], patternObj = null },
9588
9570
  ) => {
9589
9571
  // Parse the URL
9590
9572
  const urlObj = new URL(url, baseUrl);
@@ -9668,23 +9650,15 @@ const matchUrl = (
9668
9650
  if (urlSegmentIndex >= urlSegments.length) {
9669
9651
  // No URL segment for this parameter
9670
9652
  if (patternSeg.optional) {
9671
- // Optional parameter - use default if available
9672
- const defaultValue = parameterDefaults.get(patternSeg.name);
9673
- if (defaultValue !== undefined) {
9674
- params[patternSeg.name] = defaultValue;
9675
- }
9653
+ // Optional parameter - don't add default here, let resolveParams handle it
9676
9654
  continue;
9677
9655
  }
9678
9656
  // Required parameter missing - but check if we can use trailing slash logic
9679
9657
  // If this is the last segment and we have a trailing slash difference, it might still match
9680
9658
  const isLastSegment = i === parsedPattern.segments.length - 1;
9681
9659
  if (isLastSegment && patternHasTrailingSlash && !urlHasTrailingSlash) {
9682
- // Pattern expects trailing slash segment, URL doesn't have it
9683
- const defaultValue = parameterDefaults.get(patternSeg.name);
9684
- if (defaultValue !== undefined) {
9685
- params[patternSeg.name] = defaultValue;
9686
- continue;
9687
- }
9660
+ // Pattern expects trailing slash segment, URL doesn't have it - allow missing optional param
9661
+ continue;
9688
9662
  }
9689
9663
  return null; // Required parameter missing
9690
9664
  }
@@ -9700,8 +9674,7 @@ const matchUrl = (
9700
9674
  // Patterns with trailing slashes can match additional URL segments (like wildcards)
9701
9675
  // Patterns without trailing slashes should match exactly (unless they're wildcards)
9702
9676
  // BUT: if pattern has children, it can also match additional segments (hierarchical matching)
9703
- const hasChildren =
9704
- patternObj && patternObj.children && patternObj.children.length > 0;
9677
+ const hasChildren = patternObj && patternObj.children.length > 0;
9705
9678
  if (
9706
9679
  !parsedPattern.wildcard &&
9707
9680
  !parsedPattern.trailingSlash &&
@@ -9716,12 +9689,8 @@ const matchUrl = (
9716
9689
  const searchParams = extractSearchParams(urlObj, connections);
9717
9690
  Object.assign(params, searchParams);
9718
9691
 
9719
- // Apply remaining parameter defaults for unmatched parameters
9720
- for (const [paramName, defaultValue] of parameterDefaults) {
9721
- if (!(paramName in params)) {
9722
- params[paramName] = defaultValue;
9723
- }
9724
- }
9692
+ // Don't add defaults here - rawParams should only contain what's in the URL
9693
+ // Defaults are handled by resolveParams() to create the final merged parameters
9725
9694
 
9726
9695
  return params;
9727
9696
  };
@@ -9735,8 +9704,8 @@ const extractSearchParams = (urlObj, connections = []) => {
9735
9704
  // Create a map for quick signal type lookup
9736
9705
  const signalTypes = new Map();
9737
9706
  for (const connection of connections) {
9738
- if (connection.options.type) {
9739
- signalTypes.set(connection.paramName, connection.options.type);
9707
+ if (connection.type) {
9708
+ signalTypes.set(connection.paramName, connection.type);
9740
9709
  }
9741
9710
  }
9742
9711
 
@@ -10181,9 +10150,7 @@ const setupPatterns = (patternDefinitions) => {
10181
10150
  let parentPatternObj = currentPatternObj.parent;
10182
10151
  while (parentPatternObj) {
10183
10152
  for (const connection of parentPatternObj.connections) {
10184
- if (connection.signal) {
10185
- allRelevantSignals.add(connection.signal);
10186
- }
10153
+ allRelevantSignals.add(connection.signal);
10187
10154
  }
10188
10155
  // Move up the parent chain
10189
10156
  parentPatternObj = parentPatternObj.parent;
@@ -10194,9 +10161,7 @@ const setupPatterns = (patternDefinitions) => {
10194
10161
  for (const childPatternObj of patternObj.children || []) {
10195
10162
  // Add child's own signals
10196
10163
  for (const connection of childPatternObj.connections) {
10197
- if (connection.signal) {
10198
- allRelevantSignals.add(connection.signal);
10199
- }
10164
+ allRelevantSignals.add(connection.signal);
10200
10165
  }
10201
10166
  // Recursively add grandchildren signals
10202
10167
  addDescendantSignals(childPatternObj);
@@ -10362,14 +10327,10 @@ const updateRoutes = (
10362
10327
  newMatching,
10363
10328
  } of routeMatchInfoSet) {
10364
10329
  const { routePattern } = routePrivateProperties;
10365
- const { connections } = routePattern;
10330
+ const { connectionMap } = routePattern;
10366
10331
 
10367
- for (const {
10368
- signal: stateSignal,
10369
- paramName,
10370
- options = {},
10371
- } of connections) {
10372
- const { debug } = options;
10332
+ for (const [paramName, connection] of connectionMap) {
10333
+ const { signal: paramSignal, debug } = connection;
10373
10334
  const params = routePrivateProperties.rawParamsSignal.value;
10374
10335
  const urlParamValue = params[paramName];
10375
10336
 
@@ -10450,7 +10411,7 @@ const updateRoutes = (
10450
10411
  // 2. AND no matching route extracts this parameter from URL
10451
10412
  // 3. AND parameter has no default value (making it truly optional)
10452
10413
  if (matchingRouteInSameFamily && !parameterExtractedByMatchingRoute) {
10453
- const defaultValue = routePattern.parameterDefaults?.get(paramName);
10414
+ const defaultValue = connection.getDefaultValue();
10454
10415
  if (defaultValue === undefined) {
10455
10416
  // Parameter is not extracted within same family and has no default - reset it
10456
10417
  if (debug) {
@@ -10458,21 +10419,21 @@ const updateRoutes = (
10458
10419
  `[route] Same family navigation, ${paramName} not extracted and has no default: resetting signal`,
10459
10420
  );
10460
10421
  }
10461
- stateSignal.value = undefined;
10422
+ paramSignal.value = undefined;
10462
10423
  } else if (debug) {
10463
10424
  // Parameter has a default value - preserve current signal value
10464
10425
  console.debug(
10465
- `[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}`,
10466
10427
  );
10467
10428
  }
10468
10429
  } else if (debug) {
10469
10430
  if (!matchingRouteInSameFamily) {
10470
10431
  console.debug(
10471
- `[route] Different route family: preserving ${paramName} signal value: ${stateSignal.value}`,
10432
+ `[route] Different route family: preserving ${paramName} signal value: ${paramSignal.value}`,
10472
10433
  );
10473
10434
  } else {
10474
10435
  console.debug(
10475
- `[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}`,
10476
10437
  );
10477
10438
  }
10478
10439
  }
@@ -10481,11 +10442,11 @@ const updateRoutes = (
10481
10442
 
10482
10443
  // URL -> Signal sync: When route matches, ensure signal matches URL state
10483
10444
  // URL is the source of truth for explicit parameters
10484
- const value = stateSignal.peek();
10445
+ const value = paramSignal.peek();
10485
10446
  if (urlParamValue === undefined) {
10486
10447
  // No URL parameter - reset signal to its current default value
10487
10448
  // (handles both static fallback and dynamic default cases)
10488
- const defaultValue = options.getDefaultValue();
10449
+ const defaultValue = connection.getDefaultValue();
10489
10450
  if (value === defaultValue) {
10490
10451
  // Signal already has correct default value, no sync needed
10491
10452
  continue;
@@ -10495,7 +10456,7 @@ const updateRoutes = (
10495
10456
  `[route] URL->Signal: ${paramName} not in URL, reset signal to default (${defaultValue})`,
10496
10457
  );
10497
10458
  }
10498
- stateSignal.value = defaultValue;
10459
+ paramSignal.value = defaultValue;
10499
10460
  continue;
10500
10461
  }
10501
10462
  if (urlParamValue === value) {
@@ -10507,7 +10468,7 @@ const updateRoutes = (
10507
10468
  `[route] URL->Signal: ${paramName}=${urlParamValue} in url, sync signal with url`,
10508
10469
  );
10509
10470
  }
10510
- stateSignal.value = urlParamValue;
10471
+ paramSignal.value = urlParamValue;
10511
10472
  continue;
10512
10473
  }
10513
10474
  }
@@ -10612,7 +10573,7 @@ const getRoutePrivateProperties = (route) => {
10612
10573
 
10613
10574
  const registerRoute = (routePattern) => {
10614
10575
  const urlPatternRaw = routePattern.originalPattern;
10615
- const { cleanPattern, connections } = routePattern;
10576
+ const { cleanPattern, connectionMap } = routePattern;
10616
10577
 
10617
10578
  const cleanupCallbackSet = new Set();
10618
10579
  const cleanup = () => {
@@ -10702,22 +10663,19 @@ const registerRoute = (routePattern) => {
10702
10663
  }
10703
10664
  });
10704
10665
 
10705
- for (const { signal: stateSignal, paramName, options = {} } of connections) {
10706
- const { debug } = options;
10666
+ for (const [paramName, connection] of connectionMap) {
10667
+ const { signal: paramSignal, debug } = connection;
10707
10668
 
10708
10669
  if (debug) {
10709
10670
  console.debug(
10710
10671
  `[route] connecting url param "${paramName}" to signal`,
10711
- stateSignal,
10672
+ paramSignal,
10712
10673
  );
10713
10674
  }
10714
-
10715
- // URL -> Signal synchronization now handled in updateRoutes() to eliminate circular dependency
10716
-
10717
10675
  // Signal -> URL sync: When signal changes, update URL to reflect meaningful state
10718
10676
  // Only sync non-default values to keep URLs clean (static fallbacks stay invisible)
10719
10677
  effect(() => {
10720
- const value = stateSignal.value;
10678
+ const value = paramSignal.value;
10721
10679
  const params = rawParamsSignal.value;
10722
10680
  const urlParamValue = params[paramName];
10723
10681
  const matching = matchingSignal.value;
@@ -10728,7 +10686,7 @@ const registerRoute = (routePattern) => {
10728
10686
  }
10729
10687
  if (urlParamValue === undefined) {
10730
10688
  // No URL parameter exists - check if signal has meaningful value to add
10731
- const defaultValue = options.getDefaultValue();
10689
+ const defaultValue = connection.getDefaultValue();
10732
10690
  if (value === defaultValue) {
10733
10691
  // Signal using default value, keep URL clean (no parameter needed)
10734
10692
  return;