diff --git a/packages/docs/docs/composables/use-form-validator.md b/packages/docs/docs/composables/use-form-validator.md index a586805e1a..e5d759ed83 100644 --- a/packages/docs/docs/composables/use-form-validator.md +++ b/packages/docs/docs/composables/use-form-validator.md @@ -70,7 +70,7 @@ const { model } = useFormValidator({ To use the `eager`, `blur`, or `progressive` validation modes, you must use the `useFormField` composable to add the necessary validation events. -Two ways to bind validation events: +3 ways to bind validation events: ##### 1. Use the `ref` attribute on the component to get the reference @@ -104,7 +104,7 @@ const { value, errorMessage, isValid, hasError } = useFormField('name', { You can use the `v-bind` directive to bind the validation events to the component or HTML element. -If you use this method with a custom component, the component must emit the `blur` event to trigger the field validation. +If you use this method with a custom component, the component must emit the `blur` event to trigger the field validation. Otherwise, use the first method. ```vue{7,16} +##### 3. Use the `onBlur` handler directly from `useFormField` + +This method works if the component emits the `blur` event. Otherwise, use the first method. + +```vue{7} + + ``` diff --git a/packages/lib/modules/composables/useFormValidator/useFormField.ts b/packages/lib/modules/composables/useFormValidator/useFormField.ts index b45d4f6580..51b6e029c6 100644 --- a/packages/lib/modules/composables/useFormValidator/useFormField.ts +++ b/packages/lib/modules/composables/useFormValidator/useFormField.ts @@ -1,9 +1,10 @@ -import type { ComponentPublicInstance } from 'vue' +import type { ComponentPublicInstance, ComputedRef, WritableComputedRef } from 'vue' import type { BaseFormPayload, ExtractModelKey, FormFieldOptions, FormSchema, + ValidationIssues, } from './types' import { computed, onMounted, onUnmounted } from 'vue' @@ -24,10 +25,62 @@ import { updateFieldState, } from './utils' +interface UseFormFieldReturn { + /** + * Indicates if the field has an error + */ + hasError: ComputedRef + /** + * Errors of the field + */ + errors: ComputedRef + /** + * Error message of the field + * It's the first error of the field + */ + errorMessage: ComputedRef + /** + * Indicates if the field is valid + */ + isValid: ComputedRef + /** + * Indicates if the field has been modified + */ + isDirty: ComputedRef + /** + * Indicates if the field has been blurred + */ + isBlurred: ComputedRef + /** + * Indicates if the field has been validated + */ + isValidated: ComputedRef + /** + * Indicates if the field is validating + */ + isValidating: ComputedRef + /** + * Validation mode of the field + */ + mode: ComputedRef['mode']> + /** + * Value of the field + */ + value: WritableComputedRef + /** + * Validation events of the field + */ + validationEvents: ComputedRef> + /** + * Function to handle the blur event of the field + */ + onBlur: () => void +} + export function useFormField< FieldType extends Model[ExtractModelKey>], Model extends BaseFormPayload = BaseFormPayload, ->(name: ExtractModelKey>, options?: FormFieldOptions) { +>(name: ExtractModelKey>, options?: FormFieldOptions): UseFormFieldReturn { const opts = { formIdentifier: 'main-form-validator', ...options, @@ -72,7 +125,7 @@ export function useFormField< }) } - function onBlurHandler() { + function onBlur() { handleFieldBlur({ name, fieldState: fieldState.value, @@ -85,7 +138,7 @@ export function useFormField< const validationEvents = computed(() => getValidationEvents({ ref: opts.ref, - onBlurHandler, + onBlur, fieldState: fieldState.value, }), ) @@ -97,7 +150,7 @@ export function useFormField< interactiveElements = findInteractiveElements(element) addEventToInteractiveElements({ interactiveElements, - onBlurHandler, + onBlur, mode: fieldMode, }) } @@ -118,7 +171,7 @@ export function useFormField< onUnmounted(() => { removeEventFromInteractiveElements({ interactiveElements, - onBlurHandler, + onBlur, }) }) } @@ -138,5 +191,6 @@ export function useFormField< set: value => (payload.value[name] = value), }), validationEvents, + onBlur, } } diff --git a/packages/lib/modules/composables/useFormValidator/utils.ts b/packages/lib/modules/composables/useFormValidator/utils.ts index 33304f2547..a1461851e5 100644 --- a/packages/lib/modules/composables/useFormValidator/utils.ts +++ b/packages/lib/modules/composables/useFormValidator/utils.ts @@ -248,29 +248,29 @@ export function findInteractiveElements(el: HTMLElement) { export function addEventToInteractiveElements({ interactiveElements, - onBlurHandler, + onBlur, mode, }: { interactiveElements: HTMLElement[] - onBlurHandler: () => void + onBlur: () => void mode: StrictOptions['mode'] }) { interactiveElements.forEach((element) => { if (hasModeIncludes(['eager', 'blur', 'progressive'], mode)) { - element.addEventListener('blur', onBlurHandler) + element.addEventListener('blur', onBlur) } }) } export function removeEventFromInteractiveElements({ interactiveElements, - onBlurHandler, + onBlur, }: { interactiveElements: HTMLElement[] - onBlurHandler: () => void + onBlur: () => void }) { interactiveElements.forEach((element) => { - element.removeEventListener('blur', onBlurHandler) + element.removeEventListener('blur', onBlur) }) } @@ -522,18 +522,18 @@ export function getContext( export function getValidationEvents({ ref, fieldState, - onBlurHandler, + onBlur, }: { ref?: string fieldState: FieldState - onBlurHandler: () => void + onBlur: () => void }) { if (ref || hasModeIncludes(['aggressive', 'lazy'], fieldState.mode)) { return } return { - onBlur: onBlurHandler, + onBlur, } } diff --git a/packages/lib/tests/specs/composables/useFormValidator/utils.spec.ts b/packages/lib/tests/specs/composables/useFormValidator/utils.spec.ts index 3ab587cb91..af9be72271 100644 --- a/packages/lib/tests/specs/composables/useFormValidator/utils.spec.ts +++ b/packages/lib/tests/specs/composables/useFormValidator/utils.spec.ts @@ -204,12 +204,12 @@ describe('given addEventToInteractiveElements function', () => { { addEventListener: vi.fn(), getAttribute: vi.fn().mockReturnValue('text') }, { addEventListener: vi.fn(), getAttribute: vi.fn().mockReturnValue('radio') }, ] as unknown as HTMLElement[] - const onBlurHandler = vi.fn() + const onBlur = vi.fn() - addEventToInteractiveElements({ interactiveElements: mockElements, onBlurHandler, mode: 'eager' }) + addEventToInteractiveElements({ interactiveElements: mockElements, onBlur, mode: 'eager' }) - expect(mockElements[0].addEventListener).toHaveBeenCalledWith('blur', onBlurHandler) - expect(mockElements[1].addEventListener).toHaveBeenCalledWith('blur', onBlurHandler) + expect(mockElements[0].addEventListener).toHaveBeenCalledWith('blur', onBlur) + expect(mockElements[1].addEventListener).toHaveBeenCalledWith('blur', onBlur) }) }) }) @@ -218,12 +218,12 @@ describe('given removeEventFromInteractiveElements function', () => { describe('when called with interactive elements and event handlers', () => { it('then it removes all event listeners from interactive elements', () => { const mockElements = [{ removeEventListener: vi.fn() }, { removeEventListener: vi.fn() }] as unknown as HTMLElement[] - const onBlurHandler = vi.fn() + const onBlur = vi.fn() - removeEventFromInteractiveElements({ interactiveElements: mockElements, onBlurHandler }) + removeEventFromInteractiveElements({ interactiveElements: mockElements, onBlur }) mockElements.forEach((element) => { - expect(element.removeEventListener).toHaveBeenCalledWith('blur', onBlurHandler) + expect(element.removeEventListener).toHaveBeenCalledWith('blur', onBlur) }) }) }) @@ -511,13 +511,13 @@ describe('given getErrorMessages function', () => { }) describe('given getValidationEvents function', () => { - const onBlurHandler = vi.fn() + const onBlur = vi.fn() it('returns undefined when ref is provided', () => { const result = getValidationEvents({ ref: 'test', fieldState: { mode: 'eager' } as FieldState<{ name: string }>, - onBlurHandler, + onBlur, }) expect(result).toBeUndefined() }) @@ -525,13 +525,13 @@ describe('given getValidationEvents function', () => { it('returns undefined for aggressive or lazy mode', () => { const result = getValidationEvents({ fieldState: { mode: 'aggressive' } as FieldState<{ name: string }>, - onBlurHandler, + onBlur, }) expect(result).toBeUndefined() const result2 = getValidationEvents({ fieldState: { mode: 'lazy' } as FieldState<{ name: string }>, - onBlurHandler, + onBlur, }) expect(result2).toBeUndefined() }) @@ -539,15 +539,15 @@ describe('given getValidationEvents function', () => { it('returns all events for other modes', () => { const result = getValidationEvents({ fieldState: { mode: 'blur' } as FieldState<{ name: string }>, - onBlurHandler, + onBlur, }) - expect(result).toEqual({ onBlur: onBlurHandler }) + expect(result).toEqual({ onBlur }) const result3 = getValidationEvents({ fieldState: { mode: 'eager' } as FieldState<{ name: string }>, - onBlurHandler, + onBlur, }) - expect(result3).toEqual({ onBlur: onBlurHandler }) + expect(result3).toEqual({ onBlur }) }) })