diff --git a/CHANGELOG.md b/CHANGELOG.md
index 360cc8cb..23168c07 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,15 @@
+# 19.0.5(2024-12-17)
+
+### Fix
+
+- Fix ([#1481](https://github.com/JsDaddy/ngx-mask/issues/1481))
+- Fix ([#1411](https://github.com/JsDaddy/ngx-mask/issues/1411))
+
# 19.0.4(2024-12-13)
### Feature
-- add input property instantPrefix
+- add input property instantPrefix
### Fix
diff --git a/USAGE.md b/USAGE.md
index 9e0932f8..cfa80741 100644
--- a/USAGE.md
+++ b/USAGE.md
@@ -132,7 +132,6 @@ pattern = {
You can add prefix to you masked value
-
#### Usage
```html
@@ -150,10 +149,9 @@ When set to false, the prefix only becomes visible when a value is present in th
```html
-
+
```
-
### suffix (string)
You can add suffix to you masked value
diff --git a/bun.lockb b/bun.lockb
index 1f5f9289..7da869d5 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/package.json b/package.json
index 435286ae..947bed2f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ngx-mask",
- "version": "19.0.4",
+ "version": "19.0.5",
"description": "Awesome ngx mask",
"license": "MIT",
"engines": {
@@ -60,67 +60,67 @@
"url": "https://github.com/JsDaddy/ngx-mask.git"
},
"dependencies": {
- "@angular/animations": "19.0.3",
- "@angular/common": "19.0.3",
- "@angular/compiler": "19.0.3",
- "@angular/core": "19.0.3",
- "@angular/forms": "19.0.3",
- "@angular/platform-browser": "19.0.3",
- "@angular/platform-browser-dynamic": "19.0.3",
- "@angular/router": "19.0.3",
+ "@angular/animations": "19.0.4",
+ "@angular/common": "19.0.4",
+ "@angular/compiler": "19.0.4",
+ "@angular/core": "19.0.4",
+ "@angular/forms": "19.0.4",
+ "@angular/platform-browser": "19.0.4",
+ "@angular/platform-browser-dynamic": "19.0.4",
+ "@angular/router": "19.0.4",
"@types/jest": "^29.5.14",
"@types/mocha": "^10.0.10",
"ajv": "^8.17.1",
"cypress": "^13.16.1",
- "highlight.js": "11.10.0",
+ "highlight.js": "11.11.0",
"ngx-highlightjs": "12.0.0",
- "ngxtension": "^4.1.0",
+ "ngxtension": "^4.2.0",
"rxjs": "7.8.1",
"semantic-release": "24.2.0",
"semantic-release-export-data": "^1.1.0",
- "snyk": "^1.1294.2"
+ "snyk": "^1.1294.3"
},
"devDependencies": {
- "@angular-devkit/build-angular": "19.0.3",
- "@angular-eslint/builder": "19.0.0",
- "@angular-eslint/eslint-plugin": "19.0.0",
- "@angular-eslint/eslint-plugin-template": "19.0.0",
- "@angular-eslint/schematics": "19.0.0",
- "@angular-eslint/template-parser": "19.0.0",
- "@angular/cli": "19.0.3",
- "@angular/compiler-cli": "19.0.3",
- "@angular/language-service": "19.0.3",
- "@commitlint/cli": "19.6.0",
+ "@angular-devkit/build-angular": "19.0.5",
+ "@angular-eslint/builder": "19.0.2",
+ "@angular-eslint/eslint-plugin": "19.0.2",
+ "@angular-eslint/eslint-plugin-template": "19.0.2",
+ "@angular-eslint/schematics": "19.0.2",
+ "@angular-eslint/template-parser": "19.0.2",
+ "@angular/cli": "19.0.5",
+ "@angular/compiler-cli": "19.0.4",
+ "@angular/language-service": "19.0.4",
+ "@commitlint/cli": "19.6.1",
"@commitlint/config-conventional": "19.6.0",
"@jscutlery/cypress-angular": "^0.9.22",
"@types/highlight.js": "9.12.4",
"@types/jasmine": "5.1.5",
- "@types/node": "22.10.1",
- "@typescript-eslint/eslint-plugin": "8.17.0",
- "@typescript-eslint/parser": "8.17.0",
+ "@types/node": "22.10.2",
+ "@typescript-eslint/eslint-plugin": "8.18.1",
+ "@typescript-eslint/parser": "8.18.1",
"@web/test-runner": "^0.19.0",
"angular-cli-ghpages": "2.0.3",
"angular-http-server": "1.12.0",
- "eslint": "9.16.0",
+ "eslint": "9.17.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-json": "4.0.1",
"eslint-plugin-prettier": "5.2.1",
"jasmine-core": "5.5.0",
"jasmine-spec-reporter": "7.0.0",
- "lint-staged": "15.2.10",
+ "lint-staged": "15.2.11",
"markdownlint-cli": "0.43.0",
"ng-packagr": "19.0.1",
"npm-check-updates": "^17.1.11",
"prettier": "3.4.2",
- "puppeteer": "23.10.1",
- "stylelint": "16.11.0",
+ "puppeteer": "23.10.4",
+ "stylelint": "16.12.0",
"stylelint-config-prettier": "9.0.5",
"stylelint-config-recommended-scss": "14.1.0",
"stylelint-prettier": "5.0.2",
"type-coverage": "^2.29.7",
"typescript": "5.6.3",
- "angular-eslint": "^19.0.0",
- "typescript-eslint": "^8.17.0",
+ "angular-eslint": "^19.0.2",
+ "typescript-eslint": "^8.18.1",
"tailwindcss": "^3.4.16",
"bun-types": "^1.1.38",
"postcss": "8.4.49",
diff --git a/projects/ngx-mask-lib/package.json b/projects/ngx-mask-lib/package.json
index c78fd945..7ce1935b 100644
--- a/projects/ngx-mask-lib/package.json
+++ b/projects/ngx-mask-lib/package.json
@@ -1,6 +1,6 @@
{
"name": "ngx-mask",
- "version": "19.0.4",
+ "version": "19.0.5",
"description": "awesome ngx mask",
"keywords": [
"ng2-mask",
diff --git a/projects/ngx-mask-lib/src/lib/ngx-mask-applier.service.ts b/projects/ngx-mask-lib/src/lib/ngx-mask-applier.service.ts
index 529f920d..b5ab3521 100644
--- a/projects/ngx-mask-lib/src/lib/ngx-mask-applier.service.ts
+++ b/projects/ngx-mask-lib/src/lib/ngx-mask-applier.service.ts
@@ -197,11 +197,62 @@ export class NgxMaskApplierService {
}
const precision: number = this.getPrecision(maskExpression);
- const decimalMarker = Array.isArray(this.decimalMarker)
- ? this.thousandSeparator === MaskExpression.DOT
- ? MaskExpression.COMMA
- : MaskExpression.DOT
- : this.decimalMarker;
+ let decimalMarker = this.decimalMarker;
+
+ if (Array.isArray(this.decimalMarker)) {
+ const marker = this.decimalMarker.find((dm) => dm !== this.thousandSeparator);
+
+ decimalMarker = marker
+ ? marker
+ : this.actualValue.includes(this.decimalMarker[0])
+ ? this.decimalMarker[0]
+ : this.decimalMarker[1];
+ }
+
+ if (backspaced) {
+ const { decimalMarkerIndex, nonZeroIndex } = this._findFirstNonZeroAndDecimalIndex(
+ processedValue,
+ decimalMarker as '.' | ','
+ );
+ const zeroIndexMinus = processedValue[0] === MaskExpression.MINUS;
+ const zeroIndexNumberZero = processedValue[0] === MaskExpression.NUMBER_ZERO;
+ const zeroIndexDecimalMarker = processedValue[0] === decimalMarker;
+ const firstIndexDecimalMarker = processedValue[1] === decimalMarker;
+
+ if (
+ (zeroIndexDecimalMarker && !nonZeroIndex) ||
+ (zeroIndexMinus && firstIndexDecimalMarker && !nonZeroIndex) ||
+ (zeroIndexNumberZero && !decimalMarkerIndex && !nonZeroIndex)
+ ) {
+ processedValue = MaskExpression.NUMBER_ZERO;
+ }
+
+ if (
+ decimalMarkerIndex &&
+ nonZeroIndex &&
+ zeroIndexMinus &&
+ processedPosition === 1
+ ) {
+ if (decimalMarkerIndex < nonZeroIndex || decimalMarkerIndex > nonZeroIndex) {
+ processedValue = MaskExpression.MINUS + processedValue.slice(nonZeroIndex);
+ }
+ }
+
+ if (!decimalMarkerIndex && nonZeroIndex && processedValue.length > nonZeroIndex) {
+ processedValue = zeroIndexMinus
+ ? MaskExpression.MINUS + processedValue.slice(nonZeroIndex)
+ : processedValue.slice(nonZeroIndex);
+ }
+
+ if (decimalMarkerIndex && nonZeroIndex && processedPosition === 0) {
+ if (decimalMarkerIndex < nonZeroIndex) {
+ processedValue = processedValue.slice(decimalMarkerIndex - 1);
+ }
+ if (decimalMarkerIndex > nonZeroIndex) {
+ processedValue = processedValue.slice(nonZeroIndex);
+ }
+ }
+ }
if (precision === 0) {
processedValue = this.allowNegativeNumbers
@@ -240,7 +291,8 @@ export class NgxMaskApplierService {
if (
processedValue[0] === MaskExpression.NUMBER_ZERO &&
processedValue[1] !== decimalMarker &&
- processedValue[1] !== this.thousandSeparator
+ processedValue[1] !== this.thousandSeparator &&
+ !backspaced
) {
processedValue =
processedValue.length > 1
@@ -252,6 +304,7 @@ export class NgxMaskApplierService {
}
if (
this.allowNegativeNumbers &&
+ !backspaced &&
processedValue[0] === MaskExpression.MINUS &&
(processedValue[1] === decimalMarker ||
processedValue[1] === MaskExpression.NUMBER_ZERO)
@@ -272,61 +325,6 @@ export class NgxMaskApplierService {
}
}
- if (backspaced) {
- const inputValueAfterZero = processedValue.slice(
- this._findFirstNonZeroDigitIndex(processedValue),
- processedValue.length
- );
- const positionOfZeroOrDecimalMarker =
- processedValue[processedPosition] === MaskExpression.NUMBER_ZERO ||
- processedValue[processedPosition] === decimalMarker;
- const zeroIndexNumberZero = processedValue[0] === MaskExpression.NUMBER_ZERO;
- const firstIndexNumberZero = processedValue[1] === MaskExpression.NUMBER_ZERO;
- const zeroIndexMinus = processedValue[0] === MaskExpression.MINUS;
- const zeroIndexThousand = processedValue[0] === this.thousandSeparator;
- const firstIndexDecimalMarker = processedValue[1] === decimalMarker;
- const zeroIndexDecimalMarker = processedValue[0] === decimalMarker;
- const secondIndexDecimalMarker = processedValue[2] === decimalMarker;
-
- if (zeroIndexNumberZero && firstIndexDecimalMarker && processedPosition === 0) {
- return processedValue;
- }
-
- if (zeroIndexDecimalMarker && processedPosition === 0) {
- processedValue = inputValueAfterZero;
- }
-
- if (
- zeroIndexNumberZero &&
- firstIndexDecimalMarker &&
- positionOfZeroOrDecimalMarker &&
- processedPosition < 2
- ) {
- processedValue = inputValueAfterZero;
- }
- if (
- zeroIndexMinus &&
- firstIndexNumberZero &&
- secondIndexDecimalMarker &&
- positionOfZeroOrDecimalMarker &&
- processedPosition < 3
- ) {
- processedValue = MaskExpression.MINUS + inputValueAfterZero;
- }
-
- if (
- inputValueAfterZero !== MaskExpression.MINUS &&
- ((processedPosition === 0 && (zeroIndexNumberZero || zeroIndexThousand)) ||
- (this.allowNegativeNumbers &&
- processedPosition === 1 &&
- zeroIndexMinus &&
- !firstIndexNumberZero))
- ) {
- processedValue = zeroIndexMinus
- ? MaskExpression.MINUS + inputValueAfterZero
- : inputValueAfterZero;
- }
- }
// TODO: we had different rexexps here for the different cases... but tests dont seam to bother - check this
// separator: no COMMA, dot-sep: no SPACE, COMMA OK, comma-sep: no SPACE, COMMA OK
@@ -1036,13 +1034,29 @@ export class NgxMaskApplierService {
}
}
- private _findFirstNonZeroDigitIndex(inputString: string): number {
+ private _findFirstNonZeroAndDecimalIndex(inputString: string, decimalMarker: '.' | ',') {
+ let decimalMarkerIndex: number | null = null;
+ let nonZeroIndex: number | null = null;
+
for (let i = 0; i < inputString.length; i++) {
const char = inputString[i];
- if (char && char >= '1' && char <= '9') {
- return i;
+
+ if (char === decimalMarker && decimalMarkerIndex === null) {
+ decimalMarkerIndex = i;
+ }
+
+ if (char && char >= '1' && char <= '9' && nonZeroIndex === null) {
+ nonZeroIndex = i;
+ }
+
+ if (decimalMarkerIndex !== null && nonZeroIndex !== null) {
+ break;
}
}
- return -1;
+
+ return {
+ decimalMarkerIndex,
+ nonZeroIndex,
+ };
}
}
diff --git a/projects/ngx-mask-lib/src/lib/ngx-mask.directive.ts b/projects/ngx-mask-lib/src/lib/ngx-mask.directive.ts
index 6cb07c9f..0a8591ac 100644
--- a/projects/ngx-mask-lib/src/lib/ngx-mask.directive.ts
+++ b/projects/ngx-mask-lib/src/lib/ngx-mask.directive.ts
@@ -161,6 +161,32 @@ export class NgxMaskDirective implements ControlValueAccessor, OnChanges, Valida
}
if (thousandSeparator) {
this._maskService.thousandSeparator = thousandSeparator.currentValue;
+ if (thousandSeparator.previousValue && thousandSeparator.currentValue) {
+ const previousDecimalMarker = this._maskService.decimalMarker;
+
+ if (thousandSeparator.currentValue === this._maskService.decimalMarker) {
+ this._maskService.decimalMarker =
+ thousandSeparator.currentValue === MaskExpression.COMMA
+ ? MaskExpression.DOT
+ : MaskExpression.COMMA;
+ }
+ if (this._maskService.dropSpecialCharacters === true) {
+ this._maskService.specialCharacters = this._config.specialCharacters;
+ }
+ if (
+ typeof previousDecimalMarker === 'string' &&
+ typeof this._maskService.decimalMarker === 'string'
+ ) {
+ this._inputValue.set(
+ this._inputValue()
+ .split(thousandSeparator.previousValue)
+ .join('')
+ .replace(previousDecimalMarker, this._maskService.decimalMarker)
+ );
+ this._maskService.actualValue = this._inputValue();
+ }
+ this._maskService.writingValue = true;
+ }
}
if (decimalMarker) {
this._maskService.decimalMarker = decimalMarker.currentValue;
diff --git a/projects/ngx-mask-lib/src/lib/ngx-mask.service.ts b/projects/ngx-mask-lib/src/lib/ngx-mask.service.ts
index 064e88a1..bd95795c 100644
--- a/projects/ngx-mask-lib/src/lib/ngx-mask.service.ts
+++ b/projects/ngx-mask-lib/src/lib/ngx-mask.service.ts
@@ -611,6 +611,7 @@ export class NgxMaskService extends NgxMaskApplierService {
)
)
: '';
+ this.writingValue = false;
this.maskChanged = false;
return;
}
diff --git a/projects/ngx-mask-lib/src/test/allow-negative-numbers.spec.ts b/projects/ngx-mask-lib/src/test/allow-negative-numbers.spec.ts
index 5eb8bf00..9a89808b 100644
--- a/projects/ngx-mask-lib/src/test/allow-negative-numbers.spec.ts
+++ b/projects/ngx-mask-lib/src/test/allow-negative-numbers.spec.ts
@@ -41,8 +41,8 @@ describe('Directive: Mask (Allow negative numbers)', () => {
component.dropSpecialCharacters.set(true);
component.form.setValue(-123456);
- equal('-123456.00', '-123,456.00', fixture);
expect(component.form.value).toBe(-123456);
+ equal('-123456.00', '-123,456.00', fixture);
});
it('allowNegativeNumber to mask', () => {
diff --git a/projects/ngx-mask-lib/src/test/separator.cy-spec.ts b/projects/ngx-mask-lib/src/test/separator.cy-spec.ts
index 4ae25843..a373041d 100644
--- a/projects/ngx-mask-lib/src/test/separator.cy-spec.ts
+++ b/projects/ngx-mask-lib/src/test/separator.cy-spec.ts
@@ -297,7 +297,7 @@ describe('Test Date Hh:m0', () => {
.type('{leftArrow}'.repeat(2))
.type('{backspace}')
.should('have.value', '-0.14')
- .type('{leftArrow}'.repeat(2))
+ .type('{leftArrow}')
.type('{backspace}')
.should('have.value', '-14');
});
@@ -317,6 +317,7 @@ describe('Test Date Hh:m0', () => {
.type('{leftArrow}'.repeat(2))
.type('{backspace}')
.should('have.value', '-1')
+ .type('{rightArrow}')
.type('{backspace}')
.should('have.value', '-');
});
@@ -462,4 +463,47 @@ describe('Test Date Hh:m0', () => {
.type('{backspace}')
.should('have.value', '5');
});
+
+ it('should correct work after backspace separator.2 when after first digit 0', () => {
+ cy.mount(CypressTestMaskComponent, {
+ componentProperties: {
+ mask: signal('separator.2'),
+ decimalMarker: signal(','),
+ thousandSeparator: signal(' '),
+ },
+ });
+
+ cy.get('#masked')
+ .type('1 000 000,05')
+ .should('have.value', '1 000 000,05')
+ .type('{leftArrow}'.repeat(11))
+ .type('{backspace}')
+ .should('have.value', '0,05');
+
+ cy.get('#masked').clear();
+
+ cy.get('#masked')
+ .type('60,05')
+ .should('have.value', '60,05')
+ .type('{leftArrow}'.repeat(4))
+ .type('{backspace}')
+ .should('have.value', '0,05');
+ });
+
+ it('should correct work after backspace separator.2 when after first digit 0', () => {
+ cy.mount(CypressTestMaskComponent, {
+ componentProperties: {
+ mask: signal('separator.2'),
+ decimalMarker: signal('.'),
+ thousandSeparator: signal(' '),
+ },
+ });
+
+ cy.get('#masked')
+ .type('200.05')
+ .should('have.value', '200.05')
+ .type('{leftArrow}'.repeat(5))
+ .type('{backspace}')
+ .should('have.value', '0.05');
+ });
});
diff --git a/projects/ngx-mask-lib/src/test/separator.spec.ts b/projects/ngx-mask-lib/src/test/separator.spec.ts
index cc629b5f..7707786a 100644
--- a/projects/ngx-mask-lib/src/test/separator.spec.ts
+++ b/projects/ngx-mask-lib/src/test/separator.spec.ts
@@ -4,7 +4,7 @@ import { By } from '@angular/platform-browser';
import type { DebugElement } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { TestMaskComponent } from './utils/test-component.component';
-import { equal, Paste, typeTest } from './utils/test-functions.component';
+import { equal, Paste, Type, typeTest } from './utils/test-functions.component';
import { initialConfig, NgxMaskDirective, provideNgxMask } from 'ngx-mask';
describe('Separator: Mask', () => {
@@ -1902,4 +1902,67 @@ describe('Separator: Mask', () => {
expect(component.form.dirty).toBe(false);
expect(component.form.pristine).toBe(true);
});
+
+ it('should show correct value in model after changing thousandSeparator', () => {
+ component.mask.set('separator.2');
+ component.thousandSeparator.set(' ');
+ component.decimalMarker.set(',');
+
+ const inputElement = fixture.nativeElement.querySelector('input');
+ inputElement.value = '100000.00';
+ inputElement.dispatchEvent(new Event('input'));
+ fixture.detectChanges();
+
+ expect(component.form.value).toBe('100000.00');
+
+ component.thousandSeparator.set('.');
+ fixture.detectChanges();
+
+ expect(component.form.value).toBe('100000.00');
+
+ component.thousandSeparator.set('-');
+ fixture.detectChanges();
+
+ expect(component.form.value).toBe('100000.00');
+
+ component.thousandSeparator.set(',');
+ fixture.detectChanges();
+
+ expect(component.form.value).toBe('100000.00');
+ });
+
+ it('should show correct value in input after changing thousandSeparator', () => {
+ const debugElement: DebugElement = fixture.debugElement.query(By.css('input'));
+ const inputTarget: HTMLInputElement = debugElement.nativeElement as HTMLInputElement;
+ spyOnProperty(document, 'activeElement').and.returnValue(inputTarget);
+ fixture.detectChanges();
+ component.mask.set('separator.2');
+ component.thousandSeparator.set(' ');
+ component.decimalMarker.set(',');
+
+ equal('123456,10', '123 456,10', fixture, false, Type);
+ expect(inputTarget.value).toBe('123 456,10');
+ expect(component.form.value).toBe('123456.10');
+
+ component.thousandSeparator.set('.');
+ fixture.detectChanges();
+
+ equal('123456,10', '123.456,10', fixture, false, Type);
+ expect(inputTarget.value).toBe('123.456,10');
+ expect(component.form.value).toBe('123456.10');
+
+ component.thousandSeparator.set('-');
+ fixture.detectChanges();
+
+ equal('123456,10', '123-456,10', fixture, false, Type);
+ expect(inputTarget.value).toBe('123-456,10');
+ expect(component.form.value).toBe('123456.10');
+
+ component.thousandSeparator.set(',');
+ fixture.detectChanges();
+
+ equal('123456.10', '123,456.10', fixture, false, Type);
+ expect(inputTarget.value).toBe('123,456.10');
+ expect(component.form.value).toBe('123456.10');
+ });
});