Fix container actions menu stacking above toolbar
Release rolling / release (push) Successful in 9m47s
Release rolling / release (push) Successful in 9m47s
Use Bootstrap Popper fixed strategy and a high z-index so the containers ⋮ menu is no longer covered by search, headers, or the titlebar.
This commit is contained in:
@@ -7522,10 +7522,10 @@ function buildContainerRow(container) {
|
||||
</div>
|
||||
<div class="dropdown container-actions-more">
|
||||
<button
|
||||
class="btn btn-sm btn-outline-secondary dropdown-toggle p-1"
|
||||
class="btn btn-sm btn-outline-secondary dropdown-toggle p-1 container-actions-more-toggle"
|
||||
type="button"
|
||||
data-bs-toggle="dropdown"
|
||||
data-bs-display="static"
|
||||
data-bs-auto-close="true"
|
||||
aria-expanded="false"
|
||||
title="More actions"
|
||||
>
|
||||
@@ -7579,15 +7579,85 @@ function buildContainerRow(container) {
|
||||
showContainerDetails(container);
|
||||
});
|
||||
}
|
||||
// Popper fixed strategy: menu paints above search/header/titlebar
|
||||
const moreToggle = row.querySelector('.container-actions-more-toggle');
|
||||
if (moreToggle) initContainerActionsDropdown(moreToggle);
|
||||
addActionListeners(row, container);
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap Dropdown with fixed Popper strategy so the ⋮ menu is never covered
|
||||
* by sticky toolbars, search, or the app titlebar (table overflow stacking).
|
||||
* @param {HTMLElement} toggleBtn
|
||||
*/
|
||||
function initContainerActionsDropdown(toggleBtn) {
|
||||
if (!toggleBtn || typeof window.bootstrap === 'undefined' || !window.bootstrap.Dropdown) return null;
|
||||
try {
|
||||
const existing = window.bootstrap.Dropdown.getInstance(toggleBtn);
|
||||
if (existing) return existing;
|
||||
const dd = new window.bootstrap.Dropdown(toggleBtn, {
|
||||
autoClose: true,
|
||||
popperConfig(defaultBsPopperConfig) {
|
||||
const base = typeof defaultBsPopperConfig === 'function'
|
||||
? defaultBsPopperConfig()
|
||||
: { ...(defaultBsPopperConfig || {}) };
|
||||
const modifiers = [...(base.modifiers || [])];
|
||||
// Prefer fixed so we escape overflow:hidden / stacking contexts in the table
|
||||
const withoutOverflow = modifiers.filter((m) => m.name !== 'preventOverflow' && m.name !== 'flip');
|
||||
withoutOverflow.push(
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
boundary: 'viewport',
|
||||
padding: 8,
|
||||
altAxis: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'flip',
|
||||
options: {
|
||||
fallbackPlacements: ['top-end', 'bottom-end', 'top-start', 'bottom-start'],
|
||||
padding: 8,
|
||||
},
|
||||
}
|
||||
);
|
||||
return {
|
||||
...base,
|
||||
strategy: 'fixed',
|
||||
placement: 'bottom-end',
|
||||
modifiers: withoutOverflow,
|
||||
};
|
||||
},
|
||||
});
|
||||
// Ensure open menu is painted above titlebar/search (Popper may set its own z-index)
|
||||
toggleBtn.addEventListener('shown.bs.dropdown', () => {
|
||||
const menu = toggleBtn.nextElementSibling;
|
||||
if (menu?.classList?.contains('dropdown-menu')) {
|
||||
menu.style.zIndex = '2800';
|
||||
}
|
||||
});
|
||||
return dd;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Lazy-init fixed dropdowns if a row was rendered before bootstrap was ready
|
||||
document.addEventListener(
|
||||
'click',
|
||||
(e) => {
|
||||
const btn = e.target?.closest?.('.container-actions-more-toggle');
|
||||
if (btn) initContainerActionsDropdown(btn);
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
/** Close any open container action menus before DOM moves (avoids stuck fixed menus). */
|
||||
function closeContainerActionMenus(root) {
|
||||
const host = root || domCache.containerList || containerList || document;
|
||||
try {
|
||||
host.querySelectorAll?.('.container-actions-more .dropdown-toggle[aria-expanded="true"]').forEach((btn) => {
|
||||
host.querySelectorAll?.('.container-actions-more .dropdown-toggle[aria-expanded="true"], .container-actions-more-toggle[aria-expanded="true"]').forEach((btn) => {
|
||||
try {
|
||||
const inst = window.bootstrap?.Dropdown?.getInstance?.(btn);
|
||||
if (inst) inst.hide();
|
||||
@@ -7603,7 +7673,9 @@ function closeContainerActionMenus(root) {
|
||||
}
|
||||
});
|
||||
// Force-hide any leftover open menus after list refresh / popper desync
|
||||
host.querySelectorAll?.('.container-actions-dropdown-menu.show').forEach((menu) => {
|
||||
// (include body-level leftovers if Popper reparented styles)
|
||||
document.querySelectorAll?.('.container-actions-dropdown-menu.show').forEach((menu) => {
|
||||
if (root && root !== document && !root.contains(menu) && !menu.isConnected) return;
|
||||
menu.classList.remove('show');
|
||||
menu.style.removeProperty('position');
|
||||
menu.style.removeProperty('inset');
|
||||
@@ -7612,6 +7684,7 @@ function closeContainerActionMenus(root) {
|
||||
menu.style.removeProperty('left');
|
||||
menu.style.removeProperty('right');
|
||||
menu.style.removeProperty('bottom');
|
||||
menu.style.removeProperty('margin');
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
|
||||
+35
-33
@@ -2394,35 +2394,36 @@ textarea::placeholder {
|
||||
}
|
||||
|
||||
/*
|
||||
* ⋮ more-actions menu: use static display (no Popper fixed) so menus stay
|
||||
* anchored to the row and never stick to the bottom of the viewport after
|
||||
* list refreshes. Elevate the open row above neighbors; never clip the menu.
|
||||
* ⋮ more-actions menu: Popper strategy "fixed" (see app.js) so the menu escapes
|
||||
* table/overflow stacking and always paints above toolbars, search, titlebar.
|
||||
* z-index must beat #titlebar (1100), #sidebar (1000), activity tray (1200).
|
||||
* Stay below full-screen modals (3000).
|
||||
*/
|
||||
.container-actions-more {
|
||||
position: static;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container-actions-cell .dropdown {
|
||||
position: static;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container-actions .dropdown-menu,
|
||||
.container-actions-dropdown-menu {
|
||||
min-width: 11.5rem;
|
||||
font-size: 13px;
|
||||
z-index: 1080 !important;
|
||||
z-index: 2800 !important;
|
||||
box-shadow: var(--shadow-lg);
|
||||
border: 1px solid var(--border-strong);
|
||||
background: var(--bg-elevated) !important;
|
||||
/* Anchor to the actions cell, open upward when near table bottom */
|
||||
position: absolute !important;
|
||||
inset: auto auto 100% auto !important;
|
||||
top: auto !important;
|
||||
left: auto !important;
|
||||
right: 0 !important;
|
||||
bottom: 100% !important;
|
||||
margin: 0 0 4px 0 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
|
||||
/* Popper fixed menus — do not force absolute/inset (that re-clips under headers) */
|
||||
.container-actions-dropdown-menu.show,
|
||||
.dropdown-menu.container-actions-dropdown-menu.show {
|
||||
z-index: 2800 !important;
|
||||
display: block !important;
|
||||
visibility: visible !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
/* Closed menus must never paint (Popper leftovers / list refresh desync) */
|
||||
@@ -2433,25 +2434,10 @@ textarea::placeholder {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
|
||||
/* Elevate the open row so the menu paints above neighboring table rows */
|
||||
#container-list > tr:has(.container-actions-more.show),
|
||||
#container-list > tr:has(.dropdown-menu.show),
|
||||
#container-list > tr:has(.dropdown.show) {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
#container-list > tr:has(.container-actions-more.show) > td,
|
||||
#container-list > tr:has(.dropdown-menu.show) > td,
|
||||
#container-list > tr:has(.dropdown.show) > td {
|
||||
background: var(--bg-elevated);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Containers table shell: never grow a horizontal scrollbar */
|
||||
#containers-view .table-responsive {
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: visible;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 100%;
|
||||
@@ -2464,18 +2450,27 @@ textarea::placeholder {
|
||||
table-layout: fixed;
|
||||
}
|
||||
|
||||
/* Text truncates by default; actions cell may overflow for menus */
|
||||
/* Text truncates by default; actions cell stays usable */
|
||||
#containers-view #container-list > tr > td {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#containers-view #container-list > tr > td.container-actions-cell {
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Open-row elevation already sets background; keep action cell usable */
|
||||
#containers-view #container-list > tr:has(.dropdown.show) > td.container-actions-cell {
|
||||
overflow: visible;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* Search / filter toolbar must not stack above open action menus */
|
||||
#containers-view .page-header,
|
||||
#containers-view .view-toolbar {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Accordion bodies must stay collapsed when not .show (defensive vs BS CDN lag) */
|
||||
@@ -3235,6 +3230,13 @@ select.bg-dark {
|
||||
background: var(--bg-elevated);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-primary);
|
||||
z-index: 1080;
|
||||
}
|
||||
|
||||
/* Container ⋮ menus always win over toolbars / titlebar (Popper fixed + high stack) */
|
||||
.dropdown-menu.container-actions-dropdown-menu,
|
||||
.container-actions-dropdown-menu.dropdown-menu {
|
||||
z-index: 2800 !important;
|
||||
}
|
||||
|
||||
.dropdown-item {
|
||||
|
||||
Reference in New Issue
Block a user