@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
589 lines (588 loc) • 19.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _isFunction2 = _interopRequireDefault(require("lodash/isFunction"));
var _isUndefined2 = _interopRequireDefault(require("lodash/isUndefined"));
var _isNull2 = _interopRequireDefault(require("lodash/isNull"));
var _isArray2 = _interopRequireDefault(require("lodash/isArray"));
var _isString2 = _interopRequireDefault(require("lodash/isString"));
var _noop2 = _interopRequireDefault(require("lodash/noop"));
var _react = _interopRequireDefault(require("react"));
var _classnames = _interopRequireDefault(require("classnames"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _constants = require("@douyinfe/semi-foundation/lib/cjs/tagInput/constants");
require("@douyinfe/semi-foundation/lib/cjs/tagInput/tagInput.css");
var _foundation = _interopRequireDefault(require("@douyinfe/semi-foundation/lib/cjs/tagInput/foundation"));
var _utils = require("../_utils");
var _baseComponent = _interopRequireDefault(require("../_base/baseComponent"));
var _tag = _interopRequireDefault(require("../tag"));
var _input = _interopRequireDefault(require("../input"));
var _popover = _interopRequireDefault(require("../popover"));
var _paragraph = _interopRequireDefault(require("../typography/paragraph"));
var _semiIcons = require("@douyinfe/semi-icons");
var _sortable = require("../_sortable");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
var __rest = void 0 && (void 0).__rest || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
};
const prefixCls = _constants.cssClasses.PREFIX;
function SortContainer(props) {
return /*#__PURE__*/_react.default.createElement("div", Object.assign({
className: `${prefixCls}-sortable-list`
}, props));
}
class TagInput extends _baseComponent.default {
constructor(props) {
super(props);
this.handleInputChange = e => {
this.foundation.handleInputChange(e);
};
this.handleKeyDown = e => {
this.foundation.handleKeyDown(e);
};
this.handleInputFocus = e => {
this.foundation.handleInputFocus(e);
};
this.handleInputBlur = e => {
this.foundation.handleInputBlur(e);
};
this.handleClearBtn = e => {
this.foundation.handleClearBtn(e);
};
/* istanbul ignore next */
this.handleClearEnterPress = e => {
this.foundation.handleClearEnterPress(e);
};
this.handleTagClose = idx => {
this.foundation.handleTagClose(idx);
};
this.handleInputMouseLeave = e => {
this.foundation.handleInputMouseLeave();
};
this.handleClick = e => {
this.foundation.handleClick(e);
};
this.handleInputMouseEnter = e => {
this.foundation.handleInputMouseEnter();
};
this.handleClickPrefixOrSuffix = e => {
this.foundation.handleClickPrefixOrSuffix(e);
};
this.handlePreventMouseDown = e => {
this.foundation.handlePreventMouseDown(e);
};
this.getAllTags = () => {
const {
tagsArray
} = this.state;
return tagsArray.map((value, index) => this.renderTag(value, index));
};
this.renderTag = (value, index, sortableHandle) => {
const {
size,
disabled,
renderTagItem,
showContentTooltip,
draggable
} = this.props;
const {
active
} = this.state;
const showIconHandler = active && draggable;
const tagCls = (0, _classnames.default)(`${prefixCls}-wrapper-tag`, {
[`${prefixCls}-wrapper-tag-size-${size}`]: size,
[`${prefixCls}-wrapper-tag-icon`]: showIconHandler
});
const typoCls = (0, _classnames.default)(`${prefixCls}-wrapper-typo`, {
[`${prefixCls}-wrapper-typo-disabled`]: disabled
});
const itemWrapperCls = (0, _classnames.default)({
[`${prefixCls}-drag-item`]: showIconHandler,
[`${prefixCls}-wrapper-tag-icon`]: showIconHandler
});
const DragHandle = sortableHandle && sortableHandle(() => /*#__PURE__*/_react.default.createElement(_semiIcons.IconHandle, {
className: `${prefixCls}-drag-handler`
}));
const elementKey = showIconHandler ? value : `${index}${value}`;
const onClose = () => {
!disabled && this.handleTagClose(index);
};
if ((0, _isFunction2.default)(renderTagItem)) {
return /*#__PURE__*/_react.default.createElement("div", {
className: itemWrapperCls,
key: elementKey
}, showIconHandler && sortableHandle ? /*#__PURE__*/_react.default.createElement(DragHandle, null) : null, renderTagItem(value, index, onClose));
} else {
return /*#__PURE__*/_react.default.createElement(_tag.default, {
className: tagCls,
color: "white",
size: size === 'small' ? 'small' : 'large',
type: "light",
onClose: onClose,
closable: !disabled,
key: elementKey,
visible: true,
"aria-label": `${!disabled ? 'Closable ' : ''}Tag: ${value}`
}, showIconHandler && sortableHandle ? /*#__PURE__*/_react.default.createElement(DragHandle, null) : null, /*#__PURE__*/_react.default.createElement(_paragraph.default, {
className: typoCls,
ellipsis: {
showTooltip: showContentTooltip,
rows: 1
}
}, value));
}
};
this.renderSortTag = props => {
const {
id: item,
sortableHandle
} = props;
const {
tagsArray
} = this.state;
const index = tagsArray.indexOf(item);
return this.renderTag(item, index, sortableHandle);
};
this.onSortEnd = callbackProps => {
this.foundation.handleSortEnd(callbackProps);
};
this.handleInputCompositionStart = e => {
this.foundation.handleInputCompositionStart(e);
};
this.handleInputCompositionEnd = e => {
this.foundation.handleInputCompositionEnd(e);
};
this.foundation = new _foundation.default(this.adapter);
this.state = {
tagsArray: props.defaultValue || [],
inputValue: '',
focusing: false,
hovering: false,
active: false,
entering: false
};
this.inputRef = /*#__PURE__*/_react.default.createRef();
this.tagInputRef = /*#__PURE__*/_react.default.createRef();
this.clickOutsideHandler = null;
}
static getDerivedStateFromProps(nextProps, prevState) {
const {
value,
inputValue
} = nextProps;
const {
tagsArray: prevTagsArray
} = prevState;
let tagsArray;
if ((0, _isArray2.default)(value)) {
tagsArray = value;
} else if ('value' in nextProps && !value) {
tagsArray = [];
} else {
tagsArray = prevTagsArray;
}
return {
tagsArray,
inputValue: (0, _isString2.default)(inputValue) ? inputValue : prevState.inputValue
};
}
get adapter() {
return Object.assign(Object.assign({}, super.adapter), {
setInputValue: inputValue => {
this.setState({
inputValue
});
},
setTagsArray: tagsArray => {
this.setState({
tagsArray
});
},
setFocusing: focusing => {
this.setState({
focusing
});
},
toggleFocusing: isFocus => {
const {
preventScroll
} = this.props;
const input = this.inputRef && this.inputRef.current;
if (isFocus) {
input && input.focus({
preventScroll
});
} else {
input && input.blur();
}
this.setState({
focusing: isFocus
});
},
setHovering: hovering => {
this.setState({
hovering
});
},
setActive: active => {
this.setState({
active
});
},
setEntering: entering => {
this.setState({
entering
});
},
getClickOutsideHandler: () => {
return this.clickOutsideHandler;
},
notifyBlur: e => {
this.props.onBlur(e);
},
notifyFocus: e => {
this.props.onFocus(e);
},
notifyInputChange: (v, e) => {
this.props.onInputChange(v, e);
},
notifyTagChange: v => {
this.props.onChange(v);
},
notifyTagAdd: v => {
this.props.onAdd(v);
},
notifyTagRemove: (v, idx) => {
this.props.onRemove(v, idx);
},
notifyKeyDown: e => {
this.props.onKeyDown(e);
},
registerClickOutsideHandler: cb => {
const clickOutsideHandler = e => {
const tagInputDom = this.tagInputRef && this.tagInputRef.current;
const target = e.target;
const path = e.composedPath && e.composedPath() || [target];
if (tagInputDom && !tagInputDom.contains(target) && !path.includes(tagInputDom)) {
cb(e);
}
};
this.clickOutsideHandler = clickOutsideHandler;
document.addEventListener('click', clickOutsideHandler, false);
},
unregisterClickOutsideHandler: () => {
document.removeEventListener('click', this.clickOutsideHandler, false);
this.clickOutsideHandler = null;
}
});
}
componentDidMount() {
const {
disabled,
autoFocus,
preventScroll
} = this.props;
if (!disabled && autoFocus) {
this.inputRef.current.focus({
preventScroll
});
this.foundation.handleClick();
}
this.foundation.init();
}
renderClearBtn() {
const {
hovering,
tagsArray,
inputValue
} = this.state;
const {
showClear,
disabled,
clearIcon
} = this.props;
const clearCls = (0, _classnames.default)(`${prefixCls}-clearBtn`, {
[`${prefixCls}-clearBtn-invisible`]: !hovering || inputValue === '' && tagsArray.length === 0 || disabled
});
if (showClear) {
return /*#__PURE__*/_react.default.createElement("div", {
role: "button",
tabIndex: 0,
"aria-label": "Clear TagInput value",
className: clearCls,
onClick: e => this.handleClearBtn(e),
onKeyPress: e => this.handleClearEnterPress(e)
}, clearIcon ? clearIcon : /*#__PURE__*/_react.default.createElement(_semiIcons.IconClear, null));
}
return null;
}
renderPrefix() {
const {
prefix,
insetLabel,
insetLabelId
} = this.props;
const labelNode = prefix || insetLabel;
if ((0, _isNull2.default)(labelNode) || (0, _isUndefined2.default)(labelNode)) {
return null;
}
const prefixWrapperCls = (0, _classnames.default)(`${prefixCls}-prefix`, {
[`${prefixCls}-inset-label`]: insetLabel,
[`${prefixCls}-prefix-text`]: labelNode && (0, _isString2.default)(labelNode),
[`${prefixCls}-prefix-icon`]: (0, _utils.isSemiIcon)(labelNode)
});
return (
/*#__PURE__*/
// eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
_react.default.createElement("div", {
className: prefixWrapperCls,
onMouseDown: this.handlePreventMouseDown,
onClick: this.handleClickPrefixOrSuffix,
id: insetLabelId,
"x-semi-prop": "prefix"
}, labelNode)
);
}
renderSuffix() {
const {
suffix
} = this.props;
if ((0, _isNull2.default)(suffix) || (0, _isUndefined2.default)(suffix)) {
return null;
}
const suffixWrapperCls = (0, _classnames.default)(`${prefixCls}-suffix`, {
[`${prefixCls}-suffix-text`]: suffix && (0, _isString2.default)(suffix),
[`${prefixCls}-suffix-icon`]: (0, _utils.isSemiIcon)(suffix)
});
return (
/*#__PURE__*/
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
_react.default.createElement("div", {
className: suffixWrapperCls,
onMouseDown: this.handlePreventMouseDown,
onClick: this.handleClickPrefixOrSuffix,
"x-semi-prop": "suffix"
}, suffix)
);
}
renderTags() {
const {
disabled,
maxTagCount,
showRestTagsPopover,
restTagsPopoverProps = {},
draggable,
expandRestTagsOnClick
} = this.props;
const {
tagsArray,
active
} = this.state;
const restTagsCls = (0, _classnames.default)(`${prefixCls}-wrapper-n`, {
[`${prefixCls}-wrapper-n-disabled`]: disabled
});
const allTags = this.getAllTags();
let restTags = [];
let tags = [...allTags];
if ((!active || !expandRestTagsOnClick) && maxTagCount && maxTagCount < allTags.length) {
tags = allTags.slice(0, maxTagCount);
restTags = allTags.slice(maxTagCount);
}
const restTagsContent = /*#__PURE__*/_react.default.createElement("span", {
className: restTagsCls
}, "+", tagsArray.length - maxTagCount);
const sortableListItems = allTags.map((item, index) => ({
item: item,
key: tagsArray[index]
}));
if (active && draggable && sortableListItems.length > 0) {
return /*#__PURE__*/_react.default.createElement(_sortable.Sortable, {
items: tagsArray,
onSortEnd: this.onSortEnd,
renderItem: this.renderSortTag,
container: SortContainer,
prefix: prefixCls,
transition: null,
dragOverlayCls: `${prefixCls}-right-item-drag-item-move`
});
}
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, tags, restTags.length > 0 && (showRestTagsPopover ? (/*#__PURE__*/_react.default.createElement(_popover.default, Object.assign({
content: restTags,
showArrow: true,
trigger: "hover",
position: "top",
autoAdjustOverflow: true
}, restTagsPopoverProps), restTagsContent)) : restTagsContent));
}
blur() {
this.inputRef.current.blur();
// unregister clickOutside event
this.foundation.clickOutsideCallBack();
}
focus() {
const {
preventScroll,
disabled
} = this.props;
this.inputRef.current.focus({
preventScroll
});
if (!disabled) {
// register clickOutside event
this.foundation.handleClick();
}
}
render() {
const _a = this.props,
{
size,
style,
className,
disabled,
placeholder,
validateStatus,
prefix,
insetLabel,
suffix
} = _a,
rest = __rest(_a, ["size", "style", "className", "disabled", "placeholder", "validateStatus", "prefix", "insetLabel", "suffix"]);
const {
focusing,
hovering,
tagsArray,
inputValue,
active
} = this.state;
const tagInputCls = (0, _classnames.default)(prefixCls, className, {
[`${prefixCls}-focus`]: focusing || active,
[`${prefixCls}-disabled`]: disabled,
[`${prefixCls}-hover`]: hovering && !disabled,
[`${prefixCls}-error`]: validateStatus === 'error',
[`${prefixCls}-warning`]: validateStatus === 'warning',
[`${prefixCls}-small`]: size === 'small',
[`${prefixCls}-large`]: size === 'large',
[`${prefixCls}-with-prefix`]: !!prefix || !!insetLabel,
[`${prefixCls}-with-suffix`]: !!suffix
});
const inputCls = (0, _classnames.default)(`${prefixCls}-wrapper-input`, `${prefixCls}-wrapper-input-${size}`);
const wrapperCls = (0, _classnames.default)(`${prefixCls}-wrapper`);
return (
/*#__PURE__*/
// eslint-disable-next-line
_react.default.createElement("div", Object.assign({
ref: this.tagInputRef,
style: style,
className: tagInputCls,
"aria-disabled": disabled,
"aria-label": this.props['aria-label'],
"aria-invalid": validateStatus === 'error',
onMouseEnter: e => {
this.handleInputMouseEnter(e);
},
onMouseLeave: e => {
this.handleInputMouseLeave(e);
},
onClick: e => {
this.handleClick(e);
}
}, this.getDataAttr(rest)), this.renderPrefix(), /*#__PURE__*/_react.default.createElement("div", {
className: wrapperCls
}, this.renderTags(), /*#__PURE__*/_react.default.createElement(_input.default, {
"aria-label": 'input value',
ref: this.inputRef,
className: inputCls,
disabled: disabled,
value: inputValue,
size: size,
placeholder: tagsArray.length === 0 ? placeholder : '',
onKeyDown: e => {
this.handleKeyDown(e);
},
onChange: (v, e) => {
this.handleInputChange(e);
},
onBlur: e => {
this.handleInputBlur(e);
},
onFocus: e => {
this.handleInputFocus(e);
},
onCompositionStart: this.handleInputCompositionStart,
onCompositionEnd: this.handleInputCompositionEnd
})), this.renderClearBtn(), this.renderSuffix())
);
}
}
TagInput.propTypes = {
children: _propTypes.default.node,
clearIcon: _propTypes.default.node,
style: _propTypes.default.object,
className: _propTypes.default.string,
disabled: _propTypes.default.bool,
allowDuplicates: _propTypes.default.bool,
max: _propTypes.default.number,
maxTagCount: _propTypes.default.number,
maxLength: _propTypes.default.number,
showRestTagsPopover: _propTypes.default.bool,
restTagsPopoverProps: _propTypes.default.object,
showContentTooltip: _propTypes.default.oneOfType([_propTypes.default.shape({
type: _propTypes.default.string,
opts: _propTypes.default.object
}), _propTypes.default.bool]),
defaultValue: _propTypes.default.array,
value: _propTypes.default.array,
inputValue: _propTypes.default.string,
placeholder: _propTypes.default.string,
separator: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.array]),
showClear: _propTypes.default.bool,
addOnBlur: _propTypes.default.bool,
draggable: _propTypes.default.bool,
expandRestTagsOnClick: _propTypes.default.bool,
autoFocus: _propTypes.default.bool,
renderTagItem: _propTypes.default.func,
onBlur: _propTypes.default.func,
onFocus: _propTypes.default.func,
onChange: _propTypes.default.func,
onInputChange: _propTypes.default.func,
onExceed: _propTypes.default.func,
onInputExceed: _propTypes.default.func,
onAdd: _propTypes.default.func,
onRemove: _propTypes.default.func,
onKeyDown: _propTypes.default.func,
size: _propTypes.default.oneOf(_constants.strings.SIZE_SET),
validateStatus: _propTypes.default.oneOf(_constants.strings.STATUS),
prefix: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.node]),
suffix: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.node]),
'aria-label': _propTypes.default.string,
preventScroll: _propTypes.default.bool
};
TagInput.defaultProps = {
showClear: false,
addOnBlur: false,
allowDuplicates: true,
showRestTagsPopover: true,
autoFocus: false,
draggable: false,
expandRestTagsOnClick: true,
showContentTooltip: true,
separator: ',',
size: 'default',
validateStatus: 'default',
onBlur: _noop2.default,
onFocus: _noop2.default,
onChange: _noop2.default,
onInputChange: _noop2.default,
onExceed: _noop2.default,
onInputExceed: _noop2.default,
onAdd: _noop2.default,
onRemove: _noop2.default,
onKeyDown: _noop2.default
};
var _default = exports.default = TagInput;