UNPKG

30.9 kBJavaScriptView Raw
1!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){
2/*!
3 Copyright (c) 2015 Jed Watson.
4 Licensed under the MIT License (MIT), see
5 http://jedwatson.github.io/classnames
6*/
7
8function classNames() {
9 var classes = '';
10 var arg;
11
12 for (var i = 0; i < arguments.length; i++) {
13 arg = arguments[i];
14 if (!arg) {
15 continue;
16 }
17
18 if ('string' === typeof arg || 'number' === typeof arg) {
19 classes += ' ' + arg;
20 } else if (Object.prototype.toString.call(arg) === '[object Array]') {
21 classes += ' ' + classNames.apply(null, arg);
22 } else if ('object' === typeof arg) {
23 for (var key in arg) {
24 if (!arg.hasOwnProperty(key) || !arg[key]) {
25 continue;
26 }
27 classes += ' ' + key;
28 }
29 }
30 }
31 return classes.substr(1);
32}
33
34// safely export classNames for node / browserify
35if (typeof module !== 'undefined' && module.exports) {
36 module.exports = classNames;
37}
38
39// safely export classNames for RequireJS
40if (typeof define !== 'undefined' && define.amd) {
41 define('classnames', [], function() {
42 return classNames;
43 });
44}
45
46},{}],2:[function(require,module,exports){
47/*
48 * Fuzzy
49 * https://github.com/myork/fuzzy
50 *
51 * Copyright (c) 2012 Matt York
52 * Licensed under the MIT license.
53 */
54
55(function() {
56
57var root = this;
58
59var fuzzy = {};
60
61// Use in node or in browser
62if (typeof exports !== 'undefined') {
63 module.exports = fuzzy;
64} else {
65 root.fuzzy = fuzzy;
66}
67
68// Return all elements of `array` that have a fuzzy
69// match against `pattern`.
70fuzzy.simpleFilter = function(pattern, array) {
71 return array.filter(function(string) {
72 return fuzzy.test(pattern, string);
73 });
74};
75
76// Does `pattern` fuzzy match `string`?
77fuzzy.test = function(pattern, string) {
78 return fuzzy.match(pattern, string) !== null;
79};
80
81// If `pattern` matches `string`, wrap each matching character
82// in `opts.pre` and `opts.post`. If no match, return null
83fuzzy.match = function(pattern, string, opts) {
84 opts = opts || {};
85 var patternIdx = 0
86 , result = []
87 , len = string.length
88 , totalScore = 0
89 , currScore = 0
90 // prefix
91 , pre = opts.pre || ''
92 // suffix
93 , post = opts.post || ''
94 // String to compare against. This might be a lowercase version of the
95 // raw string
96 , compareString = opts.caseSensitive && string || string.toLowerCase()
97 , ch, compareChar;
98
99 pattern = opts.caseSensitive && pattern || pattern.toLowerCase();
100
101 // For each character in the string, either add it to the result
102 // or wrap in template if its the next string in the pattern
103 for(var idx = 0; idx < len; idx++) {
104 ch = string[idx];
105 if(compareString[idx] === pattern[patternIdx]) {
106 ch = pre + ch + post;
107 patternIdx += 1;
108
109 // consecutive characters should increase the score more than linearly
110 currScore += 1 + currScore;
111 } else {
112 currScore = 0;
113 }
114 totalScore += currScore;
115 result[result.length] = ch;
116 }
117
118 // return rendered string if we have a match for every char
119 if(patternIdx === pattern.length) {
120 return {rendered: result.join(''), score: totalScore};
121 }
122
123 return null;
124};
125
126// The normal entry point. Filters `arr` for matches against `pattern`.
127// It returns an array with matching values of the type:
128//
129// [{
130// string: '<b>lah' // The rendered string
131// , index: 2 // The index of the element in `arr`
132// , original: 'blah' // The original element in `arr`
133// }]
134//
135// `opts` is an optional argument bag. Details:
136//
137// opts = {
138// // string to put before a matching character
139// pre: '<b>'
140//
141// // string to put after matching character
142// , post: '</b>'
143//
144// // Optional function. Input is an element from the passed in
145// // `arr`, output should be the string to test `pattern` against.
146// // In this example, if `arr = [{crying: 'koala'}]` we would return
147// // 'koala'.
148// , extract: function(arg) { return arg.crying; }
149// }
150fuzzy.filter = function(pattern, arr, opts) {
151 opts = opts || {};
152 return arr
153 .reduce(function(prev, element, idx, arr) {
154 var str = element;
155 if(opts.extract) {
156 str = opts.extract(element);
157 }
158 var rendered = fuzzy.match(pattern, str, opts);
159 if(rendered != null) {
160 prev[prev.length] = {
161 string: rendered.rendered
162 , score: rendered.score
163 , index: idx
164 , original: element
165 };
166 }
167 return prev;
168 }, [])
169
170 // Sort by score. Browsers are inconsistent wrt stable/unstable
171 // sorting, so force stable by using the index in the case of tie.
172 // See http://ofb.net/~sethml/is-sort-stable.html
173 .sort(function(a,b) {
174 var compare = b.score - a.score;
175 if(compare) return compare;
176 return a.index - b.index;
177 });
178};
179
180
181}());
182
183
184},{}],3:[function(require,module,exports){
185var Accessor = {
186 IDENTITY_FN: function (input) {
187 return input;
188 },
189
190 generateAccessor: function (field) {
191 return function (object) {
192 return object[field];
193 };
194 },
195
196 generateOptionToStringFor: function (prop) {
197 if (typeof prop === 'string') {
198 return this.generateAccessor(prop);
199 } else if (typeof prop === 'function') {
200 return prop;
201 } else {
202 return this.IDENTITY_FN;
203 }
204 },
205
206 valueForOption: function (option, object) {
207 if (typeof option === 'string') {
208 return object[option];
209 } else if (typeof option === 'function') {
210 return option(object);
211 } else {
212 return object;
213 }
214 }
215};
216
217module.exports = Accessor;
218
219},{}],4:[function(require,module,exports){
220/**
221 * PolyFills make me sad
222 */
223var KeyEvent = KeyEvent || {};
224KeyEvent.DOM_VK_UP = KeyEvent.DOM_VK_UP || 38;
225KeyEvent.DOM_VK_DOWN = KeyEvent.DOM_VK_DOWN || 40;
226KeyEvent.DOM_VK_BACK_SPACE = KeyEvent.DOM_VK_BACK_SPACE || 8;
227KeyEvent.DOM_VK_RETURN = KeyEvent.DOM_VK_RETURN || 13;
228KeyEvent.DOM_VK_ENTER = KeyEvent.DOM_VK_ENTER || 14;
229KeyEvent.DOM_VK_ESCAPE = KeyEvent.DOM_VK_ESCAPE || 27;
230KeyEvent.DOM_VK_TAB = KeyEvent.DOM_VK_TAB || 9;
231
232module.exports = KeyEvent;
233
234},{}],5:[function(require,module,exports){
235var Typeahead = require('./typeahead');
236var Tokenizer = require('./tokenizer');
237
238module.exports = {
239 Typeahead: Typeahead,
240 Tokenizer: Tokenizer
241};
242
243},{"./tokenizer":6,"./typeahead":8}],6:[function(require,module,exports){
244var Accessor = require('../accessor');
245var React = window.React || require('react');
246var Token = require('./token');
247var KeyEvent = require('../keyevent');
248var Typeahead = require('../typeahead');
249var classNames = require('classnames');
250
251function _arraysAreDifferent(array1, array2) {
252 if (array1.length != array2.length) {
253 return true;
254 }
255 for (var i = array2.length - 1; i >= 0; i--) {
256 if (array2[i] !== array1[i]) {
257 return true;
258 }
259 }
260}
261
262/**
263 * A typeahead that, when an option is selected, instead of simply filling
264 * the text entry widget, prepends a renderable "token", that may be deleted
265 * by pressing backspace on the beginning of the line with the keyboard.
266 */
267var TypeaheadTokenizer = React.createClass({
268 displayName: 'TypeaheadTokenizer',
269
270 propTypes: {
271 name: React.PropTypes.string,
272 options: React.PropTypes.array,
273 customClasses: React.PropTypes.object,
274 allowCustomValues: React.PropTypes.number,
275 defaultSelected: React.PropTypes.array,
276 initialValue: React.PropTypes.string,
277 placeholder: React.PropTypes.string,
278 disabled: React.PropTypes.bool,
279 inputProps: React.PropTypes.object,
280 onTokenRemove: React.PropTypes.func,
281 onKeyDown: React.PropTypes.func,
282 onKeyPress: React.PropTypes.func,
283 onKeyUp: React.PropTypes.func,
284 onTokenAdd: React.PropTypes.func,
285 onFocus: React.PropTypes.func,
286 onBlur: React.PropTypes.func,
287 filterOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
288 displayOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
289 formInputOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
290 maxVisible: React.PropTypes.number,
291 defaultClassNames: React.PropTypes.bool
292 },
293
294 getInitialState: function () {
295 return {
296 // We need to copy this to avoid incorrect sharing
297 // of state across instances (e.g., via getDefaultProps())
298 selected: this.props.defaultSelected.slice(0)
299 };
300 },
301
302 getDefaultProps: function () {
303 return {
304 options: [],
305 defaultSelected: [],
306 customClasses: {},
307 allowCustomValues: 0,
308 initialValue: "",
309 placeholder: "",
310 disabled: false,
311 inputProps: {},
312 defaultClassNames: true,
313 filterOption: null,
314 displayOption: function (token) {
315 return token;
316 },
317 formInputOption: null,
318 onKeyDown: function (event) {},
319 onKeyPress: function (event) {},
320 onKeyUp: function (event) {},
321 onFocus: function (event) {},
322 onBlur: function (event) {},
323 onTokenAdd: function () {},
324 onTokenRemove: function () {}
325 };
326 },
327
328 componentWillReceiveProps: function (nextProps) {
329 // if we get new defaultProps, update selected
330 if (_arraysAreDifferent(this.props.defaultSelected, nextProps.defaultSelected)) {
331 this.setState({ selected: nextProps.defaultSelected.slice(0) });
332 }
333 },
334
335 focus: function () {
336 this.refs.typeahead.focus();
337 },
338
339 getSelectedTokens: function () {
340 return this.state.selected;
341 },
342
343 // TODO: Support initialized tokens
344 //
345 _renderTokens: function () {
346 var tokenClasses = {};
347 tokenClasses[this.props.customClasses.token] = !!this.props.customClasses.token;
348 var classList = classNames(tokenClasses);
349 var result = this.state.selected.map(function (selected) {
350 var displayString = Accessor.valueForOption(this.props.displayOption, selected);
351 var value = Accessor.valueForOption(this.props.formInputOption || this.props.displayOption, selected);
352 return React.createElement(
353 Token,
354 { key: displayString, className: classList,
355 onRemove: this._removeTokenForValue,
356 object: selected,
357 value: value,
358 name: this.props.name },
359 displayString
360 );
361 }, this);
362 return result;
363 },
364
365 _getOptionsForTypeahead: function () {
366 // return this.props.options without this.selected
367 return this.props.options;
368 },
369
370 _onKeyDown: function (event) {
371 // We only care about intercepting backspaces
372 if (event.keyCode === KeyEvent.DOM_VK_BACK_SPACE) {
373 return this._handleBackspace(event);
374 }
375 this.props.onKeyDown(event);
376 },
377
378 _handleBackspace: function (event) {
379 // No tokens
380 if (!this.state.selected.length) {
381 return;
382 }
383
384 // Remove token ONLY when bksp pressed at beginning of line
385 // without a selection
386 var entry = this.refs.typeahead.refs.entry;
387 if (entry.selectionStart == entry.selectionEnd && entry.selectionStart == 0) {
388 this._removeTokenForValue(this.state.selected[this.state.selected.length - 1]);
389 event.preventDefault();
390 }
391 },
392
393 _removeTokenForValue: function (value) {
394 var index = this.state.selected.indexOf(value);
395 if (index == -1) {
396 return;
397 }
398
399 this.state.selected.splice(index, 1);
400 this.setState({ selected: this.state.selected });
401 this.props.onTokenRemove(value);
402 return;
403 },
404
405 _addTokenForValue: function (value) {
406 if (this.state.selected.indexOf(value) != -1) {
407 return;
408 }
409 this.state.selected.push(value);
410 this.setState({ selected: this.state.selected });
411 this.refs.typeahead.setEntryText("");
412 this.props.onTokenAdd(value);
413 },
414
415 render: function () {
416 var classes = {};
417 classes[this.props.customClasses.typeahead] = !!this.props.customClasses.typeahead;
418 var classList = classNames(classes);
419 var tokenizerClasses = [this.props.defaultClassNames && "typeahead-tokenizer"];
420 tokenizerClasses[this.props.className] = !!this.props.className;
421 var tokenizerClassList = classNames(tokenizerClasses);
422
423 return React.createElement(
424 'div',
425 { className: tokenizerClassList },
426 this._renderTokens(),
427 React.createElement(Typeahead, { ref: 'typeahead',
428 className: classList,
429 placeholder: this.props.placeholder,
430 disabled: this.props.disabled,
431 inputProps: this.props.inputProps,
432 allowCustomValues: this.props.allowCustomValues,
433 customClasses: this.props.customClasses,
434 options: this._getOptionsForTypeahead(),
435 initialValue: this.props.initialValue,
436 maxVisible: this.props.maxVisible,
437 onOptionSelected: this._addTokenForValue,
438 onKeyDown: this._onKeyDown,
439 onKeyPress: this.props.onKeyPress,
440 onKeyUp: this.props.onKeyUp,
441 onFocus: this.props.onFocus,
442 onBlur: this.props.onBlur,
443 displayOption: this.props.displayOption,
444 defaultClassNames: this.props.defaultClassNames,
445 filterOption: this.props.filterOption })
446 );
447 }
448});
449
450module.exports = TypeaheadTokenizer;
451
452},{"../accessor":3,"../keyevent":4,"../typeahead":8,"./token":7,"classnames":1,"react":"react"}],7:[function(require,module,exports){
453var React = window.React || require('react');
454var classNames = require('classnames');
455
456/**
457 * Encapsulates the rendering of an option that has been "selected" in a
458 * TypeaheadTokenizer
459 */
460var Token = React.createClass({
461 displayName: 'Token',
462
463 propTypes: {
464 className: React.PropTypes.string,
465 name: React.PropTypes.string,
466 children: React.PropTypes.string,
467 object: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]),
468 onRemove: React.PropTypes.func,
469 value: React.PropTypes.string
470 },
471
472 render: function () {
473 var className = classNames(["typeahead-token", this.props.className]);
474
475 return React.createElement(
476 'div',
477 { className: className },
478 this._renderHiddenInput(),
479 this.props.children,
480 this._renderCloseButton()
481 );
482 },
483
484 _renderHiddenInput: function () {
485 // If no name was set, don't create a hidden input
486 if (!this.props.name) {
487 return null;
488 }
489
490 return React.createElement('input', {
491 type: 'hidden',
492 name: this.props.name + '[]',
493 value: this.props.value || this.props.object
494 });
495 },
496
497 _renderCloseButton: function () {
498 if (!this.props.onRemove) {
499 return "";
500 }
501 return React.createElement(
502 'a',
503 { className: 'typeahead-token-close', href: '#', onClick: function (event) {
504 this.props.onRemove(this.props.object);
505 event.preventDefault();
506 }.bind(this) },
507 '×'
508 );
509 }
510});
511
512module.exports = Token;
513
514},{"classnames":1,"react":"react"}],8:[function(require,module,exports){
515var _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; };
516
517var Accessor = require('../accessor');
518var React = window.React || require('react');
519var TypeaheadSelector = require('./selector');
520var KeyEvent = require('../keyevent');
521var fuzzy = require('fuzzy');
522var classNames = require('classnames');
523
524/**
525 * A "typeahead", an auto-completing text input
526 *
527 * Renders an text input that shows options nearby that you can use the
528 * keyboard or mouse to select. Requires CSS for MASSIVE DAMAGE.
529 */
530var Typeahead = React.createClass({
531 displayName: 'Typeahead',
532
533 propTypes: {
534 name: React.PropTypes.string,
535 customClasses: React.PropTypes.object,
536 maxVisible: React.PropTypes.number,
537 options: React.PropTypes.array,
538 allowCustomValues: React.PropTypes.number,
539 initialValue: React.PropTypes.string,
540 value: React.PropTypes.string,
541 placeholder: React.PropTypes.string,
542 disabled: React.PropTypes.bool,
543 textarea: React.PropTypes.bool,
544 inputProps: React.PropTypes.object,
545 onOptionSelected: React.PropTypes.func,
546 onChange: React.PropTypes.func,
547 onKeyDown: React.PropTypes.func,
548 onKeyPress: React.PropTypes.func,
549 onKeyUp: React.PropTypes.func,
550 onFocus: React.PropTypes.func,
551 onBlur: React.PropTypes.func,
552 filterOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
553 displayOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
554 formInputOption: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.func]),
555 defaultClassNames: React.PropTypes.bool,
556 customListComponent: React.PropTypes.oneOfType([React.PropTypes.element, React.PropTypes.func]),
557 showOptionsWhenEmpty: React.PropTypes.bool
558 },
559
560 getDefaultProps: function () {
561 return {
562 options: [],
563 customClasses: {},
564 allowCustomValues: 0,
565 initialValue: "",
566 value: "",
567 placeholder: "",
568 disabled: false,
569 textarea: false,
570 inputProps: {},
571 onOptionSelected: function (option) {},
572 onChange: function (event) {},
573 onKeyDown: function (event) {},
574 onKeyPress: function (event) {},
575 onKeyUp: function (event) {},
576 onFocus: function (event) {},
577 onBlur: function (event) {},
578 filterOption: null,
579 defaultClassNames: true,
580 customListComponent: TypeaheadSelector,
581 showOptionsWhenEmpty: false
582 };
583 },
584
585 getInitialState: function () {
586 return {
587 // The currently visible set of options
588 visible: this.getOptionsForValue(this.props.initialValue, this.props.options),
589
590 // This should be called something else, "entryValue"
591 entryValue: this.props.value || this.props.initialValue,
592
593 // A valid typeahead value
594 selection: this.props.value,
595
596 // Index of the selection
597 selectionIndex: null
598 };
599 },
600
601 _shouldSkipSearch: function (input) {
602 var emptyValue = !input || input.trim().length == 0;
603 return !this.props.showOptionsWhenEmpty && emptyValue;
604 },
605
606 getOptionsForValue: function (value, options) {
607 if (this._shouldSkipSearch(value)) {
608 return [];
609 }
610
611 var filterOptions = this._generateFilterFunction();
612 var result = filterOptions(value, options);
613 if (this.props.maxVisible) {
614 result = result.slice(0, this.props.maxVisible);
615 }
616 return result;
617 },
618
619 setEntryText: function (value) {
620 this.refs.entry.value = value;
621 this._onTextEntryUpdated();
622 },
623
624 focus: function () {
625 this.refs.entry.focus();
626 },
627
628 _hasCustomValue: function () {
629 if (this.props.allowCustomValues > 0 && this.state.entryValue.length >= this.props.allowCustomValues && this.state.visible.indexOf(this.state.entryValue) < 0) {
630 return true;
631 }
632 return false;
633 },
634
635 _getCustomValue: function () {
636 if (this._hasCustomValue()) {
637 return this.state.entryValue;
638 }
639 return null;
640 },
641
642 _renderIncrementalSearchResults: function () {
643 // Nothing has been entered into the textbox
644 if (this._shouldSkipSearch(this.state.entryValue)) {
645 return "";
646 }
647
648 // Something was just selected
649 if (this.state.selection) {
650 return "";
651 }
652
653 return React.createElement(this.props.customListComponent, {
654 ref: 'sel', options: this.state.visible,
655 onOptionSelected: this._onOptionSelected,
656 allowCustomValues: this.props.allowCustomValues,
657 customValue: this._getCustomValue(),
658 customClasses: this.props.customClasses,
659 selectionIndex: this.state.selectionIndex,
660 defaultClassNames: this.props.defaultClassNames,
661 displayOption: Accessor.generateOptionToStringFor(this.props.displayOption) });
662 },
663
664 getSelection: function () {
665 var index = this.state.selectionIndex;
666 if (this._hasCustomValue()) {
667 if (index === 0) {
668 return this.state.entryValue;
669 } else {
670 index--;
671 }
672 }
673 return this.state.visible[index];
674 },
675
676 _onOptionSelected: function (option, event) {
677 var nEntry = this.refs.entry;
678 nEntry.focus();
679
680 var displayOption = Accessor.generateOptionToStringFor(this.props.displayOption);
681 var optionString = displayOption(option, 0);
682
683 var formInputOption = Accessor.generateOptionToStringFor(this.props.formInputOption || displayOption);
684 var formInputOptionString = formInputOption(option);
685
686 nEntry.value = optionString;
687 this.setState({ visible: this.getOptionsForValue(optionString, this.props.options),
688 selection: formInputOptionString,
689 entryValue: optionString });
690 return this.props.onOptionSelected(option, event);
691 },
692
693 _onTextEntryUpdated: function () {
694 var value = this.refs.entry.value;
695 this.setState({ visible: this.getOptionsForValue(value, this.props.options),
696 selection: '',
697 entryValue: value });
698 },
699
700 _onEnter: function (event) {
701 var selection = this.getSelection();
702 if (!selection) {
703 return this.props.onKeyDown(event);
704 }
705 return this._onOptionSelected(selection, event);
706 },
707
708 _onEscape: function () {
709 this.setState({
710 selectionIndex: null
711 });
712 },
713
714 _onTab: function (event) {
715 var selection = this.getSelection();
716 var option = selection ? selection : this.state.visible.length > 0 ? this.state.visible[0] : null;
717
718 if (option === null && this._hasCustomValue()) {
719 option = this._getCustomValue();
720 }
721
722 if (option !== null) {
723 return this._onOptionSelected(option, event);
724 }
725 },
726
727 eventMap: function (event) {
728 var events = {};
729
730 events[KeyEvent.DOM_VK_UP] = this.navUp;
731 events[KeyEvent.DOM_VK_DOWN] = this.navDown;
732 events[KeyEvent.DOM_VK_RETURN] = events[KeyEvent.DOM_VK_ENTER] = this._onEnter;
733 events[KeyEvent.DOM_VK_ESCAPE] = this._onEscape;
734 events[KeyEvent.DOM_VK_TAB] = this._onTab;
735
736 return events;
737 },
738
739 _nav: function (delta) {
740 if (!this._hasHint()) {
741 return;
742 }
743 var newIndex = this.state.selectionIndex === null ? delta == 1 ? 0 : delta : this.state.selectionIndex + delta;
744 var length = this.state.visible.length;
745 if (this._hasCustomValue()) {
746 length += 1;
747 }
748
749 if (newIndex < 0) {
750 newIndex += length;
751 } else if (newIndex >= length) {
752 newIndex -= length;
753 }
754
755 this.setState({ selectionIndex: newIndex });
756 },
757
758 navDown: function () {
759 this._nav(1);
760 },
761
762 navUp: function () {
763 this._nav(-1);
764 },
765
766 _onChange: function (event) {
767 if (this.props.onChange) {
768 this.props.onChange(event);
769 }
770
771 this._onTextEntryUpdated();
772 },
773
774 _onKeyDown: function (event) {
775 // If there are no visible elements, don't perform selector navigation.
776 // Just pass this up to the upstream onKeydown handler.
777 // Also skip if the user is pressing the shift key, since none of our handlers are looking for shift
778 if (!this._hasHint() || event.shiftKey) {
779 return this.props.onKeyDown(event);
780 }
781
782 var handler = this.eventMap()[event.keyCode];
783
784 if (handler) {
785 handler(event);
786 } else {
787 return this.props.onKeyDown(event);
788 }
789 // Don't propagate the keystroke back to the DOM/browser
790 event.preventDefault();
791 },
792
793 componentWillReceiveProps: function (nextProps) {
794 this.setState({
795 visible: this.getOptionsForValue(this.state.entryValue, nextProps.options)
796 });
797 },
798
799 render: function () {
800 var inputClasses = {};
801 inputClasses[this.props.customClasses.input] = !!this.props.customClasses.input;
802 var inputClassList = classNames(inputClasses);
803
804 var classes = {
805 typeahead: this.props.defaultClassNames
806 };
807 classes[this.props.className] = !!this.props.className;
808 var classList = classNames(classes);
809
810 var InputElement = this.props.textarea ? 'textarea' : 'input';
811 return React.createElement(
812 'div',
813 { className: classList },
814 this._renderHiddenInput(),
815 React.createElement(InputElement, _extends({ ref: 'entry', type: 'text',
816 disabled: this.props.disabled
817 }, this.props.inputProps, {
818 placeholder: this.props.placeholder,
819 className: inputClassList,
820 value: this.state.entryValue,
821 onChange: this._onChange,
822 onKeyDown: this._onKeyDown,
823 onKeyPress: this.props.onKeyPress,
824 onKeyUp: this.props.onKeyUp,
825 onFocus: this.props.onFocus,
826 onBlur: this.props.onBlur
827 })),
828 this._renderIncrementalSearchResults()
829 );
830 },
831
832 _renderHiddenInput: function () {
833 if (!this.props.name) {
834 return null;
835 }
836
837 return React.createElement('input', {
838 type: 'hidden',
839 name: this.props.name,
840 value: this.state.selection
841 });
842 },
843
844 _generateFilterFunction: function () {
845 var filterOptionProp = this.props.filterOption;
846 if (typeof filterOptionProp === 'function') {
847 return function (value, options) {
848 return options.filter(function (o) {
849 return filterOptionProp(value, o);
850 });
851 };
852 } else {
853 var mapper;
854 if (typeof filterOptionProp === 'string') {
855 mapper = Accessor.generateAccessor(filterOptionProp);
856 } else {
857 mapper = Accessor.IDENTITY_FN;
858 }
859 return function (value, options) {
860 return fuzzy.filter(value, options, { extract: mapper }).map(function (res) {
861 return options[res.index];
862 });
863 };
864 }
865 },
866
867 _hasHint: function () {
868 return this.state.visible.length > 0 || this._hasCustomValue();
869 }
870});
871
872module.exports = Typeahead;
873
874},{"../accessor":3,"../keyevent":4,"./selector":10,"classnames":1,"fuzzy":2,"react":"react"}],9:[function(require,module,exports){
875var React = window.React || require('react');
876var classNames = require('classnames');
877
878/**
879 * A single option within the TypeaheadSelector
880 */
881var TypeaheadOption = React.createClass({
882 displayName: 'TypeaheadOption',
883
884 propTypes: {
885 customClasses: React.PropTypes.object,
886 customValue: React.PropTypes.string,
887 onClick: React.PropTypes.func,
888 children: React.PropTypes.string,
889 hover: React.PropTypes.bool
890 },
891
892 getDefaultProps: function () {
893 return {
894 customClasses: {},
895 onClick: function (event) {
896 event.preventDefault();
897 }
898 };
899 },
900
901 render: function () {
902 var classes = {};
903 classes[this.props.customClasses.hover || "hover"] = !!this.props.hover;
904 classes[this.props.customClasses.listItem] = !!this.props.customClasses.listItem;
905
906 if (this.props.customValue) {
907 classes[this.props.customClasses.customAdd] = !!this.props.customClasses.customAdd;
908 }
909
910 var classList = classNames(classes);
911
912 return React.createElement(
913 'li',
914 { className: classList, onClick: this._onClick },
915 React.createElement(
916 'a',
917 { href: 'javascript: void 0;', className: this._getClasses(), ref: 'anchor' },
918 this.props.children
919 )
920 );
921 },
922
923 _getClasses: function () {
924 var classes = {
925 "typeahead-option": true
926 };
927 classes[this.props.customClasses.listAnchor] = !!this.props.customClasses.listAnchor;
928
929 return classNames(classes);
930 },
931
932 _onClick: function (event) {
933 event.preventDefault();
934 return this.props.onClick(event);
935 }
936});
937
938module.exports = TypeaheadOption;
939
940},{"classnames":1,"react":"react"}],10:[function(require,module,exports){
941var React = window.React || require('react');
942var TypeaheadOption = require('./option');
943var classNames = require('classnames');
944
945/**
946 * Container for the options rendered as part of the autocompletion process
947 * of the typeahead
948 */
949var TypeaheadSelector = React.createClass({
950 displayName: 'TypeaheadSelector',
951
952 propTypes: {
953 options: React.PropTypes.array,
954 allowCustomValues: React.PropTypes.number,
955 customClasses: React.PropTypes.object,
956 customValue: React.PropTypes.string,
957 selectionIndex: React.PropTypes.number,
958 onOptionSelected: React.PropTypes.func,
959 displayOption: React.PropTypes.func.isRequired,
960 defaultClassNames: React.PropTypes.bool
961 },
962
963 getDefaultProps: function () {
964 return {
965 selectionIndex: null,
966 customClasses: {},
967 allowCustomValues: 0,
968 customValue: null,
969 onOptionSelected: function (option) {},
970 defaultClassNames: true
971 };
972 },
973
974 render: function () {
975 // Don't render if there are no options to display
976 if (!this.props.options.length && this.props.allowCustomValues <= 0) {
977 return false;
978 }
979
980 var classes = {
981 "typeahead-selector": this.props.defaultClassNames
982 };
983 classes[this.props.customClasses.results] = this.props.customClasses.results;
984 var classList = classNames(classes);
985
986 // CustomValue should be added to top of results list with different class name
987 var customValue = null;
988 var customValueOffset = 0;
989 if (this.props.customValue !== null) {
990 customValueOffset++;
991 customValue = React.createElement(
992 TypeaheadOption,
993 { ref: this.props.customValue, key: this.props.customValue,
994 hover: this.props.selectionIndex === 0,
995 customClasses: this.props.customClasses,
996 customValue: this.props.customValue,
997 onClick: this._onClick.bind(this, this.props.customValue) },
998 this.props.customValue
999 );
1000 }
1001
1002 var results = this.props.options.map(function (result, i) {
1003 var displayString = this.props.displayOption(result, i);
1004 var uniqueKey = displayString + '_' + i;
1005 return React.createElement(
1006 TypeaheadOption,
1007 { ref: uniqueKey, key: uniqueKey,
1008 hover: this.props.selectionIndex === i + customValueOffset,
1009 customClasses: this.props.customClasses,
1010 onClick: this._onClick.bind(this, result) },
1011 displayString
1012 );
1013 }, this);
1014
1015 return React.createElement(
1016 'ul',
1017 { className: classList },
1018 customValue,
1019 results
1020 );
1021 },
1022
1023 _onClick: function (result, event) {
1024 return this.props.onOptionSelected(result, event);
1025 }
1026
1027});
1028
1029module.exports = TypeaheadSelector;
1030
1031},{"./option":9,"classnames":1,"react":"react"}]},{},[5])(5)
1032});
\No newline at end of file