Переключение между режимами ввода

Cleave.js реализует форматирование ввода через конфигурацию, которая задаёт «режим» обработки значения поля. Под режимом ввода в данном контексте понимается набор правил преобразования строки: телефон, дата, числовые значения, кредитные карты или произвольные маски. Переключение между такими режимами означает изменение логики разбиения, вставки разделителей и нормализации вводимого значения без разрушения UX-потока.

Каждый экземпляр библиотеки представляет собой слой над input-элементом и хранит:

  • исходное (raw) значение без форматирования;
  • форматированное значение для отображения;
  • конфигурацию форматирования (options);
  • внутреннее состояние курсора.

Режим ввода определяется набором опций:

  • numeral — числовой режим;
  • date — работа с датами;
  • phone — телефонные номера;
  • creditCard — банковские карты;
  • blocks + delimiter — кастомные маски;
  • prefix / numeralDecimalMark / numeralIntegerScale — числовая кастомизация.

Смена режима — это фактически смена конфигурации интерпретации строки, а не просто форматирование вывода.

Поведение экземпляра при смене конфигурации

После инициализации Cleave.js не предполагает «переключение типа» как статическую операцию. Внутренний процесс привязан к текущим правилам парсинга, поэтому изменение режима требует либо обновления состояния, либо пересоздания экземпляра.

Ключевой момент: форматирование выполняется на основе текущих properties, и они не всегда полностью реактивны.


Подход 1. Полное пересоздание экземпляра

Наиболее предсказуемый способ смены режима — уничтожение текущего экземпляра и создание нового.

let cleaveInstance = new Cleave(input, {
  numeral: true,
  numeralThousandsGroupStyle: 'thousand'
});

function switchToDateMode() {
  cleaveInstance.destroy();

  cleaveInstance = new Cleave(input, {
    date: true,
    datePattern: ['d', 'm', 'Y']
  });
}

Особенности подхода:

  • гарантированная консистентность поведения;
  • сброс внутреннего состояния курсора;
  • необходимость вручную сохранять/восстанавливать значение.

Для сохранения данных используется извлечение raw-значения до уничтожения:

const raw = cleaveInstance.getRawValue();
cleaveInstance.destroy();

После пересоздания значение может быть повторно применено:

cleaveInstance = new Cleave(input, config);
cleaveInstance.setRawValue(raw);

Подход 2. Динамическое изменение параметров экземпляра

Некоторые конфигурации допускают частичную модификацию без пересоздания. Это зависит от конкретной версии и набора опций.

Пример изменения числовых параметров:

cleaveInstance.properties.numeralThousandsGroupStyle = 'lakh';
cleaveInstance.properties.numeralDecimalScale = 3;

Однако подобный подход имеет ограничения:

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

Типичный приём:

const raw = cleaveInstance.getRawValue();

cleaveInstance.properties.numeral = true;
cleaveInstance.properties.date = false;

cleaveInstance.setRawValue(raw);

Недостаток подхода — непредсказуемость при смене несовместимых режимов (например, date → phone).


Подход 3. Переключение через стратегию конфигураций

Практически применяемая архитектура — хранение набора конфигураций и их подстановка.

const configs = {
  phone: {
    phone: true,
    phoneRegionCode: 'KZ'
  },
  date: {
    date: true,
    datePattern: ['d', 'm', 'Y']
  },
  numeral: {
    numeral: true,
    numeralThousandsGroupStyle: 'thousand'
  }
};

Смена режима:

function applyMode(mode) {
  const raw = cleaveInstance.getRawValue();
  cleaveInstance.destroy();

  cleaveInstance = new Cleave(input, configs[mode]);
  cleaveInstance.setRawValue(raw);
}

Данный подход стабилизирует поведение при переходе между несовместимыми режимами и снижает риск артефактов форматирования.


Сохранение и преобразование значения между режимами

При смене режима важно учитывать различие форматов:

  • телефон: строка с кодом страны и группировкой;
  • дата: структурированное значение;
  • число: нормализованная числовая строка.

Поэтому перенос значения часто требует промежуточной нормализации:

const rawValue = cleaveInstance.getRawValue();

function normalize(value, fromMode, toMode) {
  if (fromMode === 'phone' && toMode === 'numeral') {
    return value.replace(/\D/g, '');
  }

  if (fromMode === 'date') {
    return value.replace(/\D/g, '');
  }

  return value;
}

Управление курсором при переключении

Одной из сложностей является потеря позиции каретки. Cleave.js пересчитывает курсор на основе форматирования, и при пересоздании экземпляра это состояние теряется.

Типовой механизм компенсации:

const selectionStart = input.selectionStart;

applyMode('date');

requestAnimationFrame(() => {
  input.setSelectionRange(selectionStart, selectionStart);
});

Однако корректность зависит от длины нового форматированного значения.


Реактивные сценарии переключения

В UI, где режим зависит от состояния (например, выбор типа ввода), применяется декларативная модель:

let mode = 'phone';

function setMode(nextMode) {
  mode = nextMode;
  applyMode(mode);
}

При каждом изменении режима экземпляр пересоздаётся, а конфигурация берётся из централизованного источника.


Интеграция с формами и синхронизация состояния

При переключении режимов важно разделять:

  • отображаемое значение (formatted);
  • бизнес-значение (raw).

Используемая модель:

function getState() {
  return {
    formatted: input.value,
    raw: cleaveInstance.getRawValue()
  };
}

При переключении режимов источником истины остаётся raw-значение.


Гибридный подход: минимизация пересозданий

Для близких режимов возможно частичное обновление без destroy:

cleaveInstance.properties.prefix = '+7';
cleaveInstance.properties.delimiter = '-';

cleaveInstance.setRawValue(cleaveInstance.getRawValue());

Подходит для вариаций одного типа маски, но не для смены класса данных (например, число → дата).


Поведенческие ограничения при смене режима

При переключении необходимо учитывать:

  • несовместимость форматов (date не интерпретирует телефонные символы);
  • необходимость очистки недопустимых символов;
  • отсутствие встроенного автоматического мигратора форматов;
  • зависимость результата от порядка инициализации.

Архитектурная модель переключения

На практике используется трёхуровневая схема:

  1. UI слой — переключение режима;
  2. Controller — хранение активной конфигурации;
  3. Cleave instance manager — пересоздание или обновление экземпляра.
class InputModeManager {
  constructor(input) {
    this.input = input;
    this.instance = null;
  }

  setMode(config) {
    const raw = this.instance ? this.instance.getRawValue() : '';

    if (this.instance) {
      this.instance.destroy();
    }

    this.instance = new Cleave(this.input, config);
    this.instance.setRawValue(raw);
  }
}

Итоговая модель поведения системы режимов

Переключение режимов в Cleave.js сводится к управлению жизненным циклом экземпляра и миграции данных между несовместимыми схемами форматирования. Основная сложность заключается не в самом переключении, а в сохранении семантики данных при смене правил интерпретации ввода.