react-tag-autocomplete
Advanced tools
Comparing version 6.1.0 to 6.2.0
# Changelog | ||
## 6.2.0 | ||
- Added `newTagText` option to display a prompt in the suggestions list to create a new tag when the `allowNew` option is enabled ([cml391](https://github.com/cml391)) | ||
- Refactored call of `onAddition` callback to always provide a new object instead of passing the selected tag by reference. | ||
- Refactored `classNames` option to merge the provided prop with defaults ([alexandernst](https://github.com/alexandernst)) | ||
- Fixed updates to the `placeholder` option which did not recalculate input size ([LarsHassler](https://github.com/LarsHassler)) | ||
- Updated React peer dependency support to include 17+ | ||
- Updated example page with a new demo | ||
## 6.1.0 | ||
@@ -26,3 +35,3 @@ | ||
- Removed `delimiterChars` option | ||
- Updated React dependency to 16.5+ | ||
- Updated React peer dependency to 16.5+ | ||
@@ -29,0 +38,0 @@ ## 5.13.1 |
'use strict'; | ||
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } | ||
var React = require('react'); | ||
var PropTypes = require('prop-types'); | ||
var React = _interopDefault(require('react')); | ||
var PropTypes = _interopDefault(require('prop-types')); | ||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
var React__default = /*#__PURE__*/_interopDefaultLegacy(React); | ||
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes); | ||
var Tag = (props) => ( | ||
React.createElement( 'button', { type: 'button', className: props.classNames.selectedTag, title: props.removeButtonText, onClick: props.onDelete }, | ||
React.createElement( 'span', { className: props.classNames.selectedTagName }, props.tag.name) | ||
React__default['default'].createElement( 'button', { type: 'button', className: props.classNames.selectedTag, title: props.removeButtonText, onClick: props.onDelete }, | ||
React__default['default'].createElement( 'span', { className: props.classNames.selectedTagName }, props.tag.name) | ||
) | ||
@@ -32,3 +35,3 @@ ); | ||
class Input extends React.Component { | ||
class Input extends React__default['default'].Component { | ||
constructor (props) { | ||
@@ -38,4 +41,4 @@ super(props); | ||
this.input = React.createRef(); | ||
this.sizer = React.createRef(); | ||
this.input = React__default['default'].createRef(); | ||
this.sizer = React__default['default'].createRef(); | ||
} | ||
@@ -50,4 +53,4 @@ | ||
componentDidUpdate ({ query, placeholder }) { | ||
if (query !== this.props.query || placeholder !== this.props.placeholder) { | ||
componentDidUpdate ({ query, placeholderText }) { | ||
if (query !== this.props.query || placeholderText !== this.props.placeholderText) { | ||
this.updateInputWidth(); | ||
@@ -83,6 +86,6 @@ } | ||
return ( | ||
React.createElement( 'div', { className: classNames.searchWrapper }, | ||
React.createElement( 'input', Object.assign({}, | ||
React__default['default'].createElement( 'div', { className: classNames.searchWrapper }, | ||
React__default['default'].createElement( 'input', Object.assign({}, | ||
inputAttributes, inputEventHandlers, { ref: this.input, value: query, placeholder: placeholderText, className: classNames.searchInput, role: 'combobox', 'aria-autocomplete': 'list', 'aria-label': ariaLabelText || placeholderText, 'aria-owns': id, 'aria-activedescendant': index > -1 ? `${id}-${index}` : null, 'aria-expanded': expanded, style: { width: this.state.inputWidth } })), | ||
React.createElement( 'div', { ref: this.sizer, style: SIZER_STYLES }, query || placeholderText) | ||
React__default['default'].createElement( 'div', { ref: this.sizer, style: SIZER_STYLES }, query || placeholderText) | ||
) | ||
@@ -115,6 +118,6 @@ ) | ||
const DefaultSuggestionComponent = ({ item, query }) => ( | ||
React.createElement( 'span', { dangerouslySetInnerHTML: { __html: markIt(item.name, query) } }) | ||
React__default['default'].createElement( 'span', { dangerouslySetInnerHTML: { __html: markIt(item.name, query) } }) | ||
); | ||
class Suggestions extends React.Component { | ||
class Suggestions extends React__default['default'].Component { | ||
onMouseDown (item, e) { | ||
@@ -146,6 +149,10 @@ // focus is shifted on mouse down but calling preventDefault prevents this | ||
return ( | ||
React.createElement( 'li', { | ||
React__default['default'].createElement( 'li', { | ||
id: key, key: key, role: 'option', className: classNames.join(' '), 'aria-disabled': item.disabled === true, onMouseDown: this.onMouseDown.bind(this, item) }, | ||
item.disableMarkIt ? item.name | ||
: React.createElement( SuggestionComponent, { item: item, query: this.props.query }) | ||
item.prefix | ||
? React__default['default'].createElement( 'span', { className: this.props.classNames.suggestionPrefix }, item.prefix, ' ') | ||
: null, | ||
item.disableMarkIt | ||
? item.name | ||
: React__default['default'].createElement( SuggestionComponent, { item: item, query: this.props.query }) | ||
) | ||
@@ -156,4 +163,4 @@ ) | ||
return ( | ||
React.createElement( 'div', { className: this.props.classNames.suggestions }, | ||
React.createElement( 'ul', { role: 'listbox', id: this.props.id }, options) | ||
React__default['default'].createElement( 'div', { className: this.props.classNames.suggestions }, | ||
React__default['default'].createElement( 'ul', { role: 'listbox', id: this.props.id }, options) | ||
) | ||
@@ -164,2 +171,17 @@ ) | ||
function focusNextElement(scope, currentTarget) { | ||
const interactiveEls = scope.querySelectorAll("a,button,input"); | ||
const currentEl = Array.prototype.findIndex.call( | ||
interactiveEls, | ||
(element) => element === currentTarget | ||
); | ||
const nextEl = interactiveEls[currentEl - 1] || interactiveEls[currentEl + 1]; | ||
if (nextEl) { | ||
nextEl.focus(); | ||
} | ||
} | ||
const KEYS = { | ||
@@ -186,16 +208,19 @@ ENTER: 'Enter', | ||
suggestionActive: 'is-active', | ||
suggestionDisabled: 'is-disabled' | ||
suggestionDisabled: 'is-disabled', | ||
suggestionPrefix: 'react-tags__suggestion-prefix' | ||
}; | ||
function findMatchIndex (options, query) { | ||
return options.findIndex((option) => matchExact(query).test(option.name)) | ||
} | ||
function pressDelimiter () { | ||
if (this.state.query.length >= this.props.minQueryLength) { | ||
// Check if the user typed in an existing suggestion. | ||
const match = this.state.options.findIndex((option) => { | ||
return matchExact(this.state.query).test(option.name) | ||
}); | ||
const match = findMatchIndex(this.state.options, this.state.query); | ||
const index = this.state.index === -1 ? match : this.state.index; | ||
const tag = index > -1 ? this.state.options[index] : null; | ||
if (index > -1 && this.state.options[index]) { | ||
this.addTag(this.state.options[index]); | ||
if (tag) { | ||
this.addTag(tag); | ||
} else if (this.props.allowNew) { | ||
@@ -244,10 +269,18 @@ this.addTag({ name: this.state.query }); | ||
if (options.length === 0 && props.noSuggestionsText) { | ||
options.push({ id: 0, name: props.noSuggestionsText, disabled: true, disableMarkIt: true }); | ||
options = options.slice(0, props.maxSuggestionsLength); | ||
if (props.allowNew) { | ||
if (props.newTagText && findMatchIndex(options, state.query) === -1) { | ||
options.push({ id: 0, name: state.query, prefix: props.newTagText, disableMarkIt: true }); | ||
} | ||
} else { | ||
if (props.noSuggestionsText && options.length === 0) { | ||
options.push({ id: 0, name: props.noSuggestionsText, disabled: true, disableMarkIt: true }); | ||
} | ||
} | ||
return options.slice(0, props.maxSuggestionsLength) | ||
return options | ||
} | ||
class ReactTags extends React.Component { | ||
class ReactTags extends React__default['default'].Component { | ||
constructor (props) { | ||
@@ -273,5 +306,5 @@ super(props); | ||
this.container = React.createRef(); | ||
this.input = React.createRef(); | ||
this.suggestions = React.createRef(); | ||
this.container = React__default['default'].createRef(); | ||
this.input = React__default['default'].createRef(); | ||
this.suggestions = React__default['default'].createRef(); | ||
} | ||
@@ -354,13 +387,3 @@ | ||
if (this.container.current) { | ||
const interactiveEls = this.container.current.querySelectorAll('a,button,input'); | ||
const currentEl = Array.prototype.findIndex.call(interactiveEls, (element) => { | ||
return element === event.currentTarget | ||
}); | ||
const nextEl = interactiveEls[currentEl - 1] || interactiveEls[currentEl + 1]; | ||
if (nextEl) { | ||
nextEl.focus(); | ||
} | ||
focusNextElement(this.container.current, event.currentTarget); | ||
} | ||
@@ -380,3 +403,3 @@ | ||
this.props.onAddition(tag); | ||
this.props.onAddition({ id: tag.id, name: tag.name }); | ||
@@ -401,20 +424,21 @@ this.clearInput(); | ||
const expanded = this.state.focused && this.state.query.length >= this.props.minQueryLength; | ||
const classNames = [this.props.classNames.root]; | ||
const classNames = Object.assign({}, CLASS_NAMES, this.props.classNames); | ||
const rootClassNames = [classNames.root]; | ||
this.state.focused && classNames.push(this.props.classNames.rootFocused); | ||
this.state.focused && rootClassNames.push(classNames.rootFocused); | ||
return ( | ||
React.createElement( 'div', { ref: this.container, className: classNames.join(' '), onClick: this.onClick.bind(this) }, | ||
React.createElement( 'div', { | ||
className: this.props.classNames.selected, 'aria-relevant': 'additions removals', 'aria-live': 'polite' }, | ||
React__default['default'].createElement( 'div', { ref: this.container, className: rootClassNames.join(' '), onClick: this.onClick.bind(this) }, | ||
React__default['default'].createElement( 'div', { | ||
className: classNames.selected, 'aria-relevant': 'additions removals', 'aria-live': 'polite' }, | ||
this.props.tags.map((tag, i) => ( | ||
React.createElement( TagComponent, { | ||
key: i, tag: tag, removeButtonText: this.props.removeButtonText, classNames: this.props.classNames, onDelete: this.onDeleteTag.bind(this, i) }) | ||
React__default['default'].createElement( TagComponent, { | ||
key: i, tag: tag, removeButtonText: this.props.removeButtonText, classNames: classNames, onDelete: this.onDeleteTag.bind(this, i) }) | ||
)) | ||
), | ||
React.createElement( 'div', { className: this.props.classNames.search }, | ||
React.createElement( Input, Object.assign({}, | ||
this.state, { id: this.props.id, ref: this.input, classNames: this.props.classNames, inputAttributes: this.props.inputAttributes, inputEventHandlers: this.inputEventHandlers, autoresize: this.props.autoresize, expanded: expanded, placeholderText: this.props.placeholderText, ariaLabelText: this.props.ariaLabelText })), | ||
React.createElement( Suggestions, Object.assign({}, | ||
this.state, { id: this.props.id, ref: this.suggestions, classNames: this.props.classNames, expanded: expanded, addTag: this.addTag.bind(this), suggestionComponent: this.props.suggestionComponent })) | ||
React__default['default'].createElement( 'div', { className: classNames.search }, | ||
React__default['default'].createElement( Input, Object.assign({}, | ||
this.state, { id: this.props.id, ref: this.input, classNames: classNames, inputAttributes: this.props.inputAttributes, inputEventHandlers: this.inputEventHandlers, autoresize: this.props.autoresize, expanded: expanded, placeholderText: this.props.placeholderText, ariaLabelText: this.props.ariaLabelText })), | ||
React__default['default'].createElement( Suggestions, Object.assign({}, | ||
this.state, { id: this.props.id, ref: this.suggestions, classNames: classNames, expanded: expanded, addTag: this.addTag.bind(this), suggestionComponent: this.props.suggestionComponent })) | ||
) | ||
@@ -444,2 +468,3 @@ ) | ||
noSuggestionsText: null, | ||
newTagText: null, | ||
suggestions: [], | ||
@@ -462,36 +487,37 @@ suggestionsFilter: defaultSuggestionsFilter, | ||
ReactTags.propTypes = { | ||
id: PropTypes.string, | ||
tags: PropTypes.arrayOf(PropTypes.object), | ||
placeholderText: PropTypes.string, | ||
ariaLabelText: PropTypes.string, | ||
removeButtonText: PropTypes.string, | ||
noSuggestionsText: PropTypes.string, | ||
suggestions: PropTypes.arrayOf(PropTypes.object), | ||
suggestionsFilter: PropTypes.func, | ||
suggestionsTransform: PropTypes.func, | ||
autoresize: PropTypes.bool, | ||
delimiters: PropTypes.arrayOf(PropTypes.string), | ||
onDelete: PropTypes.func.isRequired, | ||
onAddition: PropTypes.func.isRequired, | ||
onInput: PropTypes.func, | ||
onFocus: PropTypes.func, | ||
onBlur: PropTypes.func, | ||
onValidate: PropTypes.func, | ||
minQueryLength: PropTypes.number, | ||
maxSuggestionsLength: PropTypes.number, | ||
classNames: PropTypes.object, | ||
allowNew: PropTypes.bool, | ||
allowBackspace: PropTypes.bool, | ||
addOnBlur: PropTypes.bool, | ||
tagComponent: PropTypes.oneOfType([ | ||
PropTypes.func, | ||
PropTypes.element | ||
id: PropTypes__default['default'].string, | ||
tags: PropTypes__default['default'].arrayOf(PropTypes__default['default'].object), | ||
placeholderText: PropTypes__default['default'].string, | ||
ariaLabelText: PropTypes__default['default'].string, | ||
removeButtonText: PropTypes__default['default'].string, | ||
noSuggestionsText: PropTypes__default['default'].string, | ||
newTagText: PropTypes__default['default'].string, | ||
suggestions: PropTypes__default['default'].arrayOf(PropTypes__default['default'].object), | ||
suggestionsFilter: PropTypes__default['default'].func, | ||
suggestionsTransform: PropTypes__default['default'].func, | ||
autoresize: PropTypes__default['default'].bool, | ||
delimiters: PropTypes__default['default'].arrayOf(PropTypes__default['default'].string), | ||
onDelete: PropTypes__default['default'].func.isRequired, | ||
onAddition: PropTypes__default['default'].func.isRequired, | ||
onInput: PropTypes__default['default'].func, | ||
onFocus: PropTypes__default['default'].func, | ||
onBlur: PropTypes__default['default'].func, | ||
onValidate: PropTypes__default['default'].func, | ||
minQueryLength: PropTypes__default['default'].number, | ||
maxSuggestionsLength: PropTypes__default['default'].number, | ||
classNames: PropTypes__default['default'].object, | ||
allowNew: PropTypes__default['default'].bool, | ||
allowBackspace: PropTypes__default['default'].bool, | ||
addOnBlur: PropTypes__default['default'].bool, | ||
tagComponent: PropTypes__default['default'].oneOfType([ | ||
PropTypes__default['default'].func, | ||
PropTypes__default['default'].element | ||
]), | ||
suggestionComponent: PropTypes.oneOfType([ | ||
PropTypes.func, | ||
PropTypes.element | ||
suggestionComponent: PropTypes__default['default'].oneOfType([ | ||
PropTypes__default['default'].func, | ||
PropTypes__default['default'].element | ||
]), | ||
inputAttributes: PropTypes.object | ||
inputAttributes: PropTypes__default['default'].object | ||
}; | ||
module.exports = ReactTags; |
@@ -44,4 +44,4 @@ import React from 'react'; | ||
componentDidUpdate ({ query, placeholder }) { | ||
if (query !== this.props.query || placeholder !== this.props.placeholder) { | ||
componentDidUpdate ({ query, placeholderText }) { | ||
if (query !== this.props.query || placeholderText !== this.props.placeholderText) { | ||
this.updateInputWidth(); | ||
@@ -140,3 +140,7 @@ } | ||
id: key, key: key, role: 'option', className: classNames.join(' '), 'aria-disabled': item.disabled === true, onMouseDown: this.onMouseDown.bind(this, item) }, | ||
item.disableMarkIt ? item.name | ||
item.prefix | ||
? React.createElement( 'span', { className: this.props.classNames.suggestionPrefix }, item.prefix, ' ') | ||
: null, | ||
item.disableMarkIt | ||
? item.name | ||
: React.createElement( SuggestionComponent, { item: item, query: this.props.query }) | ||
@@ -155,2 +159,17 @@ ) | ||
function focusNextElement(scope, currentTarget) { | ||
const interactiveEls = scope.querySelectorAll("a,button,input"); | ||
const currentEl = Array.prototype.findIndex.call( | ||
interactiveEls, | ||
(element) => element === currentTarget | ||
); | ||
const nextEl = interactiveEls[currentEl - 1] || interactiveEls[currentEl + 1]; | ||
if (nextEl) { | ||
nextEl.focus(); | ||
} | ||
} | ||
const KEYS = { | ||
@@ -177,16 +196,19 @@ ENTER: 'Enter', | ||
suggestionActive: 'is-active', | ||
suggestionDisabled: 'is-disabled' | ||
suggestionDisabled: 'is-disabled', | ||
suggestionPrefix: 'react-tags__suggestion-prefix' | ||
}; | ||
function findMatchIndex (options, query) { | ||
return options.findIndex((option) => matchExact(query).test(option.name)) | ||
} | ||
function pressDelimiter () { | ||
if (this.state.query.length >= this.props.minQueryLength) { | ||
// Check if the user typed in an existing suggestion. | ||
const match = this.state.options.findIndex((option) => { | ||
return matchExact(this.state.query).test(option.name) | ||
}); | ||
const match = findMatchIndex(this.state.options, this.state.query); | ||
const index = this.state.index === -1 ? match : this.state.index; | ||
const tag = index > -1 ? this.state.options[index] : null; | ||
if (index > -1 && this.state.options[index]) { | ||
this.addTag(this.state.options[index]); | ||
if (tag) { | ||
this.addTag(tag); | ||
} else if (this.props.allowNew) { | ||
@@ -235,7 +257,15 @@ this.addTag({ name: this.state.query }); | ||
if (options.length === 0 && props.noSuggestionsText) { | ||
options.push({ id: 0, name: props.noSuggestionsText, disabled: true, disableMarkIt: true }); | ||
options = options.slice(0, props.maxSuggestionsLength); | ||
if (props.allowNew) { | ||
if (props.newTagText && findMatchIndex(options, state.query) === -1) { | ||
options.push({ id: 0, name: state.query, prefix: props.newTagText, disableMarkIt: true }); | ||
} | ||
} else { | ||
if (props.noSuggestionsText && options.length === 0) { | ||
options.push({ id: 0, name: props.noSuggestionsText, disabled: true, disableMarkIt: true }); | ||
} | ||
} | ||
return options.slice(0, props.maxSuggestionsLength) | ||
return options | ||
} | ||
@@ -344,13 +374,3 @@ | ||
if (this.container.current) { | ||
const interactiveEls = this.container.current.querySelectorAll('a,button,input'); | ||
const currentEl = Array.prototype.findIndex.call(interactiveEls, (element) => { | ||
return element === event.currentTarget | ||
}); | ||
const nextEl = interactiveEls[currentEl - 1] || interactiveEls[currentEl + 1]; | ||
if (nextEl) { | ||
nextEl.focus(); | ||
} | ||
focusNextElement(this.container.current, event.currentTarget); | ||
} | ||
@@ -370,3 +390,3 @@ | ||
this.props.onAddition(tag); | ||
this.props.onAddition({ id: tag.id, name: tag.name }); | ||
@@ -391,20 +411,21 @@ this.clearInput(); | ||
const expanded = this.state.focused && this.state.query.length >= this.props.minQueryLength; | ||
const classNames = [this.props.classNames.root]; | ||
const classNames = Object.assign({}, CLASS_NAMES, this.props.classNames); | ||
const rootClassNames = [classNames.root]; | ||
this.state.focused && classNames.push(this.props.classNames.rootFocused); | ||
this.state.focused && rootClassNames.push(classNames.rootFocused); | ||
return ( | ||
React.createElement( 'div', { ref: this.container, className: classNames.join(' '), onClick: this.onClick.bind(this) }, | ||
React.createElement( 'div', { ref: this.container, className: rootClassNames.join(' '), onClick: this.onClick.bind(this) }, | ||
React.createElement( 'div', { | ||
className: this.props.classNames.selected, 'aria-relevant': 'additions removals', 'aria-live': 'polite' }, | ||
className: classNames.selected, 'aria-relevant': 'additions removals', 'aria-live': 'polite' }, | ||
this.props.tags.map((tag, i) => ( | ||
React.createElement( TagComponent, { | ||
key: i, tag: tag, removeButtonText: this.props.removeButtonText, classNames: this.props.classNames, onDelete: this.onDeleteTag.bind(this, i) }) | ||
key: i, tag: tag, removeButtonText: this.props.removeButtonText, classNames: classNames, onDelete: this.onDeleteTag.bind(this, i) }) | ||
)) | ||
), | ||
React.createElement( 'div', { className: this.props.classNames.search }, | ||
React.createElement( 'div', { className: classNames.search }, | ||
React.createElement( Input, Object.assign({}, | ||
this.state, { id: this.props.id, ref: this.input, classNames: this.props.classNames, inputAttributes: this.props.inputAttributes, inputEventHandlers: this.inputEventHandlers, autoresize: this.props.autoresize, expanded: expanded, placeholderText: this.props.placeholderText, ariaLabelText: this.props.ariaLabelText })), | ||
this.state, { id: this.props.id, ref: this.input, classNames: classNames, inputAttributes: this.props.inputAttributes, inputEventHandlers: this.inputEventHandlers, autoresize: this.props.autoresize, expanded: expanded, placeholderText: this.props.placeholderText, ariaLabelText: this.props.ariaLabelText })), | ||
React.createElement( Suggestions, Object.assign({}, | ||
this.state, { id: this.props.id, ref: this.suggestions, classNames: this.props.classNames, expanded: expanded, addTag: this.addTag.bind(this), suggestionComponent: this.props.suggestionComponent })) | ||
this.state, { id: this.props.id, ref: this.suggestions, classNames: classNames, expanded: expanded, addTag: this.addTag.bind(this), suggestionComponent: this.props.suggestionComponent })) | ||
) | ||
@@ -434,2 +455,3 @@ ) | ||
noSuggestionsText: null, | ||
newTagText: null, | ||
suggestions: [], | ||
@@ -458,2 +480,3 @@ suggestionsFilter: defaultSuggestionsFilter, | ||
noSuggestionsText: PropTypes.string, | ||
newTagText: PropTypes.string, | ||
suggestions: PropTypes.arrayOf(PropTypes.object), | ||
@@ -460,0 +483,0 @@ suggestionsFilter: PropTypes.func, |
(function (global, factory) { | ||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('react'), require('prop-types')) : | ||
typeof define === 'function' && define.amd ? define(['react', 'prop-types'], factory) : | ||
(global = global || self, global.ReactTags = factory(global.React, global.PropTypes)); | ||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.ReactTags = factory(global.React, global.PropTypes)); | ||
}(this, (function (React, PropTypes) { 'use strict'; | ||
React = React && Object.prototype.hasOwnProperty.call(React, 'default') ? React['default'] : React; | ||
PropTypes = PropTypes && Object.prototype.hasOwnProperty.call(PropTypes, 'default') ? PropTypes['default'] : PropTypes; | ||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } | ||
var React__default = /*#__PURE__*/_interopDefaultLegacy(React); | ||
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes); | ||
function Tag (props) { return ( | ||
React.createElement( 'button', { type: 'button', className: props.classNames.selectedTag, title: props.removeButtonText, onClick: props.onDelete }, | ||
React.createElement( 'span', { className: props.classNames.selectedTagName }, props.tag.name) | ||
React__default['default'].createElement( 'button', { type: 'button', className: props.classNames.selectedTag, title: props.removeButtonText, onClick: props.onDelete }, | ||
React__default['default'].createElement( 'span', { className: props.classNames.selectedTagName }, props.tag.name) | ||
) | ||
@@ -39,4 +41,4 @@ ); } | ||
this.input = React.createRef(); | ||
this.sizer = React.createRef(); | ||
this.input = React__default['default'].createRef(); | ||
this.sizer = React__default['default'].createRef(); | ||
} | ||
@@ -57,5 +59,5 @@ | ||
var query = ref.query; | ||
var placeholder = ref.placeholder; | ||
var placeholderText = ref.placeholderText; | ||
if (query !== this.props.query || placeholder !== this.props.placeholder) { | ||
if (query !== this.props.query || placeholderText !== this.props.placeholderText) { | ||
this.updateInputWidth(); | ||
@@ -66,3 +68,3 @@ } | ||
Input.prototype.copyInputStyles = function copyInputStyles () { | ||
var this$1 = this; | ||
var this$1$1 = this; | ||
@@ -72,3 +74,3 @@ var inputStyle = window.getComputedStyle(this.input.current); | ||
STYLE_PROPS.forEach(function (prop) { | ||
this$1.sizer.current.style[prop] = inputStyle[prop]; | ||
this$1$1.sizer.current.style[prop] = inputStyle[prop]; | ||
}); | ||
@@ -104,6 +106,6 @@ }; | ||
return ( | ||
React.createElement( 'div', { className: classNames.searchWrapper }, | ||
React.createElement( 'input', Object.assign({}, | ||
React__default['default'].createElement( 'div', { className: classNames.searchWrapper }, | ||
React__default['default'].createElement( 'input', Object.assign({}, | ||
inputAttributes, inputEventHandlers, { ref: this.input, value: query, placeholder: placeholderText, className: classNames.searchInput, role: 'combobox', 'aria-autocomplete': 'list', 'aria-label': ariaLabelText || placeholderText, 'aria-owns': id, 'aria-activedescendant': index > -1 ? (id + "-" + index) : null, 'aria-expanded': expanded, style: { width: this.state.inputWidth } })), | ||
React.createElement( 'div', { ref: this.sizer, style: SIZER_STYLES }, query || placeholderText) | ||
React__default['default'].createElement( 'div', { ref: this.sizer, style: SIZER_STYLES }, query || placeholderText) | ||
) | ||
@@ -114,3 +116,3 @@ ) | ||
return Input; | ||
}(React.Component)); | ||
}(React__default['default'].Component)); | ||
@@ -143,3 +145,3 @@ function escapeForRegExp (string) { | ||
return ( | ||
React.createElement( 'span', { dangerouslySetInnerHTML: { __html: markIt(item.name, query) } }) | ||
React__default['default'].createElement( 'span', { dangerouslySetInnerHTML: { __html: markIt(item.name, query) } }) | ||
); | ||
@@ -164,3 +166,3 @@ }; | ||
Suggestions.prototype.render = function render () { | ||
var this$1 = this; | ||
var this$1$1 = this; | ||
@@ -174,18 +176,22 @@ if (!this.props.expanded || !this.props.options.length) { | ||
var options = this.props.options.map(function (item, index) { | ||
var key = (this$1.props.id) + "-" + index; | ||
var key = (this$1$1.props.id) + "-" + index; | ||
var classNames = []; | ||
if (this$1.props.index === index) { | ||
classNames.push(this$1.props.classNames.suggestionActive); | ||
if (this$1$1.props.index === index) { | ||
classNames.push(this$1$1.props.classNames.suggestionActive); | ||
} | ||
if (item.disabled) { | ||
classNames.push(this$1.props.classNames.suggestionDisabled); | ||
classNames.push(this$1$1.props.classNames.suggestionDisabled); | ||
} | ||
return ( | ||
React.createElement( 'li', { | ||
id: key, key: key, role: 'option', className: classNames.join(' '), 'aria-disabled': item.disabled === true, onMouseDown: this$1.onMouseDown.bind(this$1, item) }, | ||
item.disableMarkIt ? item.name | ||
: React.createElement( SuggestionComponent, { item: item, query: this$1.props.query }) | ||
React__default['default'].createElement( 'li', { | ||
id: key, key: key, role: 'option', className: classNames.join(' '), 'aria-disabled': item.disabled === true, onMouseDown: this$1$1.onMouseDown.bind(this$1$1, item) }, | ||
item.prefix | ||
? React__default['default'].createElement( 'span', { className: this$1$1.props.classNames.suggestionPrefix }, item.prefix, ' ') | ||
: null, | ||
item.disableMarkIt | ||
? item.name | ||
: React__default['default'].createElement( SuggestionComponent, { item: item, query: this$1$1.props.query }) | ||
) | ||
@@ -196,4 +202,4 @@ ) | ||
return ( | ||
React.createElement( 'div', { className: this.props.classNames.suggestions }, | ||
React.createElement( 'ul', { role: 'listbox', id: this.props.id }, options) | ||
React__default['default'].createElement( 'div', { className: this.props.classNames.suggestions }, | ||
React__default['default'].createElement( 'ul', { role: 'listbox', id: this.props.id }, options) | ||
) | ||
@@ -204,4 +210,19 @@ ) | ||
return Suggestions; | ||
}(React.Component)); | ||
}(React__default['default'].Component)); | ||
function focusNextElement(scope, currentTarget) { | ||
var interactiveEls = scope.querySelectorAll("a,button,input"); | ||
var currentEl = Array.prototype.findIndex.call( | ||
interactiveEls, | ||
function (element) { return element === currentTarget; } | ||
); | ||
var nextEl = interactiveEls[currentEl - 1] || interactiveEls[currentEl + 1]; | ||
if (nextEl) { | ||
nextEl.focus(); | ||
} | ||
} | ||
var KEYS = { | ||
@@ -228,18 +249,19 @@ ENTER: 'Enter', | ||
suggestionActive: 'is-active', | ||
suggestionDisabled: 'is-disabled' | ||
suggestionDisabled: 'is-disabled', | ||
suggestionPrefix: 'react-tags__suggestion-prefix' | ||
}; | ||
function findMatchIndex (options, query) { | ||
return options.findIndex(function (option) { return matchExact(query).test(option.name); }) | ||
} | ||
function pressDelimiter () { | ||
var this$1 = this; | ||
if (this.state.query.length >= this.props.minQueryLength) { | ||
// Check if the user typed in an existing suggestion. | ||
var match = this.state.options.findIndex(function (option) { | ||
return matchExact(this$1.state.query).test(option.name) | ||
}); | ||
var match = findMatchIndex(this.state.options, this.state.query); | ||
var index = this.state.index === -1 ? match : this.state.index; | ||
var tag = index > -1 ? this.state.options[index] : null; | ||
if (index > -1 && this.state.options[index]) { | ||
this.addTag(this.state.options[index]); | ||
if (tag) { | ||
this.addTag(tag); | ||
} else if (this.props.allowNew) { | ||
@@ -288,7 +310,15 @@ this.addTag({ name: this.state.query }); | ||
if (options.length === 0 && props.noSuggestionsText) { | ||
options.push({ id: 0, name: props.noSuggestionsText, disabled: true, disableMarkIt: true }); | ||
options = options.slice(0, props.maxSuggestionsLength); | ||
if (props.allowNew) { | ||
if (props.newTagText && findMatchIndex(options, state.query) === -1) { | ||
options.push({ id: 0, name: state.query, prefix: props.newTagText, disableMarkIt: true }); | ||
} | ||
} else { | ||
if (props.noSuggestionsText && options.length === 0) { | ||
options.push({ id: 0, name: props.noSuggestionsText, disabled: true, disableMarkIt: true }); | ||
} | ||
} | ||
return options.slice(0, props.maxSuggestionsLength) | ||
return options | ||
} | ||
@@ -317,5 +347,5 @@ | ||
this.container = React.createRef(); | ||
this.input = React.createRef(); | ||
this.suggestions = React.createRef(); | ||
this.container = React__default['default'].createRef(); | ||
this.input = React__default['default'].createRef(); | ||
this.suggestions = React__default['default'].createRef(); | ||
} | ||
@@ -402,13 +432,3 @@ | ||
if (this.container.current) { | ||
var interactiveEls = this.container.current.querySelectorAll('a,button,input'); | ||
var currentEl = Array.prototype.findIndex.call(interactiveEls, function (element) { | ||
return element === event.currentTarget | ||
}); | ||
var nextEl = interactiveEls[currentEl - 1] || interactiveEls[currentEl + 1]; | ||
if (nextEl) { | ||
nextEl.focus(); | ||
} | ||
focusNextElement(this.container.current, event.currentTarget); | ||
} | ||
@@ -428,3 +448,3 @@ | ||
this.props.onAddition(tag); | ||
this.props.onAddition({ id: tag.id, name: tag.name }); | ||
@@ -446,3 +466,3 @@ this.clearInput(); | ||
ReactTags.prototype.render = function render () { | ||
var this$1 = this; | ||
var this$1$1 = this; | ||
@@ -452,20 +472,21 @@ var TagComponent = this.props.tagComponent || Tag; | ||
var expanded = this.state.focused && this.state.query.length >= this.props.minQueryLength; | ||
var classNames = [this.props.classNames.root]; | ||
var classNames = Object.assign({}, CLASS_NAMES, this.props.classNames); | ||
var rootClassNames = [classNames.root]; | ||
this.state.focused && classNames.push(this.props.classNames.rootFocused); | ||
this.state.focused && rootClassNames.push(classNames.rootFocused); | ||
return ( | ||
React.createElement( 'div', { ref: this.container, className: classNames.join(' '), onClick: this.onClick.bind(this) }, | ||
React.createElement( 'div', { | ||
className: this.props.classNames.selected, 'aria-relevant': 'additions removals', 'aria-live': 'polite' }, | ||
React__default['default'].createElement( 'div', { ref: this.container, className: rootClassNames.join(' '), onClick: this.onClick.bind(this) }, | ||
React__default['default'].createElement( 'div', { | ||
className: classNames.selected, 'aria-relevant': 'additions removals', 'aria-live': 'polite' }, | ||
this.props.tags.map(function (tag, i) { return ( | ||
React.createElement( TagComponent, { | ||
key: i, tag: tag, removeButtonText: this$1.props.removeButtonText, classNames: this$1.props.classNames, onDelete: this$1.onDeleteTag.bind(this$1, i) }) | ||
React__default['default'].createElement( TagComponent, { | ||
key: i, tag: tag, removeButtonText: this$1$1.props.removeButtonText, classNames: classNames, onDelete: this$1$1.onDeleteTag.bind(this$1$1, i) }) | ||
); }) | ||
), | ||
React.createElement( 'div', { className: this.props.classNames.search }, | ||
React.createElement( Input, Object.assign({}, | ||
this.state, { id: this.props.id, ref: this.input, classNames: this.props.classNames, inputAttributes: this.props.inputAttributes, inputEventHandlers: this.inputEventHandlers, autoresize: this.props.autoresize, expanded: expanded, placeholderText: this.props.placeholderText, ariaLabelText: this.props.ariaLabelText })), | ||
React.createElement( Suggestions, Object.assign({}, | ||
this.state, { id: this.props.id, ref: this.suggestions, classNames: this.props.classNames, expanded: expanded, addTag: this.addTag.bind(this), suggestionComponent: this.props.suggestionComponent })) | ||
React__default['default'].createElement( 'div', { className: classNames.search }, | ||
React__default['default'].createElement( Input, Object.assign({}, | ||
this.state, { id: this.props.id, ref: this.input, classNames: classNames, inputAttributes: this.props.inputAttributes, inputEventHandlers: this.inputEventHandlers, autoresize: this.props.autoresize, expanded: expanded, placeholderText: this.props.placeholderText, ariaLabelText: this.props.ariaLabelText })), | ||
React__default['default'].createElement( Suggestions, Object.assign({}, | ||
this.state, { id: this.props.id, ref: this.suggestions, classNames: classNames, expanded: expanded, addTag: this.addTag.bind(this), suggestionComponent: this.props.suggestionComponent })) | ||
) | ||
@@ -489,3 +510,3 @@ ) | ||
return ReactTags; | ||
}(React.Component)); | ||
}(React__default['default'].Component)); | ||
@@ -498,2 +519,3 @@ ReactTags.defaultProps = { | ||
noSuggestionsText: null, | ||
newTagText: null, | ||
suggestions: [], | ||
@@ -516,34 +538,35 @@ suggestionsFilter: defaultSuggestionsFilter, | ||
ReactTags.propTypes = { | ||
id: PropTypes.string, | ||
tags: PropTypes.arrayOf(PropTypes.object), | ||
placeholderText: PropTypes.string, | ||
ariaLabelText: PropTypes.string, | ||
removeButtonText: PropTypes.string, | ||
noSuggestionsText: PropTypes.string, | ||
suggestions: PropTypes.arrayOf(PropTypes.object), | ||
suggestionsFilter: PropTypes.func, | ||
suggestionsTransform: PropTypes.func, | ||
autoresize: PropTypes.bool, | ||
delimiters: PropTypes.arrayOf(PropTypes.string), | ||
onDelete: PropTypes.func.isRequired, | ||
onAddition: PropTypes.func.isRequired, | ||
onInput: PropTypes.func, | ||
onFocus: PropTypes.func, | ||
onBlur: PropTypes.func, | ||
onValidate: PropTypes.func, | ||
minQueryLength: PropTypes.number, | ||
maxSuggestionsLength: PropTypes.number, | ||
classNames: PropTypes.object, | ||
allowNew: PropTypes.bool, | ||
allowBackspace: PropTypes.bool, | ||
addOnBlur: PropTypes.bool, | ||
tagComponent: PropTypes.oneOfType([ | ||
PropTypes.func, | ||
PropTypes.element | ||
id: PropTypes__default['default'].string, | ||
tags: PropTypes__default['default'].arrayOf(PropTypes__default['default'].object), | ||
placeholderText: PropTypes__default['default'].string, | ||
ariaLabelText: PropTypes__default['default'].string, | ||
removeButtonText: PropTypes__default['default'].string, | ||
noSuggestionsText: PropTypes__default['default'].string, | ||
newTagText: PropTypes__default['default'].string, | ||
suggestions: PropTypes__default['default'].arrayOf(PropTypes__default['default'].object), | ||
suggestionsFilter: PropTypes__default['default'].func, | ||
suggestionsTransform: PropTypes__default['default'].func, | ||
autoresize: PropTypes__default['default'].bool, | ||
delimiters: PropTypes__default['default'].arrayOf(PropTypes__default['default'].string), | ||
onDelete: PropTypes__default['default'].func.isRequired, | ||
onAddition: PropTypes__default['default'].func.isRequired, | ||
onInput: PropTypes__default['default'].func, | ||
onFocus: PropTypes__default['default'].func, | ||
onBlur: PropTypes__default['default'].func, | ||
onValidate: PropTypes__default['default'].func, | ||
minQueryLength: PropTypes__default['default'].number, | ||
maxSuggestionsLength: PropTypes__default['default'].number, | ||
classNames: PropTypes__default['default'].object, | ||
allowNew: PropTypes__default['default'].bool, | ||
allowBackspace: PropTypes__default['default'].bool, | ||
addOnBlur: PropTypes__default['default'].bool, | ||
tagComponent: PropTypes__default['default'].oneOfType([ | ||
PropTypes__default['default'].func, | ||
PropTypes__default['default'].element | ||
]), | ||
suggestionComponent: PropTypes.oneOfType([ | ||
PropTypes.func, | ||
PropTypes.element | ||
suggestionComponent: PropTypes__default['default'].oneOfType([ | ||
PropTypes__default['default'].func, | ||
PropTypes__default['default'].element | ||
]), | ||
inputAttributes: PropTypes.object | ||
inputAttributes: PropTypes__default['default'].object | ||
}; | ||
@@ -550,0 +573,0 @@ |
{ | ||
"name": "react-tag-autocomplete", | ||
"version": "6.1.0", | ||
"version": "6.2.0", | ||
"description": "React Tag Autocomplete is a simple tagging component ready to drop in your React projects.", | ||
@@ -15,4 +15,4 @@ "main": "dist/ReactTags.cjs.js", | ||
"coverage": "nyc --include 'dist/**' npm test", | ||
"dev": "NODE_ENV=development rollup -c example/rollup.config.js --watch", | ||
"example": "NODE_ENV=production rollup -c example/rollup.config.js", | ||
"dev": "rollup --environment=NODE_ENV:development -c example/rollup.config.js --watch", | ||
"example": "rollup --environment=NODE_ENV:production -c example/rollup.config.js", | ||
"build": "rollup -c rollup.config.js" | ||
@@ -40,3 +40,6 @@ }, | ||
"Herdis Maria", | ||
"Sibiraj S" | ||
"Sibiraj S", | ||
"Cristina Lozano", | ||
"Alexander Nestorov", | ||
"Lars Haßler" | ||
], | ||
@@ -47,23 +50,23 @@ "license": "MIT", | ||
"prop-types": "^15.5.0", | ||
"react": "^16.5.0", | ||
"react-dom": "^16.5.0" | ||
"react": "^16.5.0 || ^17.0.0", | ||
"react-dom": "^16.5.0 || ^17.0.0" | ||
}, | ||
"devDependencies": { | ||
"@rollup/plugin-buble": "^0.21.0", | ||
"@rollup/plugin-commonjs": "^11.0.2", | ||
"@rollup/plugin-node-resolve": "^7.1.0", | ||
"@rollup/plugin-replace": "^2.3.0", | ||
"coveralls": "^3.0.0", | ||
"jasmine": "^3.5.0", | ||
"jsdom": "^16.1.0", | ||
"@rollup/plugin-commonjs": "^19.0.0", | ||
"@rollup/plugin-node-resolve": "^13.0.0", | ||
"@rollup/plugin-replace": "^2.4.2", | ||
"coveralls": "^3.1.0", | ||
"jasmine": "^3.7.0", | ||
"jsdom": "^16.6.0", | ||
"match-sorter": "^4.2.0", | ||
"nyc": "^15.0.0", | ||
"prop-types": "^15.7.0", | ||
"react": "^16.10.0", | ||
"react-dom": "^16.10.0", | ||
"rollup": "^1.30.0", | ||
"rollup-plugin-serve": "^1.0.0", | ||
"rollup-plugin-terser": "^5.2.0", | ||
"sinon": "^8.1.0", | ||
"standard": "^14.3.0" | ||
"nyc": "^15.1.0", | ||
"prop-types": "^15.7.2", | ||
"react": "^16.13.0", | ||
"react-dom": "^16.13.0", | ||
"rollup": "^2.52.6", | ||
"rollup-plugin-serve": "^1.1.0", | ||
"rollup-plugin-terser": "^7.0.2", | ||
"sinon": "^11.1.0", | ||
"standard": "^16.0.2" | ||
}, | ||
@@ -70,0 +73,0 @@ "engines": { |
@@ -90,2 +90,3 @@ # React Tag Autocomplete | ||
- [`noSuggestionsText`](#noSuggestionsText-optional) | ||
- [`newTagText`](#newtagtext-optional) | ||
- [`autoresize`](#autoresize-optional) | ||
@@ -96,4 +97,4 @@ - [`delimiters`](#delimiters-optional) | ||
- [`classNames`](#classnames-optional) | ||
- [`onAddition`](#onaddition-optional) | ||
- [`onDelete`](#ondelete-optional) | ||
- [`onAddition`](#onaddition-required) | ||
- [`onDelete`](#ondelete-required) | ||
- [`onInput`](#oninput-optional) | ||
@@ -107,2 +108,3 @@ - [`onFocus`](#onfocus-optional) | ||
- [`tagComponent`](#tagcomponent-optional) | ||
- [`suggestionComponent`](#suggestioncomponent-optional) | ||
- [`inputAttributes`](#inputAttributes-optional) | ||
@@ -176,2 +178,6 @@ | ||
#### newTagText (optional) | ||
Enables users to show a prompt to add a new tag at the bottom of the suggestions list if `allowNew` is enabled. Defaults to `null`. | ||
#### autoresize (optional) | ||
@@ -209,3 +215,4 @@ | ||
suggestionActive: 'is-active', | ||
suggestionDisabled: 'is-disabled' | ||
suggestionDisabled: 'is-disabled', | ||
suggestionPrefix: 'react-tags__suggestion-prefix' | ||
} | ||
@@ -212,0 +219,0 @@ ``` |
70556
1301
363