fix(Form): handle schema output types

This commit is contained in:
Romain Hamel
2025-04-14 19:50:27 +02:00
parent d5bcb0da59
commit 5aea866057
2 changed files with 30 additions and 30 deletions

View File

@@ -7,18 +7,18 @@ import type { ComponentConfig } from '../types/utils'
type FormConfig = ComponentConfig<typeof theme, AppConfig, 'form'>
export interface FormProps<T extends object> {
export interface FormProps<I extends object, O extends object = I> {
id?: string | number
/** Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs. */
schema?: FormSchema<T>
schema?: FormSchema<I, O>
/** An object representing the current state of the form. */
state: Partial<T>
state: Partial<I> | O
/**
* Custom validation function to validate the form state.
* @param state - The current state of the form.
* @returns A promise that resolves to an array of FormError objects, or an array of FormError objects directly.
*/
validate?: (state: Partial<T>) => Promise<FormError[]> | FormError[]
validate?: (state: Partial<I> | O) => Promise<FormError[]> | FormError[]
/**
* The list of input events that trigger the form validation.
* @defaultValue `['blur', 'change', 'input']`
@@ -43,11 +43,11 @@ export interface FormProps<T extends object> {
*/
loadingAuto?: boolean
class?: any
onSubmit?: ((event: FormSubmitEvent<T>) => void | Promise<void>) | (() => void | Promise<void>)
onSubmit?: ((event: FormSubmitEvent<O>) => void | Promise<void>) | (() => void | Promise<void>)
}
export interface FormEmits<T extends object> {
(e: 'submit', payload: FormSubmitEvent<T>): void
export interface FormEmits<I extends object, O extends object = I> {
(e: 'submit', payload: FormSubmitEvent<O>): void
(e: 'error', payload: FormErrorEvent): void
}
@@ -56,7 +56,7 @@ export interface FormSlots {
}
</script>
<script lang="ts" setup generic="T extends object">
<script lang="ts" setup generic="I extends object, O extends object = I">
import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from 'vue'
import { useEventBus } from '@vueuse/core'
import { useAppConfig } from '#imports'
@@ -65,7 +65,7 @@ import { tv } from '../utils/tv'
import { validateSchema } from '../utils/form'
import { FormValidationException } from '../types/form'
const props = withDefaults(defineProps<FormProps<T>>(), {
const props = withDefaults(defineProps<FormProps<I, O>>(), {
validateOn() {
return ['input', 'blur', 'change'] as FormInputEvents[]
},
@@ -74,7 +74,7 @@ const props = withDefaults(defineProps<FormProps<T>>(), {
loadingAuto: true
})
const emits = defineEmits<FormEmits<T>>()
const emits = defineEmits<FormEmits<I, O>>()
defineSlots<FormSlots>()
const appConfig = useAppConfig() as FormConfig['AppConfig']
@@ -83,7 +83,7 @@ const ui = computed(() => tv({ extend: tv(theme), ...(appConfig.ui?.form || {})
const formId = props.id ?? useId() as string
const bus = useEventBus<FormEvent<T>>(`form-${formId}`)
const bus = useEventBus<FormEvent<I>>(`form-${formId}`)
const parentBus = inject(
formBusInjectionKey,
undefined
@@ -141,12 +141,12 @@ onUnmounted(() => {
const errors = ref<FormErrorWithId[]>([])
provide('form-errors', errors)
const inputs = ref<{ [P in keyof T]?: { id?: string, pattern?: RegExp } }>({})
const inputs = ref<{ [P in keyof I]?: { id?: string, pattern?: RegExp } }>({})
provide(formInputsInjectionKey, inputs as any)
const dirtyFields = new Set<keyof T>()
const touchedFields = new Set<keyof T>()
const blurredFields = new Set<keyof T>()
const dirtyFields = new Set<keyof I>()
const touchedFields = new Set<keyof I>()
const blurredFields = new Set<keyof I>()
function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
return errs.map(err => ({
@@ -155,7 +155,7 @@ function resolveErrorIds(errs: FormError[]): FormErrorWithId[] {
}))
}
const transformedState = ref<T | null>(null)
const transformedState = ref<I | null>(null)
async function getErrors(): Promise<FormErrorWithId[]> {
let errs = props.validate ? (await props.validate(props.state)) ?? [] : []
@@ -172,8 +172,8 @@ async function getErrors(): Promise<FormErrorWithId[]> {
return resolveErrorIds(errs)
}
async function _validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean } = { silent: false, nested: true, transform: false }): Promise<T | false> {
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof T)[]
async function _validate(opts: { name?: keyof I | (keyof I)[], silent?: boolean, nested?: boolean, transform?: boolean } = { silent: false, nested: true, transform: false }): Promise<I | false> {
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof I)[]
const nestedValidatePromises = !names && opts.nested
? Array.from(nestedForms.value.values()).map(
@@ -213,7 +213,7 @@ async function _validate(opts: { name?: keyof T | (keyof T)[], silent?: boolean,
Object.assign(props.state, transformedState.value)
}
return props.state as T
return props.state as I
}
const loading = ref(false)
@@ -251,11 +251,11 @@ provide(formOptionsInjectionKey, computed(() => ({
validateOnInputDelay: props.validateOnInputDelay
})))
defineExpose<Form<T>>({
defineExpose<Form<I>>({
validate: _validate,
errors,
setErrors(errs: FormError[], name?: keyof T) {
setErrors(errs: FormError[], name?: keyof I) {
if (name) {
errors.value = errors.value
.filter(error => error.name !== name)
@@ -269,7 +269,7 @@ defineExpose<Form<T>>({
await onSubmitWrapper(new Event('submit'))
},
getErrors(name?: keyof T) {
getErrors(name?: keyof I) {
if (name) {
return errors.value.filter(err => err.name === name)
}
@@ -288,9 +288,9 @@ defineExpose<Form<T>>({
loading,
dirty: computed(() => !!dirtyFields.size),
dirtyFields: readonly(dirtyFields) as DeepReadonly<Set<keyof T>>,
blurredFields: readonly(blurredFields) as DeepReadonly<Set<keyof T>>,
touchedFields: readonly(touchedFields) as DeepReadonly<Set<keyof T>>
dirtyFields: readonly(dirtyFields) as DeepReadonly<Set<keyof I>>,
blurredFields: readonly(blurredFields) as DeepReadonly<Set<keyof I>>,
touchedFields: readonly(touchedFields) as DeepReadonly<Set<keyof I>>
})
</script>

View File

@@ -21,11 +21,11 @@ export interface Form<T extends object> {
blurredFields: DeepReadonly<Set<keyof T>>
}
export type FormSchema<T extends object> =
| YupObjectSchema<T>
| JoiSchema<T>
| SuperstructSchema<any, any>
| StandardSchemaV1
export type FormSchema<I extends object, O extends object = I> =
| YupObjectSchema<I>
| JoiSchema<I>
| SuperstructSchema<I, O>
| StandardSchemaV1<I, O>
export type FormInputEvents = 'input' | 'blur' | 'change' | 'focus'