foundation.ts 6.8 KB

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