Popups: always clamp into the viewport
positionPopup clamped horizontally but never vertically: the flip-above branch positioned tall popups (rich layouts, lead figures) above the visible region whenever the target sat near the top of the screen. Placement is now: below if it fits, above if THAT fits, else the roomier side — then clamped into the viewport on both axes. .link-popup is additionally capped at viewport height (matching GAP, overflow-y: auto) so the clamp always has room to work, and a dimension-less image that loads after positioning re-clamps instead of growing past the edge. Verified by simulating the clamp across five viewport scenarios: the near-top bug case moves from 470px above the viewport to fully visible; the fits-below and flip-above cases are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
1027b88429
commit
76bda7af13
|
|
@ -10,6 +10,12 @@
|
|||
z-index: 500;
|
||||
max-width: 420px;
|
||||
min-width: 200px;
|
||||
/* Never taller than the viewport: positionPopup clamps the popup
|
||||
into the visible region, which only works if the box itself can
|
||||
fit there. The 10px matches positionPopup's GAP on each side;
|
||||
overflow scrolls the rare popup that still exceeds the cap. */
|
||||
max-height: calc(100vh - 20px);
|
||||
overflow-y: auto;
|
||||
padding: 0.7rem 0.9rem;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
|
|
|
|||
|
|
@ -215,6 +215,19 @@
|
|||
positionPopup(target);
|
||||
popup.classList.add('is-visible');
|
||||
popup.setAttribute('aria-hidden', 'false');
|
||||
/* Images with width/height attrs reserve their space
|
||||
before load; one without them grows the popup after
|
||||
positioning and can push it past the viewport edge.
|
||||
Re-clamp when such an image arrives. */
|
||||
popup.querySelectorAll('img:not([height])').forEach(function (im) {
|
||||
if (im.complete) return;
|
||||
im.addEventListener('load', function () {
|
||||
if (activeTarget === target &&
|
||||
popup.classList.contains('is-visible')) {
|
||||
positionPopup(target);
|
||||
}
|
||||
}, { once: true });
|
||||
});
|
||||
}).catch(function () { /* silently fail */ });
|
||||
}, SHOW_DELAY);
|
||||
}
|
||||
|
|
@ -247,9 +260,26 @@
|
|||
var left = rect.left + sx + rect.width / 2 - pw / 2;
|
||||
left = Math.max(sx + GAP, Math.min(left, sx + vw - pw - GAP));
|
||||
|
||||
var top = (rect.bottom + GAP + ph <= vh)
|
||||
? rect.bottom + sy + GAP
|
||||
: rect.top + sy - ph - GAP;
|
||||
/* Below if it fits, else above if THAT fits, else whichever
|
||||
side has more room. The final clamp guarantees the popup
|
||||
never extends past either viewport edge — without it, the
|
||||
flip-above branch positions tall popups (rich layouts, lead
|
||||
figures) above the visible region for targets near the top
|
||||
of the screen. CSS caps .link-popup at viewport height
|
||||
(same 10px gap), so the clamp always has room to work. */
|
||||
var fitsBelow = rect.bottom + GAP + ph <= vh;
|
||||
var fitsAbove = rect.top - GAP - ph >= 0;
|
||||
var top;
|
||||
if (fitsBelow) {
|
||||
top = rect.bottom + sy + GAP;
|
||||
} else if (fitsAbove) {
|
||||
top = rect.top + sy - ph - GAP;
|
||||
} else {
|
||||
top = (vh - rect.bottom >= rect.top)
|
||||
? rect.bottom + sy + GAP
|
||||
: rect.top + sy - ph - GAP;
|
||||
}
|
||||
top = Math.max(sy + GAP, Math.min(top, sy + vh - ph - GAP));
|
||||
|
||||
popup.style.left = left + 'px';
|
||||
popup.style.top = top + 'px';
|
||||
|
|
|
|||
Loading…
Reference in New Issue