refactor(@vben-core/form-ui): migrate to Zod 4 and TanStack Form (#8176)

* build(@vben-core/form-ui): update form validation dependencies

* refactor(@vben-core/form-ui): replace vee-validate with TanStack Form

* test(@vben-core/form-ui): cover TanStack Form migration

* docs(@vben/docs): document Zod 4 form migration

* refactor(project): update form adapters

* refactor(project): migrate application form consumers

* refactor(@vben/playground): migrate form examples

* docs(@vben/docs): fix validate return type and required rule empty check in form docs

* fix(@vben-core/form-ui): resolve oxlint and eslint errors

- rewrite nested ternary expressions as if/else in form-field-array.vue and form-runtime.ts
- sort @tanstack/vue-form before @vben-core/composables in package.json

* chore: fix merge artifacts and formatting

- move dependencies before devDependencies in root package.json
- update preferences snapshot for widget positioning fields
- format form-array demo README

* refactor(@vben-core/form-ui): optimize form runtime and value flow

- add fine-grained field selectors and atomic dependency resolution
- expose raw and formatted value snapshots with focused regression coverage

* fix(@vben/layouts): dispose sortable instance on unmount

* docs(@vben/docs): document form runtime API changes

- list added, changed, removed, and deprecated form APIs
- document atomic dependencies and raw versus formatted values
This commit is contained in:
dream-weave
2026-07-22 19:47:07 +08:00
committed by GitHub
parent 6b6708bcf2
commit 28757fb9c8
81 changed files with 4683 additions and 1435 deletions

View File

@@ -0,0 +1,35 @@
<script setup lang="ts">
import { provide, toRefs } from 'vue';
import { FORM_FIELD_INJECTION_KEY } from './injectionKeys';
const props = withDefaults(
defineProps<{
dirty?: boolean;
error?: string;
name: string;
touched?: boolean;
valid?: boolean;
}>(),
{
dirty: false,
error: undefined,
touched: false,
valid: true,
},
);
const { dirty, error, name, touched, valid } = toRefs(props);
provide(FORM_FIELD_INJECTION_KEY, {
dirty,
error,
name,
touched,
valid,
});
</script>
<template>
<slot></slot>
</template>

View File

@@ -1,27 +1,24 @@
<script lang="ts" setup>
import type { HTMLAttributes } from 'vue';
import { toValue } from 'vue';
import { cn } from '@vben-core/shared/utils';
import { ErrorMessage } from 'vee-validate';
import { useFormField } from './useFormField';
const props = defineProps<{
class?: HTMLAttributes['class'];
}>();
const { name, formMessageId } = useFormField();
const { error, formMessageId } = useFormField();
</script>
<template>
<ErrorMessage
<p
v-if="error"
:id="formMessageId"
data-slot="form-message"
as="p"
:name="toValue(name)"
:class="cn('text-destructive text-sm', props.class)"
/>
>
{{ error }}
</p>
</template>

View File

@@ -1,11 +1,10 @@
export { default as FormControl } from './FormControl.vue';
export { default as FormDescription } from './FormDescription.vue';
export { default as FormField } from './FormField.vue';
export { default as FormItem } from './FormItem.vue';
export { default as FormLabel } from './FormLabel.vue';
export { default as FormMessage } from './FormMessage.vue';
export { FORM_ITEM_INJECTION_KEY } from './injectionKeys';
export {
Form,
Field as FormField,
FieldArray as FormFieldArray,
} from 'vee-validate';
FORM_FIELD_INJECTION_KEY,
FORM_ITEM_INJECTION_KEY,
} from './injectionKeys';

View File

@@ -1,3 +1,13 @@
import type { InjectionKey } from 'vue';
import type { InjectionKey, Ref } from 'vue';
export interface FormFieldContext {
dirty: Readonly<Ref<boolean>>;
error: Readonly<Ref<string | undefined>>;
name: Readonly<Ref<string>>;
touched: Readonly<Ref<boolean>>;
valid: Readonly<Ref<boolean>>;
}
export const FORM_ITEM_INJECTION_KEY = Symbol() as InjectionKey<string>;
export const FORM_FIELD_INJECTION_KEY =
Symbol() as InjectionKey<FormFieldContext>;

View File

@@ -1,23 +1,26 @@
import { computed, inject } from 'vue';
import { FieldContextKey } from 'vee-validate';
import { FORM_ITEM_INJECTION_KEY } from './injectionKeys';
import {
FORM_FIELD_INJECTION_KEY,
FORM_ITEM_INJECTION_KEY,
} from './injectionKeys';
export function useFormField() {
const fieldContext = inject(FieldContextKey);
const fieldContext = inject(FORM_FIELD_INJECTION_KEY);
const fieldItemContext = inject(FORM_ITEM_INJECTION_KEY);
if (!fieldContext)
throw new Error('useFormField should be used within <FormField>');
if (!fieldItemContext)
throw new Error('useFormField should be used within <FormItem>');
const { name, errorMessage: error, meta } = fieldContext;
const { dirty, error, name, touched, valid } = fieldContext;
const id = fieldItemContext;
const fieldState = {
valid: computed(() => meta.valid),
isDirty: computed(() => meta.dirty),
isTouched: computed(() => meta.touched),
valid: computed(() => valid.value),
isDirty: computed(() => dirty.value),
isTouched: computed(() => touched.value),
error,
};