index.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  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. const path = e.composedPath && e.composedPath() || [target];
  246. if (tagInputDom && !tagInputDom.contains(target) && !path.includes(tagInputDom)) {
  247. cb(e);
  248. }
  249. };
  250. this.clickOutsideHandler = clickOutsideHandler;
  251. document.addEventListener('click', clickOutsideHandler, false);
  252. },
  253. unregisterClickOutsideHandler: () => {
  254. document.removeEventListener('click', this.clickOutsideHandler, false);
  255. this.clickOutsideHandler = null;
  256. },
  257. };
  258. }
  259. componentDidMount() {
  260. const { disabled, autoFocus, preventScroll } = this.props;
  261. if (!disabled && autoFocus) {
  262. this.inputRef.current.focus({ preventScroll });
  263. this.foundation.handleClick();
  264. }
  265. this.foundation.init();
  266. }
  267. handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  268. this.foundation.handleInputChange(e);
  269. };
  270. handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
  271. this.foundation.handleKeyDown(e);
  272. };
  273. handleInputFocus = (e: React.MouseEvent<HTMLInputElement>) => {
  274. this.foundation.handleInputFocus(e);
  275. };
  276. handleInputBlur = (e: React.MouseEvent<HTMLInputElement>) => {
  277. this.foundation.handleInputBlur(e);
  278. };
  279. handleClearBtn = (e: React.MouseEvent<HTMLDivElement>) => {
  280. this.foundation.handleClearBtn(e);
  281. };
  282. /* istanbul ignore next */
  283. handleClearEnterPress = (e: React.KeyboardEvent<HTMLDivElement>) => {
  284. this.foundation.handleClearEnterPress(e);
  285. };
  286. handleTagClose = (idx: number) => {
  287. this.foundation.handleTagClose(idx);
  288. };
  289. handleInputMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => {
  290. this.foundation.handleInputMouseLeave();
  291. };
  292. handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
  293. this.foundation.handleClick(e);
  294. };
  295. handleInputMouseEnter = (e: React.MouseEvent<HTMLDivElement>) => {
  296. this.foundation.handleInputMouseEnter();
  297. };
  298. handleClickPrefixOrSuffix = (e: React.MouseEvent<HTMLInputElement>) => {
  299. this.foundation.handleClickPrefixOrSuffix(e);
  300. };
  301. handlePreventMouseDown = (e: React.MouseEvent<HTMLInputElement>) => {
  302. this.foundation.handlePreventMouseDown(e);
  303. };
  304. renderClearBtn() {
  305. const { hovering, tagsArray, inputValue } = this.state;
  306. const { showClear, disabled, clearIcon } = this.props;
  307. const clearCls = cls(`${prefixCls}-clearBtn`, {
  308. [`${prefixCls}-clearBtn-invisible`]: !hovering || (inputValue === '' && tagsArray.length === 0) || disabled,
  309. });
  310. if (showClear) {
  311. return (
  312. <div
  313. role="button"
  314. tabIndex={0}
  315. aria-label="Clear TagInput value"
  316. className={clearCls}
  317. onClick={e => this.handleClearBtn(e)}
  318. onKeyPress={e => this.handleClearEnterPress(e)}
  319. >
  320. { clearIcon ? clearIcon : <IconClear />}
  321. </div>
  322. );
  323. }
  324. return null;
  325. }
  326. renderPrefix() {
  327. const { prefix, insetLabel, insetLabelId } = this.props;
  328. const labelNode = prefix || insetLabel;
  329. if (isNull(labelNode) || isUndefined(labelNode)) {
  330. return null;
  331. }
  332. const prefixWrapperCls = cls(`${prefixCls}-prefix`, {
  333. [`${prefixCls}-inset-label`]: insetLabel,
  334. [`${prefixCls}-prefix-text`]: labelNode && isString(labelNode),
  335. [`${prefixCls}-prefix-icon`]: isSemiIcon(labelNode),
  336. });
  337. return (
  338. // eslint-disable-next-line jsx-a11y/no-static-element-interactions,jsx-a11y/click-events-have-key-events
  339. <div
  340. className={prefixWrapperCls}
  341. onMouseDown={this.handlePreventMouseDown}
  342. onClick={this.handleClickPrefixOrSuffix}
  343. id={insetLabelId} x-semi-prop="prefix"
  344. >
  345. {labelNode}
  346. </div>
  347. );
  348. }
  349. renderSuffix() {
  350. const { suffix } = this.props;
  351. if (isNull(suffix) || isUndefined(suffix)) {
  352. return null;
  353. }
  354. const suffixWrapperCls = cls(`${prefixCls}-suffix`, {
  355. [`${prefixCls}-suffix-text`]: suffix && isString(suffix),
  356. [`${prefixCls}-suffix-icon`]: isSemiIcon(suffix),
  357. });
  358. return (
  359. // eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
  360. <div
  361. className={suffixWrapperCls}
  362. onMouseDown={this.handlePreventMouseDown}
  363. onClick={this.handleClickPrefixOrSuffix}
  364. x-semi-prop="suffix"
  365. >
  366. {suffix}
  367. </div>
  368. );
  369. }
  370. getAllTags = () => {
  371. const { tagsArray } = this.state;
  372. return tagsArray.map((value, index) => this.renderTag(value, index));
  373. }
  374. renderTag = (value: any, index: number, sortableHandle?: any) => {
  375. const {
  376. size,
  377. disabled,
  378. renderTagItem,
  379. showContentTooltip,
  380. draggable,
  381. } = this.props;
  382. const { active } = this.state;
  383. const showIconHandler = active && draggable;
  384. const tagCls = cls(`${prefixCls}-wrapper-tag`, {
  385. [`${prefixCls}-wrapper-tag-size-${size}`]: size,
  386. [`${prefixCls}-wrapper-tag-icon`]: showIconHandler,
  387. });
  388. const typoCls = cls(`${prefixCls}-wrapper-typo`, {
  389. [`${prefixCls}-wrapper-typo-disabled`]: disabled,
  390. });
  391. const itemWrapperCls = cls({
  392. [`${prefixCls}-drag-item`]: showIconHandler,
  393. [`${prefixCls}-wrapper-tag-icon`]: showIconHandler,
  394. });
  395. const DragHandle = sortableHandle && sortableHandle(() => <IconHandle className={`${prefixCls}-drag-handler`}></IconHandle>);
  396. const elementKey = showIconHandler ? value : `${index}${value}`;
  397. const onClose = () => {
  398. !disabled && this.handleTagClose(index);
  399. };
  400. if (isFunction(renderTagItem)) {
  401. return (<div className={itemWrapperCls} key={elementKey}>
  402. {showIconHandler && sortableHandle ? <DragHandle /> : null}
  403. {renderTagItem(value, index, onClose)}
  404. </div>);
  405. } else {
  406. return (
  407. <Tag
  408. className={tagCls}
  409. color="white"
  410. size={size === 'small' ? 'small' : 'large'}
  411. type="light"
  412. onClose={onClose}
  413. closable={!disabled}
  414. key={elementKey}
  415. visible
  416. aria-label={`${!disabled ? 'Closable ' : ''}Tag: ${value}`}
  417. >
  418. {showIconHandler && sortableHandle ? <DragHandle /> : null}
  419. <Paragraph
  420. className={typoCls}
  421. ellipsis={{ showTooltip: showContentTooltip, rows: 1 }}
  422. >
  423. {value}
  424. </Paragraph>
  425. </Tag>
  426. );
  427. }
  428. }
  429. renderSortTag = (props: RenderItemProps) => {
  430. const { id: item, sortableHandle } = props;
  431. const { tagsArray } = this.state;
  432. const index = tagsArray.indexOf(item as string);
  433. return this.renderTag(item, index, sortableHandle);
  434. }
  435. onSortEnd = (callbackProps: OnSortEndProps) => {
  436. this.foundation.handleSortEnd(callbackProps);
  437. }
  438. renderTags() {
  439. const {
  440. disabled,
  441. maxTagCount,
  442. showRestTagsPopover,
  443. restTagsPopoverProps = {},
  444. draggable,
  445. expandRestTagsOnClick,
  446. } = this.props;
  447. const { tagsArray, active } = this.state;
  448. const restTagsCls = cls(`${prefixCls}-wrapper-n`, {
  449. [`${prefixCls}-wrapper-n-disabled`]: disabled,
  450. });
  451. const allTags = this.getAllTags();
  452. let restTags: Array<React.ReactNode> = [];
  453. let tags: Array<React.ReactNode> = [...allTags];
  454. if (( !active || !expandRestTagsOnClick) && maxTagCount && maxTagCount < allTags.length) {
  455. tags = allTags.slice(0, maxTagCount);
  456. restTags = allTags.slice(maxTagCount);
  457. }
  458. const restTagsContent = (
  459. <span className={restTagsCls}>+{tagsArray.length - maxTagCount}</span>
  460. );
  461. const sortableListItems = allTags.map((item, index) => ({
  462. item: item,
  463. key: tagsArray[index],
  464. }));
  465. if (active && draggable && sortableListItems.length > 0) {
  466. return <Sortable
  467. items={tagsArray}
  468. onSortEnd={this.onSortEnd}
  469. renderItem={this.renderSortTag}
  470. container={SortContainer}
  471. prefix={prefixCls}
  472. transition={null}
  473. dragOverlayCls={`${prefixCls}-right-item-drag-item-move`}
  474. />;
  475. }
  476. return (
  477. <>
  478. {tags}
  479. {
  480. restTags.length > 0 &&
  481. (
  482. showRestTagsPopover ?
  483. (
  484. <Popover
  485. content={restTags}
  486. showArrow
  487. trigger="hover"
  488. position="top"
  489. autoAdjustOverflow
  490. {...restTagsPopoverProps}
  491. >
  492. {restTagsContent}
  493. </Popover>
  494. ) : restTagsContent
  495. )
  496. }
  497. </>
  498. );
  499. }
  500. blur() {
  501. this.inputRef.current.blur();
  502. // unregister clickOutside event
  503. this.foundation.clickOutsideCallBack();
  504. }
  505. focus() {
  506. const { preventScroll, disabled } = this.props;
  507. this.inputRef.current.focus({ preventScroll });
  508. if (!disabled) {
  509. // register clickOutside event
  510. this.foundation.handleClick();
  511. }
  512. }
  513. handleInputCompositionStart = (e) => {
  514. this.foundation.handleInputCompositionStart(e);
  515. }
  516. handleInputCompositionEnd = (e) => {
  517. this.foundation.handleInputCompositionEnd(e);
  518. }
  519. render() {
  520. const {
  521. size,
  522. style,
  523. className,
  524. disabled,
  525. placeholder,
  526. validateStatus,
  527. prefix,
  528. insetLabel,
  529. suffix,
  530. ...rest
  531. } = this.props;
  532. const {
  533. focusing,
  534. hovering,
  535. tagsArray,
  536. inputValue,
  537. active,
  538. } = this.state;
  539. const tagInputCls = cls(prefixCls, className, {
  540. [`${prefixCls}-focus`]: focusing || active,
  541. [`${prefixCls}-disabled`]: disabled,
  542. [`${prefixCls}-hover`]: hovering && !disabled,
  543. [`${prefixCls}-error`]: validateStatus === 'error',
  544. [`${prefixCls}-warning`]: validateStatus === 'warning',
  545. [`${prefixCls}-small`]: size === 'small',
  546. [`${prefixCls}-large`]: size === 'large',
  547. [`${prefixCls}-with-prefix`]: !!prefix || !!insetLabel,
  548. [`${prefixCls}-with-suffix`]: !!suffix,
  549. });
  550. const inputCls = cls(`${prefixCls}-wrapper-input`, `${prefixCls}-wrapper-input-${size}`);
  551. const wrapperCls = cls(`${prefixCls}-wrapper`);
  552. return (
  553. // eslint-disable-next-line
  554. <div
  555. ref={this.tagInputRef}
  556. style={style}
  557. className={tagInputCls}
  558. aria-disabled={disabled}
  559. aria-label={this.props['aria-label']}
  560. aria-invalid={validateStatus === 'error'}
  561. onMouseEnter={e => {
  562. this.handleInputMouseEnter(e);
  563. }}
  564. onMouseLeave={e => {
  565. this.handleInputMouseLeave(e);
  566. }}
  567. onClick={e => {
  568. this.handleClick(e);
  569. }}
  570. {...this.getDataAttr(rest)}
  571. >
  572. {this.renderPrefix()}
  573. <div className={wrapperCls}>
  574. {this.renderTags()}
  575. <Input
  576. aria-label='input value'
  577. ref={this.inputRef as any}
  578. className={inputCls}
  579. disabled={disabled}
  580. value={inputValue}
  581. size={size}
  582. placeholder={tagsArray.length === 0 ? placeholder : ''}
  583. onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => {
  584. this.handleKeyDown(e);
  585. }}
  586. onChange={(v: string, e: React.ChangeEvent<HTMLInputElement>) => {
  587. this.handleInputChange(e);
  588. }}
  589. onBlur={(e: React.FocusEvent<HTMLInputElement>) => {
  590. this.handleInputBlur(e as any);
  591. }}
  592. onFocus={(e: React.FocusEvent<HTMLInputElement>) => {
  593. this.handleInputFocus(e as any);
  594. }}
  595. onCompositionStart={this.handleInputCompositionStart}
  596. onCompositionEnd={this.handleInputCompositionEnd}
  597. />
  598. </div>
  599. {this.renderClearBtn()}
  600. {this.renderSuffix()}
  601. </div>
  602. );
  603. }
  604. }
  605. export default TagInput;
  606. export { ValidateStatus };