@kodaris/krubble-app-components 1.0.13 → 1.0.14

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.
@@ -1403,6 +1403,34 @@
1403
1403
  this.originalXhrOpen = null;
1404
1404
  }
1405
1405
  }
1406
+ /**
1407
+ * Resolves a navigation URL by prepending the base href.
1408
+ *
1409
+ * This method is used for:
1410
+ * 1. Setting href attributes on anchor elements (so Cmd/Ctrl+click opens correct URL in new tab)
1411
+ * 2. Comparing URLs with window.location.pathname in updateActiveNavItem()
1412
+ *
1413
+ * The original item.url is kept intact (without base href) and passed to Angular
1414
+ * via the nav-item-click event. Angular Router expects paths relative to base,
1415
+ * so it handles the base href internally.
1416
+ *
1417
+ * Example with base href "/operations/":
1418
+ * - item.url = "/settings" (used for Angular routing)
1419
+ * - resolveUrl(item) = "/operations/settings" (used for href and URL comparison)
1420
+ */
1421
+ resolveUrl(item) {
1422
+ if (!item.url)
1423
+ return '#';
1424
+ // External URLs - return as-is
1425
+ if (item.external) {
1426
+ return item.url;
1427
+ }
1428
+ // Remove leading slash from url if present, then combine with base
1429
+ const path = item.url.startsWith('/') ? item.url.slice(1) : item.url;
1430
+ const baseHref = document.querySelector('base')?.getAttribute('href') || '/';
1431
+ const base = baseHref.endsWith('/') ? baseHref : (baseHref + '/');
1432
+ return base + path;
1433
+ }
1406
1434
  // =========================================================================
1407
1435
  // Navigation Data
1408
1436
  // =========================================================================
@@ -1536,7 +1564,7 @@
1536
1564
  * Handles click on the nav header (logo/title) to navigate home.
1537
1565
  * Dispatches a nav-item-click event so Angular can intercept and route.
1538
1566
  */
1539
- handleNavHeaderClick(_e) {
1567
+ handleNavHeaderClick() {
1540
1568
  // Clear active state since we're going home
1541
1569
  this.activeNavItemId = null;
1542
1570
  const navEvent = new CustomEvent('nav-item-click', {
@@ -1612,6 +1640,10 @@
1612
1640
  this.toggleNavItem(item.id);
1613
1641
  }
1614
1642
  else {
1643
+ // Allow Ctrl/Cmd+click and middle-click to open in new tab without SPA navigation
1644
+ if (e.ctrlKey || e.metaKey || e.button === 1) {
1645
+ return;
1646
+ }
1615
1647
  // Set active item immediately on click for instant visual feedback
1616
1648
  this.activeNavItemId = item.id;
1617
1649
  const navEvent = new CustomEvent('nav-item-click', {
@@ -1644,7 +1676,8 @@
1644
1676
  let activeItem = null;
1645
1677
  let longestMatch = 0;
1646
1678
  for (const item of allItems) {
1647
- const url = item.url; // We filtered for items with url above
1679
+ // Use resolveUrl because currentPath includes base href
1680
+ const url = this.resolveUrl(item);
1648
1681
  if (currentPath.startsWith(url) && url.length > longestMatch) {
1649
1682
  activeItem = item;
1650
1683
  longestMatch = url.length;
@@ -2219,7 +2252,7 @@
2219
2252
  'nav-item--drop-below': this.navItemDropTargetId === item.id && this.navItemDropPosition === 'below',
2220
2253
  })}
2221
2254
  data-id="${item.id}"
2222
- href=${item.url || '#'}
2255
+ href=${this.resolveUrl(item)}
2223
2256
  target=${item.external ? '_blank' : A}
2224
2257
  @mousedown=${(e) => this.handleNavItemMouseDown(e, item)}
2225
2258
  @click=${(e) => this.handleNavItemClick(e, item)}
@@ -2328,9 +2361,8 @@
2328
2361
  --kr-scaffold-nav-active-bg: #e5e7eb;
2329
2362
  --kr-scaffold-nav-border: rgb(229, 229, 228);
2330
2363
  --kr-scaffold-nav-divider: #e5e7eb;
2331
- --kr-scaffold-nav-search-bg: #f3f4f6;
2332
- --kr-scaffold-nav-search-focus-bg: #ffffff;
2333
- --kr-scaffold-nav-search-focus-border: #d1d5db;
2364
+ --kr-scaffold-nav-search-bg: #ffffff;
2365
+ --kr-scaffold-nav-search-border: #d1d5db;
2334
2366
  }
2335
2367
 
2336
2368
  /* Dark scheme */
@@ -2343,8 +2375,7 @@
2343
2375
  --kr-scaffold-nav-border: transparent;
2344
2376
  --kr-scaffold-nav-divider: rgba(255, 255, 255, 0.06);
2345
2377
  --kr-scaffold-nav-search-bg: rgba(255, 255, 255, 0.15);
2346
- --kr-scaffold-nav-search-focus-bg: rgba(255, 255, 255, 0.2);
2347
- --kr-scaffold-nav-search-focus-border: rgba(255, 255, 255, 0.3);
2378
+ --kr-scaffold-nav-search-border: rgba(255, 255, 255, 0.3);
2348
2379
  }
2349
2380
 
2350
2381
  *,
@@ -2422,15 +2453,10 @@
2422
2453
  padding: 0 12px;
2423
2454
  height: 40px;
2424
2455
  gap: 8px;
2425
- border: 1px solid transparent;
2456
+ border: 1px solid var(--kr-scaffold-nav-search-border);
2426
2457
  transition: all 0.15s ease;
2427
2458
  }
2428
2459
 
2429
- .nav-search__wrapper:focus-within {
2430
- background: var(--kr-scaffold-nav-search-focus-bg);
2431
- border-color: var(--kr-scaffold-nav-search-focus-border);
2432
- }
2433
-
2434
2460
  .nav-search__icon {
2435
2461
  width: 18px;
2436
2462
  height: 18px;
@@ -3539,6 +3565,29 @@
3539
3565
  _handleMenuClick() {
3540
3566
  this.dispatchEvent(new CustomEvent('menu-click', { bubbles: true, composed: true }));
3541
3567
  }
3568
+ /**
3569
+ * Resolves a breadcrumb URL by prepending the base href.
3570
+ *
3571
+ * This method is used for setting href attributes on anchor elements
3572
+ * (so Cmd/Ctrl+click opens correct URL in new tab).
3573
+ *
3574
+ * The original crumb.url is kept intact (without base href) and passed to Angular
3575
+ * via the breadcrumb-click event. Angular Router expects paths relative to base,
3576
+ * so it handles the base href internally.
3577
+ *
3578
+ * Example with base href "/operations/":
3579
+ * - crumb.url = "/settings" (used for Angular routing)
3580
+ * - resolveUrl(crumb) = "/operations/settings" (used for href)
3581
+ */
3582
+ resolveUrl(crumb) {
3583
+ if (!crumb.url)
3584
+ return '#';
3585
+ // Remove leading slash from url if present, then combine with base
3586
+ const path = crumb.url.startsWith('/') ? crumb.url.slice(1) : crumb.url;
3587
+ const baseHref = document.querySelector('base')?.getAttribute('href') || '/';
3588
+ const base = baseHref.endsWith('/') ? baseHref : (baseHref + '/');
3589
+ return base + path;
3590
+ }
3542
3591
  /**
3543
3592
  * Handles click on a breadcrumb link.
3544
3593
  * Dispatches a cancelable event so SPA routers can intercept.
@@ -3549,6 +3598,10 @@
3549
3598
  e.preventDefault();
3550
3599
  return;
3551
3600
  }
3601
+ // Allow Ctrl/Cmd+click and middle-click to open in new tab without SPA navigation
3602
+ if (e.ctrlKey || e.metaKey || e.button === 1) {
3603
+ return;
3604
+ }
3552
3605
  const breadcrumbEvent = new CustomEvent('breadcrumb-click', {
3553
3606
  detail: { breadcrumb: crumb },
3554
3607
  bubbles: true,
@@ -3579,7 +3632,7 @@
3579
3632
  ${index > 0 ? b `<span class="breadcrumb-separator">/</span>` : ''}
3580
3633
  <a
3581
3634
  class=${e$1({ 'breadcrumb': true, 'breadcrumb--current': isCurrent })}
3582
- href=${crumb.url || ''}
3635
+ href=${this.resolveUrl(crumb)}
3583
3636
  @click=${(e) => this._handleBreadcrumbClick(e, crumb, isCurrent)}
3584
3637
  >${crumb.label}</a>
3585
3638
  `;
@@ -3603,6 +3656,7 @@
3603
3656
  align-items: center;
3604
3657
  padding: 0 16px;
3605
3658
  width: 100%;
3659
+ flex-shrink: 0;
3606
3660
  }
3607
3661
 
3608
3662
  .breadcrumbs {