@orfeas126/box-ui-elements
Version:
Box UI Elements
219 lines (217 loc) • 7.43 kB
JavaScript
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 TetherComponent from 'react-tether';
import classNames from 'classnames';
import noop from 'lodash/noop';
import uniqueId from 'lodash/uniqueId';
import { KEYS } from '../../constants';
import './DropdownMenu.scss';
class DropdownMenu extends React.Component {
constructor(...args) {
super(...args);
_defineProperty(this, "menuID", uniqueId('menu'));
_defineProperty(this, "menuButtonID", uniqueId('menubutton'));
_defineProperty(this, "state", {
initialFocusIndex: null,
isOpen: false
});
_defineProperty(this, "openMenuAndSetFocusIndex", initialFocusIndex => {
this.setState({
initialFocusIndex,
isOpen: true
});
});
_defineProperty(this, "closeMenu", event => {
const {
onMenuClose = noop
} = this.props;
this.setState({
isOpen: false
}, () => onMenuClose(event));
});
_defineProperty(this, "focusButton", () => {
// @NOTE: This breaks encapsulation a bit, but the only other way is passing ref functions to unknown children components
const menuButtonEl = document.getElementById(this.menuButtonID);
if (menuButtonEl) {
menuButtonEl.focus();
}
});
_defineProperty(this, "handleButtonClick", event => {
const {
isOpen
} = this.state;
event.stopPropagation();
event.preventDefault();
if (isOpen) {
this.closeMenu(event);
} else {
this.openMenuAndSetFocusIndex(null);
}
});
_defineProperty(this, "handleButtonKeyDown", event => {
const {
isOpen
} = this.state;
switch (event.key) {
case KEYS.space:
case KEYS.enter:
case KEYS.arrowDown:
event.stopPropagation();
event.preventDefault();
this.openMenuAndSetFocusIndex(0);
break;
case KEYS.arrowUp:
event.stopPropagation();
event.preventDefault();
this.openMenuAndSetFocusIndex(-1);
break;
case KEYS.escape:
if (isOpen) {
event.stopPropagation();
}
event.preventDefault();
this.closeMenu(event);
break;
default:
break;
}
});
_defineProperty(this, "handleMenuClose", (isKeyboardEvent, event) => {
this.closeMenu(event);
this.focusButton();
});
_defineProperty(this, "handleDocumentClick", event => {
const menuEl = document.getElementById(this.menuID);
const menuButtonEl = document.getElementById(this.menuButtonID);
// Some DOM magic to get global click handlers to close menu when not interacting with menu or associated button
if (menuEl && menuButtonEl && event.target instanceof Node && !menuEl.contains(event.target) && !menuButtonEl.contains(event.target)) {
this.closeMenu(event);
}
});
}
componentDidUpdate(prevProps, prevState) {
const {
useBubble
} = this.props;
if (!prevState.isOpen && this.state.isOpen) {
// When menu is being opened
document.addEventListener('click', this.handleDocumentClick, !useBubble);
document.addEventListener('contextmenu', this.handleDocumentClick, !useBubble);
const {
onMenuOpen
} = this.props;
if (onMenuOpen) {
onMenuOpen();
}
} else if (prevState.isOpen && !this.state.isOpen) {
// When menu is being closed
document.removeEventListener('contextmenu', this.handleDocumentClick, !useBubble);
document.removeEventListener('click', this.handleDocumentClick, !useBubble);
}
}
componentWillUnmount() {
const {
useBubble
} = this.props;
if (this.state.isOpen) {
// Clean-up global click handlers
document.removeEventListener('contextmenu', this.handleDocumentClick, !useBubble);
document.removeEventListener('click', this.handleDocumentClick, !useBubble);
}
}
render() {
const {
bodyElement,
children,
className,
constrainToScrollParent,
constrainToWindow,
constrainToWindowWithPin,
isResponsive,
isRightAligned,
tetherAttachment,
tetherTargetAttachment
} = this.props;
const {
isOpen,
initialFocusIndex
} = this.state;
const elements = React.Children.toArray(children);
if (elements.length !== 2) {
throw new Error('DropdownMenu must have exactly two children: A button component and a <Menu>');
}
const menuButton = elements[0];
const menu = elements[1];
const menuButtonProps = {
id: this.menuButtonID,
key: this.menuButtonID,
onClick: this.handleButtonClick,
// NOTE: Overrides button's handler
onKeyDown: this.handleButtonKeyDown,
// NOTE: Overrides button's handler
'aria-expanded': isOpen ? 'true' : 'false'
};
if (menuButton.props['aria-haspopup'] === undefined) {
menuButtonProps['aria-haspopup'] = 'true';
}
// Add this only when its open, otherwise the menuID element isn't rendered
if (isOpen) {
menuButtonProps['aria-controls'] = this.menuID;
}
const menuProps = {
id: this.menuID,
key: this.menuID,
initialFocusIndex,
onClose: this.handleMenuClose,
'aria-labelledby': this.menuButtonID
};
let attachment = 'top left';
let targetAttachment = 'bottom left';
if (isRightAligned) {
attachment = 'top right';
targetAttachment = 'bottom right';
}
const constraints = [];
if (constrainToScrollParent) {
constraints.push({
to: 'scrollParent',
attachment: 'together'
});
}
if (constrainToWindow) {
constraints.push({
to: 'window',
attachment: 'together'
});
}
if (constrainToWindowWithPin) {
constraints.push({
to: 'window',
attachment: 'together',
pin: true
});
}
const bodyEl = bodyElement instanceof HTMLElement ? bodyElement : document.body;
return /*#__PURE__*/React.createElement(TetherComponent, {
attachment: tetherAttachment || attachment,
bodyElement: bodyEl,
className: classNames({
'bdl-DropdownMenu--responsive': isResponsive
}, className),
classPrefix: "dropdown-menu",
constraints: constraints,
enabled: isOpen,
targetAttachment: tetherTargetAttachment || targetAttachment
}, /*#__PURE__*/React.cloneElement(menuButton, menuButtonProps), isOpen && /*#__PURE__*/React.cloneElement(menu, menuProps));
}
}
_defineProperty(DropdownMenu, "defaultProps", {
constrainToScrollParent: false,
constrainToWindow: false,
isResponsive: false,
isRightAligned: false
});
export default DropdownMenu;
//# sourceMappingURL=DropdownMenu.js.map