react-select
Version:
A Select control built with and for ReactJS
215 lines (170 loc) • 5.45 kB
JavaScript
import React from 'react';
import Select from './Select';
import defaultFilterOptions from './utils/defaultFilterOptions';
import defaultMenuRenderer from './utils/defaultMenuRenderer';
const Creatable = React.createClass({
displayName: 'CreatableSelect',
propTypes: {
// See Select.propTypes.filterOptions
filterOptions: React.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: React.PropTypes.func,
// Determines if the current input text represents a valid option.
// ({ label: string }): boolean
isValidNewOption: React.PropTypes.func,
// See Select.propTypes.menuRenderer
menuRenderer: React.PropTypes.any,
// Factory to create new option.
// ({ label: string, labelKey: string, valueKey: string }): Object
newOptionCreator: React.PropTypes.func,
// Creates prompt/placeholder option text.
// (filterText: string): string
promptTextCreator: React.PropTypes.func,
// Decides if a keyDown event (eg its `keyCode`) should result in the creation of a new option.
shouldKeyDownEventCreateNewOption: React.PropTypes.func,
},
// Default prop methods
statics: {
isOptionUnique,
isValidNewOption,
newOptionCreator,
promptTextCreator,
shouldKeyDownEventCreateNewOption
},
getDefaultProps () {
return {
filterOptions: defaultFilterOptions,
isOptionUnique,
isValidNewOption,
menuRenderer: defaultMenuRenderer,
newOptionCreator,
promptTextCreator,
shouldKeyDownEventCreateNewOption,
};
},
createNewOption () {
const { isValidNewOption, newOptionCreator, shouldKeyDownEventCreateNewOption } = this.props;
const { labelKey, options, valueKey } = this.select.props;
const inputValue = this.select.getInputValue();
if (isValidNewOption({ label: inputValue })) {
const option = newOptionCreator({ label: inputValue, labelKey, valueKey });
const isOptionUnique = this.isOptionUnique({ option });
// Don't add the same option twice.
if (isOptionUnique) {
options.unshift(option);
this.select.selectValue(option);
}
}
},
filterOptions (...params) {
const { filterOptions, isValidNewOption, promptTextCreator } = this.props;
const filteredOptions = filterOptions(...params);
const inputValue = this.select
? this.select.getInputValue()
: '';
if (isValidNewOption({ label: inputValue })) {
const { newOptionCreator } = this.props;
const { labelKey, options, valueKey } = this.select.props;
const option = newOptionCreator({ label: inputValue, labelKey, 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.
const isOptionUnique = this.isOptionUnique({ option, options });
if (isOptionUnique) {
const prompt = promptTextCreator(inputValue);
this._createPlaceholderOption = newOptionCreator({ label: prompt, labelKey, valueKey });
filteredOptions.unshift(this._createPlaceholderOption);
}
}
return filteredOptions;
},
isOptionUnique ({
option,
options
}) {
if (!this.select) {
return false;
}
const { isOptionUnique } = this.props;
const { labelKey, valueKey } = this.select.props;
options = options || this.select.filterOptions();
return isOptionUnique({
labelKey,
option,
options,
valueKey
});
},
menuRenderer (params) {
const { menuRenderer } = this.props;
return menuRenderer({
...params,
onSelect: this.onOptionSelect
});
},
onInputKeyDown (event) {
const { shouldKeyDownEventCreateNewOption } = this.props;
const focusedOption = this.select.getFocusedOption();
if (
focusedOption &&
focusedOption === this._createPlaceholderOption &&
shouldKeyDownEventCreateNewOption({ keyCode: event.keyCode })
) {
this.createNewOption();
// Prevent decorated Select from doing anything additional with this keyDown event
event.preventDefault();
}
},
onOptionSelect (option, event) {
if (option === this._createPlaceholderOption) {
this.createNewOption();
} else {
this.select.selectValue(option);
}
},
render () {
const { newOptionCreator, shouldKeyDownEventCreateNewOption, ...restProps } = this.props;
return (
<Select
{...restProps}
allowCreate
filterOptions={this.filterOptions}
menuRenderer={this.menuRenderer}
onInputKeyDown={this.onInputKeyDown}
ref={(ref) => this.select = ref}
/>
);
}
});
function isOptionUnique ({ option, options, labelKey, valueKey }) {
return options
.filter((existingOption) =>
existingOption[labelKey] === option[labelKey] ||
existingOption[valueKey] === option[valueKey]
)
.length === 0;
};
function isValidNewOption ({ label }) {
return !!label;
};
function newOptionCreator ({ label, labelKey, valueKey }) {
const option = {};
option[valueKey] = label;
option[labelKey] = label;
option.className = 'Select-create-option-placeholder';
return option;
};
function promptTextCreator (label) {
return `Create option "${label}"`;
}
function shouldKeyDownEventCreateNewOption ({ keyCode }) {
switch (keyCode) {
case 9: // TAB
case 13: // ENTER
case 188: // COMMA
return true;
}
return false;
};
module.exports = Creatable;