feat: array, config label
This commit is contained in:
parent
7370383fbe
commit
1120f044e2
|
|
@ -8,98 +8,19 @@ import { toast } from '@/lib/registry/new-york/ui/toast'
|
|||
import type { Config } from '@/lib/registry/new-york/ui/auto-form'
|
||||
import { AutoForm, AutoFormField } from '@/lib/registry/new-york/ui/auto-form'
|
||||
|
||||
enum Sports {
|
||||
Football = 'Football/Soccer',
|
||||
Basketball = 'Basketball',
|
||||
Baseball = 'Baseball',
|
||||
Hockey = 'Hockey (Ice)',
|
||||
None = 'I don\'t like sports',
|
||||
}
|
||||
|
||||
const schema = z.object({
|
||||
username: z
|
||||
.string({
|
||||
required_error: 'Username is required.',
|
||||
})
|
||||
.min(2, {
|
||||
message: 'Username must be at least 2 characters.',
|
||||
guestListName: z.string(),
|
||||
invitedGuests: z
|
||||
.array(
|
||||
z.object({
|
||||
name: z.string(),
|
||||
age: z.coerce.number(),
|
||||
}),
|
||||
).default([
|
||||
{ name: '123', age: 30 },
|
||||
]).describe('How many guests'),
|
||||
|
||||
password: z
|
||||
.string({
|
||||
required_error: 'Password is required.',
|
||||
})
|
||||
.describe('Your secure password')
|
||||
.min(8, {
|
||||
message: 'Password must be at least 8 characters.',
|
||||
}),
|
||||
|
||||
favouriteNumber: z.coerce
|
||||
.number({
|
||||
invalid_type_error: 'Favourite number must be a number.',
|
||||
})
|
||||
.min(1, {
|
||||
message: 'Favourite number must be at least 1.',
|
||||
})
|
||||
.max(10, {
|
||||
message: 'Favourite number must be at most 10.',
|
||||
})
|
||||
.default(1)
|
||||
.optional(),
|
||||
|
||||
acceptTerms: z
|
||||
.boolean()
|
||||
.describe('Accept terms and conditions.')
|
||||
.refine(value => value, {
|
||||
message: 'You must accept the terms and conditions.',
|
||||
path: ['acceptTerms'],
|
||||
}),
|
||||
|
||||
sendMeMails: z.boolean().optional(),
|
||||
|
||||
birthday: z.coerce.date().optional(),
|
||||
|
||||
color: z.enum(['red', 'green', 'blue']).optional(),
|
||||
|
||||
// Another enum example
|
||||
marshmallows: z
|
||||
.enum(['not many', 'a few', 'a lot', 'too many'])
|
||||
.describe('How many marshmallows fit in your mouth?'),
|
||||
|
||||
// Native enum example
|
||||
sports: z.nativeEnum(Sports).describe('What is your favourite sport?'),
|
||||
|
||||
bio: z
|
||||
.string()
|
||||
.min(10, {
|
||||
message: 'Bio must be at least 10 characters.',
|
||||
})
|
||||
.max(160, {
|
||||
message: 'Bio must not be longer than 30 characters.',
|
||||
})
|
||||
.optional(),
|
||||
|
||||
customParent: z.string().optional(),
|
||||
|
||||
file: z.string().optional().describe('Text file'),
|
||||
|
||||
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('123 Description'),
|
||||
|
||||
optionalSubObject: z
|
||||
.object({
|
||||
optionalSubField: z.string(),
|
||||
otherOptionalSubField: z.string(),
|
||||
})
|
||||
.optional(),
|
||||
|
||||
list: z.array(z.string()).describe('test the config'),
|
||||
})
|
||||
|
||||
const formSchema = toTypedSchema(schema)
|
||||
|
|
@ -114,81 +35,30 @@ const onSubmit = handleSubmit((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))),
|
||||
})
|
||||
})
|
||||
|
||||
const config
|
||||
= reactive({
|
||||
username: {
|
||||
label: '123',
|
||||
description: 'Test description',
|
||||
inputProps: {
|
||||
id: '123',
|
||||
},
|
||||
},
|
||||
password: {
|
||||
inputProps: {
|
||||
id: '345',
|
||||
type: 'password',
|
||||
},
|
||||
},
|
||||
acceptTerms: {
|
||||
description: 'this is custom',
|
||||
},
|
||||
sendMeMails: {
|
||||
component: 'switch',
|
||||
},
|
||||
color: {
|
||||
enumProps: {
|
||||
options: ['red', 'green', 'blue'],
|
||||
placeholder: 'Choose a color',
|
||||
},
|
||||
},
|
||||
marshmallows: {
|
||||
component: 'radio',
|
||||
},
|
||||
bio: {
|
||||
component: 'textarea',
|
||||
},
|
||||
file: {
|
||||
component: 'file',
|
||||
},
|
||||
|
||||
subObject: {
|
||||
subField: {
|
||||
label: 'custom labvel',
|
||||
description: '123',
|
||||
},
|
||||
subSubObject: {
|
||||
subSubField: {
|
||||
label: 'sub suuuub',
|
||||
},
|
||||
},
|
||||
},
|
||||
}) as Config<z.infer<typeof schema>>
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AutoForm
|
||||
class="w-2/3 space-y-6"
|
||||
:schema="schema"
|
||||
:field-config="config"
|
||||
:field-config="{
|
||||
guestListName: {
|
||||
label: 'Lisst',
|
||||
inputProps: {
|
||||
placeholder: 'testign123',
|
||||
},
|
||||
},
|
||||
invitedGuests: {
|
||||
name: {
|
||||
label: 'walaaaaao',
|
||||
},
|
||||
},
|
||||
list: {
|
||||
label: 'woohooo',
|
||||
},
|
||||
}"
|
||||
@submit="onSubmit"
|
||||
>
|
||||
<template #username="componentField">
|
||||
<div class="border p-4 rounded-lg shadow-sm">
|
||||
<AutoFormField v-bind="{ ...componentField, name: 'username' }" />
|
||||
</div>
|
||||
</template>
|
||||
<template #customParent="componentField">
|
||||
<div class="flex items-end space-x-4">
|
||||
<AutoFormField class="w-full" v-bind="{ ...componentField, name: 'customParent' }" />
|
||||
<Button>Check</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #subObject="componentField">
|
||||
<AutoFormField v-bind="{ ...componentField, name: 'subObject' }" />
|
||||
</template>
|
||||
|
||||
<Button type="submit">
|
||||
Submit
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
<script setup lang="ts" generic="U extends ZodRawShape, T extends ZodObject<U>">
|
||||
import { computed } from 'vue'
|
||||
import type { ZodAny, ZodObject, ZodRawShape, z } from 'zod'
|
||||
import { getBaseSchema, getBaseType, getDefaultValueInZodStack } from './utils'
|
||||
import { getBaseType, getDefaultValueInZodStack } from './utils'
|
||||
import type { Config, ConfigItem, Shape } from './interface'
|
||||
import AutoFormField from './AutoFormField.vue'
|
||||
import { Accordion } from '@/lib/registry/new-york/ui/accordion'
|
||||
|
||||
const props = defineProps<{
|
||||
schema: T
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from './constant'
|
|||
defineProps<{
|
||||
name: string
|
||||
shape: Shape
|
||||
label?: string
|
||||
config?: ConfigItem | Config<U>
|
||||
}>()
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ function isValidConfig(config: any): config is ConfigItem {
|
|||
<component
|
||||
:is="isValidConfig(config) ? INPUT_COMPONENTS[config.component!] : INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] "
|
||||
:name="name"
|
||||
:label="label"
|
||||
:required="shape.required"
|
||||
:options="shape.options"
|
||||
:schema="shape.schema"
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
obkect
|
||||
</template>
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import FieldArray from './fields/Array.vue'
|
||||
import FieldBoolean from './fields/Boolean.vue'
|
||||
import FieldDate from './fields/Date.vue'
|
||||
import FieldEnum from './fields/Enum.vue'
|
||||
|
|
@ -16,6 +17,7 @@ export const INPUT_COMPONENTS = {
|
|||
number: FieldNumber,
|
||||
string: FieldInput,
|
||||
file: FieldFile,
|
||||
array: FieldArray,
|
||||
object: FieldObject,
|
||||
}
|
||||
|
||||
|
|
@ -32,5 +34,6 @@ export const DEFAULT_ZOD_HANDLERS: {
|
|||
ZodEnum: 'select',
|
||||
ZodNativeEnum: 'select',
|
||||
ZodNumber: 'number',
|
||||
ZodArray: 'array',
|
||||
ZodObject: 'object',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
<script setup lang="ts" generic="T extends z.ZodAny">
|
||||
import * as z from 'zod'
|
||||
import { computed } from 'vue'
|
||||
import { PlusIcon, TrashIcon } from '@radix-icons/vue'
|
||||
import { useFieldArray } from 'vee-validate'
|
||||
import type { Config, ConfigItem } from '../interface'
|
||||
import { beautifyObjectName, getBaseType } from '../utils'
|
||||
import AutoFormField from '../AutoFormField.vue'
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/new-york/ui/accordion'
|
||||
import { Button } from '@/lib/registry/new-york/ui/button'
|
||||
import { Separator } from '@/lib/registry/new-york/ui/separator'
|
||||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
required?: boolean
|
||||
config?: Config<T>
|
||||
schema?: z.ZodArray<T>
|
||||
}>()
|
||||
|
||||
const { remove, push, fields } = useFieldArray(props.name)
|
||||
|
||||
function isZodArray(
|
||||
item: z.ZodArray<any> | z.ZodDefault<any>,
|
||||
): item is z.ZodArray<any> {
|
||||
return item instanceof z.ZodArray
|
||||
}
|
||||
|
||||
function isZodDefault(
|
||||
item: z.ZodArray<any> | z.ZodDefault<any>,
|
||||
): item is z.ZodDefault<any> {
|
||||
return item instanceof z.ZodDefault
|
||||
}
|
||||
|
||||
const itemShape = computed(() => {
|
||||
if (!props.schema)
|
||||
return
|
||||
|
||||
const schema: z.ZodAny = isZodArray(props.schema)
|
||||
? props.schema._def.type
|
||||
: isZodDefault(props.schema)
|
||||
// @ts-expect-error missing schema
|
||||
? props.schema._def.innerType._def.type
|
||||
: null
|
||||
|
||||
return {
|
||||
type: getBaseType(schema),
|
||||
schema,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section>
|
||||
<slot v-bind="props">
|
||||
<Accordion type="multiple" class="w-full" collapsible>
|
||||
<AccordionItem :value="name" class="border-none">
|
||||
<AccordionTrigger class="text-base">
|
||||
{{ schema?.description || beautifyObjectName(name) }}
|
||||
</AccordionTrigger>
|
||||
{{ fields }}
|
||||
<AccordionContent class="p-2 space-y-5">
|
||||
<template v-for="(field, index) of fields" :key="field.key">
|
||||
<AutoFormField
|
||||
:name="`${name}[${index}]`"
|
||||
:label="name"
|
||||
:shape="itemShape!"
|
||||
:config="config as ConfigItem"
|
||||
/>
|
||||
|
||||
<div class="!mt-2 flex justify-end">
|
||||
<Button
|
||||
type="button"
|
||||
size="icon"
|
||||
variant="secondary"
|
||||
@click="remove(index)"
|
||||
>
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<Separator />
|
||||
</template>
|
||||
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
class="mt-4 flex items-center"
|
||||
@click="push(null)"
|
||||
>
|
||||
<PlusIcon class="mr-2" />
|
||||
Add
|
||||
</Button>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</slot>
|
||||
</section>
|
||||
</template>
|
||||
|
|
@ -1,16 +1,12 @@
|
|||
<script setup lang="ts">
|
||||
import { beautifyObjectName } from '../utils'
|
||||
import type { ConfigItem } from '../interface'
|
||||
import type { ConfigItem, FieldProps } from '../interface'
|
||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||
import { Switch } from '@/lib/registry/new-york/ui/switch'
|
||||
import { Checkbox } from '@/lib/registry/new-york/ui/checkbox'
|
||||
|
||||
defineProps<{
|
||||
name: string
|
||||
required?: boolean
|
||||
config?: ConfigItem
|
||||
}>()
|
||||
defineProps<FieldProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -24,7 +20,7 @@ defineProps<{
|
|||
</slot>
|
||||
</FormControl>
|
||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||
{{ config?.label || beautifyObjectName(name) }}
|
||||
{{ config?.label || beautifyObjectName(label ?? name) }}
|
||||
</AutoFormLabel>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { DateFormatter, getLocalTimeZone } from '@internationalized/date'
|
|||
import { CalendarIcon } from '@radix-icons/vue'
|
||||
import { beautifyObjectName } from '../utils'
|
||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||
import type { ConfigItem } from '../interface'
|
||||
import type { ConfigItem, FieldProps } from '../interface'
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||
|
||||
import { Calendar } from '@/lib/registry/new-york/ui/calendar'
|
||||
|
|
@ -11,11 +11,7 @@ import { Button } from '@/lib/registry/new-york/ui/button'
|
|||
import { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/new-york/ui/popover'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
defineProps<{
|
||||
name: string
|
||||
required?: boolean
|
||||
config?: ConfigItem
|
||||
}>()
|
||||
defineProps<FieldProps>()
|
||||
|
||||
const df = new DateFormatter('en-US', {
|
||||
dateStyle: 'long',
|
||||
|
|
@ -26,7 +22,7 @@ const df = new DateFormatter('en-US', {
|
|||
<FormField v-slot="slotProps" :name="name">
|
||||
<FormItem>
|
||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||
{{ config?.label || beautifyObjectName(name) }}
|
||||
{{ config?.label || beautifyObjectName(label ?? name) }}
|
||||
</AutoFormLabel>
|
||||
<FormControl>
|
||||
<slot v-bind="slotProps">
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { beautifyObjectName } from '../utils'
|
||||
import type { ConfigItem } from '../interface'
|
||||
import type { FieldProps } from '../interface'
|
||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/lib/registry/new-york/ui/select'
|
||||
import { Label } from '@/lib/registry/new-york/ui/label'
|
||||
import { RadioGroup, RadioGroupItem } from '@/lib/registry/new-york/ui/radio-group'
|
||||
|
||||
const props = defineProps<{
|
||||
name: string
|
||||
required?: boolean
|
||||
const props = defineProps<FieldProps & {
|
||||
options?: string[]
|
||||
config?: ConfigItem
|
||||
}>()
|
||||
|
||||
const computedOptions = computed(() => props.config?.enumProps?.options || props.options)
|
||||
|
|
@ -22,7 +19,7 @@ const computedOptions = computed(() => props.config?.enumProps?.options || props
|
|||
<FormField v-slot="slotProps" :name="name">
|
||||
<FormItem>
|
||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||
{{ config?.label || beautifyObjectName(name) }}
|
||||
{{ config?.label || beautifyObjectName(label ?? name) }}
|
||||
</AutoFormLabel>
|
||||
<FormControl>
|
||||
<slot v-bind="slotProps">
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import { beautifyObjectName } from '../utils'
|
||||
import type { ConfigItem } from '../interface'
|
||||
import type { ConfigItem, FieldProps } from '../interface'
|
||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||
|
||||
defineProps<{
|
||||
name: string
|
||||
required?: boolean
|
||||
config?: ConfigItem
|
||||
}>()
|
||||
defineProps<FieldProps>()
|
||||
|
||||
async function parseFileAsString(file: File | undefined): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
|
@ -31,7 +27,7 @@ async function parseFileAsString(file: File | undefined): Promise<string> {
|
|||
<FormField v-slot="slotProps" :name="name">
|
||||
<FormItem v-bind="$attrs">
|
||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||
{{ config?.label || beautifyObjectName(name) }}
|
||||
{{ config?.label || beautifyObjectName(label ?? name) }}
|
||||
</AutoFormLabel>
|
||||
<FormControl>
|
||||
<slot v-bind="slotProps">
|
||||
|
|
|
|||
|
|
@ -1,23 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import { beautifyObjectName } from '../utils'
|
||||
import type { Config, ConfigItem } from '../interface'
|
||||
import type { Config, ConfigItem, FieldProps } from '../interface'
|
||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||
import { Textarea } from '@/lib/registry/new-york/ui/textarea'
|
||||
|
||||
defineProps<{
|
||||
name: string
|
||||
required?: boolean
|
||||
config?: ConfigItem
|
||||
}>()
|
||||
defineProps<FieldProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormField v-slot="slotProps" :name="name">
|
||||
<FormItem v-bind="$attrs">
|
||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||
{{ config?.label || beautifyObjectName(name) }}
|
||||
{{ config?.label || beautifyObjectName(label ?? name) }}
|
||||
</AutoFormLabel>
|
||||
<FormControl>
|
||||
<slot v-bind="slotProps">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script setup lang="ts">
|
||||
import { beautifyObjectName } from '../utils'
|
||||
import type { Config, ConfigItem } from '../interface'
|
||||
import type { Config, ConfigItem, FieldProps } from '../interface'
|
||||
import AutoFormLabel from '../AutoFormLabel.vue'
|
||||
import { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/new-york/ui/form'
|
||||
import { Input } from '@/lib/registry/new-york/ui/input'
|
||||
|
|
@ -9,18 +9,14 @@ defineOptions({
|
|||
inheritAttrs: false,
|
||||
})
|
||||
|
||||
defineProps<{
|
||||
name: string
|
||||
required?: boolean
|
||||
config?: ConfigItem
|
||||
}>()
|
||||
defineProps<FieldProps>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormField v-slot="slotProps" :name="name">
|
||||
<FormItem>
|
||||
<AutoFormLabel v-if="!config?.hideLabel" :required="required">
|
||||
{{ config?.label || beautifyObjectName(name) }}
|
||||
{{ config?.label || beautifyObjectName(label ?? name) }}
|
||||
</AutoFormLabel>
|
||||
<FormControl>
|
||||
<slot v-bind="slotProps">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts" generic="T extends ZodRawShape">
|
||||
import type { ZodAny, ZodObject, ZodRawShape } from 'zod'
|
||||
import { computed } from 'vue'
|
||||
import type { Config, ConfigItem } from '../interface'
|
||||
import type { Config, ConfigItem, Shape } from '../interface'
|
||||
import { beautifyObjectName, getBaseSchema, getBaseType, getDefaultValueInZodStack } from '../utils'
|
||||
import AutoFormField from '../AutoFormField.vue'
|
||||
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@/lib/registry/new-york/ui/accordion'
|
||||
|
|
@ -15,15 +15,7 @@ const props = defineProps<{
|
|||
|
||||
const shapes = computed(() => {
|
||||
// @ts-expect-error ignore {} not assignable to object
|
||||
const val: {
|
||||
[key in keyof T]: {
|
||||
type: string
|
||||
default: any
|
||||
required?: boolean
|
||||
options?: string[]
|
||||
schema?: ZodAny
|
||||
}
|
||||
} = {}
|
||||
const val: { [key in keyof T]: Shape } = {}
|
||||
|
||||
if (!props.schema)
|
||||
return
|
||||
|
|
@ -60,7 +52,8 @@ const shapes = computed(() => {
|
|||
<template v-for="(shape, key) in shapes" :key="key">
|
||||
<AutoFormField
|
||||
:config="config?.[key as keyof typeof config] as ConfigItem"
|
||||
:name="key.toString()"
|
||||
:name="`${name}${key.toString()}`"
|
||||
:label="key.toString()"
|
||||
:shape="shape"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
export { default as AutoFormFieldArray } from './Array.vue'
|
||||
export { default as AutoFormFieldBoolean } from './Boolean.vue'
|
||||
export { default as AutoFormFieldDate } from './Date.vue'
|
||||
export { default as AutoFormFieldEnum } from './Enum.vue'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
export { default as AutoForm } from './AutoForm.vue'
|
||||
export { default as AutoFormField } from './AutoFormField.vue'
|
||||
export { getObjectFormSchema, getBaseSchema, getBaseType, getDefaultValues } from './utils'
|
||||
export { getObjectFormSchema, getBaseSchema, getBaseType } from './utils'
|
||||
export type { Config, ConfigItem } from './interface'
|
||||
|
||||
export * from './fields'
|
||||
|
|
|
|||
|
|
@ -2,9 +2,16 @@ import type { InputHTMLAttributes, SelectHTMLAttributes } from 'vue'
|
|||
import type { ZodAny, z } from 'zod'
|
||||
import type { INPUT_COMPONENTS } from './constant'
|
||||
|
||||
export interface FieldProps {
|
||||
name: string
|
||||
label?: string
|
||||
required?: boolean
|
||||
config?: ConfigItem
|
||||
}
|
||||
|
||||
export interface Shape {
|
||||
type: string
|
||||
default: any
|
||||
default?: any
|
||||
required?: boolean
|
||||
options?: string[]
|
||||
schema?: ZodAny
|
||||
|
|
@ -23,9 +30,15 @@ export interface ConfigItem {
|
|||
enumProps?: SelectHTMLAttributes & { options?: any[] }
|
||||
}
|
||||
|
||||
// Define a type to unwrap an array
|
||||
type UnwrapArray<T> = T extends (infer U)[] ? U : never
|
||||
|
||||
export type Config<SchemaType extends object> = {
|
||||
// If SchemaType.key is an object, create a nested Config, otherwise ConfigItem
|
||||
[Key in keyof SchemaType]?: SchemaType[Key] extends object
|
||||
[Key in keyof SchemaType]?:
|
||||
SchemaType[Key] extends any[]
|
||||
? UnwrapArray<Config<SchemaType[Key]>>
|
||||
: SchemaType[Key] extends object
|
||||
? Config<SchemaType[Key]>
|
||||
: ConfigItem;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { ZodFirstPartyTypeKind, type z } from 'zod'
|
||||
import type { FieldConfig } from './interface'
|
||||
import type { z } from 'zod'
|
||||
|
||||
// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions.
|
||||
export type ZodObjectOrWrapped =
|
||||
|
|
@ -11,8 +10,9 @@ export type ZodObjectOrWrapped =
|
|||
* e.g. "myString" -> "My String"
|
||||
*/
|
||||
export function beautifyObjectName(string: string) {
|
||||
// Remove bracketed indices
|
||||
// if numbers only return the string
|
||||
let output = string.replace(/([A-Z])/g, ' $1')
|
||||
let output = string.replace(/\[\d+\]/g, '').replace(/([A-Z])/g, ' $1')
|
||||
output = output.charAt(0).toUpperCase() + output.slice(1)
|
||||
return output
|
||||
}
|
||||
|
|
@ -69,55 +69,6 @@ export function getDefaultValueInZodStack(schema: z.ZodAny): any {
|
|||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all default values from a Zod schema.
|
||||
*/
|
||||
export function getDefaultValues<Schema extends z.ZodObject<any, any>>(
|
||||
schema: Schema,
|
||||
fieldConfig?: FieldConfig<z.infer<Schema>>,
|
||||
) {
|
||||
if (!schema)
|
||||
return null
|
||||
const { shape } = schema
|
||||
type DefaultValuesType = Partial<z.infer<Schema>>
|
||||
const defaultValues = {} as DefaultValuesType
|
||||
if (!shape)
|
||||
return defaultValues
|
||||
|
||||
for (const key of Object.keys(shape)) {
|
||||
const item = shape[key] as z.ZodAny
|
||||
|
||||
// @ts-expect-error weird
|
||||
if (getBaseType(item) === ZodFirstPartyTypeKind.ZodObject) {
|
||||
const defaultItems = getDefaultValues(
|
||||
getBaseSchema(item) as unknown as z.ZodObject<any, any>,
|
||||
fieldConfig?.[key] as FieldConfig<z.infer<Schema>>,
|
||||
)
|
||||
|
||||
if (defaultItems !== null) {
|
||||
for (const defaultItemKey of Object.keys(defaultItems)) {
|
||||
const pathKey = `${key}.${defaultItemKey}` as keyof DefaultValuesType
|
||||
defaultValues[pathKey] = defaultItems[defaultItemKey]
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
let defaultValue = getDefaultValueInZodStack(item)
|
||||
if (
|
||||
(defaultValue === null || defaultValue === '')
|
||||
&& fieldConfig?.[key]?.inputProps
|
||||
) {
|
||||
defaultValue = (fieldConfig?.[key]?.inputProps as unknown as any)
|
||||
.defaultValue
|
||||
}
|
||||
if (defaultValue !== undefined)
|
||||
defaultValues[key as keyof DefaultValuesType] = defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValues
|
||||
}
|
||||
|
||||
export function getObjectFormSchema(
|
||||
schema: ZodObjectOrWrapped,
|
||||
): z.ZodObject<any, any> {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user