Input.js 13 KB

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