Skip to content

Commit

Permalink
Merge JedWatson/react-select#1213 into react-select-plus
Browse files Browse the repository at this point in the history
  • Loading branch information
Trevor Burnham committed Oct 25, 2016
2 parents 9eedd5f + 8362f8b commit fb9da64
Show file tree
Hide file tree
Showing 19 changed files with 1,339 additions and 646 deletions.
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ It decorates a `Select` and so it supports all of the default properties (eg sin
The easiest way to use it is like so:

```js
import { Creatable } from 'react-select';
import { Creatable } from 'react-select-plus';

function render (selectProps) {
return <Creatable {...selectProps} />;
Expand All @@ -217,6 +217,24 @@ Property | Type | Description
`shouldKeyDownEventCreateNewOption` | function | Decides if a keyDown event (eg its `keyCode`) should result in the creation of a new option. ENTER, TAB and comma keys create new options by dfeault. Expected signature: `({ keyCode: number }): boolean` |
`promptTextCreator` | function | Factory for overriding default option creator prompt label. By default it will read 'Create option "{label}"'. Expected signature: `(label: String): String` |

### Combining Async and Creatable

Use the `AsyncCreatable` HOC if you want both _async_ and _creatable_ functionality.
It ties `Async` and `Creatable` components together and supports a union of their properties (listed above).
Use it as follows:

```jsx
import React from 'react';
import { AsyncCreatable } from 'react-select-plus';

function render (props) {
// props can be a mix of Async, Creatable, and Select properties
return (
<AsyncCreatable {...props} />
);
}
```

### Filtering options

You can control how options are filtered with the following properties:
Expand Down
156 changes: 112 additions & 44 deletions dist/react-select-plus.js

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions dist/react-select-plus.min.js

Large diffs are not rendered by default.

28 changes: 26 additions & 2 deletions examples/dist/app.js

Large diffs are not rendered by default.

153 changes: 110 additions & 43 deletions examples/dist/bundle.js

Large diffs are not rendered by default.

1,105 changes: 661 additions & 444 deletions examples/dist/common.js

Large diffs are not rendered by default.

156 changes: 112 additions & 44 deletions examples/dist/standalone.js

Large diffs are not rendered by default.

17 changes: 16 additions & 1 deletion examples/src/components/GithubUsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,20 @@ const GithubUsers = React.createClass({
gotoUser (value, event) {
window.open(value.html_url);
},
toggleCreatable () {
this.setState({
creatable: !this.state.creatable
});
},
render () {
const AsyncComponent = this.state.creatable
? Select.AsyncCreatable
: Select.Async;

return (
<div className="section">
<h3 className="section-heading">{this.props.label}</h3>
<Select.Async multi={this.state.multi} value={this.state.value} onChange={this.onChange} onValueClick={this.gotoUser} valueKey="id" labelKey="login" loadOptions={this.getUsers} minimumInput={1} backspaceRemoves={false} />
<AsyncComponent multi={this.state.multi} value={this.state.value} onChange={this.onChange} onValueClick={this.gotoUser} valueKey="id" labelKey="login" loadOptions={this.getUsers} minimumInput={1} backspaceRemoves={false} />
<div className="checkbox-list">
<label className="checkbox">
<input type="radio" className="checkbox-control" checked={this.state.multi} onChange={this.switchToMulti}/>
Expand All @@ -55,6 +64,12 @@ const GithubUsers = React.createClass({
<span className="checkbox-label">Single Value</span>
</label>
</div>
<div className="checkbox-list">
<label className="checkbox">
<input type="checkbox" className="checkbox-control" checked={this.state.creatable} onChange={this.toggleCreatable} />
<span className="checkbox-label">Creatable?</span>
</label>
</div>
<div className="hint">This example uses fetch.js for showing Async options with Promises</div>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion lib/Async.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ var Async = _react2['default'].createClass({
return this.resetState();
}
var cacheResult = getFromCache(this.state.cache, input);
if (cacheResult) {
if (cacheResult && Array.isArray(cacheResult.options)) {
return this.setState({
options: cacheResult.options
});
Expand Down
46 changes: 46 additions & 0 deletions lib/AsyncCreatable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

var _Select = require('./Select');

var _Select2 = _interopRequireDefault(_Select);

var AsyncCreatable = _react2['default'].createClass({
displayName: 'AsyncCreatableSelect',

render: function render() {
var _this = this;

return _react2['default'].createElement(
_Select2['default'].Async,
this.props,
function (asyncProps) {
return _react2['default'].createElement(
_Select2['default'].Creatable,
_this.props,
function (creatableProps) {
return _react2['default'].createElement(_Select2['default'], _extends({}, asyncProps, creatableProps, {
onInputChange: function (input) {
creatableProps.onInputChange(input);
return asyncProps.onInputChange(input);
},
ref: function (ref) {
creatableProps.ref(ref);
asyncProps.ref(ref);
}
}));
}
);
}
);
}
});

module.exports = AsyncCreatable;
72 changes: 43 additions & 29 deletions lib/Creatable.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ var Creatable = _react2['default'].createClass({
// ({ label: string, labelKey: string, valueKey: string }): Object
newOptionCreator: _react2['default'].PropTypes.func,

// See Select.propTypes.options
options: _react2['default'].PropTypes.array,

// Creates prompt/placeholder option text.
// (filterText: string): string
promptTextCreator: _react2['default'].PropTypes.func,
Expand Down Expand Up @@ -83,16 +86,12 @@ var Creatable = _react2['default'].createClass({
var _props = this.props;
var isValidNewOption = _props.isValidNewOption;
var newOptionCreator = _props.newOptionCreator;
var _props$options = _props.options;
var options = _props$options === undefined ? [] : _props$options;
var shouldKeyDownEventCreateNewOption = _props.shouldKeyDownEventCreateNewOption;
var _select$props = this.select.props;
var labelKey = _select$props.labelKey;
var options = _select$props.options;
var valueKey = _select$props.valueKey;

var inputValue = this.select.getInputValue();

if (isValidNewOption({ label: inputValue })) {
var option = newOptionCreator({ label: inputValue, labelKey: labelKey, valueKey: valueKey });
if (isValidNewOption({ label: this.inputValue })) {
var option = newOptionCreator({ label: this.inputValue, labelKey: this.labelKey, valueKey: this.valueKey });
var _isOptionUnique = this.isOptionUnique({ option: option });

// Don't add the same option twice.
Expand All @@ -108,29 +107,40 @@ var Creatable = _react2['default'].createClass({
var _props2 = this.props;
var filterOptions = _props2.filterOptions;
var isValidNewOption = _props2.isValidNewOption;
var options = _props2.options;
var promptTextCreator = _props2.promptTextCreator;

var filteredOptions = filterOptions.apply(undefined, arguments);
// TRICKY Check currently selected options as well.
// Don't display a create-prompt for a value that's selected.
// This covers async edge-cases where a newly-created Option isn't yet in the async-loaded array.
var excludeOptions = arguments[2] || [];

var inputValue = this.select ? this.select.getInputValue() : '';
var filteredOptions = filterOptions.apply(undefined, arguments) || [];

if (isValidNewOption({ label: inputValue })) {
if (isValidNewOption({ label: this.inputValue })) {
var _newOptionCreator = this.props.newOptionCreator;
var _select$props2 = this.select.props;
var labelKey = _select$props2.labelKey;
var options = _select$props2.options;
var valueKey = _select$props2.valueKey;

var option = _newOptionCreator({ label: inputValue, labelKey: labelKey, valueKey: valueKey });
var option = _newOptionCreator({
label: this.inputValue,
labelKey: this.labelKey,
valueKey: this.valueKey
});

// TRICKY Compare to all options (not just filtered options) in case option has already been selected).
// For multi-selects, this would remove it from the filtered list.
var _isOptionUnique2 = this.isOptionUnique({ option: option, options: options });
var _isOptionUnique2 = this.isOptionUnique({
option: option,
options: excludeOptions.concat(filteredOptions)
});

if (_isOptionUnique2) {
var _prompt = promptTextCreator(inputValue);
var _prompt = promptTextCreator(this.inputValue);

this._createPlaceholderOption = _newOptionCreator({ label: _prompt, labelKey: labelKey, valueKey: valueKey });
this._createPlaceholderOption = _newOptionCreator({
label: _prompt,
labelKey: this.labelKey,
valueKey: this.valueKey
});

filteredOptions.unshift(this._createPlaceholderOption);
}
Expand All @@ -142,23 +152,15 @@ var Creatable = _react2['default'].createClass({
isOptionUnique: function isOptionUnique(_ref2) {
var option = _ref2.option;
var options = _ref2.options;

if (!this.select) {
return false;
}

var isOptionUnique = this.props.isOptionUnique;
var _select$props3 = this.select.props;
var labelKey = _select$props3.labelKey;
var valueKey = _select$props3.valueKey;

options = options || this.select.filterFlatOptions();

return isOptionUnique({
labelKey: labelKey,
labelKey: this.labelKey,
option: option,
options: options,
valueKey: valueKey
valueKey: this.valueKey
});
},

Expand All @@ -170,6 +172,11 @@ var Creatable = _react2['default'].createClass({
}));
},

onInputChange: function onInputChange(input) {
// This value may be needed in between Select mounts (when this.select is null)
this.inputValue = input;
},

onInputKeyDown: function onInputKeyDown(event) {
var shouldKeyDownEventCreateNewOption = this.props.shouldKeyDownEventCreateNewOption;

Expand Down Expand Up @@ -206,9 +213,16 @@ var Creatable = _react2['default'].createClass({
allowCreate: true,
filterOptions: this.filterOptions,
menuRenderer: this.menuRenderer,
onInputChange: this.onInputChange,
onInputKeyDown: this.onInputKeyDown,
ref: function ref(_ref) {
_this.select = _ref;

// These values may be needed in between Select mounts (when this.select is null)
if (_ref) {
_this.labelKey = _ref.props.labelKey;
_this.valueKey = _ref.props.valueKey;
}
}
});

Expand Down
10 changes: 7 additions & 3 deletions lib/Select.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,18 @@ var _Async = require('./Async');

var _Async2 = _interopRequireDefault(_Async);

var _Dropdown = require('./Dropdown');
var _AsyncCreatable = require('./AsyncCreatable');

var _Dropdown2 = _interopRequireDefault(_Dropdown);
var _AsyncCreatable2 = _interopRequireDefault(_AsyncCreatable);

var _Creatable = require('./Creatable');

var _Creatable2 = _interopRequireDefault(_Creatable);

var _Dropdown = require('./Dropdown');

var _Dropdown2 = _interopRequireDefault(_Dropdown);

var _Option = require('./Option');

var _Option2 = _interopRequireDefault(_Option);
Expand Down Expand Up @@ -180,7 +184,7 @@ var Select = _react2['default'].createClass({
wrapperStyle: _react2['default'].PropTypes.object },

// optional style to apply to the component wrapper
statics: { Async: _Async2['default'], Creatable: _Creatable2['default'] },
statics: { Async: _Async2['default'], AsyncCreatable: _AsyncCreatable2['default'], Creatable: _Creatable2['default'] },

getDefaultProps: function getDefaultProps() {
return {
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"gulp": "^3.9.1",
"isomorphic-fetch": "^2.2.1",
"istanbul": "^0.4.5",
"jsdom": "^9.4.2",
"jsdom": "^9.5.0",
"mocha": "^3.0.2",
"react": "^15.0",
"react-addons-shallow-compare": "^15.0",
Expand All @@ -33,12 +33,12 @@
"react-dom": "^15.0",
"react-gravatar": "^2.4.5",
"react-highlight-words": "^0.3.0",
"react-virtualized": "^7.22.1",
"react-virtualized-select": "^1.3.0",
"react-virtualized": "^7.23.0",
"react-virtualized-select": "^1.4.0",
"sinon": "^1.17.5",
"unexpected": "^10.16.0",
"unexpected": "^10.17.0",
"unexpected-dom": "^3.1.0",
"unexpected-react": "^3.2.3",
"unexpected-react": "^3.2.4",
"unexpected-sinon": "^10.4.0"
},
"peerDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion src/Async.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ const Async = React.createClass({
return this.resetState();
}
let cacheResult = getFromCache(this.state.cache, input);
if (cacheResult) {
if (cacheResult && Array.isArray(cacheResult.options)) {
return this.setState({
options: cacheResult.options,
});
Expand Down
33 changes: 33 additions & 0 deletions src/AsyncCreatable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import Select from './Select';

const AsyncCreatable = React.createClass({
displayName: 'AsyncCreatableSelect',

render () {
return (
<Select.Async {...this.props}>
{(asyncProps) => (
<Select.Creatable {...this.props}>
{(creatableProps) => (
<Select
{...asyncProps}
{...creatableProps}
onInputChange={(input) => {
creatableProps.onInputChange(input);
return asyncProps.onInputChange(input);
}}
ref={(ref) => {
creatableProps.ref(ref);
asyncProps.ref(ref);
}}
/>
)}
</Select.Creatable>
)}
</Select.Async>
);
}
});

module.exports = AsyncCreatable;
Loading

0 comments on commit fb9da64

Please # to comment.