react-typeahead
Version:
React-based typeahead and typeahead-tokenizer
1,032 lines (894 loc) • 30.9 kB
JavaScript
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.ReactTypeahead=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
/*!
Copyright (c) 2015 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
function classNames() {
var classes = '';
var arg;
for (var i = 0; i < arguments.length; i++) {
arg = arguments[i];
if (!arg) {
continue;
}
if ('string' === typeof arg || 'number' === typeof arg) {
classes += ' ' + arg;
} else if (Object.prototype.toString.call(arg) === '[object Array]') {
classes += ' ' + classNames.apply(null, arg);
} else if ('object' === typeof arg) {
for (var key in arg) {
if (!arg.hasOwnProperty(key) || !arg[key]) {
continue;
}
classes += ' ' + key;
}
}
}
return classes.substr(1);
}
// safely export classNames for node / browserify
if (typeof module !== 'undefined' && module.exports) {
module.exports = classNames;
}
// safely export classNames for RequireJS
if (typeof define !== 'undefined' && define.amd) {
define('classnames', [], function() {
return classNames;
});
}
},{}],2:[function(require,module,exports){
/*
* Fuzzy
* https://github.com/myork/fuzzy
*
* Copyright (c) 2012 Matt York
* Licensed under the MIT license.
*/
(function() {
var root = this;
var fuzzy = {};
// Use in node or in browser
if (typeof exports !== 'undefined') {
module.exports = fuzzy;
} else {
root.fuzzy = fuzzy;
}
// Return all elements of `array` that have a fuzzy
// match against `pattern`.
fuzzy.simpleFilter = function(pattern, array) {
return array.filter(function(string) {
return fuzzy.test(pattern, string);
});
};
// Does `pattern` fuzzy match `string`?
fuzzy.test = function(pattern, string) {
return fuzzy.match(pattern, string) !== null;
};
// If `pattern` matches `string`, wrap each matching character
// in `opts.pre` and `opts.post`. If no match, return null
fuzzy.match = function(pattern, string, opts) {
opts = opts || {};
var patternIdx = 0
, result = []
, len = string.length
, totalScore = 0
, currScore = 0
// prefix
, pre = opts.pre || ''
// suffix
, post = opts.post || ''
// String to compare against. This might be a lowercase version of the
// raw string
, compareString = opts.caseSensitive && string || string.toLowerCase()
, ch, compareChar;
pattern = opts.caseSensitive && pattern || pattern.toLowerCase();
// For each character in the string, either add it to the result
// or wrap in template if its the next string in the pattern
for(var idx = 0; idx < len; idx++) {
ch = string[idx];
if(compareString[idx] === pattern[patternIdx]) {
ch = pre + ch + post;
patternIdx += 1;
// consecutive characters should increase the score more than linearly
currScore += 1 + currScore;
} else {
currScore = 0;
}
totalScore += currScore;
result[result.length] = ch;
}
// return rendered string if we have a match for every char
if(patternIdx === pattern.length) {
return {rendered: result.join(''), score: totalScore};
}
return null;
};
// The normal entry point. Filters `arr` for matches against `pattern`.
// It returns an array with matching values of the type:
//
// [{
// string: '<b>lah' // The rendered string
// , index: 2 // The index of the element in `arr`
// , original: 'blah' // The original element in `arr`
// }]
//
// `opts` is an optional argument bag. Details:
//
// opts = {
// // string to put before a matching character
// pre: '<b>'
//
// // string to put after matching character
// , post: '</b>'
//
// // Optional function. Input is an element from the passed in
// // `arr`, output should be the string to test `pattern` against.
// // In this example, if `arr = [{crying: 'koala'}]` we would return
// // 'koala'.
// , extract: function(arg) { return arg.crying; }
// }
fuzzy.filter = function(pattern, arr, opts) {
opts = opts || {};
return arr
.reduce(function(prev, element, idx, arr) {
var str = element;
if(opts.extract) {
str = opts.extract(element);
}
var rendered = fuzzy.match(pattern, str, opts);
if(rendered != null) {
prev[prev.length] = {
string: rendered.rendered
, score: rendered.score
, index: idx
, original: element
};
}
return prev;
}, [])
// Sort by score. Browsers are inconsistent wrt stable/unstable
// sorting, so force stable by using the index in the case of tie.
// See http://ofb.net/~sethml/is-sort-stable.html
.sort(function(a,b) {
var compare = b.score - a.score;
if(compare) return compare;
return a.index - b.index;
});
};
}());
},{}],3:[function(require,module,exports){
var Accessor = {
IDENTITY_FN: function (input) {
return input;
},
generateAccessor: function (field) {
return function (object) {
return object[field];
};
},
generateOptionToStringFor: function (prop) {
if (typeof prop === 'string') {
return this.generateAccessor(prop);
} else if (typeof prop === 'function') {
return prop;
} else {
return this.IDENTITY_FN;
}
},
valueForOption: function (option, object) {
if (typeof option === 'string') {
return object[option];
} else if (typeof option === 'function') {
return option(object);
} else {
return object;
}
}
};
module.exports = Accessor;
},{}],4:[function(require,module,exports){
/**
* PolyFills make me sad
*/
var KeyEvent = KeyEvent || {};
KeyEvent.DOM_VK_UP = KeyEvent.DOM_VK_UP || 38;
KeyEvent.DOM_VK_DOWN = KeyEvent.DOM_VK_DOWN || 40;
KeyEvent.DOM_VK_BACK_SPACE = KeyEvent.DOM_VK_BACK_SPACE || 8;
KeyEvent.DOM_VK_RETURN = KeyEvent.DOM_VK_RETURN || 13;
KeyEvent.DOM_VK_ENTER = KeyEvent.DOM_VK_ENTER || 14;
KeyEvent.DOM_VK_ESCAPE = KeyEvent.DOM_VK_ESCAPE || 27;
KeyEvent.DOM_VK_TAB = KeyEvent.DOM_VK_TAB || 9;
module.exports = KeyEvent;
},{}],5:[function(require,module,exports){
var Typeahead = require('./typeahead');
var Tokenizer = require('./tokenizer');
module.exports = {
Typeahead: Typeahead,
Tokenizer: Tokenizer
};
},{"./tokenizer":6,"./typeahead":8}],6:[function(require,module,exports){
var Accessor = require('../accessor');
var React = window.React || require('react');
var Token = require('./token');
var KeyEvent = require('../keyevent');
var Typeahead = require('../typeahead');
var classNames = require('classnames');
function _arraysAreDifferent(array1, array2) {
if (array1.length != array2.length) {
return true;
}
for (var i = array2.length - 1; i >= 0; i--) {
if (array2[i] !== array1[i]) {
return true;
}
}
}
/**
* A typeahead that, when an option is selected, instead of simply filling
* the text entry widget, prepends a renderable "token", that may be deleted
* by pressing backspace on the beginning of the line with the keyboard.
*/
var TypeaheadTokenizer = React.createClass({
displayName: 'TypeaheadTokenizer',
propTypes: {
name: React.PropTypes.string,
options: React.PropTypes.array,
customClasses: React.PropTypes.object,
allowCustomValues: React.PropTypes.number,
defaultSelected: React.PropTypes.array,
initialValue: React.PropTypes.string,
placeholder: React.PropTypes.string,
disabled: React.PropTypes.bool,
inputProps: React.PropTypes.object,
onTokenRemove: React.PropTypes.func,
onKeyDown: React.PropTypes.func,
onKeyPress: React.PropTypes.func,
onKeyUp: React.PropTypes.func,
onTokenAdd: React.PropTypes.func,
onFocus: React.PropTypes.func,
onBlur: React.PropTypes.func,
filterOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
displayOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
formInputOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
maxVisible: React.PropTypes.number,
defaultClassNames: React.PropTypes.bool
},
getInitialState: function () {
return {
// We need to copy this to avoid incorrect sharing
// of state across instances (e.g., via getDefaultProps())
selected: this.props.defaultSelected.slice(0)
};
},
getDefaultProps: function () {
return {
options: [],
defaultSelected: [],
customClasses: {},
allowCustomValues: 0,
initialValue: "",
placeholder: "",
disabled: false,
inputProps: {},
defaultClassNames: true,
filterOption: null,
displayOption: function (token) {
return token;
},
formInputOption: null,
onKeyDown: function (event) {},
onKeyPress: function (event) {},
onKeyUp: function (event) {},
onFocus: function (event) {},
onBlur: function (event) {},
onTokenAdd: function () {},
onTokenRemove: function () {}
};
},
componentWillReceiveProps: function (nextProps) {
// if we get new defaultProps, update selected
if (_arraysAreDifferent(this.props.defaultSelected, nextProps.defaultSelected)) {
this.setState({ selected: nextProps.defaultSelected.slice(0) });
}
},
focus: function () {
this.refs.typeahead.focus();
},
getSelectedTokens: function () {
return this.state.selected;
},
// TODO: Support initialized tokens
//
_renderTokens: function () {
var tokenClasses = {};
tokenClasses[this.props.customClasses.token] = !!this.props.customClasses.token;
var classList = classNames(tokenClasses);
var result = this.state.selected.map(function (selected) {
var displayString = Accessor.valueForOption(this.props.displayOption, selected);
var value = Accessor.valueForOption(this.props.formInputOption || this.props.displayOption, selected);
return React.createElement(
Token,
{ key: displayString, className: classList,
onRemove: this._removeTokenForValue,
object: selected,
value: value,
name: this.props.name },
displayString
);
}, this);
return result;
},
_getOptionsForTypeahead: function () {
// return this.props.options without this.selected
return this.props.options;
},
_onKeyDown: function (event) {
// We only care about intercepting backspaces
if (event.keyCode === KeyEvent.DOM_VK_BACK_SPACE) {
return this._handleBackspace(event);
}
this.props.onKeyDown(event);
},
_handleBackspace: function (event) {
// No tokens
if (!this.state.selected.length) {
return;
}
// Remove token ONLY when bksp pressed at beginning of line
// without a selection
var entry = this.refs.typeahead.refs.entry;
if (entry.selectionStart == entry.selectionEnd && entry.selectionStart == 0) {
this._removeTokenForValue(this.state.selected[this.state.selected.length - 1]);
event.preventDefault();
}
},
_removeTokenForValue: function (value) {
var index = this.state.selected.indexOf(value);
if (index == -1) {
return;
}
this.state.selected.splice(index, 1);
this.setState({ selected: this.state.selected });
this.props.onTokenRemove(value);
return;
},
_addTokenForValue: function (value) {
if (this.state.selected.indexOf(value) != -1) {
return;
}
this.state.selected.push(value);
this.setState({ selected: this.state.selected });
this.refs.typeahead.setEntryText("");
this.props.onTokenAdd(value);
},
render: function () {
var classes = {};
classes[this.props.customClasses.typeahead] = !!this.props.customClasses.typeahead;
var classList = classNames(classes);
var tokenizerClasses = [this.props.defaultClassNames && "typeahead-tokenizer"];
tokenizerClasses[this.props.className] = !!this.props.className;
var tokenizerClassList = classNames(tokenizerClasses);
return React.createElement(
'div',
{ className: tokenizerClassList },
this._renderTokens(),
React.createElement(Typeahead, { ref: 'typeahead',
className: classList,
placeholder: this.props.placeholder,
disabled: this.props.disabled,
inputProps: this.props.inputProps,
allowCustomValues: this.props.allowCustomValues,
customClasses: this.props.customClasses,
options: this._getOptionsForTypeahead(),
initialValue: this.props.initialValue,
maxVisible: this.props.maxVisible,
onOptionSelected: this._addTokenForValue,
onKeyDown: this._onKeyDown,
onKeyPress: this.props.onKeyPress,
onKeyUp: this.props.onKeyUp,
onFocus: this.props.onFocus,
onBlur: this.props.onBlur,
displayOption: this.props.displayOption,
defaultClassNames: this.props.defaultClassNames,
filterOption: this.props.filterOption })
);
}
});
module.exports = TypeaheadTokenizer;
},{"../accessor":3,"../keyevent":4,"../typeahead":8,"./token":7,"classnames":1,"react":"react"}],7:[function(require,module,exports){
var React = window.React || require('react');
var classNames = require('classnames');
/**
* Encapsulates the rendering of an option that has been "selected" in a
* TypeaheadTokenizer
*/
var Token = React.createClass({
displayName: 'Token',
propTypes: {
className: React.PropTypes.string,
name: React.PropTypes.string,
children: React.PropTypes.string,
object: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]),
onRemove: React.PropTypes.func,
value: React.PropTypes.string
},
render: function () {
var className = classNames(["typeahead-token", this.props.className]);
return React.createElement(
'div',
{ className: className },
this._renderHiddenInput(),
this.props.children,
this._renderCloseButton()
);
},
_renderHiddenInput: function () {
// If no name was set, don't create a hidden input
if (!this.props.name) {
return null;
}
return React.createElement('input', {
type: 'hidden',
name: this.props.name + '[]',
value: this.props.value || this.props.object
});
},
_renderCloseButton: function () {
if (!this.props.onRemove) {
return "";
}
return React.createElement(
'a',
{ className: 'typeahead-token-close', href: '#', onClick: function (event) {
this.props.onRemove(this.props.object);
event.preventDefault();
}.bind(this) },
'×'
);
}
});
module.exports = Token;
},{"classnames":1,"react":"react"}],8:[function(require,module,exports){
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 Accessor = require('../accessor');
var React = window.React || require('react');
var TypeaheadSelector = require('./selector');
var KeyEvent = require('../keyevent');
var fuzzy = require('fuzzy');
var classNames = require('classnames');
/**
* A "typeahead", an auto-completing text input
*
* Renders an text input that shows options nearby that you can use the
* keyboard or mouse to select. Requires CSS for MASSIVE DAMAGE.
*/
var Typeahead = React.createClass({
displayName: 'Typeahead',
propTypes: {
name: React.PropTypes.string,
customClasses: React.PropTypes.object,
maxVisible: React.PropTypes.number,
options: React.PropTypes.array,
allowCustomValues: React.PropTypes.number,
initialValue: React.PropTypes.string,
value: React.PropTypes.string,
placeholder: React.PropTypes.string,
disabled: React.PropTypes.bool,
textarea: React.PropTypes.bool,
inputProps: React.PropTypes.object,
onOptionSelected: React.PropTypes.func,
onChange: React.PropTypes.func,
onKeyDown: React.PropTypes.func,
onKeyPress: React.PropTypes.func,
onKeyUp: React.PropTypes.func,
onFocus: React.PropTypes.func,
onBlur: React.PropTypes.func,
filterOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
displayOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
formInputOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
defaultClassNames: React.PropTypes.bool,
customListComponent: React.PropTypes.oneOfType([React.PropTypes.element, React.PropTypes.func]),
showOptionsWhenEmpty: React.PropTypes.bool
},
getDefaultProps: function () {
return {
options: [],
customClasses: {},
allowCustomValues: 0,
initialValue: "",
value: "",
placeholder: "",
disabled: false,
textarea: false,
inputProps: {},
onOptionSelected: function (option) {},
onChange: function (event) {},
onKeyDown: function (event) {},
onKeyPress: function (event) {},
onKeyUp: function (event) {},
onFocus: function (event) {},
onBlur: function (event) {},
filterOption: null,
defaultClassNames: true,
customListComponent: TypeaheadSelector,
showOptionsWhenEmpty: false
};
},
getInitialState: function () {
return {
// The currently visible set of options
visible: this.getOptionsForValue(this.props.initialValue, this.props.options),
// This should be called something else, "entryValue"
entryValue: this.props.value || this.props.initialValue,
// A valid typeahead value
selection: this.props.value,
// Index of the selection
selectionIndex: null
};
},
_shouldSkipSearch: function (input) {
var emptyValue = !input || input.trim().length == 0;
return !this.props.showOptionsWhenEmpty && emptyValue;
},
getOptionsForValue: function (value, options) {
if (this._shouldSkipSearch(value)) {
return [];
}
var filterOptions = this._generateFilterFunction();
var result = filterOptions(value, options);
if (this.props.maxVisible) {
result = result.slice(0, this.props.maxVisible);
}
return result;
},
setEntryText: function (value) {
this.refs.entry.value = value;
this._onTextEntryUpdated();
},
focus: function () {
this.refs.entry.focus();
},
_hasCustomValue: function () {
if (this.props.allowCustomValues > 0 && this.state.entryValue.length >= this.props.allowCustomValues && this.state.visible.indexOf(this.state.entryValue) < 0) {
return true;
}
return false;
},
_getCustomValue: function () {
if (this._hasCustomValue()) {
return this.state.entryValue;
}
return null;
},
_renderIncrementalSearchResults: function () {
// Nothing has been entered into the textbox
if (this._shouldSkipSearch(this.state.entryValue)) {
return "";
}
// Something was just selected
if (this.state.selection) {
return "";
}
return React.createElement(this.props.customListComponent, {
ref: 'sel', options: this.state.visible,
onOptionSelected: this._onOptionSelected,
allowCustomValues: this.props.allowCustomValues,
customValue: this._getCustomValue(),
customClasses: this.props.customClasses,
selectionIndex: this.state.selectionIndex,
defaultClassNames: this.props.defaultClassNames,
displayOption: Accessor.generateOptionToStringFor(this.props.displayOption) });
},
getSelection: function () {
var index = this.state.selectionIndex;
if (this._hasCustomValue()) {
if (index === 0) {
return this.state.entryValue;
} else {
index--;
}
}
return this.state.visible[index];
},
_onOptionSelected: function (option, event) {
var nEntry = this.refs.entry;
nEntry.focus();
var displayOption = Accessor.generateOptionToStringFor(this.props.displayOption);
var optionString = displayOption(option, 0);
var formInputOption = Accessor.generateOptionToStringFor(this.props.formInputOption || displayOption);
var formInputOptionString = formInputOption(option);
nEntry.value = optionString;
this.setState({ visible: this.getOptionsForValue(optionString, this.props.options),
selection: formInputOptionString,
entryValue: optionString });
return this.props.onOptionSelected(option, event);
},
_onTextEntryUpdated: function () {
var value = this.refs.entry.value;
this.setState({ visible: this.getOptionsForValue(value, this.props.options),
selection: '',
entryValue: value });
},
_onEnter: function (event) {
var selection = this.getSelection();
if (!selection) {
return this.props.onKeyDown(event);
}
return this._onOptionSelected(selection, event);
},
_onEscape: function () {
this.setState({
selectionIndex: null
});
},
_onTab: function (event) {
var selection = this.getSelection();
var option = selection ? selection : this.state.visible.length > 0 ? this.state.visible[0] : null;
if (option === null && this._hasCustomValue()) {
option = this._getCustomValue();
}
if (option !== null) {
return this._onOptionSelected(option, event);
}
},
eventMap: function (event) {
var events = {};
events[KeyEvent.DOM_VK_UP] = this.navUp;
events[KeyEvent.DOM_VK_DOWN] = this.navDown;
events[KeyEvent.DOM_VK_RETURN] = events[KeyEvent.DOM_VK_ENTER] = this._onEnter;
events[KeyEvent.DOM_VK_ESCAPE] = this._onEscape;
events[KeyEvent.DOM_VK_TAB] = this._onTab;
return events;
},
_nav: function (delta) {
if (!this._hasHint()) {
return;
}
var newIndex = this.state.selectionIndex === null ? delta == 1 ? 0 : delta : this.state.selectionIndex + delta;
var length = this.state.visible.length;
if (this._hasCustomValue()) {
length += 1;
}
if (newIndex < 0) {
newIndex += length;
} else if (newIndex >= length) {
newIndex -= length;
}
this.setState({ selectionIndex: newIndex });
},
navDown: function () {
this._nav(1);
},
navUp: function () {
this._nav(-1);
},
_onChange: function (event) {
if (this.props.onChange) {
this.props.onChange(event);
}
this._onTextEntryUpdated();
},
_onKeyDown: function (event) {
// If there are no visible elements, don't perform selector navigation.
// Just pass this up to the upstream onKeydown handler.
// Also skip if the user is pressing the shift key, since none of our handlers are looking for shift
if (!this._hasHint() || event.shiftKey) {
return this.props.onKeyDown(event);
}
var handler = this.eventMap()[event.keyCode];
if (handler) {
handler(event);
} else {
return this.props.onKeyDown(event);
}
// Don't propagate the keystroke back to the DOM/browser
event.preventDefault();
},
componentWillReceiveProps: function (nextProps) {
this.setState({
visible: this.getOptionsForValue(this.state.entryValue, nextProps.options)
});
},
render: function () {
var inputClasses = {};
inputClasses[this.props.customClasses.input] = !!this.props.customClasses.input;
var inputClassList = classNames(inputClasses);
var classes = {
typeahead: this.props.defaultClassNames
};
classes[this.props.className] = !!this.props.className;
var classList = classNames(classes);
var InputElement = this.props.textarea ? 'textarea' : 'input';
return React.createElement(
'div',
{ className: classList },
this._renderHiddenInput(),
React.createElement(InputElement, _extends({ ref: 'entry', type: 'text',
disabled: this.props.disabled
}, this.props.inputProps, {
placeholder: this.props.placeholder,
className: inputClassList,
value: this.state.entryValue,
onChange: this._onChange,
onKeyDown: this._onKeyDown,
onKeyPress: this.props.onKeyPress,
onKeyUp: this.props.onKeyUp,
onFocus: this.props.onFocus,
onBlur: this.props.onBlur
})),
this._renderIncrementalSearchResults()
);
},
_renderHiddenInput: function () {
if (!this.props.name) {
return null;
}
return React.createElement('input', {
type: 'hidden',
name: this.props.name,
value: this.state.selection
});
},
_generateFilterFunction: function () {
var filterOptionProp = this.props.filterOption;
if (typeof filterOptionProp === 'function') {
return function (value, options) {
return options.filter(function (o) {
return filterOptionProp(value, o);
});
};
} else {
var mapper;
if (typeof filterOptionProp === 'string') {
mapper = Accessor.generateAccessor(filterOptionProp);
} else {
mapper = Accessor.IDENTITY_FN;
}
return function (value, options) {
return fuzzy.filter(value, options, { extract: mapper }).map(function (res) {
return options[res.index];
});
};
}
},
_hasHint: function () {
return this.state.visible.length > 0 || this._hasCustomValue();
}
});
module.exports = Typeahead;
},{"../accessor":3,"../keyevent":4,"./selector":10,"classnames":1,"fuzzy":2,"react":"react"}],9:[function(require,module,exports){
var React = window.React || require('react');
var classNames = require('classnames');
/**
* A single option within the TypeaheadSelector
*/
var TypeaheadOption = React.createClass({
displayName: 'TypeaheadOption',
propTypes: {
customClasses: React.PropTypes.object,
customValue: React.PropTypes.string,
onClick: React.PropTypes.func,
children: React.PropTypes.string,
hover: React.PropTypes.bool
},
getDefaultProps: function () {
return {
customClasses: {},
onClick: function (event) {
event.preventDefault();
}
};
},
render: function () {
var classes = {};
classes[this.props.customClasses.hover || "hover"] = !!this.props.hover;
classes[this.props.customClasses.listItem] = !!this.props.customClasses.listItem;
if (this.props.customValue) {
classes[this.props.customClasses.customAdd] = !!this.props.customClasses.customAdd;
}
var classList = classNames(classes);
return React.createElement(
'li',
{ className: classList, onClick: this._onClick },
React.createElement(
'a',
{ href: 'javascript: void 0;', className: this._getClasses(), ref: 'anchor' },
this.props.children
)
);
},
_getClasses: function () {
var classes = {
"typeahead-option": true
};
classes[this.props.customClasses.listAnchor] = !!this.props.customClasses.listAnchor;
return classNames(classes);
},
_onClick: function (event) {
event.preventDefault();
return this.props.onClick(event);
}
});
module.exports = TypeaheadOption;
},{"classnames":1,"react":"react"}],10:[function(require,module,exports){
var React = window.React || require('react');
var TypeaheadOption = require('./option');
var classNames = require('classnames');
/**
* Container for the options rendered as part of the autocompletion process
* of the typeahead
*/
var TypeaheadSelector = React.createClass({
displayName: 'TypeaheadSelector',
propTypes: {
options: React.PropTypes.array,
allowCustomValues: React.PropTypes.number,
customClasses: React.PropTypes.object,
customValue: React.PropTypes.string,
selectionIndex: React.PropTypes.number,
onOptionSelected: React.PropTypes.func,
displayOption: React.PropTypes.func.isRequired,
defaultClassNames: React.PropTypes.bool
},
getDefaultProps: function () {
return {
selectionIndex: null,
customClasses: {},
allowCustomValues: 0,
customValue: null,
onOptionSelected: function (option) {},
defaultClassNames: true
};
},
render: function () {
// Don't render if there are no options to display
if (!this.props.options.length && this.props.allowCustomValues <= 0) {
return false;
}
var classes = {
"typeahead-selector": this.props.defaultClassNames
};
classes[this.props.customClasses.results] = this.props.customClasses.results;
var classList = classNames(classes);
// CustomValue should be added to top of results list with different class name
var customValue = null;
var customValueOffset = 0;
if (this.props.customValue !== null) {
customValueOffset++;
customValue = React.createElement(
TypeaheadOption,
{ ref: this.props.customValue, key: this.props.customValue,
hover: this.props.selectionIndex === 0,
customClasses: this.props.customClasses,
customValue: this.props.customValue,
onClick: this._onClick.bind(this, this.props.customValue) },
this.props.customValue
);
}
var results = this.props.options.map(function (result, i) {
var displayString = this.props.displayOption(result, i);
var uniqueKey = displayString + '_' + i;
return React.createElement(
TypeaheadOption,
{ ref: uniqueKey, key: uniqueKey,
hover: this.props.selectionIndex === i + customValueOffset,
customClasses: this.props.customClasses,
onClick: this._onClick.bind(this, result) },
displayString
);
}, this);
return React.createElement(
'ul',
{ className: classList },
customValue,
results
);
},
_onClick: function (result, event) {
return this.props.onOptionSelected(result, event);
}
});
module.exports = TypeaheadSelector;
},{"./option":9,"classnames":1,"react":"react"}]},{},[5])(5)
});