dashboard: convert the per-agent ⋮ context menu to a hive-agent-menu shadow-DOM component
Moves buildAgentMenu's DOM-building body, the menuItem/menuSep/menuLink helpers, agentMenuPost, and the open-dropdown coordination logic out of swarm.js and into a new <hive-agent-menu> autonomous custom element (dashboard/src/agent-menu/), following the same shadow-DOM + one-dir-per- component shape as hive-dialog. swarm.js's buildAgentMenu is now a thin wrapper that constructs the element and sets ._opts before appending it, same convention hive-dialog uses since a custom element created via document.createElement can't take constructor args. The module-level "one dropdown open at a time" singleton (previously a single mutable variable in swarm.js) becomes a tracked Set of open instances inside the component module; each instance closes itself via its own close() method rather than another instance reaching into its shadow internals. The document-level outside-click and Escape listeners move into the component module too, keyed off e.composedPath() instead of e.target.closest() -- shadow-DOM event retargeting means a plain e.target check no longer reliably reaches into a specific instance's shadow tree. closeAllAgentMenus() is exported for swarm.js's buildAgentTree, which still needs to close any open menu before it replaces the container tree DOM. The hover-reveal opacity rule crosses the shadow boundary via a --menu-btn-opacity custom property (custom properties inherit through shadow boundaries): dashboard.css sets it on hover of the light-DOM hive-agent-menu element, and the component sets it directly from JS while its own dropdown is open, since that's component-internal state a CSS selector out in the light DOM can't see. The host element itself takes on the structural role (flex:none, position:relative, ...) the old light-DOM .agent-menu wrapper div played, since its shadow tree's absolute-positioned dropdown needs a positioned ancestor to anchor off of. Verified end to end with a standalone esbuild-bundled test harness run under headless chromium: row layout/flex sizing, hover-reveal opacity, and dropdown positioning all render correctly, and a scripted interaction pass (singleton exclusivity, outside-click close, Escape close, toggle behavior, menu-item click close, and the exported closeAllAgentMenus()) all pass.
This commit is contained in:
parent
ed92883294
commit
f7b19c9d56
4 changed files with 358 additions and 259 deletions
|
|
@ -279,78 +279,19 @@ body.dashboard-shell {
|
|||
}
|
||||
|
||||
/* ── per-agent three-dot context menu ─────────────────────────────────────
|
||||
Positioned after .card-body in the flex row. */
|
||||
.agent-menu {
|
||||
flex: none;
|
||||
position: relative;
|
||||
align-self: flex-start;
|
||||
margin-top: 0.3em;
|
||||
}
|
||||
.agent-menu-btn {
|
||||
display: block;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--subtext0);
|
||||
font-size: 1.1em;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
padding: 0.1em 0.4em;
|
||||
border-radius: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 120ms, background 120ms, color 120ms;
|
||||
}
|
||||
.container-row:hover .agent-menu-btn,
|
||||
.agent-menu.open .agent-menu-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
.agent-menu-btn:hover,
|
||||
.agent-menu-btn:focus-visible {
|
||||
background: color-mix(in srgb, var(--purple) 10%, transparent);
|
||||
color: var(--purple);
|
||||
outline: none;
|
||||
}
|
||||
.agent-menu-btn:focus-visible {
|
||||
outline: 1px solid var(--purple);
|
||||
}
|
||||
.agent-menu-dropdown {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: calc(100% + 2px);
|
||||
z-index: 50;
|
||||
background: var(--bg-elev);
|
||||
border: 1px solid var(--purple-dim);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0.3em 0;
|
||||
min-width: 10em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.agent-menu-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--fg);
|
||||
font-family: inherit;
|
||||
font-size: 0.88em;
|
||||
letter-spacing: 0.04em;
|
||||
text-align: left;
|
||||
padding: 0.4em 0.9em;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.agent-menu-item:hover {
|
||||
background: var(--border);
|
||||
color: var(--purple);
|
||||
}
|
||||
.agent-menu-sep {
|
||||
height: 1px;
|
||||
background: var(--purple-dim);
|
||||
margin: 0.3em 0;
|
||||
padding: 0;
|
||||
Positioned after .card-body in the flex row. The menu itself is the
|
||||
<hive-agent-menu> shadow-DOM custom element (agent-menu/hive-agent-menu.js
|
||||
+ .css) — its own shadow-scoped stylesheet carries the button/dropdown/
|
||||
item/separator rules and the `:host` rules that play the structural
|
||||
role this light-DOM wrapper used to play. The one rule that still has
|
||||
to live out here is the hover reveal: a light-DOM descendant-combinator
|
||||
selector can't reach into the shadow tree, so it's relayed across the
|
||||
shadow boundary via the `--menu-btn-opacity` custom property (custom
|
||||
properties inherit through shadow boundaries) instead — the
|
||||
component's own JS forces the same variable to 1 while its dropdown is
|
||||
open (see hive-agent-menu.js's open()/close()). */
|
||||
.container-row:hover hive-agent-menu {
|
||||
--menu-btn-opacity: 1;
|
||||
}
|
||||
/* Pending state splits queued vs running. */
|
||||
.container-row.pending .actions { opacity: 0.4; pointer-events: none; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue