Rewrite position dropdown logic to be much smarter

This commit is contained in:
Andrew Kingston 2024-04-25 09:43:33 +01:00
parent f08562e0e4
commit 9c244f6645
1 changed files with 41 additions and 20 deletions

View File

@ -28,6 +28,7 @@ export default function positionDropdown(element, opts) {
// Compute bounds // Compute bounds
const anchorBounds = anchor.getBoundingClientRect() const anchorBounds = anchor.getBoundingClientRect()
const elementBounds = element.getBoundingClientRect() const elementBounds = element.getBoundingClientRect()
const padding = 8
let styles = { let styles = {
maxHeight: null, maxHeight: null,
minWidth, minWidth,
@ -42,45 +43,46 @@ export default function positionDropdown(element, opts) {
offset: opts.offset, offset: opts.offset,
}) })
} else { } else {
const renderHeight = maxHeight || elementBounds.height // "outside" alignment (popover vertical center = anchor vertical center)
// Determine vertical styles
const topSpace = anchorBounds.top
const bottomSpace = window.innerHeight - anchorBounds.bottom
if (align === "right-outside" || align === "left-outside") { if (align === "right-outside" || align === "left-outside") {
styles.top = styles.top =
anchorBounds.top + anchorBounds.height / 2 - elementBounds.height / 2 anchorBounds.top + anchorBounds.height / 2 - elementBounds.height / 2
styles.maxHeight = maxHeight styles.maxHeight = maxHeight
if (styles.top + elementBounds.height > window.innerHeight) { }
styles.top = window.innerHeight - elementBounds.height
} // Normal left/right alignment (top popover edge = botom anchor edge)
} else if ( else {
window.innerHeight - anchorBounds.bottom < renderHeight + offset &&
topSpace - bottomSpace > 100
) {
styles.top = anchorBounds.top - renderHeight - offset
styles.maxHeight = maxHeight
} else {
styles.top = anchorBounds.bottom + offset styles.top = anchorBounds.bottom + offset
styles.maxHeight = styles.maxHeight = maxHeight || window.innerHeight - styles.top
maxHeight || window.innerHeight - anchorBounds.bottom - 20
} }
// Determine horizontal styles // Determine horizontal styles
// Use anchor width if required
if (!maxWidth && useAnchorWidth) { if (!maxWidth && useAnchorWidth) {
styles.maxWidth = anchorBounds.width styles.maxWidth = anchorBounds.width
} }
if (useAnchorWidth) { if (useAnchorWidth) {
styles.minWidth = anchorBounds.width styles.minWidth = anchorBounds.width
} }
// Right alignment (right popover edge = right anchor edge)
if (align === "right") { if (align === "right") {
styles.left = styles.left =
anchorBounds.left + anchorBounds.width - elementBounds.width anchorBounds.left + anchorBounds.width - elementBounds.width
} else if (align === "right-outside") { }
// Right outside alignment (left popover edge = right anchor edge)
else if (align === "right-outside") {
styles.left = anchorBounds.right + offset styles.left = anchorBounds.right + offset
} else if (align === "left-outside") { }
// Left outside alignment (right popover edge = left anchor edge)
else if (align === "left-outside") {
styles.left = anchorBounds.left - elementBounds.width - offset styles.left = anchorBounds.left - elementBounds.width - offset
} else { }
// Left alignment by default (left popover edge = left anchor edge)
else {
styles.left = anchorBounds.left styles.left = anchorBounds.left
} }
@ -88,6 +90,25 @@ export default function positionDropdown(element, opts) {
if (noShrink) { if (noShrink) {
delete styles.maxHeight delete styles.maxHeight
} }
// Handle screen overflow
// Check right overflow
if (styles.left + elementBounds.width > window.innerWidth) {
styles.left = window.innerWidth - elementBounds.width - padding
}
// Check bottom overflow
if (styles.top + elementBounds.height > window.innerHeight) {
styles.top = window.innerHeight - elementBounds.height - padding
// If we overflowed off the bottom and therefore locked to the bottom
// edge, we might now be covering the anchor. Therefore we can try
// moving left or right to reveal the full anchor again.
if (anchorBounds.right + elementBounds.width < window.innerWidth) {
styles.left = anchorBounds.right
} else if (anchorBounds.left - elementBounds.width > 0) {
styles.left = anchorBounds.left - elementBounds.width
}
}
} }
// Apply styles // Apply styles