@canutin/svelte-currency-input 0.10.1 → 0.11.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/README.md
CHANGED
|
@@ -66,7 +66,8 @@ currency | `string` | `USD` | Overrides default currency.
|
|
|
66
66
|
name | `string` | `total` | Applies the name to the [input fields](#how-it-works) for _unformatted_ (e.g `[name=total]`) and _formatted_ (e.g. `[name=formatted-total]`) values |
|
|
67
67
|
required | `boolean` | `false` | Marks the inputs as required |
|
|
68
68
|
disabled | `boolean` | `false` | Marks the inputs as disabled |
|
|
69
|
-
placeholder | `number` `null` | `0` |
|
|
69
|
+
placeholder | `string` `number` `null` | `0` | A `string` will override the default placeholder. A `number` will override it by formatting it to the set currency. Setting it to `null` will not show a placeholder |
|
|
70
|
+
isZeroNullish | `boolean` | `false` | If `true` and when the value is `0`, it will override the default placeholder and render the formatted value in the field like any other value. _Note: this option might become the default in future versions_ |
|
|
70
71
|
autocomplete | `string` | `undefined` | Sets the autocomplete attribute. Accepts any valid HTML [autocomplete attribute values](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete#values) |
|
|
71
72
|
isNegativeAllowed | `boolean` | `true` | If `false`, forces formatting only to positive values and ignores `--positive` and `--negative` styling modifiers |
|
|
72
73
|
fractionDigits | `number` | `2` | Sets `maximumFractionDigits` in [`Intl.NumberFormat()` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#minimumfractiondigits) used for formatting the currency. Supported digits: `0` to `20` |
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<script>import { onMount } from "svelte";
|
|
2
|
+
const DEFAULT_LOCALE = "en-US";
|
|
3
|
+
const DEFAULT_CURRENCY = "USD";
|
|
4
|
+
const DEFAULT_NAME = "total";
|
|
5
|
+
const DEFAULT_VALUE = 0;
|
|
6
|
+
const DEFAULT_FRACTION_DIGITS = 2;
|
|
7
|
+
const DEFAULT_CLASS_WRAPPER = "currencyInput";
|
|
8
|
+
const DEFAULT_CLASS_UNFORMATTED = "currencyInput__unformatted";
|
|
9
|
+
const DEFAULT_CLASS_FORMATTED = "currencyInput__formatted";
|
|
10
|
+
const DEFAULT_CLASS_FORMATTED_POSITIVE = "currencyInput__formatted--positive";
|
|
11
|
+
const DEFAULT_CLASS_FORMATTED_NEGATIVE = "currencyInput__formatted--negative";
|
|
12
|
+
const DEFAULT_CLASS_FORMATTED_ZERO = "currencyInput__formatted--zero";
|
|
13
|
+
export let value = DEFAULT_VALUE;
|
|
14
|
+
export let locale = DEFAULT_LOCALE;
|
|
15
|
+
export let currency = DEFAULT_CURRENCY;
|
|
16
|
+
export let name = DEFAULT_NAME;
|
|
17
|
+
export let required = false;
|
|
18
|
+
export let disabled = false;
|
|
19
|
+
export let placeholder = DEFAULT_VALUE;
|
|
20
|
+
export let autocomplete = void 0;
|
|
21
|
+
export let isNegativeAllowed = true;
|
|
22
|
+
export let isZeroNullish = false;
|
|
23
|
+
export let fractionDigits = DEFAULT_FRACTION_DIGITS;
|
|
24
|
+
export let inputClasses = null;
|
|
25
|
+
export let onValueChange = () => {
|
|
26
|
+
};
|
|
27
|
+
const formatCurrency = (value2, maximumFractionDigits, minimumFractionDigits) => {
|
|
28
|
+
return new Intl.NumberFormat(locale, {
|
|
29
|
+
currency,
|
|
30
|
+
style: "currency",
|
|
31
|
+
maximumFractionDigits: maximumFractionDigits || 0,
|
|
32
|
+
minimumFractionDigits: minimumFractionDigits || 0
|
|
33
|
+
}).format(value2);
|
|
34
|
+
};
|
|
35
|
+
const handleKeyDown = (event) => {
|
|
36
|
+
const isDeletion = event.key === "Backspace" || event.key === "Delete";
|
|
37
|
+
const isModifier = event.metaKey || event.altKey || event.ctrlKey;
|
|
38
|
+
const isArrowKey = event.key === "ArrowLeft" || event.key === "ArrowRight";
|
|
39
|
+
const isTab = event.key === "Tab";
|
|
40
|
+
const isInvalidCharacter = !/^\d|,|\.|-$/g.test(event.key);
|
|
41
|
+
const isPunctuationDuplicated = () => {
|
|
42
|
+
if (event.key !== "," && event.key !== ".")
|
|
43
|
+
return false;
|
|
44
|
+
if (isDecimalComma)
|
|
45
|
+
return formattedValue.split(",").length >= 2;
|
|
46
|
+
if (!isDecimalComma)
|
|
47
|
+
return formattedValue.split(".").length >= 2;
|
|
48
|
+
return false;
|
|
49
|
+
};
|
|
50
|
+
if (isPunctuationDuplicated() || !isDeletion && !isModifier && !isArrowKey && isInvalidCharacter && !isTab)
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
};
|
|
53
|
+
const handleOnBlur = () => setFormattedValue(true);
|
|
54
|
+
onMount(() => setFormattedValue(true));
|
|
55
|
+
let inputTarget;
|
|
56
|
+
const currencyDecimal = new Intl.NumberFormat(locale).format(1.1).charAt(1);
|
|
57
|
+
const isDecimalComma = currencyDecimal === ",";
|
|
58
|
+
const currencySymbol = formatCurrency(0, 0).replace("0", "").replace(/\u00A0/, "");
|
|
59
|
+
const setUnformattedValue = (event) => {
|
|
60
|
+
if (event) {
|
|
61
|
+
if (event.key === currencyDecimal)
|
|
62
|
+
return;
|
|
63
|
+
if (isDecimalComma && event.key === ".")
|
|
64
|
+
formattedValue = formattedValue.replace(/\.([^.]*)$/, currencyDecimal + "$1");
|
|
65
|
+
if (!isDecimalComma && event.key === ",")
|
|
66
|
+
formattedValue = formattedValue.replace(/\,([^,]*)$/, currencyDecimal + "$1");
|
|
67
|
+
const ignoreSymbols = [currencySymbol, `-${currencySymbol}`, "-"];
|
|
68
|
+
const strippedUnformattedValue = formattedValue.replace(" ", "");
|
|
69
|
+
if (ignoreSymbols.includes(strippedUnformattedValue))
|
|
70
|
+
return;
|
|
71
|
+
inputTarget = event.target;
|
|
72
|
+
if (isNegativeAllowed && event.key === "-")
|
|
73
|
+
value = value * -1;
|
|
74
|
+
}
|
|
75
|
+
let unformattedValue = isNegativeAllowed ? formattedValue.replace(/[^0-9,.-]/g, "") : formattedValue.replace(/[^0-9,.]/g, "");
|
|
76
|
+
if (Number.isNaN(parseFloat(unformattedValue))) {
|
|
77
|
+
value = 0;
|
|
78
|
+
} else {
|
|
79
|
+
unformattedValue = unformattedValue.replace(isDecimalComma ? /\./g : /\,/g, "");
|
|
80
|
+
if (isDecimalComma)
|
|
81
|
+
unformattedValue = unformattedValue.replace(",", ".");
|
|
82
|
+
const previousValue = value;
|
|
83
|
+
value = parseFloat(unformattedValue);
|
|
84
|
+
if (event && previousValue === value) {
|
|
85
|
+
if (unformattedValue.includes(".") && unformattedValue.split(".")[1].length > fractionDigits) {
|
|
86
|
+
setFormattedValue();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
const setFormattedValue = (hasMinFractionDigits) => {
|
|
92
|
+
const startCaretPosition = inputTarget?.selectionStart || 0;
|
|
93
|
+
const previousFormattedValueLength = formattedValue.length;
|
|
94
|
+
formattedValue = isZero && !isZeroNullish ? "" : formatCurrency(value, fractionDigits, hasMinFractionDigits ? fractionDigits : 0);
|
|
95
|
+
setUnformattedValue();
|
|
96
|
+
let retries = 0;
|
|
97
|
+
while (previousFormattedValueLength === formattedValue.length && retries < 10)
|
|
98
|
+
retries++;
|
|
99
|
+
if (previousFormattedValueLength !== formattedValue.length) {
|
|
100
|
+
const endCaretPosition = startCaretPosition + formattedValue.length - previousFormattedValueLength;
|
|
101
|
+
inputTarget?.setSelectionRange(endCaretPosition, endCaretPosition);
|
|
102
|
+
}
|
|
103
|
+
onValueChange(value);
|
|
104
|
+
};
|
|
105
|
+
const handlePlaceholder = (placeholder2) => {
|
|
106
|
+
if (typeof placeholder2 === "number")
|
|
107
|
+
return formatCurrency(placeholder2, fractionDigits, fractionDigits);
|
|
108
|
+
if (placeholder2 === null)
|
|
109
|
+
return "";
|
|
110
|
+
return placeholder2;
|
|
111
|
+
};
|
|
112
|
+
let formattedValue = "";
|
|
113
|
+
$:
|
|
114
|
+
isNegative = value < 0;
|
|
115
|
+
$:
|
|
116
|
+
isPositive = value > 0;
|
|
117
|
+
$:
|
|
118
|
+
isZero = !isNegative && !isPositive;
|
|
119
|
+
$:
|
|
120
|
+
value, setFormattedValue();
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<div class={inputClasses?.wrapper ?? DEFAULT_CLASS_WRAPPER}>
|
|
124
|
+
<input
|
|
125
|
+
class={inputClasses?.unformatted ?? DEFAULT_CLASS_UNFORMATTED}
|
|
126
|
+
type="hidden"
|
|
127
|
+
{name}
|
|
128
|
+
{disabled}
|
|
129
|
+
bind:value
|
|
130
|
+
/>
|
|
131
|
+
<input
|
|
132
|
+
class="
|
|
133
|
+
{inputClasses?.formatted ?? DEFAULT_CLASS_FORMATTED}
|
|
134
|
+
{isNegativeAllowed && !isZero && !isNegative
|
|
135
|
+
? inputClasses?.formattedPositive ?? DEFAULT_CLASS_FORMATTED_POSITIVE
|
|
136
|
+
: ''}
|
|
137
|
+
{isZero ? inputClasses?.formattedZero ?? DEFAULT_CLASS_FORMATTED_ZERO : ''}
|
|
138
|
+
{isNegativeAllowed && isNegative
|
|
139
|
+
? inputClasses?.formattedNegative ?? DEFAULT_CLASS_FORMATTED_NEGATIVE
|
|
140
|
+
: ''}
|
|
141
|
+
"
|
|
142
|
+
type="text"
|
|
143
|
+
inputmode="numeric"
|
|
144
|
+
name={`formatted-${name}`}
|
|
145
|
+
required={required && !isZero}
|
|
146
|
+
placeholder={handlePlaceholder(placeholder)}
|
|
147
|
+
{autocomplete}
|
|
148
|
+
{disabled}
|
|
149
|
+
bind:value={formattedValue}
|
|
150
|
+
on:keydown={handleKeyDown}
|
|
151
|
+
on:keyup={setUnformattedValue}
|
|
152
|
+
on:blur={handleOnBlur}
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<style>
|
|
157
|
+
input.currencyInput__formatted {
|
|
158
|
+
border: 1px solid #e2e2e2;
|
|
159
|
+
padding: 10px;
|
|
160
|
+
box-sizing: border-box;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
input.currencyInput__formatted--zero {
|
|
164
|
+
color: #333;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
input.currencyInput__formatted--positive {
|
|
168
|
+
color: #00a36f;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
input.currencyInput__formatted--negative {
|
|
172
|
+
color: #e75258;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
input.currencyInput__formatted:disabled {
|
|
176
|
+
color: #999;
|
|
177
|
+
background-color: #e2e2e2;
|
|
178
|
+
pointer-events: none;
|
|
179
|
+
cursor: default;
|
|
180
|
+
}
|
|
181
|
+
</style>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
2
|
declare const __propDef: {
|
|
3
3
|
props: {
|
|
4
4
|
value?: number | undefined;
|
|
@@ -7,9 +7,10 @@ declare const __propDef: {
|
|
|
7
7
|
name?: string | undefined;
|
|
8
8
|
required?: boolean | undefined;
|
|
9
9
|
disabled?: boolean | undefined;
|
|
10
|
-
placeholder?: number | null | undefined;
|
|
10
|
+
placeholder?: string | number | null | undefined;
|
|
11
11
|
autocomplete?: string | null | undefined;
|
|
12
12
|
isNegativeAllowed?: boolean | undefined;
|
|
13
|
+
isZeroNullish?: boolean | undefined;
|
|
13
14
|
fractionDigits?: number | undefined;
|
|
14
15
|
inputClasses?: {
|
|
15
16
|
wrapper?: string | undefined;
|
|
@@ -26,9 +27,9 @@ declare const __propDef: {
|
|
|
26
27
|
};
|
|
27
28
|
slots: {};
|
|
28
29
|
};
|
|
29
|
-
export
|
|
30
|
-
export
|
|
31
|
-
export
|
|
32
|
-
export default class CurrencyInput extends
|
|
30
|
+
export type CurrencyInputProps = typeof __propDef.props;
|
|
31
|
+
export type CurrencyInputEvents = typeof __propDef.events;
|
|
32
|
+
export type CurrencyInputSlots = typeof __propDef.slots;
|
|
33
|
+
export default class CurrencyInput extends SvelteComponent<CurrencyInputProps, CurrencyInputEvents, CurrencyInputSlots> {
|
|
33
34
|
}
|
|
34
35
|
export {};
|
package/package.json
CHANGED
|
@@ -1,51 +1,73 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
2
|
+
"name": "@canutin/svelte-currency-input",
|
|
3
|
+
"version": "0.11.1",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"dev": "vite dev",
|
|
6
|
+
"build": "vite build",
|
|
7
|
+
"preview": "vite preview",
|
|
8
|
+
"package": "svelte-kit sync && svelte-package && publint",
|
|
9
|
+
"test": "playwright test",
|
|
10
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
11
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
12
|
+
"lint": "prettier --plugin-search-dir . --check . && eslint .",
|
|
13
|
+
"format": "prettier --plugin-search-dir . --write ."
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"svelte": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"!dist/**/*.test.*",
|
|
24
|
+
"!dist/**/*.spec.*"
|
|
25
|
+
],
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"svelte": "^4.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@playwright/test": "^1.39.0",
|
|
31
|
+
"@sveltejs/adapter-auto": "^2.0.0",
|
|
32
|
+
"@sveltejs/adapter-cloudflare": "^2.3.3",
|
|
33
|
+
"@sveltejs/kit": "^1.26.0",
|
|
34
|
+
"@sveltejs/package": "^2.2.2",
|
|
35
|
+
"@types/node": "^20.8.7",
|
|
36
|
+
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
|
37
|
+
"@typescript-eslint/parser": "^6.0.0",
|
|
38
|
+
"date-fns": "^2.30.0",
|
|
39
|
+
"eslint": "^8.28.0",
|
|
40
|
+
"eslint-config-prettier": "^8.5.0",
|
|
41
|
+
"eslint-plugin-svelte": "^2.30.0",
|
|
42
|
+
"prettier": "^2.8.0",
|
|
43
|
+
"prettier-plugin-svelte": "^2.10.1",
|
|
44
|
+
"publint": "^0.2.5",
|
|
45
|
+
"sass": "^1.69.0",
|
|
46
|
+
"semantic-release": "^19.0.5",
|
|
47
|
+
"svelte-check": "^3.4.3",
|
|
48
|
+
"tslib": "^2.4.1",
|
|
49
|
+
"typescript": "^5.0.0",
|
|
50
|
+
"vite": "^4.4.2"
|
|
51
|
+
},
|
|
52
|
+
"type": "module",
|
|
53
|
+
"description": "A form input that converts numbers to currencies as you type in localized formats",
|
|
54
|
+
"keywords": [
|
|
55
|
+
"svelte",
|
|
56
|
+
"currency",
|
|
57
|
+
"money",
|
|
58
|
+
"input",
|
|
59
|
+
"i18n",
|
|
60
|
+
"positive",
|
|
61
|
+
"negative"
|
|
62
|
+
],
|
|
63
|
+
"repository": {
|
|
64
|
+
"type": "git",
|
|
65
|
+
"url": "git+https://github.com/fmaclen/svelte-currency-input.git"
|
|
66
|
+
},
|
|
67
|
+
"author": "Fernando Maclen <hello@fernando.is>",
|
|
68
|
+
"license": "MIT",
|
|
69
|
+
"bugs": {
|
|
70
|
+
"url": "https://github.com/fmaclen/svelte-currency-input/issues"
|
|
71
|
+
},
|
|
72
|
+
"homepage": "https://svelte-currency-input.fernando.is"
|
|
51
73
|
}
|
package/CurrencyInput.svelte
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
<script>import { onMount } from "svelte";
|
|
2
|
-
const DEFAULT_LOCALE = 'en-US';
|
|
3
|
-
const DEFAULT_CURRENCY = 'USD';
|
|
4
|
-
const DEFAULT_NAME = 'total';
|
|
5
|
-
const DEFAULT_VALUE = 0;
|
|
6
|
-
const DEFAULT_FRACTION_DIGITS = 2;
|
|
7
|
-
const DEFAULT_CLASS_WRAPPER = 'currencyInput';
|
|
8
|
-
const DEFAULT_CLASS_UNFORMATTED = 'currencyInput__unformatted';
|
|
9
|
-
const DEFAULT_CLASS_FORMATTED = 'currencyInput__formatted';
|
|
10
|
-
const DEFAULT_CLASS_FORMATTED_POSITIVE = 'currencyInput__formatted--positive';
|
|
11
|
-
const DEFAULT_CLASS_FORMATTED_NEGATIVE = 'currencyInput__formatted--negative';
|
|
12
|
-
const DEFAULT_CLASS_FORMATTED_ZERO = 'currencyInput__formatted--zero';
|
|
13
|
-
export let value = DEFAULT_VALUE;
|
|
14
|
-
export let locale = DEFAULT_LOCALE;
|
|
15
|
-
export let currency = DEFAULT_CURRENCY;
|
|
16
|
-
export let name = DEFAULT_NAME;
|
|
17
|
-
export let required = false;
|
|
18
|
-
export let disabled = false;
|
|
19
|
-
export let placeholder = DEFAULT_VALUE;
|
|
20
|
-
export let autocomplete = undefined;
|
|
21
|
-
export let isNegativeAllowed = true;
|
|
22
|
-
export let fractionDigits = DEFAULT_FRACTION_DIGITS;
|
|
23
|
-
export let inputClasses = null;
|
|
24
|
-
export let onValueChange = () => { };
|
|
25
|
-
// Formats value as: e.g. $1,523.00 | -$1,523.00
|
|
26
|
-
const formatCurrency = (value, maximumFractionDigits, minimumFractionDigits) => {
|
|
27
|
-
return new Intl.NumberFormat(locale, {
|
|
28
|
-
currency: currency,
|
|
29
|
-
style: 'currency',
|
|
30
|
-
maximumFractionDigits: maximumFractionDigits || 0,
|
|
31
|
-
minimumFractionDigits: minimumFractionDigits || 0
|
|
32
|
-
}).format(value);
|
|
33
|
-
};
|
|
34
|
-
// Checks if the key pressed is allowed
|
|
35
|
-
const handleKeyDown = (event) => {
|
|
36
|
-
const isDeletion = event.key === 'Backspace' || event.key === 'Delete';
|
|
37
|
-
const isModifier = event.metaKey || event.altKey || event.ctrlKey;
|
|
38
|
-
const isArrowKey = event.key === 'ArrowLeft' || event.key === 'ArrowRight';
|
|
39
|
-
const isTab = event.key === 'Tab';
|
|
40
|
-
const isInvalidCharacter = !/^\d|,|\.|-$/g.test(event.key); // Keys that are not a digit, comma, period or minus sign
|
|
41
|
-
if (!isDeletion && !isModifier && !isArrowKey && isInvalidCharacter && !isTab)
|
|
42
|
-
event.preventDefault();
|
|
43
|
-
};
|
|
44
|
-
// Formats the value when the input loses focus and sets the correct number of
|
|
45
|
-
// fraction digits when the value is zero
|
|
46
|
-
const handleOnBlur = () => setFormattedValue(true);
|
|
47
|
-
// Also set the correct fraction digits when the value is zero on initial load
|
|
48
|
-
onMount(() => setFormattedValue(true));
|
|
49
|
-
let inputTarget;
|
|
50
|
-
const currencyDecimal = new Intl.NumberFormat(locale).format(1.1).charAt(1); // '.' or ','
|
|
51
|
-
const isDecimalComma = currencyDecimal === ',';
|
|
52
|
-
const currencySymbol = formatCurrency(0, 0)
|
|
53
|
-
.replace('0', '') // e.g. '$0' > '$'
|
|
54
|
-
.replace(/\u00A0/, ''); // e.g '0 €' > '€'
|
|
55
|
-
// Updates `value` by stripping away the currency formatting
|
|
56
|
-
const setUnformattedValue = (event) => {
|
|
57
|
-
if (event) {
|
|
58
|
-
// Don't format if the user is typing a `currencyDecimal` point
|
|
59
|
-
if (event.key === currencyDecimal)
|
|
60
|
-
return;
|
|
61
|
-
// Pressing `.` when the decimal point is `,` gets replaced with `,`
|
|
62
|
-
if (isDecimalComma && event.key === '.')
|
|
63
|
-
formattedValue = formattedValue.replace(/\.([^.]*)$/, currencyDecimal + '$1'); // Only replace the last occurence
|
|
64
|
-
// Pressing `,` when the decimal point is `.` gets replaced with `.`
|
|
65
|
-
if (!isDecimalComma && event.key === ',')
|
|
66
|
-
formattedValue = formattedValue.replace(/\,([^,]*)$/, currencyDecimal + '$1'); // Only replace the last occurence
|
|
67
|
-
// Don't format if `formattedValue` is ['$', '-$', "-"]
|
|
68
|
-
const ignoreSymbols = [currencySymbol, `-${currencySymbol}`, '-'];
|
|
69
|
-
const strippedUnformattedValue = formattedValue.replace(' ', '');
|
|
70
|
-
if (ignoreSymbols.includes(strippedUnformattedValue))
|
|
71
|
-
return;
|
|
72
|
-
// Set the starting caret positions
|
|
73
|
-
inputTarget = event.target;
|
|
74
|
-
// Reverse the value when minus is pressed
|
|
75
|
-
if (isNegativeAllowed && event.key === '-')
|
|
76
|
-
value = value * -1;
|
|
77
|
-
}
|
|
78
|
-
// Remove all characters that arent: numbers, commas, periods (or minus signs if `isNegativeAllowed`)
|
|
79
|
-
let unformattedValue = isNegativeAllowed
|
|
80
|
-
? formattedValue.replace(/[^0-9,.-]/g, '')
|
|
81
|
-
: formattedValue.replace(/[^0-9,.]/g, '');
|
|
82
|
-
// Finally set the value
|
|
83
|
-
if (Number.isNaN(parseFloat(unformattedValue))) {
|
|
84
|
-
value = 0;
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
// The order of the following operations is *critical*
|
|
88
|
-
unformattedValue = unformattedValue.replace(isDecimalComma ? /\./g : /\,/g, ''); // Remove all group symbols
|
|
89
|
-
if (isDecimalComma)
|
|
90
|
-
unformattedValue = unformattedValue.replace(',', '.'); // If the decimal point is a comma, replace it with a period
|
|
91
|
-
// If the zero-key has been pressed
|
|
92
|
-
// and if the current `value` is the same as the `value` before the key-press
|
|
93
|
-
// formatting may need to be done (Issue #30)
|
|
94
|
-
const previousValue = value;
|
|
95
|
-
value = parseFloat(unformattedValue);
|
|
96
|
-
if (event && previousValue === value) {
|
|
97
|
-
// Do the formatting if the number of digits after the decimal point exceeds `fractionDigits`
|
|
98
|
-
if (unformattedValue.includes('.') &&
|
|
99
|
-
unformattedValue.split('.')[1].length > fractionDigits) {
|
|
100
|
-
setFormattedValue();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
const setFormattedValue = (hasMinFractionDigits) => {
|
|
106
|
-
// Previous caret position
|
|
107
|
-
const startCaretPosition = inputTarget?.selectionStart || 0;
|
|
108
|
-
const previousFormattedValueLength = formattedValue.length;
|
|
109
|
-
// Apply formatting to input
|
|
110
|
-
formattedValue = isZero ? '' : formatCurrency(value, fractionDigits, hasMinFractionDigits ? fractionDigits : 0);
|
|
111
|
-
// Update `value` after formatting
|
|
112
|
-
setUnformattedValue();
|
|
113
|
-
let retries = 0;
|
|
114
|
-
while (previousFormattedValueLength === formattedValue.length && retries < 10)
|
|
115
|
-
retries++;
|
|
116
|
-
if (previousFormattedValueLength !== formattedValue.length) {
|
|
117
|
-
const endCaretPosition = startCaretPosition + formattedValue.length - previousFormattedValueLength;
|
|
118
|
-
inputTarget?.setSelectionRange(endCaretPosition, endCaretPosition);
|
|
119
|
-
}
|
|
120
|
-
// Run callback function when `value` changes
|
|
121
|
-
onValueChange(value);
|
|
122
|
-
};
|
|
123
|
-
let formattedValue = '';
|
|
124
|
-
let formattedPlaceholder = placeholder !== null ? formatCurrency(placeholder, fractionDigits, fractionDigits) : '';
|
|
125
|
-
$: isZero = value === 0;
|
|
126
|
-
$: isNegative = value < 0;
|
|
127
|
-
$: value, setFormattedValue();
|
|
128
|
-
</script>
|
|
129
|
-
|
|
130
|
-
<div class={inputClasses?.wrapper ?? DEFAULT_CLASS_WRAPPER}>
|
|
131
|
-
<input
|
|
132
|
-
class={inputClasses?.unformatted ?? DEFAULT_CLASS_UNFORMATTED}
|
|
133
|
-
type="hidden"
|
|
134
|
-
{name}
|
|
135
|
-
{disabled}
|
|
136
|
-
bind:value
|
|
137
|
-
/>
|
|
138
|
-
<input
|
|
139
|
-
class="
|
|
140
|
-
{inputClasses?.formatted ?? DEFAULT_CLASS_FORMATTED}
|
|
141
|
-
{isNegativeAllowed && !isZero && !isNegative
|
|
142
|
-
? inputClasses?.formattedPositive ?? DEFAULT_CLASS_FORMATTED_POSITIVE
|
|
143
|
-
: ''}
|
|
144
|
-
{isZero ? inputClasses?.formattedZero ?? DEFAULT_CLASS_FORMATTED_ZERO : ''}
|
|
145
|
-
{isNegativeAllowed && isNegative
|
|
146
|
-
? inputClasses?.formattedNegative ?? DEFAULT_CLASS_FORMATTED_NEGATIVE
|
|
147
|
-
: ''}
|
|
148
|
-
"
|
|
149
|
-
type="text"
|
|
150
|
-
inputmode="numeric"
|
|
151
|
-
name={`formatted-${name}`}
|
|
152
|
-
required={required && !isZero}
|
|
153
|
-
placeholder={formattedPlaceholder}
|
|
154
|
-
{autocomplete}
|
|
155
|
-
{disabled}
|
|
156
|
-
bind:value={formattedValue}
|
|
157
|
-
on:keydown={handleKeyDown}
|
|
158
|
-
on:keyup={setUnformattedValue}
|
|
159
|
-
on:blur={handleOnBlur}
|
|
160
|
-
/>
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
<style>
|
|
164
|
-
input.currencyInput__formatted {
|
|
165
|
-
border: 1px solid #e2e2e2;
|
|
166
|
-
padding: 10px;
|
|
167
|
-
box-sizing: border-box;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
input.currencyInput__formatted--zero {
|
|
171
|
-
color: #333;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
input.currencyInput__formatted--positive {
|
|
175
|
-
color: #00a36f;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
input.currencyInput__formatted--negative {
|
|
179
|
-
color: #e75258;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
input.currencyInput__formatted:disabled {
|
|
183
|
-
color: #999;
|
|
184
|
-
background-color: #e2e2e2;
|
|
185
|
-
pointer-events: none;
|
|
186
|
-
cursor: default;
|
|
187
|
-
}
|
|
188
|
-
</style>
|
|
File without changes
|
|
File without changes
|