view design_system/src/components/primitives.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 60a876c4587a
children
line wrap: on
line source

/** @const {!ReadonlyArray<string>} Registered presentation-only elements. */
export const ZEN_PRIMITIVE_ELEMENTS = [
  "zen-aspect-ratio",
  "zen-attachment",
  "zen-avatar",
  "zen-badge",
  "zen-breadcrumb",
  "zen-bubble",
  "zen-box",
  "zen-button-group",
  "zen-chart",
  "zen-direction",
  "zen-empty",
  "zen-input",
  "zen-input-group",
  "zen-item",
  "zen-kbd",
  "zen-label",
  "zen-heading",
  "zen-marker",
  "zen-message",
  "zen-native-select",
  "zen-pagination",
  "zen-progress",
  "zen-scroll-area",
  "zen-separator",
  "zen-skeleton",
  "zen-spinner",
  "zen-table",
  "zen-text",
  "zen-textarea",
  "zen-typography",
];

function parseRatio(value) {
  const [width, height] = String(value || "16/9")
    .split("/")
    .map(Number);
  if (!Number.isFinite(width) || !Number.isFinite(height) ||
      width <= 0 || height <= 0) return "16 / 9";
  return `${width} / ${height}`;
}

/**
 * Applies small semantic enhancements shared by presentation primitives.
 *
 * @extends {HTMLElement}
 */
export class ZenPrimitive extends HTMLElement {
  connectedCallback() {
    this.sync();
  }

  static get observedAttributes() {
    return ["dir", "ratio", "value", "width"];
  }

  attributeChangedCallback() {
    if (this.isConnected) this.sync();
  }

  sync() {
    switch (this.localName) {
      case "zen-aspect-ratio":
        this.style.setProperty(
          "--zenbu-aspect-ratio",
          parseRatio(this.getAttribute("ratio")),
        );
        break;
      case "zen-breadcrumb": {
        const navigation = this.querySelector(":scope > nav");
        if (navigation && !navigation.hasAttribute("aria-label")) {
          navigation.setAttribute("aria-label", "Breadcrumb");
        }
        break;
      }
      case "zen-chart":
        if (!this.hasAttribute("role")) this.setAttribute("role", "img");
        break;
      case "zen-direction":
        if (!this.hasAttribute("dir") && this.hasAttribute("value")) {
          this.dir = this.getAttribute("value");
        }
        break;
      case "zen-pagination": {
        const navigation = this.querySelector(":scope > nav");
        if (navigation && !navigation.hasAttribute("aria-label")) {
          navigation.setAttribute("aria-label", "Pagination");
        }
        break;
      }
      case "zen-native-select": {
        if (!this.querySelector("select")) break;
        let icon = this.querySelector(
          ":scope > zen-icon[data-zen-select-icon]",
        );
        if (!icon) {
          icon = document.createElement("zen-icon");
          icon.dataset.zenSelectIcon = "";
          icon.dataset.zenGenerated = "true";
          icon.setAttribute("name", "chevron-down");
          this.append(icon);
        }
        break;
      }
      case "zen-separator":
        if (!this.querySelector(":scope > hr")) {
          if (!this.hasAttribute("role")) {
            this.setAttribute("role", "separator");
          }
          if (!this.hasAttribute("aria-orientation")) {
            this.setAttribute(
              "aria-orientation",
              this.getAttribute("orientation") === "vertical"
                ? "vertical"
                : "horizontal",
            );
          }
        }
        break;
      case "zen-skeleton":
        if (this.hasAttribute("width")) {
          this.style.setProperty(
            "--zenbu-skeleton-width",
            this.getAttribute("width"),
          );
        } else {
          this.style.removeProperty("--zenbu-skeleton-width");
        }
        if (!this.hasAttribute("role")) this.setAttribute("aria-hidden", "true");
        break;
      case "zen-spinner":
        if (!this.hasAttribute("role")) this.setAttribute("role", "status");
        if (!this.hasAttribute("aria-label")) {
          this.setAttribute("aria-label", "Loading");
        }
        break;
    }
  }
}

for (const name of ZEN_PRIMITIVE_ELEMENTS) {
  if (!customElements.get(name)) {
    customElements.define(name, class extends ZenPrimitive {});
  }
}