ngx-digits-only 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +64 -0
- package/fesm2022/ngx-digits-only.mjs +1847 -0
- package/fesm2022/ngx-digits-only.mjs.map +1 -0
- package/package.json +24 -0
- package/types/ngx-digits-only.d.ts +601 -0
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { OnInit, OnChanges, ElementRef, Renderer2, SimpleChanges } from '@angular/core';
|
|
3
|
+
import { ControlValueAccessor, Validator, AbstractControl, ValidationErrors } from '@angular/forms';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* digits-only.directive.ts
|
|
7
|
+
*
|
|
8
|
+
* A self-contained Angular directive that replicates the key numeric features
|
|
9
|
+
* of ngx-mask without any external dependency.
|
|
10
|
+
*
|
|
11
|
+
* ─── Files in this module ─────────────────────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* digits-only.directive.ts ← you are here — the Angular directive
|
|
14
|
+
* digits-only.regex.ts ← every RegExp constant with detailed explanations
|
|
15
|
+
* digits-only.module.ts ← NgModule that declares and exports the directive
|
|
16
|
+
* eastern-numerals.ts ← standalone Arabic / Persian → Western digit converter
|
|
17
|
+
*
|
|
18
|
+
* ─── Features ─────────────────────────────────────────────────────────────────
|
|
19
|
+
*
|
|
20
|
+
* [digitsOnly] Selector — add to any <input> element
|
|
21
|
+
* [decimalPlaces] Number of decimal places allowed (default: 0 = integers)
|
|
22
|
+
* [thousandSeparator] Visual grouping character: ',' | '.' | ' ' | '_' | ''
|
|
23
|
+
* [prefix] Visual prefix shown in input (e.g. '$') — stripped from model
|
|
24
|
+
* [suffix] Visual suffix shown in input (e.g. '%') — stripped from model
|
|
25
|
+
* [allowNegative] Allow a leading minus sign (default: false)
|
|
26
|
+
* [leadingZeros] Preserve leading zeros like '007' (default: false)
|
|
27
|
+
* [maxLength] Maximum number of raw digits allowed
|
|
28
|
+
* [min] Minimum numeric value (validation, number mode only)
|
|
29
|
+
* [max] Maximum numeric value (validation, number mode only)
|
|
30
|
+
* [outputType] 'number' (default) | 'string' — type emitted to the model
|
|
31
|
+
* [pattern] Display-format mask: '0000 0000 0000 0000' or named alias
|
|
32
|
+
* [convertEasternNumerals] Convert Arabic/Persian digits to Western on input (default: true)
|
|
33
|
+
*
|
|
34
|
+
* ─── outputType ───────────────────────────────────────────────────────────────
|
|
35
|
+
*
|
|
36
|
+
* 'number' (default)
|
|
37
|
+
* Model receives: number | null
|
|
38
|
+
* Use for: prices, quantities, temperatures — values used in arithmetic
|
|
39
|
+
* Note: Leading zeros are lost (007 → 7); values beyond
|
|
40
|
+
* Number.MAX_SAFE_INTEGER may lose precision
|
|
41
|
+
*
|
|
42
|
+
* 'string'
|
|
43
|
+
* Model receives: string | null
|
|
44
|
+
* Use for: card numbers, phone numbers, postal codes, SSNs, OTPs —
|
|
45
|
+
* digit strings that are identifiers, not quantities
|
|
46
|
+
* Note: Leading zeros are always preserved; no precision limit
|
|
47
|
+
*
|
|
48
|
+
* ─── pattern ──────────────────────────────────────────────────────────────────
|
|
49
|
+
*
|
|
50
|
+
* A mask string made of '0' (digit slot) and any other character (separator).
|
|
51
|
+
* Separators are inserted in the display automatically but NEVER reach the model.
|
|
52
|
+
* Setting a pattern forces outputType='string' and derives maxLength automatically.
|
|
53
|
+
*
|
|
54
|
+
* Named aliases:
|
|
55
|
+
* 'card' → '0000 0000 0000 0000' Visa / Mastercard (16 digits)
|
|
56
|
+
*
|
|
57
|
+
* ─── convertEasternNumerals ───────────────────────────────────────────────────
|
|
58
|
+
*
|
|
59
|
+
* When true (default), Arabic-Indic (٠١٢٣٤٥٦٧٨٩) and Persian (۰۱۲۳۴۵۶۷۸۹)
|
|
60
|
+
* digit characters are silently converted to their Western equivalents before
|
|
61
|
+
* any processing. This allows users on Arabic / Persian keyboards to type
|
|
62
|
+
* naturally into any digitsOnly field.
|
|
63
|
+
* Set to false only if you specifically need to block Eastern numerals.
|
|
64
|
+
*
|
|
65
|
+
* ─── Usage examples ───────────────────────────────────────────────────────────
|
|
66
|
+
*
|
|
67
|
+
* <!-- Integer amount, $ prefix, comma thousands -->
|
|
68
|
+
* <input digitsOnly thousandSeparator="," prefix="$" formControlName="salary" />
|
|
69
|
+
*
|
|
70
|
+
* <!-- Decimal price, 2 places -->
|
|
71
|
+
* <input digitsOnly [decimalPlaces]="2" thousandSeparator="," formControlName="price" />
|
|
72
|
+
*
|
|
73
|
+
* <!-- Card number with automatic space grouping -->
|
|
74
|
+
* <input digitsOnly pattern="card" formControlName="cardNumber" />
|
|
75
|
+
*
|
|
76
|
+
* <!-- Custom pattern -->
|
|
77
|
+
* <input digitsOnly pattern="000-00-0000" formControlName="ssn" />
|
|
78
|
+
*
|
|
79
|
+
* <!-- Opt out of Eastern numeral conversion -->
|
|
80
|
+
* <input digitsOnly [convertEasternNumerals]="false" formControlName="code" />
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
type NamedPattern = 'card';
|
|
84
|
+
declare class digitsOnlyDirective implements ControlValueAccessor, Validator, OnInit, OnChanges {
|
|
85
|
+
private el;
|
|
86
|
+
private renderer;
|
|
87
|
+
private document;
|
|
88
|
+
/** How many decimal places are allowed. 0 = integers only. Ignored when pattern is set. */
|
|
89
|
+
decimalPlaces: number;
|
|
90
|
+
/** Visual grouping character inserted between digit groups. Ignored when pattern is set. */
|
|
91
|
+
thousandSeparator: '' | ',' | '.' | ' ' | '_';
|
|
92
|
+
/** Visual text prepended to the displayed value (e.g. '$'). Never reaches the model. */
|
|
93
|
+
prefix: string;
|
|
94
|
+
/** Visual text appended to the displayed value (e.g. '%'). Never reaches the model. */
|
|
95
|
+
suffix: string;
|
|
96
|
+
/** When true, a leading minus '-' is allowed for negative numbers. */
|
|
97
|
+
allowNegative: boolean;
|
|
98
|
+
/** When true, leading zeros (e.g. '007') are preserved in the model value. */
|
|
99
|
+
leadingZeros: boolean;
|
|
100
|
+
/** Maximum number of raw digit characters the user may enter. */
|
|
101
|
+
maxLength: number | null;
|
|
102
|
+
/** Minimum allowed numeric value. Produces a 'min' validation error when violated. */
|
|
103
|
+
min: number | null;
|
|
104
|
+
/** Maximum allowed numeric value. Produces a 'max' validation error when violated. */
|
|
105
|
+
max: number | null;
|
|
106
|
+
/**
|
|
107
|
+
* Controls the JavaScript type emitted to the form model.
|
|
108
|
+
* 'number' (default) → number | null
|
|
109
|
+
* 'string' → string | null
|
|
110
|
+
* Automatically overridden to 'string' when [pattern] is set.
|
|
111
|
+
*/
|
|
112
|
+
outputType: 'number' | 'string';
|
|
113
|
+
/**
|
|
114
|
+
* When true (default), Arabic-Indic and Persian digit characters typed or
|
|
115
|
+
* pasted by the user are automatically converted to Western digits 0-9
|
|
116
|
+
* before any processing happens.
|
|
117
|
+
*
|
|
118
|
+
* Safe to leave enabled for all use cases — the conversion only touches
|
|
119
|
+
* characters in the Unicode ranges U+0660–U+0669 and U+06F0–U+06F9,
|
|
120
|
+
* which have zero overlap with any ASCII character.
|
|
121
|
+
*/
|
|
122
|
+
convertEasternNumerals: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* Display-format mask string or a named alias (see NamedPattern type).
|
|
125
|
+
*
|
|
126
|
+
* Typed as NamedPattern | (string & {}) so that:
|
|
127
|
+
* • Named aliases are autocompleted in IDEs and validated at compile time.
|
|
128
|
+
* • Custom raw patterns like '000-0000' are still accepted without error.
|
|
129
|
+
*
|
|
130
|
+
* '0' = digit slot, any other char = literal separator auto-inserted in display.
|
|
131
|
+
* Setting this forces outputType='string' and derives maxLength from slot count.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* pattern="card" → '0000 0000 0000 0000'
|
|
135
|
+
* pattern="00/00/0000" → custom date pattern
|
|
136
|
+
*/
|
|
137
|
+
set pattern(value: NamedPattern | (string & {}));
|
|
138
|
+
/** Full pattern string after named-alias resolution. Empty string = no pattern. */
|
|
139
|
+
private _resolvedPattern;
|
|
140
|
+
/**
|
|
141
|
+
* The canonical internal value: pure digits (and optional '.' and '-').
|
|
142
|
+
* No prefix, suffix, separators, or pattern characters ever live here.
|
|
143
|
+
* Every other method reads or writes this field.
|
|
144
|
+
*/
|
|
145
|
+
private _rawValue;
|
|
146
|
+
private _onChange;
|
|
147
|
+
private _onTouched;
|
|
148
|
+
private _onValidatorChange;
|
|
149
|
+
/** True when a format pattern has been set on this input. */
|
|
150
|
+
private get hasPattern();
|
|
151
|
+
/**
|
|
152
|
+
* The set of literal separator characters defined in the current pattern.
|
|
153
|
+
*
|
|
154
|
+
* Pattern '(000) 000-0000' → separators: {'(', ')', ' ', '-'}
|
|
155
|
+
* Pattern '0000 0000' → separators: {' '}
|
|
156
|
+
*
|
|
157
|
+
* Used when stripping decorations back off a displayed value.
|
|
158
|
+
*/
|
|
159
|
+
private get patternSeparatorChars();
|
|
160
|
+
/**
|
|
161
|
+
* The total number of digit slots ('0' characters) in the current pattern.
|
|
162
|
+
* 'card' pattern has 16 slots; 'expiry' has 4; etc.
|
|
163
|
+
*/
|
|
164
|
+
private get patternDigitSlotCount();
|
|
165
|
+
/**
|
|
166
|
+
* The effective maximum digit length to enforce.
|
|
167
|
+
* In pattern mode, this is always the slot count (overrides any [maxLength] input).
|
|
168
|
+
* In non-pattern mode, it comes from the [maxLength] input (may be null = unlimited).
|
|
169
|
+
*/
|
|
170
|
+
private get effectiveMaxLength();
|
|
171
|
+
/**
|
|
172
|
+
* The effective output type.
|
|
173
|
+
* Pattern mode always emits strings because patterns are for identifiers.
|
|
174
|
+
* Otherwise respects the [outputType] input.
|
|
175
|
+
*/
|
|
176
|
+
private get effectiveOutputType();
|
|
177
|
+
/**
|
|
178
|
+
* The decimal separator character used in the DISPLAY.
|
|
179
|
+
* If the thousands separator is '.' (European style), we must use ','
|
|
180
|
+
* as the decimal character to avoid ambiguity (1.234,56 is valid; 1,234,56 is not).
|
|
181
|
+
* Otherwise we use the standard '.'.
|
|
182
|
+
*/
|
|
183
|
+
private get displayDecimalChar();
|
|
184
|
+
/**
|
|
185
|
+
* Returns true when the input element (or any of its ancestors) is in
|
|
186
|
+
* right-to-left mode.
|
|
187
|
+
*
|
|
188
|
+
* Detection order:
|
|
189
|
+
* 1. The input element's own computed `direction` style — this reflects
|
|
190
|
+
* any `dir` attribute on the element itself, a CSS `direction` rule,
|
|
191
|
+
* or inheritance from a parent element with dir="rtl".
|
|
192
|
+
* 2. Falls back to false (LTR) if the window / getComputedStyle is
|
|
193
|
+
* unavailable (e.g. server-side rendering).
|
|
194
|
+
*
|
|
195
|
+
* This is a getter (not a cached field) so it always reflects the current
|
|
196
|
+
* DOM state, even if the direction changes dynamically at runtime.
|
|
197
|
+
*/
|
|
198
|
+
private get isRtl();
|
|
199
|
+
/**
|
|
200
|
+
* In RTL mode the string stored in the input is prefixed with an LRM mark
|
|
201
|
+
* so that the bidi algorithm does not reorder digits or separators.
|
|
202
|
+
* This helper returns the prefix length including the LRM when RTL is active.
|
|
203
|
+
*
|
|
204
|
+
* Used wherever we need to know "how many characters before the editable
|
|
205
|
+
* number portion start" — cursor clamping, minus-key position check, etc.
|
|
206
|
+
*/
|
|
207
|
+
private get editableStartOffset();
|
|
208
|
+
/**
|
|
209
|
+
* The number of characters at the RIGHT end of the display string that
|
|
210
|
+
* belong to the suffix (LTR) or prefix (RTL) — i.e. characters the cursor
|
|
211
|
+
* must never move past on the right side.
|
|
212
|
+
*/
|
|
213
|
+
private get editableEndPadding();
|
|
214
|
+
constructor(el: ElementRef<HTMLInputElement>, renderer: Renderer2, document: Document);
|
|
215
|
+
ngOnInit(): void;
|
|
216
|
+
ngOnChanges(changes: SimpleChanges): void;
|
|
217
|
+
/** Angular calls this when the form model value changes programmatically. */
|
|
218
|
+
writeValue(value: string | number | null): void;
|
|
219
|
+
/** Angular calls this to register the function we must call when our value changes. */
|
|
220
|
+
registerOnChange(fn: (v: number | string | null) => void): void;
|
|
221
|
+
/** Angular calls this to register the function we must call when the field is touched. */
|
|
222
|
+
registerOnTouched(fn: () => void): void;
|
|
223
|
+
/** Angular calls this to enable or disable the input. */
|
|
224
|
+
setDisabledState(isDisabled: boolean): void;
|
|
225
|
+
/** Angular calls this to ask whether the current value has any errors. */
|
|
226
|
+
validate(_control: AbstractControl): ValidationErrors | null;
|
|
227
|
+
/** Angular calls this to register the function we call when validation rules change. */
|
|
228
|
+
registerOnValidatorChange(fn: () => void): void;
|
|
229
|
+
/**
|
|
230
|
+
* Intercept keydown BEFORE the character lands in the input.
|
|
231
|
+
* This is our first line of defense — blocking bad keys entirely
|
|
232
|
+
* so the input event never fires for them.
|
|
233
|
+
*/
|
|
234
|
+
onKeyDown(event: KeyboardEvent): void;
|
|
235
|
+
/**
|
|
236
|
+
* Process input AFTER the character has landed in the input element.
|
|
237
|
+
* We read the new display value, strip decorations to get raw digits,
|
|
238
|
+
* apply rules (decimal limits, maxLength, leading zeros), then re-render.
|
|
239
|
+
*/
|
|
240
|
+
onInput(): void;
|
|
241
|
+
/**
|
|
242
|
+
* When the user leaves the field, mark it as touched (triggers validation
|
|
243
|
+
* display) and clean up any trailing decimal point (e.g. the user typed
|
|
244
|
+
* '12.' and then tabbed away).
|
|
245
|
+
*/
|
|
246
|
+
onBlur(): void;
|
|
247
|
+
/**
|
|
248
|
+
* Handle paste events manually so we can sanitize the pasted content
|
|
249
|
+
* before it reaches the input, just like we do for individual keystrokes.
|
|
250
|
+
*/
|
|
251
|
+
onPaste(event: ClipboardEvent): void;
|
|
252
|
+
/**
|
|
253
|
+
* Block drag-and-drop into the field.
|
|
254
|
+
* Without this a user could drag text from elsewhere and bypass our rules.
|
|
255
|
+
*/
|
|
256
|
+
onDrop(event: DragEvent): void;
|
|
257
|
+
/**
|
|
258
|
+
* Build the value that gets emitted to the Angular form model.
|
|
259
|
+
*
|
|
260
|
+
* Pattern / string mode → string | null
|
|
261
|
+
* '' → null (empty field)
|
|
262
|
+
* '123' → '123' (raw digits, leading zeros preserved, no separators)
|
|
263
|
+
*
|
|
264
|
+
* Number mode → number | null
|
|
265
|
+
* '' → null (empty field)
|
|
266
|
+
* '-' → null (user typed a minus but no digits yet)
|
|
267
|
+
* '123' → 123
|
|
268
|
+
* '1.5' → 1.5
|
|
269
|
+
* '-7' → -7
|
|
270
|
+
*/
|
|
271
|
+
private buildModelValue;
|
|
272
|
+
/**
|
|
273
|
+
* Write the formatted display string back to the <input> DOM element
|
|
274
|
+
* and try to restore the cursor to a sensible position.
|
|
275
|
+
*
|
|
276
|
+
* @param cursorBefore Cursor position in the display string BEFORE we reformatted
|
|
277
|
+
* @param lengthBefore Total display string length BEFORE we reformatted
|
|
278
|
+
*/
|
|
279
|
+
private refreshDisplay;
|
|
280
|
+
/**
|
|
281
|
+
* Apply the display format pattern to the current raw digit string.
|
|
282
|
+
*
|
|
283
|
+
* Algorithm:
|
|
284
|
+
* Walk through the pattern character by character.
|
|
285
|
+
* On '0': consume the next raw digit and add it to the result.
|
|
286
|
+
* On anything else: this is a separator — add it ONLY if more digits follow.
|
|
287
|
+
* (The "more digits follow" check prevents trailing separators mid-type.)
|
|
288
|
+
*
|
|
289
|
+
* Examples:
|
|
290
|
+
* pattern '0000 0000 0000 0000', raw '41111111' → '4111 1111'
|
|
291
|
+
* pattern '(000) 000-0000', raw '5558675' → '(555) 867'
|
|
292
|
+
* pattern '00/00', raw '122' → '12/2'
|
|
293
|
+
*/
|
|
294
|
+
private applyPatternFormatting;
|
|
295
|
+
/**
|
|
296
|
+
* Apply thousands separator grouping and decimal character to a raw number string.
|
|
297
|
+
* Only used in number output mode (not pattern, not string mode).
|
|
298
|
+
*
|
|
299
|
+
* Examples (separator=',', decimalChar='.'):
|
|
300
|
+
* '1234567' → '1,234,567'
|
|
301
|
+
* '-9876.50' → '-9,876.50'
|
|
302
|
+
* '100' → '100'
|
|
303
|
+
*/
|
|
304
|
+
private applyThousandsFormatting;
|
|
305
|
+
/**
|
|
306
|
+
* Remove all visual decoration from the displayed input value and return
|
|
307
|
+
* the bare digit/numeric string ready for internal storage.
|
|
308
|
+
*
|
|
309
|
+
* Steps:
|
|
310
|
+
* 1. Strip prefix from the front
|
|
311
|
+
* 2. Strip suffix from the back
|
|
312
|
+
* 3. Remove pattern separators OR thousands separators (depending on mode)
|
|
313
|
+
* 4. Normalize non-ASCII decimal separator back to '.'
|
|
314
|
+
* 5. Remove any remaining non-numeric characters
|
|
315
|
+
*/
|
|
316
|
+
private stripDecorationsFrom;
|
|
317
|
+
/**
|
|
318
|
+
* Remove every separator character that the current pattern defines.
|
|
319
|
+
* For example, pattern '(000) 000-0000' defines '(', ')', ' ', '-' as separators.
|
|
320
|
+
* This strips all four from the input string, leaving only digits.
|
|
321
|
+
*/
|
|
322
|
+
private stripPatternSeparatorsFrom;
|
|
323
|
+
/**
|
|
324
|
+
* Ensure a raw numeric string has at most one decimal point
|
|
325
|
+
* and at most [decimalPlaces] digits after it.
|
|
326
|
+
*
|
|
327
|
+
* Examples (decimalPlaces=2):
|
|
328
|
+
* '12.345' → '12.34'
|
|
329
|
+
* '1.2.3' → '1.23'
|
|
330
|
+
* '100' → '100'
|
|
331
|
+
*/
|
|
332
|
+
private enforceDecimalRules;
|
|
333
|
+
/**
|
|
334
|
+
* Remove unnecessary leading zeros from the integer part of a numeric string.
|
|
335
|
+
* Always preserves at least one digit and never touches the decimal part.
|
|
336
|
+
*
|
|
337
|
+
* Examples:
|
|
338
|
+
* '007' → '7'
|
|
339
|
+
* '0042' → '42'
|
|
340
|
+
* '0.5' → '0.5' (leading zero before decimal is correct, don't strip)
|
|
341
|
+
* '100' → '100' (no leading zero)
|
|
342
|
+
* '-007' → '-7' (handles negative values)
|
|
343
|
+
*/
|
|
344
|
+
private stripLeadingZeros;
|
|
345
|
+
/**
|
|
346
|
+
* Sanitize a pasted string for non-pattern mode.
|
|
347
|
+
* The characters we keep depend on the current mode:
|
|
348
|
+
*
|
|
349
|
+
* string mode → digits only (no dot, no minus; identifiers can't be negative)
|
|
350
|
+
* integer number → digits + optional leading minus
|
|
351
|
+
* decimal number → digits + optional dot + optional leading minus
|
|
352
|
+
*/
|
|
353
|
+
private sanitizePastedText;
|
|
354
|
+
/**
|
|
355
|
+
* Map a cursor position in the formatted DISPLAY string back to an index
|
|
356
|
+
* in the raw digit string.
|
|
357
|
+
*
|
|
358
|
+
* This is needed during paste operations in pattern mode so we know
|
|
359
|
+
* exactly which digit position the cursor is sitting at.
|
|
360
|
+
*
|
|
361
|
+
* Example:
|
|
362
|
+
* pattern '(000) 000-0000'
|
|
363
|
+
* display '(555) 867' cursor at position 8 (after '(555) 86')
|
|
364
|
+
*
|
|
365
|
+
* Walking the pattern:
|
|
366
|
+
* '(' pos 0 → sep (adjusted pos 0, raw index still 0)
|
|
367
|
+
* '0' pos 1 → digit (raw index 0) digit consumed → raw index 1
|
|
368
|
+
* '0' pos 2 → digit (raw index 1) digit consumed → raw index 2
|
|
369
|
+
* '0' pos 3 → digit (raw index 2) digit consumed → raw index 3
|
|
370
|
+
* ')' pos 4 → sep
|
|
371
|
+
* ' ' pos 5 → sep
|
|
372
|
+
* '0' pos 6 → digit (raw index 3) digit consumed → raw index 4
|
|
373
|
+
* '0' pos 7 → digit (raw index 4) digit consumed → raw index 5
|
|
374
|
+
* We reached position 8, so raw index = 5 ✔
|
|
375
|
+
*/
|
|
376
|
+
private mapDisplayCursorToRawIndex;
|
|
377
|
+
/**
|
|
378
|
+
* Decide whether a keyboard event is a "control" key that should always
|
|
379
|
+
* be allowed through without interception.
|
|
380
|
+
*
|
|
381
|
+
* Includes:
|
|
382
|
+
* - Modifier keys held (Ctrl, Meta/Cmd, Alt) — so Ctrl+C, Ctrl+V, etc. work
|
|
383
|
+
* - Navigation keys (arrows, Home, End)
|
|
384
|
+
* - Editing keys (Backspace, Delete, Tab, Escape, Enter)
|
|
385
|
+
*/
|
|
386
|
+
private isControlKey;
|
|
387
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<digitsOnlyDirective, never>;
|
|
388
|
+
static ɵdir: i0.ɵɵDirectiveDeclaration<digitsOnlyDirective, "[digitsOnly]", never, { "decimalPlaces": { "alias": "decimalPlaces"; "required": false; }; "thousandSeparator": { "alias": "thousandSeparator"; "required": false; }; "prefix": { "alias": "prefix"; "required": false; }; "suffix": { "alias": "suffix"; "required": false; }; "allowNegative": { "alias": "allowNegative"; "required": false; }; "leadingZeros": { "alias": "leadingZeros"; "required": false; }; "maxLength": { "alias": "maxLength"; "required": false; }; "min": { "alias": "min"; "required": false; }; "max": { "alias": "max"; "required": false; }; "outputType": { "alias": "outputType"; "required": false; }; "convertEasternNumerals": { "alias": "convertEasternNumerals"; "required": false; }; "pattern": { "alias": "pattern"; "required": false; }; }, {}, never, never, true, never>;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* eastern-numerals.ts
|
|
393
|
+
*
|
|
394
|
+
* A fully standalone, zero-dependency TypeScript utility that converts
|
|
395
|
+
* Arabic-Indic and Persian/Farsi digit characters to their Western (ASCII)
|
|
396
|
+
* equivalents.
|
|
397
|
+
*
|
|
398
|
+
* ─── Zero dependencies ────────────────────────────────────────────────────────
|
|
399
|
+
*
|
|
400
|
+
* This file has NO imports. Copy it into ANY TypeScript or JavaScript project
|
|
401
|
+
* and use it immediately — Angular, React, Vue, Node.js, plain TS, or a browser
|
|
402
|
+
* script. Nothing else is required.
|
|
403
|
+
*
|
|
404
|
+
* ─── Why this exists ──────────────────────────────────────────────────────────
|
|
405
|
+
*
|
|
406
|
+
* Arabic and Persian keyboards — and many mobile IMEs across the Middle East
|
|
407
|
+
* and South Asia — produce digit characters from a DIFFERENT Unicode block
|
|
408
|
+
* than the ASCII digits 0–9 most Western software expects:
|
|
409
|
+
*
|
|
410
|
+
* Western / ASCII 0 1 2 3 4 5 6 7 8 9
|
|
411
|
+
* Code points U+0030 ── U+0039
|
|
412
|
+
*
|
|
413
|
+
* Arabic-Indic ٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩
|
|
414
|
+
* Code points U+0660 ── U+0669
|
|
415
|
+
*
|
|
416
|
+
* Persian / Farsi ۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹
|
|
417
|
+
* Code points U+06F0 ── U+06F9
|
|
418
|
+
*
|
|
419
|
+
* When a user types ١٢٣ on an Arabic keyboard the browser sends those exact
|
|
420
|
+
* Unicode code points, NOT '123'. Without conversion:
|
|
421
|
+
* • /^\d$/ does NOT match them (it only matches U+0030–U+0039)
|
|
422
|
+
* • Number('١٢٣') returns NaN
|
|
423
|
+
* • parseInt('١٢٣', 10) returns NaN
|
|
424
|
+
*
|
|
425
|
+
* ─── Safety guarantee ─────────────────────────────────────────────────────────
|
|
426
|
+
*
|
|
427
|
+
* This conversion is completely conflict-free because the Arabic and Persian
|
|
428
|
+
* digit Unicode blocks have ZERO overlap with:
|
|
429
|
+
* • ASCII digits (U+0030–U+0039)
|
|
430
|
+
* • ASCII letters (U+0041–U+007A)
|
|
431
|
+
* • ASCII punctuation (. , - + $ € % @ # etc.)
|
|
432
|
+
*
|
|
433
|
+
* Converting ٥ → '5' can NEVER accidentally transform a separator, currency
|
|
434
|
+
* symbol, or letter into something else. The substitution is exact and
|
|
435
|
+
* unambiguous.
|
|
436
|
+
*
|
|
437
|
+
* ─── What is NOT converted ────────────────────────────────────────────────────
|
|
438
|
+
*
|
|
439
|
+
* • Arabic letters ا ب ت ث … — no Western equivalent, left as-is
|
|
440
|
+
* • Arabic diacritics (harakat) — no Western equivalent, left as-is
|
|
441
|
+
* • Any character outside the two digit blocks described above
|
|
442
|
+
*
|
|
443
|
+
* ─── Exports ──────────────────────────────────────────────────────────────────
|
|
444
|
+
*
|
|
445
|
+
* convertEasternDigits(input) Main conversion — Eastern digits → 0-9
|
|
446
|
+
* hasEasternDigits(input) Quick check — does the string contain any?
|
|
447
|
+
* getDigitScript(input) Identify which script the digits are in
|
|
448
|
+
* EASTERN_DIGIT_MAP Full lookup table (for testing / docs)
|
|
449
|
+
* EASTERN_NUMERAL_REGEX The two RegExp patterns (for advanced use)
|
|
450
|
+
*
|
|
451
|
+
* ─── Quick usage ──────────────────────────────────────────────────────────────
|
|
452
|
+
*
|
|
453
|
+
* import { convertEasternDigits } from './eastern-numerals';
|
|
454
|
+
*
|
|
455
|
+
* convertEasternDigits('١٢٣٫٤٥') // '123.45'
|
|
456
|
+
* convertEasternDigits('۶۷۸') // '678'
|
|
457
|
+
* convertEasternDigits('price: ١٠٠') // 'price: 100' (non-digits untouched)
|
|
458
|
+
* convertEasternDigits('123') // '123' (already Western)
|
|
459
|
+
* convertEasternDigits(null) // ''
|
|
460
|
+
*/
|
|
461
|
+
/**
|
|
462
|
+
* Regex patterns used internally by this module.
|
|
463
|
+
* Also exported so callers can use them directly if needed.
|
|
464
|
+
*
|
|
465
|
+
* These are the same patterns documented in digits-only.regex.ts entries
|
|
466
|
+
* ARABIC_DIGITS, PERSIAN_DIGITS, ARABIC_DECIMAL_COMMA, ARABIC_THOUSANDS_SEPARATOR
|
|
467
|
+
* but duplicated here so this file remains self-contained.
|
|
468
|
+
*/
|
|
469
|
+
declare const EASTERN_NUMERAL_REGEX: {
|
|
470
|
+
/**
|
|
471
|
+
* Matches any Arabic-Indic digit character (U+0660 – U+0669).
|
|
472
|
+
* These are the digits used on Arabic keyboards: ٠١٢٣٤٥٦٧٨٩
|
|
473
|
+
* The g flag ensures ALL occurrences in a string are replaced, not just the first.
|
|
474
|
+
*/
|
|
475
|
+
readonly ARABIC_DIGITS: RegExp;
|
|
476
|
+
/**
|
|
477
|
+
* Matches any Persian / Extended Arabic-Indic digit (U+06F0 – U+06F9).
|
|
478
|
+
* These are the digits used on Persian / Farsi keyboards: ۰۱۲۳۴۵۶۷۸۹
|
|
479
|
+
* Note: a DIFFERENT Unicode block from Arabic even though they look similar.
|
|
480
|
+
* Arabic zero ٠ = U+0660
|
|
481
|
+
* Persian zero ۰ = U+06F0 ← different!
|
|
482
|
+
*/
|
|
483
|
+
readonly PERSIAN_DIGITS: RegExp;
|
|
484
|
+
/**
|
|
485
|
+
* Matches the Arabic Decimal Separator ٫ (U+066B).
|
|
486
|
+
* In Arabic locale this character plays the role of the Western period '.'
|
|
487
|
+
* as a decimal point. We normalize it to '.' so parseFloat / Number work.
|
|
488
|
+
*/
|
|
489
|
+
readonly ARABIC_DECIMAL_COMMA: RegExp;
|
|
490
|
+
/**
|
|
491
|
+
* Matches the Arabic Thousands Separator ٬ (U+066C).
|
|
492
|
+
* In Arabic locale this character plays the role of the Western comma ','
|
|
493
|
+
* as a digit-group separator. We strip it so it doesn't pollute the number.
|
|
494
|
+
*/
|
|
495
|
+
readonly ARABIC_THOUSANDS_SEPARATOR: RegExp;
|
|
496
|
+
};
|
|
497
|
+
/**
|
|
498
|
+
* Every Eastern digit character mapped to its Western ASCII equivalent.
|
|
499
|
+
*
|
|
500
|
+
* Key = Eastern character (as it arrives from the keyboard / clipboard)
|
|
501
|
+
* Value = Western ASCII character it should become
|
|
502
|
+
*
|
|
503
|
+
* This map is exported for:
|
|
504
|
+
* • Unit tests that want to iterate over every case
|
|
505
|
+
* • Documentation / debugging
|
|
506
|
+
* • Manual lookups without running a conversion
|
|
507
|
+
*/
|
|
508
|
+
declare const EASTERN_DIGIT_MAP: Readonly<Record<string, string>>;
|
|
509
|
+
/**
|
|
510
|
+
* Convert all Arabic-Indic and Persian digit characters in a string
|
|
511
|
+
* to their Western ASCII equivalents (0–9).
|
|
512
|
+
*
|
|
513
|
+
* The function runs four passes in order:
|
|
514
|
+
* 1. Strip Arabic thousands separator ٬ (U+066C)
|
|
515
|
+
* 2. Convert Arabic decimal comma ٫ (U+066B) → '.'
|
|
516
|
+
* 3. Convert Arabic-Indic digits ٠-٩ (U+0660–U+0669) → '0'-'9'
|
|
517
|
+
* 4. Convert Persian digits ۰-۹ (U+06F0–U+06F9) → '0'-'9'
|
|
518
|
+
*
|
|
519
|
+
* The function is PURE — it never mutates its input and has no side effects.
|
|
520
|
+
*
|
|
521
|
+
* @param input Any string, number, null, or undefined.
|
|
522
|
+
* Numbers are converted via String() first.
|
|
523
|
+
* null / undefined / '' all return ''.
|
|
524
|
+
* @returns A new string with every Eastern digit replaced by its Western
|
|
525
|
+
* equivalent. Non-digit characters are passed through unchanged.
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* convertEasternDigits('١٢٣') // '123'
|
|
529
|
+
* convertEasternDigits('۶۷۸') // '678'
|
|
530
|
+
* convertEasternDigits('١٢٣٫٤٥') // '123.45' (decimal comma)
|
|
531
|
+
* convertEasternDigits('١٬٢٣٤') // '1234' (thousands sep stripped)
|
|
532
|
+
* convertEasternDigits('price: ٩٩') // 'price: 99' (non-digits untouched)
|
|
533
|
+
* convertEasternDigits('123') // '123' (already Western)
|
|
534
|
+
* convertEasternDigits('') // ''
|
|
535
|
+
* convertEasternDigits(null) // ''
|
|
536
|
+
* convertEasternDigits(undefined) // ''
|
|
537
|
+
* convertEasternDigits(42) // '42' (number input)
|
|
538
|
+
*/
|
|
539
|
+
declare function convertEasternDigits(input: string | number | null | undefined): string;
|
|
540
|
+
/**
|
|
541
|
+
* Returns true if the string contains at least one Arabic-Indic or
|
|
542
|
+
* Persian digit character.
|
|
543
|
+
*
|
|
544
|
+
* Useful as a cheap pre-check before calling convertEasternDigits — if this
|
|
545
|
+
* returns false the string is already all-Western and no conversion is needed.
|
|
546
|
+
*
|
|
547
|
+
* @param input Any string to check.
|
|
548
|
+
* @returns true if any Eastern digit is present, false otherwise.
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* hasEasternDigits('١٢٣') // true — Arabic
|
|
552
|
+
* hasEasternDigits('۴۵۶') // true — Persian
|
|
553
|
+
* hasEasternDigits('٥0') // true — mix of Arabic and Western
|
|
554
|
+
* hasEasternDigits('123') // false — already Western
|
|
555
|
+
* hasEasternDigits('hello') // false — no digits at all
|
|
556
|
+
* hasEasternDigits('') // false
|
|
557
|
+
*/
|
|
558
|
+
declare function hasEasternDigits(input: string): boolean;
|
|
559
|
+
/**
|
|
560
|
+
* Identify which numeral script(s) the string's digits are written in.
|
|
561
|
+
* Returns one of four values so callers can branch on the exact script
|
|
562
|
+
* rather than just knowing "something eastern is present".
|
|
563
|
+
*
|
|
564
|
+
* @param input Any string to inspect.
|
|
565
|
+
* @returns
|
|
566
|
+
* 'western' — only ASCII digits 0-9 found (or no digits at all)
|
|
567
|
+
* 'arabic' — only Arabic-Indic digits ٠-٩ found
|
|
568
|
+
* 'persian' — only Persian digits ۰-۹ found
|
|
569
|
+
* 'mixed' — both Eastern script(s) AND/OR Western digits found together
|
|
570
|
+
*
|
|
571
|
+
* @example
|
|
572
|
+
* getDigitScript('١٢٣') // 'arabic'
|
|
573
|
+
* getDigitScript('۴۵۶') // 'persian'
|
|
574
|
+
* getDigitScript('123') // 'western'
|
|
575
|
+
* getDigitScript('١23') // 'mixed' (Arabic + Western)
|
|
576
|
+
* getDigitScript('١۴') // 'mixed' (Arabic + Persian)
|
|
577
|
+
* getDigitScript('hello') // 'western' (no digits → treated as Western)
|
|
578
|
+
*/
|
|
579
|
+
type DigitScript = 'western' | 'arabic' | 'persian' | 'mixed';
|
|
580
|
+
declare function getDigitScript(input: string): DigitScript;
|
|
581
|
+
/**
|
|
582
|
+
* Convert a string that may contain Eastern digits to a JavaScript number.
|
|
583
|
+
* A convenience wrapper around convertEasternDigits + parseFloat.
|
|
584
|
+
*
|
|
585
|
+
* Returns NaN for strings that don't represent a valid number
|
|
586
|
+
* (same contract as parseFloat / Number).
|
|
587
|
+
*
|
|
588
|
+
* @param input A string possibly containing Eastern digits.
|
|
589
|
+
* @returns A JavaScript number, or NaN if parsing fails.
|
|
590
|
+
*
|
|
591
|
+
* @example
|
|
592
|
+
* toWesternNumber('١٢٣') // 123
|
|
593
|
+
* toWesternNumber('١٢٣٫٤٥') // 123.45
|
|
594
|
+
* toWesternNumber('۹٩') // 99 (mixed Arabic + Persian → still works)
|
|
595
|
+
* toWesternNumber('abc') // NaN
|
|
596
|
+
* toWesternNumber('') // NaN
|
|
597
|
+
*/
|
|
598
|
+
declare function toWesternNumber(input: string | null | undefined): number;
|
|
599
|
+
|
|
600
|
+
export { EASTERN_DIGIT_MAP, EASTERN_NUMERAL_REGEX, convertEasternDigits, digitsOnlyDirective, getDigitScript, hasEasternDigits, toWesternNumber };
|
|
601
|
+
export type { DigitScript, NamedPattern };
|