/* * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ import React, { forwardRef, useRef, useImperativeHandle, useEffect, useMemo } from "react"; import { EuiForm, EuiFormProps, EuiSpacer } from "@elastic/eui"; import { isEqual, omit, pick } from "lodash"; import AllBuiltInComponents, { IFieldComponentProps } from "./built_in_components"; import useField, { InitOption, FieldOption, Rule, FieldInstance, FieldName, transformNameToString } from "../../lib/field"; import AdvancedSettings, { IAdvancedSettingsProps, IAdvancedSettingsRef } from "../AdvancedSettings"; import CustomFormRow, { CustomFormRowProps } from "../CustomFormRow"; export * from "./built_in_components"; type ParametersOfValidator = Parameters["validator"]>; interface IRule extends Omit { validator?: (rule: ParametersOfValidator[0], value: ParametersOfValidator[1], values: Record) => Promise; } interface IInitOption extends Omit { rules?: IRule[]; } interface IFormGeneratorAdvancedSettings extends IAdvancedSettingsProps { blockedNameList?: string[]; } export interface IField { rowProps: Omit; name: FieldName; type?: keyof typeof AllBuiltInComponents; component?: React.ComponentType; options?: Omit; } export interface IFormGeneratorProps { formFields: IField[]; resetValuesWhenPropsValueChange?: boolean; hasAdvancedSettings?: boolean; advancedSettingsProps?: IFormGeneratorAdvancedSettings; fieldProps?: FieldOption; formProps?: EuiFormProps; value?: T; onChange?: (totalValue: IFormGeneratorProps["value"], key?: FieldName, value?: any) => void; } export interface IFormGeneratorRef extends FieldInstance {} export { AllBuiltInComponents }; function FormGenerator(props: IFormGeneratorProps, ref: React.Ref) { const { fieldProps, formFields, advancedSettingsProps } = props; const { blockedNameList } = advancedSettingsProps || {}; const propsRef = useRef(props); const advancedRef = useRef(null); propsRef.current = props; const field = useField({ ...fieldProps, onChange(name: FieldName, value: any) { propsRef.current.onChange && propsRef.current.onChange({ ...field.getValues() }, name, value); }, }); const errorMessage: Record = field.getErrors(); useImperativeHandle(ref, () => ({ ...field, validatePromise: async () => { const result = await field.validatePromise(); if (advancedRef.current?.validate) { try { await advancedRef.current.validate(); } catch (e: any) { result.errors = result.errors || {}; result.errors._advancedSettings = [e]; } } return result; }, })); useEffect(() => { if (!isEqual(field.getValues(), props.value)) { if (propsRef.current.resetValuesWhenPropsValueChange) { field.resetValues(props.value as T); } else { field.setValues(props.value as T); } } }, [props.value]); const formattedFormFields = useMemo(() => { return formFields.map((item) => { const { rules } = item.options || {}; let arrayedRules: IRule[]; if (rules) { arrayedRules = rules; } else { arrayedRules = []; } const formattedRules = arrayedRules.map((ruleItem) => { if (ruleItem.validator) { return { ...ruleItem, validator: (rule: Rule, value: any) => ruleItem.validator?.apply(field, [rule, value, field.getValues()] as any) as any, }; } return ruleItem; }); return { ...item, options: { ...item.options, rules: formattedRules, }, }; }); }, [formFields, field]); const finalValue = useMemo(() => { const value = field.getValues(); if (!blockedNameList) { return field.getValues(); } return omit(value, blockedNameList); }, [field.getValues(), blockedNameList]); return ( {formattedFormFields.map((item) => { const RenderComponent = item.type ? AllBuiltInComponents[item.type] : item.component || (() => null); return ( ); })} {props.hasAdvancedSettings ? ( <> { const totalValue = field.getValues(); const editorValue = omit(val || {}, blockedNameList || []); const resetValue = { ...editorValue, ...pick(totalValue, blockedNameList || []), } as T; // update form field value to rerender field.resetValues(resetValue); // reset editors value advancedRef.current?.setValue(editorValue); // only validate when value changed if (!isEqual(val, finalValue)) { field.validatePromise(); } propsRef.current.onChange && propsRef.current.onChange(field.getValues(), undefined, editorValue); }} /> ) : null} ); } export default forwardRef(FormGenerator) as ( props: IFormGeneratorProps & { ref?: React.Ref } ) => ReturnType;