Fix floating menus above scrollable content; they now respect content's preferred width at edges

This commit is contained in:
Keavon Chambers 2021-12-15 04:04:59 -08:00
parent 4cbccbd8cf
commit 1cf90bde9a
2 changed files with 69 additions and 16 deletions

View file

@ -202,14 +202,20 @@ export default defineComponent({
props: {
direction: { type: String, default: MenuDirection.Bottom },
type: { type: String, required: true },
windowEdgeMargin: { type: Number, default: 8 },
windowEdgeMargin: { type: Number, default: 6 },
minWidth: { type: Number, default: 0 },
scrollable: { type: Boolean, default: false },
},
data() {
const containerResizeObserver = new ResizeObserver((entries) => {
const content = entries[0].target.querySelector(".floating-menu-content") as HTMLElement;
content.style.minWidth = `${entries[0].contentRect.width}px`;
});
return {
open: false,
mouseStillDown: false,
containerResizeObserver,
MenuDirection,
MenuType,
};
@ -218,25 +224,60 @@ export default defineComponent({
const floatingMenuContainer = this.$refs.floatingMenuContainer as HTMLElement;
const floatingMenuContent = this.$refs.floatingMenuContent as HTMLElement;
const workspace = document.querySelector(".workspace-row");
if (!floatingMenuContainer || !floatingMenuContent || !workspace) return;
if (floatingMenuContent && workspace) {
const workspaceBounds = workspace.getBoundingClientRect();
const floatingMenuBounds = floatingMenuContent.getBoundingClientRect();
const workspaceBounds = workspace.getBoundingClientRect();
const floatingMenuBounds = floatingMenuContent.getBoundingClientRect();
if (this.direction === MenuDirection.Left || this.direction === MenuDirection.Right) {
const topOffset = floatingMenuBounds.top - workspaceBounds.top - this.windowEdgeMargin;
if (topOffset < 0) floatingMenuContainer.style.transform = `translate(0, ${-topOffset}px)`;
type Edge = "Top" | "Bottom" | "Left" | "Right";
let zeroedBorderDirection1: Edge | undefined;
let zeroedBorderDirection2: Edge | undefined;
const bottomOffset = workspaceBounds.bottom - floatingMenuBounds.bottom - this.windowEdgeMargin;
if (bottomOffset < 0) floatingMenuContainer.style.transform = `translate(0, ${bottomOffset}px)`;
if (this.direction === MenuDirection.Top || this.direction === MenuDirection.Bottom) {
zeroedBorderDirection1 = this.direction === MenuDirection.Top ? "Bottom" : "Top";
if (floatingMenuBounds.left - this.windowEdgeMargin <= workspaceBounds.left) {
floatingMenuContent.style.left = `${this.windowEdgeMargin}px`;
if (workspaceBounds.left + floatingMenuContainer.getBoundingClientRect().left === 12) zeroedBorderDirection2 = "Left";
}
if (this.direction === MenuDirection.Top || this.direction === MenuDirection.Bottom) {
const leftOffset = floatingMenuBounds.left - workspaceBounds.left - this.windowEdgeMargin;
if (leftOffset < 0) floatingMenuContainer.style.transform = `translate(${-leftOffset}px, 0)`;
if (floatingMenuBounds.right + this.windowEdgeMargin >= workspaceBounds.right) {
floatingMenuContent.style.right = `${this.windowEdgeMargin}px`;
if (workspaceBounds.right - floatingMenuContainer.getBoundingClientRect().right === 12) zeroedBorderDirection2 = "Right";
}
}
const rightOffset = workspaceBounds.right - floatingMenuBounds.right - this.windowEdgeMargin;
if (rightOffset < 0) floatingMenuContainer.style.transform = `translate(${rightOffset}px, 0)`;
if (this.direction === MenuDirection.Left || this.direction === MenuDirection.Right) {
zeroedBorderDirection2 = this.direction === MenuDirection.Left ? "Right" : "Left";
if (floatingMenuBounds.top - this.windowEdgeMargin <= workspaceBounds.top) {
floatingMenuContent.style.top = `${this.windowEdgeMargin}px`;
if (workspaceBounds.top + floatingMenuContainer.getBoundingClientRect().top === 12) zeroedBorderDirection1 = "Top";
}
if (floatingMenuBounds.bottom + this.windowEdgeMargin >= workspaceBounds.bottom) {
floatingMenuContent.style.bottom = `${this.windowEdgeMargin}px`;
if (workspaceBounds.bottom - floatingMenuContainer.getBoundingClientRect().bottom === 12) zeroedBorderDirection1 = "Bottom";
}
}
// Remove the rounded corner from where the tail perfectly meets the corner
if (this.type === MenuType.Popover && this.windowEdgeMargin === 6 && zeroedBorderDirection1 && zeroedBorderDirection2) {
switch (`${zeroedBorderDirection1}${zeroedBorderDirection2}`) {
case "TopLeft":
floatingMenuContent.style.borderTopLeftRadius = "0";
break;
case "TopRight":
floatingMenuContent.style.borderTopRightRadius = "0";
break;
case "BottomLeft":
floatingMenuContent.style.borderBottomLeftRadius = "0";
break;
case "BottomRight":
floatingMenuContent.style.borderBottomRightRadius = "0";
break;
default:
break;
}
}
},
@ -346,6 +387,7 @@ export default defineComponent({
},
watch: {
open(newState: boolean, oldState: boolean) {
// Switching from closed to open
if (newState && !oldState) {
// Close floating menu if mouse strays far enough away
window.addEventListener("mousemove", this.mouseMoveHandler);
@ -355,10 +397,23 @@ export default defineComponent({
// Cancel the subsequent click event to prevent the floating menu from reopening if the floating menu's button is the click event target
window.addEventListener("mouseup", this.mouseUpHandler);
// Floating menu min-width resize observer
this.$nextTick(() => {
const floatingMenuContainer = this.$refs.floatingMenuContainer as HTMLElement;
if (floatingMenuContainer) {
this.containerResizeObserver.disconnect();
this.containerResizeObserver.observe(floatingMenuContainer);
}
});
}
// Switching from open to closed
if (!newState && oldState) {
window.removeEventListener("mousemove", this.mouseMoveHandler);
window.removeEventListener("mousedown", this.mouseDownHandler);
this.containerResizeObserver.disconnect();
}
},
},

View file

@ -40,8 +40,6 @@
.menu-list {
.floating-menu-container .floating-menu-content {
padding: 4px 0;
position: absolute;
min-width: 100%;
.row {
height: 20px;