fix(ui): keep floating menus inside viewport and clipping containers
This commit is contained in:
1 parent
9520651330
commit
7c1fcd19d4
16 files changed
+138
-33
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in new issue
Block a user