Адаптация для Svelte

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

Cleave.js предоставляет форматирование ввода для различных типов данных: номера банковских карт, телефоны, даты, числовые значения. В Svelte это требует аккуратного связывания экземпляра Cleave с input-элементом и управления его жизненным циклом через onMount и onDestroy.


Установка зависимостей и базовая подготовка

Библиотека подключается стандартным способом через пакетный менеджер:

npm install cleave.js

В рамках Svelte-проекта важно учитывать, что Cleave.js работает только в браузерной среде, поэтому любые обращения к нему должны выполняться после монтирования компонента.


Базовая интеграция через onMount

Основной механизм подключения заключается в создании экземпляра Cleave внутри onMount:

<script>
  import { onMount, onDestroy } from "svelte";
  import Cleave from "cleave.js";

  let input;
  let cleave;

  onMount(() => {
    cleave = new Cleave(input, {
      creditCard: true,
      onValueChanged: (e) => {
        // синхронизация значения с состоянием Svelte
      }
    });
  });

  onDestroy(() => {
    if (cleave) cleave.destroy();
  });
</script>

<input bind:this={input} />

Ключевой момент заключается в использовании bind:this, который предоставляет прямую ссылку на DOM-элемент. Это позволяет передать элемент в Cleave без обходных решений.


Синхронизация состояния Svelte с Cleave.js

Svelte использует реактивные переменные, поэтому требуется двусторонняя синхронизация: изменения в input должны обновлять состояние, а изменения состояния должны корректно отражаться в Cleave.

<script>
  import { onMount } from "svelte";
  import Cleave from "cleave.js";

  let input;
  let value = "";

  let cleave;

  onMount(() => {
    cleave = new Cleave(input, {
      numeral: true,
      onValueChanged: (e) => {
        value = e.target.rawValue;
      }
    });
  });
</script>

<input bind:this={input} bind:value={value} />

Здесь используется rawValue, который предоставляет неформатированное значение, что особенно важно при работе с числовыми данными и отправкой на сервер.


Управление реактивностью и обновлением внешнего значения

В Svelte переменная value может изменяться извне, например, при загрузке данных или reset-операциях. Cleave.js не отслеживает изменения автоматически, поэтому требуется ручная синхронизация:

<script>
  import { onMount } from "svelte";
  import Cleave from "cleave.js";

  let input;
  let cleave;
  export let value = "";

  onMount(() => {
    cleave = new Cleave(input, {
      date: true,
      datePattern: ["d", "m", "Y"]
    });
  });

  $: if (cleave && value !== cleave.getRawValue()) {
    cleave.setRawValue(value);
  }
</script>

<input bind:this={input} />

Реактивное выражение $: обеспечивает синхронизацию внешнего состояния с внутренним состоянием Cleave.


Инкапсуляция через Svelte Action

Более идиоматичный подход Svelte заключается в использовании actions. Это позволяет полностью отделить логику форматирования от компонента.

// cleaveAction.js
import Cleave from "cleave.js";

export function cleave(node, options) {
  const instance = new Cleave(node, options);

  return {
    upd ate(newOptions) {
      instance.destroy();
      return new Cleave(node, newOptions);
    },
    destroy() {
      instance.destroy();
    }
  };
}

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

<script>
  import { cleave } from "./cleaveAction";

  let value = "";
</script>

<input use:cleave={{ numeral: true }} bind:value />

Action-подход делает интеграцию масштабируемой и повторно используемой.


Работа с различными типами форматирования

Форматирование номеров телефонов

<input
  use:cleave={{
    phone: true,
    phoneRegionCode: "KZ"
  }}
/>

Cleave.js автоматически подстраивает формат под региональные правила, что снижает необходимость ручной обработки строк.


Форматирование дат

<input
  use:cleave={{
    date: true,
    datePattern: ["Y", "m", "d"]
  }}
/>

Особенность интеграции заключается в том, что Svelte не должен хранить форматированное значение — только логическое представление даты.


Числовые значения и валюты

<input
  use:cleave={{
    numeral: true,
    numeralThousandsGroupStyle: "thousand"
  }}
/>

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


Обработка событий и расширенная логика

Cleave.js предоставляет событие onValueChanged, которое является ключевым механизмом интеграции с Svelte.

<script>
  import Cleave from "cleave.js";
  import { createEventDispatcher, onMount } from "svelte";

  const dispatch = createEventDispatcher();
  let input;

  onMount(() => {
    new Cleave(input, {
      creditCard: true,
      onValueChanged: (e) => {
        dispatch("change", {
          value: e.target.value,
          rawValue: e.target.rawValue
        });
      }
    });
  });
</script>

<input bind:this={input} />

Такой подход позволяет интегрировать Cleave.js в более сложные формы с централизованной обработкой состояния.


Проблемы SSR и гидратации

Svelte может использовать серверный рендеринг, однако Cleave.js требует доступа к window и DOM. Поэтому любые обращения должны быть строго ограничены клиентским циклом.

Типичная ошибка — инициализация Cleave вне onMount, что приводит к сбоям при SSR.

Корректная стратегия:

  • инициализация только в onMount
  • отсутствие прямых импортов с побочными эффектами
  • проверка наличия DOM-элемента
if (typeof window !== "undefined") {
  // безопасная инициализация
}

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

В некоторых сценариях пересоздание Cleave.js приводит к потере позиции курсора. Поэтому предпочтительно использовать методы обновления:

$: if (cleave) {
  cleave.setPhoneRegionCode(region);
}

или

$: if (cleave) {
  cleave.setRawValue(value);
}

Подход зависит от типа конфигурации и версии библиотеки.


Масштабирование в крупных формах

При работе с большими формами рекомендуется централизовать управление экземплярами Cleave:

  • хранение ссылок на инстансы в Map
  • унификация actions
  • разделение форматирования и бизнес-логики

Пример стратегии:

const instances = new Map();

export function cleave(node, options) {
  const instance = new Cleave(node, options);
  instances.se t(node, instance);

  return {
    destroy() {
      instance.destroy();
      instances.delete(node);
    }
  };
}

Такой подход предотвращает утечки памяти при динамическом создании форм.


Согласование с валидаторами Svelte-форм

При использовании сторонних валидаторов важно различать:

  • отображаемое значение (formatted)
  • логическое значение (raw)

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