Отладка масок

Маски ввода создают дополнительный слой логики между пользователем и DOM-элементом. Во время ввода символов библиотека:

  • анализирует позицию курсора;
  • проверяет допустимость символов;
  • изменяет буфер;
  • синхронизирует отображаемое значение;
  • управляет caret-позицией;
  • обрабатывает события клавиатуры, вставки и удаления.

Из-за этого ошибки в масках часто выглядят неочевидно:

  • курсор «прыгает»;
  • часть символов исчезает;
  • значение не проходит валидацию;
  • вставка из буфера работает некорректно;
  • маска ломается после обновления DOM;
  • события вызываются несколько раз;
  • значение в input.value отличается от ожидаемого.

Отладка Inputmask требует понимания внутренних механизмов библиотеки и последовательности обработки ввода.


Типичные категории ошибок

Ошибки конфигурации

Наиболее распространённая категория проблем связана с неверными опциями.

Пример:

Inputmask({
    mask: "99/99/9999",
    greedy: false,
    repeat: 10
}).mask(input);

Опция repeat не имеет смысла для фиксированной маски и может приводить к неожиданному поведению.

Другой пример:

Inputmask({
    alias: "numeric",
    groupSeparator: ",",
    radixPoint: ","
}).mask(input);

Использование одинакового символа для разделителя групп и десятичной части приводит к конфликту парсинга.


Ошибки жизненного цикла DOM

Маска может быть уничтожена или потеряна после:

  • ререндеринга React;
  • обновления Vue-компонента;
  • замены DOM-узла;
  • динамического innerHTML;
  • клонирования элементов.

Пример проблемы:

container.innerHTML = `
    <input id="phone">
`;

Inputmask("+7 (999) 999-99-99").mask("#phone");

После повторного изменения innerHTML маска исчезнет, потому что DOM-элемент заменяется новым экземпляром.


Конфликты событий

Inputmask активно использует:

  • keydown
  • keypress
  • input
  • paste
  • composition
  • blur
  • focus

Если поверх поля добавляются собственные обработчики, возможны конфликты.

Пример:

input.addEventListener("input", () => {
    input.value = input.value.toUpperCase();
});

Такой код вмешивается в работу внутреннего буфера Inputmask.

Следствия:

  • потеря позиции курсора;
  • двойная обработка;
  • повреждение буфера маски.

Ошибки интеграции с фреймворками

Особенно часто проблемы возникают в:

  • React;
  • Vue;
  • Angular;
  • Svelte.

Причина — конфликт между виртуальным DOM и прямой модификацией поля библиотекой Inputmask.

Пример для React:

<input value={value} onCha nge={handleChange} />

Inputmask изменяет DOM напрямую, а React затем пытается восстановить собственное состояние.

Результат:

  • мигание текста;
  • невозможность ввода;
  • потеря символов.

Инструменты отладки

Проверка instance Inputmask

После применения маски экземпляр сохраняется внутри DOM-элемента.

Проверка:

console.log(input.inputmask);

Можно исследовать:

console.log(input.inputmask.opts);
console.log(input.inputmask.unmaskedvalue());
console.log(input.inputmask.isComplete());

Это позволяет быстро понять:

  • какие опции реально применились;
  • как библиотека воспринимает текущее значение;
  • считается ли ввод завершённым.

Анализ буфера

Inputmask хранит внутренний буфер символов.

Получение буфера:

console.log(input.inputmask.maskset.buffer);

Пример результата:

[
 "1",
 "2",
 "/",
 "0",
 "5",
 "/",
 "_",
 "_",
 "_",
 "_"
]

Буфер помогает диагностировать:

  • потерю символов;
  • некорректную вставку;
  • ошибки позиционирования;
  • проблемы optional-секций.

Просмотр maskset

Внутренняя структура:

console.log(input.inputmask.maskset);

Содержит:

  • валидаторы;
  • позиции;
  • metadata;
  • тесты;
  • кэш.

Особенно полезно поле:

validPositions

Пример:

console.log(
    input.inputmask.maskset.validPositions
);

Оно показывает, какие символы реально приняты маской.


Логирование событий

Критически важно понимать последовательность событий.

Пример:

[
    "keydown",
    "keypress",
    "input",
    "keyup",
    "blur"
].forEach(eventName => {
    input.addEventListener(eventName, event => {
        console.log(eventName, event.target.value);
    });
});

Позволяет обнаружить:

  • дублирование событий;
  • неожиданные изменения value;
  • вмешательство стороннего кода.

Проверка реального значения

value против unmaskedvalue

Многие ошибки возникают из-за путаницы между отображаемым и чистым значением.

Пример:

input.value

Результат:

+7 (999) 123-45-67

Но:

input.inputmask.unmaskedvalue()

Результат:

9991234567

Если backend ожидает одно значение, а отправляется другое — появляются ошибки валидации.


removeMaskOnSubmit

Опция:

removeMaskOnSubmit: true

Изменяет данные формы непосредственно перед submit.

Проблемы:

  • сервер получает неожиданный формат;
  • frontend-валидация работает с другим значением;
  • AJAX-отправка и обычная форма ведут себя по-разному.

Проверка:

form.addEventListener("submit", event => {
    console.log(input.value);
});

Отладка курсора

Проблема caret-позиции

Inputmask постоянно управляет курсором.

Типичные симптомы:

  • курсор перескакивает в конец;
  • невозможно удалить символ;
  • вставка происходит не в той позиции.

Проверка текущей позиции

console.log(
    input.selectionStart,
    input.selectionEnd
);

Во время событий:

input.addEventListener("input", () => {
    console.log(input.selectionStart);
});

Конфликт ручного управления caret

Ошибка:

input.setSelectionRange(0, 0);

Если код вызывается одновременно с внутренней логикой Inputmask, курсор начинает вести себя нестабильно.

Особенно опасны:

  • setTimeout;
  • асинхронные обновления;
  • React state update.

Отладка вставки из буфера

paste-события

Inputmask имеет собственную обработку вставки.

Проверка содержимого:

input.addEventListener("paste", event => {
    console.log(
        event.clipboardData.getData("text")
    );
});

sanitize при вставке

Часто проблема связана с неожиданными символами.

Пример:

+7 999 123-45-67

Здесь может использоваться:

  • non-breaking hyphen;
  • unicode space;
  • невидимые символы.

Диагностика:

const text = event.clipboardData.getData("text");

console.log(
    [...text].map(char => char.charCodeAt(0))
);

Отладка optional-блоков

Неоднозначность маски

Пример:

mask: "(999) 999[-9999]"

Inputmask должен определить:

  • пользователь завершил ввод;
  • optional-секция нужна;
  • символ относится к основной части или дополнительной.

Такие маски часто вызывают сложные баги.


Проверка поведения optional

Полезно включать:

clearIncomplete: false

И анализировать:

console.log(
    input.inputmask.isComplete()
);

Отладка numeric alias

Частые проблемы numeric

Наиболее нестабильная категория масок:

Inputmask("numeric", {
    digits: 2,
    radixPoint: ",",
    groupSeparator: " "
});

Причины:

  • локализация;
  • преобразование числа;
  • floating-point;
  • автогруппировка;
  • отрицательные значения.

Проверка unmaskedvalue

console.log(
    input.inputmask.unmaskedvalue()
);

Результат может отличаться от визуального значения.

Например:

123456

вместо:

123 456,00

Проверка autoGroup

Опция:

autoGroup: true

Иногда вызывает проблемы с caret.

Особенно:

  • при удалении;
  • при вставке;
  • при быстром вводе.

Диагностика:

autoGroup: false

Если ошибка исчезает — проблема связана с группировкой.


Отладка динамических масок

Маски с alternator

Пример:

mask: [
    "+7 (999) 999-99-99",
    "+1 (999) 999-9999"
]

Inputmask автоматически выбирает шаблон.

Проблемы:

  • внезапное переключение;
  • потеря символов;
  • неправильная ветка.

Анализ active mask

Проверка:

console.log(
    input.inputmask.maskset
);

Особенно полезны:

  • tests;
  • jitOffset;
  • validPositions.

Использование onBeforeMask

Предварительная обработка

Очень полезно для диагностики входных данных.

Пример:

Inputmask({
    mask: "+7 (999) 999-99-99",

    onBeforeMask(value) {
        console.log(value);

        return value.replace(/\D/g, "");
    }
});

Позволяет увидеть:

  • что реально приходит в маску;
  • какие символы ломают обработку;
  • как Inputmask получает исходные данные.

Использование onBeforePaste

Анализ вставляемых данных

Inputmask({
    mask: "+7 (999) 999-99-99",

    onBeforePaste(value) {
        console.log(value);

        return value.replace(/\D/g, "");
    }
});

Позволяет диагностировать:

  • мусорные символы;
  • неожиданный формат;
  • проблемы локализации.

Отладка oncomplete и onincomplete

Проверка завершённости

Inputmask({
    mask: "99/99/9999",

    oncomplete() {
        console.log("complete");
    },

    onincomplete() {
        console.log("incomplete");
    }
});

Часто помогает обнаружить:

  • скрытые optional-секции;
  • неверную длину;
  • ошибки в regex.

Проверка destroy/remove

Утечки и двойная инициализация

Ошибка:

Inputmask(mask).mask(input);
Inputmask(mask).mask(input);

Маска инициализируется повторно поверх существующей.

Следствия:

  • дублирование событий;
  • деградация производительности;
  • нестабильное поведение.

Безопасное удаление

if (input.inputmask) {
    input.inputmask.remove();
}

После этого:

Inputmask(mask).mask(input);

Отладка производительности

Симптомы проблем

Признаки:

  • задержки ввода;
  • лаги при печати;
  • зависание курсора;
  • высокий CPU usage.

Причины деградации

Слишком сложные regex

Пример:

regex: "([0-9]{1,10}|[A-Z]{1,10})*"

Сложные регулярные выражения резко увеличивают стоимость проверки.


Огромные alternator-маски

Пример:

mask: [
    "999999999",
    "AAAAAAAAA",
    "***-***-***",
    ...
]

Каждый ввод требует перебора вариантов.


Частый remask

Ошибка:

setInterval(() => {
    Inputmask(mask).mask(input);
}, 100);

Маска создаётся заново десятки раз в секунду.


Отладка в React

Основная проблема controlled inputs

Плохой пример:

<input
    value={value}
    onCha nge={e => setValue(e.target.value)}
/>

Inputmask и React одновременно контролируют поле.


Более стабильный вариант

const ref = useRef();

useEffect(() => {
    Inputmask("+7 (999) 999-99-99")
        .mask(ref.current);
}, []);

return <input ref={ref} />;

Проверка размонтирования

Важно удалять маску:

useEffect(() => {
    const instance = new Inputmask(mask);

    instance.mask(ref.current);

    return () => {
        ref.current.inputmask?.remove();
    };
}, []);

Отладка в Vue

Проблемы v-model

Пример конфликта:

<input v-model="phone">

Inputmask меняет DOM, а Vue синхронизирует значение обратно.


Более безопасная схема

mounted() {
    Inputmask(mask).mask(this.$refs.phone);
}

Отладка мобильных устройств

Особенности mobile input

Мобильные клавиатуры:

  • изменяют события;
  • используют autocomplete;
  • применяют autocorrect;
  • могут не генерировать keypress.

Проверка inputType

input.addEventListener("input", event => {
    console.log(event.inputType);
});

Например:

insertText
deleteContentBackward
insertFromPaste

Диагностика race conditions

Асинхронные обновления

Опасный код:

input.addEventListener("input", async () => {
    await save();

    input.value = normalize(input.value);
});

Пока выполняется await, состояние Inputmask уже изменилось.


Признаки race condition

  • случайные ошибки;
  • нестабильный caret;
  • баг проявляется не всегда;
  • проблема исчезает при breakpoint.

Режим минимальной конфигурации

Один из лучших способов диагностики — постепенное упрощение маски.

Исходная конфигурация:

Inputmask({
    alias: "numeric",
    digits: 2,
    autoGroup: true,
    groupSeparator: " ",
    radixPoint: ",",
    rightAlign: false,
    nullable: false,
    clearIncomplete: true,
    placeholder: "0"
});

Минимальный вариант:

Inputmask("numeric");

Если проблема исчезает, опции добавляются постепенно до обнаружения источника ошибки.


Стратегия пошаговой локализации ошибок

Этап 1 — проверка DOM

console.log(input);
console.log(input.inputmask);

Этап 2 — анализ событий

input.addEventListener("input", console.log);

Этап 3 — анализ value

console.log(input.value);
console.log(input.inputmask.unmaskedvalue());

Этап 4 — проверка буфера

console.log(
    input.inputmask.maskset.buffer
);

Этап 5 — отключение стороннего кода

Необходимо временно удалить:

  • React state sync;
  • кастомные validators;
  • formatters;
  • debounce;
  • async handlers.

Этап 6 — минимальная маска

Inputmask("9999").mask(input);

Если ошибка исчезает, проблема связана со сложностью исходной конфигурации.