mesosphere-react-typeahead
Advanced tools
Comparing version 0.5.3 to 0.7.2
@@ -13,30 +13,24 @@ 'use strict'; | ||
const NO_INPUT_MESSAGE = 'No text.'; | ||
const Checkbox = props => ( | ||
<div className="checkbox"> | ||
<label> | ||
<input | ||
checked={props.checked} | ||
disabled={props.disabled} | ||
name={props.name} | ||
onChange={props.onChange} | ||
type="checkbox" | ||
/> | ||
{props.children || props.label} | ||
</label> | ||
</div> | ||
); | ||
const Checkbox = (props) => { | ||
return ( | ||
<div className="checkbox"> | ||
<label> | ||
<input | ||
checked={props.checked} | ||
disabled={props.disabled} | ||
name={props.name} | ||
onChange={props.onChange} | ||
type="checkbox" | ||
/> | ||
{props.children || props.label} | ||
</label> | ||
</div> | ||
); | ||
}; | ||
const ExampleSection = props => ( | ||
<div className="example-section"> | ||
<h4>{props.title}</h4> | ||
{props.children} | ||
</div> | ||
); | ||
const ExampleSection = (props) => { | ||
return ( | ||
<div className="example-section"> | ||
<h4>{props.title}</h4> | ||
{props.children} | ||
</div> | ||
); | ||
}; | ||
const Example = React.createClass({ | ||
@@ -52,2 +46,3 @@ | ||
largeDataSet: false, | ||
minLength: 0, | ||
multiple: false, | ||
@@ -68,2 +63,3 @@ preSelected: false, | ||
largeDataSet, | ||
minLength, | ||
multiple, | ||
@@ -81,3 +77,3 @@ preSelected, | ||
let bigData = range(0, 2000).map((option) => ({name: option.toString()})); | ||
let bigData = range(0, 2000).map(option => ({name: option.toString()})); | ||
@@ -96,6 +92,8 @@ return ( | ||
labelKey="name" | ||
onChange={(selected) => this.setState({selected})} | ||
onInputChange={(text) => this.setState({text})} | ||
minLength={minLength} | ||
onChange={selected => this.setState({selected})} | ||
onInputChange={text => this.setState({text})} | ||
options={largeDataSet ? bigData : states} | ||
placeholder="Choose a state..." | ||
ref="typeahead" | ||
selected={selected} | ||
@@ -148,2 +146,8 @@ /> | ||
</Checkbox> | ||
<Checkbox | ||
checked={!!minLength} | ||
name="minLength" | ||
onChange={this._handleChange}> | ||
Require minimum text input before showing results | ||
</Checkbox> | ||
<button | ||
@@ -160,3 +164,3 @@ className="btn btn-default" | ||
<ExampleSection title="Input Text"> | ||
{text || <div className="text-muted">{NO_INPUT_MESSAGE}</div>} | ||
{text || <div className="text-muted">No text.</div>} | ||
</ExampleSection> | ||
@@ -179,3 +183,3 @@ </div> | ||
return selected && selected.length ? | ||
selected.map((option) => option.name).join(', ') : | ||
selected.map(option => option.name).join(', ') : | ||
<div className="text-muted">No items selected.</div>; | ||
@@ -190,3 +194,3 @@ }, | ||
disabled={!alignMenu} | ||
onChange={(e) => this.setState({align: e.target.value})} | ||
onChange={e => this.setState({align: e.target.value})} | ||
value={align}> | ||
@@ -219,2 +223,5 @@ <option value="justify">Justify (default)</option> | ||
break; | ||
case 'minLength': | ||
newState.minLength = checked ? 1 : 0; | ||
break; | ||
case 'multiple': | ||
@@ -231,6 +238,3 @@ let newSelection = this.state.selected.slice(); | ||
_handleClear(e) { | ||
this.setState({ | ||
selected: [], | ||
text: this.NO_INPUT_MESSAGE, | ||
}); | ||
this.refs.typeahead.getInstance().clear(); | ||
}, | ||
@@ -237,0 +241,0 @@ }); |
@@ -21,5 +21,5 @@ 'use strict'; | ||
var _decorator = require('react-onclickoutside/decorator'); | ||
var _reactOnclickoutside = require('react-onclickoutside'); | ||
var _decorator2 = _interopRequireDefault(_decorator); | ||
var _reactOnclickoutside2 = _interopRequireDefault(_reactOnclickoutside); | ||
@@ -131,2 +131,2 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
exports.default = (0, _decorator2.default)(Token); | ||
exports.default = (0, _reactOnclickoutside2.default)(Token); |
@@ -7,4 +7,2 @@ '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; }; | ||
var _reactInputAutosize = require('react-input-autosize'); | ||
@@ -44,5 +42,2 @@ | ||
labelKey: _react.PropTypes.string, | ||
/** | ||
* Input element placeholder text. | ||
*/ | ||
placeholder: _react.PropTypes.string, | ||
@@ -54,3 +49,3 @@ selected: _react.PropTypes.array | ||
return { | ||
focused: false | ||
isFocused: false | ||
}; | ||
@@ -71,3 +66,3 @@ }, | ||
{ | ||
className: (0, _classnames2.default)('bootstrap-tokenizer', 'clearfix', 'form-control', { 'focus': this.state.focused }), | ||
className: (0, _classnames2.default)('bootstrap-tokenizer', 'clearfix', 'form-control', { 'focus': this.state.isFocused }), | ||
disabled: disabled, | ||
@@ -82,4 +77,5 @@ onClick: this._handleInputFocus, | ||
selected.map(this._renderToken), | ||
_react2.default.createElement(_reactInputAutosize2.default, _extends({}, this.props, { | ||
_react2.default.createElement(_reactInputAutosize2.default, { | ||
className: 'bootstrap-tokenizer-input', | ||
disabled: disabled, | ||
inputStyle: { | ||
@@ -94,2 +90,4 @@ backgroundColor: 'inherit', | ||
onBlur: this._handleBlur, | ||
onChange: this._handleChange, | ||
onFocus: this.props.onFocus, | ||
onKeyDown: this._handleKeydown, | ||
@@ -102,3 +100,3 @@ placeholder: selected.length ? null : placeholder, | ||
value: text | ||
})) | ||
}) | ||
); | ||
@@ -110,3 +108,3 @@ }, | ||
var labelKey = _props2.labelKey; | ||
var onRemove = _props2.onRemove; | ||
var _onRemove = _props2.onRemove; | ||
@@ -119,3 +117,5 @@ | ||
key: idx, | ||
onRemove: onRemove.bind(null, option) }, | ||
onRemove: function onRemove() { | ||
return _onRemove(option); | ||
} }, | ||
option[labelKey] | ||
@@ -125,5 +125,8 @@ ); | ||
_handleBlur: function _handleBlur(e) { | ||
this.setState({ focused: false }); | ||
this.props.onBlur && this.props.onBlur(e); | ||
this.setState({ isFocused: false }); | ||
this.props.onBlur(e); | ||
}, | ||
_handleChange: function _handleChange(e) { | ||
this.props.onChange(e.target.value); | ||
}, | ||
_handleKeydown: function _handleKeydown(e) { | ||
@@ -142,3 +145,3 @@ switch (e.keyCode) { | ||
this.props.onKeyDown && this.props.onKeyDown(e); | ||
this.props.onKeyDown(e); | ||
}, | ||
@@ -145,0 +148,0 @@ _handleInputFocus: function _handleInputFocus(e) { |
@@ -7,2 +7,4 @@ '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; }; | ||
var _classnames = require('classnames'); | ||
@@ -32,5 +34,5 @@ | ||
var _decorator = require('react-onclickoutside/decorator'); | ||
var _reactOnclickoutside = require('react-onclickoutside'); | ||
var _decorator2 = _interopRequireDefault(_decorator); | ||
var _reactOnclickoutside2 = _interopRequireDefault(_reactOnclickoutside); | ||
@@ -83,2 +85,6 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
/** | ||
* Number of input characters that must be entered before showing results. | ||
*/ | ||
minLength: _react.PropTypes.number, | ||
/** | ||
* Whether or not multiple selections are allowed. | ||
@@ -140,2 +146,6 @@ */ | ||
labelKey: 'label', | ||
onBlur: _lodash.noop, | ||
onChange: _lodash.noop, | ||
onInputChange: _lodash.noop, | ||
minLength: 0, | ||
multiple: false, | ||
@@ -148,4 +158,2 @@ selected: [] | ||
var defaultSelected = _props.defaultSelected; | ||
var labelKey = _props.labelKey; | ||
var multiple = _props.multiple; | ||
var selected = _props.selected; | ||
@@ -160,10 +168,9 @@ | ||
return { | ||
activeIndex: 0, | ||
activeIndex: -1, | ||
selected: selected, | ||
showMenu: false, | ||
text: this._getText(labelKey, multiple, selected) | ||
text: '' | ||
}; | ||
}, | ||
componentWillReceiveProps: function componentWillReceiveProps(nextProps) { | ||
var labelKey = nextProps.labelKey; | ||
var multiple = nextProps.multiple; | ||
@@ -173,12 +180,9 @@ var selected = nextProps.selected; | ||
if (!(0, _lodash.isEqual)(this.props.selected, selected)) { | ||
if (!(0, _lodash.isEqual)(selected, this.props.selected)) { | ||
// If new selections are passed in via props, treat the component as a | ||
// controlled input. | ||
this.setState({ | ||
selected: selected, | ||
text: this._getText(labelKey, multiple, selected) | ||
}); | ||
this.setState({ selected: selected }); | ||
} | ||
if (this.props.multiple !== multiple) { | ||
if (multiple !== this.props.multiple) { | ||
this.setState({ text: '' }); | ||
@@ -188,21 +192,62 @@ } | ||
render: function render() { | ||
var _this = this; | ||
var filteredOptions = this._getFilteredOptions(); | ||
return _react2.default.createElement( | ||
'div', | ||
{ | ||
className: 'bootstrap-typeahead open', | ||
style: { position: 'relative' } }, | ||
this._renderInput(filteredOptions), | ||
this._renderMenu(filteredOptions) | ||
); | ||
}, | ||
/** | ||
* Public method to allow external clearing of the input. Clears both text | ||
* and selection(s). | ||
*/ | ||
clear: function clear() { | ||
var _getInitialState = this.getInitialState(); | ||
var activeIndex = _getInitialState.activeIndex; | ||
var showMenu = _getInitialState.showMenu; | ||
var selected = []; | ||
var text = ''; | ||
this.setState({ | ||
activeIndex: activeIndex, | ||
selected: selected, | ||
showMenu: showMenu, | ||
text: text | ||
}); | ||
this.props.onChange(selected); | ||
this.props.onInputChange(text); | ||
}, | ||
/** | ||
* Filter out options that don't match the input string or, if multiple | ||
* selections are allowed, that have already been selected. | ||
*/ | ||
_getFilteredOptions: function _getFilteredOptions() { | ||
var _props2 = this.props; | ||
var allowNew = _props2.allowNew; | ||
var labelKey = _props2.labelKey; | ||
var minLength = _props2.minLength; | ||
var multiple = _props2.multiple; | ||
var options = _props2.options; | ||
var _state = this.state; | ||
var activeIndex = _state.activeIndex; | ||
var selected = _state.selected; | ||
var showMenu = _state.showMenu; | ||
var text = _state.text; | ||
// Filter out options that don't match the input string or, if multiple | ||
// selections are allowed, that have already been selected. | ||
if (text.length < minLength) { | ||
return []; | ||
} | ||
var filteredOptions = options.filter(function (option) { | ||
var labelString = option[labelKey]; | ||
if (!labelString || typeof labelString !== 'string') { | ||
@@ -224,67 +269,68 @@ throw new Error('One or more options does not have a valid label string. Please ' + 'check the `labelKey` prop to ensure that it matches the correct ' + 'option key and provides a string for filtering and display.'); | ||
var InputComponent = _TokenizerInput2.default; | ||
var inputText = text; | ||
var selectedItems = selected.slice(); | ||
return filteredOptions; | ||
}, | ||
_renderInput: function _renderInput(filteredOptions) { | ||
var _this = this; | ||
if (!multiple) { | ||
InputComponent = _TypeaheadInput2.default; | ||
selectedItems = (0, _lodash.head)(selectedItems); | ||
inputText = selectedItems && selectedItems[labelKey] || text; | ||
} | ||
var _props3 = this.props; | ||
var labelKey = _props3.labelKey; | ||
var multiple = _props3.multiple; | ||
var _state2 = this.state; | ||
var activeIndex = _state2.activeIndex; | ||
var selected = _state2.selected; | ||
var text = _state2.text; | ||
var menu = void 0; | ||
if (showMenu) { | ||
menu = _react2.default.createElement( | ||
'div', | ||
{ className: (0, _classnames2.default)(this.props.typeaheadMenuWrapperClassName) }, | ||
_react2.default.createElement(_TypeaheadMenu2.default, { | ||
activeIndex: activeIndex, | ||
align: this.props.align, | ||
className: this.props.typeaheadMenuClassName, | ||
emptyLabel: this.props.emptyLabel, | ||
initialResultCount: this.props.paginateResults, | ||
labelKey: labelKey, | ||
maxHeight: this.props.maxHeight, | ||
newSelectionPrefix: this.props.newSelectionPrefix, | ||
onClick: this._handleAddOption, | ||
options: filteredOptions, | ||
renderMenuItemChildren: this.props.renderMenuItemChildren, | ||
text: inputText | ||
}) | ||
); | ||
var Input = multiple ? _TokenizerInput2.default : _TypeaheadInput2.default; | ||
var inputProps = (0, _lodash.pick)(this.props, ['disabled', 'placeholder']); | ||
return _react2.default.createElement(Input, _extends({}, inputProps, { | ||
activeIndex: activeIndex, | ||
labelKey: labelKey, | ||
onAdd: this._handleAddOption, | ||
onBlur: this._handleBlur, | ||
onChange: this._handleTextChange, | ||
onFocus: this._handleFocus, | ||
onKeyDown: function onKeyDown(e) { | ||
return _this._handleKeydown(filteredOptions, e); | ||
}, | ||
onRemove: this._handleRemoveOption, | ||
options: filteredOptions, | ||
selected: selected.slice(), | ||
text: text | ||
})); | ||
}, | ||
_renderMenu: function _renderMenu(filteredOptions) { | ||
var _props4 = this.props; | ||
var labelKey = _props4.labelKey; | ||
var minLength = _props4.minLength; | ||
var _state3 = this.state; | ||
var activeIndex = _state3.activeIndex; | ||
var showMenu = _state3.showMenu; | ||
var text = _state3.text; | ||
if (!(showMenu && text.length >= minLength)) { | ||
return null; | ||
} | ||
var menuProps = (0, _lodash.pick)(this.props, ['align', 'emptyLabel', 'maxHeight', 'newSelectionPrefix', 'renderMenuItemChildren']); | ||
return _react2.default.createElement( | ||
'div', | ||
{ | ||
className: 'bootstrap-typeahead open', | ||
style: { position: 'relative' } }, | ||
_react2.default.createElement(InputComponent, { | ||
disabled: this.props.disabled, | ||
filteredOptions: filteredOptions, | ||
{ className: (0, _classnames2.default)(this.props.typeaheadMenuWrapperClassName) }, | ||
_react2.default.createElement(_TypeaheadMenu2.default, _extends({}, menuProps, { | ||
activeIndex: activeIndex, | ||
className: this.props.typeaheadMenuClassName, | ||
initialResultCount: this.props.paginateResults, | ||
labelKey: labelKey, | ||
onAdd: this._handleAddOption, | ||
onBlur: this.props.onBlur, | ||
onChange: this._handleTextChange, | ||
onFocus: this._handleFocus, | ||
onKeyDown: this._handleKeydown.bind(null, filteredOptions), | ||
onRemove: this._handleRemoveOption, | ||
placeholder: this.props.placeholder, | ||
ref: function ref(_ref) { | ||
return _this.inputComponent = _ref; | ||
}, | ||
selected: selectedItems, | ||
text: inputText | ||
}), | ||
menu | ||
onClick: this._handleAddOption, | ||
options: filteredOptions, | ||
text: text | ||
})) | ||
); | ||
}, | ||
_getText: function _getText(labelKey, multiple, selected) { | ||
var selectedText = !(0, _lodash.isEmpty)(selected) && (0, _lodash.head)(selected)[labelKey]; | ||
if (!multiple && selectedText) { | ||
return selectedText; | ||
} | ||
return ''; | ||
_handleBlur: function _handleBlur(e) { | ||
// Note: Don't hide the menu here, since that interferes with other actions | ||
// like making a selection by clicking on a menu item. | ||
this.props.onBlur(e); | ||
}, | ||
@@ -294,14 +340,9 @@ _handleFocus: function _handleFocus() { | ||
}, | ||
_handleTextChange: function _handleTextChange(e) { | ||
var text = e.target.value; | ||
_handleTextChange: function _handleTextChange(text) { | ||
var _getInitialState2 = this.getInitialState(); | ||
// Clear any selections when text is entered. | ||
var selected = this.state.selected; | ||
var activeIndex = _getInitialState2.activeIndex; | ||
if (!this.props.multiple && !(0, _lodash.isEmpty)(selected)) { | ||
this._handleRemoveOption((0, _lodash.head)(selected)); | ||
} | ||
this.setState({ | ||
activeIndex: 0, | ||
activeIndex: activeIndex, | ||
showMenu: true, | ||
@@ -311,3 +352,3 @@ text: text | ||
this.props.onInputChange && this.props.onInputChange(text); | ||
this.props.onInputChange(text); | ||
}, | ||
@@ -332,3 +373,7 @@ _handleKeydown: function _handleKeydown(options, e) { | ||
// If we've reached the end, go back to the beginning or vice-versa. | ||
activeIndex = (activeIndex + options.length) % options.length; | ||
if (activeIndex === options.length) { | ||
activeIndex = -1; | ||
} else if (activeIndex === -2) { | ||
activeIndex = options.length - 1; | ||
} | ||
@@ -352,7 +397,7 @@ this.setState({ activeIndex: activeIndex }); | ||
_handleAddOption: function _handleAddOption(selectedOption) { | ||
var _props3 = this.props; | ||
var multiple = _props3.multiple; | ||
var labelKey = _props3.labelKey; | ||
var onChange = _props3.onChange; | ||
var onInputChange = _props3.onInputChange; | ||
var _props5 = this.props; | ||
var multiple = _props5.multiple; | ||
var labelKey = _props5.labelKey; | ||
var onChange = _props5.onChange; | ||
var onInputChange = _props5.onInputChange; | ||
@@ -375,11 +420,7 @@ | ||
this.setState({ | ||
activeIndex: 0, | ||
selected: selected, | ||
showMenu: false, | ||
text: text | ||
}); | ||
this.setState({ selected: selected, text: text }); | ||
this._hideDropdown(); | ||
onChange && onChange(selected); | ||
onInputChange && onInputChange(text); | ||
onChange(selected); | ||
onInputChange(text); | ||
}, | ||
@@ -392,9 +433,6 @@ _handleRemoveOption: function _handleRemoveOption(removedOption) { | ||
this.setState({ | ||
activeIndex: 0, | ||
selected: selected, | ||
showMenu: false | ||
}); | ||
this.setState({ selected: selected }); | ||
this._hideDropdown(); | ||
this.props.onChange && this.props.onChange(selected); | ||
this.props.onChange(selected); | ||
}, | ||
@@ -410,5 +448,10 @@ | ||
_hideDropdown: function _hideDropdown() { | ||
var _getInitialState3 = this.getInitialState(); | ||
var activeIndex = _getInitialState3.activeIndex; | ||
var showMenu = _getInitialState3.showMenu; | ||
this.setState({ | ||
activeIndex: 0, | ||
showMenu: false | ||
activeIndex: activeIndex, | ||
showMenu: showMenu | ||
}); | ||
@@ -418,2 +461,2 @@ } | ||
exports.default = (0, _decorator2.default)(Typeahead); | ||
exports.default = (0, _reactOnclickoutside2.default)(Typeahead); |
@@ -33,7 +33,9 @@ 'use strict'; | ||
disabled: _react.PropTypes.bool, | ||
filteredOptions: _react.PropTypes.array, | ||
labelKey: _react.PropTypes.string, | ||
onBlur: _react.PropTypes.func, | ||
onChange: _react.PropTypes.func, | ||
selected: _react.PropTypes.object, | ||
onFocus: _react.PropTypes.func, | ||
options: _react.PropTypes.array, | ||
placeholder: _react.PropTypes.string, | ||
selected: _react.PropTypes.array, | ||
text: _react.PropTypes.string | ||
@@ -47,2 +49,8 @@ }, | ||
}, | ||
componentDidUpdate: function componentDidUpdate(prevProps, prevState) { | ||
var inputText = this._getInputText(); | ||
if (this.input) { | ||
this.input.selectionStart = inputText.length; | ||
} | ||
}, | ||
render: function render() { | ||
@@ -55,4 +63,4 @@ var _this = this; | ||
var selected = _props.selected; | ||
var text = _props.text; | ||
var inputProps = (0, _lodash.pick)(this.props, ['disabled', 'onFocus', 'placeholder']); | ||
@@ -67,7 +75,8 @@ return _react2.default.createElement( | ||
tabIndex: -1 }, | ||
_react2.default.createElement('input', _extends({}, this.props, { | ||
_react2.default.createElement('input', _extends({}, inputProps, { | ||
className: (0, _classnames2.default)('bootstrap-typeahead-input-main', 'form-control', { | ||
'has-selection': !selected | ||
'has-selection': !!selected.length | ||
}), | ||
onBlur: this._handleBlur, | ||
onChange: this._handleChange, | ||
onKeyDown: this._handleKeydown, | ||
@@ -84,3 +93,3 @@ ref: function ref(_ref) { | ||
type: 'text', | ||
value: text | ||
value: this._getInputText() | ||
})), | ||
@@ -94,2 +103,3 @@ _react2.default.createElement('input', { | ||
display: 'block', | ||
opacity: 0.6, | ||
position: 'absolute', | ||
@@ -108,17 +118,26 @@ top: 0, | ||
var _props2 = this.props; | ||
var filteredOptions = _props2.filteredOptions; | ||
var activeIndex = _props2.activeIndex; | ||
var options = _props2.options; | ||
var labelKey = _props2.labelKey; | ||
var selected = _props2.selected; | ||
var text = _props2.text; | ||
var firstOption = (0, _lodash.head)(filteredOptions); | ||
var firstOption = (0, _lodash.head)(options); | ||
var firstOptionString = firstOption && firstOption[labelKey]; | ||
// Only show the hint if... | ||
// Only show the hint if: | ||
if ( | ||
// ...the input is focused. | ||
// The input is focused. | ||
this.state.isFocused && | ||
// ...the input contains text. | ||
// The input contains text. | ||
text && | ||
// ...the input text corresponds to the beginning of the first option. | ||
firstOption && firstOption[labelKey].indexOf(text) === 0) { | ||
return firstOption[labelKey]; | ||
// None of the menu options are focused. | ||
activeIndex === -1 && | ||
// There are no current selections. | ||
!selected.length && | ||
// The input text corresponds to the beginning of the first option. | ||
firstOptionString && firstOptionString.toLowerCase().indexOf(text.toLowerCase()) === 0) { | ||
// Text matching is case-insensitive, so to display the hint correctly, | ||
// splice the input text with the rest of the actual string. | ||
return text + firstOptionString.slice(text.length, firstOptionString.length); | ||
} | ||
@@ -128,8 +147,38 @@ | ||
}, | ||
_getInputText: function _getInputText() { | ||
var _props3 = this.props; | ||
var activeIndex = _props3.activeIndex; | ||
var labelKey = _props3.labelKey; | ||
var options = _props3.options; | ||
var selected = _props3.selected; | ||
var text = _props3.text; | ||
var selectedItem = !!selected.length && (0, _lodash.head)(selected); | ||
if (selectedItem) { | ||
return selectedItem[labelKey]; | ||
} | ||
if (activeIndex >= 0) { | ||
return options[activeIndex][labelKey]; | ||
} | ||
return text; | ||
}, | ||
_handleBlur: function _handleBlur(e) { | ||
this.setState({ isFocused: false }); | ||
this.props.onBlur && this.props.onBlur(e); | ||
this.props.onBlur(e); | ||
}, | ||
_handleChange: function _handleChange(e) { | ||
// Clear any selections when text is entered. | ||
var _props4 = this.props; | ||
var onRemove = _props4.onRemove; | ||
var selected = _props4.selected; | ||
!!selected.length && onRemove((0, _lodash.head)(selected)); | ||
this.props.onChange(e.target.value); | ||
}, | ||
/** | ||
@@ -140,10 +189,11 @@ * If the containing parent div is focused or clicked, focus the input. | ||
this.setState({ isFocused: true }); | ||
this.input.focus(); | ||
this.input && this.input.focus(); | ||
}, | ||
_handleKeydown: function _handleKeydown(e) { | ||
var _props3 = this.props; | ||
var filteredOptions = _props3.filteredOptions; | ||
var onAdd = _props3.onAdd; | ||
var onRemove = _props3.onRemove; | ||
var selected = _props3.selected; | ||
var _props5 = this.props; | ||
var activeIndex = _props5.activeIndex; | ||
var options = _props5.options; | ||
var onAdd = _props5.onAdd; | ||
var selected = _props5.selected; | ||
var text = _props5.text; | ||
@@ -153,14 +203,25 @@ | ||
case _keyCode.RIGHT: | ||
// Autocomplete the selection if there's a hint and no selection yet. | ||
if (this._getHintText() && !selected) { | ||
onAdd && onAdd((0, _lodash.head)(filteredOptions)); | ||
case _keyCode.TAB: | ||
var cursorPos = this.input && this.input.selectionStart; | ||
var hasHintText = !!this._getHintText(); | ||
// Autocomplete the selection if all of the following are true: | ||
if ( | ||
// There's a hint or a menu item is highlighted. | ||
(hasHintText || activeIndex !== -1) && | ||
// There's no current selection. | ||
!selected.length && | ||
// The input cursor is at the end of the text string when the user | ||
// hits the right arrow key. | ||
!(e.keyCode === _keyCode.RIGHT && cursorPos !== text.length)) { | ||
e.preventDefault(); | ||
var selectedOption = hasHintText ? (0, _lodash.head)(options) : options[activeIndex]; | ||
onAdd && onAdd(selectedOption); | ||
} | ||
break; | ||
case _keyCode.BACKSPACE: | ||
// Remove the selection if we start deleting it. | ||
selected && onRemove && onRemove(selected); | ||
break; | ||
} | ||
this.props.onKeyDown && this.props.onKeyDown(e); | ||
this.props.onKeyDown(e); | ||
} | ||
@@ -167,0 +228,0 @@ }); |
@@ -43,2 +43,9 @@ 'use strict'; | ||
render: function render() { | ||
var _props = this.props; | ||
var active = _props.active; | ||
var children = _props.children; | ||
var className = _props.className; | ||
var disabled = _props.disabled; | ||
return _react2.default.createElement( | ||
@@ -48,7 +55,7 @@ 'li', | ||
className: (0, _classnames2.default)({ | ||
'active': this.props.active, | ||
'disabled': this.props.disabled | ||
}, this.props.className), | ||
'active': active, | ||
'disabled': disabled | ||
}, className), | ||
onClick: this._handleClick }, | ||
this.props.children | ||
children | ||
); | ||
@@ -98,6 +105,6 @@ }, | ||
render: function render() { | ||
var _props = this.props; | ||
var align = _props.align; | ||
var maxHeight = _props.maxHeight; | ||
var options = _props.options; | ||
var _props2 = this.props; | ||
var align = _props2.align; | ||
var maxHeight = _props2.maxHeight; | ||
var options = _props2.options; | ||
@@ -146,9 +153,9 @@ // Render the max number of results or all results. | ||
_renderMenuItem: function _renderMenuItem(option, idx) { | ||
var _props2 = this.props; | ||
var activeIndex = _props2.activeIndex; | ||
var labelKey = _props2.labelKey; | ||
var newSelectionPrefix = _props2.newSelectionPrefix; | ||
var onClick = _props2.onClick; | ||
var renderMenuItemChildren = _props2.renderMenuItemChildren; | ||
var text = _props2.text; | ||
var _props3 = this.props; | ||
var activeIndex = _props3.activeIndex; | ||
var labelKey = _props3.labelKey; | ||
var newSelectionPrefix = _props3.newSelectionPrefix; | ||
var _onClick = _props3.onClick; | ||
var renderMenuItemChildren = _props3.renderMenuItemChildren; | ||
var text = _props3.text; | ||
@@ -159,3 +166,5 @@ | ||
key: idx, | ||
onClick: onClick.bind(null, option) | ||
onClick: function onClick() { | ||
return _onClick(option); | ||
} | ||
}; | ||
@@ -162,0 +171,0 @@ |
{ | ||
"name": "mesosphere-react-typeahead", | ||
"version": "0.5.3", | ||
"version": "0.7.2", | ||
"description": "React-based typeahead component", | ||
@@ -32,9 +32,9 @@ "main": "lib/index.js", | ||
"lodash": "^4.12.0", | ||
"react-highlighter": "^0.3.1", | ||
"react-input-autosize": "^0.6.2", | ||
"react-onclickoutside": "^4.1.1" | ||
"react-highlighter": "^0.3.3", | ||
"react-input-autosize": "^1.1.0", | ||
"react-onclickoutside": "^5.3.0" | ||
}, | ||
"peerDependencies": { | ||
"react": "^0.14.0 || ^15.0.0", | ||
"react-dom": "^0.14.0 || ^15.0.0" | ||
"react": "^0.14.0 || ^15.2.0", | ||
"react-dom": "^0.14.0 || ^15.2.0" | ||
}, | ||
@@ -41,0 +41,0 @@ "devDependencies": { |
# React Bootstrap Typeahead | ||
React-based typeahead component that uses Bootstrap as a base for styles and behaviors and supports both single- and multi-selection. Try a [live example](http://ericgio.github.io/react-bootstrap-typeahead/). | ||
React-based typeahead component that uses Bootstrap as a base for styles and behaviors and supports both single- and multi-selection. The UI and behaviors are inspired by Twitter's [typeahead.js](https://github.com/twitter/typeahead.js). Try a [live example](http://ericgio.github.io/react-bootstrap-typeahead/). | ||
@@ -86,12 +86,18 @@ [![build status](https://img.shields.io/travis/ericgio/react-bootstrap-typeahead/master.svg?style=flat-square)](https://travis-ci.org/ericgio/react-bootstrap-typeahead) | ||
``` | ||
## Public Methods | ||
## CSS | ||
The component tries to use as little CSS as possible, relying primarily on Bootstrap or any Bootstrap themes for styling. Some minimal styling is included in `Typeahead.css` and `Token.css` and should ideally be included wherever you're using the component. | ||
### `clear()` | ||
The `clear` method provides an easy way to externally reset the input. Calling the method will clear both text and selection(s). To use the method, add a ref to your typeahead instance: | ||
``` | ||
<Typeahead ref="typeahead" ... /> | ||
``` | ||
You can then access the ref from a handler function: | ||
``` | ||
<button onClick={() => this.refs.typeahead.getInstance().clear()}> | ||
Clear Typeahead | ||
</button> | ||
``` | ||
Note that you *must* use `getInstance` to get the typeahead instance. This is because `react-bootstrap-typeahead` is wrapped by the [`react-onclickoutside`](https://github.com/Pomax/react-onclickoutside) higher-order component, so the `clear` method is not directly available. See [`react-onclickoutside`'s documentation](https://github.com/Pomax/react-onclickoutside#but-how-can-i-access-my-component-it-has-an-api-that-i-rely-on) for more. | ||
## Example | ||
An example file is included with the NPM module. Simply open `example/index.html` in a browser. If you're using the repository code, you'll need to run `npm run example` to build the example index file. You can then open the HTML file as described above. You can also try the [live example](http://ericgio.github.io/react-bootstrap-typeahead/). | ||
## Documentation | ||
### Props | ||
## Props | ||
Name | Type | Default | Description | ||
@@ -106,2 +112,3 @@ -----|------|---------|------------ | ||
maxHeight | number | `300` | Maximum height of the dropdown menu, in px. | ||
minLength | number | `0` | Number of input characters that must be entered before showing results. | ||
multiple | boolean | `false` | Whether or not multiple selections are allowed. | ||
@@ -116,1 +123,10 @@ newSelectionPrefix | string | 'New selection:' | Provides the ability to specify a prefix before the user-entered text to indicate that the selection will be new. No-op unless `allowNew={true}`. | ||
selected | array | `[]` | The selected option(s) displayed in the input. Use this prop if you want to control the component via its parent. | ||
## CSS | ||
The component tries to use as little CSS as possible, relying primarily on Bootstrap or any Bootstrap themes for styling. Some minimal styling is included in `Typeahead.css` and `Token.css` and should ideally be included wherever you're using the component. | ||
## Example | ||
An example file is included with the NPM module. Simply open `example/index.html` in a browser. If you're using the repository code, you'll need to run `npm run example` to build the example index file. You can then open the HTML file as described above. You can also try the [live example](http://ericgio.github.io/react-bootstrap-typeahead/). | ||
## License | ||
[MIT](https://github.com/ericgio/react-bootstrap-typeahead/blob/master/LICENSE.md) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
1052537
19758
130
+ Addedreact-input-autosize@1.2.0(transitive)
+ Addedreact-onclickoutside@5.11.1(transitive)
- Removedreact-input-autosize@0.6.13(transitive)
- Removedreact-onclickoutside@4.9.0(transitive)
Updatedreact-highlighter@^0.3.3
Updatedreact-input-autosize@^1.1.0
Updatedreact-onclickoutside@^5.3.0