diff --git a/projects/core/src/index.ts b/projects/core/src/index.ts index 8e99fb109..7fb915cd7 100644 --- a/projects/core/src/index.ts +++ b/projects/core/src/index.ts @@ -4,10 +4,10 @@ export { } from './lib/constants'; export {Maskito} from './lib/mask'; export { - MaskExpression, MaskitoElementPredicate, MaskitoElementPredicateAsync, MaskitoMask, + MaskitoMaskExpression, MaskitoOptions, MaskitoPlugin, MaskitoPostprocessor, diff --git a/projects/core/src/lib/classes/mask-model/mask-model.ts b/projects/core/src/lib/classes/mask-model/mask-model.ts index d3c213c47..aa4b0990f 100644 --- a/projects/core/src/lib/classes/mask-model/mask-model.ts +++ b/projects/core/src/lib/classes/mask-model/mask-model.ts @@ -1,4 +1,9 @@ -import {ElementState, MaskExpression, MaskitoOptions, SelectionRange} from '../../types'; +import { + ElementState, + MaskitoMaskExpression, + MaskitoOptions, + SelectionRange, +} from '../../types'; import {areElementStatesEqual} from '../../utils/element-states-equality'; import {applyOverwriteMode} from './utils/apply-overwrite-mode'; import {calibrateValueByMask} from './utils/calibrate-value-by-mask'; @@ -102,7 +107,7 @@ export class MaskModel implements ElementState { this.selection = maskedElementState.selection; } - private getMaskExpression(elementState: ElementState): MaskExpression { + private getMaskExpression(elementState: ElementState): MaskitoMaskExpression { const {mask} = this.maskOptions; return typeof mask === 'function' ? mask(elementState) : mask; diff --git a/projects/core/src/lib/classes/mask-model/utils/calibrate-value-by-mask.ts b/projects/core/src/lib/classes/mask-model/utils/calibrate-value-by-mask.ts index a30f098d7..400903a7e 100644 --- a/projects/core/src/lib/classes/mask-model/utils/calibrate-value-by-mask.ts +++ b/projects/core/src/lib/classes/mask-model/utils/calibrate-value-by-mask.ts @@ -1,11 +1,11 @@ -import {ElementState, MaskExpression} from '../../../types'; +import {ElementState, MaskitoMaskExpression} from '../../../types'; import {guessValidValueByPattern} from './guess-valid-value-by-pattern'; import {guessValidValueByRegExp} from './guess-valid-value-by-reg-exp'; import {validateValueWithMask} from './validate-value-with-mask'; export function calibrateValueByMask( elementState: ElementState, - mask: MaskExpression, + mask: MaskitoMaskExpression, initialElementState: ElementState | null = null, ): ElementState { if (validateValueWithMask(elementState.value, mask)) { diff --git a/projects/core/src/lib/classes/mask-model/utils/remove-fixed-mask-characters.ts b/projects/core/src/lib/classes/mask-model/utils/remove-fixed-mask-characters.ts index c32ca3e0e..ba23ea5aa 100644 --- a/projects/core/src/lib/classes/mask-model/utils/remove-fixed-mask-characters.ts +++ b/projects/core/src/lib/classes/mask-model/utils/remove-fixed-mask-characters.ts @@ -1,9 +1,9 @@ -import {ElementState, MaskExpression} from '../../../types'; +import {ElementState, MaskitoMaskExpression} from '../../../types'; import {isFixedCharacter} from './is-fixed-character'; export function removeFixedMaskCharacters( initialElementState: ElementState, - mask: MaskExpression, + mask: MaskitoMaskExpression, ): ElementState { if (!Array.isArray(mask)) { return initialElementState; diff --git a/projects/core/src/lib/classes/mask-model/utils/validate-value-with-mask.ts b/projects/core/src/lib/classes/mask-model/utils/validate-value-with-mask.ts index 69ea2fd9f..9537e3795 100644 --- a/projects/core/src/lib/classes/mask-model/utils/validate-value-with-mask.ts +++ b/projects/core/src/lib/classes/mask-model/utils/validate-value-with-mask.ts @@ -1,9 +1,9 @@ -import {MaskExpression} from '../../../types'; +import {MaskitoMaskExpression} from '../../../types'; import {isFixedCharacter} from './is-fixed-character'; export function validateValueWithMask( value: string, - maskExpression: MaskExpression, + maskExpression: MaskitoMaskExpression, ): boolean { if (Array.isArray(maskExpression)) { return ( diff --git a/projects/core/src/lib/types/mask.ts b/projects/core/src/lib/types/mask.ts index 8d3df7a13..880c338e5 100644 --- a/projects/core/src/lib/types/mask.ts +++ b/projects/core/src/lib/types/mask.ts @@ -1,7 +1,7 @@ import {ElementState} from './element-state'; -export type MaskExpression = Array | RegExp; +export type MaskitoMaskExpression = Array | RegExp; export type MaskitoMask = - | MaskExpression - | ((elementState: ElementState) => MaskExpression); + | MaskitoMaskExpression + | ((elementState: ElementState) => MaskitoMaskExpression); diff --git a/projects/demo-integrations/cypress/tests/addons/phone/phone-basic.cy.ts b/projects/demo-integrations/cypress/tests/addons/phone/phone-basic.cy.ts index ade96c849..d2540568d 100644 --- a/projects/demo-integrations/cypress/tests/addons/phone/phone-basic.cy.ts +++ b/projects/demo-integrations/cypress/tests/addons/phone/phone-basic.cy.ts @@ -67,7 +67,7 @@ describe('Phone', () => { it('+7 920 424-1|1-32 => Backspace => +7 920 424-|13-2 => Type "2" => +7 920 424-2|1-32', () => { cy.get('@input') - .type('{leftArrow}'.repeat(4)) + .type('{leftArrow}'.repeat('13-2'.length)) .type('{backspace}') .should('have.value', '+7 920 424-13-2') .should('have.prop', 'selectionStart', '+7 920 424-'.length) @@ -80,7 +80,7 @@ describe('Phone', () => { it('+7 9|20 424-11-32 => Backspace => +7 2|04241132', () => { cy.get('@input') - .type('{leftArrow}'.repeat(12)) + .type('{leftArrow}'.repeat('20 424-11-32'.length)) .type('{backspace}') .should('have.value', '+7 204241132') .should('have.prop', 'selectionStart', '+7 '.length) @@ -114,7 +114,7 @@ describe('Phone', () => { it('+7 920 424-11-32 => Select "+7 920 424-1|1-3|2" => Backspace => +7 920 424-1|2', () => { cy.get('@input') - .type('{leftArrow}'.repeat('2'.length)) + .type('{leftArrow}') .realPress([ 'Shift', ...Array('1-3'.length).fill('ArrowLeft'), @@ -150,7 +150,7 @@ describe('Phone', () => { it('+7 920 424-11-32 => Select "+7 920 424-1|1-3|2" => Type "5" => +7 920 424-15-|2', () => { cy.get('@input') - .type('{leftArrow}'.repeat('2'.length)) + .type('{leftArrow}') .realPress([ 'Shift', ...Array('1-3'.length).fill('ArrowLeft'), diff --git a/projects/demo/src/pages/phone/examples/1-basic/component.ts b/projects/demo/src/pages/phone/examples/1-basic/component.ts index 07f989a03..fc7fb6017 100644 --- a/projects/demo/src/pages/phone/examples/1-basic/component.ts +++ b/projects/demo/src/pages/phone/examples/1-basic/component.ts @@ -1,23 +1,8 @@ import {ChangeDetectionStrategy, Component} from '@angular/core'; -import { - AbstractControl, - FormControl, - ValidationErrors, - ValidatorFn, -} from '@angular/forms'; -import {TuiValidationError} from '@taiga-ui/cdk'; -import {CountryCode, isValidPhoneNumber} from 'libphonenumber-js/max'; +import {FormControl} from '@angular/forms'; import mask from './mask'; -function phoneValidator(countryCode: CountryCode): ValidatorFn { - return (control: AbstractControl): ValidationErrors | null => { - const valid = isValidPhoneNumber(control.value, countryCode); - - return valid ? null : new TuiValidationError('Invalid number'); - }; -} - @Component({ selector: 'phone-doc-example-1', template: ` @@ -41,7 +26,7 @@ function phoneValidator(countryCode: CountryCode): ValidatorFn { `, changeDetection: ChangeDetectionStrategy.OnPush, }) -export class DateMaskDocExample1 { - value = new FormControl('+7 771 931-1111', phoneValidator('KZ')); +export class PhoneMaskDocExample1 { + value = new FormControl('+7 771 931-1111'); readonly mask = mask; } diff --git a/projects/demo/src/pages/phone/examples/2-validation/component.ts b/projects/demo/src/pages/phone/examples/2-validation/component.ts new file mode 100644 index 000000000..22f081771 --- /dev/null +++ b/projects/demo/src/pages/phone/examples/2-validation/component.ts @@ -0,0 +1,47 @@ +import {ChangeDetectionStrategy, Component} from '@angular/core'; +import { + AbstractControl, + FormControl, + ValidationErrors, + ValidatorFn, +} from '@angular/forms'; +import {TuiValidationError} from '@taiga-ui/cdk'; +import {CountryCode, isValidPhoneNumber} from 'libphonenumber-js/max'; + +import mask from './mask'; + +function phoneValidator(countryCode: CountryCode): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const valid = isValidPhoneNumber(control.value, countryCode); + + return valid ? null : new TuiValidationError('Invalid number'); + }; +} + +@Component({ + selector: 'phone-doc-example-2', + template: ` + + Basic + + + + `, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class PhoneMaskDocExample2 { + value = new FormControl('+36 20 123-3122', phoneValidator('HU')); + readonly mask = mask; +} diff --git a/projects/demo/src/pages/phone/examples/2-validation/mask.ts b/projects/demo/src/pages/phone/examples/2-validation/mask.ts new file mode 100644 index 000000000..03712213b --- /dev/null +++ b/projects/demo/src/pages/phone/examples/2-validation/mask.ts @@ -0,0 +1,4 @@ +import {maskitoPhoneOptionsGenerator} from '@maskito/phone'; +import metadata from 'libphonenumber-js/mobile/metadata'; + +export default maskitoPhoneOptionsGenerator({countryIsoCode: 'HU', metadata}); diff --git a/projects/demo/src/pages/phone/phone-doc.component.ts b/projects/demo/src/pages/phone/phone-doc.component.ts index c66ede0ea..317cd1e1d 100644 --- a/projects/demo/src/pages/phone/phone-doc.component.ts +++ b/projects/demo/src/pages/phone/phone-doc.component.ts @@ -21,6 +21,15 @@ export class PhoneDocComponent implements GeneratorOptions { [DocExamplePrimaryTab.MaskitoOptions]: import('./examples/1-basic/mask.ts?raw'), }; + readonly validation: TuiDocExample = { + [DocExamplePrimaryTab.MaskitoOptions]: import( + './examples/2-validation/mask.ts?raw' + ), + [DocExamplePrimaryTab.Angular]: import( + './examples/2-validation/component.ts?raw' + ), + }; + metadata = metadata; countryCodeVariants = getCountries(this.metadata); diff --git a/projects/demo/src/pages/phone/phone-doc.module.ts b/projects/demo/src/pages/phone/phone-doc.module.ts index 5766f0880..cb8beb32b 100644 --- a/projects/demo/src/pages/phone/phone-doc.module.ts +++ b/projects/demo/src/pages/phone/phone-doc.module.ts @@ -11,7 +11,8 @@ import { } from '@taiga-ui/core'; import {TuiFieldErrorPipeModule, TuiInputModule} from '@taiga-ui/kit'; -import {DateMaskDocExample1} from './examples/1-basic/component'; +import {PhoneMaskDocExample1} from './examples/1-basic/component'; +import {PhoneMaskDocExample2} from './examples/2-validation/component'; import {PhoneDocComponent} from './phone-doc.component'; @NgModule({ @@ -28,7 +29,7 @@ import {PhoneDocComponent} from './phone-doc.component'; TuiTextfieldControllerModule, RouterModule.forChild(tuiGenerateRoutes(PhoneDocComponent)), ], - declarations: [PhoneDocComponent, DateMaskDocExample1], + declarations: [PhoneDocComponent, PhoneMaskDocExample1, PhoneMaskDocExample2], exports: [PhoneDocComponent], }) export class PhoneDocModule {} diff --git a/projects/demo/src/pages/phone/phone-doc.template.html b/projects/demo/src/pages/phone/phone-doc.template.html index 43bbc5e9a..ec18c1f96 100644 --- a/projects/demo/src/pages/phone/phone-doc.template.html +++ b/projects/demo/src/pages/phone/phone-doc.template.html @@ -25,6 +25,43 @@ > + + + + + +

+ For validating phone number you can use + isValidPhoneNumber + , + isPossiblePhoneNumber + functions from + + libphonenumber-js + + package. + + Read more + +

+ +

+ Below is an example of a Hungarian phone mask with an + angular validator. +

+
+
@@ -38,7 +75,8 @@ Enter phone diff --git a/projects/kit/src/index.ts b/projects/kit/src/index.ts index 2372db4c2..fe5bf3aa7 100644 --- a/projects/kit/src/index.ts +++ b/projects/kit/src/index.ts @@ -3,7 +3,13 @@ export {maskitoDateRangeOptionsGenerator} from './lib/masks/date-range'; export {maskitoDateTimeOptionsGenerator} from './lib/masks/date-time'; export {maskitoNumberOptionsGenerator, maskitoParseNumber} from './lib/masks/number'; export {maskitoTimeOptionsGenerator} from './lib/masks/time'; -export {maskitoCaretGuard, maskitoEventHandler, maskitoRejectEvent} from './lib/plugins'; +export { + maskitoAddOnFocusPlugin, + maskitoCaretGuard, + maskitoEventHandler, + maskitoRejectEvent, + maskitoRemoveOnBlurPlugin, +} from './lib/plugins'; export { maskitoPostfixPostprocessorGenerator, maskitoPrefixPostprocessorGenerator, diff --git a/projects/phone/src/lib/plugins/add-on-focus.ts b/projects/kit/src/lib/plugins/add-on-focus.ts similarity index 85% rename from projects/phone/src/lib/plugins/add-on-focus.ts rename to projects/kit/src/lib/plugins/add-on-focus.ts index 164504d92..7d26faf48 100644 --- a/projects/phone/src/lib/plugins/add-on-focus.ts +++ b/projects/kit/src/lib/plugins/add-on-focus.ts @@ -1,5 +1,5 @@ import {MaskitoPlugin} from '@maskito/core'; -import {maskitoEventHandler} from '@maskito/kit'; +import {maskitoEventHandler} from './event-handler'; export function maskitoAddOnFocusPlugin(value: string): MaskitoPlugin { return maskitoEventHandler('focus', element => { diff --git a/projects/kit/src/lib/plugins/index.ts b/projects/kit/src/lib/plugins/index.ts index 698709200..cbc8b4325 100644 --- a/projects/kit/src/lib/plugins/index.ts +++ b/projects/kit/src/lib/plugins/index.ts @@ -1,3 +1,5 @@ +export {maskitoAddOnFocusPlugin} from './add-on-focus'; export {maskitoCaretGuard} from './caret-guard'; export {maskitoEventHandler} from './event-handler'; export {maskitoRejectEvent} from './reject-event'; +export {maskitoRemoveOnBlurPlugin} from './remove-on-blur'; diff --git a/projects/phone/src/lib/plugins/remove-on-blur.ts b/projects/kit/src/lib/plugins/remove-on-blur.ts similarity index 85% rename from projects/phone/src/lib/plugins/remove-on-blur.ts rename to projects/kit/src/lib/plugins/remove-on-blur.ts index 4b4468ebb..4d852393a 100644 --- a/projects/phone/src/lib/plugins/remove-on-blur.ts +++ b/projects/kit/src/lib/plugins/remove-on-blur.ts @@ -1,5 +1,5 @@ import {MaskitoPlugin} from '@maskito/core'; -import {maskitoEventHandler} from '@maskito/kit'; +import {maskitoEventHandler} from './event-handler'; export function maskitoRemoveOnBlurPlugin(value: string): MaskitoPlugin { return maskitoEventHandler('blur', element => { diff --git a/projects/phone/package.json b/projects/phone/package.json index 2e3500b91..3d53fc695 100644 --- a/projects/phone/package.json +++ b/projects/phone/package.json @@ -29,7 +29,7 @@ "Nikita Barsukov " ], "devDependencies": { - "libphonenumber-js": "^1.10.0" + "libphonenumber-js": "1.10.37" }, "peerDependencies": { "@maskito/core": "^1.4.0", diff --git a/projects/phone/project.json b/projects/phone/project.json index 702ec4c21..c2720c9f2 100644 --- a/projects/phone/project.json +++ b/projects/phone/project.json @@ -35,19 +35,6 @@ "jestConfig": "projects/phone/jest.config.js", "passWithNoTests": true } - }, - "publish": { - "executor": "@nrwl/workspace:run-commands", - "options": { - "command": "ts-node ./scripts/npm-publish.ts --path ./dist/phone --dry-run {args.dry-run}" - }, - "dependsOn": [ - { - "target": "build", - "projects": "self", - "params": "ignore" - } - ] } }, "tags": [] diff --git a/projects/phone/src/index.ts b/projects/phone/src/index.ts index e6156ec41..f66abb5f3 100644 --- a/projects/phone/src/index.ts +++ b/projects/phone/src/index.ts @@ -1,2 +1 @@ export * from './lib/masks'; -export * from './lib/plugins'; diff --git a/projects/phone/src/lib/masks/phone/phone-mask.ts b/projects/phone/src/lib/masks/phone/phone-mask.ts index 5dc036217..dd99805ca 100644 --- a/projects/phone/src/lib/masks/phone/phone-mask.ts +++ b/projects/phone/src/lib/masks/phone/phone-mask.ts @@ -1,5 +1,10 @@ import {MASKITO_DEFAULT_OPTIONS, MaskitoOptions} from '@maskito/core'; -import {maskitoCaretGuard, maskitoPrefixPostprocessorGenerator} from '@maskito/kit'; +import { + maskitoAddOnFocusPlugin, + maskitoCaretGuard, + maskitoPrefixPostprocessorGenerator, + maskitoRemoveOnBlurPlugin, +} from '@maskito/kit'; import { AsYouType, CountryCode, @@ -7,10 +12,9 @@ import { MetadataJson, } from 'libphonenumber-js/core'; -import {maskitoAddOnFocusPlugin, maskitoRemoveOnBlurPlugin} from '../../plugins'; import { cutInitCountryCodePreprocessor, - maskitoCutPhonePostprocessorGenerator, + maskitoPhoneLengthPostprocessorGenerator, maskitoValidatePhonePreprocessorGenerator, } from './processors'; import {generatePhoneMask, getPhoneTemplate} from './utils'; @@ -43,7 +47,7 @@ export function maskitoPhoneOptionsGenerator({ ], postprocessors: [ maskitoPrefixPostprocessorGenerator(prefix), - maskitoCutPhonePostprocessorGenerator(metadata, countryIsoCode), + maskitoPhoneLengthPostprocessorGenerator(metadata, countryIsoCode), ], preprocessors: [ cutInitCountryCodePreprocessor({countryIsoCode, metadata}), diff --git a/projects/phone/src/lib/masks/phone/processors/cut-phone-postprocessor.ts b/projects/phone/src/lib/masks/phone/processors/cut-phone-postprocessor.ts deleted file mode 100644 index ca48ff904..000000000 --- a/projects/phone/src/lib/masks/phone/processors/cut-phone-postprocessor.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {MaskitoPostprocessor} from '@maskito/core'; -import {CountryCode, MetadataJson} from 'libphonenumber-js/core'; - -import {cutPhoneByValidLength} from '../utils'; - -const MIN_LENGTH = 3; -export function maskitoCutPhonePostprocessorGenerator( - metadata: MetadataJson, - countryIsoCode: CountryCode, -): MaskitoPostprocessor { - return ({value, selection}) => { - return { - value: - value.length > MIN_LENGTH - ? cutPhoneByValidLength({phone: value, countryIsoCode, metadata}) - : value, - selection, - }; - }; -} diff --git a/projects/phone/src/lib/masks/phone/processors/index.ts b/projects/phone/src/lib/masks/phone/processors/index.ts index f515bf5a8..9dc24f51d 100644 --- a/projects/phone/src/lib/masks/phone/processors/index.ts +++ b/projects/phone/src/lib/masks/phone/processors/index.ts @@ -1,3 +1,3 @@ export * from './cut-init-country-code-preprocessor'; -export * from './cut-phone-postprocessor'; +export * from './phone-length-postprocessor'; export * from './validate-phone-preprocessor'; diff --git a/projects/phone/src/lib/masks/phone/processors/phone-length-postprocessor.ts b/projects/phone/src/lib/masks/phone/processors/phone-length-postprocessor.ts new file mode 100644 index 000000000..5ee77be57 --- /dev/null +++ b/projects/phone/src/lib/masks/phone/processors/phone-length-postprocessor.ts @@ -0,0 +1,18 @@ +import {MaskitoPostprocessor} from '@maskito/core'; +import {CountryCode, MetadataJson} from 'libphonenumber-js/core'; + +import {cutPhoneByValidLength} from '../utils'; + +const MIN_LENGTH = 3; +export function maskitoPhoneLengthPostprocessorGenerator( + metadata: MetadataJson, + countryIsoCode: CountryCode, +): MaskitoPostprocessor { + return ({value, selection}) => ({ + value: + value.length > MIN_LENGTH + ? cutPhoneByValidLength({phone: value, countryIsoCode, metadata}) + : value, + selection, + }); +} diff --git a/projects/phone/src/lib/masks/phone/tests/phone-mask.spec.ts b/projects/phone/src/lib/masks/phone/tests/phone-mask.spec.ts new file mode 100644 index 000000000..5dc33af8a --- /dev/null +++ b/projects/phone/src/lib/masks/phone/tests/phone-mask.spec.ts @@ -0,0 +1,35 @@ +import {MASKITO_DEFAULT_OPTIONS, MaskitoOptions, maskitoTransform} from '@maskito/core'; +import metadata from 'libphonenumber-js/min/metadata'; + +import {maskitoPhoneOptionsGenerator} from '../phone-mask'; + +describe('Phone (maskitoTransform)', () => { + describe('RU number', () => { + let options: MaskitoOptions = MASKITO_DEFAULT_OPTIONS; + + beforeEach(() => { + options = maskitoPhoneOptionsGenerator({ + countryIsoCode: 'RU', + metadata, + }); + }); + + it('full number +7 code', () => { + expect(maskitoTransform('+79202800155', options)).toBe('+7 920 280-01-55'); + }); + + it('full number 8 code', () => { + expect(maskitoTransform('89202800155', options)).toBe('+7 920 280-01-55'); + }); + + it('full number without code', () => { + expect(maskitoTransform('9202800155', options)).toBe('+7 920 280-01-55'); + }); + + it('full number with extra chars', () => { + expect(maskitoTransform('8 (920) 280-01-55', options)).toBe( + '+7 920 280-01-55', + ); + }); + }); +}); diff --git a/projects/phone/src/lib/masks/phone/utils/generate-phone-mask.ts b/projects/phone/src/lib/masks/phone/utils/generate-phone-mask.ts index a9e2accce..bab77855e 100644 --- a/projects/phone/src/lib/masks/phone/utils/generate-phone-mask.ts +++ b/projects/phone/src/lib/masks/phone/utils/generate-phone-mask.ts @@ -1,4 +1,4 @@ -import {MaskExpression} from '@maskito/core'; +import {MaskitoMaskExpression} from '@maskito/core'; import {TEMPLATE_FILLER} from '../constants'; @@ -10,9 +10,9 @@ export function generatePhoneMask({ value: string; template: string; prefix: string; -}): MaskExpression { +}): MaskitoMaskExpression { return [ - ...prefix.split(''), + ...prefix, ...(template ? template .slice(prefix.length) diff --git a/projects/phone/src/lib/plugins/index.ts b/projects/phone/src/lib/plugins/index.ts deleted file mode 100644 index 9fa688301..000000000 --- a/projects/phone/src/lib/plugins/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './add-on-focus'; -export * from './remove-on-blur';