import React, { useState, useImperativeHandle, useMemo } from 'react'; import { IProps } from '@uiw/utils'; import FormItem, { FormItemProps } from './FormItem'; import './style/form.less'; export interface FormProps extends IProps, Omit, 'onChange' | 'onSubmit' | 'children'> { prefixCls?: string; fields?: Record>; onSubmit?: (state: FormSubmitProps, event: React.FormEvent) => any; afterSubmit?: (result: FormAfterSubmitProps) => any; onChange?: (state: FormState) => void; onSubmitError?: (evn: any) => any; resetOnSubmit?: boolean; children?: (handle: FormChildrenProps) => JSX.Element; } export interface FormState { submitting: boolean; initial: Record; current: FormState['initial']; errors: Record; } export interface FormFieldsProps extends Omit, 'children'> { name?: string; children?: ((handle: FormChildrenProps) => JSX.Element) | JSX.Element; help?: React.ReactNode; labelFor?: string; inline?: boolean; checked?: boolean; initialValue?: string | number | T; required?: boolean; validator?: (currentValue: any) => any; } export interface FormSubmitProps { initial: FormState['initial']; current: FormState['current']; } export interface FormAfterSubmitProps { state: FormState; response: any; reset: () => void; } export interface FormChildrenProps { onChange?: (env: React.BaseSyntheticEvent, list?: string[]) => void; onSubmit?: (env: React.FormEvent) => void; fields?: Record; resetForm?: () => void; canSubmit?: () => boolean; state?: FormState; } export type FormElementProps = { id?: string; name?: string; value?: string | boolean; checked?: boolean; onChange?: (env: React.BaseSyntheticEvent, list?: string[]) => void; }; export type FormRefType = Record< 'onSubmit' | 'resetForm' | 'getFieldValues' | 'setFields' | 'getError' | 'setFieldValue', Function >; function newFormState( fields: FormProps['fields'], cb: (porps: FormFieldsProps) => { initialValue: FormFieldsProps['initialValue']; currentValue: FormFieldsProps['initialValue']; }, ): FormState { const state: FormState = { initial: {}, current: {}, submitting: false, errors: {}, }; for (const name in fields) { const props = fields[name]; if (!props) continue; const { initialValue, currentValue } = cb({ ...props, name }); state.initial[name] = Array.isArray(initialValue) ? [...initialValue] : initialValue; state.current[name] = currentValue; } return state; } function newInitialValue(value: FormFieldsProps['initialValue']) { return value === null || value === undefined ? '' : value; } const isPromise = (promise: Promise) => promise && typeof promise.then === 'function'; function Form(props: FormProps, ref: React.ForwardedRef) { const { prefixCls = 'w-form', className, fields, children, resetOnSubmit, onSubmitError, onChange, onSubmit, afterSubmit, ...others } = props; const initData = useMemo( () => newFormState(fields, ({ initialValue }) => { initialValue = newInitialValue(initialValue); return { initialValue, currentValue: initialValue }; }), [], ); const [data, setData] = useState(initData); useImperativeHandle( ref, () => ({ onSubmit: handleSubmit, resetForm: handleReset, getFieldValues: () => data.current, getError: () => data.errors, setFields: setFields, setFieldValue: setFieldValue, }), [data], ); const formUnits: FormChildrenProps['fields'] = {}; for (const name in fields) { const itemProps = fields[name]; if (!itemProps) continue; const error = data.errors[name]; if (typeof itemProps.initialValue === 'boolean') { itemProps.checked = itemProps.initialValue; } const childField: any = controlField({ ...itemProps, name }); const help = error || itemProps.help; const labelFor = itemProps.labelFor; formUnits[name] = ( ); } function setFields(fields: FormState['current']) { const tempData = { ...data, current: fields }; setData(tempData); } function setFieldValue(fieldName: string, value: V) { const tempData = { ...data, current: { ...data.current, [fieldName]: value } }; setData(tempData); } function handleChange( name: string, validator: FormFieldsProps['validator'], element?: React.ReactElement, cb?: (env: React.BaseSyntheticEvent) => void, ) { return (env: React.BaseSyntheticEvent, list?: string[]) => { let value = env && env.target && 'value' in env.target ? env.target.value : env; // 控件 Checkbox.Group 多选值的处理 value = list || value; // 控件 Checkbox 值的处理 if (!list && element && env && env.target && /(radio)/.test(env.target.type)) { // 控件 Switch/Radio/Checkbox 值的处理 value = env.target.value ? env.target.value : env.target.checked; } if (!list && element && env && env.target && /(checkbox)/.test(env.target.type)) { // 控件 Switch/Radio/Checkbox 值的处理 value = env.target.checked; } const nextState = { current: { ...data.current, [name]: value }, } as FormState; const error = validator && validator(value); if (!error) { nextState.errors = { ...data.errors }; delete nextState.errors[name]; } if (env && env.persist && typeof env.persist === 'function') env.persist(); setData({ ...data, ...nextState }); if (cb) { cb(env); } onChange && onChange({ ...data, ...nextState }); }; } function handleSubmit(e: React.FormEvent) { e && e.preventDefault(); const { initial, current } = data; setData({ ...data, submitting: true }); const nextState = { submitting: false } as FormState; const onError = (evn: unknown) => setData({ ...data, ...nextState, errors: (onSubmitError && onSubmitError(evn)) || {}, }); const onSuccess = (response: any) => { if (resetOnSubmit) { nextState.current = initial; } setData({ ...data, ...nextState, errors: {} }); afterSubmit && afterSubmit({ state: data, response, reset: handleReset }); }; try { const afterSubmitPromise = onSubmit ? onSubmit({ initial, current }, e) : undefined; if (afterSubmitPromise && isPromise(afterSubmitPromise)) { return afterSubmitPromise.then(onSuccess).catch(onError); } else { return onSuccess(afterSubmitPromise); } } catch (evn) { onError(evn); } } function canSubmit() { const { submitting, current = {} } = data; let passesValidators = true; for (const name in fields) { if (Object.prototype.hasOwnProperty.call(fields, name)) { const fieldsProps: FormFieldsProps = fields[name]; if (!fieldsProps) continue; if (fieldsProps.validator && fieldsProps.validator(current[name])) { passesValidators = false; break; } } } return !submitting && passesValidators; } function handleReset() { let { initial } = data; const initials = { ...initial }; Object.entries(initials).map(([key, value]) => { if (Array.isArray(value)) { initials[key] = [...value]; } }); setData({ ...data, initial, current: initials, errors: {} }); } function controlField({ children, validator, name, help, label, labelFor, labelClassName, labelStyle, inline, initialValue, ...other }: FormFieldsProps) { const element = typeof children !== 'function' ? children : children({ onChange: handleChange(name!, validator), onSubmit: handleSubmit, canSubmit: canSubmit, }); if (!element || React.Children.count(element) !== 1 || !name) return element; const props = { name: element.props.name || name, ...other, } as FormElementProps; const hasCurrentValue = Object.prototype.hasOwnProperty.call(data.current, name); props.id = element.props.id; props.value = hasCurrentValue ? data.current && data.current[name] : props.value; // : element.props.value; const type = element.props.type; if (type === 'checkbox' || type === 'switch' || typeof props.value === 'boolean') { props.checked = !!props.value; delete props.value; } props.onChange = handleChange(name, validator, element, element.props.onChange) as FormElementProps['onChange']; return React.cloneElement(element, props as FormElementProps); } return (
{typeof children === 'function' ? children({ fields: formUnits, state: data, resetForm: handleReset, canSubmit: canSubmit, }) : children}
); } export default React.forwardRef>(Form);