From c7fe6f44763d680313ef7620e3c5373c87b8e319 Mon Sep 17 00:00:00 2001 From: Jed Watson Date: Wed, 13 Sep 2017 02:06:24 +1000 Subject: [PATCH] Adding closeOnSelect prop, closes #1194 --- HISTORY.md | 2 + README.md | 3 +- examples/src/components/Multiselect.js | 32 +++++++---- examples/src/components/States.js | 4 +- src/Select.js | 77 ++++++++++++++------------ 5 files changed, 69 insertions(+), 49 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index f6fe584445..9432bf05a3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -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 diff --git a/README.md b/README.md index 416d2d4e1a..2b73d9dcf8 100644 --- a/README.md +++ b/README.md @@ -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 | @@ -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 | diff --git a/examples/src/components/Multiselect.js b/examples/src/components/Multiselect.js index b6ced49178..23cbea74e9 100644 --- a/examples/src/components/Multiselect.js +++ b/examples/src/components/Multiselect.js @@ -25,7 +25,7 @@ var MultiSelectField = createClass({ return { disabled: false, crazy: false, - options: FLAVOURS, + stayOpen: false, value: [], }; }, @@ -33,31 +33,41 @@ var MultiSelectField = createClass({ 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 (

{this.props.label}

-
+
); diff --git a/examples/src/components/States.js b/examples/src/components/States.js index 4405705f61..8c642a3b9e 100644 --- a/examples/src/components/States.js +++ b/examples/src/components/States.js @@ -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 () { diff --git a/src/Select.js b/src/Select.js index 06c5681228..c8475c0497 100644 --- a/src/Select.js +++ b/src/Select.js @@ -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: '', @@ -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; @@ -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); @@ -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(); @@ -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 @@ -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 @@ -1120,6 +1126,7 @@ Select.defaultProps = { clearAllText: 'Clear all', clearRenderer: defaultClearRenderer, clearValueText: 'Clear value', + closeOnSelect: true, deleteRemoves: true, delimiter: ',', disabled: false,