/** * dsh-header-menu — browser half. * * The conversation header exposes a utilities region (`.headerUtilities` inside * `.headerActions`) that plugins/packages fill via the * `conversation.session.header.utilities` slot: Session log, Stop All, Resume * Stopped, Resume All, the Git indicator, etc. Several standalone pills eat a * lot of horizontal space. This plugin collapses them behind one `⋯` trigger. * * Strategy — no React fighting: * • We never move or synthetically click the real buttons; their real React * handlers stay intact. We only (a) inject global CSS that restyles the * `.headerUtilities` container into a hidden dropdown panel, and (b) add a * single vanilla trigger button next to it. * • CSS module classes are content-hashed, so we match the stable suffix via * `[class*="_headerUtilities"]` / `[class*="_headerActions"]`. * • Open/close state and the "managed" marks are stored in imperatively-set * `data-*` attributes, which React does not manage and therefore does not * clobber on re-render. A MutationObserver re-applies the enhancement (and * re-adds the trigger) whenever React rebuilds the header. * @module dsh-header-menu/client */ window.__ModuleLoader__.load({ id: "dsh-header-menu", factory: (require) => { var module = { exports: {} }; var exports = module.exports; Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); // ---- CONFIG (edit here) ----------------------------------------------- // Label shown on the collapsed trigger button. const TRIGGER_LABEL = "⋯"; // ⋯ (e.g. "Menu", "Actions ▾") // Close the dropdown after clicking one of its items. const CLOSE_ON_ITEM_CLICK = true; // ----------------------------------------------------------------------- const STYLE_ID = "dsh-header-menu-style"; const STYLE_TEXT = [ // host anchors the absolutely-positioned panel '[data-dsh-hdr-menu="host"]{position:relative}', // the utility row becomes a dropdown panel '[data-dsh-hdr-menu="panel"]{', "position:absolute;top:calc(100% + 8px);right:0;z-index:2147483000;", "margin-left:0 !important;flex-direction:column !important;", "align-items:stretch !important;gap:6px !important;padding:8px;min-width:210px;", "background:var(--dsw-alias-bg-elevated, var(--dsw-specific-sidebar-fill, var(--dsw-alias-bg-base,#1f1f1f)));", "border:1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12));", "border-radius:12px;box-shadow:0 12px 32px rgba(0,0,0,.45)}", // hidden unless the host is marked open '[data-dsh-hdr-menu="host"]:not([data-open="1"]) [data-dsh-hdr-menu="panel"]{display:none !important}', // stack items full width, left aligned '[data-dsh-hdr-menu="panel"] > *{width:100%;box-sizing:border-box;justify-content:flex-start !important}', // known grouped entry (fleet-control) stacks vertically too '[data-dsh-hdr-menu="panel"] .dsh-fleet-control{flex-direction:column;align-items:stretch;gap:6px;width:100%}', '[data-dsh-hdr-menu="panel"] .dsh-fleet-control > *{width:100%;justify-content:flex-start}', // the trigger pill ".dsh-hdr-menu-trigger{border:1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12));", "height:32px;min-width:32px;color:var(--dsw-alias-label-primary);background:transparent;", "border-radius:18px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;", "gap:5px;padding:6px 12px;font-size:15px;line-height:20px;white-space:nowrap;", "font-family:var(--dsw-font-family)}", ".dsh-hdr-menu-trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}", '.dsh-hdr-menu-trigger[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover)}', ].join(""); function installStyle() { if (document.getElementById(STYLE_ID)) return; const style = document.createElement("style"); style.id = STYLE_ID; style.dataset.plugin = "dsh-header-menu"; style.textContent = STYLE_TEXT; (document.head || document.documentElement).appendChild(style); } /** Close every open header menu. */ function closeAll(except) { document.querySelectorAll('[data-dsh-hdr-menu="host"][data-open="1"]').forEach((host) => { if (host === except) return; host.removeAttribute("data-open"); const t = host.querySelector(".dsh-hdr-menu-trigger"); if (t) t.setAttribute("aria-expanded", "false"); }); } /** Build the trigger button for a given host (the .headerActions element). */ function makeTrigger(host) { const btn = document.createElement("button"); btn.type = "button"; btn.className = "dsh-hdr-menu-trigger"; btn.textContent = TRIGGER_LABEL; btn.title = "Session actions"; btn.setAttribute("aria-label", "Session actions menu"); btn.setAttribute("aria-expanded", "false"); btn.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); const open = host.getAttribute("data-open") === "1"; closeAll(host); if (open) { host.removeAttribute("data-open"); btn.setAttribute("aria-expanded", "false"); } else { host.setAttribute("data-open", "1"); btn.setAttribute("aria-expanded", "true"); } }); return btn; } /** Enhance every header-utilities region currently in the DOM (idempotent). */ function enhance() { const panels = document.querySelectorAll('[class*="_headerUtilities"]'); panels.forEach((panel) => { const host = panel.parentElement; if (!host) return; panel.setAttribute("data-dsh-hdr-menu", "panel"); host.setAttribute("data-dsh-hdr-menu", "host"); // close-on-item-click wiring (once) if (CLOSE_ON_ITEM_CLICK && panel.getAttribute("data-dsh-hdr-wired") !== "1") { panel.setAttribute("data-dsh-hdr-wired", "1"); panel.addEventListener("click", (e) => { if (e.target && e.target.closest("button,a,[role='menuitem'],[role='button']")) { // let the real handler run, then close on next tick setTimeout(() => closeAll(), 0); } }); } // ensure exactly one trigger, placed right after the panel if (!host.querySelector(".dsh-hdr-menu-trigger")) { const trigger = makeTrigger(host); if (panel.nextSibling) host.insertBefore(trigger, panel.nextSibling); else host.appendChild(trigger); } }); } let scheduled = false; function scheduleEnhance() { if (scheduled) return; scheduled = true; requestAnimationFrame(() => { scheduled = false; enhance(); }); } /** No client services required. */ const inject = []; function apply(ctx) { const start = () => { installStyle(); enhance(); const observer = new MutationObserver(scheduleEnhance); observer.observe(document.body, { childList: true, subtree: true }); const onDocClick = (e) => { const host = e.target && e.target.closest && e.target.closest('[data-dsh-hdr-menu="host"]'); if (!host) closeAll(); }; const onKey = (e) => { if (e.key === "Escape") closeAll(); }; document.addEventListener("click", onDocClick, true); document.addEventListener("keydown", onKey, true); return () => { observer.disconnect(); document.removeEventListener("click", onDocClick, true); document.removeEventListener("keydown", onKey, true); closeAll(); const style = document.getElementById(STYLE_ID); if (style) style.remove(); document.querySelectorAll(".dsh-hdr-menu-trigger").forEach((n) => n.remove()); document.querySelectorAll('[data-dsh-hdr-menu]').forEach((n) => { n.removeAttribute("data-dsh-hdr-menu"); n.removeAttribute("data-open"); n.removeAttribute("data-dsh-hdr-wired"); }); }; }; if (ctx && typeof ctx.effect === "function") ctx.effect(start, "dsh-header-menu: collapse header utilities"); else start(); } exports.apply = apply; exports.inject = inject; return module.exports; }, });