@alfalab/core-components-number-input 1.0.16 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Component.js CHANGED
@@ -63,11 +63,12 @@ var NumberInput = React__default.default.forwardRef(function (_a, ref) {
63
63
  var handleKeyDown = function (event) {
64
64
  var disallowedSymbols = /[/|?!@#$%^&*()_=A-Za-zА-Яа-яЁё ]/;
65
65
  var oneKeyPress = !event.altKey && !event.metaKey && !event.ctrlKey;
66
+ var target = event.target;
66
67
  // Запрещаем вводить неразрешенные символы за исключением комбинаций клавиш
67
68
  if (oneKeyPress && event.key.length === 1 && disallowedSymbols.test(event.key)) {
68
69
  return event.preventDefault();
69
70
  }
70
- var val = event.target.value;
71
+ var val = target.value;
71
72
  var hasSeparator = (val.match(utils.createSeparatorsRegExp()) || []).length > 0;
72
73
  // Запрещаем вводить второй сепаратор
73
74
  if (hasSeparator && utils.SEPARATORS.some(function (s) { return s === event.key; })) {
@@ -78,7 +79,7 @@ var NumberInput = React__default.default.forwardRef(function (_a, ref) {
78
79
  utils.SIGNS.some(function (s) { return s === event.key; })) {
79
80
  return event.preventDefault();
80
81
  }
81
- var selectionStart = event.target.selectionStart || 0;
82
+ var selectionStart = target.selectionStart || 0;
82
83
  // Запрещаем вводить цифры в дробную часть, если кол-во цифр больше fractionLength
83
84
  if (hasSeparator &&
84
85
  fractionLength &&
package/cssm/Component.js CHANGED
@@ -63,11 +63,12 @@ var NumberInput = React__default.default.forwardRef(function (_a, ref) {
63
63
  var handleKeyDown = function (event) {
64
64
  var disallowedSymbols = /[/|?!@#$%^&*()_=A-Za-zА-Яа-яЁё ]/;
65
65
  var oneKeyPress = !event.altKey && !event.metaKey && !event.ctrlKey;
66
+ var target = event.target;
66
67
  // Запрещаем вводить неразрешенные символы за исключением комбинаций клавиш
67
68
  if (oneKeyPress && event.key.length === 1 && disallowedSymbols.test(event.key)) {
68
69
  return event.preventDefault();
69
70
  }
70
- var val = event.target.value;
71
+ var val = target.value;
71
72
  var hasSeparator = (val.match(utils.createSeparatorsRegExp()) || []).length > 0;
72
73
  // Запрещаем вводить второй сепаратор
73
74
  if (hasSeparator && utils.SEPARATORS.some(function (s) { return s === event.key; })) {
@@ -78,7 +79,7 @@ var NumberInput = React__default.default.forwardRef(function (_a, ref) {
78
79
  utils.SIGNS.some(function (s) { return s === event.key; })) {
79
80
  return event.preventDefault();
80
81
  }
81
- var selectionStart = event.target.selectionStart || 0;
82
+ var selectionStart = target.selectionStart || 0;
82
83
  // Запрещаем вводить цифры в дробную часть, если кол-во цифр больше fractionLength
83
84
  if (hasSeparator &&
84
85
  fractionLength &&
package/esm/Component.js CHANGED
@@ -54,11 +54,12 @@ var NumberInput = React.forwardRef(function (_a, ref) {
54
54
  var handleKeyDown = function (event) {
55
55
  var disallowedSymbols = /[/|?!@#$%^&*()_=A-Za-zА-Яа-яЁё ]/;
56
56
  var oneKeyPress = !event.altKey && !event.metaKey && !event.ctrlKey;
57
+ var target = event.target;
57
58
  // Запрещаем вводить неразрешенные символы за исключением комбинаций клавиш
58
59
  if (oneKeyPress && event.key.length === 1 && disallowedSymbols.test(event.key)) {
59
60
  return event.preventDefault();
60
61
  }
61
- var val = event.target.value;
62
+ var val = target.value;
62
63
  var hasSeparator = (val.match(createSeparatorsRegExp()) || []).length > 0;
63
64
  // Запрещаем вводить второй сепаратор
64
65
  if (hasSeparator && SEPARATORS.some(function (s) { return s === event.key; })) {
@@ -69,7 +70,7 @@ var NumberInput = React.forwardRef(function (_a, ref) {
69
70
  SIGNS.some(function (s) { return s === event.key; })) {
70
71
  return event.preventDefault();
71
72
  }
72
- var selectionStart = event.target.selectionStart || 0;
73
+ var selectionStart = target.selectionStart || 0;
73
74
  // Запрещаем вводить цифры в дробную часть, если кол-во цифр больше fractionLength
74
75
  if (hasSeparator &&
75
76
  fractionLength &&
@@ -52,11 +52,12 @@ const NumberInput = React.forwardRef(({ value: propValue, onChange, onBlur, allo
52
52
  const handleKeyDown = (event) => {
53
53
  const disallowedSymbols = /[/|?!@#$%^&*()_=A-Za-zА-Яа-яЁё ]/;
54
54
  const oneKeyPress = !event.altKey && !event.metaKey && !event.ctrlKey;
55
+ const target = event.target;
55
56
  // Запрещаем вводить неразрешенные символы за исключением комбинаций клавиш
56
57
  if (oneKeyPress && event.key.length === 1 && disallowedSymbols.test(event.key)) {
57
58
  return event.preventDefault();
58
59
  }
59
- const val = event.target.value;
60
+ const val = target.value;
60
61
  const hasSeparator = (val.match(createSeparatorsRegExp()) || []).length > 0;
61
62
  // Запрещаем вводить второй сепаратор
62
63
  if (hasSeparator && SEPARATORS.some((s) => s === event.key)) {
@@ -67,7 +68,7 @@ const NumberInput = React.forwardRef(({ value: propValue, onChange, onBlur, allo
67
68
  SIGNS.some((s) => s === event.key)) {
68
69
  return event.preventDefault();
69
70
  }
70
- const selectionStart = event.target.selectionStart || 0;
71
+ const selectionStart = target.selectionStart || 0;
71
72
  // Запрещаем вводить цифры в дробную часть, если кол-во цифр больше fractionLength
72
73
  if (hasSeparator &&
73
74
  fractionLength &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfalab/core-components-number-input",
3
- "version": "1.0.16",
3
+ "version": "1.1.0",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "license": "MIT",
@@ -14,7 +14,7 @@
14
14
  "react": "^16.9.0 || ^17.0.1 || ^18.0.0"
15
15
  },
16
16
  "dependencies": {
17
- "@alfalab/core-components-input": "^11.1.18",
17
+ "@alfalab/core-components-input": "^12.0.0",
18
18
  "react-merge-refs": "^1.1.0",
19
19
  "tslib": "^2.4.0"
20
20
  }
@@ -0,0 +1,195 @@
1
+ import React, { ChangeEvent, FocusEvent, KeyboardEvent, useRef, useState } from 'react';
2
+ import mergeRefs from 'react-merge-refs';
3
+
4
+ import { Input, InputProps } from '@alfalab/core-components-input';
5
+
6
+ import { createSeparatorsRegExp, getAllowedValue, SEPARATORS, SIGNS } from './utils';
7
+
8
+ export type NumberInputProps = Omit<InputProps, 'value' | 'onChange' | 'type'> & {
9
+ /**
10
+ * Значение поля ввода
11
+ */
12
+ value?: string | number | null;
13
+
14
+ /**
15
+ * Учитывать знаки '+' и '-'
16
+ */
17
+ allowSigns?: boolean;
18
+
19
+ /**
20
+ * Разделитель ',' или '.'
21
+ */
22
+ separator?: '.' | ',';
23
+
24
+ /**
25
+ * Количество символов после разделителя
26
+ */
27
+ fractionLength?: number;
28
+
29
+ /**
30
+ * Обработчик события изменения значения
31
+ */
32
+ onChange?: (
33
+ e: React.ChangeEvent<HTMLInputElement>,
34
+ payload: {
35
+ /**
36
+ * Числовое значение инпута
37
+ */
38
+ value: number | null;
39
+ /**
40
+ * Строковое значение инпута
41
+ * Используйте для изменения значения инпута
42
+ */
43
+ valueString: string;
44
+ },
45
+ ) => void;
46
+ };
47
+
48
+ export const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
49
+ (
50
+ {
51
+ value: propValue,
52
+ onChange,
53
+ onBlur,
54
+ allowSigns = true,
55
+ separator = ',',
56
+ fractionLength,
57
+ defaultValue,
58
+ ...restProps
59
+ },
60
+ ref,
61
+ ) => {
62
+ const uncontrolled = propValue === undefined;
63
+ const inputRef = useRef<HTMLInputElement>(null);
64
+ const [value, setValue] = useState<string>(defaultValue || '');
65
+
66
+ const getNumberValueFromStr = (valueString: string) => {
67
+ if (valueString === '') return null;
68
+
69
+ if (valueString.includes(',')) {
70
+ return parseFloat(valueString.replace(',', '.'));
71
+ }
72
+
73
+ return parseFloat(valueString);
74
+ };
75
+
76
+ const restoreCaret = (target: HTMLInputElement) => {
77
+ setTimeout(() => {
78
+ const input = target;
79
+ const positionCursor = input.selectionStart || 0;
80
+ const isEndPosition = input.value.length === positionCursor;
81
+
82
+ const enteredSign = SIGNS.some((s) => s === input.value[positionCursor - 1]);
83
+ const enteredSeparator = SEPARATORS.filter((s) => s !== separator).some(
84
+ (s) => s === input.value[positionCursor - 1],
85
+ );
86
+
87
+ const shouldRestore = enteredSeparator || enteredSign;
88
+
89
+ if (!isEndPosition && shouldRestore) {
90
+ input.selectionStart = positionCursor;
91
+ input.selectionEnd = positionCursor;
92
+ }
93
+ });
94
+ };
95
+
96
+ const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
97
+ const input = event.target;
98
+ const newValue = input.value.replace(createSeparatorsRegExp(), separator);
99
+
100
+ const allowedValue = getAllowedValue({
101
+ value: newValue,
102
+ fractionLength,
103
+ allowSigns,
104
+ separator,
105
+ });
106
+
107
+ if (onChange) {
108
+ onChange(event, {
109
+ value: getNumberValueFromStr(allowedValue),
110
+ valueString: allowedValue,
111
+ });
112
+ }
113
+
114
+ if (uncontrolled) {
115
+ setValue(allowedValue);
116
+ }
117
+
118
+ restoreCaret(input);
119
+ };
120
+
121
+ const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
122
+ const disallowedSymbols = /[/|?!@#$%^&*()_=A-Za-zА-Яа-яЁё ]/;
123
+ const oneKeyPress = !event.altKey && !event.metaKey && !event.ctrlKey;
124
+ const target = event.target as HTMLInputElement;
125
+
126
+ // Запрещаем вводить неразрешенные символы за исключением комбинаций клавиш
127
+ if (oneKeyPress && event.key.length === 1 && disallowedSymbols.test(event.key)) {
128
+ return event.preventDefault();
129
+ }
130
+
131
+ const val = target.value;
132
+
133
+ const hasSeparator = (val.match(createSeparatorsRegExp()) || []).length > 0;
134
+
135
+ // Запрещаем вводить второй сепаратор
136
+ if (hasSeparator && SEPARATORS.some((s) => s === event.key)) {
137
+ return event.preventDefault();
138
+ }
139
+
140
+ // Запрещаем вводить лишний знак
141
+ if (
142
+ (!allowSigns || SIGNS.some((s) => s === val[0])) &&
143
+ SIGNS.some((s) => s === event.key)
144
+ ) {
145
+ return event.preventDefault();
146
+ }
147
+
148
+ const selectionStart = target.selectionStart || 0;
149
+
150
+ // Запрещаем вводить цифры в дробную часть, если кол-во цифр больше fractionLength
151
+ if (
152
+ hasSeparator &&
153
+ fractionLength &&
154
+ event.key.length === 1 &&
155
+ selectionStart > val.indexOf(separator) &&
156
+ val.split(separator)[1].length >= fractionLength
157
+ ) {
158
+ return event.preventDefault();
159
+ }
160
+
161
+ return undefined;
162
+ };
163
+
164
+ const handleBlur = (event: FocusEvent<HTMLInputElement>) => {
165
+ const valueBlur = event.target.value.replace(new RegExp(`\\${separator}$`), '');
166
+
167
+ if (onChange) {
168
+ onChange(event, {
169
+ value: getNumberValueFromStr(valueBlur),
170
+ valueString: valueBlur,
171
+ });
172
+ }
173
+
174
+ if (uncontrolled) {
175
+ setValue(valueBlur);
176
+ }
177
+
178
+ if (onBlur) onBlur(event);
179
+ };
180
+
181
+ const visibleValue = uncontrolled ? value : propValue?.toString();
182
+
183
+ return (
184
+ <Input
185
+ ref={mergeRefs([ref, inputRef])}
186
+ value={visibleValue}
187
+ onBlur={handleBlur}
188
+ onChange={handleChange}
189
+ onKeyDown={handleKeyDown}
190
+ inputMode='decimal'
191
+ {...restProps}
192
+ />
193
+ );
194
+ },
195
+ );
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './Component';
package/src/utils.ts ADDED
@@ -0,0 +1,46 @@
1
+ export const SIGNS = ['-', '+'];
2
+ export const SEPARATORS = [',', '.'];
3
+
4
+ export function createSeparatorsRegExp() {
5
+ return new RegExp(`[${SEPARATORS.map((s) => `\\${s}`).join('')}]`, 'g');
6
+ }
7
+
8
+ const getNumberRegExp = (fractionLength?: number): RegExp => {
9
+ let reStr = '[0-9]+';
10
+
11
+ if (fractionLength !== 0) {
12
+ reStr = `${reStr}[${SEPARATORS.map((s) => `\\${s}`).join('')}]?[0-9]{0,${
13
+ fractionLength || Number.MAX_SAFE_INTEGER
14
+ }}`;
15
+ }
16
+
17
+ return new RegExp(`^${reStr}$`);
18
+ };
19
+
20
+ /**
21
+ * Проверка вводимых значений.
22
+ */
23
+ export const getAllowedValue = ({
24
+ value = '',
25
+ fractionLength,
26
+ separator,
27
+ allowSigns,
28
+ }: {
29
+ value: string;
30
+ fractionLength?: number;
31
+ separator: string;
32
+ allowSigns: boolean;
33
+ }): string => {
34
+ const sign = allowSigns && SIGNS.some((s) => s === value[0]) ? value[0] : '';
35
+ const testedValue = sign ? value.slice(1) : value;
36
+
37
+ if (getNumberRegExp(fractionLength).test(testedValue)) {
38
+ return value;
39
+ }
40
+
41
+ const [majorPart, minorPart] = testedValue
42
+ .split(separator)
43
+ .map((v) => v.replace(/[^0-9]/g, ''));
44
+
45
+ return `${sign}${majorPart}${minorPart ? separator + minorPart.slice(0, fractionLength) : ''}`;
46
+ };