UNPKG

@adbros/vue-validation

Version:

Composable for schema-based form validation in Vue 3 using Valibot

183 lines (136 loc) 4.52 kB
# 📋 `@adbros/vue-validation` A flexible, schema-driven form validation composable for Vue 3 using **Valibot**, with support for: * Deeply nested schemas * Dirty field tracking * Custom errors * Full form or per-field validation * `MaybeRefOrGetter` inputs * Composable-first design (external state as source of truth) * "Reward early, punish late" validation behavior --- ## 🧩 Components ### 🔹 `useValidation<TSchema>()` #### Parameters * `schema: MaybeRefOrGetter<TSchema>` A Valibot schema (`BaseSchema` or `BaseSchemaAsync`) defining the structure and rules for validation. * `data: MaybeRefOrGetter<Record<string, unknown>>` The reactive object that holds the form data to be validated. #### Returns ```ts { handleSubmit: (onSubmit, onError?) => Promise<void>; errors: ComputedRef<Record<string, string>>; output: Ref<InferOutput<TSchema> | undefined>; makeFieldDirty: (name: string) => void; cleanField: (name: string) => void; makeFormDirty: () => void; cleanForm: () => void; setCustomError: (field: string, message: string) => void; clearCustomError: (field: string) => void; clearAllCustomErrors: () => void; validate: () => Promise<SafeParseReturn<TSchema>>; silentErrors: Ref<FlatErrors<TSchema> | undefined>; isDirty: (name: string) => boolean; isFormValid: ComputedRef<boolean>; dirtyFields: Ref<string[]>; validDirtyFields: Ref<string[]>; } ``` --- ## 🧠 Behavior Summary * **Validation**: Runs automatically on schema/data changes and can be manually triggered via `validate()`. * **Dirty tracking**: Optionally tracks which fields have been interacted with via `makeFieldDirty()`, `makeFormDirty()` and `cleanField()`. * **Custom errors**: Allows adding external (non-schema) validation messages using `setCustomError()` etc. ## 🔸 Key Concepts ### ✅ Dirty State * Fields become **dirty** via: * `makeFieldDirty(name)` * `makeFormDirty()` (marks *all* fields dirty) * A dirty field is considered **valid** if no Valibot error exists for it. ### ❌ Error Reporting * Errors are a computed merge of: * Valibot field-level errors (only for dirty + invalid fields) * Manually set `customErrors` --- ## 📥 `handleSubmit(onSubmit, onError?)` Triggers a full validation of the form and invokes the `onSubmit` callback if validation passes. Optionally calls `onError()` if the form is invalid. ```ts await handleSubmit( async (data) => { // Do something with validated `data` }, () => { // Optional error handler } ) ``` --- ## ✏️ Example Usage ```vue <script setup lang="ts"> import { ref } from 'vue' import * as v from 'valibot' import { useValidation } from '@adbros/vue-validation' const schema = v.object({ username: v.pipe( v.string(), v.nonEmpty('Zadejte uživatelské jméno.') ), password: v.pipe( v.string(), v.nonEmpty('Zadejte heslo.') ), }) const form = ref({ username: '', password: '', }) const { errors, makeFieldDirty, handleSubmit, } = useValidation(schema, form) const submitForm = handleSubmit( (values) => { console.log('Success:', values); }, () => { console.warn('Form has errors', errors.value) } ) </script> <template> <form @submit.prevent="submitForm"> <label> Uživatelské jméno <input v-model="form.username" @blur="makeFieldDirty('username')" /> <span class="error" v-if="errors.username">{{ errors.username }}</span> </label> <label> Heslo <input type="password" v-model="form.password" @blur="makeFieldDirty('password')" /> <span class="error" v-if="errors.password">{{ errors.password }}</span> </label> <button type="submit">Přihlásit se</button> </form> </template> ``` --- ## 🔄 Auto Validation The schema and data are both **deeply watched**, meaning any change triggers validation automatically unless throttled externally. --- ## 🔐 Type Safety * Fully type-safe thanks to `InferOutput<TSchema>` and Valibot's schema inference. * Validation schema and input are treated as generic inputs (`TSchema`), enabling reusable, scalable logic across forms. --- ## 💡 Notes * You can control the timing and granularity of field validation using your own inputs, e.g., `@blur` or `@change` events. * Form state (data) is kept external and not mutated internally. * This approach is ideal for decoupled forms, such as those using Vue's Composition API and `<script setup>` pattern.