From 3b5e1db2aa53befb2ee0536b5015e2d10c74fc12 Mon Sep 17 00:00:00 2001 From: CJ <72030708+cj12312021@users.noreply.github.com> Date: Mon, 5 Jan 2026 18:12:59 -0600 Subject: [PATCH] feat(ui): make CustomFieldsInput patchable via PluginApi (#6468) Wrap the CustomFieldsInput component with PatchComponent to allow plugins to modify custom field input behavior. This enables plugins to inject default fields, modify the onChange handler, or customize the component rendering. --- .../src/components/Shared/CustomFields.tsx | 246 +++++++++--------- ui/v2.5/src/docs/en/Manual/UIPluginApi.md | 1 + 2 files changed, 123 insertions(+), 124 deletions(-) diff --git a/ui/v2.5/src/components/Shared/CustomFields.tsx b/ui/v2.5/src/components/Shared/CustomFields.tsx index e7355df66..c8d389a17 100644 --- a/ui/v2.5/src/components/Shared/CustomFields.tsx +++ b/ui/v2.5/src/components/Shared/CustomFields.tsx @@ -189,136 +189,134 @@ interface ICustomFieldsInput { setError: (error?: string) => void; } -export const CustomFieldsInput: React.FC = ({ - values, - error, - onChange, - setError, -}) => { - const intl = useIntl(); +export const CustomFieldsInput: React.FC = PatchComponent( + "CustomFieldsInput", + ({ values, error, onChange, setError }) => { + const intl = useIntl(); - const [newCustomField, setNewCustomField] = useState({ - field: "", - value: "", - }); + const [newCustomField, setNewCustomField] = useState({ + field: "", + value: "", + }); - const fields = useMemo(() => { - const valueCopy = cloneDeep(values); - if (newCustomField.field !== "" && error === undefined) { - delete valueCopy[newCustomField.field]; + const fields = useMemo(() => { + const valueCopy = cloneDeep(values); + if (newCustomField.field !== "" && error === undefined) { + delete valueCopy[newCustomField.field]; + } + + const ret = Object.keys(valueCopy); + ret.sort(); + return ret; + }, [values, newCustomField, error]); + + function onSetNewField(v: ICustomField) { + // validate the field name + let newError = undefined; + if (v.field.length > maxFieldNameLength) { + newError = intl.formatMessage({ + id: "errors.custom_fields.field_name_length", + }); + } + if (v.field.trim() === "" && v.value !== "") { + newError = intl.formatMessage({ + id: "errors.custom_fields.field_name_required", + }); + } + if (v.field.trim() !== v.field) { + newError = intl.formatMessage({ + id: "errors.custom_fields.field_name_whitespace", + }); + } + if (fields.includes(v.field)) { + newError = intl.formatMessage({ + id: "errors.custom_fields.duplicate_field", + }); + } + + const oldField = newCustomField; + + setNewCustomField(v); + + const valuesCopy = cloneDeep(values); + if (oldField.field !== "" && error === undefined) { + delete valuesCopy[oldField.field]; + } + + // if valid, pass up + if (!newError && v.field !== "") { + valuesCopy[v.field] = v.value; + } + + onChange(valuesCopy); + setError(newError); } - const ret = Object.keys(valueCopy); - ret.sort(); - return ret; - }, [values, newCustomField, error]); - - function onSetNewField(v: ICustomField) { - // validate the field name - let newError = undefined; - if (v.field.length > maxFieldNameLength) { - newError = intl.formatMessage({ - id: "errors.custom_fields.field_name_length", - }); - } - if (v.field.trim() === "" && v.value !== "") { - newError = intl.formatMessage({ - id: "errors.custom_fields.field_name_required", - }); - } - if (v.field.trim() !== v.field) { - newError = intl.formatMessage({ - id: "errors.custom_fields.field_name_whitespace", - }); - } - if (fields.includes(v.field)) { - newError = intl.formatMessage({ - id: "errors.custom_fields.duplicate_field", - }); + function onAdd() { + const newValues = { + ...values, + [newCustomField.field]: newCustomField.value, + }; + setNewCustomField({ field: "", value: "" }); + onChange(newValues); } - const oldField = newCustomField; - - setNewCustomField(v); - - const valuesCopy = cloneDeep(values); - if (oldField.field !== "" && error === undefined) { - delete valuesCopy[oldField.field]; + function fieldChanged( + currentField: string, + newField: string, + value: unknown + ) { + let newValues = cloneDeep(values); + delete newValues[currentField]; + if (newField !== "") { + newValues[newField] = value; + } + onChange(newValues); } - // if valid, pass up - if (!newError && v.field !== "") { - valuesCopy[v.field] = v.value; - } - - onChange(valuesCopy); - setError(newError); - } - - function onAdd() { - const newValues = { - ...values, - [newCustomField.field]: newCustomField.value, - }; - setNewCustomField({ field: "", value: "" }); - onChange(newValues); - } - - function fieldChanged( - currentField: string, - newField: string, - value: unknown - ) { - let newValues = cloneDeep(values); - delete newValues[currentField]; - if (newField !== "") { - newValues[newField] = value; - } - onChange(newValues); - } - - return ( - - - - - - - - - - - - {fields.map((field) => ( - - fieldChanged(field, newField, newValue) - } - /> - ))} - onSetNewField({ field, value })} - isNew - /> - - - - - ); -}; + + + + + + + + + + + {fields.map((field) => ( + + fieldChanged(field, newField, newValue) + } + /> + ))} + onSetNewField({ field, value })} + isNew + /> + + + + + ); + } +); diff --git a/ui/v2.5/src/docs/en/Manual/UIPluginApi.md b/ui/v2.5/src/docs/en/Manual/UIPluginApi.md index 67d837ed7..e1347a46f 100644 --- a/ui/v2.5/src/docs/en/Manual/UIPluginApi.md +++ b/ui/v2.5/src/docs/en/Manual/UIPluginApi.md @@ -223,6 +223,7 @@ Returns `void`. - `CountrySelect` - `CustomFieldInput` - `CustomFields` +- `CustomFieldsInput` - `DateInput` - `DetailImage` - `ExternalLinkButtons`