|
- 'use strict';
-
- Object.defineProperty(exports, "__esModule", {
- value: true
- });
-
- var _extends2 = require('babel-runtime/helpers/extends');
-
- var _extends3 = _interopRequireDefault(_extends2);
-
- var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties');
-
- var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2);
-
- var _toArray2 = require('babel-runtime/helpers/toArray');
-
- var _toArray3 = _interopRequireDefault(_toArray2);
-
- var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
-
- var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
-
- var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
-
- var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
-
- var _createClass2 = require('babel-runtime/helpers/createClass');
-
- var _createClass3 = _interopRequireDefault(_createClass2);
-
- var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
-
- var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
-
- var _inherits2 = require('babel-runtime/helpers/inherits');
-
- var _inherits3 = _interopRequireDefault(_inherits2);
-
- var _simpleAssign = require('simple-assign');
-
- var _simpleAssign2 = _interopRequireDefault(_simpleAssign);
-
- var _react = require('react');
-
- var _react2 = _interopRequireDefault(_react);
-
- var _propTypes = require('prop-types');
-
- var _propTypes2 = _interopRequireDefault(_propTypes);
-
- var _reactDom = require('react-dom');
-
- var _reactDom2 = _interopRequireDefault(_reactDom);
-
- var _shallowEqual = require('recompose/shallowEqual');
-
- var _shallowEqual2 = _interopRequireDefault(_shallowEqual);
-
- var _ClickAwayListener = require('../internal/ClickAwayListener');
-
- var _ClickAwayListener2 = _interopRequireDefault(_ClickAwayListener);
-
- var _keycode = require('keycode');
-
- var _keycode2 = _interopRequireDefault(_keycode);
-
- var _propTypes3 = require('../utils/propTypes');
-
- var _propTypes4 = _interopRequireDefault(_propTypes3);
-
- var _List = require('../List/List');
-
- var _List2 = _interopRequireDefault(_List);
-
- var _menuUtils = require('./menuUtils');
-
- function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
-
- function getStyles(props, context) {
- var desktop = props.desktop,
- maxHeight = props.maxHeight,
- width = props.width;
- var muiTheme = context.muiTheme;
-
-
- var styles = {
- root: {
- // Nested div because the List scales x faster than it scales y
- zIndex: muiTheme.zIndex.menu,
- maxHeight: maxHeight,
- overflowY: maxHeight ? 'auto' : null
- },
- divider: {
- marginTop: 7,
- marginBottom: 8
- },
- list: {
- display: 'table-cell',
- paddingBottom: desktop ? 16 : 8,
- paddingTop: desktop ? 16 : 8,
- userSelect: 'none',
- width: width
- },
- selectedMenuItem: {
- color: muiTheme.menuItem.selectedTextColor
- }
- };
-
- return styles;
- }
-
- var Menu = function (_Component) {
- (0, _inherits3.default)(Menu, _Component);
-
- function Menu(props, context) {
- (0, _classCallCheck3.default)(this, Menu);
-
- var _this = (0, _possibleConstructorReturn3.default)(this, (Menu.__proto__ || (0, _getPrototypeOf2.default)(Menu)).call(this, props, context));
-
- _initialiseProps.call(_this);
-
- var filteredChildren = _this.getFilteredChildren(props.children);
- var selectedIndex = _this.getLastSelectedIndex(props, filteredChildren);
-
- var newFocusIndex = props.disableAutoFocus ? -1 : selectedIndex >= 0 ? selectedIndex : 0;
- if (newFocusIndex !== -1 && props.onMenuItemFocusChange) {
- props.onMenuItemFocusChange(null, newFocusIndex);
- }
- _this.state = {
- focusIndex: newFocusIndex,
- isKeyboardFocused: props.initiallyKeyboardFocused,
- keyWidth: props.desktop ? 64 : 56
- };
-
- _this.hotKeyHolder = new _menuUtils.HotKeyHolder();
- return _this;
- }
-
- (0, _createClass3.default)(Menu, [{
- key: 'componentDidMount',
- value: function componentDidMount() {
- if (this.props.autoWidth) {
- this.setWidth();
- }
- this.setScollPosition();
- }
- }, {
- key: 'componentWillReceiveProps',
- value: function componentWillReceiveProps(nextProps) {
- var selectedIndex = void 0;
- var filteredChildren = this.getFilteredChildren(nextProps.children);
-
- if (this.props.multiple !== true) {
- selectedIndex = this.getLastSelectedIndex(nextProps, filteredChildren);
- } else {
- selectedIndex = this.state.focusIndex;
- }
-
- var newFocusIndex = nextProps.disableAutoFocus ? -1 : selectedIndex >= 0 ? selectedIndex : 0;
- if (newFocusIndex !== this.state.focusIndex && this.props.onMenuItemFocusChange) {
- this.props.onMenuItemFocusChange(null, newFocusIndex);
- }
- this.setState({
- focusIndex: newFocusIndex,
- keyWidth: nextProps.desktop ? 64 : 56
- });
- }
- }, {
- key: 'shouldComponentUpdate',
- value: function shouldComponentUpdate(nextProps, nextState, nextContext) {
- return !(0, _shallowEqual2.default)(this.props, nextProps) || !(0, _shallowEqual2.default)(this.state, nextState) || !(0, _shallowEqual2.default)(this.context, nextContext);
- }
- }, {
- key: 'componentDidUpdate',
- value: function componentDidUpdate() {
- if (this.props.autoWidth) this.setWidth();
- }
- }, {
- key: 'getValueLink',
-
-
- // Do not use outside of this component, it will be removed once valueLink is deprecated
- value: function getValueLink(props) {
- return props.valueLink || {
- value: props.value,
- requestChange: props.onChange
- };
- }
- }, {
- key: 'setKeyboardFocused',
- value: function setKeyboardFocused(keyboardFocused) {
- this.setState({
- isKeyboardFocused: keyboardFocused
- });
- }
- }, {
- key: 'getFilteredChildren',
- value: function getFilteredChildren(children) {
- var filteredChildren = [];
- _react2.default.Children.forEach(children, function (child) {
- if (child) {
- filteredChildren.push(child);
- }
- });
- return filteredChildren;
- }
- }, {
- key: 'cloneMenuItem',
- value: function cloneMenuItem(child, childIndex, styles, index) {
- var _this2 = this;
-
- var childIsDisabled = child.props.disabled;
-
- var selectedChildStyles = {};
- if (!childIsDisabled) {
- var selected = this.isChildSelected(child, this.props);
-
- if (selected) {
- (0, _simpleAssign2.default)(selectedChildStyles, styles.selectedMenuItem, this.props.selectedMenuItemStyle);
- }
- }
- var mergedChildStyles = (0, _simpleAssign2.default)({}, child.props.style, this.props.menuItemStyle, selectedChildStyles);
-
- var extraProps = {
- desktop: this.props.desktop,
- style: mergedChildStyles
- };
- if (!childIsDisabled) {
- var isFocused = childIndex === this.state.focusIndex;
- var focusState = 'none';
- if (isFocused) {
- focusState = this.state.isKeyboardFocused ? 'keyboard-focused' : 'focused';
- }
-
- (0, _simpleAssign2.default)(extraProps, {
- focusState: focusState,
- onClick: function onClick(event) {
- _this2.handleMenuItemClick(event, child, index);
- if (child.props.onClick) child.props.onClick(event);
- },
- ref: isFocused ? 'focusedMenuItem' : null
- });
- }
- return _react2.default.cloneElement(child, extraProps);
- }
- }, {
- key: 'decrementKeyboardFocusIndex',
- value: function decrementKeyboardFocusIndex(event) {
- var index = this.state.focusIndex;
-
- index--;
- if (index < 0) index = 0;
-
- this.setFocusIndex(event, index, true);
- }
- }, {
- key: 'getMenuItemCount',
- value: function getMenuItemCount(filteredChildren) {
- var menuItemCount = 0;
- filteredChildren.forEach(function (child) {
- var childIsADivider = child.type && child.type.muiName === 'Divider';
- var childIsDisabled = child.props.disabled;
- if (!childIsADivider && !childIsDisabled) menuItemCount++;
- });
- return menuItemCount;
- }
- }, {
- key: 'getLastSelectedIndex',
- value: function getLastSelectedIndex(props, filteredChildren) {
- var _this3 = this;
-
- var selectedIndex = -1;
- var menuItemIndex = 0;
-
- filteredChildren.forEach(function (child) {
- var childIsADivider = child.type && child.type.muiName === 'Divider';
-
- if (_this3.isChildSelected(child, props)) selectedIndex = menuItemIndex;
- if (!childIsADivider) menuItemIndex++;
- });
-
- return selectedIndex;
- }
- }, {
- key: 'setFocusIndexStartsWith',
- value: function setFocusIndexStartsWith(event, keys, filteredChildren) {
- var foundIndex = -1;
- _react2.default.Children.forEach(filteredChildren, function (child, index) {
- if (foundIndex >= 0) {
- return;
- }
- var primaryText = child.props.primaryText;
-
- if (typeof primaryText === 'string' && primaryText.substr(0, keys.length).toLowerCase() === keys.toLowerCase()) {
- foundIndex = index;
- }
- });
- if (foundIndex >= 0) {
- this.setFocusIndex(event, foundIndex, true);
- return true;
- }
- return false;
- }
- }, {
- key: 'handleMenuItemClick',
- value: function handleMenuItemClick(event, item, index) {
- 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(event, focusIndex, false);
-
- if (multiple) {
- menuValue = menuValue || [];
-
- var itemIndex = menuValue.indexOf(itemValue);
-
- var _menuValue = menuValue,
- _menuValue2 = (0, _toArray3.default)(_menuValue),
- newMenuValue = _menuValue2.slice(0);
-
- if (itemIndex === -1) {
- newMenuValue.push(itemValue);
- } else {
- newMenuValue.splice(itemIndex, 1);
- }
-
- valueLink.requestChange(event, newMenuValue);
- } else if (!multiple && itemValue !== menuValue) {
- valueLink.requestChange(event, itemValue);
- }
-
- this.props.onItemClick(event, item, index);
- }
- }, {
- key: 'incrementKeyboardFocusIndex',
- value: function incrementKeyboardFocusIndex(event, filteredChildren) {
- var index = this.state.focusIndex;
- var maxIndex = this.getMenuItemCount(filteredChildren) - 1;
-
- index++;
- if (index > maxIndex) index = maxIndex;
-
- this.setFocusIndex(event, index, true);
- }
- }, {
- key: 'isChildSelected',
- value: function isChildSelected(child, props) {
- var menuValue = this.getValueLink(props).value;
- var childValue = child.props.value;
-
- if (props.multiple) {
- return menuValue && menuValue.length && menuValue.indexOf(childValue) !== -1;
- } else {
- return child.props.hasOwnProperty('value') && menuValue === childValue;
- }
- }
- }, {
- key: 'setFocusIndex',
- value: function setFocusIndex(event, newIndex, isKeyboardFocused) {
- if (this.props.onMenuItemFocusChange) {
- // Do this even if `newIndex === this.state.focusIndex` to allow users
- // to detect up-arrow on the first MenuItem or down-arrow on the last.
- this.props.onMenuItemFocusChange(event, newIndex);
- }
- this.setState({
- focusIndex: newIndex,
- isKeyboardFocused: isKeyboardFocused
- });
- }
- }, {
- key: 'setScollPosition',
- value: 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;
- }
- }
- }, {
- key: 'cancelScrollEvent',
- value: function cancelScrollEvent(event) {
- event.stopPropagation();
- event.preventDefault();
- return false;
- }
- }, {
- key: 'setWidth',
- value: 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 = void 0;
-
- 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';
- }
- }, {
- key: 'render',
- value: function render() {
- var _this4 = this;
-
- var _props = this.props,
- autoWidth = _props.autoWidth,
- children = _props.children,
- desktop = _props.desktop,
- disableAutoFocus = _props.disableAutoFocus,
- initiallyKeyboardFocused = _props.initiallyKeyboardFocused,
- listStyle = _props.listStyle,
- maxHeight = _props.maxHeight,
- multiple = _props.multiple,
- onItemClick = _props.onItemClick,
- onEscKeyDown = _props.onEscKeyDown,
- onMenuItemFocusChange = _props.onMenuItemFocusChange,
- selectedMenuItemStyle = _props.selectedMenuItemStyle,
- menuItemStyle = _props.menuItemStyle,
- style = _props.style,
- value = _props.value,
- valueLink = _props.valueLink,
- width = _props.width,
- other = (0, _objectWithoutProperties3.default)(_props, ['autoWidth', 'children', 'desktop', 'disableAutoFocus', 'initiallyKeyboardFocused', 'listStyle', 'maxHeight', 'multiple', 'onItemClick', 'onEscKeyDown', 'onMenuItemFocusChange', 'selectedMenuItemStyle', 'menuItemStyle', 'style', 'value', 'valueLink', 'width']);
- var prepareStyles = this.context.muiTheme.prepareStyles;
-
- var styles = getStyles(this.props, this.context);
-
- var mergedRootStyles = (0, _simpleAssign2.default)(styles.root, style);
- var mergedListStyles = (0, _simpleAssign2.default)(styles.list, listStyle);
-
- var filteredChildren = this.getFilteredChildren(children);
-
- var menuItemIndex = 0;
- var newChildren = _react2.default.Children.map(filteredChildren, function (child, index) {
- var childIsDisabled = child.props.disabled;
- var childName = child.type ? child.type.muiName : '';
- var newChild = child;
-
- switch (childName) {
- case 'MenuItem':
- newChild = _this4.cloneMenuItem(child, menuItemIndex, styles, index);
- break;
-
- case 'Divider':
- newChild = _react2.default.cloneElement(child, {
- style: (0, _simpleAssign2.default)({}, styles.divider, child.props.style)
- });
- break;
- }
-
- if (childName === 'MenuItem' && !childIsDisabled) {
- menuItemIndex++;
- }
-
- return newChild;
- });
-
- return _react2.default.createElement(
- _ClickAwayListener2.default,
- { onClickAway: this.handleClickAway },
- _react2.default.createElement(
- 'div',
- {
- onKeyDown: this.handleKeyDown,
- onWheel: this.handleOnWheel,
- style: prepareStyles(mergedRootStyles),
- ref: 'scrollContainer',
- role: 'presentation'
- },
- _react2.default.createElement(
- _List2.default,
- (0, _extends3.default)({}, other, {
- ref: 'list',
- style: mergedListStyles,
- role: 'menu'
- }),
- newChildren
- )
- )
- );
- }
- }]);
- return Menu;
- }(_react.Component);
-
- Menu.defaultProps = {
- autoWidth: true,
- desktop: false,
- disableAutoFocus: false,
- initiallyKeyboardFocused: false,
- maxHeight: null,
- multiple: false,
- onChange: function onChange() {},
- onEscKeyDown: function onEscKeyDown() {},
- onItemClick: function onItemClick() {},
- onKeyDown: function onKeyDown() {}
- };
- Menu.contextTypes = {
- muiTheme: _propTypes2.default.object.isRequired
- };
-
- var _initialiseProps = function _initialiseProps() {
- var _this5 = this;
-
- this.handleClickAway = function (event) {
- if (event.defaultPrevented) {
- return;
- }
-
- var focusIndex = _this5.state.focusIndex;
-
- if (focusIndex < 0) {
- return;
- }
-
- var filteredChildren = _this5.getFilteredChildren(_this5.props.children);
- var focusedItem = filteredChildren[focusIndex];
- if (!!focusedItem && focusedItem.props.menuItems && focusedItem.props.menuItems.length > 0) {
- return;
- }
-
- _this5.setFocusIndex(event, -1, false);
- };
-
- this.handleKeyDown = function (event) {
- var filteredChildren = _this5.getFilteredChildren(_this5.props.children);
- var key = (0, _keycode2.default)(event);
- switch (key) {
- case 'down':
- event.preventDefault();
- _this5.incrementKeyboardFocusIndex(event, filteredChildren);
- break;
- case 'esc':
- _this5.props.onEscKeyDown(event);
- break;
- case 'tab':
- event.preventDefault();
- if (event.shiftKey) {
- _this5.decrementKeyboardFocusIndex(event);
- } else {
- _this5.incrementKeyboardFocusIndex(event, filteredChildren);
- }
- break;
- case 'up':
- event.preventDefault();
- _this5.decrementKeyboardFocusIndex(event);
- break;
- default:
- if (key && key.length === 1) {
- var hotKeys = _this5.hotKeyHolder.append(key);
- if (_this5.setFocusIndexStartsWith(event, hotKeys, filteredChildren)) {
- event.preventDefault();
- }
- }
- }
- _this5.props.onKeyDown(event);
- };
-
- this.handleOnWheel = function (event) {
- var scrollContainer = _this5.refs.scrollContainer;
- // Only scroll lock if the the Menu is scrollable.
- if (scrollContainer.scrollHeight <= scrollContainer.clientHeight) return;
-
- var scrollTop = scrollContainer.scrollTop,
- scrollHeight = scrollContainer.scrollHeight,
- clientHeight = scrollContainer.clientHeight;
-
- var wheelDelta = event.deltaY;
- var isDeltaPositive = wheelDelta > 0;
-
- if (isDeltaPositive && wheelDelta > scrollHeight - clientHeight - scrollTop) {
- scrollContainer.scrollTop = scrollHeight;
- return _this5.cancelScrollEvent(event);
- } else if (!isDeltaPositive && -wheelDelta > scrollTop) {
- scrollContainer.scrollTop = 0;
- return _this5.cancelScrollEvent(event);
- }
- };
- };
-
- Menu.propTypes = process.env.NODE_ENV !== "production" ? {
- /**
- * If true, the width of the menu will be set automatically
- * according to the widths of its children,
- * using proper keyline increments (64px for desktop,
- * 56px otherwise).
- */
- autoWidth: _propTypes2.default.bool,
- /**
- * The content of the menu. This is usually used to pass `MenuItem`
- * elements.
- */
- children: _propTypes2.default.node,
- /**
- * If true, the menu item will render with compact desktop styles.
- */
- desktop: _propTypes2.default.bool,
- /**
- * If true, the menu will not be auto-focused.
- */
- disableAutoFocus: _propTypes2.default.bool,
- /**
- * If true, the menu will be keyboard-focused initially.
- */
- initiallyKeyboardFocused: _propTypes2.default.bool,
- /**
- * Override the inline-styles of the underlying `List` element.
- */
- listStyle: _propTypes2.default.object,
- /**
- * The maximum height of the menu in pixels. If specified,
- * the menu will be scrollable if it is taller than the provided
- * height.
- */
- maxHeight: _propTypes2.default.number,
- /**
- * Override the inline-styles of menu items.
- */
- menuItemStyle: _propTypes2.default.object,
- /**
- * If true, `value` must be an array and the menu will support
- * multiple selections.
- */
- multiple: _propTypes2.default.bool,
- /**
- * Callback function fired when a menu item with `value` not
- * equal to the current `value` of the menu is clicked.
- *
- * @param {object} event Click event targeting the menu item.
- * @param {any} value If `multiple` is true, the menu's `value`
- * array with either the menu item's `value` added (if
- * it wasn't already selected) or omitted (if it was already selected).
- * Otherwise, the `value` of the menu item.
- */
- onChange: _propTypes2.default.func,
- /**
- * Callback function fired when the menu is focused and the *Esc* key
- * is pressed.
- *
- * @param {object} event `keydown` event targeting the menu.
- */
- onEscKeyDown: _propTypes2.default.func,
- /**
- * Callback function fired when a menu item is clicked.
- *
- * @param {object} event Click event targeting the menu item.
- * @param {object} menuItem The menu item.
- * @param {number} index The index of the menu item.
- */
- onItemClick: _propTypes2.default.func,
- /** @ignore */
- onKeyDown: _propTypes2.default.func,
- /**
- * Callback function fired when the focus on a `MenuItem` is changed.
- * There will be some "duplicate" changes reported if two different
- * focusing event happen, for example if a `MenuItem` is focused via
- * the keyboard and then it is clicked on.
- *
- * @param {object} event The event that triggered the focus change.
- * The event can be null since the focus can be changed for non-event
- * reasons such as prop changes.
- * @param {number} newFocusIndex The index of the newly focused
- * `MenuItem` or `-1` if focus was lost.
- */
- onMenuItemFocusChange: _propTypes2.default.func,
- /**
- * Override the inline-styles of selected menu items.
- */
- selectedMenuItemStyle: _propTypes2.default.object,
- /**
- * Override the inline-styles of the root element.
- */
- style: _propTypes2.default.object,
- /**
- * If `multiple` is true, an array of the `value`s of the selected
- * menu items. Otherwise, the `value` of the selected menu item.
- * If provided, the menu will be a controlled component.
- * This component also supports valueLink.
- */
- value: _propTypes2.default.any,
- /**
- * ValueLink for the menu's `value`.
- */
- valueLink: _propTypes2.default.object,
- /**
- * The width of the menu. If not specified, the menu's width
- * will be set according to the widths of its children, using
- * proper keyline increments (64px for desktop, 56px otherwise).
- */
- width: _propTypes4.default.stringOrNumber
- } : {};
- exports.default = Menu;
|