foundation.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import BaseFoundation, { DefaultAdapter } from '../base/foundation';
  2. import keyCode from '../utils/keyCode';
  3. import {
  4. isString,
  5. isNumber,
  6. isFunction,
  7. isUndefined
  8. } from 'lodash';
  9. import getSplitedArray from './utils/getSplitedArray';
  10. import isEnterPress from '../utils/isEnterPress';
  11. export type TagInputChangeEvent = any;
  12. export type TagInputCursorEvent = any;
  13. export type TagInputKeyboardEvent = any;
  14. export type TagInputMouseEvent = any;
  15. export interface TagInputAdapter extends DefaultAdapter {
  16. setInputValue: (inputValue: string) => void;
  17. setTagsArray: (tagsArray: string[]) => void;
  18. setFocusing: (focusing: boolean) => void;
  19. toggleFocusing(focused: boolean): void;
  20. setHovering: (hovering: boolean) => void;
  21. notifyBlur: (e: TagInputCursorEvent) => void;
  22. notifyFocus: (e: TagInputCursorEvent) => void;
  23. notifyInputChange: (v: string, e: TagInputChangeEvent) => void;
  24. notifyTagChange: (v: string[]) => void;
  25. notifyTagAdd: (v: string[]) => void;
  26. notifyTagRemove: (v: string, idx: number) => void;
  27. notifyKeyDown: (e: TagInputMouseEvent) => void;
  28. }
  29. class TagInputFoundation extends BaseFoundation<TagInputAdapter> {
  30. constructor(adapter: TagInputAdapter) {
  31. super({ ...adapter });
  32. }
  33. /**
  34. * handler of input change
  35. */
  36. handleInputChange = (e: TagInputChangeEvent) => {
  37. const { value } = e.target;
  38. this._checkInputChangeValid(value) && this._onInputChange(value, e);
  39. };
  40. /**
  41. * check whether the input change is legal
  42. */
  43. _checkInputChangeValid = (value: string) => {
  44. // e.target.value legitimacy judgment needs to be based on this.state.input Value
  45. const {
  46. maxLength,
  47. onInputExceed,
  48. separator
  49. } = this._adapter.getProps();
  50. const { inputValue } = this._adapter.getStates();
  51. let allowChange = true;
  52. if (isNumber(maxLength)) {
  53. const valueArr = getSplitedArray(value, separator);
  54. const inputArr = getSplitedArray(inputValue, separator);
  55. const maxLen = Math.max(valueArr.length, inputArr.length);
  56. for (let i = 0; i < maxLen; i++) {
  57. // When the input length is increasing
  58. // eslint-disable-next-line max-len
  59. if (!isUndefined(valueArr[i]) && (isUndefined(inputArr[i]) || valueArr[i].length > inputArr[i].length)) {
  60. // When the input length exceeds maxLength
  61. // eslint-disable-next-line max-depth
  62. if (valueArr[i].length > maxLength) {
  63. allowChange = false;
  64. isFunction(onInputExceed) && onInputExceed(value);
  65. break;
  66. }
  67. }
  68. }
  69. }
  70. return allowChange;
  71. };
  72. /**
  73. * Input event handler when onKeyDown is triggered
  74. */
  75. handleKeyDown = (e: TagInputKeyboardEvent) => {
  76. const {
  77. inputValue,
  78. tagsArray
  79. } = this._adapter.getStates();
  80. const code = e.keyCode;
  81. if (code === keyCode.ENTER) {
  82. e.preventDefault(); // prevent trigger submit when using in form
  83. if (inputValue !== '') {
  84. this._handleAddTags(e);
  85. }
  86. }
  87. const { length } = tagsArray;
  88. if (code === keyCode.BACKSPACE && inputValue === '' && length > 0) {
  89. const newTagList = tagsArray.slice(0, length - 1);
  90. const removedTag = tagsArray[length - 1];
  91. this._onRemove(newTagList, removedTag, length - 1);
  92. }
  93. this._adapter.notifyKeyDown(e);
  94. };
  95. _handleAddTags(e: TagInputChangeEvent) {
  96. const {
  97. separator,
  98. max,
  99. onExceed,
  100. allowDuplicates
  101. } = this._adapter.getProps();
  102. const {
  103. inputValue,
  104. tagsArray
  105. } = this._adapter.getStates();
  106. let addTags = getSplitedArray(inputValue, separator);
  107. addTags = addTags.filter((item, idx) => {
  108. // If allowDuplicates is false, then filter duplicates
  109. if (!allowDuplicates) {
  110. if (tagsArray.includes(item) || addTags.indexOf(item) !== idx) {
  111. return false;
  112. }
  113. }
  114. // Filter empty strings and pure space strings in new items
  115. return isString(item) && item.trim() !== '';
  116. });
  117. let newTagList = tagsArray.concat(addTags);
  118. if (isNumber(max) && newTagList.length > max) {
  119. isFunction(onExceed) && onExceed(newTagList);
  120. newTagList = newTagList.slice(0, max);
  121. addTags = addTags.slice(0, max - tagsArray.length);
  122. }
  123. if (addTags.length > 0) {
  124. this._onAdd(newTagList, addTags);
  125. }
  126. this._onInputChange('', e);
  127. }
  128. handleInputBlur(e: TagInputCursorEvent) {
  129. const { addOnBlur } = this._adapter.getProps();
  130. if (addOnBlur === true) {
  131. this._handleAddTags(e);
  132. }
  133. this._adapter.setFocusing(false);
  134. this._adapter.notifyBlur(e);
  135. }
  136. handleInputFocus(e: TagInputCursorEvent) {
  137. this._adapter.setFocusing(true);
  138. this._adapter.notifyFocus(e);
  139. }
  140. /**
  141. * A11y: simulate clear button click
  142. */
  143. /* istanbul ignore next */
  144. handleClearEnterPress(e: TagInputKeyboardEvent) {
  145. if (isEnterPress(e)) {
  146. this.handleClearBtn(e);
  147. }
  148. }
  149. handleClearBtn(e: TagInputMouseEvent) {
  150. const { inputValue, tagsArray } = this._adapter.getStates();
  151. if (tagsArray.length > 0) {
  152. this._adapter.setTagsArray([]);
  153. this._adapter.notifyTagChange([]);
  154. }
  155. if (inputValue.length > 0) {
  156. this._onInputChange('', e);
  157. }
  158. }
  159. handleTagClose(index: number) {
  160. const { tagsArray } = this._adapter.getStates();
  161. const newTagList = [...tagsArray];
  162. newTagList.splice(index, 1);
  163. const removedTag = tagsArray[index];
  164. this._onRemove(newTagList, removedTag, index);
  165. }
  166. handleInputMouseEnter() {
  167. this._adapter.setHovering(true);
  168. }
  169. handleInputMouseLeave() {
  170. this._adapter.setHovering(false);
  171. }
  172. handleClickPrefixOrSuffix(e: any) {
  173. const { disabled } = this._adapter.getProps();
  174. const { isFocus } = this._adapter.getStates();
  175. if (!disabled && !isFocus) {
  176. this._adapter.toggleFocusing(true);
  177. }
  178. }
  179. handlePreventMouseDown(e: any) {
  180. if (e && isFunction(e.preventDefault)) {
  181. e.preventDefault();
  182. }
  183. }
  184. /**
  185. * handler of delete tag
  186. */
  187. _onRemove(newTagList: string[], removedTags: string, index: number) {
  188. if (!this._isControlledComponent()) {
  189. this._adapter.setTagsArray(newTagList);
  190. }
  191. this._adapter.notifyTagChange(newTagList);
  192. this._adapter.notifyTagRemove(removedTags, index);
  193. }
  194. /**
  195. * handler of add tag
  196. */
  197. _onAdd(newTagList: string[], addTags: string[]) {
  198. if (!this._isControlledComponent()) {
  199. this._adapter.setTagsArray(newTagList);
  200. }
  201. this._adapter.notifyTagChange(newTagList);
  202. this._adapter.notifyTagAdd(addTags);
  203. }
  204. /**
  205. * handler of input change
  206. */
  207. _onInputChange(value: string, e: TagInputChangeEvent) {
  208. this._adapter.setInputValue(value);
  209. this._adapter.notifyInputChange(value, e);
  210. }
  211. }
  212. export default TagInputFoundation;