@volverjs/form-vue
Version:
Vue 3 Forms with @volverjs/ui-vue
178 lines (174 loc) • 4.14 kB
text/typescript
import { get } from 'ts-dot-prop'
import {
type Component,
type PropType,
type InjectionKey,
type DeepReadonly,
type Ref,
type VNode,
defineComponent,
h,
inject,
unref,
} from 'vue'
import type { TypeOf, z } from 'zod'
import type { FormSchema, InjectedFormData, FormTemplate } from './types'
import type { FormStatus } from './enums'
export const defineFormTemplate = <Schema extends FormSchema>(
formProvideKey: InjectionKey<InjectedFormData<Schema>>,
VvFormField: Component,
) => {
const VvFormTemplate = defineComponent({
props: {
schema: {
type: [Array, Function] as PropType<FormTemplate<Schema>>,
required: true,
},
},
setup(templateProps, { slots: templateSlots }) {
const injectedFormData = inject(formProvideKey)
if (!injectedFormData?.formData) return
return () => {
const normalizedSchema =
typeof templateProps.schema === 'function'
? templateProps.schema(injectedFormData)
: templateProps.schema
let lastIf: boolean | undefined = undefined
const toReturn = normalizedSchema.reduce<
(VNode | VNode[] | undefined)[]
>((acc, field) => {
const normalizedField =
typeof field === 'function'
? field(injectedFormData)
: field
const {
vvIs,
vvName,
vvSlots,
vvChildren,
vvIf,
vvElseIf,
vvType,
vvDefaultValue,
vvShowValid,
vvContent,
...props
} = normalizedField
// conditions
if (vvIf !== undefined) {
if (typeof vvIf === 'string') {
lastIf = Boolean(
get(
Object(injectedFormData.formData.value),
vvIf,
),
)
} else if (typeof vvIf === 'function') {
lastIf = unref(vvIf(injectedFormData))
} else {
lastIf = unref(vvIf)
}
if (!lastIf) {
return acc
}
} else if (vvElseIf !== undefined && lastIf !== undefined) {
if (lastIf) {
return acc
}
if (typeof vvElseIf === 'string') {
lastIf = Boolean(
get(
Object(injectedFormData.formData.value),
vvElseIf,
),
)
} else if (typeof vvElseIf === 'function') {
lastIf = unref(vvElseIf(injectedFormData))
} else {
lastIf = unref(vvElseIf)
}
if (!lastIf) {
return acc
}
} else {
lastIf = undefined
}
// children
const hChildren = vvChildren
? h(VvFormTemplate, {
schema: vvChildren,
})
: undefined
// render
if (vvName) {
acc.push(
h(
VvFormField,
{
name: vvName,
is: vvIs,
type: vvType,
defaultValue: vvDefaultValue,
showValid: vvShowValid,
props,
},
vvSlots ?? hChildren ?? vvContent,
),
)
return acc
}
if (vvIs) {
acc.push(
h(
vvIs as Component,
props,
vvSlots ?? hChildren ?? vvContent,
),
)
return acc
}
if (vvChildren) {
acc.push(hChildren)
return acc
}
return acc
}, [])
toReturn.push(
templateSlots?.default?.({
formData: injectedFormData?.formData.value,
submit: injectedFormData?.submit,
validate: injectedFormData?.validate,
errors: injectedFormData?.errors.value,
status: injectedFormData?.status.value,
invalid: injectedFormData?.invalid.value,
}),
)
return toReturn
}
},
})
/**
* An hack to add types to the default slot
*/
return VvFormTemplate as typeof VvFormTemplate & {
new (): {
$slots: {
default: (_: {
formData: unknown extends
| Partial<TypeOf<Schema>>
| undefined
? undefined
: Partial<TypeOf<Schema>> | undefined
submit: () => boolean
validate: () => boolean
errors: Readonly<
Ref<DeepReadonly<z.inferFormattedError<Schema>>>
>
status: Ref<DeepReadonly<`${FormStatus}` | undefined>>
invalid: Ref<DeepReadonly<boolean>>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
}) => any
}
}
}
}