@vuehookform/core 0.4.2 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,12 +42,12 @@ const onSubmit = (data) => {
42
42
  <template>
43
43
  <form @submit="handleSubmit(onSubmit)">
44
44
  <input v-bind="register('email')" type="email" />
45
- <span v-if="formState.errors.email">{{ formState.errors.email }}</span>
45
+ <span v-if="formState.value.errors.email">{{ formState.value.errors.email }}</span>
46
46
 
47
47
  <input v-bind="register('password')" type="password" />
48
- <span v-if="formState.errors.password">{{ formState.errors.password }}</span>
48
+ <span v-if="formState.value.errors.password">{{ formState.value.errors.password }}</span>
49
49
 
50
- <button type="submit" :disabled="formState.isSubmitting">Submit</button>
50
+ <button type="submit" :disabled="formState.value.isSubmitting">Submit</button>
51
51
  </form>
52
52
  </template>
53
53
  ```
@@ -121,12 +121,56 @@ const { value, ...bindings } = register('field', { controlled: true })
121
121
 
122
122
  ### Common Mistakes
123
123
 
124
- | Wrong | Right |
125
- | ------------------------ | ------------------------ |
126
- | `items[0].name` | `items.0.name` |
127
- | `:key="index"` | `:key="field.key"` |
128
- | `formState.errors` | `formState.value.errors` |
129
- | `v-model` + `register()` | Either one, not both |
124
+ | Wrong | Right | Why |
125
+ | ------------------------------------ | ----------------------------------------- | -------------------------------------------- |
126
+ | `items[0].name` | `items.0.name` | Always use dot notation for paths |
127
+ | `:key="index"` | `:key="field.key"` | Index can change during reordering |
128
+ | `formState.errors` | `formState.value.errors` | formState is a Ref, must access `.value` |
129
+ | `v-model` + `register()` | Either one, not both | Causes double binding conflict |
130
+ | `const state = getFieldState('x')` | `formState.value.errors.x` | getFieldState returns snapshot, not reactive |
131
+ | `<CustomInput v-bind="register()"/>` | Use `controlled: true` or `useController` | Custom components need controlled mode |
132
+
133
+ #### ⚠️ Critical: `getFieldState()` is NOT Reactive
134
+
135
+ **Problem:** Calling `getFieldState()` once returns a snapshot that never updates.
136
+
137
+ ```vue
138
+ <!-- ❌ WRONG - Error will persist even after fixing the input -->
139
+ <script setup>
140
+ const emailState = getFieldState('email')
141
+ </script>
142
+ <template>
143
+ <span v-if="emailState.error">{{ emailState.error }}</span>
144
+ </template>
145
+ ```
146
+
147
+ **Solutions:**
148
+
149
+ ```vue
150
+ <!-- ✅ Option 1: Use formState (always reactive) -->
151
+ <span v-if="formState.value.errors.email">{{ formState.value.errors.email }}</span>
152
+
153
+ <!-- ✅ Option 2: Use computed for specific field -->
154
+ <script setup>
155
+ const emailError = computed(() => formState.value.errors.email)
156
+ </script>
157
+ <template>
158
+ <span v-if="emailError">{{ emailError }}</span>
159
+ </template>
160
+
161
+ <!-- ✅ Option 3: Use useController for reusable components -->
162
+ <script setup>
163
+ import { useForm, useController, type Control } from '@vuehookform/core'
164
+
165
+ // control comes from useForm (pass it as a prop to child components)
166
+ const { control } = useForm({ schema })
167
+ const { fieldState } = useController({ name: 'email', control })
168
+ // fieldState is a ComputedRef that updates automatically
169
+ </script>
170
+ <template>
171
+ <span v-if="fieldState.value.error">{{ fieldState.value.error }}</span>
172
+ </template>
173
+ ```
130
174
 
131
175
  ## Contributing
132
176
 
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@vuehookform/core",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "TypeScript-first form library for Vue 3, inspired by React Hook Form. Form-level state management with Zod validation.",
5
5
  "type": "module",
6
+ "workspaces": [
7
+ "e2e"
8
+ ],
6
9
  "main": "./dist/vuehookform.cjs",
7
10
  "module": "./dist/vuehookform.js",
8
11
  "types": "./dist/index.d.ts",
@@ -34,6 +37,9 @@
34
37
  "test": "vitest",
35
38
  "test:run": "vitest run",
36
39
  "test:coverage": "vitest run --coverage",
40
+ "e2e:dev": "npm run -w e2e dev",
41
+ "test:e2e": "npm run build:lib && npm run -w e2e test:e2e:ci",
42
+ "test:e2e:open": "npm run build:lib && npm run -w e2e test:e2e:open",
37
43
  "prepare": "husky"
38
44
  },
39
45
  "repository": {
package/dist/context.d.ts DELETED
@@ -1,37 +0,0 @@
1
- import { InjectionKey } from 'vue';
2
- import { UseFormReturn } from './types';
3
- import { ZodType } from 'zod';
4
- /**
5
- * Injection key for form context
6
- */
7
- export declare const FormContextKey: InjectionKey<UseFormReturn<ZodType>>;
8
- /**
9
- * Provide form methods to child components via Vue's dependency injection.
10
- *
11
- * Call this in a parent component's setup function after calling useForm().
12
- *
13
- * @example
14
- * ```ts
15
- * // Parent component
16
- * const form = useForm({ schema })
17
- * provideForm(form)
18
- * ```
19
- *
20
- * @param methods - The return value from useForm()
21
- */
22
- export declare function provideForm<TSchema extends ZodType>(methods: UseFormReturn<TSchema>): void;
23
- /**
24
- * Access form methods in a child component via Vue's dependency injection.
25
- *
26
- * Must be used within a component tree where provideForm() has been called.
27
- *
28
- * @example
29
- * ```ts
30
- * // Child component
31
- * const { register, formState } = useFormContext()
32
- * ```
33
- *
34
- * @returns The form methods from the parent component's useForm() call
35
- * @throws Error if used outside of a FormProvider context
36
- */
37
- export declare function useFormContext<TSchema extends ZodType>(): UseFormReturn<TSchema>;
@@ -1,27 +0,0 @@
1
- import { Ref } from 'vue';
2
- import { RegisterOptions } from '../types';
3
- /**
4
- * Sync values from uncontrolled DOM inputs to form data
5
- *
6
- * This reads the current DOM state from uncontrolled inputs and updates
7
- * the formData object. Used before form submission and when getting values.
8
- *
9
- * Handles type coercion for:
10
- * - checkbox: returns boolean (el.checked)
11
- * - number/range: returns number (el.valueAsNumber)
12
- * - all other types: returns string (el.value)
13
- *
14
- * @param fieldRefs - Map of field names to their DOM element refs
15
- * @param fieldOptions - Map of field names to their registration options
16
- * @param formData - The reactive form data object to update
17
- */
18
- export declare function syncUncontrolledInputs(fieldRefs: Map<string, Ref<HTMLInputElement | null>>, fieldOptions: Map<string, RegisterOptions>, formData: Record<string, unknown>): void;
19
- /**
20
- * Update a single DOM element with a new value
21
- *
22
- * Handles both checkbox and text inputs appropriately.
23
- *
24
- * @param el - The DOM input element to update
25
- * @param value - The value to set
26
- */
27
- export declare function updateDomElement(el: HTMLInputElement, value: unknown): void;
@@ -1,53 +0,0 @@
1
- import { ShallowRef } from 'vue';
2
- /**
3
- * Mark a field as dirty (value has changed from default).
4
- * Optimized to skip clone if already dirty.
5
- *
6
- * @param dirtyFields - The reactive dirty fields record
7
- * @param fieldName - Name of the field to mark as dirty
8
- */
9
- export declare function markFieldDirty(dirtyFields: ShallowRef<Record<string, boolean>>, fieldName: string): void;
10
- /**
11
- * Mark a field as touched (user has interacted with it).
12
- * Optimized to skip clone if already touched.
13
- *
14
- * @param touchedFields - The reactive touched fields record
15
- * @param fieldName - Name of the field to mark as touched
16
- */
17
- export declare function markFieldTouched(touchedFields: ShallowRef<Record<string, boolean>>, fieldName: string): void;
18
- /**
19
- * Clear dirty state for a field.
20
- *
21
- * @param dirtyFields - The reactive dirty fields record
22
- * @param fieldName - Name of the field to clear
23
- */
24
- export declare function clearFieldDirty(dirtyFields: ShallowRef<Record<string, boolean>>, fieldName: string): void;
25
- /**
26
- * Clear touched state for a field.
27
- *
28
- * @param touchedFields - The reactive touched fields record
29
- * @param fieldName - Name of the field to clear
30
- */
31
- export declare function clearFieldTouched(touchedFields: ShallowRef<Record<string, boolean>>, fieldName: string): void;
32
- /**
33
- * Clear errors for a field and its nested paths.
34
- * Optimized with early exit if nothing to delete.
35
- *
36
- * @param errors - The reactive errors record
37
- * @param fieldName - Name of the field (clears exact match and all nested paths)
38
- */
39
- export declare function clearFieldErrors<T>(errors: ShallowRef<Record<string, T>>, fieldName: string): void;
40
- /**
41
- * Update field dirty state based on value comparison with default.
42
- * Field is dirty only if current value differs from default value.
43
- *
44
- * Uses lazy hash computation - default hashes are computed on first access
45
- * and cached for subsequent comparisons.
46
- *
47
- * @param dirtyFields - The reactive dirty fields record
48
- * @param defaultValues - The original default values
49
- * @param defaultValueHashes - Cache of hashed default values
50
- * @param fieldName - Name of the field to check
51
- * @param currentValue - The current value to compare against default
52
- */
53
- export declare function updateFieldDirtyState(dirtyFields: ShallowRef<Record<string, boolean>>, defaultValues: Record<string, unknown>, defaultValueHashes: Map<string, string>, fieldName: string, currentValue: unknown): void;
@@ -1,117 +0,0 @@
1
- import { Ref, ShallowRef } from 'vue';
2
- import { ZodType } from 'zod';
3
- import { UseFormOptions, FieldErrors, FieldErrorValue, InferSchema, RegisterOptions, FieldArrayItem, FieldArrayRules } from '../types';
4
- /**
5
- * Internal state for field array management.
6
- * Tracks items, their indices, and array-level validation rules.
7
- *
8
- * @internal This interface is used internally by useFieldArray and should not be
9
- * directly instantiated by consumers.
10
- */
11
- export interface FieldArrayState {
12
- /** Reactive list of field array items with stable keys for Vue reconciliation */
13
- items: Ref<FieldArrayItem[]>;
14
- /** Raw array values (kept in sync with formData) */
15
- values: unknown[];
16
- /** O(1) lookup cache mapping item keys to their current indices */
17
- indexCache: Map<string, number>;
18
- /** Optional validation rules for the array itself (minLength, maxLength, custom) */
19
- rules?: FieldArrayRules;
20
- }
21
- /**
22
- * Cached event handlers for a field.
23
- * These are created once per field registration and reused to prevent
24
- * unnecessary re-renders and closure recreation.
25
- *
26
- * @internal This interface is used internally by useFieldRegistration and should not be
27
- * directly instantiated by consumers.
28
- */
29
- export interface FieldHandlers {
30
- /** Handler for input events, triggers validation based on mode */
31
- onInput: (e: Event) => Promise<void>;
32
- /** Handler for blur events, marks field as touched and may trigger validation */
33
- onBlur: (e: Event) => Promise<void>;
34
- /** Ref callback to capture the DOM element reference */
35
- refCallback: (el: unknown) => void;
36
- }
37
- /**
38
- * Shared form context containing all reactive state.
39
- * This is the central state container passed to sub-modules via dependency injection.
40
- *
41
- * The context is organized into several categories:
42
- * - **Form Data**: Raw form values and their defaults
43
- * - **Form State**: Validation errors, touched/dirty tracking, submission state
44
- * - **Field Tracking**: DOM refs, registration options, field arrays
45
- * - **Validation**: Caching, debouncing, and async validation coordination
46
- * - **Configuration**: Form options and disabled state
47
- *
48
- * @typeParam FormValues - The inferred type from the Zod schema
49
- *
50
- * @internal This interface is used internally by useForm and its sub-modules.
51
- * Consumers should use the public API returned by useForm() instead.
52
- */
53
- export interface FormContext<FormValues> {
54
- /** Reactive form data object containing current field values */
55
- formData: Record<string, unknown>;
56
- /** Original default values used for reset() and dirty detection */
57
- defaultValues: Record<string, unknown>;
58
- /** Current validation errors keyed by field path */
59
- errors: ShallowRef<FieldErrors<FormValues>>;
60
- /** Record of field paths that have been touched (blurred) */
61
- touchedFields: ShallowRef<Record<string, boolean>>;
62
- /** Record of field paths that differ from default values */
63
- dirtyFields: ShallowRef<Record<string, boolean>>;
64
- /** Whether the form is currently being submitted */
65
- isSubmitting: Ref<boolean>;
66
- /** Whether async default values are being loaded */
67
- isLoading: Ref<boolean>;
68
- /** Number of times the form has been submitted */
69
- submitCount: Ref<number>;
70
- /** Error that occurred while loading async default values */
71
- defaultValuesError: Ref<unknown>;
72
- /** Whether the last submission completed successfully */
73
- isSubmitSuccessful: Ref<boolean>;
74
- /** Set of field paths currently being validated (for isValidating state) */
75
- validatingFields: ShallowRef<Set<string>>;
76
- /** External errors (e.g., from server) merged with validation errors */
77
- externalErrors: ShallowRef<FieldErrors<FormValues>>;
78
- /** Timers for delayed error display per field */
79
- errorDelayTimers: Map<string, ReturnType<typeof setTimeout>>;
80
- /** Pending errors waiting for delay timer to complete */
81
- pendingErrors: Map<string, FieldErrorValue>;
82
- /** DOM element refs for registered uncontrolled fields */
83
- fieldRefs: Map<string, Ref<HTMLInputElement | null>>;
84
- /** Registration options per field path */
85
- fieldOptions: Map<string, RegisterOptions>;
86
- /** Field array state for array fields managed by fields() */
87
- fieldArrays: Map<string, FieldArrayState>;
88
- /** Cached event handlers to prevent recreation on re-render */
89
- fieldHandlers: Map<string, FieldHandlers>;
90
- /** Debounce timers for custom async validation per field */
91
- debounceTimers: Map<string, ReturnType<typeof setTimeout>>;
92
- /** Request IDs for canceling stale async validation results */
93
- validationRequestIds: Map<string, number>;
94
- /** Generation counter incremented on reset to cancel in-flight validations */
95
- resetGeneration: Ref<number>;
96
- /** Cache of validation results keyed by field path and value hash */
97
- validationCache: Map<string, {
98
- hash: string;
99
- isValid: boolean;
100
- }>;
101
- /** Debounce timers for schema validation per field */
102
- schemaValidationTimers: Map<string, ReturnType<typeof setTimeout>>;
103
- /** Set of field paths with persistent errors (survive validation cycles) */
104
- persistentErrorFields: Set<string>;
105
- /** Hashed default values for value-comparison dirty detection */
106
- defaultValueHashes: Map<string, string>;
107
- /** Whether the entire form is disabled */
108
- isDisabled: Ref<boolean>;
109
- /** Original options passed to useForm() */
110
- options: UseFormOptions<ZodType>;
111
- /** Cleanup function to stop all watchers and prevent memory leaks */
112
- cleanup: () => void;
113
- }
114
- /**
115
- * Create a new form context with all reactive state initialized
116
- */
117
- export declare function createFormContext<TSchema extends ZodType>(options: UseFormOptions<TSchema>): FormContext<InferSchema<TSchema>>;
@@ -1,8 +0,0 @@
1
- import { FormContext } from './formContext';
2
- import { FieldArray, FieldArrayOptions, Path } from '../types';
3
- /**
4
- * Create field array management functions
5
- */
6
- export declare function createFieldArrayManager<FormValues>(ctx: FormContext<FormValues>, validate: (fieldPath?: string) => Promise<boolean>, setFocus: (name: string) => void): {
7
- fields: <TPath extends Path<FormValues>>(name: TPath, options?: FieldArrayOptions) => FieldArray;
8
- };
@@ -1,9 +0,0 @@
1
- import { FormContext } from './formContext';
2
- import { RegisterOptions, RegisterReturn, UnregisterOptions, Path } from '../types';
3
- /**
4
- * Create field registration functions
5
- */
6
- export declare function createFieldRegistration<FormValues>(ctx: FormContext<FormValues>, validate: (fieldPath?: string) => Promise<boolean>): {
7
- register: <TPath extends Path<FormValues>>(name: TPath, registerOptions?: RegisterOptions) => RegisterReturn;
8
- unregister: <TPath extends Path<FormValues>>(name: TPath, options?: UnregisterOptions) => void;
9
- };
@@ -1,8 +0,0 @@
1
- import { FormContext } from './formContext';
2
- /**
3
- * Create validation functions for form
4
- */
5
- export declare function createValidation<FormValues>(ctx: FormContext<FormValues>): {
6
- validate: (fieldPath?: string) => Promise<boolean>;
7
- clearAllPendingErrors: () => void;
8
- };
package/dist/index.d.ts DELETED
@@ -1,24 +0,0 @@
1
- /**
2
- * Vue Hook Form - TypeScript-first form library for Vue 3
3
- *
4
- * @example
5
- * ```ts
6
- * import { useForm } from './lib'
7
- * import { z } from 'zod'
8
- *
9
- * const schema = z.object({
10
- * email: z.email(),
11
- * name: z.string().min(2)
12
- * })
13
- *
14
- * const { register, handleSubmit, formState } = useForm({ schema })
15
- * ```
16
- */
17
- export { useForm } from './useForm';
18
- export { provideForm, useFormContext, FormContextKey } from './context';
19
- export { useWatch, type UseWatchOptions } from './useWatch';
20
- export { useController, type UseControllerOptions, type UseControllerReturn, type ControllerFieldProps, } from './useController';
21
- export { useFormState, type UseFormStateOptions, type FormStateKey } from './useFormState';
22
- export { isFieldError } from './types';
23
- export type { UseFormOptions, UseFormReturn, RegisterOptions, RegisterReturn, FormState, FieldState, FieldErrors, FieldError, FieldErrorValue, ErrorOption, SetErrorsOptions, FieldArray, FieldArrayItem, FieldArrayOptions, FieldArrayRules, FieldArrayFocusOptions, InferSchema, FormValues, FormPath, Path, PathValue, ArrayElement, ArrayPath, FieldPath, ValidationMode, SetFocusOptions, ResetOptions, ResetFieldOptions, AsyncDefaultValues, TriggerOptions, SetValueOptions, UnregisterOptions, CriteriaMode, } from './types';
24
- export { get, set, unset, generateId, clearPathCache } from './utils/paths';