Skip to content

Commit

Permalink
Adding closeOnSelect prop, closes JedWatson#1194
Browse files Browse the repository at this point in the history
  • Loading branch information
JedWatson committed Sep 12, 2017
1 parent 80a9a34 commit c7fe6f4
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 49 deletions.
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

* added; new `closeOnSelect` prop (defaults to `true`) that controls whether the menu is closed when an option is selected, thanks to [Michael Elgar](https://github.com/melgar) for the original idea
* changed; by default, the menu for multi-selects now closes when an option is selected
* fixed; `Async` component always called `onChange` even when it wasn't provided
* fixed; input lag for the `Async` component when results are returned from cache

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ function onInputKeyDown(event) {
| clearAllText | string | 'Clear all' | title for the "clear" control when `multi` is true |
| clearRenderer | func | undefined | Renders a custom clear to be shown in the right-hand side of the select when clearable true: `clearRenderer()` |
| clearValueText | string | 'Clear value' | title for the "clear" control |
| resetValue | any | null | value to use when you clear the control |
| closeOnSelect | bool | true | whether to close the menu when a value is selected
| deleteRemoves | bool | true | whether pressing delete key removes the last item when there is no input value |
| delimiter | string | ',' | delimiter to use to join multiple values |
| disabled | bool | false | whether the Select is disabled or not |
Expand Down Expand Up @@ -396,6 +396,7 @@ function onInputKeyDown(event) {
| options | array | undefined | array of options |
| placeholder | string\|node | 'Select ...' | field placeholder, displayed when there's no value |
| required | bool | false | applies HTML5 required attribute when needed |
| resetValue | any | null | value to set when the control is cleared |
| scrollMenuIntoView | bool | true | whether the viewport will shift to display the entire menu when engaged |
| searchable | bool | true | whether to enable searching feature or not |
| searchPromptText | string\|node | 'Type to search' | label to prompt for search input |
Expand Down
32 changes: 21 additions & 11 deletions examples/src/components/Multiselect.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,39 +25,49 @@ var MultiSelectField = createClass({
return {
disabled: false,
crazy: false,
options: FLAVOURS,
stayOpen: false,
value: [],
};
},
handleSelectChange (value) {
console.log('You\'ve selected:', value);
this.setState({ value });
},
toggleDisabled (e) {
this.setState({ disabled: e.target.checked });
},
toggleChocolate (e) {
let crazy = e.target.checked;
toggleCheckbox (e) {
this.setState({
crazy: crazy,
options: crazy ? WHY_WOULD_YOU : FLAVOURS,
[e.target.name]: e.target.checked,
});
},
render () {
const { crazy, disabled, stayOpen, value } = this.state;
const options = crazy ? WHY_WOULD_YOU : FLAVOURS;
return (
<div className="section">
<h3 className="section-heading">{this.props.label}</h3>
<Select multi simpleValue disabled={this.state.disabled} value={this.state.value} placeholder="Select your favourite(s)" options={this.state.options} onChange={this.handleSelectChange} />
<Select
closeOnSelect={!stayOpen}
disabled={disabled}
multi
onChange={this.handleSelectChange}
options={options}
placeholder="Select your favourite(s)"
simpleValue
value={value}
/>

<div className="checkbox-list">
<label className="checkbox">
<input type="checkbox" className="checkbox-control" checked={this.state.disabled} onChange={this.toggleDisabled} />
<input type="checkbox" className="checkbox-control" name="disabled" checked={disabled} onChange={this.toggleCheckbox} />
<span className="checkbox-label">Disable the control</span>
</label>
<label className="checkbox">
<input type="checkbox" className="checkbox-control" checked={this.state.crazy} onChange={this.toggleChocolate} />
<input type="checkbox" className="checkbox-control" name="crazy" checked={crazy} onChange={this.toggleCheckbox} />
<span className="checkbox-label">I don't like Chocolate (disabled the option)</span>
</label>
<label className="checkbox">
<input type="checkbox" className="checkbox-control" name="stayOpen" checked={stayOpen} onChange={this.toggleCheckbox}/>
<span className="checkbox-label">Stay open when an Option is selected</span>
</label>
</div>
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions examples/src/components/States.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ var StatesField = createClass({
console.log('Country changed to ' + newCountry);
this.setState({
country: newCountry,
selectValue: null
selectValue: null,
});
},
updateValue (newValue) {
console.log('State changed to ' + newValue);
this.setState({
selectValue: newValue
selectValue: newValue,
});
},
focusStateSelect () {
Expand Down
77 changes: 42 additions & 35 deletions src/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,44 +17,46 @@ import defaultClearRenderer from './utils/defaultClearRenderer';
import Option from './Option';
import Value from './Value';

const stringifyValue = (value) => typeof value === 'string' ? value : value !== null && JSON.stringify(value) || '';
const stringifyValue = value =>
typeof value === 'string'
? value
: (value !== null && JSON.stringify(value)) || '';

const stringOrNode = PropTypes.oneOfType([
PropTypes.string,
PropTypes.node
PropTypes.node,
]);

let instanceId = 1;

class Select extends React.Component {

constructor(props) {
constructor (props) {
super(props);

[
'clearValue',
'focusOption',
'handleInputBlur',
'handleInputChange',
'handleInputFocus',
'handleInputValueChange',
'handleKeyDown',
'handleMenuScroll',
'handleMouseDown',
'handleMouseDownOnArrow',
'handleMouseDownOnMenu',
'handleRequired',
'handleTouchOutside',
'handleTouchMove',
'handleTouchStart',
'handleTouchEnd',
'handleTouchEndClearValue',
'handleValueClick',
'getOptionLabel',
'onOptionRef',
'removeValue',
'selectValue'
].forEach((fn) => this[fn] = this[fn].bind(this));
[
'clearValue',
'focusOption',
'handleInputBlur',
'handleInputChange',
'handleInputFocus',
'handleInputValueChange',
'handleKeyDown',
'handleMenuScroll',
'handleMouseDown',
'handleMouseDownOnArrow',
'handleMouseDownOnMenu',
'handleRequired',
'handleTouchOutside',
'handleTouchMove',
'handleTouchStart',
'handleTouchEnd',
'handleTouchEndClearValue',
'handleValueClick',
'getOptionLabel',
'onOptionRef',
'removeValue',
'selectValue',
].forEach((fn) => this[fn] = this[fn].bind(this));

this.state = {
inputValue: '',
Expand Down Expand Up @@ -492,7 +494,7 @@ class Select extends React.Component {
}

setValue (value) {
if (this.props.autoBlur){
if (this.props.autoBlur) {
this.blurInput();
}
if (!this.props.onChange) return;
Expand All @@ -507,20 +509,24 @@ class Select extends React.Component {
}

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;
// NOTE: we actually add/set the value in a callback to make sure the
// input value is empty to avoid styling issues in Chrome
if (this.props.closeOnSelect) {
this.hasScrolledToOption = false;
}
if (this.props.multi) {
const updatedValue = this.props.onSelectResetsInput ? '' : this.state.inputValue;
this.setState({
focusedIndex: null,
inputValue: this.handleInputValueChange(updatedValue),
focusedIndex: null
isOpen: !this.props.closeOnSelect,
}, () => {
this.addValue(value);
});
} else {
this.setState({
isOpen: false,
inputValue: this.handleInputValueChange(''),
isOpen: !this.props.closeOnSelect,
isPseudoFocused: this.state.isFocused,
}, () => {
this.setValue(value);
Expand Down Expand Up @@ -805,7 +811,6 @@ class Select extends React.Component {
}

renderClear () {

if (!this.props.clearable || this.props.value === undefined || this.props.value === null || this.props.multi && !this.props.value.length || this.props.disabled || this.props.isLoading) return;
const clear = this.props.clearRenderer();

Expand Down Expand Up @@ -1040,7 +1045,7 @@ Select.propTypes = {
'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)
addLabelText: PropTypes.string, // placeholder displayed when you want to add a label on a multi-value input
addLabelText: PropTypes.string, // placeholder displayed when you want to add a label on a multi-value input
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
Expand All @@ -1052,6 +1057,7 @@ Select.propTypes = {
clearRenderer: PropTypes.func, // create clearable x element
clearValueText: stringOrNode, // title for the "clear" control
clearable: PropTypes.bool, // should it be possible to reset value
closeOnSelect: React.PropTypes.bool, // whether to close the menu when a value is selected
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
Expand Down Expand Up @@ -1120,6 +1126,7 @@ Select.defaultProps = {
clearAllText: 'Clear all',
clearRenderer: defaultClearRenderer,
clearValueText: 'Clear value',
closeOnSelect: true,
deleteRemoves: true,
delimiter: ',',
disabled: false,
Expand Down

0 comments on commit c7fe6f4

Please sign in to comment.