You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

209 lines
7.8 KiB

  1. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  2. 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; }
  3. 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; }
  4. import React, { Component } from 'react';
  5. import PropTypes from 'prop-types';
  6. import MenuItem from './MenuItem';
  7. var AbstractMenu = function (_Component) {
  8. _inherits(AbstractMenu, _Component);
  9. function AbstractMenu(props) {
  10. _classCallCheck(this, AbstractMenu);
  11. var _this = _possibleConstructorReturn(this, (AbstractMenu.__proto__ || Object.getPrototypeOf(AbstractMenu)).call(this, props));
  12. _initialiseProps.call(_this);
  13. _this.seletedItemRef = null;
  14. _this.state = {
  15. selectedItem: null,
  16. forceSubMenuOpen: false
  17. };
  18. return _this;
  19. }
  20. return AbstractMenu;
  21. }(Component);
  22. AbstractMenu.propTypes = {
  23. children: PropTypes.node.isRequired
  24. };
  25. var _initialiseProps = function _initialiseProps() {
  26. var _this2 = this;
  27. this.handleKeyNavigation = function (e) {
  28. // check for isVisible strictly here as it might be undefined when this code executes in the context of SubMenu
  29. // but we only need to check when it runs in the ContextMenu context
  30. if (_this2.state.isVisible === false) {
  31. return;
  32. }
  33. switch (e.keyCode) {
  34. case 37: // left arrow
  35. case 27:
  36. // escape
  37. e.preventDefault();
  38. _this2.hideMenu(e);
  39. break;
  40. case 38:
  41. // up arrow
  42. e.preventDefault();
  43. _this2.selectChildren(true);
  44. break;
  45. case 40:
  46. // down arrow
  47. e.preventDefault();
  48. _this2.selectChildren(false);
  49. break;
  50. case 39:
  51. // right arrow
  52. _this2.tryToOpenSubMenu(e);
  53. break;
  54. case 13:
  55. // enter
  56. e.preventDefault();
  57. _this2.tryToOpenSubMenu(e);
  58. {
  59. // determine the selected item is disabled or not
  60. var disabled = _this2.seletedItemRef && _this2.seletedItemRef.props && _this2.seletedItemRef.props.disabled;
  61. if (_this2.seletedItemRef && _this2.seletedItemRef.ref instanceof HTMLElement && !disabled) {
  62. _this2.seletedItemRef.ref.click();
  63. } else {
  64. _this2.hideMenu(e);
  65. }
  66. }
  67. break;
  68. default:
  69. // do nothing
  70. }
  71. };
  72. this.handleForceClose = function () {
  73. _this2.setState({ forceSubMenuOpen: false });
  74. };
  75. this.tryToOpenSubMenu = function (e) {
  76. if (_this2.state.selectedItem && _this2.state.selectedItem.type === _this2.getSubMenuType()) {
  77. e.preventDefault();
  78. _this2.setState({ forceSubMenuOpen: true });
  79. }
  80. };
  81. this.selectChildren = function (forward) {
  82. var selectedItem = _this2.state.selectedItem;
  83. var children = [];
  84. var disabledChildrenCount = 0;
  85. var disabledChildIndexes = {};
  86. var childCollector = function childCollector(child, index) {
  87. // child can be empty in case you do conditional rendering of components, in which
  88. // case it should not be accounted for as a real child
  89. if (!child) {
  90. return;
  91. }
  92. if ([MenuItem, _this2.getSubMenuType()].indexOf(child.type) < 0) {
  93. // Maybe the MenuItem or SubMenu is capsuled in a wrapper div or something else
  94. React.Children.forEach(child.props.children, childCollector);
  95. } else if (!child.props.divider) {
  96. if (child.props.disabled) {
  97. ++disabledChildrenCount;
  98. disabledChildIndexes[index] = true;
  99. }
  100. children.push(child);
  101. }
  102. };
  103. React.Children.forEach(_this2.props.children, childCollector);
  104. if (disabledChildrenCount === children.length) {
  105. // All menu items are disabled, so none can be selected, don't do anything
  106. return;
  107. }
  108. function findNextEnabledChildIndex(currentIndex) {
  109. var i = currentIndex;
  110. var incrementCounter = function incrementCounter() {
  111. if (forward) {
  112. --i;
  113. } else {
  114. ++i;
  115. }
  116. if (i < 0) {
  117. i = children.length - 1;
  118. } else if (i >= children.length) {
  119. i = 0;
  120. }
  121. };
  122. do {
  123. incrementCounter();
  124. } while (i !== currentIndex && disabledChildIndexes[i]);
  125. return i === currentIndex ? null : i;
  126. }
  127. var currentIndex = children.indexOf(selectedItem);
  128. var nextEnabledChildIndex = findNextEnabledChildIndex(currentIndex);
  129. if (nextEnabledChildIndex !== null) {
  130. _this2.setState({
  131. selectedItem: children[nextEnabledChildIndex],
  132. forceSubMenuOpen: false
  133. });
  134. }
  135. };
  136. this.onChildMouseMove = function (child) {
  137. if (_this2.state.selectedItem !== child) {
  138. _this2.setState({ selectedItem: child, forceSubMenuOpen: false });
  139. }
  140. };
  141. this.onChildMouseLeave = function () {
  142. _this2.setState({ selectedItem: null, forceSubMenuOpen: false });
  143. };
  144. this.renderChildren = function (children) {
  145. return React.Children.map(children, function (child) {
  146. var props = {};
  147. if (!React.isValidElement(child)) return child;
  148. if ([MenuItem, _this2.getSubMenuType()].indexOf(child.type) < 0) {
  149. // Maybe the MenuItem or SubMenu is capsuled in a wrapper div or something else
  150. props.children = _this2.renderChildren(child.props.children);
  151. return React.cloneElement(child, props);
  152. }
  153. props.onMouseLeave = _this2.onChildMouseLeave.bind(_this2);
  154. if (child.type === _this2.getSubMenuType()) {
  155. // special props for SubMenu only
  156. props.forceOpen = _this2.state.forceSubMenuOpen && _this2.state.selectedItem === child;
  157. props.forceClose = _this2.handleForceClose;
  158. props.parentKeyNavigationHandler = _this2.handleKeyNavigation;
  159. }
  160. if (!child.props.divider && _this2.state.selectedItem === child) {
  161. // special props for selected item only
  162. props.selected = true;
  163. props.ref = function (ref) {
  164. _this2.seletedItemRef = ref;
  165. };
  166. return React.cloneElement(child, props);
  167. }
  168. // onMouseMove is only needed for non selected items
  169. props.onMouseMove = function () {
  170. return _this2.onChildMouseMove(child);
  171. };
  172. return React.cloneElement(child, props);
  173. });
  174. };
  175. };
  176. export default AbstractMenu;