UNPKG

@maskito/phone

Version:

The optional framework-agnostic Maskito's package with phone masks

205 lines (191 loc) 8.01 kB
import { MASKITO_DEFAULT_OPTIONS } from '@maskito/core'; import { parsePhoneNumber, getCountryCallingCode, validatePhoneNumberLength, AsYouType } from 'libphonenumber-js/core'; import { maskitoPrefixPostprocessorGenerator, maskitoCaretGuard } from '@maskito/kit'; const TEMPLATE_FILLER = 'x'; /** * This preprocessor works only once at initialization phase (when `new Maskito(...)` is executed). * This preprocessor helps to avoid conflicts during transition from one mask to another (for the same input). */ function cutInitCountryCodePreprocessor({ countryIsoCode, metadata, }) { let isInitializationPhase = true; return ({ elementState, data }) => { if (!isInitializationPhase) { return { elementState, data }; } const { value, selection } = elementState; isInitializationPhase = false; try { const phone = parsePhoneNumber(value, countryIsoCode, metadata); const code = getCountryCallingCode(countryIsoCode, metadata); const newValue = `+${code} ${phone.nationalNumber}`; return { elementState: { value: newValue, selection, }, }; } catch (_a) { return { elementState, }; } }; } function cutPhoneByValidLength({ phone, metadata, }) { const validationResult = validatePhoneNumberLength(phone, metadata); if (validationResult === 'TOO_LONG') { return cutPhoneByValidLength({ phone: phone.slice(0, phone.length - 1), metadata, }); } return phone; } function generatePhoneMask({ value, template, prefix, }) { return [ ...prefix, ...(template ? template .slice(prefix.length) .split('') .map((сhar) => сhar === TEMPLATE_FILLER || /\d/.test(сhar) ? /\d/ : сhar) : new Array(Math.max(value.length - prefix.length, prefix.length)).fill(/\d/)), ]; } function maskitoGetCountryFromNumber(number, metadata) { const formatter = new AsYouType({}, metadata); formatter.input(number); return formatter.getCountry(); } function getPhoneTemplate(formatter, value, separator) { formatter.input(value.replaceAll(/[^\d+]/g, '')); const initialTemplate = formatter.getTemplate(); const split = initialTemplate.split(' '); const template = split.length > 1 ? `${split.slice(0, 2).join(' ')} ${split.slice(2).join(separator)}` : initialTemplate; formatter.reset(); return template.trim(); } function selectTemplate({ currentTemplate, newTemplate, currentPhoneLength, newPhoneLength, }) { return newTemplate.length < currentTemplate.length && newPhoneLength > currentPhoneLength ? currentTemplate : newTemplate; } const MIN_LENGTH = 3; function phoneLengthPostprocessorGenerator(metadata) { return ({ value, selection }) => ({ value: value.length > MIN_LENGTH ? cutPhoneByValidLength({ phone: value, metadata }) : value, selection, }); } function validatePhonePreprocessorGenerator({ prefix, countryIsoCode, metadata, }) { return ({ elementState, data }) => { var _a; const { selection, value } = elementState; const [from] = selection; const selectionIncludesPrefix = from < prefix.length; const cleanCode = prefix.trim(); // handling autocomplete if (value && !value.startsWith(cleanCode) && !data) { const formatter = new AsYouType({ defaultCountry: countryIsoCode }, metadata); formatter.input(value); const numberValue = (_a = formatter.getNumberValue()) !== null && _a !== void 0 ? _a : ''; formatter.reset(); return { elementState: { value: formatter.input(numberValue), selection } }; } try { const validationError = validatePhoneNumberLength(data, { defaultCountry: countryIsoCode }, metadata); if (!validationError || validationError === 'TOO_SHORT') { // handle paste-event with different code, for example for 8 / +7 const phone = countryIsoCode ? parsePhoneNumber(data, countryIsoCode, metadata) : parsePhoneNumber(data, metadata); const { nationalNumber, countryCallingCode } = phone; return { elementState: { selection, value: selectionIncludesPrefix ? '' : prefix, }, data: selectionIncludesPrefix ? `+${countryCallingCode} ${nationalNumber}` : nationalNumber, }; } } catch (_b) { return { elementState }; } return { elementState }; }; } function maskitoPhoneNonStrictOptionsGenerator({ defaultIsoCode, metadata, separator = '-', }) { const formatter = new AsYouType(defaultIsoCode, metadata); const prefix = '+'; let currentTemplate = ''; let currentPhoneLength = 0; return Object.assign(Object.assign({}, MASKITO_DEFAULT_OPTIONS), { mask: ({ value }) => { const newTemplate = getPhoneTemplate(formatter, value, separator); const newPhoneLength = value.replaceAll(/\D/g, '').length; currentTemplate = selectTemplate({ currentTemplate, newTemplate, currentPhoneLength, newPhoneLength, }); currentPhoneLength = newPhoneLength; return currentTemplate.length === 1 ? ['+', /\d/] : generatePhoneMask({ value, template: currentTemplate, prefix }); }, preprocessors: [ validatePhonePreprocessorGenerator({ prefix, countryIsoCode: defaultIsoCode, metadata, }), ], postprocessors: [phoneLengthPostprocessorGenerator(metadata)] }); } function maskitoPhoneStrictOptionsGenerator({ countryIsoCode, metadata, separator = '-', }) { const code = getCountryCallingCode(countryIsoCode, metadata); const formatter = new AsYouType(countryIsoCode, metadata); const prefix = `+${code} `; let currentTemplate = ''; let currentPhoneLength = 0; return Object.assign(Object.assign({}, MASKITO_DEFAULT_OPTIONS), { mask: ({ value }) => { const newTemplate = getPhoneTemplate(formatter, value, separator); const newPhoneLength = value.replaceAll(/\D/g, '').length; currentTemplate = selectTemplate({ currentTemplate, newTemplate, currentPhoneLength, newPhoneLength, }); currentPhoneLength = newPhoneLength; return generatePhoneMask({ value, template: currentTemplate, prefix }); }, plugins: [ maskitoCaretGuard((value, [from, to]) => [ from === to ? prefix.length : 0, value.length, ]), ], preprocessors: [ cutInitCountryCodePreprocessor({ countryIsoCode, metadata }), validatePhonePreprocessorGenerator({ prefix, countryIsoCode, metadata }), ], postprocessors: [ maskitoPrefixPostprocessorGenerator(prefix), phoneLengthPostprocessorGenerator(metadata), ] }); } function maskitoPhoneOptionsGenerator({ countryIsoCode, metadata, strict = true, separator = '-', }) { return strict && countryIsoCode ? maskitoPhoneStrictOptionsGenerator({ countryIsoCode, metadata, separator }) : maskitoPhoneNonStrictOptionsGenerator({ defaultIsoCode: countryIsoCode, metadata, separator, }); } export { maskitoGetCountryFromNumber, maskitoPhoneOptionsGenerator };