UNPKG

@orfeas126/box-ui-elements

Version:
181 lines (179 loc) 7.89 kB
const _excluded = ["children", "className", "isDisabled"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var s = Object.getOwnPropertySymbols(e); for (r = 0; r < s.length; r++) o = s[r], t.includes(o) || {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; } function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (e.includes(n)) continue; t[n] = r[n]; } return t; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } import * as React from 'react'; import classNames from 'classnames'; import omit from 'lodash/omit'; import debounce from 'lodash/debounce'; import './SubmenuItem.scss'; import Arrow16 from '../../icon/fill/Arrow16'; const SUBMENU_LEFT_ALIGNED_CLASS = 'is-left-aligned'; const SUBMENU_BOTTOM_ALIGNED_CLASS = 'is-bottom-aligned'; const SUBMENU_RIGHT_BOTTOM_ALIGNED_CLASS = 'is-right-bottom-aligned'; /** * A menu-item component which triggers open a submenu * * @NOTE: Nested submenus are NOT currently supported, switching * focus with arrow keys in the subsubmenu is not working properly. */ class SubmenuItem extends React.Component { constructor(...args) { super(...args); _defineProperty(this, "state", { isSubmenuOpen: false, submenuFocusIndex: null }); _defineProperty(this, "getMenuAlignmentClasses", () => { if (!this.submenuTriggerEl || !this.submenuEl) { return {}; } const { rightBoundaryElement, bottomBoundaryElement } = this.props; const submenuElBounding = this.submenuEl.getBoundingClientRect(); const submenuTriggerElBounding = this.submenuTriggerEl.getBoundingClientRect(); const rightBoundaryElementBounding = rightBoundaryElement ? rightBoundaryElement.getBoundingClientRect() : { right: window.innerWidth }; const bottomBoundaryElementBounding = bottomBoundaryElement ? bottomBoundaryElement.getBoundingClientRect() : { bottom: window.innerHeight }; const isLeftAligned = submenuTriggerElBounding.right + submenuElBounding.width > rightBoundaryElementBounding.right; const isBottomAligned = submenuTriggerElBounding.top + submenuElBounding.height > bottomBoundaryElementBounding.bottom; const isRightBottomAligned = submenuTriggerElBounding.bottom + submenuElBounding.height > bottomBoundaryElementBounding.bottom; return { [SUBMENU_LEFT_ALIGNED_CLASS]: isLeftAligned, [SUBMENU_BOTTOM_ALIGNED_CLASS]: isBottomAligned, [SUBMENU_RIGHT_BOTTOM_ALIGNED_CLASS]: isRightBottomAligned // Used only in medium-screen viewport sizes }; }); _defineProperty(this, "handleMenuItemClick", event => { const { isDisabled, onClick } = this.props; // If aria-disabled is passed as a prop, we should ignore clicks on this menu item if (isDisabled) { event.stopPropagation(); event.preventDefault(); return; } if (onClick) { onClick(event); } // If event target is triggering submenu element, do not propagate to close menu if (this.submenuEl && !this.submenuEl.contains(event.target)) { event.stopPropagation(); event.preventDefault(); } }); _defineProperty(this, "handleKeyDown", event => { switch (event.key) { case ' ': case 'Enter': case 'ArrowRight': event.stopPropagation(); event.preventDefault(); this.openSubmenuAndFocus(); break; default: break; } }); _defineProperty(this, "closeSubmenu", debounce(() => { this.setState({ isSubmenuOpen: false }); }, 50)); _defineProperty(this, "closeSubmenuAndFocusTrigger", isKeyboardEvent => { this.closeSubmenu(); if (this.submenuTriggerEl && isKeyboardEvent) { this.submenuTriggerEl.focus(); } }); _defineProperty(this, "openSubmenu", () => { this.closeSubmenu.cancel(); const { onOpen } = this.props; if (onOpen) { onOpen(); } this.setState({ isSubmenuOpen: true, submenuFocusIndex: null }); }); _defineProperty(this, "openSubmenuAndFocus", () => { const { onOpen } = this.props; if (onOpen) { onOpen(); } this.setState({ isSubmenuOpen: true, submenuFocusIndex: 0 }); }); } render() { const _this$props = this.props, { children, className, isDisabled } = _this$props, rest = _objectWithoutProperties(_this$props, _excluded); const { isSubmenuOpen, submenuFocusIndex } = this.state; const elements = React.Children.toArray(children); const submenuTriggerContent = elements[0]; const submenu = elements[1]; if (elements.length !== 2 || !submenuTriggerContent || !submenu) { throw new Error('SubmenuItem must have exactly two children, a trigger component and a <Menu>'); } const chevron = /*#__PURE__*/React.createElement(Arrow16, { className: "menu-item-arrow", width: 12, height: 12 }); const menuItemProps = _objectSpread(_objectSpread({}, omit(rest, ['bottomBoundaryElement', 'onClick', 'onOpen', 'rightBoundaryElement', 'role', 'tabIndex'])), {}, { 'aria-disabled': isDisabled ? 'true' : undefined, 'aria-expanded': isSubmenuOpen ? 'true' : 'false', 'aria-haspopup': 'true', className: classNames('menu-item', 'submenu-target', className), onClick: this.handleMenuItemClick, onMouseLeave: this.closeSubmenu, onMouseEnter: this.openSubmenu, onKeyDown: this.handleKeyDown, ref: ref => { this.submenuTriggerEl = ref; }, role: 'menuitem', tabIndex: -1 }); const submenuProps = { className: classNames(submenu.props.className, 'submenu', this.getMenuAlignmentClasses()), initialFocusIndex: submenuFocusIndex, // Hide the menu instead of unmounting it. Otherwise onMouseLeave won't work. isHidden: !isSubmenuOpen, isSubmenu: true, onClose: this.closeSubmenuAndFocusTrigger, setRef: ref => { this.submenuEl = ref; } }; return /*#__PURE__*/React.createElement("li", menuItemProps, submenuTriggerContent, chevron, /*#__PURE__*/React.cloneElement(submenu, submenuProps)); } } export default SubmenuItem; //# sourceMappingURL=SubmenuItem.js.map