react-typeahead
Advanced tools
Comparing version
@@ -23,2 +23,3 @@ 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; }; | ||
maxVisible: React.PropTypes.number, | ||
resultsTruncatedMessage: React.PropTypes.string, | ||
options: React.PropTypes.array, | ||
@@ -68,3 +69,4 @@ allowCustomValues: React.PropTypes.number, | ||
customListComponent: TypeaheadSelector, | ||
showOptionsWhenEmpty: false | ||
showOptionsWhenEmpty: false, | ||
resultsTruncatedMessage: null | ||
}; | ||
@@ -75,4 +77,4 @@ }, | ||
return { | ||
// The currently visible set of options | ||
visible: this.getOptionsForValue(this.props.initialValue, this.props.options), | ||
// The options matching the entry value | ||
searchResults: this.getOptionsForValue(this.props.initialValue, this.props.options), | ||
@@ -86,3 +88,7 @@ // This should be called something else, "entryValue" | ||
// Index of the selection | ||
selectionIndex: null | ||
selectionIndex: null, | ||
// Keep track of the focus state of the input element, to determine | ||
// whether to show options when empty (if showOptionsWhenEmpty is true) | ||
isFocused: false | ||
}; | ||
@@ -93,3 +99,7 @@ }, | ||
var emptyValue = !input || input.trim().length == 0; | ||
return !this.props.showOptionsWhenEmpty && emptyValue; | ||
// this.state must be checked because it may not be defined yet if this function | ||
// is called from within getInitialState | ||
var isFocused = this.state && this.state.isFocused; | ||
return !(this.props.showOptionsWhenEmpty && isFocused) && emptyValue; | ||
}, | ||
@@ -103,7 +113,3 @@ | ||
var filterOptions = this._generateFilterFunction(); | ||
var result = filterOptions(value, options); | ||
if (this.props.maxVisible) { | ||
result = result.slice(0, this.props.maxVisible); | ||
} | ||
return result; | ||
return filterOptions(value, options); | ||
}, | ||
@@ -121,3 +127,3 @@ | ||
_hasCustomValue: function () { | ||
if (this.props.allowCustomValues > 0 && this.state.entryValue.length >= this.props.allowCustomValues && this.state.visible.indexOf(this.state.entryValue) < 0) { | ||
if (this.props.allowCustomValues > 0 && this.state.entryValue.length >= this.props.allowCustomValues && this.state.searchResults.indexOf(this.state.entryValue) < 0) { | ||
return true; | ||
@@ -147,3 +153,5 @@ } | ||
return React.createElement(this.props.customListComponent, { | ||
ref: 'sel', options: this.state.visible, | ||
ref: 'sel', options: this.props.maxVisible ? this.state.searchResults.slice(0, this.props.maxVisible) : this.state.searchResults, | ||
areResultsTruncated: this.props.maxVisible && this.state.searchResults.length > this.props.maxVisible, | ||
resultsTruncatedMessage: this.props.resultsTruncatedMessage, | ||
onOptionSelected: this._onOptionSelected, | ||
@@ -167,3 +175,3 @@ allowCustomValues: this.props.allowCustomValues, | ||
} | ||
return this.state.visible[index]; | ||
return this.state.searchResults[index]; | ||
}, | ||
@@ -182,3 +190,3 @@ | ||
nEntry.value = optionString; | ||
this.setState({ visible: this.getOptionsForValue(optionString, this.props.options), | ||
this.setState({ searchResults: this.getOptionsForValue(optionString, this.props.options), | ||
selection: formInputOptionString, | ||
@@ -191,3 +199,3 @@ entryValue: optionString }); | ||
var value = this.refs.entry.value; | ||
this.setState({ visible: this.getOptionsForValue(value, this.props.options), | ||
this.setState({ searchResults: this.getOptionsForValue(value, this.props.options), | ||
selection: '', | ||
@@ -213,3 +221,3 @@ entryValue: value }); | ||
var selection = this.getSelection(); | ||
var option = selection ? selection : this.state.visible.length > 0 ? this.state.visible[0] : null; | ||
var option = selection ? selection : this.state.searchResults.length > 0 ? this.state.searchResults[0] : null; | ||
@@ -242,3 +250,3 @@ if (option === null && this._hasCustomValue()) { | ||
var newIndex = this.state.selectionIndex === null ? delta == 1 ? 0 : delta : this.state.selectionIndex + delta; | ||
var length = this.state.visible.length; | ||
var length = this.props.maxVisible ? this.state.searchResults.slice(0, this.props.maxVisible).length : this.state.searchResults.length; | ||
if (this._hasCustomValue()) { | ||
@@ -294,3 +302,3 @@ length += 1; | ||
this.setState({ | ||
visible: this.getOptionsForValue(this.state.entryValue, nextProps.options) | ||
searchResults: this.getOptionsForValue(this.state.entryValue, nextProps.options) | ||
}); | ||
@@ -325,4 +333,4 @@ }, | ||
onKeyUp: this.props.onKeyUp, | ||
onFocus: this.props.onFocus, | ||
onBlur: this.props.onBlur | ||
onFocus: this._onFocus, | ||
onBlur: this._onBlur | ||
})), | ||
@@ -333,2 +341,20 @@ this._renderIncrementalSearchResults() | ||
_onFocus: function (event) { | ||
this.setState({ isFocused: true }, function () { | ||
this._onTextEntryUpdated(); | ||
}.bind(this)); | ||
if (this.props.onFocus) { | ||
return this.props.onFocus(event); | ||
} | ||
}, | ||
_onBlur: function (event) { | ||
this.setState({ isFocused: false }, function () { | ||
this._onTextEntryUpdated(); | ||
}.bind(this)); | ||
if (this.props.onBlur) { | ||
return this.props.onBlur(event); | ||
} | ||
}, | ||
_renderHiddenInput: function () { | ||
@@ -370,3 +396,3 @@ if (!this.props.name) { | ||
_hasHint: function () { | ||
return this.state.visible.length > 0 || this._hasCustomValue(); | ||
return this.state.searchResults.length > 0 || this._hasCustomValue(); | ||
} | ||
@@ -373,0 +399,0 @@ }); |
@@ -20,3 +20,5 @@ var React = require('react'); | ||
displayOption: React.PropTypes.func.isRequired, | ||
defaultClassNames: React.PropTypes.bool | ||
defaultClassNames: React.PropTypes.bool, | ||
areResultsTruncated: React.PropTypes.bool, | ||
resultsTruncatedMessage: React.PropTypes.string | ||
}, | ||
@@ -76,2 +78,16 @@ | ||
if (this.props.areResultsTruncated && this.props.resultsTruncatedMessage !== null) { | ||
var resultsTruncatedClasses = { | ||
"results-truncated": this.props.defaultClassNames | ||
}; | ||
resultsTruncatedClasses[this.props.customClasses.resultsTruncated] = this.props.customClasses.resultsTruncated; | ||
var resultsTruncatedClassList = classNames(resultsTruncatedClasses); | ||
results.push(React.createElement( | ||
'li', | ||
{ key: 'results-truncated', className: resultsTruncatedClassList }, | ||
this.props.resultsTruncatedMessage | ||
)); | ||
} | ||
return React.createElement( | ||
@@ -78,0 +94,0 @@ 'ul', |
{ | ||
"name": "react-typeahead", | ||
"version": "2.0.0-alpha.2", | ||
"version": "2.0.0-alpha.3", | ||
"description": "React-based typeahead and typeahead-tokenizer", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -83,6 +83,12 @@ # react-typeahead | ||
#### props.resultsTruncatedMessage | ||
Type: `String` | ||
If `maxVisible` is set, display this custom message at the bottom of the list of results when the result are truncated. | ||
#### props.customClasses | ||
Type: `Object` | ||
Allowed Keys: `input`, `results`, `listItem`, `listAnchor`, `hover` | ||
Allowed Keys: `input`, `results`, `listItem`, `listAnchor`, `hover`, `resultsTruncated` | ||
@@ -89,0 +95,0 @@ An object containing custom class names for child elements. Useful for |
@@ -19,2 +19,3 @@ var Accessor = require('../accessor'); | ||
maxVisible: React.PropTypes.number, | ||
resultsTruncatedMessage: React.PropTypes.string, | ||
options: React.PropTypes.array, | ||
@@ -76,3 +77,4 @@ allowCustomValues: React.PropTypes.number, | ||
customListComponent: TypeaheadSelector, | ||
showOptionsWhenEmpty: false | ||
showOptionsWhenEmpty: false, | ||
resultsTruncatedMessage: null | ||
}; | ||
@@ -83,4 +85,4 @@ }, | ||
return { | ||
// The currently visible set of options | ||
visible: this.getOptionsForValue(this.props.initialValue, this.props.options), | ||
// The options matching the entry value | ||
searchResults: this.getOptionsForValue(this.props.initialValue, this.props.options), | ||
@@ -94,3 +96,7 @@ // This should be called something else, "entryValue" | ||
// Index of the selection | ||
selectionIndex: null | ||
selectionIndex: null, | ||
// Keep track of the focus state of the input element, to determine | ||
// whether to show options when empty (if showOptionsWhenEmpty is true) | ||
isFocused: false, | ||
}; | ||
@@ -101,3 +107,7 @@ }, | ||
var emptyValue = !input || input.trim().length == 0; | ||
return !this.props.showOptionsWhenEmpty && emptyValue; | ||
// this.state must be checked because it may not be defined yet if this function | ||
// is called from within getInitialState | ||
var isFocused = this.state && this.state.isFocused; | ||
return !(this.props.showOptionsWhenEmpty && isFocused) && emptyValue; | ||
}, | ||
@@ -109,7 +119,3 @@ | ||
var filterOptions = this._generateFilterFunction(); | ||
var result = filterOptions(value, options); | ||
if (this.props.maxVisible) { | ||
result = result.slice(0, this.props.maxVisible); | ||
} | ||
return result; | ||
return filterOptions(value, options); | ||
}, | ||
@@ -129,3 +135,3 @@ | ||
this.state.entryValue.length >= this.props.allowCustomValues && | ||
this.state.visible.indexOf(this.state.entryValue) < 0) { | ||
this.state.searchResults.indexOf(this.state.entryValue) < 0) { | ||
return true; | ||
@@ -156,3 +162,5 @@ } | ||
<this.props.customListComponent | ||
ref="sel" options={this.state.visible} | ||
ref="sel" options={this.props.maxVisible ? this.state.searchResults.slice(0, this.props.maxVisible) : this.state.searchResults} | ||
areResultsTruncated={this.props.maxVisible && this.state.searchResults.length > this.props.maxVisible} | ||
resultsTruncatedMessage={this.props.resultsTruncatedMessage} | ||
onOptionSelected={this._onOptionSelected} | ||
@@ -177,3 +185,3 @@ allowCustomValues={this.props.allowCustomValues} | ||
} | ||
return this.state.visible[index]; | ||
return this.state.searchResults[index]; | ||
}, | ||
@@ -192,3 +200,3 @@ | ||
nEntry.value = optionString; | ||
this.setState({visible: this.getOptionsForValue(optionString, this.props.options), | ||
this.setState({searchResults: this.getOptionsForValue(optionString, this.props.options), | ||
selection: formInputOptionString, | ||
@@ -201,3 +209,3 @@ entryValue: optionString}); | ||
var value = this.refs.entry.value; | ||
this.setState({visible: this.getOptionsForValue(value, this.props.options), | ||
this.setState({searchResults: this.getOptionsForValue(value, this.props.options), | ||
selection: '', | ||
@@ -224,3 +232,3 @@ entryValue: value}); | ||
var option = selection ? | ||
selection : (this.state.visible.length > 0 ? this.state.visible[0] : null); | ||
selection : (this.state.searchResults.length > 0 ? this.state.searchResults[0] : null); | ||
@@ -253,3 +261,3 @@ if (option === null && this._hasCustomValue()) { | ||
var newIndex = this.state.selectionIndex === null ? (delta == 1 ? 0 : delta) : this.state.selectionIndex + delta; | ||
var length = this.state.visible.length; | ||
var length = this.props.maxVisible ? this.state.searchResults.slice(0, this.props.maxVisible).length : this.state.searchResults.length; | ||
if (this._hasCustomValue()) { | ||
@@ -305,3 +313,3 @@ length += 1; | ||
this.setState({ | ||
visible: this.getOptionsForValue(this.state.entryValue, nextProps.options) | ||
searchResults: this.getOptionsForValue(this.state.entryValue, nextProps.options) | ||
}); | ||
@@ -335,4 +343,4 @@ }, | ||
onKeyUp={this.props.onKeyUp} | ||
onFocus={this.props.onFocus} | ||
onBlur={this.props.onBlur} | ||
onFocus={this._onFocus} | ||
onBlur={this._onBlur} | ||
/> | ||
@@ -344,2 +352,20 @@ { this._renderIncrementalSearchResults() } | ||
_onFocus: function(event) { | ||
this.setState({isFocused: true}, function () { | ||
this._onTextEntryUpdated(); | ||
}.bind(this)); | ||
if ( this.props.onFocus ) { | ||
return this.props.onFocus(event); | ||
} | ||
}, | ||
_onBlur: function(event) { | ||
this.setState({isFocused: false}, function () { | ||
this._onTextEntryUpdated(); | ||
}.bind(this)); | ||
if ( this.props.onBlur ) { | ||
return this.props.onBlur(event); | ||
} | ||
}, | ||
_renderHiddenInput: function() { | ||
@@ -381,3 +407,3 @@ if (!this.props.name) { | ||
_hasHint: function() { | ||
return this.state.visible.length > 0 || this._hasCustomValue(); | ||
return this.state.searchResults.length > 0 || this._hasCustomValue(); | ||
} | ||
@@ -384,0 +410,0 @@ }); |
@@ -18,3 +18,5 @@ var React = require('react'); | ||
displayOption: React.PropTypes.func.isRequired, | ||
defaultClassNames: React.PropTypes.bool | ||
defaultClassNames: React.PropTypes.bool, | ||
areResultsTruncated: React.PropTypes.bool, | ||
resultsTruncatedMessage: React.PropTypes.string | ||
}, | ||
@@ -74,2 +76,16 @@ | ||
if (this.props.areResultsTruncated && this.props.resultsTruncatedMessage !== null) { | ||
var resultsTruncatedClasses = { | ||
"results-truncated": this.props.defaultClassNames | ||
}; | ||
resultsTruncatedClasses[this.props.customClasses.resultsTruncated] = this.props.customClasses.resultsTruncated; | ||
var resultsTruncatedClassList = classNames(resultsTruncatedClasses); | ||
results.push( | ||
<li key="results-truncated" className={resultsTruncatedClassList}> | ||
{this.props.resultsTruncatedMessage} | ||
</li> | ||
); | ||
} | ||
return ( | ||
@@ -76,0 +92,0 @@ <ul className={classList}> |
@@ -167,2 +167,12 @@ var _ = require('lodash'); | ||
}); | ||
it('limits the result set based on the maxVisible option, and shows resultsTruncatedMessage when specified', function() { | ||
var component = TestUtils.renderIntoDocument(<Typeahead | ||
options={ BEATLES } | ||
maxVisible={ 1 } | ||
resultsTruncatedMessage='Results truncated' | ||
></Typeahead>); | ||
var results = simulateTextInput(component, 'o'); | ||
assert.equal(TestUtils.findRenderedDOMComponentWithClass(component, 'results-truncated').textContent, 'Results truncated'); | ||
}); | ||
}); | ||
@@ -596,3 +606,3 @@ | ||
it('render options when value is empty when set to true', function() { | ||
it('do not render options when value is empty when set to true and not focused', function() { | ||
var component = TestUtils.renderIntoDocument( | ||
@@ -606,2 +616,15 @@ <Typeahead | ||
var results = TestUtils.scryRenderedComponentsWithType(component, TypeaheadOption); | ||
assert.equal(0, results.length); | ||
}); | ||
it('render options when value is empty when set to true and focused', function() { | ||
var component = TestUtils.renderIntoDocument( | ||
<Typeahead | ||
options={ BEATLES } | ||
showOptionsWhenEmpty={ true } | ||
/> | ||
); | ||
TestUtils.Simulate.focus(component.refs.entry); | ||
var results = TestUtils.scryRenderedComponentsWithType(component, TypeaheadOption); | ||
assert.equal(4, results.length); | ||
@@ -608,0 +631,0 @@ }); |
1578353
0.31%41784
0.22%418
1.46%