react-contextmenu
Version:
Context Menu implemented in React
294 lines (239 loc) • 11.4 kB
JavaScript
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
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; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import assign from 'object-assign';
import listener from './globalEventListener';
import AbstractMenu from './AbstractMenu';
import SubMenu from './SubMenu';
import { hideMenu } from './actions';
import { cssClasses, callIfExists, store } from './helpers';
var ContextMenu = function (_AbstractMenu) {
_inherits(ContextMenu, _AbstractMenu);
function ContextMenu(props) {
_classCallCheck(this, ContextMenu);
var _this = _possibleConstructorReturn(this, (ContextMenu.__proto__ || Object.getPrototypeOf(ContextMenu)).call(this, props));
_this.registerHandlers = function () {
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);
};
_this.unregisterHandlers = function () {
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);
};
_this.handleShow = function (e) {
if (e.detail.id !== _this.props.id || _this.state.isVisible) return;
var _e$detail$position = e.detail.position,
x = _e$detail$position.x,
y = _e$detail$position.y;
_this.setState({ isVisible: true, x: x, y: y });
_this.registerHandlers();
callIfExists(_this.props.onShow, e);
};
_this.handleHide = function (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 });
callIfExists(_this.props.onHide, e);
}
};
_this.handleOutsideClick = function (e) {
if (!_this.menu.contains(e.target)) hideMenu();
};
_this.handleMouseLeave = function (event) {
event.preventDefault();
callIfExists(_this.props.onMouseLeave, event, assign({}, _this.props.data, store.data), store.target);
if (_this.props.hideOnLeave) hideMenu();
};
_this.handleContextMenu = function (e) {
if (process.env.NODE_ENV === 'production') {
e.preventDefault();
}
_this.handleHide(e);
};
_this.hideMenu = function (e) {
if (e.keyCode === 27 || e.keyCode === 13) {
// ECS or enter
hideMenu();
}
};
_this.getMenuPosition = function () {
var x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var menuStyles = {
top: y,
left: x
};
if (!_this.menu) return menuStyles;
var _window = window,
innerWidth = _window.innerWidth,
innerHeight = _window.innerHeight;
var 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;
};
_this.getRTLMenuPosition = function () {
var x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
var menuStyles = {
top: y,
left: x
};
if (!_this.menu) return menuStyles;
var _window2 = window,
innerWidth = _window2.innerWidth,
innerHeight = _window2.innerHeight;
var 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;
};
_this.menuRef = function (c) {
_this.menu = c;
};
_this.state = assign({}, _this.state, {
x: 0,
y: 0,
isVisible: false
});
return _this;
}
_createClass(ContextMenu, [{
key: 'getSubMenuType',
value: function getSubMenuType() {
// eslint-disable-line class-methods-use-this
return SubMenu;
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
this.listenId = listener.register(this.handleShow, this.handleHide);
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
var _this2 = this;
var wrapper = window.requestAnimationFrame || setTimeout;
if (this.state.isVisible) {
wrapper(function () {
var _state = _this2.state,
x = _state.x,
y = _state.y;
var _ref = _this2.props.rtl ? _this2.getRTLMenuPosition(x, y) : _this2.getMenuPosition(x, y),
top = _ref.top,
left = _ref.left;
wrapper(function () {
if (!_this2.menu) return;
_this2.menu.style.top = top + 'px';
_this2.menu.style.left = left + 'px';
_this2.menu.style.opacity = 1;
_this2.menu.style.pointerEvents = 'auto';
});
});
} else {
wrapper(function () {
if (!_this2.menu) return;
_this2.menu.style.opacity = 0;
_this2.menu.style.pointerEvents = 'none';
});
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
if (this.listenId) {
listener.unregister(this.listenId);
}
this.unregisterHandlers();
}
}, {
key: 'render',
value: function render() {
var _props = this.props,
children = _props.children,
className = _props.className,
style = _props.style;
var isVisible = this.state.isVisible;
var inlineStyle = assign({}, style, { position: 'fixed', opacity: 0, pointerEvents: 'none' });
var menuClassnames = cx(cssClasses.menu, className, _defineProperty({}, cssClasses.menuVisible, isVisible));
return React.createElement(
'nav',
{
role: 'menu', tabIndex: '-1', ref: this.menuRef, style: inlineStyle, className: menuClassnames,
onContextMenu: this.handleContextMenu, onMouseLeave: this.handleMouseLeave },
this.renderChildren(children)
);
}
}]);
return ContextMenu;
}(AbstractMenu);
ContextMenu.propTypes = {
id: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
data: PropTypes.object,
className: PropTypes.string,
hideOnLeave: PropTypes.bool,
rtl: PropTypes.bool,
onHide: PropTypes.func,
onMouseLeave: PropTypes.func,
onShow: PropTypes.func,
preventHideOnContextMenu: PropTypes.bool,
preventHideOnResize: PropTypes.bool,
preventHideOnScroll: PropTypes.bool,
style: PropTypes.object
};
ContextMenu.defaultProps = {
className: '',
data: {},
hideOnLeave: false,
rtl: false,
onHide: function onHide() {
return null;
},
onMouseLeave: function onMouseLeave() {
return null;
},
onShow: function onShow() {
return null;
},
preventHideOnContextMenu: false,
preventHideOnResize: false,
preventHideOnScroll: false,
style: {}
};
export default ContextMenu;