@jsenv/navi 0.16.2 → 0.16.4

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.
@@ -7522,7 +7522,6 @@ setBaseUrl(
7522
7522
  // Pattern registry for building relationships before routes are created
7523
7523
  const patternRegistry = new Map(); // pattern -> patternData
7524
7524
  const patternRelationships = new Map(); // pattern -> relationships
7525
- let patternsRegistered = false;
7526
7525
 
7527
7526
  // Function to detect signals in route patterns and connect them
7528
7527
  const detectSignals = (routePattern) => {
@@ -8433,8 +8432,6 @@ const setupPatterns = (patternDefinitions) => {
8433
8432
  originalPattern: currentPattern,
8434
8433
  });
8435
8434
  }
8436
-
8437
- patternsRegistered = true;
8438
8435
  };
8439
8436
 
8440
8437
  /**
@@ -8444,25 +8441,12 @@ const getPatternData = (urlPatternRaw) => {
8444
8441
  return patternRegistry.get(urlPatternRaw);
8445
8442
  };
8446
8443
 
8447
- /**
8448
- * Get pattern relationships for route creation
8449
- */
8450
- const getPatternRelationships = () => {
8451
- if (!patternsRegistered) {
8452
- throw new Error(
8453
- "Patterns must be registered before accessing relationships",
8454
- );
8455
- }
8456
- return patternRelationships;
8457
- };
8458
-
8459
8444
  /**
8460
8445
  * Clear all registered patterns
8461
8446
  */
8462
8447
  const clearPatterns = () => {
8463
8448
  patternRegistry.clear();
8464
8449
  patternRelationships.clear();
8465
- patternsRegistered = false;
8466
8450
  };
8467
8451
 
8468
8452
  const resolveRouteUrl = (relativeUrl) => {
@@ -8487,6 +8471,22 @@ const resolveRouteUrl = (relativeUrl) => {
8487
8471
  */
8488
8472
 
8489
8473
 
8474
+ // Helper to check if one route pattern is a parent of another
8475
+ const isParentRoute = (parentPattern, childPattern) => {
8476
+ // Normalize patterns by removing query parts and trailing slashes
8477
+ const normalizePath = (pattern) => {
8478
+ const pathPart = pattern.split("?")[0];
8479
+ return pathPart.replace(/\/$/, "") || "/";
8480
+ };
8481
+
8482
+ const parentPath = normalizePath(parentPattern);
8483
+ const childPath = normalizePath(childPattern);
8484
+
8485
+ // Parent route is a parent if child path starts with parent path
8486
+ // and child has additional segments
8487
+ return childPath.startsWith(parentPath) && childPath !== parentPath;
8488
+ };
8489
+
8490
8490
  // Controls what happens to actions when their route stops matching:
8491
8491
  // 'abort' - Cancel the action immediately when route stops matching
8492
8492
  // 'keep-loading' - Allow action to continue running after route stops matching
@@ -8679,41 +8679,6 @@ const getRoutePrivateProperties = (route) => {
8679
8679
  return routePrivatePropertiesMap.get(route);
8680
8680
  };
8681
8681
 
8682
- /**
8683
- * Get child routes of a given route
8684
- */
8685
- const getRouteChildren = (route) => {
8686
- const children = [];
8687
- const routePrivateProperties = getRoutePrivateProperties(route);
8688
- if (!routePrivateProperties) {
8689
- return children;
8690
- }
8691
-
8692
- const { originalPattern } = routePrivateProperties;
8693
- const relationships = getPatternRelationships();
8694
- const relationshipData = relationships.get(originalPattern);
8695
-
8696
- if (!relationshipData || !relationshipData.children) {
8697
- return children;
8698
- }
8699
-
8700
- // Find child routes
8701
- for (const childPattern of relationshipData.children) {
8702
- for (const otherRoute of routeSet) {
8703
- const otherRoutePrivateProperties = getRoutePrivateProperties(otherRoute);
8704
- if (
8705
- otherRoutePrivateProperties &&
8706
- otherRoutePrivateProperties.originalPattern === childPattern
8707
- ) {
8708
- children.push(otherRoute);
8709
- break;
8710
- }
8711
- }
8712
- }
8713
-
8714
- return children;
8715
- };
8716
-
8717
8682
  const registerRoute = (routePattern) => {
8718
8683
  const urlPatternRaw = routePattern.originalPattern;
8719
8684
  const patternData = getPatternData(urlPatternRaw);
@@ -8807,21 +8772,35 @@ const registerRoute = (routePattern) => {
8807
8772
  );
8808
8773
  }
8809
8774
 
8810
- // URL -> Signal synchronization
8775
+ // URL -> Signal synchronization with parent-child route hierarchy checking
8811
8776
  effect(() => {
8812
8777
  const matching = matchingSignal.value;
8813
8778
  const params = rawParamsSignal.value;
8814
8779
  const urlParamValue = params[paramName];
8815
8780
 
8816
- if (!matching) {
8817
- return;
8818
- }
8819
- if (debug) {
8820
- console.debug(
8821
- `[stateSignal] URL -> Signal: ${paramName}=${urlParamValue}`,
8822
- );
8781
+ if (matching) {
8782
+ // When route matches, sync signal with URL parameter value
8783
+ // This ensures URL is the source of truth
8784
+ stateSignal.value = urlParamValue;
8785
+ } else {
8786
+ // When route doesn't match, check if we're navigating to a parent route
8787
+ const currentRoutePattern = routePattern.cleanPattern;
8788
+ const parentRouteMatching = Array.from(routeSet).find((otherRoute) => {
8789
+ if (otherRoute === route || !otherRoute.matching) return false;
8790
+
8791
+ const otherRouteProperties = getRoutePrivateProperties(otherRoute);
8792
+ const otherPattern = otherRouteProperties.routePattern.cleanPattern;
8793
+
8794
+ // Check if the other route is a parent of this route
8795
+ return isParentRoute(otherPattern, currentRoutePattern);
8796
+ });
8797
+
8798
+ if (parentRouteMatching) {
8799
+ // We're navigating to a parent route - clear this signal to reflect the hierarchy
8800
+ const defaultValue = routePattern.parameterDefaults?.[paramName];
8801
+ stateSignal.value = defaultValue;
8802
+ }
8823
8803
  }
8824
- stateSignal.value = urlParamValue;
8825
8804
  });
8826
8805
 
8827
8806
  // Signal -> URL synchronization
@@ -8866,38 +8845,48 @@ const registerRoute = (routePattern) => {
8866
8845
  return null;
8867
8846
  }
8868
8847
 
8869
- // Walk down the hierarchy updating action params and tracking most specific route
8870
- let currentRoute = route;
8871
- let mostSpecificRoute;
8848
+ // Find all matching routes and update their actions, then delegate to most specific
8849
+ const allMatchingRoutes = Array.from(routeSet).filter((r) => r.matching);
8872
8850
 
8873
- while (currentRoute) {
8874
- if (!currentRoute.matching) {
8875
- break;
8876
- }
8877
-
8878
- // Update the most specific route as we go
8879
- mostSpecificRoute = currentRoute;
8880
- // Update action params
8881
- if (currentRoute.action) {
8882
- const currentRoutePrivateProperties =
8883
- getRoutePrivateProperties(currentRoute);
8884
- if (currentRoutePrivateProperties) {
8885
- const { routePattern: currentRoutePattern } =
8886
- currentRoutePrivateProperties;
8887
- const currentResolvedParams = currentRoutePattern.resolveParams();
8851
+ // Update action params on all matching routes
8852
+ for (const matchingRoute of allMatchingRoutes) {
8853
+ if (matchingRoute.action) {
8854
+ const matchingRoutePrivateProperties =
8855
+ getRoutePrivateProperties(matchingRoute);
8856
+ if (matchingRoutePrivateProperties) {
8857
+ const { routePattern: matchingRoutePattern } =
8858
+ matchingRoutePrivateProperties;
8859
+ const currentResolvedParams = matchingRoutePattern.resolveParams();
8888
8860
  const updatedActionParams = {
8889
8861
  ...currentResolvedParams,
8890
8862
  ...newParams,
8891
8863
  };
8892
- currentRoute.action.replaceParams(updatedActionParams);
8864
+ matchingRoute.action.replaceParams(updatedActionParams);
8893
8865
  }
8894
8866
  }
8867
+ }
8895
8868
 
8896
- // Find the first matching child to continue down the hierarchy
8897
- const children = getRouteChildren(currentRoute);
8898
- currentRoute = children.find((child) => child.matching) || null;
8869
+ // Find the most specific route (the one with the longest pattern path)
8870
+ let mostSpecificRoute = route;
8871
+ let maxSegments = route.pattern.split("/").filter((s) => s !== "").length;
8872
+
8873
+ for (const matchingRoute of allMatchingRoutes) {
8874
+ const segments = matchingRoute.pattern
8875
+ .split("/")
8876
+ .filter((s) => s !== "").length;
8877
+ if (segments > maxSegments) {
8878
+ maxSegments = segments;
8879
+ mostSpecificRoute = matchingRoute;
8880
+ }
8899
8881
  }
8900
- return mostSpecificRoute.redirectTo(newParams);
8882
+
8883
+ // If we found a more specific route, delegate to it; otherwise handle it ourselves
8884
+ if (mostSpecificRoute !== route) {
8885
+ return mostSpecificRoute.redirectTo(newParams);
8886
+ }
8887
+
8888
+ // This route is the most specific, handle the redirect ourselves
8889
+ return route.redirectTo(newParams);
8901
8890
  };
8902
8891
  route.buildRelativeUrl = (params) => {
8903
8892
  // buildMostPreciseUrl now handles parameter resolution internally