UNPKG

@jstarpl/react-contextmenu

Version:
297 lines (245 loc) 8.51 kB
"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: {} });