chore: add examples

This commit is contained in:
zernonia 2024-04-21 20:27:28 +08:00
parent bdc4ccc5bf
commit 07a0b9a910
22 changed files with 714 additions and 26 deletions

View File

@ -38,12 +38,54 @@ export const Index = {
component: () => import("../src/lib/registry/default/example/AspectRatioDemo.vue").then((m) => m.default), component: () => import("../src/lib/registry/default/example/AspectRatioDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AspectRatioDemo.vue"], files: ["../src/lib/registry/default/example/AspectRatioDemo.vue"],
}, },
"AutoForm": { "AutoFormArray": {
name: "AutoForm", name: "AutoFormArray",
type: "components:example", type: "components:example",
registryDependencies: ["button","toast","auto-form"], registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoForm.vue").then((m) => m.default), component: () => import("../src/lib/registry/default/example/AutoFormArray.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoForm.vue"], files: ["../src/lib/registry/default/example/AutoFormArray.vue"],
},
"AutoFormBasic": {
name: "AutoFormBasic",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormBasic.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormBasic.vue"],
},
"AutoFormConfirmPassword": {
name: "AutoFormConfirmPassword",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormConfirmPassword.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormConfirmPassword.vue"],
},
"AutoFormControlled": {
name: "AutoFormControlled",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormControlled.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormControlled.vue"],
},
"AutoFormDependencies": {
name: "AutoFormDependencies",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormDependencies.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormDependencies.vue"],
},
"AutoFormInputWithoutLabel": {
name: "AutoFormInputWithoutLabel",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormInputWithoutLabel.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormInputWithoutLabel.vue"],
},
"AutoFormSubObject": {
name: "AutoFormSubObject",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/default/example/AutoFormSubObject.vue").then((m) => m.default),
files: ["../src/lib/registry/default/example/AutoFormSubObject.vue"],
}, },
"AvatarDemo": { "AvatarDemo": {
name: "AvatarDemo", name: "AvatarDemo",
@ -1285,12 +1327,54 @@ export const Index = {
component: () => import("../src/lib/registry/new-york/example/AspectRatioDemo.vue").then((m) => m.default), component: () => import("../src/lib/registry/new-york/example/AspectRatioDemo.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AspectRatioDemo.vue"], files: ["../src/lib/registry/new-york/example/AspectRatioDemo.vue"],
}, },
"AutoForm": { "AutoFormArray": {
name: "AutoForm", name: "AutoFormArray",
type: "components:example", type: "components:example",
registryDependencies: ["button","toast","auto-form"], registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoForm.vue").then((m) => m.default), component: () => import("../src/lib/registry/new-york/example/AutoFormArray.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoForm.vue"], files: ["../src/lib/registry/new-york/example/AutoFormArray.vue"],
},
"AutoFormBasic": {
name: "AutoFormBasic",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormBasic.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormBasic.vue"],
},
"AutoFormConfirmPassword": {
name: "AutoFormConfirmPassword",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormConfirmPassword.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormConfirmPassword.vue"],
},
"AutoFormControlled": {
name: "AutoFormControlled",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormControlled.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormControlled.vue"],
},
"AutoFormDependencies": {
name: "AutoFormDependencies",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormDependencies.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormDependencies.vue"],
},
"AutoFormInputWithoutLabel": {
name: "AutoFormInputWithoutLabel",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormInputWithoutLabel.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormInputWithoutLabel.vue"],
},
"AutoFormSubObject": {
name: "AutoFormSubObject",
type: "components:example",
registryDependencies: ["button","toast","auto-form"],
component: () => import("../src/lib/registry/new-york/example/AutoFormSubObject.vue").then((m) => m.default),
files: ["../src/lib/registry/new-york/example/AutoFormSubObject.vue"],
}, },
"AvatarDemo": { "AvatarDemo": {
name: "AvatarDemo", name: "AvatarDemo",

View File

@ -1,7 +1,39 @@
--- ---
title: Auto Form title: Auto Form
description: Building forms with VeeValidate and Zod. description: Automatically generate a form from Zod schema.
primitive: https://vee-validate.logaretm.com/v4/guide/overview/ primitive: https://vee-validate.logaretm.com/v4/guide/overview/
--- ---
<ComponentPreview name="AutoForm" /> ### Basic
<ComponentPreview name="AutoFormBasic" />
### Input Without Label
This example shows how to use AutoForm input without label.
<ComponentPreview name="AutoFormInputWithoutLabel" />
### Sub Object
Automatically generate a form from a Zod schema.
<ComponentPreview name="AutoFormSubObject" />
### Controlled
This example shows how to use AutoForm in a controlled way.
<ComponentPreview name="AutoFormControlled" />
### Confirm Password
Refined schema to validate that two fields match.
<ComponentPreview name="AutoFormConfirmPassword" />
### Array support
You can use arrays in your schemas to create dynamic forms.
<ComponentPreview name="AutoFormArray" />
### Dependencies
Create dependencies between fields.
<ComponentPreview name="AutoFormDependencies" />

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm } from '@/lib/registry/default/ui/auto-form'
const schema = z.object({
guestListName: z.string(),
invitedGuests: z
.array(
z.object({
name: z.string(),
age: z.coerce.number(),
}),
)
.describe('Guests invited to the party'),
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,36 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm } from '@/lib/registry/default/ui/auto-form'
const schema = z
.object({
password: z.string(),
confirm: z.string(),
})
.refine(data => data.password === data.confirm, {
message: 'Passwords must match.',
path: ['confirm'],
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,37 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm } from '@/lib/registry/default/ui/auto-form'
const schema = z.object({
username: z.string(),
})
const form = useForm({
validationSchema: toTypedSchema(schema),
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:form="form"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,72 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { DependencyType } from '../ui/auto-form/interface'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm } from '@/lib/registry/default/ui/auto-form'
const schema = z.object({
age: z.number(),
parentsAllowed: z.boolean().optional(),
vegetarian: z.boolean().optional(),
mealOptions: z.enum(['Pasta', 'Salad', 'Beef Wellington']).optional(),
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:field-config="{
age: {
description:
'Setting this below 18 will require parents consent.',
},
parentsAllowed: {
label: 'Did your parents allow you to register?',
},
vegetarian: {
label: 'Are you a vegetarian?',
description:
'Setting this to true will remove non-vegetarian food options.',
},
mealOptions: {
component: 'radio',
},
}"
:dependencies="[
{
sourceField: 'age',
type: DependencyType.HIDES,
targetField: 'parentsAllowed',
when: (age) => age >= 18,
},
{
sourceField: 'age',
type: DependencyType.REQUIRES,
targetField: 'parentsAllowed',
when: (age) => age < 18,
},
{
sourceField: 'vegetarian',
type: DependencyType.SETS_OPTIONS,
targetField: 'mealOptions',
when: (vegetarian) => vegetarian,
options: ['Pasta', 'Salad'],
},
]"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,48 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm, AutoFormField } from '@/lib/registry/default/ui/auto-form'
const schema = z.object({
username: z.string(),
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:field-config="{
username: {
hideLabel: true,
},
}"
@submit="onSubmit"
>
<template #username="slotProps">
<div class="flex items-start gap-3">
<div class="flex-1">
<AutoFormField v-bind="slotProps" />
</div>
<div>
<Button type="button">
Update
</Button>
</div>
</div>
</template>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,54 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/default/ui/button'
import { toast } from '@/lib/registry/default/ui/toast'
import { AutoForm, AutoFormField } from '@/lib/registry/default/ui/auto-form'
const schema = z.object({
subObject: z.object({
subField: z.string().optional().default('Sub Field'),
numberField: z.number().optional().default(1),
subSubObject: z
.object({
subSubField: z.string().default('Sub Sub Field'),
})
.describe('Sub Sub Object Description'),
}),
optionalSubObject: z
.object({
optionalSubField: z.string(),
otherOptionalSubField: z.string(),
})
.optional(),
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:field-config="{
subObject: {
numberField: {
inputProps: {
type: 'number',
},
},
},
}"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -1,9 +1,9 @@
<script setup lang="ts" generic="U extends ZodRawShape, T extends ZodObject<U>"> <script setup lang="ts" generic="T extends ZodObjectOrWrapped">
import { computed, ref, toRef, toRefs } from 'vue' import { computed, toRefs } from 'vue'
import type { ZodAny, ZodObject, ZodRawShape, z } from 'zod' import type { ZodAny, z } from 'zod'
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
import type { FormContext, GenericObject } from 'vee-validate' import type { FormContext, GenericObject } from 'vee-validate'
import { getBaseSchema, getBaseType, getDefaultValueInZodStack } from './utils' import { type ZodObjectOrWrapped, getBaseSchema, getBaseType, getDefaultValueInZodStack, getObjectFormSchema } from './utils'
import type { Config, ConfigItem, Dependency, Shape } from './interface' import type { Config, ConfigItem, Dependency, Shape } from './interface'
import AutoFormField from './AutoFormField.vue' import AutoFormField from './AutoFormField.vue'
import { provideDependencies } from './dependencies' import { provideDependencies } from './dependencies'
@ -26,7 +26,8 @@ provideDependencies(dependencies)
const shapes = computed(() => { const shapes = computed(() => {
// @ts-expect-error ignore {} not assignable to object // @ts-expect-error ignore {} not assignable to object
const val: { [key in keyof T]: Shape } = {} const val: { [key in keyof T]: Shape } = {}
const shape = props.schema.shape const baseSchema = getObjectFormSchema(props.schema)
const shape = baseSchema.shape
Object.keys(shape).forEach((name) => { Object.keys(shape).forEach((name) => {
const item = shape[name] as ZodAny const item = shape[name] as ZodAny
const baseItem = getBaseSchema(item) as ZodAny const baseItem = getBaseSchema(item) as ZodAny

View File

@ -68,7 +68,7 @@ provide(FieldContextKey, fieldContext)
<AccordionContent> <AccordionContent>
<template v-for="(field, index) of fields" :key="field.key"> <template v-for="(field, index) of fields" :key="field.key">
<div class="mb-4 p-[1px]"> <div class="mb-4 p-1">
<AutoFormField <AutoFormField
:field-name="`${fieldName}[${index}]`" :field-name="`${fieldName}[${index}]`"
:label="fieldName" :label="fieldName"

View File

@ -49,7 +49,7 @@ const shapes = computed(() => {
<AccordionTrigger class="text-base"> <AccordionTrigger class="text-base">
{{ schema?.description || beautifyObjectName(fieldName) }} {{ schema?.description || beautifyObjectName(fieldName) }}
</AccordionTrigger> </AccordionTrigger>
<AccordionContent class="p-[1px] space-y-5"> <AccordionContent class="p-1 space-y-5">
<template v-for="(shape, key) in shapes" :key="key"> <template v-for="(shape, key) in shapes" :key="key">
<AutoFormField <AutoFormField
:config="config?.[key as keyof typeof config] as ConfigItem" :config="config?.[key as keyof typeof config] as ConfigItem"

View File

@ -0,0 +1,38 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/new-york/ui/button'
import { toast } from '@/lib/registry/new-york/ui/toast'
import { AutoForm } from '@/lib/registry/new-york/ui/auto-form'
const schema = z.object({
guestListName: z.string(),
invitedGuests: z
.array(
z.object({
name: z.string(),
age: z.coerce.number(),
}),
)
.describe('Guests invited to the party'),
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,36 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/new-york/ui/button'
import { toast } from '@/lib/registry/new-york/ui/toast'
import { AutoForm } from '@/lib/registry/new-york/ui/auto-form'
const schema = z
.object({
password: z.string(),
confirm: z.string(),
})
.refine(data => data.password === data.confirm, {
message: 'Passwords must match.',
path: ['confirm'],
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,37 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { useForm } from 'vee-validate'
import { toTypedSchema } from '@vee-validate/zod'
import { Button } from '@/lib/registry/new-york/ui/button'
import { toast } from '@/lib/registry/new-york/ui/toast'
import { AutoForm } from '@/lib/registry/new-york/ui/auto-form'
const schema = z.object({
username: z.string(),
})
const form = useForm({
validationSchema: toTypedSchema(schema),
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:form="form"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,72 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { DependencyType } from '../ui/auto-form/interface'
import { Button } from '@/lib/registry/new-york/ui/button'
import { toast } from '@/lib/registry/new-york/ui/toast'
import { AutoForm } from '@/lib/registry/new-york/ui/auto-form'
const schema = z.object({
age: z.number(),
parentsAllowed: z.boolean().optional(),
vegetarian: z.boolean().optional(),
mealOptions: z.enum(['Pasta', 'Salad', 'Beef Wellington']).optional(),
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:field-config="{
age: {
description:
'Setting this below 18 will require parents consent.',
},
parentsAllowed: {
label: 'Did your parents allow you to register?',
},
vegetarian: {
label: 'Are you a vegetarian?',
description:
'Setting this to true will remove non-vegetarian food options.',
},
mealOptions: {
component: 'radio',
},
}"
:dependencies="[
{
sourceField: 'age',
type: DependencyType.HIDES,
targetField: 'parentsAllowed',
when: (age) => age >= 18,
},
{
sourceField: 'age',
type: DependencyType.REQUIRES,
targetField: 'parentsAllowed',
when: (age) => age < 18,
},
{
sourceField: 'vegetarian',
type: DependencyType.SETS_OPTIONS,
targetField: 'mealOptions',
when: (vegetarian) => vegetarian,
options: ['Pasta', 'Salad'],
},
]"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,48 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/new-york/ui/button'
import { toast } from '@/lib/registry/new-york/ui/toast'
import { AutoForm, AutoFormField } from '@/lib/registry/new-york/ui/auto-form'
const schema = z.object({
username: z.string(),
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:field-config="{
username: {
hideLabel: true,
},
}"
@submit="onSubmit"
>
<template #username="slotProps">
<div class="flex items-center gap-3">
<div class="flex-1">
<AutoFormField v-bind="slotProps" />
</div>
<div>
<Button type="button">
Update
</Button>
</div>
</div>
</template>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -0,0 +1,54 @@
<script setup lang="ts">
import * as z from 'zod'
import { h } from 'vue'
import { Button } from '@/lib/registry/new-york/ui/button'
import { toast } from '@/lib/registry/new-york/ui/toast'
import { AutoForm, AutoFormField } from '@/lib/registry/new-york/ui/auto-form'
const schema = z.object({
subObject: z.object({
subField: z.string().optional().default('Sub Field'),
numberField: z.number().optional().default(1),
subSubObject: z
.object({
subSubField: z.string().default('Sub Sub Field'),
})
.describe('Sub Sub Object Description'),
}),
optionalSubObject: z
.object({
optionalSubField: z.string(),
otherOptionalSubField: z.string(),
})
.optional(),
})
function onSubmit(values: Record<string, any>) {
toast({
title: 'You submitted the following values:',
description: h('pre', { class: 'mt-2 w-[340px] rounded-md bg-slate-950 p-4' }, h('code', { class: 'text-white' }, JSON.stringify(values, null, 2))),
})
}
</script>
<template>
<AutoForm
class="w-2/3 space-y-6"
:schema="schema"
:field-config="{
subObject: {
numberField: {
inputProps: {
type: 'number',
},
},
},
}"
@submit="onSubmit"
>
<Button type="submit">
Submit
</Button>
</AutoForm>
</template>

View File

@ -1,9 +1,9 @@
<script setup lang="ts" generic="U extends ZodRawShape, T extends ZodObject<U>"> <script setup lang="ts" generic="T extends ZodObjectOrWrapped">
import { computed, ref, toRef, toRefs } from 'vue' import { computed, toRefs } from 'vue'
import type { ZodAny, ZodObject, ZodRawShape, z } from 'zod' import type { ZodAny, z } from 'zod'
import { toTypedSchema } from '@vee-validate/zod' import { toTypedSchema } from '@vee-validate/zod'
import type { FormContext, GenericObject } from 'vee-validate' import type { FormContext, GenericObject } from 'vee-validate'
import { getBaseSchema, getBaseType, getDefaultValueInZodStack } from './utils' import { type ZodObjectOrWrapped, getBaseSchema, getBaseType, getDefaultValueInZodStack, getObjectFormSchema } from './utils'
import type { Config, ConfigItem, Dependency, Shape } from './interface' import type { Config, ConfigItem, Dependency, Shape } from './interface'
import AutoFormField from './AutoFormField.vue' import AutoFormField from './AutoFormField.vue'
import { provideDependencies } from './dependencies' import { provideDependencies } from './dependencies'
@ -26,7 +26,8 @@ provideDependencies(dependencies)
const shapes = computed(() => { const shapes = computed(() => {
// @ts-expect-error ignore {} not assignable to object // @ts-expect-error ignore {} not assignable to object
const val: { [key in keyof T]: Shape } = {} const val: { [key in keyof T]: Shape } = {}
const shape = props.schema.shape const baseSchema = getObjectFormSchema(props.schema)
const shape = baseSchema.shape
Object.keys(shape).forEach((name) => { Object.keys(shape).forEach((name) => {
const item = shape[name] as ZodAny const item = shape[name] as ZodAny
const baseItem = getBaseSchema(item) as ZodAny const baseItem = getBaseSchema(item) as ZodAny

View File

@ -24,7 +24,7 @@
"files": [ "files": [
{ {
"name": "AutoForm.vue", "name": "AutoForm.vue",
"content": "<script setup lang=\"ts\" generic=\"U extends ZodRawShape, T extends ZodObject<U>\">\nimport { computed, ref, toRef, toRefs } from 'vue'\nimport type { ZodAny, ZodObject, ZodRawShape, z } from 'zod'\nimport { toTypedSchema } from '@vee-validate/zod'\nimport type { FormContext, GenericObject } from 'vee-validate'\nimport { getBaseSchema, getBaseType, getDefaultValueInZodStack } from './utils'\nimport type { Config, ConfigItem, Dependency, Shape } from './interface'\nimport AutoFormField from './AutoFormField.vue'\nimport { provideDependencies } from './dependencies'\nimport { Form } from '@/lib/registry/default/ui/form'\n\nconst props = defineProps<{\n schema: T\n form?: FormContext<GenericObject>\n fieldConfig?: Config<z.infer<T>>\n dependencies?: Dependency<z.infer<T>>[]\n}>()\n\nconst emits = defineEmits<{\n submit: [event: GenericObject]\n}>()\n\nconst { dependencies } = toRefs(props)\nprovideDependencies(dependencies)\n\nconst shapes = computed(() => {\n // @ts-expect-error ignore {} not assignable to object\n const val: { [key in keyof T]: Shape } = {}\n const shape = props.schema.shape\n Object.keys(shape).forEach((name) => {\n const item = shape[name] as ZodAny\n const baseItem = getBaseSchema(item) as ZodAny\n let options = (baseItem && 'values' in baseItem._def) ? baseItem._def.values as string[] : undefined\n if (!Array.isArray(options) && typeof options === 'object')\n options = Object.values(options)\n\n val[name as keyof T] = {\n type: getBaseType(item),\n default: getDefaultValueInZodStack(item),\n options,\n required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName),\n schema: baseItem,\n }\n })\n return val\n})\n\nconst fields = computed(() => {\n // @ts-expect-error ignore {} not assignable to object\n const val: { [key in keyof z.infer<T>]: { shape: Shape, fieldName: string, config: ConfigItem } } = {}\n for (const key in shapes.value) {\n const shape = shapes.value[key]\n val[key as keyof z.infer<T>] = {\n shape,\n config: props.fieldConfig?.[key] as ConfigItem,\n fieldName: key,\n }\n }\n return val\n})\n\nconst formComponent = computed(() => props.form ? 'form' : Form)\nconst formComponentProps = computed(() => {\n if (props.form) {\n return {\n onSubmit: props.form.handleSubmit(val => emits('submit', val)),\n }\n }\n else {\n const formSchema = toTypedSchema(props.schema)\n return {\n keepValues: true,\n validationSchema: formSchema,\n onSubmit: (val: GenericObject) => emits('submit', val),\n }\n }\n})\n</script>\n\n<template>\n <component\n :is=\"formComponent\"\n v-bind=\"formComponentProps\"\n >\n <slot name=\"customAutoForm\" :fields=\"fields\">\n <template v-for=\"(shape, key) of shapes\" :key=\"key\">\n <slot\n :shape=\"shape\"\n :name=\"key.toString() as keyof z.infer<T>\"\n :field-name=\"key.toString()\"\n :config=\"fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem\"\n >\n <AutoFormField\n :config=\"fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem\"\n :field-name=\"key.toString()\"\n :shape=\"shape\"\n />\n </slot>\n </template>\n </slot>\n\n <slot :shapes=\"shapes\" />\n </component>\n</template>\n" "content": "<script setup lang=\"ts\" generic=\"T extends ZodObjectOrWrapped\">\nimport { computed, toRefs } from 'vue'\nimport type { ZodAny, z } from 'zod'\nimport { toTypedSchema } from '@vee-validate/zod'\nimport type { FormContext, GenericObject } from 'vee-validate'\nimport { type ZodObjectOrWrapped, getBaseSchema, getBaseType, getDefaultValueInZodStack, getObjectFormSchema } from './utils'\nimport type { Config, ConfigItem, Dependency, Shape } from './interface'\nimport AutoFormField from './AutoFormField.vue'\nimport { provideDependencies } from './dependencies'\nimport { Form } from '@/lib/registry/default/ui/form'\n\nconst props = defineProps<{\n schema: T\n form?: FormContext<GenericObject>\n fieldConfig?: Config<z.infer<T>>\n dependencies?: Dependency<z.infer<T>>[]\n}>()\n\nconst emits = defineEmits<{\n submit: [event: GenericObject]\n}>()\n\nconst { dependencies } = toRefs(props)\nprovideDependencies(dependencies)\n\nconst shapes = computed(() => {\n // @ts-expect-error ignore {} not assignable to object\n const val: { [key in keyof T]: Shape } = {}\n const baseSchema = getObjectFormSchema(props.schema)\n const shape = baseSchema.shape\n Object.keys(shape).forEach((name) => {\n const item = shape[name] as ZodAny\n const baseItem = getBaseSchema(item) as ZodAny\n let options = (baseItem && 'values' in baseItem._def) ? baseItem._def.values as string[] : undefined\n if (!Array.isArray(options) && typeof options === 'object')\n options = Object.values(options)\n\n val[name as keyof T] = {\n type: getBaseType(item),\n default: getDefaultValueInZodStack(item),\n options,\n required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName),\n schema: baseItem,\n }\n })\n return val\n})\n\nconst fields = computed(() => {\n // @ts-expect-error ignore {} not assignable to object\n const val: { [key in keyof z.infer<T>]: { shape: Shape, fieldName: string, config: ConfigItem } } = {}\n for (const key in shapes.value) {\n const shape = shapes.value[key]\n val[key as keyof z.infer<T>] = {\n shape,\n config: props.fieldConfig?.[key] as ConfigItem,\n fieldName: key,\n }\n }\n return val\n})\n\nconst formComponent = computed(() => props.form ? 'form' : Form)\nconst formComponentProps = computed(() => {\n if (props.form) {\n return {\n onSubmit: props.form.handleSubmit(val => emits('submit', val)),\n }\n }\n else {\n const formSchema = toTypedSchema(props.schema)\n return {\n keepValues: true,\n validationSchema: formSchema,\n onSubmit: (val: GenericObject) => emits('submit', val),\n }\n }\n})\n</script>\n\n<template>\n <component\n :is=\"formComponent\"\n v-bind=\"formComponentProps\"\n >\n <slot name=\"customAutoForm\" :fields=\"fields\">\n <template v-for=\"(shape, key) of shapes\" :key=\"key\">\n <slot\n :shape=\"shape\"\n :name=\"key.toString() as keyof z.infer<T>\"\n :field-name=\"key.toString()\"\n :config=\"fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem\"\n >\n <AutoFormField\n :config=\"fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem\"\n :field-name=\"key.toString()\"\n :shape=\"shape\"\n />\n </slot>\n </template>\n </slot>\n\n <slot :shapes=\"shapes\" />\n </component>\n</template>\n"
}, },
{ {
"name": "AutoFormField.vue", "name": "AutoFormField.vue",
@ -32,7 +32,7 @@
}, },
{ {
"name": "AutoFormFieldArray.vue", "name": "AutoFormFieldArray.vue",
"content": "<script setup lang=\"ts\" generic=\"T extends z.ZodAny\">\nimport * as z from 'zod'\nimport { computed, provide } from 'vue'\nimport { PlusIcon, TrashIcon } from 'lucide-vue-next'\nimport { FieldArray, FieldContextKey, useField, useFieldArray } from 'vee-validate'\nimport type { Config, ConfigItem } from './interface'\nimport { beautifyObjectName, getBaseType } from './utils'\nimport AutoFormField from './AutoFormField.vue'\nimport AutoFormLabel from './AutoFormLabel.vue'\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/default/ui/accordion'\nimport { Button } from '@/lib/registry/default/ui/button'\nimport { Separator } from '@/lib/registry/default/ui/separator'\nimport { FormMessage } from '@/lib/registry/default/ui/form'\n\nconst props = defineProps<{\n fieldName: string\n required?: boolean\n config?: Config<T>\n schema?: z.ZodArray<T>\n disabled?: boolean\n}>()\n\nconst fieldContext = useField(props.fieldName)\n\nfunction isZodArray(\n item: z.ZodArray<any> | z.ZodDefault<any>,\n): item is z.ZodArray<any> {\n return item instanceof z.ZodArray\n}\n\nfunction isZodDefault(\n item: z.ZodArray<any> | z.ZodDefault<any>,\n): item is z.ZodDefault<any> {\n return item instanceof z.ZodDefault\n}\n\nconst itemShape = computed(() => {\n if (!props.schema)\n return\n\n const schema: z.ZodAny = isZodArray(props.schema)\n ? props.schema._def.type\n : isZodDefault(props.schema)\n // @ts-expect-error missing schema\n ? props.schema._def.innerType._def.type\n : null\n\n return {\n type: getBaseType(schema),\n schema,\n }\n})\n\n// @ts-expect-error ignore missing `id`\nprovide(FieldContextKey, fieldContext)\n</script>\n\n<template>\n <FieldArray v-slot=\"{ fields, remove, push }\" as=\"section\" :name=\"fieldName\">\n <slot v-bind=\"props\">\n <Accordion type=\"multiple\" class=\"w-full\" collapsible :disabled=\"disabled\">\n <AccordionItem :value=\"fieldName\" class=\"border-none\">\n <AccordionTrigger>\n <AutoFormLabel class=\"text-base\" :required=\"required\">\n {{ schema?.description || beautifyObjectName(fieldName) }}\n </AutoFormLabel>\n </AccordionTrigger>\n\n <AccordionContent>\n <template v-for=\"(field, index) of fields\" :key=\"field.key\">\n <div class=\"mb-4 p-[1px]\">\n <AutoFormField\n :field-name=\"`${fieldName}[${index}]`\"\n :label=\"fieldName\"\n :shape=\"itemShape!\"\n :config=\"config as ConfigItem\"\n />\n\n <div class=\"!my-4 flex justify-end\">\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"secondary\"\n @click=\"remove(index)\"\n >\n <TrashIcon :size=\"16\" />\n </Button>\n </div>\n <Separator v-if=\"!field.isLast\" />\n </div>\n </template>\n\n <Button\n type=\"button\"\n variant=\"secondary\"\n class=\"mt-4 flex items-center\"\n @click=\"push(null)\"\n >\n <PlusIcon class=\"mr-2\" :size=\"16\" />\n Add\n </Button>\n </AccordionContent>\n\n <FormMessage />\n </AccordionItem>\n </Accordion>\n </slot>\n </FieldArray>\n</template>\n" "content": "<script setup lang=\"ts\" generic=\"T extends z.ZodAny\">\nimport * as z from 'zod'\nimport { computed, provide } from 'vue'\nimport { PlusIcon, TrashIcon } from 'lucide-vue-next'\nimport { FieldArray, FieldContextKey, useField, useFieldArray } from 'vee-validate'\nimport type { Config, ConfigItem } from './interface'\nimport { beautifyObjectName, getBaseType } from './utils'\nimport AutoFormField from './AutoFormField.vue'\nimport AutoFormLabel from './AutoFormLabel.vue'\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/default/ui/accordion'\nimport { Button } from '@/lib/registry/default/ui/button'\nimport { Separator } from '@/lib/registry/default/ui/separator'\nimport { FormMessage } from '@/lib/registry/default/ui/form'\n\nconst props = defineProps<{\n fieldName: string\n required?: boolean\n config?: Config<T>\n schema?: z.ZodArray<T>\n disabled?: boolean\n}>()\n\nconst fieldContext = useField(props.fieldName)\n\nfunction isZodArray(\n item: z.ZodArray<any> | z.ZodDefault<any>,\n): item is z.ZodArray<any> {\n return item instanceof z.ZodArray\n}\n\nfunction isZodDefault(\n item: z.ZodArray<any> | z.ZodDefault<any>,\n): item is z.ZodDefault<any> {\n return item instanceof z.ZodDefault\n}\n\nconst itemShape = computed(() => {\n if (!props.schema)\n return\n\n const schema: z.ZodAny = isZodArray(props.schema)\n ? props.schema._def.type\n : isZodDefault(props.schema)\n // @ts-expect-error missing schema\n ? props.schema._def.innerType._def.type\n : null\n\n return {\n type: getBaseType(schema),\n schema,\n }\n})\n\n// @ts-expect-error ignore missing `id`\nprovide(FieldContextKey, fieldContext)\n</script>\n\n<template>\n <FieldArray v-slot=\"{ fields, remove, push }\" as=\"section\" :name=\"fieldName\">\n <slot v-bind=\"props\">\n <Accordion type=\"multiple\" class=\"w-full\" collapsible :disabled=\"disabled\">\n <AccordionItem :value=\"fieldName\" class=\"border-none\">\n <AccordionTrigger>\n <AutoFormLabel class=\"text-base\" :required=\"required\">\n {{ schema?.description || beautifyObjectName(fieldName) }}\n </AutoFormLabel>\n </AccordionTrigger>\n\n <AccordionContent>\n <template v-for=\"(field, index) of fields\" :key=\"field.key\">\n <div class=\"mb-4 p-1\">\n <AutoFormField\n :field-name=\"`${fieldName}[${index}]`\"\n :label=\"fieldName\"\n :shape=\"itemShape!\"\n :config=\"config as ConfigItem\"\n />\n\n <div class=\"!my-4 flex justify-end\">\n <Button\n type=\"button\"\n size=\"icon\"\n variant=\"secondary\"\n @click=\"remove(index)\"\n >\n <TrashIcon :size=\"16\" />\n </Button>\n </div>\n <Separator v-if=\"!field.isLast\" />\n </div>\n </template>\n\n <Button\n type=\"button\"\n variant=\"secondary\"\n class=\"mt-4 flex items-center\"\n @click=\"push(null)\"\n >\n <PlusIcon class=\"mr-2\" :size=\"16\" />\n Add\n </Button>\n </AccordionContent>\n\n <FormMessage />\n </AccordionItem>\n </Accordion>\n </slot>\n </FieldArray>\n</template>\n"
}, },
{ {
"name": "AutoFormFieldBoolean.vue", "name": "AutoFormFieldBoolean.vue",
@ -60,7 +60,7 @@
}, },
{ {
"name": "AutoFormFieldObject.vue", "name": "AutoFormFieldObject.vue",
"content": "<script setup lang=\"ts\" generic=\"T extends ZodRawShape\">\nimport type { ZodAny, ZodObject, ZodRawShape } from 'zod'\nimport { computed } from 'vue'\nimport AutoFormField from './AutoFormField.vue'\nimport type { Config, ConfigItem, Shape } from './interface'\nimport { beautifyObjectName, getBaseSchema, getBaseType, getDefaultValueInZodStack } from './utils'\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/default/ui/accordion'\n\nconst props = defineProps<{\n fieldName: string\n required?: boolean\n config?: Config<T>\n schema?: ZodObject<T>\n disabled?: boolean\n}>()\n\nconst shapes = computed(() => {\n // @ts-expect-error ignore {} not assignable to object\n const val: { [key in keyof T]: Shape } = {}\n\n if (!props.schema)\n return\n const shape = getBaseSchema(props.schema)?.shape\n if (!shape)\n return\n Object.keys(shape).forEach((name) => {\n const item = shape[name] as ZodAny\n let options = 'values' in item._def ? item._def.values as string[] : undefined\n if (!Array.isArray(options) && typeof options === 'object')\n options = Object.values(options)\n\n val[name as keyof T] = {\n type: getBaseType(item),\n default: getDefaultValueInZodStack(item),\n options,\n required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName),\n schema: item,\n }\n })\n return val\n})\n</script>\n\n<template>\n <section>\n <slot v-bind=\"props\">\n <Accordion type=\"multiple\" class=\"w-full\" collapsible :disabled=\"disabled\">\n <AccordionItem :value=\"fieldName\" class=\"border-none\">\n <AccordionTrigger class=\"text-base\">\n {{ schema?.description || beautifyObjectName(fieldName) }}\n </AccordionTrigger>\n <AccordionContent class=\"p-[1px] space-y-5\">\n <template v-for=\"(shape, key) in shapes\" :key=\"key\">\n <AutoFormField\n :config=\"config?.[key as keyof typeof config] as ConfigItem\"\n :field-name=\"`${fieldName}.${key.toString()}`\"\n :label=\"key.toString()\"\n :shape=\"shape\"\n />\n </template>\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n </slot>\n </section>\n</template>\n" "content": "<script setup lang=\"ts\" generic=\"T extends ZodRawShape\">\nimport type { ZodAny, ZodObject, ZodRawShape } from 'zod'\nimport { computed } from 'vue'\nimport AutoFormField from './AutoFormField.vue'\nimport type { Config, ConfigItem, Shape } from './interface'\nimport { beautifyObjectName, getBaseSchema, getBaseType, getDefaultValueInZodStack } from './utils'\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/default/ui/accordion'\n\nconst props = defineProps<{\n fieldName: string\n required?: boolean\n config?: Config<T>\n schema?: ZodObject<T>\n disabled?: boolean\n}>()\n\nconst shapes = computed(() => {\n // @ts-expect-error ignore {} not assignable to object\n const val: { [key in keyof T]: Shape } = {}\n\n if (!props.schema)\n return\n const shape = getBaseSchema(props.schema)?.shape\n if (!shape)\n return\n Object.keys(shape).forEach((name) => {\n const item = shape[name] as ZodAny\n let options = 'values' in item._def ? item._def.values as string[] : undefined\n if (!Array.isArray(options) && typeof options === 'object')\n options = Object.values(options)\n\n val[name as keyof T] = {\n type: getBaseType(item),\n default: getDefaultValueInZodStack(item),\n options,\n required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName),\n schema: item,\n }\n })\n return val\n})\n</script>\n\n<template>\n <section>\n <slot v-bind=\"props\">\n <Accordion type=\"multiple\" class=\"w-full\" collapsible :disabled=\"disabled\">\n <AccordionItem :value=\"fieldName\" class=\"border-none\">\n <AccordionTrigger class=\"text-base\">\n {{ schema?.description || beautifyObjectName(fieldName) }}\n </AccordionTrigger>\n <AccordionContent class=\"p-1 space-y-5\">\n <template v-for=\"(shape, key) in shapes\" :key=\"key\">\n <AutoFormField\n :config=\"config?.[key as keyof typeof config] as ConfigItem\"\n :field-name=\"`${fieldName}.${key.toString()}`\"\n :label=\"key.toString()\"\n :shape=\"shape\"\n />\n </template>\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n </slot>\n </section>\n</template>\n"
}, },
{ {
"name": "AutoFormLabel.vue", "name": "AutoFormLabel.vue",

View File

@ -24,7 +24,7 @@
"files": [ "files": [
{ {
"name": "AutoForm.vue", "name": "AutoForm.vue",
"content": "<script setup lang=\"ts\" generic=\"U extends ZodRawShape, T extends ZodObject<U>\">\nimport { computed, ref, toRef, toRefs } from 'vue'\nimport type { ZodAny, ZodObject, ZodRawShape, z } from 'zod'\nimport { toTypedSchema } from '@vee-validate/zod'\nimport type { FormContext, GenericObject } from 'vee-validate'\nimport { getBaseSchema, getBaseType, getDefaultValueInZodStack } from './utils'\nimport type { Config, ConfigItem, Dependency, Shape } from './interface'\nimport AutoFormField from './AutoFormField.vue'\nimport { provideDependencies } from './dependencies'\nimport { Form } from '@/lib/registry/new-york/ui/form'\n\nconst props = defineProps<{\n schema: T\n form?: FormContext<GenericObject>\n fieldConfig?: Config<z.infer<T>>\n dependencies?: Dependency<z.infer<T>>[]\n}>()\n\nconst emits = defineEmits<{\n submit: [event: GenericObject]\n}>()\n\nconst { dependencies } = toRefs(props)\nprovideDependencies(dependencies)\n\nconst shapes = computed(() => {\n // @ts-expect-error ignore {} not assignable to object\n const val: { [key in keyof T]: Shape } = {}\n const shape = props.schema.shape\n Object.keys(shape).forEach((name) => {\n const item = shape[name] as ZodAny\n const baseItem = getBaseSchema(item) as ZodAny\n let options = (baseItem && 'values' in baseItem._def) ? baseItem._def.values as string[] : undefined\n if (!Array.isArray(options) && typeof options === 'object')\n options = Object.values(options)\n\n val[name as keyof T] = {\n type: getBaseType(item),\n default: getDefaultValueInZodStack(item),\n options,\n required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName),\n schema: baseItem,\n }\n })\n return val\n})\n\nconst fields = computed(() => {\n // @ts-expect-error ignore {} not assignable to object\n const val: { [key in keyof z.infer<T>]: { shape: Shape, fieldName: string, config: ConfigItem } } = {}\n for (const key in shapes.value) {\n const shape = shapes.value[key]\n val[key as keyof z.infer<T>] = {\n shape,\n config: props.fieldConfig?.[key] as ConfigItem,\n fieldName: key,\n }\n }\n return val\n})\n\nconst formComponent = computed(() => props.form ? 'form' : Form)\nconst formComponentProps = computed(() => {\n if (props.form) {\n return {\n onSubmit: props.form.handleSubmit(val => emits('submit', val)),\n }\n }\n else {\n const formSchema = toTypedSchema(props.schema)\n return {\n keepValues: true,\n validationSchema: formSchema,\n onSubmit: (val: GenericObject) => emits('submit', val),\n }\n }\n})\n</script>\n\n<template>\n <component\n :is=\"formComponent\"\n v-bind=\"formComponentProps\"\n >\n <slot name=\"customAutoForm\" :fields=\"fields\">\n <template v-for=\"(shape, key) of shapes\" :key=\"key\">\n <slot\n :shape=\"shape\"\n :name=\"key.toString() as keyof z.infer<T>\"\n :field-name=\"key.toString()\"\n :config=\"fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem\"\n >\n <AutoFormField\n :config=\"fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem\"\n :field-name=\"key.toString()\"\n :shape=\"shape\"\n />\n </slot>\n </template>\n </slot>\n\n <slot :shapes=\"shapes\" />\n </component>\n</template>\n" "content": "<script setup lang=\"ts\" generic=\"T extends ZodObjectOrWrapped\">\nimport { computed, toRefs } from 'vue'\nimport type { ZodAny, z } from 'zod'\nimport { toTypedSchema } from '@vee-validate/zod'\nimport type { FormContext, GenericObject } from 'vee-validate'\nimport { type ZodObjectOrWrapped, getBaseSchema, getBaseType, getDefaultValueInZodStack, getObjectFormSchema } from './utils'\nimport type { Config, ConfigItem, Dependency, Shape } from './interface'\nimport AutoFormField from './AutoFormField.vue'\nimport { provideDependencies } from './dependencies'\nimport { Form } from '@/lib/registry/new-york/ui/form'\n\nconst props = defineProps<{\n schema: T\n form?: FormContext<GenericObject>\n fieldConfig?: Config<z.infer<T>>\n dependencies?: Dependency<z.infer<T>>[]\n}>()\n\nconst emits = defineEmits<{\n submit: [event: GenericObject]\n}>()\n\nconst { dependencies } = toRefs(props)\nprovideDependencies(dependencies)\n\nconst shapes = computed(() => {\n // @ts-expect-error ignore {} not assignable to object\n const val: { [key in keyof T]: Shape } = {}\n const baseSchema = getObjectFormSchema(props.schema)\n const shape = baseSchema.shape\n Object.keys(shape).forEach((name) => {\n const item = shape[name] as ZodAny\n const baseItem = getBaseSchema(item) as ZodAny\n let options = (baseItem && 'values' in baseItem._def) ? baseItem._def.values as string[] : undefined\n if (!Array.isArray(options) && typeof options === 'object')\n options = Object.values(options)\n\n val[name as keyof T] = {\n type: getBaseType(item),\n default: getDefaultValueInZodStack(item),\n options,\n required: !['ZodOptional', 'ZodNullable'].includes(item._def.typeName),\n schema: baseItem,\n }\n })\n return val\n})\n\nconst fields = computed(() => {\n // @ts-expect-error ignore {} not assignable to object\n const val: { [key in keyof z.infer<T>]: { shape: Shape, fieldName: string, config: ConfigItem } } = {}\n for (const key in shapes.value) {\n const shape = shapes.value[key]\n val[key as keyof z.infer<T>] = {\n shape,\n config: props.fieldConfig?.[key] as ConfigItem,\n fieldName: key,\n }\n }\n return val\n})\n\nconst formComponent = computed(() => props.form ? 'form' : Form)\nconst formComponentProps = computed(() => {\n if (props.form) {\n return {\n onSubmit: props.form.handleSubmit(val => emits('submit', val)),\n }\n }\n else {\n const formSchema = toTypedSchema(props.schema)\n return {\n keepValues: true,\n validationSchema: formSchema,\n onSubmit: (val: GenericObject) => emits('submit', val),\n }\n }\n})\n</script>\n\n<template>\n <component\n :is=\"formComponent\"\n v-bind=\"formComponentProps\"\n >\n <slot name=\"customAutoForm\" :fields=\"fields\">\n <template v-for=\"(shape, key) of shapes\" :key=\"key\">\n <slot\n :shape=\"shape\"\n :name=\"key.toString() as keyof z.infer<T>\"\n :field-name=\"key.toString()\"\n :config=\"fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem\"\n >\n <AutoFormField\n :config=\"fieldConfig?.[key as keyof typeof fieldConfig] as ConfigItem\"\n :field-name=\"key.toString()\"\n :shape=\"shape\"\n />\n </slot>\n </template>\n </slot>\n\n <slot :shapes=\"shapes\" />\n </component>\n</template>\n"
}, },
{ {
"name": "AutoFormField.vue", "name": "AutoFormField.vue",