gvf3n4ygn-test
Version:
CLI for SIGMA-UI components.
76 lines • 37.8 kB
JSON
{
"name": "auto-form",
"dependencies": [
"vee-validate",
"@vee-validate/zod",
"zod"
],
"registryDependencies": [],
"files": [
{
"name": "AutoForm.vue",
"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 '@ui/registry/css/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\n if (!Array.isArray(options) && typeof options === 'object') {\n options = Object.values(options);\n }\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\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\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 } 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\n name=\"customAutoForm\"\n :fields=\"fields\"\n >\n <template\n v-for=\"(shape, key) of shapes\"\n :key=\"key\"\n >\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 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 }\n\n return undefined;\n});\n\nconst { isDisabled, isHidden, isRequired, overrideOptions } = useDependencies(props.fieldName);\n</script>\n\n<template>\n <component\n :is=\"isValidConfig(config)\n ? typeof config.component === 'string'\n ? INPUT_COMPONENTS[config.component!]\n : config.component\n : INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] \"\n v-if=\"!isHidden\"\n :field-name=\"fieldName\"\n :label=\"shape.schema?.description\"\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 } 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 '@ui/registry/css/ui/accordion';\nimport { Button } from '@ui/registry/css/ui/button';\nimport { Separator } from '@ui/registry/css/ui/separator';\nimport { FormItem, FormMessage } from '@ui/registry/css/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\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\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\nconst fieldContext = useField(props.fieldName);\n// @ts-expect-error ignore missing `id`\nprovide(FieldContextKey, fieldContext);\n</script>\n\n<template>\n <FieldArray\n v-slot=\"{ fields, remove, push }\"\n as=\"section\"\n :name=\"fieldName\"\n >\n <slot v-bind=\"props\">\n <Accordion\n type=\"multiple\"\n class=\"w-full\"\n collapsible\n :disabled=\"disabled\"\n as-child\n >\n <FormItem>\n <AccordionItem\n :value=\"fieldName\"\n class=\"border-none\"\n >\n <AccordionTrigger>\n <AutoFormLabel\n class=\"text-base\"\n :required=\"required\"\n >\n {{ schema?.description || beautifyObjectName(fieldName) }}\n </AutoFormLabel>\n </AccordionTrigger>\n\n <AccordionContent>\n <template\n v-for=\"(field, index) of fields\"\n :key=\"field.key\"\n >\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\n class=\"mr-2\"\n :size=\"16\"\n />\n Add\n </Button>\n </AccordionContent>\n\n <FormMessage />\n </AccordionItem>\n </FormItem>\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 '@ui/registry/css/ui/form';\nimport { Switch } from '@ui/registry/css/ui/switch';\nimport { Checkbox } from '@ui/registry/css/ui/checkbox';\n\nconst props = defineProps<FieldProps>();\n\nconst booleanComponent = computed(() => props.config?.component === 'switch' ? Switch : Checkbox);\n</script>\n\n<template>\n <FormField\n v-slot=\"slotProps\"\n :name=\"fieldName\"\n >\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\n v-if=\"!config?.hideLabel\"\n :required=\"required\"\n >\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 '@ui/registry/css/ui/form';\nimport { Calendar } from '@ui/registry/css/ui/calendar';\nimport { Button } from '@ui/registry/css/ui/button';\nimport { Popover, PopoverContent, PopoverTrigger } from '@ui/registry/css/ui/popover';\nimport { cn } from '@ui/utils';\n\ndefineProps<FieldProps>();\n\nconst df = new DateFormatter('en-US', {\n dateStyle: 'long',\n});\n</script>\n\n<template>\n <FormField\n v-slot=\"slotProps\"\n :name=\"fieldName\"\n >\n <FormItem>\n <AutoFormLabel\n v-if=\"!config?.hideLabel\"\n :required=\"required\"\n >\n {{ config?.label || beautifyObjectName(label ?? fieldName) }}\n </AutoFormLabel>\n <FormControl>\n <slot v-bind=\"slotProps\">\n <div>\n <Popover>\n <PopoverTrigger\n as-child\n :disabled=\"disabled\"\n >\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\n class=\"mr-2 h-4 w-4\"\n :size=\"16\"\n />\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\n initial-focus\n v-bind=\"slotProps.componentField\"\n />\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 AutoFormLabel from './AutoFormLabel.vue';\nimport { beautifyObjectName } from './utils';\nimport type { FieldProps } from './interface';\nimport { FormControl, FormDescription, FormField, FormItem, FormMessage } from '@ui/registry/css/ui/form';\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@ui/registry/css/ui/select';\nimport { Label } from '@ui/registry/css/ui/label';\nimport { RadioGroup, RadioGroupItem } from '@ui/registry/css/ui/radio-group';\n\ndefineProps<FieldProps & {\n options?: string[];\n}>();\n</script>\n\n<template>\n <FormField\n v-slot=\"slotProps\"\n :name=\"fieldName\"\n >\n <FormItem>\n <AutoFormLabel\n v-if=\"!config?.hideLabel\"\n :required=\"required\"\n >\n {{ config?.label || beautifyObjectName(label ?? fieldName) }}\n </AutoFormLabel>\n <FormControl>\n <slot v-bind=\"slotProps\">\n <RadioGroup\n v-if=\"config?.component === 'radio'\"\n :disabled=\"disabled\"\n :orientation=\"'vertical'\"\n v-bind=\"{ ...slotProps.componentField }\"\n >\n <div\n v-for=\"(option, index) in options\"\n :key=\"option\"\n class=\"mb-2 flex items-center gap-3 space-y-0\"\n >\n <RadioGroupItem\n :id=\"`${option}-${index}`\"\n :value=\"option\"\n />\n <Label :for=\"`${option}-${index}`\">{{ beautifyObjectName(option) }}</Label>\n </div>\n </RadioGroup>\n\n <Select\n v-else\n :disabled=\"disabled\"\n v-bind=\"{ ...slotProps.componentField }\"\n >\n <SelectTrigger class=\"w-full\">\n <SelectValue :placeholder=\"config?.inputProps?.placeholder\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem\n v-for=\"option in options\"\n :key=\"option\"\n :value=\"option\"\n >\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 '@ui/registry/css/ui/form';\nimport { Input } from '@ui/registry/css/ui/input';\nimport { Button } from '@ui/registry/css/ui/button';\n\ndefineProps<FieldProps>();\n\nconst inputFile = ref<File>();\n\nasync function parseFileAsString(file: File | undefined): Promise<string> {\n return new Promise((resolve, reject) => {\n if (file) {\n const reader = new FileReader();\n\n reader.onloadend = () => {\n resolve(reader.result as string);\n };\n\n reader.onerror = (err) => {\n reject(err);\n };\n\n reader.readAsDataURL(file);\n }\n });\n}\n</script>\n\n<template>\n <FormField\n v-slot=\"slotProps\"\n :name=\"fieldName\"\n >\n <FormItem v-bind=\"$attrs\">\n <AutoFormLabel\n v-if=\"!config?.hideLabel\"\n :required=\"required\"\n >\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\n v-else\n 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 >\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 '@ui/registry/css/ui/form';\nimport { Input } from '@ui/registry/css/ui/input';\nimport { Textarea } from '@ui/registry/css/ui/textarea';\n\nconst props = defineProps<FieldProps>();\nconst inputComponent = computed(() => props.config?.component === 'textarea' ? Textarea : Input);\n</script>\n\n<template>\n <FormField\n v-slot=\"slotProps\"\n :name=\"fieldName\"\n >\n <FormItem v-bind=\"$attrs\">\n <AutoFormLabel\n v-if=\"!config?.hideLabel\"\n :required=\"required\"\n >\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 '@ui/registry/css/ui/form';\nimport { Input } from '@ui/registry/css/ui/input';\n\ndefineOptions({\n inheritAttrs: false,\n});\n\ndefineProps<FieldProps>();\n</script>\n\n<template>\n <FormField\n v-slot=\"slotProps\"\n :name=\"fieldName\"\n >\n <FormItem>\n <AutoFormLabel\n v-if=\"!config?.hideLabel\"\n :required=\"required\"\n >\n {{ config?.label || beautifyObjectName(label ?? fieldName) }}\n </AutoFormLabel>\n <FormControl>\n <slot v-bind=\"slotProps\">\n <Input\n type=\"number\"\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": "AutoFormFieldObject.vue",
"content": "<script setup lang=\"ts\" generic=\"T extends ZodRawShape\">\nimport type { ZodAny, ZodObject, ZodRawShape } from 'zod';\nimport { computed, provide } from 'vue';\nimport { FieldContextKey, useField } from 'vee-validate';\nimport AutoFormField from './AutoFormField.vue';\nimport type { Config, ConfigItem, Shape } from './interface';\nimport { beautifyObjectName, getBaseSchema, getBaseType, getDefaultValueInZodStack } from './utils';\nimport AutoFormLabel from './AutoFormLabel.vue';\nimport { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '@ui/registry/css/ui/accordion';\nimport { FormItem } from '@ui/registry/css/ui/form';\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 }\n\n const shape = getBaseSchema(props.schema)?.shape;\n\n if (!shape) {\n return;\n }\n\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\n if (!Array.isArray(options) && typeof options === 'object') {\n options = Object.values(options);\n }\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\nconst fieldContext = useField(props.fieldName);\n// @ts-expect-error ignore missing `id`\nprovide(FieldContextKey, fieldContext);\n</script>\n\n<template>\n <section>\n <slot v-bind=\"props\">\n <Accordion\n type=\"single\"\n as-child\n class=\"w-full\"\n collapsible\n :disabled=\"disabled\"\n >\n <FormItem>\n <AccordionItem\n :value=\"fieldName\"\n class=\"border-none\"\n >\n <AccordionTrigger>\n <AutoFormLabel\n class=\"text-base\"\n :required=\"required\"\n >\n {{ schema?.description || beautifyObjectName(fieldName) }}\n </AutoFormLabel>\n </AccordionTrigger>\n <AccordionContent class=\"p-1 space-y-5\">\n <template\n v-for=\"(shape, key) in shapes\"\n :key=\"key\"\n >\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 </FormItem>\n </Accordion>\n </slot>\n </section>\n</template>\n"
},
{
"name": "AutoFormLabel.vue",
"content": "<script setup lang=\"ts\">\nimport { FormLabel } from '@ui/registry/css/ui/form';\n\ndefineProps<{\n required?: boolean;\n}>();\n</script>\n\n<template>\n <FormLabel>\n <slot />\n <span\n v-if=\"required\"\n class=\"text-destructive\"\n > *</span>\n </FormLabel>\n</template>\n"
},
{
"name": "constant.ts",
"content": "import AutoFormFieldArray from './AutoFormFieldArray.vue';\nimport AutoFormFieldBoolean from './AutoFormFieldBoolean.vue';\nimport AutoFormFieldDate from './AutoFormFieldDate.vue';\nimport AutoFormFieldEnum from './AutoFormFieldEnum.vue';\nimport AutoFormFieldFile from './AutoFormFieldFile.vue';\nimport AutoFormFieldInput from './AutoFormFieldInput.vue';\nimport AutoFormFieldNumber from './AutoFormFieldNumber.vue';\nimport AutoFormFieldObject from './AutoFormFieldObject.vue';\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, ref, watch } from 'vue';\nimport { useFieldValue, useFormValues } from 'vee-validate';\nimport { createContext } from 'radix-vue';\nimport { type Dependency, DependencyType, type EnumValues } from './interface';\nimport { getFromPath, getIndexIfArray } from './utils';\n\nexport const [injectDependencies, provideDependencies] = createContext<Ref<Dependency<z.infer<z.ZodObject<any>>>[] | undefined>>('AutoFormDependencies');\n\nexport default function useDependencies(\n fieldName: string,\n) {\n const form = useFormValues();\n const currentFieldName = fieldName.replace(/\\[\\d+\\]/g, '');\n const currentFieldValue = useFieldValue<any>(fieldName);\n\n if (!form) {\n throw new Error('useDependencies should be used within <AutoForm>');\n }\n\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 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 index = getIndexIfArray(fieldName) ?? -1;\n const [sourceLast, ...sourceInitial] = source.split('.').toReversed();\n const [_, ...targetInitial] = (dep.targetField as string).split('.').toReversed();\n\n if (index >= 0 && sourceInitial.join(',') === targetInitial.join(',')) {\n const [_, ...currentInitial] = fieldName.split('.').toReversed();\n return getFromPath(form.value, currentInitial.join('.') + sourceLast);\n }\n\n return getFromPath(form.value, source);\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 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\n break;\n case DependencyType.REQUIRES:\n if (conditionMet) {\n isRequired.value = true;\n }\n\n break;\n case DependencyType.HIDES:\n if (conditionMet) {\n isHidden.value = true;\n }\n\n break;\n case DependencyType.SETS_OPTIONS:\n if (conditionMet) {\n overrideOptions.value = dep.options;\n }\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 { getObjectFormSchema, getBaseSchema, getBaseType } from './utils';\nexport type { Config, ConfigItem, FieldProps } from './interface';\n\nexport { default as AutoForm } from './AutoForm.vue';\nexport { default as AutoFormField } from './AutoFormField.vue';\nexport { default as AutoFormLabel } from './AutoFormLabel.vue';\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 { Component, InputHTMLAttributes } 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 | Component;\n /** Hide `FormLabel`. */\n hideLabel?: boolean;\n inputProps?: InputHTMLAttributes;\n}\n\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 const match = string.match(indexRegex);\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 }\n\n if ('innerType' in schema._def) {\n return getBaseSchema(schema._def.innerType as ChildType);\n }\n\n if ('schema' in schema._def) {\n return getBaseSchema(schema._def.schema as ChildType);\n }\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\n if ('innerType' in typedSchema._def) {\n return getDefaultValueInZodStack(\n typedSchema._def.innerType as unknown as z.ZodAny,\n );\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\n return schema as z.ZodObject<any, any>;\n}\n\nfunction isIndex(value: unknown): value is number {\n return Number(value) >= 0;\n}\n\n/**\n * Constructs a path with dot paths for arrays to use brackets to be compatible with vee-validate path syntax\n */\nexport function normalizeFormPath(path: string): string {\n const pathArr = path.split('.');\n\n if (!pathArr.length) {\n return '';\n }\n\n let fullPath = String(pathArr[0]);\n\n for (let i = 1; i < pathArr.length; i++) {\n if (isIndex(pathArr[i])) {\n fullPath += `[${pathArr[i]}]`;\n continue;\n }\n\n fullPath += `.${pathArr[i]}`;\n }\n\n return fullPath;\n}\n\ntype NestedRecord = Record<string, unknown> | { [k: string]: NestedRecord };\n\n/**\n * Checks if the path opted out of nested fields using `[fieldName]` syntax\n */\nexport function isNotNestedPath(path: string) {\n return /^\\[.+\\]$/.test(path);\n}\n\nfunction isObject(obj: unknown): obj is Record<string, unknown> {\n return obj !== null && !!obj && typeof obj === 'object' && !Array.isArray(obj);\n}\n\nfunction isContainerValue(value: unknown): value is Record<string, unknown> {\n return isObject(value) || Array.isArray(value);\n}\n\nfunction cleanupNonNestedPath(path: string) {\n if (isNotNestedPath(path)) {\n return path.replace(/\\[|\\]/g, '');\n }\n\n return path;\n}\n\n/**\n * Gets a nested property value from an object\n */\nexport function getFromPath<TValue = unknown>(object: NestedRecord | undefined, path: string): TValue | undefined;\nexport function getFromPath<TValue = unknown, TFallback = TValue>(\n object: NestedRecord | undefined,\n path: string,\n fallback?: TFallback,\n): TValue | TFallback;\n\nexport function getFromPath<TValue = unknown, TFallback = TValue>(\n object: NestedRecord | undefined,\n path: string,\n fallback?: TFallback,\n): TValue | TFallback | undefined {\n if (!object) {\n return fallback;\n }\n\n if (isNotNestedPath(path)) {\n return object[cleanupNonNestedPath(path)] as TValue | undefined;\n }\n\n const resolvedValue = (path || '')\n .split(/\\.|\\[(\\d+)\\]/)\n .filter(Boolean)\n .reduce((acc, propKey) => {\n if (isContainerValue(acc) && propKey in acc) {\n return acc[propKey];\n }\n\n return fallback;\n }, object as unknown);\n\n return resolvedValue as TValue | undefined;\n}\n"
}
],
"type": "components:ui"
}