@alfalab/core-components-number-input 1.3.0 → 2.0.1
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.desktop.d.ts +1 -1
- package/Component.desktop.js +9 -1
- package/Component.mobile.d.ts +1 -1
- package/Component.mobile.js +9 -1
- package/Component.responsive.d.ts +1 -1
- package/Component.responsive.js +14 -1
- package/components/number-input/Component.d.ts +21 -46
- package/components/number-input/Component.js +97 -84
- package/components/number-input/default.css +24 -0
- package/components/number-input/index.css +23 -0
- package/components/number-input/index.js +8 -0
- package/components/number-input/inverted.css +24 -0
- package/components/steppers/Component.d.ts +15 -0
- package/components/steppers/Component.js +43 -0
- package/components/steppers/default.css +20 -0
- package/components/steppers/index.css +30 -0
- package/components/steppers/index.d.ts +1 -0
- package/components/steppers/index.js +15 -0
- package/components/steppers/inverted.css +20 -0
- package/cssm/Component.desktop.d.ts +1 -1
- package/cssm/Component.desktop.js +15 -1
- package/cssm/Component.mobile.d.ts +1 -1
- package/cssm/Component.mobile.js +15 -1
- package/cssm/Component.responsive.d.ts +1 -1
- package/cssm/Component.responsive.js +20 -1
- package/cssm/components/number-input/Component.d.ts +21 -46
- package/cssm/components/number-input/Component.js +97 -84
- package/cssm/components/number-input/default.module.css +23 -0
- package/cssm/components/number-input/index.js +14 -0
- package/cssm/components/number-input/index.module.css +22 -0
- package/cssm/components/number-input/inverted.module.css +23 -0
- package/cssm/components/steppers/Component.d.ts +15 -0
- package/cssm/components/steppers/Component.js +40 -0
- package/cssm/components/steppers/default.module.css +19 -0
- package/cssm/components/steppers/index.d.ts +1 -0
- package/cssm/components/steppers/index.js +18 -0
- package/cssm/components/steppers/index.module.css +29 -0
- package/cssm/components/steppers/inverted.module.css +19 -0
- package/cssm/desktop/index.js +14 -0
- package/cssm/index.js +15 -0
- package/cssm/mobile/index.js +14 -0
- package/cssm/utils.d.ts +19 -9
- package/cssm/utils.js +244 -21
- package/desktop/index.js +8 -0
- package/esm/Component.desktop.d.ts +1 -1
- package/esm/Component.desktop.js +9 -1
- package/esm/Component.mobile.d.ts +1 -1
- package/esm/Component.mobile.js +9 -1
- package/esm/Component.responsive.d.ts +1 -1
- package/esm/Component.responsive.js +15 -2
- package/esm/components/number-input/Component.d.ts +21 -46
- package/esm/components/number-input/Component.js +98 -86
- package/esm/components/number-input/default.css +24 -0
- package/esm/components/number-input/index.css +23 -0
- package/esm/components/number-input/index.js +8 -0
- package/esm/components/number-input/inverted.css +24 -0
- package/esm/components/steppers/Component.d.ts +15 -0
- package/esm/components/steppers/Component.js +34 -0
- package/esm/components/steppers/default.css +20 -0
- package/esm/components/steppers/index.css +30 -0
- package/esm/components/steppers/index.d.ts +1 -0
- package/esm/components/steppers/index.js +7 -0
- package/esm/components/steppers/inverted.css +20 -0
- package/esm/desktop/index.js +8 -0
- package/esm/index.js +9 -0
- package/esm/mobile/index.js +8 -0
- package/esm/utils.d.ts +19 -9
- package/esm/utils.js +236 -19
- package/index.js +9 -0
- package/mobile/index.js +8 -0
- package/modern/Component.desktop.d.ts +1 -1
- package/modern/Component.desktop.js +9 -1
- package/modern/Component.mobile.d.ts +1 -1
- package/modern/Component.mobile.js +9 -1
- package/modern/Component.responsive.d.ts +1 -1
- package/modern/Component.responsive.js +13 -1
- package/modern/components/number-input/Component.d.ts +21 -46
- package/modern/components/number-input/Component.js +94 -86
- package/modern/components/number-input/default.css +24 -0
- package/modern/components/number-input/index.css +23 -0
- package/modern/components/number-input/index.js +8 -0
- package/modern/components/number-input/inverted.css +24 -0
- package/modern/components/steppers/Component.d.ts +15 -0
- package/modern/components/steppers/Component.js +33 -0
- package/modern/components/steppers/default.css +20 -0
- package/modern/components/steppers/index.css +30 -0
- package/modern/components/steppers/index.d.ts +1 -0
- package/modern/components/steppers/index.js +7 -0
- package/modern/components/steppers/inverted.css +20 -0
- package/modern/desktop/index.js +8 -0
- package/modern/index.js +9 -0
- package/modern/mobile/index.js +8 -0
- package/modern/utils.d.ts +19 -9
- package/modern/utils.js +223 -18
- package/package.json +9 -2
- package/src/Component.desktop.tsx +2 -2
- package/src/Component.mobile.tsx +2 -2
- package/src/Component.responsive.tsx +16 -2
- package/src/components/number-input/Component.tsx +204 -128
- package/src/components/number-input/default.module.css +10 -0
- package/src/components/number-input/index.module.css +9 -0
- package/src/components/number-input/inverted.module.css +10 -0
- package/src/components/steppers/Component.tsx +75 -0
- package/src/components/steppers/default.module.css +5 -0
- package/src/components/steppers/index.module.css +20 -0
- package/src/components/steppers/index.ts +1 -0
- package/src/components/steppers/inverted.module.css +5 -0
- package/src/utils.ts +302 -24
- package/utils.d.ts +19 -9
- package/utils.js +244 -21
package/src/utils.ts
CHANGED
|
@@ -1,46 +1,324 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable no-param-reassign */
|
|
2
|
+
import {
|
|
3
|
+
MaskitoOptions,
|
|
4
|
+
MaskitoPlugin,
|
|
5
|
+
MaskitoPostprocessor,
|
|
6
|
+
MaskitoPreprocessor,
|
|
7
|
+
maskitoTransform,
|
|
8
|
+
} from '@maskito/core';
|
|
9
|
+
|
|
10
|
+
import { fnUtils } from '@alfalab/core-components-shared';
|
|
11
|
+
|
|
12
|
+
export const MINUS_SIGN = '-';
|
|
2
13
|
export const SEPARATORS = [',', '.'];
|
|
14
|
+
export const MAX_SAFE_INTEGER = 2 ** 53 - 1;
|
|
15
|
+
export const MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;
|
|
16
|
+
export const MAX_DIGITS = 15; // с 16 уже упираемся в MAX_SAFE_INTEGER
|
|
17
|
+
|
|
18
|
+
export function parseNumber(value: string | number | null = '') {
|
|
19
|
+
if (typeof value === 'number') return value;
|
|
20
|
+
|
|
21
|
+
const pseudoSeparatorsRegExp = new RegExp(`[${SEPARATORS.join('')}]`, 'gi');
|
|
22
|
+
|
|
23
|
+
return value
|
|
24
|
+
? parseFloat(
|
|
25
|
+
value
|
|
26
|
+
.replace(new RegExp(`[^${MINUS_SIGN}${SEPARATORS.join('')}0-9]`, 'gi'), '')
|
|
27
|
+
.replace(pseudoSeparatorsRegExp, '.'),
|
|
28
|
+
)
|
|
29
|
+
: NaN;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Преобразовать число в строку с заменой экспоненты на десятичную дробь
|
|
34
|
+
*/
|
|
35
|
+
export function stringifyNumberWithoutExp(value: number): string {
|
|
36
|
+
const valueString = String(value);
|
|
37
|
+
const [numberPart, expPart] = valueString.split('e-');
|
|
38
|
+
|
|
39
|
+
let valueWithoutExp = valueString;
|
|
3
40
|
|
|
4
|
-
|
|
5
|
-
|
|
41
|
+
if (expPart) {
|
|
42
|
+
const [, fractionalPart] = numberPart.split('.');
|
|
43
|
+
const decimalDigits = Number(expPart) + (fractionalPart?.length || 0);
|
|
44
|
+
|
|
45
|
+
valueWithoutExp = value.toFixed(decimalDigits);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return valueWithoutExp;
|
|
6
49
|
}
|
|
7
50
|
|
|
8
|
-
const getNumberRegExp = (fractionLength
|
|
9
|
-
let reStr = '[0-9]
|
|
51
|
+
const getNumberRegExp = (min: number, fractionLength: number): RegExp => {
|
|
52
|
+
let reStr = '[0-9]*';
|
|
53
|
+
|
|
54
|
+
if (min < 0) {
|
|
55
|
+
reStr = `(\\${MINUS_SIGN})?${reStr}`;
|
|
56
|
+
}
|
|
10
57
|
|
|
11
58
|
if (fractionLength !== 0) {
|
|
12
59
|
reStr = `${reStr}[${SEPARATORS.map((s) => `\\${s}`).join('')}]?[0-9]{0,${
|
|
13
|
-
fractionLength ||
|
|
60
|
+
fractionLength || MAX_DIGITS
|
|
14
61
|
}}`;
|
|
15
62
|
}
|
|
16
63
|
|
|
17
64
|
return new RegExp(`^${reStr}$`);
|
|
18
65
|
};
|
|
19
66
|
|
|
67
|
+
export function createMaskOptions({
|
|
68
|
+
separator,
|
|
69
|
+
fractionLength,
|
|
70
|
+
min,
|
|
71
|
+
max,
|
|
72
|
+
}: {
|
|
73
|
+
separator: string;
|
|
74
|
+
fractionLength: number;
|
|
75
|
+
min: number;
|
|
76
|
+
max: number;
|
|
77
|
+
}): MaskitoOptions {
|
|
78
|
+
return {
|
|
79
|
+
mask: getNumberRegExp(min, fractionLength),
|
|
80
|
+
preprocessors: [
|
|
81
|
+
createPseudoSeparatorPreprocessor(separator),
|
|
82
|
+
createNotEmptyIntegerPartPreprocessor({ separator, fractionLength }),
|
|
83
|
+
createZeroFractionLengthPreprocessor(fractionLength, separator),
|
|
84
|
+
createRepeatedSeparatorPreprocessor(separator),
|
|
85
|
+
],
|
|
86
|
+
postprocessors: [
|
|
87
|
+
createLeadingZeroesValidationPostprocessor(separator),
|
|
88
|
+
createMinMaxPostprocessor({ min, max, separator }),
|
|
89
|
+
],
|
|
90
|
+
plugins: [createNotEmptyPartsPlugin(separator), createMinMaxPlugin({ min, max })],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
20
94
|
/**
|
|
21
|
-
*
|
|
95
|
+
* Заполняет целочисленную часть при вводе separator.
|
|
96
|
+
* @example Type , => 0,
|
|
22
97
|
*/
|
|
23
|
-
|
|
24
|
-
value = '',
|
|
25
|
-
fractionLength,
|
|
98
|
+
function createNotEmptyIntegerPartPreprocessor({
|
|
26
99
|
separator,
|
|
27
|
-
|
|
100
|
+
fractionLength,
|
|
28
101
|
}: {
|
|
29
|
-
value: string;
|
|
30
|
-
fractionLength?: number;
|
|
31
102
|
separator: string;
|
|
32
|
-
|
|
33
|
-
}):
|
|
34
|
-
const
|
|
35
|
-
|
|
103
|
+
fractionLength: number;
|
|
104
|
+
}): MaskitoPreprocessor {
|
|
105
|
+
const startWithDecimalSepRegExp = new RegExp(`^\\D*\\${separator}`);
|
|
106
|
+
|
|
107
|
+
return ({ elementState, data }) => {
|
|
108
|
+
const { value, selection } = elementState;
|
|
109
|
+
const [from] = selection;
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
fractionLength <= 0 ||
|
|
113
|
+
value.includes(separator) ||
|
|
114
|
+
!data.match(startWithDecimalSepRegExp)
|
|
115
|
+
) {
|
|
116
|
+
return { elementState, data };
|
|
117
|
+
}
|
|
36
118
|
|
|
37
|
-
|
|
38
|
-
|
|
119
|
+
const digitsBeforeCursor = value.slice(0, from).match(/\d+/);
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
elementState,
|
|
123
|
+
data: digitsBeforeCursor ? data : `0${data}`,
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Не позволяет вводить невалидный разделитель.
|
|
130
|
+
*/
|
|
131
|
+
function createPseudoSeparatorPreprocessor(separator: string): MaskitoPreprocessor {
|
|
132
|
+
const pseudoSeparatorsRegExp = new RegExp(`[${SEPARATORS.join('')}]`, 'gi');
|
|
133
|
+
|
|
134
|
+
return ({ elementState, data }) => {
|
|
135
|
+
const { value, selection } = elementState;
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
elementState: {
|
|
139
|
+
selection,
|
|
140
|
+
value: value.replace(pseudoSeparatorsRegExp, separator),
|
|
141
|
+
},
|
|
142
|
+
data: data.replace(pseudoSeparatorsRegExp, separator),
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Помогает верно обрезать значения при вставке, если fractionLength===0
|
|
149
|
+
* @example paste 123,123 -> 123
|
|
150
|
+
*/
|
|
151
|
+
function createZeroFractionLengthPreprocessor(
|
|
152
|
+
fractionLength: number,
|
|
153
|
+
separator: string,
|
|
154
|
+
): MaskitoPreprocessor {
|
|
155
|
+
if (fractionLength > 0) {
|
|
156
|
+
return (state) => state;
|
|
39
157
|
}
|
|
40
158
|
|
|
41
|
-
const
|
|
42
|
-
.split(separator)
|
|
43
|
-
.map((v) => v.replace(/[^0-9]/g, ''));
|
|
159
|
+
const decimalPartRegExp = new RegExp(`\\${separator}.*$`, 'g');
|
|
44
160
|
|
|
45
|
-
return
|
|
46
|
-
};
|
|
161
|
+
return ({ elementState, data }) => {
|
|
162
|
+
const { value, selection } = elementState;
|
|
163
|
+
const [from, to] = selection;
|
|
164
|
+
const newValue = value.replace(decimalPartRegExp, '');
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
elementState: {
|
|
168
|
+
selection: [Math.min(from, newValue.length), Math.min(to, newValue.length)],
|
|
169
|
+
value: newValue,
|
|
170
|
+
},
|
|
171
|
+
data: data.replace(decimalPartRegExp, ''),
|
|
172
|
+
};
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Запрещает вводить второй сепаратор
|
|
178
|
+
*/
|
|
179
|
+
function createRepeatedSeparatorPreprocessor(separator: string): MaskitoPreprocessor {
|
|
180
|
+
return ({ elementState, data }) => {
|
|
181
|
+
const { value, selection } = elementState;
|
|
182
|
+
const [from, to] = selection;
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
elementState,
|
|
186
|
+
data:
|
|
187
|
+
!value.includes(separator) || value.slice(from, to + 1).includes(separator)
|
|
188
|
+
? data
|
|
189
|
+
: data.replace(new RegExp(`\\${separator}`), ''),
|
|
190
|
+
};
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Удаляет лишние нули в начале целой части.
|
|
196
|
+
* @example 0,|00005 => Backspace => |5
|
|
197
|
+
* @example -0,|00005 => Backspace => -|5
|
|
198
|
+
* @example "000000" => 0|
|
|
199
|
+
* @example 0| => Type "5" => 5|
|
|
200
|
+
*/
|
|
201
|
+
function createLeadingZeroesValidationPostprocessor(separator: string): MaskitoPostprocessor {
|
|
202
|
+
const trimLeadingZeroes = (value: string): string =>
|
|
203
|
+
value
|
|
204
|
+
.replace(new RegExp('^(\\D+)?0+(?=0)'), '$1')
|
|
205
|
+
.replace(new RegExp('^(\\D+)?0+(?=[1-9])'), '$1');
|
|
206
|
+
|
|
207
|
+
const countTrimmedZeroesBefore = (value: string, index: number): number => {
|
|
208
|
+
const valueBefore = value.slice(0, index);
|
|
209
|
+
const followedByZero = value.slice(index).startsWith('0');
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
valueBefore.length - trimLeadingZeroes(valueBefore).length + (followedByZero ? 1 : 0)
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return ({ value, selection }) => {
|
|
217
|
+
const [from, to] = selection;
|
|
218
|
+
const hasSeparator = value.includes(separator);
|
|
219
|
+
const [integerPart, decimalPart = ''] = value.split(separator);
|
|
220
|
+
const zeroTrimmedIntegerPart = trimLeadingZeroes(integerPart);
|
|
221
|
+
|
|
222
|
+
if (integerPart === zeroTrimmedIntegerPart) {
|
|
223
|
+
return { value, selection };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const newFrom = from - countTrimmedZeroesBefore(value, from);
|
|
227
|
+
const newTo = to - countTrimmedZeroesBefore(value, to);
|
|
228
|
+
|
|
229
|
+
return {
|
|
230
|
+
value: zeroTrimmedIntegerPart + (hasSeparator ? separator : '') + decimalPart,
|
|
231
|
+
selection: [Math.max(newFrom, 0), Math.max(newTo, 0)],
|
|
232
|
+
};
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Валидирует значение с учетом min max значений.
|
|
238
|
+
* Работает совместно с createMinMaxPlugin
|
|
239
|
+
*/
|
|
240
|
+
function createMinMaxPostprocessor({
|
|
241
|
+
min,
|
|
242
|
+
max,
|
|
243
|
+
separator,
|
|
244
|
+
}: {
|
|
245
|
+
min: number;
|
|
246
|
+
max: number;
|
|
247
|
+
separator: string;
|
|
248
|
+
}): MaskitoPostprocessor {
|
|
249
|
+
return ({ value, selection }) => {
|
|
250
|
+
const parsedNumber = parseNumber(value);
|
|
251
|
+
|
|
252
|
+
const limitedValue =
|
|
253
|
+
/**
|
|
254
|
+
* Здесь невозможно ограничить нижнюю границу, если пользователь вводит положительное число.
|
|
255
|
+
* То же самое для верхней границы и отрицательного числа.
|
|
256
|
+
* Если min=5, то без этого условия не получится ввести 40, похожая ситуация и с отрицательным max
|
|
257
|
+
*/
|
|
258
|
+
parsedNumber > 0 ? Math.min(parsedNumber, max) : Math.max(parsedNumber, min);
|
|
259
|
+
|
|
260
|
+
if (!Number.isNaN(parsedNumber) && limitedValue !== parsedNumber) {
|
|
261
|
+
const newValue = `${limitedValue}`.replace('.', separator);
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
value: newValue,
|
|
265
|
+
selection: [newValue.length, newValue.length],
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
value,
|
|
271
|
+
selection,
|
|
272
|
+
};
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function createMinMaxPlugin({ min, max }: { min: number; max: number }): MaskitoPlugin {
|
|
277
|
+
return (element, options) => {
|
|
278
|
+
const listener = () => {
|
|
279
|
+
const parsedNumber = parseNumber(element.value);
|
|
280
|
+
|
|
281
|
+
const clampedNumber = fnUtils.clamp(parsedNumber, min, max);
|
|
282
|
+
|
|
283
|
+
if (!Number.isNaN(parsedNumber) && parsedNumber !== clampedNumber) {
|
|
284
|
+
element.value = maskitoTransform(stringifyNumberWithoutExp(clampedNumber), options);
|
|
285
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const evListenerOptions = { capture: true };
|
|
290
|
+
|
|
291
|
+
element.addEventListener('blur', listener, evListenerOptions);
|
|
292
|
+
|
|
293
|
+
return () => element.removeEventListener('blur', listener, evListenerOptions);
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function createNotEmptyPartsPlugin(separator: string): MaskitoPlugin {
|
|
298
|
+
return (element) => {
|
|
299
|
+
const listener = () => {
|
|
300
|
+
const newValue = element.value
|
|
301
|
+
// 0,9000000 -> 0,9
|
|
302
|
+
.replace(new RegExp(`(\\${separator}\\d*?)(0+$)`), '$1')
|
|
303
|
+
// ,2 => 0,2
|
|
304
|
+
.replace(new RegExp(`^(\\D+)?\\${separator}`), `$10${separator}`)
|
|
305
|
+
// 0, -> 0
|
|
306
|
+
.replace(new RegExp(`\\${separator}$`), '')
|
|
307
|
+
// -0 -> 0
|
|
308
|
+
.replace(new RegExp(`^${MINUS_SIGN}0$`), '0')
|
|
309
|
+
// - -> ''
|
|
310
|
+
.replace(new RegExp(`^${MINUS_SIGN}$`), '');
|
|
311
|
+
|
|
312
|
+
if (newValue !== element.value) {
|
|
313
|
+
element.value = newValue;
|
|
314
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
const evListenerOptions = { capture: true };
|
|
319
|
+
|
|
320
|
+
element.addEventListener('blur', listener, evListenerOptions);
|
|
321
|
+
|
|
322
|
+
return () => element.removeEventListener('blur', listener, evListenerOptions);
|
|
323
|
+
};
|
|
324
|
+
}
|
package/utils.d.ts
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
import { MaskitoOptions, MaskitoPlugin } from '@maskito/core';
|
|
2
|
+
declare const MINUS_SIGN = "-";
|
|
2
3
|
declare const SEPARATORS: string[];
|
|
3
|
-
declare
|
|
4
|
+
declare const MAX_SAFE_INTEGER: number;
|
|
5
|
+
declare const MIN_SAFE_INTEGER: number;
|
|
6
|
+
declare const MAX_DIGITS = 15;
|
|
7
|
+
declare function parseNumber(value?: string | number | null): number;
|
|
4
8
|
/**
|
|
5
|
-
*
|
|
9
|
+
* Преобразовать число в строку с заменой экспоненты на десятичную дробь
|
|
6
10
|
*/
|
|
7
|
-
declare
|
|
8
|
-
|
|
9
|
-
fractionLength?: number | undefined;
|
|
11
|
+
declare function stringifyNumberWithoutExp(value: number): string;
|
|
12
|
+
declare function createMaskOptions({ separator, fractionLength, min, max, }: {
|
|
10
13
|
separator: string;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
fractionLength: number;
|
|
15
|
+
min: number;
|
|
16
|
+
max: number;
|
|
17
|
+
}): MaskitoOptions;
|
|
18
|
+
declare function createMinMaxPlugin({ min, max }: {
|
|
19
|
+
min: number;
|
|
20
|
+
max: number;
|
|
21
|
+
}): MaskitoPlugin;
|
|
22
|
+
declare function createNotEmptyPartsPlugin(separator: string): MaskitoPlugin;
|
|
23
|
+
export { MINUS_SIGN, SEPARATORS, MAX_SAFE_INTEGER, MIN_SAFE_INTEGER, MAX_DIGITS, parseNumber, stringifyNumberWithoutExp, createMaskOptions, createMinMaxPlugin, createNotEmptyPartsPlugin };
|
package/utils.js
CHANGED
|
@@ -2,35 +2,258 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var core = require('@maskito/core');
|
|
6
|
+
var coreComponentsShared = require('@alfalab/core-components-shared');
|
|
7
|
+
|
|
8
|
+
/* eslint-disable no-param-reassign */
|
|
9
|
+
var MINUS_SIGN = '-';
|
|
6
10
|
var SEPARATORS = [',', '.'];
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
var MAX_SAFE_INTEGER = Math.pow(2, 53) - 1;
|
|
12
|
+
var MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;
|
|
13
|
+
var MAX_DIGITS = 15; // с 16 уже упираемся в MAX_SAFE_INTEGER
|
|
14
|
+
function parseNumber(value) {
|
|
15
|
+
if (value === void 0) { value = ''; }
|
|
16
|
+
if (typeof value === 'number')
|
|
17
|
+
return value;
|
|
18
|
+
var pseudoSeparatorsRegExp = new RegExp("[".concat(SEPARATORS.join(''), "]"), 'gi');
|
|
19
|
+
return value
|
|
20
|
+
? parseFloat(value
|
|
21
|
+
.replace(new RegExp("[^".concat(MINUS_SIGN).concat(SEPARATORS.join(''), "0-9]"), 'gi'), '')
|
|
22
|
+
.replace(pseudoSeparatorsRegExp, '.'))
|
|
23
|
+
: NaN;
|
|
9
24
|
}
|
|
10
|
-
|
|
11
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Преобразовать число в строку с заменой экспоненты на десятичную дробь
|
|
27
|
+
*/
|
|
28
|
+
function stringifyNumberWithoutExp(value) {
|
|
29
|
+
var valueString = String(value);
|
|
30
|
+
var _a = valueString.split('e-'), numberPart = _a[0], expPart = _a[1];
|
|
31
|
+
var valueWithoutExp = valueString;
|
|
32
|
+
if (expPart) {
|
|
33
|
+
var _b = numberPart.split('.'), fractionalPart = _b[1];
|
|
34
|
+
var decimalDigits = Number(expPart) + ((fractionalPart === null || fractionalPart === void 0 ? void 0 : fractionalPart.length) || 0);
|
|
35
|
+
valueWithoutExp = value.toFixed(decimalDigits);
|
|
36
|
+
}
|
|
37
|
+
return valueWithoutExp;
|
|
38
|
+
}
|
|
39
|
+
var getNumberRegExp = function (min, fractionLength) {
|
|
40
|
+
var reStr = '[0-9]*';
|
|
41
|
+
if (min < 0) {
|
|
42
|
+
reStr = "(\\".concat(MINUS_SIGN, ")?").concat(reStr);
|
|
43
|
+
}
|
|
12
44
|
if (fractionLength !== 0) {
|
|
13
|
-
reStr = "".concat(reStr, "[").concat(SEPARATORS.map(function (s) { return "\\".concat(s); }).join(''), "]?[0-9]{0,").concat(fractionLength ||
|
|
45
|
+
reStr = "".concat(reStr, "[").concat(SEPARATORS.map(function (s) { return "\\".concat(s); }).join(''), "]?[0-9]{0,").concat(fractionLength || MAX_DIGITS, "}");
|
|
14
46
|
}
|
|
15
47
|
return new RegExp("^".concat(reStr, "$"));
|
|
16
48
|
};
|
|
49
|
+
function createMaskOptions(_a) {
|
|
50
|
+
var separator = _a.separator, fractionLength = _a.fractionLength, min = _a.min, max = _a.max;
|
|
51
|
+
return {
|
|
52
|
+
mask: getNumberRegExp(min, fractionLength),
|
|
53
|
+
preprocessors: [
|
|
54
|
+
createPseudoSeparatorPreprocessor(separator),
|
|
55
|
+
createNotEmptyIntegerPartPreprocessor({ separator: separator, fractionLength: fractionLength }),
|
|
56
|
+
createZeroFractionLengthPreprocessor(fractionLength, separator),
|
|
57
|
+
createRepeatedSeparatorPreprocessor(separator),
|
|
58
|
+
],
|
|
59
|
+
postprocessors: [
|
|
60
|
+
createLeadingZeroesValidationPostprocessor(separator),
|
|
61
|
+
createMinMaxPostprocessor({ min: min, max: max, separator: separator }),
|
|
62
|
+
],
|
|
63
|
+
plugins: [createNotEmptyPartsPlugin(separator), createMinMaxPlugin({ min: min, max: max })],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
17
66
|
/**
|
|
18
|
-
*
|
|
67
|
+
* Заполняет целочисленную часть при вводе separator.
|
|
68
|
+
* @example Type , => 0,
|
|
19
69
|
*/
|
|
20
|
-
|
|
21
|
-
var
|
|
22
|
-
var
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
70
|
+
function createNotEmptyIntegerPartPreprocessor(_a) {
|
|
71
|
+
var separator = _a.separator, fractionLength = _a.fractionLength;
|
|
72
|
+
var startWithDecimalSepRegExp = new RegExp("^\\D*\\".concat(separator));
|
|
73
|
+
return function (_a) {
|
|
74
|
+
var elementState = _a.elementState, data = _a.data;
|
|
75
|
+
var value = elementState.value, selection = elementState.selection;
|
|
76
|
+
var from = selection[0];
|
|
77
|
+
if (fractionLength <= 0 ||
|
|
78
|
+
value.includes(separator) ||
|
|
79
|
+
!data.match(startWithDecimalSepRegExp)) {
|
|
80
|
+
return { elementState: elementState, data: data };
|
|
81
|
+
}
|
|
82
|
+
var digitsBeforeCursor = value.slice(0, from).match(/\d+/);
|
|
83
|
+
return {
|
|
84
|
+
elementState: elementState,
|
|
85
|
+
data: digitsBeforeCursor ? data : "0".concat(data),
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Не позволяет вводить невалидный разделитель.
|
|
91
|
+
*/
|
|
92
|
+
function createPseudoSeparatorPreprocessor(separator) {
|
|
93
|
+
var pseudoSeparatorsRegExp = new RegExp("[".concat(SEPARATORS.join(''), "]"), 'gi');
|
|
94
|
+
return function (_a) {
|
|
95
|
+
var elementState = _a.elementState, data = _a.data;
|
|
96
|
+
var value = elementState.value, selection = elementState.selection;
|
|
97
|
+
return {
|
|
98
|
+
elementState: {
|
|
99
|
+
selection: selection,
|
|
100
|
+
value: value.replace(pseudoSeparatorsRegExp, separator),
|
|
101
|
+
},
|
|
102
|
+
data: data.replace(pseudoSeparatorsRegExp, separator),
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Помогает верно обрезать значения при вставке, если fractionLength===0
|
|
108
|
+
* @example paste 123,123 -> 123
|
|
109
|
+
*/
|
|
110
|
+
function createZeroFractionLengthPreprocessor(fractionLength, separator) {
|
|
111
|
+
if (fractionLength > 0) {
|
|
112
|
+
return function (state) { return state; };
|
|
26
113
|
}
|
|
27
|
-
var
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
114
|
+
var decimalPartRegExp = new RegExp("\\".concat(separator, ".*$"), 'g');
|
|
115
|
+
return function (_a) {
|
|
116
|
+
var elementState = _a.elementState, data = _a.data;
|
|
117
|
+
var value = elementState.value, selection = elementState.selection;
|
|
118
|
+
var from = selection[0], to = selection[1];
|
|
119
|
+
var newValue = value.replace(decimalPartRegExp, '');
|
|
120
|
+
return {
|
|
121
|
+
elementState: {
|
|
122
|
+
selection: [Math.min(from, newValue.length), Math.min(to, newValue.length)],
|
|
123
|
+
value: newValue,
|
|
124
|
+
},
|
|
125
|
+
data: data.replace(decimalPartRegExp, ''),
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Запрещает вводить второй сепаратор
|
|
131
|
+
*/
|
|
132
|
+
function createRepeatedSeparatorPreprocessor(separator) {
|
|
133
|
+
return function (_a) {
|
|
134
|
+
var elementState = _a.elementState, data = _a.data;
|
|
135
|
+
var value = elementState.value, selection = elementState.selection;
|
|
136
|
+
var from = selection[0], to = selection[1];
|
|
137
|
+
return {
|
|
138
|
+
elementState: elementState,
|
|
139
|
+
data: !value.includes(separator) || value.slice(from, to + 1).includes(separator)
|
|
140
|
+
? data
|
|
141
|
+
: data.replace(new RegExp("\\".concat(separator)), ''),
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Удаляет лишние нули в начале целой части.
|
|
147
|
+
* @example 0,|00005 => Backspace => |5
|
|
148
|
+
* @example -0,|00005 => Backspace => -|5
|
|
149
|
+
* @example "000000" => 0|
|
|
150
|
+
* @example 0| => Type "5" => 5|
|
|
151
|
+
*/
|
|
152
|
+
function createLeadingZeroesValidationPostprocessor(separator) {
|
|
153
|
+
var trimLeadingZeroes = function (value) {
|
|
154
|
+
return value
|
|
155
|
+
.replace(new RegExp('^(\\D+)?0+(?=0)'), '$1')
|
|
156
|
+
.replace(new RegExp('^(\\D+)?0+(?=[1-9])'), '$1');
|
|
157
|
+
};
|
|
158
|
+
var countTrimmedZeroesBefore = function (value, index) {
|
|
159
|
+
var valueBefore = value.slice(0, index);
|
|
160
|
+
var followedByZero = value.slice(index).startsWith('0');
|
|
161
|
+
return (valueBefore.length - trimLeadingZeroes(valueBefore).length + (followedByZero ? 1 : 0));
|
|
162
|
+
};
|
|
163
|
+
return function (_a) {
|
|
164
|
+
var value = _a.value, selection = _a.selection;
|
|
165
|
+
var from = selection[0], to = selection[1];
|
|
166
|
+
var hasSeparator = value.includes(separator);
|
|
167
|
+
var _b = value.split(separator), integerPart = _b[0], _c = _b[1], decimalPart = _c === void 0 ? '' : _c;
|
|
168
|
+
var zeroTrimmedIntegerPart = trimLeadingZeroes(integerPart);
|
|
169
|
+
if (integerPart === zeroTrimmedIntegerPart) {
|
|
170
|
+
return { value: value, selection: selection };
|
|
171
|
+
}
|
|
172
|
+
var newFrom = from - countTrimmedZeroesBefore(value, from);
|
|
173
|
+
var newTo = to - countTrimmedZeroesBefore(value, to);
|
|
174
|
+
return {
|
|
175
|
+
value: zeroTrimmedIntegerPart + (hasSeparator ? separator : '') + decimalPart,
|
|
176
|
+
selection: [Math.max(newFrom, 0), Math.max(newTo, 0)],
|
|
177
|
+
};
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Валидирует значение с учетом min max значений.
|
|
182
|
+
* Работает совместно с createMinMaxPlugin
|
|
183
|
+
*/
|
|
184
|
+
function createMinMaxPostprocessor(_a) {
|
|
185
|
+
var min = _a.min, max = _a.max, separator = _a.separator;
|
|
186
|
+
return function (_a) {
|
|
187
|
+
var value = _a.value, selection = _a.selection;
|
|
188
|
+
var parsedNumber = parseNumber(value);
|
|
189
|
+
var limitedValue =
|
|
190
|
+
/**
|
|
191
|
+
* Здесь невозможно ограничить нижнюю границу, если пользователь вводит положительное число.
|
|
192
|
+
* То же самое для верхней границы и отрицательного числа.
|
|
193
|
+
* Если min=5, то без этого условия не получится ввести 40, похожая ситуация и с отрицательным max
|
|
194
|
+
*/
|
|
195
|
+
parsedNumber > 0 ? Math.min(parsedNumber, max) : Math.max(parsedNumber, min);
|
|
196
|
+
if (!Number.isNaN(parsedNumber) && limitedValue !== parsedNumber) {
|
|
197
|
+
var newValue = "".concat(limitedValue).replace('.', separator);
|
|
198
|
+
return {
|
|
199
|
+
value: newValue,
|
|
200
|
+
selection: [newValue.length, newValue.length],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
value: value,
|
|
205
|
+
selection: selection,
|
|
206
|
+
};
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
function createMinMaxPlugin(_a) {
|
|
210
|
+
var min = _a.min, max = _a.max;
|
|
211
|
+
return function (element, options) {
|
|
212
|
+
var listener = function () {
|
|
213
|
+
var parsedNumber = parseNumber(element.value);
|
|
214
|
+
var clampedNumber = coreComponentsShared.fnUtils.clamp(parsedNumber, min, max);
|
|
215
|
+
if (!Number.isNaN(parsedNumber) && parsedNumber !== clampedNumber) {
|
|
216
|
+
element.value = core.maskitoTransform(stringifyNumberWithoutExp(clampedNumber), options);
|
|
217
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
var evListenerOptions = { capture: true };
|
|
221
|
+
element.addEventListener('blur', listener, evListenerOptions);
|
|
222
|
+
return function () { return element.removeEventListener('blur', listener, evListenerOptions); };
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function createNotEmptyPartsPlugin(separator) {
|
|
226
|
+
return function (element) {
|
|
227
|
+
var listener = function () {
|
|
228
|
+
var newValue = element.value
|
|
229
|
+
// 0,9000000 -> 0,9
|
|
230
|
+
.replace(new RegExp("(\\".concat(separator, "\\d*?)(0+$)")), '$1')
|
|
231
|
+
// ,2 => 0,2
|
|
232
|
+
.replace(new RegExp("^(\\D+)?\\".concat(separator)), "$10".concat(separator))
|
|
233
|
+
// 0, -> 0
|
|
234
|
+
.replace(new RegExp("\\".concat(separator, "$")), '')
|
|
235
|
+
// -0 -> 0
|
|
236
|
+
.replace(new RegExp("^".concat(MINUS_SIGN, "0$")), '0')
|
|
237
|
+
// - -> ''
|
|
238
|
+
.replace(new RegExp("^".concat(MINUS_SIGN, "$")), '');
|
|
239
|
+
if (newValue !== element.value) {
|
|
240
|
+
element.value = newValue;
|
|
241
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
var evListenerOptions = { capture: true };
|
|
245
|
+
element.addEventListener('blur', listener, evListenerOptions);
|
|
246
|
+
return function () { return element.removeEventListener('blur', listener, evListenerOptions); };
|
|
247
|
+
};
|
|
248
|
+
}
|
|
32
249
|
|
|
250
|
+
exports.MAX_DIGITS = MAX_DIGITS;
|
|
251
|
+
exports.MAX_SAFE_INTEGER = MAX_SAFE_INTEGER;
|
|
252
|
+
exports.MINUS_SIGN = MINUS_SIGN;
|
|
253
|
+
exports.MIN_SAFE_INTEGER = MIN_SAFE_INTEGER;
|
|
33
254
|
exports.SEPARATORS = SEPARATORS;
|
|
34
|
-
exports.
|
|
35
|
-
exports.
|
|
36
|
-
exports.
|
|
255
|
+
exports.createMaskOptions = createMaskOptions;
|
|
256
|
+
exports.createMinMaxPlugin = createMinMaxPlugin;
|
|
257
|
+
exports.createNotEmptyPartsPlugin = createNotEmptyPartsPlugin;
|
|
258
|
+
exports.parseNumber = parseNumber;
|
|
259
|
+
exports.stringifyNumberWithoutExp = stringifyNumberWithoutExp;
|