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;
|
z-index: 500;
|
||||||
max-width: 420px;
|
max-width: 420px;
|
||||||
min-width: 200px;
|
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;
|
padding: 0.7rem 0.9rem;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
|
|
||||||
|
|
@ -215,6 +215,19 @@
|
||||||
positionPopup(target);
|
positionPopup(target);
|
||||||
popup.classList.add('is-visible');
|
popup.classList.add('is-visible');
|
||||||
popup.setAttribute('aria-hidden', 'false');
|
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 */ });
|
}).catch(function () { /* silently fail */ });
|
||||||
}, SHOW_DELAY);
|
}, SHOW_DELAY);
|
||||||
}
|
}
|
||||||
|
|
@ -247,9 +260,26 @@
|
||||||
var left = rect.left + sx + rect.width / 2 - pw / 2;
|
var left = rect.left + sx + rect.width / 2 - pw / 2;
|
||||||
left = Math.max(sx + GAP, Math.min(left, sx + vw - pw - GAP));
|
left = Math.max(sx + GAP, Math.min(left, sx + vw - pw - GAP));
|
||||||
|
|
||||||
var top = (rect.bottom + GAP + ph <= vh)
|
/* 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.bottom + sy + GAP
|
||||||
: rect.top + sy - ph - 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.left = left + 'px';
|
||||||
popup.style.top = top + 'px';
|
popup.style.top = top + 'px';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue