UNPKG

react-typeahead

Version:

React-based typeahead and typeahead-tokenizer

1,032 lines (894 loc) 30.9 kB
!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) });