Fix: Properly position and manage visibility of filters popover

This commit is contained in:
Simon Cordt
2025-09-24 23:11:38 +02:00
parent 1dedd4bebe
commit b941cc03ec
2 changed files with 86 additions and 1 deletions
+1
View File
@@ -111,6 +111,7 @@
.pqueue-history-sentinel{display:flex;align-items:center;justify-content:center;min-height:48px;opacity:.7}
.pqueue-popover{position:fixed;min-width:320px;max-width:420px;max-height:80vh;overflow-y:auto;overflow-x:hidden;padding:0;background:#1a1a1a;border:1px solid rgba(82,82,91,.22);border-radius:14px;box-shadow:0 10px 30px rgba(0,0,0,.35);z-index:100000}
.pqueue-popover__arrow{position:absolute;top:-8px;left:24px;width:16px;height:16px;background:#1a1a1a;border-left:1px solid rgba(82,82,91,.22);border-top:1px solid rgba(82,82,91,.22);transform:rotate(45deg);border-top-left-radius:3px}
.pqueue-popover--above .pqueue-popover__arrow{top:auto;bottom:-8px;transform:rotate(45deg);background:#1a1a1a;border-left:none;border-top:none;border-right:1px solid rgba(82,82,91,.22);border-bottom:1px solid rgba(82,82,91,.22)}
.pqueue-popover__header{position:sticky;top:0;display:flex;align-items:center;gap:.5rem;justify-content:space-between;padding:.7rem .85rem;border-bottom:1px solid rgba(82,82,91,.22);background:#1a1a1a;backdrop-filter:blur(6px);border-top-left-radius:14px;border-top-right-radius:14px}
.pqueue-popover__title{font-weight:600;color:var(--heading-text,#f9fafb)}
.pqueue-popover__hint{font-size:.72rem;color:var(--subtle-text,#9ca3af)}
+85 -1
View File
@@ -1670,14 +1670,98 @@
toggleFiltersPopover(anchor) {
try {
// If open, close and cleanup listeners
if (state.dom.filtersPopover) {
state.dom.filtersPopover.remove();
const pop = state.dom.filtersPopover;
if (state.dom.filtersPopoverListeners) {
const { onDocPointer, onResize, onScroll } = state.dom.filtersPopoverListeners;
if (onDocPointer) document.removeEventListener('pointerdown', onDocPointer, true);
if (onResize) window.removeEventListener('resize', onResize);
if (onScroll) window.removeEventListener('scroll', onScroll, true);
state.dom.filtersPopoverListeners = null;
}
pop.remove();
state.dom.filtersPopover = null;
return;
}
const pop = UI.buildFiltersPopover(anchor);
document.body.appendChild(pop);
state.dom.filtersPopover = pop;
// Positioning function to keep popover within viewport
const position = () => {
try {
const margin = 10;
const rect = anchor.getBoundingClientRect();
const viewportW = window.innerWidth;
const viewportH = window.innerHeight;
const scrollX = window.scrollX;
const scrollY = window.scrollY;
// reset classes and styles affecting size
pop.classList.remove('pqueue-popover--above');
pop.style.maxHeight = '80vh';
const popW = pop.offsetWidth;
const popH = pop.offsetHeight;
const anchorCenter = rect.left + rect.width / 2 + scrollX;
let left = Math.round(anchorCenter - popW / 2);
left = Math.max(scrollX + margin, Math.min(left, scrollX + viewportW - popW - margin));
// Prefer below; flip above if overflowing bottom
let top = rect.bottom + margin + scrollY;
const bottomOverflow = (top + popH) - (scrollY + viewportH - margin);
if (bottomOverflow > 0) {
const aboveTop = rect.top + scrollY - popH - margin;
if (aboveTop >= scrollY + margin) {
top = aboveTop;
pop.classList.add('pqueue-popover--above');
} else {
// Clamp within viewport if neither fits perfectly
top = Math.max(scrollY + margin, Math.min(top, scrollY + viewportH - popH - margin));
}
}
pop.style.left = `${left}px`;
pop.style.top = `${top}px`;
// Position arrow
const arrow = pop.querySelector('.pqueue-popover__arrow');
if (arrow) {
const popLeft = left;
const offset = Math.max(14, Math.min(anchorCenter - popLeft, popW - 14));
arrow.style.left = `${offset}px`;
}
} catch (err) { /* noop */ }
};
// Outside click/tap closes popover
const onDocPointer = (ev) => {
try {
if (!state.dom.filtersPopover) return;
const target = ev.target;
if (state.dom.filtersPopover.contains(target)) return;
if (anchor && (target === anchor || (anchor.contains && anchor.contains(target)))) return;
// Close
document.removeEventListener('pointerdown', onDocPointer, true);
window.removeEventListener('resize', onResize);
window.removeEventListener('scroll', onScroll, true);
state.dom.filtersPopoverListeners = null;
state.dom.filtersPopover.remove();
state.dom.filtersPopover = null;
} catch (err) { /* noop */ }
};
const onResize = () => position();
const onScroll = () => position();
document.addEventListener('pointerdown', onDocPointer, true);
window.addEventListener('resize', onResize);
window.addEventListener('scroll', onScroll, true);
state.dom.filtersPopoverListeners = { onDocPointer, onResize, onScroll };
// Initial position
position();
} catch (err) {
/* noop */
}