index.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. import React from 'react';
  2. import cls from 'classnames';
  3. import PropTypes from 'prop-types';
  4. import {
  5. noop,
  6. isString,
  7. isArray,
  8. isNull,
  9. isUndefined,
  10. isFunction
  11. } from 'lodash';
  12. import { cssClasses, strings } from '@douyinfe/semi-foundation/tagInput/constants';
  13. import '@douyinfe/semi-foundation/tagInput/tagInput.scss';
  14. import TagInputFoundation, { TagInputAdapter, OnSortEndProps } from '@douyinfe/semi-foundation/tagInput/foundation';
  15. import { ArrayElement } from '../_base/base';
  16. import { isSemiIcon } from '../_utils';
  17. import BaseComponent from '../_base/baseComponent';
  18. import Tag from '../tag';
  19. import Input from '../input';
  20. import Popover, { PopoverProps } from '../popover';
  21. import Paragraph from '../typography/paragraph';
  22. import { IconClear, IconHandle } from '@douyinfe/semi-icons';
  23. import { ShowTooltip } from '../typography';
  24. import { RenderItemProps, Sortable } from '../_sortable';
  25. const prefixCls = cssClasses.PREFIX;
  26. export type Size = ArrayElement<typeof strings.SIZE_SET>;
  27. export type RestTagsPopoverProps = PopoverProps;
  28. type ValidateStatus = "default" | "error" | "warning";
  29. function SortContainer(props) {
  30. return <div className={`${prefixCls}-sortable-list`} {...props}></div>;
  31. }
  32. export interface TagInputProps {
  33. className?: string;
  34. clearIcon?: React.ReactNode;
  35. defaultValue?: string[];
  36. disabled?: boolean;
  37. inputValue?: string;
  38. maxLength?: number;
  39. max?: number;
  40. maxTagCount?: number;
  41. showRestTagsPopover?: boolean;
  42. restTagsPopoverProps?: RestTagsPopoverProps;
  43. showContentTooltip?: boolean | ShowTooltip;
  44. allowDuplicates?: boolean;
  45. addOnBlur?: boolean;
  46. draggable?: boolean;
  47. expandRestTagsOnClick?: boolean;
  48. onAdd?: (addedValue: string[]) => void;
  49. onBlur?: (e: React.MouseEvent<HTMLInputElement>) => void;
  50. onChange?: (value: string[]) => void;
  51. onExceed?: ((value: string[]) => void);
  52. onFocus?: (e: React.MouseEvent<HTMLInputElement>) => void;
  53. onInputChange?: (value: string, e: React.MouseEvent<HTMLInputElement>) => void;
  54. onInputExceed?: ((value: string) => void);
  55. onKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
  56. onRemove?: (removedValue: string, idx: number) => void;
  57. placeholder?: string;
  58. insetLabel?: React.ReactNode;
  59. insetLabelId?: string;
  60. prefix?: React.ReactNode;
  61. renderTagItem?: (value: string, index: number, onClose: () => void) => React.ReactNode;
  62. separator?: string | string[] | null;
  63. showClear?: boolean;
  64. size?: Size;
  65. style?: React.CSSProperties;
  66. suffix?: React.ReactNode;
  67. validateStatus?: ValidateStatus;
  68. value?: string[];
  69. autoFocus?: boolean;
  70. 'aria-label'?: string;
  71. preventScroll?: boolean
  72. }
  73. export interface TagInputState {
  74. tagsArray?: string[];
  75. inputValue?: string;
  76. focusing?: boolean;
  77. hovering?: boolean;
  78. active?: boolean;
  79. // entering: Used to identify whether the user is in a new composition session(eg,Input Chinese)
  80. entering?: boolean
  81. }
  82. class TagInput extends BaseComponent<TagInputProps, TagInputState> {
  83. static propTypes = {
  84. children: PropTypes.node,
  85. clearIcon: PropTypes.node,
  86. style: PropTypes.object,
  87. className: PropTypes.string,
  88. disabled: PropTypes.bool,
  89. allowDuplicates: PropTypes.bool,
  90. max: PropTypes.number,
  91. maxTagCount: PropTypes.number,
  92. maxLength: PropTypes.number,
  93. showRestTagsPopover: PropTypes.bool,
  94. restTagsPopoverProps: PropTypes.object,
  95. showContentTooltip: PropTypes.oneOfType([
  96. PropTypes.shape({
  97. type: PropTypes.string,
  98. opts: PropTypes.object,
  99. }),
  100. PropTypes.bool,
  101. ]),
  102. defaultValue: PropTypes.array,
  103. value: PropTypes.array,
  104. inputValue: PropTypes.string,
  105. placeholder: PropTypes.string,
  106. separator: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
  107. showClear: PropTypes.bool,
  108. addOnBlur: PropTypes.bool,
  109. draggable: PropTypes.bool,
  110. expandRestTagsOnClick: PropTypes.bool,
  111. autoFocus: PropTypes.bool,
  112. renderTagItem: PropTypes.func,
  113. onBlur: PropTypes.func,
  114. onFocus: PropTypes.func,
  115. onChange: PropTypes.func,
  116. onInputChange: PropTypes.func,
  117. onExceed: PropTypes.func,
  118. onInputExceed: PropTypes.func,
  119. onAdd: PropTypes.func,
  120. onRemove: PropTypes.func,
  121. onKeyDown: PropTypes.func,
  122. size: PropTypes.oneOf(strings.SIZE_SET),
  123. validateStatus: PropTypes.oneOf(strings.STATUS),
  124. prefix: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
  125. suffix: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
  126. 'aria-label': PropTypes.string,
  127. preventScroll: PropTypes.bool,
  128. };
  129. static defaultProps = {
  130. showClear: false,
  131. addOnBlur: false,
  132. allowDuplicates: true,
  133. showRestTagsPopover: true,
  134. autoFocus: false,
  135. draggable: false,
  136. expandRestTagsOnClick: true,
  137. showContentTooltip: true,
  138. separator: ',',
  139. size: 'default' as const,
  140. validateStatus: 'default' as const,
  141. onBlur: noop,
  142. onFocus: noop,
  143. onChange: noop,
  144. onInputChange: noop,
  145. onExceed: noop,
  146. onInputExceed: noop,
  147. onAdd: noop,
  148. onRemove: noop,
  149. onKeyDown: noop,
  150. };
  151. inputRef: React.RefObject<HTMLInputElement>;
  152. tagInputRef: React.RefObject<HTMLDivElement>;
  153. foundation: TagInputFoundation;
  154. clickOutsideHandler: any;
  155. constructor(props: TagInputProps) {
  156. super(props);
  157. this.foundation = new TagInputFoundation(this.adapter);
  158. this.state = {
  159. tagsArray: props.defaultValue || [],
  160. inputValue: '',
  161. focusing: false,
  162. hovering: false,
  163. active: false,
  164. entering: false,
  165. };
  166. this.inputRef = React.createRef();
  167. this.tagInputRef = React.createRef();
  168. this.clickOutsideHandler = null;
  169. }
  170. static getDerivedStateFromProps(nextProps: TagInputProps, prevState: TagInputState) {
  171. const { value, inputValue } = nextProps;
  172. const { tagsArray: prevTagsArray } = prevState;
  173. let tagsArray: string[];
  174. if (isArray(value)) {
  175. tagsArray = value;
  176. } else if ('value' in nextProps && !value) {
  177. tagsArray = [];
  178. } else {
  179. tagsArray = prevTagsArray;
  180. }
  181. return {
  182. tagsArray,
  183. inputValue: isString(inputValue) ? inputValue : prevState.inputValue
  184. };
  185. }
  186. get adapter(): TagInputAdapter {
  187. return {
  188. ...super.adapter,
  189. setInputValue: (inputValue: string) => {
  190. this.setState({ inputValue });
  191. },
  192. setTagsArray: (tagsArray: string[]) => {
  193. this.setState({ tagsArray });
  194. },
  195. setFocusing: (focusing: boolean) => {
  196. this.setState({ focusing });
  197. },
  198. toggleFocusing: (isFocus: boolean) => {
  199. const { preventScroll } = this.props;
  200. const input = this.inputRef && this.inputRef.current;
  201. if (isFocus) {
  202. input && input.focus({ preventScroll });
  203. } else {
  204. input && input.blur();
  205. }
  206. this.setState({ focusing: isFocus });
  207. },
  208. setHovering: (hovering: boolean) => {
  209. this.setState({ hovering });
  210. },
  211. setActive: (active: boolean) => {
  212. this.setState({ active });
  213. },
  214. setEntering: (entering: boolean) => {
  215. this.setState({ entering });
  216. },
  217. getClickOutsideHandler: () => {
  218. return this.clickOutsideHandler;
  219. },
  220. notifyBlur: (e: React.MouseEvent<HTMLInputElement>) => {
  221. this.props.onBlur(e);
  222. },
  223. notifyFocus: (e: React.MouseEvent<HTMLInputElement>) => {
  224. this.props.onFocus(e);
  225. },
  226. notifyInputChange: (v: string, e: React.MouseEvent<HTMLInputElement>) => {
  227. this.props.onInputChange(v, e);
  228. },
  229. notifyTagChange: (v: string[]) => {
  230. this.props.onChange(v);
  231. },
  232. notifyTagAdd: (v: string[]) => {
  233. this.props.onAdd(v);
  234. },
  235. notifyTagRemove: (v: string, idx: number) => {
  236. this.props.onRemove(v, idx);
  237. },
  238. notifyKeyDown: e => {
  239. this.props.onKeyDown(e);
  240. },
  241. registerClickOutsideHandler: cb => {
  242. const clickOutsideHandler = (e: Event) => {
  243. const tagInputDom = this.tagInputRef && this.tagInputRef.current;
  244. const target = e.target as Element;
  245. if (tagInputDom && !tagInputDom.contains(target)) {
  246. cb(e);
  247. }
  248. };
  249. this.clickOutsideHandler = clickOutsideHandler;
  250. document.addEventListener('click', clickOutsideHandler, false);
  251. },
  252. unregisterClickOutsideHandler: () => {
  253. document.removeEventListener('click', this.clickOutsideHandler, false);
  254. this.clickOutsideHandler = null;
  255. },
  256. };
  257. }
  258. componentDidMount() {
  259. const { disabled, autoFocus, preventScroll } = this.props;
  260. if (!disabled && autoFocus) {
  261. this.inputRef.current.focus({ preventScroll });
  262. this.foundation.handleClick();
  263. }
  264. this.foundation.init();
  265. }
  266. handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  267. this.foundation.handleInputChange(e);
  268. };
  269. handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
  270. this.foundation.handleKeyDown(e);
  271. };
  272. handleInputFocus = (e: React.MouseEvent<HTMLInputElement>) => {
  273. this.foundation.handleInputFocus(e);
  274. };
  275. handleInputBlur = (e: React.MouseEvent<HTMLInputElement>) => {
  276. this.foundation.handleInputBlur(e);
  277. };
  278. handleClearBtn = (e: React.MouseEvent<HTMLDivElement>) => {
  279. this.foundation.handleClearBtn(e);
  280. };
  281. /* istanbul ignore next */
  282. handleClearEnterPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
  283. this.foundation.handleClearEnterPress(e);
  284. };
  285. handleTagClose = (idx: number) => {
  286. this.foundation.handleTagClose(idx);
  287. };
  288. handleInputMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => {
  289. this.foundation.handleInputMouseLeave();
  290. };
  291. handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
  292. this.foundation.handleClick(e);
  293. };
  294. handleInputMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
  295. this.foundation.handleInputMouseEnter();
  296. };
  297. handleClickPrefixOrSuffix = (e: React.MouseEvent<HTMLInputElement>) => {
  298. this.foundation.handleClickPrefixOrSuffix(e);
  299. };
  300. handlePreventMouseDown = (e: React.MouseEvent<HTMLInputElement>) => {
  301. this.foundation.handlePreventMouseDown(e);
  302. };
  303. renderClearBtn() {
  304. const { hovering, tagsArray, inputValue } = this.state;
  305. const { showClear, disabled, clearIcon } = this.props;
  306. const clearCls = cls(`${prefixCls}-clearBtn`, {
  307. [`${prefixCls}-clearBtn-invisible`]: !hovering || (inputValue === '' && tagsArray.length === 0) || disabled,
  308. });
  309. if (showClear) {
  310. return (
  311. <div
  312. role="button"
  313. tabIndex={0}
  314. aria-label="Clear TagInput value"
  315. className={clearCls}
  316. onClick={e => this.handleClearBtn(e)}
  317. onKeyPress={e => this.handleClearEnterPress(e)}
  318. >
  319. { clearIcon ? clearIcon : <IconClear />}
  320. </div>
  321. );
  322. }
  323. return null;
  324. }
  325. renderPrefix() {
  326. const { prefix, insetLabel, insetLabelId } = this.props;
  327. const labelNode = prefix || insetLabel;
  328. if (isNull(labelNode) || isUndefined(labelNode)) {
  329. return null;
  330. }
  331. const prefixWrapperCls = cls(`${prefixCls}-prefix`, {
  332. [`${prefixCls}-inset-label`]: insetLabel,
  333. [`${prefixCls}-prefix-text`]: labelNode && isString(labelNode),
  334. [`${prefixCls}-prefix-icon`]: isSemiIcon(labelNode),
  335. });
  336. return (
  337. // eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
  338. <div
  339. className={prefixWrapperCls}
  340. onMouseDown={this.handlePreventMouseDown}
  341. onClick={this.handleClickPrefixOrSuffix}
  342. id={insetLabelId} x-semi-prop="prefix"
  343. >
  344. {labelNode}
  345. </div>
  346. );
  347. }
  348. renderSuffix() {
  349. const { suffix } = this.props;
  350. if (isNull(suffix) || isUndefined(suffix)) {
  351. return null;
  352. }
  353. const suffixWrapperCls = cls(`${prefixCls}-suffix`, {
  354. [`${prefixCls}-suffix-text`]: suffix && isString(suffix),
  355. [`${prefixCls}-suffix-icon`]: isSemiIcon(suffix),
  356. });
  357. return (
  358. // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
  359. <div
  360. className={suffixWrapperCls}
  361. onMouseDown={this.handlePreventMouseDown}
  362. onClick={this.handleClickPrefixOrSuffix}
  363. x-semi-prop="suffix"
  364. >
  365. {suffix}
  366. </div>
  367. );
  368. }
  369. getAllTags = () => {
  370. const { tagsArray } = this.state;
  371. return tagsArray.map((value, index) => this.renderTag(value, index));
  372. }
  373. renderTag = (value: any, index: number, sortableHandle?: any) => {
  374. const {
  375. size,
  376. disabled,
  377. renderTagItem,
  378. showContentTooltip,
  379. draggable,
  380. } = this.props;
  381. const { active } = this.state;
  382. const showIconHandler = active && draggable;
  383. const tagCls = cls(`${prefixCls}-wrapper-tag`, {
  384. [`${prefixCls}-wrapper-tag-size-${size}`]: size,
  385. [`${prefixCls}-wrapper-tag-icon`]: showIconHandler,
  386. });
  387. const typoCls = cls(`${prefixCls}-wrapper-typo`, {
  388. [`${prefixCls}-wrapper-typo-disabled`]: disabled,
  389. });
  390. const itemWrapperCls = cls({
  391. [`${prefixCls}-drag-item`]: showIconHandler,
  392. [`${prefixCls}-wrapper-tag-icon`]: showIconHandler,
  393. });
  394. const DragHandle = sortableHandle && sortableHandle(() => <IconHandle className={`${prefixCls}-drag-handler`}></IconHandle>);
  395. const elementKey = showIconHandler ? value : `${index}${value}`;
  396. const onClose = () => {
  397. !disabled && this.handleTagClose(index);
  398. };
  399. if (isFunction(renderTagItem)) {
  400. return (<div className={itemWrapperCls} key={elementKey}>
  401. {showIconHandler && sortableHandle ? <DragHandle /> : null}
  402. {renderTagItem(value, index, onClose)}
  403. </div>);
  404. } else {
  405. return (
  406. <Tag
  407. className={tagCls}
  408. color="white"
  409. size={size === 'small' ? 'small' : 'large'}
  410. type="light"
  411. onClose={onClose}
  412. closable={!disabled}
  413. key={elementKey}
  414. visible
  415. aria-label={`${!disabled ? 'Closable ' : ''}Tag: ${value}`}
  416. >
  417. {showIconHandler && sortableHandle ? <DragHandle /> : null}
  418. <Paragraph
  419. className={typoCls}
  420. ellipsis={{ showTooltip: showContentTooltip, rows: 1 }}
  421. >
  422. {value}
  423. </Paragraph>
  424. </Tag>
  425. );
  426. }
  427. }
  428. renderSortTag = (props: RenderItemProps) => {
  429. const { id: item, sortableHandle } = props;
  430. const { tagsArray } = this.state;
  431. const index = tagsArray.indexOf(item as string);
  432. return this.renderTag(item, index, sortableHandle);
  433. }
  434. onSortEnd = (callbackProps: OnSortEndProps) => {
  435. this.foundation.handleSortEnd(callbackProps);
  436. }
  437. renderTags() {
  438. const {
  439. disabled,
  440. maxTagCount,
  441. showRestTagsPopover,
  442. restTagsPopoverProps = {},
  443. draggable,
  444. expandRestTagsOnClick,
  445. } = this.props;
  446. const { tagsArray, active } = this.state;
  447. const restTagsCls = cls(`${prefixCls}-wrapper-n`, {
  448. [`${prefixCls}-wrapper-n-disabled`]: disabled,
  449. });
  450. const allTags = this.getAllTags();
  451. let restTags: Array<React.ReactNode> = [];
  452. let tags: Array<React.ReactNode> = [...allTags];
  453. if (( !active || !expandRestTagsOnClick) && maxTagCount && maxTagCount < allTags.length) {
  454. tags = allTags.slice(0, maxTagCount);
  455. restTags = allTags.slice(maxTagCount);
  456. }
  457. const restTagsContent = (
  458. <span className={restTagsCls}>+{tagsArray.length - maxTagCount}</span>
  459. );
  460. const sortableListItems = allTags.map((item, index) => ({
  461. item: item,
  462. key: tagsArray[index],
  463. }));
  464. if (active && draggable && sortableListItems.length > 0) {
  465. return <Sortable
  466. items={tagsArray}
  467. onSortEnd={this.onSortEnd}
  468. renderItem={this.renderSortTag}
  469. container={SortContainer}
  470. prefix={prefixCls}
  471. transition={null}
  472. dragOverlayCls={`${prefixCls}-right-item-drag-item-move`}
  473. />;
  474. }
  475. return (
  476. <>
  477. {tags}
  478. {
  479. restTags.length > 0 &&
  480. (
  481. showRestTagsPopover ?
  482. (
  483. <Popover
  484. content={restTags}
  485. showArrow
  486. trigger="hover"
  487. position="top"
  488. autoAdjustOverflow
  489. {...restTagsPopoverProps}
  490. >
  491. {restTagsContent}
  492. </Popover>
  493. ) : restTagsContent
  494. )
  495. }
  496. </>
  497. );
  498. }
  499. blur() {
  500. this.inputRef.current.blur();
  501. // unregister clickOutside event
  502. this.foundation.clickOutsideCallBack();
  503. }
  504. focus() {
  505. const { preventScroll, disabled } = this.props;
  506. this.inputRef.current.focus({ preventScroll });
  507. if (!disabled) {
  508. // register clickOutside event
  509. this.foundation.handleClick();
  510. }
  511. }
  512. handleInputCompositionStart = (e) => {
  513. this.foundation.handleInputCompositionStart(e);
  514. }
  515. handleInputCompositionEnd = (e) => {
  516. this.foundation.handleInputCompositionEnd(e);
  517. }
  518. render() {
  519. const {
  520. size,
  521. style,
  522. className,
  523. disabled,
  524. placeholder,
  525. validateStatus,
  526. ...rest
  527. } = this.props;
  528. const {
  529. focusing,
  530. hovering,
  531. tagsArray,
  532. inputValue,
  533. active,
  534. } = this.state;
  535. const tagInputCls = cls(prefixCls, className, {
  536. [`${prefixCls}-focus`]: focusing || active,
  537. [`${prefixCls}-disabled`]: disabled,
  538. [`${prefixCls}-hover`]: hovering && !disabled,
  539. [`${prefixCls}-error`]: validateStatus === 'error',
  540. [`${prefixCls}-warning`]: validateStatus === 'warning',
  541. [`${prefixCls}-small`]: size === 'small',
  542. [`${prefixCls}-large`]: size === 'large',
  543. });
  544. const inputCls = cls(`${prefixCls}-wrapper-input`, `${prefixCls}-wrapper-input-${size}`);
  545. const wrapperCls = cls(`${prefixCls}-wrapper`);
  546. return (
  547. // eslint-disable-next-line
  548. <div
  549. ref={this.tagInputRef}
  550. style={style}
  551. className={tagInputCls}
  552. aria-disabled={disabled}
  553. aria-label={this.props['aria-label']}
  554. aria-invalid={validateStatus === 'error'}
  555. onMouseEnter={e => {
  556. this.handleInputMouseEnter(e);
  557. }}
  558. onMouseLeave={e => {
  559. this.handleInputMouseLeave(e);
  560. }}
  561. onClick={e => {
  562. this.handleClick(e);
  563. }}
  564. {...this.getDataAttr(rest)}
  565. >
  566. {this.renderPrefix()}
  567. <div className={wrapperCls}>
  568. {this.renderTags()}
  569. <Input
  570. aria-label='input value'
  571. ref={this.inputRef as any}
  572. className={inputCls}
  573. disabled={disabled}
  574. value={inputValue}
  575. size={size}
  576. placeholder={tagsArray.length === 0 ? placeholder : ''}
  577. onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
  578. this.handleKeyDown(e);
  579. }}
  580. onChange={(v: string, e: React.ChangeEvent<HTMLInputElement>) => {
  581. this.handleInputChange(e);
  582. }}
  583. onBlur={(e: React.FocusEvent<HTMLInputElement>) => {
  584. this.handleInputBlur(e as any);
  585. }}
  586. onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
  587. this.handleInputFocus(e as any);
  588. }}
  589. onCompositionStart={this.handleInputCompositionStart}
  590. onCompositionEnd={this.handleInputCompositionEnd}
  591. />
  592. </div>
  593. {this.renderClearBtn()}
  594. {this.renderSuffix()}
  595. </div>
  596. );
  597. }
  598. }
  599. export default TagInput;
  600. export { ValidateStatus };