# Form

表单组件，基于 react-final-form 封装，提供表单状态管理和符合规范的 Field 结构与样式。Form 是 uncontrolled 型组件，通过 render props 提供操作 API。

## 适用场景

- 标准表单录入（新建、编辑）
- 需要字段级或表单级校验
- 需要读取表单状态（submitting、dirty 等）控制 UI
- 纯文本只读表单（staticOnly 模式）

## Props

### Form Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| staticOnly | `boolean` | `false` | 否 | 是否使用纯文本的行间距（全静态只读表单） |
| validate | `(values: FormValues) => FormErrors \| Promise<FormErrors>` | `-` | 否 | 表单级（formLevel）校验函数 |
| validateOnBlur | `boolean` | `false` | 否 | 是否在 blur 时才校验（false 时 change 即触发校验） |
| subscription | `FormSubscription` | `-` | 否 | FormState 触发 render 的订阅字段，不配置则所有字段变化都触发 render |
| onSubmit | `(values, finalForm) => FormErrors \| Promise<FormErrors \| undefined> \| void` | `-` | 否 | submit 校验通过后的回调，返回 undefined 表示成功，返回 formErrors 表示失败 |
| finalForm | `FormApi` | `-` | 否 | 自行构造的 finalForm 实例，提供后其他 FinalForm.FormConfig 相关 props 均忽略 |
| children | `((formRenderProps: FormRenderProps) => ReactNode) \| ReactNode` | `-` | 否 | Form 主体 render 函数或内容 |
| actions | `((formRenderProps: FormRenderProps) => ReactNode) \| ReactNode` | `-` | 否 | 操作区 render 函数或内容 |
| size | `"md" \| "sm"` | `-` | 否 | 尺寸 |
| labelPlacement | `"left" \| "top"` | `"left"` | 否 | 标签相对控件的位置 |
| labelAlign | `"left" \| "right"` | `"right"` | 否 | 标签的对齐方式 |
| labelWidth | `Width<string \| number>` | `-` | 否 | 标签的宽度 |
| controlWidth | `Width<string \| number>` | `-` | 否 | 控件宽度 |

### Field Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| name | `string` | `-` | 是 | 字段标识 |
| labelWidth | `Width<string \| number>` | `-` | 否 | 标签宽度，设为 0 将隐藏 label |
| controlWidth | `Width<string \| number>` | `-` | 否 | 控件宽度 |
| label | `ReactNode` | `-` | 否 | 字段标签 |
| required | `boolean` | `false` | 否 | 是否必填（仅影响标签前必填标记，不负责校验） |
| help | `ReactNode` | `-` | 否 | 标签中的问号 tips 内容 |
| hint | `ReactNode` | `-` | 否 | 静态提示信息；设为 false 禁用提示 |
| initialValue | `Val` | `-` | 否 | 字段初始值 |
| validate | `(value, formValues, fieldState) => FormValidationResult \| Promise<FormValidationResult>` | `-` | 否 | 字段级校验函数，返回 undefined 为通过，返回字符串为错误信息 |
| validateFields | `string[]` | `-` | 否 | 触发此字段校验时，同时触发其他字段校验的字段名列表 |
| subscription | `StringIndexedObject<boolean>` | `-` | 否 | FieldState 触发 render 的订阅字段 |
| children | `ReactNode \| ((fieldRenderProps: FieldRenderProps) => ReactNode)` | `-` | 否 | 接收 FieldRenderProps 的 render 函数 |
| hidden | `boolean` | `-` | 否 | 是否为隐藏字段（不渲染 DOM，但保留字段状态） |
| type | `"checkbox"` | `-` | 否 | 字段值类型，设为 checkbox 使 control 包含 checked 值 |
| isEqual | `(a: any, b: any) => boolean` | `-` | 否 | 判断两值是否相等的函数，影响 fieldState.dirty 判断 |

### FieldRenderProps Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| value | `Val` | `-` | 是 | 字段的 value |
| change | `(event: any) => void` | `-` | 是 | 触发字段值变化，同 `control.onChange` |
| focus | `(event?) => void` | `-` | 是 | 触发字段 focus，同 `control.onFocus` |
| blur | `(event?) => void` | `-` | 是 | 触发字段 blur，同 `control.onBlur` |
| control | `FieldControlProps` | `-` | 是 | 可直接展开给控件的 api 对象 `{value, name, onChange, onFocus, onBlur, size}` |
| fieldState | `FieldState` | `-` | 是 | 字段状态（touched、invalid、dirty 等） |

### FieldStatic Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |
| title | `string` | `-` | 否 | native title 提示 |

### FormActions Props

| 名称 | 类型 | 默认值 | 必填 | 说明 |
|------|------|--------|------|------|
| className | `string` | `""` | 否 | 自定义 class |

## 典型用法

### 基础表单

```tsx
import { useRef } from 'react'
import { Input } from '@befe/brick-comp-input'
import { Button } from '@befe/brick-comp-button'
import { Form, Field, FormActions, SubmitHandler } from '@befe/brick-comp-form'

function Demo() {
    const refForm = useRef<Form>(null)

    const submit = () => {
        refForm.current?.submit((values) => {
            console.log('values', values)
        })
    }

    return (
        <Form ref={refForm}>
            {() => (
                <>
                    <Field name={'username'} label={'用户名'} required>
                        {({ control }) => <Input {...control} />}
                    </Field>
                    <Field name={'phone'} label={'手机号'}>
                        {({ value, change }) => <Input value={value} onChange={change} />}
                    </Field>
                    <FormActions>
                        <Button onClick={submit} type={'important'}>提交</Button>
                    </FormActions>
                </>
            )}
        </Form>
    )
}
```

### 字段校验

```tsx
<Field
    name={'email'}
    label={'邮箱'}
    required
    validate={(value: string) => {
        if (!value) return '必填'
        if (!value.includes('@')) return '邮箱格式不正确'
        return undefined
    }}
>
    {({ control, fieldState }) => (
        <Input
            {...control}
            status={fieldState.touched && fieldState.invalid ? 'error' : 'normal'}
        />
    )}
</Field>
```

### 标签置顶布局

```tsx
<Form labelPlacement={'top'}>
    {() => (
        <>
            <Field name={'name'} label={'姓名'}>
                {({ control }) => <Input {...control} />}
            </Field>
        </>
    )}
</Form>
```

### 静态只读表单

```tsx
import { Form, Field, FieldStatic } from '@befe/brick-comp-form'

<Form staticOnly controlWidth={180}>
    {() => (
        <Field name={'status'} label={'状态'}>
            {() => <FieldStatic>已审批</FieldStatic>}
        </Field>
    )}
</Form>
```

### 命令式 submit / reset / initialize

```tsx
const refForm = useRef<Form>(null)

// 提交
refForm.current?.submit(handleSubmit)

// 重置到初始值（可同时设置新初始值）
refForm.current?.reset({ username: 'new_default' })

// 设置初始值（不重置当前值）
refForm.current?.initialize({ field_1: 100 })
```

## 注意事项

- Form 是 **uncontrolled** 型组件，不通过 state 驱动字段值，而是通过 react-final-form 内部管理
- `Field.validate` 支持行内箭头函数，但仅改变此 prop 不会引起 field 重新注册校验函数；如需重新注册，请同时变更其他 prop（如 key）
- `validateFields` 同理，仅改变此 prop 不会触发重新注册
- `RangePicker` 等 props API 不标准（非 `value / onChange(value)` 形式）的控件，不能直接 `{...control}` 展开，需手动适配
- 纯文本只读表单须设置 `staticOnly={true}` 以使用正确的行间距样式，不要直接用普通 Form
- `hint={false}` 可禁用字段的提示信息区，适用于控件自行提示的情况
