UNPKG

react-select

Version:

A Select control built with and for ReactJS

1,536 lines (1,325 loc) 56.1 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultProps = undefined; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); 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; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _memoizeOne = require('memoize-one'); var _memoizeOne2 = _interopRequireDefault(_memoizeOne); var _reactFastCompare = require('./internal/react-fast-compare'); var _reactFastCompare2 = _interopRequireDefault(_reactFastCompare); var _filters = require('./filters'); var _index = require('./internal/index'); var _index2 = require('./accessibility/index'); var _utils = require('./utils'); var _builtins = require('./builtins'); var _index3 = require('./components/index'); var _styles = require('./styles'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } 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; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var defaultProps = exports.defaultProps = { backspaceRemovesValue: true, blurInputOnSelect: (0, _utils.isTouchCapable)(), captureMenuScroll: !(0, _utils.isTouchCapable)(), closeMenuOnSelect: true, closeMenuOnScroll: false, components: {}, controlShouldRenderValue: true, escapeClearsValue: false, filterOption: (0, _filters.createFilter)(), formatGroupLabel: _builtins.formatGroupLabel, getOptionLabel: _builtins.getOptionLabel, getOptionValue: _builtins.getOptionValue, isDisabled: false, isLoading: false, isMulti: false, isRtl: false, isSearchable: true, isOptionDisabled: _builtins.isOptionDisabled, loadingMessage: function loadingMessage() { return 'Loading...'; }, maxMenuHeight: 300, minMenuHeight: 140, menuIsOpen: false, menuPlacement: 'bottom', menuPosition: 'absolute', menuShouldBlockScroll: false, menuShouldScrollIntoView: !(0, _utils.isMobileDevice)(), noOptionsMessage: function noOptionsMessage() { return 'No options'; }, openMenuOnFocus: false, openMenuOnClick: true, options: [], pageSize: 5, placeholder: 'Select...', screenReaderStatus: function screenReaderStatus(_ref) { var count = _ref.count; return count + ' result' + (count !== 1 ? 's' : '') + ' available'; }, styles: {}, tabIndex: '0', tabSelectsValue: true }; var instanceId = 1; var Select = function (_Component) { _inherits(Select, _Component); // Lifecycle // ------------------------------ // Refs // ------------------------------ // Misc. Instance Properties // ------------------------------ function Select(props) { _classCallCheck(this, Select); var _this = _possibleConstructorReturn(this, (Select.__proto__ || Object.getPrototypeOf(Select)).call(this, props)); _initialiseProps.call(_this); var value = props.value; _this.cacheComponents = (0, _memoizeOne2.default)(_this.cacheComponents, _reactFastCompare2.default).bind(_this); _this.cacheComponents(props.components); _this.instancePrefix = 'react-select-' + (_this.props.instanceId || ++instanceId); var selectValue = (0, _utils.cleanValue)(value); var menuOptions = _this.buildMenuOptions(props, selectValue); _this.state.menuOptions = menuOptions; _this.state.selectValue = selectValue; return _this; } // TODO _createClass(Select, [{ key: 'componentDidMount', value: function componentDidMount() { this.startListeningComposition(); this.startListeningToTouch(); if (this.props.closeMenuOnScroll && document && document.addEventListener) { // Listen to all scroll events, and filter them out inside of 'onScroll' document.addEventListener('scroll', this.onScroll, true); } if (this.props.autoFocus) { this.focusInput(); } } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps) { var _props = this.props, options = _props.options, value = _props.value, inputValue = _props.inputValue; // re-cache custom components this.cacheComponents(nextProps.components); // rebuild the menu options if (nextProps.value !== value || nextProps.options !== options || nextProps.inputValue !== inputValue) { var _selectValue = (0, _utils.cleanValue)(nextProps.value); var _menuOptions = this.buildMenuOptions(nextProps, _selectValue); var _focusedValue = this.getNextFocusedValue(_selectValue); var _focusedOption = this.getNextFocusedOption(_menuOptions.focusable); this.setState({ menuOptions: _menuOptions, selectValue: _selectValue, focusedOption: _focusedOption, focusedValue: _focusedValue }); } // some updates should toggle the state of the input visibility if (this.inputIsHiddenAfterUpdate != null) { this.setState({ inputIsHidden: this.inputIsHiddenAfterUpdate }); delete this.inputIsHiddenAfterUpdate; } } }, { key: 'componentDidUpdate', value: function componentDidUpdate(prevProps) { var _props2 = this.props, isDisabled = _props2.isDisabled, menuIsOpen = _props2.menuIsOpen; var isFocused = this.state.isFocused; if ( // ensure focus is restored correctly when the control becomes enabled isFocused && !isDisabled && prevProps.isDisabled || // ensure focus is on the Input when the menu opens isFocused && menuIsOpen && !prevProps.menuIsOpen) { this.focusInput(); } // scroll the focused option into view if necessary if (this.menuListRef && this.focusedOptionRef && this.scrollToFocusedOptionOnUpdate) { (0, _utils.scrollIntoView)(this.menuListRef, this.focusedOptionRef); } this.scrollToFocusedOptionOnUpdate = false; } }, { key: 'componentWillUnmount', value: function componentWillUnmount() { this.stopListeningComposition(); this.stopListeningToTouch(); document.removeEventListener('scroll', this.onScroll, true); } }, { key: 'onMenuOpen', // ============================== // Consumer Handlers // ============================== value: function onMenuOpen() { this.props.onMenuOpen(); } }, { key: 'onMenuClose', value: function onMenuClose() { var _props3 = this.props, isSearchable = _props3.isSearchable, isMulti = _props3.isMulti; this.announceAriaLiveContext({ event: 'input', context: { isSearchable: isSearchable, isMulti: isMulti } }); this.onInputChange('', { action: 'menu-close' }); this.props.onMenuClose(); } }, { key: 'onInputChange', value: function onInputChange(newValue, actionMeta) { this.props.onInputChange(newValue, actionMeta); } // ============================== // Methods // ============================== }, { key: 'focusInput', value: function focusInput() { if (!this.inputRef) return; this.inputRef.focus(); } }, { key: 'blurInput', value: function blurInput() { if (!this.inputRef) return; this.inputRef.blur(); } // aliased for consumers }, { key: 'openMenu', value: function openMenu(focusOption) { var _state = this.state, menuOptions = _state.menuOptions, selectValue = _state.selectValue; var isMulti = this.props.isMulti; var openAtIndex = focusOption === 'first' ? 0 : menuOptions.focusable.length - 1; if (!isMulti) { var selectedIndex = menuOptions.focusable.indexOf(selectValue[0]); if (selectedIndex > -1) { openAtIndex = selectedIndex; } } this.scrollToFocusedOptionOnUpdate = true; this.inputIsHiddenAfterUpdate = false; this.onMenuOpen(); this.setState({ focusedValue: null, focusedOption: menuOptions.focusable[openAtIndex] }); this.announceAriaLiveContext({ event: 'menu' }); } }, { key: 'focusValue', value: function focusValue(direction) { var _props4 = this.props, isMulti = _props4.isMulti, isSearchable = _props4.isSearchable; var _state2 = this.state, selectValue = _state2.selectValue, focusedValue = _state2.focusedValue; // Only multiselects support value focusing if (!isMulti) return; this.setState({ focusedOption: null }); var focusedIndex = selectValue.indexOf(focusedValue); if (!focusedValue) { focusedIndex = -1; this.announceAriaLiveContext({ event: 'value' }); } var lastIndex = selectValue.length - 1; var nextFocus = -1; if (!selectValue.length) return; switch (direction) { case 'previous': if (focusedIndex === 0) { // don't cycle from the start to the end nextFocus = 0; } else if (focusedIndex === -1) { // if nothing is focused, focus the last value first nextFocus = lastIndex; } else { nextFocus = focusedIndex - 1; } break; case 'next': if (focusedIndex > -1 && focusedIndex < lastIndex) { nextFocus = focusedIndex + 1; } break; } if (nextFocus === -1) { this.announceAriaLiveContext({ event: 'input', context: { isSearchable: isSearchable, isMulti: isMulti } }); } this.setState({ inputIsHidden: nextFocus === -1 ? false : true, focusedValue: selectValue[nextFocus] }); } }, { key: 'focusOption', value: function focusOption() { var direction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'first'; var pageSize = this.props.pageSize; var _state3 = this.state, focusedOption = _state3.focusedOption, menuOptions = _state3.menuOptions; var options = menuOptions.focusable; if (!options.length) return; var nextFocus = 0; // handles 'first' var focusedIndex = options.indexOf(focusedOption); if (!focusedOption) { focusedIndex = -1; this.announceAriaLiveContext({ event: 'menu' }); } if (direction === 'up') { nextFocus = focusedIndex > 0 ? focusedIndex - 1 : options.length - 1; } else if (direction === 'down') { nextFocus = (focusedIndex + 1) % options.length; } else if (direction === 'pageup') { nextFocus = focusedIndex - pageSize; if (nextFocus < 0) nextFocus = 0; } else if (direction === 'pagedown') { nextFocus = focusedIndex + pageSize; if (nextFocus > options.length - 1) nextFocus = options.length - 1; } else if (direction === 'last') { nextFocus = options.length - 1; } this.scrollToFocusedOptionOnUpdate = true; this.setState({ focusedOption: options[nextFocus], focusedValue: null }); } }, { key: 'getCommonProps', // ============================== // Getters // ============================== value: function getCommonProps() { var clearValue = this.clearValue, getStyles = this.getStyles, setValue = this.setValue, selectOption = this.selectOption, props = this.props; var classNamePrefix = props.classNamePrefix, isMulti = props.isMulti, isRtl = props.isRtl, options = props.options; var selectValue = this.state.selectValue; var hasValue = this.hasValue(); var getValue = function getValue() { return selectValue; }; var cxPrefix = classNamePrefix; var cx = _utils.classNames.bind(null, cxPrefix); return { cx: cx, clearValue: clearValue, getStyles: getStyles, getValue: getValue, hasValue: hasValue, isMulti: isMulti, isRtl: isRtl, options: options, selectOption: selectOption, setValue: setValue, selectProps: props }; } }, { key: 'getNextFocusedValue', value: function getNextFocusedValue(nextSelectValue) { if (this.clearFocusValueOnUpdate) { this.clearFocusValueOnUpdate = false; return null; } var _state4 = this.state, focusedValue = _state4.focusedValue, lastSelectValue = _state4.selectValue; var lastFocusedIndex = lastSelectValue.indexOf(focusedValue); if (lastFocusedIndex > -1) { var nextFocusedIndex = nextSelectValue.indexOf(focusedValue); if (nextFocusedIndex > -1) { // the focused value is still in the selectValue, return it return focusedValue; } else if (lastFocusedIndex < nextSelectValue.length) { // the focusedValue is not present in the next selectValue array by // reference, so return the new value at the same index return nextSelectValue[lastFocusedIndex]; } } return null; } }, { key: 'getNextFocusedOption', value: function getNextFocusedOption(options) { var lastFocusedOption = this.state.focusedOption; return lastFocusedOption && options.indexOf(lastFocusedOption) > -1 ? lastFocusedOption : options[0]; } // ============================== // Helpers // ============================== }, { key: 'hasValue', value: function hasValue() { var selectValue = this.state.selectValue; return selectValue.length > 0; } }, { key: 'hasOptions', value: function hasOptions() { return !!this.state.menuOptions.render.length; } }, { key: 'countOptions', value: function countOptions() { return this.state.menuOptions.focusable.length; } }, { key: 'isClearable', value: function isClearable() { var _props5 = this.props, isClearable = _props5.isClearable, isMulti = _props5.isMulti; // single select, by default, IS NOT clearable // multi select, by default, IS clearable if (isClearable === undefined) return isMulti; return isClearable; } }, { key: 'isOptionDisabled', value: function isOptionDisabled(option, selectValue) { return typeof this.props.isOptionDisabled === 'function' ? this.props.isOptionDisabled(option, selectValue) : false; } }, { key: 'isOptionSelected', value: function isOptionSelected(option, selectValue) { var _this2 = this; if (selectValue.indexOf(option) > -1) return true; if (typeof this.props.isOptionSelected === 'function') { return this.props.isOptionSelected(option, selectValue); } var candidate = this.getOptionValue(option); return selectValue.some(function (i) { return _this2.getOptionValue(i) === candidate; }); } }, { key: 'filterOption', value: function filterOption(option, inputValue) { return this.props.filterOption ? this.props.filterOption(option, inputValue) : true; } }, { key: 'formatOptionLabel', value: function formatOptionLabel(data, context) { if (typeof this.props.formatOptionLabel === 'function') { var _inputValue = this.props.inputValue; var _selectValue2 = this.state.selectValue; return this.props.formatOptionLabel(data, { context: context, inputValue: _inputValue, selectValue: _selectValue2 }); } else { return this.getOptionLabel(data); } } }, { key: 'formatGroupLabel', value: function formatGroupLabel(data) { return this.props.formatGroupLabel(data); } // ============================== // Mouse Handlers // ============================== }, { key: 'startListeningComposition', // ============================== // Composition Handlers // ============================== value: function startListeningComposition() { if (document && document.addEventListener) { document.addEventListener('compositionstart', this.onCompositionStart, false); document.addEventListener('compositionend', this.onCompositionEnd, false); } } }, { key: 'stopListeningComposition', value: function stopListeningComposition() { if (document && document.removeEventListener) { document.removeEventListener('compositionstart', this.onCompositionStart); document.removeEventListener('compositionend', this.onCompositionEnd); } } }, { key: 'startListeningToTouch', // ============================== // Touch Handlers // ============================== value: function startListeningToTouch() { if (document && document.addEventListener) { document.addEventListener('touchstart', this.onTouchStart, false); document.addEventListener('touchmove', this.onTouchMove, false); document.addEventListener('touchend', this.onTouchEnd, false); } } }, { key: 'stopListeningToTouch', value: function stopListeningToTouch() { if (document && document.removeEventListener) { document.removeEventListener('touchstart', this.onTouchStart); document.removeEventListener('touchmove', this.onTouchMove); document.removeEventListener('touchend', this.onTouchEnd); } } // ============================== // Focus Handlers // ============================== // ============================== // Keyboard Handlers // ============================== }, { key: 'buildMenuOptions', // ============================== // Menu Options // ============================== value: function buildMenuOptions(props, selectValue) { var _this3 = this; var _props$inputValue = props.inputValue, inputValue = _props$inputValue === undefined ? '' : _props$inputValue, options = props.options; var toOption = function toOption(option, id) { var isDisabled = _this3.isOptionDisabled(option, selectValue); var isSelected = _this3.isOptionSelected(option, selectValue); var label = _this3.getOptionLabel(option); var value = _this3.getOptionValue(option); if (_this3.shouldHideSelectedOptions() && isSelected || !_this3.filterOption({ label: label, value: value, data: option }, inputValue)) { return; } var onHover = isDisabled ? undefined : function () { return _this3.onOptionHover(option); }; var onSelect = isDisabled ? undefined : function () { return _this3.selectOption(option); }; var optionId = _this3.getElementId('option') + '-' + id; return { innerProps: { id: optionId, onClick: onSelect, onMouseMove: onHover, onMouseOver: onHover, role: 'option', tabIndex: -1 }, data: option, isDisabled: isDisabled, isSelected: isSelected, key: optionId, label: label, type: 'option', value: value }; }; return options.reduce(function (acc, item, itemIndex) { if (item.options) { // TODO needs a tidier implementation if (!_this3.hasGroups) _this3.hasGroups = true; var items = item.options; var children = items.map(function (child, i) { var option = toOption(child, itemIndex + '-' + i); if (option && !option.isDisabled) acc.focusable.push(child); return option; }).filter(Boolean); if (children.length) { var groupId = _this3.getElementId('group') + '-' + itemIndex; acc.render.push({ type: 'group', key: groupId, data: item, options: children }); } } else { var option = toOption(item, '' + itemIndex); if (option) { acc.render.push(option); if (!option.isDisabled) acc.focusable.push(item); } } return acc; }, { render: [], focusable: [] }); } // ============================== // Renderers // ============================== }, { key: 'constructAriaLiveMessage', value: function constructAriaLiveMessage() { var _state5 = this.state, ariaLiveContext = _state5.ariaLiveContext, selectValue = _state5.selectValue, focusedValue = _state5.focusedValue, focusedOption = _state5.focusedOption; var _props6 = this.props, options = _props6.options, menuIsOpen = _props6.menuIsOpen, inputValue = _props6.inputValue, screenReaderStatus = _props6.screenReaderStatus; // An aria live message representing the currently focused value in the select. var focusedValueMsg = focusedValue ? (0, _index2.valueFocusAriaMessage)({ focusedValue: focusedValue, getOptionLabel: this.getOptionLabel, selectValue: selectValue }) : ''; // An aria live message representing the currently focused option in the select. var focusedOptionMsg = focusedOption && menuIsOpen ? (0, _index2.optionFocusAriaMessage)({ focusedOption: focusedOption, getOptionLabel: this.getOptionLabel, options: options }) : ''; // An aria live message representing the set of focusable results and current searchterm/inputvalue. var resultsMsg = (0, _index2.resultsAriaMessage)({ inputValue: inputValue, screenReaderMessage: screenReaderStatus({ count: this.countOptions() }) }); return focusedValueMsg + ' ' + focusedOptionMsg + ' ' + resultsMsg + ' ' + ariaLiveContext; } }, { key: 'renderInput', value: function renderInput() { var _props7 = this.props, isDisabled = _props7.isDisabled, isSearchable = _props7.isSearchable, inputId = _props7.inputId, inputValue = _props7.inputValue, tabIndex = _props7.tabIndex; var Input = this.components.Input; var inputIsHidden = this.state.inputIsHidden; var id = inputId || this.getElementId('input'); if (!isSearchable) { // use a dummy input to maintain focus/blur functionality return _react2.default.createElement(_index.DummyInput, { id: id, innerRef: this.getInputRef, onBlur: this.onInputBlur, onChange: _utils.noop, onFocus: this.onInputFocus, readOnly: true, tabIndex: tabIndex, value: '' }); } // aria attributes makes the JSX "noisy", separated for clarity var ariaAttributes = { 'aria-autocomplete': 'list', 'aria-label': this.props['aria-label'], 'aria-labelledby': this.props['aria-labelledby'] }; var cx = this.commonProps.cx; return _react2.default.createElement(Input, _extends({ autoCapitalize: 'none', autoComplete: 'off', autoCorrect: 'off', cx: cx, getStyles: this.getStyles, id: id, innerRef: this.getInputRef, isDisabled: isDisabled, isHidden: inputIsHidden, onBlur: this.onInputBlur, onChange: this.handleInputChange, onFocus: this.onInputFocus, spellCheck: 'false', tabIndex: tabIndex, type: 'text', value: inputValue }, ariaAttributes)); } }, { key: 'renderPlaceholderOrValue', value: function renderPlaceholderOrValue() { var _this4 = this; var _components = this.components, MultiValue = _components.MultiValue, MultiValueContainer = _components.MultiValueContainer, MultiValueLabel = _components.MultiValueLabel, MultiValueRemove = _components.MultiValueRemove, SingleValue = _components.SingleValue, Placeholder = _components.Placeholder; var commonProps = this.commonProps; var _props8 = this.props, controlShouldRenderValue = _props8.controlShouldRenderValue, isDisabled = _props8.isDisabled, isMulti = _props8.isMulti, inputValue = _props8.inputValue, placeholder = _props8.placeholder; var _state6 = this.state, selectValue = _state6.selectValue, focusedValue = _state6.focusedValue; if (!this.hasValue() || !controlShouldRenderValue) { return inputValue ? null : _react2.default.createElement( Placeholder, _extends({}, commonProps, { key: 'placeholder', isDisabled: isDisabled }), placeholder ); } if (isMulti) { var selectValues = selectValue.map(function (opt) { var isFocused = opt === focusedValue; return _react2.default.createElement( MultiValue, _extends({}, commonProps, { components: { Container: MultiValueContainer, Label: MultiValueLabel, Remove: MultiValueRemove }, isFocused: isFocused, isDisabled: isDisabled, key: _this4.getOptionValue(opt), removeProps: { onClick: function onClick() { return _this4.removeValue(opt); }, onTouchEnd: function onTouchEnd() { return _this4.removeValue(opt); }, onMouseDown: function onMouseDown(e) { e.preventDefault(); e.stopPropagation(); } }, data: opt }), _this4.formatOptionLabel(opt, 'value') ); }); return selectValues; } if (inputValue) { return null; } var singleValue = selectValue[0]; return _react2.default.createElement( SingleValue, _extends({}, commonProps, { data: singleValue, isDisabled: isDisabled }), this.formatOptionLabel(singleValue, 'value') ); } }, { key: 'renderClearIndicator', value: function renderClearIndicator() { var ClearIndicator = this.components.ClearIndicator; var commonProps = this.commonProps; var _props9 = this.props, isDisabled = _props9.isDisabled, isLoading = _props9.isLoading; var isFocused = this.state.isFocused; if (!this.isClearable() || !ClearIndicator || isDisabled || !this.hasValue() || isLoading) { return null; } var innerProps = { onMouseDown: this.onClearIndicatorMouseDown, onTouchEnd: this.onClearIndicatorTouchEnd, 'aria-hidden': 'true' }; return _react2.default.createElement(ClearIndicator, _extends({}, commonProps, { innerProps: innerProps, isFocused: isFocused })); } }, { key: 'renderLoadingIndicator', value: function renderLoadingIndicator() { var LoadingIndicator = this.components.LoadingIndicator; var commonProps = this.commonProps; var _props10 = this.props, isDisabled = _props10.isDisabled, isLoading = _props10.isLoading; var isFocused = this.state.isFocused; if (!LoadingIndicator || !isLoading) return null; var innerProps = { 'aria-hidden': 'true' }; return _react2.default.createElement(LoadingIndicator, _extends({}, commonProps, { innerProps: innerProps, isDisabled: isDisabled, isFocused: isFocused })); } }, { key: 'renderIndicatorSeparator', value: function renderIndicatorSeparator() { var _components2 = this.components, DropdownIndicator = _components2.DropdownIndicator, IndicatorSeparator = _components2.IndicatorSeparator; // separator doesn't make sense without the dropdown indicator if (!DropdownIndicator || !IndicatorSeparator) return null; var commonProps = this.commonProps; var isDisabled = this.props.isDisabled; var isFocused = this.state.isFocused; return _react2.default.createElement(IndicatorSeparator, _extends({}, commonProps, { isDisabled: isDisabled, isFocused: isFocused })); } }, { key: 'renderDropdownIndicator', value: function renderDropdownIndicator() { var DropdownIndicator = this.components.DropdownIndicator; if (!DropdownIndicator) return null; var commonProps = this.commonProps; var isDisabled = this.props.isDisabled; var isFocused = this.state.isFocused; var innerProps = { onMouseDown: this.onDropdownIndicatorMouseDown, onTouchEnd: this.onDropdownIndicatorTouchEnd, 'aria-hidden': 'true' }; return _react2.default.createElement(DropdownIndicator, _extends({}, commonProps, { innerProps: innerProps, isDisabled: isDisabled, isFocused: isFocused })); } }, { key: 'renderMenu', value: function renderMenu() { var _this5 = this; var _components3 = this.components, Group = _components3.Group, GroupHeading = _components3.GroupHeading, Menu = _components3.Menu, MenuList = _components3.MenuList, MenuPortal = _components3.MenuPortal, LoadingMessage = _components3.LoadingMessage, NoOptionsMessage = _components3.NoOptionsMessage, Option = _components3.Option; var commonProps = this.commonProps; var _state7 = this.state, focusedOption = _state7.focusedOption, menuOptions = _state7.menuOptions; var _props11 = this.props, captureMenuScroll = _props11.captureMenuScroll, inputValue = _props11.inputValue, isLoading = _props11.isLoading, loadingMessage = _props11.loadingMessage, minMenuHeight = _props11.minMenuHeight, maxMenuHeight = _props11.maxMenuHeight, menuIsOpen = _props11.menuIsOpen, menuPlacement = _props11.menuPlacement, menuPosition = _props11.menuPosition, menuPortalTarget = _props11.menuPortalTarget, menuShouldBlockScroll = _props11.menuShouldBlockScroll, menuShouldScrollIntoView = _props11.menuShouldScrollIntoView, noOptionsMessage = _props11.noOptionsMessage, onMenuScrollToTop = _props11.onMenuScrollToTop, onMenuScrollToBottom = _props11.onMenuScrollToBottom; if (!menuIsOpen) return null; // TODO: Internal Option Type here var render = function render(props) { // for performance, the menu options in state aren't changed when the // focused option changes so we calculate additional props based on that var isFocused = focusedOption === props.data; props.innerRef = isFocused ? _this5.getFocusedOptionRef : undefined; return _react2.default.createElement( Option, _extends({}, commonProps, props, { isFocused: isFocused }), _this5.formatOptionLabel(props.data, 'menu') ); }; var menuUI = void 0; if (this.hasOptions()) { menuUI = menuOptions.render.map(function (item) { if (item.type === 'group') { var type = item.type, group = _objectWithoutProperties(item, ['type']); var headingId = item.key + '-heading'; return _react2.default.createElement( Group, _extends({}, commonProps, group, { Heading: GroupHeading, headingProps: { id: headingId }, label: _this5.formatGroupLabel(item.data) }), item.options.map(function (option) { return render(option); }) ); } else if (item.type === 'option') { return render(item); } }); } else if (isLoading) { var message = loadingMessage({ inputValue: inputValue }); if (message === null) return null; menuUI = _react2.default.createElement( LoadingMessage, commonProps, message ); } else { var _message = noOptionsMessage({ inputValue: inputValue }); if (_message === null) return null; menuUI = _react2.default.createElement( NoOptionsMessage, commonProps, _message ); } var menuElement = _react2.default.createElement( 'div', null, _react2.default.createElement( Menu, _extends({}, commonProps, { innerProps: { onMouseDown: this.onMenuMouseDown, onMouseMove: this.onMenuMouseMove }, isLoading: isLoading, minMenuHeight: minMenuHeight, maxMenuHeight: maxMenuHeight, menuPlacement: menuPlacement, menuPosition: menuPosition, menuShouldScrollIntoView: menuShouldScrollIntoView }), _react2.default.createElement( _index.ScrollCaptor, { isEnabled: captureMenuScroll, onTopArrive: onMenuScrollToTop, onBottomArrive: onMenuScrollToBottom }, _react2.default.createElement( _index.ScrollBlock, { isEnabled: menuShouldBlockScroll }, _react2.default.createElement( MenuList, _extends({}, commonProps, { innerRef: this.getMenuListRef, isLoading: isLoading, maxHeight: maxMenuHeight }), menuUI ) ) ) ) ); // positioning behaviour is almost identical for portalled and fixed, // so we use the same component. the actual portalling logic is forked // within the component based on `menuPosition` return menuPortalTarget || menuPosition === 'fixed' ? _react2.default.createElement( MenuPortal, _extends({}, commonProps, { appendTo: menuPortalTarget, controlElement: this.controlRef, menuPlacement: menuPlacement, menuPosition: menuPosition }), menuElement ) : menuElement; } }, { key: 'renderFormField', value: function renderFormField() { var _this6 = this; var _props12 = this.props, delimiter = _props12.delimiter, isDisabled = _props12.isDisabled, isMulti = _props12.isMulti, name = _props12.name; var selectValue = this.state.selectValue; if (!name || isDisabled) return; if (isMulti) { if (delimiter) { var _value = selectValue.map(function (opt) { return _this6.getOptionValue(opt); }).join(delimiter); return _react2.default.createElement('input', { name: name, type: 'hidden', value: _value }); } else { return _react2.default.createElement( 'div', null, selectValue.map(function (opt, i) { return _react2.default.createElement('input', { key: 'i-' + i, name: name, type: 'hidden', value: _this6.getOptionValue(opt) }); }) ); } } else { var _value2 = selectValue[0] ? this.getOptionValue(selectValue[0]) : ''; return _react2.default.createElement('input', { name: name, type: 'hidden', value: _value2 }); } } }, { key: 'renderLiveRegion', value: function renderLiveRegion() { if (!this.state.isFocused) return null; return _react2.default.createElement( _index.A11yText, { 'aria-live': 'assertive' }, _react2.default.createElement( 'p', { id: 'aria-selection-event' }, '\xA0', this.state.ariaLiveSelection ), _react2.default.createElement( 'p', { id: 'aria-context' }, '\xA0', this.constructAriaLiveMessage() ) ); } }, { key: 'render', value: function render() { var _components4 = this.components, Control = _components4.Control, IndicatorsContainer = _components4.IndicatorsContainer, SelectContainer = _components4.SelectContainer, ValueContainer = _components4.ValueContainer; var _props13 = this.props, className = _props13.className, id = _props13.id, isDisabled = _props13.isDisabled; var isFocused = this.state.isFocused; var commonProps = this.commonProps = this.getCommonProps(); return _react2.default.createElement( SelectContainer, _extends({}, commonProps, { className: className, innerProps: { id: id, onKeyDown: this.onKeyDown }, isDisabled: isDisabled, isFocused: isFocused }), this.renderLiveRegion(), _react2.default.createElement( Control, _extends({}, commonProps, { innerRef: this.getControlRef, innerProps: { onMouseDown: this.onControlMouseDown, onTouchEnd: this.onControlTouchEnd }, isDisabled: isDisabled, isFocused: isFocused }), _react2.default.createElement( ValueContainer, _extends({}, commonProps, { isDisabled: isDisabled }), this.renderPlaceholderOrValue(), this.renderInput() ), _react2.default.createElement( IndicatorsContainer, _extends({}, commonProps, { isDisabled: isDisabled }), this.renderClearIndicator(), this.renderLoadingIndicator(), this.renderIndicatorSeparator(), this.renderDropdownIndicator() ) ), this.renderMenu(), this.renderFormField() ); } }]); return Select; }(_react.Component); Select.defaultProps = defaultProps; var _initialiseProps = function _initialiseProps() { var _this7 = this; this.state = { ariaLiveSelection: '', ariaLiveContext: '', focusedOption: null, focusedValue: null, inputIsHidden: false, isFocused: false, isComposing: false, menuOptions: { render: [], focusable: [] }, selectValue: [] }; this.blockOptionHover = false; this.clearFocusValueOnUpdate = false; this.hasGroups = false; this.initialTouchX = 0; this.initialTouchY = 0; this.instancePrefix = ''; this.openAfterFocus = false; this.scrollToFocusedOptionOnUpdate = false; this.controlRef = null; this.getControlRef = function (ref) { _this7.controlRef = ref; }; this.focusedOptionRef = null; this.getFocusedOptionRef = function (ref) { _this7.focusedOptionRef = ref; }; this.menuListRef = null; this.getMenuListRef = function (ref) { _this7.menuListRef = ref; }; this.inputRef = null; this.getInputRef = function (ref) { _this7.inputRef = ref; }; this.cacheComponents = function (components) { _this7.components = (0, _index3.defaultComponents)({ components: components }); }; this.focus = this.focusInput; this.blur = this.blurInput; this.setValue = function (newValue) { var action = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'set-value'; var option = arguments[2]; var _props14 = _this7.props, closeMenuOnSelect = _props14.closeMenuOnSelect, isMulti = _props14.isMulti, onChange = _props14.onChange; _this7.onInputChange('', { action: 'set-value' }); if (closeMenuOnSelect) { _this7.inputIsHiddenAfterUpdate = !isMulti; _this7.onMenuClose(); } // when the select value should change, we should reset focusedValue _this7.clearFocusValueOnUpdate = true; onChange(newValue, { action: action, option: option }); }; this.selectOption = function (newValue) { var _props15 = _this7.props, blurInputOnSelect = _props15.blurInputOnSelect, isMulti = _props15.isMulti; if (isMulti) { var _selectValue3 = _this7.state.selectValue; if (_this7.isOptionSelected(newValue, _selectValue3)) { var candidate = _this7.getOptionValue(newValue); _this7.setValue(_selectValue3.filter(function (i) { return _this7.getOptionValue(i) !== candidate; }), 'deselect-option', newValue); _this7.announceAriaLiveSelection({ event: 'deselect-option', context: { value: _this7.getOptionLabel(newValue) } }); } else { _this7.setValue([].concat(_toConsumableArray(_selectValue3), [newValue]), 'select-option', newValue); _this7.announceAriaLiveSelection({ event: 'select-option', context: { value: _this7.getOptionLabel(newValue) } }); } } else { _this7.setValue(newValue, 'select-option'); _this7.announceAriaLiveSelection({ event: 'select-option', context: { value: _this7.getOptionLabel(newValue) } }); } if (blurInputOnSelect) { _this7.blurInput(); } }; this.removeValue = function (removedValue) { var onChange = _this7.props.onChange; var selectValue = _this7.state.selectValue; var candidate = _this7.getOptionValue(removedValue); onChange(selectValue.filter(function (i) { return _this7.getOptionValue(i) !== candidate; }), { action: 'remove-value', removedValue: removedValue }); _this7.announceAriaLiveSelection({ event: 'remove-value', context: { value: removedValue ? _this7.getOptionLabel(removedValue) : undefined } }); _this7.focusInput(); }; this.clearValue = function () { var _props16 = _this7.props, isMulti = _props16.isMulti, onChange = _props16.onChange; onChange(isMulti ? [] : null, { action: 'clear' }); }; this.popValue = function () { var onChange = _this7.props.onChange; var selectValue = _this7.state.selectValue; var lastSelectedValue = selectValue[selectValue.length - 1]; _this7.announceAriaLiveSelection({ event: 'pop-value', context: { value: lastSelectedValue ? _this7.getOptionLabel(lastSelectedValue) : undefined } }); onChange(selectValue.slice(0, selectValue.length - 1), { action: 'pop-value', removedValue: lastSelectedValue }); }; this.getOptionLabel = function (data) { return _this7.props.getOptionLabel(data); }; this.getOptionValue = function (data) { return _this7.props.getOptionValue(data); }; this.getStyles = function (key, props) { var base = _styles.defaultStyles[key](props); base.boxSizing = 'border-box'; var custom = _this7.props.styles[key]; return custom ? custom(base, props) : base; }; this.getElementId = function (element) { return _this7.instancePrefix + '-' + element; }; this.getActiveDescendentId = function () { var menuIsOpen = _this7.props.menuIsOpen; var _state8 = _this7.state, menuOptions = _state8.menuOptions, focusedOption = _state8.focusedOption; if (!focusedOption || !menuIsOpen) return undefined; var index = menuOptions.focusable.indexOf(focusedOption); var option = menuOptions.render[index]; return option && option.key; }; this.announceAriaLiveSelection = function (_ref2) { var event = _ref2.event, context = _ref2.context; _this7.setState({ ariaLiveSelection: (0, _index2.valueEventAriaMessage)(event, context) }); }; this.announceAriaLiveContext = function (_ref3) { var event = _ref3.event, context = _ref3.context; _this7.setState({ ariaLiveContext: (0, _index2.instructionsAriaMessage)(event, _extends({}, context, { label: _this7.props['aria-label'] })) }); }; this.onMenuMouseDown = function (event) { if (event.button !== 0) { return; } event.stopPropagation(); event.preventDefault(); _this7.focusInput(); }; this.onMenuMouseMove = function (event) { _this7.blockOptionHover = false; }; this.onControlMouseDown = function (event) { var openMenuOnClick = _this7.props.openMenuOnClick; if (!_this7.state.isFocused) { if (openMenuOnClick) { _this7.openAfterFocus = true; } _this7.focusInput(); } else if (!_this7.props.menuIsOpen) { _this7.openMenu('first'); } else { _this7.onMenuClose(); } // $FlowFixMe HTMLElement type does not have tagName property if (event.target.tagName !== 'INPUT') { event.preventDefault(); } }; this.onDropdownIndicatorMouseDown = function (event) { // ignore mouse events that weren't triggered by the primary button if (event && event.type === 'mousedown' && event.button !== 0) { return; } if (_this7.props.isDisabled) return; var _props17 = _this7.props, isMulti = _props17.isMulti, menuIsOpen = _props17.menuIsOpen; _this7.focusInput(); if (menuIsOpen) { _this7.inputIsHiddenAfterUpdate = !isMulti; _this7.onMenuClose(); } else { _this7.openMenu('first'); } event.preventDefault(); event.stopPropagation(); }; this.onClearIndicatorMouseDown = function (event) { // ignore mouse events that weren't triggered by the primary button if (event && event.type === 'mousedown' && event.button !== 0) { return; } _this7.clearValue(); event.stopPropagation(); _this7.openAfterFocus = false; setTimeout(function () { return _this7.focusInput(); }); }; this.onScroll = function (event) { if (typeof _this7.props.closeMenuOnScroll === 'boolean') { if (event.target instanceof HTMLElement && (0, _utils.isDocumentElement)(event.target)) { _this7.props.onMenuClose(); } } else if (typeof _this7.props.closeMenuOnScroll === 'function') { if (_this7.props.closeMenuOnScroll(event)) { _this7.props.onMenuClose(); } } }; this.onCompositionStart = function () { _this7.setState({ isComposing: true }); }; this.onCompositionEnd = function () { _this7.setState({ isComposing: false }); }; this.onTouchStart = function (_ref4) { var _ref4$touches = _slicedToArray(_ref4.touches, 1), touch = _ref4$touches[0]; _this7.initialTouchX = touch.clientX; _this7.initialTouchY = touch.clientY; _this7.userIsDragging = false; }; this.onTouchMove = function (_ref5) { var _ref5$touches = _slicedToArray(_ref5.touches, 1), touch = _ref5$touches[0]; var deltaX = Math.abs(touch.clientX - _this7.initialTouchX); var deltaY = Math.abs(touch.clientY - _this7.initialTouchY); var moveThreshold = 5; _this7.userIsDragging = deltaX > moveThreshold ||