From a71036a9933788bf2ae899d19df48546f84a7c5d Mon Sep 17 00:00:00 2001 From: martha Date: Thu, 5 Mar 2026 15:12:05 +0100 Subject: [PATCH 1/2] Prevent autofill value from marking field dirty --- src/components/elements/input/NumberInput.tsx | 23 +++++++++++++++---- src/modules/form/components/DynamicField.tsx | 7 ++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/components/elements/input/NumberInput.tsx b/src/components/elements/input/NumberInput.tsx index 322c081a8..fa21e1c57 100644 --- a/src/components/elements/input/NumberInput.tsx +++ b/src/components/elements/input/NumberInput.tsx @@ -1,6 +1,6 @@ import { InputAdornment } from '@mui/material'; import { isFinite, isNil } from 'lodash-es'; -import { ChangeEventHandler, useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import { NumberFormatValues, @@ -8,6 +8,7 @@ import { OnValueChange, } from 'react-number-format'; import TextInput, { TextInputProps } from './TextInput'; +import { ChangeType } from '@/modules/form/types'; import { preventImplicitSubmission } from '@/utils/forms'; // protect from integer overflows @@ -20,8 +21,15 @@ const withValueLimit = ({ floatValue }: NumberFormatValues) => { return true; }; -interface Props extends TextInputProps { - onChange: ChangeEventHandler; +interface Props extends Omit { + onChange: ( + event: React.ChangeEvent, + // Optional eventType argument indicates whether the change was + // triggered by a user or system event (e.g. autofill). + // This is necessary for NumericInput specifically, because NumericFormat + // triggers onChange whenever the value changes, not just when the user types. + eventType?: ChangeType + ) => void; currency?: boolean; } @@ -80,7 +88,7 @@ const NumberInput: React.FC = ({ const decimalScale = currency ? 2 : 0; const prefix = currency ? '$' : undefined; - const handleChange: OnValueChange = (v) => { + const handleChange: OnValueChange = (v, sourceInfo) => { const syntheticEvent = { target: { value: v.value, @@ -91,7 +99,12 @@ const NumberInput: React.FC = ({ stopPropagation: () => {}, } as React.ChangeEvent; - onChange(syntheticEvent); + // NumericFormat provides sourceInfo.source to indicate whether the change was triggered by: + // - 'event' - user typing in *this* input field + // - 'prop' - value changed from props, e.g. due to autofill + const eventType = + sourceInfo.source === 'event' ? ChangeType.User : ChangeType.System; + onChange(syntheticEvent, eventType); }; return ( diff --git a/src/modules/form/components/DynamicField.tsx b/src/modules/form/components/DynamicField.tsx index f7bfbb9cd..6916a58c1 100644 --- a/src/modules/form/components/DynamicField.tsx +++ b/src/modules/form/components/DynamicField.tsx @@ -100,8 +100,11 @@ const DynamicField: React.FC = ({ ? undefined : formValue; const onChangeEvent = useCallback( - (e: React.ChangeEvent) => - itemChanged({ linkId, value: e.target.value, type: ChangeType.User }), + ( + e: React.ChangeEvent, + // Accept a ChangeType arg. Default to User if not provided + eventType: ChangeType = ChangeType.User + ) => itemChanged({ linkId, value: e.target.value, type: eventType }), [linkId, itemChanged] ); const onChangeValue = useCallback( From 60ffd875bb12c1edd0021ae5ac9ad7dff4d3c645 Mon Sep 17 00:00:00 2001 From: martha Date: Thu, 5 Mar 2026 15:16:07 +0100 Subject: [PATCH 2/2] Reset all autofill fields on clear, whether readonly or not --- .../form/hooks/rhf/useDynamicFieldAutofillValue.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/modules/form/hooks/rhf/useDynamicFieldAutofillValue.tsx b/src/modules/form/hooks/rhf/useDynamicFieldAutofillValue.tsx index 3841b6269..2bbf91090 100644 --- a/src/modules/form/hooks/rhf/useDynamicFieldAutofillValue.tsx +++ b/src/modules/form/hooks/rhf/useDynamicFieldAutofillValue.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react'; import { useDynamicFieldWatchValues } from '@/modules/form/hooks/rhf/useDynamicFieldWatchValues'; import useDynamicFormContext from '@/modules/form/hooks/useDynamicFormContext'; import { autofillValues } from '@/modules/form/util/formUtil'; -import { FormItem, ItemType } from '@/types/gqlTypes'; +import { FormItem } from '@/types/gqlTypes'; interface AutoFillValueResult { value: any; @@ -32,15 +32,11 @@ export const useDynamicFieldAutofillValue = ( }); /** - For read-only items that are displayed on editable forms, the autofill value should nullify when its conditions are no longer met. - For example, a read-only field showing the sum of 2 input fields should be cleared if the inputs are cleared. + For items that are displayed on editable forms, the autofill value should nullify when its conditions are no longer met. + For example, a field showing the sum of 2 input fields should be cleared if the inputs are cleared. (In that example, we assume the autofill rule for summing has an autofill_when condition requiring the inputs to be present) **/ - if ( - !result && - (item.readOnly || item.type === ItemType.Display) && - !viewOnly - ) { + if (!result && !viewOnly) { return { value: null }; }