search-suggestion
Version:
Simple, lightweight, flexible search suggestion, autocomplete component
261 lines (231 loc) • 5.58 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import { scrollIntoView, getNodeById } from './utils';
const KEYS_EVENT = {
UP: 38,
DOWN: 40,
ESCAPE: 27,
ENTER: 13
};
class Suggestion extends React.Component {
constructor(props) {
super(props);
this.state = {
inputValue: '',
dataLength: 0,
currentValue: null,
selectedItem: null,
isOutsideClick: false,
isOpen: false,
highlightedIndex: 0
};
}
componentDidMount() {
document.addEventListener('mousedown', this.clickOutside);
if (this.props.initInputValue) {
this.setState({
inputValue: this.props.initInputValue,
dataLength: this.props.items.length
});
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.items.length !== this.props.items.length) {
this.setState({
dataLength: nextProps.items.length
});
}
if (nextProps.initInputValue !== this.props.initInputValue) {
this.setState({
inputValue: nextProps.initInputValue || ''
});
}
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.clickOutside);
}
onChange = e => {
this.setState({
inputValue: e.target.value,
isOpen: true
});
};
onBlurInput = e => {
if (this.state.isOutsideClick) {
this.setState({
isOpen: false
});
}
};
onFocusInput = e => {
this.setState({
isOutsideClick: false,
isOpen: true
});
};
onSelectItem = item => {
let displayName = {};
if (this.props.getDisplayName) {
displayName = {
inputValue: this.props.getDisplayName(item) || ''
};
}
if (this.props.onSelectedItem) {
this.props.onSelectedItem(item);
}
this.setState({
selectedItem: item,
isOpen: false,
...displayName
});
};
onKeyDown = e => {
let itemIndex = this.state.highlightedIndex;
if (this.state.isOpen && !this.state.isOutsideClick) {
switch (e.keyCode) {
case KEYS_EVENT.UP:
{
itemIndex--;
if (itemIndex < 0) {
itemIndex = 0;
}
}
break;
case KEYS_EVENT.DOWN:
{
itemIndex++;
if (itemIndex + 1 > this.state.dataLength) {
itemIndex = this.state.dataLength - 1;
}
}
break;
case KEYS_EVENT.ENTER:
{
this.onSelectItem(this.props.items[itemIndex]);
}
break;
default:
itemIndex = -1;
break;
}
const item = getNodeById(`item_${itemIndex}`);
if (!item) return;
const container = getNodeById('__listItemProps');
scrollIntoView(item, container);
this.setState({
highlightedIndex: itemIndex
});
}
};
getInputProps = props => {
const {
onChange: handleChange,
onBlur: handleBlur,
onFocus: handleFocus,
onKeyDown: handleKeyDown,
placeholder
} = props;
const { inputValue } = this.state;
const onKeyDown = e => {
this.onKeyDown(e);
if (typeof handleKeyDown === 'function') {
handleKeyDown(e);
}
};
const onChange = e => {
this.onChange(e);
if (typeof handleChange === 'function') {
handleChange(e);
}
};
const onBlur = e => {
this.onBlurInput(e);
if (typeof handleBlur === 'function') {
handleBlur(e);
}
};
const onFocus = e => {
this.onFocusInput(e);
if (typeof handleFocus === 'function') {
handleFocus(e);
}
};
return {
...props,
id: '__inputItemProps',
placeholder,
onKeyDown,
onChange,
onBlur,
onFocus,
value: inputValue
};
};
getListItemProps = props => {
return {
id: '__listItemProps'
};
};
getItemProps = props => {
const { item, index, handleSelectItem } = props;
const onClick = e => {
this.onSelectItem(item);
if (typeof handleSelectItem === 'function') {
handleSelectItem(e.target.value);
}
};
return {
onClick,
id: `item_${index}`
};
};
clickOutside = e => {
if (this.__autoComplete && !this.__autoComplete.contains(e.target)) {
this.setState({
isOutsideClick: true,
isOpen: false
});
}
};
clearInputValue = () => {
this.setState({
inputValue: ''
});
};
render() {
const { inputValue, selectedItem, isOpen, highlightedIndex } = this.state;
const { items, id = 'autoComplete' } = this.props;
return (
<div id={id} ref={el => (this.__autoComplete = el)}>
{this.props.children({
getInputProps: this.getInputProps,
getItemProps: this.getItemProps,
getListItemProps: this.getListItemProps,
clearInputValue: this.clearInputValue,
inputValue,
selectedItem,
isOpen,
items,
highlightedIndex
})}
</div>
);
}
}
Suggestion.propTypes = {
children: PropTypes.func.isRequired,
items: PropTypes.array.isRequired,
initInputValue: PropTypes.string,
placeholder: PropTypes.string,
id: PropTypes.string,
onSelectedItem: PropTypes.func,
onChange: PropTypes.func,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
handleSelectItem: PropTypes.func,
getDisplayName: PropTypes.func
};
Suggestion.defaultProps = {
items: []
};
export default Suggestion;