From 8faa050126cf4d7f6b2b2ccce04240a6190d05d6 Mon Sep 17 00:00:00 2001 From: hans000 Date: Tue, 2 Jan 2024 22:10:29 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0FormItemRender?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/FormItemRender/index.tsx | 219 + packages/form/src/components/form.md | 114 +- packages/form/src/components/index.ts | 9 + packages/form/src/demos/antd.modify.tsx | 62 + packages/form/src/demos/antd.nest.tsx | 56 + packages/form/src/demos/antd.tsx | 103 + .../form/src/demos/form-control-render.tsx | 81 + packages/form/src/demos/form-item-render.tsx | 164 + tests/form/__snapshots__/demo.test.ts.snap | 4029 +++++++++++------ tests/form/formitemrender.test.tsx | 131 + 10 files changed, 3463 insertions(+), 1505 deletions(-) create mode 100644 packages/form/src/components/FormItemRender/index.tsx create mode 100644 packages/form/src/demos/antd.modify.tsx create mode 100644 packages/form/src/demos/antd.nest.tsx create mode 100644 packages/form/src/demos/antd.tsx create mode 100644 packages/form/src/demos/form-control-render.tsx create mode 100644 packages/form/src/demos/form-item-render.tsx create mode 100644 tests/form/formitemrender.test.tsx diff --git a/packages/form/src/components/FormItemRender/index.tsx b/packages/form/src/components/FormItemRender/index.tsx new file mode 100644 index 000000000000..add3801715af --- /dev/null +++ b/packages/form/src/components/FormItemRender/index.tsx @@ -0,0 +1,219 @@ +import { Form } from 'antd'; +import React from 'react'; +import ProFormItem from '../FormItem'; + +interface ControlPropsType { + id: string; + value: any; + onChange: (value: any, ...args: any[]) => void; +} + +export type WithControlPropsType = T & Partial; + +interface ControlModelType { + value: any; + onChange: (value: any) => void; +} +interface FormControlProps { + valuePropName?: string; + trigger?: string; +} +interface FormControlMultiProps extends FormControlProps { + name: string; +} +type GetArrayFieldType = + T[number]['name']; + +function getControlConfigProps(props = {} as any): { + valuePropName: string; + trigger: string; + name: string; +} { + const valuePropName = props.valuePropName || 'value'; + const trigger = props.trigger || 'onChange'; + const name = props.name; + return { + valuePropName, + trigger, + name, + }; +} + +export function useControlModel( + { value, onChange, id }: WithControlPropsType, + model?: T, +): ControlModelType; +export function useControlModel( + { value, onChange, id }: WithControlPropsType, + model?: T, +): { [P in T[number]]: ControlModelType }; +export function useControlModel< + const T extends readonly FormControlMultiProps[], +>( + { value, onChange, id }: WithControlPropsType, + model?: T, +): { [P in GetArrayFieldType]: ControlModelType }; +export function useControlModel< + T extends + | FormControlProps + | (string | FormControlMultiProps)[] = FormControlProps, +>({ value, onChange }: WithControlPropsType, model?: T): unknown { + if (!Array.isArray(model)) { + const p = getControlConfigProps(model); + return { + [p.valuePropName]: value, + [p.trigger]: (e: any) => { + onChange?.(e?.target ? e.target[p.valuePropName] : e); + }, + }; + } + + return model.reduce((acc, k) => { + const p = getControlConfigProps(k); + const name = p.name || (k as string); + acc[name] = { + [p.valuePropName]: value?.[name], + [p.trigger]: (v: any) => { + onChange?.({ + ...value, + [name]: v?.target ? v.target[p.valuePropName] : v, + }); + }, + }; + return acc; + }, {} as Record) as unknown; +} + +export type FormControlFC

= ( + props: WithControlPropsType

, +) => React.ReactNode; + +type FormControlInjectProps = ReturnType & { + id: string; + value: any; + onChange: (value: any) => void; + [x: string]: any; +}; + +/** + * 用在 ProForm.Item 或 Form.Item 于 表单项之间的,用于劫持渲染的函数 + * ``` tsx + * + * + * { + * formItemProps => ( + *

+ * prefix + * + *
+ * ) + * } + * + * + * ``` + */ +export function FormControlRender( + props: WithControlPropsType<{ + children: (props: FormControlInjectProps) => React.ReactElement; + }>, +) { + const { children, ...restProps } = props; + const { status, errors, warnings } = Form.Item.useStatus(); + return children({ + status, + errors, + warnings, + ...(restProps as ControlPropsType), + }); +} + +/** + * 提取props中的 value 和 onChange 属性 + */ +export function pickControlProps(props: FormControlInjectProps) { + return { + value: props.value, + onChange: (value: any) => + props.onChange(value?.target ? value.target.value : value), + }; +} + +/** + * 提取props中的 value、onChange 和 id 属性 + */ +export function pickControlPropsWithId(props: FormControlInjectProps) { + return { + ...pickControlProps(props), + id: props.id, + }; +} + +/** + * 用于包裹ProForm.Item Form.Item,使其可以使用render props的形式 + * @description 用法 + * const FormItem = withFormItemRender(用于包裹ProForm.Item) + * ``` tsx + * + * { + * formItemProps => ( + *
+ * prefix + * + *
+ * ) + * } + *
+ * ``` + */ +export function withFormItemRender>( + Comp: T, +): React.FC< + Omit, 'children'> & { + children: (formItemProps: FormControlInjectProps) => React.ReactNode; + } +> { + return function (props: React.PropsWithChildren) { + const { children, ...restProps } = props; + + return ( + + {children} + + ); + }; +} + +/** + * 用 withFormItemRender 加工Form.Item,使其拥有render props能力,同时暴露出 status 属性方便自定义组件校验使用 + * + * ``` tsx + * + * { + * formItemProps => ( + *
+ *

prefix

+ *