[fix](trx-frontend-http): render the header menus above the page
CI / lint (push) Successful in 2m17s
CI / test (push) Successful in 7m23s
CI / lint (pull_request) Successful in 2m16s
CI / test (pull_request) Successful in 8m13s
CI / frontend (pull_request) Successful in 2m59s
CI / reuse (pull_request) Successful in 3s
CI / frontend (push) Successful in 2m10s
CI / reuse (push) Successful in 3s

Both header dropdowns were laid out inside the bar rather than over the
page.  The navigation menu opened as an 18px sliver positioned above its
own button, and the overflow menu did not appear at all.

Two causes.  The tab strip kept `overflow-x: auto` from when it scrolled,
which clips an absolutely positioned descendant — and the strip is what
the navigation menu anchors to.  The strip no longer scrolls, since the
occasional destinations moved behind More, so the property and the edge
fade that went with it are both gone.

Anchoring in fixed coordinates at open time addresses the general case:
an absolutely positioned menu is clipped by any scrolling ancestor and
trapped inside whatever stacking context its ancestors create, so it can
be squashed inside the bar or painted underneath page content.  Fixed
coordinates answer to the viewport, and the menu flips above its button
near the bottom edge.

Clearing `right` when setting `left` keeps the menus at their natural
width: the stylesheet pins them to the right of their anchor, and leaving
that in place stretched them across the bar — 845px for a four-item list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz
Signed-off-by: Stan Grams <sjg@haxx.space>
This commit was merged in pull request #27.
This commit is contained in:
sjg
2026-08-02 19:18:47 +02:00
co-authored by Claude Opus 5
parent 27f2558193
commit b4912f5879
3 changed files with 58 additions and 20 deletions
@@ -748,6 +748,24 @@ function elementById(id) {
}
}
const overflowOrder = [".operator-layout-picker", ".header-style-pick", "#theme-toggle"];
function anchorMenu(button, menu) {
const rect = button.getBoundingClientRect();
menu.style.position = "fixed";
menu.style.zIndex = "200";
menu.style.top = "0px";
menu.style.bottom = "auto";
menu.style.right = "auto";
const width = menu.offsetWidth || 200;
const height = menu.offsetHeight || 0;
menu.style.left = `${String(Math.round(Math.min(Math.max(8, rect.right - width), window.innerWidth - width - 8)))}px`;
const below = rect.bottom + 8;
if (below + height <= window.innerHeight - 8) {
menu.style.top = `${String(Math.round(below))}px`;
} else {
menu.style.top = "auto";
menu.style.bottom = `${String(Math.round(window.innerHeight - rect.top + 8))}px`;
}
}
function installTopBarOverflow() {
const actions = document.querySelector(".top-bar-actions");
if (!actions || document.getElementById("top-bar-more")) return;
@@ -777,6 +795,7 @@ function elementById(id) {
button.addEventListener("click", () => {
const open = menu.classList.toggle("is-open");
button.setAttribute("aria-expanded", String(open));
if (open) anchorMenu(button, menu);
});
document.addEventListener("click", (event) => {
if (!(event.target instanceof Node) || !wrap.contains(event.target)) closeMenu();
@@ -851,7 +870,10 @@ function elementById(id) {
more.addEventListener("click", () => {
const open = menu.classList.toggle("is-open");
more.setAttribute("aria-expanded", String(open));
if (open) menu.querySelector('[role="menuitem"]')?.focus();
if (open) {
anchorMenu(more, menu);
menu.querySelector('[role="menuitem"]')?.focus();
}
});
document.addEventListener("click", (event) => {
if (!(event.target instanceof Node) || !menu.contains(event.target) && !more.contains(event.target)) closeMore();