@canutin/svelte-currency-input 0.13.0 → 1.0.0-next.8
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 +78 -58
- package/dist/CurrencyInput.svelte +0 -181
- package/dist/CurrencyInput.svelte.d.ts +0 -30
- package/dist/constants.d.ts +0 -11
- package/dist/constants.js +0 -11
package/README.md
CHANGED
|
@@ -1,172 +1,293 @@
|
|
|
1
1
|
# svelte-currency-input
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A fully-featured currency input component for Svelte 5 that handles formatting, localization, and validation as you type.
|
|
4
4
|
|
|
5
|
-
[
|
|
5
|
+

|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
👩💻 Play with it
|
|
8
|
+
👩💻 Play with it in the <a href="https://svelte-currency-input.fernando.is" target="_blank">live demo</a>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
+
- [Features](#features)
|
|
14
|
+
- [Installation](#installation)
|
|
15
|
+
- [Usage](#usage)
|
|
16
|
+
- [How it works](#how-it-works)
|
|
17
|
+
- [API](#api)
|
|
18
|
+
- [Examples](#examples)
|
|
19
|
+
- [Styling](#styling)
|
|
20
|
+
- [Exported utilities](#exported-utilities)
|
|
21
|
+
- [Svelte 4 / migration guide](#svelte-4)
|
|
22
|
+
- [Contributing](#contributing)
|
|
23
|
+
|
|
13
24
|
## Features
|
|
14
25
|
|
|
15
26
|
- Formats **positive** and **negative** values
|
|
16
|
-
- Leverages [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) for **localizing** currency denominations
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
- Leverages [`Intl.NumberFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat) for **localizing** currency denominations
|
|
28
|
+
- Supports **abbreviations** (k, m, b) for quick input
|
|
29
|
+
- Configurable **decimal precision** with multiple control options
|
|
30
|
+
- **Min/max constraints** with arrow key stepping
|
|
31
|
+
- Custom **prefix** and **suffix** support
|
|
32
|
+
- **Zero built-in styles** — works with Tailwind, vanilla CSS, or any framework
|
|
33
|
+
- Simple, single `<input>` element (no wrapper)
|
|
34
|
+
- Full **TypeScript** support
|
|
35
|
+
- **Lightweight** — ~3.6KB gzipped with no runtime dependencies
|
|
36
|
+
- API and logic heavily inspired by [@cchanxzy](https://github.com/cchanxzy)'s [react-currency-input-field](https://github.com/cchanxzy/react-currency-input-field)
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
21
39
|
|
|
22
40
|
```bash
|
|
23
|
-
|
|
41
|
+
# bun
|
|
42
|
+
bun add @canutin/svelte-currency-input
|
|
43
|
+
|
|
44
|
+
# pnpm
|
|
45
|
+
pnpm add @canutin/svelte-currency-input
|
|
46
|
+
|
|
47
|
+
# npm
|
|
48
|
+
npm install @canutin/svelte-currency-input
|
|
49
|
+
|
|
50
|
+
# yarn
|
|
51
|
+
yarn add @canutin/svelte-currency-input
|
|
24
52
|
```
|
|
25
53
|
|
|
26
|
-
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
```svelte
|
|
27
57
|
<script lang="ts">
|
|
28
|
-
|
|
58
|
+
import { CurrencyInput } from '@canutin/svelte-currency-input';
|
|
59
|
+
|
|
60
|
+
let value = $state('1234.56');
|
|
29
61
|
</script>
|
|
30
62
|
|
|
31
|
-
<CurrencyInput
|
|
63
|
+
<CurrencyInput bind:value intlConfig={{ locale: 'en-US', currency: 'USD' }} />
|
|
32
64
|
```
|
|
33
65
|
|
|
66
|
+
The input displays `$1,234.56` while `value` contains the raw string `"1234.56"`.
|
|
67
|
+
|
|
34
68
|
## How it works
|
|
35
69
|
|
|
36
|
-
|
|
37
|
-
This is more or less what `<CurrencyInput />` looks like under the hood:
|
|
38
|
-
|
|
39
|
-
```html
|
|
40
|
-
<div class="currencyInput">
|
|
41
|
-
<!-- Unformatted value -->
|
|
42
|
-
<input
|
|
43
|
-
class="currencyInput__unformatted"
|
|
44
|
-
type="hidden"
|
|
45
|
-
name="total"
|
|
46
|
-
value="-420.69"
|
|
47
|
-
/>
|
|
48
|
-
|
|
49
|
-
<!-- Formatted value -->
|
|
50
|
-
<input
|
|
51
|
-
class="currencyInput__formatted"
|
|
52
|
-
type="text"
|
|
53
|
-
name="formatted-total"
|
|
54
|
-
value="€ -420,69"
|
|
55
|
-
/>
|
|
56
|
-
</div>
|
|
57
|
-
```
|
|
70
|
+
The component renders a single `<input>` element. The `value` prop is a **string** representing the unformatted number:
|
|
58
71
|
|
|
59
|
-
|
|
72
|
+
- `""` = empty input
|
|
73
|
+
- `"0"` = zero
|
|
74
|
+
- `"1234.56"` = the number 1234.56
|
|
60
75
|
|
|
61
|
-
|
|
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://www.xe.com/symbols/) |
|
|
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
|
-
id | `string` | `undefined` | Sets the `id` attribute on the input |
|
|
68
|
-
required | `boolean` | `false` | Marks the input as required |
|
|
69
|
-
disabled | `boolean` | `false` | Marks the input as disabled |
|
|
70
|
-
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 |
|
|
71
|
-
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_ |
|
|
72
|
-
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) |
|
|
73
|
-
isNegativeAllowed | `boolean` | `true` | If `false`, forces formatting only to positive values and ignores `--positive` and `--negative` styling modifiers |
|
|
74
|
-
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` |
|
|
75
|
-
inputClasses | `object` | [See below](#Styling) | Selectively overrides any class names passed |
|
|
76
|
-
onValueChange | `Callback` | `undefined` | Runs a callback function after the value changes |
|
|
76
|
+
The formatted display (e.g., `$1,234.56`) is handled internally. For form submissions where you need the raw value, you can add a hidden input:
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
```svelte
|
|
79
|
+
<form>
|
|
80
|
+
<CurrencyInput bind:value name="display" />
|
|
81
|
+
<input type="hidden" name="amount" {value} />
|
|
82
|
+
</form>
|
|
83
|
+
```
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
1. Passing it your own CSS classes
|
|
82
|
-
2. Overriding the styles using the existing class names
|
|
85
|
+
## API
|
|
83
86
|
|
|
84
|
-
|
|
87
|
+
### Props
|
|
88
|
+
|
|
89
|
+
| Prop | Type | Default | Description |
|
|
90
|
+
| ------------------------ | --------------------------------------- | ----------- | ------------------------------------------------------------------------------- |
|
|
91
|
+
| `value` | `string` | `''` | Bindable raw value (e.g., `"1234.56"`) |
|
|
92
|
+
| `intlConfig` | `IntlConfig` | `undefined` | Locale and currency configuration |
|
|
93
|
+
| `prefix` | `string` | From locale | Override the currency prefix |
|
|
94
|
+
| `suffix` | `string` | `''` | Override the currency suffix |
|
|
95
|
+
| `decimalSeparator` | `string` | From locale | Override the decimal separator |
|
|
96
|
+
| `groupSeparator` | `string` | From locale | Override the grouping separator |
|
|
97
|
+
| `disableGroupSeparators` | `boolean` | `false` | Disable thousand separators |
|
|
98
|
+
| `allowDecimals` | `boolean` | `true` | Allow decimal values |
|
|
99
|
+
| `decimalsLimit` | `number` | `2` | Max decimal digits while typing |
|
|
100
|
+
| `decimalScale` | `number` | `undefined` | Pad/trim decimals on blur |
|
|
101
|
+
| `fixedDecimalLength` | `number` | `undefined` | Fixed decimal input (e.g., `2`: typing `123` → `1.23`) |
|
|
102
|
+
| `allowNegativeValue` | `boolean` | `true` | Allow negative values |
|
|
103
|
+
| `min` | `number` | `undefined` | Minimum value (enforced on arrow key step) |
|
|
104
|
+
| `max` | `number` | `undefined` | Maximum value (enforced on arrow key step) |
|
|
105
|
+
| `maxLength` | `number` | `undefined` | Max characters (excluding formatting) |
|
|
106
|
+
| `step` | `number` | `undefined` | Arrow key increment/decrement |
|
|
107
|
+
| `disableAbbreviations` | `boolean` | `false` | Disable k/m/b abbreviations |
|
|
108
|
+
| `formatValueOnBlur` | `boolean` | `true` | Apply formatting when input loses focus |
|
|
109
|
+
| `transformRawValue` | `(value: string) => string` | `undefined` | Transform the raw value before processing |
|
|
110
|
+
| `oninputvalue` | `(values: CurrencyInputValues) => void` | `undefined` | Callback on every input change |
|
|
111
|
+
| `onchangevalue` | `(values: CurrencyInputValues) => void` | `undefined` | Callback on blur/commit |
|
|
112
|
+
| `ref` | `HTMLInputElement \| null` | `null` | Bindable reference to the input element |
|
|
113
|
+
| `class` | `string` | `undefined` | CSS class(es) for the input |
|
|
114
|
+
| `...restProps` | `HTMLInputAttributes` | — | All standard input attributes (id, name, placeholder, disabled, required, etc.) |
|
|
115
|
+
|
|
116
|
+
### Types
|
|
85
117
|
|
|
86
118
|
```typescript
|
|
87
|
-
interface
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
formattedPositive?: string; // Class added when the formatted input is positive
|
|
92
|
-
formattedNegative?: string; // Class added when the formatted input is negative
|
|
93
|
-
formattedZero?: string; // Class added when the formatted input is zero
|
|
119
|
+
interface IntlConfig {
|
|
120
|
+
locale: string;
|
|
121
|
+
currency?: string;
|
|
122
|
+
// Also accepts other Intl.NumberFormatOptions
|
|
94
123
|
}
|
|
124
|
+
|
|
125
|
+
interface CurrencyInputValues {
|
|
126
|
+
float: number | null; // Parsed number or null if empty
|
|
127
|
+
formatted: string; // Display value: "$1,234.56"
|
|
128
|
+
value: string; // Raw value: "1234.56"
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Examples
|
|
133
|
+
|
|
134
|
+
### International currencies
|
|
135
|
+
|
|
136
|
+
```svelte
|
|
137
|
+
<CurrencyInput bind:value intlConfig={{ locale: 'de-DE', currency: 'EUR' }} />
|
|
138
|
+
<!-- Displays: 1.234,56 € -->
|
|
95
139
|
```
|
|
96
140
|
|
|
97
|
-
|
|
141
|
+
### Decimal precision
|
|
98
142
|
|
|
99
143
|
```svelte
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
} />
|
|
144
|
+
<!-- Limit to 2 decimals while typing, pad to 2 on blur -->
|
|
145
|
+
<CurrencyInput
|
|
146
|
+
bind:value
|
|
147
|
+
intlConfig={{ locale: 'en-US', currency: 'USD' }}
|
|
148
|
+
decimalsLimit={2}
|
|
149
|
+
decimalScale={2}
|
|
150
|
+
/>
|
|
108
151
|
```
|
|
109
152
|
|
|
110
|
-
|
|
153
|
+
### Min, max, and step
|
|
111
154
|
|
|
112
155
|
```svelte
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
156
|
+
<!-- Use arrow keys to increment/decrement by 10, constrained to 0-100 -->
|
|
157
|
+
<CurrencyInput
|
|
158
|
+
bind:value
|
|
159
|
+
intlConfig={{ locale: 'en-US', currency: 'USD' }}
|
|
160
|
+
min={0}
|
|
161
|
+
max={100}
|
|
162
|
+
step={10}
|
|
163
|
+
/>
|
|
164
|
+
```
|
|
116
165
|
|
|
117
|
-
|
|
118
|
-
/* Container */
|
|
119
|
-
div.my-currency-input :global(div.currencyInput) { /* ... */ }
|
|
166
|
+
### Custom prefix and suffix
|
|
120
167
|
|
|
121
|
-
|
|
122
|
-
|
|
168
|
+
```svelte
|
|
169
|
+
<!-- Points system -->
|
|
170
|
+
<CurrencyInput bind:value suffix=" pts" decimalsLimit={0} />
|
|
123
171
|
|
|
124
|
-
|
|
125
|
-
|
|
172
|
+
<!-- Bitcoin -->
|
|
173
|
+
<CurrencyInput bind:value prefix="₿ " decimalsLimit={8} />
|
|
174
|
+
```
|
|
126
175
|
|
|
127
|
-
|
|
128
|
-
div.my-currency-input :global(input.currencyInput__formatted--zero) { /* ... */ }
|
|
176
|
+
### Abbreviations
|
|
129
177
|
|
|
130
|
-
|
|
131
|
-
div.my-currency-input :global(input.currencyInput__formatted--positive) { /* ... */ }
|
|
178
|
+
Type `1k`, `2.5m`, or `1b` to quickly enter large numbers:
|
|
132
179
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
180
|
+
```svelte
|
|
181
|
+
<CurrencyInput
|
|
182
|
+
bind:value
|
|
183
|
+
intlConfig={{ locale: 'en-US', currency: 'USD' }}
|
|
184
|
+
placeholder="Try 1k, 2.5m, or 1b"
|
|
185
|
+
/>
|
|
186
|
+
<!-- Typing "2.5m" results in value="2500000" -->
|
|
136
187
|
```
|
|
137
188
|
|
|
138
|
-
|
|
189
|
+
### Callbacks
|
|
139
190
|
|
|
140
|
-
|
|
191
|
+
```svelte
|
|
192
|
+
<CurrencyInput
|
|
193
|
+
bind:value
|
|
194
|
+
intlConfig={{ locale: 'en-US', currency: 'USD' }}
|
|
195
|
+
oninputvalue={({ float, formatted, value }) => {
|
|
196
|
+
console.log('On input:', { float, formatted, value });
|
|
197
|
+
}}
|
|
198
|
+
onchangevalue={({ float, formatted, value }) => {
|
|
199
|
+
console.log('On blur:', { float, formatted, value });
|
|
200
|
+
}}
|
|
201
|
+
/>
|
|
202
|
+
```
|
|
141
203
|
|
|
142
|
-
|
|
143
|
-
- Comment or upvote [existing issues](https://github.com/canutin/svelte-currency-input/issues)
|
|
144
|
-
- Submit a [pull request](https://github.com/canutin/svelte-currency-input/pulls)
|
|
204
|
+
### Input element reference
|
|
145
205
|
|
|
146
|
-
|
|
206
|
+
```svelte
|
|
207
|
+
<script>
|
|
208
|
+
let inputRef = $state(null);
|
|
147
209
|
|
|
148
|
-
|
|
210
|
+
function focusInput() {
|
|
211
|
+
inputRef?.focus();
|
|
212
|
+
}
|
|
213
|
+
</script>
|
|
149
214
|
|
|
150
|
-
|
|
151
|
-
|
|
215
|
+
<CurrencyInput bind:ref={inputRef} bind:value />
|
|
216
|
+
<button onclick={focusInput}>Focus</button>
|
|
217
|
+
```
|
|
152
218
|
|
|
153
|
-
|
|
154
|
-
|
|
219
|
+
## Styling
|
|
220
|
+
|
|
221
|
+
The component renders a single `<input>` element with no built-in styles. You can use the `class` prop to style it:
|
|
222
|
+
|
|
223
|
+
```svelte
|
|
224
|
+
<!-- Tailwind CSS -->
|
|
225
|
+
<CurrencyInput bind:value class="rounded border px-3 py-2 focus:ring-2 focus:ring-blue-500" />
|
|
226
|
+
|
|
227
|
+
<!-- Custom CSS class -->
|
|
228
|
+
<CurrencyInput bind:value class="my-currency-input" />
|
|
155
229
|
```
|
|
156
230
|
|
|
157
|
-
|
|
231
|
+
For dynamic styling based on value (positive/negative/zero), use the callback:
|
|
158
232
|
|
|
159
|
-
|
|
160
|
-
|
|
233
|
+
```svelte
|
|
234
|
+
<script>
|
|
235
|
+
let value = $state('');
|
|
236
|
+
let colorClass = $state('');
|
|
237
|
+
|
|
238
|
+
function updateStyle({ float }) {
|
|
239
|
+
if (float === null || float === 0) colorClass = 'text-gray-500';
|
|
240
|
+
else if (float > 0) colorClass = 'text-green-600';
|
|
241
|
+
else colorClass = 'text-red-600';
|
|
242
|
+
}
|
|
243
|
+
</script>
|
|
161
244
|
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
npm run test
|
|
245
|
+
<CurrencyInput bind:value class="border px-3 py-2 {colorClass}" oninputvalue={updateStyle} />
|
|
165
246
|
```
|
|
166
247
|
|
|
167
|
-
|
|
248
|
+
## Exported utilities
|
|
249
|
+
|
|
250
|
+
The package exports utility functions for use outside the component:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import {
|
|
254
|
+
formatValue,
|
|
255
|
+
getLocaleConfig,
|
|
256
|
+
cleanValue,
|
|
257
|
+
parseAbbrValue,
|
|
258
|
+
abbrValue
|
|
259
|
+
} from '@canutin/svelte-currency-input';
|
|
260
|
+
|
|
261
|
+
// Format a value with locale
|
|
262
|
+
const formatted = formatValue({
|
|
263
|
+
value: '1234.56',
|
|
264
|
+
intlConfig: { locale: 'en-US', currency: 'USD' }
|
|
265
|
+
});
|
|
266
|
+
// → "$1,234.56"
|
|
267
|
+
|
|
268
|
+
// Get locale configuration
|
|
269
|
+
const config = getLocaleConfig({ locale: 'de-DE', currency: 'EUR' });
|
|
270
|
+
// → { decimalSeparator: ',', groupSeparator: '.', prefix: '', suffix: ' €', ... }
|
|
271
|
+
|
|
272
|
+
// Parse abbreviations
|
|
273
|
+
const expanded = parseAbbrValue('2.5m', 'en-US');
|
|
274
|
+
// → "2500000"
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Svelte 4
|
|
278
|
+
|
|
279
|
+
For Svelte 4 support, use the [0.x version](https://github.com/fmaclen/svelte-currency-input/tree/v0.13.0):
|
|
280
|
+
|
|
168
281
|
```bash
|
|
169
|
-
|
|
282
|
+
npm install @canutin/svelte-currency-input@0
|
|
170
283
|
```
|
|
171
284
|
|
|
172
|
-
|
|
285
|
+
If you're upgrading from v0.x, see the [migration guide](./MIGRATION.md).
|
|
286
|
+
|
|
287
|
+
## Contributing
|
|
288
|
+
|
|
289
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for development setup, testing, and contribution guidelines.
|
|
290
|
+
|
|
291
|
+
## License
|
|
292
|
+
|
|
293
|
+
[MIT](./LICENSE)
|