Input.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. import _extends from "@babel/runtime/helpers/esm/extends";
  2. import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
  3. import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
  4. import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
  5. var _excluded = ["active", "showActiveCls", "suffixIcon", "format", "validateFormat", "onChange", "onInput", "helped", "onHelp", "onSubmit", "onKeyDown", "preserveInvalidOnBlur", "invalid", "clearIcon"];
  6. import classNames from 'classnames';
  7. import { useEvent } from 'rc-util';
  8. import useLayoutEffect from "rc-util/es/hooks/useLayoutEffect";
  9. import raf from "rc-util/es/raf";
  10. import * as React from 'react';
  11. import { leftPad } from "../../utils/miscUtil";
  12. import PickerContext from "../context";
  13. import useLockEffect from "../hooks/useLockEffect";
  14. import Icon from "./Icon";
  15. import MaskFormat from "./MaskFormat";
  16. import { getMaskRange } from "./util";
  17. // Format logic
  18. //
  19. // First time on focus:
  20. // 1. check if the text is valid, if not fill with format
  21. // 2. set highlight cell to the first cell
  22. // Cells
  23. // 1. Selection the index cell, set inner `cacheValue` to ''
  24. // 2. Key input filter non-number char, patch after the `cacheValue`
  25. // 1. Replace the `cacheValue` with input align the cell length
  26. // 2. Re-selection the mask cell
  27. // 3. If `cacheValue` match the limit length or cell format (like 1 ~ 12 month), go to next cell
  28. var Input = /*#__PURE__*/React.forwardRef(function (props, ref) {
  29. var active = props.active,
  30. _props$showActiveCls = props.showActiveCls,
  31. showActiveCls = _props$showActiveCls === void 0 ? true : _props$showActiveCls,
  32. suffixIcon = props.suffixIcon,
  33. format = props.format,
  34. validateFormat = props.validateFormat,
  35. onChange = props.onChange,
  36. onInput = props.onInput,
  37. helped = props.helped,
  38. onHelp = props.onHelp,
  39. onSubmit = props.onSubmit,
  40. onKeyDown = props.onKeyDown,
  41. _props$preserveInvali = props.preserveInvalidOnBlur,
  42. preserveInvalidOnBlur = _props$preserveInvali === void 0 ? false : _props$preserveInvali,
  43. invalid = props.invalid,
  44. clearIcon = props.clearIcon,
  45. restProps = _objectWithoutProperties(props, _excluded);
  46. var value = props.value,
  47. onFocus = props.onFocus,
  48. onBlur = props.onBlur,
  49. onMouseUp = props.onMouseUp;
  50. var _React$useContext = React.useContext(PickerContext),
  51. prefixCls = _React$useContext.prefixCls,
  52. _React$useContext$inp = _React$useContext.input,
  53. Component = _React$useContext$inp === void 0 ? 'input' : _React$useContext$inp;
  54. var inputPrefixCls = "".concat(prefixCls, "-input");
  55. // ======================== Value =========================
  56. var _React$useState = React.useState(false),
  57. _React$useState2 = _slicedToArray(_React$useState, 2),
  58. focused = _React$useState2[0],
  59. setFocused = _React$useState2[1];
  60. var _React$useState3 = React.useState(value),
  61. _React$useState4 = _slicedToArray(_React$useState3, 2),
  62. internalInputValue = _React$useState4[0],
  63. setInputValue = _React$useState4[1];
  64. var _React$useState5 = React.useState(''),
  65. _React$useState6 = _slicedToArray(_React$useState5, 2),
  66. focusCellText = _React$useState6[0],
  67. setFocusCellText = _React$useState6[1];
  68. var _React$useState7 = React.useState(null),
  69. _React$useState8 = _slicedToArray(_React$useState7, 2),
  70. focusCellIndex = _React$useState8[0],
  71. setFocusCellIndex = _React$useState8[1];
  72. var _React$useState9 = React.useState(null),
  73. _React$useState10 = _slicedToArray(_React$useState9, 2),
  74. forceSelectionSyncMark = _React$useState10[0],
  75. forceSelectionSync = _React$useState10[1];
  76. var inputValue = internalInputValue || '';
  77. // Sync value if needed
  78. React.useEffect(function () {
  79. setInputValue(value);
  80. }, [value]);
  81. // ========================= Refs =========================
  82. var holderRef = React.useRef();
  83. var inputRef = React.useRef();
  84. React.useImperativeHandle(ref, function () {
  85. return {
  86. nativeElement: holderRef.current,
  87. inputElement: inputRef.current,
  88. focus: function focus(options) {
  89. inputRef.current.focus(options);
  90. },
  91. blur: function blur() {
  92. inputRef.current.blur();
  93. }
  94. };
  95. });
  96. // ======================== Format ========================
  97. var maskFormat = React.useMemo(function () {
  98. return new MaskFormat(format || '');
  99. }, [format]);
  100. var _React$useMemo = React.useMemo(function () {
  101. if (helped) {
  102. return [0, 0];
  103. }
  104. return maskFormat.getSelection(focusCellIndex);
  105. }, [maskFormat, focusCellIndex, helped]),
  106. _React$useMemo2 = _slicedToArray(_React$useMemo, 2),
  107. selectionStart = _React$useMemo2[0],
  108. selectionEnd = _React$useMemo2[1];
  109. // ======================== Modify ========================
  110. // When input modify content, trigger `onHelp` if is not the format
  111. var onModify = function onModify(text) {
  112. if (text && text !== format && text !== value) {
  113. onHelp();
  114. }
  115. };
  116. // ======================== Change ========================
  117. /**
  118. * Triggered by paste, keyDown and focus to show format
  119. */
  120. var triggerInputChange = useEvent(function (text) {
  121. if (validateFormat(text)) {
  122. onChange(text);
  123. }
  124. setInputValue(text);
  125. onModify(text);
  126. });
  127. // Directly trigger `onChange` if `format` is empty
  128. var onInternalChange = function onInternalChange(event) {
  129. // Hack `onChange` with format to do nothing
  130. if (!format) {
  131. var text = event.target.value;
  132. onModify(text);
  133. setInputValue(text);
  134. onChange(text);
  135. }
  136. };
  137. var onFormatPaste = function onFormatPaste(event) {
  138. // Get paste text
  139. var pasteText = event.clipboardData.getData('text');
  140. if (validateFormat(pasteText)) {
  141. triggerInputChange(pasteText);
  142. }
  143. };
  144. // ======================== Mouse =========================
  145. // When `mouseDown` get focus, it's better to not to change the selection
  146. // Since the up position maybe not is the first cell
  147. var mouseDownRef = React.useRef(false);
  148. var onFormatMouseDown = function onFormatMouseDown() {
  149. mouseDownRef.current = true;
  150. };
  151. var onFormatMouseUp = function onFormatMouseUp(event) {
  152. var _ref = event.target,
  153. start = _ref.selectionStart;
  154. var closeMaskIndex = maskFormat.getMaskCellIndex(start);
  155. setFocusCellIndex(closeMaskIndex);
  156. // Force update the selection
  157. forceSelectionSync({});
  158. onMouseUp === null || onMouseUp === void 0 || onMouseUp(event);
  159. mouseDownRef.current = false;
  160. };
  161. // ====================== Focus Blur ======================
  162. var onFormatFocus = function onFormatFocus(event) {
  163. setFocused(true);
  164. setFocusCellIndex(0);
  165. setFocusCellText('');
  166. onFocus(event);
  167. };
  168. var onSharedBlur = function onSharedBlur(event) {
  169. onBlur(event);
  170. };
  171. var onFormatBlur = function onFormatBlur(event) {
  172. setFocused(false);
  173. onSharedBlur(event);
  174. };
  175. // ======================== Active ========================
  176. // Check if blur need reset input value
  177. useLockEffect(active, function () {
  178. if (!active && !preserveInvalidOnBlur) {
  179. setInputValue(value);
  180. }
  181. });
  182. // ======================= Keyboard =======================
  183. var onSharedKeyDown = function onSharedKeyDown(event) {
  184. if (event.key === 'Enter' && validateFormat(inputValue)) {
  185. onSubmit();
  186. }
  187. onKeyDown === null || onKeyDown === void 0 || onKeyDown(event);
  188. };
  189. var onFormatKeyDown = function onFormatKeyDown(event) {
  190. onSharedKeyDown(event);
  191. var key = event.key;
  192. // Save the cache with cell text
  193. var nextCellText = null;
  194. // Fill in the input
  195. var nextFillText = null;
  196. var maskCellLen = selectionEnd - selectionStart;
  197. var cellFormat = format.slice(selectionStart, selectionEnd);
  198. // Cell Index
  199. var offsetCellIndex = function offsetCellIndex(offset) {
  200. setFocusCellIndex(function (idx) {
  201. var nextIndex = idx + offset;
  202. nextIndex = Math.max(nextIndex, 0);
  203. nextIndex = Math.min(nextIndex, maskFormat.size() - 1);
  204. return nextIndex;
  205. });
  206. };
  207. // Range
  208. var offsetCellValue = function offsetCellValue(offset) {
  209. var _getMaskRange = getMaskRange(cellFormat),
  210. _getMaskRange2 = _slicedToArray(_getMaskRange, 3),
  211. rangeStart = _getMaskRange2[0],
  212. rangeEnd = _getMaskRange2[1],
  213. rangeDefault = _getMaskRange2[2];
  214. var currentText = inputValue.slice(selectionStart, selectionEnd);
  215. var currentTextNum = Number(currentText);
  216. if (isNaN(currentTextNum)) {
  217. return String(rangeDefault ? rangeDefault : offset > 0 ? rangeStart : rangeEnd);
  218. }
  219. var num = currentTextNum + offset;
  220. var range = rangeEnd - rangeStart + 1;
  221. return String(rangeStart + (range + num - rangeStart) % range);
  222. };
  223. switch (key) {
  224. // =============== Remove ===============
  225. case 'Backspace':
  226. case 'Delete':
  227. nextCellText = '';
  228. nextFillText = cellFormat;
  229. break;
  230. // =============== Arrows ===============
  231. // Left key
  232. case 'ArrowLeft':
  233. nextCellText = '';
  234. offsetCellIndex(-1);
  235. break;
  236. // Right key
  237. case 'ArrowRight':
  238. nextCellText = '';
  239. offsetCellIndex(1);
  240. break;
  241. // Up key
  242. case 'ArrowUp':
  243. nextCellText = '';
  244. nextFillText = offsetCellValue(1);
  245. break;
  246. // Down key
  247. case 'ArrowDown':
  248. nextCellText = '';
  249. nextFillText = offsetCellValue(-1);
  250. break;
  251. // =============== Number ===============
  252. default:
  253. if (!isNaN(Number(key))) {
  254. nextCellText = focusCellText + key;
  255. nextFillText = nextCellText;
  256. }
  257. break;
  258. }
  259. // Update cell text
  260. if (nextCellText !== null) {
  261. setFocusCellText(nextCellText);
  262. if (nextCellText.length >= maskCellLen) {
  263. // Go to next cell
  264. offsetCellIndex(1);
  265. setFocusCellText('');
  266. }
  267. }
  268. // Update the input text
  269. if (nextFillText !== null) {
  270. // Replace selection range with `nextCellText`
  271. var nextFocusValue =
  272. // before
  273. inputValue.slice(0, selectionStart) +
  274. // replace
  275. leftPad(nextFillText, maskCellLen) +
  276. // after
  277. inputValue.slice(selectionEnd);
  278. triggerInputChange(nextFocusValue.slice(0, format.length));
  279. }
  280. // Always trigger selection sync after key down
  281. forceSelectionSync({});
  282. };
  283. // ======================== Format ========================
  284. var rafRef = React.useRef();
  285. useLayoutEffect(function () {
  286. if (!focused || !format || mouseDownRef.current) {
  287. return;
  288. }
  289. // Reset with format if not match
  290. if (!maskFormat.match(inputValue)) {
  291. triggerInputChange(format);
  292. return;
  293. }
  294. // Match the selection range
  295. inputRef.current.setSelectionRange(selectionStart, selectionEnd);
  296. // Chrome has the bug anchor position looks not correct but actually correct
  297. rafRef.current = raf(function () {
  298. inputRef.current.setSelectionRange(selectionStart, selectionEnd);
  299. });
  300. return function () {
  301. raf.cancel(rafRef.current);
  302. };
  303. }, [maskFormat, format, focused, inputValue, focusCellIndex, selectionStart, selectionEnd, forceSelectionSyncMark, triggerInputChange]);
  304. // ======================== Render ========================
  305. // Input props for format
  306. var inputProps = format ? {
  307. onFocus: onFormatFocus,
  308. onBlur: onFormatBlur,
  309. onKeyDown: onFormatKeyDown,
  310. onMouseDown: onFormatMouseDown,
  311. onMouseUp: onFormatMouseUp,
  312. onPaste: onFormatPaste
  313. } : {};
  314. return /*#__PURE__*/React.createElement("div", {
  315. ref: holderRef,
  316. className: classNames(inputPrefixCls, _defineProperty(_defineProperty({}, "".concat(inputPrefixCls, "-active"), active && showActiveCls), "".concat(inputPrefixCls, "-placeholder"), helped))
  317. }, /*#__PURE__*/React.createElement(Component, _extends({
  318. ref: inputRef,
  319. "aria-invalid": invalid,
  320. autoComplete: "off"
  321. }, restProps, {
  322. onKeyDown: onSharedKeyDown,
  323. onBlur: onSharedBlur
  324. // Replace with format
  325. }, inputProps, {
  326. // Value
  327. value: inputValue,
  328. onChange: onInternalChange
  329. })), /*#__PURE__*/React.createElement(Icon, {
  330. type: "suffix",
  331. icon: suffixIcon
  332. }), clearIcon);
  333. });
  334. if (process.env.NODE_ENV !== 'production') {
  335. Input.displayName = 'Input';
  336. }
  337. export default Input;