UNPKG

react-simple-autocomplete

Version:

A simple, customizable wrapper to provide autocomplete functionality to inputs

268 lines (221 loc) 8.63 kB
'use strict'; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); Object.defineProperty(exports, "__esModule", { value: true }); var _react = require('react'); var _react2 = _interopRequireDefault(_react); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var Autocomplete = function (_Component) { _inherits(Autocomplete, _Component); function Autocomplete() { var _Object$getPrototypeO; var _temp, _this, _ret; _classCallCheck(this, Autocomplete); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_Object$getPrototypeO = Object.getPrototypeOf(Autocomplete)).call.apply(_Object$getPrototypeO, [this].concat(args))), _this), _this.state = { open: false, highlighted: -1 }, _this.open = function () { _this.setState({ open: true }); }, _this.close = function () { _this.setState({ open: false }); }, _this.handleMouseLeave = function (_event) { _this.setState({ highlighted: -1 }); }, _this.handleMouseEnter = function (index) { return function (_event) { _this.setState({ highlighted: index }); }; }, _this.handleMouseDown = function () { _this._blur = false; }, _this.handleSelectItem = function (item) { return function (event) { var onSelectItem = _this.props.onSelectItem; var input = _this.refs.input; /** * If there is no onSelectItem, just update the value */ var selectHandler = onSelectItem ? onSelectItem(item, event) : input.value = item; /** * After updating the value, we need to trigger an onChange event. * Can also trigger by returning true in onSelectItem */ if (selectHandler) { var changeEvent = new Event('input', { bubbles: true }); input.dispatchEvent(changeEvent); } /** * Close the menu and allow blur events * to continue, */ setTimeout(function () { input.focus(); _this._blur = true; _this.close(); }); }; }, _this.handleFocus = function (event) { var onFocus = _this.props.onFocus; _this.open(); onFocus && onFocus(event); }, _this.handleBlur = function (event) { var onBlur = _this.props.onBlur; if (!_this._blur) return; _this.close(); onBlur && onBlur(event); }, _this.handleKeyDown = function (event) { var highlighted = _this.state.highlighted; _this.open(); switch (event.key) { case 'ArrowDown': event.preventDefault(); var next = Math.min(highlighted + 1, _this.items.length - 1); _this.setState({ highlighted: next }); return; case 'ArrowUp': event.preventDefault(); var prev = Math.max(highlighted - 1, -1); _this.setState({ highlighted: prev }); return; case 'Enter': event.preventDefault(); if (highlighted > -1) { _this.handleSelectItem(_this.items[highlighted])(event); } return; case 'Escape': _this.close(); return; default: // TODO: Add debounce setTimeout(function () { return _this.forceUpdate(); }); return; } }, _temp), _possibleConstructorReturn(_this, _ret); } _createClass(Autocomplete, [{ key: 'componentWillMount', value: function componentWillMount() { this._blur = true; } // menu // item // item // item // children // children // children // children }, { key: 'render', value: function render() { var _this2 = this; var _props = this.props; var children = _props.children; var items = _props.items; var filter = _props.filter; var sort = _props.sort; var Menu = _props.renderMenu; var Item = _props.renderItem; var props = _objectWithoutProperties(_props, ['children', 'items', 'filter', 'sort', 'renderMenu', 'renderItem']); var renderedItems = this.items.map(function (item, index) { var highlighted = _this2.state.highlighted === index; return _react2.default.cloneElement(Item({ item: item, index: index, highlighted: highlighted }), { onMouseEnter: _this2.handleMouseEnter(index), onMouseDown: _this2.handleMouseDown, onClick: _this2.handleSelectItem(item), key: '' + index + item.substr(0, 3) }); }); var renderedMenu = _react2.default.cloneElement(Menu({ items: renderedItems }), { onMouseLeave: this.handleMouseLeave }); return _react2.default.createElement( 'div', props, _react2.default.cloneElement(children, { onKeyDown: this.handleKeyDown, onFocus: this.handleFocus, onBlur: this.handleBlur, ref: 'input' }), this.state.open && renderedMenu ); } }, { key: 'input', get: function get() { return this.refs.input; } }, { key: 'items', get: function get() { var _props2 = this.props; var items = _props2.items; var filter = _props2.filter; var sort = _props2.sort; var limit = _props2.limit; var _ref = this.refs.input || {}; var _ref$value = _ref.value; var value = _ref$value === undefined ? '' : _ref$value; return items.filter(function (item) { return filter(item, value); }).sort(sort).slice(0, limit); } }]); return Autocomplete; }(_react.Component); Autocomplete.propTypes = { items: _react.PropTypes.array, filter: _react.PropTypes.func, sort: _react.PropTypes.any, limit: _react.PropTypes.number, renderMenu: _react.PropTypes.func, renderItem: _react.PropTypes.func, onSelectItem: _react.PropTypes.func, onFocus: _react.PropTypes.func, onBlur: _react.PropTypes.func, children: _react.PropTypes.element }; Autocomplete.defaultProps = { items: [], filter: function filter(item, query) { return item.toLowerCase().includes(query.toLowerCase()); }, sort: function sort() {}, renderMenu: function renderMenu(_ref2) { var items = _ref2.items; return _react2.default.createElement( 'ul', null, items ); }, renderItem: function renderItem(_ref3) { var item = _ref3.item; var highlighted = _ref3.highlighted; return highlighted ? _react2.default.createElement( 'em', null, _react2.default.createElement( 'li', null, item ) ) : _react2.default.createElement( 'li', null, item ); }, children: _react2.default.createElement('input', { type: 'text' }) }; exports.default = Autocomplete;