1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Fix menu rendering on when slightly off screen

Menu would bounce around in some cases because the
refreshPosition() would calculate different results
based on different state of the component and previous
render cycle CSS classes.

Also I found that the render function calls causes
in an infinite loop while open because of refreshPosition
will cause a state change which will rerender the function.
Fixed that with the shouldComponentUpdate which checks
if state has actually changed in between renders.

Signed-off-by: Juho Heikka <juho.heikka@gmail.com>
This commit is contained in:
Juho Heikka 2021-09-21 23:53:11 +03:00
parent 1ebb8eedf7
commit eb789a0ab1
2 changed files with 31 additions and 31 deletions

View File

@ -35,14 +35,6 @@
&.portal { &.portal {
left: -1000px; left: -1000px;
top: -1000px; top: -1000px;
&.top {
margin-top: -$margin;
}
&.bottom {
margin-top: $margin;
}
} }
&:not(.portal) { &:not(.portal) {

View File

@ -26,6 +26,7 @@ import { createPortal } from "react-dom";
import { autoBind, cssNames, noop } from "../../utils"; import { autoBind, cssNames, noop } from "../../utils";
import { Animate } from "../animate"; import { Animate } from "../animate";
import { Icon, IconProps } from "../icon"; import { Icon, IconProps } from "../icon";
import isEqual from "lodash/isEqual";
export const MenuContext = React.createContext<MenuContextValue>(null); export const MenuContext = React.createContext<MenuContextValue>(null);
export type MenuContextValue = Menu; export type MenuContextValue = Menu;
@ -85,6 +86,10 @@ export class Menu extends React.Component<MenuProps, State> {
return !!this.props.isOpen; return !!this.props.isOpen;
} }
shouldComponentUpdate(nextProps: MenuProps, nextState: State) {
return !isEqual(nextState, this.state) || !isEqual(nextProps, this.props);
}
componentDidMount() { componentDidMount() {
if (!this.props.usePortal) { if (!this.props.usePortal) {
const parent = this.elem.parentElement; const parent = this.elem.parentElement;
@ -150,39 +155,42 @@ export class Menu extends React.Component<MenuProps, State> {
return; return;
} }
const { width, height } = this.opener.getBoundingClientRect(); const openerClientRect = this.opener.getBoundingClientRect();
let { left, top, bottom, right } = this.opener.getBoundingClientRect(); let { left: openerLeft, top: openerTop, bottom: openerBottom, right: openerRight } = this.opener.getBoundingClientRect();
const withScroll = window.getComputedStyle(this.elem).position !== "fixed"; const withScroll = window.getComputedStyle(this.elem).position !== "fixed";
// window global scroll corrections // window global scroll corrections
if (withScroll) { if (withScroll) {
left += window.pageXOffset; openerLeft += window.pageXOffset;
top += window.pageYOffset; openerTop += window.pageYOffset;
right = left + width; openerRight = openerLeft + openerClientRect.width;
bottom = top + height; openerBottom = openerTop + openerClientRect.height;
} }
// setup initial position const extraMargin = this.props.usePortal ? 8 : 0;
const position: MenuPosition = { left: true, bottom: true };
this.elem.style.left = `${left}px`; const { width: menuWidth, height: menuHeight } = this.elem.getBoundingClientRect();
this.elem.style.top = `${bottom}px`;
// correct position if menu doesn't fit to viewport const rightSideOfMenu = openerLeft + menuWidth;
const menuPos = this.elem.getBoundingClientRect(); const renderMenuLeft = rightSideOfMenu > window.innerWidth;
const menuOnLeftSidePosition = `${openerRight - this.elem.offsetWidth}px`;
const menuOnRightSidePosition = `${openerLeft}px`;
if (menuPos.right > window.innerWidth) { this.elem.style.left = renderMenuLeft ? menuOnLeftSidePosition : menuOnRightSidePosition;
this.elem.style.left = `${right - this.elem.offsetWidth}px`;
position.right = true;
delete position.left;
}
if (menuPos.bottom > window.innerHeight) { const bottomOfMenu = openerBottom + extraMargin + menuHeight;
this.elem.style.top = `${top - this.elem.offsetHeight}px`; const renderMenuOnTop = bottomOfMenu > window.innerHeight;
position.top = true; const menuOnTopPosition = `${openerTop - this.elem.offsetHeight - extraMargin}px`;
delete position.bottom; const menuOnBottomPosition = `${openerBottom + extraMargin}px`;
}
this.setState({ position }); this.elem.style.top = renderMenuOnTop ? menuOnTopPosition : menuOnBottomPosition;
this.setState({ position: {
top: renderMenuOnTop,
bottom: !renderMenuOnTop,
left: renderMenuLeft,
right: !renderMenuLeft
} });
}; };
open() { open() {