Skip to content

Commit

Permalink
feat(kit): new maskitoWithPlaceholder utility (#299)
Browse files Browse the repository at this point in the history
  • Loading branch information
nsbarsukov authored May 17, 2023
1 parent c3b7ee4 commit 21eb69c
Show file tree
Hide file tree
Showing 24 changed files with 764 additions and 61 deletions.
19 changes: 11 additions & 8 deletions projects/core/src/lib/utils/pipe.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import {MaskPostprocessor, MaskPreprocessor} from '../types';

export function maskitoPipe(...processors: readonly MaskPreprocessor[]): MaskPreprocessor;
export function maskitoPipe(
...processors: ReadonlyArray<MaskPreprocessor | null | undefined>
): MaskPreprocessor;

export function maskitoPipe(
...processors: readonly MaskPostprocessor[]
...processors: ReadonlyArray<MaskPostprocessor | null | undefined>
): MaskPostprocessor;

// eslint-disable-next-line @typescript-eslint/ban-types
export function maskitoPipe(...processors: readonly Function[]): Function {
/* eslint-disable @typescript-eslint/ban-types */
export function maskitoPipe(
...processors: ReadonlyArray<Function | null | undefined>
): Function {
return (initialData: object, ...readonlyArgs: unknown[]) =>
processors.reduce(
(data, fn) => ({...data, ...fn(data, ...readonlyArgs)}),
initialData,
);
processors
.filter(<T>(x: T | null | undefined): x is T => Boolean(x))
.reduce((data, fn) => ({...data, ...fn(data, ...readonlyArgs)}), initialData);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import {DemoPath} from '@demo/constants';

describe('Placeholder | Date', () => {
beforeEach(() => {
cy.visit(DemoPath.Placeholder);
cy.get('#date input')
.should('be.visible')
.first()
.should('have.value', '')
.focus()
.should('have.value', 'dd/mm/yyyy')
.should('have.prop', 'selectionStart', 0)
.should('have.prop', 'selectionEnd', 0)
.as('input');
});

describe('basic typing (1 character per keydown)', () => {
const tests = [
// [Typed value, Masked value, caretIndex]
['1', '1d/mm/yyyy', 1],
['16', '16/mm/yyyy', '16'.length],
['160', '16/0m/yyyy', '16/0'.length],
['1605', '16/05/yyyy', '16/05'.length],
['16052', '16/05/2yyy', '16/05/2'.length],
['160520', '16/05/20yy', '16/05/20'.length],
['1605202', '16/05/202y', '16/05/202'.length],
['16052023', '16/05/2023', '16/05/2023'.length],
] as const;

tests.forEach(([typed, masked, caretIndex]) => {
it(`Type ${typed} => ${masked}`, () => {
cy.get('@input')
.type(typed)
.should('have.value', masked)
.should('have.prop', 'selectionStart', caretIndex)
.should('have.prop', 'selectionEnd', caretIndex);
});
});
});

it('Type 999 => 09/09/9yyy', () => {
cy.get('@input')
.type('999')
.should('have.value', '09/09/9yyy')
.should('have.prop', 'selectionStart', '09/09/9'.length)
.should('have.prop', 'selectionEnd', '09/09/9'.length);
});

it('Type 39 => 3d/mm/yyyy', () => {
cy.get('@input')
.type('39')
.should('have.value', '3d/mm/yyyy')
.should('have.prop', 'selectionStart', 1)
.should('have.prop', 'selectionEnd', 1);
});

it('Type 31/13 => 31/1m/yyyy', () => {
cy.get('@input')
.type('3113')
.should('have.value', '31/1m/yyyy')
.should('have.prop', 'selectionStart', '31/1'.length)
.should('have.prop', 'selectionEnd', '31/1'.length);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import {DemoPath} from '@demo/constants';

describe('Placeholder | US phone', () => {
beforeEach(() => {
cy.visit(DemoPath.Placeholder);
cy.get('#phone input')
.should('be.visible')
.first()
.should('have.value', '')
.focus()
.should('have.value', '+1 (   ) ___-____')
.should('have.prop', 'selectionStart', '+1 ('.length)
.should('have.prop', 'selectionEnd', '+1 ('.length)
.as('input');
});

describe('basic typing (1 character per keydown)', () => {
const tests = [
// [Typed value, Masked value, caretIndex]
['2', '+1 (2  ) ___-____', '+1 (2'.length],
['21', '+1 (21 ) ___-____', '+1 (21'.length],
['212', '+1 (212) ___-____', '+1 (212'.length],
['2125', '+1 (212) 5__-____', '+1 (212) 5'.length],
['21255', '+1 (212) 55_-____', '+1 (212) 55'.length],
['212555', '+1 (212) 555-____', '+1 (212) 555'.length],
['2125552', '+1 (212) 555-2___', '+1 (212) 555-2'.length],
['21255523', '+1 (212) 555-23__', '+1 (212) 555-23'.length],
['212555236', '+1 (212) 555-236_', '+1 (212) 555-236'.length],
['2125552368', '+1 (212) 555-2368', '+1 (212) 555-2368'.length],
] as const;

tests.forEach(([typed, masked, caretIndex]) => {
it(`Type ${typed} => ${masked}`, () => {
cy.get('@input')
.type(typed)
.should('have.value', masked)
.should('have.prop', 'selectionStart', caretIndex)
.should('have.prop', 'selectionEnd', caretIndex);
});
});
});

it('Can type 1 after country code +1', () => {
cy.get('@input')
.type('1')
.should('have.value', '+1 (1  ) ___-____')
.should('have.prop', 'selectionStart', '+1 (1'.length)
.should('have.prop', 'selectionEnd', '+1 (1'.length);
});

it('cannot erase country code +1', () => {
cy.get('@input')
.type('{backspace}'.repeat(10))
.should('have.value', '+1 (   ) ___-____')
.type('{selectAll}{backspace}')
.should('have.value', '+1 (   ) ___-____')
.type('{selectAll}{del}')
.should('have.value', '+1 (   ) ___-____')
.should('have.prop', 'selectionStart', '+1'.length)
.should('have.prop', 'selectionEnd', '+1'.length);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {DemoPath} from '@demo/constants';

describe('Placeholder | CVC code', () => {
beforeEach(() => {
cy.visit(DemoPath.Placeholder);
cy.get('#cvc input')
.should('be.visible')
.first()
.focus()
.should('have.value', 'xxx')
.should('have.prop', 'selectionStart', 0)
.should('have.prop', 'selectionEnd', 0)
.as('input');
});

it('Type 1 => 1|xx', () => {
cy.get('@input')
.type('1')
.should('have.value', '1xx')
.should('have.prop', 'selectionStart', 1)
.should('have.prop', 'selectionEnd', 1);
});

it('Type 12 => 12|x', () => {
cy.get('@input')
.type('12')
.should('have.value', '12x')
.should('have.prop', 'selectionStart', 2)
.should('have.prop', 'selectionEnd', 2);
});

it('Type 123 => 123|', () => {
cy.get('@input')
.type('123')
.should('have.value', '123')
.should('have.prop', 'selectionStart', 3)
.should('have.prop', 'selectionEnd', 3);
});

it('12|3 => Backspace => 1|3x', () => {
cy.get('@input')
.type('123')
.type('{leftArrow}{backspace}')
.should('have.value', '13x')
.should('have.prop', 'selectionStart', 1)
.should('have.prop', 'selectionEnd', 1);
});

it('1|3x => Type 0 => 10|3', () => {
cy.get('@input')
.type('13')
.type('{leftArrow}0')
.should('have.value', '103')
.should('have.prop', 'selectionStart', 2)
.should('have.prop', 'selectionEnd', 2);
});

it('1xx => select all => backspace => xxx', () => {
cy.get('@input')
.type('1')
.type('{selectAll}{backspace}')
.should('have.value', 'xxx')
.should('have.prop', 'selectionStart', 0)
.should('have.prop', 'selectionEnd', 0);
});

it('1xx => select all => delete => xxx', () => {
cy.get('@input')
.type('1')
.type('{selectAll}{del}')
.should('have.value', 'xxx')
.should('have.prop', 'selectionStart', 0)
.should('have.prop', 'selectionEnd', 0);
});

it('12x| => Backspace => 12|x', () => {
cy.get('@input')
.type('12')
.type('{rightArrow}')
.should('have.prop', 'selectionStart', 3)
.should('have.prop', 'selectionEnd', 3)
.type('{backspace}')
.should('have.value', '12x')
.should('have.prop', 'selectionStart', 2)
.should('have.prop', 'selectionEnd', 2);
});
});
7 changes: 5 additions & 2 deletions projects/demo/src/app/app.providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,12 @@ export const APP_PROVIDERS: Provider[] = [
},
tuiDocExampleOptionsProvider({
codeEditorVisibilityHandler: files => {
const primaryTabs: string[] = Object.values(DocExamplePrimaryTab);
const fileNames = Object.keys(files);

return Object.keys(files).every(fileName => primaryTabs.includes(fileName));
return (
fileNames.includes(DocExamplePrimaryTab.MaskitoOptions) &&
fileNames.includes(DocExamplePrimaryTab.JavaScript)
);
},
tabTitles: new Map<string, PolymorpheusContent>([
[DocExamplePrimaryTab.JavaScript, JAVASCRIPT_LOGO],
Expand Down
10 changes: 10 additions & 0 deletions projects/demo/src/app/app.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,16 @@ export const appRoutes: Routes = [
title: `With postfix`,
},
},
{
path: DemoPath.Placeholder,
loadChildren: async () =>
import(`../pages/recipes/placeholder/placeholder-doc.module`).then(
m => m.PlaceholderDocModule,
),
data: {
title: `With placeholder`,
},
},
// Other
{
path: DemoPath.BrowserSupport,
Expand Down
3 changes: 2 additions & 1 deletion projects/demo/src/app/constants/demo-path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ export const enum DemoPath {
DateTime = 'kit/date-time',
Card = 'recipes/card',
Phone = 'recipes/phone',
Textarea = 'recipes/textarea',
Prefix = 'recipes/prefix',
Postfix = 'recipes/postfix',
Textarea = 'recipes/textarea',
Placeholder = 'recipes/placeholder',
BrowserSupport = 'browser-support',
Changelog = 'changelog',
Stackblitz = 'stackblitz',
Expand Down
21 changes: 4 additions & 17 deletions projects/demo/src/pages/kit/number/examples/3-postfix/component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
NgZone,
ViewChild,
} from '@angular/core';
import {ChangeDetectionStrategy, Component, ElementRef, ViewChild} from '@angular/core';

import mask from './mask';

Expand Down Expand Up @@ -37,23 +31,16 @@ export class NumberMaskDocExample3 {
value = `97${this.postfix}`;
maskitoOptions = mask;

constructor(private readonly ngZone: NgZone) {}

onFocus(): void {
if (!this.value) {
this.value = this.postfix;
}

const newCaretIndex = this.value.length - this.postfix.length;

this.ngZone.runOutsideAngular(() => {
setTimeout(() => {
// To put cursor before postfix
this.inputRef.nativeElement.setSelectionRange(
newCaretIndex,
newCaretIndex,
);
});
setTimeout(() => {
// To put cursor before postfix
this.inputRef.nativeElement.setSelectionRange(newCaretIndex, newCaretIndex);
});
}

Expand Down
28 changes: 10 additions & 18 deletions projects/demo/src/pages/kit/number/number-mask-doc.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import {
ChangeDetectionStrategy,
Component,
ElementRef,
NgZone,
ViewChild,
} from '@angular/core';
import {ChangeDetectionStrategy, Component, ElementRef, ViewChild} from '@angular/core';
import {FormControl} from '@angular/forms';
import {DocExamplePrimaryTab} from '@demo/constants';
import {MaskitoOptions} from '@maskito/core';
Expand Down Expand Up @@ -56,7 +50,9 @@ export class NumberMaskDocComponent implements GeneratorOptions {
[DocExamplePrimaryTab.MaskitoOptions]: import(
'./examples/5-dynamic-decimal-zero-padding/mask.ts?raw'
),
Component: import('./examples/5-dynamic-decimal-zero-padding/component.ts?raw'),
[DocExamplePrimaryTab.Angular]: import(
'./examples/5-dynamic-decimal-zero-padding/component.ts?raw'
),
};

apiPageControl = new FormControl('');
Expand All @@ -76,8 +72,6 @@ export class NumberMaskDocComponent implements GeneratorOptions {
prefix = '';
postfix = '';

constructor(private readonly ngZone: NgZone) {}

updateOptions(): void {
this.maskitoOptions = maskitoNumberOptionsGenerator(this);
}
Expand All @@ -93,14 +87,12 @@ export class NumberMaskDocComponent implements GeneratorOptions {
if (this.postfix) {
const newCaretIndex = value.length - this.postfix.length;

this.ngZone.runOutsideAngular(() => {
setTimeout(() => {
// To put cursor before postfix
this.apiPageInput.nativeElement.setSelectionRange(
newCaretIndex,
newCaretIndex,
);
});
setTimeout(() => {
// To put cursor before postfix
this.apiPageInput.nativeElement.setSelectionRange(
newCaretIndex,
newCaretIndex,
);
});
}
}
Expand Down
6 changes: 6 additions & 0 deletions projects/demo/src/pages/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,12 @@ export const DEMO_PAGES: TuiDocPages = [
route: DemoPath.Postfix,
keywords: `postfix, after, percent, am, pm, recipe`,
},
{
section: 'Recipes',
title: 'With placeholder',
route: DemoPath.Placeholder,
keywords: `guide, placeholder, fill, recipe`,
},
{
section: 'Other',
title: 'Browser support',
Expand Down
Loading

0 comments on commit 21eb69c

Please sign in to comment.