UNPKG

react-taginput

Version:

it's a react component for input multi tags while showing autocompleted dropdown list

237 lines (217 loc) 5.85 kB
/** * TODO * 1. need to add test * 2. click outside to close dropdown , not use input's blur * */ import React, { Component, PropTypes } from 'react' import classNames from 'classnames' // import { uuid } from '../../tool/utils' // action creators function startInput () { return { // isDropdownOpen: true, isInput: true, } } function blurInput () { return { inputValue: null, highlightIndex: 0, // isInput: false, } } function doingInput (val) { return { inputValue: val, highlightIndex: 0, } } function addTag (val, state) { let value = !!val.trim() ? [...state.value , val.trim() ] : [...state.value] return { value, inputValue: null, isInput: false, } } function delTag (index, state) { let value = [...state.value] value.splice(index,1) return { value, isInput: false, } } function moveHighlight (index, state) { return { highlightIndex: index } } // is targets match key or true function isMatchedWithKey(target, key){ if ( !!key ) { let keyMatched = target.match(key); return keyMatched && keyMatched.length } else { return true } } // get subset from target subscribed arr and keyword function getSubset( target, {sub, key=''} ){ return target.filter((item, index)=>{ return sub.indexOf( item ) == -1 && isMatchedWithKey( item, key ) }) } class TagInput extends Component{ constructor(props){ super(props) let value = props.valueFormater(props.value) this.state = { value, highlightIndex: 0, // isDropdownOpen: false, // isInput == isDropdownOpen isInput: false, inputValue: null, } // binding this.handleInputFocus = this.handleInputFocus.bind(this) this.handleInputBlur = this.handleInputBlur.bind(this) this.handleInputChange = this.handleInputChange.bind(this) this.handleInputKeyDown = this.handleInputKeyDown.bind(this) this.handleDel = this.handleDel.bind(this) this.handleDropdownMouseEnter = this.handleDropdownMouseEnter.bind(this) this.handleDropdownMouseClick = this.handleDropdownMouseClick.bind(this) } componentWillReceiveProps(nextProps){ let value = nextProps.valueFormater(nextProps.value) this.setState({ value, // dropdownList: getSubset( nextProps.autoComplete, value ) }) } getDropdownList(){ let { value, inputValue } = this.state let targetArr = !!inputValue ? [ inputValue, ...this.props.autoComplete ] : this.props.autoComplete return getSubset( targetArr, { sub: value, key: inputValue } ) } handleInputFocus(){ this.setState(startInput() ) } handleInputBlur(){ this.setState(blurInput() ) } handleInputChange(e){ this.setState(doingInput(e.target.value) ) } handleInputKeyDown(e){ let {value, highlightIndex } = this.state; let dropdownListLength = this.getDropdownList().length; let valueLength = value.length; let newState; switch(e.key){ case 'Enter': this.setState( addTag(e.target.value, this.state) ) break; case 'Backspace': !e.target.value && valueLength && this.setState( delTag(valueLength - 1, this.state) ) break; case 'ArrowUp': if( dropdownListLength ){ newState = moveHighlight( highlightIndex ? highlightIndex - 1 : dropdownListLength - 1 , this.state) this.setState(newState) } break; case 'ArrowDown': if( dropdownListLength ){ newState = moveHighlight( highlightIndex == dropdownListLength - 1 ? 0 : highlightIndex + 1, this.state) this.setState(newState) } break; } } handleDel(index){ this.setState( delTag( index, this.state ) ) } handleDropdownMouseEnter(e){ this.setState( moveHighlight( parseInt(e.target.dataset.index) ) ) } handleDropdownMouseClick(e){ // Problem here // this click will trigger input blur first, // then dropdown close , and cannot trigger this handler this.setState( addTag( e.target.textContent, this.state ) ) } renderTagList(){ let list = this.state.value.map((tag, index)=>{ return ( <li key={index} className="list-item"> <span className="label label-default pull-left"> {tag} <i className="fa fa-close" onClick={this.handleDel.bind( this, index )}></i> </span> </li> ) }) return <ul className="tag-list list">{list}</ul> } renderInputField(){ // <li className="list-item" key="input-li">{inputDOM}</li> return <input type="text" onFocus={this.handleInputFocus} onBlur={this.handleInputBlur} onChange={this.handleInputChange} onKeyDown={this.handleInputKeyDown} value = {this.state.inputValue} /> } renderDropdown(){ let { highlightIndex, isInput, value } = this.state let dropdownList = this.getDropdownList() let listDOM = dropdownList.map((item, index)=>{ let className = classNames( 'list-item', { 'list-item-highlight': highlightIndex == index } ) // dropdown list is consist of inputValue and autoComplete list // so index here need to minus 1 // data-index here for move highlight when is triggered mouseenter event return ( <li className={className} data-index={index} key={index} onMouseEnter={this.handleDropdownMouseEnter} onClick={this.handleDropdownMouseClick} >{item}</li> ) }) console.log('renderDropdown', isInput) return isInput ? ( <div className="dropdown"> <ul className="auto-complete-list"> {listDOM} </ul> </div> ) : null } render(){ return( <div className="c-tag-input" > <div> {this.renderTagList()} {this.renderInputField()} </div> {this.renderDropdown()} </div> ) } } TagInput.propTypes = { value: PropTypes.array.isRequired, valueFormater: PropTypes.func, autoComplete: PropTypes.array, max: PropTypes.number, onlyQnique: PropTypes.bool, } TagInput.defaultProps = { value: [], valueFormater: (val) => val, autoComplete: [], max: -1, onlyQnique: true, } export default TagInput