ngx-digits-only 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +336 -52
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,19 +1,16 @@
1
1
  # ngx-digits-only
2
2
 
3
- Dependency-free Angular directive for numeric-only inputs — a lightweight alternative to `ngx-mask` when all you need is digit filtering and formatting.
3
+ A dependency-free, standalone Angular directive for smart numeric inputs — digit filtering, formatting, and validation without pulling in a full masking library like `ngx-mask`.
4
4
 
5
- ## Features
5
+ ## What does it do?
6
6
 
7
- - Restricts input to digits only, with optional decimal and negative number support
8
- - Thousands separator formatting (configurable)
9
- - Prefix / suffix support (e.g. currency symbols, units)
10
- - Configurable decimal places
11
- - Min / max value validation
12
- - Persian / Arabic (Eastern) numeral conversion and display
13
- - Full RTL / LTR layout support
14
- - Implements `ControlValueAccessor` and `Validator` — works natively with Reactive Forms and Template-driven forms
15
- - Standalone directive — no NgModule required
16
- - Zero runtime dependencies
7
+ Turns any `<input>` into a smart numeric field:
8
+
9
+ - **Blocks** letters, symbols, and anything that isn't a digit — on every keystroke and paste
10
+ - **Formats** the display with thousands separators, prefix, suffix, and pattern masks
11
+ - **Strips** all display decoration before sending the value to your model
12
+ - **Validates** min, max, maxLength, and pattern completeness automatically
13
+ - **Converts** Arabic / Persian digits to Western digits silently
17
14
 
18
15
  ## Installation
19
16
 
@@ -21,68 +18,355 @@ Dependency-free Angular directive for numeric-only inputs — a lightweight alte
21
18
  npm install ngx-digits-only
22
19
  ```
23
20
 
24
- ## Usage
21
+ ## Quick start
25
22
 
26
- Import the directive directly into your standalone component:
23
+ This is a **standalone directive** no module to import, just the directive itself.
27
24
 
28
25
  ```typescript
29
26
  import { Component } from '@angular/core';
30
27
  import { ReactiveFormsModule } from '@angular/forms';
31
- import { DigitsOnlyDirective } from 'digits-only';
28
+ import { DigitsOnlyDirective } from 'ngx-digits-only';
32
29
 
33
30
  @Component({
34
- selector: 'app-example',
35
31
  standalone: true,
36
- imports: [ReactiveFormsModule, DigitsOnlyDirective],
37
- template: `
38
- <input
39
- digitsOnly
40
- [allowDecimal]="true"
41
- [decimalPlaces]="2"
42
- [allowNegative]="false"
43
- [thousandsSeparator]="true"
44
- [min]="0"
45
- [max]="1000000"
46
- [formControl]="amountControl"
47
- />
48
- `
32
+ imports: [
33
+ ReactiveFormsModule, // or FormsModule if you use ngModel
34
+ DigitsOnlyDirective,
35
+ ],
36
+ template: `<input digitsOnly formControlName="quantity" />`,
49
37
  })
50
- export class ExampleComponent {
51
- amountControl = new FormControl('');
52
- }
38
+ export class ExampleComponent {}
53
39
  ```
54
40
 
55
- ### In an NgModule-based app
56
-
57
- Standalone directives can still be used inside NgModule-based components — just add it to that component's own `imports` array if the component itself is standalone, or import it directly where the input lives:
41
+ In an NgModule-based app, standalone directives can be added directly to that module's `imports` array too:
58
42
 
59
43
  ```typescript
60
- @Component({
61
- standalone: true,
62
- imports: [DigitsOnlyDirective],
63
- ...
44
+ @NgModule({
45
+ imports: [
46
+ FormsModule,
47
+ ReactiveFormsModule,
48
+ DigitsOnlyDirective,
49
+ ],
50
+ declarations: [MyComponent],
64
51
  })
52
+ export class MyFeatureModule {}
65
53
  ```
66
54
 
67
- If your component is *not* standalone (declared in an `NgModule`), you can import the directive into that NgModule's `imports` array directly — standalone directives are valid entries there too.
55
+ ## Your first input
56
+
57
+ ```html
58
+ <input digitsOnly />
59
+ ```
68
60
 
69
- ## API
61
+ That's it. The field now only accepts digits `0–9`. Nothing else gets through.
70
62
 
71
- | Input | Type | Default | Description |
63
+ ```html
64
+ <input digitsOnly formControlName="quantity" />
65
+ <input digitsOnly [(ngModel)]="quantity" name="quantity" />
66
+ ```
67
+
68
+ ## All inputs at a glance
69
+
70
+ | Input | Type | Default | What it does |
72
71
  |---|---|---|---|
73
- | `allowDecimal` | `boolean` | `false` | Allow decimal point input |
74
- | `decimalPlaces` | `number` | `2` | Max digits after decimal point |
75
- | `allowNegative` | `boolean` | `false` | Allow leading minus sign |
76
- | `thousandsSeparator` | `boolean` | `false` | Format with thousands separators as user types |
77
- | `prefix` | `string` | `''` | Text prepended to the display value |
78
- | `suffix` | `string` | `''` | Text appended to the display value |
79
- | `min` | `number` | `undefined` | Minimum allowed value (adds validator) |
80
- | `max` | `number` | `undefined` | Maximum allowed value (adds validator) |
81
- | `easternNumerals` | `boolean` | `false` | Display Persian/Arabic-Indic digits instead of Western |
72
+ | `decimalPlaces` | `number` | `0` | How many decimal digits are allowed |
73
+ | `thousandSeparator` | `'' \| ',' \| '.' \| ' ' \| '_'` | `''` | Visual grouping character |
74
+ | `prefix` | `string` | `''` | Text shown before the number (e.g. `$`) |
75
+ | `suffix` | `string` | `''` | Text shown after the number (e.g. `%`) |
76
+ | `allowNegative` | `boolean` | `false` | Allow a leading minus sign |
77
+ | `leadingZeros` | `boolean` | `false` | Preserve leading zeros like `007` |
78
+ | `maxLength` | `number \| null` | `null` | Max raw digit count |
79
+ | `min` | `number \| null` | `null` | Minimum value (validation) |
80
+ | `max` | `number \| null` | `null` | Maximum value (validation) |
81
+ | `outputType` | `'number' \| 'string'` | `'number'` | Type emitted to the model |
82
+ | `pattern` | `NamedPattern \| string` | `''` | Display format mask |
83
+ | `convertEasternNumerals` | `boolean` | `true` | Convert Arabic/Persian digits |
84
+
85
+ ## decimalPlaces
86
+
87
+ Default `0` means integers only.
88
+
89
+ ```html
90
+ <input digitsOnly formControlName="quantity" />
91
+ <!-- user types: 1234 model: 1234 -->
92
+
93
+ <input digitsOnly [decimalPlaces]="2" formControlName="price" />
94
+ <!-- user types: 99.99 model: 99.99 -->
95
+ ```
96
+
97
+ A keystroke that would exceed the allowed decimal places is blocked outright.
98
+
99
+ ## thousandSeparator
100
+
101
+ Display-only formatting — the model always gets the plain number.
102
+
103
+ ```html
104
+ <input digitsOnly thousandSeparator="," formControlName="salary" />
105
+ <!-- display: 1,234,567 model: 1234567 -->
106
+
107
+ <input digitsOnly thousandSeparator="." [decimalPlaces]="2" formControlName="amount" />
108
+ <!-- display: 1.234.567,89 model: 1234567.89 -->
109
+ <!-- setting "." as the thousands separator auto-switches the decimal char to "," -->
110
+ ```
111
+
112
+ Also accepts `' '` (space) and `'_'` (underscore).
113
+
114
+ ## prefix and suffix
115
+
116
+ ```html
117
+ <input digitsOnly prefix="$" formControlName="price" />
118
+ <!-- display: $1234 model: 1234 -->
119
+
120
+ <input digitsOnly prefix="€" suffix=" EUR" [decimalPlaces]="2" formControlName="amount" />
121
+ <!-- display: €1,234.56 EUR model: 1234.56 -->
122
+ ```
123
+
124
+ Both are stripped before the value reaches your model.
125
+
126
+ ## allowNegative
127
+
128
+ ```html
129
+ <input digitsOnly [allowNegative]="true" formControlName="temperature" />
130
+ <!-- user can type: -42 model: -42 -->
131
+ ```
132
+
133
+ The minus sign is only valid as the first character, and works correctly even with `prefix` set.
134
+
135
+ ## leadingZeros
136
+
137
+ By default, leading zeros are stripped in number mode.
138
+
139
+ ```html
140
+ <input digitsOnly outputType="string" [leadingZeros]="true" [maxLength]="5" formControlName="zip" />
141
+ <!-- user types: 01234 model: '01234' (not 1234) -->
142
+ ```
143
+
144
+ When `pattern` is set, leading zeros are always preserved automatically.
145
+
146
+ ## maxLength
147
+
148
+ ```html
149
+ <input digitsOnly [maxLength]="5" formControlName="pin" />
150
+ ```
151
+
152
+ When `pattern` is set, `maxLength` is derived automatically from the number of `0` slots — any manual value is ignored.
153
+
154
+ ## min and max
155
+
156
+ Only apply in `outputType="number"` mode.
157
+
158
+ ```html
159
+ <input digitsOnly [min]="0" [max]="999" formControlName="score" #scoreCtrl="ngModel" />
160
+
161
+ <p *ngIf="scoreCtrl.errors?.['min']">Minimum value is {{ scoreCtrl.errors?.['min'].min }}</p>
162
+ <p *ngIf="scoreCtrl.errors?.['max']">Maximum value is {{ scoreCtrl.errors?.['max'].max }}</p>
163
+ ```
164
+
165
+ Error payloads: `{ min: 0, actual: -5 }` / `{ max: 999, actual: 1200 }`.
166
+
167
+ ## outputType — the most important decision
168
+
169
+ **`outputType="number"` (default)** — model gets a JS `number` or `null`. Use for prices, quantities, anything you'll do arithmetic with.
170
+
171
+ **`outputType="string"`** — model gets a JS `string` or `null`. Use for card numbers, phone numbers, postal codes, SSNs — identifiers, not quantities.
172
+
173
+ Why it matters:
174
+
175
+ - Leading zeros are lost in number mode: `01234` → `1234`
176
+ - 16-digit card numbers exceed `Number.MAX_SAFE_INTEGER` (`9007199254740991`) and get silently corrupted if stored as a number
177
+
178
+ ```typescript
179
+ // WRONG
180
+ cardNumber: number = 4111111111111111; // stored as 4111111111111112 — last digit wrong
181
+
182
+ // CORRECT
183
+ cardNumber: string = '4111111111111111'; // exact
184
+ ```
185
+
186
+ ## pattern — auto formatting
187
+
188
+ ```html
189
+ <input digitsOnly pattern="0000 0000 0000 0000" formControlName="card" />
190
+ <!-- display: 4111 1111 1111 1111 model: '4111111111111111' -->
191
+ ```
192
+
193
+ Setting `pattern` automatically:
194
+
195
+ | Effect | Why |
196
+ |---|---|
197
+ | Forces `outputType="string"` | Patterns are for identifiers, never arithmetic |
198
+ | Derives `maxLength` from slot count | No need to set it separately |
199
+ | Forces `leadingZeros="true"` | Every digit position is significant |
200
+ | Disables `thousandSeparator` | Patterns have their own separators |
201
+ | Disables `decimalPlaces` | Patterns are for integers/identifiers |
202
+ | Disables `allowNegative` | Identifiers are never negative |
203
+
204
+ ### Named patterns
205
+
206
+ | Name | Slots | Example |
207
+ |---|---|---|
208
+ | `card` | 16 | `4111 1111 1111 1111` |
209
+ | `card-amex` | 15 | `3782 822463 10005` |
210
+ | `expiry` | 4 | `12/26` |
211
+ | `cvv` | 3 | `123` |
212
+ | `cvv-amex` | 4 | `1234` |
213
+ | `phone-us` | 10 | `(555) 867-5309` |
214
+ | `ssn` | 9 | `123-45-6789` |
215
+ | `date` | 8 | `01/01/1990` |
216
+ | `time` | 4 | `09:30` |
217
+ | `sort-code` | 6 | `20-00-00` |
218
+ | `bsb` | 6 | `062-000` |
219
+
220
+ ```html
221
+ <input digitsOnly pattern="card" formControlName="cardNumber" />
222
+ <input digitsOnly pattern="expiry" formControlName="expiry" />
223
+ <input digitsOnly pattern="ssn" formControlName="ssn" />
224
+ ```
225
+
226
+ ### Custom patterns
227
+
228
+ Any string not in the named list is used as a raw pattern — `0` for a digit slot, anything else as a literal separator.
229
+
230
+ ```html
231
+ <input digitsOnly pattern="000 000" formControlName="otp" />
232
+ <!-- display: 123 456 model: '123456' -->
233
+
234
+ <input digitsOnly pattern="0000-0000-0000" formControlName="account" />
235
+ <!-- display: 1234-5678-9012 model: '123456789012' -->
236
+ ```
237
+
238
+ Separators are only inserted once more digits are coming, so you never get a trailing separator mid-type.
239
+
240
+ ## convertEasternNumerals
241
+
242
+ Arabic and Persian keyboards produce different Unicode digit characters:
243
+
244
+ | Script | Characters |
245
+ |---|---|
246
+ | Western | `0 1 2 3 4 5 6 7 8 9` |
247
+ | Arabic-Indic | `٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩` |
248
+ | Persian/Farsi | `۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹` |
249
+
250
+ By default (`convertEasternNumerals="true"`), both typing and pasting Eastern digits work transparently, with the Arabic decimal (`٫`) and thousands (`٬`) separators normalized automatically.
251
+
252
+ ```html
253
+ <input digitsOnly [convertEasternNumerals]="false" formControlName="code" />
254
+ <!-- Eastern digits now blocked — only ASCII 0-9 accepted -->
255
+ ```
256
+
257
+ Conversion is unicode-range-safe — it can never accidentally transform a comma, dollar sign, or letter.
258
+
259
+ ## Validation errors
260
+
261
+ Implements Angular's `Validator` interface — errors land on `control.errors` automatically.
262
+
263
+ | Error key | When it fires | Payload |
264
+ |---|---|---|
265
+ | `min` | Value below `[min]` | `{ min, actual }` |
266
+ | `max` | Value above `[max]` | `{ max, actual }` |
267
+ | `maxLength` | Raw digit count exceeds `[maxLength]` | `{ maxLength, actual }` |
268
+ | `patternIncomplete` | Pattern mode, not all slots filled | `{ required, actual, pattern }` |
269
+
270
+ ```html
271
+ <input digitsOnly pattern="card" formControlName="cardNumber" />
272
+ <p *ngIf="form.get('cardNumber')?.errors?.['patternIncomplete']">
273
+ Enter all {{ form.get('cardNumber')?.errors?.['patternIncomplete']?.required }} digits
274
+ </p>
275
+ ```
276
+
277
+ Directive errors merge with Angular's own validators (e.g. `Validators.required`) on the same `control.errors` object.
278
+
279
+ ## Reactive Forms example
280
+
281
+ ```typescript
282
+ this.form = this.fb.group({
283
+ cardNumber: [null, Validators.required],
284
+ expiry: [null, Validators.required],
285
+ cvv: [null, Validators.required],
286
+ amount: [null, Validators.required],
287
+ });
288
+ ```
289
+
290
+ ```html
291
+ <form [formGroup]="form" (ngSubmit)="pay()">
292
+ <input digitsOnly pattern="card" formControlName="cardNumber" placeholder="4111 1111 1111 1111" />
293
+ <input digitsOnly pattern="expiry" formControlName="expiry" placeholder="MM/YY" />
294
+ <input digitsOnly pattern="cvv" formControlName="cvv" placeholder="123" />
295
+ <input digitsOnly [decimalPlaces]="2" thousandSeparator="," prefix="$" [min]="0.01" formControlName="amount" />
296
+ <button type="submit" [disabled]="form.invalid">Pay</button>
297
+ </form>
298
+ ```
299
+
300
+ ```typescript
301
+ onSubmit() {
302
+ console.log(this.form.value);
303
+ // {
304
+ // cardNumber: '4111111111111111', // string, no spaces
305
+ // expiry: '1226', // string, no slash
306
+ // cvv: '123',
307
+ // amount: 49.99 // number
308
+ // }
309
+ }
310
+ ```
311
+
312
+ ## Type safety — NamedPattern
313
+
314
+ ```typescript
315
+ import { NamedPattern } from 'ngx-digits-only';
316
+
317
+ export class MyComponent {
318
+ selectedPattern: NamedPattern = 'card'; // autocomplete + compile-time typo checking
319
+ customPattern = '000-0000'; // custom strings still accepted
320
+ }
321
+ ```
322
+
323
+ ```html
324
+ <input digitsOnly [pattern]="selectedPattern" formControlName="id" />
325
+ ```
326
+
327
+ `NamedPattern` uses the `(string & {})` trick internally so IDE autocomplete for named patterns survives alongside free-form custom pattern strings.
328
+
329
+ ## Standalone Eastern numeral utilities
330
+
331
+ Zero-dependency functions, usable outside Angular entirely (React, Vue, Node, plain TS):
332
+
333
+ ```typescript
334
+ import { convertEasternDigits, hasEasternDigits, getDigitScript, toWesternNumber } from 'ngx-digits-only';
335
+
336
+ convertEasternDigits('١٢٣'); // '123'
337
+ hasEasternDigits('١٢٣'); // true
338
+ getDigitScript('۴۵۶'); // 'persian'
339
+ toWesternNumber('١٢٣٫٤٥'); // 123.45
340
+ ```
341
+
342
+ ## Common mistakes
343
+
344
+ **Using `number` for a card number** — exceeds `Number.MAX_SAFE_INTEGER` and corrupts digits. Use `outputType="string"` or, better, `pattern="card"` which handles it automatically.
345
+
346
+ **Setting `[maxLength]` alongside `pattern`** — ignored; pattern derives it from slot count.
347
+
348
+ **Expecting `min`/`max` with `outputType="string"`** — silently ignored in string mode; only works with `outputType="number"`.
349
+
350
+ **Forgetting `FormsModule`/`ReactiveFormsModule`** — required alongside `DigitsOnlyDirective` for `ngModel`/`formControlName` to work.
351
+
352
+ ## FAQ
353
+
354
+ **Can I combine `prefix`/`suffix` with `pattern`?** Yes — they wrap the formatted pattern display; the model still gets the raw digits.
355
+
356
+ **Can I pre-fill from the server?** Yes, via `fb.group()` initial values or `patchValue()` — `writeValue()` formats the display automatically.
357
+
358
+ **Why `null` instead of `0` for an empty field?** By design, to distinguish "user typed zero" from "user left it blank."
359
+
360
+ **Does it work with Angular signal-based forms?** Yes — it implements `ControlValueAccessor`, compatible with any Angular form API.
361
+
362
+ **Can I use it without a form?** Yes:
363
+ ```html
364
+ <input digitsOnly (input)="onInput($event.target.value)" />
365
+ ```
82
366
 
83
367
  ## Compatibility
84
368
 
85
- Requires Angular `>=16.0.0` (standalone APIs). Works in both standalone and NgModule-based applications.
369
+ Requires Angular `>=16.0.0`.
86
370
 
87
371
  ## License
88
372
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-digits-only",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=16.0.0",
6
6
  "@angular/core": ">=16.0.0"