Skip to content

Основы схемы

Справочник FieldDefinition

ts
interface FieldDefinition {
  // ─── Обязательное ────────────────────────────────────────────────────────
  type:
    | 'text'
    | 'number'
    | 'email'
    | 'select'
    | 'checkbox'
    | 'radio'
    | 'textarea'
    | 'date'
    | 'array'
    | 'group'
    | 'file'

  /** Плоский dot-path ключ в объекте значений, например "address.city" */
  name: string

  // ─── Отображение ──────────────────────────────────────────────────────────
  label?: string
  placeholder?: string

  // ─── Начальное значение ───────────────────────────────────────────────────
  /** Статическое значение или функция, вызываемая при инициализации с уже разрешёнными частичными значениями */
  defaultValue?: unknown | ((values: Record<string, unknown>) => unknown)

  // ─── Ограничения ──────────────────────────────────────────────────────────
  required?: boolean
  disabled?: boolean | ((values: Record<string, unknown>) => boolean)
  /** Булево значение, функция или строковое выражение, вычисляемое над живыми значениями */
  visible?: boolean | string | ((values: Record<string, unknown>) => boolean)

  // ─── Валидация ────────────────────────────────────────────────────────────
  validators?: ValidatorFn[]
  asyncValidators?: AsyncValidatorFn[]

  // ─── Маскирование ─────────────────────────────────────────────────────────
  mask?: string | MaskConfig

  // ─── опции select / radio ─────────────────────────────────────────────────
  /** Статический массив, синхронная функция или асинхронная функция */
  options?:
    | FieldOption[]
    | ((values: Record<string, unknown>) => FieldOption[])
    | ((values: Record<string, unknown>) => Promise<FieldOption[]>)
  /** Имена полей, изменение значений которых запускает повторную загрузку асинхронных options */
  optionsDeps?: string[]

  // ─── group / array ────────────────────────────────────────────────────────
  fields?: FieldDefinition[]

  // ─── transform / parse ────────────────────────────────────────────────────
  /** Применяется при каждом вызове setField — используйте для trim, приведения типов, форматирования */
  transform?: (value: unknown, values: Record<string, unknown>) => unknown
  /** Применяется во время отправки для формирования финального значения payload */
  parse?: (raw: unknown) => unknown

  // ─── Кастомный компонент ──────────────────────────────────────────────────
  /** Компонент Vue или зарегистрированное имя; получает FormFieldProps */
  component?: Component | string

  // ─── Опции поля file ──────────────────────────────────────────────────────
  accept?: string // передаётся в <input accept>
  multiple?: boolean
  maxSize?: number // байты (информационно; для принудительного ограничения используйте валидатор fileSize)
  maxFiles?: number // информационно; для принудительного ограничения используйте валидатор fileCount
}

Composable useForm

ts
import { useForm } from '@macrulez/vue-form-schema'
const form = useForm(config)

Конфигурация

СвойствоТипПо умолчаниюОписание
schemaFieldDefinition[] | JSONSchemaОпределения полей
initialValuesPartial<T>{}Начальные значения (переопределяют defaultValue полей)
validateOn'input' | 'blur' | 'submit' | 'eager''blur'Когда срабатывает валидация
validateMode'first' | 'all''first'Возвращать только первую ошибку или все
clearOnHidebooleanfalseСбрасывать значение поля, когда оно скрывается
onSubmit(values: T) => void | Promise<void>Вызывается после успешной валидации
persistfalse | 'session' | 'local'falseСохранять значения в sessionStorage / localStorage
persistKeystringавтоПрефикс ключа хранилища
debugbooleanfalseЛогировать изменения состояния в console.group

Возвращаемое значение

СвойствоТипОписание
fieldsComputedRef<FieldDefinition[]>Поля после вычисления условий
valuesRef<T>Текущие значения формы
errorsRef<Record<string, string[]>>Ошибки валидации по имени поля
touchedRef<Record<string, boolean>>Поля, у которых уже был blur
optionsLoadingRef<Record<string, boolean>>Состояние загрузки асинхронных options по каждому полю
isDirtyComputedRef<boolean>true, когда значения отличаются от начального состояния
isValidComputedRef<boolean>true, когда все видимые поля проходят валидацию
isSubmittingRef<boolean>true, пока выполняется onSubmit
submit()() => Promise<void>Отметить все поля, провалидировать, вызвать onSubmit
reset(values?)Восстановить начальное состояние или задать новые значения
setField(path, value)Установить значение по dot-path
getField(path)Прочитать значение по dot-path

validateOn: 'eager'

При 'eager' валидация запускается при вводе — но только после того, как поле хотя бы раз потеряло фокус. Это избавляет от показа ошибок, пока пользователь ещё вводит значение впервые.

Форматы схемы

Массив FieldDefinition

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

const schema: FieldDefinition[] = [{ type: 'text', name: 'username', required: true }]
useForm({ schema })

JSON-схема

Сериализуемый формат для схем, управляемых сервером. Передайте напрямую в useForm (автоопределение) либо вызовите parseJSON явно.

ts
const raw = [
  {
    type: 'text',
    name: 'username',
    default: '',
    required: true,
    validators: [
      { rule: 'minLength', value: 3, message: 'At least 3 characters' },
      { rule: 'maxLength', value: 20 },
    ],
  },
]

useForm({ schema: raw }) // автоопределение
// или
import { parseJSON } from '@macrulez/vue-form-schema'
const fields = parseJSON(raw)

Поддерживаемые правила JSON-валидатора: required, minLength, maxLength, min, max, pattern, email, url. Все принимают опциональное переопределение message.

Zod

ts
import { z } from 'zod'
import { parseZod } from '@macrulez/vue-form-schema/zod'

const schema = z.object({
  name: z.string().min(2).describe('Full name'),
  age: z.number().min(0).optional(),
  email: z.string().email(),
  role: z.enum(['admin', 'user']),
})

const fields = parseZod(schema)
const { values } = useForm({ schema: fields })
// values.value.name — string, values.value.age — number | undefined, ...
// — выводится автоматически из `schema` через z.infer<typeof schema>,
// useForm<Values>(...) не нужен.

Маппинг Zod → тип поля: z.string()text, z.number()number, z.boolean()checkbox, z.enum()select, z.array()array, z.object()group. Используйте .describe('label'), чтобы задать метку поля.

parseZod также принимает корневую схему z.discriminatedUnion(key, [...]) — см. Дискриминированные схемы.

Yup

ts
import { object, string, number } from 'yup'
import { parseYup } from '@macrulez/vue-form-schema/yup'

const schema = object({
  name: string().required().label('Full name'),
  email: string().email().required(),
  age: number().min(0).optional(),
})

const fields = parseYup(schema)
const { values } = useForm({ schema: fields })
// values.value типизирован из InferType<typeof schema> автоматически

Valibot

ts
import * as v from 'valibot'
import { parseValibot } from '@macrulez/vue-form-schema/valibot'

const schema = v.object({
  name: v.pipe(v.string(), v.minLength(2)),
  email: v.pipe(v.string(), v.email()),
  age: v.optional(v.number()),
  role: v.picklist(['admin', 'user']),
})

const fields = parseValibot(schema)
const { values } = useForm({ schema: fields })
// values.value типизирован из v.InferOutput<typeof schema> автоматически

Маппинг Valibot → тип поля: v.string()text, v.number()number, v.boolean()checkbox, v.picklist() / v.enum()select, v.array()array, v.object()group. v.pipe(v.string(), v.email())type: 'email'. v.optional() / v.nullable()required: false.

parseValibot также принимает корневую схему v.variant(key, [...]) — см. Дискриминированные схемы.

OpenAPI / стандартная JSON Schema

В отличие от parseJSON (собственный упрощённый формат этой библиотеки на основе правил), parseJSONSchema / parseOpenAPI принимают настоящую JSON Schema — ту, что ваш backend уже выдаёт через OpenAPI/Swagger — так что слой трансляции между спецификацией API и формой не нужен.

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

// openapiDocument — ваш полный документ OpenAPI (например, полученный с /openapi.json)
const fields = parseOpenAPI(openapiDocument, { path: '/users', method: 'post' })
// или по JSON pointer в components.schemas:
const fields2 = parseOpenAPI(openapiDocument, '#/components/schemas/User')

const { values } = useForm({ schema: fields })

Либо на отдельном объекте JSON Schema, без обёртки OpenAPI:

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

const fields = parseJSONSchema({
  type: 'object',
  properties: {
    name: { type: 'string', minLength: 2 },
    age: { type: 'integer', minimum: 0 },
    role: { type: 'string', enum: ['admin', 'user'] },
  },
  required: ['name', 'role'],
} as const)

const { values } = useForm({ schema: fields })
// values.value.role типизирован как 'admin' | 'user' — выведено из схемы с `as const`

Поддерживаемое подмножество: type (object / string / number / integer / boolean / array, включая массив типов вроде ['string', 'null']), properties + required, items (схемы элементов массива — у object-элементов их properties отображаются в безымянные строковые fields, по соглашениям для полей-массивов), enum / constselect, format (email, date / date-time, uri/url), minLength/maxLength/minimum/maximum/pattern и локальные $ref (#/..., разрешаются относительно документа, переданного как rootDocument, либо относительно самой схемы для самодостаточных $defs).

Не поддерживается (намеренно — полная JSON Schema это огромная спецификация): oneOf / anyOf / allOf, additionalProperties, patternProperties, удалённый/внешний $ref, tuple-форма items. Свойства, использующие их, разбираются как обычное поле text без неподдерживаемого ограничения, а не выбрасывают исключение.

Возвращаемый тип parseJSONSchema несёт наилучшим образом выведенный тип значения из литерала схемы (нужен as const, как и у defineSchema). parseOpenAPI не может вывести тип статически (форма извлечённой схемы зависит от аргумента path/selector во время выполнения) — передайте явный типовой аргумент, если вы уже генерируете его из своего документа OpenAPI, например через openapi-typescript: parseOpenAPI<CreateUserRequest>(document, '#/components/schemas/User').