@alfalab/core-components-number-input 1.0.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.d.ts +186 -0
- package/Component.js +164 -0
- package/cssm/Component.d.ts +186 -0
- package/cssm/Component.js +164 -0
- package/cssm/index.d.ts +1 -0
- package/cssm/index.js +10 -0
- package/esm/Component.d.ts +186 -0
- package/esm/Component.js +157 -0
- package/esm/index.d.ts +1 -0
- package/esm/index.js +4 -0
- package/index.d.ts +1 -0
- package/index.js +10 -0
- package/modern/Component.d.ts +186 -0
- package/modern/Component.js +116 -0
- package/modern/index.d.ts +1 -0
- package/modern/index.js +4 -0
- package/package.json +23 -0
- package/send-stats.js +82 -0
package/cssm/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Component";
|
package/cssm/index.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ChangeEvent, InputHTMLAttributes, MouseEvent, ReactNode } from "react";
|
|
4
|
+
type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type" | "value" | "defaultValue" | "onChange" | "onClick" | "onMouseDown" | "enterKeyHint"> & {
|
|
5
|
+
/**
|
|
6
|
+
* Значение поля ввода
|
|
7
|
+
*/
|
|
8
|
+
value?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Начальное значение поля
|
|
11
|
+
*/
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Растягивает компонент на ширину контейнера
|
|
15
|
+
*/
|
|
16
|
+
block?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Крестик для очистки поля
|
|
19
|
+
*/
|
|
20
|
+
clear?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Размер компонента
|
|
23
|
+
*/
|
|
24
|
+
size?: "s" | "m" | "l" | "xl";
|
|
25
|
+
/**
|
|
26
|
+
* Набор цветов для компонента
|
|
27
|
+
*/
|
|
28
|
+
colors?: "default" | "inverted";
|
|
29
|
+
/**
|
|
30
|
+
* Отображение ошибки
|
|
31
|
+
*/
|
|
32
|
+
error?: ReactNode | boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Отображение иконки успеха
|
|
35
|
+
*/
|
|
36
|
+
success?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Текст подсказки
|
|
39
|
+
*/
|
|
40
|
+
hint?: ReactNode;
|
|
41
|
+
/**
|
|
42
|
+
* Лейбл компонента
|
|
43
|
+
*/
|
|
44
|
+
label?: React.ReactNode;
|
|
45
|
+
/**
|
|
46
|
+
* Вид лейбла внутри / снаружи
|
|
47
|
+
*/
|
|
48
|
+
labelView?: "inner" | "outer";
|
|
49
|
+
/**
|
|
50
|
+
* Атрибут type
|
|
51
|
+
*/
|
|
52
|
+
type?: "number" | "card" | "email" | "money" | "password" | "tel" | "text";
|
|
53
|
+
/**
|
|
54
|
+
* Ref для обертки input
|
|
55
|
+
*/
|
|
56
|
+
wrapperRef?: React.Ref<HTMLDivElement>;
|
|
57
|
+
/**
|
|
58
|
+
* Слот слева
|
|
59
|
+
*/
|
|
60
|
+
leftAddons?: React.ReactNode;
|
|
61
|
+
/**
|
|
62
|
+
* Слот справа
|
|
63
|
+
*/
|
|
64
|
+
rightAddons?: React.ReactNode;
|
|
65
|
+
/**
|
|
66
|
+
* Слот под инпутом
|
|
67
|
+
*/
|
|
68
|
+
bottomAddons?: React.ReactNode;
|
|
69
|
+
/**
|
|
70
|
+
* Дополнительный класс
|
|
71
|
+
*/
|
|
72
|
+
className?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Дополнительный класс для поля
|
|
75
|
+
*/
|
|
76
|
+
fieldClassName?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Дополнительный класс инпута
|
|
79
|
+
*/
|
|
80
|
+
inputClassName?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Дополнительный класс для лейбла
|
|
83
|
+
*/
|
|
84
|
+
labelClassName?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Дополнительный класс для аддонов
|
|
87
|
+
*/
|
|
88
|
+
addonsClassName?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Класс, который будет установлен при фокусе
|
|
91
|
+
*/
|
|
92
|
+
focusedClassName?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Класс, который будет установлен, если в поле есть значение
|
|
95
|
+
*/
|
|
96
|
+
filledClassName?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Обработчик поля ввода
|
|
99
|
+
*/
|
|
100
|
+
onChange?: (event: ChangeEvent<HTMLInputElement>, payload: {
|
|
101
|
+
value: string;
|
|
102
|
+
}) => void;
|
|
103
|
+
/**
|
|
104
|
+
* Обработчик нажатия на кнопку очистки
|
|
105
|
+
*/
|
|
106
|
+
onClear?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
107
|
+
/**
|
|
108
|
+
* Обработчик клика по полю
|
|
109
|
+
*/
|
|
110
|
+
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
|
111
|
+
/**
|
|
112
|
+
* Обработчик MouseDown по полю
|
|
113
|
+
*/
|
|
114
|
+
onMouseDown?: (event: MouseEvent<HTMLDivElement>) => void;
|
|
115
|
+
/**
|
|
116
|
+
* Обработчик MouseUp по полю
|
|
117
|
+
*/
|
|
118
|
+
onMouseUp?: (event: MouseEvent<HTMLDivElement>) => void;
|
|
119
|
+
/**
|
|
120
|
+
* Идентификатор для систем автоматизированного тестирования
|
|
121
|
+
*/
|
|
122
|
+
dataTestId?: string;
|
|
123
|
+
};
|
|
124
|
+
type NumberInputProps = Omit<InputProps, 'value' | 'onChange' | 'type'> & {
|
|
125
|
+
/**
|
|
126
|
+
* Значение поля ввода
|
|
127
|
+
*/
|
|
128
|
+
value?: string | number | null;
|
|
129
|
+
/**
|
|
130
|
+
* Учитывать знаки '+' и '-'
|
|
131
|
+
*/
|
|
132
|
+
allowSigns?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Разделитель ',' или '.'
|
|
135
|
+
*/
|
|
136
|
+
separator?: '.' | ',';
|
|
137
|
+
/**
|
|
138
|
+
* Количество символов после разделителя
|
|
139
|
+
*/
|
|
140
|
+
fractionLength?: number;
|
|
141
|
+
/**
|
|
142
|
+
* Обработчик события изменения значения
|
|
143
|
+
*/
|
|
144
|
+
onChange?: (e: React.ChangeEvent<HTMLInputElement>, payload: {
|
|
145
|
+
/**
|
|
146
|
+
* Числовое значение инпута
|
|
147
|
+
*/
|
|
148
|
+
value: number | null;
|
|
149
|
+
/**
|
|
150
|
+
* Строковое значение инпута
|
|
151
|
+
*/
|
|
152
|
+
valueString: string;
|
|
153
|
+
}) => void;
|
|
154
|
+
};
|
|
155
|
+
declare const NumberInput: React.ForwardRefExoticComponent<Omit<InputProps, "type" | "onChange" | "value"> & {
|
|
156
|
+
/**
|
|
157
|
+
* Значение поля ввода
|
|
158
|
+
*/
|
|
159
|
+
value?: string | number | null | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* Учитывать знаки '+' и '-'
|
|
162
|
+
*/
|
|
163
|
+
allowSigns?: boolean | undefined;
|
|
164
|
+
/**
|
|
165
|
+
* Разделитель ',' или '.'
|
|
166
|
+
*/
|
|
167
|
+
separator?: "." | "," | undefined;
|
|
168
|
+
/**
|
|
169
|
+
* Количество символов после разделителя
|
|
170
|
+
*/
|
|
171
|
+
fractionLength?: number | undefined;
|
|
172
|
+
/**
|
|
173
|
+
* Обработчик события изменения значения
|
|
174
|
+
*/
|
|
175
|
+
onChange?: ((e: React.ChangeEvent<HTMLInputElement>, payload: {
|
|
176
|
+
/**
|
|
177
|
+
* Числовое значение инпута
|
|
178
|
+
*/
|
|
179
|
+
value: number | null;
|
|
180
|
+
/**
|
|
181
|
+
* Строковое значение инпута
|
|
182
|
+
*/
|
|
183
|
+
valueString: string;
|
|
184
|
+
}) => void) | undefined;
|
|
185
|
+
} & React.RefAttributes<HTMLInputElement>>;
|
|
186
|
+
export { NumberInputProps, NumberInput };
|
package/esm/Component.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
|
+
import mergeRefs from 'react-merge-refs';
|
|
3
|
+
import { MaskedInput } from '@alfalab/core-components-masked-input/esm';
|
|
4
|
+
|
|
5
|
+
/******************************************************************************
|
|
6
|
+
Copyright (c) Microsoft Corporation.
|
|
7
|
+
|
|
8
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
|
9
|
+
purpose with or without fee is hereby granted.
|
|
10
|
+
|
|
11
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
12
|
+
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
13
|
+
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
14
|
+
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
15
|
+
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
16
|
+
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
17
|
+
PERFORMANCE OF THIS SOFTWARE.
|
|
18
|
+
***************************************************************************** */
|
|
19
|
+
var __assign = function () {
|
|
20
|
+
__assign = Object.assign || function __assign(t) {
|
|
21
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
22
|
+
s = arguments[i];
|
|
23
|
+
for (var p in s)
|
|
24
|
+
if (Object.prototype.hasOwnProperty.call(s, p))
|
|
25
|
+
t[p] = s[p];
|
|
26
|
+
}
|
|
27
|
+
return t;
|
|
28
|
+
};
|
|
29
|
+
return __assign.apply(this, arguments);
|
|
30
|
+
};
|
|
31
|
+
function __rest(s, e) {
|
|
32
|
+
var t = {};
|
|
33
|
+
for (var p in s)
|
|
34
|
+
if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
35
|
+
t[p] = s[p];
|
|
36
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
37
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
38
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
39
|
+
t[p[i]] = s[p[i]];
|
|
40
|
+
}
|
|
41
|
+
return t;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
var NumberInput = React.forwardRef(function (_a, ref) {
|
|
45
|
+
var propValue = _a.value, onChange = _a.onChange, onBlur = _a.onBlur, _b = _a.allowSigns, allowSigns = _b === void 0 ? true : _b, _c = _a.separator, separator = _c === void 0 ? ',' : _c, fractionLength = _a.fractionLength, defaultValue = _a.defaultValue, restProps = __rest(_a, ["value", "onChange", "onBlur", "allowSigns", "separator", "fractionLength", "defaultValue"]);
|
|
46
|
+
var uncontrolled = propValue === undefined;
|
|
47
|
+
var inputRef = useRef(null);
|
|
48
|
+
var _d = useState(defaultValue || ''), value = _d[0], setValue = _d[1];
|
|
49
|
+
var getNumberValueFromStr = function (valueString) {
|
|
50
|
+
if (valueString.includes(',')) {
|
|
51
|
+
return parseFloat(valueString.replace(',', '.'));
|
|
52
|
+
}
|
|
53
|
+
return parseFloat(valueString);
|
|
54
|
+
};
|
|
55
|
+
var handleChange = function (event, payload) {
|
|
56
|
+
var input = event.target;
|
|
57
|
+
var positionСursor = input.selectionStart || 0;
|
|
58
|
+
if (input.value[positionСursor] === separator) {
|
|
59
|
+
var index = input.selectionStart + 1;
|
|
60
|
+
input.selectionStart = index;
|
|
61
|
+
}
|
|
62
|
+
if (onChange) {
|
|
63
|
+
onChange(event, {
|
|
64
|
+
value: getNumberValueFromStr(input.value),
|
|
65
|
+
valueString: input.value,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (uncontrolled) {
|
|
69
|
+
setValue(payload.value);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var formatSeparatorValue = function (separator, rawValue) {
|
|
73
|
+
var singsRegexp = /\W+/;
|
|
74
|
+
var sings = singsRegexp.exec(rawValue);
|
|
75
|
+
if (sings && sings[0] !== ',' && sings[0] !== '.') {
|
|
76
|
+
return rawValue.replace(sings[0], '');
|
|
77
|
+
}
|
|
78
|
+
var separatorRegexp = /\.|,/;
|
|
79
|
+
if (rawValue.length === 1 && separatorRegexp.test(rawValue))
|
|
80
|
+
return '';
|
|
81
|
+
if (separator === ',') {
|
|
82
|
+
return rawValue.replace('.', ',');
|
|
83
|
+
}
|
|
84
|
+
if (separator === '.') {
|
|
85
|
+
return rawValue.replace(',', '.');
|
|
86
|
+
}
|
|
87
|
+
return rawValue;
|
|
88
|
+
};
|
|
89
|
+
var handleBeforeDisplay = function (conformedValue, _a) {
|
|
90
|
+
var rawValue = _a.rawValue, previousConformedValue = _a.previousConformedValue;
|
|
91
|
+
var isTwoSign = rawValue.length - conformedValue.length >= 1;
|
|
92
|
+
var isOneSign = rawValue.length - conformedValue.length === 1;
|
|
93
|
+
var signRegexp = /\.|,/;
|
|
94
|
+
if (fractionLength === 0 && signRegexp.test(rawValue)) {
|
|
95
|
+
return previousConformedValue;
|
|
96
|
+
}
|
|
97
|
+
var zeroRegexp = /^[+|-]?00+$/;
|
|
98
|
+
if (zeroRegexp.test(rawValue)) {
|
|
99
|
+
var index = rawValue.search(/[+|-]/) !== -1 ? 2 : 1;
|
|
100
|
+
return rawValue.slice(0, index);
|
|
101
|
+
}
|
|
102
|
+
if (isTwoSign && previousConformedValue) {
|
|
103
|
+
if (rawValue.length !== conformedValue.length && fractionLength) {
|
|
104
|
+
var valueLength = conformedValue.length - fractionLength;
|
|
105
|
+
var sign = separator === ',' ? '.' : ',';
|
|
106
|
+
if (rawValue.indexOf(sign) !== valueLength &&
|
|
107
|
+
rawValue.indexOf(sign) !== conformedValue.length) {
|
|
108
|
+
return previousConformedValue;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
var hasSign = conformedValue.includes(separator);
|
|
112
|
+
if (!hasSign && isOneSign) {
|
|
113
|
+
return formatSeparatorValue(separator, rawValue);
|
|
114
|
+
}
|
|
115
|
+
return previousConformedValue;
|
|
116
|
+
}
|
|
117
|
+
if (isOneSign && !uncontrolled) {
|
|
118
|
+
return formatSeparatorValue(separator, rawValue);
|
|
119
|
+
}
|
|
120
|
+
return conformedValue;
|
|
121
|
+
};
|
|
122
|
+
var handleBlur = function (event) {
|
|
123
|
+
var valueBlur = event.target.value.replace(new RegExp("\\".concat(separator, "$")), '');
|
|
124
|
+
if (onChange) {
|
|
125
|
+
onChange(event, {
|
|
126
|
+
value: getNumberValueFromStr(valueBlur),
|
|
127
|
+
valueString: valueBlur,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (uncontrolled) {
|
|
131
|
+
setValue(valueBlur);
|
|
132
|
+
}
|
|
133
|
+
if (onBlur)
|
|
134
|
+
onBlur(event);
|
|
135
|
+
};
|
|
136
|
+
var handleMask = function (valueInput) {
|
|
137
|
+
var mask = allowSigns ? [/[+-\d]/] : [/\d/];
|
|
138
|
+
if (valueInput.length <= 1)
|
|
139
|
+
return mask;
|
|
140
|
+
mask.push.apply(mask, Array(valueInput.length - 1).fill(/\d/));
|
|
141
|
+
var dotIndex = valueInput.indexOf(separator);
|
|
142
|
+
var signRegexp = new RegExp("[\\d".concat(separator, "]"));
|
|
143
|
+
var startWithDigit = signRegexp.test(valueInput[0]);
|
|
144
|
+
if (dotIndex > (startWithDigit ? 0 : 1)) {
|
|
145
|
+
mask[dotIndex] = signRegexp;
|
|
146
|
+
}
|
|
147
|
+
if (fractionLength && dotIndex !== -1) {
|
|
148
|
+
var endMaskLength = dotIndex + fractionLength + 1;
|
|
149
|
+
mask = mask.slice(0, endMaskLength);
|
|
150
|
+
}
|
|
151
|
+
return mask;
|
|
152
|
+
};
|
|
153
|
+
var visibleValue = uncontrolled ? value : propValue === null || propValue === void 0 ? void 0 : propValue.toString();
|
|
154
|
+
return (React.createElement(MaskedInput, __assign({ ref: mergeRefs([ref, inputRef]), mask: handleMask, value: visibleValue, onBlur: handleBlur, onChange: handleChange, inputMode: 'decimal', onBeforeDisplay: handleBeforeDisplay }, restProps)));
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
export { NumberInput };
|
package/esm/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Component";
|
package/esm/index.js
ADDED
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Component";
|
package/index.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { ChangeEvent, InputHTMLAttributes, MouseEvent, ReactNode } from "react";
|
|
4
|
+
type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type" | "value" | "defaultValue" | "onChange" | "onClick" | "onMouseDown" | "enterKeyHint"> & {
|
|
5
|
+
/**
|
|
6
|
+
* Значение поля ввода
|
|
7
|
+
*/
|
|
8
|
+
value?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Начальное значение поля
|
|
11
|
+
*/
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Растягивает компонент на ширину контейнера
|
|
15
|
+
*/
|
|
16
|
+
block?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Крестик для очистки поля
|
|
19
|
+
*/
|
|
20
|
+
clear?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Размер компонента
|
|
23
|
+
*/
|
|
24
|
+
size?: "s" | "m" | "l" | "xl";
|
|
25
|
+
/**
|
|
26
|
+
* Набор цветов для компонента
|
|
27
|
+
*/
|
|
28
|
+
colors?: "default" | "inverted";
|
|
29
|
+
/**
|
|
30
|
+
* Отображение ошибки
|
|
31
|
+
*/
|
|
32
|
+
error?: ReactNode | boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Отображение иконки успеха
|
|
35
|
+
*/
|
|
36
|
+
success?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Текст подсказки
|
|
39
|
+
*/
|
|
40
|
+
hint?: ReactNode;
|
|
41
|
+
/**
|
|
42
|
+
* Лейбл компонента
|
|
43
|
+
*/
|
|
44
|
+
label?: React.ReactNode;
|
|
45
|
+
/**
|
|
46
|
+
* Вид лейбла внутри / снаружи
|
|
47
|
+
*/
|
|
48
|
+
labelView?: "inner" | "outer";
|
|
49
|
+
/**
|
|
50
|
+
* Атрибут type
|
|
51
|
+
*/
|
|
52
|
+
type?: "number" | "card" | "email" | "money" | "password" | "tel" | "text";
|
|
53
|
+
/**
|
|
54
|
+
* Ref для обертки input
|
|
55
|
+
*/
|
|
56
|
+
wrapperRef?: React.Ref<HTMLDivElement>;
|
|
57
|
+
/**
|
|
58
|
+
* Слот слева
|
|
59
|
+
*/
|
|
60
|
+
leftAddons?: React.ReactNode;
|
|
61
|
+
/**
|
|
62
|
+
* Слот справа
|
|
63
|
+
*/
|
|
64
|
+
rightAddons?: React.ReactNode;
|
|
65
|
+
/**
|
|
66
|
+
* Слот под инпутом
|
|
67
|
+
*/
|
|
68
|
+
bottomAddons?: React.ReactNode;
|
|
69
|
+
/**
|
|
70
|
+
* Дополнительный класс
|
|
71
|
+
*/
|
|
72
|
+
className?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Дополнительный класс для поля
|
|
75
|
+
*/
|
|
76
|
+
fieldClassName?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Дополнительный класс инпута
|
|
79
|
+
*/
|
|
80
|
+
inputClassName?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Дополнительный класс для лейбла
|
|
83
|
+
*/
|
|
84
|
+
labelClassName?: string;
|
|
85
|
+
/**
|
|
86
|
+
* Дополнительный класс для аддонов
|
|
87
|
+
*/
|
|
88
|
+
addonsClassName?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Класс, который будет установлен при фокусе
|
|
91
|
+
*/
|
|
92
|
+
focusedClassName?: string;
|
|
93
|
+
/**
|
|
94
|
+
* Класс, который будет установлен, если в поле есть значение
|
|
95
|
+
*/
|
|
96
|
+
filledClassName?: string;
|
|
97
|
+
/**
|
|
98
|
+
* Обработчик поля ввода
|
|
99
|
+
*/
|
|
100
|
+
onChange?: (event: ChangeEvent<HTMLInputElement>, payload: {
|
|
101
|
+
value: string;
|
|
102
|
+
}) => void;
|
|
103
|
+
/**
|
|
104
|
+
* Обработчик нажатия на кнопку очистки
|
|
105
|
+
*/
|
|
106
|
+
onClear?: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
107
|
+
/**
|
|
108
|
+
* Обработчик клика по полю
|
|
109
|
+
*/
|
|
110
|
+
onClick?: (event: MouseEvent<HTMLDivElement>) => void;
|
|
111
|
+
/**
|
|
112
|
+
* Обработчик MouseDown по полю
|
|
113
|
+
*/
|
|
114
|
+
onMouseDown?: (event: MouseEvent<HTMLDivElement>) => void;
|
|
115
|
+
/**
|
|
116
|
+
* Обработчик MouseUp по полю
|
|
117
|
+
*/
|
|
118
|
+
onMouseUp?: (event: MouseEvent<HTMLDivElement>) => void;
|
|
119
|
+
/**
|
|
120
|
+
* Идентификатор для систем автоматизированного тестирования
|
|
121
|
+
*/
|
|
122
|
+
dataTestId?: string;
|
|
123
|
+
};
|
|
124
|
+
type NumberInputProps = Omit<InputProps, 'value' | 'onChange' | 'type'> & {
|
|
125
|
+
/**
|
|
126
|
+
* Значение поля ввода
|
|
127
|
+
*/
|
|
128
|
+
value?: string | number | null;
|
|
129
|
+
/**
|
|
130
|
+
* Учитывать знаки '+' и '-'
|
|
131
|
+
*/
|
|
132
|
+
allowSigns?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Разделитель ',' или '.'
|
|
135
|
+
*/
|
|
136
|
+
separator?: '.' | ',';
|
|
137
|
+
/**
|
|
138
|
+
* Количество символов после разделителя
|
|
139
|
+
*/
|
|
140
|
+
fractionLength?: number;
|
|
141
|
+
/**
|
|
142
|
+
* Обработчик события изменения значения
|
|
143
|
+
*/
|
|
144
|
+
onChange?: (e: React.ChangeEvent<HTMLInputElement>, payload: {
|
|
145
|
+
/**
|
|
146
|
+
* Числовое значение инпута
|
|
147
|
+
*/
|
|
148
|
+
value: number | null;
|
|
149
|
+
/**
|
|
150
|
+
* Строковое значение инпута
|
|
151
|
+
*/
|
|
152
|
+
valueString: string;
|
|
153
|
+
}) => void;
|
|
154
|
+
};
|
|
155
|
+
declare const NumberInput: React.ForwardRefExoticComponent<Omit<InputProps, "type" | "onChange" | "value"> & {
|
|
156
|
+
/**
|
|
157
|
+
* Значение поля ввода
|
|
158
|
+
*/
|
|
159
|
+
value?: string | number | null | undefined;
|
|
160
|
+
/**
|
|
161
|
+
* Учитывать знаки '+' и '-'
|
|
162
|
+
*/
|
|
163
|
+
allowSigns?: boolean | undefined;
|
|
164
|
+
/**
|
|
165
|
+
* Разделитель ',' или '.'
|
|
166
|
+
*/
|
|
167
|
+
separator?: "." | "," | undefined;
|
|
168
|
+
/**
|
|
169
|
+
* Количество символов после разделителя
|
|
170
|
+
*/
|
|
171
|
+
fractionLength?: number | undefined;
|
|
172
|
+
/**
|
|
173
|
+
* Обработчик события изменения значения
|
|
174
|
+
*/
|
|
175
|
+
onChange?: ((e: React.ChangeEvent<HTMLInputElement>, payload: {
|
|
176
|
+
/**
|
|
177
|
+
* Числовое значение инпута
|
|
178
|
+
*/
|
|
179
|
+
value: number | null;
|
|
180
|
+
/**
|
|
181
|
+
* Строковое значение инпута
|
|
182
|
+
*/
|
|
183
|
+
valueString: string;
|
|
184
|
+
}) => void) | undefined;
|
|
185
|
+
} & React.RefAttributes<HTMLInputElement>>;
|
|
186
|
+
export { NumberInputProps, NumberInput };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import React, { useRef, useState } from 'react';
|
|
2
|
+
import mergeRefs from 'react-merge-refs';
|
|
3
|
+
import { MaskedInput } from '@alfalab/core-components-masked-input/modern';
|
|
4
|
+
|
|
5
|
+
const NumberInput = React.forwardRef(({ value: propValue, onChange, onBlur, allowSigns = true, separator = ',', fractionLength, defaultValue, ...restProps }, ref) => {
|
|
6
|
+
const uncontrolled = propValue === undefined;
|
|
7
|
+
const inputRef = useRef(null);
|
|
8
|
+
const [value, setValue] = useState(defaultValue || '');
|
|
9
|
+
const getNumberValueFromStr = (valueString) => {
|
|
10
|
+
if (valueString.includes(',')) {
|
|
11
|
+
return parseFloat(valueString.replace(',', '.'));
|
|
12
|
+
}
|
|
13
|
+
return parseFloat(valueString);
|
|
14
|
+
};
|
|
15
|
+
const handleChange = (event, payload) => {
|
|
16
|
+
const input = event.target;
|
|
17
|
+
const positionСursor = input.selectionStart || 0;
|
|
18
|
+
if (input.value[positionСursor] === separator) {
|
|
19
|
+
const index = input.selectionStart + 1;
|
|
20
|
+
input.selectionStart = index;
|
|
21
|
+
}
|
|
22
|
+
if (onChange) {
|
|
23
|
+
onChange(event, {
|
|
24
|
+
value: getNumberValueFromStr(input.value),
|
|
25
|
+
valueString: input.value,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (uncontrolled) {
|
|
29
|
+
setValue(payload.value);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const formatSeparatorValue = (separator, rawValue) => {
|
|
33
|
+
const singsRegexp = /\W+/;
|
|
34
|
+
const sings = singsRegexp.exec(rawValue);
|
|
35
|
+
if (sings && sings[0] !== ',' && sings[0] !== '.') {
|
|
36
|
+
return rawValue.replace(sings[0], '');
|
|
37
|
+
}
|
|
38
|
+
const separatorRegexp = /\.|,/;
|
|
39
|
+
if (rawValue.length === 1 && separatorRegexp.test(rawValue))
|
|
40
|
+
return '';
|
|
41
|
+
if (separator === ',') {
|
|
42
|
+
return rawValue.replace('.', ',');
|
|
43
|
+
}
|
|
44
|
+
if (separator === '.') {
|
|
45
|
+
return rawValue.replace(',', '.');
|
|
46
|
+
}
|
|
47
|
+
return rawValue;
|
|
48
|
+
};
|
|
49
|
+
const handleBeforeDisplay = (conformedValue, { rawValue, previousConformedValue }) => {
|
|
50
|
+
const isTwoSign = rawValue.length - conformedValue.length >= 1;
|
|
51
|
+
const isOneSign = rawValue.length - conformedValue.length === 1;
|
|
52
|
+
const signRegexp = /\.|,/;
|
|
53
|
+
if (fractionLength === 0 && signRegexp.test(rawValue)) {
|
|
54
|
+
return previousConformedValue;
|
|
55
|
+
}
|
|
56
|
+
const zeroRegexp = /^[+|-]?00+$/;
|
|
57
|
+
if (zeroRegexp.test(rawValue)) {
|
|
58
|
+
const index = rawValue.search(/[+|-]/) !== -1 ? 2 : 1;
|
|
59
|
+
return rawValue.slice(0, index);
|
|
60
|
+
}
|
|
61
|
+
if (isTwoSign && previousConformedValue) {
|
|
62
|
+
if (rawValue.length !== conformedValue.length && fractionLength) {
|
|
63
|
+
const valueLength = conformedValue.length - fractionLength;
|
|
64
|
+
const sign = separator === ',' ? '.' : ',';
|
|
65
|
+
if (rawValue.indexOf(sign) !== valueLength &&
|
|
66
|
+
rawValue.indexOf(sign) !== conformedValue.length) {
|
|
67
|
+
return previousConformedValue;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const hasSign = conformedValue.includes(separator);
|
|
71
|
+
if (!hasSign && isOneSign) {
|
|
72
|
+
return formatSeparatorValue(separator, rawValue);
|
|
73
|
+
}
|
|
74
|
+
return previousConformedValue;
|
|
75
|
+
}
|
|
76
|
+
if (isOneSign && !uncontrolled) {
|
|
77
|
+
return formatSeparatorValue(separator, rawValue);
|
|
78
|
+
}
|
|
79
|
+
return conformedValue;
|
|
80
|
+
};
|
|
81
|
+
const handleBlur = (event) => {
|
|
82
|
+
const valueBlur = event.target.value.replace(new RegExp(`\\${separator}$`), '');
|
|
83
|
+
if (onChange) {
|
|
84
|
+
onChange(event, {
|
|
85
|
+
value: getNumberValueFromStr(valueBlur),
|
|
86
|
+
valueString: valueBlur,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (uncontrolled) {
|
|
90
|
+
setValue(valueBlur);
|
|
91
|
+
}
|
|
92
|
+
if (onBlur)
|
|
93
|
+
onBlur(event);
|
|
94
|
+
};
|
|
95
|
+
const handleMask = (valueInput) => {
|
|
96
|
+
let mask = allowSigns ? [/[+-\d]/] : [/\d/];
|
|
97
|
+
if (valueInput.length <= 1)
|
|
98
|
+
return mask;
|
|
99
|
+
mask.push(...Array(valueInput.length - 1).fill(/\d/));
|
|
100
|
+
const dotIndex = valueInput.indexOf(separator);
|
|
101
|
+
const signRegexp = new RegExp(`[\\d${separator}]`);
|
|
102
|
+
const startWithDigit = signRegexp.test(valueInput[0]);
|
|
103
|
+
if (dotIndex > (startWithDigit ? 0 : 1)) {
|
|
104
|
+
mask[dotIndex] = signRegexp;
|
|
105
|
+
}
|
|
106
|
+
if (fractionLength && dotIndex !== -1) {
|
|
107
|
+
const endMaskLength = dotIndex + fractionLength + 1;
|
|
108
|
+
mask = mask.slice(0, endMaskLength);
|
|
109
|
+
}
|
|
110
|
+
return mask;
|
|
111
|
+
};
|
|
112
|
+
const visibleValue = uncontrolled ? value : propValue?.toString();
|
|
113
|
+
return (React.createElement(MaskedInput, { ref: mergeRefs([ref, inputRef]), mask: handleMask, value: visibleValue, onBlur: handleBlur, onChange: handleChange, inputMode: 'decimal', onBeforeDisplay: handleBeforeDisplay, ...restProps }));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
export { NumberInput };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./Component";
|
package/modern/index.js
ADDED