import { useCallback, useEffect, useImperativeHandle, useRef } from 'react'; import { useField, Validators, FieldModel, ValidateOption, IValidator, IValidators, FieldUtils, isModelRef, ModelRef, } from './formulr'; import { defaultRenderError, IFormFieldProps, useFormChild, isViewDrivenProps, ValidateOccasion, TouchWhen, } from './shared'; import { FormControl } from './Control'; import { FormNotice } from './Notice'; import { FormDescription } from './Description'; import { $MergeParams } from './utils'; import id from '../utils/identity'; import { useComponentI18nData } from '../i18n/useComponentI18n'; import { II18nLocaleForm } from '../i18n'; export { IFormFieldChildProps, IFormFieldProps } from './shared'; export function defaultGetValidateOption() { return ValidateOption.Default; } function withDefaultOption(option: ValidateOption | null | undefined) { return option ?? ValidateOption.Default; } /** * 为 model 设置初始值,初始值会被作为 effect 的依赖,谨慎使用字面量 * @param model * @param initialValue */ export function useInitialValue(model: FieldModel, initialValue?: T) { useEffect(() => { if (initialValue !== undefined) { model.initialize(initialValue); } }, [model, initialValue]); } function getValidators( { validators, required }: IFormFieldProps, i18n: II18nLocaleForm ) { validators = validators ?? []; if ( required && !validators.some( it => (it as $MergeParams>).$$id === Validators.SYMBOL_REQUIRED ) ) { validators = ( [ Validators.required( typeof required === 'string' ? required : i18n.required ), ] as IValidators ).concat(validators); } return validators; } export function FormField(props: IFormFieldProps) { let model: FieldModel; const i18n = useComponentI18nData('Form'); if (isViewDrivenProps(props)) { const { name, defaultValue, destroyOnUnmount, normalizeBeforeSubmit } = props; // eslint-disable-next-line react-hooks/rules-of-hooks model = useField(name, defaultValue, getValidators(props, i18n)); model.destroyOnUnmount = Boolean(destroyOnUnmount); if (typeof normalizeBeforeSubmit === 'function') { model.normalizeBeforeSubmit = normalizeBeforeSubmit; } } else if (isModelRef(props.model)) { // eslint-disable-next-line react-hooks/rules-of-hooks model = useField( props.model as ModelRef, props.defaultValue, getValidators(props, i18n) ); } else { // eslint-disable-next-line react-hooks/rules-of-hooks model = useField(props.model); } useInitialValue(model, props.initialValue); useImperativeHandle(props.modelRef, () => model, [model]); const { className, style, label, required, before, after, notice, helpDesc, withoutError, renderError = defaultRenderError, children, validateOccasion = ValidateOccasion.Default, getValidateOption = defaultGetValidateOption, normalize = id, normalizeBeforeBlur, format = id, withoutLabel, touchWhen = TouchWhen.Change, } = props; const anchorRef = useRef(null); useFormChild(model, anchorRef); const normalizer = useCallback( (value: Value) => { const prevValue = model.value; return normalize(value, prevValue); }, [model, normalize] ); const setValue = useCallback(value => (model.value = value), [model]); const defaultOnChangeHandler = FieldUtils.useChangeHandler( model, withDefaultOption(getValidateOption('change')), props.onChange ); const onChangeProps = props.onChange; const onChange = FieldUtils.useMulti( () => { if (touchWhen === TouchWhen.Change) { model.isTouched = true; } }, FieldUtils.usePipe( normalizer, ValidateOccasion.Change & validateOccasion ? defaultOnChangeHandler : (value: Value) => { setValue(value); onChangeProps?.(value); } ), [ model, touchWhen, normalizer, validateOccasion, defaultOnChangeHandler, onChangeProps, ] ); const onBlurProps = props.onBlur; const onBlur = useCallback( (e: React.FocusEvent) => { if (touchWhen === TouchWhen.Blur) { model.isTouched = true; } if (normalizeBeforeBlur) { model.value = normalizeBeforeBlur(model.value); } if (validateOccasion & ValidateOccasion.Blur) { model.validate(getValidateOption('blur')); } onBlurProps?.(e); }, [ getValidateOption, validateOccasion, normalizeBeforeBlur, touchWhen, model, onBlurProps, ] ); const { onCompositionStart, onCompositionEnd } = FieldUtils.useCompositionHandler(model, { onCompositionStart: props.onCompositionStart, onCompositionEnd: props.onCompositionEnd, }); return (
{before} {children({ value: format(model.value), onChange, onCompositionStart, onCompositionEnd, onBlur, })} {after}
{withoutError ? null : renderError(model.error)} {!!notice && {notice}} {!!helpDesc && {helpDesc}}
); }