packages/antd/src/form-step/index.tsx (155 lines of code) (raw):
import React, { Fragment } from 'react'
import { define, observable, action, markRaw, model } from '@formily/reactive'
import { Steps } from 'antd'
import cls from 'classnames'
import { StepsProps, StepProps } from 'antd/lib/steps'
import { Form, VoidField } from '@formily/core'
import {
connect,
useField,
observer,
useFieldSchema,
RecursionField,
} from '@formily/react'
import { Schema, SchemaKey } from '@formily/json-schema'
import { usePrefixCls } from '../__builtins__'
export interface IFormStep {
connect: (steps: SchemaStep[], field: VoidField) => void
current: number
allowNext: boolean
allowBack: boolean
setCurrent(key: number): void
submit: Form['submit']
next(): void
back(): void
}
export interface IFormStepProps extends StepsProps {
formStep?: IFormStep
}
type ComposedFormStep = React.FC<React.PropsWithChildren<IFormStepProps>> & {
StepPane: React.FC<React.PropsWithChildren<StepProps>>
createFormStep: (defaultCurrent?: number) => IFormStep
}
type SchemaStep = {
name: SchemaKey
props: any
schema: Schema
}
type FormStepEnv = {
form: Form
field: VoidField
steps: SchemaStep[]
}
const parseSteps = (schema: Schema) => {
const steps: SchemaStep[] = []
schema.mapProperties((schema, name) => {
if (schema['x-component']?.indexOf('StepPane') > -1) {
steps.push({
name,
props: schema['x-component-props'],
schema,
})
}
})
return steps
}
const createFormStep = (defaultCurrent = 0): IFormStep => {
const env: FormStepEnv = define(
{
form: null,
field: null,
steps: [],
},
{
form: observable.ref,
field: observable.ref,
steps: observable.shallow,
}
)
const setDisplay = action.bound((target: number) => {
const currentStep = env.steps[target]
env.steps.forEach(({ name }) => {
env.form.query(`${env.field.address}.${name}`).take((field) => {
if (name === currentStep.name) {
field.setDisplay('visible')
} else {
field.setDisplay('hidden')
}
})
})
})
const next = action.bound(() => {
if (formStep.allowNext) {
formStep.setCurrent(formStep.current + 1)
}
})
const back = action.bound(() => {
if (formStep.allowBack) {
formStep.setCurrent(formStep.current - 1)
}
})
const formStep: IFormStep = model({
connect(steps, field) {
env.steps = steps
env.form = field?.form
env.field = field
},
current: defaultCurrent,
setCurrent(key: number) {
setDisplay(key)
formStep.current = key
},
get allowNext() {
return formStep.current < env.steps.length - 1
},
get allowBack() {
return formStep.current > 0
},
async next() {
try {
await env.form.validate()
if (env.form.valid) {
next()
}
} catch {}
},
async back() {
back()
},
async submit(onSubmit) {
return env.form?.submit?.(onSubmit)
},
})
return markRaw(formStep)
}
export const FormStep = connect(
observer(({ formStep, className, ...props }: IFormStepProps) => {
const field = useField<VoidField>()
const prefixCls = usePrefixCls('formily-step', props)
const schema = useFieldSchema()
const steps = parseSteps(schema)
const current = props.current || formStep?.current || 0
formStep?.connect?.(steps, field)
return (
<div className={cls(prefixCls, className)}>
<Steps
{...props}
style={{ marginBottom: 10, ...props.style }}
current={current}
>
{steps.map(({ props }, key) => {
return <Steps.Step {...props} key={key} />
})}
</Steps>
{steps.map(({ name, schema }, key) => {
if (key !== current) return
return <RecursionField key={key} name={name} schema={schema} />
})}
</div>
)
})
) as unknown as ComposedFormStep
const StepPane: React.FC<React.PropsWithChildren<StepProps>> = ({
children,
}) => {
return <Fragment>{children}</Fragment>
}
FormStep.StepPane = StepPane
FormStep.createFormStep = createFormStep
export default FormStep