@jsenv/navi 0.16.1 → 0.16.2

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,21 +7522,31 @@ 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;
7525
7526
 
7526
7527
  // Function to detect signals in route patterns and connect them
7527
7528
  const detectSignals = (routePattern) => {
7528
7529
  const signalConnections = [];
7529
7530
  let updatedPattern = routePattern;
7530
7531
 
7531
- // Look for signals in the new syntax: :paramName={navi_state_signal:id} or ?paramName={navi_state_signal:id} or &paramName={navi_state_signal:id}
7532
- // Using curly braces to avoid conflicts with underscores in signal IDs
7533
- const signalParamRegex = /([?:&])(\w+)=(\{navi_state_signal:[^}]+\})/g;
7532
+ // Look for signals in two formats:
7533
+ // 1. Expected format: :paramName={navi_state_signal:id} or ?paramName={navi_state_signal:id} or &paramName={navi_state_signal:id}
7534
+ // 2. Typoe format (missing = sign): &paramName{navi_state_signal:id}
7535
+ const signalParamRegex = /([?:&])(\w+)(=)?(\{navi_state_signal:[^}]+\})/g;
7534
7536
  let match;
7535
7537
 
7536
7538
  while ((match = signalParamRegex.exec(routePattern)) !== null) {
7537
- const [fullMatch, prefix, paramName, signalString] = match;
7539
+ const [fullMatch, prefix, paramName, equalSign, signalString] = match;
7538
7540
 
7539
- // Extract the signal ID from the new format: {navi_state_signal:id}
7541
+ // Emit warning if equal sign is missing
7542
+ if (!equalSign) {
7543
+ console.warn(
7544
+ `[detectSignals] Missing '=' sign in route pattern: "${prefix}${paramName}${signalString}". ` +
7545
+ `Consider using "${prefix}${paramName}=${signalString}" for better clarity.`,
7546
+ );
7547
+ }
7548
+
7549
+ // Extract the signal ID from the format: {navi_state_signal:id}
7540
7550
  const signalIdMatch = signalString.match(/\{navi_state_signal:([^}]+)\}/);
7541
7551
  if (!signalIdMatch) {
7542
7552
  console.warn(
@@ -7553,13 +7563,10 @@ const detectSignals = (routePattern) => {
7553
7563
 
7554
7564
  let replacement;
7555
7565
  if (prefix === ":") {
7556
- // Path parameter: :section=__jsenv_signal_1__ becomes :section
7566
+ // Path parameter: :section={navi_state_signal:...} becomes :section
7557
7567
  replacement = `${prefix}${paramName}`;
7558
- } else if (prefix === "?") {
7559
- // First search parameter: ?city=__jsenv_signal_1__ becomes ?city
7560
- replacement = `${prefix}${paramName}`;
7561
- } else if (prefix === "&") {
7562
- // Additional search parameter: &lon=__jsenv_signal_1__ becomes &lon
7568
+ } else if (prefix === "?" || prefix === "&") {
7569
+ // Query parameter: ?city={navi_state_signal:...} or &lon{navi_state_signal:...} becomes ?city or &lon
7563
7570
  replacement = `${prefix}${paramName}`;
7564
7571
  }
7565
7572
  updatedPattern = updatedPattern.replace(fullMatch, replacement);
@@ -7606,6 +7613,7 @@ const createRoutePattern = (pattern) => {
7606
7613
  const result = matchUrl(parsedPattern, url, {
7607
7614
  parameterDefaults,
7608
7615
  baseUrl,
7616
+ connections,
7609
7617
  });
7610
7618
 
7611
7619
  return result;
@@ -7985,7 +7993,11 @@ const checkIfLiteralCanBeOptional = (literalValue, patternRegistry) => {
7985
7993
  /**
7986
7994
  * Match a URL against a parsed pattern
7987
7995
  */
7988
- const matchUrl = (parsedPattern, url, { parameterDefaults, baseUrl }) => {
7996
+ const matchUrl = (
7997
+ parsedPattern,
7998
+ url,
7999
+ { parameterDefaults, baseUrl, connections = [] },
8000
+ ) => {
7989
8001
  // Parse the URL
7990
8002
  const urlObj = new URL(url, baseUrl);
7991
8003
  let pathname = urlObj.pathname;
@@ -8007,14 +8019,14 @@ const matchUrl = (parsedPattern, url, { parameterDefaults, baseUrl }) => {
8007
8019
  // OR when URL exactly matches baseUrl (treating baseUrl as root)
8008
8020
  if (parsedPattern.segments.length === 0) {
8009
8021
  if (pathname === "/" || pathname === "") {
8010
- return extractSearchParams(urlObj);
8022
+ return extractSearchParams(urlObj, connections);
8011
8023
  }
8012
8024
 
8013
8025
  // Special case: if URL exactly matches baseUrl, treat as root route
8014
8026
  if (baseUrl) {
8015
8027
  const baseUrlObj = new URL(baseUrl);
8016
8028
  if (originalPathname === baseUrlObj.pathname) {
8017
- return extractSearchParams(urlObj);
8029
+ return extractSearchParams(urlObj, connections);
8018
8030
  }
8019
8031
  }
8020
8032
 
@@ -8109,7 +8121,7 @@ const matchUrl = (parsedPattern, url, { parameterDefaults, baseUrl }) => {
8109
8121
  // If pattern has trailing slash or wildcard, allow extra segments (no additional check needed)
8110
8122
 
8111
8123
  // Add search parameters
8112
- const searchParams = extractSearchParams(urlObj);
8124
+ const searchParams = extractSearchParams(urlObj, connections);
8113
8125
  Object.assign(params, searchParams);
8114
8126
 
8115
8127
  // Apply remaining parameter defaults for unmatched parameters
@@ -8125,10 +8137,29 @@ const matchUrl = (parsedPattern, url, { parameterDefaults, baseUrl }) => {
8125
8137
  /**
8126
8138
  * Extract search parameters from URL
8127
8139
  */
8128
- const extractSearchParams = (urlObj) => {
8140
+ const extractSearchParams = (urlObj, connections = []) => {
8129
8141
  const params = {};
8142
+
8143
+ // Create a map for quick signal type lookup
8144
+ const signalTypes = new Map();
8145
+ for (const connection of connections) {
8146
+ if (connection.options.type) {
8147
+ signalTypes.set(connection.paramName, connection.options.type);
8148
+ }
8149
+ }
8150
+
8130
8151
  for (const [key, value] of urlObj.searchParams) {
8131
- params[key] = value;
8152
+ const signalType = signalTypes.get(key);
8153
+
8154
+ // Cast value based on signal type
8155
+ if (signalType === "number" || signalType === "float") {
8156
+ const numberValue = Number(value);
8157
+ params[key] = isNaN(numberValue) ? value : numberValue;
8158
+ } else if (signalType === "boolean") {
8159
+ params[key] = value === "true" || value === "1";
8160
+ } else {
8161
+ params[key] = value;
8162
+ }
8132
8163
  }
8133
8164
  return params;
8134
8165
  };
@@ -8402,6 +8433,8 @@ const setupPatterns = (patternDefinitions) => {
8402
8433
  originalPattern: currentPattern,
8403
8434
  });
8404
8435
  }
8436
+
8437
+ patternsRegistered = true;
8405
8438
  };
8406
8439
 
8407
8440
  /**
@@ -8411,12 +8444,25 @@ const getPatternData = (urlPatternRaw) => {
8411
8444
  return patternRegistry.get(urlPatternRaw);
8412
8445
  };
8413
8446
 
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
+
8414
8459
  /**
8415
8460
  * Clear all registered patterns
8416
8461
  */
8417
8462
  const clearPatterns = () => {
8418
8463
  patternRegistry.clear();
8419
8464
  patternRelationships.clear();
8465
+ patternsRegistered = false;
8420
8466
  };
8421
8467
 
8422
8468
  const resolveRouteUrl = (relativeUrl) => {
@@ -8633,6 +8679,41 @@ const getRoutePrivateProperties = (route) => {
8633
8679
  return routePrivatePropertiesMap.get(route);
8634
8680
  };
8635
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
+
8636
8717
  const registerRoute = (routePattern) => {
8637
8718
  const urlPatternRaw = routePattern.originalPattern;
8638
8719
  const patternData = getPatternData(urlPatternRaw);
@@ -8784,13 +8865,39 @@ const registerRoute = (routePattern) => {
8784
8865
  );
8785
8866
  return null;
8786
8867
  }
8787
- if (route.action) {
8788
- // For action: merge with resolved params (includes defaults) so action gets complete params
8789
- const currentResolvedParams = routePattern.resolveParams();
8790
- const updatedActionParams = { ...currentResolvedParams, ...newParams };
8791
- route.action.replaceParams(updatedActionParams);
8868
+
8869
+ // Walk down the hierarchy updating action params and tracking most specific route
8870
+ let currentRoute = route;
8871
+ let mostSpecificRoute;
8872
+
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();
8888
+ const updatedActionParams = {
8889
+ ...currentResolvedParams,
8890
+ ...newParams,
8891
+ };
8892
+ currentRoute.action.replaceParams(updatedActionParams);
8893
+ }
8894
+ }
8895
+
8896
+ // Find the first matching child to continue down the hierarchy
8897
+ const children = getRouteChildren(currentRoute);
8898
+ currentRoute = children.find((child) => child.matching) || null;
8792
8899
  }
8793
- return route.redirectTo(newParams);
8900
+ return mostSpecificRoute.redirectTo(newParams);
8794
8901
  };
8795
8902
  route.buildRelativeUrl = (params) => {
8796
8903
  // buildMostPreciseUrl now handles parameter resolution internally