@vuehookform/core 0.6.0 → 0.7.0
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/dist/core/useFieldRegistration.d.ts +9 -2
- package/dist/index.d.ts +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/useFieldError.d.ts +54 -0
- package/dist/useFormState.d.ts +5 -6
- package/dist/useWatch.d.ts +1 -1
- package/dist/vuehookform.cjs +43 -13
- package/dist/vuehookform.cjs.map +1 -1
- package/dist/vuehookform.js +43 -14
- package/dist/vuehookform.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { FormContext } from './formContext';
|
|
2
|
-
import { RegisterOptions, RegisterReturn, UnregisterOptions, Path } from '../types';
|
|
2
|
+
import { RegisterOptions, RegisterReturn, UnregisterOptions, SetValueOptions, Path } from '../types';
|
|
3
|
+
/**
|
|
4
|
+
* Helpers object for onChange callbacks.
|
|
5
|
+
* Populated after useForm's setValue is defined (mutable reference pattern).
|
|
6
|
+
*/
|
|
7
|
+
export interface OnChangeHelpers {
|
|
8
|
+
setValue: (name: string, value: unknown, options?: SetValueOptions) => void;
|
|
9
|
+
}
|
|
3
10
|
/**
|
|
4
11
|
* Create field registration functions
|
|
5
12
|
*/
|
|
6
|
-
export declare function createFieldRegistration<FormValues>(ctx: FormContext<FormValues>, validate: (fieldPath?: string) => Promise<boolean
|
|
13
|
+
export declare function createFieldRegistration<FormValues>(ctx: FormContext<FormValues>, validate: (fieldPath?: string) => Promise<boolean>, onChangeHelpers?: OnChangeHelpers): {
|
|
7
14
|
register: <TPath extends Path<FormValues>>(name: TPath, registerOptions?: RegisterOptions) => RegisterReturn;
|
|
8
15
|
unregister: <TPath extends Path<FormValues>>(name: TPath, options?: UnregisterOptions) => void;
|
|
9
16
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export { provideForm, useFormContext, FormContextKey } from './context';
|
|
|
19
19
|
export { useWatch, type UseWatchOptions } from './useWatch';
|
|
20
20
|
export { useController, type UseControllerOptions, type UseControllerReturn, type ControllerFieldProps, type LooseControllerOptions, } from './useController';
|
|
21
21
|
export { useFormState, type UseFormStateOptions, type FormStateKey } from './useFormState';
|
|
22
|
+
export { useFieldError, type UseFieldErrorOptions, type LooseFieldErrorOptions, } from './useFieldError';
|
|
22
23
|
export { isFieldError } from './types';
|
|
23
24
|
export type { UseFormOptions, UseFormReturn, UseFormReturn as Control, LooseControl, 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
25
|
export { get, set, unset, clearPathCache } from './utils/paths';
|
package/dist/types.d.ts
CHANGED
|
@@ -186,6 +186,10 @@ export interface FormState<T> {
|
|
|
186
186
|
isSubmitSuccessful: boolean;
|
|
187
187
|
/** Whether the form is disabled */
|
|
188
188
|
disabled: boolean;
|
|
189
|
+
/** Whether form values match default values (opposite of isDirty) */
|
|
190
|
+
isPristine: boolean;
|
|
191
|
+
/** Whether the form can be submitted (isValid && !isSubmitting && !isLoading && !disabled) */
|
|
192
|
+
canSubmit: boolean;
|
|
189
193
|
}
|
|
190
194
|
/**
|
|
191
195
|
* State of an individual field
|
|
@@ -328,6 +332,26 @@ export interface RegisterOptions<TValue = unknown> {
|
|
|
328
332
|
shouldUnregister?: boolean;
|
|
329
333
|
/** Dependent fields to re-validate when this field changes */
|
|
330
334
|
deps?: string[];
|
|
335
|
+
/**
|
|
336
|
+
* Called after the field value changes via user input.
|
|
337
|
+
* Use for side effects like resetting dependent fields.
|
|
338
|
+
* Does NOT fire on programmatic setValue() calls.
|
|
339
|
+
*
|
|
340
|
+
* @param value - The new field value (typed based on field path)
|
|
341
|
+
* @param helpers - Helper functions for triggering side effects
|
|
342
|
+
*
|
|
343
|
+
* @example Reset dependent field when parent changes
|
|
344
|
+
* ```ts
|
|
345
|
+
* register('country', {
|
|
346
|
+
* onChange: (country, { setValue }) => {
|
|
347
|
+
* setValue('province', '') // Reset province when country changes
|
|
348
|
+
* },
|
|
349
|
+
* })
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
onChange?: (value: TValue, helpers: {
|
|
353
|
+
setValue: (name: string, value: unknown, options?: SetValueOptions) => void;
|
|
354
|
+
}) => void;
|
|
331
355
|
}
|
|
332
356
|
/**
|
|
333
357
|
* Return value from register() for binding to inputs.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ComputedRef } from 'vue';
|
|
2
|
+
import { ZodType } from 'zod';
|
|
3
|
+
import { UseFormReturn, Path, InferSchema, LooseControl } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Options for useFieldError composable
|
|
6
|
+
*/
|
|
7
|
+
export interface UseFieldErrorOptions<TSchema extends ZodType, TPath extends Path<InferSchema<TSchema>>> {
|
|
8
|
+
/** Field path to get the error for */
|
|
9
|
+
name: TPath;
|
|
10
|
+
/** Form control from useForm (uses context if not provided) */
|
|
11
|
+
control?: UseFormReturn<TSchema>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Loose options for useFieldError when schema type is unknown.
|
|
15
|
+
*/
|
|
16
|
+
export interface LooseFieldErrorOptions {
|
|
17
|
+
/** Field path as a string */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Form control from useForm (uses context if not provided) */
|
|
20
|
+
control?: LooseControl;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get a reactive error message for a single field.
|
|
24
|
+
*
|
|
25
|
+
* Returns a ComputedRef that automatically updates when the field's
|
|
26
|
+
* error state changes. Normalizes both string errors and structured
|
|
27
|
+
* FieldError objects to a plain string message.
|
|
28
|
+
*
|
|
29
|
+
* For field array paths with per-item errors (not a single array-level error),
|
|
30
|
+
* returns `undefined`. Use `formState.value.errors` directly for item-level errors.
|
|
31
|
+
*
|
|
32
|
+
* @example Basic usage with context
|
|
33
|
+
* ```vue
|
|
34
|
+
* <script setup>
|
|
35
|
+
* const emailError = useFieldError({ name: 'email' })
|
|
36
|
+
* </script>
|
|
37
|
+
* <template>
|
|
38
|
+
* <span v-if="emailError" class="error">{{ emailError }}</span>
|
|
39
|
+
* </template>
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @example With explicit control
|
|
43
|
+
* ```ts
|
|
44
|
+
* const form = useForm({ schema })
|
|
45
|
+
* const emailError = useFieldError({ name: 'email', control: form })
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @example Nested path
|
|
49
|
+
* ```ts
|
|
50
|
+
* const cityError = useFieldError({ name: 'user.address.city' })
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function useFieldError(options: LooseFieldErrorOptions): ComputedRef<string | undefined>;
|
|
54
|
+
export declare function useFieldError<TSchema extends ZodType, TPath extends Path<InferSchema<TSchema>>>(options: UseFieldErrorOptions<TSchema, TPath>): ComputedRef<string | undefined>;
|
package/dist/useFormState.d.ts
CHANGED
|
@@ -23,17 +23,16 @@ export interface UseFormStateOptions<TSchema extends ZodType> {
|
|
|
23
23
|
* @example
|
|
24
24
|
* ```ts
|
|
25
25
|
* // Subscribe to all form state
|
|
26
|
-
* const
|
|
26
|
+
* const state = useFormState({})
|
|
27
|
+
* // Access: state.value.errors, state.value.isDirty, etc.
|
|
27
28
|
*
|
|
28
29
|
* // Subscribe to specific properties
|
|
29
|
-
* const
|
|
30
|
-
*
|
|
31
|
-
* // Subscribe to single property
|
|
32
|
-
* const isDirty = useFormState({ name: 'isDirty' })
|
|
30
|
+
* const state = useFormState({ name: ['isSubmitting', 'errors'] })
|
|
31
|
+
* // Access: state.value.isSubmitting, state.value.errors
|
|
33
32
|
*
|
|
34
33
|
* // With explicit control
|
|
35
34
|
* const { control } = useForm({ schema })
|
|
36
|
-
* const
|
|
35
|
+
* const state = useFormState({ control })
|
|
37
36
|
* ```
|
|
38
37
|
*/
|
|
39
38
|
export declare function useFormState<TSchema extends ZodType>(options?: UseFormStateOptions<TSchema>): ComputedRef<Partial<FormState<InferSchema<TSchema>>>>;
|
package/dist/useWatch.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export interface UseWatchOptions<TSchema extends ZodType, TPath extends Path<Inf
|
|
|
10
10
|
/** Field path or array of paths to watch (watches all if not provided) */
|
|
11
11
|
name?: TPath | TPath[];
|
|
12
12
|
/** Default value when field is undefined */
|
|
13
|
-
defaultValue?:
|
|
13
|
+
defaultValue?: PathValue<InferSchema<TSchema>, TPath>;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Watch form field values reactively without the full form instance
|
package/dist/vuehookform.cjs
CHANGED
|
@@ -818,7 +818,7 @@ function shouldValidateOnBlur(mode, hasSubmitted, reValidateMode) {
|
|
|
818
818
|
return mode === "onBlur" || mode === "onTouched" || hasSubmitted && (reValidateMode === "onBlur" || reValidateMode === "onTouched");
|
|
819
819
|
}
|
|
820
820
|
var validationRequestCounter = 0;
|
|
821
|
-
function createFieldRegistration(ctx, validate) {
|
|
821
|
+
function createFieldRegistration(ctx, validate, onChangeHelpers) {
|
|
822
822
|
function register(name, registerOptions) {
|
|
823
823
|
if (__DEV__) {
|
|
824
824
|
const syntaxError = validatePathSyntax(name);
|
|
@@ -864,6 +864,11 @@ function createFieldRegistration(ctx, validate) {
|
|
|
864
864
|
set(ctx.formData, name, value);
|
|
865
865
|
updateFieldDirtyState(ctx.dirtyFields, ctx.defaultValues, ctx.defaultValueHashes, name, value);
|
|
866
866
|
const fieldOpts = ctx.fieldOptions.get(name);
|
|
867
|
+
if (fieldOpts?.onChange && onChangeHelpers?.setValue) try {
|
|
868
|
+
fieldOpts.onChange(value, { setValue: onChangeHelpers.setValue });
|
|
869
|
+
} catch (err) {
|
|
870
|
+
if (__DEV__) console.error(`[vue-hook-form] Error in onChange callback for field '${name}':`, err);
|
|
871
|
+
}
|
|
867
872
|
if (shouldValidateOnChange(ctx.options.mode ?? "onSubmit", ctx.touchedFields.value[name] === true, ctx.submitCount.value > 0, ctx.options.reValidateMode)) {
|
|
868
873
|
const validationDebounceMs = ctx.options.validationDebounce || 0;
|
|
869
874
|
if (validationDebounceMs > 0) {
|
|
@@ -1423,7 +1428,8 @@ function useForm(options) {
|
|
|
1423
1428
|
ctx.cleanup();
|
|
1424
1429
|
});
|
|
1425
1430
|
const { validate, clearAllPendingErrors } = createValidation(ctx);
|
|
1426
|
-
const {
|
|
1431
|
+
const onChangeHelpers = { setValue: null };
|
|
1432
|
+
const { register, unregister } = createFieldRegistration(ctx, validate, onChangeHelpers);
|
|
1427
1433
|
function setFocus(name, focusOptions) {
|
|
1428
1434
|
if (__DEV__) {
|
|
1429
1435
|
const syntaxError = validatePathSyntax(name);
|
|
@@ -1514,6 +1520,12 @@ function useForm(options) {
|
|
|
1514
1520
|
},
|
|
1515
1521
|
get disabled() {
|
|
1516
1522
|
return ctx.isDisabled.value;
|
|
1523
|
+
},
|
|
1524
|
+
get isPristine() {
|
|
1525
|
+
return !isDirtyComputed.value;
|
|
1526
|
+
},
|
|
1527
|
+
get canSubmit() {
|
|
1528
|
+
return isValidComputed.value && !ctx.isSubmitting.value && !ctx.isLoading.value && !ctx.isDisabled.value;
|
|
1517
1529
|
}
|
|
1518
1530
|
});
|
|
1519
1531
|
const formState = (0, vue.computed)(() => formStateInternal);
|
|
@@ -1563,6 +1575,7 @@ function useForm(options) {
|
|
|
1563
1575
|
}
|
|
1564
1576
|
if (setValueOptions?.shouldValidate) validate(name);
|
|
1565
1577
|
}
|
|
1578
|
+
onChangeHelpers.setValue = setValue;
|
|
1566
1579
|
function reset(values, resetOptions) {
|
|
1567
1580
|
const opts = resetOptions || {};
|
|
1568
1581
|
ctx.validationCache.clear();
|
|
@@ -1828,25 +1841,28 @@ function useFormContext() {
|
|
|
1828
1841
|
function useWatch(options = {}) {
|
|
1829
1842
|
const { control, name, defaultValue } = options;
|
|
1830
1843
|
const form = control ?? useFormContext();
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1844
|
+
if (name === void 0) return form.watch();
|
|
1845
|
+
if (Array.isArray(name)) {
|
|
1846
|
+
const watched$1 = form.watch(name);
|
|
1847
|
+
if (defaultValue === void 0) return watched$1;
|
|
1848
|
+
return (0, vue.computed)(() => {
|
|
1849
|
+
const result = { ...watched$1.value };
|
|
1850
|
+
for (const fieldName of name) if (result[fieldName] === void 0) result[fieldName] = defaultValue;
|
|
1836
1851
|
return result;
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1854
|
+
const watched = form.watch(name);
|
|
1855
|
+
if (defaultValue === void 0) return watched;
|
|
1856
|
+
return (0, vue.computed)(() => watched.value ?? defaultValue);
|
|
1840
1857
|
}
|
|
1841
1858
|
function useController(options) {
|
|
1842
1859
|
const { name, control, defaultValue } = options;
|
|
1843
1860
|
const form = control ?? useFormContext();
|
|
1844
1861
|
const elementRef = (0, vue.ref)(null);
|
|
1845
1862
|
if (defaultValue !== void 0 && form.getValues(name) === void 0) form.setValue(name, defaultValue);
|
|
1863
|
+
const watchedValue = form.watch(name);
|
|
1846
1864
|
const value = (0, vue.computed)({
|
|
1847
|
-
get: () =>
|
|
1848
|
-
return form.getValues(name) ?? defaultValue;
|
|
1849
|
-
},
|
|
1865
|
+
get: () => watchedValue.value ?? defaultValue,
|
|
1850
1866
|
set: (newValue) => {
|
|
1851
1867
|
form.setValue(name, newValue);
|
|
1852
1868
|
}
|
|
@@ -1905,6 +1921,19 @@ function useFormState(options = {}) {
|
|
|
1905
1921
|
return { [name]: fullState[name] };
|
|
1906
1922
|
});
|
|
1907
1923
|
}
|
|
1924
|
+
function useFieldError(options) {
|
|
1925
|
+
const form = options.control ?? useFormContext();
|
|
1926
|
+
return (0, vue.computed)(() => {
|
|
1927
|
+
const error = get(form.formState.value.errors, options.name);
|
|
1928
|
+
if (!error) return void 0;
|
|
1929
|
+
if (typeof error === "string") return error;
|
|
1930
|
+
if (Array.isArray(error)) {
|
|
1931
|
+
if (__DEV__) console.warn(`[vue-hook-form] useFieldError('${options.name}') resolved to an array of per-item errors.\nuseFieldError only returns scalar error messages. Use formState.value.errors['${options.name}'] directly for item-level errors.`);
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
if (typeof error === "object" && "message" in error) return error.message;
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1908
1937
|
function isFieldError(error) {
|
|
1909
1938
|
return typeof error === "object" && error !== null && "type" in error && "message" in error && typeof error.type === "string" && typeof error.message === "string";
|
|
1910
1939
|
}
|
|
@@ -1916,6 +1945,7 @@ exports.provideForm = provideForm;
|
|
|
1916
1945
|
exports.set = set;
|
|
1917
1946
|
exports.unset = unset;
|
|
1918
1947
|
exports.useController = useController;
|
|
1948
|
+
exports.useFieldError = useFieldError;
|
|
1919
1949
|
exports.useForm = useForm;
|
|
1920
1950
|
exports.useFormContext = useFormContext;
|
|
1921
1951
|
exports.useFormState = useFormState;
|