All files / components/Header/Nav NavMenu.svelte

100% Statements 97/97
83.33% Branches 10/12
100% Functions 5/5
100% Lines 97/97

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 91 92 93 94 95 96 97 981x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x  
<script lang="ts">
  import Link from "$lib/components/Link/Link.svelte";
  import chunk from "$lib/util/chunk";
  import classNames from "$lib/util/classNames";
  import { createEventDispatcher } from "svelte";
  import type { NavMenuProps, NavLinkType } from "./types";
  type $$Props = NavMenuProps;
 
  const dispatch = createEventDispatcher();
 
  export let id = "";
  export let current = false;
  export let columns = 1;
  export let children: NavLinkType[] = [];
  export let expanded = false;
 
  $: buttonClassNames = classNames("usa-accordion__button usa-nav__link", current && "usa-current");
 
  // If we want more than one column, evenly divide the nav items into mega menu columns.
  // TODO: We may want to let the CMS determine the position of each item in the future.
  let megaMenuColumns: NavLinkType[][] = [];
  $: {
    if (columns > 1) {
      // Calculate the maximum length of columns (maximum number of items in a column) and provide
      //   to chunk as the size (second) parameter.
      megaMenuColumns = chunk(children, Math.ceil(children.length / columns));
    }
  }
 
  // We can't use on:click since it only triggers if the mousedown and mouseup events occur on the
  //   same target, and on mobile the collapse of an accordion when it loses focus happens on
  //   mousedown, sometimes shifting the elements so that mouseup misses the target.
  // Instead, we'll use both mousedown and keydown to ensure everything functions accessibly on
  //   both desktop and mobile screen sizes.
  const handleKeyDown = (event: KeyboardEvent) =>
    (event.key === "Enter" || event.key === " ") && dispatch("toggle");
  const handleMouseDown = () => dispatch("toggle");
 
  // By default focusout on the container will trigger for all its children, but using 'self'
  //   doesn't resolve the issue since the container element isn't the one with the focus.
  // This workaround will ignore the event if the new focused element is a child of the container.
  // Based on this REPL: https://svelte.dev/repl/4c5dfd34cc634774bd242725f0fc2dab?version=3.46.4
  const handleDropdownFocusLoss = ({
    relatedTarget,
    currentTarget,
  }: FocusEvent & { currentTarget: EventTarget & HTMLDivElement }) => {
    if (relatedTarget instanceof HTMLElement && currentTarget?.contains(relatedTarget)) return;
    dispatch("close");
  };
</script>
 
<!-- TODO: This div is necessary for handling focus loss, but it breaks the styling for a menu
           being marked with an underline as the active / current nav item. -->
<div on:focusout={handleDropdownFocusLoss}>
  <button
    type="button"
    class={buttonClassNames}
    aria-expanded={expanded}
    aria-controls="extended-mega-nav-section-{id}"
    on:keydown={handleKeyDown}
    on:mousedown={handleMouseDown}
  >
    <!-- Extra <span/> element is necessary for expand icons to load. -->
    <span><slot /></span>
  </button>
  {#if columns <= 1}
    <!-- Basic Menu Layout-->
    <ul id="extended-mega-nav-section-{id}" class="usa-nav__submenu" hidden={!expanded}>
      {#each children as { id, link, name } (id)}
        <li class="usa-nav__submenu-item">
          <Link href={link}>{name}</Link>
        </li>
      {/each}
    </ul>
  {:else}
    <!-- Mega Menu Layout -->
    <div
      id="extended-mega-nav-section-{id}"
      class="usa-nav__submenu usa-megamenu"
      hidden={!expanded}
    >
      <div class="grid-row grid-gap-4">
        {#each megaMenuColumns as column, i (i)}
          <div class="usa-col">
            <ul class="usa-nav__submenu-list">
              {#each column as { id, link, name } (id)}
                <li class="usa-nav__submenu-item">
                  <Link href={link}>{name}</Link>
                </li>
              {/each}
            </ul>
          </div>
        {/each}
      </div>
    </div>
  {/if}
</div>