fix(webui): portal custom-select popup to body so cards' hover-transform can't break it
Location config dropdowns (Type, Path, etc.) live inside .task-item cards, whose :hover applies transform: translateY(-2px). A transformed element becomes the containing block for position:fixed descendants, so the popup — previously a child of the card — was positioned with viewport coords against the card instead of the viewport (wrong placement) and perturbed layout (content shifted left). Portal the popup to <body> on open and detach on close, so position:fixed is always relative to the viewport regardless of any transformed/overflow ancestor. flip-up styling moves onto the popup element and the topbar's wider popup is carried via a class, since the popup no longer nests in the wrapper. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
9f02d350c5
commit
d75024b22c
@ -884,7 +884,7 @@ select.form-control:focus {
|
||||
animation: customSelectIn 0.12s ease-out;
|
||||
}
|
||||
|
||||
.custom-select.flip-up .custom-select-popup {
|
||||
.custom-select-popup.flip-up {
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
|
||||
@ -893,7 +893,7 @@ select.form-control:focus {
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.custom-select.flip-up .custom-select-popup {
|
||||
.custom-select-popup.flip-up {
|
||||
animation-name: customSelectInUp;
|
||||
}
|
||||
|
||||
|
||||
@ -252,6 +252,6 @@
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.topbar-controls .custom-select-popup {
|
||||
.custom-select-popup-topbar {
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
@ -56,10 +56,19 @@
|
||||
this.popup.className = 'custom-select-popup';
|
||||
this.popup.setAttribute('role', 'listbox');
|
||||
this.popup.hidden = true;
|
||||
// The popup is portaled to <body> on open (below), escaping .topbar-controls,
|
||||
// so carry the topbar's wider-popup styling via a class instead.
|
||||
if (this.select.closest('.topbar-controls')) {
|
||||
this.popup.classList.add('custom-select-popup-topbar');
|
||||
}
|
||||
|
||||
// Move the native select inside the wrapper so it stays in the form.
|
||||
// Move the native select inside the wrapper so it stays in the form. The
|
||||
// popup is deliberately NOT added here — it's portaled to <body> on
|
||||
// open() so no ancestor's transform (e.g. a card's :hover translateY,
|
||||
// which becomes the containing block for position:fixed) can throw off
|
||||
// its placement or clip it. See open()/close().
|
||||
this.select.parentNode.insertBefore(this.wrapper, this.select);
|
||||
this.wrapper.append(this.select, this.button, this.popup);
|
||||
this.wrapper.append(this.select, this.button);
|
||||
this.select.classList.add('custom-select-native');
|
||||
// Forward width-related layout from the native select to the wrapper.
|
||||
if (this.select.style.maxWidth) {
|
||||
@ -105,6 +114,9 @@
|
||||
document.querySelectorAll('.custom-select.is-open').forEach(w => {
|
||||
if (w !== this.wrapper) w.dispatchEvent(new CustomEvent('custom-select:close'));
|
||||
});
|
||||
// Portal into <body> so position:fixed is relative to the viewport,
|
||||
// immune to any transformed/overflow ancestor.
|
||||
document.body.appendChild(this.popup);
|
||||
this.popup.hidden = false;
|
||||
this.button.setAttribute('aria-expanded', 'true');
|
||||
this.wrapper.classList.add('is-open');
|
||||
@ -124,6 +136,7 @@
|
||||
close() {
|
||||
if (this.popup.hidden) return;
|
||||
this.popup.hidden = true;
|
||||
this.popup.remove(); // detach from <body> so popups don't accumulate
|
||||
this.button.setAttribute('aria-expanded', 'false');
|
||||
this.wrapper.classList.remove('is-open');
|
||||
this.clearFocused();
|
||||
@ -141,7 +154,7 @@
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const popupMax = 280;
|
||||
const flipUp = spaceBelow < 200 && rect.top > popupMax;
|
||||
this.wrapper.classList.toggle('flip-up', flipUp);
|
||||
this.popup.classList.toggle('flip-up', flipUp);
|
||||
|
||||
this.popup.style.left = `${rect.left}px`;
|
||||
this.popup.style.width = `${rect.width}px`;
|
||||
@ -222,7 +235,7 @@
|
||||
};
|
||||
|
||||
this._outsideClick = (e) => {
|
||||
if (!this.wrapper.contains(e.target)) this.close();
|
||||
if (!this.wrapper.contains(e.target) && !this.popup.contains(e.target)) this.close();
|
||||
};
|
||||
|
||||
this._reposition = () => this.positionPopup();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user