fix(ui): keep floating menus inside viewport and clipping containers
Quality / quality (ubuntu-latest) (push) Failing after 4s
Quality / quality (windows-latest) (push) Canceled after 0s

This commit is contained in:
Clintchiz committed 2026-10-19 17:51:15 +05:30
1 parent 9520651330
commit 7c1fcd19d4
16 files changed
+138 -33

No files matched your search

+58 -9
View File
@@ -3244,6 +3244,16 @@ export const REACTIVE_RUNTIME = String.raw`
function clampAnchored(element) {
// Measure the untouched position; the offset is recomputed from scratch.
element.style.translate = "";
// Restore authored limits before measuring, including after a resize.
if (element.__wrnAnchorLimits) {
var saved = element.__wrnAnchorLimits;
element.style.maxWidth = saved[0];
element.style.maxHeight = saved[1];
element.style.minWidth = saved[2];
element.style.minHeight = saved[3];
element.style.overflow = saved[4];
element.style.boxSizing = saved[5];
}
var arrow = element.querySelector("[data-wrn-anchor-arrow]");
if (arrow) arrow.style.translate = "";
if (!isRenderedElement(element)) return;
@@ -3251,6 +3261,38 @@ export const REACTIVE_RUNTIME = String.raw`
var rect = element.getBoundingClientRect();
var viewportWidth = window.innerWidth || document.documentElement.clientWidth;
var viewportHeight = window.innerHeight || document.documentElement.clientHeight;
var visual = window.visualViewport;
var left = (visual ? visual.offsetLeft : 0) + ANCHOR_MARGIN;
var top = (visual ? visual.offsetTop : 0) + ANCHOR_MARGIN;
var right = left + (visual ? visual.width : viewportWidth) - ANCHOR_MARGIN * 2;
var bottom = top + (visual ? visual.height : viewportHeight) - ANCHOR_MARGIN * 2;
// A preview, sidebar or scroll container may clip well before the screen edge.
for (var parent = element.parentElement; parent && parent !== document.documentElement; parent = parent.parentElement) {
var css = window.getComputedStyle(parent);
var clipX = /auto|scroll|hidden|clip/.test(css.overflowX || css.overflow);
var clipY = /auto|scroll|hidden|clip/.test(css.overflowY || css.overflow);
if (!clipX && !clipY) continue;
var bounds = parent.getBoundingClientRect();
if (clipX) {
left = Math.max(left, bounds.left + parent.clientLeft + ANCHOR_MARGIN);
right = Math.min(right, bounds.left + parent.clientLeft + parent.clientWidth - ANCHOR_MARGIN);
}
if (clipY) {
top = Math.max(top, bounds.top + parent.clientTop + ANCHOR_MARGIN);
bottom = Math.min(bottom, bounds.top + parent.clientTop + parent.clientHeight - ANCHOR_MARGIN);
}
}
if (right <= left || bottom <= top) return;
if (!element.__wrnAnchorLimits) element.__wrnAnchorLimits = [element.style.maxWidth, element.style.maxHeight, element.style.minWidth, element.style.minHeight, element.style.overflow, element.style.boxSizing];
if (rect.width > right - left || rect.height > bottom - top) {
element.style.boxSizing = "border-box";
element.style.minWidth = "0px";
element.style.minHeight = "0px";
element.style.maxWidth = Math.min(rect.width, right - left) + "px";
element.style.maxHeight = Math.min(rect.height, bottom - top) + "px";
element.style.overflow = "auto";
rect = element.getBoundingClientRect();
}
/*
* Clamp only while the overlay's own trigger is on screen.
@@ -3263,7 +3305,7 @@ export const REACTIVE_RUNTIME = String.raw`
* one very much does need moving; skipping it left the menu invisible
* with no way to reach it. The trigger tells the two apart.
*/
var host = element.closest("[data-ui-component]") || element.parentElement;
var host = element.parentElement && element.parentElement.closest("[data-wrn-menu]") || element.closest("[data-ui-component]") || element.parentElement;
var anchor = host ? host.getBoundingClientRect() : rect;
var anchorVisible =
anchor.bottom > 0 &&
@@ -3278,17 +3320,20 @@ export const REACTIVE_RUNTIME = String.raw`
var shiftX = 0;
var shiftY = 0;
if (rect.width < viewportWidth - ANCHOR_MARGIN * 2) {
if (rect.left < ANCHOR_MARGIN) shiftX = ANCHOR_MARGIN - rect.left;
else if (rect.right > viewportWidth - ANCHOR_MARGIN) {
shiftX = viewportWidth - ANCHOR_MARGIN - rect.right;
if (rect.width <= right - left) {
if (rect.left < left) shiftX = left - rect.left;
else if (rect.right > right) {
shiftX = right - rect.right;
}
}
if (rect.height < viewportHeight - ANCHOR_MARGIN * 2) {
if (rect.top < ANCHOR_MARGIN) shiftY = ANCHOR_MARGIN - rect.top;
else if (rect.bottom > viewportHeight - ANCHOR_MARGIN) {
shiftY = viewportHeight - ANCHOR_MARGIN - rect.bottom;
if (rect.height <= bottom - top) {
if (rect.top < top) shiftY = top - rect.top;
else if (rect.bottom > bottom) {
// Prefer opening above the trigger when there is enough room.
shiftY = anchor.top - rect.height - ANCHOR_MARGIN >= top && rect.top >= anchor.bottom
? anchor.top - rect.height - ANCHOR_MARGIN - rect.top
: bottom - rect.bottom;
}
}
@@ -3392,6 +3437,10 @@ export const REACTIVE_RUNTIME = String.raw`
window.addEventListener("resize", scheduleAnchoredReposition);
window.addEventListener("scroll", scheduleAnchoredReposition, true);
if (window.visualViewport) {
window.visualViewport.addEventListener("resize", scheduleAnchoredReposition);
window.visualViewport.addEventListener("scroll", scheduleAnchoredReposition);
}
}
/*