All files / packages/theme-selector/src navbar.ts

100% Statements 31/31
93.75% Branches 15/16
100% Functions 4/4
100% Lines 30/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90                      17x             12x 12x 12x     12x 12x   12x 7x 7x   6x 6x 6x 4x     1x               12x 1x 1x       12x 4x 4x       12x 12x 5x                 5x 21x   5x 4x   1x                         11x 11x    
// SPDX-License-Identifier: MIT
/**
 * Navbar active state management
 */
 
import { DOM_SELECTORS } from './constants.js';
 
/**
 * Normalizes a path by removing trailing slashes, defaulting to '/' if empty.
 */
function normalizePath(path: string): string {
  return path.replace(/\/$/, '') || '/';
}
 
/**
 * Initializes navbar active state based on current path
 */
export function initNavbar(documentObj: Document): void {
  const currentPath = documentObj.location.pathname;
  const normalizedCurrentPath = normalizePath(currentPath);
  const navbarItems = documentObj.querySelectorAll(DOM_SELECTORS.NAVBAR_ITEM);
 
  // Single pass: find matching item and collect items to deactivate
  let matchingItem: Element | null = null;
  const itemsToDeactivate: Element[] = [];
 
  navbarItems.forEach((item) => {
    const link = item as HTMLAnchorElement;
    if (!link.href) return;
 
    try {
      const normalizedLinkPath = normalizePath(new URL(link.href).pathname);
      if (normalizedCurrentPath === normalizedLinkPath) {
        matchingItem = item;
      } else {
        // Collect non-matching items with valid URLs for deactivation
        itemsToDeactivate.push(item);
      }
    } catch {
      // Ignore invalid URLs
    }
  });
 
  // Apply deactivation after finding match (single DOM batch)
  for (const item of itemsToDeactivate) {
    item.classList.remove('is-active');
    (item as HTMLAnchorElement).removeAttribute('aria-current');
  }
 
  // Set active state for the matching link
  if (matchingItem) {
    (matchingItem as HTMLElement).classList.add('is-active');
    (matchingItem as HTMLAnchorElement).setAttribute('aria-current', 'page');
  }
 
  // Handle Reports dropdown highlighting
  const reportsLink = documentObj.querySelector<HTMLElement>(DOM_SELECTORS.NAV_REPORTS);
  if (reportsLink) {
    const reportPaths = [
      '/coverage',
      '/coverage-python',
      '/coverage-swift',
      '/coverage-ruby',
      '/playwright',
      '/playwright-examples',
      '/lighthouse',
    ];
    const isOnReportsPage = reportPaths.some(
      (path) => normalizedCurrentPath === path || normalizedCurrentPath.startsWith(path + '/')
    );
    if (isOnReportsPage) {
      reportsLink.classList.add('is-active');
    } else {
      reportsLink.classList.remove('is-active');
    }
  }
}
 
// Expose function to global scope for use in HTML
declare global {
  interface Window {
    initNavbar: typeof initNavbar;
  }
}
 
// Only assign to window in browser context
Eif (typeof window !== 'undefined') {
  window.initNavbar = initNavbar;
}