UNPKG

semantic-ui-react

Version:
1,342 lines (1,038 loc) 50.8 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _defineProperty2 = require('babel-runtime/helpers/defineProperty'); var _defineProperty3 = _interopRequireDefault(_defineProperty2); var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); 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 _get3 = require('babel-runtime/helpers/get'); var _get4 = _interopRequireDefault(_get3); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _compact2 = require('lodash/compact'); var _compact3 = _interopRequireDefault(_compact2); var _map2 = require('lodash/map'); var _map3 = _interopRequireDefault(_map2); var _every2 = require('lodash/every'); var _every3 = _interopRequireDefault(_every2); var _without2 = require('lodash/without'); var _without3 = _interopRequireDefault(_without2); var _findIndex2 = require('lodash/findIndex'); var _findIndex3 = _interopRequireDefault(_findIndex2); var _find2 = require('lodash/find'); var _find3 = _interopRequireDefault(_find2); var _reduce2 = require('lodash/reduce'); var _reduce3 = _interopRequireDefault(_reduce2); var _some2 = require('lodash/some'); var _some3 = _interopRequireDefault(_some2); var _escapeRegExp2 = require('lodash/escapeRegExp'); var _escapeRegExp3 = _interopRequireDefault(_escapeRegExp2); var _deburr2 = require('lodash/deburr'); var _deburr3 = _interopRequireDefault(_deburr2); var _filter2 = require('lodash/filter'); var _filter3 = _interopRequireDefault(_filter2); var _isFunction2 = require('lodash/isFunction'); var _isFunction3 = _interopRequireDefault(_isFunction2); var _dropRight2 = require('lodash/dropRight'); var _dropRight3 = _interopRequireDefault(_dropRight2); var _isEmpty2 = require('lodash/isEmpty'); var _isEmpty3 = _interopRequireDefault(_isEmpty2); var _size2 = require('lodash/size'); var _size3 = _interopRequireDefault(_size2); var _union2 = require('lodash/union'); var _union3 = _interopRequireDefault(_union2); var _get5 = require('lodash/get'); var _get6 = _interopRequireDefault(_get5); var _includes2 = require('lodash/includes'); var _includes3 = _interopRequireDefault(_includes2); var _isUndefined2 = require('lodash/isUndefined'); var _isUndefined3 = _interopRequireDefault(_isUndefined2); var _invoke2 = require('lodash/invoke'); var _invoke3 = _interopRequireDefault(_invoke2); var _isEqual2 = require('lodash/isEqual'); var _isEqual3 = _interopRequireDefault(_isEqual2); var _has2 = require('lodash/has'); var _has3 = _interopRequireDefault(_has2); var _isNil2 = require('lodash/isNil'); var _isNil3 = _interopRequireDefault(_isNil2); var _classnames = require('classnames'); var _classnames2 = _interopRequireDefault(_classnames); var _propTypes = require('prop-types'); var _propTypes2 = _interopRequireDefault(_propTypes); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _lib = require('../../lib'); var _Icon = require('../../elements/Icon'); var _Icon2 = _interopRequireDefault(_Icon); var _Label = require('../../elements/Label'); var _Label2 = _interopRequireDefault(_Label); var _DropdownDivider = require('./DropdownDivider'); var _DropdownDivider2 = _interopRequireDefault(_DropdownDivider); var _DropdownItem = require('./DropdownItem'); var _DropdownItem2 = _interopRequireDefault(_DropdownItem); var _DropdownHeader = require('./DropdownHeader'); var _DropdownHeader2 = _interopRequireDefault(_DropdownHeader); var _DropdownMenu = require('./DropdownMenu'); var _DropdownMenu2 = _interopRequireDefault(_DropdownMenu); var _DropdownSearchInput = require('./DropdownSearchInput'); var _DropdownSearchInput2 = _interopRequireDefault(_DropdownSearchInput); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var getKeyOrValue = function getKeyOrValue(key, value) { return (0, _isNil3.default)(key) ? value : key; }; /** * A dropdown allows a user to select a value from a series of options. * @see Form * @see Select * @see Menu */ var Dropdown = function (_Component) { (0, _inherits3.default)(Dropdown, _Component); function Dropdown() { var _ref; var _temp, _this, _ret; (0, _classCallCheck3.default)(this, Dropdown); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = (0, _possibleConstructorReturn3.default)(this, (_ref = Dropdown.__proto__ || Object.getPrototypeOf(Dropdown)).call.apply(_ref, [this].concat(args))), _this), _this.handleChange = function (e, value) { (0, _invoke3.default)(_this.props, 'onChange', e, (0, _extends3.default)({}, _this.props, { value: value })); }, _this.closeOnChange = function (e) { var _this$props = _this.props, closeOnChange = _this$props.closeOnChange, multiple = _this$props.multiple; var shouldClose = (0, _isUndefined3.default)(closeOnChange) ? !multiple : closeOnChange; if (shouldClose) _this.close(e); }, _this.closeOnEscape = function (e) { if (_lib.keyboardKey.getCode(e) !== _lib.keyboardKey.Escape) return; e.preventDefault(); _this.close(); }, _this.moveSelectionOnKeyDown = function (e) { var _moves; var _this$props2 = _this.props, multiple = _this$props2.multiple, selectOnNavigation = _this$props2.selectOnNavigation; var moves = (_moves = {}, (0, _defineProperty3.default)(_moves, _lib.keyboardKey.ArrowDown, 1), (0, _defineProperty3.default)(_moves, _lib.keyboardKey.ArrowUp, -1), _moves); var move = moves[_lib.keyboardKey.getCode(e)]; if (move === undefined) return; e.preventDefault(); _this.moveSelectionBy(move); if (!multiple && selectOnNavigation) _this.makeSelectedItemActive(e); }, _this.openOnSpace = function (e) { if (_lib.keyboardKey.getCode(e) !== _lib.keyboardKey.Spacebar) return; if (_this.state.open) return; e.preventDefault(); _this.open(e); }, _this.openOnArrow = function (e) { var code = _lib.keyboardKey.getCode(e); if (!(0, _includes3.default)([_lib.keyboardKey.ArrowDown, _lib.keyboardKey.ArrowUp], code)) return; if (_this.state.open) return; e.preventDefault(); _this.open(e); }, _this.makeSelectedItemActive = function (e) { var open = _this.state.open; var multiple = _this.props.multiple; var item = _this.getSelectedItem(); var value = (0, _get6.default)(item, 'value'); // prevent selecting null if there was no selected item value // prevent selecting duplicate items when the dropdown is closed if ((0, _isNil3.default)(value) || !open) return; // state value may be undefined var newValue = multiple ? (0, _union3.default)(_this.state.value, [value]) : value; // notify the onChange prop that the user is trying to change value _this.setValue(newValue); _this.setSelectedIndex(newValue); _this.handleChange(e, newValue); // Heads up! This event handler should be called after `onChange` // Notify the onAddItem prop if this is a new value if (item['data-additional']) (0, _invoke3.default)(_this.props, 'onAddItem', e, (0, _extends3.default)({}, _this.props, { value: value })); }, _this.selectItemOnEnter = function (e) { var search = _this.props.search; if (_lib.keyboardKey.getCode(e) !== _lib.keyboardKey.Enter) return; e.preventDefault(); var optionSize = (0, _size3.default)(_this.getMenuOptions()); if (search && optionSize === 0) return; _this.makeSelectedItemActive(e); _this.closeOnChange(e); _this.clearSearchQuery(); if (search && _this.searchRef) _this.searchRef.focus(); }, _this.removeItemOnBackspace = function (e) { var _this$props3 = _this.props, multiple = _this$props3.multiple, search = _this$props3.search; var _this$state = _this.state, searchQuery = _this$state.searchQuery, value = _this$state.value; if (_lib.keyboardKey.getCode(e) !== _lib.keyboardKey.Backspace) return; if (searchQuery || !search || !multiple || (0, _isEmpty3.default)(value)) return; e.preventDefault(); // remove most recent value var newValue = (0, _dropRight3.default)(value); _this.setValue(newValue); _this.setSelectedIndex(newValue); _this.handleChange(e, newValue); }, _this.closeOnDocumentClick = function (e) { if (!_this.props.closeOnBlur) return; // If event happened in the dropdown, ignore it if (_this.ref && (0, _isFunction3.default)(_this.ref.contains) && _this.ref.contains(e.target)) return; _this.close(); }, _this.handleMouseDown = function (e) { _this.isMouseDown = true; _lib.eventStack.sub('mouseup', _this.handleDocumentMouseUp); (0, _invoke3.default)(_this.props, 'onMouseDown', e, _this.props); }, _this.handleDocumentMouseUp = function () { _this.isMouseDown = false; _lib.eventStack.unsub('mouseup', _this.handleDocumentMouseUp); }, _this.handleClick = function (e) { var _this$props4 = _this.props, minCharacters = _this$props4.minCharacters, search = _this$props4.search; var _this$state2 = _this.state, open = _this$state2.open, searchQuery = _this$state2.searchQuery; (0, _invoke3.default)(_this.props, 'onClick', e, _this.props); // prevent closeOnDocumentClick() e.stopPropagation(); if (!search) return _this.toggle(e); if (open) return; if (searchQuery.length >= minCharacters || minCharacters === 1) { _this.open(e); return; } if (_this.searchRef) _this.searchRef.focus(); }, _this.handleIconClick = function (e) { (0, _invoke3.default)(_this.props, 'onClick', e, _this.props); // prevent handleClick() e.stopPropagation(); _this.toggle(e); }, _this.handleItemClick = function (e, item) { var _this$props5 = _this.props, multiple = _this$props5.multiple, search = _this$props5.search; var value = item.value; // prevent toggle() in handleClick() e.stopPropagation(); // prevent closeOnDocumentClick() if multiple or item is disabled if (multiple || item.disabled) e.nativeEvent.stopImmediatePropagation(); if (item.disabled) return; var isAdditionItem = item['data-additional']; var newValue = multiple ? (0, _union3.default)(_this.state.value, [value]) : value; // notify the onChange prop that the user is trying to change value _this.setValue(newValue); _this.setSelectedIndex(value); var optionSize = (0, _size3.default)(_this.getMenuOptions()); if (!multiple || isAdditionItem || optionSize === 1) _this.clearSearchQuery(); _this.handleChange(e, newValue); _this.closeOnChange(e); // Heads up! This event handler should be called after `onChange` // Notify the onAddItem prop if this is a new value if (isAdditionItem) (0, _invoke3.default)(_this.props, 'onAddItem', e, (0, _extends3.default)({}, _this.props, { value: value })); if (multiple && search && _this.searchRef) _this.searchRef.focus(); }, _this.handleFocus = function (e) { var focus = _this.state.focus; if (focus) return; (0, _invoke3.default)(_this.props, 'onFocus', e, _this.props); _this.setState({ focus: true }); }, _this.handleBlur = function (e) { // Heads up! Don't remove this. // https://github.com/Semantic-Org/Semantic-UI-React/issues/1315 var currentTarget = (0, _get6.default)(e, 'currentTarget'); if (currentTarget && currentTarget.contains(document.activeElement)) return; var _this$props6 = _this.props, closeOnBlur = _this$props6.closeOnBlur, multiple = _this$props6.multiple, onBlur = _this$props6.onBlur, selectOnBlur = _this$props6.selectOnBlur; // do not "blur" when the mouse is down inside of the Dropdown if (_this.isMouseDown) return; if (onBlur) onBlur(e, _this.props); if (selectOnBlur && !multiple) { _this.makeSelectedItemActive(e); if (closeOnBlur) _this.close(); } _this.setState({ focus: false }); _this.clearSearchQuery(); }, _this.handleSearchChange = function (e, _ref2) { var value = _ref2.value; // prevent propagating to this.props.onChange() e.stopPropagation(); var minCharacters = _this.props.minCharacters; var open = _this.state.open; var newQuery = value; (0, _invoke3.default)(_this.props, 'onSearchChange', e, (0, _extends3.default)({}, _this.props, { searchQuery: newQuery })); _this.trySetState({ searchQuery: newQuery }, { selectedIndex: 0 }); // open search dropdown on search query if (!open && newQuery.length >= minCharacters) { _this.open(); return; } // close search dropdown if search query is too small if (open && minCharacters !== 1 && newQuery.length < minCharacters) _this.close(); }, _this.getMenuOptions = function () { var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.state.value; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _this.props.options; var _this$props7 = _this.props, additionLabel = _this$props7.additionLabel, additionPosition = _this$props7.additionPosition, allowAdditions = _this$props7.allowAdditions, deburr = _this$props7.deburr, multiple = _this$props7.multiple, search = _this$props7.search; var searchQuery = _this.state.searchQuery; var filteredOptions = options; // filter out active options if (multiple) { filteredOptions = (0, _filter3.default)(filteredOptions, function (opt) { return !(0, _includes3.default)(value, opt.value); }); } // filter by search query if (search && searchQuery) { if ((0, _isFunction3.default)(search)) { filteredOptions = search(filteredOptions, searchQuery); } else { // remove diacritics on search input and options, if deburr prop is set var strippedQuery = deburr ? (0, _deburr3.default)(searchQuery) : searchQuery; var re = new RegExp((0, _escapeRegExp3.default)(strippedQuery), 'i'); filteredOptions = (0, _filter3.default)(filteredOptions, function (opt) { return re.test(deburr ? (0, _deburr3.default)(opt.text) : opt.text); }); } } // insert the "add" item if (allowAdditions && search && searchQuery && !(0, _some3.default)(filteredOptions, { text: searchQuery })) { var additionLabelElement = _react2.default.isValidElement(additionLabel) ? _react2.default.cloneElement(additionLabel, { key: 'addition-label' }) : additionLabel || ''; var addItem = { key: 'addition', // by using an array, we can pass multiple elements, but when doing so // we must specify a `key` for React to know which one is which text: [additionLabelElement, _react2.default.createElement( 'b', { key: 'addition-query' }, searchQuery )], value: searchQuery, className: 'addition', 'data-additional': true }; if (additionPosition === 'top') filteredOptions.unshift(addItem);else filteredOptions.push(addItem); } return filteredOptions; }, _this.getSelectedItem = function () { var selectedIndex = _this.state.selectedIndex; var options = _this.getMenuOptions(); return (0, _get6.default)(options, '[' + selectedIndex + ']'); }, _this.getEnabledIndices = function (givenOptions) { var options = givenOptions || _this.getMenuOptions(); return (0, _reduce3.default)(options, function (memo, item, index) { if (!item.disabled) memo.push(index); return memo; }, []); }, _this.getItemByValue = function (value) { var options = _this.props.options; return (0, _find3.default)(options, { value: value }); }, _this.getMenuItemIndexByValue = function (value, givenOptions) { var options = givenOptions || _this.getMenuOptions(); return (0, _findIndex3.default)(options, ['value', value]); }, _this.getDropdownAriaOptions = function () { var _this$props8 = _this.props, loading = _this$props8.loading, disabled = _this$props8.disabled, search = _this$props8.search, multiple = _this$props8.multiple; var open = _this.state.open; var ariaOptions = { role: search ? 'combobox' : 'listbox', 'aria-busy': loading, 'aria-disabled': disabled, 'aria-expanded': !!open }; if (ariaOptions.role === 'listbox') { ariaOptions['aria-multiselectable'] = multiple; } return ariaOptions; }, _this.clearSearchQuery = function () { _this.trySetState({ searchQuery: '' }); }, _this.setValue = function (value) { _this.trySetState({ value: value }); }, _this.setSelectedIndex = function () { var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _this.state.value; var optionsProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _this.props.options; var multiple = _this.props.multiple; var selectedIndex = _this.state.selectedIndex; var options = _this.getMenuOptions(value, optionsProps); var enabledIndicies = _this.getEnabledIndices(options); var newSelectedIndex = void 0; // update the selected index if (!selectedIndex || selectedIndex < 0) { var firstIndex = enabledIndicies[0]; // Select the currently active item, if none, use the first item. // Multiple selects remove active items from the list, // their initial selected index should be 0. newSelectedIndex = multiple ? firstIndex : _this.getMenuItemIndexByValue(value, options) || enabledIndicies[0]; } else if (multiple) { // multiple selects remove options from the menu as they are made active // keep the selected index within range of the remaining items if (selectedIndex >= options.length - 1) { newSelectedIndex = enabledIndicies[enabledIndicies.length - 1]; } } else { var activeIndex = _this.getMenuItemIndexByValue(value, options); // regular selects can only have one active item // set the selected index to the currently active item newSelectedIndex = (0, _includes3.default)(enabledIndicies, activeIndex) ? activeIndex : undefined; } if (!newSelectedIndex || newSelectedIndex < 0) { newSelectedIndex = enabledIndicies[0]; } _this.setState({ selectedIndex: newSelectedIndex }); }, _this.handleLabelClick = function (e, labelProps) { // prevent focusing search input on click e.stopPropagation(); _this.setState({ selectedLabel: labelProps.value }); var onLabelClick = _this.props.onLabelClick; if (onLabelClick) onLabelClick(e, labelProps); }, _this.handleLabelRemove = function (e, labelProps) { // prevent focusing search input on click e.stopPropagation(); var value = _this.state.value; var newValue = (0, _without3.default)(value, labelProps.value); _this.setValue(newValue); _this.setSelectedIndex(newValue); _this.handleChange(e, newValue); }, _this.moveSelectionBy = function (offset) { var startIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _this.state.selectedIndex; var options = _this.getMenuOptions(); // Prevent infinite loop // TODO: remove left part of condition after children API will be removed if (options === undefined || (0, _every3.default)(options, 'disabled')) return; var lastIndex = options.length - 1; // next is after last, wrap to beginning // next is before first, wrap to end var nextIndex = startIndex + offset; if (nextIndex > lastIndex) nextIndex = 0;else if (nextIndex < 0) nextIndex = lastIndex; if (options[nextIndex].disabled) { _this.moveSelectionBy(offset, nextIndex); return; } _this.setState({ selectedIndex: nextIndex }); _this.scrollSelectedItemIntoView(); }, _this.handleIconOverrides = function (predefinedProps) { return { onClick: function onClick(e) { (0, _invoke3.default)(predefinedProps, 'onClick', e, predefinedProps); _this.handleIconClick(e); } }; }, _this.handleSearchRef = function (c) { return _this.searchRef = c; }, _this.handleSizerRef = function (c) { return _this.sizerRef = c; }, _this.handleRef = function (c) { return _this.ref = c; }, _this.computeSearchInputTabIndex = function () { var _this$props9 = _this.props, disabled = _this$props9.disabled, tabIndex = _this$props9.tabIndex; if (!(0, _isNil3.default)(tabIndex)) return tabIndex; return disabled ? -1 : 0; }, _this.computeSearchInputWidth = function () { var searchQuery = _this.state.searchQuery; if (_this.sizerRef && searchQuery) { // resize the search input, temporarily show the sizer so we can measure it _this.sizerRef.style.display = 'inline'; _this.sizerRef.textContent = searchQuery; var searchWidth = Math.ceil(_this.sizerRef.getBoundingClientRect().width); _this.sizerRef.style.removeProperty('display'); return searchWidth; } }, _this.computeTabIndex = function () { var _this$props10 = _this.props, disabled = _this$props10.disabled, search = _this$props10.search, tabIndex = _this$props10.tabIndex; // don't set a root node tabIndex as the search input has its own tabIndex if (search) return undefined; if (disabled) return -1; return (0, _isNil3.default)(tabIndex) ? 0 : tabIndex; }, _this.scrollSelectedItemIntoView = function () { if (!_this.ref) return; var menu = _this.ref.querySelector('.menu.visible'); if (!menu) return; var item = menu.querySelector('.item.selected'); if (!item) return; var isOutOfUpperView = item.offsetTop < menu.scrollTop; var isOutOfLowerView = item.offsetTop + item.clientHeight > menu.scrollTop + menu.clientHeight; if (isOutOfUpperView) { menu.scrollTop = item.offsetTop; } else if (isOutOfLowerView) { menu.scrollTop = item.offsetTop + item.clientHeight - menu.clientHeight; } }, _this.open = function (e) { var _this$props11 = _this.props, disabled = _this$props11.disabled, onOpen = _this$props11.onOpen, search = _this$props11.search; if (disabled) return; if (search && _this.searchRef) _this.searchRef.focus(); if (onOpen) onOpen(e, _this.props); _this.trySetState({ open: true }); _this.scrollSelectedItemIntoView(); }, _this.close = function (e) { var onClose = _this.props.onClose; if (onClose) onClose(e, _this.props); _this.trySetState({ open: false }); }, _this.handleClose = function () { var hasSearchFocus = document.activeElement === _this.searchRef; var hasDropdownFocus = document.activeElement === _this.ref; var hasFocus = hasSearchFocus || hasDropdownFocus; // https://github.com/Semantic-Org/Semantic-UI-React/issues/627 // Blur the Dropdown on close so it is blurred after selecting an item. // This is to prevent it from re-opening when switching tabs after selecting an item. if (!hasSearchFocus) { _this.ref.blur(); } // We need to keep the virtual model in sync with the browser focus change // https://github.com/Semantic-Org/Semantic-UI-React/issues/692 _this.setState({ focus: hasFocus }); }, _this.toggle = function (e) { return _this.state.open ? _this.close(e) : _this.open(e); }, _this.renderText = function () { var _this$props12 = _this.props, multiple = _this$props12.multiple, placeholder = _this$props12.placeholder, search = _this$props12.search, text = _this$props12.text; var _this$state3 = _this.state, searchQuery = _this$state3.searchQuery, value = _this$state3.value, open = _this$state3.open; var hasValue = multiple ? !(0, _isEmpty3.default)(value) : !(0, _isNil3.default)(value) && value !== ''; var classes = (0, _classnames2.default)(placeholder && !hasValue && 'default', 'text', search && searchQuery && 'filtered'); var _text = placeholder; if (searchQuery) { _text = null; } else if (text) { _text = text; } else if (open && !multiple) { _text = (0, _get6.default)(_this.getSelectedItem(), 'text'); } else if (hasValue) { _text = (0, _get6.default)(_this.getItemByValue(value), 'text'); } return _react2.default.createElement( 'div', { className: classes, role: 'alert', 'aria-live': 'polite' }, _text ); }, _this.renderSearchInput = function () { var _this$props13 = _this.props, search = _this$props13.search, searchInput = _this$props13.searchInput; var searchQuery = _this.state.searchQuery; if (!search) return null; return _DropdownSearchInput2.default.create(searchInput, { defaultProps: { inputRef: _this.handleSearchRef, onChange: _this.handleSearchChange, style: { width: _this.computeSearchInputWidth() }, tabIndex: _this.computeSearchInputTabIndex(), value: searchQuery } }); }, _this.renderSearchSizer = function () { var _this$props14 = _this.props, search = _this$props14.search, multiple = _this$props14.multiple; if (!(search && multiple)) return null; return _react2.default.createElement('span', { className: 'sizer', ref: _this.handleSizerRef }); }, _this.renderLabels = function () { var _this$props15 = _this.props, multiple = _this$props15.multiple, renderLabel = _this$props15.renderLabel; var _this$state4 = _this.state, selectedLabel = _this$state4.selectedLabel, value = _this$state4.value; if (!multiple || (0, _isEmpty3.default)(value)) { return; } var selectedItems = (0, _map3.default)(value, _this.getItemByValue); // if no item could be found for a given state value the selected item will be undefined // compact the selectedItems so we only have actual objects left return (0, _map3.default)((0, _compact3.default)(selectedItems), function (item, index) { var defaultProps = { active: item.value === selectedLabel, as: 'a', key: getKeyOrValue(item.key, item.value), onClick: _this.handleLabelClick, onRemove: _this.handleLabelRemove, value: item.value }; return _Label2.default.create(renderLabel(item, index, defaultProps), { defaultProps: defaultProps }); }); }, _this.renderOptions = function () { var _this$props16 = _this.props, multiple = _this$props16.multiple, search = _this$props16.search, noResultsMessage = _this$props16.noResultsMessage; var _this$state5 = _this.state, selectedIndex = _this$state5.selectedIndex, value = _this$state5.value; var options = _this.getMenuOptions(); if (noResultsMessage !== null && search && (0, _isEmpty3.default)(options)) { return _react2.default.createElement( 'div', { className: 'message' }, noResultsMessage ); } var isActive = multiple ? function (optValue) { return (0, _includes3.default)(value, optValue); } : function (optValue) { return optValue === value; }; return (0, _map3.default)(options, function (opt, i) { return _DropdownItem2.default.create((0, _extends3.default)({ active: isActive(opt.value), onClick: _this.handleItemClick, selected: selectedIndex === i }, opt, { key: getKeyOrValue(opt.key, opt.value), // Needed for handling click events on disabled items style: (0, _extends3.default)({}, opt.style, { pointerEvents: 'all' }) })); }); }, _this.renderMenu = function () { var _this$props17 = _this.props, children = _this$props17.children, header = _this$props17.header; var open = _this.state.open; var menuClasses = open ? 'visible' : ''; var ariaOptions = _this.getDropdownMenuAriaOptions(); // single menu child if (!_lib.childrenUtils.isNil(children)) { var menuChild = _react.Children.only(children); var className = (0, _classnames2.default)(menuClasses, menuChild.props.className); return (0, _react.cloneElement)(menuChild, (0, _extends3.default)({ className: className }, ariaOptions)); } return _react2.default.createElement( _DropdownMenu2.default, (0, _extends3.default)({}, ariaOptions, { className: menuClasses }), _DropdownHeader2.default.create(header), _this.renderOptions() ); }, _temp), (0, _possibleConstructorReturn3.default)(_this, _ret); } (0, _createClass3.default)(Dropdown, [{ key: 'getInitialAutoControlledState', value: function getInitialAutoControlledState() { return { searchQuery: '' }; } }, { key: 'componentWillMount', value: function componentWillMount() { var _state = this.state, open = _state.open, value = _state.value; this.setValue(value); this.setSelectedIndex(value); if (open) this.open(); } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { (0, _get4.default)(Dropdown.prototype.__proto__ || Object.getPrototypeOf(Dropdown.prototype), 'componentWillReceiveProps', this).call(this, nextProps); /* eslint-disable no-console */ if (process.env.NODE_ENV !== 'production') { // in development, validate value type matches dropdown type var isNextValueArray = Array.isArray(nextProps.value); var hasValue = (0, _has3.default)(nextProps, 'value'); if (hasValue && nextProps.multiple && !isNextValueArray) { console.error('Dropdown `value` must be an array when `multiple` is set.' + (' Received type: `' + Object.prototype.toString.call(nextProps.value) + '`.')); } else if (hasValue && !nextProps.multiple && isNextValueArray) { console.error('Dropdown `value` must not be an array when `multiple` is not set.' + ' Either set `multiple={true}` or use a string or number value.'); } } /* eslint-enable no-console */ if (!(0, _lib.shallowEqual)(nextProps.value, this.props.value)) { this.setValue(nextProps.value); this.setSelectedIndex(nextProps.value); } if (!(0, _isEqual3.default)(nextProps.options, this.props.options)) { this.setSelectedIndex(undefined, nextProps.options); } } }, { key: 'shouldComponentUpdate', value: function shouldComponentUpdate(nextProps, nextState) { return !(0, _lib.shallowEqual)(nextProps, this.props) || !(0, _lib.shallowEqual)(nextState, this.state); } }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps, prevState) { // focused / blurred // eslint-disable-line complexity if (!prevState.focus && this.state.focus) { if (!this.isMouseDown) { var _props = this.props, minCharacters = _props.minCharacters, openOnFocus = _props.openOnFocus, search = _props.search; var openable = !search || search && minCharacters === 1; if (openOnFocus && openable) this.open(); } if (!this.state.open) { _lib.eventStack.sub('keydown', [this.openOnArrow, this.openOnSpace]); } else { _lib.eventStack.sub('keydown', [this.moveSelectionOnKeyDown, this.selectItemOnEnter]); } _lib.eventStack.sub('keydown', this.removeItemOnBackspace); } else if (prevState.focus && !this.state.focus) { var closeOnBlur = this.props.closeOnBlur; if (!this.isMouseDown && closeOnBlur) { this.close(); } _lib.eventStack.unsub('keydown', [this.openOnArrow, this.openOnSpace, this.moveSelectionOnKeyDown, this.selectItemOnEnter, this.removeItemOnBackspace]); } // opened / closed if (!prevState.open && this.state.open) { _lib.eventStack.sub('keydown', [this.closeOnEscape, this.moveSelectionOnKeyDown, this.selectItemOnEnter, this.removeItemOnBackspace]); _lib.eventStack.sub('click', this.closeOnDocumentClick); _lib.eventStack.unsub('keydown', [this.openOnArrow, this.openOnSpace]); this.scrollSelectedItemIntoView(); } else if (prevState.open && !this.state.open) { this.handleClose(); _lib.eventStack.unsub('keydown', [this.closeOnEscape, this.moveSelectionOnKeyDown, this.selectItemOnEnter]); _lib.eventStack.unsub('click', this.closeOnDocumentClick); if (!this.state.focus) { _lib.eventStack.unsub('keydown', this.removeItemOnBackspace); } } } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { _lib.eventStack.unsub('keydown', [this.openOnArrow, this.openOnSpace, this.moveSelectionOnKeyDown, this.selectItemOnEnter, this.removeItemOnBackspace, this.closeOnEscape]); _lib.eventStack.unsub('click', this.closeOnDocumentClick); } // ---------------------------------------- // Document Event Handlers // ---------------------------------------- // onChange needs to receive a value // can't rely on props.value if we are controlled // ---------------------------------------- // Component Event Handlers // ---------------------------------------- // ---------------------------------------- // Getters // ---------------------------------------- // There are times when we need to calculate the options based on a value // that hasn't yet been persisted to state. }, { key: 'getDropdownMenuAriaOptions', value: function getDropdownMenuAriaOptions() { var _props2 = this.props, search = _props2.search, multiple = _props2.multiple; var ariaOptions = {}; if (search) { ariaOptions['aria-multiselectable'] = multiple; ariaOptions.role = 'listbox'; } return ariaOptions; } // ---------------------------------------- // Setters // ---------------------------------------- // ---------------------------------------- // Overrides // ---------------------------------------- // ---------------------------------------- // Refs // ---------------------------------------- // ---------------------------------------- // Helpers // ---------------------------------------- // ---------------------------------------- // Behavior // ---------------------------------------- // ---------------------------------------- // Render // ---------------------------------------- }, { key: 'render', value: function render() { var _props3 = this.props, basic = _props3.basic, button = _props3.button, className = _props3.className, compact = _props3.compact, disabled = _props3.disabled, error = _props3.error, fluid = _props3.fluid, floating = _props3.floating, icon = _props3.icon, inline = _props3.inline, item = _props3.item, labeled = _props3.labeled, loading = _props3.loading, multiple = _props3.multiple, pointing = _props3.pointing, search = _props3.search, selection = _props3.selection, scrolling = _props3.scrolling, simple = _props3.simple, trigger = _props3.trigger, upward = _props3.upward; var open = this.state.open; // Classes var classes = (0, _classnames2.default)('ui', (0, _lib.useKeyOnly)(open, 'active visible'), (0, _lib.useKeyOnly)(disabled, 'disabled'), (0, _lib.useKeyOnly)(error, 'error'), (0, _lib.useKeyOnly)(loading, 'loading'), (0, _lib.useKeyOnly)(basic, 'basic'), (0, _lib.useKeyOnly)(button, 'button'), (0, _lib.useKeyOnly)(compact, 'compact'), (0, _lib.useKeyOnly)(fluid, 'fluid'), (0, _lib.useKeyOnly)(floating, 'floating'), (0, _lib.useKeyOnly)(inline, 'inline'), // TODO: consider augmentation to render Dropdowns as Button/Menu, solves icon/link item issues // https://github.com/Semantic-Org/Semantic-UI-React/issues/401#issuecomment-240487229 // TODO: the icon class is only required when a dropdown is a button // useKeyOnly(icon, 'icon'), (0, _lib.useKeyOnly)(labeled, 'labeled'), (0, _lib.useKeyOnly)(item, 'item'), (0, _lib.useKeyOnly)(multiple, 'multiple'), (0, _lib.useKeyOnly)(search, 'search'), (0, _lib.useKeyOnly)(selection, 'selection'), (0, _lib.useKeyOnly)(simple, 'simple'), (0, _lib.useKeyOnly)(scrolling, 'scrolling'), (0, _lib.useKeyOnly)(upward, 'upward'), (0, _lib.useKeyOrValueAndKey)(pointing, 'pointing'), 'dropdown', className); var rest = (0, _lib.getUnhandledProps)(Dropdown, this.props); var ElementType = (0, _lib.getElementType)(Dropdown, this.props); var ariaOptions = this.getDropdownAriaOptions(ElementType, this.props); return _react2.default.createElement( ElementType, (0, _extends3.default)({}, rest, ariaOptions, { className: classes, onBlur: this.handleBlur, onClick: this.handleClick, onMouseDown: this.handleMouseDown, onFocus: this.handleFocus, onChange: this.handleChange, tabIndex: this.computeTabIndex(), ref: this.handleRef }), this.renderLabels(), this.renderSearchInput(), this.renderSearchSizer(), trigger || this.renderText(), _Icon2.default.create(icon, { overrideProps: this.handleIconOverrides }), this.renderMenu() ); } }]); return Dropdown; }(_lib.AutoControlledComponent); Dropdown.defaultProps = { additionLabel: 'Add ', additionPosition: 'top', closeOnBlur: true, deburr: false, icon: 'dropdown', minCharacters: 1, noResultsMessage: 'No results found.', openOnFocus: true, renderLabel: function renderLabel(_ref3) { var text = _ref3.text; return text; }, searchInput: 'text', selectOnBlur: true, selectOnNavigation: true }; Dropdown.autoControlledProps = ['open', 'searchQuery', 'selectedLabel', 'value']; Dropdown._meta = { name: 'Dropdown', type: _lib.META.TYPES.MODULE }; Dropdown.Divider = _DropdownDivider2.default; Dropdown.Header = _DropdownHeader2.default; Dropdown.Item = _DropdownItem2.default; Dropdown.Menu = _DropdownMenu2.default; Dropdown.SearchInput = _DropdownSearchInput2.default; Dropdown.handledProps = ['additionLabel', 'additionPosition', 'allowAdditions', 'as', 'basic', 'button', 'children', 'className', 'closeOnBlur', 'closeOnChange', 'compact', 'deburr', 'defaultOpen', 'defaultSearchQuery', 'defaultSelectedLabel', 'defaultValue', 'disabled', 'error', 'floating', 'fluid', 'header', 'icon', 'inline', 'item', 'labeled', 'loading', 'minCharacters', 'multiple', 'noResultsMessage', 'onAddItem', 'onBlur', 'onChange', 'onClick', 'onClose', 'onFocus', 'onLabelClick', 'onMouseDown', 'onOpen', 'onSearchChange', 'open', 'openOnFocus', 'options', 'placeholder', 'pointing', 'renderLabel', 'scrolling', 'search', 'searchInput', 'searchQuery', 'selectOnBlur', 'selectOnNavigation', 'selectedLabel', 'selection', 'simple', 'tabIndex', 'text', 'trigger', 'upward', 'value']; exports.default = Dropdown; Dropdown.propTypes = process.env.NODE_ENV !== "production" ? { /** An element type to render as (string or function). */ as: _lib.customPropTypes.as, /** Label prefixed to an option added by a user. */ additionLabel: _propTypes2.default.oneOfType([_propTypes2.default.element, _propTypes2.default.string]), /** Position of the `Add: ...` option in the dropdown list ('top' or 'bottom'). */ additionPosition: _propTypes2.default.oneOf(['top', 'bottom']), /** * Allow user additions to the list of options (boolean). * Requires the use of `selection`, `options` and `search`. */ allowAdditions: _lib.customPropTypes.every([_lib.customPropTypes.demand(['options', 'selection', 'search']), _propTypes2.default.bool]), /** A Dropdown can reduce its complexity. */ basic: _propTypes2.default.bool, /** Format the Dropdown to appear as a button. */ button: _propTypes2.default.bool, /** Primary content. */ children: _lib.customPropTypes.every([_lib.customPropTypes.disallow(['options', 'selection']), _lib.customPropTypes.givenProps({ children: _propTypes2.default.any.isRequired }, _propTypes2.default.element.isRequired)]), /** Additional classes. */ className: _propTypes2.default.string, /** Whether or not the menu should close when the dropdown is blurred. */ closeOnBlur: _propTypes2.default.bool, /** * Whether or not the menu should close when a value is selected from the dropdown. * By default, multiple selection dropdowns will remain open on change, while single * selection dropdowns will close on change. */ closeOnChange: _propTypes2.default.bool, /** A compact dropdown has no minimum width. */ compact: _propTypes2.default.bool, /** Whether or not the dropdown should strip diacritics in options and input search */ deburr: _propTypes2.default.bool, /** Initial value of open. */ defaultOpen: _propTypes2.default.bool, /** Initial value of searchQuery. */ defaultSearchQuery: _propTypes2.default.string, /** Currently selected label in multi-select. */ defaultSelectedLabel: _lib.customPropTypes.every([_lib.customPropTypes.demand(['multiple']), _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.string])]), /** Initial value or value array if multiple. */ defaultValue: _propTypes2.default.oneOfType([_propTypes2.default.number, _propTypes2.default.string, _propTypes2.default.arrayOf(_propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number]))]), /** A disabled dropdown menu or item does not allow user interaction. */ disabled: _propTypes2.default.bool, /** An errored dropdown can alert a user to a problem. */ error: _propTypes2.default.bool, /** A dropdown menu can contain floated content. */ floating: _propTypes2.default.bool, /** A dropdown can take the full width of its parent */ fluid: _propTypes2.default.bool, /** A dropdown menu can contain a header. */ header: _propTypes2.default.node, /** Shorthand for Icon. */ icon: _propTypes2.default.oneOfType([_propTypes2.default.node, _propTypes2.default.object]), /** A dropdown can be formatted to appear inline in other content. */ inline: _propTypes2.default.bool, /** A dropdown can be formatted as a Menu item. */ item: _propTypes2.default.bool, /** A dropdown can be labeled. */ labeled: _propTypes2.default.bool, /** A dropdown can show that it is currently loading data. */ loading: _propTypes2.default.bool, /** The minimum characters for a search to begin showing results. */ minCharacters: _propTypes2.default.number, /** A selection dropdown can allow multiple selections. */ multiple: _propTypes2.default.bool, /** Message to display when there are no results. */ noResultsMessage: _propTypes2.default.string, /** * Called when a user adds a new item. Use this to update the options list. * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All props and the new item's value. */ onAddItem: _propTypes2.default.func, /** * Called on blur. * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All props. */ onBlur: _propTypes2.default.func, /** * Called when the user attempts to change the value. * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All props and proposed value. */ onChange: _propTypes2.default.func, /** * Called on click. * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All props. */ onClick: _propTypes2.default.func, /** * Called when a close event happens. * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All props. */ onClose: _propTypes2.default.func, /** * Called on focus. * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All props. */ onFocus: _propTypes2.default.func, /** * Called when a multi-select label is clicked. * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All label props. */ onLabelClick: _propTypes2.default.func, /** * Called on mousedown. * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All props. */ onMouseDown: _propTypes2.default.func, /** * Called when an open event happens. * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All props. */ onOpen: _propTypes2.default.func, /** * Called on search input change. * * @param {SyntheticEvent} event - React's original SyntheticEvent. * @param {object} data - All props, includes current value of searchQuery. */ onSearchChange: _propTypes2.default.func, /** Controls whether or not the dropdown menu is displayed. */ open: _propTypes2.default.bool, /** Whether or not the menu should open when the dropdown is focused. */ openOnFocus: _propTypes2.default.bool, /** Array of Dropdown.Item props e.g. `{ text: '', value: '' }` */ options: _lib.customPropTypes.every([_lib.customPropTypes.disallow(['children']), _propTypes2.default.arrayOf(_propTypes2.default.shape(_DropdownItem2.default.propTypes))]), /** Placeholder text. */ placeholder: _propTypes2.default.string, /** A dropdown can be formatted so that its menu is pointing. */ pointing: _propTypes2.default.oneOfType([_propTypes2.default.bool, _propTypes2.default.oneOf(['left', 'right', 'top', 'top left', 'top right', 'bottom', 'bottom left', 'bottom right'])]), /** * Mapped over the active items and returns shorthand for the active item Labels. * Only applies to `multiple` Dropdowns. * * @param {object} item - A currently active dropdown item. * @param {number} index - The current index. * @param {object} defaultLabelProps - The default props for an active item Label. * @returns {*} Shorthand for a Label. */ renderLabel: _propTypes2.default.func, /** A dropdown can have its menu scroll. */ scrolling: _propTypes2.default.bool, /** * A selection dropdown can allow a user to search through a large list of choices. * Pass a function here to replace the default search. */ search: _propTypes2.default.oneOfType([_propTypes2.default.bool, _propTypes2.default.func]), /** A shorthand for a search input. */ searchInput: _propTypes2.default.oneOfType([_propTypes2.default.array, _propTypes2.default.node, _propTypes2.default.object]), /** Current value of searchQuery. Creates a controlled component. */ searchQuery: _propTypes2.default.string, // TODO 'searchInMenu' or 'search='in menu' or ??? How to handle this markup and functionality? /** Define whether the highlighted item should be selected on blur. */ selectOnBlur: _propTypes2.default.bool, /** * Whether or not to change the value when navigating the menu using arrow keys. * Setting to false will require enter or left click to confirm a choice. */ selectOnNavigation: _propTypes2.default.bool, /** Currently selected label in multi-select. */ selectedLabel: _lib.customPropTypes.every([_lib.customPropTypes.demand(['multiple']), _propTypes2.default.oneOfType([_propTypes2.default.string, _propTypes2.default.number])]), /** A dropdown can be used to select between choices in a form. */ selection: _lib.customPropTypes.every([_lib.customPropTypes.disallow(['children']), _lib.customPropTypes.demand(['options']), _propTypes2.default.bool]), /** A simple dropdown can open without Javascript. */ simple: _propTypes2.default.bool, /** A dropdown can receive focus. */ tabIndex: _propTypes2.default.oneOfTyp