81 lines
3.1 KiB
JavaScript
81 lines
3.1 KiB
JavaScript
function initPhoneMask(selector = 'input[type="tel"]') {
|
||
const format = (digits) => {
|
||
// Пример маски: +7 (999) 123-45-67
|
||
const d = digits.slice(0, 11); // ограничим 11 цифрами (под РФ)
|
||
if (!d.length) return '';
|
||
|
||
// если начали вводить с 8/7 — приведём к +7
|
||
let rest = d;
|
||
let prefix = '+7';
|
||
if (d[0] === '8') rest = '7' + d.slice(1);
|
||
if (rest[0] === '7') rest = rest.slice(1);
|
||
else {
|
||
// если первая цифра не 7, считаем что пользователь вводит локально, но всё равно показываем +7
|
||
// (при желании можно тут менять логику)
|
||
}
|
||
|
||
const a = rest.slice(0, 3);
|
||
const b = rest.slice(3, 6);
|
||
const c = rest.slice(6, 8);
|
||
const e = rest.slice(8, 10);
|
||
|
||
let out = prefix;
|
||
if (a) out += ` (${a}`;
|
||
if (a && a.length === 3) out += `)`;
|
||
if (b) out += ` ${b}`;
|
||
if (c) out += `-${c}`;
|
||
if (e) out += `-${e}`;
|
||
return out;
|
||
};
|
||
|
||
const applyToInput = (input) => {
|
||
if (input.dataset.phoneMasked === '1') return;
|
||
input.dataset.phoneMasked = '1';
|
||
|
||
// Подсказки браузеру: tel-поле и события input/change поддерживаются стандартно [web:2]
|
||
input.autocomplete = input.autocomplete || 'tel';
|
||
input.inputMode = input.inputMode || 'tel'; // подсказка клавиатуры, особенно на мобилках [web:12]
|
||
|
||
const handler = () => {
|
||
const digits = input.value.replace(/\D/g, '');
|
||
input.value = format(digits);
|
||
};
|
||
|
||
input.addEventListener('input', handler); // события input/change для tel описаны в MDN [web:2]
|
||
input.addEventListener('change', handler); // [web:2]
|
||
};
|
||
|
||
// 1) применяем ко всем уже существующим
|
||
document.querySelectorAll(selector).forEach(applyToInput);
|
||
|
||
// 2) применяем к добавляемым динамически
|
||
const mo = new MutationObserver((mutations) => {
|
||
for (const m of mutations) {
|
||
for (const node of m.addedNodes) {
|
||
if (!(node instanceof Element)) continue;
|
||
if (node.matches?.(selector)) applyToInput(node);
|
||
node.querySelectorAll?.(selector).forEach(applyToInput);
|
||
}
|
||
}
|
||
});
|
||
|
||
mo.observe(document.documentElement, { childList: true, subtree: true });
|
||
return () => mo.disconnect();
|
||
}
|
||
// запуск
|
||
initPhoneMask();
|
||
|
||
function runOnDomUpdate(fn, root = document.documentElement) {
|
||
const observer = new MutationObserver((mutations) => {
|
||
// если важно “не дергать” лишний раз — фильтруем
|
||
if (mutations.some(m => m.type === 'childList' && (m.addedNodes.length || m.removedNodes.length))) {
|
||
fn();
|
||
}
|
||
});
|
||
|
||
observer.observe(root, { childList: true, subtree: true }); // childList/subtree — ключевые опции [web:16]
|
||
return () => observer.disconnect();
|
||
}
|
||
|
||
// пример: заново применить маску к новым инпутам
|
||
const stop = runOnDomUpdate(() => initPhoneMask()); |