ron-react-autocomplete
Version:
Autocomplete component for React
504 lines (425 loc) • 14.8 kB
JavaScript
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(require("react"), require("react-dom"));
else if(typeof define === 'function' && define.amd)
define(["react", "react-dom"], factory);
else if(typeof exports === 'object')
exports["ReactAutocomplete"] = factory(require("react"), require("react-dom"));
else
root["ReactAutocomplete"] = factory(root["React"], root["ReactDOM"]);
})(this, function(__WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_3__) {
return /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
var React = __webpack_require__(2);
var ReactDOM = __webpack_require__(3);
var joinClasses = __webpack_require__(1);
var Autocomplete = React.createClass({ displayName: "Autocomplete",
propTypes: {
options: React.PropTypes.any,
search: React.PropTypes.func,
resultRenderer: React.PropTypes.oneOfType([React.PropTypes.component, React.PropTypes.func]),
value: React.PropTypes.object,
onChange: React.PropTypes.func,
onError: React.PropTypes.func,
onFocus: React.PropTypes.func
},
getDefaultProps: function () {
return { search: searchArray };
},
getInitialState: function () {
var searchTerm = this.props.searchTerm ? this.props.searchTerm : this.props.value ? this.props.value.title : "";
return {
results: [],
showResults: false,
showResultsInProgress: false,
searchTerm: searchTerm,
focusedValue: null
};
},
getResultIdentifier: function (result) {
if (this.props.resultIdentifier === undefined) {
return result.id;
} else {
return result[this.props.resultIdentifier];
}
},
render: function () {
var className = joinClasses(this.props.className, "react-autocomplete-Autocomplete", this.state.showResults ? "react-autocomplete-Autocomplete--resultsShown" : undefined);
var style = {
position: "relative",
outline: "none"
};
return React.createElement("div", {
tabIndex: "1",
className: className,
onFocus: this.onFocus,
onBlur: this.onBlur,
style: style }, React.createElement("input", {
ref: "search",
className: "react-autocomplete-Autocomplete__search",
style: { width: "100%" },
onClick: this.showAllResults,
onChange: this.onQueryChange,
onFocus: this.onSearchInputFocus,
onBlur: this.onQueryBlur,
onKeyDown: this.onQueryKeyDown,
value: this.state.searchTerm }), React.createElement(Results, {
className: "react-autocomplete-Autocomplete__results",
onSelect: this.onValueChange,
onFocus: this.onValueFocus,
results: this.state.results,
focusedValue: this.state.focusedValue,
show: this.state.showResults,
renderer: this.props.resultRenderer,
label: this.props.label,
resultIdentifier: this.props.resultIdentifier }));
},
componentWillReceiveProps: function (nextProps) {
var searchTerm = nextProps.searchTerm ? nextProps.searchTerm : nextProps.value ? nextProps.value.title : "";
this.setState({ searchTerm: searchTerm });
},
componentWillMount: function () {
this.blurTimer = null;
},
/**
* Show results for a search term value.
*
* This method doesn't update search term value itself.
*
* @param {Search} searchTerm
*/
showResults: function (searchTerm) {
this.setState({ showResultsInProgress: true });
this.props.search(this.props.options, searchTerm.trim(), this.onSearchComplete);
},
showAllResults: function () {
if (!this.state.showResultsInProgress && !this.state.showResults) {
this.showResults("");
}
},
onValueChange: function (value) {
var state = {
value: value,
showResults: false
};
if (value) {
state.searchTerm = value.title;
}
this.setState(state);
if (this.props.onChange) {
this.props.onChange(value);
}
},
onSearchComplete: function (err, results) {
if (err) {
if (this.props.onError) {
this.props.onError(err);
} else {
throw err;
}
}
this.setState({
showResultsInProgress: false,
showResults: true,
results: results
});
},
onValueFocus: function (value) {
this.setState({ focusedValue: value });
},
onQueryChange: function (e) {
var searchTerm = e.target.value;
this.setState({
searchTerm: searchTerm,
focusedValue: null
});
this.showResults(searchTerm);
},
onFocus: function () {
if (this.blurTimer) {
clearTimeout(this.blurTimer);
this.blurTimer = null;
}
this.refs.search.focus();
},
onSearchInputFocus: function () {
if (this.props.onFocus) {
this.props.onFocus();
}
this.showAllResults();
},
onBlur: function () {
// wrap in setTimeout so we can catch a click on results
this.blurTimer = setTimeout((function () {
if (this.isMounted()) {
this.setState({ showResults: false });
}
}).bind(this), 100);
},
onQueryKeyDown: function (e) {
if (e.key === "Enter") {
e.preventDefault();
if (this.state.focusedValue) {
this.onValueChange(this.state.focusedValue);
}
} else if (e.key === "ArrowUp" && this.state.showResults) {
e.preventDefault();
var prevIdx = Math.max(this.focusedValueIndex() - 1, 0);
this.setState({
focusedValue: this.state.results[prevIdx]
});
} else if (e.key === "ArrowDown") {
e.preventDefault();
if (this.state.showResults) {
var nextIdx = Math.min(this.focusedValueIndex() + (this.state.showResults ? 1 : 0), this.state.results.length - 1);
this.setState({
showResults: true,
focusedValue: this.state.results[nextIdx]
});
} else {
this.showAllResults();
}
}
},
focusedValueIndex: function () {
if (!this.state.focusedValue) {
return -1;
}
for (var i = 0, len = this.state.results.length; i < len; i++) {
if (this.getResultIdentifier(this.state.results[i]) === this.getResultIdentifier(this.state.focusedValue)) {
return i;
}
}
return -1;
}
});
var Results = React.createClass({ displayName: "Results",
getResultIdentifier: function (result) {
if (this.props.resultIdentifier === undefined) {
if (!result.id) {
throw Error("id property not found on result. You must specify a resultIdentifier and pass as props to autocomplete component");
}
return result.id;
} else {
return result[this.props.resultIdentifier];
}
},
render: function () {
var style = {
display: this.props.show ? "block" : "none",
position: "absolute",
listStyleType: "none"
};
var $__0 = this.props,
className = $__0.className;
return React.createElement("ul", { style: style, className: className + " react-autocomplete-Results" }, this.props.results.map(this.renderResult));
},
renderResult: function (result) {
var focused = this.props.focusedValue && this.getResultIdentifier(this.props.focusedValue) === this.getResultIdentifier(result);
var Renderer = this.props.renderer || Result;
return React.createElement(Renderer, {
ref: focused ? "focused" : undefined,
key: this.getResultIdentifier(result),
result: result,
focused: focused,
onMouseEnter: this.onMouseEnterResult,
onClick: this.props.onSelect,
label: this.props.label });
},
componentDidUpdate: function () {
this.scrollToFocused();
},
componentDidMount: function () {
this.scrollToFocused();
},
componentWillMount: function () {
this.ignoreFocus = false;
},
scrollToFocused: function () {
var focused = this.refs && this.refs.focused;
if (focused) {
var containerNode = ReactDOM.findDOMNode(this);
var scroll = containerNode.scrollTop;
var height = containerNode.offsetHeight;
var node = ReactDOM.findDOMNode(focused);
var top = node.offsetTop;
var bottom = top + node.offsetHeight;
// we update ignoreFocus to true if we change the scroll position so
// the mouseover event triggered because of that won't have an
// effect
if (top < scroll) {
this.ignoreFocus = true;
containerNode.scrollTop = top;
} else if (bottom - scroll > height) {
this.ignoreFocus = true;
containerNode.scrollTop = bottom - height;
}
}
},
onMouseEnterResult: function (e, result) {
// check if we need to prevent the next onFocus event because it was
// probably caused by a mouseover due to scroll position change
if (this.ignoreFocus) {
this.ignoreFocus = false;
} else {
// we need to make sure focused node is visible
// for some reason mouse events fire on visible nodes due to
// box-shadow
var containerNode = ReactDOM.findDOMNode(this);
var scroll = containerNode.scrollTop;
var height = containerNode.offsetHeight;
var node = e.target;
var top = node.offsetTop;
var bottom = top + node.offsetHeight;
if (bottom > scroll && top < scroll + height) {
this.props.onFocus(result);
}
}
}
});
var Result = React.createClass({ displayName: "Result",
getDefaultProps: function () {
return {
label: function (result) {
return result.title;
}
};
},
getLabel: function (result) {
if (typeof this.props.label === "function") {
return this.props.label(result);
} else if (typeof this.props.label === "string") {
return result[this.props.label];
}
},
render: function () {
var className = joinClasses({
"react-autocomplete-Result": true,
"react-autocomplete-Result--active": this.props.focused
});
return React.createElement("li", {
style: { listStyleType: "none" },
className: className,
onClick: this.onClick,
onMouseEnter: this.onMouseEnter }, React.createElement("a", null, this.getLabel(this.props.result)));
},
onClick: function () {
this.props.onClick(this.props.result);
},
onMouseEnter: function (e) {
if (this.props.onMouseEnter) {
this.props.onMouseEnter(e, this.props.result);
}
},
shouldComponentUpdate: function (nextProps) {
return nextProps.result.id !== this.props.result.id || nextProps.focused !== this.props.focused;
}
});
/**
* Search options using specified search term treating options as an array
* of candidates.
*
* @param {Array.<Object>} options
* @param {String} searchTerm
* @param {Callback} cb
*/
function searchArray(options, searchTerm, cb) {
if (!options) {
return cb(null, []);
}
searchTerm = new RegExp(searchTerm, "i");
var results = [];
for (var i = 0, len = options.length; i < len; i++) {
if (searchTerm.exec(options[i].title)) {
results.push(options[i]);
}
}
cb(null, results);
}
module.exports = Autocomplete;
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*!
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 (true) {
!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function() {
return classNames;
}.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
}
/***/ }),
/* 2 */
/***/ (function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE_2__;
/***/ }),
/* 3 */
/***/ (function(module, exports) {
module.exports = __WEBPACK_EXTERNAL_MODULE_3__;
/***/ })
/******/ ])
});
;