Skip to content

Commit

Permalink
feat(angular): add CVA and pipe (#187)
Browse files Browse the repository at this point in the history
Co-authored-by: splincode <>
Co-authored-by: Nikita Barsukov <nikita.s.barsukov@gmail.com>
  • Loading branch information
waterplea and nsbarsukov authored Mar 15, 2023
1 parent 7376b2e commit a099257
Show file tree
Hide file tree
Showing 19 changed files with 204 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions projects/angular/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export * from './lib/maskito.cva';
export * from './lib/maskito.directive';
export * from './lib/maskito.module';
export * from './lib/maskito.pipe';
28 changes: 28 additions & 0 deletions projects/angular/src/lib/maskito.cva.ts
Original file line number Diff line number Diff line change
@@ -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));
}
}
6 changes: 4 additions & 2 deletions projects/angular/src/lib/maskito.module.ts
Original file line number Diff line number Diff line change
@@ -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 {}
11 changes: 11 additions & 0 deletions projects/angular/src/lib/maskito.pipe.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
57 changes: 57 additions & 0 deletions projects/angular/src/lib/maskito.spec.ts
Original file line number Diff line number Diff line change
@@ -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: `
<div id="pipe">{{ control.value | maskito: options }}</div>
<input
id="input"
[formControl]="control"
[maskito]="options"
/>
`,
})
class TestComponent {
readonly control = new FormControl();
readonly options = maskitoNumberOptionsGenerator({precision: 2});
}

let fixture: ComponentFixture<TestComponent>;

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;
}
});
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});

Expand Down
10 changes: 10 additions & 0 deletions projects/demo/src/pages/documentation/angular/angular.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
};
}
15 changes: 12 additions & 3 deletions projects/demo/src/pages/documentation/angular/angular.module.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 {}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,22 @@ <h2>Nested input element</h2>
[maskitoElement]="example.predicate"
></nested-doc-example-2>
</tui-doc-example>

<tui-doc-example
heading="Form controls"
description="When directly on native input/textarea tag, maskito directive formats value set programmatically with Angular forms."
[content]="cvaExample"
>
<cva-doc-example-3></cva-doc-example-3>
</tui-doc-example>

<tui-doc-example
heading="Pipe"
description="Format arbitrary value with the same options"
[content]="pipeExample"
>
<pipe-doc-example-4></pipe-doc-example-4>
</tui-doc-example>
</ng-template>

<ng-template pageTab="Setup">
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<input
[formControl]="control"
[maskito]="maskito"
/>
<button (click)="setValue()">Set 12345.67</button>
Original file line number Diff line number Diff line change
@@ -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});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Balance: ${{ value | maskito: options }}
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ' ';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@

<p>
<strong>Default:</strong>
comma.
dot.
</p>
</ng-template>
<ng-template
Expand Down
6 changes: 3 additions & 3 deletions projects/kit/src/lib/masks/number/number-mask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ export function maskitoNumberOptionsGenerator({
isNegativeAllowed = true,
precision = 0,
thousandSeparator = CHAR_NO_BREAK_SPACE,
decimalSeparator = ',',
decimalPseudoSeparators = getDefaultPseudoSeparators({
decimalSeparator = '.',
decimalPseudoSeparators = getDefaultPseudoSeparators(
decimalSeparator,
thousandSeparator,
}),
),
decimalZeroPadding = false,
}: {
max?: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
export function getDefaultPseudoSeparators({
decimalSeparator,
thousandSeparator,
}: {
decimalSeparator: string;
thousandSeparator: string;
}): string[] {
if (decimalSeparator === ',' || decimalSeparator === '.') {
return ['.', ',', 'б', 'ю'].filter(
char => char !== thousandSeparator && char !== decimalSeparator,
);
}

return [];
export function getDefaultPseudoSeparators(
decimalSeparator: string,
thousandSeparator: string,
): string[] {
return decimalSeparator === ',' || decimalSeparator === '.'
? ['.', ',', 'б', 'ю'].filter(
char => char !== thousandSeparator && char !== decimalSeparator,
)
: [];
}

0 comments on commit a099257

Please sign in to comment.