diff --git a/.changeset/shy-scissors-hug.md b/.changeset/shy-scissors-hug.md new file mode 100644 index 000000000..a9f4387ff --- /dev/null +++ b/.changeset/shy-scissors-hug.md @@ -0,0 +1,5 @@ +--- +"vee-validate": patch +--- + +feat: allow path meta querying for nested fields closes #4575 diff --git a/packages/vee-validate/src/useForm.ts b/packages/vee-validate/src/useForm.ts index 7bc4e2b89..e6092664e 100644 --- a/packages/vee-validate/src/useForm.ts +++ b/packages/vee-validate/src/useForm.ts @@ -719,15 +719,31 @@ export function useForm< } function isFieldTouched(field: Path) { - return !!findPathState(field)?.touched; + const pathState = findPathState(field); + if (pathState) { + return pathState.touched; + } + + // Find all nested paths and consider their touched state + return pathStates.value.filter(s => s.path.startsWith(field)).some(s => s.touched); } function isFieldDirty(field: Path) { - return !!findPathState(field)?.dirty; + const pathState = findPathState(field); + if (pathState) { + return pathState.dirty; + } + + return pathStates.value.filter(s => s.path.startsWith(field)).some(s => s.dirty); } function isFieldValid(field: Path) { - return !!findPathState(field)?.valid; + const pathState = findPathState(field); + if (pathState) { + return pathState.valid; + } + + return pathStates.value.filter(s => s.path.startsWith(field)).every(s => s.valid); } /** diff --git a/packages/vee-validate/tests/useForm.spec.ts b/packages/vee-validate/tests/useForm.spec.ts index f0b6cff7d..9e95a0ce8 100644 --- a/packages/vee-validate/tests/useForm.spec.ts +++ b/packages/vee-validate/tests/useForm.spec.ts @@ -1183,6 +1183,8 @@ describe('useForm()', () => { setup() { form = useForm(); useField('fname'); + useField('nested.lname'); + useField('nested.fname'); return {}; }, @@ -1192,10 +1194,13 @@ describe('useForm()', () => { await flushPromises(); expect(form.meta.value.touched).toBe(false); expect(form.isFieldTouched('fname')).toBe(false); + expect(form.isFieldTouched('nested')).toBe(false); form.setFieldTouched('fname', true); + form.setFieldTouched('nested.lname', true); await flushPromises(); expect(form.meta.value.touched).toBe(true); expect(form.isFieldTouched('fname')).toBe(true); + expect(form.isFieldTouched('nested')).toBe(true); }); test('can query field dirty state', async () => { @@ -1204,6 +1209,8 @@ describe('useForm()', () => { setup() { form = useForm(); useField('fname'); + useField('nested.lname'); + useField('nested.fname'); return {}; }, @@ -1213,10 +1220,13 @@ describe('useForm()', () => { await flushPromises(); expect(form.meta.value.dirty).toBe(false); expect(form.isFieldDirty('fname')).toBe(false); + expect(form.isFieldDirty('nested')).toBe(false); form.setFieldValue('fname', 'value'); + form.setFieldValue('nested.lname', 'value'); await flushPromises(); expect(form.meta.value.dirty).toBe(true); expect(form.isFieldDirty('fname')).toBe(true); + expect(form.isFieldDirty('nested')).toBe(true); }); test('can query field valid state', async () => { @@ -1225,6 +1235,8 @@ describe('useForm()', () => { setup() { form = useForm(); useField('fname'); + useField('nested.lname'); + useField('nested.fname'); return {}; }, @@ -1234,10 +1246,13 @@ describe('useForm()', () => { await flushPromises(); expect(form.meta.value.valid).toBe(true); expect(form.isFieldValid('fname')).toBe(true); + expect(form.isFieldValid('nested')).toBe(true); form.setFieldError('fname', 'ERROR'); + form.setFieldError('nested.lname', 'ERROR'); await flushPromises(); expect(form.meta.value.valid).toBe(false); expect(form.isFieldValid('fname')).toBe(false); + expect(form.isFieldValid('nested')).toBe(false); }); // #4438