@jsenv/navi 0.16.49 → 0.16.50

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.
@@ -2630,6 +2630,7 @@ const localStorageTypeMap = {
2630
2630
  date: "string",
2631
2631
  time: "string",
2632
2632
  email: "string",
2633
+ array: "object",
2633
2634
  };
2634
2635
 
2635
2636
  const getCallerInfo = (targetFunction = null, additionalOffset = 0) => {
@@ -7578,8 +7579,17 @@ const buildQueryString = (params) => {
7578
7579
  if (value !== undefined && value !== null) {
7579
7580
  const encodedKey = encodeURIComponent(key);
7580
7581
 
7582
+ // Handle array values - join with commas
7583
+ if (Array.isArray(value)) {
7584
+ if (value.length === 0) ; else {
7585
+ const encodedValue = value
7586
+ .map((item) => encodeURIComponent(String(item)))
7587
+ .join(",");
7588
+ searchParamPairs.push(`${encodedKey}=${encodedValue}`);
7589
+ }
7590
+ }
7581
7591
  // Handle boolean values - if true, just add the key without value
7582
- if (value === true || value === "") {
7592
+ else if (value === true || value === "") {
7583
7593
  searchParamPairs.push(encodedKey);
7584
7594
  } else {
7585
7595
  const encodedValue = encodeParamValue(value, false); // Search params encode slashes
@@ -7702,7 +7712,6 @@ const createRoutePattern = (pattern) => {
7702
7712
  const signalSet = new Set();
7703
7713
  for (const connection of connections) {
7704
7714
  connectionMap.set(connection.paramName, connection);
7705
-
7706
7715
  signalSet.add(connection.signal);
7707
7716
  }
7708
7717
 
@@ -7717,7 +7726,7 @@ const createRoutePattern = (pattern) => {
7717
7726
  const applyOn = (url) => {
7718
7727
  const result = matchUrl(parsedPattern, url, {
7719
7728
  baseUrl,
7720
- connections,
7729
+ connectionMap,
7721
7730
  patternObj: patternObject,
7722
7731
  });
7723
7732
 
@@ -9531,7 +9540,7 @@ const checkIfLiteralCanBeOptionalWithPatternObj = (
9531
9540
  const matchUrl = (
9532
9541
  parsedPattern,
9533
9542
  url,
9534
- { baseUrl, connections = [], patternObj = null },
9543
+ { baseUrl, connectionMap, patternObj = null },
9535
9544
  ) => {
9536
9545
  // Parse the URL
9537
9546
  const urlObj = new URL(url, baseUrl);
@@ -9554,14 +9563,14 @@ const matchUrl = (
9554
9563
  // OR when URL exactly matches baseUrl (treating baseUrl as root)
9555
9564
  if (parsedPattern.segments.length === 0) {
9556
9565
  if (pathname === "/" || pathname === "") {
9557
- return extractSearchParams(urlObj, connections);
9566
+ return extractSearchParams(urlObj, connectionMap);
9558
9567
  }
9559
9568
 
9560
9569
  // Special case: if URL exactly matches baseUrl, treat as root route
9561
9570
  if (baseUrl) {
9562
9571
  const baseUrlObj = new URL(baseUrl);
9563
9572
  if (originalPathname === baseUrlObj.pathname) {
9564
- return extractSearchParams(urlObj, connections);
9573
+ return extractSearchParams(urlObj, connectionMap);
9565
9574
  }
9566
9575
  }
9567
9576
 
@@ -9651,7 +9660,7 @@ const matchUrl = (
9651
9660
  // If pattern has trailing slash, wildcard, or children, allow extra segments
9652
9661
 
9653
9662
  // Add search parameters
9654
- const searchParams = extractSearchParams(urlObj, connections);
9663
+ const searchParams = extractSearchParams(urlObj, connectionMap);
9655
9664
  Object.assign(params, searchParams);
9656
9665
 
9657
9666
  // Don't add defaults here - rawParams should only contain what's in the URL
@@ -9663,36 +9672,75 @@ const matchUrl = (
9663
9672
  /**
9664
9673
  * Extract search parameters from URL
9665
9674
  */
9666
- const extractSearchParams = (urlObj, connections = []) => {
9675
+ const extractSearchParams = (urlObj, connectionMap) => {
9667
9676
  const params = {};
9668
9677
 
9669
- // Create a map for quick signal type lookup
9670
- const signalTypes = new Map();
9671
- for (const connection of connections) {
9672
- if (connection.type) {
9673
- signalTypes.set(connection.paramName, connection.type);
9678
+ // Parse the raw query string manually instead of using urlObj.searchParams
9679
+ // This is necessary for array parameters to handle encoded commas correctly.
9680
+ // urlObj.searchParams automatically decodes %2C to , which breaks our comma-based array splitting.
9681
+ //
9682
+ // Design choice: We use comma-separated values (colors=red,blue,green) instead of
9683
+ // the standard repeated parameters (colors=red&colors=blue&colors=green) because:
9684
+ // 1. More human-readable URLs
9685
+ // 2. Shorter URL length
9686
+ // 3. Easier to copy/paste and manually edit
9687
+ if (!urlObj.search) {
9688
+ return params;
9689
+ }
9690
+
9691
+ const rawQuery = urlObj.search.slice(1); // Remove leading ?
9692
+ const pairs = rawQuery.split("&");
9693
+
9694
+ for (const pair of pairs) {
9695
+ const eqIndex = pair.indexOf("=");
9696
+ let key;
9697
+ let rawValue;
9698
+
9699
+ if (eqIndex > -1) {
9700
+ key = decodeURIComponent(pair.slice(0, eqIndex));
9701
+ rawValue = pair.slice(eqIndex + 1); // Keep raw for array processing
9702
+ } else {
9703
+ key = decodeURIComponent(pair);
9704
+ rawValue = "";
9674
9705
  }
9675
- }
9676
9706
 
9677
- for (const [key, value] of urlObj.searchParams) {
9678
- const signalType = signalTypes.get(key);
9707
+ const connection = connectionMap.get(key);
9708
+ const signalType = connection ? connection.type : null;
9679
9709
 
9680
9710
  // Cast value based on signal type
9681
- if (signalType === "number" || signalType === "float") {
9682
- const numberValue = Number(value);
9683
- params[key] = isNaN(numberValue) ? value : numberValue;
9711
+ if (signalType === "array") {
9712
+ // Handle array query parameters with proper comma encoding:
9713
+ // ?colors=red,blue,green ["red", "blue", "green"]
9714
+ // ?colors=red,blue%2Cgreen → ["red", "blue,green"] (comma in value)
9715
+ // ?colors= → []
9716
+ // ?colors → []
9717
+ if (rawValue === "") {
9718
+ params[key] = [];
9719
+ } else {
9720
+ params[key] = rawValue
9721
+ .split(",")
9722
+ .map((item) => decodeURIComponent(item))
9723
+ .filter((item) => item.trim() !== "");
9724
+ }
9725
+ } else if (signalType === "number" || signalType === "float") {
9726
+ const decodedValue = decodeURIComponent(rawValue);
9727
+ const numberValue = Number(decodedValue);
9728
+ params[key] = isNaN(numberValue) ? decodedValue : numberValue;
9684
9729
  } else if (signalType === "boolean") {
9730
+ const decodedValue = decodeURIComponent(rawValue);
9685
9731
  // Handle boolean query parameters:
9686
9732
  // ?walk=true → true
9687
9733
  // ?walk=1 → true
9688
9734
  // ?walk → true (parameter present without value)
9689
9735
  // ?walk=false → false
9690
9736
  // ?walk=0 → false
9691
- params[key] = value === "true" || value === "1" || value === "";
9737
+ params[key] =
9738
+ decodedValue === "true" || decodedValue === "1" || decodedValue === "";
9692
9739
  } else {
9693
- params[key] = value;
9740
+ params[key] = decodeURIComponent(rawValue);
9694
9741
  }
9695
9742
  }
9743
+
9696
9744
  return params;
9697
9745
  };
9698
9746