@jsenv/navi 0.16.21 → 0.16.23

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.
@@ -7770,6 +7770,53 @@ const createRoutePattern = (pattern) => {
7770
7770
  }
7771
7771
  }
7772
7772
 
7773
+ // Include active non-default parameters from child routes for URL optimization
7774
+ // Only include from child routes that would actually match the current parameters
7775
+ const childPatternObjs = patternObject.children || [];
7776
+ for (const childPatternObj of childPatternObjs) {
7777
+ // Check if this child route would match the current resolved parameters
7778
+ // by simulating URL building and seeing if the child segments align
7779
+ let childWouldMatch = true;
7780
+
7781
+ // Compare child segments with what would be built from current params
7782
+ for (let i = 0; i < childPatternObj.pattern.segments.length; i++) {
7783
+ const childSegment = childPatternObj.pattern.segments[i];
7784
+ const parentSegment = parsedPattern.segments[i];
7785
+
7786
+ if (childSegment.type === "literal") {
7787
+ if (parentSegment && parentSegment.type === "param") {
7788
+ // Child has literal where parent has parameter - check if values match
7789
+ const paramValue = resolvedParams[parentSegment.name];
7790
+ if (paramValue !== childSegment.value) {
7791
+ childWouldMatch = false;
7792
+ break;
7793
+ }
7794
+ }
7795
+ // If parent also has literal at this position, they should already match from route hierarchy
7796
+ }
7797
+ // Parameter segments are always compatible
7798
+ }
7799
+
7800
+ if (childWouldMatch) {
7801
+ for (const childConnection of childPatternObj.connections) {
7802
+ const {
7803
+ paramName: childParam,
7804
+ signal: childSignal,
7805
+ options: childOptions,
7806
+ } = childConnection;
7807
+
7808
+ // Only include if not already resolved and is non-default
7809
+ if (
7810
+ !(childParam in resolvedParams) &&
7811
+ childSignal?.value !== undefined &&
7812
+ childSignal.value !== childOptions.defaultValue
7813
+ ) {
7814
+ resolvedParams[childParam] = childSignal.value;
7815
+ }
7816
+ }
7817
+ }
7818
+ }
7819
+
7773
7820
  return resolvedParams;
7774
7821
  };
7775
7822
 
@@ -7785,8 +7832,8 @@ const createRoutePattern = (pattern) => {
7785
7832
  const filtered = { ...params };
7786
7833
 
7787
7834
  for (const connection of connections) {
7788
- const { paramName, signal, options } = connection;
7789
- const defaultValue = options.defaultValue;
7835
+ const { paramName, signal } = connection;
7836
+ const defaultValue = parameterDefaults.get(paramName);
7790
7837
 
7791
7838
  if (paramName in filtered && filtered[paramName] === defaultValue) {
7792
7839
  delete filtered[paramName];
@@ -8085,7 +8132,12 @@ const createRoutePattern = (pattern) => {
8085
8132
  /**
8086
8133
  * Helper: Determine if child route should be used based on active parameters
8087
8134
  */
8088
- const shouldUseChildRoute = (childPatternObj, params, compatibility) => {
8135
+ const shouldUseChildRoute = (
8136
+ childPatternObj,
8137
+ params,
8138
+ compatibility,
8139
+ resolvedParams,
8140
+ ) => {
8089
8141
  // CRITICAL: Check if user explicitly passed undefined for parameters that would
8090
8142
  // normally be used to select this child route via sibling route relationships
8091
8143
  for (const [paramName, paramValue] of Object.entries(params)) {
@@ -8207,10 +8259,114 @@ const createRoutePattern = (pattern) => {
8207
8259
  // Use child route if:
8208
8260
  // 1. Child has active non-default parameters, OR
8209
8261
  // 2. User provided non-default params AND child can be built completely
8210
- const shouldUse =
8262
+ // EXCEPT: Don't use child if parent can produce cleaner URL by omitting defaults
8263
+ let shouldUse =
8211
8264
  hasActiveParams ||
8212
8265
  (hasNonDefaultProvidedParams && canBuildChildCompletely);
8213
8266
 
8267
+ // Optimization: Check if child would include literal segments that represent default values
8268
+ if (shouldUse) {
8269
+ // Check if child pattern has literal segments that correspond to default parameter values
8270
+ const childLiterals = childPatternObj.pattern.segments
8271
+ .filter((seg) => seg.type === "literal")
8272
+ .map((seg) => seg.value);
8273
+
8274
+ const parentLiterals = parsedPattern.segments
8275
+ .filter((seg) => seg.type === "literal")
8276
+ .map((seg) => seg.value);
8277
+
8278
+ // If child has more literal segments than parent, check if the extra ones are defaults
8279
+ if (childLiterals.length > parentLiterals.length) {
8280
+ const extraLiterals = childLiterals.slice(parentLiterals.length);
8281
+
8282
+ // Check if any extra literal matches a default parameter value
8283
+ // BUT only skip if user didn't explicitly provide that parameter AND
8284
+ // both conditions are true:
8285
+ // 1. The parameters that would cause us to use this child route are defaults
8286
+ // 2. The child route doesn't have non-default parameters that would be lost
8287
+ let childSpecificParamsAreDefaults = true;
8288
+
8289
+ // Check if parameters that determine child selection are non-default
8290
+ // OR if any descendant parameters indicate explicit navigation
8291
+ for (const connection of connections) {
8292
+ const { paramName } = connection;
8293
+ const defaultValue = parameterDefaults.get(paramName);
8294
+ const resolvedValue = resolvedParams[paramName];
8295
+ const userProvidedParam = paramName in params;
8296
+
8297
+ if (extraLiterals.includes(defaultValue)) {
8298
+ // This literal corresponds to a parameter in the parent
8299
+ if (
8300
+ userProvidedParam ||
8301
+ (resolvedValue !== undefined && resolvedValue !== defaultValue)
8302
+ ) {
8303
+ // Parameter was explicitly provided or has non-default value - child is needed
8304
+ childSpecificParamsAreDefaults = false;
8305
+ break;
8306
+ }
8307
+ }
8308
+ }
8309
+
8310
+ // Additional check: if child route has path parameters that are non-default,
8311
+ // this indicates explicit navigation even if structural parameters happen to be default
8312
+ // (Query parameters don't count as they don't indicate structural navigation)
8313
+ if (childSpecificParamsAreDefaults) {
8314
+ for (const childConnection of childPatternObj.connections) {
8315
+ const childParamName = childConnection.paramName;
8316
+ const childDefaultValue = childConnection.options?.defaultValue;
8317
+ const childResolvedValue = resolvedParams[childParamName];
8318
+
8319
+ // Only consider path parameters, not query parameters
8320
+ const isPathParam = childPatternObj.pattern.segments.some(
8321
+ (seg) => seg.type === "param" && seg.name === childParamName,
8322
+ );
8323
+
8324
+ if (
8325
+ isPathParam &&
8326
+ childResolvedValue !== undefined &&
8327
+ childResolvedValue !== childDefaultValue
8328
+ ) {
8329
+ // Child has non-default path parameters, indicating explicit navigation
8330
+ childSpecificParamsAreDefaults = false;
8331
+ if (DEBUG$2) {
8332
+ console.debug(
8333
+ `[${pattern}] Child has non-default path parameter '${childParamName}=${childResolvedValue}' (default: ${childDefaultValue}) - indicates explicit navigation`,
8334
+ );
8335
+ }
8336
+ break;
8337
+ }
8338
+ }
8339
+ }
8340
+
8341
+ // When structural parameters (those that determine child selection) are defaults,
8342
+ // prefer parent route regardless of whether child has other non-default parameters
8343
+ if (childSpecificParamsAreDefaults) {
8344
+ for (const connection of connections) {
8345
+ const { paramName } = connection;
8346
+ const defaultValue = parameterDefaults.get(paramName);
8347
+ const userProvidedParam = paramName in params;
8348
+
8349
+ if (extraLiterals.includes(defaultValue) && !userProvidedParam) {
8350
+ // This child includes a literal that represents a default value
8351
+ // AND user didn't explicitly provide this parameter
8352
+ // When structural parameters are defaults, prefer parent for cleaner URL
8353
+ shouldUse = false;
8354
+ if (DEBUG$2) {
8355
+ console.debug(
8356
+ `[${pattern}] Preferring parent over child - child includes default literal '${defaultValue}' for param '${paramName}' (structural parameter is default)`,
8357
+ );
8358
+ }
8359
+ break;
8360
+ }
8361
+ }
8362
+ } else if (DEBUG$2) {
8363
+ console.debug(
8364
+ `[${pattern}] Using child route - parameters that determine child selection are non-default`,
8365
+ );
8366
+ }
8367
+ }
8368
+ }
8369
+
8214
8370
  if (DEBUG$2 && shouldUse) {
8215
8371
  console.debug(
8216
8372
  `[${pattern}] Will use child route ${childPatternObj.originalPattern}`,
@@ -8784,6 +8940,26 @@ const createRoutePattern = (pattern) => {
8784
8940
  targetAncestor.connections.length === 0;
8785
8941
 
8786
8942
  if (sourceHasOnlyLiterals && targetHasOnlyLiterals) {
8943
+ // Check if user provided any parameters that would be lost in optimization
8944
+ const hasUserProvidedParams = Object.keys(resolvedParams).some(
8945
+ (paramName) => {
8946
+ // Check if this parameter was explicitly provided by the user
8947
+ // (not just inherited from signal values with default values)
8948
+ const userProvided = resolvedParams[paramName] !== undefined;
8949
+ return userProvided;
8950
+ },
8951
+ );
8952
+
8953
+ if (hasUserProvidedParams) {
8954
+ if (DEBUG$2) {
8955
+ console.debug(
8956
+ `[${pattern}] tryDirectOptimization: Cannot optimize literal-only routes - would lose user-provided parameters`,
8957
+ Object.keys(resolvedParams),
8958
+ );
8959
+ }
8960
+ return null;
8961
+ }
8962
+
8787
8963
  if (DEBUG$2) {
8788
8964
  console.debug(
8789
8965
  `[${pattern}] tryDirectOptimization: Both are pure literal-only routes, allowing optimization`,
@@ -8996,6 +9172,7 @@ const createRoutePattern = (pattern) => {
8996
9172
  descendantPatternObj,
8997
9173
  params,
8998
9174
  compatibility,
9175
+ parentResolvedParams,
8999
9176
  );
9000
9177
  if (!shouldUse) {
9001
9178
  return null;
@@ -9844,12 +10021,23 @@ const setupPatterns = (patternDefinitions) => {
9844
10021
  otherPatternObj.cleanPattern,
9845
10022
  )
9846
10023
  ) {
9847
- // Store the most specific parent (only one parent per pattern in tree structure)
10024
+ // Store the most specific parent (closest parent in hierarchy)
10025
+ const getPathSegmentCount = (pattern) => {
10026
+ // Only count path segments, not query parameters
10027
+ const pathPart = pattern.split("?")[0];
10028
+ return pathPart.split("/").filter(Boolean).length;
10029
+ };
10030
+
10031
+ const currentSegmentCount = currentPatternObj.parent
10032
+ ? getPathSegmentCount(currentPatternObj.parent.originalPattern)
10033
+ : 0;
10034
+ const otherSegmentCount = getPathSegmentCount(
10035
+ otherPatternObj.originalPattern,
10036
+ );
10037
+
9848
10038
  if (
9849
10039
  !currentPatternObj.parent ||
9850
- otherPatternObj.originalPattern.split("/").filter(Boolean).length >
9851
- currentPatternObj.parent.originalPattern.split("/").filter(Boolean)
9852
- .length
10040
+ otherSegmentCount > currentSegmentCount
9853
10041
  ) {
9854
10042
  currentPatternObj.parent = otherPatternObj;
9855
10043
  }