react-select
Version:
A Select control built with and for ReactJS
1,536 lines (1,325 loc) • 56.1 kB
JavaScript
'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 ||