view design_system/src/components/overlays.js @ 261:b401627fc49e

Add JRPG mock flows and interactive previews Add scripted mock SSE commands, custom event forwarding, animated chat turns, full-height message navigation, and a cyberpunk resume dossier. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <mrjunejune@users.noreply.github.com>
date Wed, 05 Aug 2026 20:38:32 -0700
parents 2b6e732087ff
children
line wrap: on
line source

let nextOverlayId = 1;

function uniqueId(prefix) {
  let id;
  do id = `${prefix}-${nextOverlayId++}`;
  while (document.getElementById(id));
  return id;
}

function rememberAttribute(state, element, name) {
  let attributes = state.get(element);
  if (!attributes) {
    attributes = new Map();
    state.set(element, attributes);
  }
  if (!attributes.has(name)) {
    attributes.set(name, element.getAttribute(name));
  }
}

function setManagedAttribute(state, element, name, value) {
  rememberAttribute(state, element, name);
  if (value === null) element.removeAttribute(name);
  else element.setAttribute(name, value);
}

function restoreAttributes(state) {
  for (const [element, attributes] of state) {
    for (const [name, value] of attributes) {
      if (value === null) element.removeAttribute(name);
      else element.setAttribute(name, value);
    }
  }
  state.clear();
}

function resetManagedAttribute(state, element, name) {
  const attributes = state.get(element);
  if (!attributes?.has(name)) return;
  const value = attributes.get(name);
  if (value === null) element.removeAttribute(name);
  else element.setAttribute(name, value);
}

function ensureId(state, element, prefix) {
  const existing = element.id && document.getElementById(element.id);
  if (!element.id || (existing && existing !== element)) {
    setManagedAttribute(state, element, "id", uniqueId(prefix));
  }
  return element.id;
}

function eventElement(event) {
  return event.target instanceof Element ? event.target : null;
}

const OVERLAY_COMPONENTS = [
  "zen-dialog",
  "zen-alert-dialog",
  "zen-sheet",
  "zen-drawer",
  "zen-popover",
  "zen-hover-card",
  "zen-tooltip",
].join(",");

function ownedElement(host, selector) {
  return [...host.querySelectorAll(selector)].find(
    element => element.closest(OVERLAY_COMPONENTS) === host,
  ) || null;
}

/**
 * Wires a native modal dialog to explicit trigger and close controls.
 *
 * @extends {HTMLElement}
 */
export class ZenDialog extends HTMLElement {
  static dialogRole = null;

  constructor() {
    super();
    this._managed = new Map();
    this._open = false;
    this._openedByComponent = false;
    this._onTriggerClick = event => {
      event.preventDefault();
      this.open();
    };
    this._onDialogClick = event => {
      const control = eventElement(event)?.closest("[data-zen-close]");
      if (!control || !this._dialog?.contains(control)) return;
      const value = control.hasAttribute("value")
        ? control.getAttribute("value")
        : undefined;
      this.close(value);
    };
    this._onNativeClose = () => this.syncOpen(false);
  }

  connectedCallback() {
    this._observer ||= new MutationObserver(() => this.sync());
    this._observer.observe(this, { childList: true, subtree: true });
    this.sync();
  }

  sync() {
    const trigger = ownedElement(this, "button[data-zen-trigger]");
    const dialog = ownedElement(this, "dialog");
    if (trigger === this._trigger && dialog === this._dialog) return;
    if (this._openedByComponent) this.close();
    this.unbind();
    this._trigger = trigger;
    this._dialog = dialog;
    if (!this._trigger || !this._dialog) return;

    const id = ensureId(this._managed, this._dialog, this.localName);
    setManagedAttribute(
      this._managed,
      this._trigger,
      "aria-haspopup",
      "dialog",
    );
    setManagedAttribute(this._managed, this._trigger, "aria-controls", id);
    if (this.constructor.dialogRole) {
      setManagedAttribute(
        this._managed,
        this._dialog,
        "role",
        this.constructor.dialogRole,
      );
    }
    this._trigger.addEventListener("click", this._onTriggerClick);
    this._dialog.addEventListener("click", this._onDialogClick);
    this._dialog.addEventListener("close", this._onNativeClose);
    this.syncOpen(this._dialog.open);
  }

  unbind() {
    this._trigger?.removeEventListener("click", this._onTriggerClick);
    this._dialog?.removeEventListener("click", this._onDialogClick);
    this._dialog?.removeEventListener("close", this._onNativeClose);
    restoreAttributes(this._managed);
    this._trigger = null;
    this._dialog = null;
    this._open = false;
    this._openedByComponent = false;
  }

  disconnectedCallback() {
    this._observer?.disconnect();
    if (this._openedByComponent) this.close();
    this.unbind();
  }

  open() {
    if (!this._dialog || this._dialog.open || this._open) return;
    try {
      if (typeof this._dialog.showModal === "function") {
        this._dialog.showModal();
      } else {
        setManagedAttribute(this._managed, this._dialog, "open", "");
      }
      this._openedByComponent = true;
      this.syncOpen(true);
    } catch {
      this.syncOpen(Boolean(this._dialog.open));
    }
  }

  close(returnValue) {
    if (!this._dialog || (!this._dialog.open && !this._open)) return;
    if (typeof this._dialog.close === "function") {
      if (returnValue === undefined) this._dialog.close();
      else this._dialog.close(returnValue);
    } else {
      setManagedAttribute(this._managed, this._dialog, "open", null);
    }
    this.syncOpen(false);
  }

  syncOpen(open) {
    this._open = open;
    if (!open) this._openedByComponent = false;
    if (this._trigger) {
      setManagedAttribute(
        this._managed,
        this._trigger,
        "aria-expanded",
        String(open),
      );
    }
  }
}

/**
 * Presents a native modal with alert-dialog semantics.
 *
 * @extends {ZenDialog}
 */
export class ZenAlertDialog extends ZenDialog {
  static dialogRole = "alertdialog";
}

/**
 * Presents a native dialog as an edge-aligned sheet.
 *
 * @extends {ZenDialog}
 */
export class ZenSheet extends ZenDialog {}

/**
 * Presents a native dialog as a drawer.
 *
 * @extends {ZenDialog}
 */
export class ZenDrawer extends ZenDialog {}

function isPopoverOpen(element) {
  try {
    return element.matches(":popover-open");
  } catch {
    return false;
  }
}

/**
 * Connects a trigger to a native popover with an accessible fallback.
 *
 * @extends {HTMLElement}
 */
export class ZenPopover extends HTMLElement {
  constructor() {
    super();
    this._managed = new Map();
    this._open = false;
    this._onClick = event => {
      const target = eventElement(event);
      if (!target) return;
      if (this._trigger?.contains(target)) {
        event.preventDefault();
        this.toggle();
      } else if (this._content?.contains(target) &&
                 target.closest("[data-zen-close]")) {
        this.hide();
      }
    };
    this._onToggle = event => {
      this.syncOpen(event.newState
        ? event.newState === "open"
        : isPopoverOpen(this._content));
    };
    this._onDocumentPointerDown = event => {
      if (!this._native && this._open && !this.contains(event.target)) {
        this.hide();
      }
    };
    this._onDocumentKeyDown = event => {
      if (!this._native && this._open && event.key === "Escape") {
        this.hide();
        this._trigger?.focus();
      }
    };
  }

  connectedCallback() {
    this.addEventListener("click", this._onClick);
    document.addEventListener("pointerdown", this._onDocumentPointerDown);
    document.addEventListener("keydown", this._onDocumentKeyDown);
    this._observer ||= new MutationObserver(() => this.sync());
    this._observer.observe(this, { childList: true, subtree: true });
    this.sync();
  }

  sync() {
    const trigger = ownedElement(this, "[data-zen-trigger]");
    const content = ownedElement(
      this,
      "[data-zen-content], [popover]",
    );
    if (trigger === this._trigger && content === this._content) return;
    if (this._open) this.hide();
    this.unbindElements();
    this._trigger = trigger;
    this._content = content;
    if (!this._trigger || !this._content) return;

    const id = ensureId(this._managed, this._content, "zen-popover");
    setManagedAttribute(this._managed, this._trigger, "aria-controls", id);
    this._native = typeof this._content.showPopover === "function" &&
      typeof this._content.hidePopover === "function";
    if (this._native && !this._content.hasAttribute("popover")) {
      setManagedAttribute(this._managed, this._content, "popover", "auto");
    }
    if (this._native && "popoverTargetElement" in this._trigger) {
      this._originalPopoverTarget = this._trigger.popoverTargetElement;
      this._trigger.popoverTargetElement = this._content;
    }
    if (this._native) {
      setManagedAttribute(this._managed, this._content, "hidden", null);
    } else {
      setManagedAttribute(this._managed, this._content, "hidden", "");
    }

    this._content.addEventListener("toggle", this._onToggle);
    this.syncOpen(this._native && isPopoverOpen(this._content));
  }

  unbindElements() {
    this._content?.removeEventListener("toggle", this._onToggle);
    if (this._trigger && "popoverTargetElement" in this._trigger) {
      this._trigger.popoverTargetElement = this._originalPopoverTarget || null;
    }
    restoreAttributes(this._managed);
    this._trigger = null;
    this._content = null;
    this._native = false;
    this._open = false;
    this._originalPopoverTarget = null;
  }

  disconnectedCallback() {
    this._observer?.disconnect();
    this.removeEventListener("click", this._onClick);
    document.removeEventListener("pointerdown", this._onDocumentPointerDown);
    document.removeEventListener("keydown", this._onDocumentKeyDown);
    if (this._open) this.hide();
    this.unbindElements();
  }

  toggle() {
    if (this._open || isPopoverOpen(this._content)) this.hide();
    else this.show();
  }

  show() {
    if (!this._content || this._open) return;
    if (this._native) {
      try {
        this._content.showPopover();
      } catch {
        this._native = false;
        setManagedAttribute(this._managed, this._content, "hidden", null);
      }
    } else {
      setManagedAttribute(this._managed, this._content, "hidden", null);
    }
    this.syncOpen(true);
  }

  hide() {
    if (!this._content || !this._open) return;
    if (this._native) {
      try {
        this._content.hidePopover();
      } catch {
        this._native = false;
        setManagedAttribute(this._managed, this._content, "hidden", "");
      }
    } else {
      setManagedAttribute(this._managed, this._content, "hidden", "");
    }
    this.syncOpen(false);
  }

  syncOpen(open) {
    this._open = open;
    if (this._trigger) {
      setManagedAttribute(
        this._managed,
        this._trigger,
        "aria-expanded",
        String(open),
      );
    }
    if (!this._native && this._content) {
      setManagedAttribute(
        this._managed,
        this._content,
        "hidden",
        open ? null : "",
      );
    }
  }
}

class ZenTimedOverlay extends HTMLElement {
  static delay = 150;
  static tooltip = false;

  constructor() {
    super();
    this._managed = new Map();
    this._timer = null;
    this._open = false;
    this._pointerWithin = false;
    this._focusWithin = false;
    this._onPointerEnter = () => {
      this._pointerWithin = true;
      this.schedule(true);
    };
    this._onPointerLeave = event => {
      if (event.relatedTarget instanceof Node &&
          this.contains(event.relatedTarget)) return;
      this._pointerWithin = false;
      this.schedule(this._focusWithin);
    };
    this._onFocusIn = () => {
      this._focusWithin = true;
      this.schedule(true);
    };
    this._onFocusOut = event => {
      if (event.relatedTarget instanceof Node &&
          this.contains(event.relatedTarget)) return;
      this._focusWithin = false;
      this.schedule(this._pointerWithin);
    };
    this._onKeyDown = event => {
      if (event.key !== "Escape" || !this._open) return;
      clearTimeout(this._timer);
      this._timer = null;
      this.setOpen(false);
    };
  }

  connectedCallback() {
    this.addEventListener("keydown", this._onKeyDown);
    this._observer ||= new MutationObserver(() => this.sync());
    this._observer.observe(this, { childList: true, subtree: true });
    this.sync();
  }

  sync() {
    const trigger = ownedElement(this, "[data-zen-trigger]");
    const content = ownedElement(this, "[data-zen-content]");
    if (trigger === this._trigger && content === this._content) return;
    this.unbindElements();
    this._trigger = trigger;
    this._content = content;
    if (!this._trigger || !this._content) return;

    const id = ensureId(this._managed, this._content, this.localName);
    setManagedAttribute(this._managed, this._content, "hidden", "");
    if (this.constructor.tooltip) {
      setManagedAttribute(this._managed, this._content, "role", "tooltip");
      const ids = new Set(
        (this._trigger.getAttribute("aria-describedby") || "")
          .split(/\s+/)
          .filter(Boolean),
      );
      ids.add(id);
      setManagedAttribute(
        this._managed,
        this._trigger,
        "aria-describedby",
        [...ids].join(" "),
      );
    } else {
      setManagedAttribute(this._managed, this._trigger, "aria-controls", id);
      setManagedAttribute(
        this._managed,
        this._trigger,
        "aria-expanded",
        "false",
      );
    }

    for (const element of [this._trigger, this._content]) {
      element.addEventListener("pointerenter", this._onPointerEnter);
      element.addEventListener("pointerleave", this._onPointerLeave);
      element.addEventListener("focusin", this._onFocusIn);
      element.addEventListener("focusout", this._onFocusOut);
    }
  }

  unbindElements() {
    clearTimeout(this._timer);
    this._timer = null;
    for (const element of [this._trigger, this._content]) {
      element?.removeEventListener("pointerenter", this._onPointerEnter);
      element?.removeEventListener("pointerleave", this._onPointerLeave);
      element?.removeEventListener("focusin", this._onFocusIn);
      element?.removeEventListener("focusout", this._onFocusOut);
    }
    restoreAttributes(this._managed);
    this._trigger = null;
    this._content = null;
    this._open = false;
    this._pointerWithin = false;
    this._focusWithin = false;
  }

  disconnectedCallback() {
    this._observer?.disconnect();
    this.removeEventListener("keydown", this._onKeyDown);
    this.unbindElements();
  }

  schedule(open) {
    clearTimeout(this._timer);
    this._timer = setTimeout(
      () => this.setOpen(open),
      this.constructor.delay,
    );
  }

  setOpen(open) {
    if (!this._content || open === this._open) return;
    this._open = open;
    setManagedAttribute(
      this._managed,
      this._content,
      "hidden",
      open ? null : "",
    );
    if (!this.constructor.tooltip) {
      setManagedAttribute(
        this._managed,
        this._trigger,
        "aria-expanded",
        String(open),
      );
    }
  }
}

/**
 * Shows supplemental interactive content on hover or focus.
 *
 * @extends {ZenTimedOverlay}
 */
export class ZenHoverCard extends ZenTimedOverlay {
  static delay = 150;
}

/**
 * Shows a delayed non-interactive description on hover or focus.
 *
 * @extends {ZenTimedOverlay}
 */
export class ZenTooltip extends ZenTimedOverlay {
  static delay = 350;
  static tooltip = true;
}

const MENU_COMPONENTS = [
  "zen-dropdown-menu",
  "zen-context-menu",
  "zen-menubar",
  "zen-navigation-menu",
].join(",");
const MENU_SELECTOR = "[data-zen-menu], [role='menu'], [data-zen-content]";
const ITEM_SELECTOR = [
  "[role='menuitem']",
  "[role='menuitemcheckbox']",
  "[role='menuitemradio']",
  "[data-zen-menu-item]",
  "a[href]",
  "button",
  "input[type='button']",
  "input[type='submit']",
].join(",");

class ZenMenu extends HTMLElement {
  static contextMenu = false;
  static menubar = false;
  static multiple = false;

  constructor() {
    super();
    this._managed = new Map();
    this._pairs = [];
    this._openPair = null;
    this._onClick = event => {
      const target = eventElement(event);
      if (!target) return;
      const pair = this._pairs.find(({ trigger }) =>
        trigger.contains(target));
      if (pair) {
        event.preventDefault();
        if (this._openPair === pair) this.close(false);
        else this.open(pair, 0, this.constructor.contextMenu ? event : null);
        return;
      }
      const item = target.closest(ITEM_SELECTOR);
      if (this._openPair?.menu.contains(target) &&
          item &&
          !item.hasAttribute("disabled") &&
          item.getAttribute("aria-disabled") !== "true") {
        this.close(true);
      }
    };
    this._onContextMenu = event => {
      if (!this.constructor.contextMenu ||
          this._pairs.some(({ menu }) => menu.contains(event.target))) return;
      event.preventDefault();
      this.open(this._pairs[0], 0, event);
    };
    this._onKeyDown = event => this.handleKeyDown(event);
    this._onDocumentPointerDown = event => {
      if (!this._openPair) return;
      const { trigger, menu } = this._openPair;
      if (!trigger.contains(event.target) && !menu.contains(event.target)) {
        this.close(false);
      }
    };
  }

  connectedCallback() {
    this.setupPairs();
    this.addEventListener("click", this._onClick);
    this.addEventListener("contextmenu", this._onContextMenu);
    this.addEventListener("keydown", this._onKeyDown);
    document.addEventListener("pointerdown", this._onDocumentPointerDown);
    this._observer ||= new MutationObserver(() => this.refreshPairs());
    this._observer.observe(this, { childList: true, subtree: true });
  }

  disconnectedCallback() {
    this._observer?.disconnect();
    this.removeEventListener("click", this._onClick);
    this.removeEventListener("contextmenu", this._onContextMenu);
    this.removeEventListener("keydown", this._onKeyDown);
    document.removeEventListener("pointerdown", this._onDocumentPointerDown);
    this._openPair = null;
    restoreAttributes(this._managed);
    this._pairs = [];
  }

  refreshPairs() {
    if (this._openPair) this.close(false);
    restoreAttributes(this._managed);
    this._pairs = [];
    this.setupPairs();
  }

  ownedElements(selector) {
    return [...this.querySelectorAll(selector)].filter(element =>
      element.closest(MENU_COMPONENTS) === this);
  }

  setupPairs() {
    let triggers = this.ownedElements("[data-zen-trigger]");
    const menus = this.ownedElements(MENU_SELECTOR);
    triggers = triggers.filter(trigger =>
      !menus.some(menu => menu.contains(trigger)));
    if (!this.constructor.multiple) triggers = triggers.slice(0, 1);

    const unused = new Set(menus);
    for (const trigger of triggers) {
      const controlled = trigger.getAttribute("aria-controls");
      let menu = controlled
        ? menus.find(candidate => candidate.id === controlled)
        : null;
      if (!menu) menu = [...unused][0];
      if (!menu) continue;
      unused.delete(menu);

      const triggerId = ensureId(this._managed, trigger, `${this.localName}-trigger`);
      const menuId = ensureId(this._managed, menu, `${this.localName}-menu`);
      setManagedAttribute(this._managed, trigger, "aria-haspopup", "menu");
      setManagedAttribute(this._managed, trigger, "aria-controls", menuId);
      setManagedAttribute(this._managed, trigger, "aria-expanded", "false");
      setManagedAttribute(this._managed, menu, "role", "menu");
      setManagedAttribute(this._managed, menu, "aria-labelledby", triggerId);
      setManagedAttribute(this._managed, menu, "hidden", "");
      if (this.constructor.menubar) {
        setManagedAttribute(this._managed, trigger, "role", "menuitem");
      }
      const pair = { trigger, menu };
      this._pairs.push(pair);
      this.prepareItems(pair);
    }
    if (this.constructor.menubar) {
      setManagedAttribute(this._managed, this, "role", "menubar");
      this.setMenubarTabStop(this._pairs[0]);
    }
  }

  prepareItems(pair) {
    const items = [...pair.menu.querySelectorAll(ITEM_SELECTOR)].filter(item =>
      item.closest(MENU_SELECTOR) === pair.menu &&
      !item.hidden &&
      !item.hasAttribute("disabled") &&
      item.getAttribute("aria-disabled") !== "true");
    for (const item of items) {
      if (!item.hasAttribute("role")) {
        setManagedAttribute(this._managed, item, "role", "menuitem");
      }
      setManagedAttribute(this._managed, item, "tabindex", "-1");
    }
    return items;
  }

  open(pair, itemIndex = 0, pointerEvent = null) {
    if (!pair) return;
    if (this._openPair && this._openPair !== pair) this.close(false);
    this._openPair = pair;
    if (this.constructor.menubar) this.setMenubarTabStop(pair);
    if (pointerEvent) {
      rememberAttribute(this._managed, pair.menu, "style");
      pair.menu.style.position = "fixed";
      pair.menu.style.left = `${Number(pointerEvent.clientX) || 0}px`;
      pair.menu.style.top = `${Number(pointerEvent.clientY) || 0}px`;
    } else {
      resetManagedAttribute(this._managed, pair.menu, "style");
    }
    setManagedAttribute(this._managed, pair.menu, "hidden", null);
    setManagedAttribute(this._managed, pair.trigger, "aria-expanded", "true");
    const items = this.prepareItems(pair);
    if (items.length) {
      const index = itemIndex < 0 ? items.length - 1 : itemIndex;
      setManagedAttribute(this._managed, items[index], "tabindex", "0");
      items[index].focus();
    } else {
      setManagedAttribute(this._managed, pair.menu, "tabindex", "-1");
      pair.menu.focus();
    }
  }

  close(restoreFocus) {
    if (!this._openPair) return;
    const pair = this._openPair;
    this._openPair = null;
    setManagedAttribute(this._managed, pair.menu, "hidden", "");
    setManagedAttribute(this._managed, pair.trigger, "aria-expanded", "false");
    resetManagedAttribute(this._managed, pair.menu, "style");
    if (restoreFocus) pair.trigger.focus();
  }

  moveItem(pair, offset, edge = null) {
    const active = document.activeElement;
    const items = this.prepareItems(pair);
    if (!items.length) return;
    let index = items.indexOf(active);
    if (edge === "first") index = 0;
    else if (edge === "last") index = items.length - 1;
    else index = (Math.max(index, 0) + offset + items.length) % items.length;
    setManagedAttribute(this._managed, items[index], "tabindex", "0");
    items[index].focus();
  }

  moveMenubarTrigger(pair, offset, openMenu) {
    const index = this._pairs.indexOf(pair);
    const next = this._pairs[
      (index + offset + this._pairs.length) % this._pairs.length
    ];
    this.setMenubarTabStop(next);
    if (openMenu) this.open(next);
    else next.trigger.focus();
  }

  setMenubarTabStop(activePair) {
    for (const pair of this._pairs) {
      setManagedAttribute(
        this._managed,
        pair.trigger,
        "tabindex",
        pair === activePair ? "0" : "-1",
      );
    }
  }

  handleKeyDown(event) {
    const target = eventElement(event);
    if (!target) return;
    const triggerPair = this._pairs.find(({ trigger }) =>
      trigger.contains(target));
    if (triggerPair) {
      if (this.constructor.menubar &&
          (event.key === "ArrowRight" || event.key === "ArrowLeft")) {
        event.preventDefault();
        this.moveMenubarTrigger(
          triggerPair,
          event.key === "ArrowRight" ? 1 : -1,
          false,
        );
      } else if (["Enter", " ", "ArrowDown", "ArrowUp"].includes(event.key)) {
        event.preventDefault();
        this.open(triggerPair, event.key === "ArrowUp" ? -1 : 0);
      } else if (event.key === "Escape" && this._openPair) {
        event.preventDefault();
        this.close(true);
      }
      return;
    }

    const pair = this._pairs.find(({ menu }) => menu.contains(target));
    if (!pair || this._openPair !== pair) return;
    if (event.key === "ArrowDown") {
      event.preventDefault();
      this.moveItem(pair, 1);
    } else if (event.key === "ArrowUp") {
      event.preventDefault();
      this.moveItem(pair, -1);
    } else if (event.key === "Home" || event.key === "End") {
      event.preventDefault();
      this.moveItem(pair, 0, event.key === "Home" ? "first" : "last");
    } else if (event.key === "Escape") {
      event.preventDefault();
      this.close(true);
    } else if (event.key === "Tab") {
      this.close(false);
    } else if (this.constructor.menubar &&
               (event.key === "ArrowRight" || event.key === "ArrowLeft")) {
      event.preventDefault();
      this.moveMenubarTrigger(
        pair,
        event.key === "ArrowRight" ? 1 : -1,
        true,
      );
    }
  }
}

/**
 * Provides an accessible action menu opened from a native trigger.
 *
 * @extends {ZenMenu}
 */
export class ZenDropdownMenu extends ZenMenu {}

/**
 * Opens an accessible action menu at the pointer location.
 *
 * @extends {ZenMenu}
 */
export class ZenContextMenu extends ZenMenu {
  static contextMenu = true;
}

/**
 * Coordinates multiple keyboard-accessible application menus.
 *
 * @extends {ZenMenu}
 */
export class ZenMenubar extends ZenMenu {
  static menubar = true;
  static multiple = true;
}

/**
 * Provides keyboard-accessible navigation flyouts.
 *
 * @extends {ZenMenu}
 */
export class ZenNavigationMenu extends ZenMenu {}

const definitions = {
  "zen-dialog": ZenDialog,
  "zen-alert-dialog": ZenAlertDialog,
  "zen-sheet": ZenSheet,
  "zen-drawer": ZenDrawer,
  "zen-popover": ZenPopover,
  "zen-hover-card": ZenHoverCard,
  "zen-tooltip": ZenTooltip,
  "zen-dropdown-menu": ZenDropdownMenu,
  "zen-context-menu": ZenContextMenu,
  "zen-menubar": ZenMenubar,
  "zen-navigation-menu": ZenNavigationMenu,
};

for (const [name, constructor] of Object.entries(definitions)) {
  if (!customElements.get(name)) customElements.define(name, constructor);
}