@macrulez/vue-form-schema

Реактивные формы из декларативной схемы (JSON, Zod, Yup или Valibot) для Vue 3. Headless, SSR-совместимая альтернатива VeeValidate / FormKit для форм, которые генерируются динамически или управляются с сервера.
Возможности
- Любой источник схемы —
FieldDefinition[], JSON-массив, Zod, Yup, Valibot или стандартная JSON Schema / OpenAPI - Headless по умолчанию — никаких UI-зависимостей в ядре; подключайте свои компоненты
- Реактивные условия —
visible,disabledпринимают булево значение, функцию или строковое выражение - Динамические опции — синхронные и асинхронные функции
optionsс отслеживанием зависимостей (optionsDeps) - Динамические массивы полей —
type: 'array'с composableuseFieldArray(append / remove / move / swap) - Многошаговый визард —
useMultiStepFormс валидацией на каждом шаге иMultiStepFormRenderer - Валидация — синхронные и асинхронные валидаторы,
validateMode: 'first' | 'all',validateOn: 'eager' - Кросс-полевая валидация — валидатор
sameAs; валидаторы получают все текущие значения вторым аргументом - Transform и parse —
transformзапускается на каждомsetField;parse— во время отправки - Загрузка файлов —
type: 'file'с валидаторамиfileType,fileSize,fileCount; drag-and-drop UI - Кастомные компоненты —
field.component+ реестр компонентов на уровне приложения и поддерева - Маскирование ввода — телефон (RU/EU), дата, IBAN, ИНН, кастомные паттерны
#/A; без внешних зависимостей - Композиция схем —
mergeSchemas,omitFields,pickFields,extendField - Дискриминированные схемы —
discriminatedFieldsстроитvisible-связку для набора полей, который целиком переключается по значению дискриминатора, с нативным маппингомz.discriminatedUnion/v.variant - Вывод типов TypeScript —
InferValues<T>отображает литералы схемы в типизированные значения - Персистентные формы —
persist: 'local' | 'session'с SSR-безопасным хранением - Ошибки валидации с сервера —
applyServerErrorsотображает форматы Laravel/DRF/flat/кастомный наerrors - Режим отладки —
debug: trueлогирует изменения состояния;useFormDebugвозвращает реактивный снапшот - Тема Tailwind UI — подточка входа
vue-form-schema/ui/tailwindс компонентами на utility-классах - Темы shadcn / PrimeVue / Naive UI — готовые рендереры для популярных библиотек компонентов
- Доступность —
aria-required,aria-invalid,aria-describedby,fieldset/legendдля radio - SSR-безопасность — никаких прямых обращений к browser API в ядре
- Tree-shakeable — адаптеры Zod/Yup/Valibot и UI — отдельные точки входа
- Модуль Nuxt —
@macrulez/nuxt-vue-form-schemaавтоимпортирует composables, валидаторы и адаптеры схем - Vue DevTools —
vue-form-schema/devtoolsдобавляет живой инспектор форм + таймлайн, нулевая цена, если не установлен
Установка
bash
npm install @macrulez/vue-form-schemaОпциональные peer-зависимости:
bash
npm install zod # адаптер Zod
npm install yup # адаптер Yup
npm install valibot # адаптер ValibotИспользуете Nuxt?
@macrulez/nuxt-vue-form-schema автоимпортирует useForm, useFieldArray, встроенные валидаторы, адаптеры схем и многое другое — ручной import не нужен:
bash
npm install @macrulez/nuxt-vue-form-schemats
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@macrulez/nuxt-vue-form-schema'],
})Опции см. на странице Модуль Nuxt.
Быстрый старт
vue
<script setup lang="ts">
import { useForm } from '@macrulez/vue-form-schema'
import type { FieldDefinition } from '@macrulez/vue-form-schema'
const schema: FieldDefinition[] = [
{ type: 'text', name: 'name', label: 'Full name', required: true },
{ type: 'email', name: 'email', label: 'Email', required: true },
{
type: 'select',
name: 'role',
label: 'Role',
options: [
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
],
},
]
const { values, errors, touched, isValid, isSubmitting, submit, setField } = useForm({
schema,
validateOn: 'blur',
onSubmit: async (data) => {
await fetch('/api/users', { method: 'POST', body: JSON.stringify(data) })
},
})
</script>
<template>
<form @submit.prevent="submit">
<div v-for="field in schema" :key="field.name">
<label>{{ field.label }}</label>
<input
:type="field.type"
:value="values[field.name]"
@input="setField(field.name, ($event.target as HTMLInputElement).value)"
@blur="touched[field.name] = true"
/>
<span v-if="touched[field.name] && errors[field.name]">
{{ errors[field.name][0] }}
</span>
</div>
<button type="submit" :disabled="!isValid || isSubmitting">Submit</button>
</form>
</template>Либо используйте FormRenderer для рендеринга без разметки:
vue
<script setup lang="ts">
import { useForm } from '@macrulez/vue-form-schema'
import { FormRenderer } from '@macrulez/vue-form-schema/ui'
const form = useForm({ schema, onSubmit })
</script>
<template>
<FormRenderer :form="form" submit-label="Save" />
</template>