blueprint-fuzzy-select
Version:
Select options using fuzzy search suggestions.
376 lines (326 loc) • 12.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
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 _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; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _core = require('@blueprintjs/core');
var _fuzzySearch = require('fuzzy-search');
var _fuzzySearch2 = _interopRequireDefault(_fuzzySearch);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
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 FuzzySelect = function (_Component) {
_inherits(FuzzySelect, _Component);
function FuzzySelect(props, context) {
_classCallCheck(this, FuzzySelect);
var _this = _possibleConstructorReturn(this, (FuzzySelect.__proto__ || Object.getPrototypeOf(FuzzySelect)).call(this, props, context));
_this.state = {
input: '',
selected: null,
suggestions: [],
focused: false
};
_this.handleFocus = _this.handleFocus.bind(_this);
_this.handleKeydown = _this.handleKeydown.bind(_this);
_this.handleWindowClick = _this.handleWindowClick.bind(_this);
_this.handleInput = _this.handleInput.bind(_this);
return _this;
}
_createClass(FuzzySelect, [{
key: 'componentWillMount',
value: function componentWillMount() {
var selected = this.props.selected;
if (selected !== undefined) {
this.setState({ selected: selected });
this.resetInput(selected);
}
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
window.addEventListener('click', this.handleWindowClick, false);
window.addEventListener('keydown', this.handleKeydown, false);
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
var selected = nextProps.selected;
if (selected !== undefined) this.setState({ selected: selected });
}
}, {
key: 'componentWillUpdate',
value: function componentWillUpdate(nextProps, nextState) {
if (this.state.selected !== nextState.selected) {
this.resetInput(nextState.selected);
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
window.removeEventListener('click', this.handleWindowClick, false);
window.removeEventListener('keydown', this.handleKeydown, false);
}
}, {
key: 'resetInput',
value: function resetInput() {
var selected = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.state.selected;
var field = this.props.field;
var input = selected ? selected[field] : '';
this.setState({ input: input });
}
}, {
key: 'handleFocus',
value: function handleFocus() {
var onFocus = this.props.onFocus;
if (onFocus) onFocus();
this.setState({ input: '', focused: true });
}
}, {
key: 'handleWindowClick',
value: function handleWindowClick(event) {
var isParent = function isParent(reference, target) {
return target === reference || target.parentNode && isParent(reference, target.parentNode);
};
if (!isParent(this.inputWrapper, event.target)) {
this.handleBlur('Click', event);
}
}
}, {
key: 'handleKeydown',
value: function handleKeydown(event) {
switch (event.key) {
case 'Tab':
this.handleBlur('Tab', event);
break;
case 'Enter':
this.handleEnter(event);
break;
default: // pass
}
}
}, {
key: 'handleBlur',
value: function handleBlur(action) {
var _props = this.props,
selectOnBlur = _props.selectOnBlur,
onBlur = _props.onBlur;
var focused = this.state.focused;
if (focused) {
if (onBlur) onBlur();
if (selectOnBlur === true || selectOnBlur === action) {
this.selectFirst();
} else {
this.resetInput();
this.setState({ focused: false });
}
}
}
}, {
key: 'handleEnter',
value: function handleEnter(event) {
var selectOnEnter = this.props.selectOnEnter;
var focused = this.state.focused;
if (focused && selectOnEnter) {
event.preventDefault();
this.selectFirst();
this.blurInput();
}
}
}, {
key: 'blurInput',
value: function blurInput() {
var inputDOM = this.inputDOM;
if (this.inputDOM.tagName === 'input') {
inputDOM = this.inputDOM;
} else {
inputDOM = this.inputDOM.querySelector('input');
}
inputDOM.blur();
}
}, {
key: 'selectFirst',
value: function selectFirst() {
var onAdd = this.props.onAdd;
var _state = this.state,
input = _state.input,
focused = _state.focused,
suggestions = _state.suggestions;
if (focused && input) {
if (suggestions.length > 0) {
this.chooseOption(suggestions[0]);
return true;
} else if (onAdd) {
this.addOption(input);
return true;
}
}
}
}, {
key: 'handleInput',
value: function handleInput(event) {
var _props2 = this.props,
onInput = _props2.onInput,
onSuggestions = _props2.onSuggestions;
var input = event.target.value;
var suggestions = this.search(input);
if (onInput) onInput(input);
if (onSuggestions) onSuggestions(suggestions);
this.setState({ input: input, suggestions: suggestions });
}
}, {
key: 'search',
value: function search(input) {
var _props3 = this.props,
haystack = _props3.haystack,
field = _props3.field,
caseSensitive = _props3.caseSensitive,
sort = _props3.sort;
var keys = field ? [field] : [];
if (haystack.length > 0) {
this.searcher = new _fuzzySearch2.default(haystack, keys, { caseSensitive: caseSensitive, sort: sort });
return this.searcher.search(input);
}
return [];
}
}, {
key: 'chooseOption',
value: function chooseOption(selected) {
var _props4 = this.props,
field = _props4.field,
onSelect = _props4.onSelect;
if (onSelect) onSelect(selected);
if (this.props.selected === undefined) {
this.setState({
selected: selected,
input: selected[field],
suggestions: [],
focused: false
});
} else {
this.setState({
suggestions: [],
focused: false
});
this.resetInput();
}
}
}, {
key: 'addOption',
value: function addOption(input) {
var onAdd = this.props.onAdd;
var selected = onAdd(input);
if (selected) {
this.setState({
input: input,
selected: selected,
suggestions: [],
focused: false
});
} else {
this.setState({
input: '',
selected: null,
suggestions: [],
focused: false
});
}
}
}, {
key: 'render',
value: function render() {
var _this2 = this;
var _props5 = this.props,
field = _props5.field,
onAdd = _props5.onAdd;
var _props6 = this.props,
className = _props6.className,
style = _props6.style;
var _state2 = this.state,
input = _state2.input,
suggestions = _state2.suggestions,
focused = _state2.focused;
var inputElement = _react2.default.cloneElement(this.props.children, {
onChange: this.handleInput,
onFocus: this.handleFocus,
value: input,
ref: function ref(inputDOM) {
return _this2.inputDOM = _reactDom2.default.findDOMNode(inputDOM);
}
});
className = className ? className + ' input-suggest' : 'input-suggest';
style = _extends({ position: 'relative' }, style);
var showNew = onAdd && input;
var showSuggestions = suggestions.length > 0;
var suggestionsCount = 0;
return _react2.default.createElement(
'div',
{
className: className,
style: style,
ref: function ref(div) {
return _this2.inputWrapper = div;
}
},
inputElement,
focused && (showNew || showSuggestions) && _react2.default.createElement(
'div',
{
style: {
position: 'absolute',
top: '100%',
right: '1px',
left: '1px'
}
},
_react2.default.createElement(
_core.Menu,
{ className: 'pt-elevation-1' },
suggestions.map(function (suggestion) {
return _react2.default.createElement(_core.MenuItem, {
key: 'suggestion-' + (suggestionsCount += 1),
text: field ? suggestion[field] : suggestion,
onClick: function onClick() {
return _this2.chooseOption(suggestion);
}
});
}),
showNew && _react2.default.createElement(_core.MenuItem, {
key: 'suggestion-add-new',
text: input,
iconName: 'add',
onClick: function onClick() {
return _this2.addOption(input);
}
})
)
)
);
}
}]);
return FuzzySelect;
}(_react.Component);
FuzzySelect.propTypes = {
haystack: _react.PropTypes.arrayOf(_react2.default.PropTypes.oneOfType([_react.PropTypes.object, _react.PropTypes.string])).isRequired,
selected: _react.PropTypes.oneOfType([_react.PropTypes.object, _react.PropTypes.string]),
field: _react.PropTypes.string,
caseSensitive: _react.PropTypes.bool,
sort: _react.PropTypes.bool,
selectOnBlur: _react.PropTypes.oneOfType([_react.PropTypes.bool, _react.PropTypes.string]),
selectOnEnter: _react.PropTypes.bool,
onSelect: _react.PropTypes.func.isRequired,
onAdd: _react.PropTypes.func,
onInput: _react.PropTypes.func,
onSuggestions: _react.PropTypes.func,
onFocus: _react.PropTypes.func,
onBlur: _react.PropTypes.func,
className: _react.PropTypes.string,
style: _react.PropTypes.objectOf(_react.PropTypes.string),
children: _react.PropTypes.element
};
exports.default = FuzzySelect;