diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 137773c75..e8d01bc02 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -16,7 +16,7 @@ jobs:
uses: ./.github/actions/nodejs
- name: Run tests
- run: npx nx run-many --target test --all --coverage
+ run: npx nx run-many --target test --all
- uses: codecov/codecov-action@v2
with:
diff --git a/projects/angular/src/index.ts b/projects/angular/src/index.ts
index 865bea367..1ddb58875 100644
--- a/projects/angular/src/index.ts
+++ b/projects/angular/src/index.ts
@@ -1,2 +1,4 @@
+export * from './lib/maskito.cva';
export * from './lib/maskito.directive';
export * from './lib/maskito.module';
+export * from './lib/maskito.pipe';
diff --git a/projects/angular/src/lib/maskito.cva.ts b/projects/angular/src/lib/maskito.cva.ts
new file mode 100644
index 000000000..9b2aac1e2
--- /dev/null
+++ b/projects/angular/src/lib/maskito.cva.ts
@@ -0,0 +1,28 @@
+import {Directive, Input} from '@angular/core';
+import {DefaultValueAccessor, NG_VALUE_ACCESSOR} from '@angular/forms';
+import {MASKITO_DEFAULT_OPTIONS, MaskitoOptions, maskitoTransform} from '@maskito/core';
+
+@Directive({
+ selector: 'input[maskito], textarea[maskito]',
+ providers: [
+ {
+ provide: NG_VALUE_ACCESSOR,
+ multi: true,
+ useExisting: MaskitoCva,
+ },
+ ],
+ host: {
+ '(input)': '$any(this)._handleInput($event.target.value)',
+ '(blur)': 'onTouched()',
+ '(compositionstart)': '$any(this)._compositionStart()',
+ '(compositionend)': '$any(this)._compositionEnd($event.target.value)',
+ },
+})
+export class MaskitoCva extends DefaultValueAccessor {
+ @Input()
+ maskito: MaskitoOptions = MASKITO_DEFAULT_OPTIONS;
+
+ override writeValue(value: unknown): void {
+ super.writeValue(maskitoTransform(String(value ?? ''), this.maskito));
+ }
+}
diff --git a/projects/angular/src/lib/maskito.module.ts b/projects/angular/src/lib/maskito.module.ts
index 0a728608c..9e041af29 100644
--- a/projects/angular/src/lib/maskito.module.ts
+++ b/projects/angular/src/lib/maskito.module.ts
@@ -1,9 +1,11 @@
import {NgModule} from '@angular/core';
+import {MaskitoCva} from './maskito.cva';
import {MaskitoDirective} from './maskito.directive';
+import {MaskitoPipe} from './maskito.pipe';
@NgModule({
- declarations: [MaskitoDirective],
- exports: [MaskitoDirective],
+ declarations: [MaskitoDirective, MaskitoCva, MaskitoPipe],
+ exports: [MaskitoDirective, MaskitoCva, MaskitoPipe],
})
export class MaskitoModule {}
diff --git a/projects/angular/src/lib/maskito.pipe.ts b/projects/angular/src/lib/maskito.pipe.ts
new file mode 100644
index 000000000..1781be337
--- /dev/null
+++ b/projects/angular/src/lib/maskito.pipe.ts
@@ -0,0 +1,11 @@
+import {Pipe, PipeTransform} from '@angular/core';
+import {MaskitoOptions, maskitoTransform} from '@maskito/core';
+
+@Pipe({
+ name: 'maskito',
+})
+export class MaskitoPipe implements PipeTransform {
+ transform(value: unknown, maskitoOptions: MaskitoOptions): string {
+ return maskitoTransform(String(value ?? ''), maskitoOptions);
+ }
+}
diff --git a/projects/angular/src/lib/maskito.spec.ts b/projects/angular/src/lib/maskito.spec.ts
new file mode 100644
index 000000000..b0ad86a59
--- /dev/null
+++ b/projects/angular/src/lib/maskito.spec.ts
@@ -0,0 +1,57 @@
+import {Component} from '@angular/core';
+import {ComponentFixture, TestBed} from '@angular/core/testing';
+import {FormControl, ReactiveFormsModule} from '@angular/forms';
+import {MaskitoModule} from '@maskito/angular';
+import {maskitoNumberOptionsGenerator} from '@maskito/kit';
+
+describe(`Maskito Angular package`, () => {
+ @Component({
+ template: `
+
{{ control.value | maskito: options }}
+
+ `,
+ })
+ class TestComponent {
+ readonly control = new FormControl();
+ readonly options = maskitoNumberOptionsGenerator({precision: 2});
+ }
+
+ let fixture: ComponentFixture;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [MaskitoModule, ReactiveFormsModule],
+ declarations: [TestComponent],
+ });
+
+ fixture = TestBed.createComponent(TestComponent);
+ fixture.detectChanges();
+ });
+
+ it(`Null is treated as empty string`, () => {
+ expect(getText()).toBe(``);
+ expect(getValue()).toBe(``);
+ });
+
+ it(`Formats new control value`, () => {
+ fixture.componentInstance.control.setValue(12345.67);
+ fixture.detectChanges();
+
+ expect(getText()).toBe(`12\u00A0345.67`);
+ expect(getValue()).toBe(`12\u00A0345.67`);
+ });
+
+ function getText(): string {
+ return fixture.debugElement.nativeElement
+ .querySelector('#pipe')
+ .textContent.trim();
+ }
+
+ function getValue(): string {
+ return fixture.debugElement.nativeElement.querySelector('#input').value;
+ }
+});
diff --git a/projects/demo-integrations/cypress/tests/kit/number/number-basic.cy.ts b/projects/demo-integrations/cypress/tests/kit/number/number-basic.cy.ts
index 02a86a0ac..cb59a7c74 100644
--- a/projects/demo-integrations/cypress/tests/kit/number/number-basic.cy.ts
+++ b/projects/demo-integrations/cypress/tests/kit/number/number-basic.cy.ts
@@ -107,17 +107,17 @@ describe('Number | Basic', () => {
it('can type "–123.45" (en-dash)', () => {
cy.get('@input')
.type('–123.45')
- .should('have.value', '−123,45')
- .should('have.prop', 'selectionStart', '−123,45'.length)
- .should('have.prop', 'selectionEnd', '−123,45'.length);
+ .should('have.value', '−123.45')
+ .should('have.prop', 'selectionStart', '−123.45'.length)
+ .should('have.prop', 'selectionEnd', '−123.45'.length);
});
it('can type "—0,12" (em-dash)', () => {
cy.get('@input')
.type('—0,12')
- .should('have.value', '−0,12')
- .should('have.prop', 'selectionStart', '−0,12'.length)
- .should('have.prop', 'selectionEnd', '−0,12'.length);
+ .should('have.value', '−0.12')
+ .should('have.prop', 'selectionStart', '−0.12'.length)
+ .should('have.prop', 'selectionEnd', '−0.12'.length);
});
});
diff --git a/projects/demo-integrations/cypress/tests/kit/number/number-max-validation.cy.ts b/projects/demo-integrations/cypress/tests/kit/number/number-max-validation.cy.ts
index b02c30d37..2e36a606a 100644
--- a/projects/demo-integrations/cypress/tests/kit/number/number-max-validation.cy.ts
+++ b/projects/demo-integrations/cypress/tests/kit/number/number-max-validation.cy.ts
@@ -30,17 +30,17 @@ describe('Number | Max validation', () => {
it('0,9999', () => {
cy.get('@input')
.type(',9999')
- .should('have.value', '0,9999')
- .should('have.prop', 'selectionStart', '0,9999'.length)
- .should('have.prop', 'selectionEnd', '0,9999'.length);
+ .should('have.value', '0.9999')
+ .should('have.prop', 'selectionStart', '0.9999'.length)
+ .should('have.prop', 'selectionEnd', '0.9999'.length);
});
it('2,777', () => {
cy.get('@input')
.type('2,777')
- .should('have.value', '2,777')
- .should('have.prop', 'selectionStart', '2,777'.length)
- .should('have.prop', 'selectionEnd', '2,777'.length);
+ .should('have.value', '2.777')
+ .should('have.prop', 'selectionStart', '2.777'.length)
+ .should('have.prop', 'selectionEnd', '2.777'.length);
});
});
diff --git a/projects/demo/src/pages/documentation/angular/angular.component.ts b/projects/demo/src/pages/documentation/angular/angular.component.ts
index 50c738413..4d6aa6536 100644
--- a/projects/demo/src/pages/documentation/angular/angular.component.ts
+++ b/projects/demo/src/pages/documentation/angular/angular.component.ts
@@ -26,4 +26,14 @@ export class AngularDocPageComponent {
Default: import('./examples/1-nested/template.html?raw'),
Custom: import('./examples/2-nested/template.html?raw'),
};
+
+ readonly cvaExample: TuiDocExample = {
+ TypeScript: import('./examples/3-cva/component.ts?raw'),
+ HTML: import('./examples/3-cva/template.html?raw'),
+ };
+
+ readonly pipeExample: TuiDocExample = {
+ TypeScript: import('./examples/4-pipe/component.ts?raw'),
+ HTML: import('./examples/4-pipe/template.html?raw'),
+ };
}
diff --git a/projects/demo/src/pages/documentation/angular/angular.module.ts b/projects/demo/src/pages/documentation/angular/angular.module.ts
index b2a2778bf..3e48e8611 100644
--- a/projects/demo/src/pages/documentation/angular/angular.module.ts
+++ b/projects/demo/src/pages/documentation/angular/angular.module.ts
@@ -1,6 +1,6 @@
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
-import {FormsModule} from '@angular/forms';
+import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {RouterModule} from '@angular/router';
import {MaskitoModule} from '@maskito/angular';
import {TuiAddonDocModule, tuiGenerateRoutes} from '@taiga-ui/addon-doc';
@@ -10,20 +10,29 @@ import {TuiCheckboxLabeledModule, TuiInputModule} from '@taiga-ui/kit';
import {AngularDocPageComponent} from './angular.component';
import {NestedDocExample1} from './examples/1-nested/component';
import {NestedDocExample2} from './examples/2-nested/component';
+import {CvaDocExample3} from './examples/3-cva/component';
+import {PipeDocExample4} from './examples/4-pipe/component';
@NgModule({
imports: [
CommonModule,
FormsModule,
+ ReactiveFormsModule,
MaskitoModule,
TuiInputModule,
TuiLinkModule,
TuiNotificationModule,
+ TuiCheckboxLabeledModule,
TuiAddonDocModule,
RouterModule.forChild(tuiGenerateRoutes(AngularDocPageComponent)),
- TuiCheckboxLabeledModule,
],
- declarations: [AngularDocPageComponent, NestedDocExample1, NestedDocExample2],
+ declarations: [
+ AngularDocPageComponent,
+ NestedDocExample1,
+ NestedDocExample2,
+ CvaDocExample3,
+ PipeDocExample4,
+ ],
exports: [AngularDocPageComponent],
})
export class AngularDocPageModule {}
diff --git a/projects/demo/src/pages/documentation/angular/angular.template.html b/projects/demo/src/pages/documentation/angular/angular.template.html
index 1145e0987..3bac76b5e 100644
--- a/projects/demo/src/pages/documentation/angular/angular.template.html
+++ b/projects/demo/src/pages/documentation/angular/angular.template.html
@@ -108,6 +108,22 @@ Nested input element
[maskitoElement]="example.predicate"
>
+
+
+
+
+
+
+
+
diff --git a/projects/demo/src/pages/documentation/angular/examples/3-cva/component.ts b/projects/demo/src/pages/documentation/angular/examples/3-cva/component.ts
new file mode 100644
index 000000000..60cdc5001
--- /dev/null
+++ b/projects/demo/src/pages/documentation/angular/examples/3-cva/component.ts
@@ -0,0 +1,18 @@
+import {ChangeDetectionStrategy, Component} from '@angular/core';
+import {FormControl} from '@angular/forms';
+import {maskitoNumberOptionsGenerator} from '@maskito/kit';
+
+@Component({
+ selector: 'cva-doc-example-3',
+ templateUrl: './template.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class CvaDocExample3 {
+ readonly control = new FormControl();
+
+ readonly maskito = maskitoNumberOptionsGenerator({precision: 2});
+
+ setValue(): void {
+ this.control.setValue(12345.67);
+ }
+}
diff --git a/projects/demo/src/pages/documentation/angular/examples/3-cva/template.html b/projects/demo/src/pages/documentation/angular/examples/3-cva/template.html
new file mode 100644
index 000000000..a4564b679
--- /dev/null
+++ b/projects/demo/src/pages/documentation/angular/examples/3-cva/template.html
@@ -0,0 +1,5 @@
+
+
diff --git a/projects/demo/src/pages/documentation/angular/examples/4-pipe/component.ts b/projects/demo/src/pages/documentation/angular/examples/4-pipe/component.ts
new file mode 100644
index 000000000..ba4821b7d
--- /dev/null
+++ b/projects/demo/src/pages/documentation/angular/examples/4-pipe/component.ts
@@ -0,0 +1,13 @@
+import {ChangeDetectionStrategy, Component} from '@angular/core';
+import {maskitoNumberOptionsGenerator} from '@maskito/kit';
+
+@Component({
+ selector: 'pipe-doc-example-4',
+ templateUrl: './template.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class PipeDocExample4 {
+ value = 12345.67;
+
+ readonly options = maskitoNumberOptionsGenerator({precision: 2});
+}
diff --git a/projects/demo/src/pages/documentation/angular/examples/4-pipe/template.html b/projects/demo/src/pages/documentation/angular/examples/4-pipe/template.html
new file mode 100644
index 000000000..999b852d5
--- /dev/null
+++ b/projects/demo/src/pages/documentation/angular/examples/4-pipe/template.html
@@ -0,0 +1 @@
+Balance: ${{ value | maskito: options }}
diff --git a/projects/demo/src/pages/kit/number/number-mask-doc.component.ts b/projects/demo/src/pages/kit/number/number-mask-doc.component.ts
index db5f7219b..0a388a1bf 100644
--- a/projects/demo/src/pages/kit/number/number-mask-doc.component.ts
+++ b/projects/demo/src/pages/kit/number/number-mask-doc.component.ts
@@ -46,7 +46,7 @@ export class NumberMaskDocComponent implements GeneratorOptions {
precision = 0;
isNegativeAllowed = true;
max = Number.MAX_SAFE_INTEGER;
- decimalSeparator = ',';
+ decimalSeparator = '.';
decimalZeroPadding = false;
decimalPseudoSeparators = this.decimalPseudoSeparatorsOptions[0];
thousandSeparator = ' ';
diff --git a/projects/demo/src/pages/kit/number/number-mask-doc.template.html b/projects/demo/src/pages/kit/number/number-mask-doc.template.html
index ee5ba9e41..0a7650185 100644
--- a/projects/demo/src/pages/kit/number/number-mask-doc.template.html
+++ b/projects/demo/src/pages/kit/number/number-mask-doc.template.html
@@ -166,7 +166,7 @@
Default:
- comma.
+ dot.
char !== thousandSeparator && char !== decimalSeparator,
- );
- }
-
- return [];
+export function getDefaultPseudoSeparators(
+ decimalSeparator: string,
+ thousandSeparator: string,
+): string[] {
+ return decimalSeparator === ',' || decimalSeparator === '.'
+ ? ['.', ',', 'б', 'ю'].filter(
+ char => char !== thousandSeparator && char !== decimalSeparator,
+ )
+ : [];
}