UNPKG

d2-ui

Version:
610 lines (501 loc) 19.9 kB
'use strict'; 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; }; Object.defineProperty(exports, "__esModule", { value: true }); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _reactAddonsUpdate = require('react-addons-update'); var _reactAddonsUpdate2 = _interopRequireDefault(_reactAddonsUpdate); var _stylePropable = require('../mixins/style-propable'); var _stylePropable2 = _interopRequireDefault(_stylePropable); var _clickAwayable = require('../mixins/click-awayable'); var _clickAwayable2 = _interopRequireDefault(_clickAwayable); var _autoPrefix = require('../styles/auto-prefix'); var _autoPrefix2 = _interopRequireDefault(_autoPrefix); var _transitions = require('../styles/transitions'); var _transitions2 = _interopRequireDefault(_transitions); var _keyCode = require('../utils/key-code'); var _keyCode2 = _interopRequireDefault(_keyCode); var _propTypes = require('../utils/prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _list = require('../lists/list'); var _list2 = _interopRequireDefault(_list); var _paper = require('../paper'); var _paper2 = _interopRequireDefault(_paper); var _getMuiTheme = require('../styles/getMuiTheme'); var _getMuiTheme2 = _interopRequireDefault(_getMuiTheme); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } var Menu = _react2.default.createClass({ displayName: 'Menu', propTypes: { /** * If true, the menu will apply transitions when added it * gets added to the DOM. In order for transitions to * work, wrap the menu inside a ReactTransitionGroup. */ animated: _react2.default.PropTypes.bool, /** * If true, the width will automatically be * set according to the items inside the menu * using the proper keyline increment. */ autoWidth: _react2.default.PropTypes.bool, /** * Children for the Menu. Usually MenuItems. */ children: _react2.default.PropTypes.node, /** * Indicates if the menu should render with compact desktop styles. */ desktop: _react2.default.PropTypes.bool, /** * True if this item should be focused by the keyboard initially. */ initiallyKeyboardFocused: _react2.default.PropTypes.bool, /** * The style object to use to override underlying list style. */ listStyle: _react2.default.PropTypes.object, /** * The maxHeight of the menu in pixels. If * specified, the menu will scroll if larger than the maxHeight. */ maxHeight: _react2.default.PropTypes.number, /** * If true, the value can an array and allow the menu to be a multi-select. */ multiple: _react2.default.PropTypes.bool, /** * Fired when a menu item is touchTapped and the menu item * value is not equal to the current menu value. */ onChange: _react2.default.PropTypes.func, /** * Fired when an Esc key is keyed down. */ onEscKeyDown: _react2.default.PropTypes.func, /** * Fired when a menu item is touchTapped. */ onItemTouchTap: _react2.default.PropTypes.func, /** * Fired when a key is pressed. */ onKeyDown: _react2.default.PropTypes.func, /** * This is the placement of the menu relative to the IconButton. */ openDirection: _propTypes2.default.corners, /** * Style for the selected Menu Item. */ selectedMenuItemStyle: _react2.default.PropTypes.object, /** * Override the inline-styles of the root element. */ style: _react2.default.PropTypes.object, /** * The value of the selected menu item. If passed in, * this will make the menu a controlled component. * This component also supports valueLink. */ value: _react2.default.PropTypes.any, /** * ValueLink for this component when controlled. */ valueLink: _react2.default.PropTypes.object, /** * Sets the width of the menu. If not specified, the menu * width will be dictated by its children. The rendered * width will always be a keyline increment * (64px for desktop, 56px otherwise). */ width: _propTypes2.default.stringOrNumber, /** * Sets the width of the menu. If not specified, * the menu width will be dictated by its children. * The rendered width will always be a keyline increment * (64px for desktop, 56px otherwise). */ zDepth: _propTypes2.default.zDepth }, contextTypes: { muiTheme: _react2.default.PropTypes.object }, //for passing default theme context to children childContextTypes: { muiTheme: _react2.default.PropTypes.object }, mixins: [_stylePropable2.default, _clickAwayable2.default], getDefaultProps: function getDefaultProps() { return { animated: false, autoWidth: true, desktop: false, initiallyKeyboardFocused: false, maxHeight: null, multiple: false, onChange: function onChange() {}, onEscKeyDown: function onEscKeyDown() {}, onItemTouchTap: function onItemTouchTap() {}, onKeyDown: function onKeyDown() {}, openDirection: 'bottom-left', zDepth: 1 }; }, getInitialState: function getInitialState() { var filteredChildren = this._getFilteredChildren(this.props.children); var selectedIndex = this._getSelectedIndex(this.props, filteredChildren); return { focusIndex: selectedIndex >= 0 ? selectedIndex : 0, isKeyboardFocused: this.props.initiallyKeyboardFocused, keyWidth: this.props.desktop ? 64 : 56, muiTheme: this.context.muiTheme || (0, _getMuiTheme2.default)() }; }, getChildContext: function getChildContext() { return { muiTheme: this.state.muiTheme }; }, componentDidMount: function componentDidMount() { if (this.props.autoWidth) this._setWidth(); if (!this.props.animated) this._animateOpen(); this._setScollPosition(); }, componentWillReceiveProps: function componentWillReceiveProps(nextProps, nextContext) { var filteredChildren = this._getFilteredChildren(nextProps.children); var selectedIndex = this._getSelectedIndex(nextProps, filteredChildren); var newMuiTheme = nextContext.muiTheme ? nextContext.muiTheme : this.state.muiTheme; this.setState({ focusIndex: selectedIndex >= 0 ? selectedIndex : 0, keyWidth: nextProps.desktop ? 64 : 56, muiTheme: newMuiTheme }); }, componentDidUpdate: function componentDidUpdate() { if (this.props.autoWidth) this._setWidth(); }, componentClickAway: function componentClickAway(e) { if (e.defaultPrevented) return; this._setFocusIndex(-1, false); }, // Do not use outside of this component, it will be removed once valueLink is deprecated getValueLink: function getValueLink(props) { return props.valueLink || { value: props.value, requestChange: props.onChange }; }, setKeyboardFocused: function setKeyboardFocused(keyboardFocused) { this.setState({ isKeyboardFocused: keyboardFocused }); }, _getFilteredChildren: function _getFilteredChildren(children) { var filteredChildren = []; _react2.default.Children.forEach(children, function (child) { if (child) { filteredChildren.push(child); } }); return filteredChildren; }, _animateOpen: function _animateOpen() { var rootStyle = _reactDom2.default.findDOMNode(this).style; var scrollContainerStyle = _reactDom2.default.findDOMNode(this.refs.scrollContainer).style; var menuContainers = _reactDom2.default.findDOMNode(this.refs.list).childNodes; _autoPrefix2.default.set(rootStyle, 'transform', 'scaleX(1)', this.state.muiTheme); _autoPrefix2.default.set(scrollContainerStyle, 'transform', 'scaleY(1)', this.state.muiTheme); scrollContainerStyle.opacity = 1; for (var i = 0; i < menuContainers.length; ++i) { menuContainers[i].style.opacity = 1; } }, _cloneMenuItem: function _cloneMenuItem(child, childIndex, styles) { var _this = this; var _props = this.props; var desktop = _props.desktop; var selectedMenuItemStyle = _props.selectedMenuItemStyle; var selected = this._isChildSelected(child, this.props); var selectedChildrenStyles = {}; if (selected) { selectedChildrenStyles = this.mergeStyles(styles.selectedMenuItem, selectedMenuItemStyle); } var mergedChildrenStyles = this.mergeStyles(child.props.style || {}, selectedChildrenStyles); var isFocused = childIndex === this.state.focusIndex; var focusState = 'none'; if (isFocused) { focusState = this.state.isKeyboardFocused ? 'keyboard-focused' : 'focused'; } return _react2.default.cloneElement(child, { desktop: desktop, focusState: focusState, onTouchTap: function onTouchTap(e) { _this._handleMenuItemTouchTap(e, child); if (child.props.onTouchTap) child.props.onTouchTap(e); }, ref: isFocused ? 'focusedMenuItem' : null, style: mergedChildrenStyles }); }, _decrementKeyboardFocusIndex: function _decrementKeyboardFocusIndex() { var index = this.state.focusIndex; index--; if (index < 0) index = 0; this._setFocusIndex(index, true); }, _getCascadeChildrenCount: function _getCascadeChildrenCount(filteredChildren) { var _props2 = this.props; var desktop = _props2.desktop; var maxHeight = _props2.maxHeight; var count = 1; var currentHeight = desktop ? 16 : 8; var menuItemHeight = desktop ? 32 : 48; //MaxHeight isn't set - cascade all of the children if (!maxHeight) return filteredChildren.length; //Count all the children that will fit inside the //max menu height filteredChildren.forEach(function (child) { if (currentHeight < maxHeight) { var childIsADivider = child.type && child.type.displayName === 'Divider'; currentHeight += childIsADivider ? 16 : menuItemHeight; count++; } }); return count; }, _getMenuItemCount: function _getMenuItemCount(filteredChildren) { var menuItemCount = 0; filteredChildren.forEach(function (child) { var childIsADivider = child.type && child.type.displayName === 'Divider'; var childIsDisabled = child.props.disabled; if (!childIsADivider && !childIsDisabled) menuItemCount++; }); return menuItemCount; }, _getSelectedIndex: function _getSelectedIndex(props, filteredChildren) { var _this2 = this; var selectedIndex = -1; var menuItemIndex = 0; filteredChildren.forEach(function (child) { var childIsADivider = child.type && child.type.displayName === 'Divider'; if (_this2._isChildSelected(child, props)) selectedIndex = menuItemIndex; if (!childIsADivider) menuItemIndex++; }); return selectedIndex; }, _handleKeyDown: function _handleKeyDown(e) { var filteredChildren = this._getFilteredChildren(this.props.children); switch (e.keyCode) { case _keyCode2.default.DOWN: e.preventDefault(); this._incrementKeyboardFocusIndex(filteredChildren); break; case _keyCode2.default.ESC: this.props.onEscKeyDown(e); break; case _keyCode2.default.TAB: e.preventDefault(); if (e.shiftKey) { this._decrementKeyboardFocusIndex(); } else { this._incrementKeyboardFocusIndex(filteredChildren); } break; case _keyCode2.default.UP: e.preventDefault(); this._decrementKeyboardFocusIndex(); break; } this.props.onKeyDown(e); }, _handleMenuItemTouchTap: function _handleMenuItemTouchTap(e, item) { var children = this.props.children; var multiple = this.props.multiple; var valueLink = this.getValueLink(this.props); var menuValue = valueLink.value; var itemValue = item.props.value; var focusIndex = _react2.default.isValidElement(children) ? 0 : children.indexOf(item); this._setFocusIndex(focusIndex, false); if (multiple) { var index = menuValue.indexOf(itemValue); var newMenuValue = index === -1 ? (0, _reactAddonsUpdate2.default)(menuValue, { $push: [itemValue] }) : (0, _reactAddonsUpdate2.default)(menuValue, { $splice: [[index, 1]] }); valueLink.requestChange(e, newMenuValue); } else if (!multiple && itemValue !== menuValue) { valueLink.requestChange(e, itemValue); } this.props.onItemTouchTap(e, item); }, _incrementKeyboardFocusIndex: function _incrementKeyboardFocusIndex(filteredChildren) { var index = this.state.focusIndex; var maxIndex = this._getMenuItemCount(filteredChildren) - 1; index++; if (index > maxIndex) index = maxIndex; this._setFocusIndex(index, true); }, _isChildSelected: function _isChildSelected(child, props) { var multiple = props.multiple; var menuValue = this.getValueLink(props).value; var childValue = child.props.value; return multiple && menuValue.length && menuValue.indexOf(childValue) !== -1 || !multiple && menuValue && menuValue === childValue; }, _setFocusIndex: function _setFocusIndex(newIndex, isKeyboardFocused) { this.setState({ focusIndex: newIndex, isKeyboardFocused: isKeyboardFocused }); }, _setScollPosition: function _setScollPosition() { var desktop = this.props.desktop; var focusedMenuItem = this.refs.focusedMenuItem; var menuItemHeight = desktop ? 32 : 48; if (focusedMenuItem) { var selectedOffSet = _reactDom2.default.findDOMNode(focusedMenuItem).offsetTop; //Make the focused item be the 2nd item in the list the //user sees var scrollTop = selectedOffSet - menuItemHeight; if (scrollTop < menuItemHeight) scrollTop = 0; _reactDom2.default.findDOMNode(this.refs.scrollContainer).scrollTop = scrollTop; } }, _setWidth: function _setWidth() { var el = _reactDom2.default.findDOMNode(this); var listEl = _reactDom2.default.findDOMNode(this.refs.list); var elWidth = el.offsetWidth; var keyWidth = this.state.keyWidth; var minWidth = keyWidth * 1.5; var keyIncrements = elWidth / keyWidth; var newWidth = undefined; keyIncrements = keyIncrements <= 1.5 ? 1.5 : Math.ceil(keyIncrements); newWidth = keyIncrements * keyWidth; if (newWidth < minWidth) newWidth = minWidth; el.style.width = newWidth + 'px'; listEl.style.width = newWidth + 'px'; }, render: function render() { var _this3 = this; var _props3 = this.props; var animated = _props3.animated; var autoWidth = _props3.autoWidth; var children = _props3.children; var desktop = _props3.desktop; var initiallyKeyboardFocused = _props3.initiallyKeyboardFocused; var listStyle = _props3.listStyle; var maxHeight = _props3.maxHeight; var multiple = _props3.multiple; var openDirection = _props3.openDirection; var selectedMenuItemStyle = _props3.selectedMenuItemStyle; var style = _props3.style; var value = _props3.value; var valueLink = _props3.valueLink; var width = _props3.width; var zDepth = _props3.zDepth; var other = _objectWithoutProperties(_props3, ['animated', 'autoWidth', 'children', 'desktop', 'initiallyKeyboardFocused', 'listStyle', 'maxHeight', 'multiple', 'openDirection', 'selectedMenuItemStyle', 'style', 'value', 'valueLink', 'width', 'zDepth']); var openDown = openDirection.split('-')[0] === 'bottom'; var openLeft = openDirection.split('-')[1] === 'left'; var muiTheme = this.state.muiTheme; var rawTheme = muiTheme.rawTheme; var styles = { root: { //Nested div bacause the List scales x faster than //it scales y transition: animated ? _transitions2.default.easeOut('250ms', 'transform') : null, zIndex: muiTheme.zIndex.menu, top: openDown ? 0 : null, bottom: !openDown ? 0 : null, left: !openLeft ? 0 : null, right: openLeft ? 0 : null, transform: 'scaleX(0)', transformOrigin: openLeft ? 'right' : 'left' }, divider: { marginTop: 7, marginBottom: 8 }, list: { display: 'table-cell', paddingBottom: desktop ? 16 : 8, paddingTop: desktop ? 16 : 8, userSelect: 'none', width: width }, menuItemContainer: { transition: animated ? _transitions2.default.easeOut(null, 'opacity') : null, opacity: 0 }, paper: { transition: animated ? _transitions2.default.easeOut('500ms', ['transform', 'opacity']) : null, transform: 'scaleY(0)', transformOrigin: openDown ? 'top' : 'bottom', opacity: 0, maxHeight: maxHeight, overflowY: maxHeight ? 'auto' : null }, selectedMenuItem: { color: rawTheme.palette.accent1Color } }; var mergedRootStyles = this.mergeStyles(styles.root, style); var mergedListStyles = this.mergeStyles(styles.list, listStyle); var filteredChildren = this._getFilteredChildren(children); //Cascade children opacity var cumulativeDelay = openDown ? 175 : 325; var cascadeChildrenCount = this._getCascadeChildrenCount(filteredChildren); var cumulativeDelayIncrement = Math.ceil(150 / cascadeChildrenCount); var menuItemIndex = 0; var newChildren = _react2.default.Children.map(filteredChildren, function (child) { var childIsADivider = child.type && child.type.displayName === 'Divider'; var childIsDisabled = child.props.disabled; var childrenContainerStyles = {}; if (animated) { var focusIndex = _this3.state.focusIndex; var transitionDelay = 0; //Only cascade the visible menu items if (menuItemIndex >= focusIndex - 1 && menuItemIndex <= focusIndex + cascadeChildrenCount - 1) { cumulativeDelay = openDown ? cumulativeDelay + cumulativeDelayIncrement : cumulativeDelay - cumulativeDelayIncrement; transitionDelay = cumulativeDelay; } childrenContainerStyles = _this3.mergeStyles(styles.menuItemContainer, { transitionDelay: transitionDelay + 'ms' }); } var clonedChild = childIsADivider ? _react2.default.cloneElement(child, { style: styles.divider }) : childIsDisabled ? _react2.default.cloneElement(child, { desktop: desktop }) : _this3._cloneMenuItem(child, menuItemIndex, styles); if (!childIsADivider && !childIsDisabled) menuItemIndex++; return animated ? _react2.default.createElement( 'div', { style: _this3.prepareStyles(childrenContainerStyles) }, clonedChild ) : clonedChild; }); return _react2.default.createElement( 'div', { onKeyDown: this._handleKeyDown, style: this.prepareStyles(mergedRootStyles) }, _react2.default.createElement( _paper2.default, { ref: 'scrollContainer', style: styles.paper, zDepth: zDepth }, _react2.default.createElement( _list2.default, _extends({}, other, { ref: 'list', style: mergedListStyles }), newChildren ) ) ); } }); exports.default = Menu; module.exports = exports['default'];