@maskito/phone
Version:
The optional framework-agnostic Maskito's package with phone masks
205 lines (191 loc) • 8.01 kB
JavaScript
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 };