persian-number-input 4.3.4 → 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.
Files changed (45) hide show
  1. package/README.fa.md +190 -240
  2. package/README.md +152 -230
  3. package/dist/components/PersianNumberInput.d.ts +1 -1
  4. package/dist/components/PersianNumberInput.d.ts.map +1 -1
  5. package/dist/components/PersianNumberInput.js +22 -17
  6. package/dist/components/PersianNumberInput.js.map +1 -1
  7. package/dist/components/PersianNumberInput.test.js +24 -6
  8. package/dist/components/PersianNumberInput.test.js.map +1 -1
  9. package/dist/hooks/useCursorManager.d.ts +4 -0
  10. package/dist/hooks/useCursorManager.d.ts.map +1 -0
  11. package/dist/hooks/useCursorManager.js +19 -0
  12. package/dist/hooks/useCursorManager.js.map +1 -0
  13. package/dist/hooks/useNumberFormatter.d.ts +18 -0
  14. package/dist/hooks/useNumberFormatter.d.ts.map +1 -0
  15. package/dist/hooks/useNumberFormatter.js +29 -0
  16. package/dist/hooks/useNumberFormatter.js.map +1 -0
  17. package/dist/hooks/useNumberValidation.d.ts +11 -0
  18. package/dist/hooks/useNumberValidation.d.ts.map +1 -0
  19. package/dist/hooks/useNumberValidation.js +22 -0
  20. package/dist/hooks/useNumberValidation.js.map +1 -0
  21. package/dist/hooks/useNumericInputEvents.d.ts +18 -0
  22. package/dist/hooks/useNumericInputEvents.d.ts.map +1 -0
  23. package/dist/hooks/useNumericInputEvents.js +39 -0
  24. package/dist/hooks/useNumericInputEvents.js.map +1 -0
  25. package/dist/hooks/usePersianNumberInput.d.ts +7 -2
  26. package/dist/hooks/usePersianNumberInput.d.ts.map +1 -1
  27. package/dist/hooks/usePersianNumberInput.js +66 -63
  28. package/dist/hooks/usePersianNumberInput.js.map +1 -1
  29. package/dist/test-utils.d.ts +7 -0
  30. package/dist/test-utils.d.ts.map +1 -0
  31. package/dist/test-utils.js +27 -0
  32. package/dist/test-utils.js.map +1 -0
  33. package/dist/utils/digitUtils.d.ts +4 -0
  34. package/dist/utils/digitUtils.d.ts.map +1 -1
  35. package/dist/utils/digitUtils.js +44 -17
  36. package/dist/utils/digitUtils.js.map +1 -1
  37. package/dist/utils/keyFilter.d.ts +16 -0
  38. package/dist/utils/keyFilter.d.ts.map +1 -0
  39. package/dist/utils/keyFilter.js +31 -0
  40. package/dist/utils/keyFilter.js.map +1 -0
  41. package/dist/utils/validation.d.ts +22 -0
  42. package/dist/utils/validation.d.ts.map +1 -0
  43. package/dist/utils/validation.js +85 -0
  44. package/dist/utils/validation.js.map +1 -0
  45. package/package.json +15 -4
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
- A lightweight, powerful React library for handling Persian (Farsi) and Arabic number inputs with automatic digit conversion, formatting, and localization support.
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
  [![npm version](https://img.shields.io/npm/v/persian-number-input.svg)](https://www.npmjs.com/package/persian-number-input)
7
8
  [![npm downloads](https://img.shields.io/npm/dm/persian-number-input.svg)](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
- Experience the component in action with our interactive demo!
16
+ ## Why Persian Number Input?
16
17
 
17
- ## 📊 Bundle Size
18
+ Building forms for Persian or Arabic-speaking users comes with real challenges:
18
19
 
19
- This library is extremely lightweight:
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
- persian-number-input: ~1KB (minified + gzipped)
29
+ User types: ۱۲۳۴۵۶۷
30
+ Displayed as: ۱,۲۳۴,۵۶۷
31
+ Form receives: "1234567"
23
32
  ```
24
33
 
25
- ![Bundle Size Comparison](./public/size.png)
26
-
27
34
  ---
28
35
 
29
36
  ## ✨ Features
30
37
 
31
- - 🔢 **Automatic Digit Conversion** - Seamlessly converts Persian (۰-۹) and Arabic (٠-٩) digits to English and vice versa
32
- - 🌍 **Multi-locale Support** - Built-in support for Persian (fa), Arabic (ar), and English (en)
33
- - 📊 **Number Formatting** - Automatic thousand separators with customizable characters
34
- - 💰 **Currency Ready** - Add prefixes, suffixes, and custom decimal separators
35
- - ⚡ **Lightweight** - Tiny bundle size with zero dependencies (except decimal.js for precision)
36
- - 🎯 **Type-Safe** - Full TypeScript support with complete type definitions
37
- - **Accessible** - Follows best practices for input accessibility
38
- - 🎨 **Customizable** - Extensive configuration options for any use case
39
- - 🔄 **Real-time Formatting** - Format numbers as users type with cursor position preservation
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
- **Output:** `۱,۲۳۴,۵۶۷`
80
+ Output displayed to user: `۱,۲۳۴,۵۶۷`
79
81
 
80
82
  ---
81
83
 
82
84
  ## 📚 Usage Examples
83
85
 
84
- ### Currency Input (Persian Toman)
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
- **Output:** `۵,۰۰۰,۰۰۰ تومان`
99
+ Displayed: `۵,۰۰۰,۰۰۰ تومان`
98
100
 
99
101
  ---
100
102
 
101
- ### Decimal Numbers with Custom Separator
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
- **Output:** `۱,۲۳۴٫۵۶`
132
+ Displayed: `۱,۲۳۴٫۵۶`
115
133
 
116
134
  ---
117
135
 
118
- ### Price Input with Validation
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
- ### Arabic Locale
152
+ ### With React Hook Form
137
153
 
138
154
  ```tsx
139
- <PersianNumberInput
140
- initialValue={987654}
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
- **Output:** `٩٨٧,٦٥٤ ر.س`
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 Hook (Advanced)
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
- className="custom-input"
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 Props
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"` | Locale for digit conversion |
193
- | `separatorCount` | `number` | `3` | Number of digits between separators |
194
- | `separatorChar` | `string` | `","` | Character used for thousand separator |
195
- | `decimalChar` | `string` | Auto | Decimal separator character |
196
- | `suffix` | `string` | `undefined` | Suffix text (e.g., currency symbol) |
197
- | `maxDecimals` | `number` | `undefined` | Maximum decimal places allowed |
198
- | `min` | `number` | `undefined` | Minimum allowed value |
199
- | `max` | `number` | `undefined` | Maximum allowed value |
200
- | `showZero` | `boolean` | `false` | Show zero when value is empty |
201
- | `onValueChange` | `(value: string \| undefined) => void` | `undefined` | Callback when value changes (returns raw English digits) |
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 Functions
236
+ ### Utility functions
208
237
 
209
238
  #### `transformNumber(rawValue, options)`
210
239
 
211
- Formats a number string according to locale and options.
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
- const formatted = transformNumber("1234567.89", {
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
- Converts Persian/Arabic digits to English digits.
257
+ Convert Persian or Arabic digits to English.
230
258
 
231
259
  ```tsx
232
260
  import { toEnglishDigits } from "persian-number-input";
233
261
 
234
- console.log(toEnglishDigits("۱۲۳۴")); // "1234"
235
- console.log(toEnglishDigits("٩٨٧٦")); // "9876"
262
+ toEnglishDigits("۱۲۳۴"); // "1234"
263
+ toEnglishDigits("٩٨٧٦"); // "9876"
236
264
  ```
237
265
 
238
266
  #### `toLocalizedDigits(numStr, locale)`
239
267
 
240
- Converts English digits to localized digits.
268
+ Convert English digits to the target locale.
241
269
 
242
270
  ```tsx
243
271
  import { toLocalizedDigits } from "persian-number-input";
244
272
 
245
- console.log(toLocalizedDigits("1234", "fa")); // "۱۲۳۴"
246
- console.log(toLocalizedDigits("5678", "ar")); // "٥٦٧٨"
273
+ toLocalizedDigits("1234", "fa"); // "۱۲۳۴"
274
+ toLocalizedDigits("5678", "ar"); // "٥٦٧٨"
247
275
  ```
248
276
 
249
277
  #### `sanitizeNumericInput(value, maxDecimals?, decimalChar?)`
250
278
 
251
- Cleans and validates numeric input.
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
- console.log(sanitizeNumericInput("۱۲۳abc۴۵۶", 2)); // "123456"
257
- console.log(sanitizeNumericInput("12.345.67", 2)); // "12.34"
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, including `className` and `style`:
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="custom-input"
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
- ### With Tailwind CSS
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
- ## 🌟 Advanced Examples
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
- ## 🔍 Why Persian Number Input?
315
+ ## 🏆 Comparison
392
316
 
393
- ### The Problem
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
- Working with Persian and Arabic numerals in web applications is challenging:
329
+ ---
396
330
 
397
- - Users type in their native digits, but forms expect English digits
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
- ### The Solution
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
- Persian Number Input handles all these complexities automatically:
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
- ```tsx
407
- // User types: ۱۲۳۴۵۶۷
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
- ## 🏆 Comparison
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
- | Feature | Persian Number Input | Native Input | Other Libraries |
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! Please feel free to submit a Pull Request.
355
+ Contributions are welcome!
430
356
 
431
357
  1. Fork the repository
432
- 2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
433
- 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
434
- 4. Push to the branch (`git push origin feature/AmazingFeature`)
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
- ## 🙏 Acknowledgments
367
+ ## 📄 License
446
368
 
447
- - Built with TypeScript and React
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
- - 📧 telegram: [Javad Sharifi](https://t.me/Javad_sharifi98)
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 the Persian and Arabic developer community**
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>, "onChange" | "value" | "min" | "max">, Omit<TransformNumberOptions, "maxDecimals"> {
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,KAA0C,MAAM,OAAO,CAAC;AAE/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAEvE,UAAU,uBACR,SAAQ,IAAI,CACR,KAAK,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAC3C,UAAU,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,CACrC,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;AAED,QAAA,MAAM,kBAAkB,kGAiDtB,CAAC;AAIH,eAAe,kBAAkB,CAAC"}
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, onBlur: propsOnBlur } = props, rest = __rest(props, ["initialValue", "separatorCount", "separatorChar", "decimalChar", "suffix", "locale", "showZero", "onValueChange", "min", "max", "maxDecimals", "onBlur"]);
19
- const { value, onChange, onBlur, inputRef } = (0, usePersianNumberInput_1.usePersianNumberInput)({
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
- onBlur: propsOnBlur,
32
- });
33
- (0, react_1.useImperativeHandle)(ref, () => inputRef.current);
34
- return ((0, jsx_runtime_1.jsx)("input", Object.assign({}, rest, { ref: inputRef, type: "text", inputMode: "decimal", dir: "ltr", value: value, onChange: onChange, onBlur: onBlur })));
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":";;;;;;;;;;;;;;AAAA,iCAA+D;AAC/D,0EAAuE;AAgBvE,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,MAAM,EAAE,WAAW,KAEjB,KAAK,EADJ,IAAI,UACL,KAAK,EAdH,0JAcL,CAAQ,CAAC;IAEV,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAA,6CAAqB,EAAC;QAClE,YAAY;QACZ,cAAc;QACd,aAAa;QACb,WAAW;QACX,MAAM;QACN,MAAM;QACN,QAAQ;QACR,aAAa;QACb,GAAG;QACH,GAAG;QACH,WAAW;QACX,MAAM,EAAE,WAAW;KACpB,CAAC,CAAC;IAEH,IAAA,2BAAmB,EAAC,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAQ,CAAC,CAAC;IAElD,OAAO,CACL,kDACM,IAAI,IACR,GAAG,EAAE,QAAQ,EACb,IAAI,EAAC,MAAM,EACX,SAAS,EAAC,SAAS,EACnB,GAAG,EAAC,KAAK,EACT,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,MAAM,IACd,CACH,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,kBAAkB,CAAC,WAAW,GAAG,oBAAoB,CAAC;AAEtD,kBAAe,kBAAkB,CAAC"}
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"}