feat: allow zod description as label, allow custom component
This commit is contained in:
parent
02a4f0241f
commit
5ac2f74dac
|
|
@ -8,7 +8,6 @@ import useDependencies from './dependencies'
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fieldName: string
|
fieldName: string
|
||||||
shape: Shape
|
shape: Shape
|
||||||
label?: string
|
|
||||||
config?: ConfigItem | Config<U>
|
config?: ConfigItem | Config<U>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|
@ -27,10 +26,14 @@ const { isDisabled, isHidden, isRequired, overrideOptions } = useDependencies(pr
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component
|
<component
|
||||||
:is="isValidConfig(config) ? INPUT_COMPONENTS[config.component!] : INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] "
|
:is="isValidConfig(config)
|
||||||
|
? typeof config.component === 'string'
|
||||||
|
? INPUT_COMPONENTS[config.component!]
|
||||||
|
: config.component
|
||||||
|
: INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] "
|
||||||
v-if="!isHidden"
|
v-if="!isHidden"
|
||||||
:field-name="fieldName"
|
:field-name="fieldName"
|
||||||
:label="label"
|
:label="shape.schema?.description"
|
||||||
:required="isRequired || shape.required"
|
:required="isRequired || shape.required"
|
||||||
:options="overrideOptions || shape.options"
|
:options="overrideOptions || shape.options"
|
||||||
:disabled="isDisabled"
|
:disabled="isDisabled"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
export { getObjectFormSchema, getBaseSchema, getBaseType } from './utils'
|
export { getObjectFormSchema, getBaseSchema, getBaseType } from './utils'
|
||||||
export type { Config, ConfigItem } from './interface'
|
export type { Config, ConfigItem, FieldProps } from './interface'
|
||||||
|
|
||||||
export { default as AutoForm } from './AutoForm.vue'
|
export { default as AutoForm } from './AutoForm.vue'
|
||||||
export { default as AutoFormField } from './AutoFormField.vue'
|
export { default as AutoFormField } from './AutoFormField.vue'
|
||||||
|
export { default as AutoFormLabel } from './AutoFormLabel.vue'
|
||||||
|
|
||||||
export { default as AutoFormFieldArray } from './AutoFormFieldArray.vue'
|
export { default as AutoFormFieldArray } from './AutoFormFieldArray.vue'
|
||||||
export { default as AutoFormFieldBoolean } from './AutoFormFieldBoolean.vue'
|
export { default as AutoFormFieldBoolean } from './AutoFormFieldBoolean.vue'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { InputHTMLAttributes, SelectHTMLAttributes } from 'vue'
|
import type { Component, InputHTMLAttributes, SelectHTMLAttributes } from 'vue'
|
||||||
import type { ZodAny, z } from 'zod'
|
import type { ZodAny, z } from 'zod'
|
||||||
import type { INPUT_COMPONENTS } from './constant'
|
import type { INPUT_COMPONENTS } from './constant'
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ export interface ConfigItem {
|
||||||
/** Value for the `FormDescription` */
|
/** Value for the `FormDescription` */
|
||||||
description?: string
|
description?: string
|
||||||
/** Pick which component to be rendered. */
|
/** Pick which component to be rendered. */
|
||||||
component?: keyof typeof INPUT_COMPONENTS
|
component?: keyof typeof INPUT_COMPONENTS | Component
|
||||||
/** Hide `FormLabel`. */
|
/** Hide `FormLabel`. */
|
||||||
hideLabel?: boolean
|
hideLabel?: boolean
|
||||||
inputProps?: InputHTMLAttributes
|
inputProps?: InputHTMLAttributes
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import useDependencies from './dependencies'
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fieldName: string
|
fieldName: string
|
||||||
shape: Shape
|
shape: Shape
|
||||||
label?: string
|
|
||||||
config?: ConfigItem | Config<U>
|
config?: ConfigItem | Config<U>
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|
@ -27,10 +26,14 @@ const { isDisabled, isHidden, isRequired, overrideOptions } = useDependencies(pr
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<component
|
<component
|
||||||
:is="isValidConfig(config) ? INPUT_COMPONENTS[config.component!] : INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] "
|
:is="isValidConfig(config)
|
||||||
|
? typeof config.component === 'string'
|
||||||
|
? INPUT_COMPONENTS[config.component!]
|
||||||
|
: config.component
|
||||||
|
: INPUT_COMPONENTS[DEFAULT_ZOD_HANDLERS[shape.type]] "
|
||||||
v-if="!isHidden"
|
v-if="!isHidden"
|
||||||
:field-name="fieldName"
|
:field-name="fieldName"
|
||||||
:label="label"
|
:label="shape.schema?.description"
|
||||||
:required="isRequired || shape.required"
|
:required="isRequired || shape.required"
|
||||||
:options="overrideOptions || shape.options"
|
:options="overrideOptions || shape.options"
|
||||||
:disabled="isDisabled"
|
:disabled="isDisabled"
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
|
export { getObjectFormSchema, getBaseSchema, getBaseType } from './utils'
|
||||||
|
export type { Config, ConfigItem, FieldProps } from './interface'
|
||||||
|
|
||||||
export { default as AutoForm } from './AutoForm.vue'
|
export { default as AutoForm } from './AutoForm.vue'
|
||||||
export { default as AutoFormField } from './AutoFormField.vue'
|
export { default as AutoFormField } from './AutoFormField.vue'
|
||||||
|
export { default as AutoFormLabel } from './AutoFormLabel.vue'
|
||||||
export { getObjectFormSchema, getBaseSchema, getBaseType } from './utils'
|
|
||||||
export type { Config, ConfigItem } from './interface'
|
|
||||||
|
|
||||||
export { default as AutoFormFieldArray } from './AutoFormFieldArray.vue'
|
export { default as AutoFormFieldArray } from './AutoFormFieldArray.vue'
|
||||||
export { default as AutoFormFieldBoolean } from './AutoFormFieldBoolean.vue'
|
export { default as AutoFormFieldBoolean } from './AutoFormFieldBoolean.vue'
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import type { InputHTMLAttributes, SelectHTMLAttributes } from 'vue'
|
import type { Component, InputHTMLAttributes, SelectHTMLAttributes } from 'vue'
|
||||||
import type { ZodAny, z } from 'zod'
|
import type { ZodAny, z } from 'zod'
|
||||||
import type { INPUT_COMPONENTS } from './constant'
|
import type { INPUT_COMPONENTS } from './constant'
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ export interface ConfigItem {
|
||||||
/** Value for the `FormDescription` */
|
/** Value for the `FormDescription` */
|
||||||
description?: string
|
description?: string
|
||||||
/** Pick which component to be rendered. */
|
/** Pick which component to be rendered. */
|
||||||
component?: keyof typeof INPUT_COMPONENTS
|
component?: keyof typeof INPUT_COMPONENTS | Component
|
||||||
/** Hide `FormLabel`. */
|
/** Hide `FormLabel`. */
|
||||||
hideLabel?: boolean
|
hideLabel?: boolean
|
||||||
inputProps?: InputHTMLAttributes
|
inputProps?: InputHTMLAttributes
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "AutoFormField.vue",
|
"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"
|
"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 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",
|
"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-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"
|
"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 '@/lib/registry/default/ui/accordion'\nimport { Button } from '@/lib/registry/default/ui/button'\nimport { Separator } from '@/lib/registry/default/ui/separator'\nimport { FormItem, 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\" as-child>\n <FormItem>\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 </FormItem>\n </Accordion>\n </slot>\n </FieldArray>\n</template>\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "AutoFormFieldBoolean.vue",
|
"name": "AutoFormFieldBoolean.vue",
|
||||||
|
|
@ -72,19 +72,19 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dependencies.ts",
|
"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"
|
"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 // parsed test[0].age => test.age\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 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 [_targetLast, ...targetInitial] = (dep.targetField as string).split('.').toReversed()\n\n if (index >= 0 && sourceInitial.join(',') === targetInitial.join(',')) {\n const [_currentLast, ...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 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",
|
"name": "index.ts",
|
||||||
"content": "export { getObjectFormSchema, getBaseSchema, getBaseType } from './utils'\nexport type { Config, ConfigItem } from './interface'\n\nexport { default as AutoForm } from './AutoForm.vue'\nexport { default as AutoFormField } from './AutoFormField.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"
|
"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",
|
"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"
|
"content": "import type { Component, 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 | Component\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",
|
"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"
|
"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\nfunction isIndex(value: unknown): value is number {\n return Number(value) >= 0\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 if (!pathArr.length)\n return ''\n\n let fullPath = String(pathArr[0])\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 * Checks if the path opted out of nested fields using `[fieldName]` syntax\n */\nexport function isNotNestedPath(path: string) {\n return /^\\[.+\\]$/i.test(path)\n}\nfunction isObject(obj: unknown): obj is Record<string, unknown> {\n return obj !== null && !!obj && typeof obj === 'object' && !Array.isArray(obj)\n}\nfunction isContainerValue(value: unknown): value is Record<string, unknown> {\n return isObject(value) || Array.isArray(value)\n}\nfunction cleanupNonNestedPath(path: string) {\n if (isNotNestedPath(path))\n return path.replace(/\\[|\\]/gi, '')\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\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 if (isNotNestedPath(path))\n return object[cleanupNonNestedPath(path)] as TValue | undefined\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 return fallback\n }, object as unknown)\n\n return resolvedValue as TValue | undefined\n}\n"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "components:ui"
|
"type": "components:ui"
|
||||||
|
|
|
||||||
|
|
@ -28,11 +28,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "AutoFormField.vue",
|
"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"
|
"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 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",
|
"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 '@radix-icons/vue'\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/new-york/ui/accordion'\nimport { Button } from '@/lib/registry/new-york/ui/button'\nimport { Separator } from '@/lib/registry/new-york/ui/separator'\nimport { FormMessage } from '@/lib/registry/new-york/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 />\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\" />\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 } 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/new-york/ui/accordion'\nimport { Button } from '@/lib/registry/new-york/ui/button'\nimport { Separator } from '@/lib/registry/new-york/ui/separator'\nimport { FormItem, FormMessage } from '@/lib/registry/new-york/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\" as-child>\n <FormItem>\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 </FormItem>\n </Accordion>\n </slot>\n </FieldArray>\n</template>\n"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "AutoFormFieldBoolean.vue",
|
"name": "AutoFormFieldBoolean.vue",
|
||||||
|
|
@ -72,19 +72,19 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dependencies.ts",
|
"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"
|
"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 // parsed test[0].age => test.age\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 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 [_targetLast, ...targetInitial] = (dep.targetField as string).split('.').toReversed()\n\n if (index >= 0 && sourceInitial.join(',') === targetInitial.join(',')) {\n const [_currentLast, ...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 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",
|
"name": "index.ts",
|
||||||
"content": "export { default as AutoForm } from './AutoForm.vue'\nexport { default as AutoFormField } from './AutoFormField.vue'\n\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"
|
"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",
|
"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"
|
"content": "import type { Component, 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 | Component\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",
|
"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"
|
"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\nfunction isIndex(value: unknown): value is number {\n return Number(value) >= 0\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 if (!pathArr.length)\n return ''\n\n let fullPath = String(pathArr[0])\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 * Checks if the path opted out of nested fields using `[fieldName]` syntax\n */\nexport function isNotNestedPath(path: string) {\n return /^\\[.+\\]$/i.test(path)\n}\nfunction isObject(obj: unknown): obj is Record<string, unknown> {\n return obj !== null && !!obj && typeof obj === 'object' && !Array.isArray(obj)\n}\nfunction isContainerValue(value: unknown): value is Record<string, unknown> {\n return isObject(value) || Array.isArray(value)\n}\nfunction cleanupNonNestedPath(path: string) {\n if (isNotNestedPath(path))\n return path.replace(/\\[|\\]/gi, '')\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\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 if (isNotNestedPath(path))\n return object[cleanupNonNestedPath(path)] as TValue | undefined\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 return fallback\n }, object as unknown)\n\n return resolvedValue as TValue | undefined\n}\n"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "components:ui"
|
"type": "components:ui"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user