react-token
Version:
Token/Tag input component
472 lines (416 loc) • 16 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _token = require('./token');
var _token2 = _interopRequireDefault(_token);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var ReactToken = function (_React$Component) {
_inherits(ReactToken, _React$Component);
function ReactToken(props) {
_classCallCheck(this, ReactToken);
var _this = _possibleConstructorReturn(this, (ReactToken.__proto__ || Object.getPrototypeOf(ReactToken)).call(this, props));
_this.keyMap = {
BACKSPACE: 8,
DOWN_ARROW: 40,
ENTER: 13,
ESC: 27,
UP_ARROW: 38
};
_this.onClick = function (e) {
if (!_this.container.contains(e.target)) {
var inputValue = _this.state.inputValue;
var tokens = _this.state.tokens;
var val = _this.input.value;
if (val) {
tokens = [].concat(_toConsumableArray(tokens), [val]);
_this.input.value = '';
inputValue = '';
}
if (_this.state.tokens.length !== tokens.length) {
_this.props.onAdd(val);
}
_this.setState({ focused: false, hideAutocomplete: true, tokens: tokens, inputValue: inputValue });
}
};
_this.focusInput = function (e) {
_this.input.focus();
};
_this.onFocus = function (e) {
_this.setState({ focused: true }, function () {
_this.props.onFocus(e);
});
};
_this.onBlur = function (e) {
var shouldRenderAutocomplete = _this.state.inputValue !== '' && _this.props.autocompleteOptions.length > 0 && !_this.state.hideAutocomplete;
if (shouldRenderAutocomplete) {
return;
}
var inputValue = _this.state.inputValue;
var tokens = _this.state.tokens;
var val = _this.input.value;
if (val) {
tokens = [].concat(_toConsumableArray(tokens), [val]);
_this.input.value = '';
inputValue = '';
}
_this.setState({ focused: false, tokens: tokens, inputValue: inputValue });
_this.props.onBlur(e);
if (_this.state.tokens.length !== tokens.length) {
_this.props.onAdd(val);
}
};
_this.onKeyDown = function (e) {
if ([_this.keyMap.DOWN_ARROW, _this.keyMap.UP_ARROW, _this.keyMap.ENTER].includes(e.keyCode)) {
e.preventDefault();
e.stopPropagation();
}
};
_this.onKeyUp = function (e) {
e.preventDefault();
if (e.keyCode === _this.keyMap.ENTER && _this.input.value.length > 0) {
var value = _this.state.selectedAutocompleteIdx >= 0 ? _this.props.autocompleteOptions[_this.state.selectedAutocompleteIdx] : _this.input.value;
_this.setState({
selectedAutocompleteIdx: -1,
tokens: [].concat(_toConsumableArray(_this.state.tokens), [value]),
inputValue: '',
hideAutocomplete: false
});
_this.props.onAdd(value);
_this.input.value = '';
_this.input.focus();
} else if (e.keyCode === _this.keyMap.BACKSPACE && _this.state.inputValue.length === 0) {
_this.removeToken(_this.state.tokens.length - 1)();
_this.setState({ inputValue: '' });
} else if (e.keyCode === _this.keyMap.ESC) {
_this.setState({ hideAutocomplete: true });
_this.input.focus();
} else if (e.keyCode === _this.keyMap.DOWN_ARROW && _this.state.inputValue !== '') {
var nextIdx = _this.state.selectedAutocompleteIdx + 1 >= _this.props.autocompleteOptions.length ? 0 : _this.state.selectedAutocompleteIdx + 1;
_this.setState({ selectedAutocompleteIdx: nextIdx });
_this.suggestions.childNodes[nextIdx].focus();
} else if (e.keyCode === _this.keyMap.UP_ARROW && _this.state.inputValue !== '') {
var _nextIdx = _this.state.selectedAutocompleteIdx - 1 < 0 ? _this.props.autocompleteOptions.length - 1 : _this.state.selectedAutocompleteIdx - 1;
_this.setState({ selectedAutocompleteIdx: _nextIdx });
_this.suggestions.childNodes[_nextIdx].focus();
} else {
_this.setState({
inputWidth: _this.calcWidth(_this.input.value),
inputValue: _this.input.value,
hideAutocomplete: false,
selectedAutocompleteIdx: -1
});
}
if (e.target === _this.input) {
_this.props.onKeyUp(e);
}
};
_this.highlightOption = function (idx) {
return function () {
_this.setState({ selectedAutocompleteIdx: idx });
};
};
_this.selectAutoCompleteOption = function (idx) {
return function () {
var option = _this.props.autocompleteOptions[idx];
_this.props.onAdd(option);
_this.input.value = '';
// If autocomplete option is a react element then don't automatically add to
// the selected token list. Consumer should be responsible for updating the
// selected prop with a text value.
var tokens = (typeof option === 'undefined' ? 'undefined' : _typeof(option)) === 'object' ? _this.state.tokens : [].concat(_toConsumableArray(_this.state.tokens), [_this.props.autocompleteOptions[idx]]);
_this.setState({
hideAutocomplete: false,
inputValue: '',
selectedAutocompleteIdx: -1,
tokens: tokens
});
};
};
_this.removeToken = function (index) {
return function () {
if (_this.props.disabled) {
return null;
}
var removedToken = _this.state.tokens[index];
var tokens = _this.state.tokens;
tokens.splice(index, 1);
_this.setState({ tokens: tokens });
_this.props.onRemove(removedToken);
};
};
_this.state = {
focused: false,
hideAutocomplete: false,
inputValue: '',
inputWidth: null,
selectedAutocompleteIdx: -1,
tokens: _this.props.selected
};
return _this;
}
_createClass(ReactToken, [{
key: 'componentDidMount',
value: function componentDidMount() {
window.addEventListener('click', this.onClick);
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps) {
if (this.props.selected !== nextProps.selected) {
this.setState({ tokens: nextProps.selected });
}
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
window.removeEventListener('click', this.onClick);
}
}, {
key: 'calcWidth',
value: function calcWidth(val) {
var tmp = document.createElement('div');
tmp.style.padding = '0';
tmp.style.width = '';
tmp.style.position = 'absolute';
tmp.style.left = '-9999';
tmp.style.fontFamily = '"Sofia Pro", "Helvetica", "Arial", sans-serif';
tmp.style.fontSize = '11px';
tmp.innerText = val;
this.input.parentNode.appendChild(tmp);
var width = tmp.clientWidth;
this.input.parentNode.removeChild(tmp);
return width + 40;
}
}, {
key: 'renderRequired',
value: function renderRequired() {
var styles = {
container: {
color: '#ACACAC',
fontSize: 12,
fontWeight: 200,
overflow: 'hidden',
position: 'absolute',
right: 10,
top: 14
},
span: {
position: 'relative',
transition: 'all 200ms',
top: 0
}
};
var spanStyle = this.state.focused || this.state.tokens.length > 0 ? _extends({}, styles.span, { top: -20 }) : styles.span;
return _react2.default.createElement(
'div',
{ style: styles.container },
_react2.default.createElement(
'span',
{ style: spanStyle },
'required'
)
);
}
}, {
key: 'renderTokens',
value: function renderTokens() {
var _this2 = this;
return this.state.tokens.map(function (token, idx) {
return _react2.default.createElement(_token2.default, { text: token, key: idx, onRemove: _this2.removeToken(idx) });
});
}
}, {
key: 'renderAutoComplete',
value: function renderAutoComplete() {
var _this3 = this;
var styles = {
container: {
position: 'absolute',
marginTop: '5px',
left: 0,
paddingTop: 10,
paddingBottom: 10,
fontSize: 14,
fontWeight: 600,
letterSpacing: 0,
backgroundColor: '#F1F1F1',
color: '#000000',
width: '100%',
borderRadius: '3px',
cursor: 'pointer',
maxHeight: 160,
overflowY: 'auto',
border: '1px solid #E4E4E4',
zIndex: 1
},
option: {
paddingLeft: 10,
paddingRight: 10,
paddingTop: 5,
paddingBottom: 5,
border: 'none',
outline: 'none'
},
highlighted: {
backgroundColor: '#E4E2E2'
}
};
return _react2.default.createElement(
'div',
{
style: styles.container,
ref: function ref(r) {
return _this3.suggestions = r;
},
className: 'rt-suggestions',
role: 'listbox',
onKeyDown: this.onKeyDown,
onKeyUp: this.onKeyUp
},
this.props.autocompleteOptions.map(function (option, idx) {
var style = _this3.state.selectedAutocompleteIdx === idx ? _extends({}, styles.option, styles.highlighted) : styles.option;
return _react2.default.createElement(
'div',
{
className: 'rt-option',
key: 'autocomplete-' + idx,
onClick: _this3.selectAutoCompleteOption(idx),
onMouseOver: _this3.highlightOption(idx),
role: 'option',
style: style,
tabIndex: -1
},
option
);
})
);
}
}, {
key: 'render',
value: function render() {
var _this4 = this;
var width = this.state.inputValue !== '' ? this.state.inputWidth : '100%';
var styles = {
container: {
position: 'relative',
backgroundColor: '#F1F1F1',
border: '1px solid #F1F1F1',
borderRadius: '3px',
width: '100%',
fontFamily: '"Sofia Pro", "Helvetica", "Arial", sans-serif'
},
ul: {
cursor: 'text',
display: 'flex',
flexWrap: 'wrap',
fontSize: 14,
fontWeight: 600,
letterSpacing: 0,
listStyle: 'none',
lineHeight: 'normal',
margin: 0,
padding: '4px 6px',
position: 'relative',
willChange: 'transform'
},
li: {
display: 'inline-flex'
},
inputContainer: {
display: 'inline-flex',
position: 'relative'
},
input: {
backgroundColor: '#F1F1F1',
fontFamily: '"Sofia Pro", "Helvetica", "Arial", sans-serif',
paddingTop: 1,
paddingLeft: 1,
paddingBottom: 1,
paddingRight: 20,
width: width,
border: 'none',
outline: 'none',
marginTop: 9,
marginBottom: 9,
boxSizing: 'border-box'
}
};
var shouldRenderAutocomplete = this.state.inputValue !== '' && this.props.autocompleteOptions.length > 0 && !this.state.hideAutocomplete;
return _react2.default.createElement(
'div',
{ style: styles.container, className: 'rt-container', ref: function ref(r) {
return _this4.container = r;
} },
_react2.default.createElement(
'ul',
{ style: styles.ul, onClick: this.focusInput },
this.renderTokens(),
_react2.default.createElement(
'li',
{ style: styles.li },
_react2.default.createElement(
'div',
{ style: styles.inputContainer },
_react2.default.createElement('input', {
disabled: this.props.disabled,
ref: function ref(r) {
return _this4.input = r;
},
type: 'text',
role: 'combobox',
className: 'rt-input',
style: styles.input,
onKeyDown: this.onKeyDown,
onKeyUp: this.onKeyUp,
placeholder: this.props.placeholder,
onFocus: this.onFocus,
onBlur: this.onBlur
})
)
),
this.props.required && this.renderRequired()
),
shouldRenderAutocomplete && this.renderAutoComplete()
);
}
}]);
return ReactToken;
}(_react2.default.Component);
ReactToken.propTypes = {
autocompleteOptions: _propTypes2.default.array,
disabled: _propTypes2.default.bool,
onAdd: _propTypes2.default.func,
onBlur: _propTypes2.default.func,
onFocus: _propTypes2.default.func,
onKeyUp: _propTypes2.default.func,
onRemove: _propTypes2.default.func,
placeholder: _propTypes2.default.string,
required: _propTypes2.default.bool,
selected: _propTypes2.default.array
};
ReactToken.defaultProps = {
autocompleteOptions: [],
disabled: false,
onAdd: function onAdd() {},
onBlur: function onBlur() {},
onFocus: function onFocus() {},
onKeyUp: function onKeyUp() {},
onRemove: function onRemove() {},
placeholder: '',
required: false,
selected: []
};
exports.default = ReactToken;
module.exports = exports['default'];