vue3-form-validation
Advanced tools
| export default function useUid(): number; |
| let uid = 1; | ||
| export default function useUid() { | ||
| return uid++; | ||
| } |
| import { Ref, ComputedRef, UnwrapRef } from 'vue'; | ||
| export declare type SimpleRule<T = any> = (value: T) => any; | ||
| export declare type KeyedRule<T = any> = { | ||
| key: string; | ||
| rule: SimpleRule<T>; | ||
| }; | ||
| export declare type Rule<T = any> = SimpleRule<T> | KeyedRule<T>; | ||
| export declare type Field<TValue> = { | ||
| $value: TValue extends Ref ? TValue | UnwrapRef<TValue> : TValue extends any[] ? TValue : TValue extends Record<string, unknown> ? RefUnref<TValue> : Ref<TValue> | TValue; | ||
| $rules?: Rule<UnwrapRef<TValue>>[]; | ||
| }; | ||
| export declare type TransformedField<T> = { | ||
| $uid: number; | ||
| $value: T; | ||
| $errors: string[]; | ||
| $validating: boolean; | ||
| $onBlur(): void; | ||
| }; | ||
| export declare type RefUnref<T extends Record<string, unknown>> = { | ||
| [K in keyof T]: T[K] extends Ref ? T[K] | UnwrapRef<T[K]> : T[K] extends any[] ? T[K] : T[K] extends Record<string, unknown> ? RefUnref<T[K]> : Ref<T[K]> | T[K]; | ||
| }; | ||
| export declare type TransformedFormData<T extends object> = T extends any ? { | ||
| [K in keyof T]: T[K] extends { | ||
| $value: infer TValue; | ||
| } ? TransformedField<UnwrapRef<TValue>> : T[K] extends Record<string, unknown> | any[] ? TransformedFormData<T[K]> : T[K]; | ||
| } : never; | ||
| export declare type FormData<T extends object> = T extends any ? { | ||
| [K in keyof T]: T[K] extends { | ||
| $value: infer TValue; | ||
| } ? UnwrapRef<TValue> : T[K] extends Record<string, unknown> | any[] ? FormData<T[K]> : T[K]; | ||
| } : never; | ||
| export declare type Keys = readonly (string | number)[]; | ||
| export declare type DeepIndex<T, Ks extends Keys> = Ks extends [ | ||
| infer First, | ||
| ...infer Rest | ||
| ] ? First extends keyof T ? Rest extends Keys ? DeepIndex<T[First], Rest> : undefined : undefined : T; | ||
| declare type UseValidation<T extends object> = { | ||
| form: TransformedFormData<T>; | ||
| submitting: Ref<boolean>; | ||
| errors: ComputedRef<string[]>; | ||
| validateFields(): Promise<FormData<T>>; | ||
| resetFields(formData?: FormData<T>): void; | ||
| add<Ks extends Keys>(pathToArray: readonly [...Ks], value: DeepIndex<T, Ks> extends Array<infer TArray> ? TArray : never): void; | ||
| remove<Ks extends Keys>(pathToArray: readonly [...Ks], index: DeepIndex<T, Ks> extends any[] ? number : never): void; | ||
| }; | ||
| /** | ||
| * | ||
| * @param formData The structure of your Form Data. | ||
| * @description | ||
| * Vue composition function for Form Validation. | ||
| * @docs | ||
| * https://github.com/JensDll/vue3-form-validation | ||
| * @typescript | ||
| * For better type inference, consider defining the structure | ||
| * of your `formData` upfront and pass it as the generic parameter `T`: | ||
| * ``` | ||
| * type FormData = { | ||
| * name: Field<string>, | ||
| * email: Field<string>, | ||
| * password: Field<string> | ||
| * } | ||
| * | ||
| * const { ... } = useValidation<FormData>({ ... }) | ||
| * ``` | ||
| */ | ||
| export declare function useValidation<T extends object>(formData: T): UseValidation<T>; | ||
| export {}; |
| import { reactive } from 'vue'; | ||
| import Form from '../Form'; | ||
| import { cleanupForm, getResultFormData, path, PromiseCancel, resetFields, transformFormData } from '../utils'; | ||
| /** | ||
| * | ||
| * @param formData The structure of your Form Data. | ||
| * @description | ||
| * Vue composition function for Form Validation. | ||
| * @docs | ||
| * https://github.com/JensDll/vue3-form-validation | ||
| * @typescript | ||
| * For better type inference, consider defining the structure | ||
| * of your `formData` upfront and pass it as the generic parameter `T`: | ||
| * ``` | ||
| * type FormData = { | ||
| * name: Field<string>, | ||
| * email: Field<string>, | ||
| * password: Field<string> | ||
| * } | ||
| * | ||
| * const { ... } = useValidation<FormData>({ ... }) | ||
| * ``` | ||
| */ | ||
| export function useValidation(formData) { | ||
| const form = new Form(); | ||
| const promiseCancel = new PromiseCancel(); | ||
| transformFormData(form, formData); | ||
| const transformedFormData = reactive(formData); | ||
| return { | ||
| form: transformedFormData, | ||
| submitting: form.submitting, | ||
| errors: form.getErrors(), | ||
| async validateFields() { | ||
| form.submitting.value = true; | ||
| const resultFormData = {}; | ||
| getResultFormData(transformedFormData, resultFormData); | ||
| const hasError = await promiseCancel.race(form.validateAll()); | ||
| form.submitting.value = false; | ||
| if (hasError) { | ||
| throw void 0; | ||
| } | ||
| return resultFormData; | ||
| }, | ||
| resetFields(formData) { | ||
| if (form.submitting.value) { | ||
| promiseCancel.cancelResolve(true); | ||
| } | ||
| if (formData) { | ||
| form.resetFields(false); | ||
| resetFields(transformedFormData, formData); | ||
| } | ||
| else { | ||
| form.resetFields(); | ||
| } | ||
| }, | ||
| add(pathToArray, value) { | ||
| const xs = path(pathToArray, transformedFormData); | ||
| if (Array.isArray(xs)) { | ||
| transformFormData(form, value); | ||
| xs.push(value); | ||
| } | ||
| }, | ||
| remove(pathToArray, index) { | ||
| const xs = path(pathToArray, transformedFormData); | ||
| if (Array.isArray(xs)) { | ||
| const deleted = xs.splice(index, 1); | ||
| deleted.forEach(deleted => cleanupForm(form, deleted)); | ||
| } | ||
| } | ||
| }; | ||
| } |
| import Form from '../../Form'; | ||
| export declare function transformFormData(form: Form, formData: any): void; | ||
| export declare function resetFields(transformedFormData: any, formData: any): void; | ||
| export declare function cleanupForm(form: Form, formData: any): void; | ||
| export declare function getResultFormData(formData: any, resultFormData: any): void; |
| import { isReactive, watch } from 'vue'; | ||
| import useUid from '../../composition/useUid'; | ||
| const isField = (field) => typeof field === 'object' ? '$value' in field : false; | ||
| const isTransformedField = (field) => typeof field === 'object' | ||
| ? '$uid' in field && | ||
| '$value' in field && | ||
| '$errors' in field && | ||
| '$validating' in field | ||
| : false; | ||
| export function transformFormData(form, formData) { | ||
| Object.entries(formData).forEach(([key, value]) => { | ||
| var _a; | ||
| if (isField(value)) { | ||
| const uid = useUid(); | ||
| const formField = form.registerField(uid, (_a = value.$rules) !== null && _a !== void 0 ? _a : [], value.$value); | ||
| watch(formField.modelValue, () => { | ||
| if (formField.touched) { | ||
| form.validate(uid); | ||
| } | ||
| }); | ||
| formData[key] = { | ||
| $uid: uid, | ||
| $value: formField.modelValue, | ||
| $errors: formField.getErrors(), | ||
| $validating: formField.validating(), | ||
| async $onBlur() { | ||
| if (!formField.touched) { | ||
| formField.touched = true; | ||
| await form.validate(uid); | ||
| } | ||
| } | ||
| }; | ||
| return; | ||
| } | ||
| if (typeof value === 'object') { | ||
| transformFormData(form, value); | ||
| } | ||
| }); | ||
| } | ||
| export function resetFields(transformedFormData, formData) { | ||
| Object.entries(transformedFormData).forEach(([key, value]) => { | ||
| if (isTransformedField(value)) { | ||
| if (isReactive(value.$value) && !Array.isArray(value.$value)) { | ||
| Object.assign(value.$value, formData[key]); | ||
| } | ||
| else { | ||
| value.$value = formData[key]; | ||
| } | ||
| return; | ||
| } | ||
| if (typeof value === 'object') { | ||
| resetFields(value, formData[key]); | ||
| } | ||
| }); | ||
| } | ||
| export function cleanupForm(form, formData) { | ||
| Object.values(formData).forEach(value => { | ||
| if (isTransformedField(value)) { | ||
| form.onDelete(value.$uid); | ||
| return; | ||
| } | ||
| if (typeof value === 'object') { | ||
| cleanupForm(form, value); | ||
| } | ||
| }); | ||
| } | ||
| export function getResultFormData(formData, resultFormData) { | ||
| Object.entries(formData).forEach(([key, value]) => { | ||
| if (isTransformedField(value)) { | ||
| resultFormData[key] = | ||
| typeof value.$value === 'object' | ||
| ? JSON.parse(JSON.stringify(value.$value)) | ||
| : value.$value; | ||
| return; | ||
| } | ||
| if (typeof value == 'object') { | ||
| resultFormData[key] = {}; | ||
| } | ||
| else { | ||
| resultFormData[key] = value; | ||
| return; | ||
| } | ||
| if (Array.isArray(value)) { | ||
| resultFormData[key] = []; | ||
| } | ||
| getResultFormData(value, resultFormData[key]); | ||
| }); | ||
| } |
+2
-2
@@ -1,2 +0,2 @@ | ||
| import { Rule } from './composable/useValidation'; | ||
| import { Rule } from './composition/useValidation'; | ||
| import FormField from './FormField'; | ||
@@ -14,3 +14,3 @@ declare type ValidateResult = void | string; | ||
| getErrors(): import("vue").ComputedRef<string[]>; | ||
| resetFields(): void; | ||
| resetFields(toDefaultValues?: boolean): void; | ||
| validateAll(): Promise<boolean>; | ||
@@ -17,0 +17,0 @@ validate(uid: number): Promise<PromiseSettledResult<ValidateResult>[]>; |
+4
-4
@@ -59,5 +59,5 @@ import { computed, reactive, ref, unref } from 'vue'; | ||
| } | ||
| resetFields() { | ||
| resetFields(toDefaultValues = true) { | ||
| for (const { formField } of this.simpleValidators.values()) { | ||
| formField.reset(); | ||
| formField.reset(toDefaultValues); | ||
| } | ||
@@ -76,4 +76,4 @@ } | ||
| } | ||
| const settledResult = await Promise.allSettled(promises); | ||
| for (const result of settledResult) { | ||
| const settledResults = await Promise.allSettled(promises); | ||
| for (const result of settledResults) { | ||
| if (result.status === 'rejected') { | ||
@@ -80,0 +80,0 @@ return true; |
| import { reactive, ref } from 'vue'; | ||
| import { Rule } from './composable/useValidation'; | ||
| import { Rule } from './composition/useValidation'; | ||
| export default class FormField { | ||
| modelValue: ReturnType<typeof ref> | ReturnType<typeof reactive>; | ||
| touched: boolean; | ||
| rulesValidating: import("vue").Ref<number>; | ||
| private errors; | ||
| rulesValidating: import("vue").Ref<number>; | ||
| modelValue: ReturnType<typeof ref> | ReturnType<typeof reactive>; | ||
| private initialModelValue; | ||
| touched: boolean; | ||
| constructor(rules: Rule[], modelValue: unknown); | ||
@@ -13,3 +13,3 @@ setError(ruleNumber: number, error: string | null): void; | ||
| validating(): import("vue").ComputedRef<boolean>; | ||
| reset(): void; | ||
| reset(toDefaultValues: boolean): void; | ||
| } |
+10
-8
@@ -5,4 +5,4 @@ import { computed, isRef, reactive, ref } from 'vue'; | ||
| constructor(rules, modelValue) { | ||
| this.touched = false; | ||
| this.rulesValidating = ref(0); | ||
| this.touched = false; | ||
| this.errors = reactive(rules.map(() => null)); | ||
@@ -33,13 +33,15 @@ if (isRef(modelValue)) { | ||
| } | ||
| reset() { | ||
| reset(toDefaultValues) { | ||
| this.touched = false; | ||
| if (isRef(this.modelValue)) { | ||
| this.modelValue.value = this.initialModelValue; | ||
| if (toDefaultValues) { | ||
| if (isRef(this.modelValue)) { | ||
| this.modelValue.value = this.initialModelValue; | ||
| } | ||
| else { | ||
| Object.assign(this.modelValue, this.initialModelValue); | ||
| this.initialModelValue = JSON.parse(JSON.stringify(this.initialModelValue)); | ||
| } | ||
| } | ||
| else { | ||
| Object.assign(this.modelValue, this.initialModelValue); | ||
| this.initialModelValue = JSON.parse(JSON.stringify(this.initialModelValue)); | ||
| } | ||
| Object.assign(this.errors, this.errors.map(() => null)); | ||
| } | ||
| } |
+2
-2
@@ -1,2 +0,2 @@ | ||
| export { useValidation } from './composable/useValidation'; | ||
| export type { Field, Rule, KeyedRule, SimpleRule } from './composable/useValidation'; | ||
| export { useValidation } from './composition/useValidation'; | ||
| export type { Field, Rule, KeyedRule, SimpleRule } from './composition/useValidation'; |
+1
-1
@@ -1,1 +0,1 @@ | ||
| export { useValidation } from './composable/useValidation'; | ||
| export { useValidation } from './composition/useValidation'; |
@@ -5,1 +5,2 @@ export { trySet } from './map/trySet'; | ||
| export { PromiseCancel } from './promise-cancel/PromiseCancel'; | ||
| export * from './helper/helper'; |
@@ -5,1 +5,2 @@ export { trySet } from './map/trySet'; | ||
| export { PromiseCancel } from './promise-cancel/PromiseCancel'; | ||
| export * from './helper/helper'; |
+15
-15
| { | ||
| "name": "vue3-form-validation", | ||
| "version": "3.0.4", | ||
| "version": "3.0.5", | ||
| "description": "Vue composition function for Form Validation", | ||
@@ -41,25 +41,25 @@ "author": { | ||
| "@types/jest": "^26.0.20", | ||
| "@types/node": "^14.14.27", | ||
| "@typescript-eslint/eslint-plugin": "^4.15.0", | ||
| "@typescript-eslint/parser": "^4.15.0", | ||
| "@vitejs/plugin-vue": "^1.0.6", | ||
| "@vue/compiler-sfc": "^3.0.5", | ||
| "@types/node": "^14.14.31", | ||
| "@typescript-eslint/eslint-plugin": "^4.16.0", | ||
| "@typescript-eslint/parser": "^4.16.0", | ||
| "@vitejs/plugin-vue": "^1.1.5", | ||
| "@vue/compiler-sfc": "^3.0.7", | ||
| "autoprefixer": "^10.2.4", | ||
| "eslint": "^7.18.0", | ||
| "eslint-config-prettier": "^7.2.0", | ||
| "eslint": "^7.21.0", | ||
| "eslint-config-prettier": "^8.1.0", | ||
| "eslint-plugin-vue": "^7.4.1", | ||
| "husky": "^5.0.9", | ||
| "husky": "^5.1.2", | ||
| "jest": "^26.6.3", | ||
| "lint-staged": "^10.5.3", | ||
| "pinst": "^2.1.4", | ||
| "pinst": "^2.1.6", | ||
| "postcss": "^8.2.6", | ||
| "prettier": "^2.2.1", | ||
| "tailwindcss": "^2.0.3", | ||
| "ts-jest": "^26.5.1", | ||
| "ts-jest": "^26.5.2", | ||
| "ts-node": "^9.1.1", | ||
| "tsd": "^0.14.0", | ||
| "typescript": "^4.1.5", | ||
| "vite": "^2.0.0-beta.69", | ||
| "vue": "^3.0.5", | ||
| "vue-router": "^4.0.3" | ||
| "typescript": "^4.2.2", | ||
| "vite": "^2.0.4", | ||
| "vue": "^3.0.7", | ||
| "vue-router": "^4.0.4" | ||
| }, | ||
@@ -66,0 +66,0 @@ "prettier": { |
+44
-28
@@ -5,3 +5,3 @@ # Form Validation for Vue 3 | ||
| Opinionated Vue composition function for Form Validation. | ||
| Vue composition function for Form Validation. | ||
@@ -38,8 +38,7 @@ - :milky_way: **Written in TypeScript** | ||
| - **Type** - `object` | ||
| - **Required** - `true` | ||
| - **Description** - The structure of your `formData`. | ||
| - **Description** - The structure of your form data. | ||
| The `formData` object has a structure that is similar to any other object you would write for `v-model` data binding. The only difference being that together with every value you can provide rules to display validation errors. | ||
| The form data object has a structure that is similar to any other object you would write for `v-model` data binding. The only difference being that together with every value you can provide rules to display validation errors. | ||
| Let's look at an example how the structure of some `formData` object can be converted to an object with the addition of rules: | ||
| Let's look at an example how the structure of some form data object can be converted to an object with the addition of rules: | ||
@@ -72,3 +71,3 @@ ```ts | ||
| The `formData` object can contain arrays and can be deeply nested. At the leaf level, the object should contain Form Fields whose simplified type definition looks like the following: | ||
| The form data object can contain arrays and can be deeply nested. At the leaf level, the object should contain fields whose simplified type definition looks like the following: | ||
@@ -82,3 +81,3 @@ ```ts | ||
| To get better type inference while writing the `useValidation` function, it's recommended to define the structure of your `formData` upfront and pass it as the generic parameter `T`. The type for the example above is pretty straightforward: | ||
| To get better type inference while writing the `useValidation` function, it's recommended to define the structure of your data upfront and pass it as the generic parameter `T`. The type for the example above is pretty straightforward: | ||
@@ -91,2 +90,4 @@ ```ts | ||
| }; | ||
| const { ... } = useValidation<FormData>({ ... }); | ||
| ``` | ||
@@ -98,3 +99,3 @@ | ||
| - **Type** - `object` | ||
| - **Description** - Transformed `formData` object. | ||
| - **Description** - Transformed form data object. | ||
| - `submitting` | ||
@@ -107,3 +108,4 @@ - **Type** - `Ref<boolean>` | ||
| `Form` is a reactive object with identical structure as the `formData` input, but with added metadata to every Form Field. | ||
| `Form` is a reactive object with identical structure as the form data input. | ||
| Every object with a `$value` property will be converted to an object of the following form: | ||
@@ -118,5 +120,8 @@ ```ts | ||
| }; | ||
| ``` | ||
| // The type of form in the example above would therefore be | ||
| const form: { | ||
| Given the structure of the previous example, this will result in the following type: | ||
| ```ts | ||
| type Form = { | ||
| name: TransformedField<string>; | ||
@@ -128,10 +133,11 @@ email: TransformedField<string>; | ||
| As you may have noticed, all of the properties are prefixed with the `$` symbol, which is to distinguish them from other properties but also to avoid naming conflicts. | ||
| As you may have noticed, all of the properties are prefixed with the `$` symbol, which is to distinguish them from other properties but also to avoid naming conflicts. Below is a | ||
| description of all the properties and their use case: | ||
| - `$uid` | ||
| - **Type** - `number` | ||
| - **Description** - Unique identifier of the Form Field. For dynamic Forms this can be used as the `key` attribute in `v-for`. | ||
| - **Description** - Unique identifier of the field. For dynamic forms this can be used as the `key` attribute in `v-for`. | ||
| - `$value` | ||
| - **Type** - `T` | ||
| - **Description** - The `modelValue` of the Form Field which is meant to be used together with `v-model`. | ||
| - **Description** - The `modelValue` of the field, which is meant to be used together with `v-model`. | ||
| - `$errors` | ||
@@ -145,3 +151,3 @@ - **Type** - `string[]` | ||
| - **Type** - `function` | ||
| - **Description** - Function which will mark this Form Field as touched. When a Form Field has been touched it will validate all it's rules after every input. Before it will not do any validation. | ||
| - **Description** - Function which will mark this field as touched. When a field has been touched, it will validate all it's rules after every input. Before it will not do any validation. | ||
@@ -151,16 +157,18 @@ ### `useValidation` exposes the following methods: | ||
| - `validateFields() -> Promise` | ||
| - **Description** - Validate all Form Fields. | ||
| - **Returns** - A `Promise` which will reject if there are validation errors, and resolve with the `formData` otherwise. | ||
| - `resetFields() -> void` | ||
| - **Description** - Reset all Form Fields to their original values. | ||
| - **Description** - Validate all fields. | ||
| - **Returns** - A `Promise` which will reject if there are validation errors, and resolve with the form data otherwise. | ||
| - `resetFields(formData?: object) -> void` | ||
| - **Description** - Reset all fields to their original value, or pass an object to set specific values. Check out the [Sandbox](https://codesandbox.io/s/vue-3-form-validation-demo-7mp4z?file=/src/views/LoginForm.vue) for usage examples. | ||
| - **Parameters** | ||
| - `formData?` - Values to use. | ||
| - `add(pathToArray: (string | number)[], value: any) -> void` | ||
| - **Description** - Utility function for writing dynamic Forms. | ||
| - **Description** - Utility function for writing dynamic forms. | ||
| - **Parameters** | ||
| - `pathToArray` - Tuple representing the path to an array in the `formData`. | ||
| - `pathToArray` - Tuple representing the path to an array in the form data. | ||
| - `value` - The value that will be pushed to the array at the given path. | ||
| - `remove(pathToArray: (string | number)[], index: number) -> void` | ||
| - **Description** - Utility function for writing dynamic Forms. | ||
| - **Description** - Utility function for writing dynamic forms. | ||
| - **Parameters** | ||
| - `pathToArray` - Tuple representing the path to an array in the `formData`. | ||
| - `index` - Array index that will be remove. | ||
| - `pathToArray` - Tuple representing the path to an array in the form data. | ||
| - `index` - Array index which will be remove. | ||
@@ -180,6 +188,10 @@ ## Writing Rules | ||
| Keyed rules that share the same `key` will be executed together, this can be useful in a situation where rules are dependent on another. For example the `Password` and `Repeat Password` fields in a [Login Form](https://codesandbox.io/s/vue-3-form-validation-demo-7mp4z?file=/src/views/LoginForm.vue). | ||
| Keyed rules that share the same `key` will be executed together. This can be useful in a situation where rules are dependent on another, such as the `Password` and `Repeat Password` fields in a [Login Form](https://codesandbox.io/s/vue-3-form-validation-demo-7mp4z?file=/src/views/LoginForm.vue). | ||
| Rules will always be called with the latest `modelValue`, to determine if a call should result in an error, it will check if the rule's return value is of type `string`. | ||
| This allows you to write many rules in one line: | ||
| > To prevent overly aggressive error messages, keyed rules will only be called | ||
| > after all Fields with connected rules have been touched. | ||
| Because of the way the [logical operators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#logical_operators) work in JavaScript, many basic rules can be written in one line: | ||
| ```ts | ||
@@ -193,3 +205,3 @@ const required = value => !value && 'This field is required'; | ||
| Async rules allow you to perform network requests, for example checking if a username exists in the database: | ||
| Async rules allow you to perform network requests, for instance checking if a username exists in the database. Same rules apply as for simple rules, `resolve` or `reject` with a string if the validation fails: | ||
@@ -209,6 +221,10 @@ ```ts | ||
| ## Troubleshooting | ||
| - If you encounter errors while building, you may have to install a TypeScript **version >= 4.1** | ||
| ## Contributing | ||
| If you find problems or if you have use cases that you think are not easy to achieve with the current API, please let me know :+1: | ||
| Feel free to file an issue or open a pull request, for more information checkout the | ||
| Feel free to write an issue or open a pull request, for more information about the project check out the | ||
| [contributing guideline](https://github.com/JensD98/vue3-form-validation/blob/master/.github/contributing.md). | ||
@@ -215,0 +231,0 @@ |
| export default function useUid(): number; |
| let uid = 1; | ||
| export default function useUid() { | ||
| return uid++; | ||
| } |
| import { Ref, ComputedRef, UnwrapRef } from 'vue'; | ||
| import Form from '../Form'; | ||
| export declare type SimpleRule<T = any> = (value: T) => any; | ||
| export declare type KeyedRule<T = any> = { | ||
| key: string; | ||
| rule: SimpleRule<T>; | ||
| }; | ||
| export declare type Rule<T = any> = SimpleRule<T> | KeyedRule<T>; | ||
| export declare type Field<TValue> = { | ||
| $value: TValue extends Ref ? TValue | UnwrapRef<TValue> : TValue extends any[] ? TValue : TValue extends Record<string, unknown> ? RefUnref<TValue> : Ref<TValue> | TValue; | ||
| $rules?: Rule<UnwrapRef<TValue>>[]; | ||
| }; | ||
| export declare type TransformedField<T> = { | ||
| $uid: number; | ||
| $value: T; | ||
| $errors: string[]; | ||
| $validating: boolean; | ||
| $onBlur(): void; | ||
| }; | ||
| export declare type RefUnref<T extends Record<string, unknown>> = { | ||
| [K in keyof T]: T[K] extends Ref ? T[K] | UnwrapRef<T[K]> : T[K] extends any[] ? T[K] : T[K] extends Record<string, unknown> ? RefUnref<T[K]> : Ref<T[K]> | T[K]; | ||
| }; | ||
| export declare type TransformedFormData<T extends object> = T extends any ? { | ||
| [K in keyof T]: T[K] extends { | ||
| $value: infer TValue; | ||
| } ? TransformedField<UnwrapRef<TValue>> : T[K] extends Record<string, unknown> | any[] ? TransformedFormData<T[K]> : T[K]; | ||
| } : never; | ||
| export declare type FormData<T extends object> = T extends any ? { | ||
| [K in keyof T]: T[K] extends { | ||
| $value: infer TValue; | ||
| } ? UnwrapRef<TValue> : T[K] extends Record<string, unknown> | any[] ? FormData<T[K]> : T[K]; | ||
| } : never; | ||
| export declare type Keys = readonly (string | number)[]; | ||
| export declare type DeepIndex<T, Ks extends Keys> = Ks extends [ | ||
| infer First, | ||
| ...infer Rest | ||
| ] ? First extends keyof T ? Rest extends Keys ? DeepIndex<T[First], Rest> : undefined : undefined : T; | ||
| export declare function transformFormData(form: Form, formData: any): void; | ||
| export declare function cleanupForm(form: Form, formData: any): void; | ||
| export declare function getResultFormData(formData: any, resultFormData: any): void; | ||
| declare type UseValidation<T extends object> = { | ||
| form: TransformedFormData<T>; | ||
| submitting: Ref<boolean>; | ||
| errors: ComputedRef<string[]>; | ||
| validateFields(): Promise<FormData<T>>; | ||
| resetFields(): void; | ||
| add<Ks extends Keys>(pathToArray: readonly [...Ks], value: DeepIndex<T, Ks> extends Array<infer TArray> ? TArray : never): void; | ||
| remove<Ks extends Keys>(pathToArray: readonly [...Ks], index: DeepIndex<T, Ks> extends any[] ? number : never): void; | ||
| }; | ||
| /** | ||
| * | ||
| * @param formData The structure of your Form Data. | ||
| * @description | ||
| * Vue composition function for Form Validation. | ||
| * @docs | ||
| * https://github.com/JensDll/vue3-form-validation | ||
| * @typescript | ||
| * For better type inference, consider defining the structure | ||
| * of your `formData` upfront and pass it as the generic parameter `T`: | ||
| * ``` | ||
| * type FormData = { | ||
| * name: Field<string>, | ||
| * email: Field<string>, | ||
| * password: Field<string> | ||
| * } | ||
| * | ||
| * const { ... } = useValidation<FormData>({ ... }) | ||
| * ``` | ||
| */ | ||
| export declare function useValidation<T extends object>(formData: T): UseValidation<T>; | ||
| export {}; |
| import { reactive, watch } from 'vue'; | ||
| import useUid from './useUid'; | ||
| import Form from '../Form'; | ||
| import { path, PromiseCancel } from '../utils'; | ||
| const isField = (field) => typeof field === 'object' ? '$value' in field : false; | ||
| const isTransformedField = (field) => typeof field === 'object' | ||
| ? '$uid' in field && | ||
| '$value' in field && | ||
| '$errors' in field && | ||
| '$validating' in field | ||
| : false; | ||
| export function transformFormData(form, formData) { | ||
| Object.entries(formData).forEach(([key, value]) => { | ||
| var _a; | ||
| if (isField(value)) { | ||
| const uid = useUid(); | ||
| const formField = form.registerField(uid, (_a = value.$rules) !== null && _a !== void 0 ? _a : [], value.$value); | ||
| formData[key] = reactive({ | ||
| $uid: uid, | ||
| $value: formField.modelValue, | ||
| $errors: formField.getErrors(), | ||
| $validating: formField.validating(), | ||
| async $onBlur() { | ||
| if (!formField.touched) { | ||
| formField.touched = true; | ||
| await form.validate(uid); | ||
| } | ||
| } | ||
| }); | ||
| watch(formField.modelValue, () => { | ||
| if (formField.touched) { | ||
| form.validate(uid); | ||
| } | ||
| }); | ||
| return; | ||
| } | ||
| if (typeof value === 'object') { | ||
| transformFormData(form, value); | ||
| } | ||
| }); | ||
| } | ||
| export function cleanupForm(form, formData) { | ||
| Object.values(formData).forEach(value => { | ||
| if (isTransformedField(value)) { | ||
| form.onDelete(value.$uid); | ||
| return; | ||
| } | ||
| if (typeof value === 'object') { | ||
| cleanupForm(form, value); | ||
| } | ||
| }); | ||
| } | ||
| export function getResultFormData(formData, resultFormData) { | ||
| Object.entries(formData).forEach(([key, value]) => { | ||
| if (isTransformedField(value)) { | ||
| resultFormData[key] = | ||
| typeof value.$value === 'object' | ||
| ? JSON.parse(JSON.stringify(value.$value)) | ||
| : value.$value; | ||
| return; | ||
| } | ||
| if (typeof value == 'object') { | ||
| resultFormData[key] = {}; | ||
| } | ||
| else { | ||
| resultFormData[key] = value; | ||
| return; | ||
| } | ||
| if (Array.isArray(value)) { | ||
| resultFormData[key] = []; | ||
| } | ||
| getResultFormData(value, resultFormData[key]); | ||
| }); | ||
| } | ||
| /** | ||
| * | ||
| * @param formData The structure of your Form Data. | ||
| * @description | ||
| * Vue composition function for Form Validation. | ||
| * @docs | ||
| * https://github.com/JensDll/vue3-form-validation | ||
| * @typescript | ||
| * For better type inference, consider defining the structure | ||
| * of your `formData` upfront and pass it as the generic parameter `T`: | ||
| * ``` | ||
| * type FormData = { | ||
| * name: Field<string>, | ||
| * email: Field<string>, | ||
| * password: Field<string> | ||
| * } | ||
| * | ||
| * const { ... } = useValidation<FormData>({ ... }) | ||
| * ``` | ||
| */ | ||
| export function useValidation(formData) { | ||
| const form = new Form(); | ||
| const promiseCancel = new PromiseCancel(); | ||
| transformFormData(form, formData); | ||
| const transformedFormData = reactive(formData); | ||
| return { | ||
| form: transformedFormData, | ||
| submitting: form.submitting, | ||
| errors: form.getErrors(), | ||
| async validateFields() { | ||
| form.submitting.value = true; | ||
| const resultFormData = {}; | ||
| getResultFormData(transformedFormData, resultFormData); | ||
| const hasError = await promiseCancel.race(form.validateAll()); | ||
| form.submitting.value = false; | ||
| if (hasError) { | ||
| throw undefined; | ||
| } | ||
| return resultFormData; | ||
| }, | ||
| async resetFields() { | ||
| if (form.submitting.value) { | ||
| promiseCancel.cancelResolve(true); | ||
| } | ||
| form.resetFields(); | ||
| }, | ||
| add(pathToArray, value) { | ||
| const xs = path(pathToArray, transformedFormData); | ||
| if (Array.isArray(xs)) { | ||
| transformFormData(form, value); | ||
| xs.push(value); | ||
| } | ||
| }, | ||
| remove(pathToArray, index) { | ||
| const xs = path(pathToArray, transformedFormData); | ||
| if (Array.isArray(xs)) { | ||
| const deleted = xs.splice(index, 1); | ||
| deleted.forEach(deleted => cleanupForm(form, deleted)); | ||
| } | ||
| } | ||
| }; | ||
| } |
32437
6.95%25
8.7%574
5.13%221
7.8%