Skip to content

Валидация

Встроенные валидаторы

ts
import {
  required,
  minLength,
  maxLength,
  min,
  max,
  pattern,
  email,
  url,
  sameAs,
  fileType,
  fileSize,
  fileCount,
} from '@macrulez/vue-form-schema'
ФункцияОписание
requiredПроваливается для null, undefined, '' или пустого массива
minLength(n, msg?)Минимальная длина строки или массива
maxLength(n, msg?)Максимальная длина строки или массива
min(n, msg?)Числовой минимум
max(n, msg?)Числовой максимум
pattern(re, msg?)Совпадение с regex
emailБазовый формат email
urlКорректный URL (new URL())
sameAs(field, msg?)Значение должно совпадать с другим полем
fileType(types[], msg?)Белый список MIME-типа или расширения файла
fileSize(bytes, msg?)Максимальный размер файла
fileCount(n, msg?)Максимальное количество файлов

Кастомные валидаторы

Синхронные

ts
import type { ValidatorFn } from '@macrulez/vue-form-schema'

const noSpaces: ValidatorFn = (value) =>
  typeof value === 'string' && value.includes(' ') ? 'No spaces allowed' : null

Асинхронные

Асинхронные валидаторы дебаунсятся (300 мс), пока пользователь вводит текст или переводит фокус с поля. Ошибки объединяются в errors после разрешения. При submit() асинхронные валидаторы запускаются немедленно (минуя дебаунс) и ожидаются, так что незавершённая проверка (например, «имя пользователя занято») блокирует отправку, а не разрешается уже после того, как onSubmit сработал.

ts
import type { AsyncValidatorFn } from '@macrulez/vue-form-schema'

const uniqueUsername: AsyncValidatorFn = async (value) => {
  const { taken } = await fetch(`/api/check?q=${value}`).then((r) => r.json())
  return taken ? 'Username is taken' : null
}

Несколько ошибок на поле (validateMode)

ts
useForm({
  schema,
  validateMode: 'all', // собирать все ошибки для поля (по умолчанию: 'first')
})

Кросс-полевая валидация

Используйте sameAs для подтверждения пароля или напишите свой валидатор — все валидаторы получают allValues вторым аргументом.

ts
import { sameAs } from '@macrulez/vue-form-schema'

const schema: FieldDefinition[] = [
  { type: 'text', name: 'password', label: 'Password', required: true },
  {
    type: 'text',
    name: 'confirm',
    label: 'Confirm password',
    validators: [sameAs('password', 'Passwords must match')],
  },
]

Условные поля

visible и disabled могут быть булевым значением, реактивной функцией или безопасным строковым выражением.

ts
const schema: FieldDefinition[] = [
  { type: 'checkbox', name: 'hasCompany', label: 'I represent a company' },
  {
    type: 'text',
    name: 'companyName',
    label: 'Company name',
    visible: (values) => values['hasCompany'] === true,
    required: true,
  },
  // строковое выражение — имеет доступ к переменной `values`
  {
    type: 'select',
    name: 'drink',
    label: 'Drink',
    visible: 'values.age >= 18',
    options: [
      { label: 'Beer', value: 'beer' },
      { label: 'Water', value: 'water' },
    ],
  },
]

Задайте clearOnHide: true в useForm, чтобы автоматически сбрасывать значение скрытого поля.

Динамические опции

options может быть статическим массивом, синхронной функцией или асинхронной функцией.

ts
// Синхронно — переоценивается при каждом изменении values
{
  type: 'select',
  name: 'city',
  options: (values) => citiesByCountry[values['country'] as string] ?? [],
}

// Асинхронно — загружается при монтировании и повторно при изменении optionsDeps
{
  type: 'select',
  name: 'framework',
  optionsDeps: ['language'],
  options: async (values) => {
    const res = await fetch(`/api/frameworks?lang=${values['language']}`)
    return res.json()
  },
}

Пока идёт загрузка, optionsLoading.value['framework'] равно true, и select отключён в FormRenderer. Доступ к состоянию загрузки напрямую — через form.optionsLoading.

Вычисляемый defaultValue

defaultValue также может быть функцией, вычисляемой при инициализации формы с уже разрешёнными частичными значениями в качестве контекста:

ts
{
  type: 'text',
  name: 'displayName',
  defaultValue: (values) => `${values.firstName} ${values.lastName}`,
}