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.
@@ -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 };