From 265bfbd53550b6e5a70ee750f7212ff55a582587 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Mon, 12 Sep 2022 00:03:19 +0200 Subject: [PATCH] feat: allow passing form control to useField closes #3204 --- packages/vee-validate/src/useField.ts | 23 ++++++++----- packages/vee-validate/src/useFieldState.ts | 18 ++++------- packages/vee-validate/src/useForm.ts | 19 ++--------- packages/vee-validate/tests/useField.spec.ts | 34 +++++++++++++++++++- 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/packages/vee-validate/src/useField.ts b/packages/vee-validate/src/useField.ts index 0f2a4dda7..6a5279ba1 100644 --- a/packages/vee-validate/src/useField.ts +++ b/packages/vee-validate/src/useField.ts @@ -23,6 +23,8 @@ import { PrivateFieldContext, SchemaValidationMode, ValidationOptions, + FormContext, + PrivateFormContext, } from './types'; import { normalizeRules, @@ -52,10 +54,12 @@ export interface FieldOptions { checkedValue?: MaybeRef; uncheckedValue?: MaybeRef; label?: MaybeRef; + controlled?: boolean; standalone?: boolean; keepValueOnUnmount?: MaybeRef; modelPropName?: string; syncVModel?: boolean; + form?: FormContext; } export type RuleExpression = @@ -95,19 +99,21 @@ function _useField( label, validateOnValueUpdate, uncheckedValue, - standalone, + controlled, keepValueOnUnmount, modelPropName, syncVModel, + form: controlForm, } = normalizeOptions(unref(name), opts); - const form = !standalone ? injectWithSelf(FormContextKey) : undefined; + const injectedForm = controlled ? injectWithSelf(FormContextKey) : undefined; + const form = (controlForm as PrivateFormContext | undefined) || injectedForm; // a flag indicating if the field is about to be removed/unmounted. let markedForRemoval = false; const { id, value, initialValue, meta, setState, errors, errorMessage } = useFieldState(name, { modelValue, - standalone, + form, }); if (syncVModel) { @@ -382,31 +388,32 @@ function _useField( * Normalizes partial field options to include the full options */ function normalizeOptions(name: string, opts: Partial> | undefined): FieldOptions { - const defaults = () => ({ + const defaults = (): Partial => ({ initialValue: undefined, validateOnMount: false, bails: true, - rules: '', label: name, validateOnValueUpdate: true, - standalone: false, keepValueOnUnmount: undefined, modelPropName: 'modelValue', syncVModel: true, + controlled: true, }); if (!opts) { - return defaults(); + return defaults() as FieldOptions; } // TODO: Deprecate this in next major release const checkedValue = 'valueProp' in opts ? opts.valueProp : opts.checkedValue; + const controlled = 'standalone' in opts ? !opts.standalone : opts.controlled; return { ...defaults(), ...(opts || {}), + controlled: controlled ?? true, checkedValue, - }; + } as FieldOptions; } /** diff --git a/packages/vee-validate/src/useFieldState.ts b/packages/vee-validate/src/useFieldState.ts index 679f044d8..0ca59490d 100644 --- a/packages/vee-validate/src/useFieldState.ts +++ b/packages/vee-validate/src/useFieldState.ts @@ -1,7 +1,6 @@ import { computed, reactive, ref, Ref, unref, watch } from 'vue'; -import { FormContextKey } from './symbols'; -import { FieldMeta, FieldState, MaybeRef } from './types'; -import { getFromPath, injectWithSelf, isEqual } from './utils'; +import { FieldMeta, FieldState, MaybeRef, PrivateFormContext } from './types'; +import { getFromPath, isEqual } from './utils'; export interface StateSetterInit extends FieldState { initialValue: TValue; @@ -20,7 +19,7 @@ export interface FieldStateComposable { export interface StateInit { modelValue: MaybeRef; - standalone: boolean; + form?: PrivateFormContext; } let ID_COUNTER = 0; @@ -29,8 +28,8 @@ export function useFieldState( path: MaybeRef, init: Partial> ): FieldStateComposable { - const { value, initialValue, setInitialValue } = _useFieldValue(path, init.modelValue, !init.standalone); - const { errorMessage, errors, setErrors } = _useFieldErrors(path, !init.standalone); + const { value, initialValue, setInitialValue } = _useFieldValue(path, init.modelValue, init.form); + const { errorMessage, errors, setErrors } = _useFieldErrors(path, init.form); const meta = _useFieldMeta(value, initialValue, errors); const id = ID_COUNTER >= Number.MAX_SAFE_INTEGER ? 0 : ++ID_COUNTER; @@ -76,9 +75,8 @@ interface FieldValueComposable { export function _useFieldValue( path: MaybeRef, modelValue?: MaybeRef, - shouldInjectForm = true + form?: PrivateFormContext ): FieldValueComposable { - const form = shouldInjectForm === true ? injectWithSelf(FormContextKey, undefined) : undefined; const modelRef = ref(unref(modelValue)) as Ref; function resolveInitialValue() { @@ -170,9 +168,7 @@ function _useFieldMeta( /** * Creates the error message state for the field state */ -export function _useFieldErrors(path: MaybeRef, shouldInjectForm: boolean) { - const form = shouldInjectForm ? injectWithSelf(FormContextKey, undefined) : undefined; - +export function _useFieldErrors(path: MaybeRef, form?: PrivateFormContext) { function normalizeErrors(messages: string | string[] | null | undefined) { if (!messages) { return []; diff --git a/packages/vee-validate/src/useForm.ts b/packages/vee-validate/src/useForm.ts index 9e66aeb73..16a73ed62 100644 --- a/packages/vee-validate/src/useForm.ts +++ b/packages/vee-validate/src/useForm.ts @@ -355,7 +355,7 @@ export function useForm = Record(path: MaybeRef) { - const { value } = _useFieldValue(path as string); + const { value } = _useFieldValue(path as string, undefined, formCtx as PrivateFormContext); watch( value, () => { @@ -782,24 +782,9 @@ export function useForm = Record resetForm(), - resetForm, - handleSubmit, submitForm, - setFieldError, - setErrors, - setFieldValue, - setValues, - setFieldTouched, - setTouched, - useFieldModel, }; } diff --git a/packages/vee-validate/tests/useField.spec.ts b/packages/vee-validate/tests/useField.spec.ts index e9aa7ebc2..4ea082079 100644 --- a/packages/vee-validate/tests/useField.spec.ts +++ b/packages/vee-validate/tests/useField.spec.ts @@ -1,4 +1,4 @@ -import { useField, useForm } from '@/vee-validate'; +import { FieldContext, FormContext, useField, useForm } from '@/vee-validate'; import { defineComponent, nextTick, onMounted, ref } from 'vue'; import { mountWithHoc, setValue, flushPromises } from './helpers'; @@ -744,4 +744,36 @@ describe('useField()', () => { await flushPromises(); expect(error?.textContent).toBe('not b'); }); + + test('allows explicit forms to be provided via the form option', async () => { + let form1!: FormContext; + let form2!: FormContext; + let field1!: FieldContext; + let field2!: FieldContext; + mountWithHoc({ + setup() { + form1 = useForm(); + form2 = useForm(); + field1 = useField('field', undefined, { + form: form1, + }); + field2 = useField('field', undefined, { + form: form2, + }); + + return {}; + }, + template: ` +
+ `, + }); + + await flushPromises(); + field1.value.value = '1'; + field2.value.value = '2'; + await flushPromises(); + + expect(form1.values.field).toBe('1'); + expect(form2.values.field).toBe('2'); + }); });