135 lines
3.4 KiB
JavaScript
135 lines
3.4 KiB
JavaScript
import React from "react";
|
|
import PropTypes from "prop-types";
|
|
import DropDownItem from "../drop-down-item";
|
|
import DropDown from "../drop-down";
|
|
|
|
class ContextMenu extends React.PureComponent {
|
|
constructor(props) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
visible: false,
|
|
};
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.container =
|
|
document.getElementById(this.props.targetAreaId) || document;
|
|
|
|
this.container.addEventListener("contextmenu", this.handleContextMenu);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.container.removeEventListener("contextmenu", this.handleContextMenu);
|
|
}
|
|
|
|
moveMenu = (e) => {
|
|
const menu = document.getElementById(this.props.id);
|
|
|
|
const bounds =
|
|
this.container !== document && this.container.getBoundingClientRect();
|
|
const clickX = e.clientX - bounds.left;
|
|
const clickY = e.clientY - bounds.top;
|
|
const containerWidth = this.container.offsetWidth;
|
|
const containerHeight = this.container.offsetHeight;
|
|
const menuWidth = (menu && menu.offsetWidth) || 180;
|
|
const menuHeight = menu && menu.offsetHeight;
|
|
|
|
const right = containerWidth - clickX < menuWidth && clickX > menuWidth;
|
|
const bottom = containerHeight - clickY < menuHeight && clickY > menuHeight;
|
|
|
|
let newTop = `0px`;
|
|
let newLeft = `0px`;
|
|
|
|
newLeft = right ? `${clickX - menuWidth - 8}px` : `${clickX + 8}px`;
|
|
newTop = bottom ? `${clickY - menuHeight}px` : `${clickY}px`;
|
|
|
|
if (menu) {
|
|
menu.style.top = newTop;
|
|
menu.style.left = newLeft;
|
|
}
|
|
};
|
|
|
|
handleContextMenu = (e) => {
|
|
if (e) {
|
|
e.preventDefault();
|
|
this.handleClick(e);
|
|
}
|
|
|
|
this.setState(
|
|
{
|
|
visible: true,
|
|
},
|
|
() => this.moveMenu(e)
|
|
);
|
|
};
|
|
|
|
handleClick = (e) => {
|
|
const { visible } = this.state;
|
|
const menu = document.getElementById(this.props.id);
|
|
const wasOutside = e.target ? !(e.target.contains === menu) : true;
|
|
|
|
if (wasOutside && visible) this.setState({ visible: false });
|
|
};
|
|
|
|
itemClick = (action, e) => {
|
|
action && action(e);
|
|
|
|
this.setState({ visible: false });
|
|
};
|
|
|
|
render() {
|
|
//console.log('ContextMenu render', this.props);
|
|
const { visible } = this.state;
|
|
const { options, id, className, style, withBackdrop } = this.props;
|
|
|
|
return (
|
|
((visible && options) || null) && (
|
|
<DropDown
|
|
id={id}
|
|
className={className}
|
|
style={style}
|
|
open={visible}
|
|
clickOutsideAction={this.handleClick}
|
|
withBackdrop={withBackdrop}
|
|
>
|
|
{options.map((item) => {
|
|
if (item && item.key !== undefined) {
|
|
return (
|
|
<DropDownItem
|
|
key={item.key}
|
|
{...item}
|
|
onClick={this.itemClick.bind(this, item.onClick)}
|
|
/>
|
|
);
|
|
}
|
|
})}
|
|
</DropDown>
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
ContextMenu.propTypes = {
|
|
/** DropDownItems collection */
|
|
options: PropTypes.array,
|
|
/** Id of container apply to */
|
|
targetAreaId: PropTypes.string,
|
|
/** Accepts class */
|
|
className: PropTypes.string,
|
|
/** Accepts id */
|
|
id: PropTypes.string,
|
|
/** Accepts css style */
|
|
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
|
/** Used to display backdrop */
|
|
withBackdrop: PropTypes.bool,
|
|
};
|
|
|
|
ContextMenu.defaultProps = {
|
|
options: [],
|
|
id: "contextMenu",
|
|
withBackdrop: true,
|
|
};
|
|
|
|
export default ContextMenu;
|