mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-21 15:31:46 +01:00
fix(Form): conditionally type form data via transform prop (#4188)
Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
@@ -2,12 +2,12 @@
|
||||
import type { DeepReadonly } from 'vue'
|
||||
import type { AppConfig } from '@nuxt/schema'
|
||||
import theme from '#build/ui/form'
|
||||
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId, InferInput, InferOutput } from '../types/form'
|
||||
import type { FormSchema, FormError, FormInputEvents, FormErrorEvent, FormSubmitEvent, FormEvent, Form, FormErrorWithId, InferInput, InferOutput, FormData } from '../types/form'
|
||||
import type { ComponentConfig } from '../types/utils'
|
||||
|
||||
type FormConfig = ComponentConfig<typeof theme, AppConfig, 'form'>
|
||||
|
||||
export interface FormProps<S extends FormSchema> {
|
||||
export interface FormProps<S extends FormSchema, T extends boolean = true> {
|
||||
id?: string | number
|
||||
/** Schema to validate the form state. Supports Standard Schema objects, Yup, Joi, and Superstructs. */
|
||||
schema?: S
|
||||
@@ -35,7 +35,7 @@ export interface FormProps<S extends FormSchema> {
|
||||
* If true, schema transformations will be applied to the state on submit.
|
||||
* @defaultValue `true`
|
||||
*/
|
||||
transform?: boolean
|
||||
transform?: T
|
||||
|
||||
/**
|
||||
* If true, this form will attach to its parent Form (if any) and validate at the same time.
|
||||
@@ -50,11 +50,11 @@ export interface FormProps<S extends FormSchema> {
|
||||
*/
|
||||
loadingAuto?: boolean
|
||||
class?: any
|
||||
onSubmit?: ((event: FormSubmitEvent<InferOutput<S>>) => void | Promise<void>) | (() => void | Promise<void>)
|
||||
onSubmit?: ((event: FormSubmitEvent<FormData<S, T>>) => void | Promise<void>) | (() => void | Promise<void>)
|
||||
}
|
||||
|
||||
export interface FormEmits<S extends FormSchema> {
|
||||
(e: 'submit', payload: FormSubmitEvent<InferOutput<S>>): void
|
||||
export interface FormEmits<S extends FormSchema, T extends boolean = true> {
|
||||
(e: 'submit', payload: FormSubmitEvent<FormData<S, T>>): void
|
||||
(e: 'error', payload: FormErrorEvent): void
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ export interface FormSlots {
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup generic="S extends FormSchema">
|
||||
<script lang="ts" setup generic="S extends FormSchema, T extends boolean = true">
|
||||
import { provide, inject, nextTick, ref, onUnmounted, onMounted, computed, useId, readonly } from 'vue'
|
||||
import { useEventBus } from '@vueuse/core'
|
||||
import { useAppConfig } from '#imports'
|
||||
@@ -75,17 +75,17 @@ import { FormValidationException } from '../types/form'
|
||||
type I = InferInput<S>
|
||||
type O = InferOutput<S>
|
||||
|
||||
const props = withDefaults(defineProps<FormProps<S>>(), {
|
||||
const props = withDefaults(defineProps<FormProps<S, T>>(), {
|
||||
validateOn() {
|
||||
return ['input', 'blur', 'change'] as FormInputEvents[]
|
||||
},
|
||||
validateOnInputDelay: 300,
|
||||
attach: true,
|
||||
transform: true,
|
||||
transform: () => true as T,
|
||||
loadingAuto: true
|
||||
})
|
||||
|
||||
const emits = defineEmits<FormEmits<S>>()
|
||||
const emits = defineEmits<FormEmits<S, T>>()
|
||||
defineSlots<FormSlots>()
|
||||
|
||||
const appConfig = useAppConfig() as FormConfig['AppConfig']
|
||||
@@ -183,10 +183,10 @@ async function getErrors(): Promise<FormErrorWithId[]> {
|
||||
return resolveErrorIds(errs)
|
||||
}
|
||||
|
||||
type ValidateOpts<Silent extends boolean> = { name?: keyof I | (keyof I)[], silent?: Silent, nested?: boolean, transform?: boolean }
|
||||
async function _validate(opts: ValidateOpts<false>): Promise<O>
|
||||
async function _validate(opts: ValidateOpts<true>): Promise<O | false>
|
||||
async function _validate(opts: ValidateOpts<boolean> = { silent: false, nested: true, transform: false }): Promise<O | false> {
|
||||
type ValidateOpts<Silent extends boolean, Transform extends boolean> = { name?: keyof I | (keyof I)[], silent?: Silent, nested?: boolean, transform?: Transform }
|
||||
async function _validate<T extends boolean>(opts: ValidateOpts<false, T>): Promise<FormData<S, T>>
|
||||
async function _validate<T extends boolean>(opts: ValidateOpts<true, T>): Promise<FormData<S, T> | false>
|
||||
async function _validate<T extends boolean>(opts: ValidateOpts<boolean, boolean> = { silent: false, nested: true, transform: false }): Promise<FormData<S, T> | false> {
|
||||
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof O)[]
|
||||
|
||||
const nestedValidatePromises = !names && opts.nested
|
||||
@@ -227,7 +227,7 @@ async function _validate(opts: ValidateOpts<boolean> = { silent: false, nested:
|
||||
Object.assign(props.state, transformedState.value)
|
||||
}
|
||||
|
||||
return props.state as O
|
||||
return props.state as FormData<S, T>
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -236,7 +236,7 @@ provide(formLoadingInjectionKey, readonly(loading))
|
||||
async function onSubmitWrapper(payload: Event) {
|
||||
loading.value = props.loadingAuto && true
|
||||
|
||||
const event = payload as FormSubmitEvent<O>
|
||||
const event = payload as FormSubmitEvent<FormData<S, T>>
|
||||
|
||||
try {
|
||||
event.data = await _validate({ nested: true, transform: props.transform })
|
||||
@@ -265,7 +265,7 @@ provide(formOptionsInjectionKey, computed(() => ({
|
||||
validateOnInputDelay: props.validateOnInputDelay
|
||||
})))
|
||||
|
||||
defineExpose<Form<I>>({
|
||||
defineExpose<Form<S>>({
|
||||
validate: _validate,
|
||||
errors,
|
||||
|
||||
|
||||
@@ -5,20 +5,20 @@ import type { ObjectSchema as YupObjectSchema } from 'yup'
|
||||
import type { GetObjectField } from './utils'
|
||||
import type { Struct as SuperstructSchema } from 'superstruct'
|
||||
|
||||
export interface Form<T extends object> {
|
||||
validate (opts?: { name?: keyof T | (keyof T)[], silent?: boolean, nested?: boolean, transform?: boolean }): Promise<T | false>
|
||||
export interface Form<S extends FormSchema> {
|
||||
validate<T extends boolean>(opts?: { name?: keyof FormData<S, false> | (keyof FormData<S, false>)[], silent?: boolean, nested?: boolean, transform?: T }): Promise<FormData<S, T> | false>
|
||||
clear (path?: string): void
|
||||
errors: Ref<FormError[]>
|
||||
setErrors (errs: FormError[], name?: keyof T): void
|
||||
getErrors (name?: keyof T): FormError[]
|
||||
setErrors (errs: FormError[], name?: keyof FormData<S, false>): void
|
||||
getErrors (name?: keyof FormData<S, false>): FormError[]
|
||||
submit (): Promise<void>
|
||||
disabled: ComputedRef<boolean>
|
||||
dirty: ComputedRef<boolean>
|
||||
loading: Ref<boolean>
|
||||
|
||||
dirtyFields: DeepReadonly<Set<keyof T>>
|
||||
touchedFields: DeepReadonly<Set<keyof T>>
|
||||
blurredFields: DeepReadonly<Set<keyof T>>
|
||||
dirtyFields: DeepReadonly<Set<keyof FormData<S, false>>>
|
||||
touchedFields: DeepReadonly<Set<keyof FormData<S, false>>>
|
||||
blurredFields: DeepReadonly<Set<keyof FormData<S, false>>>
|
||||
}
|
||||
|
||||
export type FormSchema<I extends object = object, O extends object = I> =
|
||||
@@ -42,6 +42,8 @@ export type InferOutput<Schema> = Schema extends StandardSchemaV1 ? StandardSche
|
||||
: Schema extends SuperstructSchema<infer O, any> ? O
|
||||
: never
|
||||
|
||||
export type FormData<S extends FormSchema, T extends boolean = true> = T extends true ? InferOutput<S> : InferInput<S>
|
||||
|
||||
export type FormInputEvents = 'input' | 'blur' | 'change' | 'focus'
|
||||
|
||||
export interface FormError<P extends string = string> {
|
||||
|
||||
Reference in New Issue
Block a user