@canutin/svelte-currency-input 0.12.0 → 1.0.0-next.7
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/README.md +231 -110
- package/dist/currency-input.svelte +361 -0
- package/dist/currency-input.svelte.d.ts +29 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -2
- package/dist/types.d.ts +36 -9
- package/dist/utils/addSeparators.d.ts +1 -0
- package/dist/utils/addSeparators.js +3 -0
- package/dist/utils/cleanValue.d.ts +2 -0
- package/dist/utils/cleanValue.js +43 -0
- package/dist/utils/escapeRegExp.d.ts +1 -0
- package/dist/utils/escapeRegExp.js +3 -0
- package/dist/utils/fixedDecimalValue.d.ts +1 -0
- package/dist/utils/fixedDecimalValue.js +25 -0
- package/dist/utils/formatValue.d.ts +2 -0
- package/dist/utils/formatValue.js +101 -0
- package/dist/utils/getLocaleConfig.d.ts +2 -0
- package/dist/utils/getLocaleConfig.js +33 -0
- package/dist/utils/getSuffix.d.ts +6 -0
- package/dist/utils/getSuffix.js +6 -0
- package/dist/utils/index.d.ts +13 -0
- package/dist/utils/index.js +13 -0
- package/dist/utils/isNumber.d.ts +1 -0
- package/dist/utils/isNumber.js +1 -0
- package/dist/utils/padTrimValue.d.ts +1 -0
- package/dist/utils/padTrimValue.js +26 -0
- package/dist/utils/parseAbbrValue.d.ts +2 -0
- package/dist/utils/parseAbbrValue.js +23 -0
- package/dist/utils/removeInvalidChars.d.ts +1 -0
- package/dist/utils/removeInvalidChars.js +6 -0
- package/dist/utils/removeSeparators.d.ts +1 -0
- package/dist/utils/removeSeparators.js +5 -0
- package/dist/utils/repositionCursor.d.ts +12 -0
- package/dist/utils/repositionCursor.js +18 -0
- package/package.json +77 -58
- package/dist/CurrencyInput.svelte +0 -180
- package/dist/CurrencyInput.svelte.d.ts +0 -30
- package/dist/constants.d.ts +0 -11
- package/dist/constants.js +0 -11
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
import type {
|
|
4
|
+
IntlConfig,
|
|
5
|
+
CurrencyInputValues,
|
|
6
|
+
CleanValueOptions,
|
|
7
|
+
FormatValueOptions
|
|
8
|
+
} from './types';
|
|
9
|
+
import {
|
|
10
|
+
isNumber,
|
|
11
|
+
cleanValue,
|
|
12
|
+
fixedDecimalValue,
|
|
13
|
+
formatValue,
|
|
14
|
+
getLocaleConfig,
|
|
15
|
+
padTrimValue,
|
|
16
|
+
getSuffix,
|
|
17
|
+
repositionCursor
|
|
18
|
+
} from './utils/index';
|
|
19
|
+
|
|
20
|
+
type Props = Omit<HTMLInputAttributes, 'value'> & {
|
|
21
|
+
value?: string;
|
|
22
|
+
intlConfig?: IntlConfig;
|
|
23
|
+
prefix?: string;
|
|
24
|
+
suffix?: string;
|
|
25
|
+
decimalSeparator?: string;
|
|
26
|
+
groupSeparator?: string;
|
|
27
|
+
allowDecimals?: boolean;
|
|
28
|
+
decimalsLimit?: number;
|
|
29
|
+
decimalScale?: number;
|
|
30
|
+
fixedDecimalLength?: number;
|
|
31
|
+
allowNegativeValue?: boolean;
|
|
32
|
+
min?: number;
|
|
33
|
+
max?: number;
|
|
34
|
+
maxLength?: number;
|
|
35
|
+
step?: number;
|
|
36
|
+
disableGroupSeparators?: boolean;
|
|
37
|
+
disableAbbreviations?: boolean;
|
|
38
|
+
formatValueOnBlur?: boolean;
|
|
39
|
+
transformRawValue?: (rawValue: string) => string;
|
|
40
|
+
oninputvalue?: (values: CurrencyInputValues) => void;
|
|
41
|
+
onchangevalue?: (values: CurrencyInputValues) => void;
|
|
42
|
+
ref?: HTMLInputElement | null;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
let {
|
|
46
|
+
value = $bindable(''),
|
|
47
|
+
intlConfig,
|
|
48
|
+
prefix: userPrefix,
|
|
49
|
+
suffix: userSuffix,
|
|
50
|
+
decimalSeparator: _decimalSeparator,
|
|
51
|
+
groupSeparator: _groupSeparator,
|
|
52
|
+
allowDecimals = true,
|
|
53
|
+
decimalsLimit,
|
|
54
|
+
decimalScale,
|
|
55
|
+
fixedDecimalLength,
|
|
56
|
+
allowNegativeValue = true,
|
|
57
|
+
min,
|
|
58
|
+
max,
|
|
59
|
+
maxLength: userMaxLength,
|
|
60
|
+
step,
|
|
61
|
+
disableGroupSeparators = false,
|
|
62
|
+
disableAbbreviations = false,
|
|
63
|
+
formatValueOnBlur = true,
|
|
64
|
+
transformRawValue,
|
|
65
|
+
oninputvalue,
|
|
66
|
+
onchangevalue,
|
|
67
|
+
ref = $bindable(null),
|
|
68
|
+
...restProps
|
|
69
|
+
}: Props = $props();
|
|
70
|
+
|
|
71
|
+
$effect(() => {
|
|
72
|
+
if (_decimalSeparator && isNumber(_decimalSeparator)) {
|
|
73
|
+
throw new Error('decimalSeparator cannot be a number');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (_groupSeparator && isNumber(_groupSeparator)) {
|
|
77
|
+
throw new Error('groupSeparator cannot be a number');
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
let localeConfig = $derived(getLocaleConfig(intlConfig));
|
|
82
|
+
let decimalSeparator = $derived(_decimalSeparator || localeConfig.decimalSeparator || '');
|
|
83
|
+
let groupSeparator = $derived(_groupSeparator || localeConfig.groupSeparator || '');
|
|
84
|
+
let prefix = $derived(userPrefix ?? localeConfig.prefix);
|
|
85
|
+
let suffix = $derived(userSuffix ?? '');
|
|
86
|
+
|
|
87
|
+
$effect(() => {
|
|
88
|
+
if (
|
|
89
|
+
decimalSeparator &&
|
|
90
|
+
groupSeparator &&
|
|
91
|
+
decimalSeparator === groupSeparator &&
|
|
92
|
+
disableGroupSeparators === false
|
|
93
|
+
) {
|
|
94
|
+
throw new Error('decimalSeparator cannot be the same as groupSeparator');
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
let formatValueOptions = $derived<Partial<FormatValueOptions>>({
|
|
99
|
+
decimalSeparator,
|
|
100
|
+
groupSeparator,
|
|
101
|
+
disableGroupSeparators,
|
|
102
|
+
intlConfig,
|
|
103
|
+
prefix,
|
|
104
|
+
suffix
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
let cleanValueOptions = $derived<Partial<CleanValueOptions>>({
|
|
108
|
+
decimalSeparator,
|
|
109
|
+
groupSeparator,
|
|
110
|
+
allowDecimals,
|
|
111
|
+
decimalsLimit: decimalsLimit ?? fixedDecimalLength ?? 2,
|
|
112
|
+
allowNegativeValue,
|
|
113
|
+
disableAbbreviations,
|
|
114
|
+
prefix,
|
|
115
|
+
transformRawValue
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
let stateValue = $state('');
|
|
119
|
+
let dirty = $state(false);
|
|
120
|
+
let cursor = $state(0);
|
|
121
|
+
let changeCount = $state(0);
|
|
122
|
+
let lastKeyStroke = $state<string | null>(null);
|
|
123
|
+
|
|
124
|
+
$effect(() => {
|
|
125
|
+
if (stateValue === '-' || (decimalSeparator && stateValue === decimalSeparator)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
129
|
+
const formatted = formatValue({
|
|
130
|
+
...formatValueOptions,
|
|
131
|
+
decimalScale: dirty ? undefined : decimalScale,
|
|
132
|
+
value: String(value)
|
|
133
|
+
});
|
|
134
|
+
if (!dirty) {
|
|
135
|
+
stateValue = formatted;
|
|
136
|
+
}
|
|
137
|
+
} else if (!dirty) {
|
|
138
|
+
stateValue = '';
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
$effect(() => {
|
|
143
|
+
if (dirty && stateValue !== '-' && ref && document.activeElement === ref) {
|
|
144
|
+
ref.setSelectionRange(cursor, cursor);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
function processChange(inputValue: string, selectionStart?: number | null): void {
|
|
149
|
+
dirty = true;
|
|
150
|
+
|
|
151
|
+
const { modifiedValue, cursorPosition } = repositionCursor({
|
|
152
|
+
selectionStart,
|
|
153
|
+
value: inputValue,
|
|
154
|
+
lastKeyStroke,
|
|
155
|
+
stateValue,
|
|
156
|
+
groupSeparator
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const stringValue = cleanValue({ value: modifiedValue, ...cleanValueOptions });
|
|
160
|
+
|
|
161
|
+
if (userMaxLength && stringValue.replace(/-/g, '').length > userMaxLength) {
|
|
162
|
+
if (ref) {
|
|
163
|
+
ref.value = stateValue;
|
|
164
|
+
const cursorPos = Math.min(selectionStart ?? 0, stateValue.length);
|
|
165
|
+
ref.setSelectionRange(cursorPos, cursorPos);
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (stringValue === '' || stringValue === '-' || stringValue === decimalSeparator) {
|
|
171
|
+
value = '';
|
|
172
|
+
stateValue = stringValue;
|
|
173
|
+
cursor = 1;
|
|
174
|
+
|
|
175
|
+
if (ref) {
|
|
176
|
+
ref.value = stringValue;
|
|
177
|
+
ref.setSelectionRange(cursor, cursor);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const values: CurrencyInputValues = { float: null, formatted: '', value: '' };
|
|
181
|
+
oninputvalue?.(values);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const stringValueWithoutSeparator = decimalSeparator
|
|
186
|
+
? stringValue.replace(decimalSeparator, '.')
|
|
187
|
+
: stringValue;
|
|
188
|
+
|
|
189
|
+
const numberValue = parseFloat(stringValueWithoutSeparator);
|
|
190
|
+
|
|
191
|
+
const formattedValue = formatValue({
|
|
192
|
+
value: stringValue,
|
|
193
|
+
...formatValueOptions
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
let newCursor = cursorPosition ?? formattedValue.length;
|
|
197
|
+
if (cursorPosition != null) {
|
|
198
|
+
newCursor = cursorPosition + (formattedValue.length - inputValue.length);
|
|
199
|
+
newCursor = newCursor <= 0 ? (prefix ? prefix.length : 0) : newCursor;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
cursor = newCursor;
|
|
203
|
+
changeCount = changeCount + 1;
|
|
204
|
+
stateValue = formattedValue;
|
|
205
|
+
value = stringValue;
|
|
206
|
+
|
|
207
|
+
if (ref) {
|
|
208
|
+
ref.value = formattedValue;
|
|
209
|
+
ref.setSelectionRange(cursor, cursor);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const values: CurrencyInputValues = {
|
|
213
|
+
float: numberValue,
|
|
214
|
+
formatted: formattedValue,
|
|
215
|
+
value: stringValue
|
|
216
|
+
};
|
|
217
|
+
oninputvalue?.(values);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function handleInput(event: Event): void {
|
|
221
|
+
const target = event.target as HTMLInputElement;
|
|
222
|
+
const { value: inputValue, selectionStart } = target;
|
|
223
|
+
|
|
224
|
+
processChange(inputValue, selectionStart);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function handleBlur(event: FocusEvent): void {
|
|
228
|
+
const target = event.target as HTMLInputElement;
|
|
229
|
+
const { value: inputValue } = target;
|
|
230
|
+
|
|
231
|
+
const valueOnly = cleanValue({ value: inputValue, ...cleanValueOptions });
|
|
232
|
+
|
|
233
|
+
if (valueOnly === '-' || valueOnly === decimalSeparator || !valueOnly) {
|
|
234
|
+
stateValue = '';
|
|
235
|
+
value = '';
|
|
236
|
+
dirty = false;
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const fixedDecimals = fixedDecimalValue(valueOnly, decimalSeparator, fixedDecimalLength);
|
|
241
|
+
|
|
242
|
+
const newValue = padTrimValue(
|
|
243
|
+
fixedDecimals,
|
|
244
|
+
decimalSeparator,
|
|
245
|
+
decimalScale !== undefined ? decimalScale : fixedDecimalLength
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const stringValueWithoutSeparator = decimalSeparator
|
|
249
|
+
? newValue.replace(decimalSeparator, '.')
|
|
250
|
+
: newValue;
|
|
251
|
+
|
|
252
|
+
const numberValue = parseFloat(stringValueWithoutSeparator);
|
|
253
|
+
|
|
254
|
+
const formattedValue = formatValue({
|
|
255
|
+
...formatValueOptions,
|
|
256
|
+
value: newValue
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (formatValueOnBlur) {
|
|
260
|
+
stateValue = formattedValue;
|
|
261
|
+
value = newValue;
|
|
262
|
+
|
|
263
|
+
const values: CurrencyInputValues = {
|
|
264
|
+
float: numberValue,
|
|
265
|
+
formatted: formattedValue,
|
|
266
|
+
value: newValue
|
|
267
|
+
};
|
|
268
|
+
onchangevalue?.(values);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
dirty = false;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function handleKeyDown(event: KeyboardEvent): void {
|
|
275
|
+
const { key } = event;
|
|
276
|
+
|
|
277
|
+
lastKeyStroke = key;
|
|
278
|
+
|
|
279
|
+
if (step && (key === 'ArrowUp' || key === 'ArrowDown')) {
|
|
280
|
+
event.preventDefault();
|
|
281
|
+
cursor = stateValue.length;
|
|
282
|
+
|
|
283
|
+
const stringValueWithoutSeparator =
|
|
284
|
+
decimalSeparator && value ? value.replace(decimalSeparator, '.') : value;
|
|
285
|
+
|
|
286
|
+
const currentValue =
|
|
287
|
+
parseFloat(
|
|
288
|
+
stringValueWithoutSeparator != null && stringValueWithoutSeparator !== ''
|
|
289
|
+
? stringValueWithoutSeparator
|
|
290
|
+
: cleanValue({ value: stateValue, ...cleanValueOptions })
|
|
291
|
+
) || 0;
|
|
292
|
+
const newValue = key === 'ArrowUp' ? currentValue + step : currentValue - step;
|
|
293
|
+
|
|
294
|
+
if ((min !== undefined && newValue < min) || (!allowNegativeValue && newValue < 0)) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (max !== undefined && newValue > max) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const fixedLength = String(step).includes('.')
|
|
303
|
+
? Number(String(step).split('.')[1].length)
|
|
304
|
+
: undefined;
|
|
305
|
+
|
|
306
|
+
processChange(
|
|
307
|
+
String(fixedLength ? newValue.toFixed(fixedLength) : newValue).replace(
|
|
308
|
+
'.',
|
|
309
|
+
decimalSeparator
|
|
310
|
+
)
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function handleKeyUp(event: KeyboardEvent): void {
|
|
316
|
+
const { key } = event;
|
|
317
|
+
const target = event.currentTarget as HTMLInputElement;
|
|
318
|
+
const { selectionStart } = target;
|
|
319
|
+
|
|
320
|
+
if (key !== 'ArrowUp' && key !== 'ArrowDown' && stateValue !== '-') {
|
|
321
|
+
const suffixValue = getSuffix(stateValue, { groupSeparator, decimalSeparator });
|
|
322
|
+
|
|
323
|
+
if (
|
|
324
|
+
suffixValue &&
|
|
325
|
+
selectionStart &&
|
|
326
|
+
selectionStart > stateValue.length - suffixValue.length
|
|
327
|
+
) {
|
|
328
|
+
if (ref) {
|
|
329
|
+
const newCursor = stateValue.length - suffixValue.length;
|
|
330
|
+
ref.setSelectionRange(newCursor, newCursor);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
let displayValue = $derived.by(() => {
|
|
337
|
+
if (stateValue === '-' || stateValue === decimalSeparator) {
|
|
338
|
+
return stateValue;
|
|
339
|
+
}
|
|
340
|
+
if (value !== undefined && value !== null && value !== '') {
|
|
341
|
+
return formatValue({
|
|
342
|
+
...formatValueOptions,
|
|
343
|
+
decimalScale: dirty ? undefined : decimalScale,
|
|
344
|
+
value: String(value)
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return stateValue;
|
|
348
|
+
});
|
|
349
|
+
</script>
|
|
350
|
+
|
|
351
|
+
<input
|
|
352
|
+
bind:this={ref}
|
|
353
|
+
type="text"
|
|
354
|
+
inputmode="decimal"
|
|
355
|
+
value={displayValue}
|
|
356
|
+
oninput={handleInput}
|
|
357
|
+
onblur={handleBlur}
|
|
358
|
+
onkeydown={handleKeyDown}
|
|
359
|
+
onkeyup={handleKeyUp}
|
|
360
|
+
{...restProps}
|
|
361
|
+
/>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
2
|
+
import type { IntlConfig, CurrencyInputValues } from './types';
|
|
3
|
+
type Props = Omit<HTMLInputAttributes, 'value'> & {
|
|
4
|
+
value?: string;
|
|
5
|
+
intlConfig?: IntlConfig;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
suffix?: string;
|
|
8
|
+
decimalSeparator?: string;
|
|
9
|
+
groupSeparator?: string;
|
|
10
|
+
allowDecimals?: boolean;
|
|
11
|
+
decimalsLimit?: number;
|
|
12
|
+
decimalScale?: number;
|
|
13
|
+
fixedDecimalLength?: number;
|
|
14
|
+
allowNegativeValue?: boolean;
|
|
15
|
+
min?: number;
|
|
16
|
+
max?: number;
|
|
17
|
+
maxLength?: number;
|
|
18
|
+
step?: number;
|
|
19
|
+
disableGroupSeparators?: boolean;
|
|
20
|
+
disableAbbreviations?: boolean;
|
|
21
|
+
formatValueOnBlur?: boolean;
|
|
22
|
+
transformRawValue?: (rawValue: string) => string;
|
|
23
|
+
oninputvalue?: (values: CurrencyInputValues) => void;
|
|
24
|
+
onchangevalue?: (values: CurrencyInputValues) => void;
|
|
25
|
+
ref?: HTMLInputElement | null;
|
|
26
|
+
};
|
|
27
|
+
declare const CurrencyInput: import("svelte").Component<Props, {}, "value" | "ref">;
|
|
28
|
+
type CurrencyInput = ReturnType<typeof CurrencyInput>;
|
|
29
|
+
export default CurrencyInput;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
export { default as CurrencyInput } from './currency-input.svelte';
|
|
2
|
+
export type { IntlConfig, CurrencyInputValues, LocaleConfig, FormatValueOptions, CleanValueOptions } from './types';
|
|
3
|
+
export { addSeparators, cleanValue, escapeRegExp, fixedDecimalValue, formatValue, getLocaleConfig, getSuffix, isNumber, padTrimValue, parseAbbrValue, abbrValue, removeInvalidChars, removeSeparators, repositionCursor } from './utils/index';
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export default CurrencyInput;
|
|
1
|
+
// Component
|
|
2
|
+
export { default as CurrencyInput } from './currency-input.svelte';
|
|
3
|
+
// Utilities
|
|
4
|
+
export { addSeparators, cleanValue, escapeRegExp, fixedDecimalValue, formatValue, getLocaleConfig, getSuffix, isNumber, padTrimValue, parseAbbrValue, abbrValue, removeInvalidChars, removeSeparators, repositionCursor } from './utils/index';
|
package/dist/types.d.ts
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
export type
|
|
1
|
+
export type IntlConfig = {
|
|
2
|
+
locale: string;
|
|
3
|
+
} & Intl.NumberFormatOptions;
|
|
4
|
+
export type CurrencyInputValues = {
|
|
5
|
+
float: number | null;
|
|
6
|
+
formatted: string;
|
|
7
|
+
value: string;
|
|
8
|
+
};
|
|
9
|
+
export type LocaleConfig = {
|
|
10
|
+
currencySymbol: string;
|
|
11
|
+
groupSeparator: string;
|
|
12
|
+
decimalSeparator: string;
|
|
13
|
+
prefix: string;
|
|
14
|
+
suffix: string;
|
|
15
|
+
};
|
|
16
|
+
export type FormatValueOptions = {
|
|
17
|
+
value: string | undefined;
|
|
18
|
+
decimalSeparator?: string;
|
|
19
|
+
groupSeparator?: string;
|
|
20
|
+
disableGroupSeparators?: boolean;
|
|
21
|
+
intlConfig?: IntlConfig;
|
|
22
|
+
decimalScale?: number;
|
|
23
|
+
prefix?: string;
|
|
24
|
+
suffix?: string;
|
|
25
|
+
};
|
|
26
|
+
export type CleanValueOptions = {
|
|
27
|
+
value: string;
|
|
28
|
+
decimalSeparator?: string;
|
|
29
|
+
groupSeparator?: string;
|
|
30
|
+
allowDecimals?: boolean;
|
|
31
|
+
decimalsLimit?: number;
|
|
32
|
+
allowNegativeValue?: boolean;
|
|
33
|
+
disableAbbreviations?: boolean;
|
|
34
|
+
prefix?: string;
|
|
35
|
+
transformRawValue?: (rawValue: string) => string;
|
|
36
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const addSeparators: (value: string, separator?: string) => string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { escapeRegExp } from './escapeRegExp';
|
|
2
|
+
import { parseAbbrValue } from './parseAbbrValue';
|
|
3
|
+
import { removeInvalidChars } from './removeInvalidChars';
|
|
4
|
+
import { removeSeparators } from './removeSeparators';
|
|
5
|
+
export const cleanValue = ({ value, groupSeparator = ',', decimalSeparator = '.', allowDecimals = true, decimalsLimit = 2, allowNegativeValue = true, disableAbbreviations = false, prefix = '', transformRawValue = (rawValue) => rawValue }) => {
|
|
6
|
+
const transformedValue = transformRawValue(value);
|
|
7
|
+
if (transformedValue === '-') {
|
|
8
|
+
return transformedValue;
|
|
9
|
+
}
|
|
10
|
+
const abbreviations = disableAbbreviations ? [] : ['k', 'm', 'b'];
|
|
11
|
+
const reg = new RegExp(`((^|\\D)-\\d)|(-${escapeRegExp(prefix)})`);
|
|
12
|
+
const isNegative = reg.test(transformedValue);
|
|
13
|
+
const [prefixWithValue, preValue] = RegExp(`(\\d+)-?${escapeRegExp(prefix)}`).exec(value) || [];
|
|
14
|
+
const withoutPrefix = prefix
|
|
15
|
+
? prefixWithValue
|
|
16
|
+
? transformedValue.replace(prefixWithValue, '').concat(preValue)
|
|
17
|
+
: transformedValue.replace(prefix, '')
|
|
18
|
+
: transformedValue;
|
|
19
|
+
const withoutSeparators = removeSeparators(withoutPrefix, groupSeparator);
|
|
20
|
+
const withoutInvalidChars = removeInvalidChars(withoutSeparators, [
|
|
21
|
+
groupSeparator,
|
|
22
|
+
decimalSeparator,
|
|
23
|
+
...abbreviations
|
|
24
|
+
]);
|
|
25
|
+
let valueOnly = withoutInvalidChars;
|
|
26
|
+
if (!disableAbbreviations) {
|
|
27
|
+
if (abbreviations.some((letter) => letter === withoutInvalidChars.toLowerCase().replace(decimalSeparator, ''))) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
const parsed = parseAbbrValue(withoutInvalidChars, decimalSeparator);
|
|
31
|
+
if (parsed) {
|
|
32
|
+
valueOnly = String(parsed);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const includeNegative = isNegative && allowNegativeValue ? '-' : '';
|
|
36
|
+
if (decimalSeparator && valueOnly.includes(decimalSeparator)) {
|
|
37
|
+
const [int, decimals] = withoutInvalidChars.split(decimalSeparator);
|
|
38
|
+
const trimmedDecimals = decimalsLimit && decimals ? decimals.slice(0, decimalsLimit) : decimals;
|
|
39
|
+
const includeDecimals = allowDecimals ? `${decimalSeparator}${trimmedDecimals}` : '';
|
|
40
|
+
return `${includeNegative}${int}${includeDecimals}`;
|
|
41
|
+
}
|
|
42
|
+
return `${includeNegative}${valueOnly}`;
|
|
43
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const escapeRegExp: (stringToGoIntoTheRegex: string) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const fixedDecimalValue: (value: string, decimalSeparator: string, fixedDecimalLength?: number) => string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const fixedDecimalValue = (value, decimalSeparator, fixedDecimalLength) => {
|
|
2
|
+
if (fixedDecimalLength !== undefined && value.length > 1) {
|
|
3
|
+
if (fixedDecimalLength === 0) {
|
|
4
|
+
return value.replace(decimalSeparator, '');
|
|
5
|
+
}
|
|
6
|
+
if (value.includes(decimalSeparator)) {
|
|
7
|
+
const [int, decimals] = value.split(decimalSeparator);
|
|
8
|
+
if (decimals.length === fixedDecimalLength) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
if (decimals.length > fixedDecimalLength) {
|
|
12
|
+
return `${int}${decimalSeparator}${decimals.slice(0, fixedDecimalLength)}`;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const reg = value.length > fixedDecimalLength
|
|
16
|
+
? new RegExp(`(\\d+)(\\d{${fixedDecimalLength}})`)
|
|
17
|
+
: new RegExp(`(\\d)(\\d+)`);
|
|
18
|
+
const match = value.match(reg);
|
|
19
|
+
if (match) {
|
|
20
|
+
const [, int, decimals] = match;
|
|
21
|
+
return `${int}${decimalSeparator}${decimals}`;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return value;
|
|
25
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { escapeRegExp } from './escapeRegExp';
|
|
2
|
+
import { getSuffix } from './getSuffix';
|
|
3
|
+
export const formatValue = (options) => {
|
|
4
|
+
const { value: _value, decimalSeparator, intlConfig, decimalScale, prefix = '', suffix = '' } = options;
|
|
5
|
+
if (_value === '' || _value === undefined) {
|
|
6
|
+
return '';
|
|
7
|
+
}
|
|
8
|
+
if (_value === '-') {
|
|
9
|
+
return '-';
|
|
10
|
+
}
|
|
11
|
+
const isNegative = new RegExp(`^\\d?-${prefix ? `${escapeRegExp(prefix)}?` : ''}\\d`).test(_value);
|
|
12
|
+
let value = decimalSeparator !== '.'
|
|
13
|
+
? replaceDecimalSeparator(_value, decimalSeparator, isNegative)
|
|
14
|
+
: _value;
|
|
15
|
+
if (decimalSeparator && decimalSeparator !== '-' && value.startsWith(decimalSeparator)) {
|
|
16
|
+
value = '0' + value;
|
|
17
|
+
}
|
|
18
|
+
const { locale, currency, ...formatOptions } = intlConfig || {};
|
|
19
|
+
const defaultNumberFormatOptions = {
|
|
20
|
+
...formatOptions,
|
|
21
|
+
minimumFractionDigits: decimalScale || 0,
|
|
22
|
+
maximumFractionDigits: 20
|
|
23
|
+
};
|
|
24
|
+
const numberFormatter = intlConfig
|
|
25
|
+
? new Intl.NumberFormat(locale, {
|
|
26
|
+
...defaultNumberFormatOptions,
|
|
27
|
+
...(currency && { style: 'currency', currency })
|
|
28
|
+
})
|
|
29
|
+
: new Intl.NumberFormat(undefined, defaultNumberFormatOptions);
|
|
30
|
+
const parts = numberFormatter.formatToParts(Number(value));
|
|
31
|
+
let formatted = replaceParts(parts, options);
|
|
32
|
+
const intlSuffix = getSuffix(formatted, { ...options });
|
|
33
|
+
const includeDecimalSeparator = _value.slice(-1) === decimalSeparator ? decimalSeparator : '';
|
|
34
|
+
const [, decimals] = value.match(RegExp('\\d+\\.(\\d+)')) || [];
|
|
35
|
+
if (decimalScale === undefined && decimals && decimalSeparator) {
|
|
36
|
+
if (formatted.includes(decimalSeparator)) {
|
|
37
|
+
formatted = formatted.replace(RegExp(`(\\d+)(${escapeRegExp(decimalSeparator)})(\\d+)`, 'g'), `$1$2${decimals}`);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
if (intlSuffix && !suffix) {
|
|
41
|
+
formatted = formatted.replace(intlSuffix, `${decimalSeparator}${decimals}${intlSuffix}`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
formatted = `${formatted}${decimalSeparator}${decimals}`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (suffix && includeDecimalSeparator) {
|
|
49
|
+
return `${formatted}${includeDecimalSeparator}${suffix}`;
|
|
50
|
+
}
|
|
51
|
+
if (intlSuffix && includeDecimalSeparator) {
|
|
52
|
+
return formatted.replace(intlSuffix, `${includeDecimalSeparator}${intlSuffix}`);
|
|
53
|
+
}
|
|
54
|
+
if (intlSuffix && suffix) {
|
|
55
|
+
return formatted.replace(intlSuffix, `${includeDecimalSeparator}${suffix}`);
|
|
56
|
+
}
|
|
57
|
+
return [formatted, includeDecimalSeparator, suffix].join('');
|
|
58
|
+
};
|
|
59
|
+
const replaceDecimalSeparator = (value, decimalSeparator, isNegative) => {
|
|
60
|
+
let newValue = value;
|
|
61
|
+
if (decimalSeparator && decimalSeparator !== '.') {
|
|
62
|
+
newValue = newValue.replace(RegExp(escapeRegExp(decimalSeparator), 'g'), '.');
|
|
63
|
+
if (isNegative && decimalSeparator === '-') {
|
|
64
|
+
newValue = `-${newValue.slice(1)}`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return newValue;
|
|
68
|
+
};
|
|
69
|
+
const replaceParts = (parts, { prefix, groupSeparator, decimalSeparator, decimalScale, disableGroupSeparators = false }) => {
|
|
70
|
+
return parts
|
|
71
|
+
.reduce((prev, { type, value }, i) => {
|
|
72
|
+
if (i === 0 && prefix) {
|
|
73
|
+
if (type === 'minusSign') {
|
|
74
|
+
return [value, prefix];
|
|
75
|
+
}
|
|
76
|
+
if (type === 'currency') {
|
|
77
|
+
return [...prev, prefix];
|
|
78
|
+
}
|
|
79
|
+
return [prefix, value];
|
|
80
|
+
}
|
|
81
|
+
if (type === 'currency') {
|
|
82
|
+
return prefix ? prev : [...prev, value];
|
|
83
|
+
}
|
|
84
|
+
if (type === 'group') {
|
|
85
|
+
return !disableGroupSeparators
|
|
86
|
+
? [...prev, groupSeparator !== undefined ? groupSeparator : value]
|
|
87
|
+
: prev;
|
|
88
|
+
}
|
|
89
|
+
if (type === 'decimal') {
|
|
90
|
+
if (decimalScale !== undefined && decimalScale === 0) {
|
|
91
|
+
return prev;
|
|
92
|
+
}
|
|
93
|
+
return [...prev, decimalSeparator !== undefined ? decimalSeparator : value];
|
|
94
|
+
}
|
|
95
|
+
if (type === 'fraction') {
|
|
96
|
+
return [...prev, decimalScale !== undefined ? value.slice(0, decimalScale) : value];
|
|
97
|
+
}
|
|
98
|
+
return [...prev, value];
|
|
99
|
+
}, [''])
|
|
100
|
+
.join('');
|
|
101
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const defaultConfig = {
|
|
2
|
+
currencySymbol: '',
|
|
3
|
+
groupSeparator: '',
|
|
4
|
+
decimalSeparator: '',
|
|
5
|
+
prefix: '',
|
|
6
|
+
suffix: ''
|
|
7
|
+
};
|
|
8
|
+
export const getLocaleConfig = (intlConfig) => {
|
|
9
|
+
const { locale, currency, ...formatOptions } = intlConfig || {};
|
|
10
|
+
const numberFormatter = locale
|
|
11
|
+
? new Intl.NumberFormat(locale, {
|
|
12
|
+
...formatOptions,
|
|
13
|
+
...(currency && { currency, style: 'currency' })
|
|
14
|
+
})
|
|
15
|
+
: new Intl.NumberFormat();
|
|
16
|
+
return numberFormatter.formatToParts(1000.1).reduce((prev, curr, i) => {
|
|
17
|
+
if (curr.type === 'currency') {
|
|
18
|
+
if (i === 0) {
|
|
19
|
+
return { ...prev, currencySymbol: curr.value, prefix: curr.value };
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
return { ...prev, currencySymbol: curr.value, suffix: curr.value };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (curr.type === 'group') {
|
|
26
|
+
return { ...prev, groupSeparator: curr.value };
|
|
27
|
+
}
|
|
28
|
+
if (curr.type === 'decimal') {
|
|
29
|
+
return { ...prev, decimalSeparator: curr.value };
|
|
30
|
+
}
|
|
31
|
+
return prev;
|
|
32
|
+
}, defaultConfig);
|
|
33
|
+
};
|