@dravishek/number-validate 1.2.0 → 1.2.2

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.
@@ -1,16 +1,12 @@
1
- import * as i1 from '@angular/common';
2
- import { DecimalPipe } from '@angular/common';
3
1
  import * as i0 from '@angular/core';
4
2
  import { input, HostListener, Optional, Directive } from '@angular/core';
5
- import * as i2 from '@angular/forms';
3
+ import * as i1 from '@angular/forms';
6
4
 
7
5
  class AdrNumberValidateDirective {
8
6
  el;
9
- decimalPipe;
10
7
  control;
11
- constructor(el, decimalPipe, control) {
8
+ constructor(el, control) {
12
9
  this.el = el;
13
- this.decimalPipe = decimalPipe;
14
10
  this.control = control;
15
11
  }
16
12
  /**
@@ -20,19 +16,32 @@ class AdrNumberValidateDirective {
20
16
  */
21
17
  adrNumberValidate = input('');
22
18
  previousValue = '';
23
- onKeyDown = (event) => this.execute(this.el.nativeElement.value);
24
- onPaste = (event) => this.execute(this.el.nativeElement.value);
25
- onFocus() {
26
- const input = this.el.nativeElement;
27
- input.value = input.value.replace(/,/g, '');
28
- }
29
- ngDoCheck() {
30
- const currentValue = this.el.nativeElement.value;
31
- if (currentValue !== this.previousValue) {
32
- this.execute(this.previousValue);
33
- this.previousValue = currentValue;
19
+ valueChangeSub;
20
+ onValueChange = (event) => this.execute(this.el.nativeElement.value);
21
+ /**
22
+ * Lifecycle hook that is called after data-bound properties of a directive are initialized.
23
+ * Subscribes to value changes of the associated form control, and executes custom logic
24
+ * whenever the value changes and is different from the previous value.
25
+ *
26
+ * @remarks
27
+ * This method sets up a subscription to the form control's `valueChanges` observable.
28
+ * When the value changes, it checks if the new value is different from the previous value.
29
+ * If so, it calls the `execute` method with the previous value and updates the `previousValue`.
30
+ */
31
+ ngOnInit() {
32
+ if (this.control?.control) {
33
+ this.valueChangeSub = this.control.control.valueChanges.subscribe((value) => {
34
+ const currentValue = value?.toString() ?? '';
35
+ if (currentValue !== this.previousValue) {
36
+ this.execute(this.previousValue);
37
+ this.previousValue = currentValue;
38
+ }
39
+ });
34
40
  }
35
41
  }
42
+ ngOnDestroy() {
43
+ this.valueChangeSub?.unsubscribe();
44
+ }
36
45
  /**
37
46
  * Validates the input string against a dynamically constructed regular expression
38
47
  * based on the ADR number format specification.
@@ -45,8 +54,8 @@ class AdrNumberValidateDirective {
45
54
  * @returns A `RegExpMatchArray` if the input matches the constructed regular expression,
46
55
  * or `null` if it does not match.
47
56
  */
48
- checkValue(value) {
49
- const [prefix, scale] = this.adrNumberValidate().split('.'), { regex, prefix: maxDigitsBeforeDecimal } = this.extractSignWithLength(prefix);
57
+ checkValue(value, prefix, scale) {
58
+ const { regex, prefix: maxDigitsBeforeDecimal } = this.extractSignWithLength(prefix);
50
59
  let regExpString = `^(${regex}{1,${maxDigitsBeforeDecimal}})`;
51
60
  if (+scale > 0) {
52
61
  regExpString += `(\\.{1}[\\d]{0,${+scale}})?`;
@@ -96,7 +105,7 @@ class AdrNumberValidateDirective {
96
105
  */
97
106
  execute(oldValue) {
98
107
  setTimeout(() => {
99
- const inputElement = this.el.nativeElement, currentValue = inputElement.value.replace(/,/g, ''), pattern = this.adrNumberValidate(), requiresNegative = pattern.startsWith('-'), requiresPositive = pattern.startsWith('+');
108
+ const inputElement = this.el.nativeElement, pattern = this.adrNumberValidate(), requiresNegative = pattern.startsWith('-'), requiresPositive = pattern.startsWith('+'), [prefix, scale] = pattern.split('.'), currentValue = this.formatNumber(inputElement.value.replace(/,/g, ''), +scale || 0).replace(/,/g, '');
100
109
  // Enforce sign rules:
101
110
  // - If negative is required, must start with '-'
102
111
  // - If positive is required, must NOT start with '-' (but '+' is optional)
@@ -110,18 +119,14 @@ class AdrNumberValidateDirective {
110
119
  return;
111
120
  }
112
121
  // Allow intermediate states like "-", "-.", "-12.", etc.
113
- const isIntermediate = /^-?$|^\.$|^-?\.$|^-?\d+\.$|^\d+\.$/.test(currentValue), isValid = this.checkValue(currentValue);
122
+ const isIntermediate = /^-?$|^\.$|^-?\.$|^-?\d+\.$|^\d+\.$/.test(currentValue), isValid = this.checkValue(currentValue, prefix, scale);
114
123
  if (currentValue === '' || isValid || isIntermediate) {
115
124
  this.previousValue = currentValue;
116
125
  const endsWithDot = currentValue.endsWith('.'), patchValue = currentValue === '' ? '' : (!endsWithDot && !isNaN(+currentValue)) ? +currentValue : currentValue;
117
126
  this.control?.control?.patchValue(patchValue);
118
127
  // Format view only if it's a valid number and not intermediate
119
128
  if (!isIntermediate && !isNaN(+currentValue)) {
120
- const [intPart, decimalPart] = currentValue.split('.');
121
- const formatted = this.decimalPipe.transform(intPart, '1.0-0');
122
- inputElement.value = decimalPart
123
- ? `${formatted}.${decimalPart}`
124
- : formatted ?? '';
129
+ inputElement.value = this.formatNumber(currentValue, +scale || 0);
125
130
  }
126
131
  else {
127
132
  inputElement.value = currentValue;
@@ -133,27 +138,26 @@ class AdrNumberValidateDirective {
133
138
  }
134
139
  });
135
140
  }
136
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AdrNumberValidateDirective, deps: [{ token: i0.ElementRef }, { token: i1.DecimalPipe }, { token: i2.NgControl, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
137
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.14", type: AdrNumberValidateDirective, isStandalone: true, selector: "[adrNumberValidate]", inputs: { adrNumberValidate: { classPropertyName: "adrNumberValidate", publicName: "adrNumberValidate", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown": "onKeyDown($event)", "paste": "onPaste($event)", "focus": "onFocus()" } }, providers: [DecimalPipe], ngImport: i0 });
141
+ formatNumber(value, scale = 0) {
142
+ return new Intl.NumberFormat('en-US', { minimumFractionDigits: 0, maximumFractionDigits: scale }).format(+value);
143
+ }
144
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AdrNumberValidateDirective, deps: [{ token: i0.ElementRef }, { token: i1.NgControl, optional: true }], target: i0.ɵɵFactoryTarget.Directive });
145
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.14", type: AdrNumberValidateDirective, isStandalone: true, selector: "[adrNumberValidate]", inputs: { adrNumberValidate: { classPropertyName: "adrNumberValidate", publicName: "adrNumberValidate", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "keydown": "onValueChange($event)", "paste": "onValueChange($event)" } }, ngImport: i0 });
138
146
  }
139
147
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AdrNumberValidateDirective, decorators: [{
140
148
  type: Directive,
141
149
  args: [{
142
150
  selector: '[adrNumberValidate]',
143
- standalone: true,
144
- providers: [DecimalPipe]
151
+ standalone: true
145
152
  }]
146
- }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.DecimalPipe }, { type: i2.NgControl, decorators: [{
153
+ }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i1.NgControl, decorators: [{
147
154
  type: Optional
148
- }] }], propDecorators: { onKeyDown: [{
155
+ }] }], propDecorators: { onValueChange: [{
149
156
  type: HostListener,
150
157
  args: ["keydown", ["$event"]]
151
- }], onPaste: [{
158
+ }, {
152
159
  type: HostListener,
153
160
  args: ["paste", ["$event"]]
154
- }], onFocus: [{
155
- type: HostListener,
156
- args: ['focus']
157
161
  }] } });
158
162
 
159
163
  /*
@@ -1 +1 @@
1
- {"version":3,"file":"dravishek-number-validate.mjs","sources":["../../../../projects/dravishek/number-validate/src/lib/adr-number-validate.directive.ts","../../../../projects/dravishek/number-validate/src/public-api.ts","../../../../projects/dravishek/number-validate/src/dravishek-number-validate.ts"],"sourcesContent":["import { DecimalPipe } from '@angular/common';\r\nimport { Directive, DoCheck, ElementRef, HostListener, input, Optional } from '@angular/core';\r\nimport { NgControl } from '@angular/forms';\r\n\r\n@Directive({\r\n selector: '[adrNumberValidate]',\r\n standalone: true,\r\n providers: [DecimalPipe]\r\n})\r\nexport class AdrNumberValidateDirective implements DoCheck {\r\n\r\n constructor(private el: ElementRef, private decimalPipe: DecimalPipe, @Optional() private control: NgControl) { }\r\n\r\n /**\r\n * @description Value before the decimal point specifies the number of digits before decimal and value after the decimal specifies the number of digits after decimal.\r\n * Example: 7.3 (Before decimal 7 digits & 3 digits after decimal)\r\n * Possible type of patterns allowed: X, X.X\r\n */\r\n adrNumberValidate = input<string>('');\r\n private previousValue: string = '';\r\n\r\n @HostListener(\"keydown\", [\"$event\"])\r\n onKeyDown = (event: KeyboardEvent) => this.execute(this.el.nativeElement.value);\r\n\r\n @HostListener(\"paste\", [\"$event\"])\r\n onPaste = (event: ClipboardEvent) => this.execute(this.el.nativeElement.value);\r\n\r\n @HostListener('focus')\r\n onFocus() {\r\n const input = this.el.nativeElement as HTMLInputElement;\r\n input.value = input.value.replace(/,/g, '');\r\n }\r\n\r\n ngDoCheck(): void {\r\n const currentValue = this.el.nativeElement.value;\r\n if (currentValue !== this.previousValue) {\r\n this.execute(this.previousValue);\r\n this.previousValue = currentValue;\r\n }\r\n }\r\n\r\n /**\r\n * Validates the input string against a dynamically constructed regular expression\r\n * based on the ADR number format specification.\r\n *\r\n * The format is determined by splitting the result of `adrNumberValidate()` into\r\n * a prefix and scale. The prefix is used to extract the allowed sign and digit length,\r\n * while the scale determines the number of digits allowed after the decimal point.\r\n *\r\n * @param value - The input string to validate.\r\n * @returns A `RegExpMatchArray` if the input matches the constructed regular expression,\r\n * or `null` if it does not match.\r\n */\r\n private checkValue(value: string): RegExpMatchArray | null {\r\n const [prefix, scale] = this.adrNumberValidate().split('.'), { regex, prefix: maxDigitsBeforeDecimal } = this.extractSignWithLength(prefix);\r\n let regExpString = `^(${regex}{1,${maxDigitsBeforeDecimal}})`;\r\n if (+scale > 0) {\r\n regExpString += `(\\\\.{1}[\\\\d]{0,${+scale}})?`;\r\n }\r\n const fullRegex = new RegExp(`${regExpString}$`);\r\n return value.match(fullRegex);\r\n }\r\n\r\n /**\r\n * Extracts the sign and digit length from a given prefix string and generates a corresponding regex pattern.\r\n *\r\n * The prefix should be in the format of an optional sign ('-' or '+') followed by one or more digits (e.g., \"-3\", \"+2\", \"5\").\r\n * The function returns an object containing:\r\n * - `regex`: A string representing the regex pattern for the sign and digit.\r\n * - `prefix`: The number of digits specified in the prefix.\r\n *\r\n * If the prefix does not match the expected format, a default regex pattern and prefix value are returned.\r\n *\r\n * @param prefix - The string containing an optional sign and digit count.\r\n * @returns An object with `regex` (string) and `prefix` (number) properties.\r\n */\r\n private extractSignWithLength(prefix: string) {\r\n const signMatch = prefix.match(/^([-+]?)(\\d+)$/);\r\n if (signMatch) {\r\n const sign = signMatch[1]; // \"-\" or \"+\"\r\n const digitCount = +signMatch[2];\r\n // Enforce mandatory sign if specified\r\n const signRegexMap: { [key: string]: string } = { '-': '\\\\-', '+': '\\\\+?' },\r\n signRegex = signRegexMap[sign] ?? '[-+]?';\r\n return { regex: `${signRegex}\\\\d`, prefix: digitCount };\r\n }\r\n return { regex: '[-+]?\\\\d', prefix: 0 };\r\n }\r\n\r\n /**\r\n * Handles input validation and value patching for ADR number fields.\r\n *\r\n * This method enforces sign rules based on the validation pattern:\r\n * - If a negative sign is required, the value must start with '-'.\r\n * - If a positive sign is required, the value must not start with '-'.\r\n * - If no sign is required, any value is allowed.\r\n *\r\n * The method allows intermediate input states (e.g., \"-\", \"-.\", \"-12.\") to support user typing.\r\n * If the input does not meet the required sign rules or is invalid, it reverts to the previous value.\r\n * Otherwise, it patches the control value with the parsed number or the current string value,\r\n * preserving intermediate states and empty input.\r\n * It also formats the input value with thousand separators when appropriate.\r\n *\r\n * @param oldValue - The previous valid value of the input, used to revert in case of invalid input.\r\n */\r\n private execute(oldValue: string) {\r\n setTimeout(() => {\r\n const inputElement = this.el.nativeElement as HTMLInputElement, currentValue: string = inputElement.value.replace(/,/g, ''),\r\n pattern = this.adrNumberValidate(), requiresNegative = pattern.startsWith('-'), requiresPositive = pattern.startsWith('+');\r\n // Enforce sign rules:\r\n // - If negative is required, must start with '-'\r\n // - If positive is required, must NOT start with '-' (but '+' is optional)\r\n const hasRequiredSign =\r\n (!requiresNegative && !requiresPositive) ||\r\n (requiresNegative && currentValue.startsWith('-')) ||\r\n (requiresPositive && !currentValue.startsWith('-'));\r\n // If the sign is missing and required, reject immediately\r\n if (!hasRequiredSign && currentValue !== '') {\r\n this.control?.control?.patchValue(0);\r\n inputElement.value = '';\r\n return;\r\n }\r\n // Allow intermediate states like \"-\", \"-.\", \"-12.\", etc.\r\n const isIntermediate = /^-?$|^\\.$|^-?\\.$|^-?\\d+\\.$|^\\d+\\.$/.test(currentValue), isValid = this.checkValue(currentValue);\r\n if (currentValue === '' || isValid || isIntermediate) {\r\n this.previousValue = currentValue;\r\n const endsWithDot = currentValue.endsWith('.'),\r\n patchValue = currentValue === '' ? '' : (!endsWithDot && !isNaN(+currentValue)) ? +currentValue : currentValue;\r\n this.control?.control?.patchValue(patchValue);\r\n // Format view only if it's a valid number and not intermediate\r\n if (!isIntermediate && !isNaN(+currentValue)) {\r\n const [intPart, decimalPart] = currentValue.split('.');\r\n const formatted = this.decimalPipe.transform(intPart, '1.0-0');\r\n inputElement.value = decimalPart\r\n ? `${formatted}.${decimalPart}`\r\n : formatted ?? '';\r\n } else {\r\n inputElement.value = currentValue;\r\n }\r\n } else {\r\n inputElement.value = oldValue;\r\n this.control?.control?.patchValue(isNaN(+oldValue) ? oldValue : +oldValue);\r\n }\r\n });\r\n }\r\n}\r\n","/*\r\n * Public API Surface of number-validate\r\n */\r\n\r\nexport * from './lib/adr-number-validate.directive';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dravishek-number-validate.mjs","sources":["../../../../projects/dravishek/number-validate/src/lib/adr-number-validate.directive.ts","../../../../projects/dravishek/number-validate/src/public-api.ts","../../../../projects/dravishek/number-validate/src/dravishek-number-validate.ts"],"sourcesContent":["import { Directive, ElementRef, HostListener, input, OnDestroy, OnInit, Optional } from '@angular/core';\r\nimport { NgControl } from '@angular/forms';\r\nimport { Subscription } from 'rxjs';\r\n\r\n@Directive({\r\n selector: '[adrNumberValidate]',\r\n standalone: true\r\n})\r\nexport class AdrNumberValidateDirective implements OnInit, OnDestroy {\r\n\r\n constructor(private el: ElementRef, @Optional() private control: NgControl) { }\r\n\r\n /**\r\n * @description Value before the decimal point specifies the number of digits before decimal and value after the decimal specifies the number of digits after decimal.\r\n * Example: 7.3 (Before decimal 7 digits & 3 digits after decimal)\r\n * Possible type of patterns allowed: X, X.X\r\n */\r\n adrNumberValidate = input<string>('');\r\n private previousValue: string = '';\r\n private valueChangeSub?: Subscription;\r\n\r\n @HostListener(\"keydown\", [\"$event\"])\r\n @HostListener(\"paste\", [\"$event\"])\r\n onValueChange = (event: KeyboardEvent) => this.execute(this.el.nativeElement.value);\r\n\r\n /**\r\n * Lifecycle hook that is called after data-bound properties of a directive are initialized.\r\n * Subscribes to value changes of the associated form control, and executes custom logic\r\n * whenever the value changes and is different from the previous value.\r\n *\r\n * @remarks\r\n * This method sets up a subscription to the form control's `valueChanges` observable.\r\n * When the value changes, it checks if the new value is different from the previous value.\r\n * If so, it calls the `execute` method with the previous value and updates the `previousValue`.\r\n */\r\n ngOnInit(): void {\r\n if (this.control?.control) {\r\n this.valueChangeSub = this.control.control.valueChanges.subscribe(\r\n (value: any) => {\r\n const currentValue = value?.toString() ?? '';\r\n if (currentValue !== this.previousValue) {\r\n this.execute(this.previousValue);\r\n this.previousValue = currentValue;\r\n }\r\n }\r\n );\r\n }\r\n }\r\n\r\n ngOnDestroy(): void {\r\n this.valueChangeSub?.unsubscribe();\r\n }\r\n\r\n /**\r\n * Validates the input string against a dynamically constructed regular expression\r\n * based on the ADR number format specification.\r\n *\r\n * The format is determined by splitting the result of `adrNumberValidate()` into\r\n * a prefix and scale. The prefix is used to extract the allowed sign and digit length,\r\n * while the scale determines the number of digits allowed after the decimal point.\r\n *\r\n * @param value - The input string to validate.\r\n * @returns A `RegExpMatchArray` if the input matches the constructed regular expression,\r\n * or `null` if it does not match.\r\n */\r\n private checkValue(value: string, prefix: string, scale: string): RegExpMatchArray | null {\r\n const { regex, prefix: maxDigitsBeforeDecimal } = this.extractSignWithLength(prefix);\r\n let regExpString = `^(${regex}{1,${maxDigitsBeforeDecimal}})`;\r\n if (+scale > 0) {\r\n regExpString += `(\\\\.{1}[\\\\d]{0,${+scale}})?`;\r\n }\r\n const fullRegex = new RegExp(`${regExpString}$`);\r\n return value.match(fullRegex);\r\n }\r\n\r\n /**\r\n * Extracts the sign and digit length from a given prefix string and generates a corresponding regex pattern.\r\n *\r\n * The prefix should be in the format of an optional sign ('-' or '+') followed by one or more digits (e.g., \"-3\", \"+2\", \"5\").\r\n * The function returns an object containing:\r\n * - `regex`: A string representing the regex pattern for the sign and digit.\r\n * - `prefix`: The number of digits specified in the prefix.\r\n *\r\n * If the prefix does not match the expected format, a default regex pattern and prefix value are returned.\r\n *\r\n * @param prefix - The string containing an optional sign and digit count.\r\n * @returns An object with `regex` (string) and `prefix` (number) properties.\r\n */\r\n private extractSignWithLength(prefix: string) {\r\n const signMatch = prefix.match(/^([-+]?)(\\d+)$/);\r\n if (signMatch) {\r\n const sign = signMatch[1]; // \"-\" or \"+\"\r\n const digitCount = +signMatch[2];\r\n // Enforce mandatory sign if specified\r\n const signRegexMap: { [key: string]: string } = { '-': '\\\\-', '+': '\\\\+?' },\r\n signRegex = signRegexMap[sign] ?? '[-+]?';\r\n return { regex: `${signRegex}\\\\d`, prefix: digitCount };\r\n }\r\n return { regex: '[-+]?\\\\d', prefix: 0 };\r\n }\r\n\r\n /**\r\n * Handles input validation and value patching for ADR number fields.\r\n *\r\n * This method enforces sign rules based on the validation pattern:\r\n * - If a negative sign is required, the value must start with '-'.\r\n * - If a positive sign is required, the value must not start with '-'.\r\n * - If no sign is required, any value is allowed.\r\n *\r\n * The method allows intermediate input states (e.g., \"-\", \"-.\", \"-12.\") to support user typing.\r\n * If the input does not meet the required sign rules or is invalid, it reverts to the previous value.\r\n * Otherwise, it patches the control value with the parsed number or the current string value,\r\n * preserving intermediate states and empty input.\r\n * It also formats the input value with thousand separators when appropriate.\r\n *\r\n * @param oldValue - The previous valid value of the input, used to revert in case of invalid input.\r\n */\r\n private execute(oldValue: string) {\r\n setTimeout(() => {\r\n const inputElement = this.el.nativeElement as HTMLInputElement, pattern = this.adrNumberValidate(),\r\n requiresNegative = pattern.startsWith('-'), requiresPositive = pattern.startsWith('+'),\r\n [prefix, scale] = pattern.split('.'), currentValue: string = this.formatNumber(inputElement.value.replace(/,/g, ''), +scale || 0).replace(/,/g, '');\r\n // Enforce sign rules:\r\n // - If negative is required, must start with '-'\r\n // - If positive is required, must NOT start with '-' (but '+' is optional)\r\n const hasRequiredSign =\r\n (!requiresNegative && !requiresPositive) ||\r\n (requiresNegative && currentValue.startsWith('-')) ||\r\n (requiresPositive && !currentValue.startsWith('-'));\r\n // If the sign is missing and required, reject immediately\r\n if (!hasRequiredSign && currentValue !== '') {\r\n this.control?.control?.patchValue(0);\r\n inputElement.value = '';\r\n return;\r\n }\r\n // Allow intermediate states like \"-\", \"-.\", \"-12.\", etc.\r\n const isIntermediate = /^-?$|^\\.$|^-?\\.$|^-?\\d+\\.$|^\\d+\\.$/.test(currentValue), isValid = this.checkValue(currentValue, prefix, scale);\r\n if (currentValue === '' || isValid || isIntermediate) {\r\n this.previousValue = currentValue;\r\n const endsWithDot = currentValue.endsWith('.'),\r\n patchValue = currentValue === '' ? '' : (!endsWithDot && !isNaN(+currentValue)) ? +currentValue : currentValue;\r\n this.control?.control?.patchValue(patchValue);\r\n // Format view only if it's a valid number and not intermediate\r\n if (!isIntermediate && !isNaN(+currentValue)) {\r\n inputElement.value = this.formatNumber(currentValue, +scale || 0);\r\n } else {\r\n inputElement.value = currentValue;\r\n }\r\n } else {\r\n inputElement.value = oldValue;\r\n this.control?.control?.patchValue(isNaN(+oldValue) ? oldValue : +oldValue);\r\n }\r\n });\r\n }\r\n\r\n private formatNumber(value: string, scale = 0): string {\r\n return new Intl.NumberFormat('en-US', { minimumFractionDigits: 0, maximumFractionDigits: scale }).format(+value);\r\n }\r\n}\r\n","/*\r\n * Public API Surface of number-validate\r\n */\r\n\r\nexport * from './lib/adr-number-validate.directive';\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
@@ -1,12 +1,10 @@
1
- import { DecimalPipe } from '@angular/common';
2
- import { DoCheck, ElementRef } from '@angular/core';
1
+ import { ElementRef, OnDestroy, OnInit } from '@angular/core';
3
2
  import { NgControl } from '@angular/forms';
4
3
  import * as i0 from "@angular/core";
5
- export declare class AdrNumberValidateDirective implements DoCheck {
4
+ export declare class AdrNumberValidateDirective implements OnInit, OnDestroy {
6
5
  private el;
7
- private decimalPipe;
8
6
  private control;
9
- constructor(el: ElementRef, decimalPipe: DecimalPipe, control: NgControl);
7
+ constructor(el: ElementRef, control: NgControl);
10
8
  /**
11
9
  * @description Value before the decimal point specifies the number of digits before decimal and value after the decimal specifies the number of digits after decimal.
12
10
  * Example: 7.3 (Before decimal 7 digits & 3 digits after decimal)
@@ -14,10 +12,20 @@ export declare class AdrNumberValidateDirective implements DoCheck {
14
12
  */
15
13
  adrNumberValidate: import("@angular/core").InputSignal<string>;
16
14
  private previousValue;
17
- onKeyDown: (event: KeyboardEvent) => void;
18
- onPaste: (event: ClipboardEvent) => void;
19
- onFocus(): void;
20
- ngDoCheck(): void;
15
+ private valueChangeSub?;
16
+ onValueChange: (event: KeyboardEvent) => void;
17
+ /**
18
+ * Lifecycle hook that is called after data-bound properties of a directive are initialized.
19
+ * Subscribes to value changes of the associated form control, and executes custom logic
20
+ * whenever the value changes and is different from the previous value.
21
+ *
22
+ * @remarks
23
+ * This method sets up a subscription to the form control's `valueChanges` observable.
24
+ * When the value changes, it checks if the new value is different from the previous value.
25
+ * If so, it calls the `execute` method with the previous value and updates the `previousValue`.
26
+ */
27
+ ngOnInit(): void;
28
+ ngOnDestroy(): void;
21
29
  /**
22
30
  * Validates the input string against a dynamically constructed regular expression
23
31
  * based on the ADR number format specification.
@@ -62,6 +70,7 @@ export declare class AdrNumberValidateDirective implements DoCheck {
62
70
  * @param oldValue - The previous valid value of the input, used to revert in case of invalid input.
63
71
  */
64
72
  private execute;
65
- static ɵfac: i0.ɵɵFactoryDeclaration<AdrNumberValidateDirective, [null, null, { optional: true; }]>;
73
+ private formatNumber;
74
+ static ɵfac: i0.ɵɵFactoryDeclaration<AdrNumberValidateDirective, [null, { optional: true; }]>;
66
75
  static ɵdir: i0.ɵɵDirectiveDeclaration<AdrNumberValidateDirective, "[adrNumberValidate]", never, { "adrNumberValidate": { "alias": "adrNumberValidate"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
67
76
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dravishek/number-validate",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "keywords": [
5
5
  "angular",
6
6
  "javascript",