@vuehookform/core 0.4.0 → 0.4.2

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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2025 vuehookform
3
+ Copyright (c) 2026 vuehookform
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -6,6 +6,11 @@ import { RegisterOptions } from '../types';
6
6
  * This reads the current DOM state from uncontrolled inputs and updates
7
7
  * the formData object. Used before form submission and when getting values.
8
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
+ *
9
14
  * @param fieldRefs - Map of field names to their DOM element refs
10
15
  * @param fieldOptions - Map of field names to their registration options
11
16
  * @param formData - The reactive form data object to update
@@ -1,42 +1,34 @@
1
- import { Ref, ShallowRef } from 'vue';
1
+ import { ShallowRef } from 'vue';
2
2
  /**
3
3
  * Mark a field as dirty (value has changed from default).
4
4
  * Optimized to skip clone if already dirty.
5
- * Maintains O(1) dirty field count for performance.
6
5
  *
7
6
  * @param dirtyFields - The reactive dirty fields record
8
- * @param dirtyFieldCount - Counter for O(1) isDirty checks
9
7
  * @param fieldName - Name of the field to mark as dirty
10
8
  */
11
- export declare function markFieldDirty(dirtyFields: ShallowRef<Record<string, boolean>>, dirtyFieldCount: Ref<number>, fieldName: string): void;
9
+ export declare function markFieldDirty(dirtyFields: ShallowRef<Record<string, boolean>>, fieldName: string): void;
12
10
  /**
13
11
  * Mark a field as touched (user has interacted with it).
14
12
  * Optimized to skip clone if already touched.
15
- * Maintains O(1) touched field count for performance.
16
13
  *
17
14
  * @param touchedFields - The reactive touched fields record
18
- * @param touchedFieldCount - Counter for O(1) touched checks
19
15
  * @param fieldName - Name of the field to mark as touched
20
16
  */
21
- export declare function markFieldTouched(touchedFields: ShallowRef<Record<string, boolean>>, touchedFieldCount: Ref<number>, fieldName: string): void;
17
+ export declare function markFieldTouched(touchedFields: ShallowRef<Record<string, boolean>>, fieldName: string): void;
22
18
  /**
23
19
  * Clear dirty state for a field.
24
- * Maintains O(1) dirty field count for performance.
25
20
  *
26
21
  * @param dirtyFields - The reactive dirty fields record
27
- * @param dirtyFieldCount - Counter for O(1) isDirty checks
28
22
  * @param fieldName - Name of the field to clear
29
23
  */
30
- export declare function clearFieldDirty(dirtyFields: ShallowRef<Record<string, boolean>>, dirtyFieldCount: Ref<number>, fieldName: string): void;
24
+ export declare function clearFieldDirty(dirtyFields: ShallowRef<Record<string, boolean>>, fieldName: string): void;
31
25
  /**
32
26
  * Clear touched state for a field.
33
- * Maintains O(1) touched field count for performance.
34
27
  *
35
28
  * @param touchedFields - The reactive touched fields record
36
- * @param touchedFieldCount - Counter for O(1) touched checks
37
29
  * @param fieldName - Name of the field to clear
38
30
  */
39
- export declare function clearFieldTouched(touchedFields: ShallowRef<Record<string, boolean>>, touchedFieldCount: Ref<number>, fieldName: string): void;
31
+ export declare function clearFieldTouched(touchedFields: ShallowRef<Record<string, boolean>>, fieldName: string): void;
40
32
  /**
41
33
  * Clear errors for a field and its nested paths.
42
34
  * Optimized with early exit if nothing to delete.
@@ -45,3 +37,17 @@ export declare function clearFieldTouched(touchedFields: ShallowRef<Record<strin
45
37
  * @param fieldName - Name of the field (clears exact match and all nested paths)
46
38
  */
47
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;
@@ -2,57 +2,114 @@ import { Ref, ShallowRef } from 'vue';
2
2
  import { ZodType } from 'zod';
3
3
  import { UseFormOptions, FieldErrors, FieldErrorValue, InferSchema, RegisterOptions, FieldArrayItem, FieldArrayRules } from '../types';
4
4
  /**
5
- * Internal state for field array management
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.
6
10
  */
7
11
  export interface FieldArrayState {
12
+ /** Reactive list of field array items with stable keys for Vue reconciliation */
8
13
  items: Ref<FieldArrayItem[]>;
14
+ /** Raw array values (kept in sync with formData) */
9
15
  values: unknown[];
16
+ /** O(1) lookup cache mapping item keys to their current indices */
10
17
  indexCache: Map<string, number>;
18
+ /** Optional validation rules for the array itself (minLength, maxLength, custom) */
11
19
  rules?: FieldArrayRules;
12
20
  }
13
21
  /**
14
- * Cached event handlers for a field to prevent recreation on every render
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.
15
28
  */
16
29
  export interface FieldHandlers {
30
+ /** Handler for input events, triggers validation based on mode */
17
31
  onInput: (e: Event) => Promise<void>;
32
+ /** Handler for blur events, marks field as touched and may trigger validation */
18
33
  onBlur: (e: Event) => Promise<void>;
34
+ /** Ref callback to capture the DOM element reference */
19
35
  refCallback: (el: unknown) => void;
20
36
  }
21
37
  /**
22
- * Shared form context containing all reactive state
23
- * This is passed to sub-modules via dependency injection
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.
24
52
  */
25
53
  export interface FormContext<FormValues> {
54
+ /** Reactive form data object containing current field values */
26
55
  formData: Record<string, unknown>;
56
+ /** Original default values used for reset() and dirty detection */
27
57
  defaultValues: Record<string, unknown>;
58
+ /** Current validation errors keyed by field path */
28
59
  errors: ShallowRef<FieldErrors<FormValues>>;
60
+ /** Record of field paths that have been touched (blurred) */
29
61
  touchedFields: ShallowRef<Record<string, boolean>>;
62
+ /** Record of field paths that differ from default values */
30
63
  dirtyFields: ShallowRef<Record<string, boolean>>;
64
+ /** Whether the form is currently being submitted */
31
65
  isSubmitting: Ref<boolean>;
66
+ /** Whether async default values are being loaded */
32
67
  isLoading: Ref<boolean>;
68
+ /** Number of times the form has been submitted */
33
69
  submitCount: Ref<number>;
70
+ /** Error that occurred while loading async default values */
34
71
  defaultValuesError: Ref<unknown>;
72
+ /** Whether the last submission completed successfully */
35
73
  isSubmitSuccessful: Ref<boolean>;
74
+ /** Set of field paths currently being validated (for isValidating state) */
36
75
  validatingFields: ShallowRef<Set<string>>;
76
+ /** External errors (e.g., from server) merged with validation errors */
37
77
  externalErrors: ShallowRef<FieldErrors<FormValues>>;
78
+ /** Timers for delayed error display per field */
38
79
  errorDelayTimers: Map<string, ReturnType<typeof setTimeout>>;
80
+ /** Pending errors waiting for delay timer to complete */
39
81
  pendingErrors: Map<string, FieldErrorValue>;
82
+ /** DOM element refs for registered uncontrolled fields */
40
83
  fieldRefs: Map<string, Ref<HTMLInputElement | null>>;
84
+ /** Registration options per field path */
41
85
  fieldOptions: Map<string, RegisterOptions>;
86
+ /** Field array state for array fields managed by fields() */
42
87
  fieldArrays: Map<string, FieldArrayState>;
88
+ /** Cached event handlers to prevent recreation on re-render */
43
89
  fieldHandlers: Map<string, FieldHandlers>;
90
+ /** Debounce timers for custom async validation per field */
44
91
  debounceTimers: Map<string, ReturnType<typeof setTimeout>>;
92
+ /** Request IDs for canceling stale async validation results */
45
93
  validationRequestIds: Map<string, number>;
94
+ /** Generation counter incremented on reset to cancel in-flight validations */
46
95
  resetGeneration: Ref<number>;
47
- isDisabled: Ref<boolean>;
48
- dirtyFieldCount: Ref<number>;
49
- touchedFieldCount: Ref<number>;
96
+ /** Cache of validation results keyed by field path and value hash */
50
97
  validationCache: Map<string, {
51
98
  hash: string;
52
99
  isValid: boolean;
53
100
  }>;
101
+ /** Debounce timers for schema validation per field */
54
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() */
55
110
  options: UseFormOptions<ZodType>;
111
+ /** Cleanup function to stop all watchers and prevent memory leaks */
112
+ cleanup: () => void;
56
113
  }
57
114
  /**
58
115
  * Create a new form context with all reactive state initialized
package/dist/index.d.ts CHANGED
@@ -20,4 +20,5 @@ export { useWatch, type UseWatchOptions } from './useWatch';
20
20
  export { useController, type UseControllerOptions, type UseControllerReturn, type ControllerFieldProps, } from './useController';
21
21
  export { useFormState, type UseFormStateOptions, type FormStateKey } from './useFormState';
22
22
  export { isFieldError } from './types';
23
- export type { UseFormOptions, UseFormReturn, RegisterOptions, RegisterReturn, FormState, FieldState, FieldErrors, FieldError, FieldErrorValue, ErrorOption, SetErrorsOptions, FieldArray, FieldArrayItem, InferSchema, FormValues, FormPath, Path, PathValue, ArrayElement, ArrayPath, FieldPath, ValidationMode, SetFocusOptions, ResetOptions, ResetFieldOptions, AsyncDefaultValues, } 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';
package/dist/types.d.ts CHANGED
@@ -7,7 +7,7 @@ export type ValidationMode = 'onSubmit' | 'onBlur' | 'onChange' | 'onTouched';
7
7
  /**
8
8
  * Extract the inferred type from a Zod schema
9
9
  */
10
- export type InferSchema<T extends ZodType> = z.infer<T>;
10
+ export type InferSchema<TSchema extends ZodType> = z.infer<TSchema>;
11
11
  /**
12
12
  * Alias for InferSchema - extracts form value type from schema.
13
13
  * Use this when you need the actual form data type.
@@ -198,7 +198,7 @@ export interface FieldState {
198
198
  /** Whether field has a validation error */
199
199
  invalid: boolean;
200
200
  /** The error (string for backward compatibility, or FieldError for structured errors) */
201
- error?: string | FieldError;
201
+ error?: FieldErrorValue;
202
202
  }
203
203
  /**
204
204
  * Error option for setError()
@@ -208,6 +208,11 @@ export interface ErrorOption {
208
208
  type?: string;
209
209
  /** Error message to display */
210
210
  message: string;
211
+ /**
212
+ * If true, the error will not be cleared by subsequent validations.
213
+ * Useful for server-side validation errors that should persist until explicitly cleared.
214
+ */
215
+ persistent?: boolean;
211
216
  }
212
217
  /**
213
218
  * Options for setFocus()
@@ -241,6 +246,16 @@ export interface ResetFieldOptions<TValue = unknown> {
241
246
  /** New default value (updates stored default) - typed to match field */
242
247
  defaultValue?: TValue;
243
248
  }
249
+ /**
250
+ * Options for trigger()
251
+ */
252
+ export interface TriggerOptions {
253
+ /**
254
+ * If true, increments submitCount to activate reValidateMode behavior.
255
+ * Useful when you want manual validation to trigger reValidation on subsequent changes.
256
+ */
257
+ markAsSubmitted?: boolean;
258
+ }
244
259
  /**
245
260
  * Options for unregister()
246
261
  */
@@ -839,14 +854,20 @@ export interface UseFormReturn<TSchema extends ZodType> {
839
854
  /**
840
855
  * Manually trigger validation for specific fields or entire form
841
856
  * @param name - Optional field path or array of paths
857
+ * @param options - Optional trigger options (e.g., markAsSubmitted)
842
858
  */
843
- trigger: <TPath extends Path<InferSchema<TSchema>>>(name?: TPath | TPath[]) => Promise<boolean>;
859
+ trigger: <TPath extends Path<InferSchema<TSchema>>>(name?: TPath | TPath[], options?: TriggerOptions) => Promise<boolean>;
844
860
  /**
845
861
  * Programmatically focus a field
846
862
  * @param name - Field path
847
863
  * @param options - Focus options
848
864
  */
849
865
  setFocus: <TPath extends Path<InferSchema<TSchema>>>(name: TPath, options?: SetFocusOptions) => void;
866
+ /**
867
+ * Form configuration options (mode, reValidateMode).
868
+ * Useful for composables like useController that need to respect validation modes.
869
+ */
870
+ options: Pick<UseFormOptions<TSchema>, 'mode' | 'reValidateMode'>;
850
871
  }
851
872
  /**
852
873
  * Type guard to check if an error value is a structured FieldError object.
@@ -8,12 +8,12 @@
8
8
  * - Arrays (recursive clone)
9
9
  * - Date objects (new Date instance)
10
10
  * - null/undefined (pass-through)
11
+ * - Circular references (recreates the circular structure in the clone)
11
12
  *
12
13
  * Does NOT handle (by design, not needed for form data):
13
- * - Circular references
14
14
  * - Map/Set/WeakMap/WeakSet
15
15
  * - Functions
16
16
  * - Symbols
17
17
  * - Class instances (cloned as plain objects)
18
18
  */
19
- export declare function deepClone<T>(obj: T): T;
19
+ export declare function deepClone<T>(obj: T, seen?: Map<object, unknown>): T;
@@ -2,5 +2,8 @@
2
2
  * Fast value hashing for validation cache.
3
3
  * Uses JSON.stringify for objects/arrays, direct conversion for primitives.
4
4
  * Returns a string that can be compared for equality.
5
+ *
6
+ * Handles circular references by assigning stable IDs via WeakMap,
7
+ * ensuring the same object always produces the same hash.
5
8
  */
6
9
  export declare function hashValue(value: unknown): string;
@@ -0,0 +1,22 @@
1
+ import { ValidationMode } from '../types';
2
+ /**
3
+ * Determines if validation should occur on change event.
4
+ * Used by useController and register for consistent mode handling.
5
+ *
6
+ * @param mode - The form's validation mode
7
+ * @param isTouched - Whether the field has been touched
8
+ * @param reValidateMode - The form's reValidateMode (used after first submit)
9
+ * @param hasSubmitted - Whether the form has been submitted at least once (optional, for reValidateMode)
10
+ * @returns true if validation should be triggered
11
+ */
12
+ export declare function shouldValidateOnChange(mode: ValidationMode, isTouched: boolean, reValidateMode?: ValidationMode, hasSubmitted?: boolean): boolean;
13
+ /**
14
+ * Determines if validation should occur on blur event.
15
+ * Used by useController and register for consistent mode handling.
16
+ *
17
+ * @param mode - The form's validation mode
18
+ * @param hasSubmitted - Whether the form has been submitted at least once
19
+ * @param reValidateMode - The form's reValidateMode (used after first submit)
20
+ * @returns true if validation should be triggered
21
+ */
22
+ export declare function shouldValidateOnBlur(mode: ValidationMode, hasSubmitted: boolean, reValidateMode?: ValidationMode): boolean;
@@ -1,6 +1,10 @@
1
1
  /**
2
- * Clear the path cache. Useful for testing or when paths change significantly.
3
- * @internal
2
+ * Clear the path segment cache.
3
+ * Call this between SSR requests to prevent memory accumulation,
4
+ * or in tests to reset state.
5
+ *
6
+ * The cache is bounded to 256 entries, so clearing is optional
7
+ * for client-side only applications.
4
8
  */
5
9
  export declare function clearPathCache(): void;
6
10
  /**
@@ -19,7 +23,15 @@ export declare function set(obj: Record<string, unknown>, path: string, value: u
19
23
  */
20
24
  export declare function unset(obj: Record<string, unknown>, path: string): void;
21
25
  /**
22
- * Check if path exists in object
26
+ * Check if path exists in object.
27
+ * Unlike `get(obj, path) !== undefined`, this properly distinguishes between
28
+ * a missing path and a path that exists with an `undefined` value.
29
+ *
30
+ * @example
31
+ * has({ name: undefined }, 'name') // true - path exists
32
+ * has({ }, 'name') // false - path doesn't exist
33
+ * has({ user: { name: 'John' } }, 'user.name') // true
34
+ * has({ user: { name: 'John' } }, 'user.age') // false
23
35
  */
24
36
  export declare function has(obj: Record<string, unknown>, path: string): boolean;
25
37
  export declare function generateId(): string;