raqam 0.2.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/LICENSE +21 -0
- package/README.md +344 -0
- package/dist/chunk-IG7CVIA2.js +14 -0
- package/dist/chunk-IG7CVIA2.js.map +1 -0
- package/dist/chunk-NBAZIJ5W.js +25 -0
- package/dist/chunk-NBAZIJ5W.js.map +1 -0
- package/dist/chunk-NSFX2EAT.js +14 -0
- package/dist/chunk-NSFX2EAT.js.map +1 -0
- package/dist/chunk-NTROGAES.js +14 -0
- package/dist/chunk-NTROGAES.js.map +1 -0
- package/dist/chunk-VOBTYII2.js +14 -0
- package/dist/chunk-VOBTYII2.js.map +1 -0
- package/dist/chunk-WTS5RY7S.js +16 -0
- package/dist/chunk-WTS5RY7S.js.map +1 -0
- package/dist/core.cjs +2 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +351 -0
- package/dist/core.d.ts +351 -0
- package/dist/core.js +2 -0
- package/dist/core.js.map +1 -0
- package/dist/index-B8X3-9h1.d.cts +343 -0
- package/dist/index-B8X3-9h1.d.ts +343 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +193 -0
- package/dist/index.d.ts +193 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/locales/ar.cjs +36 -0
- package/dist/locales/ar.cjs.map +1 -0
- package/dist/locales/ar.d.cts +4 -0
- package/dist/locales/ar.d.ts +4 -0
- package/dist/locales/ar.js +4 -0
- package/dist/locales/ar.js.map +1 -0
- package/dist/locales/bn.cjs +36 -0
- package/dist/locales/bn.cjs.map +1 -0
- package/dist/locales/bn.d.cts +4 -0
- package/dist/locales/bn.d.ts +4 -0
- package/dist/locales/bn.js +4 -0
- package/dist/locales/bn.js.map +1 -0
- package/dist/locales/fa.cjs +38 -0
- package/dist/locales/fa.cjs.map +1 -0
- package/dist/locales/fa.d.cts +4 -0
- package/dist/locales/fa.d.ts +4 -0
- package/dist/locales/fa.js +4 -0
- package/dist/locales/fa.js.map +1 -0
- package/dist/locales/hi.cjs +36 -0
- package/dist/locales/hi.cjs.map +1 -0
- package/dist/locales/hi.d.cts +4 -0
- package/dist/locales/hi.d.ts +4 -0
- package/dist/locales/hi.js +4 -0
- package/dist/locales/hi.js.map +1 -0
- package/dist/locales/index.cjs +78 -0
- package/dist/locales/index.cjs.map +1 -0
- package/dist/locales/index.d.cts +5 -0
- package/dist/locales/index.d.ts +5 -0
- package/dist/locales/index.js +8 -0
- package/dist/locales/index.js.map +1 -0
- package/dist/locales/th.cjs +36 -0
- package/dist/locales/th.cjs.map +1 -0
- package/dist/locales/th.d.cts +4 -0
- package/dist/locales/th.d.ts +4 -0
- package/dist/locales/th.js +4 -0
- package/dist/locales/th.js.map +1 -0
- package/dist/react.cjs +3 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +3 -0
- package/dist/react.d.ts +3 -0
- package/dist/react.js +3 -0
- package/dist/react.js.map +1 -0
- package/package.json +170 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 numra contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
# raqam đĸ
|
|
2
|
+
|
|
3
|
+
**The definitive React number input: live formatting, full i18n, headless, accessible.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/raqam)
|
|
6
|
+
[](https://bundlephobia.com/package/raqam)
|
|
7
|
+
[](https://github.com/47vigen/numra/actions)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
|
|
11
|
+
## ⨠Why raqam?
|
|
12
|
+
|
|
13
|
+
| Feature | Base UI | React Aria | Mantine | **raqam** |
|
|
14
|
+
|---------|:-------:|:----------:|:-------:|:---------:|
|
|
15
|
+
| Live formatting while typing | â blur | â blur | â
| â
|
|
|
16
|
+
| Truly headless | â
| â
| â | â
|
|
|
17
|
+
| i18n digit input (Persian ÛąÛ˛Ûŗ, Arabic ŲĄŲĸŲŖâĻ) | â | â
| â | â
|
|
|
18
|
+
| WAI-ARIA spinbutton | â
| â
â
| â ī¸ | â
â
|
|
|
19
|
+
| Bundle size | ~10 KB | ~30 KB | ~60 KB | **~1.7 KB core** |
|
|
20
|
+
|
|
21
|
+
No existing package combines all four. raqam does.
|
|
22
|
+
|
|
23
|
+
## đĻ Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install raqam
|
|
27
|
+
# or
|
|
28
|
+
pnpm add raqam
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Peer dependencies:** React 18 or 19.
|
|
32
|
+
|
|
33
|
+
## đ Quick start
|
|
34
|
+
|
|
35
|
+
### Hook API
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { useNumberFieldState, useNumberField } from 'raqam'
|
|
39
|
+
import { useRef } from 'react'
|
|
40
|
+
|
|
41
|
+
function PriceInput() {
|
|
42
|
+
const state = useNumberFieldState({
|
|
43
|
+
locale: 'en-US',
|
|
44
|
+
formatOptions: { style: 'currency', currency: 'USD' },
|
|
45
|
+
minValue: 0,
|
|
46
|
+
defaultValue: 1234.56,
|
|
47
|
+
})
|
|
48
|
+
const inputRef = useRef(null)
|
|
49
|
+
const { inputProps, labelProps, incrementButtonProps, decrementButtonProps } =
|
|
50
|
+
useNumberField({ label: 'Price' }, state, inputRef)
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div>
|
|
54
|
+
<label {...labelProps}>Price</label>
|
|
55
|
+
<button {...decrementButtonProps}>â</button>
|
|
56
|
+
<input ref={inputRef} {...inputProps} />
|
|
57
|
+
<button {...incrementButtonProps}>+</button>
|
|
58
|
+
</div>
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Headless Component API
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { NumberField } from 'raqam'
|
|
67
|
+
|
|
68
|
+
function PriceField() {
|
|
69
|
+
return (
|
|
70
|
+
<NumberField.Root
|
|
71
|
+
locale="en-US"
|
|
72
|
+
formatOptions={{ style: 'currency', currency: 'USD' }}
|
|
73
|
+
defaultValue={1234.56}
|
|
74
|
+
minValue={0}
|
|
75
|
+
onValueChange={(value, { reason }) => console.log(value, reason)}
|
|
76
|
+
>
|
|
77
|
+
<NumberField.Label>Price</NumberField.Label>
|
|
78
|
+
<NumberField.Group>
|
|
79
|
+
<NumberField.Decrement>â</NumberField.Decrement>
|
|
80
|
+
<NumberField.Input />
|
|
81
|
+
<NumberField.Increment>+</NumberField.Increment>
|
|
82
|
+
</NumberField.Group>
|
|
83
|
+
<NumberField.Description>Enter the product price</NumberField.Description>
|
|
84
|
+
<NumberField.ErrorMessage />
|
|
85
|
+
</NumberField.Root>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## đ¨ Format presets
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { presets, NumberField } from 'raqam'
|
|
94
|
+
|
|
95
|
+
<NumberField.Root formatOptions={presets.currency('USD')} /> // $1,234.56
|
|
96
|
+
<NumberField.Root formatOptions={presets.accounting('USD')} /> // (1,234.56)
|
|
97
|
+
<NumberField.Root formatOptions={presets.percent} /> // 12.3%
|
|
98
|
+
<NumberField.Root formatOptions={presets.compact} /> // 1.2K
|
|
99
|
+
<NumberField.Root formatOptions={presets.scientific} /> // 1.23E3
|
|
100
|
+
<NumberField.Root formatOptions={presets.integer} /> // 1,234
|
|
101
|
+
<NumberField.Root formatOptions={presets.financial} fixedDecimalScale /> // 1,234.00
|
|
102
|
+
<NumberField.Root formatOptions={presets.unit('kilometer-per-hour')} /> // 120 km/h
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## đ Locales & i18n
|
|
106
|
+
|
|
107
|
+
Persian input with native digits â just import the plugin and set the locale:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import 'raqam/locales/fa' // registers Û°âÛš digit normalization (< 200 B)
|
|
111
|
+
import { NumberField } from 'raqam'
|
|
112
|
+
|
|
113
|
+
<NumberField.Root
|
|
114
|
+
locale="fa-IR"
|
|
115
|
+
formatOptions={{ style: 'currency', currency: 'IRR' }}
|
|
116
|
+
suffix=" ØĒŲŲ
اŲ"
|
|
117
|
+
/>
|
|
118
|
+
// user types ÛąÛ˛ÛŗÛ´, raqam parses and formats it correctly in real-time
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Supported scripts: đŽđˇ Persian `fa`, đ¸đĻ Arabic `ar`, đ§đŠ Bengali `bn`, đŽđŗ Hindi `hi`, đšđ Thai `th`. RTL is auto-detected and handled.
|
|
122
|
+
|
|
123
|
+
## â
Custom validation
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
<NumberField.Root
|
|
127
|
+
minValue={0}
|
|
128
|
+
validate={(value) => {
|
|
129
|
+
if (value === null) return 'Required'
|
|
130
|
+
if (value % 2 !== 0) return 'Must be an even number'
|
|
131
|
+
return true
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
<NumberField.Input />
|
|
135
|
+
<NumberField.ErrorMessage /> {/* auto-renders the validate() error string */}
|
|
136
|
+
</NumberField.Root>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## đī¸ Display-only formatting
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import { useNumberFieldFormat } from 'raqam'
|
|
143
|
+
|
|
144
|
+
function PriceDisplay({ price }: { price: number }) {
|
|
145
|
+
const formatted = useNumberFieldFormat(price, {
|
|
146
|
+
locale: 'en-US',
|
|
147
|
+
formatOptions: { style: 'currency', currency: 'USD' },
|
|
148
|
+
})
|
|
149
|
+
return <span>{formatted}</span> // "$1,234.56"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Works in React Server Components too via `raqam/server`:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
import { createFormatter } from 'raqam/server' // zero React deps
|
|
157
|
+
|
|
158
|
+
const formatter = createFormatter({
|
|
159
|
+
locale: 'en-US',
|
|
160
|
+
formatOptions: { style: 'currency', currency: 'USD' },
|
|
161
|
+
})
|
|
162
|
+
const displayPrice = formatter.format(1234.56) // "$1,234.56"
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## đąī¸ ScrubArea (drag to change value)
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
<NumberField.Root defaultValue={50} minValue={0} maxValue={100}>
|
|
169
|
+
<NumberField.ScrubArea direction="horizontal" pixelSensitivity={2}>
|
|
170
|
+
<NumberField.Label>Opacity</NumberField.Label>
|
|
171
|
+
<NumberField.ScrubAreaCursor>âē</NumberField.ScrubAreaCursor>
|
|
172
|
+
</NumberField.ScrubArea>
|
|
173
|
+
<NumberField.Input />
|
|
174
|
+
</NumberField.Root>
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Uses the Pointer Lock API so the cursor never hits the screen edge during drag.
|
|
178
|
+
|
|
179
|
+
## đ CSS styling with data attributes
|
|
180
|
+
|
|
181
|
+
```css
|
|
182
|
+
/* All state-based styling â no JS needed */
|
|
183
|
+
[data-focused] { outline: 2px solid blue; }
|
|
184
|
+
[data-invalid] { border-color: red; }
|
|
185
|
+
[data-disabled] { opacity: 0.5; }
|
|
186
|
+
[data-readonly] { background: #f5f5f5; }
|
|
187
|
+
[data-rtl] { /* RTL-specific overrides */ }
|
|
188
|
+
[data-scrubbing] { cursor: ew-resize; }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## đ react-hook-form integration
|
|
192
|
+
|
|
193
|
+
```tsx
|
|
194
|
+
import { Controller } from 'react-hook-form'
|
|
195
|
+
import { NumberField } from 'raqam'
|
|
196
|
+
|
|
197
|
+
<Controller
|
|
198
|
+
name="price"
|
|
199
|
+
control={control}
|
|
200
|
+
render={({ field, fieldState }) => (
|
|
201
|
+
<NumberField.Root
|
|
202
|
+
value={field.value}
|
|
203
|
+
onChange={field.onChange}
|
|
204
|
+
onBlur={field.onBlur}
|
|
205
|
+
validate={() => fieldState.error?.message ?? true}
|
|
206
|
+
>
|
|
207
|
+
<NumberField.Label>Price</NumberField.Label>
|
|
208
|
+
<NumberField.Input />
|
|
209
|
+
<NumberField.ErrorMessage />
|
|
210
|
+
</NumberField.Root>
|
|
211
|
+
)}
|
|
212
|
+
/>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## ⥠Arbitrary-precision string mode
|
|
216
|
+
|
|
217
|
+
For financial apps that need to avoid IEEE 754 float rounding:
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
<NumberField.Root
|
|
221
|
+
onRawChange={(rawValue) => {
|
|
222
|
+
// rawValue is the exact string before any JS float conversion
|
|
223
|
+
// e.g. "0.1000000001" â feed it to your BigDecimal library
|
|
224
|
+
myDecimal.set(rawValue)
|
|
225
|
+
}}
|
|
226
|
+
/>
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Also available as `state.rawValue` from the hook API.
|
|
230
|
+
|
|
231
|
+
## đ§ Custom formatter / parser
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
import Decimal from 'decimal.js'
|
|
235
|
+
|
|
236
|
+
<NumberField.Root
|
|
237
|
+
formatValue={(value) => new Decimal(value).toFixed(8)}
|
|
238
|
+
parseValue={(input) => {
|
|
239
|
+
try {
|
|
240
|
+
return { value: new Decimal(input).toNumber(), isIntermediate: false }
|
|
241
|
+
} catch {
|
|
242
|
+
return { value: null, isIntermediate: input.endsWith('.') }
|
|
243
|
+
}
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## đ API Reference
|
|
249
|
+
|
|
250
|
+
### `useNumberFieldState(options)`
|
|
251
|
+
|
|
252
|
+
State management hook â returns `NumberFieldState`.
|
|
253
|
+
|
|
254
|
+
| Prop | Type | Default | Description |
|
|
255
|
+
|------|------|---------|-------------|
|
|
256
|
+
| `value` | `number \| null` | â | Controlled value |
|
|
257
|
+
| `defaultValue` | `number` | â | Uncontrolled default |
|
|
258
|
+
| `onChange` | `(value: number \| null) => void` | â | Fires on every change |
|
|
259
|
+
| `onRawChange` | `(raw: string \| null) => void` | â | Fires with raw unformatted string |
|
|
260
|
+
| `locale` | `string` | browser | BCP 47 locale tag |
|
|
261
|
+
| `formatOptions` | `Intl.NumberFormatOptions` | `{}` | Full Intl options |
|
|
262
|
+
| `minValue` | `number` | â | Minimum value |
|
|
263
|
+
| `maxValue` | `number` | â | Maximum value |
|
|
264
|
+
| `step` | `number` | `1` | Arrow key step |
|
|
265
|
+
| `largeStep` | `number` | `step à 10` | Shift+Arrow step |
|
|
266
|
+
| `smallStep` | `number` | `step à 0.1` | Ctrl/Meta+Arrow step |
|
|
267
|
+
| `clampBehavior` | `"blur" \| "strict" \| "none"` | `"blur"` | When to clamp to min/max |
|
|
268
|
+
| `allowNegative` | `boolean` | `true` | Allow negative values |
|
|
269
|
+
| `allowDecimal` | `boolean` | `true` | Allow decimal values |
|
|
270
|
+
| `fixedDecimalScale` | `boolean` | `false` | Always show max decimal places |
|
|
271
|
+
| `allowOutOfRange` | `boolean` | `false` | Skip clamping (server-side validation) |
|
|
272
|
+
| `validate` | `(v: number \| null) => boolean \| string \| null` | â | Custom validation |
|
|
273
|
+
| `prefix` | `string` | â | String prefix (e.g. `"$"`) |
|
|
274
|
+
| `suffix` | `string` | â | String suffix (e.g. `" ØĒŲŲ
اŲ"`) |
|
|
275
|
+
| `disabled` | `boolean` | `false` | Disable the field |
|
|
276
|
+
| `readOnly` | `boolean` | `false` | Read-only mode |
|
|
277
|
+
|
|
278
|
+
### `useNumberField(props, state, inputRef)`
|
|
279
|
+
|
|
280
|
+
Behavior hook â returns `NumberFieldAria` prop objects for each element.
|
|
281
|
+
|
|
282
|
+
Additional props beyond state options:
|
|
283
|
+
|
|
284
|
+
| Prop | Type | Default | Description |
|
|
285
|
+
|------|------|---------|-------------|
|
|
286
|
+
| `allowMouseWheel` | `boolean` | `false` | Mouse wheel to increment/decrement |
|
|
287
|
+
| `copyBehavior` | `"formatted" \| "raw" \| "number"` | `"formatted"` | Clipboard content on copy |
|
|
288
|
+
| `stepHoldDelay` | `number` | `400` | Press-and-hold initial delay (ms) |
|
|
289
|
+
| `stepHoldInterval` | `number` | `200` | Press-and-hold repeat interval (ms) |
|
|
290
|
+
| `formatValue` | `(value: number) => string` | â | Custom format function |
|
|
291
|
+
| `parseValue` | `(input: string) => ParseResult` | â | Custom parse function |
|
|
292
|
+
|
|
293
|
+
### `NumberField.Root` extra props
|
|
294
|
+
|
|
295
|
+
| Prop | Type | Description |
|
|
296
|
+
|------|------|-------------|
|
|
297
|
+
| `onValueChange` | `(value, { reason, formattedValue }) => void` | Fires with change reason |
|
|
298
|
+
| `onValueCommitted` | `(value, { reason }) => void` | Fires only on blur/Enter |
|
|
299
|
+
|
|
300
|
+
### `useNumberFieldFormat(value, options)`
|
|
301
|
+
|
|
302
|
+
Display-only formatting hook. Returns a formatted string. Zero state overhead â safe in RSC via `raqam/server`.
|
|
303
|
+
|
|
304
|
+
### `NumberField.*` components
|
|
305
|
+
|
|
306
|
+
| Component | Description |
|
|
307
|
+
|-----------|-------------|
|
|
308
|
+
| `Root` | Context provider + state orchestration |
|
|
309
|
+
| `Label` | `<label>` with correct `htmlFor` wiring |
|
|
310
|
+
| `Group` | `<div role="group">` for input + buttons |
|
|
311
|
+
| `Input` | `<input type="text" role="spinbutton">` with live formatting |
|
|
312
|
+
| `Increment` | Increment button with press-and-hold acceleration |
|
|
313
|
+
| `Decrement` | Decrement button with press-and-hold acceleration |
|
|
314
|
+
| `HiddenInput` | Hidden `<input>` for native FormData submission |
|
|
315
|
+
| `ScrubArea` | Pointer Lock drag-to-adjust area |
|
|
316
|
+
| `ScrubAreaCursor` | Custom cursor rendered during pointer lock |
|
|
317
|
+
| `Description` | Help text linked via `aria-describedby` |
|
|
318
|
+
| `ErrorMessage` | Error display with `role="alert"` |
|
|
319
|
+
| `Formatted` | Read-only formatted value display span |
|
|
320
|
+
|
|
321
|
+
Every component accepts a `render` prop for element replacement:
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
<NumberField.Increment render={<MyIconButton />}>â˛</NumberField.Increment>
|
|
325
|
+
// or with state access:
|
|
326
|
+
<NumberField.Increment render={(props, state) => (
|
|
327
|
+
<MyBtn disabled={!state.canIncrement} {...props} />
|
|
328
|
+
)} />
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## đĻ Bundle size
|
|
332
|
+
|
|
333
|
+
Actual sizes (brotli compressed):
|
|
334
|
+
|
|
335
|
+
| Entry | Size |
|
|
336
|
+
|-------|------|
|
|
337
|
+
| `raqam/core` | ~1.7 KB |
|
|
338
|
+
| `raqam` (hooks + components) | ~7 KB |
|
|
339
|
+
| `raqam/react` | ~6.8 KB |
|
|
340
|
+
| `raqam/locales/fa` | ~200 B |
|
|
341
|
+
|
|
342
|
+
## đ License
|
|
343
|
+
|
|
344
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { registerLocale } from './chunk-NBAZIJ5W.js';
|
|
2
|
+
|
|
3
|
+
// src/locales/ar.ts
|
|
4
|
+
registerLocale({
|
|
5
|
+
digitBlocks: [
|
|
6
|
+
[1632, 1641]
|
|
7
|
+
// Arabic-Indic Ų âŲŠ
|
|
8
|
+
]
|
|
9
|
+
});
|
|
10
|
+
var LOCALE_CODES = ["ar", "ar-EG", "ar-SA", "ar-MA", "ar-DZ", "ar-TN"];
|
|
11
|
+
|
|
12
|
+
export { LOCALE_CODES };
|
|
13
|
+
//# sourceMappingURL=chunk-IG7CVIA2.js.map
|
|
14
|
+
//# sourceMappingURL=chunk-IG7CVIA2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/locales/ar.ts"],"names":[],"mappings":";;;AAUA,cAAA,CAAe;AAAA,EACb,WAAA,EAAa;AAAA,IACX,CAAC,MAAQ,IAAM;AAAA;AAAA;AAEnB,CAAC,CAAA;AAGM,IAAM,eAAe,CAAC,IAAA,EAAM,SAAS,OAAA,EAAS,OAAA,EAAS,SAAS,OAAO","file":"chunk-IG7CVIA2.js","sourcesContent":["/**\n * Arabic (ar) locale plugin.\n *\n * Registers Arabic-Indic digit block (U+0660âU+0669).\n *\n * Usage:\n * import 'raqam/locales/ar'\n */\nimport { registerLocale } from \"../core/normalizer.js\";\n\nregisterLocale({\n digitBlocks: [\n [0x0660, 0x0669], // Arabic-Indic Ų âŲŠ\n ],\n});\n\n/** BCP 47 locale tags that this plugin covers. */\nexport const LOCALE_CODES = [\"ar\", \"ar-EG\", \"ar-SA\", \"ar-MA\", \"ar-DZ\", \"ar-TN\"] as const;\n"]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/core/normalizer.ts
|
|
2
|
+
var BUILTIN_DIGIT_BLOCKS = [
|
|
3
|
+
[1632, 1641],
|
|
4
|
+
// Arabic-Indic (arab)
|
|
5
|
+
[1776, 1785],
|
|
6
|
+
// Extended Arabic-Indic / Persian (arabext)
|
|
7
|
+
[2406, 2415],
|
|
8
|
+
// Devanagari / Hindi (deva)
|
|
9
|
+
[2534, 2543],
|
|
10
|
+
// Bengali (beng)
|
|
11
|
+
[3664, 3673]
|
|
12
|
+
// Thai (thai)
|
|
13
|
+
];
|
|
14
|
+
var registeredBlocks = [...BUILTIN_DIGIT_BLOCKS];
|
|
15
|
+
function registerLocale(config) {
|
|
16
|
+
if (!config.digitBlocks) return;
|
|
17
|
+
for (const block of config.digitBlocks) {
|
|
18
|
+
const already = registeredBlocks.some(([s]) => s === block[0]);
|
|
19
|
+
if (!already) registeredBlocks.push(block);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { registerLocale };
|
|
24
|
+
//# sourceMappingURL=chunk-NBAZIJ5W.js.map
|
|
25
|
+
//# sourceMappingURL=chunk-NBAZIJ5W.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/normalizer.ts"],"names":[],"mappings":";AAMA,IAAM,oBAAA,GAAqC;AAAA,EACzC,CAAC,MAAQ,IAAM,CAAA;AAAA;AAAA,EACf,CAAC,MAAQ,IAAM,CAAA;AAAA;AAAA,EACf,CAAC,MAAQ,IAAM,CAAA;AAAA;AAAA,EACf,CAAC,MAAQ,IAAM,CAAA;AAAA;AAAA,EACf,CAAC,MAAQ,IAAM;AAAA;AACjB,CAAA;AAGA,IAAM,gBAAA,GAAiC,CAAC,GAAG,oBAAoB,CAAA;AAaxD,SAAS,eAAe,MAAA,EAA4B;AACzD,EAAA,IAAI,CAAC,OAAO,WAAA,EAAa;AACzB,EAAA,KAAA,MAAW,KAAA,IAAS,OAAO,WAAA,EAAa;AACtC,IAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,IAAA,CAAK,CAAC,CAAC,CAAC,CAAA,KAAM,CAAA,KAAM,KAAA,CAAM,CAAC,CAAC,CAAA;AAC7D,IAAA,IAAI,CAAC,OAAA,EAAS,gBAAA,CAAiB,IAAA,CAAK,KAAK,CAAA;AAAA,EAC3C;AACF","file":"chunk-NBAZIJ5W.js","sourcesContent":["import type { DigitBlock } from \"./types.js\";\n\n// ââ Built-in digit blocks ââââââââââââââââââââââââââââââââââââââââââââââââââââ\n// These cover the digit systems required by the spec.\n// Additional blocks can be registered via registerLocale().\n\nconst BUILTIN_DIGIT_BLOCKS: DigitBlock[] = [\n [0x0660, 0x0669], // Arabic-Indic (arab)\n [0x06f0, 0x06f9], // Extended Arabic-Indic / Persian (arabext)\n [0x0966, 0x096f], // Devanagari / Hindi (deva)\n [0x09e6, 0x09ef], // Bengali (beng)\n [0x0e50, 0x0e59], // Thai (thai)\n];\n\n// Mutable registry â locale plugins can add blocks here\nconst registeredBlocks: DigitBlock[] = [...BUILTIN_DIGIT_BLOCKS];\n\n// ââ Public API ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ\n\nexport interface LocaleConfig {\n /** Extra digit block ranges to register */\n digitBlocks?: DigitBlock[];\n}\n\n/**\n * Register additional digit blocks (called by locale plugins as a side effect).\n * Duplicate ranges are silently ignored.\n */\nexport function registerLocale(config: LocaleConfig): void {\n if (!config.digitBlocks) return;\n for (const block of config.digitBlocks) {\n const already = registeredBlocks.some(([s]) => s === block[0]);\n if (!already) registeredBlocks.push(block);\n }\n}\n\n/**\n * Normalise any Unicode decimal digit in `input` to its ASCII equivalent (0â9).\n * Non-digit characters pass through unchanged.\n */\nexport function normalizeDigits(input: string): string {\n // Fast path: if there are no non-ASCII chars, return as-is\n if (!/[^\\u0020-\\u007e]/.test(input)) return input;\n\n return input.replace(/\\p{Nd}/gu, (ch) => {\n const code = ch.codePointAt(0)!;\n for (const [start, end] of registeredBlocks) {\n if (code >= start && code <= end) {\n return String(code - start);\n }\n }\n // Fallback: let JS try to parse it as a decimal digit\n const digit = Number.parseInt(ch, 10);\n return Number.isNaN(digit) ? ch : String(digit);\n });\n}\n\n/**\n * Returns true if the character is a non-Latin Unicode decimal digit\n * (i.e. would need normalization).\n */\nexport function isNonLatinDigit(ch: string): boolean {\n const code = ch.codePointAt(0);\n if (code === undefined) return false;\n if (code >= 0x30 && code <= 0x39) return false; // ASCII 0-9\n for (const [start, end] of registeredBlocks) {\n if (code >= start && code <= end) return true;\n }\n return false;\n}\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { registerLocale } from './chunk-NBAZIJ5W.js';
|
|
2
|
+
|
|
3
|
+
// src/locales/hi.ts
|
|
4
|
+
registerLocale({
|
|
5
|
+
digitBlocks: [
|
|
6
|
+
[2406, 2415]
|
|
7
|
+
// Devanagari āĨĻâāĨ¯
|
|
8
|
+
]
|
|
9
|
+
});
|
|
10
|
+
var LOCALE_CODES = ["hi", "hi-IN", "mr", "mr-IN", "ne", "ne-NP"];
|
|
11
|
+
|
|
12
|
+
export { LOCALE_CODES };
|
|
13
|
+
//# sourceMappingURL=chunk-NSFX2EAT.js.map
|
|
14
|
+
//# sourceMappingURL=chunk-NSFX2EAT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/locales/hi.ts"],"names":[],"mappings":";;;AAUA,cAAA,CAAe;AAAA,EACb,WAAA,EAAa;AAAA,IACX,CAAC,MAAQ,IAAM;AAAA;AAAA;AAEnB,CAAC,CAAA;AAGM,IAAM,eAAe,CAAC,IAAA,EAAM,SAAS,IAAA,EAAM,OAAA,EAAS,MAAM,OAAO","file":"chunk-NSFX2EAT.js","sourcesContent":["/**\n * Hindi / Devanagari (hi-IN) locale plugin.\n *\n * Registers Devanagari digit block (U+0966âU+096F).\n *\n * Usage:\n * import 'raqam/locales/hi'\n */\nimport { registerLocale } from \"../core/normalizer.js\";\n\nregisterLocale({\n digitBlocks: [\n [0x0966, 0x096f], // Devanagari āĨĻâāĨ¯\n ],\n});\n\n/** BCP 47 locale tags that this plugin covers. */\nexport const LOCALE_CODES = [\"hi\", \"hi-IN\", \"mr\", \"mr-IN\", \"ne\", \"ne-NP\"] as const;\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { registerLocale } from './chunk-NBAZIJ5W.js';
|
|
2
|
+
|
|
3
|
+
// src/locales/bn.ts
|
|
4
|
+
registerLocale({
|
|
5
|
+
digitBlocks: [
|
|
6
|
+
[2534, 2543]
|
|
7
|
+
// Bengali ā§Ļâ⧝
|
|
8
|
+
]
|
|
9
|
+
});
|
|
10
|
+
var LOCALE_CODES = ["bn", "bn-BD", "bn-IN"];
|
|
11
|
+
|
|
12
|
+
export { LOCALE_CODES };
|
|
13
|
+
//# sourceMappingURL=chunk-NTROGAES.js.map
|
|
14
|
+
//# sourceMappingURL=chunk-NTROGAES.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/locales/bn.ts"],"names":[],"mappings":";;;AAUA,cAAA,CAAe;AAAA,EACb,WAAA,EAAa;AAAA,IACX,CAAC,MAAQ,IAAM;AAAA;AAAA;AAEnB,CAAC,CAAA;AAGM,IAAM,YAAA,GAAe,CAAC,IAAA,EAAM,OAAA,EAAS,OAAO","file":"chunk-NTROGAES.js","sourcesContent":["/**\n * Bengali (bn-BD / bn-IN) locale plugin.\n *\n * Registers Bengali digit block (U+09E6âU+09EF).\n *\n * Usage:\n * import 'raqam/locales/bn'\n */\nimport { registerLocale } from \"../core/normalizer.js\";\n\nregisterLocale({\n digitBlocks: [\n [0x09e6, 0x09ef], // Bengali ā§Ļâ⧝\n ],\n});\n\n/** BCP 47 locale tags that this plugin covers. */\nexport const LOCALE_CODES = [\"bn\", \"bn-BD\", \"bn-IN\"] as const;\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { registerLocale } from './chunk-NBAZIJ5W.js';
|
|
2
|
+
|
|
3
|
+
// src/locales/th.ts
|
|
4
|
+
registerLocale({
|
|
5
|
+
digitBlocks: [
|
|
6
|
+
[3664, 3673]
|
|
7
|
+
// Thai āšâāš
|
|
8
|
+
]
|
|
9
|
+
});
|
|
10
|
+
var LOCALE_CODES = ["th", "th-TH"];
|
|
11
|
+
|
|
12
|
+
export { LOCALE_CODES };
|
|
13
|
+
//# sourceMappingURL=chunk-VOBTYII2.js.map
|
|
14
|
+
//# sourceMappingURL=chunk-VOBTYII2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/locales/th.ts"],"names":[],"mappings":";;;AAUA,cAAA,CAAe;AAAA,EACb,WAAA,EAAa;AAAA,IACX,CAAC,MAAQ,IAAM;AAAA;AAAA;AAEnB,CAAC,CAAA;AAGM,IAAM,YAAA,GAAe,CAAC,IAAA,EAAM,OAAO","file":"chunk-VOBTYII2.js","sourcesContent":["/**\n * Thai (th-TH) locale plugin.\n *\n * Registers Thai digit block (U+0E50âU+0E59).\n *\n * Usage:\n * import 'raqam/locales/th'\n */\nimport { registerLocale } from \"../core/normalizer.js\";\n\nregisterLocale({\n digitBlocks: [\n [0x0e50, 0x0e59], // Thai āšâāš\n ],\n});\n\n/** BCP 47 locale tags that this plugin covers. */\nexport const LOCALE_CODES = [\"th\", \"th-TH\"] as const;\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { registerLocale } from './chunk-NBAZIJ5W.js';
|
|
2
|
+
|
|
3
|
+
// src/locales/fa.ts
|
|
4
|
+
registerLocale({
|
|
5
|
+
digitBlocks: [
|
|
6
|
+
[1776, 1785],
|
|
7
|
+
// Extended Arabic-Indic (Persian) Û°âÛš
|
|
8
|
+
[1632, 1641]
|
|
9
|
+
// Arabic-Indic Ų âŲŠ
|
|
10
|
+
]
|
|
11
|
+
});
|
|
12
|
+
var LOCALE_CODES = ["fa", "fa-IR", "fa-AF"];
|
|
13
|
+
|
|
14
|
+
export { LOCALE_CODES };
|
|
15
|
+
//# sourceMappingURL=chunk-WTS5RY7S.js.map
|
|
16
|
+
//# sourceMappingURL=chunk-WTS5RY7S.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/locales/fa.ts"],"names":[],"mappings":";;;AAeA,cAAA,CAAe;AAAA,EACb,WAAA,EAAa;AAAA,IACX,CAAC,MAAQ,IAAM,CAAA;AAAA;AAAA,IACf,CAAC,MAAQ,IAAM;AAAA;AAAA;AAEnB,CAAC,CAAA;AAGM,IAAM,YAAA,GAAe,CAAC,IAAA,EAAM,OAAA,EAAS,OAAO","file":"chunk-WTS5RY7S.js","sourcesContent":["/**\n * Persian (fa-IR) locale plugin.\n *\n * Registers Extended Arabic-Indic digit block (U+06F0âU+06F9).\n * The ISIRI 9147 keyboard produces these codepoints when number keys are\n * pressed on a Persian layout. This side-effect import enables their\n * normalization to ASCII 0â9 before parsing.\n *\n * Also registers the Arabic-Indic block for mixed-input scenarios.\n *\n * Usage:\n * import 'raqam/locales/fa'\n */\nimport { registerLocale } from \"../core/normalizer.js\";\n\nregisterLocale({\n digitBlocks: [\n [0x06f0, 0x06f9], // Extended Arabic-Indic (Persian) Û°âÛš\n [0x0660, 0x0669], // Arabic-Indic Ų âŲŠ\n ],\n});\n\n/** BCP 47 locale tags that this plugin covers. */\nexport const LOCALE_CODES = [\"fa\", \"fa-IR\", \"fa-AF\"] as const;\n"]}
|
package/dist/core.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var L={currency:e=>({style:"currency",currency:e}),accounting:e=>({style:"currency",currency:e,currencySign:"accounting"}),percent:{style:"percent"},compact:{notation:"compact"},compactLong:{notation:"compact",compactDisplay:"long"},scientific:{notation:"scientific"},engineering:{notation:"engineering"},integer:{maximumFractionDigits:0},financial:{minimumFractionDigits:2,maximumFractionDigits:2},unit:e=>({style:"unit",unit:e})};var O=[[1632,1641],[1776,1785],[2406,2415],[2534,2543],[3664,3673]],p=[...O];function D(e){if(e.digitBlocks)for(let t of e.digitBlocks)p.some(([i])=>i===t[0])||p.push(t);}function g(e){return /[^\u0020-\u007e]/.test(e)?e.replace(/\p{Nd}/gu,t=>{let r=t.codePointAt(0);for(let[o,s]of p)if(r>=o&&r<=s)return String(r-o);let i=Number.parseInt(t,10);return Number.isNaN(i)?t:String(i)}):e}var x=new Map;function F(e,t){let r=`${e??""}::${JSON.stringify(t??{})}`,i=x.get(r);return i||(i=new Intl.NumberFormat(e,t),x.set(r,i)),i}function P(e,t){let r=F(e,t),i=r.formatToParts(12345.6),o=".",s=",",f="-",c="0";for(let m of i)m.type==="decimal"&&(o=m.value),m.type==="group"&&(s=m.value),m.type==="minusSign"&&(f=m.value);let u=r.formatToParts(0);for(let m of u)if(m.type==="integer"){c=m.value;break}let a=/^(ar|he|fa|ur|syc|nqo|ug|yi)/i,n=r.resolvedOptions().locale,l=a.test(n);return {decimalSeparator:o,groupingSeparator:s,minusSign:f,zero:c,isRTL:l}}function d(e){let t={...e.formatOptions};e.minimumFractionDigits!==void 0&&(t.minimumFractionDigits=e.minimumFractionDigits),e.maximumFractionDigits!==void 0&&(t.maximumFractionDigits=e.maximumFractionDigits),e.fixedDecimalScale&&e.maximumFractionDigits!==void 0&&(t.minimumFractionDigits=e.maximumFractionDigits,t.maximumFractionDigits=e.maximumFractionDigits);let r=F(e.locale,t),i=null;function o(){return i||(i=P(e.locale,t)),i}function s(u){let a=r.formatToParts(u);if(!e.prefix&&!e.suffix)return a;let n=[];return e.prefix&&n.push({type:"literal",value:e.prefix}),n.push(...a),e.suffix&&n.push({type:"literal",value:e.suffix}),n}function f(u){if(!Number.isFinite(u))return "";let a=r.format(u);return (e.prefix??"")+a+(e.suffix??"")}function c(u){let a=s(u);return {formatted:a.map(l=>l.value).join(""),parts:a}}return {format:f,formatToParts:s,getLocaleInfo:o,formatResult:c}}function I(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function b(e,t,r,i){let o=I(t.decimalSeparator),s=I(t.minusSign);return !!(r&&(e==="-"||e===t.minusSign)||i&&new RegExp(`^${s}?\\d+${o}$`).test(e)||i&&new RegExp(`^${s}?\\d+${o}\\d*0+$`).test(e))}function S(e={}){let t=e.allowNegative??true,r=e.allowDecimal??true,i=d({locale:e.locale,formatOptions:e.formatOptions,prefix:e.prefix,suffix:e.suffix});function o(){return i.getLocaleInfo()}function s(u){let a=o(),n=g(u),l=n.match(/^\((.+)\)$/);return l&&(n=`-${l[1]}`),e.prefix&&n.startsWith(e.prefix)&&(n=n.slice(e.prefix.length)),e.suffix&&n.endsWith(e.suffix)&&(n=n.slice(0,-e.suffix.length)),a.groupingSeparator&&(n=n.split(a.groupingSeparator).join("")),a.decimalSeparator!=="."&&(n=n.split(a.decimalSeparator).join(".")),a.minusSign!=="-"&&(n=n.split(a.minusSign).join("-")),n=n.replace(/[^\d.\-]/g,"").trim(),n}function f(u){if(!u||u.trim()==="")return {value:null,isValid:false,isIntermediate:false};let a=o();if(b(u,a,t,r))return {value:null,isValid:false,isIntermediate:true};let n=s(u);if(n==="")return {value:null,isValid:false,isIntermediate:false};if(n==="-")return {value:null,isValid:false,isIntermediate:t};if(!/^-?\d+\.?\d*$/.test(n))return {value:null,isValid:false,isIntermediate:false};if(!t&&n.startsWith("-"))return {value:null,isValid:false,isIntermediate:false};if(!r&&n.includes("."))return {value:null,isValid:false,isIntermediate:false};let l=Number.parseFloat(n);return Number.isFinite(l)?{value:l,isValid:true,isIntermediate:false}:{value:null,isValid:false,isIntermediate:false}}function c(u){let a=o();return b(u,a,t,r)}return {parse:f,isIntermediate:c,getLocaleInfo:o}}function y(e,t){return e>="0"&&e<="9"||e===t.decimalSeparator||e===t.minusSign||e==="-"}function v(e,t,r){let i=g(e),o=0;for(let s=0;s<t&&s<i.length;s++)y(i[s],r)&&o++;return o}function N(e,t){let r=e.length,i=new Array(r+1).fill(true);for(let o=0;o<r;o++)e[o]===t.groupingSeparator&&(i[o+1]=false);return i[0]=true,i[r]=true,i}function B(e,t,r,i,o){let s=g(e),f=v(s,t,i);o==="deleteContentBackward"&&t>0&&e[t-1]===i.groupingSeparator&&(f=Math.max(0,f-1));let c=N(r,i),u=g(r),a=0,n=0;for(let l=0;l<u.length;l++){if(a===f){n=l;break}y(u[l],i)&&a++,n=l+1;}return f>0&&a<f&&(n=r.length),n=R(n,c),n}function R(e,t){if(t[e])return e;for(let r=e+1;r<t.length;r++)if(t[r])return r;for(let r=e-1;r>=0;r--)if(t[r])return r;return 0}exports.computeNewCursorPosition=B;exports.createFormatter=d;exports.createParser=S;exports.getCaretBoundary=N;exports.normalizeDigits=g;exports.presets=L;exports.registerLocale=D;//# sourceMappingURL=core.cjs.map
|
|
2
|
+
//# sourceMappingURL=core.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/presets.ts","../src/core/normalizer.ts","../src/core/formatter.ts","../src/core/parser.ts","../src/core/cursor.ts"],"names":["presets","code","unit","BUILTIN_DIGIT_BLOCKS","registeredBlocks","registerLocale","config","block","s","normalizeDigits","input","ch","start","end","digit","formatterCache","getFormatter","locale","options","key","fmt","extractLocaleInfo","parts","decimalSeparator","groupingSeparator","minusSign","zero","part","zeroParts","rtlLocales","resolvedLocale","isRTL","createFormatter","opts","intlOptions","intlFmt","cachedLocaleInfo","getLocaleInfo","formatToParts","value","result","format","formatted","formatResult","p","escapeRegex","checkIntermediate","normalized","info","allowNegative","allowDecimal","dec","minus","createParser","stripAffordances","raw","accountingMatch","parse","stripped","n","isIntermediate","isAccepted","countAcceptedBefore","str","cursor","normalised","count","i","getCaretBoundary","formattedValue","len","boundary","computeNewCursorPosition","oldInput","oldCursor","newFormatted","inputType","acceptedCount","normNew","pos","snapToBoundary"],"mappings":"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","file":"core.cjs","sourcesContent":["/**\n * Format presets â named Intl.NumberFormatOptions configurations for common\n * number input patterns. Use these as the `formatOptions` prop value.\n *\n * @example\n * import { presets } from 'raqam'\n * <NumberField.Root formatOptions={presets.currency('USD')} />\n * <NumberField.Root formatOptions={presets.percent} />\n * <NumberField.Root formatOptions={presets.compact} />\n */\n\nexport const presets = {\n /** Currency with standard sign display. Shorthand for `{ style:'currency', currency:code }`. */\n currency: (code: string): Intl.NumberFormatOptions => ({\n style: \"currency\",\n currency: code,\n }),\n\n /**\n * Accounting currency â negatives shown as `(1,234.56)` instead of `-$1,234.56`.\n * Requires the accounting format parser fix (built-in to raqam).\n */\n accounting: (code: string): Intl.NumberFormatOptions => ({\n style: \"currency\",\n currency: code,\n currencySign: \"accounting\",\n }),\n\n /** Percentage â formats 0.42 as \"42%\" */\n percent: { style: \"percent\" } as Intl.NumberFormatOptions,\n\n /** Compact short â \"1.2K\", \"3.4M\" */\n compact: { notation: \"compact\" } as Intl.NumberFormatOptions,\n\n /** Compact long â \"1.2 thousand\", \"3.4 million\" */\n compactLong: {\n notation: \"compact\",\n compactDisplay: \"long\",\n } as Intl.NumberFormatOptions,\n\n /** Scientific notation â \"1.234E3\" */\n scientific: { notation: \"scientific\" } as Intl.NumberFormatOptions,\n\n /** Engineering notation â exponents always multiples of 3 */\n engineering: { notation: \"engineering\" } as Intl.NumberFormatOptions,\n\n /** Integer â no decimal places */\n integer: { maximumFractionDigits: 0 } as Intl.NumberFormatOptions,\n\n /**\n * Financial â always exactly 2 decimal places (fixed scale).\n * Combine with `fixedDecimalScale` prop for strict display.\n */\n financial: {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n } as Intl.NumberFormatOptions,\n\n /**\n * Unit â shorthand for `{ style:'unit', unit:unitCode }`.\n * @example presets.unit('kilometer-per-hour') â { style:'unit', unit:'kilometer-per-hour' }\n */\n unit: (unit: string): Intl.NumberFormatOptions => ({\n style: \"unit\",\n unit,\n }),\n} as const;\n","import type { DigitBlock } from \"./types.js\";\n\n// ââ Built-in digit blocks ââââââââââââââââââââââââââââââââââââââââââââââââââââ\n// These cover the digit systems required by the spec.\n// Additional blocks can be registered via registerLocale().\n\nconst BUILTIN_DIGIT_BLOCKS: DigitBlock[] = [\n [0x0660, 0x0669], // Arabic-Indic (arab)\n [0x06f0, 0x06f9], // Extended Arabic-Indic / Persian (arabext)\n [0x0966, 0x096f], // Devanagari / Hindi (deva)\n [0x09e6, 0x09ef], // Bengali (beng)\n [0x0e50, 0x0e59], // Thai (thai)\n];\n\n// Mutable registry â locale plugins can add blocks here\nconst registeredBlocks: DigitBlock[] = [...BUILTIN_DIGIT_BLOCKS];\n\n// ââ Public API ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ\n\nexport interface LocaleConfig {\n /** Extra digit block ranges to register */\n digitBlocks?: DigitBlock[];\n}\n\n/**\n * Register additional digit blocks (called by locale plugins as a side effect).\n * Duplicate ranges are silently ignored.\n */\nexport function registerLocale(config: LocaleConfig): void {\n if (!config.digitBlocks) return;\n for (const block of config.digitBlocks) {\n const already = registeredBlocks.some(([s]) => s === block[0]);\n if (!already) registeredBlocks.push(block);\n }\n}\n\n/**\n * Normalise any Unicode decimal digit in `input` to its ASCII equivalent (0â9).\n * Non-digit characters pass through unchanged.\n */\nexport function normalizeDigits(input: string): string {\n // Fast path: if there are no non-ASCII chars, return as-is\n if (!/[^\\u0020-\\u007e]/.test(input)) return input;\n\n return input.replace(/\\p{Nd}/gu, (ch) => {\n const code = ch.codePointAt(0)!;\n for (const [start, end] of registeredBlocks) {\n if (code >= start && code <= end) {\n return String(code - start);\n }\n }\n // Fallback: let JS try to parse it as a decimal digit\n const digit = Number.parseInt(ch, 10);\n return Number.isNaN(digit) ? ch : String(digit);\n });\n}\n\n/**\n * Returns true if the character is a non-Latin Unicode decimal digit\n * (i.e. would need normalization).\n */\nexport function isNonLatinDigit(ch: string): boolean {\n const code = ch.codePointAt(0);\n if (code === undefined) return false;\n if (code >= 0x30 && code <= 0x39) return false; // ASCII 0-9\n for (const [start, end] of registeredBlocks) {\n if (code >= start && code <= end) return true;\n }\n return false;\n}\n","import type { FormatResult, LocaleInfo } from \"./types.js\";\n\n// ââ Internal ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ\n\n/** Probe value that will surface decimal AND grouping parts */\nconst PROBE_VALUE = 12345.6;\n\n/** Cache key = locale + JSON.stringify(options) */\nconst formatterCache = new Map<string, Intl.NumberFormat>();\n\nfunction getFormatter(\n locale: string | undefined,\n options: Intl.NumberFormatOptions | undefined\n): Intl.NumberFormat {\n const key = `${locale ?? \"\"}::${JSON.stringify(options ?? {})}`;\n let fmt = formatterCache.get(key);\n if (!fmt) {\n fmt = new Intl.NumberFormat(locale, options);\n formatterCache.set(key, fmt);\n }\n return fmt;\n}\n\n/** Extract locale meta from formatToParts â never hardcoded. */\nfunction extractLocaleInfo(\n locale: string | undefined,\n options: Intl.NumberFormatOptions | undefined\n): LocaleInfo {\n const fmt = getFormatter(locale, options);\n // Use a simple decimal number â we only need the separators\n const parts = fmt.formatToParts(PROBE_VALUE);\n\n let decimalSeparator = \".\";\n let groupingSeparator = \",\";\n let minusSign = \"-\";\n let zero = \"0\";\n\n for (const part of parts) {\n if (part.type === \"decimal\") decimalSeparator = part.value;\n if (part.type === \"group\") groupingSeparator = part.value;\n if (part.type === \"minusSign\") minusSign = part.value;\n }\n\n // Detect locale zero digit\n const zeroParts = fmt.formatToParts(0);\n for (const part of zeroParts) {\n if (part.type === \"integer\") {\n zero = part.value;\n break;\n }\n }\n\n // RTL locales: Arabic / Hebrew / Persian / Urdu / Syriac etc.\n const rtlLocales = /^(ar|he|fa|ur|syc|nqo|ug|yi)/i;\n const resolvedLocale = fmt.resolvedOptions().locale;\n const isRTL = rtlLocales.test(resolvedLocale);\n\n return { decimalSeparator, groupingSeparator, minusSign, zero, isRTL };\n}\n\n// ââ Factory âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ\n\nexport interface FormatterOptions {\n locale?: string;\n formatOptions?: Intl.NumberFormatOptions;\n prefix?: string;\n suffix?: string;\n minimumFractionDigits?: number;\n maximumFractionDigits?: number;\n fixedDecimalScale?: boolean;\n}\n\nexport interface Formatter {\n format(value: number): string;\n formatToParts(value: number): Intl.NumberFormatPart[];\n getLocaleInfo(): LocaleInfo;\n formatResult(value: number): FormatResult;\n}\n\n/**\n * Create a formatter instance. Intl.NumberFormat is cached â safe to call\n * on every render.\n */\nexport function createFormatter(opts: FormatterOptions): Formatter {\n // Merge fraction digit overrides into formatOptions\n const intlOptions: Intl.NumberFormatOptions = { ...opts.formatOptions };\n\n if (opts.minimumFractionDigits !== undefined) {\n intlOptions.minimumFractionDigits = opts.minimumFractionDigits;\n }\n if (opts.maximumFractionDigits !== undefined) {\n intlOptions.maximumFractionDigits = opts.maximumFractionDigits;\n }\n if (opts.fixedDecimalScale && opts.maximumFractionDigits !== undefined) {\n intlOptions.minimumFractionDigits = opts.maximumFractionDigits;\n intlOptions.maximumFractionDigits = opts.maximumFractionDigits;\n }\n\n const intlFmt = getFormatter(opts.locale, intlOptions);\n // Lazy â computed once on first call\n let cachedLocaleInfo: LocaleInfo | null = null;\n\n function getLocaleInfo(): LocaleInfo {\n if (!cachedLocaleInfo) {\n cachedLocaleInfo = extractLocaleInfo(opts.locale, intlOptions);\n }\n return cachedLocaleInfo;\n }\n\n function formatToParts(value: number): Intl.NumberFormatPart[] {\n const parts = intlFmt.formatToParts(value);\n if (!opts.prefix && !opts.suffix) return parts;\n\n const result: Intl.NumberFormatPart[] = [];\n if (opts.prefix) result.push({ type: \"literal\", value: opts.prefix });\n result.push(...parts);\n if (opts.suffix) result.push({ type: \"literal\", value: opts.suffix });\n return result;\n }\n\n function format(value: number): string {\n if (!Number.isFinite(value)) return \"\";\n const formatted = intlFmt.format(value);\n return (opts.prefix ?? \"\") + formatted + (opts.suffix ?? \"\");\n }\n\n function formatResult(value: number): FormatResult {\n const parts = formatToParts(value);\n const formatted = parts.map((p) => p.value).join(\"\");\n return { formatted, parts };\n }\n\n return { format, formatToParts, getLocaleInfo, formatResult };\n}\n","import { createFormatter } from \"./formatter.js\";\nimport { normalizeDigits } from \"./normalizer.js\";\nimport type { LocaleInfo, ParseResult } from \"./types.js\";\n\n// ââ Helpers âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ\n\n/**\n * Escape a string so it can be used literally inside a RegExp.\n */\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Returns true if `s` represents a valid-but-incomplete number:\n * \"-\" lone minus\n * \"1.\" trailing decimal separator (any locale)\n * \"1.0\" trailing zero after decimal\n * \"1.00\" etc.\n *\n * These should NOT be reformatted while the user is still typing.\n */\nfunction checkIntermediate(\n normalized: string,\n info: LocaleInfo,\n allowNegative: boolean,\n allowDecimal: boolean\n): boolean {\n const dec = escapeRegex(info.decimalSeparator);\n const minus = escapeRegex(info.minusSign);\n\n // Lone minus sign\n if (allowNegative && (normalized === \"-\" || normalized === info.minusSign)) {\n return true;\n }\n // Trailing decimal separator\n if (allowDecimal && new RegExp(`^${minus}?\\\\d+${dec}$`).test(normalized)) {\n return true;\n }\n // Trailing zeros after decimal (e.g. \"1.0\", \"1.00\", \"-1.0\")\n if (allowDecimal && new RegExp(`^${minus}?\\\\d+${dec}\\\\d*0+$`).test(normalized)) {\n return true;\n }\n return false;\n}\n\n// ââ Factory âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ\n\nexport interface ParserOptions {\n locale?: string;\n formatOptions?: Intl.NumberFormatOptions;\n allowNegative?: boolean;\n allowDecimal?: boolean;\n prefix?: string;\n suffix?: string;\n}\n\nexport interface Parser {\n parse(input: string): ParseResult;\n isIntermediate(input: string): boolean;\n getLocaleInfo(): LocaleInfo;\n}\n\n/**\n * Create a locale-aware parser. Separator characters are extracted from\n * Intl.NumberFormat â never hardcoded.\n */\nexport function createParser(opts: ParserOptions = {}): Parser {\n const allowNegative = opts.allowNegative ?? true;\n const allowDecimal = opts.allowDecimal ?? true;\n\n // Re-use the formatter to get locale info\n const fmt = createFormatter({\n locale: opts.locale,\n formatOptions: opts.formatOptions,\n prefix: opts.prefix,\n suffix: opts.suffix,\n });\n\n function getLocaleInfo(): LocaleInfo {\n return fmt.getLocaleInfo();\n }\n\n function stripAffordances(raw: string): string {\n const info = getLocaleInfo();\n\n // 1. Normalise non-Latin digits to ASCII\n let s = normalizeDigits(raw);\n\n // 2. Accounting format: \"(1,234.56)\" or \"($1,234.56)\" â negative\n // Intl.NumberFormat with currencySign:\"accounting\" wraps negatives in parens\n const accountingMatch = s.match(/^\\((.+)\\)$/);\n if (accountingMatch) {\n s = `-${accountingMatch[1]}`;\n }\n\n // 3. Strip prefix / suffix\n if (opts.prefix && s.startsWith(opts.prefix)) {\n s = s.slice(opts.prefix.length);\n }\n if (opts.suffix && s.endsWith(opts.suffix)) {\n s = s.slice(0, -opts.suffix.length);\n }\n\n // 4. Strip grouping separators (escape special chars)\n if (info.groupingSeparator) {\n s = s.split(info.groupingSeparator).join(\"\");\n }\n\n // 5. Replace locale decimal separator with ASCII \".\"\n if (info.decimalSeparator !== \".\") {\n s = s.split(info.decimalSeparator).join(\".\");\n }\n\n // 6. Replace locale minus sign with ASCII \"-\"\n if (info.minusSign !== \"-\") {\n s = s.split(info.minusSign).join(\"-\");\n }\n\n // 7. Strip currency symbol, percent sign, spaces that Intl might prepend/append\n // Strip any remaining non-numeric chars except digits, \".\", \"-\"\n // (handles currency prefixes/suffixes from Intl)\n s = s.replace(/[^\\d.\\-]/g, \"\").trim();\n\n return s;\n }\n\n function parse(input: string): ParseResult {\n if (!input || input.trim() === \"\") {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n const info = getLocaleInfo();\n\n // Check for intermediate state before stripping\n if (checkIntermediate(input, info, allowNegative, allowDecimal)) {\n return { value: null, isValid: false, isIntermediate: true };\n }\n\n const stripped = stripAffordances(input);\n\n if (stripped === \"\") {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n if (stripped === \"-\") {\n // Lone minus after stripping â only intermediate if negatives are allowed\n return { value: null, isValid: false, isIntermediate: allowNegative };\n }\n\n // Reject if not a valid numeric string\n if (!/^-?\\d+\\.?\\d*$/.test(stripped)) {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n if (!allowNegative && stripped.startsWith(\"-\")) {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n if (!allowDecimal && stripped.includes(\".\")) {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n const n = Number.parseFloat(stripped);\n if (!Number.isFinite(n)) {\n return { value: null, isValid: false, isIntermediate: false };\n }\n\n return { value: n, isValid: true, isIntermediate: false };\n }\n\n function isIntermediate(input: string): boolean {\n const info = getLocaleInfo();\n return checkIntermediate(input, info, allowNegative, allowDecimal);\n }\n\n return { parse, isIntermediate, getLocaleInfo };\n}\n","import { normalizeDigits } from \"./normalizer.js\";\nimport type { CaretBoundary, LocaleInfo } from \"./types.js\";\n\n// ââ Accepted-character helpers ââââââââââââââââââââââââââââââââââââââââââââââââ\n\n/**\n * Returns true if `ch` is an \"accepted\" character â one that the user typed\n * intentionally and that contributes to the numeric value:\n * - ASCII digit 0-9\n * - The locale decimal separator\n * - The locale minus sign\n */\nfunction isAccepted(ch: string, info: LocaleInfo): boolean {\n if (ch >= \"0\" && ch <= \"9\") return true;\n if (ch === info.decimalSeparator) return true;\n if (ch === info.minusSign || ch === \"-\") return true;\n return false;\n}\n\n/**\n * Count how many \"accepted\" characters appear before position `cursor`\n * in `str` (after normalising non-Latin digits to ASCII).\n */\nfunction countAcceptedBefore(str: string, cursor: number, info: LocaleInfo): number {\n const normalised = normalizeDigits(str);\n let count = 0;\n for (let i = 0; i < cursor && i < normalised.length; i++) {\n if (isAccepted(normalised[i]!, info)) count++;\n }\n return count;\n}\n\n// ââ Caret boundary ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ\n\n/**\n * Build a boolean array of length `formattedValue.length + 1`.\n * `true` â cursor may rest at this position.\n * `false` â cursor must snap away (sits inside a formatting-only character\n * such as a grouping separator or a currency prefix).\n *\n * Rules:\n * - Start and end positions are always valid.\n * - A position immediately AFTER a grouping separator is invalid (the\n * cursor would look like it's between two digits but moving left would\n * skip the comma).\n * - A position immediately BEFORE a grouping separator is valid.\n */\nexport function getCaretBoundary(formattedValue: string, info: LocaleInfo): CaretBoundary {\n const len = formattedValue.length;\n const boundary: CaretBoundary = new Array(len + 1).fill(true) as boolean[];\n\n for (let i = 0; i < len; i++) {\n const ch = formattedValue[i]!;\n if (ch === info.groupingSeparator) {\n // Position immediately after the grouping separator is invalid\n boundary[i + 1] = false;\n }\n }\n\n // First and last are always valid\n boundary[0] = true;\n boundary[len] = true;\n\n return boundary;\n}\n\n// ââ Cursor computation ââââââââââââââââââââââââââââââââââââââââââââââââââââââââ\n\n/**\n * Compute the new cursor position in `newFormatted` that corresponds\n * semantically to `oldCursor` in `oldInput`.\n *\n * Algorithm (3 stages from the spec):\n *\n * 1. Count accepted characters before `oldCursor` in `oldInput`.\n * 2. Adjust for backspace-over-separator edge case.\n * 3. Walk `newFormatted` to find the position where the same count of\n * accepted chars precede it; snap to the nearest valid boundary.\n *\n * @param oldInput The raw string the user just typed into (pre-format)\n * @param oldCursor selectionStart captured from the native event\n * @param newFormatted The formatted string we're about to display\n * @param info Locale separators\n * @param inputType e.nativeEvent.inputType (optional â for backspace detection)\n */\nexport function computeNewCursorPosition(\n oldInput: string,\n oldCursor: number,\n newFormatted: string,\n info: LocaleInfo,\n inputType?: string\n): number {\n const normalised = normalizeDigits(oldInput);\n\n // Stage 1: count accepted chars before cursor in old input\n let acceptedCount = countAcceptedBefore(normalised, oldCursor, info);\n\n // Stage 2: backspace on grouping separator â also delete the preceding digit\n if (\n inputType === \"deleteContentBackward\" &&\n oldCursor > 0 &&\n oldInput[oldCursor - 1] === info.groupingSeparator\n ) {\n // The separator was deleted but we want to remove the preceding digit too\n acceptedCount = Math.max(0, acceptedCount - 1);\n }\n\n // Stage 3: build boundary and walk new formatted string\n const boundary = getCaretBoundary(newFormatted, info);\n const normNew = normalizeDigits(newFormatted);\n let count = 0;\n let pos = 0;\n\n for (let i = 0; i < normNew.length; i++) {\n if (count === acceptedCount) {\n pos = i;\n break;\n }\n if (isAccepted(normNew[i]!, info)) count++;\n pos = i + 1;\n }\n\n // If we ran through the entire string without reaching the target count,\n // place the cursor at the end. Note: acceptedCount === 0 means we correctly\n // want pos = 0 (set in the loop), so we must NOT override that case.\n if (acceptedCount > 0 && count < acceptedCount) {\n pos = newFormatted.length;\n }\n\n // Snap to nearest valid boundary\n pos = snapToBoundary(pos, boundary);\n\n return pos;\n}\n\n/**\n * If `pos` is at a false (invalid) boundary position, find the nearest\n * true position. Prefers moving forward; falls back to backward.\n */\nfunction snapToBoundary(pos: number, boundary: CaretBoundary): number {\n if (boundary[pos]) return pos;\n\n // Try forward first\n for (let i = pos + 1; i < boundary.length; i++) {\n if (boundary[i]) return i;\n }\n // Fall back to backward\n for (let i = pos - 1; i >= 0; i--) {\n if (boundary[i]) return i;\n }\n return 0;\n}\n"]}
|