@jstarpl/react-contextmenu
Version:
Context Menu implemented in React
297 lines (245 loc) • 8.51 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _react = _interopRequireDefault(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _classnames = _interopRequireDefault(require("classnames"));
var _objectAssign = _interopRequireDefault(require("object-assign"));
var _globalEventListener = _interopRequireDefault(require("./globalEventListener"));
var _AbstractMenu = _interopRequireDefault(require("./AbstractMenu"));
var _SubMenu = _interopRequireDefault(require("./SubMenu"));
var _actions = require("./actions");
var _helpers = require("./helpers");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
/* eslint-disable no-magic-numbers */
class ContextMenu extends _AbstractMenu.default {
constructor(props) {
super(props);
_defineProperty(this, "registerHandlers", () => {
document.addEventListener('mousedown', this.handleOutsideClick);
document.addEventListener('touchstart', this.handleOutsideClick);
if (!this.props.preventHideOnScroll) document.addEventListener('scroll', this.handleHide);
if (!this.props.preventHideOnContextMenu) document.addEventListener('contextmenu', this.handleHide);
document.addEventListener('keydown', this.handleKeyNavigation);
if (!this.props.preventHideOnResize) window.addEventListener('resize', this.handleHide);
});
_defineProperty(this, "unregisterHandlers", () => {
document.removeEventListener('mousedown', this.handleOutsideClick);
document.removeEventListener('touchstart', this.handleOutsideClick);
document.removeEventListener('scroll', this.handleHide);
document.removeEventListener('contextmenu', this.handleHide);
document.removeEventListener('keydown', this.handleKeyNavigation);
window.removeEventListener('resize', this.handleHide);
});
_defineProperty(this, "handleShow", e => {
if (e.detail.id !== this.props.id || this.state.isVisible) return;
const {
x,
y
} = e.detail.position;
this.setState({
isVisible: true,
x,
y
});
this.registerHandlers();
(0, _helpers.callIfExists)(this.props.onShow, e);
});
_defineProperty(this, "handleHide", e => {
if (this.state.isVisible && (!e.detail || !e.detail.id || e.detail.id === this.props.id)) {
this.unregisterHandlers();
this.setState({
isVisible: false,
selectedItem: null,
forceSubMenuOpen: false
});
(0, _helpers.callIfExists)(this.props.onHide, e);
}
});
_defineProperty(this, "handleOutsideClick", e => {
if (!this.menu.contains(e.target)) (0, _actions.hideMenu)();
});
_defineProperty(this, "handleMouseLeave", event => {
event.preventDefault();
(0, _helpers.callIfExists)(this.props.onMouseLeave, event, (0, _objectAssign.default)({}, this.props.data, _helpers.store.data), _helpers.store.target);
if (this.props.hideOnLeave) (0, _actions.hideMenu)();
});
_defineProperty(this, "handleContextMenu", e => {
if (process.env.NODE_ENV === 'production') {
e.preventDefault();
}
this.handleHide(e);
});
_defineProperty(this, "hideMenu", e => {
if (e.code === _helpers.KEYBOARD_CODES.Escape || e.code === _helpers.KEYBOARD_CODES.Enter || e.code === _helpers.KEYBOARD_CODES.NumpadEnter) {
// ECS or enter
(0, _actions.hideMenu)();
}
});
_defineProperty(this, "getMenuPosition", (x = 0, y = 0) => {
let menuStyles = {
top: y,
left: x
};
if (!this.menu) return menuStyles;
const {
innerWidth,
innerHeight
} = window;
const rect = this.menu.getBoundingClientRect();
if (y + rect.height > innerHeight) {
menuStyles.top -= rect.height;
}
if (x + rect.width > innerWidth) {
menuStyles.left -= rect.width;
}
if (menuStyles.top < 0) {
menuStyles.top = rect.height < innerHeight ? (innerHeight - rect.height) / 2 : 0;
}
if (menuStyles.left < 0) {
menuStyles.left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
}
return menuStyles;
});
_defineProperty(this, "getRTLMenuPosition", (x = 0, y = 0) => {
let menuStyles = {
top: y,
left: x
};
if (!this.menu) return menuStyles;
const {
innerWidth,
innerHeight
} = window;
const rect = this.menu.getBoundingClientRect(); // Try to position the menu on the left side of the cursor
menuStyles.left = x - rect.width;
if (y + rect.height > innerHeight) {
menuStyles.top -= rect.height;
}
if (menuStyles.left < 0) {
menuStyles.left += rect.width;
}
if (menuStyles.top < 0) {
menuStyles.top = rect.height < innerHeight ? (innerHeight - rect.height) / 2 : 0;
}
if (menuStyles.left + rect.width > innerWidth) {
menuStyles.left = rect.width < innerWidth ? (innerWidth - rect.width) / 2 : 0;
}
return menuStyles;
});
_defineProperty(this, "menuRef", c => {
this.menu = c;
});
this.state = (0, _objectAssign.default)({}, this.state, {
x: 0,
y: 0,
isVisible: false
});
}
getSubMenuType() {
// eslint-disable-line class-methods-use-this
return _SubMenu.default;
}
componentDidMount() {
this.listenId = _globalEventListener.default.register(this.handleShow, this.handleHide);
}
componentDidUpdate() {
const wrapper = window.requestAnimationFrame || setTimeout;
if (this.state.isVisible) {
wrapper(() => {
const {
x,
y
} = this.state;
const {
top,
left
} = this.props.rtl ? this.getRTLMenuPosition(x, y) : this.getMenuPosition(x, y);
wrapper(() => {
if (!this.menu) return;
this.menu.style.top = `${top}px`;
this.menu.style.left = `${left}px`;
this.menu.style.opacity = 1;
this.menu.style.pointerEvents = 'auto';
});
});
} else {
wrapper(() => {
if (!this.menu) return;
this.menu.style.opacity = 0;
this.menu.style.pointerEvents = 'none';
});
}
}
componentWillUnmount() {
if (this.listenId) {
_globalEventListener.default.unregister(this.listenId);
}
this.unregisterHandlers();
}
render() {
const {
children,
className,
style
} = this.props;
const {
isVisible
} = this.state;
const inlineStyle = (0, _objectAssign.default)({}, style, {
position: 'fixed',
opacity: 0,
pointerEvents: 'none'
});
const menuClassnames = (0, _classnames.default)(_helpers.cssClasses.menu, className, {
[_helpers.cssClasses.menuVisible]: isVisible
});
return /*#__PURE__*/_react.default.createElement("nav", {
role: "menu",
tabIndex: "-1",
ref: this.menuRef,
style: inlineStyle,
className: menuClassnames,
onContextMenu: this.handleContextMenu,
onMouseLeave: this.handleMouseLeave
}, this.renderChildren(children));
}
}
exports.default = ContextMenu;
_defineProperty(ContextMenu, "propTypes", {
id: _propTypes.default.string.isRequired,
children: _propTypes.default.node.isRequired,
data: _propTypes.default.object,
className: _propTypes.default.string,
hideOnLeave: _propTypes.default.bool,
rtl: _propTypes.default.bool,
onHide: _propTypes.default.func,
onMouseLeave: _propTypes.default.func,
onShow: _propTypes.default.func,
preventHideOnContextMenu: _propTypes.default.bool,
preventHideOnResize: _propTypes.default.bool,
preventHideOnScroll: _propTypes.default.bool,
style: _propTypes.default.object
});
_defineProperty(ContextMenu, "defaultProps", {
className: '',
data: {},
hideOnLeave: false,
rtl: false,
onHide() {
return null;
},
onMouseLeave() {
return null;
},
onShow() {
return null;
},
preventHideOnContextMenu: false,
preventHideOnResize: false,
preventHideOnScroll: false,
style: {}
});