diff --git a/dist/react-contextmenu.js b/dist/react-contextmenu.js new file mode 100644 index 00000000..b484098c --- /dev/null +++ b/dist/react-contextmenu.js @@ -0,0 +1,11 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports.ReactContextMenu=t(require("react")):e.ReactContextMenu=t(e.React)}(this,function(e){return function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=11)}([function(t,n){t.exports=e},function(e,t,n){"use strict";function o(e){for(var t=arguments.length,n=Array(t>1?t-1:0),o=1;o2&&void 0!==arguments[2]?arguments[2]:window,o=void 0;"function"==typeof window.CustomEvent?o=new window.CustomEvent(e,{detail:t}):(o=document.createEvent("CustomEvent"),o.initCustomEvent(e,!1,!0,t)),n&&(n.dispatchEvent(o),u()(s.e,t))}function r(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1];o(c,u()({},e,{type:c}),t)}function i(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1];o(l,u()({},e,{type:l}),t)}n.d(t,"b",function(){return c}),n.d(t,"a",function(){return l}),t.d=r,t.c=i;var a=n(3),u=n.n(a),s=n(1),c="REACT_CONTEXTMENU_SHOW",l="REACT_CONTEXTMENU_HIDE"},function(e,t,n){var o,r;/*! + Copyright (c) 2016 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames +*/ +!function(){"use strict";function n(){for(var e=[],t=0;to?i.bottom=0:i.top=0,r.rightt?r.bottom=0:r.top=0,o.left<0?r.left="100%":r.right="100%",r},n.hideMenu=function(){n.props.forceOpen&&n.props.forceClose(),n.setState({visible:!1,selectedItem:null}),n.unregisterHandlers()},n.handleClick=function(e){e.preventDefault(),n.props.disabled||Object(m.a)(n.props.onClick,e,h()({},n.props.data,m.e.data),m.e.target)},n.handleMouseEnter=function(){n.closetimer&&clearTimeout(n.closetimer),n.props.disabled||n.state.visible||(n.opentimer=setTimeout(function(){return n.setState({visible:!0,selectedItem:null})},n.props.hoverDelay))},n.handleMouseLeave=function(){n.opentimer&&clearTimeout(n.opentimer),n.state.visible&&(n.closetimer=setTimeout(function(){return n.setState({visible:!1,selectedItem:null})},n.props.hoverDelay))},n.menuRef=function(e){n.menu=e},n.subMenuRef=function(e){n.subMenu=e},n.registerHandlers=function(){document.removeEventListener("keydown",n.props.parentKeyNavigationHandler),document.addEventListener("keydown",n.handleKeyNavigation)},n.unregisterHandlers=function(){document.removeEventListener("keydown",n.handleKeyNavigation),document.addEventListener("keydown",n.props.parentKeyNavigationHandler)},n.state=h()({},n.state,{visible:!1}),n}return a(t,e),g(t,[{key:"componentDidMount",value:function(){this.listenId=v.a.register(function(){},this.hideMenu)}},{key:"getSubMenuType",value:function(){return t}},{key:"shouldComponentUpdate",value:function(e,t){return this.isVisibilityChange=!(this.state.visible===t.visible&&this.props.forceOpen===e.forceOpen||this.state.visible&&e.forceOpen||this.props.forceOpen&&t.visible),!0}},{key:"componentDidUpdate",value:function(){var e=this;if(this.isVisibilityChange)if(this.props.forceOpen||this.state.visible){var t=window.requestAnimationFrame||setTimeout;t(function(){var t=e.props.rtl?e.getRTLMenuPosition():e.getMenuPosition();e.subMenu.style.removeProperty("top"),e.subMenu.style.removeProperty("bottom"),e.subMenu.style.removeProperty("left"),e.subMenu.style.removeProperty("right"),Object(m.d)(t,"top")&&(e.subMenu.style.top=t.top),Object(m.d)(t,"left")&&(e.subMenu.style.left=t.left),Object(m.d)(t,"bottom")&&(e.subMenu.style.bottom=t.bottom),Object(m.d)(t,"right")&&(e.subMenu.style.right=t.right),e.subMenu.classList.add(m.c.menuVisible),e.registerHandlers(),e.setState({selectedItem:null})})}else{var n=function t(){e.subMenu.removeEventListener("transitionend",t),e.subMenu.style.removeProperty("bottom"),e.subMenu.style.removeProperty("right"),e.subMenu.style.top=0,e.subMenu.style.left="100%",e.unregisterHandlers()};this.subMenu.addEventListener("transitionend",n),this.subMenu.classList.remove(m.c.menuVisible)}}},{key:"componentWillUnmount",value:function(){this.listenId&&v.a.unregister(this.listenId),this.opentimer&&clearTimeout(this.opentimer),this.closetimer&&clearTimeout(this.closetimer),this.unregisterHandlers()}},{key:"render",value:function(){var e,t=this.props,n=t.children,r=t.attributes,i=t.disabled,a=t.title,u=t.selected,c=this.state.visible,l={ref:this.menuRef,onMouseEnter:this.handleMouseEnter,onMouseLeave:this.handleMouseLeave,className:f()(m.c.menuItem,m.c.subMenu,r.listClassName),style:{position:"relative"}},d={className:f()(m.c.menuItem,r.className,(e={},o(e,f()(m.c.menuItemDisabled,r.disabledClassName),i),o(e,f()(m.c.menuItemActive,r.visibleClassName),c),o(e,f()(m.c.menuItemSelected,r.selectedClassName),u),e)),onMouseMove:this.props.onMouseMove,onMouseOut:this.props.onMouseOut,onClick:this.handleClick},p={ref:this.subMenuRef,style:{position:"absolute",transition:"opacity 1ms",top:0,left:"100%"},className:f()(m.c.menu,this.props.className)};return s.a.createElement("nav",y({},l,{role:"menuitem",tabIndex:"-1","aria-haspopup":"true"}),s.a.createElement("div",y({},r,d),a),s.a.createElement("nav",y({},p,{role:"menu",tabIndex:"-1"}),this.renderChildren(n)))}}]),t}(b.a);O.propTypes={children:l.a.node.isRequired,attributes:l.a.object,title:l.a.node.isRequired,className:l.a.string,disabled:l.a.bool,hoverDelay:l.a.number,rtl:l.a.bool,selected:l.a.bool,onMouseMove:l.a.func,onMouseOut:l.a.func,forceOpen:l.a.bool,forceClose:l.a.func,parentKeyNavigationHandler:l.a.func},O.defaultProps={disabled:!1,hoverDelay:500,attributes:{},className:"",rtl:!1,selected:!1,onMouseMove:function(){return null},onMouseOut:function(){return null},forceOpen:!1,forceClose:function(){return null},parentKeyNavigationHandler:function(){return null}},t.a=O},function(e,t,n){"use strict";function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function i(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var a=n(0),u=n.n(a),s=n(2),c=n.n(s),l=n(5),d=n.n(l),f=n(3),p=n.n(f),h=n(4),b=n(1),m=function(){function e(e,t){for(var n=0;n=0&&0===e.button&&(e.persist(),e.stopPropagation(),i.mouseDownTimeoutId=setTimeout(function(){return i.handleContextClick(e)},i.props.holdToDisplay)),Object(b.a)(i.props.attributes.onMouseDown,e)},i.handleMouseUp=function(e){0===e.button&&clearTimeout(i.mouseDownTimeoutId),Object(b.a)(i.props.attributes.onMouseUp,e)},i.handleMouseOut=function(e){0===e.button&&clearTimeout(i.mouseDownTimeoutId),Object(b.a)(i.props.attributes.onMouseOut,e)},i.handleTouchstart=function(e){i.touchHandled=!1,i.props.holdToDisplay>=0&&(e.persist(),e.stopPropagation(),i.touchstartTimeoutId=setTimeout(function(){i.handleContextClick(e),i.touchHandled=!0},i.props.holdToDisplay)),Object(b.a)(i.props.attributes.onTouchStart,e)},i.handleTouchEnd=function(e){i.touchHandled&&e.preventDefault(),clearTimeout(i.touchstartTimeoutId),Object(b.a)(i.props.attributes.onTouchEnd,e)},i.handleContextMenu=function(e){i.handleContextClick(e),Object(b.a)(i.props.attributes.onContextMenu,e)},i.handleContextClick=function(e){if(!i.props.disable){e.preventDefault(),e.stopPropagation();var t=e.clientX||e.touches&&e.touches[0].pageX,n=e.clientY||e.touches&&e.touches[0].pageY;Object(h.c)();var o=Object(b.a)(i.props.collect,i.props),r={position:{x:t,y:n},target:i.elem,id:i.props.id,data:o};o&&"function"==typeof o.then?o.then(function(e){r.data=e,Object(h.d)(r)}):Object(h.d)(r)}},i.elemRef=function(e){i.elem=e},a=n,r(i,a)}return i(t,e),m(t,[{key:"render",value:function(){var e=this.props,t=e.renderTag,n=e.attributes,o=e.children,r=p()({},n,{className:d()(b.c.menuWrapper,n.className),onContextMenu:this.handleContextMenu,onMouseDown:this.handleMouseDown,onMouseUp:this.handleMouseUp,onTouchStart:this.handleTouchstart,onTouchEnd:this.handleTouchEnd,onMouseOut:this.handleMouseOut,ref:this.elemRef});return u.a.createElement(t,r,o)}}]),t}(a.Component);v.propTypes={id:c.a.string.isRequired,children:c.a.node.isRequired,attributes:c.a.object,collect:c.a.func,disable:c.a.bool,holdToDisplay:c.a.number,renderTag:c.a.oneOfType([c.a.node,c.a.func])},v.defaultProps={attributes:{},collect:function(){return null},disable:!1,holdToDisplay:1e3,renderTag:"div"},t.a=v},function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var o=n(12);n.d(t,"ContextMenu",function(){return o.a});var r=n(10);n.d(t,"ContextMenuTrigger",function(){return r.a});var i=n(8);n.d(t,"MenuItem",function(){return i.a});var a=n(9);n.d(t,"SubMenu",function(){return a.a});var u=n(17);n.d(t,"connectMenu",function(){return u.a});var s=n(4);n.d(t,"hideMenu",function(){return s.c}),n.d(t,"showMenu",function(){return s.d})},function(e,t,n){"use strict";function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function i(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}var u=n(0),s=n.n(u),c=n(2),l=n.n(c),d=n(5),f=n.n(d),p=n(3),h=n.n(p),b=n(6),m=n(7),v=n(9),y=n(4),g=n(1),O=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:0,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,o={top:t,left:e};if(!n.menu)return o;var r=window,i=r.innerWidth,a=r.innerHeight,u=n.menu.getBoundingClientRect();return t+u.height>a&&(o.top-=u.height),e+u.width>i&&(o.left-=u.width),o.top<0&&(o.top=u.height 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.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; + + if (this.state.isVisible) { + var wrapper = window.requestAnimationFrame || setTimeout; + + wrapper(function () { + var _state = _this2.state, + x = _state.x, + y = _state.y; + + var _getMenuPosition = _this2.getMenuPosition(x, y), + top = _getMenuPosition.top, + left = _getMenuPosition.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 { + if (!this.menu) return; + this.menu.style.opacity = 0; + this.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, + onHide: PropTypes.func, + onMouseLeave: PropTypes.func, + onShow: PropTypes.func, + style: PropTypes.object +}; +ContextMenu.defaultProps = { + className: '', + data: {}, + hideOnLeave: false, + onHide: function onHide() { + return null; + }, + onMouseLeave: function onMouseLeave() { + return null; + }, + onShow: function onShow() { + return null; + }, + + style: {} +}; +export default ContextMenu; \ No newline at end of file diff --git a/es6/ContextMenuTrigger.js b/es6/ContextMenuTrigger.js new file mode 100644 index 00000000..d2158ec9 --- /dev/null +++ b/es6/ContextMenuTrigger.js @@ -0,0 +1,150 @@ +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 _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, { Component } from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import assign from 'object-assign'; + +import { showMenu, hideMenu } from './actions'; +import { callIfExists, cssClasses } from './helpers'; + +var ContextMenuTrigger = function (_Component) { + _inherits(ContextMenuTrigger, _Component); + + function ContextMenuTrigger() { + var _ref; + + var _temp, _this, _ret; + + _classCallCheck(this, ContextMenuTrigger); + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = ContextMenuTrigger.__proto__ || Object.getPrototypeOf(ContextMenuTrigger)).call.apply(_ref, [this].concat(args))), _this), _this.touchHandled = false, _this.handleMouseDown = function (event) { + if (_this.props.holdToDisplay >= 0 && event.button === 0) { + event.persist(); + event.stopPropagation(); + + _this.mouseDownTimeoutId = setTimeout(function () { + return _this.handleContextClick(event); + }, _this.props.holdToDisplay); + } + callIfExists(_this.props.attributes.onMouseDown, event); + }, _this.handleMouseUp = function (event) { + if (event.button === 0) { + clearTimeout(_this.mouseDownTimeoutId); + } + callIfExists(_this.props.attributes.onMouseUp, event); + }, _this.handleMouseOut = function (event) { + if (event.button === 0) { + clearTimeout(_this.mouseDownTimeoutId); + } + callIfExists(_this.props.attributes.onMouseOut, event); + }, _this.handleTouchstart = function (event) { + _this.touchHandled = false; + + if (_this.props.holdToDisplay >= 0) { + event.persist(); + event.stopPropagation(); + + _this.touchstartTimeoutId = setTimeout(function () { + _this.handleContextClick(event); + _this.touchHandled = true; + }, _this.props.holdToDisplay); + } + callIfExists(_this.props.attributes.onTouchStart, event); + }, _this.handleTouchEnd = function (event) { + if (_this.touchHandled) { + event.preventDefault(); + } + clearTimeout(_this.touchstartTimeoutId); + callIfExists(_this.props.attributes.onTouchEnd, event); + }, _this.handleContextMenu = function (event) { + _this.handleContextClick(event); + callIfExists(_this.props.attributes.onContextMenu, event); + }, _this.handleContextClick = function (event) { + if (_this.props.disable) return; + + event.preventDefault(); + event.stopPropagation(); + + var x = event.clientX || event.touches && event.touches[0].pageX; + var y = event.clientY || event.touches && event.touches[0].pageY; + + hideMenu(); + + var data = callIfExists(_this.props.collect, _this.props); + var showMenuConfig = { + position: { x: x, y: y }, + target: _this.elem, + id: _this.props.id, + data: data + }; + if (data && typeof data.then === 'function') { + // it's promise + data.then(function (resp) { + showMenuConfig.data = resp; + showMenu(showMenuConfig); + }); + } else { + showMenu(showMenuConfig); + } + }, _this.elemRef = function (c) { + _this.elem = c; + }, _temp), _possibleConstructorReturn(_this, _ret); + } + + _createClass(ContextMenuTrigger, [{ + key: 'render', + value: function render() { + var _props = this.props, + renderTag = _props.renderTag, + attributes = _props.attributes, + children = _props.children; + + var newAttrs = assign({}, attributes, { + className: cx(cssClasses.menuWrapper, attributes.className), + onContextMenu: this.handleContextMenu, + onMouseDown: this.handleMouseDown, + onMouseUp: this.handleMouseUp, + onTouchStart: this.handleTouchstart, + onTouchEnd: this.handleTouchEnd, + onMouseOut: this.handleMouseOut, + ref: this.elemRef + }); + + return React.createElement(renderTag, newAttrs, children); + } + }]); + + return ContextMenuTrigger; +}(Component); + +ContextMenuTrigger.propTypes = { + id: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, + attributes: PropTypes.object, + collect: PropTypes.func, + disable: PropTypes.bool, + holdToDisplay: PropTypes.number, + renderTag: PropTypes.oneOfType([PropTypes.node, PropTypes.func]) +}; +ContextMenuTrigger.defaultProps = { + attributes: {}, + collect: function collect() { + return null; + }, + + disable: false, + holdToDisplay: 1000, + renderTag: 'div' +}; +export default ContextMenuTrigger; \ No newline at end of file diff --git a/es6/MenuItem.js b/es6/MenuItem.js new file mode 100644 index 00000000..41037a39 --- /dev/null +++ b/es6/MenuItem.js @@ -0,0 +1,112 @@ +var _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; }; + +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, { Component } from 'react'; +import PropTypes from 'prop-types'; +import cx from 'classnames'; +import assign from 'object-assign'; + +import { hideMenu } from './actions'; +import { callIfExists, cssClasses, store } from './helpers'; + +var MenuItem = function (_Component) { + _inherits(MenuItem, _Component); + + function MenuItem() { + var _ref; + + var _temp, _this, _ret; + + _classCallCheck(this, MenuItem); + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = MenuItem.__proto__ || Object.getPrototypeOf(MenuItem)).call.apply(_ref, [this].concat(args))), _this), _this.handleClick = function (event) { + event.preventDefault(); + + if (_this.props.disabled || _this.props.divider) return; + + callIfExists(_this.props.onClick, event, assign({}, _this.props.data, store.data), store.target); + + if (_this.props.preventClose) return; + + hideMenu(); + }, _temp), _possibleConstructorReturn(_this, _ret); + } + + _createClass(MenuItem, [{ + key: 'render', + value: function render() { + var _cx, + _this2 = this; + + var _props = this.props, + disabled = _props.disabled, + divider = _props.divider, + children = _props.children, + attributes = _props.attributes, + selected = _props.selected; + + var menuItemClassNames = cx(cssClasses.menuItem, attributes && attributes.className, (_cx = {}, _defineProperty(_cx, cssClasses.menuItemDisabled, disabled), _defineProperty(_cx, cssClasses.menuItemDivider, divider), _defineProperty(_cx, cssClasses.menuItemSelected, selected), _cx)); + + return React.createElement( + 'div', + _extends({}, attributes, { className: menuItemClassNames, + role: 'menuitem', tabIndex: '-1', 'aria-disabled': disabled ? 'true' : 'false', + 'aria-orientation': divider ? 'horizontal' : null, + ref: function ref(_ref2) { + _this2.ref = _ref2; + }, + onMouseMove: this.props.onMouseMove, onMouseLeave: this.props.onMouseLeave, + onTouchEnd: this.handleClick, onClick: this.handleClick }), + divider ? null : children + ); + } + }]); + + return MenuItem; +}(Component); + +MenuItem.propTypes = { + children: PropTypes.node, + attributes: PropTypes.object, + data: PropTypes.object, + disabled: PropTypes.bool, + divider: PropTypes.bool, + preventClose: PropTypes.bool, + onClick: PropTypes.func, + selected: PropTypes.bool, + onMouseMove: PropTypes.func, + onMouseLeave: PropTypes.func +}; +MenuItem.defaultProps = { + disabled: false, + data: {}, + divider: false, + attributes: {}, + preventClose: false, + onClick: function onClick() { + return null; + }, + + children: null, + selected: false, + onMouseMove: function onMouseMove() { + return null; + }, + onMouseLeave: function onMouseLeave() { + return null; + } +}; +export default MenuItem; \ No newline at end of file diff --git a/es6/SubMenu.js b/es6/SubMenu.js new file mode 100644 index 00000000..20448c5d --- /dev/null +++ b/es6/SubMenu.js @@ -0,0 +1,306 @@ +var _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; }; + +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 AbstractMenu from './AbstractMenu'; +import { hideMenu } from './actions'; +import { callIfExists, cssClasses, hasOwnProp, store } from './helpers'; +import listener from './globalEventListener'; + +var SubMenu = function (_AbstractMenu) { + _inherits(SubMenu, _AbstractMenu); + + function SubMenu(props) { + _classCallCheck(this, SubMenu); + + var _this = _possibleConstructorReturn(this, (SubMenu.__proto__ || Object.getPrototypeOf(SubMenu)).call(this, props)); + + _this.getMenuPosition = function () { + var _window = window, + innerWidth = _window.innerWidth, + innerHeight = _window.innerHeight; + + var rect = _this.subMenu.getBoundingClientRect(); + var 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; + }; + + _this.getRTLMenuPosition = function () { + var _window2 = window, + innerHeight = _window2.innerHeight; + + var rect = _this.subMenu.getBoundingClientRect(); + var position = {}; + + if (rect.bottom > innerHeight) { + position.bottom = 0; + } else { + position.top = 0; + } + + if (rect.left < 0) { + position.left = '100%'; + } else { + position.right = '100%'; + } + + return position; + }; + + _this.hideMenu = function () { + if (_this.props.forceOpen) { + _this.props.forceClose(); + } + _this.setState({ visible: false, selectedItem: null }); + _this.unregisterHandlers(); + }; + + _this.handleClick = function (event) { + event.preventDefault(); + + if (_this.props.disabled) return; + + callIfExists(_this.props.onClick, event, assign({}, _this.props.data, store.data), store.target); + + if (_this.props.preventClose) return; + + hideMenu(); + }; + + _this.handleMouseEnter = function () { + if (_this.closetimer) clearTimeout(_this.closetimer); + + if (_this.props.disabled || _this.state.visible) return; + + _this.opentimer = setTimeout(function () { + return _this.setState({ + visible: true, + selectedItem: null + }); + }, _this.props.hoverDelay); + }; + + _this.handleMouseLeave = function () { + if (_this.opentimer) clearTimeout(_this.opentimer); + + if (!_this.state.visible) return; + + _this.closetimer = setTimeout(function () { + return _this.setState({ + visible: false, + selectedItem: null + }); + }, _this.props.hoverDelay); + }; + + _this.menuRef = function (c) { + _this.menu = c; + }; + + _this.subMenuRef = function (c) { + _this.subMenu = c; + }; + + _this.registerHandlers = function () { + document.removeEventListener('keydown', _this.props.parentKeyNavigationHandler); + document.addEventListener('keydown', _this.handleKeyNavigation); + }; + + _this.unregisterHandlers = function () { + document.removeEventListener('keydown', _this.handleKeyNavigation); + document.addEventListener('keydown', _this.props.parentKeyNavigationHandler); + }; + + _this.state = assign({}, _this.state, { + visible: false + }); + return _this; + } + + _createClass(SubMenu, [{ + key: 'componentDidMount', + value: function componentDidMount() { + this.listenId = listener.register(function () {}, this.hideMenu); + } + }, { + key: 'getSubMenuType', + value: function getSubMenuType() { + // eslint-disable-line class-methods-use-this + return SubMenu; + } + }, { + key: 'shouldComponentUpdate', + value: function 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; + } + }, { + key: 'componentDidUpdate', + value: function componentDidUpdate() { + var _this2 = this; + + if (!this.isVisibilityChange) return; + if (this.props.forceOpen || this.state.visible) { + var wrapper = window.requestAnimationFrame || setTimeout; + wrapper(function () { + var styles = _this2.props.rtl ? _this2.getRTLMenuPosition() : _this2.getMenuPosition(); + + _this2.subMenu.style.removeProperty('top'); + _this2.subMenu.style.removeProperty('bottom'); + _this2.subMenu.style.removeProperty('left'); + _this2.subMenu.style.removeProperty('right'); + + if (hasOwnProp(styles, 'top')) _this2.subMenu.style.top = styles.top; + if (hasOwnProp(styles, 'left')) _this2.subMenu.style.left = styles.left; + if (hasOwnProp(styles, 'bottom')) _this2.subMenu.style.bottom = styles.bottom; + if (hasOwnProp(styles, 'right')) _this2.subMenu.style.right = styles.right; + _this2.subMenu.classList.add(cssClasses.menuVisible); + + _this2.registerHandlers(); + _this2.setState({ selectedItem: null }); + }); + } else { + var cleanup = function cleanup() { + _this2.subMenu.removeEventListener('transitionend', cleanup); + _this2.subMenu.style.removeProperty('bottom'); + _this2.subMenu.style.removeProperty('right'); + _this2.subMenu.style.top = 0; + _this2.subMenu.style.left = '100%'; + _this2.unregisterHandlers(); + }; + this.subMenu.addEventListener('transitionend', cleanup); + this.subMenu.classList.remove(cssClasses.menuVisible); + } + } + }, { + key: 'componentWillUnmount', + value: function componentWillUnmount() { + if (this.listenId) { + listener.unregister(this.listenId); + } + + if (this.opentimer) clearTimeout(this.opentimer); + + if (this.closetimer) clearTimeout(this.closetimer); + + this.unregisterHandlers(); + } + }, { + key: 'render', + value: function render() { + var _cx; + + var _props = this.props, + children = _props.children, + disabled = _props.disabled, + title = _props.title, + selected = _props.selected; + var visible = this.state.visible; + + var menuProps = { + ref: this.menuRef, + onMouseEnter: this.handleMouseEnter, + onMouseLeave: this.handleMouseLeave, + className: cx(cssClasses.menuItem, cssClasses.subMenu), + style: { + position: 'relative' + } + }; + var menuItemProps = { + className: cx(cssClasses.menuItem, (_cx = {}, _defineProperty(_cx, cssClasses.menuItemDisabled, disabled), _defineProperty(_cx, cssClasses.menuItemActive, visible), _defineProperty(_cx, cssClasses.menuItemSelected, selected), _cx)), + onMouseMove: this.props.onMouseMove, + onMouseOut: this.props.onMouseOut, + onClick: this.handleClick + }; + var subMenuProps = { + ref: this.subMenuRef, + style: { + position: 'absolute', + transition: 'opacity 1ms', // trigger transitionend event + top: 0, + left: '100%' + }, + className: cx(cssClasses.menu, this.props.className) + }; + + return React.createElement( + 'nav', + _extends({}, menuProps, { role: 'menuitem', tabIndex: '-1', 'aria-haspopup': 'true' }), + React.createElement( + 'div', + menuItemProps, + title + ), + React.createElement( + 'nav', + _extends({}, subMenuProps, { role: 'menu', tabIndex: '-1' }), + this.renderChildren(children) + ) + ); + } + }]); + + return SubMenu; +}(AbstractMenu); + +SubMenu.propTypes = { + children: PropTypes.node.isRequired, + title: PropTypes.node.isRequired, + className: PropTypes.string, + disabled: PropTypes.bool, + hoverDelay: PropTypes.number, + rtl: PropTypes.bool, + selected: PropTypes.bool, + onMouseMove: PropTypes.func, + onMouseOut: PropTypes.func, + forceOpen: PropTypes.bool, + forceClose: PropTypes.func, + parentKeyNavigationHandler: PropTypes.func +}; +SubMenu.defaultProps = { + disabled: false, + hoverDelay: 500, + className: '', + rtl: false, + selected: false, + onMouseMove: function onMouseMove() { + return null; + }, + onMouseOut: function onMouseOut() { + return null; + }, + forceOpen: false, + forceClose: function forceClose() { + return null; + }, + parentKeyNavigationHandler: function parentKeyNavigationHandler() { + return null; + } +}; +export default SubMenu; \ No newline at end of file diff --git a/es6/actions.js b/es6/actions.js new file mode 100644 index 00000000..fdee9e67 --- /dev/null +++ b/es6/actions.js @@ -0,0 +1,40 @@ +import assign from 'object-assign'; + +import { store } from './helpers'; + +export var MENU_SHOW = 'REACT_CONTEXTMENU_SHOW'; +export var MENU_HIDE = 'REACT_CONTEXTMENU_HIDE'; + +export function dispatchGlobalEvent(eventName, opts) { + var target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : window; + + // Compatibale with IE + // @see http://stackoverflow.com/questions/26596123/internet-explorer-9-10-11-event-constructor-doesnt-work + var event = void 0; + + if (typeof window.CustomEvent === 'function') { + event = new window.CustomEvent(eventName, { detail: opts }); + } else { + event = document.createEvent('CustomEvent'); + event.initCustomEvent(eventName, false, true, opts); + } + + if (target) { + target.dispatchEvent(event); + assign(store, opts); + } +} + +export function showMenu() { + var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var target = arguments[1]; + + dispatchGlobalEvent(MENU_SHOW, assign({}, opts, { type: MENU_SHOW }), target); +} + +export function hideMenu() { + var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var target = arguments[1]; + + dispatchGlobalEvent(MENU_HIDE, assign({}, opts, { type: MENU_HIDE }), target); +} \ No newline at end of file diff --git a/es6/connectMenu.js b/es6/connectMenu.js new file mode 100644 index 00000000..f879bb75 --- /dev/null +++ b/es6/connectMenu.js @@ -0,0 +1,82 @@ +var _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; }; + +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 _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; } + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +import React, { Component } from 'react'; + +import ContextMenuTrigger from './ContextMenuTrigger'; +import listener from './globalEventListener'; + +// collect ContextMenuTrigger's expected props to NOT pass them on as part of the context +var ignoredTriggerProps = [].concat(_toConsumableArray(Object.keys(ContextMenuTrigger.propTypes)), ['children']); + +// expect the id of the menu to be responsible for as outer parameter +export default function (menuId) { + // expect menu component to connect as inner parameter + // is presumably a wrapper of + return function (Child) { + // return wrapper for that forwards the ContextMenuTrigger's additional props + return function (_Component) { + _inherits(ConnectMenu, _Component); + + function ConnectMenu(props) { + _classCallCheck(this, ConnectMenu); + + var _this = _possibleConstructorReturn(this, (ConnectMenu.__proto__ || Object.getPrototypeOf(ConnectMenu)).call(this, props)); + + _this.handleShow = function (e) { + if (e.detail.id !== menuId) return; + + // the onShow event's detail.data object holds all ContextMenuTrigger props + var data = e.detail.data; + + var filteredData = {}; + + for (var key in data) { + // exclude props the ContextMenuTrigger is expecting itself + if (!ignoredTriggerProps.includes(key)) { + filteredData[key] = data[key]; + } + } + _this.setState({ trigger: filteredData }); + }; + + _this.handleHide = function () { + _this.setState({ trigger: null }); + }; + + _this.state = { trigger: null }; + return _this; + } + + _createClass(ConnectMenu, [{ + key: 'componentDidMount', + value: function componentDidMount() { + this.listenId = listener.register(this.handleShow, this.handleHide); + } + }, { + key: 'componentWillUnmount', + value: function componentWillUnmount() { + if (this.listenId) { + listener.unregister(this.listenId); + } + } + }, { + key: 'render', + value: function render() { + return React.createElement(Child, _extends({}, this.props, { id: menuId, trigger: this.state.trigger })); + } + }]); + + return ConnectMenu; + }(Component); + }; +} \ No newline at end of file diff --git a/es6/globalEventListener.js b/es6/globalEventListener.js new file mode 100644 index 00000000..03492f0d --- /dev/null +++ b/es6/globalEventListener.js @@ -0,0 +1,48 @@ +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +import { MENU_SHOW, MENU_HIDE } from './actions'; +import { uniqueId, hasOwnProp, canUseDOM } from './helpers'; + +var GlobalEventListener = function GlobalEventListener() { + var _this = this; + + _classCallCheck(this, GlobalEventListener); + + this.handleShowEvent = function (event) { + for (var id in _this.callbacks) { + if (hasOwnProp(_this.callbacks, id)) _this.callbacks[id].show(event); + } + }; + + this.handleHideEvent = function (event) { + for (var id in _this.callbacks) { + if (hasOwnProp(_this.callbacks, id)) _this.callbacks[id].hide(event); + } + }; + + this.register = function (showCallback, hideCallback) { + var id = uniqueId(); + + _this.callbacks[id] = { + show: showCallback, + hide: hideCallback + }; + + return id; + }; + + this.unregister = function (id) { + if (id && _this.callbacks[id]) { + delete _this.callbacks[id]; + } + }; + + this.callbacks = {}; + + if (canUseDOM) { + window.addEventListener(MENU_SHOW, this.handleShowEvent); + window.addEventListener(MENU_HIDE, this.handleHideEvent); + } +}; + +export default new GlobalEventListener(); \ No newline at end of file diff --git a/es6/helpers.js b/es6/helpers.js new file mode 100644 index 00000000..1b1e6b92 --- /dev/null +++ b/es6/helpers.js @@ -0,0 +1,31 @@ +export function callIfExists(func) { + for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + return typeof func === 'function' && func.apply(undefined, args); +} + +export function hasOwnProp(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +export function uniqueId() { + return Math.random().toString(36).substring(7); +} + +export var cssClasses = { + menu: 'react-contextmenu', + menuVisible: 'react-contextmenu--visible', + menuWrapper: 'react-contextmenu-wrapper', + menuItem: 'react-contextmenu-item', + menuItemActive: 'react-contextmenu-item--active', + menuItemDisabled: 'react-contextmenu-item--disabled', + menuItemDivider: 'react-contextmenu-item--divider', + menuItemSelected: 'react-contextmenu-item--selected', + subMenu: 'react-contextmenu-submenu' +}; + +export var store = {}; + +export var canUseDOM = Boolean(typeof window !== 'undefined' && window.document && window.document.createElement); \ No newline at end of file diff --git a/es6/index.js b/es6/index.js new file mode 100644 index 00000000..623c1d2a --- /dev/null +++ b/es6/index.js @@ -0,0 +1,6 @@ +export { default as ContextMenu } from './ContextMenu'; +export { default as ContextMenuTrigger } from './ContextMenuTrigger'; +export { default as MenuItem } from './MenuItem'; +export { default as SubMenu } from './SubMenu'; +export { default as connectMenu } from './connectMenu'; +export { hideMenu, showMenu } from './actions'; \ No newline at end of file diff --git a/modules/AbstractMenu.js b/modules/AbstractMenu.js new file mode 100644 index 00000000..b229c74e --- /dev/null +++ b/modules/AbstractMenu.js @@ -0,0 +1,195 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _propTypes = require('prop-types'); + +var _propTypes2 = _interopRequireDefault(_propTypes); + +var _MenuItem = require('./MenuItem'); + +var _MenuItem2 = _interopRequireDefault(_MenuItem); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: 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; } + +var AbstractMenu = function (_Component) { + _inherits(AbstractMenu, _Component); + + function AbstractMenu(props) { + _classCallCheck(this, AbstractMenu); + + var _this = _possibleConstructorReturn(this, (AbstractMenu.__proto__ || Object.getPrototypeOf(AbstractMenu)).call(this, props)); + + _initialiseProps.call(_this); + + _this.seletedItemRef = null; + _this.state = { + selectedItem: null, + forceSubMenuOpen: false + }; + return _this; + } + + return AbstractMenu; +}(_react.Component); + +AbstractMenu.propTypes = { + children: _propTypes2.default.node.isRequired +}; + +var _initialiseProps = function _initialiseProps() { + var _this2 = this; + + this.handleKeyNavigation = function (e) { + // check for isVisible strictly here as it might be undefined when this code executes in the context of SubMenu + // but we only need to check when it runs in the ContextMenu context + if (_this2.state.isVisible === false) { + return; + } + + switch (e.keyCode) { + case 37: // left arrow + case 27: + // escape + e.preventDefault(); + _this2.hideMenu(e); + break; + case 38: + // up arrow + e.preventDefault(); + _this2.selectChildren(true); + break; + case 40: + // down arrow + e.preventDefault(); + _this2.selectChildren(false); + break; + case 39: + // right arrow + _this2.tryToOpenSubMenu(e); + break; + case 13: + // enter + e.preventDefault(); + _this2.tryToOpenSubMenu(e); + { + // determine the selected item is disabled or not + var disabled = _this2.seletedItemRef && _this2.seletedItemRef.props && _this2.seletedItemRef.props.disabled; + + if (_this2.seletedItemRef && _this2.seletedItemRef.ref instanceof HTMLElement && !disabled) { + _this2.seletedItemRef.ref.click(); + } else { + _this2.hideMenu(e); + } + } + break; + default: + // do nothing + } + }; + + this.handleForceClose = function () { + _this2.setState({ forceSubMenuOpen: false }); + }; + + this.tryToOpenSubMenu = function (e) { + if (_this2.state.selectedItem && _this2.state.selectedItem.type === _this2.getSubMenuType()) { + e.preventDefault(); + _this2.setState({ forceSubMenuOpen: true }); + } + }; + + this.selectChildren = function (forward) { + var selectedItem = _this2.state.selectedItem; + + var children = []; + var childCollector = function childCollector(child) { + // child can be empty in case you do conditional rendering of components, in which + // case it should not be accounted for as a real child + if (!child) { + return; + } + + if ([_MenuItem2.default, _this2.getSubMenuType()].indexOf(child.type) < 0) { + // Maybe the MenuItem or SubMenu is capsuled in a wrapper div or something else + _react2.default.Children.forEach(child.props.children, childCollector); + } else if (!child.props.divider) { + children.push(child); + } + }; + _react2.default.Children.forEach(_this2.props.children, childCollector); + var currentIndex = children.indexOf(selectedItem); + if (currentIndex < 0) { + _this2.setState({ + selectedItem: forward ? children[children.length - 1] : children[0], + forceSubMenuOpen: false + }); + } else if (forward) { + _this2.setState({ + selectedItem: children[currentIndex - 1 < 0 ? children.length - 1 : currentIndex - 1], + forceSubMenuOpen: false + }); + } else { + _this2.setState({ + selectedItem: children[currentIndex + 1 < children.length ? currentIndex + 1 : 0], + forceSubMenuOpen: false + }); + } + }; + + this.onChildMouseMove = function (child) { + if (_this2.state.selectedItem !== child) { + _this2.setState({ selectedItem: child, forceSubMenuOpen: false }); + } + }; + + this.onChildMouseLeave = function () { + _this2.setState({ selectedItem: null, forceSubMenuOpen: false }); + }; + + this.renderChildren = function (children) { + return _react2.default.Children.map(children, function (child) { + var props = {}; + if (!_react2.default.isValidElement(child)) return child; + if ([_MenuItem2.default, _this2.getSubMenuType()].indexOf(child.type) < 0) { + // Maybe the MenuItem or SubMenu is capsuled in a wrapper div or something else + props.children = _this2.renderChildren(child.props.children); + return _react2.default.cloneElement(child, props); + } + props.onMouseLeave = _this2.onChildMouseLeave.bind(_this2); + if (child.type === _this2.getSubMenuType()) { + // special props for SubMenu only + props.forceOpen = _this2.state.forceSubMenuOpen && _this2.state.selectedItem === child; + props.forceClose = _this2.handleForceClose; + props.parentKeyNavigationHandler = _this2.handleKeyNavigation; + } + if (!child.props.divider && _this2.state.selectedItem === child) { + // special props for selected item only + props.selected = true; + props.ref = function (ref) { + _this2.seletedItemRef = ref; + }; + return _react2.default.cloneElement(child, props); + } + // onMouseMove is only needed for non selected items + props.onMouseMove = function () { + return _this2.onChildMouseMove(child); + }; + return _react2.default.cloneElement(child, props); + }); + }; +}; + +exports.default = AbstractMenu; \ No newline at end of file diff --git a/modules/ContextMenu.js b/modules/ContextMenu.js new file mode 100644 index 00000000..ad3d19fe --- /dev/null +++ b/modules/ContextMenu.js @@ -0,0 +1,275 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +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; }; }(); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _propTypes = require('prop-types'); + +var _propTypes2 = _interopRequireDefault(_propTypes); + +var _classnames = require('classnames'); + +var _classnames2 = _interopRequireDefault(_classnames); + +var _objectAssign = require('object-assign'); + +var _objectAssign2 = _interopRequireDefault(_objectAssign); + +var _globalEventListener = require('./globalEventListener'); + +var _globalEventListener2 = _interopRequireDefault(_globalEventListener); + +var _AbstractMenu2 = require('./AbstractMenu'); + +var _AbstractMenu3 = _interopRequireDefault(_AbstractMenu2); + +var _SubMenu = require('./SubMenu'); + +var _SubMenu2 = _interopRequireDefault(_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; } + +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; } + +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('ontouchstart', _this.handleOutsideClick); + document.addEventListener('scroll', _this.handleHide); + document.addEventListener('contextmenu', _this.handleHide); + document.addEventListener('keydown', _this.handleKeyNavigation); + window.addEventListener('resize', _this.handleHide); + }; + + _this.unregisterHandlers = function () { + document.removeEventListener('mousedown', _this.handleOutsideClick); + document.removeEventListener('ontouchstart', _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(); + (0, _helpers.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 }); + (0, _helpers.callIfExists)(_this.props.onHide, e); + } + }; + + _this.handleOutsideClick = function (e) { + if (!_this.menu.contains(e.target)) (0, _actions.hideMenu)(); + }; + + _this.handleMouseLeave = function (event) { + event.preventDefault(); + + (0, _helpers.callIfExists)(_this.props.onMouseLeave, event, (0, _objectAssign2.default)({}, _this.props.data, _helpers.store.data), _helpers.store.target); + + if (_this.props.hideOnLeave) (0, _actions.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 + (0, _actions.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.menuRef = function (c) { + _this.menu = c; + }; + + _this.state = (0, _objectAssign2.default)({}, _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 _SubMenu2.default; + } + }, { + key: 'componentDidMount', + value: function componentDidMount() { + this.listenId = _globalEventListener2.default.register(this.handleShow, this.handleHide); + } + }, { + key: 'componentDidUpdate', + value: function componentDidUpdate() { + var _this2 = this; + + if (this.state.isVisible) { + var wrapper = window.requestAnimationFrame || setTimeout; + + wrapper(function () { + var _state = _this2.state, + x = _state.x, + y = _state.y; + + var _getMenuPosition = _this2.getMenuPosition(x, y), + top = _getMenuPosition.top, + left = _getMenuPosition.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 { + if (!this.menu) return; + this.menu.style.opacity = 0; + this.menu.style.pointerEvents = 'none'; + } + } + }, { + key: 'componentWillUnmount', + value: function componentWillUnmount() { + if (this.listenId) { + _globalEventListener2.default.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 = (0, _objectAssign2.default)({}, style, { position: 'fixed', opacity: 0, pointerEvents: 'none' }); + var menuClassnames = (0, _classnames2.default)(_helpers.cssClasses.menu, className, _defineProperty({}, _helpers.cssClasses.menuVisible, isVisible)); + + return _react2.default.createElement( + 'nav', + { + role: 'menu', tabIndex: '-1', ref: this.menuRef, style: inlineStyle, className: menuClassnames, + onContextMenu: this.handleContextMenu, onMouseLeave: this.handleMouseLeave }, + this.renderChildren(children) + ); + } + }]); + + return ContextMenu; +}(_AbstractMenu3.default); + +ContextMenu.propTypes = { + id: _propTypes2.default.string.isRequired, + children: _propTypes2.default.node.isRequired, + data: _propTypes2.default.object, + className: _propTypes2.default.string, + hideOnLeave: _propTypes2.default.bool, + onHide: _propTypes2.default.func, + onMouseLeave: _propTypes2.default.func, + onShow: _propTypes2.default.func, + style: _propTypes2.default.object +}; +ContextMenu.defaultProps = { + className: '', + data: {}, + hideOnLeave: false, + onHide: function onHide() { + return null; + }, + onMouseLeave: function onMouseLeave() { + return null; + }, + onShow: function onShow() { + return null; + }, + + style: {} +}; +exports.default = ContextMenu; \ No newline at end of file diff --git a/modules/ContextMenuTrigger.js b/modules/ContextMenuTrigger.js new file mode 100644 index 00000000..cbe208a9 --- /dev/null +++ b/modules/ContextMenuTrigger.js @@ -0,0 +1,170 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +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; }; }(); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _propTypes = require('prop-types'); + +var _propTypes2 = _interopRequireDefault(_propTypes); + +var _classnames = require('classnames'); + +var _classnames2 = _interopRequireDefault(_classnames); + +var _objectAssign = require('object-assign'); + +var _objectAssign2 = _interopRequireDefault(_objectAssign); + +var _actions = require('./actions'); + +var _helpers = require('./helpers'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: 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; } + +var ContextMenuTrigger = function (_Component) { + _inherits(ContextMenuTrigger, _Component); + + function ContextMenuTrigger() { + var _ref; + + var _temp, _this, _ret; + + _classCallCheck(this, ContextMenuTrigger); + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = ContextMenuTrigger.__proto__ || Object.getPrototypeOf(ContextMenuTrigger)).call.apply(_ref, [this].concat(args))), _this), _this.touchHandled = false, _this.handleMouseDown = function (event) { + if (_this.props.holdToDisplay >= 0 && event.button === 0) { + event.persist(); + event.stopPropagation(); + + _this.mouseDownTimeoutId = setTimeout(function () { + return _this.handleContextClick(event); + }, _this.props.holdToDisplay); + } + (0, _helpers.callIfExists)(_this.props.attributes.onMouseDown, event); + }, _this.handleMouseUp = function (event) { + if (event.button === 0) { + clearTimeout(_this.mouseDownTimeoutId); + } + (0, _helpers.callIfExists)(_this.props.attributes.onMouseUp, event); + }, _this.handleMouseOut = function (event) { + if (event.button === 0) { + clearTimeout(_this.mouseDownTimeoutId); + } + (0, _helpers.callIfExists)(_this.props.attributes.onMouseOut, event); + }, _this.handleTouchstart = function (event) { + _this.touchHandled = false; + + if (_this.props.holdToDisplay >= 0) { + event.persist(); + event.stopPropagation(); + + _this.touchstartTimeoutId = setTimeout(function () { + _this.handleContextClick(event); + _this.touchHandled = true; + }, _this.props.holdToDisplay); + } + (0, _helpers.callIfExists)(_this.props.attributes.onTouchStart, event); + }, _this.handleTouchEnd = function (event) { + if (_this.touchHandled) { + event.preventDefault(); + } + clearTimeout(_this.touchstartTimeoutId); + (0, _helpers.callIfExists)(_this.props.attributes.onTouchEnd, event); + }, _this.handleContextMenu = function (event) { + _this.handleContextClick(event); + (0, _helpers.callIfExists)(_this.props.attributes.onContextMenu, event); + }, _this.handleContextClick = function (event) { + if (_this.props.disable) return; + + event.preventDefault(); + event.stopPropagation(); + + var x = event.clientX || event.touches && event.touches[0].pageX; + var y = event.clientY || event.touches && event.touches[0].pageY; + + (0, _actions.hideMenu)(); + + var data = (0, _helpers.callIfExists)(_this.props.collect, _this.props); + var showMenuConfig = { + position: { x: x, y: y }, + target: _this.elem, + id: _this.props.id, + data: data + }; + if (data && typeof data.then === 'function') { + // it's promise + data.then(function (resp) { + showMenuConfig.data = resp; + (0, _actions.showMenu)(showMenuConfig); + }); + } else { + (0, _actions.showMenu)(showMenuConfig); + } + }, _this.elemRef = function (c) { + _this.elem = c; + }, _temp), _possibleConstructorReturn(_this, _ret); + } + + _createClass(ContextMenuTrigger, [{ + key: 'render', + value: function render() { + var _props = this.props, + renderTag = _props.renderTag, + attributes = _props.attributes, + children = _props.children; + + var newAttrs = (0, _objectAssign2.default)({}, attributes, { + className: (0, _classnames2.default)(_helpers.cssClasses.menuWrapper, attributes.className), + onContextMenu: this.handleContextMenu, + onMouseDown: this.handleMouseDown, + onMouseUp: this.handleMouseUp, + onTouchStart: this.handleTouchstart, + onTouchEnd: this.handleTouchEnd, + onMouseOut: this.handleMouseOut, + ref: this.elemRef + }); + + return _react2.default.createElement(renderTag, newAttrs, children); + } + }]); + + return ContextMenuTrigger; +}(_react.Component); + +ContextMenuTrigger.propTypes = { + id: _propTypes2.default.string.isRequired, + children: _propTypes2.default.node.isRequired, + attributes: _propTypes2.default.object, + collect: _propTypes2.default.func, + disable: _propTypes2.default.bool, + holdToDisplay: _propTypes2.default.number, + renderTag: _propTypes2.default.oneOfType([_propTypes2.default.node, _propTypes2.default.func]) +}; +ContextMenuTrigger.defaultProps = { + attributes: {}, + collect: function collect() { + return null; + }, + + disable: false, + holdToDisplay: 1000, + renderTag: 'div' +}; +exports.default = ContextMenuTrigger; \ No newline at end of file diff --git a/modules/MenuItem.js b/modules/MenuItem.js new file mode 100644 index 00000000..057d4f8d --- /dev/null +++ b/modules/MenuItem.js @@ -0,0 +1,132 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _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; }; + +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; }; }(); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _propTypes = require('prop-types'); + +var _propTypes2 = _interopRequireDefault(_propTypes); + +var _classnames = require('classnames'); + +var _classnames2 = _interopRequireDefault(_classnames); + +var _objectAssign = require('object-assign'); + +var _objectAssign2 = _interopRequireDefault(_objectAssign); + +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; } + +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; } + +var MenuItem = function (_Component) { + _inherits(MenuItem, _Component); + + function MenuItem() { + var _ref; + + var _temp, _this, _ret; + + _classCallCheck(this, MenuItem); + + for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = MenuItem.__proto__ || Object.getPrototypeOf(MenuItem)).call.apply(_ref, [this].concat(args))), _this), _this.handleClick = function (event) { + event.preventDefault(); + + if (_this.props.disabled || _this.props.divider) return; + + (0, _helpers.callIfExists)(_this.props.onClick, event, (0, _objectAssign2.default)({}, _this.props.data, _helpers.store.data), _helpers.store.target); + + if (_this.props.preventClose) return; + + (0, _actions.hideMenu)(); + }, _temp), _possibleConstructorReturn(_this, _ret); + } + + _createClass(MenuItem, [{ + key: 'render', + value: function render() { + var _cx, + _this2 = this; + + var _props = this.props, + disabled = _props.disabled, + divider = _props.divider, + children = _props.children, + attributes = _props.attributes, + selected = _props.selected; + + var menuItemClassNames = (0, _classnames2.default)(_helpers.cssClasses.menuItem, attributes && attributes.className, (_cx = {}, _defineProperty(_cx, _helpers.cssClasses.menuItemDisabled, disabled), _defineProperty(_cx, _helpers.cssClasses.menuItemDivider, divider), _defineProperty(_cx, _helpers.cssClasses.menuItemSelected, selected), _cx)); + + return _react2.default.createElement( + 'div', + _extends({}, attributes, { className: menuItemClassNames, + role: 'menuitem', tabIndex: '-1', 'aria-disabled': disabled ? 'true' : 'false', + 'aria-orientation': divider ? 'horizontal' : null, + ref: function ref(_ref2) { + _this2.ref = _ref2; + }, + onMouseMove: this.props.onMouseMove, onMouseLeave: this.props.onMouseLeave, + onTouchEnd: this.handleClick, onClick: this.handleClick }), + divider ? null : children + ); + } + }]); + + return MenuItem; +}(_react.Component); + +MenuItem.propTypes = { + children: _propTypes2.default.node, + attributes: _propTypes2.default.object, + data: _propTypes2.default.object, + disabled: _propTypes2.default.bool, + divider: _propTypes2.default.bool, + preventClose: _propTypes2.default.bool, + onClick: _propTypes2.default.func, + selected: _propTypes2.default.bool, + onMouseMove: _propTypes2.default.func, + onMouseLeave: _propTypes2.default.func +}; +MenuItem.defaultProps = { + disabled: false, + data: {}, + divider: false, + attributes: {}, + preventClose: false, + onClick: function onClick() { + return null; + }, + + children: null, + selected: false, + onMouseMove: function onMouseMove() { + return null; + }, + onMouseLeave: function onMouseLeave() { + return null; + } +}; +exports.default = MenuItem; \ No newline at end of file diff --git a/modules/SubMenu.js b/modules/SubMenu.js new file mode 100644 index 00000000..c60c00fc --- /dev/null +++ b/modules/SubMenu.js @@ -0,0 +1,332 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _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; }; + +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; }; }(); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _propTypes = require('prop-types'); + +var _propTypes2 = _interopRequireDefault(_propTypes); + +var _classnames = require('classnames'); + +var _classnames2 = _interopRequireDefault(_classnames); + +var _objectAssign = require('object-assign'); + +var _objectAssign2 = _interopRequireDefault(_objectAssign); + +var _AbstractMenu2 = require('./AbstractMenu'); + +var _AbstractMenu3 = _interopRequireDefault(_AbstractMenu2); + +var _actions = require('./actions'); + +var _helpers = require('./helpers'); + +var _globalEventListener = require('./globalEventListener'); + +var _globalEventListener2 = _interopRequireDefault(_globalEventListener); + +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; } + +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; } + +var SubMenu = function (_AbstractMenu) { + _inherits(SubMenu, _AbstractMenu); + + function SubMenu(props) { + _classCallCheck(this, SubMenu); + + var _this = _possibleConstructorReturn(this, (SubMenu.__proto__ || Object.getPrototypeOf(SubMenu)).call(this, props)); + + _this.getMenuPosition = function () { + var _window = window, + innerWidth = _window.innerWidth, + innerHeight = _window.innerHeight; + + var rect = _this.subMenu.getBoundingClientRect(); + var 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; + }; + + _this.getRTLMenuPosition = function () { + var _window2 = window, + innerHeight = _window2.innerHeight; + + var rect = _this.subMenu.getBoundingClientRect(); + var position = {}; + + if (rect.bottom > innerHeight) { + position.bottom = 0; + } else { + position.top = 0; + } + + if (rect.left < 0) { + position.left = '100%'; + } else { + position.right = '100%'; + } + + return position; + }; + + _this.hideMenu = function () { + if (_this.props.forceOpen) { + _this.props.forceClose(); + } + _this.setState({ visible: false, selectedItem: null }); + _this.unregisterHandlers(); + }; + + _this.handleClick = function (event) { + event.preventDefault(); + + if (_this.props.disabled) return; + + (0, _helpers.callIfExists)(_this.props.onClick, event, (0, _objectAssign2.default)({}, _this.props.data, _helpers.store.data), _helpers.store.target); + + if (_this.props.preventClose) return; + + (0, _actions.hideMenu)(); + }; + + _this.handleMouseEnter = function () { + if (_this.closetimer) clearTimeout(_this.closetimer); + + if (_this.props.disabled || _this.state.visible) return; + + _this.opentimer = setTimeout(function () { + return _this.setState({ + visible: true, + selectedItem: null + }); + }, _this.props.hoverDelay); + }; + + _this.handleMouseLeave = function () { + if (_this.opentimer) clearTimeout(_this.opentimer); + + if (!_this.state.visible) return; + + _this.closetimer = setTimeout(function () { + return _this.setState({ + visible: false, + selectedItem: null + }); + }, _this.props.hoverDelay); + }; + + _this.menuRef = function (c) { + _this.menu = c; + }; + + _this.subMenuRef = function (c) { + _this.subMenu = c; + }; + + _this.registerHandlers = function () { + document.removeEventListener('keydown', _this.props.parentKeyNavigationHandler); + document.addEventListener('keydown', _this.handleKeyNavigation); + }; + + _this.unregisterHandlers = function () { + document.removeEventListener('keydown', _this.handleKeyNavigation); + document.addEventListener('keydown', _this.props.parentKeyNavigationHandler); + }; + + _this.state = (0, _objectAssign2.default)({}, _this.state, { + visible: false + }); + return _this; + } + + _createClass(SubMenu, [{ + key: 'componentDidMount', + value: function componentDidMount() { + this.listenId = _globalEventListener2.default.register(function () {}, this.hideMenu); + } + }, { + key: 'getSubMenuType', + value: function getSubMenuType() { + // eslint-disable-line class-methods-use-this + return SubMenu; + } + }, { + key: 'shouldComponentUpdate', + value: function 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; + } + }, { + key: 'componentDidUpdate', + value: function componentDidUpdate() { + var _this2 = this; + + if (!this.isVisibilityChange) return; + if (this.props.forceOpen || this.state.visible) { + var wrapper = window.requestAnimationFrame || setTimeout; + wrapper(function () { + var styles = _this2.props.rtl ? _this2.getRTLMenuPosition() : _this2.getMenuPosition(); + + _this2.subMenu.style.removeProperty('top'); + _this2.subMenu.style.removeProperty('bottom'); + _this2.subMenu.style.removeProperty('left'); + _this2.subMenu.style.removeProperty('right'); + + if ((0, _helpers.hasOwnProp)(styles, 'top')) _this2.subMenu.style.top = styles.top; + if ((0, _helpers.hasOwnProp)(styles, 'left')) _this2.subMenu.style.left = styles.left; + if ((0, _helpers.hasOwnProp)(styles, 'bottom')) _this2.subMenu.style.bottom = styles.bottom; + if ((0, _helpers.hasOwnProp)(styles, 'right')) _this2.subMenu.style.right = styles.right; + _this2.subMenu.classList.add(_helpers.cssClasses.menuVisible); + + _this2.registerHandlers(); + _this2.setState({ selectedItem: null }); + }); + } else { + var cleanup = function cleanup() { + _this2.subMenu.removeEventListener('transitionend', cleanup); + _this2.subMenu.style.removeProperty('bottom'); + _this2.subMenu.style.removeProperty('right'); + _this2.subMenu.style.top = 0; + _this2.subMenu.style.left = '100%'; + _this2.unregisterHandlers(); + }; + this.subMenu.addEventListener('transitionend', cleanup); + this.subMenu.classList.remove(_helpers.cssClasses.menuVisible); + } + } + }, { + key: 'componentWillUnmount', + value: function componentWillUnmount() { + if (this.listenId) { + _globalEventListener2.default.unregister(this.listenId); + } + + if (this.opentimer) clearTimeout(this.opentimer); + + if (this.closetimer) clearTimeout(this.closetimer); + + this.unregisterHandlers(); + } + }, { + key: 'render', + value: function render() { + var _cx; + + var _props = this.props, + children = _props.children, + disabled = _props.disabled, + title = _props.title, + selected = _props.selected; + var visible = this.state.visible; + + var menuProps = { + ref: this.menuRef, + onMouseEnter: this.handleMouseEnter, + onMouseLeave: this.handleMouseLeave, + className: (0, _classnames2.default)(_helpers.cssClasses.menuItem, _helpers.cssClasses.subMenu), + style: { + position: 'relative' + } + }; + var menuItemProps = { + className: (0, _classnames2.default)(_helpers.cssClasses.menuItem, (_cx = {}, _defineProperty(_cx, _helpers.cssClasses.menuItemDisabled, disabled), _defineProperty(_cx, _helpers.cssClasses.menuItemActive, visible), _defineProperty(_cx, _helpers.cssClasses.menuItemSelected, selected), _cx)), + onMouseMove: this.props.onMouseMove, + onMouseOut: this.props.onMouseOut, + onClick: this.handleClick + }; + var subMenuProps = { + ref: this.subMenuRef, + style: { + position: 'absolute', + transition: 'opacity 1ms', // trigger transitionend event + top: 0, + left: '100%' + }, + className: (0, _classnames2.default)(_helpers.cssClasses.menu, this.props.className) + }; + + return _react2.default.createElement( + 'nav', + _extends({}, menuProps, { role: 'menuitem', tabIndex: '-1', 'aria-haspopup': 'true' }), + _react2.default.createElement( + 'div', + menuItemProps, + title + ), + _react2.default.createElement( + 'nav', + _extends({}, subMenuProps, { role: 'menu', tabIndex: '-1' }), + this.renderChildren(children) + ) + ); + } + }]); + + return SubMenu; +}(_AbstractMenu3.default); + +SubMenu.propTypes = { + children: _propTypes2.default.node.isRequired, + title: _propTypes2.default.node.isRequired, + className: _propTypes2.default.string, + disabled: _propTypes2.default.bool, + hoverDelay: _propTypes2.default.number, + rtl: _propTypes2.default.bool, + selected: _propTypes2.default.bool, + onMouseMove: _propTypes2.default.func, + onMouseOut: _propTypes2.default.func, + forceOpen: _propTypes2.default.bool, + forceClose: _propTypes2.default.func, + parentKeyNavigationHandler: _propTypes2.default.func +}; +SubMenu.defaultProps = { + disabled: false, + hoverDelay: 500, + className: '', + rtl: false, + selected: false, + onMouseMove: function onMouseMove() { + return null; + }, + onMouseOut: function onMouseOut() { + return null; + }, + forceOpen: false, + forceClose: function forceClose() { + return null; + }, + parentKeyNavigationHandler: function parentKeyNavigationHandler() { + return null; + } +}; +exports.default = SubMenu; \ No newline at end of file diff --git a/modules/actions.js b/modules/actions.js new file mode 100644 index 00000000..a564cd73 --- /dev/null +++ b/modules/actions.js @@ -0,0 +1,54 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.MENU_HIDE = exports.MENU_SHOW = undefined; +exports.dispatchGlobalEvent = dispatchGlobalEvent; +exports.showMenu = showMenu; +exports.hideMenu = hideMenu; + +var _objectAssign = require('object-assign'); + +var _objectAssign2 = _interopRequireDefault(_objectAssign); + +var _helpers = require('./helpers'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var MENU_SHOW = exports.MENU_SHOW = 'REACT_CONTEXTMENU_SHOW'; +var MENU_HIDE = exports.MENU_HIDE = 'REACT_CONTEXTMENU_HIDE'; + +function dispatchGlobalEvent(eventName, opts) { + var target = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : window; + + // Compatibale with IE + // @see http://stackoverflow.com/questions/26596123/internet-explorer-9-10-11-event-constructor-doesnt-work + var event = void 0; + + if (typeof window.CustomEvent === 'function') { + event = new window.CustomEvent(eventName, { detail: opts }); + } else { + event = document.createEvent('CustomEvent'); + event.initCustomEvent(eventName, false, true, opts); + } + + if (target) { + target.dispatchEvent(event); + (0, _objectAssign2.default)(_helpers.store, opts); + } +} + +function showMenu() { + var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var target = arguments[1]; + + dispatchGlobalEvent(MENU_SHOW, (0, _objectAssign2.default)({}, opts, { type: MENU_SHOW }), target); +} + +function hideMenu() { + var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + var target = arguments[1]; + + dispatchGlobalEvent(MENU_HIDE, (0, _objectAssign2.default)({}, opts, { type: MENU_HIDE }), target); +} \ No newline at end of file diff --git a/modules/connectMenu.js b/modules/connectMenu.js new file mode 100644 index 00000000..c895f3cb --- /dev/null +++ b/modules/connectMenu.js @@ -0,0 +1,98 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _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; }; + +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; }; }(); + +exports.default = function (menuId) { + // expect menu component to connect as inner parameter + // is presumably a wrapper of + return function (Child) { + // return wrapper for that forwards the ContextMenuTrigger's additional props + return function (_Component) { + _inherits(ConnectMenu, _Component); + + function ConnectMenu(props) { + _classCallCheck(this, ConnectMenu); + + var _this = _possibleConstructorReturn(this, (ConnectMenu.__proto__ || Object.getPrototypeOf(ConnectMenu)).call(this, props)); + + _this.handleShow = function (e) { + if (e.detail.id !== menuId) return; + + // the onShow event's detail.data object holds all ContextMenuTrigger props + var data = e.detail.data; + + var filteredData = {}; + + for (var key in data) { + // exclude props the ContextMenuTrigger is expecting itself + if (!ignoredTriggerProps.includes(key)) { + filteredData[key] = data[key]; + } + } + _this.setState({ trigger: filteredData }); + }; + + _this.handleHide = function () { + _this.setState({ trigger: null }); + }; + + _this.state = { trigger: null }; + return _this; + } + + _createClass(ConnectMenu, [{ + key: 'componentDidMount', + value: function componentDidMount() { + this.listenId = _globalEventListener2.default.register(this.handleShow, this.handleHide); + } + }, { + key: 'componentWillUnmount', + value: function componentWillUnmount() { + if (this.listenId) { + _globalEventListener2.default.unregister(this.listenId); + } + } + }, { + key: 'render', + value: function render() { + return _react2.default.createElement(Child, _extends({}, this.props, { id: menuId, trigger: this.state.trigger })); + } + }]); + + return ConnectMenu; + }(_react.Component); + }; +}; + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _ContextMenuTrigger = require('./ContextMenuTrigger'); + +var _ContextMenuTrigger2 = _interopRequireDefault(_ContextMenuTrigger); + +var _globalEventListener = require('./globalEventListener'); + +var _globalEventListener2 = _interopRequireDefault(_globalEventListener); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: 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; } + +function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } + +// collect ContextMenuTrigger's expected props to NOT pass them on as part of the context +var ignoredTriggerProps = [].concat(_toConsumableArray(Object.keys(_ContextMenuTrigger2.default.propTypes)), ['children']); + +// expect the id of the menu to be responsible for as outer parameter \ No newline at end of file diff --git a/modules/globalEventListener.js b/modules/globalEventListener.js new file mode 100644 index 00000000..32276107 --- /dev/null +++ b/modules/globalEventListener.js @@ -0,0 +1,55 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _actions = require('./actions'); + +var _helpers = require('./helpers'); + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +var GlobalEventListener = function GlobalEventListener() { + var _this = this; + + _classCallCheck(this, GlobalEventListener); + + this.handleShowEvent = function (event) { + for (var id in _this.callbacks) { + if ((0, _helpers.hasOwnProp)(_this.callbacks, id)) _this.callbacks[id].show(event); + } + }; + + this.handleHideEvent = function (event) { + for (var id in _this.callbacks) { + if ((0, _helpers.hasOwnProp)(_this.callbacks, id)) _this.callbacks[id].hide(event); + } + }; + + this.register = function (showCallback, hideCallback) { + var id = (0, _helpers.uniqueId)(); + + _this.callbacks[id] = { + show: showCallback, + hide: hideCallback + }; + + return id; + }; + + this.unregister = function (id) { + if (id && _this.callbacks[id]) { + delete _this.callbacks[id]; + } + }; + + this.callbacks = {}; + + if (_helpers.canUseDOM) { + window.addEventListener(_actions.MENU_SHOW, this.handleShowEvent); + window.addEventListener(_actions.MENU_HIDE, this.handleHideEvent); + } +}; + +exports.default = new GlobalEventListener(); \ No newline at end of file diff --git a/modules/helpers.js b/modules/helpers.js new file mode 100644 index 00000000..c160e9e1 --- /dev/null +++ b/modules/helpers.js @@ -0,0 +1,39 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.callIfExists = callIfExists; +exports.hasOwnProp = hasOwnProp; +exports.uniqueId = uniqueId; +function callIfExists(func) { + for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + return typeof func === 'function' && func.apply(undefined, args); +} + +function hasOwnProp(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +function uniqueId() { + return Math.random().toString(36).substring(7); +} + +var cssClasses = exports.cssClasses = { + menu: 'react-contextmenu', + menuVisible: 'react-contextmenu--visible', + menuWrapper: 'react-contextmenu-wrapper', + menuItem: 'react-contextmenu-item', + menuItemActive: 'react-contextmenu-item--active', + menuItemDisabled: 'react-contextmenu-item--disabled', + menuItemDivider: 'react-contextmenu-item--divider', + menuItemSelected: 'react-contextmenu-item--selected', + subMenu: 'react-contextmenu-submenu' +}; + +var store = exports.store = {}; + +var canUseDOM = exports.canUseDOM = Boolean(typeof window !== 'undefined' && window.document && window.document.createElement); \ No newline at end of file diff --git a/modules/index.js b/modules/index.js new file mode 100644 index 00000000..60dcffa3 --- /dev/null +++ b/modules/index.js @@ -0,0 +1,67 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _ContextMenu = require('./ContextMenu'); + +Object.defineProperty(exports, 'ContextMenu', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_ContextMenu).default; + } +}); + +var _ContextMenuTrigger = require('./ContextMenuTrigger'); + +Object.defineProperty(exports, 'ContextMenuTrigger', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_ContextMenuTrigger).default; + } +}); + +var _MenuItem = require('./MenuItem'); + +Object.defineProperty(exports, 'MenuItem', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_MenuItem).default; + } +}); + +var _SubMenu = require('./SubMenu'); + +Object.defineProperty(exports, 'SubMenu', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_SubMenu).default; + } +}); + +var _connectMenu = require('./connectMenu'); + +Object.defineProperty(exports, 'connectMenu', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_connectMenu).default; + } +}); + +var _actions = require('./actions'); + +Object.defineProperty(exports, 'hideMenu', { + enumerable: true, + get: function get() { + return _actions.hideMenu; + } +}); +Object.defineProperty(exports, 'showMenu', { + enumerable: true, + get: function get() { + return _actions.showMenu; + } +}); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } \ No newline at end of file