@jsenv/navi 0.13.4 → 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) {
368
342
  return false;
369
343
  }
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)) {
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)) {
355
+ return false;
356
+ }
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
@@ -4574,16 +4584,17 @@ const useStateArray = (
4574
4584
  const createRequestCanceller = (reason = "Request superseded") => {
4575
4585
  let previousAbortController;
4576
4586
  return () => {
4577
- previousAbortController?.abort(reason);
4587
+ if (previousAbortController) {
4588
+ const abortError = new DOMException(reason, "AbortError");
4589
+ abortError.isHandled = true;
4590
+ previousAbortController.abort(abortError);
4591
+ }
4578
4592
  previousAbortController = new AbortController();
4579
4593
  return previousAbortController.signal;
4580
4594
  };
4581
4595
  };
4582
4596
  window.addEventListener("unhandledrejection", (event) => {
4583
- if (
4584
- event.reason?.name === "AbortError" &&
4585
- event.reason?.message === "Request superseded"
4586
- ) {
4597
+ if (event.reason?.isHandled) {
4587
4598
  event.preventDefault(); // 💥 empêche les "uncaught rejection" devtools pour nos cancellations
4588
4599
  }
4589
4600
  });
@@ -4733,7 +4744,7 @@ const DIMENSION_PROPS = {
4733
4744
  return null;
4734
4745
  }
4735
4746
  if (parentBoxFlow === "column" || parentBoxFlow === "inline-column") {
4736
- return { flexGrow: 1 }; // Grow horizontally in row
4747
+ return { flexGrow: 1, flexBasis: "0%" }; // Grow horizontally in row
4737
4748
  }
4738
4749
  if (parentBoxFlow === "row") {
4739
4750
  return { minWidth: "100%", width: "auto" }; // Take full width in column
@@ -4748,7 +4759,7 @@ const DIMENSION_PROPS = {
4748
4759
  return { minHeight: "100%", height: "auto" }; // Make column full height
4749
4760
  }
4750
4761
  if (parentBoxFlow === "row" || parentBoxFlow === "inline-row") {
4751
- return { flexGrow: 1 }; // Make row full height
4762
+ return { flexGrow: 1, flexBasis: "0%" }; // Make row full height
4752
4763
  }
4753
4764
  return { minHeight: "100%", height: "auto" }; // Take full height outside flex
4754
4765
  },
@@ -4932,6 +4943,9 @@ const VISUAL_PROPS = {
4932
4943
  filter: PASS_THROUGH,
4933
4944
  cursor: PASS_THROUGH,
4934
4945
  transition: PASS_THROUGH,
4946
+ overflow: PASS_THROUGH,
4947
+ overflowX: PASS_THROUGH,
4948
+ overflowY: PASS_THROUGH,
4935
4949
  };
4936
4950
  const CONTENT_PROPS = {
4937
4951
  align: applyOnTwoProps("alignX", "alignY"),
@@ -7172,26 +7186,337 @@ const useUITransitionContentId = value => {
7172
7186
  }, []);
7173
7187
  };
7174
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
+
7175
7504
  /**
7176
7505
  *
7177
7506
  *
7178
7507
  */
7179
7508
 
7180
7509
 
7181
- 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
+ }
7182
7516
 
7183
7517
  const setBaseUrl = (value) => {
7184
7518
  baseUrl = new URL(value, window.location).href;
7185
7519
  };
7186
-
7187
- const rawUrlPartSymbol = Symbol("raw_url_part");
7188
- const rawUrlPart = (value) => {
7189
- return {
7190
- [rawUrlPartSymbol]: true,
7191
- value,
7192
- };
7193
- };
7194
- const NO_PARAMS = { [SYMBOL_IDENTITY]: Symbol("no_params") };
7195
7520
  // Controls what happens to actions when their route becomes inactive:
7196
7521
  // 'abort' - Cancel the action immediately when route deactivates
7197
7522
  // 'keep-loading' - Allow action to continue running after route deactivation
@@ -7217,26 +7542,19 @@ const updateRoutes = (
7217
7542
  const routeMatchInfoSet = new Set();
7218
7543
  for (const route of routeSet) {
7219
7544
  const routePrivateProperties = getRoutePrivateProperties(route);
7220
- const { urlPattern } = routePrivateProperties;
7545
+ const { routePattern } = routePrivateProperties;
7221
7546
 
7222
7547
  // Get previous state
7223
7548
  const previousState = routePreviousStateMap.get(route) || {
7224
7549
  active: false,
7225
- params: NO_PARAMS,
7550
+ params: null,
7226
7551
  };
7227
7552
  const oldActive = previousState.active;
7228
7553
  const oldParams = previousState.params;
7229
- // Check if the URL matches the route pattern
7230
- const match = urlPattern.exec(url);
7231
- const newActive = Boolean(match);
7554
+ const extractedParams = routePattern.applyOn(url);
7555
+ const newActive = Boolean(extractedParams);
7232
7556
  let newParams;
7233
- if (match) {
7234
- const { optionalParamKeySet } = routePrivateProperties;
7235
- const extractedParams = extractParams(
7236
- urlPattern,
7237
- url,
7238
- optionalParamKeySet,
7239
- );
7557
+ if (extractedParams) {
7240
7558
  if (compareTwoJsValues(oldParams, extractedParams)) {
7241
7559
  // No change in parameters, keep the old params
7242
7560
  newParams = oldParams;
@@ -7244,7 +7562,7 @@ const updateRoutes = (
7244
7562
  newParams = extractedParams;
7245
7563
  }
7246
7564
  } else {
7247
- newParams = NO_PARAMS;
7565
+ newParams = null;
7248
7566
  }
7249
7567
 
7250
7568
  const routeMatchInfo = {
@@ -7373,51 +7691,6 @@ const updateRoutes = (
7373
7691
  activeRouteSet,
7374
7692
  };
7375
7693
  };
7376
- const extractParams = (urlPattern, url, ignoreSet = new Set()) => {
7377
- const match = urlPattern.exec(url);
7378
- if (!match) {
7379
- return NO_PARAMS;
7380
- }
7381
- const params = {};
7382
-
7383
- // Collect all parameters from URLPattern groups, handling both named and numbered groups
7384
- let wildcardOffset = 0;
7385
- for (const property of URL_PATTERN_PROPERTIES_WITH_GROUP_SET) {
7386
- const urlPartMatch = match[property];
7387
- if (urlPartMatch && urlPartMatch.groups) {
7388
- let localWildcardCount = 0;
7389
- for (const key of Object.keys(urlPartMatch.groups)) {
7390
- const value = urlPartMatch.groups[key];
7391
- const keyAsNumber = parseInt(key, 10);
7392
- if (!isNaN(keyAsNumber)) {
7393
- if (value) {
7394
- // Only include non-empty values and non-ignored wildcard indices
7395
- const wildcardKey = String(wildcardOffset + keyAsNumber);
7396
- if (!ignoreSet.has(wildcardKey)) {
7397
- params[wildcardKey] = decodeURIComponent(value);
7398
- }
7399
- localWildcardCount++;
7400
- }
7401
- } else if (!ignoreSet.has(key)) {
7402
- // Named group (:param or {param}) - only include if not ignored
7403
- params[key] = decodeURIComponent(value);
7404
- }
7405
- }
7406
- // Update wildcard offset for next URL part
7407
- wildcardOffset += localWildcardCount;
7408
- }
7409
- }
7410
- return params;
7411
- };
7412
- const URL_PATTERN_PROPERTIES_WITH_GROUP_SET = new Set([
7413
- "protocol",
7414
- "username",
7415
- "password",
7416
- "hostname",
7417
- "pathname",
7418
- "search",
7419
- "hash",
7420
- ]);
7421
7694
 
7422
7695
  const routePrivatePropertiesMap = new Map();
7423
7696
  const getRoutePrivateProperties = (route) => {
@@ -7438,7 +7711,7 @@ const createRoute = (urlPatternInput) => {
7438
7711
  urlPattern: urlPatternInput,
7439
7712
  isRoute: true,
7440
7713
  active: false,
7441
- params: NO_PARAMS,
7714
+ params: null,
7442
7715
  buildUrl: null,
7443
7716
  bindAction: null,
7444
7717
  relativeUrl: null,
@@ -7454,13 +7727,12 @@ const createRoute = (urlPatternInput) => {
7454
7727
  routeSet.add(route);
7455
7728
 
7456
7729
  const routePrivateProperties = {
7457
- urlPattern: undefined,
7730
+ routePattern: null,
7458
7731
  activeSignal: null,
7459
7732
  paramsSignal: null,
7460
7733
  visitedSignal: null,
7461
7734
  relativeUrlSignal: null,
7462
7735
  urlSignal: null,
7463
- optionalParamKeySet: null,
7464
7736
  updateStatus: ({ active, params, visited }) => {
7465
7737
  let someChange = false;
7466
7738
  activeSignal.value = active;
@@ -7485,101 +7757,32 @@ const createRoute = (urlPatternInput) => {
7485
7757
  };
7486
7758
  routePrivatePropertiesMap.set(route, routePrivateProperties);
7487
7759
 
7488
- const buildRelativeUrl = (
7489
- params = {},
7490
- { extraParamEffect = "inject_as_search_param" } = {},
7491
- ) => {
7492
- let relativeUrl = urlPatternInput;
7493
- let hasRawUrlPartWithInvalidChars = false;
7494
-
7495
- // Encode parameter values for URL usage, with special handling for raw URL parts.
7496
- // When a parameter is wrapped with rawUrlPart(), it bypasses encoding and is
7497
- // inserted as-is into the URL. This allows including pre-encoded values or
7498
- // special characters that should not be percent-encoded.
7499
- const encodeParamValue = (value) => {
7500
- if (value && value[rawUrlPartSymbol]) {
7501
- const rawValue = value.value;
7502
- // Check if raw value contains invalid URL characters
7503
- if (/[\s<>{}|\\^`]/.test(rawValue)) {
7504
- hasRawUrlPartWithInvalidChars = true;
7505
- }
7506
- return rawValue;
7507
- }
7508
- return encodeURIComponent(value);
7509
- };
7510
-
7511
- const keys = Object.keys(params);
7512
- 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
+ };
7513
7766
 
7514
- // Replace named parameters (:param and {param})
7515
- for (const key of keys) {
7516
- const value = params[key];
7517
- const encodedValue = encodeParamValue(value);
7518
- const beforeReplace = relativeUrl;
7519
- relativeUrl = relativeUrl.replace(`:${key}`, encodedValue);
7520
- relativeUrl = relativeUrl.replace(`{${key}}`, encodedValue);
7521
- // If the URL changed, no need to inject this param
7522
- if (relativeUrl !== beforeReplace) {
7523
- extraParamSet.delete(key);
7524
- }
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;
7525
7774
  }
7526
-
7527
- // Handle wildcards: if the pattern ends with /*? (optional wildcard)
7528
- // always remove the wildcard part for URL building since it's optional
7529
- if (relativeUrl.endsWith("/*?")) {
7530
- // Always remove the optional wildcard part for URL building
7531
- relativeUrl = relativeUrl.slice(0, -"/*?".length);
7532
- } else if (relativeUrl.endsWith("{/}?*")) {
7533
- relativeUrl = relativeUrl.slice(0, -"{/}?*".length);
7534
- } else {
7535
- // For required wildcards (/*) or other patterns, replace normally
7536
- let wildcardIndex = 0;
7537
- relativeUrl = relativeUrl.replace(/\*/g, () => {
7538
- const paramKey = wildcardIndex.toString();
7539
- const paramValue = params[paramKey];
7540
- if (paramValue) {
7541
- extraParamSet.delete(paramKey);
7542
- }
7543
- const replacement = paramValue ? encodeParamValue(paramValue) : "*";
7544
- wildcardIndex++;
7545
- return replacement;
7546
- });
7547
- // we did not replace anything, or not enough to remove the last "*"
7548
- if (relativeUrl.endsWith("*")) {
7549
- relativeUrl = relativeUrl.slice(0, -1);
7550
- }
7775
+ if (otherParamsFalsyOrEmpty) {
7776
+ return false;
7551
7777
  }
7552
-
7553
- // Add remaining parameters as search params
7554
- if (extraParamSet.size > 0) {
7555
- if (extraParamEffect === "inject_as_search_param") {
7556
- const searchParamPairs = [];
7557
- for (const key of extraParamSet) {
7558
- const value = params[key];
7559
- if (value !== undefined && value !== null) {
7560
- const encodedKey = encodeURIComponent(key);
7561
- const encodedValue = encodeParamValue(value);
7562
- searchParamPairs.push(`${encodedKey}=${encodedValue}`);
7563
- }
7564
- }
7565
- if (searchParamPairs.length > 0) {
7566
- const searchString = searchParamPairs.join("&");
7567
- relativeUrl += (relativeUrl.includes("?") ? "&" : "?") + searchString;
7568
- }
7569
- } else if (extraParamEffect === "warn") {
7570
- console.warn(
7571
- `Unknown parameters given to "${urlPatternInput}":`,
7572
- Array.from(extraParamSet),
7573
- );
7778
+ const paramsWithoutWildcards = {};
7779
+ for (const key of Object.keys(params)) {
7780
+ if (!Number.isInteger(Number(key))) {
7781
+ paramsWithoutWildcards[key] = params[key];
7574
7782
  }
7575
7783
  }
7576
-
7577
- return {
7578
- relativeUrl,
7579
- hasRawUrlPartWithInvalidChars,
7580
- };
7784
+ return compareTwoJsValues(paramsWithoutWildcards, otherParams);
7581
7785
  };
7582
- route.buildRelativeUrl = buildRelativeUrl;
7583
7786
 
7584
7787
  /**
7585
7788
  * Builds a complete URL for this route with the given parameters.
@@ -7607,7 +7810,7 @@ const createRoute = (urlPatternInput) => {
7607
7810
  processedRelativeUrl = processedRelativeUrl.slice(1);
7608
7811
  }
7609
7812
  if (hasRawUrlPartWithInvalidChars) {
7610
- return `${baseUrl}/${processedRelativeUrl}`;
7813
+ return `${baseUrl}${processedRelativeUrl}`;
7611
7814
  }
7612
7815
  const url = new URL(processedRelativeUrl, baseUrl).href;
7613
7816
  return url;
@@ -7615,7 +7818,7 @@ const createRoute = (urlPatternInput) => {
7615
7818
  route.buildUrl = buildUrl;
7616
7819
 
7617
7820
  const activeSignal = signal(false);
7618
- const paramsSignal = signal(NO_PARAMS);
7821
+ const paramsSignal = signal(null);
7619
7822
  const visitedSignal = signal(false);
7620
7823
  const relativeUrlSignal = computed(() => {
7621
7824
  const params = paramsSignal.value;
@@ -7644,7 +7847,7 @@ const createRoute = (urlPatternInput) => {
7644
7847
  if (route.action) {
7645
7848
  route.action.replaceParams(updatedParams);
7646
7849
  }
7647
- browserIntegration$1.goTo(updatedUrl, { replace: true });
7850
+ browserIntegration$1.navTo(updatedUrl, { replace: true });
7648
7851
  };
7649
7852
  route.replaceParams = replaceParams;
7650
7853
 
@@ -7655,7 +7858,7 @@ const createRoute = (urlPatternInput) => {
7655
7858
  * and listen store changes to do this:
7656
7859
  *
7657
7860
  * When we detect changes we want to update the route params
7658
- * so we'll need to use goTo(buildUrl(params), { replace: true })
7861
+ * so we'll need to use navTo(buildUrl(params), { replace: true })
7659
7862
  *
7660
7863
  * reinserted is useful because the item id might have changed
7661
7864
  * but not the mutable key
@@ -7720,37 +7923,14 @@ const createRoute = (urlPatternInput) => {
7720
7923
  route.bindAction = bindAction;
7721
7924
 
7722
7925
  {
7723
- // Remove leading slash from urlPattern to make it relative to baseUrl
7724
- const normalizedUrlPattern = urlPatternInput.startsWith("/")
7725
- ? urlPatternInput.slice(1)
7726
- : urlPatternInput;
7727
- const urlPattern = new URLPattern(normalizedUrlPattern, baseUrl, {
7728
- ignoreCase: true,
7729
- });
7730
- routePrivateProperties.urlPattern = urlPattern;
7731
7926
  routePrivateProperties.activeSignal = activeSignal;
7732
7927
  routePrivateProperties.paramsSignal = paramsSignal;
7733
7928
  routePrivateProperties.visitedSignal = visitedSignal;
7734
7929
  routePrivateProperties.relativeUrlSignal = relativeUrlSignal;
7735
7930
  routePrivateProperties.urlSignal = urlSignal;
7736
7931
  routePrivateProperties.cleanupCallbackSet = cleanupCallbackSet;
7737
-
7738
- // Analyze pattern once to detect optional params (named and wildcard indices)
7739
- // Note: Wildcard indices are stored as strings ("0", "1", ...) to match keys from extractParams
7740
- const optionalParamKeySet = new Set();
7741
- normalizedUrlPattern.replace(/:([A-Za-z0-9_]+)\?/g, (_m, name) => {
7742
- optionalParamKeySet.add(name);
7743
- return "";
7744
- });
7745
- let wildcardIndex = 0;
7746
- normalizedUrlPattern.replace(/\*(\?)?/g, (_m, opt) => {
7747
- if (opt === "?") {
7748
- optionalParamKeySet.add(String(wildcardIndex));
7749
- }
7750
- wildcardIndex++;
7751
- return "";
7752
- });
7753
- routePrivateProperties.optionalParamKeySet = optionalParamKeySet;
7932
+ const routePattern = createRoutePattern(urlPatternInput, baseUrl);
7933
+ routePrivateProperties.routePattern = routePattern;
7754
7934
  }
7755
7935
 
7756
7936
  return route;
@@ -7943,14 +8123,6 @@ computed(() => {
7943
8123
  return reasonArray;
7944
8124
  });
7945
8125
 
7946
- const documentStateSignal = signal(null);
7947
- const useDocumentState = () => {
7948
- return documentStateSignal.value;
7949
- };
7950
- const updateDocumentState = (value) => {
7951
- documentStateSignal.value = value;
7952
- };
7953
-
7954
8126
  const documentUrlSignal = signal(window.location.href);
7955
8127
  const useDocumentUrl = () => {
7956
8128
  return documentUrlSignal.value;
@@ -7993,6 +8165,14 @@ const urlToScheme = (url) => {
7993
8165
  return scheme;
7994
8166
  };
7995
8167
 
8168
+ const documentStateSignal = signal(null);
8169
+ const useDocumentState = () => {
8170
+ return documentStateSignal.value;
8171
+ };
8172
+ const updateDocumentState = (value) => {
8173
+ documentStateSignal.value = value;
8174
+ };
8175
+
7996
8176
  const getHrefTargetInfo = (href) => {
7997
8177
  href = String(href);
7998
8178
 
@@ -8213,12 +8393,7 @@ const setupBrowserIntegrationViaHistory = ({
8213
8393
  });
8214
8394
  });
8215
8395
 
8216
- const goTo = async (target, { state = null, replace } = {}) => {
8217
- const url = new URL(target, window.location.href).href;
8218
- const currentUrl = documentUrlSignal.peek();
8219
- if (url === currentUrl) {
8220
- return;
8221
- }
8396
+ const navTo = async (url, { state = null, replace } = {}) => {
8222
8397
  if (replace) {
8223
8398
  window.history.replaceState(state, null, url);
8224
8399
  } else {
@@ -8227,7 +8402,7 @@ const setupBrowserIntegrationViaHistory = ({
8227
8402
  handleRoutingTask(url, {
8228
8403
  state,
8229
8404
  replace,
8230
- reason: `goTo called with "${url}"`,
8405
+ reason: `navTo called with "${url}"`,
8231
8406
  });
8232
8407
  };
8233
8408
 
@@ -8243,11 +8418,11 @@ const setupBrowserIntegrationViaHistory = ({
8243
8418
  });
8244
8419
  };
8245
8420
 
8246
- const goBack = () => {
8421
+ const navBack = () => {
8247
8422
  window.history.back();
8248
8423
  };
8249
8424
 
8250
- const goForward = () => {
8425
+ const navForward = () => {
8251
8426
  window.history.forward();
8252
8427
  };
8253
8428
 
@@ -8265,11 +8440,11 @@ const setupBrowserIntegrationViaHistory = ({
8265
8440
  return {
8266
8441
  integration: "browser_history_api",
8267
8442
  init,
8268
- goTo,
8443
+ navTo,
8269
8444
  stop,
8270
8445
  reload,
8271
- goBack,
8272
- goForward,
8446
+ navBack,
8447
+ navForward,
8273
8448
  getDocumentState,
8274
8449
  replaceDocumentState,
8275
8450
  isVisited,
@@ -8347,7 +8522,17 @@ setOnRouteDefined(() => {
8347
8522
  setBrowserIntegration(browserIntegration);
8348
8523
 
8349
8524
  const actionIntegratedVia = browserIntegration.integration;
8350
- 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
+ };
8351
8536
  const stopLoad = (reason = "stopLoad() called") => {
8352
8537
  const windowIsLoading = windowIsLoadingSignal.value;
8353
8538
  if (windowIsLoading) {
@@ -8359,8 +8544,8 @@ const stopLoad = (reason = "stopLoad() called") => {
8359
8544
  }
8360
8545
  };
8361
8546
  const reload = browserIntegration.reload;
8362
- const goBack = browserIntegration.goBack;
8363
- const goForward = browserIntegration.goForward;
8547
+ const navBack = browserIntegration.navBack;
8548
+ const navForward = browserIntegration.navForward;
8364
8549
  const isVisited = browserIntegration.isVisited;
8365
8550
  const visitedUrlsSignal = browserIntegration.visitedUrlsSignal;
8366
8551
  browserIntegration.handleActionTask;
@@ -8450,11 +8635,11 @@ const useUrlSearchParam = (paramName, defaultValue) => {
8450
8635
  setValue(searchParam);
8451
8636
  }
8452
8637
 
8453
- const setSearchParamValue = (newValue, { replace = true } = {}) => {
8638
+ const setSearchParamValue = (newValue, { replace = false } = {}) => {
8454
8639
  const newUrlObject = new URL(window.location.href);
8455
8640
  newUrlObject.searchParams.set(paramName, newValue);
8456
8641
  const newUrl = newUrlObject.href;
8457
- goTo(newUrl, { replace });
8642
+ navTo(newUrl, { replace });
8458
8643
  };
8459
8644
 
8460
8645
  return [value, setSearchParamValue];
@@ -8491,6 +8676,26 @@ const useForceRender = () => {
8491
8676
  const debug$1 = (...args) => {
8492
8677
  return;
8493
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
+ };
8494
8699
  const RootElement = () => {
8495
8700
  return jsx(Route.Slot, {});
8496
8701
  };
@@ -8500,7 +8705,10 @@ const Routes = ({
8500
8705
  element = RootElement,
8501
8706
  children
8502
8707
  }) => {
8708
+ const routeInfo = useActiveRouteInfo();
8709
+ const route = routeInfo?.route;
8503
8710
  return jsx(Route, {
8711
+ route: route,
8504
8712
  element: element,
8505
8713
  children: children
8506
8714
  });
@@ -8509,6 +8717,7 @@ const useActiveRouteInfo = () => useContext(RouteInfoContext);
8509
8717
  const Route = ({
8510
8718
  element,
8511
8719
  route,
8720
+ index,
8512
8721
  fallback,
8513
8722
  meta,
8514
8723
  children
@@ -8520,6 +8729,7 @@ const Route = ({
8520
8729
  return jsx(ActiveRouteManager, {
8521
8730
  element: element,
8522
8731
  route: route,
8732
+ index: index,
8523
8733
  fallback: fallback,
8524
8734
  meta: meta,
8525
8735
  onActiveInfoChange: activeInfo => {
@@ -8547,6 +8757,7 @@ it's executed once for the entier app lifecycle */
8547
8757
  const ActiveRouteManager = ({
8548
8758
  element,
8549
8759
  route,
8760
+ index,
8550
8761
  fallback,
8551
8762
  meta,
8552
8763
  onActiveInfoChange,
@@ -8556,23 +8767,42 @@ const ActiveRouteManager = ({
8556
8767
  throw new Error("Route cannot have both route and fallback props");
8557
8768
  }
8558
8769
  const registerChildRouteFromContext = useContext(RegisterChildRouteContext);
8559
- getElementSignature(element);
8770
+ const elementId = getElementSignature(element);
8560
8771
  const candidateSet = new Set();
8561
- const registerChildRoute = (ChildActiveElement, childRoute, childFallback, childMeta) => {
8562
- getElementSignature(ChildActiveElement);
8563
- candidateSet.add({
8564
- ActiveElement: ChildActiveElement,
8565
- route: childRoute,
8566
- fallback: childFallback,
8567
- meta: childMeta
8568
- });
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
+ }
8569
8796
  };
8570
8797
  useLayoutEffect(() => {
8571
8798
  initRouteObserver({
8572
8799
  element,
8573
8800
  route,
8801
+ index,
8574
8802
  fallback,
8575
8803
  meta,
8804
+ indexCandidate,
8805
+ fallbackCandidate,
8576
8806
  candidateSet,
8577
8807
  onActiveInfoChange,
8578
8808
  registerChildRouteFromContext
@@ -8586,15 +8816,24 @@ const ActiveRouteManager = ({
8586
8816
  const initRouteObserver = ({
8587
8817
  element,
8588
8818
  route,
8819
+ index,
8589
8820
  fallback,
8590
8821
  meta,
8822
+ indexCandidate,
8823
+ fallbackCandidate,
8591
8824
  candidateSet,
8592
8825
  onActiveInfoChange,
8593
8826
  registerChildRouteFromContext
8594
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
+ }
8595
8834
  const [teardown, addTeardown] = createPubSub();
8596
8835
  const elementId = getElementSignature(element);
8597
- const candidateElementIds = Array.from(candidateSet, c => getElementSignature(c.ActiveElement));
8836
+ const candidateElementIds = Array.from(candidateSet, c => getElementSignature(c.element));
8598
8837
  if (candidateElementIds.length === 0) ; else {
8599
8838
  debug$1(`initRouteObserver ${elementId}, child candidates:
8600
8839
  - ${candidateElementIds.join("\n - ")}`);
@@ -8610,19 +8849,26 @@ const initRouteObserver = ({
8610
8849
  elementFromProps: element
8611
8850
  };
8612
8851
  const findActiveChildInfo = () => {
8613
- let fallbackInfo = null;
8614
8852
  for (const candidate of candidateSet) {
8615
8853
  if (candidate.route?.active) {
8616
8854
  return candidate;
8617
8855
  }
8618
- // fallback without route can match when no other route matches.
8619
- // This is useful solely for "catch all" fallback used on the <Routes>
8620
- // otherwise a fallback would always match and make the parent route always active
8621
- if (candidate.fallback && !candidate.route.routeFromProps) {
8622
- 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;
8623
8866
  }
8624
8867
  }
8625
- return fallbackInfo;
8868
+ if (fallbackCandidate) {
8869
+ return fallbackCandidate;
8870
+ }
8871
+ return null;
8626
8872
  };
8627
8873
  const getActiveInfo = route ? () => {
8628
8874
  if (!route.active) {
@@ -8636,8 +8882,8 @@ const initRouteObserver = ({
8636
8882
  return activeChildInfo;
8637
8883
  }
8638
8884
  return {
8639
- ActiveElement: null,
8640
8885
  route,
8886
+ element: null,
8641
8887
  meta
8642
8888
  };
8643
8889
  } : () => {
@@ -8658,6 +8904,13 @@ const initRouteObserver = ({
8658
8904
  const Element = element;
8659
8905
  element = jsx(Element, {});
8660
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
+ }
8661
8914
  return jsx(RouteInfoContext.Provider, {
8662
8915
  value: activeRouteInfo,
8663
8916
  children: jsx(SlotContext.Provider, {
@@ -8672,11 +8925,13 @@ const initRouteObserver = ({
8672
8925
  if (newActiveInfo) {
8673
8926
  compositeRoute.active = true;
8674
8927
  activeRouteInfoSignal.value = newActiveInfo;
8675
- SlotActiveElementSignal.value = newActiveInfo.ActiveElement;
8928
+ SlotActiveElementSignal.value = newActiveInfo.element;
8676
8929
  onActiveInfoChange({
8677
- ActiveElement,
8678
- SlotActiveElement: newActiveInfo.ActiveElement,
8679
8930
  route: newActiveInfo.route,
8931
+ ActiveElement,
8932
+ SlotActiveElement: newActiveInfo.element,
8933
+ index: newActiveInfo.index,
8934
+ fallback: newActiveInfo.fallback,
8680
8935
  meta: newActiveInfo.meta
8681
8936
  });
8682
8937
  } else {
@@ -8697,7 +8952,13 @@ const initRouteObserver = ({
8697
8952
  addTeardown(candidate.route.subscribeStatus(onChange));
8698
8953
  }
8699
8954
  if (registerChildRouteFromContext) {
8700
- registerChildRouteFromContext(ActiveElement, compositeRoute, fallback, meta);
8955
+ registerChildRouteFromContext({
8956
+ route: compositeRoute,
8957
+ element: ActiveElement,
8958
+ index,
8959
+ fallback,
8960
+ meta
8961
+ });
8701
8962
  }
8702
8963
  updateActiveInfo();
8703
8964
  return () => {
@@ -13731,6 +13992,10 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
13731
13992
  .navi_text {
13732
13993
  position: relative;
13733
13994
  color: inherit;
13995
+
13996
+ &[data-has-absolute-child] {
13997
+ display: inline-block;
13998
+ }
13734
13999
  }
13735
14000
 
13736
14001
  .navi_text_overflow {
@@ -13754,6 +14019,53 @@ installImportMetaCss(import.meta);import.meta.css = /* css */`
13754
14019
 
13755
14020
  .navi_custom_space {
13756
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
+ }
13757
14069
  `;
13758
14070
  const REGULAR_SPACE = jsx("span", {
13759
14071
  "data-navi-space": "",
@@ -13923,14 +14235,65 @@ const TextWithSelectRange = ({
13923
14235
  };
13924
14236
  const TextBasic = ({
13925
14237
  spacing = " ",
14238
+ boldTransition,
14239
+ boldStable,
14240
+ preventBoldLayoutShift = boldTransition,
13926
14241
  children,
13927
14242
  ...rest
13928
14243
  }) => {
13929
- return jsx(Box, {
13930
- as: "span",
13931
- 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,
13932
14251
  ...rest,
13933
- 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
13934
14297
  });
13935
14298
  };
13936
14299
 
@@ -16399,14 +16762,15 @@ const RouteLink = ({
16399
16762
  }
16400
16763
  const routeStatus = useRouteStatus(route);
16401
16764
  const url = route.buildUrl(routeParams);
16402
- const routeIsActive = routeStatus.active;
16765
+ const active = routeStatus.active;
16766
+ const paramsAreMatching = route.matchesParams(routeParams);
16403
16767
  return jsx(Link, {
16404
16768
  ...rest,
16405
16769
  href: url,
16406
16770
  pseudoState: {
16407
- ":-navi-href-current": routeIsActive
16771
+ ":-navi-href-current": active && paramsAreMatching
16408
16772
  },
16409
- children: children
16773
+ children: children || route.buildRelativeUrl(routeParams)
16410
16774
  });
16411
16775
  };
16412
16776
 
@@ -16428,8 +16792,9 @@ import.meta.css = /* css */`
16428
16792
  --tab-color: inherit;
16429
16793
  --tab-color-hover: #010409;
16430
16794
  --tab-color-selected: inherit;
16431
- --tab-marker-height: 2px;
16432
- --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);
16433
16798
  }
16434
16799
  }
16435
16800
 
@@ -16438,100 +16803,175 @@ import.meta.css = /* css */`
16438
16803
  line-height: 2em;
16439
16804
  overflow-x: auto;
16440
16805
  overflow-y: hidden;
16441
- }
16442
- .navi_tablist > ul {
16443
- display: flex;
16444
- width: 100%;
16445
- margin: 0;
16446
- padding: 0;
16447
- align-items: center;
16448
- gap: 0.5rem;
16449
- list-style: none;
16450
- background: var(--tablist-background);
16451
- border-radius: var(--tablist-border-radius);
16452
- }
16453
- .navi_tablist > ul > li {
16454
- position: relative;
16455
- display: inline-flex;
16456
- }
16457
-
16458
- .navi_tab {
16459
- --x-tab-background: var(--tab-background);
16460
- --x-tab-color: var(--tab-color);
16461
16806
 
16462
- display: flex;
16463
- flex-direction: column;
16464
- white-space: nowrap;
16465
- border-radius: var(--tab-border-radius);
16466
-
16467
- .navi_tab_content {
16468
- display: flex;
16469
- color: var(--x-tab-color);
16470
- background: var(--x-tab-background);
16471
- border-radius: inherit;
16472
- transition: background 0.12s ease-out;
16473
-
16474
- .navi_link {
16475
- flex-grow: 1;
16476
- text-align: center;
16477
- border-radius: inherit;
16807
+ &[data-tab-indicator-position="start"] {
16808
+ .navi_tab {
16809
+ margin-top: var(--tab-indicator-spacing);
16478
16810
  }
16479
16811
  }
16480
- /* Hidden bold clone to reserve space for bold width without affecting height */
16481
- .navi_tab_content_bold_clone {
16482
- display: block; /* in-flow so it contributes to width */
16483
- height: 0; /* zero height so it doesn't change layout height */
16484
- font-weight: 600; /* force bold to compute max width */
16485
- visibility: hidden; /* not visible */
16486
- pointer-events: none; /* inert */
16487
- overflow: hidden; /* avoid any accidental height */
16812
+ &[data-tab-indicator-position="end"] {
16813
+ .navi_tab {
16814
+ margin-bottom: var(--tab-indicator-spacing);
16815
+ }
16488
16816
  }
16489
- .navi_tab_selected_marker {
16490
- z-index: 1;
16817
+
16818
+ > ul {
16491
16819
  display: flex;
16492
16820
  width: 100%;
16493
- height: var(--tab-marker-height);
16494
- margin-top: 5px;
16495
- background: transparent;
16496
- border-radius: 0.1px;
16497
- }
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);
16498
16828
 
16499
- /* Interactive */
16500
- &[data-interactive] {
16501
- cursor: pointer;
16502
- }
16503
- /* Hover */
16504
- &:hover {
16505
- --x-tab-background: var(--tab-background-hover);
16506
- --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
+ }
16507
16896
  }
16508
- /* Selected */
16509
- &[data-selected] {
16510
- --x-tab-background: var(--tab-background-selected);
16511
- --x-tab-color: var(--tab-color-selected);
16512
16897
 
16513
- .navi_tab_content {
16514
- 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
+ }
16515
16927
  }
16516
- .navi_tab_selected_marker {
16517
- 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
+ }
16518
16939
  }
16519
- }
16520
- }
16521
16940
 
16522
- .navi_tablist[data-expand] {
16523
- .navi_tab {
16524
- flex: 1;
16525
- 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
+ }
16526
16959
  }
16527
16960
 
16528
- .navi_tab_content {
16529
- width: 100%;
16530
- 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
+ }
16531
16970
  }
16532
16971
  }
16533
16972
  `;
16534
- const TabListUnderlinerContext = createContext();
16973
+ const TabListIndicatorContext = createContext();
16974
+ const TabListAlignXContext = createContext();
16535
16975
  const TabListStyleCSSVars = {
16536
16976
  borderRadius: "--tablist-border-radius",
16537
16977
  background: "--tablist-background"
@@ -16539,7 +16979,9 @@ const TabListStyleCSSVars = {
16539
16979
  const TabList = ({
16540
16980
  children,
16541
16981
  spacing,
16542
- underline,
16982
+ vertical,
16983
+ indicator = vertical ? "start" : "end",
16984
+ alignX,
16543
16985
  expand,
16544
16986
  expandX,
16545
16987
  paddingX,
@@ -16547,11 +16989,14 @@ const TabList = ({
16547
16989
  padding,
16548
16990
  ...props
16549
16991
  }) => {
16992
+ children = toChildArray(children);
16550
16993
  return jsx(Box, {
16551
16994
  as: "nav",
16552
16995
  baseClassName: "navi_tablist",
16553
16996
  role: "tablist",
16997
+ "data-tab-indicator-position": indicator === "start" || indicator === "end" ? indicator : undefined,
16554
16998
  "data-expand": expand || expandX ? "" : undefined,
16999
+ "data-vertical": vertical ? "" : undefined,
16555
17000
  expand: expand,
16556
17001
  expandX: expandX,
16557
17002
  ...props,
@@ -16564,16 +17009,19 @@ const TabList = ({
16564
17009
  paddingY: paddingY,
16565
17010
  padding: padding,
16566
17011
  spacing: spacing,
16567
- children: jsx(TabListUnderlinerContext.Provider, {
16568
- value: underline,
16569
- children: children.map(child => {
16570
- return jsx(Box, {
16571
- as: "li",
16572
- column: true,
16573
- expandX: expandX,
16574
- expand: expand,
16575
- children: child
16576
- }, 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
+ })
16577
17025
  })
16578
17026
  })
16579
17027
  })
@@ -16592,7 +17040,7 @@ const TAB_STYLE_CSS_VARS = {
16592
17040
  }
16593
17041
  };
16594
17042
  const TAB_PSEUDO_CLASSES = [":hover", ":-navi-selected"];
16595
- const TAB_PSEUDO_ELEMENTS = ["::-navi-marker"];
17043
+ const TAB_PSEUDO_ELEMENTS = ["::-navi-indicator"];
16596
17044
  const Tab = props => {
16597
17045
  if (props.route) {
16598
17046
  return jsx(TabRoute, {
@@ -16603,8 +17051,10 @@ const Tab = props => {
16603
17051
  ...props
16604
17052
  });
16605
17053
  };
17054
+ TabList.Tab = Tab;
16606
17055
  const TabRoute = ({
16607
17056
  route,
17057
+ routeParams,
16608
17058
  children,
16609
17059
  paddingX,
16610
17060
  padding,
@@ -16614,15 +17064,17 @@ const TabRoute = ({
16614
17064
  const {
16615
17065
  active
16616
17066
  } = useRouteStatus(route);
17067
+ const paramsAreMatching = route.matchesParams(routeParams);
17068
+ const selected = active && paramsAreMatching;
16617
17069
  return jsx(TabBasic, {
16618
- selected: active,
17070
+ selected: selected,
16619
17071
  paddingX: "0",
16620
17072
  ...props,
16621
17073
  children: jsx(RouteLink, {
16622
17074
  route: route,
17075
+ routeParams: routeParams,
16623
17076
  expand: true,
16624
17077
  discrete: true,
16625
- align: "center",
16626
17078
  paddingX: paddingX,
16627
17079
  padding: padding,
16628
17080
  paddingY: paddingY,
@@ -16633,18 +17085,17 @@ const TabRoute = ({
16633
17085
  const TabBasic = ({
16634
17086
  children,
16635
17087
  selected,
16636
- padding,
16637
- paddingX = "s",
16638
- paddingY,
16639
17088
  onClick,
16640
17089
  ...props
16641
17090
  }) => {
16642
- const tabListUnderline = useContext(TabListUnderlinerContext);
17091
+ const tabListIndicator = useContext(TabListIndicatorContext);
17092
+ const tabListAlignX = useContext(TabListAlignXContext);
16643
17093
  return jsxs(Box, {
16644
17094
  role: "tab",
16645
17095
  "aria-selected": selected ? "true" : "false",
16646
17096
  "data-interactive": onClick ? "" : undefined,
16647
- onClick: onClick
17097
+ onClick: onClick,
17098
+ paddingX: "s"
16648
17099
  // Style system
16649
17100
  ,
16650
17101
  baseClassName: "navi_tab",
@@ -16654,19 +17105,18 @@ const TabBasic = ({
16654
17105
  basePseudoState: {
16655
17106
  ":-navi-selected": selected
16656
17107
  },
17108
+ selfAlignX: tabListAlignX,
17109
+ "data-align-x": tabListAlignX,
16657
17110
  ...props,
16658
- children: [jsx(Box, {
16659
- className: "navi_tab_content",
16660
- paddingX: paddingX,
16661
- paddingY: paddingY,
16662
- padding: padding,
16663
- children: children
16664
- }), jsx("div", {
16665
- className: "navi_tab_content_bold_clone",
16666
- "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
+ ,
16667
17119
  children: children
16668
- }), tabListUnderline && jsx("span", {
16669
- className: "navi_tab_selected_marker"
16670
17120
  })]
16671
17121
  });
16672
17122
  };
@@ -23787,5 +24237,5 @@ const UserSvg = () => jsx("svg", {
23787
24237
  })
23788
24238
  });
23789
24239
 
23790
- 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 };
23791
24241
  //# sourceMappingURL=jsenv_navi.js.map