persian-number-input 4.3.2 → 4.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.fa.md +190 -240
- package/README.md +152 -230
- package/dist/components/PersianNumberInput.d.ts +1 -1
- package/dist/components/PersianNumberInput.d.ts.map +1 -1
- package/dist/components/PersianNumberInput.js +22 -17
- package/dist/components/PersianNumberInput.js.map +1 -1
- package/dist/components/PersianNumberInput.test.d.ts +2 -0
- package/dist/components/PersianNumberInput.test.d.ts.map +1 -0
- package/dist/components/PersianNumberInput.test.js +394 -0
- package/dist/components/PersianNumberInput.test.js.map +1 -0
- package/dist/hooks/useCursorManager.d.ts +4 -0
- package/dist/hooks/useCursorManager.d.ts.map +1 -0
- package/dist/hooks/useCursorManager.js +19 -0
- package/dist/hooks/useCursorManager.js.map +1 -0
- package/dist/hooks/useNumberFormatter.d.ts +18 -0
- package/dist/hooks/useNumberFormatter.d.ts.map +1 -0
- package/dist/hooks/useNumberFormatter.js +29 -0
- package/dist/hooks/useNumberFormatter.js.map +1 -0
- package/dist/hooks/useNumberValidation.d.ts +11 -0
- package/dist/hooks/useNumberValidation.d.ts.map +1 -0
- package/dist/hooks/useNumberValidation.js +22 -0
- package/dist/hooks/useNumberValidation.js.map +1 -0
- package/dist/hooks/useNumericInputEvents.d.ts +18 -0
- package/dist/hooks/useNumericInputEvents.d.ts.map +1 -0
- package/dist/hooks/useNumericInputEvents.js +39 -0
- package/dist/hooks/useNumericInputEvents.js.map +1 -0
- package/dist/hooks/usePersianNumberInput.d.ts +7 -2
- package/dist/hooks/usePersianNumberInput.d.ts.map +1 -1
- package/dist/hooks/usePersianNumberInput.js +66 -63
- package/dist/hooks/usePersianNumberInput.js.map +1 -1
- package/dist/test-utils.d.ts +7 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +27 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/utils/digitUtils.d.ts +4 -0
- package/dist/utils/digitUtils.d.ts.map +1 -1
- package/dist/utils/digitUtils.js +44 -17
- package/dist/utils/digitUtils.js.map +1 -1
- package/dist/utils/keyFilter.d.ts +16 -0
- package/dist/utils/keyFilter.d.ts.map +1 -0
- package/dist/utils/keyFilter.js +31 -0
- package/dist/utils/keyFilter.js.map +1 -0
- package/dist/utils/validation.d.ts +22 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +85 -0
- package/dist/utils/validation.js.map +1 -0
- package/package.json +19 -7
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
# Persian Number Input
|
|
1
|
+
# Persian Number Input — React Component for Farsi, Arabic & RTL Number Formatting
|
|
2
2
|
|
|
3
3
|
[فارسی](./README.fa.md) | English
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
A lightweight React library for **Persian (Farsi) and Arabic number inputs** with automatic digit conversion, thousand-separator formatting, decimal precision, and full RTL support. No more manual conversion — users type in their native digits, your form receives clean English numbers.
|
|
5
6
|
|
|
6
7
|
[](https://www.npmjs.com/package/persian-number-input)
|
|
7
8
|
[](https://www.npmjs.com/package/persian-number-input)
|
|
@@ -12,32 +13,37 @@ A lightweight, powerful React library for handling Persian (Farsi) and Arabic nu
|
|
|
12
13
|
|
|
13
14
|
---
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
## Why Persian Number Input?
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
Building forms for Persian or Arabic-speaking users comes with real challenges:
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
- Users naturally type `۱۲۳۴` — but your API expects `1234`
|
|
21
|
+
- Standard `<input type="number">` doesn't support Persian or Arabic digits at all
|
|
22
|
+
- Cursor jumps erratically when formatting thousand separators on the fly
|
|
23
|
+
- Decimal handling differs across locales (`٫` vs `.`)
|
|
24
|
+
- RTL inputs need right-aligned text and correct suffix placement
|
|
25
|
+
|
|
26
|
+
This library solves all of these automatically:
|
|
20
27
|
|
|
21
28
|
```
|
|
22
|
-
|
|
29
|
+
User types: ۱۲۳۴۵۶۷
|
|
30
|
+
Displayed as: ۱,۲۳۴,۵۶۷
|
|
31
|
+
Form receives: "1234567"
|
|
23
32
|
```
|
|
24
33
|
|
|
25
|
-

|
|
26
|
-
|
|
27
34
|
---
|
|
28
35
|
|
|
29
36
|
## ✨ Features
|
|
30
37
|
|
|
31
|
-
- 🔢 **Automatic
|
|
32
|
-
- 🌍 **Multi-locale
|
|
33
|
-
- 📊 **
|
|
34
|
-
- 💰 **Currency
|
|
35
|
-
- ⚡ **
|
|
36
|
-
- 🎯 **
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
- ✅ **Validation** - Built-in min/max value validation and decimal precision control
|
|
38
|
+
- 🔢 **Automatic digit conversion** — Persian (۰-۹) and Arabic (٠-٩) to English and back
|
|
39
|
+
- 🌍 **Multi-locale** — `fa` (Farsi/Persian), `ar` (Arabic), `en` (English)
|
|
40
|
+
- 📊 **Thousand separator formatting** — customizable separator character and group size
|
|
41
|
+
- 💰 **Currency-ready** — add prefix, suffix (e.g. تومان, ریال, ر.س), and custom decimal separator
|
|
42
|
+
- ⚡ **~1KB gzipped** — zero extra dependencies, pure TypeScript
|
|
43
|
+
- 🎯 **TypeScript** — full type definitions included
|
|
44
|
+
- 🔄 **Cursor-position preservation** — no jump on re-format
|
|
45
|
+
- ✅ **Min/max validation** — built-in range enforcement and decimal precision control
|
|
46
|
+
- ♿ **Accessible** — follows WCAG input best practices
|
|
41
47
|
|
|
42
48
|
---
|
|
43
49
|
|
|
@@ -45,22 +51,18 @@ persian-number-input: ~1KB (minified + gzipped)
|
|
|
45
51
|
|
|
46
52
|
```bash
|
|
47
53
|
npm install persian-number-input
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
```bash
|
|
54
|
+
# or
|
|
51
55
|
yarn add persian-number-input
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
```bash
|
|
56
|
+
# or
|
|
55
57
|
pnpm add persian-number-input
|
|
56
58
|
```
|
|
57
59
|
|
|
60
|
+
> **Requirements:** React 16.8+, works with Next.js, Vite, CRA, and any React-based framework.
|
|
61
|
+
|
|
58
62
|
---
|
|
59
63
|
|
|
60
64
|
## 🎯 Quick Start
|
|
61
65
|
|
|
62
|
-
### Basic Usage
|
|
63
|
-
|
|
64
66
|
```tsx
|
|
65
67
|
import { PersianNumberInput } from "persian-number-input";
|
|
66
68
|
|
|
@@ -69,19 +71,19 @@ function App() {
|
|
|
69
71
|
<PersianNumberInput
|
|
70
72
|
initialValue={1234567}
|
|
71
73
|
locale="fa"
|
|
72
|
-
onValueChange={(value) => console.log(value)}
|
|
74
|
+
onValueChange={(value) => console.log(value)} // "1234567"
|
|
73
75
|
/>
|
|
74
76
|
);
|
|
75
77
|
}
|
|
76
78
|
```
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
Output displayed to user: `۱,۲۳۴,۵۶۷`
|
|
79
81
|
|
|
80
82
|
---
|
|
81
83
|
|
|
82
84
|
## 📚 Usage Examples
|
|
83
85
|
|
|
84
|
-
###
|
|
86
|
+
### Persian Toman currency input
|
|
85
87
|
|
|
86
88
|
```tsx
|
|
87
89
|
<PersianNumberInput
|
|
@@ -94,11 +96,27 @@ function App() {
|
|
|
94
96
|
/>
|
|
95
97
|
```
|
|
96
98
|
|
|
97
|
-
|
|
99
|
+
Displayed: `۵,۰۰۰,۰۰۰ تومان`
|
|
98
100
|
|
|
99
101
|
---
|
|
100
102
|
|
|
101
|
-
###
|
|
103
|
+
### Arabic locale (SAR)
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
<PersianNumberInput
|
|
107
|
+
initialValue={987654}
|
|
108
|
+
locale="ar"
|
|
109
|
+
separatorChar=","
|
|
110
|
+
suffix="ر.س"
|
|
111
|
+
onValueChange={(value) => console.log(value)}
|
|
112
|
+
/>
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Displayed: `٩٨٧,٦٥٤ ر.س`
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### Decimal numbers with custom separator
|
|
102
120
|
|
|
103
121
|
```tsx
|
|
104
122
|
<PersianNumberInput
|
|
@@ -111,11 +129,11 @@ function App() {
|
|
|
111
129
|
/>
|
|
112
130
|
```
|
|
113
131
|
|
|
114
|
-
|
|
132
|
+
Displayed: `۱,۲۳۴٫۵۶`
|
|
115
133
|
|
|
116
134
|
---
|
|
117
135
|
|
|
118
|
-
### Price
|
|
136
|
+
### Price input with min/max validation
|
|
119
137
|
|
|
120
138
|
```tsx
|
|
121
139
|
<PersianNumberInput
|
|
@@ -129,27 +147,41 @@ function App() {
|
|
|
129
147
|
/>
|
|
130
148
|
```
|
|
131
149
|
|
|
132
|
-
**Output:** `۰ ریال`
|
|
133
|
-
|
|
134
150
|
---
|
|
135
151
|
|
|
136
|
-
###
|
|
152
|
+
### With React Hook Form
|
|
137
153
|
|
|
138
154
|
```tsx
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
locale="ar"
|
|
142
|
-
separatorChar=","
|
|
143
|
-
suffix="ر.س"
|
|
144
|
-
onValueChange={(value) => console.log(value)}
|
|
145
|
-
/>
|
|
146
|
-
```
|
|
155
|
+
import { useForm, Controller } from "react-hook-form";
|
|
156
|
+
import { PersianNumberInput } from "persian-number-input";
|
|
147
157
|
|
|
148
|
-
|
|
158
|
+
function ProductForm() {
|
|
159
|
+
const { control, handleSubmit } = useForm();
|
|
160
|
+
|
|
161
|
+
return (
|
|
162
|
+
<form onSubmit={handleSubmit(console.log)}>
|
|
163
|
+
<Controller
|
|
164
|
+
name="price"
|
|
165
|
+
control={control}
|
|
166
|
+
rules={{ required: true }}
|
|
167
|
+
render={({ field }) => (
|
|
168
|
+
<PersianNumberInput
|
|
169
|
+
locale="fa"
|
|
170
|
+
suffix="تومان"
|
|
171
|
+
onValueChange={field.onChange}
|
|
172
|
+
initialValue={field.value}
|
|
173
|
+
/>
|
|
174
|
+
)}
|
|
175
|
+
/>
|
|
176
|
+
<button type="submit">ثبت</button>
|
|
177
|
+
</form>
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
```
|
|
149
181
|
|
|
150
182
|
---
|
|
151
183
|
|
|
152
|
-
### Using the
|
|
184
|
+
### Using the hook directly (advanced)
|
|
153
185
|
|
|
154
186
|
```tsx
|
|
155
187
|
import { usePersianNumberInput } from "persian-number-input";
|
|
@@ -162,10 +194,7 @@ function CustomInput() {
|
|
|
162
194
|
maxDecimals: 2,
|
|
163
195
|
min: 0,
|
|
164
196
|
max: 1000000,
|
|
165
|
-
onValueChange: (val) =>
|
|
166
|
-
console.log("Raw value:", val); // "1000"
|
|
167
|
-
console.log("Displayed value:", value); // "۱,۰۰۰"
|
|
168
|
-
},
|
|
197
|
+
onValueChange: (val) => console.log("English value:", val),
|
|
169
198
|
});
|
|
170
199
|
|
|
171
200
|
return (
|
|
@@ -174,7 +203,7 @@ function CustomInput() {
|
|
|
174
203
|
value={value}
|
|
175
204
|
onChange={onChange}
|
|
176
205
|
onBlur={onBlur}
|
|
177
|
-
|
|
206
|
+
dir="rtl"
|
|
178
207
|
/>
|
|
179
208
|
);
|
|
180
209
|
}
|
|
@@ -184,278 +213,171 @@ function CustomInput() {
|
|
|
184
213
|
|
|
185
214
|
## 🛠️ API Reference
|
|
186
215
|
|
|
187
|
-
### PersianNumberInput
|
|
216
|
+
### `PersianNumberInput` props
|
|
188
217
|
|
|
189
|
-
| Prop | Type | Default | Description
|
|
190
|
-
| ---------------- | -------------------------------------- | ----------- |
|
|
191
|
-
| `initialValue` | `number \| string` | `undefined` | Initial value of the input
|
|
192
|
-
| `locale` | `"fa" \| "ar" \| "en"` | `"fa"` |
|
|
193
|
-
| `separatorCount` | `number` | `3` |
|
|
194
|
-
| `separatorChar` | `string` | `","` |
|
|
195
|
-
| `decimalChar` | `string` | Auto | Decimal separator
|
|
196
|
-
| `suffix` | `string` | `undefined` |
|
|
197
|
-
| `maxDecimals` | `number` | `undefined` | Maximum decimal places
|
|
198
|
-
| `min` | `number` | `undefined` | Minimum allowed value
|
|
199
|
-
| `max` | `number` | `undefined` | Maximum allowed value
|
|
200
|
-
| `showZero` | `boolean` | `false` |
|
|
201
|
-
| `onValueChange` | `(value: string \| undefined) => void` | `undefined` |
|
|
218
|
+
| Prop | Type | Default | Description |
|
|
219
|
+
| ---------------- | -------------------------------------- | ----------- | ---------------------------------------------------- |
|
|
220
|
+
| `initialValue` | `number \| string` | `undefined` | Initial value of the input |
|
|
221
|
+
| `locale` | `"fa" \| "ar" \| "en"` | `"fa"` | Digit locale — Farsi, Arabic, or English |
|
|
222
|
+
| `separatorCount` | `number` | `3` | Digits per group (3 = thousand separator) |
|
|
223
|
+
| `separatorChar` | `string` | `","` | Thousand separator character |
|
|
224
|
+
| `decimalChar` | `string` | Auto | Decimal separator (`٫` for fa, `.` for en) |
|
|
225
|
+
| `suffix` | `string` | `undefined` | Currency or unit suffix (e.g. `تومان`, `ریال`) |
|
|
226
|
+
| `maxDecimals` | `number` | `undefined` | Maximum allowed decimal places |
|
|
227
|
+
| `min` | `number` | `undefined` | Minimum allowed value |
|
|
228
|
+
| `max` | `number` | `undefined` | Maximum allowed value |
|
|
229
|
+
| `showZero` | `boolean` | `false` | Display `0` instead of empty when value is zero |
|
|
230
|
+
| `onValueChange` | `(value: string \| undefined) => void` | `undefined` | Fires on change — always returns English digits |
|
|
202
231
|
|
|
203
|
-
All standard HTML input props are also supported.
|
|
232
|
+
All standard HTML `<input>` props (e.g. `className`, `style`, `placeholder`, `disabled`) are also supported.
|
|
204
233
|
|
|
205
234
|
---
|
|
206
235
|
|
|
207
|
-
### Utility
|
|
236
|
+
### Utility functions
|
|
208
237
|
|
|
209
238
|
#### `transformNumber(rawValue, options)`
|
|
210
239
|
|
|
211
|
-
|
|
240
|
+
Format any number string according to locale and options.
|
|
212
241
|
|
|
213
242
|
```tsx
|
|
214
243
|
import { transformNumber } from "persian-number-input";
|
|
215
244
|
|
|
216
|
-
|
|
245
|
+
transformNumber("1234567.89", {
|
|
217
246
|
locale: "fa",
|
|
218
247
|
separatorCount: 3,
|
|
219
248
|
separatorChar: ",",
|
|
220
249
|
maxDecimals: 2,
|
|
221
250
|
suffix: "تومان",
|
|
222
251
|
});
|
|
223
|
-
|
|
224
|
-
console.log(formatted); // "۱,۲۳۴,۵۶۷٫۸۹ تومان"
|
|
252
|
+
// → "۱,۲۳۴,۵۶۷٫۸۹ تومان"
|
|
225
253
|
```
|
|
226
254
|
|
|
227
255
|
#### `toEnglishDigits(str, decimalChar?)`
|
|
228
256
|
|
|
229
|
-
|
|
257
|
+
Convert Persian or Arabic digits to English.
|
|
230
258
|
|
|
231
259
|
```tsx
|
|
232
260
|
import { toEnglishDigits } from "persian-number-input";
|
|
233
261
|
|
|
234
|
-
|
|
235
|
-
|
|
262
|
+
toEnglishDigits("۱۲۳۴"); // "1234"
|
|
263
|
+
toEnglishDigits("٩٨٧٦"); // "9876"
|
|
236
264
|
```
|
|
237
265
|
|
|
238
266
|
#### `toLocalizedDigits(numStr, locale)`
|
|
239
267
|
|
|
240
|
-
|
|
268
|
+
Convert English digits to the target locale.
|
|
241
269
|
|
|
242
270
|
```tsx
|
|
243
271
|
import { toLocalizedDigits } from "persian-number-input";
|
|
244
272
|
|
|
245
|
-
|
|
246
|
-
|
|
273
|
+
toLocalizedDigits("1234", "fa"); // "۱۲۳۴"
|
|
274
|
+
toLocalizedDigits("5678", "ar"); // "٥٦٧٨"
|
|
247
275
|
```
|
|
248
276
|
|
|
249
277
|
#### `sanitizeNumericInput(value, maxDecimals?, decimalChar?)`
|
|
250
278
|
|
|
251
|
-
|
|
279
|
+
Strip non-numeric characters and enforce decimal limits.
|
|
252
280
|
|
|
253
281
|
```tsx
|
|
254
282
|
import { sanitizeNumericInput } from "persian-number-input";
|
|
255
283
|
|
|
256
|
-
|
|
257
|
-
|
|
284
|
+
sanitizeNumericInput("۱۲۳abc۴۵۶", 2); // "123456"
|
|
285
|
+
sanitizeNumericInput("12.345.67", 2); // "12.34"
|
|
258
286
|
```
|
|
259
287
|
|
|
260
288
|
---
|
|
261
289
|
|
|
262
290
|
## 🎨 Styling
|
|
263
291
|
|
|
264
|
-
The component accepts all standard input props
|
|
292
|
+
The component accepts all standard input props including `className` and `style`:
|
|
265
293
|
|
|
266
294
|
```tsx
|
|
267
295
|
<PersianNumberInput
|
|
268
|
-
initialValue={1000}
|
|
269
296
|
locale="fa"
|
|
270
|
-
className="
|
|
271
|
-
style={{
|
|
272
|
-
padding: "12px",
|
|
273
|
-
fontSize: "16px",
|
|
274
|
-
border: "2px solid #4F46E5",
|
|
275
|
-
borderRadius: "8px",
|
|
276
|
-
textAlign: "right",
|
|
277
|
-
}}
|
|
297
|
+
className="w-full px-4 py-3 text-lg border-2 border-indigo-500 rounded-lg text-right focus:outline-none focus:ring-2 focus:ring-indigo-600"
|
|
278
298
|
/>
|
|
279
299
|
```
|
|
280
300
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
```tsx
|
|
284
|
-
<PersianNumberInput
|
|
285
|
-
initialValue={1000}
|
|
286
|
-
locale="fa"
|
|
287
|
-
className="w-full px-4 py-3 text-lg border-2 border-indigo-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-600 text-right"
|
|
288
|
-
/>
|
|
289
|
-
```
|
|
301
|
+
For RTL layouts, add `dir="rtl"` to the wrapper or `style={{ textAlign: "right" }}` on the input.
|
|
290
302
|
|
|
291
303
|
---
|
|
292
304
|
|
|
293
|
-
##
|
|
294
|
-
|
|
295
|
-
### Financial Calculator
|
|
296
|
-
|
|
297
|
-
```tsx
|
|
298
|
-
import { useState } from "react";
|
|
299
|
-
import { PersianNumberInput } from "persian-number-input";
|
|
300
|
-
|
|
301
|
-
function LoanCalculator() {
|
|
302
|
-
const [principal, setPrincipal] = useState<string>();
|
|
303
|
-
const [rate, setRate] = useState<string>();
|
|
304
|
-
const [years, setYears] = useState<string>();
|
|
305
|
-
|
|
306
|
-
const calculateMonthlyPayment = () => {
|
|
307
|
-
if (!principal || !rate || !years) return 0;
|
|
308
|
-
const p = parseFloat(principal);
|
|
309
|
-
const r = parseFloat(rate) / 100 / 12;
|
|
310
|
-
const n = parseFloat(years) * 12;
|
|
311
|
-
return (p * r * Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1);
|
|
312
|
-
};
|
|
305
|
+
## 📊 Bundle Size
|
|
313
306
|
|
|
314
|
-
return (
|
|
315
|
-
<div className="space-y-4">
|
|
316
|
-
<div>
|
|
317
|
-
<label>مبلغ وام:</label>
|
|
318
|
-
<PersianNumberInput
|
|
319
|
-
locale="fa"
|
|
320
|
-
suffix="تومان"
|
|
321
|
-
onValueChange={setPrincipal}
|
|
322
|
-
min={0}
|
|
323
|
-
/>
|
|
324
|
-
</div>
|
|
325
|
-
<div>
|
|
326
|
-
<label>نرخ سود (٪):</label>
|
|
327
|
-
<PersianNumberInput
|
|
328
|
-
locale="fa"
|
|
329
|
-
maxDecimals={2}
|
|
330
|
-
onValueChange={setRate}
|
|
331
|
-
min={0}
|
|
332
|
-
max={100}
|
|
333
|
-
/>
|
|
334
|
-
</div>
|
|
335
|
-
<div>
|
|
336
|
-
<label>مدت زمان (سال):</label>
|
|
337
|
-
<PersianNumberInput
|
|
338
|
-
locale="fa"
|
|
339
|
-
onValueChange={setYears}
|
|
340
|
-
min={1}
|
|
341
|
-
max={30}
|
|
342
|
-
/>
|
|
343
|
-
</div>
|
|
344
|
-
<p>
|
|
345
|
-
پرداخت ماهیانه: {calculateMonthlyPayment().toLocaleString("fa-IR")}{" "}
|
|
346
|
-
تومان
|
|
347
|
-
</p>
|
|
348
|
-
</div>
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
307
|
```
|
|
352
|
-
|
|
353
|
-
---
|
|
354
|
-
|
|
355
|
-
### Form Integration
|
|
356
|
-
|
|
357
|
-
```tsx
|
|
358
|
-
import { useForm, Controller } from "react-hook-form";
|
|
359
|
-
import { PersianNumberInput } from "persian-number-input";
|
|
360
|
-
|
|
361
|
-
function ProductForm() {
|
|
362
|
-
const { control, handleSubmit } = useForm();
|
|
363
|
-
|
|
364
|
-
const onSubmit = (data) => {
|
|
365
|
-
console.log(data);
|
|
366
|
-
};
|
|
367
|
-
|
|
368
|
-
return (
|
|
369
|
-
<form onSubmit={handleSubmit(onSubmit)}>
|
|
370
|
-
<Controller
|
|
371
|
-
name="price"
|
|
372
|
-
control={control}
|
|
373
|
-
rules={{ required: true }}
|
|
374
|
-
render={({ field }) => (
|
|
375
|
-
<PersianNumberInput
|
|
376
|
-
locale="fa"
|
|
377
|
-
suffix="تومان"
|
|
378
|
-
onValueChange={field.onChange}
|
|
379
|
-
initialValue={field.value}
|
|
380
|
-
/>
|
|
381
|
-
)}
|
|
382
|
-
/>
|
|
383
|
-
<button type="submit">ثبت</button>
|
|
384
|
-
</form>
|
|
385
|
-
);
|
|
386
|
-
}
|
|
308
|
+
persian-number-input: ~1KB (minified + gzipped)
|
|
387
309
|
```
|
|
388
310
|
|
|
311
|
+
One of the smallest solutions for Persian/Arabic number input in the React ecosystem.
|
|
312
|
+
|
|
389
313
|
---
|
|
390
314
|
|
|
391
|
-
##
|
|
315
|
+
## 🏆 Comparison
|
|
392
316
|
|
|
393
|
-
|
|
317
|
+
| Feature | persian-number-input | Native `<input>` | Other libraries |
|
|
318
|
+
| ------------------------------ | :------------------: | :--------------: | :-------------: |
|
|
319
|
+
| Persian & Arabic digit input | ✅ | ❌ | ⚠️ Partial |
|
|
320
|
+
| Cursor preservation on format | ✅ | ❌ | ⚠️ Often buggy |
|
|
321
|
+
| Multi-locale (fa / ar / en) | ✅ | ❌ | ❌ |
|
|
322
|
+
| Thousand separator formatting | ✅ | ❌ | ⚠️ Limited |
|
|
323
|
+
| Decimal precision control | ✅ | ❌ | ⚠️ Limited |
|
|
324
|
+
| Min/max validation | ✅ | Partial | ⚠️ Varies |
|
|
325
|
+
| TypeScript support | ✅ | ✅ | ⚠️ Varies |
|
|
326
|
+
| Bundle size | 🟢 ~1KB | 🟢 Native | 🔴 5–30KB |
|
|
327
|
+
| Zero peer dependencies | ✅ | ✅ | ❌ |
|
|
394
328
|
|
|
395
|
-
|
|
329
|
+
---
|
|
396
330
|
|
|
397
|
-
|
|
398
|
-
- Number formatting varies across locales
|
|
399
|
-
- Maintaining cursor position during formatting is complex
|
|
400
|
-
- Decimal precision handling requires careful implementation
|
|
331
|
+
## ❓ FAQ
|
|
401
332
|
|
|
402
|
-
|
|
333
|
+
**Q: Does this work with Next.js and Server Components?**
|
|
334
|
+
A: Yes. The component is a client component — add `"use client"` at the top of your file when using it inside a Next.js App Router layout.
|
|
403
335
|
|
|
404
|
-
|
|
336
|
+
**Q: How do I get the numeric value back in English for my API?**
|
|
337
|
+
A: The `onValueChange` callback always returns the raw English digit string (e.g. `"1234567"`), regardless of which locale is displayed.
|
|
405
338
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
// Component displays: ۱,۲۳۴,۵۶۷
|
|
409
|
-
// Form receives: "1234567"
|
|
410
|
-
```
|
|
339
|
+
**Q: Can I use this inside a form with native `onSubmit`?**
|
|
340
|
+
A: Use `onValueChange` to store the value in local state, then read from state on submit. The component also supports React Hook Form via the `Controller` pattern (see example above).
|
|
411
341
|
|
|
412
|
-
|
|
342
|
+
**Q: Does it handle right-to-left text direction automatically?**
|
|
343
|
+
A: The component formats numbers correctly for RTL — for full RTL layout, set `dir="rtl"` on your container or `style={{ textAlign: "right" }}` on the input.
|
|
413
344
|
|
|
414
|
-
|
|
345
|
+
**Q: What's the difference between `locale="fa"` and `locale="ar"`?**
|
|
346
|
+
A: `fa` uses Persian digits (۰–۹) and the `٫` decimal separator. `ar` uses Eastern Arabic digits (٠–٩). Both return English digits to `onValueChange`.
|
|
415
347
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
| Auto digit conversion | ✅ | ❌ | ⚠️ Partial |
|
|
419
|
-
| Cursor preservation | ✅ | ❌ | ⚠️ Buggy |
|
|
420
|
-
| TypeScript support | ✅ | ✅ | ⚠️ Varies |
|
|
421
|
-
| Multi-locale | ✅ | ❌ | ❌ |
|
|
422
|
-
| Bundle size | 🟢 Small | 🟢 N/A | 🔴 Large |
|
|
423
|
-
| Decimal precision | ✅ | ❌ | ⚠️ Limited |
|
|
348
|
+
**Q: Is there a maximum number size?**
|
|
349
|
+
A: The library uses string-based numeric comparison for validation, so it handles arbitrarily large and precise numbers without floating-point artifacts.
|
|
424
350
|
|
|
425
351
|
---
|
|
426
352
|
|
|
427
353
|
## 🤝 Contributing
|
|
428
354
|
|
|
429
|
-
Contributions are welcome!
|
|
355
|
+
Contributions are welcome!
|
|
430
356
|
|
|
431
357
|
1. Fork the repository
|
|
432
|
-
2. Create your feature branch
|
|
433
|
-
3. Commit your changes
|
|
434
|
-
4. Push
|
|
358
|
+
2. Create your feature branch: `git checkout -b feature/your-feature`
|
|
359
|
+
3. Commit your changes: `git commit -m 'Add your feature'`
|
|
360
|
+
4. Push: `git push origin feature/your-feature`
|
|
435
361
|
5. Open a Pull Request
|
|
436
362
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
## 📄 License
|
|
440
|
-
|
|
441
|
-
MIT © javad Sharifi
|
|
363
|
+
Please open an issue first for major changes so we can discuss the approach.
|
|
442
364
|
|
|
443
365
|
---
|
|
444
366
|
|
|
445
|
-
##
|
|
367
|
+
## 📄 License
|
|
446
368
|
|
|
447
|
-
|
|
448
|
-
- Uses [decimal.js](https://github.com/MikeMcl/decimal.js/) for precise decimal calculations
|
|
449
|
-
- Inspired by the needs of Persian and Arabic speaking developers
|
|
369
|
+
MIT © [Javad Sharifi](https://github.com/javadSharifi)
|
|
450
370
|
|
|
451
371
|
---
|
|
452
372
|
|
|
453
373
|
## 📞 Support
|
|
454
374
|
|
|
455
|
-
-
|
|
375
|
+
- 💬 Telegram: [@Javad_sharifi98](https://t.me/Javad_sharifi98)
|
|
456
376
|
- 🐛 [Issue Tracker](https://github.com/javadSharifi/persian-number-input/issues)
|
|
457
377
|
- 💬 [Discussions](https://github.com/javadSharifi/persian-number-input/discussions)
|
|
458
378
|
|
|
459
379
|
---
|
|
460
380
|
|
|
461
|
-
**Made with ❤️ for
|
|
381
|
+
**Made with ❤️ for Persian and Arabic developer communities**
|
|
382
|
+
|
|
383
|
+
<!-- Keywords for discoverability: persian number input react, farsi digit input, arabic number react component, rtl number input, persian input component, تبدیل اعداد فارسی به انگلیسی, ورودی عدد فارسی ریکت -->
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { TransformNumberOptions } from "../utils/transformNumber";
|
|
3
|
-
interface PersianNumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "
|
|
3
|
+
interface PersianNumberInputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "value" | "min" | "max">, Omit<TransformNumberOptions, "maxDecimals"> {
|
|
4
4
|
initialValue?: number | string;
|
|
5
5
|
onValueChange?: (value: string | undefined) => void;
|
|
6
6
|
min?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PersianNumberInput.d.ts","sourceRoot":"","sources":["../../src/components/PersianNumberInput.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"PersianNumberInput.d.ts","sourceRoot":"","sources":["../../src/components/PersianNumberInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAKnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvE,UAAU,uBACR,SAAQ,IAAI,CACR,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAC3C,OAAO,GAAG,KAAK,GAAG,KAAK,CACxB,EACD,IAAI,CAAC,sBAAsB,EAAE,aAAa,CAAC;IAC7C,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IACpD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAgBD,QAAA,MAAM,kBAAkB,kGA6DtB,CAAC;AAIH,eAAe,kBAAkB,CAAC"}
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
-
var t = {};
|
|
4
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
-
t[p] = s[p];
|
|
6
|
-
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
-
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
-
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
-
t[p[i]] = s[p[i]];
|
|
10
|
-
}
|
|
11
|
-
return t;
|
|
12
|
-
};
|
|
13
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
3
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
15
4
|
const react_1 = require("react");
|
|
16
5
|
const usePersianNumberInput_1 = require("../hooks/usePersianNumberInput");
|
|
6
|
+
function mergeRefs(...refs) {
|
|
7
|
+
return (value) => {
|
|
8
|
+
for (const ref of refs) {
|
|
9
|
+
if (typeof ref === "function") {
|
|
10
|
+
ref(value);
|
|
11
|
+
}
|
|
12
|
+
else if (ref && typeof ref === "object") {
|
|
13
|
+
ref.current = value;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
17
18
|
const PersianNumberInput = (0, react_1.forwardRef)((props, ref) => {
|
|
18
|
-
const { initialValue, separatorCount, separatorChar, decimalChar, suffix, locale, showZero, onValueChange, min, max, maxDecimals,
|
|
19
|
-
const
|
|
19
|
+
const { initialValue, separatorCount, separatorChar, decimalChar, suffix, locale, showZero, onValueChange, min, max, maxDecimals, onChange: externalOnChange, onBlur: externalOnBlur, onKeyDown: externalOnKeyDown, onPaste: externalOnPaste, ...rest } = props;
|
|
20
|
+
const hookProps = {
|
|
20
21
|
initialValue,
|
|
21
22
|
separatorCount,
|
|
22
23
|
separatorChar,
|
|
@@ -28,10 +29,14 @@ const PersianNumberInput = (0, react_1.forwardRef)((props, ref) => {
|
|
|
28
29
|
min,
|
|
29
30
|
max,
|
|
30
31
|
maxDecimals,
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
onChange: externalOnChange,
|
|
33
|
+
onBlur: externalOnBlur,
|
|
34
|
+
onKeyDown: externalOnKeyDown,
|
|
35
|
+
onPaste: externalOnPaste,
|
|
36
|
+
};
|
|
37
|
+
const { value, onChange, onKeyDown, onPaste, onBlur, inputRef, isInvalid } = (0, usePersianNumberInput_1.usePersianNumberInput)(hookProps);
|
|
38
|
+
const mergedRef = (0, react_1.useMemo)(() => mergeRefs(ref, inputRef), [ref, inputRef]);
|
|
39
|
+
return ((0, jsx_runtime_1.jsx)("input", { ref: mergedRef, type: "text", inputMode: "decimal", dir: "ltr", value: value, onChange: onChange, onKeyDown: onKeyDown, onPaste: onPaste, onBlur: onBlur, "aria-invalid": isInvalid || undefined, ...rest }));
|
|
35
40
|
});
|
|
36
41
|
PersianNumberInput.displayName = "PersianNumberInput";
|
|
37
42
|
exports.default = PersianNumberInput;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PersianNumberInput.js","sourceRoot":"","sources":["../../src/components/PersianNumberInput.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PersianNumberInput.js","sourceRoot":"","sources":["../../src/components/PersianNumberInput.tsx"],"names":[],"mappings":";;;AAAA,iCAAmD;AACnD,0EAGwC;AAgBxC,SAAS,SAAS,CAChB,GAAG,IAAyC;IAE5C,OAAO,CAAC,KAAQ,EAAE,EAAE;QAClB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,IAAI,OAAO,GAAG,KAAK,UAAU,EAAE,CAAC;gBAC9B,GAAG,CAAC,KAAK,CAAC,CAAC;YACb,CAAC;iBAAM,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;gBACzC,GAAwC,CAAC,OAAO,GAAG,KAAK,CAAC;YAC5D,CAAC;QACH,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,kBAAkB,GAAG,IAAA,kBAAU,EAGnC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IACf,MAAM,EACJ,YAAY,EACZ,cAAc,EACd,aAAa,EACb,WAAW,EACX,MAAM,EACN,MAAM,EACN,QAAQ,EACR,aAAa,EACb,GAAG,EACH,GAAG,EACH,WAAW,EACX,QAAQ,EAAE,gBAAgB,EAC1B,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,iBAAiB,EAC5B,OAAO,EAAE,eAAe,EACxB,GAAG,IAAI,EACR,GAAG,KAAK,CAAC;IAEV,MAAM,SAAS,GAA+B;QAC5C,YAAY;QACZ,cAAc;QACd,aAAa;QACb,WAAW;QACX,MAAM;QACN,MAAM;QACN,QAAQ;QACR,aAAa;QACb,GAAG;QACH,GAAG;QACH,WAAW;QACX,QAAQ,EAAE,gBAAgB;QAC1B,MAAM,EAAE,cAAc;QACtB,SAAS,EAAE,iBAAiB;QAC5B,OAAO,EAAE,eAAe;KACzB,CAAC;IAEF,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GACxE,IAAA,6CAAqB,EAAC,SAAS,CAAC,CAAC;IAEnC,MAAM,SAAS,GAAG,IAAA,eAAO,EAAC,GAAG,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE3E,OAAO,CACL,kCACE,GAAG,EAAE,SAAS,EACd,IAAI,EAAC,MAAM,EACX,SAAS,EAAC,SAAS,EACnB,GAAG,EAAC,KAAK,EACT,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,SAAS,EACpB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,kBACA,SAAS,IAAI,SAAS,KAChC,IAAI,GACR,CACH,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,kBAAkB,CAAC,WAAW,GAAG,oBAAoB,CAAC;AAEtD,kBAAe,kBAAkB,CAAC"}
|