OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
 
master @ 82 LINES
 
[ HISTORY ]  [ UP ]
 

---
// Dropdown menu: [ LABEL ↓ ] trigger over a framed popover of actions.
// ↓ instead of ? - it's the arrow the frame font ships (see Select).
import Pre from "../Pre/Pre.astro";
import { G, pad, rule, checkGrid } from "../../lib/ascii";
import { menuCells, type Props } from "./Menu";
import "./Menu.scss";

const { label, items, cells } = Astro.props as Props;
const inner = cells ?? menuCells(items);
const ctx = `Menu(${label})`;
checkGrid(items.map((it) => it.label).join("\n"), inner, ctx);
---
<span class="menu">
  <button type="button" class="menu-trigger" aria-haspopup="menu" aria-expanded="f
alse">[ {label} ↓ ]</button>
  <div class="menu-pop" role="menu" hidden>
    <Pre>{G.TL}{rule(inner + 2)}{G.TR}</Pre>
    {items.map((it) => (
      <Pre>{G.V}{" "}{it.href
        ? <a class="menu-item" role="menuitem" href={it.href}>{pad(it.label, inner
)}</a>
        : <button type="button" class="menu-item" role="menuitem">{pad(it.label, i
nner)}</button>
      }{" "}{G.V}</Pre>
    ))}
    <Pre>{G.BL}{rule(inner + 2)}{G.BR}</Pre>
  </div>
</span>

<script>
  function close(menu: HTMLElement) {
    menu.querySelector<HTMLElement>(".menu-pop")!.hidden = true;
    menu.querySelector(".menu-trigger")!.setAttribute("aria-expanded", "false");
  }

  function closeAll(except?: HTMLElement) {
    document.querySelectorAll<HTMLElement>(".menu").forEach((m) => {
      if (m !== except) close(m);
    });
  }

  document.addEventListener("click", (e) => {
    const target = e.target as HTMLElement;
    const trigger = target.closest<HTMLElement>(".menu-trigger");
    if (trigger) {
      const menu = trigger.closest<HTMLElement>(".menu")!;
      const pop = menu.querySelector<HTMLElement>(".menu-pop")!;
      closeAll(menu);
      pop.hidden = !pop.hidden;
      trigger.setAttribute("aria-expanded", String(!pop.hidden));
      if (!pop.hidden) pop.querySelector<HTMLElement>(".menu-item")?.focus();
      return;
    }
    if (target.closest(".menu-item")) {
      const menu = target.closest<HTMLElement>(".menu");
      if (menu) close(menu);
      return;
    }
    closeAll();
  });

  document.addEventListener("keydown", (e) => {
    const menu = (e.target as HTMLElement).closest?.<HTMLElement>(".menu");
    if (!menu) return;
    const pop = menu.querySelector<HTMLElement>(".menu-pop")!;
    if (pop.hidden) return;

    if (e.key === "Escape") {
      e.preventDefault();
      close(menu);
      menu.querySelector<HTMLElement>(".menu-trigger")?.focus();
      return;
    }
    if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
    e.preventDefault();
    const menuItems = Array.from(pop.querySelectorAll<HTMLElement>(".menu-item"));
    const idx = menuItems.indexOf(document.activeElement as HTMLElement);
    const next = e.key === "ArrowDown"
      ? Math.min(menuItems.length - 1, idx + 1)
      : Math.max(0, idx - 1);
    menuItems[next]?.focus();
  });
</script>

---
// Dropdown menu: [ LABEL ↓ ] trigger over a
 framed popover of actions.
// ↓ instead of ? - it's the arrow the frame
 font ships (see Select).
import Pre from "../Pre/Pre.astro";
import { G, pad, rule, checkGrid } from "../
../lib/ascii";
import { menuCells, type Props } from "./Men
u";
import "./Menu.scss";

const { label, items, cells } = Astro.props 
as Props;
const inner = cells ?? menuCells(items);
const ctx = `Menu(${label})`;
checkGrid(items.map((it) => it.label).join("
\n"), inner, ctx);
---
<span class="menu">
  <button type="button" class="menu-trigger"
 aria-haspopup="menu" aria-expanded="false">
[ {label} ↓ ]</button>
  <div class="menu-pop" role="menu" hidden>
    <Pre>{G.TL}{rule(inner + 2)}{G.TR}</Pre>
    {items.map((it) => (
      <Pre>{G.V}{" "}{it.href
        ? <a class="menu-item" role="menuite
m" href={it.href}>{pad(it.label, inner)}</a>
        : <button type="button" class="menu-
item" role="menuitem">{pad(it.label, inner)}
</button>
      }{" "}{G.V}</Pre>
    ))}
    <Pre>{G.BL}{rule(inner + 2)}{G.BR}</Pre>
  </div>
</span>

<script>
  function close(menu: HTMLElement) {
    menu.querySelector<HTMLElement>(".menu-p
op")!.hidden = true;
    menu.querySelector(".menu-trigger")!.set
Attribute("aria-expanded", "false");
  }

  function closeAll(except?: HTMLElement) {
    document.querySelectorAll<HTMLElement>("
.menu").forEach((m) => {
      if (m !== except) close(m);
    });
  }

  document.addEventListener("click", (e) => 
{
    const target = e.target as HTMLElement;
    const trigger = target.closest<HTMLEleme
nt>(".menu-trigger");
    if (trigger) {
      const menu = trigger.closest<HTMLEleme
nt>(".menu")!;
      const pop = menu.querySelector<HTMLEle
ment>(".menu-pop")!;
      closeAll(menu);
      pop.hidden = !pop.hidden;
      trigger.setAttribute("aria-expanded", 
String(!pop.hidden));
      if (!pop.hidden) pop.querySelector<HTM
LElement>(".menu-item")?.focus();
      return;
    }
    if (target.closest(".menu-item")) {
      const menu = target.closest<HTMLElemen
t>(".menu");
      if (menu) close(menu);
      return;
    }
    closeAll();
  });

  document.addEventListener("keydown", (e) =
> {
    const menu = (e.target as HTMLElement).c
losest?.<HTMLElement>(".menu");
    if (!menu) return;
    const pop = menu.querySelector<HTMLEleme
nt>(".menu-pop")!;
    if (pop.hidden) return;

    if (e.key === "Escape") {
      e.preventDefault();
      close(menu);
      menu.querySelector<HTMLElement>(".menu
-trigger")?.focus();
      return;
    }
    if (e.key !== "ArrowDown" && e.key !== "
ArrowUp") return;
    e.preventDefault();
    const menuItems = Array.from(pop.querySe
lectorAll<HTMLElement>(".menu-item"));
    const idx = menuItems.indexOf(document.a
ctiveElement as HTMLElement);
    const next = e.key === "ArrowDown"
      ? Math.min(menuItems.length - 1, idx +
 1)
      : Math.max(0, idx - 1);
    menuItems[next]?.focus();
  });
</script>
 
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET