react-tag-input
Advanced tools
Comparing version 4.9.1 to 5.0.0
@@ -1,2 +0,2 @@ | ||
"use strict"; | ||
'use strict'; | ||
@@ -7,28 +7,30 @@ 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 _react = require("react"); | ||
var _react = require('react'); | ||
var _react2 = _interopRequireDefault(_react); | ||
var _reactDom = require("react-dom"); | ||
var _reactDom = require('react-dom'); | ||
var _reactDom2 = _interopRequireDefault(_reactDom); | ||
var _reactDnd = require("react-dnd"); | ||
var _reactDnd = require('react-dnd'); | ||
var _reactDndHtml5Backend = require("react-dnd-html5-backend"); | ||
var _reactDndHtml5Backend = require('react-dnd-html5-backend'); | ||
var _reactDndHtml5Backend2 = _interopRequireDefault(_reactDndHtml5Backend); | ||
var _Suggestions = require("./Suggestions"); | ||
var _Suggestions = require('./Suggestions'); | ||
var _Suggestions2 = _interopRequireDefault(_Suggestions); | ||
var _propTypes = require("prop-types"); | ||
var _propTypes = require('prop-types'); | ||
var _propTypes2 = _interopRequireDefault(_propTypes); | ||
var _Tag = require("./Tag"); | ||
var _Tag = require('./Tag'); | ||
var _Tag2 = _interopRequireDefault(_Tag); | ||
var _constants = require('./constants'); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -42,25 +44,5 @@ | ||
// Constants | ||
var Keys = { | ||
ENTER: 13, | ||
TAB: 9, | ||
BACKSPACE: 8, | ||
UP_ARROW: 38, | ||
DOWN_ARROW: 40, | ||
ESCAPE: 27 | ||
}; | ||
//Constants | ||
var DEFAULT_PLACEHOLDER = "Add new tag"; | ||
var DefaultClassNames = { | ||
tags: "ReactTags__tags", | ||
tagInput: "ReactTags__tagInput", | ||
tagInputField: "ReactTags__tagInputField", | ||
selected: "ReactTags__selected", | ||
tag: "ReactTags__tag", | ||
remove: "ReactTags__remove", | ||
suggestions: "ReactTags__suggestions", | ||
activeSuggestion: "ReactTags__activeSuggestion" | ||
}; | ||
var ReactTags = function (_Component) { | ||
@@ -74,5 +56,32 @@ _inherits(ReactTags, _Component); | ||
_this.getTagItems = function () { | ||
var _this$props = _this.props, | ||
tags = _this$props.tags, | ||
labelField = _this$props.labelField, | ||
removeComponent = _this$props.removeComponent, | ||
readOnly = _this$props.readOnly, | ||
handleDrag = _this$props.handleDrag; | ||
var classNames = _this.state.classNames; | ||
var moveTag = handleDrag ? _this.moveTag : null; | ||
return tags.map(function (tag, index) { | ||
return _react2.default.createElement(_Tag2.default, { | ||
key: tag.text + '-index', | ||
index: index, | ||
tag: tag, | ||
labelField: labelField, | ||
onDelete: _this.handleDelete.bind(_this, index), | ||
moveTag: moveTag, | ||
removeComponent: removeComponent, | ||
onTagClicked: _this.handleTagClick.bind(_this, index), | ||
readOnly: readOnly, | ||
classNames: classNames | ||
}); | ||
}); | ||
}; | ||
_this.state = { | ||
suggestions: _this.props.suggestions, | ||
query: "", | ||
query: '', | ||
isFocused: false, | ||
@@ -96,17 +105,19 @@ selectedIndex: -1, | ||
_createClass(ReactTags, [{ | ||
key: "componentWillMount", | ||
key: 'componentWillMount', | ||
value: function componentWillMount() { | ||
this.setState({ | ||
classNames: _extends({}, DefaultClassNames, this.props.classNames) | ||
classNames: _extends({}, _constants.DEFAULT_CLASSNAMES, this.props.classNames) | ||
}); | ||
} | ||
}, { | ||
key: "resetAndFocusInput", | ||
key: 'resetAndFocusInput', | ||
value: function resetAndFocusInput() { | ||
this.setState({ query: "" }); | ||
this.textInput.value = ""; | ||
this.textInput.focus(); | ||
this.setState({ query: '' }); | ||
if (this.textInput) { | ||
this.textInput.value = ''; | ||
this.textInput.focus(); | ||
} | ||
} | ||
}, { | ||
key: "componentDidMount", | ||
key: 'componentDidMount', | ||
value: function componentDidMount() { | ||
@@ -122,3 +133,3 @@ var _props = this.props, | ||
}, { | ||
key: "filteredSuggestions", | ||
key: 'filteredSuggestions', | ||
value: function filteredSuggestions(query, suggestions) { | ||
@@ -134,3 +145,3 @@ if (this.props.handleFilterSuggestions) { | ||
}, { | ||
key: "componentWillReceiveProps", | ||
key: 'componentWillReceiveProps', | ||
value: function componentWillReceiveProps(props) { | ||
@@ -140,11 +151,11 @@ var suggestions = this.filteredSuggestions(this.state.query, props.suggestions); | ||
suggestions: suggestions, | ||
classNames: _extends({}, DefaultClassNames, props.classNames) | ||
classNames: _extends({}, _constants.DEFAULT_CLASSNAMES, props.classNames) | ||
}); | ||
} | ||
}, { | ||
key: "handleDelete", | ||
key: 'handleDelete', | ||
value: function handleDelete(i, e) { | ||
this.props.handleDelete(i, e); | ||
if (!this.props.resetInputOnDelete) { | ||
this.textInput.focus(); | ||
this.textInput && this.textInput.focus(); | ||
} else { | ||
@@ -156,3 +167,3 @@ this.resetAndFocusInput(); | ||
}, { | ||
key: "handleTagClick", | ||
key: 'handleTagClick', | ||
value: function handleTagClick(i, e) { | ||
@@ -162,5 +173,4 @@ if (this.props.handleTagClick) { | ||
} | ||
if (!this.props.resetInputOnDelete) { | ||
this.textInput.focus(); | ||
this.textInput && this.textInput.focus(); | ||
} else { | ||
@@ -171,3 +181,3 @@ this.resetAndFocusInput(); | ||
}, { | ||
key: "handleChange", | ||
key: 'handleChange', | ||
value: function handleChange(e) { | ||
@@ -182,14 +192,12 @@ if (this.props.handleInputChange) { | ||
var selectedIndex = this.state.selectedIndex; | ||
if (selectedIndex >= suggestions.length) { | ||
selectedIndex = suggestions.length - 1; | ||
} | ||
this.setState({ | ||
query: query, | ||
suggestions: suggestions, | ||
selectedIndex: selectedIndex | ||
selectedIndex: selectedIndex >= suggestions.length ? suggestions.length - 1 : selectedIndex | ||
}); | ||
} | ||
}, { | ||
key: "handleFocus", | ||
key: 'handleFocus', | ||
value: function handleFocus(e) { | ||
@@ -203,3 +211,3 @@ var value = e.target.value.trim(); | ||
}, { | ||
key: "handleBlur", | ||
key: 'handleBlur', | ||
value: function handleBlur(e) { | ||
@@ -209,3 +217,5 @@ var value = e.target.value.trim(); | ||
this.props.handleInputBlur(value); | ||
this.textInput.value = ""; | ||
if (this.textInput) { | ||
this.textInput.value = ''; | ||
} | ||
} | ||
@@ -215,3 +225,3 @@ this.setState({ isFocused: false }); | ||
}, { | ||
key: "handleKeyDown", | ||
key: 'handleKeyDown', | ||
value: function handleKeyDown(e) { | ||
@@ -221,7 +231,8 @@ var _state = this.state, | ||
selectedIndex = _state.selectedIndex, | ||
suggestions = _state.suggestions; | ||
suggestions = _state.suggestions, | ||
selectionMode = _state.selectionMode; | ||
// hide suggestions menu on escape | ||
if (e.keyCode === Keys.ESCAPE) { | ||
if (e.keyCode === _constants.KEYS.ESCAPE) { | ||
e.preventDefault(); | ||
@@ -240,16 +251,15 @@ e.stopPropagation(); | ||
if (this.props.delimiters.indexOf(e.keyCode) !== -1 && !e.shiftKey) { | ||
if (e.keyCode !== Keys.TAB || query !== "") { | ||
if (e.keyCode !== _constants.KEYS.TAB || query !== '') { | ||
e.preventDefault(); | ||
} | ||
if (this.state.selectionMode && this.state.selectedIndex != -1) { | ||
query = this.state.suggestions[this.state.selectedIndex]; | ||
var selectedQuery = selectionMode && selectedIndex !== -1 ? suggestions[selectedIndex] : query; | ||
if (selectedQuery !== '') { | ||
this.addTag(selectedQuery); | ||
} | ||
if (query !== "") { | ||
this.addTag(query); | ||
} | ||
} | ||
// when backspace key is pressed and query is blank, delete tag | ||
if (e.keyCode === Keys.BACKSPACE && query == "" && this.props.allowDeleteFromEmptyInput) { | ||
if (e.keyCode === _constants.KEYS.BACKSPACE && query == '' && this.props.allowDeleteFromEmptyInput) { | ||
this.handleDelete(this.props.tags.length - 1, e); | ||
@@ -259,14 +269,6 @@ } | ||
// up arrow | ||
if (e.keyCode === Keys.UP_ARROW) { | ||
if (e.keyCode === _constants.KEYS.UP_ARROW) { | ||
e.preventDefault(); | ||
var _state2 = this.state, | ||
_selectedIndex = _state2.selectedIndex, | ||
_suggestions = _state2.suggestions; | ||
_selectedIndex = _selectedIndex <= 0 ? _suggestions.length - 1 : _selectedIndex - 1; | ||
this.setState({ | ||
selectedIndex: _selectedIndex, | ||
selectedIndex: selectedIndex <= 0 ? suggestions.length - 1 : selectedIndex - 1, | ||
selectionMode: true | ||
@@ -277,8 +279,6 @@ }); | ||
// down arrow | ||
if (e.keyCode === Keys.DOWN_ARROW) { | ||
if (e.keyCode === _constants.KEYS.DOWN_ARROW) { | ||
e.preventDefault(); | ||
var newSelectedIndex = suggestions.length === 0 ? -1 : (selectedIndex + 1) % suggestions.length; | ||
this.setState({ | ||
selectedIndex: newSelectedIndex, | ||
selectedIndex: suggestions.length === 0 ? -1 : (selectedIndex + 1) % suggestions.length, | ||
selectionMode: true | ||
@@ -289,3 +289,3 @@ }); | ||
}, { | ||
key: "handlePaste", | ||
key: 'handlePaste', | ||
value: function handlePaste(e) { | ||
@@ -302,3 +302,3 @@ var _this2 = this; | ||
var escapeRegex = function escapeRegex(str) { | ||
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); | ||
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); | ||
}; | ||
@@ -311,7 +311,7 @@ | ||
return String.fromCharCode(96 <= delimiter ? chrCode : delimiter); | ||
}).join("")); | ||
}).join('')); | ||
var clipboardData = e.clipboardData || window.clipboardData; | ||
var string = clipboardData.getData("text"); | ||
var regExp = new RegExp("[" + delimiterChars + "]+"); | ||
var string = clipboardData.getData('text'); | ||
var regExp = new RegExp('[' + delimiterChars + ']+'); | ||
string.split(regExp).forEach(function (tag) { | ||
@@ -322,3 +322,3 @@ return _this2.props.handleAddition(tag); | ||
}, { | ||
key: "addTag", | ||
key: 'addTag', | ||
value: function addTag(tag) { | ||
@@ -348,3 +348,3 @@ var tags = this.props.tags; | ||
this.setState({ | ||
query: "", | ||
query: '', | ||
selectionMode: false, | ||
@@ -357,3 +357,3 @@ selectedIndex: -1 | ||
}, { | ||
key: "handleSuggestionClick", | ||
key: 'handleSuggestionClick', | ||
value: function handleSuggestionClick(i, e) { | ||
@@ -363,3 +363,3 @@ this.addTag(this.state.suggestions[i]); | ||
}, { | ||
key: "handleSuggestionHover", | ||
key: 'handleSuggestionHover', | ||
value: function handleSuggestionHover(i, e) { | ||
@@ -372,3 +372,3 @@ this.setState({ | ||
}, { | ||
key: "moveTag", | ||
key: 'moveTag', | ||
value: function moveTag(dragIndex, hoverIndex) { | ||
@@ -385,3 +385,3 @@ var tags = this.props.tags; | ||
}, { | ||
key: "render", | ||
key: 'render', | ||
value: function render() { | ||
@@ -392,16 +392,3 @@ var _this3 = this; | ||
var tagItems = this.props.tags.map(function (tag, i) { | ||
return _react2.default.createElement(_Tag2.default, { | ||
key: tag.id ? tag.id : i, | ||
index: i, | ||
tag: tag, | ||
labelField: this.props.labelField, | ||
onDelete: this.handleDelete.bind(this, i), | ||
moveTag: moveTag, | ||
removeComponent: this.props.removeComponent, | ||
onTagClicked: this.handleTagClick.bind(this, i), | ||
readOnly: this.props.readOnly, | ||
classNames: this.state.classNames | ||
}); | ||
}.bind(this)); | ||
var tagItems = this.getTagItems(); | ||
@@ -418,5 +405,5 @@ // get the suggestions for the given query | ||
var tagInput = !this.props.readOnly ? _react2.default.createElement( | ||
"div", | ||
'div', | ||
{ className: this.state.classNames.tagInput }, | ||
_react2.default.createElement("input", { | ||
_react2.default.createElement('input', { | ||
ref: function ref(input) { | ||
@@ -426,5 +413,5 @@ _this3.textInput = input; | ||
className: this.state.classNames.tagInputField, | ||
type: "text", | ||
type: 'text', | ||
placeholder: placeholder, | ||
"aria-label": placeholder, | ||
'aria-label': placeholder, | ||
onFocus: this.handleFocus, | ||
@@ -454,6 +441,6 @@ onBlur: this.handleBlur, | ||
return _react2.default.createElement( | ||
"div", | ||
'div', | ||
{ className: this.state.classNames.tags }, | ||
_react2.default.createElement( | ||
"div", | ||
'div', | ||
{ className: this.state.classNames.selected }, | ||
@@ -502,6 +489,6 @@ tagItems, | ||
ReactTags.defaultProps = { | ||
placeholder: DEFAULT_PLACEHOLDER, | ||
placeholder: _constants.DEFAULT_PLACEHOLDER, | ||
tags: [], | ||
suggestions: [], | ||
delimiters: [Keys.ENTER, Keys.TAB], | ||
delimiters: [_constants.KEYS.ENTER, _constants.KEYS.TAB], | ||
autofocus: true, | ||
@@ -520,3 +507,3 @@ inline: true, | ||
WithOutContext: ReactTags, | ||
Keys: Keys | ||
KEYS: _constants.KEYS | ||
}; |
@@ -1,2 +0,2 @@ | ||
"use strict"; | ||
'use strict'; | ||
@@ -9,14 +9,18 @@ Object.defineProperty(exports, "__esModule", { | ||
var _react = require("react"); | ||
var _react = require('react'); | ||
var _react2 = _interopRequireDefault(_react); | ||
var _propTypes = require("prop-types"); | ||
var _propTypes = require('prop-types'); | ||
var _propTypes2 = _interopRequireDefault(_propTypes); | ||
var _isEqual = require("lodash/isEqual"); | ||
var _isEqual = require('lodash/isEqual'); | ||
var _isEqual2 = _interopRequireDefault(_isEqual); | ||
var _number = require('core-js/library/fn/number'); | ||
var _number2 = _interopRequireDefault(_number); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
@@ -30,2 +34,5 @@ | ||
// Polyfills | ||
var maybeScrollSuggestionIntoView = function maybeScrollSuggestionIntoView(suggestionEl, suggestionsContainer) { | ||
@@ -58,5 +65,5 @@ var containerHeight = suggestionsContainer.offsetHeight; | ||
return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Suggestions.__proto__ || Object.getPrototypeOf(Suggestions)).call.apply(_ref, [this].concat(args))), _this), _this.markIt = function (input, query) { | ||
var escapedRegex = query.trim().replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); | ||
var escapedRegex = query.trim().replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&'); | ||
return { | ||
__html: input.replace(RegExp(escapedRegex, "gi"), "<mark>$&</mark>") | ||
__html: input.replace(RegExp(escapedRegex, 'gi'), '<mark>$&</mark>') | ||
}; | ||
@@ -67,3 +74,3 @@ }, _this.shouldRenderSuggestions = function (query) { | ||
var minQueryLength = Number.isInteger(props.minQueryLength) ? props.minQueryLength : 2; | ||
var minQueryLength = _number2.default.isInteger(props.minQueryLength) ? props.minQueryLength : 2; | ||
return query.length >= minQueryLength && props.isFocused; | ||
@@ -74,3 +81,3 @@ }, _temp), _possibleConstructorReturn(_this, _ret); | ||
_createClass(Suggestions, [{ | ||
key: "shouldComponentUpdate", | ||
key: 'shouldComponentUpdate', | ||
value: function shouldComponentUpdate(nextProps) { | ||
@@ -83,3 +90,3 @@ var props = this.props; | ||
}, { | ||
key: "componentDidUpdate", | ||
key: 'componentDidUpdate', | ||
value: function componentDidUpdate(prevProps) { | ||
@@ -100,3 +107,3 @@ var _props = this.props, | ||
}, { | ||
key: "render", | ||
key: 'render', | ||
value: function render() { | ||
@@ -109,3 +116,3 @@ var _this3 = this; | ||
return _react2.default.createElement( | ||
"li", | ||
'li', | ||
{ | ||
@@ -115,4 +122,4 @@ key: i, | ||
onMouseOver: props.handleHover.bind(null, i), | ||
className: i == props.selectedIndex ? props.classNames.activeSuggestion : "" }, | ||
_react2.default.createElement("span", { dangerouslySetInnerHTML: this.markIt(item, props.query) }) | ||
className: i == props.selectedIndex ? props.classNames.activeSuggestion : '' }, | ||
_react2.default.createElement('span', { dangerouslySetInnerHTML: this.markIt(item, props.query) }) | ||
); | ||
@@ -128,3 +135,3 @@ }.bind(this)); | ||
return _react2.default.createElement( | ||
"div", | ||
'div', | ||
{ | ||
@@ -136,7 +143,7 @@ ref: function ref(elem) { | ||
_react2.default.createElement( | ||
"ul", | ||
'ul', | ||
null, | ||
" ", | ||
' ', | ||
suggestions, | ||
" " | ||
' ' | ||
) | ||
@@ -143,0 +150,0 @@ ); |
@@ -1,2 +0,2 @@ | ||
"use strict"; | ||
'use strict'; | ||
@@ -7,15 +7,15 @@ Object.defineProperty(exports, "__esModule", { | ||
var _react = require("react"); | ||
var _react = require('react'); | ||
var _react2 = _interopRequireDefault(_react); | ||
var _reactDom = require("react-dom"); | ||
var _reactDom = require('react-dom'); | ||
var _reactDnd = require("react-dnd"); | ||
var _reactDnd = require('react-dnd'); | ||
var _propTypes = require("prop-types"); | ||
var _propTypes = require('prop-types'); | ||
var _propTypes2 = _interopRequireDefault(_propTypes); | ||
var _flow = require("lodash/flow"); | ||
var _flow = require('lodash/flow'); | ||
@@ -32,7 +32,7 @@ var _flow2 = _interopRequireDefault(_flow); | ||
var ItemTypes = { TAG: "tag" }; | ||
var ItemTypes = { TAG: 'tag' }; | ||
var tagSource = { | ||
beginDrag: function beginDrag(props) { | ||
return { id: props.tag.id, index: props.index }; | ||
return { id: props.tag.index, index: props.index }; | ||
}, | ||
@@ -91,3 +91,3 @@ canDrag: function canDrag(props) { | ||
if (props.readOnly) { | ||
return _react2.default.createElement("span", null); | ||
return _react2.default.createElement('span', null); | ||
} | ||
@@ -101,3 +101,3 @@ | ||
return _react2.default.createElement( | ||
"a", | ||
'a', | ||
{ onClick: props.onClick, className: props.className }, | ||
@@ -135,3 +135,3 @@ String.fromCharCode(215) | ||
var tagComponent = _react2.default.createElement( | ||
"span", | ||
'span', | ||
{ | ||
@@ -172,3 +172,3 @@ style: { opacity: isDragging ? 0 : 1 }, | ||
Tag.defaultProps = { | ||
labelField: "text", | ||
labelField: 'text', | ||
readOnly: false | ||
@@ -175,0 +175,0 @@ }; |
// Set up test data | ||
const Countries = [ | ||
"Afghanistan", | ||
"Albania", | ||
"Algeria", | ||
"Andorra", | ||
"Angola", | ||
"Anguilla", | ||
"Antigua & Barbuda", | ||
"Argentina", | ||
"Armenia", | ||
"Aruba", | ||
"Australia", | ||
"Austria", | ||
"Azerbaijan", | ||
"Bahamas", | ||
"Bahrain", | ||
"Bangladesh", | ||
"Barbados", | ||
"Belarus", | ||
"Belgium", | ||
"Belize", | ||
"Benin", | ||
"Bermuda", | ||
"Bhutan", | ||
"Bolivia", | ||
"Bosnia & Herzegovina", | ||
"Botswana", | ||
"Brazil", | ||
"British Virgin Islands", | ||
"Brunei", | ||
"Bulgaria", | ||
"Burkina Faso", | ||
"Burundi", | ||
"Cambodia", | ||
"Cameroon", | ||
"Cape Verde", | ||
"Cayman Islands", | ||
"Chad", | ||
"Chile", | ||
"China", | ||
"Colombia", | ||
"Congo", | ||
"Cook Islands", | ||
"Costa Rica", | ||
"Cote D Ivoire", | ||
"Croatia", | ||
"Cruise Ship", | ||
"Cuba", | ||
"Cyprus", | ||
"Czech Republic", | ||
"Denmark", | ||
"Djibouti", | ||
"Dominica", | ||
"Dominican Republic", | ||
"Ecuador", | ||
"Egypt", | ||
"El Salvador", | ||
"Equatorial Guinea", | ||
"Estonia", | ||
"Ethiopia", | ||
"Falkland Islands", | ||
"Faroe Islands", | ||
"Fiji", | ||
"Finland", | ||
"France", | ||
"French Polynesia", | ||
"French West Indies", | ||
"Gabon", | ||
"Gambia", | ||
"Georgia", | ||
"Germany", | ||
"Ghana", | ||
"Gibraltar", | ||
"Greece", | ||
"Greenland", | ||
"Grenada", | ||
"Guam", | ||
"Guatemala", | ||
"Guernsey", | ||
"Guinea", | ||
"Guinea Bissau", | ||
"Guyana", | ||
"Haiti", | ||
"Honduras", | ||
"Hong Kong", | ||
"Hungary", | ||
"Iceland", | ||
"India", | ||
"Indonesia", | ||
"Iran", | ||
"Iraq", | ||
"Ireland", | ||
"Isle of Man", | ||
"Israel", | ||
"Italy", | ||
"Jamaica", | ||
"Japan", | ||
"Jersey", | ||
"Jordan", | ||
"Kazakhstan", | ||
"Kenya", | ||
"Kuwait", | ||
"Kyrgyz Republic", | ||
"Laos", | ||
"Latvia", | ||
"Lebanon", | ||
"Lesotho", | ||
"Liberia", | ||
"Libya", | ||
"Liechtenstein", | ||
"Lithuania", | ||
"Luxembourg", | ||
"Macau", | ||
"Macedonia", | ||
"Madagascar", | ||
"Malawi", | ||
"Malaysia", | ||
"Maldives", | ||
"Mali", | ||
"Malta", | ||
"Mauritania", | ||
"Mauritius", | ||
"Mexico", | ||
"Moldova", | ||
"Monaco", | ||
"Mongolia", | ||
"Montenegro", | ||
"Montserrat", | ||
"Morocco", | ||
"Mozambique", | ||
"Namibia", | ||
"Nepal", | ||
"Netherlands", | ||
"Netherlands Antilles", | ||
"New Caledonia", | ||
"New Zealand", | ||
"Nicaragua", | ||
"Niger", | ||
"Nigeria", | ||
"Norway", | ||
"Oman", | ||
"Pakistan", | ||
"Palestine", | ||
"Panama", | ||
"Papua New Guinea", | ||
"Paraguay", | ||
"Peru", | ||
"Philippines", | ||
"Poland", | ||
"Portugal", | ||
"Puerto Rico", | ||
"Qatar", | ||
"Reunion", | ||
"Romania", | ||
"Russia", | ||
"Rwanda", | ||
"Saint Pierre & Miquelon", | ||
"Samoa", | ||
"San Marino", | ||
"Satellite", | ||
"Saudi Arabia", | ||
"Senegal", | ||
"Serbia", | ||
"Seychelles", | ||
"Sierra Leone", | ||
"Singapore", | ||
"Slovakia", | ||
"Slovenia", | ||
"South Africa", | ||
"South Korea", | ||
"Spain", | ||
"Sri Lanka", | ||
"St Kitts & Nevis", | ||
"St Lucia", | ||
"St Vincent", | ||
"St. Lucia", | ||
"Sudan", | ||
"Suriname", | ||
"Swaziland", | ||
"Sweden", | ||
"Switzerland", | ||
"Syria", | ||
"Taiwan", | ||
"Tajikistan", | ||
"Tanzania", | ||
"Thailand", | ||
'Afghanistan', | ||
'Albania', | ||
'Algeria', | ||
'Andorra', | ||
'Angola', | ||
'Anguilla', | ||
'Antigua & Barbuda', | ||
'Argentina', | ||
'Armenia', | ||
'Aruba', | ||
'Australia', | ||
'Austria', | ||
'Azerbaijan', | ||
'Bahamas', | ||
'Bahrain', | ||
'Bangladesh', | ||
'Barbados', | ||
'Belarus', | ||
'Belgium', | ||
'Belize', | ||
'Benin', | ||
'Bermuda', | ||
'Bhutan', | ||
'Bolivia', | ||
'Bosnia & Herzegovina', | ||
'Botswana', | ||
'Brazil', | ||
'British Virgin Islands', | ||
'Brunei', | ||
'Bulgaria', | ||
'Burkina Faso', | ||
'Burundi', | ||
'Cambodia', | ||
'Cameroon', | ||
'Cape Verde', | ||
'Cayman Islands', | ||
'Chad', | ||
'Chile', | ||
'China', | ||
'Colombia', | ||
'Congo', | ||
'Cook Islands', | ||
'Costa Rica', | ||
'Cote D Ivoire', | ||
'Croatia', | ||
'Cruise Ship', | ||
'Cuba', | ||
'Cyprus', | ||
'Czech Republic', | ||
'Denmark', | ||
'Djibouti', | ||
'Dominica', | ||
'Dominican Republic', | ||
'Ecuador', | ||
'Egypt', | ||
'El Salvador', | ||
'Equatorial Guinea', | ||
'Estonia', | ||
'Ethiopia', | ||
'Falkland Islands', | ||
'Faroe Islands', | ||
'Fiji', | ||
'Finland', | ||
'France', | ||
'French Polynesia', | ||
'French West Indies', | ||
'Gabon', | ||
'Gambia', | ||
'Georgia', | ||
'Germany', | ||
'Ghana', | ||
'Gibraltar', | ||
'Greece', | ||
'Greenland', | ||
'Grenada', | ||
'Guam', | ||
'Guatemala', | ||
'Guernsey', | ||
'Guinea', | ||
'Guinea Bissau', | ||
'Guyana', | ||
'Haiti', | ||
'Honduras', | ||
'Hong Kong', | ||
'Hungary', | ||
'Iceland', | ||
'India', | ||
'Indonesia', | ||
'Iran', | ||
'Iraq', | ||
'Ireland', | ||
'Isle of Man', | ||
'Israel', | ||
'Italy', | ||
'Jamaica', | ||
'Japan', | ||
'Jersey', | ||
'Jordan', | ||
'Kazakhstan', | ||
'Kenya', | ||
'Kuwait', | ||
'Kyrgyz Republic', | ||
'Laos', | ||
'Latvia', | ||
'Lebanon', | ||
'Lesotho', | ||
'Liberia', | ||
'Libya', | ||
'Liechtenstein', | ||
'Lithuania', | ||
'Luxembourg', | ||
'Macau', | ||
'Macedonia', | ||
'Madagascar', | ||
'Malawi', | ||
'Malaysia', | ||
'Maldives', | ||
'Mali', | ||
'Malta', | ||
'Mauritania', | ||
'Mauritius', | ||
'Mexico', | ||
'Moldova', | ||
'Monaco', | ||
'Mongolia', | ||
'Montenegro', | ||
'Montserrat', | ||
'Morocco', | ||
'Mozambique', | ||
'Namibia', | ||
'Nepal', | ||
'Netherlands', | ||
'Netherlands Antilles', | ||
'New Caledonia', | ||
'New Zealand', | ||
'Nicaragua', | ||
'Niger', | ||
'Nigeria', | ||
'Norway', | ||
'Oman', | ||
'Pakistan', | ||
'Palestine', | ||
'Panama', | ||
'Papua New Guinea', | ||
'Paraguay', | ||
'Peru', | ||
'Philippines', | ||
'Poland', | ||
'Portugal', | ||
'Puerto Rico', | ||
'Qatar', | ||
'Reunion', | ||
'Romania', | ||
'Russia', | ||
'Rwanda', | ||
'Saint Pierre & Miquelon', | ||
'Samoa', | ||
'San Marino', | ||
'Satellite', | ||
'Saudi Arabia', | ||
'Senegal', | ||
'Serbia', | ||
'Seychelles', | ||
'Sierra Leone', | ||
'Singapore', | ||
'Slovakia', | ||
'Slovenia', | ||
'South Africa', | ||
'South Korea', | ||
'Spain', | ||
'Sri Lanka', | ||
'St Kitts & Nevis', | ||
'St Lucia', | ||
'St Vincent', | ||
'St. Lucia', | ||
'Sudan', | ||
'Suriname', | ||
'Swaziland', | ||
'Sweden', | ||
'Switzerland', | ||
'Syria', | ||
'Taiwan', | ||
'Tajikistan', | ||
'Tanzania', | ||
'Thailand', | ||
"Timor L'Este", | ||
"Togo", | ||
"Tonga", | ||
"Trinidad & Tobago", | ||
"Tunisia", | ||
"Turkey", | ||
"Turkmenistan", | ||
"Turks & Caicos", | ||
"Uganda", | ||
"Ukraine", | ||
"United Arab Emirates", | ||
"United Kingdom", | ||
"United States of America", | ||
"Uruguay", | ||
"Uzbekistan", | ||
"Venezuela", | ||
"Vietnam", | ||
"Virgin Islands (US)", | ||
"Yemen", | ||
"Zambia", | ||
"Zimbabwe", | ||
'Togo', | ||
'Tonga', | ||
'Trinidad & Tobago', | ||
'Tunisia', | ||
'Turkey', | ||
'Turkmenistan', | ||
'Turks & Caicos', | ||
'Uganda', | ||
'Ukraine', | ||
'United Arab Emirates', | ||
'United Kingdom', | ||
'United States of America', | ||
'Uruguay', | ||
'Uzbekistan', | ||
'Venezuela', | ||
'Vietnam', | ||
'Virgin Islands (US)', | ||
'Yemen', | ||
'Zambia', | ||
'Zimbabwe', | ||
]; | ||
const suggestions = Countries.map((country) => { | ||
return { | ||
id: country, | ||
text: country | ||
} | ||
}) | ||
/* | ||
@@ -226,4 +233,4 @@ * If your app already uses react-dnd, then having multiple | ||
this.state = { | ||
tags: [{ id: 1, text: "Thailand" }, { id: 2, text: "India" }], | ||
suggestions: Countries, | ||
tags: [{ id: 'Thailand', text: 'Thailand' }, { id: 'India', text: 'India' }], | ||
suggestions: suggestions, | ||
}; | ||
@@ -237,4 +244,5 @@ this.handleDelete = this.handleDelete.bind(this); | ||
handleDelete(i) { | ||
const { tags } = this.state; | ||
this.setState({ | ||
tags: this.state.tags.filter((tag, index) => index !== i), | ||
tags: tags.filter((tag, index) => index !== i), | ||
}); | ||
@@ -244,4 +252,4 @@ } | ||
handleAddition(tag) { | ||
let { tags } = this.state; | ||
this.setState({ tags: [...tags, { id: tags.length + 1, text: tag }] }); | ||
const { tags } = this.state; | ||
this.setState({ tags: [...tags, ...[tag]] }); | ||
} | ||
@@ -251,9 +259,9 @@ | ||
const tags = [...this.state.tags]; | ||
const newTags = tags.slice(); | ||
// mutate array | ||
tags.splice(currPos, 1); | ||
tags.splice(newPos, 0, tag); | ||
newTags.splice(currPos, 1); | ||
newTags.splice(newPos, 0, tag); | ||
// re-render | ||
this.setState({ tags }); | ||
this.setState({ tags: newTags }); | ||
} | ||
@@ -282,2 +290,2 @@ | ||
ReactDOM.render(<App />, document.getElementById("app")); | ||
ReactDOM.render(<App />, document.getElementById('app')); |
@@ -1,32 +0,12 @@ | ||
import React, { Component } from "react"; | ||
import ReactDOM from "react-dom"; | ||
import { DragDropContext } from "react-dnd"; | ||
import HTML5Backend from "react-dnd-html5-backend"; | ||
import Suggestions from "./Suggestions"; | ||
import PropTypes from "prop-types"; | ||
import Tag from "./Tag"; | ||
import React, { Component } from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import { DragDropContext } from 'react-dnd'; | ||
import HTML5Backend from 'react-dnd-html5-backend'; | ||
import Suggestions from './Suggestions'; | ||
import PropTypes from 'prop-types'; | ||
import Tag from './Tag'; | ||
// Constants | ||
const Keys = { | ||
ENTER: 13, | ||
TAB: 9, | ||
BACKSPACE: 8, | ||
UP_ARROW: 38, | ||
DOWN_ARROW: 40, | ||
ESCAPE: 27, | ||
}; | ||
//Constants | ||
import { KEYS, DEFAULT_PLACEHOLDER, DEFAULT_CLASSNAMES } from './constants'; | ||
const DEFAULT_PLACEHOLDER = "Add new tag"; | ||
const DefaultClassNames = { | ||
tags: "ReactTags__tags", | ||
tagInput: "ReactTags__tagInput", | ||
tagInputField: "ReactTags__tagInputField", | ||
selected: "ReactTags__selected", | ||
tag: "ReactTags__tag", | ||
remove: "ReactTags__remove", | ||
suggestions: "ReactTags__suggestions", | ||
activeSuggestion: "ReactTags__activeSuggestion", | ||
}; | ||
class ReactTags extends Component { | ||
@@ -38,3 +18,3 @@ constructor(props) { | ||
suggestions: this.props.suggestions, | ||
query: "", | ||
query: '', | ||
isFocused: false, | ||
@@ -58,3 +38,3 @@ selectedIndex: -1, | ||
this.setState({ | ||
classNames: { ...DefaultClassNames, ...this.props.classNames }, | ||
classNames: { ...DEFAULT_CLASSNAMES, ...this.props.classNames }, | ||
}); | ||
@@ -64,5 +44,7 @@ } | ||
resetAndFocusInput() { | ||
this.setState({ query: "" }); | ||
this.textInput.value = ""; | ||
this.textInput.focus(); | ||
this.setState({ query: '' }); | ||
if (this.textInput) { | ||
this.textInput.value = ''; | ||
this.textInput.focus(); | ||
} | ||
} | ||
@@ -83,3 +65,3 @@ | ||
return suggestions.filter(function(item) { | ||
return item.toLowerCase().indexOf(query.toLowerCase()) === 0; | ||
return item.text.toLowerCase().indexOf(query.toLowerCase()) === 0; | ||
}); | ||
@@ -94,4 +76,4 @@ } | ||
this.setState({ | ||
suggestions: suggestions, | ||
classNames: { ...DefaultClassNames, ...props.classNames }, | ||
suggestions, | ||
classNames: { ...DEFAULT_CLASSNAMES, ...props.classNames }, | ||
}); | ||
@@ -103,3 +85,3 @@ } | ||
if (!this.props.resetInputOnDelete) { | ||
this.textInput.focus(); | ||
this.textInput && this.textInput.focus(); | ||
} else { | ||
@@ -115,5 +97,4 @@ this.resetAndFocusInput(); | ||
} | ||
if (!this.props.resetInputOnDelete) { | ||
this.textInput.focus(); | ||
this.textInput && this.textInput.focus(); | ||
} else { | ||
@@ -132,6 +113,3 @@ this.resetAndFocusInput(); | ||
let selectedIndex = this.state.selectedIndex; | ||
if (selectedIndex >= suggestions.length) { | ||
selectedIndex = suggestions.length - 1; | ||
} | ||
const { selectedIndex } = this.state; | ||
@@ -141,3 +119,6 @@ this.setState({ | ||
suggestions: suggestions, | ||
selectedIndex: selectedIndex, | ||
selectedIndex: | ||
selectedIndex >= suggestions.length | ||
? suggestions.length - 1 | ||
: selectedIndex, | ||
}); | ||
@@ -158,3 +139,5 @@ } | ||
this.props.handleInputBlur(value); | ||
this.textInput.value = ""; | ||
if (this.textInput) { | ||
this.textInput.value = ''; | ||
} | ||
} | ||
@@ -165,6 +148,6 @@ this.setState({ isFocused: false }); | ||
handleKeyDown(e) { | ||
let { query, selectedIndex, suggestions } = this.state; | ||
const { query, selectedIndex, suggestions, selectionMode } = this.state; | ||
// hide suggestions menu on escape | ||
if (e.keyCode === Keys.ESCAPE) { | ||
if (e.keyCode === KEYS.ESCAPE) { | ||
e.preventDefault(); | ||
@@ -183,12 +166,14 @@ e.stopPropagation(); | ||
if (this.props.delimiters.indexOf(e.keyCode) !== -1 && !e.shiftKey) { | ||
if (e.keyCode !== Keys.TAB || query !== "") { | ||
if (e.keyCode !== KEYS.TAB || query !== '') { | ||
e.preventDefault(); | ||
} | ||
if (this.state.selectionMode && this.state.selectedIndex != -1) { | ||
query = this.state.suggestions[this.state.selectedIndex]; | ||
const selectedQuery = | ||
selectionMode && selectedIndex !== -1 | ||
? suggestions[selectedIndex] | ||
: { id: query, text: query }; | ||
if (selectedQuery !== '') { | ||
this.addTag(selectedQuery); | ||
} | ||
if (query !== "") { | ||
this.addTag(query); | ||
} | ||
} | ||
@@ -198,4 +183,4 @@ | ||
if ( | ||
e.keyCode === Keys.BACKSPACE && | ||
query == "" && | ||
e.keyCode === KEYS.BACKSPACE && | ||
query == '' && | ||
this.props.allowDeleteFromEmptyInput | ||
@@ -207,12 +192,7 @@ ) { | ||
// up arrow | ||
if (e.keyCode === Keys.UP_ARROW) { | ||
if (e.keyCode === KEYS.UP_ARROW) { | ||
e.preventDefault(); | ||
let { selectedIndex, suggestions } = this.state; | ||
selectedIndex = | ||
selectedIndex <= 0 ? suggestions.length - 1 : selectedIndex - 1; | ||
this.setState({ | ||
selectedIndex: selectedIndex, | ||
selectedIndex: | ||
selectedIndex <= 0 ? suggestions.length - 1 : selectedIndex - 1, | ||
selectionMode: true, | ||
@@ -223,11 +203,9 @@ }); | ||
// down arrow | ||
if (e.keyCode === Keys.DOWN_ARROW) { | ||
if (e.keyCode === KEYS.DOWN_ARROW) { | ||
e.preventDefault(); | ||
const newSelectedIndex = | ||
suggestions.length === 0 | ||
? -1 | ||
: (selectedIndex + 1) % suggestions.length; | ||
this.setState({ | ||
selectedIndex: newSelectedIndex, | ||
selectedIndex: | ||
suggestions.length === 0 | ||
? -1 | ||
: (selectedIndex + 1) % suggestions.length, | ||
selectionMode: true, | ||
@@ -246,4 +224,4 @@ }); | ||
// See: http://stackoverflow.com/a/6969486/1463681 | ||
const escapeRegex = str => | ||
str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); | ||
const escapeRegex = (str) => | ||
str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); | ||
@@ -253,3 +231,3 @@ // Used to determine how the pasted content is split. | ||
this.props.delimiters | ||
.map(delimiter => { | ||
.map((delimiter) => { | ||
// See: http://stackoverflow.com/a/34711175/1463681 | ||
@@ -259,17 +237,19 @@ const chrCode = delimiter - 48 * Math.floor(delimiter / 48); | ||
}) | ||
.join("") | ||
.join('') | ||
); | ||
const clipboardData = e.clipboardData || window.clipboardData; | ||
const string = clipboardData.getData("text"); | ||
const string = clipboardData.getData('text'); | ||
const regExp = new RegExp(`[${delimiterChars}]+`); | ||
string.split(regExp).forEach(tag => this.props.handleAddition(tag)); | ||
string.split(regExp).forEach((tag) => this.props.handleAddition(tag)); | ||
} | ||
addTag(tag) { | ||
if (!tag.id && !tag.text) { | ||
return; | ||
} | ||
const { tags } = this.props; | ||
const existingTags = tags.map(tag => tag.text.toLowerCase()); | ||
const existingKeys = tags.map((tag) => tag.id.toLowerCase()); | ||
// Return if tag has been already added | ||
if (existingTags.indexOf(tag.toLowerCase()) >= 0) { | ||
if (existingKeys.indexOf(tag.id.toLowerCase()) >= 0) { | ||
return; | ||
@@ -296,3 +276,3 @@ } | ||
this.setState({ | ||
query: "", | ||
query: '', | ||
selectionMode: false, | ||
@@ -327,23 +307,34 @@ selectedIndex: -1, | ||
getTagItems = () => { | ||
const { | ||
tags, | ||
labelField, | ||
removeComponent, | ||
readOnly, | ||
handleDrag, | ||
} = this.props; | ||
const { classNames } = this.state; | ||
const moveTag = handleDrag ? this.moveTag : null; | ||
return tags.map((tag, index) => { | ||
return ( | ||
<Tag | ||
key={tag.id} | ||
index={index} | ||
tag={tag} | ||
labelField={labelField} | ||
onDelete={this.handleDelete.bind(this, index)} | ||
moveTag={moveTag} | ||
removeComponent={removeComponent} | ||
onTagClicked={this.handleTagClick.bind(this, index)} | ||
readOnly={readOnly} | ||
classNames={classNames} | ||
/> | ||
); | ||
}); | ||
}; | ||
render() { | ||
const moveTag = this.props.handleDrag ? this.moveTag : null; | ||
const tagItems = this.props.tags.map( | ||
function(tag, i) { | ||
return ( | ||
<Tag | ||
key={tag.id ? tag.id : i} | ||
index={i} | ||
tag={tag} | ||
labelField={this.props.labelField} | ||
onDelete={this.handleDelete.bind(this, i)} | ||
moveTag={moveTag} | ||
removeComponent={this.props.removeComponent} | ||
onTagClicked={this.handleTagClick.bind(this, i)} | ||
readOnly={this.props.readOnly} | ||
classNames={this.state.classNames} | ||
/> | ||
); | ||
}.bind(this) | ||
); | ||
const tagItems = this.getTagItems(); | ||
@@ -362,3 +353,3 @@ // get the suggestions for the given query | ||
<input | ||
ref={input => { | ||
ref={(input) => { | ||
this.textInput = input; | ||
@@ -410,3 +401,6 @@ }} | ||
labelField: PropTypes.string, | ||
suggestions: PropTypes.array, | ||
suggestions: PropTypes.arrayOf(PropTypes.shape({ | ||
id: PropTypes.string.isRequired, | ||
text: PropTypes.string.isRequired | ||
})), | ||
delimiters: PropTypes.array, | ||
@@ -436,2 +430,6 @@ autofocus: PropTypes.bool, | ||
inputValue: PropTypes.string, | ||
tags: PropTypes.arrayOf(PropTypes.shape({ | ||
id: PropTypes.string.isRequired, | ||
text: PropTypes.any.isRequired | ||
})) | ||
}; | ||
@@ -441,5 +439,4 @@ | ||
placeholder: DEFAULT_PLACEHOLDER, | ||
tags: [], | ||
suggestions: [], | ||
delimiters: [Keys.ENTER, Keys.TAB], | ||
delimiters: [KEYS.ENTER, KEYS.TAB], | ||
autofocus: true, | ||
@@ -458,3 +455,3 @@ inline: true, | ||
WithOutContext: ReactTags, | ||
Keys: Keys, | ||
KEYS: KEYS, | ||
}; |
@@ -1,5 +0,8 @@ | ||
import React, { Component } from "react"; | ||
import PropTypes from "prop-types"; | ||
import isEqual from "lodash/isEqual"; | ||
import React, { Component } from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import isEqual from 'lodash/isEqual'; | ||
// Polyfills | ||
import Number from 'core-js/library/fn/number'; | ||
const maybeScrollSuggestionIntoView = (suggestionEl, suggestionsContainer) => { | ||
@@ -66,9 +69,9 @@ const containerHeight = suggestionsContainer.offsetHeight; | ||
markIt = (input, query) => { | ||
const escapedRegex = query.trim().replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&"); | ||
const escapedRegex = query.trim().replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&'); | ||
return { | ||
__html: input.replace(RegExp(escapedRegex, "gi"), "<mark>$&</mark>"), | ||
__html: input.text.replace(RegExp(escapedRegex, 'gi'), '<mark>$&</mark>'), | ||
}; | ||
}; | ||
shouldRenderSuggestions = query => { | ||
shouldRenderSuggestions = (query) => { | ||
const { props } = this; | ||
@@ -91,3 +94,3 @@ const minQueryLength = Number.isInteger(props.minQueryLength) | ||
className={ | ||
i == props.selectedIndex ? props.classNames.activeSuggestion : "" | ||
i == props.selectedIndex ? props.classNames.activeSuggestion : '' | ||
}> | ||
@@ -109,3 +112,3 @@ <span dangerouslySetInnerHTML={this.markIt(item, props.query)} /> | ||
<div | ||
ref={elem => { | ||
ref={(elem) => { | ||
this.suggestionsContainer = elem; | ||
@@ -112,0 +115,0 @@ }} |
@@ -1,14 +0,14 @@ | ||
import React, { Component } from "react"; | ||
import { findDOMNode } from "react-dom"; | ||
import { DragSource, DropTarget } from "react-dnd"; | ||
import PropTypes from "prop-types"; | ||
import flow from "lodash/flow"; | ||
import React, { Component } from 'react'; | ||
import { findDOMNode } from 'react-dom'; | ||
import { DragSource, DropTarget } from 'react-dnd'; | ||
import PropTypes from 'prop-types'; | ||
import flow from 'lodash/flow'; | ||
const ItemTypes = { TAG: "tag" }; | ||
const ItemTypes = { TAG: 'tag' }; | ||
const tagSource = { | ||
beginDrag: props => { | ||
return { id: props.tag.id, index: props.index }; | ||
beginDrag: (props) => { | ||
return { id: props.tag.index, index: props.index }; | ||
}, | ||
canDrag: props => props.moveTag && !props.readOnly, | ||
canDrag: (props) => props.moveTag && !props.readOnly, | ||
}; | ||
@@ -43,3 +43,3 @@ | ||
}, | ||
canDrop: props => !props.readOnly, | ||
canDrop: (props) => !props.readOnly, | ||
}; | ||
@@ -123,3 +123,3 @@ | ||
Tag.defaultProps = { | ||
labelField: "text", | ||
labelField: 'text', | ||
readOnly: false, | ||
@@ -126,0 +126,0 @@ }; |
{ | ||
"name": "react-tag-input", | ||
"version": "4.9.1", | ||
"version": "5.0.0", | ||
"description": "React tags is a fantastically simple tagging component for your React projects", | ||
@@ -8,6 +8,7 @@ "main": "dist-modules/ReactTags.js", | ||
"test": "jest --notify --coverage", | ||
"test-watch": "jest --watch --notify --coverage", | ||
"open-localhost": "node -e 'require(\"opn\")(\"http://localhost:8090/example/index.html\")'", | ||
"test-watch": "jest --watch --notify", | ||
"dev": "webpack-dev-server --config webpack-dev-server.config.js --progress --inline --colors", | ||
"format": "prettier --write --jsx-bracket-same-line --trailing-comma es5 lib/*.js test/*.js", | ||
"format": "prettier --write lib/*.js test/*.js", | ||
"build": "webpack --config webpack-production.config.js --progress --colors && babel lib --out-dir dist-modules", | ||
@@ -34,6 +35,7 @@ "start": "npm-run-all --parallel open-localhost dev" | ||
"dependencies": { | ||
"core-js": "2.5.4", | ||
"lodash": "4.17.5", | ||
"prop-types": "15.6.1", | ||
"react": "16.2.0", | ||
"react-dnd": "2.5.4", | ||
"react-dnd": "2.6.0", | ||
"react-dnd-html5-backend": "2.5.4", | ||
@@ -54,2 +56,5 @@ "react-dom": "16.2.0" | ||
"enzyme-adapter-react-16": "1.1.0", | ||
"eslint": "4.19.1", | ||
"eslint-plugin-jsx-a11y": "6.0.3", | ||
"eslint-plugin-react": "7.7.0", | ||
"jest": "22.1.4", | ||
@@ -64,5 +69,5 @@ "jsdom": "11.6.2", | ||
"sinon": "4.2.2", | ||
"webpack": "3.0.0", | ||
"webpack": "4.2.0", | ||
"webpack-dev-server": "2.11.1" | ||
} | ||
} |
@@ -9,3 +9,7 @@ React-Tags | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/prakhar1989/react-tags.svg)](https://greenkeeper.io/) | ||
[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettier) | ||
<a href="https://codeclimate.com/github/prakhar1989/react-tags/maintainability"><img src="https://api.codeclimate.com/v1/badges/b9edb2810b02bb845d20/maintainability" /></a> | ||
<a href="https://codeclimate.com/github/prakhar1989/react-tags/test_coverage"><img src="https://api.codeclimate.com/v1/badges/b9edb2810b02bb845d20/test_coverage" /></a> | ||
React-tags is a simple tagging component ready to drop in your React projects. The component is inspired by GMail's *To* field in the compose window. | ||
@@ -53,4 +57,14 @@ | ||
this.state = { | ||
tags: [{ id: 1, text: "Thailand" }, { id: 2, text: "India" }], | ||
suggestions: ['USA', 'Germany', 'Austria', 'Costa Rica', 'Sri Lanka', 'Thailand'] | ||
tags: [ | ||
{ id: "Thailand", text: "Thailand" }, | ||
{ id: "India", text: "India" } | ||
], | ||
suggestions: [{ | ||
{ id: 'USA', text: 'USA' }, | ||
{ id: 'Germany', text: 'Germany' }, | ||
{ id: 'Austria', text: 'Austria' }, | ||
{ id: 'Costa Rica', text: 'Costa Rica' }, | ||
{ id: 'Sri Lanka', text: 'Sri Lanka' }, | ||
{ id: 'Thailand', text: 'Thailand' } | ||
}] | ||
}; | ||
@@ -63,25 +77,22 @@ this.handleDelete = this.handleDelete.bind(this); | ||
handleDelete(i) { | ||
let tags = this.state.tags; | ||
tags.splice(i, 1); | ||
this.setState({tags: tags}); | ||
const { tags } = this.state; | ||
this.setState({ | ||
tags: tags.filter((tag, index) => index !== i), | ||
}); | ||
} | ||
handleAddition(tag) { | ||
let tags = this.state.tags; | ||
tags.push({ | ||
id: tags.length + 1, | ||
text: tag | ||
}); | ||
this.setState({tags: tags}); | ||
const { tags } = this.state; | ||
this.setState({tags: [...tags, ...[tag] }); | ||
} | ||
handleDrag(tag, currPos, newPos) { | ||
let tags = this.state.tags; | ||
const tags = [...this.state.tags]; | ||
const newTags = tags.slice(); | ||
// mutate array | ||
tags.splice(currPos, 1); | ||
tags.splice(newPos, 0, tag); | ||
newTags.splice(currPos, 1); | ||
newTags.splice(newPos, 0, tag); | ||
// re-render | ||
this.setState({ tags: tags }); | ||
this.setState({ tags: newTags }); | ||
} | ||
@@ -139,9 +150,10 @@ | ||
- [`maxLength`](#maxLength) | ||
- [`inline`](#inline) | ||
<a name="tagsOption"></a> | ||
##### tags (optional) | ||
An array of tags that are displayed as pre-selected. Each tag should have an `id` and a `text` property which is used to display. | ||
An array of tags that are displayed as pre-selected. Each tag should have an `id` and `text` property which is used to display. | ||
```js | ||
let tags = [ {id: 1, text: "Apples"} ] | ||
const tags = [ { id: "1", text: "Apples" } ] | ||
``` | ||
@@ -154,3 +166,8 @@ | ||
```js | ||
let suggestions = ["mango", "pineapple", "orange", "pear"]; | ||
const suggestions = [ | ||
{ id: "mango" text: "mango" }, | ||
{ id: "pineapple", text: "pineapple" }, | ||
{ id: "orange", text: "orange" }, | ||
{ id: "pear", text: "pear" } | ||
]; | ||
``` | ||
@@ -176,6 +193,8 @@ | ||
``` | ||
<ReactTags tags={tags} | ||
<ReactTags | ||
tags={tags} | ||
suggestions={} | ||
labelField={'name'} | ||
handleDrag={} /> | ||
handleDrag={} | ||
/> | ||
``` | ||
@@ -258,3 +277,3 @@ This is useful if your data uses the `text` property for something else. | ||
autofocus={false} | ||
...> | ||
...> | ||
``` | ||
@@ -375,2 +394,22 @@ | ||
<a name="inline"></a> | ||
##### inline (optional) | ||
The inline attributes decides whether the input fields and selected tags will be rendered in-line. | ||
``` | ||
<ReactTags | ||
inline | ||
...> | ||
``` | ||
![img](docs/inline-true.png) | ||
``` | ||
<ReactTags | ||
inline={false} | ||
...> | ||
``` | ||
![img](docs/inline-false.png) | ||
### Styling | ||
@@ -377,0 +416,0 @@ `<ReactTags>` does not come up with any styles. However, it is very easy to customize the look of the component the way you want it. By default, the component provides the following classes with which you can style - |
@@ -1,12 +0,18 @@ | ||
import React from "react"; | ||
import { expect } from "chai"; | ||
import { mount, shallow } from "enzyme"; | ||
import { spy } from "sinon"; | ||
import noop from "lodash/noop"; | ||
import { WithContext as ReactTags } from "../lib/ReactTags"; | ||
import renderer from "react-test-renderer"; | ||
import React from 'react'; | ||
import { expect } from 'chai'; | ||
import { mount, shallow } from 'enzyme'; | ||
import { spy } from 'sinon'; | ||
import noop from 'lodash/noop'; | ||
import { WithContext as ReactTags } from '../lib/ReactTags'; | ||
import renderer from 'react-test-renderer'; | ||
const defaults = { | ||
tags: [{ id: 1, text: "Apple" }], | ||
suggestions: ["Banana", "Apple", "Apricot", "Pear", "Peach"], | ||
tags: [{ id: 'Apple', text: 'Apple' }], | ||
suggestions: [ | ||
{ id: 'Banana', text: 'Banana' }, | ||
{ id: 'Apple', text: 'Apple' }, | ||
{ id: 'Apricot', text: 'Apricot' }, | ||
{ id: 'Pear', text: 'Pear' }, | ||
{ id: 'Peach', text: 'Peach' }, | ||
], | ||
handleAddition: noop, | ||
@@ -25,273 +31,386 @@ handleDelete: noop, | ||
test("focus on input by default", () => { | ||
const $el = mount(mockItem()); | ||
expect(document.activeElement.tagName).to.equal("INPUT"); | ||
expect(document.activeElement.className).to.equal("ReactTags__tagInputField"); | ||
$el.unmount(); | ||
}); | ||
describe('Test ReactTags', () => { | ||
test('focus on input by default', () => { | ||
const $el = mount(mockItem()); | ||
expect(document.activeElement.tagName).to.equal('INPUT'); | ||
expect(document.activeElement.className).to.equal( | ||
'ReactTags__tagInputField' | ||
); | ||
$el.unmount(); | ||
}); | ||
test("should not focus on input if autofocus is false", () => { | ||
const $el = mount(mockItem({ autofocus: false })); | ||
expect(document.activeElement.tagName).to.equal("BODY"); | ||
$el.unmount(); | ||
}); | ||
test('should not focus on input if autofocus is false', () => { | ||
const $el = mount(mockItem({ autofocus: false })); | ||
expect(document.activeElement.tagName).to.equal('BODY'); | ||
$el.unmount(); | ||
}); | ||
test("should not focus on input if readOnly is true", () => { | ||
const $el = mount(mockItem({ autofocus: false })); | ||
expect(document.activeElement.tagName).to.equal("BODY"); | ||
$el.unmount(); | ||
}); | ||
test('should not focus on input if readOnly is true', () => { | ||
const $el = mount(mockItem({ autofocus: false })); | ||
expect(document.activeElement.tagName).to.equal('BODY'); | ||
$el.unmount(); | ||
}); | ||
test("shows the classnames of children properly", () => { | ||
const $el = mount(mockItem()); | ||
expect($el.find(".ReactTags__tags").length).to.equal(1); | ||
expect($el.find(".ReactTags__selected").length).to.equal(1); | ||
expect($el.find(".ReactTags__tagInput").length).to.equal(1); | ||
expect($el.find(".ReactTags__tagInputField").length).to.equal(1); | ||
}); | ||
test('shows the classnames of children properly', () => { | ||
const $el = mount(mockItem()); | ||
expect($el.find('.ReactTags__tags').length).to.equal(1); | ||
expect($el.find('.ReactTags__selected').length).to.equal(1); | ||
expect($el.find('.ReactTags__tagInput').length).to.equal(1); | ||
expect($el.find('.ReactTags__tagInputField').length).to.equal(1); | ||
}); | ||
test("renders preselected tags properly", () => { | ||
const $el = mount(mockItem()); | ||
expect($el.text()).to.have.string("Apple"); | ||
}); | ||
test('renders preselected tags properly', () => { | ||
const $el = mount(mockItem()); | ||
expect($el.text()).to.have.string('Apple'); | ||
}); | ||
test("invokes the onBlur event", () => { | ||
const handleInputBlur = spy(); | ||
const $el = mount(mockItem()); | ||
test('invokes the onBlur event', () => { | ||
const handleInputBlur = spy(); | ||
const $el = mount(mockItem()); | ||
// Won't be invoked as there's no `handleInputBlur` event yet. | ||
$el.find(".ReactTags__tagInputField").simulate("blur"); | ||
expect(handleInputBlur.callCount).to.equal(0); | ||
// Won't be invoked as there's no `handleInputBlur` event yet. | ||
$el.find('.ReactTags__tagInputField').simulate('blur'); | ||
expect(handleInputBlur.callCount).to.equal(0); | ||
// Will be invoked despite the input being empty. | ||
$el.setProps({ handleInputBlur }); | ||
$el.find(".ReactTags__tagInputField").simulate("blur"); | ||
expect(handleInputBlur.callCount).to.equal(1); | ||
expect(handleInputBlur.calledWith("")).to.be.true; | ||
expect($el.find(".ReactTags__tagInputField").get(0).value).to.be.undefined; | ||
}); | ||
// Will be invoked despite the input being empty. | ||
$el.setProps({ handleInputBlur }); | ||
$el.find('.ReactTags__tagInputField').simulate('blur'); | ||
expect(handleInputBlur.callCount).to.equal(1); | ||
expect(handleInputBlur.calledWith('')).to.be.true; | ||
expect($el.find('.ReactTags__tagInputField').get(0).value).to.be.undefined; | ||
}); | ||
test("invokes the onFocus event", () => { | ||
const handleInputFocus = spy(); | ||
const $el = mount(mockItem({ inputValue: "Example" })); | ||
test('invokes the onFocus event', () => { | ||
const handleInputFocus = spy(); | ||
const $el = mount(mockItem({ inputValue: 'Example' })); | ||
$el.setProps({ handleInputFocus }); | ||
$el.find(".ReactTags__tagInputField").simulate("focus"); | ||
expect(handleInputFocus.callCount).to.equal(1); | ||
expect(handleInputFocus.calledWith("Example")).to.be.true; | ||
}); | ||
$el.setProps({ handleInputFocus }); | ||
$el.find('.ReactTags__tagInputField').simulate('focus'); | ||
expect(handleInputFocus.callCount).to.equal(1); | ||
expect(handleInputFocus.calledWith('Example')).to.be.true; | ||
}); | ||
test("invokes the onBlur event when input has value", () => { | ||
const handleInputBlur = spy(); | ||
const $el = mount(mockItem({ inputValue: "Example" })); | ||
test('invokes the onBlur event when input has value', () => { | ||
const handleInputBlur = spy(); | ||
const $el = mount(mockItem({ inputValue: 'Example' })); | ||
// Will also be invoked for when the input has a value. | ||
$el.setProps({ handleInputBlur }); | ||
$el.find(".ReactTags__tagInputField").simulate("blur"); | ||
expect(handleInputBlur.callCount).to.equal(1); | ||
expect(handleInputBlur.calledWith("Example")).to.be.true; | ||
expect($el.find(".ReactTags__tagInputField").get(0).value).to.be.undefined; | ||
}); | ||
test("should not add new tag on paste event", () => { | ||
const actual = []; | ||
const $el = mount( | ||
mockItem({ | ||
allowAdditionFromPaste: false, | ||
handleAddition(tag) { | ||
actual.push(tag); | ||
}, | ||
}) | ||
); | ||
const ReactTagsInstance = $el.instance().refs.child; | ||
const $input = $el.find(".ReactTags__tagInputField"); | ||
$input.simulate("paste", { | ||
clipboardData: { | ||
getData: () => "Banana", | ||
}, | ||
// Will also be invoked for when the input has a value. | ||
$el.setProps({ handleInputBlur }); | ||
$el.find('.ReactTags__tagInputField').simulate('blur'); | ||
expect(handleInputBlur.callCount).to.equal(1); | ||
expect(handleInputBlur.calledWith('Example')).to.be.true; | ||
expect($el.find('.ReactTags__tagInputField').get(0).value).to.be.undefined; | ||
}); | ||
expect(actual).to.have.length(0); | ||
expect(actual).to.not.have.members(["Banana"]); | ||
}); | ||
test('should not add new tag on paste event', () => { | ||
const actual = []; | ||
const $el = mount( | ||
mockItem({ | ||
allowAdditionFromPaste: false, | ||
handleAddition(tag) { | ||
actual.push(tag); | ||
}, | ||
}) | ||
); | ||
test("handles the paste event and splits the clipboard on delimiters", () => { | ||
const Keys = { | ||
TAB: 9, | ||
SPACE: 32, | ||
COMMA: 188, | ||
}; | ||
const ReactTagsInstance = $el.instance().refs.child; | ||
const $input = $el.find('.ReactTags__tagInputField'); | ||
const actual = []; | ||
const $el = mount( | ||
mockItem({ | ||
delimiters: [Keys.TAB, Keys.SPACE, Keys.COMMA], | ||
handleAddition(tag) { | ||
actual.push(tag); | ||
$input.simulate('paste', { | ||
clipboardData: { | ||
getData: () => 'Banana', | ||
}, | ||
}) | ||
); | ||
}); | ||
const ReactTagsInstance = $el.instance().refs.child; | ||
const $input = $el.find(".ReactTags__tagInputField"); | ||
$input.simulate("paste", { | ||
clipboardData: { | ||
getData: () => "Banana,Apple,Apricot\nOrange Blueberry,Pear,Peach\tKiwi", | ||
}, | ||
expect(actual).to.have.length(0); | ||
expect(actual).to.not.have.members(['Banana']); | ||
}); | ||
expect(actual).to.have.members([ | ||
"Banana", | ||
"Apple", | ||
"Apricot\nOrange", | ||
"Blueberry", | ||
"Pear", | ||
"Peach", | ||
"Kiwi", | ||
]); | ||
}); | ||
test('handles the paste event and splits the clipboard on delimiters', () => { | ||
const Keys = { | ||
TAB: 9, | ||
SPACE: 32, | ||
COMMA: 188, | ||
}; | ||
test("should not allow duplicate tags", () => { | ||
const actual = []; | ||
const $el = mount( | ||
mockItem({ | ||
handleAddition(tag) { | ||
actual.push(tag); | ||
}, | ||
}) | ||
); | ||
const actual = []; | ||
const $el = mount( | ||
mockItem({ | ||
delimiters: [Keys.TAB, Keys.SPACE, Keys.COMMA], | ||
handleAddition(tag) { | ||
actual.push(tag); | ||
}, | ||
}) | ||
); | ||
expect($el.instance().props.tags).to.have.members(defaults.tags); | ||
const $input = $el.find(".ReactTags__tagInputField"); | ||
$input.simulate("change", { target: { value: "Apple" } }); | ||
const ReactTagsInstance = $el.instance().refs.child; | ||
const $input = $el.find('.ReactTags__tagInputField'); | ||
$input.simulate("keyDown", { keyCode: ENTER_ARROW_KEY_CODE }); | ||
expect(actual).to.have.length(0); | ||
}); | ||
test("should not add empty tag when down arrow is clicked followed by enter key", () => { | ||
const actual = []; | ||
const $el = mount( | ||
mockItem({ | ||
handleAddition(tag) { | ||
actual.push(tag); | ||
$input.simulate('paste', { | ||
clipboardData: { | ||
getData: () => | ||
'Banana,Apple,Apricot\nOrange Blueberry,Pear,Peach\tKiwi', | ||
}, | ||
suggestions: [], | ||
}) | ||
); | ||
}); | ||
expect($el.instance().props.tags).to.have.members(defaults.tags); | ||
const $input = $el.find(".ReactTags__tagInputField"); | ||
$input.simulate("keyDown", { keyCode: DOWN_ARROW_KEY_CODE }); | ||
$input.simulate("keyDown", { keyCode: ENTER_ARROW_KEY_CODE }); | ||
expect(actual).to.have.length(0); | ||
}); | ||
describe("autocomplete/suggestions filtering", () => { | ||
test("updates suggestions state as expected based on default filter logic", () => { | ||
const $el = mount(mockItem()); | ||
const ReactTagsInstance = $el.instance().getDecoratedComponentInstance(); | ||
const $input = $el.find(".ReactTags__tagInputField"); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members( | ||
defaults.suggestions | ||
); | ||
$input.simulate("change", { target: { value: "ea" } }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members([]); | ||
$input.simulate("change", { target: { value: "ap" } }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members([ | ||
"Apple", | ||
"Apricot", | ||
expect(actual).to.have.members([ | ||
'Banana', | ||
'Apple', | ||
'Apricot\nOrange', | ||
'Blueberry', | ||
'Pear', | ||
'Peach', | ||
'Kiwi', | ||
]); | ||
}); | ||
test("updates suggestions state as expected based on custom filter logic", () => { | ||
test('should not allow duplicate tags', () => { | ||
const actual = []; | ||
const $el = mount( | ||
mockItem({ | ||
handleFilterSuggestions: (query, suggestions) => { | ||
return suggestions.filter(suggestion => { | ||
return suggestion.toLowerCase().indexOf(query.toLowerCase()) >= 0; | ||
}); | ||
handleAddition(tag) { | ||
actual.push(tag); | ||
}, | ||
}) | ||
); | ||
const ReactTagsInstance = $el.instance().getDecoratedComponentInstance(); | ||
const $input = $el.find(".ReactTags__tagInputField"); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members( | ||
defaults.suggestions | ||
); | ||
expect($el.instance().props.tags).to.have.deep.members(defaults.tags); | ||
const $input = $el.find('.ReactTags__tagInputField'); | ||
$input.simulate('change', { target: { value: 'Apple' } }); | ||
$input.simulate("change", { target: { value: "Ea" } }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members([ | ||
"Pear", | ||
"Peach", | ||
]); | ||
$input.simulate("change", { target: { value: "ap" } }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members([ | ||
"Apple", | ||
"Apricot", | ||
]); | ||
$input.simulate('keyDown', { keyCode: ENTER_ARROW_KEY_CODE }); | ||
expect(actual).to.have.length(0); | ||
}); | ||
test("updates selectedIndex state as expected based on changing suggestions", () => { | ||
test('should not add empty tag when down arrow is clicked followed by enter key', () => { | ||
const actual = []; | ||
const $el = mount( | ||
mockItem({ | ||
autocomplete: true, | ||
handleFilterSuggestions: (query, suggestions) => { | ||
return suggestions.filter(suggestion => { | ||
return suggestion.toLowerCase().indexOf(query.toLowerCase()) >= 0; | ||
}); | ||
handleAddition(tag) { | ||
actual.push(tag); | ||
}, | ||
suggestions: [], | ||
}) | ||
); | ||
const ReactTagsInstance = $el.instance().getDecoratedComponentInstance(); | ||
const $input = $el.find(".ReactTags__tagInputField"); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members( | ||
defaults.suggestions | ||
); | ||
expect($el.instance().props.tags).to.have.members(defaults.tags); | ||
$input.simulate("change", { target: { value: "Ea" } }); | ||
$input.simulate("focus"); | ||
$input.simulate("keyDown", { keyCode: DOWN_ARROW_KEY_CODE }); | ||
$input.simulate("keyDown", { keyCode: DOWN_ARROW_KEY_CODE }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members([ | ||
"Pear", | ||
"Peach", | ||
]); | ||
expect(ReactTagsInstance.state.selectedIndex).to.equal(1); | ||
$input.simulate("change", { target: { value: "Each" } }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members(["Peach"]); | ||
expect(ReactTagsInstance.state.selectedIndex).to.equal(0); | ||
const $input = $el.find('.ReactTags__tagInputField'); | ||
$input.simulate('keyDown', { keyCode: DOWN_ARROW_KEY_CODE }); | ||
$input.simulate('keyDown', { keyCode: ENTER_ARROW_KEY_CODE }); | ||
expect(actual).to.have.length(0); | ||
}); | ||
test("selects the correct suggestion using the keyboard when minQueryLength is set to 0", function() { | ||
let actual = []; | ||
// this test will fail if console.error occurs | ||
test('should not set any property of this.textInput when readOnly', () => { | ||
console.error = jest.fn((error) => { | ||
throw 'Error'; | ||
}); | ||
const $el = mount(mockItem({ readOnly: true, resetInputOnDelete: false })); | ||
const $tag = $el.find('.ReactTags__tag'); | ||
$tag.simulate('click'); | ||
}); | ||
test('should fail the test if two tags have same key, issue #110', () => { | ||
console.warn = jest.fn((error) => { | ||
throw 'Error'; | ||
}); | ||
let modifiedTags = [ | ||
...defaults.tags, | ||
{ id: 'NewYork', text: 'NewYork' }, | ||
{ id: 'Austria', text: 'Austria' }, | ||
]; | ||
const $el = mount( | ||
mockItem({ | ||
query: "", | ||
minQueryLength: 0, | ||
handleAddition(tag) { | ||
actual.push(tag); | ||
tags: modifiedTags, | ||
handleDelete: (i) => { | ||
modifiedTags = modifiedTags.filter((tag, index) => index !== i); | ||
}, | ||
}) | ||
); | ||
const $input = $el.find(".ReactTags__tagInputField"); | ||
//remove Apple | ||
$el | ||
.find('.ReactTags__remove') | ||
.at(0) | ||
.simulate('click'); | ||
//remove NewYork | ||
$el | ||
.find('.ReactTags__remove') | ||
.at(1) | ||
.simulate('click'); | ||
$el.setProps({ tags: modifiedTags }); | ||
const $input = $el.find('.ReactTags__tagInputField'); | ||
$input.simulate('change', { target: { value: 'Hello' } }); | ||
$input.simulate('keyDown', { keyCode: DOWN_ARROW_KEY_CODE }); | ||
}); | ||
$input.simulate("keyDown", { keyCode: DOWN_ARROW_KEY_CODE }); | ||
$input.simulate("keyDown", { keyCode: DOWN_ARROW_KEY_CODE }); | ||
$input.simulate("keyDown", { keyCode: DOWN_ARROW_KEY_CODE }); | ||
$input.simulate("keyDown", { keyCode: ENTER_ARROW_KEY_CODE }); | ||
expect(actual).to.have.members(["Apricot"]); | ||
describe('render tags correctly when html passed in text attribute, fix #267', () => { | ||
let modifiedTags = []; | ||
let handleAddition; | ||
let actual; | ||
beforeEach(() => { | ||
actual = []; | ||
modifiedTags = [ | ||
...defaults.tags, | ||
{ id: '1', text: <span style={{ color: 'red' }}> NewYork</span> }, | ||
{ id: '2', text: <span style={{ color: 'blue' }}> Austria</span> }, | ||
]; | ||
handleAddition = ({ id, text }) => { | ||
actual.push({ | ||
id, | ||
text: <span style={{ color: 'yellow' }}>{text}</span>, | ||
}); | ||
}; | ||
}); | ||
test('should render tags correctly', () => { | ||
const $el = mount( | ||
mockItem({ | ||
tags: modifiedTags, | ||
}) | ||
); | ||
expect($el.instance().props.tags).to.have.members(modifiedTags); | ||
}); | ||
$el.unmount(); | ||
test('allow adding tag which is not in the list', () => { | ||
const $el = mount( | ||
mockItem({ | ||
tags: modifiedTags, | ||
handleAddition, | ||
}) | ||
); | ||
const $input = $el.find('.ReactTags__tagInputField'); | ||
$input.simulate('change', { target: { value: 'Custom tag' } }); | ||
$input.simulate('keyDown', { keyCode: ENTER_ARROW_KEY_CODE }); | ||
expect(actual).to.have.length(1); | ||
expect(React.isValidElement(actual[0].text)).to.be.true; | ||
}); | ||
test('should not allow duplicate tags', () => { | ||
const actual = []; | ||
const $el = mount( | ||
mockItem({ | ||
tags: modifiedTags, | ||
handleAddition, | ||
}) | ||
); | ||
const $input = $el.find('.ReactTags__tagInputField'); | ||
$input.simulate('change', { target: { value: 'Austria' } }); | ||
$input.simulate('keyDown', { keyCode: ENTER_ARROW_KEY_CODE }); | ||
expect(actual).to.have.length(0); | ||
}); | ||
}); | ||
describe('autocomplete/suggestions filtering', () => { | ||
test('updates suggestions state as expected based on default filter logic', () => { | ||
const $el = mount(mockItem()); | ||
const ReactTagsInstance = $el.instance().getDecoratedComponentInstance(); | ||
const $input = $el.find('.ReactTags__tagInputField'); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members( | ||
defaults.suggestions | ||
); | ||
$input.simulate('change', { target: { value: 'ea' } }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members([]); | ||
$input.simulate('change', { target: { value: 'ap' } }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.deep.members([ | ||
{ id: 'Apple', text: 'Apple' }, | ||
{ id: 'Apricot', text: 'Apricot' }, | ||
]); | ||
}); | ||
test('updates suggestions state as expected based on custom filter logic', () => { | ||
const $el = mount( | ||
mockItem({ | ||
handleFilterSuggestions: (query, suggestions) => { | ||
return suggestions.filter((suggestion) => { | ||
return ( | ||
suggestion.text.toLowerCase().indexOf(query.toLowerCase()) >= 0 | ||
); | ||
}); | ||
}, | ||
}) | ||
); | ||
const ReactTagsInstance = $el.instance().getDecoratedComponentInstance(); | ||
const $input = $el.find('.ReactTags__tagInputField'); | ||
expect(ReactTagsInstance.state.suggestions).to.have.members( | ||
defaults.suggestions | ||
); | ||
$input.simulate('change', { target: { value: 'Ea' } }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.deep.members([ | ||
{ id: 'Pear', text: 'Pear' }, | ||
{ id: 'Peach', text: 'Peach' }, | ||
]); | ||
$input.simulate('change', { target: { value: 'ap' } }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.deep.members([ | ||
{ id: 'Apple', text: 'Apple' }, | ||
{ id: 'Apricot', text: 'Apricot' }, | ||
]); | ||
}); | ||
test('updates selectedIndex state as expected based on changing suggestions', () => { | ||
const $el = mount( | ||
mockItem({ | ||
autocomplete: true, | ||
handleFilterSuggestions: (query, suggestions) => { | ||
return suggestions.filter((suggestion) => { | ||
return ( | ||
suggestion.text.toLowerCase().indexOf(query.toLowerCase()) >= 0 | ||
); | ||
}); | ||
}, | ||
}) | ||
); | ||
const ReactTagsInstance = $el.instance().getDecoratedComponentInstance(); | ||
const $input = $el.find('.ReactTags__tagInputField'); | ||
expect(ReactTagsInstance.state.suggestions).to.have.deep.members( | ||
defaults.suggestions | ||
); | ||
$input.simulate('change', { target: { value: 'Ea' } }); | ||
$input.simulate('focus'); | ||
$input.simulate('keyDown', { keyCode: DOWN_ARROW_KEY_CODE }); | ||
$input.simulate('keyDown', { keyCode: DOWN_ARROW_KEY_CODE }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.deep.members([ | ||
{ id: 'Pear', text: 'Pear' }, | ||
{ id: 'Peach', text: 'Peach' }, | ||
]); | ||
expect(ReactTagsInstance.state.selectedIndex).to.equal(1); | ||
$input.simulate('change', { target: { value: 'Each' } }); | ||
expect(ReactTagsInstance.state.suggestions).to.have.deep.members([ | ||
{ id: 'Peach', text: 'Peach' }, | ||
]); | ||
expect(ReactTagsInstance.state.selectedIndex).to.equal(0); | ||
}); | ||
test('selects the correct suggestion using the keyboard when minQueryLength is set to 0', () => { | ||
let actual = []; | ||
const $el = mount( | ||
mockItem({ | ||
query: '', | ||
minQueryLength: 0, | ||
handleAddition(tag) { | ||
actual.push(tag); | ||
}, | ||
}) | ||
); | ||
const $input = $el.find('.ReactTags__tagInputField'); | ||
$input.simulate('keyDown', { keyCode: DOWN_ARROW_KEY_CODE }); | ||
$input.simulate('keyDown', { keyCode: DOWN_ARROW_KEY_CODE }); | ||
$input.simulate('keyDown', { keyCode: DOWN_ARROW_KEY_CODE }); | ||
$input.simulate('keyDown', { keyCode: ENTER_ARROW_KEY_CODE }); | ||
expect(actual).to.have.deep.members([{ id: 'Apricot', text: 'Apricot' }]); | ||
$el.unmount(); | ||
}); | ||
}); | ||
}); |
@@ -1,4 +0,4 @@ | ||
import Enzyme from "enzyme"; | ||
import React16Adapter from "enzyme-adapter-react-16"; | ||
import Enzyme from 'enzyme'; | ||
import React16Adapter from 'enzyme-adapter-react-16'; | ||
Enzyme.configure({ adapter: new React16Adapter() }); |
@@ -1,13 +0,18 @@ | ||
import React from "react"; | ||
import ReactDOM from "react-dom"; | ||
import { expect } from "chai"; | ||
import { shallow, mount, render } from "enzyme"; | ||
import { spy } from "sinon"; | ||
import Suggestions from "../lib/Suggestions"; | ||
import noop from "lodash/noop"; | ||
import React from 'react'; | ||
import ReactDOM from 'react-dom'; | ||
import { expect } from 'chai'; | ||
import { shallow, mount, render } from 'enzyme'; | ||
import { spy } from 'sinon'; | ||
import Suggestions from '../lib/Suggestions'; | ||
import noop from 'lodash/noop'; | ||
function mockItem(overrides) { | ||
const defaults = { | ||
query: "ang", | ||
suggestions: ["Banana", "Mango", "Pear", "Apricot"], | ||
query: 'ang', | ||
suggestions: [ | ||
{ id: 'Banana', text: 'Banana' }, | ||
{ id: 'Mango', text: 'Mango' }, | ||
{ id: 'Pear', text: 'Pear' }, | ||
{ id: 'Apricot', text: 'Apricot' }, | ||
], | ||
selectedIndex: 1, | ||
@@ -17,3 +22,3 @@ isFocused: true, | ||
handleHover: noop, | ||
classNames: { suggestions: "foo", activeSuggestion: "active" }, | ||
classNames: { suggestions: 'foo', activeSuggestion: 'active' }, | ||
}; | ||
@@ -24,58 +29,62 @@ const props = Object.assign({}, defaults, overrides); | ||
describe("Suggestions", function() { | ||
test("shows the classname properly", function() { | ||
describe('Suggestions', function() { | ||
test('shows the classname properly', function() { | ||
const $el = shallow(mockItem()); | ||
expect($el.find(".foo").length).to.equal(1); | ||
expect($el.find('.foo').length).to.equal(1); | ||
}); | ||
test("renders all suggestions properly", function() { | ||
test('renders all suggestions properly', function() { | ||
const $el = shallow(mockItem()); | ||
expect($el.find("li").length).to.equal(4); | ||
expect($el.find('li').length).to.equal(4); | ||
}); | ||
test("selects the correct suggestion", function() { | ||
test('selects the correct suggestion', function() { | ||
const $el = mount(mockItem()); | ||
expect($el.find("li.active").length).to.equal(1); | ||
expect($el.find("li.active").text()).to.equal("Mango"); | ||
expect($el.find('li.active').length).to.equal(1); | ||
expect($el.find('li.active').text()).to.equal('Mango'); | ||
}); | ||
test("should not render suggestion with less than minQueryLength", function() { | ||
test('should not render suggestion with less than minQueryLength', function() { | ||
const $el = shallow( | ||
mockItem({ | ||
minQueryLength: 2, | ||
query: "q", | ||
query: 'q', | ||
}) | ||
); | ||
expect($el.find(".foo").length).to.equal(0); | ||
expect($el.find("li").length).to.equal(0); | ||
expect($el.find('.foo').length).to.equal(0); | ||
expect($el.find('li').length).to.equal(0); | ||
}); | ||
test("should be able to override suggestion render", function() { | ||
test('should be able to override suggestion render', function() { | ||
const $el = shallow( | ||
mockItem({ | ||
minQueryLength: 2, | ||
query: "ignore_query", | ||
shouldRenderSuggestions: q => q !== "ignore_query", | ||
query: 'ignore_query', | ||
shouldRenderSuggestions: (q) => q !== 'ignore_query', | ||
}) | ||
); | ||
expect($el.find(".foo").length).to.equal(0); | ||
expect($el.find("li").length).to.equal(0); | ||
expect($el.find('.foo').length).to.equal(0); | ||
expect($el.find('li').length).to.equal(0); | ||
}); | ||
test("should mark highlighted suggestions correctly", function() { | ||
test('should mark highlighted suggestions correctly', function() { | ||
const $el = shallow(mockItem()); | ||
expect( | ||
$el | ||
.find("li.active") | ||
.find("span") | ||
.find('li.active') | ||
.find('span') | ||
.html() | ||
).to.equal("<span>M<mark>ang</mark>o</span>"); | ||
).to.equal('<span>M<mark>ang</mark>o</span>'); | ||
}); | ||
test("should not wastefully re-render if the list of suggestions have not changed", function() { | ||
const suggestions = ["queue", "quiz", "quantify"]; | ||
test('should not wastefully re-render if the list of suggestions have not changed', function() { | ||
const suggestions = [ | ||
{ id: 'queue', text: 'queue' }, | ||
{ id: 'quiz', text: 'quiz' }, | ||
{ id: 'quantify', text: 'quantify' }, | ||
]; | ||
const $el = mount( | ||
mockItem({ | ||
minQueryLength: 2, | ||
query: "q", | ||
query: 'q', | ||
suggestions: suggestions, | ||
@@ -85,3 +94,3 @@ }) | ||
spy(Suggestions.prototype, "componentDidUpdate"); | ||
spy(Suggestions.prototype, 'componentDidUpdate'); | ||
$el.setProps({ suggestions: suggestions }); | ||
@@ -92,12 +101,16 @@ expect(Suggestions.prototype.componentDidUpdate.called).to.equal(false); | ||
test("should re-render if there is an active query", function() { | ||
const suggestions = ["queue", "quiz", "quantify"]; | ||
test('should re-render if there is an active query', function() { | ||
const suggestions = [ | ||
{ id: 'queue', text: 'queue' }, | ||
{ id: 'quiz', text: 'quiz' }, | ||
{ id: 'quantify', text: 'quantify' }, | ||
]; | ||
const $el = mount( | ||
mockItem({ | ||
minQueryLength: 2, | ||
query: "qu", | ||
query: 'qu', | ||
suggestions: suggestions, | ||
}) | ||
); | ||
spy(Suggestions.prototype, "componentDidUpdate"); | ||
spy(Suggestions.prototype, 'componentDidUpdate'); | ||
$el.setProps({ suggestions: suggestions }); | ||
@@ -108,12 +121,16 @@ expect(Suggestions.prototype.componentDidUpdate.called).to.equal(true); | ||
test("should re-render if minQueryLength is set to 0", function() { | ||
const suggestions = ["queue", "quiz", "quantify"]; | ||
test('should re-render if minQueryLength is set to 0', function() { | ||
const suggestions = [ | ||
{ id: 'queue', text: 'queue' }, | ||
{ id: 'quiz', text: 'quiz' }, | ||
{ id: 'quantify', text: 'quantify' }, | ||
]; | ||
const $el = mount( | ||
mockItem({ | ||
minQueryLength: 0, | ||
query: "", | ||
query: '', | ||
suggestions: suggestions, | ||
}) | ||
); | ||
spy(Suggestions.prototype, "componentDidUpdate"); | ||
spy(Suggestions.prototype, 'componentDidUpdate'); | ||
$el.setProps({ suggestions: suggestions }); | ||
@@ -125,3 +142,7 @@ expect(Suggestions.prototype.componentDidUpdate.called).to.equal(true); | ||
test("should re-render if the provided 'shouldRenderSuggestions' prop returns true", function() { | ||
const suggestions = ["queue", "quiz", "quantify"]; | ||
const suggestions = [ | ||
{ id: 'queue', text: 'queue' }, | ||
{ id: 'quiz', text: 'quiz' }, | ||
{ id: 'quantify', text: 'quantify' }, | ||
]; | ||
const $el = mount( | ||
@@ -135,3 +156,3 @@ mockItem({ | ||
); | ||
spy(Suggestions.prototype, "componentDidUpdate"); | ||
spy(Suggestions.prototype, 'componentDidUpdate'); | ||
$el.setProps({ suggestions: suggestions }); | ||
@@ -142,12 +163,16 @@ expect(Suggestions.prototype.componentDidUpdate.called).to.equal(true); | ||
test("should re-render when the query reaches minQueryLength", function() { | ||
const suggestions = ["queue", "quiz", "quantify"]; | ||
let div = document.createElement("div"); | ||
test('should re-render when the query reaches minQueryLength', function() { | ||
const suggestions = [ | ||
{ id: 'queue', text: 'queue' }, | ||
{ id: 'quiz', text: 'quiz' }, | ||
{ id: 'quantify', text: 'quantify' }, | ||
]; | ||
let div = document.createElement('div'); | ||
let component = mockItem({ | ||
minQueryLength: 2, | ||
query: "", | ||
query: '', | ||
suggestions: suggestions, | ||
}); | ||
var $el = ReactDOM.render(component, div); | ||
spy($el, "componentDidUpdate"); | ||
spy($el, 'componentDidUpdate'); | ||
@@ -158,3 +183,3 @@ // re-render with updated query prop | ||
minQueryLength: 2, | ||
query: "qu", | ||
query: 'qu', | ||
suggestions: suggestions, | ||
@@ -161,0 +186,0 @@ }), |
@@ -1,13 +0,12 @@ | ||
import React, { Component } from "react"; | ||
import { expect } from "chai"; | ||
import { DragDropContext } from "react-dnd"; | ||
import TestBackend from "react-dnd-test-backend"; | ||
import { shallow, mount, render } from "enzyme"; | ||
import sinon from "sinon"; | ||
import TestUtils from "react-dom/test-utils"; | ||
import noop from "lodash/noop"; | ||
import Tag from "../lib/Tag"; | ||
import React, { Component } from 'react'; | ||
import { expect } from 'chai'; | ||
import { DragDropContext } from 'react-dnd'; | ||
import TestBackend from 'react-dnd-test-backend'; | ||
import { shallow, mount, render } from 'enzyme'; | ||
import sinon from 'sinon'; | ||
import TestUtils from 'react-dom/test-utils'; | ||
import noop from 'lodash/noop'; | ||
import Tag from '../lib/Tag'; | ||
function wrapInTestContext(DecoratedComponent) { | ||
class DecoratedComponentWrapper extends Component { | ||
@@ -17,4 +16,4 @@ constructor(props) { | ||
} | ||
render(){ | ||
return <DecoratedComponent {...this.props} /> | ||
render() { | ||
return <DecoratedComponent {...this.props} />; | ||
} | ||
@@ -29,3 +28,3 @@ } | ||
{ | ||
tag: { id: 1, text: "FooBar" }, | ||
tag: { id: '1', text: 'FooBar' }, | ||
onDelete: noop, | ||
@@ -35,4 +34,4 @@ readOnly: false, | ||
classNames: { | ||
tag: "tag", | ||
remove: "remove", | ||
tag: 'tag', | ||
remove: 'remove', | ||
}, | ||
@@ -46,20 +45,20 @@ }, | ||
describe("Tag", () => { | ||
test("shows the classnames of children properly", () => { | ||
describe('Tag', () => { | ||
test('shows the classnames of children properly', () => { | ||
const $el = mount(mockItem()); | ||
expect($el.find(".tag").length).to.equal(1); | ||
expect($el.text()).to.have.string("FooBar"); | ||
expect($el.find('.tag').length).to.equal(1); | ||
expect($el.text()).to.have.string('FooBar'); | ||
}); | ||
test("should show cross for removing tag when read-only is false", () => { | ||
test('should show cross for removing tag when read-only is false', () => { | ||
const $el = mount(mockItem()); | ||
expect($el.find("a.remove").length).to.equal(1); | ||
expect($el.find('a.remove').length).to.equal(1); | ||
}); | ||
test("should not show cross for removing tag when read-only is true", () => { | ||
test('should not show cross for removing tag when read-only is true', () => { | ||
const $el = mount(mockItem({ readOnly: true })); | ||
expect($el.find("a.remove").length).to.equal(0); | ||
expect($el.find('a.remove').length).to.equal(0); | ||
}); | ||
test("renders passed in removed component correctly", () => { | ||
test('renders passed in removed component correctly', () => { | ||
const CustomRemoveComponent = function() { | ||
@@ -69,9 +68,9 @@ return <a className="remove">delete me</a>; | ||
const $el = mount(mockItem({ removeComponent: CustomRemoveComponent })); | ||
expect($el.find("a.remove").length).to.equal(1); | ||
expect($el.text()).to.have.string("delete me"); | ||
expect($el.find('a.remove').length).to.equal(1); | ||
expect($el.text()).to.have.string('delete me'); | ||
}); | ||
test("renders conditionaly passed in removed component correctly", () => { | ||
test('renders conditionaly passed in removed component correctly', () => { | ||
const CustomConditionRemoveComponent = function(props) { | ||
return props.tag.id === 1 ? null : <a className="removeTag">x</a>; | ||
return props.tag.id === '1' ? null : <a className="removeTag">x</a>; | ||
}; | ||
@@ -81,20 +80,20 @@ const $el = mount( | ||
); | ||
expect($el.find(".removeTag").length).to.equal(0); | ||
expect($el.find('.removeTag').length).to.equal(0); | ||
}); | ||
test("calls the delete handler correctly", () => { | ||
test('calls the delete handler correctly', () => { | ||
const spy = sinon.spy(); | ||
const $el = mount(mockItem({ onDelete: spy })); | ||
$el.find("a.remove").simulate("click"); | ||
$el.find('a.remove').simulate('click'); | ||
expect(spy.calledOnce).to.be.true; | ||
}); | ||
test("calls the tag click handler correctly", () => { | ||
test('calls the tag click handler correctly', () => { | ||
const spy = sinon.spy(); | ||
const $el = mount(mockItem({ onTagClicked: spy })); | ||
$el.find("span").simulate("click"); | ||
$el.find('span').simulate('click'); | ||
expect(spy.calledOnce).to.be.true; | ||
}); | ||
test("should be draggable", () => { | ||
test('should be draggable', () => { | ||
const root = TestUtils.renderIntoDocument(mockItem()); | ||
@@ -107,10 +106,10 @@ const backend = root.getManager().getBackend(); | ||
expect(tag.getDecoratedComponentInstance().state.isDragging).to.be.true; | ||
const el = TestUtils.findRenderedDOMComponentWithTag(root, "span"); | ||
expect(el.style.opacity).to.equal("0"); | ||
const el = TestUtils.findRenderedDOMComponentWithTag(root, 'span'); | ||
expect(el.style.opacity).to.equal('0'); | ||
backend.simulateEndDrag(); | ||
expect(el.style.opacity).to.equal("1"); | ||
expect(el.style.opacity).to.equal('1'); | ||
expect(tag.getDecoratedComponentInstance().state.isDragging).to.be.false; | ||
}); | ||
test("should not be draggable if readOnly is true", () => { | ||
test('should not be draggable if readOnly is true', () => { | ||
const root = TestUtils.renderIntoDocument(mockItem({ readOnly: true })); | ||
@@ -117,0 +116,0 @@ const backend = root.getManager().getBackend(); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1963568
36
24355
458
7
25
7
+ Addedcore-js@2.5.4
+ Addedcore-js@2.5.4(transitive)
+ Addedreact-dnd@2.6.0(transitive)
- Removedreact-dnd@2.5.4(transitive)
Updatedreact-dnd@2.6.0