92 lines
33 KiB
JSON
92 lines
33 KiB
JSON
{
|
|
"name": "auto-form",
|
|
"dependencies": [
|
|
"vee-validate",
|
|
"@vee-validate/zod",
|
|
"zod"
|
|
],
|
|
"registryDependencies": [
|
|
"form",
|
|
"accordion",
|
|
"button",
|
|
"separator",
|
|
"switch",
|
|
"checkbox",
|
|
"calendar",
|
|
"popover",
|
|
"utils",
|
|
"select",
|
|
"label",
|
|
"radio-group",
|
|
"input",
|
|
"textarea"
|
|
],
|
|
"files": [
|
|
{
|
|
"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"
|
|
},
|
|
{
|
|
"name": "AutoFormField.vue",
|
|
"content": "<script setup lang=\"ts\" generic=\"U extends ZodAny\">\nimport type { ZodAny } from 'zod'\nimport { computed } from 'vue'\nimport type { Config, ConfigItem, Shape } from './interface'\nimport { DEFAULT_ZOD_HANDLERS, INPUT_COMPONENTS } from './constant'\nimport useDependencies from './dependencies'\n\nconst props = defineProps<{\n fieldName: string\n shape: Shape\n label?: string\n config?: ConfigItem | Config<U>\n}>()\n\nfunction isValidConfig(config: any): config is ConfigItem {\n return !!config?.component\n}\n\nconst delegatedProps = computed(() => {\n if (['ZodObject', 'ZodArray'].includes(props.shape?.type))\n return { schema: props.shape?.schema }\n return undefined\n})\n\nconst { isDisabled, isHidden, isRequired, overrideOptions } = useDependencies(props.fieldName)\n</script>\n\n<template>\n <component\n :is=\"isValidConfig(config) ? INPUT_COMPONENTS[config.component!] : INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] \"\n v-if=\"!isHidden\"\n :field-name=\"fieldName\"\n :label=\"label\"\n :required=\"isRequired || shape.required\"\n :options=\"overrideOptions || shape.options\"\n :disabled=\"isDisabled\"\n :config=\"config\"\n v-bind=\"delegatedProps\"\n >\n <slot />\n </component>\n</template>\n"
|
|
},
|
|
{
|
|
"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"
|
|
},
|
|
{
|
|
"name": "AutoFormFieldBoolean.vue",
|
|
"content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport { beautifyObjectName } from './utils'\nimport type { FieldProps } from './interface'\nimport AutoFormLabel from './AutoFormLabel.vue'\nimport { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/default/ui/form'\nimport { Switch } from '@/lib/registry/default/ui/switch'\nimport { Checkbox } from '@/lib/registry/default/ui/checkbox'\n\nconst props = defineProps<FieldProps>()\n\nconst booleanComponent = computed(() => props.config?.component === 'switch' ? Switch : Checkbox)\n</script>\n\n<template>\n <FormField v-slot=\"slotProps\" :name=\"fieldName\">\n <FormItem>\n <div class=\"space-y-0 mb-3 flex items-center gap-3\">\n <FormControl>\n <slot v-bind=\"slotProps\">\n <component\n :is=\"booleanComponent\"\n v-bind=\"{ ...slotProps.componentField }\"\n :disabled=\"disabled\"\n :checked=\"slotProps.componentField.modelValue\"\n @update:checked=\"slotProps.componentField['onUpdate:modelValue']\"\n />\n </slot>\n </FormControl>\n <AutoFormLabel v-if=\"!config?.hideLabel\" :required=\"required\">\n {{ config?.label || beautifyObjectName(label ?? fieldName) }}\n </AutoFormLabel>\n </div>\n\n <FormDescription v-if=\"config?.description\">\n {{ config.description }}\n </FormDescription>\n <FormMessage />\n </FormItem>\n </FormField>\n</template>\n"
|
|
},
|
|
{
|
|
"name": "AutoFormFieldDate.vue",
|
|
"content": "<script setup lang=\"ts\">\nimport { DateFormatter, getLocalTimeZone } from '@internationalized/date'\nimport { CalendarIcon } from 'lucide-vue-next'\nimport { beautifyObjectName } from './utils'\nimport AutoFormLabel from './AutoFormLabel.vue'\nimport type { FieldProps } from './interface'\nimport { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/default/ui/form'\n\nimport { Calendar } from '@/lib/registry/default/ui/calendar'\nimport { Button } from '@/lib/registry/default/ui/button'\nimport { Popover, PopoverContent, PopoverTrigger } from '@/lib/registry/default/ui/popover'\nimport { cn } from '@/lib/utils'\n\ndefineProps<FieldProps>()\n\nconst df = new DateFormatter('en-US', {\n dateStyle: 'long',\n})\n</script>\n\n<template>\n <FormField v-slot=\"slotProps\" :name=\"fieldName\">\n <FormItem>\n <AutoFormLabel v-if=\"!config?.hideLabel\" :required=\"required\">\n {{ config?.label || beautifyObjectName(label ?? fieldName) }}\n </AutoFormLabel>\n <FormControl>\n <slot v-bind=\"slotProps\">\n <div>\n <Popover>\n <PopoverTrigger as-child :disabled=\"disabled\">\n <Button\n variant=\"outline\"\n :class=\"cn(\n 'w-full justify-start text-left font-normal',\n !slotProps.componentField.modelValue && 'text-muted-foreground',\n )\"\n >\n <CalendarIcon class=\"mr-2 h-4 w-4\" :size=\"16\" />\n {{ slotProps.componentField.modelValue ? df.format(slotProps.componentField.modelValue.toDate(getLocalTimeZone())) : \"Pick a date\" }}\n </Button>\n </PopoverTrigger>\n <PopoverContent class=\"w-auto p-0\">\n <Calendar initial-focus v-bind=\"slotProps.componentField\" />\n </PopoverContent>\n </Popover>\n </div>\n </slot>\n </FormControl>\n\n <FormDescription v-if=\"config?.description\">\n {{ config.description }}\n </FormDescription>\n <FormMessage />\n </FormItem>\n </FormField>\n</template>\n"
|
|
},
|
|
{
|
|
"name": "AutoFormFieldEnum.vue",
|
|
"content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport AutoFormLabel from './AutoFormLabel.vue'\nimport { beautifyObjectName } from './utils'\nimport type { FieldProps } from './interface'\nimport { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/default/ui/form'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/lib/registry/default/ui/select'\nimport { Label } from '@/lib/registry/default/ui/label'\nimport { RadioGroup, RadioGroupItem } from '@/lib/registry/default/ui/radio-group'\n\nconst props = defineProps<FieldProps & {\n options?: string[]\n}>()\n\nconst computedOptions = computed(() => props.config?.enumProps?.options || props.options)\n</script>\n\n<template>\n <FormField v-slot=\"slotProps\" :name=\"fieldName\">\n <FormItem>\n <AutoFormLabel v-if=\"!config?.hideLabel\" :required=\"required\">\n {{ config?.label || beautifyObjectName(label ?? fieldName) }}\n </AutoFormLabel>\n <FormControl>\n <slot v-bind=\"slotProps\">\n <RadioGroup v-if=\"config?.component === 'radio'\" :disabled=\"disabled\" :orientation=\"'vertical'\" v-bind=\"{ ...slotProps.componentField }\">\n <div v-for=\"(option, index) in computedOptions\" :key=\"option\" class=\"mb-2 flex items-center gap-3 space-y-0\">\n <RadioGroupItem :id=\"`${option}-${index}`\" :value=\"option\" />\n <Label :for=\"`${option}-${index}`\">{{ beautifyObjectName(option) }}</Label>\n </div>\n </RadioGroup>\n\n <Select v-else :disabled=\"disabled\" v-bind=\"{ ...slotProps.componentField }\">\n <SelectTrigger class=\"w-full\">\n <SelectValue :placeholder=\"config?.enumProps?.placeholder\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem v-for=\"option in computedOptions\" :key=\"option\" :value=\"option\">\n {{ beautifyObjectName(option) }}\n </SelectItem>\n </SelectContent>\n </Select>\n </slot>\n </FormControl>\n\n <FormDescription v-if=\"config?.description\">\n {{ config.description }}\n </FormDescription>\n <FormMessage />\n </FormItem>\n </FormField>\n</template>\n"
|
|
},
|
|
{
|
|
"name": "AutoFormFieldFile.vue",
|
|
"content": "<script setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { TrashIcon } from 'lucide-vue-next'\nimport AutoFormLabel from './AutoFormLabel.vue'\nimport { beautifyObjectName } from './utils'\nimport type { FieldProps } from './interface'\nimport { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/default/ui/form'\nimport { Input } from '@/lib/registry/default/ui/input'\nimport { Button } from '@/lib/registry/default/ui/button'\n\ndefineProps<FieldProps>()\n\nconst inputFile = ref<File>()\nasync function parseFileAsString(file: File | undefined): Promise<string> {\n return new Promise((resolve, reject) => {\n if (file) {\n const reader = new FileReader()\n reader.onloadend = () => {\n resolve(reader.result as string)\n }\n reader.onerror = (err) => {\n reject(err)\n }\n reader.readAsDataURL(file)\n }\n })\n}\n</script>\n\n<template>\n <FormField v-slot=\"slotProps\" :name=\"fieldName\">\n <FormItem v-bind=\"$attrs\">\n <AutoFormLabel v-if=\"!config?.hideLabel\" :required=\"required\">\n {{ config?.label || beautifyObjectName(label ?? fieldName) }}\n </AutoFormLabel>\n <FormControl>\n <slot v-bind=\"slotProps\">\n <Input\n v-if=\"!inputFile\"\n type=\"file\"\n v-bind=\"{ ...config?.inputProps }\"\n :disabled=\"disabled\"\n @change=\"async (ev: InputEvent) => {\n const file = (ev.target as HTMLInputElement).files?.[0]\n inputFile = file\n const parsed = await parseFileAsString(file)\n slotProps.componentField.onInput(parsed)\n }\"\n />\n <div v-else class=\"flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent pl-3 pr-1 py-1 text-sm shadow-sm transition-colors\">\n <p>{{ inputFile?.name }}</p>\n <Button\n :size=\"'icon'\"\n :variant=\"'ghost'\"\n class=\"h-[26px] w-[26px]\"\n aria-label=\"Remove file\"\n type=\"button\"\n @click=\"() => {\n inputFile = undefined\n slotProps.componentField.onInput(undefined)\n }\"\n >\n <TrashIcon :size=\"16\" />\n </Button>\n </div>\n </slot>\n </FormControl>\n <FormDescription v-if=\"config?.description\">\n {{ config.description }}\n </FormDescription>\n <FormMessage />\n </FormItem>\n </FormField>\n</template>\n"
|
|
},
|
|
{
|
|
"name": "AutoFormFieldInput.vue",
|
|
"content": "<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport AutoFormLabel from './AutoFormLabel.vue'\nimport { beautifyObjectName } from './utils'\nimport type { FieldProps } from './interface'\nimport { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/default/ui/form'\nimport { Input } from '@/lib/registry/default/ui/input'\nimport { Textarea } from '@/lib/registry/default/ui/textarea'\n\nconst props = defineProps<FieldProps>()\nconst inputComponent = computed(() => props.config?.component === 'textarea' ? Textarea : Input)\n</script>\n\n<template>\n <FormField v-slot=\"slotProps\" :name=\"fieldName\">\n <FormItem v-bind=\"$attrs\">\n <AutoFormLabel v-if=\"!config?.hideLabel\" :required=\"required\">\n {{ config?.label || beautifyObjectName(label ?? fieldName) }}\n </AutoFormLabel>\n <FormControl>\n <slot v-bind=\"slotProps\">\n <component\n :is=\"inputComponent\"\n type=\"text\"\n v-bind=\"{ ...slotProps.componentField, ...config?.inputProps }\"\n :disabled=\"disabled\"\n />\n </slot>\n </FormControl>\n <FormDescription v-if=\"config?.description\">\n {{ config.description }}\n </FormDescription>\n <FormMessage />\n </FormItem>\n </FormField>\n</template>\n"
|
|
},
|
|
{
|
|
"name": "AutoFormFieldNumber.vue",
|
|
"content": "<script setup lang=\"ts\">\nimport AutoFormLabel from './AutoFormLabel.vue'\nimport { beautifyObjectName } from './utils'\nimport type { FieldProps } from './interface'\nimport { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@/lib/registry/default/ui/form'\nimport { Input } from '@/lib/registry/default/ui/input'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\ndefineProps<FieldProps>()\n</script>\n\n<template>\n <FormField v-slot=\"slotProps\" :name=\"fieldName\">\n <FormItem>\n <AutoFormLabel v-if=\"!config?.hideLabel\" :required=\"required\">\n {{ config?.label || beautifyObjectName(label ?? fieldName) }}\n </AutoFormLabel>\n <FormControl>\n <slot v-bind=\"slotProps\">\n <Input type=\"number\" v-bind=\"{ ...slotProps.componentField, ...config?.inputProps }\" :disabled=\"disabled\" />\n </slot>\n </FormControl>\n <FormDescription v-if=\"config?.description\">\n {{ config.description }}\n </FormDescription>\n <FormMessage />\n </FormItem>\n </FormField>\n</template>\n"
|
|
},
|
|
{
|
|
"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"
|
|
},
|
|
{
|
|
"name": "AutoFormLabel.vue",
|
|
"content": "<script setup lang=\"ts\">\nimport { FormLabel } from '@/lib/registry/default/ui/form'\n\ndefineProps<{\n required?: boolean\n}>()\n</script>\n\n<template>\n <FormLabel>\n <slot />\n <span v-if=\"required\" class=\"text-destructive\"> *</span>\n </FormLabel>\n</template>\n"
|
|
},
|
|
{
|
|
"name": "constant.ts",
|
|
"content": "import { AutoFormFieldArray, AutoFormFieldBoolean, AutoFormFieldDate, AutoFormFieldEnum, AutoFormFieldFile, AutoFormFieldInput, AutoFormFieldNumber, AutoFormFieldObject } from './'\n\nexport const INPUT_COMPONENTS = {\n date: AutoFormFieldDate,\n select: AutoFormFieldEnum,\n radio: AutoFormFieldEnum,\n checkbox: AutoFormFieldBoolean,\n switch: AutoFormFieldBoolean,\n textarea: AutoFormFieldInput,\n number: AutoFormFieldNumber,\n string: AutoFormFieldInput,\n file: AutoFormFieldFile,\n array: AutoFormFieldArray,\n object: AutoFormFieldObject,\n}\n\n/**\n * Define handlers for specific Zod types.\n * You can expand this object to support more types.\n */\nexport const DEFAULT_ZOD_HANDLERS: {\n [key: string]: keyof typeof INPUT_COMPONENTS\n} = {\n ZodString: 'string',\n ZodBoolean: 'checkbox',\n ZodDate: 'date',\n ZodEnum: 'select',\n ZodNativeEnum: 'select',\n ZodNumber: 'number',\n ZodArray: 'array',\n ZodObject: 'object',\n}\n"
|
|
},
|
|
{
|
|
"name": "dependencies.ts",
|
|
"content": "import type * as z from 'zod'\nimport type { Ref } from 'vue'\nimport { computed, inject, ref, watch } from 'vue'\nimport { FieldContextKey, FormContextKey } from 'vee-validate'\nimport { createContext } from 'radix-vue'\nimport { type Dependency, DependencyType, type EnumValues } from './interface'\nimport { getIndexIfArray } from './utils'\n\nexport const [injectDependencies, provideDependencies] = createContext<Ref<Dependency<z.infer<z.ZodObject<any>>>[] | undefined>>('AutoFormDependencies')\n\nfunction getValueByPath<T extends Record<string, any>>(obj: T, path: string): any {\n const keys = path.split('.')\n let value = obj\n\n for (const key of keys) {\n if (value && typeof value === 'object' && key in value)\n value = value[key]\n else\n return undefined\n }\n\n return value\n}\n\nexport default function useDependencies(\n fieldName: string,\n) {\n const form = inject(FormContextKey)\n const field = inject(FieldContextKey)\n\n const currentFieldName = fieldName.replace(/\\[\\d+\\]/g, '')\n\n if (!form)\n throw new Error('useDependencies should be used within <AutoForm>')\n\n const { controlledValues } = form\n const dependencies = injectDependencies()\n const isDisabled = ref(false)\n const isHidden = ref(false)\n const isRequired = ref(false)\n const overrideOptions = ref<EnumValues | undefined>()\n\n const currentFieldValue = computed(() => field?.value.value)\n const currentFieldDependencies = computed(() => dependencies.value?.filter(\n dependency => dependency.targetField === currentFieldName,\n ))\n\n function getSourceValue(dep: Dependency<any>) {\n const source = dep.sourceField as string\n const lastKey = source.split('.').at(-1)\n if (source.includes('.') && lastKey) {\n if (Array.isArray(field?.value.value)) {\n const index = getIndexIfArray(fieldName) ?? -1\n return field?.value.value[index][lastKey]\n }\n\n return getValueByPath(form!.values, source)\n }\n\n return controlledValues.value[source as string]\n }\n\n const sourceFieldValues = computed(() => currentFieldDependencies.value?.map(dep => getSourceValue(dep)))\n\n const resetConditionState = () => {\n isDisabled.value = false\n isHidden.value = false\n isRequired.value = false\n overrideOptions.value = undefined\n }\n\n watch([sourceFieldValues, dependencies], () => {\n resetConditionState()\n currentFieldDependencies.value?.forEach((dep) => {\n const sourceValue = getSourceValue(dep)\n\n const conditionMet = dep.when(sourceValue, currentFieldValue.value)\n\n switch (dep.type) {\n case DependencyType.DISABLES:\n if (conditionMet)\n isDisabled.value = true\n\n break\n case DependencyType.REQUIRES:\n if (conditionMet)\n isRequired.value = true\n\n break\n case DependencyType.HIDES:\n if (conditionMet)\n isHidden.value = true\n\n break\n case DependencyType.SETS_OPTIONS:\n if (conditionMet)\n overrideOptions.value = dep.options\n\n break\n }\n })\n }, { immediate: true, deep: true })\n\n return {\n isDisabled,\n isHidden,\n isRequired,\n overrideOptions,\n }\n}\n"
|
|
},
|
|
{
|
|
"name": "index.ts",
|
|
"content": "export { default as AutoForm } from './AutoForm.vue'\nexport { default as AutoFormField } from './AutoFormField.vue'\nexport { getObjectFormSchema, getBaseSchema, getBaseType } from './utils'\nexport type { Config, ConfigItem } from './interface'\n\nexport { default as AutoFormFieldArray } from './AutoFormFieldArray.vue'\nexport { default as AutoFormFieldBoolean } from './AutoFormFieldBoolean.vue'\nexport { default as AutoFormFieldDate } from './AutoFormFieldDate.vue'\nexport { default as AutoFormFieldEnum } from './AutoFormFieldEnum.vue'\nexport { default as AutoFormFieldFile } from './AutoFormFieldFile.vue'\nexport { default as AutoFormFieldInput } from './AutoFormFieldInput.vue'\nexport { default as AutoFormFieldNumber } from './AutoFormFieldNumber.vue'\nexport { default as AutoFormFieldObject } from './AutoFormFieldObject.vue'\n"
|
|
},
|
|
{
|
|
"name": "interface.ts",
|
|
"content": "import type { InputHTMLAttributes, SelectHTMLAttributes } from 'vue'\nimport type { ZodAny, z } from 'zod'\nimport type { INPUT_COMPONENTS } from './constant'\n\nexport interface FieldProps {\n fieldName: string\n label?: string\n required?: boolean\n config?: ConfigItem\n disabled?: boolean\n}\n\nexport interface Shape {\n type: string\n default?: any\n required?: boolean\n options?: string[]\n schema?: ZodAny\n}\n\nexport interface ConfigItem {\n /** Value for the `FormLabel` */\n label?: string\n /** Value for the `FormDescription` */\n description?: string\n /** Pick which component to be rendered. */\n component?: keyof typeof INPUT_COMPONENTS\n /** Hide `FormLabel`. */\n hideLabel?: boolean\n inputProps?: InputHTMLAttributes\n enumProps?: SelectHTMLAttributes & { options?: any[] }\n}\n\n// Define a type to unwrap an array\ntype UnwrapArray<T> = T extends (infer U)[] ? U : never\n\nexport type Config<SchemaType extends object> = {\n // If SchemaType.key is an object, create a nested Config, otherwise ConfigItem\n [Key in keyof SchemaType]?:\n SchemaType[Key] extends any[]\n ? UnwrapArray<Config<SchemaType[Key]>>\n : SchemaType[Key] extends object\n ? Config<SchemaType[Key]>\n : ConfigItem;\n}\n\nexport enum DependencyType {\n DISABLES,\n REQUIRES,\n HIDES,\n SETS_OPTIONS,\n}\n\ninterface BaseDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> {\n sourceField: keyof SchemaType\n type: DependencyType\n targetField: keyof SchemaType\n when: (sourceFieldValue: any, targetFieldValue: any) => boolean\n}\n\nexport type ValueDependency<SchemaType extends z.infer<z.ZodObject<any, any>>> =\n BaseDependency<SchemaType> & {\n type:\n | DependencyType.DISABLES\n | DependencyType.REQUIRES\n | DependencyType.HIDES\n }\n\nexport type EnumValues = readonly [string, ...string[]]\n\nexport type OptionsDependency<\n SchemaType extends z.infer<z.ZodObject<any, any>>,\n> = BaseDependency<SchemaType> & {\n type: DependencyType.SETS_OPTIONS\n\n // Partial array of values from sourceField that will trigger the dependency\n options: EnumValues\n}\n\nexport type Dependency<SchemaType extends z.infer<z.ZodObject<any, any>>> =\n | ValueDependency<SchemaType>\n | OptionsDependency<SchemaType>\n"
|
|
},
|
|
{
|
|
"name": "utils.ts",
|
|
"content": "import type { z } from 'zod'\n\n// TODO: This should support recursive ZodEffects but TypeScript doesn't allow circular type definitions.\nexport type ZodObjectOrWrapped =\n | z.ZodObject<any, any>\n | z.ZodEffects<z.ZodObject<any, any>>\n\n/**\n * Beautify a camelCase string.\n * e.g. \"myString\" -> \"My String\"\n */\nexport function beautifyObjectName(string: string) {\n // Remove bracketed indices\n // if numbers only return the string\n let output = string.replace(/\\[\\d+\\]/g, '').replace(/([A-Z])/g, ' $1')\n output = output.charAt(0).toUpperCase() + output.slice(1)\n return output\n}\n\n/**\n * Parse string and extract the index\n * @param string\n * @returns index or undefined\n */\nexport function getIndexIfArray(string: string) {\n const indexRegex = /\\[(\\d+)\\]/\n // Match the index\n const match = string.match(indexRegex)\n // Extract the index (number)\n const index = match ? Number.parseInt(match[1]) : undefined\n return index\n}\n\n/**\n * Get the lowest level Zod type.\n * This will unpack optionals, refinements, etc.\n */\nexport function getBaseSchema<\n ChildType extends z.ZodAny | z.AnyZodObject = z.ZodAny,\n>(schema: ChildType | z.ZodEffects<ChildType>): ChildType | null {\n if (!schema)\n return null\n if ('innerType' in schema._def)\n return getBaseSchema(schema._def.innerType as ChildType)\n\n if ('schema' in schema._def)\n return getBaseSchema(schema._def.schema as ChildType)\n\n return schema as ChildType\n}\n\n/**\n * Get the type name of the lowest level Zod type.\n * This will unpack optionals, refinements, etc.\n */\nexport function getBaseType(schema: z.ZodAny) {\n const baseSchema = getBaseSchema(schema)\n return baseSchema ? baseSchema._def.typeName : ''\n}\n\n/**\n * Search for a \"ZodDefault\" in the Zod stack and return its value.\n */\nexport function getDefaultValueInZodStack(schema: z.ZodAny): any {\n const typedSchema = schema as unknown as z.ZodDefault<\n z.ZodNumber | z.ZodString\n >\n\n if (typedSchema._def.typeName === 'ZodDefault')\n return typedSchema._def.defaultValue()\n\n if ('innerType' in typedSchema._def) {\n return getDefaultValueInZodStack(\n typedSchema._def.innerType as unknown as z.ZodAny,\n )\n }\n if ('schema' in typedSchema._def) {\n return getDefaultValueInZodStack(\n (typedSchema._def as any).schema as z.ZodAny,\n )\n }\n\n return undefined\n}\n\nexport function getObjectFormSchema(\n schema: ZodObjectOrWrapped,\n): z.ZodObject<any, any> {\n if (schema?._def.typeName === 'ZodEffects') {\n const typedSchema = schema as z.ZodEffects<z.ZodObject<any, any>>\n return getObjectFormSchema(typedSchema._def.schema)\n }\n return schema as z.ZodObject<any, any>\n}\n"
|
|
}
|
|
],
|
|
"type": "components:ui"
|
|
}
|