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)); } )} - + )} - + ); } -}); +}; -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