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());