diff --git a/HISTORY.md b/HISTORY.md
index 67c687e5..6681fb7c 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,10 @@
# React-Select-Plus
+## Next
+
+* fixed; issues synchronising options props in `Async`, thanks [cbergmiller](https://github.com/cbergmiller)
+* fixed; backspace handling for non-multi select controls, thanks [Jeremy Liberman](https://github.com/MrLeebo)
+
## v1.0.0-rc.5 / 2017-05-25
* fixed; Allow `falsey` values to be clearable, thanks [Simon Gaestel](https://github.com/sgaestel)
diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md
index b7451a6b..fb96d21b 100644
--- a/ISSUE_TEMPLATE.md
+++ b/ISSUE_TEMPLATE.md
@@ -1,9 +1,24 @@
### Thanks for using react-select!
-If you are reporting an error please include a test case that demonstrates the issue you're reporting!
+Before creating an issue...
+
+# Are you asking a question?
+Please don't file GitHub issues to ask questions. Use Stack Overflow with a [#react-select](http://stackoverflow.com/questions/tagged/react-select) tag
+
+
+# Are you reporting a bug or runtime error?
+Please include a test case that demonstrates the issue you're reporting!
This is very helpful to maintainers in order to help us see the issue you're seeing.
Here is a Plunker you can fork that has react-select loaded and supports JSX syntax:
-https://plnkr.co/edit/HTmtER9AMNcPoWhXV707?p=preview
+https://plnkr.co/edit/dHygFMWWqVwaRAfpEmbn?p=preview
You may also find the [online Babel tool](https://babeljs.io/repl/) quite helpful if you wish to use ES6/ES7 syntax not yet supported by the browser you are using.
+
+
+# Are you making a feature request?
+Provide as much information as possible about your requested feature. Here are a few questions you may consider answering:
+
+* What's your use case? (Tell us about your application and what problem you're trying to solve.)
+* What interface do you have in mind? (What new properties or methods do you think might be helpful?)
+* Can you point to similar functionality with any existing libraries or components? (Working demos can be helpful.)
diff --git a/README.md b/README.md
index f31cb305..d63fd58e 100644
--- a/README.md
+++ b/README.md
@@ -74,7 +74,7 @@ var options = [
];
function logChange(val) {
- console.log("Selected: " + val);
+ console.log("Selected: " + JSON.stringify(val));
}
| function which returns a custom way to render/manage the value selected `` |
| valueKey | string | 'value' | the option property to use for the value |
| valueRenderer | func | undefined | function which returns a custom way to render the value selected `function (option) {}` |
diff --git a/gulpfile.js b/gulpfile.js
index 2fc2bed5..c3a8eb65 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -4,7 +4,7 @@ var initGulpTasks = require('react-component-gulp-tasks');
var taskConfig = {
component: {
- name: 'Select',
+ name: 'index',
dependencies: [
'classnames',
'react-input-autosize',
diff --git a/package.json b/package.json
index a575a7ef..3fc81536 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,6 @@
"url": "https://github.com/HubSpot/react-select-plus.git"
},
"dependencies": {
- "create-react-class": "^15.5.2",
"classnames": "^2.2.4",
"react-input-autosize": "^1.1.3",
"prop-types": "^15.5.8"
@@ -21,6 +20,7 @@
"babel-eslint": "^4.1.3",
"chai": "^3.5.0",
"coveralls": "^2.11.12",
+ "create-react-class": "^15.5.2",
"eslint": "^1.10.3",
"eslint-plugin-react": "^3.15.0",
"gulp": "^3.9.1",
diff --git a/src/Async.js b/src/Async.js
index de2c8ad9..0356cf12 100644
--- a/src/Async.js
+++ b/src/Async.js
@@ -68,15 +68,12 @@ export default class Async extends Component {
}
}
- componentWillUpdate (nextProps, nextState) {
- const propertiesToSync = ['options'];
- propertiesToSync.forEach((prop) => {
- if (this.props[prop] !== nextProps[prop]) {
- this.setState({
- [prop]: nextProps[prop]
- });
- }
- });
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.options !== this.props.options) {
+ this.setState({
+ options: nextProps.options,
+ });
+ }
}
clearOptions() {
@@ -134,26 +131,28 @@ export default class Async extends Component {
isLoading: true
});
}
-
- return inputValue;
}
_onInputChange (inputValue) {
const { ignoreAccents, ignoreCase, onInputChange } = this.props;
+ let transformedInputValue = inputValue;
if (ignoreAccents) {
- inputValue = stripDiacritics(inputValue);
+ transformedInputValue = stripDiacritics(transformedInputValue);
}
if (ignoreCase) {
- inputValue = inputValue.toLowerCase();
+ transformedInputValue = transformedInputValue.toLowerCase();
}
if (onInputChange) {
- onInputChange(inputValue);
+ onInputChange(transformedInputValue);
}
- return this.loadOptions(inputValue);
+ this.loadOptions(transformedInputValue);
+
+ // Return the original input value to avoid modifying the user's view of the input while typing.
+ return inputValue;
}
inputValue() {
diff --git a/src/AsyncCreatable.js b/src/AsyncCreatable.js
index 40c5ed45..d456fa8b 100644
--- a/src/AsyncCreatable.js
+++ b/src/AsyncCreatable.js
@@ -1,6 +1,7 @@
import React from 'react';
-import createClass from 'create-react-class';
import Select from './Select';
+import Async from './Async';
+import Creatable from './Creatable';
function reduce(obj, props = {}){
return Object.keys(obj)
@@ -11,18 +12,17 @@ function reduce(obj, props = {}){
}, props);
}
-const AsyncCreatable = createClass({
- displayName: 'AsyncCreatableSelect',
+class AsyncCreatableSelect extends React.Component {
focus () {
this.select.focus();
- },
+ }
render () {
return (
-
+
{(asyncProps) => (
-
+
{(creatableProps) => (
)}
-
+
)}
-
+
);
}
-});
+};
-module.exports = AsyncCreatable;
+module.exports = AsyncCreatableSelect;
diff --git a/src/Creatable.js b/src/Creatable.js
index 44b49554..66e0cbd4 100644
--- a/src/Creatable.js
+++ b/src/Creatable.js
@@ -1,78 +1,19 @@
import React from 'react';
-import createClass from 'create-react-class';
import PropTypes from 'prop-types';
import Select from './Select';
import defaultFilterOptions from './utils/defaultFilterOptions';
import defaultMenuRenderer from './utils/defaultMenuRenderer';
-const Creatable = createClass({
- displayName: 'CreatableSelect',
+class CreatableSelect extends React.Component {
+ constructor (props, context) {
+ super(props, context);
- propTypes: {
- // Child function responsible for creating the inner Select component
- // This component can be used to compose HOCs (eg Creatable and Async)
- // (props: Object): PropTypes.element
- children: PropTypes.func,
-
- // See Select.propTypes.filterOptions
- filterOptions: PropTypes.any,
-
- // Searches for any matching option within the set of options.
- // This function prevents duplicate options from being created.
- // ({ option: Object, options: Array, labelKey: string, valueKey: string }): boolean
- isOptionUnique: PropTypes.func,
-
- // Determines if the current input text represents a valid option.
- // ({ label: string }): boolean
- isValidNewOption: PropTypes.func,
-
- // See Select.propTypes.menuRenderer
- menuRenderer: PropTypes.any,
-
- // Factory to create new option.
- // ({ label: string, labelKey: string, valueKey: string }): Object
- newOptionCreator: PropTypes.func,
-
- // input change handler: function (inputValue) {}
- onInputChange: PropTypes.func,
-
- // input keyDown handler: function (event) {}
- onInputKeyDown: PropTypes.func,
-
- // new option click handler: function (option) {}
- onNewOptionClick: PropTypes.func,
-
- // See Select.propTypes.options
- options: PropTypes.array,
-
- // Creates prompt/placeholder option text.
- // (filterText: string): string
- promptTextCreator: PropTypes.func,
-
- // Decides if a keyDown event (eg its `keyCode`) should result in the creation of a new option.
- shouldKeyDownEventCreateNewOption: PropTypes.func,
- },
-
- // Default prop methods
- statics: {
- isOptionUnique,
- isValidNewOption,
- newOptionCreator,
- promptTextCreator,
- shouldKeyDownEventCreateNewOption
- },
-
- getDefaultProps () {
- return {
- filterOptions: defaultFilterOptions,
- isOptionUnique,
- isValidNewOption,
- menuRenderer: defaultMenuRenderer,
- newOptionCreator,
- promptTextCreator,
- shouldKeyDownEventCreateNewOption,
- };
- },
+ this.filterOptions = this.filterOptions.bind(this);
+ this.menuRenderer = this.menuRenderer.bind(this);
+ this.onInputKeyDown = this.onInputKeyDown.bind(this);
+ this.onInputChange = this.onInputChange.bind(this);
+ this.onOptionSelect = this.onOptionSelect .bind(this);
+ }
createNewOption () {
const {
@@ -98,7 +39,7 @@ const Creatable = createClass({
}
}
}
- },
+ }
filterOptions (...params) {
const { filterOptions, isValidNewOption, options, promptTextCreator } = this.props;
@@ -140,7 +81,7 @@ const Creatable = createClass({
}
return filteredOptions;
- },
+ }
isOptionUnique ({
option,
@@ -156,7 +97,7 @@ const Creatable = createClass({
options,
valueKey: this.valueKey
});
- },
+ }
menuRenderer (params) {
const { menuRenderer } = this.props;
@@ -166,7 +107,7 @@ const Creatable = createClass({
onSelect: this.onOptionSelect,
selectValue: this.onOptionSelect
});
- },
+ }
onInputChange (input) {
const { onInputChange } = this.props;
@@ -177,7 +118,7 @@ const Creatable = createClass({
// This value may be needed in between Select mounts (when this.select is null)
this.inputValue = input;
- },
+ }
onInputKeyDown (event) {
const { shouldKeyDownEventCreateNewOption, onInputKeyDown } = this.props;
@@ -195,7 +136,7 @@ const Creatable = createClass({
} else if (onInputKeyDown) {
onInputKeyDown(event);
}
- },
+ }
onOptionSelect (option, event) {
if (option === this._createPlaceholderOption) {
@@ -203,11 +144,11 @@ const Creatable = createClass({
} else {
this.select.selectValue(option);
}
- },
+ }
focus () {
this.select.focus();
- },
+ }
render () {
const {
@@ -245,7 +186,7 @@ const Creatable = createClass({
return children(props);
}
-});
+};
function defaultChildren (props) {
return (
@@ -269,9 +210,9 @@ function isValidNewOption ({ label }) {
function newOptionCreator ({ label, labelKey, valueKey }) {
const option = {};
option[valueKey] = label;
- option[labelKey] = label;
- option.className = 'Select-create-option-placeholder';
- return option;
+ option[labelKey] = label;
+ option.className = 'Select-create-option-placeholder';
+ return option;
};
function promptTextCreator (label) {
@@ -289,4 +230,68 @@ function shouldKeyDownEventCreateNewOption ({ keyCode }) {
return false;
};
-module.exports = Creatable;
+ // Default prop methods
+CreatableSelect.isOptionUnique = isOptionUnique;
+CreatableSelect.isValidNewOption = isValidNewOption;
+CreatableSelect.newOptionCreator = newOptionCreator;
+CreatableSelect.promptTextCreator = promptTextCreator;
+CreatableSelect.shouldKeyDownEventCreateNewOption = shouldKeyDownEventCreateNewOption;
+
+
+CreatableSelect.defaultProps = {
+ filterOptions: defaultFilterOptions,
+ isOptionUnique,
+ isValidNewOption,
+ menuRenderer: defaultMenuRenderer,
+ newOptionCreator,
+ promptTextCreator,
+ shouldKeyDownEventCreateNewOption
+};
+
+CreatableSelect.propTypes = {
+ // Child function responsible for creating the inner Select component
+ // This component can be used to compose HOCs (eg Creatable and Async)
+ // (props: Object): PropTypes.element
+ children: PropTypes.func,
+
+ // See Select.propTypes.filterOptions
+ filterOptions: PropTypes.any,
+
+ // Searches for any matching option within the set of options.
+ // This function prevents duplicate options from being created.
+ // ({ option: Object, options: Array, labelKey: string, valueKey: string }): boolean
+ isOptionUnique: PropTypes.func,
+
+ // Determines if the current input text represents a valid option.
+ // ({ label: string }): boolean
+ isValidNewOption: PropTypes.func,
+
+ // See Select.propTypes.menuRenderer
+ menuRenderer: PropTypes.any,
+
+ // Factory to create new option.
+ // ({ label: string, labelKey: string, valueKey: string }): Object
+ newOptionCreator: PropTypes.func,
+
+ // input change handler: function (inputValue) {}
+ onInputChange: PropTypes.func,
+
+ // input keyDown handler: function (event) {}
+ onInputKeyDown: PropTypes.func,
+
+ // new option click handler: function (option) {}
+ onNewOptionClick: PropTypes.func,
+
+ // See Select.propTypes.options
+ options: PropTypes.array,
+
+ // Creates prompt/placeholder option text.
+ // (filterText: string): string
+ promptTextCreator: PropTypes.func,
+
+ // Decides if a keyDown event (eg its `keyCode`) should result in the creation of a new option.
+ shouldKeyDownEventCreateNewOption: PropTypes.func,
+};
+
+
+module.exports = CreatableSelect;
diff --git a/src/Option.js b/src/Option.js
index ca38e895..7cb94802 100644
--- a/src/Option.js
+++ b/src/Option.js
@@ -1,22 +1,20 @@
import React from 'react';
-import createClass from 'create-react-class';
import PropTypes from 'prop-types';
import classNames from 'classnames';
-const Option = createClass({
- propTypes: {
- children: PropTypes.node,
- className: PropTypes.string, // className (based on mouse position)
- instancePrefix: PropTypes.string.isRequired, // unique prefix for the ids (used for aria)
- isDisabled: PropTypes.bool, // the option is disabled
- isFocused: PropTypes.bool, // the option is focused
- isSelected: PropTypes.bool, // the option is selected
- onFocus: PropTypes.func, // method to handle mouseEnter on option element
- onSelect: PropTypes.func, // method to handle click on option element
- onUnfocus: PropTypes.func, // method to handle mouseLeave on option element
- option: PropTypes.object.isRequired, // object that is base for that option
- optionIndex: PropTypes.number, // index of the option, used to generate unique ids for aria
- },
+class Option extends React.Component {
+
+ constructor(props) {
+ super(props);
+
+ this.handleMouseDown = this.handleMouseDown.bind(this);
+ this.handleMouseEnter = this.handleMouseEnter.bind(this);
+ this.handleMouseMove = this.handleMouseMove.bind(this);
+ this.handleTouchStart = this.handleTouchStart.bind(this);
+ this.onFocus = this.onFocus.bind(this);
+ }
+
+
blockEvent (event) {
event.preventDefault();
event.stopPropagation();
@@ -28,21 +26,21 @@ const Option = createClass({
} else {
window.location.href = event.target.href;
}
- },
+ }
handleMouseDown (event) {
event.preventDefault();
event.stopPropagation();
this.props.onSelect(this.props.option, event);
- },
+ }
handleMouseEnter (event) {
this.onFocus(event);
- },
+ }
handleMouseMove (event) {
this.onFocus(event);
- },
+ }
handleTouchEnd(event){
// Check if the view is being dragged, In this case
@@ -50,23 +48,24 @@ const Option = createClass({
if(this.dragging) return;
this.handleMouseDown(event);
- },
+ }
handleTouchMove (event) {
// Set a flag that the view is being dragged
this.dragging = true;
- },
+ }
handleTouchStart (event) {
// Set a flag that the view is not being dragged
this.dragging = false;
- },
+ }
onFocus (event) {
if (!this.props.isFocused) {
this.props.onFocus(this.props.option, event);
}
- },
+ }
+
render () {
var { option, instancePrefix, optionIndex } = this.props;
var className = classNames(this.props.className, option.className);
@@ -93,6 +92,20 @@ const Option = createClass({
);
}
-});
+};
+
+Option.propTypes = {
+ children: PropTypes.node,
+ className: PropTypes.string, // className (based on mouse position)
+ instancePrefix: PropTypes.string.isRequired, // unique prefix for the ids (used for aria)
+ isDisabled: PropTypes.bool, // the option is disabled
+ isFocused: PropTypes.bool, // the option is focused
+ isSelected: PropTypes.bool, // the option is selected
+ onFocus: PropTypes.func, // method to handle mouseEnter on option element
+ onSelect: PropTypes.func, // method to handle click on option element
+ onUnfocus: PropTypes.func, // method to handle mouseLeave on option element
+ option: PropTypes.object.isRequired, // object that is base for that option
+ optionIndex: PropTypes.number, // index of the option, used to generate unique ids for aria
+};
module.exports = Option;
diff --git a/src/Select.js b/src/Select.js
index a844483a..a21ca477 100644
--- a/src/Select.js
+++ b/src/Select.js
@@ -5,7 +5,6 @@
*/
import React from 'react';
-import createClass from 'create-react-class';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import AutosizeInput from 'react-input-autosize';
@@ -16,9 +15,6 @@ import defaultFilterOptions from './utils/defaultFilterOptions';
import defaultMenuRenderer from './utils/defaultMenuRenderer';
import defaultClearRenderer from './utils/defaultClearRenderer';
-import Async from './Async';
-import AsyncCreatable from './AsyncCreatable';
-import Creatable from './Creatable';
import Dropdown from './Dropdown';
import Option from './Option';
import OptionGroup from './OptionGroup';
@@ -60,144 +56,42 @@ let instanceId = 1;
const invalidOptions = {};
-const Select = createClass({
-
- displayName: 'Select',
-
- propTypes: {
- addLabelText: PropTypes.string, // placeholder displayed when you want to add a label on a multi-value input
- 'aria-describedby': PropTypes.string, // HTML ID(s) of element(s) that should be used to describe this input (for assistive tech)
- 'aria-label': PropTypes.string, // Aria label (for assistive tech)
- 'aria-labelledby': PropTypes.string, // HTML ID of an element that should be used as the label (for assistive tech)
- arrowRenderer: PropTypes.func, // Create drop-down caret element
- autoBlur: PropTypes.bool, // automatically blur the component when an option is selected
- autofocus: PropTypes.bool, // autofocus the component on mount
- autosize: PropTypes.bool, // whether to enable autosizing or not
- backspaceRemoves: PropTypes.bool, // whether backspace removes an item if there is no text input
- backspaceToRemoveMessage: PropTypes.string, // Message to use for screenreaders to press backspace to remove the current item - {label} is replaced with the item label
- className: PropTypes.string, // className for the outer element
- clearAllText: stringOrNode, // title for the "clear" control when multi: true
- clearRenderer: PropTypes.func, // create clearable x element
- clearValueText: stringOrNode, // title for the "clear" control
- clearable: PropTypes.bool, // should it be possible to reset value
- deleteRemoves: PropTypes.bool, // whether backspace removes an item if there is no text input
- delimiter: PropTypes.string, // delimiter to use to join multiple values for the hidden field value
- disabled: PropTypes.bool, // whether the Select is disabled or not
- dropdownComponent: PropTypes.func, // dropdown component to render the menu in
- escapeClearsValue: PropTypes.bool, // whether escape clears the value when the menu is closed
- filterOption: PropTypes.func, // method to filter a single option (option, filterString)
- filterOptions: PropTypes.any, // boolean to enable default filtering or function to filter the options array ([options], filterString, [values])
- ignoreAccents: PropTypes.bool, // whether to strip diacritics when filtering
- ignoreCase: PropTypes.bool, // whether to perform case-insensitive filtering
- inputProps: PropTypes.object, // custom attributes for the Input
- inputRenderer: PropTypes.func, // returns a custom input component
- instanceId: PropTypes.string, // set the components instanceId
- isLoading: PropTypes.bool, // whether the Select is loading externally or not (such as options being loaded)
- isOpen: PropTypes.bool, // whether the Select dropdown menu is open or not
- joinValues: PropTypes.bool, // joins multiple values into a single form field with the delimiter (legacy mode)
- labelKey: PropTypes.string, // path of the label value in option objects
- matchPos: PropTypes.string, // (any|start) match the start or entire string when filtering
- matchProp: PropTypes.string, // (any|label|value) which option property to filter on
- menuBuffer: PropTypes.number, // optional buffer (in px) between the bottom of the viewport and the bottom of the menu
- menuContainerStyle: PropTypes.object, // optional style to apply to the menu container
- menuRenderer: PropTypes.func, // renders a custom menu with options
- menuStyle: PropTypes.object, // optional style to apply to the menu
- multi: PropTypes.bool, // multi-value input
- name: PropTypes.string, // generates a hidden tag with this field name for html forms
- noResultsText: stringOrNode, // placeholder displayed when there are no matching search results
- onBlur: PropTypes.func, // onBlur handler: function (event) {}
- onBlurResetsInput: PropTypes.bool, // whether input is cleared on blur
- onChange: PropTypes.func, // onChange handler: function (newValue) {}
- onClose: PropTypes.func, // fires when the menu is closed
- onCloseResetsInput: PropTypes.bool, // whether input is cleared when menu is closed through the arrow
- onFocus: PropTypes.func, // onFocus handler: function (event) {}
- onInputChange: PropTypes.func, // onInputChange handler: function (inputValue) {}
- onInputKeyDown: PropTypes.func, // input keyDown handler: function (event) {}
- onMenuScrollToBottom: PropTypes.func, // fires when the menu is scrolled to the bottom; can be used to paginate options
- onOpen: PropTypes.func, // fires when the menu is opened
- onValueClick: PropTypes.func, // onClick handler for value labels: function (value, event) {}
- openAfterFocus: PropTypes.bool, // boolean to enable opening dropdown when focused
- openOnFocus: PropTypes.bool, // always open options menu on focus
- optionClassName: PropTypes.string, // additional class(es) to apply to the elements
- optionComponent: PropTypes.func, // option component to render in dropdown
- optionGroupComponent: PropTypes.func, // option group component to render in dropdown
- optionRenderer: PropTypes.func, // optionRenderer: function (option) {}
- options: PropTypes.array, // array of options
- pageSize: PropTypes.number, // number of entries to page when using page up/down keys
- placeholder: stringOrNode, // field placeholder, displayed when there's no value
- renderInvalidValues: PropTypes.bool, // boolean to enable rendering values that do not match any options
- required: PropTypes.bool, // applies HTML5 required attribute when needed
- resetValue: PropTypes.any, // value to use when you clear the control
- scrollMenuIntoView: PropTypes.bool, // boolean to enable the viewport to shift so that the full menu fully visible when engaged
- searchable: PropTypes.bool, // whether to enable searching feature or not
- simpleValue: PropTypes.bool, // pass the value to onChange as a simple value (legacy pre 1.0 mode), defaults to false
- style: PropTypes.object, // optional style to apply to the control
- tabIndex: PropTypes.string, // optional tab index of the control
- tabSelectsValue: PropTypes.bool, // whether to treat tabbing out while focused to be value selection
- value: PropTypes.any, // initial field value
- valueComponent: PropTypes.func, // value component to render
- valueKey: PropTypes.string, // path of the label value in option objects
- valueRenderer: PropTypes.func, // valueRenderer: function (option) {}
- wrapperStyle: PropTypes.object, // optional style to apply to the component wrapper
- },
-
- statics: { Async, AsyncCreatable, Creatable },
-
- getDefaultProps () {
- return {
- addLabelText: 'Add "{label}"?',
- arrowRenderer: defaultArrowRenderer,
- autosize: true,
- backspaceRemoves: true,
- backspaceToRemoveMessage: 'Press backspace to remove {label}',
- clearable: true,
- clearAllText: 'Clear all',
- clearRenderer: defaultClearRenderer,
- clearValueText: 'Clear value',
- deleteRemoves: true,
- delimiter: ',',
- disabled: false,
- dropdownComponent: Dropdown,
- escapeClearsValue: true,
- filterOptions: defaultFilterOptions,
- ignoreAccents: true,
- ignoreCase: true,
- inputProps: {},
- isLoading: false,
- joinValues: false,
- labelKey: 'label',
- matchPos: 'any',
- matchProp: 'any',
- menuBuffer: 0,
- menuRenderer: defaultMenuRenderer,
- multi: false,
- noResultsText: 'No results found',
- onBlurResetsInput: true,
- onCloseResetsInput: true,
- optionComponent: Option,
- optionGroupComponent: OptionGroup,
- pageSize: 5,
- placeholder: 'Select...',
- renderInvalidValues: false,
- required: false,
- scrollMenuIntoView: true,
- searchable: true,
- simpleValue: false,
- tabSelectsValue: true,
- valueComponent: Value,
- valueKey: 'value',
- };
- },
-
- getInitialState () {
- return {
+class Select extends React.Component {
+
+ constructor(props) {
+ super(props);
+
+ this.handleTouchOutside = this.handleTouchOutside.bind(this);
+ this.handleTouchMove = this.handleTouchMove.bind(this);
+ this.handleTouchStart = this.handleTouchStart.bind(this);
+ this.handleTouchEnd = this.handleTouchEnd.bind(this);
+ this.handleTouchEndClearValue = this.handleTouchEndClearValue.bind(this);
+ this.handleMouseDown = this.handleMouseDown.bind(this);
+ this.handleMouseDownOnArrow = this.handleMouseDownOnArrow.bind(this);
+ this.handleMouseDownOnMenu = this.handleMouseDownOnMenu.bind(this);
+ this.handleInputFocus = this.handleInputFocus.bind(this);
+ this.handleInputBlur = this.handleInputBlur.bind(this);
+ this.handleInputChange = this.handleInputChange.bind(this);
+ this.handleInputValueChange = this.handleInputValueChange.bind(this);
+ this.handleKeyDown = this.handleKeyDown.bind(this);
+ this.handleValueClick = this.handleValueClick.bind(this);
+ this.handleMenuScroll = this.handleMenuScroll.bind(this);
+ this.handleRequired = this.handleRequired.bind(this);
+ this.getOptionLabel = this.getOptionLabel.bind(this);
+ this.onOptionRef = this.onOptionRef.bind(this);
+ this.clearValue = this.clearValue.bind(this);
+ this.removeValue = this.removeValue.bind(this);
+ this.selectValue = this.selectValue.bind(this);
+ this.focusOption = this.focusOption.bind(this);
+
+ this.state = {
inputValue: '',
isFocused: false,
isOpen: false,
isPseudoFocused: false,
required: false,
};
- },
+ }
componentWillMount () {
this._flatOptions = this.flattenOptions(this.props.options);
@@ -209,13 +103,13 @@ const Select = createClass({
required: this.handleRequired(valueArray[0], this.props.multi),
});
}
- },
+ }
componentDidMount () {
if (this.props.autofocus) {
this.focus();
}
- },
+ }
componentWillReceiveProps (nextProps) {
if (nextProps.options !== this.props.options) {
@@ -233,7 +127,7 @@ const Select = createClass({
required: this.handleRequired(valueArray[0], nextProps.multi),
});
}
- },
+ }
componentWillUpdate (nextProps, nextState) {
if (nextState.isOpen !== this.state.isOpen) {
@@ -241,7 +135,7 @@ const Select = createClass({
const handler = nextState.isOpen ? nextProps.onOpen : nextProps.onClose;
handler && handler();
}
- },
+ }
componentDidUpdate (prevProps, prevState) {
// focus to the selected option
@@ -282,7 +176,7 @@ const Select = createClass({
this.setState({ isFocused: false }); // eslint-disable-line react/no-did-update-set-state
this.closeMenu();
}
- },
+ }
componentWillUnmount () {
if (!document.removeEventListener && document.detachEvent) {
@@ -290,7 +184,7 @@ const Select = createClass({
} else {
document.removeEventListener('touchstart', this.handleTouchOutside);
}
- },
+ }
toggleTouchOutsideEvent (enabled) {
if (enabled) {
@@ -306,7 +200,7 @@ const Select = createClass({
document.removeEventListener('touchstart', this.handleTouchOutside);
}
}
- },
+ }
handleTouchOutside (event) {
// handle touch outside on ios to dismiss menu
@@ -314,27 +208,27 @@ const Select = createClass({
this.menuContainer && !this.menuContainer.contains(event.target)) {
this.closeMenu();
}
- },
+ }
focus () {
if (!this.input) return;
this.input.focus();
- },
+ }
blurInput () {
if (!this.input) return;
this.input.blur();
- },
+ }
handleTouchMove (event) {
// Set a flag that the view is being dragged
this.dragging = true;
- },
+ }
handleTouchStart (event) {
// Set a flag that the view is not being dragged
this.dragging = false;
- },
+ }
handleTouchEnd (event) {
// Check if the view is being dragged, In this case
@@ -343,7 +237,7 @@ const Select = createClass({
// Fire the mouse events
this.handleMouseDown(event);
- },
+ }
handleTouchEndClearValue (event) {
// Check if the view is being dragged, In this case
@@ -352,7 +246,7 @@ const Select = createClass({
// Clear the value
this.clearValue(event);
- },
+ }
handleMouseDown (event) {
// if the event was triggered by a mousedown and not the primary
@@ -402,7 +296,7 @@ const Select = createClass({
this._openAfterFocus = true;
this.focus();
}
- },
+ }
handleMouseDownOnArrow (event) {
// if the event was triggered by a mousedown and not the primary
@@ -419,7 +313,7 @@ const Select = createClass({
event.preventDefault();
// close the menu
this.closeMenu();
- },
+ }
handleMouseDownOnMenu (event) {
// if the event was triggered by a mousedown and not the primary
@@ -432,24 +326,23 @@ const Select = createClass({
this._openAfterFocus = true;
this.focus();
- },
+ }
closeMenu () {
if(this.props.onCloseResetsInput) {
this.setState({
isOpen: false,
isPseudoFocused: this.state.isFocused && !this.props.multi,
- inputValue: ''
+ inputValue: this.handleInputValueChange('')
});
} else {
this.setState({
isOpen: false,
- isPseudoFocused: this.state.isFocused && !this.props.multi,
- inputValue: this.state.inputValue
+ isPseudoFocused: this.state.isFocused && !this.props.multi
});
}
this.hasScrolledToOption = false;
- },
+ }
handleInputFocus (event) {
if (this.props.disabled) return;
@@ -462,7 +355,7 @@ const Select = createClass({
isOpen: isOpen
});
this._openAfterFocus = false;
- },
+ }
handleInputBlur (event) {
// The check for menu.contains(activeElement) is necessary to prevent IE11's scrollbar from closing the menu in certain contexts.
@@ -480,20 +373,16 @@ const Select = createClass({
isPseudoFocused: false,
};
if (this.props.onBlurResetsInput) {
- onBlurredState.inputValue = '';
+ onBlurredState.inputValue = this.handleInputValueChange('');
}
this.setState(onBlurredState);
- },
+ }
handleInputChange (event) {
let newInputValue = event.target.value;
- if (this.state.inputValue !== event.target.value && this.props.onInputChange) {
- let nextState = this.props.onInputChange(newInputValue);
- // Note: != used deliberately here to catch undefined and null
- if (nextState != null && typeof nextState !== 'object') {
- newInputValue = '' + nextState;
- }
+ if (this.state.inputValue !== event.target.value) {
+ newInputValue = this.handleInputValueChange(newInputValue);
}
this.setState({
@@ -501,7 +390,18 @@ const Select = createClass({
isPseudoFocused: false,
inputValue: newInputValue,
});
- },
+ }
+
+ handleInputValueChange(newValue) {
+ if (this.props.onInputChange) {
+ let nextState = this.props.onInputChange(newValue);
+ // Note: != used deliberately here to catch undefined and null
+ if (nextState != null && typeof nextState !== 'object') {
+ newValue = '' + nextState;
+ }
+ }
+ return newValue;
+ }
handleKeyDown (event) {
if (this.props.disabled) return;
@@ -573,12 +473,12 @@ const Select = createClass({
default: return;
}
event.preventDefault();
- },
+ }
handleValueClick (option, event) {
if (!this.props.onValueClick) return;
this.props.onValueClick(option, event);
- },
+ }
handleMenuScroll (event) {
if (!this.props.onMenuScrollToBottom) return;
@@ -586,16 +486,16 @@ const Select = createClass({
if (target.scrollHeight > target.offsetHeight && !(target.scrollHeight - target.offsetHeight - target.scrollTop)) {
this.props.onMenuScrollToBottom();
}
- },
+ }
handleRequired (value, multi) {
if (!value) return true;
return (multi ? value.length === 0 : Object.keys(value).length === 0);
- },
+ }
getOptionLabel (op) {
return op[this.props.labelKey];
- },
+ }
/**
* Turns a value into an array from the given options
@@ -616,7 +516,7 @@ const Select = createClass({
}
var expandedValue = this.expandValue(value, props);
return expandedValue ? [expandedValue] : [];
- },
+ }
/**
* Retrieve a value from the given options and valueKey
@@ -642,7 +542,7 @@ const Select = createClass({
};
return invalidOptions[value];
}
- },
+ }
setValue (value) {
if (this.props.autoBlur){
@@ -657,14 +557,14 @@ const Select = createClass({
value = this.props.multi ? value.map(i => i[this.props.valueKey]).join(this.props.delimiter) : value[this.props.valueKey];
}
this.props.onChange(value);
- },
+ }
selectValue (value) {
//NOTE: update value in the callback to make sure the input value is empty so that there are no styling issues (Chrome had issue otherwise)
this.hasScrolledToOption = false;
if (this.props.multi) {
this.setState({
- inputValue: '',
+ inputValue: this.handleInputValueChange(''),
focusedIndex: null
}, () => {
this.addValue(value);
@@ -672,13 +572,13 @@ const Select = createClass({
} else {
this.setState({
isOpen: false,
- inputValue: '',
+ inputValue: this.handleInputValueChange(''),
isPseudoFocused: this.state.isFocused,
}, () => {
this.setValue(value);
});
}
- },
+ }
addValue (value) {
var valueArray = this.getValueArray(this.props.value);
@@ -692,19 +592,20 @@ const Select = createClass({
// focus the option below the selected one
this.focusOption(visibleOptions[lastValueIndex + 1]);
}
- },
+ }
popValue () {
var valueArray = this.getValueArray(this.props.value);
if (!valueArray.length) return;
if (valueArray[valueArray.length-1].clearableValue === false) return;
- this.setValue(valueArray.slice(0, valueArray.length - 1));
- },
+ this.setValue(this.props.multi ? valueArray.slice(0, valueArray.length - 1) : null);
+ }
removeValue (value) {
var valueArray = this.getValueArray(this.props.value);
this.setValue(valueArray.filter(i => i !== value));
- },
+ this.focus();
+ }
clearValue (event) {
// if the event was triggered by a mousedown and not the primary
@@ -717,9 +618,9 @@ const Select = createClass({
this.setValue(this.getResetValue());
this.setState({
isOpen: false,
- inputValue: '',
+ inputValue: this.handleInputValueChange(''),
}, this.focus);
- },
+ }
getResetValue () {
if (this.props.resetValue !== undefined) {
@@ -729,37 +630,37 @@ const Select = createClass({
} else {
return null;
}
- },
+ }
focusOption (option) {
this.setState({
focusedOption: option
});
- },
+ }
focusNextOption () {
this.focusAdjacentOption('next');
- },
+ }
focusPreviousOption () {
this.focusAdjacentOption('previous');
- },
+ }
focusPageUpOption () {
this.focusAdjacentOption('page_up');
- },
+ }
focusPageDownOption () {
this.focusAdjacentOption('page_down');
- },
+ }
focusStartOption () {
this.focusAdjacentOption('start');
- },
+ }
focusEndOption () {
this.focusAdjacentOption('end');
- },
+ }
focusAdjacentOption (dir) {
var options = this._visibleOptions
@@ -818,21 +719,21 @@ const Select = createClass({
focusedIndex: options[focusedIndex].index,
focusedOption: options[focusedIndex].option
});
- },
+ }
getFocusedOption () {
return this._focusedOption;
- },
+ }
getInputValue () {
return this.state.inputValue;
- },
+ }
selectFocusedOption () {
if (this._focusedOption) {
return this.selectValue(this._focusedOption);
}
- },
+ }
renderLoading () {
if (!this.props.isLoading) return;
@@ -841,7 +742,7 @@ const Select = createClass({
);
- },
+ }
renderValue (valueArray, isOpen) {
let renderLabel = this.props.valueRenderer || this.getOptionLabel;
@@ -881,7 +782,7 @@ const Select = createClass({
);
}
- },
+ }
renderInput (valueArray, focusedOptionIndex) {
var className = classNames('Select-input', this.props.inputProps.className);
@@ -953,7 +854,7 @@ const Select = createClass({
);
- },
+ }
renderClear () {
@@ -971,11 +872,11 @@ const Select = createClass({
{clear}
);
- },
+ }
renderArrow () {
const onMouseDown = this.handleMouseDownOnArrow;
- const isOpen = this.state.isOpen;
+ const isOpen = this.state.isOpen;
const arrow = this.props.arrowRenderer({ onMouseDown, isOpen });
return (
@@ -986,7 +887,7 @@ const Select = createClass({
{arrow}
);
- },
+ }
filterFlatOptions (excludeOptions) {
const filterValue = this.state.inputValue;
@@ -1014,7 +915,7 @@ const Select = createClass({
} else {
return flatOptions;
}
- },
+ }
flattenOptions (options, group) {
if (!options) return [];
@@ -1034,7 +935,7 @@ const Select = createClass({
}
}
return flatOptions;
- },
+ }
unflattenOptions (flatOptions) {
const groupedOptions = [];
@@ -1072,13 +973,13 @@ const Select = createClass({
}
});
return groupedOptions;
- },
+ }
onOptionRef(ref, isFocused) {
if (isFocused) {
this.focused = ref;
}
- },
+ }
renderMenu (options, valueArray, focusedOption) {
if (options && options.length) {
@@ -1108,7 +1009,7 @@ const Select = createClass({
} else {
return null;
}
- },
+ }
renderHiddenField (valueArray) {
if (!this.props.name) return;
@@ -1131,7 +1032,7 @@ const Select = createClass({
value={stringifyValue(item[this.props.valueKey])}
disabled={this.props.disabled} />
));
- },
+ }
getFocusableOptionIndex (selectedOption) {
var options = this._visibleOptions;
@@ -1157,7 +1058,7 @@ const Select = createClass({
if (!options[i].disabled) return i;
}
return null;
- },
+ }
renderOuter (options, valueArray, focusedOption) {
let Dropdown = this.props.dropdownComponent;
@@ -1178,7 +1079,7 @@ const Select = createClass({
);
- },
+ }
render () {
let valueArray = this.getValueArray(this.props.value);
@@ -1248,7 +1149,127 @@ const Select = createClass({
);
}
-
-});
+};
+
+Select.propTypes = {
+ addLabelText: PropTypes.string, // placeholder displayed when you want to add a label on a multi-value input
+ 'aria-describedby': PropTypes.string, // HTML ID(s) of element(s) that should be used to describe this input (for assistive tech)
+ 'aria-label': PropTypes.string, // Aria label (for assistive tech)
+ 'aria-labelledby': PropTypes.string, // HTML ID of an element that should be used as the label (for assistive tech)
+ arrowRenderer: PropTypes.func, // Create drop-down caret element
+ autoBlur: PropTypes.bool, // automatically blur the component when an option is selected
+ autofocus: PropTypes.bool, // autofocus the component on mount
+ autosize: PropTypes.bool, // whether to enable autosizing or not
+ backspaceRemoves: PropTypes.bool, // whether backspace removes an item if there is no text input
+ backspaceToRemoveMessage: PropTypes.string, // Message to use for screenreaders to press backspace to remove the current item - {label} is replaced with the item label
+ className: PropTypes.string, // className for the outer element
+ clearAllText: stringOrNode, // title for the "clear" control when multi: true
+ clearRenderer: PropTypes.func, // create clearable x element
+ clearValueText: stringOrNode, // title for the "clear" control
+ clearable: PropTypes.bool, // should it be possible to reset value
+ deleteRemoves: PropTypes.bool, // whether backspace removes an item if there is no text input
+ delimiter: PropTypes.string, // delimiter to use to join multiple values for the hidden field value
+ disabled: PropTypes.bool, // whether the Select is disabled or not
+ dropdownComponent: PropTypes.func, // dropdown component to render the menu in
+ escapeClearsValue: PropTypes.bool, // whether escape clears the value when the menu is closed
+ filterOption: PropTypes.func, // method to filter a single option (option, filterString)
+ filterOptions: PropTypes.any, // boolean to enable default filtering or function to filter the options array ([options], filterString, [values])
+ ignoreAccents: PropTypes.bool, // whether to strip diacritics when filtering
+ ignoreCase: PropTypes.bool, // whether to perform case-insensitive filtering
+ inputProps: PropTypes.object, // custom attributes for the Input
+ inputRenderer: PropTypes.func, // returns a custom input component
+ instanceId: PropTypes.string, // set the components instanceId
+ isLoading: PropTypes.bool, // whether the Select is loading externally or not (such as options being loaded)
+ isOpen: PropTypes.bool, // whether the Select dropdown menu is open or not
+ joinValues: PropTypes.bool, // joins multiple values into a single form field with the delimiter (legacy mode)
+ labelKey: PropTypes.string, // path of the label value in option objects
+ matchPos: PropTypes.string, // (any|start) match the start or entire string when filtering
+ matchProp: PropTypes.string, // (any|label|value) which option property to filter on
+ menuBuffer: PropTypes.number, // optional buffer (in px) between the bottom of the viewport and the bottom of the menu
+ menuContainerStyle: PropTypes.object, // optional style to apply to the menu container
+ menuRenderer: PropTypes.func, // renders a custom menu with options
+ menuStyle: PropTypes.object, // optional style to apply to the menu
+ multi: PropTypes.bool, // multi-value input
+ name: PropTypes.string, // generates a hidden tag with this field name for html forms
+ noResultsText: stringOrNode, // placeholder displayed when there are no matching search results
+ onBlur: PropTypes.func, // onBlur handler: function (event) {}
+ onBlurResetsInput: PropTypes.bool, // whether input is cleared on blur
+ onChange: PropTypes.func, // onChange handler: function (newValue) {}
+ onClose: PropTypes.func, // fires when the menu is closed
+ onCloseResetsInput: PropTypes.bool, // whether input is cleared when menu is closed through the arrow
+ onFocus: PropTypes.func, // onFocus handler: function (event) {}
+ onInputChange: PropTypes.func, // onInputChange handler: function (inputValue) {}
+ onInputKeyDown: PropTypes.func, // input keyDown handler: function (event) {}
+ onMenuScrollToBottom: PropTypes.func, // fires when the menu is scrolled to the bottom; can be used to paginate options
+ onOpen: PropTypes.func, // fires when the menu is opened
+ onValueClick: PropTypes.func, // onClick handler for value labels: function (value, event) {}
+ openAfterFocus: PropTypes.bool, // boolean to enable opening dropdown when focused
+ openOnFocus: PropTypes.bool, // always open options menu on focus
+ optionClassName: PropTypes.string, // additional class(es) to apply to the elements
+ optionComponent: PropTypes.func, // option component to render in dropdown
+ optionGroupComponent: PropTypes.func, // option group component to render in dropdown
+ optionRenderer: PropTypes.func, // optionRenderer: function (option) {}
+ options: PropTypes.array, // array of options
+ pageSize: PropTypes.number, // number of entries to page when using page up/down keys
+ placeholder: stringOrNode, // field placeholder, displayed when there's no value
+ renderInvalidValues: PropTypes.bool, // boolean to enable rendering values that do not match any options
+ required: PropTypes.bool, // applies HTML5 required attribute when needed
+ resetValue: PropTypes.any, // value to use when you clear the control
+ scrollMenuIntoView: PropTypes.bool, // boolean to enable the viewport to shift so that the full menu fully visible when engaged
+ searchable: PropTypes.bool, // whether to enable searching feature or not
+ simpleValue: PropTypes.bool, // pass the value to onChange as a simple value (legacy pre 1.0 mode), defaults to false
+ style: PropTypes.object, // optional style to apply to the control
+ tabIndex: PropTypes.string, // optional tab index of the control
+ tabSelectsValue: PropTypes.bool, // whether to treat tabbing out while focused to be value selection
+ value: PropTypes.any, // initial field value
+ valueComponent: PropTypes.func, // value component to render
+ valueKey: PropTypes.string, // path of the label value in option objects
+ valueRenderer: PropTypes.func, // valueRenderer: function (option) {}
+ wrapperStyle: PropTypes.object, // optional style to apply to the component wrapper
+};
+
+Select.defaultProps = {
+ addLabelText: 'Add "{label}"?',
+ arrowRenderer: defaultArrowRenderer,
+ autosize: true,
+ backspaceRemoves: true,
+ backspaceToRemoveMessage: 'Press backspace to remove {label}',
+ clearable: true,
+ clearAllText: 'Clear all',
+ clearRenderer: defaultClearRenderer,
+ clearValueText: 'Clear value',
+ deleteRemoves: true,
+ delimiter: ',',
+ disabled: false,
+ dropdownComponent: Dropdown,
+ escapeClearsValue: true,
+ filterOptions: defaultFilterOptions,
+ ignoreAccents: true,
+ ignoreCase: true,
+ inputProps: {},
+ isLoading: false,
+ joinValues: false,
+ labelKey: 'label',
+ matchPos: 'any',
+ matchProp: 'any',
+ menuBuffer: 0,
+ menuRenderer: defaultMenuRenderer,
+ multi: false,
+ noResultsText: 'No results found',
+ onBlurResetsInput: true,
+ onCloseResetsInput: true,
+ optionComponent: Option,
+ optionGroupComponent: OptionGroup,
+ pageSize: 5,
+ placeholder: 'Select...',
+ renderInvalidValues: false,
+ required: false,
+ scrollMenuIntoView: true,
+ searchable: true,
+ simpleValue: false,
+ tabSelectsValue: true,
+ valueComponent: Value,
+ valueKey: 'value',
+};
export default Select;
diff --git a/src/Value.js b/src/Value.js
index d9925654..74431fb4 100644
--- a/src/Value.js
+++ b/src/Value.js
@@ -1,20 +1,18 @@
import React from 'react';
-import createClass from 'create-react-class';
import PropTypes from 'prop-types';
import classNames from 'classnames';
-const Value = createClass({
+class Value extends React.Component {
- displayName: 'Value',
+ constructor(props) {
+ super(props);
- propTypes: {
- children: PropTypes.node,
- disabled: PropTypes.bool, // disabled prop passed to ReactSelect
- id: PropTypes.string, // Unique id for the value - used for aria
- onClick: PropTypes.func, // method to handle click on value label
- onRemove: PropTypes.func, // method to handle removal of the value
- value: PropTypes.object.isRequired, // the option object for this value
- },
+ this.handleMouseDown = this.handleMouseDown.bind(this);
+ this.onRemove = this.onRemove.bind(this);
+ this.handleTouchEndRemove = this.handleTouchEndRemove.bind(this);
+ this.handleTouchMove = this.handleTouchMove.bind(this);
+ this.handleTouchStart = this.handleTouchStart.bind(this);
+ }
handleMouseDown (event) {
if (event.type === 'mousedown' && event.button !== 0) {
@@ -28,13 +26,13 @@ const Value = createClass({
if (this.props.value.href) {
event.stopPropagation();
}
- },
+ }
onRemove (event) {
event.preventDefault();
event.stopPropagation();
this.props.onRemove(this.props.value);
- },
+ }
handleTouchEndRemove (event){
// Check if the view is being dragged, In this case
@@ -43,17 +41,17 @@ const Value = createClass({
// Fire the mouse events
this.onRemove(event);
- },
+ }
handleTouchMove (event) {
// Set a flag that the view is being dragged
this.dragging = true;
- },
+ }
handleTouchStart (event) {
// Set a flag that the view is not being dragged
this.dragging = false;
- },
+ }
renderRemoveIcon () {
if (this.props.disabled || !this.props.onRemove) return;
@@ -67,7 +65,7 @@ const Value = createClass({
×
);
- },
+ }
renderLabel () {
let className = 'Select-value-label';
@@ -80,7 +78,7 @@ const Value = createClass({
{this.props.children}
);
- },
+ }
render () {
return (
@@ -93,7 +91,16 @@ const Value = createClass({
);
}
+};
+
-});
+Value.propTypes = {
+ children: PropTypes.node,
+ disabled: PropTypes.bool, // disabled prop passed to ReactSelect
+ id: PropTypes.string, // Unique id for the value - used for aria
+ onClick: PropTypes.func, // method to handle click on value label
+ onRemove: PropTypes.func, // method to handle removal of the value
+ value: PropTypes.object.isRequired, // the option object for this value
+};
module.exports = Value;
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 00000000..4a27e4b5
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,15 @@
+import Select from './Select';
+import Async from './Async';
+import AsyncCreatable from './AsyncCreatable';
+import Creatable from './Creatable';
+
+Select.Async = Async;
+Select.AsyncCreatable = AsyncCreatable;
+Select.Creatable = Creatable;
+
+export default Select;
+export {
+ Async,
+ AsyncCreatable,
+ Creatable
+};
diff --git a/test/Async-test.js b/test/Async-test.js
index 41f794b4..6a63ee8b 100644
--- a/test/Async-test.js
+++ b/test/Async-test.js
@@ -20,7 +20,7 @@ var ReactDOM = require('react-dom');
var TestUtils = require('react-dom/test-utils');
var sinon = require('sinon');
-var Select = require('../src/Select');
+var Select = require('../src');
describe('Async', () => {
let asyncInstance, asyncNode, filterInputNode, loadOptions;
@@ -31,8 +31,8 @@ describe('Async', () => {
);
asyncNode = ReactDOM.findDOMNode(asyncInstance);
@@ -319,6 +319,15 @@ describe('Async', () => {
typeSearchText('WÄRE');
expect(loadOptions, 'was called with', 'WÄRE');
});
+
+ it('does not mutate the user input', () => {
+ createControl({
+ ignoreAccents: false,
+ ignoreCase: true
+ });
+ typeSearchText('A');
+ expect(asyncNode.textContent, 'to begin with', 'A');
+ });
});
describe('with ignore case and ignore accents', () => {
@@ -438,13 +447,29 @@ describe('Async', () => {
describe('.focus()', () => {
beforeEach(() => {
createControl({});
+ TestUtils.Simulate.blur(filterInputNode);
});
it('focuses the search input', () => {
- var input = asyncNode.querySelector('input');
- expect(input, 'not to equal', document.activeElement);
+ expect(filterInputNode, 'not to equal', document.activeElement);
asyncInstance.focus();
- expect(input, 'to equal', document.activeElement);
+ expect(filterInputNode, 'to equal', document.activeElement);
+ });
+ });
+
+
+ describe('props sync test', () => {
+ it('should update options on componentWillReceiveProps', () => {
+ createControl({
+ });
+ asyncInstance.componentWillReceiveProps({
+ options: [{
+ label: 'bar',
+ value: 'foo',
+ }]
+ });
+ expect(asyncNode.querySelectorAll('[role=option]').length, 'to equal', 1);
+ expect(asyncNode.querySelector('[role=option]').textContent, 'to equal', 'bar');
});
});
});
diff --git a/test/AsyncCreatable-test.js b/test/AsyncCreatable-test.js
index 00b80a9b..514a40bd 100644
--- a/test/AsyncCreatable-test.js
+++ b/test/AsyncCreatable-test.js
@@ -16,7 +16,7 @@ var React = require('react');
var ReactDOM = require('react-dom');
var TestUtils = require('react-dom/test-utils');
var sinon = require('sinon');
-var Select = require('../src/Select');
+var Select = require('../src');
describe('AsyncCreatable', () => {
let creatableInstance, creatableNode, filterInputNode, loadOptions, renderer;
diff --git a/test/Creatable-test.js b/test/Creatable-test.js
index cd49307c..0c317aab 100644
--- a/test/Creatable-test.js
+++ b/test/Creatable-test.js
@@ -16,10 +16,10 @@ var expect = unexpected
var React = require('react');
var ReactDOM = require('react-dom');
var TestUtils = require('react-dom/test-utils');
-var Select = require('../src/Select');
+var Select = require('../src');
describe('Creatable', () => {
- let creatableInstance, creatableNode, filterInputNode, innserSelectInstance, renderer;
+ let creatableInstance, creatableNode, filterInputNode, innerSelectInstance, renderer;
beforeEach(() => renderer = TestUtils.createRenderer());
@@ -36,7 +36,7 @@ describe('Creatable', () => {
);
creatableNode = ReactDOM.findDOMNode(creatableInstance);
- innserSelectInstance = creatableInstance.select;
+ innerSelectInstance = creatableInstance.select;
findAndFocusInputControl();
};
@@ -106,7 +106,7 @@ describe('Creatable', () => {
createControl({
filterOptions: () => null
});
- typeSearchText('test');;
+ typeSearchText('test');
});
it('should not show a "create..." prompt if current filter text is not a valid option (as determined by :isValidNewOption prop)', () => {
@@ -150,7 +150,6 @@ describe('Creatable', () => {
const options = [{ label: 'One', value: 1 }];
createControl({
options,
- shouldKeyDownEventCreateNewOption: ({ keyCode }) => keyCode === 13
});
typeSearchText('on'); // ['Create option "on"', 'One']
TestUtils.Simulate.keyDown(filterInputNode, { keyCode: 40, key: 'ArrowDown' }); // Select 'One'
@@ -158,6 +157,28 @@ describe('Creatable', () => {
expect(options, 'to have length', 1);
});
+ it('should remove the new option after closing on selecting option', () => {
+ createControl();
+ typeSearchText('9');
+ TestUtils.Simulate.keyDown(filterInputNode, { keyCode: 40, key: 'ArrowDown' });
+ TestUtils.Simulate.keyDown(filterInputNode, { keyCode: 13 });
+ expect(creatableInstance.inputValue, 'to equal', '');
+ });
+
+ it('should remove the new option after closing on escape', () => {
+ createControl();
+ typeSearchText('9');
+ TestUtils.Simulate.keyDown(filterInputNode, { keyCode: 27 });
+ expect(creatableInstance.inputValue, 'to equal', '');
+ });
+
+ it('should remove the new option after closing on blur', () => {
+ createControl();
+ typeSearchText('9');
+ TestUtils.Simulate.blur(filterInputNode);
+ expect(creatableInstance.inputValue, 'to equal', '');
+ });
+
it('should allow a custom select type to be rendered via the :children property', () => {
let childProps;
createControl({
diff --git a/test/Select-test.js b/test/Select-test.js
index 47b76237..8a40f550 100644
--- a/test/Select-test.js
+++ b/test/Select-test.js
@@ -2004,6 +2004,19 @@ describe('Select', () => {
]);
});
+ it('removes the last item with backspace', () => {
+
+ wrapper.setPropsForChild({
+ multi: false,
+ value: 'one'
+ });
+ onChange.reset(); // Ignore previous onChange calls
+
+ pressBackspace();
+
+ expect(onChange, 'was called with', null);
+ });
+
it('doesn\'t show the X if clearableValue=false', () => {
setValueProp(['two']);
@@ -2153,8 +2166,8 @@ describe('Select', () => {
clickArrowToOpen();
expect(instance,
- 'with event mouseDown', 'on',
Two
,
- 'with event mouseDown', 'on', One
,
+ 'with event', 'mouseDown', 'on', Two
,
+ 'with event', 'mouseDown', 'on', One
,
'to contain',
Two
@@ -2726,7 +2739,7 @@ describe('Select', () => {
expect(spyFilterOption, 'was called with', defaultOptions[1], '');
expect(spyFilterOption, 'was called with', defaultOptions[2], '');
expect(spyFilterOption, 'was called with', defaultOptions[3], '');
- });
+ }).timeout(5000);
describe('when entering text', () => {
@@ -2744,7 +2757,7 @@ describe('Select', () => {
expect(spyFilterOption, 'was called with', defaultOptions[1], 'xyz');
expect(spyFilterOption, 'was called with', defaultOptions[2], 'xyz');
expect(spyFilterOption, 'was called with', defaultOptions[3], 'xyz');
- });
+ }).timeout(5000);
it('only shows the filtered option', () => {
@@ -4045,7 +4058,7 @@ describe('Select', () => {
it('sets the haspopup and expanded to true when menu is shown', () => {
expect(instance,
- 'with event keyDown', ARROW_DOWN, 'on', ,
+ 'with event', 'keyDown', ARROW_DOWN, 'on', ,
'to contain', );
});
@@ -4128,7 +4141,7 @@ describe('Select', () => {
it('hides the `press backspace to remove` message on blur', () => {
expect(instance,
- 'with event blur', 'on', ,
+ 'with event', 'blur', 'on', ,
'not to contain',
Press backspace to remove label two
@@ -4148,8 +4161,8 @@ describe('Select', () => {
it('updates the active descendant after a selection', () => {
return expect(wrapper,
- 'with event keyDown', ARROW_DOWN, 'on', ,
- 'with event keyDown', KEY_ENTER, 'on', ,
+ 'with event', 'keyDown', ARROW_DOWN, 'on', ,
+ 'with event', 'keyDown', KEY_ENTER, 'on', ,
'queried for', )
.then(input => {