@jstarpl/react-contextmenu
Version:
Context Menu implemented in React
301 lines (253 loc) • 9.72 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 _actions = require("./actions");
var _AbstractMenu = _interopRequireDefault(require("./AbstractMenu"));
var _helpers = require("./helpers");
var _globalEventListener = _interopRequireDefault(require("./globalEventListener"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
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; }
class SubMenu extends _AbstractMenu.default {
constructor(props) {
super(props);
_defineProperty(this, "getMenuPosition", () => {
const {
innerWidth,
innerHeight
} = window;
const rect = this.subMenu.getBoundingClientRect();
const position = {};
if (rect.bottom > innerHeight) {
position.bottom = 0;
} else {
position.top = 0;
}
if (rect.right < innerWidth) {
position.left = '100%';
} else {
position.right = '100%';
}
return position;
});
_defineProperty(this, "getRTLMenuPosition", () => {
const {
innerHeight
} = window;
const rect = this.subMenu.getBoundingClientRect();
const position = {};
if (rect.bottom > innerHeight) {
position.bottom = 0;
} else {
position.top = 0;
} // eslint-disable-next-line no-magic-numbers
if (rect.left < 0) {
position.left = '100%';
} else {
position.right = '100%';
}
return position;
});
_defineProperty(this, "hideMenu", e => {
e.preventDefault();
this.hideSubMenu(e);
});
_defineProperty(this, "hideSubMenu", e => {
// avoid closing submenus of a different menu tree
if (e.detail && e.detail.id && this.menu && e.detail.id !== this.menu.id) {
return;
}
if (this.props.forceOpen) {
this.props.forceClose();
}
this.setState({
visible: false,
selectedItem: null
});
this.unregisterHandlers();
});
_defineProperty(this, "handleClick", event => {
event.preventDefault();
if (this.props.disabled) return;
(0, _helpers.callIfExists)(this.props.onClick, event, (0, _objectAssign.default)({}, this.props.data, _helpers.store.data), _helpers.store.target);
if (!this.props.onClick || this.props.preventCloseOnClick) return;
(0, _actions.hideMenu)();
});
_defineProperty(this, "handleMouseEnter", () => {
if (this.closetimer) clearTimeout(this.closetimer);
if (this.props.disabled || this.state.visible) return;
this.opentimer = setTimeout(() => this.setState({
visible: true,
selectedItem: null
}), this.props.hoverDelay);
});
_defineProperty(this, "handleMouseLeave", () => {
if (this.opentimer) clearTimeout(this.opentimer);
if (!this.state.visible) return;
this.closetimer = setTimeout(() => this.setState({
visible: false,
selectedItem: null
}), this.props.hoverDelay);
});
_defineProperty(this, "menuRef", c => {
this.menu = c;
});
_defineProperty(this, "subMenuRef", c => {
this.subMenu = c;
});
_defineProperty(this, "registerHandlers", () => {
document.removeEventListener('keydown', this.props.parentKeyNavigationHandler);
document.addEventListener('keydown', this.handleKeyNavigation);
});
_defineProperty(this, "unregisterHandlers", dismounting => {
document.removeEventListener('keydown', this.handleKeyNavigation);
if (!dismounting) {
document.addEventListener('keydown', this.props.parentKeyNavigationHandler);
}
});
this.state = (0, _objectAssign.default)({}, this.state, {
visible: false
});
}
componentDidMount() {
this.listenId = _globalEventListener.default.register(() => {}, this.hideSubMenu);
}
getSubMenuType() {
// eslint-disable-line class-methods-use-this
return SubMenu;
}
shouldComponentUpdate(nextProps, nextState) {
this.isVisibilityChange = (this.state.visible !== nextState.visible || this.props.forceOpen !== nextProps.forceOpen) && !(this.state.visible && nextProps.forceOpen) && !(this.props.forceOpen && nextState.visible);
return true;
}
componentDidUpdate() {
if (!this.isVisibilityChange) return;
if (this.props.forceOpen || this.state.visible) {
const wrapper = window.requestAnimationFrame || setTimeout;
wrapper(() => {
const styles = this.props.rtl ? this.getRTLMenuPosition() : this.getMenuPosition();
this.subMenu.style.removeProperty('top');
this.subMenu.style.removeProperty('bottom');
this.subMenu.style.removeProperty('left');
this.subMenu.style.removeProperty('right');
if ((0, _helpers.hasOwnProp)(styles, 'top')) this.subMenu.style.top = styles.top;
if ((0, _helpers.hasOwnProp)(styles, 'left')) this.subMenu.style.left = styles.left;
if ((0, _helpers.hasOwnProp)(styles, 'bottom')) this.subMenu.style.bottom = styles.bottom;
if ((0, _helpers.hasOwnProp)(styles, 'right')) this.subMenu.style.right = styles.right;
this.subMenu.classList.add(_helpers.cssClasses.menuVisible);
this.registerHandlers();
this.setState({
selectedItem: null
});
});
} else {
const cleanup = () => {
this.subMenu.removeEventListener('transitionend', cleanup);
this.subMenu.style.removeProperty('bottom');
this.subMenu.style.removeProperty('right');
this.subMenu.style.top = 0;
this.subMenu.style.left = '100%';
this.unregisterHandlers();
};
this.subMenu.addEventListener('transitionend', cleanup);
this.subMenu.classList.remove(_helpers.cssClasses.menuVisible);
}
}
componentWillUnmount() {
if (this.listenId) {
_globalEventListener.default.unregister(this.listenId);
}
if (this.opentimer) clearTimeout(this.opentimer);
if (this.closetimer) clearTimeout(this.closetimer);
this.unregisterHandlers(true);
}
render() {
const {
children,
attributes,
disabled,
title,
selected
} = this.props;
const {
visible
} = this.state;
const menuProps = {
ref: this.menuRef,
onMouseEnter: this.handleMouseEnter,
onMouseLeave: this.handleMouseLeave,
className: (0, _classnames.default)(_helpers.cssClasses.menuItem, _helpers.cssClasses.subMenu, attributes.listClassName),
style: {
position: 'relative'
}
};
const menuItemProps = {
className: (0, _classnames.default)(_helpers.cssClasses.menuItem, attributes.className, {
[(0, _classnames.default)(_helpers.cssClasses.menuItemDisabled, attributes.disabledClassName)]: disabled,
[(0, _classnames.default)(_helpers.cssClasses.menuItemActive, attributes.visibleClassName)]: visible,
[(0, _classnames.default)(_helpers.cssClasses.menuItemSelected, attributes.selectedClassName)]: selected
}),
onMouseMove: this.props.onMouseMove,
onMouseOut: this.props.onMouseOut,
onClick: this.handleClick
};
const subMenuProps = {
ref: this.subMenuRef,
style: {
position: 'absolute',
transition: 'opacity 1ms',
// trigger transitionend event
top: 0,
left: '100%'
},
className: (0, _classnames.default)(_helpers.cssClasses.menu, this.props.className)
};
return (
/*#__PURE__*/
// eslint-disable-next-line react/jsx-props-no-spreading
_react.default.createElement("nav", _extends({}, menuProps, {
role: "menuitem",
tabIndex: "-1",
"aria-haspopup": "true"
}), /*#__PURE__*/_react.default.createElement("div", _extends({}, attributes, menuItemProps), title), /*#__PURE__*/_react.default.createElement("nav", _extends({}, subMenuProps, {
role: "menu",
tabIndex: "-1"
}), this.renderChildren(children)))
);
}
}
exports.default = SubMenu;
_defineProperty(SubMenu, "propTypes", {
children: _propTypes.default.node.isRequired,
attributes: _propTypes.default.object,
title: _propTypes.default.node.isRequired,
className: _propTypes.default.string,
disabled: _propTypes.default.bool,
hoverDelay: _propTypes.default.number,
rtl: _propTypes.default.bool,
selected: _propTypes.default.bool,
onMouseMove: _propTypes.default.func,
onMouseOut: _propTypes.default.func,
forceOpen: _propTypes.default.bool,
forceClose: _propTypes.default.func,
parentKeyNavigationHandler: _propTypes.default.func
});
_defineProperty(SubMenu, "defaultProps", {
disabled: false,
hoverDelay: 500,
attributes: {},
className: '',
rtl: false,
selected: false,
onMouseMove: () => null,
onMouseOut: () => null,
forceOpen: false,
forceClose: () => null,
parentKeyNavigationHandler: () => null
});