mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 12:14:41 +01:00
refactor(Form): remove state assignment and opt-in to nested forms
This commit is contained in:
@@ -39,7 +39,7 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
|
|||||||
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
|
<UCheckbox v-model="state.news" name="news" label="Register to our newsletter" @update:model-value="state.email = undefined" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<UForm v-if="state.news" :state="state" :schema="nestedSchema">
|
<UForm v-if="state.news" :state="state" :schema="nestedSchema" nested>
|
||||||
<UFormField label="Email" name="email">
|
<UFormField label="Email" name="email">
|
||||||
<UInput v-model="state.email" placeholder="john@lennon.com" />
|
<UInput v-model="state.email" placeholder="john@lennon.com" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|||||||
@@ -51,7 +51,14 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
|
|||||||
<UInput v-model="state.customer" placeholder="Wonka Industries" />
|
<UInput v-model="state.customer" placeholder="Wonka Industries" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|
||||||
<UForm v-for="item, count in state.items" :key="count" :state="item" :schema="itemSchema" class="flex gap-2">
|
<UForm
|
||||||
|
v-for="item, count in state.items"
|
||||||
|
:key="count"
|
||||||
|
:state="item"
|
||||||
|
:schema="itemSchema"
|
||||||
|
nested
|
||||||
|
class="flex gap-2"
|
||||||
|
>
|
||||||
<UFormField :label="!count ? 'Description' : undefined" name="description">
|
<UFormField :label="!count ? 'Description' : undefined" name="description">
|
||||||
<UInput v-model="item.description" />
|
<UInput v-model="item.description" />
|
||||||
</UFormField>
|
</UFormField>
|
||||||
|
|||||||
71
playground/app/pages/components/form-sandro.vue
Normal file
71
playground/app/pages/components/form-sandro.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<UContainer>
|
||||||
|
<UForm :schema :state @submit="onSubmit">
|
||||||
|
<UFormField label="A" name="a">
|
||||||
|
<UInput v-model="state.a" />
|
||||||
|
</UFormField>
|
||||||
|
<UFormField label="B" name="b">
|
||||||
|
<UInput v-model="state.b" />
|
||||||
|
</UFormField>
|
||||||
|
|
||||||
|
<UButton type="submit">
|
||||||
|
Submit
|
||||||
|
</UButton>
|
||||||
|
</UForm>
|
||||||
|
|
||||||
|
{{ output }}
|
||||||
|
</UContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { FormSubmitEvent } from '@nuxt/ui'
|
||||||
|
import * as v from 'valibot'
|
||||||
|
|
||||||
|
const _schemaStringFiltered = v.pipe(v.string(), v.trim())
|
||||||
|
const schema = v.object({
|
||||||
|
a: v.string(),
|
||||||
|
b: v.union([
|
||||||
|
v.pipe(
|
||||||
|
v.array(_schemaStringFiltered),
|
||||||
|
v.filterItems((item, index, array) => (array.indexOf(item) === index || item !== ''))
|
||||||
|
),
|
||||||
|
v.pipe(
|
||||||
|
v.string(),
|
||||||
|
v.trim(),
|
||||||
|
v.transform(
|
||||||
|
(item) => {
|
||||||
|
if (item === '') return undefined
|
||||||
|
|
||||||
|
return item
|
||||||
|
.split(',')
|
||||||
|
.map(val => val.trim())
|
||||||
|
.filter(val => val !== '')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = reactive<{
|
||||||
|
a: string
|
||||||
|
b: string
|
||||||
|
}>({
|
||||||
|
a: 'hello, world',
|
||||||
|
b: 'hello, world'
|
||||||
|
})
|
||||||
|
const output = reactive<{
|
||||||
|
a: string
|
||||||
|
b?: string[]
|
||||||
|
}>({
|
||||||
|
a: '',
|
||||||
|
b: []
|
||||||
|
})
|
||||||
|
|
||||||
|
function onSubmit(event: FormSubmitEvent<v.InferOutput<typeof schema>>) {
|
||||||
|
console.log('typeof `a`:', typeof event.data.a) // should be string
|
||||||
|
console.log('typeof `b`:', typeof event.data.b) // should be object (array of strings)
|
||||||
|
|
||||||
|
output.a = event.data.a
|
||||||
|
output.b = event.data.b
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -19,7 +19,7 @@ export interface FormProps<I extends object, O extends object = I> {
|
|||||||
* @param state - The current state of the form.
|
* @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.
|
* @returns A promise that resolves to an array of FormError objects, or an array of FormError objects directly.
|
||||||
*/
|
*/
|
||||||
validate?: (state: Partial<I> | O) => Promise<FormError[]> | FormError[]
|
validate?: (state: Partial<I>) => Promise<FormError[]> | FormError[]
|
||||||
/**
|
/**
|
||||||
* The list of input events that trigger the form validation.
|
* The list of input events that trigger the form validation.
|
||||||
* @defaultValue `['blur', 'change', 'input']`
|
* @defaultValue `['blur', 'change', 'input']`
|
||||||
@@ -32,11 +32,11 @@ export interface FormProps<I extends object, O extends object = I> {
|
|||||||
* @defaultValue `300`
|
* @defaultValue `300`
|
||||||
*/
|
*/
|
||||||
validateOnInputDelay?: number
|
validateOnInputDelay?: number
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If true, schema transformations will be applied to the state on submit.
|
* If true and nested in another form, this form will attach to its parent and validate at the same time.
|
||||||
* @defaultValue `true`
|
|
||||||
*/
|
*/
|
||||||
transform?: boolean
|
nested?: boolean
|
||||||
/**
|
/**
|
||||||
* When `true`, all form elements will be disabled on `@submit` event.
|
* When `true`, all form elements will be disabled on `@submit` event.
|
||||||
* This will cause any focused input elements to lose their focus state.
|
* This will cause any focused input elements to lose their focus state.
|
||||||
@@ -71,7 +71,6 @@ const props = withDefaults(defineProps<FormProps<I, O>>(), {
|
|||||||
return ['input', 'blur', 'change'] as FormInputEvents[]
|
return ['input', 'blur', 'change'] as FormInputEvents[]
|
||||||
},
|
},
|
||||||
validateOnInputDelay: 300,
|
validateOnInputDelay: 300,
|
||||||
transform: true,
|
|
||||||
loadingAuto: true
|
loadingAuto: true
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -127,14 +126,14 @@ onUnmounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (parentBus) {
|
if (props.nested && parentBus) {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
parentBus.emit({ type: 'attach', validate: _validate, formId })
|
parentBus.emit({ type: 'attach', validate: _validate, formId })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (parentBus) {
|
if (props.nested && parentBus) {
|
||||||
parentBus.emit({ type: 'detach', formId })
|
parentBus.emit({ type: 'detach', formId })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -173,7 +172,7 @@ async function getErrors(): Promise<FormErrorWithId[]> {
|
|||||||
return resolveErrorIds(errs)
|
return resolveErrorIds(errs)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _validate(opts: { name?: keyof I | (keyof I)[], silent?: boolean, nested?: boolean, transform?: boolean } = { silent: false, nested: true, transform: false }): Promise<O | false> {
|
async function _validate(opts: { name?: keyof I | (keyof I)[], silent?: boolean, nested?: boolean } = { silent: false, nested: true }): Promise<O | false> {
|
||||||
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof I)[]
|
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as (keyof I)[]
|
||||||
|
|
||||||
const nestedValidatePromises = !names && opts.nested
|
const nestedValidatePromises = !names && opts.nested
|
||||||
@@ -210,11 +209,7 @@ async function _validate(opts: { name?: keyof I | (keyof I)[], silent?: boolean,
|
|||||||
throw new FormValidationException(formId, errors.value, childErrors)
|
throw new FormValidationException(formId, errors.value, childErrors)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.transform) {
|
return transformedState.value
|
||||||
Object.assign(props.state, transformedState.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.state as O
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -226,7 +221,7 @@ async function onSubmitWrapper(payload: Event) {
|
|||||||
const event = payload as FormSubmitEvent<any>
|
const event = payload as FormSubmitEvent<any>
|
||||||
|
|
||||||
try {
|
try {
|
||||||
event.data = await _validate({ nested: true, transform: props.transform })
|
event.data = await _validate({ nested: true })
|
||||||
await props.onSubmit?.(event)
|
await props.onSubmit?.(event)
|
||||||
dirtyFields.clear()
|
dirtyFields.clear()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user