@canutin/svelte-currency-input 0.3.1 → 0.5.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/README.md +11 -9
- package/package.json +1 -1
- package/src/lib/CurrencyInput.svelte +14 -10
- package/src/routes/+page.svelte +9 -3
- package/tests/svelte-currency-input.test.ts +54 -13
package/README.md
CHANGED
|
@@ -58,15 +58,17 @@ This is more or less what `<CurrencyInput />` looks like under the hood:
|
|
|
58
58
|
|
|
59
59
|
## API
|
|
60
60
|
|
|
61
|
-
| Option | Type
|
|
62
|
-
| ----------------- |
|
|
63
|
-
| value | `number`
|
|
64
|
-
| locale | `string`
|
|
65
|
-
| currency | `string`
|
|
66
|
-
| name | `string`
|
|
67
|
-
| required | `boolean`
|
|
68
|
-
| disabled | `boolean`
|
|
69
|
-
|
|
|
61
|
+
| Option | Type | Default | Description |
|
|
62
|
+
| ----------------- | --------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
63
|
+
| value | `number` | `undefined` | Initial value. If left `undefined` a formatted value of `0` is visible as a placeholder |
|
|
64
|
+
| locale | `string` | `en-US` | Overrides default locale. [Examples](https://gist.github.com/ncreated/9934896) |
|
|
65
|
+
| currency | `string` | `USD` | Overrides default currency. [Examples](https://github.com/datasets/currency-codes/blob/master/data/codes-all.csv) |
|
|
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
|
+
| required | `boolean` | `false` | Marks the inputs as required |
|
|
68
|
+
| disabled | `boolean` | `false` | Marks the inputs as disabled |
|
|
69
|
+
| placeholder | `number` `null` | `0` | Overrides the default placeholder. Setting the value to a `number` will display it as formatted. Setting it to `null` will not show a placeholder |
|
|
70
|
+
| isNegativeAllowed | `boolean` | `true` | If `false`, forces formatting only to positive values and ignores `--positive` and `--negative` styling modifiers |
|
|
71
|
+
| 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` |
|
|
70
72
|
|
|
71
73
|
## Styling
|
|
72
74
|
|
package/package.json
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const DEFAULT_CURRENCY = 'USD';
|
|
4
4
|
const DEFAULT_NAME = 'total';
|
|
5
5
|
const DEFAULT_VALUE = 0;
|
|
6
|
+
const DEFAULT_FRACTION_DIGITS = 2;
|
|
6
7
|
|
|
7
8
|
export let value: number = DEFAULT_VALUE;
|
|
8
9
|
export let locale: string = DEFAULT_LOCALE;
|
|
@@ -10,7 +11,9 @@
|
|
|
10
11
|
export let name: string = DEFAULT_NAME;
|
|
11
12
|
export let required: boolean = false;
|
|
12
13
|
export let disabled: boolean = false;
|
|
14
|
+
export let placeholder: number | null = DEFAULT_VALUE;
|
|
13
15
|
export let isNegativeAllowed: boolean = true;
|
|
16
|
+
export let fractionDigits: number = DEFAULT_FRACTION_DIGITS;
|
|
14
17
|
|
|
15
18
|
// Formats value as: e.g. $1,523.00 | -$1,523.00
|
|
16
19
|
const formatCurrency = (
|
|
@@ -36,16 +39,18 @@
|
|
|
36
39
|
if (!isDeletion && !isModifier && !isArrowKey && isInvalidCharacter) event.preventDefault();
|
|
37
40
|
};
|
|
38
41
|
|
|
42
|
+
const currencyDecimal = new Intl.NumberFormat(locale).format(1.1).charAt(1); // '.' or ','
|
|
43
|
+
const isDecimalComma = currencyDecimal === ','; // Remove currency formatting from `formattedValue` so we can assign it to `value`
|
|
44
|
+
const currencySymbol = formatCurrency(0, 0)
|
|
45
|
+
.replace('0', '') // e.g. '$0' > '$'
|
|
46
|
+
.replace(/\u00A0/, ''); // e.g '0 €' > '€'
|
|
47
|
+
|
|
39
48
|
// Updates `value` by stripping away the currency formatting
|
|
40
49
|
const setUnformattedValue = (event: KeyboardEvent) => {
|
|
41
50
|
// Don't format if the user is typing a `currencyDecimal` point
|
|
42
|
-
const currencyDecimal = new Intl.NumberFormat(locale).format(1.1).charAt(1); // '.' or ','
|
|
43
51
|
if (event.key === currencyDecimal) return;
|
|
44
52
|
|
|
45
|
-
//
|
|
46
|
-
const currencySymbol = formatCurrency(0, 0)
|
|
47
|
-
.replace('0', '') // e.g. '$0' > '$'
|
|
48
|
-
.replace(/\u00A0/, ''); // e.g '0 €' > '€'
|
|
53
|
+
// Don't format if `formattedValue` is ['$', '-$', "-"]
|
|
49
54
|
const ignoreSymbols = [currencySymbol, `-${currencySymbol}`, '-'];
|
|
50
55
|
const strippedUnformattedValue = formattedValue.replace(' ', '');
|
|
51
56
|
if (ignoreSymbols.includes(strippedUnformattedValue)) return;
|
|
@@ -63,7 +68,6 @@
|
|
|
63
68
|
value = 0;
|
|
64
69
|
} else {
|
|
65
70
|
// The order of the following operations is *critical*
|
|
66
|
-
const isDecimalComma = currencyDecimal === ','; // Remove currency formatting from `formattedValue` so we can assign it to `value`
|
|
67
71
|
unformattedValue = unformattedValue.replace(isDecimalComma ? /\./g : /\,/g, ''); // Remove all group symbols
|
|
68
72
|
if (isDecimalComma) unformattedValue = unformattedValue.replace(',', '.'); // If the decimal point is a comma, replace it with a period
|
|
69
73
|
value = parseFloat(unformattedValue);
|
|
@@ -71,15 +75,15 @@
|
|
|
71
75
|
};
|
|
72
76
|
|
|
73
77
|
const setFormattedValue = () => {
|
|
74
|
-
formattedValue = isZero ? '' : formatCurrency(value,
|
|
78
|
+
formattedValue = isZero ? '' : formatCurrency(value, fractionDigits, 0);
|
|
75
79
|
};
|
|
76
80
|
|
|
77
81
|
let formattedValue = '';
|
|
82
|
+
let formattedPlaceholder =
|
|
83
|
+
placeholder !== null ? formatCurrency(placeholder, fractionDigits, fractionDigits) : '';
|
|
78
84
|
$: isZero = value === 0;
|
|
79
85
|
$: isNegative = value < 0;
|
|
80
86
|
$: value, setFormattedValue();
|
|
81
|
-
|
|
82
|
-
const placeholder = formatCurrency(DEFAULT_VALUE, 2, 2); // e.g. '$0.00'
|
|
83
87
|
</script>
|
|
84
88
|
|
|
85
89
|
<div class="currencyInput">
|
|
@@ -95,7 +99,7 @@
|
|
|
95
99
|
inputmode="numeric"
|
|
96
100
|
name={`formatted-${name}`}
|
|
97
101
|
required={required && !isZero}
|
|
98
|
-
{
|
|
102
|
+
placeholder={formattedPlaceholder}
|
|
99
103
|
{disabled}
|
|
100
104
|
bind:value={formattedValue}
|
|
101
105
|
on:keydown={handleKeyDown}
|
package/src/routes/+page.svelte
CHANGED
|
@@ -18,13 +18,19 @@
|
|
|
18
18
|
<div class="demoForm__container">
|
|
19
19
|
<CurrencyInput name="total" value={-42069.69} />
|
|
20
20
|
<CurrencyInput name="rent" />
|
|
21
|
-
<CurrencyInput name="
|
|
22
|
-
<CurrencyInput name="
|
|
21
|
+
<CurrencyInput name="balance" value={1234.56} isNegativeAllowed={false} placeholder={null} />
|
|
22
|
+
<CurrencyInput name="btc" value={0.87654321} fractionDigits={8} />
|
|
23
23
|
|
|
24
24
|
<CurrencyInput name="amount" value={5678.9} {locale} {currency} />
|
|
25
|
-
<CurrencyInput name="deficit" value={1234.56} isNegativeAllowed={false} {locale} {currency} />
|
|
26
25
|
<CurrencyInput name="loss" value={97532.95} disabled={true} {locale} {currency} />
|
|
27
26
|
<CurrencyInput name="cost" value={-42069.69} {locale} {currency} />
|
|
27
|
+
<CurrencyInput
|
|
28
|
+
name="deficit"
|
|
29
|
+
placeholder={1234.56}
|
|
30
|
+
isNegativeAllowed={false}
|
|
31
|
+
{locale}
|
|
32
|
+
{currency}
|
|
33
|
+
/>
|
|
28
34
|
</div>
|
|
29
35
|
|
|
30
36
|
<nav class="demoForm__output">
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { expect, test } from '@playwright/test';
|
|
1
|
+
import { expect, test, type Page } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
const isMacOs = process.platform === 'darwin';
|
|
4
|
+
const selectAll = async (page: Page) => {
|
|
5
|
+
isMacOs ? await page.keyboard.press('Meta+A') : await page.keyboard.press('Control+A');
|
|
6
|
+
};
|
|
2
7
|
|
|
3
8
|
test.describe('CurrencyInput', () => {
|
|
4
9
|
test('Default behavior is correct', async ({ page }) => {
|
|
@@ -71,16 +76,16 @@ test.describe('CurrencyInput', () => {
|
|
|
71
76
|
'formatted-total': '-$42,069.69',
|
|
72
77
|
rent: '0',
|
|
73
78
|
'formatted-rent': '',
|
|
74
|
-
cashflow: '5678.9',
|
|
75
|
-
'formatted-cashflow': '$5,678.9',
|
|
76
79
|
balance: '1234.56',
|
|
77
80
|
'formatted-balance': '$1,234.56',
|
|
81
|
+
btc: '0.87654321',
|
|
82
|
+
'formatted-btc': '$0.87654321',
|
|
78
83
|
amount: '5678.9',
|
|
79
84
|
'formatted-amount': '€ 5.678,9',
|
|
80
|
-
deficit: '1234.56',
|
|
81
|
-
'formatted-deficit': '€ 1.234,56',
|
|
82
85
|
cost: '-42069.69',
|
|
83
|
-
'formatted-cost': '€ -42.069,69'
|
|
86
|
+
'formatted-cost': '€ -42.069,69',
|
|
87
|
+
deficit: '0',
|
|
88
|
+
'formatted-deficit': ''
|
|
84
89
|
},
|
|
85
90
|
null,
|
|
86
91
|
2
|
|
@@ -139,7 +144,6 @@ test.describe('CurrencyInput', () => {
|
|
|
139
144
|
test("Incorrect characters can't be entered", async ({ page }) => {
|
|
140
145
|
await page.goto('/');
|
|
141
146
|
|
|
142
|
-
const isMacOs = process.platform === 'darwin';
|
|
143
147
|
const rentUnformattedInput = page.locator('.currencyInput__unformatted[name=rent]');
|
|
144
148
|
const rentFormattedInput = page.locator('.currencyInput__formatted[name="formatted-rent"]');
|
|
145
149
|
|
|
@@ -162,10 +166,8 @@ test.describe('CurrencyInput', () => {
|
|
|
162
166
|
await expect(rentFormattedInput).toHaveValue('$420.69');
|
|
163
167
|
await expect(rentUnformattedInput).toHaveValue('420.69');
|
|
164
168
|
|
|
165
|
-
// Select all
|
|
166
|
-
isMacOs ? await page.keyboard.press('Meta+A') : await page.keyboard.press('Control+A');
|
|
167
|
-
|
|
168
169
|
// Check "Backspace" works
|
|
170
|
+
await selectAll(page);
|
|
169
171
|
await page.keyboard.press('Backspace');
|
|
170
172
|
await expect(rentUnformattedInput).toHaveValue('0');
|
|
171
173
|
await expect(rentFormattedInput).toHaveValue('');
|
|
@@ -175,15 +177,54 @@ test.describe('CurrencyInput', () => {
|
|
|
175
177
|
await expect(rentFormattedInput).toHaveValue('-$420.69');
|
|
176
178
|
await expect(rentUnformattedInput).toHaveValue('-420.69');
|
|
177
179
|
|
|
178
|
-
// Select all
|
|
179
|
-
isMacOs ? await page.keyboard.press('Meta+A') : await page.keyboard.press('Control+A');
|
|
180
|
-
|
|
181
180
|
// Check "Delete" also works
|
|
181
|
+
await selectAll(page);
|
|
182
182
|
await page.keyboard.press('Delete');
|
|
183
183
|
await expect(rentUnformattedInput).toHaveValue('0');
|
|
184
184
|
await expect(rentFormattedInput).toHaveValue('');
|
|
185
185
|
});
|
|
186
186
|
|
|
187
|
+
test('Placeholders can be overriden', async ({ page }) => {
|
|
188
|
+
await page.goto('/');
|
|
189
|
+
|
|
190
|
+
// Default placeholder
|
|
191
|
+
const rentFormattedInput = page.locator('.currencyInput__formatted[name="formatted-rent"]');
|
|
192
|
+
await expect(rentFormattedInput).toHaveAttribute('placeholder', '$0.00');
|
|
193
|
+
|
|
194
|
+
// Null placeholder
|
|
195
|
+
const balanceFormattedInput = page.locator(
|
|
196
|
+
'.currencyInput__formatted[name="formatted-balance"]'
|
|
197
|
+
);
|
|
198
|
+
await expect(balanceFormattedInput).toHaveAttribute('placeholder', '');
|
|
199
|
+
|
|
200
|
+
// Overriden placeholder
|
|
201
|
+
const deficitFormattedInput = page.locator(
|
|
202
|
+
'.currencyInput__formatted[name="formatted-deficit"]'
|
|
203
|
+
);
|
|
204
|
+
await expect(deficitFormattedInput).toHaveAttribute('placeholder', '€ 1.234,56'); // The space is `%A0`, not `%20`
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('Fraction digits can be overriden', async ({ page }) => {
|
|
208
|
+
await page.goto('/');
|
|
209
|
+
|
|
210
|
+
const btcUnformattedInput = page.locator('.currencyInput__unformatted[name=btc]');
|
|
211
|
+
const btcFormattedInput = page.locator('.currencyInput__formatted[name="formatted-btc"]');
|
|
212
|
+
|
|
213
|
+
await expect(btcUnformattedInput).toHaveValue('0.87654321');
|
|
214
|
+
await expect(btcFormattedInput).toHaveValue('$0.87654321');
|
|
215
|
+
await expect(btcFormattedInput).toHaveAttribute('placeholder', '$0.00000000');
|
|
216
|
+
|
|
217
|
+
await btcFormattedInput.focus();
|
|
218
|
+
await selectAll(page);
|
|
219
|
+
await page.keyboard.press('Backspace');
|
|
220
|
+
await expect(btcUnformattedInput).toHaveValue('0');
|
|
221
|
+
await expect(btcFormattedInput).toHaveValue('');
|
|
222
|
+
|
|
223
|
+
await page.keyboard.type('-0.987654321');
|
|
224
|
+
await expect(btcUnformattedInput).toHaveValue('-0.987654321');
|
|
225
|
+
await expect(btcFormattedInput).toHaveValue('-$0.98765432');
|
|
226
|
+
});
|
|
227
|
+
|
|
187
228
|
test.skip('Updating chained inputs have the correct behavior', async () => {
|
|
188
229
|
// TODO
|
|
189
230
|
});
|