foundation.ts 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. import BaseFoundation, { DefaultAdapter, noopFunction } from '../base/foundation';
  2. import { strings } from './constants';
  3. import { noop, set, isNumber, isString, isFunction } from 'lodash';
  4. import { ENTER_KEY } from './../utils/keyCode';
  5. export interface InputDefaultAdapter {
  6. notifyChange: noopFunction;
  7. setValue: noopFunction
  8. }
  9. export interface InputAdapter extends Partial<DefaultAdapter>, Partial<InputDefaultAdapter> {
  10. setMinLength(minLength: number): void;
  11. notifyClear(e: any): void;
  12. notifyBlur(value: any, e: any): void;
  13. setEyeClosed(eyeClosed: boolean): void;
  14. toggleFocusing(focused: boolean): void;
  15. notifyFocus(value: any, e: any): void;
  16. notifyInput(e: any): void;
  17. notifyKeyDown(e: any): void;
  18. notifyKeyUp(e: any): void;
  19. notifyKeyPress(e: any): void;
  20. notifyEnterPress(e: any): void;
  21. setPaddingLeft(paddingLeft: string): void;
  22. isEventTarget(e: any): boolean
  23. }
  24. class InputFoundation extends BaseFoundation<InputAdapter> {
  25. static get inputDefaultAdapter() {
  26. return {
  27. notifyChange: noop,
  28. setValue: noop,
  29. // toggleAllowClear: noop,
  30. };
  31. }
  32. _timer: number | null;
  33. constructor(adapter: InputAdapter) {
  34. super({ ...InputFoundation.inputDefaultAdapter, ...adapter });
  35. }
  36. init() {
  37. this._setInitValue();
  38. }
  39. destroy() {
  40. if (this._timer) {
  41. clearTimeout(this._timer);
  42. this._timer = null;
  43. }
  44. }
  45. // eslint-disable-next-line
  46. setDisable() {}
  47. _setInitValue() {
  48. const { defaultValue, value } = this.getProps();
  49. let v = defaultValue;
  50. if (this._isControlledComponent()) {
  51. v = value;
  52. }
  53. this._adapter.setValue(v);
  54. // this.checkAllowClear(v);
  55. }
  56. setValue(value: any) {
  57. this._adapter.setValue(value);
  58. }
  59. handleChange(value: any, e: any) {
  60. const { maxLength, minLength, getValueLength } = this._adapter.getProps();
  61. let nextValue = value;
  62. if (maxLength && isFunction(getValueLength)) {
  63. nextValue = this.handleVisibleMaxLength(value);
  64. }
  65. if (minLength && isFunction(getValueLength)) {
  66. this.handleVisibleMinLength(nextValue);
  67. }
  68. if (this._isControlledComponent()) {
  69. /**
  70. * If it is a controlled component, directly notify the caller of the modified value.
  71. * Truncate the input value from the input box if the input value exceeds the maximum length limit.
  72. * Even in controlled components, characters that exceed the length limit cannot be entered through the input box.
  73. */
  74. this._adapter.notifyChange(nextValue, e);
  75. } else {
  76. this._adapter.setValue(nextValue);
  77. this._adapter.notifyChange(nextValue, e);
  78. // this.checkAllowClear(value);
  79. }
  80. }
  81. /**
  82. * Modify minLength to trigger browser check for minimum length
  83. * Controlled mode is not checked
  84. * @param {String} value
  85. */
  86. handleVisibleMinLength(value: any) {
  87. const { minLength, getValueLength } = this._adapter.getProps();
  88. const { minLength: stateMinLength } = this._adapter.getStates();
  89. if (isNumber(minLength) && minLength >= 0 && isFunction(getValueLength) && isString(value)) {
  90. const valueLength = getValueLength(value);
  91. if (valueLength < minLength) {
  92. const newMinLength = value.length + (minLength - valueLength);
  93. newMinLength !== stateMinLength && this._adapter.setMinLength(newMinLength);
  94. } else {
  95. stateMinLength !== minLength && this._adapter.setMinLength(minLength);
  96. }
  97. }
  98. }
  99. /**
  100. * Handle input emoji characters beyond maxLength
  101. * Controlled mode is not checked
  102. * @param {String} value
  103. */
  104. handleVisibleMaxLength(value: any) {
  105. const { maxLength, getValueLength } = this._adapter.getProps();
  106. if (isNumber(maxLength) && maxLength >= 0 && isFunction(getValueLength) && isString(value)) {
  107. const valueLength = getValueLength(value);
  108. if (valueLength > maxLength) {
  109. // eslint-disable-next-line max-len
  110. console.warn('[Semi Input] The input character is truncated because the input length exceeds the maximum length limit');
  111. const truncatedValue = this.handleTruncateValue(value, maxLength);
  112. return truncatedValue;
  113. } else {
  114. return value;
  115. }
  116. }
  117. }
  118. /**
  119. * Truncate input values based on maximum length
  120. * @param {String} value
  121. * @param {Number} maxLength
  122. * @returns {String}
  123. */
  124. handleTruncateValue(value: any, maxLength: number) {
  125. const { getValueLength } = this._adapter.getProps();
  126. if (isFunction(getValueLength)) {
  127. let truncatedValue = '';
  128. for (let i = 1, len = value.length; i <= len; i++) {
  129. const currentValue = value.slice(0, i);
  130. if (getValueLength(currentValue) > maxLength) {
  131. return truncatedValue;
  132. } else {
  133. truncatedValue = currentValue;
  134. }
  135. }
  136. return truncatedValue;
  137. } else {
  138. return value.slice(0, maxLength);
  139. }
  140. }
  141. handleClear(e: any) {
  142. let eventObj = e;
  143. const value = '';
  144. // let input = this._adapter.getInput();
  145. if (this._isControlledComponent('value')) {
  146. this._adapter.setState({
  147. isFocus: false,
  148. });
  149. } else {
  150. this._adapter.setState({
  151. value: '',
  152. isFocus: false,
  153. });
  154. }
  155. if (!eventObj || typeof eventObj !== 'object') {
  156. eventObj = {};
  157. }
  158. set(eventObj, strings.CLEARBTN_CLICKED_EVENT_FLAG, true); // this is useful for DateInput
  159. this._adapter.notifyChange(value, eventObj);
  160. this._adapter.notifyClear(eventObj);
  161. if (eventObj) {
  162. // When input is in popover and popover needs to judge clickOutSide, such as TreeSelect
  163. // If the click event bubbles up, it will mistakenly trigger clickOutSide's judgment.
  164. // At the same time, because the clear icon is not in the dom tree after clicking, and clickOutSide uses dom.contain (e.target), it will be considered as clicking on the outside, which will cause the floating layer to fold
  165. // So we need to stop the incident from bubbling up
  166. this.stopPropagation(eventObj);
  167. }
  168. }
  169. /**
  170. * trigger when click input wrapper
  171. * @param {Event} e
  172. */
  173. handleClick(e: any) {
  174. const { disabled } = this._adapter.getProps();
  175. const { isFocus } = this._adapter.getStates();
  176. if (disabled || isFocus) {
  177. return;
  178. }
  179. // do not handle bubbling up events
  180. if (this._adapter.isEventTarget(e)) {
  181. this._adapter.toggleFocusing(true);
  182. }
  183. }
  184. handleModeChange(mode: string) {
  185. if (mode === 'password') {
  186. this._adapter.setEyeClosed(true);
  187. } else {
  188. this._adapter.setEyeClosed(false);
  189. }
  190. }
  191. handleClickEye(e: any) {
  192. const eyeClosed = this._adapter.getState('eyeClosed');
  193. this._adapter.toggleFocusing(true);
  194. this._adapter.setEyeClosed(!eyeClosed);
  195. }
  196. handleInputType(type: string) {
  197. const mode = this._adapter.getProp('mode');
  198. const eyeClosed = this._adapter.getState('eyeClosed');
  199. if (mode === 'password') {
  200. return eyeClosed ? 'password' : 'text';
  201. }
  202. return type;
  203. }
  204. handleMouseDown(e: any) {
  205. e.preventDefault();
  206. }
  207. handleMouseUp(e: any) {
  208. e.preventDefault();
  209. }
  210. handleBlur(e: any) {
  211. const { value } = this.getStates();
  212. this._adapter.toggleFocusing(false);
  213. this._adapter.notifyBlur(value, e);
  214. }
  215. handleFocus(e: any) {
  216. const { value } = this.getStates();
  217. this._adapter.toggleFocusing(true);
  218. // this.checkAllowClear(this.getState('value'), true);
  219. this._adapter.notifyFocus(value, e);
  220. }
  221. handleInput(e: any) {
  222. this._adapter.notifyInput(e);
  223. }
  224. handleKeyDown(e: any) {
  225. this._adapter.notifyKeyDown(e);
  226. }
  227. handleKeyUp(e: any) {
  228. this._adapter.notifyKeyUp(e);
  229. }
  230. handleKeyPress(e: any) {
  231. this._adapter.notifyKeyPress(e);
  232. if (e.key === ENTER_KEY) {
  233. this._adapter.notifyEnterPress(e);
  234. }
  235. }
  236. setPaddingLeft(paddingLeft: string) {
  237. this._adapter.setPaddingLeft(paddingLeft);
  238. }
  239. isAllowClear() {
  240. const { value, isFocus, isHovering } = this._adapter.getStates();
  241. const { showClear, disabled } = this._adapter.getProps();
  242. const allowClear = value && showClear && !disabled && (isFocus || isHovering);
  243. return allowClear;
  244. }
  245. handleClickPrefixOrSuffix(e: any) {
  246. const { disabled } = this._adapter.getProps();
  247. const { isFocus } = this._adapter.getStates();
  248. if (!disabled && !isFocus) {
  249. this._adapter.toggleFocusing(true);
  250. }
  251. }
  252. /**
  253. * Blocking mousedown events prevents input from losing focus
  254. * @param {Event} e
  255. */
  256. handlePreventMouseDown(e: any) {
  257. if (e && isFunction(e.preventDefault)) {
  258. e.preventDefault();
  259. }
  260. }
  261. /**
  262. * A11y: simulate password button click
  263. */
  264. handleModeEnterPress(e: any) {
  265. // trigger by Enter or Space key
  266. if (['Enter', ' '].includes(e?.key)) {
  267. this.handlePreventMouseDown(e);
  268. this.handleClickEye(e);
  269. }
  270. }
  271. }
  272. export default InputFoundation;