Form focus (#7008)

* Add 'focus' property for ApiFormProps

* Attempt to focus on particular field

* Improve setFocus hook

- Set the focus at the appropriate time
- Auto focus on first field if not specified

* Add custom focus field

* Update useEffect property list
This commit is contained in:
Oliver 2024-04-15 14:42:34 +10:00 committed by GitHub
parent 40e867896b
commit 36c00803b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 54 additions and 21 deletions

View File

@ -66,6 +66,7 @@ export interface ApiFormProps {
pathParams?: PathParams;
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
fields?: ApiFormFieldSet;
focus?: string;
initialData?: FieldValues;
submitText?: string;
submitColor?: string;
@ -292,7 +293,48 @@ export function ApiForm({
});
initialDataQuery.refetch();
}
}, []);
}, [props.fetchInitialData]);
const isLoading = useMemo(
() =>
isFormLoading ||
initialDataQuery.isFetching ||
optionsLoading ||
isSubmitting ||
!props.fields,
[
isFormLoading,
initialDataQuery.isFetching,
isSubmitting,
props.fields,
optionsLoading
]
);
const [initialFocus, setInitialFocus] = useState<string>('');
// Update field focus when the form is loaded
useEffect(() => {
let focusField = props.focus ?? '';
if (!focusField) {
// If a focus field is not specified, then focus on the first available field
Object.entries(props.fields ?? {}).forEach(([fieldName, field]) => {
if (focusField || field.read_only || field.disabled || field.hidden) {
return;
}
focusField = fieldName;
});
}
if (isLoading || initialFocus == focusField) {
return;
}
form.setFocus(focusField);
setInitialFocus(focusField);
}, [props.focus, props.fields, form.setFocus, isLoading, initialFocus]);
const submitForm: SubmitHandler<FieldValues> = async (data) => {
setNonFieldErrors([]);
@ -392,22 +434,6 @@ export function ApiForm({
});
};
const isLoading = useMemo(
() =>
isFormLoading ||
initialDataQuery.isFetching ||
optionsLoading ||
isSubmitting ||
!props.fields,
[
isFormLoading,
initialDataQuery.isFetching,
isSubmitting,
props.fields,
optionsLoading
]
);
const onFormError = useCallback<SubmitErrorHandler<FieldValues>>(() => {
props.onFormError?.();
}, [props.onFormError]);

View File

@ -188,7 +188,7 @@ export function ApiFormField({
return (
<TextInput
{...reducedDefinition}
ref={ref}
ref={field.ref}
id={fieldId}
type={definition.field_type}
value={value || ''}
@ -226,7 +226,7 @@ export function ApiFormField({
<NumberInput
{...reducedDefinition}
radius="sm"
ref={ref}
ref={field.ref}
id={fieldId}
value={numericalValue}
error={error?.message}
@ -256,7 +256,7 @@ export function ApiFormField({
<FileInput
{...reducedDefinition}
id={fieldId}
ref={ref}
ref={field.ref}
radius="sm"
value={value}
error={error?.message}

View File

@ -52,6 +52,7 @@ export default function DateField({
<DateInput
id={fieldId}
radius="sm"
ref={field.ref}
type={undefined}
error={error?.message}
value={dateValue}

View File

@ -269,6 +269,7 @@ export function RelatedModelField({
<Select
id={fieldId}
value={currentValue}
ref={field.ref}
options={data}
filterOption={null}
onInputChange={(value: any) => {

View File

@ -132,6 +132,7 @@ export default function ParametricPartTable({
url: ApiEndpoints.part_parameter_list,
title: t`Add Part Parameter`,
fields: partParameterFields,
focus: 'data',
onFormSuccess: (parameter: any) => {
updateParameterRecord(selectedPart, parameter);
},
@ -146,6 +147,7 @@ export default function ParametricPartTable({
title: t`Edit Part Parameter`,
pk: selectedParameter,
fields: partParameterFields,
focus: 'data',
onFormSuccess: (parameter: any) => {
updateParameterRecord(selectedPart, parameter);
}

View File

@ -95,7 +95,9 @@ export function PartParameterTable({ partId }: { partId: any }) {
const partParameterFields: ApiFormFieldSet = useMemo(() => {
return {
part: {},
part: {
disabled: true
},
template: {},
data: {}
};
@ -105,6 +107,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
url: ApiEndpoints.part_parameter_list,
title: t`New Part Parameter`,
fields: partParameterFields,
focus: 'template',
initialData: {
part: partId
},