diff --git a/frontend/.changeset/shaky-sites-think.md b/frontend/.changeset/shaky-sites-think.md
new file mode 100644
index 00000000..3e4279f8
--- /dev/null
+++ b/frontend/.changeset/shaky-sites-think.md
@@ -0,0 +1,5 @@
+---
+'pydantic-forms': patch
+---
+
+Adds default value for required array fields. Disables descendants of disabled fields
diff --git a/frontend/packages/pydantic-forms/src/components/fields/ArrayField.tsx b/frontend/packages/pydantic-forms/src/components/fields/ArrayField.tsx
index bc317a6e..a94d01e8 100644
--- a/frontend/packages/pydantic-forms/src/components/fields/ArrayField.tsx
+++ b/frontend/packages/pydantic-forms/src/components/fields/ArrayField.tsx
@@ -4,13 +4,14 @@ import { useFieldArray } from 'react-hook-form';
import { usePydanticFormContext } from '@/core';
import { fieldToComponentMatcher } from '@/core/helper';
import { PydanticFormElementProps } from '@/types';
-import { itemizeArrayItem } from '@/utils';
+import { disableField, itemizeArrayItem } from '@/utils';
import { RenderFields } from '../render';
export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
const { rhf, config } = usePydanticFormContext();
+ const disabled = pydanticFormField.attributes?.disabled || false;
const { control } = rhf;
const { id: arrayName, arrayItem } = pydanticFormField;
const { fields, append, remove } = useFieldArray({
@@ -28,7 +29,12 @@ export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
);
const renderField = (field: Record<'id', string>, index: number) => {
- const arrayItemField = itemizeArrayItem(index, arrayItem, arrayName);
+ const itemizedField = itemizeArrayItem(index, arrayItem, arrayName);
+ // We have decided - for now - on the convention that all descendants of disabled fields will be disabled as well
+ // so we will not displaying any interactive elements inside a disabled element
+ const arrayItemField = disabled
+ ? disableField(itemizedField)
+ : itemizedField;
return (
{
]}
extraTriggerFields={[arrayName]}
/>
- {(!minItems || (minItems && fields.length > minItems)) && (
- {
- remove(index);
- }}
- >
- -
-
- )}
+ {(!minItems || (minItems && fields.length > minItems)) &&
+ !disabled && (
+ {
+ remove(index);
+ }}
+ >
+ -
+
+ )}
);
};
@@ -66,7 +73,7 @@ export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
{
}}
>
{fields.map(renderField)}
- {(!maxItems || (maxItems && fields.length < maxItems)) && (
-
{
- append({
- [arrayName]: arrayItem.default,
- });
- }}
- style={{
- cursor: 'pointer',
- fontSize: '32px',
- display: 'flex',
- justifyContent: 'end',
- marginTop: '8px',
- marginBottom: '8px',
- padding: '16px',
- }}
- >
- +
-
- )}
+ {(!maxItems || (maxItems && fields.length < maxItems)) &&
+ !disabled && (
+
{
+ append({
+ [arrayName]: arrayItem.default,
+ });
+ }}
+ style={{
+ cursor: 'pointer',
+ fontSize: '32px',
+ display: 'flex',
+ justifyContent: 'end',
+ marginTop: '8px',
+ marginBottom: '8px',
+ padding: '16px',
+ }}
+ >
+ +
+
+ )}
);
};
diff --git a/frontend/packages/pydantic-forms/src/components/fields/ObjectField.tsx b/frontend/packages/pydantic-forms/src/components/fields/ObjectField.tsx
index b2faa78d..e826d9ca 100644
--- a/frontend/packages/pydantic-forms/src/components/fields/ObjectField.tsx
+++ b/frontend/packages/pydantic-forms/src/components/fields/ObjectField.tsx
@@ -3,6 +3,7 @@ import React from 'react';
import { usePydanticFormContext } from '@/core';
import { getPydanticFormComponents } from '@/core/helper';
import { PydanticFormElementProps } from '@/types';
+import { disableField } from '@/utils';
import { RenderFields } from '../render';
@@ -10,11 +11,22 @@ export const ObjectField = ({
pydanticFormField,
}: PydanticFormElementProps) => {
const { config } = usePydanticFormContext();
+ const disabled = pydanticFormField.attributes?.disabled || false;
const components = getPydanticFormComponents(
pydanticFormField.properties || {},
config?.componentMatcherExtender,
);
+ // We have decided - for now - on the convention that all descendants of disabled fields will be disabled as well
+ // so we will not displaying any interactive elements inside a disabled element
+ if (disabled) {
+ components.forEach((component) => {
+ component.pydanticFormField = disableField(
+ component.pydanticFormField,
+ );
+ });
+ }
+
return (
{
});
});
- it('Returns empty object if arrayItem and array both have no default values', () => {
- // When an array fields has no default value the default value should be taken from the arrayItem
+ it('Returns empty object if arrayItem and array both have no default values and the array is not required', () => {
+ // When an array field has no default value the default value and the arrayItem doesn't either we assume an empty array
const properties: Properties = {
test: getMockPydanticFormField({
id: 'test',
@@ -724,11 +724,28 @@ describe('getFormValuesFromFieldOrLabels', () => {
id: 'nestedField',
type: PydanticFormFieldType.STRING,
}),
+ required: false,
}),
};
expect(getFormValuesFromFieldOrLabels(properties)).toEqual({});
});
-
+ it('Returns empty array if arrayItem and array both have no default values and the array is required', () => {
+ // When an array field has no default value the default value and the arrayItem doesn't either we assume an empty array
+ const properties: Properties = {
+ test: getMockPydanticFormField({
+ id: 'test',
+ type: PydanticFormFieldType.ARRAY,
+ arrayItem: getMockPydanticFormField({
+ id: 'nestedField',
+ type: PydanticFormFieldType.STRING,
+ }),
+ required: true,
+ }),
+ };
+ expect(getFormValuesFromFieldOrLabels(properties)).toEqual({
+ test: [],
+ });
+ });
it('Returns empty object if object field and properties both have no default values', () => {
// When an array fields has no default value the default value should be taken from the arrayItem
const properties: Properties = {
diff --git a/frontend/packages/pydantic-forms/src/core/helper.ts b/frontend/packages/pydantic-forms/src/core/helper.ts
index 0d7402d3..1e149900 100644
--- a/frontend/packages/pydantic-forms/src/core/helper.ts
+++ b/frontend/packages/pydantic-forms/src/core/helper.ts
@@ -385,10 +385,16 @@ export const getFormValuesFromFieldOrLabels = (
labelData,
componentMatcherExtender,
);
+
if (objectHasProperties(arrayItemDefault)) {
fieldValues[pydanticFormField.id] = [
arrayItemDefault[arrayItem.id],
];
+ } else if (pydanticFormField.required) {
+ // This is somewhat of a special case.
+ // It deals with the situation where an array is marked required but has no default value.
+ // Not setting the value here would require a user to select and then unselect an item if they want to send an empty array
+ fieldValues[pydanticFormField.id] = [];
}
}
} else if (hasDefaultValue(defaultFieldValue)) {
diff --git a/frontend/packages/pydantic-forms/src/utils.spec.ts b/frontend/packages/pydantic-forms/src/utils.spec.ts
index a91d4c02..307c9404 100644
--- a/frontend/packages/pydantic-forms/src/utils.spec.ts
+++ b/frontend/packages/pydantic-forms/src/utils.spec.ts
@@ -3,6 +3,7 @@ import type { FieldValues } from 'react-hook-form';
import { getMockPydanticFormField } from './core/helper.spec';
import { PydanticFormFieldType } from './types';
import {
+ disableField,
getFormFieldIdWithPath,
getFormFieldValue,
insertItemAtIndex,
@@ -374,3 +375,13 @@ describe('itemizeArrayItem', () => {
}
});
});
+
+describe('disableField', () => {
+ it('Disables the field by setting the disabled attribute to true', () => {
+ const field = getMockPydanticFormField({
+ attributes: { disabled: false },
+ });
+ const disabledField = disableField(field);
+ expect(disabledField.attributes?.disabled).toBe(true);
+ });
+});
diff --git a/frontend/packages/pydantic-forms/src/utils.ts b/frontend/packages/pydantic-forms/src/utils.ts
index 7819f7f2..8f0678f6 100644
--- a/frontend/packages/pydantic-forms/src/utils.ts
+++ b/frontend/packages/pydantic-forms/src/utils.ts
@@ -43,6 +43,18 @@ export const itemizeArrayItem = (
};
};
+export const disableField = (
+ pydanticFormField: PydanticFormField,
+): PydanticFormField => {
+ return {
+ ...pydanticFormField,
+ attributes: {
+ ...pydanticFormField.attributes,
+ disabled: true,
+ },
+ };
+};
+
/**
* Determines how many parts to slice from the PydanticFormField's id.
* If the last segment is a number we conclude it's an array item and it returns 2 (to slice off the index and the field name).