diff --git a/README.md b/README.md
index 44f611ae72..594649c385 100644
--- a/README.md
+++ b/README.md
@@ -106,7 +106,7 @@ The built-in Options renderer also support custom classNames, just add a `classN
You can enable multi-value selection by setting `multi={true}`. In this mode:
-* Selected options will be removed from the dropdown menu
+* Selected options will be removed from the dropdown menu by default. If you want them to remain in the list, set `removeSelected={false}`
* The selected values are submitted in multiple ` ` fields, use `joinValues` to submit joined values in a single field instead
* The values of the selected items are joined using the `delimiter` prop to create the input value when `joinValues` is true
* A simple value, if provided, will be split using the `delimiter` prop
diff --git a/examples/src/components/Multiselect.js b/examples/src/components/Multiselect.js
index c7288716e5..b54377c685 100644
--- a/examples/src/components/Multiselect.js
+++ b/examples/src/components/Multiselect.js
@@ -23,6 +23,7 @@ var MultiSelectField = createClass({
},
getInitialState () {
return {
+ removeSelected: true,
disabled: false,
crazy: false,
stayOpen: false,
@@ -57,6 +58,7 @@ var MultiSelectField = createClass({
onChange={this.handleSelectChange}
options={options}
placeholder="Select your favourite(s)"
+ removeSelected={this.state.removeSelected}
rtl={this.state.rtl}
simpleValue
value={value}
@@ -64,7 +66,11 @@ var MultiSelectField = createClass({
-
+
+ Remove selected options
+
+
+
Disable the control
diff --git a/src/Select.js b/src/Select.js
index dc57b25089..3cdd096d24 100644
--- a/src/Select.js
+++ b/src/Select.js
@@ -536,7 +536,12 @@ class Select extends React.Component {
inputValue: this.handleInputValueChange(updatedValue),
isOpen: !this.props.closeOnSelect,
}, () => {
- this.addValue(value);
+ var valueArray = this.getValueArray(this.props.value);
+ if (valueArray.some(i => i[this.props.valueKey] === value[this.props.valueKey])) {
+ this.removeValue(value);
+ } else {
+ this.addValue(value);
+ }
});
} else {
this.setState({
@@ -572,7 +577,7 @@ class Select extends React.Component {
removeValue (value) {
var valueArray = this.getValueArray(this.props.value);
- this.setValue(valueArray.filter(i => i !== value));
+ this.setValue(valueArray.filter(i => i[this.props.valueKey] !== value[this.props.valueKey]));
this.focus();
}
@@ -999,7 +1004,7 @@ class Select extends React.Component {
render () {
let valueArray = this.getValueArray(this.props.value);
- let options = this._visibleOptions = this.filterOptions(this.props.multi ? this.getValueArray(this.props.value) : null);
+ let options = this._visibleOptions = this.filterOptions(this.props.multi && this.props.removeSelected ? valueArray : null);
let isOpen = this.state.isOpen;
if (this.props.multi && !options.length && valueArray.length && !this.state.inputValue) isOpen = false;
const focusedOptionIndex = this.getFocusableOptionIndex(valueArray[0]);
@@ -1128,6 +1133,7 @@ Select.propTypes = {
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
+ removeSelected: PropTypes.bool, // whether the selected option is removed from the dropdown on multi selects
required: PropTypes.bool, // applies HTML5 required attribute when needed
resetValue: PropTypes.any, // value to use when you clear the control
rtl: PropTypes.bool, // set to true in order to use react-select in right-to-left direction
@@ -1179,6 +1185,7 @@ Select.defaultProps = {
optionComponent: Option,
pageSize: 5,
placeholder: 'Select...',
+ removeSelected: true,
required: false,
rtl: false,
scrollMenuIntoView: true,
diff --git a/src/utils/defaultMenuRenderer.js b/src/utils/defaultMenuRenderer.js
index 9c1d23d6a7..c9b269e05b 100644
--- a/src/utils/defaultMenuRenderer.js
+++ b/src/utils/defaultMenuRenderer.js
@@ -18,7 +18,7 @@ function menuRenderer ({
let Option = optionComponent;
return options.map((option, i) => {
- let isSelected = valueArray && valueArray.indexOf(option) > -1;
+ let isSelected = valueArray && valueArray.some(x => x[valueKey] == option[valueKey]);
let isFocused = option === focusedOption;
let optionClass = classNames(optionClassName, {
'Select-option': true,
diff --git a/test/Select-test.js b/test/Select-test.js
index 9312ff4283..cf1d8e8d2f 100644
--- a/test/Select-test.js
+++ b/test/Select-test.js
@@ -2041,6 +2041,110 @@ describe('Select', () => {
});
+ describe('with removeSelected=false', () => {
+ beforeEach(() => {
+ options = [
+ { value: 'one', label: 'One' },
+ { value: 'two', label: 'Two' },
+ { value: 'three', label: 'Three' },
+ { value: 'four', label: 'Four' }
+ ];
+
+ // Render an instance of the component
+ wrapper = createControlWithWrapper({
+ value: '',
+ options: options,
+ multi: true,
+ closeOnSelect: false,
+ removeSelected: false
+ }, {
+ wireUpOnChangeToValue: true
+ });
+
+ // We need a hack here.
+ // JSDOM (at least v3.x) doesn't appear to support div's with tabindex
+ // This just hacks that we are focused
+ // This is (obviously) implementation dependent, and may need to change
+ instance.setState({
+ isFocused: true
+ });
+ });
+
+ it('does not remove the selected options from the menu', () => {
+
+ clickArrowToOpen();
+
+ var items = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option');
+
+ // Click the option "Two" to select it
+ expect(items[1], 'to have text', 'Two');
+ TestUtils.Simulate.mouseDown(items[1]);
+ expect(onChange, 'was called times', 1);
+
+ // Now get the list again
+ items = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option');
+ expect(items[0], 'to have text', 'One');
+ expect(items[1], 'to have text', 'Two');
+ expect(items[2], 'to have text', 'Three');
+ expect(items[3], 'to have text', 'Four');
+ expect(items, 'to have length', 4);
+
+ // Click first item, 'One'
+ TestUtils.Simulate.mouseDown(items[0]);
+ expect(onChange, 'was called times', 2);
+
+ items = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option');
+ expect(items[0], 'to have text', 'One');
+ expect(items[1], 'to have text', 'Two');
+ expect(items[2], 'to have text', 'Three');
+ expect(items[3], 'to have text', 'Four');
+ expect(items, 'to have length', 4);
+
+ // Click last item, 'Four'
+ TestUtils.Simulate.mouseDown(items[3]);
+ expect(onChange, 'was called times', 3);
+
+ items = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option');
+ expect(items[0], 'to have text', 'One');
+ expect(items[1], 'to have text', 'Two');
+ expect(items[2], 'to have text', 'Three');
+ expect(items[3], 'to have text', 'Four');
+ expect(items, 'to have length', 4);
+
+ expect(onChange.args, 'to equal', [
+ [[{ value: 'two', label: 'Two' }]],
+ [[{ value: 'two', label: 'Two' }, { value: 'one', label: 'One' }]],
+ [
+ [
+ { value: 'two', label: 'Two' },
+ { value: 'one', label: 'One' },
+ { value: 'four', label: 'Four' },
+ ],
+ ],
+ ]);
+ });
+
+ it('removes a selected value if chosen again', () => {
+
+ clickArrowToOpen();
+
+ var items = ReactDOM.findDOMNode(instance).querySelectorAll('.Select-option');
+
+ // Click the option "Two" to select it
+ TestUtils.Simulate.mouseDown(items[1]);
+ expect(onChange, 'was called times', 1);
+
+ // Click the option "Two" again to deselect it
+ TestUtils.Simulate.mouseDown(items[1]);
+ expect(onChange, 'was called times', 2);
+
+ expect(onChange.args, 'to equal', [
+ [[{ value: 'two', label: 'Two' }]],
+ [[]],
+ ]);
+ });
+ });
+
describe('with props', () => {
describe('className', () => {