@jsenv/navi 0.14.0 → 0.14.1

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.
@@ -318,85 +318,95 @@ const getSignalType = (value) => {
318
318
  */
319
319
  const SYMBOL_IDENTITY = Symbol.for("navi_object_identity");
320
320
 
321
- const compareTwoJsValues = (a, b, seenSet = new Set()) => {
322
- if (a === b) {
323
- return true;
324
- }
325
- const aIsIsTruthy = Boolean(a);
326
- const bIsTruthy = Boolean(b);
327
- if (aIsIsTruthy && !bIsTruthy) {
328
- return false;
329
- }
330
- if (!aIsIsTruthy && !bIsTruthy) {
331
- // null, undefined, 0, false, NaN
332
- if (isNaN(a) && isNaN(b)) {
321
+ const compareTwoJsValues = (rootA, rootB, { keyComparator } = {}) => {
322
+ const seenSet = new Set();
323
+ const compare = (a, b) => {
324
+ if (a === b) {
333
325
  return true;
334
326
  }
335
- return a === b;
336
- }
337
- const aType = typeof a;
338
- const bType = typeof b;
339
- if (aType !== bType) {
340
- return false;
341
- }
342
- const aIsPrimitive =
343
- a === null || (aType !== "object" && aType !== "function");
344
- const bIsPrimitive =
345
- b === null || (bType !== "object" && bType !== "function");
346
- if (aIsPrimitive !== bIsPrimitive) {
347
- return false;
348
- }
349
- if (aIsPrimitive && bIsPrimitive) {
350
- return a === b;
351
- }
352
- if (seenSet.has(a)) {
353
- return false;
354
- }
355
- if (seenSet.has(b)) {
356
- return false;
357
- }
358
- seenSet.add(a);
359
- seenSet.add(b);
360
- const aIsArray = Array.isArray(a);
361
- const bIsArray = Array.isArray(b);
362
- if (aIsArray !== bIsArray) {
363
- return false;
364
- }
365
- if (aIsArray) {
366
- // compare arrays
367
- if (a.length !== b.length) {
327
+ const aIsIsTruthy = Boolean(a);
328
+ const bIsTruthy = Boolean(b);
329
+ if (aIsIsTruthy && !bIsTruthy) {
330
+ return false;
331
+ }
332
+ if (!aIsIsTruthy && !bIsTruthy) {
333
+ // null, undefined, 0, false, NaN
334
+ if (isNaN(a) && isNaN(b)) {
335
+ return true;
336
+ }
337
+ return a === b;
338
+ }
339
+ const aType = typeof a;
340
+ const bType = typeof b;
341
+ if (aType !== bType) {
342
+ return false;
343
+ }
344
+ const aIsPrimitive =
345
+ a === null || (aType !== "object" && aType !== "function");
346
+ const bIsPrimitive =
347
+ b === null || (bType !== "object" && bType !== "function");
348
+ if (aIsPrimitive !== bIsPrimitive) {
349
+ return false;
350
+ }
351
+ if (aIsPrimitive && bIsPrimitive) {
352
+ return a === b;
353
+ }
354
+ if (seenSet.has(a)) {
368
355
  return false;
369
356
  }
370
- let i = 0;
371
- while (i < a.length) {
372
- const aValue = a[i];
373
- const bValue = b[i];
374
- if (!compareTwoJsValues(aValue, bValue, seenSet)) {
357
+ if (seenSet.has(b)) {
358
+ return false;
359
+ }
360
+ seenSet.add(a);
361
+ seenSet.add(b);
362
+ const aIsArray = Array.isArray(a);
363
+ const bIsArray = Array.isArray(b);
364
+ if (aIsArray !== bIsArray) {
365
+ return false;
366
+ }
367
+ if (aIsArray) {
368
+ // compare arrays
369
+ if (a.length !== b.length) {
375
370
  return false;
376
371
  }
377
- i++;
372
+ let i = 0;
373
+ while (i < a.length) {
374
+ const aValue = a[i];
375
+ const bValue = b[i];
376
+ const comparator = keyComparator || compare;
377
+ if (!comparator(aValue, bValue, i, compare)) {
378
+ return false;
379
+ }
380
+ i++;
381
+ }
382
+ return true;
378
383
  }
379
- return true;
380
- }
381
- // compare objects
382
- const aIdentity = a[SYMBOL_IDENTITY];
383
- const bIdentity = b[SYMBOL_IDENTITY];
384
- if (aIdentity === bIdentity && SYMBOL_IDENTITY in a && SYMBOL_IDENTITY in b) {
385
- return true;
386
- }
387
- const aKeys = Object.keys(a);
388
- const bKeys = Object.keys(b);
389
- if (aKeys.length !== bKeys.length) {
390
- return false;
391
- }
392
- for (const key of aKeys) {
393
- const aValue = a[key];
394
- const bValue = b[key];
395
- if (!compareTwoJsValues(aValue, bValue, seenSet)) {
384
+ // compare objects
385
+ const aIdentity = a[SYMBOL_IDENTITY];
386
+ const bIdentity = b[SYMBOL_IDENTITY];
387
+ if (
388
+ aIdentity === bIdentity &&
389
+ SYMBOL_IDENTITY in a &&
390
+ SYMBOL_IDENTITY in b
391
+ ) {
392
+ return true;
393
+ }
394
+ const aKeys = Object.keys(a);
395
+ const bKeys = Object.keys(b);
396
+ if (aKeys.length !== bKeys.length) {
396
397
  return false;
397
398
  }
398
- }
399
- return true;
399
+ for (const key of aKeys) {
400
+ const aValue = a[key];
401
+ const bValue = b[key];
402
+ const comparator = keyComparator || compare;
403
+ if (!comparator(aValue, bValue, key, compare)) {
404
+ return false;
405
+ }
406
+ }
407
+ return true;
408
+ };
409
+ return compare(rootA, rootB);
400
410
  };
401
411
 
402
412
  /**
@@ -1247,8 +1257,8 @@ ${lines.join("\n")}`);
1247
1257
  };
1248
1258
  };
1249
1259
 
1250
- const NO_PARAMS$1 = {};
1251
- const initialParamsDefault = NO_PARAMS$1;
1260
+ const NO_PARAMS = {};
1261
+ const initialParamsDefault = NO_PARAMS;
1252
1262
 
1253
1263
  const actionWeakMap = new WeakMap();
1254
1264
  const createAction = (callback, rootOptions = {}) => {
@@ -1855,7 +1865,7 @@ const createActionProxyFromSignal = (
1855
1865
  const _updateTarget = (params) => {
1856
1866
  const previousActionTarget = actionTargetPreviousWeakRef?.deref();
1857
1867
 
1858
- if (params === NO_PARAMS$1) {
1868
+ if (params === NO_PARAMS) {
1859
1869
  actionTarget = null;
1860
1870
  currentAction = action;
1861
1871
  currentActionPrivateProperties = getActionPrivateProperties(action);
@@ -2060,7 +2070,7 @@ const createActionProxyFromSignal = (
2060
2070
  };
2061
2071
 
2062
2072
  const generateActionName = (name, params) => {
2063
- if (params === NO_PARAMS$1) {
2073
+ if (params === NO_PARAMS) {
2064
2074
  return `${name}({})`;
2065
2075
  }
2066
2076
  // Use stringifyForDisplay with asFunctionArgs option for the entire args array
@@ -4734,7 +4744,7 @@ const DIMENSION_PROPS = {
4734
4744
  return null;
4735
4745
  }
4736
4746
  if (parentBoxFlow === "column" || parentBoxFlow === "inline-column") {
4737
- return { flexGrow: 1 }; // Grow horizontally in row
4747
+ return { flexGrow: 1, flexBasis: "0%" }; // Grow horizontally in row
4738
4748
  }
4739
4749
  if (parentBoxFlow === "row") {
4740
4750
  return { minWidth: "100%", width: "auto" }; // Take full width in column
@@ -4749,7 +4759,7 @@ const DIMENSION_PROPS = {
4749
4759
  return { minHeight: "100%", height: "auto" }; // Make column full height
4750
4760
  }
4751
4761
  if (parentBoxFlow === "row" || parentBoxFlow === "inline-row") {
4752
- return { flexGrow: 1 }; // Make row full height
4762
+ return { flexGrow: 1, flexBasis: "0%" }; // Make row full height
4753
4763
  }
4754
4764
  return { minHeight: "100%", height: "auto" }; // Take full height outside flex
4755
4765
  },
@@ -4933,6 +4943,9 @@ const VISUAL_PROPS = {
4933
4943
  filter: PASS_THROUGH,
4934
4944
  cursor: PASS_THROUGH,
4935
4945
  transition: PASS_THROUGH,
4946
+ overflow: PASS_THROUGH,
4947
+ overflowX: PASS_THROUGH,
4948
+ overflowY: PASS_THROUGH,
4936
4949
  };
4937
4950
  const CONTENT_PROPS = {
4938
4951
  align: applyOnTwoProps("alignX", "alignY"),
@@ -7173,26 +7186,337 @@ const useUITransitionContentId = value => {
7173
7186
  }, []);
7174
7187
  };
7175
7188
 
7189
+ const rawUrlPartSymbol = Symbol("raw_url_part");
7190
+ const rawUrlPart = (value) => {
7191
+ return {
7192
+ [rawUrlPartSymbol]: true,
7193
+ value,
7194
+ };
7195
+ };
7196
+
7197
+ const removeOptionalParts = (url) => {
7198
+ // Only remove optional parts that still have ? (weren't replaced with actual values)
7199
+ // Find the first unused optional part and remove everything from there onwards
7200
+ let result = url;
7201
+
7202
+ // Find the first occurrence of an unused optional part (still has ?)
7203
+ const optionalPartMatch = result.match(/(\/?\*|\/:[^/?]*|\{[^}]*\})\?/);
7204
+
7205
+ if (optionalPartMatch) {
7206
+ // Remove everything from the start of the first unused optional part
7207
+ const optionalStartIndex = optionalPartMatch.index;
7208
+ result = result.substring(0, optionalStartIndex);
7209
+
7210
+ // Clean up trailing slashes
7211
+ result = result.replace(/\/$/, "");
7212
+ }
7213
+
7214
+ return result;
7215
+ };
7216
+
7217
+ const buildRouteRelativeUrl = (
7218
+ urlPatternInput,
7219
+ params,
7220
+ { extraParamEffect = "inject_as_search_param" } = {},
7221
+ ) => {
7222
+ let relativeUrl = urlPatternInput;
7223
+ let hasRawUrlPartWithInvalidChars = false;
7224
+ let stringQueryParams = "";
7225
+
7226
+ // Handle string params (query string) - store for later appending
7227
+ if (typeof params === "string") {
7228
+ stringQueryParams = params;
7229
+ // Remove leading ? if present for processing
7230
+ if (stringQueryParams.startsWith("?")) {
7231
+ stringQueryParams = stringQueryParams.slice(1);
7232
+ }
7233
+ // Set params to empty object so the rest of the function processes the URL pattern
7234
+ params = null;
7235
+ }
7236
+
7237
+ // Encode parameter values for URL usage, with special handling for raw URL parts.
7238
+ // When a parameter is wrapped with rawUrlPart(), it bypasses encoding and is
7239
+ // inserted as-is into the URL. This allows including pre-encoded values or
7240
+ // special characters that should not be percent-encoded.
7241
+ const encodeParamValue = (value) => {
7242
+ if (value && value[rawUrlPartSymbol]) {
7243
+ const rawValue = value.value;
7244
+ // Check if raw value contains invalid URL characters
7245
+ if (/[\s<>{}|\\^`]/.test(rawValue)) {
7246
+ hasRawUrlPartWithInvalidChars = true;
7247
+ }
7248
+ return rawValue;
7249
+ }
7250
+ return encodeURIComponent(value);
7251
+ };
7252
+ const extraParamMap = new Map();
7253
+ if (params) {
7254
+ const keys = Object.keys(params);
7255
+ // Replace named parameters (:param and {param}) and remove optional markers
7256
+ for (const key of keys) {
7257
+ const value = params[key];
7258
+ const encodedValue = encodeParamValue(value);
7259
+ const beforeReplace = relativeUrl;
7260
+
7261
+ // Replace parameter and remove optional marker if present
7262
+ relativeUrl = relativeUrl.replace(`:${key}?`, encodedValue);
7263
+ relativeUrl = relativeUrl.replace(`:${key}`, encodedValue);
7264
+ relativeUrl = relativeUrl.replace(`{${key}}?`, encodedValue);
7265
+ relativeUrl = relativeUrl.replace(`{${key}}`, encodedValue);
7266
+
7267
+ // If the URL did not change we'll maybe delete that param
7268
+ if (relativeUrl === beforeReplace) {
7269
+ extraParamMap.set(key, value);
7270
+ }
7271
+ }
7272
+ // Handle complex optional groups like {/time/:duration}?
7273
+ // Replace parameters inside optional groups and remove the optional marker
7274
+ relativeUrl = relativeUrl.replace(/\{([^}]*)\}\?/g, (match, group) => {
7275
+ let processedGroup = group;
7276
+ let hasReplacements = false;
7277
+
7278
+ // Check if any parameters in the group were provided
7279
+ for (const key of keys) {
7280
+ if (params[key] !== undefined) {
7281
+ const encodedValue = encodeParamValue(params[key]);
7282
+ const paramPattern = new RegExp(`:${key}\\b`);
7283
+ if (paramPattern.test(processedGroup)) {
7284
+ processedGroup = processedGroup.replace(paramPattern, encodedValue);
7285
+ hasReplacements = true;
7286
+ extraParamMap.delete(key);
7287
+ }
7288
+ }
7289
+ }
7290
+
7291
+ // Also check for literal parts that match parameter names (like /time where time is a param)
7292
+ for (const key of keys) {
7293
+ if (params[key] !== undefined) {
7294
+ const encodedValue = encodeParamValue(params[key]);
7295
+ // Check for literal parts like /time that match parameter names
7296
+ const literalPattern = new RegExp(`\\/${key}\\b`);
7297
+ if (literalPattern.test(processedGroup)) {
7298
+ processedGroup = processedGroup.replace(
7299
+ literalPattern,
7300
+ `/${encodedValue}`,
7301
+ );
7302
+ hasReplacements = true;
7303
+ extraParamMap.delete(key);
7304
+ }
7305
+ }
7306
+ }
7307
+
7308
+ // If we made replacements, include the group (without the optional marker)
7309
+ // If no replacements, return empty string (remove the optional group)
7310
+ return hasReplacements ? processedGroup : "";
7311
+ });
7312
+ }
7313
+
7314
+ // Clean up any double slashes or trailing slashes that might result
7315
+ relativeUrl = relativeUrl.replace(/\/+/g, "/").replace(/\/$/, "");
7316
+
7317
+ // Handle remaining wildcards
7318
+ if (params) {
7319
+ let wildcardIndex = 0;
7320
+ relativeUrl = relativeUrl.replace(/\*/g, () => {
7321
+ const paramKey = wildcardIndex.toString();
7322
+ const paramValue = params[paramKey];
7323
+ if (paramValue) {
7324
+ extraParamMap.delete(paramKey);
7325
+ }
7326
+ const replacement = paramValue ? encodeParamValue(paramValue) : "*";
7327
+ wildcardIndex++;
7328
+ return replacement;
7329
+ });
7330
+ }
7331
+
7332
+ // Handle optional parts after parameter replacement
7333
+ // This includes patterns like /*?, {/time/*}?, :param?, etc.
7334
+ relativeUrl = removeOptionalParts(relativeUrl);
7335
+ // we did not replace anything, or not enough to remove the last "*"
7336
+ if (relativeUrl.endsWith("*")) {
7337
+ relativeUrl = relativeUrl.slice(0, -1);
7338
+ }
7339
+
7340
+ // Normalize trailing slash: always favor URLs without trailing slash
7341
+ // except for root path which should remain "/"
7342
+ if (relativeUrl.endsWith("/") && relativeUrl.length > 1) {
7343
+ relativeUrl = relativeUrl.slice(0, -1);
7344
+ }
7345
+
7346
+ // Add remaining parameters as search params
7347
+ if (extraParamMap.size > 0) {
7348
+ if (extraParamEffect === "inject_as_search_param") {
7349
+ const searchParamPairs = [];
7350
+ for (const [key, value] of extraParamMap) {
7351
+ if (value !== undefined && value !== null) {
7352
+ const encodedKey = encodeURIComponent(key);
7353
+ // Handle boolean values - if true, just add the key without value
7354
+ if (value === true) {
7355
+ searchParamPairs.push(encodedKey);
7356
+ } else {
7357
+ const encodedValue = encodeParamValue(value);
7358
+ searchParamPairs.push(`${encodedKey}=${encodedValue}`);
7359
+ }
7360
+ }
7361
+ }
7362
+ if (searchParamPairs.length > 0) {
7363
+ const searchString = searchParamPairs.join("&");
7364
+ relativeUrl += (relativeUrl.includes("?") ? "&" : "?") + searchString;
7365
+ }
7366
+ } else if (extraParamEffect === "warn") {
7367
+ console.warn(
7368
+ `Unknown parameters given to "${urlPatternInput}":`,
7369
+ Array.from(extraParamMap.keys()),
7370
+ );
7371
+ }
7372
+ }
7373
+
7374
+ // Append string query params if any
7375
+ if (stringQueryParams) {
7376
+ relativeUrl += (relativeUrl.includes("?") ? "&" : "?") + stringQueryParams;
7377
+ }
7378
+
7379
+ return {
7380
+ relativeUrl,
7381
+ hasRawUrlPartWithInvalidChars,
7382
+ };
7383
+ };
7384
+
7385
+ const createRoutePattern = (urlPatternInput, baseUrl) => {
7386
+ // Remove leading slash from urlPattern to make it relative to baseUrl
7387
+ const normalizedUrlPattern = urlPatternInput.startsWith("/")
7388
+ ? urlPatternInput.slice(1)
7389
+ : urlPatternInput;
7390
+ const urlPattern = new URLPattern(normalizedUrlPattern, baseUrl, {
7391
+ ignoreCase: true,
7392
+ });
7393
+
7394
+ // Analyze pattern once to detect optional params (named and wildcard indices)
7395
+ // Note: Wildcard indices are stored as strings ("0", "1", ...) to match keys from extractParams
7396
+ const optionalParamKeySet = new Set();
7397
+ normalizedUrlPattern.replace(/:([A-Za-z0-9_]+)\?/g, (_m, name) => {
7398
+ optionalParamKeySet.add(name);
7399
+ return "";
7400
+ });
7401
+ let wildcardIndex = 0;
7402
+ normalizedUrlPattern.replace(/\*(\?)?/g, (_m, opt) => {
7403
+ if (opt === "?") {
7404
+ optionalParamKeySet.add(String(wildcardIndex));
7405
+ }
7406
+ wildcardIndex++;
7407
+ return "";
7408
+ });
7409
+
7410
+ const applyOn = (url) => {
7411
+
7412
+ // Check if the URL matches the route pattern
7413
+ const match = urlPattern.exec(url);
7414
+ if (match) {
7415
+ return extractParams(match, url);
7416
+ }
7417
+
7418
+ // If no match, try with normalized URLs (trailing slash handling)
7419
+ const urlObj = new URL(url, baseUrl);
7420
+ const pathname = urlObj.pathname;
7421
+
7422
+ // Try removing trailing slash from pathname
7423
+ if (pathname.endsWith("/") && pathname.length > 1) {
7424
+ const pathnameWithoutSlash = pathname.slice(0, -1);
7425
+ urlObj.pathname = pathnameWithoutSlash;
7426
+ const normalizedUrl = urlObj.href;
7427
+ const matchWithoutTrailingSlash = urlPattern.exec(normalizedUrl);
7428
+ if (matchWithoutTrailingSlash) {
7429
+ return extractParams(matchWithoutTrailingSlash, url);
7430
+ }
7431
+ }
7432
+ // Try adding trailing slash to pathname
7433
+ else if (!pathname.endsWith("/")) {
7434
+ const pathnameWithSlash = `${pathname}/`;
7435
+ urlObj.pathname = pathnameWithSlash;
7436
+ const normalizedUrl = urlObj.href;
7437
+ const matchWithTrailingSlash = urlPattern.exec(normalizedUrl);
7438
+ if (matchWithTrailingSlash) {
7439
+ return extractParams(matchWithTrailingSlash, url);
7440
+ }
7441
+ }
7442
+ return null;
7443
+ };
7444
+
7445
+ const extractParams = (match, originalUrl) => {
7446
+ const params = {};
7447
+
7448
+ // Extract search parameters from the original URL
7449
+ const urlObj = new URL(originalUrl, baseUrl);
7450
+ for (const [key, value] of urlObj.searchParams) {
7451
+ params[key] = value;
7452
+ }
7453
+
7454
+ // Collect all parameters from URLPattern groups, handling both named and numbered groups
7455
+ let wildcardOffset = 0;
7456
+ for (const property of URL_PATTERN_PROPERTIES_WITH_GROUP_SET) {
7457
+ const urlPartMatch = match[property];
7458
+ if (urlPartMatch && urlPartMatch.groups) {
7459
+ let localWildcardCount = 0;
7460
+ for (const key of Object.keys(urlPartMatch.groups)) {
7461
+ const value = urlPartMatch.groups[key];
7462
+ const keyAsNumber = parseInt(key, 10);
7463
+ if (!isNaN(keyAsNumber)) {
7464
+ // Skip group "0" from search params as it captures the entire search string
7465
+ if (property === "search" && key === "0") {
7466
+ continue;
7467
+ }
7468
+ if (value) {
7469
+ // Only include non-empty values and non-ignored wildcard indices
7470
+ const wildcardKey = String(wildcardOffset + keyAsNumber);
7471
+ if (!optionalParamKeySet.has(wildcardKey)) {
7472
+ params[wildcardKey] = decodeURIComponent(value);
7473
+ }
7474
+ localWildcardCount++;
7475
+ }
7476
+ } else if (!optionalParamKeySet.has(key)) {
7477
+ // Named group (:param or {param}) - only include if not ignored
7478
+ params[key] = decodeURIComponent(value);
7479
+ }
7480
+ }
7481
+ // Update wildcard offset for next URL part
7482
+ wildcardOffset += localWildcardCount;
7483
+ }
7484
+ }
7485
+ return params;
7486
+ };
7487
+
7488
+ return {
7489
+ urlPattern,
7490
+ applyOn,
7491
+ };
7492
+ };
7493
+
7494
+ const URL_PATTERN_PROPERTIES_WITH_GROUP_SET = new Set([
7495
+ "protocol",
7496
+ "username",
7497
+ "password",
7498
+ "hostname",
7499
+ "pathname",
7500
+ "search",
7501
+ "hash",
7502
+ ]);
7503
+
7176
7504
  /**
7177
7505
  *
7178
7506
  *
7179
7507
  */
7180
7508
 
7181
7509
 
7182
- let baseUrl = window.location.origin;
7510
+ let baseUrl;
7511
+ if (typeof window === "undefined") {
7512
+ baseUrl = "http://localhost/";
7513
+ } else {
7514
+ baseUrl = window.location.origin;
7515
+ }
7183
7516
 
7184
7517
  const setBaseUrl = (value) => {
7185
7518
  baseUrl = new URL(value, window.location).href;
7186
7519
  };
7187
-
7188
- const rawUrlPartSymbol = Symbol("raw_url_part");
7189
- const rawUrlPart = (value) => {
7190
- return {
7191
- [rawUrlPartSymbol]: true,
7192
- value,
7193
- };
7194
- };
7195
- const NO_PARAMS = { [SYMBOL_IDENTITY]: Symbol("no_params") };
7196
7520
  // Controls what happens to actions when their route becomes inactive:
7197
7521
  // 'abort' - Cancel the action immediately when route deactivates
7198
7522
  // 'keep-loading' - Allow action to continue running after route deactivation
@@ -7218,26 +7542,19 @@ const updateRoutes = (
7218
7542
  const routeMatchInfoSet = new Set();
7219
7543
  for (const route of routeSet) {
7220
7544
  const routePrivateProperties = getRoutePrivateProperties(route);
7221
- const { urlPattern } = routePrivateProperties;
7545
+ const { routePattern } = routePrivateProperties;
7222
7546
 
7223
7547
  // Get previous state
7224
7548
  const previousState = routePreviousStateMap.get(route) || {
7225
7549
  active: false,
7226
- params: NO_PARAMS,
7550
+ params: null,
7227
7551
  };
7228
7552
  const oldActive = previousState.active;
7229
7553
  const oldParams = previousState.params;
7230
- // Check if the URL matches the route pattern
7231
- const match = urlPattern.exec(url);
7232
- const newActive = Boolean(match);
7554
+ const extractedParams = routePattern.applyOn(url);
7555
+ const newActive = Boolean(extractedParams);
7233
7556
  let newParams;
7234
- if (match) {
7235
- const { optionalParamKeySet } = routePrivateProperties;
7236
- const extractedParams = extractParams(
7237
- urlPattern,
7238
- url,
7239
- optionalParamKeySet,
7240
- );
7557
+ if (extractedParams) {
7241
7558
  if (compareTwoJsValues(oldParams, extractedParams)) {
7242
7559
  // No change in parameters, keep the old params
7243
7560
  newParams = oldParams;
@@ -7245,7 +7562,7 @@ const updateRoutes = (
7245
7562
  newParams = extractedParams;
7246
7563
  }
7247
7564
  } else {
7248
- newParams = NO_PARAMS;
7565
+ newParams = null;
7249
7566
  }
7250
7567
 
7251
7568
  const routeMatchInfo = {
@@ -7374,51 +7691,6 @@ const updateRoutes = (
7374
7691
  activeRouteSet,
7375
7692
  };
7376
7693
  };
7377
- const extractParams = (urlPattern, url, ignoreSet = new Set()) => {
7378
- const match = urlPattern.exec(url);
7379
- if (!match) {
7380
- return NO_PARAMS;
7381
- }
7382
- const params = {};
7383
-
7384
- // Collect all parameters from URLPattern groups, handling both named and numbered groups
7385
- let wildcardOffset = 0;
7386
- for (const property of URL_PATTERN_PROPERTIES_WITH_GROUP_SET) {
7387
- const urlPartMatch = match[property];
7388
- if (urlPartMatch && urlPartMatch.groups) {
7389
- let localWildcardCount = 0;
7390
- for (const key of Object.keys(urlPartMatch.groups)) {
7391
- const value = urlPartMatch.groups[key];
7392
- const keyAsNumber = parseInt(key, 10);
7393
- if (!isNaN(keyAsNumber)) {
7394
- if (value) {
7395
- // Only include non-empty values and non-ignored wildcard indices
7396
- const wildcardKey = String(wildcardOffset + keyAsNumber);
7397
- if (!ignoreSet.has(wildcardKey)) {
7398
- params[wildcardKey] = decodeURIComponent(value);
7399
- }
7400
- localWildcardCount++;
7401
- }
7402
- } else if (!ignoreSet.has(key)) {
7403
- // Named group (:param or {param}) - only include if not ignored
7404
- params[key] = decodeURIComponent(value);
7405
- }
7406
- }
7407
- // Update wildcard offset for next URL part
7408
- wildcardOffset += localWildcardCount;
7409
- }
7410
- }
7411
- return params;
7412
- };
7413
- const URL_PATTERN_PROPERTIES_WITH_GROUP_SET = new Set([
7414
- "protocol",
7415
- "username",
7416
- "password",
7417
- "hostname",
7418
- "pathname",
7419
- "search",
7420
- "hash",
7421
- ]);
7422
7694
 
7423
7695
  const routePrivatePropertiesMap = new Map();
7424
7696
  const getRoutePrivateProperties = (route) => {
@@ -7439,7 +7711,7 @@ const createRoute = (urlPatternInput) => {
7439
7711
  urlPattern: urlPatternInput,
7440
7712
  isRoute: true,
7441
7713
  active: false,
7442
- params: NO_PARAMS,
7714
+ params: null,
7443
7715
  buildUrl: null,
7444
7716
  bindAction: null,
7445
7717
  relativeUrl: null,
@@ -7455,13 +7727,12 @@ const createRoute = (urlPatternInput) => {
7455
7727
  routeSet.add(route);
7456
7728
 
7457
7729
  const routePrivateProperties = {
7458
- urlPattern: undefined,
7730
+ routePattern: null,
7459
7731
  activeSignal: null,
7460
7732
  paramsSignal: null,
7461
7733
  visitedSignal: null,
7462
7734
  relativeUrlSignal: null,
7463
7735
  urlSignal: null,
7464
- optionalParamKeySet: null,
7465
7736
  updateStatus: ({ active, params, visited }) => {
7466
7737
  let someChange = false;
7467
7738
  activeSignal.value = active;
@@ -7486,101 +7757,32 @@ const createRoute = (urlPatternInput) => {
7486
7757
  };
7487
7758
  routePrivatePropertiesMap.set(route, routePrivateProperties);
7488
7759
 
7489
- const buildRelativeUrl = (
7490
- params = {},
7491
- { extraParamEffect = "inject_as_search_param" } = {},
7492
- ) => {
7493
- let relativeUrl = urlPatternInput;
7494
- let hasRawUrlPartWithInvalidChars = false;
7495
-
7496
- // Encode parameter values for URL usage, with special handling for raw URL parts.
7497
- // When a parameter is wrapped with rawUrlPart(), it bypasses encoding and is
7498
- // inserted as-is into the URL. This allows including pre-encoded values or
7499
- // special characters that should not be percent-encoded.
7500
- const encodeParamValue = (value) => {
7501
- if (value && value[rawUrlPartSymbol]) {
7502
- const rawValue = value.value;
7503
- // Check if raw value contains invalid URL characters
7504
- if (/[\s<>{}|\\^`]/.test(rawValue)) {
7505
- hasRawUrlPartWithInvalidChars = true;
7506
- }
7507
- return rawValue;
7508
- }
7509
- return encodeURIComponent(value);
7510
- };
7511
-
7512
- const keys = Object.keys(params);
7513
- const extraParamSet = new Set(keys);
7760
+ const buildRelativeUrl = (params, options) =>
7761
+ buildRouteRelativeUrl(urlPatternInput, params, options);
7762
+ route.buildRelativeUrl = (params, options) => {
7763
+ const { relativeUrl } = buildRelativeUrl(params, options);
7764
+ return relativeUrl;
7765
+ };
7514
7766
 
7515
- // Replace named parameters (:param and {param})
7516
- for (const key of keys) {
7517
- const value = params[key];
7518
- const encodedValue = encodeParamValue(value);
7519
- const beforeReplace = relativeUrl;
7520
- relativeUrl = relativeUrl.replace(`:${key}`, encodedValue);
7521
- relativeUrl = relativeUrl.replace(`{${key}}`, encodedValue);
7522
- // If the URL changed, no need to inject this param
7523
- if (relativeUrl !== beforeReplace) {
7524
- extraParamSet.delete(key);
7525
- }
7767
+ route.matchesParams = (otherParams) => {
7768
+ const params = route.params;
7769
+ const paramsIsFalsyOrEmpty = !params || Object.keys(params).length === 0;
7770
+ const otherParamsFalsyOrEmpty =
7771
+ !otherParams || Object.keys(otherParams).length === 0;
7772
+ if (paramsIsFalsyOrEmpty) {
7773
+ return otherParamsFalsyOrEmpty;
7526
7774
  }
7527
-
7528
- // Handle wildcards: if the pattern ends with /*? (optional wildcard)
7529
- // always remove the wildcard part for URL building since it's optional
7530
- if (relativeUrl.endsWith("/*?")) {
7531
- // Always remove the optional wildcard part for URL building
7532
- relativeUrl = relativeUrl.slice(0, -"/*?".length);
7533
- } else if (relativeUrl.endsWith("{/}?*")) {
7534
- relativeUrl = relativeUrl.slice(0, -"{/}?*".length);
7535
- } else {
7536
- // For required wildcards (/*) or other patterns, replace normally
7537
- let wildcardIndex = 0;
7538
- relativeUrl = relativeUrl.replace(/\*/g, () => {
7539
- const paramKey = wildcardIndex.toString();
7540
- const paramValue = params[paramKey];
7541
- if (paramValue) {
7542
- extraParamSet.delete(paramKey);
7543
- }
7544
- const replacement = paramValue ? encodeParamValue(paramValue) : "*";
7545
- wildcardIndex++;
7546
- return replacement;
7547
- });
7548
- // we did not replace anything, or not enough to remove the last "*"
7549
- if (relativeUrl.endsWith("*")) {
7550
- relativeUrl = relativeUrl.slice(0, -1);
7551
- }
7775
+ if (otherParamsFalsyOrEmpty) {
7776
+ return false;
7552
7777
  }
7553
-
7554
- // Add remaining parameters as search params
7555
- if (extraParamSet.size > 0) {
7556
- if (extraParamEffect === "inject_as_search_param") {
7557
- const searchParamPairs = [];
7558
- for (const key of extraParamSet) {
7559
- const value = params[key];
7560
- if (value !== undefined && value !== null) {
7561
- const encodedKey = encodeURIComponent(key);
7562
- const encodedValue = encodeParamValue(value);
7563
- searchParamPairs.push(`${encodedKey}=${encodedValue}`);
7564
- }
7565
- }
7566
- if (searchParamPairs.length > 0) {
7567
- const searchString = searchParamPairs.join("&");
7568
- relativeUrl += (relativeUrl.includes("?") ? "&" : "?") + searchString;
7569
- }
7570
- } else if (extraParamEffect === "warn") {
7571
- console.warn(
7572
- `Unknown parameters given to "${urlPatternInput}":`,
7573
- Array.from(extraParamSet),
7574
- );
7778
+ const paramsWithoutWildcards = {};
7779
+ for (const key of Object.keys(params)) {
7780
+ if (!Number.isInteger(Number(key))) {
7781
+ paramsWithoutWildcards[key] = params[key];
7575
7782
  }
7576
7783
  }
7577
-
7578
- return {
7579
- relativeUrl,
7580
- hasRawUrlPartWithInvalidChars,
7581
- };
7784
+ return compareTwoJsValues(paramsWithoutWildcards, otherParams);
7582
7785
  };
7583
- route.buildRelativeUrl = buildRelativeUrl;
7584
7786
 
7585
7787
  /**
7586
7788
  * Builds a complete URL for this route with the given parameters.
@@ -7608,7 +7810,7 @@ const createRoute = (urlPatternInput) => {
7608
7810
  processedRelativeUrl = processedRelativeUrl.slice(1);
7609
7811
  }
7610
7812
  if (hasRawUrlPartWithInvalidChars) {
7611
- return `${baseUrl}/${processedRelativeUrl}`;
7813
+ return `${baseUrl}${processedRelativeUrl}`;
7612
7814
  }
7613
7815
  const url = new URL(processedRelativeUrl, baseUrl).href;
7614
7816
  return url;
@@ -7616,7 +7818,7 @@ const createRoute = (urlPatternInput) => {
7616
7818
  route.buildUrl = buildUrl;
7617
7819
 
7618
7820
  const activeSignal = signal(false);
7619
- const paramsSignal = signal(NO_PARAMS);
7821
+ const paramsSignal = signal(null);
7620
7822
  const visitedSignal = signal(false);
7621
7823
  const relativeUrlSignal = computed(() => {
7622
7824
  const params = paramsSignal.value;
@@ -7645,7 +7847,7 @@ const createRoute = (urlPatternInput) => {
7645
7847
  if (route.action) {
7646
7848
  route.action.replaceParams(updatedParams);
7647
7849
  }
7648
- browserIntegration$1.goTo(updatedUrl, { replace: true });
7850
+ browserIntegration$1.navTo(updatedUrl, { replace: true });
7649
7851
  };
7650
7852
  route.replaceParams = replaceParams;
7651
7853
 
@@ -7656,7 +7858,7 @@ const createRoute = (urlPatternInput) => {
7656
7858
  * and listen store changes to do this:
7657
7859
  *
7658
7860
  * When we detect changes we want to update the route params
7659
- * so we'll need to use goTo(buildUrl(params), { replace: true })
7861
+ * so we'll need to use navTo(buildUrl(params), { replace: true })
7660
7862
  *
7661
7863
  * reinserted is useful because the item id might have changed
7662
7864
  * but not the mutable key
@@ -7721,37 +7923,14 @@ const createRoute = (urlPatternInput) => {
7721
7923
  route.bindAction = bindAction;
7722
7924
 
7723
7925
  {
7724
- // Remove leading slash from urlPattern to make it relative to baseUrl
7725
- const normalizedUrlPattern = urlPatternInput.startsWith("/")
7726
- ? urlPatternInput.slice(1)
7727
- : urlPatternInput;
7728
- const urlPattern = new URLPattern(normalizedUrlPattern, baseUrl, {
7729
- ignoreCase: true,
7730
- });
7731
- routePrivateProperties.urlPattern = urlPattern;
7732
7926
  routePrivateProperties.activeSignal = activeSignal;
7733
7927
  routePrivateProperties.paramsSignal = paramsSignal;
7734
7928
  routePrivateProperties.visitedSignal = visitedSignal;
7735
7929
  routePrivateProperties.relativeUrlSignal = relativeUrlSignal;
7736
7930
  routePrivateProperties.urlSignal = urlSignal;
7737
7931
  routePrivateProperties.cleanupCallbackSet = cleanupCallbackSet;
7738
-
7739
- // Analyze pattern once to detect optional params (named and wildcard indices)
7740
- // Note: Wildcard indices are stored as strings ("0", "1", ...) to match keys from extractParams
7741
- const optionalParamKeySet = new Set();
7742
- normalizedUrlPattern.replace(/:([A-Za-z0-9_]+)\?/g, (_m, name) => {
7743
- optionalParamKeySet.add(name);
7744
- return "";
7745
- });
7746
- let wildcardIndex = 0;
7747
- normalizedUrlPattern.replace(/\*(\?)?/g, (_m, opt) => {
7748
- if (opt === "?") {
7749
- optionalParamKeySet.add(String(wildcardIndex));
7750
- }
7751
- wildcardIndex++;
7752
- return "";
7753
- });
7754
- routePrivateProperties.optionalParamKeySet = optionalParamKeySet;
7932
+ const routePattern = createRoutePattern(urlPatternInput, baseUrl);
7933
+ routePrivateProperties.routePattern = routePattern;
7755
7934
  }
7756
7935
 
7757
7936
  return route;
@@ -7944,14 +8123,6 @@ computed(() => {
7944
8123
  return reasonArray;
7945
8124
  });
7946
8125
 
7947
- const documentStateSignal = signal(null);
7948
- const useDocumentState = () => {
7949
- return documentStateSignal.value;
7950
- };
7951
- const updateDocumentState = (value) => {
7952
- documentStateSignal.value = value;
7953
- };
7954
-
7955
8126
  const documentUrlSignal = signal(window.location.href);
7956
8127
  const useDocumentUrl = () => {
7957
8128
  return documentUrlSignal.value;
@@ -7994,6 +8165,14 @@ const urlToScheme = (url) => {
7994
8165
  return scheme;
7995
8166
  };
7996
8167
 
8168
+ const documentStateSignal = signal(null);
8169
+ const useDocumentState = () => {
8170
+ return documentStateSignal.value;
8171
+ };
8172
+ const updateDocumentState = (value) => {
8173
+ documentStateSignal.value = value;
8174
+ };
8175
+
7997
8176
  const getHrefTargetInfo = (href) => {
7998
8177
  href = String(href);
7999
8178
 
@@ -8214,12 +8393,7 @@ const setupBrowserIntegrationViaHistory = ({
8214
8393
  });
8215
8394
  });
8216
8395
 
8217
- const goTo = async (target, { state = null, replace } = {}) => {
8218
- const url = new URL(target, window.location.href).href;
8219
- const currentUrl = documentUrlSignal.peek();
8220
- if (url === currentUrl) {
8221
- return;
8222
- }
8396
+ const navTo = async (url, { state = null, replace } = {}) => {
8223
8397
  if (replace) {
8224
8398
  window.history.replaceState(state, null, url);
8225
8399
  } else {
@@ -8228,7 +8402,7 @@ const setupBrowserIntegrationViaHistory = ({
8228
8402
  handleRoutingTask(url, {
8229
8403
  state,
8230
8404
  replace,
8231
- reason: `goTo called with "${url}"`,
8405
+ reason: `navTo called with "${url}"`,
8232
8406
  });
8233
8407
  };
8234
8408
 
@@ -8244,11 +8418,11 @@ const setupBrowserIntegrationViaHistory = ({
8244
8418
  });
8245
8419
  };
8246
8420
 
8247
- const goBack = () => {
8421
+ const navBack = () => {
8248
8422
  window.history.back();
8249
8423
  };
8250
8424
 
8251
- const goForward = () => {
8425
+ const navForward = () => {
8252
8426
  window.history.forward();
8253
8427
  };
8254
8428
 
@@ -8266,11 +8440,11 @@ const setupBrowserIntegrationViaHistory = ({
8266
8440
  return {
8267
8441
  integration: "browser_history_api",
8268
8442
  init,
8269
- goTo,
8443
+ navTo,
8270
8444
  stop,
8271
8445
  reload,
8272
- goBack,
8273
- goForward,
8446
+ navBack,
8447
+ navForward,
8274
8448
  getDocumentState,
8275
8449
  replaceDocumentState,
8276
8450
  isVisited,
@@ -8348,7 +8522,17 @@ setOnRouteDefined(() => {
8348
8522
  setBrowserIntegration(browserIntegration);
8349
8523
 
8350
8524
  const actionIntegratedVia = browserIntegration.integration;
8351
- const goTo = browserIntegration.goTo;
8525
+ const navTo = (target, options) => {
8526
+ const url = new URL(target, window.location.href).href;
8527
+ const currentUrl = documentUrlSignal.peek();
8528
+ if (url === currentUrl) {
8529
+ return null;
8530
+ }
8531
+ return browserIntegration.navTo(url, options);
8532
+ };
8533
+ const replaceUrl = (target, options = {}) => {
8534
+ return navTo(target, { ...options, replace: true });
8535
+ };
8352
8536
  const stopLoad = (reason = "stopLoad() called") => {
8353
8537
  const windowIsLoading = windowIsLoadingSignal.value;
8354
8538
  if (windowIsLoading) {
@@ -8360,8 +8544,8 @@ const stopLoad = (reason = "stopLoad() called") => {
8360
8544
  }
8361
8545
  };
8362
8546
  const reload = browserIntegration.reload;
8363
- const goBack = browserIntegration.goBack;
8364
- const goForward = browserIntegration.goForward;
8547
+ const navBack = browserIntegration.navBack;
8548
+ const navForward = browserIntegration.navForward;
8365
8549
  const isVisited = browserIntegration.isVisited;
8366
8550
  const visitedUrlsSignal = browserIntegration.visitedUrlsSignal;
8367
8551
  browserIntegration.handleActionTask;
@@ -8451,11 +8635,11 @@ const useUrlSearchParam = (paramName, defaultValue) => {
8451
8635
  setValue(searchParam);
8452
8636
  }
8453
8637
 
8454
- const setSearchParamValue = (newValue, { replace = true } = {}) => {
8638
+ const setSearchParamValue = (newValue, { replace = false } = {}) => {
8455
8639
  const newUrlObject = new URL(window.location.href);
8456
8640
  newUrlObject.searchParams.set(paramName, newValue);
8457
8641
  const newUrl = newUrlObject.href;
8458
- goTo(newUrl, { replace });
8642
+ navTo(newUrl, { replace });
8459
8643
  };
8460
8644
 
8461
8645
  return [value, setSearchParamValue];
@@ -8492,6 +8676,26 @@ const useForceRender = () => {
8492
8676
  const debug$1 = (...args) => {
8493
8677
  return;
8494
8678
  };
8679
+
8680
+ // Check if a route is a "parent" route (catches multiple routes) and if current URL matches exactly
8681
+ const isParentRouteExactMatch = route => {
8682
+ if (!route) {
8683
+ return false;
8684
+ }
8685
+ const currentUrl = window.location.href;
8686
+ const parentUrl = route.buildUrl();
8687
+ if (currentUrl === parentUrl) {
8688
+ return true;
8689
+ }
8690
+ const currentUrlObject = new URL(currentUrl);
8691
+ if (!currentUrlObject.pathname.endsWith("/")) {
8692
+ return false;
8693
+ }
8694
+ const pathnameWithoutSlash = currentUrlObject.pathname.slice(0, -1);
8695
+ currentUrlObject.pathname = pathnameWithoutSlash;
8696
+ const currentUrlWithoutTrailingSlash = currentUrlObject.href;
8697
+ return currentUrlWithoutTrailingSlash === parentUrl;
8698
+ };
8495
8699
  const RootElement = () => {
8496
8700
  return jsx(Route.Slot, {});
8497
8701
  };
@@ -8501,7 +8705,10 @@ const Routes = ({
8501
8705
  element = RootElement,
8502
8706
  children
8503
8707
  }) => {
8708
+ const routeInfo = useActiveRouteInfo();
8709
+ const route = routeInfo?.route;
8504
8710
  return jsx(Route, {
8711
+ route: route,
8505
8712
  element: element,
8506
8713
  children: children
8507
8714
  });
@@ -8510,6 +8717,7 @@ const useActiveRouteInfo = () => useContext(RouteInfoContext);
8510
8717
  const Route = ({
8511
8718
  element,
8512
8719
  route,
8720
+ index,
8513
8721
  fallback,
8514
8722
  meta,
8515
8723
  children
@@ -8521,6 +8729,7 @@ const Route = ({
8521
8729
  return jsx(ActiveRouteManager, {
8522
8730
  element: element,
8523
8731
  route: route,
8732
+ index: index,
8524
8733
  fallback: fallback,
8525
8734
  meta: meta,
8526
8735
  onActiveInfoChange: activeInfo => {
@@ -8548,6 +8757,7 @@ it's executed once for the entier app lifecycle */
8548
8757
  const ActiveRouteManager = ({
8549
8758
  element,
8550
8759
  route,
8760
+ index,
8551
8761
  fallback,
8552
8762
  meta,
8553
8763
  onActiveInfoChange,
@@ -8557,23 +8767,42 @@ const ActiveRouteManager = ({
8557
8767
  throw new Error("Route cannot have both route and fallback props");
8558
8768
  }
8559
8769
  const registerChildRouteFromContext = useContext(RegisterChildRouteContext);
8560
- getElementSignature(element);
8770
+ const elementId = getElementSignature(element);
8561
8771
  const candidateSet = new Set();
8562
- const registerChildRoute = (ChildActiveElement, childRoute, childFallback, childMeta) => {
8563
- getElementSignature(ChildActiveElement);
8564
- candidateSet.add({
8565
- ActiveElement: ChildActiveElement,
8566
- route: childRoute,
8567
- fallback: childFallback,
8568
- meta: childMeta
8569
- });
8772
+ let indexCandidate = null;
8773
+ let fallbackCandidate = null;
8774
+ const registerChildRoute = childRouteInfo => {
8775
+ const childElementId = getElementSignature(childRouteInfo.element);
8776
+ candidateSet.add(childRouteInfo);
8777
+ if (childRouteInfo.index) {
8778
+ if (indexCandidate) {
8779
+ throw new Error(`Multiple index routes registered under the same parent route (${elementId}):
8780
+ - ${getElementSignature(indexCandidate.element)}
8781
+ - ${childElementId}`);
8782
+ }
8783
+ indexCandidate = childRouteInfo;
8784
+ }
8785
+ if (childRouteInfo.fallback) {
8786
+ if (fallbackCandidate) {
8787
+ throw new Error(`Multiple fallback routes registered under the same parent route (${elementId}):
8788
+ - ${getElementSignature(fallbackCandidate.element)}
8789
+ - ${childElementId}`);
8790
+ }
8791
+ if (childRouteInfo.route.routeFromProps) {
8792
+ throw new Error(`Fallback route cannot have a route prop (${childElementId})`);
8793
+ }
8794
+ fallbackCandidate = childRouteInfo;
8795
+ }
8570
8796
  };
8571
8797
  useLayoutEffect(() => {
8572
8798
  initRouteObserver({
8573
8799
  element,
8574
8800
  route,
8801
+ index,
8575
8802
  fallback,
8576
8803
  meta,
8804
+ indexCandidate,
8805
+ fallbackCandidate,
8577
8806
  candidateSet,
8578
8807
  onActiveInfoChange,
8579
8808
  registerChildRouteFromContext
@@ -8587,15 +8816,24 @@ const ActiveRouteManager = ({
8587
8816
  const initRouteObserver = ({
8588
8817
  element,
8589
8818
  route,
8819
+ index,
8590
8820
  fallback,
8591
8821
  meta,
8822
+ indexCandidate,
8823
+ fallbackCandidate,
8592
8824
  candidateSet,
8593
8825
  onActiveInfoChange,
8594
8826
  registerChildRouteFromContext
8595
8827
  }) => {
8828
+ if (!fallbackCandidate && indexCandidate && indexCandidate.fallback !== false) {
8829
+ // no fallback + an index -> index behaves as a fallback (handle urls under a parent when no sibling matches)
8830
+ // to disable this behavior set fallback={false} on the index route
8831
+ // (in that case no route will be rendered when no child matches meaning only parent route element will be shown)
8832
+ fallbackCandidate = indexCandidate;
8833
+ }
8596
8834
  const [teardown, addTeardown] = createPubSub();
8597
8835
  const elementId = getElementSignature(element);
8598
- const candidateElementIds = Array.from(candidateSet, c => getElementSignature(c.ActiveElement));
8836
+ const candidateElementIds = Array.from(candidateSet, c => getElementSignature(c.element));
8599
8837
  if (candidateElementIds.length === 0) ; else {
8600
8838
  debug$1(`initRouteObserver ${elementId}, child candidates:
8601
8839
  - ${candidateElementIds.join("\n - ")}`);
@@ -8611,19 +8849,26 @@ const initRouteObserver = ({
8611
8849
  elementFromProps: element
8612
8850
  };
8613
8851
  const findActiveChildInfo = () => {
8614
- let fallbackInfo = null;
8615
8852
  for (const candidate of candidateSet) {
8616
8853
  if (candidate.route?.active) {
8617
8854
  return candidate;
8618
8855
  }
8619
- // fallback without route can match when no other route matches.
8620
- // This is useful solely for "catch all" fallback used on the <Routes>
8621
- // otherwise a fallback would always match and make the parent route always active
8622
- if (candidate.fallback && !candidate.route.routeFromProps) {
8623
- fallbackInfo = candidate;
8856
+ }
8857
+ if (indexCandidate) {
8858
+ if (indexCandidate === fallbackCandidate) {
8859
+ // the index is also used as fallback (catch all routes under a parent)
8860
+ return indexCandidate;
8861
+ }
8862
+ // Only return the index candidate if the current URL matches exactly the parent route
8863
+ // This allows fallback routes to handle non-defined URLs under this parent route
8864
+ if (route && isParentRouteExactMatch(route)) {
8865
+ return indexCandidate;
8624
8866
  }
8625
8867
  }
8626
- return fallbackInfo;
8868
+ if (fallbackCandidate) {
8869
+ return fallbackCandidate;
8870
+ }
8871
+ return null;
8627
8872
  };
8628
8873
  const getActiveInfo = route ? () => {
8629
8874
  if (!route.active) {
@@ -8637,8 +8882,8 @@ const initRouteObserver = ({
8637
8882
  return activeChildInfo;
8638
8883
  }
8639
8884
  return {
8640
- ActiveElement: null,
8641
8885
  route,
8886
+ element: null,
8642
8887
  meta
8643
8888
  };
8644
8889
  } : () => {
@@ -8659,6 +8904,13 @@ const initRouteObserver = ({
8659
8904
  const Element = element;
8660
8905
  element = jsx(Element, {});
8661
8906
  }
8907
+ // ensure we re-render on document url change (useful when navigating from /users/list to /users)
8908
+ // so that we re-replace urls back to /users/list when /users/list is an index
8909
+ useDocumentUrl();
8910
+ if (activeRouteInfo && activeRouteInfo.index && !activeRouteInfo.route.active) {
8911
+ const routeUrl = activeRouteInfo.route.routeFromProps.buildUrl();
8912
+ replaceUrl(routeUrl);
8913
+ }
8662
8914
  return jsx(RouteInfoContext.Provider, {
8663
8915
  value: activeRouteInfo,
8664
8916
  children: jsx(SlotContext.Provider, {
@@ -8673,11 +8925,13 @@ const initRouteObserver = ({
8673
8925
  if (newActiveInfo) {
8674
8926
  compositeRoute.active = true;
8675
8927
  activeRouteInfoSignal.value = newActiveInfo;
8676
- SlotActiveElementSignal.value = newActiveInfo.ActiveElement;
8928
+ SlotActiveElementSignal.value = newActiveInfo.element;
8677
8929
  onActiveInfoChange({
8678
- ActiveElement,
8679
- SlotActiveElement: newActiveInfo.ActiveElement,
8680
8930
  route: newActiveInfo.route,
8931
+ ActiveElement,
8932
+ SlotActiveElement: newActiveInfo.element,
8933
+ index: newActiveInfo.index,
8934
+ fallback: newActiveInfo.fallback,
8681
8935
  meta: newActiveInfo.meta
8682
8936
  });
8683
8937
  } else {
@@ -8698,7 +8952,13 @@ const initRouteObserver = ({
8698
8952
  addTeardown(candidate.route.subscribeStatus(onChange));
8699
8953
  }
8700
8954
  if (registerChildRouteFromContext) {
8701
- registerChildRouteFromContext(ActiveElement, compositeRoute, fallback, meta);
8955
+ registerChildRouteFromContext({
8956
+ route: compositeRoute,
8957
+ element: ActiveElement,
8958
+ index,
8959
+ fallback,
8960
+ meta
8961
+ });
8702
8962
  }
8703
8963
  updateActiveInfo();
8704
8964
  return () => {
@@ -13732,6 +13992,10 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
13732
13992
  .navi_text {
13733
13993
  position: relative;
13734
13994
  color: inherit;
13995
+
13996
+ &[data-has-absolute-child] {
13997
+ display: inline-block;
13998
+ }
13735
13999
  }
13736
14000
 
13737
14001
  .navi_text_overflow {
@@ -13755,6 +14019,53 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
13755
14019
 
13756
14020
  .navi_custom_space {
13757
14021
  }
14022
+
14023
+ .navi_text_bold_wrapper {
14024
+ position: relative;
14025
+ display: inline-block;
14026
+ }
14027
+ .navi_text_bold_clone {
14028
+ font-weight: bold;
14029
+ opacity: 0;
14030
+ }
14031
+ .navi_text_bold_foreground {
14032
+ position: absolute;
14033
+ inset: 0;
14034
+ }
14035
+
14036
+ .navi_text_bold_background {
14037
+ position: absolute;
14038
+ top: 0;
14039
+ left: 0;
14040
+ color: currentColor;
14041
+ font-weight: normal;
14042
+ background: currentColor;
14043
+ background-clip: text;
14044
+ -webkit-background-clip: text;
14045
+ transform-origin: center;
14046
+ -webkit-text-fill-color: transparent;
14047
+ opacity: 0;
14048
+ }
14049
+
14050
+ .navi_text[data-bold] {
14051
+ .navi_text_bold_background {
14052
+ opacity: 1;
14053
+ }
14054
+ }
14055
+
14056
+ .navi_text[data-bold-transition] {
14057
+ .navi_text_bold_foreground {
14058
+ transition-property: font-weight;
14059
+ transition-duration: 0.3s;
14060
+ transition-timing-function: ease;
14061
+ }
14062
+
14063
+ .navi_text_bold_background {
14064
+ transition-property: opacity;
14065
+ transition-duration: 0.3s;
14066
+ transition-timing-function: ease;
14067
+ }
14068
+ }
13758
14069
  `;
13759
14070
  const REGULAR_SPACE = jsx("span", {
13760
14071
  "data-navi-space": "",
@@ -13924,14 +14235,65 @@ const TextWithSelectRange = ({
13924
14235
  };
13925
14236
  const TextBasic = ({
13926
14237
  spacing = " ",
14238
+ boldTransition,
14239
+ boldStable,
14240
+ preventBoldLayoutShift = boldTransition,
13927
14241
  children,
13928
14242
  ...rest
13929
14243
  }) => {
13930
- return jsx(Box, {
13931
- as: "span",
13932
- baseClassName: "navi_text",
14244
+ const shouldPreserveSpacing = rest.as === "pre" || rest.box || rest.column || rest.row;
14245
+ if (!shouldPreserveSpacing) {
14246
+ children = applySpacingOnTextChildren(children, spacing);
14247
+ }
14248
+ const boxProps = {
14249
+ "as": "span",
14250
+ "data-bold-transition": boldTransition ? "" : undefined,
13933
14251
  ...rest,
13934
- children: rest.as === "pre" || rest.box || rest.column || rest.row ? children : applySpacingOnTextChildren(children, spacing)
14252
+ "baseClassName": withPropsClassName("navi_text", rest.baseClassName)
14253
+ };
14254
+ if (boldStable) {
14255
+ const {
14256
+ bold
14257
+ } = boxProps;
14258
+ return jsxs(Box, {
14259
+ ...boxProps,
14260
+ bold: undefined,
14261
+ "data-bold": bold ? "" : undefined,
14262
+ "data-has-absolute-child": "",
14263
+ children: [jsx("span", {
14264
+ className: "navi_text_bold_background",
14265
+ "aria-hidden": "true",
14266
+ children: children
14267
+ }), children]
14268
+ });
14269
+ }
14270
+ if (preventBoldLayoutShift) {
14271
+ const alignX = rest.alignX || rest.align || "start";
14272
+
14273
+ // La technique consiste a avoid un double gras qui force une taille
14274
+ // et la version light par dessus en position absolute
14275
+ // on la centre aussi pour donner l'impression que le gras s'applique depuis le centre
14276
+ // ne fonctionne que sur une seul ligne de texte (donc lorsque noWrap est actif)
14277
+ // on pourrait auto-active cela sur une prop genre boldCanChange
14278
+ return jsx(Box, {
14279
+ ...boxProps,
14280
+ children: jsxs("span", {
14281
+ className: "navi_text_bold_wrapper",
14282
+ children: [jsx("span", {
14283
+ className: "navi_text_bold_clone",
14284
+ "aria-hidden": "true",
14285
+ children: children
14286
+ }), jsx("span", {
14287
+ className: "navi_text_bold_foreground",
14288
+ "data-align": alignX,
14289
+ children: children
14290
+ })]
14291
+ })
14292
+ });
14293
+ }
14294
+ return jsx(Box, {
14295
+ ...boxProps,
14296
+ children: children
13935
14297
  });
13936
14298
  };
13937
14299
 
@@ -16400,14 +16762,15 @@ const RouteLink = ({
16400
16762
  }
16401
16763
  const routeStatus = useRouteStatus(route);
16402
16764
  const url = route.buildUrl(routeParams);
16403
- const routeIsActive = routeStatus.active;
16765
+ const active = routeStatus.active;
16766
+ const paramsAreMatching = route.matchesParams(routeParams);
16404
16767
  return jsx(Link, {
16405
16768
  ...rest,
16406
16769
  href: url,
16407
16770
  pseudoState: {
16408
- ":-navi-href-current": routeIsActive
16771
+ ":-navi-href-current": active && paramsAreMatching
16409
16772
  },
16410
- children: children
16773
+ children: children || route.buildRelativeUrl(routeParams)
16411
16774
  });
16412
16775
  };
16413
16776
 
@@ -16429,8 +16792,9 @@ import.meta.css = /* css */`
16429
16792
  --tab-color: inherit;
16430
16793
  --tab-color-hover: #010409;
16431
16794
  --tab-color-selected: inherit;
16432
- --tab-marker-height: 2px;
16433
- --tab-marker-color: rgb(205, 52, 37);
16795
+ --tab-indicator-size: 2px;
16796
+ --tab-indicator-spacing: 5px;
16797
+ --tab-indicator-color: rgb(205, 52, 37);
16434
16798
  }
16435
16799
  }
16436
16800
 
@@ -16439,100 +16803,175 @@ import.meta.css = /* css */`
16439
16803
  line-height: 2em;
16440
16804
  overflow-x: auto;
16441
16805
  overflow-y: hidden;
16442
- }
16443
- .navi_tablist > ul {
16444
- display: flex;
16445
- width: 100%;
16446
- margin: 0;
16447
- padding: 0;
16448
- align-items: center;
16449
- gap: 0.5rem;
16450
- list-style: none;
16451
- background: var(--tablist-background);
16452
- border-radius: var(--tablist-border-radius);
16453
- }
16454
- .navi_tablist > ul > li {
16455
- position: relative;
16456
- display: inline-flex;
16457
- }
16458
-
16459
- .navi_tab {
16460
- --x-tab-background: var(--tab-background);
16461
- --x-tab-color: var(--tab-color);
16462
16806
 
16463
- display: flex;
16464
- flex-direction: column;
16465
- white-space: nowrap;
16466
- border-radius: var(--tab-border-radius);
16467
-
16468
- .navi_tab_content {
16469
- display: flex;
16470
- color: var(--x-tab-color);
16471
- background: var(--x-tab-background);
16472
- border-radius: inherit;
16473
- transition: background 0.12s ease-out;
16474
-
16475
- .navi_link {
16476
- flex-grow: 1;
16477
- text-align: center;
16478
- border-radius: inherit;
16807
+ &[data-tab-indicator-position="start"] {
16808
+ .navi_tab {
16809
+ margin-top: var(--tab-indicator-spacing);
16479
16810
  }
16480
16811
  }
16481
- /* Hidden bold clone to reserve space for bold width without affecting height */
16482
- .navi_tab_content_bold_clone {
16483
- display: block; /* in-flow so it contributes to width */
16484
- height: 0; /* zero height so it doesn't change layout height */
16485
- font-weight: 600; /* force bold to compute max width */
16486
- visibility: hidden; /* not visible */
16487
- pointer-events: none; /* inert */
16488
- overflow: hidden; /* avoid any accidental height */
16812
+ &[data-tab-indicator-position="end"] {
16813
+ .navi_tab {
16814
+ margin-bottom: var(--tab-indicator-spacing);
16815
+ }
16489
16816
  }
16490
- .navi_tab_selected_marker {
16491
- z-index: 1;
16817
+
16818
+ > ul {
16492
16819
  display: flex;
16493
16820
  width: 100%;
16494
- height: var(--tab-marker-height);
16495
- margin-top: 5px;
16496
- background: transparent;
16497
- border-radius: 0.1px;
16498
- }
16821
+ margin: 0;
16822
+ padding: 2px; /* space for border radius and outline */
16823
+ align-items: center;
16824
+ gap: 0.5rem;
16825
+ list-style: none;
16826
+ background: var(--tablist-background);
16827
+ border-radius: var(--tablist-border-radius);
16499
16828
 
16500
- /* Interactive */
16501
- &[data-interactive] {
16502
- cursor: pointer;
16503
- }
16504
- /* Hover */
16505
- &:hover {
16506
- --x-tab-background: var(--tab-background-hover);
16507
- --x-tab-color: var(--tab-color-hover);
16829
+ > li {
16830
+ position: relative;
16831
+ display: inline-flex;
16832
+
16833
+ .navi_tab {
16834
+ --x-tab-background: var(--tab-background);
16835
+ --x-tab-color: var(--tab-color);
16836
+
16837
+ display: flex;
16838
+ flex-direction: column;
16839
+ color: var(--x-tab-color);
16840
+ white-space: nowrap;
16841
+ background: var(--x-tab-background);
16842
+ border-radius: var(--tab-border-radius);
16843
+ transition: background 0.12s ease-out;
16844
+ user-select: none;
16845
+
16846
+ span,
16847
+ a {
16848
+ display: inline-flex;
16849
+ flex-grow: 1;
16850
+ justify-content: center;
16851
+ text-align: center;
16852
+ border-radius: inherit;
16853
+ }
16854
+
16855
+ .navi_tab_indicator {
16856
+ position: absolute;
16857
+ z-index: 1;
16858
+ display: flex;
16859
+ width: 100%;
16860
+ height: var(--tab-indicator-size);
16861
+ background: transparent;
16862
+ border-radius: 0.1px;
16863
+
16864
+ &[data-position="start"] {
16865
+ top: 0;
16866
+ left: 0;
16867
+ }
16868
+
16869
+ &[data-position="end"] {
16870
+ bottom: 0;
16871
+ left: 0;
16872
+ }
16873
+ }
16874
+
16875
+ /* Interactive */
16876
+ &[data-interactive] {
16877
+ cursor: pointer;
16878
+ }
16879
+ /* Hover */
16880
+ &:hover {
16881
+ --x-tab-background: var(--tab-background-hover);
16882
+ --x-tab-color: var(--tab-color-hover);
16883
+ }
16884
+ /* Selected */
16885
+ &[data-selected] {
16886
+ --x-tab-background: var(--tab-background-selected);
16887
+ --x-tab-color: var(--tab-color-selected);
16888
+ font-weight: bold;
16889
+
16890
+ .navi_tab_indicator {
16891
+ background: var(--tab-indicator-color);
16892
+ }
16893
+ }
16894
+ }
16895
+ }
16508
16896
  }
16509
- /* Selected */
16510
- &[data-selected] {
16511
- --x-tab-background: var(--tab-background-selected);
16512
- --x-tab-color: var(--tab-color-selected);
16513
16897
 
16514
- .navi_tab_content {
16515
- font-weight: 600;
16898
+ /* Vertical layout */
16899
+ &[data-vertical] {
16900
+ overflow-x: hidden;
16901
+ overflow-y: auto;
16902
+
16903
+ .navi_tab {
16904
+ span,
16905
+ a {
16906
+ justify-content: start;
16907
+ }
16908
+
16909
+ &[data-align-x="end"] {
16910
+ span,
16911
+ a {
16912
+ justify-content: end;
16913
+ }
16914
+ }
16915
+ }
16916
+
16917
+ &[data-tab-indicator-position="start"] {
16918
+ .navi_tab {
16919
+ margin-top: 0;
16920
+ margin-left: var(--tab-indicator-spacing);
16921
+
16922
+ .navi_tab_indicator {
16923
+ top: 0;
16924
+ left: 0;
16925
+ }
16926
+ }
16516
16927
  }
16517
- .navi_tab_selected_marker {
16518
- background: var(--tab-marker-color);
16928
+ &[data-tab-indicator-position="end"] {
16929
+ .navi_tab {
16930
+ margin-right: var(--tab-indicator-spacing);
16931
+ margin-bottom: 0;
16932
+
16933
+ .navi_tab_indicator {
16934
+ top: 0;
16935
+ right: 0;
16936
+ left: auto;
16937
+ }
16938
+ }
16519
16939
  }
16520
- }
16521
- }
16522
16940
 
16523
- .navi_tablist[data-expand] {
16524
- .navi_tab {
16525
- flex: 1;
16526
- align-items: center;
16941
+ > ul {
16942
+ flex-direction: column;
16943
+ align-items: start;
16944
+
16945
+ > li {
16946
+ width: 100%;
16947
+
16948
+ .navi_tab {
16949
+ flex-direction: row;
16950
+ text-align: left;
16951
+
16952
+ .navi_tab_indicator {
16953
+ width: var(--tab-indicator-size);
16954
+ height: 100%;
16955
+ }
16956
+ }
16957
+ }
16958
+ }
16527
16959
  }
16528
16960
 
16529
- .navi_tab_content {
16530
- width: 100%;
16531
- justify-content: center;
16961
+ &[data-expand] {
16962
+ > ul {
16963
+ .navi_tab {
16964
+ width: 100%;
16965
+ flex: 1;
16966
+ align-items: stretch;
16967
+ justify-content: center;
16968
+ }
16969
+ }
16532
16970
  }
16533
16971
  }
16534
16972
  `;
16535
- const TabListUnderlinerContext = createContext();
16973
+ const TabListIndicatorContext = createContext();
16974
+ const TabListAlignXContext = createContext();
16536
16975
  const TabListStyleCSSVars = {
16537
16976
  borderRadius: "--tablist-border-radius",
16538
16977
  background: "--tablist-background"
@@ -16540,7 +16979,9 @@ const TabListStyleCSSVars = {
16540
16979
  const TabList = ({
16541
16980
  children,
16542
16981
  spacing,
16543
- underline,
16982
+ vertical,
16983
+ indicator = vertical ? "start" : "end",
16984
+ alignX,
16544
16985
  expand,
16545
16986
  expandX,
16546
16987
  paddingX,
@@ -16548,11 +16989,14 @@ const TabList = ({
16548
16989
  padding,
16549
16990
  ...props
16550
16991
  }) => {
16992
+ children = toChildArray(children);
16551
16993
  return jsx(Box, {
16552
16994
  as: "nav",
16553
16995
  baseClassName: "navi_tablist",
16554
16996
  role: "tablist",
16997
+ "data-tab-indicator-position": indicator === "start" || indicator === "end" ? indicator : undefined,
16555
16998
  "data-expand": expand || expandX ? "" : undefined,
16999
+ "data-vertical": vertical ? "" : undefined,
16556
17000
  expand: expand,
16557
17001
  expandX: expandX,
16558
17002
  ...props,
@@ -16565,16 +17009,19 @@ const TabList = ({
16565
17009
  paddingY: paddingY,
16566
17010
  padding: padding,
16567
17011
  spacing: spacing,
16568
- children: jsx(TabListUnderlinerContext.Provider, {
16569
- value: underline,
16570
- children: children.map(child => {
16571
- return jsx(Box, {
16572
- as: "li",
16573
- column: true,
16574
- expandX: expandX,
16575
- expand: expand,
16576
- children: child
16577
- }, child.props.key);
17012
+ children: jsx(TabListIndicatorContext.Provider, {
17013
+ value: indicator,
17014
+ children: jsx(TabListAlignXContext.Provider, {
17015
+ value: alignX,
17016
+ children: children.map(child => {
17017
+ return jsx(Box, {
17018
+ as: "li",
17019
+ column: true,
17020
+ expandX: expandX,
17021
+ expand: expand,
17022
+ children: child
17023
+ }, child.props.key);
17024
+ })
16578
17025
  })
16579
17026
  })
16580
17027
  })
@@ -16593,7 +17040,7 @@ const TAB_STYLE_CSS_VARS = {
16593
17040
  }
16594
17041
  };
16595
17042
  const TAB_PSEUDO_CLASSES = [":hover", ":-navi-selected"];
16596
- const TAB_PSEUDO_ELEMENTS = ["::-navi-marker"];
17043
+ const TAB_PSEUDO_ELEMENTS = ["::-navi-indicator"];
16597
17044
  const Tab = props => {
16598
17045
  if (props.route) {
16599
17046
  return jsx(TabRoute, {
@@ -16604,8 +17051,10 @@ const Tab = props => {
16604
17051
  ...props
16605
17052
  });
16606
17053
  };
17054
+ TabList.Tab = Tab;
16607
17055
  const TabRoute = ({
16608
17056
  route,
17057
+ routeParams,
16609
17058
  children,
16610
17059
  paddingX,
16611
17060
  padding,
@@ -16615,15 +17064,17 @@ const TabRoute = ({
16615
17064
  const {
16616
17065
  active
16617
17066
  } = useRouteStatus(route);
17067
+ const paramsAreMatching = route.matchesParams(routeParams);
17068
+ const selected = active && paramsAreMatching;
16618
17069
  return jsx(TabBasic, {
16619
- selected: active,
17070
+ selected: selected,
16620
17071
  paddingX: "0",
16621
17072
  ...props,
16622
17073
  children: jsx(RouteLink, {
16623
17074
  route: route,
17075
+ routeParams: routeParams,
16624
17076
  expand: true,
16625
17077
  discrete: true,
16626
- align: "center",
16627
17078
  paddingX: paddingX,
16628
17079
  padding: padding,
16629
17080
  paddingY: paddingY,
@@ -16634,18 +17085,17 @@ const TabRoute = ({
16634
17085
  const TabBasic = ({
16635
17086
  children,
16636
17087
  selected,
16637
- padding,
16638
- paddingX = "s",
16639
- paddingY,
16640
17088
  onClick,
16641
17089
  ...props
16642
17090
  }) => {
16643
- const tabListUnderline = useContext(TabListUnderlinerContext);
17091
+ const tabListIndicator = useContext(TabListIndicatorContext);
17092
+ const tabListAlignX = useContext(TabListAlignXContext);
16644
17093
  return jsxs(Box, {
16645
17094
  role: "tab",
16646
17095
  "aria-selected": selected ? "true" : "false",
16647
17096
  "data-interactive": onClick ? "" : undefined,
16648
- onClick: onClick
17097
+ onClick: onClick,
17098
+ paddingX: "s"
16649
17099
  // Style system
16650
17100
  ,
16651
17101
  baseClassName: "navi_tab",
@@ -16655,19 +17105,18 @@ const TabBasic = ({
16655
17105
  basePseudoState: {
16656
17106
  ":-navi-selected": selected
16657
17107
  },
17108
+ selfAlignX: tabListAlignX,
17109
+ "data-align-x": tabListAlignX,
16658
17110
  ...props,
16659
- children: [jsx(Box, {
16660
- className: "navi_tab_content",
16661
- paddingX: paddingX,
16662
- paddingY: paddingY,
16663
- padding: padding,
16664
- children: children
16665
- }), jsx("div", {
16666
- className: "navi_tab_content_bold_clone",
16667
- "aria-hidden": "true",
17111
+ children: [(tabListIndicator === "start" || tabListIndicator === "end") && jsx("span", {
17112
+ className: "navi_tab_indicator",
17113
+ "data-position": tabListIndicator
17114
+ }), jsx(Text, {
17115
+ noWrap: true,
17116
+ preventBoldLayoutShift: true
17117
+ // boldTransition
17118
+ ,
16668
17119
  children: children
16669
- }), tabListUnderline && jsx("span", {
16670
- className: "navi_tab_selected_marker"
16671
17120
  })]
16672
17121
  });
16673
17122
  };
@@ -23788,5 +24237,5 @@ const UserSvg = () => jsx("svg", {
23788
24237
  })
23789
24238
  });
23790
24239
 
23791
- export { ActionRenderer, ActiveKeyboardShortcuts, BadgeCount, Box, Button, Caption, CheckSvg, Checkbox, CheckboxList, Code, Col, Colgroup, Details, DialogLayout, Editable, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, SearchSvg, Select, SelectionContext, SettingsSvg, StarSvg, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, addCustomMessage, compareTwoJsValues, createAction, createRequestCanceller, createSelectionKeyboardShortcuts, createUniqueValueConstraint, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, goBack, goForward, goTo, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, openCallout, rawUrlPart, reload, removeCustomMessage, rerunActions, resource, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useActiveRouteInfo, useCellsAndColumns, useConstraintValidityState, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState$1 as useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useUrlSearchParam, valueInLocalStorage };
24240
+ export { ActionRenderer, ActiveKeyboardShortcuts, BadgeCount, Box, Button, Caption, CheckSvg, Checkbox, CheckboxList, Code, Col, Colgroup, Details, DialogLayout, Editable, ErrorBoundaryContext, ExclamationSvg, EyeClosedSvg, EyeSvg, Form, HeartSvg, HomeSvg, Icon, Image, Input, Label, Link, LinkAnchorSvg, LinkBlankTargetSvg, MessageBox, Paragraph, Radio, RadioList, Route, RouteLink, Routes, RowNumberCol, RowNumberTableCell, SINGLE_SPACE_CONSTRAINT, SVGMaskOverlay, SearchSvg, Select, SelectionContext, SettingsSvg, StarSvg, SummaryMarker, Svg, Tab, TabList, Table, TableCell, Tbody, Text, Thead, Title, Tr, UITransition, UserSvg, ViewportLayout, actionIntegratedVia, addCustomMessage, compareTwoJsValues, createAction, createRequestCanceller, createSelectionKeyboardShortcuts, createUniqueValueConstraint, enableDebugActions, enableDebugOnDocumentLoading, forwardActionRequested, installCustomConstraintValidation, isCellSelected, isColumnSelected, isRowSelected, localStorageSignal, navBack, navForward, navTo, openCallout, rawUrlPart, reload, removeCustomMessage, rerunActions, resource, setBaseUrl, setupRoutes, stateSignal, stopLoad, stringifyTableSelectionValue, updateActions, useActionData, useActionStatus, useActiveRouteInfo, useCellsAndColumns, useConstraintValidityState, useDependenciesDiff, useDocumentResource, useDocumentState, useDocumentUrl, useEditionController, useFocusGroup, useKeyboardShortcuts, useNavState$1 as useNavState, useRouteStatus, useRunOnMount, useSelectableElement, useSelectionController, useSignalSync, useStateArray, useUrlSearchParam, valueInLocalStorage };
23792
24241
  //# sourceMappingURL=jsenv_navi.js.map