Маски ввода редко работают изолированно. Они взаимодействуют с DOM, браузерными событиями, пользовательским вводом, фреймворками, системами валидации и динамическими интерфейсами. Ошибки могут проявляться только в определённых браузерах, при вставке текста из буфера обмена, во время IME-ввода, при мобильной клавиатуре или после повторной инициализации компонента.
Без системы диагностики подобные проблемы превращаются в трудноуловимые дефекты:
Логирование позволяет:
После инициализации библиотека сохраняет экземпляр в DOM-элементе.
const input = document.querySelector("#phone");
Inputmask("+7 (999) 999-99-99").mask(input);
console.log(input.inputmask);
Результат содержит внутренний объект:
Inputmask {
opts: {...},
maskset: {...},
undoValue: "",
el: input,
...
}
Проверка полезна при диагностике:
if (input.inputmask) {
console.log("Маска активна");
}
Либо:
console.log(Inputmask.isValid("79991234567", {
mask: "+7 (999) 999-99-99"
}));
Одной из самых распространённых проблем является расхождение между отображаемым значением и фактическими данными.
console.log(input.value);
console.log(input.inputmask.unmaskedvalue());
Пример:
value:
+7 (999) 123-45-67
unmaskedvalue:
9991234567
Подобная диагностика особенно важна при:
console.log(input.inputmask.isComplete());
Пример применения:
input.addEventListener("blur", () => {
console.log("Complete:", input.inputmask.isComplete());
});
Inputmask активно использует:
Для анализа конфликтов необходимо логировать последовательность событий.
[
"keydown",
"keypress",
"beforeinput",
"input",
"keyup",
"paste",
"focus",
"blur"
].forEach(eventName => {
input.addEventListener(eventName, event => {
console.log(eventName, {
value: input.value,
key: event.key,
inputType: event.inputType
});
});
});
Разные браузеры вызывают события по-разному.
Например:
Chrome:
keydown → beforeinput → input → keyup
Safari:
keydown → input → keyup
Android:
beforeinput → input
Некорректная работа маски часто связана именно с различиями последовательности событий.
Вставка текста — один из наиболее проблемных сценариев.
input.addEventListener("paste", event => {
console.log("PASTE");
const pasted = event.clipboardData.getData("text");
console.log({
pasted,
before: input.value
});
setTimeout(() => {
console.log({
after: input.value
});
});
});
Inputmask({
mask: "+7 (999) 999-99-99",
oncomplete() {
console.log("Маска заполнена");
}
}).mask(input);
Inputmask({
mask: "+7 (999) 999-99-99",
onincomplete() {
console.log("Маска не заполнена");
}
}).mask(input);
Inputmask({
mask: "+7 (999) 999-99-99",
oncleared() {
console.log("Поле очищено");
}
}).mask(input);
Полезен для анализа посимвольного ввода.
Inputmask({
mask: "9999",
onKeyValidation(key, result) {
console.log({
key,
valid: result
});
}
}).mask(input);
Внутреннее состояние маски хранится в maskset.
console.log(input.inputmask.maskset);
Основные поля:
{
validPositions: {...},
tests: {...},
buffer: [...],
p: 5
}
Хранит подтверждённые символы.
console.log(
input.inputmask.maskset.validPositions
);
Пример:
{
0: { input: "9" },
1: { input: "9" },
2: { input: "9" }
}
Используется для диагностики:
console.log(
input.inputmask.maskset.buffer
);
Пример:
[
"+",
"7",
" ",
"(",
"_",
"_",
"_",
")"
]
Буфер помогает понять:
const caret = input.inputmask.caret();
console.log(caret);
Результат:
{
begin: 5,
end: 5
}
input.addEventListener("click", () => {
console.log(
input.inputmask.caret()
);
});
input.addEventListener("keyup", () => {
console.log(
input.inputmask.caret()
);
});
Типичная проблема:
input.value = "123";
Прямое изменение value разрушает внутреннее состояние маски.
Правильный вариант:
input.inputmask.setValue("123");
Для диагностики:
console.log({
value: input.value,
caret: input.inputmask.caret(),
maskset: input.inputmask.maskset
});
try {
Inputmask({
mask: "(999"
}).mask(input);
} catch (error) {
console.error(error);
}
function createMask(selector, config) {
try {
const element = document.querySelector(selector);
if (!element) {
throw new Error("Элемент не найден");
}
Inputmask(config).mask(element);
console.log("Mask initialized");
} catch (error) {
console.error({
selector,
config,
error
});
}
}
Inputmask({
mask: [
"+7 (999) 999-99-99",
"+1 (999) 999-9999"
],
onBeforeMask(value) {
console.log("Before mask:", value);
return value;
}
}).mask(input);
Inputmask({
mask: [
"9999",
"99999"
],
dispatch(appended, dynamicMasked) {
console.log({
appended,
value: dynamicMasked.value
});
return dynamicMasked.compiledMasks[0];
}
});
Regex-маски особенно сложны для отладки.
Inputmask({
regex: "[A-Za-z]{3}[0-9]{2}"
}).mask(input);
Для анализа:
input.addEventListener("input", () => {
console.log({
value: input.value,
complete: input.inputmask.isComplete()
});
});
console.time("mask-init");
Inputmask({
mask: "+7 (999) 999-99-99"
}).mask(input);
console.timeEnd("mask-init");
console.time("all-masks");
document.querySelectorAll("input").forEach(input => {
Inputmask("9999").mask(input);
});
console.timeEnd("all-masks");
input.addEventListener("input", () => {
performance.mark("input-start");
requestAnimationFrame(() => {
performance.mark("input-end");
performance.measure(
"mask-input",
"input-start",
"input-end"
);
console.log(
performance.getEntriesByName("mask-input")
);
});
});
Опасный сценарий:
setInterval(() => {
Inputmask("9999").mask(input);
}, 1000);
Диагностика:
console.count("MASK_INIT");
input.inputmask.remove();
После удаления:
console.log(input.inputmask);
Должно быть:
undefined
const observer = new MutationObserver(records => {
console.log(records);
});
observer.observe(document.body, {
childList: true,
subtree: true
});
Позволяет выявлять:
Основная проблема — повторный рендер.
Неправильно:
<Input value={state} />
Правильно диагностировать через:
useEffect(() => {
console.log("mounted");
return () => {
console.log("unmounted");
};
}, []);
useEffect(() => {
console.count("MASK_CREATE");
Inputmask("9999").mask(ref.current);
}, []);
onMounted(() => {
console.log("mounted");
});
onUnmounted(() => {
console.log("unmounted");
});
Для японского, китайского и корейского ввода:
input.addEventListener(
"compositionstart",
() => {
console.log("compositionstart");
}
);
input.addEventListener(
"compositionupdate",
event => {
console.log(
"compositionupdate",
event.data
);
}
);
input.addEventListener(
"compositionend",
event => {
console.log(
"compositionend",
event.data
);
}
);
Android часто нарушает стандартную последовательность событий.
Полезное логирование:
[
"keydown",
"beforeinput",
"input",
"keyup"
].forEach(eventName => {
input.addEventListener(eventName, event => {
console.log({
event: eventName,
value: input.value,
inputType: event.inputType
});
});
});
function createDebugMask(element, options) {
console.log("INIT", element);
const mask = new Inputmask({
...options,
oncomplete() {
console.log("COMPLETE");
},
onincomplete() {
console.log("INCOMPLETE");
},
oncleared() {
console.log("CLEARED");
},
onKeyValidation(key, valid) {
console.log("KEY", {
key,
valid
});
}
});
mask.mask(element);
[
"focus",
"blur",
"input",
"paste"
].forEach(eventName => {
element.addEventListener(eventName, event => {
console.log(eventName, {
value: element.value,
caret: element.inputmask.caret()
});
});
});
return mask;
}
try {
Inputmask(config).mask(input);
} catch (error) {
Sentry.captureException(error, {
extra: {
config,
value: input.value
}
});
}
console.group("Inputmask");
console.log("Value:", input.value);
console.log(
"Unmasked:",
input.inputmask.unmaskedvalue()
);
console.log(
"Complete:",
input.inputmask.isComplete()
);
console.groupEnd();
Проверка:
console.log(typeof Inputmask);
Должно быть:
function
Также:
console.log(input);
Диагностика:
input.addEventListener("keydown", e => {
console.log(e.key);
});
Проверка:
console.log(input.disabled);
console.log(input.readOnly);
Причина:
Диагностика:
console.count("render");
Проверка:
console.log(
input.inputmask.maskset.validPositions
);
Часто причина:
Диагностика:
form.addEventListener("submit", event => {
console.log({
masked: input.value,
unmasked:
input.inputmask.unmaskedvalue()
});
});
const original = Inputmask.prototype.writeBuffer;
Inputmask.prototype.writeBuffer = function(...args) {
console.log("writeBuffer", args);
return original.apply(this, args);
};
const originalSetValue =
HTMLInputElement.prototype.value;
Object.defineProperty(
HTMLInputElement.prototype,
"value",
{
set(value) {
console.log("SET VALUE", value);
return originalSetValue.call(this, value);
}
}
);
setInterval(() => {
debug.innerHTML = `
<pre>
value: ${input.value}
unmasked: ${input.inputmask.unmaskedvalue()}
complete: ${input.inputmask.isComplete()}
caret: ${
JSON.stringify(
input.inputmask.caret(),
null,
2
)
}
</pre>
`;
}, 100);
console.log(Inputmask);
console.log(input);
console.log(input.inputmask);
input.addEventListener("input", () => {
console.log(input.value);
});
console.log(
input.inputmask.maskset
);
console.time("typing");
input.inputmask.remove();